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
Serverclass -- 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 andmanaged_synth()/managed_group()/managed_par_group()/managed_buffer()for automatic resource cleanup. Same API for both engines -- just passprotocol=EmbeddedSupernovaProtocol()to use supernova -
Pythonic SynthDef builder -- define UGen graphs using a context manager and operator overloading, compiled to SuperCollider's
SCgfbinary 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 --
Scoreclass for offline audio rendering to WAV/AIFF files without audio hardware. Timestamped OSC commands are serialized and rendered by the embedded engine -
Bus allocation --
Busproxy class withServer.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, andPlayerfor musical event scheduling. Patterns are reusable iterables;Pbindproduces event streams that drive synth creation with automatic gate release.Clockprovides tempo-driven playback with drift-free scheduling -
MIDI input --
MidiInclass 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.
NodeProxyowns a private audio bus, a source synth (with ASR envelope for crossfade), and a monitor synth. Swap the source seamlessly while audio plays.Ndefis 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 -
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 ofUGenNode/UGenInputNamedTuples for programmatic traversal.SynthDef.to_dot()exports to Graphviz DOT format -
Envelope system --
Envelopeclass with factory methods (adsr,asr,linen,percussive,triangle) and theEnvGenUGen -
OSC codec -- pure-Python
OscMessage/OscBundleencode/decode with optional C++ acceleration via nanobind -
@synthdefdecorator -- 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").
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
_midiextension. - 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
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nanosynth-0.1.6.tar.gz.
File metadata
- Download URL: nanosynth-0.1.6.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
736cf9570d8fd1816636c71052cf5c069907e70c380e17433839431b12dbc15e
|
|
| MD5 |
e4247bc5c70d1bc03d65c65b975bbb6f
|
|
| BLAKE2b-256 |
2047f3bc69cbc7cb35005621e6fc23e1b970e1c58c7bef4b599a6727222d9e17
|
File details
Details for the file nanosynth-0.1.6-cp314-cp314-win_amd64.whl.
File metadata
- Download URL: nanosynth-0.1.6-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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f9db6c5d0c490095db05dd15fc6dcd6ba398dc769850fe13cd3a1d365a0588d6
|
|
| MD5 |
3e3e700daf5c0a968cd8fc9a6a967dc2
|
|
| BLAKE2b-256 |
b92aeaa941b67178b7f39b70830d51d7597a74932e4a44228a4c256a3e242fdc
|
File details
Details for the file nanosynth-0.1.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: nanosynth-0.1.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 3.9 MB
- Tags: CPython 3.14, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
62369f625dc2beea27a1e9bb9750602889f53d129e1b3b79fec1ce2e8d5b9fe8
|
|
| MD5 |
53a98302fa419e6ec3e973afbf8bf4a3
|
|
| BLAKE2b-256 |
cc78bc2f1ee7ad162106e82edb35cd6ea25ef81e4ee5f3e761eb04ca153d2f4a
|
File details
Details for the file nanosynth-0.1.6-cp314-cp314-macosx_11_0_arm64.whl.
File metadata
- Download URL: nanosynth-0.1.6-cp314-cp314-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.3 MB
- Tags: CPython 3.14, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
14c977ca079231c62a37fc6b0c63b8b1afbaa4b7ba749079c5b0aaa75bd57bc0
|
|
| MD5 |
b2194c6be1b7e50009439a2bcb6e0048
|
|
| BLAKE2b-256 |
8c9abfced98d586d0d42b785338bfbd51ea2452f61ab96d96dceb7e77393b460
|
File details
Details for the file nanosynth-0.1.6-cp313-cp313-win_amd64.whl.
File metadata
- Download URL: nanosynth-0.1.6-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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
94e3ee488e4a26ed29848b4f358b30473962fae1538caa53eeb6fe7eb7e03a34
|
|
| MD5 |
2a9518a479ca07fb0564d0fc2adfc837
|
|
| BLAKE2b-256 |
4888866e538dec5cc96143602cd826591f4b4df587ecfa9e82f5c912de999efe
|
File details
Details for the file nanosynth-0.1.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: nanosynth-0.1.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 3.9 MB
- Tags: CPython 3.13, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c03cb8bc69ce61c12841adec4c3733e99812380e4b865d4d8faed41e375fb15c
|
|
| MD5 |
8f6e3039169fb79f91af0b074bab77cf
|
|
| BLAKE2b-256 |
6585d3db01af0e96b8fcc69b5ec1feb91dbe5c091bca5bccd9a77885ba171d46
|
File details
Details for the file nanosynth-0.1.6-cp313-cp313-macosx_11_0_arm64.whl.
File metadata
- Download URL: nanosynth-0.1.6-cp313-cp313-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.3 MB
- Tags: CPython 3.13, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ebf0ff38475bf396c77c0244d06d8d3a66ea84d6793483cc2dd85ebe4ce986e0
|
|
| MD5 |
a97a7749f6470eb2626baf87ddaf2973
|
|
| BLAKE2b-256 |
568188f3ebba8db50081db5a2813435bf59cbc6054376405bd4a10ec8f146e93
|
File details
Details for the file nanosynth-0.1.6-cp312-cp312-win_amd64.whl.
File metadata
- Download URL: nanosynth-0.1.6-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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8ea7955424f01dce19d1892381cdc9c38ba363ff8dad027d4524ceb060bde57c
|
|
| MD5 |
5724cc5068f028b290f045c0e15be2e3
|
|
| BLAKE2b-256 |
11cb10206171ad329b0e18e1110e6a0d4a25b8c2945a620753dc50d7b9e244e0
|
File details
Details for the file nanosynth-0.1.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: nanosynth-0.1.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 3.9 MB
- Tags: CPython 3.12, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
df28bb251464ffa13fd6afea02ac1f6834c603be0c5a434c5d7afd97b4fc0f72
|
|
| MD5 |
fb4bbbf229af495a9ae5899021e69749
|
|
| BLAKE2b-256 |
fac566c31fe909ab31be0a7efe42d28d531669fa6eda3fe7b60e97c8489c42bb
|
File details
Details for the file nanosynth-0.1.6-cp312-cp312-macosx_11_0_arm64.whl.
File metadata
- Download URL: nanosynth-0.1.6-cp312-cp312-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.3 MB
- Tags: CPython 3.12, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
777059ab90b5992e6ce4490ff6babbef8fd1730a8fd8e86dec518e9e5d618103
|
|
| MD5 |
75bcc1a0224d488fecd3e669a6360895
|
|
| BLAKE2b-256 |
907253a0e3f3be0a21ffdc247d7bd779f33838573b4965eaa550e82af43bb803
|
File details
Details for the file nanosynth-0.1.6-cp311-cp311-win_amd64.whl.
File metadata
- Download URL: nanosynth-0.1.6-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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2517590416676dbbe238f03f23c1c76bf2aab71402d4d6cf33d1104ad3d98bf
|
|
| MD5 |
7216b8bd880a1d9fd57cf1f035b78f77
|
|
| BLAKE2b-256 |
8c4aeffb7cf23680a0c9e26e2f12b010992863ccb752d0d9486f1f87a36905ec
|
File details
Details for the file nanosynth-0.1.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: nanosynth-0.1.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 3.9 MB
- Tags: CPython 3.11, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
246ab38800a13ce7d550eb3f9e6d1e582376656c4084ff460767957142c97e5c
|
|
| MD5 |
297650930e041e13f2156d9aa609dacd
|
|
| BLAKE2b-256 |
fe75795c259ea47e4f8d2c728728341713524e6b2e69c41602d82485c4f0f65f
|
File details
Details for the file nanosynth-0.1.6-cp311-cp311-macosx_11_0_arm64.whl.
File metadata
- Download URL: nanosynth-0.1.6-cp311-cp311-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.3 MB
- Tags: CPython 3.11, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dbffb9cca8557f5e31883bc22dda095f0378b9883788c11af368f668d8a48529
|
|
| MD5 |
d1c790c1e4fd9b8961034b36555a6600
|
|
| BLAKE2b-256 |
b9448d0039ea293aee63370c265aa0acbd8c948bbcdd0f54db3a4adbae420a16
|
File details
Details for the file nanosynth-0.1.6-cp310-cp310-win_amd64.whl.
File metadata
- Download URL: nanosynth-0.1.6-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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9aa13b8ed2c9a689e475fbdd08325a5fb47cdb1fa43c99cce7f4981c9c8f6c8
|
|
| MD5 |
87404caddfedc9b298dfe958842dcaf9
|
|
| BLAKE2b-256 |
e4da8d078bd64ab4f92899a0da9abcf5359d356cba9b603fcec5a5219782a586
|
File details
Details for the file nanosynth-0.1.6-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: nanosynth-0.1.6-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 3.9 MB
- Tags: CPython 3.10, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
87ccaf04bd0397f5cbedfdea7004bc80e816add12158fd8f6fa6d43e84666855
|
|
| MD5 |
14ff4a491512a0da4313ea7409a18954
|
|
| BLAKE2b-256 |
d2d26ba46a35aa607c910ec4200765199e56fe3d1fe3bc3a02e37fb430fcf2c5
|
File details
Details for the file nanosynth-0.1.6-cp310-cp310-macosx_11_0_arm64.whl.
File metadata
- Download URL: nanosynth-0.1.6-cp310-cp310-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.3 MB
- Tags: CPython 3.10, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f33a851bd236a580402acedc86a9d3158266a2643b724f5bf86488edef29638f
|
|
| MD5 |
9834ed53b6f99d1c4f94b57651aa0908
|
|
| BLAKE2b-256 |
be7469e69d0eaf6fd52041a1908e2a15af45db599948c07040549b2ee990cd06
|