import { AbstractParam } from "../context/AbstractParam"; import { dbToGain, gainToDb } from "../type/Conversions"; import { AudioRange, Decibels, Frequency, NormalRange, Positive, Time, Unit, UnitName } from "../type/Units"; import { optionsFromArguments } from "../util/Defaults"; import { Timeline } from "../util/Timeline"; import { isDefined, isNumber } from "../util/TypeCheck"; import { ToneWithContext, ToneWithContextOptions } from "./ToneWithContext"; export interface ParamOptions extends ToneWithContextOptions { units: UnitName; value?: any; param: AudioParam; convert: boolean; } /** * the possible automation types */ type AutomationType = "linear" | "exponential" | "setValue" | "setTarget" | "cancel"; /** * The events on the automation */ export interface AutomationEvent { type: AutomationType; time: number; value: number; constant?: number; } /** * Param wraps the native Web Audio's AudioParam to provide * additional unit conversion functionality. It also * serves as a base-class for classes which have a single, * automatable parameter. */ export class Param extends ToneWithContext implements AbstractParam { name = "Param"; static getDefaults(): ParamOptions { return Object.assign(ToneWithContext.getDefaults(), { convert: true, units: "number" as UnitName, } as ParamOptions); } /** * The input connection */ readonly input: AudioParam; readonly units: UnitName; convert: boolean; overridden: boolean = false; /** * The timeline which tracks all of the automations. */ protected _events: Timeline; /** * The native parameter to control */ protected _param: AudioParam; /** * The default value before anything is assigned */ protected _initialValue: number; /** * The minimum output value */ private _minOutput = 1e-5; constructor(param: AudioParam, units?: Unit, convert?: boolean); constructor(options: Partial); constructor() { super(optionsFromArguments(Param.getDefaults(), arguments, ["param", "units", "convert"])); const options = optionsFromArguments(Param.getDefaults(), arguments, ["param", "units", "convert"]); this.assert(isDefined(options.param) && options.param instanceof AudioParam, "param must be an AudioParam"); // initialize this._param = this.input = options.param; this._events = new Timeline(1000); this._initialValue = this._param.defaultValue; this.units = options.units; this.convert = options.convert; // if the value is defined, set it immediately if (isDefined(options.value)) { this.setValueAtTime(options.value, 0); } } get value(): Type { const now = this.now(); return this.getValueAtTime(now); } set value(value: Type) { this._initialValue = this._fromType(value); this.cancelScheduledValues(this.now()); this.setValueAtTime(value, this.now()); } get minValue(): number { if (this.units === "time" || this.units === "frequency" || this.units === "normalRange" || this.units === "positive" || this.units === "transportTime" || this.units === "ticks" || this.units === "bpm" || this.units === "hertz" || this.units === "samples") { return 0; } else if (this.units === "audioRange") { return -1; } else if (this.units === "decibels") { return -Infinity; } else { return this._param.minValue; } } get maxValue(): number { if (this.units === "normalRange" || this.units === "audioRange") { return 1; } else { return this._param.maxValue; } } /** * Type guard based on the unit name */ private _is(arg: any, type: UnitName): arg is T { return this.units === type; } /** * Convert the given value from the type specified by Param.units * into the destination value (such as Gain or Frequency). */ protected _fromType(val: Type): number { if (this.convert && !this.overridden) { if (this._is