Skip to main content
Glama
AudioController.cs117 kB
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 } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dsgarage/UniMCP4CC'

If you have feedback or need assistance with the MCP directory API, please join our Discord server