lab 7 - sequencer
NAME
lab 7 - sequencer; create a simple sequencer that can play back a subset of SKINI messages.
SETUP
Inferno 4th Edition release 20040830.
DESCRIPTION
2004/0928 20:37 SKINI is the sequencing language from the STK. It is a readable form of MIDI, and was designed to be "extensable and hackable"; all of which make it ideally suited to this application and Inferno. Here is a brief example
// Measure number 1 =0 NoteOn 0.416667 2 72 64 NoteOff 0.208333 2 72 64 NoteOn 0 2 71 64 NoteOff 0.208333 2 71 64 NoteOn 0 2 72 64 NoteOff 0.416667 2 72 64 NoteOn 0 2 67 64 NoteOff 0.416667 2 67 64
There is one command per line. The line begins with command name followed by parameters separated by space or tabs. The second parameter is always the time delta. For the NoteOn command, the third argument is channel (or voice), fourth is midi pitch, and fifth if velocity (I guess! I'm ignoring it for now).
These SKINI messages are interpreted by sequencer, which sends messages to one or more instruments and reads back audio data.
I created a basic instrument module, which should be used as a template for later instruments, called simple. It uses the adsr and wave modules.
The wave module I have described previously. Adsr is a standard Attack, Decay, Sustain, Release envelope. I copied the implementation from the STK.
Simple and sequencer use a new interface to assist in using other signal sources.
Sig: adt { ctl: ref Sys->FD; data: ref Sys->FD; open: fn(s: string): ref Sig; read: fn(s: self ref Sig, nsamples: int): array of real; readbytes: fn(s: self ref Sig, nbyte: int): array of byte; };
This will evolve. It is currently part of the dsp module.
The source signals are opened by init. I made a small modification to signalfs.b moving the init call from signalfs.b:/serve to signalfs.b:/requestproc to avoid deadlock.
I created /mnt/dsp for mounting signalfs and /mnt/dsp/raw because I am hardcoding the names of some signal sources and raw sound files. The raw files from the STK should be bound or copied to /mnt/dsp/raw. Therefore start as
% bind /n/j/stk-4.1.3/rawwaves /mnt/dsp/raw % signalfs -a /mnt/dsp
Setup the modules and /dev/audio
% bind -a '#A' /dev % echo rate 22050 > /dev/audioctl % echo chans 1 > /dev/audioctl % echo add wave.dis wave > /mnt/dsp/ctl % echo add adsr.dis adsr > /mnt/dsp/ctl % echo add simple.dis simple > /mnt/dsp/ctl
Run a SKINI file.
% sequencer /mnt/dsp/simple < bachfugue.ski > /dev/audio
The sequencer assumes 4 voices currently. It's very basic; just for testing while creating new instruments. It will most surely be rewritten.
CONCLUSION
Here are the latest. adsr.b bachfugue.ski dsp.b dsp.m sequencer.b signal.m signalfs.b simple.b wave.b
The SKINI language has more commands and features than implemented here.
Again, it is slow. I should buffer writes to the /dev/audio, maybe a few seconds worth, so the audio sounds smooth. Otherwise, I need to write to a file first then stream the file to /dev/audio. However, It's enough for testing while creating some of the more complex instruments from the STK.
The sequencer could be a module of signalfs like any other. Reads of the data return the audio data. The ctl file is a synthetic file which could be edited within any editor. But this is a slightly different interface than other signals. An alternative is to use the skini file as a source file just like the raw files for the wave module. The sequencer module then represents patterns, which can be combined, looped, and sequenced just like any other signal.
DAY DREAMING
I can have a grid generating the sounds. What is possible with unlimited cpu power? Using a grid we should be able to create, in realtime, a very complex syntesized soundscape. Could we use the plan9 grid for this? Run emu on each node, serve a signalfs, bind them all into a local namespace, then generate the patterns. 8 channel, 24 bit, unlimited voice and polyphony.
REFERENCES
STK is now a link on the side bar.
Comments