@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"}
@@ -2,7 +2,45 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MidyGMLite = 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
+ this.noteNumber = noteNumber;
39
+ this.velocity = velocity;
40
+ this.startTime = startTime;
41
+ this.instrumentKey = instrumentKey;
42
+ }
43
+ }
6
44
  class MidyGMLite {
7
45
  constructor(audioContext) {
8
46
  Object.defineProperty(this, "ticksPerBeat", {
@@ -154,14 +192,11 @@ class MidyGMLite {
154
192
  const pannerNode = new StereoPannerNode(audioContext, {
155
193
  pan: MidyGMLite.channelSettings.pan,
156
194
  });
157
- const modulationEffect = this.createModulationEffect(audioContext);
158
- modulationEffect.lfo.start();
159
195
  pannerNode.connect(gainNode);
160
196
  gainNode.connect(this.masterGain);
161
197
  return {
162
198
  gainNode,
163
199
  pannerNode,
164
- modulationEffect,
165
200
  };
166
201
  }
167
202
  createChannels(audioContext) {
@@ -171,16 +206,15 @@ class MidyGMLite {
171
206
  ...MidyGMLite.effectSettings,
172
207
  ...this.setChannelAudioNodes(audioContext),
173
208
  scheduledNotes: new Map(),
174
- sostenutoNotes: new Map(),
175
209
  };
176
210
  });
177
211
  return channels;
178
212
  }
179
- async createNoteBuffer(noteInfo, isSF3) {
180
- const sampleEnd = noteInfo.sample.length + noteInfo.end;
213
+ async createNoteBuffer(instrumentKey, isSF3) {
214
+ const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
181
215
  if (isSF3) {
182
- const sample = new Uint8Array(noteInfo.sample.length);
183
- sample.set(noteInfo.sample);
216
+ const sample = new Uint8Array(instrumentKey.sample.length);
217
+ sample.set(instrumentKey.sample);
184
218
  const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
185
219
  for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
186
220
  const channelData = audioBuffer.getChannelData(channel);
@@ -189,26 +223,27 @@ class MidyGMLite {
189
223
  return audioBuffer;
190
224
  }
191
225
  else {
192
- const sample = noteInfo.sample.subarray(0, sampleEnd);
226
+ const sample = instrumentKey.sample.subarray(0, sampleEnd);
193
227
  const floatSample = this.convertToFloat32Array(sample);
194
228
  const audioBuffer = new AudioBuffer({
195
229
  numberOfChannels: 1,
196
230
  length: sample.length,
197
- sampleRate: noteInfo.sampleRate,
231
+ sampleRate: instrumentKey.sampleRate,
198
232
  });
199
233
  const channelData = audioBuffer.getChannelData(0);
200
234
  channelData.set(floatSample);
201
235
  return audioBuffer;
202
236
  }
203
237
  }
204
- async createNoteBufferNode(noteInfo, isSF3) {
238
+ async createNoteBufferNode(instrumentKey, isSF3) {
205
239
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
206
- const audioBuffer = await this.createNoteBuffer(noteInfo, isSF3);
240
+ const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
207
241
  bufferSource.buffer = audioBuffer;
208
- bufferSource.loop = noteInfo.sampleModes % 2 !== 0;
242
+ bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
209
243
  if (bufferSource.loop) {
210
- bufferSource.loopStart = noteInfo.loopStart / noteInfo.sampleRate;
211
- bufferSource.loopEnd = noteInfo.loopEnd / noteInfo.sampleRate;
244
+ bufferSource.loopStart = instrumentKey.loopStart /
245
+ instrumentKey.sampleRate;
246
+ bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
212
247
  }
213
248
  return bufferSource;
214
249
  }
@@ -226,9 +261,6 @@ class MidyGMLite {
226
261
  if (event.startTime > t + this.lookAhead)
227
262
  break;
228
263
  switch (event.type) {
229
- case "controller":
230
- this.handleControlChange(event.channel, event.controllerType, event.value);
231
- break;
232
264
  case "noteOn":
233
265
  if (event.velocity !== 0) {
234
266
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
@@ -242,9 +274,15 @@ class MidyGMLite {
242
274
  }
243
275
  break;
244
276
  }
277
+ case "controller":
278
+ this.handleControlChange(event.channel, event.controllerType, event.value);
279
+ break;
245
280
  case "programChange":
246
281
  this.handleProgramChange(event.channel, event.programNumber);
247
282
  break;
283
+ case "pitchBend":
284
+ this.handlePitchBend(event.channel, event.value);
285
+ break;
248
286
  case "sysEx":
249
287
  this.handleSysEx(event.data);
250
288
  }
@@ -447,30 +485,26 @@ class MidyGMLite {
447
485
  const now = this.audioContext.currentTime;
448
486
  return this.resumeTime + now - this.startTime - this.startDelay;
449
487
  }
450
- getActiveNotes(channel) {
488
+ getActiveNotes(channel, time) {
451
489
  const activeNotes = new Map();
452
- channel.scheduledNotes.forEach((scheduledNotes) => {
453
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
490
+ channel.scheduledNotes.forEach((noteList) => {
491
+ const activeNote = this.getActiveNote(noteList, time);
454
492
  if (activeNote) {
455
493
  activeNotes.set(activeNote.noteNumber, activeNote);
456
494
  }
457
495
  });
458
496
  return activeNotes;
459
497
  }
460
- getActiveChannelNotes(scheduledNotes) {
461
- for (let i = 0; i < scheduledNotes; i++) {
462
- const scheduledNote = scheduledNotes[i];
463
- if (scheduledNote)
464
- return scheduledNote;
498
+ getActiveNote(noteList, time) {
499
+ for (let i = noteList.length - 1; i >= 0; i--) {
500
+ const note = noteList[i];
501
+ if (!note)
502
+ return;
503
+ if (time < note.startTime)
504
+ continue;
505
+ return (note.ending) ? null : note;
465
506
  }
466
- }
467
- createModulationEffect(audioContext) {
468
- const lfo = new OscillatorNode(audioContext, {
469
- frequency: 5,
470
- });
471
- return {
472
- lfo,
473
- };
507
+ return noteList[0];
474
508
  }
475
509
  connectNoteEffects(channel, gainNode) {
476
510
  gainNode.connect(channel.pannerNode);
@@ -481,70 +515,92 @@ class MidyGMLite {
481
515
  centToHz(cent) {
482
516
  return 8.176 * Math.pow(2, cent / 1200);
483
517
  }
484
- async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
485
- const semitoneOffset = channel.pitchBend * channel.pitchBendRange;
486
- const playbackRate = noteInfo.playbackRate(noteNumber) *
518
+ calcSemitoneOffset(channel) {
519
+ return channel.pitchBend * channel.pitchBendRange;
520
+ }
521
+ calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
522
+ return instrumentKey.playbackRate(noteNumber) *
487
523
  Math.pow(2, semitoneOffset / 12);
488
- const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
489
- bufferSource.playbackRate.value = playbackRate;
490
- // volume envelope
491
- const gainNode = new GainNode(this.audioContext, {
524
+ }
525
+ setVolumeEnvelope(channel, note) {
526
+ const { instrumentKey, startTime, velocity } = note;
527
+ note.gainNode = new GainNode(this.audioContext, {
492
528
  gain: 0,
493
529
  });
494
530
  let volume = (velocity / 127) * channel.volume * channel.expression;
495
531
  if (volume === 0)
496
532
  volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
497
- const attackVolume = this.cbToRatio(-noteInfo.initialAttenuation) * volume;
498
- const sustainVolume = attackVolume * (1 - noteInfo.volSustain);
499
- const volDelay = startTime + noteInfo.volDelay;
500
- const volAttack = volDelay + noteInfo.volAttack;
501
- const volHold = volAttack + noteInfo.volHold;
502
- const volDecay = volHold + noteInfo.volDecay;
503
- gainNode.gain
533
+ const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
534
+ volume;
535
+ const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
536
+ const volDelay = startTime + instrumentKey.volDelay;
537
+ const volAttack = volDelay + instrumentKey.volAttack;
538
+ const volHold = volAttack + instrumentKey.volHold;
539
+ const volDecay = volHold + instrumentKey.volDecay;
540
+ note.gainNode.gain
504
541
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
505
542
  .exponentialRampToValueAtTime(attackVolume, volAttack)
506
543
  .setValueAtTime(attackVolume, volHold)
507
544
  .linearRampToValueAtTime(sustainVolume, volDecay);
508
- // filter envelope
545
+ }
546
+ setFilterEnvelope(channel, note) {
547
+ const { instrumentKey, startTime, noteNumber } = note;
548
+ const softPedalFactor = 1 -
549
+ (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
509
550
  const maxFreq = this.audioContext.sampleRate / 2;
510
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
511
- const peekFreq = this.centToHz(noteInfo.initialFilterFc + noteInfo.modEnvToFilterFc);
512
- const sustainFreq = baseFreq +
513
- (peekFreq - baseFreq) * (1 - noteInfo.modSustain);
551
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
552
+ softPedalFactor;
553
+ const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
554
+ const sustainFreq = (baseFreq +
555
+ (peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
556
+ const modDelay = startTime + instrumentKey.modDelay;
557
+ const modAttack = modDelay + instrumentKey.modAttack;
558
+ const modHold = modAttack + instrumentKey.modHold;
559
+ const modDecay = modHold + instrumentKey.modDecay;
514
560
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
515
561
  const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
516
562
  const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
517
- const filterNode = new BiquadFilterNode(this.audioContext, {
563
+ note.filterNode = new BiquadFilterNode(this.audioContext, {
518
564
  type: "lowpass",
519
- Q: noteInfo.initialFilterQ / 10, // dB
565
+ Q: instrumentKey.initialFilterQ / 10, // dB
520
566
  frequency: adjustedBaseFreq,
521
567
  });
522
- const modDelay = startTime + noteInfo.modDelay;
523
- const modAttack = modDelay + noteInfo.modAttack;
524
- const modHold = modAttack + noteInfo.modHold;
525
- const modDecay = modHold + noteInfo.modDecay;
526
- filterNode.frequency
568
+ note.filterNode.frequency
527
569
  .setValueAtTime(adjustedBaseFreq, modDelay)
528
570
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
529
571
  .setValueAtTime(adjustedPeekFreq, modHold)
530
572
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
531
- let lfoGain;
573
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
574
+ }
575
+ startModulation(channel, note, time) {
576
+ const { instrumentKey } = note;
577
+ note.modLFOGain = new GainNode(this.audioContext, {
578
+ gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
579
+ });
580
+ note.modLFO = new OscillatorNode(this.audioContext, {
581
+ frequency: this.centToHz(instrumentKey.freqModLFO),
582
+ });
583
+ note.modLFO.start(time);
584
+ note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
585
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
586
+ note.modLFO.connect(note.modLFOGain);
587
+ note.modLFOGain.connect(note.bufferSource.detune);
588
+ }
589
+ async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
590
+ const semitoneOffset = this.calcSemitoneOffset(channel);
591
+ const note = new Note(noteNumber, velocity, startTime, instrumentKey);
592
+ note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
593
+ note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
594
+ this.setVolumeEnvelope(channel, note);
595
+ this.setFilterEnvelope(channel, note);
532
596
  if (channel.modulation > 0) {
533
- const vibratoDelay = startTime + channel.vibratoDelay;
534
- const vibratoAttack = vibratoDelay + 0.1;
535
- lfoGain = new GainNode(this.audioContext, {
536
- gain: 0,
537
- });
538
- lfoGain.gain
539
- .setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
540
- .exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
541
- channel.modulationEffect.lfo.connect(lfoGain);
542
- lfoGain.connect(bufferSource.detune);
597
+ const delayModLFO = startTime + instrumentKey.delayModLFO;
598
+ this.startModulation(channel, note, delayModLFO);
543
599
  }
544
- bufferSource.connect(filterNode);
545
- filterNode.connect(gainNode);
546
- bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
547
- return { bufferSource, gainNode, filterNode, lfoGain };
600
+ note.bufferSource.connect(note.filterNode);
601
+ note.filterNode.connect(note.gainNode);
602
+ note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
603
+ return note;
548
604
  }
549
605
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
550
606
  const channel = this.channels[channelNumber];
@@ -554,27 +610,17 @@ class MidyGMLite {
554
610
  return;
555
611
  const soundFont = this.soundFonts[soundFontIndex];
556
612
  const isSF3 = soundFont.parsed.info.version.major === 3;
557
- const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
558
- if (!noteInfo)
613
+ const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
614
+ if (!instrumentKey)
559
615
  return;
560
- const { bufferSource, gainNode, filterNode, lfoGain } = await this
561
- .createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
562
- this.connectNoteEffects(channel, gainNode);
616
+ const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
617
+ this.connectNoteEffects(channel, note.gainNode);
563
618
  const scheduledNotes = channel.scheduledNotes;
564
- const scheduledNote = {
565
- bufferSource,
566
- filterNode,
567
- gainNode,
568
- lfoGain,
569
- noteInfo,
570
- noteNumber,
571
- startTime,
572
- };
573
619
  if (scheduledNotes.has(noteNumber)) {
574
- scheduledNotes.get(noteNumber).push(scheduledNote);
620
+ scheduledNotes.get(noteNumber).push(note);
575
621
  }
576
622
  else {
577
- scheduledNotes.set(noteNumber, [scheduledNote]);
623
+ scheduledNotes.set(noteNumber, [note]);
578
624
  }
579
625
  }
580
626
  noteOn(channelNumber, noteNumber, velocity) {
@@ -594,15 +640,15 @@ class MidyGMLite {
594
640
  continue;
595
641
  if (targetNote.ending)
596
642
  continue;
597
- const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
643
+ const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
598
644
  const velocityRate = (velocity + 127) / 127;
599
- const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
645
+ const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
600
646
  gainNode.gain.cancelScheduledValues(stopTime);
601
647
  gainNode.gain.linearRampToValueAtTime(0, volEndTime);
602
648
  const maxFreq = this.audioContext.sampleRate / 2;
603
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
649
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
604
650
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
605
- const modEndTime = stopTime + noteInfo.modRelease * velocityRate;
651
+ const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
606
652
  filterNode.frequency
607
653
  .cancelScheduledValues(stopTime)
608
654
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
@@ -616,8 +662,10 @@ class MidyGMLite {
616
662
  bufferSource.disconnect(0);
617
663
  filterNode.disconnect(0);
618
664
  gainNode.disconnect(0);
619
- if (lfoGain)
620
- lfoGain.disconnect(0);
665
+ if (modLFOGain)
666
+ modLFOGain.disconnect(0);
667
+ if (modLFO)
668
+ modLFO.stop();
621
669
  resolve();
622
670
  };
623
671
  bufferSource.stop(volEndTime);
@@ -652,46 +700,37 @@ class MidyGMLite {
652
700
  return this.releaseNote(channelNumber, data1, data2);
653
701
  case 0x90:
654
702
  return this.noteOn(channelNumber, data1, data2);
655
- case 0xA0:
656
- return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
657
703
  case 0xB0:
658
704
  return this.handleControlChange(channelNumber, data1, data2);
659
705
  case 0xC0:
660
706
  return this.handleProgramChange(channelNumber, data1);
661
- case 0xD0:
662
- return this.handleChannelPressure(channelNumber, data1);
663
707
  case 0xE0:
664
- return this.handlePitchBend(channelNumber, data1, data2);
708
+ return this.handlePitchBendMessage(channelNumber, data1, data2);
665
709
  default:
666
710
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
667
711
  }
668
712
  }
669
- handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
670
- const now = this.audioContext.currentTime;
671
- const channel = this.channels[channelNumber];
672
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
673
- pressure /= 127;
674
- if (scheduledNotes) {
675
- scheduledNotes.forEach((scheduledNote) => {
676
- if (scheduledNote) {
677
- const { initialAttenuation } = scheduledNote.noteInfo;
678
- const gain = this.cbToRatio(-initialAttenuation) * pressure;
679
- scheduledNote.gainNode.gain.cancelScheduledValues(now);
680
- scheduledNote.gainNode.gain.setValueAtTime(gain, now);
681
- }
682
- });
683
- }
684
- }
685
713
  handleProgramChange(channelNumber, program) {
686
714
  const channel = this.channels[channelNumber];
687
715
  channel.program = program;
688
716
  }
689
- handleChannelPressure(channelNumber, pressure) {
690
- this.channels[channelNumber].channelPressure = pressure;
717
+ handlePitchBendMessage(channelNumber, lsb, msb) {
718
+ const pitchBend = msb * 128 + lsb;
719
+ this.handlePitchBend(channelNumber, pitchBend);
691
720
  }
692
- handlePitchBend(channelNumber, lsb, msb) {
693
- const pitchBend = (msb * 128 + lsb - 8192) / 8192;
694
- this.channels[channelNumber].pitchBend = pitchBend;
721
+ handlePitchBend(channelNumber, pitchBend) {
722
+ const now = this.audioContext.currentTime;
723
+ const channel = this.channels[channelNumber];
724
+ channel.pitchBend = (pitchBend - 8192) / 8192;
725
+ const semitoneOffset = this.calcSemitoneOffset(channel);
726
+ const activeNotes = this.getActiveNotes(channel, now);
727
+ activeNotes.forEach((activeNote) => {
728
+ const { bufferSource, instrumentKey, noteNumber } = activeNote;
729
+ const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
730
+ bufferSource.playbackRate
731
+ .cancelScheduledValues(now)
732
+ .setValueAtTime(playbackRate * pressure, now);
733
+ });
695
734
  }
696
735
  handleControlChange(channelNumber, controller, value) {
697
736
  switch (controller) {
@@ -724,9 +763,20 @@ class MidyGMLite {
724
763
  }
725
764
  }
726
765
  setModulation(channelNumber, modulation) {
766
+ const now = this.audioContext.currentTime;
727
767
  const channel = this.channels[channelNumber];
728
768
  channel.modulation = (modulation / 127) *
729
769
  (channel.modulationDepthRange * 100);
770
+ const activeNotes = this.getActiveNotes(channel, now);
771
+ activeNotes.forEach((activeNote) => {
772
+ if (activeNote.modLFO) {
773
+ activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
774
+ channel.modulation, now);
775
+ }
776
+ else {
777
+ this.startModulation(channel, activeNote, now);
778
+ }
779
+ });
730
780
  }
731
781
  setVolume(channelNumber, volume) {
732
782
  const channel = this.channels[channelNumber];
@@ -783,8 +833,8 @@ class MidyGMLite {
783
833
  const velocity = 0;
784
834
  const stopPedal = true;
785
835
  const promises = [];
786
- channel.scheduledNotes.forEach((scheduledNotes) => {
787
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
836
+ channel.scheduledNotes.forEach((noteList) => {
837
+ const activeNote = this.getActiveNote(noteList, now);
788
838
  if (activeNote) {
789
839
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
790
840
  promises.push(notePromise);
@@ -801,8 +851,8 @@ class MidyGMLite {
801
851
  const velocity = 0;
802
852
  const stopPedal = false;
803
853
  const promises = [];
804
- channel.scheduledNotes.forEach((scheduledNotes) => {
805
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
854
+ channel.scheduledNotes.forEach((noteList) => {
855
+ const activeNote = this.getActiveNote(noteList, now);
806
856
  if (activeNote) {
807
857
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
808
858
  promises.push(notePromise);
@@ -851,13 +901,18 @@ class MidyGMLite {
851
901
  }
852
902
  }
853
903
  handleMasterVolumeSysEx(data) {
854
- const volume = (data[5] * 128 + data[4] - 8192) / 8192;
904
+ const volume = (data[5] * 128 + data[4]) / 16383;
855
905
  this.handleMasterVolume(volume);
856
906
  }
857
907
  handleMasterVolume(volume) {
858
- const now = this.audioContext.currentTime;
859
- this.masterGain.gain.cancelScheduledValues(now);
860
- this.masterGain.gain.setValueAtTime(volume * volume, now);
908
+ if (volume < 0 && 1 < volume) {
909
+ console.error("Master Volume is out of range");
910
+ }
911
+ else {
912
+ const now = this.audioContext.currentTime;
913
+ this.masterGain.gain.cancelScheduledValues(now);
914
+ this.masterGain.gain.setValueAtTime(volume * volume, now);
915
+ }
861
916
  }
862
917
  handleExclusiveMessage(data) {
863
918
  console.warn(`Unsupported Exclusive Message ${data}`);
@@ -892,9 +947,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
892
947
  value: {
893
948
  volume: 100 / 127,
894
949
  pan: 0,
895
- vibratoRate: 5,
896
- vibratoDepth: 0.5,
897
- vibratoDelay: 2.5,
898
950
  bank: 0,
899
951
  dataMSB: 0,
900
952
  dataLSB: 0,