Skip to main content

Minimal embedded SuperCollider synthesis engine (libscsynth) wrapper

Project description

nanosynth

nanosynth is a Python package that embeds SuperCollider's libscsynth and supernova synthesis engines in-process using nanobind. It makes it possible to define SynthDefs in Python, compile them to SuperCollider's SCgf binary format, boot the embedded audio engine, and control it via OSC -- all without leaving Python.

Features

  • Self-contained with embedded synthesis engines -- both libscsynth and supernova run in-process as Python extensions (vendored and built from source), no separate process required. supernova is SuperCollider's parallel DSP engine -- it distributes independent synth nodes across CPU cores via ParGroups, while scsynth runs everything on a single audio thread

  • High-level Server class -- boot/quit lifecycle, node ID allocation, SynthDef dispatch, buffer management, OSC reply handling, and convenience methods (synth, group, par_group, free, set). Context manager support and managed_synth()/managed_group()/managed_par_group()/managed_buffer() for automatic resource cleanup. Same API for both engines -- just pass protocol=EmbeddedSupernovaProtocol() to use supernova

  • Pythonic SynthDef builder -- define UGen graphs using a context manager and operator overloading, compiled to SuperCollider's SCgf binary format

  • 340+ UGens -- oscillators, filters, delays, noise, chaos, granular, demand, dynamics, panning, physical modeling, reverb, phase vocoder, machine listening, stochastic synthesis, and more

  • Rich operator algebra -- 43 binary and 34 unary operators on all UGen signals, including arithmetic, comparison, bitwise, power, trig, pitch conversion (midicps/cpsmidi), clipping (clip2/fold2/wrap2), and more. Compile-time constant folding and algebraic optimizations

  • Non-real-time (NRT) rendering -- Score class for offline audio rendering to WAV/AIFF files without audio hardware. Timestamped OSC commands are serialized and rendered by the embedded engine

  • Bus allocation -- Bus proxy class with Server.audio_bus(), Server.control_bus(), managed_audio_bus(), managed_control_bus(). Eliminates hardcoded magic bus numbers in effect chains. int() compatible for passing as synth parameters

  • Pattern sequencing -- Pbind, Pseq, Prand, Pwhite, Pseries, Pgeom, Pchoose, Pn, Pconst, Rest, Clock, and Player for musical event scheduling. Patterns are reusable iterables; Pbind produces event streams that drive synth creation with automatic gate release. Clock provides tempo-driven playback with drift-free scheduling

  • MIDI input -- MidiIn class for receiving MIDI from hardware controllers (via embedded RtMidi: CoreMIDI on macOS, ALSA on Linux, WinMM on Windows). Parsed message types (NoteOn, NoteOff, ControlChange, PitchBend) with handler registration. High-level helpers: midi_note_map() for polyphonic note-to-synth mapping, midi_cc_map() for CC-to-parameter control

  • NodeProxy / Ndef -- live coding with hot-swappable synth definitions. NodeProxy owns a private audio bus, a source synth (with ASR envelope for crossfade), and a monitor synth. Swap the source seamlessly while audio plays. Ndef is a global named proxy registry for concise live-coding workflows

  • Server recording -- Server.record(path) captures real-time audio output to WAV/AIFF via DiskOut. stop_recording() finalizes the file. Configurable channel count, bus, and format

  • Buffer management -- alloc_buffer, read_buffer, write_buffer, free_buffer, zero_buffer, close_buffer, and context managers for automatic cleanup

  • Direct numpy buffer exchange -- get_buffer_data / set_buffer_data / alloc_buffer_from_array copy samples straight between a numpy array and a buffer's in-process memory (a memcpy, no OSC round-trip and no datagram-size limit). This is the payoff of running the engine in-process: cheap two-way data transfer with the numpy/scipy stack that an out-of-process SuperCollider client cannot match. Requires nanosynth[numpy]

  • Introspection & control -- status() (CPU, sample rate, node/synth/group/ugen counts), version(), query_tree() (the live node graph as a nested NodeInfo tree, optionally with control values), dump_tree(), and reset() (panic: free all nodes, clear the scheduler, recreate the default group)

  • Node lifecycle notifications -- enable_notifications() then on_node(callback) to observe /n_go//n_end//n_off//n_on//n_move as NodeEvents, or Synth.wait_free() / wait_for_node_free(id) to block until a node ends -- the only way to know when a self-freeing DoneAction synth has actually finished

  • Reply handling -- bidirectional OSC communication with the engine: persistent handlers (on/off), blocking one-shot waits (wait_for_reply), and send-and-wait (send_msg_sync)

  • SynthDef graph introspection -- SynthDef.graph() returns a structured DAG of UGenNode/UGenInput NamedTuples for programmatic traversal. SynthDef.to_dot() exports to Graphviz DOT format

  • Envelope system -- Envelope class with factory methods (adsr, asr, linen, percussive, triangle) and the EnvGen UGen

  • OSC codec -- pure-Python OscMessage/OscBundle encode/decode with optional C++ acceleration via nanobind

  • @synthdef decorator -- shorthand for defining SynthDefs as plain functions with parameter rate/lag annotations

  • Full type safety -- passes mypy --strict, complete type annotations throughout

Requirements

  • Python 3.10+

  • uv (package manager)

  • For embedded engines: SuperCollider 3.14.1 (both scsynth and supernova), libsndfile, and PortAudio are vendored and built from source automatically. Audio backend: CoreAudio on macOS, PortAudio (ALSA) on Linux, PortAudio (WASAPI) on Windows -- no system-level audio dependencies beyond the compiler toolchain.

Installation

pip install nanosynth

Or build from source:

# Editable install with embedded scsynth + supernova
uv pip install -e .

# Build wheel (incremental -- reuses cmake build cache in build/)
make build

# Install without supernova (scsynth only)
uv pip install -e . -C cmake.define.NANOSYNTH_EMBED_SUPERNOVA=OFF

# Install without any audio engine (OSC codec + SynthDef compiler only)
uv pip install -e . -C cmake.define.NANOSYNTH_EMBED_SCSYNTH=OFF -C cmake.define.NANOSYNTH_EMBED_SUPERNOVA=OFF

Quick Start

Run the Audio Demos

make demos            # scsynth demos
make demos-supernova  # supernova demos

Define a SynthDef and Play It

The Server class manages the embedded engine lifecycle. Define a SynthDef, boot the server, and play:

import time
from nanosynth import Options, Server
from nanosynth.envelopes import EnvGen, Envelope
from nanosynth.synthdef import DoneAction, SynthDefBuilder
from nanosynth.ugens import Out, Pan2, SinOsc

# Define a SynthDef
with SynthDefBuilder(frequency=440.0, amplitude=0.3) as builder:
    sig = SinOsc.ar(frequency=builder["frequency"])
    sig = sig * builder["amplitude"]
    env = EnvGen.kr(
        envelope=Envelope.linen(attack_time=0.1, sustain_time=1.8, release_time=0.1),
        done_action=DoneAction.FREE_SYNTH,
    )
    sig = sig * env
    Out.ar(bus=0, source=Pan2.ar(source=sig))

synthdef = builder.build(name="sine")

# Boot the server, send the SynthDef, create a synth
with Server(Options(verbosity=0)) as server:
    synthdef.send(server)
    time.sleep(0.1)

    node = server.synth("sine", frequency=440.0, amplitude=0.3)
    print(f"Playing 440 Hz sine (node {node}) for 2 seconds...")
    time.sleep(2.0)
    server.free(node)

# Engine shuts down automatically on context exit

Or use SynthDef.play() to send and create a synth in one call:

with Server() as server:
    node = synthdef.play(server, frequency=880.0, amplitude=0.2)
    time.sleep(2.0)

Managed Nodes (Automatic Cleanup)

managed_synth() and managed_group() create nodes that are automatically freed on context exit, even if an exception occurs:

import time
from nanosynth import Server

with Server() as server:
    synthdef.send(server)
    time.sleep(0.1)

    with server.managed_synth("sine", frequency=440.0, amplitude=0.3) as node:
        print(f"Playing node {node}...")
        time.sleep(2.0)
    # node freed automatically here

    # Group multiple voices and free them together
    with server.managed_group(target=1) as group:
        server.synth("sine", target=group, frequency=261.63, amplitude=0.2)
        server.synth("sine", target=group, frequency=329.63, amplitude=0.2)
        server.synth("sine", target=group, frequency=392.00, amplitude=0.2)
        time.sleep(2.0)
    # entire group freed here

Effect Chains with Bus Allocation

Use AddAction to control node execution order and audio_bus() to allocate private buses for effect routing -- no more hardcoded magic bus numbers:

import time
from nanosynth import AddAction, Options, Server

with Server(Options(verbosity=0)) as server:
    src_def.send(server)
    delay_def.send(server)
    time.sleep(0.1)

    # Allocate a private bus for routing source -> effect
    with server.managed_audio_bus(2) as fx_bus:
        # Source group executes first, effect group after
        src_group = server.group(target=1, action=AddAction.ADD_TO_HEAD)
        fx_group = server.group(target=int(src_group), action=AddAction.ADD_AFTER)

        # Effect reads from the allocated bus, writes to hardware output
        server.synth("comb_delay", target=int(fx_group),
                     in_bus=float(int(fx_bus)), delay_time=0.375, mix=0.4)

        # Source writes to the allocated bus
        server.synth("perc_src", target=int(src_group),
                     out_bus=float(int(fx_bus)), frequency=440.0)
        time.sleep(2.0)
    # bus freed automatically

Supernova (Parallel DSP)

Use supernova instead of scsynth to distribute independent synth nodes across CPU cores. The API is identical -- just pass a different protocol:

import time
from nanosynth import AddAction, EmbeddedSupernovaProtocol, Options, Server
from nanosynth.envelopes import EnvGen, Envelope
from nanosynth.synthdef import DoneAction, SynthDefBuilder
from nanosynth.ugens import Out, Pan2, SinOsc

with SynthDefBuilder(frequency=440.0, amplitude=0.3) as builder:
    sig = SinOsc.ar(frequency=builder["frequency"]) * builder["amplitude"]
    env = EnvGen.kr(
        envelope=Envelope.linen(attack_time=0.5, sustain_time=2.0, release_time=0.5),
        done_action=DoneAction.FREE_SYNTH,
    )
    Out.ar(bus=0, source=Pan2.ar(source=sig * env))

voice = builder.build(name="voice")

# Boot supernova instead of scsynth
with Server(
    Options(verbosity=0, load_synthdefs=False),
    protocol=EmbeddedSupernovaProtocol(),
) as server:
    voice.send(server)
    time.sleep(0.1)

    # ParGroup: children execute in parallel across CPU cores
    par = server.par_group(target=1, action=AddAction.ADD_TO_HEAD)

    for freq in [261.63, 329.63, 392.00, 523.25]:
        server.synth("voice", target=par, frequency=freq, amplitude=0.15)

    time.sleep(3.0)

With scsynth, all voices execute sequentially on one audio thread. With supernova, voices inside a ParGroup are distributed across cores -- providing a measurable speedup for dense polyphony and independent effect chains.

Recording

Capture real-time audio output to a file:

import time
from nanosynth import Server

with Server() as server:
    synthdef.send(server)
    time.sleep(0.1)

    # Start recording to WAV
    server.record("output.wav", header_format="wav", sample_format="int16")

    # Play some audio
    node = server.synth("sine", frequency=440.0, amplitude=0.3)
    time.sleep(2.0)
    server.free(node)

    # Stop recording -- finalizes the file
    server.stop_recording()

Recording options include num_channels (defaults to output bus count), bus (which bus to record from), header_format ("wav" or "aiff"), and sample_format ("int16", "int24", "float").

Buffer Data Exchange (numpy)

Because the engine runs in-process, you can move sample data between a numpy array and a buffer's memory with a direct memcpy -- no OSC /b_setn//b_getn round-trip and none of their datagram-size limits. This is the concrete advantage of the embedded engine over an out-of-process SuperCollider client: cheap, arbitrary-size, two-way transfer with the numpy/scipy/ML stack.

Install the optional dependency with pip install nanosynth[numpy].

import numpy as np
from nanosynth import Server

with Server() as server:
    # Load a numpy array straight into a new buffer (allocates + fills).
    wavetable = np.sin(np.linspace(0, 2 * np.pi, 1024, endpoint=False)).astype("float32")
    buffer_id = server.alloc_buffer_from_array(wavetable)

    # Inspect it: (frames, channels, sample_rate)
    frames, channels, sample_rate = server.buffer_info(buffer_id)

    # Read samples back into numpy -- shape (frames, channels), float32.
    data = server.get_buffer_data(buffer_id)

    # Process in numpy and write back in place (shape must match the buffer).
    server.set_buffer_data(buffer_id, np.tanh(data * 2.0))

get_buffer_data always returns a 2-D (frames, channels) array (mono is (frames, 1)); set_buffer_data accepts 1-D (mono) or 2-D input and coerces it to contiguous float32. These methods read and write the live buffer, so for clean results the buffer should not be in active use by a synth during the transfer (a concurrent read/write may tear or glitch -- it never crashes). Direct buffer access is available on the embedded scsynth engine only (not supernova).

Introspection and Control

Query the running engine and inspect or reset its node graph:

from nanosynth import Server

with Server() as server:
    status = server.status()      # CPU load, sample rate, node/synth/ugen counts
    print(status.num_synths, status.actual_sample_rate)

    version = server.version()    # program name and version

    # The live node tree as a nested NodeInfo (groups, synths, control values).
    tree = server.query_tree(controls=True)
    for child in tree.children:
        print(child.node_id, "group" if child.is_group else child.synthdef)

    # "Panic": free all nodes, clear the scheduler, recreate the default group.
    server.reset()

status(), version(), and query_tree() block for a reply and raise EngineError on timeout. reset() frees all nodes and resets node-id allocation but leaves loaded SynthDefs, buffers, and buses intact.

Node Lifecycle Notifications

Register for node events to observe nodes being created, freed, paused, resumed, or moved. This is the only way to know when a synth that frees itself (via a DoneAction envelope) has actually finished:

from nanosynth import Server

with Server() as server:
    synthdef.send(server)
    server.enable_notifications()

    # Observe every node event.
    server.on_node(lambda e: print(e.action, e.node_id))

    # ...or wait for one specific node to free itself.
    node = server.synth("ping", frequency=440.0)  # a self-freeing percussive synth
    if node.wait_free(timeout=3.0):
        print("the synth finished and freed itself")

Each callback receives a NodeEvent with action ("go", "end", "off", "on", "move"), node_id, parent_group_id, is_group, and (for groups) head_node_id/tail_node_id. Synth.on_free(callback) is a one-shot convenience that fires when that specific node ends. Notifications are off by default; enable_notifications() waits for the engine to confirm registration so the first node's /n_go is not missed.

Offline (NRT) Rendering

Render audio to a file without real-time audio hardware -- useful for batch processing, testing, and CI pipelines:

from nanosynth import Score, SynthDefBuilder, DoneAction
from nanosynth.envelopes import EnvGen, Envelope
from nanosynth.ugens import Out, Pan2, SinOsc

# Define a SynthDef
with SynthDefBuilder(freq=440.0, amp=0.3) as builder:
    sig = SinOsc.ar(frequency=builder["freq"]) * builder["amp"]
    env = EnvGen.kr(
        envelope=Envelope.percussive(attack_time=0.01, release_time=0.5),
        done_action=DoneAction.FREE_SYNTH,
    )
    Out.ar(bus=0, source=Pan2.ar(source=sig * env))
sd = builder.build(name="sine")

# Build a Score -- a sequence of timestamped OSC commands
score = Score()
score.add_synthdef(0.0, sd)
score.add_synth(0.0, "sine", freq=440.0, amp=0.3)
score.add_synth(0.5, "sine", freq=554.37, amp=0.2)
score.add_synth(1.0, "sine", freq=659.26, amp=0.2)

# Render to a WAV file (no audio hardware needed)
score.render("output.wav", sample_rate=44100, header_format="WAV", sample_format="int16")

Pattern Sequencing

Replace manual time.sleep() loops with musical patterns. Pbind binds keys to patterns or scalars to produce event streams; Clock drives playback at a given tempo:

import time
from nanosynth import Options, Server
from nanosynth.patterns import Clock, Pbind, Prand, Pseq, Pwhite, Rest

with Server(Options(verbosity=0)) as server:
    # (assume a "default" SynthDef with freq, amp, gate params is loaded)
    clock = Clock(bpm=140)

    # Ascending melody
    melody = Pbind(
        instrument="default",
        freq=Pseq([261.63, 293.66, 329.63, 392.00, 440.00]),
        dur=Pseq([0.5, 0.5, 0.5, 0.5, 1.0]),
        amp=0.2,
    )
    melody.play(clock, server)
    time.sleep(3.0)

    # Randomized melody with rests and dynamic amplitude
    rand_melody = Pbind(
        instrument="default",
        freq=Prand([261.63, 329.63, 392.00, 440.00], repeats=8),
        dur=Pseq([0.25, 0.25, Rest(0.5), 0.5], repeats=2),
        amp=Pwhite(0.1, 0.25, repeats=8),
    )
    player = rand_melody.play(clock, server)
    time.sleep(4.0)

    player.stop()
    clock.stop()

Available patterns: Pseq (sequential), Prand (random choice), Pwhite (uniform random float), Pseries (arithmetic series), Pgeom (geometric series), Pchoose (weighted random), Pn (repeat N times), Pconst (yield until sum reaches total). Patterns support chaining with | and preview with .take(n).

MIDI Input

Connect hardware MIDI controllers. Requires the _midi C extension (built by default with NANOSYNTH_EMBED_MIDI=ON):

import time
from nanosynth import Options, Server
from nanosynth.midi import MidiIn, midi_note_map, midi_cc_map

# List available MIDI ports
print(MidiIn.list_ports())

with Server(Options(verbosity=0)) as server:
    # (assume a gated SynthDef "synth" is loaded)

    with MidiIn(port=0) as midi:
        # Polyphonic note mapping: note-on creates synth, note-off sends gate=0
        cleanup_notes = midi_note_map(midi, server, "synth")

        # Map CC1 (mod wheel) to a parameter on an existing synth
        # cleanup_cc = midi_cc_map(midi, server, some_synth,
        #                          cc_map={1: "cutoff"}, range_min=200.0, range_max=8000.0)

        # Or register handlers directly
        midi.on_note_on(lambda msg: print(f"Note {msg.note} vel {msg.velocity}"))
        midi.on_cc(lambda msg: print(f"CC {msg.control} = {msg.value}"))

        input("Press Enter to quit...")
        cleanup_notes()

NodeProxy / Ndef (Live Coding)

Hot-swap synth definitions while audio plays. NodeProxy manages a private audio bus, source synth, and monitor synth -- swapping replaces only the source with a crossfade:

import time
from nanosynth import Options, Server
from nanosynth.proxy import Ndef, NodeProxy
from nanosynth.ugens import LFNoise1, LPF, Saw, SinOsc

with Server(Options(verbosity=0)) as server:
    # NodeProxy: manual usage
    proxy = NodeProxy(server)
    proxy.source = lambda: SinOsc.ar(frequency=440) * 0.2
    proxy.play()
    time.sleep(2.0)

    # Hot-swap to saw wave (crossfades automatically)
    proxy.source = lambda: Saw.ar(frequency=330) * 0.15
    time.sleep(2.0)

    proxy.clear()

    # Ndef: concise named proxy registry
    Ndef(server, "pad", lambda: SinOsc.ar(frequency=220) * 0.2)
    Ndef(server, "pad").play()
    time.sleep(1.5)

    # Hot-swap via Ndef
    Ndef(server, "pad", lambda: Saw.ar(frequency=165) * 0.15)
    time.sleep(1.5)

    Ndef.clear_all(server)

Synthesis Techniques

The following examples show SynthDef definitions for various synthesis techniques. Each can be played using the Server class as shown above.

Using the @synthdef Decorator

For simpler definitions, use the decorator to skip the builder boilerplate. Parameter rates and lags are specified positionally:

from nanosynth import synthdef, DoneAction
from nanosynth.envelopes import EnvGen, Envelope
from nanosynth.ugens import Out, Pan2, SinOsc

@synthdef("kr", ("kr", 0.5))  # freq: control rate, amp: control rate with 0.5s lag
def my_sine(freq=440.0, amp=0.3):
    sig = SinOsc.ar(frequency=freq)
    env = EnvGen.kr(
        envelope=Envelope.percussive(attack_time=0.01, release_time=1.0),
        done_action=DoneAction.FREE_SYNTH,
    )
    Out.ar(bus=0, source=Pan2.ar(source=sig * amp * env))

scgf_bytes = my_sine.compile()  # my_sine is a SynthDef instance

Subtractive Synthesis

from nanosynth import SynthDefBuilder, DoneAction
from nanosynth.envelopes import EnvGen, Envelope
from nanosynth.ugens import LFNoise1, LPF, Out, Pan2, RLPF, Saw, WhiteNoise, XLine

# Saw wave through a sweeping low-pass filter
with SynthDefBuilder(frequency=110.0, amplitude=0.4) as builder:
    sig = Saw.ar(frequency=builder["frequency"])
    cutoff = XLine.kr(start=8000.0, stop=200.0, duration=3.0,
                      done_action=DoneAction.FREE_SYNTH)
    sig = LPF.ar(source=sig, frequency=cutoff)
    sig = sig * builder["amplitude"]
    Out.ar(bus=0, source=Pan2.ar(source=sig))

filtered_saw = builder.build(name="filtered_saw")

# White noise through a resonant LPF with LFO-modulated cutoff
with SynthDefBuilder(amplitude=0.15) as builder:
    sig = WhiteNoise.ar()
    lfo = LFNoise1.kr(frequency=4.0)
    cutoff = lfo * 1900.0 + 2100.0  # map [-1,1] to [200, 4000]
    sig = RLPF.ar(source=sig, frequency=cutoff, reciprocal_of_q=0.1)
    env = EnvGen.kr(
        envelope=Envelope.linen(attack_time=0.5, sustain_time=2.0, release_time=0.5),
        done_action=DoneAction.FREE_SYNTH,
    )
    sig = sig * env * builder["amplitude"]
    Out.ar(bus=0, source=Pan2.ar(source=sig))

resonant_noise = builder.build(name="resonant_noise")

FM Synthesis

from nanosynth import SynthDefBuilder, DoneAction
from nanosynth.envelopes import EnvGen, Envelope
from nanosynth.ugens import Out, Pan2, SinOsc

with SynthDefBuilder(
    carrier_freq=440.0, mod_ratio=2.0, mod_index=3.0,
    amplitude=0.3, gate=1.0,
) as builder:
    mod_freq = builder["carrier_freq"] * builder["mod_ratio"]
    modulator = SinOsc.ar(frequency=mod_freq) * builder["mod_index"] * mod_freq
    carrier = SinOsc.ar(frequency=builder["carrier_freq"] + modulator)
    env = EnvGen.kr(
        envelope=Envelope.adsr(
            attack_time=0.01, decay_time=0.1, sustain=0.7, release_time=0.3,
        ),
        gate=builder["gate"],
        done_action=DoneAction.FREE_SYNTH,
    )
    sig = carrier * env * builder["amplitude"]
    Out.ar(bus=0, source=Pan2.ar(source=sig))

fm_synth = builder.build(name="fm_synth")

Additive Synthesis

Sum harmonics with decreasing amplitude to build a rich tone from pure sine partials:

from nanosynth import SynthDefBuilder, DoneAction
from nanosynth.envelopes import EnvGen, Envelope
from nanosynth.ugens import Out, Pan2, SinOsc

with SynthDefBuilder(frequency=200.0, amplitude=0.3) as builder:
    sig = SinOsc.ar(frequency=builder["frequency"]) * 1.0
    sig = sig + SinOsc.ar(frequency=builder["frequency"] * 2.0) * 0.5
    sig = sig + SinOsc.ar(frequency=builder["frequency"] * 3.0) * 0.33
    sig = sig + SinOsc.ar(frequency=builder["frequency"] * 4.0) * 0.25
    sig = sig + SinOsc.ar(frequency=builder["frequency"] * 5.0) * 0.2
    sig = sig * 0.3  # normalize
    env = EnvGen.kr(
        envelope=Envelope.percussive(attack_time=0.01, release_time=2.0),
        done_action=DoneAction.FREE_SYNTH,
    )
    sig = sig * env * builder["amplitude"]
    Out.ar(bus=0, source=Pan2.ar(source=sig))

additive = builder.build(name="additive")

Plucked String (Physical Modeling)

Karplus-Strong style plucked string using the Pluck UGen:

from nanosynth import SynthDefBuilder, DoneAction
from nanosynth.envelopes import EnvGen, Envelope
from nanosynth.ugens import Dust, Out, Pan2, Pluck, WhiteNoise

with SynthDefBuilder(frequency=440.0, amplitude=0.5, decay=5.0) as builder:
    trig = Dust.ar(density=1.0)
    sig = Pluck.ar(
        source=WhiteNoise.ar(),
        trigger=trig,
        maximum_delay_time=1.0 / 100.0,
        delay_time=1.0 / builder["frequency"],
        decay_time=builder["decay"],
        coefficient=0.3,
    )
    sig = sig * builder["amplitude"]
    Out.ar(bus=0, source=Pan2.ar(source=sig))

pluck = builder.build(name="plucked_string")

Delay and Reverb Effects

Process a dry signal through comb delay and FreeVerb:

from nanosynth import SynthDefBuilder, DoneAction
from nanosynth.envelopes import EnvGen, Envelope
from nanosynth.ugens import CombC, FreeVerb, Out, Pan2, Saw, LPF

with SynthDefBuilder(frequency=220.0, amplitude=0.3) as builder:
    # Dry signal: filtered saw
    dry = Saw.ar(frequency=builder["frequency"])
    dry = LPF.ar(source=dry, frequency=2000.0)
    env = EnvGen.kr(
        envelope=Envelope.percussive(attack_time=0.005, release_time=0.3),
        done_action=DoneAction.FREE_SYNTH,
    )
    dry = dry * env * builder["amplitude"]

    # Comb delay for metallic echo
    sig = CombC.ar(
        source=dry,
        maximum_delay_time=0.2,
        delay_time=0.15,
        decay_time=2.0,
    )

    # Reverb
    sig = FreeVerb.ar(source=dry + sig, mix=0.4, room_size=0.8, damping=0.3)
    Out.ar(bus=0, source=Pan2.ar(source=sig))

delay_reverb = builder.build(name="delay_reverb")

Demand-Rate Sequencing

Use demand UGens to sequence pitches without host-side scheduling:

from nanosynth import SynthDefBuilder, DoneAction
from nanosynth.envelopes import EnvGen, Envelope
from nanosynth.ugens import Duty, Dseq, Out, Pan2, SinOsc

with SynthDefBuilder(amplitude=0.3) as builder:
    # Dseq loops a sequence of MIDI-note frequencies at demand rate
    freq_pattern = Dseq.dr(
        repeats=4,
        sequence=[261.63, 293.66, 329.63, 392.00, 440.00, 392.00, 329.63, 293.66],
    )
    # Duty reads from the demand pattern every 0.25 seconds
    freq = Duty.kr(duration=0.25, level=freq_pattern)
    sig = SinOsc.ar(frequency=freq) * builder["amplitude"]
    env = EnvGen.kr(
        envelope=Envelope.linen(attack_time=0.01, sustain_time=7.9, release_time=0.1),
        done_action=DoneAction.FREE_SYNTH,
    )
    sig = sig * env
    Out.ar(bus=0, source=Pan2.ar(source=sig))

sequencer = builder.build(name="sequencer")

Ring Modulation

Multiply two signals together for classic ring modulation:

from nanosynth import SynthDefBuilder, DoneAction
from nanosynth.envelopes import EnvGen, Envelope
from nanosynth.ugens import LFTri, Out, Pan2, SinOsc

with SynthDefBuilder(
    carrier_freq=440.0, mod_freq=60.0, amplitude=0.3,
) as builder:
    carrier = SinOsc.ar(frequency=builder["carrier_freq"])
    modulator = LFTri.ar(frequency=builder["mod_freq"])
    sig = carrier * modulator  # ring mod = simple multiplication
    env = EnvGen.kr(
        envelope=Envelope.linen(attack_time=0.05, sustain_time=2.0, release_time=0.5),
        done_action=DoneAction.FREE_SYNTH,
    )
    sig = sig * env * builder["amplitude"]
    Out.ar(bus=0, source=Pan2.ar(source=sig))

ring_mod = builder.build(name="ring_mod")

Stereo Width with Detuning

Fatten a sound by panning two slightly detuned oscillators:

from nanosynth import SynthDefBuilder, DoneAction
from nanosynth.envelopes import EnvGen, Envelope
from nanosynth.ugens import LPF, Out, Saw

with SynthDefBuilder(frequency=110.0, detune=0.5, amplitude=0.4) as builder:
    left = Saw.ar(frequency=builder["frequency"] - builder["detune"])
    right = Saw.ar(frequency=builder["frequency"] + builder["detune"])
    left = LPF.ar(source=left, frequency=3000.0)
    right = LPF.ar(source=right, frequency=3000.0)
    env = EnvGen.kr(
        envelope=Envelope.linen(attack_time=0.1, sustain_time=2.0, release_time=0.5),
        done_action=DoneAction.FREE_SYNTH,
    )
    left = left * env * builder["amplitude"]
    right = right * env * builder["amplitude"]
    Out.ar(bus=0, source=[left, right])  # direct stereo output

stereo_saw = builder.build(name="stereo_saw")

Dynamics Processing

Apply compression to a signal using Compander:

from nanosynth import SynthDefBuilder, DoneAction
from nanosynth.envelopes import EnvGen, Envelope
from nanosynth.ugens import Compander, Dust, Out, Pan2, Ringz

with SynthDefBuilder(amplitude=0.5) as builder:
    # Sparse impulses through a resonant filter -- wide dynamic range
    sig = Ringz.ar(
        source=Dust.ar(density=3.0),
        frequency=2000.0,
        decay_time=0.2,
    )
    # Compress: bring quiet parts up, loud parts down
    sig = Compander.ar(
        source=sig,
        control=sig,
        threshold=0.3,
        slope_below=2.0,   # expand below threshold
        slope_above=0.5,   # compress above threshold
        clamp_time=0.01,
        relax_time=0.1,
    )
    env = EnvGen.kr(
        envelope=Envelope.linen(attack_time=0.01, sustain_time=3.0, release_time=0.5),
        done_action=DoneAction.FREE_SYNTH,
    )
    sig = sig * env * builder["amplitude"]
    Out.ar(bus=0, source=Pan2.ar(source=sig))

compressed = builder.build(name="compressed")

Advanced Features

SynthDef Compilation (No Engine Required)

SynthDef graphs can be compiled to SuperCollider's SCgf binary format without booting the audio engine -- useful for generating SynthDefs for any SuperCollider server:

from nanosynth import SynthDefBuilder, compile_synthdefs
from nanosynth.ugens import Out, Pan2, SinOsc

with SynthDefBuilder(frequency=440.0) as builder:
    Out.ar(bus=0, source=Pan2.ar(source=SinOsc.ar(frequency=builder["frequency"])))

synthdef = builder.build(name="sine")
scgf_bytes = synthdef.compile()

# Or compile multiple SynthDefs into a single SCgf blob
blob = compile_synthdefs(synthdef1, synthdef2, synthdef3)

Debugging SynthDef Graphs

SynthDef.dump_ugens() prints a human-readable UGen graph (like SuperCollider's SynthDef.dumpUGens):

print(synthdef.dump_ugens())
# SynthDef: sine
#   0: Control.kr - frequency, amplitude
#   1: SinOsc.ar(frequency: Control[0], phase: 0.0)
#   2: BinaryOpUGen.ar(MULTIPLICATION, a: SinOsc[0], b: Control[1])
#   ...

SynthDef Graph Introspection

Walk the UGen graph programmatically or export to Graphviz DOT format:

# Structured graph -- returns UGenNode/UGenInput NamedTuples
graph = sd.graph()
for node in graph.nodes:
    print(f"{node.node_index}: {node.type_name}.{node.rate}")
    for inp in node.inputs:
        if inp.source is not None:
            print(f"  {inp.name} <- {inp.source.type_name}[{inp.output_index}]")
        else:
            print(f"  {inp.name} = {inp.value}")

# Export to Graphviz DOT
dot = sd.to_dot(rankdir="LR")
print(dot)  # pipe to `dot -Tpng -o graph.png`

OSC Codec

The OSC module works standalone for any OSC communication needs:

from nanosynth import OscMessage, OscBundle

# Encode
msg = OscMessage("/s_new", "sine", 1000, 0, 1, "frequency", 440.0)
datagram = msg.to_datagram()

# Decode
decoded = OscMessage.from_datagram(datagram)
assert decoded == msg

# Bundles
bundle = OscBundle(
    timestamp=None,  # immediately
    contents=[
        OscMessage("/s_new", "sine", 1000, 0, 1),
        OscMessage("/n_set", 1000, "frequency", 880.0),
    ],
)
bundle_bytes = bundle.to_datagram()

Available UGens

Organized by category:

Category UGens
Oscillators SinOsc, Saw, Pulse, Blip, Klank, LFSaw, LFPulse, LFTri, LFCub, LFPar, VarSaw, SyncSaw, Impulse, FSinOsc, LFGauss, Vibrato, Osc, OscN, COsc, VOsc, VOsc3
Filters LPF, HPF, BPF, BRF, RLPF, RHPF, MoogFF, Lag, Lag2, Lag3, LagUD, Lag2UD, Lag3UD, Ramp, Decay, Decay2, Ringz, Formlet, Median, LeakDC, OnePole, OneZero, TwoPole, TwoZero, APF, FOS, SOS, MidEQ, Slew, Slope, Integrator, DetectSilence, Changed
BEQ Filters BLowPass, BHiPass, BBandPass, BBandStop, BAllPass, BLowShelf, BHiShelf, BPeakEQ, BLowCut, BHiCut
Noise WhiteNoise, PinkNoise, BrownNoise, GrayNoise, ClipNoise, Dust, Dust2, Crackle, LFNoise0, LFNoise1, LFNoise2, LFDNoise0, LFDNoise1, LFDNoise3, LFClipNoise, LFDClipNoise, Logistic
Stochastic Gendy1, Gendy2, Gendy3
Delays DelayN, DelayL, DelayC, Delay1, Delay2, CombN, CombL, CombC, AllpassN, AllpassL, AllpassC, BufDelayN, BufDelayL, BufDelayC, BufCombN, BufCombL, BufCombC, BufAllpassN, BufAllpassL, BufAllpassC, DelTapRd, DelTapWr
Envelopes EnvGen, Linen, Done, Free, FreeSelf, FreeSelfWhenDone, Pause, PauseSelf, PauseSelfWhenDone
Panning Pan2, Pan4, PanAz, PanB, PanB2, BiPanB2, Balance2, Rotate2, DecodeB2, XFade2, Splay
Demand Dseq, Dser, Dseries, Drand, Dxrand, Dshuf, Dwrand, Dwhite, Dbrown, Diwhite, Dibrown, Dgeom, Demand, Duty, DemandEnvGen, Dbufrd, Dbufwr, Dstutter, Dreset, Dswitch, Dswitch1, Dunique
Dynamics Compander, CompanderD, Limiter, Normalizer, Amplitude
Chaos LorenzL, HenonN/L/C, GbmanN/L, LatoocarfianN/L/C, LinCongN/L/C, CuspN/L, QuadN/L/C, StandardN/L, FBSineN/L/C
Granular GrainBuf, GrainIn, PitchShift, Warp1
Buffer I/O PlayBuf, RecordBuf, BufRd, BufWr, ClearBuf, LocalBuf, MaxLocalBufs, ScopeOut, ScopeOut2
Disk I/O DiskIn, DiskOut, VDiskIn
Physical Modeling Pluck, Ball, TBall, Spring
Reverb FreeVerb
Convolution Convolution, Convolution2, Convolution2L, Convolution3
Phase Vocoder FFT, IFFT, PV_Add, PV_BinScramble, PV_BinShift, PV_BinWipe, PV_BrickWall, PV_ConformalMap, PV_Conj, PV_Copy, PV_CopyPhase, PV_Diffuser, PV_Div, PV_HainsworthFoote, PV_JensenAndersen, PV_LocalMax, PV_MagAbove, PV_MagBelow, PV_MagClip, PV_MagDiv, PV_MagFreeze, PV_MagMul, PV_MagNoise, PV_MagShift, PV_MagSmear, PV_MagSquared, PV_Max, PV_Min, PV_Mul, PV_PhaseShift, PV_PhaseShift90, PV_PhaseShift270, PV_RandComb, PV_RandWipe, PV_RectComb, PV_RectComb2, RunningSum
Machine Listening BeatTrack, BeatTrack2, KeyTrack, Loudness, MFCC, Onsets, Pitch, SpecCentroid, SpecFlatness, SpecPcile
Hilbert FreqShift, Hilbert, HilbertFIR
I/O In, Out, InFeedback, LocalIn, LocalOut, OffsetOut, ReplaceOut, XOut
Lines Line, XLine, LinExp, LinLin, DC, K2A, A2K, AmpComp, AmpCompA, Silence
Triggers Trig, Trig1, Latch, Gate, Schmidt, Sweep, Phasor, Peak, PeakFollower, RunningMax, RunningMin, SendTrig, Poll, SendReply, SendPeakRMS, ToggleFF, TDelay, ZeroCrossing, LeastChange, MostChange, Clip, Fold, Wrap, InRange
Mouse/Keyboard KeyState, MouseButton, MouseX, MouseY
Info SampleRate, SampleDur, BlockSize, ControlRate, ControlDur, SubsampleOffset, RadiansPerSample, NumRunningSynths, BufFrames, BufSamples, BufSampleRate, BufRateScale, BufChannels, BufDur, NumOutputBuses, NumInputBuses, NumAudioBuses, NumControlBuses, NumBuffers, NodeID
Random Rand, IRand, ExpRand, LinRand, NRand, TRand, TIRand, TExpRand, CoinGate, TWindex, RandID, RandSeed, Hasher, MantissaMask
Utility MulAdd, Sum3, Sum4, Mix
Safety CheckBadValues, Sanitize

Envelope Types

from nanosynth import Envelope

Envelope.adsr(attack_time=0.01, decay_time=0.3, sustain=0.5, release_time=1.0)
Envelope.asr(attack_time=0.01, sustain=1.0, release_time=1.0)
Envelope.linen(attack_time=0.01, sustain_time=1.0, release_time=1.0)
Envelope.percussive(attack_time=0.01, release_time=1.0)
Envelope.triangle(duration=1.0, amplitude=1.0)

# Custom envelope
Envelope(amplitudes=[0, 1, 0.5, 0], durations=[0.1, 0.3, 0.6], curves=[-4])

Documentation

API reference docs are auto-generated from docstrings using mkdocs-material and mkdocstrings.

make docs        # build static site to site/
make docs-serve  # serve locally at http://127.0.0.1:8000 with live reload
make docs-deploy # deploy to GitHub Pages

Browse the docs at shakfu.github.io/nanosynth.

Development

make dev              # uv sync + editable install
make build            # build wheel (incremental via build cache)
make sdist            # build source distribution
make test             # run tests
make lint             # ruff check --fix
make format           # ruff format
make typecheck        # mypy --strict
make qa               # all of the above
make demos            # run scsynth demo scripts
make demos-supernova  # run supernova demo scripts
make clean            # remove transitory files (preserves build cache)
make reset            # clean everything including build cache

CI

The GitHub Actions workflow (.github/workflows/build.yml) builds wheels for CPython 3.10--3.14 on macOS ARM64, Linux x86_64, and Windows x86_64 using cibuildwheel. A qa job runs lint, format check, typecheck, and tests on every push. A source distribution ('sdist') is built separately and all artifacts are aggregated into a single downloadable archive.

A separate release workflow (.github/workflows/release.yml) publishes to PyPI on tag push via trusted publisher, with manual dispatch for TestPyPI.

Attributions

  • SuperCollider -- the audio synthesis engine and programming language that nanosynth embeds.
  • supriya -- the inspiration for nanosynth; its UGen system and SynthDef compiler were the basis for this project's graph compilation pipeline.
  • sc3 - Another SuperCollider library for Python with less features than supriya.
  • TidalCycles -- live coding pattern language for music, built on SuperCollider.
  • Strudel -- JavaScript port of TidalCycles for browser-based live coding.
  • Sonic Pi -- live coding music synth built on SuperCollider.
  • RtMidi -- cross-platform MIDI I/O library, vendored for the _midi extension.
  • nanobind -- the C++/Python binding library used to embed libscsynth, RtiMidi and the OSC codec.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nanosynth-0.2.0.tar.gz (7.2 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

nanosynth-0.2.0-cp314-cp314-win_amd64.whl (2.7 MB view details)

Uploaded CPython 3.14Windows x86-64

nanosynth-0.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (3.9 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

nanosynth-0.2.0-cp314-cp314-macosx_11_0_arm64.whl (2.3 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

nanosynth-0.2.0-cp313-cp313-win_amd64.whl (2.6 MB view details)

Uploaded CPython 3.13Windows x86-64

nanosynth-0.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (3.9 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

nanosynth-0.2.0-cp313-cp313-macosx_11_0_arm64.whl (2.3 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

nanosynth-0.2.0-cp312-cp312-win_amd64.whl (2.6 MB view details)

Uploaded CPython 3.12Windows x86-64

nanosynth-0.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (3.9 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

nanosynth-0.2.0-cp312-cp312-macosx_11_0_arm64.whl (2.3 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

nanosynth-0.2.0-cp311-cp311-win_amd64.whl (2.6 MB view details)

Uploaded CPython 3.11Windows x86-64

nanosynth-0.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (4.0 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

nanosynth-0.2.0-cp311-cp311-macosx_11_0_arm64.whl (2.3 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

nanosynth-0.2.0-cp310-cp310-win_amd64.whl (2.6 MB view details)

Uploaded CPython 3.10Windows x86-64

nanosynth-0.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (4.0 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

nanosynth-0.2.0-cp310-cp310-macosx_11_0_arm64.whl (2.3 MB view details)

Uploaded CPython 3.10macOS 11.0+ ARM64

File details

Details for the file nanosynth-0.2.0.tar.gz.

File metadata

  • Download URL: nanosynth-0.2.0.tar.gz
  • Upload date:
  • Size: 7.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for nanosynth-0.2.0.tar.gz
Algorithm Hash digest
SHA256 6ae46e3c86aa5960982b399d3d45e5361373e5d118c84886eb9467c6dfa4f449
MD5 18d71a044f87eba849b4635d5972515e
BLAKE2b-256 482e946010f9a39659ddefb6a9bf3f2bb2e21c1171d598c05402054e272aacca

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: nanosynth-0.2.0-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 2.7 MB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for nanosynth-0.2.0-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 7ebf55abc31e7cd2503bbaa94213e4649c2f6ec6a7f3a52c0066b4bdef466602
MD5 45b446a2a59ad447b061b872b44015d9
BLAKE2b-256 f0dc8b8e2b6a5b98c5f2c44d589ad67456cf38af93de720c6d95287cbf9a8f6e

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for nanosynth-0.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 d73600e3263b3a2caaacc97fff76df81c8b9b9d6368a52c23f6480c20a8c7be6
MD5 d0b8059db3ec30cc6901c9975151ca7c
BLAKE2b-256 1eec1d9b4ba319e242efd9862723748e04a138fce3168142f55f60ccb70e3c1a

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for nanosynth-0.2.0-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 21c87928b05a45ca60bf1ea136f44ab8d4bd361c40026940df469698427726f4
MD5 bcee57447364595ad68d3873903ba8e9
BLAKE2b-256 e6922a0c728defc90adf29bacb20c78dfbaea2c7b020456416c35f4c521c977e

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: nanosynth-0.2.0-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 2.6 MB
  • Tags: CPython 3.13, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for nanosynth-0.2.0-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 d091941359d517c418d6d2ae905794c4a710dfc66473d92b2f67167925d96eb8
MD5 42c4a1abb48d6970cd3391e2459e446f
BLAKE2b-256 4d61f0e07861cdf94f5ee4a9df8a0c08610dcfdce72dc9ba4010eed315fde1a2

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for nanosynth-0.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 7b66b95a1f9faa0c89d13626450a6f8060181f59432e192352f49471bf2b094c
MD5 3d279cd7ffddd48af28bc613aecce0c4
BLAKE2b-256 c04a104623526fb6240c6882869d97d335e7ff988ca66f1d2e157fb72ef79f3e

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for nanosynth-0.2.0-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 8d7f60dc65b4165f6a1bd7b664e146235700a9f65468cc934f79a910a3f0d9d7
MD5 26b05decca49d89f1c93410a8d1f6425
BLAKE2b-256 4327c8492f750cdab5e9522144418dbef95eaa72d7987437d9416f8dd8e560e6

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: nanosynth-0.2.0-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 2.6 MB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for nanosynth-0.2.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 8802d28680b012eec2688cb8aa59cdf20d8fd809c62083e2c94ddc30d5dfb21b
MD5 cf8657d8564a54e8140576272e72bb4c
BLAKE2b-256 917a4f2b284f619e3ebdbfb08b478e85033b3baf9b28ba61b23729afd9c07d21

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for nanosynth-0.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 7ee43bb012a766542f969302e6989dc865406ece8dde9f6bff6b83cd931c839d
MD5 c7d9ac7e2c87559ae6ea3b805924ac9c
BLAKE2b-256 d07a67f3fc559fa7a3dcd44900b2f2ed650dcfe7761305a46b473975dcf841da

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for nanosynth-0.2.0-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 15ab4ca10106d46a3656334aba9f8700162f39fefd745888b79a6ffebc5dae59
MD5 098a0e49e761b6cecf2cbf655d089def
BLAKE2b-256 6048a4ca61658d256980aacb3fdb981512c75af5994cfb4d23ecb978c858843c

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: nanosynth-0.2.0-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 2.6 MB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for nanosynth-0.2.0-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 f4c695cc5e455ad0539adef23e3f24a9bd0c2d87a1a0ed21f46f2c156e2be66a
MD5 2c7e6c1b29b083e111d5a14b87d6ccfa
BLAKE2b-256 985d7cd8f47f335929a34fa16c08ea05bdd511e452a4e483415c3afe808b2ca2

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for nanosynth-0.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 4ee77078da2f99e62d709c8f9c4d3d91a81d6df24ef7e5455e5c8b9b2f40ad28
MD5 dbb43cf10ceec35c046593fa55474281
BLAKE2b-256 75471aeacae570e24484e8bacf9e3e6a1b022dd29b3f88b097eacf246b4e8640

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for nanosynth-0.2.0-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 7d38f07839a51d36d11c8fed5f87e9a622e0196a37fd2ec9413a47bc158aa925
MD5 ddc95d5b936766c3ba5ea4adcb27c063
BLAKE2b-256 7e275d258da4805cdfc4d5df280d92e32a0a7d3a4a2e60dd2285515fdc676615

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: nanosynth-0.2.0-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 2.6 MB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for nanosynth-0.2.0-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 20e5bbf6dbb12c007515581a9a7d272713883c619f7ac0218170f547cd120744
MD5 0395dd70b715f6ab083b5ea7d5c7675e
BLAKE2b-256 681a9fe02e56d6f830c18ed6551a3dadb612d4c70d8bade0311242727638a2b5

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for nanosynth-0.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 563153d341640375d5b52d20c1c139bdccd519ad3308c294ef5c35bfe2d08eb6
MD5 289435cac5613822974070723487a37f
BLAKE2b-256 e6932143abb09a788c2697e8163b77b85aef5b742baaa640f388647e0aa24b91

See more details on using hashes here.

File details

Details for the file nanosynth-0.2.0-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for nanosynth-0.2.0-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 3f8f8e3809b154955650ddf218d8ab769d56d7c0b95f23b4c8ea80273e4901fb
MD5 e3197c4c2719e5a8aa6a5fb02fb6da29
BLAKE2b-256 4aefed14741057f75afb319078d06762cb6a24e2f62b3efee0cbcb5def6e2d4c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page