abcjs-rails 2.1 → 2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
})();
|