abcjs-rails 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,546 @@
1
+ /*global window */
2
+
3
+ if (!window.ABCJS)
4
+ window.ABCJS = {};
5
+
6
+ if (!window.ABCJS.parse)
7
+ window.ABCJS.parse = {};
8
+
9
+ window.ABCJS.parse.parseDirective = {};
10
+
11
+ (function() {
12
+ var tokenizer;
13
+ var warn;
14
+ var multilineVars;
15
+ var tune;
16
+ window.ABCJS.parse.parseDirective.initialize = function(tokenizer_, warn_, multilineVars_, tune_) {
17
+ tokenizer = tokenizer_;
18
+ warn = warn_;
19
+ multilineVars = multilineVars_;
20
+ tune = tune_;
21
+ };
22
+
23
+ window.ABCJS.parse.parseDirective.parseFontChangeLine = function(textstr) {
24
+ var textParts = textstr.split('$');
25
+ if (textParts.length > 1 && multilineVars.setfont) {
26
+ var textarr = [ { text: textParts[0] }];
27
+ for (var i = 1; i < textParts.length; i++) {
28
+ if (textParts[i].charAt(0) === '0')
29
+ textarr.push({ text: textParts[i].substring(1) });
30
+ else if (textParts[i].charAt(0) === '1' && multilineVars.setfont[1])
31
+ textarr.push({font: multilineVars.setfont[1], text: textParts[i].substring(1) });
32
+ else if (textParts[i].charAt(0) === '2' && multilineVars.setfont[2])
33
+ textarr.push({font: multilineVars.setfont[2], text: textParts[i].substring(1) });
34
+ else if (textParts[i].charAt(0) === '3' && multilineVars.setfont[3])
35
+ textarr.push({font: multilineVars.setfont[3], text: textParts[i].substring(1) });
36
+ else if (textParts[i].charAt(0) === '4' && multilineVars.setfont[4])
37
+ textarr.push({font: multilineVars.setfont[4], text: textParts[i].substring(1) });
38
+ else
39
+ textarr[textarr.length-1].text += '$' + textParts[i];
40
+ }
41
+ if (textarr.length > 1)
42
+ return textarr;
43
+ }
44
+ return textstr;
45
+ };
46
+
47
+ window.ABCJS.parse.parseDirective.addDirective = function(str) {
48
+ var getRequiredMeasurement = function(cmd, tokens) {
49
+ var points = tokenizer.getMeasurement(tokens);
50
+ if (points.used === 0 || tokens.length !== 0)
51
+ return { error: "Directive \"" + cmd + "\" requires a measurement as a parameter."};
52
+ return points.value;
53
+ };
54
+ var oneParameterMeasurement = function(cmd, tokens) {
55
+ var points = tokenizer.getMeasurement(tokens);
56
+ if (points.used === 0 || tokens.length !== 0)
57
+ return "Directive \"" + cmd + "\" requires a measurement as a parameter.";
58
+ tune.formatting[cmd] = points.value;
59
+ return null;
60
+ };
61
+ var getFontParameter = function(tokens) {
62
+ var font = {};
63
+ var token = window.ABCJS.parse.last(tokens);
64
+ if (token.type === 'number') {
65
+ font.size = parseInt(token.token);
66
+ tokens.pop();
67
+ }
68
+ if (tokens.length > 0) {
69
+ var scratch = "";
70
+ window.ABCJS.parse.each(tokens, function(tok) {
71
+ if (tok.token !== '-') {
72
+ if (scratch.length > 0) scratch += ' ';
73
+ scratch += tok.token;
74
+ }
75
+ });
76
+ font.font = scratch;
77
+ }
78
+ return font;
79
+ };
80
+ var getChangingFont = function(cmd, tokens) {
81
+ if (tokens.length === 0)
82
+ return "Directive \"" + cmd + "\" requires a font as a parameter.";
83
+ multilineVars[cmd] = getFontParameter(tokens);
84
+ return null;
85
+ };
86
+ var getGlobalFont = function(cmd, tokens) {
87
+ if (tokens.length === 0)
88
+ return "Directive \"" + cmd + "\" requires a font as a parameter.";
89
+ tune.formatting[cmd] = getFontParameter(tokens);
90
+ return null;
91
+ };
92
+
93
+ var addMultilineVar = function(key, cmd, tokens, min, max) {
94
+ if (tokens.length !== 1 || tokens[0].type !== 'number')
95
+ return "Directive \"" + cmd + "\" requires a number as a parameter.";
96
+ var i = tokens[0].intt;
97
+ if (min !== undefined && i < min)
98
+ return "Directive \"" + cmd + "\" requires a number greater than or equal to " + min + " as a parameter.";
99
+ if (max !== undefined && i > max)
100
+ return "Directive \"" + cmd + "\" requires a number less than or equal to " + max + " as a parameter.";
101
+ multilineVars[key] = i;
102
+ return null;
103
+ };
104
+
105
+ var addMultilineVarBool = function(key, cmd, tokens) {
106
+ var str = addMultilineVar(key, cmd, tokens, 0, 1);
107
+ if (str !== null) return str;
108
+ multilineVars[key] = (multilineVars[key] === 1);
109
+ return null;
110
+ };
111
+
112
+ var tokens = tokenizer.tokenize(str, 0, str.length); // 3 or more % in a row, or just spaces after %% is just a comment
113
+ if (tokens.length === 0 || tokens[0].type !== 'alpha') return null;
114
+ var restOfString = str.substring(str.indexOf(tokens[0].token)+tokens[0].token.length);
115
+ restOfString = tokenizer.stripComment(restOfString);
116
+ var cmd = tokens.shift().token.toLowerCase();
117
+ var num;
118
+ var scratch = "";
119
+ switch (cmd)
120
+ {
121
+ // The following directives were added to abc_parser_lint, but haven't been implemented here.
122
+ // Most of them are direct translations from the directives that will be parsed in. See abcm2ps's format.txt for info on each of these.
123
+ // alignbars: { type: "number", optional: true },
124
+ // aligncomposer: { type: "string", Enum: [ 'left', 'center','right' ], optional: true },
125
+ // annotationfont: fontType,
126
+ // bstemdown: { type: "boolean", optional: true },
127
+ // continueall: { type: "boolean", optional: true },
128
+ // dynalign: { type: "boolean", optional: true },
129
+ // exprabove: { type: "boolean", optional: true },
130
+ // exprbelow: { type: "boolean", optional: true },
131
+ // flatbeams: { type: "boolean", optional: true },
132
+ // footer: { type: "string", optional: true },
133
+ // footerfont: fontType,
134
+ // gchordbox: { type: "boolean", optional: true },
135
+ // graceslurs: { type: "boolean", optional: true },
136
+ // gracespacebefore: { type: "number", optional: true },
137
+ // gracespaceinside: { type: "number", optional: true },
138
+ // gracespaceafter: { type: "number", optional: true },
139
+ // header: { type: "string", optional: true },
140
+ // headerfont: fontType,
141
+ // historyfont: fontType,
142
+ // infofont: fontType,
143
+ // infospace: { type: "number", optional: true },
144
+ // lineskipfac: { type: "number", optional: true },
145
+ // maxshrink: { type: "number", optional: true },
146
+ // maxstaffsep: { type: "number", optional: true },
147
+ // maxsysstaffsep: { type: "number", optional: true },
148
+ // measurebox: { type: "boolean", optional: true },
149
+ // measurefont: fontType,
150
+ // notespacingfactor: { type: "number", optional: true },
151
+ // parskipfac: { type: "number", optional: true },
152
+ // partsbox: { type: "boolean", optional: true },
153
+ // repeatfont: fontType,
154
+ // rightmargin: { type: "number", optional: true },
155
+ // slurheight: { type: "number", optional: true },
156
+ // splittune: { type: "boolean", optional: true },
157
+ // squarebreve: { type: "boolean", optional: true },
158
+ // stemheight: { type: "number", optional: true },
159
+ // straightflags: { type: "boolean", optional: true },
160
+ // stretchstaff: { type: "boolean", optional: true },
161
+ // textfont: fontType,
162
+ // titleformat: { type: "string", optional: true },
163
+ // vocalabove: { type: "boolean", optional: true },
164
+ // vocalfont: fontType,
165
+ // wordsfont: fontType,
166
+ case "bagpipes":tune.formatting.bagpipes = true;break;
167
+ case "landscape":multilineVars.landscape = true;break;
168
+ case "papersize":multilineVars.papersize = restOfString;break;
169
+ case "slurgraces":tune.formatting.slurgraces = true;break;
170
+ case "stretchlast":tune.formatting.stretchlast = true;break;
171
+ case "titlecaps":multilineVars.titlecaps = true;break;
172
+ case "titleleft":tune.formatting.titleleft = true;break;
173
+ case "measurebox":tune.formatting.measurebox = true;break;
174
+
175
+ case "botmargin":
176
+ case "botspace":
177
+ case "composerspace":
178
+ case "indent":
179
+ case "leftmargin":
180
+ case "linesep":
181
+ case "musicspace":
182
+ case "partsspace":
183
+ case "pageheight":
184
+ case "pagewidth":
185
+ case "rightmargin":
186
+ case "staffsep":
187
+ case "staffwidth":
188
+ case "subtitlespace":
189
+ case "sysstaffsep":
190
+ case "systemsep":
191
+ case "textspace":
192
+ case "titlespace":
193
+ case "topmargin":
194
+ case "topspace":
195
+ case "vocalspace":
196
+ case "wordsspace":
197
+ return oneParameterMeasurement(cmd, tokens);
198
+ case "vskip":
199
+ var vskip = getRequiredMeasurement(cmd, tokens);
200
+ if (vskip.error)
201
+ return vskip.error;
202
+ tune.addSpacing(vskip);
203
+ return null;
204
+ case "scale":
205
+ scratch = "";
206
+ window.ABCJS.parse.each(tokens, function(tok) {
207
+ scratch += tok.token;
208
+ });
209
+ num = parseFloat(scratch);
210
+ if (isNaN(num) || num === 0)
211
+ return "Directive \"" + cmd + "\" requires a number as a parameter.";
212
+ tune.formatting.scale = num;
213
+ break;
214
+ case "sep":
215
+ if (tokens.length === 0)
216
+ tune.addSeparator();
217
+ else {
218
+ var points = tokenizer.getMeasurement(tokens);
219
+ if (points.used === 0)
220
+ return "Directive \"" + cmd + "\" requires 3 numbers: space above, space below, length of line";
221
+ var spaceAbove = points.value;
222
+
223
+ points = tokenizer.getMeasurement(tokens);
224
+ if (points.used === 0)
225
+ return "Directive \"" + cmd + "\" requires 3 numbers: space above, space below, length of line";
226
+ var spaceBelow = points.value;
227
+
228
+ points = tokenizer.getMeasurement(tokens);
229
+ if (points.used === 0 || tokens.length !== 0)
230
+ return "Directive \"" + cmd + "\" requires 3 numbers: space above, space below, length of line";
231
+ var lenLine = points.value;
232
+ tune.addSeparator(spaceAbove, spaceBelow, lenLine);
233
+ }
234
+ break;
235
+ case "barsperstaff":
236
+ scratch = addMultilineVar('barsperstaff', cmd, tokens);
237
+ if (scratch !== null) return scratch;
238
+ break;
239
+ case "staffnonote":
240
+ scratch = addMultilineVarBool('staffnonote', cmd, tokens);
241
+ if (scratch !== null) return scratch;
242
+ break;
243
+ case "printtempo":
244
+ scratch = addMultilineVarBool('printTempo', cmd, tokens);
245
+ if (scratch !== null) return scratch;
246
+ break;
247
+ case "measurenb":
248
+ case "barnumbers":
249
+ scratch = addMultilineVar('barNumbers', cmd, tokens);
250
+ if (scratch !== null) return scratch;
251
+ break;
252
+ case "begintext":
253
+ multilineVars.inTextBlock = true;
254
+ break;
255
+ case "continueall":
256
+ multilineVars.continueall = true;
257
+ break;
258
+ case "beginps":
259
+ multilineVars.inPsBlock = true;
260
+ warn("Postscript ignored", str, 0);
261
+ break;
262
+ case "deco":
263
+ if (restOfString.length > 0)
264
+ multilineVars.ignoredDecorations.push(restOfString.substring(0, restOfString.indexOf(' ')));
265
+ warn("Decoration redefinition ignored", str, 0);
266
+ break;
267
+ case "text":
268
+ var textstr = tokenizer.translateString(restOfString);
269
+ tune.addText(window.ABCJS.parse.parseDirective.parseFontChangeLine(textstr));
270
+ break;
271
+ case "center":
272
+ var centerstr = tokenizer.translateString(restOfString);
273
+ tune.addCentered(window.ABCJS.parse.parseDirective.parseFontChangeLine(centerstr));
274
+ break;
275
+ case "font":
276
+ // don't need to do anything for this; it is a useless directive
277
+ break;
278
+ case "setfont":
279
+ var sfTokens = tokenizer.tokenize(restOfString, 0, restOfString.length);
280
+ var sfDone = false;
281
+ if (sfTokens.length >= 4) {
282
+ if (sfTokens[0].token === '-' && sfTokens[1].type === 'number') {
283
+ var sfNum = parseInt(sfTokens[1].token);
284
+ if (sfNum >= 1 && sfNum <= 4) {
285
+ if (!multilineVars.setfont)
286
+ multilineVars.setfont = [];
287
+ var sfSize = sfTokens.pop();
288
+ if (sfSize.type === 'number') {
289
+ sfSize = parseInt(sfSize.token);
290
+ var sfFontName = '';
291
+ for (var sfi = 2; sfi < sfTokens.length; sfi++)
292
+ sfFontName += sfTokens[sfi].token;
293
+ multilineVars.setfont[sfNum] = { font: sfFontName, size: sfSize };
294
+ sfDone = true;
295
+ }
296
+ }
297
+ }
298
+ }
299
+ if (!sfDone)
300
+ return "Bad parameters: " + cmd;
301
+ break;
302
+ case "gchordfont":
303
+ case "partsfont":
304
+ case "vocalfont":
305
+ case "textfont":
306
+ return getChangingFont(cmd, tokens);
307
+ case "barlabelfont":
308
+ case "barnumberfont":
309
+ case "composerfont":
310
+ case "subtitlefont":
311
+ case "tempofont":
312
+ case "titlefont":
313
+ case "voicefont":
314
+ return getGlobalFont(cmd, tokens);
315
+ case "barnumfont":
316
+ return getGlobalFont("barnumberfont", tokens);
317
+ case "staves":
318
+ case "score":
319
+ multilineVars.score_is_present = true;
320
+ var addVoice = function(id, newStaff, bracket, brace, continueBar) {
321
+ if (newStaff || multilineVars.staves.length === 0) {
322
+ multilineVars.staves.push({index: multilineVars.staves.length, numVoices: 0});
323
+ }
324
+ var staff = window.ABCJS.parse.last(multilineVars.staves);
325
+ if (bracket !== undefined) staff.bracket = bracket;
326
+ if (brace !== undefined) staff.brace = brace;
327
+ if (continueBar) staff.connectBarLines = 'end';
328
+ if (multilineVars.voices[id] === undefined) {
329
+ multilineVars.voices[id] = {staffNum: staff.index, index: staff.numVoices};
330
+ staff.numVoices++;
331
+ }
332
+ };
333
+
334
+ var openParen = false;
335
+ var openBracket = false;
336
+ var openBrace = false;
337
+ var justOpenParen = false;
338
+ var justOpenBracket = false;
339
+ var justOpenBrace = false;
340
+ var continueBar = false;
341
+ var lastVoice;
342
+ var addContinueBar = function() {
343
+ continueBar = true;
344
+ if (lastVoice) {
345
+ var ty = 'start';
346
+ if (lastVoice.staffNum > 0) {
347
+ if (multilineVars.staves[lastVoice.staffNum-1].connectBarLines === 'start' ||
348
+ multilineVars.staves[lastVoice.staffNum-1].connectBarLines === 'continue')
349
+ ty = 'continue';
350
+ }
351
+ multilineVars.staves[lastVoice.staffNum].connectBarLines = ty;
352
+ }
353
+ };
354
+ while (tokens.length) {
355
+ var t = tokens.shift();
356
+ switch (t.token) {
357
+ case '(':
358
+ if (openParen) warn("Can't nest parenthesis in %%score", str, t.start);
359
+ else {openParen = true;justOpenParen = true;}
360
+ break;
361
+ case ')':
362
+ if (!openParen || justOpenParen) warn("Unexpected close parenthesis in %%score", str, t.start);
363
+ else openParen = false;
364
+ break;
365
+ case '[':
366
+ if (openBracket) warn("Can't nest brackets in %%score", str, t.start);
367
+ else {openBracket = true;justOpenBracket = true;}
368
+ break;
369
+ case ']':
370
+ if (!openBracket || justOpenBracket) warn("Unexpected close bracket in %%score", str, t.start);
371
+ else {openBracket = false;multilineVars.staves[lastVoice.staffNum].bracket = 'end';}
372
+ break;
373
+ case '{':
374
+ if (openBrace ) warn("Can't nest braces in %%score", str, t.start);
375
+ else {openBrace = true;justOpenBrace = true;}
376
+ break;
377
+ case '}':
378
+ if (!openBrace || justOpenBrace) warn("Unexpected close brace in %%score", str, t.start);
379
+ else {openBrace = false;multilineVars.staves[lastVoice.staffNum].brace = 'end';}
380
+ break;
381
+ case '|':
382
+ addContinueBar();
383
+ break;
384
+ default:
385
+ var vc = "";
386
+ while (t.type === 'alpha' || t.type === 'number') {
387
+ vc += t.token;
388
+ if (t.continueId)
389
+ t = tokens.shift();
390
+ else
391
+ break;
392
+ }
393
+ var newStaff = !openParen || justOpenParen;
394
+ var bracket = justOpenBracket ? 'start' : openBracket ? 'continue' : undefined;
395
+ var brace = justOpenBrace ? 'start' : openBrace ? 'continue' : undefined;
396
+ addVoice(vc, newStaff, bracket, brace, continueBar);
397
+ justOpenParen = false;
398
+ justOpenBracket = false;
399
+ justOpenBrace = false;
400
+ continueBar = false;
401
+ lastVoice = multilineVars.voices[vc];
402
+ if (cmd === 'staves')
403
+ addContinueBar();
404
+ break;
405
+ }
406
+ }
407
+ break;
408
+
409
+ case "newpage":
410
+ var pgNum = tokenizer.getInt(restOfString);
411
+ tune.addNewPage(pgNum.digits === 0 ? -1 : pgNum.value);
412
+ break;
413
+
414
+ case "abc-copyright":
415
+ case "abc-creator":
416
+ case "abc-version":
417
+ case "abc-charset":
418
+ case "abc-edited-by":
419
+ tune.addMetaText(cmd, restOfString);
420
+ break;
421
+ case "header":
422
+ case "footer":
423
+ var footerStr = tokenizer.getMeat(restOfString, 0, restOfString.length);
424
+ footerStr = restOfString.substring(footerStr.start, footerStr.end);
425
+ if (footerStr.charAt(0) === '"' && footerStr.charAt(footerStr.length-1) === '"' )
426
+ footerStr = footerStr.substring(1, footerStr.length-2);
427
+ var footerArr = footerStr.split('\t');
428
+ var footer = {};
429
+ if (footerArr.length === 1)
430
+ footer = { left: "", center: footerArr[0], right: "" };
431
+ else if (footerArr.length === 2)
432
+ footer = { left: footerArr[0], center: footerArr[1], right: "" };
433
+ else
434
+ footer = { left: footerArr[0], center: footerArr[1], right: footerArr[2] };
435
+ if (footerArr.length > 3)
436
+ warn("Too many tabs in "+cmd+": "+footerArr.length+" found.", restOfString, 0);
437
+
438
+ tune.addMetaTextObj(cmd, footer);
439
+ break;
440
+
441
+ case "midi":
442
+ var midi = tokenizer.tokenize(restOfString, 0, restOfString.length);
443
+ if (midi.length > 0 && midi[0].token === '=')
444
+ midi.shift();
445
+ if (midi.length === 0)
446
+ warn("Expected midi command", restOfString, 0);
447
+ else {
448
+ // var midiCmd = restOfString.split(' ')[0];
449
+ // var midiParam = restOfString.substring(midiCmd.length+1);
450
+ var getNextMidiParam = function(midiToks) {
451
+ if (midiToks.length > 0) {
452
+ var t = midiToks.shift();
453
+ var p = t.token;
454
+ if (t.type === "number")
455
+ p = t.intt;
456
+ return p;
457
+ }
458
+ else
459
+ return null;
460
+ };
461
+ // TODO-PER: make sure the command is legal
462
+ if (tune.formatting[cmd] === undefined)
463
+ tune.formatting[cmd] = {};
464
+ var midi_cmd = midi.shift().token;
465
+ var midi_param = true;
466
+ if (midi_cmd === 'program') {
467
+ var p1 = getNextMidiParam(midi);
468
+ if (p1) {
469
+ var p2 = getNextMidiParam(midi);
470
+ // NOTE: The program number has an off by one error in ABC, so we add one here.
471
+ if (p2)
472
+ midi_param = { channel: p1, program: p2};
473
+ else
474
+ midi_param = { program: p1};
475
+ }
476
+ } else {
477
+ // TODO-PER: handle the params for all MIDI commands
478
+ var p = getNextMidiParam(midi);
479
+ if (p !== null)
480
+ midi_param = p;
481
+ }
482
+ tune.formatting[cmd][midi_cmd] = midi_param;
483
+ // TODO-PER: save all the parameters, not just the first.
484
+ }
485
+ //%%MIDI barlines: deactivates %%nobarlines.
486
+ //%%MIDI bassprog n
487
+ //%%MIDI bassvol n
488
+ //%%MIDI beat ⟨int1⟩ ⟨int2⟩ ⟨int3⟩ ⟨int4⟩: controls the volumes of the notes in a measure. The first note in a bar has volume ⟨int1⟩; other ‘strong’ notes have volume ⟨int2⟩ and all the rest have volume ⟨int3⟩. These values must be in the range 0–127. The parameter ⟨int4⟩ determines which notes are ‘strong’. If the time signature is x/y, then each note is given a position number k = 0, 1, 2. . . x-1 within each bar. If k is a multiple of ⟨int4⟩, then the note is ‘strong’.
489
+ //%%MIDI beataccents: reverts to normally emphasised notes. See also %%MIDI nobeat-
490
+ //%%MIDI beatmod ⟨int⟩: increments the velocities as defined by %%MIDI beat
491
+ //%%MIDI beatstring ⟨string⟩: similar to %%MIDI beat, but indicated with an fmp string.
492
+ //%%MIDI c ⟨int⟩: specifies the MIDI pitch which corresponds to . The default is 60.
493
+ //%%MIDI channel ⟨int⟩: selects the melody channel ⟨int⟩ (1–16).
494
+ //%%MIDI chordattack ⟨int⟩: delays the start of chord notes by ⟨int⟩ MIDI units.
495
+ //%%MIDI chordname ⟨string int1 int2 int3 int4 int5 int6⟩: defines new chords or re-defines existing ones as was seen in Section 12.8.
496
+ //%%MIDI chordprog 20 % Church organ
497
+ //%%MIDI chordvol ⟨int⟩: sets the volume (velocity) of the chord notes to ⟨int⟩ (0–127).
498
+ //%%MIDI control ⟨bass/chord⟩ ⟨int1 int2⟩: generates a MIDI control event. If %%control is followed by ⟨bass⟩ or ⟨chord⟩, the event apply to the bass or chord channel, otherwise it will be applied to the melody channel. ⟨int1⟩ is the MIDI control number (0–127) and ⟨int2⟩ the value (0–127).
499
+ //%%MIDI deltaloudness⟨int⟩: bydefault,!crescendo!and!dimuendo!modifythebe- at variables ⟨vol1⟩ ⟨vol2⟩ ⟨vol3⟩ 15 volume units. This command allows the user to change this default.
500
+ //%%MIDI drone ⟨int1 int2 int3 int4 int5⟩: specifies a two-note drone accompaniment. ⟨int1⟩ is the drone MIDI instrument, ⟨int2⟩ the MIDI pitch 1, ⟨int3⟩ the MIDI pitch 2, ⟨int4⟩ the MIDI volume 1, ⟨int5⟩ the MIDI volume 2. Default values are 70 45 33 80 80.
501
+ //%%MIDI droneoff: turns the drone accompaniment off.
502
+ //%%MIDI droneon: turns the drone accompaniment on.
503
+ //%%MIDI drum string [drum programs] [drum velocities]
504
+ //%%MIDI drumbars ⟨int⟩: specifies the number of bars over which a drum pattern string is spread. Default is 1.
505
+ //%%MIDI drummap ⟨str⟩ ⟨int⟩: associates the note ⟨str⟩ (in ABC notation) to the a percussion instrument, as listed in Section H.2.
506
+ //%%MIDI drumoff turns drum accompaniment off.
507
+ //%%MIDI drumon turns drum accompaniment on.
508
+ //%%MIDI fermatafixed: expands a !fermata! by one unit length; that is, GC3 becomes
509
+ //%%MIDI fermataproportional: doubles the length of a note preceded by !fermata!;
510
+ //%%MIDI gchord string
511
+ //%%MIDI gchord str
512
+ //%%MIDI gchordon
513
+ //%%MIDI gchordoff
514
+ //%%MIDI grace ⟨float⟩: sets the fraction of the next note that grace notes will take up. ⟨float⟩ must be a fraction such as 1/6.
515
+ //%%MIDI gracedivider ⟨int⟩: sets the grace note length as 1/⟨int⟩th of the following note.
516
+ //%%MIDI makechordchannels⟨int⟩: thisisaverycomplexcommandusedinchordscon-
517
+ //%%MIDI nobarlines
518
+ //%%MIDI nobeataccents: forces the ⟨int2⟩ volume (see %%MIDI beat) for each note in a bar, regardless of their position.
519
+ //%%MIDI noportamento: turns off the portamento controller on the current channel.
520
+ //%%MIDI pitchbend [bass/chord] <high byte> <low byte>
521
+ //%%MIDI program 2 75
522
+ //%%MIDI portamento ⟨int⟩: turns on the portamento controller on the current channel and set it to ⟨int⟩. Experts only.
523
+ //%%MIDI randomchordattack: delays the start of chord notes by a random number of MIDI units.
524
+ //%%MIDI ratio n m
525
+ //%%MIDI rtranspose ⟨int1⟩: transposes relatively to a prior %%transpose command by ⟨int1⟩ semitones; the total transposition will be ⟨int1 + int2⟩ semitones.
526
+ //%%MIDI temperament ⟨int1⟩ ⟨int2⟩: TO BE WRITTEN
527
+ //%%MIDI temperamentlinear ⟨float1 float2⟩: changes the temperament of the scale. ⟨fl- oat1⟩ specifies the size of an octave in cents of a semitone, or 1/1200 of an octave. ⟨float2⟩ specifies in the size of a fifth (normally 700 cents).
528
+ //%%MIDI temperamentnormal: restores normal temperament.
529
+ //%%MIDI transpose n
530
+ //%%MIDI voice [<ID>] [instrument=<integer> [bank=<integer>]] [mute]
531
+ break;
532
+
533
+ case "playtempo":
534
+ case "auquality":
535
+ case "continuous":
536
+ case "nobarcheck":
537
+ // TODO-PER: Actually handle the parameters of these
538
+ tune.formatting[cmd] = restOfString;
539
+ break;
540
+ default:
541
+ return "Unknown directive: " + cmd;
542
+ }
543
+ return null;
544
+ };
545
+
546
+ })();