@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/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 },
|
|
@@ -245,18 +278,6 @@ export class MidyGM2 {
|
|
|
245
278
|
writable: true,
|
|
246
279
|
value: "GM2"
|
|
247
280
|
});
|
|
248
|
-
Object.defineProperty(this, "ticksPerBeat", {
|
|
249
|
-
enumerable: true,
|
|
250
|
-
configurable: true,
|
|
251
|
-
writable: true,
|
|
252
|
-
value: 120
|
|
253
|
-
});
|
|
254
|
-
Object.defineProperty(this, "totalTime", {
|
|
255
|
-
enumerable: true,
|
|
256
|
-
configurable: true,
|
|
257
|
-
writable: true,
|
|
258
|
-
value: 0
|
|
259
|
-
});
|
|
260
281
|
Object.defineProperty(this, "masterFineTuning", {
|
|
261
282
|
enumerable: true,
|
|
262
283
|
configurable: true,
|
|
@@ -290,6 +311,24 @@ export class MidyGM2 {
|
|
|
290
311
|
delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
|
|
291
312
|
}
|
|
292
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
|
+
});
|
|
293
332
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
294
333
|
enumerable: true,
|
|
295
334
|
configurable: true,
|
|
@@ -392,11 +431,17 @@ export class MidyGM2 {
|
|
|
392
431
|
writable: true,
|
|
393
432
|
value: []
|
|
394
433
|
});
|
|
395
|
-
Object.defineProperty(this, "
|
|
434
|
+
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
396
435
|
enumerable: true,
|
|
397
436
|
configurable: true,
|
|
398
437
|
writable: true,
|
|
399
|
-
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)
|
|
400
445
|
});
|
|
401
446
|
Object.defineProperty(this, "defaultOptions", {
|
|
402
447
|
enumerable: true,
|
|
@@ -490,8 +535,10 @@ export class MidyGM2 {
|
|
|
490
535
|
};
|
|
491
536
|
}
|
|
492
537
|
createChannels(audioContext) {
|
|
493
|
-
const channels = Array.from({ length:
|
|
538
|
+
const channels = Array.from({ length: this.numChannels }, () => {
|
|
494
539
|
return {
|
|
540
|
+
currentBufferSource: null,
|
|
541
|
+
isDrum: false,
|
|
495
542
|
...this.constructor.channelSettings,
|
|
496
543
|
state: new ControllerState(),
|
|
497
544
|
controlTable: this.initControlTable(),
|
|
@@ -535,24 +582,10 @@ export class MidyGM2 {
|
|
|
535
582
|
return audioBuffer;
|
|
536
583
|
}
|
|
537
584
|
}
|
|
538
|
-
|
|
539
|
-
if (channel.isDrum) {
|
|
540
|
-
const noteNumber = note.noteNumber;
|
|
541
|
-
if (noteNumber === 88 || 47 <= noteNumber && noteNumber <= 84) {
|
|
542
|
-
return true;
|
|
543
|
-
}
|
|
544
|
-
else {
|
|
545
|
-
return false;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
else {
|
|
549
|
-
return voiceParams.sampleModes % 2 !== 0;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
createBufferSource(channel, note, voiceParams, audioBuffer) {
|
|
585
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
553
586
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
554
587
|
bufferSource.buffer = audioBuffer;
|
|
555
|
-
bufferSource.loop =
|
|
588
|
+
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
556
589
|
if (bufferSource.loop) {
|
|
557
590
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
558
591
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -585,7 +618,7 @@ export class MidyGM2 {
|
|
|
585
618
|
const startTime = event.startTime + this.startDelay - offset;
|
|
586
619
|
switch (event.type) {
|
|
587
620
|
case "noteOn":
|
|
588
|
-
if (event.velocity
|
|
621
|
+
if (0 < event.velocity) {
|
|
589
622
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
|
|
590
623
|
break;
|
|
591
624
|
}
|
|
@@ -640,7 +673,8 @@ export class MidyGM2 {
|
|
|
640
673
|
if (queueIndex >= this.timeline.length) {
|
|
641
674
|
await Promise.all(this.notePromises);
|
|
642
675
|
this.notePromises = [];
|
|
643
|
-
this.
|
|
676
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
677
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
644
678
|
this.audioBufferCache.clear();
|
|
645
679
|
resolve();
|
|
646
680
|
return;
|
|
@@ -659,7 +693,8 @@ export class MidyGM2 {
|
|
|
659
693
|
else if (this.isStopping) {
|
|
660
694
|
await this.stopNotes(0, true, now);
|
|
661
695
|
this.notePromises = [];
|
|
662
|
-
this.
|
|
696
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
697
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
663
698
|
this.audioBufferCache.clear();
|
|
664
699
|
resolve();
|
|
665
700
|
this.isStopping = false;
|
|
@@ -668,7 +703,8 @@ export class MidyGM2 {
|
|
|
668
703
|
}
|
|
669
704
|
else if (this.isSeeking) {
|
|
670
705
|
this.stopNotes(0, true, now);
|
|
671
|
-
this.
|
|
706
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
707
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
672
708
|
this.startTime = this.audioContext.currentTime;
|
|
673
709
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
674
710
|
offset = this.resumeTime - this.startTime;
|
|
@@ -696,7 +732,7 @@ export class MidyGM2 {
|
|
|
696
732
|
extractMidiData(midi) {
|
|
697
733
|
const instruments = new Set();
|
|
698
734
|
const timeline = [];
|
|
699
|
-
const tmpChannels = new Array(
|
|
735
|
+
const tmpChannels = new Array(this.channels.length);
|
|
700
736
|
for (let i = 0; i < tmpChannels.length; i++) {
|
|
701
737
|
tmpChannels[i] = {
|
|
702
738
|
programNumber: -1,
|
|
@@ -795,6 +831,17 @@ export class MidyGM2 {
|
|
|
795
831
|
}
|
|
796
832
|
return { instruments, timeline };
|
|
797
833
|
}
|
|
834
|
+
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
835
|
+
const channel = this.channels[channelNumber];
|
|
836
|
+
const promises = [];
|
|
837
|
+
const activeNotes = this.getActiveNotes(channel, scheduleTime);
|
|
838
|
+
activeNotes.forEach((note) => {
|
|
839
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
840
|
+
this.notePromises.push(promise);
|
|
841
|
+
promises.push(promise);
|
|
842
|
+
});
|
|
843
|
+
return Promise.all(promises);
|
|
844
|
+
}
|
|
798
845
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
799
846
|
const channel = this.channels[channelNumber];
|
|
800
847
|
const promises = [];
|
|
@@ -824,6 +871,9 @@ export class MidyGM2 {
|
|
|
824
871
|
if (!this.isPlaying)
|
|
825
872
|
return;
|
|
826
873
|
this.isStopping = true;
|
|
874
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
875
|
+
this.resetAllStates(i);
|
|
876
|
+
}
|
|
827
877
|
}
|
|
828
878
|
pause() {
|
|
829
879
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -863,6 +913,8 @@ export class MidyGM2 {
|
|
|
863
913
|
const note = noteList[i];
|
|
864
914
|
if (!note)
|
|
865
915
|
continue;
|
|
916
|
+
if (note.ending)
|
|
917
|
+
continue;
|
|
866
918
|
callback(note);
|
|
867
919
|
}
|
|
868
920
|
});
|
|
@@ -1194,8 +1246,8 @@ export class MidyGM2 {
|
|
|
1194
1246
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1195
1247
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1196
1248
|
}
|
|
1197
|
-
async getAudioBuffer(
|
|
1198
|
-
const audioBufferId = this.getAudioBufferId(
|
|
1249
|
+
async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
|
|
1250
|
+
const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
|
|
1199
1251
|
const cache = this.audioBufferCache.get(audioBufferId);
|
|
1200
1252
|
if (cache) {
|
|
1201
1253
|
cache.counter += 1;
|
|
@@ -1218,8 +1270,8 @@ export class MidyGM2 {
|
|
|
1218
1270
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1219
1271
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1220
1272
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1221
|
-
const audioBuffer = await this.getAudioBuffer(channel.
|
|
1222
|
-
note.bufferSource = this.createBufferSource(
|
|
1273
|
+
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1274
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
1223
1275
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1224
1276
|
note.gainL = new GainNode(this.audioContext);
|
|
1225
1277
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1228,7 +1280,7 @@ export class MidyGM2 {
|
|
|
1228
1280
|
type: "lowpass",
|
|
1229
1281
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
1230
1282
|
});
|
|
1231
|
-
if (portamento) {
|
|
1283
|
+
if (0.5 <= state.portamento && portamento) {
|
|
1232
1284
|
note.portamento = true;
|
|
1233
1285
|
this.setPortamentoStartVolumeEnvelope(channel, note, now);
|
|
1234
1286
|
this.setPortamentoStartFilterEnvelope(channel, note, now);
|
|
@@ -1279,14 +1331,56 @@ export class MidyGM2 {
|
|
|
1279
1331
|
return channel.bank;
|
|
1280
1332
|
}
|
|
1281
1333
|
}
|
|
1334
|
+
handleExclusiveClass(note, channelNumber, startTime) {
|
|
1335
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1336
|
+
if (exclusiveClass === 0)
|
|
1337
|
+
return;
|
|
1338
|
+
const prev = this.exclusiveClassNotes[exclusiveClass];
|
|
1339
|
+
if (prev) {
|
|
1340
|
+
const [prevNote, prevChannelNumber] = prev;
|
|
1341
|
+
if (prevNote && !prevNote.ending) {
|
|
1342
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1343
|
+
startTime, true, // force
|
|
1344
|
+
undefined);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
1348
|
+
}
|
|
1349
|
+
handleDrumExclusiveClass(note, channelNumber, startTime) {
|
|
1350
|
+
const channel = this.channels[channelNumber];
|
|
1351
|
+
if (!channel.isDrum)
|
|
1352
|
+
return;
|
|
1353
|
+
const kitTable = drumExclusiveClassesByKit[channel.programNumber];
|
|
1354
|
+
if (!kitTable)
|
|
1355
|
+
return;
|
|
1356
|
+
const drumExclusiveClass = kitTable[note.noteNumber];
|
|
1357
|
+
if (drumExclusiveClass === 0)
|
|
1358
|
+
return;
|
|
1359
|
+
const index = (drumExclusiveClass - 1) * this.channels.length +
|
|
1360
|
+
channelNumber;
|
|
1361
|
+
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1362
|
+
if (prevNote && !prevNote.ending) {
|
|
1363
|
+
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1364
|
+
startTime, true, // force
|
|
1365
|
+
undefined);
|
|
1366
|
+
}
|
|
1367
|
+
this.drumExclusiveClassNotes[index] = note;
|
|
1368
|
+
}
|
|
1369
|
+
isDrumNoteOffException(channel, noteNumber) {
|
|
1370
|
+
if (!channel.isDrum)
|
|
1371
|
+
return false;
|
|
1372
|
+
const programNumber = channel.programNumber;
|
|
1373
|
+
return !((programNumber === 48 && noteNumber === 88) ||
|
|
1374
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
1375
|
+
}
|
|
1282
1376
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
|
|
1283
1377
|
const channel = this.channels[channelNumber];
|
|
1284
1378
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1285
|
-
const soundFontIndex = this.soundFontTable[channel.
|
|
1379
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
1286
1380
|
if (soundFontIndex === undefined)
|
|
1287
1381
|
return;
|
|
1288
1382
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1289
|
-
const voice = soundFont.getVoice(bankNumber, channel.
|
|
1383
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1290
1384
|
if (!voice)
|
|
1291
1385
|
return;
|
|
1292
1386
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
@@ -1296,33 +1390,60 @@ export class MidyGM2 {
|
|
|
1296
1390
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1297
1391
|
channel.sustainNotes.push(note);
|
|
1298
1392
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1302
|
-
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1303
|
-
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1304
|
-
if (prevNote && !prevNote.ending) {
|
|
1305
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1306
|
-
startTime, true, // force
|
|
1307
|
-
undefined);
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
1311
|
-
}
|
|
1393
|
+
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1394
|
+
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1312
1395
|
const scheduledNotes = channel.scheduledNotes;
|
|
1313
|
-
|
|
1314
|
-
|
|
1396
|
+
let noteList = scheduledNotes.get(noteNumber);
|
|
1397
|
+
if (noteList) {
|
|
1398
|
+
noteList.push(note);
|
|
1315
1399
|
}
|
|
1316
1400
|
else {
|
|
1317
|
-
|
|
1401
|
+
noteList = [note];
|
|
1402
|
+
scheduledNotes.set(noteNumber, noteList);
|
|
1403
|
+
}
|
|
1404
|
+
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1405
|
+
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1406
|
+
const index = noteList.length - 1;
|
|
1407
|
+
const promise = new Promise((resolve) => {
|
|
1408
|
+
note.bufferSource.onended = () => {
|
|
1409
|
+
noteList[index] = undefined;
|
|
1410
|
+
this.disconnectNote(note);
|
|
1411
|
+
resolve();
|
|
1412
|
+
};
|
|
1413
|
+
note.bufferSource.stop(stopTime);
|
|
1414
|
+
});
|
|
1415
|
+
this.notePromises.push(promise);
|
|
1318
1416
|
}
|
|
1319
1417
|
}
|
|
1320
1418
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1321
1419
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1322
1420
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1323
1421
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1422
|
+
disconnectNote(note) {
|
|
1423
|
+
note.bufferSource.disconnect();
|
|
1424
|
+
note.filterNode.disconnect();
|
|
1425
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1426
|
+
note.volumeNode.disconnect();
|
|
1427
|
+
note.gainL.disconnect();
|
|
1428
|
+
note.gainR.disconnect();
|
|
1429
|
+
if (note.modulationDepth) {
|
|
1430
|
+
note.volumeDepth.disconnect();
|
|
1431
|
+
note.modulationDepth.disconnect();
|
|
1432
|
+
note.modulationLFO.stop();
|
|
1433
|
+
}
|
|
1434
|
+
if (note.vibratoDepth) {
|
|
1435
|
+
note.vibratoDepth.disconnect();
|
|
1436
|
+
note.vibratoLFO.stop();
|
|
1437
|
+
}
|
|
1438
|
+
if (note.reverbEffectsSend) {
|
|
1439
|
+
note.reverbEffectsSend.disconnect();
|
|
1440
|
+
}
|
|
1441
|
+
if (note.chorusEffectsSend) {
|
|
1442
|
+
note.chorusEffectsSend.disconnect();
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
stopNote(endTime, stopTime, noteList, index) {
|
|
1446
|
+
const note = noteList[index];
|
|
1326
1447
|
note.volumeEnvelopeNode.gain
|
|
1327
1448
|
.cancelScheduledValues(endTime)
|
|
1328
1449
|
.linearRampToValueAtTime(0, stopTime);
|
|
@@ -1332,35 +1453,27 @@ export class MidyGM2 {
|
|
|
1332
1453
|
}, stopTime);
|
|
1333
1454
|
return new Promise((resolve) => {
|
|
1334
1455
|
note.bufferSource.onended = () => {
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
note.filterNode.disconnect();
|
|
1338
|
-
note.volumeEnvelopeNode.disconnect();
|
|
1339
|
-
note.volumeNode.disconnect();
|
|
1340
|
-
note.gainL.disconnect();
|
|
1341
|
-
note.gainR.disconnect();
|
|
1342
|
-
if (note.modulationDepth) {
|
|
1343
|
-
note.volumeDepth.disconnect();
|
|
1344
|
-
note.modulationDepth.disconnect();
|
|
1345
|
-
note.modulationLFO.stop();
|
|
1346
|
-
}
|
|
1347
|
-
if (note.vibratoDepth) {
|
|
1348
|
-
note.vibratoDepth.disconnect();
|
|
1349
|
-
note.vibratoLFO.stop();
|
|
1350
|
-
}
|
|
1351
|
-
if (note.reverbEffectsSend) {
|
|
1352
|
-
note.reverbEffectsSend.disconnect();
|
|
1353
|
-
}
|
|
1354
|
-
if (note.chorusEffectsSend) {
|
|
1355
|
-
note.chorusEffectsSend.disconnect();
|
|
1356
|
-
}
|
|
1456
|
+
noteList[index] = undefined;
|
|
1457
|
+
this.disconnectNote(note);
|
|
1357
1458
|
resolve();
|
|
1358
1459
|
};
|
|
1359
1460
|
note.bufferSource.stop(stopTime);
|
|
1360
1461
|
});
|
|
1361
1462
|
}
|
|
1463
|
+
findNoteOffTarget(noteList) {
|
|
1464
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1465
|
+
const note = noteList[i];
|
|
1466
|
+
if (!note)
|
|
1467
|
+
continue;
|
|
1468
|
+
if (note.ending)
|
|
1469
|
+
continue;
|
|
1470
|
+
return [note, i];
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1362
1473
|
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1363
1474
|
const channel = this.channels[channelNumber];
|
|
1475
|
+
if (this.isDrumNoteOffException(channel, noteNumber))
|
|
1476
|
+
return;
|
|
1364
1477
|
const state = channel.state;
|
|
1365
1478
|
if (!force) {
|
|
1366
1479
|
if (0.5 <= state.sustainPedal)
|
|
@@ -1368,34 +1481,32 @@ export class MidyGM2 {
|
|
|
1368
1481
|
if (channel.sostenutoNotes.has(noteNumber))
|
|
1369
1482
|
return;
|
|
1370
1483
|
}
|
|
1371
|
-
|
|
1484
|
+
const noteList = channel.scheduledNotes.get(noteNumber);
|
|
1485
|
+
if (!noteList)
|
|
1486
|
+
return; // be careful with drum channel
|
|
1487
|
+
const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
|
|
1488
|
+
if (!noteOffTarget)
|
|
1372
1489
|
return;
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
const
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1394
|
-
note.bufferSource.playbackRate
|
|
1395
|
-
.cancelScheduledValues(endTime)
|
|
1396
|
-
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1397
|
-
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1398
|
-
}
|
|
1490
|
+
const [note, i] = noteOffTarget;
|
|
1491
|
+
if (0.5 <= state.portamento && portamentoNoteNumber !== undefined) {
|
|
1492
|
+
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1493
|
+
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1494
|
+
const baseRate = note.voiceParams.playbackRate;
|
|
1495
|
+
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1496
|
+
note.bufferSource.playbackRate
|
|
1497
|
+
.cancelScheduledValues(endTime)
|
|
1498
|
+
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1499
|
+
return this.stopNote(endTime, portamentoTime, noteList, i);
|
|
1500
|
+
}
|
|
1501
|
+
else {
|
|
1502
|
+
const volRelease = endTime +
|
|
1503
|
+
note.voiceParams.volRelease * state.releaseTime * 2;
|
|
1504
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1505
|
+
note.filterNode.frequency
|
|
1506
|
+
.cancelScheduledValues(endTime)
|
|
1507
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1508
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1509
|
+
return this.stopNote(endTime, stopTime, noteList, i);
|
|
1399
1510
|
}
|
|
1400
1511
|
}
|
|
1401
1512
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
@@ -1446,10 +1557,10 @@ export class MidyGM2 {
|
|
|
1446
1557
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1447
1558
|
}
|
|
1448
1559
|
}
|
|
1449
|
-
handleProgramChange(channelNumber,
|
|
1560
|
+
handleProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1450
1561
|
const channel = this.channels[channelNumber];
|
|
1451
1562
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1452
|
-
channel.
|
|
1563
|
+
channel.programNumber = programNumber;
|
|
1453
1564
|
if (this.mode === "GM2") {
|
|
1454
1565
|
switch (channel.bankMSB) {
|
|
1455
1566
|
case 120:
|
|
@@ -1476,7 +1587,7 @@ export class MidyGM2 {
|
|
|
1476
1587
|
this.getActiveNotes(channel, scheduleTime).forEach((note) => {
|
|
1477
1588
|
this.setControllerParameters(channel, note, table);
|
|
1478
1589
|
});
|
|
1479
|
-
|
|
1590
|
+
this.applyVoiceParams(channel, 13);
|
|
1480
1591
|
}
|
|
1481
1592
|
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
1482
1593
|
const pitchBend = msb * 128 + lsb;
|
|
@@ -1653,6 +1764,7 @@ export class MidyGM2 {
|
|
|
1653
1764
|
state.set(channel.state.array);
|
|
1654
1765
|
state[2] = velocity / 127;
|
|
1655
1766
|
state[3] = noteNumber / 127;
|
|
1767
|
+
state[13] = state.channelPressure / 127;
|
|
1656
1768
|
return state;
|
|
1657
1769
|
}
|
|
1658
1770
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
@@ -1679,7 +1791,7 @@ export class MidyGM2 {
|
|
|
1679
1791
|
if (key in voiceParams)
|
|
1680
1792
|
noteVoiceParams[key] = voiceParams[key];
|
|
1681
1793
|
}
|
|
1682
|
-
if (note.portamento) {
|
|
1794
|
+
if (0.5 <= channel.state.portamento && note.portamento) {
|
|
1683
1795
|
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1684
1796
|
}
|
|
1685
1797
|
else {
|
|
@@ -1876,10 +1988,11 @@ export class MidyGM2 {
|
|
|
1876
1988
|
const channel = this.channels[channelNumber];
|
|
1877
1989
|
if (channel.isDrum)
|
|
1878
1990
|
return;
|
|
1991
|
+
const state = channel.state;
|
|
1879
1992
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1880
|
-
|
|
1993
|
+
state.softPedal = softPedal / 127;
|
|
1881
1994
|
this.processScheduledNotes(channel, (note) => {
|
|
1882
|
-
if (note.portamento) {
|
|
1995
|
+
if (0.5 <= state.portamento && note.portamento) {
|
|
1883
1996
|
this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
|
|
1884
1997
|
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1885
1998
|
}
|
|
@@ -2078,18 +2191,32 @@ export class MidyGM2 {
|
|
|
2078
2191
|
}
|
|
2079
2192
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2080
2193
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2081
|
-
return this.
|
|
2194
|
+
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
2082
2195
|
}
|
|
2196
|
+
resetAllStates(channelNumber) {
|
|
2197
|
+
const channel = this.channels[channelNumber];
|
|
2198
|
+
const state = channel.state;
|
|
2199
|
+
for (const type of Object.keys(defaultControllerState)) {
|
|
2200
|
+
state[type] = defaultControllerState[type].defaultValue;
|
|
2201
|
+
}
|
|
2202
|
+
for (const type of Object.keys(this.constructor.channelSettings)) {
|
|
2203
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
2204
|
+
}
|
|
2205
|
+
this.mode = "GM2";
|
|
2206
|
+
this.masterFineTuning = 0; // cb
|
|
2207
|
+
this.masterCoarseTuning = 0; // cb
|
|
2208
|
+
}
|
|
2209
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2083
2210
|
resetAllControllers(channelNumber) {
|
|
2084
2211
|
const stateTypes = [
|
|
2212
|
+
"channelPressure",
|
|
2213
|
+
"pitchWheel",
|
|
2085
2214
|
"expression",
|
|
2086
2215
|
"modulationDepth",
|
|
2087
2216
|
"sustainPedal",
|
|
2088
2217
|
"portamento",
|
|
2089
2218
|
"sostenutoPedal",
|
|
2090
2219
|
"softPedal",
|
|
2091
|
-
"channelPressure",
|
|
2092
|
-
"pitchWheelSensitivity",
|
|
2093
2220
|
];
|
|
2094
2221
|
const channel = this.channels[channelNumber];
|
|
2095
2222
|
const state = channel.state;
|
|
@@ -2108,7 +2235,7 @@ export class MidyGM2 {
|
|
|
2108
2235
|
}
|
|
2109
2236
|
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
2110
2237
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2111
|
-
return this.
|
|
2238
|
+
return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
|
|
2112
2239
|
}
|
|
2113
2240
|
omniOff(channelNumber, value, scheduleTime) {
|
|
2114
2241
|
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
@@ -2454,7 +2581,7 @@ export class MidyGM2 {
|
|
|
2454
2581
|
return value * 0.00787;
|
|
2455
2582
|
}
|
|
2456
2583
|
getChannelBitmap(data) {
|
|
2457
|
-
const bitmap = new Array(
|
|
2584
|
+
const bitmap = new Array(this.channels.length).fill(false);
|
|
2458
2585
|
const ff = data[4] & 0b11;
|
|
2459
2586
|
const gg = data[5] & 0x7F;
|
|
2460
2587
|
const hh = data[6] & 0x7F;
|
|
@@ -2596,6 +2723,7 @@ export class MidyGM2 {
|
|
|
2596
2723
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2597
2724
|
}
|
|
2598
2725
|
}
|
|
2726
|
+
// https://github.com/marmooo/js-timer-benchmark
|
|
2599
2727
|
scheduleTask(callback, scheduleTime) {
|
|
2600
2728
|
return new Promise((resolve) => {
|
|
2601
2729
|
const bufferSource = new AudioBufferSourceNode(this.audioContext, {
|
|
@@ -2620,10 +2748,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2620
2748
|
configurable: true,
|
|
2621
2749
|
writable: true,
|
|
2622
2750
|
value: {
|
|
2623
|
-
currentBufferSource: null,
|
|
2624
|
-
isDrum: false,
|
|
2625
2751
|
detune: 0,
|
|
2626
|
-
|
|
2752
|
+
programNumber: 0,
|
|
2627
2753
|
bank: 121 * 128,
|
|
2628
2754
|
bankMSB: 121,
|
|
2629
2755
|
bankLSB: 0,
|