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