diff --git a/src/automation.ts b/src/automation.ts index f99b54a..db76061 100644 --- a/src/automation.ts +++ b/src/automation.ts @@ -13,12 +13,23 @@ export enum AudioRampType { EXPONENTIAL = 'exponential', /** - * Natural ramp. Depending on the adjustment being made, this will either be a - * logarithmic adjustment, or an equal-power adjustment. In general, this option - * will produce the best sounding results compared to the other options, and in - * general should always be preferred over the others. + * Natural ramp. This is like exponential, but ideal for adjustments where + * you want a long tail, perfect for fading out sounds. */ NATURAL = 'natural', + + /** + * Equal power ramp. This is ideal for crossfading two sources. + */ + EQUAL_POWER = 'equal_power', + + /** + * Inverse equal power ramp. Advanced usages only! + * + * This should only be used in tandem with the normal equal power ramp, + * specifically applied to the incoming source of a crossfade. + */ + EQUAL_POWER_IN = 'equal_power_in', } /** @@ -150,6 +161,34 @@ export default function automation( ); break; } + case AudioRampType.EQUAL_POWER: + case AudioRampType.EQUAL_POWER_IN: { + // Web Audio API does not have a built in equal power ramp + // setValueCurveAtTime linearly interpolates between values + const pollRate = 10; + const length = Math.round(pollRate * options.duration); + const valueCurve = new Float32Array(length); + const halfPi = Math.PI / 2; + const squashFactor = halfPi / length; + if (options.ramp == AudioRampType.EQUAL_POWER) { + for (let index = 0; index < length; index++) { + // V_0 -> V_1 == V_1 - (V_1 - V_0) * cos( (t - T) * (π / 2T) + (π / 2) ) + valueCurve[index] = + value - difference * Math.cos((index - length) * squashFactor + halfPi); + } + } else { + for (let index = 0; index < length; index++) { + // V_0 -> V_1 == V_0 + (V_1 - V_0) * cos( (t - T) * (π / 2T) ) + valueCurve[index] = currentValue + difference * Math.cos((index - length) * squashFactor); + } + } + audioParam.setValueCurveAtTime( + valueCurve, + options.delay + audioContext.currentTime, + options.duration, + ); + break; + } default: { console.warn(`Automation function received unknown ramp type ${options.ramp}`); audioParam.setValueAtTime(value, options.delay + audioContext.currentTime); diff --git a/src/defaults.ts b/src/defaults.ts index 94251da..8e112ae 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -133,12 +133,12 @@ export const trackSwapOutIn: TrackSwapAdvancedOptions = Object.freeze({ */ export const trackSwapCross: TrackSwapAdvancedOptions = Object.freeze({ oldSource: Object.freeze({ - ramp: AudioRampType.NATURAL, + ramp: AudioRampType.EQUAL_POWER, delay: 0, duration: 1.2, }), newSource: Object.freeze({ - ramp: AudioRampType.NATURAL, + ramp: AudioRampType.EQUAL_POWER_IN, delay: 0, duration: 1.2, }),