Skip to content

Commit

Permalink
Delegate cancelAndHoldAtTime for FF
Browse files Browse the repository at this point in the history
  • Loading branch information
stephband committed Oct 30, 2023
1 parent 1d75dbb commit 78cfaff
Show file tree
Hide file tree
Showing 13 changed files with 115 additions and 65 deletions.
2 changes: 1 addition & 1 deletion module.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default Soundstage;
export { register };
export { timeAtDomTime, domTimeAtTime, getContextTime } from './modules/context.js';
export { transforms, parseValue } from './modules/transforms.js';
export { automate, automato__, isAudioParam, getValueAtTime } from './modules/automate.js';
export automate, { automato__, isAudioParam, getValueAtTime } from './modules/automate.js';
export * from './modules/encode.js';
export { requestBuffer } from './modules/request-buffer.js';
export { getEventsDuration, getEventDuration } from './modules/events.js';
3 changes: 2 additions & 1 deletion modules/assign-settings.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@

import { log, logGroup, logGroupEnd } from './print.js';
import parseValue32 from './parse/parse-value-32.js';
import { automato__, getAutomation } from './automate.js';
import { automato__ } from './automate.js';
import { getAutomation } from './param/automation.js';

const DEBUG = false;

Expand Down
105 changes: 63 additions & 42 deletions modules/automate.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,53 @@ import last from '../../fn/modules/last.js';
import overload from '../../fn/modules/overload.js';

import { isAudioContext, timeAtDomTime } from './context.js';
import { isAudioParam } from './param.js';
import { hasAutomation, getAutomation } from './param/automation.js';
import config from '../config.js';


const DEBUG = false;

// 60 frames/sec frame rate
const frameDuration = 1000 / 60;

export function isAudioNode(object) {
return window.AudioNode && window.AudioNode.prototype.isPrototypeOf(object);
}

export function getAutomation(param) {
if (DEBUG && !isAudioParam(param)) {
throw new Error('Not an AudioParam ' + JSON.stringify(param));
// Polyfill param.cancelAndHoldAtTime().
//
// This is here because it delegates to
// automate(), and putting it in a separate file creates a circular dependency,
// because automate() can call param.cancelAndHoldAtTime().

const AudioParam = window.AudioParam;
let isCancelAndHoldPolyfilled = false;

// Attempt to partially fill .cancelAndHoldAtTime()
if (AudioParam && !AudioParam.prototype.cancelAndHoldAtTime) {
// Old chrome had the same method under another name. Use that.
if (AudioParam.prototype.cancelValuesAndHoldAtTime) {
AudioParam.prototype.cancelAndHoldAtTime = AudioParam.prototype.cancelValuesAndHoldAtTime;
}

// Todo: I would love to use a WeakMap to store data about AudioParams,
// but FF refuses to allow AudioParams as WeakMap keys. So... lets use
// an expando.
return param[config.automationEventsKey] || (param[config.automationEventsKey] = []);
// Firefox has not yet implemented the spec, still, in 2023. Attempt to
// partially fill it.
else {
AudioParam.prototype.cancelAndHoldAtTime = function(time) {
if (hasAutomation(this)) {
// Automate with Soundstage automation data
automate(this, time, 'hold');
}
else {
// Do something either simple and broken, or nasty. Try simple
// and broken.
this.cancelScheduledValues(time);
}
};

isCancelAndHoldPolyfilled = true;
}
}



// Automate audio param

export const validateParamEvent = overload(get(1), {
Expand All @@ -53,41 +76,39 @@ export const validateParamEvent = overload(get(1), {
}
});

function holdFn(param, event) {
// Cancel values
param.cancelScheduledValues(event.time);

// Set a curve of the same type as the next to this time and value
if (event.curve === 'linear') {
param.linearRampToValueAtTime(event.value, event.time);
}
else if (event.curve === 'exponential') {
param.exponentialRampToValueAtTime(event.value, event.time);
}
else if (event.curve === 'step') {
param.setValueAtTime(event.value, event.time);
}
}

const curves = {
'step': (param, event) => param.setValueAtTime(event.value, event.time),
'linear': (param, event) => param.linearRampToValueAtTime(event.value, event.time),
'exponential': (param, event) => param.exponentialRampToValueAtTime(event.value, event.time),
'target': (param, event) => param.setTargetAtTime(event.value, event.time, event.duration),
'curve': (param, event) => param.setValueCurveAtTime(event.value, event.time, event.duration),
'hold': AudioParam.prototype.cancelAndHoldAtTime ?
function hold(param, event, event1) {
// Work around a Chrome bug where target curves are not
// cancelled by hold events inserted in front of them:
// https://bugs.chromium.org/p/chromium/issues/detail?id=952642&q=cancelAndHoldAtTime&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified
if (event1 && event1.curve === 'target') {
return holdFn(param, event);
'hold': isCancelAndHoldPolyfilled ?
(param, event) => {
// Cancel values
param.cancelScheduledValues(event.time);

// Set a curve of the same type as the next to this time and value
if (event.curve === 'linear') {
param.linearRampToValueAtTime(event.value, event.time);
}
else if (event.curve === 'exponential') {
param.exponentialRampToValueAtTime(event.value, event.time);
}
else if (event.curve === 'step') {
param.setValueAtTime(event.value, event.time);
}

param.cancelAndHoldAtTime(event.time);
} :

holdFn
// Todo: Fixed in 2019! Remove.
// Work around a Chrome bug where target curves are not cancelled
// by hold events inserted in front of them:
// https://bugs.chromium.org/p/chromium/issues/detail?id=952642&q=cancelAndHoldAtTime&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified
//if (event1 && event1.curve === 'target') {
// return holdFn(param, event);
//}
(param, event) => param.cancelAndHoldAtTime(event.value, event.time)
};

const createEvent = overload(id, {
Expand Down Expand Up @@ -215,16 +236,16 @@ export function automateParamEvents(param, events, time, curve, value, duration)
}

/**
automate(param, time, value, curve, decay)
automate(param, time, curve, value, duration)
param - AudioParam object
time -
value -
curve - one of 'step', 'hold', 'linear', 'exponential' or 'target'
decay - where curve is 'target', decay is a time constant for the decay curve
param - AudioParam object
time - a 32-bit floating point number
curve - one of 'step', 'hold', 'linear', 'exponential' or 'target'
value -
duration - where curve is 'target', decay is a time constant for the decay curve
**/

export function automate(param, time, curve, value, duration, notify, context) {
export default function automate(param, time, curve, value, duration, notify, context) {
if (curve === 'target' && duration === undefined) {
throw new Error('Automation curve "target" must have a duration');
}
Expand Down
3 changes: 3 additions & 0 deletions modules/node/is-audio-node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function isAudioNode(object) {
return window.AudioNode && window.AudioNode.prototype.isPrototypeOf(object);
}
8 changes: 2 additions & 6 deletions modules/param.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@

import { getAutomation, automateParamEvents } from './automate.js';
export { default as isAudioParam } from './param/is-audio-param.js';

const fadeDuration = 0.012;

/** isAudioParam(object) **/

export function isAudioParam(object) {
return window.AudioParam && window.AudioParam.prototype.isPrototypeOf(object);
}

/**
fadeInFromTime(context, param, duration, time)
Expand All @@ -26,6 +21,7 @@ export function fadeInFromTime(context, param, gain, duration, time) {
return time + duration;
}


/**
fadeInToTime(context, param, duration, time)
TODO
Expand Down
21 changes: 21 additions & 0 deletions modules/param/automation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

import isAudioParam from './is-audio-param.js';

/**
getAutomation(param)
**/

const $automation = Symbol('soundstage-automation');

export function getAutomation(param) {
if (window.DEBUG && !isAudioParam(param)) {
throw new Error('Not an AudioParam ' + JSON.stringify(param));
}

// FF refuses to allow AudioParams as WeakMap keys. So lets use an expando.
return param[$automation] || (param[$automation] = []);
}

export function hasAutomation(param) {
return !!param[$automation];
}
8 changes: 8 additions & 0 deletions modules/param/is-audio-param.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

/**
isAudioParam(object)
**/

export default function isAudioParam(object) {
return window.AudioParam && window.AudioParam.prototype.isPrototypeOf(object);
}
2 changes: 1 addition & 1 deletion modules/sequencer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Sequence from './sequencer/sequence.js';
import { print } from './print.js';
import { getDejitterTime } from './context.js';
import Playable, { IDLE, PLAYING } from './playable.js';
import { automate, getValueAtTime } from './automate.js';
import automate, { getValueAtTime } from './automate.js';
import { isRateEvent, getDuration } from './event.js';
import { timeAtBeatOfEvents } from './sequencer/location.js';
import parseEvent from './parse/parse-event.js';
Expand Down
2 changes: 1 addition & 1 deletion modules/transport.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Stream from '../../fn/modules/stream.js';
import Clock from './clock.js';

import { roundBeat } from './utilities.js';
import { automate, getValueAtTime, getAutomation } from './automate.js';
import automate, { getValueAtTime, getAutomation } from './automate.js';
import { connect, disconnect } from './connect.js';
import { barAtBeat, beatAtBar } from './sequencer/meter.js';
import { beatAtTimeOfAutomation, timeAtBeatOfAutomation } from './sequencer/location.js';
Expand Down
2 changes: 1 addition & 1 deletion nodes/envelope.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ They are capable of producing DC signal.

import last from '../../fn/modules/last.js';
import Playable from '../modules/playable.js';
import { automate, getValueAtTime, validateParamEvent } from '../modules/automate.js';
import automate, { getValueAtTime, validateParamEvent } from '../modules/automate.js';
import config from '../config.js';
import { assignSettingz__ } from '../modules/assign-settings.js';

Expand Down
12 changes: 6 additions & 6 deletions nodes/looper.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ Creates a node that records and loops audio.
**/

import { print, logGroup, logGroupEnd } from './print.js';
import Privates from '../../fn/modules/privates.js';
import NodeGraph from './graph.js';
import Playable from './play-node.js';
import Recorder from './recorder.js';
import Sample from './sample-set.js';
import Privates from '../../fn/modules/privates.js';
import NodeGraph from './graph.js';
import Playable from './play-node.js';
import Recorder from './recorder.js';
import Sample from './sample-set.js';
import { assignSettings } from '../modules/assign-settings.js';
import { automate } from '../modules/automate.js';
import automate from '../modules/automate.js';
import { getInputLatency, getOutputLatency } from '../modules/context.js';

const DEBUG = window.DEBUG;
Expand Down
10 changes: 5 additions & 5 deletions nodes/track.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { logGroup, logGroupEnd } from './print.js';
import Privates from '../../fn/modules/privates.js';
import NodeGraph from './graph.js';
import Recorder from './recorder.js';
import Sample from './sample-set.js';
import { automate } from '../modules/automate.js';
import Privates from '../../fn/modules/privates.js';
import NodeGraph from './graph.js';
import Recorder from './recorder.js';
import Sample from './sample-set.js';
import automate from '../modules/automate.js';

const DEBUG = window.DEBUG;

Expand Down
2 changes: 1 addition & 1 deletion test/unconverted/audio-param.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test } from '../../fn/module.js';
import { create, append, find } from '../../dom/module.js';
import { automate, getValueAtTime, getAutomation, requestAutomationData } from '../modules/automate.js';
import automate, { getValueAtTime, getAutomation, requestAutomationData } from '../modules/automate.js';
import { drawYAxisAmplitude, drawCurve, drawPoint } from '../modules/canvas.js';
import audio from '../modules/context.js';

Expand Down

0 comments on commit 78cfaff

Please sign in to comment.