Skip to content

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

set

set(**params: float) -> None

Set parameter values on this synth.

free

free() -> None

Free this synth node.

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().

free

free() -> None

Free this group node.

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()

bus_id property

bus_id: int

The server-side bus index.

num_channels property

num_channels: int

Number of contiguous channels this bus spans.

rate property

rate: str

Bus rate: "audio" or "control".

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.

free

free() -> None

Return this bus to the allocator pool.

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)
    ...

is_running property

is_running: bool

Whether the engine is currently online.

options property

options: Options

The server's Options configuration.

is_recording property

is_recording: bool

Whether the server is currently recording.

boot

boot() -> None

Boot the embedded scsynth engine and create the default group.

quit

quit() -> None

Shut down the embedded engine.

next_node_id

next_node_id() -> int

Return a unique node ID (cycling from 1000 within scsynth's range).

send_msg

send_msg(address: str, *args: OscArgument) -> None

Send an OSC message to the engine.

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).

free

free(node_id: SupportsInt) -> None

Free a node by ID (accepts int or Synth/Group proxy).

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.

next_buffer_id

next_buffer_id() -> int

Return a unique buffer ID, reusing freed ids (from 0).

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.

zero_buffer

zero_buffer(buffer_id: int) -> None

Zero all samples in a buffer.

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).