Skip to content

✨ soir

soir

Soir is a Python library for live coding music. It provides facilities to create and manipulate audio tracks, and to interact with external synthesizers. There are two important concepts in Soir:

  • Live functions (@live decorator) that are executed each time the code is changed. They are used to setup the environment, and to create tracks and instruments.

  • Loops functions (@loop decorator) that are rescheduled every given number of beats. They are used to create patterns and sequences.

# Live function
@live
def setup():
    bpm.set(120)

# Loop function
@loop(beats=1)
def kick():
    log('beat')

Soir's facilities are organized in modules that are accessible from the global context. For example, to set the BPM, you can use bpm.set(120) without having to explicitly import the bpm module. The available modules are:

Cookbook

Minimalistic Example

@live
def setup():
    bpm.set(120)

    tracks.setup({
        'sampler': tracks.mk_sampler(muted=False, volume=100),
    })

s = sampler.new('passage')

@loop(track='sampler', beats=4)
def kick():
    for i in range(4):
        s.play('kick')
        sleep(1)

Reference

Modules:

Name Description
bpm

The bpm module provides a way to set the tempo of the current

cli
ctrls

The ctrls module contains facilities to control settings of soir

errors

The errors module contains exceptions raised by the Soir engine to

fx

The fx module provides a set of audio effects that can be applied to

midi

The midi module provides a way to communicate with external

rnd

The rnd module provides facilities to introduce randomness in the

sampler
Info
system

The system module provides access to low-level facilities

tracks

The tracks module provides a way to setup and control tracks in the

Functions:

Name Description
ctrl

Get a control by its name.

current_loop

Get the current loop.

live

Decorator to create a live function that is executed each time the code is changed.

log

Log a message to the console.

loop

Decorator to create a loop that is rescheduled every given number of beats.

sleep

Sleep for the given duration in beats in the current loop.

ctrl(name)

Get a control by its name.

Parameters:

Name Type Description Default
name str

The name of the control.

required

Returns:

Type Description
Control

The control.

current_loop()

Get the current loop.

Raises:

Type Description
NotInLiveLoopException

If we are not in a loop.

live()

Decorator to create a live function that is executed each time the code is changed.

@live
def setup:
  tracks.setup({
    'bass': tracks.mk("sampler", 1, muted=False, volume=100),
  })

Returns:

Type Description
callable

A decorator registering and executing the live function.

log(message)

Log a message to the console.

This function is used to log messages to the console. It is useful for debugging purposes.

log(f'We are at beat {bpm.beat()}')

Parameters:

Name Type Description Default
message str

The message to log.

required

loop(track=None, beats=4, align=True)

Decorator to create a loop that is rescheduled every given number of beats.

The concept of a loop is similar to Sonic Pi's live loops. Code within a loop is executed using temporal recursion, and can be updated in real-time: the next run of the loop will execute the updated version. This provides a way to incrementally build audio performances by editing code. Loops should not be blocking as it would freeze the main thread. For this reason, blocking facilities such are sleep are provided by the engine.

@loop('bass', beats=4, track=1)
def my_loop():
  log("Hello World")

Parameters:

Name Type Description Default
track str

The track to use.

None
beats int

The duration of the loop in beats.

4
align bool

Whether to align the loop on its next beat sequence.

True

Returns:

Type Description
callable

A decorator registering and scheduling the function in a loop.

sleep(beats)

Sleep for the given duration in beats in the current loop.

In this example, we define a 4-beats loop that plays a kick sample every beats, and sleeps for 1 beat between each sample.

@loop
def my_loop(beats=4, track=1):
   for i in range(4):
      s.play('kick')
      sleep(1)

Parameters:

Name Type Description Default
beats float

The duration to sleep in beats.

required

Raises:

Type Description
NotInLiveLoopException

If we are not in a loop.