@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.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.Midy = 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
|
|
@@ -205,7 +206,13 @@ class Note {
|
|
|
205
206
|
}
|
|
206
207
|
}
|
|
207
208
|
class Channel {
|
|
208
|
-
constructor(audioNodes, settings) {
|
|
209
|
+
constructor(channelNumber, audioNodes, settings) {
|
|
210
|
+
Object.defineProperty(this, "channelNumber", {
|
|
211
|
+
enumerable: true,
|
|
212
|
+
configurable: true,
|
|
213
|
+
writable: true,
|
|
214
|
+
value: 0
|
|
215
|
+
});
|
|
209
216
|
Object.defineProperty(this, "isDrum", {
|
|
210
217
|
enumerable: true,
|
|
211
218
|
configurable: true,
|
|
@@ -356,6 +363,7 @@ class Channel {
|
|
|
356
363
|
writable: true,
|
|
357
364
|
value: null
|
|
358
365
|
});
|
|
366
|
+
this.channelNumber = channelNumber;
|
|
359
367
|
Object.assign(this, audioNodes);
|
|
360
368
|
Object.assign(this, settings);
|
|
361
369
|
this.state = new ControllerState();
|
|
@@ -621,7 +629,7 @@ class Midy extends EventTarget {
|
|
|
621
629
|
configurable: true,
|
|
622
630
|
writable: true,
|
|
623
631
|
value: {
|
|
624
|
-
algorithm: "
|
|
632
|
+
algorithm: "Schroeder",
|
|
625
633
|
time: this.getReverbTime(64),
|
|
626
634
|
feedback: 0.8,
|
|
627
635
|
}
|
|
@@ -869,6 +877,7 @@ class Midy extends EventTarget {
|
|
|
869
877
|
writable: true,
|
|
870
878
|
value: null
|
|
871
879
|
});
|
|
880
|
+
// MPE
|
|
872
881
|
Object.defineProperty(this, "mpeEnabled", {
|
|
873
882
|
enumerable: true,
|
|
874
883
|
configurable: true,
|
|
@@ -909,9 +918,9 @@ class Midy extends EventTarget {
|
|
|
909
918
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
910
919
|
this.keyBasedControllerHandlers = this.createKeyBasedControllerHandlers();
|
|
911
920
|
this.effectHandlers = this.createEffectHandlers();
|
|
912
|
-
this.channels = this.createChannels(
|
|
913
|
-
this.reverbEffect = this.createReverbEffect(
|
|
914
|
-
this.chorusEffect = this.createChorusEffect(
|
|
921
|
+
this.channels = this.createChannels();
|
|
922
|
+
this.reverbEffect = this.createReverbEffect(this.reverb.algorithm);
|
|
923
|
+
this.chorusEffect = this.createChorusEffect();
|
|
915
924
|
this.chorusEffect.output.connect(this.masterVolume);
|
|
916
925
|
this.reverbEffect.output.connect(this.masterVolume);
|
|
917
926
|
this.masterVolume.connect(audioContext.destination);
|
|
@@ -1190,6 +1199,8 @@ class Midy extends EventTarget {
|
|
|
1190
1199
|
return;
|
|
1191
1200
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1192
1201
|
const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
1202
|
+
if (!voice)
|
|
1203
|
+
return;
|
|
1193
1204
|
const { instrument, sampleID } = voice.generators;
|
|
1194
1205
|
return soundFontIndex * (2 ** 31) + instrument * (2 ** 24) +
|
|
1195
1206
|
(sampleID << 8);
|
|
@@ -1204,9 +1215,10 @@ class Midy extends EventTarget {
|
|
|
1204
1215
|
merger.connect(this.masterVolume);
|
|
1205
1216
|
return { gainL, gainR, merger };
|
|
1206
1217
|
}
|
|
1207
|
-
createChannels(
|
|
1218
|
+
createChannels() {
|
|
1208
1219
|
const settings = this.constructor.channelSettings;
|
|
1209
|
-
|
|
1220
|
+
const audioContext = this.audioContext;
|
|
1221
|
+
return Array.from({ length: this.numChannels }, (_, ch) => new Channel(ch, this.createChannelAudioNodes(audioContext), settings));
|
|
1210
1222
|
}
|
|
1211
1223
|
decodeOggVorbis(sample) {
|
|
1212
1224
|
const task = decoderQueue.then(async () => {
|
|
@@ -1742,6 +1754,7 @@ class Midy extends EventTarget {
|
|
|
1742
1754
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1743
1755
|
const pressure = renderNoteAftertouch[ch * 128 + noteNumber];
|
|
1744
1756
|
const fakeChannel = {
|
|
1757
|
+
channelNumber: ch,
|
|
1745
1758
|
state: { array: renderControllerStates[ch].slice() },
|
|
1746
1759
|
programNumber,
|
|
1747
1760
|
isDrum,
|
|
@@ -1993,62 +2006,6 @@ class Midy extends EventTarget {
|
|
|
1993
2006
|
}
|
|
1994
2007
|
}
|
|
1995
2008
|
}
|
|
1996
|
-
createConvolutionReverbImpulse(audioContext, decay, preDecay) {
|
|
1997
|
-
const sampleRate = audioContext.sampleRate;
|
|
1998
|
-
const length = sampleRate * decay;
|
|
1999
|
-
const impulse = new AudioBuffer({
|
|
2000
|
-
numberOfChannels: 2,
|
|
2001
|
-
length,
|
|
2002
|
-
sampleRate,
|
|
2003
|
-
});
|
|
2004
|
-
const preDecayLength = Math.min(sampleRate * preDecay, length);
|
|
2005
|
-
for (let channel = 0; channel < impulse.numberOfChannels; channel++) {
|
|
2006
|
-
const channelData = impulse.getChannelData(channel);
|
|
2007
|
-
for (let i = 0; i < preDecayLength; i++) {
|
|
2008
|
-
channelData[i] = Math.random() * 2 - 1;
|
|
2009
|
-
}
|
|
2010
|
-
const attenuationFactor = 1 / (sampleRate * decay);
|
|
2011
|
-
for (let i = preDecayLength; i < length; i++) {
|
|
2012
|
-
const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
|
|
2013
|
-
channelData[i] = (Math.random() * 2 - 1) * attenuation;
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
return impulse;
|
|
2017
|
-
}
|
|
2018
|
-
createConvolutionReverb(audioContext, impulse) {
|
|
2019
|
-
const convolverNode = new ConvolverNode(audioContext, {
|
|
2020
|
-
buffer: impulse,
|
|
2021
|
-
});
|
|
2022
|
-
return {
|
|
2023
|
-
input: convolverNode,
|
|
2024
|
-
output: convolverNode,
|
|
2025
|
-
convolverNode,
|
|
2026
|
-
};
|
|
2027
|
-
}
|
|
2028
|
-
createCombFilter(audioContext, input, delay, feedback) {
|
|
2029
|
-
const delayNode = new DelayNode(audioContext, {
|
|
2030
|
-
maxDelayTime: delay,
|
|
2031
|
-
delayTime: delay,
|
|
2032
|
-
});
|
|
2033
|
-
const feedbackGain = new GainNode(audioContext, { gain: feedback });
|
|
2034
|
-
input.connect(delayNode);
|
|
2035
|
-
delayNode.connect(feedbackGain);
|
|
2036
|
-
feedbackGain.connect(delayNode);
|
|
2037
|
-
return delayNode;
|
|
2038
|
-
}
|
|
2039
|
-
createAllpassFilter(audioContext, input, delay, feedback) {
|
|
2040
|
-
const delayNode = new DelayNode(audioContext, {
|
|
2041
|
-
maxDelayTime: delay,
|
|
2042
|
-
delayTime: delay,
|
|
2043
|
-
});
|
|
2044
|
-
const feedbackGain = new GainNode(audioContext, { gain: feedback });
|
|
2045
|
-
const passGain = new GainNode(audioContext, { gain: 1 - feedback });
|
|
2046
|
-
input.connect(delayNode);
|
|
2047
|
-
delayNode.connect(feedbackGain);
|
|
2048
|
-
feedbackGain.connect(delayNode);
|
|
2049
|
-
delayNode.connect(passGain);
|
|
2050
|
-
return passGain;
|
|
2051
|
-
}
|
|
2052
2009
|
generateDistributedArray(center, count, varianceRatio = 0.1, randomness = 0.05) {
|
|
2053
2010
|
const variance = center * varianceRatio;
|
|
2054
2011
|
const array = new Array(count);
|
|
@@ -2059,40 +2016,60 @@ class Midy extends EventTarget {
|
|
|
2059
2016
|
}
|
|
2060
2017
|
return array;
|
|
2061
2018
|
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
for (let i = 0; i < combDelays.length; i++) {
|
|
2068
|
-
const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
|
|
2069
|
-
comb.connect(mergerGain);
|
|
2070
|
-
}
|
|
2071
|
-
const allpasses = [];
|
|
2072
|
-
for (let i = 0; i < allpassDelays.length; i++) {
|
|
2073
|
-
const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
|
|
2074
|
-
allpasses.push(allpass);
|
|
2075
|
-
}
|
|
2076
|
-
const output = allpasses.at(-1);
|
|
2077
|
-
return { input, output };
|
|
2019
|
+
setReverbEffect(algorithm) {
|
|
2020
|
+
if (this.reverbEffect)
|
|
2021
|
+
this.reverbEffect.output.disconnect();
|
|
2022
|
+
this.reverbEffect = this.createReverbEffect(algorithm);
|
|
2023
|
+
this.reverb.algorithm = algorithm;
|
|
2078
2024
|
}
|
|
2079
|
-
createReverbEffect(
|
|
2080
|
-
const {
|
|
2025
|
+
createReverbEffect(algorithm) {
|
|
2026
|
+
const { audioContext, reverb } = this;
|
|
2027
|
+
const { time: rt60, feedback } = reverb;
|
|
2081
2028
|
switch (algorithm) {
|
|
2082
|
-
case "
|
|
2083
|
-
const impulse =
|
|
2084
|
-
return
|
|
2029
|
+
case "Convolution": {
|
|
2030
|
+
const impulse = (0, reverb_js_1.createConvolutionReverbImpulse)(audioContext, rt60, this.calcDelay(rt60, feedback));
|
|
2031
|
+
return (0, reverb_js_1.createConvolutionReverb)(audioContext, impulse);
|
|
2085
2032
|
}
|
|
2086
|
-
case "
|
|
2033
|
+
case "Schroeder": {
|
|
2087
2034
|
const combFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
2088
|
-
const combDelays = combFeedbacks.map((
|
|
2035
|
+
const combDelays = combFeedbacks.map((fb) => this.calcDelay(rt60, fb));
|
|
2089
2036
|
const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
2090
|
-
const allpassDelays = allpassFeedbacks.map((
|
|
2091
|
-
return
|
|
2037
|
+
const allpassDelays = allpassFeedbacks.map((fb) => this.calcDelay(rt60, fb));
|
|
2038
|
+
return (0, reverb_js_1.createSchroederReverb)(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
|
|
2039
|
+
}
|
|
2040
|
+
case "Moorer":
|
|
2041
|
+
return (0, reverb_js_1.createMoorerReverbDefault)(audioContext, {
|
|
2042
|
+
rt60,
|
|
2043
|
+
damping: 1 - feedback,
|
|
2044
|
+
});
|
|
2045
|
+
case "FDN":
|
|
2046
|
+
return (0, reverb_js_1.createFDNDefault)(audioContext, { rt60, damping: 1 - feedback });
|
|
2047
|
+
case "Dattorro": {
|
|
2048
|
+
const decay = feedback * 0.28 + 0.7;
|
|
2049
|
+
return (0, reverb_js_1.createDattorroReverb)(audioContext, {
|
|
2050
|
+
decay,
|
|
2051
|
+
damping: 1 - feedback,
|
|
2052
|
+
});
|
|
2092
2053
|
}
|
|
2054
|
+
case "Freeverb": {
|
|
2055
|
+
const damping = 1 - feedback;
|
|
2056
|
+
const { inputL, inputR, outputL, outputR } = (0, reverb_js_1.createFreeverb)(audioContext, { roomSize: feedback, damping });
|
|
2057
|
+
const inputMerger = new GainNode(audioContext);
|
|
2058
|
+
const outputMerger = new GainNode(audioContext, { gain: 0.5 });
|
|
2059
|
+
inputMerger.connect(inputL);
|
|
2060
|
+
inputMerger.connect(inputR);
|
|
2061
|
+
outputL.connect(outputMerger);
|
|
2062
|
+
outputR.connect(outputMerger);
|
|
2063
|
+
return { input: inputMerger, output: outputMerger };
|
|
2064
|
+
}
|
|
2065
|
+
case "VelvetNoise":
|
|
2066
|
+
return (0, reverb_js_1.createVelvetNoiseReverb)(audioContext, rt60);
|
|
2067
|
+
default:
|
|
2068
|
+
throw new Error(`Unknown reverb algorithm: ${algorithm}`);
|
|
2093
2069
|
}
|
|
2094
2070
|
}
|
|
2095
|
-
createChorusEffect(
|
|
2071
|
+
createChorusEffect() {
|
|
2072
|
+
const audioContext = this.audioContext;
|
|
2096
2073
|
const input = new GainNode(audioContext);
|
|
2097
2074
|
const output = new GainNode(audioContext);
|
|
2098
2075
|
const sendGain = new GainNode(audioContext);
|
|
@@ -2275,9 +2252,10 @@ class Midy extends EventTarget {
|
|
|
2275
2252
|
}
|
|
2276
2253
|
setVolumeNode(channel, note, scheduleTime) {
|
|
2277
2254
|
const depth = 1 + this.getNoteAmplitudeControl(channel, note);
|
|
2255
|
+
const timeConstant = this.perceptualSmoothingTime / 5; // 99.3% (5 * tau)
|
|
2278
2256
|
note.volumeNode.gain
|
|
2279
|
-
.
|
|
2280
|
-
.
|
|
2257
|
+
.cancelAndHoldAtTime(scheduleTime)
|
|
2258
|
+
.setTargetAtTime(depth, scheduleTime, timeConstant);
|
|
2281
2259
|
}
|
|
2282
2260
|
setPortamentoDetune(channel, note, scheduleTime) {
|
|
2283
2261
|
if (channel.portamentoControl) {
|
|
@@ -2417,15 +2395,16 @@ class Midy extends EventTarget {
|
|
|
2417
2395
|
note.modLfoToVolume.connect(volumeTarget.gain);
|
|
2418
2396
|
}
|
|
2419
2397
|
startVibrato(channel, note, scheduleTime) {
|
|
2398
|
+
const audioContext = this.audioContext;
|
|
2420
2399
|
const { voiceParams, noteNumber } = note;
|
|
2421
2400
|
const vibratoRate = this.getRelativeKeyBasedValue(channel, noteNumber, 76) *
|
|
2422
2401
|
2;
|
|
2423
2402
|
const vibratoDelay = this.getRelativeKeyBasedValue(channel, noteNumber, 78) * 2;
|
|
2424
|
-
note.vibLfo = new OscillatorNode(
|
|
2403
|
+
note.vibLfo = new OscillatorNode(audioContext, {
|
|
2425
2404
|
frequency: this.centToHz(voiceParams.freqVibLFO) * vibratoRate,
|
|
2426
2405
|
});
|
|
2427
2406
|
note.vibLfo.start(note.startTime + voiceParams.delayVibLFO * vibratoDelay);
|
|
2428
|
-
note.vibLfoToPitch = new GainNode(
|
|
2407
|
+
note.vibLfoToPitch = new GainNode(audioContext);
|
|
2429
2408
|
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
2430
2409
|
note.vibLfo.connect(note.vibLfoToPitch);
|
|
2431
2410
|
note.vibLfoToPitch.connect(note.bufferSource.detune);
|
|
@@ -2436,25 +2415,29 @@ class Midy extends EventTarget {
|
|
|
2436
2415
|
const volHold = volAttack + voiceParams.volHold;
|
|
2437
2416
|
const decayDuration = voiceParams.volDecay;
|
|
2438
2417
|
const adsDuration = volHold + decayDuration * decayCurve * 5;
|
|
2439
|
-
const
|
|
2440
|
-
const
|
|
2418
|
+
const sampleLoopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
2419
|
+
const sampleLoopDuration = isLoop
|
|
2441
2420
|
? (voiceParams.loopEnd - voiceParams.loopStart) / voiceParams.sampleRate
|
|
2442
2421
|
: 0;
|
|
2443
|
-
const
|
|
2444
|
-
|
|
2422
|
+
const playbackRate = voiceParams.playbackRate;
|
|
2423
|
+
const outputLoopStart = sampleLoopStart / playbackRate;
|
|
2424
|
+
const outputLoopDuration = sampleLoopDuration / playbackRate;
|
|
2425
|
+
const loopCount = isLoop && adsDuration > outputLoopStart
|
|
2426
|
+
? Math.ceil((adsDuration - outputLoopStart) / outputLoopDuration)
|
|
2445
2427
|
: 0;
|
|
2446
|
-
const alignedLoopStart =
|
|
2428
|
+
const alignedLoopStart = outputLoopStart + loopCount * outputLoopDuration;
|
|
2447
2429
|
const renderDuration = isLoop
|
|
2448
|
-
? alignedLoopStart +
|
|
2449
|
-
: audioBuffer.duration;
|
|
2450
|
-
const
|
|
2430
|
+
? alignedLoopStart + outputLoopDuration
|
|
2431
|
+
: audioBuffer.duration / playbackRate;
|
|
2432
|
+
const sampleRate = this.audioContext.sampleRate;
|
|
2433
|
+
const offlineContext = new OfflineAudioContext(audioBuffer.numberOfChannels, Math.ceil(renderDuration * sampleRate), sampleRate);
|
|
2451
2434
|
const bufferSource = new AudioBufferSourceNode(offlineContext);
|
|
2452
2435
|
bufferSource.buffer = audioBuffer;
|
|
2453
|
-
bufferSource.playbackRate.value =
|
|
2436
|
+
bufferSource.playbackRate.value = playbackRate;
|
|
2454
2437
|
bufferSource.loop = isLoop;
|
|
2455
2438
|
if (isLoop) {
|
|
2456
|
-
bufferSource.loopStart =
|
|
2457
|
-
bufferSource.loopEnd =
|
|
2439
|
+
bufferSource.loopStart = sampleLoopStart;
|
|
2440
|
+
bufferSource.loopEnd = sampleLoopStart + sampleLoopDuration;
|
|
2458
2441
|
}
|
|
2459
2442
|
const initialFreq = this.clampCutoffFrequency(this.centToHz(voiceParams.initialFilterFc));
|
|
2460
2443
|
const filterEnvelopeNode = new BiquadFilterNode(offlineContext, {
|
|
@@ -2486,7 +2469,7 @@ class Midy extends EventTarget {
|
|
|
2486
2469
|
isLoop,
|
|
2487
2470
|
adsDuration,
|
|
2488
2471
|
loopStart: alignedLoopStart,
|
|
2489
|
-
loopDuration,
|
|
2472
|
+
loopDuration: outputLoopDuration,
|
|
2490
2473
|
});
|
|
2491
2474
|
}
|
|
2492
2475
|
async createAdsrRenderedBuffer(channel, note, voiceParams, audioBuffer, noteDuration) {
|
|
@@ -2584,7 +2567,7 @@ class Midy extends EventTarget {
|
|
|
2584
2567
|
}
|
|
2585
2568
|
async createFullRenderedBuffer(channel, note, voiceParams, noteDuration, noteEvent = {}) {
|
|
2586
2569
|
const { startTime: noteStartTime = 0, events: noteEvents = [] } = noteEvent;
|
|
2587
|
-
const ch =
|
|
2570
|
+
const ch = channel.channelNumber;
|
|
2588
2571
|
const releaseEndDuration = voiceParams.volRelease * releaseCurve * 5;
|
|
2589
2572
|
const totalDuration = noteDuration + releaseEndDuration;
|
|
2590
2573
|
const sampleRate = this.audioContext.sampleRate;
|
|
@@ -2639,7 +2622,7 @@ class Midy extends EventTarget {
|
|
|
2639
2622
|
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
2640
2623
|
if (!realtime) {
|
|
2641
2624
|
if (cacheMode === "note") {
|
|
2642
|
-
return await this.getFullCachedBuffer(note, audioBufferId);
|
|
2625
|
+
return await this.getFullCachedBuffer(channel, note, audioBufferId);
|
|
2643
2626
|
}
|
|
2644
2627
|
else if (cacheMode === "adsr") {
|
|
2645
2628
|
return await this.getAdsrCachedBuffer(channel, note, audioBufferId);
|
|
@@ -2730,7 +2713,7 @@ class Midy extends EventTarget {
|
|
|
2730
2713
|
durationMap.set(cacheKey, renderPromise);
|
|
2731
2714
|
return await renderPromise;
|
|
2732
2715
|
}
|
|
2733
|
-
async getFullCachedBuffer(note, audioBufferId) {
|
|
2716
|
+
async getFullCachedBuffer(channel, note, audioBufferId) {
|
|
2734
2717
|
const voiceParams = note.voiceParams;
|
|
2735
2718
|
const timelineIndex = note.timelineIndex;
|
|
2736
2719
|
const noteEvent = this.noteOnEvents.get(timelineIndex);
|
|
@@ -2755,8 +2738,7 @@ class Midy extends EventTarget {
|
|
|
2755
2738
|
}
|
|
2756
2739
|
const renderPromise = (async () => {
|
|
2757
2740
|
try {
|
|
2758
|
-
const
|
|
2759
|
-
const rendered = await this.createFullRenderedBuffer(note, voiceParams, rawBuffer, noteDuration, noteEvent);
|
|
2741
|
+
const rendered = await this.createFullRenderedBuffer(channel, note, voiceParams, noteDuration, noteEvent);
|
|
2760
2742
|
durationMap.set(cacheKey, rendered);
|
|
2761
2743
|
return rendered;
|
|
2762
2744
|
}
|
|
@@ -2783,7 +2765,6 @@ class Midy extends EventTarget {
|
|
|
2783
2765
|
note.renderedBuffer = isRendered ? audioBuffer : null;
|
|
2784
2766
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
2785
2767
|
note.volumeNode = new GainNode(audioContext);
|
|
2786
|
-
note.volumeNode.gain.setValueAtTime(1, now);
|
|
2787
2768
|
const cacheMode = this.cacheMode;
|
|
2788
2769
|
const isFullCached = isRendered && audioBuffer.isFull === true;
|
|
2789
2770
|
if (cacheMode === "none") {
|
|
@@ -2929,9 +2910,6 @@ class Midy extends EventTarget {
|
|
|
2929
2910
|
startTime = this.audioContext.currentTime;
|
|
2930
2911
|
const note = new Note(noteNumber, velocity, startTime);
|
|
2931
2912
|
note.channel = channelNumber;
|
|
2932
|
-
const channel = this.channels[channelNumber];
|
|
2933
|
-
note.index = channel.scheduledNotes.length;
|
|
2934
|
-
channel.scheduledNotes.push(note);
|
|
2935
2913
|
return note;
|
|
2936
2914
|
}
|
|
2937
2915
|
async setupNote(channelNumber, note, startTime) {
|
|
@@ -2954,6 +2932,8 @@ class Midy extends EventTarget {
|
|
|
2954
2932
|
note.voice = soundFont.getVoice(bank, programNumber, note.noteNumber, note.velocity);
|
|
2955
2933
|
if (!note.voice)
|
|
2956
2934
|
return;
|
|
2935
|
+
note.index = channel.scheduledNotes.length;
|
|
2936
|
+
channel.scheduledNotes.push(note);
|
|
2957
2937
|
await this.setNoteAudioNode(channel, note, realtime);
|
|
2958
2938
|
this.setNoteRouting(channelNumber, note, startTime);
|
|
2959
2939
|
note.resolveReady();
|
|
@@ -3013,18 +2993,8 @@ class Midy extends EventTarget {
|
|
|
3013
2993
|
const volRelease = endTime + volDuration;
|
|
3014
2994
|
note.volumeNode.gain
|
|
3015
2995
|
.cancelScheduledValues(endTime)
|
|
3016
|
-
.setValueAtTime(1, endTime)
|
|
3017
2996
|
.setTargetAtTime(0, endTime, volDuration * releaseCurve);
|
|
3018
|
-
|
|
3019
|
-
this.scheduleTask(() => {
|
|
3020
|
-
note.bufferSource.loop = false;
|
|
3021
|
-
note.bufferSource.stop(volRelease);
|
|
3022
|
-
this.disconnectNote(note);
|
|
3023
|
-
channel.scheduledNotes[note.index] = undefined;
|
|
3024
|
-
this.releaseFullCache(note);
|
|
3025
|
-
resolve();
|
|
3026
|
-
}, volRelease);
|
|
3027
|
-
});
|
|
2997
|
+
note.bufferSource.stop(volRelease);
|
|
3028
2998
|
}
|
|
3029
2999
|
else {
|
|
3030
3000
|
const now = this.audioContext.currentTime;
|
|
@@ -3034,15 +3004,16 @@ class Midy extends EventTarget {
|
|
|
3034
3004
|
this.releaseFullCache(note);
|
|
3035
3005
|
return Promise.resolve();
|
|
3036
3006
|
}
|
|
3037
|
-
|
|
3038
|
-
this.scheduleTask(() => {
|
|
3039
|
-
this.disconnectNote(note);
|
|
3040
|
-
channel.scheduledNotes[note.index] = undefined;
|
|
3041
|
-
this.releaseFullCache(note);
|
|
3042
|
-
resolve();
|
|
3043
|
-
}, naturalEndTime);
|
|
3044
|
-
});
|
|
3007
|
+
note.bufferSource.stop(naturalEndTime);
|
|
3045
3008
|
}
|
|
3009
|
+
return new Promise((resolve) => {
|
|
3010
|
+
note.bufferSource.onended = () => {
|
|
3011
|
+
this.disconnectNote(note);
|
|
3012
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
3013
|
+
this.releaseFullCache(note);
|
|
3014
|
+
resolve();
|
|
3015
|
+
};
|
|
3016
|
+
});
|
|
3046
3017
|
}
|
|
3047
3018
|
const releaseTime = this.getRelativeKeyBasedValue(channel, note.noteNumber, 72) * 2;
|
|
3048
3019
|
const volDuration = note.voiceParams.volRelease * releaseTime;
|
|
@@ -3064,45 +3035,33 @@ class Midy extends EventTarget {
|
|
|
3064
3035
|
const noteOffTime = note.startTime + (rb.noteDuration ?? 0);
|
|
3065
3036
|
const isEarlyCut = endTime < noteOffTime;
|
|
3066
3037
|
if (isEarlyCut) {
|
|
3067
|
-
const volRelease = endTime + volDuration;
|
|
3068
3038
|
note.volumeNode.gain
|
|
3069
3039
|
.cancelScheduledValues(endTime)
|
|
3070
|
-
.setValueAtTime(1, endTime)
|
|
3071
3040
|
.setTargetAtTime(0, endTime, volDuration * releaseCurve);
|
|
3072
|
-
|
|
3073
|
-
this.scheduleTask(() => {
|
|
3074
|
-
note.bufferSource.stop(volRelease);
|
|
3075
|
-
this.disconnectNote(note);
|
|
3076
|
-
channel.scheduledNotes[note.index] = undefined;
|
|
3077
|
-
resolve();
|
|
3078
|
-
}, volRelease);
|
|
3079
|
-
});
|
|
3041
|
+
note.bufferSource.stop(volRelease);
|
|
3080
3042
|
}
|
|
3081
3043
|
else {
|
|
3082
|
-
|
|
3083
|
-
this.scheduleTask(() => {
|
|
3084
|
-
note.bufferSource.stop();
|
|
3085
|
-
this.disconnectNote(note);
|
|
3086
|
-
channel.scheduledNotes[note.index] = undefined;
|
|
3087
|
-
resolve();
|
|
3088
|
-
}, naturalEndTime);
|
|
3089
|
-
});
|
|
3044
|
+
note.bufferSource.stop(naturalEndTime);
|
|
3090
3045
|
}
|
|
3046
|
+
return new Promise((resolve) => {
|
|
3047
|
+
note.bufferSource.onended = () => {
|
|
3048
|
+
this.disconnectNote(note);
|
|
3049
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
3050
|
+
resolve();
|
|
3051
|
+
};
|
|
3052
|
+
});
|
|
3091
3053
|
}
|
|
3092
3054
|
note.volumeNode.gain
|
|
3093
3055
|
.cancelScheduledValues(endTime)
|
|
3094
|
-
.setValueAtTime(1, endTime)
|
|
3095
3056
|
.setTargetAtTime(0, endTime, volDuration * releaseCurve);
|
|
3096
3057
|
}
|
|
3058
|
+
note.bufferSource.stop(volRelease);
|
|
3097
3059
|
return new Promise((resolve) => {
|
|
3098
|
-
|
|
3099
|
-
const bufferSource = note.bufferSource;
|
|
3100
|
-
bufferSource.loop = false;
|
|
3101
|
-
bufferSource.stop(volRelease);
|
|
3060
|
+
note.bufferSource.onended = () => {
|
|
3102
3061
|
this.disconnectNote(note);
|
|
3103
3062
|
channel.scheduledNotes[note.index] = undefined;
|
|
3104
3063
|
resolve();
|
|
3105
|
-
}
|
|
3064
|
+
};
|
|
3106
3065
|
});
|
|
3107
3066
|
}
|
|
3108
3067
|
noteOff(channelNumber, noteNumber, velocity, endTime, force) {
|
|
@@ -4404,9 +4363,10 @@ class Midy extends EventTarget {
|
|
|
4404
4363
|
setMasterVolume(value, scheduleTime) {
|
|
4405
4364
|
if (!(0 <= scheduleTime))
|
|
4406
4365
|
scheduleTime = this.audioContext.currentTime;
|
|
4366
|
+
const timeConstant = this.perceptualSmoothingTime / 5; // 99.3% (5 * tau)
|
|
4407
4367
|
this.masterVolume.gain
|
|
4408
|
-
.
|
|
4409
|
-
.
|
|
4368
|
+
.cancelAndHoldAtTime(scheduleTime)
|
|
4369
|
+
.setTargetAtTime(value * value, scheduleTime, timeConstant);
|
|
4410
4370
|
}
|
|
4411
4371
|
handleMasterFineTuningSysEx(data, scheduleTime) {
|
|
4412
4372
|
const value = (data[5] * 128 + data[4]) / 16383;
|
|
@@ -4471,7 +4431,7 @@ class Midy extends EventTarget {
|
|
|
4471
4431
|
setReverbType(type) {
|
|
4472
4432
|
this.reverb.time = this.getReverbTimeFromType(type);
|
|
4473
4433
|
this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
|
|
4474
|
-
this.reverbEffect = this.
|
|
4434
|
+
this.reverbEffect = this.setReverbEffect(this.reverb.algorithm);
|
|
4475
4435
|
}
|
|
4476
4436
|
getReverbTimeFromType(type) {
|
|
4477
4437
|
switch (type) {
|
|
@@ -4493,7 +4453,7 @@ class Midy extends EventTarget {
|
|
|
4493
4453
|
}
|
|
4494
4454
|
setReverbTime(value) {
|
|
4495
4455
|
this.reverb.time = this.getReverbTime(value);
|
|
4496
|
-
this.reverbEffect = this.
|
|
4456
|
+
this.reverbEffect = this.setReverbEffect(this.reverb.algorithm);
|
|
4497
4457
|
}
|
|
4498
4458
|
getReverbTime(value) {
|
|
4499
4459
|
return Math.exp((value - 40) * 0.025);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function createConvolutionReverbImpulse(audioContext: any, decay: any, preDecay: any): any;
|
|
2
|
+
export function createConvolutionReverb(audioContext: any, impulse: any): {
|
|
3
|
+
input: any;
|
|
4
|
+
output: any;
|
|
5
|
+
};
|
|
6
|
+
export function createCombFilter(audioContext: any, input: any, delay: any, feedback: any): any;
|
|
7
|
+
export function createAllpassFilter(audioContext: any, input: any, delay: any, feedback: any): any;
|
|
8
|
+
export function createLPFCombFilter(audioContext: any, input: any, delayTime: any, feedback: any, damping: any): any;
|
|
9
|
+
export function createSchroederReverb(audioContext: any, combFeedbacks: any, combDelays: any, allpassFeedbacks: any, allpassDelays: any): {
|
|
10
|
+
input: any;
|
|
11
|
+
output: any;
|
|
12
|
+
};
|
|
13
|
+
export function createMoorerReverb(audioContext: any, earlyTaps: any, earlyGains: any, combDelays: any, combFeedbacks: any, damping: any, allpassDelays: any, allpassFeedbacks: any): {
|
|
14
|
+
input: any;
|
|
15
|
+
output: any;
|
|
16
|
+
};
|
|
17
|
+
export function createMoorerReverbDefault(audioContext: any, { rt60, damping, }?: {
|
|
18
|
+
rt60?: number | undefined;
|
|
19
|
+
damping?: number | undefined;
|
|
20
|
+
}): {
|
|
21
|
+
input: any;
|
|
22
|
+
output: any;
|
|
23
|
+
};
|
|
24
|
+
export function createFDN(audioContext: any, delayTimes: any, gains: any, damping?: number, modulation?: number): {
|
|
25
|
+
input: any;
|
|
26
|
+
output: any;
|
|
27
|
+
};
|
|
28
|
+
export function createFDNDefault(audioContext: any, { rt60, damping, modulation }?: {
|
|
29
|
+
rt60?: number | undefined;
|
|
30
|
+
damping?: number | undefined;
|
|
31
|
+
modulation?: number | undefined;
|
|
32
|
+
}): {
|
|
33
|
+
input: any;
|
|
34
|
+
output: any;
|
|
35
|
+
};
|
|
36
|
+
export function createDattorroReverb(audioContext: any, { decay, damping, bandwidth, }?: {
|
|
37
|
+
decay?: number | undefined;
|
|
38
|
+
damping?: number | undefined;
|
|
39
|
+
bandwidth?: number | undefined;
|
|
40
|
+
}): {
|
|
41
|
+
input: any;
|
|
42
|
+
output: any;
|
|
43
|
+
};
|
|
44
|
+
export function createFreeverb(audioContext: any, { roomSize, damping }?: {
|
|
45
|
+
roomSize?: number | undefined;
|
|
46
|
+
damping?: number | undefined;
|
|
47
|
+
}): {
|
|
48
|
+
inputL: any;
|
|
49
|
+
inputR: any;
|
|
50
|
+
outputL: any;
|
|
51
|
+
outputR: any;
|
|
52
|
+
};
|
|
53
|
+
export function createVelvetNoiseImpulse(audioContext: any, decay: any, density?: number): any;
|
|
54
|
+
export function createVelvetNoiseReverb(audioContext: any, decay: any, density: any): {
|
|
55
|
+
input: any;
|
|
56
|
+
output: any;
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=reverb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reverb.d.ts","sourceRoot":"","sources":["../src/reverb.js"],"names":[],"mappings":"AAWA,kGAiBC;AAED;;;EAGC;AAED,gGAUC;AAMD,mGAYC;AAKD,qHAuBC;AAOD;;;EA8BC;AAWD;;;EAmDC;AAGD;;;;;;EA0BC;AAcD;;;EA+EC;AAGD;;;;;;;EAWC;AAcD;;;;;;;EAoFC;AAoBD;;;;;;;;EA2CC;AAOD,+FAkBC;AAED;;;EAGC"}
|