Synthesis Fun for Ethan (1)

Ok, here's how I might go about developing a synthetic sound. Suppose I want a low droney sound for some reason. I'll start with a square wave at the C two octives below middle-C:
rtsetparams(44100, 2)
load("WAVETABLE")

wave = maketable("wave", 1000, "square")

WAVETABLE(0, 8.7, 30000, 6.00, 0.5, wave)


[NOTE: I'm leaving the rtsetparams() and load() functions in the example scores in case you want to run them with 'standlone' RTcmix. For 'embedded' RTcmix applications (like rtcmix~ or ofRTcmix, etc.) you don't need these. It won't do anything to include them; they will be ignored]

A very square-wavey sound, if you like that kind of thing. I'm going to try adding some interest by putting four slightly detuned square waves together:
rtsetparams(44100, 2)
load("WAVETABLE")

wave = maketable("wave", 1000, "square")
amp = 10000
dev = 0.004

for (i = 0; i < 4; i += 1) {
   WAVETABLE(0, 8.7, amp, 6.00+irand(0, dev), 0.5, wave)
}


Coupla notes on this: I've turned "amp" and "dev" into vars. This is so that I can easly tweak the overall values. "amp" is set to 10000, but shouldn't it be 8000? 4*8000 = 32000, which is close to the 32768 upper limit for 16-bit synthesis. Why does 10000 only yield an output amplitude of 20000? It's because the square waves are detuned, and parts of them are destructively interfering (and sometimes constructively) with each other. Certainly the peaks on each wave don't line up to give us a full 32768 addition.

The "dev" parameter is set to less then 1/2-a-semitone (I'm using oct.pc for my pitch specification) -- and that's the upper bound. The real amount of pitch deviation will be set by the random number generator. Fooling around with the "dev" value can be fun! Also notice that the note is slightly sharp; I don't have it 'centered' around 6.00. If I did this:

    WAVETABLE(0, 8.7, amp, 6.00+irand(-dev, dev), 0.5, wave)
you might think that I would get pitches centered around 6.00. But the real result will be surprising (think about what 6.00 - 0.00278 [for example] would translate into for an oct.pc specification...). I've found that the 'sharpness' in these composite notes isn't a real perceptual problem. If it is, you can always translate to absolute frequency and do your work there.

I would like to have a 'bigger' sound in the stereo spectrum, though. One way to do this would be to spread each of the four notes across the two speakers using the p4 p-field of the notes:

rtsetparams(44100, 2)
load("WAVETABLE")

wave = maketable("wave", 1000, "square")
amp = 10000
dev = 0.004

for (i = 0; i < 4; i += 1) {
   WAVETABLE(0, 8.7, amp, 6.00+irand(0, dev), i/3, wave)
}


Using the index, I can have the notes placed at 0, 0.333, 0.6666, and 1.0 in the 0-1 stereo output.

I don't really think that is 'wide' enough, though, and I'd like to 'fatten' the sound a little more. I'm going to collapse all four of the square-wave notes into channel 0 of their output and run that into the PANECHO instrument to have the sound ping-pong delayed between the two stereo channels:

rtsetparams(44100, 2)
load("WAVETABLE")
load("PANECHO")

bus_config("WAVETABLE", "aux 0 out")
bus_config("PANECHO", "aux 0 in", "out 0-1")


wave = maketable("wave", 1000, "square")
amp = 4000
dev = 0.004

for (i = 0; i < 4; i += 1) {
   WAVETABLE(0, 8.7, amp, 6.00+irand(0, dev), 0, wave)
}

PANECHO(0, 0, 8.7, 1, 0.2, 0.34, 0.5, 5.0, 0)


I had to cut back on my WAVETABLE amplitude a lot, because now the echoed versions of each note are being added back in and causing the amplitude to greatly increase. I could have done the same thing by reducing the amp on the PANECHO to something less then 1.0.

Two things I want to change now. First of all, the abrupt attack and decay are being picked up by the PANECHO and scattered through the beginning and ending of the sound. This could be a cool effect, but I want something more 'rounded'. This is easily done by applying an amplitude envelope with maketable(). I could do it to each of the WAVETABLE notes, but it's easier just to apply it to the PANECHO, which is the last instrument in the audio chain:

rtsetparams(44100, 2)
load("WAVETABLE")
load("PANECHO")

bus_config("WAVETABLE", "aux 0 out")
bus_config("PANECHO", "aux 0 in", "out 0-1")


wave = maketable("wave", 1000, "square")
amp = 4000
dev = 0.004

for (i = 0; i < 4; i += 1) {
   WAVETABLE(0, 8.7, amp, 6.00+irand(0, dev), 0, wave)
}

pamp = 1
pampenv = maketable("line", 1000, 0,0, 10,1, 90,1, 100,0)

PANECHO(0, 0, 8.7, pamp*pampenv, 0.2, 0.34, 0.5, 5.0, 0)


Remember that maketable() will fit the overall line-segment curve to the length of the note. I just used an arbitary 1-100 scale to construct an envelope that will fade up for 10% of the note and fade down for the last 10% of the note.

The other thing I want to change is to add a little more animation to the sound. I'm getting a fair amount of action from the detuned square waves, but the tuning remains completely fixed during the course of the sound. In the real world, this doesn't happen; oscillators drift out of tune one way and another. I'm going to semi-simulate this by attaching an LFO (low frequency oscillator -- generally an oscillator that works under 20 Hz) to the pitch of each of my four square-wave oscillators:

rtsetparams(44100, 2)
load("WAVETABLE")
load("PANECHO")

bus_config("WAVETABLE", "aux 0 out")
bus_config("PANECHO", "aux 0 in", "out 0-1")


wave = maketable("wave", 1000, "square")
amp = 4000
dev = 0.004

theLFO = {} // note that we have to initialize Minc arrayse
maxLFOfreq = 2.0
maxLFOdev = 0.001
for (i = 0; i < 4; i += 1) {
	theLFO[i] = makeLFO("sine", irand(maxLFOfreq), irand(maxLFOdev))
}

for (i = 0; i < 4; i += 1) {
	WAVETABLE(0, 18.7, amp, 6.00+irand(0, dev)+theLFO[i], 0, wave)
}

pamp = 1
pampenv = maketable("line", 1000, 0,0, 10,1, 90,1, 100,0)

PANECHO(0, 0, 18.7, pamp*pampenv, 0.2, 0.34, 0.5, 5.0, 0)


I use a separate LFO for each WAVETABLE so that they can all evolve slightly differently (the source of the audio 'interest'). Altering the "maxLFOfreq" and "maxLFOdev" params can produce some interesting (and sometimes subtle) alterations.

I also made the notes longer so I could hear more of the evolution of the sound due to the dynamic pitch-changing. Hearing the longer audio really brought out the 'square-waviness' of the sound for me, and I think I'd like to mellow things out a little more. I can do this by inserting a low-pass filter into the audio chain:

rtsetparams(44100, 2)
load("WAVETABLE")
load("MOOGVCF")
load("PANECHO")

bus_config("WAVETABLE", "aux 0 out")
bus_config("MOOGVCF", "aux 0 in", "aux 1 out")
bus_config("PANECHO", "aux 1 in", "out 0-1")


wave = maketable("wave", 1000, "square")
amp = 10000
dev = 0.004
dur = 21

theLFO = {}
maxLFOfreq = 2.0
maxLFOdev = 0.001
for (i = 0; i < 4; i += 1) {
	theLFO[i] = makeLFO("sine", irand(maxLFOfreq), irand(maxLFOdev))
}

for (i = 0; i < 4; i += 1) {
	WAVETABLE(0, dur, amp, 6.00+irand(0, dev)+theLFO[i], 0, wave)
}

MOOGVCF(0, 0, dur, 1, 0, 0, 0, 700, 0.7)

pamp = 1
pampenv = maketable("line", 1000, 0,0, 10,1, 90,1, 100,0)

PANECHO(0, 0, dur, pamp*pampenv, 0.2, 0.34, 0.5, 5.0, 0)


Nifty sound! I had to boost the WAVETABLE amps back up from 4000 to 8000 to compensate for the loss of amplitude from the filter. I also changed "dur" into a variable so that I can change it once and have all of the synthesis and DSP RTcmix instruments alter to fit.

One last thing I'd like to do -- the filter creates an interesting sound, but it might even be more interesting if I had the cutoff frequency of the filter randomly move around (by the way, how did I know to try 700 Hz for the cutoff? Just by fooling around... for years). This can be done using the low-freqency makerandom() p-field command:

rtsetparams(44100, 2)
load("WAVETABLE")
load("MOOGVCF")
load("PANECHO")

bus_config("WAVETABLE", "aux 0 out")
bus_config("MOOGVCF", "aux 0 in", "aux 1 out")
bus_config("PANECHO", "aux 1 in", "out 0-1")


wave = maketable("wave", 1000, "square")
amp = 10000
dev = 0.004
dur = 21

theLFO = {}
maxLFOfreq = 2.0
maxLFOdev = 0.001
for (i = 0; i < 4; i += 1) {
	theLFO[i] = makeLFO("sine", irand(maxLFOfreq), irand(maxLFOdev))
}

for (i = 0; i < 4; i += 1) {
	WAVETABLE(0, dur, amp, 6.00+irand(0, dev)+theLFO[i], 0, wave)
}

randcutoff = makerandom("even", 0.3, 500, 2000)
MOOGVCF(0, 0, dur, 1, 0, 0, 0, randcutoff, 0.7)

pamp = 1
pampenv = maketable("line", 1000, 0,0, 10,1, 90,1, 100,0)

PANECHO(0, 0, dur, pamp*pampenv, 0.2, 0.34, 0.5, 5.0, 0)


I'm using a frequency of 0.3 for the makerandom() generator, which will alter the cutoff frequency about every 3 seconds. It sounds pretty good, but what I really want is for the frequency to slide around from one value to the next instead of jumping is it does in the above score. The "smooth" option for the makefilter() p-field command can do this:
rtsetparams(44100, 2)
load("WAVETABLE")
load("MOOGVCF")
load("PANECHO")

bus_config("WAVETABLE", "aux 0 out")
bus_config("MOOGVCF", "aux 0 in", "aux 1 out")
bus_config("PANECHO", "aux 1 in", "out 0-1")


wave = maketable("wave", 1000, "square")
amp = 10000
dev = 0.004
dur = 21

theLFO = {}
maxLFOfreq = 2.0
maxLFOdev = 0.001
for (i = 0; i < 4; i += 1) {
	theLFO[i] = makeLFO("sine", irand(maxLFOfreq), irand(maxLFOdev))
}

for (i = 0; i < 4; i += 1) {
	WAVETABLE(0, dur, amp, 6.00+irand(0, dev)+theLFO[i], 0, wave)
}

randcutoff = makerandom("even", 0.3, 500, 2000)
smoothcutoff = makefilter(randcutoff, "smooth", 100, 1000)
MOOGVCF(0, 0, dur, 1, 0, 0, 0, smoothcutoff, 0.7)

pamp = 1
pampenv = maketable("line", 1000, 0,0, 10,1, 90,1, 100,0)

PANECHO(0, 0, dur, pamp*pampenv, 0.2, 0.34, 0.5, 5.0, 0)


Ok, just for fun, I did one final iteration of building this sound. I liked what I have, but I decided to tweak several things. First of all, I sharpened the filter resonance to get more of the filter "effect" involved in the sound. Secondly, I wanted a more robust sound, so I added a set of WAVETABLEs generating audio one octave below the originals. I used them at 1/2 ampitude, though, because otherwise they would overwhelm the sound. I increased the PANECHO feedback to get a longer 'echo tail' from the filter sweeping (and I had to a adjust the ampitude slightly to keep it from clipping). I put an an srand() call to seed the random-number algorithms from the system clock, so it would be different every time I ran the score. You can also put in a seed value for srand() if you want to explore specific sequences. I also made the whole thing longer to get more of the droneyness I wanted... wheee!!!!
rtsetparams(44100, 2)
load("WAVETABLE")
load("MOOGVCF")
load("PANECHO")

srand()

bus_config("WAVETABLE", "aux 0 out")
bus_config("MOOGVCF", "aux 0 in", "aux 1 out")
bus_config("PANECHO", "aux 1 in", "out 0-1")


wave = maketable("wave", 1000, "square")
amp = 8000
dev = 0.004
dur = 49

theLFO = {}
maxLFOfreq = 2.0
maxLFOdev = 0.001
for (i = 0; i < 4; i += 1) {
	theLFO[i] = makeLFO("sine", irand(maxLFOfreq), irand(maxLFOdev))
}

for (i = 0; i < 4; i += 1) {
	WAVETABLE(0, dur, amp, 6.00+irand(0, dev)+theLFO[i], 0, wave)
	WAVETABLE(0, dur, amp/2, 5.00+irand(0, dev)+theLFO[i], 0, wave)
}

randcutoff = makerandom("even", 0.3, 500, 2000)
smoothcutoff = makefilter(randcutoff, "smooth", 100, 1000)
MOOGVCF(0, 0, dur, 1, 0, 0, 0, smoothcutoff, 0.8)

pamp = 1
pampenv = maketable("line", 1000, 0,0, 10,1, 90,1, 100,0)

PANECHO(0, 0, dur, pamp*pampenv, 0.2, 0.34, 0.7, 5.0, 0)


The fun part now is to go back and start messing with the various paramaters. :-)