Extensions
Sardine also has an extension/plugin system that lets you write custom handlers and take advantage of the time scheduling engine for your own purposes.
The steps for making a Sardine extension are:
- create a Python package containing your custom handlers
- create a configuration file for your extension
- reference this configuration file in Sardine's user configuration file.
The next paragraphs describe in more details how to do this with a simple example: the Doug extension.
Doug structure
The project structure for a Sardine extension is quite standard: all you need is a root folder containing your Python package, and inside that package the modules containing the handlers (of course the package's content is not restricted to Sardine handlers, it is a regular Python package).
You can also place your extension's configuration file in your project for convenience, but note that this is not mandatory as you can place it anywhere that is accessible on your file system.
Doug
|__ doug
| |__ __init__.py
| |__ DougHandler.py
|__ doug-config.json
Doug Handler
Here is an example of a Sardine extension handler:
from sardine_core.handlers.sender import Number, NumericElement, ParsableElement, Sender, StringElement
from sardine_core.utils import alias_param
from typing import Optional, List
class DougHandler(Sender):
def __init__(self, params: dict):
super().__init__()
self._intro = params['intro']
def _doug_print(self, message):
word = message["words"]
if word is not None:
print(f'{self._intro}:: {word}')
@alias_param(name="iterator", alias="i")
@alias_param(name="divisor", alias="d")
@alias_param(name="rate", alias="r")
def send(
self,
words: Optional[StringElement | List[StringElement]],
iterator: Number = 0,
divisor: NumericElement = 1,
rate: NumericElement = 1,
**pattern: ParsableElement,
):
if words is None:
return
if self.apply_conditional_mask_to_bars(pattern):
return
pattern["words"] = words
deadline = self.env.clock.shifted_time
for message in self.pattern_reduce(pattern, iterator, divisor, rate):
self.call_timed(deadline, self._doug_print, message)
Let's decompose this and understand what Doug does.
The DougHandler
class inherits from Sardine's Sender
class: this allows us to use Sardine's native pattern parsing and time scheduling tools.
In the constructor method, we initialize our handler and provide it with a custom parameter called intro::
. The value of such initialization parameters are specified in the extension's configuration file.
In the _doug_print
method comes the core of what Doug does: it simply prints what it receives, starting all its lines with a little intro
.
The send
method is a simplified version the send
method of Sardine's native sender (MidiHandler
, SuperDirtHandler
, etc.): it allows us to use Doug in swim functions and to use patterns.
Doug configuration
The extension's configuration file is a JSON file that describes the project structure:
{
"root": "/path/to/Doug",
"package": "doug",
"handlers": [
{
"module": "DougHandler",
"class": "DougHandler",
"send_alias": "Doug",
"params": {
"intro": "sample"
}
}
]
}
The params
field corresponds to the handler's constructor parameters.
The send_alias
field corresponds to the alias that will be given to your handler's send
method in Sardine's session (like the D
for SuperDirt or N
for Midi Notes): this alias will become a global variable so you have to make sure it doesn't conflict with anything else.
Using Doug in Sardine
Using extensions is achieved by referencing the extension configuration files in Sardine's user configuration file:
{
"config": {
...
},
"extensions": [
"/path/to/extension-config.json"
]
}
For now you have to edit the file manually by adding the path to the configuration files in the extensions
list.
Resetting the user configuration file will clear all references to any extension you may have.
Doug in practice
Now that we are able to use Doug in a Sardine session, let's see an example of what we can do with it:
@swim
def hello_doug(p=1, i=0):
pat = 'alphabet:[0:26]'
D(pat, i=i)
Doug(pat, i=i)
again(hello_doug, p=1, i=i+1)
Calling Doug
will actually call the DougHandler.send
method (as it is the alias for it), which will parse the given pattern: hence in this example Doug will print the name of the sample SuperCollider is playing.
The visual output should look like:
sample:: alphabet:0
sample:: alphabet:1
sample:: alphabet:2
...
and the audio output should sound like: "A", "B", "C", ...