automation-events
Version:
A module which provides an implementation of an automation event list.
264 lines (223 loc) • 13.2 kB
text/typescript
import { createExtendedExponentialRampToValueAutomationEvent } from '../functions/create-extended-exponential-ramp-to-value-automation-event';
import { createExtendedLinearRampToValueAutomationEvent } from '../functions/create-extended-linear-ramp-to-value-automation-event';
import { createSetValueAutomationEvent } from '../functions/create-set-value-automation-event';
import { createSetValueCurveAutomationEvent } from '../functions/create-set-value-curve-automation-event';
import { getEndTimeAndValueOfPreviousAutomationEvent } from '../functions/get-end-time-and-value-of-previous-automation-event';
import { getEventTime } from '../functions/get-event-time';
import { getExponentialRampValueAtTime } from '../functions/get-exponential-ramp-value-at-time';
import { getLinearRampValueAtTime } from '../functions/get-linear-ramp-value-at-time';
import { getTargetValueAtTime } from '../functions/get-target-value-at-time';
import { getValueCurveValueAtTime } from '../functions/get-value-curve-value-at-time';
import { getValueOfAutomationEventAtIndexAtTime } from '../functions/get-value-of-automation-event-at-index-at-time';
import { isAnyRampToValueAutomationEvent } from '../guards/any-ramp-to-value-automation-event';
import { isCancelAndHoldAutomationEvent } from '../guards/cancel-and-hold-automation-event';
import { isCancelScheduledValuesAutomationEvent } from '../guards/cancel-scheduled-values-automation-event';
import { isExponentialRampToValueAutomationEvent } from '../guards/exponential-ramp-to-value-automation-event';
import { isLinearRampToValueAutomationEvent } from '../guards/linear-ramp-to-value-automation-event';
import { isSetTargetAutomationEvent } from '../guards/set-target-automation-event';
import { isSetValueAutomationEvent } from '../guards/set-value-automation-event';
import { isSetValueCurveAutomationEvent } from '../guards/set-value-curve-automation-event';
import { TAutomationEvent, TPersistentAutomationEvent } from '../types';
export class AutomationEventList {
private _automationEvents: TPersistentAutomationEvent[];
private _currenTime: number;
private _defaultValue: number;
constructor(defaultValue: number) {
this._automationEvents = [];
this._currenTime = 0;
this._defaultValue = defaultValue;
}
public [Symbol.iterator](): Iterator<TPersistentAutomationEvent> {
return this._automationEvents[Symbol.iterator]();
}
public add(automationEvent: TAutomationEvent): boolean {
const eventTime = getEventTime(automationEvent);
if (isCancelAndHoldAutomationEvent(automationEvent) || isCancelScheduledValuesAutomationEvent(automationEvent)) {
const index = this._automationEvents.findIndex((currentAutomationEvent) => {
if (isCancelScheduledValuesAutomationEvent(automationEvent) && isSetValueCurveAutomationEvent(currentAutomationEvent)) {
return currentAutomationEvent.startTime + currentAutomationEvent.duration >= eventTime;
}
return getEventTime(currentAutomationEvent) >= eventTime;
});
const removedAutomationEvent = this._automationEvents[index];
if (index !== -1) {
this._automationEvents = this._automationEvents.slice(0, index);
}
if (isCancelAndHoldAutomationEvent(automationEvent)) {
const lastAutomationEvent = this._automationEvents[this._automationEvents.length - 1];
if (removedAutomationEvent !== undefined && isAnyRampToValueAutomationEvent(removedAutomationEvent)) {
if (lastAutomationEvent !== undefined && isSetTargetAutomationEvent(lastAutomationEvent)) {
throw new Error('The internal list is malformed.');
}
const startTime =
lastAutomationEvent === undefined
? removedAutomationEvent.insertTime
: isSetValueCurveAutomationEvent(lastAutomationEvent)
? lastAutomationEvent.startTime + lastAutomationEvent.duration
: getEventTime(lastAutomationEvent);
const startValue =
lastAutomationEvent === undefined
? this._defaultValue
: isSetValueCurveAutomationEvent(lastAutomationEvent)
? lastAutomationEvent.values[lastAutomationEvent.values.length - 1]
: lastAutomationEvent.value;
const value = isExponentialRampToValueAutomationEvent(removedAutomationEvent)
? getExponentialRampValueAtTime(eventTime, startTime, startValue, removedAutomationEvent)
: getLinearRampValueAtTime(eventTime, startTime, startValue, removedAutomationEvent);
const truncatedAutomationEvent = isExponentialRampToValueAutomationEvent(removedAutomationEvent)
? createExtendedExponentialRampToValueAutomationEvent(value, eventTime, this._currenTime)
: createExtendedLinearRampToValueAutomationEvent(value, eventTime, this._currenTime);
this._automationEvents.push(truncatedAutomationEvent);
}
if (lastAutomationEvent !== undefined && isSetTargetAutomationEvent(lastAutomationEvent)) {
this._automationEvents.push(createSetValueAutomationEvent(this.getValue(eventTime), eventTime));
}
if (
lastAutomationEvent !== undefined &&
isSetValueCurveAutomationEvent(lastAutomationEvent) &&
lastAutomationEvent.startTime + lastAutomationEvent.duration > eventTime
) {
const duration = eventTime - lastAutomationEvent.startTime;
const ratio = (lastAutomationEvent.values.length - 1) / lastAutomationEvent.duration;
const length = Math.max(2, 1 + Math.ceil(duration * ratio));
const fraction = (duration / (length - 1)) * ratio;
const values = lastAutomationEvent.values.slice(0, length);
if (fraction < 1) {
for (let i = 1; i < length; i += 1) {
const factor = (fraction * i) % 1;
values[i] = lastAutomationEvent.values[i - 1] * (1 - factor) + lastAutomationEvent.values[i] * factor;
}
}
this._automationEvents[this._automationEvents.length - 1] = createSetValueCurveAutomationEvent(
values,
lastAutomationEvent.startTime,
duration
);
}
}
} else {
const index = this._automationEvents.findIndex((currentAutomationEvent) => getEventTime(currentAutomationEvent) > eventTime);
const previousAutomationEvent =
index === -1 ? this._automationEvents[this._automationEvents.length - 1] : this._automationEvents[index - 1];
if (
previousAutomationEvent !== undefined &&
isSetValueCurveAutomationEvent(previousAutomationEvent) &&
getEventTime(previousAutomationEvent) + previousAutomationEvent.duration > eventTime
) {
return false;
}
const persistentAutomationEvent = isExponentialRampToValueAutomationEvent(automationEvent)
? createExtendedExponentialRampToValueAutomationEvent(automationEvent.value, automationEvent.endTime, this._currenTime)
: isLinearRampToValueAutomationEvent(automationEvent)
? createExtendedLinearRampToValueAutomationEvent(automationEvent.value, eventTime, this._currenTime)
: automationEvent;
if (index === -1) {
this._automationEvents.push(persistentAutomationEvent);
} else {
if (
isSetValueCurveAutomationEvent(automationEvent) &&
eventTime + automationEvent.duration > getEventTime(this._automationEvents[index])
) {
return false;
}
this._automationEvents.splice(index, 0, persistentAutomationEvent);
}
}
return true;
}
public flush(time: number): void {
const index = this._automationEvents.findIndex((currentAutomationEvent) => getEventTime(currentAutomationEvent) > time);
if (index > 1) {
const remainingAutomationEvents = this._automationEvents.slice(index - 1);
const firstRemainingAutomationEvent = remainingAutomationEvents[0];
if (isSetTargetAutomationEvent(firstRemainingAutomationEvent)) {
remainingAutomationEvents.unshift(
createSetValueAutomationEvent(
getValueOfAutomationEventAtIndexAtTime(
this._automationEvents,
index - 2,
firstRemainingAutomationEvent.startTime,
this._defaultValue
),
firstRemainingAutomationEvent.startTime
)
);
}
this._automationEvents = remainingAutomationEvents;
}
}
public getValue(time: number): number {
if (this._automationEvents.length === 0) {
return this._defaultValue;
}
const indexOfNextEvent = this._automationEvents.findIndex((automationEvent) => getEventTime(automationEvent) > time);
const nextAutomationEvent = this._automationEvents[indexOfNextEvent];
const indexOfCurrentEvent = (indexOfNextEvent === -1 ? this._automationEvents.length : indexOfNextEvent) - 1;
const currentAutomationEvent = this._automationEvents[indexOfCurrentEvent];
if (
currentAutomationEvent !== undefined &&
isSetTargetAutomationEvent(currentAutomationEvent) &&
(nextAutomationEvent === undefined ||
!isAnyRampToValueAutomationEvent(nextAutomationEvent) ||
nextAutomationEvent.insertTime > time)
) {
return getTargetValueAtTime(
time,
getValueOfAutomationEventAtIndexAtTime(
this._automationEvents,
indexOfCurrentEvent - 1,
currentAutomationEvent.startTime,
this._defaultValue
),
currentAutomationEvent
);
}
if (
currentAutomationEvent !== undefined &&
isSetValueAutomationEvent(currentAutomationEvent) &&
(nextAutomationEvent === undefined || !isAnyRampToValueAutomationEvent(nextAutomationEvent))
) {
return currentAutomationEvent.value;
}
if (
currentAutomationEvent !== undefined &&
isSetValueCurveAutomationEvent(currentAutomationEvent) &&
(nextAutomationEvent === undefined ||
!isAnyRampToValueAutomationEvent(nextAutomationEvent) ||
currentAutomationEvent.startTime + currentAutomationEvent.duration > time)
) {
if (time < currentAutomationEvent.startTime + currentAutomationEvent.duration) {
return getValueCurveValueAtTime(time, currentAutomationEvent);
}
return currentAutomationEvent.values[currentAutomationEvent.values.length - 1];
}
if (
currentAutomationEvent !== undefined &&
isAnyRampToValueAutomationEvent(currentAutomationEvent) &&
(nextAutomationEvent === undefined || !isAnyRampToValueAutomationEvent(nextAutomationEvent))
) {
return currentAutomationEvent.value;
}
if (nextAutomationEvent !== undefined && isExponentialRampToValueAutomationEvent(nextAutomationEvent)) {
const [startTime, value] = getEndTimeAndValueOfPreviousAutomationEvent(
this._automationEvents,
indexOfCurrentEvent,
currentAutomationEvent,
nextAutomationEvent,
this._defaultValue
);
return getExponentialRampValueAtTime(time, startTime, value, nextAutomationEvent);
}
if (nextAutomationEvent !== undefined && isLinearRampToValueAutomationEvent(nextAutomationEvent)) {
const [startTime, value] = getEndTimeAndValueOfPreviousAutomationEvent(
this._automationEvents,
indexOfCurrentEvent,
currentAutomationEvent,
nextAutomationEvent,
this._defaultValue
);
return getLinearRampValueAtTime(time, startTime, value, nextAutomationEvent);
}
return this._defaultValue;
}
}