Dave
Dave

Reputation: 8091

How to implement an instrument/score pattern in supercollider?

I've been through a few of the tutorials, but none of them seem to get at what, in my opinion, is a sensible architecture:

  1. There are one or more Instrument instances,
  2. There is a Score which defines a set of Note objects,
  3. A Player class (maybe function) that routes the Note instances from the score to the instruments so that music is produced.

What I see in this pattern, but haven't seen in the examples I've read so far, is (a) the total separation between the score and the instruments and (b) explicit definition (in the form of a class and/or API) of the Note objects that tell the instruments what to do.

Are their built in utilities that support this type of operating pattern?

Is this an un-smallalkey way of thinking about the problem?

Upvotes: 1

Views: 1349

Answers (2)

MarcinP
MarcinP

Reputation: 1

I would also add that there is a set of extensions (a "Quark") called Ctk, that wraps the SynthDef (into CtkSynthDef), the concept of a note (into CtkNote) and the score (into CtkScore) facilitating work in both real time and non real time. I feel that the examples provided with its helpfiles are (mostly) following the architecture suggested by the OP. To install it, run Quarks.install("Ctk") in SuperCollider.

Upvotes: 0

ezra buchla
ezra buchla

Reputation: 364

I'm not sure exactly what you want, given that you've looked at the examples. The odd bit is the "total separation" requirement; usually a score needs to make some assumptions about what parameters are relevant to what instruments - although there are enough introspective methods in SynthDef that a program could make educated guesses.

But the basic schematic is pretty standard: SynthDef defines instruments, Collection and its subclasses store data, Routine and other classes can interpret data structures in scheduled time to make music.

At the bottom I'm pasting some boilerplate code for a very simple c-like approach to such a structure, using SynthDef, Routine, and Array. Which instrument to use is arbitrarily chosen at note generation time, and the "score" is instrument-agnostic.

However, the idiomatic approach in SC is to use Patterns and Events, and the Pbind class in particular. Personally I find these a little restrictive and verbose, but they'll certainly do what you ask. Check out the "Streams-Patterns-Events" series of helpfiles.

And various people have written third-party extensions like Instr and Voicer to accommodate their own flavors of the score-instrument model. Check out the Quarks listing or consider rolling your own?

s = Server.local.boot;
s.waitForBoot{ Routine {
/// in a "real" patch, i'd make these local variables,
/// but in testing its convenient to use environment variables.
// var inst, tclock, score, playr, switchr;

// the current instrument
~inst = \ding;
// a fast TempoClock
~tclock = TempoClock.new(8);

// two instruments that take the same arguments
SynthDef.new(\ding, {
    arg dur=0.2, hz=880, out=0, level=0.25, pan=0.0;
    var snd;
    var amp = EnvGen.ar(Env.perc, doneAction:2, timeScale:dur);
    snd = SinOsc.ar(hz) * amp * level;
    Out.ar(out, Pan2.ar(snd, pan));
}).send(s);

SynthDef.new(\tick, {
    arg dur=0.1, hz=880, out=0, level=0.25, pan=0.0;
    var snd;
    var amp = EnvGen.ar(Env.perc, doneAction:2, timeScale:dur);
    snd = LPF.ar(WhiteNoise.ar, hz) * amp * level;
    Out.ar(out, Pan2.ar(snd, pan));
}).send(s);

s.sync;

// the "score" is just a nested array of argument values
// there are also many kinds of associative collections in SC if you prefer
~score = [
    // each entry:
    // midi note offset, note duration in seconds, wait time in beats
    [0, 0.4, 2],
    [0, 0.4, 1],
    [7, 0.2, 1],
    [0, 0.2, 1],
    [7, 0.15, 1],
    [10, 0.5, 2],
    [7, 0.1, 1],
    [2, 0.3, 1]

];

// a routine that plays the score, not knowing which instrument is the target
~playr = Routine { var note, hz; inf.do({ arg i;
    // get the next note
    note = ~score.wrapAt(i);
    // interpret scale degree as MIDI note plus offset
    hz = (note[0] + 60).midicps;
    // play the note
    Synth.new(~inst, [\hz, hz, \dur, note[1] ], s);
    // wait
    note[2].wait;
}); }.play(~tclock);


// a routine that randomly switches instruments
~switchr = Routine { var note, hz; inf.do({ arg i;
    if(0.2.coin, {
        if(~inst == \ding, {
            ~inst = \tick;
        }, {
            ~inst = \ding;
        });
        ~inst.postln;
    });
    // wait
    1.wait;
}); }.play(~tclock);

}.play; };

Upvotes: 2

Related Questions