abcjs-rails 1.1.0 → 1.1.1

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.
@@ -0,0 +1,414 @@
1
+ // abc_editor.js
2
+ // window.ABCJS.Editor is the interface class for the area that contains the ABC text. It is responsible for
3
+ // holding the text of the tune and calling the parser and the rendering engines.
4
+ //
5
+ // EditArea is an example of using a textarea as the control that is shown to the user. As long as
6
+ // the same interface is used, window.ABCJS.Editor can use a different type of object.
7
+ //
8
+ // EditArea:
9
+ // - constructor(textareaid)
10
+ // This contains the id of a textarea control that will be used.
11
+ // - addSelectionListener(listener)
12
+ // A callback class that contains the entry point fireSelectionChanged()
13
+ // - addChangeListener(listener)
14
+ // A callback class that contains the entry point fireChanged()
15
+ // - getSelection()
16
+ // returns the object { start: , end: } with the current selection in characters
17
+ // - setSelection(start, end)
18
+ // start and end are the character positions that should be selected.
19
+ // - getString()
20
+ // returns the ABC text that is currently displayed.
21
+ // - setString(str)
22
+ // sets the ABC text that is currently displayed, and resets the initialText variable
23
+ // - getElem()
24
+ // returns the textarea element
25
+ // - string initialText
26
+ // Contains the starting text. This can be compared against the current text to see if anything changed.
27
+ //
28
+
29
+ /*global document, window, clearTimeout, setTimeout */
30
+ /*global Raphael */
31
+
32
+ if (!window.ABCJS)
33
+ window.ABCJS = {};
34
+
35
+ if (!window.ABCJS.edit)
36
+ window.ABCJS.edit = {};
37
+
38
+ window.ABCJS.edit.EditArea = function(textareaid) {
39
+ this.textarea = document.getElementById(textareaid);
40
+ this.initialText = this.textarea.value;
41
+ this.isDragging = false;
42
+ }
43
+
44
+ window.ABCJS.edit.EditArea.prototype.addSelectionListener = function(listener) {
45
+ this.textarea.onmousemove = function(ev) {
46
+ if (this.isDragging)
47
+ listener.fireSelectionChanged();
48
+ };
49
+ };
50
+
51
+ window.ABCJS.edit.EditArea.prototype.addChangeListener = function(listener) {
52
+ this.changelistener = listener;
53
+ this.textarea.onkeyup = function() {
54
+ listener.fireChanged();
55
+ };
56
+ this.textarea.onmousedown = function() {
57
+ this.isDragging = true;
58
+ listener.fireSelectionChanged();
59
+ };
60
+ this.textarea.onmouseup = function() {
61
+ this.isDragging = false;
62
+ listener.fireChanged();
63
+ };
64
+ this.textarea.onchange = function() {
65
+ listener.fireChanged();
66
+ };
67
+ };
68
+
69
+ //TODO won't work under IE?
70
+ window.ABCJS.edit.EditArea.prototype.getSelection = function() {
71
+ return {start: this.textarea.selectionStart, end: this.textarea.selectionEnd};
72
+ };
73
+
74
+ window.ABCJS.edit.EditArea.prototype.setSelection = function(start, end) {
75
+ if(this.textarea.setSelectionRange)
76
+ this.textarea.setSelectionRange(start, end);
77
+ else if(this.textarea.createTextRange) {
78
+ // For IE8
79
+ var e = this.textarea.createTextRange();
80
+ e.collapse(true);
81
+ e.moveEnd('character', end);
82
+ e.moveStart('character', start);
83
+ e.select();
84
+ }
85
+ this.textarea.focus();
86
+ };
87
+
88
+ window.ABCJS.edit.EditArea.prototype.getString = function() {
89
+ return this.textarea.value;
90
+ };
91
+
92
+ window.ABCJS.edit.EditArea.prototype.setString = function(str) {
93
+ this.textarea.value = str;
94
+ this.initialText = this.getString();
95
+ if (this.changelistener) {
96
+ this.changelistener.fireChanged();
97
+ }
98
+ };
99
+
100
+ window.ABCJS.edit.EditArea.prototype.getElem = function() {
101
+ return this.textarea;
102
+ };
103
+
104
+ //
105
+ // window.ABCJS.Editor:
106
+ //
107
+ // constructor(editarea, params)
108
+ // if editarea is a string, then it is an HTML id of a textarea control.
109
+ // Otherwise, it should be an instantiation of an object that expresses the EditArea interface.
110
+ //
111
+ // params is a hash of:
112
+ // canvas_id: or paper_id: HTML id to draw in. If not present, then the drawing happens just below the editor.
113
+ // generate_midi: if present, then midi is generated.
114
+ // midi_id: if present, the HTML id to place the midi control. Otherwise it is placed in the same div as the paper.
115
+ // generate_warnings: if present, then parser warnings are displayed on the page.
116
+ // warnings_id: if present, the HTML id to place the warnings. Otherwise they are placed in the same div as the paper.
117
+ // onchange: if present, the callback function to call whenever there has been a change.
118
+ // gui: if present, the paper can send changes back to the editor (presumably because the user changed something directly.)
119
+ // parser_options: options to send to the parser engine.
120
+ // midi_options: options to send to the midi engine.
121
+ // render_options: options to send to the render engine.
122
+ // indicate_changed: the dirty flag is set if this is true.
123
+ //
124
+ // - setReadOnly(bool)
125
+ // adds or removes the class abc_textarea_readonly, and adds or removes the attribute readonly=yes
126
+ // - setDirtyStyle(bool)
127
+ // adds or removes the class abc_textarea_dirty
128
+ // - renderTune(abc, parserparams, div)
129
+ // Immediately renders the tune. (Useful for creating the SVG output behind the scenes, if div is hidden)
130
+ // string abc: the ABC text
131
+ // parserparams: params to send to the parser
132
+ // div: the HTML id to render to.
133
+ // - modelChanged()
134
+ // Called when the model has been changed to trigger re-rendering
135
+ // - parseABC()
136
+ // Called internally by fireChanged()
137
+ // returns true if there has been a change since last call.
138
+ // - updateSelection()
139
+ // Called when the user has changed the selection. This calls the printer to show the selection.
140
+ // - fireSelectionChanged()
141
+ // Called by the textarea object when the user has changed the selection.
142
+ // - paramChanged(printerparams)
143
+ // Called to signal that the printer params have changed, so re-rendering should occur.
144
+ // - fireChanged()
145
+ // Called by the textarea object when the user has changed something.
146
+ // - setNotDirty()
147
+ // Called by the client app to reset the dirty flag
148
+ // - isDirty()
149
+ // Returns true or false, whether the textarea contains the same text that it started with.
150
+ // - highlight(abcelem)
151
+ // Called by the printer to highlight an area.
152
+ // - pause(bool)
153
+ // Stops the automatic rendering when the user is typing.
154
+ //
155
+
156
+ window.ABCJS.Editor = function(editarea, params) {
157
+ if (params.indicate_changed)
158
+ this.indicate_changed = true;
159
+ if (typeof editarea === "string") {
160
+ this.editarea = new window.ABCJS.edit.EditArea(editarea);
161
+ } else {
162
+ this.editarea = editarea;
163
+ }
164
+ this.editarea.addSelectionListener(this);
165
+ this.editarea.addChangeListener(this);
166
+
167
+ if (params.canvas_id) {
168
+ this.div = document.getElementById(params.canvas_id);
169
+ } else if (params.paper_id) {
170
+ this.div = document.getElementById(params.paper_id);
171
+ } else {
172
+ this.div = document.createElement("DIV");
173
+ this.editarea.getElem().parentNode.insertBefore(this.div, this.editarea.getElem());
174
+ }
175
+
176
+ if (params.generate_midi || params.midi_id) {
177
+ if (params.midi_id) {
178
+ this.mididiv = document.getElementById(params.midi_id);
179
+ } else {
180
+ this.mididiv = this.div;
181
+ }
182
+ }
183
+
184
+ if (params.generate_warnings || params.warnings_id) {
185
+ if (params.warnings_id) {
186
+ this.warningsdiv = document.getElementById(params.warnings_id);
187
+ } else {
188
+ this.warningsdiv = this.div;
189
+ }
190
+ }
191
+
192
+ this.parserparams = params.parser_options || {};
193
+ this.midiparams = params.midi_options || {};
194
+ this.onchangeCallback = params.onchange;
195
+
196
+ this.printerparams = params.render_options || {};
197
+
198
+ if (params.gui) {
199
+ this.target = document.getElementById(editarea);
200
+ this.printerparams.editable = true;
201
+ }
202
+ this.oldt = "";
203
+ this.bReentry = false;
204
+ this.parseABC();
205
+ this.modelChanged();
206
+
207
+ this.addClassName = function(element, className) {
208
+ var hasClassName = function(element, className) {
209
+ var elementClassName = element.className;
210
+ return (elementClassName.length > 0 && (elementClassName === className ||
211
+ new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
212
+ };
213
+
214
+ if (!hasClassName(element, className))
215
+ element.className += (element.className ? ' ' : '') + className;
216
+ return element;
217
+ };
218
+
219
+ this.removeClassName = function(element, className) {
220
+ element.className = window.ABCJS.parse.strip(element.className.replace(
221
+ new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' '));
222
+ return element;
223
+ };
224
+
225
+ this.setReadOnly = function(readOnly) {
226
+ var readonlyClass = 'abc_textarea_readonly';
227
+ var el = this.editarea.getElem();
228
+ if (readOnly) {
229
+ el.setAttribute('readonly', 'yes');
230
+ this.addClassName(el, readonlyClass);
231
+ } else {
232
+ el.removeAttribute('readonly');
233
+ this.removeClassName(el, readonlyClass);
234
+ }
235
+ };
236
+ }
237
+
238
+ window.ABCJS.Editor.prototype.renderTune = function(abc, params, div) {
239
+ var tunebook = new ABCJS.TuneBook(abc);
240
+ var abcParser = window.ABCJS.parse.Parse();
241
+ abcParser.parse(tunebook.tunes[0].abc, params); //TODO handle multiple tunes
242
+ var tune = abcParser.getTune();
243
+ var paper = Raphael(div, 800, 400);
244
+ var printer = new ABCJS.write.Printer(paper, {}); // TODO: handle printer params
245
+ printer.printABC(tune);
246
+ };
247
+
248
+ window.ABCJS.Editor.prototype.modelChanged = function() {
249
+ if (this.tunes === undefined) {
250
+ if (this.mididiv !== undefined && this.mididiv !== this.div)
251
+ this.mididiv.innerHTML = "";
252
+ this.div.innerHTML = "";
253
+ return;
254
+ }
255
+
256
+ if (this.bReentry)
257
+ return; // TODO is this likely? maybe, if we rewrite abc immediately w/ abc2abc
258
+ this.bReentry = true;
259
+ this.timerId = null;
260
+ this.div.innerHTML = "";
261
+ var paper = Raphael(this.div, 800, 400);
262
+ this.printer = new ABCJS.write.Printer(paper, this.printerparams);
263
+ this.printer.printABC(this.tunes);
264
+ if (ABCJS.midi.MidiWriter && this.mididiv) {
265
+ if (this.mididiv !== this.div)
266
+ this.mididiv.innerHTML = "";
267
+ var midiwriter = new ABCJS.midi.MidiWriter(this.mididiv,this.midiparams);
268
+ midiwriter.addListener(this.printer);
269
+ midiwriter.writeABC(this.tunes[0]); //TODO handle multiple tunes
270
+ }
271
+ if (this.warningsdiv) {
272
+ this.warningsdiv.innerHTML = (this.warnings) ? this.warnings.join("<br />") : "No errors";
273
+ }
274
+ if (this.target) {
275
+ var textprinter = new window.ABCJS.transform.TextPrinter(this.target, true);
276
+ textprinter.printABC(this.tunes[0]); //TODO handle multiple tunes
277
+ }
278
+ this.printer.addSelectListener(this);
279
+ this.updateSelection();
280
+ this.bReentry = false;
281
+ };
282
+
283
+ // Call this to reparse in response to the printing parameters changing
284
+ window.ABCJS.Editor.prototype.paramChanged = function(printerparams) {
285
+ this.printerparams = printerparams;
286
+ this.oldt = "";
287
+ this.fireChanged();
288
+ };
289
+
290
+ // return true if the model has changed
291
+ window.ABCJS.Editor.prototype.parseABC = function() {
292
+ var t = this.editarea.getString();
293
+ if (t===this.oldt) {
294
+ this.updateSelection();
295
+ return false;
296
+ }
297
+
298
+ this.oldt = t;
299
+ if (t === "") {
300
+ this.tunes = undefined;
301
+ this.warnings = "";
302
+ return true;
303
+ }
304
+ var tunebook = new ABCJS.TuneBook(t);
305
+
306
+ this.tunes = [];
307
+ this.warnings = [];
308
+ for (var i=0; i<tunebook.tunes.length; i++) {
309
+ var abcParser = new window.ABCJS.parse.Parse();
310
+ abcParser.parse(tunebook.tunes[i].abc, this.parserparams); //TODO handle multiple tunes
311
+ this.tunes[i] = abcParser.getTune();
312
+ var warnings = abcParser.getWarnings() || [];
313
+ for (var j=0; j<warnings.length; j++) {
314
+ this.warnings.push(warnings[j]);
315
+ }
316
+ }
317
+ return true;
318
+ };
319
+
320
+ window.ABCJS.Editor.prototype.updateSelection = function() {
321
+ var selection = this.editarea.getSelection();
322
+ try {
323
+ this.printer.rangeHighlight(selection.start, selection.end);
324
+ } catch (e) {} // maybe printer isn't defined yet?
325
+ };
326
+
327
+ window.ABCJS.Editor.prototype.fireSelectionChanged = function() {
328
+ this.updateSelection();
329
+ };
330
+
331
+ window.ABCJS.Editor.prototype.setDirtyStyle = function(isDirty) {
332
+ if (this.indicate_changed === undefined)
333
+ return;
334
+ var addClassName = function(element, className) {
335
+ var hasClassName = function(element, className) {
336
+ var elementClassName = element.className;
337
+ return (elementClassName.length > 0 && (elementClassName === className ||
338
+ new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
339
+ };
340
+
341
+ if (!hasClassName(element, className))
342
+ element.className += (element.className ? ' ' : '') + className;
343
+ return element;
344
+ };
345
+
346
+ var removeClassName = function(element, className) {
347
+ element.className = window.ABCJS.parse.strip(element.className.replace(
348
+ new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' '));
349
+ return element;
350
+ };
351
+
352
+ var readonlyClass = 'abc_textarea_dirty';
353
+ var el = this.editarea.getElem();
354
+ if (isDirty) {
355
+ addClassName(el, readonlyClass);
356
+ } else {
357
+ removeClassName(el, readonlyClass);
358
+ }
359
+ };
360
+
361
+ // call when abc text is changed and needs re-parsing
362
+ window.ABCJS.Editor.prototype.fireChanged = function() {
363
+ if (this.bIsPaused)
364
+ return;
365
+ if (this.parseABC()) {
366
+ var self = this;
367
+ if (this.timerId) // If the user is still typing, cancel the update
368
+ clearTimeout(this.timerId);
369
+ this.timerId = setTimeout(function () {
370
+ self.modelChanged();
371
+ }, 300); // Is this a good comprimise between responsiveness and not redrawing too much?
372
+ var isDirty = this.isDirty();
373
+ if (this.wasDirty !== isDirty) {
374
+ this.wasDirty = isDirty;
375
+ this.setDirtyStyle(isDirty);
376
+ }
377
+ if (this.onchangeCallback)
378
+ this.onchangeCallback(this);
379
+ }
380
+ };
381
+
382
+ window.ABCJS.Editor.prototype.setNotDirty = function() {
383
+ this.editarea.initialText = this.editarea.getString();
384
+ this.wasDirty = false;
385
+ this.setDirtyStyle(false);
386
+ };
387
+
388
+ window.ABCJS.Editor.prototype.isDirty = function() {
389
+ if (this.indicate_changed === undefined)
390
+ return false;
391
+ return this.editarea.initialText !== this.editarea.getString();
392
+ };
393
+
394
+ window.ABCJS.Editor.prototype.highlight = function(abcelem) {
395
+ this.editarea.setSelection(abcelem.startChar, abcelem.endChar);
396
+ };
397
+
398
+ window.ABCJS.Editor.prototype.pause = function(shouldPause) {
399
+ this.bIsPaused = shouldPause;
400
+ if (!shouldPause)
401
+ this.updateRendering();
402
+ };
403
+
404
+ window.ABCJS.Editor.prototype.pauseMidi = function(shouldPause) {
405
+ if (shouldPause && this.mididiv) {
406
+ this.mididivSave = this.mididiv;
407
+ this.addClassName(this.mididiv, 'hidden');
408
+ this.mididiv = null;
409
+ } else if (!shouldPause && this.mididivSave) {
410
+ this.mididiv = this.mididivSave;
411
+ this.removeClassName(this.mididiv, 'hidden');
412
+ this.mididivSave = null;
413
+ }
414
+ };
@@ -0,0 +1,698 @@
1
+ /*global window, document, setTimeout */
2
+
3
+ if (!window.ABCJS)
4
+ window.ABCJS = {};
5
+
6
+ if (!window.ABCJS.midi)
7
+ window.ABCJS.midi = {};
8
+
9
+ (function() {
10
+ function setAttributes(elm, attrs){
11
+ for(var attr in attrs)
12
+ if (attrs.hasOwnProperty(attr))
13
+ elm.setAttribute(attr, attrs[attr]);
14
+ return elm;
15
+ }
16
+
17
+ //TODO-PER: put this back in when the MIDIPlugin works again.
18
+ //window.oldunload = window.onbeforeunload;
19
+ //window.onbeforeunload = function() {
20
+ // if (window.oldunload)
21
+ // window.oldunload();
22
+ // if (typeof(MIDIPlugin) !== "undefined" && MIDIPlugin) { // PER: take care of crash in IE 8
23
+ // MIDIPlugin.closePlugin();
24
+ // }
25
+ //};
26
+
27
+
28
+ function MidiProxy(javamidi,qtmidi) {
29
+ this.javamidi = javamidi;
30
+ this.qtmidi = qtmidi;
31
+ }
32
+
33
+ MidiProxy.prototype.setTempo = function (qpm) {
34
+ this.javamidi.setTempo(qpm);
35
+ this.qtmidi.setTempo(qpm);
36
+ };
37
+
38
+ MidiProxy.prototype.startTrack = function () {
39
+ this.javamidi.startTrack();
40
+ this.qtmidi.startTrack();
41
+ };
42
+
43
+ MidiProxy.prototype.endTrack = function () {
44
+ this.javamidi.endTrack();
45
+ this.qtmidi.endTrack();
46
+ };
47
+
48
+ MidiProxy.prototype.setInstrument = function (number) {
49
+ this.javamidi.setInstrument(number);
50
+ this.qtmidi.setInstrument(number);
51
+ };
52
+
53
+ MidiProxy.prototype.startNote = function (pitch, loudness, abcelem) {
54
+ this.javamidi.startNote(pitch, loudness, abcelem);
55
+ this.qtmidi.startNote(pitch, loudness, abcelem);
56
+ };
57
+
58
+ MidiProxy.prototype.endNote = function (pitch, length) {
59
+ this.javamidi.endNote(pitch, length);
60
+ this.qtmidi.endNote(pitch, length);
61
+ };
62
+
63
+ MidiProxy.prototype.addRest = function (length) {
64
+ this.javamidi.addRest(length);
65
+ this.qtmidi.addRest(length);
66
+ };
67
+
68
+ MidiProxy.prototype.embed = function(parent) {
69
+ this.javamidi.embed(parent);
70
+ this.qtmidi.embed(parent,true);
71
+ };
72
+
73
+ function JavaMidi(midiwriter) {
74
+ this.playlist = []; // contains {time:t,funct:f} pairs
75
+ this.trackcount = 0;
76
+ this.timecount = 0;
77
+ this.tempo = 60;
78
+ this.midiapi = MIDIPlugin;
79
+ this.midiwriter = midiwriter;
80
+ this.noteOnAndChannel = "%90";
81
+ }
82
+
83
+ JavaMidi.prototype.setTempo = function (qpm) {
84
+ this.tempo = qpm;
85
+ };
86
+
87
+ JavaMidi.prototype.startTrack = function () {
88
+ this.silencelength = 0;
89
+ this.trackcount++;
90
+ this.timecount=0;
91
+ this.playlistpos=0;
92
+ this.first=true;
93
+ if (this.instrument) {
94
+ this.setInstrument(this.instrument);
95
+ }
96
+ if (this.channel) {
97
+ this.setChannel(this.channel);
98
+ }
99
+ };
100
+
101
+ JavaMidi.prototype.endTrack = function () {
102
+ // need to do anything?
103
+ };
104
+
105
+ JavaMidi.prototype.setInstrument = function (number) {
106
+ this.instrument=number;
107
+ this.midiapi.setInstrument(number);
108
+ //TODO push this into the playlist?
109
+ };
110
+
111
+ JavaMidi.prototype.setChannel = function (number) {
112
+ this.channel=number;
113
+ this.midiapi.setChannel(number);
114
+ };
115
+
116
+ JavaMidi.prototype.updatePos = function() {
117
+ while(this.playlist[this.playlistpos] &&
118
+ this.playlist[this.playlistpos].time<this.timecount) {
119
+ this.playlistpos++;
120
+ }
121
+ };
122
+
123
+ JavaMidi.prototype.startNote = function (pitch, loudness, abcelem) {
124
+ this.timecount+=this.silencelength;
125
+ this.silencelength = 0;
126
+ if (this.first) {
127
+ //nothing special if first?
128
+ }
129
+ this.updatePos();
130
+ var self=this;
131
+ this.playlist.splice(this.playlistpos,0, {
132
+ time:this.timecount,
133
+ funct:function() {
134
+ self.midiapi.playNote(pitch);
135
+ self.midiwriter.notifySelect(abcelem);
136
+ }
137
+ });
138
+ };
139
+
140
+ JavaMidi.prototype.endNote = function (pitch, length) {
141
+ this.timecount+=length;
142
+ this.updatePos();
143
+ var self=this;
144
+ this.playlist.splice(this.playlistpos, 0, {
145
+ time:this.timecount,
146
+ funct: function() {
147
+ self.midiapi.stopNote(pitch);
148
+ }
149
+ });
150
+ };
151
+
152
+ JavaMidi.prototype.addRest = function (length) {
153
+ this.silencelength += length;
154
+ };
155
+
156
+ JavaMidi.prototype.embed = function(parent) {
157
+
158
+
159
+ this.playlink = setAttributes(document.createElement('a'), {
160
+ style: "border:1px solid black; margin:3px;"
161
+ });
162
+ this.playlink.innerHTML = "play";
163
+ var self = this;
164
+ this.playlink.onmousedown = function() {
165
+ if (self.playing) {
166
+ this.innerHTML = "play";
167
+ self.pausePlay();
168
+ } else {
169
+ this.innerHTML = "pause";
170
+ self.startPlay();
171
+ }
172
+ };
173
+ parent.appendChild(this.playlink);
174
+
175
+ var stoplink = setAttributes(document.createElement('a'), {
176
+ style: "border:1px solid black; margin:3px;"
177
+ });
178
+ stoplink.innerHTML = "stop";
179
+ //var self = this;
180
+ stoplink.onmousedown = function() {
181
+ self.stopPlay();
182
+ };
183
+ parent.appendChild(stoplink);
184
+ this.i=0;
185
+ this.currenttime=0;
186
+ this.playing = false;
187
+ };
188
+
189
+ JavaMidi.prototype.stopPlay = function() {
190
+ this.i=0;
191
+ this.currenttime=0;
192
+ this.pausePlay();
193
+ this.playlink.innerHTML = "play";
194
+ };
195
+
196
+ JavaMidi.prototype.startPlay = function() {
197
+ this.playing = true;
198
+ var self = this;
199
+ // repeat every 16th note TODO see the min in the piece
200
+ this.ticksperinterval = 480/4;
201
+ this.doPlay();
202
+ this.playinterval = window.setInterval(function() {self.doPlay(); },
203
+ (60000/(this.tempo*4)));
204
+ };
205
+
206
+ JavaMidi.prototype.pausePlay = function() {
207
+ this.playing = false;
208
+ window.clearInterval(this.playinterval);
209
+ this.midiapi.stopAllNotes();
210
+ };
211
+
212
+ JavaMidi.prototype.doPlay = function() {
213
+ while(this.playlist[this.i] &&
214
+ this.playlist[this.i].time <= this.currenttime) {
215
+ this.playlist[this.i].funct();
216
+ this.i++;
217
+ }
218
+ if (this.playlist[this.i]) {
219
+ this.currenttime+=this.ticksperinterval;
220
+ } else {
221
+ this.stopPlay();
222
+ }
223
+ };
224
+
225
+ function Midi() {
226
+ this.trackstrings="";
227
+ this.trackcount = 0;
228
+ this.noteOnAndChannel = "%90";
229
+ }
230
+
231
+ Midi.prototype.setTempo = function (qpm) {
232
+ if (this.trackcount===0) {
233
+ this.startTrack();
234
+ this.track+="%00%FF%51%03"+toHex(Math.round(60000000/qpm),6);
235
+ this.endTrack();
236
+ }
237
+ };
238
+
239
+ Midi.prototype.startTrack = function () {
240
+ this.track = "";
241
+ this.silencelength = 0;
242
+ this.trackcount++;
243
+ this.first=true;
244
+ if (this.instrument) {
245
+ this.setInstrument(this.instrument);
246
+ }
247
+ };
248
+
249
+ Midi.prototype.endTrack = function () {
250
+ var tracklength = toHex(this.track.length/3+4,8);
251
+ this.track = "MTrk"+tracklength+ // track header
252
+ this.track +
253
+ '%00%FF%2F%00'; // track end
254
+ this.trackstrings += this.track;
255
+ };
256
+
257
+ Midi.prototype.setInstrument = function (number) {
258
+ if (this.track)
259
+ this.track = "%00%C0"+toHex(number,2)+this.track;
260
+ else
261
+ this.track = "%00%C0"+toHex(number,2);
262
+ this.instrument=number;
263
+ };
264
+
265
+ Midi.prototype.setChannel = function (number) {
266
+ this.channel=number - 1;
267
+ this.noteOnAndChannel = "%9" + this.channel.toString(16);
268
+ };
269
+
270
+ Midi.prototype.startNote = function (pitch, loudness) {
271
+ this.track+=toDurationHex(this.silencelength); // only need to shift by amount of silence (if there is any)
272
+ this.silencelength = 0;
273
+ if (this.first) {
274
+ this.first = false;
275
+ this.track+=this.noteOnAndChannel;
276
+ }
277
+ this.track += "%"+pitch.toString(16)+"%"+loudness; //note
278
+ };
279
+
280
+ Midi.prototype.endNote = function (pitch, length) {
281
+ this.track += toDurationHex(length); //duration
282
+ this.track += "%"+pitch.toString(16)+"%00";//end note
283
+ };
284
+
285
+ Midi.prototype.addRest = function (length) {
286
+ this.silencelength += length;
287
+ };
288
+
289
+ Midi.prototype.embed = function(parent, noplayer) {
290
+
291
+ var data="data:audio/midi," +
292
+ "MThd%00%00%00%06%00%01"+toHex(this.trackcount,4)+"%01%e0"+ // header
293
+ this.trackstrings;
294
+
295
+ // var embedContainer = document.createElement("div");
296
+ // embedContainer.className = "embedContainer";
297
+ // document.body.appendChild(embedContainer);
298
+ // embedContainer.innerHTML = '<object id="embed1" classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" codebase="http://www.apple.com/qtactivex/qtplugin.cab"><param name="src" value="' + data + '"></param><param name="Autoplay" value="false"></param><embed name="embed1" src="' + data + '" autostart="false" enablejavascript="true" /></object>';
299
+ // embed = document["embed1"];
300
+
301
+
302
+ var link = setAttributes(document.createElement('a'), {
303
+ href: data
304
+ });
305
+ link.innerHTML = "download midi";
306
+ parent.insertBefore(link,parent.firstChild);
307
+
308
+ if (noplayer) return;
309
+
310
+ var embed = setAttributes(document.createElement('embed'), {
311
+ src : data,
312
+ type : 'video/quicktime',
313
+ controller : 'true',
314
+ autoplay : 'false',
315
+ loop : 'false',
316
+ enablejavascript: 'true',
317
+ style:'display:block; height: 20px;'
318
+ });
319
+ parent.insertBefore(embed,parent.firstChild);
320
+ };
321
+
322
+ // s is assumed to be of even length
323
+ function encodeHex(s) {
324
+ var ret = "";
325
+ for (var i=0; i<s.length; i+=2) {
326
+ ret += "%";
327
+ ret += s.substr(i,2);
328
+ }
329
+ return ret;
330
+ }
331
+
332
+ function toHex(n, padding) {
333
+ var s = n.toString(16);
334
+ while (s.length<padding) {
335
+ s="0"+s;
336
+ }
337
+ return encodeHex(s);
338
+ }
339
+
340
+ function toDurationHex(n) {
341
+ var res = 0;
342
+ var a = [];
343
+
344
+ // cut up into 7 bit chunks;
345
+ while (n!==0) {
346
+ a.push(n & 0x7F);
347
+ n = n>>7;
348
+ }
349
+
350
+ // join the 7 bit chunks together, all but last chunk get leading 1
351
+ for (var i=a.length-1;i>=0;i--) {
352
+ res = res << 8;
353
+ var bits = a[i];
354
+ if (i!==0) {
355
+ bits = bits | 0x80;
356
+ }
357
+ res = res | bits;
358
+ }
359
+
360
+ var padding = res.toString(16).length;
361
+ padding += padding%2;
362
+
363
+ return toHex(res, padding);
364
+ }
365
+
366
+ ABCJS.midi.MidiWriter = function(parent, options) {
367
+ options = options || {};
368
+ this.parent = parent;
369
+ this.scale = [0,2,4,5,7,9,11];
370
+ this.restart = {line:0, staff:0, voice:0, pos:0};
371
+ this.visited = {};
372
+ this.multiplier =1;
373
+ this.next = null;
374
+ this.qpm = options.qpm || 180;
375
+ this.program = options.program || 2;
376
+ this.noteOnAndChannel = "%90";
377
+ this.javamidi = options.type ==="java" || false;
378
+ this.listeners = [];
379
+ this.transpose = 0; // PER
380
+ if (this.javamidi) {
381
+ MIDIPlugin = document.MIDIPlugin;
382
+ setTimeout(function() { // run on next event loop (once MIDIPlugin is loaded)
383
+ try { // activate MIDIPlugin
384
+ MIDIPlugin.openPlugin();
385
+
386
+ } catch(e) { // plugin not supported (download externals)
387
+ var a = document.createElement("a");
388
+ a.href = "http://java.sun.com/products/java-media/sound/soundbanks.html";
389
+ a.target = "_blank";
390
+ a.appendChild(document.createTextNode("Download Soundbank"));
391
+ parent.appendChild(a);
392
+ }
393
+ }, 0);
394
+ }
395
+
396
+ };
397
+
398
+ ABCJS.midi.MidiWriter.prototype.addListener = function(listener) {
399
+ this.listeners.push(listener);
400
+ };
401
+
402
+ ABCJS.midi.MidiWriter.prototype.notifySelect = function (abcelem) {
403
+ for (var i=0; i<this.listeners.length;i++) {
404
+ this.listeners[i].notifySelect(abcelem.abselem);
405
+ }
406
+ };
407
+
408
+ ABCJS.midi.MidiWriter.prototype.getMark = function() {
409
+ return {line:this.line, staff:this.staff,
410
+ voice:this.voice, pos:this.pos};
411
+ };
412
+
413
+ ABCJS.midi.MidiWriter.prototype.getMarkString = function(mark) {
414
+ mark = mark || this;
415
+ return "line"+mark.line+"staff"+mark.staff+
416
+ "voice"+mark.voice+"pos"+mark.pos;
417
+ };
418
+
419
+ ABCJS.midi.MidiWriter.prototype.goToMark = function(mark) {
420
+ this.line=mark.line;
421
+ this.staff=mark.staff;
422
+ this.voice=mark.voice;
423
+ this.pos=mark.pos;
424
+ };
425
+
426
+ ABCJS.midi.MidiWriter.prototype.markVisited = function() {
427
+ this.lastmark = this.getMarkString();
428
+ this.visited[this.lastmark] = true;
429
+ };
430
+
431
+ ABCJS.midi.MidiWriter.prototype.isVisited = function() {
432
+ if (this.visited[this.getMarkString()]) return true;
433
+ return false;
434
+ };
435
+
436
+ ABCJS.midi.MidiWriter.prototype.setJumpMark = function(mark) {
437
+ this.visited[this.lastmark] = mark;
438
+ };
439
+
440
+ ABCJS.midi.MidiWriter.prototype.getJumpMark = function() {
441
+ return this.visited[this.getMarkString()];
442
+ };
443
+
444
+ ABCJS.midi.MidiWriter.prototype.getLine = function() {
445
+ return this.abctune.lines[this.line];
446
+ };
447
+
448
+ ABCJS.midi.MidiWriter.prototype.getStaff = function() {
449
+ try {
450
+ return this.getLine().staff[this.staff];
451
+ } catch (e) {
452
+
453
+ }
454
+ };
455
+
456
+ ABCJS.midi.MidiWriter.prototype.getVoice = function() {
457
+ return this.getStaff().voices[this.voice];
458
+ };
459
+
460
+ ABCJS.midi.MidiWriter.prototype.getElem = function() {
461
+ return this.getVoice()[this.pos];
462
+ };
463
+
464
+ ABCJS.midi.MidiWriter.prototype.writeABC = function(abctune) {
465
+ try {
466
+ this.midi = (this.javamidi) ? new MidiProxy(new JavaMidi(this), new Midi()) : new Midi();
467
+ this.baraccidentals = [];
468
+ this.abctune = abctune;
469
+ this.baseduration = 480*4; // nice and divisible, equals 1 whole note
470
+
471
+ // PER: add global transposition.
472
+ if (abctune.formatting.midi && abctune.formatting.midi.transpose)
473
+ this.transpose = abctune.formatting.midi.transpose;
474
+
475
+ // PER: changed format of the global midi commands from the parser. Using the new definition here.
476
+ if (abctune.formatting.midi && abctune.formatting.midi.program && abctune.formatting.midi.program.program) {
477
+ this.midi.setInstrument(abctune.formatting.midi.program.program);
478
+ } else {
479
+ this.midi.setInstrument(this.program);
480
+ }
481
+ if (abctune.formatting.midi && abctune.formatting.midi.channel) {
482
+ this.midi.setChannel(abctune.formatting.midi.channel);
483
+ }
484
+
485
+ if (abctune.metaText.tempo) {
486
+ var duration = 1/4;
487
+ if (abctune.metaText.tempo.duration) {
488
+ duration = abctune.metaText.tempo.duration[0];
489
+ }
490
+ var bpm = 60;
491
+ if (abctune.metaText.tempo.bpm) {
492
+ bpm = abctune.metaText.tempo.bpm;
493
+ }
494
+ this.qpm = bpm*duration*4;
495
+ }
496
+ this.midi.setTempo(this.qpm);
497
+
498
+ // visit each voice completely in turn
499
+ // "problematic" because it means visiting only one staff+voice for each line each time
500
+ this.staffcount=1; // we'll know the actual number once we enter the code
501
+ for(this.staff=0;this.staff<this.staffcount;this.staff++) {
502
+ this.voicecount=1;
503
+ for(this.voice=0;this.voice<this.voicecount;this.voice++) {
504
+ this.midi.startTrack();
505
+ this.restart = {line:0, staff:this.staff, voice:this.voice, pos:0};
506
+ this.next= null;
507
+ for(this.line=0; this.line<abctune.lines.length; this.line++) {
508
+ var abcline = abctune.lines[this.line];
509
+ if (this.getLine().staff) {
510
+ this.writeABCLine();
511
+ }
512
+ }
513
+ this.midi.endTrack();
514
+ }
515
+ }
516
+
517
+ this.midi.embed(this.parent);
518
+ } catch (e) {
519
+ this.parent.innerHTML="Couldn't write midi: "+e;
520
+ }
521
+ };
522
+
523
+ ABCJS.midi.MidiWriter.prototype.writeABCLine = function() {
524
+ this.staffcount = this.getLine().staff.length;
525
+ this.voicecount = this.getStaff().voices.length;
526
+ this.setKeySignature(this.getStaff().key);
527
+ this.writeABCVoiceLine();
528
+ };
529
+
530
+ ABCJS.midi.MidiWriter.prototype.writeABCVoiceLine = function () {
531
+ this.pos=0;
532
+ while (this.pos<this.getVoice().length) {
533
+ this.writeABCElement(this.getElem());
534
+ if (this.next) {
535
+ this.goToMark(this.next);
536
+ this.next = null;
537
+ if (!this.getLine().staff) return;
538
+ } else {
539
+ this.pos++;
540
+ }
541
+ }
542
+ };
543
+
544
+ ABCJS.midi.MidiWriter.prototype.writeABCElement = function(elem) {
545
+ var foo;
546
+ switch (elem.el_type) {
547
+ case "note":
548
+ this.writeNote(elem);
549
+ break;
550
+
551
+ case "key":
552
+ this.setKeySignature(elem);
553
+ break;
554
+ case "bar":
555
+ this.handleBar(elem);
556
+ break;
557
+ case "meter":
558
+ case "clef":
559
+ break;
560
+ default:
561
+
562
+ }
563
+
564
+ };
565
+
566
+
567
+ ABCJS.midi.MidiWriter.prototype.writeNote = function(elem) {
568
+
569
+ if (elem.startTriplet) {
570
+ this.multiplier=2/3;
571
+ }
572
+
573
+ var mididuration = elem.duration*this.baseduration*this.multiplier;
574
+ if (elem.pitches) {
575
+ var midipitches = [];
576
+ for (var i=0; i<elem.pitches.length; i++) {
577
+ var note = elem.pitches[i];
578
+ var pitch= note.pitch;
579
+ if (note.accidental) {
580
+ switch(note.accidental) { // change that pitch (not other octaves) for the rest of the bar
581
+ case "sharp":
582
+ this.baraccidentals[pitch]=1; break;
583
+ case "flat":
584
+ this.baraccidentals[pitch]=-1; break;
585
+ case "natural":
586
+ this.baraccidentals[pitch]=0; break;
587
+ case "dblsharp":
588
+ this.baraccidentals[pitch]=2; break;
589
+ case "dblflat":
590
+ this.baraccidentals[pitch]=-2; break;
591
+ }
592
+ }
593
+
594
+ midipitches[i] = 60 + 12*this.extractOctave(pitch)+this.scale[this.extractNote(pitch)];
595
+
596
+ if (this.baraccidentals[pitch]!==undefined) {
597
+ midipitches[i] += this.baraccidentals[pitch];
598
+ } else { // use normal accidentals
599
+ midipitches[i] += this.accidentals[this.extractNote(pitch)];
600
+ }
601
+ midipitches[i] += this.transpose; // PER
602
+
603
+ this.midi.startNote(midipitches[i], 64, elem);
604
+
605
+ if (note.startTie) {
606
+ this.tieduration=mididuration;
607
+ }
608
+ }
609
+
610
+ for (i=0; i<elem.pitches.length; i++) {
611
+ var note = elem.pitches[i];
612
+ var pitch= note.pitch+this.transpose; // PER
613
+ if (note.startTie) continue; // don't terminate it
614
+ if (note.endTie) {
615
+ this.midi.endNote(midipitches[i],mididuration+this.tieduration);
616
+ } else {
617
+ this.midi.endNote(midipitches[i],mididuration);
618
+ }
619
+ mididuration = 0; // put these to zero as we've moved forward in the midi
620
+ this.tieduration=0;
621
+ }
622
+ } else if (elem.rest && elem.rest.type !== 'spacer') {
623
+ this.midi.addRest(mididuration);
624
+ }
625
+
626
+ if (elem.endTriplet) {
627
+ this.multiplier=1;
628
+ }
629
+
630
+ };
631
+
632
+ ABCJS.midi.MidiWriter.prototype.handleBar = function (elem) {
633
+ this.baraccidentals = [];
634
+
635
+
636
+ var repeat = (elem.type==="bar_right_repeat" || elem.type==="bar_dbl_repeat");
637
+ var skip = (elem.startEnding)?true:false;
638
+ var setvisited = (repeat || skip);
639
+ var setrestart = (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");
640
+
641
+ var next = null;
642
+
643
+ if (this.isVisited()) {
644
+ next = this.getJumpMark();
645
+ } else {
646
+
647
+ if (skip || repeat) {
648
+ if (this.visited[this.lastmark] === true) {
649
+ this.setJumpMark(this.getMark());
650
+ }
651
+ }
652
+
653
+ if (setvisited) {
654
+ this.markVisited();
655
+ }
656
+
657
+ if (repeat) {
658
+ next = this.restart;
659
+ this.setJumpMark(this.getMark());
660
+ }
661
+ }
662
+
663
+ if (setrestart) {
664
+ this.restart = this.getMark();
665
+ }
666
+
667
+ if (next && this.getMarkString(next)!==this.getMarkString()) {
668
+ this.next = next;
669
+ }
670
+
671
+ };
672
+
673
+ ABCJS.midi.MidiWriter.prototype.setKeySignature = function(elem) {
674
+ this.accidentals = [0,0,0,0,0,0,0];
675
+ if (this.abctune.formatting.bagpipes) {
676
+ elem.accidentals=[{acc: 'natural', note: 'g'}, {acc: 'sharp', note: 'f'}, {acc: 'sharp', note: 'c'}];
677
+ }
678
+ if (!elem.accidentals) return;
679
+ window.ABCJS.parse.each(elem.accidentals, function(acc) {
680
+ var d = (acc.acc === "sharp") ? 1 : (acc.acc === "natural") ?0 : -1;
681
+
682
+ var lowercase = acc.note.toLowerCase();
683
+ var note = this.extractNote(lowercase.charCodeAt(0)-'c'.charCodeAt(0));
684
+ this.accidentals[note]+=d;
685
+ }, this);
686
+
687
+ };
688
+
689
+ ABCJS.midi.MidiWriter.prototype.extractNote = function(pitch) {
690
+ pitch = pitch%7;
691
+ if (pitch<0) pitch+=7;
692
+ return pitch;
693
+ };
694
+
695
+ ABCJS.midi.MidiWriter.prototype.extractOctave = function(pitch) {
696
+ return Math.floor(pitch/7);
697
+ };
698
+ })();