- eq-prob-markovprogs.tar.gz -- C source code we did in class
- algopatches.sit -- stuffit archive of two basic MAX/MSP packages doing equation tracking

The basic parabolic equation y = x^2 creates a graph that looks like this:

*there will be a graph here*

There are several problems with the basic equation. First of all,
it goes in the opposite direction from the sonic effect we want.
Secondly, the output numbers are wrong for our purpose -- frequencies
between 0 Hz 4 Hz, while surely fun to have around, won't really
create the rising/falling pitch that we'd like.

There are two approaches to dealing with these problems. The first is to change the equation to output the numerical values we want directly. The second is to take the numbers resulting from the equation and then operate on them to move them to the range we prefer. In this case, we'll do both.

The first thing is to change the direction of the equation. This
is easy -- just change the sign of the equation to generate negative
values instead of positive values:

*there will be a graph here*

Next we'll shift the equation up to a partly positive range to give us
a span of values that we can scale up to appropriate frequencies.
This is done by adding a constant value to the equation:

*there will be a graph here*

Now we have a nice, "well-behaved" equation that will give us
values between 0.0 and +4.0 as we move from x = -2.0 to x = +2.0.
At this point, it is trivial to translate this into a scorefile
to generate the notes. We do need to make some decisions about
how the sound will be generated -- we'll create 40 separate
notes using the WAVETABLE instrument
*(see the documentation for
WAVETABLE
for information describing how the instrument works)*,
and we'll let each one
last for 1 second. We'll also generate one note every 0.1 seconds to
create a snazzy overlapping effect that will surely shake the
foundations of serious music. Finally, we will have our notes
start with a base frequency of 100 Hz and reach a height of
500 Hz at the top of the parabolic trajectory:

rtsetparams(44100, 1) load("WAVETABLE") start = 0.0 x = -2 makegen(1, 24, 1000, 0,0, 1,1, 2,0) makegen(2, 10, 1000, 1, 0.3, 0.2) for (i = 0; i < 40; i = i+1) { y = 0.0-(x*x) + 4.0 freq = (y/4.0) * 400.0 + 100.0 WAVETABLE(start, 1.0, 7000.0, freq) start = start + 0.1 x = x + (4.0/40.0) }

A couple of notes -- the line

y = 0.0-(x*x) + 4.0

freq = (y/4.0) * 400.0 + 100.0

start = start + 0.1 x = x + (4.0/40.0)

Next, we decided to control several parameters of a slightly
more complex synthesis technique (FM) using an algorithmic/equation
approach. Basic FM requires that you specify the frequency of
the carrier, the frequency of the modulator and an index (or depth
of FM) for each note *(see the documentation for
FMINST
for more information.)* One way to accomplish this would be to define
two independent equations that would generate values for the
three parameters. This is a "one-to-many" mapping, where each equation
would essentially be a function of time (NOTE: the above simple parabola
example is also a function of time -- x varies in direct proportion to
the start time of each note).

A more interesting technique (perhaps) would be to link the parameters
together, where the values of carrier/modulator/index would be
somewhat dependent on each other. This would define a "surface" of
possible values; the shape of the surface would be constrained by
the equation we used to link the parameters together. We worked from
a modified version of the simple parabolic equation, linking the frequency
of the carrier to the frequency of the modulator to produce the
index: z = (x^2) - (y^2):

*there will be a graph here*

"x" became the carrier frequency, "y" the modulator, and "z" the
resultant index. The surface above decribes the set of possible
combinations of these three parameters. By changing how we vary
x and y over time, we can 'explore' parts of this surface, discovering
bold new combinations of carrier/modulator/index *never before
heard by human ears!*

The following scorefile demonstrates this:

rtsetparams(44100, 1) load("FMINST") start = 0.0 x = 0 y = 0 makegen(1, 24, 1000, 0,0, 1,1, 2,0) makegen(2, 10, 1000, 1, 0.3, 0.2) makegen(3, 24, 1000, 0,0, 1,1, 2,0) for (i = 0; i < 40; i = i+1) { z = (x*x) - (y*y) carrier = x * 100 modulator = y * 100 index = z FMINST(start, 0.5, carrier, modulator, 0.0, index) start = start + 0.1 x = x + (100.0/40.0) y = y + (5.0/40.0) }

As fascinating as these equation-driven sounds can be, many composers are after a sound that is a little less "computer-music-like". The direct mapping of abstract mathematical constructions onto sonic parameters can yield a rather 'perfect' sound -- and music in the Real World is often much less than perfect.

The quick and dirty way to change this is to simply inject a bit of randomness (or actually pseudo-randomness on contemporary digital machines, but random enough to fool my ears!) into the generated parameter values. The following RTcmix score does exactly this. It creates a "cloud" of notes that follow the simple parabolic frequency trajectory used in the first scorefile above:

rtsetparams(44100, 1) load("WAVETABLE") start = 0.0 x = -2 makegen(1, 24, 1000, 0,0, 1,1, 2,0) makegen(2, 10, 1000, 1, 0.3, 0.2) for (i = 0; i < 40; i = i+1) { y = 0.0-(x*x) + 4.0 freq = (y/4.0) * 400.0 + 100.0 for (k = 0; k < 4; k = k + 1) { WAVETABLE(start, 1.0, 1000.0, freq + (random() * 200.0)) } start = start + 0.1 x = x + (4.0/40.0) }

As powerful as this technique of "windowing" a random value can
be, it is possible to get much more subtle in our use of randomness.
The use of different types of randomness allows us to control
things like 'tendencies' towards particular values, or to
generate random numbers with greater or lesser likelihoods
in certain ranges. This is done by employing different
probability distributions. The good news is that the generating
equations for a wide variety of probability distributions
already exist (an early **Computer Music Journal** titled
* A Panoply of Stochastic Cannons* (Spring, 1979, vol. 3 --
also available through the Music Library)
by Denis Lorraine has a good description/listing of a number
of musically useful probability distribution equations).

In class, we used one of the simplest and most common probability
distributions, the Gaussian or "normal" distribution
(also known as the infamous "Bell Curve"):

*there will be a graph here*

The appropriately-scaled equation for this distribution will
generate "random" numbers between 0 and 1, but the probabilty of
getting a number close to the 0.5 is much greater than a number
near 0 or 1.

The first thing we tried with the Gaussian probability distribution
is a simple mapping of generated numbers onto one octave of
a chromatic scale. We were hoping to achieve a "pitchiness"
centered around F# due to the higher probability of generating
a random number in the middle of the octave (0.5). The following
C program (__diationic1.c__) prints an RTcmix scorefile
for the STRUM instrument that does this:

#include <stdio.h> main() { int i; float prob; float gauss(float,float); float st; printf("rtsetparams(44100, 1)\n"); printf("load(\"STRUM\")\n"); st = 0.0; for (i = 0; i < 500; i++) { prob = gauss(0.5, 0.16666); if (prob < (1.0/12.0)) { printf("START(%f, 1.0, 8.00, 1.0, 0.1, 10000.0, 1)\n",st); } else if (prob < (2.0/12.0)) { printf("START(%f, 1.0, 8.01, 1.0, 0.1, 10000.0, 1)\n",st); } else if (prob < (3.0/12.0)) { printf("START(%f, 1.0, 8.02, 1.0, 0.1, 10000.0, 1)\n",st); } else if (prob < (4.0/12.0)) { printf("START(%f, 1.0, 8.03, 1.0, 0.1, 10000.0, 1)\n",st); } else if (prob < (5.0/12.0)) { printf("START(%f, 1.0, 8.04, 1.0, 0.1, 10000.0, 1)\n",st); } else if (prob < (6.0/12.0)) { printf("START(%f, 1.0, 8.05, 1.0, 0.1, 10000.0, 1)\n",st); } else if (prob < (7.0/12.0)) { printf("START(%f, 1.0, 8.06, 1.0, 0.1, 10000.0, 1)\n",st); } else if (prob < (8.0/12.0)) { printf("START(%f, 1.0, 8.07, 1.0, 0.1, 10000.0, 1)\n",st); } else if (prob < (9.0/12.0)) { printf("START(%f, 1.0, 8.08, 1.0, 0.1, 10000.0, 1)\n",st); } else if (prob < (10.0/12.0)) { printf("START(%f, 1.0, 8.09, 1.0, 0.1, 10000.0, 1)\n",st); } else if (prob < (11.0/12.0)) { printf("START(%f, 1.0, 8.10, 1.0, 0.1, 10000.0, 1)\n",st); } else { printf("START(%f, 1.0, 8.11, 1.0, 0.1, 10000.0, 1)\n",st); } st = st + 0.1; } } float gauss(float mean, float stdev) { int j,k; float randnum; float scale = 1.0; float halfN = 6.0; int N = 12; static long randx = 1; float output; output = -1.0; while (output < 0.0) { randnum = 0.0; for (j = 0; j < N; j++) { k = ((randx = randx * 1103515245 + 12345) >> 16) & 077777; randnum += (float) k / 32768.0; } output = stdev * scale * (randnum - halfN) + mean; } return(output); }

if (prob < (1.0/12.0)) { printf("START(%f, 1.0, 8.00, 1.0, 0.1, 10000.0, 1)\n",st); } else if (prob < (2.0/12.0)) { printf("START(%f, 1.0, 8.01, 1.0, 0.1, 10000.0, 1)\n",st); } else if (prob < (3.0/12.0)) { ... (etc.)

A better approach would be to use what we know of the tonal system
to make pitches in the scale more probable that would reinforce
a specific tonality. The following program (__diatonic2.c__)
attempts to do just that.
It writes an RTcmix scorefile for the WAVESHAPE instrument, placing
notes that reinforce a tonality of F (F, Bb, C, etc.) at
the center of the Gaussian distribution:

#include <stdio.h> main() { int i; float prob; float gauss(float,float); float st; int index; float pitches[10]; float pch; printf("rtsetparams(44100, 2)\n"); printf("load(\"WAVESHAPE\")\n"); printf("makegen(1, 24, 1000, 0,0, 0.1,0, 1,1, 2,0, 2.1,0)\n"); printf("makegen(2, 10, 1000, 1)\n"); printf("makegen(3, 17, 1000, 0.9, 0.3, -0.2, 0.6, -0.7)\n"); printf("makegen(4, 24, 1000, 0,0, 1,1, 2,0)\n"); pitches[0] = 6.04; pitches[1] = 6.08; pitches[2] = 6.09; pitches[3] = 6.07; pitches[4] = 6.00; pitches[5] = 6.05; pitches[6] = 6.10; pitches[7] = 6.03; pitches[8] = 6.02; pitches[9] = 6.01; st = 0.0; for (i = 0; i < 50; i++) { do { prob = gauss(0.5, 0.16666); } while (prob > 1.0); index = prob * 10.0; pch = pitches[index]; printf("WAVESHAPE(%f,7.0,%f+(random()*0.003),0,0.9,5000,0)\n",st,pch); printf("WAVESHAPE(%f,7.0,%f+(random()*0.003),0,0.9,5000,0.2)\n",st,pch); printf("WAVESHAPE(%f,7.0,%f+(random()*0.003),0,0.9,5000, 0.8)\n",st,pch); printf("WAVESHAPE(%f,7.0,%f+(random()*0.003),0,0.9,5000,1)\n",st,pch); st = st + (4.0 * gauss(0.5, 0.16666)); } } float gauss(float mean, float stdev) { int j,k; float randnum; float scale = 1.0; float halfN = 6.0; int N = 12; static long randx = 0.1; float output; output = -1.0; while (output < 0.0) { randnum = 0.0; for (j = 0; j < N; j++) { k = ((randx = randx * 1103515245 + 12345) >> 16) & 077777; randnum += (float) k / 32768.0; } output = stdev * scale * (randnum - halfN) + mean; } return(output); }

Our final use of the Gaussian distribution was to generate values for time points instead of pitches. Our goal was to produce a semi-convincing simulation of a group of people clapping hands in unison, sort of like what you might experience at a sporting event. Using the first part of the Gaussian curve as a guide, it is possible to create the 'thwump' of a crowd attempting to clap a repeated rhythm together.

The method used to do this is very straighfoward -- just take a repeated time-step and add to it a scaled-down version of the values coming from the first half of the Gaussian curve. The following program does this, using as input a file of individual handclaps as a source soundfile:

#include <stdio.h> main() { int i,j; float claparray[25] = { 0.298, 0.846, 1.405, 1.998, 2.54, 3.078, 3.659, 4.232, 4.802, 5.413, 5.989, 8.787, 7.134, 7.630, 8.267, 8.781, 9.353, 9.898, 10.435, 10.991, 11.561, 12.129, 12.637, 13.207, 13.782 }; float gauss(float, float); float brrand(); float gnum; float st,inskip; int index; printf("rtsetparams(44100, 2)\n"); printf("load(\"STEREO\")\n"); printf("rtinput(\"../claps.aiff\")\n"); st = 0.0; for (i = 0; i < 20; i++) { for (j = 0; j < 30; j++) { do { gnum = gauss(0.5, 0.16666); } while (gnum > 0.5); gnum *= 0.7; index = brrand() * 25.0; inskip = claparray[index]; printf("STEREO(%f, %f, 0.25, 1.0, %f)\n",st+gnum,inskip,brrand()); } st += 0.6; } } static long randx = 1; float gauss(float mean, float stdev) { int j,k; float randnum; float scale = 1.0; float halfN = 6.0; int N = 12; float output; output = -1.0; while (output < 0.0) { randnum = 0.0; for (j = 0; j < N; j++) { k = ((randx = randx * 1103515245 + 12345) >> 16) & 077777; randnum += (float) k / 32768.0; } output = stdev * scale * (randnum - halfN) + mean; } return(output); } float brrand() { int i = ((randx = randx*1103515245 + 12345)>>16) & 077777; return((float)i/32768.0); }

Note that the following section of code (used to produce the
"start point offset" from each time-step starting point)
makes use of *only* the 'rising' portion of the Gaussian
distribution:

do { gnum = gauss(0.5, 0.16666); } while (gnum > 0.5);

In one sense, the above use of probabilities is relatively straightforward. The probability distribution is mapped directly onto a musical parameter for a single note or event -- every other event is independent from the others.

The basic idea behind Markov chains is quite simple. Given the
occurence of some event *E1*, then
the next events *E2*, *E3*,
etc. have a certain probability of subsequently happening.
Each of these events can lead to another probabilistic branching.
The following graph shows how this works:

*there will be a graph here*

To see this idea in action, we took a set of simple garage-band
blues guitar riffs (the set of riffs can be generated using
the "blues" program in the
eq-prob-markovprogs.tar.gz
package)
-- seven total -- and created a set of
Markov dependencies according to the following graph:

*there will be a graph here*

Note the recursion, or return back to the "base" riff, that keeps
the music happening until we decide we've had enough.

The following code realizes the Markov graph for the blues guitar
riffs (__markovblues.c__):

#include <stdio.h> main() { int i; float start; float prob; float brrand(); void base(float); void riff1(float); void riff2(float); void riff3(float); void riff4(float); void riff5(float); void riff6(float); void riff7(float); printf("rtsetparams(44100, 1)\n"); printf("load(\"STRUM\")\n"); start = 0.0; while (start < 20.0) { base(start); start += 2.0; prob = brrand(); if (prob < 0.5) { riff1(start); start += 2.0; } else { riff2(start); start += 2.0; prob = brrand(); if (prob < 0.5) { } else { riff3(start); start += 2.0; prob = brrand(); if(prob < 0.5) { riff4(start); start += 2.0; } else if (prob < 0.8) { riff5(start); start += 2.0; prob = brrand(); if (prob < 0.5) { riff6(start); start += 2.0; prob = brrand(); if (prob < 0.5) { riff7(start); start += 2.0; } else { } } else { } } } } } } void base(float t) { printf("START1(%f, 0.55, 6.09, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t); printf("START1(%f, 0.55, 7.04, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t); printf("FRET1(%f, 0.1, 6.09, 0.1, 0.1, 100, 0.0, 7.00, 0,1,10000)\n",t+0.55); printf("FRET1(%f, 0.1, 7.04, 0.1, 0.1, 100, 0.0, 7.00, 0,1,10000)\n",t+0.55); printf("START1(%f, 0.25, 6.09, 1, 1, 100, 0.05, 7.00, 0, 1,10000, 9)\n",t+0.65); printf("START1(%f, 0.25, 7.04, 1, 1, 100, 0.05, 7.00, 0, 1,10000, 9)\n",t+0.65); printf("FRET1(%f, 0.1, 6.09, 0.1, 0.1, 100, 0.0, 7.00, 0,1, 10000)\n",t+0.9); printf("FRET1(%f, 0.1, 7.04, 0.1, 0.1, 100, 0.0, 7.00, 0,1, 10000)\n",t+0.9); printf("START1(%f, 0.55, 6.09, 1, 1, 100, 0.05, 7.00, 0, 1, 10000, 7)\n",t+1.0); printf("START1(%f, 0.55, 7.06, 1, 1, 100, 0.05, 7.00, 0, 1, 10000, 7)\n",t+1.0); printf("FRET1(%f, 0.1, 6.09, 0.1, 0.1, 100, 0.0, 7.00, 0,1,10000)\n",t+1.55); printf("FRET1(%f, 0.1, 7.06, 0.1, 0.1, 100, 0.0, 7.00, 0,1,10000)\n",t+1.55); printf("START1(%f, 0.25, 6.09, 1, 1, 100, 0.05, 7.00, 0, 1,10000, 9)\n",t+1.65); printf("START1(%f, 0.25, 7.06, 1, 1, 100, 0.05, 7.00, 0, 1,10000, 9)\n",t+1.65); printf("FRET1(%f, 0.1, 6.09, 0.1, 0.1, 100, 0.0, 7.00, 0,1, 10000)\n",t+1.9); printf("FRET1(%f, 0.1, 7.06, 0.1, 0.1, 100, 0.0, 7.00, 0,1, 10000)\n",t+1.9); } void riff1(float t) { printf("START1(%f, 0.55, 6.09, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t); printf("START1(%f, 0.55, 7.04, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t); printf("FRET1(%f, 0.1, 6.09, 0.1, 0.1, 100, 0.0, 7.00, 0,1,10000)\n",t+0.55); printf("FRET1(%f, 0.1, 7.04, 0.1, 0.1, 100, 0.0, 7.00, 0,1,10000)\n",t+0.55); printf("START1(%f, 0.35, 6.04, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+0.65); printf("START1(%f, 0.33, 6.07, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+1.0); printf("START1(%f, 0.33, 6.04, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+1.33); printf("START1(%f, 0.33, 6.07, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+1.66); } void riff2(float t) { printf("START1(%f, 0.33, 6.09, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t); printf("START1(%f, 0.33, 7.00, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+0.33); printf("START1(%f, 0.33, 7.02, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+0.66); printf("START1(%f, 0.0, 7.02, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+1.0); printf("makegen(1, 24, 1000, 0,0, 1,1, 2,0)\n"); printf("BEND1(%f,0.33,7.02,7.03,1, 1,1,100, 0.05, 7.00,0,1,10000,100)\n",t+1.0); printf("START1(%f, 0.33, 7.02, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+1.33); printf("START1(%f, 0.33, 7.00, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+1.66); } void riff3(float t) { printf("START1(%f, 0.0, 7.02, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t); printf("makegen(1, 24, 1000, 0,0, 1,1, 2,0)\n"); printf("BEND1(%f,0.33,7.02,7.03,1, 1,1,100, 0.05, 7.00,0,1,10000,100)\n",t); printf("START1(%f, 0.33, 7.02, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+0.33); printf("START1(%f, 0.33, 7.00, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+0.66); printf("START1(%f, 0.0, 7.02, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+1.0); printf("makegen(1, 24, 1000, 0,0, 1,1, 2,0)\n"); printf("BEND1(%f,0.33,7.02,7.03,1, 1,1,100, 0.05, 7.00,0,1,10000,100)\n",t+1.0); printf("START1(%f, 0.33, 7.02, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+1.33); printf("START1(%f, 0.33, 7.00, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+1.66); } void riff4(float t) { printf("START1(%f, 0.0, 7.02, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t); printf("makegen(1, 24, 1000, 0,0, 1,1, 2,0)\n"); printf("BEND1(%f,0.33,7.02,7.03,1, 1,1,100, 0.05, 7.00,0,1,10000,100)\n",t); printf("BEND1(%f,0.33,7.02,7.03,1, 1,1,100, 0.05,7.00,0,1,10000,100)\n",t+0.33); printf("BEND1(%f,0.33,7.02,7.03,1, 1,1,100, 0.05,7.00,0,1,10000,100)\n",t+0.66); printf("START1(%f, 0.0, 7.02, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+1.0); printf("makegen(1, 24, 1000, 0,0, 1,1, 2,0)\n"); printf("BEND1(%f,0.33,7.02,7.03,1, 1,1,100, 0.05, 7.00,0,1,10000,100)\n",t+1.0); printf("START1(%f, 0.33, 7.02, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+1.33); printf("START1(%f, 0.33, 7.00, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+1.66); } void riff5(float t) { int i; float tadd,tincr; tadd = 0.0; tincr = 0.33/2.0; for (i = 0; i < 4; i++) { printf("START1(%f, %f, 7.02, 1, 1,100,0.05,7.00,0,1,10000, 7)\n",t+tadd,tincr); tadd += tincr; printf("FRET1(%f, %f, 7.04, 1,1,100,0.05, 7.00, 0,1, 10000, 7)\n",t+tadd,tincr); tadd += tincr; printf("FRET1(%f, %f, 7.07, 1,1,100,0.05, 7.00, 0,1, 10000, 7)\n",t+tadd,tincr); tadd += tincr; } } void riff6(float t) { int i; float tadd,tincr; tadd = 0.0; tincr = 0.33/3.0; for (i = 0; i < 6; i++) { printf("START1(%f, %f, 7.09, 1, 1,100,0.05,7.00,0,1,10000, 7)\n",t+tadd,tincr); tadd += tincr; printf("FRET1(%f, %f, 7.07, 1,1,100,0.05, 7.00, 0,1, 10000, 7)\n",t+tadd,tincr); tadd += tincr; printf("FRET1(%f, %f, 7.04, 1,1,100,0.05, 7.00, 0,1, 10000, 7)\n",t+tadd,tincr); tadd += tincr; } } void riff7(float t) { int i; float tadd,tincr; tadd = 0.0; tincr = 0.33/3.0; for (i = 0; i < 5; i++) { printf("START1(%f, %f, 7.09, 1, 1,100,0.05,7.00,0,1,10000, 7)\n",t+tadd,tincr); tadd += tincr; printf("FRET1(%f, %f, 7.07, 1,1,100,0.05, 7.00, 0,1, 10000, 7)\n",t+tadd,tincr); tadd += tincr; printf("FRET1(%f, %f, 7.04, 1,1,100,0.05, 7.00, 0,1, 10000, 7)\n",t+tadd,tincr); tadd += tincr; } printf("START1(%f, 0.0, 7.02, 1, 1, 100, 0.05, 7.00, 0,1, 10000, 7)\n",t+1.66); printf("makegen(1, 24, 1000, 0,0, 1,1, 2,1)\n"); printf("BEND1(%f,0.33,7.11,8.00,1, 1,1,100, 0.05,7.00,0,1,10000,100)\n",t+1.66); } static long randx = 1; float brrand() { int i = ((randx = randx*1103515245 + 12345)>>16) & 077777; return((float)i/32768.0); }

The striking thing about this example is that the probabilities
were working at a fairly 'high' level in the musical construction --
all of the basic work of note-creation was encapsulted in the
various *riff*()* functions that were being selected.
We generally took a lower-level approach in most of the class
examples, using algorithmic and probabilistic processes to
set the values of parameters for individual notes. These
same processes can indeed be applied to greater spans
of musical data, to the point where a single mathematical process can
be used to unfold an entire piece of music.

See, composition doesn't *have* to be hard!

1. Try different equations to generate single or multiple note parameters (my personal favorite from the Apple Graphing Calculator demos is the following:)

2. Model a varying drum pattern using Markov techniques. Try altering the probabilities of each branch as time passes.

3. Apply a probabilistic approach to the construction of
timbre.