├── .codeclimate.yml ├── .eslintrc ├── .gitignore ├── .jsdoc3.json ├── .npmignore ├── .travis.yml ├── API.md ├── CHANGELOG ├── LICENSE ├── README.md ├── TODO ├── core ├── measures.js └── score.js ├── dist ├── score.js └── score.min.js ├── examples ├── dm.js ├── index.html ├── pianoroll-example.js ├── player-example.js ├── polyrhythms.js ├── schedule-example.js ├── transcriptions │ └── AnthropologyParker.ls.json └── twelveTone.js ├── ext ├── fp.js ├── pianoroll.js ├── player.js └── scheduler.js ├── lib ├── build.js ├── index.js ├── measures.js ├── notes.js ├── performance.js ├── rhythm.js ├── score.js ├── stats.js └── timed.js ├── package.json └── test ├── build-test.js ├── integration ├── AnthropologyParker.ls.json ├── GoodByePorkPieHat.scorejs.txt └── TestFormat.scorejs.txt ├── measures-test.js ├── notes-test.js ├── performance-test.js ├── pianoroll-test.js ├── rhythm-test.js ├── score-test.js ├── stats-test.js └── timed-test.js /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | Ruby: false 3 | JavaScript: true 4 | PHP: false 5 | Python: false 6 | exclude_paths: 7 | - "dist/*" 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | .tmp/ 29 | docs/ 30 | -------------------------------------------------------------------------------- /.jsdoc3.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "include": ["README.md", "index.js", "lib/"] 4 | }, 5 | "opts": { 6 | "destination": "./docs/" 7 | }, 8 | "plugins": ["plugins/markdown"] 9 | } 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | examples 3 | .codeclimate 4 | .jshintrc 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | - "4.0" 5 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | ## `chord` 2 | 3 | Create a collection of simultaneus notes 4 | 5 | You can specify a collection of pitches, durations and attributes 6 | and `chord` will arrange them as a collection of notes in simultaneus 7 | layout 8 | 9 | ### Parameters 10 | 11 | * `pitches` **`String or Array`** the chord note pitches 12 | * `durations` **`String or Array`** the chord note durations 13 | * `attributes` **`Hash`** the chord note attributes 14 | 15 | 16 | ### Examples 17 | 18 | ```js 19 | score.phrase('A B C D E', 1) 20 | ``` 21 | 22 | Returns `Array` a parallel musical structure 23 | 24 | 25 | ## `chords` 26 | 27 | Create a chord names sequence 28 | 29 | ### Parameters 30 | 31 | * `meter` **`String`** the meter used in the measures 32 | * `measures` **`String`** the chords 33 | * `a` **`Sequence`** sequence of chords 34 | 35 | 36 | ### Examples 37 | 38 | ```js 39 | score.chords('4/4', 'C6 | Dm7 G7 | Cmaj7') 40 | ``` 41 | ```js 42 | score(['chords', '4/4', 'Cmaj7 | Dm7 G7']) 43 | ``` 44 | 45 | 46 | 47 | ## `el` 48 | 49 | Create a score element: an object with duration 50 | 51 | It's accepts any data you supply, but duration property has a special 52 | meaning: it's a number representing the duration in arbitrary units. 53 | It's assumed to be 0 (no duration) if not present or not a valid number 54 | 55 | ### Parameters 56 | 57 | * `duration` **`Number`** the element duration 58 | * `data` **`Object`** the additional element data 59 | 60 | 61 | 62 | 63 | 64 | ## `events` 65 | 66 | Get a sorted events array from a score 67 | 68 | 69 | 70 | 71 | 72 | 73 | ## `expandChords` 74 | 75 | Convert a chord names sequence into a chord notes sequence 76 | 77 | 78 | 79 | 80 | 81 | 82 | ## `forEachTime` 83 | 84 | Get all notes for side-effects 85 | 86 | __Important:__ ascending time ordered is not guaranteed 87 | 88 | ### Parameters 89 | 90 | * `fn` **`Function`** the function 91 | * `ctx` **`Object`** (Optional) a context object passed to the function 92 | * `score` **`Score`** the score object 93 | 94 | 95 | 96 | 97 | 98 | ## `harmony` 99 | 100 | Create a harmony sequence 101 | 102 | 103 | 104 | 105 | 106 | 107 | ## `ioi` 108 | 109 | Create a rhythmic sequence from an inter onset interval number 110 | 111 | 112 | 113 | 114 | 115 | 116 | ## `ioiToPattern` 117 | 118 | Convert an [inter onset interval](https://en.wikipedia.org/wiki/Time_point#Interonset_interval) 119 | to a pattern 120 | 121 | ### Parameters 122 | 123 | * `ioi` **`String`** the inter onset interval 124 | * `the` **`String`** rhythm pattern 125 | 126 | 127 | 128 | 129 | 130 | ## `isArray` 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | ## `map` 140 | 141 | Map the notes of a musical structure using a function 142 | 143 | ### Parameters 144 | 145 | * `fn` **`Function`** the function used to map the notes 146 | * `ctx` **`Object`** a context object passed to the function 147 | * `score` **`Score`** the score to transform 148 | 149 | 150 | 151 | Returns `Score` the transformed score 152 | 153 | 154 | ## `measures` 155 | 156 | Parse masures using a time meter to get a sequence 157 | 158 | ### Parameters 159 | 160 | * `meter` **`String`** the time meter 161 | * `measures` **`String`** the measures string 162 | * `builder` **`Function`** (Optional) the function used to build the notes 163 | 164 | 165 | ### Examples 166 | 167 | ```js 168 | measures('4/4', 'c d (e f) | g | (a b c) d') 169 | ``` 170 | 171 | Returns `Score` the score object 172 | 173 | 174 | ## `note` 175 | 176 | Create a note from duration and pitch 177 | 178 | A note is any object with duration and pitch attributes. The duration 179 | must be a number, but the pitch can be any value (although only strings with 180 | scientific notation pitches and midi numbers are recogniced by the manipulation 181 | or display functions) 182 | 183 | If only duration is provided, a partially applied function is returned. 184 | 185 | ### Parameters 186 | 187 | * `duration` **`Integer`** the note duration 188 | * `pitch` **`String or Integer`** the note pitch 189 | * `data` **`Hash`** (Optional) arbitraty note data 190 | 191 | 192 | ### Examples 193 | 194 | ```js 195 | score.note(1, 'A') // => { duration: 1, pitch: 'A' } 196 | score.note(0.5, 'anything') // => { duration: 0.5, pitch: 'anything' } 197 | score.note(2, 'A', 2, { inst: 'piano' }) // => { duration: 2, pitch: 'A', inst: 'piano' } 198 | ``` 199 | ```js 200 | // partially applied 201 | ['C', 'D', 'E'].map(score.note(1)) // => [{ duration: 1, pitch: 'C'}, 202 | { duration: 1, pitch: 'D'}, { duration: 1, pitch: 'E'}] 203 | ``` 204 | 205 | Returns `Hash` a note 206 | 207 | 208 | ## `notes` 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | ## `pattern` 218 | 219 | Create a rhythmic sequence from a pattern 220 | 221 | 222 | 223 | 224 | 225 | 226 | ## `patternToIoi` 227 | 228 | Convert a pattern string to inter onset interval string 229 | 230 | ### Parameters 231 | 232 | * `pattern` **`String`** the pattern to be converted 233 | 234 | 235 | 236 | Returns `String` the inter onset interval 237 | 238 | 239 | ## `phrase` 240 | 241 | Create a phrase (a sequential structure of notes) 242 | 243 | ### Parameters 244 | 245 | * `pitches` **`String or Array`** the phrase note pitches 246 | * `durations` **`String or Array`** the phrase note durations 247 | * `attributes` **`Hash`** the phrase note attributes 248 | 249 | 250 | ### Examples 251 | 252 | ```js 253 | score.phrase('A B C D E', 1) 254 | ``` 255 | 256 | Returns `Array` a sequential musical structure 257 | 258 | 259 | ## `rhythm` 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | ## `seq` 269 | 270 | Create a musical structure where elements are sequenetial 271 | 272 | ### Parameters 273 | 274 | * `elements` **`Array`** an array of elements 275 | 276 | 277 | ### Examples 278 | 279 | ```js 280 | score.sequential([score.note('A'), score.note('B')]) 281 | ``` 282 | 283 | Returns `Array` the sequential musical structure 284 | 285 | 286 | ## `sim` 287 | 288 | Create a musical structure where elements are simultaneous 289 | 290 | 291 | 292 | ### Examples 293 | 294 | ```js 295 | score.sim([score.note('A'), score.note('B')]) 296 | ``` 297 | 298 | 299 | 300 | ## `trans` 301 | 302 | Transpose notes 303 | 304 | ### Parameters 305 | 306 | * `interval` **`String`** the interval to transpose 307 | * `score` **`Object`** the score object 308 | 309 | 310 | 311 | Returns `Score` the score with the notes transposed 312 | 313 | 314 | ## `transform` 315 | 316 | Transform a musical structure 317 | 318 | This is probably the most important function. It allows complex 319 | transformations of musical structures using three functions 320 | 321 | ### Parameters 322 | 323 | * `elTransform` **`Function`** element transform function 324 | * `seqTransform` **`Function`** sequential structure transform function 325 | * `parTransform` **`Function`** simultaneous structure transform function 326 | * `ctx` **`Any`** an additional object passed to transform functions 327 | * `score` **`Object`** the score to transform 328 | 329 | 330 | 331 | Returns the result of the transformation 332 | 333 | 334 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | [0.9.0] 2 | - builder: build() support a json string as parameter 3 | - builder: build() supports custom txt format 4 | 5 | [0.8.0] 6 | - time: loopUntil(duration) 7 | - chords: chords(), playChords() 8 | - example 'plays' charlie parker 9 | 10 | [0.7.0] 11 | - Move to standard js style 12 | - Use music-parse to parse music notation 13 | - Update note-pitch to new API 14 | - Builder is now part of core 15 | - time: Add score.toTempo 16 | - notes: score.notes 17 | - notes: transpose only transpose notes (leave the rest) 18 | 19 | [0.6.0] 20 | - Add score.set method 21 | - Add reverse to time plugin 22 | - Export window.Score if window available 23 | 24 | [0.5.0] 25 | - Simplify code base. Remove Score.Sequence class 26 | - Rename Score(obj) to Score.build(obj) in a plugin 27 | - Remove score.clone 28 | - Make events more explicitly immutable 29 | 30 | [0.4.0] 31 | - Combine plugin: merge function 32 | - Sequence: better map implementation 33 | - WalkinBass plugin: a naive implementation 34 | - LeftHandPiano plugin annotates chords and instruments 35 | - ChordPlayer plugin renamed to LeftHandPiano 36 | 37 | [0.3.0] 38 | - First working version. 39 | - Plaguin infrastructure 40 | - Teoria integration 41 | 42 | [0.2.0] 43 | - Working with notes jQuery style 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015 Daniel Gómez Blasco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScoreJS 2 | 3 | [![Build Status](https://travis-ci.org/danigb/scorejs.svg?branch=master)](https://travis-ci.org/danigb/scorejs) 4 | [![CodeClimate](https://codeclimate.com/github/danigb/scorejs/badges/gpa.svg)](https://codeclimate.com/github/danigb/scorejs) 5 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) 6 | 7 | __Work in progress__ 8 | 9 | Create and manipulate musical scores with javascript. The aim of this project 10 | is to provide a common interface and a high level toolkit that make easy 11 | build useful tools for musicians. 12 | 13 | This library is oriented to composition, music learning, score analysis or algorithmic composition. Even you can play music with it, it's not a sequencer or DAW type software. 14 | 15 | This code is largely based in two papers: 16 | 17 | - Lisp as a second language, composing programs and music: http://www.mcg.uva.nl/papers/lisp2nd/functional.pdf 18 | - Haskell School of Music: http://www.cs.yale.edu/homes/hudak/Papers/HSoM.pdf 19 | 20 | ## Example 21 | 22 | ```js 23 | // require the library 24 | var score = require('scorejs') 25 | 26 | // create the score 27 | var song = score( 28 | ['melody', '4/4', 'c2 d2 e2 (f2 g2) | a2 b2 | c3'], 29 | ['harmony', '4/4', 'Cmaj7 | Dm7 G7 | Cmaj7'] 30 | ) 31 | 32 | // play the score: 33 | // create an audio context 34 | var ac = new AudioContext() 35 | // require the player module 36 | var player = require('scorejs/ext/player') 37 | player.play(ac, player.synth, score.tempo(120, song)) 38 | 39 | // show the score in a piano roll 40 | // given a canvas... 41 | var ctx = canvas.getContext('2d') 42 | // require the pianoroll module 43 | var pianoRoll = require('scorejs/ext/pianoroll') 44 | pianoRoll.draw(ctx, song) 45 | ``` 46 | 47 | ## Installation 48 | 49 | Via npm package: `npm i --save scorejs` and require it: 50 | `var score = require('scorejs')` 51 | 52 | For browsers use the file in the `dist` folder: 53 | 54 | __Important:__ 55 | 56 | It uses `Object.assign` so if your environment doesn't have it you need a polyfill, for example: https://github.com/sindresorhus/object-assign 57 | 58 | ## Usage 59 | 60 | `scorejs` models scores as collection of notes (objects with `duration` and `pitch` properties), that can be arranged sequentially o simultaneously: 61 | 62 | ```js 63 | // a melody 64 | var seq = score.seq(score.note(1, 'C'), score.note(1, 'D'), score.note(1, 'E')) 65 | // a chord 66 | var chord = score.sim(score.note(3, 'C'), score.note(3, 'E'), score.note(3, 'G')) 67 | ``` 68 | 69 | The `phrase` and `chord` functions are helpers to write the above more concisely: 70 | 71 | ```js 72 | var seq = score.phrase('C D E', 1) 73 | var chord = score.chord('C E G', 3) 74 | ``` 75 | 76 | You can combine elements freely: 77 | 78 | ```js 79 | // the melody and the chord simultaneously 80 | var song = score.sim( 81 | score.phrase('C D E', 1), 82 | score.chord('C E G', 3) 83 | ) 84 | ``` 85 | 86 | Finally, you can use a valid JSON data to define scores: 87 | 88 | ```js 89 | var song = score( 90 | ['phrase', 'C D E', 1], 91 | ['chord', 'C E G', 3] 92 | ) 93 | ``` 94 | 95 | ## Play 96 | 97 | There's a built-in scheduler and player based on Web Audio API. 98 | 99 | 100 | ## Tests and examples 101 | 102 | Clone this repo and install dependencies: `npm install` 103 | 104 | Tests are executed with `npm test` 105 | 106 | Examples can be running with beefy: `npm -g install beefy` and then: `beefy examples/pianoroll-example` 107 | 108 | ## License 109 | 110 | The MIT License (MIT) 111 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 2 | - scheduler web audio api 3 | - player web audio api 4 | - better time meter 5 | -------------------------------------------------------------------------------- /core/measures.js: -------------------------------------------------------------------------------- 1 | var score = require('./score') 2 | 3 | function measures (meter, measures) { 4 | var list 5 | var seq = [] 6 | splitMeasures(measures).forEach(function (measure) { 7 | measure = measure.trim() 8 | if (measure.length > 0) { 9 | list = parenthesize(tokenize(measure), []) 10 | processList(seq, list, measureLength(meter)) 11 | } 12 | }) 13 | return score.seq(seq) 14 | } 15 | 16 | function measureLength (meter) { 17 | return 4 18 | } 19 | 20 | function processList (seq, list, total) { 21 | var dur = total / list.length 22 | list.forEach(function (i) { 23 | if (Array.isArray(i)) processList(seq, i, dur) 24 | else seq.push(score.note(dur, i)) 25 | }) 26 | } 27 | 28 | function splitMeasures (repr) { 29 | return repr 30 | .replace(/\s+\||\|\s+/, '|') // spaces between | 31 | .replace(/^\||\|\s*$/g, '') // first and last | 32 | .split('|') 33 | } 34 | 35 | /* 36 | * The following code is copied from https://github.com/maryrosecook/littlelisp 37 | * See: http://maryrosecook.com/blog/post/little-lisp-interpreter 38 | * Thanks Mary Rose Cook! 39 | */ 40 | var parenthesize = function (input, list) { 41 | var token = input.shift() 42 | if (token === undefined) { 43 | return list 44 | } else if (token === '(') { 45 | list.push(parenthesize(input, [])) 46 | return parenthesize(input, list) 47 | } else if (token === ')') { 48 | return list 49 | } else { 50 | return parenthesize(input, list.concat(token)) 51 | } 52 | } 53 | 54 | var tokenize = function (input) { 55 | return input 56 | .replace(/[\(]/g, ' ( ') 57 | .replace(/[\)]/g, ' ) ') 58 | .replace(/\,/g, ' ') 59 | .trim().split(/\s+/) 60 | } 61 | 62 | module.exports = measures 63 | -------------------------------------------------------------------------------- /core/score.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var build = require('./build') 4 | var slice = Array.prototype.slice 5 | var isArray = Array.isArray 6 | var isCollection = function (a) { return isArray(a) && typeof a[0] !== 'string' } 7 | 8 | function typeOf (obj) { 9 | if (typeof obj.duration !== 'undefined') return 'event' 10 | else if (isArray(obj)) return obj[0] 11 | } 12 | 13 | /** 14 | * @name score 15 | * @module score 16 | */ 17 | var score = function (data) { 18 | return build(score, data) 19 | } 20 | 21 | /** 22 | * Create a score element: an object with duration 23 | * 24 | * It's accepts any data you supply, but duration property has a special 25 | * meaning: it's a number representing the duration in arbitrary units. 26 | * It's assumed to be 0 (no duration) if not present or not a valid number 27 | * 28 | * @param {Number} duration - the element duration 29 | * @param {Object} data - the additional element data 30 | */ 31 | score.el = function (d, data) { 32 | return Object.assign({ duration: +d || 0 }, data) 33 | } 34 | 35 | /** 36 | * Create a note 37 | * 38 | * @param {String|Integer} pitch - the note pitch 39 | * @param {Integer} duration - (Optional) the note duration (1 by default) 40 | * @param {Hash} attributes - (Optional) note attributes 41 | * @return {Hash} a note 42 | * 43 | * @example 44 | * score.note('A') // => { duration: 1, pitch: 'A' } 45 | * score.note('A', 0.5) // => { duration: 0.5, pitch: 'A' } 46 | * score.note('A', 2, { inst: 'piano' }) // => { duration: 2, pitch: 'A', inst: 'piano' } 47 | */ 48 | score.note = function (pitch, dur, params) { 49 | var n = { pitch: pitch, duration: dur || 1 } 50 | if (params) Object.assign(n, params) 51 | return n 52 | } 53 | 54 | /** 55 | * This is an utility function to create array of notes quickly. 56 | * 57 | * @param {Array|String} pitches - the pitches of the notes 58 | * @param {Array|String} durations - the durations of the notes 59 | * @param {Hash} attributes - the attributes of the notes 60 | * @api private 61 | */ 62 | function notes (pitches, durations, params) { 63 | var p = toArray(pitches || null) 64 | var d = toArray(durations || 1) 65 | return p.map(function (pitch, i) { 66 | return score.note(pitch, +d[i % d.length], params) 67 | }) 68 | } 69 | 70 | // convert anything to an array 71 | // it its a string, split it by spaces 72 | function toArray (obj) { 73 | if (Array.isArray(obj)) return obj 74 | else if (typeof obj === 'string') return obj.trim().split(/\s+/) 75 | else return [ obj ] 76 | } 77 | 78 | /** 79 | * Create a sequential musical structure 80 | * 81 | * @example 82 | * score.seq(score.note('A'), score.note('B')) 83 | */ 84 | score.seq = structureAs('seq') 85 | 86 | /** 87 | * Create a parallel musical structure 88 | * 89 | * @example 90 | * score.par(score.note('A'), score.note('B')) 91 | */ 92 | score.par = structureAs('sim') 93 | 94 | // Create musical structures from function arguments 95 | function structureAs (name) { 96 | return function () { 97 | return [name].concat(isCollection(arguments[0]) ? arguments[0] : slice.call(arguments)) 98 | } 99 | } 100 | 101 | /** 102 | * Create a phrase (a sequential structure of notes) 103 | * 104 | * @param {String|Array} pitches - the phrase note pitches 105 | * @param {String|Array} durations - the phrase note durations 106 | * @param {Hash} attributes - the phrase note attributes 107 | * @return {Array} a sequential musical structure 108 | * 109 | * @example 110 | * score.phrase('A B C D E', 1) 111 | */ 112 | score.phrase = function (p, d, a) { return score.seq(notes(p, d, a)) } 113 | 114 | /** 115 | * Create a chord (a parallel structure of notes) 116 | * 117 | * @param {String|Array} pitches - the chord note pitches 118 | * @param {String|Array} durations - the chord note durations 119 | * @param {Hash} attributes - the chord note attributes 120 | * @return {Array} a parallel musical structure 121 | * 122 | * @example 123 | * score.phrase('A B C D E', 1) 124 | */ 125 | score.chord = function (p, d, a) { return score.par(notes(p, d, a)) } 126 | 127 | /** 128 | * Transform a musical structure 129 | * 130 | * This is probably the most important function. It allows complex 131 | * transformations of musical structures using three functions 132 | */ 133 | score.transform = function (nt, st, pt, obj, ctx) { 134 | if (arguments.length > 3) return score.transform(nt, st, pt)(obj, ctx) 135 | 136 | var T = function (obj, ctx) { 137 | switch (typeOf(obj)) { 138 | case 'event': return nt(obj, ctx) 139 | case 'seq': return st(obj.slice(1).map(T), ctx) 140 | case 'sim': return pt(obj.slice(1).map(T), ctx) 141 | default: return obj 142 | } 143 | } 144 | return T 145 | } 146 | 147 | /** 148 | * Map the notes of a musical structure using a function 149 | * 150 | * @param {Function} fn - the function used to map the notes 151 | * @param {Score} score - the score to transform 152 | * @param {Object} ctx - (Optional) a context object passed to the function 153 | * @return {Score} the transformed score 154 | */ 155 | score.map = function (nt, obj, ctx) { 156 | if (arguments.length > 1) return score.map(nt)(obj, ctx) 157 | return score.transform(nt, score.seq, score.par) 158 | } 159 | 160 | module.exports = score 161 | -------------------------------------------------------------------------------- /dist/score.js: -------------------------------------------------------------------------------- 1 | (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 1) data = build['sim'](slice.call(arguments)) 24 | return exec({}, build, data) 25 | } 26 | 27 | build.score = build 28 | modules.forEach(function (m) { Object.assign(build, m) }) 29 | return build 30 | } 31 | 32 | var VAR = { type: 'var' } 33 | function exec (ctx, fns, data) { 34 | var elements, params 35 | var fnName = data[0] 36 | var fn = fns[fnName] 37 | 38 | if (fnName[0] === '$') { 39 | ctx[fnName] = exec(ctx, fns, data[1]) 40 | return VAR 41 | } else if (!fn) { 42 | throw Error('Command not found: "' + fnName + '"') 43 | } else { 44 | elements = data.slice(1) 45 | params = elements.map(function (p) { 46 | return Array.isArray(p) ? exec(ctx, fns, p) 47 | : (p[0] === '$') ? ctx[p] 48 | : p 49 | }).filter(function (p) { return p !== VAR }) 50 | return fn.apply(null, params) 51 | } 52 | } 53 | 54 | module.exports = { init: init } 55 | 56 | },{}],3:[function(require,module,exports){ 57 | var score = require('./score') 58 | var measures = require('./measures').measures 59 | var getChord = require('chord-dictionary') 60 | 61 | /** 62 | * Create a chord names sequence 63 | * 64 | * @param {String} meter - the meter used in the measures 65 | * @param {String} measures - the chords 66 | * @param {Sequence} a sequence of chords 67 | * 68 | * @example 69 | * score.chords('4/4', 'C6 | Dm7 G7 | Cmaj7') 70 | */ 71 | function chords (meter, data) { 72 | return measures(meter, data, function (dur, el) { 73 | return score.el({ duration: dur, chord: el }) 74 | }) 75 | } 76 | 77 | /** 78 | * Convert a chord names sequence into a chord notes sequence 79 | */ 80 | var expandChords = score.map(function (el) { 81 | return el.chord 82 | ? score.sim(getChord(el.chord).map(score.note(el.duration))) : el 83 | }) 84 | 85 | /** 86 | * Create a harmony sequence 87 | */ 88 | function harmony (meter, data) { 89 | return expandChords(chords(meter, data)) 90 | } 91 | 92 | module.exports = { chords: chords, expandChords: expandChords, harmony: harmony } 93 | 94 | },{"./measures":4,"./score":7,"chord-dictionary":10}],4:[function(require,module,exports){ 95 | var score = require('./score') 96 | 97 | /** 98 | * Parse masures using a time meter to get a sequence 99 | * 100 | * @param {String} meter - the time meter 101 | * @param {String} measures - the measures string 102 | * @param {Function} builder - (Optional) the function used to build the notes 103 | * @return {Score} the score object 104 | * 105 | * @example 106 | * measures('4/4', 'c d (e f) | g | (a b c) d') 107 | */ 108 | function measures (meter, measures, builder) { 109 | var list 110 | var mLen = measureLength(meter) 111 | if (!mLen) throw Error('Not valid meter: ' + meter) 112 | 113 | var seq = [] 114 | builder = builder || score.note 115 | splitMeasures(measures).forEach(function (measure) { 116 | measure = measure.trim() 117 | if (measure.length > 0) { 118 | list = parenthesize(tokenize(measure), []) 119 | processList(seq, list, measureLength(meter), builder) 120 | } 121 | }) 122 | return score.seq(seq) 123 | } 124 | 125 | function measureLength (meter) { 126 | return 4 127 | } 128 | 129 | function processList (seq, list, total, builder) { 130 | var dur = total / list.length 131 | list.forEach(function (i) { 132 | if (Array.isArray(i)) processList(seq, i, dur, builder) 133 | else seq.push(builder(dur, i)) 134 | }) 135 | } 136 | 137 | function splitMeasures (repr) { 138 | return repr 139 | .replace(/\s+\||\|\s+/, '|') // spaces between | 140 | .replace(/^\||\|\s*$/g, '') // first and last | 141 | .split('|') 142 | } 143 | 144 | /* 145 | * The following code is copied from https://github.com/maryrosecook/littlelisp 146 | * See: http://maryrosecook.com/blog/post/little-lisp-interpreter 147 | * Thanks Mary Rose Cook! 148 | */ 149 | var parenthesize = function (input, list) { 150 | var token = input.shift() 151 | if (token === undefined) { 152 | return list 153 | } else if (token === '(') { 154 | list.push(parenthesize(input, [])) 155 | return parenthesize(input, list) 156 | } else if (token === ')') { 157 | return list 158 | } else { 159 | return parenthesize(input, list.concat(token)) 160 | } 161 | } 162 | 163 | var tokenize = function (input) { 164 | return input 165 | .replace(/[\(]/g, ' ( ') 166 | .replace(/[\)]/g, ' ) ') 167 | .replace(/\,/g, ' ') 168 | .trim().split(/\s+/) 169 | } 170 | 171 | module.exports = { measures: measures, melody: measures } 172 | 173 | },{"./score":7}],5:[function(require,module,exports){ 174 | var score = require('./score') 175 | var tr = require('note-transposer') 176 | 177 | // ======== UTILITY ======== 178 | // This is an utility function to create array of notes quickly. 179 | function notes (pitches, durations, params) { 180 | var p = toArray(pitches || null) 181 | var d = toArray(durations || 1) 182 | return p.map(function (pitch, i) { 183 | return score.note(+d[i % d.length], pitch, params) 184 | }) 185 | } 186 | 187 | // convert anything to an array (if string, split it) 188 | function toArray (obj) { 189 | if (Array.isArray(obj)) return obj 190 | else if (typeof obj === 'string') return obj.trim().split(/\s+/) 191 | else return [ obj ] 192 | } 193 | 194 | // ======= API ======== 195 | 196 | /** 197 | * Create a phrase (a sequential structure of notes) 198 | * 199 | * @param {String|Array} pitches - the phrase note pitches 200 | * @param {String|Array} durations - the phrase note durations 201 | * @param {Hash} attributes - the phrase note attributes 202 | * @return {Array} a sequential musical structure 203 | * 204 | * @example 205 | * score.phrase('A B C D E', 1) 206 | */ 207 | function phrase (p, d, a) { return score.seq(notes(p, d, a)) } 208 | 209 | /** 210 | * Create a collection of simultaneus notes 211 | * 212 | * You can specify a collection of pitches, durations and attributes 213 | * and `chord` will arrange them as a collection of notes in simultaneus 214 | * layout 215 | * 216 | * @param {String|Array} pitches - the chord note pitches 217 | * @param {String|Array} durations - the chord note durations 218 | * @param {Hash} attributes - the chord note attributes 219 | * @return {Array} a parallel musical structure 220 | * 221 | * @example 222 | * score.phrase('A B C D E', 1) 223 | */ 224 | function chord (p, d, a) { return score.sim(notes(p, d, a)) } 225 | 226 | /** 227 | * Transpose notes 228 | * 229 | * @param {String} interval - the interval to transpose 230 | * @param {Object} score - the score object 231 | * @return {Score} the score with the notes transposed 232 | */ 233 | function trans (interval, obj) { 234 | if (arguments.length > 1) return trans(interval)(obj) 235 | return score.map(function (note) { 236 | return note.pitch ? score.el(note, { pitch: tr(interval, note.pitch) }) 237 | : note 238 | }) 239 | } 240 | 241 | module.exports = { phrase: phrase, chord: chord, trans: trans } 242 | 243 | },{"./score":7,"note-transposer":17}],6:[function(require,module,exports){ 244 | var score = require('./score') 245 | 246 | /** 247 | * @module rhythm 248 | */ 249 | var rhythm = {} 250 | 251 | /** 252 | * Create a rhythmic sequence from a pattern 253 | */ 254 | rhythm.pattern = function (pattern) { 255 | return score.seq(pattern.split('').map(score.note(1))) 256 | } 257 | 258 | /** 259 | * Create a rhythmic sequence from an inter onset interval number 260 | */ 261 | rhythm.ioi = function (ioi) { 262 | return rhythm.pattern(rhythm.ioiToPattern(ioi)) 263 | } 264 | 265 | /** 266 | * Convert an [inter onset interval](https://en.wikipedia.org/wiki/Time_point#Interonset_interval) 267 | * to a pattern 268 | * 269 | * @param {String} ioi - the inter onset interval 270 | * @param {String} the rhythm pattern 271 | */ 272 | rhythm.ioiToPattern = function (num) { 273 | return num.split('').map(function (n) { 274 | return 'x' + Array(+n).join('.') 275 | }).join('') 276 | } 277 | 278 | /** 279 | * Convert a pattern string to inter onset interval string 280 | * 281 | * @param {String} pattern - the pattern to be converted 282 | * @return {String} the inter onset interval 283 | */ 284 | rhythm.patternToIoi = function (pattern) { 285 | return pattern.split(/x/) 286 | .map(function (d) { return d.length }) 287 | .filter(function (_, i) { return i }) // remove first 288 | .map(function (d) { return d + 1 }) 289 | .join('') 290 | } 291 | 292 | module.exports = rhythm 293 | 294 | },{"./score":7}],7:[function(require,module,exports){ 295 | 'use strict' 296 | 297 | var isArray = Array.isArray 298 | var slice = Array.prototype.slice 299 | var assign = Object.assign 300 | function typeOf (obj) { return isArray(obj) ? obj[0] : 'el' } 301 | function isStruct (e) { return isArray(e) && typeof e[0] === 'string' } 302 | // create a sequence builder 303 | function builder (name) { 304 | return function (elements) { 305 | if (arguments.length > 1) return [name].concat(slice.call(arguments)) 306 | else if (isStruct(elements)) return [name, elements] 307 | return [name].concat(elements) 308 | } 309 | } 310 | 311 | /** 312 | * Create a score element: an object with duration 313 | * 314 | * It's accepts any data you supply, but duration property has a special 315 | * meaning: it's a number representing the duration in arbitrary units. 316 | * It's assumed to be 0 (no duration) if not present or not a valid number 317 | * 318 | * @param {Number} duration - the element duration 319 | * @param {Object} data - the additional element data 320 | */ 321 | function el (d, data) { 322 | if (typeof d === 'object') return assign({}, d, data) 323 | else return assign({ duration: +d || 0 }, data) 324 | } 325 | 326 | /** 327 | * Create a note from duration and pitch 328 | * 329 | * A note is any object with duration and pitch attributes. The duration 330 | * must be a number, but the pitch can be any value (although only strings with 331 | * scientific notation pitches and midi numbers are recogniced by the manipulation 332 | * or display functions) 333 | * 334 | * If only duration is provided, a partially applied function is returned. 335 | * 336 | * @param {Integer} duration - the note duration 337 | * @param {String|Integer} pitch - the note pitch 338 | * @param {Hash} data - (Optional) arbitraty note data 339 | * @return {Hash} a note 340 | * 341 | * @example 342 | * score.note(1, 'A') // => { duration: 1, pitch: 'A' } 343 | * score.note(0.5, 'anything') // => { duration: 0.5, pitch: 'anything' } 344 | * score.note(2, 'A', 2, { inst: 'piano' }) // => { duration: 2, pitch: 'A', inst: 'piano' } 345 | * 346 | * @example 347 | * // partially applied 348 | * ['C', 'D', 'E'].map(score.note(1)) // => [{ duration: 1, pitch: 'C'}, 349 | * { duration: 1, pitch: 'D'}, { duration: 1, pitch: 'E'}] 350 | */ 351 | function note (dur, pitch, data) { 352 | if (arguments.length === 1) return function (p, d) { return note(dur, p, d) } 353 | return assign({ pitch: pitch, duration: dur || 1 }, data) 354 | } 355 | 356 | /** 357 | * Create a musical structure where elements are sequenetial 358 | * 359 | * @param {Array} elements - an array of elements 360 | * @return {Array} the sequential musical structure 361 | * 362 | * @example 363 | * score.sequential([score.note('A'), score.note('B')]) 364 | */ 365 | var seq = builder('seq') 366 | 367 | /** 368 | * Create a musical structure where elements are simultaneous 369 | * 370 | * @example 371 | * score.sim([score.note('A'), score.note('B')]) 372 | */ 373 | var sim = builder('sim') 374 | 375 | /** 376 | * Transform a musical structure 377 | * 378 | * This is probably the most important function. It allows complex 379 | * transformations of musical structures using three functions 380 | * 381 | * @param {Function} elTransform - element transform function 382 | * @param {Function} seqTransform - sequential structure transform function 383 | * @param {Function} parTransform - simultaneous structure transform function 384 | * @param {Object} score - the score to transform 385 | * @param {*} ctx - an additional object passed to transform functions 386 | * @return {*} the result of the transformation 387 | */ 388 | function transform (nt, st, pt, obj, ctx) { 389 | if (arguments.length > 3) return transform(nt, st, pt)(obj, ctx) 390 | 391 | var T = function (obj, ctx) { 392 | switch (typeOf(obj)) { 393 | case 'el': return nt(obj, ctx) 394 | case 'seq': return st(obj.slice(1).map(T), ctx) 395 | case 'sim': return pt(obj.slice(1).map(T), ctx) 396 | default: return obj 397 | } 398 | } 399 | return T 400 | } 401 | 402 | /** 403 | * Map the notes of a musical structure using a function 404 | * 405 | * @param {Function} fn - the function used to map the notes 406 | * @param {Score} score - the score to transform 407 | * @param {Object} ctx - (Optional) a context object passed to the function 408 | * @return {Score} the transformed score 409 | */ 410 | function map (fn, obj, ctx) { 411 | if (arguments.length > 1) return map(fn)(obj, ctx) 412 | return transform(fn, buildSeq, buildSim) 413 | } 414 | function buildSeq (el, ctx) { return seq(el) } 415 | function buildSim (el, ctx) { return sim(el) } 416 | 417 | module.exports = { 418 | el: el, note: note, 419 | seq: seq, sequentially: seq, 420 | sim: sim, simultaneosly: sim, 421 | transform: transform, map: map } 422 | 423 | },{}],8:[function(require,module,exports){ 424 | var score = require('./score') 425 | 426 | /** 427 | * Get all notes for side-effects 428 | * 429 | * __Important:__ ascending time ordered is not guaranteed 430 | * 431 | * @param {Function} fn - the function 432 | * @param {Score} score - the score object 433 | * @param {Object} ctx - (Optional) a context object passed to the function 434 | */ 435 | function forEachTime (fn, obj, ctx) { 436 | if (arguments.length > 1) return forEachTime(fn)(obj, ctx) 437 | return function (obj, ctx) { 438 | return score.transform( 439 | function (note) { 440 | return function (time, ctx) { 441 | fn(time, note, ctx) 442 | return note.duration 443 | } 444 | }, 445 | function (seq) { 446 | return function (time, ctx) { 447 | return seq.reduce(function (dur, fn) { 448 | return dur + fn(time + dur, ctx) 449 | }, 0) 450 | } 451 | }, 452 | function (par) { 453 | return function (time, ctx) { 454 | return par.reduce(function (max, fn) { 455 | return Math.max(max, fn(time, ctx)) 456 | }, 0) 457 | } 458 | } 459 | )(obj)(0, ctx) 460 | } 461 | } 462 | 463 | /** 464 | * Get a sorted events array from a score 465 | * 466 | */ 467 | function events (obj, build, compare) { 468 | var e = [] 469 | forEachTime(function (time, obj) { 470 | e.push(build ? build(time, obj) : [time, obj]) 471 | }, obj) 472 | return e.sort(compare || function (a, b) { return a[0] - b[0] }) 473 | } 474 | 475 | module.exports = { forEachTime: forEachTime, events: events } 476 | 477 | },{"./score":7}],9:[function(require,module,exports){ 478 | module.exports={ 479 | "4": [ "1 4 7b 10m", [ "quartal" ] ], 480 | "5": [ "1 5" ], 481 | 482 | "M": [ "1 3 5", [ "Major", "" ] ], 483 | "M#5": [ "1 3 5A", [ "augmented", "maj#5", "Maj#5", "+", "aug" ] ], 484 | "M#5add9": [ "1 3 5A 9", [ "+add9" ] ], 485 | "M13": [ "1 3 5 7 9 13", [ "maj13", "Maj13" ] ], 486 | "M13#11": [ "1 3 5 7 9 11# 13", [ "maj13#11", "Maj13#11", "M13+4", "M13#4" ] ], 487 | "M6": [ "1 3 5 13", [ "6" ] ], 488 | "M6#11": [ "1 3 5 6 11#", [ "M6b5", "6#11", "6b5" ] ], 489 | "M69": [ "1 3 5 6 9", [ "69" ] ], 490 | "M69#11": [ "1 3 5 6 9 11#" ], 491 | "M7#11": [ "1 3 5 7 11#", [ "maj7#11", "Maj7#11", "M7+4", "M7#4" ] ], 492 | "M7#5": [ "1 3 5A 7", [ "maj7#5", "Maj7#5", "maj9#5", "M7+" ] ], 493 | "M7#5sus4": [ "1 4 5A 7" ], 494 | "M7#9#11": [ "1 3 5 7 9# 11#" ], 495 | "M7add13": [ "1 3 5 6 7 9" ], 496 | "M7b5": [ "1 3 5d 7" ], 497 | "M7b6": [ "1 3 6b 7" ], 498 | "M7b9": [ "1 3 5 7 9b" ], 499 | "M7sus4": [ "1 4 5 7" ], 500 | "M9": [ "1 3 5 7 9", [ "maj9", "Maj9" ] ], 501 | "M9#11": [ "1 3 5 7 9 11#", [ "maj9#11", "Maj9#11", "M9+4", "M9#4" ] ], 502 | "M9#5": [ "1 3 5A 7 9", [ "Maj9#5" ] ], 503 | "M9#5sus4": [ "1 4 5A 7 9" ], 504 | "M9b5": [ "1 3 5d 7 9" ], 505 | "M9sus4": [ "1 4 5 7 9" ], 506 | "Madd9": [ "1 3 5 9", [ "2", "add9", "add2" ] ], 507 | "Maj7": [ "1 3 5 7", [ "maj7", "M7" ] ], 508 | "Mb5": [ "1 3 5d" ], 509 | "Mb6": [ "1 3 13b" ], 510 | "Msus2": [ "1 2M 5", [ "add9no3", "sus2" ] ], 511 | "Msus4": [ "1 4 5", [ "sus", "sus4" ] ], 512 | "addb9": [ "1 3 5 9b" ], 513 | "7": [ "1 3 5 7b", [ "Dominant", "Dom" ] ], 514 | "9": [ "1 3 5 7b 9", [ "79" ] ], 515 | "11": [ "1 5 7b 9 11" ], 516 | "13": [ "1 3 5 7b 9 13", [ "13_" ] ], 517 | "11b9": [ "1 5 7b 9b 11" ], 518 | "13#11": [ "1 3 5 7b 9 11# 13", [ "13+4", "13#4" ] ], 519 | "13#9": [ "1 3 5 7b 9# 13", [ "13#9_" ] ], 520 | "13#9#11": [ "1 3 5 7b 9# 11# 13" ], 521 | "13b5": [ "1 3 5d 6 7b 9" ], 522 | "13b9": [ "1 3 5 7b 9b 13" ], 523 | "13b9#11": [ "1 3 5 7b 9b 11# 13" ], 524 | "13no5": [ "1 3 7b 9 13" ], 525 | "13sus4": [ "1 4 5 7b 9 13", [ "13sus" ] ], 526 | "69#11": [ "1 3 5 6 9 11#" ], 527 | "7#11": [ "1 3 5 7b 11#", [ "7+4", "7#4", "7#11_", "7#4_" ] ], 528 | "7#11b13": [ "1 3 5 7b 11# 13b", [ "7b5b13" ] ], 529 | "7#5": [ "1 3 5A 7b", [ "+7", "7aug", "aug7" ] ], 530 | "7#5#9": [ "1 3 5A 7b 9#", [ "7alt", "7#5#9_", "7#9b13_" ] ], 531 | "7#5b9": [ "1 3 5A 7b 9b" ], 532 | "7#5b9#11": [ "1 3 5A 7b 9b 11#" ], 533 | "7#5sus4": [ "1 4 5A 7b" ], 534 | "7#9": [ "1 3 5 7b 9#", [ "7#9_" ] ], 535 | "7#9#11": [ "1 3 5 7b 9# 11#", [ "7b5#9" ] ], 536 | "7#9#11b13": [ "1 3 5 7b 9# 11# 13b" ], 537 | "7#9b13": [ "1 3 5 7b 9# 13b" ], 538 | "7add6": [ "1 3 5 7b 13", [ "67", "7add13" ] ], 539 | "7b13": [ "1 3 7b 13b" ], 540 | "7b5": [ "1 3 5d 7b" ], 541 | "7b6": [ "1 3 5 6b 7b" ], 542 | "7b9": [ "1 3 5 7b 9b" ], 543 | "7b9#11": [ "1 3 5 7b 9b 11#", [ "7b5b9" ] ], 544 | "7b9#9": [ "1 3 5 7b 9b 9#" ], 545 | "7b9b13": [ "1 3 5 7b 9b 13b" ], 546 | "7b9b13#11": [ "1 3 5 7b 9b 11# 13b", [ "7b9#11b13", "7b5b9b13" ] ], 547 | "7no5": [ "1 3 7b" ], 548 | "7sus4": [ "1 4 5 7b", [ "7sus" ] ], 549 | "7sus4b9": [ "1 4 5 7b 9b", [ "susb9", "7susb9", "7b9sus", "7b9sus4", "phryg" ] ], 550 | "7sus4b9b13": [ "1 4 5 7b 9b 13b", [ "7b9b13sus4" ] ], 551 | "9#11": [ "1 3 5 7b 9 11#", [ "9+4", "9#4", "9#11_", "9#4_" ] ], 552 | "9#11b13": [ "1 3 5 7b 9 11# 13b", [ "9b5b13" ] ], 553 | "9#5": [ "1 3 5A 7b 9", [ "9+" ] ], 554 | "9#5#11": [ "1 3 5A 7b 9 11#" ], 555 | "9b13": [ "1 3 7b 9 13b" ], 556 | "9b5": [ "1 3 5d 7b 9" ], 557 | "9no5": [ "1 3 7b 9" ], 558 | "9sus4": [ "1 4 5 7b 9", [ "9sus" ] ], 559 | "m": [ "1 3b 5", [ "minor" ] ], 560 | "m#5": [ "1 3b 5A", [ "m+", "mb6" ] ], 561 | "m11": [ "1 3b 5 7b 9 11", [ "_11" ] ], 562 | "m11#5": [ "1 3b 6b 7b 9 11" ], 563 | "m11b5": [ "1 3b 7b 12d 2M 4", [ "h11", "_11b5" ] ], 564 | "m13": [ "1 3b 5 7b 9 11 13", [ "_13" ] ], 565 | "m6": [ "1 3b 4 5 13", [ "_6" ] ], 566 | "m69": [ "1 3b 5 6 9", [ "_69" ] ], 567 | "m7": [ "1 3b 5 7b", [ "minor7", "_", "_7" ] ], 568 | "m7#5": [ "1 3b 6b 7b" ], 569 | "m7add11": [ "1 3b 5 7b 11", [ "m7add4" ] ], 570 | "m7b5": [ "1 3b 5d 7b", [ "half-diminished", "h7", "_7b5" ] ], 571 | "m9": [ "1 3b 5 7b 9", [ "_9" ] ], 572 | "m9#5": [ "1 3b 6b 7b 9" ], 573 | "m9b5": [ "1 3b 7b 12d 2M", [ "h9", "-9b5" ] ], 574 | "mMaj7": [ "1 3b 5 7", [ "mM7", "_M7" ] ], 575 | "mMaj7b6": [ "1 3b 5 6b 7", [ "mM7b6" ] ], 576 | "mM9": [ "1 3b 5 7 9", [ "mMaj9", "-M9" ] ], 577 | "mM9b6": [ "1 3b 5 6b 7 9", [ "mMaj9b6" ] ], 578 | "mb6M7": [ "1 3b 6b 7" ], 579 | "mb6b9": [ "1 3b 6b 9b" ], 580 | "o": [ "1 3b 5d", [ "mb5", "dim" ] ], 581 | "o7": [ "1 3b 5d 13", [ "diminished", "m6b5", "dim7" ] ], 582 | "o7M7": [ "1 3b 5d 6 7" ], 583 | "oM7": [ "1 3b 5d 7" ], 584 | "sus24": [ "1 2M 4 5", [ "sus4add9" ] ], 585 | "+add#9": [ "1 3 5A 9#" ], 586 | "madd4": [ "1 3b 4 5" ], 587 | "madd9": [ "1 3b 5 9" ] 588 | } 589 | 590 | },{}],10:[function(require,module,exports){ 591 | 'use strict' 592 | 593 | var chords = require('./chords.json') 594 | var dictionary = require('music-dictionary') 595 | 596 | /** 597 | * A chord dictionary. Get chord data from a chord name. 598 | * 599 | * @name chord 600 | * @function 601 | * @param {String} name - the chord name 602 | * @see music-dictionary 603 | * 604 | * @example 605 | * // get chord data 606 | * var chord = require('chord-dictionary') 607 | * chord('Maj7') // => { name: 'Maj7', aliases: ['M7', 'maj7'] 608 | * // intervals: [ ...], 609 | * // binary: '100010010001', decimal: 2193 } 610 | * 611 | * @example 612 | * // get it from aliases, binary or decimal numbers 613 | * chord('Maj7') === chord('M7') === chord('100010010001') === chord(2913) 614 | * 615 | * @example 616 | * // get chord names 617 | * chord.names // => ['Maj7', 'm7', ...] 618 | */ 619 | module.exports = dictionary(chords) 620 | 621 | },{"./chords.json":9,"music-dictionary":11}],11:[function(require,module,exports){ 622 | 'use strict' 623 | 624 | var parse = require('music-notation/interval/parse') 625 | var R = require('music-notation/note/regex') 626 | var transpose = require('note-transposer') 627 | 628 | /** 629 | * Create a musical dictionary. A musical dictionary is a function that given 630 | * a name (and optionally a tonic) returns an array of notes. 631 | * 632 | * A dictionary is created from a HashMap. It maps a name to a string with 633 | * an interval list and, optionally, an alternative name list (see example) 634 | * 635 | * Additionally, the dictionary has properties (see examples): 636 | * 637 | * - data: a hash with the dictionary data 638 | * - names: an array with all the names 639 | * - aliases: an array with all the names including aliases 640 | * - source: the source of the dictionary 641 | * 642 | * Each value of the data hash have the following properties: 643 | * 644 | * - name: the name 645 | * - aliases: an array with the alternative names 646 | * - intervals: an array with the intervals 647 | * - steps: an array with the intervals in __array notation__ 648 | * - binary: a binary representation of the set 649 | * - decimal: the decimal representation of the set 650 | * 651 | * @name dictionary 652 | * @function 653 | * @param {Hash} source - the dictionary source 654 | * @return {Function} the dictionary 655 | * 656 | * @example 657 | * var dictionary = require('music-dictionary') 658 | * var chords = dictionary({'Maj7': ['1 3 5 7', ['M7']], 'm7': ['1 3b 5 7b'] }) 659 | * chords('CMaj7') // => ['C', 'E', 'G', 'B'] 660 | * chords('DM7') // => ['D', 'F#', 'A', 'C#'] 661 | * chords('Bm7') // => ['B', 'D', 'F#', 'A'] 662 | * 663 | * @example 664 | * // dictionary data 665 | * chords.data['M7'] // => { name: 'Maj7', aliases: ['M7'], 666 | * // intervals: ['1', '3', '5', '7'], steps: [ ...], 667 | * // binary: '10010010001', decimal: 2193 } 668 | * 669 | * // get chord by binary numbers 670 | * chords.data['100010010001'] === chords.data['Maj7'] 671 | * chords.data[2193] === chords.data['Maj7'] 672 | * 673 | * @example 674 | * // available names 675 | * chords.names // => ['Maj7', 'm7'] 676 | * chords.aliases // => ['Maj7', 'm7', 'M7'] 677 | */ 678 | module.exports = function (src) { 679 | function dict (name, tonic) { 680 | var v = dict.props(name) 681 | if (!v) { 682 | var n = R.exec(name) 683 | v = n ? dict.props(n[5]) : null 684 | if (!v) return [] 685 | tonic = tonic === false ? tonic : tonic || n[1] + n[2] + n[3] 686 | } 687 | if (tonic !== false && !tonic) return function (t) { return dict(name, t) } 688 | return v.intervals.map(transpose(tonic)) 689 | } 690 | return build(src, dict) 691 | } 692 | 693 | function build (src, dict) { 694 | var data = {} 695 | var names = Object.keys(src) 696 | var aliases = names.slice() 697 | 698 | dict.props = function (name) { return data[name] } 699 | dict.names = function (a) { return (a ? aliases : names).slice() } 700 | 701 | names.forEach(function (k) { 702 | var d = src[k] 703 | var c = { name: k, aliases: d[1] || [] } 704 | c.intervals = d[0].split(' ') 705 | c.steps = c.intervals.map(parse) 706 | c.binary = binary([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], c.steps) 707 | c.decimal = parseInt(c.binary, 2) 708 | data[k] = data[c.binary] = data[c.decimal] = c 709 | c.aliases.forEach(function (a) { data[a] = c }) 710 | if (c.aliases.length > 0) aliases = aliases.concat(c.aliases) 711 | }) 712 | return dict 713 | } 714 | 715 | function binary (num, intervals) { 716 | intervals.forEach(function (i) { num[(i[0] * 7 + i[1] * 12) % 12] = '1' }) 717 | return num.join('') 718 | } 719 | 720 | },{"music-notation/interval/parse":13,"music-notation/note/regex":16,"note-transposer":17}],12:[function(require,module,exports){ 721 | 'use strict' 722 | 723 | // map from pitch number to number of fifths and octaves 724 | var BASES = [ [0, 0], [2, -1], [4, -2], [-1, 1], [1, 0], [3, -1], [5, -2] ] 725 | 726 | /** 727 | * Get a pitch in [array notation]() from pitch properties 728 | * 729 | * @name array.fromProps 730 | * @function 731 | * @param {Integer} step - the step index 732 | * @param {Integer} alterations - (Optional) the alterations number 733 | * @param {Integer} octave - (Optional) the octave 734 | * @param {Integer} duration - (Optional) duration 735 | * @return {Array} the pitch in array format 736 | * 737 | * @example 738 | * var fromProps = require('music-notation/array/from-props') 739 | * fromProps([0, 1, 4, 0]) 740 | */ 741 | module.exports = function (step, alt, oct, dur) { 742 | var base = BASES[step] 743 | alt = alt || 0 744 | var f = base[0] + 7 * alt 745 | if (typeof oct === 'undefined') return [f] 746 | var o = oct + base[1] - 4 * alt 747 | if (typeof dur === 'undefined') return [f, o] 748 | else return [f, o, dur] 749 | } 750 | 751 | },{}],13:[function(require,module,exports){ 752 | 'use strict' 753 | 754 | var memoize = require('../memoize') 755 | var fromProps = require('../array/from-props') 756 | var INTERVAL = require('./regex') 757 | var TYPES = 'PMMPPMM' 758 | var QALT = { 759 | P: { dddd: -4, ddd: -3, dd: -2, d: -1, P: 0, A: 1, AA: 2, AAA: 3, AAAA: 4 }, 760 | M: { ddd: -4, dd: -3, d: -2, m: -1, M: 0, A: 1, AA: 2, AAA: 3, AAAA: 4 } 761 | } 762 | 763 | /** 764 | * Parse a [interval shorthand notation](https://en.wikipedia.org/wiki/Interval_(music)#Shorthand_notation) 765 | * to [interval coord notation](https://github.com/danigb/music.array.notation) 766 | * 767 | * This function is cached for better performance. 768 | * 769 | * @name interval.parse 770 | * @function 771 | * @param {String} interval - the interval string 772 | * @return {Array} the interval in array notation or null if not a valid interval 773 | * 774 | * @example 775 | * var parse = require('music-notation/interval/parse') 776 | * parse('3m') // => [2, -1, 0] 777 | * parse('9b') // => [1, -1, 1] 778 | * parse('-2M') // => [6, -1, -1] 779 | */ 780 | module.exports = memoize(function (str) { 781 | var m = INTERVAL.exec(str) 782 | if (!m) return null 783 | var dir = (m[2] || m[7]) === '-' ? -1 : 1 784 | var num = +(m[3] || m[8]) - 1 785 | var q = m[4] || m[6] || '' 786 | 787 | var simple = num % 7 788 | 789 | var alt 790 | if (q === '') alt = 0 791 | else if (q[0] === '#') alt = q.length 792 | else if (q[0] === 'b') alt = -q.length 793 | else { 794 | alt = QALT[TYPES[simple]][q] 795 | if (typeof alt === 'undefined') return null 796 | } 797 | var oct = Math.floor(num / 7) 798 | var arr = fromProps(simple, alt, oct) 799 | return dir === 1 ? arr : [-arr[0], -arr[1]] 800 | }) 801 | 802 | },{"../array/from-props":12,"../memoize":15,"./regex":14}],14:[function(require,module,exports){ 803 | 804 | // shorthand tonal notation (with quality after number) 805 | var TONAL = '([-+]?)(\\d+)(d{1,4}|m|M|P|A{1,4}|b{1,4}|#{1,4}|)' 806 | // strict shorthand notation (with quality before number) 807 | var STRICT = '(AA|A|P|M|m|d|dd)([-+]?)(\\d+)' 808 | var COMPOSE = '(?:(' + TONAL + ')|(' + STRICT + '))' 809 | 810 | /** 811 | * A regex for parse intervals in shorthand notation 812 | * 813 | * Three different shorthand notations are supported: 814 | * 815 | * - default [direction][number][quality]: the preferred style `3M`, `-5A` 816 | * - strict: [quality][direction][number], for example: `M3`, `A-5` 817 | * - altered: [direction][number][alterations]: `3`, `-5#` 818 | * 819 | * @name interval.regex 820 | */ 821 | module.exports = new RegExp('^' + COMPOSE + '$') 822 | 823 | },{}],15:[function(require,module,exports){ 824 | 'use strict' 825 | 826 | /** 827 | * A simple and fast memoization function 828 | * 829 | * It helps creating functions that convert from string to pitch in array format. 830 | * Basically it does two things: 831 | * - ensure the function only receives strings 832 | * - memoize the result 833 | * 834 | * @name memoize 835 | * @function 836 | * @private 837 | */ 838 | module.exports = function (fn) { 839 | var cache = {} 840 | return function (str) { 841 | if (typeof str !== 'string') return null 842 | return (str in cache) ? cache[str] : cache[str] = fn(str) 843 | } 844 | } 845 | 846 | },{}],16:[function(require,module,exports){ 847 | 'use strict' 848 | 849 | /** 850 | * A regex for matching note strings in scientific notation. 851 | * 852 | * The note string should have the form `letter[accidentals][octave][/duration]` 853 | * where: 854 | * 855 | * - letter: (Required) is a letter from A to G either upper or lower case 856 | * - accidentals: (Optional) can be one or more `b` (flats), `#` (sharps) or `x` (double sharps). 857 | * They can NOT be mixed. 858 | * - octave: (Optional) a positive or negative integer 859 | * - duration: (Optional) anything follows a slash `/` is considered to be the duration 860 | * - element: (Optional) additionally anything after the duration is considered to 861 | * be the element name (for example: 'C2 dorian') 862 | * 863 | * @name note.regex 864 | * @example 865 | * var R = require('music-notation/note/regex') 866 | * R.exec('c#4') // => ['c#4', 'c', '#', '4', '', ''] 867 | */ 868 | module.exports = /^([a-gA-G])(#{1,}|b{1,}|x{1,}|)(-?\d*)(\/\d+|)\s*(.*)\s*$/ 869 | 870 | },{}],17:[function(require,module,exports){ 871 | var parse = require('music-notation/pitch/parse') 872 | var str = require('music-notation/pitch/str') 873 | var operation = require('music-notation/operation')(parse, str) 874 | 875 | /** 876 | * Transposes a note by an interval. 877 | * 878 | * Given a note and an interval it returns the transposed note. It can be used 879 | * to add intervals if both parameters are intervals. 880 | * 881 | * The order of the parameters is indifferent. 882 | * 883 | * This function is currified so it can be used to map arrays of notes. 884 | * 885 | * @name transpose 886 | * @function 887 | * @param {String|Array} interval - the interval. If its false, the note is not 888 | * transposed. 889 | * @param {String|Array} note - the note to transpose 890 | * @return {String|Array} the note transposed 891 | * 892 | * @example 893 | * var transpose = require('note-transposer') 894 | * transpose('3m', 'C4') // => 'Eb4' 895 | * transpose('C4', '3m') // => 'Eb4' 896 | * tranpose([1, 0, 2], [3, -1, 0]) // => [3, 0, 2] 897 | * ['C', 'D', 'E'].map(transpose('3M')) // => ['E', 'F#', 'G#'] 898 | */ 899 | var transpose = operation(function (i, n) { 900 | if (i === false) return n 901 | else if (!Array.isArray(i) || !Array.isArray(n)) return null 902 | else if (i.length === 1 || n.length === 1) return [n[0] + i[0]] 903 | var d = i.length === 2 && n.length === 2 ? null : n[2] || i[2] 904 | return [n[0] + i[0], n[1] + i[1], d] 905 | }) 906 | 907 | if (typeof module === 'object' && module.exports) module.exports = transpose 908 | if (typeof window !== 'undefined') window.transpose = transpose 909 | 910 | },{"music-notation/operation":28,"music-notation/pitch/parse":29,"music-notation/pitch/str":30}],18:[function(require,module,exports){ 911 | 'use strict' 912 | 913 | /** 914 | * Build an accidentals string from alteration number 915 | * 916 | * @name accidentals.str 917 | * @param {Integer} alteration - the alteration number 918 | * @return {String} the accidentals string 919 | * 920 | * @example 921 | * var accidentals = require('music-notation/accidentals/str') 922 | * accidentals(0) // => '' 923 | * accidentals(1) // => '#' 924 | * accidentals(2) // => '##' 925 | * accidentals(-1) // => 'b' 926 | * accidentals(-2) // => 'bb' 927 | */ 928 | module.exports = function (num) { 929 | if (num < 0) return Array(-num + 1).join('b') 930 | else if (num > 0) return Array(num + 1).join('#') 931 | else return '' 932 | } 933 | 934 | },{}],19:[function(require,module,exports){ 935 | arguments[4][12][0].apply(exports,arguments) 936 | },{"dup":12}],20:[function(require,module,exports){ 937 | 'use strict' 938 | 939 | // Map from number of fifths to interval number (0-index) and octave 940 | // -1 = fourth, 0 = unison, 1 = fifth, 2 = second, 3 = sixth... 941 | var BASES = [[3, 1], [0, 0], [4, 0], [1, -1], [5, -1], [2, -2], [6, -2], [3, -3]] 942 | 943 | /** 944 | * Get properties from a pitch in array format 945 | * 946 | * The properties is an array with the form [number, alteration, octave, duration] 947 | * 948 | * @name array.toProps 949 | * @function 950 | * @param {Array} array - the pitch in coord format 951 | * @return {Array} the pitch in property format 952 | * 953 | * @example 954 | * var toProps = require('music-notation/array/to-props') 955 | * toProps([2, 1, 4]) // => [1, 2, 4] 956 | */ 957 | module.exports = function (arr) { 958 | if (!Array.isArray(arr)) return null 959 | var index = (arr[0] + 1) % 7 960 | if (index < 0) index = 7 + index 961 | var base = BASES[index] 962 | var alter = Math.floor((arr[0] + 1) / 7) 963 | var oct = arr.length === 1 ? null : arr[1] - base[1] + alter * 4 964 | var dur = arr[2] || null 965 | return [base[0], alter, oct, dur] 966 | } 967 | 968 | },{}],21:[function(require,module,exports){ 969 | arguments[4][13][0].apply(exports,arguments) 970 | },{"../array/from-props":19,"../memoize":24,"./regex":22,"dup":13}],22:[function(require,module,exports){ 971 | arguments[4][14][0].apply(exports,arguments) 972 | },{"dup":14}],23:[function(require,module,exports){ 973 | 'use strict' 974 | 975 | var props = require('../array/to-props') 976 | var cache = {} 977 | 978 | /** 979 | * Get a string with a [shorthand interval notation](https://en.wikipedia.org/wiki/Interval_(music)#Shorthand_notation) 980 | * from interval in [array notation](https://github.com/danigb/music.array.notation) 981 | * 982 | * The returned string has the form: `number + quality` where number is the interval number 983 | * (positive integer for ascending intervals, negative integer for descending intervals, never 0) 984 | * and the quality is one of: 'M', 'm', 'P', 'd', 'A' (major, minor, perfect, dimished, augmented) 985 | * 986 | * @name interval.str 987 | * @function 988 | * @param {Array} interval - the interval in array notation 989 | * @return {String} the interval string in shorthand notation or null if not valid interval 990 | * 991 | * @example 992 | * var str = require('music-notation/interval/str') 993 | * str([1, 0, 0]) // => '2M' 994 | * str([1, 0, 1]) // => '9M' 995 | */ 996 | module.exports = function (arr) { 997 | if (!Array.isArray(arr) || arr.length !== 2) return null 998 | var str = '|' + arr[0] + '|' + arr[1] 999 | return str in cache ? cache[str] : cache[str] = build(arr) 1000 | } 1001 | 1002 | var ALTER = { 1003 | P: ['dddd', 'ddd', 'dd', 'd', 'P', 'A', 'AA', 'AAA', 'AAAA'], 1004 | M: ['ddd', 'dd', 'd', 'm', 'M', 'A', 'AA', 'AAA', 'AAAA'] 1005 | } 1006 | var TYPES = 'PMMPPMM' 1007 | 1008 | function build (coord) { 1009 | var p = props(coord) 1010 | var t = TYPES[p[0]] 1011 | 1012 | var dir, num, alt 1013 | // if its descening, invert number 1014 | if (p[2] < 0) { 1015 | dir = -1 1016 | num = (8 - p[0]) - 7 * (p[2] + 1) 1017 | alt = t === 'P' ? -p[1] : -(p[1] + 1) 1018 | } else { 1019 | dir = 1 1020 | num = p[0] + 1 + 7 * p[2] 1021 | alt = p[1] 1022 | } 1023 | var q = ALTER[t][4 + alt] 1024 | return dir * num + q 1025 | } 1026 | 1027 | },{"../array/to-props":20}],24:[function(require,module,exports){ 1028 | arguments[4][15][0].apply(exports,arguments) 1029 | },{"dup":15}],25:[function(require,module,exports){ 1030 | 'use strict' 1031 | 1032 | var memoize = require('../memoize') 1033 | var R = require('./regex') 1034 | var BASES = { C: [0, 0], D: [2, -1], E: [4, -2], F: [-1, 1], G: [1, 0], A: [3, -1], B: [5, -2] } 1035 | 1036 | /** 1037 | * Get a pitch in [array notation]() 1038 | * from a string in [scientific pitch notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation) 1039 | * 1040 | * The string to parse must be in the form of: `letter[accidentals][octave]` 1041 | * The accidentals can be up to four # (sharp) or b (flat) or two x (double sharps) 1042 | * 1043 | * This function is cached for better performance. 1044 | * 1045 | * @name note.parse 1046 | * @function 1047 | * @param {String} str - the string to parse 1048 | * @return {Array} the note in array notation or null if not valid note 1049 | * 1050 | * @example 1051 | * var parse = require('music-notation/note/parse') 1052 | * parse('C') // => [ 0 ] 1053 | * parse('c#') // => [ 8 ] 1054 | * parse('c##') // => [ 16 ] 1055 | * parse('Cx') // => [ 16 ] (double sharp) 1056 | * parse('Cb') // => [ -6 ] 1057 | * parse('db') // => [ -4 ] 1058 | * parse('G4') // => [ 2, 3, null ] 1059 | * parse('c#3') // => [ 8, -1, null ] 1060 | */ 1061 | module.exports = memoize(function (str) { 1062 | var m = R.exec(str) 1063 | if (!m || m[5]) return null 1064 | 1065 | var base = BASES[m[1].toUpperCase()] 1066 | var alt = m[2].replace(/x/g, '##').length 1067 | if (m[2][0] === 'b') alt *= -1 1068 | var fifths = base[0] + 7 * alt 1069 | if (!m[3]) return [fifths] 1070 | var oct = +m[3] + base[1] - 4 * alt 1071 | var dur = m[4] ? +(m[4].substring(1)) : null 1072 | return [fifths, oct, dur] 1073 | }) 1074 | 1075 | },{"../memoize":24,"./regex":26}],26:[function(require,module,exports){ 1076 | arguments[4][16][0].apply(exports,arguments) 1077 | },{"dup":16}],27:[function(require,module,exports){ 1078 | 'use strict' 1079 | 1080 | var props = require('../array/to-props') 1081 | var acc = require('../accidentals/str') 1082 | var cache = {} 1083 | 1084 | /** 1085 | * Get [scientific pitch notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation) string 1086 | * from pitch in [array notation]() 1087 | * 1088 | * Array length must be 1 or 3 (see array notation documentation) 1089 | * 1090 | * The returned string format is `letter[+ accidentals][+ octave][/duration]` where the letter 1091 | * is always uppercase, and the accidentals, octave and duration are optional. 1092 | * 1093 | * This function is memoized for better perfomance. 1094 | * 1095 | * @name note.str 1096 | * @function 1097 | * @param {Array} arr - the note in array notation 1098 | * @return {String} the note in scientific notation or null if not valid note array 1099 | * 1100 | * @example 1101 | * var str = require('music-notation/note/str') 1102 | * str([0]) // => 'F' 1103 | * str([0, 4]) // => null (its an interval) 1104 | * str([0, 4, null]) // => 'F4' 1105 | * str([0, 4, 2]) // => 'F4/2' 1106 | */ 1107 | module.exports = function (arr) { 1108 | if (!Array.isArray(arr) || arr.length < 1 || arr.length === 2) return null 1109 | var str = '|' + arr[0] + '|' + arr[1] + '|' + arr[2] 1110 | return str in cache ? cache[str] : cache[str] = build(arr) 1111 | } 1112 | 1113 | var LETTER = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] 1114 | function build (coord) { 1115 | var p = props(coord) 1116 | return LETTER[p[0]] + acc(p[1]) + (p[2] !== null ? p[2] : '') + (p[3] !== null ? '/' + p[3] : '') 1117 | } 1118 | 1119 | },{"../accidentals/str":18,"../array/to-props":20}],28:[function(require,module,exports){ 1120 | 'use strict' 1121 | 1122 | function curry (fn, arity) { 1123 | if (arity === 1) return fn 1124 | return function (a, b) { 1125 | if (arguments.length === 1) return function (c) { return fn(a, c) } 1126 | return fn(a, b) 1127 | } 1128 | } 1129 | 1130 | /** 1131 | * Decorate a function to work with intervals, notes or pitches in 1132 | * [array notation](https://github.com/danigb/tonal/tree/next/packages/music-notation) 1133 | * with independence of string representations. 1134 | * 1135 | * This is the base of the pluggable notation system of 1136 | * [tonal](https://github.com/danigb/tonal) 1137 | * 1138 | * @name operation 1139 | * @function 1140 | * @param {Function} parse - the parser 1141 | * @param {Function} str - the string builder 1142 | * @param {Function} fn - the operation to decorate 1143 | * 1144 | * @example 1145 | * var parse = require('music-notation/interval/parse') 1146 | * var str = require('music-notation/interval/str') 1147 | * var operation = require('music-notation/operation')(parse, str) 1148 | * var add = operation(function(a, b) { return [a[0] + b[0], a[1] + b[1]] }) 1149 | * add('3m', '3M') // => '5P' 1150 | */ 1151 | module.exports = function op (parse, str, fn) { 1152 | if (arguments.length === 2) return function (f) { return op(parse, str, f) } 1153 | return curry(function (a, b) { 1154 | var ac = parse(a) 1155 | var bc = parse(b) 1156 | if (!ac && !bc) return fn(a, b) 1157 | var v = fn(ac || a, bc || b) 1158 | return str(v) || v 1159 | }, fn.length) 1160 | } 1161 | 1162 | },{}],29:[function(require,module,exports){ 1163 | var note = require('../note/parse') 1164 | var interval = require('../interval/parse') 1165 | 1166 | /** 1167 | * Convert a note or interval string to a [pitch in coord notation]() 1168 | * 1169 | * @name pitch.parse 1170 | * @function 1171 | * @param {String} pitch - the note or interval to parse 1172 | * @return {Array} the pitch in array notation 1173 | * 1174 | * @example 1175 | * var parse = require('music-notation/pitch/parse') 1176 | * parse('C2') // => [0, 2, null] 1177 | * parse('5P') // => [1, 0] 1178 | */ 1179 | module.exports = function (n) { return note(n) || interval(n) } 1180 | 1181 | },{"../interval/parse":21,"../note/parse":25}],30:[function(require,module,exports){ 1182 | var note = require('../note/str') 1183 | var interval = require('../interval/str') 1184 | 1185 | /** 1186 | * Convert a pitch in coordinate notation to string. It deals with notes, pitch 1187 | * classes and intervals. 1188 | * 1189 | * @name pitch.str 1190 | * @funistron 1191 | * @param {Array} pitch - the pitch in array notation 1192 | * @return {String} the pitch string 1193 | * 1194 | * @example 1195 | * var str = require('music-notation/pitch.str') 1196 | * // pitch class 1197 | * str([0]) // => 'C' 1198 | * // interval 1199 | * str([0, 0]) // => '1P' 1200 | * // note 1201 | * str([0, 2, 4]) // => 'C2/4' 1202 | */ 1203 | module.exports = function (n) { return note(n) || interval(n) } 1204 | 1205 | },{"../interval/str":23,"../note/str":27}]},{},[1]); 1206 | -------------------------------------------------------------------------------- /dist/score.min.js: -------------------------------------------------------------------------------- 1 | (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;o1)data=build["sim"](slice.call(arguments));return exec({},build,data)};build.score=build;modules.forEach(function(m){Object.assign(build,m)});return build}var VAR={type:"var"};function exec(ctx,fns,data){var elements,params;var fnName=data[0];var fn=fns[fnName];if(fnName[0]==="$"){ctx[fnName]=exec(ctx,fns,data[1]);return VAR}else if(!fn){throw Error('Command not found: "'+fnName+'"')}else{elements=data.slice(1);params=elements.map(function(p){return Array.isArray(p)?exec(ctx,fns,p):p[0]==="$"?ctx[p]:p}).filter(function(p){return p!==VAR});return fn.apply(null,params)}}module.exports={init:init}},{}],3:[function(require,module,exports){var score=require("./score");var measures=require("./measures").measures;var getChord=require("chord-dictionary");function chords(meter,data){return measures(meter,data,function(dur,el){return score.el({duration:dur,chord:el})})}var expandChords=score.map(function(el){return el.chord?score.sim(getChord(el.chord).map(score.note(el.duration))):el});function harmony(meter,data){return expandChords(chords(meter,data))}module.exports={chords:chords,expandChords:expandChords,harmony:harmony}},{"./measures":4,"./score":7,"chord-dictionary":10}],4:[function(require,module,exports){var score=require("./score");function measures(meter,measures,builder){var list;var mLen=measureLength(meter);if(!mLen)throw Error("Not valid meter: "+meter);var seq=[];builder=builder||score.note;splitMeasures(measures).forEach(function(measure){measure=measure.trim();if(measure.length>0){list=parenthesize(tokenize(measure),[]);processList(seq,list,measureLength(meter),builder)}});return score.seq(seq)}function measureLength(meter){return 4}function processList(seq,list,total,builder){var dur=total/list.length;list.forEach(function(i){if(Array.isArray(i))processList(seq,i,dur,builder);else seq.push(builder(dur,i))})}function splitMeasures(repr){return repr.replace(/\s+\||\|\s+/,"|").replace(/^\||\|\s*$/g,"").split("|")}var parenthesize=function(input,list){var token=input.shift();if(token===undefined){return list}else if(token==="("){list.push(parenthesize(input,[]));return parenthesize(input,list)}else if(token===")"){return list}else{return parenthesize(input,list.concat(token))}};var tokenize=function(input){return input.replace(/[\(]/g," ( ").replace(/[\)]/g," ) ").replace(/\,/g," ").trim().split(/\s+/)};module.exports={measures:measures,melody:measures}},{"./score":7}],5:[function(require,module,exports){var score=require("./score");var tr=require("note-transposer");function notes(pitches,durations,params){var p=toArray(pitches||null);var d=toArray(durations||1);return p.map(function(pitch,i){return score.note(+d[i%d.length],pitch,params)})}function toArray(obj){if(Array.isArray(obj))return obj;else if(typeof obj==="string")return obj.trim().split(/\s+/);else return[obj]}function phrase(p,d,a){return score.seq(notes(p,d,a))}function chord(p,d,a){return score.sim(notes(p,d,a))}function trans(interval,obj){if(arguments.length>1)return trans(interval)(obj);return score.map(function(note){return note.pitch?score.el(note,{pitch:tr(interval,note.pitch)}):note})}module.exports={phrase:phrase,chord:chord,trans:trans}},{"./score":7,"note-transposer":17}],6:[function(require,module,exports){var score=require("./score");var rhythm={};rhythm.pattern=function(pattern){return score.seq(pattern.split("").map(score.note(1)))};rhythm.ioi=function(ioi){return rhythm.pattern(rhythm.ioiToPattern(ioi))};rhythm.ioiToPattern=function(num){return num.split("").map(function(n){return"x"+Array(+n).join(".")}).join("")};rhythm.patternToIoi=function(pattern){return pattern.split(/x/).map(function(d){return d.length}).filter(function(_,i){return i}).map(function(d){return d+1}).join("")};module.exports=rhythm},{"./score":7}],7:[function(require,module,exports){"use strict";var isArray=Array.isArray;var slice=Array.prototype.slice;var assign=Object.assign;function typeOf(obj){return isArray(obj)?obj[0]:"el"}function isStruct(e){return isArray(e)&&typeof e[0]==="string"}function builder(name){return function(elements){if(arguments.length>1)return[name].concat(slice.call(arguments));else if(isStruct(elements))return[name,elements];return[name].concat(elements)}}function el(d,data){if(typeof d==="object")return assign({},d,data);else return assign({duration:+d||0},data)}function note(dur,pitch,data){if(arguments.length===1)return function(p,d){return note(dur,p,d)};return assign({pitch:pitch,duration:dur||1},data)}var seq=builder("seq");var sim=builder("sim");function transform(nt,st,pt,obj,ctx){if(arguments.length>3)return transform(nt,st,pt)(obj,ctx);var T=function(obj,ctx){switch(typeOf(obj)){case"el":return nt(obj,ctx);case"seq":return st(obj.slice(1).map(T),ctx);case"sim":return pt(obj.slice(1).map(T),ctx);default:return obj}};return T}function map(fn,obj,ctx){if(arguments.length>1)return map(fn)(obj,ctx);return transform(fn,buildSeq,buildSim)}function buildSeq(el,ctx){return seq(el)}function buildSim(el,ctx){return sim(el)}module.exports={el:el,note:note,seq:seq,sequentially:seq,sim:sim,simultaneosly:sim,transform:transform,map:map}},{}],8:[function(require,module,exports){var score=require("./score");function forEachTime(fn,obj,ctx){if(arguments.length>1)return forEachTime(fn)(obj,ctx);return function(obj,ctx){return score.transform(function(note){return function(time,ctx){fn(time,note,ctx);return note.duration}},function(seq){return function(time,ctx){return seq.reduce(function(dur,fn){return dur+fn(time+dur,ctx)},0)}},function(par){return function(time,ctx){return par.reduce(function(max,fn){return Math.max(max,fn(time,ctx))},0)}})(obj)(0,ctx)}}function events(obj,build,compare){var e=[];forEachTime(function(time,obj){e.push(build?build(time,obj):[time,obj])},obj);return e.sort(compare||function(a,b){return a[0]-b[0]})}module.exports={forEachTime:forEachTime,events:events}},{"./score":7}],9:[function(require,module,exports){module.exports={4:["1 4 7b 10m",["quartal"]],5:["1 5"],M:["1 3 5",["Major",""]],"M#5":["1 3 5A",["augmented","maj#5","Maj#5","+","aug"]],"M#5add9":["1 3 5A 9",["+add9"]],M13:["1 3 5 7 9 13",["maj13","Maj13"]],"M13#11":["1 3 5 7 9 11# 13",["maj13#11","Maj13#11","M13+4","M13#4"]],M6:["1 3 5 13",["6"]],"M6#11":["1 3 5 6 11#",["M6b5","6#11","6b5"]],M69:["1 3 5 6 9",["69"]],"M69#11":["1 3 5 6 9 11#"],"M7#11":["1 3 5 7 11#",["maj7#11","Maj7#11","M7+4","M7#4"]],"M7#5":["1 3 5A 7",["maj7#5","Maj7#5","maj9#5","M7+"]],"M7#5sus4":["1 4 5A 7"],"M7#9#11":["1 3 5 7 9# 11#"],M7add13:["1 3 5 6 7 9"],M7b5:["1 3 5d 7"],M7b6:["1 3 6b 7"],M7b9:["1 3 5 7 9b"],M7sus4:["1 4 5 7"],M9:["1 3 5 7 9",["maj9","Maj9"]],"M9#11":["1 3 5 7 9 11#",["maj9#11","Maj9#11","M9+4","M9#4"]],"M9#5":["1 3 5A 7 9",["Maj9#5"]],"M9#5sus4":["1 4 5A 7 9"],M9b5:["1 3 5d 7 9"],M9sus4:["1 4 5 7 9"],Madd9:["1 3 5 9",["2","add9","add2"]],Maj7:["1 3 5 7",["maj7","M7"]],Mb5:["1 3 5d"],Mb6:["1 3 13b"],Msus2:["1 2M 5",["add9no3","sus2"]],Msus4:["1 4 5",["sus","sus4"]],addb9:["1 3 5 9b"],7:["1 3 5 7b",["Dominant","Dom"]],9:["1 3 5 7b 9",["79"]],11:["1 5 7b 9 11"],13:["1 3 5 7b 9 13",["13_"]],"11b9":["1 5 7b 9b 11"],"13#11":["1 3 5 7b 9 11# 13",["13+4","13#4"]],"13#9":["1 3 5 7b 9# 13",["13#9_"]],"13#9#11":["1 3 5 7b 9# 11# 13"],"13b5":["1 3 5d 6 7b 9"],"13b9":["1 3 5 7b 9b 13"],"13b9#11":["1 3 5 7b 9b 11# 13"],"13no5":["1 3 7b 9 13"],"13sus4":["1 4 5 7b 9 13",["13sus"]],"69#11":["1 3 5 6 9 11#"],"7#11":["1 3 5 7b 11#",["7+4","7#4","7#11_","7#4_"]],"7#11b13":["1 3 5 7b 11# 13b",["7b5b13"]],"7#5":["1 3 5A 7b",["+7","7aug","aug7"]],"7#5#9":["1 3 5A 7b 9#",["7alt","7#5#9_","7#9b13_"]],"7#5b9":["1 3 5A 7b 9b"],"7#5b9#11":["1 3 5A 7b 9b 11#"],"7#5sus4":["1 4 5A 7b"],"7#9":["1 3 5 7b 9#",["7#9_"]],"7#9#11":["1 3 5 7b 9# 11#",["7b5#9"]],"7#9#11b13":["1 3 5 7b 9# 11# 13b"],"7#9b13":["1 3 5 7b 9# 13b"],"7add6":["1 3 5 7b 13",["67","7add13"]],"7b13":["1 3 7b 13b"],"7b5":["1 3 5d 7b"],"7b6":["1 3 5 6b 7b"],"7b9":["1 3 5 7b 9b"],"7b9#11":["1 3 5 7b 9b 11#",["7b5b9"]],"7b9#9":["1 3 5 7b 9b 9#"],"7b9b13":["1 3 5 7b 9b 13b"],"7b9b13#11":["1 3 5 7b 9b 11# 13b",["7b9#11b13","7b5b9b13"]],"7no5":["1 3 7b"],"7sus4":["1 4 5 7b",["7sus"]],"7sus4b9":["1 4 5 7b 9b",["susb9","7susb9","7b9sus","7b9sus4","phryg"]],"7sus4b9b13":["1 4 5 7b 9b 13b",["7b9b13sus4"]],"9#11":["1 3 5 7b 9 11#",["9+4","9#4","9#11_","9#4_"]],"9#11b13":["1 3 5 7b 9 11# 13b",["9b5b13"]],"9#5":["1 3 5A 7b 9",["9+"]],"9#5#11":["1 3 5A 7b 9 11#"],"9b13":["1 3 7b 9 13b"],"9b5":["1 3 5d 7b 9"],"9no5":["1 3 7b 9"],"9sus4":["1 4 5 7b 9",["9sus"]],m:["1 3b 5",["minor"]],"m#5":["1 3b 5A",["m+","mb6"]],m11:["1 3b 5 7b 9 11",["_11"]],"m11#5":["1 3b 6b 7b 9 11"],m11b5:["1 3b 7b 12d 2M 4",["h11","_11b5"]],m13:["1 3b 5 7b 9 11 13",["_13"]],m6:["1 3b 4 5 13",["_6"]],m69:["1 3b 5 6 9",["_69"]],m7:["1 3b 5 7b",["minor7","_","_7"]],"m7#5":["1 3b 6b 7b"],m7add11:["1 3b 5 7b 11",["m7add4"]],m7b5:["1 3b 5d 7b",["half-diminished","h7","_7b5"]],m9:["1 3b 5 7b 9",["_9"]],"m9#5":["1 3b 6b 7b 9"],m9b5:["1 3b 7b 12d 2M",["h9","-9b5"]],mMaj7:["1 3b 5 7",["mM7","_M7"]],mMaj7b6:["1 3b 5 6b 7",["mM7b6"]],mM9:["1 3b 5 7 9",["mMaj9","-M9"]],mM9b6:["1 3b 5 6b 7 9",["mMaj9b6"]],mb6M7:["1 3b 6b 7"],mb6b9:["1 3b 6b 9b"],o:["1 3b 5d",["mb5","dim"]],o7:["1 3b 5d 13",["diminished","m6b5","dim7"]],o7M7:["1 3b 5d 6 7"],oM7:["1 3b 5d 7"],sus24:["1 2M 4 5",["sus4add9"]],"+add#9":["1 3 5A 9#"],madd4:["1 3b 4 5"],madd9:["1 3b 5 9"]}},{}],10:[function(require,module,exports){"use strict";var chords=require("./chords.json");var dictionary=require("music-dictionary");module.exports=dictionary(chords)},{"./chords.json":9,"music-dictionary":11}],11:[function(require,module,exports){"use strict";var parse=require("music-notation/interval/parse");var R=require("music-notation/note/regex");var transpose=require("note-transposer");module.exports=function(src){function dict(name,tonic){var v=dict.props(name);if(!v){var n=R.exec(name);v=n?dict.props(n[5]):null;if(!v)return[];tonic=tonic===false?tonic:tonic||n[1]+n[2]+n[3]}if(tonic!==false&&!tonic)return function(t){return dict(name,t)};return v.intervals.map(transpose(tonic))}return build(src,dict)};function build(src,dict){var data={};var names=Object.keys(src);var aliases=names.slice();dict.props=function(name){return data[name]};dict.names=function(a){return(a?aliases:names).slice()};names.forEach(function(k){var d=src[k];var c={name:k,aliases:d[1]||[]};c.intervals=d[0].split(" ");c.steps=c.intervals.map(parse);c.binary=binary([0,0,0,0,0,0,0,0,0,0,0,0],c.steps);c.decimal=parseInt(c.binary,2);data[k]=data[c.binary]=data[c.decimal]=c;c.aliases.forEach(function(a){data[a]=c});if(c.aliases.length>0)aliases=aliases.concat(c.aliases)});return dict}function binary(num,intervals){intervals.forEach(function(i){num[(i[0]*7+i[1]*12)%12]="1"});return num.join("")}},{"music-notation/interval/parse":13,"music-notation/note/regex":16,"note-transposer":17}],12:[function(require,module,exports){"use strict";var BASES=[[0,0],[2,-1],[4,-2],[-1,1],[1,0],[3,-1],[5,-2]];module.exports=function(step,alt,oct,dur){var base=BASES[step];alt=alt||0;var f=base[0]+7*alt;if(typeof oct==="undefined")return[f];var o=oct+base[1]-4*alt;if(typeof dur==="undefined")return[f,o];else return[f,o,dur]}},{}],13:[function(require,module,exports){"use strict";var memoize=require("../memoize");var fromProps=require("../array/from-props");var INTERVAL=require("./regex");var TYPES="PMMPPMM";var QALT={P:{dddd:-4,ddd:-3,dd:-2,d:-1,P:0,A:1,AA:2,AAA:3,AAAA:4},M:{ddd:-4,dd:-3,d:-2,m:-1,M:0,A:1,AA:2,AAA:3,AAAA:4}};module.exports=memoize(function(str){var m=INTERVAL.exec(str);if(!m)return null;var dir=(m[2]||m[7])==="-"?-1:1;var num=+(m[3]||m[8])-1;var q=m[4]||m[6]||"";var simple=num%7;var alt;if(q==="")alt=0;else if(q[0]==="#")alt=q.length;else if(q[0]==="b")alt=-q.length;else{alt=QALT[TYPES[simple]][q];if(typeof alt==="undefined")return null}var oct=Math.floor(num/7);var arr=fromProps(simple,alt,oct);return dir===1?arr:[-arr[0],-arr[1]]})},{"../array/from-props":12,"../memoize":15,"./regex":14}],14:[function(require,module,exports){var TONAL="([-+]?)(\\d+)(d{1,4}|m|M|P|A{1,4}|b{1,4}|#{1,4}|)";var STRICT="(AA|A|P|M|m|d|dd)([-+]?)(\\d+)";var COMPOSE="(?:("+TONAL+")|("+STRICT+"))";module.exports=new RegExp("^"+COMPOSE+"$")},{}],15:[function(require,module,exports){"use strict";module.exports=function(fn){var cache={};return function(str){if(typeof str!=="string")return null;return str in cache?cache[str]:cache[str]=fn(str)}}},{}],16:[function(require,module,exports){"use strict";module.exports=/^([a-gA-G])(#{1,}|b{1,}|x{1,}|)(-?\d*)(\/\d+|)\s*(.*)\s*$/},{}],17:[function(require,module,exports){var parse=require("music-notation/pitch/parse");var str=require("music-notation/pitch/str");var operation=require("music-notation/operation")(parse,str);var transpose=operation(function(i,n){if(i===false)return n;else if(!Array.isArray(i)||!Array.isArray(n))return null;else if(i.length===1||n.length===1)return[n[0]+i[0]];var d=i.length===2&&n.length===2?null:n[2]||i[2];return[n[0]+i[0],n[1]+i[1],d]});if(typeof module==="object"&&module.exports)module.exports=transpose;if(typeof window!=="undefined")window.transpose=transpose},{"music-notation/operation":28,"music-notation/pitch/parse":29,"music-notation/pitch/str":30}],18:[function(require,module,exports){"use strict";module.exports=function(num){if(num<0)return Array(-num+1).join("b");else if(num>0)return Array(num+1).join("#");else return""}},{}],19:[function(require,module,exports){arguments[4][12][0].apply(exports,arguments)},{dup:12}],20:[function(require,module,exports){"use strict";var BASES=[[3,1],[0,0],[4,0],[1,-1],[5,-1],[2,-2],[6,-2],[3,-3]];module.exports=function(arr){if(!Array.isArray(arr))return null;var index=(arr[0]+1)%7;if(index<0)index=7+index;var base=BASES[index];var alter=Math.floor((arr[0]+1)/7);var oct=arr.length===1?null:arr[1]-base[1]+alter*4;var dur=arr[2]||null;return[base[0],alter,oct,dur]}},{}],21:[function(require,module,exports){arguments[4][13][0].apply(exports,arguments)},{"../array/from-props":19,"../memoize":24,"./regex":22,dup:13}],22:[function(require,module,exports){arguments[4][14][0].apply(exports,arguments)},{dup:14}],23:[function(require,module,exports){"use strict";var props=require("../array/to-props");var cache={};module.exports=function(arr){if(!Array.isArray(arr)||arr.length!==2)return null;var str="|"+arr[0]+"|"+arr[1];return str in cache?cache[str]:cache[str]=build(arr)};var ALTER={P:["dddd","ddd","dd","d","P","A","AA","AAA","AAAA"],M:["ddd","dd","d","m","M","A","AA","AAA","AAAA"]};var TYPES="PMMPPMM";function build(coord){var p=props(coord);var t=TYPES[p[0]];var dir,num,alt;if(p[2]<0){dir=-1;num=8-p[0]-7*(p[2]+1);alt=t==="P"?-p[1]:-(p[1]+1)}else{dir=1;num=p[0]+1+7*p[2];alt=p[1]}var q=ALTER[t][4+alt];return dir*num+q}},{"../array/to-props":20}],24:[function(require,module,exports){arguments[4][15][0].apply(exports,arguments)},{dup:15}],25:[function(require,module,exports){"use strict";var memoize=require("../memoize");var R=require("./regex");var BASES={C:[0,0],D:[2,-1],E:[4,-2],F:[-1,1],G:[1,0],A:[3,-1],B:[5,-2]};module.exports=memoize(function(str){var m=R.exec(str);if(!m||m[5])return null;var base=BASES[m[1].toUpperCase()];var alt=m[2].replace(/x/g,"##").length;if(m[2][0]==="b")alt*=-1;var fifths=base[0]+7*alt;if(!m[3])return[fifths];var oct=+m[3]+base[1]-4*alt;var dur=m[4]?+m[4].substring(1):null;return[fifths,oct,dur]})},{"../memoize":24,"./regex":26}],26:[function(require,module,exports){arguments[4][16][0].apply(exports,arguments)},{dup:16}],27:[function(require,module,exports){"use strict";var props=require("../array/to-props");var acc=require("../accidentals/str");var cache={};module.exports=function(arr){if(!Array.isArray(arr)||arr.length<1||arr.length===2)return null;var str="|"+arr[0]+"|"+arr[1]+"|"+arr[2];return str in cache?cache[str]:cache[str]=build(arr)};var LETTER=["C","D","E","F","G","A","B"];function build(coord){var p=props(coord);return LETTER[p[0]]+acc(p[1])+(p[2]!==null?p[2]:"")+(p[3]!==null?"/"+p[3]:"")}},{"../accidentals/str":18,"../array/to-props":20}],28:[function(require,module,exports){"use strict";function curry(fn,arity){if(arity===1)return fn;return function(a,b){if(arguments.length===1)return function(c){return fn(a,c)};return fn(a,b)}}module.exports=function op(parse,str,fn){if(arguments.length===2)return function(f){return op(parse,str,f)};return curry(function(a,b){var ac=parse(a);var bc=parse(b);if(!ac&&!bc)return fn(a,b);var v=fn(ac||a,bc||b);return str(v)||v},fn.length)}},{}],29:[function(require,module,exports){var note=require("../note/parse");var interval=require("../interval/parse");module.exports=function(n){return note(n)||interval(n)}},{"../interval/parse":21,"../note/parse":25}],30:[function(require,module,exports){var note=require("../note/str");var interval=require("../interval/str");module.exports=function(n){return note(n)||interval(n)}},{"../interval/str":23,"../note/str":27}]},{},[1]); -------------------------------------------------------------------------------- /examples/dm.js: -------------------------------------------------------------------------------- 1 | // https://dev.opera.com/articles/drum-sounds-webaudio/ 2 | // http://joesul.li/van/synthesizing-hi-hats/ 3 | 4 | var dm = {} 5 | 6 | function noiseBuffer (ac) { 7 | var bufferSize = ac.sampleRate 8 | var buffer = ac.createBuffer(1, bufferSize, ac.sampleRate) 9 | var output = buffer.getChannelData(0) 10 | 11 | for (var i = 0; i < bufferSize; i++) { 12 | output[i] = Math.random() * 2 - 1 13 | } 14 | return buffer 15 | } 16 | 17 | dm.clave = function (ac, when) { 18 | var gain = ac.createGain() 19 | // Define the volume envelope 20 | gain.gain.setValueAtTime(0.00001, when) 21 | gain.gain.exponentialRampToValueAtTime(1, when + 0.02) 22 | gain.gain.exponentialRampToValueAtTime(0.3, when + 0.03) 23 | gain.gain.exponentialRampToValueAtTime(0.00001, when + 0.3) 24 | gain.connect(ac.destination) 25 | 26 | var osc = ac.createOscillator() 27 | osc.type = 'square' 28 | osc.frequency.value = 440 29 | osc.connect(gain) 30 | osc.start(when) 31 | osc.stop(when + 0.3) 32 | } 33 | 34 | dm.snare = function (ac, when) { 35 | var noise = ac.createBufferSource() 36 | noise.buffer = noiseBuffer(ac) 37 | var noiseFilter = ac.createBiquadFilter() 38 | noiseFilter.type = 'highpass' 39 | noiseFilter.frequency.value = 800 40 | noise.connect(noiseFilter) 41 | var noiseEnvelope = ac.createGain() 42 | noiseFilter.connect(noiseEnvelope) 43 | 44 | noiseEnvelope.connect(ac.destination) 45 | var osc = ac.createOscillator() 46 | osc.type = 'triangle' 47 | 48 | var oscEnvelope = ac.createGain() 49 | osc.connect(oscEnvelope) 50 | oscEnvelope.connect(ac.destination) 51 | noiseEnvelope.gain.setValueAtTime(1, when) 52 | noiseEnvelope.gain.exponentialRampToValueAtTime(0.01, when + 0.2) 53 | noise.start(when) 54 | 55 | osc.frequency.setValueAtTime(100, when) 56 | oscEnvelope.gain.setValueAtTime(0.7, when) 57 | oscEnvelope.gain.exponentialRampToValueAtTime(0.01, when + 0.1) 58 | osc.start(when) 59 | 60 | osc.stop(when + 0.2) 61 | noise.stop(when + 0.2) 62 | } 63 | 64 | dm.hihat = function (ac, when, duration) { 65 | var fundamental = 40 66 | var ratios = [2, 3, 4.16, 5.43, 6.79, 8.21] 67 | var gain = ac.createGain() 68 | 69 | // Bandpass 70 | var bandpass = ac.createBiquadFilter() 71 | bandpass.type = 'bandpass' 72 | bandpass.frequency.value = 10000 73 | 74 | // Highpass 75 | var highpass = ac.createBiquadFilter() 76 | highpass.type = 'highpass' 77 | highpass.frequency.value = 7000 78 | 79 | // Connect the graph 80 | bandpass.connect(highpass) 81 | highpass.connect(gain) 82 | gain.connect(ac.destination) 83 | 84 | // Create the oscillators 85 | ratios.forEach(function (ratio) { 86 | var osc = ac.createOscillator() 87 | osc.type = 'square' 88 | // Frequency is the fundamental * this oscillator's ratio 89 | osc.frequency.value = fundamental * ratio 90 | osc.connect(bandpass) 91 | osc.start(when) 92 | osc.stop(when + 0.3) 93 | }) 94 | 95 | // Define the volume envelope 96 | gain.gain.setValueAtTime(0.00001, when) 97 | gain.gain.exponentialRampToValueAtTime(1, when + 0.02) 98 | gain.gain.exponentialRampToValueAtTime(0.3, when + 0.03) 99 | gain.gain.exponentialRampToValueAtTime(0.00001, when + 0.3) 100 | } 101 | 102 | module.exports = dm 103 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ScoreJS - transcriptions 6 | 7 | 8 | 10 | 11 | 12 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/pianoroll-example.js: -------------------------------------------------------------------------------- 1 | var score = require('..') 2 | var pianoRoll = require('../ext/pianoroll') 3 | 4 | function canvas (w, h) { 5 | var canvas = document.createElement('canvas') 6 | canvas.width = w 7 | canvas.height = h 8 | document.body.appendChild(canvas) 9 | return canvas.getContext('2d') 10 | } 11 | 12 | pianoRoll.draw(canvas(200, 200), score( 13 | ['melody', '4/4', 'c2 d2 e2 (f2 g2) | a2 b2 | c3'] 14 | )) 15 | pianoRoll.draw(canvas(200, 200), score( 16 | ['$melo', ['phrase', 'c2 d2 e2 g2', 1]], 17 | ['seq', '$melo', ['sim', '$melo', ['trans', 'M3', '$melo']]] 18 | )) 19 | pianoRoll.draw(canvas(200, 200), score( 20 | ['harmony', '4/4', 'Cmaj7 | Dm7 Gdom | Cmaj79'] 21 | )) 22 | -------------------------------------------------------------------------------- /examples/player-example.js: -------------------------------------------------------------------------------- 1 | /* global AudioContext */ 2 | 3 | var score = require('..') 4 | var player = require('../ext/player') 5 | var ac = new AudioContext() 6 | 7 | var h = score( 8 | ['vel', 80, ['melody', '4/4', 'c4 d4 e4 f4 | g4 a4 b4 c5 | _ . . .']], 9 | ['vel', 10, ['harmony', '4/4', 'Cmaj7 | Dm7 Gdom | C']] 10 | ) 11 | 12 | player.play(ac, player.synth, score.tempo(120, h)) 13 | -------------------------------------------------------------------------------- /examples/polyrhythms.js: -------------------------------------------------------------------------------- 1 | var score = require('..') 2 | var pianoroll = require('../ext/pianoroll') 3 | 4 | var p1 = score( 5 | ['pattern', 'x..', 4], 6 | ['pattern', 'y..', 4] 7 | ) 8 | 9 | score.forEachTime(console.log) 10 | 11 | pianoroll.draw(pianoroll.canvas(), p1) 12 | -------------------------------------------------------------------------------- /examples/schedule-example.js: -------------------------------------------------------------------------------- 1 | /* global AudioContext */ 2 | var score = require('..') 3 | var dm = require('./dm') 4 | var scheduler = require('../ext/scheduler') 5 | 6 | var ac = new AudioContext() 7 | var s = score( 8 | ['inst', 'hihat', ['measures', '4/4', 'a b c d']], 9 | ['inst', 'clave', ['measures', '4/4', 'a (_ c) d (_ f g)']] 10 | ) 11 | console.log(s) 12 | 13 | scheduler.schedule(ac, 0, score.events(s), function (time, note) { 14 | if (note.pitch !== '_') dm[note.inst](ac, time) 15 | }) 16 | -------------------------------------------------------------------------------- /examples/transcriptions/AnthropologyParker.ls.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Anthropology", 3 | "composer": "Charlie Parker", 4 | "meter": "4/4", 5 | "key": "-2", 6 | "tempo": "180.0", 7 | "parts": { 8 | "chords": "BbM7 G7 | Cm7 F7 | Dm7 G7 | Cm7 F7 | BbM7 | Eb Ebm6 | Dm7 G7 | Cm7 F7 | BbM7 G7 | Cm7 F7 | Dm7 G7 | Cm7 F7 | Bb7 | Eb Ebm6 | Dm7 G7 | Cm7 F7 BbM7 _ | D7 | _ | G7 | _ | C7 | _ | F7 | _ | BbM7 G7 | Cm7 F7 | Dm7 G7 | Cm7 F7 | Bb7 | Eb Ebm6 | Dm7 G7 | Cm7 F7 BbM7 _ | ", 9 | "Chorus 1": "bb/8 d+/8 c+/8t c#+/8t d+/8t e+/8 c#+/8 d+/8 f+/8 r/4+8 eb+/8 r/8 f+/8 eb+/4 r/8 d+/8 e+/8 d+/8 c+/8 a/8 b/8 d+/8 c+/8 g/8 bb/8 a/8 r/4+8 b/4+8 g/8 bb/8 d+/8 bb/4 fb+/8 r/8 eb+/4 f#/8 r/8 bb/8 c+/8 d+/8 f+/8 r/8 b/8 r/8 ab+/4 r/8 f#+/8 g+/8 eb+/8 bb/8 g/8 d+/8 f+/8 r/8 f/8 bb/8 d+/8 c+/8t c#+/8t d+/8t e+/8 c#+/8 d+/8 f+/8 r/4+8 eb+/8 r/8 f+/8 eb+/4 r/8 d+/8 e+/8 d+/8 c+/8 a/8 b/8 d+/8 c+/8 g/8 bb/8 a/8 r/4+8 b/4+8 g/8 bb/8 d+/8 bb/4 fb+/8 r/8 eb+/4 f#/8 r/8 bb/8 c+/8 d+/8 e+/8 f+/8 d+/8 f+/8 r/8 d+/8 e+/8 g/8 c+/8 g/8 b/8 c+/8 r/4+8 f#+/4+8 g+/8 g#+/8 a+/8 f#+/8 g#+/8 a+/8 f#+/8 d+/8 r/2+8 e+/8 f+/8 e+/8 d+/8 c#+/8 e+/8 d+/8 b/8 a/8 r/4+8 a+/8 r/8 a+/4 a+/4 g+/8 e+/8 d+/2 a/8 c+/8 g/8 bb/8 c+/8 r/4+8 e+/4+8 d+/8 c+/8 b/8 d+/8 c+/8 g/8 c+/4 bb/8 a/8 r/4+8 f/8 bb/8 d+/8 c+/8t c#+/8t d+/8t e+/8 c#+/8 d+/8 f+/8 r/4+8 eb+/8 r/8 f+/8 eb+/4 r/8 d+/8 e+/8 d+/8 c+/8 a/8 b/8 d+/8 c+/8 g/8 bb/8 a/8 r/4+8 b/4+8 g/8 bb/8 d+/8 bb/4 fb+/8 r/8 eb+/4 f#/8 r/8 bb/8 c+/8 d+/8 e+/8 f+/8 d+/8 f+/8 r/8 d+/8 e+/8 g/8 c+/8 bb/8 a/8 b/8 r/2", 10 | "melody2": "d/8t eb/8t e/8t f/8 d/8 eb/8 f/8 r/8 d/4 c/8 bb-/8 g-/8 c/4 r/8 bb-/8 a-/8 bb-/8 d/8 f/8 a/8 ab/8 g/8 gb/8 f/8 fb/8 eb/8 a-/8 r/4 d/8 c/8 bb-/8 a-/8 ab-/8 bb-/8 bb/8 a/8 g/8 f/8 eb/8 a-/8 bb-/8 r/8 db/8 bb-/8 g-/8 a-/8 c/8 bb-/8 a-/8 bb-/8 d/8 f/4+8 r/1+4+8 f+/4 d+/4 bb/8 c+/2 r/2+8 e/8 f/8t bb/8t d+/8t f+/8 fb+/8 eb+/8 cb/8 d+/8 db+/8 c+/8 cb/8 bb/8 gb/8 eb/8 c/8 d/8 f/8 a/8 c+/8 bb/8 gb/8 eb/8 r/2+8 bb/8 c+/16 bb/16 g/8 f/8 eb/4 bb/8t c+/8t bb/8t gb/8 f/8 eb/8 c/8 d/8 f/4 r/1 b/8 a/8t fb/8t c/8t bb/2 a/8 g/8 gb/8 a/8 c+/8 e+/8 d+/8 c+/8 b/8 d+/8 a/8 f/8 d/8 a-/8 b-/8 e/8 eb/8 b-/8 d/8 f-/8 r/2+8 c/16 d/16 e/8 f/8 g/8 a/8 c+/8 cb/8 bb/8 a/8 g/8 f/8 e/8t g/8t bb/8t d+/8t db+/8t c+/8t b/8 g/8 eb/8 c/8 bb/8 g/8 a/8 ab+/2+8 gb+/8 e+/8 f+/8 d+/8 r/1+2+4+8 d+/16 bb/16 f/8 g/8 bb/8 eb+/8 d+/8 c+/8 bb/8 gb/8 eb/8 c/8 d/8 bb-/8 g-/8 a-/8 c/8 b-/8 bb-/8 a-/8 ab-/8 c/8 bb/8 ab/8 g/8 f/8 eb/4 bb/8 gb/8 eb/8 c/8 d/8 bb-/8 r/1+2+4", 11 | "melody3": "r/4 e+/4 f+/4 db+/4 d+/4 a/8 bb/4 d+/8 bb/8 f/8 g/8 a/8 c+/8 a/8 cb/8 ab/8 fb/8 db/8 bb/8t c+/8t bb/8t a/8 g/8 f/8 eb/8 d/8 c/8 bb-/8 ab-/8 g-/8t ab-/8t c/8t bb/8 ab/8 g/8 f/8 eb/8 a-/8 bb-/8 c/8 db/8 bb-/8 g-/8 a-/8 c/8 bb-/2 r/1+4+8 bb-/8t c/8t bb-/8t a-/8 bb-/8 c/8 a-/8 bb-/8t c/8t d/8t f/8 eb/4 fb/4 g/4 fb/8 f/4 r/2+4+8 c+/16 a/16 cb/16 ab/16 fb/16 d/16 bb/16 a/16 g/16 f/16 eb/16 d/16 c/16 bb-/16 a-/16 c/16 eb/16 gb/16 f/4 r/8 eb/4 c/8 bb-/8 ab-/8 g-/8 bb-/8 bb/8 gb/8 eb/8 c/8 d/8 bb-/8 g-/8 a-/8 c/8 bb-/8 a-/8 bb-/8 d/2 r/1+1 d+/8 db+/8 c+/8 cb/8 a/8 g/8 gb/8 a/8 c+/8 eb+/8 d+/8 c+/8 b/8 d/8 f/8 ab/4+8 g/8 f/8 e/8 c/8 bb-/4 d/8 bb-/8 g-/8 a-/8 c/8 a-/8 bb-/8t d/8t fb/8t a/8 g/8 fb/8 c/8 f/4 f/2 r/1+2 f/8t g/8t a/8t bb/8t c+/8t c#+/8t d+/8 bb/4 c+/16 bb/16 g/8 bb/8 c+/8 d+/4+8 r/4 f/8t g/8t a/8t bb/8t c+/8t c#+/8t d+/8 c+/4+8 bb/8 g/8 a/8 f/4+8 r/4+8 c+/16 cb/16 bb/8 a/8 ab/8 c+/8 bb/8 g/8 eb/8 c/8 bb/8 gb/8 eb/8 c/8 d/8 bb-/8 g-/8 a-/8 c/8 bb-/8 a-/8 bb-/8 eb/8 f/8 d/2+8 f/16 d/16" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/twelveTone.js: -------------------------------------------------------------------------------- 1 | /* global AudioContext */ 2 | var soundfont = require('soundfont-player') 3 | var score = require('..') 4 | var ac = new AudioContext() 5 | var tonal = require('tonal') 6 | var Score = score.build(Object.assign({}, score, tonal)) 7 | 8 | document.body.innerHTML = '

Scores (scorejs demo)

' 9 | 10 | soundfont.instrument(ac, 'marimba').then(function (marimba) { 11 | var s = ttone1(4, row, 60, 0.25) 12 | marimba.schedule(0, score.events(s).map(function (i) { 13 | i[1] = i[1].pitch 14 | return i 15 | })) 16 | }) 17 | 18 | var row = '0 1 6 7 10 11 5 4 3 9 2 8' 19 | // Metalevel, pg 124 20 | function ttone1 (reps, row, key, beat, amp) { 21 | var tr = row.split(' ').map(function (n) { 22 | return +n + key 23 | }) 24 | var r = score.repeat(reps, score.phrase(tr, beat, amp)) 25 | return score.map(function (e) { 26 | return randDur(randOct(e), beat) 27 | }, null, r) 28 | } 29 | function randDur (e, beat) { 30 | var offset = Math.round(Math.random() * 4) * beat / 4 31 | return score.el(e, { duration: e.duration + offset }) 32 | } 33 | function randOct (e) { 34 | return Math.random() < 0.5 35 | ? score.el(e, { pitch: e.pitch + 12 }) 36 | : score.el(e, { pitch: e.pitch - 12 }) 37 | } 38 | -------------------------------------------------------------------------------- /ext/fp.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (score) { 3 | score.mapVal = function (name, nt, obj, ctx) { 4 | if (arguments.length > 2) return score.mapVal(name, nt)(obj, ctx) 5 | return score.map(function (obj) { 6 | var o = Object.assign(obj) 7 | o[name] = nt(o[name]) 8 | return o 9 | }) 10 | } 11 | 12 | score.with = function (attrs, obj) { 13 | if (arguments.length > 1) return score.with(attrs)(obj) 14 | return score.map(function (note) { 15 | return Object.assign({}, note, attrs) 16 | }) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ext/pianoroll.js: -------------------------------------------------------------------------------- 1 | var forEachTime = require('../lib/timed').forEachTime 2 | var midi = require('note-parser').midi 3 | var toMidi = function (n) { return midi(n) || +n } 4 | 5 | var box = { 6 | width: 300, height: 200, 7 | base: 30, time: 20, pitch: 10, 8 | x: function (time) { return time * box.time }, 9 | y: function (midi) { return box.height - (midi - box.base + 1) * box.pitch }, 10 | nw: function (duration) { return duration * box.time - 1 }, 11 | nh: function () { return box.pitch - 1 } 12 | } 13 | 14 | function canvas (w, h, parent) { 15 | parent = parent || document.body 16 | var canvas = document.createElement('canvas') 17 | canvas.width = w || 200 18 | canvas.height = h || 200 19 | parent.appendChild(canvas) 20 | return canvas.getContext('2d') 21 | } 22 | 23 | function build (obj) { 24 | 25 | } 26 | 27 | /** 28 | * Draw a piano roll 29 | */ 30 | function draw (ctx, score) { 31 | console.log(score) 32 | drawStripes(box, ctx) 33 | forEachTime(function (time, note) { 34 | var midi = toMidi(note.pitch) 35 | console.log(time, note, midi) 36 | ctx.fillRect(box.x(time), box.y(midi), box.nw(note.duration), box.nh()) 37 | }, null, score) 38 | } 39 | 40 | var ALT = [0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0] 41 | 42 | function drawStripes (box, ctx) { 43 | var alt 44 | var num = box.height / box.pitch 45 | ctx.fillStyle = '#efefef' 46 | for (var i = 0; i < num; i++) { 47 | alt = ALT[(box.base + num - i) % 12] 48 | ctx.fillRect(0, box.y(box.base + num - i), box.width, alt ? 10 : 1) 49 | } 50 | for (var v = 0; v < box.width / box.time; v++) { 51 | ctx.fillRect(v * box.time, 0, 1, box.height) 52 | } 53 | ctx.fillStyle = '#000000' 54 | } 55 | 56 | module.exports = { build: build, draw: draw, canvas: canvas } 57 | -------------------------------------------------------------------------------- /ext/player.js: -------------------------------------------------------------------------------- 1 | var timed = require('../lib/timed') 2 | var scheduler = require('./scheduler') 3 | var toMidi = require('note-midi') 4 | var toFreq = require('midi-freq') 5 | 6 | function play (ac, player, obj) { 7 | var e = timed.events(obj) 8 | scheduler.schedule(ac, 0, e, function (time, note) { 9 | console.log(time, note) 10 | var freq = toFreq(440, toMidi(note.pitch)) 11 | if (freq) player(ac, freq, time, note.duration, note.velocity) 12 | }) 13 | } 14 | 15 | function synth (ac, freq, when, dur, vel) { 16 | console.log('synth', freq, when, dur, vel) 17 | var osc = ac.createOscillator() 18 | var gain = ac.createGain() 19 | osc.connect(gain) 20 | gain.connect(ac.destination) 21 | 22 | gain.gain.value = (vel || 80) / 100 23 | osc.type = 'square' 24 | osc.frequency.value = freq || 440 25 | osc.start(when || 0) 26 | osc.stop(when + (dur || 0.5)) 27 | } 28 | 29 | module.exports = { play: play, synth: synth } 30 | -------------------------------------------------------------------------------- /ext/scheduler.js: -------------------------------------------------------------------------------- 1 | 2 | var DEFAULTS = { 3 | // time in milliseconds of the scheduler lookahead 4 | lookahead: 500, 5 | overlap: 250 6 | } 7 | 8 | /** 9 | * 10 | */ 11 | function schedule (ac, time, events, fn, options) { 12 | console.log(events) 13 | time = Math.max(time, ac.currentTime) 14 | var opts = DEFAULTS 15 | var id = null 16 | var nextEvtNdx = 0 17 | 18 | function scheduleEvents () { 19 | var current = ac.currentTime 20 | var from = current - time 21 | var to = current + (opts.lookahead + opts.overlap) / 1000 22 | console.log('scheduling', from, to) 23 | var next = events[nextEvtNdx] 24 | while (next && next[0] >= from && next[0] < to) { 25 | fn(time + next[0], next[1]) 26 | console.log('event', next, current, time, time + next[0]) 27 | nextEvtNdx++ 28 | next = events[nextEvtNdx] 29 | } 30 | if (next) id = setTimeout(scheduleEvents, opts.lookahead) 31 | } 32 | scheduleEvents() 33 | 34 | return { 35 | stop: function () { 36 | clearTimeout(id) 37 | } 38 | } 39 | } 40 | 41 | module.exports = { schedule: schedule } 42 | -------------------------------------------------------------------------------- /lib/build.js: -------------------------------------------------------------------------------- 1 | /** @module build */ 2 | 3 | function build (scope, data) { 4 | if (arguments.length > 1) return build(scope)(data) 5 | 6 | return function (data) { 7 | var ctx = {} 8 | ctx.score = exec(ctx, scope, data) 9 | return ctx 10 | } 11 | } 12 | 13 | // exec a data array 14 | function exec (ctx, scope, data) { 15 | console.log('exec', ctx, scope, data) 16 | var fn = getFunction(ctx, scope, data[0]) 17 | var elements = data.slice(1) 18 | var params = elements.map(function (p) { 19 | return Array.isArray(p) ? exec(ctx, scope, p) 20 | : (p[0] === '$') ? ctx[p] : p 21 | }).filter(function (p) { return p !== VAR }) 22 | return fn.apply(null, params) 23 | } 24 | 25 | function getFunction (ctx, scope, name) { 26 | if (typeof name === 'function') return name 27 | else if (typeof name !== 'string') throw Error('Not a valid function: ' + name) 28 | else if (name[0] === '$') return variableFn(ctx, name) 29 | else if (!scope[name]) throw Error('Command not found: ' + name) 30 | else return scope[name] 31 | } 32 | 33 | var VAR = { type: 'var' } 34 | function variableFn (ctx, name) { 35 | return function (obj) { 36 | ctx[name] = obj 37 | return VAR 38 | } 39 | } 40 | 41 | module.exports = { build: build } 42 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var slice = Array.prototype.slice 4 | var modules = [ 5 | require('./score'), 6 | require('./notes'), 7 | require('./stats'), 8 | require('./timed'), 9 | require('./rhythm'), 10 | require('./measures'), 11 | require('./performance'), 12 | require('./build') 13 | ] 14 | 15 | function score (data) { 16 | if (arguments.length > 1) data = score.sim(slice.call(arguments)) 17 | return score.build(score, data).score 18 | } 19 | 20 | modules.forEach(function (module) { 21 | Object.keys(module).forEach(function (name) { score[name] = module[name] }) 22 | }) 23 | 24 | if (typeof module === 'object' && module.exports) module.exports = score 25 | if (typeof window !== 'undefined') window.Score = score 26 | -------------------------------------------------------------------------------- /lib/measures.js: -------------------------------------------------------------------------------- 1 | /** @module measures */ 2 | 3 | var score = require('./score') 4 | 5 | /** 6 | * Parse masures using a time meter to get a sequence 7 | * 8 | * @param {String} meter - the time meter 9 | * @param {String} measures - the measures string 10 | * @param {Function} builder - (Optional) the function used to build the notes 11 | * @return {Score} the score object 12 | * 13 | * @example 14 | * measures('4/4', 'c d (e f) | g | (a b c) d') 15 | */ 16 | function measures (meter, measures, builder) { 17 | var list 18 | var mLen = measureLength(meter) 19 | if (!mLen) throw Error('Not valid meter: ' + meter) 20 | 21 | var seq = [] 22 | builder = builder || score.note 23 | splitMeasures(measures).forEach(function (measure) { 24 | measure = measure.trim() 25 | if (measure.length > 0) { 26 | list = parenthesize(tokenize(measure), []) 27 | processList(seq, list, measureLength(meter), builder) 28 | } 29 | }) 30 | return score.seq(seq) 31 | } 32 | 33 | /** 34 | * Create a melody 35 | * @function 36 | */ 37 | var melody = measures 38 | 39 | /** 40 | * Create a chord names sequence 41 | * 42 | * @param {String} meter - the meter used in the measures 43 | * @param {String} measures - the chords 44 | * @param {Sequence} a sequence of chords 45 | * 46 | * @example 47 | * score.chords('4/4', 'C6 | Dm7 G7 | Cmaj7') 48 | * 49 | * @example 50 | * score(['chords', '4/4', 'Cmaj7 | Dm7 G7']) 51 | */ 52 | function chords (meter, data) { 53 | return measures(meter, data, function (dur, el) { 54 | return score.el({ duration: dur, chord: el }) 55 | }) 56 | } 57 | 58 | // get the length of one measure 59 | function measureLength (meter) { 60 | var m = meter.split('/').map(function (n) { 61 | return +n.trim() 62 | }) 63 | return m[0] * (4 / m[1]) 64 | } 65 | 66 | function processList (seq, list, total, builder) { 67 | var dur = total / list.length 68 | list.forEach(function (i) { 69 | if (Array.isArray(i)) processList(seq, i, dur, builder) 70 | else seq.push(builder(dur, i)) 71 | }) 72 | } 73 | 74 | function splitMeasures (repr) { 75 | return repr 76 | .replace(/\s+\||\|\s+/, '|') // spaces between | 77 | .replace(/^\||\|\s*$/g, '') // first and last | 78 | .split('|') 79 | } 80 | 81 | /* 82 | * The following code is copied from https://github.com/maryrosecook/littlelisp 83 | * See: http://maryrosecook.com/blog/post/little-lisp-interpreter 84 | * Thanks Mary Rose Cook! 85 | */ 86 | var parenthesize = function (input, list) { 87 | var token = input.shift() 88 | if (token === undefined) { 89 | return list 90 | } else if (token === '(') { 91 | list.push(parenthesize(input, [])) 92 | return parenthesize(input, list) 93 | } else if (token === ')') { 94 | return list 95 | } else { 96 | return parenthesize(input, list.concat(token)) 97 | } 98 | } 99 | 100 | var tokenize = function (input) { 101 | return input 102 | .replace(/[\(]/g, ' ( ') 103 | .replace(/[\)]/g, ' ) ') 104 | .replace(/\,/g, ' ') 105 | .trim().split(/\s+/) 106 | } 107 | 108 | module.exports = { measures: measures, melody: melody, chords: chords } 109 | -------------------------------------------------------------------------------- /lib/notes.js: -------------------------------------------------------------------------------- 1 | /** @module notes */ 2 | 3 | var score = require('./score') 4 | 5 | // ======== UTILITY ======== 6 | // This is an utility function to create array of notes quickly. 7 | function notes (pitches, durations, params) { 8 | var p = toArray(pitches || null) 9 | var d = toArray(durations || 1) 10 | return p.map(function (pitch, i) { 11 | return score.note(+d[i % d.length], pitch, params) 12 | }) 13 | } 14 | 15 | // convert anything to an array (if string, split it) 16 | function toArray (obj) { 17 | if (Array.isArray(obj)) return obj 18 | else if (typeof obj === 'string') return obj.trim().split(/\s+/) 19 | else return [ obj ] 20 | } 21 | 22 | // ======= API ======== 23 | 24 | /** 25 | * Create a phrase (a sequential structure of notes) 26 | * 27 | * @param {String|Array} pitches - the phrase note pitches 28 | * @param {String|Array} durations - the phrase note durations 29 | * @param {Hash} attributes - the phrase note attributes 30 | * @return {Array} a sequential musical structure 31 | * 32 | * @example 33 | * score.phrase('A B C D E', 1) 34 | */ 35 | function phrase (p, d, a) { return score.seq(notes(p, d, a)) } 36 | 37 | /** 38 | * Create a collection of simultaneus notes 39 | * 40 | * You can specify a collection of pitches, durations and attributes 41 | * and `chord` will arrange them as a collection of notes in simultaneus 42 | * layout 43 | * 44 | * @param {String|Array} pitches - the chord note pitches 45 | * @param {String|Array} durations - the chord note durations 46 | * @param {Hash} attributes - the chord note attributes 47 | * @return {Array} a parallel musical structure 48 | * 49 | * @example 50 | * score.phrase('A B C D E', 1) 51 | */ 52 | function chord (p, d, a) { return score.sim(notes(p, d, a)) } 53 | 54 | module.exports = { phrase: phrase, chord: chord } 55 | -------------------------------------------------------------------------------- /lib/performance.js: -------------------------------------------------------------------------------- 1 | /** @module performance */ 2 | 3 | var score = require('./score') 4 | 5 | var inst = score.map(function (note, name) { 6 | return score.el(note, { inst: name }) 7 | }) 8 | 9 | var tempo = score.map(function (note, tempo) { 10 | var c = 60 / tempo 11 | return score.el(note, { duration: c * note.duration }) 12 | }) 13 | 14 | var vel = score.map(function (note, vel) { 15 | return score.el(note, { velocity: vel }) 16 | }) 17 | 18 | module.exports = { inst: inst, tempo: tempo, vel: vel } 19 | -------------------------------------------------------------------------------- /lib/rhythm.js: -------------------------------------------------------------------------------- 1 | /** @module rhythm */ 2 | 3 | var score = require('./score') 4 | 5 | var rhythm = {} 6 | 7 | /** 8 | * Create a rhythmic sequence from a pattern 9 | */ 10 | rhythm.pattern = function (pattern, duration) { 11 | var arr = pattern.split('') 12 | var dur = duration ? duration / arr.length : 1 13 | return score.seq(arr.map(score.note(dur))) 14 | } 15 | 16 | /** 17 | * Create a rhythmic sequence from an inter onset interval number 18 | */ 19 | rhythm.ioi = function (ioi) { 20 | return rhythm.pattern(rhythm.ioiToPattern(ioi)) 21 | } 22 | 23 | /** 24 | * Convert an [inter onset interval](https://en.wikipedia.org/wiki/Time_point#Interonset_interval) 25 | * to a pattern 26 | * 27 | * @param {String} ioi - the inter onset interval 28 | * @param {String} the rhythm pattern 29 | */ 30 | rhythm.ioiToPattern = function (num) { 31 | return num.split('').map(function (n) { 32 | return 'x' + Array(+n).join('.') 33 | }).join('') 34 | } 35 | 36 | /** 37 | * Convert a pattern string to inter onset interval string 38 | * 39 | * @param {String} pattern - the pattern to be converted 40 | * @return {String} the inter onset interval 41 | */ 42 | rhythm.patternToIoi = function (pattern) { 43 | return pattern.split(/x/) 44 | .map(function (d) { return d.length }) 45 | .filter(function (_, i) { return i }) // remove first 46 | .map(function (d) { return d + 1 }) 47 | .join('') 48 | } 49 | 50 | module.exports = rhythm 51 | -------------------------------------------------------------------------------- /lib/score.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * @module score 5 | */ 6 | var isArray = Array.isArray 7 | var slice = Array.prototype.slice 8 | var assign = Object.assign 9 | function typeOf (obj) { return isArray(obj) ? obj[0] : 'el' } 10 | function isStruct (e) { return isArray(e) && typeof e[0] === 'string' } 11 | 12 | // create a sequence builder 13 | function builder (name) { 14 | return function (elements) { 15 | if (arguments.length > 1) return [name].concat(slice.call(arguments)) 16 | else if (isStruct(elements)) return [name, elements] 17 | return [name].concat(elements) 18 | } 19 | } 20 | 21 | /** 22 | * Create a score element: an object with duration 23 | * 24 | * It's accepts any data you supply, but duration property has a special 25 | * meaning: it's a number representing the duration in arbitrary units. 26 | * It's assumed to be 0 (no duration) if not present or not a valid number 27 | * 28 | * @param {Number} duration - the element duration 29 | * @param {Object} data - the additional element data 30 | */ 31 | function el (d, data) { 32 | if (typeof d === 'object') return assign({}, d, data) 33 | else return assign({ duration: +d || 0 }, data) 34 | } 35 | 36 | /** 37 | * Create a note from duration and pitch. It's a helper function to create 38 | * score elements easely. 39 | * 40 | * A note is any object with duration and pitch attributes. The duration 41 | * must be a number and the pitch can be any value (but usually expressed by 42 | * integers, floats or strings) 43 | * 44 | * If only duration is provided, a partially applied function is returned. 45 | * 46 | * @param {Integer} duration - the note duration 47 | * @param {String|Integer} pitch - the note pitch 48 | * @param {Hash} data - (Optional) arbitraty note data 49 | * @return {Hash} a note 50 | * 51 | * @example 52 | * score.note(1, 'A') // => { duration: 1, pitch: 'A' } 53 | * score.note(0.5, 'anything') // => { duration: 0.5, pitch: 'anything' } 54 | * score.note(2, 'A', 2, { inst: 'piano' }) // => { duration: 2, pitch: 'A', inst: 'piano' } 55 | * 56 | * @example 57 | * // partially applied 58 | * ['C', 'D', 'E'].map(score.note(1)) // => [{ duration: 1, pitch: 'C'}, 59 | * { duration: 1, pitch: 'D'}, { duration: 1, pitch: 'E'}] 60 | */ 61 | function note (dur, pitch, data) { 62 | if (arguments.length === 1) return function (p, d) { return note(dur, p, d) } 63 | return assign({ pitch: pitch, duration: dur || 1 }, data) 64 | } 65 | 66 | /** 67 | * Create a musical structure where elements are sequenetial 68 | * 69 | * @function 70 | * @param {Array} elements - an array of elements 71 | * @return {Array} the sequential musical structure 72 | * 73 | * @example 74 | * score.sequential([score.note('A'), score.note('B')]) 75 | */ 76 | var seq = builder('seq') 77 | 78 | /** 79 | * Create a musical structure where elements are simultaneous 80 | * 81 | * @function 82 | * @example 83 | * score.sim([score.note('A'), score.note('B')]) 84 | */ 85 | var sim = builder('sim') 86 | 87 | /** 88 | * Transform a musical structure 89 | * 90 | * This is probably the most important function. It allows complex 91 | * transformations of musical structures using three functions 92 | * 93 | * @param {Function} elTransform - element transform function 94 | * @param {Function} seqTransform - sequential structure transform function 95 | * @param {Function} parTransform - simultaneous structure transform function 96 | * @param {*} ctx - an additional object passed to transform functions 97 | * @param {Object} score - the score to transform 98 | * @return {*} the result of the transformation 99 | */ 100 | function transform (nt, st, pt, ctx, obj) { 101 | switch (arguments.length) { 102 | case 0: return transform 103 | case 1: 104 | case 2: 105 | case 3: return transformer(nt, st, pt) 106 | case 4: return function (o) { return transformer(nt, st, pt)(ctx, o) } 107 | default: return transformer(nt, st, pt)(ctx, obj) 108 | } 109 | } 110 | 111 | function transformer (nt, st, pt) { 112 | var T = function (ctx, obj) { 113 | var m = function (o) { return T(ctx, o) } 114 | switch (typeOf(obj)) { 115 | case 'el': return nt(obj, ctx) 116 | case 'seq': return st(obj.slice(1).map(m), ctx) 117 | case 'sim': return pt(obj.slice(1).map(m), ctx) 118 | default: return obj 119 | } 120 | } 121 | return T 122 | } 123 | 124 | /** 125 | * Map the notes of a musical structure using a function 126 | * 127 | * @param {Function} fn - the function used to map the notes 128 | * @param {Object} ctx - a context object passed to the function 129 | * @param {Score} score - the score to transform 130 | * @return {Score} the transformed score 131 | */ 132 | function map (fn, ctx, obj) { 133 | switch (arguments.length) { 134 | case 0: return map 135 | case 1: return transform(fn, buildSeq, buildSim) 136 | case 2: return function (obj) { return map(fn)(ctx, obj) } 137 | case 3: return map(fn)(ctx, obj) 138 | } 139 | } 140 | function buildSeq (el, ctx) { return seq(el) } 141 | function buildSim (el, ctx) { return sim(el) } 142 | 143 | module.exports = { 144 | el: el, note: note, 145 | seq: seq, sequentially: seq, 146 | sim: sim, simultaneosly: sim, 147 | transform: transform, map: map } 148 | -------------------------------------------------------------------------------- /lib/stats.js: -------------------------------------------------------------------------------- 1 | /** @module stats */ 2 | var score = require('./score') 3 | 4 | function dur (obj) { return obj.duration } 5 | function one () { return 1 } 6 | function arrayMax (arr) { return Math.max.apply(null, arr) } 7 | function arrayAdd (arr) { return arr.reduce(function (a, b) { return a + b }) } 8 | 9 | /** 10 | * Get the total duration of a score 11 | * @function 12 | */ 13 | var duration = score.transform(dur, arrayAdd, arrayMax, null) 14 | 15 | /** 16 | * Get the longest element duration of a score 17 | * @function 18 | */ 19 | var longest = score.transform(dur, arrayMax, arrayMax, null) 20 | 21 | /** 22 | * Return the number of elements of a score 23 | */ 24 | var count = score.transform(one, arrayAdd, arrayAdd, null) 25 | 26 | module.exports = { duration: duration, longest: longest, count: count } 27 | -------------------------------------------------------------------------------- /lib/timed.js: -------------------------------------------------------------------------------- 1 | /** @module timed */ 2 | var score = require('./score') 3 | 4 | function repeat (times, seq) { 5 | var rep = [] 6 | for (var i = 0; i < times; i++) { 7 | rep.push(seq) 8 | } 9 | return score.seq(rep) 10 | } 11 | 12 | /** 13 | * Get all notes for side-effects 14 | * 15 | * __Important:__ ascending time ordered is not guaranteed 16 | * 17 | * @param {Function} fn - the function 18 | * @param {Object} ctx - (Optional) a context object passed to the function 19 | * @param {Score} score - the score object 20 | */ 21 | function forEachTime (fn, ctx, obj) { 22 | if (arguments.length > 1) return forEachTime(fn)(ctx, obj) 23 | 24 | return function (ctx, obj) { 25 | return score.transform( 26 | function (note) { 27 | return function (time, ctx) { 28 | fn(time, note, ctx) 29 | return note.duration 30 | } 31 | }, 32 | function (seq) { 33 | return function (time, ctx) { 34 | return seq.reduce(function (dur, fn) { 35 | return dur + fn(time + dur, ctx) 36 | }, 0) 37 | } 38 | }, 39 | function (par) { 40 | return function (time, ctx) { 41 | return par.reduce(function (max, fn) { 42 | return Math.max(max, fn(time, ctx)) 43 | }, 0) 44 | } 45 | } 46 | )(null, obj)(0, ctx) 47 | } 48 | } 49 | 50 | /** 51 | * Get a sorted events array from a score 52 | * 53 | */ 54 | function events (obj, build, compare) { 55 | var e = [] 56 | forEachTime(function (time, obj) { 57 | e.push(build ? build(time, obj) : [time, obj]) 58 | }, null, obj) 59 | return e.sort(compare || function (a, b) { return a[0] - b[0] }) 60 | } 61 | 62 | module.exports = { forEachTime: forEachTime, events: events, repeat: repeat } 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scorejs", 3 | "version": "0.10.0", 4 | "description": "Create and manipulate musical scores", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "mocha test/", 8 | "dist": "browserify lib/index.js > dist/score.js && uglifyjs dist/score.js > dist/score.min.js", 9 | "docs": "browserify lib/index.js | documentation -f md > API.md", 10 | "jsdoc": "jsdoc -c .jsdoc3.json && open docs/index.html", 11 | "disc": "mkdir -p tmp && browserify --full-paths lib/index.js | discify > tmp/disc.html" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/danigb/scorejs.git" 16 | }, 17 | "keywords": [ 18 | "music", 19 | "score" 20 | ], 21 | "author": "danigb", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/danigb/scorejs/issues" 25 | }, 26 | "homepage": "https://github.com/danigb/scorejs#readme", 27 | "devDependencies": { 28 | "eslint": "^2", 29 | "eslint-config-standard": "^5.1.0", 30 | "eslint-plugin-promise": "^1.1.0", 31 | "eslint-plugin-standard": "^1.3.2", 32 | "mocha": "^2", 33 | "soundfont-player": "^0.10.1", 34 | "tonal": "^0.50.2" 35 | }, 36 | "dependencies": { 37 | "note-parser": "^1.1.0" 38 | }, 39 | "standard": { 40 | "ignore": [ 41 | "dist/**" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/build-test.js: -------------------------------------------------------------------------------- 1 | /* global describe it */ 2 | 3 | var assert = require('assert') 4 | var score = require('..') 5 | var qn = score.note(1) 6 | 7 | describe('Builder module', function () { 8 | describe('build function', function () { 9 | it('create elements', function () { 10 | assert.deepEqual(score(['seq', ['note', 1, 'C']]), 11 | score.seq(qn('C'))) 12 | }) 13 | it('create simultaneosly if more than one parameter', function () { 14 | assert.deepEqual(score(['note', 1, 'A'], ['note', 1, 'B']), 15 | score.sim(qn('A'), qn('B'))) 16 | }) 17 | it('accepts functions', function () { 18 | var piano = score.map(function (e) { return score.el(e, { inst: 'piano' }) }, null) 19 | assert.deepEqual(score([piano, ['note', 1, 'A']]), 20 | { pitch: 'A', duration: 1, inst: 'piano' }) 21 | }) 22 | it('can create variables with $', function () { 23 | assert.deepEqual(score(['$v', ['note', 1, 'A']], ['seq', '$v', '$v']), 24 | ['sim', ['seq', {pitch: 'A', duration: 1}, {pitch: 'A', duration: 1}]]) 25 | }) 26 | it('accepts its own output', function () { 27 | var s = ['seq', 28 | ['sim', { pitch: 'C4', duration: 1 }, { pitch: 'E4', duration: 1 }, 29 | { pitch: 'G4', duration: 1 }, { pitch: 'B4', duration: 1 }], 30 | ['sim', { pitch: 'G4', duration: 1 }, { pitch: 'B4', duration: 1 }, 31 | { pitch: 'D4', duration: 1 }, { pitch: 'F4', duration: 1 }] 32 | ] 33 | assert.deepEqual(score(s), s) 34 | }) 35 | }) 36 | describe('build integration', function () { 37 | it('create notes', function () { 38 | assert.deepEqual(score(['note', 2, 'C']), score.note(2, 'C')) 39 | }) 40 | it('create sequences', function () { 41 | assert.deepEqual(score(['seq', ['note', 2, 'C']]), 42 | score.seq(score.note(2, 'C'))) 43 | }) 44 | it('create simultaneous', function () { 45 | assert.deepEqual(score(['sim', ['note', 2, 'C']]), 46 | score.sim(score.note(2, 'C'))) 47 | }) 48 | it('create phrases', function () { 49 | assert.deepEqual(score(['phrase', 'C D E', '3 2 1']), ['seq', 50 | { duration: 3, pitch: 'C' }, 51 | { duration: 2, pitch: 'D' }, 52 | { duration: 1, pitch: 'E' }]) 53 | }) 54 | it('create chords', function () { 55 | assert.deepEqual(score(['chord', 'C D E', '3 2 1']), ['sim', 56 | { duration: 3, pitch: 'C' }, 57 | { duration: 2, pitch: 'D' }, 58 | { duration: 1, pitch: 'E' }]) 59 | }) 60 | }) 61 | it.skip('create complex score', function () { 62 | score(['tempo', 120, ['parts', 63 | ['oct', '2', ['phrase', 'C D E']], 64 | ['phrase', 'F G E'], 65 | ['harmony', ['measures', '4/4', 'CM7 | % | Dm7 G7 | Am7']] 66 | ]]) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/integration/AnthropologyParker.ls.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Anthropology", 3 | "composer": "Charlie Parker", 4 | "meter": "4/4", 5 | "key": "-2", 6 | "tempo": "180.0", 7 | "parts": { 8 | "chords": "BbM7 G7 | Cm7 F7 | Dm7 G7 | Cm7 F7 | BbM7 | Eb Ebm6 | Dm7 G7 | Cm7 F7 | BbM7 G7 | Cm7 F7 | Dm7 G7 | Cm7 F7 | Bb7 | Eb Ebm6 | Dm7 G7 | Cm7 F7 BbM7 _ | D7 | _ | G7 | _ | C7 | _ | F7 | _ | BbM7 G7 | Cm7 F7 | Dm7 G7 | Cm7 F7 | Bb7 | Eb Ebm6 | Dm7 G7 | Cm7 F7 BbM7 _ | ", 9 | "Chorus 1": "bb/8 d+/8 c+/8t c#+/8t d+/8t e+/8 c#+/8 d+/8 f+/8 r/4+8 eb+/8 r/8 f+/8 eb+/4 r/8 d+/8 e+/8 d+/8 c+/8 a/8 b/8 d+/8 c+/8 g/8 bb/8 a/8 r/4+8 b/4+8 g/8 bb/8 d+/8 bb/4 fb+/8 r/8 eb+/4 f#/8 r/8 bb/8 c+/8 d+/8 f+/8 r/8 b/8 r/8 ab+/4 r/8 f#+/8 g+/8 eb+/8 bb/8 g/8 d+/8 f+/8 r/8 f/8 bb/8 d+/8 c+/8t c#+/8t d+/8t e+/8 c#+/8 d+/8 f+/8 r/4+8 eb+/8 r/8 f+/8 eb+/4 r/8 d+/8 e+/8 d+/8 c+/8 a/8 b/8 d+/8 c+/8 g/8 bb/8 a/8 r/4+8 b/4+8 g/8 bb/8 d+/8 bb/4 fb+/8 r/8 eb+/4 f#/8 r/8 bb/8 c+/8 d+/8 e+/8 f+/8 d+/8 f+/8 r/8 d+/8 e+/8 g/8 c+/8 g/8 b/8 c+/8 r/4+8 f#+/4+8 g+/8 g#+/8 a+/8 f#+/8 g#+/8 a+/8 f#+/8 d+/8 r/2+8 e+/8 f+/8 e+/8 d+/8 c#+/8 e+/8 d+/8 b/8 a/8 r/4+8 a+/8 r/8 a+/4 a+/4 g+/8 e+/8 d+/2 a/8 c+/8 g/8 bb/8 c+/8 r/4+8 e+/4+8 d+/8 c+/8 b/8 d+/8 c+/8 g/8 c+/4 bb/8 a/8 r/4+8 f/8 bb/8 d+/8 c+/8t c#+/8t d+/8t e+/8 c#+/8 d+/8 f+/8 r/4+8 eb+/8 r/8 f+/8 eb+/4 r/8 d+/8 e+/8 d+/8 c+/8 a/8 b/8 d+/8 c+/8 g/8 bb/8 a/8 r/4+8 b/4+8 g/8 bb/8 d+/8 bb/4 fb+/8 r/8 eb+/4 f#/8 r/8 bb/8 c+/8 d+/8 e+/8 f+/8 d+/8 f+/8 r/8 d+/8 e+/8 g/8 c+/8 bb/8 a/8 b/8 r/2", 10 | "melody2": "d/8t eb/8t e/8t f/8 d/8 eb/8 f/8 r/8 d/4 c/8 bb-/8 g-/8 c/4 r/8 bb-/8 a-/8 bb-/8 d/8 f/8 a/8 ab/8 g/8 gb/8 f/8 fb/8 eb/8 a-/8 r/4 d/8 c/8 bb-/8 a-/8 ab-/8 bb-/8 bb/8 a/8 g/8 f/8 eb/8 a-/8 bb-/8 r/8 db/8 bb-/8 g-/8 a-/8 c/8 bb-/8 a-/8 bb-/8 d/8 f/4+8 r/1+4+8 f+/4 d+/4 bb/8 c+/2 r/2+8 e/8 f/8t bb/8t d+/8t f+/8 fb+/8 eb+/8 cb/8 d+/8 db+/8 c+/8 cb/8 bb/8 gb/8 eb/8 c/8 d/8 f/8 a/8 c+/8 bb/8 gb/8 eb/8 r/2+8 bb/8 c+/16 bb/16 g/8 f/8 eb/4 bb/8t c+/8t bb/8t gb/8 f/8 eb/8 c/8 d/8 f/4 r/1 b/8 a/8t fb/8t c/8t bb/2 a/8 g/8 gb/8 a/8 c+/8 e+/8 d+/8 c+/8 b/8 d+/8 a/8 f/8 d/8 a-/8 b-/8 e/8 eb/8 b-/8 d/8 f-/8 r/2+8 c/16 d/16 e/8 f/8 g/8 a/8 c+/8 cb/8 bb/8 a/8 g/8 f/8 e/8t g/8t bb/8t d+/8t db+/8t c+/8t b/8 g/8 eb/8 c/8 bb/8 g/8 a/8 ab+/2+8 gb+/8 e+/8 f+/8 d+/8 r/1+2+4+8 d+/16 bb/16 f/8 g/8 bb/8 eb+/8 d+/8 c+/8 bb/8 gb/8 eb/8 c/8 d/8 bb-/8 g-/8 a-/8 c/8 b-/8 bb-/8 a-/8 ab-/8 c/8 bb/8 ab/8 g/8 f/8 eb/4 bb/8 gb/8 eb/8 c/8 d/8 bb-/8 r/1+2+4", 11 | "melody3": "r/4 e+/4 f+/4 db+/4 d+/4 a/8 bb/4 d+/8 bb/8 f/8 g/8 a/8 c+/8 a/8 cb/8 ab/8 fb/8 db/8 bb/8t c+/8t bb/8t a/8 g/8 f/8 eb/8 d/8 c/8 bb-/8 ab-/8 g-/8t ab-/8t c/8t bb/8 ab/8 g/8 f/8 eb/8 a-/8 bb-/8 c/8 db/8 bb-/8 g-/8 a-/8 c/8 bb-/2 r/1+4+8 bb-/8t c/8t bb-/8t a-/8 bb-/8 c/8 a-/8 bb-/8t c/8t d/8t f/8 eb/4 fb/4 g/4 fb/8 f/4 r/2+4+8 c+/16 a/16 cb/16 ab/16 fb/16 d/16 bb/16 a/16 g/16 f/16 eb/16 d/16 c/16 bb-/16 a-/16 c/16 eb/16 gb/16 f/4 r/8 eb/4 c/8 bb-/8 ab-/8 g-/8 bb-/8 bb/8 gb/8 eb/8 c/8 d/8 bb-/8 g-/8 a-/8 c/8 bb-/8 a-/8 bb-/8 d/2 r/1+1 d+/8 db+/8 c+/8 cb/8 a/8 g/8 gb/8 a/8 c+/8 eb+/8 d+/8 c+/8 b/8 d/8 f/8 ab/4+8 g/8 f/8 e/8 c/8 bb-/4 d/8 bb-/8 g-/8 a-/8 c/8 a-/8 bb-/8t d/8t fb/8t a/8 g/8 fb/8 c/8 f/4 f/2 r/1+2 f/8t g/8t a/8t bb/8t c+/8t c#+/8t d+/8 bb/4 c+/16 bb/16 g/8 bb/8 c+/8 d+/4+8 r/4 f/8t g/8t a/8t bb/8t c+/8t c#+/8t d+/8 c+/4+8 bb/8 g/8 a/8 f/4+8 r/4+8 c+/16 cb/16 bb/8 a/8 ab/8 c+/8 bb/8 g/8 eb/8 c/8 bb/8 gb/8 eb/8 c/8 d/8 bb-/8 g-/8 a-/8 c/8 bb-/8 a-/8 bb-/8 eb/8 f/8 d/2+8 f/16 d/16" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/integration/GoodByePorkPieHat.scorejs.txt: -------------------------------------------------------------------------------- 1 | title = Good Bye Pork Pie Hat 2 | author = Charles Mingus 3 | meter = 4/4 4 | key = -4 5 | 6 | [chords] 7 | r | F7 Db7 | Gbmaj7 B7 | Eb7 Db7 | 8 | | Eb7 F7 | Bbm7 Db7 | Gm7 C7 | 9 | | D7 G7 | Db7Gbmaj7 | B7 Bb7 | 10 | | C7 Eb7 | F7 Db7 | Gbmaj7 B7 | r 11 | 12 | [melody] 13 | r r r (r c) | (f a) (_ f a) (b a _ (f d)) | (f a _ (f e)) (f _ _ c) | (f a) (_ f a) (cb+ b _ (f e)) | 14 | | (f a _ (f e)) (f _ _ f) | (c+ e+ (_ f a)) (b a (f a)) | (c+ e+) (_ f b) (e+ _ c+) (a b; e) | 15 | | (a b f) (e b g) | a (f _ _ (cb+ b)) | a f | 16 | | b a f e cb+ b a f | a f | (c b a) (f e c) | f 17 | -------------------------------------------------------------------------------- /test/integration/TestFormat.scorejs.txt: -------------------------------------------------------------------------------- 1 | title = Values are left and right trimmed 2 | author = Keys are trimmed too 3 | quotes = "Double quotes are ignored" 4 | singleQuotes = 'Single quotes are ignored' 5 | 6 | [partA] 7 | | A | B | 8 | 9 | [partB] 10 | | a b | 11 | | c d | 12 | -------------------------------------------------------------------------------- /test/measures-test.js: -------------------------------------------------------------------------------- 1 | /* global describe it */ 2 | 3 | var assert = require('assert') 4 | var score = require('..') 5 | 6 | describe('Measures module', function () { 7 | describe('chords', function () { 8 | it('create chords', function () { 9 | assert.deepEqual(score.chords('4/4', 'C | Dm G7 | C'), [ 'seq', 10 | { duration: 4, chord: 'C' }, 11 | { duration: 2, chord: 'Dm' }, 12 | { duration: 2, chord: 'G7' }, 13 | { duration: 4, chord: 'C' } ]) 14 | }) 15 | }) 16 | describe('measure', function () { 17 | it('no parenthesis divide time by number of items', function () { 18 | assert.deepEqual(score(['measures', '4/4', 'a | b c ']), 19 | score.phrase('a b c', '4 2 2')) 20 | }) 21 | it('parenthesis group time', function () { 22 | assert.deepEqual(score(['measures', '4/4', '(a b) c']), 23 | score.phrase('a b c', '1 1 2')) 24 | }) 25 | it('can have nested parenthesis', function () { 26 | assert.deepEqual(score.measures('4/4', 'a (b (c d))'), 27 | score.phrase('a b c d', '2 1 0.5 0.5')) 28 | }) 29 | it('can handle 3/4 meter', function () { 30 | assert.deepEqual(score.measures('3/4', 'a b c'), 31 | score.phrase('a b c', 1)) 32 | }) 33 | it('can handle 6/8 meter', function () { 34 | assert.deepEqual(score.measures('6/8', 'a b'), 35 | score.phrase('a b', 1.5)) 36 | }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /test/notes-test.js: -------------------------------------------------------------------------------- 1 | /* global describe it */ 2 | 3 | var assert = require('assert') 4 | var _ = require('..') 5 | 6 | describe('Notes module', function () { 7 | describe('phrase', function () { 8 | it('accepts pitches and durations', function () { 9 | assert.deepEqual(_.phrase('A B C D', '1 0.5'), [ 'seq', 10 | { pitch: 'A', duration: 1 }, 11 | { pitch: 'B', duration: 0.5 }, 12 | { pitch: 'C', duration: 1 }, 13 | { pitch: 'D', duration: 0.5 } ]) 14 | }) 15 | }) 16 | 17 | describe('chord', function () { 18 | it('set duration 1 by default', function () { 19 | assert.deepEqual(_.chord('C E G'), [ 'sim', 20 | { pitch: 'C', duration: 1 }, 21 | { pitch: 'E', duration: 1 }, 22 | { pitch: 'G', duration: 1 } ]) 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /test/performance-test.js: -------------------------------------------------------------------------------- 1 | /* global describe it */ 2 | 3 | var assert = require('assert') 4 | var score = require('..') 5 | 6 | describe('Performance module', function () { 7 | describe('inst function', function () { 8 | it('can set instrument', function () { 9 | assert.deepEqual(score.inst('piano', score.phrase('a b c')), [ 'seq', 10 | { pitch: 'a', duration: 1, inst: 'piano' }, 11 | { pitch: 'b', duration: 1, inst: 'piano' }, 12 | { pitch: 'c', duration: 1, inst: 'piano' } ]) 13 | }) 14 | }) 15 | 16 | describe('tempo function', function () { 17 | it('set tempo and return events', function () { 18 | assert.deepEqual(score.tempo(120, score.phrase('a b', 1)), [ 'seq', 19 | { pitch: 'a', duration: 0.5 }, 20 | { pitch: 'b', duration: 0.5 } ]) 21 | }) 22 | }) 23 | 24 | describe('vel function', function () { 25 | it('set velocity and return events', function () { 26 | assert.deepEqual(score.vel(50, score.phrase('a b', 1)), [ 'seq', 27 | { pitch: 'a', duration: 1, velocity: 50 }, 28 | { pitch: 'b', duration: 1, velocity: 50 } ]) 29 | }) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /test/pianoroll-test.js: -------------------------------------------------------------------------------- 1 | /* global describe it */ 2 | 3 | var assert = require('assert') 4 | var score = require('..') 5 | var pianoroll = require('../ext/pianoroll') 6 | 7 | describe('Piano roll', function () { 8 | describe('build', function () { 9 | it('has default size', function () { 10 | var pr = pianoroll.build(null, score.phrase('a b c')) 11 | }) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /test/rhythm-test.js: -------------------------------------------------------------------------------- 1 | /* global describe it */ 2 | 3 | var assert = require('assert') 4 | var rhythm = require('../lib/rhythm') 5 | 6 | describe('Rhythm module', function () { 7 | describe('pattern function', function () { 8 | it('create rhythm with duration 1 elements', function () { 9 | assert.deepEqual(rhythm.pattern('x.x.'), [ 'seq', 10 | { pitch: 'x', duration: 1 }, 11 | { pitch: '.', duration: 1 }, 12 | { pitch: 'x', duration: 1 }, 13 | { pitch: '.', duration: 1 } ]) 14 | }) 15 | }) 16 | 17 | describe('ioi function', function () { 18 | it('creates rhythm from interOnsetInterval', function () { 19 | assert.deepEqual(rhythm.ioi('233'), rhythm.pattern('x.x..x..')) 20 | }) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /test/score-test.js: -------------------------------------------------------------------------------- 1 | /* global describe it */ 2 | 3 | var assert = require('assert') 4 | var score = require('..') 5 | 6 | describe('Score module', function () { 7 | describe('el function', function () { 8 | it('accepts duration', function () { 9 | assert.deepEqual(score.el(2), { duration: 2 }) 10 | }) 11 | it('accepts duration and data', function () { 12 | assert.deepEqual(score.el(3, { any: 'thing' }), 13 | { duration: 3, any: 'thing' }) 14 | }) 15 | it('accepts two object to merge as they were immutable', function () { 16 | var note = score.el(3, { pitch: 'A' }) 17 | var update = { pitch: 'C' } 18 | assert.deepEqual(score.el(note, update), { duration: 3, pitch: 'C' }) 19 | assert.deepEqual(note, { duration: 3, pitch: 'A' }) 20 | assert.deepEqual(update, { pitch: 'C' }) 21 | }) 22 | }) 23 | describe('note function', function () { 24 | it('partially apply duration', function () { 25 | var qn = score.note(1) 26 | assert.deepEqual(qn('A'), { pitch: 'A', duration: 1 }) 27 | }) 28 | it('accepts pitch and duration', function () { 29 | assert.deepEqual(score.note(2, 'A'), { pitch: 'A', duration: 2 }) 30 | }) 31 | it('accepts pitch, duration and data', function () { 32 | assert.deepEqual(score.note(3, 'A', { inst: 'synth' }), 33 | { pitch: 'A', duration: 3, inst: 'synth' }) 34 | }) 35 | }) 36 | 37 | describe('sequential', function () { 38 | var qn = score.note(1) 39 | it('from array', function () { 40 | assert.deepEqual(score.seq([qn('A'), qn('B')]), ['seq', 41 | { pitch: 'A', duration: 1 }, 42 | { pitch: 'B', duration: 1 } ]) 43 | }) 44 | it('from arguments', function () { 45 | assert.deepEqual(score.seq(qn('A'), qn('B')), 46 | score.seq([qn('A'), qn('B')])) 47 | }) 48 | it('joins seq sim', function () { 49 | assert.deepEqual(score.seq(score.sim(qn('A'))), 50 | [ 'seq', [ 'sim', { pitch: 'A', duration: 1 } ] ]) 51 | }) 52 | }) 53 | describe('simultaneous', function () { 54 | var qn = score.note(1) 55 | it('from array', function () { 56 | assert.deepEqual(score.sim([qn('A'), qn('B')]), ['sim', 57 | { pitch: 'A', duration: 1 }, 58 | { pitch: 'B', duration: 1 } ]) 59 | }) 60 | it('from arguments', function () { 61 | assert.deepEqual(score.sim(qn('A'), qn('B')), 62 | score.sim([qn('A'), qn('B')])) 63 | }) 64 | it('joins sim seq', function () { 65 | assert.deepEqual(score.sim(score.seq(qn('A'))), 66 | ['sim', ['seq', { duration: 1, pitch: 'A' }]]) 67 | }) 68 | }) 69 | 70 | describe('transform function', function () { 71 | var T = score.transform( 72 | function (note) { return note.pitch }, 73 | function (seq) { return '(' + seq.join(',') + ')' }, 74 | function (par) { return '[' + par.join('+') + ']' }, 75 | null 76 | ) 77 | it('can transform notes', function () { 78 | assert.deepEqual(T(score.note(1, 'A')), 'A') 79 | }) 80 | it('can transform sequences', function () { 81 | assert.deepEqual(T(score.phrase('A B C')), '(A,B,C)') 82 | }) 83 | it('can transform sim', function () { 84 | assert.deepEqual(T(score.chord('A B C')), '[A+B+C]') 85 | }) 86 | it('can transform musical structures', function () { 87 | assert.deepEqual(T(score.sim( 88 | score.phrase('A B C'), score.seq(score.chord('C E G'), score.chord('D F A')) 89 | )), '[(A,B,C)+([C+E+G],[D+F+A])]') 90 | }) 91 | }) 92 | 93 | describe('map function', function () { 94 | it('can map notes', function () { 95 | var piano = score.map(function (note) { 96 | return score.el(note, { inst: 'piano' }) 97 | }, null) 98 | assert.deepEqual(piano(score.seq(score.chord('A B'), score.chord('B C'))), ['seq', 99 | ['sim', { pitch: 'A', duration: 1, inst: 'piano' }, 100 | { pitch: 'B', duration: 1, inst: 'piano' }], 101 | ['sim', { pitch: 'B', duration: 1, inst: 'piano' }, 102 | { pitch: 'C', duration: 1, inst: 'piano' }] ]) 103 | }) 104 | it('passes a context object', function () { 105 | var inst = score.map(function (note, name) { 106 | return score.el(note, { inst: name }) 107 | }) 108 | assert.deepEqual(inst('piano', score.seq(score.chord('A B'), score.chord('B C'))), ['seq', 109 | ['sim', { pitch: 'A', duration: 1, inst: 'piano' }, 110 | { pitch: 'B', duration: 1, inst: 'piano' }], 111 | ['sim', { pitch: 'B', duration: 1, inst: 'piano' }, 112 | { pitch: 'C', duration: 1, inst: 'piano' }] ]) 113 | }) 114 | }) 115 | }) 116 | -------------------------------------------------------------------------------- /test/stats-test.js: -------------------------------------------------------------------------------- 1 | /* global describe it */ 2 | 3 | var assert = require('assert') 4 | var score = require('..') 5 | 6 | describe('[stats]', function () { 7 | it('get note duration', function () { 8 | assert.equal(score.duration(score.phrase('a b c', 1)), 3) 9 | assert.equal(score.duration(score.chord('a b c', 1)), 1) 10 | }) 11 | it('can get longest note duration', function () { 12 | assert.equal(score.longest(score.phrase('a b c', '1 2 1')), 2) 13 | }) 14 | it('can get the number of elements', function () { 15 | assert.equal(score.count(score.phrase('a b c')), 3) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /test/timed-test.js: -------------------------------------------------------------------------------- 1 | /* global describe it */ 2 | 3 | var assert = require('assert') 4 | var score = require('..') 5 | 6 | describe('Timed module', function () { 7 | describe('forEachTime', function () { 8 | it('have time', function () { 9 | var ctx = [] 10 | score.forEachTime(function (time, note, ctx) { 11 | ctx.push(time) 12 | }, ctx, score.sim(score.phrase('C D E'), score.phrase('E F G'))) 13 | 14 | assert.deepEqual(ctx, [ 0, 1, 2, 0, 1, 2 ]) 15 | }) 16 | }) 17 | 18 | describe('events', function () { 19 | it('have ordered events', function () { 20 | assert.deepEqual(score.events(score.sim(score.phrase('A B C'), score.phrase('D'))), 21 | [ [ 0, { pitch: 'A', duration: 1 } ], 22 | [ 0, { pitch: 'D', duration: 1 } ], 23 | [ 1, { pitch: 'B', duration: 1 } ], 24 | [ 2, { pitch: 'C', duration: 1 } ] ]) 25 | }) 26 | }) 27 | }) 28 | --------------------------------------------------------------------------------