@marmooo/midy 0.2.9 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/esm/midy-GM1.d.ts +11 -7
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +114 -74
- package/esm/midy-GM2.d.ts +17 -11
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +250 -124
- package/esm/midy-GMLite.d.ts +14 -9
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +167 -81
- package/esm/midy.d.ts +20 -14
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +257 -129
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +11 -7
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +114 -74
- package/script/midy-GM2.d.ts +17 -11
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +250 -124
- package/script/midy-GMLite.d.ts +14 -9
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +167 -81
- package/script/midy.d.ts +20 -14
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +257 -129
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 },
|
|
@@ -248,18 +281,6 @@ class MidyGM2 {
|
|
|
248
281
|
writable: true,
|
|
249
282
|
value: "GM2"
|
|
250
283
|
});
|
|
251
|
-
Object.defineProperty(this, "ticksPerBeat", {
|
|
252
|
-
enumerable: true,
|
|
253
|
-
configurable: true,
|
|
254
|
-
writable: true,
|
|
255
|
-
value: 120
|
|
256
|
-
});
|
|
257
|
-
Object.defineProperty(this, "totalTime", {
|
|
258
|
-
enumerable: true,
|
|
259
|
-
configurable: true,
|
|
260
|
-
writable: true,
|
|
261
|
-
value: 0
|
|
262
|
-
});
|
|
263
284
|
Object.defineProperty(this, "masterFineTuning", {
|
|
264
285
|
enumerable: true,
|
|
265
286
|
configurable: true,
|
|
@@ -293,6 +314,24 @@ class MidyGM2 {
|
|
|
293
314
|
delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
|
|
294
315
|
}
|
|
295
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
|
+
});
|
|
296
335
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
297
336
|
enumerable: true,
|
|
298
337
|
configurable: true,
|
|
@@ -395,11 +434,17 @@ class MidyGM2 {
|
|
|
395
434
|
writable: true,
|
|
396
435
|
value: []
|
|
397
436
|
});
|
|
398
|
-
Object.defineProperty(this, "
|
|
437
|
+
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
399
438
|
enumerable: true,
|
|
400
439
|
configurable: true,
|
|
401
440
|
writable: true,
|
|
402
|
-
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)
|
|
403
448
|
});
|
|
404
449
|
Object.defineProperty(this, "defaultOptions", {
|
|
405
450
|
enumerable: true,
|
|
@@ -493,8 +538,10 @@ class MidyGM2 {
|
|
|
493
538
|
};
|
|
494
539
|
}
|
|
495
540
|
createChannels(audioContext) {
|
|
496
|
-
const channels = Array.from({ length:
|
|
541
|
+
const channels = Array.from({ length: this.numChannels }, () => {
|
|
497
542
|
return {
|
|
543
|
+
currentBufferSource: null,
|
|
544
|
+
isDrum: false,
|
|
498
545
|
...this.constructor.channelSettings,
|
|
499
546
|
state: new ControllerState(),
|
|
500
547
|
controlTable: this.initControlTable(),
|
|
@@ -538,24 +585,10 @@ class MidyGM2 {
|
|
|
538
585
|
return audioBuffer;
|
|
539
586
|
}
|
|
540
587
|
}
|
|
541
|
-
|
|
542
|
-
if (channel.isDrum) {
|
|
543
|
-
const noteNumber = note.noteNumber;
|
|
544
|
-
if (noteNumber === 88 || 47 <= noteNumber && noteNumber <= 84) {
|
|
545
|
-
return true;
|
|
546
|
-
}
|
|
547
|
-
else {
|
|
548
|
-
return false;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
else {
|
|
552
|
-
return voiceParams.sampleModes % 2 !== 0;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
createBufferSource(channel, note, voiceParams, audioBuffer) {
|
|
588
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
556
589
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
557
590
|
bufferSource.buffer = audioBuffer;
|
|
558
|
-
bufferSource.loop =
|
|
591
|
+
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
559
592
|
if (bufferSource.loop) {
|
|
560
593
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
561
594
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -588,7 +621,7 @@ class MidyGM2 {
|
|
|
588
621
|
const startTime = event.startTime + this.startDelay - offset;
|
|
589
622
|
switch (event.type) {
|
|
590
623
|
case "noteOn":
|
|
591
|
-
if (event.velocity
|
|
624
|
+
if (0 < event.velocity) {
|
|
592
625
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
|
|
593
626
|
break;
|
|
594
627
|
}
|
|
@@ -643,7 +676,8 @@ class MidyGM2 {
|
|
|
643
676
|
if (queueIndex >= this.timeline.length) {
|
|
644
677
|
await Promise.all(this.notePromises);
|
|
645
678
|
this.notePromises = [];
|
|
646
|
-
this.
|
|
679
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
680
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
647
681
|
this.audioBufferCache.clear();
|
|
648
682
|
resolve();
|
|
649
683
|
return;
|
|
@@ -662,7 +696,8 @@ class MidyGM2 {
|
|
|
662
696
|
else if (this.isStopping) {
|
|
663
697
|
await this.stopNotes(0, true, now);
|
|
664
698
|
this.notePromises = [];
|
|
665
|
-
this.
|
|
699
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
700
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
666
701
|
this.audioBufferCache.clear();
|
|
667
702
|
resolve();
|
|
668
703
|
this.isStopping = false;
|
|
@@ -671,7 +706,8 @@ class MidyGM2 {
|
|
|
671
706
|
}
|
|
672
707
|
else if (this.isSeeking) {
|
|
673
708
|
this.stopNotes(0, true, now);
|
|
674
|
-
this.
|
|
709
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
710
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
675
711
|
this.startTime = this.audioContext.currentTime;
|
|
676
712
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
677
713
|
offset = this.resumeTime - this.startTime;
|
|
@@ -699,7 +735,7 @@ class MidyGM2 {
|
|
|
699
735
|
extractMidiData(midi) {
|
|
700
736
|
const instruments = new Set();
|
|
701
737
|
const timeline = [];
|
|
702
|
-
const tmpChannels = new Array(
|
|
738
|
+
const tmpChannels = new Array(this.channels.length);
|
|
703
739
|
for (let i = 0; i < tmpChannels.length; i++) {
|
|
704
740
|
tmpChannels[i] = {
|
|
705
741
|
programNumber: -1,
|
|
@@ -798,6 +834,17 @@ class MidyGM2 {
|
|
|
798
834
|
}
|
|
799
835
|
return { instruments, timeline };
|
|
800
836
|
}
|
|
837
|
+
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
838
|
+
const channel = this.channels[channelNumber];
|
|
839
|
+
const promises = [];
|
|
840
|
+
const activeNotes = this.getActiveNotes(channel, scheduleTime);
|
|
841
|
+
activeNotes.forEach((note) => {
|
|
842
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
843
|
+
this.notePromises.push(promise);
|
|
844
|
+
promises.push(promise);
|
|
845
|
+
});
|
|
846
|
+
return Promise.all(promises);
|
|
847
|
+
}
|
|
801
848
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
802
849
|
const channel = this.channels[channelNumber];
|
|
803
850
|
const promises = [];
|
|
@@ -827,6 +874,9 @@ class MidyGM2 {
|
|
|
827
874
|
if (!this.isPlaying)
|
|
828
875
|
return;
|
|
829
876
|
this.isStopping = true;
|
|
877
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
878
|
+
this.resetAllStates(i);
|
|
879
|
+
}
|
|
830
880
|
}
|
|
831
881
|
pause() {
|
|
832
882
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -866,6 +916,8 @@ class MidyGM2 {
|
|
|
866
916
|
const note = noteList[i];
|
|
867
917
|
if (!note)
|
|
868
918
|
continue;
|
|
919
|
+
if (note.ending)
|
|
920
|
+
continue;
|
|
869
921
|
callback(note);
|
|
870
922
|
}
|
|
871
923
|
});
|
|
@@ -1197,8 +1249,8 @@ class MidyGM2 {
|
|
|
1197
1249
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1198
1250
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1199
1251
|
}
|
|
1200
|
-
async getAudioBuffer(
|
|
1201
|
-
const audioBufferId = this.getAudioBufferId(
|
|
1252
|
+
async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
|
|
1253
|
+
const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
|
|
1202
1254
|
const cache = this.audioBufferCache.get(audioBufferId);
|
|
1203
1255
|
if (cache) {
|
|
1204
1256
|
cache.counter += 1;
|
|
@@ -1221,8 +1273,8 @@ class MidyGM2 {
|
|
|
1221
1273
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1222
1274
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1223
1275
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1224
|
-
const audioBuffer = await this.getAudioBuffer(channel.
|
|
1225
|
-
note.bufferSource = this.createBufferSource(
|
|
1276
|
+
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1277
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
1226
1278
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1227
1279
|
note.gainL = new GainNode(this.audioContext);
|
|
1228
1280
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1231,7 +1283,7 @@ class MidyGM2 {
|
|
|
1231
1283
|
type: "lowpass",
|
|
1232
1284
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
1233
1285
|
});
|
|
1234
|
-
if (portamento) {
|
|
1286
|
+
if (0.5 <= state.portamento && portamento) {
|
|
1235
1287
|
note.portamento = true;
|
|
1236
1288
|
this.setPortamentoStartVolumeEnvelope(channel, note, now);
|
|
1237
1289
|
this.setPortamentoStartFilterEnvelope(channel, note, now);
|
|
@@ -1282,14 +1334,56 @@ class MidyGM2 {
|
|
|
1282
1334
|
return channel.bank;
|
|
1283
1335
|
}
|
|
1284
1336
|
}
|
|
1337
|
+
handleExclusiveClass(note, channelNumber, startTime) {
|
|
1338
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1339
|
+
if (exclusiveClass === 0)
|
|
1340
|
+
return;
|
|
1341
|
+
const prev = this.exclusiveClassNotes[exclusiveClass];
|
|
1342
|
+
if (prev) {
|
|
1343
|
+
const [prevNote, prevChannelNumber] = prev;
|
|
1344
|
+
if (prevNote && !prevNote.ending) {
|
|
1345
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1346
|
+
startTime, true, // force
|
|
1347
|
+
undefined);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
1351
|
+
}
|
|
1352
|
+
handleDrumExclusiveClass(note, channelNumber, startTime) {
|
|
1353
|
+
const channel = this.channels[channelNumber];
|
|
1354
|
+
if (!channel.isDrum)
|
|
1355
|
+
return;
|
|
1356
|
+
const kitTable = drumExclusiveClassesByKit[channel.programNumber];
|
|
1357
|
+
if (!kitTable)
|
|
1358
|
+
return;
|
|
1359
|
+
const drumExclusiveClass = kitTable[note.noteNumber];
|
|
1360
|
+
if (drumExclusiveClass === 0)
|
|
1361
|
+
return;
|
|
1362
|
+
const index = (drumExclusiveClass - 1) * this.channels.length +
|
|
1363
|
+
channelNumber;
|
|
1364
|
+
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1365
|
+
if (prevNote && !prevNote.ending) {
|
|
1366
|
+
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1367
|
+
startTime, true, // force
|
|
1368
|
+
undefined);
|
|
1369
|
+
}
|
|
1370
|
+
this.drumExclusiveClassNotes[index] = note;
|
|
1371
|
+
}
|
|
1372
|
+
isDrumNoteOffException(channel, noteNumber) {
|
|
1373
|
+
if (!channel.isDrum)
|
|
1374
|
+
return false;
|
|
1375
|
+
const programNumber = channel.programNumber;
|
|
1376
|
+
return !((programNumber === 48 && noteNumber === 88) ||
|
|
1377
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
1378
|
+
}
|
|
1285
1379
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
|
|
1286
1380
|
const channel = this.channels[channelNumber];
|
|
1287
1381
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1288
|
-
const soundFontIndex = this.soundFontTable[channel.
|
|
1382
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
1289
1383
|
if (soundFontIndex === undefined)
|
|
1290
1384
|
return;
|
|
1291
1385
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1292
|
-
const voice = soundFont.getVoice(bankNumber, channel.
|
|
1386
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1293
1387
|
if (!voice)
|
|
1294
1388
|
return;
|
|
1295
1389
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
@@ -1299,33 +1393,60 @@ class MidyGM2 {
|
|
|
1299
1393
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1300
1394
|
channel.sustainNotes.push(note);
|
|
1301
1395
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1305
|
-
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1306
|
-
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1307
|
-
if (prevNote && !prevNote.ending) {
|
|
1308
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1309
|
-
startTime, true, // force
|
|
1310
|
-
undefined);
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
1314
|
-
}
|
|
1396
|
+
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1397
|
+
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1315
1398
|
const scheduledNotes = channel.scheduledNotes;
|
|
1316
|
-
|
|
1317
|
-
|
|
1399
|
+
let noteList = scheduledNotes.get(noteNumber);
|
|
1400
|
+
if (noteList) {
|
|
1401
|
+
noteList.push(note);
|
|
1318
1402
|
}
|
|
1319
1403
|
else {
|
|
1320
|
-
|
|
1404
|
+
noteList = [note];
|
|
1405
|
+
scheduledNotes.set(noteNumber, noteList);
|
|
1406
|
+
}
|
|
1407
|
+
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1408
|
+
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1409
|
+
const index = noteList.length - 1;
|
|
1410
|
+
const promise = new Promise((resolve) => {
|
|
1411
|
+
note.bufferSource.onended = () => {
|
|
1412
|
+
noteList[index] = undefined;
|
|
1413
|
+
this.disconnectNote(note);
|
|
1414
|
+
resolve();
|
|
1415
|
+
};
|
|
1416
|
+
note.bufferSource.stop(stopTime);
|
|
1417
|
+
});
|
|
1418
|
+
this.notePromises.push(promise);
|
|
1321
1419
|
}
|
|
1322
1420
|
}
|
|
1323
1421
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1324
1422
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1325
1423
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1326
1424
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1425
|
+
disconnectNote(note) {
|
|
1426
|
+
note.bufferSource.disconnect();
|
|
1427
|
+
note.filterNode.disconnect();
|
|
1428
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1429
|
+
note.volumeNode.disconnect();
|
|
1430
|
+
note.gainL.disconnect();
|
|
1431
|
+
note.gainR.disconnect();
|
|
1432
|
+
if (note.modulationDepth) {
|
|
1433
|
+
note.volumeDepth.disconnect();
|
|
1434
|
+
note.modulationDepth.disconnect();
|
|
1435
|
+
note.modulationLFO.stop();
|
|
1436
|
+
}
|
|
1437
|
+
if (note.vibratoDepth) {
|
|
1438
|
+
note.vibratoDepth.disconnect();
|
|
1439
|
+
note.vibratoLFO.stop();
|
|
1440
|
+
}
|
|
1441
|
+
if (note.reverbEffectsSend) {
|
|
1442
|
+
note.reverbEffectsSend.disconnect();
|
|
1443
|
+
}
|
|
1444
|
+
if (note.chorusEffectsSend) {
|
|
1445
|
+
note.chorusEffectsSend.disconnect();
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
stopNote(endTime, stopTime, noteList, index) {
|
|
1449
|
+
const note = noteList[index];
|
|
1329
1450
|
note.volumeEnvelopeNode.gain
|
|
1330
1451
|
.cancelScheduledValues(endTime)
|
|
1331
1452
|
.linearRampToValueAtTime(0, stopTime);
|
|
@@ -1335,35 +1456,27 @@ class MidyGM2 {
|
|
|
1335
1456
|
}, stopTime);
|
|
1336
1457
|
return new Promise((resolve) => {
|
|
1337
1458
|
note.bufferSource.onended = () => {
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
note.filterNode.disconnect();
|
|
1341
|
-
note.volumeEnvelopeNode.disconnect();
|
|
1342
|
-
note.volumeNode.disconnect();
|
|
1343
|
-
note.gainL.disconnect();
|
|
1344
|
-
note.gainR.disconnect();
|
|
1345
|
-
if (note.modulationDepth) {
|
|
1346
|
-
note.volumeDepth.disconnect();
|
|
1347
|
-
note.modulationDepth.disconnect();
|
|
1348
|
-
note.modulationLFO.stop();
|
|
1349
|
-
}
|
|
1350
|
-
if (note.vibratoDepth) {
|
|
1351
|
-
note.vibratoDepth.disconnect();
|
|
1352
|
-
note.vibratoLFO.stop();
|
|
1353
|
-
}
|
|
1354
|
-
if (note.reverbEffectsSend) {
|
|
1355
|
-
note.reverbEffectsSend.disconnect();
|
|
1356
|
-
}
|
|
1357
|
-
if (note.chorusEffectsSend) {
|
|
1358
|
-
note.chorusEffectsSend.disconnect();
|
|
1359
|
-
}
|
|
1459
|
+
noteList[index] = undefined;
|
|
1460
|
+
this.disconnectNote(note);
|
|
1360
1461
|
resolve();
|
|
1361
1462
|
};
|
|
1362
1463
|
note.bufferSource.stop(stopTime);
|
|
1363
1464
|
});
|
|
1364
1465
|
}
|
|
1466
|
+
findNoteOffTarget(noteList) {
|
|
1467
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1468
|
+
const note = noteList[i];
|
|
1469
|
+
if (!note)
|
|
1470
|
+
continue;
|
|
1471
|
+
if (note.ending)
|
|
1472
|
+
continue;
|
|
1473
|
+
return [note, i];
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1365
1476
|
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1366
1477
|
const channel = this.channels[channelNumber];
|
|
1478
|
+
if (this.isDrumNoteOffException(channel, noteNumber))
|
|
1479
|
+
return;
|
|
1367
1480
|
const state = channel.state;
|
|
1368
1481
|
if (!force) {
|
|
1369
1482
|
if (0.5 <= state.sustainPedal)
|
|
@@ -1371,34 +1484,32 @@ class MidyGM2 {
|
|
|
1371
1484
|
if (channel.sostenutoNotes.has(noteNumber))
|
|
1372
1485
|
return;
|
|
1373
1486
|
}
|
|
1374
|
-
|
|
1487
|
+
const noteList = channel.scheduledNotes.get(noteNumber);
|
|
1488
|
+
if (!noteList)
|
|
1489
|
+
return; // be careful with drum channel
|
|
1490
|
+
const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
|
|
1491
|
+
if (!noteOffTarget)
|
|
1375
1492
|
return;
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1397
|
-
note.bufferSource.playbackRate
|
|
1398
|
-
.cancelScheduledValues(endTime)
|
|
1399
|
-
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1400
|
-
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1401
|
-
}
|
|
1493
|
+
const [note, i] = noteOffTarget;
|
|
1494
|
+
if (0.5 <= state.portamento && portamentoNoteNumber !== undefined) {
|
|
1495
|
+
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1496
|
+
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1497
|
+
const baseRate = note.voiceParams.playbackRate;
|
|
1498
|
+
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1499
|
+
note.bufferSource.playbackRate
|
|
1500
|
+
.cancelScheduledValues(endTime)
|
|
1501
|
+
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1502
|
+
return this.stopNote(endTime, portamentoTime, noteList, i);
|
|
1503
|
+
}
|
|
1504
|
+
else {
|
|
1505
|
+
const volRelease = endTime +
|
|
1506
|
+
note.voiceParams.volRelease * state.releaseTime * 2;
|
|
1507
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1508
|
+
note.filterNode.frequency
|
|
1509
|
+
.cancelScheduledValues(endTime)
|
|
1510
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1511
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1512
|
+
return this.stopNote(endTime, stopTime, noteList, i);
|
|
1402
1513
|
}
|
|
1403
1514
|
}
|
|
1404
1515
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
@@ -1449,10 +1560,10 @@ class MidyGM2 {
|
|
|
1449
1560
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1450
1561
|
}
|
|
1451
1562
|
}
|
|
1452
|
-
handleProgramChange(channelNumber,
|
|
1563
|
+
handleProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1453
1564
|
const channel = this.channels[channelNumber];
|
|
1454
1565
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1455
|
-
channel.
|
|
1566
|
+
channel.programNumber = programNumber;
|
|
1456
1567
|
if (this.mode === "GM2") {
|
|
1457
1568
|
switch (channel.bankMSB) {
|
|
1458
1569
|
case 120:
|
|
@@ -1479,7 +1590,7 @@ class MidyGM2 {
|
|
|
1479
1590
|
this.getActiveNotes(channel, scheduleTime).forEach((note) => {
|
|
1480
1591
|
this.setControllerParameters(channel, note, table);
|
|
1481
1592
|
});
|
|
1482
|
-
|
|
1593
|
+
this.applyVoiceParams(channel, 13);
|
|
1483
1594
|
}
|
|
1484
1595
|
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
1485
1596
|
const pitchBend = msb * 128 + lsb;
|
|
@@ -1656,6 +1767,7 @@ class MidyGM2 {
|
|
|
1656
1767
|
state.set(channel.state.array);
|
|
1657
1768
|
state[2] = velocity / 127;
|
|
1658
1769
|
state[3] = noteNumber / 127;
|
|
1770
|
+
state[13] = state.channelPressure / 127;
|
|
1659
1771
|
return state;
|
|
1660
1772
|
}
|
|
1661
1773
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
@@ -1682,7 +1794,7 @@ class MidyGM2 {
|
|
|
1682
1794
|
if (key in voiceParams)
|
|
1683
1795
|
noteVoiceParams[key] = voiceParams[key];
|
|
1684
1796
|
}
|
|
1685
|
-
if (note.portamento) {
|
|
1797
|
+
if (0.5 <= channel.state.portamento && note.portamento) {
|
|
1686
1798
|
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1687
1799
|
}
|
|
1688
1800
|
else {
|
|
@@ -1879,10 +1991,11 @@ class MidyGM2 {
|
|
|
1879
1991
|
const channel = this.channels[channelNumber];
|
|
1880
1992
|
if (channel.isDrum)
|
|
1881
1993
|
return;
|
|
1994
|
+
const state = channel.state;
|
|
1882
1995
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1883
|
-
|
|
1996
|
+
state.softPedal = softPedal / 127;
|
|
1884
1997
|
this.processScheduledNotes(channel, (note) => {
|
|
1885
|
-
if (note.portamento) {
|
|
1998
|
+
if (0.5 <= state.portamento && note.portamento) {
|
|
1886
1999
|
this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
|
|
1887
2000
|
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1888
2001
|
}
|
|
@@ -2081,18 +2194,32 @@ class MidyGM2 {
|
|
|
2081
2194
|
}
|
|
2082
2195
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2083
2196
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2084
|
-
return this.
|
|
2197
|
+
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
2085
2198
|
}
|
|
2199
|
+
resetAllStates(channelNumber) {
|
|
2200
|
+
const channel = this.channels[channelNumber];
|
|
2201
|
+
const state = channel.state;
|
|
2202
|
+
for (const type of Object.keys(defaultControllerState)) {
|
|
2203
|
+
state[type] = defaultControllerState[type].defaultValue;
|
|
2204
|
+
}
|
|
2205
|
+
for (const type of Object.keys(this.constructor.channelSettings)) {
|
|
2206
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
2207
|
+
}
|
|
2208
|
+
this.mode = "GM2";
|
|
2209
|
+
this.masterFineTuning = 0; // cb
|
|
2210
|
+
this.masterCoarseTuning = 0; // cb
|
|
2211
|
+
}
|
|
2212
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2086
2213
|
resetAllControllers(channelNumber) {
|
|
2087
2214
|
const stateTypes = [
|
|
2215
|
+
"channelPressure",
|
|
2216
|
+
"pitchWheel",
|
|
2088
2217
|
"expression",
|
|
2089
2218
|
"modulationDepth",
|
|
2090
2219
|
"sustainPedal",
|
|
2091
2220
|
"portamento",
|
|
2092
2221
|
"sostenutoPedal",
|
|
2093
2222
|
"softPedal",
|
|
2094
|
-
"channelPressure",
|
|
2095
|
-
"pitchWheelSensitivity",
|
|
2096
2223
|
];
|
|
2097
2224
|
const channel = this.channels[channelNumber];
|
|
2098
2225
|
const state = channel.state;
|
|
@@ -2111,7 +2238,7 @@ class MidyGM2 {
|
|
|
2111
2238
|
}
|
|
2112
2239
|
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
2113
2240
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2114
|
-
return this.
|
|
2241
|
+
return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
|
|
2115
2242
|
}
|
|
2116
2243
|
omniOff(channelNumber, value, scheduleTime) {
|
|
2117
2244
|
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
@@ -2457,7 +2584,7 @@ class MidyGM2 {
|
|
|
2457
2584
|
return value * 0.00787;
|
|
2458
2585
|
}
|
|
2459
2586
|
getChannelBitmap(data) {
|
|
2460
|
-
const bitmap = new Array(
|
|
2587
|
+
const bitmap = new Array(this.channels.length).fill(false);
|
|
2461
2588
|
const ff = data[4] & 0b11;
|
|
2462
2589
|
const gg = data[5] & 0x7F;
|
|
2463
2590
|
const hh = data[6] & 0x7F;
|
|
@@ -2599,6 +2726,7 @@ class MidyGM2 {
|
|
|
2599
2726
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2600
2727
|
}
|
|
2601
2728
|
}
|
|
2729
|
+
// https://github.com/marmooo/js-timer-benchmark
|
|
2602
2730
|
scheduleTask(callback, scheduleTime) {
|
|
2603
2731
|
return new Promise((resolve) => {
|
|
2604
2732
|
const bufferSource = new AudioBufferSourceNode(this.audioContext, {
|
|
@@ -2624,10 +2752,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2624
2752
|
configurable: true,
|
|
2625
2753
|
writable: true,
|
|
2626
2754
|
value: {
|
|
2627
|
-
currentBufferSource: null,
|
|
2628
|
-
isDrum: false,
|
|
2629
2755
|
detune: 0,
|
|
2630
|
-
|
|
2756
|
+
programNumber: 0,
|
|
2631
2757
|
bank: 121 * 128,
|
|
2632
2758
|
bankMSB: 121,
|
|
2633
2759
|
bankLSB: 0,
|