A Python API for SuperCollider
Project description
Supriya
Supriya is a Python API for SuperCollider.
Supriya lets you:
-
Boot and communicate with SuperCollider's
scsynth
synthesis engine: servers in realtime -
Compile SuperCollider SynthDefs natively in Python code
-
Explore nonrealtime composition with object-oriented sessions
-
Build time-agnostic asyncio-aware applications with providers
-
Schedule patterns and callbacks with tempo- and meter-aware clocks
Installation
Get SuperCollider from http://supercollider.github.io/.
Get Supriya from PyPI:
pip install supriya
... or from source:
git clone https://github.com/josiah-wolf-oberholtzer/supriya.git
cd supriya/
pip install -e .
Example: Hello World!
Let's make some noise. One synthesis context, one synth, one parameter change.
>>> import supriya
Realtime
Grab a reference to a realtime server and boot it:
>>> server = supriya.Server().boot()
Add a synth, using the default SynthDef
:
>>> synth = server.add_synth()
Set the synth's frequency parameter like a dictionary:
>>> synth["frequency"] = 123.45
Release the synth:
>>> synth.release()
Quit the server:
>>> server.quit()
Non-realtime
Non-realtime work looks similar to realtime, with a couple key differences.
Create a Session
instead of a Server
:
>>> session = supriya.Session()
Use Session.at(...)
to select the desired point in time to make a mutation,
and add a synth using the default SynthDef
, and an explicit duration for the
synth which the Session will use to terminate the synth automatically at the
appropriate timestep:
>>> with session.at(0):
... synth = session.add_synth(duration=2)
...
Select another point in time and modify the synth's frequency, just like in realtime work:
>>> with session.at(1):
... synth["frequency"] = 123.45
...
Finally, render the session to disk:
>>> session.render(duration=3)
(0, PosixPath('/Users/josiah/Library/Caches/supriya/session-981245bde945c7550fa5548c04fb47f7.aiff'))
Example: Defining SynthDefs
Let's build a simple SynthDef
for playing a sine tone with an ADSR envelope.
First, some imports:
>>> from supriya.ugens import EnvGen, Out, SinOsc
>>> from supriya.synthdefs import Envelope, synthdef
We'll define a function and decorate it with the synthdef
decorator:
>>> @synthdef()
... def simple_sine(frequency=440, amplitude=0.1, gate=1):
... sine = SinOsc.ar(frequency=frequency) * amplitude
... envelope = EnvGen.kr(envelope=Envelope.adsr(), gate=gate, done_action=2)
... Out.ar(bus=0, source=[sine * envelope] * 2)
...
This results not in a function definition, but in the creation of a SynthDef
object:
>>> simple_sine
<SynthDef: simple_sine>
... which we can print to dump out its structure:
>>> print(simple_sine)
synthdef:
name: simple_sine
ugens:
- Control.kr: null
- SinOsc.ar:
frequency: Control.kr[1:frequency]
phase: 0.0
- BinaryOpUGen(MULTIPLICATION).ar/0:
left: SinOsc.ar[0]
right: Control.kr[0:amplitude]
- EnvGen.kr:
gate: Control.kr[2:gate]
level_scale: 1.0
level_bias: 0.0
time_scale: 1.0
done_action: 2.0
envelope[0]: 0.0
envelope[1]: 3.0
envelope[2]: 2.0
envelope[3]: -99.0
envelope[4]: 1.0
envelope[5]: 0.01
envelope[6]: 5.0
envelope[7]: -4.0
envelope[8]: 0.5
envelope[9]: 0.3
envelope[10]: 5.0
envelope[11]: -4.0
envelope[12]: 0.0
envelope[13]: 1.0
envelope[14]: 5.0
envelope[15]: -4.0
- BinaryOpUGen(MULTIPLICATION).ar/1:
left: BinaryOpUGen(MULTIPLICATION).ar/0[0]
right: EnvGen.kr[0]
- Out.ar:
bus: 0.0
source[0]: BinaryOpUGen(MULTIPLICATION).ar/1[0]
source[1]: BinaryOpUGen(MULTIPLICATION).ar/1[0]
Now let's boot the server:
>>> server = supriya.Server().boot()
... add our SynthDef
to it explicitly:
>>> server.add_synthdef(simple_sine)
... make a synth using our new SynthDef:
>>> synth = server.add_synth(simple_sine)
...release it:
>>> synth.release()
...and quit:
>>> server.quit()
Example: SynthDef Builders
Let's build a simple SynthDef
for playing an audio buffer as a one-shot, with
panning and speed controls.
This time we'll use Supriya's SynthDefBuilder
context manager. It's more
verbose than decorating a function, but it also gives more flexibility. For
example, the context manager can be passed around from function to function to
add progressively more complexity. The synthdef
decorator uses
SynthDefBuilder
under the hood.
First, some imports, just to save horizontal space:
>>> from supriya.ugens import Out, Pan2, PlayBuf
Second, define a builder with the control parameters we want for our
SynthDef
:
>>> builder = supriya.SynthDefBuilder(
... amplitude=1, buffer_id=0, out=0, panning=0.0, rate=1.0
... )
Third, use the builder as a context manager. Unit generators defined inside the context will be added automatically to the builder:
>>> with builder:
... player = PlayBuf.ar(
... buffer_id=builder["buffer_id"],
... done_action=supriya.DoneAction.FREE_SYNTH,
... rate=builder["rate"],
... )
... panner = Pan2.ar(
... source=player,
... position=builder["panning"],
... level=builder["amplitude"],
... )
... _ = Out.ar(bus=builder["out"], source=panner)
...
Finally, build the SynthDef
:
>>> buffer_player = builder.build()
Let's print its structure. Note that Supriya has given the SynthDef
a name
automatically by hashing its structure:
>>> print(buffer_player)
synthdef:
name: a056603c05d80c575333c2544abf0a05
ugens:
- Control.kr: null
- PlayBuf.ar:
buffer_id: Control.kr[1:buffer_id]
done_action: 2.0
loop: 0.0
rate: Control.kr[4:rate]
start_position: 0.0
trigger: 1.0
- Pan2.ar:
level: Control.kr[0:amplitude]
position: Control.kr[3:panning]
source: PlayBuf.ar[0]
- Out.ar:
bus: Control.kr[2:out]
source[0]: Pan2.ar[0]
source[1]: Pan2.ar[1]
"Anonymous" SynthDef
s are great! Supriya keeps track of what SynthDef
s have
been allocated by name, so naming them after the hash of their structure
guarantees no accidental overwrites and no accidental re-allocations.
Example: Playing Samples
Boot the server and allocate a sample:
>>> server = supriya.Server().boot()
>>> buffer_ = server.add_buffer(file_path="supriya/assets/audio/birds/birds-01.wav")
Allocate a synth using the SynthDef
we defined before:
>>> server.add_synth(synthdef=buffer_player, buffer_id=buffer_)
<+ Synth: 1000 a056603c05d80c575333c2544abf0a05>
The synth will play to completion and terminate itself.
Example: Performing Patterns
Supriya implements a pattern library inspired by SuperCollider's, using nested generators.
Let's import some pattern classes:
>>> from supriya.patterns import EventPattern, ParallelPattern, SequencePattern
... then define a pattern comprised of two event patterns played in parallel:
>>> pattern = ParallelPattern([
... EventPattern(
... frequency=SequencePattern([440, 550]),
... ),
... EventPattern(
... frequency=SequencePattern([1500, 1600, 1700]),
... delta=0.75,
... ),
... ])
Patterns can be manually iterated over:
>>> for event in pattern:
... event
...
CompositeEvent([
NoteEvent(UUID('ec648473-4e7b-4a9a-9708-6893c054ac0b'), delta=0.0, frequency=440),
NoteEvent(UUID('32b84a7d-ba7b-4508-81f3-b9f560bc34a7'), delta=0.0, frequency=1500),
], delta=0.75)
NoteEvent(UUID('412f3bf3-df75-4eb5-bc9d-3e074bfd2f46'), delta=0.25, frequency=1600)
NoteEvent(UUID('69a01ba8-4c00-4b55-905a-99f3933a6963'), delta=0.5, frequency=550)
NoteEvent(UUID('2c6a6d95-f418-4613-b213-811a442ea4c8'), delta=0.5, frequency=1700)
Patterns can be played (and stopped) in real-time contexts:
>>> server = supriya.Server().boot()
>>> player = pattern.play(provider=server)
>>> player.stop()
... or in non-realtime contexts:
>>> session = supriya.Session()
>>> _ = pattern.play(provider=session, at=0.5)
>>> print(session.to_strings(include_controls=True))
0.0:
NODE TREE 0 group
0.5:
NODE TREE 0 group
1001 default
amplitude: 0.1, frequency: 1500.0, gate: 1.0, out: 0.0, pan: 0.5
1000 default
amplitude: 0.1, frequency: 440.0, gate: 1.0, out: 0.0, pan: 0.5
2.0:
NODE TREE 0 group
1002 default
amplitude: 0.1, frequency: 1600.0, gate: 1.0, out: 0.0, pan: 0.5
1001 default
amplitude: 0.1, frequency: 1500.0, gate: 1.0, out: 0.0, pan: 0.5
1000 default
amplitude: 0.1, frequency: 440.0, gate: 1.0, out: 0.0, pan: 0.5
2.5:
NODE TREE 0 group
1003 default
amplitude: 0.1, frequency: 550.0, gate: 1.0, out: 0.0, pan: 0.5
1002 default
amplitude: 0.1, frequency: 1600.0, gate: 1.0, out: 0.0, pan: 0.5
3.5:
NODE TREE 0 group
1006 default
amplitude: 0.1, frequency: 1700.0, gate: 1.0, out: 0.0, pan: 0.5
1003 default
amplitude: 0.1, frequency: 550.0, gate: 1.0, out: 0.0, pan: 0.5
1002 default
amplitude: 0.1, frequency: 1600.0, gate: 1.0, out: 0.0, pan: 0.5
4.0:
NODE TREE 0 group
1006 default
amplitude: 0.1, frequency: 1700.0, gate: 1.0, out: 0.0, pan: 0.5
1003 default
amplitude: 0.1, frequency: 550.0, gate: 1.0, out: 0.0, pan: 0.5
4.5:
NODE TREE 0 group
1006 default
amplitude: 0.1, frequency: 1700.0, gate: 1.0, out: 0.0, pan: 0.5
5.5:
NODE TREE 0 group
Example: Asyncio
Supriya also supports asyncio, with async servers, providers and clocks.
Async servers expose a minimal interface (effectively just .boot()
, .send()
and .quit()
), and don't support the rich stateful entities their non-async
siblings do (e.g. Group
, Synth
, Bus
, Buffer
). To split the difference,
we'll wrap the async server with a Provider
that exposes an API of common
actions and returns lightweight stateless proxies we can use as references.
The proxies know their IDs and provide convenience functions, but otherwise
don't keep track of changes reported by the server.
Let's grab a couple imports:
import asyncio, random
... and get a little silly.
First we'll define an async clock callback. It takes a Provider
and a list of
buffer proxies created elsewhere by that provider, picks a random buffer and
uses the buffer_player
SynthDef
we defined earlier to play it (with a lot
of randomized parameters). Finally, it returns a random delta between 0 beats
and 2/4:
async def callback(clock_context, provider, buffer_proxies):
print("playing a bird...")
buffer_proxy = random.choice(buffer_proxies)
async with provider.at():
provider.add_synth(
synthdef=buffer_player,
buffer_id=buffer_proxy,
amplitude=random.random(),
rate=random.random() * 2,
panning=(random.random() * 2) - 1,
)
return random.random() * 0.5
Next we'll define our top-level async function. This one boots the server,
creates the Provider
we'll use to interact with it, loads in all of Supriya's
built-in bird samples, schedules our clock callback with an async clock, waits
10 seconds and shuts down:
async def serve():
print("preparing the birds...")
# Boot an async server
server = await supriya.AsyncServer().boot(
port=supriya.osc.utils.find_free_port(),
)
# Create a provider for higher-level interaction
provider = supriya.Provider.from_context(server)
# Use the provider as a context manager and load some buffers
async with provider.at():
buffer_proxies = [
provider.add_buffer(file_path=bird_sample)
for bird_sample in supriya.Assets["audio/birds/*"]
]
# Create an async clock
clock = supriya.AsyncClock()
# Schedule our bird-playing clock callback to run immediately
clock.schedule(callback, args=[provider, buffer_proxies])
# Start the clock
await clock.start()
# Wait 10 seconds
await asyncio.sleep(10)
# Stop the clock
await clock.stop()
# And quit the server
await server.quit()
print("... done!")
Let's make some noise:
>>> asyncio.run(serve())
preparing the birds...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
playing a bird...
... done!
Working async means we can hook into other interesting projects like python-prompt-toolkit, aiohttp and pymonome.
License
This library is made available under the terms of the MIT license.
Project details
Release history Release notifications | RSS feed
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
Hashes for supriya-23.2b1-cp310-cp310-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f69bf7e7b5427b06ea854a500b4081cc48f3348c860cdb77e5aa7e6f8c9eed4e |
|
MD5 | 4039a0cd193377e81466757355769c94 |
|
BLAKE2b-256 | 5e746d853ae638ace25428c8b65056061541d066cd6d4d27df53a8123e8d5f58 |
Hashes for supriya-23.2b1-cp310-cp310-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6b518473e1956d8cdb8675b47bbf9c965b55760941f8df2f3a81c448a141a5e1 |
|
MD5 | e76b37ee01a436625959d79a97affcef |
|
BLAKE2b-256 | 8a4bc95c48a74a13c2b59d3236ae9b749a396bb0a5782ca45f34a52291dca943 |
Hashes for supriya-23.2b1-cp310-cp310-musllinux_1_1_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 2c397ea04bd3ad8539be10358d29ccfab0083b627123eeebd33d12f16848c3b6 |
|
MD5 | ebe8730ba81d636ff5eb8f4bfb433a3e |
|
BLAKE2b-256 | 5b4dbc635ac63fe82077dc6f4c07cdba25288bad51096ea95103b0b9ab415bf1 |
Hashes for supriya-23.2b1-cp310-cp310-musllinux_1_1_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 51e3b57c4817a629383bbffb9ebf82b60a78c7a0b0a23888e91468727ea1baad |
|
MD5 | f95bc1a9c33f67e0b15e71e63e235fc2 |
|
BLAKE2b-256 | 5b1e9fd58bb53599dfb14fdcf7f667f0c515a995c0fa1427fd2b7d0f9bac49aa |
Hashes for supriya-23.2b1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | cb38e5eddf8bdb6a0437650f84d6dc0a8ea819928260f04429abba2ef88413ce |
|
MD5 | 3e4baf650fee00954f61773bca73bbfb |
|
BLAKE2b-256 | 968ee55909984dfdce6daaa3fa3c1b32ef55bc984f594507c66f3e604e6c4d0d |
Hashes for supriya-23.2b1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8f41fe683c97dd184144b901b85ec65d91b453cba3811585113afcb3555a4161 |
|
MD5 | 5fb657c0ddde538e9bbf521877762180 |
|
BLAKE2b-256 | 7f7093d46f8e02ee867d7ae1151649fb5a752ab3ae7a25a72f45d32fbd9663c4 |
Hashes for supriya-23.2b1-cp310-cp310-macosx_10_9_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 24f13eaa3fd6db31f5b928f2db0ab659daac0d29fd5613c641a8345317c9a633 |
|
MD5 | 4ae8a8d2fe042cc877d845c0612abdda |
|
BLAKE2b-256 | 0517e98c51d426dc07d18c06b7d2abf83e8931f57d1a3b6d6243ab06af28bca3 |
Hashes for supriya-23.2b1-cp39-cp39-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 2ba40e2cff55981c87b27bb026707c4e0dff7bab418a90272ab3591c8c044b6e |
|
MD5 | 9e1fe764612fe8e2952c78f246701910 |
|
BLAKE2b-256 | a4f37725bd523c41dfb006c8edaa970db55cbd7e7091b6bd85aee8b64c16fab9 |
Hashes for supriya-23.2b1-cp39-cp39-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4a1840f6feb3a133031e190c440febaca68070d8a1959fe44b6f001b65fd33bb |
|
MD5 | 6b724c9ea6155c6c720ceefea190b528 |
|
BLAKE2b-256 | 3caa82963a2a9cf4beaf9d792f701ededd56a0b473941cd2f2190599caa5e716 |
Hashes for supriya-23.2b1-cp39-cp39-musllinux_1_1_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4a109a55a33b192b7902699ed6b4727285192dbc9bab17e3cf28e06019a31aa0 |
|
MD5 | ba9813bc51664c3dca58a931c633b856 |
|
BLAKE2b-256 | b8e3333fe3ebada8c96c353dd9b76af1f6e78770ace5e4efc73cd2d362679b10 |
Hashes for supriya-23.2b1-cp39-cp39-musllinux_1_1_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 67a4075dd023efe3dd0b311d5a622bc472364021249e13b1a96269c8cfaab3e7 |
|
MD5 | 24ee8f9d55bd7c71c55bf4107ddb2b13 |
|
BLAKE2b-256 | f32d4b1eab3bc45199f431bf645bbbe4a7e624631f9b66ccd83bbb653b77231e |
Hashes for supriya-23.2b1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3ad095e3dffea93f0ecf2759b29972db44b9728010316e6c0d68d5cc5c9080fd |
|
MD5 | ac466f2bfdedb4031c9c10d57c9f379a |
|
BLAKE2b-256 | 605c7d3f0da75314aabbf8260193124c4c99a1d2bf0f3c36d676312bc8096a63 |
Hashes for supriya-23.2b1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9cf275a2a909df939a49b80be1aae9ae0a8a10c92c9450c59db32d5d52a460eb |
|
MD5 | 67e8ec3a7f8e6e39b4f5a68fbe231bde |
|
BLAKE2b-256 | 944d66ae843ecae0d71dc4f7cca8e3bc5b5e45572291e0202ba03bad1caea800 |
Hashes for supriya-23.2b1-cp39-cp39-macosx_10_9_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 2336603f16990c6f61f71960e06867e16ab3f99c681d23ce64af6b7cf4c235af |
|
MD5 | 17e2a4e23a091be3804e38cc38eeb685 |
|
BLAKE2b-256 | 9de42abfbf2e5bf9d56960a11bee459d6011682db91984379827956621bd7882 |
Hashes for supriya-23.2b1-cp38-cp38-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6ab16a38365185aa6532a7285c2b9e7567b4b79327716a80adde5c2c60f12fc2 |
|
MD5 | d36b2d6ad0d941c5f2c3f9d440de72db |
|
BLAKE2b-256 | 44f61b5fd02f0ed1131a9cc858cc403fa250b9c8b15e94aad72c711f794ede0a |
Hashes for supriya-23.2b1-cp38-cp38-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1c7d4d12e868f6aacde52d105a49d7087fc929cd5177a98c6fb57ba9f9fb11be |
|
MD5 | 9871cbcd7d49969c1c94b885676971ca |
|
BLAKE2b-256 | 74d1a0649ac267004ab01d0a9803d5d69f282219fdc565db74875cb9487e52a9 |
Hashes for supriya-23.2b1-cp38-cp38-musllinux_1_1_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 90081e6348518d7ee9c9b26c76f62332492439595bbc81c4b99dea8e3d79f777 |
|
MD5 | 9390bb30b00335f6dd58be7f8a1ec7ed |
|
BLAKE2b-256 | ce0898ebf2b528f5d2cd1903f09ea0f140224a96100e6a0756cb5a00e1e6a9ed |
Hashes for supriya-23.2b1-cp38-cp38-musllinux_1_1_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c63fc463e00637d2878638e72b57c8d9176dff4b4b1b4fba24865754264c9272 |
|
MD5 | 1e69c6ea6c4c8d0707f25c3ac914f27b |
|
BLAKE2b-256 | 6cd8230533f159f6d3aa7b61de05a9da0960b22edb64829b5d9fc0530ee1a6c9 |
Hashes for supriya-23.2b1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c7103c4fc6a59c5316adec7e9828a94f755f3c0e372502e10d48ad63b6f7c879 |
|
MD5 | 58801cd3df3b34c9e66b77457a70f55b |
|
BLAKE2b-256 | b756ec9d922230e13a6c1d526eb20742df0441a1954c9923ea67c51735441e27 |
Hashes for supriya-23.2b1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | d2c4b1a1685d0d8e5d91b572bfde49e8867f2ef78f264ec755a517ff77d8c4d2 |
|
MD5 | d18f108f60a4ad7ac664be7a2a1557eb |
|
BLAKE2b-256 | 3e9f835333a3f4c9520bfe3600b6cfbaad1ba7a3b985f6984569c72c7ddaddd7 |
Hashes for supriya-23.2b1-cp38-cp38-macosx_10_9_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f21517dfab09c58db14946c30f5e0116e8e89de261319cf4b580385ef09ec817 |
|
MD5 | 9a547abac19683abca01ce53c360ede9 |
|
BLAKE2b-256 | 80fb0390becc0767bc22dbfd8b7034530133d8c67df2afbc890b28555627bf04 |