Skip to content

feat: add shared app helper modules and libfp clock/brightness helpers - #641

Open
kosmar wants to merge 6 commits into
ATOVproject:mainfrom
kosmar:feat/shared-app-modules
Open

kosmar wants to merge 6 commits into
ATOVproject:mainfrom
kosmar:feat/shared-app-modules

Conversation

@kosmar

@kosmar kosmar commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds small shared helpers that the upcoming WIP apps all need, so each app PR
can shrink to its own app file plus a registry line.

Under faderpunk/src/apps/, for the genre-based apps (Grooves, Chord Vamp,
Bassment):

  • genre_palette.rs — shared genre names and 8-bar tropes
  • groove.rs — swing/feel curves and swing delay maths
  • led_fx.rs — spectrum colour helpers for the genre scrub axis
  • follow_key.rs — shared helper for following the device tonality

In libfp::utils, for the clocked and LFO-style apps (Super LFO, Manifold,
Venn, Contura, Hold Sam):

  • CLOCK_DIVISIONS and division_at — one division table, and an even mapping
    from a fader to a prefix of it, replacing the per-app tables and the ad-hoc
    value / 500 style lookups
  • lfo_step / lfo_step_modulated — the exponential rate curve, with and
    without CV modulation
  • quant_step — phase increment for a clock-synced cycle
  • signal_brightness — the LED brightness curve for a unipolar or bipolar
    signal

Each of those apps currently carries its own copy on its feature branch, and
the copies have already diverged — genre_palette gained GENRE_PROG_8 on
some branches while others still ship the now-unused GENRE_COLORS table.
Landing the helpers once, ahead of the apps, gives them a single source of
truth. The contents are taken from the integration branch these apps are
flashed from, which is ahead of every feature branch for groove.rs.

Centralising signal_brightness also fixed a bug the copies shared: at full
scale a bipolar value produced (4095 - 2047) / 8 = 256, which wrapped to 0
in u8 and dimmed the LED instead of lighting it. The shared version clamps
before the cast, and the unit tests cover it.

Scope

Additive. New app-side modules plus their mod declarations, and new functions
plus unit tests in libfp::utils. register_apps! is untouched and no existing
app or platform file is modified, so stock app behaviour cannot be affected.

The app-side modules are dead code until the first consumer app lands, hence
the #[allow(dead_code)] on the declarations; the first app PR to use them can
drop it.

Test plan

No hardware checklist: this change adds no reachable code path and no runtime
behaviour. cargo build --release and cargo test --lib -p libfp against
current main are the whole verification.

Made with Cursor

Grooves, Chord Vamp, and Bassment each carried their own copy of these
helpers on their feature branches, and the copies had already started to
diverge: genre_palette gained GENRE_PROG_8 on some branches while others
still shipped the unused GENRE_COLORS table. Landing them once, ahead of
the apps, gives the three a single source of truth and lets each app PR
shrink to its own app file.

The files are taken from the flashed playground state, which is ahead of
every feature branch for groove.rs.

Purely additive: three new modules plus their declarations. register_apps!
is untouched and no existing app or platform file changes, so stock app
behaviour cannot be affected. The modules are dead code until the first
consumer app lands, hence the allow(dead_code) on the declarations.

Authored by an AI coding agent on behalf of kosmar.
kosmar and others added 3 commits August 12, 2026 18:06
Same reasoning as the genre helpers next to it: Bassment, Contura, Chord Vamp,
Venn and Arp de Lévy all resolve the device Key/Tonic, and landing it once ahead
of the app PRs keeps the copies from diverging.

Note-generating apps each carried their own idea of Root and Scale, so the
device-wide Key/Tonic — live on the Scene button plus Fader 4/5 — could not act
as a transpose. This gives one place to resolve it: the normalized device Key,
the tonic pitch class, a root retuned onto it keeping its octave, and a combined
call that costs a single GlobalConfig copy instead of two.

Authored by an AI coding agent on behalf of kosmar.

Co-authored-by: Cursor <cursoragent@cursor.com>
clippy::chunks_exact_to_as_chunks fires on the constant chunk size in the USB RX
loop, and CI runs clippy with -D warnings, so main does not pass its own gate on
a current toolchain. Feature branches have been carrying this same one-line fix
inside unrelated app commits to stay green; landing it here means they no longer
have to.

as_chunks::<4>() yields &[[u8; 4]], so the packet indexing below is unchanged.
The discarded remainder matches the previous behaviour: chunks_exact dropped a
trailing partial packet too, and a USB MIDI bulk transfer is always a multiple
of four bytes.

Authored by an AI coding agent on behalf of kosmar.

Co-authored-by: Cursor <cursoragent@cursor.com>
Fourteen apps carry their own copy of the 24 PPQN division table and five
repeat the free-running LFO speed curve, so the magic numbers and the
bounds checks drift independently. Centralise them as CLOCK_DIVISIONS,
division_at, lfo_step, lfo_step_modulated, quant_step and
signal_brightness with unit tests.

The tests uncovered a wrap in the brightness curve that every caller
shared: a bipolar 4095 divides to 256 and cast to u8 as 0, dimming the
button at the very peak of the wave. It clamps now.

Authored by an AI coding agent on behalf of kosmar.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kosmar kosmar changed the title feat: add shared genre, groove, and LED helper modules feat: add shared app helper modules and libfp clock/brightness helpers Aug 13, 2026
kosmar and others added 2 commits August 14, 2026 17:47
Apps that add their own swing on top of the device clock had no way to ask how
much the clock has already moved a step, so they could only switch their own
timing off whenever global swing was non-zero. device_swing_permille reports
that displacement as a fraction of the app's grid step, and returns zero for
grids coarser than the swing window half, where the clock never moves the step
at all. device_swing_reverses tells an app which parity to delay so its feel
leans with the clock rather than against it, with a threshold so a global swing
of a few percent stays straight instead of flipping parity for a displacement
that is barely audible.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kosmar

kosmar commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

Written by an AI coding agent on @kosmar's behalf.

48febdc4 adds device_swing_permille and device_swing_reverses on groove.rs.

Apps that layer their own swing on the device clock had no way to ask how much the clock had already moved a step, so they could only switch their own timing off whenever global swing was non-zero. The new helpers report that displacement as a fraction of the app’s grid step (zero when the grid is coarser than the swing window half) and which parity to delay so Feel leans with the clock. Consumed by #633 and #648.

This branch has not been deployed

No deployments
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.

1 participant