@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/README.md +17 -2
- package/esm/midy-GM1.d.ts +3 -2
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +59 -67
- package/esm/midy-GM2.d.ts +8 -18
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +117 -158
- package/esm/midy-GMLite.d.ts +3 -2
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +60 -68
- package/esm/midy.d.ts +8 -18
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +121 -161
- package/esm/reverb.d.ts +58 -0
- package/esm/reverb.d.ts.map +1 -0
- package/esm/reverb.js +389 -0
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +3 -2
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +59 -67
- package/script/midy-GM2.d.ts +8 -18
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +117 -158
- package/script/midy-GMLite.d.ts +3 -2
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +60 -68
- package/script/midy.d.ts +8 -18
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +121 -161
- package/script/reverb.d.ts +58 -0
- package/script/reverb.d.ts.map +1 -0
- package/script/reverb.js +405 -0
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: "
|
|
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(
|
|
842
|
-
this.reverbEffect = this.createReverbEffect(
|
|
843
|
-
this.chorusEffect = this.createChorusEffect(
|
|
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(
|
|
1145
|
+
createChannels() {
|
|
1136
1146
|
const settings = this.constructor.channelSettings;
|
|
1137
|
-
|
|
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
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
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(
|
|
1972
|
-
const {
|
|
1916
|
+
createReverbEffect(algorithm) {
|
|
1917
|
+
const { audioContext, reverb } = this;
|
|
1918
|
+
const { time: rt60, feedback } = reverb;
|
|
1973
1919
|
switch (algorithm) {
|
|
1974
|
-
case "
|
|
1975
|
-
const impulse =
|
|
1976
|
-
return
|
|
1920
|
+
case "Convolution": {
|
|
1921
|
+
const impulse = createConvolutionReverbImpulse(audioContext, rt60, this.calcDelay(rt60, feedback));
|
|
1922
|
+
return createConvolutionReverb(audioContext, impulse);
|
|
1977
1923
|
}
|
|
1978
|
-
case "
|
|
1924
|
+
case "Schroeder": {
|
|
1979
1925
|
const combFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
1980
|
-
const combDelays = combFeedbacks.map((
|
|
1926
|
+
const combDelays = combFeedbacks.map((fb) => this.calcDelay(rt60, fb));
|
|
1981
1927
|
const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
1982
|
-
const allpassDelays = allpassFeedbacks.map((
|
|
1983
|
-
return
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
2306
|
-
const
|
|
2282
|
+
const sampleLoopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
2283
|
+
const sampleLoopDuration = isLoop
|
|
2307
2284
|
? (voiceParams.loopEnd - voiceParams.loopStart) / voiceParams.sampleRate
|
|
2308
2285
|
: 0;
|
|
2309
|
-
const
|
|
2310
|
-
|
|
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 =
|
|
2292
|
+
const alignedLoopStart = outputLoopStart + loopCount * outputLoopDuration;
|
|
2313
2293
|
const renderDuration = isLoop
|
|
2314
|
-
? alignedLoopStart +
|
|
2315
|
-
: audioBuffer.duration;
|
|
2316
|
-
const
|
|
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 =
|
|
2300
|
+
bufferSource.playbackRate.value = playbackRate;
|
|
2320
2301
|
bufferSource.loop = isLoop;
|
|
2321
2302
|
if (isLoop) {
|
|
2322
|
-
bufferSource.loopStart =
|
|
2323
|
-
bufferSource.loopEnd =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
.
|
|
3939
|
-
.
|
|
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.
|
|
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.
|
|
3985
|
+
this.reverbEffect = this.setReverbEffect(this.reverb.algorithm);
|
|
4027
3986
|
}
|
|
4028
3987
|
getReverbTime(value) {
|
|
4029
3988
|
return Math.exp((value - 40) * 0.025);
|
package/esm/midy-GMLite.d.ts
CHANGED
|
@@ -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;
|
package/esm/midy-GMLite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"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"}
|