@marmooo/midy 0.2.8 → 0.3.0
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/esm/midy-GM1.d.ts +15 -9
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +112 -58
- package/esm/midy-GM2.d.ts +24 -15
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +311 -102
- package/esm/midy-GMLite.d.ts +17 -8
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +159 -55
- package/esm/midy.d.ts +24 -15
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +352 -113
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +15 -9
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +112 -58
- package/script/midy-GM2.d.ts +24 -15
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +311 -102
- package/script/midy-GMLite.d.ts +17 -8
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +159 -55
- package/script/midy.d.ts +24 -15
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +352 -113
package/esm/midy.js
CHANGED
|
@@ -157,6 +157,39 @@ class Note {
|
|
|
157
157
|
this.voiceParams = voiceParams;
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
|
+
const drumExclusiveClassesByKit = new Array(57);
|
|
161
|
+
const drumExclusiveClassCount = 10;
|
|
162
|
+
const standardSet = new Uint8Array(128);
|
|
163
|
+
standardSet[42] = 1;
|
|
164
|
+
standardSet[44] = 1;
|
|
165
|
+
standardSet[46] = 1; // HH
|
|
166
|
+
standardSet[71] = 2;
|
|
167
|
+
standardSet[72] = 2; // Whistle
|
|
168
|
+
standardSet[73] = 3;
|
|
169
|
+
standardSet[74] = 3; // Guiro
|
|
170
|
+
standardSet[78] = 4;
|
|
171
|
+
standardSet[79] = 4; // Cuica
|
|
172
|
+
standardSet[80] = 5;
|
|
173
|
+
standardSet[81] = 5; // Triangle
|
|
174
|
+
standardSet[29] = 6;
|
|
175
|
+
standardSet[30] = 6; // Scratch
|
|
176
|
+
standardSet[86] = 7;
|
|
177
|
+
standardSet[87] = 7; // Surdo
|
|
178
|
+
drumExclusiveClassesByKit[0] = standardSet;
|
|
179
|
+
const analogSet = new Uint8Array(128);
|
|
180
|
+
analogSet[42] = 8;
|
|
181
|
+
analogSet[44] = 8;
|
|
182
|
+
analogSet[46] = 8; // CHH
|
|
183
|
+
drumExclusiveClassesByKit[25] = analogSet;
|
|
184
|
+
const orchestraSet = new Uint8Array(128);
|
|
185
|
+
orchestraSet[27] = 9;
|
|
186
|
+
orchestraSet[28] = 9;
|
|
187
|
+
orchestraSet[29] = 9; // HH
|
|
188
|
+
drumExclusiveClassesByKit[48] = orchestraSet;
|
|
189
|
+
const sfxSet = new Uint8Array(128);
|
|
190
|
+
sfxSet[41] = 10;
|
|
191
|
+
sfxSet[42] = 10; // Scratch
|
|
192
|
+
drumExclusiveClassesByKit[56] = sfxSet;
|
|
160
193
|
// normalized to 0-1 for use with the SF2 modulator model
|
|
161
194
|
const defaultControllerState = {
|
|
162
195
|
noteOnVelocity: { type: 2, defaultValue: 0 },
|
|
@@ -246,17 +279,11 @@ const volumeEnvelopeKeys = [
|
|
|
246
279
|
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
247
280
|
export class Midy {
|
|
248
281
|
constructor(audioContext, options = this.defaultOptions) {
|
|
249
|
-
Object.defineProperty(this, "
|
|
282
|
+
Object.defineProperty(this, "mode", {
|
|
250
283
|
enumerable: true,
|
|
251
284
|
configurable: true,
|
|
252
285
|
writable: true,
|
|
253
|
-
value:
|
|
254
|
-
});
|
|
255
|
-
Object.defineProperty(this, "totalTime", {
|
|
256
|
-
enumerable: true,
|
|
257
|
-
configurable: true,
|
|
258
|
-
writable: true,
|
|
259
|
-
value: 0
|
|
286
|
+
value: "GM2"
|
|
260
287
|
});
|
|
261
288
|
Object.defineProperty(this, "masterFineTuning", {
|
|
262
289
|
enumerable: true,
|
|
@@ -291,6 +318,24 @@ export class Midy {
|
|
|
291
318
|
delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
|
|
292
319
|
}
|
|
293
320
|
});
|
|
321
|
+
Object.defineProperty(this, "numChannels", {
|
|
322
|
+
enumerable: true,
|
|
323
|
+
configurable: true,
|
|
324
|
+
writable: true,
|
|
325
|
+
value: 16
|
|
326
|
+
});
|
|
327
|
+
Object.defineProperty(this, "ticksPerBeat", {
|
|
328
|
+
enumerable: true,
|
|
329
|
+
configurable: true,
|
|
330
|
+
writable: true,
|
|
331
|
+
value: 120
|
|
332
|
+
});
|
|
333
|
+
Object.defineProperty(this, "totalTime", {
|
|
334
|
+
enumerable: true,
|
|
335
|
+
configurable: true,
|
|
336
|
+
writable: true,
|
|
337
|
+
value: 0
|
|
338
|
+
});
|
|
294
339
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
295
340
|
enumerable: true,
|
|
296
341
|
configurable: true,
|
|
@@ -393,11 +438,17 @@ export class Midy {
|
|
|
393
438
|
writable: true,
|
|
394
439
|
value: []
|
|
395
440
|
});
|
|
396
|
-
Object.defineProperty(this, "
|
|
441
|
+
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
442
|
+
enumerable: true,
|
|
443
|
+
configurable: true,
|
|
444
|
+
writable: true,
|
|
445
|
+
value: new Array(128)
|
|
446
|
+
});
|
|
447
|
+
Object.defineProperty(this, "drumExclusiveClassNotes", {
|
|
397
448
|
enumerable: true,
|
|
398
449
|
configurable: true,
|
|
399
450
|
writable: true,
|
|
400
|
-
value: new
|
|
451
|
+
value: new Array(this.numChannels * drumExclusiveClassCount)
|
|
401
452
|
});
|
|
402
453
|
Object.defineProperty(this, "defaultOptions", {
|
|
403
454
|
enumerable: true,
|
|
@@ -424,6 +475,11 @@ export class Midy {
|
|
|
424
475
|
this.audioContext = audioContext;
|
|
425
476
|
this.options = { ...this.defaultOptions, ...options };
|
|
426
477
|
this.masterVolume = new GainNode(audioContext);
|
|
478
|
+
this.scheduler = new GainNode(audioContext, { gain: 0 });
|
|
479
|
+
this.schedulerBuffer = new AudioBuffer({
|
|
480
|
+
length: 1,
|
|
481
|
+
sampleRate: audioContext.sampleRate,
|
|
482
|
+
});
|
|
427
483
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
428
484
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
429
485
|
this.channels = this.createChannels(audioContext);
|
|
@@ -432,6 +488,7 @@ export class Midy {
|
|
|
432
488
|
this.chorusEffect.output.connect(this.masterVolume);
|
|
433
489
|
this.reverbEffect.output.connect(this.masterVolume);
|
|
434
490
|
this.masterVolume.connect(audioContext.destination);
|
|
491
|
+
this.scheduler.connect(audioContext.destination);
|
|
435
492
|
this.GM2SystemOn();
|
|
436
493
|
}
|
|
437
494
|
initSoundFontTable() {
|
|
@@ -485,8 +542,10 @@ export class Midy {
|
|
|
485
542
|
};
|
|
486
543
|
}
|
|
487
544
|
createChannels(audioContext) {
|
|
488
|
-
const channels = Array.from({ length:
|
|
545
|
+
const channels = Array.from({ length: this.numChannels }, () => {
|
|
489
546
|
return {
|
|
547
|
+
currentBufferSource: null,
|
|
548
|
+
isDrum: false,
|
|
490
549
|
...this.constructor.channelSettings,
|
|
491
550
|
state: new ControllerState(),
|
|
492
551
|
controlTable: this.initControlTable(),
|
|
@@ -531,7 +590,7 @@ export class Midy {
|
|
|
531
590
|
return audioBuffer;
|
|
532
591
|
}
|
|
533
592
|
}
|
|
534
|
-
|
|
593
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
535
594
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
536
595
|
bufferSource.buffer = audioBuffer;
|
|
537
596
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
@@ -625,7 +684,8 @@ export class Midy {
|
|
|
625
684
|
if (queueIndex >= this.timeline.length) {
|
|
626
685
|
await Promise.all(this.notePromises);
|
|
627
686
|
this.notePromises = [];
|
|
628
|
-
this.
|
|
687
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
688
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
629
689
|
this.audioBufferCache.clear();
|
|
630
690
|
resolve();
|
|
631
691
|
return;
|
|
@@ -644,7 +704,8 @@ export class Midy {
|
|
|
644
704
|
else if (this.isStopping) {
|
|
645
705
|
await this.stopNotes(0, true, now);
|
|
646
706
|
this.notePromises = [];
|
|
647
|
-
this.
|
|
707
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
708
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
648
709
|
this.audioBufferCache.clear();
|
|
649
710
|
resolve();
|
|
650
711
|
this.isStopping = false;
|
|
@@ -653,7 +714,8 @@ export class Midy {
|
|
|
653
714
|
}
|
|
654
715
|
else if (this.isSeeking) {
|
|
655
716
|
this.stopNotes(0, true, now);
|
|
656
|
-
this.
|
|
717
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
718
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
657
719
|
this.startTime = this.audioContext.currentTime;
|
|
658
720
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
659
721
|
offset = this.resumeTime - this.startTime;
|
|
@@ -681,7 +743,7 @@ export class Midy {
|
|
|
681
743
|
extractMidiData(midi) {
|
|
682
744
|
const instruments = new Set();
|
|
683
745
|
const timeline = [];
|
|
684
|
-
const tmpChannels = new Array(
|
|
746
|
+
const tmpChannels = new Array(this.channels.length);
|
|
685
747
|
for (let i = 0; i < tmpChannels.length; i++) {
|
|
686
748
|
tmpChannels[i] = {
|
|
687
749
|
programNumber: -1,
|
|
@@ -809,6 +871,9 @@ export class Midy {
|
|
|
809
871
|
if (!this.isPlaying)
|
|
810
872
|
return;
|
|
811
873
|
this.isStopping = true;
|
|
874
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
875
|
+
this.resetAllStates(i);
|
|
876
|
+
}
|
|
812
877
|
}
|
|
813
878
|
pause() {
|
|
814
879
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -1014,7 +1079,9 @@ export class Midy {
|
|
|
1014
1079
|
return 8.176 * this.centToRate(cent);
|
|
1015
1080
|
}
|
|
1016
1081
|
calcChannelDetune(channel) {
|
|
1017
|
-
const masterTuning =
|
|
1082
|
+
const masterTuning = channel.isDrum
|
|
1083
|
+
? 0
|
|
1084
|
+
: this.masterCoarseTuning + this.masterFineTuning;
|
|
1018
1085
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
1019
1086
|
const tuning = masterTuning + channelTuning;
|
|
1020
1087
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
@@ -1041,9 +1108,8 @@ export class Midy {
|
|
|
1041
1108
|
.setValueAtTime(detune, scheduleTime);
|
|
1042
1109
|
}
|
|
1043
1110
|
getPortamentoTime(channel) {
|
|
1044
|
-
const factor = 5 * Math.log(10)
|
|
1045
|
-
|
|
1046
|
-
return Math.log(time) / factor;
|
|
1111
|
+
const factor = 5 * Math.log(10) * 127;
|
|
1112
|
+
return channel.state.portamentoTime * factor;
|
|
1047
1113
|
}
|
|
1048
1114
|
setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
|
|
1049
1115
|
const { voiceParams, startTime } = note;
|
|
@@ -1182,8 +1248,8 @@ export class Midy {
|
|
|
1182
1248
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1183
1249
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1184
1250
|
}
|
|
1185
|
-
async getAudioBuffer(
|
|
1186
|
-
const audioBufferId = this.getAudioBufferId(
|
|
1251
|
+
async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
|
|
1252
|
+
const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
|
|
1187
1253
|
const cache = this.audioBufferCache.get(audioBufferId);
|
|
1188
1254
|
if (cache) {
|
|
1189
1255
|
cache.counter += 1;
|
|
@@ -1206,8 +1272,8 @@ export class Midy {
|
|
|
1206
1272
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1207
1273
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1208
1274
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1209
|
-
const audioBuffer = await this.getAudioBuffer(channel.
|
|
1210
|
-
note.bufferSource = this.
|
|
1275
|
+
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1276
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
1211
1277
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1212
1278
|
note.gainL = new GainNode(this.audioContext);
|
|
1213
1279
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1251,23 +1317,72 @@ export class Midy {
|
|
|
1251
1317
|
note.bufferSource.start(startTime);
|
|
1252
1318
|
return note;
|
|
1253
1319
|
}
|
|
1254
|
-
calcBank(channel
|
|
1255
|
-
|
|
1256
|
-
|
|
1320
|
+
calcBank(channel) {
|
|
1321
|
+
switch (this.mode) {
|
|
1322
|
+
case "GM1":
|
|
1323
|
+
if (channel.isDrum)
|
|
1324
|
+
return 128;
|
|
1325
|
+
return 0;
|
|
1326
|
+
case "GM2":
|
|
1327
|
+
if (channel.bankMSB === 121)
|
|
1328
|
+
return 0;
|
|
1329
|
+
if (channel.isDrum)
|
|
1330
|
+
return 128;
|
|
1331
|
+
return channel.bank;
|
|
1332
|
+
default:
|
|
1333
|
+
return channel.bank;
|
|
1257
1334
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1335
|
+
}
|
|
1336
|
+
handleExclusiveClass(note, channelNumber, startTime) {
|
|
1337
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1338
|
+
if (exclusiveClass === 0)
|
|
1339
|
+
return;
|
|
1340
|
+
const prev = this.exclusiveClassNotes[exclusiveClass];
|
|
1341
|
+
if (prev) {
|
|
1342
|
+
const [prevNote, prevChannelNumber] = prev;
|
|
1343
|
+
if (prevNote && !prevNote.ending) {
|
|
1344
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1345
|
+
startTime, true, // force
|
|
1346
|
+
undefined);
|
|
1347
|
+
}
|
|
1260
1348
|
}
|
|
1261
|
-
|
|
1349
|
+
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
1350
|
+
}
|
|
1351
|
+
handleDrumExclusiveClass(note, channelNumber, startTime) {
|
|
1352
|
+
const channel = this.channels[channelNumber];
|
|
1353
|
+
if (!channel.isDrum)
|
|
1354
|
+
return;
|
|
1355
|
+
const kitTable = drumExclusiveClassesByKit[channel.programNumber];
|
|
1356
|
+
if (!kitTable)
|
|
1357
|
+
return;
|
|
1358
|
+
const drumExclusiveClass = kitTable[note.noteNumber];
|
|
1359
|
+
if (drumExclusiveClass === 0)
|
|
1360
|
+
return;
|
|
1361
|
+
const index = (drumExclusiveClass - 1) * this.channels.length +
|
|
1362
|
+
channelNumber;
|
|
1363
|
+
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1364
|
+
if (prevNote && !prevNote.ending) {
|
|
1365
|
+
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1366
|
+
startTime, true, // force
|
|
1367
|
+
undefined);
|
|
1368
|
+
}
|
|
1369
|
+
this.drumExclusiveClassNotes[index] = note;
|
|
1370
|
+
}
|
|
1371
|
+
isDrumNoteOffException(channel, noteNumber) {
|
|
1372
|
+
if (!channel.isDrum)
|
|
1373
|
+
return false;
|
|
1374
|
+
const programNumber = channel.programNumber;
|
|
1375
|
+
return (programNumber === 48 && noteNumber === 88) ||
|
|
1376
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84);
|
|
1262
1377
|
}
|
|
1263
1378
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
|
|
1264
1379
|
const channel = this.channels[channelNumber];
|
|
1265
1380
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1266
|
-
const soundFontIndex = this.soundFontTable[channel.
|
|
1381
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
1267
1382
|
if (soundFontIndex === undefined)
|
|
1268
1383
|
return;
|
|
1269
1384
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1270
|
-
const voice = soundFont.getVoice(bankNumber, channel.
|
|
1385
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1271
1386
|
if (!voice)
|
|
1272
1387
|
return;
|
|
1273
1388
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
@@ -1277,31 +1392,58 @@ export class Midy {
|
|
|
1277
1392
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1278
1393
|
channel.sustainNotes.push(note);
|
|
1279
1394
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1283
|
-
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1284
|
-
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1285
|
-
if (!prevNote.ending) {
|
|
1286
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1287
|
-
startTime, true, // force
|
|
1288
|
-
undefined);
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
1292
|
-
}
|
|
1395
|
+
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1396
|
+
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1293
1397
|
const scheduledNotes = channel.scheduledNotes;
|
|
1294
|
-
|
|
1295
|
-
|
|
1398
|
+
let notes = scheduledNotes.get(noteNumber);
|
|
1399
|
+
if (notes) {
|
|
1400
|
+
notes.push(note);
|
|
1296
1401
|
}
|
|
1297
1402
|
else {
|
|
1298
|
-
|
|
1403
|
+
notes = [note];
|
|
1404
|
+
scheduledNotes.set(noteNumber, notes);
|
|
1405
|
+
}
|
|
1406
|
+
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1407
|
+
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1408
|
+
const index = notes.length - 1;
|
|
1409
|
+
const promise = new Promise((resolve) => {
|
|
1410
|
+
note.bufferSource.onended = () => {
|
|
1411
|
+
this.disconnectNote(note, scheduledNotes, index);
|
|
1412
|
+
resolve();
|
|
1413
|
+
};
|
|
1414
|
+
note.bufferSource.stop(stopTime);
|
|
1415
|
+
});
|
|
1416
|
+
this.notePromises.push(promise);
|
|
1299
1417
|
}
|
|
1300
1418
|
}
|
|
1301
1419
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1302
1420
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1303
1421
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1304
1422
|
}
|
|
1423
|
+
disconnectNote(note, scheduledNotes, index) {
|
|
1424
|
+
scheduledNotes[index] = null;
|
|
1425
|
+
note.bufferSource.disconnect();
|
|
1426
|
+
note.filterNode.disconnect();
|
|
1427
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1428
|
+
note.volumeNode.disconnect();
|
|
1429
|
+
note.gainL.disconnect();
|
|
1430
|
+
note.gainR.disconnect();
|
|
1431
|
+
if (note.modulationDepth) {
|
|
1432
|
+
note.volumeDepth.disconnect();
|
|
1433
|
+
note.modulationDepth.disconnect();
|
|
1434
|
+
note.modulationLFO.stop();
|
|
1435
|
+
}
|
|
1436
|
+
if (note.vibratoDepth) {
|
|
1437
|
+
note.vibratoDepth.disconnect();
|
|
1438
|
+
note.vibratoLFO.stop();
|
|
1439
|
+
}
|
|
1440
|
+
if (note.reverbEffectsSend) {
|
|
1441
|
+
note.reverbEffectsSend.disconnect();
|
|
1442
|
+
}
|
|
1443
|
+
if (note.chorusEffectsSend) {
|
|
1444
|
+
note.chorusEffectsSend.disconnect();
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1305
1447
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1306
1448
|
const note = scheduledNotes[index];
|
|
1307
1449
|
note.volumeEnvelopeNode.gain
|
|
@@ -1313,28 +1455,7 @@ export class Midy {
|
|
|
1313
1455
|
}, stopTime);
|
|
1314
1456
|
return new Promise((resolve) => {
|
|
1315
1457
|
note.bufferSource.onended = () => {
|
|
1316
|
-
scheduledNotes
|
|
1317
|
-
note.bufferSource.disconnect();
|
|
1318
|
-
note.filterNode.disconnect();
|
|
1319
|
-
note.volumeEnvelopeNode.disconnect();
|
|
1320
|
-
note.volumeNode.disconnect();
|
|
1321
|
-
note.gainL.disconnect();
|
|
1322
|
-
note.gainR.disconnect();
|
|
1323
|
-
if (note.modulationDepth) {
|
|
1324
|
-
note.volumeDepth.disconnect();
|
|
1325
|
-
note.modulationDepth.disconnect();
|
|
1326
|
-
note.modulationLFO.stop();
|
|
1327
|
-
}
|
|
1328
|
-
if (note.vibratoDepth) {
|
|
1329
|
-
note.vibratoDepth.disconnect();
|
|
1330
|
-
note.vibratoLFO.stop();
|
|
1331
|
-
}
|
|
1332
|
-
if (note.reverbEffectsSend) {
|
|
1333
|
-
note.reverbEffectsSend.disconnect();
|
|
1334
|
-
}
|
|
1335
|
-
if (note.chorusEffectsSend) {
|
|
1336
|
-
note.chorusEffectsSend.disconnect();
|
|
1337
|
-
}
|
|
1458
|
+
this.disconnectNote(note, scheduledNotes, index);
|
|
1338
1459
|
resolve();
|
|
1339
1460
|
};
|
|
1340
1461
|
note.bufferSource.stop(stopTime);
|
|
@@ -1342,6 +1463,8 @@ export class Midy {
|
|
|
1342
1463
|
}
|
|
1343
1464
|
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1344
1465
|
const channel = this.channels[channelNumber];
|
|
1466
|
+
if (this.isDrumNoteOffException(channel, noteNumber))
|
|
1467
|
+
return;
|
|
1345
1468
|
const state = channel.state;
|
|
1346
1469
|
if (!force) {
|
|
1347
1470
|
if (0.5 <= state.sustainPedal)
|
|
@@ -1441,13 +1564,25 @@ export class Midy {
|
|
|
1441
1564
|
}
|
|
1442
1565
|
// this.applyVoiceParams(channel, 10);
|
|
1443
1566
|
}
|
|
1444
|
-
handleProgramChange(channelNumber,
|
|
1567
|
+
handleProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1445
1568
|
const channel = this.channels[channelNumber];
|
|
1446
1569
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1447
|
-
channel.
|
|
1570
|
+
channel.programNumber = programNumber;
|
|
1571
|
+
if (this.mode === "GM2") {
|
|
1572
|
+
switch (channel.bankMSB) {
|
|
1573
|
+
case 120:
|
|
1574
|
+
channel.isDrum = true;
|
|
1575
|
+
break;
|
|
1576
|
+
case 121:
|
|
1577
|
+
channel.isDrum = false;
|
|
1578
|
+
break;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1448
1581
|
}
|
|
1449
1582
|
handleChannelPressure(channelNumber, value, scheduleTime) {
|
|
1450
1583
|
const channel = this.channels[channelNumber];
|
|
1584
|
+
if (channel.isDrum)
|
|
1585
|
+
return;
|
|
1451
1586
|
const prev = channel.state.channelPressure;
|
|
1452
1587
|
const next = value / 127;
|
|
1453
1588
|
channel.state.channelPressure = next;
|
|
@@ -1466,8 +1601,10 @@ export class Midy {
|
|
|
1466
1601
|
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
1467
1602
|
}
|
|
1468
1603
|
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1469
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1470
1604
|
const channel = this.channels[channelNumber];
|
|
1605
|
+
if (channel.isDrum)
|
|
1606
|
+
return;
|
|
1607
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1471
1608
|
const state = channel.state;
|
|
1472
1609
|
const prev = state.pitchWheel * 2 - 1;
|
|
1473
1610
|
const next = (value - 8192) / 8192;
|
|
@@ -1749,15 +1886,16 @@ export class Midy {
|
|
|
1749
1886
|
});
|
|
1750
1887
|
}
|
|
1751
1888
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1752
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1753
1889
|
const channel = this.channels[channelNumber];
|
|
1890
|
+
if (channel.isDrum)
|
|
1891
|
+
return;
|
|
1892
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1754
1893
|
channel.state.modulationDepth = modulation / 127;
|
|
1755
1894
|
this.updateModulation(channel, scheduleTime);
|
|
1756
1895
|
}
|
|
1757
1896
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1758
1897
|
const channel = this.channels[channelNumber];
|
|
1759
|
-
|
|
1760
|
-
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1898
|
+
channel.state.portamentoTime = portamentoTime / 127;
|
|
1761
1899
|
}
|
|
1762
1900
|
setKeyBasedVolume(channel, scheduleTime) {
|
|
1763
1901
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1829,8 +1967,10 @@ export class Midy {
|
|
|
1829
1967
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1830
1968
|
}
|
|
1831
1969
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1832
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1833
1970
|
const channel = this.channels[channelNumber];
|
|
1971
|
+
if (channel.isDrum)
|
|
1972
|
+
return;
|
|
1973
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1834
1974
|
channel.state.sustainPedal = value / 127;
|
|
1835
1975
|
if (64 <= value) {
|
|
1836
1976
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1842,11 +1982,16 @@ export class Midy {
|
|
|
1842
1982
|
}
|
|
1843
1983
|
}
|
|
1844
1984
|
setPortamento(channelNumber, value) {
|
|
1845
|
-
this.channels[channelNumber]
|
|
1985
|
+
const channel = this.channels[channelNumber];
|
|
1986
|
+
if (channel.isDrum)
|
|
1987
|
+
return;
|
|
1988
|
+
channel.state.portamento = value / 127;
|
|
1846
1989
|
}
|
|
1847
1990
|
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1848
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1849
1991
|
const channel = this.channels[channelNumber];
|
|
1992
|
+
if (channel.isDrum)
|
|
1993
|
+
return;
|
|
1994
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1850
1995
|
channel.state.sostenutoPedal = value / 127;
|
|
1851
1996
|
if (64 <= value) {
|
|
1852
1997
|
channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
|
|
@@ -1855,13 +2000,28 @@ export class Midy {
|
|
|
1855
2000
|
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
1856
2001
|
}
|
|
1857
2002
|
}
|
|
1858
|
-
setSoftPedal(channelNumber, softPedal,
|
|
2003
|
+
setSoftPedal(channelNumber, softPedal, scheduleTime) {
|
|
1859
2004
|
const channel = this.channels[channelNumber];
|
|
2005
|
+
if (channel.isDrum)
|
|
2006
|
+
return;
|
|
2007
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1860
2008
|
channel.state.softPedal = softPedal / 127;
|
|
2009
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2010
|
+
if (note.portamento) {
|
|
2011
|
+
this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
|
|
2012
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
2013
|
+
}
|
|
2014
|
+
else {
|
|
2015
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2016
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2017
|
+
}
|
|
2018
|
+
});
|
|
1861
2019
|
}
|
|
1862
2020
|
setFilterResonance(channelNumber, filterResonance, scheduleTime) {
|
|
1863
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1864
2021
|
const channel = this.channels[channelNumber];
|
|
2022
|
+
if (channel.isDrum)
|
|
2023
|
+
return;
|
|
2024
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1865
2025
|
const state = channel.state;
|
|
1866
2026
|
state.filterResonance = filterResonance / 64;
|
|
1867
2027
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1870,13 +2030,17 @@ export class Midy {
|
|
|
1870
2030
|
});
|
|
1871
2031
|
}
|
|
1872
2032
|
setReleaseTime(channelNumber, releaseTime, _scheduleTime) {
|
|
1873
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1874
2033
|
const channel = this.channels[channelNumber];
|
|
2034
|
+
if (channel.isDrum)
|
|
2035
|
+
return;
|
|
2036
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1875
2037
|
channel.state.releaseTime = releaseTime / 64;
|
|
1876
2038
|
}
|
|
1877
2039
|
setAttackTime(channelNumber, attackTime, scheduleTime) {
|
|
1878
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1879
2040
|
const channel = this.channels[channelNumber];
|
|
2041
|
+
if (channel.isDrum)
|
|
2042
|
+
return;
|
|
2043
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1880
2044
|
channel.state.attackTime = attackTime / 64;
|
|
1881
2045
|
this.processScheduledNotes(channel, (note) => {
|
|
1882
2046
|
if (note.startTime < scheduleTime)
|
|
@@ -1885,8 +2049,10 @@ export class Midy {
|
|
|
1885
2049
|
});
|
|
1886
2050
|
}
|
|
1887
2051
|
setBrightness(channelNumber, brightness, scheduleTime) {
|
|
1888
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1889
2052
|
const channel = this.channels[channelNumber];
|
|
2053
|
+
if (channel.isDrum)
|
|
2054
|
+
return;
|
|
2055
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1890
2056
|
channel.state.brightness = brightness / 64;
|
|
1891
2057
|
this.processScheduledNotes(channel, (note) => {
|
|
1892
2058
|
if (note.portamento) {
|
|
@@ -1898,16 +2064,20 @@ export class Midy {
|
|
|
1898
2064
|
});
|
|
1899
2065
|
}
|
|
1900
2066
|
setDecayTime(channelNumber, dacayTime, scheduleTime) {
|
|
1901
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1902
2067
|
const channel = this.channels[channelNumber];
|
|
2068
|
+
if (channel.isDrum)
|
|
2069
|
+
return;
|
|
2070
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1903
2071
|
channel.state.decayTime = dacayTime / 64;
|
|
1904
2072
|
this.processScheduledNotes(channel, (note) => {
|
|
1905
2073
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1906
2074
|
});
|
|
1907
2075
|
}
|
|
1908
2076
|
setVibratoRate(channelNumber, vibratoRate, scheduleTime) {
|
|
1909
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1910
2077
|
const channel = this.channels[channelNumber];
|
|
2078
|
+
if (channel.isDrum)
|
|
2079
|
+
return;
|
|
2080
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1911
2081
|
channel.state.vibratoRate = vibratoRate / 64;
|
|
1912
2082
|
if (channel.vibratoDepth <= 0)
|
|
1913
2083
|
return;
|
|
@@ -1916,8 +2086,10 @@ export class Midy {
|
|
|
1916
2086
|
});
|
|
1917
2087
|
}
|
|
1918
2088
|
setVibratoDepth(channelNumber, vibratoDepth, scheduleTime) {
|
|
1919
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1920
2089
|
const channel = this.channels[channelNumber];
|
|
2090
|
+
if (channel.isDrum)
|
|
2091
|
+
return;
|
|
2092
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1921
2093
|
const prev = channel.state.vibratoDepth;
|
|
1922
2094
|
channel.state.vibratoDepth = vibratoDepth / 64;
|
|
1923
2095
|
if (0 < prev) {
|
|
@@ -1932,8 +2104,10 @@ export class Midy {
|
|
|
1932
2104
|
}
|
|
1933
2105
|
}
|
|
1934
2106
|
setVibratoDelay(channelNumber, vibratoDelay) {
|
|
1935
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1936
2107
|
const channel = this.channels[channelNumber];
|
|
2108
|
+
if (channel.isDrum)
|
|
2109
|
+
return;
|
|
2110
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1937
2111
|
channel.state.vibratoDelay = vibratoDelay / 64;
|
|
1938
2112
|
if (0 < channel.state.vibratoDepth) {
|
|
1939
2113
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -2080,8 +2254,10 @@ export class Midy {
|
|
|
2080
2254
|
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
2081
2255
|
}
|
|
2082
2256
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
2083
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
2084
2257
|
const channel = this.channels[channelNumber];
|
|
2258
|
+
if (channel.isDrum)
|
|
2259
|
+
return;
|
|
2260
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2085
2261
|
const state = channel.state;
|
|
2086
2262
|
const prev = state.pitchWheelSensitivity;
|
|
2087
2263
|
const next = value / 128;
|
|
@@ -2097,8 +2273,10 @@ export class Midy {
|
|
|
2097
2273
|
this.setFineTuning(channelNumber, fineTuning, scheduleTime);
|
|
2098
2274
|
}
|
|
2099
2275
|
setFineTuning(channelNumber, value, scheduleTime) {
|
|
2100
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
2101
2276
|
const channel = this.channels[channelNumber];
|
|
2277
|
+
if (channel.isDrum)
|
|
2278
|
+
return;
|
|
2279
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2102
2280
|
const prev = channel.fineTuning;
|
|
2103
2281
|
const next = (value - 8192) / 8.192; // cent
|
|
2104
2282
|
channel.fineTuning = next;
|
|
@@ -2112,8 +2290,10 @@ export class Midy {
|
|
|
2112
2290
|
this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
|
|
2113
2291
|
}
|
|
2114
2292
|
setCoarseTuning(channelNumber, value, scheduleTime) {
|
|
2115
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
2116
2293
|
const channel = this.channels[channelNumber];
|
|
2294
|
+
if (channel.isDrum)
|
|
2295
|
+
return;
|
|
2296
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2117
2297
|
const prev = channel.coarseTuning;
|
|
2118
2298
|
const next = (value - 64) * 100; // cent
|
|
2119
2299
|
channel.coarseTuning = next;
|
|
@@ -2127,8 +2307,10 @@ export class Midy {
|
|
|
2127
2307
|
this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
|
|
2128
2308
|
}
|
|
2129
2309
|
setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
|
|
2130
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
2131
2310
|
const channel = this.channels[channelNumber];
|
|
2311
|
+
if (channel.isDrum)
|
|
2312
|
+
return;
|
|
2313
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2132
2314
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2133
2315
|
this.updateModulation(channel, scheduleTime);
|
|
2134
2316
|
}
|
|
@@ -2136,16 +2318,31 @@ export class Midy {
|
|
|
2136
2318
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2137
2319
|
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
2138
2320
|
}
|
|
2321
|
+
resetAllStates(channelNumber) {
|
|
2322
|
+
const channel = this.channels[channelNumber];
|
|
2323
|
+
const state = channel.state;
|
|
2324
|
+
for (const type of Object.keys(defaultControllerState)) {
|
|
2325
|
+
state[type] = defaultControllerState[type].defaultValue;
|
|
2326
|
+
}
|
|
2327
|
+
for (const type of Object.keys(this.constructor.channelSettings)) {
|
|
2328
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
2329
|
+
}
|
|
2330
|
+
this.mode = "GM2";
|
|
2331
|
+
this.masterFineTuning = 0; // cb
|
|
2332
|
+
this.masterCoarseTuning = 0; // cb
|
|
2333
|
+
}
|
|
2334
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2139
2335
|
resetAllControllers(channelNumber) {
|
|
2140
2336
|
const stateTypes = [
|
|
2337
|
+
"polyphonicKeyPressure",
|
|
2338
|
+
"channelPressure",
|
|
2339
|
+
"pitchWheel",
|
|
2141
2340
|
"expression",
|
|
2142
2341
|
"modulationDepth",
|
|
2143
2342
|
"sustainPedal",
|
|
2144
2343
|
"portamento",
|
|
2145
2344
|
"sostenutoPedal",
|
|
2146
2345
|
"softPedal",
|
|
2147
|
-
"channelPressure",
|
|
2148
|
-
"pitchWheelSensitivity",
|
|
2149
2346
|
];
|
|
2150
2347
|
const channel = this.channels[channelNumber];
|
|
2151
2348
|
const state = channel.state;
|
|
@@ -2199,12 +2396,12 @@ export class Midy {
|
|
|
2199
2396
|
case 9:
|
|
2200
2397
|
switch (data[3]) {
|
|
2201
2398
|
case 1:
|
|
2202
|
-
this.GM1SystemOn();
|
|
2399
|
+
this.GM1SystemOn(scheduleTime);
|
|
2203
2400
|
break;
|
|
2204
2401
|
case 2: // GM System Off
|
|
2205
2402
|
break;
|
|
2206
2403
|
case 3:
|
|
2207
|
-
this.GM2SystemOn();
|
|
2404
|
+
this.GM2SystemOn(scheduleTime);
|
|
2208
2405
|
break;
|
|
2209
2406
|
default:
|
|
2210
2407
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -2214,25 +2411,35 @@ export class Midy {
|
|
|
2214
2411
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2215
2412
|
}
|
|
2216
2413
|
}
|
|
2217
|
-
GM1SystemOn() {
|
|
2414
|
+
GM1SystemOn(scheduleTime) {
|
|
2415
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2416
|
+
this.mode = "GM1";
|
|
2218
2417
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2418
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2219
2419
|
const channel = this.channels[i];
|
|
2220
2420
|
channel.bankMSB = 0;
|
|
2221
2421
|
channel.bankLSB = 0;
|
|
2222
2422
|
channel.bank = 0;
|
|
2423
|
+
channel.isDrum = false;
|
|
2223
2424
|
}
|
|
2224
2425
|
this.channels[9].bankMSB = 1;
|
|
2225
2426
|
this.channels[9].bank = 128;
|
|
2427
|
+
this.channels[9].isDrum = true;
|
|
2226
2428
|
}
|
|
2227
|
-
GM2SystemOn() {
|
|
2429
|
+
GM2SystemOn(scheduleTime) {
|
|
2430
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2431
|
+
this.mode = "GM2";
|
|
2228
2432
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2433
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2229
2434
|
const channel = this.channels[i];
|
|
2230
2435
|
channel.bankMSB = 121;
|
|
2231
2436
|
channel.bankLSB = 0;
|
|
2232
2437
|
channel.bank = 121 * 128;
|
|
2438
|
+
channel.isDrum = false;
|
|
2233
2439
|
}
|
|
2234
2440
|
this.channels[9].bankMSB = 120;
|
|
2235
2441
|
this.channels[9].bank = 120 * 128;
|
|
2442
|
+
this.channels[9].isDrum = true;
|
|
2236
2443
|
}
|
|
2237
2444
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2238
2445
|
switch (data[2]) {
|
|
@@ -2308,8 +2515,14 @@ export class Midy {
|
|
|
2308
2515
|
const prev = this.masterFineTuning;
|
|
2309
2516
|
const next = (value - 8192) / 8.192; // cent
|
|
2310
2517
|
this.masterFineTuning = next;
|
|
2311
|
-
|
|
2312
|
-
this.
|
|
2518
|
+
const detuneChange = next - prev;
|
|
2519
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2520
|
+
const channel = this.channels[i];
|
|
2521
|
+
if (channel.isDrum)
|
|
2522
|
+
continue;
|
|
2523
|
+
channel.detune += detuneChange;
|
|
2524
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2525
|
+
}
|
|
2313
2526
|
}
|
|
2314
2527
|
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2315
2528
|
const coarseTuning = data[4];
|
|
@@ -2319,8 +2532,14 @@ export class Midy {
|
|
|
2319
2532
|
const prev = this.masterCoarseTuning;
|
|
2320
2533
|
const next = (value - 64) * 100; // cent
|
|
2321
2534
|
this.masterCoarseTuning = next;
|
|
2322
|
-
|
|
2323
|
-
this.
|
|
2535
|
+
const detuneChange = next - prev;
|
|
2536
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2537
|
+
const channel = this.channels[i];
|
|
2538
|
+
if (channel.isDrum)
|
|
2539
|
+
continue;
|
|
2540
|
+
channel.detune += detuneChange;
|
|
2541
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2542
|
+
}
|
|
2324
2543
|
}
|
|
2325
2544
|
handleGlobalParameterControlSysEx(data, scheduleTime) {
|
|
2326
2545
|
if (data[7] === 1) {
|
|
@@ -2504,7 +2723,7 @@ export class Midy {
|
|
|
2504
2723
|
return value * 0.00787;
|
|
2505
2724
|
}
|
|
2506
2725
|
getChannelBitmap(data) {
|
|
2507
|
-
const bitmap = new Array(
|
|
2726
|
+
const bitmap = new Array(this.channels.length).fill(false);
|
|
2508
2727
|
const ff = data[4] & 0b11;
|
|
2509
2728
|
const gg = data[5] & 0x7F;
|
|
2510
2729
|
const hh = data[6] & 0x7F;
|
|
@@ -2532,6 +2751,8 @@ export class Midy {
|
|
|
2532
2751
|
if (!channelBitmap[i])
|
|
2533
2752
|
continue;
|
|
2534
2753
|
const channel = this.channels[i];
|
|
2754
|
+
if (channel.isDrum)
|
|
2755
|
+
continue;
|
|
2535
2756
|
for (let j = 0; j < 12; j++) {
|
|
2536
2757
|
const centValue = data[j + 7] - 64;
|
|
2537
2758
|
channel.scaleOctaveTuningTable[j] = centValue;
|
|
@@ -2550,6 +2771,8 @@ export class Midy {
|
|
|
2550
2771
|
if (!channelBitmap[i])
|
|
2551
2772
|
continue;
|
|
2552
2773
|
const channel = this.channels[i];
|
|
2774
|
+
if (channel.isDrum)
|
|
2775
|
+
continue;
|
|
2553
2776
|
for (let j = 0; j < 12; j++) {
|
|
2554
2777
|
const index = 7 + j * 2;
|
|
2555
2778
|
const msb = data[index] & 0x7F;
|
|
@@ -2620,7 +2843,10 @@ export class Midy {
|
|
|
2620
2843
|
}
|
|
2621
2844
|
handlePressureSysEx(data, tableName) {
|
|
2622
2845
|
const channelNumber = data[4];
|
|
2623
|
-
const
|
|
2846
|
+
const channel = this.channels[channelNumber];
|
|
2847
|
+
if (channel.isDrum)
|
|
2848
|
+
return;
|
|
2849
|
+
const table = channel[tableName];
|
|
2624
2850
|
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2625
2851
|
const pp = data[i];
|
|
2626
2852
|
const rr = data[i + 1];
|
|
@@ -2648,8 +2874,11 @@ export class Midy {
|
|
|
2648
2874
|
}
|
|
2649
2875
|
handleControlChangeSysEx(data) {
|
|
2650
2876
|
const channelNumber = data[4];
|
|
2877
|
+
const channel = this.channels[channelNumber];
|
|
2878
|
+
if (channel.isDrum)
|
|
2879
|
+
return;
|
|
2651
2880
|
const controllerType = data[5];
|
|
2652
|
-
const table =
|
|
2881
|
+
const table = channel.controlTable[controllerType];
|
|
2653
2882
|
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2654
2883
|
const pp = data[i];
|
|
2655
2884
|
const rr = data[i + 1];
|
|
@@ -2663,8 +2892,11 @@ export class Midy {
|
|
|
2663
2892
|
}
|
|
2664
2893
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2665
2894
|
const channelNumber = data[4];
|
|
2895
|
+
const channel = this.channels[channelNumber];
|
|
2896
|
+
if (channel.isDrum)
|
|
2897
|
+
return;
|
|
2666
2898
|
const keyNumber = data[5];
|
|
2667
|
-
const table =
|
|
2899
|
+
const table = channel.keyBasedInstrumentControlTable;
|
|
2668
2900
|
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2669
2901
|
const controllerType = data[i];
|
|
2670
2902
|
const value = data[i + 1];
|
|
@@ -2683,15 +2915,23 @@ export class Midy {
|
|
|
2683
2915
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2684
2916
|
}
|
|
2685
2917
|
}
|
|
2918
|
+
// https://github.com/marmooo/js-timer-benchmark
|
|
2686
2919
|
scheduleTask(callback, scheduleTime) {
|
|
2687
2920
|
return new Promise((resolve) => {
|
|
2688
|
-
const bufferSource = new AudioBufferSourceNode(this.audioContext
|
|
2921
|
+
const bufferSource = new AudioBufferSourceNode(this.audioContext, {
|
|
2922
|
+
buffer: this.schedulerBuffer,
|
|
2923
|
+
});
|
|
2924
|
+
bufferSource.connect(this.scheduler);
|
|
2689
2925
|
bufferSource.onended = () => {
|
|
2690
|
-
|
|
2691
|
-
|
|
2926
|
+
try {
|
|
2927
|
+
callback();
|
|
2928
|
+
}
|
|
2929
|
+
finally {
|
|
2930
|
+
bufferSource.disconnect();
|
|
2931
|
+
resolve();
|
|
2932
|
+
}
|
|
2692
2933
|
};
|
|
2693
2934
|
bufferSource.start(scheduleTime);
|
|
2694
|
-
bufferSource.stop(scheduleTime);
|
|
2695
2935
|
});
|
|
2696
2936
|
}
|
|
2697
2937
|
}
|
|
@@ -2700,9 +2940,8 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2700
2940
|
configurable: true,
|
|
2701
2941
|
writable: true,
|
|
2702
2942
|
value: {
|
|
2703
|
-
currentBufferSource: null,
|
|
2704
2943
|
detune: 0,
|
|
2705
|
-
|
|
2944
|
+
programNumber: 0,
|
|
2706
2945
|
bank: 121 * 128,
|
|
2707
2946
|
bankMSB: 121,
|
|
2708
2947
|
bankLSB: 0,
|
|
@@ -2711,8 +2950,8 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2711
2950
|
rpnMSB: 127,
|
|
2712
2951
|
rpnLSB: 127,
|
|
2713
2952
|
mono: false, // CC#124, CC#125
|
|
2953
|
+
modulationDepthRange: 50, // cent
|
|
2714
2954
|
fineTuning: 0, // cb
|
|
2715
2955
|
coarseTuning: 0, // cb
|
|
2716
|
-
modulationDepthRange: 50, // cent
|
|
2717
2956
|
}
|
|
2718
2957
|
});
|