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) }
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);
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!
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.