Skip to content
Open

Refs #104

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "./core/audioParamPolyfill.js";
import { getAudioContext, setAudioContext, userStartAudio, userStopAudio } from './core/Utils';
p5.prototype.getAudioContext = getAudioContext;
p5.prototype.setAudioContext = setAudioContext;
Expand Down
7 changes: 5 additions & 2 deletions src/core/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* @for p5.sound
*/

import { polyfillAudioListener } from "./audioListenerPolyfill.js";
import { getContext as ToneGetContext, setContext as ToneSetContext } from "tone/build/esm/core/Global.js";
import { start as ToneStart } from "tone/build/esm/core/Global.js";

Expand Down Expand Up @@ -53,8 +54,9 @@ function clamp(value, min, max) {
*/
function getAudioContext() {
if (!audioContext) {
audioContext = new window.AudioContext();
ToneSetContext(audioContext);
audioContext = ToneGetContext().rawContext;
//polyfillAudioListener(audioContext);
//ToneSetContext(audioContext);
}
return audioContext;
}
Expand Down Expand Up @@ -94,6 +96,7 @@ function getAudioContext() {
*/
function setAudioContext(context) {
audioContext = context;
polyfillAudioListener(audioContext);
ToneSetContext(context);
}

Expand Down
52 changes: 52 additions & 0 deletions src/core/audioListenerPolyfill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Firefox does not implement the AudioParam-based AudioListener
* properties (positionX/Y/Z, forwardX/Y/Z, upX/Y/Z) — only the
* deprecated setPosition()/setOrientation() methods.
*
* We fake each property with a real AudioParam borrowed from a
* ConstantSourceNode, then drive the deprecated setters from their
* values once per animation frame.
*
* Limitation: listener movement becomes k-rate (updated per frame,
* ~60Hz), not a-rate. Fine for perceptual listener movement; not
* sample-accurate automation. Per-source Panner3D positioning is
* unaffected (Firefox supports PannerNode params natively).
*/
const LISTENER_PARAMS = [
["positionX", 0], ["positionY", 0], ["positionZ", 0],
["forwardX", 0], ["forwardY", 0], ["forwardZ", -1],
["upX", 0], ["upY", 1], ["upZ", 0],
];

export function polyfillAudioListener(ctx) {
const listener = ctx.listener;
if (listener.positionX) return; // Chrome/Edge/Safari: native, no-op

// Muted sink keeps the donor nodes in the processing graph —
// params on unpulled nodes may not advance their automation.
const sink = ctx.createGain();
sink.gain.value = 0;
sink.connect(ctx.destination);

const params = {};
for (const [name, defaultValue] of LISTENER_PARAMS) {
const source = ctx.createConstantSource();
source.offset.value = defaultValue;
source.connect(sink);
source.start();
params[name] = source.offset;
Object.defineProperty(listener, name, { value: source.offset });
}

let last = [];
(function sync() {
if (ctx.state === "closed") return;
const v = LISTENER_PARAMS.map(([n]) => params[n].value);
if (v.some((x, i) => x !== last[i])) {
listener.setPosition(v[0], v[1], v[2]);
listener.setOrientation(v[3], v[4], v[5], v[6], v[7], v[8]);
last = v;
}
requestAnimationFrame(sync);
})();
}
31 changes: 31 additions & 0 deletions src/core/audioParamPolyfill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Polyfill for AudioParam.cancelAndHoldAtTime().
*
* Firefox does not implement it (Bugzilla #1308431, open since 2016).
* Chrome, Edge, and Safari have native implementations, so this is a
* no-op there — the typeof guard means we never clobber a native method.
*
* Approximation note: this holds the param at its value as of *call*
* time, not at cancelTime. That is exact for Tone.js's actual usage
* (Envelope.triggerRelease and Param.rampTo pass "now") but wrong for
* a cancel scheduled in the future during an active ramp. Documented
* limitation — do not "fix" by removing, fix by computing the ramp
* value at cancelTime if that case ever matters.
*
* Patching AudioParam.prototype covers params from AudioContext and
* OfflineAudioContext alike — they share the prototype.
*
* This module is side-effect-only. It must execute before any
* AudioNode is constructed.
*/
if (
typeof AudioParam !== "undefined" &&
typeof AudioParam.prototype.cancelAndHoldAtTime !== "function"
) {
AudioParam.prototype.cancelAndHoldAtTime = function (cancelTime) {
const heldValue = this.value;
this.cancelScheduledValues(cancelTime);
this.setValueAtTime(heldValue, cancelTime);
return this; // spec: returns the AudioParam, allows chaining
};
}
48 changes: 48 additions & 0 deletions src/effects/Delay.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,54 @@ class Delay extends p5soundMixEffect {
* @method feedback
* @for Delay
* @param {number} feedbackAmount A number between 0 and 0.99.
* @example
* <div>
* <code>
* let osc, delay, feedback = 0.5;
*
* async function setup() {
* sound = await loadSound('assets/chime.mp3')
* sound.loop(true);
*
* let cnv = createCanvas(100, 100);
* background(220);
* textAlign(CENTER);
* textWrap(WORD);
* textSize(9);
* text('click to start audio', width/2, height/2);
*
* delay = new p5.Delay(0.250, feedback);
*
* sound.disconnect();
* sound.connect(delay);
*
* describe('a sketch that demonstrates the feedback parameter.');
* }
*
* function mousePressed() {
* if (!sound.isPlaying()) {
* sound.play();
* }
* else {
* sound.stop();
* }
* }
*
* function draw() {
* if(!sound.isPlaying()) {
* background(220);
* text('click to start audio', 0, height/2 - 20, width);
* }
* else {
* background(0, 255, 255);
* text('move mouse to change feedback', 0, height/2 - 20, width);
* }
* feedback = map(mouseX, 0, width, 0.1, 0.99);
* delay.feedback(feedback);
* text('feedback: ' + feedback.toFixed(2), width/2, height/2 + 20);
* }
* </code>
* </div>
*/
feedback(value) {
this.node.feedback.rampTo(clamp(value, 0, 0.99), 0.1);
Expand Down
2 changes: 1 addition & 1 deletion src/effects/Reverb.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { p5soundMixEffect } from "../core/p5soundMixEffect.js";
/**
* Add reverb to a sound.
*
* Reverb is an effect that is used commonly in electronic sound production. It makes input sound like it is in an actual space. Large spaces have longer decay times
* Reverb is an effect that is used commonly in electronic sound production. It makes input sound like it is in an actual space. Large spaces have longer decay times, while smaller spaces produce less acoustic reflections and therefore have less distinct reverb characteristics.
* @class Reverb
* @constructor
* @extends p5soundMixEffect
Expand Down