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.1.tar.gz (7.3 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.1-cp314-cp314-win_amd64.whl (2.7 MB view details)

Uploaded CPython 3.14Windows x86-64

nanosynth-0.2.1-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.1-cp314-cp314-macosx_11_0_arm64.whl (2.4 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

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

Uploaded CPython 3.13Windows x86-64

nanosynth-0.2.1-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.1-cp313-cp313-macosx_11_0_arm64.whl (2.4 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

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

Uploaded CPython 3.12Windows x86-64

nanosynth-0.2.1-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.1-cp312-cp312-macosx_11_0_arm64.whl (2.4 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

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

Uploaded CPython 3.11Windows x86-64

nanosynth-0.2.1-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.1-cp311-cp311-macosx_11_0_arm64.whl (2.4 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

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

Uploaded CPython 3.10Windows x86-64

nanosynth-0.2.1-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.1-cp310-cp310-macosx_11_0_arm64.whl (2.4 MB view details)

Uploaded CPython 3.10macOS 11.0+ ARM64

File details

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

File metadata

  • Download URL: nanosynth-0.2.1.tar.gz
  • Upload date:
  • Size: 7.3 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.1.tar.gz
Algorithm Hash digest
SHA256 b387beac540658ebf89dbd5b801e57b307476f0bcc2d15ebb5f72e0c4a9421eb
MD5 64a674a847e63afe775c1acbdc014bb2
BLAKE2b-256 00089ac1f028fb5c221e89f87f04f48db5e9afb5dbdaad2b0c8a68707bfbe70f

See more details on using hashes here.

File details

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

File metadata

  • Download URL: nanosynth-0.2.1-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.1-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 b660be0ea6d2c83a8581a37a1d40f81ce2ac6266cbcc41c5def85efad57cf1f2
MD5 f321fc196e99ae8b74fc8ab084a92a20
BLAKE2b-256 29e568d9f02237abe456a956a0d18e9a296250ad14332aeb9b5a5ddeaf0cfe2d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for nanosynth-0.2.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 6f38d58d6965518600274a5ce7e555237c3d60f6403aa1343cdd367f210b86ea
MD5 fea63d501988edd1cf8e1ba345ee8c5a
BLAKE2b-256 be7238cf7b3ba446cf45e22f3628422cb3e9b624707e5380acc6b34374316eee

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for nanosynth-0.2.1-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 5b2c6108c03e176e33630fcf690d378a41be44ec507db79e8a2e2d8ab99d9436
MD5 cb77e72f12d7740e49f2a2e59c44522a
BLAKE2b-256 8c59e2f7f660e1dfb6ad83da6a37f64fb732846fa797b3ebe79f2e31be7ea9f6

See more details on using hashes here.

File details

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

File metadata

  • Download URL: nanosynth-0.2.1-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.1-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 6d95ba15cf2675c6efbad597f4387c5b3a48023681c9dfec19ecad4b79fd8bda
MD5 5edaa44cd3dc88fb8a3dc7b5f9bcc0d0
BLAKE2b-256 5b8c0240c4aa004d28a09480bc0e51905aa2d8051294e3555640ea5d77c23c96

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for nanosynth-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 4445368c2be18e831b354d9dd82d916da7f7c048265f2c9fce7aece870333f85
MD5 f1ace888c1509b6c834333ac1e315c2a
BLAKE2b-256 aec1dbb81deacf43b1459279c84fc393aa931d724627dae80d24ff93110a73ef

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for nanosynth-0.2.1-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d503afbaaa75869623b3ec33be57d65e69bb7237ccc484f6339d025e8e3059d9
MD5 8beca1d33dcfe93d9717f181ed0d7704
BLAKE2b-256 d2d2211b46559e607994c90a9831c280ca9980f2af86f75235d0718fb57f73f7

See more details on using hashes here.

File details

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

File metadata

  • Download URL: nanosynth-0.2.1-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.1-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 fa7dccfea5ed85f0ca34fd1b4a6cc7eb62f988f95659ef3bfa3e8d64d53cf14b
MD5 e49d5b02fe8e1b16e22c0a2df966a029
BLAKE2b-256 4553c7f7956a5cb168cafcabbff2fb96b8a2a890919a9526e8b35de628fdf43e

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for nanosynth-0.2.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 83abaa4f2cb2c0478b9801166055b026194171faeac13cef12fe4bdb68e1c01b
MD5 1c0817358593219374df209935e62fe8
BLAKE2b-256 014fb50115ad6fe0896e460bd9de2e8def992ada091c029a0ec4eac12cbdb5ec

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for nanosynth-0.2.1-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1b997d73c44f0cce654b2a8735ebbb88acb53c1c51f57b23e97c6cf04b6ed221
MD5 16f2fcb2e2a0da82ac1c7180635ee6cd
BLAKE2b-256 106b6d0d9d6b76b8106e5ca24f2bddd46a9a432bdd6c52b967a4ec36bbf91435

See more details on using hashes here.

File details

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

File metadata

  • Download URL: nanosynth-0.2.1-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.1-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 4254ce5840c0a15dccc280e0814203d59610e30631792718cfd02dfc4c057761
MD5 4a49a104dec6b5b2b4e1c3ef83ee1b02
BLAKE2b-256 fa224070dd225f4f2502dbdc04afa8bbc5a2a3de96a524be8688a27060239c81

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for nanosynth-0.2.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 9346518a01d960d9f7f5bd09afc88c754b527fa7428bf1b06ef121b3027bede2
MD5 c8b8daaed68e96fdd1341a99ab0b1a56
BLAKE2b-256 2d29c85d537069b2a5738720a432b19b024ca7d638be8389c01ab86e8f3161c2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for nanosynth-0.2.1-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 3e471ce9ba217c84581ad3d557195a7367cedc736b5a9a7d798daefed45bcc68
MD5 d67da5bc158582a64d32b58721eb8073
BLAKE2b-256 8f70409ebc4015da8bba955a745ec7452bcbf278f119340ea0d6ae5cf6fb3a93

See more details on using hashes here.

File details

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

File metadata

  • Download URL: nanosynth-0.2.1-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.1-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 d4f2b39f6701e7bfa6fafd4124a4df515d28a6cdf57262094a04cab86b82358b
MD5 534c23344c90751b0bfa1d9fd90163b0
BLAKE2b-256 713d0d5c8fbe46b97223fdc39556a13e7566fa9ec72d3fe2b6e026a58739c1bd

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for nanosynth-0.2.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e3151683e45454947f91f69b1753fa11c8a8b91b4247a3472f52040b1c5beff4
MD5 01c4d2058ddc40dc51003abcbd13e83a
BLAKE2b-256 270c71f795db266084d2fe602ccae1dec4bbcf37debf8ccfcdc16ff28dc83969

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for nanosynth-0.2.1-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 4a6a08e1aebdcebb50df6be08a03fb2c1c887fbc33ab12846313993082325204
MD5 13d9611e6ad0cc102c4acff9002b982a
BLAKE2b-256 e5b3dfad8a3c4a042e701e1f9fbdd014d3f0e3f3f9a48148e5c49eb4f56e312f

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