diff --git a/src/MusicMixer.ts b/src/MusicMixer.ts index 4322ab0..d3eb429 100644 --- a/src/MusicMixer.ts +++ b/src/MusicMixer.ts @@ -1,5 +1,7 @@ -import AudioSourceNode, { AudioAdjustmentOptions } from './AudioSourceNode'; +import AudioSourceNode, { AudioAdjustmentOptions, AudioRampType } from './AudioSourceNode'; import TrackSingle, { Track, TrackGroup } from './Track'; +import buildOptions from './defaults'; +import * as defaults from './defaults'; /** * MusicMixer @@ -89,9 +91,76 @@ class MusicMixer { * @returns {MusicMixer} this MusicMixer */ public volume(volume: number, options?: AudioAdjustmentOptions): MusicMixer { - console.log(`stub volume changed to ${volume} with ${options}`); + const currentValue = this.gainNode.gain.value; + const difference = volume - currentValue; + const adjustment = options ? buildOptions(options) : defaults.automationDefault; + + // Stop automations and immediately ramp. + if (Math.abs(difference) < Number.EPSILON) { + this.gainNode.gain.cancelAndHoldAtTime(this.currentTime); + this.gainNode.gain.setValueAtTime(currentValue, this.currentTime); + this.gainNode.gain.linearRampToValueAtTime(volume, adjustment.delay + this.currentTime); + return this; + } + + this.gainNode.gain.cancelAndHoldAtTime(adjustment.delay + this.currentTime); + this.gainNode.gain.setValueAtTime(currentValue, adjustment.delay + this.currentTime); + if (Array.isArray(adjustment.ramp)) { + const valueCurve = []; + for (const markiplier of adjustment.ramp) { + valueCurve.push(currentValue + difference * markiplier); + } + this.gainNode.gain.setValueCurveAtTime( + valueCurve, + adjustment.delay + this.currentTime, + adjustment.duration, + ); + return this; + } + + switch (adjustment.ramp) { + case AudioRampType.EXPONENTIAL: { + this.gainNode.gain.exponentialRampToValueAtTime( + volume, + adjustment.delay + adjustment.duration + this.currentTime, + ); + break; + } + case AudioRampType.LINEAR: { + this.gainNode.gain.linearRampToValueAtTime( + volume, + adjustment.delay + adjustment.duration + this.currentTime, + ); + break; + } + case AudioRampType.NATURAL: { + // Logarithmic approach to value, it is 95% the way there after 3 timeConstant, so we linearly ramp at that point + const timeConstant = adjustment.duration / 4; + this.gainNode.gain.setTargetAtTime(volume, adjustment.delay + this.currentTime, timeConstant); + this.gainNode.gain.cancelAndHoldAtTime( + adjustment.delay + timeConstant * 3 + this.currentTime, + ); + // The following event is implicitly added, per WebAudio spec. + // https://webaudio.github.io/web-audio-api/#dom-audioparam-cancelandholdattime + // this.gainNode.gain.setValueAtTime(currentValue + (difference * (1 - Math.pow(Math.E, -3))), timeConstant * 3 + this.currentTime); + this.gainNode.gain.linearRampToValueAtTime( + volume, + adjustment.delay + adjustment.duration + this.currentTime, + ); + break; + } + default: { + this.gainNode.gain.setValueAtTime(volume, adjustment.delay); + break; + } + } + return this; } + + get currentTime(): number { + return this.audioContext.currentTime; + } } export default MusicMixer;