@marmooo/midy 0.3.3 → 0.3.5
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 +19 -11
- package/esm/midy-GM1.d.ts +14 -11
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +159 -131
- package/esm/midy-GM2.d.ts +28 -42
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +309 -297
- package/esm/midy-GMLite.d.ts +15 -11
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +158 -132
- package/esm/midy.d.ts +30 -43
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +348 -315
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +14 -11
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +159 -131
- package/script/midy-GM2.d.ts +28 -42
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +309 -297
- package/script/midy-GMLite.d.ts +15 -11
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +158 -132
- package/script/midy.d.ts +30 -43
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +348 -315
package/esm/midy-GM1.js
CHANGED
|
@@ -70,13 +70,11 @@ const defaultControllerState = {
|
|
|
70
70
|
pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
|
|
71
71
|
pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
|
|
72
72
|
link: { type: 127, defaultValue: 0 },
|
|
73
|
-
// bankMSB: { type: 128 + 0, defaultValue: 121, },
|
|
74
73
|
modulationDepth: { type: 128 + 1, defaultValue: 0 },
|
|
75
74
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
76
75
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
77
76
|
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
78
77
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
79
|
-
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
80
78
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
81
79
|
sustainPedal: { type: 128 + 64, defaultValue: 0 },
|
|
82
80
|
// rpnLSB: { type: 128 + 100, defaultValue: 127 },
|
|
@@ -105,6 +103,16 @@ class ControllerState {
|
|
|
105
103
|
}
|
|
106
104
|
}
|
|
107
105
|
}
|
|
106
|
+
const volumeEnvelopeKeys = [
|
|
107
|
+
"volDelay",
|
|
108
|
+
"volAttack",
|
|
109
|
+
"volHold",
|
|
110
|
+
"volDecay",
|
|
111
|
+
"volSustain",
|
|
112
|
+
"volRelease",
|
|
113
|
+
"initialAttenuation",
|
|
114
|
+
];
|
|
115
|
+
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
108
116
|
const filterEnvelopeKeys = [
|
|
109
117
|
"modEnvToPitch",
|
|
110
118
|
"initialFilterFc",
|
|
@@ -114,20 +122,18 @@ const filterEnvelopeKeys = [
|
|
|
114
122
|
"modHold",
|
|
115
123
|
"modDecay",
|
|
116
124
|
"modSustain",
|
|
117
|
-
"modRelease",
|
|
118
|
-
"playbackRate",
|
|
119
125
|
];
|
|
120
126
|
const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
|
|
121
|
-
const
|
|
122
|
-
"
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
"
|
|
127
|
+
const pitchEnvelopeKeys = [
|
|
128
|
+
"modEnvToPitch",
|
|
129
|
+
"modDelay",
|
|
130
|
+
"modAttack",
|
|
131
|
+
"modHold",
|
|
132
|
+
"modDecay",
|
|
133
|
+
"modSustain",
|
|
134
|
+
"playbackRate",
|
|
129
135
|
];
|
|
130
|
-
const
|
|
136
|
+
const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
|
|
131
137
|
export class MidyGM1 {
|
|
132
138
|
constructor(audioContext) {
|
|
133
139
|
Object.defineProperty(this, "mode", {
|
|
@@ -196,13 +202,13 @@ export class MidyGM1 {
|
|
|
196
202
|
writable: true,
|
|
197
203
|
value: this.initSoundFontTable()
|
|
198
204
|
});
|
|
199
|
-
Object.defineProperty(this, "
|
|
205
|
+
Object.defineProperty(this, "voiceCounter", {
|
|
200
206
|
enumerable: true,
|
|
201
207
|
configurable: true,
|
|
202
208
|
writable: true,
|
|
203
209
|
value: new Map()
|
|
204
210
|
});
|
|
205
|
-
Object.defineProperty(this, "
|
|
211
|
+
Object.defineProperty(this, "voiceCache", {
|
|
206
212
|
enumerable: true,
|
|
207
213
|
configurable: true,
|
|
208
214
|
writable: true,
|
|
@@ -289,13 +295,11 @@ export class MidyGM1 {
|
|
|
289
295
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
290
296
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
291
297
|
const presetHeader = presetHeaders[i];
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
banks.set(presetHeader.bank, index);
|
|
295
|
-
}
|
|
298
|
+
const banks = this.soundFontTable[presetHeader.preset];
|
|
299
|
+
banks.set(presetHeader.bank, index);
|
|
296
300
|
}
|
|
297
301
|
}
|
|
298
|
-
async
|
|
302
|
+
async toUint8Array(input) {
|
|
299
303
|
let uint8Array;
|
|
300
304
|
if (typeof input === "string") {
|
|
301
305
|
const response = await fetch(input);
|
|
@@ -308,23 +312,32 @@ export class MidyGM1 {
|
|
|
308
312
|
else {
|
|
309
313
|
throw new TypeError("input must be a URL string or Uint8Array");
|
|
310
314
|
}
|
|
311
|
-
|
|
312
|
-
const soundFont = new SoundFont(parsed);
|
|
313
|
-
this.addSoundFont(soundFont);
|
|
315
|
+
return uint8Array;
|
|
314
316
|
}
|
|
315
|
-
async
|
|
316
|
-
|
|
317
|
-
if (
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
317
|
+
async loadSoundFont(input) {
|
|
318
|
+
this.voiceCounter.clear();
|
|
319
|
+
if (Array.isArray(input)) {
|
|
320
|
+
const promises = new Array(input.length);
|
|
321
|
+
for (let i = 0; i < input.length; i++) {
|
|
322
|
+
promises[i] = this.toUint8Array(input[i]);
|
|
323
|
+
}
|
|
324
|
+
const uint8Arrays = await Promise.all(promises);
|
|
325
|
+
for (let i = 0; i < uint8Arrays.length; i++) {
|
|
326
|
+
const parsed = parse(uint8Arrays[i]);
|
|
327
|
+
const soundFont = new SoundFont(parsed);
|
|
328
|
+
this.addSoundFont(soundFont);
|
|
329
|
+
}
|
|
324
330
|
}
|
|
325
331
|
else {
|
|
326
|
-
|
|
332
|
+
const uint8Array = await this.toUint8Array(input);
|
|
333
|
+
const parsed = parse(uint8Array);
|
|
334
|
+
const soundFont = new SoundFont(parsed);
|
|
335
|
+
this.addSoundFont(soundFont);
|
|
327
336
|
}
|
|
337
|
+
}
|
|
338
|
+
async loadMIDI(input) {
|
|
339
|
+
this.voiceCounter.clear();
|
|
340
|
+
const uint8Array = await this.toUint8Array(input);
|
|
328
341
|
const midi = parseMidi(uint8Array);
|
|
329
342
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
330
343
|
const midiData = this.extractMidiData(midi);
|
|
@@ -332,7 +345,46 @@ export class MidyGM1 {
|
|
|
332
345
|
this.timeline = midiData.timeline;
|
|
333
346
|
this.totalTime = this.calcTotalTime();
|
|
334
347
|
}
|
|
335
|
-
|
|
348
|
+
cacheVoiceIds() {
|
|
349
|
+
const timeline = this.timeline;
|
|
350
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
351
|
+
const event = timeline[i];
|
|
352
|
+
switch (event.type) {
|
|
353
|
+
case "noteOn": {
|
|
354
|
+
const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
|
|
355
|
+
this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
case "controller":
|
|
359
|
+
if (event.controllerType === 0) {
|
|
360
|
+
this.setBankMSB(event.channel, event.value);
|
|
361
|
+
}
|
|
362
|
+
else if (event.controllerType === 32) {
|
|
363
|
+
this.setBankLSB(event.channel, event.value);
|
|
364
|
+
}
|
|
365
|
+
break;
|
|
366
|
+
case "programChange":
|
|
367
|
+
this.setProgramChange(event.channel, event.programNumber, event.startTime);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
for (const [audioBufferId, count] of this.voiceCounter) {
|
|
371
|
+
if (count === 1)
|
|
372
|
+
this.voiceCounter.delete(audioBufferId);
|
|
373
|
+
}
|
|
374
|
+
this.GM1SystemOn();
|
|
375
|
+
}
|
|
376
|
+
getVoiceId(channel, noteNumber, velocity) {
|
|
377
|
+
const bankNumber = this.calcBank(channel);
|
|
378
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
379
|
+
.get(bankNumber);
|
|
380
|
+
if (soundFontIndex === undefined)
|
|
381
|
+
return;
|
|
382
|
+
const soundFont = this.soundFonts[soundFontIndex];
|
|
383
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
384
|
+
const { instrument, sampleID } = voice.generators;
|
|
385
|
+
return `${soundFontIndex}:${instrument}:${sampleID}`;
|
|
386
|
+
}
|
|
387
|
+
createChannelAudioNodes(audioContext) {
|
|
336
388
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
337
389
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
338
390
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
@@ -353,41 +405,19 @@ export class MidyGM1 {
|
|
|
353
405
|
isDrum: false,
|
|
354
406
|
state: new ControllerState(),
|
|
355
407
|
...this.constructor.channelSettings,
|
|
356
|
-
...this.
|
|
408
|
+
...this.createChannelAudioNodes(audioContext),
|
|
357
409
|
scheduledNotes: [],
|
|
358
410
|
sustainNotes: [],
|
|
359
411
|
};
|
|
360
412
|
});
|
|
361
413
|
return channels;
|
|
362
414
|
}
|
|
363
|
-
async
|
|
415
|
+
async createAudioBuffer(voiceParams) {
|
|
416
|
+
const sample = voiceParams.sample;
|
|
364
417
|
const sampleStart = voiceParams.start;
|
|
365
|
-
const sampleEnd =
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const start = sample.byteOffset + sampleStart;
|
|
369
|
-
const end = sample.byteOffset + sampleEnd;
|
|
370
|
-
const buffer = sample.buffer.slice(start, end);
|
|
371
|
-
const audioBuffer = await this.audioContext.decodeAudioData(buffer);
|
|
372
|
-
return audioBuffer;
|
|
373
|
-
}
|
|
374
|
-
else {
|
|
375
|
-
const sample = voiceParams.sample;
|
|
376
|
-
const start = sample.byteOffset + sampleStart;
|
|
377
|
-
const end = sample.byteOffset + sampleEnd;
|
|
378
|
-
const buffer = sample.buffer.slice(start, end);
|
|
379
|
-
const audioBuffer = new AudioBuffer({
|
|
380
|
-
numberOfChannels: 1,
|
|
381
|
-
length: sample.length,
|
|
382
|
-
sampleRate: voiceParams.sampleRate,
|
|
383
|
-
});
|
|
384
|
-
const channelData = audioBuffer.getChannelData(0);
|
|
385
|
-
const int16Array = new Int16Array(buffer);
|
|
386
|
-
for (let i = 0; i < int16Array.length; i++) {
|
|
387
|
-
channelData[i] = int16Array[i] / 32768;
|
|
388
|
-
}
|
|
389
|
-
return audioBuffer;
|
|
390
|
-
}
|
|
418
|
+
const sampleEnd = sample.data.length + voiceParams.end;
|
|
419
|
+
const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
|
|
420
|
+
return audioBuffer;
|
|
391
421
|
}
|
|
392
422
|
createBufferSource(voiceParams, audioBuffer) {
|
|
393
423
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
@@ -417,10 +447,10 @@ export class MidyGM1 {
|
|
|
417
447
|
break;
|
|
418
448
|
}
|
|
419
449
|
case "controller":
|
|
420
|
-
this.
|
|
450
|
+
this.setControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
421
451
|
break;
|
|
422
452
|
case "programChange":
|
|
423
|
-
this.
|
|
453
|
+
this.setProgramChange(event.channel, event.programNumber, startTime);
|
|
424
454
|
break;
|
|
425
455
|
case "pitchBend":
|
|
426
456
|
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
@@ -453,8 +483,9 @@ export class MidyGM1 {
|
|
|
453
483
|
await Promise.all(this.notePromises);
|
|
454
484
|
this.notePromises = [];
|
|
455
485
|
this.exclusiveClassNotes.fill(undefined);
|
|
456
|
-
this.
|
|
486
|
+
this.voiceCache.clear();
|
|
457
487
|
for (let i = 0; i < this.channels.length; i++) {
|
|
488
|
+
this.channels[i].scheduledNotes = [];
|
|
458
489
|
this.resetAllStates(i);
|
|
459
490
|
}
|
|
460
491
|
resolve();
|
|
@@ -475,8 +506,9 @@ export class MidyGM1 {
|
|
|
475
506
|
await this.stopNotes(0, true, now);
|
|
476
507
|
this.notePromises = [];
|
|
477
508
|
this.exclusiveClassNotes.fill(undefined);
|
|
478
|
-
this.
|
|
509
|
+
this.voiceCache.clear();
|
|
479
510
|
for (let i = 0; i < this.channels.length; i++) {
|
|
511
|
+
this.channels[i].scheduledNotes = [];
|
|
480
512
|
this.resetAllStates(i);
|
|
481
513
|
}
|
|
482
514
|
this.isStopping = false;
|
|
@@ -508,11 +540,7 @@ export class MidyGM1 {
|
|
|
508
540
|
secondToTicks(second, secondsPerBeat) {
|
|
509
541
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
510
542
|
}
|
|
511
|
-
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
512
|
-
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
513
|
-
}
|
|
514
543
|
extractMidiData(midi) {
|
|
515
|
-
this.audioBufferCounter.clear();
|
|
516
544
|
const instruments = new Set();
|
|
517
545
|
const timeline = [];
|
|
518
546
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -532,8 +560,6 @@ export class MidyGM1 {
|
|
|
532
560
|
switch (event.type) {
|
|
533
561
|
case "noteOn": {
|
|
534
562
|
const channel = tmpChannels[event.channel];
|
|
535
|
-
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
536
|
-
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
537
563
|
if (channel.programNumber < 0) {
|
|
538
564
|
instruments.add(`${channel.bank}:0`);
|
|
539
565
|
channel.programNumber = 0;
|
|
@@ -550,10 +576,6 @@ export class MidyGM1 {
|
|
|
550
576
|
timeline.push(event);
|
|
551
577
|
}
|
|
552
578
|
}
|
|
553
|
-
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
554
|
-
if (count === 1)
|
|
555
|
-
this.audioBufferCounter.delete(audioBufferId);
|
|
556
|
-
}
|
|
557
579
|
const priority = {
|
|
558
580
|
controller: 0,
|
|
559
581
|
sysEx: 1,
|
|
@@ -596,7 +618,6 @@ export class MidyGM1 {
|
|
|
596
618
|
this.notePromises.push(promise);
|
|
597
619
|
promises.push(promise);
|
|
598
620
|
});
|
|
599
|
-
channel.scheduledNotes = [];
|
|
600
621
|
return Promise.all(promises);
|
|
601
622
|
}
|
|
602
623
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -610,6 +631,8 @@ export class MidyGM1 {
|
|
|
610
631
|
if (this.isPlaying || this.isPaused)
|
|
611
632
|
return;
|
|
612
633
|
this.resumeTime = 0;
|
|
634
|
+
if (this.voiceCounter.size === 0)
|
|
635
|
+
this.cacheVoiceIds();
|
|
613
636
|
await this.playNotes();
|
|
614
637
|
this.isPlaying = false;
|
|
615
638
|
}
|
|
@@ -652,7 +675,7 @@ export class MidyGM1 {
|
|
|
652
675
|
}
|
|
653
676
|
processScheduledNotes(channel, callback) {
|
|
654
677
|
const scheduledNotes = channel.scheduledNotes;
|
|
655
|
-
for (let i =
|
|
678
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
656
679
|
const note = scheduledNotes[i];
|
|
657
680
|
if (!note)
|
|
658
681
|
continue;
|
|
@@ -663,14 +686,14 @@ export class MidyGM1 {
|
|
|
663
686
|
}
|
|
664
687
|
processActiveNotes(channel, scheduleTime, callback) {
|
|
665
688
|
const scheduledNotes = channel.scheduledNotes;
|
|
666
|
-
for (let i =
|
|
689
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
667
690
|
const note = scheduledNotes[i];
|
|
668
691
|
if (!note)
|
|
669
692
|
continue;
|
|
670
693
|
if (note.ending)
|
|
671
694
|
continue;
|
|
672
695
|
if (scheduleTime < note.startTime)
|
|
673
|
-
|
|
696
|
+
break;
|
|
674
697
|
callback(note);
|
|
675
698
|
}
|
|
676
699
|
}
|
|
@@ -787,31 +810,31 @@ export class MidyGM1 {
|
|
|
787
810
|
note.modulationLFO.connect(note.volumeDepth);
|
|
788
811
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
789
812
|
}
|
|
790
|
-
async getAudioBuffer(
|
|
791
|
-
const audioBufferId = this.
|
|
792
|
-
const cache = this.
|
|
813
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
814
|
+
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
815
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
793
816
|
if (cache) {
|
|
794
817
|
cache.counter += 1;
|
|
795
818
|
if (cache.maxCount <= cache.counter) {
|
|
796
|
-
this.
|
|
819
|
+
this.voiceCache.delete(audioBufferId);
|
|
797
820
|
}
|
|
798
821
|
return cache.audioBuffer;
|
|
799
822
|
}
|
|
800
823
|
else {
|
|
801
|
-
const maxCount = this.
|
|
802
|
-
const audioBuffer = await this.
|
|
824
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
825
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
803
826
|
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
804
|
-
this.
|
|
827
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
805
828
|
return audioBuffer;
|
|
806
829
|
}
|
|
807
830
|
}
|
|
808
|
-
async createNote(channel, voice, noteNumber, velocity, startTime
|
|
831
|
+
async createNote(channel, voice, noteNumber, velocity, startTime) {
|
|
809
832
|
const now = this.audioContext.currentTime;
|
|
810
833
|
const state = channel.state;
|
|
811
834
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
812
835
|
const voiceParams = voice.getAllParams(controllerState);
|
|
813
836
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
814
|
-
const audioBuffer = await this.getAudioBuffer(channel
|
|
837
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
815
838
|
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
816
839
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
817
840
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
@@ -844,19 +867,18 @@ export class MidyGM1 {
|
|
|
844
867
|
}
|
|
845
868
|
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
846
869
|
}
|
|
847
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime
|
|
870
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
848
871
|
const channel = this.channels[channelNumber];
|
|
849
872
|
const bankNumber = channel.bank;
|
|
850
|
-
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
873
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
874
|
+
.get(bankNumber);
|
|
851
875
|
if (soundFontIndex === undefined)
|
|
852
876
|
return;
|
|
853
877
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
854
878
|
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
855
879
|
if (!voice)
|
|
856
880
|
return;
|
|
857
|
-
const
|
|
858
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
859
|
-
note.noteOffEvent = noteOffEvent;
|
|
881
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
860
882
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
861
883
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
862
884
|
if (0.5 <= channel.state.sustainPedal) {
|
|
@@ -906,15 +928,29 @@ export class MidyGM1 {
|
|
|
906
928
|
const channel = this.channels[channelNumber];
|
|
907
929
|
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
908
930
|
return;
|
|
909
|
-
const
|
|
910
|
-
if (
|
|
931
|
+
const index = this.findNoteOffIndex(channel, noteNumber);
|
|
932
|
+
if (index < 0)
|
|
911
933
|
return;
|
|
934
|
+
const note = channel.scheduledNotes[index];
|
|
912
935
|
note.ending = true;
|
|
936
|
+
this.setNoteIndex(channel, index);
|
|
913
937
|
this.releaseNote(channel, note, endTime);
|
|
914
938
|
}
|
|
915
|
-
|
|
939
|
+
setNoteIndex(channel, index) {
|
|
940
|
+
let allEnds = true;
|
|
941
|
+
for (let i = channel.scheduleIndex; i < index; i++) {
|
|
942
|
+
const note = channel.scheduledNotes[i];
|
|
943
|
+
if (note && !note.ending) {
|
|
944
|
+
allEnds = false;
|
|
945
|
+
break;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
if (allEnds)
|
|
949
|
+
channel.scheduleIndex = index + 1;
|
|
950
|
+
}
|
|
951
|
+
findNoteOffIndex(channel, noteNumber) {
|
|
916
952
|
const scheduledNotes = channel.scheduledNotes;
|
|
917
|
-
for (let i =
|
|
953
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
918
954
|
const note = scheduledNotes[i];
|
|
919
955
|
if (!note)
|
|
920
956
|
continue;
|
|
@@ -922,8 +958,9 @@ export class MidyGM1 {
|
|
|
922
958
|
continue;
|
|
923
959
|
if (note.noteNumber !== noteNumber)
|
|
924
960
|
continue;
|
|
925
|
-
return
|
|
961
|
+
return i;
|
|
926
962
|
}
|
|
963
|
+
return -1;
|
|
927
964
|
}
|
|
928
965
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
929
966
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -949,16 +986,16 @@ export class MidyGM1 {
|
|
|
949
986
|
case 0x90:
|
|
950
987
|
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
951
988
|
case 0xB0:
|
|
952
|
-
return this.
|
|
989
|
+
return this.setControlChange(channelNumber, data1, data2, scheduleTime);
|
|
953
990
|
case 0xC0:
|
|
954
|
-
return this.
|
|
991
|
+
return this.setProgramChange(channelNumber, data1, scheduleTime);
|
|
955
992
|
case 0xE0:
|
|
956
993
|
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
957
994
|
default:
|
|
958
995
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
959
996
|
}
|
|
960
997
|
}
|
|
961
|
-
|
|
998
|
+
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
962
999
|
const channel = this.channels[channelNumber];
|
|
963
1000
|
channel.programNumber = programNumber;
|
|
964
1001
|
}
|
|
@@ -1051,8 +1088,9 @@ export class MidyGM1 {
|
|
|
1051
1088
|
this.processScheduledNotes(channel, (note) => {
|
|
1052
1089
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1053
1090
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1054
|
-
let
|
|
1055
|
-
let
|
|
1091
|
+
let applyVolumeEnvelope = false;
|
|
1092
|
+
let applyFilterEnvelope = false;
|
|
1093
|
+
let applyPitchEnvelope = false;
|
|
1056
1094
|
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1057
1095
|
const prevValue = note.voiceParams[key];
|
|
1058
1096
|
if (value === prevValue)
|
|
@@ -1061,32 +1099,21 @@ export class MidyGM1 {
|
|
|
1061
1099
|
if (key in this.voiceParamsHandlers) {
|
|
1062
1100
|
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1063
1101
|
}
|
|
1064
|
-
else
|
|
1065
|
-
if (
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
if (key in voiceParams)
|
|
1072
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1073
|
-
}
|
|
1074
|
-
this.setFilterEnvelope(note, scheduleTime);
|
|
1075
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1076
|
-
}
|
|
1077
|
-
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1078
|
-
if (appliedVolumeEnvelope)
|
|
1079
|
-
continue;
|
|
1080
|
-
appliedVolumeEnvelope = true;
|
|
1081
|
-
const noteVoiceParams = note.voiceParams;
|
|
1082
|
-
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1083
|
-
const key = volumeEnvelopeKeys[i];
|
|
1084
|
-
if (key in voiceParams)
|
|
1085
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1086
|
-
}
|
|
1087
|
-
this.setVolumeEnvelope(note, scheduleTime);
|
|
1102
|
+
else {
|
|
1103
|
+
if (volumeEnvelopeKeySet.has(key))
|
|
1104
|
+
applyVolumeEnvelope = true;
|
|
1105
|
+
if (filterEnvelopeKeySet.has(key))
|
|
1106
|
+
applyFilterEnvelope = true;
|
|
1107
|
+
if (pitchEnvelopeKeySet.has(key))
|
|
1108
|
+
applyPitchEnvelope = true;
|
|
1088
1109
|
}
|
|
1089
1110
|
}
|
|
1111
|
+
if (applyVolumeEnvelope)
|
|
1112
|
+
this.setVolumeEnvelope(note, scheduleTime);
|
|
1113
|
+
if (applyFilterEnvelope)
|
|
1114
|
+
this.setFilterEnvelope(note, scheduleTime);
|
|
1115
|
+
if (applyPitchEnvelope)
|
|
1116
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1090
1117
|
});
|
|
1091
1118
|
}
|
|
1092
1119
|
createControlChangeHandlers() {
|
|
@@ -1102,9 +1129,10 @@ export class MidyGM1 {
|
|
|
1102
1129
|
handlers[101] = this.setRPNMSB;
|
|
1103
1130
|
handlers[120] = this.allSoundOff;
|
|
1104
1131
|
handlers[121] = this.resetAllControllers;
|
|
1132
|
+
handlers[123] = this.allNotesOff;
|
|
1105
1133
|
return handlers;
|
|
1106
1134
|
}
|
|
1107
|
-
|
|
1135
|
+
setControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1108
1136
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1109
1137
|
if (handler) {
|
|
1110
1138
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
@@ -1297,7 +1325,7 @@ export class MidyGM1 {
|
|
|
1297
1325
|
const entries = Object.entries(defaultControllerState);
|
|
1298
1326
|
for (const [key, { type, defaultValue }] of entries) {
|
|
1299
1327
|
if (128 <= type) {
|
|
1300
|
-
this.
|
|
1328
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1301
1329
|
}
|
|
1302
1330
|
else {
|
|
1303
1331
|
state[key] = defaultValue;
|
|
@@ -1322,7 +1350,7 @@ export class MidyGM1 {
|
|
|
1322
1350
|
const key = keys[i];
|
|
1323
1351
|
const { type, defaultValue } = defaultControllerState[key];
|
|
1324
1352
|
if (128 <= type) {
|
|
1325
|
-
this.
|
|
1353
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1326
1354
|
}
|
|
1327
1355
|
else {
|
|
1328
1356
|
state[key] = defaultValue;
|