@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-GM2.js
CHANGED
|
@@ -154,6 +154,39 @@ class Note {
|
|
|
154
154
|
this.voiceParams = voiceParams;
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
+
const drumExclusiveClassesByKit = new Array(57);
|
|
158
|
+
const drumExclusiveClassCount = 10;
|
|
159
|
+
const standardSet = new Uint8Array(128);
|
|
160
|
+
standardSet[42] = 1;
|
|
161
|
+
standardSet[44] = 1;
|
|
162
|
+
standardSet[46] = 1; // HH
|
|
163
|
+
standardSet[71] = 2;
|
|
164
|
+
standardSet[72] = 2; // Whistle
|
|
165
|
+
standardSet[73] = 3;
|
|
166
|
+
standardSet[74] = 3; // Guiro
|
|
167
|
+
standardSet[78] = 4;
|
|
168
|
+
standardSet[79] = 4; // Cuica
|
|
169
|
+
standardSet[80] = 5;
|
|
170
|
+
standardSet[81] = 5; // Triangle
|
|
171
|
+
standardSet[29] = 6;
|
|
172
|
+
standardSet[30] = 6; // Scratch
|
|
173
|
+
standardSet[86] = 7;
|
|
174
|
+
standardSet[87] = 7; // Surdo
|
|
175
|
+
drumExclusiveClassesByKit[0] = standardSet;
|
|
176
|
+
const analogSet = new Uint8Array(128);
|
|
177
|
+
analogSet[42] = 8;
|
|
178
|
+
analogSet[44] = 8;
|
|
179
|
+
analogSet[46] = 8; // CHH
|
|
180
|
+
drumExclusiveClassesByKit[25] = analogSet;
|
|
181
|
+
const orchestraSet = new Uint8Array(128);
|
|
182
|
+
orchestraSet[27] = 9;
|
|
183
|
+
orchestraSet[28] = 9;
|
|
184
|
+
orchestraSet[29] = 9; // HH
|
|
185
|
+
drumExclusiveClassesByKit[48] = orchestraSet;
|
|
186
|
+
const sfxSet = new Uint8Array(128);
|
|
187
|
+
sfxSet[41] = 10;
|
|
188
|
+
sfxSet[42] = 10; // Scratch
|
|
189
|
+
drumExclusiveClassesByKit[56] = sfxSet;
|
|
157
190
|
// normalized to 0-1 for use with the SF2 modulator model
|
|
158
191
|
const defaultControllerState = {
|
|
159
192
|
noteOnVelocity: { type: 2, defaultValue: 0 },
|
|
@@ -242,17 +275,11 @@ const volumeEnvelopeKeys = [
|
|
|
242
275
|
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
243
276
|
class MidyGM2 {
|
|
244
277
|
constructor(audioContext, options = this.defaultOptions) {
|
|
245
|
-
Object.defineProperty(this, "
|
|
246
|
-
enumerable: true,
|
|
247
|
-
configurable: true,
|
|
248
|
-
writable: true,
|
|
249
|
-
value: 120
|
|
250
|
-
});
|
|
251
|
-
Object.defineProperty(this, "totalTime", {
|
|
278
|
+
Object.defineProperty(this, "mode", {
|
|
252
279
|
enumerable: true,
|
|
253
280
|
configurable: true,
|
|
254
281
|
writable: true,
|
|
255
|
-
value:
|
|
282
|
+
value: "GM2"
|
|
256
283
|
});
|
|
257
284
|
Object.defineProperty(this, "masterFineTuning", {
|
|
258
285
|
enumerable: true,
|
|
@@ -287,6 +314,24 @@ class MidyGM2 {
|
|
|
287
314
|
delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
|
|
288
315
|
}
|
|
289
316
|
});
|
|
317
|
+
Object.defineProperty(this, "numChannels", {
|
|
318
|
+
enumerable: true,
|
|
319
|
+
configurable: true,
|
|
320
|
+
writable: true,
|
|
321
|
+
value: 16
|
|
322
|
+
});
|
|
323
|
+
Object.defineProperty(this, "ticksPerBeat", {
|
|
324
|
+
enumerable: true,
|
|
325
|
+
configurable: true,
|
|
326
|
+
writable: true,
|
|
327
|
+
value: 120
|
|
328
|
+
});
|
|
329
|
+
Object.defineProperty(this, "totalTime", {
|
|
330
|
+
enumerable: true,
|
|
331
|
+
configurable: true,
|
|
332
|
+
writable: true,
|
|
333
|
+
value: 0
|
|
334
|
+
});
|
|
290
335
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
291
336
|
enumerable: true,
|
|
292
337
|
configurable: true,
|
|
@@ -389,11 +434,17 @@ class MidyGM2 {
|
|
|
389
434
|
writable: true,
|
|
390
435
|
value: []
|
|
391
436
|
});
|
|
392
|
-
Object.defineProperty(this, "
|
|
437
|
+
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
393
438
|
enumerable: true,
|
|
394
439
|
configurable: true,
|
|
395
440
|
writable: true,
|
|
396
|
-
value: new
|
|
441
|
+
value: new Array(128)
|
|
442
|
+
});
|
|
443
|
+
Object.defineProperty(this, "drumExclusiveClassNotes", {
|
|
444
|
+
enumerable: true,
|
|
445
|
+
configurable: true,
|
|
446
|
+
writable: true,
|
|
447
|
+
value: new Array(this.numChannels * drumExclusiveClassCount)
|
|
397
448
|
});
|
|
398
449
|
Object.defineProperty(this, "defaultOptions", {
|
|
399
450
|
enumerable: true,
|
|
@@ -420,6 +471,11 @@ class MidyGM2 {
|
|
|
420
471
|
this.audioContext = audioContext;
|
|
421
472
|
this.options = { ...this.defaultOptions, ...options };
|
|
422
473
|
this.masterVolume = new GainNode(audioContext);
|
|
474
|
+
this.scheduler = new GainNode(audioContext, { gain: 0 });
|
|
475
|
+
this.schedulerBuffer = new AudioBuffer({
|
|
476
|
+
length: 1,
|
|
477
|
+
sampleRate: audioContext.sampleRate,
|
|
478
|
+
});
|
|
423
479
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
424
480
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
425
481
|
this.channels = this.createChannels(audioContext);
|
|
@@ -428,6 +484,7 @@ class MidyGM2 {
|
|
|
428
484
|
this.chorusEffect.output.connect(this.masterVolume);
|
|
429
485
|
this.reverbEffect.output.connect(this.masterVolume);
|
|
430
486
|
this.masterVolume.connect(audioContext.destination);
|
|
487
|
+
this.scheduler.connect(audioContext.destination);
|
|
431
488
|
this.GM2SystemOn();
|
|
432
489
|
}
|
|
433
490
|
initSoundFontTable() {
|
|
@@ -481,8 +538,10 @@ class MidyGM2 {
|
|
|
481
538
|
};
|
|
482
539
|
}
|
|
483
540
|
createChannels(audioContext) {
|
|
484
|
-
const channels = Array.from({ length:
|
|
541
|
+
const channels = Array.from({ length: this.numChannels }, () => {
|
|
485
542
|
return {
|
|
543
|
+
currentBufferSource: null,
|
|
544
|
+
isDrum: false,
|
|
486
545
|
...this.constructor.channelSettings,
|
|
487
546
|
state: new ControllerState(),
|
|
488
547
|
controlTable: this.initControlTable(),
|
|
@@ -526,7 +585,7 @@ class MidyGM2 {
|
|
|
526
585
|
return audioBuffer;
|
|
527
586
|
}
|
|
528
587
|
}
|
|
529
|
-
|
|
588
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
530
589
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
531
590
|
bufferSource.buffer = audioBuffer;
|
|
532
591
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
@@ -617,7 +676,8 @@ class MidyGM2 {
|
|
|
617
676
|
if (queueIndex >= this.timeline.length) {
|
|
618
677
|
await Promise.all(this.notePromises);
|
|
619
678
|
this.notePromises = [];
|
|
620
|
-
this.
|
|
679
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
680
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
621
681
|
this.audioBufferCache.clear();
|
|
622
682
|
resolve();
|
|
623
683
|
return;
|
|
@@ -636,7 +696,8 @@ class MidyGM2 {
|
|
|
636
696
|
else if (this.isStopping) {
|
|
637
697
|
await this.stopNotes(0, true, now);
|
|
638
698
|
this.notePromises = [];
|
|
639
|
-
this.
|
|
699
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
700
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
640
701
|
this.audioBufferCache.clear();
|
|
641
702
|
resolve();
|
|
642
703
|
this.isStopping = false;
|
|
@@ -645,7 +706,8 @@ class MidyGM2 {
|
|
|
645
706
|
}
|
|
646
707
|
else if (this.isSeeking) {
|
|
647
708
|
this.stopNotes(0, true, now);
|
|
648
|
-
this.
|
|
709
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
710
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
649
711
|
this.startTime = this.audioContext.currentTime;
|
|
650
712
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
651
713
|
offset = this.resumeTime - this.startTime;
|
|
@@ -673,7 +735,7 @@ class MidyGM2 {
|
|
|
673
735
|
extractMidiData(midi) {
|
|
674
736
|
const instruments = new Set();
|
|
675
737
|
const timeline = [];
|
|
676
|
-
const tmpChannels = new Array(
|
|
738
|
+
const tmpChannels = new Array(this.channels.length);
|
|
677
739
|
for (let i = 0; i < tmpChannels.length; i++) {
|
|
678
740
|
tmpChannels[i] = {
|
|
679
741
|
programNumber: -1,
|
|
@@ -801,6 +863,9 @@ class MidyGM2 {
|
|
|
801
863
|
if (!this.isPlaying)
|
|
802
864
|
return;
|
|
803
865
|
this.isStopping = true;
|
|
866
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
867
|
+
this.resetAllStates(i);
|
|
868
|
+
}
|
|
804
869
|
}
|
|
805
870
|
pause() {
|
|
806
871
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -1006,7 +1071,9 @@ class MidyGM2 {
|
|
|
1006
1071
|
return 8.176 * this.centToRate(cent);
|
|
1007
1072
|
}
|
|
1008
1073
|
calcChannelDetune(channel) {
|
|
1009
|
-
const masterTuning =
|
|
1074
|
+
const masterTuning = channel.isDrum
|
|
1075
|
+
? 0
|
|
1076
|
+
: this.masterCoarseTuning + this.masterFineTuning;
|
|
1010
1077
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
1011
1078
|
const tuning = masterTuning + channelTuning;
|
|
1012
1079
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
@@ -1032,9 +1099,8 @@ class MidyGM2 {
|
|
|
1032
1099
|
.setValueAtTime(detune, scheduleTime);
|
|
1033
1100
|
}
|
|
1034
1101
|
getPortamentoTime(channel) {
|
|
1035
|
-
const factor = 5 * Math.log(10)
|
|
1036
|
-
|
|
1037
|
-
return Math.log(time) / factor;
|
|
1102
|
+
const factor = 5 * Math.log(10) * 127;
|
|
1103
|
+
return channel.state.portamentoTime * factor;
|
|
1038
1104
|
}
|
|
1039
1105
|
setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
|
|
1040
1106
|
const { voiceParams, startTime } = note;
|
|
@@ -1170,8 +1236,8 @@ class MidyGM2 {
|
|
|
1170
1236
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1171
1237
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1172
1238
|
}
|
|
1173
|
-
async getAudioBuffer(
|
|
1174
|
-
const audioBufferId = this.getAudioBufferId(
|
|
1239
|
+
async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
|
|
1240
|
+
const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
|
|
1175
1241
|
const cache = this.audioBufferCache.get(audioBufferId);
|
|
1176
1242
|
if (cache) {
|
|
1177
1243
|
cache.counter += 1;
|
|
@@ -1194,8 +1260,8 @@ class MidyGM2 {
|
|
|
1194
1260
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1195
1261
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1196
1262
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1197
|
-
const audioBuffer = await this.getAudioBuffer(channel.
|
|
1198
|
-
note.bufferSource = this.
|
|
1263
|
+
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1264
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
1199
1265
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1200
1266
|
note.gainL = new GainNode(this.audioContext);
|
|
1201
1267
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1239,23 +1305,72 @@ class MidyGM2 {
|
|
|
1239
1305
|
note.bufferSource.start(startTime);
|
|
1240
1306
|
return note;
|
|
1241
1307
|
}
|
|
1242
|
-
calcBank(channel
|
|
1243
|
-
|
|
1244
|
-
|
|
1308
|
+
calcBank(channel) {
|
|
1309
|
+
switch (this.mode) {
|
|
1310
|
+
case "GM1":
|
|
1311
|
+
if (channel.isDrum)
|
|
1312
|
+
return 128;
|
|
1313
|
+
return 0;
|
|
1314
|
+
case "GM2":
|
|
1315
|
+
if (channel.bankMSB === 121)
|
|
1316
|
+
return 0;
|
|
1317
|
+
if (channel.isDrum)
|
|
1318
|
+
return 128;
|
|
1319
|
+
return channel.bank;
|
|
1320
|
+
default:
|
|
1321
|
+
return channel.bank;
|
|
1245
1322
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1323
|
+
}
|
|
1324
|
+
handleExclusiveClass(note, channelNumber, startTime) {
|
|
1325
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1326
|
+
if (exclusiveClass === 0)
|
|
1327
|
+
return;
|
|
1328
|
+
const prev = this.exclusiveClassNotes[exclusiveClass];
|
|
1329
|
+
if (prev) {
|
|
1330
|
+
const [prevNote, prevChannelNumber] = prev;
|
|
1331
|
+
if (prevNote && !prevNote.ending) {
|
|
1332
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1333
|
+
startTime, true, // force
|
|
1334
|
+
undefined);
|
|
1335
|
+
}
|
|
1248
1336
|
}
|
|
1249
|
-
|
|
1337
|
+
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
1338
|
+
}
|
|
1339
|
+
handleDrumExclusiveClass(note, channelNumber, startTime) {
|
|
1340
|
+
const channel = this.channels[channelNumber];
|
|
1341
|
+
if (!channel.isDrum)
|
|
1342
|
+
return;
|
|
1343
|
+
const kitTable = drumExclusiveClassesByKit[channel.programNumber];
|
|
1344
|
+
if (!kitTable)
|
|
1345
|
+
return;
|
|
1346
|
+
const drumExclusiveClass = kitTable[note.noteNumber];
|
|
1347
|
+
if (drumExclusiveClass === 0)
|
|
1348
|
+
return;
|
|
1349
|
+
const index = (drumExclusiveClass - 1) * this.channels.length +
|
|
1350
|
+
channelNumber;
|
|
1351
|
+
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1352
|
+
if (prevNote && !prevNote.ending) {
|
|
1353
|
+
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1354
|
+
startTime, true, // force
|
|
1355
|
+
undefined);
|
|
1356
|
+
}
|
|
1357
|
+
this.drumExclusiveClassNotes[index] = note;
|
|
1358
|
+
}
|
|
1359
|
+
isDrumNoteOffException(channel, noteNumber) {
|
|
1360
|
+
if (!channel.isDrum)
|
|
1361
|
+
return false;
|
|
1362
|
+
const programNumber = channel.programNumber;
|
|
1363
|
+
return (programNumber === 48 && noteNumber === 88) ||
|
|
1364
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84);
|
|
1250
1365
|
}
|
|
1251
1366
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
|
|
1252
1367
|
const channel = this.channels[channelNumber];
|
|
1253
1368
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1254
|
-
const soundFontIndex = this.soundFontTable[channel.
|
|
1369
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
1255
1370
|
if (soundFontIndex === undefined)
|
|
1256
1371
|
return;
|
|
1257
1372
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1258
|
-
const voice = soundFont.getVoice(bankNumber, channel.
|
|
1373
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1259
1374
|
if (!voice)
|
|
1260
1375
|
return;
|
|
1261
1376
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
@@ -1265,31 +1380,58 @@ class MidyGM2 {
|
|
|
1265
1380
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1266
1381
|
channel.sustainNotes.push(note);
|
|
1267
1382
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1271
|
-
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1272
|
-
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1273
|
-
if (!prevNote.ending) {
|
|
1274
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1275
|
-
startTime, true, // force
|
|
1276
|
-
undefined);
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
1280
|
-
}
|
|
1383
|
+
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1384
|
+
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1281
1385
|
const scheduledNotes = channel.scheduledNotes;
|
|
1282
|
-
|
|
1283
|
-
|
|
1386
|
+
let notes = scheduledNotes.get(noteNumber);
|
|
1387
|
+
if (notes) {
|
|
1388
|
+
notes.push(note);
|
|
1284
1389
|
}
|
|
1285
1390
|
else {
|
|
1286
|
-
|
|
1391
|
+
notes = [note];
|
|
1392
|
+
scheduledNotes.set(noteNumber, notes);
|
|
1393
|
+
}
|
|
1394
|
+
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1395
|
+
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1396
|
+
const index = notes.length - 1;
|
|
1397
|
+
const promise = new Promise((resolve) => {
|
|
1398
|
+
note.bufferSource.onended = () => {
|
|
1399
|
+
this.disconnectNote(note, scheduledNotes, index);
|
|
1400
|
+
resolve();
|
|
1401
|
+
};
|
|
1402
|
+
note.bufferSource.stop(stopTime);
|
|
1403
|
+
});
|
|
1404
|
+
this.notePromises.push(promise);
|
|
1287
1405
|
}
|
|
1288
1406
|
}
|
|
1289
1407
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1290
1408
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1291
1409
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1292
1410
|
}
|
|
1411
|
+
disconnectNote(note, scheduledNotes, index) {
|
|
1412
|
+
scheduledNotes[index] = null;
|
|
1413
|
+
note.bufferSource.disconnect();
|
|
1414
|
+
note.filterNode.disconnect();
|
|
1415
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1416
|
+
note.volumeNode.disconnect();
|
|
1417
|
+
note.gainL.disconnect();
|
|
1418
|
+
note.gainR.disconnect();
|
|
1419
|
+
if (note.modulationDepth) {
|
|
1420
|
+
note.volumeDepth.disconnect();
|
|
1421
|
+
note.modulationDepth.disconnect();
|
|
1422
|
+
note.modulationLFO.stop();
|
|
1423
|
+
}
|
|
1424
|
+
if (note.vibratoDepth) {
|
|
1425
|
+
note.vibratoDepth.disconnect();
|
|
1426
|
+
note.vibratoLFO.stop();
|
|
1427
|
+
}
|
|
1428
|
+
if (note.reverbEffectsSend) {
|
|
1429
|
+
note.reverbEffectsSend.disconnect();
|
|
1430
|
+
}
|
|
1431
|
+
if (note.chorusEffectsSend) {
|
|
1432
|
+
note.chorusEffectsSend.disconnect();
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1293
1435
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1294
1436
|
const note = scheduledNotes[index];
|
|
1295
1437
|
note.volumeEnvelopeNode.gain
|
|
@@ -1301,28 +1443,7 @@ class MidyGM2 {
|
|
|
1301
1443
|
}, stopTime);
|
|
1302
1444
|
return new Promise((resolve) => {
|
|
1303
1445
|
note.bufferSource.onended = () => {
|
|
1304
|
-
scheduledNotes
|
|
1305
|
-
note.bufferSource.disconnect();
|
|
1306
|
-
note.filterNode.disconnect();
|
|
1307
|
-
note.volumeEnvelopeNode.disconnect();
|
|
1308
|
-
note.volumeNode.disconnect();
|
|
1309
|
-
note.gainL.disconnect();
|
|
1310
|
-
note.gainR.disconnect();
|
|
1311
|
-
if (note.modulationDepth) {
|
|
1312
|
-
note.volumeDepth.disconnect();
|
|
1313
|
-
note.modulationDepth.disconnect();
|
|
1314
|
-
note.modulationLFO.stop();
|
|
1315
|
-
}
|
|
1316
|
-
if (note.vibratoDepth) {
|
|
1317
|
-
note.vibratoDepth.disconnect();
|
|
1318
|
-
note.vibratoLFO.stop();
|
|
1319
|
-
}
|
|
1320
|
-
if (note.reverbEffectsSend) {
|
|
1321
|
-
note.reverbEffectsSend.disconnect();
|
|
1322
|
-
}
|
|
1323
|
-
if (note.chorusEffectsSend) {
|
|
1324
|
-
note.chorusEffectsSend.disconnect();
|
|
1325
|
-
}
|
|
1446
|
+
this.disconnectNote(note, scheduledNotes, index);
|
|
1326
1447
|
resolve();
|
|
1327
1448
|
};
|
|
1328
1449
|
note.bufferSource.stop(stopTime);
|
|
@@ -1330,6 +1451,8 @@ class MidyGM2 {
|
|
|
1330
1451
|
}
|
|
1331
1452
|
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1332
1453
|
const channel = this.channels[channelNumber];
|
|
1454
|
+
if (this.isDrumNoteOffException(channel, noteNumber))
|
|
1455
|
+
return;
|
|
1333
1456
|
const state = channel.state;
|
|
1334
1457
|
if (!force) {
|
|
1335
1458
|
if (0.5 <= state.sustainPedal)
|
|
@@ -1415,13 +1538,25 @@ class MidyGM2 {
|
|
|
1415
1538
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1416
1539
|
}
|
|
1417
1540
|
}
|
|
1418
|
-
handleProgramChange(channelNumber,
|
|
1541
|
+
handleProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1419
1542
|
const channel = this.channels[channelNumber];
|
|
1420
1543
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1421
|
-
channel.
|
|
1544
|
+
channel.programNumber = programNumber;
|
|
1545
|
+
if (this.mode === "GM2") {
|
|
1546
|
+
switch (channel.bankMSB) {
|
|
1547
|
+
case 120:
|
|
1548
|
+
channel.isDrum = true;
|
|
1549
|
+
break;
|
|
1550
|
+
case 121:
|
|
1551
|
+
channel.isDrum = false;
|
|
1552
|
+
break;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1422
1555
|
}
|
|
1423
1556
|
handleChannelPressure(channelNumber, value, scheduleTime) {
|
|
1424
1557
|
const channel = this.channels[channelNumber];
|
|
1558
|
+
if (channel.isDrum)
|
|
1559
|
+
return;
|
|
1425
1560
|
const prev = channel.state.channelPressure;
|
|
1426
1561
|
const next = value / 127;
|
|
1427
1562
|
channel.state.channelPressure = next;
|
|
@@ -1440,8 +1575,10 @@ class MidyGM2 {
|
|
|
1440
1575
|
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
1441
1576
|
}
|
|
1442
1577
|
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1443
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1444
1578
|
const channel = this.channels[channelNumber];
|
|
1579
|
+
if (channel.isDrum)
|
|
1580
|
+
return;
|
|
1581
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1445
1582
|
const state = channel.state;
|
|
1446
1583
|
const prev = state.pitchWheel * 2 - 1;
|
|
1447
1584
|
const next = (value - 8192) / 8192;
|
|
@@ -1713,15 +1850,16 @@ class MidyGM2 {
|
|
|
1713
1850
|
});
|
|
1714
1851
|
}
|
|
1715
1852
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1716
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1717
1853
|
const channel = this.channels[channelNumber];
|
|
1854
|
+
if (channel.isDrum)
|
|
1855
|
+
return;
|
|
1856
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1718
1857
|
channel.state.modulationDepth = modulation / 127;
|
|
1719
1858
|
this.updateModulation(channel, scheduleTime);
|
|
1720
1859
|
}
|
|
1721
1860
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1722
1861
|
const channel = this.channels[channelNumber];
|
|
1723
|
-
|
|
1724
|
-
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1862
|
+
channel.state.portamentoTime = portamentoTime / 127;
|
|
1725
1863
|
}
|
|
1726
1864
|
setKeyBasedVolume(channel, scheduleTime) {
|
|
1727
1865
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1793,8 +1931,10 @@ class MidyGM2 {
|
|
|
1793
1931
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1794
1932
|
}
|
|
1795
1933
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1796
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1797
1934
|
const channel = this.channels[channelNumber];
|
|
1935
|
+
if (channel.isDrum)
|
|
1936
|
+
return;
|
|
1937
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1798
1938
|
channel.state.sustainPedal = value / 127;
|
|
1799
1939
|
if (64 <= value) {
|
|
1800
1940
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1806,11 +1946,16 @@ class MidyGM2 {
|
|
|
1806
1946
|
}
|
|
1807
1947
|
}
|
|
1808
1948
|
setPortamento(channelNumber, value) {
|
|
1809
|
-
this.channels[channelNumber]
|
|
1949
|
+
const channel = this.channels[channelNumber];
|
|
1950
|
+
if (channel.isDrum)
|
|
1951
|
+
return;
|
|
1952
|
+
channel.state.portamento = value / 127;
|
|
1810
1953
|
}
|
|
1811
1954
|
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1812
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1813
1955
|
const channel = this.channels[channelNumber];
|
|
1956
|
+
if (channel.isDrum)
|
|
1957
|
+
return;
|
|
1958
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1814
1959
|
channel.state.sostenutoPedal = value / 127;
|
|
1815
1960
|
if (64 <= value) {
|
|
1816
1961
|
channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
|
|
@@ -1819,9 +1964,22 @@ class MidyGM2 {
|
|
|
1819
1964
|
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
1820
1965
|
}
|
|
1821
1966
|
}
|
|
1822
|
-
setSoftPedal(channelNumber, softPedal,
|
|
1967
|
+
setSoftPedal(channelNumber, softPedal, scheduleTime) {
|
|
1823
1968
|
const channel = this.channels[channelNumber];
|
|
1969
|
+
if (channel.isDrum)
|
|
1970
|
+
return;
|
|
1971
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1824
1972
|
channel.state.softPedal = softPedal / 127;
|
|
1973
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1974
|
+
if (note.portamento) {
|
|
1975
|
+
this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
|
|
1976
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1977
|
+
}
|
|
1978
|
+
else {
|
|
1979
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1980
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1981
|
+
}
|
|
1982
|
+
});
|
|
1825
1983
|
}
|
|
1826
1984
|
setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
|
|
1827
1985
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1950,8 +2108,10 @@ class MidyGM2 {
|
|
|
1950
2108
|
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1951
2109
|
}
|
|
1952
2110
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1953
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1954
2111
|
const channel = this.channels[channelNumber];
|
|
2112
|
+
if (channel.isDrum)
|
|
2113
|
+
return;
|
|
2114
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1955
2115
|
const state = channel.state;
|
|
1956
2116
|
const prev = state.pitchWheelSensitivity;
|
|
1957
2117
|
const next = value / 128;
|
|
@@ -1967,8 +2127,10 @@ class MidyGM2 {
|
|
|
1967
2127
|
this.setFineTuning(channelNumber, fineTuning, scheduleTime);
|
|
1968
2128
|
}
|
|
1969
2129
|
setFineTuning(channelNumber, value, scheduleTime) {
|
|
1970
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1971
2130
|
const channel = this.channels[channelNumber];
|
|
2131
|
+
if (channel.isDrum)
|
|
2132
|
+
return;
|
|
2133
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1972
2134
|
const prev = channel.fineTuning;
|
|
1973
2135
|
const next = (value - 8192) / 8.192; // cent
|
|
1974
2136
|
channel.fineTuning = next;
|
|
@@ -1982,8 +2144,10 @@ class MidyGM2 {
|
|
|
1982
2144
|
this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
|
|
1983
2145
|
}
|
|
1984
2146
|
setCoarseTuning(channelNumber, value, scheduleTime) {
|
|
1985
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1986
2147
|
const channel = this.channels[channelNumber];
|
|
2148
|
+
if (channel.isDrum)
|
|
2149
|
+
return;
|
|
2150
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1987
2151
|
const prev = channel.coarseTuning;
|
|
1988
2152
|
const next = (value - 64) * 100; // cent
|
|
1989
2153
|
channel.coarseTuning = next;
|
|
@@ -1997,8 +2161,10 @@ class MidyGM2 {
|
|
|
1997
2161
|
this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
|
|
1998
2162
|
}
|
|
1999
2163
|
setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
|
|
2000
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
2001
2164
|
const channel = this.channels[channelNumber];
|
|
2165
|
+
if (channel.isDrum)
|
|
2166
|
+
return;
|
|
2167
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2002
2168
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2003
2169
|
this.updateModulation(channel, scheduleTime);
|
|
2004
2170
|
}
|
|
@@ -2006,16 +2172,30 @@ class MidyGM2 {
|
|
|
2006
2172
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2007
2173
|
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
2008
2174
|
}
|
|
2175
|
+
resetAllStates(channelNumber) {
|
|
2176
|
+
const channel = this.channels[channelNumber];
|
|
2177
|
+
const state = channel.state;
|
|
2178
|
+
for (const type of Object.keys(defaultControllerState)) {
|
|
2179
|
+
state[type] = defaultControllerState[type].defaultValue;
|
|
2180
|
+
}
|
|
2181
|
+
for (const type of Object.keys(this.constructor.channelSettings)) {
|
|
2182
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
2183
|
+
}
|
|
2184
|
+
this.mode = "GM2";
|
|
2185
|
+
this.masterFineTuning = 0; // cb
|
|
2186
|
+
this.masterCoarseTuning = 0; // cb
|
|
2187
|
+
}
|
|
2188
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2009
2189
|
resetAllControllers(channelNumber) {
|
|
2010
2190
|
const stateTypes = [
|
|
2191
|
+
"channelPressure",
|
|
2192
|
+
"pitchWheel",
|
|
2011
2193
|
"expression",
|
|
2012
2194
|
"modulationDepth",
|
|
2013
2195
|
"sustainPedal",
|
|
2014
2196
|
"portamento",
|
|
2015
2197
|
"sostenutoPedal",
|
|
2016
2198
|
"softPedal",
|
|
2017
|
-
"channelPressure",
|
|
2018
|
-
"pitchWheelSensitivity",
|
|
2019
2199
|
];
|
|
2020
2200
|
const channel = this.channels[channelNumber];
|
|
2021
2201
|
const state = channel.state;
|
|
@@ -2066,12 +2246,12 @@ class MidyGM2 {
|
|
|
2066
2246
|
case 9:
|
|
2067
2247
|
switch (data[3]) {
|
|
2068
2248
|
case 1:
|
|
2069
|
-
this.GM1SystemOn();
|
|
2249
|
+
this.GM1SystemOn(scheduleTime);
|
|
2070
2250
|
break;
|
|
2071
2251
|
case 2: // GM System Off
|
|
2072
2252
|
break;
|
|
2073
2253
|
case 3:
|
|
2074
|
-
this.GM2SystemOn();
|
|
2254
|
+
this.GM2SystemOn(scheduleTime);
|
|
2075
2255
|
break;
|
|
2076
2256
|
default:
|
|
2077
2257
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -2081,25 +2261,35 @@ class MidyGM2 {
|
|
|
2081
2261
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2082
2262
|
}
|
|
2083
2263
|
}
|
|
2084
|
-
GM1SystemOn() {
|
|
2264
|
+
GM1SystemOn(scheduleTime) {
|
|
2265
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2266
|
+
this.mode = "GM1";
|
|
2085
2267
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2268
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2086
2269
|
const channel = this.channels[i];
|
|
2087
2270
|
channel.bankMSB = 0;
|
|
2088
2271
|
channel.bankLSB = 0;
|
|
2089
2272
|
channel.bank = 0;
|
|
2273
|
+
channel.isDrum = false;
|
|
2090
2274
|
}
|
|
2091
2275
|
this.channels[9].bankMSB = 1;
|
|
2092
2276
|
this.channels[9].bank = 128;
|
|
2277
|
+
this.channels[9].isDrum = true;
|
|
2093
2278
|
}
|
|
2094
|
-
GM2SystemOn() {
|
|
2279
|
+
GM2SystemOn(scheduleTime) {
|
|
2280
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2281
|
+
this.mode = "GM2";
|
|
2095
2282
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2283
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2096
2284
|
const channel = this.channels[i];
|
|
2097
2285
|
channel.bankMSB = 121;
|
|
2098
2286
|
channel.bankLSB = 0;
|
|
2099
2287
|
channel.bank = 121 * 128;
|
|
2288
|
+
channel.isDrum = false;
|
|
2100
2289
|
}
|
|
2101
2290
|
this.channels[9].bankMSB = 120;
|
|
2102
2291
|
this.channels[9].bank = 120 * 128;
|
|
2292
|
+
this.channels[9].isDrum = true;
|
|
2103
2293
|
}
|
|
2104
2294
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2105
2295
|
switch (data[2]) {
|
|
@@ -2162,8 +2352,14 @@ class MidyGM2 {
|
|
|
2162
2352
|
const prev = this.masterFineTuning;
|
|
2163
2353
|
const next = (value - 8192) / 8.192; // cent
|
|
2164
2354
|
this.masterFineTuning = next;
|
|
2165
|
-
|
|
2166
|
-
this.
|
|
2355
|
+
const detuneChange = next - prev;
|
|
2356
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2357
|
+
const channel = this.channels[i];
|
|
2358
|
+
if (channel.isDrum)
|
|
2359
|
+
continue;
|
|
2360
|
+
channel.detune += detuneChange;
|
|
2361
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2362
|
+
}
|
|
2167
2363
|
}
|
|
2168
2364
|
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2169
2365
|
const coarseTuning = data[4];
|
|
@@ -2173,8 +2369,14 @@ class MidyGM2 {
|
|
|
2173
2369
|
const prev = this.masterCoarseTuning;
|
|
2174
2370
|
const next = (value - 64) * 100; // cent
|
|
2175
2371
|
this.masterCoarseTuning = next;
|
|
2176
|
-
|
|
2177
|
-
this.
|
|
2372
|
+
const detuneChange = next - prev;
|
|
2373
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2374
|
+
const channel = this.channels[i];
|
|
2375
|
+
if (channel.isDrum)
|
|
2376
|
+
continue;
|
|
2377
|
+
channel.detune += detuneChange;
|
|
2378
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2379
|
+
}
|
|
2178
2380
|
}
|
|
2179
2381
|
handleGlobalParameterControlSysEx(data, scheduleTime) {
|
|
2180
2382
|
if (data[7] === 1) {
|
|
@@ -2358,7 +2560,7 @@ class MidyGM2 {
|
|
|
2358
2560
|
return value * 0.00787;
|
|
2359
2561
|
}
|
|
2360
2562
|
getChannelBitmap(data) {
|
|
2361
|
-
const bitmap = new Array(
|
|
2563
|
+
const bitmap = new Array(this.channels.length).fill(false);
|
|
2362
2564
|
const ff = data[4] & 0b11;
|
|
2363
2565
|
const gg = data[5] & 0x7F;
|
|
2364
2566
|
const hh = data[6] & 0x7F;
|
|
@@ -2500,15 +2702,23 @@ class MidyGM2 {
|
|
|
2500
2702
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2501
2703
|
}
|
|
2502
2704
|
}
|
|
2705
|
+
// https://github.com/marmooo/js-timer-benchmark
|
|
2503
2706
|
scheduleTask(callback, scheduleTime) {
|
|
2504
2707
|
return new Promise((resolve) => {
|
|
2505
|
-
const bufferSource = new AudioBufferSourceNode(this.audioContext
|
|
2708
|
+
const bufferSource = new AudioBufferSourceNode(this.audioContext, {
|
|
2709
|
+
buffer: this.schedulerBuffer,
|
|
2710
|
+
});
|
|
2711
|
+
bufferSource.connect(this.scheduler);
|
|
2506
2712
|
bufferSource.onended = () => {
|
|
2507
|
-
|
|
2508
|
-
|
|
2713
|
+
try {
|
|
2714
|
+
callback();
|
|
2715
|
+
}
|
|
2716
|
+
finally {
|
|
2717
|
+
bufferSource.disconnect();
|
|
2718
|
+
resolve();
|
|
2719
|
+
}
|
|
2509
2720
|
};
|
|
2510
2721
|
bufferSource.start(scheduleTime);
|
|
2511
|
-
bufferSource.stop(scheduleTime);
|
|
2512
2722
|
});
|
|
2513
2723
|
}
|
|
2514
2724
|
}
|
|
@@ -2518,9 +2728,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2518
2728
|
configurable: true,
|
|
2519
2729
|
writable: true,
|
|
2520
2730
|
value: {
|
|
2521
|
-
currentBufferSource: null,
|
|
2522
2731
|
detune: 0,
|
|
2523
|
-
|
|
2732
|
+
programNumber: 0,
|
|
2524
2733
|
bank: 121 * 128,
|
|
2525
2734
|
bankMSB: 121,
|
|
2526
2735
|
bankLSB: 0,
|
|
@@ -2529,8 +2738,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2529
2738
|
rpnMSB: 127,
|
|
2530
2739
|
rpnLSB: 127,
|
|
2531
2740
|
mono: false, // CC#124, CC#125
|
|
2741
|
+
modulationDepthRange: 50, // cent
|
|
2532
2742
|
fineTuning: 0, // cb
|
|
2533
2743
|
coarseTuning: 0, // cb
|
|
2534
|
-
modulationDepthRange: 50, // cent
|
|
2535
2744
|
}
|
|
2536
2745
|
});
|