@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.
package/esm/midy-GM2.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { parseMidi } from "midi-file";
2
2
  import { parse, SoundFont } from "@marmooo/soundfont-parser";
3
3
  import { OggVorbisDecoderWebWorker } from "@wasm-audio-decoders/ogg-vorbis";
4
+ import { createConvolutionReverb, createConvolutionReverbImpulse, createDattorroReverb, createFDNDefault, createFreeverb, createMoorerReverbDefault, createSchroederReverb, createVelvetNoiseReverb, } from "./reverb.js";
4
5
  // Cache mode
5
6
  // - "none" for full real-time control (dynamic CC, LFO, pitch)
6
7
  // - "ads" for real-time playback with higher cache hit rate
@@ -190,7 +191,13 @@ class Note {
190
191
  }
191
192
  }
192
193
  class Channel {
193
- constructor(audioNodes, settings) {
194
+ constructor(channelNumber, audioNodes, settings) {
195
+ Object.defineProperty(this, "channelNumber", {
196
+ enumerable: true,
197
+ configurable: true,
198
+ writable: true,
199
+ value: 0
200
+ });
194
201
  Object.defineProperty(this, "isDrum", {
195
202
  enumerable: true,
196
203
  configurable: true,
@@ -335,6 +342,7 @@ class Channel {
335
342
  writable: true,
336
343
  value: null
337
344
  });
345
+ this.channelNumber = channelNumber;
338
346
  Object.assign(this, audioNodes);
339
347
  Object.assign(this, settings);
340
348
  this.state = new ControllerState();
@@ -583,7 +591,7 @@ export class MidyGM2 extends EventTarget {
583
591
  configurable: true,
584
592
  writable: true,
585
593
  value: {
586
- algorithm: "SchroederReverb",
594
+ algorithm: "Schroeder",
587
595
  time: this.getReverbTime(64),
588
596
  feedback: 0.8,
589
597
  }
@@ -838,9 +846,9 @@ export class MidyGM2 extends EventTarget {
838
846
  this.controlChangeHandlers = this.createControlChangeHandlers();
839
847
  this.keyBasedControllerHandlers = this.createKeyBasedControllerHandlers();
840
848
  this.effectHandlers = this.createEffectHandlers();
841
- this.channels = this.createChannels(audioContext);
842
- this.reverbEffect = this.createReverbEffect(audioContext);
843
- this.chorusEffect = this.createChorusEffect(audioContext);
849
+ this.channels = this.createChannels();
850
+ this.reverbEffect = this.createReverbEffect(this.reverb.algorithm);
851
+ this.chorusEffect = this.createChorusEffect();
844
852
  this.chorusEffect.output.connect(this.masterVolume);
845
853
  this.reverbEffect.output.connect(this.masterVolume);
846
854
  this.masterVolume.connect(audioContext.destination);
@@ -1118,6 +1126,8 @@ export class MidyGM2 extends EventTarget {
1118
1126
  return;
1119
1127
  const soundFont = this.soundFonts[soundFontIndex];
1120
1128
  const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
1129
+ if (!voice)
1130
+ return;
1121
1131
  const { instrument, sampleID } = voice.generators;
1122
1132
  return soundFontIndex * (2 ** 31) + instrument * (2 ** 24) +
1123
1133
  (sampleID << 8);
@@ -1132,9 +1142,10 @@ export class MidyGM2 extends EventTarget {
1132
1142
  merger.connect(this.masterVolume);
1133
1143
  return { gainL, gainR, merger };
1134
1144
  }
1135
- createChannels(audioContext) {
1145
+ createChannels() {
1136
1146
  const settings = this.constructor.channelSettings;
1137
- return Array.from({ length: this.numChannels }, () => new Channel(this.createChannelAudioNodes(audioContext), settings));
1147
+ const audioContext = this.audioContext;
1148
+ return Array.from({ length: this.numChannels }, (_, ch) => new Channel(ch, this.createChannelAudioNodes(audioContext), settings));
1138
1149
  }
1139
1150
  decodeOggVorbis(sample) {
1140
1151
  const task = decoderQueue.then(async () => {
@@ -1654,6 +1665,7 @@ export class MidyGM2 extends EventTarget {
1654
1665
  continue;
1655
1666
  const soundFont = this.soundFonts[soundFontIndex];
1656
1667
  const fakeChannel = {
1668
+ channelNumber: ch,
1657
1669
  state: { array: renderControllerStates[ch].slice() },
1658
1670
  programNumber,
1659
1671
  isDrum,
@@ -1885,62 +1897,6 @@ export class MidyGM2 extends EventTarget {
1885
1897
  }
1886
1898
  await Promise.all(tasks);
1887
1899
  }
1888
- createConvolutionReverbImpulse(audioContext, decay, preDecay) {
1889
- const sampleRate = audioContext.sampleRate;
1890
- const length = sampleRate * decay;
1891
- const impulse = new AudioBuffer({
1892
- numberOfChannels: 2,
1893
- length,
1894
- sampleRate,
1895
- });
1896
- const preDecayLength = Math.min(sampleRate * preDecay, length);
1897
- for (let channel = 0; channel < impulse.numberOfChannels; channel++) {
1898
- const channelData = impulse.getChannelData(channel);
1899
- for (let i = 0; i < preDecayLength; i++) {
1900
- channelData[i] = Math.random() * 2 - 1;
1901
- }
1902
- const attenuationFactor = 1 / (sampleRate * decay);
1903
- for (let i = preDecayLength; i < length; i++) {
1904
- const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
1905
- channelData[i] = (Math.random() * 2 - 1) * attenuation;
1906
- }
1907
- }
1908
- return impulse;
1909
- }
1910
- createConvolutionReverb(audioContext, impulse) {
1911
- const convolverNode = new ConvolverNode(audioContext, {
1912
- buffer: impulse,
1913
- });
1914
- return {
1915
- input: convolverNode,
1916
- output: convolverNode,
1917
- convolverNode,
1918
- };
1919
- }
1920
- createCombFilter(audioContext, input, delay, feedback) {
1921
- const delayNode = new DelayNode(audioContext, {
1922
- maxDelayTime: delay,
1923
- delayTime: delay,
1924
- });
1925
- const feedbackGain = new GainNode(audioContext, { gain: feedback });
1926
- input.connect(delayNode);
1927
- delayNode.connect(feedbackGain);
1928
- feedbackGain.connect(delayNode);
1929
- return delayNode;
1930
- }
1931
- createAllpassFilter(audioContext, input, delay, feedback) {
1932
- const delayNode = new DelayNode(audioContext, {
1933
- maxDelayTime: delay,
1934
- delayTime: delay,
1935
- });
1936
- const feedbackGain = new GainNode(audioContext, { gain: feedback });
1937
- const passGain = new GainNode(audioContext, { gain: 1 - feedback });
1938
- input.connect(delayNode);
1939
- delayNode.connect(feedbackGain);
1940
- feedbackGain.connect(delayNode);
1941
- delayNode.connect(passGain);
1942
- return passGain;
1943
- }
1944
1900
  generateDistributedArray(center, count, varianceRatio = 0.1, randomness = 0.05) {
1945
1901
  const variance = center * varianceRatio;
1946
1902
  const array = new Array(count);
@@ -1951,40 +1907,60 @@ export class MidyGM2 extends EventTarget {
1951
1907
  }
1952
1908
  return array;
1953
1909
  }
1954
- // https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
1955
- // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
1956
- createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
1957
- const input = new GainNode(audioContext);
1958
- const mergerGain = new GainNode(audioContext);
1959
- for (let i = 0; i < combDelays.length; i++) {
1960
- const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
1961
- comb.connect(mergerGain);
1962
- }
1963
- const allpasses = [];
1964
- for (let i = 0; i < allpassDelays.length; i++) {
1965
- const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
1966
- allpasses.push(allpass);
1967
- }
1968
- const output = allpasses.at(-1);
1969
- return { input, output };
1910
+ setReverbEffect(algorithm) {
1911
+ if (this.reverbEffect)
1912
+ this.reverbEffect.output.disconnect();
1913
+ this.reverbEffect = this.createReverbEffect(algorithm);
1914
+ this.reverb.algorithm = algorithm;
1970
1915
  }
1971
- createReverbEffect(audioContext) {
1972
- const { algorithm, time: rt60, feedback } = this.reverb;
1916
+ createReverbEffect(algorithm) {
1917
+ const { audioContext, reverb } = this;
1918
+ const { time: rt60, feedback } = reverb;
1973
1919
  switch (algorithm) {
1974
- case "ConvolutionReverb": {
1975
- const impulse = this.createConvolutionReverbImpulse(audioContext, rt60, this.calcDelay(rt60, feedback));
1976
- return this.createConvolutionReverb(audioContext, impulse);
1920
+ case "Convolution": {
1921
+ const impulse = createConvolutionReverbImpulse(audioContext, rt60, this.calcDelay(rt60, feedback));
1922
+ return createConvolutionReverb(audioContext, impulse);
1977
1923
  }
1978
- case "SchroederReverb": {
1924
+ case "Schroeder": {
1979
1925
  const combFeedbacks = this.generateDistributedArray(feedback, 4);
1980
- const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
1926
+ const combDelays = combFeedbacks.map((fb) => this.calcDelay(rt60, fb));
1981
1927
  const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
1982
- const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
1983
- return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
1928
+ const allpassDelays = allpassFeedbacks.map((fb) => this.calcDelay(rt60, fb));
1929
+ return createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
1930
+ }
1931
+ case "Moorer":
1932
+ return createMoorerReverbDefault(audioContext, {
1933
+ rt60,
1934
+ damping: 1 - feedback,
1935
+ });
1936
+ case "FDN":
1937
+ return createFDNDefault(audioContext, { rt60, damping: 1 - feedback });
1938
+ case "Dattorro": {
1939
+ const decay = feedback * 0.28 + 0.7;
1940
+ return createDattorroReverb(audioContext, {
1941
+ decay,
1942
+ damping: 1 - feedback,
1943
+ });
1944
+ }
1945
+ case "Freeverb": {
1946
+ const damping = 1 - feedback;
1947
+ const { inputL, inputR, outputL, outputR } = createFreeverb(audioContext, { roomSize: feedback, damping });
1948
+ const inputMerger = new GainNode(audioContext);
1949
+ const outputMerger = new GainNode(audioContext, { gain: 0.5 });
1950
+ inputMerger.connect(inputL);
1951
+ inputMerger.connect(inputR);
1952
+ outputL.connect(outputMerger);
1953
+ outputR.connect(outputMerger);
1954
+ return { input: inputMerger, output: outputMerger };
1984
1955
  }
1956
+ case "VelvetNoise":
1957
+ return createVelvetNoiseReverb(audioContext, rt60);
1958
+ default:
1959
+ throw new Error(`Unknown reverb algorithm: ${algorithm}`);
1985
1960
  }
1986
1961
  }
1987
- createChorusEffect(audioContext) {
1962
+ createChorusEffect() {
1963
+ const audioContext = this.audioContext;
1988
1964
  const input = new GainNode(audioContext);
1989
1965
  const output = new GainNode(audioContext);
1990
1966
  const sendGain = new GainNode(audioContext);
@@ -2283,15 +2259,16 @@ export class MidyGM2 extends EventTarget {
2283
2259
  note.modLfoToVolume.connect(volumeTarget.gain);
2284
2260
  }
2285
2261
  startVibrato(channel, note, scheduleTime) {
2262
+ const audioContext = this.audioContext;
2286
2263
  const { voiceParams } = note;
2287
2264
  const state = channel.state;
2288
2265
  const vibratoRate = state.vibratoRate * 2;
2289
2266
  const vibratoDelay = state.vibratoDelay * 2;
2290
- note.vibLfo = new OscillatorNode(this.audioContext, {
2267
+ note.vibLfo = new OscillatorNode(audioContext, {
2291
2268
  frequency: this.centToHz(voiceParams.freqVibLFO) * vibratoRate,
2292
2269
  });
2293
2270
  note.vibLfo.start(note.startTime + voiceParams.delayVibLFO * vibratoDelay);
2294
- note.vibLfoToPitch = new GainNode(this.audioContext);
2271
+ note.vibLfoToPitch = new GainNode(audioContext);
2295
2272
  this.setVibLfoToPitch(channel, note, scheduleTime);
2296
2273
  note.vibLfo.connect(note.vibLfoToPitch);
2297
2274
  note.vibLfoToPitch.connect(note.bufferSource.detune);
@@ -2302,25 +2279,29 @@ export class MidyGM2 extends EventTarget {
2302
2279
  const volHold = volAttack + voiceParams.volHold;
2303
2280
  const decayDuration = voiceParams.volDecay;
2304
2281
  const adsDuration = volHold + decayDuration * decayCurve * 5;
2305
- const loopStartTime = voiceParams.loopStart / voiceParams.sampleRate;
2306
- const loopDuration = isLoop
2282
+ const sampleLoopStart = voiceParams.loopStart / voiceParams.sampleRate;
2283
+ const sampleLoopDuration = isLoop
2307
2284
  ? (voiceParams.loopEnd - voiceParams.loopStart) / voiceParams.sampleRate
2308
2285
  : 0;
2309
- const loopCount = isLoop && adsDuration > loopStartTime
2310
- ? Math.ceil((adsDuration - loopStartTime) / loopDuration)
2286
+ const playbackRate = voiceParams.playbackRate;
2287
+ const outputLoopStart = sampleLoopStart / playbackRate;
2288
+ const outputLoopDuration = sampleLoopDuration / playbackRate;
2289
+ const loopCount = isLoop && adsDuration > outputLoopStart
2290
+ ? Math.ceil((adsDuration - outputLoopStart) / outputLoopDuration)
2311
2291
  : 0;
2312
- const alignedLoopStart = loopStartTime + loopCount * loopDuration;
2292
+ const alignedLoopStart = outputLoopStart + loopCount * outputLoopDuration;
2313
2293
  const renderDuration = isLoop
2314
- ? alignedLoopStart + loopDuration
2315
- : audioBuffer.duration;
2316
- const offlineContext = new OfflineAudioContext(audioBuffer.numberOfChannels, Math.ceil(renderDuration * this.audioContext.sampleRate), this.audioContext.sampleRate);
2294
+ ? alignedLoopStart + outputLoopDuration
2295
+ : audioBuffer.duration / playbackRate;
2296
+ const sampleRate = this.audioContext.sampleRate;
2297
+ const offlineContext = new OfflineAudioContext(audioBuffer.numberOfChannels, Math.ceil(renderDuration * sampleRate), sampleRate);
2317
2298
  const bufferSource = new AudioBufferSourceNode(offlineContext);
2318
2299
  bufferSource.buffer = audioBuffer;
2319
- bufferSource.playbackRate.value = voiceParams.playbackRate;
2300
+ bufferSource.playbackRate.value = playbackRate;
2320
2301
  bufferSource.loop = isLoop;
2321
2302
  if (isLoop) {
2322
- bufferSource.loopStart = loopStartTime;
2323
- bufferSource.loopEnd = loopStartTime + loopDuration;
2303
+ bufferSource.loopStart = sampleLoopStart;
2304
+ bufferSource.loopEnd = sampleLoopStart + sampleLoopDuration;
2324
2305
  }
2325
2306
  const initialFreq = this.clampCutoffFrequency(this.centToHz(voiceParams.initialFilterFc));
2326
2307
  const filterEnvelopeNode = new BiquadFilterNode(offlineContext, {
@@ -2352,7 +2333,7 @@ export class MidyGM2 extends EventTarget {
2352
2333
  isLoop,
2353
2334
  adsDuration,
2354
2335
  loopStart: alignedLoopStart,
2355
- loopDuration,
2336
+ loopDuration: outputLoopDuration,
2356
2337
  });
2357
2338
  }
2358
2339
  async createAdsrRenderedBuffer(channel, note, voiceParams, audioBuffer, noteDuration) {
@@ -2450,7 +2431,7 @@ export class MidyGM2 extends EventTarget {
2450
2431
  }
2451
2432
  async createFullRenderedBuffer(channel, note, voiceParams, noteDuration, noteEvent = {}) {
2452
2433
  const { startTime: noteStartTime = 0, events: noteEvents = [] } = noteEvent;
2453
- const ch = note.channel ?? 0;
2434
+ const ch = channel.channelNumber;
2454
2435
  const releaseEndDuration = voiceParams.volRelease * releaseCurve * 5;
2455
2436
  const totalDuration = noteDuration + releaseEndDuration;
2456
2437
  const sampleRate = this.audioContext.sampleRate;
@@ -2502,7 +2483,7 @@ export class MidyGM2 extends EventTarget {
2502
2483
  const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
2503
2484
  if (!realtime) {
2504
2485
  if (cacheMode === "note") {
2505
- return await this.getFullCachedBuffer(note, audioBufferId);
2486
+ return await this.getFullCachedBuffer(channel, note, audioBufferId);
2506
2487
  }
2507
2488
  else if (cacheMode === "adsr") {
2508
2489
  return await this.getAdsrCachedBuffer(channel, note, audioBufferId);
@@ -2593,7 +2574,7 @@ export class MidyGM2 extends EventTarget {
2593
2574
  durationMap.set(cacheKey, renderPromise);
2594
2575
  return await renderPromise;
2595
2576
  }
2596
- async getFullCachedBuffer(note, audioBufferId) {
2577
+ async getFullCachedBuffer(channel, note, audioBufferId) {
2597
2578
  const voiceParams = note.voiceParams;
2598
2579
  const timelineIndex = note.timelineIndex;
2599
2580
  const noteEvent = this.noteOnEvents.get(timelineIndex);
@@ -2618,7 +2599,7 @@ export class MidyGM2 extends EventTarget {
2618
2599
  }
2619
2600
  const renderPromise = (async () => {
2620
2601
  try {
2621
- const rendered = await this.createFullRenderedBuffer(this.channels[note.channel], note, voiceParams, noteDuration, noteEvent);
2602
+ const rendered = await this.createFullRenderedBuffer(channel, note, voiceParams, noteDuration, noteEvent);
2622
2603
  durationMap.set(cacheKey, rendered);
2623
2604
  return rendered;
2624
2605
  }
@@ -2645,7 +2626,6 @@ export class MidyGM2 extends EventTarget {
2645
2626
  note.renderedBuffer = isRendered ? audioBuffer : null;
2646
2627
  note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
2647
2628
  note.volumeNode = new GainNode(audioContext);
2648
- note.volumeNode.gain.setValueAtTime(1, now);
2649
2629
  const cacheMode = this.cacheMode;
2650
2630
  const isFullCached = isRendered && audioBuffer.isFull === true;
2651
2631
  if (cacheMode === "none") {
@@ -2782,9 +2762,6 @@ export class MidyGM2 extends EventTarget {
2782
2762
  startTime = this.audioContext.currentTime;
2783
2763
  const note = new Note(noteNumber, velocity, startTime);
2784
2764
  note.channel = channelNumber;
2785
- const channel = this.channels[channelNumber];
2786
- note.index = channel.scheduledNotes.length;
2787
- channel.scheduledNotes.push(note);
2788
2765
  return note;
2789
2766
  }
2790
2767
  async setupNote(channelNumber, note, startTime) {
@@ -2807,6 +2784,8 @@ export class MidyGM2 extends EventTarget {
2807
2784
  note.voice = soundFont.getVoice(bank, programNumber, note.noteNumber, note.velocity);
2808
2785
  if (!note.voice)
2809
2786
  return;
2787
+ note.index = channel.scheduledNotes.length;
2788
+ channel.scheduledNotes.push(note);
2810
2789
  await this.setNoteAudioNode(channel, note, realtime);
2811
2790
  this.setNoteRouting(channelNumber, note, startTime);
2812
2791
  note.resolveReady();
@@ -2865,18 +2844,8 @@ export class MidyGM2 extends EventTarget {
2865
2844
  const volRelease = endTime + volDuration;
2866
2845
  note.volumeNode.gain
2867
2846
  .cancelScheduledValues(endTime)
2868
- .setValueAtTime(1, endTime)
2869
2847
  .setTargetAtTime(0, endTime, volDuration * releaseCurve);
2870
- return new Promise((resolve) => {
2871
- this.scheduleTask(() => {
2872
- note.bufferSource.loop = false;
2873
- note.bufferSource.stop(volRelease);
2874
- this.disconnectNote(note);
2875
- channel.scheduledNotes[note.index] = undefined;
2876
- this.releaseFullCache(note);
2877
- resolve();
2878
- }, volRelease);
2879
- });
2848
+ note.bufferSource.stop(volRelease);
2880
2849
  }
2881
2850
  else {
2882
2851
  const now = this.audioContext.currentTime;
@@ -2886,15 +2855,16 @@ export class MidyGM2 extends EventTarget {
2886
2855
  this.releaseFullCache(note);
2887
2856
  return Promise.resolve();
2888
2857
  }
2889
- return new Promise((resolve) => {
2890
- this.scheduleTask(() => {
2891
- this.disconnectNote(note);
2892
- channel.scheduledNotes[note.index] = undefined;
2893
- this.releaseFullCache(note);
2894
- resolve();
2895
- }, naturalEndTime);
2896
- });
2858
+ note.bufferSource.stop(naturalEndTime);
2897
2859
  }
2860
+ return new Promise((resolve) => {
2861
+ note.bufferSource.onended = () => {
2862
+ this.disconnectNote(note);
2863
+ channel.scheduledNotes[note.index] = undefined;
2864
+ this.releaseFullCache(note);
2865
+ resolve();
2866
+ };
2867
+ });
2898
2868
  }
2899
2869
  const volDuration = note.voiceParams.volRelease;
2900
2870
  const volRelease = endTime + volDuration;
@@ -2915,45 +2885,33 @@ export class MidyGM2 extends EventTarget {
2915
2885
  const noteOffTime = note.startTime + (rb.noteDuration ?? 0);
2916
2886
  const isEarlyCut = endTime < noteOffTime;
2917
2887
  if (isEarlyCut) {
2918
- const volRelease = endTime + volDuration;
2919
2888
  note.volumeNode.gain
2920
2889
  .cancelScheduledValues(endTime)
2921
- .setValueAtTime(1, endTime)
2922
2890
  .setTargetAtTime(0, endTime, volDuration * releaseCurve);
2923
- return new Promise((resolve) => {
2924
- this.scheduleTask(() => {
2925
- note.bufferSource.stop(volRelease);
2926
- this.disconnectNote(note);
2927
- channel.scheduledNotes[note.index] = undefined;
2928
- resolve();
2929
- }, volRelease);
2930
- });
2891
+ note.bufferSource.stop(volRelease);
2931
2892
  }
2932
2893
  else {
2933
- return new Promise((resolve) => {
2934
- this.scheduleTask(() => {
2935
- note.bufferSource.stop();
2936
- this.disconnectNote(note);
2937
- channel.scheduledNotes[note.index] = undefined;
2938
- resolve();
2939
- }, naturalEndTime);
2940
- });
2894
+ note.bufferSource.stop(naturalEndTime);
2941
2895
  }
2896
+ return new Promise((resolve) => {
2897
+ note.bufferSource.onended = () => {
2898
+ this.disconnectNote(note);
2899
+ channel.scheduledNotes[note.index] = undefined;
2900
+ resolve();
2901
+ };
2902
+ });
2942
2903
  }
2943
2904
  note.volumeNode.gain
2944
2905
  .cancelScheduledValues(endTime)
2945
- .setValueAtTime(1, endTime)
2946
2906
  .setTargetAtTime(0, endTime, volDuration * releaseCurve);
2947
2907
  }
2908
+ note.bufferSource.stop(volRelease);
2948
2909
  return new Promise((resolve) => {
2949
- this.scheduleTask(() => {
2950
- const bufferSource = note.bufferSource;
2951
- bufferSource.loop = false;
2952
- bufferSource.stop(volRelease);
2910
+ note.bufferSource.onended = () => {
2953
2911
  this.disconnectNote(note);
2954
2912
  channel.scheduledNotes[note.index] = undefined;
2955
2913
  resolve();
2956
- }, volRelease);
2914
+ };
2957
2915
  });
2958
2916
  }
2959
2917
  noteOff(channelNumber, noteNumber, _velocity, endTime, force) {
@@ -3934,9 +3892,10 @@ export class MidyGM2 extends EventTarget {
3934
3892
  setMasterVolume(value, scheduleTime) {
3935
3893
  if (!(0 <= scheduleTime))
3936
3894
  scheduleTime = this.audioContext.currentTime;
3895
+ const timeConstant = this.perceptualSmoothingTime / 5; // 99.3% (5 * tau)
3937
3896
  this.masterVolume.gain
3938
- .cancelScheduledValues(scheduleTime)
3939
- .setValueAtTime(value * value, scheduleTime);
3897
+ .cancelAndHoldAtTime(scheduleTime)
3898
+ .setTargetAtTime(value * value, scheduleTime, timeConstant);
3940
3899
  }
3941
3900
  handleMasterFineTuningSysEx(data, scheduleTime) {
3942
3901
  const value = (data[5] * 128 + data[4]) / 16383;
@@ -4001,7 +3960,7 @@ export class MidyGM2 extends EventTarget {
4001
3960
  setReverbType(type) {
4002
3961
  this.reverb.time = this.getReverbTimeFromType(type);
4003
3962
  this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
4004
- this.reverbEffect = this.createReverbEffect(this.audioContext);
3963
+ this.reverbEffect = this.setReverbEffect(this.reverb.algorithm);
4005
3964
  }
4006
3965
  getReverbTimeFromType(type) {
4007
3966
  switch (type) {
@@ -4023,7 +3982,7 @@ export class MidyGM2 extends EventTarget {
4023
3982
  }
4024
3983
  setReverbTime(value) {
4025
3984
  this.reverb.time = this.getReverbTime(value);
4026
- this.reverbEffect = this.createReverbEffect(this.audioContext);
3985
+ this.reverbEffect = this.setReverbEffect(this.reverb.algorithm);
4027
3986
  }
4028
3987
  getReverbTime(value) {
4029
3988
  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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"}