@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/script/midy.js
CHANGED
|
@@ -160,6 +160,39 @@ class Note {
|
|
|
160
160
|
this.voiceParams = voiceParams;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
|
+
const drumExclusiveClassesByKit = new Array(57);
|
|
164
|
+
const drumExclusiveClassCount = 10;
|
|
165
|
+
const standardSet = new Uint8Array(128);
|
|
166
|
+
standardSet[42] = 1;
|
|
167
|
+
standardSet[44] = 1;
|
|
168
|
+
standardSet[46] = 1; // HH
|
|
169
|
+
standardSet[71] = 2;
|
|
170
|
+
standardSet[72] = 2; // Whistle
|
|
171
|
+
standardSet[73] = 3;
|
|
172
|
+
standardSet[74] = 3; // Guiro
|
|
173
|
+
standardSet[78] = 4;
|
|
174
|
+
standardSet[79] = 4; // Cuica
|
|
175
|
+
standardSet[80] = 5;
|
|
176
|
+
standardSet[81] = 5; // Triangle
|
|
177
|
+
standardSet[29] = 6;
|
|
178
|
+
standardSet[30] = 6; // Scratch
|
|
179
|
+
standardSet[86] = 7;
|
|
180
|
+
standardSet[87] = 7; // Surdo
|
|
181
|
+
drumExclusiveClassesByKit[0] = standardSet;
|
|
182
|
+
const analogSet = new Uint8Array(128);
|
|
183
|
+
analogSet[42] = 8;
|
|
184
|
+
analogSet[44] = 8;
|
|
185
|
+
analogSet[46] = 8; // CHH
|
|
186
|
+
drumExclusiveClassesByKit[25] = analogSet;
|
|
187
|
+
const orchestraSet = new Uint8Array(128);
|
|
188
|
+
orchestraSet[27] = 9;
|
|
189
|
+
orchestraSet[28] = 9;
|
|
190
|
+
orchestraSet[29] = 9; // HH
|
|
191
|
+
drumExclusiveClassesByKit[48] = orchestraSet;
|
|
192
|
+
const sfxSet = new Uint8Array(128);
|
|
193
|
+
sfxSet[41] = 10;
|
|
194
|
+
sfxSet[42] = 10; // Scratch
|
|
195
|
+
drumExclusiveClassesByKit[56] = sfxSet;
|
|
163
196
|
// normalized to 0-1 for use with the SF2 modulator model
|
|
164
197
|
const defaultControllerState = {
|
|
165
198
|
noteOnVelocity: { type: 2, defaultValue: 0 },
|
|
@@ -249,17 +282,11 @@ const volumeEnvelopeKeys = [
|
|
|
249
282
|
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
250
283
|
class Midy {
|
|
251
284
|
constructor(audioContext, options = this.defaultOptions) {
|
|
252
|
-
Object.defineProperty(this, "
|
|
285
|
+
Object.defineProperty(this, "mode", {
|
|
253
286
|
enumerable: true,
|
|
254
287
|
configurable: true,
|
|
255
288
|
writable: true,
|
|
256
|
-
value:
|
|
257
|
-
});
|
|
258
|
-
Object.defineProperty(this, "totalTime", {
|
|
259
|
-
enumerable: true,
|
|
260
|
-
configurable: true,
|
|
261
|
-
writable: true,
|
|
262
|
-
value: 0
|
|
289
|
+
value: "GM2"
|
|
263
290
|
});
|
|
264
291
|
Object.defineProperty(this, "masterFineTuning", {
|
|
265
292
|
enumerable: true,
|
|
@@ -294,6 +321,24 @@ class Midy {
|
|
|
294
321
|
delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
|
|
295
322
|
}
|
|
296
323
|
});
|
|
324
|
+
Object.defineProperty(this, "numChannels", {
|
|
325
|
+
enumerable: true,
|
|
326
|
+
configurable: true,
|
|
327
|
+
writable: true,
|
|
328
|
+
value: 16
|
|
329
|
+
});
|
|
330
|
+
Object.defineProperty(this, "ticksPerBeat", {
|
|
331
|
+
enumerable: true,
|
|
332
|
+
configurable: true,
|
|
333
|
+
writable: true,
|
|
334
|
+
value: 120
|
|
335
|
+
});
|
|
336
|
+
Object.defineProperty(this, "totalTime", {
|
|
337
|
+
enumerable: true,
|
|
338
|
+
configurable: true,
|
|
339
|
+
writable: true,
|
|
340
|
+
value: 0
|
|
341
|
+
});
|
|
297
342
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
298
343
|
enumerable: true,
|
|
299
344
|
configurable: true,
|
|
@@ -396,11 +441,17 @@ class Midy {
|
|
|
396
441
|
writable: true,
|
|
397
442
|
value: []
|
|
398
443
|
});
|
|
399
|
-
Object.defineProperty(this, "
|
|
444
|
+
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
445
|
+
enumerable: true,
|
|
446
|
+
configurable: true,
|
|
447
|
+
writable: true,
|
|
448
|
+
value: new Array(128)
|
|
449
|
+
});
|
|
450
|
+
Object.defineProperty(this, "drumExclusiveClassNotes", {
|
|
400
451
|
enumerable: true,
|
|
401
452
|
configurable: true,
|
|
402
453
|
writable: true,
|
|
403
|
-
value: new
|
|
454
|
+
value: new Array(this.numChannels * drumExclusiveClassCount)
|
|
404
455
|
});
|
|
405
456
|
Object.defineProperty(this, "defaultOptions", {
|
|
406
457
|
enumerable: true,
|
|
@@ -427,6 +478,11 @@ class Midy {
|
|
|
427
478
|
this.audioContext = audioContext;
|
|
428
479
|
this.options = { ...this.defaultOptions, ...options };
|
|
429
480
|
this.masterVolume = new GainNode(audioContext);
|
|
481
|
+
this.scheduler = new GainNode(audioContext, { gain: 0 });
|
|
482
|
+
this.schedulerBuffer = new AudioBuffer({
|
|
483
|
+
length: 1,
|
|
484
|
+
sampleRate: audioContext.sampleRate,
|
|
485
|
+
});
|
|
430
486
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
431
487
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
432
488
|
this.channels = this.createChannels(audioContext);
|
|
@@ -435,6 +491,7 @@ class Midy {
|
|
|
435
491
|
this.chorusEffect.output.connect(this.masterVolume);
|
|
436
492
|
this.reverbEffect.output.connect(this.masterVolume);
|
|
437
493
|
this.masterVolume.connect(audioContext.destination);
|
|
494
|
+
this.scheduler.connect(audioContext.destination);
|
|
438
495
|
this.GM2SystemOn();
|
|
439
496
|
}
|
|
440
497
|
initSoundFontTable() {
|
|
@@ -488,8 +545,10 @@ class Midy {
|
|
|
488
545
|
};
|
|
489
546
|
}
|
|
490
547
|
createChannels(audioContext) {
|
|
491
|
-
const channels = Array.from({ length:
|
|
548
|
+
const channels = Array.from({ length: this.numChannels }, () => {
|
|
492
549
|
return {
|
|
550
|
+
currentBufferSource: null,
|
|
551
|
+
isDrum: false,
|
|
493
552
|
...this.constructor.channelSettings,
|
|
494
553
|
state: new ControllerState(),
|
|
495
554
|
controlTable: this.initControlTable(),
|
|
@@ -534,7 +593,7 @@ class Midy {
|
|
|
534
593
|
return audioBuffer;
|
|
535
594
|
}
|
|
536
595
|
}
|
|
537
|
-
|
|
596
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
538
597
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
539
598
|
bufferSource.buffer = audioBuffer;
|
|
540
599
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
@@ -628,7 +687,8 @@ class Midy {
|
|
|
628
687
|
if (queueIndex >= this.timeline.length) {
|
|
629
688
|
await Promise.all(this.notePromises);
|
|
630
689
|
this.notePromises = [];
|
|
631
|
-
this.
|
|
690
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
691
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
632
692
|
this.audioBufferCache.clear();
|
|
633
693
|
resolve();
|
|
634
694
|
return;
|
|
@@ -647,7 +707,8 @@ class Midy {
|
|
|
647
707
|
else if (this.isStopping) {
|
|
648
708
|
await this.stopNotes(0, true, now);
|
|
649
709
|
this.notePromises = [];
|
|
650
|
-
this.
|
|
710
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
711
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
651
712
|
this.audioBufferCache.clear();
|
|
652
713
|
resolve();
|
|
653
714
|
this.isStopping = false;
|
|
@@ -656,7 +717,8 @@ class Midy {
|
|
|
656
717
|
}
|
|
657
718
|
else if (this.isSeeking) {
|
|
658
719
|
this.stopNotes(0, true, now);
|
|
659
|
-
this.
|
|
720
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
721
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
660
722
|
this.startTime = this.audioContext.currentTime;
|
|
661
723
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
662
724
|
offset = this.resumeTime - this.startTime;
|
|
@@ -684,7 +746,7 @@ class Midy {
|
|
|
684
746
|
extractMidiData(midi) {
|
|
685
747
|
const instruments = new Set();
|
|
686
748
|
const timeline = [];
|
|
687
|
-
const tmpChannels = new Array(
|
|
749
|
+
const tmpChannels = new Array(this.channels.length);
|
|
688
750
|
for (let i = 0; i < tmpChannels.length; i++) {
|
|
689
751
|
tmpChannels[i] = {
|
|
690
752
|
programNumber: -1,
|
|
@@ -812,6 +874,9 @@ class Midy {
|
|
|
812
874
|
if (!this.isPlaying)
|
|
813
875
|
return;
|
|
814
876
|
this.isStopping = true;
|
|
877
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
878
|
+
this.resetAllStates(i);
|
|
879
|
+
}
|
|
815
880
|
}
|
|
816
881
|
pause() {
|
|
817
882
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -1017,7 +1082,9 @@ class Midy {
|
|
|
1017
1082
|
return 8.176 * this.centToRate(cent);
|
|
1018
1083
|
}
|
|
1019
1084
|
calcChannelDetune(channel) {
|
|
1020
|
-
const masterTuning =
|
|
1085
|
+
const masterTuning = channel.isDrum
|
|
1086
|
+
? 0
|
|
1087
|
+
: this.masterCoarseTuning + this.masterFineTuning;
|
|
1021
1088
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
1022
1089
|
const tuning = masterTuning + channelTuning;
|
|
1023
1090
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
@@ -1044,9 +1111,8 @@ class Midy {
|
|
|
1044
1111
|
.setValueAtTime(detune, scheduleTime);
|
|
1045
1112
|
}
|
|
1046
1113
|
getPortamentoTime(channel) {
|
|
1047
|
-
const factor = 5 * Math.log(10)
|
|
1048
|
-
|
|
1049
|
-
return Math.log(time) / factor;
|
|
1114
|
+
const factor = 5 * Math.log(10) * 127;
|
|
1115
|
+
return channel.state.portamentoTime * factor;
|
|
1050
1116
|
}
|
|
1051
1117
|
setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
|
|
1052
1118
|
const { voiceParams, startTime } = note;
|
|
@@ -1185,8 +1251,8 @@ class Midy {
|
|
|
1185
1251
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1186
1252
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1187
1253
|
}
|
|
1188
|
-
async getAudioBuffer(
|
|
1189
|
-
const audioBufferId = this.getAudioBufferId(
|
|
1254
|
+
async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
|
|
1255
|
+
const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
|
|
1190
1256
|
const cache = this.audioBufferCache.get(audioBufferId);
|
|
1191
1257
|
if (cache) {
|
|
1192
1258
|
cache.counter += 1;
|
|
@@ -1209,8 +1275,8 @@ class Midy {
|
|
|
1209
1275
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1210
1276
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1211
1277
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1212
|
-
const audioBuffer = await this.getAudioBuffer(channel.
|
|
1213
|
-
note.bufferSource = this.
|
|
1278
|
+
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1279
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
1214
1280
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1215
1281
|
note.gainL = new GainNode(this.audioContext);
|
|
1216
1282
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1254,23 +1320,72 @@ class Midy {
|
|
|
1254
1320
|
note.bufferSource.start(startTime);
|
|
1255
1321
|
return note;
|
|
1256
1322
|
}
|
|
1257
|
-
calcBank(channel
|
|
1258
|
-
|
|
1259
|
-
|
|
1323
|
+
calcBank(channel) {
|
|
1324
|
+
switch (this.mode) {
|
|
1325
|
+
case "GM1":
|
|
1326
|
+
if (channel.isDrum)
|
|
1327
|
+
return 128;
|
|
1328
|
+
return 0;
|
|
1329
|
+
case "GM2":
|
|
1330
|
+
if (channel.bankMSB === 121)
|
|
1331
|
+
return 0;
|
|
1332
|
+
if (channel.isDrum)
|
|
1333
|
+
return 128;
|
|
1334
|
+
return channel.bank;
|
|
1335
|
+
default:
|
|
1336
|
+
return channel.bank;
|
|
1260
1337
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1338
|
+
}
|
|
1339
|
+
handleExclusiveClass(note, channelNumber, startTime) {
|
|
1340
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1341
|
+
if (exclusiveClass === 0)
|
|
1342
|
+
return;
|
|
1343
|
+
const prev = this.exclusiveClassNotes[exclusiveClass];
|
|
1344
|
+
if (prev) {
|
|
1345
|
+
const [prevNote, prevChannelNumber] = prev;
|
|
1346
|
+
if (prevNote && !prevNote.ending) {
|
|
1347
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1348
|
+
startTime, true, // force
|
|
1349
|
+
undefined);
|
|
1350
|
+
}
|
|
1263
1351
|
}
|
|
1264
|
-
|
|
1352
|
+
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
1353
|
+
}
|
|
1354
|
+
handleDrumExclusiveClass(note, channelNumber, startTime) {
|
|
1355
|
+
const channel = this.channels[channelNumber];
|
|
1356
|
+
if (!channel.isDrum)
|
|
1357
|
+
return;
|
|
1358
|
+
const kitTable = drumExclusiveClassesByKit[channel.programNumber];
|
|
1359
|
+
if (!kitTable)
|
|
1360
|
+
return;
|
|
1361
|
+
const drumExclusiveClass = kitTable[note.noteNumber];
|
|
1362
|
+
if (drumExclusiveClass === 0)
|
|
1363
|
+
return;
|
|
1364
|
+
const index = (drumExclusiveClass - 1) * this.channels.length +
|
|
1365
|
+
channelNumber;
|
|
1366
|
+
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1367
|
+
if (prevNote && !prevNote.ending) {
|
|
1368
|
+
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1369
|
+
startTime, true, // force
|
|
1370
|
+
undefined);
|
|
1371
|
+
}
|
|
1372
|
+
this.drumExclusiveClassNotes[index] = note;
|
|
1373
|
+
}
|
|
1374
|
+
isDrumNoteOffException(channel, noteNumber) {
|
|
1375
|
+
if (!channel.isDrum)
|
|
1376
|
+
return false;
|
|
1377
|
+
const programNumber = channel.programNumber;
|
|
1378
|
+
return (programNumber === 48 && noteNumber === 88) ||
|
|
1379
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84);
|
|
1265
1380
|
}
|
|
1266
1381
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
|
|
1267
1382
|
const channel = this.channels[channelNumber];
|
|
1268
1383
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1269
|
-
const soundFontIndex = this.soundFontTable[channel.
|
|
1384
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
1270
1385
|
if (soundFontIndex === undefined)
|
|
1271
1386
|
return;
|
|
1272
1387
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1273
|
-
const voice = soundFont.getVoice(bankNumber, channel.
|
|
1388
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1274
1389
|
if (!voice)
|
|
1275
1390
|
return;
|
|
1276
1391
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
@@ -1280,31 +1395,58 @@ class Midy {
|
|
|
1280
1395
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1281
1396
|
channel.sustainNotes.push(note);
|
|
1282
1397
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1286
|
-
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1287
|
-
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1288
|
-
if (!prevNote.ending) {
|
|
1289
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1290
|
-
startTime, true, // force
|
|
1291
|
-
undefined);
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
1295
|
-
}
|
|
1398
|
+
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1399
|
+
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1296
1400
|
const scheduledNotes = channel.scheduledNotes;
|
|
1297
|
-
|
|
1298
|
-
|
|
1401
|
+
let notes = scheduledNotes.get(noteNumber);
|
|
1402
|
+
if (notes) {
|
|
1403
|
+
notes.push(note);
|
|
1299
1404
|
}
|
|
1300
1405
|
else {
|
|
1301
|
-
|
|
1406
|
+
notes = [note];
|
|
1407
|
+
scheduledNotes.set(noteNumber, notes);
|
|
1408
|
+
}
|
|
1409
|
+
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1410
|
+
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1411
|
+
const index = notes.length - 1;
|
|
1412
|
+
const promise = new Promise((resolve) => {
|
|
1413
|
+
note.bufferSource.onended = () => {
|
|
1414
|
+
this.disconnectNote(note, scheduledNotes, index);
|
|
1415
|
+
resolve();
|
|
1416
|
+
};
|
|
1417
|
+
note.bufferSource.stop(stopTime);
|
|
1418
|
+
});
|
|
1419
|
+
this.notePromises.push(promise);
|
|
1302
1420
|
}
|
|
1303
1421
|
}
|
|
1304
1422
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1305
1423
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1306
1424
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1307
1425
|
}
|
|
1426
|
+
disconnectNote(note, scheduledNotes, index) {
|
|
1427
|
+
scheduledNotes[index] = null;
|
|
1428
|
+
note.bufferSource.disconnect();
|
|
1429
|
+
note.filterNode.disconnect();
|
|
1430
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1431
|
+
note.volumeNode.disconnect();
|
|
1432
|
+
note.gainL.disconnect();
|
|
1433
|
+
note.gainR.disconnect();
|
|
1434
|
+
if (note.modulationDepth) {
|
|
1435
|
+
note.volumeDepth.disconnect();
|
|
1436
|
+
note.modulationDepth.disconnect();
|
|
1437
|
+
note.modulationLFO.stop();
|
|
1438
|
+
}
|
|
1439
|
+
if (note.vibratoDepth) {
|
|
1440
|
+
note.vibratoDepth.disconnect();
|
|
1441
|
+
note.vibratoLFO.stop();
|
|
1442
|
+
}
|
|
1443
|
+
if (note.reverbEffectsSend) {
|
|
1444
|
+
note.reverbEffectsSend.disconnect();
|
|
1445
|
+
}
|
|
1446
|
+
if (note.chorusEffectsSend) {
|
|
1447
|
+
note.chorusEffectsSend.disconnect();
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1308
1450
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1309
1451
|
const note = scheduledNotes[index];
|
|
1310
1452
|
note.volumeEnvelopeNode.gain
|
|
@@ -1316,28 +1458,7 @@ class Midy {
|
|
|
1316
1458
|
}, stopTime);
|
|
1317
1459
|
return new Promise((resolve) => {
|
|
1318
1460
|
note.bufferSource.onended = () => {
|
|
1319
|
-
scheduledNotes
|
|
1320
|
-
note.bufferSource.disconnect();
|
|
1321
|
-
note.filterNode.disconnect();
|
|
1322
|
-
note.volumeEnvelopeNode.disconnect();
|
|
1323
|
-
note.volumeNode.disconnect();
|
|
1324
|
-
note.gainL.disconnect();
|
|
1325
|
-
note.gainR.disconnect();
|
|
1326
|
-
if (note.modulationDepth) {
|
|
1327
|
-
note.volumeDepth.disconnect();
|
|
1328
|
-
note.modulationDepth.disconnect();
|
|
1329
|
-
note.modulationLFO.stop();
|
|
1330
|
-
}
|
|
1331
|
-
if (note.vibratoDepth) {
|
|
1332
|
-
note.vibratoDepth.disconnect();
|
|
1333
|
-
note.vibratoLFO.stop();
|
|
1334
|
-
}
|
|
1335
|
-
if (note.reverbEffectsSend) {
|
|
1336
|
-
note.reverbEffectsSend.disconnect();
|
|
1337
|
-
}
|
|
1338
|
-
if (note.chorusEffectsSend) {
|
|
1339
|
-
note.chorusEffectsSend.disconnect();
|
|
1340
|
-
}
|
|
1461
|
+
this.disconnectNote(note, scheduledNotes, index);
|
|
1341
1462
|
resolve();
|
|
1342
1463
|
};
|
|
1343
1464
|
note.bufferSource.stop(stopTime);
|
|
@@ -1345,6 +1466,8 @@ class Midy {
|
|
|
1345
1466
|
}
|
|
1346
1467
|
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1347
1468
|
const channel = this.channels[channelNumber];
|
|
1469
|
+
if (this.isDrumNoteOffException(channel, noteNumber))
|
|
1470
|
+
return;
|
|
1348
1471
|
const state = channel.state;
|
|
1349
1472
|
if (!force) {
|
|
1350
1473
|
if (0.5 <= state.sustainPedal)
|
|
@@ -1444,13 +1567,25 @@ class Midy {
|
|
|
1444
1567
|
}
|
|
1445
1568
|
// this.applyVoiceParams(channel, 10);
|
|
1446
1569
|
}
|
|
1447
|
-
handleProgramChange(channelNumber,
|
|
1570
|
+
handleProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1448
1571
|
const channel = this.channels[channelNumber];
|
|
1449
1572
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1450
|
-
channel.
|
|
1573
|
+
channel.programNumber = programNumber;
|
|
1574
|
+
if (this.mode === "GM2") {
|
|
1575
|
+
switch (channel.bankMSB) {
|
|
1576
|
+
case 120:
|
|
1577
|
+
channel.isDrum = true;
|
|
1578
|
+
break;
|
|
1579
|
+
case 121:
|
|
1580
|
+
channel.isDrum = false;
|
|
1581
|
+
break;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1451
1584
|
}
|
|
1452
1585
|
handleChannelPressure(channelNumber, value, scheduleTime) {
|
|
1453
1586
|
const channel = this.channels[channelNumber];
|
|
1587
|
+
if (channel.isDrum)
|
|
1588
|
+
return;
|
|
1454
1589
|
const prev = channel.state.channelPressure;
|
|
1455
1590
|
const next = value / 127;
|
|
1456
1591
|
channel.state.channelPressure = next;
|
|
@@ -1469,8 +1604,10 @@ class Midy {
|
|
|
1469
1604
|
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
1470
1605
|
}
|
|
1471
1606
|
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1472
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1473
1607
|
const channel = this.channels[channelNumber];
|
|
1608
|
+
if (channel.isDrum)
|
|
1609
|
+
return;
|
|
1610
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1474
1611
|
const state = channel.state;
|
|
1475
1612
|
const prev = state.pitchWheel * 2 - 1;
|
|
1476
1613
|
const next = (value - 8192) / 8192;
|
|
@@ -1752,15 +1889,16 @@ class Midy {
|
|
|
1752
1889
|
});
|
|
1753
1890
|
}
|
|
1754
1891
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1755
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1756
1892
|
const channel = this.channels[channelNumber];
|
|
1893
|
+
if (channel.isDrum)
|
|
1894
|
+
return;
|
|
1895
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1757
1896
|
channel.state.modulationDepth = modulation / 127;
|
|
1758
1897
|
this.updateModulation(channel, scheduleTime);
|
|
1759
1898
|
}
|
|
1760
1899
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1761
1900
|
const channel = this.channels[channelNumber];
|
|
1762
|
-
|
|
1763
|
-
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1901
|
+
channel.state.portamentoTime = portamentoTime / 127;
|
|
1764
1902
|
}
|
|
1765
1903
|
setKeyBasedVolume(channel, scheduleTime) {
|
|
1766
1904
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1832,8 +1970,10 @@ class Midy {
|
|
|
1832
1970
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1833
1971
|
}
|
|
1834
1972
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1835
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1836
1973
|
const channel = this.channels[channelNumber];
|
|
1974
|
+
if (channel.isDrum)
|
|
1975
|
+
return;
|
|
1976
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1837
1977
|
channel.state.sustainPedal = value / 127;
|
|
1838
1978
|
if (64 <= value) {
|
|
1839
1979
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1845,11 +1985,16 @@ class Midy {
|
|
|
1845
1985
|
}
|
|
1846
1986
|
}
|
|
1847
1987
|
setPortamento(channelNumber, value) {
|
|
1848
|
-
this.channels[channelNumber]
|
|
1988
|
+
const channel = this.channels[channelNumber];
|
|
1989
|
+
if (channel.isDrum)
|
|
1990
|
+
return;
|
|
1991
|
+
channel.state.portamento = value / 127;
|
|
1849
1992
|
}
|
|
1850
1993
|
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1851
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1852
1994
|
const channel = this.channels[channelNumber];
|
|
1995
|
+
if (channel.isDrum)
|
|
1996
|
+
return;
|
|
1997
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1853
1998
|
channel.state.sostenutoPedal = value / 127;
|
|
1854
1999
|
if (64 <= value) {
|
|
1855
2000
|
channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
|
|
@@ -1858,13 +2003,28 @@ class Midy {
|
|
|
1858
2003
|
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
1859
2004
|
}
|
|
1860
2005
|
}
|
|
1861
|
-
setSoftPedal(channelNumber, softPedal,
|
|
2006
|
+
setSoftPedal(channelNumber, softPedal, scheduleTime) {
|
|
1862
2007
|
const channel = this.channels[channelNumber];
|
|
2008
|
+
if (channel.isDrum)
|
|
2009
|
+
return;
|
|
2010
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1863
2011
|
channel.state.softPedal = softPedal / 127;
|
|
2012
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2013
|
+
if (note.portamento) {
|
|
2014
|
+
this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
|
|
2015
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
2016
|
+
}
|
|
2017
|
+
else {
|
|
2018
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2019
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2020
|
+
}
|
|
2021
|
+
});
|
|
1864
2022
|
}
|
|
1865
2023
|
setFilterResonance(channelNumber, filterResonance, scheduleTime) {
|
|
1866
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1867
2024
|
const channel = this.channels[channelNumber];
|
|
2025
|
+
if (channel.isDrum)
|
|
2026
|
+
return;
|
|
2027
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1868
2028
|
const state = channel.state;
|
|
1869
2029
|
state.filterResonance = filterResonance / 64;
|
|
1870
2030
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1873,13 +2033,17 @@ class Midy {
|
|
|
1873
2033
|
});
|
|
1874
2034
|
}
|
|
1875
2035
|
setReleaseTime(channelNumber, releaseTime, _scheduleTime) {
|
|
1876
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1877
2036
|
const channel = this.channels[channelNumber];
|
|
2037
|
+
if (channel.isDrum)
|
|
2038
|
+
return;
|
|
2039
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1878
2040
|
channel.state.releaseTime = releaseTime / 64;
|
|
1879
2041
|
}
|
|
1880
2042
|
setAttackTime(channelNumber, attackTime, scheduleTime) {
|
|
1881
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1882
2043
|
const channel = this.channels[channelNumber];
|
|
2044
|
+
if (channel.isDrum)
|
|
2045
|
+
return;
|
|
2046
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1883
2047
|
channel.state.attackTime = attackTime / 64;
|
|
1884
2048
|
this.processScheduledNotes(channel, (note) => {
|
|
1885
2049
|
if (note.startTime < scheduleTime)
|
|
@@ -1888,8 +2052,10 @@ class Midy {
|
|
|
1888
2052
|
});
|
|
1889
2053
|
}
|
|
1890
2054
|
setBrightness(channelNumber, brightness, scheduleTime) {
|
|
1891
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1892
2055
|
const channel = this.channels[channelNumber];
|
|
2056
|
+
if (channel.isDrum)
|
|
2057
|
+
return;
|
|
2058
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1893
2059
|
channel.state.brightness = brightness / 64;
|
|
1894
2060
|
this.processScheduledNotes(channel, (note) => {
|
|
1895
2061
|
if (note.portamento) {
|
|
@@ -1901,16 +2067,20 @@ class Midy {
|
|
|
1901
2067
|
});
|
|
1902
2068
|
}
|
|
1903
2069
|
setDecayTime(channelNumber, dacayTime, scheduleTime) {
|
|
1904
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1905
2070
|
const channel = this.channels[channelNumber];
|
|
2071
|
+
if (channel.isDrum)
|
|
2072
|
+
return;
|
|
2073
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1906
2074
|
channel.state.decayTime = dacayTime / 64;
|
|
1907
2075
|
this.processScheduledNotes(channel, (note) => {
|
|
1908
2076
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1909
2077
|
});
|
|
1910
2078
|
}
|
|
1911
2079
|
setVibratoRate(channelNumber, vibratoRate, scheduleTime) {
|
|
1912
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1913
2080
|
const channel = this.channels[channelNumber];
|
|
2081
|
+
if (channel.isDrum)
|
|
2082
|
+
return;
|
|
2083
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1914
2084
|
channel.state.vibratoRate = vibratoRate / 64;
|
|
1915
2085
|
if (channel.vibratoDepth <= 0)
|
|
1916
2086
|
return;
|
|
@@ -1919,8 +2089,10 @@ class Midy {
|
|
|
1919
2089
|
});
|
|
1920
2090
|
}
|
|
1921
2091
|
setVibratoDepth(channelNumber, vibratoDepth, scheduleTime) {
|
|
1922
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1923
2092
|
const channel = this.channels[channelNumber];
|
|
2093
|
+
if (channel.isDrum)
|
|
2094
|
+
return;
|
|
2095
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1924
2096
|
const prev = channel.state.vibratoDepth;
|
|
1925
2097
|
channel.state.vibratoDepth = vibratoDepth / 64;
|
|
1926
2098
|
if (0 < prev) {
|
|
@@ -1935,8 +2107,10 @@ class Midy {
|
|
|
1935
2107
|
}
|
|
1936
2108
|
}
|
|
1937
2109
|
setVibratoDelay(channelNumber, vibratoDelay) {
|
|
1938
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1939
2110
|
const channel = this.channels[channelNumber];
|
|
2111
|
+
if (channel.isDrum)
|
|
2112
|
+
return;
|
|
2113
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1940
2114
|
channel.state.vibratoDelay = vibratoDelay / 64;
|
|
1941
2115
|
if (0 < channel.state.vibratoDepth) {
|
|
1942
2116
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -2083,8 +2257,10 @@ class Midy {
|
|
|
2083
2257
|
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
2084
2258
|
}
|
|
2085
2259
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
2086
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
2087
2260
|
const channel = this.channels[channelNumber];
|
|
2261
|
+
if (channel.isDrum)
|
|
2262
|
+
return;
|
|
2263
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2088
2264
|
const state = channel.state;
|
|
2089
2265
|
const prev = state.pitchWheelSensitivity;
|
|
2090
2266
|
const next = value / 128;
|
|
@@ -2100,8 +2276,10 @@ class Midy {
|
|
|
2100
2276
|
this.setFineTuning(channelNumber, fineTuning, scheduleTime);
|
|
2101
2277
|
}
|
|
2102
2278
|
setFineTuning(channelNumber, value, scheduleTime) {
|
|
2103
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
2104
2279
|
const channel = this.channels[channelNumber];
|
|
2280
|
+
if (channel.isDrum)
|
|
2281
|
+
return;
|
|
2282
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2105
2283
|
const prev = channel.fineTuning;
|
|
2106
2284
|
const next = (value - 8192) / 8.192; // cent
|
|
2107
2285
|
channel.fineTuning = next;
|
|
@@ -2115,8 +2293,10 @@ class Midy {
|
|
|
2115
2293
|
this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
|
|
2116
2294
|
}
|
|
2117
2295
|
setCoarseTuning(channelNumber, value, scheduleTime) {
|
|
2118
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
2119
2296
|
const channel = this.channels[channelNumber];
|
|
2297
|
+
if (channel.isDrum)
|
|
2298
|
+
return;
|
|
2299
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2120
2300
|
const prev = channel.coarseTuning;
|
|
2121
2301
|
const next = (value - 64) * 100; // cent
|
|
2122
2302
|
channel.coarseTuning = next;
|
|
@@ -2130,8 +2310,10 @@ class Midy {
|
|
|
2130
2310
|
this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
|
|
2131
2311
|
}
|
|
2132
2312
|
setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
|
|
2133
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
2134
2313
|
const channel = this.channels[channelNumber];
|
|
2314
|
+
if (channel.isDrum)
|
|
2315
|
+
return;
|
|
2316
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2135
2317
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2136
2318
|
this.updateModulation(channel, scheduleTime);
|
|
2137
2319
|
}
|
|
@@ -2139,16 +2321,31 @@ class Midy {
|
|
|
2139
2321
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2140
2322
|
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
2141
2323
|
}
|
|
2324
|
+
resetAllStates(channelNumber) {
|
|
2325
|
+
const channel = this.channels[channelNumber];
|
|
2326
|
+
const state = channel.state;
|
|
2327
|
+
for (const type of Object.keys(defaultControllerState)) {
|
|
2328
|
+
state[type] = defaultControllerState[type].defaultValue;
|
|
2329
|
+
}
|
|
2330
|
+
for (const type of Object.keys(this.constructor.channelSettings)) {
|
|
2331
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
2332
|
+
}
|
|
2333
|
+
this.mode = "GM2";
|
|
2334
|
+
this.masterFineTuning = 0; // cb
|
|
2335
|
+
this.masterCoarseTuning = 0; // cb
|
|
2336
|
+
}
|
|
2337
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2142
2338
|
resetAllControllers(channelNumber) {
|
|
2143
2339
|
const stateTypes = [
|
|
2340
|
+
"polyphonicKeyPressure",
|
|
2341
|
+
"channelPressure",
|
|
2342
|
+
"pitchWheel",
|
|
2144
2343
|
"expression",
|
|
2145
2344
|
"modulationDepth",
|
|
2146
2345
|
"sustainPedal",
|
|
2147
2346
|
"portamento",
|
|
2148
2347
|
"sostenutoPedal",
|
|
2149
2348
|
"softPedal",
|
|
2150
|
-
"channelPressure",
|
|
2151
|
-
"pitchWheelSensitivity",
|
|
2152
2349
|
];
|
|
2153
2350
|
const channel = this.channels[channelNumber];
|
|
2154
2351
|
const state = channel.state;
|
|
@@ -2202,12 +2399,12 @@ class Midy {
|
|
|
2202
2399
|
case 9:
|
|
2203
2400
|
switch (data[3]) {
|
|
2204
2401
|
case 1:
|
|
2205
|
-
this.GM1SystemOn();
|
|
2402
|
+
this.GM1SystemOn(scheduleTime);
|
|
2206
2403
|
break;
|
|
2207
2404
|
case 2: // GM System Off
|
|
2208
2405
|
break;
|
|
2209
2406
|
case 3:
|
|
2210
|
-
this.GM2SystemOn();
|
|
2407
|
+
this.GM2SystemOn(scheduleTime);
|
|
2211
2408
|
break;
|
|
2212
2409
|
default:
|
|
2213
2410
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -2217,25 +2414,35 @@ class Midy {
|
|
|
2217
2414
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2218
2415
|
}
|
|
2219
2416
|
}
|
|
2220
|
-
GM1SystemOn() {
|
|
2417
|
+
GM1SystemOn(scheduleTime) {
|
|
2418
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2419
|
+
this.mode = "GM1";
|
|
2221
2420
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2421
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2222
2422
|
const channel = this.channels[i];
|
|
2223
2423
|
channel.bankMSB = 0;
|
|
2224
2424
|
channel.bankLSB = 0;
|
|
2225
2425
|
channel.bank = 0;
|
|
2426
|
+
channel.isDrum = false;
|
|
2226
2427
|
}
|
|
2227
2428
|
this.channels[9].bankMSB = 1;
|
|
2228
2429
|
this.channels[9].bank = 128;
|
|
2430
|
+
this.channels[9].isDrum = true;
|
|
2229
2431
|
}
|
|
2230
|
-
GM2SystemOn() {
|
|
2432
|
+
GM2SystemOn(scheduleTime) {
|
|
2433
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2434
|
+
this.mode = "GM2";
|
|
2231
2435
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2436
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2232
2437
|
const channel = this.channels[i];
|
|
2233
2438
|
channel.bankMSB = 121;
|
|
2234
2439
|
channel.bankLSB = 0;
|
|
2235
2440
|
channel.bank = 121 * 128;
|
|
2441
|
+
channel.isDrum = false;
|
|
2236
2442
|
}
|
|
2237
2443
|
this.channels[9].bankMSB = 120;
|
|
2238
2444
|
this.channels[9].bank = 120 * 128;
|
|
2445
|
+
this.channels[9].isDrum = true;
|
|
2239
2446
|
}
|
|
2240
2447
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2241
2448
|
switch (data[2]) {
|
|
@@ -2311,8 +2518,14 @@ class Midy {
|
|
|
2311
2518
|
const prev = this.masterFineTuning;
|
|
2312
2519
|
const next = (value - 8192) / 8.192; // cent
|
|
2313
2520
|
this.masterFineTuning = next;
|
|
2314
|
-
|
|
2315
|
-
this.
|
|
2521
|
+
const detuneChange = next - prev;
|
|
2522
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2523
|
+
const channel = this.channels[i];
|
|
2524
|
+
if (channel.isDrum)
|
|
2525
|
+
continue;
|
|
2526
|
+
channel.detune += detuneChange;
|
|
2527
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2528
|
+
}
|
|
2316
2529
|
}
|
|
2317
2530
|
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2318
2531
|
const coarseTuning = data[4];
|
|
@@ -2322,8 +2535,14 @@ class Midy {
|
|
|
2322
2535
|
const prev = this.masterCoarseTuning;
|
|
2323
2536
|
const next = (value - 64) * 100; // cent
|
|
2324
2537
|
this.masterCoarseTuning = next;
|
|
2325
|
-
|
|
2326
|
-
this.
|
|
2538
|
+
const detuneChange = next - prev;
|
|
2539
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2540
|
+
const channel = this.channels[i];
|
|
2541
|
+
if (channel.isDrum)
|
|
2542
|
+
continue;
|
|
2543
|
+
channel.detune += detuneChange;
|
|
2544
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2545
|
+
}
|
|
2327
2546
|
}
|
|
2328
2547
|
handleGlobalParameterControlSysEx(data, scheduleTime) {
|
|
2329
2548
|
if (data[7] === 1) {
|
|
@@ -2507,7 +2726,7 @@ class Midy {
|
|
|
2507
2726
|
return value * 0.00787;
|
|
2508
2727
|
}
|
|
2509
2728
|
getChannelBitmap(data) {
|
|
2510
|
-
const bitmap = new Array(
|
|
2729
|
+
const bitmap = new Array(this.channels.length).fill(false);
|
|
2511
2730
|
const ff = data[4] & 0b11;
|
|
2512
2731
|
const gg = data[5] & 0x7F;
|
|
2513
2732
|
const hh = data[6] & 0x7F;
|
|
@@ -2535,6 +2754,8 @@ class Midy {
|
|
|
2535
2754
|
if (!channelBitmap[i])
|
|
2536
2755
|
continue;
|
|
2537
2756
|
const channel = this.channels[i];
|
|
2757
|
+
if (channel.isDrum)
|
|
2758
|
+
continue;
|
|
2538
2759
|
for (let j = 0; j < 12; j++) {
|
|
2539
2760
|
const centValue = data[j + 7] - 64;
|
|
2540
2761
|
channel.scaleOctaveTuningTable[j] = centValue;
|
|
@@ -2553,6 +2774,8 @@ class Midy {
|
|
|
2553
2774
|
if (!channelBitmap[i])
|
|
2554
2775
|
continue;
|
|
2555
2776
|
const channel = this.channels[i];
|
|
2777
|
+
if (channel.isDrum)
|
|
2778
|
+
continue;
|
|
2556
2779
|
for (let j = 0; j < 12; j++) {
|
|
2557
2780
|
const index = 7 + j * 2;
|
|
2558
2781
|
const msb = data[index] & 0x7F;
|
|
@@ -2623,7 +2846,10 @@ class Midy {
|
|
|
2623
2846
|
}
|
|
2624
2847
|
handlePressureSysEx(data, tableName) {
|
|
2625
2848
|
const channelNumber = data[4];
|
|
2626
|
-
const
|
|
2849
|
+
const channel = this.channels[channelNumber];
|
|
2850
|
+
if (channel.isDrum)
|
|
2851
|
+
return;
|
|
2852
|
+
const table = channel[tableName];
|
|
2627
2853
|
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2628
2854
|
const pp = data[i];
|
|
2629
2855
|
const rr = data[i + 1];
|
|
@@ -2651,8 +2877,11 @@ class Midy {
|
|
|
2651
2877
|
}
|
|
2652
2878
|
handleControlChangeSysEx(data) {
|
|
2653
2879
|
const channelNumber = data[4];
|
|
2880
|
+
const channel = this.channels[channelNumber];
|
|
2881
|
+
if (channel.isDrum)
|
|
2882
|
+
return;
|
|
2654
2883
|
const controllerType = data[5];
|
|
2655
|
-
const table =
|
|
2884
|
+
const table = channel.controlTable[controllerType];
|
|
2656
2885
|
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2657
2886
|
const pp = data[i];
|
|
2658
2887
|
const rr = data[i + 1];
|
|
@@ -2666,8 +2895,11 @@ class Midy {
|
|
|
2666
2895
|
}
|
|
2667
2896
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2668
2897
|
const channelNumber = data[4];
|
|
2898
|
+
const channel = this.channels[channelNumber];
|
|
2899
|
+
if (channel.isDrum)
|
|
2900
|
+
return;
|
|
2669
2901
|
const keyNumber = data[5];
|
|
2670
|
-
const table =
|
|
2902
|
+
const table = channel.keyBasedInstrumentControlTable;
|
|
2671
2903
|
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2672
2904
|
const controllerType = data[i];
|
|
2673
2905
|
const value = data[i + 1];
|
|
@@ -2686,15 +2918,23 @@ class Midy {
|
|
|
2686
2918
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2687
2919
|
}
|
|
2688
2920
|
}
|
|
2921
|
+
// https://github.com/marmooo/js-timer-benchmark
|
|
2689
2922
|
scheduleTask(callback, scheduleTime) {
|
|
2690
2923
|
return new Promise((resolve) => {
|
|
2691
|
-
const bufferSource = new AudioBufferSourceNode(this.audioContext
|
|
2924
|
+
const bufferSource = new AudioBufferSourceNode(this.audioContext, {
|
|
2925
|
+
buffer: this.schedulerBuffer,
|
|
2926
|
+
});
|
|
2927
|
+
bufferSource.connect(this.scheduler);
|
|
2692
2928
|
bufferSource.onended = () => {
|
|
2693
|
-
|
|
2694
|
-
|
|
2929
|
+
try {
|
|
2930
|
+
callback();
|
|
2931
|
+
}
|
|
2932
|
+
finally {
|
|
2933
|
+
bufferSource.disconnect();
|
|
2934
|
+
resolve();
|
|
2935
|
+
}
|
|
2695
2936
|
};
|
|
2696
2937
|
bufferSource.start(scheduleTime);
|
|
2697
|
-
bufferSource.stop(scheduleTime);
|
|
2698
2938
|
});
|
|
2699
2939
|
}
|
|
2700
2940
|
}
|
|
@@ -2704,9 +2944,8 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2704
2944
|
configurable: true,
|
|
2705
2945
|
writable: true,
|
|
2706
2946
|
value: {
|
|
2707
|
-
currentBufferSource: null,
|
|
2708
2947
|
detune: 0,
|
|
2709
|
-
|
|
2948
|
+
programNumber: 0,
|
|
2710
2949
|
bank: 121 * 128,
|
|
2711
2950
|
bankMSB: 121,
|
|
2712
2951
|
bankLSB: 0,
|
|
@@ -2715,8 +2954,8 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2715
2954
|
rpnMSB: 127,
|
|
2716
2955
|
rpnLSB: 127,
|
|
2717
2956
|
mono: false, // CC#124, CC#125
|
|
2957
|
+
modulationDepthRange: 50, // cent
|
|
2718
2958
|
fineTuning: 0, // cb
|
|
2719
2959
|
coarseTuning: 0, // cb
|
|
2720
|
-
modulationDepthRange: 50, // cent
|
|
2721
2960
|
}
|
|
2722
2961
|
});
|