Sunday, April 16, 2006

lab 62 - software synth csp style

NAME

lab 62 - software synth CSP style

NOTES

This is the next iteration of my software synth code for Inferno. Of particular note is the embrace of CSP style as implementation technique. This was true of the code in lab 60. But this time much more of it actually works and the basic framework, the interfaces, are in place for me to extend it further. I think it makes a nice show case of CSP style programming. I thought my lexis database did too (lab 34) but this code is probably easier and more fun to play with.

I want to post this now before I move onto the next phase, which may add a lot more complexity but won't illustrate any better the CSP style of programming in this application. You are encouraged to edit this code to create your own synthesizer, and use that as a way into studying CSP style.

This synth comes with a basic GUI. Here is a screen shot.

synth1

The GUI is bare bones, designed just so that it very easy to add new knobs for the control of filters. (One of the things that worries me is that I'll get bogged down designing a pretty GUI.)

I'll now try and describe in more detail the code.

The interface for the processes has changed a little since lab 60. It is more general in that all the processes see the same interface and can a be plugged together in many different ways.

The interface is as follows,

 Inst: adt {
  c: Sample;
  ctl: Control;

  mk: fn(insts: Source, f: Instrument): ref Inst;
 };

 Source: type array of ref Inst;
 Sample: type chan of (array of real, chan of array of real);
 Control: type chan of (int, real);
 Instrument: type ref fn(s: Source, c: Sample, ctl: Control);

The main type here is the Instrument function. Each instrument is spawned and interacts with other processes, usually other instruments, through the source, sample, and control channels. An instrument will typically loop forever receiving on the sample or control channel. The message on the sample channel will be an array of real that the instrument should fill with samples or filter the incoming samples, or whatever it likes. It sends the array back down a receive channel that came with the request. Control requests are things such as change frequency, or gain, for example. Finally, an instrument may optionaly take as argument and array of Source instruments it can use when answering requests for an array of samples. A good example is the mixer instrument which takes an array of any other instruments and mixes their samples down to a single stream of samples.

Here's an example of a one-pole filter

# y(n) = b₀x(n) - a₁y(n-1)
onepole(nil: Source, c: Sample, ctl: Control)
{
 lastout := array[channels] of {* => 0.0};
 b := array[1] of {0.4};  #gain
 a := array[2] of {1.0, -0.9};

 for(;;) alt {
 (x, rc) := <-c =>
  for(j := 0; j < channels; j++){
   x[j] = b[0] * x[j] - a[1] * lastout[j];
   for(i := channels+j; i < len x; i += channels)
    x[i] = b[0] * x[i] - a[1] * x[i-channels];
  }
  lastout[0:] = x[len x-channels:];
  rc <-= x;
 (m, n) := <-ctl =>
  case m {
  CPOLE =>
   a[1] = -n;
   if(n > 0.0)
    b[0] = 1.0 - n;
   else
    b[0] = 1.0 + n;
  }
 }
}

Here is some example code to use this filter, and add a knob to control the filter parameter then add the filter to the main loop. See also gui.b:209


 filter := Inst.mk(nil, onepole);
 spawn knob(filter.ctl, "onepole", 0.0, 1.0, 0.01);
 
 ...

  filter.c <-= (<-wrc, wrc);
  rc <-= <-wrc;

I'll show another example that illustrates plugging the instruments together.

mypoly(nil: Source, c: Sample, ctl: Control)
{
  voice := Inst.mk(array[2] of 
     {* => Inst.mk(array[2] of 
        {Inst.mk(nil, waveloop), Inst.mk(nil, adsr)}, 
        instrument)
     }, poly);

  for(;;) alt {
    (a, rc) := <-c =>
        poly.c <-= (a, rc);
    (m, n) := <-ctl =>
        poly.ctl <-= (m, n);
}

I'll pull that first statement apart to make it clearer what's going on. Inst.mk(nil, waveloop) creates the sample and control channels and spawns the waveloop process, passing it the channels and returning an object that stores references to those channels. The same is done for the adsr process, which is an envelope filter. These two sources are passed to the instrument process, which is expecting, say, a generator and filter which it combines to form the monophonic voice that responds to keyon and keyoff control events. Finally, two instances of this instrument voice are passed to the poly process which knows how to mix the input sources into a single polyphonic sound.

Is this an example of higher order function in CSP style?

As before, you need the jit turned on to run this program for it to sound acceptable.

FILES

caerwyn.com/lab/62

Tuesday, April 11, 2006

lab 61 - javascript tool

NAME

lab 61 javascript tool

NOTES

In this lab I wanted to get a standalone javascript interpreter for general scripting use within inferno.

[Deleted a section on reasons why I'd want to do this based on the mix of languages already available--it's not that interesting and is just me trying to rationalize why I like javascript]

There is already a javascript interpreter in inferno as part of the charon web browser. All I'd need to do is add a small set of host objects to interface with the OS.

In this lab I haven't included all the host objects I'd want; not even a fraction. This is just the setup, the beginnings of a javascript tool. The only host object I've added is System which contains three functions: getline, print, and readFile. Getline reads a line from stdin and returns it as a string. Print writes a string to stdout. ReadFile reads the in the contents of a file and returns it as a string.

Suggestions on what the host objects to add are welcome.

Here is an example script showing how to call the tool, called js.

% cat t1.js
function f(n) {
 return n * 2;
}

var s;
while((s = System.getline()))
 System.print(s + f(1));

% echo a | js -f t1.js
a
2% 

FILES

caerwyn.com/lab/61

lab 60 - sequencer using channels

NAME

lab 60 - sequencer using channels

NOTES

In the comments to lab 53 Rog suggested using channels to parse buffers between processes in place of the one-sample-at-a-time technique I was using in my earlier DSP attempts.

This lab is an attempt at Rog's suggestion. It's one limbo file that acts as a simple sequencer and generates it's own voices.

Each instrument should have an interface:

f: fn(c: chan of (array of real, 
         chan of array of real), 
     ctl: chan of (int, real));

This function is spawned and control messages are sent on the ctl channel, and the request for samples and the response channel are sent down c.

It really requires jit to be turned on to sound acceptable.

Here's a sample setup,

% bind -a '#A' /dev
% echo 1 > /dev/jit
% sequencer4 < bachfugue.ski  > /dev/audio

Uncomment sequencer.b4:60 to add a little echo to the music.

I think I'm getting closer to Rog's ideas, but I'm not sure I'm still exploiting it to the fullest. I'm already liking this approach better. I do feel I can change things quicker and plug the modules together easier.

I'll try rearranging things some more.

FILES

caerwyn.com/lab/60