The Programs of the Week of Valentine's Day
This Week’s Program: Feb 15 - Feb 19
No time to shmooze, let’s get right to it.
4e9d63a174265cd3387d722ade6a266e24f64b28
I build up another step-sequencer, this one called
note-sequencer
. This sequencer has the same method signature, but
the pulses
here should be a sequence of vectors with key-value
pairs. On each metronome tick, that vector will be applied to the
instrument (via apply
naturally). This means I can set up an
instrument with certain parameters already set and use
partial
to delay
execution. For non-percussive instruments, this allows me to model
melodies similar to how I model the drummachine.
4520f26ccc2f17eaf4b49bd9d30d520333bb9df1
Here’s a simple function, similar to rand-drumsequence, that creates a random sequence suitable for the note-sequencer.
cabc8775857aa5434e83bcd7aa90abdc031a9c44
The natural thing to do from the note-sequencer is to create an
abstraction that both note-sequencer and step-sequencer can
use. That’s what this function play-sequence
does. It takes a
metronome, a sequence, a lambda, and a (later made optional)
predicate. When the metronome ticks, the lambda is called with the
next step in the sequence when the predicate matches. However, instead
of using the traditional temporal recursion model favored by Overtone,
I chose to lean in hard on core.async
. I’m using go-loop
to wrap a
loop in a go thread. The sequence is now an async channel. Channels
can easily be created from sequences with
to-chan
. A
step is read off the channel and then the thread “sleeps” (using the
timeout
channel) for a metronome tick. Once the input channel is
closed, the loop exits. This feels pretty slick: no scheduling is
needed.
6fd169ffc68f524bcce21dd4792f76ae0a8f5cda
I update the step-sequencer
and note-sequencer
functions to use
this new play-sequence
abstraction. Their API’s don’t change at
all. That’s re-fun-ctoring.
3911eaa342b306c61d0571a56f2b8b4b4bd2af7e
One of the downsides to the above approach is I lose the ability to
use (stop)
to stop all scheduling and music players. I decided that
the best way to tell a sequencer to halt is to cut off it’s
metronome. Instead of using the metronome as is traditional, I create
a clock signal similar
to how you would see this in traditional analog modular
synthesis. This clock signal is an abstraction of the timeout
mechanism from play-sequence
. Every metronome beat, a message is
sent into the channel. Back in play-sequence
, the loop there reads
from this channel and awaits the message (which happens to be the
metronome itself). If I close the clock channel, the sequencer will
stop execution.
Now that’s how you core.async
. I feel like a concurrency
wizard. See you next week!
💝 Mark