@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/script/midy-GM2.js
CHANGED
|
@@ -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: "
|
|
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(
|
|
845
|
-
this.reverbEffect = this.createReverbEffect(
|
|
846
|
-
this.chorusEffect = this.createChorusEffect(
|
|
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(
|
|
1148
|
+
createChannels() {
|
|
1139
1149
|
const settings = this.constructor.channelSettings;
|
|
1140
|
-
|
|
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
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
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(
|
|
1975
|
-
const {
|
|
1919
|
+
createReverbEffect(algorithm) {
|
|
1920
|
+
const { audioContext, reverb } = this;
|
|
1921
|
+
const { time: rt60, feedback } = reverb;
|
|
1976
1922
|
switch (algorithm) {
|
|
1977
|
-
case "
|
|
1978
|
-
const impulse =
|
|
1979
|
-
return
|
|
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 "
|
|
1927
|
+
case "Schroeder": {
|
|
1982
1928
|
const combFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
1983
|
-
const combDelays = combFeedbacks.map((
|
|
1929
|
+
const combDelays = combFeedbacks.map((fb) => this.calcDelay(rt60, fb));
|
|
1984
1930
|
const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
1985
|
-
const allpassDelays = allpassFeedbacks.map((
|
|
1986
|
-
return
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
2309
|
-
const
|
|
2285
|
+
const sampleLoopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
2286
|
+
const sampleLoopDuration = isLoop
|
|
2310
2287
|
? (voiceParams.loopEnd - voiceParams.loopStart) / voiceParams.sampleRate
|
|
2311
2288
|
: 0;
|
|
2312
|
-
const
|
|
2313
|
-
|
|
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 =
|
|
2295
|
+
const alignedLoopStart = outputLoopStart + loopCount * outputLoopDuration;
|
|
2316
2296
|
const renderDuration = isLoop
|
|
2317
|
-
? alignedLoopStart +
|
|
2318
|
-
: audioBuffer.duration;
|
|
2319
|
-
const
|
|
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 =
|
|
2303
|
+
bufferSource.playbackRate.value = playbackRate;
|
|
2323
2304
|
bufferSource.loop = isLoop;
|
|
2324
2305
|
if (isLoop) {
|
|
2325
|
-
bufferSource.loopStart =
|
|
2326
|
-
bufferSource.loopEnd =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
.
|
|
3942
|
-
.
|
|
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.
|
|
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.
|
|
3988
|
+
this.reverbEffect = this.setReverbEffect(this.reverb.algorithm);
|
|
4030
3989
|
}
|
|
4031
3990
|
getReverbTime(value) {
|
|
4032
3991
|
return Math.exp((value - 40) * 0.025);
|
package/script/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;
|
|
@@ -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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"}
|