@marmooo/midy 0.0.4 → 0.0.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.
Files changed (33) hide show
  1. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.d.ts +13 -6
  2. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts.map +1 -0
  3. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.js +5 -5
  4. package/esm/midy-GM1.d.ts +24 -34
  5. package/esm/midy-GM1.d.ts.map +1 -1
  6. package/esm/midy-GM1.js +167 -106
  7. package/esm/midy-GM2.d.ts +123 -21
  8. package/esm/midy-GM2.d.ts.map +1 -1
  9. package/esm/midy-GM2.js +170 -116
  10. package/esm/midy-GMLite.d.ts +23 -35
  11. package/esm/midy-GMLite.d.ts.map +1 -1
  12. package/esm/midy-GMLite.js +156 -107
  13. package/esm/midy.d.ts +25 -23
  14. package/esm/midy.d.ts.map +1 -1
  15. package/esm/midy.js +191 -120
  16. package/package.json +1 -1
  17. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.d.ts +13 -6
  18. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts.map +1 -0
  19. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.js +5 -5
  20. package/script/midy-GM1.d.ts +24 -34
  21. package/script/midy-GM1.d.ts.map +1 -1
  22. package/script/midy-GM1.js +167 -106
  23. package/script/midy-GM2.d.ts +123 -21
  24. package/script/midy-GM2.d.ts.map +1 -1
  25. package/script/midy-GM2.js +170 -116
  26. package/script/midy-GMLite.d.ts +23 -35
  27. package/script/midy-GMLite.d.ts.map +1 -1
  28. package/script/midy-GMLite.js +156 -107
  29. package/script/midy.d.ts +25 -23
  30. package/script/midy.d.ts.map +1 -1
  31. package/script/midy.js +191 -120
  32. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.1/+esm.d.ts.map +0 -1
  33. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.1/+esm.d.ts.map +0 -1
@@ -2,9 +2,6 @@ export class MidyGM1 {
2
2
  static channelSettings: {
3
3
  volume: number;
4
4
  pan: number;
5
- vibratoRate: number;
6
- vibratoDepth: number;
7
- vibratoDelay: number;
8
5
  bank: number;
9
6
  dataMSB: number;
10
7
  dataLSB: number;
@@ -44,12 +41,8 @@ export class MidyGM1 {
44
41
  masterGain: any;
45
42
  channels: {
46
43
  scheduledNotes: Map<any, any>;
47
- sostenutoNotes: Map<any, any>;
48
44
  gainNode: any;
49
45
  pannerNode: any;
50
- modulationEffect: {
51
- lfo: any;
52
- };
53
46
  expression: number;
54
47
  modulation: number;
55
48
  sustainPedal: boolean;
@@ -58,9 +51,6 @@ export class MidyGM1 {
58
51
  pitchBendRange: number;
59
52
  volume: number;
60
53
  pan: number;
61
- vibratoRate: number;
62
- vibratoDepth: number;
63
- vibratoDelay: number;
64
54
  bank: number;
65
55
  dataMSB: number;
66
56
  dataLSB: number;
@@ -77,18 +67,11 @@ export class MidyGM1 {
77
67
  setChannelAudioNodes(audioContext: any): {
78
68
  gainNode: any;
79
69
  pannerNode: any;
80
- modulationEffect: {
81
- lfo: any;
82
- };
83
70
  };
84
71
  createChannels(audioContext: any): {
85
72
  scheduledNotes: Map<any, any>;
86
- sostenutoNotes: Map<any, any>;
87
73
  gainNode: any;
88
74
  pannerNode: any;
89
- modulationEffect: {
90
- lfo: any;
91
- };
92
75
  expression: number;
93
76
  modulation: number;
94
77
  sustainPedal: boolean;
@@ -97,9 +80,6 @@ export class MidyGM1 {
97
80
  pitchBendRange: number;
98
81
  volume: number;
99
82
  pan: number;
100
- vibratoRate: number;
101
- vibratoDepth: number;
102
- vibratoDelay: number;
103
83
  bank: number;
104
84
  dataMSB: number;
105
85
  dataLSB: number;
@@ -109,8 +89,8 @@ export class MidyGM1 {
109
89
  coarseTuning: number;
110
90
  modulationDepthRange: number;
111
91
  }[];
112
- createNoteBuffer(noteInfo: any, isSF3: any): Promise<any>;
113
- createNoteBufferNode(noteInfo: any, isSF3: any): Promise<any>;
92
+ createNoteBuffer(instrumentKey: any, isSF3: any): Promise<any>;
93
+ createNoteBufferNode(instrumentKey: any, isSF3: any): Promise<any>;
114
94
  convertToFloat32Array(uint8Array: any): Float32Array;
115
95
  scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
116
96
  getQueueIndex(second: any): number;
@@ -129,22 +109,17 @@ export class MidyGM1 {
129
109
  seekTo(second: any): void;
130
110
  calcTotalTime(): number;
131
111
  currentTime(): number;
132
- getActiveNotes(channel: any): Map<any, any>;
133
- getActiveChannelNotes(scheduledNotes: any): any;
134
- createModulationEffect(audioContext: any): {
135
- lfo: any;
136
- };
112
+ getActiveNotes(channel: any, time: any): Map<any, any>;
113
+ getActiveNote(noteList: any, time: any): any;
137
114
  connectNoteEffects(channel: any, gainNode: any): void;
138
115
  cbToRatio(cb: any): number;
139
116
  centToHz(cent: any): number;
140
117
  calcSemitoneOffset(channel: any): any;
141
- calcPlaybackRate(noteInfo: any, noteNumber: any, semitoneOffset: any): number;
142
- createNoteAudioChain(channel: any, noteInfo: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<{
143
- bufferSource: any;
144
- gainNode: any;
145
- filterNode: any;
146
- lfoGain: any;
147
- }>;
118
+ calcPlaybackRate(instrumentKey: any, noteNumber: any, semitoneOffset: any): number;
119
+ setVolumeEnvelope(channel: any, note: any): void;
120
+ setFilterEnvelope(channel: any, note: any): void;
121
+ startModulation(channel: any, note: any, time: any): void;
122
+ createNote(channel: any, instrumentKey: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
148
123
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
149
124
  noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
150
125
  scheduleNoteRelease(channelNumber: any, noteNumber: any, velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
@@ -176,4 +151,19 @@ export class MidyGM1 {
176
151
  handleSysEx(data: any): void;
177
152
  scheduleTask(callback: any, startTime: any): Promise<any>;
178
153
  }
154
+ declare class Note {
155
+ constructor(noteNumber: any, velocity: any, startTime: any, instrumentKey: any);
156
+ bufferSource: any;
157
+ gainNode: any;
158
+ filterNode: any;
159
+ modLFO: any;
160
+ modLFOGain: any;
161
+ vibLFO: any;
162
+ vibLFOGain: any;
163
+ noteNumber: any;
164
+ velocity: any;
165
+ startTime: any;
166
+ instrumentKey: any;
167
+ }
168
+ export {};
179
169
  //# sourceMappingURL=midy-GM1.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAMA;IAmBE;;;;;;;;;;;;;;MAcE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IAjDD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IA4BhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;MAgBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;QAWC;IAED,0DAyBC;IAED,8DAUC;IAED,qDAOC;IAED,2EA+CC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAyEC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,4CASC;IAED,gDAKC;IAED;;MAOC;IAED,sDAEC;IAED,2BAEC;IAED,4BAEC;IAED,sCAGC;IAED,8EAEC;IAED;;;;;OA8EC;IAED,kGAuCC;IAED,0EAGC;IAED,sIA4CC;IAED,0FAGC;IAED,kEAeC;IAED,wFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,0DAiBC;IAED,mFA+BC;IAED,yDAIC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,sCAKC;IAED,sDAMC;IAED,gDAEC;IAED,gDAEC;IAED,+DAoBC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF"}
1
+ {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAuBA;IAmBE;;;;;;;;;;;MAWE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA9CD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAyBhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;MAaC;IAED;;;;;;;;;;;;;;;;;;;;QAUC;IAED,+DAyBC;IAED,mEAWC;IAED,qDAOC;IAED,2EA+CC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAyEC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,sDAEC;IAED,2BAEC;IAED,4BAEC;IAED,sCAGC;IAED,mFAGC;IAED,iDAmBC;IAED,iDAiCC;IAED,0DAmBC;IAED,wHA6BC;IAED,kGA6BC;IAED,0EAGC;IAED,sIAmDC;IAED,0FAGC;IAED,kEAeC;IAED,wFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,0DAiBC;IAED,mFA+BC;IAED,yDAiBC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,sCAKC;IAED,sDAMC;IAED,gDAEC;IAED,gDAEC;IAED,+DAoBC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAv+BD;IASE,gFAKC;IAbD,kBAAa;IACb,cAAS;IACT,gBAAW;IACX,YAAO;IACP,gBAAW;IACX,YAAO;IACP,gBAAW;IAGT,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
@@ -2,7 +2,57 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MidyGM1 = void 0;
4
4
  const _esm_js_1 = require("./deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js");
5
- const _esm_js_2 = require("./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.1/+esm.js");
5
+ const _esm_js_2 = require("./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.js");
6
+ class Note {
7
+ constructor(noteNumber, velocity, startTime, instrumentKey) {
8
+ Object.defineProperty(this, "bufferSource", {
9
+ enumerable: true,
10
+ configurable: true,
11
+ writable: true,
12
+ value: void 0
13
+ });
14
+ Object.defineProperty(this, "gainNode", {
15
+ enumerable: true,
16
+ configurable: true,
17
+ writable: true,
18
+ value: void 0
19
+ });
20
+ Object.defineProperty(this, "filterNode", {
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true,
24
+ value: void 0
25
+ });
26
+ Object.defineProperty(this, "modLFO", {
27
+ enumerable: true,
28
+ configurable: true,
29
+ writable: true,
30
+ value: void 0
31
+ });
32
+ Object.defineProperty(this, "modLFOGain", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: void 0
37
+ });
38
+ Object.defineProperty(this, "vibLFO", {
39
+ enumerable: true,
40
+ configurable: true,
41
+ writable: true,
42
+ value: void 0
43
+ });
44
+ Object.defineProperty(this, "vibLFOGain", {
45
+ enumerable: true,
46
+ configurable: true,
47
+ writable: true,
48
+ value: void 0
49
+ });
50
+ this.noteNumber = noteNumber;
51
+ this.velocity = velocity;
52
+ this.startTime = startTime;
53
+ this.instrumentKey = instrumentKey;
54
+ }
55
+ }
6
56
  class MidyGM1 {
7
57
  constructor(audioContext) {
8
58
  Object.defineProperty(this, "ticksPerBeat", {
@@ -154,14 +204,11 @@ class MidyGM1 {
154
204
  const pannerNode = new StereoPannerNode(audioContext, {
155
205
  pan: MidyGM1.channelSettings.pan,
156
206
  });
157
- const modulationEffect = this.createModulationEffect(audioContext);
158
- modulationEffect.lfo.start();
159
207
  pannerNode.connect(gainNode);
160
208
  gainNode.connect(this.masterGain);
161
209
  return {
162
210
  gainNode,
163
211
  pannerNode,
164
- modulationEffect,
165
212
  };
166
213
  }
167
214
  createChannels(audioContext) {
@@ -171,16 +218,15 @@ class MidyGM1 {
171
218
  ...MidyGM1.effectSettings,
172
219
  ...this.setChannelAudioNodes(audioContext),
173
220
  scheduledNotes: new Map(),
174
- sostenutoNotes: new Map(),
175
221
  };
176
222
  });
177
223
  return channels;
178
224
  }
179
- async createNoteBuffer(noteInfo, isSF3) {
180
- const sampleEnd = noteInfo.sample.length + noteInfo.end;
225
+ async createNoteBuffer(instrumentKey, isSF3) {
226
+ const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
181
227
  if (isSF3) {
182
- const sample = new Uint8Array(noteInfo.sample.length);
183
- sample.set(noteInfo.sample);
228
+ const sample = new Uint8Array(instrumentKey.sample.length);
229
+ sample.set(instrumentKey.sample);
184
230
  const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
185
231
  for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
186
232
  const channelData = audioBuffer.getChannelData(channel);
@@ -189,26 +235,27 @@ class MidyGM1 {
189
235
  return audioBuffer;
190
236
  }
191
237
  else {
192
- const sample = noteInfo.sample.subarray(0, sampleEnd);
238
+ const sample = instrumentKey.sample.subarray(0, sampleEnd);
193
239
  const floatSample = this.convertToFloat32Array(sample);
194
240
  const audioBuffer = new AudioBuffer({
195
241
  numberOfChannels: 1,
196
242
  length: sample.length,
197
- sampleRate: noteInfo.sampleRate,
243
+ sampleRate: instrumentKey.sampleRate,
198
244
  });
199
245
  const channelData = audioBuffer.getChannelData(0);
200
246
  channelData.set(floatSample);
201
247
  return audioBuffer;
202
248
  }
203
249
  }
204
- async createNoteBufferNode(noteInfo, isSF3) {
250
+ async createNoteBufferNode(instrumentKey, isSF3) {
205
251
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
206
- const audioBuffer = await this.createNoteBuffer(noteInfo, isSF3);
252
+ const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
207
253
  bufferSource.buffer = audioBuffer;
208
- bufferSource.loop = noteInfo.sampleModes % 2 !== 0;
254
+ bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
209
255
  if (bufferSource.loop) {
210
- bufferSource.loopStart = noteInfo.loopStart / noteInfo.sampleRate;
211
- bufferSource.loopEnd = noteInfo.loopEnd / noteInfo.sampleRate;
256
+ bufferSource.loopStart = instrumentKey.loopStart /
257
+ instrumentKey.sampleRate;
258
+ bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
212
259
  }
213
260
  return bufferSource;
214
261
  }
@@ -450,30 +497,26 @@ class MidyGM1 {
450
497
  const now = this.audioContext.currentTime;
451
498
  return this.resumeTime + now - this.startTime - this.startDelay;
452
499
  }
453
- getActiveNotes(channel) {
500
+ getActiveNotes(channel, time) {
454
501
  const activeNotes = new Map();
455
- channel.scheduledNotes.forEach((scheduledNotes) => {
456
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
502
+ channel.scheduledNotes.forEach((noteList) => {
503
+ const activeNote = this.getActiveNote(noteList, time);
457
504
  if (activeNote) {
458
505
  activeNotes.set(activeNote.noteNumber, activeNote);
459
506
  }
460
507
  });
461
508
  return activeNotes;
462
509
  }
463
- getActiveChannelNotes(scheduledNotes) {
464
- for (let i = 0; i < scheduledNotes; i++) {
465
- const scheduledNote = scheduledNotes[i];
466
- if (scheduledNote)
467
- return scheduledNote;
510
+ getActiveNote(noteList, time) {
511
+ for (let i = noteList.length - 1; i >= 0; i--) {
512
+ const note = noteList[i];
513
+ if (!note)
514
+ return;
515
+ if (time < note.startTime)
516
+ continue;
517
+ return (note.ending) ? null : note;
468
518
  }
469
- }
470
- createModulationEffect(audioContext) {
471
- const lfo = new OscillatorNode(audioContext, {
472
- frequency: 5,
473
- });
474
- return {
475
- lfo,
476
- };
519
+ return noteList[0];
477
520
  }
478
521
  connectNoteEffects(channel, gainNode) {
479
522
  gainNode.connect(channel.pannerNode);
@@ -488,71 +531,89 @@ class MidyGM1 {
488
531
  const tuning = channel.coarseTuning + channel.fineTuning;
489
532
  return channel.pitchBend * channel.pitchBendRange + tuning;
490
533
  }
491
- calcPlaybackRate(noteInfo, noteNumber, semitoneOffset) {
492
- return noteInfo.playbackRate(noteNumber) * Math.pow(2, semitoneOffset / 12);
534
+ calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
535
+ return instrumentKey.playbackRate(noteNumber) *
536
+ Math.pow(2, semitoneOffset / 12);
493
537
  }
494
- async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
495
- const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
496
- const semitoneOffset = this.calcSemitoneOffset(channel);
497
- bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
498
- // volume envelope
499
- const gainNode = new GainNode(this.audioContext, {
538
+ setVolumeEnvelope(channel, note) {
539
+ const { instrumentKey, startTime, velocity } = note;
540
+ note.gainNode = new GainNode(this.audioContext, {
500
541
  gain: 0,
501
542
  });
502
543
  let volume = (velocity / 127) * channel.volume * channel.expression;
503
544
  if (volume === 0)
504
545
  volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
505
- const attackVolume = this.cbToRatio(-noteInfo.initialAttenuation) * volume;
506
- const sustainVolume = attackVolume * (1 - noteInfo.volSustain);
507
- const volDelay = startTime + noteInfo.volDelay;
508
- const volAttack = volDelay + noteInfo.volAttack;
509
- const volHold = volAttack + noteInfo.volHold;
510
- const volDecay = volHold + noteInfo.volDecay;
511
- gainNode.gain
546
+ const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
547
+ volume;
548
+ const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
549
+ const volDelay = startTime + instrumentKey.volDelay;
550
+ const volAttack = volDelay + instrumentKey.volAttack;
551
+ const volHold = volAttack + instrumentKey.volHold;
552
+ const volDecay = volHold + instrumentKey.volDecay;
553
+ note.gainNode.gain
512
554
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
513
555
  .exponentialRampToValueAtTime(attackVolume, volAttack)
514
556
  .setValueAtTime(attackVolume, volHold)
515
557
  .linearRampToValueAtTime(sustainVolume, volDecay);
516
- // filter envelope
558
+ }
559
+ setFilterEnvelope(channel, note) {
560
+ const { instrumentKey, startTime, noteNumber } = note;
561
+ const softPedalFactor = 1 -
562
+ (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
517
563
  const maxFreq = this.audioContext.sampleRate / 2;
518
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
519
- const peekFreq = this.centToHz(noteInfo.initialFilterFc + noteInfo.modEnvToFilterFc);
520
- const sustainFreq = baseFreq +
521
- (peekFreq - baseFreq) * (1 - noteInfo.modSustain);
564
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
565
+ softPedalFactor;
566
+ const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
567
+ const sustainFreq = (baseFreq +
568
+ (peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
569
+ const modDelay = startTime + instrumentKey.modDelay;
570
+ const modAttack = modDelay + instrumentKey.modAttack;
571
+ const modHold = modAttack + instrumentKey.modHold;
572
+ const modDecay = modHold + instrumentKey.modDecay;
522
573
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
523
574
  const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
524
575
  const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
525
- const filterNode = new BiquadFilterNode(this.audioContext, {
576
+ note.filterNode = new BiquadFilterNode(this.audioContext, {
526
577
  type: "lowpass",
527
- Q: noteInfo.initialFilterQ / 10, // dB
578
+ Q: instrumentKey.initialFilterQ / 10, // dB
528
579
  frequency: adjustedBaseFreq,
529
580
  });
530
- const modDelay = startTime + noteInfo.modDelay;
531
- const modAttack = modDelay + noteInfo.modAttack;
532
- const modHold = modAttack + noteInfo.modHold;
533
- const modDecay = modHold + noteInfo.modDecay;
534
- filterNode.frequency
581
+ note.filterNode.frequency
535
582
  .setValueAtTime(adjustedBaseFreq, modDelay)
536
583
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
537
584
  .setValueAtTime(adjustedPeekFreq, modHold)
538
585
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
539
- let lfoGain;
586
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
587
+ }
588
+ startModulation(channel, note, time) {
589
+ const { instrumentKey } = note;
590
+ note.modLFOGain = new GainNode(this.audioContext, {
591
+ gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
592
+ });
593
+ note.modLFO = new OscillatorNode(this.audioContext, {
594
+ frequency: this.centToHz(instrumentKey.freqModLFO),
595
+ });
596
+ note.modLFO.start(time);
597
+ note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
598
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
599
+ note.modLFO.connect(note.modLFOGain);
600
+ note.modLFOGain.connect(note.bufferSource.detune);
601
+ }
602
+ async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
603
+ const semitoneOffset = this.calcSemitoneOffset(channel);
604
+ const note = new Note(noteNumber, velocity, startTime, instrumentKey);
605
+ note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
606
+ note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
607
+ this.setVolumeEnvelope(channel, note);
608
+ this.setFilterEnvelope(channel, note);
540
609
  if (channel.modulation > 0) {
541
- const vibratoDelay = startTime + channel.vibratoDelay;
542
- const vibratoAttack = vibratoDelay + 0.1;
543
- lfoGain = new GainNode(this.audioContext, {
544
- gain: 0,
545
- });
546
- lfoGain.gain
547
- .setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
548
- .exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
549
- channel.modulationEffect.lfo.connect(lfoGain);
550
- lfoGain.connect(bufferSource.detune);
610
+ const delayModLFO = startTime + instrumentKey.delayModLFO;
611
+ this.startModulation(channel, note, delayModLFO);
551
612
  }
552
- bufferSource.connect(filterNode);
553
- filterNode.connect(gainNode);
554
- bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
555
- return { bufferSource, gainNode, filterNode, lfoGain };
613
+ note.bufferSource.connect(note.filterNode);
614
+ note.filterNode.connect(note.gainNode);
615
+ note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
616
+ return note;
556
617
  }
557
618
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
558
619
  const channel = this.channels[channelNumber];
@@ -562,27 +623,17 @@ class MidyGM1 {
562
623
  return;
563
624
  const soundFont = this.soundFonts[soundFontIndex];
564
625
  const isSF3 = soundFont.parsed.info.version.major === 3;
565
- const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
566
- if (!noteInfo)
626
+ const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
627
+ if (!instrumentKey)
567
628
  return;
568
- const { bufferSource, gainNode, filterNode, lfoGain } = await this
569
- .createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
570
- this.connectNoteEffects(channel, gainNode);
629
+ const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
630
+ this.connectNoteEffects(channel, note.gainNode);
571
631
  const scheduledNotes = channel.scheduledNotes;
572
- const scheduledNote = {
573
- bufferSource,
574
- filterNode,
575
- gainNode,
576
- lfoGain,
577
- noteInfo,
578
- noteNumber,
579
- startTime,
580
- };
581
632
  if (scheduledNotes.has(noteNumber)) {
582
- scheduledNotes.get(noteNumber).push(scheduledNote);
633
+ scheduledNotes.get(noteNumber).push(note);
583
634
  }
584
635
  else {
585
- scheduledNotes.set(noteNumber, [scheduledNote]);
636
+ scheduledNotes.set(noteNumber, [note]);
586
637
  }
587
638
  }
588
639
  noteOn(channelNumber, noteNumber, velocity) {
@@ -602,15 +653,15 @@ class MidyGM1 {
602
653
  continue;
603
654
  if (targetNote.ending)
604
655
  continue;
605
- const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
656
+ const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
606
657
  const velocityRate = (velocity + 127) / 127;
607
- const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
658
+ const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
608
659
  gainNode.gain.cancelScheduledValues(stopTime);
609
660
  gainNode.gain.linearRampToValueAtTime(0, volEndTime);
610
661
  const maxFreq = this.audioContext.sampleRate / 2;
611
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
662
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
612
663
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
613
- const modEndTime = stopTime + noteInfo.modRelease * velocityRate;
664
+ const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
614
665
  filterNode.frequency
615
666
  .cancelScheduledValues(stopTime)
616
667
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
@@ -624,8 +675,10 @@ class MidyGM1 {
624
675
  bufferSource.disconnect(0);
625
676
  filterNode.disconnect(0);
626
677
  gainNode.disconnect(0);
627
- if (lfoGain)
628
- lfoGain.disconnect(0);
678
+ if (modLFOGain)
679
+ modLFOGain.disconnect(0);
680
+ if (modLFO)
681
+ modLFO.stop();
629
682
  resolve();
630
683
  };
631
684
  bufferSource.stop(volEndTime);
@@ -683,10 +736,10 @@ class MidyGM1 {
683
736
  const channel = this.channels[channelNumber];
684
737
  channel.pitchBend = (pitchBend - 8192) / 8192;
685
738
  const semitoneOffset = this.calcSemitoneOffset(channel);
686
- const activeNotes = this.getActiveNotes(channel);
739
+ const activeNotes = this.getActiveNotes(channel, now);
687
740
  activeNotes.forEach((activeNote) => {
688
- const { bufferSource, noteInfo, noteNumber } = activeNote;
689
- const playbackRate = calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
741
+ const { bufferSource, instrumentKey, noteNumber } = activeNote;
742
+ const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
690
743
  bufferSource.playbackRate
691
744
  .cancelScheduledValues(now)
692
745
  .setValueAtTime(playbackRate * pressure, now);
@@ -723,9 +776,20 @@ class MidyGM1 {
723
776
  }
724
777
  }
725
778
  setModulation(channelNumber, modulation) {
779
+ const now = this.audioContext.currentTime;
726
780
  const channel = this.channels[channelNumber];
727
781
  channel.modulation = (modulation / 127) *
728
782
  (channel.modulationDepthRange * 100);
783
+ const activeNotes = this.getActiveNotes(channel, now);
784
+ activeNotes.forEach((activeNote) => {
785
+ if (activeNote.modLFO) {
786
+ activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
787
+ channel.modulation, now);
788
+ }
789
+ else {
790
+ this.startModulation(channel, activeNote, now);
791
+ }
792
+ });
729
793
  }
730
794
  setVolume(channelNumber, volume) {
731
795
  const channel = this.channels[channelNumber];
@@ -788,8 +852,8 @@ class MidyGM1 {
788
852
  const velocity = 0;
789
853
  const stopPedal = true;
790
854
  const promises = [];
791
- channel.scheduledNotes.forEach((scheduledNotes) => {
792
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
855
+ channel.scheduledNotes.forEach((noteList) => {
856
+ const activeNote = this.getActiveNote(noteList, now);
793
857
  if (activeNote) {
794
858
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
795
859
  promises.push(notePromise);
@@ -806,8 +870,8 @@ class MidyGM1 {
806
870
  const velocity = 0;
807
871
  const stopPedal = false;
808
872
  const promises = [];
809
- channel.scheduledNotes.forEach((scheduledNotes) => {
810
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
873
+ channel.scheduledNotes.forEach((noteList) => {
874
+ const activeNote = this.getActiveNote(noteList, now);
811
875
  if (activeNote) {
812
876
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
813
877
  promises.push(notePromise);
@@ -902,9 +966,6 @@ Object.defineProperty(MidyGM1, "channelSettings", {
902
966
  value: {
903
967
  volume: 100 / 127,
904
968
  pan: 0,
905
- vibratoRate: 5,
906
- vibratoDepth: 0.5,
907
- vibratoDelay: 2.5,
908
969
  bank: 0,
909
970
  dataMSB: 0,
910
971
  dataLSB: 0,