This is a sketch for a revised standalone version of astrid python scripts.
I’ve renamed the trigger callback to seq and added some new
trigger types. The midi trigger is broken into
cc and note, and the new serial triggers take
a single string / byte blob. I also replaced the play and
trigger methods with a single msg handler for
all astrid message types that has an interface which is just the same
string that would be used in the astrid REPL, or as an argument to the
astrid-msg program.
There’s a new patch callback that uses the new ugen graph.
Like the cython implementation, there will probably be a thread watching
source changes that reloads the instrument module, and at that point the
new graph struct could be constructed and flagged as new. Then in the
main jack callback if the new flag is set it could do an atomic swap of
the two graph structs before asking for samples from the graph during
the callback render phase.
oh…
Swapping graphs like that has the classic problem of resetting the graph state on changes. I don’t really mind that for now since I don’t do much live-coding in performance, but it would be worth looking into allowing ugens to provide a way to serialize and deserialize their params, maybe, which could be used to store the state of individual named ugen instances. Then, when creating the new graph, if those named nodes are still in the graph, their state could be restored, or really preserved through source changes.
The biggest change is the new exposed Instrument
abstraction, which was previously just an internal thing. The standalone
astrid C programs are working out, but I miss being able to compose in
python so this seems worth spending time on.
The current orc folder-full-of-instruments thing with a
single console script and implied session management is clunky. I like
the idea of astrid being library-like, too.
Self-contained astrid instrument scripts can drop themselves into the astrid REPL when run and have the same console interface available.
Instruments already expose all their messages queues in the filesystem based on the instrument name, so communication between instruments could be just a matter of knowing the instrument name. (And maybe later a network address.)
I’ve just pulled the old named pipe message queue implementation out and
settled on POSIX message queues (which are easier to use, I think) but
that also means all external communication has to happen through
astrid-msg, and that I’ve essentially dropped macos support
for now. There’s a project to bring POSIX message queues to macos which
might fix that (especially combined with cosmopolitan C) down the road,
though.
from pippi import dsp, oscs, ugens
from astrid import Instrument
def seq(ctx):
events = []
# Trigger the async play at time 0
events += [ ctx.t.msg(0, 'p sineosc freq=220 length=0.9') ]
# Send a serial message at 0.3 seconds
events += [ ctx.t.serial(0.3, '/dev/ttyACM0', 'foo') ]
# Send a MIDI CC0 changes to device 1 on channel 2
events += [ ctx.t.cc(2.2, 0, 60, device=1, channel=2) ]
events += [ ctx.t.cc(3.1, 0, 80, device=1, channel=2) ]
# Send MIDI note on/off (0.9 seconds) to device 3 on channel 0
events += [ ctx.t.note(2.2, length=0.9, freq=220, amp=0.4, device=3) ]
# Retrigger the sequence in 10 seconds
events += [ ctx.t.msg(10, 't sineosc') ]
def patch(ctx):
graph = ugens.Graph()
graph.add_node('s0', 'sine', freq=100)
graph.add_node('s1', 'sine', freq=100)
graph.add_node('m0', 'mult')
graph.connect('s0.output', 's1.freq', 1, 200)
graph.connect('s1.output', 's0.freq', 1, 200)
graph.connect('s0.freq', 's1.phase', 0, 1, 1, 200)
graph.connect('s0.output', 'm0.a')
graph.connect('s1.output', 'm0.b')
graph.connect('m0.output', 'main.output', mult=0.5)
return graph
def play(ctx):
length = ctx.p.length
freq = ctx.p.freq
# short (1-3 seconds) sine tone
out = oscs.SineOsc(freq=freq).play(length) * 0.1
# pitch modulated graincloud @ half speed
out = out.cloud(length * 2, speed=dsp.win('sine'))
# random envelope w/0.04s taper
out = out.env('rnd').taper(0.04)
yield out
if __name__ == '__main__':
with Instrument(name='sineosc') as instrument:
instrument.run_forever()