From Fedora Project Wiki
// "Method One"
// 
// Composed for the Fedora Project's "Musicians' Guide."
// Christopher Antila.
// 
// Creative Commons CC-BY-SA 3.0
// http://creativecommons.org/licenses/by-sa/3.0/

(
//    The method is, "Choose something, and take it to the extreme."
// Here I've chosen to take SinOsc to the extreme, so I use it in every
// way that I can imagine.  Of course, there are more and different ways
// to use SinOsc.

//    When looking at this source code (as with any SuperCollider source
// file), keep in mind that there will be other ways to achieve the
// same end result.  I always find it helpful to arrange my code in
// such a way as to make it easily understood.






//    I'm creating a new TempoClock and assigning it to "t_c".  This
// way, when I change the tempo later, it won't be interfering with
// the default TempoClock (which is TempoClock.default).  It must be
// declared here, so that all of the functions in the file can access
// it, and I've put it first because, in each "scope," all var objects
// must be declared before anything else.  Really, I could have written:
//    var t_c;
// here, and only run:
//    t_c = TempoClock.new;
// later.  There is no benefit to that *for this file*, so they're together.
var t_c = TempoClock.new;






//    This is the function that, when executed (by calling
// secondPart.value ), will produce the second part of this simple
// three-part form.  Because it's assigned to a "var", it needs to
// be placed above the first part, which is a SynthDef sent to the
// server.
// 
//    I've written this as a function, rather than as a SynthDef. The
// intention is that it sounds different every time it is heard, which
// is why I use pseudo-randomness in the "func" sub-function (see
// inside).  If this had been written as a SynthDef, then the pitches
// would be calculated pseudo-randomly *once* and every time a new
// Synth was created from it, it would have the same pitches.  Because
// it's a function, however, the pitches are generated pseudo-randomly
// every time the function is run.
// 
//    Since the function is only run once in the execution of this
// program, you may be wondering why it matters.  Every time the
// program is run, the SynthDef would be re-calculated and sent to the
// server again.  In other words, every time the program is run, this
// middle section will sound different whether it's a SynthDef or a
// function.
// 
//    This is true, but one of the principles of good programming is
// to allow for maximum flexibility.  If I later wanted to add another
// playing of "secondPart" to the program, I can do that easily, and
// it would produce different pitches each time.  I might also want to
// take this function and put it into a different program, and writing
// it as a function again allows for greater flexibility.  Besides, if
// I decided that I wanted a SynthDef, it's easy to get one from a
// function (in fact, it's impossible to get one without a function!)
//    SynthDef.new( "SecondPart", secondPart ).send( s );
// But you can't get the function out of a SynthDef.
var secondPart = 
{
   // This creates a new array with the ability to hold ten objects of
   // any sort.  It's going to be used to hold each of the SinOsc
   // objects that are created in this section.  By using an array
   // (in SuperCollider called "ArrayedCollection"), I don't have to
   // worry about code like this:
   //    var so_1;
   //    var so_2;
   //    ...
   //    var so_10;
   // to hold the ten SinOsc's.  
   var sounds = Array.new( 10 );
   
   // This generates a pseudo-random number to use as the pitch or
   // frequency (here meaning the same thing), and then creates two
   // equal SinOsc's on either side of the stereo image.
   var func =
   {
      // a_number.rand means "generate a pseudo-random number between
      // 0 and a_number".  I've added 200 to this because frequencies
      // below 200 aren't particularly useful, in terms of pitch. So
      // the result of this is a number between 200 and 800.
      var freq = 200 + 600.rand;
      
      // This is what the function returns, and it's why you can "play"
      // the function.  When you run func.play, you aren't actually
      // playing func, but rather you're playing what func returns.
      [ SinOsc.ar( freq:freq, mul:0.01),
        SinOsc.ar( freq:freq, mul:0.01) ];
   };

   // This part could certainly be written more elegantly (that is, with
   // less pointless repetition).  When programming, whether for audio
   // or any other reason, repetition is usually regarded as the
   // enemy of good code.  What if I wanted to change the time between
   // the addition of each object, from 5 beats to 6?  What if I wanted
   // to change the number of objects from 10 to 300?  Worse still,
   // what if I made a typing mistake in one line, and copy-and-pasted
   // it to all the others?  I would have to correct them all.  What
   // a pain!
   // 
   // As an exercise, try to make this more efficient.  You'll need
   // to read up on "control structures" (see the SuperCollider help
   // files).  It's possible to work these ten lines into one - in
   // fact, it's recommended!
   t_c.sched( 1, { sounds = sounds.add( func.play ); } );
   t_c.sched( 6, { sounds = sounds.add( func.play ); } );
   t_c.sched( 11, { sounds = sounds.add( func.play ); } );
   t_c.sched( 16, { sounds = sounds.add( func.play ); } );
   t_c.sched( 21, { sounds = sounds.add( func.play ); } );
   t_c.sched( 26, { sounds = sounds.add( func.play ); } );
   t_c.sched( 31, { sounds = sounds.add( func.play ); } );
   t_c.sched( 36, { sounds = sounds.add( func.play ); } );
   t_c.sched( 41, { sounds = sounds.add( func.play ); } );
   t_c.sched( 46, { sounds = sounds.add( func.play ); } );

   // This will stop all the sounds held in "sounds".  The last "nil"
   // is required as a return value for the function, or else the .do
   // function will return "10", which t_c.sched() interprets as meaning
   // "run me again 10 beats after I finish."  This isn't disastrous,
   // as it might be in a more complex program, but it would give a
   // lot of error messages to anybody watching the "SuperCollider
   // output" pane.
   t_c.sched( 51, { 10.do( { arg index; sounds[index].free; } ); nil; } );
   
};






//    This SynthDef represents the first part of the piece.  Note that
// because the concluding brace is followed by ".send(s)", this
// definition gets sent to the server.  Although we have no local var
// that points to this SynthDef, we can use Synth.new( "FirstPart" ),
// because the server knows what "FirstPart" is.
// 
//    I've chosen to write this as a SynthDef because it does not need
// to change when it is played.  It is intended to always sound the same,
// so there is no reason for the server to re-calculate everything
// each time it is to be played.  This is not the case for the second
// part (see above).
// 
//    Above, I claimed it's impossible to get a SynthDef
// without a function.  Although "FirstPart" may look like a SynthDef
// without a function, the function simply isn't named, but it does
// start at the { and end at the }.  So, why not declare this as a
// function, then make a SynthDef below it, like this?
//    SynthDef.new( "FirstPart", firstPart ).send( s );
// Personal preference, really.  This format makes it more clear that
// "FirstPart" is only intended to be run as a Synth, and it also avoids
// the very minor computational slow-down that would have been involved
// with declaring a function, and a SynthDef from that.
SynthDef.new( \FirstPart,
{
   // These set and modulate the frequency emitted by the left and
   // right channels, respectively.
   // 
   // What I've called "drone" is the slowly-moving pitches.
   // 
   // "freq" changes how quickly the frequency changes
   // "mul" changes the range of oscillation
   // "add" is the lowest frequency it will hit
   //
   // The resulting frequency goes between "add" and "add + mul",
   // and it reaches each extreme "freq" times per second
   var frequencyL = SinOsc.kr( freq:10, mul:200, add:400 );
   var frequencyR = SinOsc.kr( freq:1, mul:50, add:150 );
   var frequencyL_drone = SinOsc.kr( freq:0.03, mul:20, add:100 );
   var frequencyR_drone = SinOsc.kr( freq:0.01, mul:20, add:210 );
   
   // This will change the volume of the left audio channel.
   // It was annoying when too loud for too long.
   // 
   // It means that the volume will go between 0.03 and 0.05, reaching
   // the extremes once every two seconds.
   var volumeL = SinOsc.kr( freq:0.5, mul:0.02, add:0.03 );
   
   //    This is where I'm compiling the sound generators themselves.
   // Notice that all the SinOsc's above were SinOsc.kr(), for "kontrol,"
   // and all the ones here are SinOsc.ar(), for "audio."  I use array
   // notation, which is the [ ] brackets, several times.  This allows
   // multiple SinOsc calls with different arguments, and within each
   // call it allows multiple audio streams to be outputted, with
   // different frequencies.  While you might expect there to by
   // multi-channel audio created from this, that's not the case,
   // because everything assigned to "left" or "right" is ultimately
   // played below.  More on that later...
   var left = [ SinOsc.ar( freq:frequencyL, mul:volumeL ), // this is the oscillating part
                SinOsc.ar( freq:[frequencyL_drone,2*frequencyL_drone], mul:0.02 ), // the rest make up the drone; the numbers are close to the 2^x series
                SinOsc.ar( freq:[5*frequencyL_drone,7*frequencyL_drone], mul:0.005 ),
                SinOsc.ar( freq:[13*frequencyL_drone,28*frequencyL_drone], mul:0.001 ) ];
   
   var right = [ SinOsc.ar( freq:frequencyR, mul:0.1 ), // this is the oscillating part
                 SinOsc.ar( freq:[frequencyR_drone,2*frequencyR_drone], mul:0.02 ), // the rest make up the drone
                 SinOsc.ar( freq:4*frequencyR_drone, mul:0.005 ),
                 SinOsc.ar( freq:[64*frequencyR_drone,128*frequencyR_drone], mul:0.01 ) ]; // high frequencies!
   
   //    This stereo mix is what ultimately gets played.  Notice that
   // any multichannel effects created while mixing "left" and "right"
   // are ignored here, and all the sound from each variable is put
   // into one channel: all of the channels from "left" get played in
   // the new left channel, and all of the channels from "right" get
   // played in the new right channel.
   // 
   //    Try commenting the array, and play "left" and "right" by
   // uncommenting the following lines, one at a time.  When you do
   // that, the multichannel mix of the variable is preserved, which
   // may lead to a surprising result (if it doesn't, listen more
   // carefully, and compare to the original).
   // 
   //    For an explanation, go read about busses, and keep in mind
   // that this array doesn't create "left" and "right" channels,
   // but rather plays to busses 0 and 1, which usually wind up being
   // played through the speakers as "left" and "right" channels.
   Out.ar( 0, [left,right] );
    
} ).send( s );






//    This is the equivalent of the "main" function in a C-like language,
// which basically "causes" everything else to happen.  It's important
// to include the ".value;" at the end, or else executing the program
// would result in "a Function" in the output pane, and no sound.
{
   // This will hold the currently-running instance of "FirstPart".
   // I've set it to 0 now as a matter of habit, like initializing an
   // int in C.
   var sound = 0;
   
   // This sets the tempo to one beat per second; in effect, t_c now
   // counts in seconds.
   t_c.tempo_( 1 );
   
   // This schedules events to take place.  By number of beats after start:
   // 1: "FirstPart" starts to play.
   // 60: "FirstPart" is silenced.
   // 60: secondPart is engaged (but starts to play only on beat 61).
   // 112: "FirstPart" starts to play.
   // 142: "FirstPart" is silenced.
   t_c.sched( 1, { sound = Synth.new( \FirstPart ); } );
   t_c.sched( 61, { sound.free; } );
   t_c.sched( 61, { secondPart.value; nil; } ); // this takes 51 beats to complete
   t_c.sched( 113, { sound = Synth.new( \FirstPart ); } );
   t_c.sched( 143, { sound.free; } );
   //    I could have simply "paused" the \FirstPart synth, but then it
   // wouldn't necessarily re-start at the same point in the oscillation
   // as it did when first created.
}.value;

)