Need help with supriya?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

josiah-wolf-oberholtzer
182 Stars 21 Forks MIT License 2.4K Commits 14 Opened issues

Description

A Python API for SuperCollider

Services available

!
?

Need anything else?

Contributors list

# 184,502
music-n...
lilypon...
Shell
superco...
2235 commits
# 585,799
music-n...
lilypon...
superco...
synthes...
54 commits
# 214,846
Jupyter...
TeX
superco...
synthes...
2 commits
# 466,241
MATLAB
Vim
Shell
superco...
1 commit

Supriya

Supriya is a Python API for SuperCollider.

Supriya lets you:

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) * amplitude
...     envelope = EnvGen.kr(envelope=Envelope.adsr(), gate=gate, done_action=2)
...     Out.ar(0, [sine * envelope] * 2)
...

This results not in a function definition, but in the creation of a

SynthDef
object:
>>> 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:
            done_action: 2.0
            envelope[0]: 0.0
            envelope[10]: 5.0
            envelope[11]: -4.0
            envelope[12]: 0.0
            envelope[13]: 1.0
            envelope[14]: 5.0
            envelope[15]: -4.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
            gate: Control.kr[2:gate]
            level_bias: 0.0
            level_scale: 1.0
            time_scale: 1.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_)

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.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.