@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-GM2.js
CHANGED
|
@@ -151,6 +151,39 @@ class Note {
|
|
|
151
151
|
this.voiceParams = voiceParams;
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
+
const drumExclusiveClassesByKit = new Array(57);
|
|
155
|
+
const drumExclusiveClassCount = 10;
|
|
156
|
+
const standardSet = new Uint8Array(128);
|
|
157
|
+
standardSet[42] = 1;
|
|
158
|
+
standardSet[44] = 1;
|
|
159
|
+
standardSet[46] = 1; // HH
|
|
160
|
+
standardSet[71] = 2;
|
|
161
|
+
standardSet[72] = 2; // Whistle
|
|
162
|
+
standardSet[73] = 3;
|
|
163
|
+
standardSet[74] = 3; // Guiro
|
|
164
|
+
standardSet[78] = 4;
|
|
165
|
+
standardSet[79] = 4; // Cuica
|
|
166
|
+
standardSet[80] = 5;
|
|
167
|
+
standardSet[81] = 5; // Triangle
|
|
168
|
+
standardSet[29] = 6;
|
|
169
|
+
standardSet[30] = 6; // Scratch
|
|
170
|
+
standardSet[86] = 7;
|
|
171
|
+
standardSet[87] = 7; // Surdo
|
|
172
|
+
drumExclusiveClassesByKit[0] = standardSet;
|
|
173
|
+
const analogSet = new Uint8Array(128);
|
|
174
|
+
analogSet[42] = 8;
|
|
175
|
+
analogSet[44] = 8;
|
|
176
|
+
analogSet[46] = 8; // CHH
|
|
177
|
+
drumExclusiveClassesByKit[25] = analogSet;
|
|
178
|
+
const orchestraSet = new Uint8Array(128);
|
|
179
|
+
orchestraSet[27] = 9;
|
|
180
|
+
orchestraSet[28] = 9;
|
|
181
|
+
orchestraSet[29] = 9; // HH
|
|
182
|
+
drumExclusiveClassesByKit[48] = orchestraSet;
|
|
183
|
+
const sfxSet = new Uint8Array(128);
|
|
184
|
+
sfxSet[41] = 10;
|
|
185
|
+
sfxSet[42] = 10; // Scratch
|
|
186
|
+
drumExclusiveClassesByKit[56] = sfxSet;
|
|
154
187
|
// normalized to 0-1 for use with the SF2 modulator model
|
|
155
188
|
const defaultControllerState = {
|
|
156
189
|
noteOnVelocity: { type: 2, defaultValue: 0 },
|
|
@@ -239,17 +272,11 @@ const volumeEnvelopeKeys = [
|
|
|
239
272
|
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
240
273
|
export class MidyGM2 {
|
|
241
274
|
constructor(audioContext, options = this.defaultOptions) {
|
|
242
|
-
Object.defineProperty(this, "
|
|
243
|
-
enumerable: true,
|
|
244
|
-
configurable: true,
|
|
245
|
-
writable: true,
|
|
246
|
-
value: 120
|
|
247
|
-
});
|
|
248
|
-
Object.defineProperty(this, "totalTime", {
|
|
275
|
+
Object.defineProperty(this, "mode", {
|
|
249
276
|
enumerable: true,
|
|
250
277
|
configurable: true,
|
|
251
278
|
writable: true,
|
|
252
|
-
value:
|
|
279
|
+
value: "GM2"
|
|
253
280
|
});
|
|
254
281
|
Object.defineProperty(this, "masterFineTuning", {
|
|
255
282
|
enumerable: true,
|
|
@@ -284,6 +311,24 @@ export class MidyGM2 {
|
|
|
284
311
|
delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
|
|
285
312
|
}
|
|
286
313
|
});
|
|
314
|
+
Object.defineProperty(this, "numChannels", {
|
|
315
|
+
enumerable: true,
|
|
316
|
+
configurable: true,
|
|
317
|
+
writable: true,
|
|
318
|
+
value: 16
|
|
319
|
+
});
|
|
320
|
+
Object.defineProperty(this, "ticksPerBeat", {
|
|
321
|
+
enumerable: true,
|
|
322
|
+
configurable: true,
|
|
323
|
+
writable: true,
|
|
324
|
+
value: 120
|
|
325
|
+
});
|
|
326
|
+
Object.defineProperty(this, "totalTime", {
|
|
327
|
+
enumerable: true,
|
|
328
|
+
configurable: true,
|
|
329
|
+
writable: true,
|
|
330
|
+
value: 0
|
|
331
|
+
});
|
|
287
332
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
288
333
|
enumerable: true,
|
|
289
334
|
configurable: true,
|
|
@@ -386,11 +431,17 @@ export class MidyGM2 {
|
|
|
386
431
|
writable: true,
|
|
387
432
|
value: []
|
|
388
433
|
});
|
|
389
|
-
Object.defineProperty(this, "
|
|
434
|
+
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
390
435
|
enumerable: true,
|
|
391
436
|
configurable: true,
|
|
392
437
|
writable: true,
|
|
393
|
-
value: new
|
|
438
|
+
value: new Array(128)
|
|
439
|
+
});
|
|
440
|
+
Object.defineProperty(this, "drumExclusiveClassNotes", {
|
|
441
|
+
enumerable: true,
|
|
442
|
+
configurable: true,
|
|
443
|
+
writable: true,
|
|
444
|
+
value: new Array(this.numChannels * drumExclusiveClassCount)
|
|
394
445
|
});
|
|
395
446
|
Object.defineProperty(this, "defaultOptions", {
|
|
396
447
|
enumerable: true,
|
|
@@ -417,6 +468,11 @@ export class MidyGM2 {
|
|
|
417
468
|
this.audioContext = audioContext;
|
|
418
469
|
this.options = { ...this.defaultOptions, ...options };
|
|
419
470
|
this.masterVolume = new GainNode(audioContext);
|
|
471
|
+
this.scheduler = new GainNode(audioContext, { gain: 0 });
|
|
472
|
+
this.schedulerBuffer = new AudioBuffer({
|
|
473
|
+
length: 1,
|
|
474
|
+
sampleRate: audioContext.sampleRate,
|
|
475
|
+
});
|
|
420
476
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
421
477
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
422
478
|
this.channels = this.createChannels(audioContext);
|
|
@@ -425,6 +481,7 @@ export class MidyGM2 {
|
|
|
425
481
|
this.chorusEffect.output.connect(this.masterVolume);
|
|
426
482
|
this.reverbEffect.output.connect(this.masterVolume);
|
|
427
483
|
this.masterVolume.connect(audioContext.destination);
|
|
484
|
+
this.scheduler.connect(audioContext.destination);
|
|
428
485
|
this.GM2SystemOn();
|
|
429
486
|
}
|
|
430
487
|
initSoundFontTable() {
|
|
@@ -478,8 +535,10 @@ export class MidyGM2 {
|
|
|
478
535
|
};
|
|
479
536
|
}
|
|
480
537
|
createChannels(audioContext) {
|
|
481
|
-
const channels = Array.from({ length:
|
|
538
|
+
const channels = Array.from({ length: this.numChannels }, () => {
|
|
482
539
|
return {
|
|
540
|
+
currentBufferSource: null,
|
|
541
|
+
isDrum: false,
|
|
483
542
|
...this.constructor.channelSettings,
|
|
484
543
|
state: new ControllerState(),
|
|
485
544
|
controlTable: this.initControlTable(),
|
|
@@ -523,7 +582,7 @@ export class MidyGM2 {
|
|
|
523
582
|
return audioBuffer;
|
|
524
583
|
}
|
|
525
584
|
}
|
|
526
|
-
|
|
585
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
527
586
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
528
587
|
bufferSource.buffer = audioBuffer;
|
|
529
588
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
@@ -614,7 +673,8 @@ export class MidyGM2 {
|
|
|
614
673
|
if (queueIndex >= this.timeline.length) {
|
|
615
674
|
await Promise.all(this.notePromises);
|
|
616
675
|
this.notePromises = [];
|
|
617
|
-
this.
|
|
676
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
677
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
618
678
|
this.audioBufferCache.clear();
|
|
619
679
|
resolve();
|
|
620
680
|
return;
|
|
@@ -633,7 +693,8 @@ export class MidyGM2 {
|
|
|
633
693
|
else if (this.isStopping) {
|
|
634
694
|
await this.stopNotes(0, true, now);
|
|
635
695
|
this.notePromises = [];
|
|
636
|
-
this.
|
|
696
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
697
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
637
698
|
this.audioBufferCache.clear();
|
|
638
699
|
resolve();
|
|
639
700
|
this.isStopping = false;
|
|
@@ -642,7 +703,8 @@ export class MidyGM2 {
|
|
|
642
703
|
}
|
|
643
704
|
else if (this.isSeeking) {
|
|
644
705
|
this.stopNotes(0, true, now);
|
|
645
|
-
this.
|
|
706
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
707
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
646
708
|
this.startTime = this.audioContext.currentTime;
|
|
647
709
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
648
710
|
offset = this.resumeTime - this.startTime;
|
|
@@ -670,7 +732,7 @@ export class MidyGM2 {
|
|
|
670
732
|
extractMidiData(midi) {
|
|
671
733
|
const instruments = new Set();
|
|
672
734
|
const timeline = [];
|
|
673
|
-
const tmpChannels = new Array(
|
|
735
|
+
const tmpChannels = new Array(this.channels.length);
|
|
674
736
|
for (let i = 0; i < tmpChannels.length; i++) {
|
|
675
737
|
tmpChannels[i] = {
|
|
676
738
|
programNumber: -1,
|
|
@@ -798,6 +860,9 @@ export class MidyGM2 {
|
|
|
798
860
|
if (!this.isPlaying)
|
|
799
861
|
return;
|
|
800
862
|
this.isStopping = true;
|
|
863
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
864
|
+
this.resetAllStates(i);
|
|
865
|
+
}
|
|
801
866
|
}
|
|
802
867
|
pause() {
|
|
803
868
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -1003,7 +1068,9 @@ export class MidyGM2 {
|
|
|
1003
1068
|
return 8.176 * this.centToRate(cent);
|
|
1004
1069
|
}
|
|
1005
1070
|
calcChannelDetune(channel) {
|
|
1006
|
-
const masterTuning =
|
|
1071
|
+
const masterTuning = channel.isDrum
|
|
1072
|
+
? 0
|
|
1073
|
+
: this.masterCoarseTuning + this.masterFineTuning;
|
|
1007
1074
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
1008
1075
|
const tuning = masterTuning + channelTuning;
|
|
1009
1076
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
@@ -1029,9 +1096,8 @@ export class MidyGM2 {
|
|
|
1029
1096
|
.setValueAtTime(detune, scheduleTime);
|
|
1030
1097
|
}
|
|
1031
1098
|
getPortamentoTime(channel) {
|
|
1032
|
-
const factor = 5 * Math.log(10)
|
|
1033
|
-
|
|
1034
|
-
return Math.log(time) / factor;
|
|
1099
|
+
const factor = 5 * Math.log(10) * 127;
|
|
1100
|
+
return channel.state.portamentoTime * factor;
|
|
1035
1101
|
}
|
|
1036
1102
|
setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
|
|
1037
1103
|
const { voiceParams, startTime } = note;
|
|
@@ -1167,8 +1233,8 @@ export class MidyGM2 {
|
|
|
1167
1233
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1168
1234
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1169
1235
|
}
|
|
1170
|
-
async getAudioBuffer(
|
|
1171
|
-
const audioBufferId = this.getAudioBufferId(
|
|
1236
|
+
async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
|
|
1237
|
+
const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
|
|
1172
1238
|
const cache = this.audioBufferCache.get(audioBufferId);
|
|
1173
1239
|
if (cache) {
|
|
1174
1240
|
cache.counter += 1;
|
|
@@ -1191,8 +1257,8 @@ export class MidyGM2 {
|
|
|
1191
1257
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1192
1258
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1193
1259
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1194
|
-
const audioBuffer = await this.getAudioBuffer(channel.
|
|
1195
|
-
note.bufferSource = this.
|
|
1260
|
+
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1261
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
1196
1262
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1197
1263
|
note.gainL = new GainNode(this.audioContext);
|
|
1198
1264
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1236,23 +1302,72 @@ export class MidyGM2 {
|
|
|
1236
1302
|
note.bufferSource.start(startTime);
|
|
1237
1303
|
return note;
|
|
1238
1304
|
}
|
|
1239
|
-
calcBank(channel
|
|
1240
|
-
|
|
1241
|
-
|
|
1305
|
+
calcBank(channel) {
|
|
1306
|
+
switch (this.mode) {
|
|
1307
|
+
case "GM1":
|
|
1308
|
+
if (channel.isDrum)
|
|
1309
|
+
return 128;
|
|
1310
|
+
return 0;
|
|
1311
|
+
case "GM2":
|
|
1312
|
+
if (channel.bankMSB === 121)
|
|
1313
|
+
return 0;
|
|
1314
|
+
if (channel.isDrum)
|
|
1315
|
+
return 128;
|
|
1316
|
+
return channel.bank;
|
|
1317
|
+
default:
|
|
1318
|
+
return channel.bank;
|
|
1242
1319
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1320
|
+
}
|
|
1321
|
+
handleExclusiveClass(note, channelNumber, startTime) {
|
|
1322
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1323
|
+
if (exclusiveClass === 0)
|
|
1324
|
+
return;
|
|
1325
|
+
const prev = this.exclusiveClassNotes[exclusiveClass];
|
|
1326
|
+
if (prev) {
|
|
1327
|
+
const [prevNote, prevChannelNumber] = prev;
|
|
1328
|
+
if (prevNote && !prevNote.ending) {
|
|
1329
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1330
|
+
startTime, true, // force
|
|
1331
|
+
undefined);
|
|
1332
|
+
}
|
|
1245
1333
|
}
|
|
1246
|
-
|
|
1334
|
+
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
1335
|
+
}
|
|
1336
|
+
handleDrumExclusiveClass(note, channelNumber, startTime) {
|
|
1337
|
+
const channel = this.channels[channelNumber];
|
|
1338
|
+
if (!channel.isDrum)
|
|
1339
|
+
return;
|
|
1340
|
+
const kitTable = drumExclusiveClassesByKit[channel.programNumber];
|
|
1341
|
+
if (!kitTable)
|
|
1342
|
+
return;
|
|
1343
|
+
const drumExclusiveClass = kitTable[note.noteNumber];
|
|
1344
|
+
if (drumExclusiveClass === 0)
|
|
1345
|
+
return;
|
|
1346
|
+
const index = (drumExclusiveClass - 1) * this.channels.length +
|
|
1347
|
+
channelNumber;
|
|
1348
|
+
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1349
|
+
if (prevNote && !prevNote.ending) {
|
|
1350
|
+
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1351
|
+
startTime, true, // force
|
|
1352
|
+
undefined);
|
|
1353
|
+
}
|
|
1354
|
+
this.drumExclusiveClassNotes[index] = note;
|
|
1355
|
+
}
|
|
1356
|
+
isDrumNoteOffException(channel, noteNumber) {
|
|
1357
|
+
if (!channel.isDrum)
|
|
1358
|
+
return false;
|
|
1359
|
+
const programNumber = channel.programNumber;
|
|
1360
|
+
return (programNumber === 48 && noteNumber === 88) ||
|
|
1361
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84);
|
|
1247
1362
|
}
|
|
1248
1363
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
|
|
1249
1364
|
const channel = this.channels[channelNumber];
|
|
1250
1365
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1251
|
-
const soundFontIndex = this.soundFontTable[channel.
|
|
1366
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
1252
1367
|
if (soundFontIndex === undefined)
|
|
1253
1368
|
return;
|
|
1254
1369
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1255
|
-
const voice = soundFont.getVoice(bankNumber, channel.
|
|
1370
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1256
1371
|
if (!voice)
|
|
1257
1372
|
return;
|
|
1258
1373
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
@@ -1262,31 +1377,58 @@ export class MidyGM2 {
|
|
|
1262
1377
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1263
1378
|
channel.sustainNotes.push(note);
|
|
1264
1379
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1268
|
-
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1269
|
-
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1270
|
-
if (!prevNote.ending) {
|
|
1271
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1272
|
-
startTime, true, // force
|
|
1273
|
-
undefined);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
1277
|
-
}
|
|
1380
|
+
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1381
|
+
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1278
1382
|
const scheduledNotes = channel.scheduledNotes;
|
|
1279
|
-
|
|
1280
|
-
|
|
1383
|
+
let notes = scheduledNotes.get(noteNumber);
|
|
1384
|
+
if (notes) {
|
|
1385
|
+
notes.push(note);
|
|
1281
1386
|
}
|
|
1282
1387
|
else {
|
|
1283
|
-
|
|
1388
|
+
notes = [note];
|
|
1389
|
+
scheduledNotes.set(noteNumber, notes);
|
|
1390
|
+
}
|
|
1391
|
+
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1392
|
+
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1393
|
+
const index = notes.length - 1;
|
|
1394
|
+
const promise = new Promise((resolve) => {
|
|
1395
|
+
note.bufferSource.onended = () => {
|
|
1396
|
+
this.disconnectNote(note, scheduledNotes, index);
|
|
1397
|
+
resolve();
|
|
1398
|
+
};
|
|
1399
|
+
note.bufferSource.stop(stopTime);
|
|
1400
|
+
});
|
|
1401
|
+
this.notePromises.push(promise);
|
|
1284
1402
|
}
|
|
1285
1403
|
}
|
|
1286
1404
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1287
1405
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1288
1406
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1289
1407
|
}
|
|
1408
|
+
disconnectNote(note, scheduledNotes, index) {
|
|
1409
|
+
scheduledNotes[index] = null;
|
|
1410
|
+
note.bufferSource.disconnect();
|
|
1411
|
+
note.filterNode.disconnect();
|
|
1412
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1413
|
+
note.volumeNode.disconnect();
|
|
1414
|
+
note.gainL.disconnect();
|
|
1415
|
+
note.gainR.disconnect();
|
|
1416
|
+
if (note.modulationDepth) {
|
|
1417
|
+
note.volumeDepth.disconnect();
|
|
1418
|
+
note.modulationDepth.disconnect();
|
|
1419
|
+
note.modulationLFO.stop();
|
|
1420
|
+
}
|
|
1421
|
+
if (note.vibratoDepth) {
|
|
1422
|
+
note.vibratoDepth.disconnect();
|
|
1423
|
+
note.vibratoLFO.stop();
|
|
1424
|
+
}
|
|
1425
|
+
if (note.reverbEffectsSend) {
|
|
1426
|
+
note.reverbEffectsSend.disconnect();
|
|
1427
|
+
}
|
|
1428
|
+
if (note.chorusEffectsSend) {
|
|
1429
|
+
note.chorusEffectsSend.disconnect();
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1290
1432
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1291
1433
|
const note = scheduledNotes[index];
|
|
1292
1434
|
note.volumeEnvelopeNode.gain
|
|
@@ -1298,28 +1440,7 @@ export class MidyGM2 {
|
|
|
1298
1440
|
}, stopTime);
|
|
1299
1441
|
return new Promise((resolve) => {
|
|
1300
1442
|
note.bufferSource.onended = () => {
|
|
1301
|
-
scheduledNotes
|
|
1302
|
-
note.bufferSource.disconnect();
|
|
1303
|
-
note.filterNode.disconnect();
|
|
1304
|
-
note.volumeEnvelopeNode.disconnect();
|
|
1305
|
-
note.volumeNode.disconnect();
|
|
1306
|
-
note.gainL.disconnect();
|
|
1307
|
-
note.gainR.disconnect();
|
|
1308
|
-
if (note.modulationDepth) {
|
|
1309
|
-
note.volumeDepth.disconnect();
|
|
1310
|
-
note.modulationDepth.disconnect();
|
|
1311
|
-
note.modulationLFO.stop();
|
|
1312
|
-
}
|
|
1313
|
-
if (note.vibratoDepth) {
|
|
1314
|
-
note.vibratoDepth.disconnect();
|
|
1315
|
-
note.vibratoLFO.stop();
|
|
1316
|
-
}
|
|
1317
|
-
if (note.reverbEffectsSend) {
|
|
1318
|
-
note.reverbEffectsSend.disconnect();
|
|
1319
|
-
}
|
|
1320
|
-
if (note.chorusEffectsSend) {
|
|
1321
|
-
note.chorusEffectsSend.disconnect();
|
|
1322
|
-
}
|
|
1443
|
+
this.disconnectNote(note, scheduledNotes, index);
|
|
1323
1444
|
resolve();
|
|
1324
1445
|
};
|
|
1325
1446
|
note.bufferSource.stop(stopTime);
|
|
@@ -1327,6 +1448,8 @@ export class MidyGM2 {
|
|
|
1327
1448
|
}
|
|
1328
1449
|
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1329
1450
|
const channel = this.channels[channelNumber];
|
|
1451
|
+
if (this.isDrumNoteOffException(channel, noteNumber))
|
|
1452
|
+
return;
|
|
1330
1453
|
const state = channel.state;
|
|
1331
1454
|
if (!force) {
|
|
1332
1455
|
if (0.5 <= state.sustainPedal)
|
|
@@ -1412,13 +1535,25 @@ export class MidyGM2 {
|
|
|
1412
1535
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1413
1536
|
}
|
|
1414
1537
|
}
|
|
1415
|
-
handleProgramChange(channelNumber,
|
|
1538
|
+
handleProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1416
1539
|
const channel = this.channels[channelNumber];
|
|
1417
1540
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1418
|
-
channel.
|
|
1541
|
+
channel.programNumber = programNumber;
|
|
1542
|
+
if (this.mode === "GM2") {
|
|
1543
|
+
switch (channel.bankMSB) {
|
|
1544
|
+
case 120:
|
|
1545
|
+
channel.isDrum = true;
|
|
1546
|
+
break;
|
|
1547
|
+
case 121:
|
|
1548
|
+
channel.isDrum = false;
|
|
1549
|
+
break;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1419
1552
|
}
|
|
1420
1553
|
handleChannelPressure(channelNumber, value, scheduleTime) {
|
|
1421
1554
|
const channel = this.channels[channelNumber];
|
|
1555
|
+
if (channel.isDrum)
|
|
1556
|
+
return;
|
|
1422
1557
|
const prev = channel.state.channelPressure;
|
|
1423
1558
|
const next = value / 127;
|
|
1424
1559
|
channel.state.channelPressure = next;
|
|
@@ -1437,8 +1572,10 @@ export class MidyGM2 {
|
|
|
1437
1572
|
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
1438
1573
|
}
|
|
1439
1574
|
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1440
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1441
1575
|
const channel = this.channels[channelNumber];
|
|
1576
|
+
if (channel.isDrum)
|
|
1577
|
+
return;
|
|
1578
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1442
1579
|
const state = channel.state;
|
|
1443
1580
|
const prev = state.pitchWheel * 2 - 1;
|
|
1444
1581
|
const next = (value - 8192) / 8192;
|
|
@@ -1710,15 +1847,16 @@ export class MidyGM2 {
|
|
|
1710
1847
|
});
|
|
1711
1848
|
}
|
|
1712
1849
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1713
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1714
1850
|
const channel = this.channels[channelNumber];
|
|
1851
|
+
if (channel.isDrum)
|
|
1852
|
+
return;
|
|
1853
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1715
1854
|
channel.state.modulationDepth = modulation / 127;
|
|
1716
1855
|
this.updateModulation(channel, scheduleTime);
|
|
1717
1856
|
}
|
|
1718
1857
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1719
1858
|
const channel = this.channels[channelNumber];
|
|
1720
|
-
|
|
1721
|
-
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1859
|
+
channel.state.portamentoTime = portamentoTime / 127;
|
|
1722
1860
|
}
|
|
1723
1861
|
setKeyBasedVolume(channel, scheduleTime) {
|
|
1724
1862
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1790,8 +1928,10 @@ export class MidyGM2 {
|
|
|
1790
1928
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1791
1929
|
}
|
|
1792
1930
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1793
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1794
1931
|
const channel = this.channels[channelNumber];
|
|
1932
|
+
if (channel.isDrum)
|
|
1933
|
+
return;
|
|
1934
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1795
1935
|
channel.state.sustainPedal = value / 127;
|
|
1796
1936
|
if (64 <= value) {
|
|
1797
1937
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1803,11 +1943,16 @@ export class MidyGM2 {
|
|
|
1803
1943
|
}
|
|
1804
1944
|
}
|
|
1805
1945
|
setPortamento(channelNumber, value) {
|
|
1806
|
-
this.channels[channelNumber]
|
|
1946
|
+
const channel = this.channels[channelNumber];
|
|
1947
|
+
if (channel.isDrum)
|
|
1948
|
+
return;
|
|
1949
|
+
channel.state.portamento = value / 127;
|
|
1807
1950
|
}
|
|
1808
1951
|
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1809
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1810
1952
|
const channel = this.channels[channelNumber];
|
|
1953
|
+
if (channel.isDrum)
|
|
1954
|
+
return;
|
|
1955
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1811
1956
|
channel.state.sostenutoPedal = value / 127;
|
|
1812
1957
|
if (64 <= value) {
|
|
1813
1958
|
channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
|
|
@@ -1816,9 +1961,22 @@ export class MidyGM2 {
|
|
|
1816
1961
|
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
1817
1962
|
}
|
|
1818
1963
|
}
|
|
1819
|
-
setSoftPedal(channelNumber, softPedal,
|
|
1964
|
+
setSoftPedal(channelNumber, softPedal, scheduleTime) {
|
|
1820
1965
|
const channel = this.channels[channelNumber];
|
|
1966
|
+
if (channel.isDrum)
|
|
1967
|
+
return;
|
|
1968
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1821
1969
|
channel.state.softPedal = softPedal / 127;
|
|
1970
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1971
|
+
if (note.portamento) {
|
|
1972
|
+
this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
|
|
1973
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1974
|
+
}
|
|
1975
|
+
else {
|
|
1976
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1977
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1822
1980
|
}
|
|
1823
1981
|
setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
|
|
1824
1982
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1947,8 +2105,10 @@ export class MidyGM2 {
|
|
|
1947
2105
|
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1948
2106
|
}
|
|
1949
2107
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1950
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1951
2108
|
const channel = this.channels[channelNumber];
|
|
2109
|
+
if (channel.isDrum)
|
|
2110
|
+
return;
|
|
2111
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1952
2112
|
const state = channel.state;
|
|
1953
2113
|
const prev = state.pitchWheelSensitivity;
|
|
1954
2114
|
const next = value / 128;
|
|
@@ -1964,8 +2124,10 @@ export class MidyGM2 {
|
|
|
1964
2124
|
this.setFineTuning(channelNumber, fineTuning, scheduleTime);
|
|
1965
2125
|
}
|
|
1966
2126
|
setFineTuning(channelNumber, value, scheduleTime) {
|
|
1967
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1968
2127
|
const channel = this.channels[channelNumber];
|
|
2128
|
+
if (channel.isDrum)
|
|
2129
|
+
return;
|
|
2130
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1969
2131
|
const prev = channel.fineTuning;
|
|
1970
2132
|
const next = (value - 8192) / 8.192; // cent
|
|
1971
2133
|
channel.fineTuning = next;
|
|
@@ -1979,8 +2141,10 @@ export class MidyGM2 {
|
|
|
1979
2141
|
this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
|
|
1980
2142
|
}
|
|
1981
2143
|
setCoarseTuning(channelNumber, value, scheduleTime) {
|
|
1982
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1983
2144
|
const channel = this.channels[channelNumber];
|
|
2145
|
+
if (channel.isDrum)
|
|
2146
|
+
return;
|
|
2147
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1984
2148
|
const prev = channel.coarseTuning;
|
|
1985
2149
|
const next = (value - 64) * 100; // cent
|
|
1986
2150
|
channel.coarseTuning = next;
|
|
@@ -1994,8 +2158,10 @@ export class MidyGM2 {
|
|
|
1994
2158
|
this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
|
|
1995
2159
|
}
|
|
1996
2160
|
setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
|
|
1997
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1998
2161
|
const channel = this.channels[channelNumber];
|
|
2162
|
+
if (channel.isDrum)
|
|
2163
|
+
return;
|
|
2164
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1999
2165
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2000
2166
|
this.updateModulation(channel, scheduleTime);
|
|
2001
2167
|
}
|
|
@@ -2003,16 +2169,30 @@ export class MidyGM2 {
|
|
|
2003
2169
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2004
2170
|
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
2005
2171
|
}
|
|
2172
|
+
resetAllStates(channelNumber) {
|
|
2173
|
+
const channel = this.channels[channelNumber];
|
|
2174
|
+
const state = channel.state;
|
|
2175
|
+
for (const type of Object.keys(defaultControllerState)) {
|
|
2176
|
+
state[type] = defaultControllerState[type].defaultValue;
|
|
2177
|
+
}
|
|
2178
|
+
for (const type of Object.keys(this.constructor.channelSettings)) {
|
|
2179
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
2180
|
+
}
|
|
2181
|
+
this.mode = "GM2";
|
|
2182
|
+
this.masterFineTuning = 0; // cb
|
|
2183
|
+
this.masterCoarseTuning = 0; // cb
|
|
2184
|
+
}
|
|
2185
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2006
2186
|
resetAllControllers(channelNumber) {
|
|
2007
2187
|
const stateTypes = [
|
|
2188
|
+
"channelPressure",
|
|
2189
|
+
"pitchWheel",
|
|
2008
2190
|
"expression",
|
|
2009
2191
|
"modulationDepth",
|
|
2010
2192
|
"sustainPedal",
|
|
2011
2193
|
"portamento",
|
|
2012
2194
|
"sostenutoPedal",
|
|
2013
2195
|
"softPedal",
|
|
2014
|
-
"channelPressure",
|
|
2015
|
-
"pitchWheelSensitivity",
|
|
2016
2196
|
];
|
|
2017
2197
|
const channel = this.channels[channelNumber];
|
|
2018
2198
|
const state = channel.state;
|
|
@@ -2063,12 +2243,12 @@ export class MidyGM2 {
|
|
|
2063
2243
|
case 9:
|
|
2064
2244
|
switch (data[3]) {
|
|
2065
2245
|
case 1:
|
|
2066
|
-
this.GM1SystemOn();
|
|
2246
|
+
this.GM1SystemOn(scheduleTime);
|
|
2067
2247
|
break;
|
|
2068
2248
|
case 2: // GM System Off
|
|
2069
2249
|
break;
|
|
2070
2250
|
case 3:
|
|
2071
|
-
this.GM2SystemOn();
|
|
2251
|
+
this.GM2SystemOn(scheduleTime);
|
|
2072
2252
|
break;
|
|
2073
2253
|
default:
|
|
2074
2254
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -2078,25 +2258,35 @@ export class MidyGM2 {
|
|
|
2078
2258
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2079
2259
|
}
|
|
2080
2260
|
}
|
|
2081
|
-
GM1SystemOn() {
|
|
2261
|
+
GM1SystemOn(scheduleTime) {
|
|
2262
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2263
|
+
this.mode = "GM1";
|
|
2082
2264
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2265
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2083
2266
|
const channel = this.channels[i];
|
|
2084
2267
|
channel.bankMSB = 0;
|
|
2085
2268
|
channel.bankLSB = 0;
|
|
2086
2269
|
channel.bank = 0;
|
|
2270
|
+
channel.isDrum = false;
|
|
2087
2271
|
}
|
|
2088
2272
|
this.channels[9].bankMSB = 1;
|
|
2089
2273
|
this.channels[9].bank = 128;
|
|
2274
|
+
this.channels[9].isDrum = true;
|
|
2090
2275
|
}
|
|
2091
|
-
GM2SystemOn() {
|
|
2276
|
+
GM2SystemOn(scheduleTime) {
|
|
2277
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2278
|
+
this.mode = "GM2";
|
|
2092
2279
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2280
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2093
2281
|
const channel = this.channels[i];
|
|
2094
2282
|
channel.bankMSB = 121;
|
|
2095
2283
|
channel.bankLSB = 0;
|
|
2096
2284
|
channel.bank = 121 * 128;
|
|
2285
|
+
channel.isDrum = false;
|
|
2097
2286
|
}
|
|
2098
2287
|
this.channels[9].bankMSB = 120;
|
|
2099
2288
|
this.channels[9].bank = 120 * 128;
|
|
2289
|
+
this.channels[9].isDrum = true;
|
|
2100
2290
|
}
|
|
2101
2291
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2102
2292
|
switch (data[2]) {
|
|
@@ -2159,8 +2349,14 @@ export class MidyGM2 {
|
|
|
2159
2349
|
const prev = this.masterFineTuning;
|
|
2160
2350
|
const next = (value - 8192) / 8.192; // cent
|
|
2161
2351
|
this.masterFineTuning = next;
|
|
2162
|
-
|
|
2163
|
-
this.
|
|
2352
|
+
const detuneChange = next - prev;
|
|
2353
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2354
|
+
const channel = this.channels[i];
|
|
2355
|
+
if (channel.isDrum)
|
|
2356
|
+
continue;
|
|
2357
|
+
channel.detune += detuneChange;
|
|
2358
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2359
|
+
}
|
|
2164
2360
|
}
|
|
2165
2361
|
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2166
2362
|
const coarseTuning = data[4];
|
|
@@ -2170,8 +2366,14 @@ export class MidyGM2 {
|
|
|
2170
2366
|
const prev = this.masterCoarseTuning;
|
|
2171
2367
|
const next = (value - 64) * 100; // cent
|
|
2172
2368
|
this.masterCoarseTuning = next;
|
|
2173
|
-
|
|
2174
|
-
this.
|
|
2369
|
+
const detuneChange = next - prev;
|
|
2370
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2371
|
+
const channel = this.channels[i];
|
|
2372
|
+
if (channel.isDrum)
|
|
2373
|
+
continue;
|
|
2374
|
+
channel.detune += detuneChange;
|
|
2375
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2376
|
+
}
|
|
2175
2377
|
}
|
|
2176
2378
|
handleGlobalParameterControlSysEx(data, scheduleTime) {
|
|
2177
2379
|
if (data[7] === 1) {
|
|
@@ -2355,7 +2557,7 @@ export class MidyGM2 {
|
|
|
2355
2557
|
return value * 0.00787;
|
|
2356
2558
|
}
|
|
2357
2559
|
getChannelBitmap(data) {
|
|
2358
|
-
const bitmap = new Array(
|
|
2560
|
+
const bitmap = new Array(this.channels.length).fill(false);
|
|
2359
2561
|
const ff = data[4] & 0b11;
|
|
2360
2562
|
const gg = data[5] & 0x7F;
|
|
2361
2563
|
const hh = data[6] & 0x7F;
|
|
@@ -2497,15 +2699,23 @@ export class MidyGM2 {
|
|
|
2497
2699
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2498
2700
|
}
|
|
2499
2701
|
}
|
|
2702
|
+
// https://github.com/marmooo/js-timer-benchmark
|
|
2500
2703
|
scheduleTask(callback, scheduleTime) {
|
|
2501
2704
|
return new Promise((resolve) => {
|
|
2502
|
-
const bufferSource = new AudioBufferSourceNode(this.audioContext
|
|
2705
|
+
const bufferSource = new AudioBufferSourceNode(this.audioContext, {
|
|
2706
|
+
buffer: this.schedulerBuffer,
|
|
2707
|
+
});
|
|
2708
|
+
bufferSource.connect(this.scheduler);
|
|
2503
2709
|
bufferSource.onended = () => {
|
|
2504
|
-
|
|
2505
|
-
|
|
2710
|
+
try {
|
|
2711
|
+
callback();
|
|
2712
|
+
}
|
|
2713
|
+
finally {
|
|
2714
|
+
bufferSource.disconnect();
|
|
2715
|
+
resolve();
|
|
2716
|
+
}
|
|
2506
2717
|
};
|
|
2507
2718
|
bufferSource.start(scheduleTime);
|
|
2508
|
-
bufferSource.stop(scheduleTime);
|
|
2509
2719
|
});
|
|
2510
2720
|
}
|
|
2511
2721
|
}
|
|
@@ -2514,9 +2724,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2514
2724
|
configurable: true,
|
|
2515
2725
|
writable: true,
|
|
2516
2726
|
value: {
|
|
2517
|
-
currentBufferSource: null,
|
|
2518
2727
|
detune: 0,
|
|
2519
|
-
|
|
2728
|
+
programNumber: 0,
|
|
2520
2729
|
bank: 121 * 128,
|
|
2521
2730
|
bankMSB: 121,
|
|
2522
2731
|
bankLSB: 0,
|
|
@@ -2525,8 +2734,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2525
2734
|
rpnMSB: 127,
|
|
2526
2735
|
rpnLSB: 127,
|
|
2527
2736
|
mono: false, // CC#124, CC#125
|
|
2737
|
+
modulationDepthRange: 50, // cent
|
|
2528
2738
|
fineTuning: 0, // cb
|
|
2529
2739
|
coarseTuning: 0, // cb
|
|
2530
|
-
modulationDepthRange: 50, // cent
|
|
2531
2740
|
}
|
|
2532
2741
|
});
|