Show a Bitcrusher node as a device that draws its pixels (#399)
A Bitcrusher only said what it does in rows of numbers. How coarse its steps and holds are could not be seen. Now the node is a device of its own size. Its screen sends a sine through the node's own crush and draws what comes out in cells, as tall as a step and as wide as a hold, so the coarser the crush, the coarser the picture. - CrusherPanel draws a 680x280 plate in bubblegum pink. Bits and rate are large knobs, wet is small. Rate gives its low end more of the turn, 8 kHz sits just past the middle. - crushTrace runs a 440 Hz sine at 0 dBFS through Crusher at 48 kHz and blends it as the worklet does. Where each hold starts comes from Crusher too: a ramp through it at 16 bits takes a new value per hold. - cellOf shapes a hold. From 5 px a step or hold gets a grid line and a gap. Finer steps give a 2 px bar. A cell is never more than twice as tall as wide, so 1 bit gives bars at -1, 0 and 1. - The screen shows two periods, or 6 holds where the rate is low. The sine going in is a grey ghost. Below wet 1 the heard line lies over the cells. - Above the screen a word for how crushed it is, CLEAN to BROKEN. Under it the noise the bits leave and where the rate folds back. - 16 lamps show the bits kept, the highest on the left. A CRUSH lamp beside the name glows the more it crushes. - COLUMNS_OF gives the Bitcrusher 4 columns in the presets. - Hook __gm.bitcrusher.panel(id) for the sound FX and instrument graphs. Decided myself: - The screen is still. Nothing on it moves, so tick() says false. - How crushed counts whichever of bits or rate takes more, the rate in octaves, times wet. Found: at 8 kHz the first hold is 7 samples long, the rest 6. Six sixths add up to just under 1 in Crusher. The worklet does the same, so the screen shows what is heard. Every sfxr effect preset ends in a Bitcrusher and gets 3 columns wider. The Laser preset still fits whole on Fit. Tests: - Unit: crusher-view 13 tests (new). The screen matches Crusher with its wet on three settings. - Counter-test: without the wet in the blend the worklet test fails. - e2e 34: ALL PASSED, 143 checks (1 new). - Gate: CHECK EXIT 0, 292 files, 4484 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b145e1f288
commit
a6c00f7b8b
9 files changed
+942
-3
No files matched your search
@@ -1637,6 +1637,56 @@ export async function sound(t) {
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(fxId)}).graph;
|
||||
graph.nodes = graph.nodes.filter((n) => n.id !== ${JSON.stringify(id)});`);
|
||||
}
|
||||
// #399: a Bitcrusher is a device of its own size whose screen draws what a sine comes out as in
|
||||
// cells as tall as a step and as wide as a hold, with a lamp for each bit kept. The bits
|
||||
// and the rate knob are one undo step each, and the screen and the lamps follow them.
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(fxId)}).graph;
|
||||
graph.nodes.push(${JSON.stringify(node('fx-bc', 'SoundFxBitcrusher', 0, 400, { bits: 4 }))});`);
|
||||
await evaluate('__gm.soundfx.fit()');
|
||||
await sleep(100);
|
||||
const bcPanel = () => json("__gm.bitcrusher.panel('fx-bc')");
|
||||
const bcLit = (p) => p.bits.filter((share) => share > 0).length;
|
||||
const bcShown = await bcPanel();
|
||||
const bcBitsDial = bcShown.knobs.bits.dial;
|
||||
await drag(bcBitsDial.x, bcBitsDial.y, bcBitsDial.x, bcBitsDial.y - 40);
|
||||
const bcBits = { params: await knobParams('fx-bc'), panel: await bcPanel() };
|
||||
const bcRateDial = bcShown.knobs.rate.dial;
|
||||
await drag(bcRateDial.x, bcRateDial.y, bcRateDial.x, bcRateDial.y + 40);
|
||||
const bcRate = { params: await knobParams('fx-bc'), panel: await bcPanel() };
|
||||
for (let i = 0; i < 2; i++) await press('z', { ctrl: true });
|
||||
const bcUndone = { params: await knobParams('fx-bc'), panel: await bcPanel() };
|
||||
check(
|
||||
'a Bitcrusher node is a device whose screen draws a sine in the cells of its bits and its rate; both knobs are one undo step each',
|
||||
bcShown.size.w === 680 &&
|
||||
bcShown.size.h === 280 &&
|
||||
Object.values(bcShown.inputs).length === 4 &&
|
||||
Object.values(bcShown.inputs).every((p) => p.x < bcShown.rect.x) &&
|
||||
Object.values(bcShown.outputs).every((p) => p.x > bcShown.rect.x + bcShown.rect.w) &&
|
||||
bcShown.knobs.bits.number === '4 bit' &&
|
||||
bcShown.knobs.rate.number === '8 kHz' &&
|
||||
bcLit(bcShown) === 4 &&
|
||||
bcShown.scope.step === 0.125 &&
|
||||
bcShown.scope.holds === 37 &&
|
||||
bcShown.scope.drawn.cells === 37 &&
|
||||
bcShown.scope.drawn.cell.gap === 1 &&
|
||||
bcShown.scope.says.noise === 'NOISE -26 dB' &&
|
||||
bcShown.scope.says.fold === 'FOLDS AT 4 kHz' &&
|
||||
bcShown.crush.word !== 'CLEAN' &&
|
||||
bcBits.params.bits > 4 &&
|
||||
bcLit(bcBits.panel) > 4 &&
|
||||
bcBits.panel.scope.step < 0.125 &&
|
||||
bcRate.params.rate < 8000 &&
|
||||
bcRate.panel.scope.holds < 37 &&
|
||||
bcUndone.params.bits === 4 &&
|
||||
bcUndone.params.rate === undefined &&
|
||||
bcLit(bcUndone.panel) === 4 &&
|
||||
bcUndone.panel.scope.holds === 37,
|
||||
JSON.stringify({ bcShown, bcBits, bcRate, bcUndone }),
|
||||
);
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(fxId)}).graph;
|
||||
graph.nodes = graph.nodes.filter((n) => n.id !== 'fx-bc');`);
|
||||
await showTab('scene');
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === 'sn-logic').graph;
|
||||
|
||||
Reference in new issue
Block a user