UNPKG

react-native-audio-api

Version:

react-native-audio-api provides system for controlling audio in React Native environment compatible with Web Audio API specification

256 lines (255 loc) • 7.43 kB
"use strict"; import AudioParam from "../../AudioParam.web.js"; const RAMP_SAMPLE_INTERVAL = 0.05; export default class AudioStretcherParam extends AudioParam { _events = []; constructor(context, initialValue, defaultValue, minValue, maxValue, onChange, onCancel) { const source = new globalThis.ConstantSourceNode(context.context, { offset: initialValue }); source.start(0); super(source.offset, context); this.defaultValue = defaultValue; this.minValue = minValue; this.maxValue = maxValue; this._initialValue = initialValue; this._onChange = onChange ?? null; this._onCancel = onCancel ?? null; } get value() { return super.value; } set value(v) { super.value = v; this._events = [{ type: 'setValue', value: v, time: this.context.currentTime }]; this._onChange?.({ type: 'setValue', value: v, time: this.context.currentTime }); } getValueAtTime(time) { if (this._events.length === 0) { return this._initialValue; } let value = this._initialValue; let tPrev = -Infinity; const sorted = [...this._events].sort((a, b) => a.time - b.time); for (const event of sorted) { if (event.type === 'setValue') { if (time < event.time) { return value; } value = event.value; tPrev = event.time; continue; } if (event.type === 'linearRamp') { const rampStart = event.rampStartTime ?? tPrev; if (time < rampStart) { continue; } if (time >= event.time) { value = event.value; tPrev = event.time; continue; } const v0 = event.startValue ?? value; const fraction = (time - rampStart) / (event.time - rampStart); return v0 + fraction * (event.value - v0); } if (event.type === 'exponentialRamp') { const rampStart = event.rampStartTime ?? tPrev; if (time < rampStart) { continue; } if (time >= event.time) { value = event.value; tPrev = event.time; continue; } const v0 = event.startValue ?? value; if (v0 === 0 || event.value === 0) { return v0; } const fraction = (time - rampStart) / (event.time - rampStart); return v0 * Math.pow(event.value / v0, fraction); } if (event.type === 'setTarget') { if (time < event.time) { return value; } const dt = time - event.time; const v0 = event.startValue; return event.target + (v0 - event.target) * Math.exp(-dt / event.timeConstant); } if (event.type === 'valueCurve') { if (time < event.time) { return value; } if (time >= event.time + event.duration) { value = event.values[event.values.length - 1]; tPrev = event.time + event.duration; continue; } const progress = (time - event.time) / event.duration; const idx = progress * (event.values.length - 1); const i0 = Math.floor(idx); const i1 = Math.min(i0 + 1, event.values.length - 1); const frac = idx - i0; return event.values[i0] + frac * (event.values[i1] - event.values[i0]); } } return value; } getRampStartTime(endTime) { let tPrev = -Infinity; const sorted = [...this._events].sort((a, b) => a.time - b.time); for (const event of sorted) { if (event.time >= endTime) { break; } tPrev = event.time; } if (tPrev === -Infinity) { return this.context.currentTime; } return tPrev; } sampleAutomation(event, mapValue) { const samples = []; if (event.type === 'setValue') { samples.push({ value: mapValue(event.value), time: event.time }); return samples; } if (event.type === 'linearRamp' || event.type === 'exponentialRamp') { const startTime = event.rampStartTime ?? this.getRampStartTime(event.time); const endTime = event.time; const duration = endTime - startTime; if (duration <= 0) { samples.push({ value: mapValue(event.value), time: endTime }); return samples; } const stepCount = Math.max(2, Math.ceil(duration / RAMP_SAMPLE_INTERVAL)); for (let i = 0; i <= stepCount; i++) { const t = startTime + duration * i / stepCount; samples.push({ value: mapValue(this.getValueAtTime(t)), time: t }); } return samples; } if (event.type === 'setTarget') { const startTime = event.time; const duration = Math.max(event.timeConstant * 5, RAMP_SAMPLE_INTERVAL); const stepCount = Math.max(2, Math.ceil(duration / RAMP_SAMPLE_INTERVAL)); for (let i = 0; i <= stepCount; i++) { const t = startTime + duration * i / stepCount; samples.push({ value: mapValue(this.getValueAtTime(t)), time: t }); } return samples; } if (event.type === 'valueCurve') { const stepCount = Math.max(2, Math.ceil(event.duration / RAMP_SAMPLE_INTERVAL)); for (let i = 0; i <= stepCount; i++) { const t = event.time + event.duration * i / stepCount; samples.push({ value: mapValue(this.getValueAtTime(t)), time: t }); } return samples; } return samples; } _recordEvent(event) { this._events.push(event); } _removeEventsFrom(cancelTime) { this._events = this._events.filter(event => event.time < cancelTime); } setValueAtTime(value, startTime) { const event = { type: 'setValue', value, time: startTime }; this._recordEvent(event); this._onChange?.(event); return this; } linearRampToValueAtTime(value, endTime) { const rampStartTime = this.getRampStartTime(endTime); const event = { type: 'linearRamp', value, time: endTime, rampStartTime, startValue: this.getValueAtTime(rampStartTime) }; this._recordEvent(event); this._onChange?.(event); return this; } exponentialRampToValueAtTime(value, endTime) { const rampStartTime = this.getRampStartTime(endTime); const event = { type: 'exponentialRamp', value, time: endTime, rampStartTime, startValue: this.getValueAtTime(rampStartTime) }; this._recordEvent(event); this._onChange?.(event); return this; } setTargetAtTime(target, startTime, timeConstant) { const event = { type: 'setTarget', target, startValue: this.getValueAtTime(startTime), time: startTime, timeConstant }; this._recordEvent(event); this._onChange?.(event); return this; } setValueCurveAtTime(values, startTime, duration) { const event = { type: 'valueCurve', values, time: startTime, duration }; this._recordEvent(event); this._onChange?.(event); return this; } cancelScheduledValues(cancelTime) { this._removeEventsFrom(cancelTime); this._onCancel?.(cancelTime); return this; } cancelAndHoldAtTime(cancelTime) { this._removeEventsFrom(cancelTime); this._onCancel?.(cancelTime); return this; } } //# sourceMappingURL=AudioStretcherParam.js.map