├── Gruntfile.js ├── README.md ├── package.json ├── src ├── css │ └── style.css ├── html │ ├── index.html │ └── test.html └── js │ ├── control_panel.js │ ├── graphical_measure.js │ ├── graphical_note.js │ ├── graphical_object.js │ ├── graphical_state.js │ ├── graphical_stave.js │ ├── live_score.js │ ├── measure.js │ ├── midi_player.js │ ├── music_constants.js │ ├── musical_state.js │ ├── note.js │ ├── note_popup.js │ ├── playback_panel.js │ ├── renderer.js │ ├── score_editor.js │ ├── score_panel.js │ ├── stave.js │ ├── structs.js │ ├── ui.js │ ├── voice.js │ └── z.js ├── support ├── MIDI.js └── soundfont │ ├── acoustic_grand_piano-mp3.js │ ├── acoustic_grand_piano-ogg.js │ ├── synth_drum-mp3.js │ └── synth_drum-ogg.js └── tests └── test.html /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Gruntfile for live_score. 2 | // John Bernier 3 | 4 | module.exports = function(grunt) { 5 | var L = grunt.log.writeln; 6 | var BANNER = '/**\n' + 7 | ' * live_score <%= pkg.version %> built on <%= grunt.template.today("yyyy-mm-dd") %>.\n' + 8 | ' * Copyright (c) 2015 John Bernier \n' + 9 | ' *\n' + 10 | ' * https://github.com/jbernie2/live_score\n' + 11 | ' */\n'; 12 | var BUILD_DIR = 'build'; 13 | var RELEASE_DIR = 'releases'; 14 | var TARGET_RAW = BUILD_DIR + '/live_score-debug.js'; 15 | var TARGET_MIN = BUILD_DIR + '/live_score-min.js'; 16 | 17 | var HTML_DIR = 'src/html'; 18 | var CSS_DIR = 'src/css'; 19 | var SUPPORT_DIR = 'support' 20 | 21 | var SOURCES = [ "src/js/*.js" ]; 22 | 23 | grunt.initConfig({ 24 | pkg: grunt.file.readJSON('package.json'), 25 | concat: { 26 | options: { 27 | banner: BANNER 28 | }, 29 | build: { 30 | src: SOURCES, 31 | dest: TARGET_RAW 32 | } 33 | }, 34 | uglify: { 35 | options: { 36 | banner: BANNER, 37 | sourceMap: true 38 | }, 39 | build: { 40 | src: TARGET_RAW, 41 | dest: TARGET_MIN 42 | } 43 | }, 44 | browserify: { 45 | live_score: { 46 | options: { 47 | banner: BANNER, 48 | browserifyOptions: { 49 | debug: true, 50 | standalone: "live_score" 51 | } 52 | }, 53 | files: [ 54 | { src: SOURCES, dest: TARGET_RAW } 55 | ] 56 | } 57 | }, 58 | jshint: { 59 | files: SOURCES, 60 | options: { 61 | eqnull: true, // allow == and ~= for nulls 62 | sub: true, // don't enforce dot notation 63 | trailing: true, // no more trailing spaces 64 | } 65 | }, 66 | copy: { 67 | release: { 68 | files: [ 69 | { 70 | expand: true, 71 | dest: RELEASE_DIR, 72 | cwd: BUILD_DIR, 73 | src : ['*.js', 'docs/**', '*.map'] 74 | } 75 | ] 76 | }, 77 | build: { 78 | files: [ 79 | { 80 | expand: true, 81 | dest: BUILD_DIR, 82 | cwd: HTML_DIR, 83 | src : ['*.html'] 84 | }, 85 | { 86 | expand: true, 87 | dest: BUILD_DIR, 88 | cwd: CSS_DIR, 89 | src : ['*.css'] 90 | }, 91 | { 92 | expand: true, 93 | dest: BUILD_DIR, 94 | cwd: SUPPORT_DIR, 95 | src : ['*.js','soundfonts/*.js'] 96 | } 97 | ] 98 | } 99 | }, 100 | docco: { 101 | src: SOURCES, 102 | options: { 103 | layout: 'linear', 104 | output: 'build/docs' 105 | } 106 | }, 107 | clean: [BUILD_DIR, RELEASE_DIR], 108 | }); 109 | 110 | // Load the plugin that provides the "uglify" task. 111 | grunt.loadNpmTasks('grunt-contrib-concat'); 112 | grunt.loadNpmTasks('grunt-contrib-uglify'); 113 | grunt.loadNpmTasks('grunt-contrib-jshint'); 114 | grunt.loadNpmTasks('grunt-contrib-copy'); 115 | grunt.loadNpmTasks('grunt-contrib-clean'); 116 | grunt.loadNpmTasks('grunt-browserify'); 117 | grunt.loadNpmTasks('grunt-docco'); 118 | 119 | // Default task(s). 120 | grunt.registerTask('default', ['jshint', 'browserify:live_score', 'uglify', 'copy:build', 'docco']); 121 | }; 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # live_score v0.4.0 2 | 3 | An interactive score editor in javascript. 4 | 5 | ## What is live_score 6 | 7 | live_score is an interactive score editor written entirely in javascript. 8 | live_score allows for score creation, editing, and playback right in 9 | your browser. live_score is based on the [VexFlow](http://vexflow.com) music notation 10 | rendering API and uses [MIDI.js](https://github.com/mudcube/MIDI.js/) for Midi playback 11 | A demo of the current version of live_score is available [here](http://jbernie2.github.io/live_score) 12 | 13 | ## Version 1.0.0 Planned Features 14 | 1. Allow for addition of notes to score 15 | 2. Allow for removal of notes from score 16 | 3. Allow for whole, half, quarter, sixteenth, and thirty-second notes 17 | 4. Allow for the number of measures to be expanded or contracted 18 | 5. Allow for score play-back 19 | 20 | ## Developing live_score 21 | 22 | Install Prerequisites: 23 | 24 | 1. [node js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager) 25 | 2. [npm](http://blog.npmjs.org/post/85484771375/how-to-install-npm) 26 | 3. [grunt-cli](http://gruntjs.com/getting-started) 27 | 28 | 29 | Clone This Repo: 30 | 31 | $ git clone https://github.com/jbernie2/live_score.git 32 | 33 | Install Dependencies Via NPM: 34 | 35 | $ cd live_score 36 | $ npm install 37 | 38 | Building The Project 39 | 40 | $ grunt 41 | 42 | Running Locally 43 | 44 | Open build/index.html in your browser 45 | 46 | ## License 47 | MIT License 48 | 49 | Copyright (c) John Bernier 2015
50 | 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "live_score", 3 | "version": "0.0.1", 4 | "author": { 5 | "name": "John Bernier", 6 | "email": "john.b.bernier@gmail.com" 7 | }, 8 | "main": "src/live_score.js", 9 | "description": "An interactive score editor in javascript", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/jbernie2/live_score" 13 | }, 14 | "license": "Free for non-commercial use", 15 | "dependencies": { 16 | "vexflow": "*" 17 | }, 18 | "devDependencies": { 19 | "browserify": "^6.3.3", 20 | "docco": "^0.6.3", 21 | "glob": "^4.2.1", 22 | "grunt": "^0.4.5", 23 | "grunt-browserify": "^3.2.1", 24 | "grunt-cli": "^0.1.13", 25 | "grunt-contrib-clean": "^0.6.0", 26 | "grunt-contrib-concat": "^0.5.0", 27 | "grunt-contrib-copy": "^0.7.0", 28 | "grunt-contrib-jshint": "^0.10.0", 29 | "grunt-contrib-uglify": "^0.6.0", 30 | "grunt-docco": "^0.3.3" 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/css/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin:0px; 3 | background-color: #354458; 4 | text-align:center; 5 | } 6 | 7 | h1 { 8 | font-family: "open-sans",sans-serif; 9 | font-size: 32px; 10 | line-height: 50px; 11 | margin-bottom: 20px; 12 | background-color: #354458; 13 | color: #f0f0f0; 14 | } 15 | 16 | a { 17 | color: #aaaaaa; 18 | } 19 | 20 | #live_score_main_panel { 21 | padding-top:50px; 22 | background-color: #fff; 23 | height:200px; 24 | } 25 | 26 | /* 27 | #middle_panel { 28 | position:relative; 29 | } 30 | 31 | #playback_panel { 32 | position:absolute; 33 | top:50%; 34 | height:10em; 35 | margin-top:-5em; 36 | text-align:center; 37 | } 38 | */ 39 | 40 | #cursor_panel { 41 | 42 | } 43 | 44 | #input_panel{ 45 | 46 | } 47 | 48 | #page_footer { 49 | margin-top:20px; 50 | float:bottom; 51 | color: #f0f0f0; 52 | } 53 | 54 | #email { 55 | margin-right:10px; 56 | } 57 | 58 | #github { 59 | margin-left:10px; 60 | } 61 | -------------------------------------------------------------------------------- /src/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Live Score v0.4.0 Demo

7 | 8 |
9 |
10 | quantization: 11 | 19 | 20 | note length: 21 | 29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 |
44 | 56 | 57 | 58 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/html/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 | 9 | 10 |
11 |
note length: 12 | 20 |
21 |
quantization: 22 | 30 |
31 |
32 | 33 |
34 |
35 | 36 |
0,0
37 |
38 | 39 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/js/control_panel.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | 3 | /** 4 | * Control_panel 5 | * Constructor for the live_score.Control_panel object, which contains all the 6 | * various buttons and controls for the UI 7 | * args 8 | * ui_info 9 | * a struct (see structs.js) containing the current state of the ui 10 | * returns 11 | * none 12 | */ 13 | live_score.Control_panel = function(ui_info){ 14 | 15 | /** 16 | * (see function description) 17 | */ 18 | this.ui_info = ui_info; 19 | 20 | /** 21 | * the button that sets the score's input mode to insert, allowing notes to be 22 | * added to the score 23 | */ 24 | this.insert_note_button = document.getElementById("insert_note"); 25 | this.insert_note_button.onclick = this.set_input_mode( 26 | live_score.insert_mode); 27 | 28 | /** 29 | * the button that sets the score's input mode to remove, allowing notes to be 30 | * removed from the score 31 | */ 32 | this.remove_note_button = document.getElementById("remove_note"); 33 | this.remove_note_button.onclick = this.set_input_mode( 34 | live_score.remove_mode); 35 | 36 | /** 37 | * drop down menu that allows for selection of the note length 38 | */ 39 | this.note_select = document.getElementById("note_select"); 40 | this.note_select.onchange = this.set_note_length(); 41 | 42 | /** 43 | * drop down menu that allows for selection of the quantization size 44 | */ 45 | this.quantization_select = document.getElementById("quantization_select"); 46 | this.quantization_select.onchange = this.set_quantization(); 47 | 48 | this.set_control_defaults(); 49 | }; 50 | 51 | /** 52 | * set_control_defaults 53 | * sets the starting state of UI 54 | * args 55 | * none 56 | * returns 57 | * none 58 | */ 59 | live_score.Control_panel.prototype.set_control_defaults = function(){ 60 | 61 | var note_length = parseInt(this.note_select.value,10); 62 | var quantization = parseInt(this.quantization_select.value,10); 63 | 64 | this.ui_info.note_length = note_length; 65 | this.ui_info.quantization = quantization; 66 | this.ui_info.input_mode = live_score.insert_mode; 67 | }; 68 | 69 | /** 70 | * set_input_mode 71 | * attaches a function to a button that when pressed sets the input mode 72 | * args 73 | * input_mode 74 | * the value that the input mode gets set to when the button is pressed 75 | * returns 76 | * none 77 | */ 78 | live_score.Control_panel.prototype.set_input_mode = function(input_mode){ 79 | var ui_info = this.ui_info; 80 | return function(){ 81 | ui_info.input_mode = input_mode; 82 | }; 83 | }; 84 | 85 | /** 86 | * set_note_length 87 | * attaches a function to the note selection drop down menu. The function 88 | * sets the length of the note to be inserted 89 | * args 90 | * none 91 | * returns 92 | * none 93 | */ 94 | live_score.Control_panel.prototype.set_note_length = function(){ 95 | var ui_info = this.ui_info; 96 | return function(){ 97 | var note_length = parseInt(this.value,10); 98 | ui_info.note_length = note_length; 99 | }; 100 | }; 101 | 102 | /** 103 | * set_quantization 104 | * attaches a function to the quantization selection drop down menu. The 105 | * function sets the quantization level 106 | * args 107 | * none 108 | * returns 109 | * none 110 | */ 111 | live_score.Control_panel.prototype.set_quantization = function(){ 112 | var ui_info = this.ui_info; 113 | return function(){ 114 | var quantization = parseInt(this.value,10); 115 | ui_info.quantization = quantization; 116 | }; 117 | }; 118 | 119 | module.exports = live_score.Control_panel; 120 | -------------------------------------------------------------------------------- /src/js/graphical_measure.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | live_score.Graphical_object = require("./graphical_object.js"); 3 | live_score.Graphical_note = require("./graphical_note.js"); 4 | 5 | /** 6 | * Graphical_measure 7 | * stores positional information about the measured displayed in the score 8 | * args 9 | * none 10 | * returns 11 | * none 12 | */ 13 | live_score.Graphical_measure = function(){ 14 | 15 | /** 16 | * a Graphical_object (see graphical_object.js) containing the coordinate 17 | * boundaries of a measure 18 | */ 19 | this.bounds = new live_score.Graphical_object(); 20 | 21 | /** 22 | * an array of Graphical_note objects (see graphical_note.js) representing the 23 | * notes contained in this measure 24 | */ 25 | this.notes = []; 26 | 27 | /** 28 | * an array of Graphical_note objects (see graphical_note.js) representing the 29 | * rests contained in this measure 30 | */ 31 | this.rests = []; 32 | 33 | /** 34 | * The union of the notes and rests arrays 35 | */ 36 | this.score_objects = []; 37 | 38 | /** 39 | * The number of ticks in this measure as defined by live_score.RESOLUTION 40 | */ 41 | this.num_ticks = 0; 42 | }; 43 | 44 | /** 45 | * extract_posiitonal_info 46 | * takes vexflow objects and determines where they are rendered on the score 47 | * args 48 | * previous_measure 49 | * a graphical_object containing the bounds of the previous measure 50 | * current_measure 51 | * an object representing a barline, created by Vexflow, that is displayed 52 | * in the score 53 | * measure_contents 54 | * an array of vexflow objects rendered within the bounds of the measure 55 | * returns 56 | * none 57 | */ 58 | live_score.Graphical_measure.prototype.extract_positional_info = function( 59 | previous_measure,current_measure,measure_contents){ 60 | 61 | this.extract_measure_info(previous_measure,current_measure); 62 | 63 | var measure_position = 0; 64 | for(var i = 0; i < measure_contents.length; i++){ 65 | var score_object = measure_contents[i]; 66 | if(this.is_note(score_object)){ 67 | this.add_note(score_object,measure_position); 68 | }else if(this.is_rest(score_object)){ 69 | this.add_rest(score_object,measure_position); 70 | } 71 | measure_position += live_score.note_length_to_ticks(score_object.duration); 72 | } 73 | this.num_ticks = measure_position; 74 | }; 75 | 76 | /** 77 | * extract_measure_info 78 | * extracts positional information from the Vexflow object and 79 | * uses that information to determine the positioning of a measure 80 | * args 81 | * previous_measure 82 | * a graphical_object containing the bounds of the previous measure 83 | * current_measure 84 | * an object representing a barline, created by Vexflow, that is displayed 85 | * in the score 86 | * returns 87 | * none 88 | */ 89 | live_score.Graphical_measure.prototype.extract_measure_info = function( 90 | previous_measure,current_measure){ 91 | 92 | if(previous_measure === null){ 93 | this.bounds.start_x = current_measure.stave.start_x; 94 | }else{ 95 | this.bounds.start_x = previous_measure.end_x + 1; 96 | } 97 | this.bounds.end_x = current_measure.getAbsoluteX(); 98 | this.bounds.start_y = current_measure.stave.bounds.y; 99 | this.bounds.end_y = current_measure.stave.bounds.y + 100 | current_measure.stave.height; 101 | }; 102 | 103 | /** 104 | * is_note 105 | * checks if a given score_object is a note 106 | * args 107 | * score_object 108 | * an object, created by Vexflow that is displayed in the score 109 | * returns 110 | * a boolean value denoting whether the score_object is a note 111 | */ 112 | live_score.Graphical_measure.prototype.is_note = function(score_object){ 113 | return (score_object.noteType && 114 | score_object.noteType === "n" && 115 | score_object.duration !== "b"); 116 | }; 117 | 118 | /** 119 | * is_rest 120 | * checks if a given score_object is a rest 121 | * args 122 | * score_object 123 | * an object, created by Vexflow that is displayed in the score 124 | * returns 125 | * a boolean value denoting whether the score_object is a rest 126 | */ 127 | live_score.Graphical_measure.prototype.is_rest = function(score_object){ 128 | return (score_object.noteType && 129 | score_object.noteType === "r" && 130 | score_object.duration !== "b"); 131 | }; 132 | 133 | /** 134 | * add_note 135 | * adds a Graphical_note (see Graphical_note.js) to the array of notes 136 | * contained within the measure 137 | * args 138 | * note_object 139 | * an object, created by Vexflow that is displayed in the score 140 | * measure_position 141 | * the starting position of the note being added (in ticks) 142 | * returns 143 | * none 144 | */ 145 | live_score.Graphical_measure.prototype.add_note = function(note_object, 146 | measure_position){ 147 | 148 | for(var i = 0; i < note_object.note_heads.length; i++){ 149 | var note_head = note_object.note_heads[i]; 150 | var pitch = note_object.keyProps[i].key + "/" + 151 | note_object.keyProps[i].octave; 152 | 153 | var gn = new live_score.Graphical_note(); 154 | gn.extract_positional_info(note_head,measure_position,pitch); 155 | 156 | this.notes.push(gn); 157 | this.score_objects.push(gn); 158 | } 159 | }; 160 | 161 | /** 162 | * add_note 163 | * adds a rest (see Graphical_note.js) to the array of rests 164 | * contained within the measure 165 | * args 166 | * rest_object 167 | * an object, created by Vexflow that is displayed in the score 168 | * measure_position 169 | * the starting position of the rest being added (in ticks) 170 | * returns 171 | * none 172 | */ 173 | live_score.Graphical_measure.prototype.add_rest = function(rest_object, 174 | measure_position){ 175 | 176 | var gn = new live_score.Graphical_note(); 177 | gn.extract_rest_info(rest_object,measure_position); 178 | this.rests.push(gn); 179 | this.score_objects.push(gn); 180 | }; 181 | 182 | /** 183 | * contains 184 | * checks if a set of coordinates is within the bounds of the measure 185 | * args 186 | * graphical_object 187 | * a Graphical_object (see graphical_object.js) containing coordinates 188 | * returns 189 | * a boolean of whether the area described by graphical_object overlaps with 190 | * the measure 191 | */ 192 | live_score.Graphical_measure.prototype.contains = function(graphical_object){ 193 | return this.bounds.intersects_area(graphical_object); 194 | }; 195 | 196 | /** 197 | * lookup_note 198 | * determines if the bounds of a graphical object overlap with the position 199 | * of a note in the score 200 | * args 201 | * graphical_object 202 | * a graphical_object containing the coordinates being checked against the 203 | * coordinates of the notes in the score 204 | * note_info 205 | * a struct, described in structs.js, with information about the note 206 | * returns 207 | * none 208 | */ 209 | live_score.Graphical_measure.prototype.get_note_info = function(graphical_object, 210 | note_info, is_new_note){ 211 | 212 | if(is_new_note){ 213 | //note_info.x_position = this.get_measure_position_x(graphical_object); 214 | note_info.tick_position = this.get_measure_position_x(graphical_object); 215 | }else{ 216 | var note_found = false; 217 | for(var i = 0; i < this.notes.length && !note_found; i++){ 218 | if(this.notes[i].contains(graphical_object)){ 219 | this.notes[i].get_note_info(note_info); 220 | note_found = true; 221 | } 222 | } 223 | note_info.note_found = note_found; 224 | } 225 | }; 226 | 227 | /** 228 | * get_measure_position_x 229 | * determines where within a measure a given position is located, this 230 | * is used to determine the final x position of the click in relation to 231 | * other notes in the score 232 | * args 233 | * graphical_object 234 | * coordinates of the new note 235 | * returns 236 | * fractional_x_position 237 | * the percentage of the way through the measure that the position starts 238 | */ 239 | live_score.Graphical_measure.prototype.get_measure_position_x = function( 240 | graphical_object){ 241 | 242 | /* 243 | var measure_length = this.bounds.end_x - this.bounds.start_x; 244 | var position_in_measure = graphical_object.start_x - this.bounds.start_x; 245 | var fractional_x_position = position_in_measure/measure_length; 246 | return fractional_x_position; 247 | */ 248 | var previous_note = this.get_closest_note_before_position(graphical_object); 249 | var next_note = this.get_closest_note_after_position(graphical_object); 250 | var tick_position = this.calculate_tick_position(graphical_object, 251 | previous_note,next_note); 252 | 253 | return tick_position; 254 | }; 255 | 256 | /** 257 | * get_closest_note_before_position 258 | * finds the closest score object (note or rest), that appears in the score 259 | * prior to the point described by the graphical_object 260 | * args 261 | * graphical_object 262 | * an object describing a point on the score (see graphical_object.js) 263 | * returns 264 | * the graphical_object of the score object immediately preceding the 265 | * graphical_object passed in 266 | */ 267 | live_score.Graphical_measure.prototype.get_closest_note_before_position = 268 | function(graphical_object){ 269 | var closest_note; 270 | for(var i = 0; i < this.score_objects.length; i++){ 271 | if(this.score_objects[i].before(graphical_object)){ 272 | closest_note = this.score_objects[i]; 273 | } 274 | } 275 | return closest_note; 276 | }; 277 | 278 | /** 279 | * get_closest_note_after_position 280 | * finds the closest score object (note or rest), that appears in the score 281 | * after the point described by the graphical_object 282 | * args 283 | * graphical_object 284 | * an object describing a point on the score (see graphical_object.js) 285 | * returns 286 | * the graphical_object of the score object immediately following the 287 | * graphical_object passed in 288 | */ 289 | live_score.Graphical_measure.prototype.get_closest_note_after_position = 290 | function(graphical_object){ 291 | var closest_note; 292 | var closest_note_found = false; 293 | for(var i = 0; i < this.score_objects.length && !closest_note_found; i++){ 294 | if(!this.score_objects[i].before(graphical_object)){ 295 | closest_note = this.score_objects[i]; 296 | closest_note_found = true; 297 | } 298 | } 299 | return closest_note; 300 | }; 301 | 302 | /** 303 | * calculate_tick_position 304 | * given a position and the notes directly preceding and following that 305 | * position, calculates the tick position in the measure of the position 306 | * args 307 | * graphical_object 308 | * an object describing a point on the score (see graphical_object.js) 309 | * previous_note 310 | * the score object that immediately preceeds graphical_object in the 311 | * measure 312 | * next_note 313 | * the score object that immediately follows graphical_object in the 314 | * measure 315 | * returns 316 | * new_note_tick_position 317 | * the position (in ticks) of the note being inserted into the measure 318 | */ 319 | live_score.Graphical_measure.prototype.calculate_tick_position = function( 320 | graphical_object,previous_note,next_note){ 321 | 322 | if(previous_note === undefined){ 323 | previous_note = {}; 324 | previous_note.bounds = {}; 325 | previous_note.bounds.start_x = this.bounds.start_x; 326 | previous_note.position = 0; 327 | } 328 | if(next_note === undefined){ 329 | next_note = {}; 330 | next_note.bounds = {}; 331 | next_note.bounds.start_x = this.bounds.end_x; 332 | next_note.position = this.num_ticks; 333 | } 334 | 335 | var prev_note_x_pos = previous_note.bounds.start_x; 336 | var next_note_x_pos = next_note.bounds.start_x; 337 | var distance_between_adjacent_notes = next_note_x_pos - prev_note_x_pos; 338 | var new_note_relative_x_pos = graphical_object.start_x - prev_note_x_pos; 339 | var new_note_relative_fractional_x_pos = new_note_relative_x_pos/ 340 | distance_between_adjacent_notes; 341 | var tick_distance_between_notes = next_note.position - 342 | previous_note.position; 343 | var new_note_tick_position = previous_note.position + 344 | (tick_distance_between_notes * new_note_relative_fractional_x_pos); 345 | 346 | return new_note_tick_position; 347 | }; 348 | 349 | module.exports = live_score.Graphical_measure; 350 | -------------------------------------------------------------------------------- /src/js/graphical_note.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | live_score.Graphical_object = require("./graphical_object.js"); 3 | 4 | /** 5 | * Graphical_note 6 | * stores positional and musical information about a note displayed in the 7 | * score 8 | * args 9 | * none 10 | * returns 11 | * none 12 | */ 13 | live_score.Graphical_note = function(){ 14 | 15 | /** 16 | * a Graphical_object (see graphical_object.js) containing the coordinate 17 | * boundaries of the note 18 | */ 19 | this.bounds = new live_score.Graphical_object(); 20 | 21 | /** 22 | * The number of ticks that have occurred in the measure before this note 23 | */ 24 | this.position = 0; 25 | 26 | /** 27 | * The pitch of the note, formatted as follows D/5, D being the note, and 5 28 | * being the octave 29 | */ 30 | this.pitch = ""; 31 | }; 32 | 33 | /** 34 | * extract_positional_info 35 | * named for the sake of continuity, sets the positional and musical values of 36 | * the note 37 | * args 38 | * note 39 | * a vexflow note object from which positional information is extracted 40 | * measure_position 41 | * the number of ticks that have occurred in the measure before this note 42 | * pitch 43 | * the musical pitch of the note 44 | * returns 45 | * none 46 | */ 47 | live_score.Graphical_note.prototype.extract_positional_info = function(note, 48 | measure_position,pitch){ 49 | 50 | this.extract_note_info(note); 51 | this.position = measure_position; 52 | this.pitch = pitch; 53 | }; 54 | 55 | /** 56 | * extract_note_info 57 | * extracts positional data from the vexflow note_head object 58 | * args 59 | * note_head 60 | * the vexflow object containing the graphical positioning information of 61 | * the note head of the note 62 | * returns 63 | * none 64 | */ 65 | live_score.Graphical_note.prototype.extract_note_info = function(note_head){ 66 | 67 | this.bounds.start_x = note_head.x; 68 | this.bounds.end_x = this.bounds.start_x + note_head.glyph.head_width; 69 | this.bounds.start_y = note_head.y - 70 | (note_head.stave.getSpacingBetweenLines()/2); 71 | this.bounds.end_y = this.bounds.start_y + note_head.stave. 72 | getSpacingBetweenLines(); 73 | }; 74 | 75 | /** 76 | * extract_rest_info 77 | * extracts positional data from the vexflow rest object 78 | * args 79 | * rest_object 80 | * the vexflow object containing the graphical positioning information of 81 | * a rest 82 | * returns 83 | * none 84 | */ 85 | live_score.Graphical_note.prototype.extract_rest_info = function(rest_object, 86 | measure_position){ 87 | 88 | this.position = measure_position; 89 | this.bounds.start_x = rest_object.stem.x_begin; 90 | this.bounds.end_x = rest_object.stem.x_end; 91 | }; 92 | 93 | /** 94 | * contains 95 | * checks if a set of coordinates is within the bounds of the note 96 | * args 97 | * graphical_object 98 | * a Graphical_object (see graphical_object.js) containing coordinates 99 | * returns 100 | * a boolean of whether the area described by graphical_object overlaps with 101 | * the note 102 | */ 103 | live_score.Graphical_note.prototype.contains = function(graphical_object){ 104 | return this.bounds.intersects_area(graphical_object); 105 | }; 106 | 107 | /** 108 | * contains 109 | * checks if a set of coordinates occurrs before this object in the score 110 | * args 111 | * graphical_object 112 | * a Graphical_object (see graphical_object.js) containing coordinates 113 | * returns 114 | * a boolean of whether the area described by graphical_object starts before 115 | * this object 116 | */ 117 | live_score.Graphical_note.prototype.before = function(graphical_object){ 118 | return this.bounds.before_area(graphical_object); 119 | }; 120 | 121 | /** 122 | * get_note_info 123 | * fills in a note_info struct (see structs.js) with information about this 124 | * note, used for determining whether a given note was clicked on 125 | * args 126 | * note_info 127 | * note_info struct (see structs.js) with information about this note 128 | * returns 129 | * none 130 | */ 131 | live_score.Graphical_note.prototype.get_note_info = function(note_info){ 132 | note_info.pitch = this.pitch; 133 | note_info.quantized_tick_position = this.position; 134 | }; 135 | 136 | module.exports = live_score.Graphical_note; 137 | -------------------------------------------------------------------------------- /src/js/graphical_object.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | 3 | /** 4 | * Graphical_object 5 | * Constructor for the Graphical_object object. This object is used to denote 6 | * the coordinates on the canvas that contain a particular object on the score. 7 | * args 8 | * start_x 9 | * the farthest left x position on the canvas of a given graphic 10 | * end_x 11 | * the farthest right x position on the canvas of a given graphic 12 | * start_y 13 | * the topmost y position on the canvas of a given graphic 14 | * end_y 15 | * the bottom-most y position on the canvas of a given graphic 16 | * returns 17 | * none 18 | */ 19 | live_score.Graphical_object = function(start_x,end_x,start_y,end_y){ 20 | this.start_x = start_x; 21 | this.end_x = end_x; 22 | this.start_y = start_y; 23 | this.end_y = end_y; 24 | }; 25 | 26 | /** 27 | * intersects_area 28 | * determines if another graphical_object intersects with this 29 | * graphical_object 30 | * args 31 | * graphical_object 32 | * the area being checked against the area of this graphical_object 33 | * returns 34 | * intersects 35 | * either true or false, whehter the two areas overlap 36 | */ 37 | live_score.Graphical_object.prototype.intersects_area = function( 38 | graphical_object){ 39 | 40 | var intersects = false; 41 | intersects = intersects || this.contains_point(graphical_object.start_x, 42 | graphical_object.start_y); 43 | intersects = intersects || this.contains_point(graphical_object.end_x, 44 | graphical_object.start_y); 45 | intersects = intersects || this.contains_point(graphical_object.start_x, 46 | graphical_object.end_y); 47 | intersects = intersects || this.contains_point(graphical_object.end_x, 48 | graphical_object.end_y); 49 | return intersects; 50 | }; 51 | 52 | /** 53 | * contains point 54 | * determines if this graphical_object contains a given x,y coordinate 55 | * args 56 | * x 57 | * the point's x coordinate 58 | * y 59 | * the point's y coordinate 60 | * returns 61 | * contains 62 | * either true or false, whehter this graphical_object contains the point 63 | */ 64 | live_score.Graphical_object.prototype.contains_point = function(x,y){ 65 | var contains = true; 66 | contains = contains && (this.start_x < x); 67 | contains = contains && (this.end_x > x); 68 | contains = contains && (this.start_y < y); 69 | contains = contains && (this.end_y > y); 70 | return contains; 71 | }; 72 | 73 | /** 74 | * before_area 75 | * determines if this graphical_object starts before a given area 76 | * args 77 | * graphical_object 78 | * the area being compared to this area 79 | * returns 80 | * a boolean denoting whether this object occurs before the object passed in 81 | */ 82 | live_score.Graphical_object.prototype.before_area = function(graphical_object){ 83 | return this.before_point(graphical_object.start_x); 84 | }; 85 | 86 | /** 87 | * before_point 88 | * determines if this graphical_object starts before a given area 89 | * args 90 | * x 91 | * the point's x coordinate 92 | * returns 93 | * before 94 | * either true or false, whehter this graphical_object is before the point 95 | */ 96 | live_score.Graphical_object.prototype.before_point = function(x){ 97 | var before = true; 98 | before = before && (this.start_x < x); 99 | return before; 100 | }; 101 | 102 | module.exports = live_score.Graphical_object; 103 | -------------------------------------------------------------------------------- /src/js/graphical_state.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | live_score.Graphical_stave = require("./graphical_stave.js"); 3 | live_score.structs = require("./structs.js"); 4 | 5 | /** 6 | * Graphical_state 7 | * Constructor for the Graphical_state object, which contains all the 8 | * positional information about the musical information in the score. 9 | * It is responsible for scraping positional information from Vexflow 10 | * and for figuring out which elements are being acted on by the user 11 | * through translating Ui events into a format which can be 12 | * understood by the Musical_state object 13 | * args 14 | * none 15 | * returns 16 | * none 17 | */ 18 | live_score.Graphical_state = function(){ 19 | 20 | /** 21 | * x,y information about the position of staves on the canvas, utilizes the 22 | * Graphical_object object 23 | */ 24 | this.staves = []; 25 | 26 | }; 27 | 28 | /** 29 | * clear_state 30 | * each time the score changes, the positional information changes as 31 | * well. The old information needs to be cleared each time the new 32 | * positional information is read 33 | * args 34 | * none 35 | * returns 36 | * none 37 | */ 38 | live_score.Graphical_state.prototype.clear_state = function(){ 39 | this.staves = []; 40 | }; 41 | 42 | /** 43 | * update 44 | * scrapes the information from the Vexflow score created by the renderer 45 | * updates the positional information of the staves, measures and notes. 46 | * args 47 | * renderer 48 | * returns 49 | * none 50 | */ 51 | live_score.Graphical_state.prototype.update = function(renderer){ 52 | 53 | this.clear_state(); 54 | 55 | var staves = renderer.get_staves(); 56 | var voice_list = renderer.get_voices(); 57 | 58 | for(var i = 0; i < voice_list.length; i++){ 59 | var stave_contents = []; 60 | var voice = voice_list[i]; 61 | for(var j = 0; j < voice.length; j++){ 62 | var voice_contents = voice[j].tickables; 63 | for(var k = 0; k < voice_contents.length; k++){ 64 | voice_contents[k].stave = staves[i]; 65 | stave_contents.push(voice_contents[k]); 66 | } 67 | } 68 | var graphical_stave = new live_score.Graphical_stave(); 69 | graphical_stave.extract_positional_info(staves[i], stave_contents); 70 | this.staves.push(graphical_stave); 71 | } 72 | }; 73 | 74 | /** 75 | * get_note_info 76 | * queries the graphical_state to either determine the position of click 77 | * within the score, or determine whether a note had been clicked on 78 | * args 79 | * graphical_object 80 | * a Graphical_object (See graphical_object.js) containing the coordinates 81 | * being looked up 82 | * is_new_note 83 | * a boolean denoting whether an existing note is being looked up, or a new 84 | * note is being added 85 | * returns 86 | * note_info 87 | * information about the note that was looked up 88 | */ 89 | live_score.Graphical_state.prototype.get_note_info = function(graphical_object, 90 | is_new_note){ 91 | 92 | var note_info = live_score.structs.create_note_info(); 93 | var stave_found = false; 94 | for(var i = 0; i < this.staves.length && !stave_found; i++){ 95 | if(this.staves[i].contains(graphical_object)){ 96 | note_info.stave_num = i; 97 | stave_found = true; 98 | this.staves[i].get_note_info(graphical_object,note_info,is_new_note); 99 | } 100 | } 101 | note_info.stave_found = stave_found; 102 | note_info.valid_input = note_info.stave_found && note_info.measure_found; 103 | 104 | return note_info; 105 | }; 106 | 107 | /** 108 | * get_new_note_position 109 | * determines the stave, measure, and x,y positioning of a new note 110 | * being inserted into the score 111 | * args 112 | * event_info 113 | * a struct, described in structs.js, containing information about the 114 | * ui event 115 | * returns 116 | * note_info 117 | * a struct, described in structs.js, containing information about the 118 | * where the new note will be inserted into the score 119 | */ 120 | live_score.Graphical_state.prototype.get_new_note_position = function( 121 | event_info){ 122 | 123 | var is_new_note = true; 124 | var note_info = this.get_note_info(event_info.graphical_object, is_new_note); 125 | note_info.note_length = event_info.note_length; 126 | note_info.quantization = event_info.quantization; 127 | 128 | return note_info; 129 | }; 130 | 131 | /** 132 | * get_note_position 133 | * determines if a position clicked in the score is a note 134 | * args 135 | * event_info 136 | * a struct (see structs.js) containing information about the 137 | * ui event 138 | * returns 139 | * note_info 140 | * a struct (see structs.js) containing information about whether the 141 | * position clicked contained a note 142 | */ 143 | live_score.Graphical_state.prototype.get_note_position = function( 144 | event_info){ 145 | 146 | var is_new_note = false; 147 | var note_info = this.get_note_info(event_info.graphical_object, is_new_note); 148 | return note_info; 149 | }; 150 | 151 | module.exports = live_score.Graphical_state; 152 | -------------------------------------------------------------------------------- /src/js/graphical_stave.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | live_score.Graphical_object = require("./graphical_object.js"); 3 | live_score.Graphical_measure = require("./graphical_measure.js"); 4 | 5 | live_score.Graphical_stave = function(){ 6 | 7 | /** 8 | * a Graphical_object (see graphical_object.js) containing the coordinate 9 | * boundaries of the stave 10 | */ 11 | this.bounds = new live_score.Graphical_object(); 12 | 13 | /** 14 | * the distance between the ledger lines in the stave 15 | */ 16 | this.space_between_notes = 0; 17 | 18 | /** 19 | * an array of Graphical_measure objects (see graphical_measure.js) contained 20 | * with in the bounds of the stave 21 | */ 22 | this.measures = []; 23 | }; 24 | 25 | /** 26 | * extract_positional_info 27 | * parses the score information into individual measures, and extracs the 28 | * stave's positional information from the score 29 | * args 30 | * stave 31 | * a vexflow stave object from which positional information is extracted 32 | * stave_contents 33 | * an array of vexflow objects contained within the stave 34 | * returns 35 | * none 36 | */ 37 | live_score.Graphical_stave.prototype.extract_positional_info = function(stave, 38 | stave_contents){ 39 | 40 | this.extract_stave_info(stave); 41 | var measure_contents = []; 42 | for(var i = 0; i < stave_contents.length; i++){ 43 | var score_object = stave_contents[i]; 44 | if(this.is_barline(score_object)){ 45 | this.add_measure(score_object,measure_contents); 46 | measure_contents = []; 47 | }else{ 48 | measure_contents.push(score_object); 49 | } 50 | } 51 | }; 52 | 53 | /** 54 | * extract_stave_info 55 | * extracts positional information from the Vexflow object and 56 | * uses that information to determine the positioning of a stave 57 | * args 58 | * stave_object 59 | * an object representing a stave, created by Vexflow, that is displayed 60 | * in the score 61 | * returns 62 | * none 63 | */ 64 | live_score.Graphical_stave.prototype.extract_stave_info = function( 65 | stave_object){ 66 | this.bounds.start_x = stave_object.bounds.x; 67 | this.bounds.end_x = stave_object.bounds.x + stave_object.bounds.w; 68 | this.bounds.start_y = stave_object.bounds.y; 69 | this.bounds.end_y = stave_object.bounds.y + stave_object.height; 70 | this.space_between_notes = this.calculate_space_between_notes( 71 | stave_object); 72 | }; 73 | 74 | /** 75 | * is_barline 76 | * checks if a given score_object is a barline, used to determine the 77 | * postions of measures 78 | * args 79 | * score_object 80 | * an object, created by Vexflow that is displayed in the score 81 | * returns 82 | * a boolean value denoting whether the score_object is a barline 83 | */ 84 | live_score.Graphical_stave.prototype.is_barline = function(score_object){ 85 | return (score_object.noteType && 86 | score_object.noteType === "n" && 87 | score_object.duration === "b"); 88 | }; 89 | 90 | /** 91 | * calculate_space_between_notes 92 | * calculates the space (in pixels) between two adjacent chromatic notes 93 | * in a stave 94 | * args 95 | * stave_object 96 | * an object representing a stave, created by Vexflow, that is displayed 97 | * in the score 98 | * returns 99 | * space_between_chromatic_notes 100 | * the space between two adjacent chromatic notes in the stave 101 | */ 102 | live_score.Graphical_stave.prototype.calculate_space_between_notes = 103 | function(stave_object){ 104 | 105 | var num_diatonic_notes = 8; 106 | var num_chromatic_notes = 12; 107 | var space_between_diatonic_notes = stave_object.getSpacingBetweenLines()/2; 108 | var space_between_chromatic_notes = Math.floor( 109 | (space_between_diatonic_notes * num_diatonic_notes)/num_chromatic_notes); 110 | return space_between_chromatic_notes; 111 | }; 112 | 113 | /** 114 | * add_measure 115 | * adds a measure to the array of measures contained with the stave. 116 | * args 117 | * barline_object 118 | * a vexflow object containing the end bounds of a measure 119 | * measure_contents 120 | * an array of vexflow objects contained within the measure 121 | * returns 122 | * none 123 | */ 124 | live_score.Graphical_stave.prototype.add_measure = function(barline_object, 125 | measure_contents){ 126 | 127 | var gm = new live_score.Graphical_measure(); 128 | var previous_gm = null; 129 | if(this.measures.length > 0){ 130 | previous_gm = this.measures[this.measures.length - 1].bounds; 131 | } 132 | gm.extract_positional_info(previous_gm,barline_object,measure_contents); 133 | this.measures.push(gm); 134 | }; 135 | 136 | /** 137 | * contains 138 | * checks if a set of coordinates is within the bounds of the stave 139 | * args 140 | * graphical_object 141 | * a Graphical_object (see graphical_object.js) containing coordinates 142 | * returns 143 | * a boolean of whether the area described by graphical_object overlaps with 144 | * the stave 145 | */ 146 | live_score.Graphical_stave.prototype.contains = function(graphical_object){ 147 | return this.bounds.intersects_area(graphical_object); 148 | }; 149 | 150 | /** 151 | * get_note_info 152 | * determines if the bounds of a graphical object overlap with the bounds of 153 | * a measure in the score 154 | * args 155 | * graphical_object 156 | * a graphical_object containing the coordinates being checked against the 157 | * coordinates of the measures in the score 158 | * note_info 159 | * struct, described in structs.js, that contains the results of the lookup 160 | * returns 161 | * none 162 | */ 163 | live_score.Graphical_stave.prototype.get_note_info = function(graphical_object, 164 | note_info,is_new_note){ 165 | 166 | if(is_new_note){ 167 | note_info.y_position = this.get_measure_position_y(graphical_object); 168 | } 169 | 170 | var measure_found = false; 171 | for(var i = 0; i < this.measures.length && !measure_found; i++){ 172 | if(this.measures[i].contains(graphical_object)){ 173 | note_info.measure_num = i; 174 | measure_found = true; 175 | this.measures[i].get_note_info(graphical_object,note_info,is_new_note); 176 | } 177 | } 178 | note_info.measure_found = measure_found; 179 | }; 180 | 181 | /** 182 | * get_note_distance 183 | * determines where within a stave a given position is located, this 184 | * is used to determine the final y position of the click in relation to 185 | * other notes in the score 186 | * args 187 | * graphical_object 188 | * coordinates of the note being checked 189 | * returns 190 | * note_distance_from_top 191 | * the distance, in chromatic notes, that a given position is from the 192 | * highest possible note in a stave 193 | */ 194 | live_score.Graphical_stave.prototype.get_measure_position_y = function( 195 | graphical_object){ 196 | var y_position_in_stave = graphical_object.start_y - this.bounds.start_y; 197 | var note_distance_from_top = y_position_in_stave / this.space_between_notes; 198 | note_distance_from_top = Math.round(note_distance_from_top); 199 | return note_distance_from_top; 200 | }; 201 | 202 | module.exports = live_score.Graphical_stave; 203 | -------------------------------------------------------------------------------- /src/js/live_score.js: -------------------------------------------------------------------------------- 1 | Vex = require("vexflow"); 2 | 3 | /** 4 | * The high-level namespace for this application 5 | */ 6 | live_score = {}; 7 | 8 | live_score.Score_editor = require("./score_editor.js"); 9 | module.exports = live_score; 10 | 11 | -------------------------------------------------------------------------------- /src/js/measure.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | live_score.structs = require("./structs.js"); 3 | live_score.Note = require("./note.js"); 4 | 5 | /** 6 | * Measure 7 | * The constructor for the Measure object. 8 | * args 9 | * measure_meta_data 10 | * the measure_meta_data, described in structs.js, for this measure 11 | * returns 12 | * none 13 | */ 14 | live_score.Measure = function(measure_meta_data){ 15 | 16 | /** 17 | * The key of this measure 18 | * TODO: make this user settable 19 | */ 20 | this.key = "C"; 21 | 22 | /** 23 | * the number of beats in the measure, can be thought of as the numerator of 24 | * the measure's time signature. 25 | */ 26 | this.num_beats = measure_meta_data.num_beats; 27 | 28 | /** 29 | * the length value of the beats in num_beats, can be thought of as the 30 | * denominator of the measure's time signature 31 | */ 32 | this.beat_value = measure_meta_data.beat_value; 33 | 34 | /** 35 | * an array of live_score.Note objects, representing the notes that are played 36 | * in this measure 37 | */ 38 | this.notes = []; 39 | 40 | /** 41 | * a tick is used to determine rhythmic positioning of notes in a measure. 42 | * this determines the number of ticks in this measure, based on the time 43 | * signature 44 | */ 45 | this.num_ticks = this.num_beats * live_score.note_length_to_ticks( 46 | this.beat_value); 47 | 48 | this.create_empty_measure(); 49 | }; 50 | 51 | /** 52 | * create_empty_measure 53 | * Creates a measure whose length is based on measure_meta_data information, 54 | * and the measure is filled with rests 55 | * args 56 | * none 57 | * returns 58 | * none 59 | */ 60 | live_score.Measure.prototype.create_empty_measure = function(){ 61 | 62 | var rests = this.fill_space_with_rests(0,this.num_ticks); 63 | for(var i = 0; i < rests.length; i++){ 64 | this.notes.push(rests[i]); 65 | } 66 | }; 67 | 68 | /** 69 | * fill_space_with_rests 70 | * given a length of time in ticks, this fills that space with rests in 71 | * such a way that makes musical sense 72 | * args 73 | * start_tick 74 | * the starting position of the space that needs to be filled 75 | * end_tick 76 | * the ending position of the space that needs to be filled 77 | * returns 78 | * rests 79 | * an array of rests that optimally fills the given space 80 | */ 81 | live_score.Measure.prototype.fill_space_with_rests = function(start_tick, 82 | end_tick){ 83 | var rests = []; 84 | var ticks_so_far = start_tick; 85 | var ticks_left = end_tick - start_tick; 86 | while(ticks_left > 0){ 87 | var beat_level = this.calculate_beat_level(ticks_so_far); 88 | var rest_length = this.optimal_length(beat_level, ticks_left); 89 | ticks_left = ticks_left - live_score.note_length_to_ticks(rest_length); 90 | ticks_so_far += live_score.note_length_to_ticks(rest_length); 91 | rests.push(new live_score.Note(live_score.rest_pitch,rest_length, 92 | live_score.rest_type)); 93 | } 94 | return rests; 95 | }; 96 | 97 | /** 98 | * calculate_beat_level 99 | * given a number of ticks, determines the beat on which the that tick is 100 | * this makes sure that highest value note that can appear on a given beat 101 | * is the same as that beat. e.g. a quarter note would not appear on the 102 | * third sixteenth note beat of a measure. 103 | * args 104 | * total_ticks 105 | * the position of the beat in the measure, in ticks 106 | * returns 107 | * beat_level 108 | * the largest note/rest that can appear at the location denoted by 109 | * total_ticks 110 | */ 111 | live_score.Measure.prototype.calculate_beat_level = function(total_ticks){ 112 | var beat_level = this.num_ticks; 113 | for(var note_name in live_score.note_lengths){ 114 | var note_length = live_score.note_lengths[note_name]; 115 | var ticks = live_score.note_length_to_ticks(note_length); 116 | if(total_ticks % ticks === 0 && 117 | note_length < beat_level && 118 | ticks <= this.num_ticks){ 119 | beat_level = note_length; 120 | } 121 | } 122 | return beat_level; 123 | }; 124 | 125 | /** 126 | * calculate_position_from_index 127 | * given a the index of an object measure's array, calculates the tick 128 | * position of that object 129 | * args 130 | * index 131 | * the index of the object in this.notes; 132 | * returns 133 | * position 134 | * the position, in ticks, of the object 135 | */ 136 | live_score.Measure.prototype.calculate_position_from_index = function(index){ 137 | var position = 0; 138 | for(var i = 0; i < index; i++){ 139 | position += live_score.note_length_to_ticks(this.notes[i].length); 140 | } 141 | return position; 142 | }; 143 | 144 | /** 145 | * optimal_length 146 | * given the largest possible length that can appear and the length of the 147 | * empty space, determines the largest note length that can be inserted 148 | * args 149 | * beat_position 150 | * the size of the largest possible rest that can be placed 151 | * num_ticks 152 | * the length of the space that needs to be filled 153 | * returns 154 | * best_fit_note 155 | * the largest note length that is both within the bounds of the 156 | * beat_position and whose length is not larger than num_ticks 157 | */ 158 | live_score.Measure.prototype.optimal_length = function(beat_position,num_ticks){ 159 | 160 | var min_tick_difference = this.num_ticks; 161 | var best_fit_note = null; 162 | for(var note_name in live_score.note_lengths){ 163 | var note_length = live_score.note_lengths[note_name]; 164 | var note_ticks = live_score.note_length_to_ticks(note_length); 165 | var tick_difference = num_ticks - note_ticks; 166 | if(note_length >= beat_position && 167 | tick_difference < min_tick_difference && 168 | tick_difference >= 0){ 169 | 170 | min_tick_difference = tick_difference; 171 | best_fit_note = note_length; 172 | } 173 | } 174 | return best_fit_note; 175 | }; 176 | 177 | /** 178 | * add_note 179 | * adds a note to the measure 180 | * args 181 | * note_info 182 | * a struct (see structs.js) that contiains information about the 183 | * note being inserted 184 | * returns 185 | * a boolean of whether or not the note was inserted successfully 186 | */ 187 | live_score.Measure.prototype.add_note = function(note_info){ 188 | note_info.quantized_tick_position = this.quantize_position( 189 | note_info.quantization,note_info.tick_position); 190 | 191 | note_info.beat_level = this.calculate_beat_level( 192 | note_info.quantized_tick_position); 193 | 194 | this.place_note_in_measure(note_info); 195 | return true; 196 | }; 197 | 198 | /** 199 | * remove_note 200 | * removes a note from the measure 201 | * args 202 | * note_info 203 | * a struct (see structs.js) that contiains information about the 204 | * note being removed 205 | * returns 206 | * a boolean of whether or not the note was removed successfully 207 | */ 208 | live_score.Measure.prototype.remove_note = function(note_info){ 209 | this.remove_note_from_measure(note_info); 210 | return true; 211 | }; 212 | 213 | /** 214 | * quantize_position 215 | * determines the quantized position, in ticks, of a note 216 | * args 217 | * quantization 218 | * the beat level to which the note is being quantized 219 | * position 220 | * the position of a note in the measure represented as a percentage 221 | * returns 222 | * quantized_ticks_position 223 | * the quantized position of the note, in ticks 224 | */ 225 | live_score.Measure.prototype.quantize_position = function(quantization, 226 | tick_position){ 227 | 228 | var quantized_beat_ticks = live_score.note_length_to_ticks(quantization); 229 | var num_quantized_beats = this.num_ticks/quantized_beat_ticks; 230 | //var position_in_ticks = position*this.num_ticks; 231 | var quantized_tick_position; 232 | var min_tick_difference; 233 | 234 | if(num_quantized_beats * quantized_beat_ticks < this.num_ticks){ 235 | quantized_tick_position = num_quantized_beats * quantized_beat_ticks; 236 | min_tick_difference = Math.abs(this.num_ticks - tick_position); 237 | }else{ 238 | quantized_tick_position = 0; 239 | min_tick_difference = this.num_ticks; 240 | } 241 | for(var i = 0; i < num_quantized_beats; i++){ 242 | var ticks_before_beat = i * quantized_beat_ticks; 243 | var tick_difference = Math.abs(ticks_before_beat - tick_position); 244 | if(tick_difference < min_tick_difference){ 245 | min_tick_difference = tick_difference; 246 | quantized_ticks_position = ticks_before_beat; 247 | } 248 | } 249 | return quantized_ticks_position; 250 | }; 251 | 252 | /** 253 | * place_note_in_measure 254 | * handles all the different cases of how a note can be inserted into 255 | * a measure 256 | * args 257 | * note_info 258 | * a struct, described in structs.js, with information about the note being 259 | * inserted 260 | * returns 261 | * none 262 | */ 263 | live_score.Measure.prototype.place_note_in_measure = function(note_info){ 264 | var note_position = note_info.quantized_tick_position; 265 | var current_position = 0; 266 | var note_added = false; 267 | for(var i = 0; i <= this.notes.length && !note_added; i++){ 268 | if(current_position === note_position && this.notes[i].is_note()){ 269 | this.notes[i].add_note(note_info); 270 | note_added = true; 271 | }else if(current_position > note_position && this.notes[i-1].is_note()){ 272 | this.shorten_previous_and_insert_note(note_position,current_position,i-1, 273 | note_info); 274 | note_added = true; 275 | }else if(current_position > note_position && this.notes[i-1].is_rest()){ 276 | this.split_rest_and_insert_note(note_position,current_position,i-1, 277 | note_info); 278 | note_added = true; 279 | }else if(current_position === note_position && this.notes[i].is_rest()){ 280 | this.remove_rest_and_insert_note(note_position,current_position,i, 281 | note_info); 282 | note_added = true; 283 | }else{ 284 | var note_length = this.notes[i].length; 285 | var tick_length = live_score.note_length_to_ticks(note_length); 286 | current_position += tick_length; 287 | } 288 | } 289 | }; 290 | 291 | /** 292 | * space_after_note 293 | * determines the amount of space a note can take up without overlapping with 294 | * the next note in the measure 295 | * args 296 | * note_position 297 | * the position of the note being inserted (in ticks) 298 | * current_position 299 | * the position of the position counter used in place_note_in_measure. 300 | * denotes the end point (in ticks) of the current note 301 | * returns 302 | * space_after_note 303 | * the amount of space (in ticks) after the starting position of the note 304 | */ 305 | live_score.Measure.prototype.space_after_note = function(note_position, 306 | current_position,current_index){ 307 | 308 | var note_found = false; 309 | var space_after_note = current_position - note_position; 310 | for(var i = current_index; i < this.notes.length && !note_found; i++){ 311 | if(!this.notes[i].is_note()){ 312 | space_after_note += live_score.note_length_to_ticks( 313 | this.notes[i].length); 314 | }else{ 315 | note_found = true; 316 | } 317 | } 318 | return space_after_note; 319 | }; 320 | 321 | /** 322 | * calculate_display_length 323 | * determines the size the note will be rendered as, this is based on the 324 | * length of the note, the position of the note in the measure, and how close 325 | * it is to other notes. 326 | * args 327 | * note_info 328 | * a struct (see structs.js) with information about the note being inserted 329 | * returns 330 | * display_length 331 | * the maximum length that the note can be without violating any constraints. 332 | * including: 333 | * the note's length being longer than the beat_level 334 | * the note not overlapping with the following note 335 | * the note not exceeding its length 336 | */ 337 | live_score.Measure.prototype.calculate_display_length = function(note_info){ 338 | 339 | var beat_level = note_info.beat_level; 340 | var length = note_info.note_length; 341 | var max_length = this.optimal_length(beat_level,note_info.max_length); 342 | var display_length = length; 343 | 344 | if(live_score.note_length_greater_than(length,max_length)){ 345 | display_length = max_length; 346 | } 347 | if(live_score.note_length_greater_than(max_length,beat_level)){ 348 | display_length = beat_level; 349 | } 350 | return display_length; 351 | }; 352 | 353 | /** 354 | * shorten_previous_and_insert_note 355 | * inserts a note into the measure in the case where the note being inserted 356 | * overlaps with a note already in the meausre. The previous note is shortened 357 | * to make room for the new note, the new note is inserted, and any extra 358 | * space is filled with rests 359 | * args 360 | * note_position 361 | * the position of the note being inserted (in ticks) 362 | * current_position 363 | * the position of the position counter used in place_note_in_measure. 364 | * denotes the end point (in ticks) of the current note 365 | * note_to_split_index 366 | * the index, in the notes array, of the note that is being split to make 367 | * room for the new note. 368 | * note_info 369 | * a struct (see structs.js) with information about the note being inserted 370 | * returns 371 | * none 372 | */ 373 | live_score.Measure.prototype.shorten_previous_and_insert_note = function( 374 | note_position,current_position,note_to_split_index,note_info){ 375 | 376 | var shortened_note_info = this.recalculate_note_info(note_position, 377 | current_position,note_to_split_index,note_info); 378 | 379 | note_info.max_length = this.space_after_note(note_position,current_position, 380 | note_to_split_index + 1); 381 | note_info.display_length = this.calculate_display_length(note_info); 382 | 383 | var shortened_note = this.remove_from_measure(note_to_split_index); 384 | 385 | this.insert_rests_after_note(current_position,note_to_split_index,note_info); 386 | this.insert_new_note(note_to_split_index,note_info); 387 | this.insert_rests_between_shortened_note_and_new_note(note_position, 388 | current_position,note_to_split_index,shortened_note,shortened_note_info); 389 | this.insert_shortened_note(shortened_note_info,shortened_note, 390 | note_to_split_index); 391 | }; 392 | 393 | /** 394 | * recalculate_note_info 395 | * recreates the note_info struct of a note that has already been inserted 396 | * in order to allow that note to be processed like a new note. 397 | * args 398 | * note_position 399 | * the position of the note being inserted (in ticks) 400 | * current_position 401 | * the position of the position counter used in place_note_in_measure. 402 | * denotes the end point (in ticks) of the current note 403 | * note_to_split_index 404 | * the index, in the notes array, of the note that is being split to make 405 | * room for the new note. 406 | * note_info 407 | * a struct (see structs.js) with information about the note being inserted 408 | * returns 409 | * old_note_info 410 | * a new note_info struct with information about a note that has already 411 | * been inserted into the measure 412 | */ 413 | live_score.Measure.prototype.recalculate_note_info = function(note_position, 414 | current_position,note_to_split_index,note_info){ 415 | 416 | var old_note_info = live_score.structs.create_note_info(); 417 | var old_note_length_in_ticks = live_score.note_length_to_ticks( 418 | this.notes[note_to_split_index].length); 419 | var start_of_old_note = current_position - old_note_length_in_ticks; 420 | 421 | old_note_info.beat_level = this.calculate_beat_level(start_of_old_note); 422 | old_note_info.max_length = note_position - start_of_old_note; 423 | old_note_info.display_length = this.calculate_display_length( 424 | old_note_info); 425 | 426 | return old_note_info; 427 | }; 428 | 429 | /** 430 | * insert_rests_between_shortened_note_and_new_note 431 | * finds the end of the shortened note, and the beginning of the new note 432 | * and fills the space remaining between them with rests. 433 | * args 434 | * note_position 435 | * the position of the note being inserted (in ticks) 436 | * current_position 437 | * the position of the position counter used in place_note_in_measure. 438 | * denotes the end point (in ticks) of the current note 439 | * note_to_split_index 440 | * the index, in the notes array, of the note that is being split to make 441 | * room for the new note. 442 | * shortened_note 443 | * the note whose length has been shortened to make room for the new note 444 | * shortened_note_info 445 | * a note_info struct (see structs.js) with information about the shortened 446 | * note 447 | * returns 448 | * none 449 | */ 450 | live_score.Measure.prototype.insert_rests_between_shortened_note_and_new_note = 451 | function(note_position,current_position,note_to_split_index,shortened_note, 452 | shortened_note_info){ 453 | 454 | var start_of_shortened_note = current_position - 455 | live_score.note_length_to_ticks(shortened_note.length); 456 | var length_of_shortened_note = live_score.note_length_to_ticks( 457 | shortened_note_info.display_length); 458 | var end_of_shortened_note = start_of_shortened_note + 459 | length_of_shortened_note; 460 | 461 | var length_of_new_note = current_position - note_position; 462 | var start_of_new_note = current_position - length_of_new_note; 463 | 464 | var start_position = end_of_shortened_note; 465 | var end_position = start_of_new_note; 466 | 467 | var rests = this.fill_space_with_rests(start_position,end_position); 468 | for(var i = rests.length - 1; i >= 0; i--){ 469 | this.notes.splice(note_to_split_index,0,rests[i]); 470 | } 471 | }; 472 | 473 | /** 474 | * insert_shortened_note 475 | * places the note the note that has been shortened to make room for the 476 | * new note back into the measure 477 | * args 478 | * shortened_note_info 479 | * a note_info struct (see structs.js) with information about the shortened 480 | * note 481 | * shortened_note 482 | * the note whose length has been shortened to make room for the new note 483 | * index 484 | * the position in the notes array where the note is to be inserted 485 | * returns 486 | * none 487 | */ 488 | live_score.Measure.prototype.insert_shortened_note = function( 489 | shortened_note_info,shortened_note,index){ 490 | shortened_note.length = shortened_note_info.display_length; 491 | this.notes.splice(index,0,shortened_note); 492 | }; 493 | 494 | /** 495 | * remove_rest_and_insert_note 496 | * handles the insert case where a note is placed where a rest currently is 497 | * args 498 | * current_position 499 | * the position of the rest that is being removed 500 | * note_to_split_index 501 | * the index of the rest that is being removed 502 | * note_info 503 | * a struct, described in structs.js, with information about the note being 504 | * inserted 505 | * returns 506 | * none 507 | */ 508 | live_score.Measure.prototype.remove_rest_and_insert_note = function( 509 | note_position,current_position,note_to_split_index,note_info){ 510 | 511 | note_info.max_length = this.space_after_note(note_position,current_position, 512 | note_to_split_index); 513 | note_info.display_length = this.calculate_display_length(note_info); 514 | 515 | var note_to_split = this.remove_from_measure(note_to_split_index); 516 | var end_of_rest = current_position + live_score.note_length_to_ticks( 517 | note_to_split.length); 518 | this.insert_rests_after_note(end_of_rest,note_to_split_index,note_info); 519 | this.insert_new_note(note_to_split_index,note_info); 520 | }; 521 | 522 | /** 523 | * split_rest_and_insert_note 524 | * handles the insert case where a note is placed in the middle of a rest 525 | * args 526 | * current_position 527 | * the end position of the rest being split 528 | * note_to_split_index 529 | * the index of the rest that is being split 530 | * note_info 531 | * a struct, described in structs.js, with information about the note being 532 | * inserted 533 | * returns 534 | * none 535 | */ 536 | live_score.Measure.prototype.split_rest_and_insert_note = function( 537 | note_position,current_position,note_to_split_index,note_info){ 538 | 539 | note_info.max_length = this.space_after_note(note_position,current_position, 540 | note_to_split_index + 1); 541 | note_info.display_length = this.calculate_display_length(note_info); 542 | 543 | var note_to_split = this.remove_from_measure(note_to_split_index); 544 | this.insert_rests_after_note(current_position,note_to_split_index,note_info); 545 | this.insert_new_note(note_to_split_index,note_info); 546 | this.insert_rests_before_note(current_position,note_to_split_index,note_to_split, 547 | note_info); 548 | }; 549 | 550 | /** 551 | * remove_from_measure 552 | * removes a note or rest at a given position 553 | * args 554 | * rest_index 555 | * the index of the note/rest that is being removed 556 | * returns 557 | * rest_to_remove 558 | * the note/rest that has been removed 559 | */ 560 | live_score.Measure.prototype.remove_from_measure = function(index){ 561 | var object_to_remove = this.notes[index]; 562 | this.notes.splice(index,1); 563 | return object_to_remove; 564 | }; 565 | 566 | /** 567 | * insert_rests_before_note 568 | * in the case where an inserted note splits a rest, determine how to fill 569 | * the empty space before the note 570 | * args 571 | * current_position 572 | * the end position of the rest being split 573 | * note_to_split_index 574 | * the index of the rest that is being split 575 | * note_to_split 576 | * the rest that is being split 577 | * note_info 578 | * a struct, described in structs.js, with information about the note being 579 | * inserted 580 | * returns 581 | * none 582 | */ 583 | live_score.Measure.prototype.insert_rests_before_note = function( 584 | current_position,note_to_split_index,note_to_split,note_info){ 585 | 586 | var start_position = current_position - live_score.note_length_to_ticks( 587 | note_to_split.length); 588 | var end_position = note_info.quantized_tick_position; 589 | 590 | var rests = this.fill_space_with_rests(start_position, 591 | end_position); 592 | for(var i = rests.length - 1; i >= 0; i--){ 593 | this.notes.splice(note_to_split_index,0,rests[i]); 594 | } 595 | }; 596 | 597 | /** 598 | * insert_rests_after_note 599 | * in the case where an inserted note splits a rest, determine how to fill 600 | * the empty space after the note 601 | * args 602 | * current_position 603 | * the end position of the rest being split 604 | * note_to_split_index 605 | * the index of the rest that is being split 606 | * note_info 607 | * a struct, described in structs.js, with information about the note being 608 | * inserted 609 | * returns 610 | * none 611 | */ 612 | live_score.Measure.prototype.insert_rests_after_note = function( 613 | current_position,note_to_split_index,note_info){ 614 | 615 | var start_position = note_info.quantized_tick_position + 616 | live_score.note_length_to_ticks(note_info.display_length); 617 | var end_position = current_position; 618 | 619 | var rests = this.fill_space_with_rests(start_position, 620 | end_position); 621 | for(var i = rests.length - 1; i >= 0; i--){ 622 | this.notes.splice(note_to_split_index,0,rests[i]); 623 | } 624 | }; 625 | 626 | /** 627 | * insert_new_note 628 | * insert a new note into the measure 629 | * args 630 | * note_to_split_index 631 | * the index where the note is being inserted 632 | * note_info 633 | * a struct, described in structs.js, with information about the note being 634 | * inserted 635 | * returns 636 | * none 637 | */ 638 | live_score.Measure.prototype.insert_new_note = function(note_to_split_index, 639 | note_info){ 640 | 641 | var pitch = live_score.translate_midi_number_to_pitch(note_info.pitch); 642 | var new_note = new live_score.Note(pitch, note_info.note_length, 643 | live_score.note_type, note_info.display_length); 644 | this.notes.splice(note_to_split_index,0,new_note); 645 | }; 646 | 647 | /** 648 | * remove_note_from_measure 649 | * removes a note from the measure 650 | * args 651 | * note_info 652 | * a struct (see structs.js) that contiains information about the 653 | * note being removed 654 | * returns 655 | * none 656 | */ 657 | live_score.Measure.prototype.remove_note_from_measure = function(note_info){ 658 | var note_position = note_info.quantized_tick_position; 659 | var current_position = 0; 660 | var note_removed = false; 661 | var empty_note; 662 | for(var i = 0; i <= this.notes.length && !note_removed; i++){ 663 | if(current_position === note_position && this.notes[i].is_note()){ 664 | empty_note = this.notes[i].remove_note(note_info); 665 | note_removed = true; 666 | }else{ 667 | var note_length = this.notes[i].length; 668 | var tick_length = live_score.note_length_to_ticks(note_length); 669 | current_position += tick_length; 670 | } 671 | } 672 | if(empty_note){ 673 | var note_index = i-1; 674 | this.notes[note_index].make_rest(); 675 | this.merge_rests(note_index); 676 | } 677 | }; 678 | 679 | /** 680 | * merge_rests 681 | * attempts to merge smaller rests together after a note has been removed 682 | * args 683 | * rest_index 684 | * the index of the rest that has replaced the removed note 685 | * returns 686 | * none 687 | */ 688 | live_score.Measure.prototype.merge_rests = function(rest_index){ 689 | 690 | var first_rest_index = this.find_start_of_rest_sequence(rest_index); 691 | var last_rest_index = this.find_end_of_rest_sequence(rest_index); 692 | var start_tick = this.calculate_position_from_index(first_rest_index); 693 | 694 | var end_tick = this.calculate_position_from_index(last_rest_index) + 695 | live_score.note_length_to_ticks(this.notes[last_rest_index].length); 696 | 697 | var rests = this.fill_space_with_rests(start_tick,end_tick); 698 | 699 | var i = 0; 700 | for(i = first_rest_index; i <= last_rest_index; i++){ 701 | this.notes.splice(first_rest_index,1); 702 | } 703 | for(i = rests.length - 1; i >= 0; i--){ 704 | this.notes.splice(first_rest_index,0,rests[i]); 705 | } 706 | }; 707 | 708 | /** 709 | * find_start_of_rest_sequence 710 | * find the first position of rests that can potentially be merged into a 711 | * single longer rest 712 | * args 713 | * rest_index 714 | * the index of the rest that has replaced the removed note 715 | * returns 716 | * first_rest_index 717 | * the first rest in the series of rests that can be merged 718 | */ 719 | live_score.Measure.prototype.find_start_of_rest_sequence = function( 720 | rest_index){ 721 | 722 | var note_found = false; 723 | var first_rest_index = rest_index; 724 | for(var i = rest_index; i >= 0 && !note_found; i--){ 725 | if(this.notes[i].is_rest()){ 726 | first_rest_index = i; 727 | } 728 | else if(this.notes[i].is_note()){ 729 | note_found = true; 730 | } 731 | } 732 | return first_rest_index; 733 | }; 734 | 735 | /** 736 | * find_end_of_rest_sequence 737 | * find the last position of rests that can potentially be merged into a 738 | * single longer rest 739 | * args 740 | * rest_index 741 | * the index of the rest that has replaced the removed note 742 | * returns 743 | * last_rest_index 744 | * the last rest in the series of rests that can be merged 745 | */ 746 | live_score.Measure.prototype.find_end_of_rest_sequence = function(rest_index){ 747 | 748 | var note_found = false; 749 | var last_rest_index = rest_index; 750 | for(var i = rest_index; i < this.notes.length && !note_found; i++){ 751 | if(this.notes[i].is_rest()){ 752 | last_rest_index = i; 753 | } 754 | else if(this.notes[i].is_note()){ 755 | note_found = true; 756 | } 757 | } 758 | return last_rest_index; 759 | }; 760 | 761 | /** 762 | * get_notes_array 763 | * gets an array of all the note objects in this measure, used for playing 764 | * midi 765 | * args 766 | * none 767 | * returns 768 | * notes 769 | * an array of all the note objects in this measure 770 | */ 771 | live_score.Measure.prototype.get_notes_array = function(){ 772 | var notes = []; 773 | for(var i = 0; i < this.notes.length; i++){ 774 | notes.push(this.notes[i].get_pitches()); 775 | } 776 | return notes; 777 | }; 778 | 779 | module.exports = live_score.Measure; 780 | -------------------------------------------------------------------------------- /src/js/midi_player.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | live_score.structs = require("./structs.js"); 3 | 4 | /** 5 | * Midi_player 6 | * renders the score contents as playable midi using MIDI.js 7 | * args 8 | * none 9 | * returns 10 | * none 11 | */ 12 | live_score.Midi_player = function(){ 13 | 14 | }; 15 | 16 | /** 17 | * play 18 | * renders the score contents as playable midi using MIDI.js 19 | * args 20 | * staves 21 | * array containing all the notes in the score 22 | * returns 23 | * none 24 | */ 25 | live_score.Midi_player.prototype.play = function(staves){ 26 | 27 | var tempo = 120; 28 | var note_list = this.create_note_list(tempo,staves); 29 | 30 | 31 | MIDI.loadPlugin({ 32 | soundfontUrl: "./soundfont/", 33 | instrument: "acoustic_grand_piano", 34 | onprogress: function(state, progress) { 35 | console.log(state, progress); 36 | }, 37 | onsuccess: function() { 38 | for(var i = 0; i < note_list.length; i++){ 39 | var note = note_list[i]; 40 | MIDI.setVolume(note.channel,note.velocity); 41 | MIDI.noteOn(note.channel,note.note_number,note.velocity, 42 | note.note_on); 43 | MIDI.noteOff(note.channel,note.note_number,note.note_off); 44 | } 45 | } 46 | }); 47 | }; 48 | 49 | /** 50 | * create_note_list 51 | * flattens the nested array of notes into a list 52 | * args 53 | * tempo 54 | * the tempo to play the notes at 55 | * staves 56 | * array containing all the notes in the score 57 | * returns 58 | * midi_notes 59 | * an array of all the midi formatted notes to be played 60 | */ 61 | live_score.Midi_player.prototype.create_note_list = function(tempo,staves){ 62 | 63 | var midi_notes = []; 64 | var timing_constant = this.calculate_timing_constant(tempo); 65 | 66 | for(var i = 0; i < staves.length; i++){ 67 | var time = 0.5; 68 | var measures = staves[i]; 69 | for(var j = 0; j < measures.length; j++){ 70 | var chords = measures[j]; 71 | for(var k = 0; k < chords.length; k++){ 72 | var notes = chords[k]; 73 | if(notes[0].type == live_score.note_type){ 74 | for(var l = 0; l < notes.length; l++){ 75 | var midi_note_info = this.populate_midi_note_info(notes[l],time, 76 | timing_constant); 77 | midi_notes.push(midi_note_info); 78 | } 79 | } 80 | time += this.increment_time(notes,timing_constant); 81 | } 82 | } 83 | } 84 | return midi_notes; 85 | }; 86 | 87 | 88 | /** 89 | * calculate_timing_constant 90 | * based on the tempo, and the max number of ticks in a 4/4 measure, creates 91 | * a constant that can be used to calculate the time length and delay of a 92 | * note based on its tick length and tick position 93 | * args 94 | * tempo 95 | * the bpm of the notes being played 96 | * returns 97 | * timing constant 98 | * a number used to convert ticks to milliseconds 99 | */ 100 | live_score.Midi_player.prototype.calculate_timing_constant = function(tempo){ 101 | 102 | var beats_per_second = 60/tempo; 103 | var time_per_4_4_measure = beats_per_second*4; 104 | var timing_constant = time_per_4_4_measure / live_score.RESOLUTION; 105 | return timing_constant; 106 | }; 107 | 108 | /** 109 | * populate_midi_note_info 110 | * based on the pitch and timing information, builds a midi_note_info object 111 | * (see structs.js) 112 | * args 113 | * note 114 | * an object containing information about the note (see note.js) 115 | * time 116 | * the amount of time that has occurred before the note is to be played 117 | * timing_constant 118 | * a constant to convert ticks to milliseconds 119 | * returns 120 | * midi_note_info 121 | * a struct (see structs.js) with information about how to play the midi 122 | * note 123 | */ 124 | live_score.Midi_player.prototype.populate_midi_note_info = function(note,time, 125 | timing_constant){ 126 | 127 | var midi_note_info = live_score.structs.create_midi_note_info(); 128 | midi_note_info.channel = 0; 129 | midi_note_info.velocity = 127; 130 | 131 | midi_note_info.note_number = live_score.translate_pitch_to_midi_number( 132 | note.pitch); 133 | midi_note_info.note_on = time; 134 | midi_note_info.note_off = time + (timing_constant * 135 | live_score.note_length_to_ticks(note.length)); 136 | 137 | return midi_note_info; 138 | }; 139 | 140 | /** 141 | * increment_time 142 | * increases the amount of time that has occurred so far in the score 143 | * args 144 | * notes 145 | * an array of notes that has been played at this time slice 146 | * timing_constant 147 | * a constant to convert ticks to milliseconds 148 | * returns 149 | * duration 150 | * the amount of time that has passed in the score, in milliseconds 151 | */ 152 | live_score.Midi_player.prototype.increment_time = function(notes,timing_constant){ 153 | var ticks = live_score.note_length_to_ticks(notes[0].display_length); 154 | var duration = ticks * timing_constant; 155 | return duration; 156 | }; 157 | 158 | module.exports = live_score.Midi_player; 159 | -------------------------------------------------------------------------------- /src/js/music_constants.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | Vex = require("vexflow"); 3 | 4 | /** 5 | * a conversion table between the note length names, and the values used 6 | * to represent those note lengths 7 | */ 8 | live_score.note_lengths = { 9 | "whole":1, 10 | "half":2, 11 | "quarter":4, 12 | "eighth":8, 13 | "sixteenth":16, 14 | "thirty_second":32 15 | }; 16 | 17 | /** 18 | * a lookup table for the highest allowed note for each clef type. This is used 19 | * to determine the y position of a note being inserted 20 | * 21 | */ 22 | live_score.highest_clef_pitch = { 23 | "treble":"g/6" 24 | }; 25 | 26 | /** 27 | * the pitch that all rests are displayed at 28 | */ 29 | live_score.rest_pitch = "d/5"; 30 | 31 | /** 32 | * in live_score.Note, this how a rest is denoted 33 | */ 34 | live_score.rest_type = "r"; 35 | 36 | /** 37 | * in live_score.Note, this how a note is denoted 38 | */ 39 | live_score.note_type = "n"; 40 | 41 | /** 42 | * the string denoting that the ui is in "insert mode" 43 | */ 44 | live_score.insert_mode = "insert"; 45 | 46 | /** 47 | * the string denoting that the ui is in "remove mode" 48 | */ 49 | live_score.remove_mode = "remove"; 50 | 51 | /** 52 | * translate_pitch_to_midi_number 53 | * given a pitch of the style "d/5" d being the note and 5 being the 54 | * octave number, returns the midi note number equivalent 55 | * args 56 | * pitch 57 | * a string of the style "d/5" d being the note and 5 being the 58 | * octave number. This is the format used by Vexflow 59 | * returns 60 | * midi_value 61 | * an integer representing the pitch value as midi value 62 | */ 63 | live_score.translate_pitch_to_midi_number = function(pitch){ 64 | var note = pitch.split("/")[0]; 65 | note = note.toUpperCase(); 66 | var octave = pitch.split("/")[1]; 67 | note = live_score.note_to_integer_table[note]; 68 | octave = parseInt(octave,10); 69 | var midi_value = note + ((octave + 1) * 12); 70 | return midi_value; 71 | }; 72 | 73 | /** 74 | * translate_midi_number_to_pitch 75 | * given a midi note value, converts it to a Vexflow style pitch string 76 | * args 77 | * midi_number 78 | * an integer representing a pitch value 79 | * returns 80 | * pitch 81 | * a string of the style "d/5" d being the note and 5 being the 82 | * octave number. This is the format used by Vexflow 83 | */ 84 | live_score.translate_midi_number_to_pitch = function(midi_number){ 85 | var note = midi_number%12; 86 | note = live_score.integer_to_note_table[note]; 87 | var octave = Math.floor(midi_number/12) - 1; 88 | octave = octave.toString(); 89 | var pitch = note +"/"+ octave; 90 | return pitch; 91 | }; 92 | 93 | /** 94 | * a table that converts an integer to its corresponding note name 95 | */ 96 | live_score.integer_to_note_table = { 97 | 0: "C", 98 | 1: "C#", 99 | 2: "D", 100 | 3: "D#", 101 | 4: "E", 102 | 5: "F", 103 | 6: "F#", 104 | 7: "G", 105 | 8: "G#", 106 | 9: "A", 107 | 10: "A#", 108 | 11: "B" 109 | }; 110 | 111 | /** 112 | * a table that converts a note name to its corresponding integer 113 | */ 114 | live_score.note_to_integer_table = { 115 | "CBB":10, 116 | "CB" :11, 117 | "C" :0, 118 | "C#" :1, 119 | "C##":2, 120 | "DBB":0, 121 | "DB" :1, 122 | "D" :2, 123 | "D#" :3, 124 | "D##":4, 125 | "EBB":2, 126 | "EB" :3, 127 | "E" :4, 128 | "E#" :5, 129 | "E##":6, 130 | "FBB":3, 131 | "FB" :4, 132 | "F" :5, 133 | "F#" :6, 134 | "F##":7, 135 | "GBB":5, 136 | "GB" :6, 137 | "G" :7, 138 | "G#" :8, 139 | "G##":9, 140 | "ABB":7, 141 | "AB" :8, 142 | "A" :9, 143 | "A#" :10, 144 | "A##":11, 145 | "BBB":9, 146 | "BB" :10, 147 | "B" :11, 148 | "B#" :0, 149 | "B##":1 150 | }; 151 | 152 | /** 153 | * a object that defines key signatures 154 | */ 155 | live_score.keys = {}; 156 | 157 | /** 158 | * defines the accidentals found in the key of C major 159 | */ 160 | live_score.keys.C = function(){ 161 | return { 162 | key:"C", 163 | sharps:[1,3,6,8,10], 164 | flats:[], 165 | double_sharps:[], 166 | double_flats:[], 167 | naturals:[] 168 | }; 169 | }; 170 | 171 | /** 172 | * a constant used by Vexflow to calculate rhythmic positioning 173 | * also used by live_score for rhythmic position 174 | * represents the number of ticks in a 4/4 measure 175 | */ 176 | live_score.RESOLUTION = Vex.Flow.RESOLUTION; 177 | 178 | /** 179 | * note_length_to_ticks 180 | * converts a note length into the equivalent number of ticks 181 | * args 182 | * note_length 183 | * the note length being converted 184 | * returns 185 | * an integer equal to the number of ticks of note_length 186 | */ 187 | live_score.note_length_to_ticks = function(note_length){ 188 | return Vex.Flow.RESOLUTION/note_length; 189 | }; 190 | 191 | /** 192 | * ticks_to_note_length 193 | * converts a number of ticks into the equivalent note_length 194 | * args 195 | * ticks 196 | * the number of ticks being converted 197 | * returns 198 | * a note length equivalent to the number of ticks 199 | */ 200 | live_score.ticks_to_note_length = function(ticks){ 201 | var derived_note_length = 0; 202 | for(var note_length_name in live_score.note_lengths){ 203 | var note_length = live_score.note_lengths[note_length_name]; 204 | if(Math.round(ticks*note_length) === live_score.RESOLUTION){ 205 | derived_note_length = note_length; 206 | } 207 | } 208 | return derived_note_length; 209 | }; 210 | 211 | /** 212 | * note_length_greater_than 213 | * compares to live_score.note_lengths to see if the first is larger than 214 | * the second 215 | * args 216 | * note_length_1 217 | * the first note length 218 | * note_length_2 219 | * the second note length 220 | * returns 221 | * a boolean denoting whether note_length_1 was longer than note_length_2 222 | * based on there rhythmic lengths 223 | */ 224 | live_score.note_length_greater_than = function(note_length_1,note_length_2){ 225 | return (note_length_1 < note_length_2); 226 | }; 227 | 228 | /** 229 | * interpret_accidental 230 | * based on the key signature and previous accidentals in the measure, 231 | * determines the appropriate accidental for a note 232 | * args 233 | * pitch 234 | * the pitch name of the note 235 | * key_signature 236 | * an struct (see structs.js) describing the current key 237 | * returns 238 | * accidental 239 | * a string with the accidental to be added to the note 240 | */ 241 | live_score.interpret_accidental = function(pitch,key_signature){ 242 | var midi_num = live_score.note_to_integer_table[pitch.toUpperCase()]; 243 | var chromatic_note = midi_num % 12; 244 | var accidental = ""; 245 | if(key_signature.sharps.indexOf(chromatic_note) != -1){ 246 | accidental = "#"; 247 | live_score.update_sharps(chromatic_note,key_signature); 248 | }else if(key_signature.flats.indexOf(chromatic_note) != -1){ 249 | accidental = "b"; 250 | live_score.update_flats(chromatic_note,key_signature); 251 | } else if(key_signature.double_sharps.indexOf(chromatic_note) != -1){ 252 | accidental = "##"; 253 | } else if(key_signature.double_flats.indexOf(chromatic_note) != -1){ 254 | accidental = "bb"; 255 | } else if(key_signature.naturals.indexOf(chromatic_note) != -1){ 256 | accidental = "n"; 257 | live_score.update_naturals(chromatic_note,key_signature); 258 | } 259 | 260 | return accidental; 261 | }; 262 | 263 | /** 264 | * update_sharps 265 | * updates the sharps that are used in the key signature for this measure 266 | * (a sharped note that follows another sharped note does not need a sharp 267 | * if the note is within the same measure and no other accidentals have 268 | * been used for that pitch) 269 | * args 270 | * chromatic_note 271 | * the note (0 - 11) that was given a sharp 272 | * key_signature 273 | * an struct (see structs.js) describing the current key 274 | * returns 275 | * none 276 | */ 277 | live_score.update_sharps = function(chromatic_note,key_signature){ 278 | live_score.remove_accidental(chromatic_note,key_signature.sharps); 279 | var natural = ((chromatic_note - 1) + 12) % 12; 280 | key_signature.naturals.push(natural); 281 | }; 282 | 283 | /** 284 | * update_flats 285 | * updates the flats that are used in the key signature for this measure 286 | * (a flatted note that follows another flatted note does not need a flat 287 | * if the note is within the same measure and no other accidentals have 288 | * been used for that pitch) 289 | * args 290 | * chromatic_note 291 | * the note (0 - 11) that was given a flat 292 | * key_signature 293 | * an struct (see structs.js) describing the current key 294 | * returns 295 | * none 296 | */ 297 | live_score.update_flats = function(chromatic_note,key_signature){ 298 | live_score.remove_accidental(chromatic_note,key_signature.flats); 299 | var natural = (chromatic_note + 1) % 12; 300 | key_signature.naturals.push(natural); 301 | }; 302 | 303 | /** 304 | * update_naturals 305 | * updates the naturals that are used in the key signature for this measure 306 | * (a note that follows an altered note must be given a natural if the note 307 | * is intended to note used the accidental used by the previous note of the 308 | * same pitch that was given an accidental) 309 | * args 310 | * chromatic_note 311 | * the note (0 - 11) that was given a natural 312 | * key_signature 313 | * an struct (see structs.js) describing the current key 314 | * returns 315 | * none 316 | */ 317 | live_score.update_naturals = function(chromatic_note,key_signature){ 318 | live_score.remove_accidental(chromatic_note,key_signature.naturals); 319 | var original_key_sig = live_score.keys[key_signature.key.toUpperCase()](); 320 | var sharp = (chromatic_note + 1) % 12; 321 | var flat = ((chromatic_note - 1) + 12) % 12; 322 | if(original_key_sig.sharps.indexOf(sharp) != -1){ 323 | key_signature.sharps.push(sharp); 324 | } 325 | if(original_key_sig.sharps.indexOf(flat) != -1){ 326 | key_signature.flats.push(flat); 327 | } 328 | }; 329 | 330 | /** 331 | * remove_accidental 332 | * removes an accidental from a key signature 333 | * args 334 | * chromatic_note 335 | * the note (0 - 11) that was given a flat 336 | * accidental_list 337 | * an array of a type of accidental used in the key 338 | * returns 339 | * accidental_removed 340 | * a boolean denoting whether the accidental was removed successfully 341 | */ 342 | live_score.remove_accidental = function(chromatic_note,accidental_list){ 343 | var accidental_removed = false; 344 | var index = accidental_list.indexOf(chromatic_note); 345 | if(index > -1){ 346 | accidental_list.splice(index,1); 347 | accidental_removed = true; 348 | } 349 | return accidental_removed; 350 | }; 351 | 352 | /** 353 | * set_note_length_lcm 354 | * find the lowest common multiple of all the note lengths 355 | * this can be used to have the number of ticks of each measure 356 | * always be divisible by the duration of each note 357 | * args 358 | * none 359 | * returns 360 | * none 361 | */ 362 | live_score.set_note_length_lcm = function(){ 363 | var note_lengths = []; 364 | for (var note_length in live_score.note_lengths) { 365 | note_lengths.push(live_score.note_lengths[note_length]); 366 | } 367 | live_score.note_length_lcm = live_score.lcm_of_array(note_lengths); 368 | }; 369 | 370 | /** 371 | * lcm_of_array 372 | * finds the lowest common multiple of an array of integers 373 | * args 374 | * a 375 | * an array if integers 376 | * returns 377 | * result 378 | * the lcm of the array of integers 379 | */ 380 | live_score.lcm_of_array = function(a){ 381 | var result = a[0]; 382 | for(var i = 1; i < a.length; i++){ 383 | result = live_score.lcm_of_pair(result, a[i]); 384 | } 385 | return result; 386 | }; 387 | 388 | /** 389 | * lcm_of_pair 390 | * finds the lowest common multiple of two numbers 391 | * args 392 | * b 393 | * an integer 394 | * c 395 | * an integer 396 | * returns 397 | * the lcm of b and c 398 | */ 399 | 400 | live_score.lcm_of_pair = function(b, c){ 401 | return b * (c / live_score.gcd_of_pair(b,c)); 402 | }; 403 | 404 | /** 405 | * gcd_of_pair 406 | * finds the greatest common divisor of two integers 407 | * args 408 | * b 409 | * an integer 410 | * c 411 | * an integer 412 | * returns 413 | * b 414 | * the greatest common divisor of b and c 415 | */ 416 | 417 | live_score.gcd_of_pair = function(b, c){ 418 | while(c > 0){ 419 | var temp = c; 420 | c = b % c; 421 | b = temp; 422 | } 423 | return b; 424 | }; 425 | 426 | -------------------------------------------------------------------------------- /src/js/musical_state.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | live_score.structs = require("./structs.js"); 3 | live_score.Stave = require("./stave.js"); 4 | 5 | /** 6 | * Musical_state 7 | * Constructor for the Musical_state object. This object is the high level 8 | * container for all the musical information represented in the score 9 | * args 10 | * none 11 | * returns 12 | * none 13 | */ 14 | live_score.Musical_state = function(){ 15 | 16 | /** 17 | * An array of live_score.Stave objects representing the staves present in the 18 | * score 19 | */ 20 | this.staves = []; 21 | }; 22 | 23 | /** 24 | * add_staves 25 | * adds additional staves to the score 26 | * args 27 | * stave_info 28 | * an object that contains information about the stave. See 29 | * live_score.add_staves for usage example 30 | * returns 31 | * none 32 | */ 33 | live_score.Musical_state.prototype.add_staves = function(num_staves, stave_info){ 34 | for(var i = 0; i < num_staves; i++){ 35 | var stave_info_copy = live_score.structs.shallow_copy(stave_info); 36 | this.staves.push(new live_score.Stave(stave_info_copy)); 37 | } 38 | }; 39 | 40 | /** 41 | * add_measures 42 | * adds additional measures to all staves in the score 43 | * args 44 | * measure_info 45 | * a struct containing the number of measures to add allong with 46 | * the measures' time signature information 47 | * returns 48 | * none 49 | */ 50 | live_score.Musical_state.prototype.add_measures = function(num_measures, 51 | measure_info){ 52 | for(var i = 0; i < this.staves.length; i++){ 53 | this.staves[i].add_measures(num_measures, measure_info); 54 | } 55 | }; 56 | 57 | /** 58 | * add_note 59 | * adds a note to the score 60 | * args 61 | * note_info 62 | * a struct, described in structs.js containing information about the 63 | * note being inserted 64 | * returns 65 | * this.staves 66 | * an array containing all the musical_state information 67 | */ 68 | live_score.Musical_state.prototype.add_note = function(note_info){ 69 | this.staves[note_info.stave_num].add_note(note_info); 70 | return this.staves; 71 | }; 72 | 73 | /** 74 | * remove_note 75 | * removes a note from a measure 76 | * args 77 | * note_info 78 | * a struct (see structs.js) that contiains information about the 79 | * note being removed 80 | * returns 81 | * a boolean of whether or not the note was removed successfully 82 | */ 83 | live_score.Musical_state.prototype.remove_note = function(note_info){ 84 | this.staves[note_info.stave_num].remove_note(note_info); 85 | return this.staves; 86 | }; 87 | 88 | /** 89 | * get_staves_array 90 | * returns an array of the notes that are in each stave 91 | * args 92 | * none 93 | * returns 94 | * staves 95 | * an array of the notes that are in each stave 96 | */ 97 | live_score.Musical_state.prototype.get_staves_array = function(){ 98 | var staves = []; 99 | for(var i = 0; i < this.staves.length; i++){ 100 | staves.push(this.staves[i].get_measures_array()); 101 | } 102 | return staves; 103 | }; 104 | 105 | /** 106 | * get_num_independent_notes 107 | * retrieves the number of musical objects that occur in a unique time 108 | * slice from the stave with the most uniquely placed object. This is used to 109 | * resize the score as the number of objects changes 110 | * args 111 | * none 112 | * returns 113 | * max_independent_notes 114 | * The maximum number of uniquely placed musical objects from any one stave 115 | */ 116 | live_score.Musical_state.prototype.get_num_independent_notes = function(){ 117 | var max_independent_notes = 0; 118 | var staves = this.get_staves_array(); 119 | for(var i = 0; i < staves.length; i++){ 120 | var num_independent_notes = 0; 121 | var measures = staves[i]; 122 | for(var j = 0; j < measures.length; j++){ 123 | var independent_notes = measures[j]; 124 | num_independent_notes += independent_notes.length; 125 | } 126 | if(num_independent_notes > max_independent_notes){ 127 | max_independent_notes = num_independent_notes; 128 | } 129 | } 130 | return max_independent_notes; 131 | }; 132 | 133 | live_score.Musical_state.prototype.get_num_measures = function(){ 134 | var staves = this.get_staves_array(); 135 | return staves[0].length; 136 | }; 137 | 138 | /** 139 | * get_pitch_from_note_position 140 | * find the pitch of a note based on its y position 141 | * args 142 | * note_info 143 | * a struct (see structs.js) with information about a musical note 144 | * returns 145 | * The midi number pitch of the note 146 | */ 147 | live_score.Musical_state.prototype.get_pitch_from_note_position = function( 148 | note_info){ 149 | var stave_num = note_info.stave_num; 150 | var y_position = note_info.y_position; 151 | return this.staves[stave_num].get_pitch_from_note_position(y_position); 152 | }; 153 | 154 | module.exports = live_score.Musical_state; 155 | -------------------------------------------------------------------------------- /src/js/note.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | 3 | /** 4 | * Note 5 | * Constructor for the Note object. Contains information about any number of 6 | * notes that are played at a specific time 7 | * args 8 | * pitches 9 | * an array of the pitches being played and their properties 10 | * length 11 | * the length that will be used to display all the notes in the score 12 | * type 13 | * denotes the type of note, either a rest or a musical note 14 | * display_length 15 | * only relevant for notes, not rests, determines the length of the note 16 | * that will be rendered. This is so notes do not overlap eachother in the 17 | * displayed score. A note's display length can differ from the length 18 | * saved in pitches[] 19 | * returns 20 | * none 21 | */ 22 | live_score.Note = function(pitch,length,type,display_length){ 23 | 24 | /** 25 | * (see function description) 26 | */ 27 | this.pitches = []; 28 | this.pitches.push({"pitch":pitch,"length":length,"type":type}); 29 | 30 | /** 31 | * (see function description) 32 | */ 33 | this.length = length; 34 | 35 | /** 36 | * (see function description) 37 | */ 38 | this.type = type; 39 | 40 | /** 41 | * (see function description) 42 | */ 43 | if(display_length && display_length !== 0){ 44 | this.length = display_length; 45 | } 46 | }; 47 | 48 | /* 49 | live_score.Note.prototype.adjust_display_length = function(space_to_fill){ 50 | var best_fit_length = 0; 51 | var least_space_remaining = space_to_fill; 52 | for(var i = 0; i < this.notes.length; i++){ 53 | var note_length = live_score.note_length_to_ticks(this.notes[i].length); 54 | var space_remaining = 55 | } 56 | }; 57 | */ 58 | 59 | /** 60 | * add_note 61 | * adds a note to the pitches array 62 | * args 63 | * note_info 64 | * a struct, described in structs.js, with information about the note being 65 | * inserted 66 | * returns 67 | * none 68 | */ 69 | live_score.Note.prototype.add_note = function(note_info){ 70 | var pitch = live_score.translate_midi_number_to_pitch(note_info.pitch); 71 | var length = note_info.note_length; 72 | var type = live_score.note_type; 73 | this.pitches.push({"pitch":pitch,"length":length,"type":type}); 74 | }; 75 | 76 | /** 77 | * remove_note 78 | * attemps to remove note from measure 79 | * args 80 | * note_info 81 | * a struct (see structs.js) that contiains information about the 82 | * note being removed 83 | * returns 84 | * is_empty 85 | * a boolean denoting whether there are any notes left at this position 86 | */ 87 | live_score.Note.prototype.remove_note = function(note_info){ 88 | var pitch = note_info.pitch; 89 | var is_empty = false; 90 | 91 | for(var i = 0; i < this.pitches.length; i++){ 92 | if(this.pitches[i].pitch === pitch){ 93 | this.pitches.splice(i,1); 94 | } 95 | } 96 | if(this.pitches.length === 0){ 97 | is_empty = true; 98 | } 99 | return is_empty; 100 | }; 101 | 102 | /** 103 | * is_note 104 | * checks if this Note object contains notes or rests 105 | * args 106 | * none 107 | * returns 108 | * a boolean that is true if there are notes in this object 109 | */ 110 | live_score.Note.prototype.is_note = function(){ 111 | return (this.type === live_score.note_type); 112 | }; 113 | 114 | /** 115 | * is_note 116 | * checks if this Note object contains notes or rests 117 | * args 118 | * none 119 | * returns 120 | * a boolean that is true if there is a rest in this object 121 | */ 122 | live_score.Note.prototype.is_rest = function(){ 123 | return (this.type === live_score.rest_type); 124 | }; 125 | 126 | /** 127 | * remove_note 128 | * makes the note render as a rest 129 | * args 130 | * none 131 | * returns 132 | * none 133 | */ 134 | live_score.Note.prototype.make_rest = function(){ 135 | var pitch = live_score.rest_pitch; 136 | this.type = live_score.rest_type; 137 | this.pitches.push({"pitch":pitch,"length":length,"type":this.type}); 138 | }; 139 | 140 | /** 141 | * get_pitches 142 | * creates an array of altered note structs to be used to play midi 143 | * args 144 | * none 145 | * returns 146 | * pitches 147 | * an array of the pitches in this object formatted for easy midi playback 148 | */ 149 | live_score.Note.prototype.get_pitches = function(){ 150 | var pitches = []; 151 | for(var i = 0; i < this.pitches.length; i++){ 152 | pitches.push({ 153 | "pitch":this.pitches[i].pitch, 154 | "length":this.pitches[i].length, 155 | "type":this.pitches[i].type, 156 | "display_length":this.length 157 | }); 158 | } 159 | return pitches; 160 | }; 161 | 162 | module.exports = live_score.Note; 163 | -------------------------------------------------------------------------------- /src/js/note_popup.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | 3 | /** 4 | * Note_popup 5 | * a div that displays the value of the pitch that would be added 6 | * if the used was to click on their current position in the score 7 | * args 8 | * none 9 | * returns 10 | * none 11 | */ 12 | live_score.Note_popup = function(){ 13 | 14 | /** 15 | * the html element 16 | */ 17 | this.popup_div = document.createElement("div"); 18 | this.set_style(); 19 | document.body.appendChild(this.popup_div); 20 | }; 21 | 22 | /** 23 | * set_style 24 | * set css of the of the div 25 | * args 26 | * none 27 | * returns 28 | * none 29 | */ 30 | live_score.Note_popup.prototype.set_style = function(){ 31 | this.popup_div.style.display = "none"; 32 | this.popup_div.style.position = "absolute"; 33 | this.popup_div.style.background = "#FAFAD2"; 34 | }; 35 | 36 | /** 37 | * update 38 | * update the text of the div based on the mouse position 39 | * args 40 | * event_info 41 | * a struct (see structs.js) with information about the ui event 42 | * note_info 43 | * a struct (see structs.js) with information about the note 44 | * returns 45 | * none 46 | */ 47 | live_score.Note_popup.prototype.update = function(event_info, note_info){ 48 | if(event_info.mouse_on_score && note_info.valid_input){ 49 | var pitch = live_score.translate_midi_number_to_pitch(note_info.pitch); 50 | this.popup_div.innerHTML = pitch; 51 | this.popup_div.style.left = event_info.absolute_x + 10; 52 | this.popup_div.style.top = event_info.absolute_y - 20; 53 | this.popup_div.style.display = ""; 54 | }else{ 55 | this.popup_div.style.display = "none"; 56 | } 57 | }; 58 | 59 | module.exports = live_score.Note_popup; 60 | -------------------------------------------------------------------------------- /src/js/playback_panel.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | 3 | /** 4 | * Playback_panel 5 | * ui element containing all the midi playback controls 6 | * args 7 | * event_controller 8 | * the object that handles all ui events 9 | * returns 10 | * none 11 | */ 12 | live_score.Playback_panel = function(event_controller){ 13 | 14 | /** 15 | * (see function description) 16 | */ 17 | this.event_controller = event_controller; 18 | 19 | /** 20 | * the html button for playing the notes in the score 21 | */ 22 | this.play_button = document.getElementById("play_button"); 23 | this.play_button.onclick = this.play(); 24 | 25 | }; 26 | 27 | /** 28 | * play 29 | * plays the notes in the score 30 | * args 31 | * none 32 | * returns 33 | * none 34 | */ 35 | live_score.Playback_panel.prototype.play = function(){ 36 | event_controller = this.event_controller; 37 | return function(){ 38 | event_controller.play(); 39 | }; 40 | }; 41 | 42 | module.exports = live_score.Playback_panel; 43 | -------------------------------------------------------------------------------- /src/js/renderer.js: -------------------------------------------------------------------------------- 1 | Vex = require("vexflow"); 2 | live_score = require("./live_score.js"); 3 | 4 | /** 5 | * live_score.Renderer 6 | * converts the information in live_score.Musical_state into Vexflow objects 7 | * allowing the information to be displayed as a score 8 | */ 9 | 10 | 11 | /** 12 | * Renderer 13 | * Constructor for the Renderer Object 14 | * args 15 | * score_panel 16 | * the html canvas element on which the rendered score is displayed 17 | * returns 18 | * none 19 | */ 20 | live_score.Renderer = function(score_panel){ 21 | 22 | /** 23 | * the html canvas element on which the score is rendered 24 | */ 25 | this.score_panel = score_panel; 26 | 27 | /** 28 | * vexflow renderer, used to get the vexflow canvas context 29 | */ 30 | this.vexflow_renderer = new Vex.Flow.Renderer(score_panel, 31 | Vex.Flow.Renderer.Backends.CANVAS); 32 | 33 | /** 34 | * vexflow renderer context, needed to render the score 35 | */ 36 | this.score_panel_context = this.vexflow_renderer.getContext(); 37 | 38 | /** 39 | * a list of the Vexflow staves created by the renderer 40 | */ 41 | this.vexflow_staves = []; 42 | 43 | /** 44 | * a list of lists. each index is a list of Vexflow voices belonging to the 45 | * corresponding Vexflow stave. 46 | */ 47 | this.vexflow_voices_list = []; 48 | 49 | /** 50 | * the current size of the score 51 | */ 52 | this.stave_size = 500; 53 | }; 54 | 55 | /** 56 | * clear_score 57 | * clears the contents of the canvas so it can be redrawn 58 | * args 59 | * none 60 | * returns 61 | * none 62 | */ 63 | live_score.Renderer.prototype.clear_score = function(){ 64 | this.score_panel_context.clearRect(0,0,this.score_panel.width, 65 | this.score_panel.height); 66 | }; 67 | 68 | /** 69 | * resize_score 70 | * changes the size of the score 71 | * args 72 | * stave_size 73 | * the new score size 74 | * returns 75 | * none 76 | */ 77 | live_score.Renderer.prototype.resize_score = function(stave_size){ 78 | this.stave_size = stave_size; 79 | }; 80 | 81 | /** 82 | * display_score 83 | * takes the rendered vexflow staves and draws them on the canvas 84 | * args 85 | * none 86 | * returns 87 | * none 88 | */ 89 | live_score.Renderer.prototype.display_score = function(){ 90 | 91 | this.clear_score(); 92 | 93 | for(var i = 0; i < this.vexflow_staves.length; i++){ 94 | var vexflow_stave = this.vexflow_staves[i]; 95 | var vexflow_voices = this.vexflow_voices_list[i]; 96 | 97 | vexflow_stave.draw(); 98 | 99 | var formatter = new Vex.Flow.Formatter().joinVoices(vexflow_voices). 100 | format(vexflow_voices, this.stave_size); 101 | 102 | for(var j = 0; j < vexflow_voices.length; j++){ 103 | vexflow_voices[j].draw(this.score_panel_context,vexflow_stave); 104 | } 105 | } 106 | }; 107 | 108 | /** 109 | * render_score 110 | * creates/renders the staves and voices of the score 111 | * args 112 | * staves 113 | * an array of live_score.Stave passed in from the Musical_state object 114 | * returns 115 | * none 116 | */ 117 | live_score.Renderer.prototype.render_score = function(staves){ 118 | 119 | this.vexflow_staves = []; 120 | this.vexflow_voices_list = []; 121 | 122 | for(var i = 0; i < staves.length; i++){ 123 | 124 | var vexflow_stave = new Vex.Flow.Stave(10,0,this.stave_size); 125 | vexflow_stave.addClef(staves[i].clef); 126 | 127 | var vexflow_voices = this.render_voices(staves[i].get_total_num_beats(), 128 | staves[i].voices); 129 | 130 | vexflow_stave.setContext(this.score_panel_context); 131 | 132 | this.vexflow_staves.push(vexflow_stave); 133 | this.vexflow_voices_list.push(vexflow_voices); 134 | } 135 | }; 136 | 137 | /** 138 | * render_voices 139 | * converts live_score voices into vexflow voices 140 | * args 141 | * total_num_beats 142 | * the total number of beats in the entire score for one voice, used to 143 | * create the vexflow voice 144 | * voices 145 | * an array of the live_score voices 146 | * returns 147 | * vexflow_voices 148 | * an array of formatted and aligned vexflow voices 149 | */ 150 | live_score.Renderer.prototype.render_voices = function(total_num_beats,voices){ 151 | 152 | var vexflow_voices = []; 153 | for(var i = 0; i < voices.length; i++){ 154 | var vexflow_voice = this.create_vexflow_voice(total_num_beats); 155 | var vexflow_notes = this.render_measures(voices[i].measures); 156 | vexflow_voice.addTickables(vexflow_notes); 157 | vexflow_voices.push(vexflow_voice); 158 | } 159 | return vexflow_voices; 160 | }; 161 | 162 | /** 163 | * render_measures 164 | * concats all notes for every measure into one array vexflow notes 165 | * args 166 | * measures 167 | * an array of live_score measures 168 | * returns 169 | * vexflow_notes 170 | * an array of all notes played in the score separated into measures by 171 | * barlines 172 | */ 173 | live_score.Renderer.prototype.render_measures = function(measures){ 174 | var vexflow_notes = []; 175 | for(var i = 0; i < measures.length; i++){ 176 | var key = measures[i].key.toUpperCase(); 177 | var key_signature = live_score.keys[key](); 178 | vexflow_notes = vexflow_notes.concat(this.render_notes(measures[i].notes, 179 | key_signature)); 180 | vexflow_notes.push(new Vex.Flow.BarNote()); 181 | } 182 | return vexflow_notes; 183 | }; 184 | 185 | /** 186 | * render_notes 187 | * converts each measure's notes into arrays of vexflow notes 188 | * args 189 | * notes 190 | * an array of live_score notes 191 | * key_signature 192 | * the key signature of the current measure 193 | * returns 194 | * vexflow_notes 195 | * an array of all the notes played in a measure 196 | */ 197 | live_score.Renderer.prototype.render_notes = function(notes,key_signature){ 198 | var vexflow_notes = []; 199 | for(var i = 0; i < notes.length; i++){ 200 | vexflow_notes.push(this.create_vexflow_note(notes[i],key_signature)); 201 | } 202 | return vexflow_notes; 203 | }; 204 | 205 | /** 206 | * create_vex_flow_voice 207 | * does the actual conversion between a live_score voice and a vexflow voice 208 | * args 209 | * total_num_beats 210 | * the total number of beats in the entire score 211 | * returns 212 | * new Vex.Flow.Voice 213 | * vexflow voice with properties parallel to the live_score voice 214 | */ 215 | live_score.Renderer.prototype.create_vexflow_voice = function(total_num_beats){ 216 | return new Vex.Flow.Voice({ 217 | num_beats: total_num_beats, 218 | beat_value: 4, 219 | resolution: Vex.Flow.RESOLUTION 220 | }); 221 | }; 222 | 223 | /** 224 | * create_vex_flow_note 225 | * does the actual conversion between a live_score note and a vexflow note 226 | * args 227 | * note 228 | * live_score note 229 | * returns 230 | * vexflow_note 231 | * vexflow note with properties parallel to the live_score note, with some 232 | * exceptions for notes that overlap notes. 233 | * key 234 | * the musical key of the measure containing the notes 235 | */ 236 | live_score.Renderer.prototype.create_vexflow_note = function(note,key){ 237 | var length = note.length.toString(); 238 | if(note.type === live_score.rest_type){ 239 | length += live_score.rest_type; 240 | } 241 | 242 | var pitches = []; 243 | for(var i = 0; i < note.pitches.length; i++){ 244 | pitches.push(note.pitches[i].pitch); 245 | } 246 | 247 | var vexflow_note = new Vex.Flow.StaveNote({keys:pitches,duration:length}); 248 | 249 | if(note.type === live_score.note_type){ 250 | this.add_accidentals(vexflow_note,key); 251 | } 252 | 253 | return vexflow_note; 254 | }; 255 | 256 | /** 257 | * add_accidentals 258 | * adds accidentals to notes before they are inserted into the score 259 | * args 260 | * vexflow_note 261 | * the vexflow note object 262 | * key 263 | * the key signature of the current measure 264 | * returns 265 | * none 266 | */ 267 | live_score.Renderer.prototype.add_accidentals = function(vexflow_note,key){ 268 | for(var i = 0; i < vexflow_note.keyProps.length; i++){ 269 | var pitch = vexflow_note.keyProps[i].key; 270 | var accidental = live_score.interpret_accidental(pitch,key); 271 | if(accidental !== ""){ 272 | vexflow_note.addAccidental(i,new Vex.Flow.Accidental(accidental)); 273 | } 274 | } 275 | }; 276 | 277 | /** 278 | * get_staves 279 | * returns an array of vexflow staves 280 | * args 281 | * none 282 | * returns 283 | * this.vexflow_staves 284 | */ 285 | live_score.Renderer.prototype.get_staves = function(){ 286 | return this.vexflow_staves; 287 | }; 288 | 289 | /** 290 | * get_voices 291 | * returns an array of arrays of Vexflow voices 292 | * args 293 | * none 294 | * returns 295 | * return this.vexflow_voices_list; 296 | */ 297 | live_score.Renderer.prototype.get_voices = function(){ 298 | return this.vexflow_voices_list; 299 | }; 300 | 301 | module.exports = live_score.Renderer; 302 | -------------------------------------------------------------------------------- /src/js/score_editor.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | live_score.structs = require("./structs.js"); 3 | live_score.Ui = require("./ui.js"); 4 | live_score.Graphical_state = require("./graphical_state.js"); 5 | live_score.Musical_state = require("./musical_state.js"); 6 | live_score.Renderer = require("./renderer.js"); 7 | live_score.Midi_player = require("./midi_player.js"); 8 | 9 | /** 10 | * Score_editor 11 | * Constructor for live_score.Score_editor. This object encapsulates all 12 | * live_score's functionality and UI 13 | * args 14 | * score_editor_div_id 15 | * the id of the div that will contain all live_score html 16 | * returns 17 | * none 18 | */ 19 | live_score.Score_editor = function(score_editor_div_id){ 20 | 21 | /** 22 | * catches all valid UI interactions and calls the appropriate Score_editor 23 | * function 24 | */ 25 | this.ui = new live_score.Ui(this,score_editor_div_id); 26 | 27 | /** 28 | * Contains all the positional information about musical notation displayed, 29 | * used to interpret the UI actions involving clicks on the score. 30 | */ 31 | this.gs = new live_score.Graphical_state(); 32 | 33 | /** 34 | * Contains all the musical information about the score 35 | */ 36 | this.ms = new live_score.Musical_state(); 37 | 38 | /** 39 | * Converts the musical representation of the score into a vexflow score 40 | */ 41 | this.renderer = new live_score.Renderer(this.ui.get_score_panel()); 42 | 43 | /** 44 | * Allows for playback of the notes rendered in the score 45 | */ 46 | this.midi_player = new live_score.Midi_player(); 47 | 48 | this.create_empty_score(); 49 | }; 50 | 51 | /** 52 | * create_empty_score 53 | * creates one stave with four empty measures 54 | * args 55 | * none 56 | * returns 57 | * none 58 | */ 59 | live_score.Score_editor.prototype.create_empty_score = function(){ 60 | 61 | var num_staves = 1; 62 | var stave_info = live_score.structs.create_stave_info(); 63 | stave_info.clef = "treble"; 64 | 65 | this.ms.add_staves(num_staves,stave_info); 66 | 67 | var num_measures = 8; 68 | var measure_info = live_score.structs.create_measure_info(); 69 | measure_info.num_beats = 4; 70 | measure_info.beat_value = 4; 71 | this.ms.add_measures(num_measures, measure_info); 72 | 73 | this.renderer.render_score(this.ms.staves); 74 | 75 | this.renderer.display_score(); 76 | 77 | this.gs.update(this.renderer); 78 | }; 79 | 80 | /** 81 | * add_staves 82 | * adds staves to the score 83 | * args 84 | * event_info 85 | * information about the ui event, will be interpreted by Graphical_state 86 | * returns 87 | * none 88 | */ 89 | live_score.Score_editor.prototype.add_staves = function(event_info){ 90 | var stave_info = live_score.structs.create_stave_info(); 91 | stave_info.clef = "treble"; 92 | this.ms.add_staves(stave_info); 93 | }; 94 | 95 | /** 96 | * add_measures 97 | * adds measures to the score 98 | * args 99 | * event_info 100 | * information about the ui event, will be interpreted by Graphical_state 101 | * returns 102 | * none 103 | */ 104 | live_score.Score_editor.prototype.add_measures = function(event_info){ 105 | var num_measures = 4; 106 | var measure_info = live_score.structs.create_measure_info(); 107 | measure_info.num_beats = 4; 108 | measure_info.beat_value = 4; 109 | this.ms.add_measures(num_measures, measure_info); 110 | }; 111 | 112 | /** 113 | * add_note 114 | * adds notes to the score 115 | * args 116 | * event_info 117 | * information about the ui event, will be interpreted by Graphical_state 118 | * returns 119 | * none 120 | */ 121 | live_score.Score_editor.prototype.add_note = function(event_info){ 122 | 123 | var note_info = this.gs.get_new_note_position(event_info); 124 | if(note_info.valid_input){ 125 | var staves = this.ms.add_note(note_info); 126 | this.resize_score(); 127 | this.renderer.render_score(staves); 128 | this.renderer.display_score(); 129 | this.gs.update(this.renderer); 130 | } 131 | }; 132 | 133 | /** 134 | * remove_note 135 | * removes notes from the score 136 | * args 137 | * event_info 138 | * information about the ui event, will be interpreted by Graphical_state 139 | * returns 140 | * none 141 | */ 142 | live_score.Score_editor.prototype.remove_note = function(event_info){ 143 | 144 | var note_info = this.gs.get_note_position(event_info); 145 | if(note_info.note_found){ 146 | var staves = this.ms.remove_note(note_info); 147 | this.resize_score(); 148 | this.renderer.render_score(staves); 149 | this.renderer.display_score(); 150 | this.gs.update(this.renderer); 151 | } 152 | }; 153 | 154 | /** 155 | * update_note_popup 156 | * update the information displayed in the note popup div based on used action 157 | * args 158 | * event_info 159 | * a struct (see structs.js) containing information about a ui event 160 | * returns 161 | * none 162 | */ 163 | live_score.Score_editor.prototype.update_note_popup = function(event_info){ 164 | 165 | var note_info; 166 | if(event_info.mouse_on_score){ 167 | note_info = this.gs.get_new_note_position(event_info); 168 | note_info.pitch = this.ms.get_pitch_from_note_position(note_info); 169 | } 170 | this.ui.update_note_popup(event_info,note_info); 171 | }; 172 | 173 | /** 174 | * play 175 | * play the contents of the score as midi 176 | * args 177 | * event_info 178 | * a struct (see structs.js) containing information about a ui event 179 | * returns 180 | * none 181 | */ 182 | live_score.Score_editor.prototype.play = function(event_info){ 183 | var staves = this.ms.get_staves_array(); 184 | this.midi_player.play(staves); 185 | }; 186 | 187 | /** 188 | * resize_score 189 | * change the size of the score based on the number of notes in the score 190 | * args 191 | * none 192 | * returns 193 | * none 194 | */ 195 | live_score.Score_editor.prototype.resize_score = function(){ 196 | var spacing_constant = this.ms.get_num_independent_notes(); 197 | spacing_constant += this.ms.get_num_measures(); 198 | var score_size = this.ui.resize_score_panel(spacing_constant); 199 | this.renderer.resize_score(score_size); 200 | }; 201 | 202 | module.exports = live_score.Score_editor; 203 | -------------------------------------------------------------------------------- /src/js/score_panel.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | live_score.structs = require("./structs.js"); 3 | 4 | /** 5 | * Score_panel 6 | * Constructor for the Score_panel Ui object 7 | * args 8 | * none 9 | * returns 10 | * none 11 | */ 12 | live_score.Score_panel = function(event_controller, ui_info){ 13 | this.default_width = 530; 14 | 15 | this.default_height = 100; 16 | 17 | /** 18 | * object that handles all ui events 19 | */ 20 | this.event_controller = event_controller; 21 | 22 | /** 23 | * shared object used to track the current ui state 24 | */ 25 | this.ui_info = ui_info; 26 | 27 | this.create_score_canvas(); 28 | this.attach_event_listeners(); 29 | }; 30 | 31 | /** 32 | * create_score_canvas 33 | * creates the score panel, the ui element in which the score is drawn 34 | * args 35 | * none 36 | * returns 37 | * none 38 | */ 39 | live_score.Score_panel.prototype.create_score_canvas = function(){ 40 | this.score_canvas = document.getElementById('score_panel'); 41 | this.score_canvas.width = this.default_width; 42 | this.score_canvas.height = this.default_height; 43 | }; 44 | 45 | /** 46 | * attach_score_panel 47 | * attaches score_panel canvas to html element 48 | * args 49 | * parent 50 | * the html element that the canvas is being attached to 51 | * returns 52 | * none 53 | */ 54 | live_score.Score_panel.prototype.attach_score_panel = function(parent_element){ 55 | parent_element.appendChild(this.score_canvas); 56 | }; 57 | 58 | /** 59 | * attach_event_listeners 60 | * attaches event listeners to the score_panel canvas 61 | * args 62 | * none 63 | * returns 64 | * none 65 | */ 66 | live_score.Score_panel.prototype.attach_event_listeners = function(){ 67 | this.score_canvas.addEventListener('mousedown',this.get_click_position(), 68 | false); 69 | this.score_canvas.addEventListener('mousemove',this.update_note_popup(), 70 | false); 71 | this.score_canvas.addEventListener("mouseout",this.hide_note_popup(),false); 72 | }; 73 | 74 | /** 75 | * get_click_position 76 | * attaches an event listener that captures the position of a mouse click 77 | * on the canvas 78 | * args 79 | * none 80 | * returns 81 | * a function that processes mouse clicks on the canvas 82 | */ 83 | live_score.Score_panel.prototype.get_click_position = function(){ 84 | 85 | var score_canvas = this.score_canvas; 86 | var event_controller = this.event_controller; 87 | var ui_info = this.ui_info; 88 | 89 | return function(e){ 90 | var event_info = live_score.structs.create_event_info(); 91 | event_info.graphical_object.start_x = e.clientX - score_canvas.offsetLeft; 92 | event_info.graphical_object.end_x = e.clientX - score_canvas.offsetLeft; 93 | event_info.graphical_object.start_y = e.clientY - score_canvas.offsetTop; 94 | event_info.graphical_object.end_y = e.clientY - score_canvas.offsetTop; 95 | 96 | event_info.note_length = ui_info.note_length; 97 | event_info.quantization = ui_info.quantization; 98 | 99 | if(ui_info.input_mode === live_score.insert_mode){ 100 | event_controller.add_note(event_info); 101 | } 102 | else if(ui_info.input_mode === live_score.remove_mode){ 103 | event_controller.remove_note(event_info); 104 | } 105 | }; 106 | }; 107 | 108 | /** 109 | * update_note_popup 110 | * updates the note popup based on mouse movement 111 | * args 112 | * none 113 | * returns 114 | * none 115 | */ 116 | live_score.Score_panel.prototype.update_note_popup = function(){ 117 | var score_canvas = this.score_canvas; 118 | var event_controller = this.event_controller; 119 | var ui_info = this.ui_info; 120 | 121 | return function(e){ 122 | var event_info = live_score.structs.create_event_info(); 123 | event_info.graphical_object.start_x = e.clientX - score_canvas.offsetLeft; 124 | event_info.graphical_object.end_x = e.clientX - score_canvas.offsetLeft; 125 | event_info.graphical_object.start_y = e.clientY - score_canvas.offsetTop; 126 | event_info.graphical_object.end_y = e.clientY - score_canvas.offsetTop; 127 | event_info.absolute_x = e.clientX; 128 | event_info.absolute_y = e.clientY; 129 | event_info.mouse_on_score = true; 130 | if(ui_info.input_mode === live_score.insert_mode){ 131 | event_controller.update_note_popup(event_info); 132 | } 133 | }; 134 | }; 135 | 136 | /** 137 | * hide_note_popup 138 | * hides the note popup based on mouse movement 139 | * args 140 | * none 141 | * returns 142 | * none 143 | */ 144 | live_score.Score_panel.prototype.hide_note_popup = function(){ 145 | var event_info = live_score.structs.create_event_info(); 146 | var event_controller = this.event_controller; 147 | var ui_info = this.ui_info; 148 | 149 | return function(e){ 150 | event_info.mouse_on_score = false; 151 | if(ui_info.input_mode === live_score.insert_mode){ 152 | event_controller.update_note_popup(event_info); 153 | } 154 | }; 155 | }; 156 | 157 | /** 158 | * resize_score_panel 159 | * changes the size of the score panel based on the number of notes in the 160 | * score 161 | * args 162 | * spacing constant 163 | * a constant based on the number of notes in the score 164 | * returns 165 | * the new width of the score 166 | */ 167 | live_score.Score_panel.prototype.resize_score_panel = function( 168 | spacing_constant){ 169 | var new_score_width = (spacing_constant/10) * 500; 170 | var stave_width; 171 | if(new_score_width > this.default_width){ 172 | this.score_canvas.width = new_score_width; 173 | }else{ 174 | this.score_canvas.width = this.default_width; 175 | } 176 | stave_width = this.score_canvas.width - 30; 177 | return stave_width; 178 | }; 179 | 180 | module.exports = live_score.Score_panel; 181 | -------------------------------------------------------------------------------- /src/js/stave.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | live_score.structs = require("./structs.js"); 3 | live_score.Voice = require("./voice.js"); 4 | 5 | /** 6 | * Stave 7 | * Constructor 8 | * args 9 | * stave_info 10 | * configuration options for creating a stave, currently just the clef type 11 | * returns 12 | * none 13 | */ 14 | live_score.Stave = function(stave_info){ 15 | 16 | /** 17 | * the type of clef that will be used by the stave 18 | */ 19 | this.clef = stave_info.clef; 20 | 21 | /** 22 | * an array of the voices contained in the stave 23 | */ 24 | this.voices = []; 25 | 26 | /** 27 | * a list of each measure's time signature information ordered chronologically, 28 | * this is maintained at 'the stave level, so that all voices reference the 29 | * same time signature information 30 | */ 31 | this.measure_meta_data = []; 32 | }; 33 | 34 | /** 35 | * add_measures 36 | * adds measures to a stave, adds measures to every voice in the stave 37 | * args 38 | * num_measures 39 | * the number of measures being added 40 | * measure_info 41 | * time signature information about the measures being added 42 | * returns 43 | * none 44 | */ 45 | live_score.Stave.prototype.add_measures = function(num_measures, measure_info){ 46 | 47 | this.add_measure_meta_data(num_measures, measure_info); 48 | 49 | if(this.voices.length === 0){ 50 | var new_voice = new live_score.Voice(this.measure_meta_data); 51 | this.voices.push(new_voice); 52 | } 53 | else{ 54 | for(var i = 0; i < voices.lengths; i++){ 55 | this.voices[i].add_measures(num_measures, measure_info); 56 | } 57 | } 58 | 59 | }; 60 | 61 | /** 62 | * add_measures 63 | * adds_measures to the score 64 | * args 65 | * event_info 66 | * information about the ui event, will be interpreted by Graphical_state 67 | * returns 68 | * none 69 | */ 70 | live_score.Stave.prototype.add_note = function(note_info){ 71 | note_info.pitch = this.get_pitch_from_note_position(note_info.y_position); 72 | var note_added = false; 73 | for(var i = 0; i < this.voices.length && !note_added; i++){ 74 | note_added = this.voices[i].add_note(note_info); 75 | } 76 | if(!note_added){ 77 | var new_voice = new live_score.Voice(this.measure_meta_data); 78 | new_voice.add_note(note_info); 79 | this.voices.push(new_voice); 80 | } 81 | }; 82 | 83 | /** 84 | * remove_note 85 | * removes a note from a measure 86 | * args 87 | * note_info 88 | * a struct (see structs.js) that contiains information about the 89 | * note being removed 90 | * returns 91 | * none 92 | */ 93 | live_score.Stave.prototype.remove_note = function(note_info){ 94 | var note_removed = false; 95 | for(var i = 0; i < this.voices.length && !note_removed; i++){ 96 | note_removed = this.voices[i].remove_note(note_info); 97 | } 98 | }; 99 | 100 | /** 101 | * add_measure_meta_data 102 | * adds time signature information about new measures to the measure meta data 103 | * args 104 | * num_measures 105 | * the number of measures being added 106 | * measure_info 107 | * time signature information for the measures being added 108 | * returns 109 | * none 110 | */ 111 | live_score.Stave.prototype.add_measure_meta_data = function(num_measures, measure_info){ 112 | for(var i = 0; i < num_measures; i++){ 113 | var measure_info_copy = live_score.structs.shallow_copy(measure_info); 114 | this.measure_meta_data.push(measure_info_copy); 115 | } 116 | }; 117 | 118 | /** 119 | * get_total_num_beats 120 | * calculates the total number of beats (in terms of whole quarter note beats, 121 | * rounded up) for the entire stave 122 | * args 123 | * none 124 | * returns 125 | * total_num_beats 126 | * the total number of quarter note beats in the stave 127 | */ 128 | live_score.Stave.prototype.get_total_num_beats = function(){ 129 | 130 | var total_beats = 0; 131 | for(var i = 0; i < this.measure_meta_data.length; i++){ 132 | var num_beats = this.measure_meta_data[i].num_beats; 133 | var beat_value = this.measure_meta_data[i].beat_value; 134 | 135 | total_beats += (num_beats * (live_score.note_lengths.quarter/beat_value)); 136 | } 137 | total_beats = Math.ceil(total_beats); 138 | 139 | return total_beats; 140 | }; 141 | 142 | /** 143 | * get_pitch_from_note_position 144 | * calculates the midi value of a y position based on its distance from 145 | * the highest possible note allows in the stave 146 | * args 147 | * y_position 148 | * the distance, in chromatic notes, from the highest note in the stave 149 | * returns 150 | * new_pitch 151 | * the midi value of the note 152 | */ 153 | live_score.Stave.prototype.get_pitch_from_note_position = function(y_position){ 154 | var highest_pitch = live_score.highest_clef_pitch[this.clef]; 155 | highest_pitch = live_score.translate_pitch_to_midi_number(highest_pitch); 156 | var new_pitch = highest_pitch - y_position; 157 | return new_pitch; 158 | }; 159 | 160 | /** 161 | * get_measures_array 162 | * gets the notes played by this stave, used for midi playback 163 | * args 164 | * none 165 | * returns 166 | * an array of notes played in this stave 167 | */ 168 | live_score.Stave.prototype.get_measures_array = function(){ 169 | return this.voices[0].get_measures_array(); 170 | }; 171 | 172 | module.exports = live_score.Stave; 173 | -------------------------------------------------------------------------------- /src/js/structs.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | live_score.structs = {}; 3 | 4 | /** 5 | * create_stave_info 6 | * creates a stave_info struct, used when creating a new measure 7 | * args 8 | * none 9 | * returns 10 | * a stave_info struct 11 | */ 12 | live_score.structs.create_stave_info = function(){ 13 | return{ 14 | /** 15 | * the clef of the stave 16 | */ 17 | clef:"" 18 | }; 19 | }; 20 | 21 | /** 22 | * create_measure_info 23 | * creates a measure_info struct, used when adding measures to a stave 24 | * args 25 | * none 26 | * returns 27 | * a measure_info struct 28 | */ 29 | live_score.structs.create_measure_info = function(){ 30 | return{ 31 | /** 32 | * the total number of beats in the measure 33 | */ 34 | num_beats:0, 35 | 36 | /** 37 | * the note length of each of the beats 38 | */ 39 | beat_value:0 40 | }; 41 | }; 42 | 43 | /** 44 | * create_note_info 45 | * creates a note_info struct, used when adding a note to the score 46 | * args 47 | * none 48 | * returns 49 | * a note_info struct 50 | */ 51 | live_score.structs.create_note_info = function(){ 52 | return{ 53 | /** 54 | * the stave number the note is going to be inserted into 55 | */ 56 | stave_num:0, 57 | 58 | /** 59 | * the measure number the note is going to be inserted into 60 | */ 61 | measure_num:0, 62 | 63 | /** 64 | * the pitch, as a midi number, of the note 65 | */ 66 | pitch:0, 67 | 68 | /** 69 | * the y position of the note as the distance from the highest possible note 70 | * in the stave 71 | */ 72 | y_position:0, 73 | 74 | /** 75 | * the length of the note being inserted 76 | */ 77 | note_length:0, 78 | 79 | /** 80 | * the length of the note as it will be displayed in the score (can differ 81 | * from the note_length) 82 | */ 83 | display_length:0, 84 | 85 | /** 86 | * the longest possible length the note can take up without overlapping with 87 | * an adjacent note 88 | */ 89 | max_length:0, 90 | 91 | /** 92 | * the beat level to which the note will be quantized before being inserted 93 | */ 94 | quantization:0, 95 | 96 | /** 97 | * the number of ticks (rhythmic units) that have occurred before this note 98 | * in the containing measure 99 | */ 100 | tick_position:0, 101 | 102 | /** 103 | * the number of ticks (rhythmic units) that have occurred before this note 104 | * in the containing measure, quantized to a given beat level 105 | */ 106 | quantized_tick_position:0, 107 | 108 | /** 109 | * the position of the note in the measure in terms of beat that it lands on 110 | */ 111 | beat_level:0, 112 | 113 | /** 114 | * a boolean denoting whether a note was found at a given position 115 | */ 116 | note_found:0, 117 | 118 | /** 119 | * a boolean denoting whether an inserted note is within the musical bounds 120 | * of the containing stave/measure 121 | */ 122 | valid_input:0 123 | }; 124 | }; 125 | 126 | /** 127 | * create_event_info 128 | * creates a event_info struct, used for ui actions 129 | * args 130 | * none 131 | * returns 132 | * an event_info struct 133 | */ 134 | live_score.structs.create_event_info = function(){ 135 | return{ 136 | /** 137 | * the graphical object containing a point or area on the score 138 | */ 139 | graphical_object: new live_score.Graphical_object(), 140 | 141 | /** 142 | * the length of the note being inserted 143 | */ 144 | note_length:0, 145 | 146 | /** 147 | * the beat level to which the note will be quantized before being inserted 148 | */ 149 | quantization:0, 150 | 151 | /** 152 | * a boolean denoting whether the mouse is currently within the bounds of the 153 | * score 154 | */ 155 | mouse_on_score:0 156 | 157 | }; 158 | }; 159 | 160 | /** 161 | * create_ui_info 162 | * creates a ui_info struct, used to pass along the state of the ui 163 | * args 164 | * none 165 | * returns 166 | * a ui_info struct 167 | */ 168 | live_score.structs.create_ui_info = function(){ 169 | return{ 170 | /** 171 | * the note length currently selected in the ui 172 | */ 173 | note_length:0, 174 | 175 | /** 176 | * the quantization level currently selected in the ui 177 | */ 178 | quantization:0, 179 | 180 | /** 181 | * denotes whether notes are being added or removed 182 | */ 183 | input_mode:"" 184 | }; 185 | }; 186 | 187 | /** 188 | * create_midi_note_info 189 | * creates a midi_note_info struct, contains information about how to play a 190 | * midi note 191 | * args 192 | * none 193 | * returns 194 | * a midi_note_info struct 195 | */ 196 | live_score.structs.create_midi_note_info = function(){ 197 | return{ 198 | /** 199 | * the midi channel on which to play the note 200 | */ 201 | channel:0, 202 | 203 | /** 204 | * the midi number of the note 205 | */ 206 | note_number:0, 207 | 208 | /** 209 | * the intensity with which the note will be played 210 | */ 211 | velocity:0, 212 | 213 | /** 214 | * the time when the note starts playing 215 | */ 216 | note_on:0, 217 | 218 | /** 219 | * the time when the note stops playing 220 | */ 221 | note_off:0 222 | }; 223 | }; 224 | 225 | /** 226 | * create_key_info 227 | * creates a key_info struct, contains information about a key signature 228 | * args 229 | * none 230 | * returns 231 | * a key_info struct 232 | */ 233 | live_score.structs.create_key_info = function(){ 234 | return{ 235 | 236 | /** 237 | * the name of the key 238 | */ 239 | key:"", 240 | 241 | /** 242 | * an array of notes needing sharps in the key 243 | */ 244 | sharps:[], 245 | 246 | /** 247 | * an array of notes needing flatss in the key 248 | */ 249 | flats:[], 250 | 251 | /** 252 | * an array of notes needing naturals in the key 253 | */ 254 | naturals:[], 255 | 256 | /** 257 | * an array of notes needing double sharps in the key 258 | */ 259 | double_sharps:[], 260 | 261 | /** 262 | * an array of notes needing double flats in the key 263 | */ 264 | double_flats:[] 265 | }; 266 | }; 267 | 268 | /** 269 | * shallow_copy 270 | * creates a shallow copy of a struct 271 | * args 272 | * struct 273 | * the struct to be copied 274 | * returns 275 | * copy 276 | * a copy of the struct passed as an argument 277 | */ 278 | live_score.structs.shallow_copy = function(struct){ 279 | var copy = {}; 280 | for(var field in struct){ 281 | copy[field] = struct[field]; 282 | } 283 | return copy; 284 | }; 285 | 286 | module.exports = live_score.structs; 287 | -------------------------------------------------------------------------------- /src/js/ui.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | live_score.structs = require("./structs.js"); 3 | live_score.Score_panel = require("./score_panel.js"); 4 | live_score.Control_panel = require("./control_panel.js"); 5 | live_score.Note_popup = require("./note_popup.js"); 6 | 7 | /** 8 | * Ui 9 | * Constructor for the Ui Object 10 | * args 11 | * event_controller 12 | * an object to which the Ui passes event information after being 13 | * parsed 14 | * score_editor_div_id 15 | * the id of the div in which all the Ui elements are created 16 | * returns 17 | * none 18 | */ 19 | live_score.Ui = function(event_controller, score_editor_div_id){ 20 | 21 | /** 22 | * (see function description) 23 | */ 24 | this.event_controller = event_controller; 25 | 26 | /** 27 | * (see function description) 28 | */ 29 | this.score_editor_div_id = score_editor_div_id; 30 | 31 | /** 32 | * the html canvas element on which the score is drawn 33 | */ 34 | this.score_editor_div = document.getElementById(score_editor_div_id); 35 | 36 | /** 37 | * a struct (see structs.js) that keeps track of the current state of the UI 38 | */ 39 | this.ui_info = live_score.structs.create_ui_info(); 40 | 41 | /** 42 | * the panel that contains the canvas on which the score is drawn 43 | * (see score_panel.js) 44 | */ 45 | this.score_panel = new live_score.Score_panel(event_controller, 46 | this.ui_info); 47 | 48 | /** 49 | * the panel that contains all the input controls (see control_panel.js) 50 | */ 51 | this.control_panel = new live_score.Control_panel(this.ui_info); 52 | 53 | /** 54 | * the panel that contains all the play back options for the score 55 | */ 56 | this.playback_panel = new live_score.Playback_panel(event_controller); 57 | 58 | /** 59 | * a div showing the pitch of the note that would be inserted based on the 60 | * the user's mouse position 61 | */ 62 | this.note_popup = new live_score.Note_popup(); 63 | }; 64 | 65 | /** 66 | * get_score_panel 67 | * a getter for the score_panel 68 | * args 69 | * none 70 | * returns 71 | * the score_panel 72 | */ 73 | live_score.Ui.prototype.get_score_panel = function(){ 74 | return this.score_panel.score_canvas; 75 | }; 76 | 77 | /** 78 | * resize_score_panel 79 | * resizes the score panel 80 | * args 81 | * spacing_constant 82 | * a constant, based on the number of notes in the score, used to determine 83 | * the new score size 84 | * returns 85 | * the new score_panel size 86 | */ 87 | live_score.Ui.prototype.resize_score_panel = function(spacing_constant){ 88 | return this.score_panel.resize_score_panel(spacing_constant); 89 | }; 90 | 91 | /** 92 | * update_note_popup 93 | * updates the information displayed in the note_popup 94 | * args 95 | * event_info 96 | * a struct (see structs.js) with information about the ui event 97 | * note_info 98 | * a struct (see structs.js) with information about the note being displayed 99 | * in the popup 100 | * returns 101 | * none 102 | */ 103 | live_score.Ui.prototype.update_note_popup = function(event_info, note_info){ 104 | this.note_popup.update(event_info, note_info); 105 | }; 106 | 107 | module.exports = live_score.Ui; 108 | -------------------------------------------------------------------------------- /src/js/voice.js: -------------------------------------------------------------------------------- 1 | live_score = require("./live_score.js"); 2 | live_score.Measure = require("./measure.js"); 3 | 4 | /** 5 | * Voice 6 | * Constructor for the live_score.Voice object 7 | * args 8 | * measure_meta_data 9 | * an array (whose length is the number of measures) with time signature 10 | * information about each measure 11 | * returns 12 | * none 13 | */ 14 | live_score.Voice = function(measure_meta_data){ 15 | 16 | /** 17 | * (see function description) 18 | */ 19 | this.measure_meta_data = measure_meta_data; 20 | 21 | /** 22 | * an array of arrays, each element is an array of the notes played in that 23 | * given measure 24 | */ 25 | this.measures = []; 26 | 27 | this.create_empty_voice(); 28 | }; 29 | 30 | /** 31 | * create_empty_voice 32 | * creates a voice filled with rests whose length is equal to that indicated 33 | * by the measure_meta_data 34 | * args 35 | * none 36 | * returns 37 | * none 38 | */ 39 | live_score.Voice.prototype.create_empty_voice = function(){ 40 | for(var i = 0; i < this.measure_meta_data.length; i++){ 41 | this.measures.push(new live_score.Measure(this.measure_meta_data[i])); 42 | } 43 | }; 44 | 45 | /** 46 | * add_note 47 | * adds a note to the voice 48 | * args 49 | * note_info 50 | * a struct, see structs.js, containing info about the note being inserted 51 | * returns 52 | * a boolean of whether the note was successfully inserted 53 | */ 54 | live_score.Voice.prototype.add_note = function(note_info){ 55 | return this.measures[note_info.measure_num].add_note(note_info); 56 | }; 57 | 58 | /** 59 | * remove_note 60 | * removes a note from a measure 61 | * args 62 | * note_info 63 | * a struct (see structs.js) that contiains information about the 64 | * note being removed 65 | * returns 66 | * none 67 | */ 68 | live_score.Voice.prototype.remove_note = function(note_info){ 69 | return this.measures[note_info.measure_num].remove_note(note_info); 70 | }; 71 | 72 | /** 73 | * get_measures_array 74 | * gets the notes played by this voice, used for midi playback 75 | * args 76 | * none 77 | * returns 78 | * an array of notes played in this voice 79 | */ 80 | live_score.Voice.prototype.get_measures_array = function(){ 81 | var measures = []; 82 | for(var i = 0; i < this.measures.length; i++){ 83 | measures.push(this.measures[i].get_notes_array()); 84 | } 85 | return measures; 86 | }; 87 | 88 | module.exports = live_score.Voice; 89 | -------------------------------------------------------------------------------- /src/js/z.js: -------------------------------------------------------------------------------- 1 | /* 2 | * due to some bug, the live score object gets over written by the last 3 | * alphabetical file. So this file just exports live_score and is the last 4 | * file to be compiled, and therefore re-overwrites the live_score object 5 | * with the correct one. 6 | * THIS HACK IS VERY STUPID AND SHOULD BE FIXED 7 | */ 8 | 9 | live_score = require('./live_score.js'); 10 | 11 | module.exports = live_score; 12 | -------------------------------------------------------------------------------- /support/MIDI.js: -------------------------------------------------------------------------------- 1 | /* 2 | ---------------------------------------------------------- 3 | MIDI.audioDetect : 0.3.2 : 2015-03-26 4 | ---------------------------------------------------------- 5 | https://github.com/mudcube/MIDI.js 6 | ---------------------------------------------------------- 7 | Probably, Maybe, No... Absolutely! 8 | Test to see what types of