@marmooo/midy 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ exports.MidyGM2 = void 0;
4
4
  const midi_file_1 = require("midi-file");
5
5
  const soundfont_parser_1 = require("@marmooo/soundfont-parser");
6
6
  const ogg_vorbis_1 = require("@wasm-audio-decoders/ogg-vorbis");
7
+ const reverb_js_1 = require("./reverb.js");
7
8
  // Cache mode
8
9
  // - "none" for full real-time control (dynamic CC, LFO, pitch)
9
10
  // - "ads" for real-time playback with higher cache hit rate
@@ -193,7 +194,13 @@ class Note {
193
194
  }
194
195
  }
195
196
  class Channel {
196
- constructor(audioNodes, settings) {
197
+ constructor(channelNumber, audioNodes, settings) {
198
+ Object.defineProperty(this, "channelNumber", {
199
+ enumerable: true,
200
+ configurable: true,
201
+ writable: true,
202
+ value: 0
203
+ });
197
204
  Object.defineProperty(this, "isDrum", {
198
205
  enumerable: true,
199
206
  configurable: true,
@@ -338,6 +345,7 @@ class Channel {
338
345
  writable: true,
339
346
  value: null
340
347
  });
348
+ this.channelNumber = channelNumber;
341
349
  Object.assign(this, audioNodes);
342
350
  Object.assign(this, settings);
343
351
  this.state = new ControllerState();
@@ -586,7 +594,7 @@ class MidyGM2 extends EventTarget {
586
594
  configurable: true,
587
595
  writable: true,
588
596
  value: {
589
- algorithm: "SchroederReverb",
597
+ algorithm: "Schroeder",
590
598
  time: this.getReverbTime(64),
591
599
  feedback: 0.8,
592
600
  }
@@ -841,9 +849,9 @@ class MidyGM2 extends EventTarget {
841
849
  this.controlChangeHandlers = this.createControlChangeHandlers();
842
850
  this.keyBasedControllerHandlers = this.createKeyBasedControllerHandlers();
843
851
  this.effectHandlers = this.createEffectHandlers();
844
- this.channels = this.createChannels(audioContext);
845
- this.reverbEffect = this.createReverbEffect(audioContext);
846
- this.chorusEffect = this.createChorusEffect(audioContext);
852
+ this.channels = this.createChannels();
853
+ this.reverbEffect = this.createReverbEffect(this.reverb.algorithm);
854
+ this.chorusEffect = this.createChorusEffect();
847
855
  this.chorusEffect.output.connect(this.masterVolume);
848
856
  this.reverbEffect.output.connect(this.masterVolume);
849
857
  this.masterVolume.connect(audioContext.destination);
@@ -1121,6 +1129,8 @@ class MidyGM2 extends EventTarget {
1121
1129
  return;
1122
1130
  const soundFont = this.soundFonts[soundFontIndex];
1123
1131
  const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
1132
+ if (!voice)
1133
+ return;
1124
1134
  const { instrument, sampleID } = voice.generators;
1125
1135
  return soundFontIndex * (2 ** 31) + instrument * (2 ** 24) +
1126
1136
  (sampleID << 8);
@@ -1135,9 +1145,10 @@ class MidyGM2 extends EventTarget {
1135
1145
  merger.connect(this.masterVolume);
1136
1146
  return { gainL, gainR, merger };
1137
1147
  }
1138
- createChannels(audioContext) {
1148
+ createChannels() {
1139
1149
  const settings = this.constructor.channelSettings;
1140
- return Array.from({ length: this.numChannels }, () => new Channel(this.createChannelAudioNodes(audioContext), settings));
1150
+ const audioContext = this.audioContext;
1151
+ return Array.from({ length: this.numChannels }, (_, ch) => new Channel(ch, this.createChannelAudioNodes(audioContext), settings));
1141
1152
  }
1142
1153
  decodeOggVorbis(sample) {
1143
1154
  const task = decoderQueue.then(async () => {
@@ -1657,6 +1668,7 @@ class MidyGM2 extends EventTarget {
1657
1668
  continue;
1658
1669
  const soundFont = this.soundFonts[soundFontIndex];
1659
1670
  const fakeChannel = {
1671
+ channelNumber: ch,
1660
1672
  state: { array: renderControllerStates[ch].slice() },
1661
1673
  programNumber,
1662
1674
  isDrum,
@@ -1888,62 +1900,6 @@ class MidyGM2 extends EventTarget {
1888
1900
  }
1889
1901
  await Promise.all(tasks);
1890
1902
  }
1891
- createConvolutionReverbImpulse(audioContext, decay, preDecay) {
1892
- const sampleRate = audioContext.sampleRate;
1893
- const length = sampleRate * decay;
1894
- const impulse = new AudioBuffer({
1895
- numberOfChannels: 2,
1896
- length,
1897
- sampleRate,
1898
- });
1899
- const preDecayLength = Math.min(sampleRate * preDecay, length);
1900
- for (let channel = 0; channel < impulse.numberOfChannels; channel++) {
1901
- const channelData = impulse.getChannelData(channel);
1902
- for (let i = 0; i < preDecayLength; i++) {
1903
- channelData[i] = Math.random() * 2 - 1;
1904
- }
1905
- const attenuationFactor = 1 / (sampleRate * decay);
1906
- for (let i = preDecayLength; i < length; i++) {
1907
- const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
1908
- channelData[i] = (Math.random() * 2 - 1) * attenuation;
1909
- }
1910
- }
1911
- return impulse;
1912
- }
1913
- createConvolutionReverb(audioContext, impulse) {
1914
- const convolverNode = new ConvolverNode(audioContext, {
1915
- buffer: impulse,
1916
- });
1917
- return {
1918
- input: convolverNode,
1919
- output: convolverNode,
1920
- convolverNode,
1921
- };
1922
- }
1923
- createCombFilter(audioContext, input, delay, feedback) {
1924
- const delayNode = new DelayNode(audioContext, {
1925
- maxDelayTime: delay,
1926
- delayTime: delay,
1927
- });
1928
- const feedbackGain = new GainNode(audioContext, { gain: feedback });
1929
- input.connect(delayNode);
1930
- delayNode.connect(feedbackGain);
1931
- feedbackGain.connect(delayNode);
1932
- return delayNode;
1933
- }
1934
- createAllpassFilter(audioContext, input, delay, feedback) {
1935
- const delayNode = new DelayNode(audioContext, {
1936
- maxDelayTime: delay,
1937
- delayTime: delay,
1938
- });
1939
- const feedbackGain = new GainNode(audioContext, { gain: feedback });
1940
- const passGain = new GainNode(audioContext, { gain: 1 - feedback });
1941
- input.connect(delayNode);
1942
- delayNode.connect(feedbackGain);
1943
- feedbackGain.connect(delayNode);
1944
- delayNode.connect(passGain);
1945
- return passGain;
1946
- }
1947
1903
  generateDistributedArray(center, count, varianceRatio = 0.1, randomness = 0.05) {
1948
1904
  const variance = center * varianceRatio;
1949
1905
  const array = new Array(count);
@@ -1954,40 +1910,60 @@ class MidyGM2 extends EventTarget {
1954
1910
  }
1955
1911
  return array;
1956
1912
  }
1957
- // https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
1958
- // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
1959
- createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
1960
- const input = new GainNode(audioContext);
1961
- const mergerGain = new GainNode(audioContext);
1962
- for (let i = 0; i < combDelays.length; i++) {
1963
- const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
1964
- comb.connect(mergerGain);
1965
- }
1966
- const allpasses = [];
1967
- for (let i = 0; i < allpassDelays.length; i++) {
1968
- const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
1969
- allpasses.push(allpass);
1970
- }
1971
- const output = allpasses.at(-1);
1972
- return { input, output };
1913
+ setReverbEffect(algorithm) {
1914
+ if (this.reverbEffect)
1915
+ this.reverbEffect.output.disconnect();
1916
+ this.reverbEffect = this.createReverbEffect(algorithm);
1917
+ this.reverb.algorithm = algorithm;
1973
1918
  }
1974
- createReverbEffect(audioContext) {
1975
- const { algorithm, time: rt60, feedback } = this.reverb;
1919
+ createReverbEffect(algorithm) {
1920
+ const { audioContext, reverb } = this;
1921
+ const { time: rt60, feedback } = reverb;
1976
1922
  switch (algorithm) {
1977
- case "ConvolutionReverb": {
1978
- const impulse = this.createConvolutionReverbImpulse(audioContext, rt60, this.calcDelay(rt60, feedback));
1979
- return this.createConvolutionReverb(audioContext, impulse);
1923
+ case "Convolution": {
1924
+ const impulse = (0, reverb_js_1.createConvolutionReverbImpulse)(audioContext, rt60, this.calcDelay(rt60, feedback));
1925
+ return (0, reverb_js_1.createConvolutionReverb)(audioContext, impulse);
1980
1926
  }
1981
- case "SchroederReverb": {
1927
+ case "Schroeder": {
1982
1928
  const combFeedbacks = this.generateDistributedArray(feedback, 4);
1983
- const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
1929
+ const combDelays = combFeedbacks.map((fb) => this.calcDelay(rt60, fb));
1984
1930
  const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
1985
- const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
1986
- return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
1931
+ const allpassDelays = allpassFeedbacks.map((fb) => this.calcDelay(rt60, fb));
1932
+ return (0, reverb_js_1.createSchroederReverb)(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
1933
+ }
1934
+ case "Moorer":
1935
+ return (0, reverb_js_1.createMoorerReverbDefault)(audioContext, {
1936
+ rt60,
1937
+ damping: 1 - feedback,
1938
+ });
1939
+ case "FDN":
1940
+ return (0, reverb_js_1.createFDNDefault)(audioContext, { rt60, damping: 1 - feedback });
1941
+ case "Dattorro": {
1942
+ const decay = feedback * 0.28 + 0.7;
1943
+ return (0, reverb_js_1.createDattorroReverb)(audioContext, {
1944
+ decay,
1945
+ damping: 1 - feedback,
1946
+ });
1947
+ }
1948
+ case "Freeverb": {
1949
+ const damping = 1 - feedback;
1950
+ const { inputL, inputR, outputL, outputR } = (0, reverb_js_1.createFreeverb)(audioContext, { roomSize: feedback, damping });
1951
+ const inputMerger = new GainNode(audioContext);
1952
+ const outputMerger = new GainNode(audioContext, { gain: 0.5 });
1953
+ inputMerger.connect(inputL);
1954
+ inputMerger.connect(inputR);
1955
+ outputL.connect(outputMerger);
1956
+ outputR.connect(outputMerger);
1957
+ return { input: inputMerger, output: outputMerger };
1987
1958
  }
1959
+ case "VelvetNoise":
1960
+ return (0, reverb_js_1.createVelvetNoiseReverb)(audioContext, rt60);
1961
+ default:
1962
+ throw new Error(`Unknown reverb algorithm: ${algorithm}`);
1988
1963
  }
1989
1964
  }
1990
- createChorusEffect(audioContext) {
1965
+ createChorusEffect() {
1966
+ const audioContext = this.audioContext;
1991
1967
  const input = new GainNode(audioContext);
1992
1968
  const output = new GainNode(audioContext);
1993
1969
  const sendGain = new GainNode(audioContext);
@@ -2286,15 +2262,16 @@ class MidyGM2 extends EventTarget {
2286
2262
  note.modLfoToVolume.connect(volumeTarget.gain);
2287
2263
  }
2288
2264
  startVibrato(channel, note, scheduleTime) {
2265
+ const audioContext = this.audioContext;
2289
2266
  const { voiceParams } = note;
2290
2267
  const state = channel.state;
2291
2268
  const vibratoRate = state.vibratoRate * 2;
2292
2269
  const vibratoDelay = state.vibratoDelay * 2;
2293
- note.vibLfo = new OscillatorNode(this.audioContext, {
2270
+ note.vibLfo = new OscillatorNode(audioContext, {
2294
2271
  frequency: this.centToHz(voiceParams.freqVibLFO) * vibratoRate,
2295
2272
  });
2296
2273
  note.vibLfo.start(note.startTime + voiceParams.delayVibLFO * vibratoDelay);
2297
- note.vibLfoToPitch = new GainNode(this.audioContext);
2274
+ note.vibLfoToPitch = new GainNode(audioContext);
2298
2275
  this.setVibLfoToPitch(channel, note, scheduleTime);
2299
2276
  note.vibLfo.connect(note.vibLfoToPitch);
2300
2277
  note.vibLfoToPitch.connect(note.bufferSource.detune);
@@ -2305,25 +2282,29 @@ class MidyGM2 extends EventTarget {
2305
2282
  const volHold = volAttack + voiceParams.volHold;
2306
2283
  const decayDuration = voiceParams.volDecay;
2307
2284
  const adsDuration = volHold + decayDuration * decayCurve * 5;
2308
- const loopStartTime = voiceParams.loopStart / voiceParams.sampleRate;
2309
- const loopDuration = isLoop
2285
+ const sampleLoopStart = voiceParams.loopStart / voiceParams.sampleRate;
2286
+ const sampleLoopDuration = isLoop
2310
2287
  ? (voiceParams.loopEnd - voiceParams.loopStart) / voiceParams.sampleRate
2311
2288
  : 0;
2312
- const loopCount = isLoop && adsDuration > loopStartTime
2313
- ? Math.ceil((adsDuration - loopStartTime) / loopDuration)
2289
+ const playbackRate = voiceParams.playbackRate;
2290
+ const outputLoopStart = sampleLoopStart / playbackRate;
2291
+ const outputLoopDuration = sampleLoopDuration / playbackRate;
2292
+ const loopCount = isLoop && adsDuration > outputLoopStart
2293
+ ? Math.ceil((adsDuration - outputLoopStart) / outputLoopDuration)
2314
2294
  : 0;
2315
- const alignedLoopStart = loopStartTime + loopCount * loopDuration;
2295
+ const alignedLoopStart = outputLoopStart + loopCount * outputLoopDuration;
2316
2296
  const renderDuration = isLoop
2317
- ? alignedLoopStart + loopDuration
2318
- : audioBuffer.duration;
2319
- const offlineContext = new OfflineAudioContext(audioBuffer.numberOfChannels, Math.ceil(renderDuration * this.audioContext.sampleRate), this.audioContext.sampleRate);
2297
+ ? alignedLoopStart + outputLoopDuration
2298
+ : audioBuffer.duration / playbackRate;
2299
+ const sampleRate = this.audioContext.sampleRate;
2300
+ const offlineContext = new OfflineAudioContext(audioBuffer.numberOfChannels, Math.ceil(renderDuration * sampleRate), sampleRate);
2320
2301
  const bufferSource = new AudioBufferSourceNode(offlineContext);
2321
2302
  bufferSource.buffer = audioBuffer;
2322
- bufferSource.playbackRate.value = voiceParams.playbackRate;
2303
+ bufferSource.playbackRate.value = playbackRate;
2323
2304
  bufferSource.loop = isLoop;
2324
2305
  if (isLoop) {
2325
- bufferSource.loopStart = loopStartTime;
2326
- bufferSource.loopEnd = loopStartTime + loopDuration;
2306
+ bufferSource.loopStart = sampleLoopStart;
2307
+ bufferSource.loopEnd = sampleLoopStart + sampleLoopDuration;
2327
2308
  }
2328
2309
  const initialFreq = this.clampCutoffFrequency(this.centToHz(voiceParams.initialFilterFc));
2329
2310
  const filterEnvelopeNode = new BiquadFilterNode(offlineContext, {
@@ -2355,7 +2336,7 @@ class MidyGM2 extends EventTarget {
2355
2336
  isLoop,
2356
2337
  adsDuration,
2357
2338
  loopStart: alignedLoopStart,
2358
- loopDuration,
2339
+ loopDuration: outputLoopDuration,
2359
2340
  });
2360
2341
  }
2361
2342
  async createAdsrRenderedBuffer(channel, note, voiceParams, audioBuffer, noteDuration) {
@@ -2453,7 +2434,7 @@ class MidyGM2 extends EventTarget {
2453
2434
  }
2454
2435
  async createFullRenderedBuffer(channel, note, voiceParams, noteDuration, noteEvent = {}) {
2455
2436
  const { startTime: noteStartTime = 0, events: noteEvents = [] } = noteEvent;
2456
- const ch = note.channel ?? 0;
2437
+ const ch = channel.channelNumber;
2457
2438
  const releaseEndDuration = voiceParams.volRelease * releaseCurve * 5;
2458
2439
  const totalDuration = noteDuration + releaseEndDuration;
2459
2440
  const sampleRate = this.audioContext.sampleRate;
@@ -2505,7 +2486,7 @@ class MidyGM2 extends EventTarget {
2505
2486
  const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
2506
2487
  if (!realtime) {
2507
2488
  if (cacheMode === "note") {
2508
- return await this.getFullCachedBuffer(note, audioBufferId);
2489
+ return await this.getFullCachedBuffer(channel, note, audioBufferId);
2509
2490
  }
2510
2491
  else if (cacheMode === "adsr") {
2511
2492
  return await this.getAdsrCachedBuffer(channel, note, audioBufferId);
@@ -2596,7 +2577,7 @@ class MidyGM2 extends EventTarget {
2596
2577
  durationMap.set(cacheKey, renderPromise);
2597
2578
  return await renderPromise;
2598
2579
  }
2599
- async getFullCachedBuffer(note, audioBufferId) {
2580
+ async getFullCachedBuffer(channel, note, audioBufferId) {
2600
2581
  const voiceParams = note.voiceParams;
2601
2582
  const timelineIndex = note.timelineIndex;
2602
2583
  const noteEvent = this.noteOnEvents.get(timelineIndex);
@@ -2621,7 +2602,7 @@ class MidyGM2 extends EventTarget {
2621
2602
  }
2622
2603
  const renderPromise = (async () => {
2623
2604
  try {
2624
- const rendered = await this.createFullRenderedBuffer(this.channels[note.channel], note, voiceParams, noteDuration, noteEvent);
2605
+ const rendered = await this.createFullRenderedBuffer(channel, note, voiceParams, noteDuration, noteEvent);
2625
2606
  durationMap.set(cacheKey, rendered);
2626
2607
  return rendered;
2627
2608
  }
@@ -2648,7 +2629,6 @@ class MidyGM2 extends EventTarget {
2648
2629
  note.renderedBuffer = isRendered ? audioBuffer : null;
2649
2630
  note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
2650
2631
  note.volumeNode = new GainNode(audioContext);
2651
- note.volumeNode.gain.setValueAtTime(1, now);
2652
2632
  const cacheMode = this.cacheMode;
2653
2633
  const isFullCached = isRendered && audioBuffer.isFull === true;
2654
2634
  if (cacheMode === "none") {
@@ -2785,9 +2765,6 @@ class MidyGM2 extends EventTarget {
2785
2765
  startTime = this.audioContext.currentTime;
2786
2766
  const note = new Note(noteNumber, velocity, startTime);
2787
2767
  note.channel = channelNumber;
2788
- const channel = this.channels[channelNumber];
2789
- note.index = channel.scheduledNotes.length;
2790
- channel.scheduledNotes.push(note);
2791
2768
  return note;
2792
2769
  }
2793
2770
  async setupNote(channelNumber, note, startTime) {
@@ -2810,6 +2787,8 @@ class MidyGM2 extends EventTarget {
2810
2787
  note.voice = soundFont.getVoice(bank, programNumber, note.noteNumber, note.velocity);
2811
2788
  if (!note.voice)
2812
2789
  return;
2790
+ note.index = channel.scheduledNotes.length;
2791
+ channel.scheduledNotes.push(note);
2813
2792
  await this.setNoteAudioNode(channel, note, realtime);
2814
2793
  this.setNoteRouting(channelNumber, note, startTime);
2815
2794
  note.resolveReady();
@@ -2868,18 +2847,8 @@ class MidyGM2 extends EventTarget {
2868
2847
  const volRelease = endTime + volDuration;
2869
2848
  note.volumeNode.gain
2870
2849
  .cancelScheduledValues(endTime)
2871
- .setValueAtTime(1, endTime)
2872
2850
  .setTargetAtTime(0, endTime, volDuration * releaseCurve);
2873
- return new Promise((resolve) => {
2874
- this.scheduleTask(() => {
2875
- note.bufferSource.loop = false;
2876
- note.bufferSource.stop(volRelease);
2877
- this.disconnectNote(note);
2878
- channel.scheduledNotes[note.index] = undefined;
2879
- this.releaseFullCache(note);
2880
- resolve();
2881
- }, volRelease);
2882
- });
2851
+ note.bufferSource.stop(volRelease);
2883
2852
  }
2884
2853
  else {
2885
2854
  const now = this.audioContext.currentTime;
@@ -2889,15 +2858,16 @@ class MidyGM2 extends EventTarget {
2889
2858
  this.releaseFullCache(note);
2890
2859
  return Promise.resolve();
2891
2860
  }
2892
- return new Promise((resolve) => {
2893
- this.scheduleTask(() => {
2894
- this.disconnectNote(note);
2895
- channel.scheduledNotes[note.index] = undefined;
2896
- this.releaseFullCache(note);
2897
- resolve();
2898
- }, naturalEndTime);
2899
- });
2861
+ note.bufferSource.stop(naturalEndTime);
2900
2862
  }
2863
+ return new Promise((resolve) => {
2864
+ note.bufferSource.onended = () => {
2865
+ this.disconnectNote(note);
2866
+ channel.scheduledNotes[note.index] = undefined;
2867
+ this.releaseFullCache(note);
2868
+ resolve();
2869
+ };
2870
+ });
2901
2871
  }
2902
2872
  const volDuration = note.voiceParams.volRelease;
2903
2873
  const volRelease = endTime + volDuration;
@@ -2918,45 +2888,33 @@ class MidyGM2 extends EventTarget {
2918
2888
  const noteOffTime = note.startTime + (rb.noteDuration ?? 0);
2919
2889
  const isEarlyCut = endTime < noteOffTime;
2920
2890
  if (isEarlyCut) {
2921
- const volRelease = endTime + volDuration;
2922
2891
  note.volumeNode.gain
2923
2892
  .cancelScheduledValues(endTime)
2924
- .setValueAtTime(1, endTime)
2925
2893
  .setTargetAtTime(0, endTime, volDuration * releaseCurve);
2926
- return new Promise((resolve) => {
2927
- this.scheduleTask(() => {
2928
- note.bufferSource.stop(volRelease);
2929
- this.disconnectNote(note);
2930
- channel.scheduledNotes[note.index] = undefined;
2931
- resolve();
2932
- }, volRelease);
2933
- });
2894
+ note.bufferSource.stop(volRelease);
2934
2895
  }
2935
2896
  else {
2936
- return new Promise((resolve) => {
2937
- this.scheduleTask(() => {
2938
- note.bufferSource.stop();
2939
- this.disconnectNote(note);
2940
- channel.scheduledNotes[note.index] = undefined;
2941
- resolve();
2942
- }, naturalEndTime);
2943
- });
2897
+ note.bufferSource.stop(naturalEndTime);
2944
2898
  }
2899
+ return new Promise((resolve) => {
2900
+ note.bufferSource.onended = () => {
2901
+ this.disconnectNote(note);
2902
+ channel.scheduledNotes[note.index] = undefined;
2903
+ resolve();
2904
+ };
2905
+ });
2945
2906
  }
2946
2907
  note.volumeNode.gain
2947
2908
  .cancelScheduledValues(endTime)
2948
- .setValueAtTime(1, endTime)
2949
2909
  .setTargetAtTime(0, endTime, volDuration * releaseCurve);
2950
2910
  }
2911
+ note.bufferSource.stop(volRelease);
2951
2912
  return new Promise((resolve) => {
2952
- this.scheduleTask(() => {
2953
- const bufferSource = note.bufferSource;
2954
- bufferSource.loop = false;
2955
- bufferSource.stop(volRelease);
2913
+ note.bufferSource.onended = () => {
2956
2914
  this.disconnectNote(note);
2957
2915
  channel.scheduledNotes[note.index] = undefined;
2958
2916
  resolve();
2959
- }, volRelease);
2917
+ };
2960
2918
  });
2961
2919
  }
2962
2920
  noteOff(channelNumber, noteNumber, _velocity, endTime, force) {
@@ -3937,9 +3895,10 @@ class MidyGM2 extends EventTarget {
3937
3895
  setMasterVolume(value, scheduleTime) {
3938
3896
  if (!(0 <= scheduleTime))
3939
3897
  scheduleTime = this.audioContext.currentTime;
3898
+ const timeConstant = this.perceptualSmoothingTime / 5; // 99.3% (5 * tau)
3940
3899
  this.masterVolume.gain
3941
- .cancelScheduledValues(scheduleTime)
3942
- .setValueAtTime(value * value, scheduleTime);
3900
+ .cancelAndHoldAtTime(scheduleTime)
3901
+ .setTargetAtTime(value * value, scheduleTime, timeConstant);
3943
3902
  }
3944
3903
  handleMasterFineTuningSysEx(data, scheduleTime) {
3945
3904
  const value = (data[5] * 128 + data[4]) / 16383;
@@ -4004,7 +3963,7 @@ class MidyGM2 extends EventTarget {
4004
3963
  setReverbType(type) {
4005
3964
  this.reverb.time = this.getReverbTimeFromType(type);
4006
3965
  this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
4007
- this.reverbEffect = this.createReverbEffect(this.audioContext);
3966
+ this.reverbEffect = this.setReverbEffect(this.reverb.algorithm);
4008
3967
  }
4009
3968
  getReverbTimeFromType(type) {
4010
3969
  switch (type) {
@@ -4026,7 +3985,7 @@ class MidyGM2 extends EventTarget {
4026
3985
  }
4027
3986
  setReverbTime(value) {
4028
3987
  this.reverb.time = this.getReverbTime(value);
4029
- this.reverbEffect = this.createReverbEffect(this.audioContext);
3988
+ this.reverbEffect = this.setReverbEffect(this.reverb.algorithm);
4030
3989
  }
4031
3990
  getReverbTime(value) {
4032
3991
  return Math.exp((value - 40) * 0.025);
@@ -129,7 +129,7 @@ export class MidyGMLite extends EventTarget {
129
129
  getAudioBuffer(channel: any, note: any, realtime: any): Promise<any>;
130
130
  getAdsCachedBuffer(channel: any, note: any, audioBufferId: any, realtime: any): Promise<any>;
131
131
  getAdsrCachedBuffer(note: any, audioBufferId: any): Promise<any>;
132
- getFullCachedBuffer(note: any, audioBufferId: any): Promise<any>;
132
+ getFullCachedBuffer(channel: any, note: any, audioBufferId: any): Promise<any>;
133
133
  setNoteAudioNode(channel: any, note: any, realtime: any): Promise<any>;
134
134
  handleExclusiveClass(note: any, channelNumber: any, startTime: any): void;
135
135
  handleDrumExclusiveClass(note: any, channelNumber: any, startTime: any): void;
@@ -204,7 +204,8 @@ export class MidyGMLite extends EventTarget {
204
204
  scheduleTask(callback: any, scheduleTime: any): Promise<any>;
205
205
  }
206
206
  declare class Channel {
207
- constructor(audioNodes: any, settings: any);
207
+ constructor(channelNumber: any, audioNodes: any, settings: any);
208
+ channelNumber: number;
208
209
  isDrum: boolean;
209
210
  programNumber: number;
210
211
  scheduleIndex: number;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"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"}