Editor MCP tool method for managing audio mixer buses in the active scene. It discovers SoundPointComponent instances, groups them by mixer target, and supports actions: list_buses/status, set_volume, mute, unmute, and duck, returning JSON tool outcomes.
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Sandbox;
namespace Editor.Mcp
{
public static partial class SboxMcpAssistant
{
/// <summary>
/// Manages and queries audio mixer buses (Master, Music, SFX, Voice, UI) across the scene
/// and audio runtime for sound isolation, volume calibration, and ducking tests.
/// </summary>
[McpTool( "pocketknife_audio_mixer_manage" )]
public static string AudioMixerManage(
[Description( "Action: 'status', 'list_buses', 'set_volume', 'mute', 'unmute', 'duck'" )] string action = "status",
[Description( "Target audio bus name (e.g. 'Master', 'Music', 'SFX', 'Voice', 'UI')" )] string bus = "Master",
[Description( "Target volume from 0.0 (silent) to 1.0 (full)" )] float volume = 1.0f,
[Description( "Ducking reduction factor from 0.0 to 1.0 (e.g. 0.3 to reduce volume by 70%)" )] float duckFactor = 0.3f )
{
try
{
var scene = EditorRuntime.ResolveActiveScene();
var normAction = (action ?? "").ToLowerInvariant().Trim();
if ( normAction == "status" || string.IsNullOrWhiteSpace( normAction ) )
{
normAction = "list_buses";
}
// Discover standard and scene-defined audio buses
var standardBuses = new[] { "Master", "Music", "SFX", "Voice", "UI", "Ambient" };
var soundPoints = scene?.GetAllComponents<SoundPointComponent>().ToList() ?? new List<SoundPointComponent>();
var busGroups = new Dictionary<string, List<SoundPointComponent>>( StringComparer.OrdinalIgnoreCase );
foreach ( var b in standardBuses )
{
busGroups[b] = new List<SoundPointComponent>();
}
foreach ( var sp in soundPoints )
{
var targetStr = sp.TargetMixer.ToString();
var target = string.IsNullOrWhiteSpace( targetStr ) || targetStr == "unknown" ? "SFX" : targetStr;
if ( !busGroups.TryGetValue( target, out var list ) )
{
list = new List<SoundPointComponent>();
busGroups[target] = list;
}
list.Add( sp );
}
if ( normAction == "list_buses" )
{
var busSummaries = busGroups.Select( kvp => new
{
Bus = kvp.Key,
ActiveSourceCount = kvp.Value.Count,
AverageVolume = kvp.Value.Count > 0 ? kvp.Value.Average( s => s.Volume ) : 1.0f,
ActiveSources = kvp.Value.Select( s => new
{
GameObject = s.GameObject?.Name,
Volume = s.Volume,
Pitch = s.Pitch,
Distance = s.Distance
} ).ToArray()
} ).ToArray();
return McpJson.Write( ToolOutcome<object>.Ready( new
{
Action = "list_buses",
TotalBuses = busSummaries.Length,
TotalSoundPoints = soundPoints.Count,
Buses = busSummaries,
Success = true
} ) );
}
var targetBus = bus.Trim();
var clampedVol = Math.Clamp( volume, 0.0f, 2.0f );
if ( normAction == "set_volume" )
{
int modifiedCount = 0;
if ( targetBus.Equals( "Master", StringComparison.OrdinalIgnoreCase ) )
{
foreach ( var sp in soundPoints )
{
sp.Volume = clampedVol;
modifiedCount++;
}
}
else if ( busGroups.TryGetValue( targetBus, out var targetList ) )
{
foreach ( var sp in targetList )
{
sp.Volume = clampedVol;
modifiedCount++;
}
}
return McpJson.Write( ToolOutcome<object>.Ready( new
{
Action = "set_volume",
Bus = targetBus,
NewVolume = clampedVol,
SourcesModified = modifiedCount,
Success = true
} ) );
}
if ( normAction == "mute" || normAction == "unmute" )
{
var isMute = normAction == "mute";
var targetVol = isMute ? 0.0f : 1.0f;
int modifiedCount = 0;
if ( targetBus.Equals( "Master", StringComparison.OrdinalIgnoreCase ) )
{
foreach ( var sp in soundPoints )
{
sp.Volume = targetVol;
modifiedCount++;
}
}
else if ( busGroups.TryGetValue( targetBus, out var targetList ) )
{
foreach ( var sp in targetList )
{
sp.Volume = targetVol;
modifiedCount++;
}
}
return McpJson.Write( ToolOutcome<object>.Ready( new
{
Action = normAction,
Bus = targetBus,
IsMuted = isMute,
SourcesModified = modifiedCount,
Success = true
} ) );
}
if ( normAction == "duck" )
{
var factor = Math.Clamp( duckFactor, 0.0f, 1.0f );
int duckedCount = 0;
if ( busGroups.TryGetValue( targetBus, out var targetList ) )
{
foreach ( var sp in targetList )
{
sp.Volume *= factor;
duckedCount++;
}
}
return McpJson.Write( ToolOutcome<object>.Ready( new
{
Action = "duck",
Bus = targetBus,
DuckFactor = factor,
SourcesDucked = duckedCount,
Success = true
} ) );
}
return McpJson.Write( ToolOutcome<object>.Failure( null, ToolStatus.Failed, "audio.invalid_action", $"Unsupported action '{action}'. Use 'status', 'list_buses', 'set_volume', 'mute', 'unmute', or 'duck'." ) );
}
catch ( Exception ex )
{
return McpJson.Write( ToolOutcome<object>.Failure( null, ToolStatus.Failed, "audio.mixer_failed", "Audio mixer operation failed.", ex ) );
}
}
}
}