@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.js
CHANGED
|
@@ -160,6 +160,39 @@ class Note {
|
|
|
160
160
|
this.voiceParams = voiceParams;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
|
+
const drumExclusiveClassesByKit = new Array(57);
|
|
164
|
+
const drumExclusiveClassCount = 10;
|
|
165
|
+
const standardSet = new Uint8Array(128);
|
|
166
|
+
standardSet[42] = 1;
|
|
167
|
+
standardSet[44] = 1;
|
|
168
|
+
standardSet[46] = 1; // HH
|
|
169
|
+
standardSet[71] = 2;
|
|
170
|
+
standardSet[72] = 2; // Whistle
|
|
171
|
+
standardSet[73] = 3;
|
|
172
|
+
standardSet[74] = 3; // Guiro
|
|
173
|
+
standardSet[78] = 4;
|
|
174
|
+
standardSet[79] = 4; // Cuica
|
|
175
|
+
standardSet[80] = 5;
|
|
176
|
+
standardSet[81] = 5; // Triangle
|
|
177
|
+
standardSet[29] = 6;
|
|
178
|
+
standardSet[30] = 6; // Scratch
|
|
179
|
+
standardSet[86] = 7;
|
|
180
|
+
standardSet[87] = 7; // Surdo
|
|
181
|
+
drumExclusiveClassesByKit[0] = standardSet;
|
|
182
|
+
const analogSet = new Uint8Array(128);
|
|
183
|
+
analogSet[42] = 8;
|
|
184
|
+
analogSet[44] = 8;
|
|
185
|
+
analogSet[46] = 8; // CHH
|
|
186
|
+
drumExclusiveClassesByKit[25] = analogSet;
|
|
187
|
+
const orchestraSet = new Uint8Array(128);
|
|
188
|
+
orchestraSet[27] = 9;
|
|
189
|
+
orchestraSet[28] = 9;
|
|
190
|
+
orchestraSet[29] = 9; // HH
|
|
191
|
+
drumExclusiveClassesByKit[48] = orchestraSet;
|
|
192
|
+
const sfxSet = new Uint8Array(128);
|
|
193
|
+
sfxSet[41] = 10;
|
|
194
|
+
sfxSet[42] = 10; // Scratch
|
|
195
|
+
drumExclusiveClassesByKit[56] = sfxSet;
|
|
163
196
|
// normalized to 0-1 for use with the SF2 modulator model
|
|
164
197
|
const defaultControllerState = {
|
|
165
198
|
noteOnVelocity: { type: 2, defaultValue: 0 },
|
|
@@ -255,18 +288,6 @@ class Midy {
|
|
|
255
288
|
writable: true,
|
|
256
289
|
value: "GM2"
|
|
257
290
|
});
|
|
258
|
-
Object.defineProperty(this, "ticksPerBeat", {
|
|
259
|
-
enumerable: true,
|
|
260
|
-
configurable: true,
|
|
261
|
-
writable: true,
|
|
262
|
-
value: 120
|
|
263
|
-
});
|
|
264
|
-
Object.defineProperty(this, "totalTime", {
|
|
265
|
-
enumerable: true,
|
|
266
|
-
configurable: true,
|
|
267
|
-
writable: true,
|
|
268
|
-
value: 0
|
|
269
|
-
});
|
|
270
291
|
Object.defineProperty(this, "masterFineTuning", {
|
|
271
292
|
enumerable: true,
|
|
272
293
|
configurable: true,
|
|
@@ -300,6 +321,24 @@ class Midy {
|
|
|
300
321
|
delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
|
|
301
322
|
}
|
|
302
323
|
});
|
|
324
|
+
Object.defineProperty(this, "numChannels", {
|
|
325
|
+
enumerable: true,
|
|
326
|
+
configurable: true,
|
|
327
|
+
writable: true,
|
|
328
|
+
value: 16
|
|
329
|
+
});
|
|
330
|
+
Object.defineProperty(this, "ticksPerBeat", {
|
|
331
|
+
enumerable: true,
|
|
332
|
+
configurable: true,
|
|
333
|
+
writable: true,
|
|
334
|
+
value: 120
|
|
335
|
+
});
|
|
336
|
+
Object.defineProperty(this, "totalTime", {
|
|
337
|
+
enumerable: true,
|
|
338
|
+
configurable: true,
|
|
339
|
+
writable: true,
|
|
340
|
+
value: 0
|
|
341
|
+
});
|
|
303
342
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
304
343
|
enumerable: true,
|
|
305
344
|
configurable: true,
|
|
@@ -402,11 +441,17 @@ class Midy {
|
|
|
402
441
|
writable: true,
|
|
403
442
|
value: []
|
|
404
443
|
});
|
|
405
|
-
Object.defineProperty(this, "
|
|
444
|
+
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
406
445
|
enumerable: true,
|
|
407
446
|
configurable: true,
|
|
408
447
|
writable: true,
|
|
409
|
-
value: new
|
|
448
|
+
value: new Array(128)
|
|
449
|
+
});
|
|
450
|
+
Object.defineProperty(this, "drumExclusiveClassNotes", {
|
|
451
|
+
enumerable: true,
|
|
452
|
+
configurable: true,
|
|
453
|
+
writable: true,
|
|
454
|
+
value: new Array(this.numChannels * drumExclusiveClassCount)
|
|
410
455
|
});
|
|
411
456
|
Object.defineProperty(this, "defaultOptions", {
|
|
412
457
|
enumerable: true,
|
|
@@ -500,8 +545,10 @@ class Midy {
|
|
|
500
545
|
};
|
|
501
546
|
}
|
|
502
547
|
createChannels(audioContext) {
|
|
503
|
-
const channels = Array.from({ length:
|
|
548
|
+
const channels = Array.from({ length: this.numChannels }, () => {
|
|
504
549
|
return {
|
|
550
|
+
currentBufferSource: null,
|
|
551
|
+
isDrum: false,
|
|
505
552
|
...this.constructor.channelSettings,
|
|
506
553
|
state: new ControllerState(),
|
|
507
554
|
controlTable: this.initControlTable(),
|
|
@@ -546,24 +593,10 @@ class Midy {
|
|
|
546
593
|
return audioBuffer;
|
|
547
594
|
}
|
|
548
595
|
}
|
|
549
|
-
|
|
550
|
-
if (channel.isDrum) {
|
|
551
|
-
const noteNumber = note.noteNumber;
|
|
552
|
-
if (noteNumber === 88 || 47 <= noteNumber && noteNumber <= 84) {
|
|
553
|
-
return true;
|
|
554
|
-
}
|
|
555
|
-
else {
|
|
556
|
-
return false;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
else {
|
|
560
|
-
return voiceParams.sampleModes % 2 !== 0;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
createBufferSource(channel, note, voiceParams, audioBuffer) {
|
|
596
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
564
597
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
565
598
|
bufferSource.buffer = audioBuffer;
|
|
566
|
-
bufferSource.loop =
|
|
599
|
+
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
567
600
|
if (bufferSource.loop) {
|
|
568
601
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
569
602
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -596,7 +629,7 @@ class Midy {
|
|
|
596
629
|
const startTime = event.startTime + this.startDelay - offset;
|
|
597
630
|
switch (event.type) {
|
|
598
631
|
case "noteOn":
|
|
599
|
-
if (event.velocity
|
|
632
|
+
if (0 < event.velocity) {
|
|
600
633
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
|
|
601
634
|
break;
|
|
602
635
|
}
|
|
@@ -654,7 +687,8 @@ class Midy {
|
|
|
654
687
|
if (queueIndex >= this.timeline.length) {
|
|
655
688
|
await Promise.all(this.notePromises);
|
|
656
689
|
this.notePromises = [];
|
|
657
|
-
this.
|
|
690
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
691
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
658
692
|
this.audioBufferCache.clear();
|
|
659
693
|
resolve();
|
|
660
694
|
return;
|
|
@@ -673,7 +707,8 @@ class Midy {
|
|
|
673
707
|
else if (this.isStopping) {
|
|
674
708
|
await this.stopNotes(0, true, now);
|
|
675
709
|
this.notePromises = [];
|
|
676
|
-
this.
|
|
710
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
711
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
677
712
|
this.audioBufferCache.clear();
|
|
678
713
|
resolve();
|
|
679
714
|
this.isStopping = false;
|
|
@@ -682,7 +717,8 @@ class Midy {
|
|
|
682
717
|
}
|
|
683
718
|
else if (this.isSeeking) {
|
|
684
719
|
this.stopNotes(0, true, now);
|
|
685
|
-
this.
|
|
720
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
721
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
686
722
|
this.startTime = this.audioContext.currentTime;
|
|
687
723
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
688
724
|
offset = this.resumeTime - this.startTime;
|
|
@@ -710,7 +746,7 @@ class Midy {
|
|
|
710
746
|
extractMidiData(midi) {
|
|
711
747
|
const instruments = new Set();
|
|
712
748
|
const timeline = [];
|
|
713
|
-
const tmpChannels = new Array(
|
|
749
|
+
const tmpChannels = new Array(this.channels.length);
|
|
714
750
|
for (let i = 0; i < tmpChannels.length; i++) {
|
|
715
751
|
tmpChannels[i] = {
|
|
716
752
|
programNumber: -1,
|
|
@@ -809,6 +845,17 @@ class Midy {
|
|
|
809
845
|
}
|
|
810
846
|
return { instruments, timeline };
|
|
811
847
|
}
|
|
848
|
+
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
849
|
+
const channel = this.channels[channelNumber];
|
|
850
|
+
const promises = [];
|
|
851
|
+
const activeNotes = this.getActiveNotes(channel, scheduleTime);
|
|
852
|
+
activeNotes.forEach((note) => {
|
|
853
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
854
|
+
this.notePromises.push(promise);
|
|
855
|
+
promises.push(promise);
|
|
856
|
+
});
|
|
857
|
+
return Promise.all(promises);
|
|
858
|
+
}
|
|
812
859
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
813
860
|
const channel = this.channels[channelNumber];
|
|
814
861
|
const promises = [];
|
|
@@ -838,6 +885,9 @@ class Midy {
|
|
|
838
885
|
if (!this.isPlaying)
|
|
839
886
|
return;
|
|
840
887
|
this.isStopping = true;
|
|
888
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
889
|
+
this.resetAllStates(i);
|
|
890
|
+
}
|
|
841
891
|
}
|
|
842
892
|
pause() {
|
|
843
893
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -877,6 +927,8 @@ class Midy {
|
|
|
877
927
|
const note = noteList[i];
|
|
878
928
|
if (!note)
|
|
879
929
|
continue;
|
|
930
|
+
if (note.ending)
|
|
931
|
+
continue;
|
|
880
932
|
callback(note);
|
|
881
933
|
}
|
|
882
934
|
});
|
|
@@ -1212,8 +1264,8 @@ class Midy {
|
|
|
1212
1264
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1213
1265
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1214
1266
|
}
|
|
1215
|
-
async getAudioBuffer(
|
|
1216
|
-
const audioBufferId = this.getAudioBufferId(
|
|
1267
|
+
async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
|
|
1268
|
+
const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
|
|
1217
1269
|
const cache = this.audioBufferCache.get(audioBufferId);
|
|
1218
1270
|
if (cache) {
|
|
1219
1271
|
cache.counter += 1;
|
|
@@ -1236,8 +1288,8 @@ class Midy {
|
|
|
1236
1288
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1237
1289
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1238
1290
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1239
|
-
const audioBuffer = await this.getAudioBuffer(channel.
|
|
1240
|
-
note.bufferSource = this.createBufferSource(
|
|
1291
|
+
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1292
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
1241
1293
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1242
1294
|
note.gainL = new GainNode(this.audioContext);
|
|
1243
1295
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1246,7 +1298,7 @@ class Midy {
|
|
|
1246
1298
|
type: "lowpass",
|
|
1247
1299
|
Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
|
|
1248
1300
|
});
|
|
1249
|
-
if (portamento) {
|
|
1301
|
+
if (0.5 <= state.portamento && portamento) {
|
|
1250
1302
|
note.portamento = true;
|
|
1251
1303
|
this.setPortamentoStartVolumeEnvelope(channel, note, now);
|
|
1252
1304
|
this.setPortamentoStartFilterEnvelope(channel, note, now);
|
|
@@ -1297,14 +1349,56 @@ class Midy {
|
|
|
1297
1349
|
return channel.bank;
|
|
1298
1350
|
}
|
|
1299
1351
|
}
|
|
1352
|
+
handleExclusiveClass(note, channelNumber, startTime) {
|
|
1353
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1354
|
+
if (exclusiveClass === 0)
|
|
1355
|
+
return;
|
|
1356
|
+
const prev = this.exclusiveClassNotes[exclusiveClass];
|
|
1357
|
+
if (prev) {
|
|
1358
|
+
const [prevNote, prevChannelNumber] = prev;
|
|
1359
|
+
if (prevNote && !prevNote.ending) {
|
|
1360
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1361
|
+
startTime, true, // force
|
|
1362
|
+
undefined);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
1366
|
+
}
|
|
1367
|
+
handleDrumExclusiveClass(note, channelNumber, startTime) {
|
|
1368
|
+
const channel = this.channels[channelNumber];
|
|
1369
|
+
if (!channel.isDrum)
|
|
1370
|
+
return;
|
|
1371
|
+
const kitTable = drumExclusiveClassesByKit[channel.programNumber];
|
|
1372
|
+
if (!kitTable)
|
|
1373
|
+
return;
|
|
1374
|
+
const drumExclusiveClass = kitTable[note.noteNumber];
|
|
1375
|
+
if (drumExclusiveClass === 0)
|
|
1376
|
+
return;
|
|
1377
|
+
const index = (drumExclusiveClass - 1) * this.channels.length +
|
|
1378
|
+
channelNumber;
|
|
1379
|
+
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1380
|
+
if (prevNote && !prevNote.ending) {
|
|
1381
|
+
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1382
|
+
startTime, true, // force
|
|
1383
|
+
undefined);
|
|
1384
|
+
}
|
|
1385
|
+
this.drumExclusiveClassNotes[index] = note;
|
|
1386
|
+
}
|
|
1387
|
+
isDrumNoteOffException(channel, noteNumber) {
|
|
1388
|
+
if (!channel.isDrum)
|
|
1389
|
+
return false;
|
|
1390
|
+
const programNumber = channel.programNumber;
|
|
1391
|
+
return !((programNumber === 48 && noteNumber === 88) ||
|
|
1392
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
1393
|
+
}
|
|
1300
1394
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
|
|
1301
1395
|
const channel = this.channels[channelNumber];
|
|
1302
1396
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1303
|
-
const soundFontIndex = this.soundFontTable[channel.
|
|
1397
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
1304
1398
|
if (soundFontIndex === undefined)
|
|
1305
1399
|
return;
|
|
1306
1400
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1307
|
-
const voice = soundFont.getVoice(bankNumber, channel.
|
|
1401
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1308
1402
|
if (!voice)
|
|
1309
1403
|
return;
|
|
1310
1404
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
@@ -1314,33 +1408,60 @@ class Midy {
|
|
|
1314
1408
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1315
1409
|
channel.sustainNotes.push(note);
|
|
1316
1410
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1320
|
-
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1321
|
-
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1322
|
-
if (prevNote && !prevNote.ending) {
|
|
1323
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1324
|
-
startTime, true, // force
|
|
1325
|
-
undefined);
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
1329
|
-
}
|
|
1411
|
+
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1412
|
+
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1330
1413
|
const scheduledNotes = channel.scheduledNotes;
|
|
1331
|
-
|
|
1332
|
-
|
|
1414
|
+
let noteList = scheduledNotes.get(noteNumber);
|
|
1415
|
+
if (noteList) {
|
|
1416
|
+
noteList.push(note);
|
|
1333
1417
|
}
|
|
1334
1418
|
else {
|
|
1335
|
-
|
|
1419
|
+
noteList = [note];
|
|
1420
|
+
scheduledNotes.set(noteNumber, noteList);
|
|
1421
|
+
}
|
|
1422
|
+
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1423
|
+
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1424
|
+
const index = noteList.length - 1;
|
|
1425
|
+
const promise = new Promise((resolve) => {
|
|
1426
|
+
note.bufferSource.onended = () => {
|
|
1427
|
+
noteList[index] = undefined;
|
|
1428
|
+
this.disconnectNote(note);
|
|
1429
|
+
resolve();
|
|
1430
|
+
};
|
|
1431
|
+
note.bufferSource.stop(stopTime);
|
|
1432
|
+
});
|
|
1433
|
+
this.notePromises.push(promise);
|
|
1336
1434
|
}
|
|
1337
1435
|
}
|
|
1338
1436
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1339
1437
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1340
1438
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1341
1439
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1440
|
+
disconnectNote(note) {
|
|
1441
|
+
note.bufferSource.disconnect();
|
|
1442
|
+
note.filterNode.disconnect();
|
|
1443
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1444
|
+
note.volumeNode.disconnect();
|
|
1445
|
+
note.gainL.disconnect();
|
|
1446
|
+
note.gainR.disconnect();
|
|
1447
|
+
if (note.modulationDepth) {
|
|
1448
|
+
note.volumeDepth.disconnect();
|
|
1449
|
+
note.modulationDepth.disconnect();
|
|
1450
|
+
note.modulationLFO.stop();
|
|
1451
|
+
}
|
|
1452
|
+
if (note.vibratoDepth) {
|
|
1453
|
+
note.vibratoDepth.disconnect();
|
|
1454
|
+
note.vibratoLFO.stop();
|
|
1455
|
+
}
|
|
1456
|
+
if (note.reverbEffectsSend) {
|
|
1457
|
+
note.reverbEffectsSend.disconnect();
|
|
1458
|
+
}
|
|
1459
|
+
if (note.chorusEffectsSend) {
|
|
1460
|
+
note.chorusEffectsSend.disconnect();
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
stopNote(endTime, stopTime, noteList, index) {
|
|
1464
|
+
const note = noteList[index];
|
|
1344
1465
|
note.volumeEnvelopeNode.gain
|
|
1345
1466
|
.cancelScheduledValues(endTime)
|
|
1346
1467
|
.linearRampToValueAtTime(0, stopTime);
|
|
@@ -1350,35 +1471,27 @@ class Midy {
|
|
|
1350
1471
|
}, stopTime);
|
|
1351
1472
|
return new Promise((resolve) => {
|
|
1352
1473
|
note.bufferSource.onended = () => {
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
note.filterNode.disconnect();
|
|
1356
|
-
note.volumeEnvelopeNode.disconnect();
|
|
1357
|
-
note.volumeNode.disconnect();
|
|
1358
|
-
note.gainL.disconnect();
|
|
1359
|
-
note.gainR.disconnect();
|
|
1360
|
-
if (note.modulationDepth) {
|
|
1361
|
-
note.volumeDepth.disconnect();
|
|
1362
|
-
note.modulationDepth.disconnect();
|
|
1363
|
-
note.modulationLFO.stop();
|
|
1364
|
-
}
|
|
1365
|
-
if (note.vibratoDepth) {
|
|
1366
|
-
note.vibratoDepth.disconnect();
|
|
1367
|
-
note.vibratoLFO.stop();
|
|
1368
|
-
}
|
|
1369
|
-
if (note.reverbEffectsSend) {
|
|
1370
|
-
note.reverbEffectsSend.disconnect();
|
|
1371
|
-
}
|
|
1372
|
-
if (note.chorusEffectsSend) {
|
|
1373
|
-
note.chorusEffectsSend.disconnect();
|
|
1374
|
-
}
|
|
1474
|
+
noteList[index] = undefined;
|
|
1475
|
+
this.disconnectNote(note);
|
|
1375
1476
|
resolve();
|
|
1376
1477
|
};
|
|
1377
1478
|
note.bufferSource.stop(stopTime);
|
|
1378
1479
|
});
|
|
1379
1480
|
}
|
|
1481
|
+
findNoteOffTarget(noteList) {
|
|
1482
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1483
|
+
const note = noteList[i];
|
|
1484
|
+
if (!note)
|
|
1485
|
+
continue;
|
|
1486
|
+
if (note.ending)
|
|
1487
|
+
continue;
|
|
1488
|
+
return [note, i];
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1380
1491
|
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1381
1492
|
const channel = this.channels[channelNumber];
|
|
1493
|
+
if (this.isDrumNoteOffException(channel, noteNumber))
|
|
1494
|
+
return;
|
|
1382
1495
|
const state = channel.state;
|
|
1383
1496
|
if (!force) {
|
|
1384
1497
|
if (0.5 <= state.sustainPedal)
|
|
@@ -1386,35 +1499,32 @@ class Midy {
|
|
|
1386
1499
|
if (channel.sostenutoNotes.has(noteNumber))
|
|
1387
1500
|
return;
|
|
1388
1501
|
}
|
|
1389
|
-
|
|
1502
|
+
const noteList = channel.scheduledNotes.get(noteNumber);
|
|
1503
|
+
if (!noteList)
|
|
1504
|
+
return; // be careful with drum channel
|
|
1505
|
+
const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
|
|
1506
|
+
if (!noteOffTarget)
|
|
1390
1507
|
return;
|
|
1391
|
-
const
|
|
1392
|
-
|
|
1393
|
-
const
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
const baseRate = note.voiceParams.playbackRate;
|
|
1412
|
-
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1413
|
-
note.bufferSource.playbackRate
|
|
1414
|
-
.cancelScheduledValues(endTime)
|
|
1415
|
-
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1416
|
-
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1417
|
-
}
|
|
1508
|
+
const [note, i] = noteOffTarget;
|
|
1509
|
+
if (0.5 <= state.portamento && portamentoNoteNumber !== undefined) {
|
|
1510
|
+
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1511
|
+
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1512
|
+
const baseRate = note.voiceParams.playbackRate;
|
|
1513
|
+
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1514
|
+
note.bufferSource.playbackRate
|
|
1515
|
+
.cancelScheduledValues(endTime)
|
|
1516
|
+
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1517
|
+
return this.stopNote(endTime, portamentoTime, noteList, i);
|
|
1518
|
+
}
|
|
1519
|
+
else {
|
|
1520
|
+
const volRelease = endTime +
|
|
1521
|
+
note.voiceParams.volRelease * state.releaseTime * 2;
|
|
1522
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1523
|
+
note.filterNode.frequency
|
|
1524
|
+
.cancelScheduledValues(endTime)
|
|
1525
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1526
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1527
|
+
return this.stopNote(endTime, stopTime, noteList, i);
|
|
1418
1528
|
}
|
|
1419
1529
|
}
|
|
1420
1530
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
@@ -1476,12 +1586,12 @@ class Midy {
|
|
|
1476
1586
|
const note = activeNotes.get(noteNumber);
|
|
1477
1587
|
this.setControllerParameters(channel, note, table);
|
|
1478
1588
|
}
|
|
1479
|
-
|
|
1589
|
+
this.applyVoiceParams(channel, 10);
|
|
1480
1590
|
}
|
|
1481
|
-
handleProgramChange(channelNumber,
|
|
1591
|
+
handleProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1482
1592
|
const channel = this.channels[channelNumber];
|
|
1483
1593
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1484
|
-
channel.
|
|
1594
|
+
channel.programNumber = programNumber;
|
|
1485
1595
|
if (this.mode === "GM2") {
|
|
1486
1596
|
switch (channel.bankMSB) {
|
|
1487
1597
|
case 120:
|
|
@@ -1508,7 +1618,7 @@ class Midy {
|
|
|
1508
1618
|
this.getActiveNotes(channel, scheduleTime).forEach((note) => {
|
|
1509
1619
|
this.setControllerParameters(channel, note, table);
|
|
1510
1620
|
});
|
|
1511
|
-
|
|
1621
|
+
this.applyVoiceParams(channel, 13);
|
|
1512
1622
|
}
|
|
1513
1623
|
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
1514
1624
|
const pitchBend = msb * 128 + lsb;
|
|
@@ -1685,6 +1795,8 @@ class Midy {
|
|
|
1685
1795
|
state.set(channel.state.array);
|
|
1686
1796
|
state[2] = velocity / 127;
|
|
1687
1797
|
state[3] = noteNumber / 127;
|
|
1798
|
+
state[10] = state.polyphonicKeyPressure / 127;
|
|
1799
|
+
state[13] = state.channelPressure / 127;
|
|
1688
1800
|
return state;
|
|
1689
1801
|
}
|
|
1690
1802
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
@@ -1711,7 +1823,7 @@ class Midy {
|
|
|
1711
1823
|
if (key in voiceParams)
|
|
1712
1824
|
noteVoiceParams[key] = voiceParams[key];
|
|
1713
1825
|
}
|
|
1714
|
-
if (note.portamento) {
|
|
1826
|
+
if (0.5 <= channel.state.portamento && note.portamento) {
|
|
1715
1827
|
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1716
1828
|
}
|
|
1717
1829
|
else {
|
|
@@ -1918,10 +2030,11 @@ class Midy {
|
|
|
1918
2030
|
const channel = this.channels[channelNumber];
|
|
1919
2031
|
if (channel.isDrum)
|
|
1920
2032
|
return;
|
|
2033
|
+
const state = channel.state;
|
|
1921
2034
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1922
|
-
|
|
2035
|
+
state.softPedal = softPedal / 127;
|
|
1923
2036
|
this.processScheduledNotes(channel, (note) => {
|
|
1924
|
-
if (note.portamento) {
|
|
2037
|
+
if (0.5 <= state.portamento && note.portamento) {
|
|
1925
2038
|
this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
|
|
1926
2039
|
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1927
2040
|
}
|
|
@@ -1943,7 +2056,7 @@ class Midy {
|
|
|
1943
2056
|
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
1944
2057
|
});
|
|
1945
2058
|
}
|
|
1946
|
-
setReleaseTime(channelNumber, releaseTime,
|
|
2059
|
+
setReleaseTime(channelNumber, releaseTime, scheduleTime) {
|
|
1947
2060
|
const channel = this.channels[channelNumber];
|
|
1948
2061
|
if (channel.isDrum)
|
|
1949
2062
|
return;
|
|
@@ -1966,10 +2079,11 @@ class Midy {
|
|
|
1966
2079
|
const channel = this.channels[channelNumber];
|
|
1967
2080
|
if (channel.isDrum)
|
|
1968
2081
|
return;
|
|
2082
|
+
const state = channel.state;
|
|
1969
2083
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1970
|
-
|
|
2084
|
+
state.brightness = brightness / 64;
|
|
1971
2085
|
this.processScheduledNotes(channel, (note) => {
|
|
1972
|
-
if (note.portamento) {
|
|
2086
|
+
if (0.5 <= state.portamento && note.portamento) {
|
|
1973
2087
|
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1974
2088
|
}
|
|
1975
2089
|
else {
|
|
@@ -2230,18 +2344,33 @@ class Midy {
|
|
|
2230
2344
|
}
|
|
2231
2345
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2232
2346
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2233
|
-
return this.
|
|
2347
|
+
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
2234
2348
|
}
|
|
2349
|
+
resetAllStates(channelNumber) {
|
|
2350
|
+
const channel = this.channels[channelNumber];
|
|
2351
|
+
const state = channel.state;
|
|
2352
|
+
for (const type of Object.keys(defaultControllerState)) {
|
|
2353
|
+
state[type] = defaultControllerState[type].defaultValue;
|
|
2354
|
+
}
|
|
2355
|
+
for (const type of Object.keys(this.constructor.channelSettings)) {
|
|
2356
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
2357
|
+
}
|
|
2358
|
+
this.mode = "GM2";
|
|
2359
|
+
this.masterFineTuning = 0; // cb
|
|
2360
|
+
this.masterCoarseTuning = 0; // cb
|
|
2361
|
+
}
|
|
2362
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2235
2363
|
resetAllControllers(channelNumber) {
|
|
2236
2364
|
const stateTypes = [
|
|
2365
|
+
"polyphonicKeyPressure",
|
|
2366
|
+
"channelPressure",
|
|
2367
|
+
"pitchWheel",
|
|
2237
2368
|
"expression",
|
|
2238
2369
|
"modulationDepth",
|
|
2239
2370
|
"sustainPedal",
|
|
2240
2371
|
"portamento",
|
|
2241
2372
|
"sostenutoPedal",
|
|
2242
2373
|
"softPedal",
|
|
2243
|
-
"channelPressure",
|
|
2244
|
-
"pitchWheelSensitivity",
|
|
2245
2374
|
];
|
|
2246
2375
|
const channel = this.channels[channelNumber];
|
|
2247
2376
|
const state = channel.state;
|
|
@@ -2260,7 +2389,7 @@ class Midy {
|
|
|
2260
2389
|
}
|
|
2261
2390
|
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
2262
2391
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2263
|
-
return this.
|
|
2392
|
+
return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
|
|
2264
2393
|
}
|
|
2265
2394
|
omniOff(channelNumber, value, scheduleTime) {
|
|
2266
2395
|
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
@@ -2622,7 +2751,7 @@ class Midy {
|
|
|
2622
2751
|
return value * 0.00787;
|
|
2623
2752
|
}
|
|
2624
2753
|
getChannelBitmap(data) {
|
|
2625
|
-
const bitmap = new Array(
|
|
2754
|
+
const bitmap = new Array(this.channels.length).fill(false);
|
|
2626
2755
|
const ff = data[4] & 0b11;
|
|
2627
2756
|
const gg = data[5] & 0x7F;
|
|
2628
2757
|
const hh = data[6] & 0x7F;
|
|
@@ -2814,6 +2943,7 @@ class Midy {
|
|
|
2814
2943
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2815
2944
|
}
|
|
2816
2945
|
}
|
|
2946
|
+
// https://github.com/marmooo/js-timer-benchmark
|
|
2817
2947
|
scheduleTask(callback, scheduleTime) {
|
|
2818
2948
|
return new Promise((resolve) => {
|
|
2819
2949
|
const bufferSource = new AudioBufferSourceNode(this.audioContext, {
|
|
@@ -2839,10 +2969,8 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2839
2969
|
configurable: true,
|
|
2840
2970
|
writable: true,
|
|
2841
2971
|
value: {
|
|
2842
|
-
currentBufferSource: null,
|
|
2843
|
-
isDrum: false,
|
|
2844
2972
|
detune: 0,
|
|
2845
|
-
|
|
2973
|
+
programNumber: 0,
|
|
2846
2974
|
bank: 121 * 128,
|
|
2847
2975
|
bankMSB: 121,
|
|
2848
2976
|
bankLSB: 0,
|