Server¶
nanosynth.server ¶
High-level Server class wrapping the embedded scsynth engine.
Synth ¶
Lightweight proxy for a synth node on the server.
Wraps a node ID with convenience methods and int-compatibility.
Returned by Server.synth() and Server.managed_synth().
Supports int() conversion, equality with plain ints, and use as
a context manager (frees the node on exit)::
node = server.synth("sine", frequency=440.0)
node.set(frequency=880.0)
node.free()
with server.synth("sine") as node:
... # freed on exit
wait_free ¶
wait_free(timeout: float = 5.0) -> bool
Block until this node is freed (e.g. by a self-freeing envelope).
Returns True if it ended, False on timeout. See
:meth:Server.wait_for_node_free.
on_free ¶
on_free(callback: Callable[[NodeEvent], None]) -> None
Call callback once, when this node is freed.
The handler unregisters itself after firing. Enables notifications.
Group ¶
Lightweight proxy for a group node on the server.
Same shape as Synth but without a name field. Returned by
Server.group() and Server.managed_group().
ParGroup ¶
Bases: Group
Lightweight proxy for a parallel group node on the server.
A ParGroup evaluates its child nodes in parallel across CPU cores.
Same interface as Group but created with /p_new instead of
/g_new.
Bus ¶
Lightweight proxy for an allocated bus on the server.
Wraps a bus ID with convenience methods and int-compatibility.
Returned by Server.audio_bus() and Server.control_bus().
Supports int() conversion and use as a context manager
(frees the bus on exit)::
bus = server.audio_bus(2)
# use int(bus) or bus.bus_id as a synth param
node = server.synth("fx", in_bus=float(int(bus)))
bus.free()
set ¶
set(*values: float) -> None
Set control bus value(s). Only valid for control-rate buses.
Args: *values: One value per channel.
Raises: RuntimeError: If called on an audio-rate bus.
ServerStatus
dataclass
¶
Snapshot of the engine's state, from /status.reply.
ServerVersion
dataclass
¶
Engine version information, from /version.reply.
NodeInfo
dataclass
¶
A node in the server's node tree, from /g_queryTree.reply.
Groups have is_group=True and populated children; synths have
is_group=False, a synthdef name, and (if queried with controls) a
controls mapping of name -> value (a float, or a bus-mapping string
like "c1"/"a0").
NodeEvent
dataclass
¶
A node lifecycle notification from the engine.
action is one of "go" (created), "end" (freed), "off"
(paused), "on" (resumed), or "move". Delivered only while
notifications are enabled (see :meth:Server.enable_notifications).
Server ¶
High-level wrapper around the embedded scsynth engine.
Manages the full boot-send-quit lifecycle, node ID allocation, SynthDef dispatch, and common OSC commands.
Can be used as a context manager::
with Server() as s:
s.send_synthdef(my_synthdef)
node = s.synth("my_synth", frequency=880.0)
...
next_node_id ¶
next_node_id() -> int
Return a unique node ID (cycling from 1000 within scsynth's range).
on ¶
on(address: str, callback: Callable[..., Any]) -> None
Register a persistent handler for replies at address.
off ¶
off(address: str, callback: Callable[..., Any]) -> None
Remove a previously registered handler.
wait_for_reply ¶
wait_for_reply(
address: str,
timeout: float = 5.0,
match: Callable[[OscMessage], bool] | None = None,
) -> OscMessage | None
Block until a matching reply arrives at address, or timeout.
Returns the decoded OscMessage, or None on timeout. On timeout the waiter is removed so it cannot be spuriously resolved by a later reply.
send_msg_sync ¶
send_msg_sync(
address: str,
*args: OscArgument,
reply_address: str,
timeout: float = 5.0,
match: Callable[[OscMessage], bool] | None = None,
) -> OscMessage | None
Send a message and wait for a matching reply at reply_address.
Returns the decoded reply OscMessage, or None on timeout.
sync ¶
sync(timeout: float = 5.0) -> bool
Block until the engine has processed all prior async commands.
Sends /sync with a unique id and waits for the matching
/synced reply -- the canonical scsynth round-trip barrier. Returns
True once synced, or False on timeout (e.g. a mock server with
no reply path). Concurrent sync() calls are matched by id.
status ¶
status(timeout: float = 5.0) -> ServerStatus
Query engine status via /status.
Returns a :class:ServerStatus (CPU load, sample rate, and node /
synth / group / synthdef / ugen counts). Raises EngineError if no
/status.reply arrives within timeout.
version ¶
version(timeout: float = 5.0) -> ServerVersion
Query engine version via /version.
Raises EngineError if no /version.reply arrives within timeout.
query_tree ¶
query_tree(
group: SupportsInt = 0,
*,
controls: bool = False,
timeout: float = 5.0,
) -> NodeInfo
Query the node tree under group via /g_queryTree.
Returns the queried group as a :class:NodeInfo with nested
children. With controls=True each synth's current control
values are included. Raises EngineError on timeout.
dump_tree ¶
dump_tree(
group: SupportsInt = 0, *, controls: bool = False
) -> None
Print the node tree under group to the engine's console/log.
Sends /g_dumpTree (no reply); output appears in the scsynth log.
For programmatic access use :meth:query_tree.
reset ¶
reset() -> None
Free all nodes, clear the scheduler, and reset node-id allocation.
The engine equivalent of a "panic" / cmd-period: frees every node
(/g_freeAll 0), clears pending scheduled bundles (/clearSched),
and recreates the default group. Loaded SynthDefs, buffers, and buses
are left intact (/g_freeAll does not free them); only the node-id
allocator is reset.
enable_notifications ¶
enable_notifications(timeout: float = 1.0) -> None
Register for node lifecycle notifications (/notify 1).
Once enabled, the engine sends /n_go//n_end//n_off/
/n_on//n_move as nodes are created, freed (including by a
self-freeing DoneAction), paused, resumed, or moved. Idempotent.
Called automatically by :meth:on_node and :meth:wait_for_node_free.
Waits for the engine's /done /notify so that registration is in
effect before any subsequent node command -- otherwise the first
node's /n_go can be missed. Falls back to fire-and-forget on
timeout (e.g. a mock server with no reply path).
disable_notifications ¶
disable_notifications() -> None
Unregister from node lifecycle notifications (/notify 0).
on_node ¶
on_node(callback: Callable[[NodeEvent], None]) -> None
Register callback to receive every :class:NodeEvent.
Enables notifications if not already on. Callbacks run on the reply thread, so they must be non-blocking.
remove_node_handler ¶
remove_node_handler(
callback: Callable[[NodeEvent], None],
) -> None
Remove a callback previously registered with :meth:on_node.
wait_for_node_free ¶
wait_for_node_free(
node_id: SupportsInt, timeout: float = 5.0
) -> bool
Block until node_id is freed (/n_end), or timeout.
Returns True if the node ended, False on timeout. Enables
notifications if needed. Register the wait before the node can free
itself (e.g. right after creating a self-freeing synth), or the
/n_end may arrive first and be missed.
send_synthdef ¶
send_synthdef(synthdef: SynthDef) -> None
Send a compiled SynthDef to the engine via /d_recv.
Waits for the engine to confirm loading (/done /d_recv)
before returning, so the SynthDef is ready for immediate use.
Falls back to fire-and-forget if no reply arrives (e.g. mock
servers in tests).
load_synthdef ¶
load_synthdef(path: str | Path) -> None
Load a .scsyndef file into the engine via /d_load.
Args: path: Path to the .scsyndef file.
synth ¶
synth(
name: str,
target: int = 1,
action: AddAction | int = AddAction.ADD_TO_HEAD,
**params: float,
) -> Synth
Create a synth node. Returns a Synth proxy.
Args: name: SynthDef name. target: Target node for placement. action: Add action (AddAction enum or int 0-4). **params: Initial synth parameter values.
group ¶
group(
target: int = 0,
action: AddAction | int = AddAction.ADD_TO_HEAD,
) -> Group
Create a group node. Returns a Group proxy.
Args: target: Target node for placement. action: Add action (AddAction enum or int 0-4).
par_group ¶
par_group(
target: int = 0,
action: AddAction | int = AddAction.ADD_TO_HEAD,
) -> ParGroup
Create a parallel group node. Returns a ParGroup proxy.
A ParGroup evaluates its child nodes in parallel across CPU cores.
Args: target: Target node for placement. action: Add action (AddAction enum or int 0-4).
managed_synth ¶
managed_synth(
name: str,
target: int = 1,
action: AddAction | int = AddAction.ADD_TO_HEAD,
**params: float,
) -> Iterator[Synth]
Create a synth and free it on context exit.
Usage::
with server.managed_synth("sine", frequency=440.0) as node:
time.sleep(1)
# node freed automatically
managed_group ¶
managed_group(
target: int = 0,
action: AddAction | int = AddAction.ADD_TO_HEAD,
) -> Iterator[Group]
Create a group and free it on context exit.
managed_par_group ¶
managed_par_group(
target: int = 0,
action: AddAction | int = AddAction.ADD_TO_HEAD,
) -> Iterator[ParGroup]
Create a parallel group and free it on context exit.
set ¶
set(node_id: SupportsInt, **params: float) -> None
Set parameter values on a running node.
Args: node_id: The node to modify (int or Synth/Group proxy). **params: Parameter name-value pairs.
alloc_buffer ¶
alloc_buffer(
num_frames: int,
num_channels: int = 1,
buffer_id: int | None = None,
) -> int
Allocate an empty buffer. Returns the buffer ID.
Args: num_frames: Number of sample frames. num_channels: Number of channels. buffer_id: Explicit buffer ID, or None for auto-allocation.
read_buffer ¶
read_buffer(
path: str,
buffer_id: int | None = None,
start_frame: int = 0,
num_frames: int = -1,
) -> int
Allocate a buffer and read a sound file into it. Returns the buffer ID.
Args: path: Path to the sound file. buffer_id: Explicit buffer ID, or None for auto-allocation. start_frame: Frame offset into the file. num_frames: Number of frames to read (-1 for entire file).
write_buffer ¶
write_buffer(
buffer_id: int,
path: str,
header_format: str = "wav",
sample_format: str = "int16",
num_frames: int = -1,
start_frame: int = 0,
leave_open: bool = False,
) -> None
Write buffer contents to a sound file.
Args: buffer_id: Buffer to write. path: Destination file path. header_format: File format (e.g. "wav", "aiff"). sample_format: Sample format (e.g. "int16", "float"). num_frames: Number of frames to write (-1 for all). start_frame: Starting frame in the buffer. leave_open: If True, keep the file open for streaming (used by DiskOut for recording).
free_buffer ¶
free_buffer(buffer_id: int) -> None
Free a buffer by ID and return it to the allocator pool.
close_buffer ¶
close_buffer(buffer_id: int) -> None
Close the sound file associated with a buffer (after b_write).
buffer_info ¶
buffer_info(
buffer_id: SupportsInt,
) -> tuple[int, int, float]
Return (frames, channels, sample_rate) for an allocated buffer.
Requires the embedded scsynth engine.
get_buffer_data ¶
get_buffer_data(buffer_id: SupportsInt) -> Any
Copy a buffer's samples into a numpy array of shape (frames, channels).
This is a direct in-process memory copy -- no OSC round-trip and no datagram-size limit -- which is the main advantage of the embedded engine. The buffer must already be allocated. The copy reads the live buffer, so a synth writing it concurrently may produce a torn read.
Requires numpy and the embedded scsynth engine.
set_buffer_data ¶
set_buffer_data(buffer_id: SupportsInt, data: Any) -> None
Copy a numpy array into an allocated buffer's samples.
data may be 1-D (mono) or 2-D (frames, channels); it is coerced
to contiguous float32 and its shape must match the buffer exactly. This
is a direct in-process memory write; a synth reading the buffer
concurrently may glitch.
Requires numpy and the embedded scsynth engine.
alloc_buffer_from_array ¶
alloc_buffer_from_array(
data: Any, *, sync: bool = True
) -> int
Allocate a buffer sized to data and fill it. Returns the buffer ID.
Convenience for loading a numpy array (wavetable, sample, window) into
the engine. data may be 1-D (mono) or 2-D (frames, channels).
By default waits (/sync) for the allocation to complete before
writing; pass sync=False if you have already synced.
Requires numpy and the embedded scsynth engine.
managed_buffer ¶
managed_buffer(
num_frames: int, num_channels: int = 1
) -> Iterator[int]
Allocate a buffer and free it on context exit.
managed_read_buffer ¶
managed_read_buffer(
path: str, start_frame: int = 0, num_frames: int = -1
) -> Iterator[int]
Read a sound file into a buffer and free it on context exit.
audio_bus ¶
audio_bus(num_channels: int = 1) -> Bus
Allocate private audio bus(es). Returns a Bus proxy.
Audio buses are allocated from the private range, starting after
the hardware I/O buses (at options.first_private_bus_id).
Args: num_channels: Number of contiguous channels to allocate.
control_bus ¶
control_bus(num_channels: int = 1) -> Bus
Allocate control bus(es). Returns a Bus proxy.
Args: num_channels: Number of contiguous channels to allocate.
free_bus ¶
free_bus(bus: Bus) -> None
Return a bus to the allocator pool.
Args: bus: The Bus proxy to free.
managed_audio_bus ¶
managed_audio_bus(num_channels: int = 1) -> Iterator[Bus]
Allocate an audio bus and free it on context exit.
managed_control_bus ¶
managed_control_bus(num_channels: int = 1) -> Iterator[Bus]
Allocate a control bus and free it on context exit.
record ¶
record(
path: str | Path,
*,
num_channels: int | None = None,
bus: int = 0,
header_format: str = "wav",
sample_format: str = "int16",
) -> None
Start recording server audio output to a file.
Args:
path: Destination file path.
num_channels: Number of channels to record. Defaults to
options.output_bus_channel_count.
bus: Bus index to record from (default 0 = main output).
header_format: File format ("wav" or "aiff").
sample_format: Sample encoding ("int16", "int24",
"float").
Raises: RuntimeError: If already recording.
stop_recording ¶
stop_recording() -> None
Stop recording and finalize the audio file.
Safe to call when not recording (no-op).