Skip to content

SynthDef Builder

nanosynth.synthdef

SynthDef builder and SCgf compiler for nanosynth.

Ported from supriya's ugens/core.py -- minimal subset for defining UGen graphs in Python and compiling them to SuperCollider's SCgf binary format.

ServerProtocol

Bases: Protocol

Structural type for servers that can receive SynthDefs and create synths.

Missing

Sentinel for required parameters (no default).

Default

Sentinel for parameters whose default is computed from other parameters.

UGenSerializable

Protocol for objects that can be serialized into a UGen graph (e.g. Envelope).

PseudoUGen

Base class for virtual UGens that compose other UGens.

UGenOperable

Mixin providing arithmetic and signal-processing operators on UGen signals.

All UGen outputs, constants, and vectors inherit these operators. Standard Python operators (+, -, *, /, **, %, //, <, >, <=, >=, &, |, ^, <<, >>) produce BinaryOpUGen nodes in the graph. Named methods provide SuperCollider-specific operations: pitch conversion (midicps, cpsmidi), amplitude conversion (dbamp, ampdb), waveshaping (distort, softclip, tanh_), clipping (clip2, fold2, wrap2), ring modulation (ring1--ring4), and more.

When both operands are constants, the operation is folded at compile time (no UGen is emitted).

equal

equal(expr: UGenRecursiveInput) -> UGenOperable

Signal equality test. Returns 1.0 when equal, 0.0 otherwise.

Uses an explicit method instead of __eq__ to preserve Python object identity semantics and hash stability.

not_equal

not_equal(expr: UGenRecursiveInput) -> UGenOperable

Signal inequality test. Returns 1.0 when not equal, 0.0 otherwise.

min_

min_(expr: UGenRecursiveInput) -> UGenOperable

Minimum of self and expr: min(a, b).

max_

max_(expr: UGenRecursiveInput) -> UGenOperable

Maximum of self and expr: max(a, b).

lcm

lcm(expr: UGenRecursiveInput) -> UGenOperable

Least common multiple.

gcd

gcd(expr: UGenRecursiveInput) -> UGenOperable

Greatest common divisor.

round_

round_(expr: UGenRecursiveInput) -> UGenOperable

Round self to the nearest multiple of expr.

round_up

round_up(expr: UGenRecursiveInput) -> UGenOperable

Round self up to the next multiple of expr.

trunc

trunc(expr: UGenRecursiveInput) -> UGenOperable

Truncate self to a multiple of expr.

atan2

atan2(expr: UGenRecursiveInput) -> UGenOperable

Two-argument arctangent: atan2(self, expr) in radians.

hypot

hypot(expr: UGenRecursiveInput) -> UGenOperable

Euclidean distance: sqrt(self**2 + expr**2).

hypotx

hypotx(expr: UGenRecursiveInput) -> UGenOperable

Approximate hypotenuse: abs(self) + abs(expr) - min(abs(self), abs(expr)) / sqrt(2).

ring1

ring1(expr: UGenRecursiveInput) -> UGenOperable

Ring modulation variant: self * expr + self.

ring2

ring2(expr: UGenRecursiveInput) -> UGenOperable

Ring modulation variant: self * expr + self + expr.

ring3

ring3(expr: UGenRecursiveInput) -> UGenOperable

Ring modulation variant: self * self * expr.

ring4

ring4(expr: UGenRecursiveInput) -> UGenOperable

Ring modulation variant: self * self * expr - self * expr * expr.

difsqr

difsqr(expr: UGenRecursiveInput) -> UGenOperable

Difference of squares: self**2 - expr**2.

sumsqr

sumsqr(expr: UGenRecursiveInput) -> UGenOperable

Sum of squares: self**2 + expr**2.

sqrsum

sqrsum(expr: UGenRecursiveInput) -> UGenOperable

Square of sum: (self + expr)**2.

sqrdif

sqrdif(expr: UGenRecursiveInput) -> UGenOperable

Square of difference: (self - expr)**2.

absdif

absdif(expr: UGenRecursiveInput) -> UGenOperable

Absolute difference: abs(self - expr).

thresh

thresh(expr: UGenRecursiveInput) -> UGenOperable

Threshold gate: self if self >= expr else 0.

amclip

amclip(expr: UGenRecursiveInput) -> UGenOperable

Amplitude clipping: self * expr if expr > 0 else 0.

scaleneg

scaleneg(expr: UGenRecursiveInput) -> UGenOperable

Scale negative part: self * expr if self < 0 else self.

clip2

clip2(expr: UGenRecursiveInput) -> UGenOperable

Bilateral clipping: clamp self to [-expr, +expr].

excess

excess(expr: UGenRecursiveInput) -> UGenOperable

Residual after clip2: self - clip2(self, expr).

fold2

fold2(expr: UGenRecursiveInput) -> UGenOperable

Bilateral folding: fold self into the range [-expr, +expr].

wrap2

wrap2(expr: UGenRecursiveInput) -> UGenOperable

Bilateral wrapping: wrap self into the range [-expr, +expr).

frac

frac() -> UGenOperable

Fractional part: self - floor(self).

sign

sign() -> UGenOperable

Sign function: -1, 0, or 1.

squared

squared() -> UGenOperable

Square: self * self.

cubed

cubed() -> UGenOperable

Cube: self * self * self.

midicps

midicps() -> UGenOperable

Convert MIDI note number to frequency in Hz (e.g. 69 -> 440.0).

cpsmidi

cpsmidi() -> UGenOperable

Convert frequency in Hz to MIDI note number (e.g. 440.0 -> 69).

midiratio

midiratio() -> UGenOperable

Convert MIDI interval (semitones) to frequency ratio (e.g. 12 -> 2.0).

ratiomidi

ratiomidi() -> UGenOperable

Convert frequency ratio to MIDI interval in semitones (e.g. 2.0 -> 12).

dbamp

dbamp() -> UGenOperable

Convert decibels to linear amplitude (e.g. -6 -> ~0.5).

ampdb

ampdb() -> UGenOperable

Convert linear amplitude to decibels (e.g. 0.5 -> ~-6).

octcps

octcps() -> UGenOperable

Convert decimal octave notation to frequency in Hz.

cpsoct

cpsoct() -> UGenOperable

Convert frequency in Hz to decimal octave notation.

distort

distort() -> UGenOperable

Nonlinear distortion: self / (1 + abs(self)).

softclip

softclip() -> UGenOperable

Soft clipping: linear below 0.5, asymptotic above. Keeps signal in approximately [-1, 1].

UGenScalar

Bases: UGenOperable

A UGen scalar.

OutputProxy

Bases: UGenScalar

A UGen output proxy -- reference to a specific output of a UGen.

ConstantProxy

Bases: UGenScalar

Wraps a float constant, exposing UGenOperable arithmetic.

UGenVector

Bases: UGenOperable, Sequence['UGenOperable']

A sequence of UGenOperables.

UGen

Bases: UGenOperable, Sequence['UGenOperable']

Base class for all unit generators.

A UGen represents a single signal-processing node in a SynthDef graph. Subclasses are declared with the @ugen decorator, which generates __init__, rate constructors (.ar, .kr, .ir, .dr), and parameter accessors.

UGens are instantiated inside a SynthDefBuilder context manager and automatically register themselves with the active builder. They support multi-channel expansion: passing a list to any parameter produces parallel UGen instances (one per list element), returned as a UGenVector.

Indexing a UGen (ugen[0]) returns an OutputProxy referencing a specific output channel.

UnaryOpUGen

Bases: UGen

Applies a unary operator to a single input signal.

Created implicitly by named unary methods on UGenOperable (e.g. sig.midicps(), sig.tanh_()). The special_index selects which UnaryOperator to apply.

BinaryOpUGen

Bases: UGen

Applies a binary operator to two input signals.

Created implicitly by Python operators (+, -, *, /, etc.) and named binary methods (clip2, ring1, etc.) on UGenOperable. The special_index selects which BinaryOperator to apply. Includes compile-time algebraic optimizations (e.g. x * 0 = 0, x + 0 = x, x ** 1 = x).

Parameter

Bases: UGen

A named SynthDef parameter with a default value, rate, and optional lag.

Parameters are created by SynthDefBuilder.add_parameter() or the SynthDefBuilder(**kwargs) constructor. During build(), they are collected, sorted, and mapped to the appropriate Control UGen type based on their rate (CONTROL -> Control, AUDIO -> AudioControl, TRIGGER -> TrigControl, CONTROL+lag -> LagControl).

Control

Bases: UGen

A group of parameters exposed as control-rate (or scalar-rate) inputs.

Created internally by SynthDefBuilder.build() to aggregate Parameter instances by rate. Subclasses AudioControl, LagControl, and TrigControl handle audio-rate, lagged, and trigger-rate parameters respectively.

UGenInput

Bases: NamedTuple

A single input to a UGen node in the graph.

UGenNode

Bases: NamedTuple

A UGen in the graph with its connections.

SynthDefGraph

Bases: NamedTuple

Structured representation of a SynthDef's UGen graph.

SynthDef

A compiled UGen graph, ready for binary serialization and server dispatch.

Produced by SynthDefBuilder.build() or the @synthdef decorator. Contains the topologically sorted UGen list, constant table, control mapping, and parameter index.

Key methods:

  • compile() -- serialize to SuperCollider's SCgf binary format.
  • send(server) -- send the compiled bytes to a running server.
  • play(server, **params) -- send and immediately create a synth.
  • dump_ugens() -- return a human-readable graph representation.
  • graph() -- return a structured SynthDefGraph for introspection.
  • to_dot() -- export the graph as a Graphviz DOT string.

graph

graph() -> SynthDefGraph

Return a structured representation of the UGen graph.

to_dot

to_dot(*, rankdir: str = 'TB', label: bool = True) -> str

Export the UGen graph as a Graphviz DOT string.

send

send(server: ServerProtocol) -> None

Send this SynthDef to a running server.

save

save(
    path: str | Path, *, use_anonymous_name: bool = False
) -> None

Write this SynthDef to a .scsyndef file.

Args: path: Destination file path. use_anonymous_name: If True, use the anonymous name in the binary.

play

play(
    server: ServerProtocol,
    target: int = 1,
    action: int = 0,
    **params: float,
) -> SupportsInt

Send this SynthDef (if needed) and create a synth. Returns a Synth proxy.

dump_ugens

dump_ugens() -> str

Return a human-readable representation of the UGen graph.

SynthDefBuilder

Context manager for constructing SynthDef UGen graphs.

Usage::

with SynthDefBuilder(frequency=440.0, amplitude=0.3) as builder:
    sig = SinOsc.ar(frequency=builder["frequency"])
    Out.ar(bus=0, source=sig * builder["amplitude"])
synthdef = builder.build(name="my_synth")

Parameters are declared as keyword arguments (name=default_value) or via add_parameter(). Inside the context, UGens instantiated with a matching _uuid are automatically added to the graph. On build(), the graph is sorted topologically, controls are mapped, unused pure UGens are eliminated, and a SynthDef is returned.

__getitem__

__getitem__(item: str) -> OutputProxy | Parameter

Look up a parameter by name.

Returns an OutputProxy for scalar parameters, or the Parameter itself for multi-channel parameters (sequence values).

Raises: KeyError: If the parameter name does not exist.

add_parameter

add_parameter(
    *,
    name: str,
    value: float | Sequence[float],
    rate: ParameterRate | None = ParameterRate.CONTROL,
    lag: float | None = None,
) -> OutputProxy | Parameter

Add a named parameter to the SynthDef.

Args: name: Parameter name (must be unique within this builder). value: Default value. A sequence creates a multi-channel parameter. rate: Parameter rate (CONTROL, AUDIO, TRIGGER, or SCALAR). lag: Lag time in seconds. Produces a LagControl when set.

Returns: OutputProxy for scalar parameters, or the Parameter for multi-channel parameters.

Raises: ValueError: If a parameter with this name already exists.

build

build(
    name: str | None = None, optimize: bool = True
) -> SynthDef

Compile the UGen graph into a SynthDef.

Performs control mapping, topological sorting, and optional dead code elimination. The builder can be reused after calling build().

Args: name: SynthDef name. If None, an anonymous MD5 hash is used. optimize: If True, eliminate unused pure UGens from the graph.

Returns: A compiled SynthDef ready for sending to a server.

param

param(
    default: Missing | Default | float | None = MISSING,
    *,
    unexpanded: bool = False,
) -> Param

Define a UGen parameter. Akin to dataclasses.field.

ugen

ugen(
    *,
    ar: bool = False,
    kr: bool = False,
    ir: bool = False,
    dr: bool = False,
    new: bool = False,
    has_done_flag: bool = False,
    is_multichannel: bool = False,
    is_output: bool = False,
    is_pure: bool = False,
    is_width_first: bool = False,
    channel_count: int = 1,
    fixed_channel_count: bool = False,
) -> Callable[[type[UGen]], type[UGen]]

Decorate a UGen class. Akin to dataclasses.dataclass.

control

control(
    value: float | Sequence[float] = 0.0,
    rate: str | ParameterRate = "kr",
    lag: float | None = None,
) -> Parameter

Define a SynthDef parameter with rate and lag metadata.

Returns a Parameter instance suitable for passing to SynthDefBuilder as a keyword argument::

SynthDefBuilder(
    freq=control(440.0, rate="ar"),        # audio rate
    amp=control(0.3, lag=0.1),              # control rate + lag
    bus=control(0, rate="ir"),              # scalar rate
)

Args: value: Default parameter value (float or sequence for multi-channel). rate: Parameter rate -- "ar", "kr", "ir", "tr", or a ParameterRate enum member. Defaults to "kr". lag: Lag time in seconds. Produces a LagControl when set.

synthdef

synthdef(
    *args: str | tuple[str, float],
) -> Callable[..., SynthDef]

Decorator for constructing SynthDefs from functions.

Parameter rates and lags can be specified positionally::

@synthdef("ar", ("kr", 0.5))
def my_synth(freq=440, amp=0.1):
    ...

Without arguments, all parameters default to control rate::

@synthdef()
def my_synth(freq=440, amp=0.1):
    ...