Skip to content

Instantly share code, notes, and snippets.

@fuxoft
Created June 10, 2024 16:56
Show Gist options
  • Save fuxoft/9e70b3ba61d2b640712b188b936df55a to your computer and use it in GitHub Desktop.
Save fuxoft/9e70b3ba61d2b640712b188b936df55a to your computer and use it in GitHub Desktop.
Self-destruction coroutines

I will try to explain in greater detail what I am trying to achieve and why I thought coroutines would be great for that. (This is a continuation of the previous thread called "Garbage collected coroutines?" which I am unable to reply to.)

I am experimenting with a way to generate musical waveforms using scripts. The script generates random melodies, renders them as tones, mixes those tones together and writes the resulting music to WAV file (or plays it directly using aplay on Linux).

So, the output of my script is a waveform consisting of 48000 samples per second.

Previously I've experimented with this in Lua a Janet where I was able to do the following:

There is an endless loop, let's call it "audio-out" that writes the resulting WAV file to disk (or plays it in semi-realtime).

This infinite loop takes its data from a coroutine called "mixer".

The "mixer coroutine" contains an array of inputs, each of them called "toneX", e.g. "tone1", "tone2", "tone3" etc... These are the notes that are currently playing. "Mixer" adds the incoming samples together (48000 times per second) and yields them to "audio-out".

Apart from "tones", the "mixer" is also getting its data from another coroutine called "controller" which tells the mixer when to start a new "note" and when to stop an existing (playing) "note".

Whenever "controller" tells the "mixer" to start a new "note" (based on frequency, velocity, waveform type etc), the mixer creates several new coroutines: One of them, called "waveX", generates non-stop sine wave (or square wave or any other shape). The other one, called "envelopeX", generates the volume onvelope (e.g. gradually increases from 0 to 1 and then yields 1 forever).

"EnvelopeX" and "waveX" coroutines yield to (newly created) "noteX" coroutine, which is freshly added to the "note" pool of "mixer" coroutine. At this instant, the new note starts being heard in the resulting mix.

When it's time to end playing one of the notes, the "controller" coroutine yields a special command to "mixer" to disconnect the specific "note".

So the structure of the coroutines is:

  • "audio-out" (loop, not coroutine) contains "mixer" coroutine.
  • "mixer" coroutine contains any number of "noteX" coroutines and one "controller" coroutine.
  • Each "noteX" coroutine contains coroutines "envelopeX" and "waveX"

Everything is controlled by the "controller" coroutine which removes "noteX" coroutines from "mixer", creates new ones as necessary and adds them to "mixer".

At any point in time, the system might consist of dozens of coroutines referenced from each other.

All "noteX", "envelopeX" and "waveX" coroutines are completely "dumb". To be as fast as possible, they produce data in endless loop. All logic and coroutine creation is being done in "controller" which sends commands to "mixer".

The advantage of Lua and Janet couroutines is that their coroutines are objects stored in variables. If the variable goes out of scope, the relevant coroutine is automatically destroyed. Whenever "noteX" coroutine is removed from "mixer"'s pool, that coroutine is automatically stopped and destroyed, which cascades to "noteX"'s "envelopeX" and "waveX" coroutines, which are also automatically destroyed because the last reference to them has been lost. There is no need to worry whether unneeded coroutines have been correctly destroyed.

With PicoLisp coroutines, it seems I'd have to create a separate bookkeeping system which knows all nodes of the coroutine "tree" and their connections and destroyes them as necessary.

I have an existing code in Janet that shows how it works this but it's 350 lines of not very clear code here: https://gist.github.com/fuxoft/af9b7c40df13910cbd6ea85730c7ac01

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment