abcjs-rails 2.1 → 2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascripts/abcjs/api/abc_tunebook.js +5 -2
- data/app/assets/javascripts/abcjs/edit/abc_editor.js +12 -6
- data/app/assets/javascripts/abcjs/midi/abc_midi_create.js +72 -0
- data/app/assets/javascripts/abcjs/midi/abc_midi_flattener.js +565 -0
- data/app/assets/javascripts/abcjs/midi/abc_midi_renderer.js +426 -0
- data/app/assets/javascripts/abcjs/midi/abc_midi_sequencer.js +207 -0
- data/app/assets/javascripts/abcjs/parse/abc_parse.js +5 -3
- data/app/assets/javascripts/abcjs/parse/abc_parse_header.js +4 -1
- data/app/assets/javascripts/abcjs/write/abc_beam_element.js +3 -1
- data/app/assets/javascripts/abcjs/write/abc_engraver_controller.js +1 -0
- data/app/assets/javascripts/abcjs/write/abc_renderer.js +6 -4
- data/app/assets/javascripts/abcjs/write/abc_staff_group_element.js +1 -0
- data/app/assets/javascripts/abcjs/write/abc_tempo_element.js +5 -5
- data/lib/abcjs-rails/version.rb +1 -1
- metadata +7 -4
- data/app/assets/javascripts/abcjs/midi/abc_midiwriter.js +0 -701
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92eedfe222f72fee3b313a63bd3c2965c198dfd2
|
4
|
+
data.tar.gz: 0a2092fb7c1c2b99bf67a628d310be52ac376816
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9368ee3727320885cb1a5acede442e1256d3e8c2158d3a7ad7219e73adaddbf92a2c885d49dc57090f5f831e15e25d848cf2f332dff52818caee10b04d72484
|
7
|
+
data.tar.gz: c1c82feb61cac98aa0ff32b75f02095c04278651682e21f2de515ae2bb6b067da31ca0d5c92cf66cb71248dee6930c05eba8e538b406ee4b48569b73432f9960
|
@@ -184,8 +184,11 @@ if (!window.ABCJS)
|
|
184
184
|
function callback(div, tune) {
|
185
185
|
if (midiParams === undefined)
|
186
186
|
midiParams = {};
|
187
|
-
var
|
188
|
-
|
187
|
+
var midi = window.ABCJS.midi.create(tune, midiParams);
|
188
|
+
var title = tune.metaText.title;
|
189
|
+
if (title)
|
190
|
+
title = title.toLowerCase().replace(/\W/g, '_');
|
191
|
+
div.innerHTML = '<a download="' + title + '.midi" href="' + midi + '">download midi</a>';
|
189
192
|
}
|
190
193
|
|
191
194
|
return renderEngine(callback, output, abc, parserParams, renderParams);
|
@@ -262,12 +262,18 @@ window.ABCJS.Editor.prototype.modelChanged = function() {
|
|
262
262
|
this.engraver_controller = new ABCJS.write.EngraverController(paper, this.engraverparams);
|
263
263
|
this.engraver_controller.engraveABC(this.tunes);
|
264
264
|
this.tunes[0].engraver = this.engraver_controller; // TODO-PER: We actually want an output object for each tune, not the entire controller. When refactoring, don't save data in the controller.
|
265
|
-
if (ABCJS.midi
|
266
|
-
if (this.mididiv !== this.div)
|
267
|
-
this.mididiv.innerHTML = "";
|
268
|
-
|
269
|
-
|
270
|
-
|
265
|
+
if (window.ABCJS.midi && this.mididiv) {
|
266
|
+
// if (this.mididiv !== this.div)
|
267
|
+
// this.mididiv.innerHTML = "";
|
268
|
+
var midi = window.ABCJS.midi.create(this.tunes[0], this.midiParams); //TODO handle multiple tunes
|
269
|
+
var title = this.tunes[0].metaText && this.tunes[0].metaText.title ? this.tunes[0].metaText.title : 'Untitled';
|
270
|
+
var linkTitle = "Download MIDI for \"" + title + "\"";
|
271
|
+
if (title)
|
272
|
+
title = title.toLowerCase().replace(/'/g, '').replace(/\W/g, '_').replace(/__/g, '_');
|
273
|
+
this.mididiv.innerHTML = '<a download="' + title + '.midi" href="' + midi + '">' + linkTitle + '</a>';
|
274
|
+
// var midiwriter = new ABCJS.midi.MidiWriter(this.mididiv,this.midiparams);
|
275
|
+
// midiwriter.addListener(this.engraver_controller);
|
276
|
+
// midiwriter.writeABC(this.tunes[0]); //TODO handle multiple tunes
|
271
277
|
}
|
272
278
|
if (this.warningsdiv) {
|
273
279
|
this.warningsdiv.innerHTML = (this.warnings) ? this.warnings.join("<br />") : "No errors";
|
@@ -0,0 +1,72 @@
|
|
1
|
+
// abc_midi_create.js: Turn a linear series of events into a midi file.
|
2
|
+
// Copyright (C) 2010,2015 Gregory Dyke (gregdyke at gmail dot com) and Paul Rosen
|
3
|
+
//
|
4
|
+
// This program is free software: you can redistribute it and/or modify
|
5
|
+
// it under the terms of the GNU General Public License as published by
|
6
|
+
// the Free Software Foundation, either version 3 of the License, or
|
7
|
+
// (at your option) any later version.
|
8
|
+
//
|
9
|
+
// This program is distributed in the hope that it will be useful,
|
10
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
// GNU General Public License for more details.
|
13
|
+
//
|
14
|
+
// You should have received a copy of the GNU General Public License
|
15
|
+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
if (!window.ABCJS)
|
18
|
+
window.ABCJS = {};
|
19
|
+
|
20
|
+
if (!window.ABCJS.midi)
|
21
|
+
window.ABCJS.midi = {};
|
22
|
+
|
23
|
+
(function() {
|
24
|
+
"use strict";
|
25
|
+
|
26
|
+
var baseDuration = 480*4; // nice and divisible, equals 1 whole note
|
27
|
+
|
28
|
+
window.ABCJS.midi.create = function(abcTune, options) {
|
29
|
+
var sequence = window.ABCJS.midi.sequence(abcTune, options);
|
30
|
+
var commands = window.ABCJS.midi.flatten(sequence);
|
31
|
+
var midi = window.ABCJS.midi.rendererFactory(false);
|
32
|
+
|
33
|
+
if (commands.instrument !== undefined)
|
34
|
+
midi.setInstrument(commands.instrument);
|
35
|
+
if (commands.channel !== undefined)
|
36
|
+
midi.setChannel(commands.channel);
|
37
|
+
var title = abcTune.metaText ? abcTune.metaText.title : undefined;
|
38
|
+
if (title && title.length > 128)
|
39
|
+
title = title.substring(0,124) + '...';
|
40
|
+
midi.setGlobalInfo(commands.tempo, title);
|
41
|
+
|
42
|
+
for (var i = 0; i < commands.tracks.length; i++) {
|
43
|
+
midi.startTrack();
|
44
|
+
for (var j = 0; j < commands.tracks[i].length; j++) {
|
45
|
+
var event = commands.tracks[i][j];
|
46
|
+
switch (event.cmd) {
|
47
|
+
case 'instrument':
|
48
|
+
midi.setInstrument(event.instrument);
|
49
|
+
break;
|
50
|
+
case 'start':
|
51
|
+
midi.startNote(convertPitch(event.pitch), event.volume);
|
52
|
+
break;
|
53
|
+
case 'stop':
|
54
|
+
midi.endNote(convertPitch(event.pitch), 0); // TODO-PER: Refactor: the old midi used a duration here.
|
55
|
+
break;
|
56
|
+
case 'move':
|
57
|
+
midi.addRest(event.duration * baseDuration);
|
58
|
+
break;
|
59
|
+
default:
|
60
|
+
console.log("MIDI create Unknown: " + event.cmd);
|
61
|
+
}
|
62
|
+
}
|
63
|
+
midi.endTrack();
|
64
|
+
}
|
65
|
+
|
66
|
+
return midi.getData();
|
67
|
+
};
|
68
|
+
|
69
|
+
function convertPitch(pitch) {
|
70
|
+
return 60 + pitch;
|
71
|
+
}
|
72
|
+
})();
|
@@ -0,0 +1,565 @@
|
|
1
|
+
// abc_midi_create.js: Turn a linear series of events into a series of MIDI commands.
|
2
|
+
// Copyright (C) 2010,2015 Gregory Dyke (gregdyke at gmail dot com) and Paul Rosen
|
3
|
+
//
|
4
|
+
// This program is free software: you can redistribute it and/or modify
|
5
|
+
// it under the terms of the GNU General Public License as published by
|
6
|
+
// the Free Software Foundation, either version 3 of the License, or
|
7
|
+
// (at your option) any later version.
|
8
|
+
//
|
9
|
+
// This program is distributed in the hope that it will be useful,
|
10
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
// GNU General Public License for more details.
|
13
|
+
//
|
14
|
+
// You should have received a copy of the GNU General Public License
|
15
|
+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
// We input a set of voices, but the notes are still complex. This pass changes the logical definitions
|
18
|
+
// of the grace notes, decorations, ties, triplets, rests, transpositions, keys, and accidentals into actual note durations.
|
19
|
+
// It also extracts guitar chords to a separate voice and resolves their rhythm.
|
20
|
+
|
21
|
+
if (!window.ABCJS)
|
22
|
+
window.ABCJS = {};
|
23
|
+
|
24
|
+
if (!window.ABCJS.midi)
|
25
|
+
window.ABCJS.midi = {};
|
26
|
+
|
27
|
+
(function() {
|
28
|
+
"use strict";
|
29
|
+
|
30
|
+
var barAccidentals;
|
31
|
+
var accidentals;
|
32
|
+
var transpose;
|
33
|
+
var bagpipes;
|
34
|
+
var multiplier;
|
35
|
+
var tracks;
|
36
|
+
var startingTempo;
|
37
|
+
var currentTempo;
|
38
|
+
var instrument;
|
39
|
+
var channel;
|
40
|
+
var currentTrack;
|
41
|
+
var pitchesTied;
|
42
|
+
var lastNoteDurationPosition;
|
43
|
+
|
44
|
+
var meter;
|
45
|
+
var chordTrack;
|
46
|
+
var chordTrackFinished;
|
47
|
+
var currentChords;
|
48
|
+
var lastChord;
|
49
|
+
var barBeat;
|
50
|
+
|
51
|
+
var normalBreakBetweenNotes = 1.0/128; // a 128th note of silence between notes for articulation.
|
52
|
+
|
53
|
+
window.ABCJS.midi.flatten = function(voices) {
|
54
|
+
barAccidentals = [];
|
55
|
+
accidentals = [0,0,0,0,0,0,0];
|
56
|
+
transpose = 0;
|
57
|
+
bagpipes = false;
|
58
|
+
multiplier = 1;
|
59
|
+
tracks = [];
|
60
|
+
startingTempo = undefined;
|
61
|
+
currentTempo = undefined;
|
62
|
+
instrument = undefined;
|
63
|
+
channel = undefined;
|
64
|
+
currentTrack = undefined;
|
65
|
+
pitchesTied = {};
|
66
|
+
lastNoteDurationPosition = -1;
|
67
|
+
|
68
|
+
// For resolving chords.
|
69
|
+
meter = undefined;
|
70
|
+
chordTrack = [];
|
71
|
+
chordTrackFinished = false;
|
72
|
+
currentChords = [];
|
73
|
+
lastChord = undefined;
|
74
|
+
barBeat = 0;
|
75
|
+
|
76
|
+
for (var i = 0; i < voices.length; i++) {
|
77
|
+
var voice = voices[i];
|
78
|
+
currentTrack = [];
|
79
|
+
pitchesTied = {};
|
80
|
+
for (var j = 0; j < voice.length; j++) {
|
81
|
+
var element = voice[j];
|
82
|
+
switch (element.el_type) {
|
83
|
+
case "note":
|
84
|
+
writeNote(element);
|
85
|
+
break;
|
86
|
+
case "key":
|
87
|
+
accidentals = setKeySignature(element);
|
88
|
+
break;
|
89
|
+
case "meter":
|
90
|
+
meter = element;
|
91
|
+
break;
|
92
|
+
case "tempo":
|
93
|
+
if (!startingTempo)
|
94
|
+
startingTempo = element.qpm;
|
95
|
+
else
|
96
|
+
currentTempo = element.qpm;
|
97
|
+
break;
|
98
|
+
case "transpose":
|
99
|
+
transpose = element.transpose;
|
100
|
+
break;
|
101
|
+
case "bar":
|
102
|
+
if (chordTrack.length > 0) {
|
103
|
+
resolveChords();
|
104
|
+
currentChords = [];
|
105
|
+
}
|
106
|
+
barBeat = 0;
|
107
|
+
barAccidentals = [];
|
108
|
+
break;
|
109
|
+
case "bagpipes":
|
110
|
+
bagpipes = true;
|
111
|
+
break;
|
112
|
+
case "instrument":
|
113
|
+
instrument = element.program;
|
114
|
+
break;
|
115
|
+
case "channel":
|
116
|
+
channel = element.channel;
|
117
|
+
break;
|
118
|
+
default:
|
119
|
+
// This should never happen
|
120
|
+
console.log("MIDI creation. Unknown el_type: " + element.el_type + "\n");// jshint ignore:line
|
121
|
+
break;
|
122
|
+
}
|
123
|
+
}
|
124
|
+
tracks.push(currentTrack);
|
125
|
+
if (chordTrack.length > 0) // Don't do chords on more than one track, so turn off chord detection after we create it.
|
126
|
+
chordTrackFinished = true;
|
127
|
+
}
|
128
|
+
if (chordTrack.length > 0)
|
129
|
+
tracks.push(chordTrack);
|
130
|
+
return { tempo: startingTempo, instrument: instrument, channel: channel, tracks: tracks };
|
131
|
+
};
|
132
|
+
|
133
|
+
//
|
134
|
+
// The algorithm for chords is:
|
135
|
+
// - The chords are done in a separate track.
|
136
|
+
// - If there are notes before the first chord, then put that much silence to start the track.
|
137
|
+
// - The pattern of chord expression depends on the meter, and how many chords are in a measure.
|
138
|
+
// - There is a possibility that a measure will have an incorrect number of beats, if that is the case, then
|
139
|
+
// start the pattern anew on the next measure number.
|
140
|
+
// - If a chord root is not A-G, then ignore it as if the chord wasn't there at all.
|
141
|
+
// - If a chord modification isn't in our supported list, change it to a major triad.
|
142
|
+
//
|
143
|
+
// - If there is only one chord in a measure:
|
144
|
+
// - If 2/4, play root chord
|
145
|
+
// - If cut time, play root(1) chord(3)
|
146
|
+
// - If 3/4, play root chord chord
|
147
|
+
// - If 4/4 or common time, play root chord fifth chord
|
148
|
+
// - If 6/8, play root(1) chord(3) fifth(4) chord(6)
|
149
|
+
// - For any other meter, play the full chord on each beat. (TODO-PER: expand this as more support is added.)
|
150
|
+
//
|
151
|
+
// - If there is a chord specified that is not on a beat, move it earlier to the previous beat, unless there is already a chord on that beat.
|
152
|
+
// - Otherwise, move it later, unless there is already a chord on that beat.
|
153
|
+
// - Otherwise, ignore it. (TODO-PER: expand this as more support is added.)
|
154
|
+
//
|
155
|
+
// - If there is a chord on the second beat, play a chord for the first beat instead of a bass note.
|
156
|
+
// - Likewise, if there is a chord on the fourth beat of 4/4, play a chord on the third beat instead of a bass note.
|
157
|
+
//
|
158
|
+
var breakSynonyms = [ 'break', '(break)', 'no chord', 'n.c.', 'tacet'];
|
159
|
+
|
160
|
+
function findChord(elem) {
|
161
|
+
// TODO-PER: Just using the first chord if there are more than one.
|
162
|
+
if (chordTrackFinished || !elem.chord || elem.chord.length === 0)
|
163
|
+
return null;
|
164
|
+
|
165
|
+
// Return the first annotation that is a regular chord: that is, it is in the default place or is a recognized "tacit" phrase.
|
166
|
+
for (var i = 0; i < elem.chord.length; i++) {
|
167
|
+
var ch = elem.chord[i];
|
168
|
+
if (ch.position === 'default')
|
169
|
+
return ch.name;
|
170
|
+
if (breakSynonyms.indexOf(ch.name.toLowerCase()) >= 0)
|
171
|
+
return 'break';
|
172
|
+
}
|
173
|
+
return null;
|
174
|
+
}
|
175
|
+
|
176
|
+
function writeNote(elem) {
|
177
|
+
//
|
178
|
+
// Create a series of note events to append to the current track.
|
179
|
+
// The output event is one of: { pitchStart: pitch_in_abc_units, volume: from_1_to_64 }
|
180
|
+
// { pitchStop: pitch_in_abc_units }
|
181
|
+
// { moveTime: duration_in_abc_units }
|
182
|
+
// If there are guitar chords, then they are put in a separate track, but they have the same format.
|
183
|
+
//
|
184
|
+
|
185
|
+
var chord = findChord(elem);
|
186
|
+
if (chord) {
|
187
|
+
var c = interpretChord(chord);
|
188
|
+
// If this isn't a recognized chord, just completely ignore it.
|
189
|
+
if (c) {
|
190
|
+
// If we ever have a chord in this voice, then we add the chord track.
|
191
|
+
// However, if there are chords on more than one voice, then just use the first voice.
|
192
|
+
if (chordTrack.length === 0) {
|
193
|
+
chordTrack.push({cmd: 'instrument', instrument: 2});
|
194
|
+
// need to figure out how far in time the chord started: if there are pickup notes before the chords start, we need pauses.
|
195
|
+
var distance = 0;
|
196
|
+
for (var ct = 0; ct < currentTrack.length; ct++) {
|
197
|
+
if (currentTrack[ct].cmd === 'move')
|
198
|
+
distance += currentTrack[ct].duration;
|
199
|
+
}
|
200
|
+
if (distance > 0)
|
201
|
+
chordTrack.push({cmd: 'move', duration: distance});
|
202
|
+
}
|
203
|
+
|
204
|
+
lastChord = c;
|
205
|
+
currentChords.push({chord: lastChord, beat: barBeat});
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
if (elem.startTriplet) {
|
210
|
+
if (elem.startTriplet === 2)
|
211
|
+
multiplier = 3/2;
|
212
|
+
else
|
213
|
+
multiplier=(elem.startTriplet-1)/elem.startTriplet;
|
214
|
+
}
|
215
|
+
|
216
|
+
var duration = elem.duration*multiplier;
|
217
|
+
barBeat += duration;
|
218
|
+
|
219
|
+
// if there are grace notes, then also play them.
|
220
|
+
// I'm not sure there is an exact rule for the length of the notes. My rule, unless I find
|
221
|
+
// a better one is: the grace notes cannot take more than 1/2 of the main note's value.
|
222
|
+
// A grace note (of 1/8 note duration) takes 1/8 of the main note's value.
|
223
|
+
var graces;
|
224
|
+
if (elem.gracenotes) {
|
225
|
+
// There are two cases: if this is bagpipe, the grace notes are played on the beat with the current note.
|
226
|
+
// Normally, the grace notes would be played before the beat. (If this is the first note in the track, however, then it is played on the current beat.)
|
227
|
+
// The reason for the exception on the first note is that it would otherwise move the whole track in time and would affect all the other tracks.
|
228
|
+
var stealFromCurrent = (bagpipes || lastNoteDurationPosition < 0);
|
229
|
+
var stealFromDuration = stealFromCurrent ? duration : currentTrack[lastNoteDurationPosition].duration;
|
230
|
+
graces = processGraceNotes(elem.gracenotes, stealFromDuration);
|
231
|
+
if (!bagpipes) {
|
232
|
+
duration = writeGraceNotes(graces, stealFromCurrent, duration);
|
233
|
+
}
|
234
|
+
}
|
235
|
+
|
236
|
+
if (elem.pitches) {
|
237
|
+
if (graces && bagpipes) {
|
238
|
+
// If it is bagpipes, then the graces are played with the note. If the grace has the same pitch as the note, then we just skip it.
|
239
|
+
duration = writeGraceNotes(graces, true, duration);
|
240
|
+
}
|
241
|
+
var pitches = [];
|
242
|
+
for (var i=0; i<elem.pitches.length; i++) {
|
243
|
+
var note = elem.pitches[i];
|
244
|
+
var actualPitch = adjustPitch(note);
|
245
|
+
pitches.push({ pitch: actualPitch, startTie: note.startTie });
|
246
|
+
|
247
|
+
// TODO-PER: should the volume vary depending on whether it is on a beat or measure start?
|
248
|
+
if (!pitchesTied[''+actualPitch]) // If this is the second note of a tie, we don't start it again.
|
249
|
+
currentTrack.push({ cmd: 'start', pitch: actualPitch, volume: 64 });
|
250
|
+
|
251
|
+
if (note.startTie)
|
252
|
+
pitchesTied[''+actualPitch] = true;
|
253
|
+
else if (note.endTie)
|
254
|
+
pitchesTied[''+actualPitch] = false;
|
255
|
+
}
|
256
|
+
currentTrack.push({ cmd: 'move', duration: duration-normalBreakBetweenNotes });
|
257
|
+
lastNoteDurationPosition = currentTrack.length-1;
|
258
|
+
|
259
|
+
for (var ii = 0; ii < pitches.length; ii++) {
|
260
|
+
if (!pitchesTied[''+pitches[ii].pitch])
|
261
|
+
currentTrack.push({ cmd: 'stop', pitch: pitches[ii].pitch });
|
262
|
+
}
|
263
|
+
currentTrack.push({ cmd: 'move', duration: normalBreakBetweenNotes });
|
264
|
+
} else if (elem.rest) {
|
265
|
+
currentTrack.push({ cmd: 'move', duration: duration });
|
266
|
+
}
|
267
|
+
|
268
|
+
if (elem.endTriplet) {
|
269
|
+
multiplier=1;
|
270
|
+
}
|
271
|
+
}
|
272
|
+
|
273
|
+
var scale = [0,2,4,5,7,9,11];
|
274
|
+
function adjustPitch(note) {
|
275
|
+
var pitch = note.pitch;
|
276
|
+
if (note.accidental) {
|
277
|
+
switch(note.accidental) { // change that pitch (not other octaves) for the rest of the bar
|
278
|
+
case "sharp":
|
279
|
+
barAccidentals[pitch]=1; break;
|
280
|
+
case "flat":
|
281
|
+
barAccidentals[pitch]=-1; break;
|
282
|
+
case "natural":
|
283
|
+
barAccidentals[pitch]=0; break;
|
284
|
+
case "dblsharp":
|
285
|
+
barAccidentals[pitch]=2; break;
|
286
|
+
case "dblflat":
|
287
|
+
barAccidentals[pitch]=-2; break;
|
288
|
+
}
|
289
|
+
}
|
290
|
+
|
291
|
+
var actualPitch = extractOctave(pitch) *12 + scale[extractNote(pitch)];
|
292
|
+
|
293
|
+
if ( barAccidentals[pitch]!==undefined) {
|
294
|
+
actualPitch += barAccidentals[pitch];
|
295
|
+
} else { // use normal accidentals
|
296
|
+
actualPitch += accidentals[extractNote(pitch)];
|
297
|
+
}
|
298
|
+
actualPitch += transpose;
|
299
|
+
return actualPitch;
|
300
|
+
}
|
301
|
+
|
302
|
+
function setKeySignature(elem) {
|
303
|
+
var accidentals = [0,0,0,0,0,0,0];
|
304
|
+
if (!elem.accidentals) return accidentals;
|
305
|
+
for (var i = 0; i < elem.accidentals.length; i++) {
|
306
|
+
var acc = elem.accidentals[i];
|
307
|
+
var d = (acc.acc === "sharp") ? 1 : (acc.acc === "natural") ?0 : -1;
|
308
|
+
|
309
|
+
var lowercase = acc.note.toLowerCase();
|
310
|
+
var note = extractNote(lowercase.charCodeAt(0)-'c'.charCodeAt(0));
|
311
|
+
accidentals[note]+=d;
|
312
|
+
}
|
313
|
+
return accidentals;
|
314
|
+
}
|
315
|
+
|
316
|
+
var graceDivider = 8; // This is the fraction of a note that the grace represents. That is, if this is 2, then a grace note of 1/16 would be a 1/32.
|
317
|
+
function processGraceNotes(graces, companionDuration) {
|
318
|
+
var graceDuration = 0;
|
319
|
+
var ret = [];
|
320
|
+
var grace;
|
321
|
+
for (var g = 0; g < graces.length; g++) {
|
322
|
+
grace = graces[g];
|
323
|
+
graceDuration += grace.duration;
|
324
|
+
}
|
325
|
+
graceDuration = graceDuration / graceDivider;
|
326
|
+
var multiplier = (graceDuration * 2 > companionDuration) ? companionDuration/(graceDuration * 2) : 1;
|
327
|
+
|
328
|
+
for (g = 0; g < graces.length; g++) {
|
329
|
+
grace = graces[g];
|
330
|
+
ret.push({ pitch: grace.pitch, duration: grace.duration/graceDivider*multiplier });
|
331
|
+
}
|
332
|
+
return ret;
|
333
|
+
}
|
334
|
+
|
335
|
+
function writeGraceNotes(graces, stealFromCurrent, duration, skipNote) {
|
336
|
+
for (var g = 0; g < graces.length; g++) {
|
337
|
+
var gp = adjustPitch(graces[g]);
|
338
|
+
if (gp !== skipNote)
|
339
|
+
currentTrack.push({cmd: 'start', pitch: gp, volume: 64});
|
340
|
+
currentTrack.push({cmd: 'move', duration: graces[g].duration});
|
341
|
+
if (gp !== skipNote)
|
342
|
+
currentTrack.push({cmd: 'stop', pitch: gp});
|
343
|
+
if (!stealFromCurrent)
|
344
|
+
currentTrack[lastNoteDurationPosition].duration -= graces[g].duration;
|
345
|
+
duration -= graces[g].duration;
|
346
|
+
}
|
347
|
+
return duration;
|
348
|
+
}
|
349
|
+
|
350
|
+
function extractOctave(pitch) {
|
351
|
+
return Math.floor(pitch/7);
|
352
|
+
}
|
353
|
+
|
354
|
+
function extractNote(pitch) {
|
355
|
+
pitch = pitch%7;
|
356
|
+
if (pitch<0) pitch+=7;
|
357
|
+
return pitch;
|
358
|
+
}
|
359
|
+
|
360
|
+
var basses = {
|
361
|
+
'A': -27, 'B': -25, 'C': -24, 'D': -22, 'E': -20, 'F': -19, 'G': -17
|
362
|
+
};
|
363
|
+
function interpretChord(name) {
|
364
|
+
// chords have the format:
|
365
|
+
// [root][acc][modifier][/][bass][acc]
|
366
|
+
// (The chord might be surrounded by parens. Just ignore them.)
|
367
|
+
// root must be present and must be from A-G.
|
368
|
+
// acc is optional and can be # or b
|
369
|
+
// The modifier can be a wide variety of things, like "maj7". As they are discovered, more are supported here.
|
370
|
+
// If there is a slash, then there is a bass note, which can be from A-G, with an optional acc.
|
371
|
+
// If the root is unrecognized, then "undefined" is returned and there is no chord.
|
372
|
+
// If the modifier is unrecognized, a major triad is returned.
|
373
|
+
// If the bass notes is unrecognized, it is ignored.
|
374
|
+
if (name.length === 0)
|
375
|
+
return undefined;
|
376
|
+
if (name === 'break')
|
377
|
+
return { chick: []};
|
378
|
+
var root = name.substring(0,1);
|
379
|
+
if (root === '(') {
|
380
|
+
name = name.substring(1,name.length-2);
|
381
|
+
if (name.length === 0)
|
382
|
+
return undefined;
|
383
|
+
root = name.substring(0,1);
|
384
|
+
}
|
385
|
+
var bass = basses[root];
|
386
|
+
if (!bass) // If the bass note isn't listed, then this was an unknown root. Only A-G are accepted.
|
387
|
+
return undefined;
|
388
|
+
bass += transpose;
|
389
|
+
var bass2 = bass - 5; // The alternating bass is a 4th below
|
390
|
+
var chick;
|
391
|
+
if (name.length === 1)
|
392
|
+
chick = chordNotes(bass, '');
|
393
|
+
var remaining = name.substring(1);
|
394
|
+
var acc = remaining.substring(0,1);
|
395
|
+
if (acc === 'b' || acc === '♭') {
|
396
|
+
bass--;
|
397
|
+
bass2--;
|
398
|
+
remaining = remaining.substring(1);
|
399
|
+
} else if (acc === '#' || acc === '♯') {
|
400
|
+
bass++;
|
401
|
+
bass2++;
|
402
|
+
remaining = remaining.substring(1);
|
403
|
+
}
|
404
|
+
var arr = remaining.split('/');
|
405
|
+
chick = chordNotes(bass, arr[0]);
|
406
|
+
if (arr.length === 2) {
|
407
|
+
bass = basses[arr[1]] + transpose;
|
408
|
+
bass2 = bass;
|
409
|
+
}
|
410
|
+
return { boom: bass, boom2: bass2, chick: chick };
|
411
|
+
}
|
412
|
+
|
413
|
+
var chordIntervals = {
|
414
|
+
'M': [ 0, 4, 7 ],
|
415
|
+
'6': [ 0, 4, 7, 9 ],
|
416
|
+
'7': [ 0, 4, 7, 10 ],
|
417
|
+
'+7': [ 0, 4, 8, 10 ],
|
418
|
+
'aug7': [ 0, 4, 8, 10 ],
|
419
|
+
'maj7': [ 0, 4, 7, 11 ],
|
420
|
+
'∆7': [ 0, 4, 7, 11 ],
|
421
|
+
'9': [ 0, 4, 7, 10, 14 ],
|
422
|
+
'11': [ 0, 4, 7, 10, 14, 16 ], // TODO-PER: check this one.
|
423
|
+
'13': [ 0, 4, 7, 10, 14, 18 ], // TODO-PER: check this one.
|
424
|
+
'+': [ 0, 4, 8 ],
|
425
|
+
'7#5': [ 0, 4, 8, 10 ],
|
426
|
+
'7+5': [ 0, 4, 8, 10 ],
|
427
|
+
'7b9': [ 0, 4, 7, 10, 13 ],
|
428
|
+
'7b5': [ 0, 4, 6, 10 ],
|
429
|
+
'9#5': [ 0, 4, 8, 10, 14 ],
|
430
|
+
'9+5': [ 0, 4, 8, 10, 14 ],
|
431
|
+
'm': [ 0, 3, 7 ],
|
432
|
+
'-': [ 0, 3, 7 ],
|
433
|
+
'm6': [ 0, 3, 7, 9 ],
|
434
|
+
'-6': [ 0, 3, 7, 9 ],
|
435
|
+
'm7': [ 0, 3, 7, 10 ],
|
436
|
+
'-7': [ 0, 3, 7, 10 ],
|
437
|
+
'dim': [ 0, 3, 6 ],
|
438
|
+
'dim7': [ 0, 3, 6, 9 ],
|
439
|
+
'°7': [ 0, 3, 6, 9 ],
|
440
|
+
'ø7': [ 0, 3, 6, 9 ], // TODO-PER: check this one.
|
441
|
+
'7sus4': [ 0, 5, 7, 10 ],
|
442
|
+
'm7sus4': [ 0, 5, 7, 10 ], // TODO-PER: check this one.
|
443
|
+
'sus4': [ 0, 5, 7 ]
|
444
|
+
};
|
445
|
+
function chordNotes(bass, modifier) {
|
446
|
+
var intervals = chordIntervals[modifier];
|
447
|
+
if (!intervals)
|
448
|
+
intervals = chordIntervals.M;
|
449
|
+
bass += 12; // the chord is an octave above the bass note.
|
450
|
+
var notes = [ ];
|
451
|
+
for (var i = 0; i < intervals.length; i++) {
|
452
|
+
notes.push(bass + intervals[i]);
|
453
|
+
}
|
454
|
+
return notes;
|
455
|
+
}
|
456
|
+
|
457
|
+
function writeBoom(boom, beatLength) {
|
458
|
+
// undefined means there is a stop time.
|
459
|
+
if (boom !== undefined)
|
460
|
+
chordTrack.push({cmd: 'start', pitch: boom, volume: 64});
|
461
|
+
chordTrack.push({ cmd: 'move', duration: beatLength/2 });
|
462
|
+
if (boom !== undefined)
|
463
|
+
chordTrack.push({ cmd: 'stop', pitch: boom });
|
464
|
+
chordTrack.push({ cmd: 'move', duration: beatLength/2 });
|
465
|
+
}
|
466
|
+
|
467
|
+
function writeChick(chick, beatLength) {
|
468
|
+
for (var c = 0; c < chick.length; c++)
|
469
|
+
chordTrack.push({cmd: 'start', pitch: chick[c], volume: 48});
|
470
|
+
chordTrack.push({ cmd: 'move', duration: beatLength/2 });
|
471
|
+
for (c = 0; c < chick.length; c++)
|
472
|
+
chordTrack.push({ cmd: 'stop', pitch: chick[c] });
|
473
|
+
chordTrack.push({ cmd: 'move', duration: beatLength/2 });
|
474
|
+
}
|
475
|
+
|
476
|
+
var rhythmPatterns = { "2/2": [ 'boom', 'chick' ],
|
477
|
+
"2/4": [ 'boom', 'chick' ],
|
478
|
+
"3/4": [ 'boom', 'chick', 'chick' ],
|
479
|
+
"4/4": [ 'boom', 'chick', 'boom2', 'chick' ],
|
480
|
+
"6/8": [ 'boom', '', 'chick', 'boom2', '', 'chick' ]
|
481
|
+
};
|
482
|
+
|
483
|
+
function resolveChords() {
|
484
|
+
var num = meter.num;
|
485
|
+
var den = meter.den;
|
486
|
+
var beatLength = 1/den;
|
487
|
+
var pattern = rhythmPatterns[num+'/'+den];
|
488
|
+
var thisMeasureLength = parseInt(num,10)/parseInt(den,10);
|
489
|
+
// See if this is a full measure: unfortunately, with triplets, there isn't an exact match, what with the floating point, so we just see if it is "close".
|
490
|
+
var portionOfAMeasure = Math.abs(thisMeasureLength - barBeat);
|
491
|
+
if (!pattern || portionOfAMeasure > 0.0078125) { // If it is an unsupported meter, or this isn't a full bar, just chick on each beat.
|
492
|
+
pattern = [];
|
493
|
+
var beatsPresent = barBeat / beatLength;
|
494
|
+
for (var p = 0; p < beatsPresent; p++)
|
495
|
+
pattern.push("chick");
|
496
|
+
}
|
497
|
+
|
498
|
+
if (currentChords.length === 0) { // there wasn't a new chord this measure, so use the last chord declared.
|
499
|
+
currentChords.push({ beat: 0, chord: lastChord});
|
500
|
+
}
|
501
|
+
if (currentChords[0].beat !== 0 && lastChord) { // this is the case where there is a chord declared in the measure, but not on its first beat.
|
502
|
+
currentChords.unshift({ beat: 0, chord: lastChord});
|
503
|
+
}
|
504
|
+
if (currentChords.length === 1) {
|
505
|
+
for (var m = 0; m < pattern.length; m++) {
|
506
|
+
switch (pattern[m]) {
|
507
|
+
case 'boom':
|
508
|
+
writeBoom(currentChords[0].chord.boom, beatLength);
|
509
|
+
break;
|
510
|
+
case 'boom2':
|
511
|
+
writeBoom(currentChords[0].chord.boom2, beatLength);
|
512
|
+
break;
|
513
|
+
case 'chick':
|
514
|
+
writeChick(currentChords[0].chord.chick, beatLength);
|
515
|
+
break;
|
516
|
+
case '':
|
517
|
+
chordTrack.push({ cmd: 'move', duration: beatLength });
|
518
|
+
break;
|
519
|
+
}
|
520
|
+
}
|
521
|
+
return;
|
522
|
+
}
|
523
|
+
|
524
|
+
// If we are here it is because more than one chord was declared in the measure, so we have to sort out what chord goes where.
|
525
|
+
|
526
|
+
// First, normalize the chords on beats.
|
527
|
+
var beats = {};
|
528
|
+
for (var i = 0; i < currentChords.length; i++) {
|
529
|
+
var cc = currentChords[i];
|
530
|
+
var beat = Math.floor(cc.beat / beatLength); // now all the beats are integers, there may be
|
531
|
+
beats[''+beat] = cc;
|
532
|
+
}
|
533
|
+
|
534
|
+
// - If there is a chord on the second beat, play a chord for the first beat instead of a bass note.
|
535
|
+
// - Likewise, if there is a chord on the fourth beat of 4/4, play a chord on the third beat instead of a bass note.
|
536
|
+
for (var m2 = 0; m2 < pattern.length; m2++) {
|
537
|
+
var thisChord;
|
538
|
+
if (beats[''+m2])
|
539
|
+
thisChord = beats[''+m2];
|
540
|
+
switch (pattern[m2]) {
|
541
|
+
case 'boom':
|
542
|
+
if (beats[''+(m2+1)]) // If there is not a chord change on the next beat, play a bass note.
|
543
|
+
writeChick(thisChord.chord.chick, beatLength);
|
544
|
+
else
|
545
|
+
writeBoom(thisChord.chord.boom, beatLength);
|
546
|
+
break;
|
547
|
+
case 'boom2':
|
548
|
+
if (beats[''+(m2+1)])
|
549
|
+
writeChick(thisChord.chord.chick, beatLength);
|
550
|
+
else
|
551
|
+
writeBoom(thisChord.chord.boom2, beatLength);
|
552
|
+
break;
|
553
|
+
case 'chick':
|
554
|
+
writeChick(thisChord.chord.chick, beatLength);
|
555
|
+
break;
|
556
|
+
case '':
|
557
|
+
if (beats[''+m2]) // If there is an explicit chord on this beat, play it.
|
558
|
+
writeChick(thisChord.chord.chick, beatLength);
|
559
|
+
else
|
560
|
+
chordTrack.push({cmd: 'move', duration: beatLength});
|
561
|
+
break;
|
562
|
+
}
|
563
|
+
}
|
564
|
+
}
|
565
|
+
})();
|