Skip to content

Commit b328759

Browse files
Tokclaude
andcommitted
fix(demo+ui): Bach scenario cleanup + sequencer card height + wrapped locks
Bach Italian Concerto scenario (demo/scenarios/bach-italian-3rd.sh): - Shift drums 15 s earlier (fill_wait 45→30) and use `sweep_pad` for audible cutoff/resonance motion during the intro — same helper the acid demo uses. - Bring back the BassPan LFO (pan-only is clean; cutoff/resonance LFOs clicked). - Lock the missing `sequencer.bass{2,3,4}_steps` paths — the planner was running bass1/bass2 lanes on the KIT agent and the step arrays were the one slot not explicitly blocked. - Stage 3 "full stop" another 6 s earlier (21 s of MIDI-end headroom). - Punchier FX targets at each stage — bitcrush wasn't audible at the previous subtle values. Sequencer card height (src/ui/rack_grid.rs): - `sequencer_grid_rows` now mirrors what the panel actually renders: gates modules on reaches_master (not just enabled), budgets the collapsed drum-chip strip, and accounts for the separator row that precedes drum voices. Fixes the "sequencer content hidden behind empty cell backgrounds" problem where a patched-but-unwritten drum kit pushed its collapsed chips past the card boundary. Song timeline + bass lock list UI (src/ui/panels/sequencer_chain.rs, src/ui/panels/bass_locks.rs): - Song-timeline bars fill the available panel width dynamically instead of a hard-coded 12-bar window; painter switched to the parent layer so 1.5 px cursor strokes don't get half-clipped. - Bass LOCKED strip wraps inside the module width with `horizontal_wrapped`, so a long scenario lock list no longer distorts the bass panel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ea3c621 commit b328759

4 files changed

Lines changed: 272 additions & 163 deletions

File tree

demo/scenarios/bach-italian-3rd.sh

Lines changed: 152 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,28 @@
66
# Deliberately simple: score playback + filter sweeps + FX, not a jam.
77
#
88
# - Bass score + sequencer stay LOCKED; nobody writes to them.
9-
# - Pan is handled by a native LFO (state.lfo[0] targeting BassPan),
10-
# configured once via the API — no scripted Python loop, no agent
11-
# touching pan, no HTTP hammering during playback.
12-
# - One PAD agent drifts the 303 filter (cutoff + resonance) across
13-
# the movement. That's its ONLY remit.
14-
# - 808 arrives mid-piece; a KIT agent writes a half-time pulse
15-
# (kicks on 0/8, closed hat on 4/12 of a 16-step loop). Agent-
16-
# driven so the drums read as a live decision, not a pre-baked
17-
# pattern; scope=kit_a keeps it away from the bass sequencer.
18-
# - One FX agent escalates reverb + bitcrush into the outro.
9+
# - Bass voices start on ENGINE DEFAULTS (known-good sound from
10+
# `./start.sh` → import MIDI). During the 30 s intro, three
11+
# direct api_params nudges move cutoff / resonance a little so
12+
# there's audible filter motion before the drums arrive — no
13+
# LFOs (every LFO variant on the bass filter clicked on note
14+
# retriggers).
15+
# - 808 arrives at 30 s; a KIT agent writes a 32-step half-time
16+
# pulse and is re-asked twice during the outro to evolve the
17+
# groove. Scope=kit_a keeps it away from the bass sequencer.
18+
# - FX outro is SCRIPTED, not agent-driven: three api_params nudges
19+
# ramp reverb + bitcrush in three stages. Gemma's FX lane kept
20+
# emitting only reverb / ring_mod and never bitcrush keys, so
21+
# the bitcrush stayed at 0 in every prior take — scripted keeps
22+
# it predictable. The whole bus passes through FX, so drums get
23+
# bitcrushed too.
24+
# - All 4 LFO slots are locked so no agent's mod lane can sneak a
25+
# modulation onto bass params; every non-reverb / non-bitcrush
26+
# FX channel is locked off so nothing else audible creeps in.
1927
# - Camera tracks each addition: focus_on the new module, linger a
2028
# few seconds, then return to the score.
2129

22-
scene_count 6
30+
scene_count 5
2331

2432
BACH_MIDI="${BACH_MIDI:-demo/scenarios/bach-italian-3rd.mid}"
2533

@@ -45,13 +53,13 @@ set_style ""
4553
# Bach-specific Huth-per-component pinning: everything grayscale
4654
# except the sequencer step dots + event-stream history, which are
4755
# Huth-coloured unconditionally. `show_thinking_in_log` is on so
48-
# the PAD / KIT / FX agents' reasoning traces are visible.
56+
# the KIT / FX agents' reasoning traces are visible.
4957
set_ui_prefs '{"huth_piano": false, "huth_bar_osc": false, "huth_ring_osc": false, "huth_spectrum": false, "show_thinking_in_log": true, "enable_thinking": true}'
5058

51-
# Master volume tuned down from the default 0.85 — the resonance
52-
# sweeps + bitcrush outro push peak loudness into clip territory at
53-
# the default gain. 0.60 leaves headroom for the FX escalation.
54-
api_params '{"fx":{"master_volume":0.60}}'
59+
# Master volume stays at its default — the 0.60 override we had here
60+
# was adding clicky transients on the bass with no audible gain in
61+
# headroom (the FX agent ramps its own wet/depth, which bounds the
62+
# outro loudness itself).
5563

5664
add_instrument bass
5765
look_at ai
@@ -68,145 +76,191 @@ PLAY_DURATION="$MIDI_DURATION"
6876
PLAY_DURATION_INT=${PLAY_DURATION%.*}
6977
echo " [scenario] will play for ${PLAY_DURATION_INT}s at ${MIDI_BPM} BPM"
7078

71-
# Starting timbre. Voice 0 (right hand) brighter and L-panned; voice
72-
# 1 (left hand) darker and R-panned. These are the static defaults —
73-
# the LFO below modulates the global bass pan on top of voice 0's
74-
# starting position (DSP applies one pan to the summed bass bus, so
75-
# the LFO moves both voices together in the current engine; real
76-
# anti-phase per-voice pan is a planned DSP refactor — see PLAN.md).
77-
api_params '{
78-
"bass_voices": [
79-
{ "enabled": true, "volume": 1.0, "cutoff": 0.55, "resonance": 0.35, "env_mod": 0.45, "decay": 0.40, "accent_level": 0.5, "distortion": 0.10, "pan": -0.25 },
80-
{ "enabled": true, "volume": 1.0, "cutoff": 0.32, "resonance": 0.55, "env_mod": 0.35, "decay": 0.65, "accent_level": 0.4, "distortion": 0.05, "pan": 0.25 }
81-
]
82-
}'
83-
84-
# Native pan LFO: state.lfo[0] targets BassPan with a slow sine. The
85-
# audio thread runs it every block — zero HTTP traffic during
86-
# playback. Rate 0.03 ≈ ~0.2 Hz (5-second cycle at the LFO's
87-
# internal scaling); depth 0.7 so pan swings ~±0.7 around the
88-
# starting position.
79+
# Starting timbre: DEFAULTS on the bass filter / env. Earlier takes
80+
# that tweaked env_mod / decay or enabled BassCutoff / BassResonance
81+
# LFOs came back clicky. `./start.sh` → import MIDI → default 303
82+
# is the known-good sound; this scenario reproduces it. Cutoff and
83+
# resonance move during Scene 3 via `sweep_pad` (scripted api_params,
84+
# no LFO — the helper has been proven clean by the intro demo).
85+
86+
# Pan LFO only: slow stereo drift on the bass bus. Pan-only LFOs
87+
# don't retrigger with the filter envelope and have never caused the
88+
# clicks we saw with BassCutoff / BassResonance LFOs. Locked below
89+
# so nothing rewires it.
8990
api_params '{
9091
"lfo": [
91-
{ "enabled": true, "target": "BassPan", "waveform": "Sine", "rate": 0.28, "depth": 0.7, "phase_offset": 0.0 }
92+
{ "enabled": true, "target": "BassPan", "waveform": "Sine", "rate": 0.28, "depth": 0.70, "phase_offset": 0.00 }
9293
]
9394
}'
9495

95-
# Lock the score + the entire sequencer so nothing (agent or jam
96-
# loop) can overwrite the imported notes. The PAD agent is scoped
97-
# to "bass" synth params only; these locks are belt-and-braces.
96+
# Lock the imported score and every FX bus channel except the two we
97+
# want audible (reverb + bitcrush). The fx state is a flat struct —
98+
# delay / chorus / phaser / ring_mod / compressor / distortion /
99+
# tape / waveshaper / eq / autotune all run in the DSP regardless of
100+
# which modules are in the rack. Last run the FX agent kept writing
101+
# ring_mod_mix despite the prompt saying reverb+bitcrush only, and
102+
# since no ring-mod module was visible, it sounded like "invisible
103+
# FX". Locking their mix/gain paths here means the agent can't turn
104+
# any of them on, so what's audible matches what's in the rack.
105+
# All four lfo slots are locked too — the planner keeps picking the
106+
# mod lane for any scoped agent, and an unlocked slot lets an LFO
107+
# onto something like `bass_voices.1.env_mod` that then reads as
108+
# "invisible LFO making the bass bleeP".
98109
lock \
99110
"sequencer.bass_pattern" \
100111
"sequencer.bass_patterns" \
101112
"sequencer.bass_steps" \
113+
"sequencer.bass2_steps" \
114+
"sequencer.bass3_steps" \
115+
"sequencer.bass4_steps" \
102116
"sequencer.bass_voice_steps" \
117+
"sequencer.bass_notes" \
118+
"sequencer.bass_accents" \
119+
"sequencer.bass_slides" \
120+
"sequencer.bass_pans" \
121+
"sequencer.bass2_notes" \
122+
"sequencer.bass2_accents" \
123+
"sequencer.bass2_slides" \
124+
"sequencer.bass2_pans" \
125+
"sequencer.bass3_notes" \
126+
"sequencer.bass3_accents" \
127+
"sequencer.bass3_slides" \
128+
"sequencer.bass3_pans" \
129+
"sequencer.bass4_notes" \
130+
"sequencer.bass4_accents" \
131+
"sequencer.bass4_slides" \
132+
"sequencer.bass4_pans" \
103133
"sequencer.bpm" \
104134
"sequencer.steps" \
105-
"bass_voices.0.note" \
106-
"bass_voices.1.note" \
107-
"bass_voices.0.pan" \
108-
"bass_voices.1.pan" \
109-
"bass.pan" \
110-
"lfo[0]"
135+
"lfo[0]" "lfo[1]" "lfo[2]" "lfo[3]" \
136+
"fx.delay_mix" "fx.chorus_mix" "fx.phaser_mix" "fx.ring_mod_mix" \
137+
"fx.waveshaper_mix" "fx.distortion_mix" "fx.compressor_mix" \
138+
"fx.tape_mix" \
139+
"fx.eq_low_gain" "fx.eq_mid_gain" "fx.eq_hi_gain"
111140

112141
show_then_return bass 4
113142

114-
# ── Scene 2: Play + PAD agent ───────────────────────────────────────────────
143+
# ── Scene 2: Play ──────────────────────────────────────────────────────────
144+
# No PAD agent. The three LFOs configured in Setup sweep pan /
145+
# cutoff / resonance natively — that's the filter performance.
146+
# AI narrative for this scenario lives on the KIT + FX agents added
147+
# below.
115148

116149
scene "Play"
117150

118151
look_at sequencer
119152
play
120153

121-
add_agent PAD gemma bass
122-
wait_for_model
123-
124-
# PAD's ONLY remit: squelch the filter (cutoff + resonance). No pan
125-
# (LFO owns it), no notes, no patterns, no anything else. Bar-based
126-
# ramps so the motion develops across pattern cycles, not bumps.
127-
ask "PAD: squelch the 303 filter across the Bach outro. Move ONLY cutoff and resonance — everything else is locked or off-limits. Cutoff in [0.05, 0.55], resonance in [0.70, 1.0]. Keep voice 0 and voice 1 within 0.12 of each other so the two hands read as one squelching instrument.
128-
129-
Use \`ramps\` with \`bars\`: 4 or 8. Never step-jump. Example per turn:
130-
131-
{\"ramps\":[
132-
{\"param\":\"bass.cutoff\",\"to\":0.12,\"bars\":8},
133-
{\"param\":\"bass.resonance\",\"to\":0.95,\"bars\":8},
134-
{\"param\":\"bass_voices.1.cutoff\",\"to\":0.14,\"bars\":8},
135-
{\"param\":\"bass_voices.1.resonance\",\"to\":0.92,\"bars\":8}
136-
]}
137-
138-
Each next cycle, re-target to new corners of the cutoff/resonance pad so the motion is a continuous drift. DO NOT touch: notes, steps, patterns, gate, accent, slide, volume, pan, distortion, env_mod, decay. DO NOT add ramps for any of those. Cutoff + resonance on both voices — that is your entire scope." PAD 0
139-
140154
# ── Scene 3: Drums arrive (+60s) ────────────────────────────────────────────
141-
# Deterministic half-time kick + hat pattern via direct api_params.
142-
# No KIT agent — the pattern is fixed, the scene is just showing the
143-
# 808 layering in. chain_advance_preserve_non_bass carries the
144-
# drums across bank swaps so they play for the rest of the piece.
155+
# KIT writes a 32-step half-time pulse under the 240 BPM bass, and
156+
# gets re-asked twice during the outro so the groove evolves rather
157+
# than loops unchanged. Scope=kit_a keeps it away from the bass
158+
# sequencer. chain_advance_preserve_non_bass carries the drums
159+
# across bank swaps so they play for the rest of the piece.
145160

146161
scene "Drums arrive"
147162

148-
# 60 s of Bach + PAD-driven filter drift before the 808 layers in.
149-
# Scroll around instead of staring at one place: filter pad → agent
150-
# console → back-panel cable tour → front → sequencer, then the 808
151-
# arrival reads as a deliberate reveal after the camera tour.
152-
fill_wait 60
163+
# 30 s of Bach before the 808 layers in. Smoothly sweep the filter
164+
# pad via the shared `sweep_pad` helper — same motion the intro /
165+
# default acid demo uses, lerped keyframes at ~7.5 fps so cutoff and
166+
# resonance move together in a visible arc on the XY pad. No LFO
167+
# (LFOs on BassCutoff kept clicking on note retriggers); this is
168+
# pure scripted api_params inside the helper, which we've proven
169+
# doesn't click. `sweep_pad` writes to voice 0 only (matches the
170+
# acid demo) — voice 1 stays at defaults so the left-hand line keeps
171+
# a stable timbre under the sweeping right hand.
172+
look_at bass
173+
wait_seconds 4
174+
sweep_pad 22
175+
look_at sequencer
176+
wait_seconds 4
153177

154178
add_instrument 808
155179
show_then_return 808 5
156180

157-
# Bass 2× drums — Bach stays centre-stage. Tuned well below the
158-
# intro mix because the filter sweep can get loud on resonance peaks.
159-
api_params '{"kit_a": {"kick": {"volume": 0.45}, "hihat_closed": {"volume": 0.30}}}'
181+
# Drums below Bach + stretch kit_a voices to 32 steps so the kick /
182+
# hat / snare rows span two bars of the 64-step bass pattern instead
183+
# of looping inside a 16-step window (which read as "only half a
184+
# bar" under the moving score).
185+
api_params '{
186+
"kit_a": {"kick": {"volume": 0.45}, "hihat_closed": {"volume": 0.30}},
187+
"sequencer": {"drum_lengths": {"kick_a": 32, "snare_a": 32, "hihat_a": 32, "hihat_a_open": 32}}
188+
}'
160189

161190
add_agent KIT gemma "kit_a"
162191
wait_for_model
163192

164-
# KIT writes a half-time pulse that feels like 120 under the running
165-
# 240 BPM bass. Scope=kit_a keeps it away from the bass sequencer.
166-
# Pattern is deliberately specific — kick on 0/8, closed hat on 4/12
167-
# of a 16-step loop — so the first response has concrete targets
168-
# instead of drifting to a full 4-on-the-floor that'd overwhelm Bach.
169-
ask "KIT: half-time drums under a 240 BPM Bach outro. Kit_a only, 16-step loop. Kick on steps 0 and 8 ONLY (not every 4 — that's too dense at this tempo). Closed hi-hat on steps 4 and 12. NO snare on 2 and 4 — this is not pop. Add one or two quiet ghost hits somewhere unusual for texture (a soft hat on a weird step, or a single tom ping). Everything quiet, velocity ~0.4 max. The drums should feel half the speed of the bass — a slow pulse under a running melodic line, not a dance beat. DO NOT touch kit_b / hoover / an1x / bass — kit_a only." KIT 0
193+
# First ask: sparse 32-step half-time pulse. Explicit step targets so
194+
# the model has concrete positions instead of drifting to a dense
195+
# 4-on-the-floor that'd bury Bach.
196+
ask "KIT: half-time drums under a 240 BPM Bach score. Kit_a only, 32-step loop (two bars). Kick on steps 0, 8, 16, 24. Closed hi-hat on steps 4, 12, 20, 28. NO snare on 2 and 4 — this is not pop. Add two or three quiet ghost hits somewhere unusual for texture across the 32 steps (a soft hat on a weird step, a single tom ping, a muted kick). Everything quiet, velocity ~0.4 max. Half the speed of the bass — a slow pulse under a running melodic line, not a dance beat. DO NOT touch kit_b / hoover / an1x / bass — kit_a only." KIT 0
170197

171198
# ── Scene 4: FX outro ───────────────────────────────────────────────────────
172-
# One FX agent, tight scope: reverb + bitcrush only. Three asks
173-
# ~30 s apart so the escalation reads as a progression, and the
174-
# camera stays on the FX module after each ask so you can see the
175-
# bitcrush wet knob swing.
199+
# FX is now DRIVEN DIRECTLY from bash via api_params, not by an FX
200+
# agent. Reason: the FX lane of Gemma 4 reliably emits `reverb_*`
201+
# and `ring_mod_*` but never `bitcrush_*` in its raw output, no
202+
# matter how explicitly the prompt spells the field names. Three
203+
# consecutive prior recordings all had bitcrush stuck at its default
204+
# (rate = 0, mix = 0) because the agent never wrote the keys.
205+
# Scripted api_params gives a predictable escalation — three nudges,
206+
# roughly 30 s apart, with each one bumping the three bitcrush
207+
# channels together so the crunch builds audibly into the ending.
176208

177209
scene "FX outro"
178210

179-
FX_START_OFFSET=$(echo "$PLAY_DURATION - 60 - 90" | bc -l)
211+
# Align FX outro so stage 3 ends ~21 s before the MIDI's natural stop
212+
# (previous cut used 15 s of headroom; TTS was still landing 6 s
213+
# after music, so another 6 s earlier). Stages are 30 + 30 + 32 =
214+
# 92 s; drums phase was 30 s; everything else comes out of this
215+
# pre-FX fill.
216+
FX_START_OFFSET=$(echo "$PLAY_DURATION - 30 - 92 - 21" | bc -l)
180217
# Long stretch between drums-arrive and FX-outro — fill it with
181218
# camera motion instead of staring at the sequencer.
182219
fill_wait "$FX_START_OFFSET"
183220

221+
# Add the visible FX modules. Both reverb and bitcrush are drawn to
222+
# the rack so what the viewer sees matches what they hear — the bus
223+
# DSP runs reverb unconditionally whenever fx.reverb_mix > 0, so
224+
# without a reverb module visible the previous take had "invisible
225+
# reverb". Other FX channels are locked above so nothing else
226+
# audible can sneak in.
227+
add_effect reverb
228+
show_then_return reverb 4
229+
enable_fx reverb
184230
add_effect bitcrush
185231
show_then_return bitcrush 4
186-
# Flip bitcrush on — it was added disabled by default so the module
187-
# could sit in the rack briefly without clicking the signal. The FX
188-
# agent's ramps below then have something to affect.
189232
enable_fx bitcrush
190233

191-
add_agent FX gemma fx
234+
# FX field-name note: state keys are `fx.bitcrush_mix`,
235+
# `fx.bitcrush_bits`, `fx.bitcrush_rate`, `fx.reverb_mix`,
236+
# `fx.reverb_size`. `bitcrush_bits` is INVERTED: 1.0 = full quality
237+
# (bypass), 0.0 = 1-bit. So "more crunch" means bits DOWN.
192238

193-
# Stage 1: gentle.
194-
ask "FX: outro starts now. Nudge reverb_mix up to about 0.30 and lengthen reverb_size a touch. Bring bitcrush in light: wet around 0.20, bit_depth_reduction around 0.30, sample_rate_reduction around 0.25. Leave delay / chorus / phaser / eq / compressor ALONE — this scenario only uses reverb and bitcrush. Use ramps with bars: 4 for every move." FX 0
239+
# Stage 1: clearly audible bitcrush + medium reverb. Previous values
240+
# were too subtle — bumped bitcrush_mix higher and bits lower so the
241+
# crunch is unmistakable from the first stage. KIT gets nudged to
242+
# evolve so the drums don't loop a single two-bar pattern through
243+
# the whole outro.
244+
api_params '{"fx":{"reverb_mix":0.40,"reverb_size":0.60,"bitcrush_mix":0.45,"bitcrush_bits":0.55,"bitcrush_rate":0.40}}'
245+
ask "KIT: evolve the 32-step groove. Keep it sparse and half-time (kick on 0/8/16/24 still anchors it), but add a couple of extra ghost kicks or off-grid hats — maybe an anticipation before the downbeat (step 7 or 23), or a pair of 16th hats somewhere in the second bar. Still no backbeat snare. 32-step loop, velocities ~0.4 max, kit_a only." KIT 0
195246

196247
look_at fx
197248
wait_seconds 30
198249

199-
# Stage 2: dirtier.
200-
ask "FX: stage two. Push bitcrush harder — wet 0.55, bit_depth_reduction 0.60, sample_rate_reduction 0.55. Raise reverb_mix another step to about 0.45. Still reverb + bitcrush only — nothing else. Ramps with bars: 4." FX 0
250+
# Stage 2: heavier crunch + fuller reverb.
251+
api_params '{"fx":{"reverb_mix":0.60,"reverb_size":0.80,"bitcrush_mix":0.75,"bitcrush_bits":0.25,"bitcrush_rate":0.70}}'
252+
ask "KIT: push the groove further. Stay 32 steps, half-time, kit_a only. Vary the kick placement (can drop one of 0/8/16/24 for a breath, add one at 11 or 27 for tension), let the hats get a bit more active in the second bar. One sparse snare ghost is allowed now — somewhere unexpected like 14 or 22, never on 4 or 12. Velocities still quiet." KIT 0
201253

202254
look_at fx
203255
wait_seconds 30
204256

205-
# Stage 3: full chaos.
206-
ask "FX: final stage. Bitcrush cranked: wet 0.85+, bit_depth_reduction 0.90, sample_rate_reduction 0.85. Reverb_mix to 0.70 with reverb_size close to 1.0 for a cavernous tail. Reverb + bitcrush ONLY. Ramps with bars: 2 or 4 for a faster collapse into the final bars." FX 0
257+
# Stage 3: full chaos — hard digital crunch + cavernous reverb.
258+
# Stage 3 is 32 s so the final bitcrush values sit long enough to
259+
# actually register as the "collapse" moment of the piece.
260+
api_params '{"fx":{"reverb_mix":0.85,"reverb_size":1.00,"bitcrush_mix":1.00,"bitcrush_bits":0.05,"bitcrush_rate":0.92}}'
207261

208262
look_at fx
209-
wait_seconds 30
263+
wait_seconds 32
210264

211265
# ── Scene 5: End ────────────────────────────────────────────────────────────
212266
# chain_loop=false stops the transport on its own at the end of the

0 commit comments

Comments
 (0)