abcjs 6.0.0-beta.8 → 6.0.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.
- package/.github/workflows/tests.yml +29 -0
- package/CODE_OF_CONDUCT.md +76 -0
- package/CONTRIBUTING.md +1 -0
- package/LICENSE.md +1 -1
- package/README.md +92 -3
- package/RELEASE.md +959 -1
- package/abcjs-audio.css +14 -5
- package/dist/.gitignore +1 -2
- package/dist/abcjs-basic-min.js +3 -0
- package/dist/abcjs-basic-min.js.LICENSE +23 -0
- package/dist/abcjs-basic.js +28232 -0
- package/dist/abcjs-basic.js.map +1 -0
- package/dist/abcjs-plugin-min.js +3 -0
- package/dist/abcjs-plugin-min.js.LICENSE +23 -0
- package/dist/report-basic.html +37 -0
- package/dist/report-before-glyph-compress.html +37 -0
- package/dist/report-brown-ts-target-es5.html +37 -0
- package/dist/report-dev-orig-no-babel.html +37 -0
- package/dist/report-synth.html +37 -0
- package/docker-build.sh +1 -0
- package/glyphs.json +1 -0
- package/index.js +27 -2
- package/{static-wrappers/license.js → license.js} +1 -1
- package/package.json +26 -29
- package/{src/plugin/abc_plugin.js → plugin.js} +31 -19
- package/src/api/abc_animation.js +1 -17
- package/src/api/abc_tablatures.js +144 -0
- package/src/api/abc_timing_callbacks.js +239 -116
- package/src/api/abc_tunebook.js +18 -67
- package/src/api/abc_tunebook_svg.js +38 -46
- package/src/data/abc_tune.js +232 -972
- package/src/data/deline-tune.js +199 -0
- package/src/edit/abc_editarea.js +112 -0
- package/src/edit/abc_editor.js +95 -221
- package/src/midi/abc_midi_create.js +48 -50
- package/src/parse/abc_common.js +0 -14
- package/src/parse/abc_parse.js +167 -1321
- package/src/parse/abc_parse_book.js +62 -0
- package/src/parse/abc_parse_directive.js +164 -41
- package/src/parse/abc_parse_header.js +116 -145
- package/src/parse/abc_parse_key_voice.js +26 -20
- package/src/parse/abc_parse_music.js +1337 -0
- package/src/parse/abc_tokenizer.js +21 -15
- package/src/parse/abc_transpose.js +3 -15
- package/src/parse/tune-builder.js +896 -0
- package/src/parse/wrap_lines.js +205 -453
- package/src/synth/abc_midi_flattener.js +1292 -0
- package/src/{midi → synth}/abc_midi_renderer.js +44 -17
- package/src/synth/abc_midi_sequencer.js +648 -0
- package/src/synth/active-audio-context.js +3 -14
- package/src/synth/cents-to-factor.js +10 -0
- package/src/synth/create-note-map.js +21 -32
- package/src/synth/create-synth-control.js +20 -103
- package/src/synth/create-synth.js +185 -77
- package/src/synth/download-buffer.js +7 -21
- package/src/synth/get-midi-file.js +13 -20
- package/src/synth/images/{loading.svg → loading.svg.js} +4 -0
- package/src/synth/images/loop.svg.js +65 -0
- package/src/synth/images/pause.svg.js +10 -0
- package/src/synth/images/play.svg.js +9 -0
- package/src/synth/images/{reset.svg → reset.svg.js} +5 -1
- package/src/synth/instrument-index-to-name.js +1 -16
- package/src/synth/load-note.js +37 -76
- package/src/synth/pitch-to-note-name.js +0 -15
- package/src/synth/pitches-to-perc.js +64 -0
- package/src/synth/place-note.js +78 -68
- package/src/synth/play-event.js +17 -18
- package/src/synth/register-audio-context.js +11 -23
- package/src/synth/sounds-cache.js +0 -15
- package/src/synth/supports-audio.js +9 -23
- package/src/synth/synth-controller.js +80 -49
- package/src/synth/synth-sequence.js +20 -34
- package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
- package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
- package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
- package/src/tablatures/instruments/string-patterns.js +277 -0
- package/src/tablatures/instruments/string-tablature.js +56 -0
- package/src/tablatures/instruments/tab-note.js +282 -0
- package/src/tablatures/instruments/tab-notes.js +41 -0
- package/src/tablatures/instruments/violin/tab-violin.js +47 -0
- package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
- package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
- package/src/tablatures/tab-absolute-elements.js +310 -0
- package/src/tablatures/tab-common.js +29 -0
- package/src/tablatures/tab-renderer.js +243 -0
- package/src/tablatures/transposer.js +110 -0
- package/src/test/abc_midi_lint.js +5 -22
- package/src/test/abc_midi_sequencer_lint.js +11 -14
- package/src/test/abc_parser_lint.js +136 -32
- package/src/test/abc_vertical_lint.js +94 -32
- package/src/test/rendering-lint.js +38 -5
- package/src/write/abc_absolute_element.js +112 -120
- package/src/write/abc_abstract_engraver.js +102 -253
- package/src/write/abc_beam_element.js +30 -290
- package/src/write/abc_brace_element.js +12 -121
- package/src/write/abc_create_clef.js +21 -32
- package/src/write/abc_create_key_signature.js +8 -26
- package/src/write/abc_create_note_head.js +107 -0
- package/src/write/abc_create_time_signature.js +2 -21
- package/src/write/abc_crescendo_element.js +3 -50
- package/src/write/abc_decoration.js +7 -30
- package/src/write/abc_dynamic_decoration.js +3 -37
- package/src/write/abc_ending_element.js +1 -57
- package/src/write/abc_engraver_controller.js +111 -234
- package/src/write/abc_glyphs.js +8 -18
- package/src/write/abc_relative_element.js +57 -97
- package/src/write/abc_renderer.js +10 -832
- package/src/write/abc_spacing.js +0 -15
- package/src/write/abc_staff_group_element.js +14 -349
- package/src/write/abc_tempo_element.js +9 -117
- package/src/write/abc_tie_element.js +5 -68
- package/src/write/abc_triplet_element.js +6 -124
- package/src/write/abc_voice_element.js +7 -222
- package/src/write/add-chord.js +103 -0
- package/src/write/add-text-if.js +33 -0
- package/src/write/bottom-text.js +79 -0
- package/src/write/calcHeight.js +17 -0
- package/src/write/classes.js +100 -0
- package/src/write/draw/absolute.js +68 -0
- package/src/write/draw/beam.js +56 -0
- package/src/write/draw/brace.js +106 -0
- package/src/write/draw/crescendo.js +38 -0
- package/src/write/draw/debug-box.js +8 -0
- package/src/write/draw/draw.js +56 -0
- package/src/write/draw/dynamics.js +20 -0
- package/src/write/draw/ending.js +46 -0
- package/src/write/draw/group-elements.js +66 -0
- package/src/write/draw/horizontal-line.js +25 -0
- package/src/write/draw/non-music.js +50 -0
- package/src/write/draw/print-line.js +24 -0
- package/src/write/draw/print-path.js +7 -0
- package/src/write/draw/print-stem.js +30 -0
- package/src/write/draw/print-symbol.js +59 -0
- package/src/write/draw/print-vertical-line.js +18 -0
- package/src/write/draw/relative.js +77 -0
- package/src/write/draw/round-number.js +5 -0
- package/src/write/draw/selectables.js +59 -0
- package/src/write/draw/separator.js +16 -0
- package/src/write/draw/set-paper-size.js +45 -0
- package/src/write/{sprintf.js → draw/sprintf.js} +0 -0
- package/src/write/draw/staff-group.js +226 -0
- package/src/write/draw/staff-line.js +9 -0
- package/src/write/draw/staff.js +33 -0
- package/src/write/draw/tab-line.js +40 -0
- package/src/write/draw/tempo.js +45 -0
- package/src/write/draw/text.js +71 -0
- package/src/write/draw/tie.js +97 -0
- package/src/write/draw/triplet.js +46 -0
- package/src/write/draw/voice.js +102 -0
- package/src/write/format-jazz-chord.js +15 -0
- package/src/write/free-text.js +41 -0
- package/src/write/get-font-and-attr.js +37 -0
- package/src/write/get-text-size.js +56 -0
- package/src/write/highlight.js +11 -0
- package/src/write/layout/VoiceElements.js +121 -0
- package/src/write/layout/beam.js +213 -0
- package/src/write/layout/get-left-edge-of-staff.js +56 -0
- package/src/write/layout/getBarYAt.js +6 -0
- package/src/write/layout/layout.js +94 -0
- package/src/write/layout/setUpperAndLowerElements.js +232 -0
- package/src/write/layout/staffGroup.js +146 -0
- package/src/write/layout/triplet.js +75 -0
- package/src/write/layout/voice.js +137 -0
- package/src/write/selection.js +188 -70
- package/src/write/separator.js +10 -0
- package/src/write/set-class.js +21 -0
- package/src/write/subtitle.js +12 -0
- package/src/write/svg.js +95 -43
- package/src/write/top-text.js +54 -0
- package/src/write/unhighlight.js +11 -0
- package/temp.txt +15256 -0
- package/test.js +27 -64
- package/types/index.d.ts +1095 -0
- package/version.js +1 -1
- package/.babelrc +0 -5
- package/.eslintrc +0 -3
- package/.gitmodules +0 -3
- package/abcjs-midi.css +0 -166
- package/build-utils/loadPresets.js +0 -14
- package/build-utils/presets/webpack.analyze.js +0 -6
- package/build-utils/presets/webpack.optimize.js +0 -30
- package/build-utils/webpack.development.js +0 -14
- package/build-utils/webpack.production.js +0 -35
- package/deploy-docs.sh +0 -25
- package/docs/README.md +0 -33
- package/fix-versions.sh +0 -23
- package/mei.js +0 -43
- package/midi.js +0 -62
- package/src/api/abc_tunebook_midi.js +0 -116
- package/src/midi/abc_midi_controls.js +0 -701
- package/src/midi/abc_midi_flattener.js +0 -1119
- package/src/midi/abc_midi_js_preparer.js +0 -243
- package/src/midi/abc_midi_sequencer.js +0 -401
- package/src/midi/abc_midi_ui_generator.js +0 -86
- package/src/plugin/abc_plugin_midi.js +0 -220
- package/src/synth/images/loop.svg +0 -61
- package/src/synth/images/pause.svg +0 -6
- package/src/synth/images/play.svg +0 -5
- package/src/transform/abc2abc_write.js +0 -395
- package/static-wrappers/basic.js +0 -2
- package/static-wrappers/midi.js +0 -2
- package/static-wrappers/plugin-midi.js +0 -6
- package/static-wrappers/plugin.js +0 -6
- package/webpack.config.js +0 -29
|
@@ -0,0 +1,1292 @@
|
|
|
1
|
+
// abc_midi_flattener.js: Turn a linear series of events into a series of MIDI commands.
|
|
2
|
+
|
|
3
|
+
// We input a set of voices, but the notes are still complex. This pass changes the logical definitions
|
|
4
|
+
// of the grace notes, decorations, ties, triplets, rests, transpositions, keys, and accidentals into actual note durations.
|
|
5
|
+
// It also extracts guitar chords to a separate voice and resolves their rhythm.
|
|
6
|
+
|
|
7
|
+
var flatten;
|
|
8
|
+
var parseCommon = require("../parse/abc_common");
|
|
9
|
+
var pitchesToPerc = require('./pitches-to-perc');
|
|
10
|
+
|
|
11
|
+
(function() {
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
var barAccidentals;
|
|
15
|
+
var accidentals;
|
|
16
|
+
var transpose;
|
|
17
|
+
var bagpipes;
|
|
18
|
+
var tracks;
|
|
19
|
+
var startingTempo;
|
|
20
|
+
var startingMeter;
|
|
21
|
+
var tempoChangeFactor = 1;
|
|
22
|
+
var instrument;
|
|
23
|
+
var currentInstrument;
|
|
24
|
+
// var channel;
|
|
25
|
+
var currentTrack;
|
|
26
|
+
var lastNoteDurationPosition;
|
|
27
|
+
var currentTrackName;
|
|
28
|
+
var lastEventTime;
|
|
29
|
+
|
|
30
|
+
var meter = { num: 4, den: 4 };
|
|
31
|
+
var chordTrack;
|
|
32
|
+
var chordSourceTrack;
|
|
33
|
+
var chordTrackFinished;
|
|
34
|
+
var chordChannel;
|
|
35
|
+
var chordInstrument = 0;
|
|
36
|
+
var drumInstrument = 128;
|
|
37
|
+
var boomVolume = 64;
|
|
38
|
+
var chickVolume = 48;
|
|
39
|
+
var currentChords;
|
|
40
|
+
var lastChord;
|
|
41
|
+
var chordLastBar;
|
|
42
|
+
var lastBarTime;
|
|
43
|
+
var gChordTacet = false;
|
|
44
|
+
var hasRhythmHead = false;
|
|
45
|
+
var doBeatAccents = true;
|
|
46
|
+
var stressBeat1 = 105;
|
|
47
|
+
var stressBeatDown = 95;
|
|
48
|
+
var stressBeatUp = 85;
|
|
49
|
+
var beatFraction = 0.25;
|
|
50
|
+
var nextVolume;
|
|
51
|
+
var nextVolumeDelta;
|
|
52
|
+
var slurCount = 0;
|
|
53
|
+
|
|
54
|
+
var drumTrack;
|
|
55
|
+
var drumTrackFinished;
|
|
56
|
+
var drumDefinition = {};
|
|
57
|
+
|
|
58
|
+
var pickupLength = 0;
|
|
59
|
+
var percmap;
|
|
60
|
+
|
|
61
|
+
// The gaps per beat. The first two are in seconds, the third is in fraction of a duration.
|
|
62
|
+
var normalBreakBetweenNotes = 0; //0.000520833333325*1.5; // for articulation (matches muse score value)
|
|
63
|
+
var slurredBreakBetweenNotes = -0.001; // make the slurred notes actually overlap
|
|
64
|
+
var staccatoBreakBetweenNotes = 0.4; // some people say staccato is half duration, some say 3/4 so this splits it
|
|
65
|
+
|
|
66
|
+
flatten = function(voices, options, percmap_) {
|
|
67
|
+
if (!options) options = {};
|
|
68
|
+
barAccidentals = [];
|
|
69
|
+
accidentals = [0,0,0,0,0,0,0];
|
|
70
|
+
bagpipes = false;
|
|
71
|
+
tracks = [];
|
|
72
|
+
startingTempo = options.qpm;
|
|
73
|
+
startingMeter = undefined;
|
|
74
|
+
tempoChangeFactor = 1;
|
|
75
|
+
instrument = undefined;
|
|
76
|
+
currentInstrument = undefined;
|
|
77
|
+
// channel = undefined;
|
|
78
|
+
currentTrack = undefined;
|
|
79
|
+
currentTrackName = undefined;
|
|
80
|
+
lastEventTime = 0;
|
|
81
|
+
percmap = percmap_;
|
|
82
|
+
|
|
83
|
+
// For resolving chords.
|
|
84
|
+
meter = { num: 4, den: 4 };
|
|
85
|
+
chordTrack = [];
|
|
86
|
+
chordSourceTrack = false;
|
|
87
|
+
chordChannel = voices.length; // first free channel for chords
|
|
88
|
+
chordTrackFinished = false;
|
|
89
|
+
currentChords = [];
|
|
90
|
+
boomVolume = 64;
|
|
91
|
+
chickVolume = 48;
|
|
92
|
+
lastChord = undefined;
|
|
93
|
+
chordLastBar = undefined;
|
|
94
|
+
gChordTacet = options.chordsOff ? true : false;
|
|
95
|
+
hasRhythmHead = false;
|
|
96
|
+
|
|
97
|
+
doBeatAccents = true;
|
|
98
|
+
stressBeat1 = 105;
|
|
99
|
+
stressBeatDown = 95;
|
|
100
|
+
stressBeatUp = 85;
|
|
101
|
+
beatFraction = 0.25;
|
|
102
|
+
nextVolume = undefined;
|
|
103
|
+
nextVolumeDelta = undefined;
|
|
104
|
+
slurCount = 0;
|
|
105
|
+
|
|
106
|
+
// For the drum/metronome track.
|
|
107
|
+
drumTrack = [];
|
|
108
|
+
drumTrackFinished = false;
|
|
109
|
+
drumDefinition = {};
|
|
110
|
+
|
|
111
|
+
if (voices.length > 0 && voices[0].length > 0)
|
|
112
|
+
pickupLength = voices[0][0].pickupLength;
|
|
113
|
+
|
|
114
|
+
// First adjust the input to resolve ties, set the starting time for each note, etc. That will make the rest of the logic easier
|
|
115
|
+
preProcess(voices, options);
|
|
116
|
+
|
|
117
|
+
for (var i = 0; i < voices.length; i++) {
|
|
118
|
+
transpose = 0;
|
|
119
|
+
lastNoteDurationPosition = -1;
|
|
120
|
+
var voice = voices[i];
|
|
121
|
+
currentTrack = [{ cmd: 'program', channel: i, instrument: instrument }];
|
|
122
|
+
currentTrackName = undefined;
|
|
123
|
+
lastBarTime = 0;
|
|
124
|
+
var voiceOff = false;
|
|
125
|
+
if (options.voicesOff === true)
|
|
126
|
+
voiceOff = true;
|
|
127
|
+
else if (options.voicesOff && options.voicesOff.length && options.voicesOff.indexOf(i) >= 0)
|
|
128
|
+
voiceOff = true;
|
|
129
|
+
for (var j = 0; j < voice.length; j++) {
|
|
130
|
+
var element = voice[j];
|
|
131
|
+
switch (element.el_type) {
|
|
132
|
+
case "name":
|
|
133
|
+
currentTrackName = {cmd: 'text', type: "name", text: element.trackName };
|
|
134
|
+
break;
|
|
135
|
+
case "note":
|
|
136
|
+
var setChordTrack = writeNote(element, voiceOff);
|
|
137
|
+
if (setChordTrack)
|
|
138
|
+
chordSourceTrack = i;
|
|
139
|
+
break;
|
|
140
|
+
case "key":
|
|
141
|
+
accidentals = setKeySignature(element);
|
|
142
|
+
break;
|
|
143
|
+
case "meter":
|
|
144
|
+
if (!startingMeter)
|
|
145
|
+
startingMeter = element;
|
|
146
|
+
meter = element;
|
|
147
|
+
beatFraction = getBeatFraction(meter);
|
|
148
|
+
break;
|
|
149
|
+
case "tempo":
|
|
150
|
+
if (!startingTempo)
|
|
151
|
+
startingTempo = element.qpm;
|
|
152
|
+
else
|
|
153
|
+
tempoChangeFactor = element.qpm ? startingTempo / element.qpm : 1;
|
|
154
|
+
break;
|
|
155
|
+
case "transpose":
|
|
156
|
+
transpose = element.transpose;
|
|
157
|
+
break;
|
|
158
|
+
case "bar":
|
|
159
|
+
if (chordTrack.length > 0 && (chordSourceTrack === false || i === chordSourceTrack)) {
|
|
160
|
+
resolveChords(lastBarTime, timeToRealTime(element.time));
|
|
161
|
+
currentChords = [];
|
|
162
|
+
}
|
|
163
|
+
barAccidentals = [];
|
|
164
|
+
if (i === 0) // Only write the drum part on the first voice so that it is not duplicated.
|
|
165
|
+
writeDrum(voices.length+1);
|
|
166
|
+
hasRhythmHead = false; // decide whether there are rhythm heads each measure.
|
|
167
|
+
chordLastBar = lastChord;
|
|
168
|
+
lastBarTime = timeToRealTime(element.time);
|
|
169
|
+
break;
|
|
170
|
+
case "bagpipes":
|
|
171
|
+
bagpipes = true;
|
|
172
|
+
break;
|
|
173
|
+
case "instrument":
|
|
174
|
+
if (instrument === undefined)
|
|
175
|
+
instrument = element.program;
|
|
176
|
+
currentInstrument = element.program;
|
|
177
|
+
if (currentTrack.length > 0 && currentTrack[currentTrack.length-1].cmd === 'program')
|
|
178
|
+
currentTrack[currentTrack.length-1].instrument = element.program;
|
|
179
|
+
else {
|
|
180
|
+
var ii;
|
|
181
|
+
for (ii = currentTrack.length-1; ii >= 0 && currentTrack[ii].cmd !== 'program'; ii--)
|
|
182
|
+
;
|
|
183
|
+
if (ii < 0 || currentTrack[ii].instrument !== element.program)
|
|
184
|
+
currentTrack.push({cmd: 'program', channel: 0, instrument: element.program});
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
case "channel":
|
|
188
|
+
setChannel(element.channel);
|
|
189
|
+
break;
|
|
190
|
+
case "drum":
|
|
191
|
+
drumDefinition = normalizeDrumDefinition(element.params);
|
|
192
|
+
break;
|
|
193
|
+
case "gchord":
|
|
194
|
+
if (!options.chordsOff)
|
|
195
|
+
gChordTacet = element.tacet;
|
|
196
|
+
break;
|
|
197
|
+
case "beat":
|
|
198
|
+
stressBeat1 = element.beats[0];
|
|
199
|
+
stressBeatDown = element.beats[1];
|
|
200
|
+
stressBeatUp = element.beats[2];
|
|
201
|
+
// TODO-PER: also use the last parameter - which changes which beats are strong.
|
|
202
|
+
break;
|
|
203
|
+
case "vol":
|
|
204
|
+
nextVolume = element.volume;
|
|
205
|
+
break;
|
|
206
|
+
case "volinc":
|
|
207
|
+
nextVolumeDelta = element.volume;
|
|
208
|
+
break;
|
|
209
|
+
case "beataccents":
|
|
210
|
+
doBeatAccents = element.value;
|
|
211
|
+
break;
|
|
212
|
+
default:
|
|
213
|
+
// This should never happen
|
|
214
|
+
console.log("MIDI creation. Unknown el_type: " + element.el_type + "\n");// jshint ignore:line
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (currentTrack[0].instrument === undefined)
|
|
219
|
+
currentTrack[0].instrument = instrument ? instrument : 0;
|
|
220
|
+
if (currentTrackName)
|
|
221
|
+
currentTrack.unshift(currentTrackName);
|
|
222
|
+
tracks.push(currentTrack);
|
|
223
|
+
if (!chordTrackEmpty()) // Don't do chords on more than one track, so turn off chord detection after we create it.
|
|
224
|
+
chordTrackFinished = true;
|
|
225
|
+
if (drumTrack.length > 0) // Don't do drums on more than one track, so turn off drum after we create it.
|
|
226
|
+
drumTrackFinished = true;
|
|
227
|
+
}
|
|
228
|
+
// See if any notes are octaves played at the same time. If so, raise the pitch of the higher one.
|
|
229
|
+
if (options.detuneOctave)
|
|
230
|
+
findOctaves(tracks, parseInt(options.detuneOctave, 10));
|
|
231
|
+
|
|
232
|
+
if (!chordTrackEmpty())
|
|
233
|
+
tracks.push(chordTrack);
|
|
234
|
+
if (drumTrack.length > 0)
|
|
235
|
+
tracks.push(drumTrack);
|
|
236
|
+
|
|
237
|
+
return { tempo: startingTempo, instrument: instrument, tracks: tracks, totalDuration: lastEventTime };
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
function setChannel(channel) {
|
|
241
|
+
for (var i = currentTrack.length-1; i>=0; i--) {
|
|
242
|
+
if (currentTrack[i].cmd === "program") {
|
|
243
|
+
currentTrack[i].channel = channel;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function chordTrackEmpty() {
|
|
250
|
+
var isEmpty = true;
|
|
251
|
+
for (var i = 0; i < chordTrack.length && isEmpty; i++) {
|
|
252
|
+
if (chordTrack[i].cmd === 'note')
|
|
253
|
+
isEmpty = false
|
|
254
|
+
}
|
|
255
|
+
return isEmpty;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function timeToRealTime(time) {
|
|
259
|
+
return time/1000000;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function durationRounded(duration) {
|
|
263
|
+
return Math.round(duration*tempoChangeFactor*1000000)/1000000;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function preProcess(voices, options) {
|
|
267
|
+
for (var i = 0; i < voices.length; i++) {
|
|
268
|
+
var voice = voices[i];
|
|
269
|
+
var ties = {};
|
|
270
|
+
var startingTempo = options.qpm;
|
|
271
|
+
var timeCounter = 0;
|
|
272
|
+
var tempoMultiplier = 1;
|
|
273
|
+
for (var j = 0; j < voice.length; j++) {
|
|
274
|
+
var element = voice[j];
|
|
275
|
+
|
|
276
|
+
if (element.el_type === 'tempo') {
|
|
277
|
+
if (!startingTempo)
|
|
278
|
+
startingTempo = element.qpm;
|
|
279
|
+
else
|
|
280
|
+
tempoMultiplier = element.qpm ? startingTempo / element.qpm : 1;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// For convenience, put the current time in each event so that it doesn't have to be calculated in the complicated stuff that follows.
|
|
285
|
+
element.time = timeCounter;
|
|
286
|
+
var thisDuration = element.duration ? element.duration : 0;
|
|
287
|
+
timeCounter += Math.round(thisDuration*tempoMultiplier*1000000); // To compensate for JS rounding problems, do all intermediate calcs on integers.
|
|
288
|
+
|
|
289
|
+
// If there are pitches then put the duration in the pitch object and if there are ties then change the duration of the first note in the tie.
|
|
290
|
+
if (element.pitches) {
|
|
291
|
+
for (var k = 0; k < element.pitches.length; k++) {
|
|
292
|
+
var pitch = element.pitches[k];
|
|
293
|
+
if (pitch) {
|
|
294
|
+
pitch.duration = element.duration;
|
|
295
|
+
if (pitch.startTie) {
|
|
296
|
+
//console.log(element)
|
|
297
|
+
if (ties[pitch.pitch] === undefined) // We might have three notes tied together - if so just add this duration.
|
|
298
|
+
ties[pitch.pitch] = {el: j, pitch: k};
|
|
299
|
+
else {
|
|
300
|
+
voice[ties[pitch.pitch].el].pitches[ties[pitch.pitch].pitch].duration += pitch.duration;
|
|
301
|
+
element.pitches[k] = null;
|
|
302
|
+
}
|
|
303
|
+
//console.log(">>> START", JSON.stringify(ties));
|
|
304
|
+
} else if (pitch.endTie) {
|
|
305
|
+
//console.log(element)
|
|
306
|
+
var tie = ties[pitch.pitch];
|
|
307
|
+
//console.log(">>> END", pitch.pitch, tie, JSON.stringify(ties));
|
|
308
|
+
if (tie) {
|
|
309
|
+
var dur = pitch.duration;
|
|
310
|
+
delete voice[tie.el].pitches[tie.pitch].startTie;
|
|
311
|
+
voice[tie.el].pitches[tie.pitch].duration += dur;
|
|
312
|
+
element.pitches[k] = null;
|
|
313
|
+
delete ties[pitch.pitch];
|
|
314
|
+
} else {
|
|
315
|
+
delete pitch.endTie;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
delete element.duration;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
for (var key in ties) {
|
|
324
|
+
if (ties.hasOwnProperty(key)) {
|
|
325
|
+
var item = ties[key];
|
|
326
|
+
delete voice[item.el].pitches[item.pitch].startTie;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
// voices[0].forEach(v => delete v.elem)
|
|
330
|
+
// voices[1].forEach(v => delete v.elem)
|
|
331
|
+
// console.log(JSON.stringify(voices))
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function getBeatFraction(meter) {
|
|
336
|
+
switch (parseInt(meter.den,10)) {
|
|
337
|
+
case 2: return 0.5;
|
|
338
|
+
case 4: return 0.25;
|
|
339
|
+
case 8:
|
|
340
|
+
if (meter.num % 3 === 0)
|
|
341
|
+
return 0.375;
|
|
342
|
+
else
|
|
343
|
+
return 0.125;
|
|
344
|
+
case 16: return 0.125;
|
|
345
|
+
}
|
|
346
|
+
return 0.25;
|
|
347
|
+
}
|
|
348
|
+
//
|
|
349
|
+
// The algorithm for chords is:
|
|
350
|
+
// - The chords are done in a separate track.
|
|
351
|
+
// - If there are notes before the first chord, then put that much silence to start the track.
|
|
352
|
+
// - The pattern of chord expression depends on the meter, and how many chords are in a measure.
|
|
353
|
+
// - There is a possibility that a measure will have an incorrect number of beats, if that is the case, then
|
|
354
|
+
// start the pattern anew on the next measure number.
|
|
355
|
+
// - If a chord root is not A-G, then ignore it as if the chord wasn't there at all.
|
|
356
|
+
// - If a chord modification isn't in our supported list, change it to a major triad.
|
|
357
|
+
//
|
|
358
|
+
// - If there is only one chord in a measure:
|
|
359
|
+
// - If 2/4, play root chord
|
|
360
|
+
// - If cut time, play root(1) chord(3)
|
|
361
|
+
// - If 3/4, play root chord chord
|
|
362
|
+
// - If 4/4 or common time, play root chord fifth chord
|
|
363
|
+
// - If 6/8, play root(1) chord(3) fifth(4) chord(6)
|
|
364
|
+
// - For any other meter, play the full chord on each beat. (TODO-PER: expand this as more support is added.)
|
|
365
|
+
//
|
|
366
|
+
// - 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.
|
|
367
|
+
// - Otherwise, move it later, unless there is already a chord on that beat.
|
|
368
|
+
// - Otherwise, ignore it. (TODO-PER: expand this as more support is added.)
|
|
369
|
+
//
|
|
370
|
+
// - If there is a chord on the second beat, play a chord for the first beat instead of a bass note.
|
|
371
|
+
// - 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.
|
|
372
|
+
//
|
|
373
|
+
// If there is any note in the melody that has a rhythm head, then assume the melody controls the rhythm, so that is
|
|
374
|
+
// the same as a break.
|
|
375
|
+
var breakSynonyms = [ 'break', '(break)', 'no chord', 'n.c.', 'tacet'];
|
|
376
|
+
|
|
377
|
+
function findChord(elem) {
|
|
378
|
+
if (gChordTacet)
|
|
379
|
+
return 'break';
|
|
380
|
+
|
|
381
|
+
// TODO-PER: Just using the first chord if there are more than one.
|
|
382
|
+
if (chordTrackFinished || !elem.chord || elem.chord.length === 0)
|
|
383
|
+
return null;
|
|
384
|
+
|
|
385
|
+
// Return the first annotation that is a regular chord: that is, it is in the default place or is a recognized "tacet" phrase.
|
|
386
|
+
for (var i = 0; i < elem.chord.length; i++) {
|
|
387
|
+
var ch = elem.chord[i];
|
|
388
|
+
if (ch.position === 'default')
|
|
389
|
+
return ch.name;
|
|
390
|
+
if (breakSynonyms.indexOf(ch.name.toLowerCase()) >= 0)
|
|
391
|
+
return 'break';
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function calcBeat(measureStart, beatLength, currTime) {
|
|
397
|
+
var distanceFromStart = currTime - measureStart;
|
|
398
|
+
return distanceFromStart / beatLength;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function processVolume(beat, voiceOff) {
|
|
402
|
+
if (voiceOff)
|
|
403
|
+
return 0;
|
|
404
|
+
|
|
405
|
+
var volume;
|
|
406
|
+
if (nextVolume) {
|
|
407
|
+
volume = nextVolume;
|
|
408
|
+
nextVolume = undefined;
|
|
409
|
+
} else if (!doBeatAccents) {
|
|
410
|
+
volume = stressBeatDown;
|
|
411
|
+
} else if (pickupLength > beat) {
|
|
412
|
+
volume = stressBeatUp;
|
|
413
|
+
} else {
|
|
414
|
+
var barLength = meter.num / meter.den;
|
|
415
|
+
var barBeat = calcBeat(lastBarTime, getBeatFraction(meter), beat);
|
|
416
|
+
if (barBeat === 0)
|
|
417
|
+
volume = stressBeat1;
|
|
418
|
+
else if (parseInt(barBeat,10) === barBeat)
|
|
419
|
+
volume = stressBeatDown;
|
|
420
|
+
else
|
|
421
|
+
volume = stressBeatUp;
|
|
422
|
+
}
|
|
423
|
+
if (nextVolumeDelta) {
|
|
424
|
+
volume += nextVolumeDelta;
|
|
425
|
+
nextVolumeDelta = undefined;
|
|
426
|
+
}
|
|
427
|
+
if (volume < 0)
|
|
428
|
+
volume = 0;
|
|
429
|
+
if (volume > 127)
|
|
430
|
+
volume = 127;
|
|
431
|
+
return voiceOff ? 0 : volume;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function processChord(elem) {
|
|
435
|
+
|
|
436
|
+
var firstChord = false;
|
|
437
|
+
var chord = findChord(elem);
|
|
438
|
+
if (chord) {
|
|
439
|
+
var c = interpretChord(chord);
|
|
440
|
+
// If this isn't a recognized chord, just completely ignore it.
|
|
441
|
+
if (c) {
|
|
442
|
+
// If we ever have a chord in this voice, then we add the chord track.
|
|
443
|
+
// However, if there are chords on more than one voice, then just use the first voice.
|
|
444
|
+
if (chordTrack.length === 0) {
|
|
445
|
+
firstChord = true;
|
|
446
|
+
chordTrack.push({cmd: 'program', channel: chordChannel, instrument: chordInstrument});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
lastChord = c;
|
|
450
|
+
var barBeat = calcBeat(lastBarTime, getBeatFraction(meter), timeToRealTime(elem.time));
|
|
451
|
+
currentChords.push({chord: lastChord, beat: barBeat, start: timeToRealTime(elem.time)});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return firstChord;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function findNoteModifications(elem, velocity) {
|
|
458
|
+
var ret = { };
|
|
459
|
+
if (elem.decoration) {
|
|
460
|
+
for (var d = 0; d < elem.decoration.length; d++) {
|
|
461
|
+
if (elem.decoration[d] === 'staccato')
|
|
462
|
+
ret.thisBreakBetweenNotes = 'staccato';
|
|
463
|
+
else if (elem.decoration[d] === 'tenuto')
|
|
464
|
+
ret.thisBreakBetweenNotes = 'tenuto';
|
|
465
|
+
else if (elem.decoration[d] === 'accent')
|
|
466
|
+
ret.velocity = Math.min(127, velocity * 1.5);
|
|
467
|
+
else if (elem.decoration[d] === 'trill')
|
|
468
|
+
ret.noteModification = "trill";
|
|
469
|
+
else if (elem.decoration[d] === 'lowermordent')
|
|
470
|
+
ret.noteModification = "lowermordent";
|
|
471
|
+
else if (elem.decoration[d] === 'uppermordent')
|
|
472
|
+
ret.noteModification = "mordent";
|
|
473
|
+
else if (elem.decoration[d] === 'mordent')
|
|
474
|
+
ret.noteModification = "mordent";
|
|
475
|
+
else if (elem.decoration[d] === 'turn')
|
|
476
|
+
ret.noteModification = "turn";
|
|
477
|
+
else if (elem.decoration[d] === 'roll')
|
|
478
|
+
ret.noteModification = "roll";
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return ret;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function doModifiedNotes(noteModification, p) {
|
|
485
|
+
var noteTime;
|
|
486
|
+
var numNotes;
|
|
487
|
+
var start = p.start;
|
|
488
|
+
var pp;
|
|
489
|
+
var runningDuration = p.duration;
|
|
490
|
+
var shortestNote = durationRounded(1.0 / 32);
|
|
491
|
+
|
|
492
|
+
switch (noteModification) {
|
|
493
|
+
case "trill":
|
|
494
|
+
var note = 1;
|
|
495
|
+
while (runningDuration > 0) {
|
|
496
|
+
currentTrack.push({ cmd: 'note', pitch: p.pitch+note, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
|
|
497
|
+
note = (note === 1) ? 0 : 1;
|
|
498
|
+
runningDuration -= shortestNote;
|
|
499
|
+
start += shortestNote;
|
|
500
|
+
}
|
|
501
|
+
break;
|
|
502
|
+
case "mordent":
|
|
503
|
+
currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
|
|
504
|
+
runningDuration -= shortestNote;
|
|
505
|
+
start += shortestNote;
|
|
506
|
+
currentTrack.push({ cmd: 'note', pitch: p.pitch+1, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
|
|
507
|
+
runningDuration -= shortestNote;
|
|
508
|
+
start += shortestNote;
|
|
509
|
+
currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: runningDuration, gap: 0, instrument: currentInstrument });
|
|
510
|
+
break;
|
|
511
|
+
case "lowermordent":
|
|
512
|
+
currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
|
|
513
|
+
runningDuration -= shortestNote;
|
|
514
|
+
start += shortestNote;
|
|
515
|
+
currentTrack.push({ cmd: 'note', pitch: p.pitch-1, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
|
|
516
|
+
runningDuration -= shortestNote;
|
|
517
|
+
start += shortestNote;
|
|
518
|
+
currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: runningDuration, gap: 0, instrument: currentInstrument });
|
|
519
|
+
break;
|
|
520
|
+
case "turn":
|
|
521
|
+
shortestNote = p.duration / 5;
|
|
522
|
+
currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
|
|
523
|
+
currentTrack.push({ cmd: 'note', pitch: p.pitch+1, volume: p.volume, start: start+shortestNote, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
|
|
524
|
+
currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start+shortestNote*2, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
|
|
525
|
+
currentTrack.push({ cmd: 'note', pitch: p.pitch+1, volume: p.volume, start: start+shortestNote*3, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
|
|
526
|
+
currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start+shortestNote*4, duration: shortestNote, gap: 0, instrument: currentInstrument });
|
|
527
|
+
break;
|
|
528
|
+
case "roll":
|
|
529
|
+
while (runningDuration > 0) {
|
|
530
|
+
currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
|
|
531
|
+
runningDuration -= shortestNote*2;
|
|
532
|
+
start += shortestNote*2;
|
|
533
|
+
}
|
|
534
|
+
break;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function writeNote(elem, voiceOff) {
|
|
539
|
+
//
|
|
540
|
+
// Create a series of note events to append to the current track.
|
|
541
|
+
// The output event is one of: { pitchStart: pitch_in_abc_units, volume: from_1_to_64 }
|
|
542
|
+
// { pitchStop: pitch_in_abc_units }
|
|
543
|
+
// { moveTime: duration_in_abc_units }
|
|
544
|
+
// If there are guitar chords, then they are put in a separate track, but they have the same format.
|
|
545
|
+
//
|
|
546
|
+
|
|
547
|
+
var trackStartingIndex = currentTrack.length;
|
|
548
|
+
|
|
549
|
+
var velocity = processVolume(timeToRealTime(elem.time), voiceOff);
|
|
550
|
+
var setChordTrack = processChord(elem);
|
|
551
|
+
|
|
552
|
+
// if there are grace notes, then also play them.
|
|
553
|
+
// I'm not sure there is an exact rule for the length of the notes. My rule, unless I find
|
|
554
|
+
// a better one is: the grace notes cannot take more than 1/2 of the main note's value.
|
|
555
|
+
// A grace note (of 1/8 note duration) takes 1/8 of the main note's value.
|
|
556
|
+
var graces;
|
|
557
|
+
if (elem.gracenotes && elem.pitches && elem.pitches.length > 0 && elem.pitches[0]) {
|
|
558
|
+
graces = processGraceNotes(elem.gracenotes, elem.pitches[0].duration);
|
|
559
|
+
if (elem.elem)
|
|
560
|
+
elem.elem.midiGraceNotePitches = writeGraceNotes(graces, timeToRealTime(elem.time), velocity*2/3, currentInstrument); // make the graces a little quieter.
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// The beat fraction is the note that gets a beat (.25 is a quarter note)
|
|
564
|
+
// The tempo is in minutes and we want to get to milliseconds.
|
|
565
|
+
// If the element is inside a repeat, there may be more than one value. If there is one value,
|
|
566
|
+
// then just store that as a number. If there are more than one value, then change that to
|
|
567
|
+
// an array and return all of them.
|
|
568
|
+
if (elem.elem) {
|
|
569
|
+
var rt = timeToRealTime(elem.time);
|
|
570
|
+
var ms = rt / beatFraction / startingTempo * 60 * 1000;
|
|
571
|
+
if (elem.elem.currentTrackMilliseconds === undefined) {
|
|
572
|
+
elem.elem.currentTrackMilliseconds = ms;
|
|
573
|
+
elem.elem.currentTrackWholeNotes = rt;
|
|
574
|
+
} else {
|
|
575
|
+
if (elem.elem.currentTrackMilliseconds.length === undefined) {
|
|
576
|
+
if (elem.elem.currentTrackMilliseconds !== ms) {
|
|
577
|
+
elem.elem.currentTrackMilliseconds = [elem.elem.currentTrackMilliseconds, ms];
|
|
578
|
+
elem.elem.currentTrackWholeNotes = [elem.elem.currentTrackWholeNotes, rt];
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
// There can be duplicates if there are multiple voices
|
|
582
|
+
var found = false;
|
|
583
|
+
for (var j = 0; j < elem.elem.currentTrackMilliseconds.length; j++) {
|
|
584
|
+
if (elem.elem.currentTrackMilliseconds[j] === ms)
|
|
585
|
+
found = true;
|
|
586
|
+
}
|
|
587
|
+
if (!found) {
|
|
588
|
+
elem.elem.currentTrackMilliseconds.push(ms);
|
|
589
|
+
elem.elem.currentTrackWholeNotes.push(rt);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
//var tieAdjustment = 0;
|
|
595
|
+
if (elem.pitches) {
|
|
596
|
+
var thisBreakBetweenNotes = '';
|
|
597
|
+
var ret = findNoteModifications(elem, velocity);
|
|
598
|
+
if (ret.thisBreakBetweenNotes)
|
|
599
|
+
thisBreakBetweenNotes = ret.thisBreakBetweenNotes;
|
|
600
|
+
if (ret.velocity)
|
|
601
|
+
velocity = ret.velocity;
|
|
602
|
+
|
|
603
|
+
// TODO-PER: Can also make a different sound on style=x and style=harmonic
|
|
604
|
+
var ePitches = elem.pitches;
|
|
605
|
+
if (elem.style === "rhythm") {
|
|
606
|
+
hasRhythmHead = true;
|
|
607
|
+
if (lastChord && lastChord.chick) {
|
|
608
|
+
ePitches = [];
|
|
609
|
+
for (var i2 = 0; i2 < lastChord.chick.length; i2++) {
|
|
610
|
+
var note2 = parseCommon.clone(elem.pitches[0]);
|
|
611
|
+
note2.actualPitch = lastChord.chick[i2];
|
|
612
|
+
ePitches.push(note2);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (elem.elem)
|
|
618
|
+
elem.elem.midiPitches = [];
|
|
619
|
+
for (var i=0; i<ePitches.length; i++) {
|
|
620
|
+
var note = ePitches[i];
|
|
621
|
+
if (!note)
|
|
622
|
+
continue;
|
|
623
|
+
if (note.startSlur)
|
|
624
|
+
slurCount += note.startSlur.length;
|
|
625
|
+
if (note.endSlur)
|
|
626
|
+
slurCount -= note.endSlur.length;
|
|
627
|
+
var actualPitch = note.actualPitch ? note.actualPitch : adjustPitch(note);
|
|
628
|
+
if (currentInstrument === drumInstrument && percmap) {
|
|
629
|
+
var name = pitchesToPerc(note)
|
|
630
|
+
if (name && percmap[name])
|
|
631
|
+
actualPitch = percmap[name].sound;
|
|
632
|
+
}
|
|
633
|
+
var p = { cmd: 'note', pitch: actualPitch, volume: velocity, start: timeToRealTime(elem.time), duration: durationRounded(note.duration), instrument: currentInstrument };
|
|
634
|
+
p = adjustForMicroTone(p);
|
|
635
|
+
if (elem.gracenotes) {
|
|
636
|
+
p.duration = p.duration / 2;
|
|
637
|
+
p.start = p.start + p.duration;
|
|
638
|
+
}
|
|
639
|
+
if (elem.elem)
|
|
640
|
+
elem.elem.midiPitches.push(p);
|
|
641
|
+
if (ret.noteModification) {
|
|
642
|
+
doModifiedNotes(ret.noteModification, p);
|
|
643
|
+
} else {
|
|
644
|
+
if (slurCount > 0)
|
|
645
|
+
p.endType = 'tenuto';
|
|
646
|
+
else if (thisBreakBetweenNotes)
|
|
647
|
+
p.endType = thisBreakBetweenNotes;
|
|
648
|
+
|
|
649
|
+
switch (p.endType) {
|
|
650
|
+
case "tenuto":
|
|
651
|
+
p.gap = slurredBreakBetweenNotes;
|
|
652
|
+
break;
|
|
653
|
+
case "staccato":
|
|
654
|
+
var d = p.duration * staccatoBreakBetweenNotes;
|
|
655
|
+
p.gap = startingTempo / 60 * d;
|
|
656
|
+
break;
|
|
657
|
+
default:
|
|
658
|
+
p.gap = normalBreakBetweenNotes;
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
currentTrack.push(p);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
lastNoteDurationPosition = currentTrack.length-1;
|
|
665
|
+
|
|
666
|
+
}
|
|
667
|
+
var realDur = getRealDuration(elem);
|
|
668
|
+
lastEventTime = Math.max(lastEventTime, timeToRealTime(elem.time)+durationRounded(realDur));
|
|
669
|
+
|
|
670
|
+
return setChordTrack;
|
|
671
|
+
}
|
|
672
|
+
function getRealDuration(elem) {
|
|
673
|
+
if (elem.pitches && elem.pitches.length > 0 && elem.pitches[0])
|
|
674
|
+
return elem.pitches[0].duration;
|
|
675
|
+
if (elem.elem)
|
|
676
|
+
return elem.elem.duration;
|
|
677
|
+
return elem.duration;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
var scale = [0,2,4,5,7,9,11];
|
|
681
|
+
function adjustPitch(note) {
|
|
682
|
+
if (note.midipitch !== undefined)
|
|
683
|
+
return note.midipitch; // The pitch might already be known, for instance if there is a drummap.
|
|
684
|
+
var pitch = note.pitch;
|
|
685
|
+
if (note.accidental) {
|
|
686
|
+
switch(note.accidental) { // change that pitch (not other octaves) for the rest of the bar
|
|
687
|
+
case "sharp":
|
|
688
|
+
barAccidentals[pitch]=1; break;
|
|
689
|
+
case "flat":
|
|
690
|
+
barAccidentals[pitch]=-1; break;
|
|
691
|
+
case "natural":
|
|
692
|
+
barAccidentals[pitch]=0; break;
|
|
693
|
+
case "dblsharp":
|
|
694
|
+
barAccidentals[pitch]=2; break;
|
|
695
|
+
case "dblflat":
|
|
696
|
+
barAccidentals[pitch]=-2; break;
|
|
697
|
+
case "quartersharp":
|
|
698
|
+
barAccidentals[pitch]=0.25; break;
|
|
699
|
+
case "quarterflat":
|
|
700
|
+
barAccidentals[pitch]=-0.25; break;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
var actualPitch = extractOctave(pitch) *12 + scale[extractNote(pitch)] + 60;
|
|
705
|
+
|
|
706
|
+
if ( barAccidentals[pitch]!==undefined) {
|
|
707
|
+
// An accidental is always taken at face value and supersedes the key signature.
|
|
708
|
+
actualPitch += barAccidentals[pitch];
|
|
709
|
+
} else { // use normal accidentals
|
|
710
|
+
actualPitch += accidentals[extractNote(pitch)];
|
|
711
|
+
}
|
|
712
|
+
actualPitch += transpose;
|
|
713
|
+
return actualPitch;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function setKeySignature(elem) {
|
|
717
|
+
var accidentals = [0,0,0,0,0,0,0];
|
|
718
|
+
if (!elem.accidentals) return accidentals;
|
|
719
|
+
for (var i = 0; i < elem.accidentals.length; i++) {
|
|
720
|
+
var acc = elem.accidentals[i];
|
|
721
|
+
var d;
|
|
722
|
+
switch (acc.acc) {
|
|
723
|
+
case "flat": d = -1; break;
|
|
724
|
+
case "quarterflat": d = -0.25; break;
|
|
725
|
+
case "sharp": d = 1; break;
|
|
726
|
+
case "quartersharp": d = 0.25; break;
|
|
727
|
+
default: d = 0; break;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
var lowercase = acc.note.toLowerCase();
|
|
731
|
+
var note = extractNote(lowercase.charCodeAt(0)-'c'.charCodeAt(0));
|
|
732
|
+
accidentals[note]+=d;
|
|
733
|
+
}
|
|
734
|
+
return accidentals;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function processGraceNotes(graces, companionDuration) {
|
|
738
|
+
// Grace notes take up half of the note value. So if there are many of them they are all real short.
|
|
739
|
+
var graceDuration = 0;
|
|
740
|
+
var ret = [];
|
|
741
|
+
var grace;
|
|
742
|
+
for (var g = 0; g < graces.length; g++) {
|
|
743
|
+
grace = graces[g];
|
|
744
|
+
graceDuration += grace.duration;
|
|
745
|
+
}
|
|
746
|
+
var multiplier = companionDuration/2 / graceDuration;
|
|
747
|
+
|
|
748
|
+
for (g = 0; g < graces.length; g++) {
|
|
749
|
+
grace = graces[g];
|
|
750
|
+
var actualPitch = adjustPitch(grace);
|
|
751
|
+
if (currentInstrument === drumInstrument && percmap) {
|
|
752
|
+
var name = pitchesToPerc(grace)
|
|
753
|
+
if (name && percmap[name])
|
|
754
|
+
actualPitch = percmap[name].sound;
|
|
755
|
+
}
|
|
756
|
+
var pitch = { pitch: actualPitch, duration: grace.duration*multiplier };
|
|
757
|
+
pitch = adjustForMicroTone(pitch);
|
|
758
|
+
ret.push(pitch);
|
|
759
|
+
}
|
|
760
|
+
return ret;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function writeGraceNotes(graces, start, velocity, currentInstrument) {
|
|
764
|
+
var midiGrace = [];
|
|
765
|
+
velocity = Math.round(velocity)
|
|
766
|
+
for (var g = 0; g < graces.length; g++) {
|
|
767
|
+
var gp = graces[g];
|
|
768
|
+
currentTrack.push({cmd: 'note', pitch: gp.pitch, volume: velocity, start: start, duration: gp.duration, gap: 0, instrument:currentInstrument, style: 'grace'});
|
|
769
|
+
midiGrace.push({
|
|
770
|
+
pitch: gp.pitch,
|
|
771
|
+
durationInMeasures: gp.duration,
|
|
772
|
+
volume: velocity,
|
|
773
|
+
instrument: currentInstrument
|
|
774
|
+
});
|
|
775
|
+
start += gp.duration;
|
|
776
|
+
}
|
|
777
|
+
return midiGrace;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
var quarterToneFactor = 0.02930223664349;
|
|
781
|
+
function adjustForMicroTone(description) {
|
|
782
|
+
// if the pitch is not a whole number then make it a whole number and add a tuning factor
|
|
783
|
+
var pitch = ''+description.pitch;
|
|
784
|
+
if (pitch.indexOf(".75") >= 0) {
|
|
785
|
+
description.pitch = Math.round(description.pitch);
|
|
786
|
+
description.cents = -50;
|
|
787
|
+
} else if (pitch.indexOf(".25") >= 0) {
|
|
788
|
+
description.pitch = Math.round(description.pitch);
|
|
789
|
+
description.cents = 50;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return description;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function extractOctave(pitch) {
|
|
796
|
+
return Math.floor(pitch/7);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function extractNote(pitch) {
|
|
800
|
+
pitch = pitch%7;
|
|
801
|
+
if (pitch<0) pitch+=7;
|
|
802
|
+
return pitch;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
var basses = {
|
|
806
|
+
'A': 33, 'B': 35, 'C': 36, 'D': 38, 'E': 40, 'F': 41, 'G': 43
|
|
807
|
+
};
|
|
808
|
+
function interpretChord(name) {
|
|
809
|
+
// chords have the format:
|
|
810
|
+
// [root][acc][modifier][/][bass][acc]
|
|
811
|
+
// (The chord might be surrounded by parens. Just ignore them.)
|
|
812
|
+
// root must be present and must be from A-G.
|
|
813
|
+
// acc is optional and can be # or b
|
|
814
|
+
// The modifier can be a wide variety of things, like "maj7". As they are discovered, more are supported here.
|
|
815
|
+
// If there is a slash, then there is a bass note, which can be from A-G, with an optional acc.
|
|
816
|
+
// If the root is unrecognized, then "undefined" is returned and there is no chord.
|
|
817
|
+
// If the modifier is unrecognized, a major triad is returned.
|
|
818
|
+
// If the bass notes is unrecognized, it is ignored.
|
|
819
|
+
if (name.length === 0)
|
|
820
|
+
return undefined;
|
|
821
|
+
if (name === 'break')
|
|
822
|
+
return { chick: []};
|
|
823
|
+
var root = name.substring(0,1);
|
|
824
|
+
if (root === '(') {
|
|
825
|
+
name = name.substring(1,name.length-2);
|
|
826
|
+
if (name.length === 0)
|
|
827
|
+
return undefined;
|
|
828
|
+
root = name.substring(0,1);
|
|
829
|
+
}
|
|
830
|
+
var bass = basses[root];
|
|
831
|
+
if (!bass) // If the bass note isn't listed, then this was an unknown root. Only A-G are accepted.
|
|
832
|
+
return undefined;
|
|
833
|
+
// Don't transpose the chords more than an octave.
|
|
834
|
+
var chordTranspose = transpose;
|
|
835
|
+
while (chordTranspose < -8)
|
|
836
|
+
chordTranspose += 12;
|
|
837
|
+
while (chordTranspose > 8)
|
|
838
|
+
chordTranspose -= 12;
|
|
839
|
+
bass += chordTranspose;
|
|
840
|
+
var bass2 = bass - 5; // The alternating bass is a 4th below
|
|
841
|
+
var chick;
|
|
842
|
+
if (name.length === 1)
|
|
843
|
+
chick = chordNotes(bass, '');
|
|
844
|
+
var remaining = name.substring(1);
|
|
845
|
+
var acc = remaining.substring(0,1);
|
|
846
|
+
if (acc === 'b' || acc === '♭') {
|
|
847
|
+
bass--;
|
|
848
|
+
bass2--;
|
|
849
|
+
remaining = remaining.substring(1);
|
|
850
|
+
} else if (acc === '#' || acc === '♯') {
|
|
851
|
+
bass++;
|
|
852
|
+
bass2++;
|
|
853
|
+
remaining = remaining.substring(1);
|
|
854
|
+
}
|
|
855
|
+
var arr = remaining.split('/');
|
|
856
|
+
chick = chordNotes(bass, arr[0]);
|
|
857
|
+
// If the 5th is altered then the bass is altered. Normally the bass is 7 from the root, so adjust if it isn't.
|
|
858
|
+
if (chick.length >= 3) {
|
|
859
|
+
var fifth = chick[2] - chick[0];
|
|
860
|
+
bass2 = bass2 + fifth - 7;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (arr.length === 2) {
|
|
864
|
+
var explicitBass = basses[arr[1].substring(0,1)];
|
|
865
|
+
if (explicitBass) {
|
|
866
|
+
var bassAcc = arr[1].substring(1);
|
|
867
|
+
var bassShift = {'#': 1, '♯': 1, 'b': -1, '♭': -1}[bassAcc] || 0;
|
|
868
|
+
bass = basses[arr[1].substring(0,1)] + bassShift + chordTranspose;
|
|
869
|
+
bass2 = bass;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return { boom: bass, boom2: bass2, chick: chick };
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
var chordIntervals = {
|
|
876
|
+
// diminished (all flat 5 chords)
|
|
877
|
+
'dim': [ 0, 3, 6 ],
|
|
878
|
+
'°': [ 0, 3, 6 ],
|
|
879
|
+
'˚': [ 0, 3, 6 ],
|
|
880
|
+
|
|
881
|
+
'dim7': [ 0, 3, 6, 9 ],
|
|
882
|
+
'°7': [ 0, 3, 6, 9 ],
|
|
883
|
+
'˚7': [ 0, 3, 6, 9 ],
|
|
884
|
+
|
|
885
|
+
'ø7': [ 0, 3, 6, 10 ],
|
|
886
|
+
'm7(b5)': [ 0, 3, 6, 10 ],
|
|
887
|
+
'm7b5': [ 0, 3, 6, 10 ],
|
|
888
|
+
'm7♭5': [ 0, 3, 6, 10 ],
|
|
889
|
+
'-7(b5)': [ 0, 3, 6, 10 ],
|
|
890
|
+
'-7b5': [ 0, 3, 6, 10 ],
|
|
891
|
+
|
|
892
|
+
'7b5': [ 0, 4, 6, 10 ],
|
|
893
|
+
'7(b5)': [ 0, 4, 6, 10 ],
|
|
894
|
+
'7♭5': [ 0, 4, 6, 10 ],
|
|
895
|
+
|
|
896
|
+
'7(b9,b5)': [ 0, 4, 6, 10, 13 ],
|
|
897
|
+
'7b9,b5': [ 0, 4, 6, 10, 13 ],
|
|
898
|
+
'7(#9,b5)': [ 0, 4, 6, 10, 15 ],
|
|
899
|
+
'7#9b5': [ 0, 4, 6, 10, 15 ],
|
|
900
|
+
'maj7(b5)': [ 0, 4, 6, 11 ],
|
|
901
|
+
'maj7b5': [ 0, 4, 6, 11 ],
|
|
902
|
+
'13(b5)': [ 0, 4, 6, 10, 14, 21 ],
|
|
903
|
+
'13b5': [ 0, 4, 6, 10, 14, 21 ],
|
|
904
|
+
|
|
905
|
+
// minor (all normal 5, minor 3 chords)
|
|
906
|
+
'm': [ 0, 3, 7 ],
|
|
907
|
+
'-': [ 0, 3, 7 ],
|
|
908
|
+
'm6': [ 0, 3, 7, 9 ],
|
|
909
|
+
'-6': [ 0, 3, 7, 9 ],
|
|
910
|
+
'm7': [ 0, 3, 7, 10 ],
|
|
911
|
+
'-7': [ 0, 3, 7, 10 ],
|
|
912
|
+
|
|
913
|
+
'-(b6)': [ 0, 3, 7, 8 ],
|
|
914
|
+
'-b6': [ 0, 3, 7, 8 ],
|
|
915
|
+
'-6/9': [ 0, 3, 7, 9, 14 ],
|
|
916
|
+
'-7(b9)': [ 0, 3, 7, 10, 13 ],
|
|
917
|
+
'-7b9': [ 0, 3, 7, 10, 13 ],
|
|
918
|
+
'-maj7': [ 0, 3, 7, 11 ],
|
|
919
|
+
'-9+7': [ 0, 3, 7, 11, 13 ],
|
|
920
|
+
'-11': [ 0, 3, 7, 11, 14, 17 ],
|
|
921
|
+
'm11': [ 0, 3, 7, 11, 14, 17 ],
|
|
922
|
+
'-maj9': [ 0, 3, 7, 11, 14 ],
|
|
923
|
+
'-∆9': [ 0, 3, 7, 11, 14 ],
|
|
924
|
+
'mM9': [ 0, 3, 7, 11, 14 ],
|
|
925
|
+
|
|
926
|
+
// major (all normal 5, major 3 chords)
|
|
927
|
+
'M': [ 0, 4, 7 ],
|
|
928
|
+
'6': [ 0, 4, 7, 9 ],
|
|
929
|
+
'6/9': [ 0, 4, 7, 9, 14 ],
|
|
930
|
+
'6add9': [ 0, 4, 7, 9, 14 ],
|
|
931
|
+
'69': [ 0, 4, 7, 9, 14 ],
|
|
932
|
+
|
|
933
|
+
'7': [ 0, 4, 7, 10 ],
|
|
934
|
+
'9': [ 0, 4, 7, 10, 14 ],
|
|
935
|
+
'11': [ 0, 7, 10, 14, 17 ],
|
|
936
|
+
'13': [ 0, 4, 7, 10, 14, 21 ],
|
|
937
|
+
'7b9': [ 0, 4, 7, 10, 13 ],
|
|
938
|
+
'7♭9': [ 0, 4, 7, 10, 13 ],
|
|
939
|
+
'7(b9)': [ 0, 4, 7, 10, 13 ],
|
|
940
|
+
'7(#9)': [ 0, 4, 7, 10, 15 ],
|
|
941
|
+
'7#9': [ 0, 4, 7, 10, 15 ],
|
|
942
|
+
'(13)': [ 0, 4, 7, 10, 14, 21 ],
|
|
943
|
+
'7(9,13)': [ 0, 4, 7, 10, 14, 21 ],
|
|
944
|
+
'7(#9,b13)': [ 0, 4, 7, 10, 15, 20 ],
|
|
945
|
+
'7(#11)': [ 0, 4, 7, 10, 14, 18 ],
|
|
946
|
+
'7#11': [ 0, 4, 7, 10, 14, 18 ],
|
|
947
|
+
'7(b13)': [ 0, 4, 7, 10, 20 ],
|
|
948
|
+
'7b13': [ 0, 4, 7, 10, 20 ],
|
|
949
|
+
'9(#11)': [ 0, 4, 7, 10, 14, 18 ],
|
|
950
|
+
'9#11': [ 0, 4, 7, 10, 14, 18 ],
|
|
951
|
+
'13(#11)': [ 0, 4, 7, 10, 18, 21 ],
|
|
952
|
+
'13#11': [ 0, 4, 7, 10, 18, 21 ],
|
|
953
|
+
|
|
954
|
+
'maj7': [ 0, 4, 7, 11 ],
|
|
955
|
+
'∆7': [ 0, 4, 7, 11 ],
|
|
956
|
+
'Δ7': [ 0, 4, 7, 11 ],
|
|
957
|
+
'maj9': [ 0, 4, 7, 11, 14 ],
|
|
958
|
+
'maj7(9)': [ 0, 4, 7, 11, 14 ],
|
|
959
|
+
'maj7(11)': [ 0, 4, 7, 11, 17 ],
|
|
960
|
+
'maj7(#11)': [ 0, 4, 7, 11, 18 ],
|
|
961
|
+
'maj7(13)': [ 0, 4, 7, 14, 21 ],
|
|
962
|
+
'maj7(9,13)': [ 0, 4, 7, 11, 14, 21 ],
|
|
963
|
+
|
|
964
|
+
'7sus4': [ 0, 5, 7, 10 ],
|
|
965
|
+
'm7sus4': [ 0, 3, 7, 10, 17 ],
|
|
966
|
+
'sus4': [ 0, 5, 7 ],
|
|
967
|
+
'sus2': [ 0, 2, 7 ],
|
|
968
|
+
'7sus2': [ 0, 2, 7, 10 ],
|
|
969
|
+
'9sus4': [ 0, 5, 7, 10, 14 ],
|
|
970
|
+
'13sus4': [ 0, 5, 7, 10, 14, 21 ],
|
|
971
|
+
|
|
972
|
+
// augmented (all sharp 5 chords)
|
|
973
|
+
'aug7': [ 0, 4, 8, 10 ],
|
|
974
|
+
'+7': [ 0, 4, 8, 10 ],
|
|
975
|
+
'+': [ 0, 4, 8 ],
|
|
976
|
+
'7#5': [ 0, 4, 8, 10 ],
|
|
977
|
+
'7♯5': [ 0, 4, 8, 10 ],
|
|
978
|
+
'7+5': [ 0, 4, 8, 10 ],
|
|
979
|
+
'9#5': [ 0, 4, 8, 10, 14 ],
|
|
980
|
+
'9♯5': [ 0, 4, 8, 10, 14 ],
|
|
981
|
+
'9+5': [ 0, 4, 8, 10, 14 ],
|
|
982
|
+
'-7(#5)': [ 0, 3, 8, 10 ],
|
|
983
|
+
'-7#5': [ 0, 3, 8, 10 ],
|
|
984
|
+
'7(#5)': [ 0, 4, 8, 10 ],
|
|
985
|
+
'7(b9,#5)': [ 0, 4, 8, 10, 13 ],
|
|
986
|
+
'7b9#5': [ 0, 4, 8, 10, 13 ],
|
|
987
|
+
'maj7(#5)': [ 0, 4, 8, 11 ],
|
|
988
|
+
'maj7#5': [ 0, 4, 8, 11 ],
|
|
989
|
+
'maj7(#5,#11)': [ 0, 4, 8, 11, 18 ],
|
|
990
|
+
'maj7#5#11': [ 0, 4, 8, 11, 18 ],
|
|
991
|
+
'9(#5)': [ 0, 4, 8, 10, 14 ],
|
|
992
|
+
'13(#5)': [ 0, 4, 8, 10, 14, 21 ],
|
|
993
|
+
'13#5': [ 0, 4, 8, 10, 14, 21 ]
|
|
994
|
+
};
|
|
995
|
+
function chordNotes(bass, modifier) {
|
|
996
|
+
var intervals = chordIntervals[modifier];
|
|
997
|
+
if (!intervals) {
|
|
998
|
+
if (modifier.slice(0,2).toLowerCase() === 'ma' || modifier.charAt(0) === 'M')
|
|
999
|
+
intervals = chordIntervals.M;
|
|
1000
|
+
else if (modifier.charAt(0) === 'm' || modifier.charAt(0) === '-')
|
|
1001
|
+
intervals = chordIntervals.m;
|
|
1002
|
+
else
|
|
1003
|
+
intervals = chordIntervals.M;
|
|
1004
|
+
}
|
|
1005
|
+
bass += 12; // the chord is an octave above the bass note.
|
|
1006
|
+
var notes = [ ];
|
|
1007
|
+
for (var i = 0; i < intervals.length; i++) {
|
|
1008
|
+
notes.push(bass + intervals[i]);
|
|
1009
|
+
}
|
|
1010
|
+
return notes;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function writeBoom(boom, beatLength, volume, beat, noteLength) {
|
|
1014
|
+
// undefined means there is a stop time.
|
|
1015
|
+
if (boom !== undefined)
|
|
1016
|
+
chordTrack.push({cmd: 'note', pitch: boom, volume: volume, start: lastBarTime+beat*durationRounded(beatLength), duration: durationRounded(noteLength), gap: 0, instrument: chordInstrument});
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function writeChick(chick, beatLength, volume, beat, noteLength) {
|
|
1020
|
+
for (var c = 0; c < chick.length; c++)
|
|
1021
|
+
chordTrack.push({cmd: 'note', pitch: chick[c], volume: volume, start: lastBarTime+beat*durationRounded(beatLength), duration: durationRounded(noteLength), gap: 0, instrument: chordInstrument});
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
var rhythmPatterns = { "2/2": [ 'boom', 'chick' ],
|
|
1025
|
+
"2/4": [ 'boom', 'chick' ],
|
|
1026
|
+
"3/4": [ 'boom', 'chick', 'chick' ],
|
|
1027
|
+
"4/4": [ 'boom', 'chick', 'boom2', 'chick' ],
|
|
1028
|
+
"5/4": [ 'boom', 'chick', 'chick', 'boom2', 'chick' ],
|
|
1029
|
+
"6/8": [ 'boom', '', 'chick', 'boom2', '', 'chick' ],
|
|
1030
|
+
"9/8": [ 'boom', '', 'chick', 'boom2', '', 'chick', 'boom2', '', 'chick' ],
|
|
1031
|
+
"12/8": [ 'boom', '', 'chick', 'boom2', '', 'chick', 'boom', '', 'chick', 'boom2', '', 'chick' ],
|
|
1032
|
+
};
|
|
1033
|
+
|
|
1034
|
+
function resolveChords(startTime, endTime) {
|
|
1035
|
+
var num = meter.num;
|
|
1036
|
+
var den = meter.den;
|
|
1037
|
+
var beatLength = 1/den;
|
|
1038
|
+
var noteLength = beatLength/2;
|
|
1039
|
+
var pattern = rhythmPatterns[num+'/'+den];
|
|
1040
|
+
var thisMeasureLength = parseInt(num,10)/parseInt(den,10);
|
|
1041
|
+
var portionOfAMeasure = thisMeasureLength - (endTime-startTime)/tempoChangeFactor;
|
|
1042
|
+
if (Math.abs(portionOfAMeasure) < 0.00001)
|
|
1043
|
+
portionOfAMeasure = false;
|
|
1044
|
+
if (!pattern || portionOfAMeasure) { // If it is an unsupported meter, or this isn't a full bar, just chick on each beat.
|
|
1045
|
+
pattern = [];
|
|
1046
|
+
var beatsPresent = ((endTime-startTime)/tempoChangeFactor) / beatLength;
|
|
1047
|
+
for (var p = 0; p < beatsPresent; p++)
|
|
1048
|
+
pattern.push("chick");
|
|
1049
|
+
}
|
|
1050
|
+
//console.log(startTime, pattern, currentChords, lastChord, portionOfAMeasure)
|
|
1051
|
+
|
|
1052
|
+
if (currentChords.length === 0) { // there wasn't a new chord this measure, so use the last chord declared.
|
|
1053
|
+
currentChords.push({ beat: 0, chord: lastChord});
|
|
1054
|
+
}
|
|
1055
|
+
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.
|
|
1056
|
+
if (chordLastBar)
|
|
1057
|
+
currentChords.unshift({ beat: 0, chord: chordLastBar});
|
|
1058
|
+
}
|
|
1059
|
+
if (currentChords.length === 1) {
|
|
1060
|
+
for (var m = currentChords[0].beat; m < pattern.length; m++) {
|
|
1061
|
+
if (!hasRhythmHead) {
|
|
1062
|
+
switch (pattern[m]) {
|
|
1063
|
+
case 'boom':
|
|
1064
|
+
writeBoom(currentChords[0].chord.boom, beatLength, boomVolume, m, noteLength);
|
|
1065
|
+
break;
|
|
1066
|
+
case 'boom2':
|
|
1067
|
+
writeBoom(currentChords[0].chord.boom2, beatLength, boomVolume, m, noteLength);
|
|
1068
|
+
break;
|
|
1069
|
+
case 'chick':
|
|
1070
|
+
writeChick(currentChords[0].chord.chick, beatLength, chickVolume, m, noteLength);
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// 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.
|
|
1079
|
+
|
|
1080
|
+
// First, normalize the chords on beats.
|
|
1081
|
+
var mult = beatLength === 0.125 ? 3 : 1; // If this is a compound meter then the beats in the currentChords is 1/3 of the true beat
|
|
1082
|
+
var beats = {};
|
|
1083
|
+
for (var i = 0; i < currentChords.length; i++) {
|
|
1084
|
+
var cc = currentChords[i];
|
|
1085
|
+
var b = Math.round(cc.beat*mult);
|
|
1086
|
+
beats[''+b] = cc;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// - If there is a chord on the second beat, play a chord for the first beat instead of a bass note.
|
|
1090
|
+
// - 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.
|
|
1091
|
+
for (var m2 = 0; m2 < pattern.length; m2++) {
|
|
1092
|
+
var thisChord;
|
|
1093
|
+
if (beats[''+m2])
|
|
1094
|
+
thisChord = beats[''+m2];
|
|
1095
|
+
var lastBoom;
|
|
1096
|
+
if (!hasRhythmHead && thisChord) {
|
|
1097
|
+
switch (pattern[m2]) {
|
|
1098
|
+
case 'boom':
|
|
1099
|
+
if (beats['' + (m2 + 1)]) // If there is not a chord change on the next beat, play a bass note.
|
|
1100
|
+
writeChick(thisChord.chord.chick, beatLength, chickVolume, m2, noteLength);
|
|
1101
|
+
else {
|
|
1102
|
+
writeBoom(thisChord.chord.boom, beatLength, boomVolume, m2, noteLength);
|
|
1103
|
+
lastBoom = thisChord.chord.boom;
|
|
1104
|
+
}
|
|
1105
|
+
break;
|
|
1106
|
+
case 'boom2':
|
|
1107
|
+
if (beats['' + (m2 + 1)])
|
|
1108
|
+
writeChick(thisChord.chord.chick, beatLength, chickVolume, m2, noteLength);
|
|
1109
|
+
else {
|
|
1110
|
+
// If there is the same root as the last chord, use the alternating bass, otherwise play the root.
|
|
1111
|
+
if (lastBoom === thisChord.chord.boom) {
|
|
1112
|
+
writeBoom(thisChord.chord.boom2, beatLength, boomVolume, m2, noteLength);
|
|
1113
|
+
lastBoom = undefined;
|
|
1114
|
+
} else {
|
|
1115
|
+
writeBoom(thisChord.chord.boom, beatLength, boomVolume, m2, noteLength);
|
|
1116
|
+
lastBoom = thisChord.chord.boom;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
break;
|
|
1120
|
+
case 'chick':
|
|
1121
|
+
writeChick(thisChord.chord.chick, beatLength, chickVolume, m2, noteLength);
|
|
1122
|
+
break;
|
|
1123
|
+
case '':
|
|
1124
|
+
if (beats['' + m2]) // If there is an explicit chord on this beat, play it.
|
|
1125
|
+
writeChick(thisChord.chord.chick, beatLength, chickVolume, m2, noteLength);
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
function normalizeDrumDefinition(params) {
|
|
1133
|
+
// Be very strict with the drum definition. If anything is not perfect,
|
|
1134
|
+
// just turn the drums off.
|
|
1135
|
+
// Perhaps all of this logic belongs in the parser instead.
|
|
1136
|
+
if (params.pattern.length === 0 || params.on === false)
|
|
1137
|
+
return { on: false };
|
|
1138
|
+
|
|
1139
|
+
var str = params.pattern[0];
|
|
1140
|
+
var events = [];
|
|
1141
|
+
var event = "";
|
|
1142
|
+
var totalPlay = 0;
|
|
1143
|
+
for (var i = 0; i < str.length; i++) {
|
|
1144
|
+
if (str[i] === 'd')
|
|
1145
|
+
totalPlay++;
|
|
1146
|
+
if (str[i] === 'd' || str[i] === 'z') {
|
|
1147
|
+
if (event.length !== 0) {
|
|
1148
|
+
events.push(event);
|
|
1149
|
+
event = str[i];
|
|
1150
|
+
} else
|
|
1151
|
+
event = event + str[i];
|
|
1152
|
+
} else {
|
|
1153
|
+
if (event.length === 0) {
|
|
1154
|
+
// there was an error: the string should have started with d or z
|
|
1155
|
+
return {on: false};
|
|
1156
|
+
}
|
|
1157
|
+
event = event + str[i];
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
if (event.length !== 0)
|
|
1162
|
+
events.push(event);
|
|
1163
|
+
|
|
1164
|
+
// Now the events array should have one item per event.
|
|
1165
|
+
// There should be two more params for each event: the volume and the pitch.
|
|
1166
|
+
if (params.pattern.length !== totalPlay*2 + 1)
|
|
1167
|
+
return { on: false };
|
|
1168
|
+
|
|
1169
|
+
var ret = { on: true, bars: params.bars, pattern: []};
|
|
1170
|
+
var beatLength = getBeatFraction(meter);
|
|
1171
|
+
var playCount = 0;
|
|
1172
|
+
for (var j = 0; j < events.length; j++) {
|
|
1173
|
+
event = events[j];
|
|
1174
|
+
var len = 1;
|
|
1175
|
+
var div = false;
|
|
1176
|
+
var num = 0;
|
|
1177
|
+
for (var k = 1; k < event.length; k++) {
|
|
1178
|
+
switch(event[k]) {
|
|
1179
|
+
case "/":
|
|
1180
|
+
if (num !== 0)
|
|
1181
|
+
len *= num;
|
|
1182
|
+
num = 0;
|
|
1183
|
+
div = true;
|
|
1184
|
+
break;
|
|
1185
|
+
case "1":
|
|
1186
|
+
case "2":
|
|
1187
|
+
case "3":
|
|
1188
|
+
case "4":
|
|
1189
|
+
case "5":
|
|
1190
|
+
case "6":
|
|
1191
|
+
case "7":
|
|
1192
|
+
case "8":
|
|
1193
|
+
case "9":
|
|
1194
|
+
num = num*10 +event[k];
|
|
1195
|
+
break;
|
|
1196
|
+
default:
|
|
1197
|
+
return { on: false };
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
if (div) {
|
|
1201
|
+
if (num === 0) num = 2; // a slash by itself is interpreted as "/2"
|
|
1202
|
+
len /= num;
|
|
1203
|
+
} else if (num)
|
|
1204
|
+
len *= num;
|
|
1205
|
+
if (event[0] === 'd') {
|
|
1206
|
+
ret.pattern.push({ len: len * beatLength, pitch: params.pattern[1 + playCount], velocity: params.pattern[1 + playCount + totalPlay]});
|
|
1207
|
+
playCount++;
|
|
1208
|
+
} else
|
|
1209
|
+
ret.pattern.push({ len: len * beatLength, pitch: null});
|
|
1210
|
+
}
|
|
1211
|
+
// Now normalize the pattern to cover the correct number of measures. The note lengths passed are relative to each other and need to be scaled to fit a measure.
|
|
1212
|
+
var totalTime = 0;
|
|
1213
|
+
var measuresPerBeat = meter.num/meter.den;
|
|
1214
|
+
for (var ii = 0; ii < ret.pattern.length; ii++)
|
|
1215
|
+
totalTime += ret.pattern[ii].len;
|
|
1216
|
+
var numBars = params.bars ? params.bars : 1;
|
|
1217
|
+
var factor = totalTime / numBars / measuresPerBeat;
|
|
1218
|
+
for (ii = 0; ii < ret.pattern.length; ii++)
|
|
1219
|
+
ret.pattern[ii].len = ret.pattern[ii].len / factor;
|
|
1220
|
+
return ret;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
function writeDrum(channel) {
|
|
1224
|
+
if (drumTrack.length === 0 && !drumDefinition.on)
|
|
1225
|
+
return;
|
|
1226
|
+
|
|
1227
|
+
var measureLen = meter.num/meter.den;
|
|
1228
|
+
if (drumTrack.length === 0) {
|
|
1229
|
+
if (lastEventTime < measureLen)
|
|
1230
|
+
return; // This is true if there are pickup notes. The drum doesn't start until the first full measure.
|
|
1231
|
+
drumTrack.push({cmd: 'program', channel: channel, instrument: drumInstrument});
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
if (!drumDefinition.on) {
|
|
1235
|
+
// this is the case where there has been a drum track, but it was specifically turned off.
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
var start = lastBarTime;
|
|
1239
|
+
for (var i = 0; i < drumDefinition.pattern.length; i++) {
|
|
1240
|
+
var len = durationRounded(drumDefinition.pattern[i].len);
|
|
1241
|
+
if (drumDefinition.pattern[i].pitch) {
|
|
1242
|
+
drumTrack.push({
|
|
1243
|
+
cmd: 'note',
|
|
1244
|
+
pitch: drumDefinition.pattern[i].pitch,
|
|
1245
|
+
volume: drumDefinition.pattern[i].velocity,
|
|
1246
|
+
start: start,
|
|
1247
|
+
duration: len,
|
|
1248
|
+
gap: 0,
|
|
1249
|
+
instrument: drumInstrument});
|
|
1250
|
+
}
|
|
1251
|
+
start += len;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
function findOctaves(tracks, detuneCents) {
|
|
1256
|
+
var timing = {};
|
|
1257
|
+
for (var i = 0; i < tracks.length; i++) {
|
|
1258
|
+
for (var j = 0; j < tracks[i].length; j++) {
|
|
1259
|
+
var note = tracks[i][j];
|
|
1260
|
+
if (note.cmd === "note") {
|
|
1261
|
+
if (timing[note.start] === undefined)
|
|
1262
|
+
timing[note.start] = [];
|
|
1263
|
+
timing[note.start].push({track: i, event: j, pitch: note.pitch});
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
var keys = Object.keys(timing);
|
|
1268
|
+
for (i = 0; i < keys.length; i++) {
|
|
1269
|
+
var arr = timing[keys[i]];
|
|
1270
|
+
if (arr.length > 1) {
|
|
1271
|
+
arr = arr.sort(function(a,b) {
|
|
1272
|
+
return a.pitch - b.pitch;
|
|
1273
|
+
});
|
|
1274
|
+
var topEvent = arr[arr.length-1];
|
|
1275
|
+
var topNote = topEvent.pitch % 12;
|
|
1276
|
+
var found = false;
|
|
1277
|
+
for (j = 0; !found && j < arr.length-1; j++) {
|
|
1278
|
+
if (arr[j].pitch % 12 === topNote)
|
|
1279
|
+
found = true;
|
|
1280
|
+
}
|
|
1281
|
+
if (found) {
|
|
1282
|
+
var event = tracks[topEvent.track][topEvent.event];
|
|
1283
|
+
if (!event.cents)
|
|
1284
|
+
event.cents = 0;
|
|
1285
|
+
event.cents += detuneCents;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
})();
|
|
1291
|
+
|
|
1292
|
+
module.exports = flatten;
|