Skip to content

Commit

Permalink
[docs] how to write plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
momentarylapse committed Oct 20, 2024
1 parent d5d3816 commit f903bb9
Show file tree
Hide file tree
Showing 3 changed files with 226 additions and 6 deletions.
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Tsunami

Tsunami is an open-source digital audio workstation (DAW). It is designed for ease of use and not-looking-crappy™.
Tsunami is an open-source *Digital Audio Workstation* (DAW). It is designed for ease of use and not-looking-crappy™.

Written for Linux, can also run on Windows and MacOS.

## Features

Expand All @@ -12,26 +14,28 @@ It was mostly developed for my personal home recording needs, i.e.
* synthesizers
* samples
* plugin system
* signal chains
* persistent sessions

![tsunami1](https://user-images.githubusercontent.com/6715031/58601128-cc391680-8287-11e9-9a9f-3db9e57f763b.png)
![tsunami1](https://raw.githubusercontent.com/momentarylapse/github-assets/refs/heads/main/screenshots/tsunami-2024-10-20.png)

### Plugin system

Tsunami uses its own just-in-time compiler for plugin code. Currently, this mostly works on x86/amd64 CPUs.
Tsunami uses its own [language](https://github.com/momentarylapse/kaba) and just-in-time compiler for plugin code. Only x86_64 and aarch64 CPUs are supported.

## Getting started

* [how to build and run](docs/how-to-build.md)
* [how to use](docs/using/main.md)
* [writing plugins](docs/design/plugins.md)
* [internal design](docs/design/main.md)
* [development plans, bugs etc.](docs/development.md)


## Authors

Just me (Michael Ankele).
Mostly me (Michael Ankele).

## Acknowledgments

Huge thanks to the two people who tried using tsunami and complained in extremely helpful ways: 2er0 and Benji!
Huge thanks to the three people who tried using tsunami and complained in extremely helpful ways: 2er0, Benji and Sergey Fedorov!
216 changes: 216 additions & 0 deletions docs/design/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# Plugins

## Introduction

Plugins are written in [kaba](https://github.com/momentarylapse/kaba). Tsunami acts as a _host_ program, loading kaba files and compiling them at run-time.

The connection between tsunami and a kaba plugin goes in both directions:
* tsunami exposes some of its internals (functions and classes) to the kaba plugins
* kaba plugins can define new classes (e.g. audio effects) that can then be used inside tsunami

The API exposed by the host to the plugins ("header files") can be found in the repo in [/plugins/tsunami/](../../plugins/tsunami).

New plugins can be created by adding a new `*.kaba` file into the corresponding folder. Tsunami will look for plugins in these folders:
* `/usr/local/share/tsunami/plugins/` (see cmake prefix) when installed
* when running from inside the repo, [/plugins/](../../plugins) will be used instead
* `~/.tsunami/plugins/`

The sub-folder corresponds to the plugin category. E.g. audio effects go into [/plugins/audio-effect/](../../plugins/audio-effect).

Plugins define classes derived from the `Module` base class. They can define a number of ports (inputs/outputs for audio/midi/beats). But it is recommended to derive from more specific classes like `AudioEffect` that come with pre-defined ports and other useful behavior.

## Example: audio effect

Audio effects simply modify chunks of audio data:

```kaba
# import the tsunami API
use tsunami.*
# define a new plugin class "MyAudioEffect" derived from tsunami's "AudioEffect"
class MyAudioEffect extends AudioEffect
# define a function "process" that will be called to alter a chunk of audio data
func override process(out buf: AudioBuffer)
# loop over all the samples in the first (left) channel... and do something stupid
for mut x in buf.c[0]
x += 0.1
```

## Example: synthesizer

Typical, simple synthesizers have 2 classes:
* the _pitch renderer_, it es responsible for producing a single, fixed pitch
* gets instanciated each time, a new pitch is being played
* is reused whenever the same pitch is played again
* the _synthesizer_ creates and manages the pitch renderers

```kaba
use tsunami.*
class MyRenderer extends PitchRenderer
# gets called when tsunami needs a chunk of audio for this pitch
func override mut render(out buf: AudioBuffer) -> bool
for mut i=>x in buf.c[0]
# we can use the predefined member "delta_phi"
x = sin(i * delta_phi)
# TODO use state to keep track of envelope and phase between chunks...
# return value: keep note alive?
return true # forever :( ...better use envelope > cutoff
# gets called when a midi note starts
func override on_start(volume: f32)
# ...restart the envelope, maybe?
# gets called when a midi note ends
func override on_end()
# ...release the envelope?
# the actual synthesizer plugin
class MySynthesizer extends Synthesizer
# gets called
func override create_pitch_renderer(pitch: int) -> xfer[PitchRenderer]
return new MyRenderer(self, pitch)
```

## Example: program extensions

These are general plugins, that can be activated by the user via the bottom bar console.

Usually they will be active as long as the user wants, interacting with the UI and data. But they can also be used for one-shot operations, ending by calling `stop()`.

They go into the `/plugins/independent/` folder.

```kaba
use tsunami.*
class MyExtension extends TsunamiPlugin
# gets called when starting the plugin
func override on_start()
# ...
# gets called when ending the plugin
func override on_stop()
# ...
```


## State and configuration

* _configuration_ is a special member variable, that is controlled by the user (i.e. the UI in the host program). Plugins must not change it
* _state_ are all other member variables, the plugin can freely change

```kaba
use tsunami.*
# a class containing all parameters
# deriving from "Module.Config" is important! Tsunami will use this to connect to the parameters
class MyConfig extends Module.Config
# some example parameters/variables
var some_float: f32
var some_string: string
# this function is called when tsunami wants to set "neutral"/initial parameters
func override reset()
some_float = 1.0
some_string = ""
class MyAudioEffect extends AudioEffect
# the variable containing the parameters
var config: MyConfig
# some extra variables acting as the state
var some_state: f32
var some_more_state: f32[]
# this function is called to initialize the state, before the plugin is used or reused
func override reset_state()
some_state = 13.0
some_more_state = [1,2,3]
func override process(out buf: AudioBuffer)
# use (read from) config
let a = config.some_float * 2.0
# use and alter the state
some_state *= 2
```

### Automagic config UI

You define magic strings to let tsunami automatically create UI controls for your parameters:

```kaba
use tsunami.*
let AUTO_CONFIG_SOME_FLOAT = "range=0:1:0.001,scale=1000,unit=ms"
let AUTO_CONFIG_SOME_STRING = ""
class ...
```

This is not a well-designed system. You can find some more examples in [/plugins/](../../plugins).

### User defined UI

If you need full control over the UI...

Admittedly, that is complicated and beyond this document. See [/plugins/](../../plugins) for examples, there is some [API reference](https://wiki.michi.is-a-geek.org/kaba.hui/) for the UI library, and here is a "simple" template:

```kaba
use tsunami.*
# define the class of your config panel
class MyConfigPanel extends ConfigPanel
# "ConfigPanel" already has a member referencing our effect, but we want to override the exact type
var override c: MyAudioEffect&
# panel constructor - gets called when panel is created
func override __init__(_c: Module)
# define UI
from_source("
Grid ? ''
Button button-id 'Button label'
Edit edit-id ''")
# register a ui event that is called when the button is pressed
event("button-id", on_button_pressed)
# callback when button is pressed
func mut on_button_pressed()
# change plugin config
c.config.some_float = 42.0
# signal that we have changed the plugin config
changed()
# this gets called, then the config has changed
func override update()
# set UI state from config
class MyAudioEffect extends AudioEffect
# this gets called, when tsunami wants a new config panel instance
func override create_panel() -> xfer[ConfigPanel]
return new MyConfigPanel(self)
```

2 changes: 1 addition & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Current state

* [internal design](design/main.md) (TODO)
* plugins (TODO)
* [plugins](design/plugins.md)

## Forward

Expand Down

0 comments on commit f903bb9

Please sign in to comment.