using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.Audio;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace LocalMcp.UnityServer
{
/// <summary>
/// Audio Control APIs for Unity MCP Server.
/// Provides AudioSource, AudioClip, AudioMixer, and AudioListener management.
/// Available in Public plan.
/// </summary>
public static class AudioController
{
#region Parameter Classes
[Serializable]
public class CreateSourceParams
{
public string gameObjectPath;
public string gameObjectName;
public string clipPath;
public float volume = 1f;
public float pitch = 1f;
public bool loop = false;
public bool playOnAwake = false;
public float spatialBlend = 0f;
public float minDistance = 1f;
public float maxDistance = 500f;
}
[Serializable]
public class DeleteSourceParams
{
public string gameObjectPath;
}
[Serializable]
public class PlayParams
{
public string gameObjectPath;
public bool fromBeginning = true;
}
[Serializable]
public class StopParams
{
public string gameObjectPath;
}
[Serializable]
public class PauseParams
{
public string gameObjectPath;
}
[Serializable]
public class SetClipParams
{
public string gameObjectPath;
public string clipPath;
}
[Serializable]
public class SetPropertyParams
{
public string gameObjectPath;
// 単一プロパティ設定用
public string propertyName;
public float value = float.MinValue;
public bool boolValue;
// バッチ設定用
public float volume = -1f;
public float pitch = -1f;
public bool? loop;
public bool? playOnAwake;
public float spatialBlend = -1f;
public float minDistance = -1f;
public float maxDistance = -1f;
public int priority = -1;
public float panStereo = -2f;
public float dopplerLevel = -1f;
public float spread = -1f;
public string rolloffMode;
public string mixerGroupPath;
}
[Serializable]
public class GetInfoParams
{
public string gameObjectPath;
}
[Serializable]
public class ListSourcesParams
{
public bool includeInactive = false;
}
[Serializable]
public class LoadMixerParams
{
public string mixerPath;
}
[Serializable]
public class SetMixerFloatParams
{
public string mixerPath;
public string parameterName;
public float value;
}
[Serializable]
public class GetMixerFloatParams
{
public string mixerPath;
public string parameterName;
}
[Serializable]
public class ListMixerGroupsParams
{
public string mixerPath;
}
[Serializable]
public class ListClipsParams
{
public string folder;
}
[Serializable]
public class GetClipInfoParams
{
public string clipPath;
}
[Serializable]
public class ImportClipParams
{
public string sourcePath;
public string destinationPath;
}
[Serializable]
public class CreateListenerParams
{
public string gameObjectPath;
public string gameObjectName;
}
[Serializable]
public class DeleteListenerParams
{
public string gameObjectPath;
}
[Serializable]
public class ListListenersParams
{
public bool includeInactive = false;
}
[Serializable]
public class GetListenerInfoParams
{
public string gameObjectPath;
}
[Serializable]
public class SetListenerPropertyParams
{
public string gameObjectPath;
public string propertyName;
public bool value;
public bool? enabled;
}
// ============ AudioMixer Extended Parameter Classes ============
[Serializable]
public class CreateMixerGroupParams
{
public string mixerPath;
public string groupName;
public string parentGroupName;
}
[Serializable]
public class DeleteMixerGroupParams
{
public string mixerPath;
public string groupName;
}
[Serializable]
public class RenameMixerGroupParams
{
public string mixerPath;
public string groupName;
public string newName;
}
[Serializable]
public class SetGroupVolumeParams
{
public string mixerPath;
public string groupName;
public float volumeDb;
}
[Serializable]
public class SetGroupOutputParams
{
public string mixerPath;
public string groupName;
public string targetGroupName;
}
[Serializable]
public class AddEffectParams
{
public string mixerPath;
public string groupName;
public string effectType;
public int insertIndex = -1;
}
[Serializable]
public class RemoveEffectParams
{
public string mixerPath;
public string groupName;
public int effectIndex;
}
[Serializable]
public class MoveEffectParams
{
public string mixerPath;
public string groupName;
public int fromIndex;
public int toIndex;
}
[Serializable]
public class ListEffectsParams
{
public string mixerPath;
public string groupName;
}
[Serializable]
public class SetEffectParameterParams
{
public string mixerPath;
public string groupName;
public int effectIndex;
public string parameterName;
public float value;
}
[Serializable]
public class GetEffectParameterParams
{
public string mixerPath;
public string groupName;
public int effectIndex;
public string parameterName;
}
[Serializable]
public class ListEffectTypesParams
{
}
[Serializable]
public class CreateSnapshotParams
{
public string mixerPath;
public string snapshotName;
}
[Serializable]
public class DeleteSnapshotParams
{
public string mixerPath;
public string snapshotName;
}
[Serializable]
public class ListSnapshotsParams
{
public string mixerPath;
}
[Serializable]
public class TransitionToSnapshotParams
{
public string mixerPath;
public string snapshotName;
public float transitionTime = 0f;
}
[Serializable]
public class AddSendParams
{
public string mixerPath;
public string sourceGroupName;
public string targetGroupName;
public float sendLevel = 0f;
}
[Serializable]
public class SetSendLevelParams
{
public string mixerPath;
public string sourceGroupName;
public int sendIndex;
public float sendLevel;
}
#endregion
#region Result Classes
[Serializable]
public class AudioSourceResult
{
public string status;
public string message;
public string gameObjectPath;
public int instanceId;
}
[Serializable]
public class AudioSourceInfo
{
public string name;
public string path;
public int instanceId;
public string clipName;
public string clipPath;
public float volume;
public float pitch;
public bool loop;
public bool playOnAwake;
public float spatialBlend;
public float minDistance;
public float maxDistance;
public int priority;
public float panStereo;
public float dopplerLevel;
public float spread;
public string rolloffMode;
public string mixerGroup;
public bool isPlaying;
public float time;
public bool enabled;
}
[Serializable]
public class ListSourcesResult
{
public string status;
public int count;
public AudioSourceInfo[] sources;
}
[Serializable]
public class AudioClipInfo
{
public string name;
public string path;
public float length;
public int channels;
public int frequency;
public int samples;
public bool loadInBackground;
public string loadType;
public bool ambisonic;
}
[Serializable]
public class ListClipsResult
{
public string status;
public int count;
public AudioClipInfo[] clips;
}
[Serializable]
public class MixerInfo
{
public string name;
public string path;
public string[] groups;
public string[] exposedParameters;
}
[Serializable]
public class MixerFloatResult
{
public string status;
public string parameterName;
public float value;
}
[Serializable]
public class AudioListenerInfo
{
public string name;
public string path;
public int instanceId;
public bool enabled;
public float velocityUpdateMode;
}
[Serializable]
public class ListListenersResult
{
public string status;
public int count;
public string warning;
public AudioListenerInfo[] listeners;
}
// ============ AudioMixer Extended Result Classes ============
[Serializable]
public class MixerGroupResult
{
public string status;
public string message;
public string groupName;
public string mixerPath;
}
[Serializable]
public class MixerGroupInfo
{
public string name;
public string parentName;
public float volume;
public bool solo;
public bool mute;
public bool bypassEffects;
public int effectCount;
}
[Serializable]
public class EffectInfo
{
public int index;
public string effectName;
public bool bypass;
public string[] parameterNames;
}
[Serializable]
public class ListEffectsResult
{
public string status;
public string groupName;
public int count;
public EffectInfo[] effects;
}
[Serializable]
public class EffectParameterResult
{
public string status;
public string parameterName;
public float value;
}
[Serializable]
public class SnapshotInfo
{
public string name;
public bool isStartSnapshot;
}
[Serializable]
public class ListSnapshotsResult
{
public string status;
public int count;
public SnapshotInfo[] snapshots;
}
#endregion
#region AudioSource API Methods
/// <summary>
/// Create an AudioSource component on a GameObject.
/// API: unity.audio.createSource
/// </summary>
public static string CreateSource(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<CreateSourceParams>(paramsJson ?? "{}");
GameObject targetGo;
if (!string.IsNullOrEmpty(p.gameObjectPath))
{
targetGo = GameObject.Find(p.gameObjectPath);
if (targetGo == null)
{
return JsonRpcResponseHelper.ErrorMessage($"GameObject not found: {p.gameObjectPath}", id);
}
}
else
{
string goName = string.IsNullOrEmpty(p.gameObjectName) ? "AudioSource" : p.gameObjectName;
targetGo = new GameObject(goName);
Undo.RegisterCreatedObjectUndo(targetGo, "Create AudioSource GameObject");
}
var existingSource = targetGo.GetComponent<AudioSource>();
if (existingSource != null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioSource already exists on: {GetGameObjectPath(targetGo)}", id);
}
var audioSource = Undo.AddComponent<AudioSource>(targetGo);
audioSource.volume = p.volume;
audioSource.pitch = p.pitch;
audioSource.loop = p.loop;
audioSource.playOnAwake = p.playOnAwake;
audioSource.spatialBlend = p.spatialBlend;
audioSource.minDistance = p.minDistance;
audioSource.maxDistance = p.maxDistance;
if (!string.IsNullOrEmpty(p.clipPath))
{
var clip = AssetDatabase.LoadAssetAtPath<AudioClip>(p.clipPath);
if (clip != null)
{
audioSource.clip = clip;
}
}
EditorUtility.SetDirty(targetGo);
var result = new AudioSourceResult
{
status = "success",
message = "AudioSource created",
gameObjectPath = GetGameObjectPath(targetGo),
instanceId = targetGo.GetInstanceID()
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.CreateSource error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to create AudioSource: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("CreateSource requires Unity Editor", id);
#endif
}
/// <summary>
/// Delete an AudioSource component from a GameObject.
/// API: unity.audio.deleteSource
/// </summary>
public static string DeleteSource(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<DeleteSourceParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.gameObjectPath))
{
return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id);
}
var audioSource = FindAudioSource(p.gameObjectPath);
if (audioSource == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioSource not found on: {p.gameObjectPath}", id);
}
string path = GetGameObjectPath(audioSource.gameObject);
Undo.DestroyObjectImmediate(audioSource);
var result = new AudioSourceResult
{
status = "success",
message = "AudioSource deleted",
gameObjectPath = path
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.DeleteSource error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to delete AudioSource: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("DeleteSource requires Unity Editor", id);
#endif
}
/// <summary>
/// Play an AudioSource.
/// API: unity.audio.play
/// </summary>
public static string Play(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<PlayParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.gameObjectPath))
{
return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id);
}
var audioSource = FindAudioSource(p.gameObjectPath);
if (audioSource == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioSource not found on: {p.gameObjectPath}", id);
}
if (p.fromBeginning)
{
audioSource.time = 0f;
}
audioSource.Play();
var result = new AudioSourceResult
{
status = "success",
message = "AudioSource playing",
gameObjectPath = GetGameObjectPath(audioSource.gameObject)
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.Play error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to play AudioSource: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("Play requires Unity Editor", id);
#endif
}
/// <summary>
/// Stop an AudioSource.
/// API: unity.audio.stop
/// </summary>
public static string Stop(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<StopParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.gameObjectPath))
{
return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id);
}
var audioSource = FindAudioSource(p.gameObjectPath);
if (audioSource == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioSource not found on: {p.gameObjectPath}", id);
}
audioSource.Stop();
var result = new AudioSourceResult
{
status = "success",
message = "AudioSource stopped",
gameObjectPath = GetGameObjectPath(audioSource.gameObject)
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.Stop error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to stop AudioSource: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("Stop requires Unity Editor", id);
#endif
}
/// <summary>
/// Pause an AudioSource.
/// API: unity.audio.pause
/// </summary>
public static string Pause(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<PauseParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.gameObjectPath))
{
return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id);
}
var audioSource = FindAudioSource(p.gameObjectPath);
if (audioSource == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioSource not found on: {p.gameObjectPath}", id);
}
audioSource.Pause();
var result = new AudioSourceResult
{
status = "success",
message = "AudioSource paused",
gameObjectPath = GetGameObjectPath(audioSource.gameObject)
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.Pause error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to pause AudioSource: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("Pause requires Unity Editor", id);
#endif
}
/// <summary>
/// Set the AudioClip of an AudioSource.
/// API: unity.audio.setClip
/// </summary>
public static string SetClip(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<SetClipParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.gameObjectPath))
{
return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id);
}
if (string.IsNullOrEmpty(p.clipPath))
{
return JsonRpcResponseHelper.InvalidParams("clipPath is required", id);
}
var audioSource = FindAudioSource(p.gameObjectPath);
if (audioSource == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioSource not found on: {p.gameObjectPath}", id);
}
var clip = AssetDatabase.LoadAssetAtPath<AudioClip>(p.clipPath);
if (clip == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioClip not found: {p.clipPath}", id);
}
Undo.RecordObject(audioSource, "Set AudioClip");
audioSource.clip = clip;
EditorUtility.SetDirty(audioSource);
var result = new AudioSourceResult
{
status = "success",
message = $"AudioClip set to: {clip.name}",
gameObjectPath = GetGameObjectPath(audioSource.gameObject)
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.SetClip error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to set AudioClip: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("SetClip requires Unity Editor", id);
#endif
}
/// <summary>
/// Set properties of an AudioSource.
/// API: unity.audio.setProperty
/// </summary>
public static string SetProperty(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<SetPropertyParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.gameObjectPath))
{
return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id);
}
var audioSource = FindAudioSource(p.gameObjectPath);
if (audioSource == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioSource not found on: {p.gameObjectPath}", id);
}
Undo.RecordObject(audioSource, "Set AudioSource Properties");
// 単一プロパティ設定(propertyName/value形式)
if (!string.IsNullOrEmpty(p.propertyName))
{
switch (p.propertyName.ToLower())
{
case "volume":
audioSource.volume = Mathf.Clamp01(p.value);
break;
case "pitch":
audioSource.pitch = Mathf.Clamp(p.value, -3f, 3f);
break;
case "loop":
audioSource.loop = p.boolValue || p.value > 0.5f;
break;
case "playonawake":
audioSource.playOnAwake = p.boolValue || p.value > 0.5f;
break;
case "spatialblend":
audioSource.spatialBlend = Mathf.Clamp01(p.value);
break;
case "mindistance":
audioSource.minDistance = Mathf.Max(0f, p.value);
break;
case "maxdistance":
audioSource.maxDistance = Mathf.Max(0f, p.value);
break;
case "priority":
audioSource.priority = Mathf.Clamp((int)p.value, 0, 256);
break;
case "panstereo":
audioSource.panStereo = Mathf.Clamp(p.value, -1f, 1f);
break;
case "dopplerlevel":
audioSource.dopplerLevel = Mathf.Clamp(p.value, 0f, 5f);
break;
case "spread":
audioSource.spread = Mathf.Clamp(p.value, 0f, 360f);
break;
default:
return JsonRpcResponseHelper.ErrorMessage($"Unknown property: {p.propertyName}", id);
}
}
else
{
// バッチ設定(複数プロパティ同時設定)
if (p.volume >= 0f) audioSource.volume = Mathf.Clamp01(p.volume);
if (p.pitch >= -3f && p.pitch != -1f) audioSource.pitch = Mathf.Clamp(p.pitch, -3f, 3f);
if (p.loop.HasValue) audioSource.loop = p.loop.Value;
if (p.playOnAwake.HasValue) audioSource.playOnAwake = p.playOnAwake.Value;
if (p.spatialBlend >= 0f) audioSource.spatialBlend = Mathf.Clamp01(p.spatialBlend);
if (p.minDistance >= 0f) audioSource.minDistance = p.minDistance;
if (p.maxDistance >= 0f) audioSource.maxDistance = p.maxDistance;
if (p.priority >= 0) audioSource.priority = Mathf.Clamp(p.priority, 0, 256);
if (p.panStereo >= -1f && p.panStereo != -2f) audioSource.panStereo = Mathf.Clamp(p.panStereo, -1f, 1f);
if (p.dopplerLevel >= 0f) audioSource.dopplerLevel = Mathf.Clamp(p.dopplerLevel, 0f, 5f);
if (p.spread >= 0f) audioSource.spread = Mathf.Clamp(p.spread, 0f, 360f);
if (!string.IsNullOrEmpty(p.rolloffMode))
{
if (Enum.TryParse<AudioRolloffMode>(p.rolloffMode, true, out var mode))
{
audioSource.rolloffMode = mode;
}
}
if (!string.IsNullOrEmpty(p.mixerGroupPath))
{
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerGroupPath);
if (mixer != null)
{
var groups = mixer.FindMatchingGroups("Master");
if (groups.Length > 0)
{
audioSource.outputAudioMixerGroup = groups[0];
}
}
}
}
EditorUtility.SetDirty(audioSource);
var result = new AudioSourceResult
{
status = "success",
message = "AudioSource properties updated",
gameObjectPath = GetGameObjectPath(audioSource.gameObject)
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.SetProperty error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to set AudioSource properties: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("SetProperty requires Unity Editor", id);
#endif
}
/// <summary>
/// Get information about an AudioSource.
/// API: unity.audio.getInfo
/// </summary>
public static string GetInfo(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<GetInfoParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.gameObjectPath))
{
return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id);
}
var audioSource = FindAudioSource(p.gameObjectPath);
if (audioSource == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioSource not found on: {p.gameObjectPath}", id);
}
var info = CreateAudioSourceInfo(audioSource);
var result = new { status = "success", source = info };
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.GetInfo error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to get AudioSource info: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("GetInfo requires Unity Editor", id);
#endif
}
/// <summary>
/// List all AudioSources in the scene.
/// API: unity.audio.list
/// </summary>
public static string ListSources(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<ListSourcesParams>(paramsJson ?? "{}");
var sources = p.includeInactive
? Resources.FindObjectsOfTypeAll<AudioSource>()
: UnityEngine.Object.FindObjectsOfType<AudioSource>();
var sourceInfos = new List<AudioSourceInfo>();
foreach (var source in sources)
{
if (source.gameObject.scene.isLoaded)
{
sourceInfos.Add(CreateAudioSourceInfo(source));
}
}
var result = new ListSourcesResult
{
status = "success",
count = sourceInfos.Count,
sources = sourceInfos.ToArray()
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.ListSources error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to list AudioSources: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("ListSources requires Unity Editor", id);
#endif
}
#endregion
#region AudioMixer API Methods
/// <summary>
/// Load an AudioMixer from the project.
/// API: unity.audio.loadMixer
/// </summary>
public static string LoadMixer(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<LoadMixerParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var groups = mixer.FindMatchingGroups(string.Empty);
var groupNames = new List<string>();
foreach (var g in groups)
{
groupNames.Add(g.name);
}
var exposedParams = new List<string>();
var so = new SerializedObject(mixer);
var exposedParamsProperty = so.FindProperty("m_ExposedParameters");
if (exposedParamsProperty != null)
{
for (int i = 0; i < exposedParamsProperty.arraySize; i++)
{
var param = exposedParamsProperty.GetArrayElementAtIndex(i);
var nameProp = param.FindPropertyRelative("name");
if (nameProp != null)
{
exposedParams.Add(nameProp.stringValue);
}
}
}
var info = new MixerInfo
{
name = mixer.name,
path = p.mixerPath,
groups = groupNames.ToArray(),
exposedParameters = exposedParams.ToArray()
};
var result = new { status = "success", mixer = info };
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.LoadMixer error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to load AudioMixer: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("LoadMixer requires Unity Editor", id);
#endif
}
/// <summary>
/// Set a float parameter on an AudioMixer.
/// API: unity.audio.setMixerFloat
/// </summary>
public static string SetMixerFloat(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<SetMixerFloatParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.parameterName))
{
return JsonRpcResponseHelper.InvalidParams("parameterName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
bool success = mixer.SetFloat(p.parameterName, p.value);
if (!success)
{
return JsonRpcResponseHelper.ErrorMessage($"Parameter not found or not exposed: {p.parameterName}", id);
}
var result = new MixerFloatResult
{
status = "success",
parameterName = p.parameterName,
value = p.value
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.SetMixerFloat error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to set mixer float: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("SetMixerFloat requires Unity Editor", id);
#endif
}
/// <summary>
/// Get a float parameter from an AudioMixer.
/// API: unity.audio.getMixerFloat
/// </summary>
public static string GetMixerFloat(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<GetMixerFloatParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.parameterName))
{
return JsonRpcResponseHelper.InvalidParams("parameterName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
float value;
bool success = mixer.GetFloat(p.parameterName, out value);
if (!success)
{
return JsonRpcResponseHelper.ErrorMessage($"Parameter not found or not exposed: {p.parameterName}", id);
}
var result = new MixerFloatResult
{
status = "success",
parameterName = p.parameterName,
value = value
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.GetMixerFloat error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to get mixer float: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("GetMixerFloat requires Unity Editor", id);
#endif
}
/// <summary>
/// List all groups in an AudioMixer.
/// API: unity.audio.listMixerGroups
/// </summary>
public static string ListMixerGroups(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<ListMixerGroupsParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var groups = mixer.FindMatchingGroups(string.Empty);
var groupInfos = new List<object>();
foreach (var g in groups)
{
groupInfos.Add(new { name = g.name, audioMixer = g.audioMixer.name });
}
var result = new { status = "success", count = groupInfos.Count, groups = groupInfos.ToArray() };
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.ListMixerGroups error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to list mixer groups: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("ListMixerGroups requires Unity Editor", id);
#endif
}
#endregion
#region AudioClip API Methods
/// <summary>
/// List all AudioClips in the project.
/// API: unity.audio.listClips
/// </summary>
public static string ListClips(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<ListClipsParams>(paramsJson ?? "{}");
string searchFolder = string.IsNullOrEmpty(p.folder) ? "Assets" : p.folder;
string[] guids = AssetDatabase.FindAssets("t:AudioClip", new[] { searchFolder });
var clipInfos = new List<AudioClipInfo>();
foreach (var guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
var clip = AssetDatabase.LoadAssetAtPath<AudioClip>(path);
if (clip != null)
{
clipInfos.Add(CreateAudioClipInfo(clip, path));
}
}
var result = new ListClipsResult
{
status = "success",
count = clipInfos.Count,
clips = clipInfos.ToArray()
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.ListClips error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to list AudioClips: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("ListClips requires Unity Editor", id);
#endif
}
/// <summary>
/// Get information about an AudioClip.
/// API: unity.audio.getClipInfo
/// </summary>
public static string GetClipInfo(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<GetClipInfoParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.clipPath))
{
return JsonRpcResponseHelper.InvalidParams("clipPath is required", id);
}
var clip = AssetDatabase.LoadAssetAtPath<AudioClip>(p.clipPath);
if (clip == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioClip not found: {p.clipPath}", id);
}
var info = CreateAudioClipInfo(clip, p.clipPath);
var result = new { status = "success", clip = info };
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.GetClipInfo error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to get AudioClip info: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("GetClipInfo requires Unity Editor", id);
#endif
}
/// <summary>
/// Import an audio file as an AudioClip.
/// API: unity.audio.importClip
/// </summary>
public static string ImportClip(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<ImportClipParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.sourcePath))
{
return JsonRpcResponseHelper.InvalidParams("sourcePath is required", id);
}
if (string.IsNullOrEmpty(p.destinationPath))
{
return JsonRpcResponseHelper.InvalidParams("destinationPath is required", id);
}
if (!System.IO.File.Exists(p.sourcePath))
{
return JsonRpcResponseHelper.ErrorMessage($"Source file not found: {p.sourcePath}", id);
}
string directory = System.IO.Path.GetDirectoryName(p.destinationPath);
if (!string.IsNullOrEmpty(directory) && !AssetDatabase.IsValidFolder(directory))
{
string[] folders = directory.Split('/');
string currentPath = folders[0];
for (int i = 1; i < folders.Length; i++)
{
string nextPath = currentPath + "/" + folders[i];
if (!AssetDatabase.IsValidFolder(nextPath))
{
AssetDatabase.CreateFolder(currentPath, folders[i]);
}
currentPath = nextPath;
}
}
System.IO.File.Copy(p.sourcePath, p.destinationPath, true);
AssetDatabase.ImportAsset(p.destinationPath);
var clip = AssetDatabase.LoadAssetAtPath<AudioClip>(p.destinationPath);
if (clip == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Failed to import audio file", id);
}
var info = CreateAudioClipInfo(clip, p.destinationPath);
var result = new { status = "success", message = "AudioClip imported", clip = info };
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.ImportClip error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to import AudioClip: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("ImportClip requires Unity Editor", id);
#endif
}
#endregion
#region AudioListener API Methods
/// <summary>
/// Create an AudioListener component on a GameObject.
/// API: unity.audio.createListener
/// </summary>
public static string CreateListener(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<CreateListenerParams>(paramsJson ?? "{}");
GameObject targetGo;
if (!string.IsNullOrEmpty(p.gameObjectPath))
{
targetGo = GameObject.Find(p.gameObjectPath);
if (targetGo == null)
{
return JsonRpcResponseHelper.ErrorMessage($"GameObject not found: {p.gameObjectPath}", id);
}
}
else if (!string.IsNullOrEmpty(p.gameObjectName))
{
targetGo = new GameObject(p.gameObjectName);
Undo.RegisterCreatedObjectUndo(targetGo, "Create AudioListener GameObject");
}
else
{
return JsonRpcResponseHelper.InvalidParams("gameObjectPath or gameObjectName is required", id);
}
var existingListener = targetGo.GetComponent<AudioListener>();
if (existingListener != null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioListener already exists on: {GetGameObjectPath(targetGo)}", id);
}
var existingListeners = UnityEngine.Object.FindObjectsOfType<AudioListener>();
string warning = null;
if (existingListeners.Length > 0)
{
warning = $"Warning: {existingListeners.Length} AudioListener(s) already exist in scene. Consider disabling others.";
}
var listener = Undo.AddComponent<AudioListener>(targetGo);
EditorUtility.SetDirty(targetGo);
var result = new
{
status = "success",
message = "AudioListener created",
warning = warning,
gameObjectPath = GetGameObjectPath(targetGo),
instanceId = targetGo.GetInstanceID()
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.CreateListener error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to create AudioListener: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("CreateListener requires Unity Editor", id);
#endif
}
/// <summary>
/// Delete an AudioListener component from a GameObject.
/// API: unity.audio.deleteListener
/// </summary>
public static string DeleteListener(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<DeleteListenerParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.gameObjectPath))
{
return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id);
}
var listener = FindAudioListener(p.gameObjectPath);
if (listener == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioListener not found on: {p.gameObjectPath}", id);
}
string path = GetGameObjectPath(listener.gameObject);
Undo.DestroyObjectImmediate(listener);
var result = new AudioSourceResult
{
status = "success",
message = "AudioListener deleted",
gameObjectPath = path
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.DeleteListener error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to delete AudioListener: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("DeleteListener requires Unity Editor", id);
#endif
}
/// <summary>
/// List all AudioListeners in the scene.
/// API: unity.audio.listListeners
/// </summary>
public static string ListListeners(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<ListListenersParams>(paramsJson ?? "{}");
var listeners = p.includeInactive
? Resources.FindObjectsOfTypeAll<AudioListener>()
: UnityEngine.Object.FindObjectsOfType<AudioListener>();
var listenerInfos = new List<AudioListenerInfo>();
int enabledCount = 0;
foreach (var listener in listeners)
{
if (listener.gameObject.scene.isLoaded)
{
listenerInfos.Add(CreateAudioListenerInfo(listener));
if (listener.enabled) enabledCount++;
}
}
string warning = null;
if (enabledCount > 1)
{
warning = $"Warning: {enabledCount} AudioListeners are enabled. Only one should be active at a time.";
}
else if (enabledCount == 0 && listenerInfos.Count > 0)
{
warning = "Warning: No AudioListener is currently enabled.";
}
var result = new ListListenersResult
{
status = "success",
count = listenerInfos.Count,
warning = warning,
listeners = listenerInfos.ToArray()
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.ListListeners error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to list AudioListeners: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("ListListeners requires Unity Editor", id);
#endif
}
/// <summary>
/// Get information about an AudioListener.
/// API: unity.audio.getListenerInfo
/// </summary>
public static string GetListenerInfo(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<GetListenerInfoParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.gameObjectPath))
{
return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id);
}
var listener = FindAudioListener(p.gameObjectPath);
if (listener == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioListener not found on: {p.gameObjectPath}", id);
}
var info = CreateAudioListenerInfo(listener);
var result = new { status = "success", listener = info };
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.GetListenerInfo error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to get AudioListener info: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("GetListenerInfo requires Unity Editor", id);
#endif
}
/// <summary>
/// Set properties of an AudioListener.
/// API: unity.audio.setListenerProperty
/// </summary>
public static string SetListenerProperty(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<SetListenerPropertyParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.gameObjectPath))
{
return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id);
}
var listener = FindAudioListener(p.gameObjectPath);
if (listener == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioListener not found on: {p.gameObjectPath}", id);
}
Undo.RecordObject(listener, "Set AudioListener Properties");
// propertyName/value形式
if (!string.IsNullOrEmpty(p.propertyName))
{
switch (p.propertyName.ToLower())
{
case "enabled":
listener.enabled = p.value;
break;
default:
return JsonRpcResponseHelper.ErrorMessage($"Unknown property: {p.propertyName}", id);
}
}
else if (p.enabled.HasValue)
{
listener.enabled = p.enabled.Value;
}
EditorUtility.SetDirty(listener);
var result = new AudioSourceResult
{
status = "success",
message = "AudioListener properties updated",
gameObjectPath = GetGameObjectPath(listener.gameObject)
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.SetListenerProperty error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to set AudioListener properties: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("SetListenerProperty requires Unity Editor", id);
#endif
}
#endregion
#region AudioMixer Extended API Methods
// ============ Reflection Helpers for Internal Unity APIs ============
private static Type _audioMixerControllerType;
private static Type _audioMixerGroupControllerType;
private static Type _audioMixerEffectControllerType;
private static Type _mixerEffectDefinitionsType;
private static Type GetAudioMixerControllerType()
{
if (_audioMixerControllerType == null)
{
_audioMixerControllerType = Type.GetType("UnityEditor.Audio.AudioMixerController, UnityEditor");
}
return _audioMixerControllerType;
}
private static Type GetAudioMixerGroupControllerType()
{
if (_audioMixerGroupControllerType == null)
{
_audioMixerGroupControllerType = Type.GetType("UnityEditor.Audio.AudioMixerGroupController, UnityEditor");
}
return _audioMixerGroupControllerType;
}
private static Type GetAudioMixerEffectControllerType()
{
if (_audioMixerEffectControllerType == null)
{
_audioMixerEffectControllerType = Type.GetType("UnityEditor.Audio.AudioMixerEffectController, UnityEditor");
}
return _audioMixerEffectControllerType;
}
private static Type GetMixerEffectDefinitionsType()
{
if (_mixerEffectDefinitionsType == null)
{
_mixerEffectDefinitionsType = Type.GetType("UnityEditor.Audio.MixerEffectDefinitions, UnityEditor");
}
return _mixerEffectDefinitionsType;
}
private static object GetMixerController(string mixerPath)
{
#if UNITY_EDITOR
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(mixerPath);
if (mixer == null) return null;
return mixer;
#else
return null;
#endif
}
private static object FindGroupController(object mixerController, string groupName)
{
#if UNITY_EDITOR
var controllerType = GetAudioMixerControllerType();
if (controllerType == null) return null;
var masterGroupProp = controllerType.GetProperty("masterGroup");
if (masterGroupProp == null) return null;
var masterGroup = masterGroupProp.GetValue(mixerController);
if (masterGroup == null) return null;
if (groupName == "Master" || string.IsNullOrEmpty(groupName))
{
return masterGroup;
}
return FindGroupRecursive(masterGroup, groupName);
#else
return null;
#endif
}
private static object FindGroupRecursive(object group, string targetName)
{
#if UNITY_EDITOR
var groupType = GetAudioMixerGroupControllerType();
if (groupType == null) return null;
var nameProp = groupType.GetProperty("name");
if (nameProp != null)
{
string name = nameProp.GetValue(group) as string;
if (name == targetName) return group;
}
var childrenProp = groupType.GetProperty("children");
if (childrenProp == null) return null;
var children = childrenProp.GetValue(group) as Array;
if (children == null) return null;
foreach (var child in children)
{
var found = FindGroupRecursive(child, targetName);
if (found != null) return found;
}
#endif
return null;
}
/// <summary>
/// Create a new AudioMixerGroup.
/// API: unity.audio.createMixerGroup
/// </summary>
public static string CreateMixerGroup(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<CreateMixerGroupParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.groupName))
{
return JsonRpcResponseHelper.InvalidParams("groupName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var controllerType = GetAudioMixerControllerType();
if (controllerType == null)
{
return JsonRpcResponseHelper.ErrorMessage("AudioMixerController type not found", id);
}
string parentName = string.IsNullOrEmpty(p.parentGroupName) ? "Master" : p.parentGroupName;
var parentGroup = FindGroupController(mixer, parentName);
if (parentGroup == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Parent group not found: {parentName}", id);
}
var createMethod = controllerType.GetMethod("CreateNewGroup", BindingFlags.Public | BindingFlags.Instance);
if (createMethod == null)
{
return JsonRpcResponseHelper.ErrorMessage("CreateNewGroup method not found", id);
}
Undo.RecordObject(mixer, "Create Mixer Group");
var newGroup = createMethod.Invoke(mixer, new object[] { p.groupName, false });
var addChildMethod = controllerType.GetMethod("AddChildToParent", BindingFlags.Public | BindingFlags.Instance);
if (addChildMethod != null)
{
addChildMethod.Invoke(mixer, new object[] { newGroup, parentGroup });
}
EditorUtility.SetDirty(mixer);
AssetDatabase.SaveAssets();
var result = new MixerGroupResult
{
status = "success",
message = $"Created group: {p.groupName}",
groupName = p.groupName,
mixerPath = p.mixerPath
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.CreateMixerGroup error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to create mixer group: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("CreateMixerGroup requires Unity Editor", id);
#endif
}
/// <summary>
/// Delete an AudioMixerGroup.
/// API: unity.audio.deleteMixerGroup
/// </summary>
public static string DeleteMixerGroup(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<DeleteMixerGroupParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.groupName))
{
return JsonRpcResponseHelper.InvalidParams("groupName is required", id);
}
if (p.groupName == "Master")
{
return JsonRpcResponseHelper.ErrorMessage("Cannot delete Master group", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var controllerType = GetAudioMixerControllerType();
var group = FindGroupController(mixer, p.groupName);
if (group == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Group not found: {p.groupName}", id);
}
Undo.RecordObject(mixer, "Delete Mixer Group");
var deleteMethod = controllerType.GetMethod("DeleteGroups", BindingFlags.Public | BindingFlags.Instance);
if (deleteMethod != null)
{
var groupArray = Array.CreateInstance(GetAudioMixerGroupControllerType(), 1);
groupArray.SetValue(group, 0);
deleteMethod.Invoke(mixer, new object[] { groupArray });
}
EditorUtility.SetDirty(mixer);
AssetDatabase.SaveAssets();
var result = new MixerGroupResult
{
status = "success",
message = $"Deleted group: {p.groupName}",
groupName = p.groupName,
mixerPath = p.mixerPath
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.DeleteMixerGroup error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to delete mixer group: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("DeleteMixerGroup requires Unity Editor", id);
#endif
}
/// <summary>
/// Rename an AudioMixerGroup.
/// API: unity.audio.renameMixerGroup
/// </summary>
public static string RenameMixerGroup(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<RenameMixerGroupParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.groupName))
{
return JsonRpcResponseHelper.InvalidParams("groupName is required", id);
}
if (string.IsNullOrEmpty(p.newName))
{
return JsonRpcResponseHelper.InvalidParams("newName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var group = FindGroupController(mixer, p.groupName);
if (group == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Group not found: {p.groupName}", id);
}
Undo.RecordObject(group as UnityEngine.Object, "Rename Mixer Group");
var groupType = GetAudioMixerGroupControllerType();
var nameProp = groupType.GetProperty("name");
if (nameProp != null && nameProp.CanWrite)
{
nameProp.SetValue(group, p.newName);
}
EditorUtility.SetDirty(mixer);
AssetDatabase.SaveAssets();
var result = new MixerGroupResult
{
status = "success",
message = $"Renamed group from '{p.groupName}' to '{p.newName}'",
groupName = p.newName,
mixerPath = p.mixerPath
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.RenameMixerGroup error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to rename mixer group: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("RenameMixerGroup requires Unity Editor", id);
#endif
}
/// <summary>
/// Set the volume of an AudioMixerGroup.
/// API: unity.audio.setGroupVolume
/// </summary>
public static string SetGroupVolume(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<SetGroupVolumeParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.groupName))
{
return JsonRpcResponseHelper.InvalidParams("groupName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var group = FindGroupController(mixer, p.groupName);
if (group == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Group not found: {p.groupName}", id);
}
var groupType = GetAudioMixerGroupControllerType();
var effectsProp = groupType.GetProperty("effects");
if (effectsProp == null)
{
return JsonRpcResponseHelper.ErrorMessage("Effects property not found", id);
}
var effects = effectsProp.GetValue(group) as Array;
if (effects == null || effects.Length == 0)
{
return JsonRpcResponseHelper.ErrorMessage("No effects found on group", id);
}
var attenuationEffect = effects.GetValue(0);
var effectType = GetAudioMixerEffectControllerType();
var setValueMethod = effectType.GetMethod("SetValueForMixLevel", BindingFlags.Public | BindingFlags.Instance);
if (setValueMethod != null)
{
Undo.RecordObject(mixer, "Set Group Volume");
var controllerType = GetAudioMixerControllerType();
var snapshotsProp = controllerType.GetProperty("snapshots");
var snapshots = snapshotsProp?.GetValue(mixer) as Array;
if (snapshots != null && snapshots.Length > 0)
{
var snapshot = snapshots.GetValue(0);
setValueMethod.Invoke(attenuationEffect, new object[] { mixer, snapshot, p.volumeDb });
}
}
EditorUtility.SetDirty(mixer);
var result = new MixerGroupResult
{
status = "success",
message = $"Set volume of '{p.groupName}' to {p.volumeDb} dB",
groupName = p.groupName,
mixerPath = p.mixerPath
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.SetGroupVolume error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to set group volume: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("SetGroupVolume requires Unity Editor", id);
#endif
}
/// <summary>
/// Set the output (parent) of an AudioMixerGroup.
/// API: unity.audio.setGroupOutput
/// </summary>
public static string SetGroupOutput(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<SetGroupOutputParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.groupName))
{
return JsonRpcResponseHelper.InvalidParams("groupName is required", id);
}
if (string.IsNullOrEmpty(p.targetGroupName))
{
return JsonRpcResponseHelper.InvalidParams("targetGroupName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var group = FindGroupController(mixer, p.groupName);
var targetGroup = FindGroupController(mixer, p.targetGroupName);
if (group == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Group not found: {p.groupName}", id);
}
if (targetGroup == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Target group not found: {p.targetGroupName}", id);
}
var controllerType = GetAudioMixerControllerType();
var reparentMethod = controllerType.GetMethod("ReparentSelection", BindingFlags.Public | BindingFlags.Instance);
if (reparentMethod != null)
{
Undo.RecordObject(mixer, "Reparent Mixer Group");
var groupArray = Array.CreateInstance(GetAudioMixerGroupControllerType(), 1);
groupArray.SetValue(group, 0);
reparentMethod.Invoke(mixer, new object[] { groupArray, targetGroup });
}
EditorUtility.SetDirty(mixer);
AssetDatabase.SaveAssets();
var result = new MixerGroupResult
{
status = "success",
message = $"Set output of '{p.groupName}' to '{p.targetGroupName}'",
groupName = p.groupName,
mixerPath = p.mixerPath
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.SetGroupOutput error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to set group output: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("SetGroupOutput requires Unity Editor", id);
#endif
}
/// <summary>
/// Add an effect to an AudioMixerGroup.
/// API: unity.audio.addEffect
/// </summary>
public static string AddEffect(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<AddEffectParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.groupName))
{
return JsonRpcResponseHelper.InvalidParams("groupName is required", id);
}
if (string.IsNullOrEmpty(p.effectType))
{
return JsonRpcResponseHelper.InvalidParams("effectType is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var group = FindGroupController(mixer, p.groupName);
if (group == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Group not found: {p.groupName}", id);
}
var controllerType = GetAudioMixerControllerType();
var effectType = GetAudioMixerEffectControllerType();
var createEffectMethod = effectType.GetConstructor(new Type[] { typeof(string) });
if (createEffectMethod == null)
{
createEffectMethod = effectType.GetConstructor(Type.EmptyTypes);
}
object newEffect;
if (createEffectMethod.GetParameters().Length > 0)
{
newEffect = createEffectMethod.Invoke(new object[] { p.effectType });
}
else
{
newEffect = createEffectMethod.Invoke(null);
var effectNameProp = effectType.GetProperty("effectName");
if (effectNameProp != null)
{
effectNameProp.SetValue(newEffect, p.effectType);
}
}
Undo.RecordObject(mixer, "Add Effect");
var groupType = GetAudioMixerGroupControllerType();
var effectsProp = groupType.GetProperty("effects");
var currentEffects = effectsProp?.GetValue(group) as Array;
int insertIdx = p.insertIndex >= 0 ? p.insertIndex : (currentEffects?.Length ?? 1);
var insertMethod = controllerType.GetMethod("InsertEffect", BindingFlags.Public | BindingFlags.Instance);
if (insertMethod != null)
{
insertMethod.Invoke(mixer, new object[] { newEffect, insertIdx });
}
EditorUtility.SetDirty(mixer);
AssetDatabase.SaveAssets();
var result = new
{
status = "success",
message = $"Added effect '{p.effectType}' to '{p.groupName}'",
effectType = p.effectType,
groupName = p.groupName,
mixerPath = p.mixerPath
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.AddEffect error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to add effect: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("AddEffect requires Unity Editor", id);
#endif
}
/// <summary>
/// Remove an effect from an AudioMixerGroup.
/// API: unity.audio.removeEffect
/// </summary>
public static string RemoveEffect(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<RemoveEffectParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.groupName))
{
return JsonRpcResponseHelper.InvalidParams("groupName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var group = FindGroupController(mixer, p.groupName);
if (group == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Group not found: {p.groupName}", id);
}
var groupType = GetAudioMixerGroupControllerType();
var effectsProp = groupType.GetProperty("effects");
var effects = effectsProp?.GetValue(group) as Array;
if (effects == null || p.effectIndex >= effects.Length)
{
return JsonRpcResponseHelper.ErrorMessage($"Effect index {p.effectIndex} is out of range", id);
}
if (p.effectIndex == 0)
{
return JsonRpcResponseHelper.ErrorMessage("Cannot remove Attenuation effect (index 0)", id);
}
var effect = effects.GetValue(p.effectIndex);
var controllerType = GetAudioMixerControllerType();
var removeMethod = controllerType.GetMethod("RemoveEffect", BindingFlags.Public | BindingFlags.Instance);
if (removeMethod != null)
{
Undo.RecordObject(mixer, "Remove Effect");
removeMethod.Invoke(mixer, new object[] { effect, group });
}
EditorUtility.SetDirty(mixer);
AssetDatabase.SaveAssets();
var result = new
{
status = "success",
message = $"Removed effect at index {p.effectIndex} from '{p.groupName}'",
groupName = p.groupName,
mixerPath = p.mixerPath
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.RemoveEffect error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to remove effect: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("RemoveEffect requires Unity Editor", id);
#endif
}
/// <summary>
/// Move an effect to a different position.
/// API: unity.audio.moveEffect
/// </summary>
public static string MoveEffect(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<MoveEffectParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.groupName))
{
return JsonRpcResponseHelper.InvalidParams("groupName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var group = FindGroupController(mixer, p.groupName);
if (group == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Group not found: {p.groupName}", id);
}
if (p.fromIndex == 0 || p.toIndex == 0)
{
return JsonRpcResponseHelper.ErrorMessage("Cannot move Attenuation effect (index 0)", id);
}
var groupType = GetAudioMixerGroupControllerType();
var effectsProp = groupType.GetProperty("effects");
var effects = effectsProp?.GetValue(group) as Array;
if (effects == null || p.fromIndex >= effects.Length || p.toIndex >= effects.Length)
{
return JsonRpcResponseHelper.ErrorMessage("Effect index out of range", id);
}
var effect = effects.GetValue(p.fromIndex);
var controllerType = GetAudioMixerControllerType();
var moveMethod = controllerType.GetMethod("MoveEffect", BindingFlags.Public | BindingFlags.Instance);
if (moveMethod != null)
{
Undo.RecordObject(mixer, "Move Effect");
moveMethod.Invoke(mixer, new object[] { p.fromIndex, p.toIndex, effect, group });
}
EditorUtility.SetDirty(mixer);
AssetDatabase.SaveAssets();
var result = new
{
status = "success",
message = $"Moved effect from index {p.fromIndex} to {p.toIndex}",
groupName = p.groupName,
mixerPath = p.mixerPath
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.MoveEffect error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to move effect: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("MoveEffect requires Unity Editor", id);
#endif
}
/// <summary>
/// List all effects on an AudioMixerGroup.
/// API: unity.audio.listEffects
/// </summary>
public static string ListEffects(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<ListEffectsParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.groupName))
{
return JsonRpcResponseHelper.InvalidParams("groupName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var group = FindGroupController(mixer, p.groupName);
if (group == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Group not found: {p.groupName}", id);
}
var groupType = GetAudioMixerGroupControllerType();
var effectsProp = groupType.GetProperty("effects");
var effects = effectsProp?.GetValue(group) as Array;
var effectInfos = new List<EffectInfo>();
var effectControllerType = GetAudioMixerEffectControllerType();
if (effects != null)
{
for (int i = 0; i < effects.Length; i++)
{
var effect = effects.GetValue(i);
var effectNameProp = effectControllerType.GetProperty("effectName");
var bypassProp = effectControllerType.GetProperty("bypass");
string effectName = effectNameProp?.GetValue(effect) as string ?? "Unknown";
bool bypass = bypassProp != null && (bool)bypassProp.GetValue(effect);
effectInfos.Add(new EffectInfo
{
index = i,
effectName = effectName,
bypass = bypass,
parameterNames = new string[0]
});
}
}
var result = new ListEffectsResult
{
status = "success",
groupName = p.groupName,
count = effectInfos.Count,
effects = effectInfos.ToArray()
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.ListEffects error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to list effects: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("ListEffects requires Unity Editor", id);
#endif
}
/// <summary>
/// Set a parameter on an effect.
/// API: unity.audio.setEffectParameter
/// </summary>
public static string SetEffectParameter(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<SetEffectParameterParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.groupName))
{
return JsonRpcResponseHelper.InvalidParams("groupName is required", id);
}
if (string.IsNullOrEmpty(p.parameterName))
{
return JsonRpcResponseHelper.InvalidParams("parameterName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var group = FindGroupController(mixer, p.groupName);
if (group == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Group not found: {p.groupName}", id);
}
var groupType = GetAudioMixerGroupControllerType();
var effectsProp = groupType.GetProperty("effects");
var effects = effectsProp?.GetValue(group) as Array;
if (effects == null || p.effectIndex >= effects.Length)
{
return JsonRpcResponseHelper.ErrorMessage($"Effect index {p.effectIndex} out of range", id);
}
var effect = effects.GetValue(p.effectIndex);
var effectType = GetAudioMixerEffectControllerType();
var setValueMethod = effectType.GetMethod("SetValue", BindingFlags.Public | BindingFlags.Instance);
if (setValueMethod != null)
{
Undo.RecordObject(mixer, "Set Effect Parameter");
var controllerType = GetAudioMixerControllerType();
var snapshotsProp = controllerType.GetProperty("snapshots");
var snapshots = snapshotsProp?.GetValue(mixer) as Array;
if (snapshots != null && snapshots.Length > 0)
{
var snapshot = snapshots.GetValue(0);
setValueMethod.Invoke(effect, new object[] { mixer, snapshot, p.parameterName, p.value });
}
}
EditorUtility.SetDirty(mixer);
var result = new EffectParameterResult
{
status = "success",
parameterName = p.parameterName,
value = p.value
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.SetEffectParameter error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to set effect parameter: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("SetEffectParameter requires Unity Editor", id);
#endif
}
/// <summary>
/// Get a parameter value from an effect.
/// API: unity.audio.getEffectParameter
/// </summary>
public static string GetEffectParameter(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<GetEffectParameterParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.groupName))
{
return JsonRpcResponseHelper.InvalidParams("groupName is required", id);
}
if (string.IsNullOrEmpty(p.parameterName))
{
return JsonRpcResponseHelper.InvalidParams("parameterName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var group = FindGroupController(mixer, p.groupName);
if (group == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Group not found: {p.groupName}", id);
}
var groupType = GetAudioMixerGroupControllerType();
var effectsProp = groupType.GetProperty("effects");
var effects = effectsProp?.GetValue(group) as Array;
if (effects == null || p.effectIndex >= effects.Length)
{
return JsonRpcResponseHelper.ErrorMessage($"Effect index {p.effectIndex} out of range", id);
}
var effect = effects.GetValue(p.effectIndex);
var effectType = GetAudioMixerEffectControllerType();
float value = 0f;
var getValueMethod = effectType.GetMethod("GetValue", BindingFlags.Public | BindingFlags.Instance);
if (getValueMethod != null)
{
var controllerType = GetAudioMixerControllerType();
var snapshotsProp = controllerType.GetProperty("snapshots");
var snapshots = snapshotsProp?.GetValue(mixer) as Array;
if (snapshots != null && snapshots.Length > 0)
{
var snapshot = snapshots.GetValue(0);
var result_value = getValueMethod.Invoke(effect, new object[] { mixer, snapshot, p.parameterName });
if (result_value != null)
{
value = (float)result_value;
}
}
}
var result = new EffectParameterResult
{
status = "success",
parameterName = p.parameterName,
value = value
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.GetEffectParameter error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to get effect parameter: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("GetEffectParameter requires Unity Editor", id);
#endif
}
/// <summary>
/// List all available effect types.
/// API: unity.audio.listEffectTypes
/// </summary>
public static string ListEffectTypes(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var effectTypes = new string[]
{
"Attenuation",
"Send",
"Receive",
"Highpass",
"Lowpass",
"HighpassSimple",
"LowpassSimple",
"ParamEQ",
"Echo",
"Flange",
"Chorus",
"Distortion",
"Compressor",
"SFXReverb",
"Normalize",
"Duck Volume",
"PitchShifter"
};
var result = new
{
status = "success",
count = effectTypes.Length,
effectTypes = effectTypes
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.ListEffectTypes error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to list effect types: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("ListEffectTypes requires Unity Editor", id);
#endif
}
/// <summary>
/// Create a new snapshot.
/// API: unity.audio.createSnapshot
/// </summary>
public static string CreateSnapshot(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<CreateSnapshotParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.snapshotName))
{
return JsonRpcResponseHelper.InvalidParams("snapshotName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var controllerType = GetAudioMixerControllerType();
var createSnapshotMethod = controllerType.GetMethod("CreateNewSnapshot", BindingFlags.Public | BindingFlags.Instance);
if (createSnapshotMethod != null)
{
Undo.RecordObject(mixer, "Create Snapshot");
createSnapshotMethod.Invoke(mixer, new object[] { p.snapshotName, true });
}
EditorUtility.SetDirty(mixer);
AssetDatabase.SaveAssets();
var result = new
{
status = "success",
message = $"Created snapshot: {p.snapshotName}",
snapshotName = p.snapshotName,
mixerPath = p.mixerPath
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.CreateSnapshot error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to create snapshot: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("CreateSnapshot requires Unity Editor", id);
#endif
}
/// <summary>
/// Delete a snapshot.
/// API: unity.audio.deleteSnapshot
/// </summary>
public static string DeleteSnapshot(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<DeleteSnapshotParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.snapshotName))
{
return JsonRpcResponseHelper.InvalidParams("snapshotName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var controllerType = GetAudioMixerControllerType();
var snapshotsProp = controllerType.GetProperty("snapshots");
var snapshots = snapshotsProp?.GetValue(mixer) as Array;
if (snapshots == null || snapshots.Length <= 1)
{
return JsonRpcResponseHelper.ErrorMessage("Cannot delete the only snapshot", id);
}
object targetSnapshot = null;
var snapshotType = Type.GetType("UnityEditor.Audio.AudioMixerSnapshotController, UnityEditor");
var nameProp = snapshotType?.GetProperty("name");
foreach (var snapshot in snapshots)
{
string name = nameProp?.GetValue(snapshot) as string;
if (name == p.snapshotName)
{
targetSnapshot = snapshot;
break;
}
}
if (targetSnapshot == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Snapshot not found: {p.snapshotName}", id);
}
var deleteMethod = controllerType.GetMethod("DeleteSnapshot", BindingFlags.Public | BindingFlags.Instance);
if (deleteMethod != null)
{
Undo.RecordObject(mixer, "Delete Snapshot");
deleteMethod.Invoke(mixer, new object[] { targetSnapshot });
}
EditorUtility.SetDirty(mixer);
AssetDatabase.SaveAssets();
var result = new
{
status = "success",
message = $"Deleted snapshot: {p.snapshotName}",
snapshotName = p.snapshotName,
mixerPath = p.mixerPath
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.DeleteSnapshot error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to delete snapshot: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("DeleteSnapshot requires Unity Editor", id);
#endif
}
/// <summary>
/// List all snapshots in an AudioMixer.
/// API: unity.audio.listSnapshots
/// </summary>
public static string ListSnapshots(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<ListSnapshotsParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var controllerType = GetAudioMixerControllerType();
var snapshotsProp = controllerType.GetProperty("snapshots");
var startSnapshotProp = controllerType.GetProperty("startSnapshot");
var snapshots = snapshotsProp?.GetValue(mixer) as Array;
var startSnapshot = startSnapshotProp?.GetValue(mixer);
var snapshotInfos = new List<SnapshotInfo>();
var snapshotType = Type.GetType("UnityEditor.Audio.AudioMixerSnapshotController, UnityEditor");
var nameProp = snapshotType?.GetProperty("name");
if (snapshots != null)
{
foreach (var snapshot in snapshots)
{
string name = nameProp?.GetValue(snapshot) as string ?? "Unknown";
bool isStart = startSnapshot != null && snapshot.Equals(startSnapshot);
snapshotInfos.Add(new SnapshotInfo
{
name = name,
isStartSnapshot = isStart
});
}
}
var result = new ListSnapshotsResult
{
status = "success",
count = snapshotInfos.Count,
snapshots = snapshotInfos.ToArray()
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.ListSnapshots error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to list snapshots: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("ListSnapshots requires Unity Editor", id);
#endif
}
/// <summary>
/// Transition to a snapshot.
/// API: unity.audio.transitionToSnapshot
/// </summary>
public static string TransitionToSnapshot(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<TransitionToSnapshotParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.snapshotName))
{
return JsonRpcResponseHelper.InvalidParams("snapshotName is required", id);
}
var mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(p.mixerPath);
if (mixer == null)
{
return JsonRpcResponseHelper.ErrorMessage($"AudioMixer not found: {p.mixerPath}", id);
}
var snapshot = mixer.FindSnapshot(p.snapshotName);
if (snapshot == null)
{
return JsonRpcResponseHelper.ErrorMessage($"Snapshot not found: {p.snapshotName}", id);
}
snapshot.TransitionTo(p.transitionTime);
var result = new
{
status = "success",
message = $"Transitioning to snapshot: {p.snapshotName}",
snapshotName = p.snapshotName,
transitionTime = p.transitionTime,
mixerPath = p.mixerPath
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.TransitionToSnapshot error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to transition to snapshot: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("TransitionToSnapshot requires Unity Editor", id);
#endif
}
/// <summary>
/// Add a send effect to a group.
/// API: unity.audio.addSend
/// </summary>
public static string AddSend(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<AddSendParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.sourceGroupName))
{
return JsonRpcResponseHelper.InvalidParams("sourceGroupName is required", id);
}
if (string.IsNullOrEmpty(p.targetGroupName))
{
return JsonRpcResponseHelper.InvalidParams("targetGroupName is required", id);
}
var addEffectParams = JsonUtility.ToJson(new AddEffectParams
{
mixerPath = p.mixerPath,
groupName = p.sourceGroupName,
effectType = "Send",
insertIndex = -1
});
var addResult = AddEffect(addEffectParams, id);
if (addResult.Contains("\"error\""))
{
return addResult;
}
var result = new
{
status = "success",
message = $"Added send from '{p.sourceGroupName}' to '{p.targetGroupName}'",
sourceGroupName = p.sourceGroupName,
targetGroupName = p.targetGroupName,
sendLevel = p.sendLevel,
mixerPath = p.mixerPath
};
return JsonRpcResponse.Success(result, id).ToJson();
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.AddSend error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to add send: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("AddSend requires Unity Editor", id);
#endif
}
/// <summary>
/// Set the send level of a send effect.
/// API: unity.audio.setSendLevel
/// </summary>
public static string SetSendLevel(string paramsJson, object id)
{
#if UNITY_EDITOR
try
{
var p = JsonUtility.FromJson<SetSendLevelParams>(paramsJson ?? "{}");
if (string.IsNullOrEmpty(p.mixerPath))
{
return JsonRpcResponseHelper.InvalidParams("mixerPath is required", id);
}
if (string.IsNullOrEmpty(p.sourceGroupName))
{
return JsonRpcResponseHelper.InvalidParams("sourceGroupName is required", id);
}
var setParamParams = JsonUtility.ToJson(new SetEffectParameterParams
{
mixerPath = p.mixerPath,
groupName = p.sourceGroupName,
effectIndex = p.sendIndex,
parameterName = "Send Level",
value = p.sendLevel
});
return SetEffectParameter(setParamParams, id);
}
catch (Exception e)
{
Debug.LogError($"[MCP] AudioController.SetSendLevel error: {e.Message}\n{e.StackTrace}");
return JsonRpcResponseHelper.ErrorMessage($"Failed to set send level: {e.Message}", id);
}
#else
return JsonRpcResponseHelper.ErrorMessage("SetSendLevel requires Unity Editor", id);
#endif
}
#endregion
#region Helper Methods
private static AudioSource FindAudioSource(string path)
{
var go = GameObject.Find(path);
if (go == null) return null;
return go.GetComponent<AudioSource>();
}
private static AudioListener FindAudioListener(string path)
{
var go = GameObject.Find(path);
if (go == null) return null;
return go.GetComponent<AudioListener>();
}
private static string GetGameObjectPath(GameObject obj)
{
string path = obj.name;
Transform parent = obj.transform.parent;
while (parent != null)
{
path = parent.name + "/" + path;
parent = parent.parent;
}
return path;
}
private static AudioSourceInfo CreateAudioSourceInfo(AudioSource source)
{
return new AudioSourceInfo
{
name = source.gameObject.name,
path = GetGameObjectPath(source.gameObject),
instanceId = source.gameObject.GetInstanceID(),
clipName = source.clip != null ? source.clip.name : null,
clipPath = source.clip != null ? AssetDatabase.GetAssetPath(source.clip) : null,
volume = source.volume,
pitch = source.pitch,
loop = source.loop,
playOnAwake = source.playOnAwake,
spatialBlend = source.spatialBlend,
minDistance = source.minDistance,
maxDistance = source.maxDistance,
priority = source.priority,
panStereo = source.panStereo,
dopplerLevel = source.dopplerLevel,
spread = source.spread,
rolloffMode = source.rolloffMode.ToString(),
mixerGroup = source.outputAudioMixerGroup != null ? source.outputAudioMixerGroup.name : null,
isPlaying = source.isPlaying,
time = source.time,
enabled = source.enabled
};
}
private static AudioClipInfo CreateAudioClipInfo(AudioClip clip, string path)
{
#if UNITY_EDITOR
var importer = AssetImporter.GetAtPath(path) as AudioImporter;
#endif
return new AudioClipInfo
{
name = clip.name,
path = path,
length = clip.length,
channels = clip.channels,
frequency = clip.frequency,
samples = clip.samples,
loadInBackground = clip.loadInBackground,
loadType = clip.loadType.ToString(),
ambisonic = clip.ambisonic
};
}
private static AudioListenerInfo CreateAudioListenerInfo(AudioListener listener)
{
return new AudioListenerInfo
{
name = listener.gameObject.name,
path = GetGameObjectPath(listener.gameObject),
instanceId = listener.gameObject.GetInstanceID(),
enabled = listener.enabled,
velocityUpdateMode = (float)AudioSettings.GetConfiguration().speakerMode
};
}
#endregion
}
}