assign_to_bank
Assigns an event to a specified bank in FMOD Studio, allowing events to belong to multiple banks for modular audio organization.
Instructions
Add the event to the given bank (event can live in multiple banks).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| event_path | Yes | ||
| bank_path | Yes |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- fmod_mcp/tools/events.py:140-156 (handler)Core implementation: evaluates JavaScript in FMOD Studio that looks up an event and a bank by path, adds the event to the bank's relationships, then returns the event path and list of assigned bank paths.
async def assign_to_bank( client: StudioClient, event_path: str, bank_path: str, ) -> dict[str, Any]: js = f""" var evt = studio.project.lookup({json.dumps(event_path)}); if (!evt) throw new Error("Event not found: " + {json.dumps(event_path)}); var bank = studio.project.lookup({json.dumps(bank_path)}); if (!bank) throw new Error("Bank not found: " + {json.dumps(bank_path)}); evt.relationships.banks.add(bank); var paths = []; var dests = evt.relationships.banks.destinations; if (dests) for (var i = 0; i < dests.length; i++) paths.push(dests[i].getPath()); return {{ path: evt.getPath(), banks: paths }}; """ return await client.eval(js) - fmod_mcp/server.py:120-123 (registration)FastMCP tool registration: decorates the server function with @mcp.tool() and delegates to events.assign_to_bank.
@mcp.tool() async def assign_to_bank(event_path: str, bank_path: str) -> dict[str, Any]: """Add the event to the given bank (event can live in multiple banks).""" return await events.assign_to_bank(_studio(), event_path, bank_path) - fmod_mcp/tools/events.py:7-142 (helper)Imports StudioClient used as the parameter type for the handler function.
from ..studio_client import StudioClient # Shared JS helper: walks/creates nested EventFolders under the master folder # so that "event:/a/b/c" resolves to the folder object for "event:/a/b/" and # the leaf "c" is the event name. Uses only /* */ comments because studio_client # flattens the JS to a single line before sending. _FOLDER_HELPER = r""" function __mcp_ensureEventFolder(pathPrefix) { if (pathPrefix.indexOf("event:/") !== 0) throw new Error("Event path must start with 'event:/'"); var remainder = pathPrefix.substring("event:/".length); if (remainder.charAt(remainder.length - 1) === "/") remainder = remainder.substring(0, remainder.length - 1); var segments = remainder.length > 0 ? remainder.split("/") : []; var cur = studio.project.workspace.masterEventFolder; if (!cur) throw new Error("Could not locate masterEventFolder"); var builtPath = "event:/"; for (var i = 0; i < segments.length; i++) { var seg = segments[i]; if (!seg) continue; builtPath += seg + "/"; var next = studio.project.lookup(builtPath); if (!next) { next = studio.project.create("EventFolder"); next.name = seg; next.folder = cur; } cur = next; } return cur; } """ async def create_event( client: StudioClient, event_path: str, bank_path: str, bus_path: str | None = None, ) -> dict[str, Any]: """Create a new event, assign it to a bank, and optionally route to a bus. ``event_path`` is the full path including the event name, e.g. ``event:/bumpers/hit_soft``. Any missing ancestor folders are created. ``bank_path`` like ``bank:/pinball_sound_effects``. ``bus_path`` like ``bus:/SFX`` (optional). """ if "/" not in event_path or not event_path.startswith("event:/"): raise ValueError("event_path must look like 'event:/folder/name'") parent_prefix, _, name = event_path.rpartition("/") # Ensure trailing slash on parent for the helper parent_with_slash = parent_prefix + "/" js = ( _FOLDER_HELPER + f""" var parent = __mcp_ensureEventFolder({json.dumps(parent_with_slash)}); var existing = studio.project.lookup({json.dumps(event_path)}); if (existing) throw new Error("Event already exists: " + {json.dumps(event_path)}); var evt = studio.project.create("Event"); evt.name = {json.dumps(name)}; evt.folder = parent; var bank = studio.project.lookup({json.dumps(bank_path)}); if (!bank) throw new Error("Bank not found: " + {json.dumps(bank_path)}); evt.relationships.banks.add(bank); var busPath = {json.dumps(bus_path) if bus_path else "null"}; if (busPath) {{ var bus = studio.project.lookup(busPath); if (!bus) throw new Error("Bus not found: " + busPath); try {{ evt.masterTrack.mixerGroup.output = bus; }} catch (e) {{ throw new Error("Could not route to bus: " + e.message); }} }} return {{ guid: evt.id, path: evt.getPath(), bank: bank.getPath(), bus: busPath }}; """ ) return await client.eval(js) async def add_single_sound( client: StudioClient, event_path: str, audio_guid: str, loop: bool = False, streaming: bool = False, async_mode: bool = False, start: float = 0.0, length: float | None = None, ) -> dict[str, Any]: """Add a new group track with a Single-Sound instrument to ``event_path``. The instrument's length defaults to the audio file's length if ``length`` is omitted. """ length_js = "asset.length" if length is None else json.dumps(length) js = f""" var evt = studio.project.lookup({json.dumps(event_path)}); if (!evt) throw new Error("Event not found: " + {json.dumps(event_path)}); var asset = null; var all = studio.project.model.AudioFile.findInstances(); for (var i = 0; i < all.length; i++) {{ if (all[i].isValid && all[i].id === {json.dumps(audio_guid)}) {{ asset = all[i]; break; }} }} if (!asset) throw new Error("AudioFile not found for guid: " + {json.dumps(audio_guid)}); var track = evt.addGroupTrack(); var sound = track.addSound(evt.timeline, "SingleSound", {json.dumps(start)}, {length_js}); sound.audioFile = asset; if (typeof sound.length !== 'undefined' && asset.length) sound.length = asset.length; try {{ sound.loopMode = {"'Loop'" if loop else "'Off'"}; }} catch (e) {{}} try {{ sound.streaming = {str(streaming).lower()}; }} catch (e) {{}} try {{ sound.asynchronous = {str(async_mode).lower()}; }} catch (e) {{}} return {{ track_guid: track.id, instrument_guid: sound.id, audio_file_guid: asset.id, length: sound.length || null }}; """ return await client.eval(js) async def set_event_property( client: StudioClient, event_path: str, property_name: str, value: Any, ) -> dict[str, Any]: """Set a property on an event (volume, pitch, min/max distance, etc.).""" js = f""" var evt = studio.project.lookup({json.dumps(event_path)}); if (!evt) throw new Error("Event not found: " + {json.dumps(event_path)}); evt[{json.dumps(property_name)}] = {json.dumps(value)}; return {{ ok: true, path: evt.getPath(), property: {json.dumps(property_name)}, value: evt[{json.dumps(property_name)}] }}; """ return await client.eval(js) async def assign_to_bank( client: StudioClient, event_path: str, - tests/test_server_registration.py:18-18 (registration)Test registration file confirms assign_to_bank is in the EXPECTED_TOOLS set.
"assign_to_bank", - tests/test_tools.py:158-163 (schema)Test case verifying the handler sends the correct JavaScript ('evt.relationships.banks.add') to FMOD Studio.
async def test_assign_to_bank( client: StudioClient, mock_studio: MockStudio ): mock_studio.responder = responder_sequence([("OK", {"path": "event:/a", "banks": ["bank:/X"]})]) await events.assign_to_bank(client, "event:/a", "bank:/X") js = _last_sent_js(mock_studio)