RTcmix: bowed-string model, chaotic oscillators

attempt to make a very simple "bowed string" model, then fun with chaotic equations as audio generators

For the first part of the class, we extended the SIMPLEOSC instrument, adding dynamic frequency control and then phase-resetting. There are no real "links" associated with that, but there are a bunch of fun web sites to visit for the chaos explorations we did in the second part. Here are a few:
• older class on chaos -- These are interpretations of several well-known chaotic attractors at the 'score' level, but the description of the math (and the equations listed) are good. The logistic map ("population equation"), Lorenz and Henon attractors are covered.
• My Music Book -- In Chapter 3, about 2/3 through, is the logistic map demos I showed in class. You can move the "R" value and see how the chaos occurs.
• Lorenz attractor app -- This is the demo I showed in class, very good for seeing how the Lorenz attractor unfolds.
• another Lorenz attractor demo -- Click in the window several times, it shows the 'sensitivity to initial conditions' we discussed.
• still another Lorenz attractor app -- This one is interesting in that shows plots of x-agaisnt-y and x-against-z simultaneously. Also, when you click way off the attractor you can see how the iteration process 'draws in' the point to the constrained attractor space.
• discussion of Lorenz attractor -- Not an app or demo, this talks about the math of how it works and shows some good plots of the attractor behavior, including the "views" of a single variable we used to generate a waveform.
• Henon attractor demo -- For some reason, finding interactive demos of the Henon attractor wasn't as easy as the Lorenz attractor. This is the one I showed in class.
• source for chaos/fractal demo app links -- I used this to find some of the ones above. Check out some of the 'fractal' links for fun -- especially the 'fractal microscope'. We didn't do any fractally-stuff in class, but you might be able to imagine some interesting uses.
• Paul Bourke's geometry/math page -- I just love this web site! The one with all the attractor equations we used in class.

Class Patches, Discussion and Code

I won't be going through all the coding involved in the instruments we now develop, choosing instead to emphasize what we did to make them unique. Check the earlier classes to see how the different member functions interact and compiling/running the code.

RTcmix Instrument: SIMPLEOSC (revisited)
We made one simple modification (and one fix) to our earlier SIMPLEOSC instrument. Click here [SIMPLEOSC.zip] to download the source code for the modified instrument. All we did was add the freq = p[3]; line in our SIMPLEOSC::doupdate() member function to allow dynamic control of the pitch of our oscillator through one of the pfield parameters. THe SIMPLEOSC::doupdate() member function now looks like this:
```void SIMPLEOSC::doupdate()
{
// The Instrument base class update() function fills the p[] array with
// the current values of all pfields.  There is a way to limit the values
// src/rtcmix/Instrument.h.

double p[6];
update(p, 6);

freq = p[3];
amp = p[2];
pan = p[5];
}
```

Also notice that our "p" array is dimensioned at "6" (double p[6];) and that the update() function is now requesting 6 parameters in the array (update(p, 6);). This is because we actually want 6 pfields, even though our highest value seems to be 5 (pan = p[5];). Remember that C/C++ starts counting at "0"! This was a bug in our (my!) earlier version of SIMPLEOSC.

The doupdate() function gets called in the SIMPLEOSC::run() member function like before. But we need to take the updated "freq" value and use it to alter the frequency of our oscillator. Scanning the documentation for the Ooscili object, we see that we can do this easily by employing the setfreq(freq) member function of the Ooscili class. All we have to do is add the frequency-setting in the block were we run the doupdate() check:

```int SIMPLEOSC::run()
{
float currentsample;

for (int i = 0; i < framesToRun(); i++) {
if (--doupdatecheck <= 0) {
doupdate();
theOscillator->setfreq(freq);
doupdatecheck = getSkip();
}

...
```

Our work is finished! We now have a SIMPLEOSC with dynamic control over pitch, amplitude and panning.

RTcmix Instrument: SIMPLEBOW

Going back to the original idea that got us started on this marvelous synthetic journey (the frog-guiro), we thought that we might be able to incorporate the ability to generate a periodic pulse into our oscillator to produce an interesting result. Just for fun, we imagined a crude bowed-string moedl with the string having some basic fundamental frequency (the oscillator part), but the string/oscillator getting reset by the horse-hairs of a rosined bow dragging across it (the pulsed part). We know how to make an oscillator, and we know how to implement a periodic 'impulse' that we could use to interrupt that oscillator. The challenge is to figure out how to reset the oscillator at with each impulse.

Again, so easy! Looking again at the Ooscili documentation, there is a really handy setphase(double phase) class member function that will allow us to reset where the oscillator is reading from the wavetable array (see last week's class for a discussion of how we set up and use the wavetable array). Our pfields now look like this:

```/* SIMPLEBOW - sample code for a very basic synthesis instrument

simple oscillator/bowed instrument

p0 = output start time
p1 = duration
p2 = amplitude multiplier
p3 = frequency of oscillator
p4 = wavetable
p5 = phase reset frequency
p6 = pan value (0 - 1)

*/
```

and we add these lines in our SIMPLEBOW::init() member function:
```resetsamps = SR/p[5];
resetcounter = 0;
```

Our doupdate() function looks like this:
```void SIMPLEBOW::doupdate()
{
double p[7];
update(p, 7);

freq = p[3];
amp = p[2];
resetsamps = SR/p[5];
pan = p[6];
}
```

and in SIMPLEBOW::run() we add the following:
```if (resetcounter <= 0) {
theOscillator->setphase(0);
resetcounter = resetsamps;
}
```

We have now implemented our (very!) crude bowed-string model. When "resetcounter" goes to 0, we set the phase of the oscillator waveform to the beginning of the waveform, no matter where it is in its current oscillation. By using the scorefile command, we can write the audio to a soundfile:
```rtoutput("/some/output/file.aif")
```

Using a soundfile editor (like
Amadeus) we can see how this affects the resulting sound waveform:

However, there is a huge flaw in how it operates as a 'bowed string', but I'll leave it to you to hear the results and figure it out (if you weren't in class, that is). It still sounds cool, though!

RTcmix Instrument: CHAOS1
Making strange oscillator instruments was so much fun, we decided to explore the world of mathematical chaos to see if we could find some very odd semi-oscillatory algorithms (see the links at the top of this page for a few examples). For our first attempt, we used the good ole standard, the "population equation", otherwise known as the logistic map. Click here [CHAOS1.zip] to download the instrument.

Because we are relying on a computation to generate our samples, we don't need a wavetable, no frequency specification, in fact we need to specify very little at all for this instrument to operate. We do want to have control over the "R" value, which determines the various output regions of the equation (perioidic, quasi-periodic, chaotic). This value typically moves from 3.0 to just less than 4.0 (4.0 and above will cause the equation to "explode", less than 3.0 produces a single repeating value -- not good as an oscillator).

Our pfields now look like this:

```/* CHAOS1 - sample code for a basic chaos synthesis instrument
This uses the "logistic map": R*x*(1.0-x)

simple chatic instrument

p0 = output start time
p1 = duration
p2 = amplitude multiplier
p3 = R value
p4 = pan value (0 - 1)

*/
```

In the CHAOS1::init() member function, we need to iterate (i.e. repeat it over and over with past x output values becoming new x inputs) the equation inorder to have it 'settle in' to the regime set by a particular "R" value:
```int CHAOS1::init(double p[], int n_args)
{
// Tell scheduler when to start this inst.  If rtsetoutput returns -1 to
// indicate an error, then return DONT_SCHEDULE.

if (rtsetoutput(p[0], p[1], this) == -1)
return DONT_SCHEDULE;

Rvalue = p[3];

amp = p[2];
doupdatecheck = 0;

// iterate the chaotic equation (probably not even necessary in our case)
x = 0.5;
for (int i = 0; i < 1000; i++) {
x = Rvalue * x * (1.0 - x);
}

pan = p[4];

repeater = 0;

return nSamps();
}
```

Note the "for" loop doing this iteration 1000 times. We have also added a variable, "repeater". We will use this to 'stretch out' the outputs of the equation in order to generate waveforms that don't oscillate around the Nyqust frequency; our 'stretched' waveform will be lower in the generated frequency spectrum. We do this by simply repeating each output of the equation a certain number of times, thus expanding the waveform in time.

The CHAOS1::run() member function looks like this:

```int CHAOS1::run()
{
float currentsample;

for (int i = 0; i < framesToRun(); i++) {
if (--doupdatecheck <= 0) {
doupdate();
doupdatecheck = getSkip();
}

float out[2];		// Space for only 2 output chans!

if (--repeater <= 0) { // get a new value from the equation
x = Rvalue * x * (1.0 - x);
repeater = 10;
}

currentsample = x * amp;

out[1] = currentsample * pan;
out[0] = currentsample * (1.0 - pan);

// Write this sample frame to the output buffer.

// Increment the count of sample frames this instrument has written.
increment();
}

// Return the number of frames we processed.
return framesToRun();
}
```

Simplicity itself! The only other thing we added was an update() to allow us to shift the "R" value as the sound was being generated:
```void CHAOS1::doupdate()
{
double p[5];
update(p, 5);

amp = p[2];
Rvalue = p[3];

for (int i = 0; i < 1000; i++) {
x = Rvalue * x * (1.0 - x);
}

pan = p[4];
}
```

Notice that we re-iterate (yeah!) the code with each new "R" coming in from a pfield control. Again, this is to allow the equation to 'settle in' to the output regime. I made one error in class that prevented us from hearing a changing "R" value. See if you can figure out what's different from what I coded. I'll tell all next week!

I should also note that we are declaring the variables "Rvalue", "x", and "repeater" in the CHAOS1.h class definition file:

```class CHAOS1 : public Instrument {

public:
CHAOS1();
virtual ~CHAOS1();
virtual int init(double *, int);
virtual int configure();
virtual int run();

private:
void doupdate();

float amp;
int doupdatecheck;
float pan;
float x;
float Rvalue;
int repeater;
};
```

This, of course, is so they may be accessed from the different member functions (init(), run(), doupdate()) of the CHAOS1 instruments. "amp", "pan", etc. are also declared and used the same way.

One final comment: we are also including the CHAOS1::configure() member function. In our current instruments we don't use it at all. Essentially this is called just prior to a note being executed. This is different from the init() member function which is called when a note is scheduled. The configure() member function allows you to do things (like allocate memory arrays, etc.) that you don't really need to do througout an entire RTcmix script, but that you want to do in time for the execution of a note. We may show how this is used in a future class.

RTcmix Instrument: CHAOS2
The next chaotic system we tackled was the famous Lorenz attractor (again, see above for good links). Click here [CHAOS2.zip] to download the instrument code.

The equation system for the Lorenz attractor is a little more complicated, instead of generating a single value it instead puts out three 'increments' (it's a system of differential equations). Each of these increments determines how far to move on an x-y-z plane to plot the next point. The only value we can easily control is the amount of increment, or "delta", that is then multiplied by the x- y- and z-outputs of the equation system as it iterates. We specify that delta as a pfield:

```p0 = output start time
p1 = duration
p2 = amplitude multiplier
p3 = delta value (typically 0.01)
p4 = pan value (0 - 1)
```

In the CHAOS2::init() member function, all we do is assign initial value for the variables "x", "y", "z" and "delta" (all declared in the CHAOS2.h file because they will be used througout the instrument):
```int CHAOS2::init(double p[], int n_args)
{
// Tell scheduler when to start this inst.  If rtsetoutput returns -1 to
// indicate an error, then return DONT_SCHEDULE.
if (rtsetoutput(p[0], p[1], this) == -1)
return DONT_SCHEDULE;

amp = p[2];
doupdatecheck = 0;

x = 1.0;
y = 1.0;
z = 1.0;
delta = p[3];

pan = p[3];

return nSamps();
}
```

The change to CHAOS2::doupdate() member function to allow control of the "delta" value is so trivial I won't even list it here.

Finally, the CHAOS2::run() member function is a little more complicated than our past instruments because the Lorenz equation itself requires a few steps to calculate:

```int CHAOS2::run()
{
float currentsample;
float xnew, ynew, znew, dx, dy, dz;

for (int i = 0; i < framesToRun(); i++) {
if (--doupdatecheck <= 0) {
doupdate();
doupdatecheck = getSkip();
}

float out[2];		// Space for only 2 output chans!

// here is the attractor equation!
dx = 10.0 * (y-x);
dy = x * (28.0-z) - y;
dz = x*y - (8.0/3.0)*z;
xnew = x + delta*dx;
ynew = y + delta*dy;
znew = z + delta*dz;

x = xnew;
y = ynew;
z = znew;

currentsample = (x/30.0) * amp;

out[1] = currentsample * pan;
out[0] = currentsample * (1.0 - pan);

// Write this sample frame to the output buffer.

// Increment the count of sample frames this instrument has written.
increment();
}

// Return the number of frames we processed.
return framesToRun();
}
```

Note the local -- i.e. inside the CHAOS2::run() member function -- declarations of these variables:
```float currentsample;
float xnew, ynew, znew, dx, dy, dz;
```

That's because we only need these variables in this function, and they are used 'new' each time we enter the function. We could have declared them in the CHAOS2.h file, but it wasn't necessary.

Also note the scaling of the "x" value before being multipled by "amp":

```currentsample = (x/30.0) * amp;
```

That's because we ran this instrument initially just printing out the "x" values and noticed that they tended to go between 0 and 30 or so. We did this to get a sense of what the outputs for the Lorenz equation system would be. Dividing the "x" value by 30.0 should get us values between 0 and 1, which we can then scale by "amp" and get a waveform amplitude close to what we expect.

The remarkable thing is that this instrument worked! The sound is interesting, definitely worthy of further exploration. I had planned to look at a few other chaotic attractor equations, but we ran out of time. Check back on this page in a few days, if I get some time I may try coding some more up and putting them here.