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
@@ -0,0 +1,426 @@
|
|
1
|
+
// abc_midi_renderer.js: Create the actual format for the midi.
|
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
|
+
function setAttributes(elm, attrs) {
|
26
|
+
for (var attr in attrs)
|
27
|
+
if (attrs.hasOwnProperty(attr))
|
28
|
+
elm.setAttribute(attr, attrs[attr]);
|
29
|
+
return elm;
|
30
|
+
}
|
31
|
+
|
32
|
+
var MIDIPlugin;
|
33
|
+
//TODO-PER: put this back in when the MIDIPlugin works again.
|
34
|
+
//window.oldunload = window.onbeforeunload;
|
35
|
+
//window.onbeforeunload = function() {
|
36
|
+
// if (window.oldunload)
|
37
|
+
// window.oldunload();
|
38
|
+
// if (typeof(MIDIPlugin) !== "undefined" && MIDIPlugin) { // PER: take care of crash in IE 8
|
39
|
+
// MIDIPlugin.closePlugin();
|
40
|
+
// }
|
41
|
+
//};
|
42
|
+
|
43
|
+
|
44
|
+
function MidiProxy(javamidi, qtmidi) {
|
45
|
+
this.javamidi = javamidi;
|
46
|
+
this.qtmidi = qtmidi;
|
47
|
+
}
|
48
|
+
|
49
|
+
MidiProxy.prototype.setTempo = function(qpm) {
|
50
|
+
this.javamidi.setTempo(qpm);
|
51
|
+
this.qtmidi.setTempo(qpm);
|
52
|
+
};
|
53
|
+
|
54
|
+
MidiProxy.prototype.startTrack = function() {
|
55
|
+
this.javamidi.startTrack();
|
56
|
+
this.qtmidi.startTrack();
|
57
|
+
};
|
58
|
+
|
59
|
+
MidiProxy.prototype.endTrack = function() {
|
60
|
+
this.javamidi.endTrack();
|
61
|
+
this.qtmidi.endTrack();
|
62
|
+
};
|
63
|
+
|
64
|
+
MidiProxy.prototype.setInstrument = function(number) {
|
65
|
+
this.javamidi.setInstrument(number);
|
66
|
+
this.qtmidi.setInstrument(number);
|
67
|
+
};
|
68
|
+
|
69
|
+
MidiProxy.prototype.startNote = function(pitch, loudness, abcelem) {
|
70
|
+
this.javamidi.startNote(pitch, loudness, abcelem);
|
71
|
+
this.qtmidi.startNote(pitch, loudness, abcelem);
|
72
|
+
};
|
73
|
+
|
74
|
+
MidiProxy.prototype.endNote = function(pitch, length) {
|
75
|
+
this.javamidi.endNote(pitch, length);
|
76
|
+
this.qtmidi.endNote(pitch, length);
|
77
|
+
};
|
78
|
+
|
79
|
+
MidiProxy.prototype.addRest = function(length) {
|
80
|
+
this.javamidi.addRest(length);
|
81
|
+
this.qtmidi.addRest(length);
|
82
|
+
};
|
83
|
+
|
84
|
+
MidiProxy.prototype.embed = function(parent) {
|
85
|
+
this.javamidi.embed(parent);
|
86
|
+
this.qtmidi.embed(parent, true);
|
87
|
+
};
|
88
|
+
|
89
|
+
function JavaMidi(midiwriter) {
|
90
|
+
this.playlist = []; // contains {time:t,funct:f} pairs
|
91
|
+
this.trackcount = 0;
|
92
|
+
this.timecount = 0;
|
93
|
+
this.tempo = 60;
|
94
|
+
this.midiapi = MIDIPlugin;
|
95
|
+
this.midiwriter = midiwriter;
|
96
|
+
this.noteOnAndChannel = "%90";
|
97
|
+
}
|
98
|
+
|
99
|
+
JavaMidi.prototype.setTempo = function(qpm) {
|
100
|
+
this.tempo = qpm;
|
101
|
+
};
|
102
|
+
|
103
|
+
JavaMidi.prototype.startTrack = function() {
|
104
|
+
this.silencelength = 0;
|
105
|
+
this.trackcount++;
|
106
|
+
this.timecount = 0;
|
107
|
+
this.playlistpos = 0;
|
108
|
+
this.first = true;
|
109
|
+
if (this.instrument) {
|
110
|
+
this.setInstrument(this.instrument);
|
111
|
+
}
|
112
|
+
if (this.channel) {
|
113
|
+
this.setChannel(this.channel);
|
114
|
+
}
|
115
|
+
};
|
116
|
+
|
117
|
+
JavaMidi.prototype.endTrack = function() {
|
118
|
+
// need to do anything?
|
119
|
+
};
|
120
|
+
|
121
|
+
JavaMidi.prototype.setInstrument = function(number) {
|
122
|
+
this.instrument = number;
|
123
|
+
this.midiapi.setInstrument(number);
|
124
|
+
//TODO push this into the playlist?
|
125
|
+
};
|
126
|
+
|
127
|
+
JavaMidi.prototype.setChannel = function(number) {
|
128
|
+
this.channel = number;
|
129
|
+
this.midiapi.setChannel(number);
|
130
|
+
};
|
131
|
+
|
132
|
+
JavaMidi.prototype.updatePos = function() {
|
133
|
+
while (this.playlist[this.playlistpos] &&
|
134
|
+
this.playlist[this.playlistpos].time < this.timecount) {
|
135
|
+
this.playlistpos++;
|
136
|
+
}
|
137
|
+
};
|
138
|
+
|
139
|
+
JavaMidi.prototype.startNote = function(pitch, loudness, abcelem) {
|
140
|
+
this.timecount += this.silencelength;
|
141
|
+
this.silencelength = 0;
|
142
|
+
if (this.first) {
|
143
|
+
//nothing special if first?
|
144
|
+
}
|
145
|
+
this.updatePos();
|
146
|
+
var self = this;
|
147
|
+
this.playlist.splice(this.playlistpos, 0, {
|
148
|
+
time: this.timecount,
|
149
|
+
funct: function() {
|
150
|
+
self.midiapi.playNote(pitch);
|
151
|
+
self.midiwriter.notifySelect(abcelem);
|
152
|
+
}
|
153
|
+
});
|
154
|
+
};
|
155
|
+
|
156
|
+
JavaMidi.prototype.endNote = function(pitch, length) {
|
157
|
+
this.timecount += length;
|
158
|
+
this.updatePos();
|
159
|
+
var self = this;
|
160
|
+
this.playlist.splice(this.playlistpos, 0, {
|
161
|
+
time: this.timecount,
|
162
|
+
funct: function() {
|
163
|
+
self.midiapi.stopNote(pitch);
|
164
|
+
}
|
165
|
+
});
|
166
|
+
};
|
167
|
+
|
168
|
+
JavaMidi.prototype.addRest = function(length) {
|
169
|
+
this.silencelength += length;
|
170
|
+
};
|
171
|
+
|
172
|
+
JavaMidi.prototype.embed = function(parent) {
|
173
|
+
|
174
|
+
|
175
|
+
this.playlink = setAttributes(document.createElement('a'), {
|
176
|
+
style: "border:1px solid black; margin:3px;"
|
177
|
+
});
|
178
|
+
this.playlink.innerHTML = "play";
|
179
|
+
var self = this;
|
180
|
+
this.playlink.onmousedown = function() {
|
181
|
+
if (self.playing) {
|
182
|
+
this.innerHTML = "play";
|
183
|
+
self.pausePlay();
|
184
|
+
} else {
|
185
|
+
this.innerHTML = "pause";
|
186
|
+
self.startPlay();
|
187
|
+
}
|
188
|
+
};
|
189
|
+
parent.appendChild(this.playlink);
|
190
|
+
|
191
|
+
var stoplink = setAttributes(document.createElement('a'), {
|
192
|
+
style: "border:1px solid black; margin:3px;"
|
193
|
+
});
|
194
|
+
stoplink.innerHTML = "stop";
|
195
|
+
//var self = this;
|
196
|
+
stoplink.onmousedown = function() {
|
197
|
+
self.stopPlay();
|
198
|
+
};
|
199
|
+
parent.appendChild(stoplink);
|
200
|
+
this.i = 0;
|
201
|
+
this.currenttime = 0;
|
202
|
+
this.playing = false;
|
203
|
+
};
|
204
|
+
|
205
|
+
JavaMidi.prototype.stopPlay = function() {
|
206
|
+
this.i = 0;
|
207
|
+
this.currenttime = 0;
|
208
|
+
this.pausePlay();
|
209
|
+
this.playlink.innerHTML = "play";
|
210
|
+
};
|
211
|
+
|
212
|
+
JavaMidi.prototype.startPlay = function() {
|
213
|
+
this.playing = true;
|
214
|
+
var self = this;
|
215
|
+
// repeat every 16th note TODO see the min in the piece
|
216
|
+
this.ticksperinterval = 480 / 4;
|
217
|
+
this.doPlay();
|
218
|
+
this.playinterval = window.setInterval(function() {self.doPlay(); },
|
219
|
+
(60000 / (this.tempo * 4)));
|
220
|
+
};
|
221
|
+
|
222
|
+
JavaMidi.prototype.pausePlay = function() {
|
223
|
+
this.playing = false;
|
224
|
+
window.clearInterval(this.playinterval);
|
225
|
+
this.midiapi.stopAllNotes();
|
226
|
+
};
|
227
|
+
|
228
|
+
JavaMidi.prototype.doPlay = function() {
|
229
|
+
while (this.playlist[this.i] &&
|
230
|
+
this.playlist[this.i].time <= this.currenttime) {
|
231
|
+
this.playlist[this.i].funct();
|
232
|
+
this.i++;
|
233
|
+
}
|
234
|
+
if (this.playlist[this.i]) {
|
235
|
+
this.currenttime += this.ticksperinterval;
|
236
|
+
} else {
|
237
|
+
this.stopPlay();
|
238
|
+
}
|
239
|
+
};
|
240
|
+
|
241
|
+
function Midi() {
|
242
|
+
this.trackstrings = "";
|
243
|
+
this.trackcount = 0;
|
244
|
+
this.noteOnAndChannel = "%90";
|
245
|
+
}
|
246
|
+
|
247
|
+
Midi.prototype.setTempo = function(qpm) {
|
248
|
+
//console.log("setTempo",qpm);
|
249
|
+
if (this.trackcount === 0) {
|
250
|
+
this.startTrack();
|
251
|
+
this.track += "%00%FF%51%03" + toHex(Math.round(60000000 / qpm), 6);
|
252
|
+
this.endTrack();
|
253
|
+
}
|
254
|
+
};
|
255
|
+
|
256
|
+
Midi.prototype.setGlobalInfo = function(qpm, name) {
|
257
|
+
//console.log("setGlobalInfo",qpm, key, time, name);
|
258
|
+
if (this.trackcount === 0) {
|
259
|
+
this.startTrack();
|
260
|
+
this.track += "%00%FF%51%03" + toHex(Math.round(60000000 / qpm), 6);
|
261
|
+
// TODO-PER: we could also store the key and time signatures, something like:
|
262
|
+
//00 FF 5902 03 00 - key signature
|
263
|
+
//00 FF 5804 04 02 30 08 - time signature
|
264
|
+
if (name) {
|
265
|
+
this.track += "%00%FF%03" + toHex(name.length, 2);
|
266
|
+
for (var i = 0; i < name.length; i++)
|
267
|
+
this.track += toHex(name.charCodeAt(i), 2);
|
268
|
+
}
|
269
|
+
this.endTrack();
|
270
|
+
}
|
271
|
+
};
|
272
|
+
|
273
|
+
Midi.prototype.startTrack = function() {
|
274
|
+
//console.log("startTrack");
|
275
|
+
this.track = "";
|
276
|
+
this.silencelength = 0;
|
277
|
+
this.trackcount++;
|
278
|
+
this.first = true;
|
279
|
+
if (this.instrument) {
|
280
|
+
this.setInstrument(this.instrument);
|
281
|
+
}
|
282
|
+
};
|
283
|
+
|
284
|
+
Midi.prototype.endTrack = function() {
|
285
|
+
//console.log("endTrack");
|
286
|
+
var tracklength = toHex(this.track.length / 3 + 4, 8);
|
287
|
+
this.track = "MTrk" + tracklength + // track header
|
288
|
+
this.track +
|
289
|
+
'%00%FF%2F%00'; // track end
|
290
|
+
this.trackstrings += this.track;
|
291
|
+
};
|
292
|
+
|
293
|
+
Midi.prototype.setInstrument = function(number) {
|
294
|
+
//console.log("setInstrument", number);
|
295
|
+
if (this.track)
|
296
|
+
this.track = "%00%C0" + toHex(number, 2) + this.track;
|
297
|
+
else
|
298
|
+
this.track = "%00%C0" + toHex(number, 2);
|
299
|
+
this.instrument = number;
|
300
|
+
};
|
301
|
+
|
302
|
+
Midi.prototype.setChannel = function(number) {
|
303
|
+
//console.log("setChannel", number);
|
304
|
+
this.channel = number - 1;
|
305
|
+
this.noteOnAndChannel = "%9" + this.channel.toString(16);
|
306
|
+
};
|
307
|
+
|
308
|
+
Midi.prototype.startNote = function(pitch, loudness) {
|
309
|
+
//console.log("startNote", pitch, loudness);
|
310
|
+
this.track += toDurationHex(this.silencelength); // only need to shift by amount of silence (if there is any)
|
311
|
+
this.silencelength = 0;
|
312
|
+
if (this.first) {
|
313
|
+
this.first = false;
|
314
|
+
this.track += this.noteOnAndChannel;
|
315
|
+
}
|
316
|
+
this.track += "%" + pitch.toString(16) + toHex(loudness, 2); //note
|
317
|
+
};
|
318
|
+
|
319
|
+
Midi.prototype.endNote = function(pitch, length) {
|
320
|
+
//console.log("endNote", pitch, length);
|
321
|
+
this.track += toDurationHex(this.silencelength+length); // only need to shift by amount of silence (if there is any)
|
322
|
+
this.silencelength = 0;
|
323
|
+
// this.track += toDurationHex(length); //duration
|
324
|
+
this.track += "%" + pitch.toString(16) + "%00";//end note
|
325
|
+
};
|
326
|
+
|
327
|
+
Midi.prototype.addRest = function(length) {
|
328
|
+
//console.log("addRest", length);
|
329
|
+
this.silencelength += length;
|
330
|
+
};
|
331
|
+
|
332
|
+
Midi.prototype.getData = function() {
|
333
|
+
return "data:audio/midi," +
|
334
|
+
"MThd%00%00%00%06%00%01" + toHex(this.trackcount, 4) + "%01%e0" + // header
|
335
|
+
this.trackstrings;
|
336
|
+
};
|
337
|
+
|
338
|
+
Midi.prototype.embed = function(parent, noplayer) {
|
339
|
+
|
340
|
+
var data = this.getData();
|
341
|
+
|
342
|
+
var link = setAttributes(document.createElement('a'), {
|
343
|
+
href: data
|
344
|
+
});
|
345
|
+
link.innerHTML = "download midi";
|
346
|
+
parent.insertBefore(link, parent.firstChild);
|
347
|
+
|
348
|
+
if (noplayer) return;
|
349
|
+
|
350
|
+
var embed = setAttributes(document.createElement('embed'), {
|
351
|
+
src: data,
|
352
|
+
type: 'video/quicktime',
|
353
|
+
controller: 'true',
|
354
|
+
autoplay: 'false',
|
355
|
+
loop: 'false',
|
356
|
+
enablejavascript: 'true',
|
357
|
+
style: 'display:block; height: 20px;'
|
358
|
+
});
|
359
|
+
parent.insertBefore(embed, parent.firstChild);
|
360
|
+
};
|
361
|
+
|
362
|
+
// s is assumed to be of even length
|
363
|
+
function encodeHex(s) {
|
364
|
+
var ret = "";
|
365
|
+
for (var i = 0; i < s.length; i += 2) {
|
366
|
+
ret += "%";
|
367
|
+
ret += s.substr(i, 2);
|
368
|
+
}
|
369
|
+
return ret;
|
370
|
+
}
|
371
|
+
|
372
|
+
function toHex(n, padding) {
|
373
|
+
var s = n.toString(16);
|
374
|
+
while (s.length < padding) {
|
375
|
+
s = "0" + s;
|
376
|
+
}
|
377
|
+
return encodeHex(s);
|
378
|
+
}
|
379
|
+
|
380
|
+
function toDurationHex(n) {
|
381
|
+
var res = 0;
|
382
|
+
var a = [];
|
383
|
+
|
384
|
+
// cut up into 7 bit chunks;
|
385
|
+
while (n !== 0) {
|
386
|
+
a.push(n & 0x7F);
|
387
|
+
n = n >> 7;
|
388
|
+
}
|
389
|
+
|
390
|
+
// join the 7 bit chunks together, all but last chunk get leading 1
|
391
|
+
for (var i = a.length - 1; i >= 0; i--) {
|
392
|
+
res = res << 8;
|
393
|
+
var bits = a[i];
|
394
|
+
if (i !== 0) {
|
395
|
+
bits = bits | 0x80;
|
396
|
+
}
|
397
|
+
res = res | bits;
|
398
|
+
}
|
399
|
+
|
400
|
+
var padding = res.toString(16).length;
|
401
|
+
padding += padding % 2;
|
402
|
+
|
403
|
+
return toHex(res, padding);
|
404
|
+
}
|
405
|
+
|
406
|
+
window.ABCJS.midi.rendererFactory = function(isJava) {
|
407
|
+
return (isJava) ? new MidiProxy(new JavaMidi(this), new Midi()) : new Midi();
|
408
|
+
};
|
409
|
+
|
410
|
+
window.ABCJS.midi.initializeJava = function() {
|
411
|
+
MIDIPlugin = document.MIDIPlugin;
|
412
|
+
setTimeout(function() { // run on next event loop (once MIDIPlugin is loaded)
|
413
|
+
try { // activate MIDIPlugin
|
414
|
+
MIDIPlugin.openPlugin();
|
415
|
+
|
416
|
+
} catch(e) { // plugin not supported (download externals)
|
417
|
+
var a = document.createElement("a");
|
418
|
+
a.href = "http://java.sun.com/products/java-media/sound/soundbanks.html";
|
419
|
+
a.target = "_blank";
|
420
|
+
a.appendChild(document.createTextNode("Download Soundbank"));
|
421
|
+
parent.appendChild(a);
|
422
|
+
}
|
423
|
+
}, 0);
|
424
|
+
};
|
425
|
+
|
426
|
+
})();
|
@@ -0,0 +1,207 @@
|
|
1
|
+
// abc_midi_sequencer.js: Turn parsed abc into a linear series of events.
|
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
|
+
// The abc is provided to us line by line. It might have repeats in it. We want to re arrange the elements to
|
27
|
+
// be an array of voices with all the repeats embedded, and no lines. Then it is trivial to go through the events
|
28
|
+
// one at a time and turn it into midi.
|
29
|
+
|
30
|
+
window.ABCJS.midi.sequence = function(abctune, options) {
|
31
|
+
// Global options
|
32
|
+
options = options || {};
|
33
|
+
var qpm = options.qpm || 180; // The tempo if there isn't a tempo specified.
|
34
|
+
var program = options.program || 2; // The program if there isn't a program specified.
|
35
|
+
var transpose = options.transpose || 0;
|
36
|
+
var channel = options.channel || 0;
|
37
|
+
|
38
|
+
var bagpipes = abctune.formatting.bagpipes; // If it is bagpipes, then the gracenotes are played on top of the main note.
|
39
|
+
if (bagpipes)
|
40
|
+
program = 71;
|
41
|
+
|
42
|
+
// %%MIDI formatting options
|
43
|
+
if (abctune.formatting.midi) {
|
44
|
+
if (abctune.formatting.midi.transpose)
|
45
|
+
transpose = abctune.formatting.midi.transpose;
|
46
|
+
|
47
|
+
// PER: changed format of the global midi commands from the parser. Using the new definition here.
|
48
|
+
if (abctune.formatting.midi.program && abctune.formatting.midi.program.program)
|
49
|
+
program = abctune.formatting.midi.program.program;
|
50
|
+
if (abctune.formatting.midi.channel)
|
51
|
+
channel = abctune.formatting.midi.channel;
|
52
|
+
}
|
53
|
+
|
54
|
+
// Specified options in abc string.
|
55
|
+
|
56
|
+
if (abctune.metaText.tempo)
|
57
|
+
qpm = interpretTempo(abctune.metaText.tempo);
|
58
|
+
|
59
|
+
var startVoice = [];
|
60
|
+
if (bagpipes)
|
61
|
+
startVoice.push({ el_type: 'bagpipes' });
|
62
|
+
startVoice.push({ el_type: 'instrument', program: program });
|
63
|
+
if (channel)
|
64
|
+
startVoice.push({ el_type: 'channel', channel: channel });
|
65
|
+
if (transpose)
|
66
|
+
startVoice.push({ el_type: 'transpose', transpose: transpose });
|
67
|
+
startVoice.push({ el_type: 'tempo', qpm: qpm });
|
68
|
+
|
69
|
+
// the relevant part of the input structure is:
|
70
|
+
// abctune
|
71
|
+
// array lines
|
72
|
+
// array staff
|
73
|
+
// object key
|
74
|
+
// object meter
|
75
|
+
// array voices
|
76
|
+
// array abcelem
|
77
|
+
|
78
|
+
// visit each voice completely in turn
|
79
|
+
var voices = [];
|
80
|
+
var startRepeatPlaceholder = []; // There is a place holder for each voice.
|
81
|
+
var skipEndingPlaceholder = []; // This is the place where the first ending starts.
|
82
|
+
for (var i = 0; i < abctune.lines.length; i++) {
|
83
|
+
// For each group of staff lines in the tune.
|
84
|
+
var line = abctune.lines[i];
|
85
|
+
if (line.staff) {
|
86
|
+
var staves = line.staff;
|
87
|
+
var voiceNumber = 0;
|
88
|
+
for (var j = 0; j < staves.length; j++) {
|
89
|
+
var staff = staves[j];
|
90
|
+
// For each staff line
|
91
|
+
for (var k = 0; k < staff.voices.length; k++) {
|
92
|
+
// For each voice in a staff line
|
93
|
+
var voice = staff.voices[k];
|
94
|
+
if (!voices[voiceNumber])
|
95
|
+
voices[voiceNumber] = [].concat(startVoice);
|
96
|
+
if (staff.key) {
|
97
|
+
if (staff.key.root === 'HP')
|
98
|
+
voices[voiceNumber].push({el_type: 'key', accidentals: [{acc: 'natural', note: 'g'}, {acc: 'sharp', note: 'f'}, {acc: 'sharp', note: 'c'}]});
|
99
|
+
else
|
100
|
+
voices[voiceNumber].push({el_type: 'key', accidentals: staff.key.accidentals });
|
101
|
+
}
|
102
|
+
if (staff.meter) {
|
103
|
+
voices[voiceNumber].push(interpretMeter(staff.meter));
|
104
|
+
}
|
105
|
+
if (staff.clef && staff.clef.transpose) {
|
106
|
+
staff.clef.el_type = 'clef';
|
107
|
+
voices[voiceNumber].push({ el_type: 'transpose', transpose: staff.clef.transpose });
|
108
|
+
}
|
109
|
+
var noteEventsInBar = 0;
|
110
|
+
for (var v = 0; v < voice.length; v++) {
|
111
|
+
// For each element in a voice
|
112
|
+
var elem = voice[v];
|
113
|
+
switch (elem.el_type) {
|
114
|
+
case "note":
|
115
|
+
// regular items are just pushed.
|
116
|
+
if (!elem.rest || elem.rest.type !== 'spacer') {
|
117
|
+
voices[voiceNumber].push(elem);
|
118
|
+
noteEventsInBar++;
|
119
|
+
}
|
120
|
+
break;
|
121
|
+
case "key":
|
122
|
+
if (elem.root === 'HP')
|
123
|
+
voices[voiceNumber].push({el_type: 'key', accidentals: [{acc: 'natural', note: 'g'}, {acc: 'sharp', note: 'f'}, {acc: 'sharp', note: 'c'}]});
|
124
|
+
else
|
125
|
+
voices[voiceNumber].push({el_type: 'key', accidentals: elem.accidentals });
|
126
|
+
break;
|
127
|
+
case "meter":
|
128
|
+
voices[voiceNumber].push(interpretMeter(elem));
|
129
|
+
break;
|
130
|
+
case "clef": // need to keep this to catch the "transpose" element.
|
131
|
+
if (elem.transpose)
|
132
|
+
voices[voiceNumber].push({ el_type: 'transpose', transpose: elem.transpose });
|
133
|
+
break;
|
134
|
+
case "tempo":
|
135
|
+
qpm = interpretTempo(elem);
|
136
|
+
voices[voiceNumber].push({ el_type: 'tempo', qpm: qpm });
|
137
|
+
break;
|
138
|
+
case "bar":
|
139
|
+
if (noteEventsInBar > 0) // don't add two bars in a row.
|
140
|
+
voices[voiceNumber].push({ el_type: 'bar' }); // We need the bar marking to reset the accidentals.
|
141
|
+
noteEventsInBar = 0;
|
142
|
+
// figure out repeats and endings --
|
143
|
+
// The important part is where there is a start repeat, and end repeat, or a first ending.
|
144
|
+
var endRepeat = (elem.type === "bar_right_repeat" || elem.type === "bar_dbl_repeat");
|
145
|
+
var startEnding = (elem.startEnding) ? true : false;
|
146
|
+
var startRepeat = (elem.type === "bar_left_repeat" || elem.type === "bar_dbl_repeat" || elem.type === "bar_thick_thin" || elem.type === "bar_thin_thick" || elem.type === "bar_thin_thin" || elem.type === "bar_right_repeat");
|
147
|
+
if (endRepeat) {
|
148
|
+
var s = startRepeatPlaceholder[voiceNumber];
|
149
|
+
if (!s) s = 0; // If there wasn't a left repeat, then we repeat from the beginning.
|
150
|
+
var e = skipEndingPlaceholder[voiceNumber];
|
151
|
+
if (!e) e = voices[voiceNumber].length; // If there wasn't a first ending marker, then we copy everything.
|
152
|
+
voices[voiceNumber] = voices[voiceNumber].concat(voices[voiceNumber].slice(s, e));
|
153
|
+
}
|
154
|
+
if (startEnding)
|
155
|
+
skipEndingPlaceholder[voiceNumber] = voices[voiceNumber].length;
|
156
|
+
if (startRepeat)
|
157
|
+
startRepeatPlaceholder[voiceNumber] = voices[voiceNumber].length;
|
158
|
+
break;
|
159
|
+
case 'style':
|
160
|
+
// TODO-PER: If this is set to rhythm heads, then it should use the percussion channel.
|
161
|
+
break;
|
162
|
+
case 'part':
|
163
|
+
// TODO-PER: If there is a part section in the header, then this should probably affect the repeats.
|
164
|
+
break;
|
165
|
+
case 'stem':
|
166
|
+
case 'scale':
|
167
|
+
// These elements don't affect sound
|
168
|
+
break;
|
169
|
+
default:
|
170
|
+
console.log("MIDI: element type " + elem.el_type + " not handled.");
|
171
|
+
}
|
172
|
+
}
|
173
|
+
voiceNumber++;
|
174
|
+
}
|
175
|
+
}
|
176
|
+
}
|
177
|
+
}
|
178
|
+
return voices;
|
179
|
+
};
|
180
|
+
|
181
|
+
function interpretTempo(element) {
|
182
|
+
var duration = 1/4;
|
183
|
+
if (element.duration) {
|
184
|
+
duration = element.duration[0];
|
185
|
+
}
|
186
|
+
var bpm = 60;
|
187
|
+
if (element.bpm) {
|
188
|
+
bpm = element.bpm;
|
189
|
+
}
|
190
|
+
return bpm*duration*4;
|
191
|
+
}
|
192
|
+
|
193
|
+
function interpretMeter(element) {
|
194
|
+
switch (element.type) {
|
195
|
+
case "common_time":
|
196
|
+
return { el_type: 'meter', num: 4, den: 4 };
|
197
|
+
case "cut_time":
|
198
|
+
return { el_type: 'meter', num: 2, den: 2 };
|
199
|
+
case "specified":
|
200
|
+
// TODO-PER: only taking the first meter, so the complex meters are not handled.
|
201
|
+
return { el_type: 'meter', num: element.value[0].num, den: element.value[0].den };
|
202
|
+
default:
|
203
|
+
// This should never happen.
|
204
|
+
return { el_type: 'meter' };
|
205
|
+
}
|
206
|
+
}
|
207
|
+
})();
|