@eboats
Just an example: It provides note names, rhythm notation, and chord/scale helpers while staying within SC’s pattern system.
(
~noteToPC = (\c:0,\d:2,\e:4,\f:5,\g:7,\a:9,\b:11,\C:0,\D:2,\E:4,\F:5,\G:7,\A:9,\B:11);
~n = { |noteSymbol|
var s = noteSymbol.asString, acc = 0, i = 1, note = s[0].asSymbol;
~noteToPC[note] ?? { Error("Invalid note: %".format(note)).throw };
while({ i < s.size and: { s[i].isAlpha or: { "#+sbfqh".includes(s[i].asString) } } }, {
[\c,\C,\d,\D,\e,\E,\f,\F,\g,\G,\a,\A,\b,\B].includes(s[i].asSymbol).if({i=s.size},{
case
{s[i]==$#||{s[i]==$s}}{acc=acc+1}
{s[i]==$b||{s[i]==$f}}{acc=acc-1}
{s[i]==$q}{acc=acc+0.5} // Quarter sharp
{s[i]==$h}{acc=acc-0.5}; // Half flat
i=i+1
})
});
i=1; while({i<s.size and:{s[i].isAlpha or:{"#+sbfqh".includes(s[i].asString)}}},{i=i+1});
~noteToPC[note] + acc + ((s[i..].asInteger + 1) * 12)
};
~nt = ~n;
~notes = { |xs| xs.collect(~n.(_)) };
~dur = (\w:4,\h:2,\q:1,\e:0.5,\s:0.25,\t:0.125,\wd:6,\hd:3,\qd:1.5,\ed:0.75,\sd:0.375,\qt:2/3,\et:1/3,\st:1/6,\hq:3,\qe:1.25,\eq:0.75);
~rhythmDict = ('1n':4,'2n':2,'4n':1,'8n':0.5,'16n':0.25,'32n':0.125,'2nd':3,'4nd':1.5,'8nd':0.75,'16nd':0.375,'4nt':2/3,'8nt':1/3,'16nt':1/6);
~rhythm = { |notation| ~rhythmDict[notation.asSymbol] };
~r = ~rhythm;
~rhythms = { |xs| xs.collect { |x| x.isNumber.if({ x }, { ~rhythm.(x) }) } };
~chords = (
\major:[0,4,7],\minor:[0,3,7],\dim:[0,3,6],\aug:[0,4,8],
\maj7:[0,4,7,11],\min7:[0,3,7,10],\dom7:[0,4,7,10],
\dim7:[0,3,6,9],\hdim7:[0,3,6,10],
\maj9:[0,4,7,11,14],\min9:[0,3,7,10,14],
\sus2:[0,2,7],\sus4:[0,5,7],\add9:[0,4,7,14],
\6:[0,4,7,9],\m6:[0,3,7,9],
// Quartertone chords
\neutral:[0,3.5,7], // Neutral third
\zalzal:[0,3.5,6.5] //
);
~chord = { |root, type=\major|
~chords[type] + root.isNumber.if(root, ~n.(root))
};
~maqams = (
\rast:[0,2,3.5,5,7,9,10.5], // C D Eq F G A Bh
\bayati:[0,1.5,3,5,7,8,10], // D Eh F G A Bb C
\saba:[0,1.5,3,4,6,8,10], // D Eh F Gb A Bb C
\sikah:[0,1.5,3.5,5.5,7,9.5,10.5], // Eh F Gq Aq Bh Cq D
\hijaz:[0,1,4,5,7,8.5,10] // D Eb F# G A Bh C
);
~maqam = { |root, type=\rast, octaves=1|
var r = root.isNumber.if(root, ~n.(root));
octaves.collect{|o| ~maqams[type] + r + (o*12)}.flat
};
~scales = (\major:[0,2,4,5,7,9,11],\minor:[0,2,3,5,7,8,10],\harmonic:[0,2,3,5,7,8,11],\melodic:[0,2,3,5,7,9,11],\dorian:[0,2,3,5,7,9,10],\phrygian:[0,1,3,5,7,8,10],\lydian:[0,2,4,6,7,9,11],\mixolydian:[0,2,4,5,7,9,10],\locrian:[0,1,3,5,6,8,10],\pentatonic:[0,2,4,7,9],\blues:[0,3,5,6,7,10],\chromatic:(0..11),\wholeTone:[0,2,4,6,8,10],\quartertone:(0,0.5..11.5));
~scale = { |root, type=\major, octaves=1|
var r = root.isNumber.if(root, ~n.(root));
octaves.collect{|o| ~scales[type] + r + (o*12)}.flat
};
~mtof = { |m| m.midicps };
~ftom = { |f| f.cpsmidi };
~noteInfo = { |note|
var m = ~n.(note);
postf("% = MIDI %, % Hz\n", note, m, m.midicps.round(0.01));
};
~melody = { |notes, rhythms, repeats = 1|
Pbind(
\midinote, Pseq(~notes.(notes), repeats),
\dur, Pseq(~rhythms.(rhythms), repeats)
)
};
~progression = { |chordList, durations|
var midiChords = chordList.collect { |item|
if(item.isArray) {
~chord.(item[0], item[1])
} {
if(item.isNumber) { [item] } { [~n.(item)] }
}
};
Pbind(
\midinote, Pseq(midiChords, inf),
\dur, Pseq(~rhythms.(durations), inf)
)
};
~iv_make = { |st, number=nil, quality=nil, name=nil, cents=nil, ratio=nil|
(type:\interval, semitones:st.asFloat, number:number, quality:quality, name:name, cents:cents, ratio:ratio)
};
~iv_base = [0, 2, 4, 5, 7, 9, 11];
~iv_isPerfectClass = { |n| [1, 4, 5].includes(((n - 1) % 7) + 1) };
~strip = { |str|
var s = str.asString, i = 0, j = s.size - 1;
var isWS = { |ch| ch.isSpace or: { ch == $\t or: { ch == $\n or: { ch == $\r }}} };
while({ i <= j and: { isWS.(s[i]) } }, { i = i + 1 });
while({ j >= i and: { isWS.(s[j]) } }, { j = j - 1 });
(i > j).if({ "" }, { s.copyRange(i, j) })
};
~iv_parse = { |token|
var s = ~strip.(token);
var i = 0, prefix = "", numStr, n, k, oct, base, delta, st;
var originalPrefix = "";
while({ i < s.size and: { s[i].isAlpha } }, {
originalPrefix = originalPrefix ++ s[i];
i = i + 1;
});
if(originalPrefix.size == 1) {
prefix = case
{ originalPrefix == "M" } { "mj" }
{ originalPrefix == "P" } { "p" }
{ originalPrefix == "A" } { "a" }
{ originalPrefix == "D" } { "d" }
{ originalPrefix == "m" } { "m" }
{ true } { originalPrefix.toLower };
} {
prefix = originalPrefix.toLower;
prefix = prefix.replace("major", "mj").replace("maj", "mj");
prefix = prefix.replace("minor", "m").replace("min", "m");
prefix = prefix.replace("perfect", "p").replace("perf", "p");
prefix = prefix.replace("augmented", "a").replace("aug", "a");
prefix = prefix.replace("diminished", "d").replace("dim", "d");
};
numStr = s.copyRange(i, s.size - 1);
if(numStr.isEmpty) {
Error("Interval needs a number: %".format(s)).throw
};
n = numStr.asInteger;
if(n < 1) {
Error("Interval number must be positive: %".format(s)).throw
};
k = ((n - 1) % 7) + 1;
oct = ((n - 1) div: 7);
base = ~iv_base[k - 1] + (12 * oct);
delta = ~iv_qualityDelta.(prefix, ~iv_isPerfectClass.(n));
st = base + delta;
~iv_make.(st, n, prefix, token)
};
~iv_qualityDelta = { |qual, perfectClass|
var q = qual;
var a = q.findAll("a").size;
var d = q.findAll("d").size;
var res = 0;
if(perfectClass) {
if(q == "p") { res = 0 };
if(a > 0) { res = a };
if(d > 0) { res = -1 * d };
} {
if(q == "mj") { res = 0 };
if(q == "m") { res = -1 };
if(a > 0) { res = a };
if(d > 0) { res = -1 - d };
};
res
};
~iv_fromSemitones = { |st| ~iv_make.(st) };
~iv_fromRatio = { |num, den = 1|
var r = num.asFloat / den.asFloat;
var cents = 1200 * (r.log / 2.log);
var st = cents / 100.0;
~iv_make.(st, nil, nil, nil, cents, (num: num, den: den))
};
~iv_add = { |a, b| ~iv_make.(a.semitones + b.semitones) };
~iv_sub = { |a, b| ~iv_make.(a.semitones - b.semitones) };
~iv_neg = { |a| ~iv_make.(-1 * a.semitones) };
~iv_invert = { |a|
var st = a.semitones % 12;
if(st < 0) { st = st + 12 };
st = (st == 0).if({ 0 }, { 12 - st });
~iv_make.(st)
};
~iv_apply = { |iv, note|
var st = iv.semitones;
var m = note.isNumber.if({ note }, { ~n.(note) });
m + st
};
~iv_applyAll = { |iv, xs|
xs.collect { |x|
x.isArray.if({ ~iv_applyAll.(iv, x) }, { ~iv_apply.(iv, x) })
}
};
~iv_between = { |a, b|
var ma = a.isNumber.if({ a }, { ~n.(a) });
var mb = b.isNumber.if({ b }, { ~n.(b) });
~iv_fromSemitones.(mb - ma)
};
)
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Examples
////////////////////////////////////////////////////////////////////////////////////////////////////////////
(
Pbind(
\midinote, Pseq(~notes.(['c4','cq4','d4','eh4','e4','f4','e4','eh4','d4','cq4','c4']), 1),
\dur, 0.25
).play;
)
(
Pbind(
\midinote, Pseq(~maqam.('c4', \rast, 2), 1),
\dur, Pseq(~rhythms.(['8n','8n','8n','8n','4n','8n','8n','2n']), inf)
).play;
)
(
Pbind(
\midinote, Pseq(~maqam.('d4', \hijaz, 1) ++ ~maqam.('d5', \hijaz, 1).reverse, 1),
\dur, Prand([0.125, 0.25], inf),
\legato, 0.9
).play;
)
(
Pbind(
\midinote, Pseq([
~chord.('c3', \neutral),
~chord.('f3', \neutral),
~chord.('g3', \major),
~chord.('c3', \neutral)
], 2),
\dur, 1,
\strum, 0.05
).play;
)
(
Pbind(
\midinote, Pseq([60, 60.5, 62, 63.5, 65, 63.5, 62, 60.5, 60], inf),
\dur, Pseq(~rhythms.(['8n','8n','8n','8n','4n','8n','8n','8n','2n']), inf),
\amp, 0.2,
\legato, 0.95
).play;
)
(
Ppar([
Pbind(
\midinote, Pseq(~maqam.('ch6', \rast, 1), inf),
\dur, 0.5,
\amp, 0.15
),
Pbind(
\midinote, Prand(~scale.('c5', \pentatonic), inf),
\dur, Pseq([0.125, 0.125, 0.25], inf),
\amp, 0.1
)
]).play;
)