Show a Chip node as a device with its wave and its pitch (#386)
The Chip had eleven numbers in rows and nothing that showed what they do to a note. Its node is now a device of one size like the Compressor's, and two screens draw the wave and the pitch from the settings, worked right in the instrument graph. - ChipPanel is 840x280 in the warm grey of a handheld console. A button per wave, the "follow note" lever and a chrome knob per number, grouped Oscillator, Slide, Vibrato, Arpeggio and Repeat. - The wave screen draws two periods as loud as the Chip plays them, a square at its duty. toneValue and periodicSteps come out of chip.ts, so the screen and the worklet share one formula. - The pitch screen draws what slide, delta slide, vibrato, arpeggio and repeat make of the pitch over a note. It ends where the note falls below "min frequency" and starts from A4 while the Chip follows the note. - Idle settings are faded, wired ones greyed and locked, as the rows were. Every change is one undo step. - Knobs take a bipolar range: 0 straight up, the taper bends each half. Slide, delta slide, duty sweep and arpeggio use it. - The Compressor's auto-gain lever is now a Lever with a name of its own, which the Chip uses for "follow note". - Presets give a Chip five columns, as a Filter has four since #385. Found on the way: - With the Chip this wide the Laser preset is about 3060 units. In a 1280 px window Fit stops at the least zoom of 0.25 and cuts the Notes node off. The zoom limit is left as it is. Tests: - Unit: chip-view (9 new), knob (1 new), chip, chip-presets and hardware-view, 53 green - e2e 34 on melektaus-atx: ALL PASSED, 131 checks (1 new). The first run was red on the check itself: a Laser's "min frequency" can lie above A4, and the screen then rightly says the note ends at once. - Gate on melektaus-atx: CHECK EXIT 0, 285 files, 4377 tests - Counter-check: a square drawn with its duty mirrored turns the wave test red Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c087993000
commit
eb78f3cbf3
14 files changed
+1190
-26
No files matched your search
+41
-11
@@ -119,18 +119,12 @@ export class ChipPlayer {
|
||||
private sample(width: number): number {
|
||||
const p = this.phase;
|
||||
switch (this.shape.wave) {
|
||||
case 'square':
|
||||
return p < width ? 0.5 : -0.5;
|
||||
case 'saw':
|
||||
return 1 - 2 * p;
|
||||
case 'triangle':
|
||||
return p < 0.5 ? 4 * p - 1 : 3 - 4 * p;
|
||||
case 'sine':
|
||||
return Math.sin(2 * Math.PI * p);
|
||||
case 'noise':
|
||||
return this.noise[Math.min(NOISE_STEPS - 1, Math.floor(p * NOISE_STEPS))]!;
|
||||
case 'periodic':
|
||||
return this.lfsr & 1 ? 0.5 : -0.5;
|
||||
default:
|
||||
return toneValue(this.shape.wave, p, width);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,12 +139,48 @@ export class ChipPlayer {
|
||||
for (let i = 0; i < NOISE_STEPS; i++) this.noise[i] = this.random() * 2 - 1;
|
||||
}
|
||||
|
||||
/** One step of the 15-bit noise register of the NES in its short mode: bit 0 and bit 6 feed back. */
|
||||
private clock(): void {
|
||||
const feedback = (this.lfsr ^ (this.lfsr >> 6)) & 1;
|
||||
this.lfsr = (this.lfsr >> 1) | (feedback << 14);
|
||||
this.lfsr = clocked(this.lfsr);
|
||||
}
|
||||
}
|
||||
|
||||
/** A wave with no noise in it at `phase` of its period, 0 to 1; also what a Chip's node draws (#386). */
|
||||
export function toneValue(
|
||||
wave: Exclude<ChipWave, 'noise' | 'periodic'>,
|
||||
phase: number,
|
||||
width: number,
|
||||
): number {
|
||||
switch (wave) {
|
||||
case 'square':
|
||||
return phase < width ? 0.5 : -0.5;
|
||||
case 'saw':
|
||||
return 1 - 2 * phase;
|
||||
case 'triangle':
|
||||
return phase < 0.5 ? 4 * phase - 1 : 3 - 4 * phase;
|
||||
case 'sine':
|
||||
return Math.sin(2 * Math.PI * phase);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The short noise of the NES as its register gives it, `steps` values from the start: what a
|
||||
* periodic Chip plays over its first period, the same every time.
|
||||
*/
|
||||
export function periodicSteps(steps = PERIODIC_STEPS): number[] {
|
||||
let lfsr = 1;
|
||||
const out: number[] = [];
|
||||
for (let i = 0; i < steps; i++) {
|
||||
lfsr = clocked(lfsr);
|
||||
out.push(lfsr & 1 ? 0.5 : -0.5);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** One step of the 15-bit noise register of the NES in its short mode: bit 0 and bit 6 feed back. */
|
||||
function clocked(lfsr: number): number {
|
||||
const feedback = (lfsr ^ (lfsr >> 6)) & 1;
|
||||
return (lfsr >> 1) | (feedback << 14);
|
||||
}
|
||||
|
||||
const at = (values: Float32Array, i: number) => (values.length > 1 ? values[i]! : values[0]!);
|
||||
const finite = (value: number, fallback: number) => (Number.isFinite(value) ? value : fallback);
|
||||
Reference in new issue
Block a user