├── MusicalNotation.quark ├── Tests ├── Note.sc ├── Percussion.sc ├── Chord.sc └── Progression.sc ├── LICENSE ├── HelpSource └── Classes │ ├── Note.schelp │ ├── Percussion.schelp │ ├── Progression.schelp │ └── Chord.schelp ├── Classes ├── Note.sc ├── extMethods.sc ├── Progression.sc ├── Percussion.sc └── Chord.sc └── README.md /MusicalNotation.quark: -------------------------------------------------------------------------------- 1 | ( 2 | \name: "MusicalNotation", 3 | \summary: "A set of classes related to Musical Notation (Percussion, Note, Chord, Progression).", 4 | \author: "Mauro Lizaur", 5 | \version: "0.2.4", 6 | \organization: "buenos aires live code", 7 | ) 8 | -------------------------------------------------------------------------------- /Tests/Note.sc: -------------------------------------------------------------------------------- 1 | TestNote : UnitTest { 2 | 3 | test_Semitones { 4 | 5 | this.assertEquals(Note.at(\c), 0, "Note.at(\c) -> 0"); 6 | this.assertEquals(Note.g, 7, "Note.g -> 7"); 7 | this.assertEquals([\c, \a, \f, \e].asNote, [ 0, 9, 5, 4 ], "[\c, \a, \f, \e] -> [ 0, 9, 5, 4 ]"); 8 | this.assertEquals( 9 | Pseq([\c, \a, \f, \e], inf).asStream.nextN(10), 10 | [ 0, 9, 5, 4, 0, 9, 5, 4, 0, 9 ], 11 | "Pseq[\c, \a, \f, \e] -> [ 0, 9, 5, 4, ... ]" 12 | ); 13 | 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /Tests/Percussion.sc: -------------------------------------------------------------------------------- 1 | TestPercussion : UnitTest { 2 | 3 | test_MIDINote { 4 | 5 | this.assertEquals(Percussion.at(\bd), 36, "Percussion.at(\bd) -> 36"); 6 | this.assertEquals(Percussion.bd, 36, "Percussion.bd -> 36"); 7 | this.assertEquals([\bd,\sn].asPercussion, [36, 38], "[\bd,\sn] -> [36, 38]"); 8 | this.assertEquals( 9 | Pseq([\bd,\sn,\ch,\oh], inf).asStream.nextN(10), 10 | [ 36, 38, 42, 46, 36, 38, 42, 46, 36, 38 ], 11 | "Pseq[\bd,\sn,\ch,\oh] -> [ 36, 38, 42, 46, ...]" 12 | ); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /Tests/Chord.sc: -------------------------------------------------------------------------------- 1 | TestChord : UnitTest { 2 | 3 | test_Chords { 4 | 5 | this.assertEquals(Chord.at(\maj), Chord([ 0, 4, 7 ], "Major"), "Major Chord"); 6 | this.assertEquals(Chord.min, Chord([ 0, 3, 7 ], "Minor"), "Minor Chord"); 7 | this.assertEquals( 8 | [\maj, \min, \m7].asChord, 9 | [ Chord([ 0, 4, 7 ], "Major"), Chord([ 0, 3, 7 ], "Minor"), Chord([ 0, 3, 7, 10 ], "Minor 7th") ], 10 | "Major, Minor, Minor 7th Chords" 11 | ); 12 | this.assertEquals( 13 | Pseq([\maj, \min, \m7], inf).asStream.nextN(10), 14 | [ [ 0, 4, 7 ], [ 0, 3, 7 ], [ 0, 3, 7, 10 ], [ 0, 4, 7 ], [ 0, 3, 7 ], [ 0, 3, 7, 10 ], [ 0, 4, 7 ], [ 0, 3, 7 ], [ 0, 3, 7, 10 ], [ 0, 4, 7 ] ], 15 | "Pseq[\maj, \min, \m7] -> [ [ 0, 4, 7 ], [ 0, 3, 7 ], [ 0, 3, 7, 10 ], ... ]" 16 | ); 17 | 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mauro Lizaur 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Tests/Progression.sc: -------------------------------------------------------------------------------- 1 | TestProgression : UnitTest { 2 | 3 | test_Progs { 4 | 5 | this.assertEquals( 6 | Progression.at(\sad), 7 | Progression([ 0, 3, 4, 4 ], "Sad"), 8 | "'Sad' chord prog" 9 | ); 10 | 11 | this.assertEquals( 12 | Progression.sad, 13 | Progression([ 0, 3, 4, 4 ], "Sad"), 14 | "'Sad' chord prog (method)" 15 | ); 16 | 17 | this.assertEquals( 18 | Progression.new(degrees: [2, 3, 1, 5], name: "Weird"), 19 | Progression([ 2, 3, 1, 5 ], "Weird"), 20 | "Creating a new Progression" 21 | ); 22 | 23 | this.assertEquals( 24 | Progression.sad.roman, 25 | "i-iv-v-v", 26 | "Roman numbers for sad prog" 27 | ); 28 | 29 | this.assertEquals( 30 | Scale.major.chordProgression(\sad, \c), 31 | [ [ 0, 4, 7 ], [ 5, 9, 12 ], [ 7, 11, 14 ], [ 7, 11, 14 ] ], 32 | "Chords for Sad Prog in a Major Scale" 33 | ); 34 | 35 | this.assertEquals(Scale.major.chordProgression(\sad, \g), 36 | [ [ 7, 11, 14 ], [ 12, 16, 19 ], [ 14, 18, 21 ], [ 14, 18, 21 ] ], 37 | "Chords for Sad Prog in a Major Scale in a G key" 38 | ); 39 | 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /HelpSource/Classes/Note.schelp: -------------------------------------------------------------------------------- 1 | TITLE:: Note 2 | summary:: Notes in ABC notation. 3 | related:: Classes/Symbol, Classes/Percussion, Classes/Chord, Classes/Progression, Classes/Scale 4 | categories:: Math, Tuning 5 | 6 | 7 | DESCRIPTION:: 8 | 9 | Takes an "note name" (link::Classes/Symbol::) and returns the semitone in a chromatic scale (0..12). 10 | 11 | 12 | CLASSMETHODS:: 13 | 14 | METHOD::all 15 | The Note repository. 16 | 17 | METHOD:: at 18 | ARGUMENT:: input 19 | Access from the Note repository. 20 | 21 | METHOD:: directory 22 | Lists all link::Classes/Note:: names and Semitones. 23 | 24 | METHOD:: names 25 | Lists all link::Classes/Note:: names. 26 | 27 | PRIVATE:: doesNotUnderstand, new, all 28 | 29 | INSTANCEMETHODS:: 30 | 31 | METHOD:: semitone 32 | Returns link::Classes/Note:: semitone. 33 | code:: 34 | a = Note.new(\a); 35 | a.semitone; 36 | -> 9 37 | :: 38 | 39 | PRIVATE:: symbol 40 | 41 | EXAMPLES:: 42 | 43 | code:: 44 | Note.at(\c); 45 | -> 0 46 | 47 | Note.g; 48 | -> 7 49 | 50 | Note.directory; 51 | -> a: Semitone 9 52 | af: Semitone 8 53 | as: Semitone 10 54 | b: Semitone 11 55 | bf: Semitone 10 56 | c: Semitone 0 57 | cs: Semitone 1 58 | d: Semitone 2 59 | ... 60 | 61 | Note.names; 62 | -> [ a, af, as, b, bf, c, cs, d, df, ds, e, ef, f, fs, g, gf, gs ] 63 | 64 | [\c, \a, \f, \e].asNote; 65 | -> [ 0, 9, 5, 4 ] 66 | 67 | Pseq([\c, \a, \f, \e], inf).asStream.nextN(10); 68 | -> [ 0, 9, 5, 4, 0, 9, 5, 4, 0, 9 ] 69 | :: 70 | -------------------------------------------------------------------------------- /Classes/Note.sc: -------------------------------------------------------------------------------- 1 | /* 2 | Note class 3 | 4 | (c) 2018 by Mauro 5 | http://www.cyberpunk.net.ar/ 6 | 7 | A class that returns Chromatic semitones. 8 | 9 | */ 10 | 11 | Note { 12 | var 0, 27 | \cs -> 1, \df -> 1, 28 | \d -> 2, 29 | \ds -> 3, \ef -> 3, 30 | \e -> 4, 31 | \f -> 5, 32 | \fs -> 6, \gf -> 6, 33 | \g -> 7, 34 | \gs -> 8, \af -> 8, 35 | \a -> 9, 36 | \as -> 10, \bf -> 10, 37 | \b -> 11 38 | ]; 39 | 40 | all = all.freezeAsParent; 41 | } 42 | 43 | *names { ^all.parent.keys.asArray.sort; } 44 | 45 | *directory { 46 | ^this.names.collect{ |k| "%: Semitone %".format(k, all.at(k)) }.join("\n") 47 | } 48 | 49 | *at { |input = \rest| 50 | if(all.at(input).notNil) { ^all.at(input); }; 51 | if(input.isRest && [\r,\rest,\].indexOf(input).notNil) { ^Rest(); }; 52 | ^nil; 53 | } 54 | 55 | semitone { ^all.at(symbol); } 56 | 57 | *doesNotUnderstand { |selector, args| 58 | var note; 59 | if (selector.class == Symbol) { 60 | note = this.at(selector, args).deepCopy; 61 | ^note ?? { super.doesNotUnderstand(selector, args) }; 62 | } 63 | ^super.doesNotUnderstand(selector, args); 64 | } 65 | 66 | printOn { |stream| this.storeOn(stream) } 67 | storeOn { |stream| stream << this.class.name << "(\\" << symbol << ")"; } 68 | storeArgs { ^[symbol] } 69 | 70 | 71 | 72 | } -------------------------------------------------------------------------------- /HelpSource/Classes/Percussion.schelp: -------------------------------------------------------------------------------- 1 | TITLE:: Percussion 2 | summary:: A class that returns associated MIDI notes according to General MIDI Level 2. 3 | related:: Classes/Symbol, Classes/Note, Classes/Chord, Classes/Progression, Classes/Scale 4 | categories:: External Control>MIDI 5 | 6 | DESCRIPTION:: 7 | 8 | Takes an "instrument name" (link::Classes/Symbol::) and returns the related MIDI note. 9 | 10 | CLASSMETHODS:: 11 | 12 | METHOD::all 13 | The Percussion repository. 14 | 15 | METHOD:: at 16 | ARGUMENT:: input 17 | Access from the Percussion repository. 18 | 19 | METHOD:: directory 20 | Lists all link::Classes/Percussion:: names and MIDI Notes. 21 | 22 | METHOD:: names 23 | Lists all link::Classes/Percussion:: names. 24 | 25 | PRIVATE:: doesNotUnderstand, new 26 | 27 | INSTANCEMETHODS:: 28 | 29 | METHOD:: midinote 30 | Returns link::Classes/Percussion:: MIDI note. 31 | code:: 32 | k = Percussion.new(\bd); 33 | k.midinote; 34 | -> 36 35 | :: 36 | 37 | PRIVATE:: symbol 38 | 39 | EXAMPLES:: 40 | 41 | code:: 42 | Percussion.at(\bd); 43 | -> 36 44 | 45 | Percussion.bd; 46 | -> 36 47 | 48 | Percussion.directory; 49 | -> abd: MIDI Note 35 50 | ag: MIDI Note 68 51 | bd: MIDI Note 36 52 | be: MIDI Note 53 53 | bt: MIDI Note 84 54 | ca: MIDI Note 69 55 | cas: MIDI Note 85 56 | ch: MIDI Note 42 57 | ... 58 | 59 | Percussion.names; 60 | -> [ abd, ag, bd, be, bt, ca, cas, ch, cl, cow, cp, cr, cr2, cy, esn, gui, hag, hb, hc, hft, hh, hmt, hq, ht, hti, hwb, jb, lag, lb, lc, lft, lgui, lmt, lt, lti, lwb, lwi, ma, mb, mc, mcl, mhc, ms, mt, oc, oh, ohc, os, ot, ph, ri, ri2, rm, scr, scy, sgui, sha, sl, sn, spl, sps, sqc, st, sti, swi, ta, vib, whi ] 61 | 62 | [\bd, \sn, \ch, \oh].asPercussion; 63 | -> [ 36, 38, 42, 46 ] 64 | 65 | Pseq([\bd, \sn, \ch, \oh], inf).asStream.nextN(10); 66 | -> [ 36, 38, 42, 46, 36, 38, 42, 46, 36, 38 ] 67 | :: -------------------------------------------------------------------------------- /HelpSource/Classes/Progression.schelp: -------------------------------------------------------------------------------- 1 | TITLE:: Progression 2 | summary:: Represents a Chord Progression. 3 | related:: Classes/Symbol, Classes/Percussion, Classes/Note, Classes/Chord, Classes/Scale 4 | categories:: Math, Tuning 5 | 6 | 7 | DESCRIPTION:: 8 | 9 | Takes an "progression name" (link::Classes/Symbol::) and returns the chords in it. 10 | 11 | 12 | CLASSMETHODS:: 13 | 14 | METHOD::all 15 | The Progression repository, to which new progressions can be added. 16 | 17 | METHOD:: at 18 | ARGUMENT:: input 19 | Takes a symbol and returns a progression. 20 | 21 | METHOD:: directory 22 | Lists all link::Classes/Progression:: names and Chords. 23 | 24 | METHOD:: names 25 | Lists all link::Classes/Progression:: names. 26 | 27 | METHOD::choose 28 | Creates a random Progression from the library, constrained by size. 29 | 30 | METHOD::chooseFromSelected 31 | Creates a random Progression from the library, constrained by a function. 32 | 33 | METHOD::new 34 | Creates a Progression from scratch. strong::notes:: should be an array of Integers. 35 | 36 | PRIVATE:: doesNotUnderstand, new, all 37 | 38 | INSTANCEMETHODS:: 39 | 40 | METHOD::size 41 | Returns the length of the Progression. 42 | 43 | METHOD::degrees 44 | Returns an Array of Integers. 45 | 46 | PRIVATE:: symbol, name, storedKey, hash, == 47 | 48 | 49 | EXAMPLES:: 50 | 51 | code:: 52 | Progression.at(\sad); 53 | -> Progression([ 0, 3, 4, 4 ], "Sad") 54 | 55 | Progression.sad; 56 | -> Progression([ 0, 3, 4, 4 ], "Sad") 57 | 58 | Progression.new(degrees: [2, 3, 1, 5], name: "Weird"); 59 | -> Progression([ 2, 3, 1, 5 ], "Weird") 60 | 61 | Progression.all.put(\weird, Progression.new(degrees: [2, 3, 1, 5], name: "Weird")); 62 | -> IdentityDictionary[ (weird -> Progression.weird) ] 63 | 64 | Progression.weird; 65 | -> Progression([ 2, 3, 1, 5 ], "Weird") 66 | 67 | Progression.sad.roman; 68 | -> i-iv-v-v 69 | 70 | Progression.directory; 71 | -> Ballad: ballad i-i-iv-vi 72 | Ballad Alt: balladAlt i-iv-vi-v 73 | Blues: blues i-iv-i-v-i 74 | Blues Alt: bluesAlt i-i-i-i-iv-iv-i-i-v-iv-i-i 75 | Creepy: creepy i-vi-iv-v 76 | Creepy Alt: creepyAlt i-vi-ii-v 77 | 50s: fifties i-vi-iv-v 78 | General: gral i-iv-v-i 79 | ... 80 | 81 | Progression.names; 82 | -> [ ballad, balladAlt, blues, bluesAlt, creepy, creepyAlt, eleven, elevenb, elevenc, elevend, fifties, gral, gralAlt, nrg, pop, rebel, rock, rockAlt, roll, sad, unresolved ] 83 | 84 | Scale.major.chordProgression(\sad, \c); 85 | -> [ [ 0, 4, 7 ], [ 5, 9, 12 ], [ 7, 11, 14 ], [ 7, 11, 14 ] ] 86 | 87 | Scale.major.chordProgression(\weird, \g); 88 | -> [ [ 11, 14, 18 ], [ 12, 16, 19 ], [ 9, 12, 16 ], [ 16, 19, 23 ] ] 89 | :: 90 | -------------------------------------------------------------------------------- /HelpSource/Classes/Chord.schelp: -------------------------------------------------------------------------------- 1 | TITLE:: Chord 2 | summary:: Represents a Chord. 3 | related:: Classes/Symbol, Classes/Percussion, Classes/Note, Classes/Progression, Classes/Scale 4 | categories:: Math, Tuning 5 | 6 | 7 | DESCRIPTION:: 8 | 9 | Takes an "chord name" (link::Classes/Symbol::) and returns the semitones in a chromatic scale (0..12). 10 | 11 | 12 | 13 | CLASSMETHODS:: 14 | 15 | METHOD::all 16 | The Chord repository, to which new chords can be added. 17 | 18 | METHOD:: at 19 | ARGUMENT:: input 20 | Takes a symbol and returns a chord. 21 | 22 | METHOD:: directory 23 | Lists all link::Classes/Note:: names and Semitones. 24 | 25 | METHOD:: names 26 | Lists all link::Classes/Note:: names. 27 | 28 | METHOD::choose 29 | Creates a random chord from the library, constrained by size. 30 | 31 | METHOD::chooseFromSelected 32 | Creates a random chord from the library, constrained by a function. 33 | 34 | METHOD::new 35 | Creates a Chord from scratch. strong::notes:: should be an array of Integers. 36 | 37 | PRIVATE:: doesNotUnderstand, new, all 38 | 39 | INSTANCEMETHODS:: 40 | 41 | METHOD::size 42 | Returns the length of the chord. 43 | 44 | METHOD::notes 45 | Returns an Array of Integers. 46 | 47 | PRIVATE:: symbol, name, storedKey, hash, == 48 | 49 | 50 | EXAMPLES:: 51 | 52 | code:: 53 | Chord.at(\maj); 54 | -> Chord([ 0, 4, 7 ], "Major") 55 | 56 | Chord.maj; 57 | -> Chord([ 0, 4, 7 ], "Major") 58 | 59 | Chord.choose(4); 60 | -> Chord([ 0, 4, 6, 10 ], "7 Flat 5") 61 | 62 | Chord.new(notes: [0, 1, 2], name: "Test chord"); 63 | -> Chord([ 0, 1, 2 ], "Test chord") 64 | 65 | Chord.all.put(\test, Chord.new(notes: [0, 1, 2], name: "Test chord")); 66 | -> IdentityDictionary[ (test -> Chord.test) ] 67 | 68 | Chord.test; 69 | -> Chord([ 0, 1, 2 ], "Test chord") 70 | 71 | Chord.directory; 72 | -> Add Eleven: add11 has 5 Notes 73 | Add Thirteen: add13 has 5 Notes 74 | Add 9: add9 has 4 Notes 75 | Augmented: aug has 3 Notes 76 | Diminished: dim has 3 Notes 77 | Diminished 7th: dim7 has 4 Notes 78 | Dominant 7th: dom7 has 4 Notes 79 | Eleven: eleven has 6 Notes 80 | ... 81 | 82 | Chord.names; 83 | -> [ sixSus4, m9, sevenSharp9, maj11, m7, sevenFlat5, dom7, m6add9, m7dim, m7Flat5, one, dim, sus4, six, m11, maj, majAdd13, m7Flat9, mMaj7, mMaj9, sevenSharp5, dim7, mMaj11, add11, maj9Sus4, m6, eleven, mAdd13, mMajAdd11, sevenFlat9, aug, m13, sevenSus4, maj7Sus4, maj7, sevenSharp5Flat9, sus2, thirteen, mAdd11, m7Sharp5, add13, nineSharp11, madd9, min, majAdd11, mMaj13, nine, maj9, nineFlat13, add9, sixadd9, five, maj13, mMajAdd13, nineSus4 ] 84 | 85 | Pseq([\maj, \min], inf).asStream.nextN(10); 86 | -> [ [ 0, 4, 7 ], [ 0, 3, 7 ], [ 0, 4, 7 ], [ 0, 3, 7 ], [ 0, 4, 7 ], [ 0, 3, 7 ], [ 0, 4, 7 ], [ 0, 3, 7 ], [ 0, 4, 7 ], [ 0, 3, 7 ] ] 87 | :: 88 | -------------------------------------------------------------------------------- /Classes/extMethods.sc: -------------------------------------------------------------------------------- 1 | /* 2 | external Methods for Percussion, Note, Chord, Progression classes. 3 | 4 | (c) 2018 by Mauro 5 | http://www.cyberpunk.net.ar/ 6 | */ 7 | 8 | 9 | // Note.sc 10 | + SimpleNumber { 11 | asNote { ^Note.all.parent.findKeyForValue(this).asSymbol; } 12 | asPercussion { ^Percussion.all.parent.findKeyForValue(this).asSymbol; } 13 | asNoteMIDI { |octave=5| ^this + (12*octave); } 14 | asMusicalNotation { ^this; } 15 | asFreq { |octave=5| ^this.midi(octave).midicps; } 16 | asCircleOf5th { ^(this + ((0..11) * 7 % 12)); } 17 | } 18 | 19 | // Percussion, Note, Chord, Progression 20 | + Symbol { 21 | 22 | asMusicalNotation { 23 | var tests = [ 24 | (type: \note, result: this.asNote), 25 | (type: \chord, result: this.asChord), 26 | (type: \percussion, result: this.asPercussion) 27 | ] 28 | .reject{ |t| t.result.isNil } 29 | .pop 30 | ; 31 | ^(tests.notNil).if { 32 | (tests.type == \chord).if { tests.result.notes } { tests.result } 33 | } { this }; 34 | } 35 | 36 | asNote { ^Note.at(this); } 37 | asChord { ^Chord.at(this); } 38 | asPercussion { ^Percussion.at(this); } 39 | 40 | embedInStream { ^this.asMusicalNotation.yield; } 41 | } 42 | 43 | + String { 44 | asNote { ^this.asSymbol.asNote; } 45 | asChord { ^this.asSymbol.asChord; } 46 | asPercussion { ^this.asSymbol.asPercussion; } 47 | asMusicalNotation { ^this.asSymbol.asMusicalNotation; } 48 | } 49 | 50 | + Rest { 51 | asMusicalNotation { ^this; } 52 | } 53 | 54 | + Collection { 55 | asNote { ^this.collect(Note.at(_)); } 56 | asChord { ^this.collect(Chord.at(_)); } 57 | asPercussion { ^this.collect(Percussion.at(_)); } 58 | asMusicalNotation { 59 | ^this.collect{ |item| 60 | (item.isRest.not).if 61 | { (item.isKindOf(Meta_Pattern).not).if 62 | { item.asMusicalNotation } 63 | { item } 64 | } 65 | { item } 66 | }; 67 | } 68 | 69 | embedInStream { ^this.collect(_.asMusicalNotation).yield; } 70 | } 71 | 72 | 73 | + Scale { 74 | chordProgression { |name = \sad, key = \c| 75 | 76 | var chords = []; 77 | var root = Note.at(key); 78 | var progression = Progression.at(name).degrees; 79 | var major = [\maj, \min, \min, \maj, \maj, \min, \dim]; 80 | var minor = [\min, \dim, \maj, \min, \min, \maj, \maj]; 81 | var harMinor = [\min, \dim, \aug, \min, \maj, \maj, \dim]; 82 | var melMinor = [\min, \min, \aug, \maj, \maj, \dim, \dim]; 83 | var dorian = [\min, \min, \maj, \maj, \min, \dim, \maj]; 84 | var phrygian = [\min, \maj, \maj, \min, \dim, \maj, \min]; 85 | var lydian = [\maj, \maj, \min, \dim, \maj, \min, \min]; 86 | var mixolydian = [\maj, \min, \dim, \maj, \min, \min, \maj]; 87 | var locrian = [\dim, \maj, \min, \min, \maj, \maj, \min]; 88 | 89 | if (this.name == "Major") { chords = major; }; 90 | if (this.name == "Natural Minor") { chords = minor; }; 91 | if (this.name == "Harmonic Minor") { chords = harMinor; }; 92 | if (this.name == "Melodic Minor") { chords = melMinor; }; 93 | if (this.name == "Dorian") { chords = dorian; }; 94 | if (this.name == "Phrygian") { chords = phrygian; }; 95 | if (this.name == "Lydian") { chords = lydian; }; 96 | if (this.name == "Mixolydian") { chords = mixolydian; }; 97 | if (this.name == "Locrian") { chords = locrian; }; 98 | 99 | chords = (this.degrees + chords.collect{|n| Chord.at(n).notes;}); 100 | ^Array.fill(progression.size, { |i| chords[progression[i]] }) + root; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Classes/Progression.sc: -------------------------------------------------------------------------------- 1 | /* 2 | Progression class 3 | 4 | (c) 2018 by Mauro 5 | http://www.cyberpunk.net.ar/ 6 | 7 | Similar to Scales / Tuning classes but for Progressions. 8 | 9 | */ 10 | 11 | Progression { 12 | var name; 13 | classvar Progression.new(degrees: [0,3,4,4], name: "Sad"), 27 | \ballad -> Progression.new(degrees: [0,0,3,5], name: "Ballad"), 28 | \balladAlt -> Progression.new(degrees: [0,3,5,4], name: "Ballad Alt"), 29 | \rock -> Progression.new(degrees: [0,3,4], name: "Rock"), 30 | \rockAlt -> Progression.new(degrees: [0,3,0,4], name: "Rock Alt"), 31 | \roll -> Progression.new(degrees: [0,3,4,3], name: "Roll"), 32 | \rebel -> Progression.new(degrees: [3,4,3], name: "Rebel"), 33 | \nrg -> Progression.new(degrees: [0,2,3,5], name: "Energetic"), 34 | \fifties -> Progression.new(degrees: [0,5,3,4], name: "50s"), 35 | \creepy -> Progression.new(degrees: [0,5,3,4], name: "Creepy"), 36 | \creepyAlt -> Progression.new(degrees: [0,5,1,4], name: "Creepy Alt"), 37 | \gral -> Progression.new(degrees: [0,3,4,0], name: "General"), 38 | \gralAlt -> Progression.new(degrees: [0,3,1,4], name: "General Alt"), 39 | \pop -> Progression.new(degrees: [0,4,5,3], name: "Pop"), 40 | \unresolved -> Progression.new(degrees: [3,0,3,4], name: "Unresolved"), 41 | \blues -> Progression.new(degrees: [0,3,0,4,0], name: "Blues"), 42 | \bluesAlt -> Progression.new(degrees: [0,0,0,0,3,3,0,0,4,3,0,0,], name: "Blues Alt"), 43 | // meh... 44 | \eleven -> Progression.new(degrees: [1,0,1,4], name: "Eleven"), 45 | \elevenb -> Progression.new(degrees: [1,4,1,0], name: "Eleven B"), 46 | \elevenc -> Progression.new(degrees: [1,4,0], name: "Eleven C"), 47 | \elevend -> Progression.new(degrees: [5,1,4,0], name: "Eleven D"), 48 | ]; 49 | 50 | all = all.freezeAsParent; 51 | } 52 | 53 | == { arg that; 54 | ^this.compareObject(that, #[\degrees]) 55 | } 56 | 57 | hash { 58 | ^this.instVarHash(#[\degrees]) 59 | } 60 | 61 | *at { |key| ^all.at(key); } 62 | 63 | *names { ^(all.keys.asArray ++ all.parent.keys).sort; } 64 | 65 | *directory { 66 | ^this.names.collect{ |k| "%: % %".format(all.at(k).name, k, all.at(k).roman) }.join("\n") 67 | } 68 | 69 | size { 70 | ^this.degrees.size; 71 | } 72 | 73 | roman { 74 | ^this.degrees.collect(_.asRomanNumber).join("-"); 75 | } 76 | 77 | *choose { |size = 16| 78 | var loop = this.chooseFromSelected { |x| (x.size == size) }; 79 | if(loop.isNil) { 80 | "No known progression with size %".format(size).warn 81 | }; 82 | ^loop 83 | } 84 | 85 | *chooseFromSelected { |selectFunc| 86 | selectFunc = selectFunc ? { true }; 87 | ^(all.copy.putAll(all.parent)).select(selectFunc) 88 | .choose.deepCopy; 89 | } 90 | 91 | *doesNotUnderstand { |selector, args| 92 | var progression = this.at(selector, args).deepCopy; 93 | ^progression ?? { super.doesNotUnderstand(selector, args) }; 94 | } 95 | 96 | storedKey { 97 | // can be optimised later 98 | ^all.findKeyForValue(this) 99 | } 100 | 101 | printOn { |stream| this.storeOn(stream) } 102 | storeOn { |stream| 103 | var storedKey = this.storedKey; 104 | stream << this.class.name; 105 | if(storedKey.notNil) { stream << "." << storedKey } { this.storeParamsOn(stream) } 106 | } 107 | storeArgs { ^[degrees, name] } 108 | 109 | } 110 | 111 | + SimpleNumber { 112 | asRomanNumber { 113 | var roman = [\i, \ii, \iii, \iv, \v, \vi, \vii, \viii, \ix, \x]; 114 | ^roman.at(this.clip(0,9)); 115 | } 116 | } -------------------------------------------------------------------------------- /Classes/Percussion.sc: -------------------------------------------------------------------------------- 1 | /* 2 | Percussion class 3 | .asPercussion method 4 | 5 | (c) 2018 by Mauro 6 | http://www.cyberpunk.net.ar/ 7 | 8 | A class that returns associated MIDI notes according to General MIDI Level 2. 9 | Reference: 10 | * https://www.midi.org/specifications/item/gm-level-1-sound-set 11 | * https://en.wikipedia.org/wiki/General_MIDI_Level_2#Drum_sounds 12 | 13 | */ 14 | 15 | Percussion { 16 | var 27, // high q 31 | \sl -> 28, // slap 32 | \scr -> 29, // scratch 33 | \sps -> 29, // scratch push 34 | \spl -> 30, // scratch pull 35 | \st -> 31, // sticks 36 | \sqc -> 32, // sq click 37 | \mcl -> 33, // metronome click 38 | \mb -> 34, // metronome bell 39 | \abd -> 35, // acoustic bass drum 40 | \bd -> 36, // bass drum 41 | \sti -> 37, // side stick 42 | \rm -> 37, // rimshot 43 | \sn -> 38, // snare 44 | \cp -> 39, // clap 45 | \esn -> 40, // electric snare 46 | \lft -> 41, // low floor tom 47 | \ch -> 42, // closed hi hat 48 | \hft -> 43, // high foor tom 49 | \hh -> 44, // pedal hi hat 50 | \ph -> 44, // pedal hi hat 51 | \lt -> 45, // low tom 52 | \oh -> 46, // open hi-hat 53 | \lmt -> 47, // low mid tom 54 | \hmt -> 48, // high mid tom 55 | \cr -> 49, // crash cymbal 56 | \ht -> 50, // high tom 57 | \ri -> 51, // ride cymbal 58 | \cy -> 52, // chinese cymbal 59 | \be -> 53, // ride bell 60 | \ta -> 54, // tambourine 61 | \scy -> 55, // splash cymbal 62 | \cow -> 56, // cowbell 63 | \cr2 -> 57, // crash cymbal 2 64 | \vib -> 58, // vibraslap 65 | \ri2 -> 59, // ride cymbal 2 66 | \hb -> 60, // hi bongo 67 | \lb -> 61, // low bongo 68 | \hc -> 62, // hi conga 69 | \mhc -> 62, // mute hi conga 70 | \ohc -> 63, // open hi conga 71 | \lc -> 64, // low conga 72 | \hti -> 65, // hi timbale 73 | \lti -> 66, // low timbale 74 | \hag -> 67, // high agogo 75 | \lag -> 68, // low agogo 76 | \ag -> 68, // agogo 77 | \ca -> 69, // cabasa 78 | \ma -> 70, // maracas 79 | \whi -> 71, // whistle 80 | \swi -> 71, // short whistle 81 | \lwi -> 72, // long whistle 82 | \gui -> 73, // guiro 83 | \sgui -> 73, // short guiro 84 | \lgui -> 74, // long guiro 85 | \cl -> 75, // claves 86 | \hwb -> 76, // hi wood block 87 | \lwb -> 77, // low wood block 88 | \mc -> 78, // mute cuica 89 | \oc -> 79, // open cuica 90 | \mt -> 80, // mute triangle 91 | \ot -> 81, // open triangle 92 | \sha -> 82, //shaker 93 | \jb -> 83, // jingle bell 94 | \bt -> 84, // belltree 95 | \cas -> 85, // castanets 96 | \ms -> 86, // mute surdo 97 | \os -> 87, // open surdo 98 | ]; 99 | 100 | all = all.freezeAsParent; 101 | } 102 | 103 | *names { ^all.parent.keys.asArray.sort; } 104 | 105 | *directory { 106 | ^this.names.collect{ |k| "%: MIDI Note %".format(k, all.at(k)) }.join("\n") 107 | } 108 | 109 | *at { |input = \rest| 110 | if(all.at(input).notNil) { ^all.at(input); }; 111 | if(input.isRest && [\r,\rest,\].indexOf(input).notNil) { ^Rest(); }; 112 | ^nil; 113 | } 114 | 115 | midinote { ^all.at(symbol); } 116 | 117 | *doesNotUnderstand { |selector, args| 118 | var percussion; 119 | if (selector.class == Symbol) { 120 | percussion = this.at(selector, args).deepCopy; 121 | ^percussion ?? { super.doesNotUnderstand(selector, args) }; 122 | } 123 | ^super.doesNotUnderstand(selector, args); 124 | } 125 | 126 | printOn { |stream| this.storeOn(stream) } 127 | storeOn { |stream| stream << this.class.name << "(\\" << symbol << ")"; } 128 | storeArgs { ^[symbol] } 129 | 130 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MusicalNotation 2 | 3 | A set of classes related to Musical Notation (Percussion, Note, Chord, Progression). 4 | 5 | ## Installation 6 | 7 | a/ Clone this repository to your computer and copy the directory to the `Extension` directory (The path can be found by evaluating `Platform.userExtensionDir` or `Platform.systemExtensionDir`). 8 | 9 | b/ Execute this -> `Quarks.install("https://github.com/lvm/MusicalNotation");` 10 | 11 | ## Usage 12 | 13 | ### Note 14 | 15 | ``` 16 | Note.at(\c); 17 | -> 0 18 | 19 | Note.g; 20 | -> 7 21 | 22 | Note.directory; 23 | -> a: Semitone 9 24 | af: Semitone 8 25 | as: Semitone 10 26 | b: Semitone 11 27 | bf: Semitone 10 28 | c: Semitone 0 29 | cs: Semitone 1 30 | d: Semitone 2 31 | ... 32 | 33 | Note.names; 34 | -> [ a, af, as, b, bf, c, cs, d, df, ds, e, ef, f, fs, g, gf, gs ] 35 | 36 | [\c, \a, \f, \e].asNote; 37 | -> [ 0, 9, 5, 4 ] 38 | 39 | Pseq([\c, \a, \f, \e], inf).asStream.nextN(10); 40 | -> [ 0, 9, 5, 4, 0, 9, 5, 4, 0, 9 ] 41 | ``` 42 | 43 | ### Chord 44 | 45 | ``` 46 | Chord.at(\maj) 47 | -> Chord([ 0, 4, 7 ], "Major") 48 | 49 | Chord.maj 50 | -> Chord([ 0, 4, 7 ], "Major") 51 | 52 | Chord.choose(4); 53 | -> Chord([ 0, 4, 6, 10 ], "7 Flat 5") 54 | 55 | Chord.new(notes: [0, 1, 2], name: "Test chord") 56 | -> Chord([ 0, 1, 2 ], "Test chord") 57 | 58 | Chord.all.put(\test, Chord.new(notes: [0, 1, 2], name: "Test chord")); 59 | -> IdentityDictionary[ (test -> Chord.test) ] 60 | 61 | Chord.test; 62 | -> Chord([ 0, 1, 2 ], "Test chord") 63 | 64 | Chord.directory; 65 | -> Add Eleven: add11 has 5 Notes 66 | Add Thirteen: add13 has 5 Notes 67 | Add 9: add9 has 4 Notes 68 | Augmented: aug has 3 Notes 69 | Diminished: dim has 3 Notes 70 | Diminished 7th: dim7 has 4 Notes 71 | Dominant 7th: dom7 has 4 Notes 72 | Eleven: eleven has 6 Notes 73 | ... 74 | 75 | Chord.names; 76 | -> [ sixSus4, m9, sevenSharp9, maj11, m7, sevenFlat5, dom7, m6add9, m7dim, m7Flat5, one, dim, sus4, six, m11, maj, majAdd13, m7Flat9, mMaj7, mMaj9, sevenSharp5, dim7, mMaj11, add11, maj9Sus4, m6, eleven, mAdd13, mMajAdd11, sevenFlat9, aug, m13, sevenSus4, maj7Sus4, maj7, sevenSharp5Flat9, sus2, thirteen, mAdd11, m7Sharp5, add13, nineSharp11, madd9, min, majAdd11, mMaj13, nine, maj9, nineFlat13, add9, sixadd9, five, maj13, mMajAdd13, nineSus4 ] 77 | 78 | Pseq([\maj, \min], inf).asStream.nextN(10); 79 | -> [ [ 0, 4, 7 ], [ 0, 3, 7 ], [ 0, 4, 7 ], [ 0, 3, 7 ], [ 0, 4, 7 ], [ 0, 3, 7 ], [ 0, 4, 7 ], [ 0, 3, 7 ], [ 0, 4, 7 ], [ 0, 3, 7 ] ] 80 | ``` 81 | 82 | ### Progression 83 | 84 | ``` 85 | Progression.at(\sad); 86 | -> Progression([ 0, 3, 4, 4 ], "Sad") 87 | 88 | Progression.sad; 89 | -> Progression([ 0, 3, 4, 4 ], "Sad") 90 | 91 | Progression.new(degrees: [2, 3, 1, 5], name: "Weird"); 92 | -> Progression([ 2, 3, 1, 5 ], "Weird") 93 | 94 | Progression.all.put(\weird, Progression.new(degrees: [2, 3, 1, 5], name: "Weird")); 95 | -> IdentityDictionary[ (weird -> Progression.weird) ] 96 | 97 | Progression.weird; 98 | -> Progression([ 2, 3, 1, 5 ], "Weird") 99 | 100 | Progression.sad.roman; 101 | -> i-iv-v-v 102 | 103 | Progression.directory; 104 | -> Ballad: ballad i-i-iv-vi 105 | Ballad Alt: balladAlt i-iv-vi-v 106 | Blues: blues i-iv-i-v-i 107 | Blues Alt: bluesAlt i-i-i-i-iv-iv-i-i-v-iv-i-i 108 | Creepy: creepy i-vi-iv-v 109 | Creepy Alt: creepyAlt i-vi-ii-v 110 | 50s: fifties i-vi-iv-v 111 | General: gral i-iv-v-i 112 | ... 113 | 114 | Progression.names; 115 | -> [ ballad, balladAlt, blues, bluesAlt, creepy, creepyAlt, eleven, elevenb, elevenc, elevend, fifties, gral, gralAlt, nrg, pop, rebel, rock, rockAlt, roll, sad, unresolved ] 116 | 117 | 118 | Scale.major.chordProgression(\sad, \c); 119 | -> [ [ 0, 4, 7 ], [ 5, 9, 12 ], [ 7, 11, 14 ], [ 7, 11, 14 ] ] 120 | 121 | Scale.major.chordProgression(\weird, \g); 122 | -> [ [ 11, 14, 18 ], [ 12, 16, 19 ], [ 9, 12, 16 ], [ 16, 19, 23 ] ] 123 | ``` 124 | 125 | ### Percussion 126 | 127 | ``` 128 | Percussion.at(\bd); 129 | -> 36 130 | 131 | Percussion.bd; 132 | -> 36 133 | 134 | Percussion.directory; 135 | -> abd: MIDI Note 35 136 | ag: MIDI Note 68 137 | bd: MIDI Note 36 138 | be: MIDI Note 53 139 | bt: MIDI Note 84 140 | ca: MIDI Note 69 141 | cas: MIDI Note 85 142 | ch: MIDI Note 42 143 | ... 144 | 145 | Percussion.names; 146 | -> [ abd, ag, bd, be, bt, ca, cas, ch, cl, cow, cp, cr, cr2, cy, esn, gui, hag, hb, hc, hft, hh, hmt, hq, ht, hti, hwb, jb, lag, lb, lc, lft, lgui, lmt, lt, lti, lwb, lwi, ma, mb, mc, mcl, mhc, ms, mt, oc, oh, ohc, os, ot, ph, ri, ri2, rm, scr, scy, sgui, sha, sl, sn, spl, sps, sqc, st, sti, swi, ta, vib, whi ] 147 | 148 | [\bd, \sn, \ch, \oh].asPercussion; 149 | -> [ 36, 38, 42, 46 ] 150 | 151 | Pseq([\bd, \sn, \ch, \oh], inf).asStream.nextN(10); 152 | -> [ 36, 38, 42, 46, 36, 38, 42, 46, 36, 38 ] 153 | ``` 154 | 155 | ## Reference 156 | 157 | * https://www.midi.org/specifications/item/gm-level-1-sound-set 158 | * https://en.wikipedia.org/wiki/General_MIDI_Level_2#Drum_sounds 159 | 160 | ## License 161 | 162 | [LICENSE](LICENSE) 163 | -------------------------------------------------------------------------------- /Classes/Chord.sc: -------------------------------------------------------------------------------- 1 | /* 2 | Chord class 3 | 4 | (c) 2018 by Mauro 5 | http://www.cyberpunk.net.ar/ 6 | 7 | Similar to Scales / Tuning classes but for Chords. 8 | 9 | */ 10 | 11 | Chord { 12 | var name; 13 | classvar Chord.new(notes: [0], name: "Single note"), 27 | \maj -> Chord.new(notes: [0, 4, 7], name: "Major"), 28 | \min -> Chord.new(notes: [0, 3, 7], name: "Minor"), 29 | \dim -> Chord.new(notes: [0, 3, 6], name: "Diminished"), 30 | \aug -> Chord.new(notes: [0, 4, 8], name: "Augmented"), 31 | \dim7 -> Chord.new(notes: [0, 3, 6, 9], name: "Diminished 7th"), 32 | \five -> Chord.new(notes: [0, 7], name: "Fifth"), 33 | \dom7 -> Chord.new(notes: [0, 4, 7, 10], name: "Dominant 7th"), 34 | \maj7 -> Chord.new(notes: [0, 4, 7, 11], name: "Major 7th"), 35 | \m7 -> Chord.new(notes: [0, 3, 7, 10], name: "Minor 7th"), 36 | \mMaj7 -> Chord.new(notes: [0, 3, 7, 11], name: "Minor-Major 7th"), 37 | \sus4 -> Chord.new(notes: [0, 5, 7], name: "Suspended 4th"), 38 | \sus2 -> Chord.new(notes: [0, 2, 7], name: "Suspended 2nd"), 39 | \six -> Chord.new(notes: [0, 4, 7, 9], name: "Six"), 40 | \m6 -> Chord.new(notes: [0, 3, 7, 9], name: "Minor 6th"), 41 | \nine -> Chord.new(notes: [0, 4, 7, 10, 14], name: "Nine"), 42 | \m9 -> Chord.new(notes: [0, 3, 7, 10, 14], name: "Minor 9th"), 43 | \maj9 -> Chord.new(notes: [0, 4, 7, 11, 14], name: "Major 9th"), 44 | \mMaj9 -> Chord.new(notes: [0, 3, 7, 11, 14], name: "Minor-Major 9th"), 45 | \eleven -> Chord.new(notes: [0, 4, 7, 10, 14, 17], name: "Eleven"), 46 | \m11 -> Chord.new(notes: [0, 3, 7, 10, 14, 17], name: "Minor 11"), 47 | \maj11 -> Chord.new(notes: [0, 4, 7, 11, 14, 17], name: "Major 11"), 48 | \mMaj11 -> Chord.new(notes: [0, 3, 7, 11, 14, 17], name: "Minor-Major 11"), 49 | \thirteen -> Chord.new(notes: [0, 4, 7, 10, 14, 21], name: "Thirtheen"), 50 | \m13 -> Chord.new(notes: [0, 3, 7, 10, 14, 21], name: "Minor 13"), 51 | \maj13 -> Chord.new(notes: [0, 4, 7, 11, 14, 21], name: "Major 13"), 52 | \mMaj13 -> Chord.new(notes: [0, 3, 7, 11, 14, 21], name: "Minor-Major 13"), 53 | \add9 -> Chord.new(notes: [0, 4, 7, 14], name: "Add 9"), 54 | \madd9 -> Chord.new(notes: [0, 3, 7, 14], name: "Minor Add 9"), 55 | \sixadd9 -> Chord.new(notes: [0, 4, 7, 9, 14], name: "Add Six-Nine"), 56 | \m6add9 -> Chord.new(notes: [0, 3, 7, 9, 14], name: "Minor Six Add Nine"), 57 | \add11 -> Chord.new(notes: [0, 4, 7, 10, 17], name: "Add Eleven"), 58 | \majAdd11 -> Chord.new(notes: [0, 4, 7, 11, 17], name: "Major Add Eleven"), 59 | \mAdd11 -> Chord.new(notes: [0, 3, 7, 10, 17], name: "Minor Add Eleven"), 60 | \mMajAdd11 -> Chord.new(notes: [0, 3, 7, 11, 17], name: "Minor-Major Add Eleven"), 61 | \add13 -> Chord.new(notes: [0, 4, 7, 10, 21], name: "Add Thirteen"), 62 | \majAdd13 -> Chord.new(notes: [0, 4, 7, 11, 21], name: "Major Add 13"), 63 | \mAdd13 -> Chord.new(notes: [0, 3, 7, 10, 21], name: "Minor Add 13"), 64 | \mMajAdd13 -> Chord.new(notes: [0, 3, 7, 11, 21], name: "Minor-Major Add 13"), 65 | \sevenFlat5 -> Chord.new(notes: [0, 4, 6, 10], name: "7 Flat 5"), 66 | \sevenSharp5 -> Chord.new(notes: [0, 4, 8, 10], name: "7 Sharp 5"), 67 | \sevenFlat9 -> Chord.new(notes: [0, 4, 7, 10, 13], name: "7 Flat 9"), 68 | \sevenSharp9 -> Chord.new(notes: [0, 4, 7, 10, 15], name: "7 Sharp 9"), 69 | \sevenSharp5Flat9 -> Chord.new(notes: [0, 4, 8, 10, 13], name: "7 Sharp 5 Flat 9"), 70 | \m7Flat5 -> Chord.new(notes: [0, 3, 6, 10], name: "Minor 7 Flat 5"), 71 | \m7dim -> Chord.new(notes: [0, 3, 6, 9], name: "Minor 7 Diminished"), 72 | \m7Sharp5 -> Chord.new(notes: [0, 3, 8, 10], name: "Minor 7 Sharp 5"), 73 | \m7Flat9 -> Chord.new(notes: [0, 3, 7, 10, 13], name: "Minor 7 Flat 9"), 74 | \nineSharp11 -> Chord.new(notes: [0, 4, 7, 10, 14, 18], name: "9 Sharp 11"), 75 | \nineFlat13 -> Chord.new(notes: [0, 4, 7, 10, 14, 20], name: "9 Flat 13"), 76 | \sixSus4 -> Chord.new(notes: [0, 5, 7, 9], name: "6 Suspended 4th"), 77 | \sevenSus4 -> Chord.new(notes: [0, 5, 7, 10], name: "7 Suspended 4th"), 78 | \maj7Sus4 -> Chord.new(notes: [0, 5, 7, 11], name: "Major 7 Suspended 4th"), 79 | \nineSus4 -> Chord.new(notes: [0, 5, 7, 10, 14], name: "9 Suspended 4th"), 80 | \maj9Sus4 -> Chord.new(notes: [0, 5, 7, 11, 14], name: "Major 9 Suspended 4th") 81 | ]; 82 | 83 | all = all.freezeAsParent; 84 | } 85 | 86 | == { arg that; 87 | ^this.compareObject(that, #[\notes]) 88 | } 89 | 90 | hash { 91 | ^this.instVarHash(#[\notes]) 92 | } 93 | 94 | *at { |key| ^all.at(key); } 95 | 96 | *names { ^(all.keys.asArray ++ all.parent.keys).sort; } 97 | 98 | *directory { 99 | ^this.names.collect{ |k| "%: % has % Notes".format(all.at(k).name, k, all.at(k).size) }.join("\n") 100 | } 101 | 102 | size { 103 | ^this.notes.size; 104 | } 105 | 106 | *choose { |size = 16| 107 | var loop = this.chooseFromSelected { |x| (x.size == size) }; 108 | if(loop.isNil) { 109 | "No known chord with size %".format(size).warn 110 | }; 111 | ^loop 112 | } 113 | 114 | *chooseFromSelected { |selectFunc| 115 | selectFunc = selectFunc ? { true }; 116 | ^(all.copy.putAll(all.parent)).select(selectFunc) 117 | .choose.deepCopy; 118 | } 119 | 120 | *doesNotUnderstand { |selector, args| 121 | var chord = this.at(selector, args).deepCopy; 122 | ^chord ?? { super.doesNotUnderstand(selector, args) }; 123 | } 124 | 125 | storedKey { 126 | // can be optimised later 127 | ^all.findKeyForValue(this) 128 | } 129 | 130 | printOn { |stream| this.storeOn(stream) } 131 | storeOn { |stream| 132 | var storedKey = this.storedKey; 133 | stream << this.class.name; 134 | if(storedKey.notNil) { stream << "." << storedKey } { this.storeParamsOn(stream) } 135 | } 136 | storeArgs { ^[notes, name] } 137 | 138 | } --------------------------------------------------------------------------------