Move a sound left or right with a Pan node (#319)
A sound FX had no way to place a sound in the stereo field. The new Pan node does that on a voice, a bus and a track alike. - One input, Pan from -1 (left) to 1 (right), 0 by default. Behind it is a StereoPannerNode. A typed-in 0 leaves the node out, a pan fed from a parameter stays at any value. - It moves with Set sound FX parameters, and an Envelope can move it in an instrument. - As the issue decided, no pan from the position in Play sound and no width control for stereo sounds. Finding: the issue and the plan said a stereo sound gets a balance. Web Audio pans stereo differently: the side it goes to stays and the other side moves over into it. The node description and the wiki say so, the e2e check measures it. Tests: - Unit soundfx 46 (1 new), sound-library 97 (1 new), green. - Counter-check: a pan from a parameter at 0 left out, 1 test red. - e2e 34 (melektaus-atx): ALL PASSED, 77 checks (1 new). Chrome offline, a mono sine at 0.5 gives 0.5 / 0 at -1 and 0.354 / 0.354 at 0. A stereo one at 0.5 panned 0.5 gives 0.354 / 0.854. - The gate runs once on the last commit of this series. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e1ca6a5f5a
commit
c99bda6489
5 files changed
+94
-2
No files matched your search
@@ -975,6 +975,36 @@ export async function sound(t) {
|
||||
filtered.notch < -40,
|
||||
JSON.stringify(filtered),
|
||||
);
|
||||
// #319: Pan rendered offline. A mono sine at 0.5 all to the left leaves the right silent, in the
|
||||
// middle each side gets 0.707 of it. A stereo sound panned half right keeps its right side
|
||||
// and moves part of its left into it, as Web Audio pans stereo: left 0.707, right 1 + 0.707.
|
||||
const panned = await evaluate(`
|
||||
(async () => {
|
||||
const RATE = 48000;
|
||||
const peaks = async (pan, channels) => {
|
||||
const context = new OfflineAudioContext(2, RATE / 10, RATE);
|
||||
const sound = context.createBuffer(channels, RATE / 10, RATE);
|
||||
for (let c = 0; c < channels; c++) {
|
||||
const data = sound.getChannelData(c);
|
||||
for (let i = 0; i < data.length; i++) data[i] = Math.sin((2 * Math.PI * 1000 * i) / RATE) * 0.5;
|
||||
}
|
||||
const source = context.createBufferSource();
|
||||
source.buffer = sound;
|
||||
const nodes = __gm.sound.stageNodes(context, { kind: 'pan', pan });
|
||||
source.connect(nodes.input);
|
||||
nodes.output.connect(context.destination);
|
||||
source.start();
|
||||
const out = await context.startRendering();
|
||||
return [0, 1].map((c) => +Math.max(...out.getChannelData(c).map(Math.abs)).toFixed(3));
|
||||
};
|
||||
return { left: await peaks(-1, 1), middle: await peaks(0, 1), balance: await peaks(0.5, 2) };
|
||||
})()`);
|
||||
check(
|
||||
'Pan moves a mono sound between the sides, and a stereo one towards the side it goes to',
|
||||
JSON.stringify(panned) ===
|
||||
JSON.stringify({ left: [0.5, 0], middle: [0.354, 0.354], balance: [0.354, 0.854] }),
|
||||
JSON.stringify(panned),
|
||||
);
|
||||
// #329: the Chip on the worklet, rendered offline. A square at 220 Hz with a pitch of 12 and a duty
|
||||
// of 0.25, starting at 0.25 s: silence until then, 440 periods a second after it, up a
|
||||
// quarter of each. Sliding two octaves a second down from 440 Hz with a lowest frequency of
|
||||
|
||||
Reference in new issue
Block a user