This is presented more as an example of how not to write music in SuperCollider - it was within the first week or 2 of me trying things out.
The piece is a drone piece based on an instrument that plays a set of overtones of a base frequency, and slowly bends those frequencies by a microtonal increment over a period of several minutes - creating cascading overtones at higher frequencies as the sound waves cross in space and cancel each other out.
The piece starts with a base drone over 20hz and later fades in complementary drones at (20*21/16)hz and (20*4/3)hz - a septimal major third and perfect fourth.
The piece ends after 30 minutes - when the pitch bending of first drone cycle returns to a unison.
This pattern of slow pitch bending in sine clusters was something I experimented for several years. I still write a piece in this style every now and then for fun. I've learned a lot about sound design since then so it's always good to go back and apply those learnings. Orbital Slingshot - composed in late 2011 is the most recent piece I've released in this style.
Commentary follows the source code.
( SynthDef("sine-phase-additive", { arg baseFreq = 32, baseFreqLFORate = 1/3600, baseFreqLFOMult = 0.01, baseFreqLFOPhase = 0 , baseFreqLFOAdd = 2, pan = 0, amp = 0.03, partials = #[1,2,4,8,16,32,3,6,9,12,18,24,25,27,36,40,48,54,56,60,64]; var base; base = baseFreq * SinOsc.kr(baseFreqLFORate, baseFreqLFOPhase, baseFreqLFOMult, baseFreqLFOAdd); Out.ar(0,(Mix.ar(Pan2.ar(FSinOsc.ar(base*partials,0,amp),pan)))); }).load(s); ) s=Server.local; s.boot; s.sendMsg("/s_new", "sine-phase-additive", 1005); s.sendMsg("/n_set", 1005, "baseFreq",20*(4/3)); s.sendMsg("/n_set", 1005, "pan", 0); s.sendMsg("/n_set", 1005, "amp", 0.013); s.sendMsg("/n_set", 1005, "baseFreqLFOPhase", 0); s.sendMsg("/n_set", 1005, "baseFreqLFOMult", 0.0125); s.sendMsg("/n_set", 1005, "baseFreqLFORate",1/300); s.sendMsg("/n_set", 1005, "pan", 0.3); s.sendMsg("/s_new", "sine-phase-additive", 1001); s.sendMsg("/n_set", 1001, "baseFreq",20*(21/16)); s.sendMsg("/n_set", 1001, "pan", 0); s.sendMsg("/n_set", 1001, "amp", 0.012); s.sendMsg("/n_set", 1001, "baseFreqLFOPhase", pi); s.sendMsg("/n_set", 1001, "baseFreqLFOMult", 0.0325); s.sendMsg("/n_set", 1001, "baseFreqLFORate",1/120); s.sendMsg("/n_set", 1001, "pan", -0.3); ( s.sendMsg("/s_new", "sine-phase-additive", 1000); s.sendMsg("/n_set", 1000, "baseFreq", 20); s.sendMsg("/n_set", 1000, "pan", 0); s.sendMsg("/n_set", 1000, "amp", 0.030); s.sendMsg("/n_set", 1000, "baseFreqLFOPhase",0); s.sendMsg("/n_set", 1000, "baseFreqLFOMult", 0.00325); s.sendMsg("/n_set", 1000, "baseFreqLFORate",1/1200); s.sendMsg("/n_set", 1000, "pan", 1.0); //static drone s.sendMsg("/s_new", "sine-phase-additive", 1003); s.sendMsg("/n_set", 1003, "baseFreq", 20); s.sendMsg("/n_set", 1003, "amp", 0.040); s.sendMsg("/n_set", 1003, "baseFreqLFOMult", 0.0); s.sendMsg("/n_set", 1003, "baseFreqLFOPhase",0); s.sendMsg("/n_set", 1003, "pan", 0.0); //base hourly cycle drone s.sendMsg("/s_new", "sine-phase-additive", 1002); s.sendMsg("/n_set", 1002, "baseFreq", 20); s.sendMsg("/n_set", 1002, "amp", 0.030); s.sendMsg("/n_set", 1002, "baseFreqLFOMult", 0.00125); s.sendMsg("/n_set", 1002, "baseFreqLFOPhase",0); s.sendMsg("/n_set", 1002, "baseFreqLFORate", 1/1800); s.sendMsg("/n_set", 1002, "pan", -1.0); SystemClock.sched(480.0, { s.sendMsg("/s_new", "sine-phase-additive", 1001); s.sendMsg("/n_set", 1001, "baseFreq",20*(21/16)); s.sendMsg("/n_set", 1001, "pan", 0); s.sendMsg("/n_set", 1001, "amp", 0.012); s.sendMsg("/n_set", 1001, "baseFreqLFOPhase", pi); s.sendMsg("/n_set", 1001, "baseFreqLFOMult", 0.0325); s.sendMsg("/n_set", 1001, "baseFreqLFORate",1/120); s.sendMsg("/n_set", 1001, "pan", -0.3); }); SystemClock.sched(1200.0, { s.sendMsg("/s_new", "sine-phase-additive", 1005); s.sendMsg("/n_set", 1005, "baseFreq",20*(4/3)); s.sendMsg("/n_set", 1005, "pan", 0); s.sendMsg("/n_set", 1005, "amp", 0.013); s.sendMsg("/n_set", 1005, "baseFreqLFOPhase", 0); s.sendMsg("/n_set", 1005, "baseFreqLFOMult", 0.0125); s.sendMsg("/n_set", 1005, "baseFreqLFORate",1/300); s.sendMsg("/n_set", 1005, "pan", 0.3); }); SystemClock.sched(1800.0, { s.sendMsg("/n_free",1000); s.sendMsg("/n_free",1001); s.sendMsg("/n_free",1002); s.sendMsg("/n_free",1003); s.sendMsg("/n_free",1005); } )
At this point I wasn't using either the Synth or Routine abstractions. I was creating a synth and giving it attributes by sending osc messages directly to the server, and manually allocating a node ID for each synth. My timing routine was to create events on the system clock at the outset and then delay them before being played - so the command to end the piece (by freeing the synth nodes, not sending them any kind of envelope) after 1800 seconds is already set when the piece starts.
Among things I hadn't learned about synth design yet - envelopes, gates and amplitude balancing. Amplitude balancing with additive synthesis can be a little tricky. I'm starting with a base frequency (20hz in this case) and sending it an array of partials = #[1,2,4,8,16,32,3,6,9,12,18,24,25,27,36,40,48,54,56,60,64] ) which is all well and good (note that there are no multiples of 5 until you get into the highest octave). 2 things to note about amplitude for your partials - 1) sound waves at the same amplitude carry more energy at higher frequencies (more wavefronts are hitting your ears over the same span of time) so they sound louder. Also, when you're creating an array of integers, there will always be more integers in the higher range of the array. To use the standard harmonic series as an example - in the 1st octave, there are only overtones at 1 and 2 times the fundamental frequency. 3 octaves up you have 8 overtones at 8,9,10,11,12,13,14,15,16. The higher you go, the more tones are likely to fit into your tuning scheme, even if you eliminate overtones that have factors higher than some number. For this reason, you should compensate for your amplitude using something like amp = (1/(f**k)) where f is your frequencies and k is a constant - usually from 1 (pink noise) to 2 (brown noise).