├── .gitignore ├── .jshintrc ├── .npmignore ├── LICENSE ├── README.md ├── index.js ├── lib ├── chord.js ├── interval.js ├── knowledge.js ├── note.js ├── scale.js ├── sugar.js └── vector.js ├── package.json ├── teoria.js └── test ├── chords.js ├── intervals.js ├── notes.js ├── scales.js └── solfege.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore node modules 2 | node_modules/ 3 | 4 | # Ignore build files 5 | dist/ 6 | 7 | # Ignore my sandbox file 8 | index.html 9 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "maxlen": 80, 4 | "strict": false, 5 | "devel": true, 6 | "newcap": true, 7 | "noempty": true, 8 | "nonew": true, 9 | "onevar": false, 10 | "plusplus": false, 11 | "trailing": true, 12 | "eqeqeq": true, 13 | "forin": true, 14 | "latedef": true, 15 | "undef": true, 16 | "evil": false, 17 | "unused": true, 18 | "laxcomma": true 19 | } 20 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | .gitignore 3 | teoria.js 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Jakob Miland 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to use, 6 | copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 7 | Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 18 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Teoria.js 2 | ========= 3 | 4 | Teoria.js is a lightweight and fast JavaScript library 5 | for music theory, both Jazz and Classical. It aims at providing an intuitive 6 | programming interface for music software (such as Sheet Readers, 7 | Sheet Writers, MIDI Players etc.). 8 | 9 | Features 10 | --------- 11 | 12 | - A note object (`teoria.Note`), which understands alterations, octaves, 13 | key number, frequency and etc. and Helmholtz notation 14 | 15 | - A chord object (`teoria.Chord`), which understands everything 16 | from simple major/minor chords to advanced Jazz chords (Ab#5b9, F(#11) and such) 17 | 18 | - A scale object (`teoria.Scale`), The scale object is a powerful presentation of 19 | a scale, which supports quite a few handy methods. A scale can either be 20 | constructed from the predefined scales, which by default contains the 7 modes 21 | (Ionian, Dorian, Phrygian etc.) a major and minor pentatonic and the harmonic 22 | chromatic scale or from an arbitrary array of intervals. The scale object 23 | also supports solfège, which makes it perfect for tutorials on sight-reading. 24 | 25 | - An interval object (`teoria.Interval`), which makes it easy to find the 26 | interval between two notes, or find a note that is a given interval from a note. 27 | There's also support for counting the interval span in semitones and inverting the 28 | interval. 29 | 30 | Usage 31 | -------- 32 | 33 | $ npm install teoria 34 | 35 | Can be used with both Node and Browserify/webpack/etc. 36 | 37 | ### ... or less preferable 38 | 39 | Include the bundled build file, `teoria.js` from this repository, directly: 40 | ```html 41 | 42 | ``` 43 | Syntax 44 | --------- 45 | 46 | This is just a short introduction to what teoria-code looks like, 47 | for a technical library reference, look further down this document. 48 | 49 | ```javascript 50 | 51 | // Create notes: 52 | var a4 = teoria.note('a4'); // Scientific notation 53 | var g5 = teoria.note("g''"); // Helmholtz notation 54 | var c3 = teoria.note.fromKey(28); // From a piano key number 55 | 56 | // Find and create notes based on intervals 57 | teoria.interval(a4, g5); // Returns a Interval object representing a minor seventh 58 | teoria.interval(a4, 'M6'); // Returns a Note representing F#5 59 | a4.interval('m3'); // Returns a Note representing C#4 60 | a4.interval(g5); // Returns a Interval object representing a minor seventh 61 | a4.interval(teoria.note('bb5')).invert(); // Returns a Interval representing a major seventh 62 | 63 | // Create scales, based on notes. 64 | a4.scale('mixolydian').simple(); // Returns: ["a", "b", "c#", "d", "e", "f#", "g"] 65 | a4.scale('aeolian').simple(); // Returns: ["a", "b", "c", "d", "e", "f", "g"] 66 | g5.scale('ionian').simple(); // Returns: ["g", "a", "b", "c", "d", "e", "f#"] 67 | g5.scale('dorian'); // Returns a Scale object 68 | 69 | // Create chords with the powerful chord parser 70 | a4.chord('sus2').name; // Returns the name of the chord: 'Asus2' 71 | c3.chord('m').name; // Returns 'Cm' 72 | teoria.chord('Ab#5b9'); // Returns a Chord object, representing a Ab#5b9 chord 73 | g5.chord('dim'); // Returns a Chord object, representing a Gdim chord 74 | 75 | // Calculate note frequencies or find the note corresponding to a frequency 76 | teoria.note.fromFrequency(467); // Returns: {'note':{...},'cents':3.102831} -> A4# a little out of tune. 77 | a4.fq(); // Outputs 440 78 | g5.fq(); // Outputs 783.9908719634985 79 | 80 | // teoria allows for crazy chaining: 81 | teoria.note('a') // Create a note, A3 82 | .scale('lydian') // Create a lydian scale with that note as root (A lydian) 83 | .interval('M2') // Transpose the whole scale a major second up (B lydian) 84 | .get('third') // Get the third note of the scale (D#4) 85 | .chord('maj9') // Create a maj9 chord with that note as root (D#maj9) 86 | .toString(); // Make a string representation: 'D#maj9' 87 | ``` 88 | 89 | Documentation 90 | ------------------------ 91 | 92 | ## teoria.note (name | coord[, duration]) 93 | 94 | *name* - The name argument is the note name as a string. The note can both 95 | be expressed in scientific and Helmholtz notation. 96 | Some examples of valid note names: `Eb4`, `C#,,`, `C4`, `d#''`, `Ab2` 97 | 98 | *coord* - If the first argument isn't a string, but a coord array, 99 | it will instantiate a `Note` instance. 100 | 101 | *duration* - The duration argument is an optional `object` argument. 102 | The object has two also optional parameters: 103 | 104 | - `value` - A `number` corresponding to the value of the duration, such that: 105 | `1 = whole`, `2 = half (minim)`, `4 = quarter`, `8 = eight` 106 | 107 | - `dots` - The number of dots attached to the note. Defaults to `0`. 108 | 109 | ### teoria.note.fromKey(key) 110 | A static method that returns an instance of Note set to the note 111 | at the given piano key, where A0 is key number 1. 112 | See [Wikipedia's piano key article](http://en.wikipedia.org/wiki/Piano_key_frequencies) 113 | for more information. 114 | 115 | ### teoria.note.fromFrequency(fq) 116 | A static method returns an object containing two elements: 117 | 118 | *note* - A `Note` which corresponds to the closest note with the given frequency 119 | 120 | *cents* - A number value of how many cents the note is out of tune 121 | 122 | ### teoria.note.fromMIDI(note) 123 | - Returns an instance of Note set to the corresponding MIDI note value. 124 | 125 | *note* - A number ranging from 0-127 representing a MIDI note value 126 | 127 | ### teoria.note.fromString(note) 128 | - Returns an instance of Note representing the note name 129 | 130 | *note* - The name argument is the note name as a string. The note can both 131 | be expressed in scientific and Helmholtz notation. 132 | Some examples of valid note names: `Eb4`, `C#,,`, `C4`, `d#''`, `Ab2` 133 | 134 | #### Note.name() 135 | - The name of the note, in lowercase letter (*only* the name, not the 136 | accidental signs) 137 | 138 | #### Note.octave() 139 | - The numeric value of the octave of the note 140 | 141 | #### Note.duration 142 | - The duration object as described in the constructor for Note 143 | 144 | #### Note.accidental() 145 | - Returns the string symbolic of the accidental sign (`x`, `#`, `b` or `bb`) 146 | 147 | #### Note.accidentalValue() 148 | - Returns the numeric value (mostly used internally) of the sign: 149 | `x = 2, # = 1, b = -1, bb = -2` 150 | 151 | #### Note#key([whitenotes]) 152 | - Returns the piano key number. E.g. A4 would return 49 153 | 154 | *whitenotes* - If this parameter is set to `true` only the white keys will 155 | be counted when finding the key number. This is mostly for internal use. 156 | 157 | #### Note#midi() 158 | - Returns a number ranging from 0-127 representing a MIDI note value 159 | 160 | #### Note#fq([concertPitch]) 161 | - Calculates and returns the frequency of the note. 162 | 163 | *concertPitch* - If supplied this number will be used instead of the normal 164 | concert pitch which is 440hz. This is useful for some classical music. 165 | 166 | #### Note#chroma() 167 | - Returns the pitch class (index) of the note. 168 | 169 | This allows for easy enharmonic checking: 170 | 171 | teoria.note('e').chroma() === teoria.note('fb').chroma(); 172 | 173 | The chroma number is ranging from pitch class C which is 0 to 11 which is B 174 | 175 | #### Note#scale(scaleName) 176 | - Returns an instance of Scale, with the tonic/root set to this note. 177 | 178 | *scaleName* - The name of the scale to be returned. `'minor'`, 179 | `'chromatic'`, `'ionian'` and others are valid scale names. 180 | 181 | #### Note#interval(interval) 182 | - A sugar function for calling teoria.interval(note, interval); 183 | 184 | Look at the documentation for `teoria.interval` 185 | 186 | #### Note#transpose(interval) 187 | - Like the `#interval` method, but changes `this` note, instead of returning a new 188 | 189 | #### Note#chord([name]) 190 | - Returns an instance of Chord, with root note set to this note 191 | 192 | *name* - The name attribute is the last part of the chord symbol. 193 | Examples: `'m7'`, `'#5b9'`, `'major'`. If the name parameter 194 | isn't set, a standard major chord will be returned. 195 | 196 | #### Note#helmholtz() 197 | - Returns the note name formatted in Helmholtz notation. 198 | 199 | Example: `teoria.note('A5').helmholtz() -> "a''"` 200 | 201 | #### Note#scientific() 202 | - Returns the note name formatted in scientific notation. 203 | 204 | Example: `teoria.note("ab'").scientific() -> "Ab4"` 205 | 206 | #### Note#enharmonics(oneAccidental) 207 | - Returns all notes that are enharmonic with the note 208 | 209 | *oneAccidental* - Boolean, if set to true, only enharmonic notes with one 210 | accidental is returned. E.g. results such as 'eb' and 'c#' but not 'ebb' and 'cx' 211 | 212 | ```javascript 213 | teoria.note('c').enharmonics().toString(); 214 | // -> 'dbb, b#' 215 | 216 | teoria.note('c').enharmonics(true).toString(); 217 | // -> 'b#' 218 | ``` 219 | 220 | #### Note#durationInSeconds(bpm, beatUnit) 221 | - Returns the duration of the note, given a tempo (in bpm) and a beat unit 222 | (the lower numeral of the time signature) 223 | 224 | #### Note#solfege(scale, showOctaves) 225 | - Returns the solfege step in the given scale context 226 | 227 | *scale* - An instance of `Scale`, which is the context of the solfege step measuring 228 | 229 | *showOctaves* - A boolean. If set to true, a "Helmholtz-like" notation will be 230 | used if there's bigger intervals than an octave 231 | 232 | #### Note#durationName() 233 | - Returns the duration name. 234 | 235 | Examples: `teoria.note('A', 8).durationName() -> 'eighth'`, 236 | `teoria.note('C', 16).durationName() -> 'sixteenth'` 237 | 238 | #### Note#scaleDegree(scale) 239 | - Returns this note's degree in a given scale (Scale). For example a 240 | `D` in a C major scale will return `2` as it is the second degree of that scale. 241 | If however the note *isn't* a part of the scale, the degree returned will be 242 | `0`, meaning that the degree doesn't exist. This allows this method to be both 243 | a scale degree index finder *and* an "isNoteInScale" method. 244 | 245 | *scale* - An instance of `Scale` which is the context of the degree measuring 246 | 247 | #### Note#toString([dontShow]) 248 | - Usability function for returning the note as a string 249 | 250 | *dontShow* - If set to `true` the octave will not be included in the returned string. 251 | 252 | ## Chord(root, chord) 253 | - A chord class with a lot of functionality to alter and analyze the chord. 254 | 255 | *root* - A `Note` instance which is to be the root of the chord 256 | 257 | *chord* - A string containing the chord symbol. This can be anything from 258 | simple chords, to super-advanced jazz chords thanks to the detailed and 259 | robust chord parser engine. Example values: 260 | `'m'`, `'m7'`, `'#5b9'`, `'9sus4` and `'#11b5#9'` 261 | 262 | ### teoria.chord(name || note[, octave || symbol]) 263 | - A simple function for getting the notes, no matter the octave, in a chord 264 | 265 | *name* - A string containing the full chord symbol, with note name. Examples: 266 | `'Ab7'`, `'F#(#11b5)'` 267 | 268 | *note* - Instead of supplying a string containing the full chord symbol, 269 | one can pass a `Note` object instead. The note will be considered root in 270 | the new chord object 271 | 272 | *octave* - If the first argument of the function is a chord name (`typeof "string"`), 273 | then the second argument is an optional octave number (`typeof "number"`) of the root. 274 | 275 | *symbol* - A string containing the chord symbol (excluding the note name) 276 | 277 | #### Chord.name 278 | - Holds the full chord symbol, inclusive the root name. 279 | 280 | #### Chord.root 281 | - Holds the `Note` that is the root of the chord. 282 | 283 | #### Chord#notes() 284 | - Returns an array of `Note`s that the chord consists of. 285 | 286 | #### Chord#simple() 287 | - Returns an `Array` of only the notes' names, not the full `Note` objects. 288 | 289 | #### Chord#bass() 290 | - Returns the bass note of the chord (The note voiced the lowest) 291 | 292 | #### Chord#voicing([voicing]) 293 | - Works both as a setter and getter. If no parameter is supplied the 294 | current voicing is returned as an array of `Interval`s 295 | 296 | *voicing* - An optional array of intervals in simple-format 297 | that represents the current voicing of the chord. 298 | 299 | Here's an example: 300 | ```javascript 301 | var bbmaj = teoria.chord('Bbmaj7'); 302 | // Default voicing: 303 | bbmaj.voicing(); // #-> ['P1', 'M3', 'P5', 'M7']; 304 | bbmaj.notes(); // #-> ['bb', 'd', 'f', 'a']; 305 | 306 | // New voicing 307 | bbmaj.voicing(['P1', 'P5', 'M7', 'M10']); 308 | bbmaj.notes(); // #-> ['bb', 'f', 'a', 'd']; 309 | ``` 310 | *NB:* Note that above returned results are pseudo-results, as they will be 311 | returned wrapped in `Interval` and `Note` objects. 312 | 313 | #### Chord#quality() 314 | - Returns a string which holds the quality of the chord, `'major'`, `'minor'`, 315 | `'augmented'`, `'diminished'`, `'half-diminished'`, `'dominant'` or `undefined` 316 | 317 | #### Chord#get(interval) 318 | - Returns the note at a given interval in the chord, if it exists. 319 | 320 | *interval* - A string name of an interval, for example `'third'`, `'fifth'`, `'ninth'`. 321 | 322 | #### Chord#dominant([additional]) 323 | - Returns the naïvely chosen dominant which is a perfect fifth away. 324 | 325 | *additional* - Additional chord extension, for example: `'b9'` or `'#5'` 326 | 327 | #### Chord#subdominant([additional]) 328 | - Returns the naïvely chosen subdominant which is a perfect fourth away. 329 | 330 | *additional* - Like the dominant's. 331 | 332 | #### Chord#parallel([additional]) 333 | - Returns the parallel chord for major and minor triads 334 | 335 | *additional* - Like the dominant's 336 | 337 | #### Chord#chordType() 338 | - Returns the type of the chord: `'dyad'`, `'triad'`, `'trichord'`, 339 | `'tetrad'` or `'unknown'`. 340 | 341 | #### Chord#interval(interval) 342 | - Returns the same chord, a `interval` away 343 | 344 | #### Chord#transpose(interval) 345 | - Like the `#interval` method, except it's `this` chord that gets changed instead of 346 | returning a new chord. 347 | 348 | #### Chord#toString() 349 | - Simple usability function which is an alias for Chord.name 350 | 351 | 352 | ## Scale(tonic, scale) 353 | - The teoria representation of a scale, with a given tonic. 354 | 355 | *tonic* - A `Note` which is to be the tonic of the scale 356 | 357 | *scale* - Can either be a name of a scale (string), or an array of 358 | absolute intervals that defines the scale. The scales supported by default are: 359 | 360 | - major 361 | - minor 362 | - ionian (Alias for major) 363 | - dorian 364 | - phrygian 365 | - lydian 366 | - mixolydian 367 | - aeolian (Alias for minor) 368 | - locrian 369 | - majorpentatonic 370 | - minorpentatonic 371 | - chromatic 372 | - harmonicchromatic (Alias for chromatic) 373 | - blues 374 | - doubleharmonic 375 | - flamenco 376 | - harmonicminor 377 | - melodicminor 378 | - wholetone 379 | 380 | ### teoria.scale(tonic, scale) 381 | - Sugar function for constructing a new `Scale` object 382 | 383 | #### teoria.Scale.KNOWN_SCALES 384 | - An array of all the scale ID's that comes with teoria 385 | 386 | #### Scale.name 387 | - The name of the scale (if available). Type `string` or `undefined` 388 | 389 | #### Scale.tonic 390 | - The `Note` which is the scale's tonic 391 | 392 | #### Scale#notes() 393 | - Returns an array of `Note`s which is the scale's notes 394 | 395 | #### Scale#simple() 396 | - Returns an `Array` of only the notes' names, not the full `Note` objects. 397 | 398 | #### Scale#type() 399 | - Returns the type of the scale, depending on the number of notes. 400 | A scale of length x gives y: 401 | - 2 gives 'ditonic' 402 | - 3 gives 'tritonic' 403 | - 4 gives 'tetratonic' 404 | - 5 gives 'pentatonic' 405 | - 6 gives 'hexatonic', 406 | - 7 gives 'heptatonic', 407 | - 8 gives 'octatonic' 408 | 409 | #### Scale#get(index) 410 | - Returns the note at the given scale index 411 | 412 | *index* - Can be a number referring to the scale step, or the name (string) of the 413 | scale step. E.g. 'first', 'second', 'fourth', 'seventh'. 414 | 415 | #### Scale#solfege(index, showOctaves) 416 | - Returns the solfege name of the given scale step 417 | 418 | *index* Same as `Scale#get` 419 | 420 | *showOctaves* - A boolean meaning the same as `showOctaves` in `Note#solfege` 421 | 422 | 423 | ## teoria.interval(from, to) 424 | - A sugar function for the `#from` and `#between` methods of the same namespace and 425 | for creating `Interval` objects. 426 | 427 | #### teoria.interval(`string`: from) 428 | - A sugar method for the `Interval.toCoord` function 429 | 430 | #### teoria.interval(`Note`: from, `string`: to) 431 | - A sugar method for the `Interval.from` function 432 | 433 | #### teoria.interval(`Note`: from, `Interval`: to) 434 | - Like above, but with a `Interval` instead of a string representation of 435 | the interval 436 | 437 | #### teoria.interval(`Note`: from, `Note`: to) 438 | - A sugar method for the `Interval.between` function 439 | 440 | ##### teoria.interval.from -> Interval.from 441 | ##### teoria.interval.between -> Interval.between 442 | ##### teoria.interval.invert -> Interval.invert 443 | ##### teoria.interval.toCoord -> Interval.toCoord 444 | 445 | 446 | ## Interval(coord) 447 | - A representation of a music interval 448 | 449 | ### Interval.toCoord(simpleInterval) 450 | - Returns a `Interval` representing the interval expressed in string form. 451 | 452 | ### Interval.from(from, to) 453 | - Returns a note which is a given interval away from a root note. 454 | 455 | *from* - The `Note` which is the root of the measuring 456 | 457 | *to* - A `Interval` 458 | 459 | ### Interval.between(from, to) 460 | - Returns an interval object which represents the interval between two notes. 461 | 462 | *from* and *to* are two `Note`s which are the notes that the 463 | interval is measured from. For example if 'a' and 'c' are given, the resulting 464 | interval object would represent a minor third. 465 | 466 | ```javascript 467 | Interval.between(teoria.note("a"), teoria.note("c'")) -> teoria.interval('m3') 468 | ``` 469 | 470 | ### Interval.invert(simpleInterval) 471 | - Returns the inversion of the interval provided 472 | 473 | *simpleInterval* - An interval represented in simple string form. Examples: 474 | 475 | - 'm3' = minor third 476 | - 'P4' = perfect fourth 477 | - 'A4' = augmented fifth 478 | - 'd7' = diminished seventh 479 | - 'M6' = major sixth. 480 | 481 | `'m' = minor`, `'M' = major`, `'A' = augmented` and 482 | `'d' = diminished` 483 | 484 | The number may be prefixed with a `-` to signify that its direction is down. E.g.: 485 | 486 | `m-3` means a descending minor third, and `P-5` means a descending perfect fifth. 487 | 488 | #### Interval.coord 489 | - The interval representation of the interval 490 | 491 | #### Interval.number() 492 | - The interval number (A ninth = 9, A seventh = 7, fifteenth = 15) 493 | 494 | #### Interval.value() 495 | - The value of the interval - That is a ninth = 9, but a downwards ninth is = -9 496 | 497 | #### Interval.toString() 498 | - Returns the *simpleInterval* representation of the interval. E.g. `'P5'`, 499 | `'M3'`, `'A9'`, etc. 500 | 501 | #### Interval.base() 502 | - Returns the name of the simple interval (not compound) 503 | 504 | #### Interval.type() 505 | - Returns the type of array, either `'perfect'` (1, 4, 5, 8) or `'minor'` (2, 3, 6, 7) 506 | 507 | #### Interval.quality([verbose]) 508 | - The quality of the interval (`'dd'`, `'d'` `'m'`, `'P'`, `'M'`, `'A'` or `'AA'`) 509 | 510 | *verbose* is set to a truish value, then long quality names are returned: 511 | `'doubly diminished'`, `'diminished'`, `'minor'`, etc. 512 | 513 | #### Interval.direction([dir]) 514 | - The direction of the interval 515 | 516 | *dir* - If supplied, then the interval's direction is to the `newDirection` 517 | which is either `'up'` or `'down'` 518 | 519 | #### Interval#semitones() 520 | - Returns the `number` of semitones the interval span. 521 | 522 | #### Interval#simple([ignoreDirection]) 523 | - Returns the simple part of the interval as a Interval. Example: 524 | 525 | *ignoreDirection* - An optional boolean that, if set to `true`, returns the 526 | "direction-agnostic" interval. That is the interval with a positive number. 527 | 528 | ```javascript 529 | teoria.interval('M17').simple(); // #-> 'M3' 530 | teoria.interval('m23').simple(); // #-> 'm2' 531 | teoria.interval('P5').simple(); // #-> 'P5' 532 | teoria.interval('P-4').simple(); // #-> 'P-4' 533 | 534 | // With ignoreDirection = true 535 | teoria.interval('M3').simple(true); // #->'M3' 536 | teoria.interval('m-10').simple(true); // #-> 'm3' 537 | ``` 538 | 539 | *NB:* Note that above returned results are pseudo-results, as they will be 540 | returned wrapped in `Interval` objects. 541 | 542 | #### Interval#octaves() 543 | - Returns the number of compound intervals 544 | 545 | #### Interval#isCompound() 546 | - Returns a boolean value, showing if the interval is a compound interval 547 | 548 | #### Interval#add(interval) 549 | - Adds the `interval` to this interval, and returns a `Interval` 550 | representing the result of the addition 551 | 552 | #### Interval#equal(interval) 553 | - Returns true if the supplied `interval` is equal to this interval 554 | 555 | #### Interval#greater(interval) 556 | - Returns true if the supplied `interval` is greater than this interval 557 | 558 | #### Interval#smaller(interval) 559 | - Returns true if the supplied `interval` is smaller than this interval 560 | 561 | #### Interval#invert() 562 | - Returns the inverted interval as a `Interval` 563 | 564 | #### Interval#qualityValue() - *internal* 565 | - Returns the relative to default, value of the quality. 566 | E.g. a teoria.interval('M6'), will have a relative quality value of 1, as all the 567 | intervals defaults to minor and perfect respectively. 568 | 569 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Note = require('./lib/note'); 2 | var Interval = require('./lib/interval'); 3 | var Chord = require('./lib/chord'); 4 | var Scale = require('./lib/scale'); 5 | 6 | var teoria; 7 | 8 | // never thought I would write this, but: Legacy support 9 | function intervalConstructor(from, to) { 10 | // Construct a Interval object from string representation 11 | if (typeof from === 'string') 12 | return Interval.toCoord(from); 13 | 14 | if (typeof to === 'string' && from instanceof Note) 15 | return Interval.from(from, Interval.toCoord(to)); 16 | 17 | if (to instanceof Interval && from instanceof Note) 18 | return Interval.from(from, to); 19 | 20 | if (to instanceof Note && from instanceof Note) 21 | return Interval.between(from, to); 22 | 23 | throw new Error('Invalid parameters'); 24 | } 25 | 26 | intervalConstructor.toCoord = Interval.toCoord; 27 | intervalConstructor.from = Interval.from; 28 | intervalConstructor.between = Interval.between; 29 | intervalConstructor.invert = Interval.invert; 30 | 31 | function noteConstructor(name, duration) { 32 | if (typeof name === 'string') 33 | return Note.fromString(name, duration); 34 | else 35 | return new Note(name, duration); 36 | } 37 | 38 | noteConstructor.fromString = Note.fromString; 39 | noteConstructor.fromKey = Note.fromKey; 40 | noteConstructor.fromFrequency = Note.fromFrequency; 41 | noteConstructor.fromMIDI = Note.fromMIDI; 42 | 43 | function chordConstructor(name, symbol) { 44 | if (typeof name === 'string') { 45 | var root, octave; 46 | root = name.match(/^([a-h])(x|#|bb|b?)/i); 47 | if (root && root[0]) { 48 | octave = typeof symbol === 'number' ? symbol.toString(10) : '4'; 49 | return new Chord(Note.fromString(root[0].toLowerCase() + octave), 50 | name.substr(root[0].length)); 51 | } 52 | } else if (name instanceof Note) 53 | return new Chord(name, symbol); 54 | 55 | throw new Error('Invalid Chord. Couldn\'t find note name'); 56 | } 57 | 58 | function scaleConstructor(tonic, scale) { 59 | tonic = (tonic instanceof Note) ? tonic : teoria.note(tonic); 60 | return new Scale(tonic, scale); 61 | } 62 | 63 | teoria = { 64 | note: noteConstructor, 65 | 66 | chord: chordConstructor, 67 | 68 | interval: intervalConstructor, 69 | 70 | scale: scaleConstructor, 71 | 72 | Note: Note, 73 | Chord: Chord, 74 | Scale: Scale, 75 | Interval: Interval 76 | }; 77 | 78 | 79 | require('./lib/sugar')(teoria); 80 | exports = module.exports = teoria; 81 | -------------------------------------------------------------------------------- /lib/chord.js: -------------------------------------------------------------------------------- 1 | var daccord = require('daccord'); 2 | var knowledge = require('./knowledge'); 3 | var Note = require('./note'); 4 | var Interval = require('./interval'); 5 | 6 | function Chord(root, name) { 7 | if (!(this instanceof Chord)) return new Chord(root, name); 8 | name = name || ''; 9 | this.name = root.name().toUpperCase() + root.accidental() + name; 10 | this.symbol = name; 11 | this.root = root; 12 | this.intervals = []; 13 | this._voicing = []; 14 | 15 | var bass = name.split('/'); 16 | if (bass.length === 2 && bass[1].trim() !== '9') { 17 | name = bass[0]; 18 | bass = bass[1].trim(); 19 | } else { 20 | bass = null; 21 | } 22 | 23 | this.intervals = daccord(name).map(Interval.toCoord); 24 | this._voicing = this.intervals.slice(); 25 | 26 | if (bass) { 27 | var intervals = this.intervals, bassInterval, note; 28 | // Make sure the bass is atop of the root note 29 | note = Note.fromString(bass + (root.octave() + 1)); // crude 30 | 31 | bassInterval = Interval.between(root, note); 32 | bass = bassInterval.simple(); 33 | bassInterval = bassInterval.invert().direction('down'); 34 | 35 | this._voicing = [bassInterval]; 36 | for (var i = 0, length = intervals.length; i < length; i++) { 37 | if (!intervals[i].simple().equal(bass)) 38 | this._voicing.push(intervals[i]); 39 | } 40 | } 41 | } 42 | 43 | Chord.prototype = { 44 | notes: function() { 45 | var root = this.root; 46 | return this.voicing().map(function(interval) { 47 | return root.interval(interval); 48 | }); 49 | }, 50 | 51 | simple: function() { 52 | return this.notes().map(function(n) { return n.toString(true); }); 53 | }, 54 | 55 | bass: function() { 56 | return this.root.interval(this._voicing[0]); 57 | }, 58 | 59 | voicing: function(voicing) { 60 | // Get the voicing 61 | if (!voicing) { 62 | return this._voicing; 63 | } 64 | 65 | // Set the voicing 66 | this._voicing = []; 67 | for (var i = 0, length = voicing.length; i < length; i++) { 68 | this._voicing[i] = Interval.toCoord(voicing[i]); 69 | } 70 | 71 | return this; 72 | }, 73 | 74 | resetVoicing: function() { 75 | this._voicing = this.intervals; 76 | }, 77 | 78 | dominant: function(additional) { 79 | additional = additional || ''; 80 | return new Chord(this.root.interval('P5'), additional); 81 | }, 82 | 83 | subdominant: function(additional) { 84 | additional = additional || ''; 85 | return new Chord(this.root.interval('P4'), additional); 86 | }, 87 | 88 | parallel: function(additional) { 89 | additional = additional || ''; 90 | var quality = this.quality(); 91 | 92 | if (this.chordType() !== 'triad' || quality === 'diminished' || 93 | quality === 'augmented') { 94 | throw new Error('Only major/minor triads have parallel chords'); 95 | } 96 | 97 | if (quality === 'major') { 98 | return new Chord(this.root.interval('m3', 'down'), 'm'); 99 | } else { 100 | return new Chord(this.root.interval('m3', 'up')); 101 | } 102 | }, 103 | 104 | quality: function() { 105 | var third, fifth, seventh, intervals = this.intervals; 106 | 107 | for (var i = 0, length = intervals.length; i < length; i++) { 108 | if (intervals[i].number() === 3) { 109 | third = intervals[i]; 110 | } else if (intervals[i].number() === 5) { 111 | fifth = intervals[i]; 112 | } else if (intervals[i].number() === 7) { 113 | seventh = intervals[i]; 114 | } 115 | } 116 | 117 | if (!third) { 118 | return; 119 | } 120 | 121 | third = (third.direction() === 'down') ? third.invert() : third; 122 | third = third.simple().toString(); 123 | 124 | if (fifth) { 125 | fifth = (fifth.direction === 'down') ? fifth.invert() : fifth; 126 | fifth = fifth.simple().toString(); 127 | } 128 | 129 | if (seventh) { 130 | seventh = (seventh.direction === 'down') ? seventh.invert() : seventh; 131 | seventh = seventh.simple().toString(); 132 | } 133 | 134 | if (third === 'M3') { 135 | if (fifth === 'A5') { 136 | return 'augmented'; 137 | } else if (fifth === 'P5') { 138 | return (seventh === 'm7') ? 'dominant' : 'major'; 139 | } 140 | 141 | return 'major'; 142 | } else if (third === 'm3') { 143 | if (fifth === 'P5') { 144 | return 'minor'; 145 | } else if (fifth === 'd5') { 146 | return (seventh === 'm7') ? 'half-diminished' : 'diminished'; 147 | } 148 | 149 | return 'minor'; 150 | } 151 | }, 152 | 153 | chordType: function() { // In need of better name 154 | var length = this.intervals.length, interval, has, invert, i, name; 155 | 156 | if (length === 2) { 157 | return 'dyad'; 158 | } else if (length === 3) { 159 | has = {unison: false, third: false, fifth: false}; 160 | for (i = 0; i < length; i++) { 161 | interval = this.intervals[i]; 162 | invert = interval.invert(); 163 | if (interval.base() in has) { 164 | has[interval.base()] = true; 165 | } else if (invert.base() in has) { 166 | has[invert.base()] = true; 167 | } 168 | } 169 | 170 | name = (has.unison && has.third && has.fifth) ? 'triad' : 'trichord'; 171 | } else if (length === 4) { 172 | has = {unison: false, third: false, fifth: false, seventh: false}; 173 | for (i = 0; i < length; i++) { 174 | interval = this.intervals[i]; 175 | invert = interval.invert(); 176 | if (interval.base() in has) { 177 | has[interval.base()] = true; 178 | } else if (invert.base() in has) { 179 | has[invert.base()] = true; 180 | } 181 | } 182 | 183 | if (has.unison && has.third && has.fifth && has.seventh) { 184 | name = 'tetrad'; 185 | } 186 | } 187 | 188 | return name || 'unknown'; 189 | }, 190 | 191 | get: function(interval) { 192 | if (typeof interval === 'string' && interval in knowledge.stepNumber) { 193 | var intervals = this.intervals, i, length; 194 | 195 | interval = knowledge.stepNumber[interval]; 196 | for (i = 0, length = intervals.length; i < length; i++) { 197 | if (intervals[i].number() === interval) { 198 | return this.root.interval(intervals[i]); 199 | } 200 | } 201 | 202 | return null; 203 | } else { 204 | throw new Error('Invalid interval name'); 205 | } 206 | }, 207 | 208 | interval: function(interval) { 209 | return new Chord(this.root.interval(interval), this.symbol); 210 | }, 211 | 212 | transpose: function(interval) { 213 | this.root.transpose(interval); 214 | this.name = this.root.name().toUpperCase() + 215 | this.root.accidental() + this.symbol; 216 | 217 | return this; 218 | }, 219 | 220 | toString: function() { 221 | return this.name; 222 | } 223 | }; 224 | 225 | module.exports = Chord; 226 | -------------------------------------------------------------------------------- /lib/interval.js: -------------------------------------------------------------------------------- 1 | var knowledge = require('./knowledge'); 2 | var vector = require('./vector'); 3 | var toCoord = require('interval-coords'); 4 | 5 | function Interval(coord) { 6 | if (!(this instanceof Interval)) return new Interval(coord); 7 | this.coord = coord; 8 | } 9 | 10 | Interval.prototype = { 11 | name: function() { 12 | return knowledge.intervalsIndex[this.number() - 1]; 13 | }, 14 | 15 | semitones: function() { 16 | return vector.sum(vector.mul(this.coord, [12, 7])); 17 | }, 18 | 19 | number: function() { 20 | return Math.abs(this.value()); 21 | }, 22 | 23 | value: function() { 24 | var toMultiply = Math.floor((this.coord[1] - 2) / 7) + 1; 25 | var product = vector.mul(knowledge.sharp, toMultiply); 26 | var without = vector.sub(this.coord, product); 27 | var i = knowledge.intervalFromFifth[without[1] + 5]; 28 | var diff = without[0] - knowledge.intervals[i][0]; 29 | var val = knowledge.stepNumber[i] + diff * 7; 30 | 31 | return (val > 0) ? val : val - 2; 32 | }, 33 | 34 | type: function() { 35 | return knowledge.intervals[this.base()][0] <= 1 ? 'perfect' : 'minor'; 36 | }, 37 | 38 | base: function() { 39 | var product = vector.mul(knowledge.sharp, this.qualityValue()); 40 | var fifth = vector.sub(this.coord, product)[1]; 41 | fifth = this.value() > 0 ? fifth + 5 : -(fifth - 5) % 7; 42 | fifth = fifth < 0 ? knowledge.intervalFromFifth.length + fifth : fifth; 43 | 44 | var name = knowledge.intervalFromFifth[fifth]; 45 | if (name === 'unison' && this.number() >= 8) 46 | name = 'octave'; 47 | 48 | return name; 49 | }, 50 | 51 | direction: function(dir) { 52 | if (dir) { 53 | var is = this.value() >= 1 ? 'up' : 'down'; 54 | if (is !== dir) 55 | this.coord = vector.mul(this.coord, -1); 56 | 57 | return this; 58 | } 59 | else 60 | return this.value() >= 1 ? 'up' : 'down'; 61 | }, 62 | 63 | simple: function(ignore) { 64 | // Get the (upwards) base interval (with quality) 65 | var simple = knowledge.intervals[this.base()]; 66 | var toAdd = vector.mul(knowledge.sharp, this.qualityValue()); 67 | simple = vector.add(simple, toAdd); 68 | 69 | // Turn it around if necessary 70 | if (!ignore) 71 | simple = this.direction() === 'down' ? vector.mul(simple, -1) : simple; 72 | 73 | return new Interval(simple); 74 | }, 75 | 76 | isCompound: function() { 77 | return this.number() > 8; 78 | }, 79 | 80 | octaves: function() { 81 | var toSubtract, without, octaves; 82 | 83 | if (this.direction() === 'up') { 84 | toSubtract = vector.mul(knowledge.sharp, this.qualityValue()); 85 | without = vector.sub(this.coord, toSubtract); 86 | octaves = without[0] - knowledge.intervals[this.base()][0]; 87 | } else { 88 | toSubtract = vector.mul(knowledge.sharp, -this.qualityValue()); 89 | without = vector.sub(this.coord, toSubtract); 90 | octaves = -(without[0] + knowledge.intervals[this.base()][0]); 91 | } 92 | 93 | return octaves; 94 | }, 95 | 96 | invert: function() { 97 | var i = this.base(); 98 | var qual = this.qualityValue(); 99 | var acc = this.type() === 'minor' ? -(qual - 1) : -qual; 100 | var idx = 9 - knowledge.stepNumber[i] - 1; 101 | var coord = knowledge.intervals[knowledge.intervalsIndex[idx]]; 102 | coord = vector.add(coord, vector.mul(knowledge.sharp, acc)); 103 | 104 | return new Interval(coord); 105 | }, 106 | 107 | quality: function(lng) { 108 | var quality = knowledge.alterations[this.type()][this.qualityValue() + 2]; 109 | 110 | return lng ? knowledge.qualityLong[quality] : quality; 111 | }, 112 | 113 | qualityValue: function() { 114 | if (this.direction() === 'down') 115 | return Math.floor((-this.coord[1] - 2) / 7) + 1; 116 | else 117 | return Math.floor((this.coord[1] - 2) / 7) + 1; 118 | }, 119 | 120 | equal: function(interval) { 121 | return this.coord[0] === interval.coord[0] && 122 | this.coord[1] === interval.coord[1]; 123 | }, 124 | 125 | greater: function(interval) { 126 | var semi = this.semitones(); 127 | var isemi = interval.semitones(); 128 | 129 | // If equal in absolute size, measure which interval is bigger 130 | // For example P4 is bigger than A3 131 | return (semi === isemi) ? 132 | (this.number() > interval.number()) : (semi > isemi); 133 | }, 134 | 135 | smaller: function(interval) { 136 | return !this.equal(interval) && !this.greater(interval); 137 | }, 138 | 139 | add: function(interval) { 140 | return new Interval(vector.add(this.coord, interval.coord)); 141 | }, 142 | 143 | toString: function(ignore) { 144 | // If given true, return the positive value 145 | var number = ignore ? this.number() : this.value(); 146 | 147 | return this.quality() + number; 148 | } 149 | }; 150 | 151 | Interval.toCoord = function(simple) { 152 | var coord = toCoord(simple); 153 | if (!coord) 154 | throw new Error('Invalid simple format interval'); 155 | 156 | return new Interval(coord); 157 | }; 158 | 159 | Interval.from = function(from, to) { 160 | return from.interval(to); 161 | }; 162 | 163 | Interval.between = function(from, to) { 164 | return new Interval(vector.sub(to.coord, from.coord)); 165 | }; 166 | 167 | Interval.invert = function(sInterval) { 168 | return Interval.toCoord(sInterval).invert().toString(); 169 | }; 170 | 171 | module.exports = Interval; 172 | -------------------------------------------------------------------------------- /lib/knowledge.js: -------------------------------------------------------------------------------- 1 | // Note coordinates [octave, fifth] relative to C 2 | module.exports = { 3 | notes: { 4 | c: [0, 0], 5 | d: [-1, 2], 6 | e: [-2, 4], 7 | f: [1, -1], 8 | g: [0, 1], 9 | a: [-1, 3], 10 | b: [-2, 5], 11 | h: [-2, 5] 12 | }, 13 | 14 | intervals: { 15 | unison: [0, 0], 16 | second: [3, -5], 17 | third: [2, -3], 18 | fourth: [1, -1], 19 | fifth: [0, 1], 20 | sixth: [3, -4], 21 | seventh: [2, -2], 22 | octave: [1, 0] 23 | }, 24 | 25 | intervalFromFifth: ['second', 'sixth', 'third', 'seventh', 'fourth', 26 | 'unison', 'fifth'], 27 | 28 | intervalsIndex: ['unison', 'second', 'third', 'fourth', 'fifth', 29 | 'sixth', 'seventh', 'octave', 'ninth', 'tenth', 30 | 'eleventh', 'twelfth', 'thirteenth', 'fourteenth', 31 | 'fifteenth'], 32 | 33 | // linear index to fifth = (2 * index + 1) % 7 34 | fifths: ['f', 'c', 'g', 'd', 'a', 'e', 'b'], 35 | accidentals: ['bb', 'b', '', '#', 'x'], 36 | 37 | sharp: [-4, 7], 38 | A4: [3, 3], 39 | 40 | durations: { 41 | '0.25': 'longa', 42 | '0.5': 'breve', 43 | '1': 'whole', 44 | '2': 'half', 45 | '4': 'quarter', 46 | '8': 'eighth', 47 | '16': 'sixteenth', 48 | '32': 'thirty-second', 49 | '64': 'sixty-fourth', 50 | '128': 'hundred-twenty-eighth' 51 | }, 52 | 53 | qualityLong: { 54 | P: 'perfect', 55 | M: 'major', 56 | m: 'minor', 57 | A: 'augmented', 58 | AA: 'doubly augmented', 59 | d: 'diminished', 60 | dd: 'doubly diminished' 61 | }, 62 | 63 | alterations: { 64 | perfect: ['dd', 'd', 'P', 'A', 'AA'], 65 | minor: ['dd', 'd', 'm', 'M', 'A', 'AA'] 66 | }, 67 | 68 | symbols: { 69 | 'min': ['m3', 'P5'], 70 | 'm': ['m3', 'P5'], 71 | '-': ['m3', 'P5'], 72 | 73 | 'M': ['M3', 'P5'], 74 | '': ['M3', 'P5'], 75 | 76 | '+': ['M3', 'A5'], 77 | 'aug': ['M3', 'A5'], 78 | 79 | 'dim': ['m3', 'd5'], 80 | 'o': ['m3', 'd5'], 81 | 82 | 'maj': ['M3', 'P5', 'M7'], 83 | 'dom': ['M3', 'P5', 'm7'], 84 | 'ø': ['m3', 'd5', 'm7'], 85 | 86 | '5': ['P5'] 87 | }, 88 | 89 | chordShort: { 90 | 'major': 'M', 91 | 'minor': 'm', 92 | 'augmented': 'aug', 93 | 'diminished': 'dim', 94 | 'half-diminished': '7b5', 95 | 'power': '5', 96 | 'dominant': '7' 97 | }, 98 | 99 | stepNumber: { 100 | 'unison': 1, 101 | 'first': 1, 102 | 'second': 2, 103 | 'third': 3, 104 | 'fourth': 4, 105 | 'fifth': 5, 106 | 'sixth': 6, 107 | 'seventh': 7, 108 | 'octave': 8, 109 | 'ninth': 9, 110 | 'eleventh': 11, 111 | 'thirteenth': 13 112 | }, 113 | 114 | // Adjusted Shearer syllables - Chromatic solfege system 115 | // Some intervals are not provided for. These include: 116 | // dd2 - Doubly diminished second 117 | // dd3 - Doubly diminished third 118 | // AA3 - Doubly augmented third 119 | // dd6 - Doubly diminished sixth 120 | // dd7 - Doubly diminished seventh 121 | // AA7 - Doubly augmented seventh 122 | intervalSolfege: { 123 | 'dd1': 'daw', 124 | 'd1': 'de', 125 | 'P1': 'do', 126 | 'A1': 'di', 127 | 'AA1': 'dai', 128 | 'd2': 'raw', 129 | 'm2': 'ra', 130 | 'M2': 're', 131 | 'A2': 'ri', 132 | 'AA2': 'rai', 133 | 'd3': 'maw', 134 | 'm3': 'me', 135 | 'M3': 'mi', 136 | 'A3': 'mai', 137 | 'dd4': 'faw', 138 | 'd4': 'fe', 139 | 'P4': 'fa', 140 | 'A4': 'fi', 141 | 'AA4': 'fai', 142 | 'dd5': 'saw', 143 | 'd5': 'se', 144 | 'P5': 'so', 145 | 'A5': 'si', 146 | 'AA5': 'sai', 147 | 'd6': 'law', 148 | 'm6': 'le', 149 | 'M6': 'la', 150 | 'A6': 'li', 151 | 'AA6': 'lai', 152 | 'd7': 'taw', 153 | 'm7': 'te', 154 | 'M7': 'ti', 155 | 'A7': 'tai', 156 | 'dd8': 'daw', 157 | 'd8': 'de', 158 | 'P8': 'do', 159 | 'A8': 'di', 160 | 'AA8': 'dai' 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /lib/note.js: -------------------------------------------------------------------------------- 1 | var scientific = require('scientific-notation'); 2 | var helmholtz = require('helmholtz'); 3 | var pitchFq = require('pitch-fq'); 4 | var knowledge = require('./knowledge'); 5 | var vector = require('./vector'); 6 | var Interval = require('./interval'); 7 | 8 | function pad(str, ch, len) { 9 | for (; len > 0; len--) { 10 | str += ch; 11 | } 12 | 13 | return str; 14 | } 15 | 16 | 17 | function Note(coord, duration) { 18 | if (!(this instanceof Note)) return new Note(coord, duration); 19 | duration = duration || {}; 20 | 21 | this.duration = { value: duration.value || 4, dots: duration.dots || 0 }; 22 | this.coord = coord; 23 | } 24 | 25 | Note.prototype = { 26 | octave: function() { 27 | return this.coord[0] + knowledge.A4[0] - knowledge.notes[this.name()][0] + 28 | this.accidentalValue() * 4; 29 | }, 30 | 31 | name: function() { 32 | var value = this.accidentalValue(); 33 | var idx = this.coord[1] + knowledge.A4[1] - value * 7 + 1; 34 | return knowledge.fifths[idx]; 35 | }, 36 | 37 | accidentalValue: function() { 38 | return Math.round((this.coord[1] + knowledge.A4[1] - 2) / 7); 39 | }, 40 | 41 | accidental: function() { 42 | return knowledge.accidentals[this.accidentalValue() + 2]; 43 | }, 44 | 45 | /** 46 | * Returns the key number of the note 47 | */ 48 | key: function(white) { 49 | if (white) 50 | return this.coord[0] * 7 + this.coord[1] * 4 + 29; 51 | else 52 | return this.coord[0] * 12 + this.coord[1] * 7 + 49; 53 | }, 54 | 55 | /** 56 | * Returns a number ranging from 0-127 representing a MIDI note value 57 | */ 58 | midi: function() { 59 | return this.key() + 20; 60 | }, 61 | 62 | /** 63 | * Calculates and returns the frequency of the note. 64 | * Optional concert pitch (def. 440) 65 | */ 66 | fq: function(concertPitch) { 67 | return pitchFq(this.coord, concertPitch); 68 | }, 69 | 70 | /** 71 | * Returns the pitch class index (chroma) of the note 72 | */ 73 | chroma: function() { 74 | var value = (vector.sum(vector.mul(this.coord, [12, 7])) - 3) % 12; 75 | 76 | return (value < 0) ? value + 12 : value; 77 | }, 78 | 79 | interval: function(interval) { 80 | if (typeof interval === 'string') interval = Interval.toCoord(interval); 81 | 82 | if (interval instanceof Interval) 83 | return new Note(vector.add(this.coord, interval.coord), this.duration); 84 | else if (interval instanceof Note) 85 | return new Interval(vector.sub(interval.coord, this.coord)); 86 | }, 87 | 88 | transpose: function(interval) { 89 | this.coord = vector.add(this.coord, interval.coord); 90 | return this; 91 | }, 92 | 93 | /** 94 | * Returns the Helmholtz notation form of the note (fx C,, d' F# g#'') 95 | */ 96 | helmholtz: function() { 97 | var octave = this.octave(); 98 | var name = this.name(); 99 | name = octave < 3 ? name.toUpperCase() : name.toLowerCase(); 100 | var padchar = octave < 3 ? ',' : '\''; 101 | var padcount = octave < 2 ? 2 - octave : octave - 3; 102 | 103 | return pad(name + this.accidental(), padchar, padcount); 104 | }, 105 | 106 | /** 107 | * Returns the scientific notation form of the note (fx E4, Bb3, C#7 etc.) 108 | */ 109 | scientific: function() { 110 | return this.name().toUpperCase() + this.accidental() + this.octave(); 111 | }, 112 | 113 | /** 114 | * Returns notes that are enharmonic with this note. 115 | */ 116 | enharmonics: function(oneaccidental) { 117 | var key = this.key(), limit = oneaccidental ? 2 : 3; 118 | 119 | return ['m3', 'm2', 'm-2', 'm-3'] 120 | .map(this.interval.bind(this)) 121 | .filter(function(note) { 122 | var acc = note.accidentalValue(); 123 | var diff = key - (note.key() - acc); 124 | 125 | if (diff < limit && diff > -limit) { 126 | var product = vector.mul(knowledge.sharp, diff - acc); 127 | note.coord = vector.add(note.coord, product); 128 | return true; 129 | } 130 | }); 131 | }, 132 | 133 | solfege: function(scale, showOctaves) { 134 | var interval = scale.tonic.interval(this), solfege, stroke, count; 135 | if (interval.direction() === 'down') 136 | interval = interval.invert(); 137 | 138 | if (showOctaves) { 139 | count = (this.key(true) - scale.tonic.key(true)) / 7; 140 | count = (count >= 0) ? Math.floor(count) : -(Math.ceil(-count)); 141 | stroke = (count >= 0) ? '\'' : ','; 142 | } 143 | 144 | solfege = knowledge.intervalSolfege[interval.simple(true).toString()]; 145 | return (showOctaves) ? pad(solfege, stroke, Math.abs(count)) : solfege; 146 | }, 147 | 148 | scaleDegree: function(scale) { 149 | var inter = scale.tonic.interval(this); 150 | 151 | // If the direction is down, or we're dealing with an octave - invert it 152 | if (inter.direction() === 'down' || 153 | (inter.coord[1] === 0 && inter.coord[0] !== 0)) { 154 | inter = inter.invert(); 155 | } 156 | 157 | inter = inter.simple(true).coord; 158 | 159 | return scale.scale.reduce(function(index, current, i) { 160 | var coord = Interval.toCoord(current).coord; 161 | return coord[0] === inter[0] && coord[1] === inter[1] ? i + 1 : index; 162 | }, 0); 163 | }, 164 | 165 | /** 166 | * Returns the name of the duration value, 167 | * such as 'whole', 'quarter', 'sixteenth' etc. 168 | */ 169 | durationName: function() { 170 | return knowledge.durations[this.duration.value]; 171 | }, 172 | 173 | /** 174 | * Returns the duration of the note (including dots) 175 | * in seconds. The first argument is the tempo in beats 176 | * per minute, the second is the beat unit (i.e. the 177 | * lower numeral in a time signature). 178 | */ 179 | durationInSeconds: function(bpm, beatUnit) { 180 | var secs = (60 / bpm) / (this.duration.value / 4) / (beatUnit / 4); 181 | return secs * 2 - secs / Math.pow(2, this.duration.dots); 182 | }, 183 | 184 | /** 185 | * Returns the name of the note, with an optional display of octave number 186 | */ 187 | toString: function(dont) { 188 | return this.name() + this.accidental() + (dont ? '' : this.octave()); 189 | } 190 | }; 191 | 192 | Note.fromString = function(name, dur) { 193 | var coord = scientific(name); 194 | if (!coord) coord = helmholtz(name); 195 | return new Note(coord, dur); 196 | }; 197 | 198 | Note.fromKey = function(key) { 199 | var octave = Math.floor((key - 4) / 12); 200 | var distance = key - (octave * 12) - 4; 201 | var name = knowledge.fifths[(2 * Math.round(distance / 2) + 1) % 7]; 202 | var subDiff = vector.sub(knowledge.notes[name], knowledge.A4); 203 | var note = vector.add(subDiff, [octave + 1, 0]); 204 | var diff = (key - 49) - vector.sum(vector.mul(note, [12, 7])); 205 | 206 | var arg = diff ? vector.add(note, vector.mul(knowledge.sharp, diff)) : note; 207 | return new Note(arg); 208 | }; 209 | 210 | Note.fromFrequency = function(fq, concertPitch) { 211 | var key, cents, originalFq; 212 | concertPitch = concertPitch || 440; 213 | 214 | key = 49 + 12 * ((Math.log(fq) - Math.log(concertPitch)) / Math.log(2)); 215 | key = Math.round(key); 216 | originalFq = concertPitch * Math.pow(2, (key - 49) / 12); 217 | cents = 1200 * (Math.log(fq / originalFq) / Math.log(2)); 218 | 219 | return { note: Note.fromKey(key), cents: cents }; 220 | }; 221 | 222 | Note.fromMIDI = function(note) { 223 | return Note.fromKey(note - 20); 224 | }; 225 | 226 | module.exports = Note; 227 | -------------------------------------------------------------------------------- /lib/scale.js: -------------------------------------------------------------------------------- 1 | var knowledge = require('./knowledge'); 2 | var Interval = require('./interval'); 3 | 4 | var scales = { 5 | aeolian: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'm7'], 6 | blues: ['P1', 'm3', 'P4', 'd5', 'P5', 'm7'], 7 | chromatic: ['P1', 'm2', 'M2', 'm3', 'M3', 'P4', 8 | 'A4', 'P5', 'm6', 'M6', 'm7', 'M7'], 9 | dorian: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'm7'], 10 | doubleharmonic: ['P1', 'm2', 'M3', 'P4', 'P5', 'm6', 'M7'], 11 | harmonicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'M7'], 12 | ionian: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'M7'], 13 | locrian: ['P1', 'm2', 'm3', 'P4', 'd5', 'm6', 'm7'], 14 | lydian: ['P1', 'M2', 'M3', 'A4', 'P5', 'M6', 'M7'], 15 | majorpentatonic: ['P1', 'M2', 'M3', 'P5', 'M6'], 16 | melodicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'M7'], 17 | minorpentatonic: ['P1', 'm3', 'P4', 'P5', 'm7'], 18 | mixolydian: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'm7'], 19 | phrygian: ['P1', 'm2', 'm3', 'P4', 'P5', 'm6', 'm7'], 20 | wholetone: ['P1', 'M2', 'M3', 'A4', 'A5', 'A6'] 21 | }; 22 | 23 | // synonyms 24 | scales.harmonicchromatic = scales.chromatic; 25 | scales.minor = scales.aeolian; 26 | scales.major = scales.ionian; 27 | scales.flamenco = scales.doubleharmonic; 28 | 29 | function Scale(tonic, scale) { 30 | if (!(this instanceof Scale)) return new Scale(tonic, scale); 31 | var scaleName, i; 32 | if (!('coord' in tonic)) { 33 | throw new Error('Invalid Tonic'); 34 | } 35 | 36 | if (typeof scale === 'string') { 37 | scaleName = scale; 38 | scale = scales[scale]; 39 | if (!scale) 40 | throw new Error('Invalid Scale'); 41 | } else { 42 | for (i in scales) { 43 | if (scales.hasOwnProperty(i)) { 44 | if (scales[i].toString() === scale.toString()) { 45 | scaleName = i; 46 | break; 47 | } 48 | } 49 | } 50 | } 51 | 52 | this.name = scaleName; 53 | this.tonic = tonic; 54 | this.scale = scale; 55 | } 56 | 57 | Scale.prototype = { 58 | notes: function() { 59 | var notes = []; 60 | 61 | for (var i = 0, length = this.scale.length; i < length; i++) { 62 | notes.push(this.tonic.interval(this.scale[i])); 63 | } 64 | 65 | return notes; 66 | }, 67 | 68 | simple: function() { 69 | return this.notes().map(function(n) { return n.toString(true); }); 70 | }, 71 | 72 | type: function() { 73 | var length = this.scale.length - 2; 74 | if (length < 8) { 75 | return ['di', 'tri', 'tetra', 'penta', 'hexa', 'hepta', 'octa'][length] + 76 | 'tonic'; 77 | } 78 | }, 79 | 80 | get: function(i) { 81 | var isStepStr = typeof i === 'string' && i in knowledge.stepNumber; 82 | i = isStepStr ? knowledge.stepNumber[i] : i; 83 | var len = this.scale.length; 84 | var interval, octaves; 85 | 86 | if (i < 0) { 87 | interval = this.scale[i % len + len - 1]; 88 | octaves = Math.floor((i - 1) / len); 89 | } else if (i % len === 0) { 90 | interval = this.scale[len - 1]; 91 | octaves = (i / len) - 1; 92 | } else { 93 | interval = this.scale[i % len - 1]; 94 | octaves = Math.floor(i / len); 95 | } 96 | 97 | return this.tonic.interval(interval).interval(new Interval([octaves, 0])); 98 | }, 99 | 100 | solfege: function(index, showOctaves) { 101 | if (index) 102 | return this.get(index).solfege(this, showOctaves); 103 | 104 | return this.notes().map(function(n) { 105 | return n.solfege(this, showOctaves); 106 | }); 107 | }, 108 | 109 | interval: function(interval) { 110 | interval = (typeof interval === 'string') ? 111 | Interval.toCoord(interval) : interval; 112 | return new Scale(this.tonic.interval(interval), this.scale); 113 | }, 114 | 115 | transpose: function(interval) { 116 | var scale = this.interval(interval); 117 | this.scale = scale.scale; 118 | this.tonic = scale.tonic; 119 | 120 | return this; 121 | } 122 | }; 123 | Scale.KNOWN_SCALES = Object.keys(scales); 124 | 125 | module.exports = Scale; 126 | -------------------------------------------------------------------------------- /lib/sugar.js: -------------------------------------------------------------------------------- 1 | var knowledge = require('./knowledge'); 2 | 3 | module.exports = function(teoria) { 4 | var Note = teoria.Note; 5 | var Chord = teoria.Chord; 6 | var Scale = teoria.Scale; 7 | 8 | Note.prototype.chord = function(chord) { 9 | var isShortChord = chord in knowledge.chordShort; 10 | chord = isShortChord ? knowledge.chordShort[chord] : chord; 11 | 12 | return new Chord(this, chord); 13 | }; 14 | 15 | Note.prototype.scale = function(scale) { 16 | return new Scale(this, scale); 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /lib/vector.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | add: function(note, interval) { 3 | return [note[0] + interval[0], note[1] + interval[1]]; 4 | }, 5 | 6 | sub: function(note, interval) { 7 | return [note[0] - interval[0], note[1] - interval[1]]; 8 | }, 9 | 10 | mul: function(note, interval) { 11 | if (typeof interval === 'number') 12 | return [note[0] * interval, note[1] * interval]; 13 | else 14 | return [note[0] * interval[0], note[1] * interval[1]]; 15 | }, 16 | 17 | sum: function(coord) { 18 | return coord[0] + coord[1]; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "teoria", 3 | "version": "2.5.0", 4 | "description": "Music theory for JavaScript", 5 | "homepage": "http://saebekassebil.github.com/teoria", 6 | "keywords": [ 7 | "music", 8 | "theory", 9 | "jazz", 10 | "classical", 11 | "chord" 12 | ], 13 | "main": "./index.js", 14 | "author": { 15 | "name": "Jakob Miland ", 16 | "email": "saebekassebil@gmail.com", 17 | "url": "https://github.com/saebekassebil" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/saebekassebil/teoria" 22 | }, 23 | "license": "MIT", 24 | "scripts": { 25 | "test": "vows --dot-matric test/*", 26 | "lint": "jshint index.js lib/", 27 | "bundle": "browserify index.js --standalone teoria > teoria.js", 28 | "build": "npm run lint && npm test && npm run bundle" 29 | }, 30 | "devDependencies": { 31 | "jshint": ">=0.9.0", 32 | "vows": ">= 0.6.0" 33 | }, 34 | "dependencies": { 35 | "daccord": "^1.0.1", 36 | "helmholtz": "^2.0.1", 37 | "interval-coords": "^1.1.1", 38 | "pitch-fq": "^1.0.0", 39 | "scientific-notation": "^1.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /teoria.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.teoria = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) ? val : val - 2; 342 | }, 343 | 344 | type: function() { 345 | return knowledge.intervals[this.base()][0] <= 1 ? 'perfect' : 'minor'; 346 | }, 347 | 348 | base: function() { 349 | var product = vector.mul(knowledge.sharp, this.qualityValue()); 350 | var fifth = vector.sub(this.coord, product)[1]; 351 | fifth = this.value() > 0 ? fifth + 5 : -(fifth - 5) % 7; 352 | fifth = fifth < 0 ? knowledge.intervalFromFifth.length + fifth : fifth; 353 | 354 | var name = knowledge.intervalFromFifth[fifth]; 355 | if (name === 'unison' && this.number() >= 8) 356 | name = 'octave'; 357 | 358 | return name; 359 | }, 360 | 361 | direction: function(dir) { 362 | if (dir) { 363 | var is = this.value() >= 1 ? 'up' : 'down'; 364 | if (is !== dir) 365 | this.coord = vector.mul(this.coord, -1); 366 | 367 | return this; 368 | } 369 | else 370 | return this.value() >= 1 ? 'up' : 'down'; 371 | }, 372 | 373 | simple: function(ignore) { 374 | // Get the (upwards) base interval (with quality) 375 | var simple = knowledge.intervals[this.base()]; 376 | var toAdd = vector.mul(knowledge.sharp, this.qualityValue()); 377 | simple = vector.add(simple, toAdd); 378 | 379 | // Turn it around if necessary 380 | if (!ignore) 381 | simple = this.direction() === 'down' ? vector.mul(simple, -1) : simple; 382 | 383 | return new Interval(simple); 384 | }, 385 | 386 | isCompound: function() { 387 | return this.number() > 8; 388 | }, 389 | 390 | octaves: function() { 391 | var toSubtract, without, octaves; 392 | 393 | if (this.direction() === 'up') { 394 | toSubtract = vector.mul(knowledge.sharp, this.qualityValue()); 395 | without = vector.sub(this.coord, toSubtract); 396 | octaves = without[0] - knowledge.intervals[this.base()][0]; 397 | } else { 398 | toSubtract = vector.mul(knowledge.sharp, -this.qualityValue()); 399 | without = vector.sub(this.coord, toSubtract); 400 | octaves = -(without[0] + knowledge.intervals[this.base()][0]); 401 | } 402 | 403 | return octaves; 404 | }, 405 | 406 | invert: function() { 407 | var i = this.base(); 408 | var qual = this.qualityValue(); 409 | var acc = this.type() === 'minor' ? -(qual - 1) : -qual; 410 | var idx = 9 - knowledge.stepNumber[i] - 1; 411 | var coord = knowledge.intervals[knowledge.intervalsIndex[idx]]; 412 | coord = vector.add(coord, vector.mul(knowledge.sharp, acc)); 413 | 414 | return new Interval(coord); 415 | }, 416 | 417 | quality: function(lng) { 418 | var quality = knowledge.alterations[this.type()][this.qualityValue() + 2]; 419 | 420 | return lng ? knowledge.qualityLong[quality] : quality; 421 | }, 422 | 423 | qualityValue: function() { 424 | if (this.direction() === 'down') 425 | return Math.floor((-this.coord[1] - 2) / 7) + 1; 426 | else 427 | return Math.floor((this.coord[1] - 2) / 7) + 1; 428 | }, 429 | 430 | equal: function(interval) { 431 | return this.coord[0] === interval.coord[0] && 432 | this.coord[1] === interval.coord[1]; 433 | }, 434 | 435 | greater: function(interval) { 436 | var semi = this.semitones(); 437 | var isemi = interval.semitones(); 438 | 439 | // If equal in absolute size, measure which interval is bigger 440 | // For example P4 is bigger than A3 441 | return (semi === isemi) ? 442 | (this.number() > interval.number()) : (semi > isemi); 443 | }, 444 | 445 | smaller: function(interval) { 446 | return !this.equal(interval) && !this.greater(interval); 447 | }, 448 | 449 | add: function(interval) { 450 | return new Interval(vector.add(this.coord, interval.coord)); 451 | }, 452 | 453 | toString: function(ignore) { 454 | // If given true, return the positive value 455 | var number = ignore ? this.number() : this.value(); 456 | 457 | return this.quality() + number; 458 | } 459 | }; 460 | 461 | Interval.toCoord = function(simple) { 462 | var coord = toCoord(simple); 463 | if (!coord) 464 | throw new Error('Invalid simple format interval'); 465 | 466 | return new Interval(coord); 467 | }; 468 | 469 | Interval.from = function(from, to) { 470 | return from.interval(to); 471 | }; 472 | 473 | Interval.between = function(from, to) { 474 | return new Interval(vector.sub(to.coord, from.coord)); 475 | }; 476 | 477 | Interval.invert = function(sInterval) { 478 | return Interval.toCoord(sInterval).invert().toString(); 479 | }; 480 | 481 | module.exports = Interval; 482 | 483 | },{"./knowledge":4,"./vector":8,"interval-coords":12}],4:[function(require,module,exports){ 484 | // Note coordinates [octave, fifth] relative to C 485 | module.exports = { 486 | notes: { 487 | c: [0, 0], 488 | d: [-1, 2], 489 | e: [-2, 4], 490 | f: [1, -1], 491 | g: [0, 1], 492 | a: [-1, 3], 493 | b: [-2, 5], 494 | h: [-2, 5] 495 | }, 496 | 497 | intervals: { 498 | unison: [0, 0], 499 | second: [3, -5], 500 | third: [2, -3], 501 | fourth: [1, -1], 502 | fifth: [0, 1], 503 | sixth: [3, -4], 504 | seventh: [2, -2], 505 | octave: [1, 0] 506 | }, 507 | 508 | intervalFromFifth: ['second', 'sixth', 'third', 'seventh', 'fourth', 509 | 'unison', 'fifth'], 510 | 511 | intervalsIndex: ['unison', 'second', 'third', 'fourth', 'fifth', 512 | 'sixth', 'seventh', 'octave', 'ninth', 'tenth', 513 | 'eleventh', 'twelfth', 'thirteenth', 'fourteenth', 514 | 'fifteenth'], 515 | 516 | // linear index to fifth = (2 * index + 1) % 7 517 | fifths: ['f', 'c', 'g', 'd', 'a', 'e', 'b'], 518 | accidentals: ['bb', 'b', '', '#', 'x'], 519 | 520 | sharp: [-4, 7], 521 | A4: [3, 3], 522 | 523 | durations: { 524 | '0.25': 'longa', 525 | '0.5': 'breve', 526 | '1': 'whole', 527 | '2': 'half', 528 | '4': 'quarter', 529 | '8': 'eighth', 530 | '16': 'sixteenth', 531 | '32': 'thirty-second', 532 | '64': 'sixty-fourth', 533 | '128': 'hundred-twenty-eighth' 534 | }, 535 | 536 | qualityLong: { 537 | P: 'perfect', 538 | M: 'major', 539 | m: 'minor', 540 | A: 'augmented', 541 | AA: 'doubly augmented', 542 | d: 'diminished', 543 | dd: 'doubly diminished' 544 | }, 545 | 546 | alterations: { 547 | perfect: ['dd', 'd', 'P', 'A', 'AA'], 548 | minor: ['dd', 'd', 'm', 'M', 'A', 'AA'] 549 | }, 550 | 551 | symbols: { 552 | 'min': ['m3', 'P5'], 553 | 'm': ['m3', 'P5'], 554 | '-': ['m3', 'P5'], 555 | 556 | 'M': ['M3', 'P5'], 557 | '': ['M3', 'P5'], 558 | 559 | '+': ['M3', 'A5'], 560 | 'aug': ['M3', 'A5'], 561 | 562 | 'dim': ['m3', 'd5'], 563 | 'o': ['m3', 'd5'], 564 | 565 | 'maj': ['M3', 'P5', 'M7'], 566 | 'dom': ['M3', 'P5', 'm7'], 567 | 'ø': ['m3', 'd5', 'm7'], 568 | 569 | '5': ['P5'] 570 | }, 571 | 572 | chordShort: { 573 | 'major': 'M', 574 | 'minor': 'm', 575 | 'augmented': 'aug', 576 | 'diminished': 'dim', 577 | 'half-diminished': '7b5', 578 | 'power': '5', 579 | 'dominant': '7' 580 | }, 581 | 582 | stepNumber: { 583 | 'unison': 1, 584 | 'first': 1, 585 | 'second': 2, 586 | 'third': 3, 587 | 'fourth': 4, 588 | 'fifth': 5, 589 | 'sixth': 6, 590 | 'seventh': 7, 591 | 'octave': 8, 592 | 'ninth': 9, 593 | 'eleventh': 11, 594 | 'thirteenth': 13 595 | }, 596 | 597 | // Adjusted Shearer syllables - Chromatic solfege system 598 | // Some intervals are not provided for. These include: 599 | // dd2 - Doubly diminished second 600 | // dd3 - Doubly diminished third 601 | // AA3 - Doubly augmented third 602 | // dd6 - Doubly diminished sixth 603 | // dd7 - Doubly diminished seventh 604 | // AA7 - Doubly augmented seventh 605 | intervalSolfege: { 606 | 'dd1': 'daw', 607 | 'd1': 'de', 608 | 'P1': 'do', 609 | 'A1': 'di', 610 | 'AA1': 'dai', 611 | 'd2': 'raw', 612 | 'm2': 'ra', 613 | 'M2': 're', 614 | 'A2': 'ri', 615 | 'AA2': 'rai', 616 | 'd3': 'maw', 617 | 'm3': 'me', 618 | 'M3': 'mi', 619 | 'A3': 'mai', 620 | 'dd4': 'faw', 621 | 'd4': 'fe', 622 | 'P4': 'fa', 623 | 'A4': 'fi', 624 | 'AA4': 'fai', 625 | 'dd5': 'saw', 626 | 'd5': 'se', 627 | 'P5': 'so', 628 | 'A5': 'si', 629 | 'AA5': 'sai', 630 | 'd6': 'law', 631 | 'm6': 'le', 632 | 'M6': 'la', 633 | 'A6': 'li', 634 | 'AA6': 'lai', 635 | 'd7': 'taw', 636 | 'm7': 'te', 637 | 'M7': 'ti', 638 | 'A7': 'tai', 639 | 'dd8': 'daw', 640 | 'd8': 'de', 641 | 'P8': 'do', 642 | 'A8': 'di', 643 | 'AA8': 'dai' 644 | } 645 | }; 646 | 647 | },{}],5:[function(require,module,exports){ 648 | var scientific = require('scientific-notation'); 649 | var helmholtz = require('helmholtz'); 650 | var pitchFq = require('pitch-fq'); 651 | var knowledge = require('./knowledge'); 652 | var vector = require('./vector'); 653 | var Interval = require('./interval'); 654 | 655 | function pad(str, ch, len) { 656 | for (; len > 0; len--) { 657 | str += ch; 658 | } 659 | 660 | return str; 661 | } 662 | 663 | 664 | function Note(coord, duration) { 665 | if (!(this instanceof Note)) return new Note(coord, duration); 666 | duration = duration || {}; 667 | 668 | this.duration = { value: duration.value || 4, dots: duration.dots || 0 }; 669 | this.coord = coord; 670 | } 671 | 672 | Note.prototype = { 673 | octave: function() { 674 | return this.coord[0] + knowledge.A4[0] - knowledge.notes[this.name()][0] + 675 | this.accidentalValue() * 4; 676 | }, 677 | 678 | name: function() { 679 | var value = this.accidentalValue(); 680 | var idx = this.coord[1] + knowledge.A4[1] - value * 7 + 1; 681 | return knowledge.fifths[idx]; 682 | }, 683 | 684 | accidentalValue: function() { 685 | return Math.round((this.coord[1] + knowledge.A4[1] - 2) / 7); 686 | }, 687 | 688 | accidental: function() { 689 | return knowledge.accidentals[this.accidentalValue() + 2]; 690 | }, 691 | 692 | /** 693 | * Returns the key number of the note 694 | */ 695 | key: function(white) { 696 | if (white) 697 | return this.coord[0] * 7 + this.coord[1] * 4 + 29; 698 | else 699 | return this.coord[0] * 12 + this.coord[1] * 7 + 49; 700 | }, 701 | 702 | /** 703 | * Returns a number ranging from 0-127 representing a MIDI note value 704 | */ 705 | midi: function() { 706 | return this.key() + 20; 707 | }, 708 | 709 | /** 710 | * Calculates and returns the frequency of the note. 711 | * Optional concert pitch (def. 440) 712 | */ 713 | fq: function(concertPitch) { 714 | return pitchFq(this.coord, concertPitch); 715 | }, 716 | 717 | /** 718 | * Returns the pitch class index (chroma) of the note 719 | */ 720 | chroma: function() { 721 | var value = (vector.sum(vector.mul(this.coord, [12, 7])) - 3) % 12; 722 | 723 | return (value < 0) ? value + 12 : value; 724 | }, 725 | 726 | interval: function(interval) { 727 | if (typeof interval === 'string') interval = Interval.toCoord(interval); 728 | 729 | if (interval instanceof Interval) 730 | return new Note(vector.add(this.coord, interval.coord), this.duration); 731 | else if (interval instanceof Note) 732 | return new Interval(vector.sub(interval.coord, this.coord)); 733 | }, 734 | 735 | transpose: function(interval) { 736 | this.coord = vector.add(this.coord, interval.coord); 737 | return this; 738 | }, 739 | 740 | /** 741 | * Returns the Helmholtz notation form of the note (fx C,, d' F# g#'') 742 | */ 743 | helmholtz: function() { 744 | var octave = this.octave(); 745 | var name = this.name(); 746 | name = octave < 3 ? name.toUpperCase() : name.toLowerCase(); 747 | var padchar = octave < 3 ? ',' : '\''; 748 | var padcount = octave < 2 ? 2 - octave : octave - 3; 749 | 750 | return pad(name + this.accidental(), padchar, padcount); 751 | }, 752 | 753 | /** 754 | * Returns the scientific notation form of the note (fx E4, Bb3, C#7 etc.) 755 | */ 756 | scientific: function() { 757 | return this.name().toUpperCase() + this.accidental() + this.octave(); 758 | }, 759 | 760 | /** 761 | * Returns notes that are enharmonic with this note. 762 | */ 763 | enharmonics: function(oneaccidental) { 764 | var key = this.key(), limit = oneaccidental ? 2 : 3; 765 | 766 | return ['m3', 'm2', 'm-2', 'm-3'] 767 | .map(this.interval.bind(this)) 768 | .filter(function(note) { 769 | var acc = note.accidentalValue(); 770 | var diff = key - (note.key() - acc); 771 | 772 | if (diff < limit && diff > -limit) { 773 | var product = vector.mul(knowledge.sharp, diff - acc); 774 | note.coord = vector.add(note.coord, product); 775 | return true; 776 | } 777 | }); 778 | }, 779 | 780 | solfege: function(scale, showOctaves) { 781 | var interval = scale.tonic.interval(this), solfege, stroke, count; 782 | if (interval.direction() === 'down') 783 | interval = interval.invert(); 784 | 785 | if (showOctaves) { 786 | count = (this.key(true) - scale.tonic.key(true)) / 7; 787 | count = (count >= 0) ? Math.floor(count) : -(Math.ceil(-count)); 788 | stroke = (count >= 0) ? '\'' : ','; 789 | } 790 | 791 | solfege = knowledge.intervalSolfege[interval.simple(true).toString()]; 792 | return (showOctaves) ? pad(solfege, stroke, Math.abs(count)) : solfege; 793 | }, 794 | 795 | scaleDegree: function(scale) { 796 | var inter = scale.tonic.interval(this); 797 | 798 | // If the direction is down, or we're dealing with an octave - invert it 799 | if (inter.direction() === 'down' || 800 | (inter.coord[1] === 0 && inter.coord[0] !== 0)) { 801 | inter = inter.invert(); 802 | } 803 | 804 | inter = inter.simple(true).coord; 805 | 806 | return scale.scale.reduce(function(index, current, i) { 807 | var coord = Interval.toCoord(current).coord; 808 | return coord[0] === inter[0] && coord[1] === inter[1] ? i + 1 : index; 809 | }, 0); 810 | }, 811 | 812 | /** 813 | * Returns the name of the duration value, 814 | * such as 'whole', 'quarter', 'sixteenth' etc. 815 | */ 816 | durationName: function() { 817 | return knowledge.durations[this.duration.value]; 818 | }, 819 | 820 | /** 821 | * Returns the duration of the note (including dots) 822 | * in seconds. The first argument is the tempo in beats 823 | * per minute, the second is the beat unit (i.e. the 824 | * lower numeral in a time signature). 825 | */ 826 | durationInSeconds: function(bpm, beatUnit) { 827 | var secs = (60 / bpm) / (this.duration.value / 4) / (beatUnit / 4); 828 | return secs * 2 - secs / Math.pow(2, this.duration.dots); 829 | }, 830 | 831 | /** 832 | * Returns the name of the note, with an optional display of octave number 833 | */ 834 | toString: function(dont) { 835 | return this.name() + this.accidental() + (dont ? '' : this.octave()); 836 | } 837 | }; 838 | 839 | Note.fromString = function(name, dur) { 840 | var coord = scientific(name); 841 | if (!coord) coord = helmholtz(name); 842 | return new Note(coord, dur); 843 | }; 844 | 845 | Note.fromKey = function(key) { 846 | var octave = Math.floor((key - 4) / 12); 847 | var distance = key - (octave * 12) - 4; 848 | var name = knowledge.fifths[(2 * Math.round(distance / 2) + 1) % 7]; 849 | var subDiff = vector.sub(knowledge.notes[name], knowledge.A4); 850 | var note = vector.add(subDiff, [octave + 1, 0]); 851 | var diff = (key - 49) - vector.sum(vector.mul(note, [12, 7])); 852 | 853 | var arg = diff ? vector.add(note, vector.mul(knowledge.sharp, diff)) : note; 854 | return new Note(arg); 855 | }; 856 | 857 | Note.fromFrequency = function(fq, concertPitch) { 858 | var key, cents, originalFq; 859 | concertPitch = concertPitch || 440; 860 | 861 | key = 49 + 12 * ((Math.log(fq) - Math.log(concertPitch)) / Math.log(2)); 862 | key = Math.round(key); 863 | originalFq = concertPitch * Math.pow(2, (key - 49) / 12); 864 | cents = 1200 * (Math.log(fq / originalFq) / Math.log(2)); 865 | 866 | return { note: Note.fromKey(key), cents: cents }; 867 | }; 868 | 869 | Note.fromMIDI = function(note) { 870 | return Note.fromKey(note - 20); 871 | }; 872 | 873 | module.exports = Note; 874 | 875 | },{"./interval":3,"./knowledge":4,"./vector":8,"helmholtz":11,"pitch-fq":14,"scientific-notation":15}],6:[function(require,module,exports){ 876 | var knowledge = require('./knowledge'); 877 | var Interval = require('./interval'); 878 | 879 | var scales = { 880 | aeolian: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'm7'], 881 | blues: ['P1', 'm3', 'P4', 'd5', 'P5', 'm7'], 882 | chromatic: ['P1', 'm2', 'M2', 'm3', 'M3', 'P4', 883 | 'A4', 'P5', 'm6', 'M6', 'm7', 'M7'], 884 | dorian: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'm7'], 885 | doubleharmonic: ['P1', 'm2', 'M3', 'P4', 'P5', 'm6', 'M7'], 886 | harmonicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'M7'], 887 | ionian: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'M7'], 888 | locrian: ['P1', 'm2', 'm3', 'P4', 'd5', 'm6', 'm7'], 889 | lydian: ['P1', 'M2', 'M3', 'A4', 'P5', 'M6', 'M7'], 890 | majorpentatonic: ['P1', 'M2', 'M3', 'P5', 'M6'], 891 | melodicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'M7'], 892 | minorpentatonic: ['P1', 'm3', 'P4', 'P5', 'm7'], 893 | mixolydian: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'm7'], 894 | phrygian: ['P1', 'm2', 'm3', 'P4', 'P5', 'm6', 'm7'], 895 | wholetone: ['P1', 'M2', 'M3', 'A4', 'A5', 'A6'] 896 | }; 897 | 898 | // synonyms 899 | scales.harmonicchromatic = scales.chromatic; 900 | scales.minor = scales.aeolian; 901 | scales.major = scales.ionian; 902 | scales.flamenco = scales.doubleharmonic; 903 | 904 | function Scale(tonic, scale) { 905 | if (!(this instanceof Scale)) return new Scale(tonic, scale); 906 | var scaleName, i; 907 | if (!('coord' in tonic)) { 908 | throw new Error('Invalid Tonic'); 909 | } 910 | 911 | if (typeof scale === 'string') { 912 | scaleName = scale; 913 | scale = scales[scale]; 914 | if (!scale) 915 | throw new Error('Invalid Scale'); 916 | } else { 917 | for (i in scales) { 918 | if (scales.hasOwnProperty(i)) { 919 | if (scales[i].toString() === scale.toString()) { 920 | scaleName = i; 921 | break; 922 | } 923 | } 924 | } 925 | } 926 | 927 | this.name = scaleName; 928 | this.tonic = tonic; 929 | this.scale = scale; 930 | } 931 | 932 | Scale.prototype = { 933 | notes: function() { 934 | var notes = []; 935 | 936 | for (var i = 0, length = this.scale.length; i < length; i++) { 937 | notes.push(this.tonic.interval(this.scale[i])); 938 | } 939 | 940 | return notes; 941 | }, 942 | 943 | simple: function() { 944 | return this.notes().map(function(n) { return n.toString(true); }); 945 | }, 946 | 947 | type: function() { 948 | var length = this.scale.length - 2; 949 | if (length < 8) { 950 | return ['di', 'tri', 'tetra', 'penta', 'hexa', 'hepta', 'octa'][length] + 951 | 'tonic'; 952 | } 953 | }, 954 | 955 | get: function(i) { 956 | var isStepStr = typeof i === 'string' && i in knowledge.stepNumber; 957 | i = isStepStr ? knowledge.stepNumber[i] : i; 958 | var len = this.scale.length; 959 | var interval, octaves; 960 | 961 | if (i < 0) { 962 | interval = this.scale[i % len + len - 1]; 963 | octaves = Math.floor((i - 1) / len); 964 | } else if (i % len === 0) { 965 | interval = this.scale[len - 1]; 966 | octaves = (i / len) - 1; 967 | } else { 968 | interval = this.scale[i % len - 1]; 969 | octaves = Math.floor(i / len); 970 | } 971 | 972 | return this.tonic.interval(interval).interval(new Interval([octaves, 0])); 973 | }, 974 | 975 | solfege: function(index, showOctaves) { 976 | if (index) 977 | return this.get(index).solfege(this, showOctaves); 978 | 979 | return this.notes().map(function(n) { 980 | return n.solfege(this, showOctaves); 981 | }); 982 | }, 983 | 984 | interval: function(interval) { 985 | interval = (typeof interval === 'string') ? 986 | Interval.toCoord(interval) : interval; 987 | return new Scale(this.tonic.interval(interval), this.scale); 988 | }, 989 | 990 | transpose: function(interval) { 991 | var scale = this.interval(interval); 992 | this.scale = scale.scale; 993 | this.tonic = scale.tonic; 994 | 995 | return this; 996 | } 997 | }; 998 | Scale.KNOWN_SCALES = Object.keys(scales); 999 | 1000 | module.exports = Scale; 1001 | 1002 | },{"./interval":3,"./knowledge":4}],7:[function(require,module,exports){ 1003 | var knowledge = require('./knowledge'); 1004 | 1005 | module.exports = function(teoria) { 1006 | var Note = teoria.Note; 1007 | var Chord = teoria.Chord; 1008 | var Scale = teoria.Scale; 1009 | 1010 | Note.prototype.chord = function(chord) { 1011 | var isShortChord = chord in knowledge.chordShort; 1012 | chord = isShortChord ? knowledge.chordShort[chord] : chord; 1013 | 1014 | return new Chord(this, chord); 1015 | }; 1016 | 1017 | Note.prototype.scale = function(scale) { 1018 | return new Scale(this, scale); 1019 | }; 1020 | }; 1021 | 1022 | },{"./knowledge":4}],8:[function(require,module,exports){ 1023 | module.exports = { 1024 | add: function(note, interval) { 1025 | return [note[0] + interval[0], note[1] + interval[1]]; 1026 | }, 1027 | 1028 | sub: function(note, interval) { 1029 | return [note[0] - interval[0], note[1] - interval[1]]; 1030 | }, 1031 | 1032 | mul: function(note, interval) { 1033 | if (typeof interval === 'number') 1034 | return [note[0] * interval, note[1] * interval]; 1035 | else 1036 | return [note[0] * interval[0], note[1] * interval[1]]; 1037 | }, 1038 | 1039 | sum: function(coord) { 1040 | return coord[0] + coord[1]; 1041 | } 1042 | }; 1043 | 1044 | },{}],9:[function(require,module,exports){ 1045 | var accidentalValues = { 1046 | 'bb': -2, 1047 | 'b': -1, 1048 | '': 0, 1049 | '#': 1, 1050 | 'x': 2 1051 | }; 1052 | 1053 | module.exports = function accidentalNumber(acc) { 1054 | return accidentalValues[acc]; 1055 | } 1056 | 1057 | module.exports.interval = function accidentalInterval(acc) { 1058 | var val = accidentalValues[acc]; 1059 | return [-4 * val, 7 * val]; 1060 | } 1061 | 1062 | },{}],10:[function(require,module,exports){ 1063 | var SYMBOLS = { 1064 | 'm': ['m3', 'P5'], 1065 | 'mi': ['m3', 'P5'], 1066 | 'min': ['m3', 'P5'], 1067 | '-': ['m3', 'P5'], 1068 | 1069 | 'M': ['M3', 'P5'], 1070 | 'ma': ['M3', 'P5'], 1071 | '': ['M3', 'P5'], 1072 | 1073 | '+': ['M3', 'A5'], 1074 | 'aug': ['M3', 'A5'], 1075 | 1076 | 'dim': ['m3', 'd5'], 1077 | 'o': ['m3', 'd5'], 1078 | 1079 | 'maj': ['M3', 'P5', 'M7'], 1080 | 'dom': ['M3', 'P5', 'm7'], 1081 | 'ø': ['m3', 'd5', 'm7'], 1082 | 1083 | '5': ['P5'], 1084 | 1085 | '6/9': ['M3', 'P5', 'M6', 'M9'] 1086 | }; 1087 | 1088 | module.exports = function(symbol) { 1089 | var c, parsing = 'quality', additionals = [], name, chordLength = 2 1090 | var notes = ['P1', 'M3', 'P5', 'm7', 'M9', 'P11', 'M13']; 1091 | var explicitMajor = false; 1092 | 1093 | function setChord(name) { 1094 | var intervals = SYMBOLS[name]; 1095 | for (var i = 0, len = intervals.length; i < len; i++) { 1096 | notes[i + 1] = intervals[i]; 1097 | } 1098 | 1099 | chordLength = intervals.length; 1100 | } 1101 | 1102 | // Remove whitespace, commas and parentheses 1103 | symbol = symbol.replace(/[,\s\(\)]/g, ''); 1104 | for (var i = 0, len = symbol.length; i < len; i++) { 1105 | if (!(c = symbol[i])) 1106 | return; 1107 | 1108 | if (parsing === 'quality') { 1109 | var sub3 = (i + 2) < len ? symbol.substr(i, 3).toLowerCase() : null; 1110 | var sub2 = (i + 1) < len ? symbol.substr(i, 2).toLowerCase() : null; 1111 | if (sub3 in SYMBOLS) 1112 | name = sub3; 1113 | else if (sub2 in SYMBOLS) 1114 | name = sub2; 1115 | else if (c in SYMBOLS) 1116 | name = c; 1117 | else 1118 | name = ''; 1119 | 1120 | if (name) 1121 | setChord(name); 1122 | 1123 | if (name === 'M' || name === 'ma' || name === 'maj') 1124 | explicitMajor = true; 1125 | 1126 | 1127 | i += name.length - 1; 1128 | parsing = 'extension'; 1129 | } else if (parsing === 'extension') { 1130 | c = (c === '1' && symbol[i + 1]) ? +symbol.substr(i, 2) : +c; 1131 | 1132 | if (!isNaN(c) && c !== 6) { 1133 | chordLength = (c - 1) / 2; 1134 | 1135 | if (chordLength !== Math.round(chordLength)) 1136 | return new Error('Invalid interval extension: ' + c.toString(10)); 1137 | 1138 | if (name === 'o' || name === 'dim') 1139 | notes[3] = 'd7'; 1140 | else if (explicitMajor) 1141 | notes[3] = 'M7'; 1142 | 1143 | i += c >= 10 ? 1 : 0; 1144 | } else if (c === 6) { 1145 | notes[3] = 'M6'; 1146 | chordLength = Math.max(3, chordLength); 1147 | } else 1148 | i -= 1; 1149 | 1150 | parsing = 'alterations'; 1151 | } else if (parsing === 'alterations') { 1152 | var alterations = symbol.substr(i).split(/(#|b|add|maj|sus|M)/i), 1153 | next, flat = false, sharp = false; 1154 | 1155 | if (alterations.length === 1) 1156 | return new Error('Invalid alteration'); 1157 | else if (alterations[0].length !== 0) 1158 | return new Error('Invalid token: \'' + alterations[0] + '\''); 1159 | 1160 | var ignore = false; 1161 | alterations.forEach(function(alt, i, arr) { 1162 | if (ignore || !alt.length) 1163 | return ignore = false; 1164 | 1165 | var next = arr[i + 1], lower = alt.toLowerCase(); 1166 | if (alt === 'M' || lower === 'maj') { 1167 | if (next === '7') 1168 | ignore = true; 1169 | 1170 | chordLength = Math.max(3, chordLength); 1171 | notes[3] = 'M7'; 1172 | } else if (lower === 'sus') { 1173 | var type = 'P4'; 1174 | if (next === '2' || next === '4') { 1175 | ignore = true; 1176 | 1177 | if (next === '2') 1178 | type = 'M2'; 1179 | } 1180 | 1181 | notes[1] = type; // Replace third with M2 or P4 1182 | } else if (lower === 'add') { 1183 | if (next === '9') 1184 | additionals.push('M9'); 1185 | else if (next === '11') 1186 | additionals.push('P11'); 1187 | else if (next === '13') 1188 | additionals.push('M13'); 1189 | 1190 | ignore = true 1191 | } else if (lower === 'b') { 1192 | flat = true; 1193 | } else if (lower === '#') { 1194 | sharp = true; 1195 | } else { 1196 | var token = +alt, quality, intPos; 1197 | if (isNaN(token) || String(token).length !== alt.length) 1198 | return new Error('Invalid token: \'' + alt + '\''); 1199 | 1200 | if (token === 6) { 1201 | if (sharp) 1202 | notes[3] = 'A6'; 1203 | else if (flat) 1204 | notes[3] = 'm6'; 1205 | else 1206 | notes[3] = 'M6'; 1207 | 1208 | chordLength = Math.max(3, chordLength); 1209 | return; 1210 | } 1211 | 1212 | // Calculate the position in the 'note' array 1213 | intPos = (token - 1) / 2; 1214 | if (chordLength < intPos) 1215 | chordLength = intPos; 1216 | 1217 | if (token < 5 || token === 7 || intPos !== Math.round(intPos)) 1218 | return new Error('Invalid interval alteration: ' + token); 1219 | 1220 | quality = notes[intPos][0]; 1221 | 1222 | // Alterate the quality of the interval according the accidentals 1223 | if (sharp) { 1224 | if (quality === 'd') 1225 | quality = 'm'; 1226 | else if (quality === 'm') 1227 | quality = 'M'; 1228 | else if (quality === 'M' || quality === 'P') 1229 | quality = 'A'; 1230 | } else if (flat) { 1231 | if (quality === 'A') 1232 | quality = 'M'; 1233 | else if (quality === 'M') 1234 | quality = 'm'; 1235 | else if (quality === 'm' || quality === 'P') 1236 | quality = 'd'; 1237 | } 1238 | 1239 | sharp = flat = false; 1240 | notes[intPos] = quality + token; 1241 | } 1242 | }); 1243 | parsing = 'ended'; 1244 | } else if (parsing === 'ended') { 1245 | break; 1246 | } 1247 | } 1248 | 1249 | return notes.slice(0, chordLength + 1).concat(additionals); 1250 | } 1251 | 1252 | },{}],11:[function(require,module,exports){ 1253 | var coords = require('notecoord'); 1254 | var accval = require('accidental-value'); 1255 | 1256 | module.exports = function helmholtz(name) { 1257 | var name = name.replace(/\u2032/g, "'").replace(/\u0375/g, ','); 1258 | var parts = name.match(/^(,*)([a-h])(x|#|bb|b?)([,\']*)$/i); 1259 | 1260 | if (!parts || name !== parts[0]) 1261 | throw new Error('Invalid formatting'); 1262 | 1263 | var note = parts[2]; 1264 | var octaveFirst = parts[1]; 1265 | var octaveLast = parts[4]; 1266 | var lower = note === note.toLowerCase(); 1267 | var octave; 1268 | 1269 | if (octaveFirst) { 1270 | if (lower) 1271 | throw new Error('Invalid formatting - found commas before lowercase note'); 1272 | 1273 | octave = 2 - octaveFirst.length; 1274 | } else if (octaveLast) { 1275 | if (octaveLast.match(/^'+$/) && lower) 1276 | octave = 3 + octaveLast.length; 1277 | else if (octaveLast.match(/^,+$/) && !lower) 1278 | octave = 2 - octaveLast.length; 1279 | else 1280 | throw new Error('Invalid formatting - mismatch between octave ' + 1281 | 'indicator and letter case') 1282 | } else 1283 | octave = lower ? 3 : 2; 1284 | 1285 | var accidentalValue = accval.interval(parts[3].toLowerCase()); 1286 | var coord = coords(note.toLowerCase()); 1287 | 1288 | coord[0] += octave; 1289 | coord[0] += accidentalValue[0] - coords.A4[0]; 1290 | coord[1] += accidentalValue[1] - coords.A4[1]; 1291 | 1292 | return coord; 1293 | }; 1294 | 1295 | },{"accidental-value":9,"notecoord":13}],12:[function(require,module,exports){ 1296 | var pattern = /^(AA|A|P|M|m|d|dd)(-?\d+)$/; 1297 | 1298 | // The interval it takes to raise a note a semitone 1299 | var sharp = [-4, 7]; 1300 | 1301 | var pAlts = ['dd', 'd', 'P', 'A', 'AA']; 1302 | var mAlts = ['dd', 'd', 'm', 'M', 'A', 'AA']; 1303 | 1304 | var baseIntervals = [ 1305 | [0, 0], 1306 | [3, -5], 1307 | [2, -3], 1308 | [1, -1], 1309 | [0, 1], 1310 | [3, -4], 1311 | [2, -2], 1312 | [1, 0] 1313 | ]; 1314 | 1315 | module.exports = function(simple) { 1316 | var parser = simple.match(pattern); 1317 | if (!parser) return null; 1318 | 1319 | var quality = parser[1]; 1320 | var number = +parser[2]; 1321 | var sign = number < 0 ? -1 : 1; 1322 | 1323 | number = sign < 0 ? -number : number; 1324 | 1325 | var lower = number > 8 ? (number % 7 || 7) : number; 1326 | var octaves = (number - lower) / 7; 1327 | 1328 | var base = baseIntervals[lower - 1]; 1329 | var alts = base[0] <= 1 ? pAlts : mAlts; 1330 | var alt = alts.indexOf(quality) - 2; 1331 | 1332 | // this happens, if the alteration wasn't suitable for this type 1333 | // of interval, such as P2 or M5 (no "perfect second" or "major fifth") 1334 | if (alt === -3) return null; 1335 | 1336 | return [ 1337 | sign * (base[0] + octaves + sharp[0] * alt), 1338 | sign * (base[1] + sharp[1] * alt) 1339 | ]; 1340 | } 1341 | 1342 | // Copy to avoid overwriting internal base intervals 1343 | module.exports.coords = baseIntervals.slice(0); 1344 | 1345 | },{}],13:[function(require,module,exports){ 1346 | // First coord is octaves, second is fifths. Distances are relative to c 1347 | var notes = { 1348 | c: [0, 0], 1349 | d: [-1, 2], 1350 | e: [-2, 4], 1351 | f: [1, -1], 1352 | g: [0, 1], 1353 | a: [-1, 3], 1354 | b: [-2, 5], 1355 | h: [-2, 5] 1356 | }; 1357 | 1358 | module.exports = function(name) { 1359 | return name in notes ? [notes[name][0], notes[name][1]] : null; 1360 | }; 1361 | 1362 | module.exports.notes = notes; 1363 | module.exports.A4 = [3, 3]; // Relative to C0 (scientic notation, ~16.35Hz) 1364 | module.exports.sharp = [-4, 7]; 1365 | 1366 | },{}],14:[function(require,module,exports){ 1367 | module.exports = function(coord, stdPitch) { 1368 | if (typeof coord === 'number') { 1369 | stdPitch = coord; 1370 | return function(coord) { 1371 | return stdPitch * Math.pow(2, (coord[0] * 12 + coord[1] * 7) / 12); 1372 | } 1373 | } 1374 | 1375 | stdPitch = stdPitch || 440; 1376 | return stdPitch * Math.pow(2, (coord[0] * 12 + coord[1] * 7) / 12); 1377 | } 1378 | 1379 | },{}],15:[function(require,module,exports){ 1380 | var coords = require('notecoord'); 1381 | var accval = require('accidental-value'); 1382 | 1383 | module.exports = function scientific(name) { 1384 | var format = /^([a-h])(x|#|bb|b?)(-?\d*)/i; 1385 | 1386 | var parser = name.match(format); 1387 | if (!(parser && name === parser[0] && parser[3].length)) return; 1388 | 1389 | var noteName = parser[1]; 1390 | var octave = +parser[3]; 1391 | var accidental = parser[2].length ? parser[2].toLowerCase() : ''; 1392 | 1393 | var accidentalValue = accval.interval(accidental); 1394 | var coord = coords(noteName.toLowerCase()); 1395 | 1396 | coord[0] += octave; 1397 | coord[0] += accidentalValue[0] - coords.A4[0]; 1398 | coord[1] += accidentalValue[1] - coords.A4[1]; 1399 | 1400 | return coord; 1401 | }; 1402 | 1403 | },{"accidental-value":9,"notecoord":13}]},{},[1])(1) 1404 | }); -------------------------------------------------------------------------------- /test/chords.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'), 2 | assert = require('assert'), 3 | teoria = require('../'); 4 | 5 | vows.describe('Chords').addBatch({ 6 | 'Chord parser': { 7 | 'Emaj7': function() { 8 | assert.deepEqual(teoria.chord('Emaj7').simple(), ['e', 'g#', 'b', 'd#']); 9 | }, 10 | 11 | 'A+': function() { 12 | assert.deepEqual(teoria.chord('A+').simple(), ['a', 'c#', 'e#']); 13 | }, 14 | 15 | 'Bb+': function() { 16 | assert.deepEqual(teoria.chord('Bb+').simple(), ['bb', 'd', 'f#']); 17 | }, 18 | 19 | 'F#maj7': function() { 20 | assert.deepEqual(teoria.chord('F#maj7').simple(), ['f#', 'a#', 'c#', 'e#']); 21 | }, 22 | 23 | 'Hmaj7': function() { 24 | assert.deepEqual(teoria.chord('Hmaj7').simple(), ['b', 'd#', 'f#', 'a#']); 25 | }, 26 | 27 | 'H#maj7': function() { 28 | assert.deepEqual(teoria.chord('H#maj7').simple(), ['b#', 'dx', 'fx', 'ax']); 29 | }, 30 | 31 | 'C7b5': function() { 32 | assert.deepEqual(teoria.chord('C7b5').simple(), ['c', 'e', 'gb', 'bb']); 33 | }, 34 | 35 | 'Eb7b5': function() { 36 | assert.deepEqual(teoria.chord('Eb7b5').simple(), ['eb', 'g', 'bbb', 'db']); 37 | }, 38 | 39 | 'D#7b5': function() { 40 | assert.deepEqual(teoria.chord('D#7b5').simple(), ['d#', 'fx', 'a', 'c#']); 41 | }, 42 | 43 | 'C9': function() { 44 | assert.deepEqual(teoria.chord('C9').simple(), ['c', 'e', 'g', 'bb', 'd']); 45 | }, 46 | 47 | 'Eb9': function() { 48 | assert.deepEqual(teoria.chord('Eb9').simple(), ['eb', 'g', 'bb', 'db', 'f']); 49 | }, 50 | 51 | 'G#(#9)': function() { 52 | assert.deepEqual(teoria.chord('G#(#9)').simple(), ['g#', 'b#', 'd#', 'f#', 'ax']); 53 | }, 54 | 55 | 'Ab(b9)': function() { 56 | assert.deepEqual(teoria.chord('Ab(b9)').simple(), ['ab', 'c', 'eb', 'gb', 'bbb']); 57 | }, 58 | 59 | 'F#(#11)': function() { 60 | assert.deepEqual(teoria.chord('F#(#11)').simple(), ['f#', 'a#', 'c#', 'e', 'g#', 'b#']); 61 | }, 62 | 63 | 'Ab13': function() { 64 | assert.deepEqual(teoria.chord('Ab13').simple(), ['ab', 'c', 'eb', 'gb', 'bb', 'db', 'f']); 65 | }, 66 | 67 | 'C7sus4': function() { 68 | assert.deepEqual(teoria.chord('C7sus4').simple(), ['c', 'f', 'g', 'bb']); 69 | }, 70 | 71 | 'Cmaj9': function() { 72 | assert.deepEqual(teoria.chord('Cmaj9').simple(), ['c', 'e', 'g', 'b', 'd']); 73 | }, 74 | 75 | 'Dmb6': function() { 76 | assert.deepEqual(teoria.chord('Dmb6').simple(), ['d', 'f', 'a', 'bb']); 77 | }, 78 | 79 | 'C#(#5#9)': function() { 80 | assert.deepEqual(teoria.chord('C#(#5#9)').simple(), ['c#', 'e#', 'gx', 'b', 'dx']); 81 | }, 82 | 83 | 'Cm(maj7)': function() { 84 | assert.deepEqual(teoria.chord('Cm(maj7)').simple(), ['c', 'eb', 'g', 'b']); 85 | }, 86 | 87 | 'F#m(11b5b9)': function() { 88 | assert.deepEqual(teoria.chord('F#m(11b5b9)').simple(), ['f#', 'a', 'c', 'e', 'g', 'b']); 89 | }, 90 | 91 | 'C/e': function() { 92 | assert.deepEqual(teoria.chord('C/e').simple(), ['e', 'c', 'g']); 93 | }, 94 | 95 | 'A7/g': function() { 96 | assert.deepEqual(teoria.chord('A7/g').simple(), ['g', 'a', 'c#', 'e']); 97 | }, 98 | 99 | 'G/f#': function() { 100 | assert.deepEqual(teoria.chord('G/f#').simple(), ['f#', 'g', 'b', 'd']); 101 | }, 102 | 103 | 'C6': function() { 104 | assert.deepEqual(teoria.chord('C6').simple(), ['c', 'e', 'g', 'a']); 105 | }, 106 | 107 | 'A#6': function() { 108 | assert.deepEqual(teoria.chord('A#6').simple(), ['a#', 'cx', 'e#', 'fx']); 109 | }, 110 | 111 | 'Bb6': function() { 112 | assert.deepEqual(teoria.chord('Bb6').simple(), ['bb', 'd', 'f', 'g']); 113 | }, 114 | 115 | 'Am6': function() { 116 | assert.deepEqual(teoria.chord('Am6').simple(), ['a', 'c', 'e', 'f#']); 117 | }, 118 | 119 | 'D(#6)': function() { 120 | assert.deepEqual(teoria.chord('D(#6)').simple(), ['d', 'f#', 'a', 'b#']); 121 | }, 122 | 123 | 'Eo': function() { 124 | assert.deepEqual(teoria.chord('Eo').simple(), ['e', 'g', 'bb']); 125 | }, 126 | 127 | 'Eø': function() { 128 | assert.deepEqual(teoria.chord('Eø').simple(), ['e', 'g', 'bb', 'd']); 129 | }, 130 | 131 | 'Do': function() { 132 | assert.deepEqual(teoria.chord('Do').simple(), ['d', 'f', 'ab']); 133 | }, 134 | 135 | 'Dø': function() { 136 | assert.deepEqual(teoria.chord('Dø').simple(), ['d', 'f', 'ab', 'c']); 137 | }, 138 | 139 | 'Fo7': function() { 140 | assert.deepEqual(teoria.chord('Fo7').simple(), ['f', 'ab', 'cb', 'ebb']); 141 | }, 142 | 143 | 'G#ø7': function() { 144 | assert.deepEqual(teoria.chord('G#ø7').simple(), ['g#', 'b', 'd', 'f#']); 145 | }, 146 | 147 | 'Cmin': function() { 148 | assert.deepEqual(teoria.chord('Cmin').simple(), ['c', 'eb', 'g']); 149 | }, 150 | 151 | 'Bmin11': function() { 152 | assert.deepEqual(teoria.chord('Bmin11').simple(), ['b', 'd', 'f#', 'a', 'c#', 'e']); 153 | }, 154 | 155 | 'C+M7': function() { 156 | assert.deepEqual(teoria.chord('C+M7').simple(), ['c', 'e', 'g#', 'b']); 157 | }, 158 | 159 | 'Bbdom7b5': function() { 160 | assert.deepEqual(teoria.chord('Bbdom7b5').simple(), ['bb', 'd', 'fb', 'ab']); 161 | }, 162 | 163 | 'E5': function() { 164 | assert.deepEqual(teoria.chord('E5').simple(), ['e', 'b']); 165 | }, 166 | 167 | 'A5': function() { 168 | assert.deepEqual(teoria.chord('A5').simple(), ['a', 'e']); 169 | }, 170 | 171 | 'C13#9b5': function() { 172 | assert.deepEqual(teoria.chord('C13#9b5').simple(), ['c', 'e', 'gb', 'bb', 'd#', 'f', 'a']); 173 | }, 174 | 175 | 'D13#5b9': function() { 176 | assert.deepEqual(teoria.chord('D13#5b9').simple(), ['d', 'f#', 'a#', 'c', 'eb', 'g', 'b']); 177 | }, 178 | 179 | 'C6/9': function() { 180 | assert.deepEqual(teoria.chord('C6/9').simple(), ['c', 'e', 'g', 'a', 'd']); 181 | }, 182 | 183 | 'Ab6/9': function() { 184 | assert.deepEqual(teoria.chord('Ab6/9').simple(), ['ab', 'c', 'eb', 'f', 'bb']); 185 | }, 186 | 187 | 'CM7': function() { 188 | assert.deepEqual(teoria.chord('CM7').simple(), ['c', 'e', 'g', 'b']); 189 | }, 190 | 191 | 'CmM7': function() { 192 | assert.deepEqual(teoria.chord('CmM7').simple(), ['c', 'eb', 'g', 'b']); 193 | }, 194 | 195 | 'DM': function() { 196 | assert.deepEqual(teoria.chord('DM').simple(), ['d', 'f#', 'a']); 197 | }, 198 | 199 | 'EM#5': function() { 200 | assert.deepEqual(teoria.chord('EM#5').simple(), ['e', 'g#', 'b#']); 201 | }, 202 | 203 | 'FM9': function() { 204 | assert.deepEqual(teoria.chord('FM9').simple(), ['f', 'a', 'c', 'e', 'g']); 205 | }, 206 | 207 | 'Dmi': function() { 208 | assert.deepEqual(teoria.chord('Dmi').simple(), ['d', 'f', 'a']); 209 | 210 | }, 211 | 212 | 'Emi7': function() { 213 | assert.deepEqual(teoria.chord('Emi7').simple(), ['e', 'g', 'b', 'd']); 214 | }, 215 | 216 | 'Dma': function() { 217 | assert.deepEqual(teoria.chord('Dma').simple(), ['d', 'f#', 'a']); 218 | }, 219 | 220 | 'Ema9': function() { 221 | assert.deepEqual(teoria.chord('Ema9').simple(), ['e', 'g#', 'b', 'd#', 'f#']); 222 | } 223 | }, 224 | 225 | 'Case doesn\'t matter': { 226 | 'BbDom': function() { 227 | assert.deepEqual(teoria.chord('BbDom').simple(), teoria.chord('Bbdom').simple()); 228 | }, 229 | 230 | 'EbMaj9': function() { 231 | assert.deepEqual(teoria.chord('EbMaj9').simple(), teoria.chord('Ebmaj9').simple()); 232 | }, 233 | 234 | 'ASus4': function() { 235 | assert.deepEqual(teoria.chord('ASus4').simple(), teoria.chord('Asus4').simple()); 236 | }, 237 | 238 | 'EAdd9': function() { 239 | assert.deepEqual(teoria.chord('EAdd9').simple(), teoria.chord('Eadd9').simple()); 240 | } 241 | }, 242 | 243 | 'Chord Methods': { 244 | '#bass of Cmaj7': function() { 245 | assert.equal(teoria.chord('Cmaj7').bass().toString(true), 'c'); 246 | }, 247 | 248 | '#bass of A/C#': function() { 249 | assert.equal(teoria.chord('A/C#').bass().toString(true), 'c#'); 250 | }, 251 | 252 | '#bass of D6/9': function() { 253 | assert.equal(teoria.chord('D6/9').bass().toString(true), 'd'); 254 | }, 255 | 256 | '#quality() of Bmaj7': function() { 257 | assert.equal(teoria.chord('Bmaj7').quality(), 'major'); 258 | }, 259 | 260 | '#quality() of E7': function() { 261 | assert.equal(teoria.chord('E7').quality(), 'dominant'); 262 | }, 263 | 264 | '#quality() of Dbm7b5': function() { 265 | assert.equal(teoria.chord('Dbm7b5').quality(), 'half-diminished'); 266 | }, 267 | 268 | '#quality() of Cmin11': function() { 269 | assert.equal(teoria.chord('Cmin11').quality(), 'minor'); 270 | }, 271 | 272 | '#quality() of A+': function() { 273 | assert.equal(teoria.chord('A+').quality(), 'augmented'); 274 | }, 275 | 276 | '#quality() of A#(b13)': function() { 277 | assert.equal(teoria.chord('A#(b13)').quality(), 'dominant'); 278 | }, 279 | 280 | '#quality() of Gmb5': function() { 281 | assert.equal(teoria.chord('Gmb5').quality(), 'diminished'); 282 | }, 283 | 284 | '#quality() of Asus4': function() { 285 | assert.equal(teoria.chord('Asus4').quality(), undefined); 286 | }, 287 | 288 | '#quality() of Fm#5': function() { 289 | assert.equal(teoria.chord('Fm#5').quality(), 'minor'); 290 | }, 291 | 292 | '#chordType() of C': function() { 293 | assert.equal(teoria.chord('C').chordType(), 'triad'); 294 | }, 295 | 296 | '#chordType() of Dm': function() { 297 | assert.equal(teoria.chord('Dm').chordType(), 'triad'); 298 | }, 299 | 300 | '#chordType() of A7': function() { 301 | assert.equal(teoria.chord('A7').chordType(), 'tetrad'); 302 | }, 303 | 304 | '#chordType() of Bsus4': function() { 305 | assert.equal(teoria.chord('Bsus4').chordType(), 'trichord'); 306 | }, 307 | 308 | '#chordType() of E5': function() { 309 | assert.equal(teoria.chord('E5').chordType(), 'dyad'); 310 | }, 311 | } 312 | }).export(module); 313 | -------------------------------------------------------------------------------- /test/intervals.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'), 2 | assert = require('assert'), 3 | teoria = require('../'); 4 | 5 | function addSimple(interval1, interval2) { 6 | return teoria.interval(interval1).add(teoria.interval(interval2)); 7 | } 8 | 9 | vows.describe('Intervals').addBatch({ 10 | 'Relative Intervals': { 11 | topic: function() { 12 | return teoria.note('F#,'); 13 | }, 14 | 15 | 'Doubly diminished second': function(note) { 16 | assert.deepEqual(note.interval('dd2'), teoria.note('Gbb,')); 17 | }, 18 | 19 | 'Diminished second': function(note) { 20 | assert.deepEqual(note.interval('d2'), teoria.note('Gb,')); 21 | }, 22 | 23 | 'Diminished second, API method two': function(note) { 24 | assert.deepEqual(teoria.interval(note, teoria.interval('d2')), teoria.note('Gb,')); 25 | }, 26 | 27 | 'Diminished second, API method three': function(note) { 28 | assert.deepEqual(note.interval(teoria.interval('d2')), teoria.note('Gb,')); 29 | }, 30 | 31 | 'Minor second': function(note) { 32 | assert.deepEqual(note.interval('m2'), teoria.note('G,')); 33 | }, 34 | 35 | 'Major second': function(note) { 36 | assert.deepEqual(note.interval('M2'), teoria.note('G#,')); 37 | }, 38 | 39 | 'Augmented second': function(note) { 40 | assert.deepEqual(note.interval('A2'), teoria.note('Gx,')); 41 | }, 42 | 43 | 'Doubly diminished third': function(note) { 44 | assert.deepEqual(note.interval('dd3'), teoria.note('Abb,')); 45 | }, 46 | 47 | 'Diminished third': function(note) { 48 | assert.deepEqual(note.interval('d3'), teoria.note('Ab,')); 49 | }, 50 | 51 | 'Minor third': function(note) { 52 | assert.deepEqual(note.interval('m3'), teoria.note('A,')); 53 | }, 54 | 55 | 'Major third': function(note) { 56 | assert.deepEqual(note.interval('M3'), teoria.note('A#,')); 57 | }, 58 | 59 | 'Augmented third': function(note) { 60 | assert.deepEqual(note.interval('A3'), teoria.note('Ax,')); 61 | }, 62 | 63 | 'Doubly diminished fourth': function(note) { 64 | assert.deepEqual(note.interval('dd4'), teoria.note('Bbb,')); 65 | }, 66 | 67 | 'Diminished fourth': function(note) { 68 | assert.deepEqual(note.interval('d4'), teoria.note('Bb,')); 69 | }, 70 | 71 | 'Perfect fourth': function(note) { 72 | assert.deepEqual(note.interval('P4'), teoria.note('B,')); 73 | }, 74 | 75 | 'Augmented fourth': function(note) { 76 | assert.deepEqual(note.interval('A4'), teoria.note('B#,')); 77 | }, 78 | 79 | 'Doubly augmented fourth': function(note) { 80 | assert.deepEqual(note.interval('AA4'), teoria.note('Bx,')); 81 | }, 82 | 83 | 'Doubly diminished fifth': function(note) { 84 | assert.deepEqual(note.interval('dd5'), teoria.note('Cb')); 85 | }, 86 | 87 | 'Diminished fifth': function(note) { 88 | assert.deepEqual(note.interval('d5'), teoria.note('C')); 89 | }, 90 | 91 | 'Perfect fifth': function(note) { 92 | assert.deepEqual(note.interval('P5'), teoria.note('C#')); 93 | }, 94 | 95 | 'Augmented fifth': function(note) { 96 | assert.deepEqual(note.interval('A5'), teoria.note('Cx')); 97 | }, 98 | 99 | 'Doubly diminished sixth': function(note) { 100 | assert.deepEqual(note.interval('dd6'), teoria.note('Dbb')); 101 | }, 102 | 103 | 'Diminished sixth': function(note) { 104 | assert.deepEqual(note.interval('d6'), teoria.note('Db')); 105 | }, 106 | 107 | 'Minor sixth': function(note) { 108 | assert.deepEqual(note.interval('m6'), teoria.note('D')); 109 | }, 110 | 111 | 'Major sixth': function(note) { 112 | assert.deepEqual(note.interval('M6'), teoria.note('D#')); 113 | }, 114 | 115 | 'Augmented sixth': function(note) { 116 | assert.deepEqual(note.interval('A6'), teoria.note('Dx')); 117 | }, 118 | 119 | 'Doubly diminished seventh': function(note) { 120 | assert.deepEqual(note.interval('dd7'), teoria.note('Ebb')); 121 | }, 122 | 123 | 'Diminished seventh': function(note) { 124 | assert.deepEqual(note.interval('d7'), teoria.note('Eb')); 125 | }, 126 | 127 | 'Minor seventh': function(note) { 128 | assert.deepEqual(note.interval('m7'), teoria.note('E')); 129 | }, 130 | 131 | 'Major seventh': function(note) { 132 | assert.deepEqual(note.interval('M7'), teoria.note('E#')); 133 | }, 134 | 135 | 'Augmented seventh': function(note) { 136 | assert.deepEqual(note.interval('A7'), teoria.note('Ex')); 137 | }, 138 | 139 | 'Doubly diminished octave': function(note) { 140 | assert.deepEqual(note.interval('dd8'), teoria.note('Fb')); 141 | }, 142 | 143 | 'Diminished octave': function(note) { 144 | assert.deepEqual(note.interval('d8'), teoria.note('F')); 145 | }, 146 | 147 | 'Perfect octave': function(note) { 148 | assert.deepEqual(note.interval('P8'), teoria.note('F#')); 149 | }, 150 | 151 | 'Augmented octave': function(note) { 152 | assert.deepEqual(note.interval('A8'), teoria.note('Fx')); 153 | }, 154 | 155 | 'Minor ninth': function(note) { 156 | assert.deepEqual(note.interval('m9'), teoria.note('G')); 157 | }, 158 | 159 | 'Major ninth': function(note) { 160 | assert.deepEqual(note.interval('M9'), teoria.note('G#')); 161 | }, 162 | 163 | 'Minor tenth': function(note) { 164 | assert.deepEqual(note.interval('m10'), teoria.note('A')); 165 | }, 166 | 167 | 'Major tenth': function(note) { 168 | assert.deepEqual(note.interval('M10'), teoria.note('A#')); 169 | }, 170 | 171 | 'Perfect eleventh': function(note) { 172 | assert.deepEqual(note.interval('P11'), teoria.note('B')); 173 | }, 174 | 175 | 'Diminished twelfth': function(note) { 176 | assert.deepEqual(note.interval('d12'), teoria.note('c')); 177 | }, 178 | 179 | 'Perfect twelfth': function(note) { 180 | assert.deepEqual(note.interval('P12'), teoria.note('c#')); 181 | }, 182 | 183 | 'Minor thirteenth': function(note) { 184 | assert.deepEqual(note.interval('m13'), teoria.note('d')); 185 | }, 186 | 187 | 'Major thirteenth': function(note) { 188 | assert.deepEqual(note.interval('M13'), teoria.note('d#')); 189 | }, 190 | 191 | 'Minor fourteenth': function(note) { 192 | assert.deepEqual(note.interval('m14'), teoria.note('e')); 193 | }, 194 | 195 | 'Major fourteenth': function(note) { 196 | assert.deepEqual(note.interval('M14'), teoria.note('e#')); 197 | }, 198 | 199 | 'Doubly diminished second up': function() { 200 | assert.deepEqual(teoria.note('e').interval(teoria.note('fbb')), 201 | teoria.interval('dd2')); 202 | }, 203 | 204 | 'Doubly diminished second down': function() { 205 | assert.deepEqual(teoria.note('f').interval(teoria.note('ex')), 206 | teoria.interval('dd-2')); 207 | } 208 | }, 209 | 210 | 'Interval descending': { 211 | 'A major third down from E4': function() { 212 | assert.deepEqual(teoria.note('E4').interval('M-3'), teoria.note('C4')); 213 | }, 214 | 215 | 'Minor second down from C2': function() { 216 | assert.deepEqual(teoria.note('C2').interval('m-2'), teoria.note('B1')); 217 | }, 218 | 219 | 'A diminished fifth down from Eb5': function() { 220 | assert.deepEqual(teoria.note('Eb5').interval('d-5'), teoria.note('A4')); 221 | }, 222 | 223 | 'A major ninth down from G#4': function() { 224 | assert.deepEqual(teoria.note('G#4').interval('M-9'), teoria.note('F#3')); 225 | }, 226 | 227 | 'An augmented sixth down from Bb4': function() { 228 | assert.deepEqual(teoria.note('Bb4').interval('A-6'), teoria.note('Dbb4')); 229 | } 230 | }, 231 | 232 | 'Interval inversions': { 233 | 'Invert m2 is M7': function() { 234 | assert.equal(teoria.interval.invert('m2'), 'M7'); 235 | }, 236 | 237 | 'Invert M2 is m7': function() { 238 | assert.equal(teoria.interval.invert('M2'), 'm7'); 239 | }, 240 | 241 | 'Invert m3 is M6': function() { 242 | assert.equal(teoria.interval.invert('m3'), 'M6'); 243 | }, 244 | 245 | 'Invert M3 is m6': function() { 246 | assert.equal(teoria.interval.invert('M3'), 'm6'); 247 | }, 248 | 249 | 'Invert P4 is P5': function() { 250 | assert.equal(teoria.interval.invert('P4'), 'P5'); 251 | }, 252 | 253 | 'Invert A5 is d4': function() { 254 | assert.equal(teoria.interval.invert('A5'), 'd4'); 255 | } 256 | }, 257 | 258 | 'Interval base': { 259 | 'Base of d5 is a fifth': function() { 260 | assert.equal(teoria.interval('d5').base(), 'fifth'); 261 | }, 262 | 263 | 'Base of A7 is a seventh': function() { 264 | assert.equal(teoria.interval('A7').base(), 'seventh'); 265 | }, 266 | 267 | 'Base of m2 is a second': function() { 268 | assert.equal(teoria.interval('m2').base(), 'second'); 269 | }, 270 | 271 | 'Base of M6 is a sixth': function() { 272 | assert.equal(teoria.interval('M6').base(), 'sixth'); 273 | }, 274 | 275 | 'Base of dd8 is an octave': function() { 276 | assert.equal(teoria.interval('dd8').base(), 'octave'); 277 | }, 278 | 279 | 'Base of AA4 is a fourth': function() { 280 | assert.equal(teoria.interval('AA4').base(), 'fourth'); 281 | }, 282 | 283 | 'Base of d-5 is a fifth': function() { 284 | assert.equal(teoria.interval('d-5').base(), 'fifth'); 285 | }, 286 | 287 | 'Base of m-9 is a second': function() { 288 | assert.equal(teoria.interval('m-2').base(), 'second'); 289 | }, 290 | 291 | 'Base of M-13 is a sixth': function() { 292 | assert.equal(teoria.interval('M-13').base(), 'sixth'); 293 | }, 294 | 295 | 'Base of P-11 is a fourth': function() { 296 | assert.equal(teoria.interval('P-11').base(), 'fourth'); 297 | }, 298 | 299 | 'Base of AA-7 is a seventh': function() { 300 | assert.equal(teoria.interval('AA-7').base(), 'seventh'); 301 | } 302 | }, 303 | 304 | 'Compound Intervals': { 305 | 'A major seventeenth is a compound interval': function() { 306 | assert.equal(teoria.interval('M17').isCompound(), true); 307 | }, 308 | 309 | 'A major seventeenth\'s simple part is a major third': function() { 310 | assert.equal(teoria.interval('M17').simple(), 'M3'); 311 | }, 312 | 313 | 'A descending major fourteenth\'s simple part is a descending major seventh': function() { 314 | assert.equal(teoria.interval('M-14').simple(), 'M-7'); 315 | }, 316 | 317 | 'A perfect nineteenth\'s simple part is equal to a perfect fifth': function() { 318 | assert.equal(teoria.interval('P19').simple().equal(teoria.interval('P5')), true); 319 | }, 320 | 321 | 'A perfect nineteenth\'s simple part is not equal to a major sixth': function() { 322 | assert.equal(teoria.interval('P19').simple().equal(teoria.interval('M6')), false); 323 | }, 324 | 325 | 'A descending augmented ninth\'s simple part is equal to a descending augmented second': function() { 326 | assert.equal(teoria.interval('A-9').simple().equal(teoria.interval('A-2')), true); 327 | }, 328 | 329 | 'A 22nd has two compound octaves': function() { 330 | assert.equal(teoria.interval('P22').octaves(), 2); 331 | }, 332 | 333 | 'A descending fourth has no compound octaves': function() { 334 | assert.equal(teoria.interval('P-4').octaves(), 0); 335 | }, 336 | 337 | 'A descending eleventh has one compound octave': function() { 338 | assert.equal(teoria.interval('P-11').octaves(), 1); 339 | }, 340 | 341 | 'A descending augmented third has no compound octaves': function() { 342 | assert.equal(teoria.interval('A-3').octaves(), 0); 343 | }, 344 | 345 | 'A descending major 16th has two compound octaves': function() { 346 | assert.equal(teoria.interval('M-16').octaves(), 2); 347 | }, 348 | 349 | 'A major seventh is greater than a minor seventh': function() { 350 | assert.equal(teoria.interval('M7').greater(teoria.interval('m7')), true); 351 | }, 352 | 353 | 'An augmented octave is smaller than a major ninth': function() { 354 | assert.equal(teoria.interval('A8').smaller(teoria.interval('M9')), true); 355 | }, 356 | 357 | 'A major third is equal to another major third': function() { 358 | assert.equal(teoria.interval('M3').equal(teoria.interval('M3')), true); 359 | }, 360 | 361 | 'An augmented fifth is not equal to a minor sixth': function() { 362 | assert.equal(teoria.interval('P5').equal(teoria.interval('m6')), false); 363 | }, 364 | 365 | 'A perfect fifth is not equal to a perfect octave': function() { 366 | assert.equal(teoria.interval('P5').equal(teoria.interval('P8')), false); 367 | }, 368 | 369 | 'The simple part of a major 23th is a major second': function() { 370 | assert.equal(teoria.interval('M23').simple(), 'M2'); 371 | } 372 | }, 373 | 374 | 'Interval direction': { 375 | 'A3 to C4 is up': function() { 376 | assert.equal(teoria.note('A3').interval(teoria.note('C4')).direction(), 'up'); 377 | }, 378 | 379 | 'Bb5 to Bb5 is up (a unison is always up)': function() { 380 | assert.equal(teoria.note('Bb5').interval(teoria.note('Bb5')).direction(), 'up'); 381 | }, 382 | 383 | 'G#4 to D4 is down': function() { 384 | assert.equal(teoria.note('G#4').interval(teoria.note('D4')).direction(), 'down'); 385 | }, 386 | 387 | 'F6 to E6 is down': function() { 388 | assert.equal(teoria.note('F6').interval(teoria.note('E6')).direction(), 'down'); 389 | }, 390 | 391 | 'C4 to A3 is up, w. direction set to up': function() { 392 | assert.equal(teoria.note('C4').interval(teoria.note('A3')).direction('up').direction(), 'up'); 393 | }, 394 | 395 | 'A3 to C4 remains up w. direction set to up': function() { 396 | assert.equal(teoria.note('A3').interval(teoria.note('C4')).direction('up').direction(), 'up'); 397 | }, 398 | 399 | 'm2 is up': function() { 400 | assert.equal(teoria.interval('m2').direction(), 'up'); 401 | }, 402 | 403 | 'P11 is up': function() { 404 | assert.equal(teoria.interval('P11').direction(), 'up'); 405 | }, 406 | 407 | 'P1 is up': function() { 408 | assert.equal(teoria.interval('P1').direction(), 'up'); 409 | }, 410 | 411 | 'A1 is up': function() { 412 | assert.equal(teoria.interval('A1').direction(), 'up'); 413 | }, 414 | 415 | 'd1 is up': function() { 416 | assert.equal(teoria.interval('d1').direction(), 'up'); 417 | }, 418 | 419 | 'm-2 is down': function() { 420 | assert.equal(teoria.interval('m-2').direction(), 'down'); 421 | }, 422 | 423 | 'M-17 is down': function() { 424 | assert.equal(teoria.interval('M-17').direction(), 'down'); 425 | }, 426 | 427 | 'd-2 is down': function() { 428 | assert.equal(teoria.interval('d-2').direction(), 'down'); 429 | }, 430 | 431 | 'dd-2 is down (although it is up)': function() { 432 | assert.equal(teoria.interval('dd-2').direction(), 'down'); 433 | }, 434 | 435 | 'A-2 is down': function() { 436 | assert.equal(teoria.interval('A-2').direction(), 'down'); 437 | }, 438 | 439 | 'd-1 is up (all unison values are up)': function() { 440 | assert.equal(teoria.interval('d-1').direction(), 'up'); 441 | }, 442 | 443 | 'A-1 is up (all unison values are up)': function() { 444 | assert.equal(teoria.interval('A-1').direction(), 'up'); 445 | } 446 | }, 447 | 448 | 'Interval arithmetic': { 449 | 'm3 + M2 = P4': function() { 450 | assert.equal(addSimple('m3', 'M2').toString(), 'P4'); 451 | }, 452 | 453 | 'P4 + P5 = P8': function() { 454 | assert.equal(addSimple('P4', 'P5').toString(), 'P8'); 455 | }, 456 | 457 | 'M6 + A4 = A9': function() { 458 | assert.equal(addSimple('M6', 'A4').toString(), 'A9'); 459 | }, 460 | 461 | 'M-2 + m-2 = m-3': function() { 462 | assert.equal(addSimple('M-2', 'm-2').toString(), 'm-3'); 463 | }, 464 | 465 | 'A11 + M9 = A19': function() { 466 | assert.equal(addSimple('A11', 'M9').toString(), 'A19'); 467 | }, 468 | 469 | 'm-10 + P4 = m-7': function() { 470 | assert.equal(addSimple('m-10', 'P4').toString(), 'm-7'); 471 | } 472 | }, 473 | 474 | 'Theoretical intervals - Triple augmented': { 475 | topic: function() { 476 | return teoria.note('F').interval(teoria.note('Bx')); 477 | }, 478 | 479 | 'F to Bx has quality value = 3 (triple augmented)': function(interval) { 480 | assert.equal(interval.qualityValue(), 3); 481 | }, 482 | 483 | '#simple() works': function(interval) { 484 | assert.deepEqual(interval.simple().coord, [-11, 20]); 485 | } 486 | } 487 | }).export(module); 488 | -------------------------------------------------------------------------------- /test/notes.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'), 2 | assert = require('assert'), 3 | teoria = require('../'); 4 | 5 | vows.describe('TeoriaNote class').addBatch({ 6 | 'A4 - a\'': { 7 | topic: function() { 8 | return teoria.note('A4'); 9 | }, 10 | 11 | 'Octave should be 4': function(note) { 12 | assert.equal(note.octave(), 4); 13 | }, 14 | 15 | 'Note name is lower case': function(note) { 16 | assert.equal(note.name(), 'a'); 17 | }, 18 | 19 | 'A4 is the 49th piano key': function(note) { 20 | assert.equal(note.key(), 49); 21 | }, 22 | 23 | 'A4 is expressed a\' in Helmholtz notation': function(note) { 24 | assert.equal(note.helmholtz(), 'a\''); 25 | }, 26 | 27 | 'A4 is expressed A4 in scientific notation': function(note) { 28 | assert.equal(note.scientific(), 'A4'); 29 | }, 30 | 31 | 'The frequency of A4 is 440hz': function(note) { 32 | assert.equal(note.fq(), 440); 33 | } 34 | }, 35 | 36 | 'C#5 - c#\'\'': { 37 | topic: function() { 38 | return teoria.note('c#\'\''); 39 | }, 40 | 41 | 'Octave should be 5': function(note) { 42 | assert.equal(note.octave(), 5); 43 | }, 44 | 45 | 'The name attribute of c# is just c': function(note) { 46 | assert.equal(note.name(), 'c'); 47 | }, 48 | 49 | 'The accidental.sign attribute is #': function(note) { 50 | assert.equal(note.accidental(), '#'); 51 | }, 52 | 53 | 'The accidental.value attribute is 1': function(note) { 54 | assert.equal(note.accidentalValue(), 1); 55 | }, 56 | 57 | 'C#5 is the 53rd piano key': function(note) { 58 | assert.equal(note.key(), 53); 59 | }, 60 | 61 | 'C#5 is c#\'\' in Helmholtz notation': function(note) { 62 | assert.equal(note.helmholtz(), 'c#\'\''); 63 | }, 64 | 65 | 'c#\'\' is C#5 in scientific notation': function(note) { 66 | assert.equal(note.scientific(), 'C#5'); 67 | }, 68 | 69 | 'The frequency of C#5 is approximately 554.365': function(note) { 70 | assert.equal(note.fq(), 554.3652619537442); 71 | }, 72 | 73 | 'The interval between C#5 and A4 is a major third': function(note) { 74 | var a4 = teoria.note('A4'); 75 | 76 | assert.deepEqual(note.interval(a4), teoria.interval('M-3')); 77 | }, 78 | 79 | 'The interval between C#5 and Eb6 is diminished tenth': function(note) { 80 | var eb6 = teoria.note('Eb6'); 81 | 82 | assert.deepEqual(note.interval(eb6), teoria.interval('d10')); 83 | }, 84 | 85 | 'An diminished fifth away from C#5 is G5': function(note) { 86 | var g5 = teoria.note('G5'); 87 | 88 | assert.deepEqual(note.interval('d5'), g5); 89 | }, 90 | 91 | 'The interval between C#4 and Db4 is a diminished second': function() { 92 | var cis4 = teoria.note('c#4'); 93 | var db4 = teoria.note('db4'); 94 | 95 | assert.deepEqual(cis4.interval(db4), teoria.interval('d2')); 96 | } 97 | }, 98 | 99 | 'Instantiate with coords': { 100 | '[0, 0] is A4': function() { 101 | assert.equal(teoria.note([0, 0]).scientific(), 'A4'); 102 | }, 103 | 104 | '[-4, 4] is C#3': function() { 105 | assert.equal(teoria.note([-4, 4]).scientific(), 'C#3'); 106 | }, 107 | 108 | '[3, -4] is F5': function() { 109 | assert.equal(teoria.note([3, -4]).scientific(), 'F5'); 110 | }, 111 | 112 | '[4, -7] is Ab4': function() { 113 | assert.equal(teoria.note([4, -7]).scientific(), 'Ab4'); 114 | } 115 | }, 116 | 117 | 'Instantiate from key': { 118 | '#49 is A4': function() { 119 | assert.equal(teoria.note.fromKey(49).scientific(), 'A4'); 120 | }, 121 | 122 | '#20 is E2': function() { 123 | assert.equal(teoria.note.fromKey(20).scientific(), 'E2'); 124 | }, 125 | 126 | '#57 is F5': function() { 127 | assert.equal(teoria.note.fromKey(57).scientific(), 'F5'); 128 | }, 129 | 130 | '#72 is G#6': function() { 131 | assert.equal(teoria.note.fromKey(72).scientific(), 'G#6'); 132 | } 133 | }, 134 | 135 | 'Instantiate from frequency': { 136 | '391.995Hz is G4': function() { 137 | assert.equal(teoria.note.fromFrequency(391.995).note.scientific(), 'G4'); 138 | }, 139 | 140 | '220.000Hz is A3': function() { 141 | assert.equal(teoria.note.fromFrequency(220.000).note.scientific(), 'A3'); 142 | }, 143 | 144 | '155.563Hz is Eb3': function() { 145 | assert.equal(teoria.note.fromFrequency(155.563).note.scientific(), 'Eb3'); 146 | }, 147 | 148 | '2959.96Hz is F#7': function() { 149 | assert.equal(teoria.note.fromFrequency(2959.96).note.scientific(), 'F#7'); 150 | } 151 | }, 152 | 153 | 'Instantiate from MIDI': { 154 | 'MIDI#36 is C2': function() { 155 | assert.equal(teoria.note.fromMIDI(36).scientific(), 'C2'); 156 | }, 157 | 158 | 'MIDI#77 is F5': function() { 159 | assert.equal(teoria.note.fromMIDI(77).scientific(), 'F5'); 160 | }, 161 | 162 | 'MIDI#61 is Db4': function() { 163 | assert.equal(teoria.note.fromMIDI(61).scientific(), 'Db4'); 164 | }, 165 | 166 | 'MIDI#80 is G#5': function() { 167 | assert.equal(teoria.note.fromMIDI(80).scientific(), 'G#5'); 168 | } 169 | }, 170 | 171 | 'Return MIDI note number': { 172 | 'MIDI#36 is C2': function() { 173 | assert.equal(teoria.note('C2').midi(), 36); 174 | }, 175 | 176 | 'MIDI#77 is F5': function() { 177 | assert.equal(teoria.note('F5').midi(), 77); 178 | }, 179 | 180 | 'MIDI#61 is Db4': function() { 181 | assert.equal(teoria.note('Db4').midi(), 61); 182 | }, 183 | 184 | 'MIDI#80 is G#5': function() { 185 | assert.equal(teoria.note('G#5').midi(), 80); 186 | } 187 | }, 188 | 189 | 'Chroma': { 190 | 'C has chroma 0': function() { 191 | assert.equal(teoria.note('c').chroma(), 0); 192 | }, 193 | 194 | 'C# has chroma 1': function() { 195 | assert.equal(teoria.note('c#').chroma(), 1); 196 | }, 197 | 198 | 'B has chroma 11': function() { 199 | assert.equal(teoria.note('b').chroma(), 11); 200 | }, 201 | 202 | 'Db has chroma 1': function() { 203 | assert.equal(teoria.note('db').chroma(), 1); 204 | }, 205 | 206 | 'Dbb has chroma 0': function() { 207 | assert.equal(teoria.note('dbb').chroma(), 0); 208 | }, 209 | 210 | 'E has chroma 4': function() { 211 | assert.equal(teoria.note('e').chroma(), 4); 212 | }, 213 | 214 | 'F has chroma 5': function() { 215 | assert.equal(teoria.note('f').chroma(), 5); 216 | }, 217 | 218 | 'Fb has chroma 4': function() { 219 | assert.equal(teoria.note('fb').chroma(), 4); 220 | }, 221 | 222 | 'H# has chroma 0': function() { 223 | assert.equal(teoria.note('h#').chroma(), 0); 224 | }, 225 | 226 | 'Bx has chroma 1': function() { 227 | assert.equal(teoria.note('bx').chroma(), 1); 228 | }, 229 | 230 | 'Cbb has chroma 10': function() { 231 | assert.equal(teoria.note('cbb').chroma(), 10); 232 | } 233 | }, 234 | 235 | 'Scale Degrees': { 236 | 'Eb is scale degree 1 (tonic) in an Eb minor scale': function() { 237 | var note = teoria.note('eb'); 238 | assert.equal(note.scaleDegree(teoria.scale('eb', 'major')), 1); 239 | }, 240 | 241 | 'E is scale degree 3 in a C# dorian': function() { 242 | var note = teoria.note('e'); 243 | assert.equal(note.scaleDegree(teoria.scale('c#', 'dorian')), 3); 244 | }, 245 | 246 | 'C is scale degree 0 in a D major scale (not in scale)': function() { 247 | var note = teoria.note('c'); 248 | assert.equal(note.scaleDegree(teoria.scale('d', 'major')), 0); 249 | }, 250 | 251 | 'Bb is scale degree 7 in a C minor': function() { 252 | var note = teoria.note('bb'); 253 | assert.equal(note.scaleDegree(teoria.scale('c', 'minor')), 7); 254 | }, 255 | 256 | 'Db is scale degree 4 in an Ab major scale': function() { 257 | var note = teoria.note('db'); 258 | assert.equal(note.scaleDegree(teoria.scale('ab', 'major')), 4); 259 | }, 260 | 261 | 'A# is scale degree 0 in a G minor scale': function() { 262 | var note = teoria.note('a#'); 263 | assert.equal(note.scaleDegree(teoria.scale('g', 'minor')), 0); 264 | } 265 | }, 266 | 267 | 'Enharmonics': { 268 | 'c is enharmonic with dbb and b#': function() { 269 | assert.deepEqual(teoria.note('c4').enharmonics(), 270 | ['dbb4', 'b#3'].map(teoria.note)); 271 | }, 272 | 273 | 'fb is enharmonic with e and dx': function() { 274 | assert.deepEqual(teoria.note('fb4').enharmonics(), 275 | ['e4', 'dx4'].map(teoria.note)); 276 | }, 277 | 278 | 'cb is enharmonic with ax and b': function() { 279 | assert.deepEqual(teoria.note('cb4').enharmonics(), 280 | ['b3', 'ax3'].map(teoria.note)); 281 | } 282 | }, 283 | 284 | 'Enharmonics with only one accidental': { 285 | 'c is enharmonic with b#': function() { 286 | assert.deepEqual(teoria.note('c4').enharmonics(true), 287 | ['b#3'].map(teoria.note)); 288 | }, 289 | 290 | 'fb is enharmonic with e': function() { 291 | assert.deepEqual(teoria.note('fb4').enharmonics(true), 292 | ['e4'].map(teoria.note)); 293 | }, 294 | 295 | 'cb is enharmonic with b': function() { 296 | assert.deepEqual(teoria.note('cb4').enharmonics(true), 297 | ['b3'].map(teoria.note)); 298 | } 299 | }, 300 | 301 | 'copy duration on interval': { 302 | 'stay whole note on call interval': function() { 303 | var note = teoria.note('a#', { duration: 1 }); 304 | assert.equal(note.duration.value, note.interval('P5').duration.value); 305 | } 306 | } 307 | }).export(module); 308 | -------------------------------------------------------------------------------- /test/scales.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'), 2 | assert = require('assert'), 3 | teoria = require('../'); 4 | 5 | vows.describe('Scales').addBatch({ 6 | 'Ab2': { 7 | topic: function() { 8 | return teoria.note('Ab2'); 9 | }, 10 | 11 | 'Blues': function(note) { 12 | assert.deepEqual(teoria.note('g#').scale('blues').simple(), 13 | ['g#', 'b', 'c#', 'd', 'd#', 'f#']); 14 | }, 15 | 16 | 'Ionian/Major': function(note) { 17 | assert.deepEqual(note.scale('ionian').simple(), 18 | ['ab', 'bb', 'c', 'db', 'eb', 'f', 'g']); 19 | }, 20 | 21 | 'Dorian': function(note) { 22 | assert.deepEqual(note.scale('dorian').simple(), 23 | ['ab', 'bb', 'cb', 'db', 'eb', 'f', 'gb']); 24 | }, 25 | 26 | 'Phrygian': function(note) { 27 | assert.deepEqual(note.scale('phrygian').simple(), 28 | ["ab", "bbb", "cb", "db", "eb", "fb", "gb"]); 29 | }, 30 | 31 | 'Lydian': function(note) { 32 | assert.deepEqual(note.scale('lydian').simple(), 33 | ["ab", "bb", "c", "d", "eb", "f", "g"]); 34 | }, 35 | 36 | 'Mixolydian': function(note) { 37 | assert.deepEqual(note.scale('mixolydian').simple(), 38 | ["ab", "bb", "c", "db", "eb", "f", "gb"]); 39 | }, 40 | 41 | 'Aeolian/Minor': function(note) { 42 | assert.deepEqual(note.scale('aeolian').simple(), 43 | ["ab", "bb", "cb", "db", "eb", "fb", "gb"]); 44 | }, 45 | 46 | 'Locrian': function(note) { 47 | assert.deepEqual(note.scale('locrian').simple(), 48 | ["ab", "bbb", "cb", "db", "ebb", "fb", "gb"]); 49 | }, 50 | 51 | 'Major Pentatonic': function(note) { 52 | assert.deepEqual(note.scale('majorpentatonic').simple(), 53 | ["ab", "bb", "c", "eb", "f"]); 54 | }, 55 | 56 | 'Minor Pentatonic': function(note) { 57 | assert.deepEqual(note.scale('minorpentatonic').simple(), 58 | ["ab", "cb", "db", "eb", "gb"]); 59 | }, 60 | 61 | 'Chromatic': function(note) { 62 | assert.deepEqual(note.scale('chromatic').simple(), 63 | ["ab", "bbb", "bb", "cb", "c", "db", 64 | "d", "eb", "fb", "f", "gb", "g"]); 65 | }, 66 | 67 | 'Whole Tone': function(note) { 68 | assert.deepEqual(teoria.note('c').scale('wholetone').simple(), 69 | ["c", "d", "e", "f#", "g#", "a#"]); 70 | } 71 | }, 72 | 73 | 'Is the #get() method octave-relative (pentatonic)?': { 74 | topic: function(){ 75 | return teoria.note('Bb3').scale('majorpentatonic'); 76 | }, 77 | 78 | 'Gets notes w/in octave': function(topic){ 79 | assert.deepEqual(topic.get(3), teoria.note('D4')); 80 | }, 81 | 82 | 'Gets notes above octave': function(topic){ 83 | assert.deepEqual(topic.get(12), teoria.note('C6')); 84 | }, 85 | 86 | 'Gets notes below octave': function(topic){ 87 | assert.deepEqual(topic.get(-12), teoria.note('D1')); 88 | }, 89 | }, 90 | 91 | 'Is the #get() method octave-relative (diatonic)': { 92 | topic: function() { 93 | return teoria.note('A4').scale('major'); 94 | }, 95 | 96 | '0 is one note down': function(topic) { 97 | assert.deepEqual(topic.get(0), teoria.note('G#4')); 98 | }, 99 | 100 | '7 is one seventh up': function(topic) { 101 | assert.deepEqual(topic.get(7), teoria.note('G#5')); 102 | }, 103 | 104 | '8 is one octave up': function(topic) { 105 | assert.deepEqual(topic.get(8), teoria.note('A5')); 106 | }, 107 | 108 | '9 is one ninth up': function(topic) { 109 | assert.deepEqual(topic.get(9), teoria.note('B5')); 110 | }, 111 | 112 | '-5 is one seventh down': function(topic) { 113 | assert.deepEqual(topic.get(-5), teoria.note('B3')); 114 | }, 115 | 116 | '-6 is one octave down': function(topic) { 117 | assert.deepEqual(topic.get(-6), teoria.note('A3')); 118 | }, 119 | 120 | '-13 is two octaves down': function(topic) { 121 | assert.deepEqual(topic.get(-13), teoria.note('A2')); 122 | } 123 | } 124 | }).export(module); 125 | -------------------------------------------------------------------------------- /test/solfege.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'), 2 | assert = require('assert'), 3 | teoria = require('../'); 4 | 5 | vows.describe('Solfege').addBatch({ 6 | 'C in C minor': function() { 7 | var note = teoria.note('c'); 8 | assert.equal(note.solfege(teoria.scale(note, 'minor')), 'do'); 9 | }, 10 | 11 | 'A in d major': function() { 12 | var note = teoria.note('a'); 13 | var tonic = teoria.note('d'); 14 | assert.equal(note.solfege(teoria.scale(tonic, 'major')), 'so'); 15 | }, 16 | 17 | 'F# in B major': function() { 18 | var note = teoria.note('f#'); 19 | var tonic = teoria.note('B'); 20 | assert.equal(note.solfege(teoria.scale(tonic, 'major')), 'so'); 21 | }, 22 | 23 | 'C4 in C4 minor': function() { 24 | var note = teoria.note('c4'); 25 | var scale = teoria.scale(note, 'minor'); 26 | assert.equal(note.solfege(scale, true), 'do'); 27 | }, 28 | 29 | 'A3 in D4 major': function() { 30 | var note = teoria.note('a3'); 31 | var scale = teoria.scale('d4', 'major'); 32 | assert.equal(note.solfege(scale, true), 'so,'); 33 | }, 34 | 35 | 'F#6 in B5 major': function() { 36 | var note = teoria.note('f#6'); 37 | var scale = teoria.scale('b5', 'major'); 38 | assert.equal(note.solfege(scale, true), 'so'); 39 | }, 40 | 41 | 'F2 in E6 phrygian': function() { 42 | var note = teoria.note('f2'); 43 | var scale = teoria.scale('e6', 'phrygian'); 44 | assert.equal(note.solfege(scale, true), 'ra,,,,'); 45 | }, 46 | 47 | 'Eb10 in E8 dorian': function() { 48 | var note = teoria.note('eb10'); 49 | var scale = teoria.scale('e8', 'dorian'); 50 | assert.equal(note.solfege(scale, true), 'de\'\''); 51 | }, 52 | 53 | 'A#6 in Bb4 locrian': function() { 54 | var note = teoria.note('A#6'); 55 | var scale = teoria.scale('Bb4', 'locrian'); 56 | assert.equal(note.solfege(scale, true), 'tai\''); 57 | }, 58 | 59 | 'E2 in C3 major': function() { 60 | var note = teoria.note('e2'); 61 | var scale = teoria.scale('c3', 'major'); 62 | assert.equal(note.solfege(scale, true), 'mi,'); 63 | } 64 | }).export(module); 65 | --------------------------------------------------------------------------------