@marmooo/midy 0.0.3 → 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 +27 -36
  5. package/esm/midy-GM1.d.ts.map +1 -1
  6. package/esm/midy-GM1.js +199 -135
  7. package/esm/midy-GM2.d.ts +51 -35
  8. package/esm/midy-GM2.d.ts.map +1 -1
  9. package/esm/midy-GM2.js +234 -141
  10. package/esm/midy-GMLite.d.ts +25 -36
  11. package/esm/midy-GMLite.d.ts.map +1 -1
  12. package/esm/midy-GMLite.js +187 -135
  13. package/esm/midy.d.ts +68 -24
  14. package/esm/midy.d.ts.map +1 -1
  15. package/esm/midy.js +274 -141
  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 +27 -36
  21. package/script/midy-GM1.d.ts.map +1 -1
  22. package/script/midy-GM1.js +199 -135
  23. package/script/midy-GM2.d.ts +51 -35
  24. package/script/midy-GM2.d.ts.map +1 -1
  25. package/script/midy-GM2.js +234 -141
  26. package/script/midy-GMLite.d.ts +25 -36
  27. package/script/midy-GMLite.d.ts.map +1 -1
  28. package/script/midy-GMLite.js +187 -135
  29. package/script/midy.d.ts +68 -24
  30. package/script/midy.d.ts.map +1 -1
  31. package/script/midy.js +274 -141
  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 MidyGMLite {
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;
@@ -42,12 +39,8 @@ export class MidyGMLite {
42
39
  masterGain: any;
43
40
  channels: {
44
41
  scheduledNotes: Map<any, any>;
45
- sostenutoNotes: Map<any, any>;
46
42
  gainNode: any;
47
43
  pannerNode: any;
48
- modulationEffect: {
49
- lfo: any;
50
- };
51
44
  expression: number;
52
45
  modulation: number;
53
46
  sustainPedal: boolean;
@@ -56,9 +49,6 @@ export class MidyGMLite {
56
49
  pitchBendRange: number;
57
50
  volume: number;
58
51
  pan: number;
59
- vibratoRate: number;
60
- vibratoDepth: number;
61
- vibratoDelay: number;
62
52
  bank: number;
63
53
  dataMSB: number;
64
54
  dataLSB: number;
@@ -73,18 +63,11 @@ export class MidyGMLite {
73
63
  setChannelAudioNodes(audioContext: any): {
74
64
  gainNode: any;
75
65
  pannerNode: any;
76
- modulationEffect: {
77
- lfo: any;
78
- };
79
66
  };
80
67
  createChannels(audioContext: any): {
81
68
  scheduledNotes: Map<any, any>;
82
- sostenutoNotes: Map<any, any>;
83
69
  gainNode: any;
84
70
  pannerNode: any;
85
- modulationEffect: {
86
- lfo: any;
87
- };
88
71
  expression: number;
89
72
  modulation: number;
90
73
  sustainPedal: boolean;
@@ -93,9 +76,6 @@ export class MidyGMLite {
93
76
  pitchBendRange: number;
94
77
  volume: number;
95
78
  pan: number;
96
- vibratoRate: number;
97
- vibratoDepth: number;
98
- vibratoDelay: number;
99
79
  bank: number;
100
80
  dataMSB: number;
101
81
  dataLSB: number;
@@ -103,8 +83,8 @@ export class MidyGMLite {
103
83
  pitchBend: number;
104
84
  modulationDepthRange: number;
105
85
  }[];
106
- createNoteBuffer(noteInfo: any, isSF3: any): Promise<any>;
107
- createNoteBufferNode(noteInfo: any, isSF3: any): Promise<any>;
86
+ createNoteBuffer(instrumentKey: any, isSF3: any): Promise<any>;
87
+ createNoteBufferNode(instrumentKey: any, isSF3: any): Promise<any>;
108
88
  convertToFloat32Array(uint8Array: any): Float32Array;
109
89
  scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
110
90
  getQueueIndex(second: any): number;
@@ -123,30 +103,26 @@ export class MidyGMLite {
123
103
  seekTo(second: any): void;
124
104
  calcTotalTime(): number;
125
105
  currentTime(): number;
126
- getActiveNotes(channel: any): Map<any, any>;
127
- getActiveChannelNotes(scheduledNotes: any): any;
128
- createModulationEffect(audioContext: any): {
129
- lfo: any;
130
- };
106
+ getActiveNotes(channel: any, time: any): Map<any, any>;
107
+ getActiveNote(noteList: any, time: any): any;
131
108
  connectNoteEffects(channel: any, gainNode: any): void;
132
109
  cbToRatio(cb: any): number;
133
110
  centToHz(cent: any): number;
134
- createNoteAudioChain(channel: any, noteInfo: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<{
135
- bufferSource: any;
136
- gainNode: any;
137
- filterNode: any;
138
- lfoGain: any;
139
- }>;
111
+ calcSemitoneOffset(channel: any): number;
112
+ calcPlaybackRate(instrumentKey: any, noteNumber: any, semitoneOffset: any): number;
113
+ setVolumeEnvelope(channel: any, note: any): void;
114
+ setFilterEnvelope(channel: any, note: any): void;
115
+ startModulation(channel: any, note: any, time: any): void;
116
+ createNote(channel: any, instrumentKey: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
140
117
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
141
118
  noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
142
119
  scheduleNoteRelease(channelNumber: any, noteNumber: any, velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
143
120
  releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
144
121
  releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
145
122
  handleMIDIMessage(statusByte: any, data1: any, data2: any): void | any[] | Promise<any>;
146
- handlePolyphonicKeyPressure(channelNumber: any, noteNumber: any, pressure: any): void;
147
123
  handleProgramChange(channelNumber: any, program: any): void;
148
- handleChannelPressure(channelNumber: any, pressure: any): void;
149
- handlePitchBend(channelNumber: any, lsb: any, msb: any): void;
124
+ handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
125
+ handlePitchBend(channelNumber: any, pitchBend: any): void;
150
126
  handleControlChange(channelNumber: any, controller: any, value: any): void | any[];
151
127
  setModulation(channelNumber: any, modulation: any): void;
152
128
  setVolume(channelNumber: any, volume: any): void;
@@ -169,4 +145,17 @@ export class MidyGMLite {
169
145
  handleSysEx(data: any): void;
170
146
  scheduleTask(callback: any, startTime: any): Promise<any>;
171
147
  }
148
+ declare class Note {
149
+ constructor(noteNumber: any, velocity: any, startTime: any, instrumentKey: any);
150
+ bufferSource: any;
151
+ gainNode: any;
152
+ filterNode: any;
153
+ modLFO: any;
154
+ modLFOGain: any;
155
+ noteNumber: any;
156
+ velocity: any;
157
+ startTime: any;
158
+ instrumentKey: any;
159
+ }
160
+ export {};
172
161
  //# sourceMappingURL=midy-GMLite.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAMA;IAmBE;;;;;;;;;;;;MAYE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA/CD,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;IA0BhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;MAgBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;QAWC;IAED,0DAyBC;IAED,8DAUC;IAED,qDAOC;IAED,2EA4CC;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;;;;;OA4EC;IAED,kGAuCC;IAED,0EAGC;IAED,sIA4CC;IAED,0FAGC;IAED,kEAeC;IAED,wFAqBC;IAED,sFAeC;IAED,4DAGC;IAED,+DAEC;IAED,8DAGC;IAED,mFA+BC;IAED,yDAIC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,sCAKC;IAED,sDAMC;IAED,gDAEC;IAED,gDAEC;IAED,+DAcC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAIC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF"}
1
+ {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAqBA;IAmBE;;;;;;;;;MASE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA5CD,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;IAuBhB,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,yCAEC;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,+DAcC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AA59BD;IAOE,gFAKC;IAXD,kBAAa;IACb,cAAS;IACT,gBAAW;IACX,YAAO;IACP,gBAAW;IAGT,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
@@ -1,5 +1,43 @@
1
1
  import { parseMidi } from "./deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js";
2
- import { parse, SoundFont, } from "./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.1/+esm.js";
2
+ import { parse, SoundFont, } from "./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.js";
3
+ class Note {
4
+ constructor(noteNumber, velocity, startTime, instrumentKey) {
5
+ Object.defineProperty(this, "bufferSource", {
6
+ enumerable: true,
7
+ configurable: true,
8
+ writable: true,
9
+ value: void 0
10
+ });
11
+ Object.defineProperty(this, "gainNode", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: void 0
16
+ });
17
+ Object.defineProperty(this, "filterNode", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: void 0
22
+ });
23
+ Object.defineProperty(this, "modLFO", {
24
+ enumerable: true,
25
+ configurable: true,
26
+ writable: true,
27
+ value: void 0
28
+ });
29
+ Object.defineProperty(this, "modLFOGain", {
30
+ enumerable: true,
31
+ configurable: true,
32
+ writable: true,
33
+ value: void 0
34
+ });
35
+ this.noteNumber = noteNumber;
36
+ this.velocity = velocity;
37
+ this.startTime = startTime;
38
+ this.instrumentKey = instrumentKey;
39
+ }
40
+ }
3
41
  export class MidyGMLite {
4
42
  constructor(audioContext) {
5
43
  Object.defineProperty(this, "ticksPerBeat", {
@@ -151,14 +189,11 @@ export class MidyGMLite {
151
189
  const pannerNode = new StereoPannerNode(audioContext, {
152
190
  pan: MidyGMLite.channelSettings.pan,
153
191
  });
154
- const modulationEffect = this.createModulationEffect(audioContext);
155
- modulationEffect.lfo.start();
156
192
  pannerNode.connect(gainNode);
157
193
  gainNode.connect(this.masterGain);
158
194
  return {
159
195
  gainNode,
160
196
  pannerNode,
161
- modulationEffect,
162
197
  };
163
198
  }
164
199
  createChannels(audioContext) {
@@ -168,16 +203,15 @@ export class MidyGMLite {
168
203
  ...MidyGMLite.effectSettings,
169
204
  ...this.setChannelAudioNodes(audioContext),
170
205
  scheduledNotes: new Map(),
171
- sostenutoNotes: new Map(),
172
206
  };
173
207
  });
174
208
  return channels;
175
209
  }
176
- async createNoteBuffer(noteInfo, isSF3) {
177
- const sampleEnd = noteInfo.sample.length + noteInfo.end;
210
+ async createNoteBuffer(instrumentKey, isSF3) {
211
+ const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
178
212
  if (isSF3) {
179
- const sample = new Uint8Array(noteInfo.sample.length);
180
- sample.set(noteInfo.sample);
213
+ const sample = new Uint8Array(instrumentKey.sample.length);
214
+ sample.set(instrumentKey.sample);
181
215
  const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
182
216
  for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
183
217
  const channelData = audioBuffer.getChannelData(channel);
@@ -186,26 +220,27 @@ export class MidyGMLite {
186
220
  return audioBuffer;
187
221
  }
188
222
  else {
189
- const sample = noteInfo.sample.subarray(0, sampleEnd);
223
+ const sample = instrumentKey.sample.subarray(0, sampleEnd);
190
224
  const floatSample = this.convertToFloat32Array(sample);
191
225
  const audioBuffer = new AudioBuffer({
192
226
  numberOfChannels: 1,
193
227
  length: sample.length,
194
- sampleRate: noteInfo.sampleRate,
228
+ sampleRate: instrumentKey.sampleRate,
195
229
  });
196
230
  const channelData = audioBuffer.getChannelData(0);
197
231
  channelData.set(floatSample);
198
232
  return audioBuffer;
199
233
  }
200
234
  }
201
- async createNoteBufferNode(noteInfo, isSF3) {
235
+ async createNoteBufferNode(instrumentKey, isSF3) {
202
236
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
203
- const audioBuffer = await this.createNoteBuffer(noteInfo, isSF3);
237
+ const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
204
238
  bufferSource.buffer = audioBuffer;
205
- bufferSource.loop = noteInfo.sampleModes % 2 !== 0;
239
+ bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
206
240
  if (bufferSource.loop) {
207
- bufferSource.loopStart = noteInfo.loopStart / noteInfo.sampleRate;
208
- bufferSource.loopEnd = noteInfo.loopEnd / noteInfo.sampleRate;
241
+ bufferSource.loopStart = instrumentKey.loopStart /
242
+ instrumentKey.sampleRate;
243
+ bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
209
244
  }
210
245
  return bufferSource;
211
246
  }
@@ -223,9 +258,6 @@ export class MidyGMLite {
223
258
  if (event.startTime > t + this.lookAhead)
224
259
  break;
225
260
  switch (event.type) {
226
- case "controller":
227
- this.handleControlChange(event.channel, event.controllerType, event.value);
228
- break;
229
261
  case "noteOn":
230
262
  if (event.velocity !== 0) {
231
263
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
@@ -239,9 +271,15 @@ export class MidyGMLite {
239
271
  }
240
272
  break;
241
273
  }
274
+ case "controller":
275
+ this.handleControlChange(event.channel, event.controllerType, event.value);
276
+ break;
242
277
  case "programChange":
243
278
  this.handleProgramChange(event.channel, event.programNumber);
244
279
  break;
280
+ case "pitchBend":
281
+ this.handlePitchBend(event.channel, event.value);
282
+ break;
245
283
  case "sysEx":
246
284
  this.handleSysEx(event.data);
247
285
  }
@@ -444,30 +482,26 @@ export class MidyGMLite {
444
482
  const now = this.audioContext.currentTime;
445
483
  return this.resumeTime + now - this.startTime - this.startDelay;
446
484
  }
447
- getActiveNotes(channel) {
485
+ getActiveNotes(channel, time) {
448
486
  const activeNotes = new Map();
449
- channel.scheduledNotes.forEach((scheduledNotes) => {
450
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
487
+ channel.scheduledNotes.forEach((noteList) => {
488
+ const activeNote = this.getActiveNote(noteList, time);
451
489
  if (activeNote) {
452
490
  activeNotes.set(activeNote.noteNumber, activeNote);
453
491
  }
454
492
  });
455
493
  return activeNotes;
456
494
  }
457
- getActiveChannelNotes(scheduledNotes) {
458
- for (let i = 0; i < scheduledNotes; i++) {
459
- const scheduledNote = scheduledNotes[i];
460
- if (scheduledNote)
461
- return scheduledNote;
495
+ getActiveNote(noteList, time) {
496
+ for (let i = noteList.length - 1; i >= 0; i--) {
497
+ const note = noteList[i];
498
+ if (!note)
499
+ return;
500
+ if (time < note.startTime)
501
+ continue;
502
+ return (note.ending) ? null : note;
462
503
  }
463
- }
464
- createModulationEffect(audioContext) {
465
- const lfo = new OscillatorNode(audioContext, {
466
- frequency: 5,
467
- });
468
- return {
469
- lfo,
470
- };
504
+ return noteList[0];
471
505
  }
472
506
  connectNoteEffects(channel, gainNode) {
473
507
  gainNode.connect(channel.pannerNode);
@@ -478,70 +512,92 @@ export class MidyGMLite {
478
512
  centToHz(cent) {
479
513
  return 8.176 * Math.pow(2, cent / 1200);
480
514
  }
481
- async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
482
- const semitoneOffset = channel.pitchBend * channel.pitchBendRange;
483
- const playbackRate = noteInfo.playbackRate(noteNumber) *
515
+ calcSemitoneOffset(channel) {
516
+ return channel.pitchBend * channel.pitchBendRange;
517
+ }
518
+ calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
519
+ return instrumentKey.playbackRate(noteNumber) *
484
520
  Math.pow(2, semitoneOffset / 12);
485
- const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
486
- bufferSource.playbackRate.value = playbackRate;
487
- // volume envelope
488
- const gainNode = new GainNode(this.audioContext, {
521
+ }
522
+ setVolumeEnvelope(channel, note) {
523
+ const { instrumentKey, startTime, velocity } = note;
524
+ note.gainNode = new GainNode(this.audioContext, {
489
525
  gain: 0,
490
526
  });
491
527
  let volume = (velocity / 127) * channel.volume * channel.expression;
492
528
  if (volume === 0)
493
529
  volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
494
- const attackVolume = this.cbToRatio(-noteInfo.initialAttenuation) * volume;
495
- const sustainVolume = attackVolume * (1 - noteInfo.volSustain);
496
- const volDelay = startTime + noteInfo.volDelay;
497
- const volAttack = volDelay + noteInfo.volAttack;
498
- const volHold = volAttack + noteInfo.volHold;
499
- const volDecay = volHold + noteInfo.volDecay;
500
- gainNode.gain
530
+ const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
531
+ volume;
532
+ const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
533
+ const volDelay = startTime + instrumentKey.volDelay;
534
+ const volAttack = volDelay + instrumentKey.volAttack;
535
+ const volHold = volAttack + instrumentKey.volHold;
536
+ const volDecay = volHold + instrumentKey.volDecay;
537
+ note.gainNode.gain
501
538
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
502
539
  .exponentialRampToValueAtTime(attackVolume, volAttack)
503
540
  .setValueAtTime(attackVolume, volHold)
504
541
  .linearRampToValueAtTime(sustainVolume, volDecay);
505
- // filter envelope
542
+ }
543
+ setFilterEnvelope(channel, note) {
544
+ const { instrumentKey, startTime, noteNumber } = note;
545
+ const softPedalFactor = 1 -
546
+ (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
506
547
  const maxFreq = this.audioContext.sampleRate / 2;
507
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
508
- const peekFreq = this.centToHz(noteInfo.initialFilterFc + noteInfo.modEnvToFilterFc);
509
- const sustainFreq = baseFreq +
510
- (peekFreq - baseFreq) * (1 - noteInfo.modSustain);
548
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
549
+ softPedalFactor;
550
+ const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
551
+ const sustainFreq = (baseFreq +
552
+ (peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
553
+ const modDelay = startTime + instrumentKey.modDelay;
554
+ const modAttack = modDelay + instrumentKey.modAttack;
555
+ const modHold = modAttack + instrumentKey.modHold;
556
+ const modDecay = modHold + instrumentKey.modDecay;
511
557
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
512
558
  const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
513
559
  const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
514
- const filterNode = new BiquadFilterNode(this.audioContext, {
560
+ note.filterNode = new BiquadFilterNode(this.audioContext, {
515
561
  type: "lowpass",
516
- Q: noteInfo.initialFilterQ / 10, // dB
562
+ Q: instrumentKey.initialFilterQ / 10, // dB
517
563
  frequency: adjustedBaseFreq,
518
564
  });
519
- const modDelay = startTime + noteInfo.modDelay;
520
- const modAttack = modDelay + noteInfo.modAttack;
521
- const modHold = modAttack + noteInfo.modHold;
522
- const modDecay = modHold + noteInfo.modDecay;
523
- filterNode.frequency
565
+ note.filterNode.frequency
524
566
  .setValueAtTime(adjustedBaseFreq, modDelay)
525
567
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
526
568
  .setValueAtTime(adjustedPeekFreq, modHold)
527
569
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
528
- let lfoGain;
570
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
571
+ }
572
+ startModulation(channel, note, time) {
573
+ const { instrumentKey } = note;
574
+ note.modLFOGain = new GainNode(this.audioContext, {
575
+ gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
576
+ });
577
+ note.modLFO = new OscillatorNode(this.audioContext, {
578
+ frequency: this.centToHz(instrumentKey.freqModLFO),
579
+ });
580
+ note.modLFO.start(time);
581
+ note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
582
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
583
+ note.modLFO.connect(note.modLFOGain);
584
+ note.modLFOGain.connect(note.bufferSource.detune);
585
+ }
586
+ async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
587
+ const semitoneOffset = this.calcSemitoneOffset(channel);
588
+ const note = new Note(noteNumber, velocity, startTime, instrumentKey);
589
+ note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
590
+ note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
591
+ this.setVolumeEnvelope(channel, note);
592
+ this.setFilterEnvelope(channel, note);
529
593
  if (channel.modulation > 0) {
530
- const vibratoDelay = startTime + channel.vibratoDelay;
531
- const vibratoAttack = vibratoDelay + 0.1;
532
- lfoGain = new GainNode(this.audioContext, {
533
- gain: 0,
534
- });
535
- lfoGain.gain
536
- .setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
537
- .exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
538
- channel.modulationEffect.lfo.connect(lfoGain);
539
- lfoGain.connect(bufferSource.detune);
594
+ const delayModLFO = startTime + instrumentKey.delayModLFO;
595
+ this.startModulation(channel, note, delayModLFO);
540
596
  }
541
- bufferSource.connect(filterNode);
542
- filterNode.connect(gainNode);
543
- bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
544
- return { bufferSource, gainNode, filterNode, lfoGain };
597
+ note.bufferSource.connect(note.filterNode);
598
+ note.filterNode.connect(note.gainNode);
599
+ note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
600
+ return note;
545
601
  }
546
602
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
547
603
  const channel = this.channels[channelNumber];
@@ -551,27 +607,17 @@ export class MidyGMLite {
551
607
  return;
552
608
  const soundFont = this.soundFonts[soundFontIndex];
553
609
  const isSF3 = soundFont.parsed.info.version.major === 3;
554
- const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
555
- if (!noteInfo)
610
+ const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
611
+ if (!instrumentKey)
556
612
  return;
557
- const { bufferSource, gainNode, filterNode, lfoGain } = await this
558
- .createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
559
- this.connectNoteEffects(channel, gainNode);
613
+ const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
614
+ this.connectNoteEffects(channel, note.gainNode);
560
615
  const scheduledNotes = channel.scheduledNotes;
561
- const scheduledNote = {
562
- bufferSource,
563
- filterNode,
564
- gainNode,
565
- lfoGain,
566
- noteInfo,
567
- noteNumber,
568
- startTime,
569
- };
570
616
  if (scheduledNotes.has(noteNumber)) {
571
- scheduledNotes.get(noteNumber).push(scheduledNote);
617
+ scheduledNotes.get(noteNumber).push(note);
572
618
  }
573
619
  else {
574
- scheduledNotes.set(noteNumber, [scheduledNote]);
620
+ scheduledNotes.set(noteNumber, [note]);
575
621
  }
576
622
  }
577
623
  noteOn(channelNumber, noteNumber, velocity) {
@@ -591,15 +637,15 @@ export class MidyGMLite {
591
637
  continue;
592
638
  if (targetNote.ending)
593
639
  continue;
594
- const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
640
+ const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
595
641
  const velocityRate = (velocity + 127) / 127;
596
- const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
642
+ const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
597
643
  gainNode.gain.cancelScheduledValues(stopTime);
598
644
  gainNode.gain.linearRampToValueAtTime(0, volEndTime);
599
645
  const maxFreq = this.audioContext.sampleRate / 2;
600
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
646
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
601
647
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
602
- const modEndTime = stopTime + noteInfo.modRelease * velocityRate;
648
+ const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
603
649
  filterNode.frequency
604
650
  .cancelScheduledValues(stopTime)
605
651
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
@@ -613,8 +659,10 @@ export class MidyGMLite {
613
659
  bufferSource.disconnect(0);
614
660
  filterNode.disconnect(0);
615
661
  gainNode.disconnect(0);
616
- if (lfoGain)
617
- lfoGain.disconnect(0);
662
+ if (modLFOGain)
663
+ modLFOGain.disconnect(0);
664
+ if (modLFO)
665
+ modLFO.stop();
618
666
  resolve();
619
667
  };
620
668
  bufferSource.stop(volEndTime);
@@ -649,46 +697,37 @@ export class MidyGMLite {
649
697
  return this.releaseNote(channelNumber, data1, data2);
650
698
  case 0x90:
651
699
  return this.noteOn(channelNumber, data1, data2);
652
- case 0xA0:
653
- return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
654
700
  case 0xB0:
655
701
  return this.handleControlChange(channelNumber, data1, data2);
656
702
  case 0xC0:
657
703
  return this.handleProgramChange(channelNumber, data1);
658
- case 0xD0:
659
- return this.handleChannelPressure(channelNumber, data1);
660
704
  case 0xE0:
661
- return this.handlePitchBend(channelNumber, data1, data2);
705
+ return this.handlePitchBendMessage(channelNumber, data1, data2);
662
706
  default:
663
707
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
664
708
  }
665
709
  }
666
- handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
667
- const now = this.audioContext.currentTime;
668
- const channel = this.channels[channelNumber];
669
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
670
- pressure /= 127;
671
- if (scheduledNotes) {
672
- scheduledNotes.forEach((scheduledNote) => {
673
- if (scheduledNote) {
674
- const { initialAttenuation } = scheduledNote.noteInfo;
675
- const gain = this.cbToRatio(-initialAttenuation) * pressure;
676
- scheduledNote.gainNode.gain.cancelScheduledValues(now);
677
- scheduledNote.gainNode.gain.setValueAtTime(gain, now);
678
- }
679
- });
680
- }
681
- }
682
710
  handleProgramChange(channelNumber, program) {
683
711
  const channel = this.channels[channelNumber];
684
712
  channel.program = program;
685
713
  }
686
- handleChannelPressure(channelNumber, pressure) {
687
- this.channels[channelNumber].channelPressure = pressure;
714
+ handlePitchBendMessage(channelNumber, lsb, msb) {
715
+ const pitchBend = msb * 128 + lsb;
716
+ this.handlePitchBend(channelNumber, pitchBend);
688
717
  }
689
- handlePitchBend(channelNumber, lsb, msb) {
690
- const pitchBend = (msb * 128 + lsb - 8192) / 8192;
691
- this.channels[channelNumber].pitchBend = pitchBend;
718
+ handlePitchBend(channelNumber, pitchBend) {
719
+ const now = this.audioContext.currentTime;
720
+ const channel = this.channels[channelNumber];
721
+ channel.pitchBend = (pitchBend - 8192) / 8192;
722
+ const semitoneOffset = this.calcSemitoneOffset(channel);
723
+ const activeNotes = this.getActiveNotes(channel, now);
724
+ activeNotes.forEach((activeNote) => {
725
+ const { bufferSource, instrumentKey, noteNumber } = activeNote;
726
+ const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
727
+ bufferSource.playbackRate
728
+ .cancelScheduledValues(now)
729
+ .setValueAtTime(playbackRate * pressure, now);
730
+ });
692
731
  }
693
732
  handleControlChange(channelNumber, controller, value) {
694
733
  switch (controller) {
@@ -721,9 +760,20 @@ export class MidyGMLite {
721
760
  }
722
761
  }
723
762
  setModulation(channelNumber, modulation) {
763
+ const now = this.audioContext.currentTime;
724
764
  const channel = this.channels[channelNumber];
725
765
  channel.modulation = (modulation / 127) *
726
766
  (channel.modulationDepthRange * 100);
767
+ const activeNotes = this.getActiveNotes(channel, now);
768
+ activeNotes.forEach((activeNote) => {
769
+ if (activeNote.modLFO) {
770
+ activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
771
+ channel.modulation, now);
772
+ }
773
+ else {
774
+ this.startModulation(channel, activeNote, now);
775
+ }
776
+ });
727
777
  }
728
778
  setVolume(channelNumber, volume) {
729
779
  const channel = this.channels[channelNumber];
@@ -780,8 +830,8 @@ export class MidyGMLite {
780
830
  const velocity = 0;
781
831
  const stopPedal = true;
782
832
  const promises = [];
783
- channel.scheduledNotes.forEach((scheduledNotes) => {
784
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
833
+ channel.scheduledNotes.forEach((noteList) => {
834
+ const activeNote = this.getActiveNote(noteList, now);
785
835
  if (activeNote) {
786
836
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
787
837
  promises.push(notePromise);
@@ -798,8 +848,8 @@ export class MidyGMLite {
798
848
  const velocity = 0;
799
849
  const stopPedal = false;
800
850
  const promises = [];
801
- channel.scheduledNotes.forEach((scheduledNotes) => {
802
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
851
+ channel.scheduledNotes.forEach((noteList) => {
852
+ const activeNote = this.getActiveNote(noteList, now);
803
853
  if (activeNote) {
804
854
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
805
855
  promises.push(notePromise);
@@ -848,13 +898,18 @@ export class MidyGMLite {
848
898
  }
849
899
  }
850
900
  handleMasterVolumeSysEx(data) {
851
- const volume = (data[5] * 128 + data[4] - 8192) / 8192;
901
+ const volume = (data[5] * 128 + data[4]) / 16383;
852
902
  this.handleMasterVolume(volume);
853
903
  }
854
904
  handleMasterVolume(volume) {
855
- const now = this.audioContext.currentTime;
856
- this.masterGain.gain.cancelScheduledValues(now);
857
- this.masterGain.gain.setValueAtTime(volume * volume, now);
905
+ if (volume < 0 && 1 < volume) {
906
+ console.error("Master Volume is out of range");
907
+ }
908
+ else {
909
+ const now = this.audioContext.currentTime;
910
+ this.masterGain.gain.cancelScheduledValues(now);
911
+ this.masterGain.gain.setValueAtTime(volume * volume, now);
912
+ }
858
913
  }
859
914
  handleExclusiveMessage(data) {
860
915
  console.warn(`Unsupported Exclusive Message ${data}`);
@@ -888,9 +943,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
888
943
  value: {
889
944
  volume: 100 / 127,
890
945
  pan: 0,
891
- vibratoRate: 5,
892
- vibratoDepth: 0.5,
893
- vibratoDelay: 2.5,
894
946
  bank: 0,
895
947
  dataMSB: 0,
896
948
  dataLSB: 0,