Skip to content

Instantly share code, notes, and snippets.

@andrewcsmith
Created November 13, 2011 15:47
Show Gist options
  • Save andrewcsmith/1362240 to your computer and use it in GitHub Desktop.
Save andrewcsmith/1362240 to your computer and use it in GitHub Desktop.
helmholtz-ellis string parsing for supercollider
/*
Basic idea of string parsing comes from Marc Sabat and Wolfgang Von Schweinitz's "Helmholtz-Ellis" set of accidentals. These accidentals take all uninflected ("normal") accidentals like naturals, sharps and flats as 3-limit Pythatgorean alterations. Further alterations include small arrows (for the 5-limit 81/80), Tartini-inspired 7s (for 7-limit 64/63), and quarter-tones for the unidecimal alteration 33/32.
I've found that while SuperCollider is great at creating scales and tunings, it's tough to really sequence a full pattern without cycling through a bunch of different tunings--it still seems conceptually tied to MIDI in that sense. So in order to sequence extended just intonation, it's important to have an easy, legible way of reading these pitches.
I've transported my Lilypond definitions to SuperCollider. "aflatUfDs" (the test String) is a Pythagorean a-flat, up a five-limit 81/80, down a seven-limit 63/64. This creates the interval 14/15, or a perfect fourth above the septimal tri-tone against an uninflected "a".
*/
HETuner {
var <>base, <>octave;
*new { |tuning_base, starting_octave|
^super.new.init(tuning_base, starting_octave);
}
init { |tuning_base, starting_octave|
this.base = tuning_base;
this.octave = starting_octave;
}
parse {|note_string|
var pitch = List.new(0);
var ratio = 1;
// Find the note name
pitch.add(note_string.findRegexp("^[abcdefg]"));
// Find sharps or flats
pitch.add(note_string.findRegexp("sharp|flat"));
// Find microtonal alterations
pitch.add(note_string.findRegexp("[UD][fset]"));
// Find any octave alterations
pitch.add(note_string.findRegexp("[,']"));
// Flatten the list and remove the indexes (just want a bunch of strings)
pitch = pitch.flatten.flatten;
pitch.do({|item, index|
if(item.class == Integer,
{pitch.removeAt(index)})});
// Find the intitial pitch, relative to A = 1/1
pitch.do({|item, index|
switch (item,
"a", {ratio = 1;},
"b", {ratio = 1.125;},
"c", {ratio = 1.1851851851852;},
"d", {ratio = 1.3333333333333;},
"e", {ratio = 1.5;},
"f", {ratio = 1.5802469135802;},
"g", {ratio = 1.7777777777778;})
});
pitch.do({|item, index|
switch (item,
",", {ratio = ratio * 0.5;},
"'", {ratio = ratio * 2.0;}
)
});
// Calculate all accidentals (pythagorean & microtonal)
pitch.do({|item, index|
switch (item,
"flat", {ratio = ratio * 0.93644261874343;},
"sharp", {ratio = ratio * 1.06787109;},
"Df", {ratio = ratio * 0.98765432098765;},
"Uf", {ratio = ratio * 1.0125;},
"Ds", {ratio = ratio * 0.984375;},
"Us", {ratio = ratio * 1.015873015873;},
"De", {ratio = ratio * 0.96969696969697;},
"Ue", {ratio = ratio * 1.03125;},
"Dt", {ratio = ratio * 0.96296296296296;},
"Ut", {ratio = ratio * 1.0384615384615;} )
});
// Return the final altered pitch
^(ratio*base*(2**octave));
}
parse_list { |note_list|
var seq = List.new(0);
note_list.do({|item, index|
seq.add(this.parse(item));
});
^seq;
}
}
@andrewcsmith
Copy link
Author

FYI, my score looks something like this (\guitar is sort of my all-purpose instrument):

~violin = HETuner.new(440, 0);
~viola = HETuner.new(440, -1);
~cello = HETuner.new(440, -2);

~durs = Pseq([Pseq([0.2], 5), Pseq([2.0/7.0], 7), Pseq([0.125], 2), Pseq([0.2], 5), Pseq([0.25], 3), Pseq([1/3], 3)], 1);

(
Pbind(*[
    instrument:     \guitar,
    attack:         0.5,
    freq:           Pseq(~violin.parse_list(["e", "fUe", "d", "cDs", "b", "fsharp", "fsharpDf", "e", "eUs", "fUe", "a'", "bDf'", "b'", "cUf'", "e", "b", "csharpUs", "a", "b", "fsharpUs,", "fsharpDf,", "e,", "dDs,", "e,", "fsharp,"]), 1),
    dur:                ~durs]).play;

Pbind(*[
    instrument:     \guitar,
    attack:         0.1,
    freq:           Pseq(~violin.parse_list(["a", "gUf,", "fUf,", "fsharpDf,", "gDs,", "a", "bDf", "cDs", "d", "cUe", "fUe", "g", "e", "a", "gsharpDf,", "gDs,", "fsharpUs,", "fsharpDf,", "fUfDs,", "a", "bDf", "cDs", "cDsDs", "b", "aUf"]), 1),
    out:                1,
    dur:                ~durs]).play;

Pbind(*[
    instrument:     \guitar,
    attack:         0.1,
    freq:           Pseq(~viola.parse_list(["b", "e", "bUs", "bDf", "a", "gDs,", "a", "b", "bflatUf", "gUe,", "fUe,", "c", "dUf", "e", "b'", "a'", "e", "d", "eUf", "d", "g,", "fDs,", "fsharpDfDs,", "aDs", "eUf"]), 1),
    out:                1,
    dur:                ~durs]).play;

Pbind(*[
    instrument:     \guitar,
    attack:         0.1,
    freq:           Pseq(~cello.parse_list(["g,", "fUe,", "g,", "cUe", "d", "a", "g,", "gDs,", "fUf,", "e,", "d,", "a", "gUf,", "cUf", "dDs", "e", "fsharpUs", "cDs", "b", "a", "aUs", "bflatUfDs", "e", "dDs", "c"]), 1),
    out:                0,
    dur:                ~durs]).play;
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment