├── .gitignore ├── examples.html ├── README.rdoc └── vexflow-json.js /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /examples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | VexFlow JSON Examples 5 | 6 | 7 | 8 | 9 | 10 | 19 | 20 | 21 | 22 | 23 |

VexFlow JSON Examples

24 | 25 |

Simple Chord

26 | 27 | 30 | 31 |

Simple Chord (SVG)

32 | 33 | 36 | 37 |

Simple Chord Scaled 75%

38 | 39 | 42 | 43 |

Simple Chord with Octaves

44 | 45 | 48 | 49 |

Simple Chord with Duration

50 | 51 | 57 | 58 |

Simple List of Notes

59 | 60 | 63 | 64 |

Simple Sequence of Chords

65 | 66 | 77 | 78 |

Explicit Sequence of Chords

79 | 80 | 91 | 92 |

Chord Across Treble & Bass Clefs

93 | 94 | 101 | 102 |

Explicit Voices

103 | 104 | 127 | 128 | 129 |

Explicit Voices with Beaming

130 |

TODO

131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = VexFlow JSON 2 | 3 | This library allows musical notation specified in JSON, for rendering with the awesome VexFlow staff engraving library (HTML5 Canvas or SVG). The goal of this project is to make 90% of staff engraving significantly easier than interacting with the VexFlow API directly. Specifying the data in JSON also allows a consistent representation of the staff to be passed around from server to client. 4 | 5 | == Dependencies: 6 | 7 | * VexFlow 8 | * Underscore.js (Will be removed as a dependency before long) 9 | 10 | == Usage: 11 | 12 | Simply define a canvas, initialize a new Vex.Flow.JSON object with the JSON data, and render it: 13 | 14 | 15 | 16 | 21 | 22 | Note that the render method accepts a second argument with render options, if you want to override the width and height used for example: 23 | 24 | json.render(canvas, { 25 | width: 200, 26 | height: 180, 27 | clef: ["treble", "bass"], // Defaults to just "treble" 28 | keySignature: "C" // Defaults to "C" 29 | }); 30 | 31 | == Examples: 32 | 33 | === Chord 34 | 35 | Without anything special, a simple array of pitch values is interpreted as notes in a single chord, rendered by default as a whole note: 36 | 37 | ["Bb", "D", "F", "A"] 38 | 39 | Although vexflow-json will automatically choose an appropriate octave in treble clef if no octave is specified, you can override this: 40 | 41 | ["Bb/4", "D/4", "F/4", "A/5"] 42 | 43 | Need to specify the duration? Wrap it around an object and set your notes to the "keys" hash value: 44 | 45 | { duration: "h", keys: ["Bb", "D", "F", "A"] } 46 | 47 | The fully-explicit way to render this is to put this inside of another object and assign it to "notes", allowing for other top-level options to be specified: 48 | 49 | { 50 | notes: [ 51 | { duration: "h", keys: ["Bb/4", "D/4", "F/4", "A/5"] } 52 | ] 53 | } 54 | 55 | === Sequence of Notes/Chords 56 | 57 | 58 | Without anything special, an array of arrays is interpreted as a sequence of notes, rendered by default as quarter notes. 59 | 60 | [["C", "D", "E", "F", "G", "A", "B"]] 61 | 62 | You can expand this out with multiple notes to make a sequence of chords. Here are three chords comprising a ii-V-I: 63 | 64 | [ 65 | ["C", "Eb", "G", "Bb"], 66 | ["C", "Eb", "F", "A"], 67 | ["Bb", "D", "F", "A"] 68 | ] 69 | 70 | As with above, any of these string key values can override the octave (chosen intelligently by default based on position in the array). 71 | 72 | Here's the fully-explicit way to render this: 73 | 74 | { 75 | notes: [ 76 | { duration: "q", keys: ["C", "Eb", "G", "Bb"] }, 77 | { duration: "q", keys: ["C", "Eb", "F", "A"] }, 78 | { duration: "h", keys: ["Bb", "D", "F", "A"] } 79 | ] 80 | } 81 | 82 | === Bars 83 | 84 | Within an array of notes, you can create a new bar line by simply including the string "|", for example: 85 | 86 | [ 87 | ["C", "Eb", "G", "Bb"], 88 | ["C", "Eb", "F", "A"], 89 | "|" 90 | ["Bb", "D", "F", "A"] 91 | ] 92 | 93 | Or if using the more explicit object notation, just include an object { barnote: true }: 94 | 95 | { 96 | notes: [ 97 | { duration: "q", keys: ["C", "Eb", "G", "Bb"] }, 98 | { duration: "q", keys: ["C", "Eb", "F", "A"] }, 99 | { barnote: true }, 100 | { duration: "h", keys: ["Bb", "D", "F", "A"] } 101 | ] 102 | } 103 | 104 | 105 | === Voices 106 | 107 | It's also possible to abstract notes further and specify full voices, which imply a time signature and must be mathematically complete: 108 | 109 | { 110 | voices: [ 111 | { time: "4/4", notes: [ 112 | { duration: "q", keys: ["E/5"] }, 113 | { duration: "h", keys: ["D/5"] }, 114 | { duration: "q", keys: ["C/5", "E/5", "G/5"] } 115 | ]}, 116 | { time: "4/4", notes: [ 117 | { duration: "w", keys: ["C/4"] } 118 | ]} 119 | ] 120 | } 121 | 122 | === Beaming 123 | 124 | VexFlow can beam contiguous notes for you. 125 | 126 | TODO 127 | 128 | === Top-Level Object 129 | 130 | Here are all the options on the top-level objects to explicitly plot any sequence of notes: 131 | 132 | { 133 | renderer: "canvas", // Or "svg" 134 | clef: ["treble", "bass"], 135 | height: 180, 136 | width: 300, // This must be wide enough to encompass all notes or an exception will be raised! 137 | notes: [], // Array of notes, rendered sequentially outside of time signature (voice) 138 | voices: [] // Array of voices, supercedes "notes" if specified 139 | } 140 | 141 | === Render Options Object 142 | 143 | Here are all optional keys to the second "render options" object: 144 | 145 | { 146 | width: 200, 147 | height: 180, 148 | scale: 1, // Scales entire renderer up or down (default 1 - no scaling) 149 | clef: "treble" // or: ["treble", "bass"] 150 | } 151 | 152 | NOTE: Even though you can specify both treble and bass clef at once, for now all notes are rendered "within" treble clef and stemmed as such. 153 | -------------------------------------------------------------------------------- /vexflow-json.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | if (!(Vex && Vex.Flow)) { 4 | throw "Please be sure vexflow is required before requiring vexflow.json." 5 | } 6 | 7 | Vex.Flow.JSON = function(data) { 8 | this.data = data; 9 | this.stave_offset = 0; 10 | this.stave_delta = 60; 11 | this.staves = {}; 12 | this.interpret_data(); 13 | } 14 | 15 | Vex.Flow.JSON.prototype.interpret_data = function() { 16 | if (this.data instanceof Array) { 17 | if (this.data[0] instanceof Array) { 18 | this.notes = this.interpret_notes(this.data); 19 | } else if (typeof this.data[0] === "string") { 20 | this.notes = this.interpret_notes([ { keys: this.data } ]); 21 | } 22 | } else if (this.data.keys) { 23 | this.notes = this.interpret_notes([this.data]); 24 | } else if (this.data.notes) { 25 | this.notes = this.interpret_notes(this.data.notes); 26 | } else if (this.data.voices) { 27 | this.voices = this.interpret_voices(this.data.voices); 28 | } 29 | }; 30 | 31 | Vex.Flow.JSON.prototype.interpret_notes = function(data) { 32 | return _(data).map(function(datum) { 33 | if (typeof datum === "string") { 34 | if (datum == "|") { return { barnote: true} } 35 | else { 36 | return { duration: "q", keys: this.interpret_keys([datum]) }; 37 | } 38 | } else if (datum instanceof Array) { 39 | return { duration: "q", keys: this.interpret_keys(datum) }; 40 | } else { 41 | if (datum.keys) { 42 | datum.keys = this.interpret_keys(datum.keys); 43 | datum.duration || (datum.duration = "q"); 44 | } 45 | return datum; 46 | } 47 | }, this); 48 | }; 49 | 50 | Vex.Flow.JSON.prototype.interpret_voices = function(data) { 51 | return _(data).map(function(datum) { 52 | return { 53 | time: datum.time, 54 | notes: this.interpret_notes(datum.notes) 55 | } 56 | }, this); 57 | }; 58 | 59 | Vex.Flow.JSON.prototype.interpret_keys = function(data) { 60 | return _(data).map(function(datum) { 61 | var note_portion, octave_portion, _ref; 62 | _ref = datum.split("/"), note_portion = _ref[0], octave_portion = _ref[1]; 63 | octave_portion || (octave_portion = "4"); 64 | return "" + note_portion + "/" + octave_portion; 65 | }); 66 | }; 67 | 68 | Vex.Flow.JSON.prototype.draw_canvas = function(canvas, canvas_options) { 69 | canvas_options = canvas_options || {}; 70 | 71 | this.canvas = canvas; 72 | var backend = Vex.Flow.Renderer.Backends.CANVAS; 73 | if (canvas.tagName.toLowerCase() === "svg") { 74 | backend = Vex.Flow.Renderer.Backends.SVG; 75 | } 76 | this.renderer = new Vex.Flow.Renderer(this.canvas, backend); 77 | this.context = this.renderer.getContext(); 78 | 79 | if (canvas_options.scale) { 80 | this.context.scale(canvas_options.scale, canvas_options.scale); 81 | } 82 | }; 83 | 84 | Vex.Flow.JSON.prototype.draw_stave = function(clef, keySignature, options) { 85 | if (clef == null) clef = "treble"; 86 | if (!(clef instanceof Array)) clef = [clef]; 87 | if (options == null) options = {}; 88 | 89 | _(clef).each(function(c) { 90 | this.staves[c] = new Vex.Flow.Stave(10, this.stave_offset, this.width - 20); 91 | this.staves[c].addClef(c).addKeySignature(keySignature).setContext(this.context).draw(); 92 | this.stave_offset += this.stave_delta; 93 | }, this); 94 | }; 95 | 96 | Vex.Flow.JSON.prototype.stave_notes = function(notes) { 97 | return _(notes).map(function(note) { 98 | if (note.barnote) { return new Vex.Flow.BarNote(); } 99 | 100 | var stave_note; 101 | note.duration || (note.duration = "h"); 102 | note.clef = "treble"; // Forcing to treble for now, even though bass may be present (we just line it up properly) 103 | stave_note = new Vex.Flow.StaveNote(note); 104 | 105 | _(note.keys).each(function(key, i) { 106 | var accidental, note_portion; 107 | note_portion = key.split("/")[0]; 108 | accidental = note_portion.slice(1, (note_portion.length + 1) || 9e9); 109 | 110 | if (accidental.length > 0) { 111 | stave_note.addAccidental(i, new Vex.Flow.Accidental(accidental)); 112 | } 113 | }); 114 | return stave_note; 115 | }); 116 | }; 117 | 118 | Vex.Flow.JSON.prototype.draw_notes = function(notes) { 119 | Vex.Flow.Formatter.FormatAndDraw(this.context, this.staves["treble"], notes); 120 | }; 121 | 122 | Vex.Flow.JSON.prototype.stave_voices = function(voices) { 123 | return _(this.voices).map(function(voice) { 124 | var stave_voice = new Vex.Flow.Voice({ 125 | num_beats: voice.time.split("/")[0], 126 | beat_value: voice.time.split("/")[1], 127 | resolution: Vex.Flow.RESOLUTION 128 | }); 129 | 130 | stave_voice.setStrict(false); 131 | stave_voice.addTickables(this.stave_notes(voice.notes)); 132 | return stave_voice; 133 | }, this); 134 | }; 135 | 136 | Vex.Flow.JSON.prototype.draw_voices = function(voices) { 137 | var formatter = new Vex.Flow.Formatter().joinVoices(voices).format(voices, this.width - 120); 138 | _(voices).each(function(voice) { 139 | voice.draw(this.context, this.staves["treble"]); 140 | }, this); 141 | }; 142 | 143 | Vex.Flow.JSON.prototype.render = function(element, options) { 144 | options = (options || {}); 145 | this.width = options.width || (element.width|0) || 600; // coerce weird SVG values to ints 146 | this.height = options.height || (element.height|0) || 120; 147 | this.clef = options.clef; 148 | this.scale = options.scale || 1; 149 | this.keySignature = options.keySignature || 'C'; 150 | 151 | this.draw_canvas(element, { 152 | scale: this.scale 153 | }); 154 | 155 | this.draw_stave(this.clef, this.keySignature); 156 | 157 | if (this.voices) { 158 | this.draw_voices(this.stave_voices(this.voices)); 159 | } else { 160 | this.draw_notes(this.stave_notes(this.notes)); 161 | } 162 | }; 163 | 164 | }).call(this); 165 | --------------------------------------------------------------------------------