├── .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 |
--------------------------------------------------------------------------------