This is a helper class to represent a musical chord, it was written for use by the robot jazz band example but could be used anywhere it is helpful.
The class is defined as a subclass of Midi.Pattern and can be used wherever a Pattern would be, e.g. for scheduling on a plugin or MIDI output.
class Chord extends Midi.Pattern {
Define MIDI pitch values for note strings:
pitches = {
A = 57, ["A#"] = 58, ["Bb"] = 58,
B = 59,
C = 60, ["C#"] = 61, ["Db"] = 61,
D = 62, ["D#"] = 63, ["Eb"] = 63,
E = 64,
F = 65, ["F#"] = 66, ["Gb"] = 66,
G = 67, ["G#"] = 68, ["Ab"] = 68
}
Define intervals for chord types, add entries here to support more chord types. Intervals are listed in half steps from the root, the root itself is not listed.
intervals = {
[""] = [4, 7],
maj = [4, 7],
m = [3, 7],
min = [3, 7],
["7"] = [4, 7, 10],
m7 = [3, 7, 10],
maj7 = [4, 7, 11],
M7 = [4, 7, 11],
["6"] = [4, 7, 9],
m6 = [3, 7, 9],
aug = [4, 8],
dim = [3, 6],
["9"] = [4, 7, 10, 14],
m9 = [3, 7, 10, 14],
maj9 = [4, 7, 11, 14],
M9 = [4, 7, 11, 14],
sus2 = [2, 7],
sus4 = [5, 7],
["5"] = [7]
}
The constructor for the chord must be called with three arguments:
constructor(name, duration) {
Call the base class constructor to create a valid Midi.Pattern:
base.constructor()
Use a regular expression to parse the chord name into root + type:
local ex = regexp(@"^([A-G][#,b]?)([a-z,2-9]*)")
local res = ex.capture(name)
if(!res) {
throw "chord not recognized: " + name
}
local root = name.slice(res[1].begin, res[1].end)
local type = name.slice(res[2].begin, res[2].end)
Find the pitch of the root note from the table above.
local pitch = pitches[root]
Create a Midi.Note for the root note with the given duration, then pass it to the add method of the Midi.Pattern base class:
add(Midi.Note(pitch, 127, duration), 1)
For each of the intervals in the chord create a Midi.Note with the correct pitch and again pass it to the add method of the Midi.Pattern base class:
foreach(interval in intervals[type]) {
add(Midi.Note(pitch + interval, 127, duration), 1)
}
End of the constructor.
}
Define a method for inverting the chord, takes as argument the inversion level, e.g. 1 for first inversion, 2 for second inversion, up to the size of the chord.
function invert(level) {
if(level <= 0) {
throw "chord inversion level must be at least 1"
}
if(level >= size()) {
throw "chord is not large enough for inversion level " + index
}
for(local i = 0; i < level; i++) {
note(i).transpose(12)
}
}
Define a method for changing the velocity of all notes in the chord at once:
function velocity(v) {
for(local i = 0; i < size(); i++) {
note(i).velocity(v)
}
}
End of the class.
}
class Chord extends Midi.Pattern {
pitches = {
A = 57, ["A#"] = 58, ["Bb"] = 58,
B = 59,
C = 60, ["C#"] = 61, ["Db"] = 61,
D = 62, ["D#"] = 63, ["Eb"] = 63,
E = 64,
F = 65, ["F#"] = 66, ["Gb"] = 66,
G = 67, ["G#"] = 68, ["Ab"] = 68
}
intervals = {
[""] = [4, 7],
maj = [4, 7],
m = [3, 7],
min = [3, 7],
["7"] = [4, 7, 10],
m7 = [3, 7, 10],
maj7 = [4, 7, 11],
M7 = [4, 7, 11],
["6"] = [4, 7, 9],
m6 = [3, 7, 9],
aug = [4, 8],
dim = [3, 6],
["9"] = [4, 7, 10, 14],
m9 = [3, 7, 10, 14],
maj9 = [4, 7, 11, 14],
M9 = [4, 7, 11, 14],
sus2 = [2, 7],
sus4 = [5, 7],
["5"] = [7]
}
constructor(name, duration) {
base.constructor()
local ex = regexp(@"^([A-G][#,b]?)([a-z,2-9]*)")
local res = ex.capture(name)
if(!res) {
throw "chord not recognized: " + name
}
local root = name.slice(res[1].begin, res[1].end)
local type = name.slice(res[2].begin, res[2].end)
local pitch = pitches[root]
add(Midi.Note(pitch, 127, duration), 1)
foreach(interval in intervals[type]) {
add(Midi.Note(pitch + interval, 127, duration), 1)
}
}
function invert(level) {
if(level <= 0) {
throw "chord inversion level must be at least 1"
}
if(level >= size()) {
throw "chord is not large enough for inversion level " + index
}
for(local i = 0; i < level; i++) {
note(i).transpose(12)
}
}
function velocity(v) {
for(local i = 0; i < size(); i++) {
note(i).velocity(v)
}
}
}
This work is licensed under a
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.