Skip to content

Instantly share code, notes, and snippets.

@scztt
Last active May 7, 2025 06:31
Show Gist options
  • Save scztt/33e5ca33cad184078942294869faee70 to your computer and use it in GitHub Desktop.
Save scztt/33e5ca33cad184078942294869faee70 to your computer and use it in GitHub Desktop.
(
SynthDef(\default, {
var env, sig, freq, velocity;
velocity = \velocity.kr(80).linlin(0, 127, 0, 1);
env = Env.adsr(
\attackTime.kr(0.05),
\decayTime.kr(0.2),
\sustainLevel.kr(-2.dbamp),
\releaseTime.kr(0.5),
curve:-2
).kr(gate:\gate.kr(1), doneAction:2);
freq = \freq.kr(440);
freq = freq * [
0,
env.linlin(0, 1, 0.1, 0.3)
].midiratio;
sig = Saw.ar(freq).sum;
sig = BLowPass.ar(
sig,
env.linexp(
0, 1,
velocity.linexp(0, 1, 80, 300),
velocity.linexp(0, 1, 700, 4200),
) * \filt.kr(0.75),
0.8
);
sig = Pan2.ar(sig, \pan.kr(0), \amp.kr(1) * env);
OffsetOut.ar(\out.kr(0), sig)
}).add
)
Event.addEventType(\pattern, {
var pat, event, outerEvent, recursionLevel, instrument, embeddingLevel, freq, rest;
var args, defaults, timingOffset;
~pattern = ~pattern ?? { ~instrument };
if (~pattern.isKindOf(Function)) {
defaults = ~pattern.def.prototypeFrame;
args = ~pattern.def.argNames.collect {
|name, i|
currentEnvironment[name].value ?? { defaults[i] }
};
pat = ~pattern.value(*args);
} {
if (~pattern.isKindOf(Symbol)) {
~pattern = Pdef(~pattern);
};
pat = ~pattern.value;
};
if (pat.isKindOf(Event)) {
Error("Event patterns must be wrapped in a Ref or a function when passed in as an \instrument argument").throw;
};
if(pat.notNil) {
if (pat.isKindOf(PatternProxy)) {
pat = pat.pattern; // optimization. outer pattern takes care for replacement
};
// preserve information from outer pattern, but not delta.
if(~transparency ? true) {
outerEvent = currentEnvironment.copy;
// outerEvent[\instrument] = nil;
outerEvent[\pattern] = nil;
outerEvent[\type] = nil;
outerEvent[\parentType] = nil;
outerEvent[\timingOffset] = nil;
outerEvent[\addToCleanup] = nil;
outerEvent[\removeFromCleanup] = nil;
} {
outerEvent = Event.default;
};
outerEvent.put(\delta, nil); // block delta modification by Ppar
outerEvent.put(\instrument, ~synthDef);
timingOffset = ~timingOffset.value ? 0;
// not sure why we DON'T need to account for positive timingOffset here,
// but if we do it breaks....
if (~gatePattern ? true) {
pat = pat.finDur(~sustain.value - timingOffset.min(0));
};
pat = Pevent(pat, outerEvent);
if (timingOffset < 0) {
pat = pat.asStream;
pat.fastForward(timingOffset.neg, 0, outerEvent);
pat = pat.asEventStreamPlayer(outerEvent);
pat.play(thisThread.clock, quant:0.0);
} {
pat.play(thisThread.clock, outerEvent, 0.0)
}
}
}, Event.parentEvents.default.copy.putAll((legato:1)));
// Couple notes:
// [1] Pdef
// I'll wrap my Pbinds in Pdef, because if you run the code again it will
// just swap out the playing pattern rather than start it again, so you
// can make adjustments on the fly.&&(function, adverb)
// Pdef just defines a pattern with a name, so:
// Pdef(\foo, Pbind())
// defines a Pbind pattern named \foo. You can do:
// Pdef(\foo).play;
// Pdef(\foo).stop;
// And you can combine Pdef(\foo, Pbind(...)).play in one block of code
// [2] default Synth
// I added attached a better default synth in default.scd, its just less
// annoying when testing vs the built-in one. You can run that code to
// add it.
// [2] \type, \pattern
// If you specify the \type key as \pattern, then each event in your Pbind
// will play the Pbind specified in the \pattern key more or less AS IF
// it were a note. I'll give some examples of how this works, but conceptually
// it will be as if you replaced the note with a pattern.
// [3] Duration stuff
// You may know this, but it's important for the examples....
// Event durations use three related keys: \dur, \legato, and \sustain.
//
// \dur (required) is the time until the *next event*
// \sustain (optional) is HOW LONG the current event plays. If it's less
// than \dur, the event fill finish before the next one - if it's
// longer, then they will overlap
// \legato (optional) is how long the event plays IN RELATION to the \dur
// so, (\dur, 4, \legato, 0.5) will play for 2 seconds, and the next
// event comes in 4.
// So, \sustain is defined as \legato * \dur. You ONLY specify one of
// \legato or \sustain, whichever makes more sense for how you want to
// set time on your events.
// [4] Scale stuff
// You also may know but I'll use three of the many note-related keys:
//
// \octave (this ones obvious)
// \degree (steps in a scale)
// \scale (the scale you're using - by default, diatonic major)
// You can list scales and the notes in them with this code snippet:
// Scale.names.do { |name| "Scale.% %".format(name, Scale.all[name].degrees).postln }
// If don't want to be locked to a scale and just want to manage that yourself
// you can use \scale, Scale.diatonic to get all 12 degrees.
// You can also specify \midinote to bypass both \octave and \degree
(
// Simple and slow, with overlaps
Pdef(\test,
Pbind(
// timing
\dur, 4,
\legato, 1.5,
// notes
\scale, Scale.augmented,
\octave, 3,
\degree, Pseq([1, -1, 3, 5], inf),
// timbre
\attackTime, 2, \decayTime, 2, \releaseTime, 1,
\filt, Pwhite(0.2, 0.6)
)
).play
)
(
// Pattern type. This will replace each 4 beat note
// with a pattern that plays repeated 1/4 beat notes
Pdef(\test,
Pbind(
\type, \pattern,
\pattern, {
Pbind(
\dur, 1/4,
\attackTime, 1/8, \decayTime, 1/8, \releaseTime, 0.2
)
},
// timing
\dur, 4,
\legato, 1.5,
// notes
\scale, Scale.augmented,
\octave, 3,
\degree, Pseq([1, -1, 3, 5], inf),
// timbre
\attackTime, 2, \decayTime, 2, \releaseTime, 1,
\filt, Pwhite(0.2, 0.6)
)
).play;
// Note that: the inner pattern inherits the keys from the outer pattern.
// So, if we dont's specify anything, our note is the same just repeated.
// Notice that I'm overriding the \attackTime though!
// \pattern HAS to be inside a {function} for some reasons that may be clear later
)
(
// \type can itself be a pattern, so you can alternate
// between single events and phrases
Pdef(\test,
Pbind(
\type, Pseq([\note, \pattern], inf),
\pattern, {
Pbind(
\dur, 1/4,
\attackTime, 1/8, \decayTime, 1/8, \releaseTime, 0.2
)
},
// timing
\dur, 4,
\legato, 1.5,
// notes
\scale, Scale.augmented,
\octave, 3,
\degree, Pseq([1, -1, 3, 5], inf),
// timbre
\attackTime, 2, \decayTime, 2, \releaseTime, 1,
\filt, Pwhite(0.2, 0.6)
)
).play;
)
(
// Inner patterns can be arbitrarily complex - its just another pattern.
// Note that BEFORE my \filt was coming from the outer pattern, so it
// was the same for all notes. But now I'm overriding it, so it's different
// for each note.
Pdef(\test,
Pbind(
\type, \pattern,
\pattern, {
Pbind(
\dur, Prand([1, 2, 4], inf) * 1/6,
\filt, Pwhite(0.2, 0.6),
\attackTime, 1/8, \decayTime, 1/8, \releaseTime, 0.2
)
},
// timing
\dur, 4,
\legato, 2,
// notes
\scale, Scale.augmented,
\octave, 3,
\degree, Pseq([1, -1, 3, 5], inf),
// timbre
\attackTime, 2, \decayTime, 2, \releaseTime, 1,
\filt, Pwhite(0.2, 0.6)
)
).play;
)
(
// Inner patterns can be arbitrarily complex - its just another pattern.
// Note that BEFORE my \filt was coming from the outer pattern, so it
// was the same for all notes of the inner pattern. But now I'm overriding
// it, so it's different for each note.
// Arguments to the \pattern function come from keys in the
// outer event. This is good to capture something like \degree and build it
// into another pattern. but there are other uses also...
Pdef(\test,
Pbind(
\type, \pattern,
\pattern, {
|degree|
Pbind(
\dur, Prand([1, 2, 4], inf) * 1/6,
\degree, degree + Pshuf([0, 3, 6]).repeat,
\filt, Pwhite(0.1, 0.8),
\attackTime, 0.01, \decayTime, 1/8, \releaseTime, 0.2
)
},
// timing
\dur, 4,
\legato, 2,
// notes
\scale, Scale.hexSus,
\octave, 3,
\degree, Pseq([0, 3, 5, -2], inf),
// timbre
\attackTime, 2, \decayTime, 2, \releaseTime, 1,
\filt, Pwhite(0.2, 0.6)
)
).play;
)
(
// I'm using a \steps key now - this doesn't have any specific meaning,
// I'm JUST using it as a parameter to my inner pattern to change the
// steps in my degree pattern. I can modulate this in the outside pattern.
// Important to know that the OUTER pattern value stays the same for the
// duration of the inner pattern, e.g. if I get \steps, 2, then my inner
// pattern has a fixed steps 2 for the whole time it plays (4*2 beats)
Pdef(\test,
Pbind(
\type, \pattern,
\pattern, {
|degree, steps|
Pbind(
\dur, Prand([1, 2, 4], inf) * 1/6,
\degree, degree + (steps * Pshuf([0, 1, 2]).repeat),
\filt, Pwhite(0.1, 0.8),
\attackTime, 0.01, \decayTime, 1/8, \releaseTime, 0.2
)
},
// timing
\dur, 4,
\legato, 2,
// notes
\scale, Scale.hexSus,
\octave, 3,
\degree, Pseq([0, 3, 5, -2], inf),
\steps, Prand([2, 3, 4], inf),
// timbre
\attackTime, 2, \decayTime, 2, \releaseTime, 1,
\filt, Pwhite(0.2, 0.6)
)
).play;
)
(
// The inner pattern will play for a duration according to \dur, \sustain, \legato.
// So, a version with differing durs. You can change \legato to 1 to hear it without overlaps.
// Note that i also added an additional duration modulation parameter, \durStep
Pdef(\test,
Pbind(
\type, Prand([\note, \pattern, \pattern], inf),
\pattern, {
|degree, steps, durStep|
Pbind(
\dur, durStep * Prand([1, Rest(1)], inf),
\sustain, 1/4,
\timingOffset, Prand([0, 0, 1/64], inf),
\degree, degree + (steps * Pshuf([0, 1, 2]).repeat),
\filt, Pexprand(0.02, 1),
\attackTime, 0.01, \decayTime, 1/16, \releaseTime, 2.5
)
},
// timing
\dur, Prand([4, 2], inf),
\durStep, Pshuf([2/16, 4/16]).repeat,
\legato, 2,
// notes
\scale, Scale.phrygian,
\octave, Prand([3, 4, 5, 5, 5], inf),
\degree, Pseq([0, 3, 5, -2], inf),
\steps, Prand([2, -3, -1, 5], inf),
// timbre
\attackTime, 2, \decayTime, 2, \releaseTime, 1,
\filt, Pwhite(0.2, 0.6)
)
).play;
)
(
// \pattern can also be a pattern of \symbols - if THIS is the case,
// then the pattern that gets played is pulled from a Pdef with the same
// name. So, this is pretty much equivalent to what we had before, but
// expressed differently.
Pdef(\innerPattern, {
|degree, steps, durStep|
Pbind(
\dur, durStep * Prand([1, Rest(1)], inf),
\sustain, 1/4,
\timingOffset, Prand([0, 0, 1/64], inf),
\degree, degree + (steps * Pshuf([0, 1, 2]).repeat),
\filt, Pexprand(0.02, 1),
\attackTime, 0.01, \decayTime, 1/16, \releaseTime, 2.5
)
});
Pdef(\test,
Pbind(
\type, Prand([\note, \pattern, \pattern], inf),
\pattern, \innerPattern,
// timing
\dur, Prand([4, 2], inf),
\durStep, Pshuf([2/16, 4/16]).repeat,
\legato, 2,
// notes
\scale, Scale.phrygian,
\octave, Prand([3, 4, 5, 5, 5], inf),
\degree, Pseq([0, 3, 5, -2], inf),
\steps, Prand([2, -3, -1, 5], inf),
// timbre
\attackTime, 2, \decayTime, 2, \releaseTime, 1,
\filt, Pwhite(0.2, 0.6)
)
).play;
)
(
// The cooler version of this is that you can string together
// sequences of different patterns...
// Original
Pdef(\a, {
|degree, steps, durStep|
Pbind(
\dur, durStep * Prand([1, Rest(1)], inf),
\sustain, 1/4,
\timingOffset, Prand([0, 0, 1/64], inf),
\degree, degree + (steps * Pshuf([0, 1, 2]).repeat),
\filt, Pexprand(0.02, 1),
\attackTime, 0.01, \decayTime, 1/16, \releaseTime, 2.5
)
});
// High notes
Pdef(\b, {
|degree, steps, durStep|
Pbind(
\dur, 3/8,
\sustain, 1,
\timingOffset, Prand([0, 0, 1/64], inf),
\octave, 7,
\degree, degree + (steps * Pshuf([0, 1, 2]).repeat),
\filt, Pexprand(0.02, 1),
\attackTime, 0.01, \decayTime, 1/16, \releaseTime, 2.5
)
});
// Slow chord
Pdef(\c, {
|degree, steps, durStep|
Pbind(
\dur, 4,
\legato, 1,
\degree, degree + (steps * [-2, 0, 1]),
\filt, Pexprand(0.01, 0.4),
\attackTime, 3, \decayTime, 1, \releaseTime, 2.5
)
});
Pdef(\test,
Pbind(
\type, Prand([\note, \pattern, \pattern], inf),
\pattern, Pshuf([\a, \a, \a, \b, \c]).repeat,
// timing
\dur, Prand([4, 2], inf),
\durStep, Pshuf([2/16, 4/16]).repeat,
\legato, 3,
// notes
\scale, Scale.phrygian,
\octave, Prand([3, 4, 5, 5, 5], inf),
\degree, Pseq([0, 3, 5, -2], inf),
\steps, Prand([2, -3, -1, 5], inf),
// timbre
\attackTime, 2, \decayTime, 2, \releaseTime, 1,
\filt, Pwhite(0.2, 0.6)
)
).play;
)
// Final comments:
//
// - There's no reason why you can't have your inner pattern ALSO be a
// \type, \pattern, meaning two levels of recursion. Complexity builds fast tho.
//
// - Pdefs can be re-defined WHILE theyre playing, so there's a cool livecoding
// or exploration workflow where you have your outer pattern triggering different
// Pdef patterns like my last example, and then you just change things about
// each of the individual \a, \b, \c patterns while its playing (and reun the definition code)
// and theyll get updated without stopping playback.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment