Skip to content

LOW OCTAVE v3 test build (do not merge) - #5

Closed
steponme82 wants to merge 130 commits into
mainfrom
low-octave-test
Closed

steponme82 wants to merge 130 commits into
mainfrom
low-octave-test

Conversation

@steponme82

Copy link
Copy Markdown
Owner

Only here to make GitHub build a test firmware: OpenPocket v37 (main after the merges) with the new PIT type LOW OCTAVE, version 3 (branch low-octave: phase halving, LOWEST, COMP). The extra commit builds the simulator's WebAssembly engine in CI, for this test only. Do not merge, it gets closed once the build is downloaded.

🤖 Generated with Claude Code

isod89 and others added 30 commits November 3, 2026 20:18
…#1, BlackBox boot loop); installer rescue mode

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…es (issue #1); show the reset source

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…ets survive

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
… USB-MIDI SysEx

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…s stock settings slowed the DSP and crackled)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…(bit-exact), NAM A1 reference, fitters

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
… gear

The amps (TWIN, AC15 TB, JCM900, JCM2000, MARK IV, SHIRLEY, PRO MOD, 5150), the drives (TS9, KLON, M77 OD,
SWEDE HM-2) and the V30 cabinet have their table numbers fitted to NAM captures on real DI guitar
(docs/FITTED-MODELS.md, results table there). Same engine and CPU (+3 % on average, the heaviest preset lighter).
The other amps / drives / cabs are removed; 7 cabinets remain.

- presets saved before v34 move to the nearest new type at boot (store format 3, px_type_v34); test for format 2
- factory presets on the new types; six renamed (their old amp is gone)
- levels re-calibrated (px_trim.h); fitters read the new float tables (exponents, comments)
- docs: README, SOUNDS-AND-CONTROLS, AUDIO-SDK; credits for the captures and the DI recordings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
… drive / cab

The old type names (RECTIFIED, MUFF PI, 4x12 H30...) map to the same types the pedal picks for its own saved
presets (px_type_v34); each change is listed in the import notes. Test added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…y and reverb lines (89 KB of RAM freed)

- reverb: 8-line feedback delay network (Hadamard, decay set per line at low frequencies and at 4 kHz, 4 slowly
  modulated lines, early reflections, Dattorro's input diffusers) and Dattorro's plate; 22.05 kHz, one 16-bit ring.
  Decay times now match DECAY; less ringing in long tails; levels matched to v34; tails fade out at the last bits
- delay: 16-bit line, TIME up to 2 s (0-80 = 20..1000 ms, 80-100 = 1..2 s), cubic reads (also chorus, flanger,
  vibrato, rotary); the read position no longer wraps through a float
- presets saved before v35 keep their delay time: store format 4 converts at boot, the editor converts files
  without "v": 35; factory presets converted
- engine 247 KB in the 336 KB pool (v34: 332 KB); CPU on the PC +4.5 % on average (reverb presets +100,
  delay + reverb presets up to +200)
- tests: format 3 store, v34 preset file import; docs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…ackages on localhost

tools/serve_offline.ps1: a small local server (PowerShell's HttpListener, localhost only, docs\ only) that opens
the installer in Chrome or Edge; Web MIDI works on localhost, so no internet is needed. The .bat builds the packages
when docs\firmware has none, and falls back to WSL's Python server if PowerShell cannot open the port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
- levels measured as loudness (BS.1770) on DI guitar (tools/calib_levels.py -> px_trim.h): each amp's level over
  GAIN (noon -23 LUFS for all, +-3 dB over the knob), cabinets, every block type unity at noon; the 52 v35 presets
  spanned -53..-10 LUFS, the 24 new ones sit at -23.0 +-0.2 (tools/level_presets.py)
- preset LEVEL: GT's 3rd setting "PRESET LVL" (-15..+15 dB); store format 5 and the editor set 0 dB on older presets
- soft output limiter instead of the hard clip; SLOW SWELL look-ahead (no attack before the swell)
- 24 factory presets round the fitted amps near noon, gentle gates on the hissy cleans; 25..52 USER slots
- screen: PLAY (number and name large, amp / cab, chain in colour, selected block) and EDIT (block band, model,
  5 settings with value + bar); fonts 26 / 58 px; ui_text.h shared with tests/uisim.c, which checks every page
- delay TIME shown in ms on the pedal; calib3.c replaced by calib_levels.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
- battery: the chip's VBAT/4 and LDO5V/4 PMU channels scaled by the bandgap (as the JieLi SDK does), read every
  2 s with the mux and test bus put back; top centre of PLAY (icon + %, USB), volts on the tuner page
- the output fades to silence while the tuner is open (5 ms ramp)
- VOL box: v35's 48 px cut "VOL 100" to "VOL 10" (v36: 66 px); EDITED moved next to the cab
- docs/WHATS-NEW-v34-v36.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…ISH-SOURCE.bat

GitHub's main had stopped at v29 + two README edits made on the site (the bundle could not be put on top), so the
source of v30..v36 was never public. The publish script now clones GitHub's main, merges the bundle's history in
(-X ours, never forced), makes the files exactly this folder's, commits, pushes, and sets the tag on that commit
(moved when it pointed at an old main). Release notes include docs/WHATS-NEW-*.md. PUBLISH-SOURCE.bat: source only;
PUBLISH-GITHUB.bat offers it when the build fails. README: the site edits (banner, editor link) carried over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…, taken from the folder

Files left over from older versions in the folder (docs/ui-v23.png, tests/engine/calib3.c...) are not published again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…r keeps the PowerShell server; CI read-only

build.bat uses the container (tools/op.mjs) when Node and Docker are running, else WSL as in v36.
INSTALL-OFFLINE.bat needs nothing installed again (tools/serve_offline.ps1), with tools/serve.py and WSL as fallbacks.
The workflow gets permissions: contents: read. The .bat files use CRLF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
… send, settings byte, clock to delay time

firmware/px_midi.h has no hardware and is tested on a PC (tests/midisim.c). px_dly_value_for_ms picks the delay TIME
step nearest a time.
… pedal's changes out, MIDI page, settings in the store

usb.c is unchanged. Nothing emptied its MIDI input ring, which also stalls the editor's and the installer's SysEx when it
fills; px_midi_service now drains it every pass. FX held 0.8 s opens the MIDI page (channel, send, clock to delay); the
settings are one byte in the preset store's header (0 = defaults, as every earlier save).
…t, docs and the hardware test plan

OP_CHANNEL=midi-test builds firmware/openpocket-midi-test.bin and docs/firmware/midi-test/ with their own version and
manifest; the release files and the default manifest are not touched. docs/MIDI.md (design, CC map, Bluetooth findings and
future work), docs/MIDI-TEST.md (checklist for the captain), README and SOUNDS-AND-CONTROLS.
A separate build target that links JieLi's Apache-2.0 LE controller
(third_party/jieli-ble) into the normal firmware and sends one scannable
advertisement named OpenPocket-BLE-TEST with the BLE-MIDI service UUID.
No connections, pairing or MIDI over Bluetooth. The normal firmware and
installer packages are byte-identical; the VM and BTIF areas are not touched.

Built, linked and packaged; not yet run on a pedal (docs/BLE-TEST.md lists
what is unverified).
… and refuse a link that would trip it

The first hardware run faulted at the first instruction of the first BLE task (EMU bit 3, stack limit):
the task stacks lie outside [_ustack_lo, _sstack_top]. The test image is now built with FM1_GUARD_STACK off
(OP_GUARD_STACK=0); the normal firmware is byte-identical. app.ld fails the link if the stack guard is on while the
task stacks are outside the window. docs/BLE-TEST.md lists the other guards as checked on paper only.
… full failure line

The status text now reads STARTING <stage> or FAILED <step> <code>; a timeout names the stage it stalled in. On FAILED the
whole line (step and stage in words) scrolls across the status area in the 500 ms redraw. The logic is in ble_status.h and
tested on the PC (tests/test_ble_status.c). The normal firmware is byte-identical.
… and two related fixes

The controller task deadlocked in ll_init -> thread_create -> crc_get_32bit: the lock is created by the vendor
__crc16_mutex_init(), which only the SDK's own start-up calls (FAILED 5 3 on the pedal). ble_test_start now calls it.
ll_hci_adv_set_params gets a six-byte direct address instead of NULL (the library reads six bytes), and the context
switch saves r4-r15, the registers the compiler preserves. The failure line gains two stage markers (radio replay
returned, baseband interrupts requested) and, at a timeout, the object the controller task waits on.
Normal firmware is byte-identical. Not run on a pedal.
isod89 and others added 29 commits November 7, 2026 10:20
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…ponme82)

Conflict in firmware/openpocket.c: the lights' update and v37's '2C' status line kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
Conflicts: README.md (the MIDI line, now part of v37), firmware/openpocket.c (both
includes; the test-channel version mechanism with the release number 37),
tests/run_tests.sh (MIDI tests beside the lights tests). The test channel's boot tag
is now its name. tests/midisim.c: the amp-type test starts from the first amp (v37's
factory presets already play the last one, NAM).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…ith the web installer (VinSpee)

Conflict: Makefile (.PHONY: package-test from isod89#10 and flash).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
… (VinSpee)

Conflicts: Makefile (flash and rescue targets), tests/run_tests.sh (both tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
Conflicts: Makefile, docs/BUILDING.md, tests/run_tests.sh, tests/test_op.mjs,
tools/op.mjs (the sim command beside flash and rescue), tests/edsim.c (v37's NAM slots
beside the simulator's screen). edsim.c now also includes px_midi.h for ui.h's MIDI
page (isod89#10); test_sim.mjs takes the version from edsim.c instead of v36.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…nSpee)

Conflicts: firmware/build.sh (the Bluetooth image's outputs and flags beside the test
channel's, which now sets APP/PKG; v37's call-range check kept), firmware/openpocket.c
(the stack-guard switch beside the channel version; the BLE includes after the LED
probe), Makefile, .gitignore, NOTICE.md, tests/run_tests.sh, tests/test_op.mjs,
tools/op.mjs (all the commands and outputs kept).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…dio fix, Chocolate Plus control (VinSpee)

Conflicts: the editor command DIAG takes number 23 (19-22 are v37's NAM commands:
firmware/px_editor.h, docs/pxedit.js, docs/EDITOR-PROTOCOL.md, the verify-pedal skill);
tests/edsim.c (#ble in the hosted simulator's command handler); firmware/openpocket.c,
firmware/build.sh (v37's version and the channel tag kept), README.md, Makefile,
docs/BUILDING.md, tests/run_tests.sh, tools/op.mjs, .gitignore (all kept).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…nel and sound, pedal log reader, scan helper (VinSpee)

Conflicts: the log channel as isod89#16 made it (5 lines, DIAG = 23, link and trace);
Makefile, docs, tests/run_tests.sh, tools/op.mjs (everything kept: flash --rescue,
wasm, log, rescue, sim). The simulator's WebAssembly engine is rebuilt from v37's
engine (it now plays the NAM amps); engine/wasm.sh and tests/test_audio.mjs count the
NAM sources (px_dsp.c includes them) in the engine's checksum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…t accepts either direction

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
The BlackBox's button lights hang on a 74HC595 on port C (DS PC8, shift
clock PC5, latch PC6), as the stock firmware drives it. PC7 is the
lights' enable, low = on. OpenPocket held PC7 high as a screen line, so
the lights stayed dark whatever the 595 held.

- px_leds.h: the 595 driver. leds_init loads the all-off byte with the
  lights disabled, then drives PC7 low, in the stock order.
- px_lights.h: what they show, once per main-loop pass. AMP, MOD, DLY,
  REV and CAB light while their block is on, FX while any block of its
  group is on (ui.h UI_FXB, shared with the FX button), HOME blinks in
  the tuner. The byte is written only when it changes. Off during an
  update (px_usb.h PX_LIGHTS_OFF).
- led_probe.h (LED_PROBE 1): the LED map build that read the bit order
  on a BlackBox: 0 HOME, 1 FX, 2 AMP, 3 MOD, 4 DLY, 5 REV, 6 CAB.
- tests/lpsim.c and tests/lightsim.c: a 595 model with its enable on
  PC7; both run from tests/run_tests.sh.

Confirmed on a BlackBox. Not tried on a Pocket X yet: the same firmware
runs there and the stock routine is the same, but its bit order may
differ.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bluetooth MIDI (docs/BLUETOOTH.md)
- Built into every firmware (OP_NOBLE=1 leaves it out); off by default, switched on from the MIDI page's new
  BLUETOOTH row (settings byte bit 7). The page shows the radio's state; the status corner shows BT while a device
  is connected. make ble stays the test image (radio always on, state scrolling).
- Fits the RAM next to the NAM network: the controller's task stacks (14.7 KB) and the tuner's buffers move to the
  data cache used as RAM (app.ld CRAM, .cram*); the heap (radio buffers, DMA) stays in RAM. The NAM vector unit gets
  what is left of the cache RAM. The bench rings of px_nam.c move to the stack. build.sh prints both figures.
- The stack guard stays on: the window's top is raised to the task stacks before they run (ble_guard_window);
  app.ld refuses task stacks anywhere but CRAM while the guard is on.
- A reset while the radio starts leaves it off until a power cycle (.noinit), so it can never loop the pedal.
- The log channel (DIAG) answers in every firmware with Bluetooth.

Battery
- v36 never switched on P3_ANA_CON4 bit 5 (VBG_BUF_EN), which the SDK's adc_pmu_detect_en always sets: no reading,
  so the icon stayed hidden. Now set as the SDK does, with the analog bias and PLL test-output bits as __adc_init
  sets them, and the bandgap's factory trim from the boot loader's parameter block (get_vbg_trim).
- No reading: an empty cell and "--" instead of nothing. The tuner page shows the raw readings for a photo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…h), retry from the MIDI page

- The first release build crashed while the radio started (PC 00100086 after fm1_rfc_vga_gain returned: a return
  address read back wrong). The start-up replay's crystal ramp keeps its writes on the caller's stack, which was the
  controller task's stack in the cache RAM, served by the cache controller like the flash. fm1_ble_rf_run now runs on
  the main stack below the waiting main loop (ble_call_on, ble_ctx.S; ble_on_main_stack, ble_os.c).
- After a crash or a failed boot the radio waits for the user: turning the BLUETOOTH row off and on again and leaving
  the page tries again (no power cycle needed). The page says so.
- BLUETOOTH.md: phones never list BLE MIDI devices in their Bluetooth settings; nRF Connect / LightBlue to check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…res ran

- With the radio running, nearly every preset went to BYP at 150%+ (before the radio started the same build ran at
  70%, peak 83%). The main loop resumed the scheduler on every pass, spinning through its tables in the cache RAM and
  the library's code in flash, both through the cache controller the DSP also needs. ble_service now polls once a
  millisecond (the controller's own clock is 10 ms).
- The status corner in bypass reads "BYP 2C 150%" / "BYP C0 150%": whether the second core was still helping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…ith Bluetooth on)

With Bluetooth on, nearly every preset bypasses at 150%+ (off: the usual load). The page now shows the baseband's
event and receive interrupts and the library's timers a second, so a photo tells an interrupt storm from a calm radio.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
Bluetooth in the release firmware (f02c1ff..a3546a6) is taken back out: with the radio running, nearly every preset
went to bypass at 150%+. The stock firmware runs all its audio on the second core (ALNK0 IRQ 11 at priority 4 on
CPU1) and the radio on the first (IRQ 45/29, priority 2); ours shares CPU0 between the audio interrupt and the
radio. To be done again with that split in mind. The test image (make ble) is as it was before.

Kept: the battery reading (VBG_BUF_EN, bias and test-bus bits as the SDK, the bandgap trim), the empty-cell "--"
icon, the raw readings on the tuner page, and "BYP 2C/C0 nn%" in the status corner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
The photos showed VB 258 (3.31 V) on a full cell and on USB alike: the chip's VBAT is a regulated rail on the Pocket X.
PocketX_016 (f_02026f60, f_02025682 = adc_get_value) samples ADC channel 8 (PC9) and maps raw 528..636 to 0..100 %
((raw * 100 - 52700) / 109); BlackBox has the same code. PC9 is set as an analog input, bat_read averages 8 readings
of channel 8 and smooths them; the tuner shows C9 BG VB 5V and the 0x16A10 bit 16 stock reads beside the gauge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…ead with the ADC only

- dsp_out: above 0.8 of full scale a soft knee (same slope at the knee, towards full scale) instead of the hard clip.
- dsp_load counts blocks over 100% (a_xrun, the audible crackle); the status turns red with "!" for 2 s after one.
  The tuner page's last line: C9 (battery raw), CH, LATE, CLIP (blocks at the knee), PK (highest block load).
- bat_read: PC9 only; the PMU test-bus, bias and PLL-test switching every 2 s and the bandgap trim are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
…issue isod89#8)

The lights were merged before (8e1c591, from the PR's first version); this merges the PR as it is now on GitHub,
the same change rebased onto v37, so that GitHub shows it merged. No file changes: main already has it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
- The clone is made with core.autocrlf=false: with Git for Windows' default the checkout had CRLF, the config set
  afterwards made every file "modified", and the history merge was refused (only the files were published, so the
  merged pull requests did not show as merged).
- Moving the tag vNN turns its release into a draft, which "--latest" refuses: the release is published again
  (--draft=false) with the notes, and gh's error is shown instead of hidden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFaxqW3CpFrLCvqoteTUv9
An octaver's poly mode with a crossover: the notes below RANGE get an
octave down, the high strings and the chords on top stay as they are.
POLY OCTAVE shifts the whole guitar, so the high strings get it too.

- RANGE: an 8th-order Butterworth low-pass picks the notes that get the
  octave, from 80 Hz (D#2) to 400 Hz (G4). The screen shows the note.
- OCT -1: the octave's level (50 = as loud as the note).
- DRY: the guitar itself, untouched at 100 (the same samples as with the
  block off).

The low band runs at sr / 8 (5.5 kHz) behind a 4th-order anti-alias
filter: the steep filter, the shifter (POLY OCTAVE's -1 with a phase
alignment search sized for that rate) and its buffer (the pitch block's
ps_buf, so no new RAM) cost little next to the NAM amp. A linear
interpolation and a one-pole filter take it back to 44.1 kHz.

tests/engine/lowoct.c: dry untouched sample for sample; low E gets a
clean octave (99% of the output), high E none (-30 dB); low E + G: only
the E's octave; RANGE at the top reaches the high E; no images from the
upsampling (-66 dB); the octave within 60 ms (15 ms); the RANGE texts.
It catches 8 of 8 deliberate mistakes in the code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the OC-3 and OC-5 POLY modes and how players rebuild them on iOS
(a range filter in front of the octave, a high-pass, a drive after it):

- Chords: with two notes below RANGE (an E5 power chord), a phase
  alignment over one period of the low E could suit only one of them:
  30% of the output was on the two octaves, the rest warble. Two notes a
  fifth apart repeat together every 2 periods of the lower one, a fourth
  every 3: searched over 38 ms and compared over 20 ms (window 46 ms),
  E5 99%, A5 98%, E + A 96%; single notes still 98%.
- GROWL (4th knob): the octave alone through a soft clipper, level
  matched and smoothed above 2 kHz, mixed in with the knob, as the
  OC-3's DRIVE mode: the sub carries without subwoofers. At 0 the octave
  is unchanged, sample for sample.
- A 2nd-order high-pass at 50 Hz in front of the shifter keeps rumble
  and thumps out of the octave (-10 dB at 30 Hz). A 4th order gives
  -18 dB but delays the octave 8 ms instead of 3.

The octave now comes in 23 ms after the note (was 15): 5 ms for the
longer window, 3 ms for the high-pass. The dry signal has no delay.
tests/engine/lowoct.c checks the chords, the rumble and GROWL too, and
catches 12 of 12 deliberate mistakes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Built in the pinned container by CI (node tools/op.mjs wasm, run
42459552246 on the fork); its sources hash is this branch's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The octave was late: measured on plucked notes, its attack came 36 ms
(v1) and 64 ms (v2) after the note's. A delay-line shifter plays the
octave from a buffer, always half a window plus the splice alignment
behind. Replaced by phase halving: two allpass chains 90 degrees apart
make the low band a turning arrow, and the octave is an arrow turning
half as fast (no buffer), given the band's level. Its attack now comes
13.9 ms after the note's: the filters that pick the note, nothing else.

- Two notes below RANGE: phase halving follows one note, so a tilt (one-
  pole low-passes at 60 Hz, -6 dB an octave each) lets the lower one
  lead the phase: E5, A5 and E + A give the lower note's octave, 96-99%
  clean, as a bass player plays the root. The level comes from the band
  before the tilt (a pluck's tilted start takes 20 ms to settle).
- RANGE 0 = LOWEST, as the OC-5: the band open to 600 Hz and a stronger
  tilt (6 poles): the octave of a chord's lowest note anywhere on the
  neck, even a softer root a fourth below (97%).
- COMP (5th knob), as a compressor in front of an octaver: the octave's
  level to the power 1 - 0.9 COMP, towards -20 dB; at 100 a note 20 dB
  softer gets an octave 2 dB softer. An expander below -54 dB keeps the
  hum between notes where it is.
- The range filter is a 6th-order Butterworth now (an 8th: +1.5 ms).

tests/engine/lowoct.c checks the attack time on plucked notes, LOWEST,
COMP and the hum too, and catches 15 of 15 deliberate mistakes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@steponme82 steponme82 closed this Nov 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants