Uh oh!
There was an error while loading. Please reload this page.
Loop Jefe - #192
Draft
sastraxi wants to merge 32 commits into
Draft
Conversation
mod-host/mod-ui now broadcast an absolute-timestamped downbeat over WebSocket (beat_sync); pi-Stomp parses it, tracks a BeatGrid anchored to CLOCK_MONOTONIC, and flashes the tap-tempo footswitch's LED/pixel on each beat as a visual metronome (no audible click is available on v3's single shared DAC). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… group
Extends the 'longpress' config field to accept {midi_CC: N} alongside the
existing named-group form (string or list), so a footswitch's longpress can
MIDI-learn directly onto a plugin port (e.g. loopjefe's future "reset")
without inventing a new pi-Stomp-internal callback for every such action.
Kept as one field rather than a sibling 'longpress_midi_CC' key so a
per-pedalboard overlay can't leave a stale chord-group registration behind
when a later config only overrides one of the two.
Consolidates the near-duplicate _emit_midi (mod.py/modhandler.py) into a
single implementation on the Handler base, since both were already routing
through self.hardware — no new shared state needed, just an optional CC
override so longpress can target a different port than the short-press
binding. Also adds 'toggle_tuner_enable' to the longpress schema enum: it
was already a registered callback and used in both shipped templates, but
the old loose ["array","string"] schema silently skipped enum validation
for the bare-string form.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts: # modalapi/websocket_bridge.py
Brings the branch up to v3.3.0 (AGPL relicense, footswitch UI, v1 removal). Longpress-sends-MIDI was implemented independently on both sides; main's is the superset (LongpressActionConfig -> RawMidiCcEffect, plus preset/pedalboard targets), so the branch's narrower Footswitch.set_longpress/longpress_midi_CC layer and its tests are dropped in favor of it. Likewise Handler. _handle_footswitch, superseded by main's Effects/BindingDecl dispatch. Kept from the branch: the per-tick LED driver (set_led is a pure state update, _drive_footswitch_leds renders), LedSpec/render_led_spec, generic output_set mirroring into Plugin.output_values, BeatGrid, and Parameter Type.TRIGGER / is_momentary. v1 (modalapi/mod.py) is gone with main's "Remove v1", taking the branch's v1 metronome work with it. Momentary trigger semantics are NOT yet wired into main's Effect model -- MidiCcEffect and RawMidiCcEffect both still toggle, so a pprops:trigger port (loopjefe advance/reset) only fires every other press. Follow-up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts: # modalapi/modhandler.py # pistomp/handler.py # pistomp/hardware.py # tests/test_hardware.py # tests/v3/test_hardware_config.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Live looping on pi-Stomp. The device now shows a multitrack looper UI for loopjefe instances per footswitch binding. Each has a progress border that visualizes loop progression and a beat pulse that comes from the transport clock.
No looper-specific code is in the handler, the LCD, or the input path.
pprops:triggerport sends one rising edge, and does not toggle.LedSpec. The generic driver reads the plugin output ports and makes the color, the style, and the loop position.output_setframes that a loaded plugin asked for, and drops the rest.Before you start
Two things are external to this repository:
beat_syncmessage comes from our mod-ui fork. Stock mod-ui does not send it. Without it, the device falls back to the tap tempo (a free grid).A route through the code
Let's start at
BeatSyncMessage, which allows us to synchronize our visuals with the audio "beat".OutputSetMessageis required for us to observe changes from the loopjefe plugins (and other plugins, if we care to listen).BeatGridholds one anchor and extrapolates from it:Each new sample fully replaces the anchor. A late sample or a lost sample means more extrapolation, so in practice de-syncs should be rare. The downbeat is computed locally.
tick()produces aTickState. When there is no anchor, or when the anchor is more than 5 seconds old,tick()falls through to_free_tick()and uses the tap tempo. A free grid has beats but no bar, so it has no downbeat and no loop position.The one subtle part is
_ANCHOR_CROSSING_EPSILON_BEATSinon_anchor(). If we recieve. The code seeds the beat index one behind, or the firsttick()never sees the crossing.LedSpecis the the plugin-facing contract for how it wants to drive its LED if it's bound to a footswitch:state_symbol— the output port whose integer value selects a color.downbeat_symbol— an output port that is 0 on the loop's own downbeat.bars_symbol— the loop length in bars. It is the denominator for the position. 0 means "not known yet".chase_states— states with a position but no denominator, such as a first take that is still recording.labels— the port is anlv2:OutputPort, so its scalePoints never reach us as aParameter. The names must be declared here.The renderer (
modalapi/led_render.py)Pure function of
(LedSpec, output_values) -> LoopProgress. The fourLoopFillmodes are the model of the feature:FILLCHASESTATICFREEHandler changes
:1072—BeatSyncMessageanchors the grid.OutputSetMessageat:1075writes toplugin.output_values.:666_drive_footswitch_leds()— called each 10 ms tick frompoll_controls. It ticks the grid one time and applies the result to every footswitch, so all LEDs pulse together.:718_render_footswitch()and:761_write_led()— where the color reaches the hardware.pistomp/footswitch.py:set_ledis now pure state, and it makes no hardware write. This side-steps a conflict between the LED driver and the pixel driver.:1415_update_interesting_outputs()— makes the subscription set fromplugin.monitored_output_symbolsand gives it to the bridge.:538and:566— the momentary path. If the boundParameteris a trigger port, the press callsParameter.pulse()(common/parameter.py) instead of a toggle.Websocket filter
Before this branch,
output_setwas dropped whole, because the audio meters flood it. Now the worker keeps the frames whoseinstance/symbolkey is in_interesting.Note that
_latest_outputsholds the last unsubscribed value per port. mod-ui dumps every monitored port at connect time, before the board binds. Without the replay, the first paint is stale until the plugin next moves.Painting
uilib/glyphs/perimeter_progress.py— draws an arc on the perimeter of a rounded rectangle.segmentsputs bar ticks around it.uilib/glyphs/loop_icon.py— the racetrack icon that replaces the plugin name whenloop_icon=True.Unrelated fixes
common/parameter.py). MOD-UI gives short property names in some paths and full LV2 URIs in others._has_property()accepts both. Before this, a plugin that reported full URIs got no type at all.ContextLayer.remove()(common/contexts.py) — the mutation counterpart toadd(), so callers do not touchrowsdirectly.TODO