"""
juce_bridge.py
This file provides a clean, simple OSC-based bridge between the MCP server (Python)
and your JUCE synthesizer application or plugin host.
The MCP server calls functions here to send instructions to the synth, such as:
set_parameter("cutoff", 0.8)
The JUCE synth listens on an OSC port (default 9001) and receives messages like:
/setParameter cutoff 0.8
You can change the port or OSC address patterns in config.py.
"""
from pythonosc.udp_client import SimpleUDPClient
from typing import List, Optional
from .config import JUCE_OSC_HOST, JUCE_OSC_PORT
# ------------------------------------------------------------
# OSC client initialization
# ------------------------------------------------------------
# SimpleUDPClient(host, port) creates a reusable OSC sender
osc_client = SimpleUDPClient(JUCE_OSC_HOST, JUCE_OSC_PORT)
# ------------------------------------------------------------
# Core parameter control functions
# ------------------------------------------------------------
def set_parameter(param: str, value: float) -> None:
"""
Send an OSC message to set a parameter in the JUCE synth.
The OSC address scheme:
/setParameter <paramName> <value>
JUCE side:
if (msg.getAddressPattern() == "/setParameter") { ... }
"""
# The OSC protocol is simple: address + arguments
osc_client.send_message("/setParameter", [param, float(value)])
def get_parameter(param: str) -> float:
"""
For now, we do not implement bidirectional OSC (synth → Python).
So this function either:
A) returns a cached/stored value (if you add a cache), or
B) raises NotImplemented, or
C) returns a placeholder.
For now we return a placeholder so the LLM has something to use.
Later, you can add TCP/OSC reply support.
"""
# TODO: implement bidirectional communication if needed
# For now, pretend all parameters default to 0.0
return 0.0
def list_parameters() -> List[str]:
"""
Return a list of all known parameters.
You can hard-code this list, load it from a config file,
or eventually query JUCE for a dynamic list.
For now, we return a starter set that you will expand.
"""
return [
"cutoff",
"resonance",
"attack",
"release",
"mix",
"gain",
"osc1Detune",
"osc2Detune",
]
# ------------------------------------------------------------
# Optional utility (rate-limited ramps, smoothing, etc.)
# ------------------------------------------------------------
def ramp_parameter(param: str, start: float, end: float, steps: int = 20) -> None:
"""
Utility: ramp a parameter smoothly from start → end in N steps.
The MCP server can call this via a dedicated tool if you want “smooth sweeps”
controlled by the LLM.
NOTE: Do NOT call this in a tight loop inside the audio thread — this is Python
and therefore control-rate only (fine for UI-level modulation).
"""
import time
if steps <= 1:
set_parameter(param, end)
return
delta = (end - start) / steps
current = start
for _ in range(steps):
current += delta
set_parameter(param, current)
time.sleep(0.01) # 10 ms step = 100 Hz control rate
# ------------------------------------------------------------
# Done — the MCP server now has clean access to your synth!
# ------------------------------------------------------------