Commit 29ce1039 by PLN (Algolia)

feat(gig): panic-chord + LED feedback, always-on lean recorder, freebox archive complete

Three independent pieces landing together from tonight's live-debug/build session:

1. Panic chord (task #34): holding LCXL push-buttons 73+74+91+92 together now
   flips a persistent silence toggle. These are momentary Note buttons (not
   latching CC), so a simultaneous hold is a natural chord — detected with an
   edge-triggered flip-flop in start_and_midi.scd (the only layer that sees raw
   per-button press/release state cleanly). Flips are echoed to Tidal as a new
   virtual control "^93", and to the LCXL's own LEDs via the Note-On method
   already validated in this project (reference_lcxl_led_stall memory). Tidal
   side is a plain gPanic gate in live/lib/prelude.tidal — PLN wires it into
   whichever track he's actively composing/testing, not applied globally.
   Not yet live-verified (needs a fresh boot) — tracked as task #34.

2. tools/gig_record.sh: a lean, reusable, always-on stereo-master capture,
   answering "I want to always record all play, compressed." Auto-resolves
   whatever the current default PipeWire sink is at start time (so it follows
   the rig from onboard SOF SoundWire onto the UMC202HD arriving tomorrow with
   zero config changes) and encodes to Opus at 160kbps in hourly segments
   (~72MB/hr — 188GB free buys thousands of hours). start/stop/status/slice
   subcommands; slice does a fast stream-copy excerpt for quick auditioning or
   feeding into Slopmotion once that's available. Smoke-tested end to end
   (start, 8s live capture, ffprobe-verified valid opus, stop) before wiring
   into anything. NOT yet wired into gig-up.sh's auto-launch — PLN wants
   silent-by-default on every boot; that wiring is the next step.

3. Local disk freed from 28GB (98%) to 188GB (81%) free. Root-caused why the
   session had crept there: the 436GB Ardour "Tidal Multi" session (deprecated
   in favor of the lean "Tidal Live" the gig now boots) was still sitting
   locally despite the freebox mirror already holding it byte-identical.
   Verified via fbk --dry-run (0 audiofiles needing transfer; the 1530-file,
   449,260,749,448-byte audiofiles tree matched exactly on both sides) before
   asking PLN to bless the delete — freed via `rm -rf` of audiofiles/export/
   peaks, keeping the tiny .ardour session file + midifiles as structural
   reference. (Real gain was 160GB not the naive 436GB sum — this machine's
   root is Btrfs with zstd compression, so du-reported sizes don't equal freed
   space 1:1; 188GB real free per df is still the number that matters.)

Also: visuals/slop/README.md — documented the staged first test clip
(Opal2024's 12_Mauerpark.flac, loudest 60s window at 2:53-3:53, chosen because
it's the same track lineage as ete_a_mauerpark.tidal currently being edited)
while the actual Slopmotion repo remains unlocated (org has 0 repos; asked
PLN to get the real URL from Kevin/Shipow).
parent c71ff2d5
......@@ -22,3 +22,4 @@ __pycache__/
# Generated at launch by gig-up.sh (set-specific sample preload + sclang log)
preload.scd
gig-sclang.log
recordings/
......@@ -57,4 +57,21 @@ let d1 = xfade 1 . (|< orbit 0)
-- NOTE: while these xfade-dN are active, an *in-track tweak* also blends over
-- 4 cycles (not instant). That's fine for launches; if you're iterating fast on
-- one line, re-eval the plain BootTidal defs (or use p N $ … for an instant set).
-- =====================================================================
-- PANIC CHORD — kills the tedium of composing/testing. Hold LCXL push
-- buttons 73+74+91+92 TOGETHER; the SC bridge (start_and_midi.scd) detects
-- the chord (edge-triggered, since these are momentary Note buttons) and
-- flips a PERSISTENT "^93" toggle, echoed on the LCXL LEDs (lit = active).
-- Tidal side is just a plain 0/1 gate — wire it wherever you want silence
-- while testing. Not applied to any track by default (your call which
-- streams it should mute) — try on whichever you're actively composing:
-- =====================================================================
:{
let gPanic = (# gain (range 1 0 "^93")) -- ^93=0 normal, ^93=1 silent (gain 0)
:}
-- try: d1 $ gPanic $ gF1 $ gMute2 $ sound "bd*4"
-- hold 73+74+91+92 -> instant silence + LEDs light; hold again -> back, LEDs off.
-- Wire gPanic into whichever track(s) you're iterating on while composing.
-- To make xfade length live-controllable later: xfadeIn N <cycles> $ pat.
......@@ -40,10 +40,12 @@ MIDIIn.connectAll;
// Simple MIDI->OSC bridge - passes CC numbers directly as control names
on = MIDIFunc.noteOn({ |val, num, chan, src|
osc.sendMsg("/ctrl", num.asString, val/127);
~lcxlChordCheck.value(num, val);
});
off = MIDIFunc.noteOff({ |val, num, chan, src|
osc.sendMsg("/ctrl", num.asString, 0);
~lcxlChordCheck.value(num, 0);
});
cc = MIDIFunc.cc({ |val, num, chan, src|
......@@ -60,6 +62,35 @@ if (~stopMidiToOsc != nil, {
cc.free;
};
// --- Panic chord: hold push-buttons 73+74+91+92 together to flip a
// persistent "^93" toggle in Tidal (rest of the mute/gF wiring stays in
// BootTidal.hs — this just detects the chord + drives the LED, since only
// this layer sees raw button state to do an edge-triggered flip-flop).
// LCXL LED write validated via plain Note-On (see reference_lcxl_led_stall
// memory: `aseqsend -p 20:0 90 29 3C ...` lights a button LED). Wrapped in
// try{} so a missing/renamed device never blocks the boot.
~lcxlOut = try {
var dest = MIDIClient.destinations.detect({ |d| d.device.asString.containsi("Launch Control") });
if (dest.notNil, { MIDIOut.newByName(dest.device, dest.name) }, { nil });
};
~lcxlChordBtns = [73, 74, 91, 92];
~lcxlChordHeld = Set.new;
~lcxlPanicState = 0;
~lcxlLed = { |btnNum, on|
if (~lcxlOut.notNil, { ~lcxlOut.noteOn(0, btnNum, if(on, 60, 0)) });
};
~lcxlChordCheck = { |num, val|
if (~lcxlChordBtns.includes(num), {
if (val > 0, { ~lcxlChordHeld.add(num) }, { ~lcxlChordHeld.remove(num) });
if (~lcxlChordHeld.size == ~lcxlChordBtns.size, {
~lcxlPanicState = 1 - ~lcxlPanicState;
osc.sendMsg("/ctrl", "93", ~lcxlPanicState);
~lcxlChordBtns.do({ |b| ~lcxlLed.value(b, ~lcxlPanicState == 1) });
"panic-chord: ".post; ~lcxlPanicState.postln;
});
});
};
// Evaluate the line below to stop it.
// ~stopMidiToOsc.value;
......
#!/usr/bin/env bash
# gig_record.sh — lean, always-on stereo master capture (compressed, black-box style).
# NOT a replacement for the Ardour multitrack session — that's the pristine, per-orbit
# archival copy. This is the cheap always-running safety net + the source for quick
# 1-minute slop-visuals test slices, so a take never goes uncaptured.
#
# Usage:
# gig_record.sh start start recording in the background (opus, hourly segments)
# gig_record.sh stop stop the background recorder
# gig_record.sh status is it running? which file? how big?
# gig_record.sh slice FILE START_SEC MINUTES OUT fast lossless-cut a test excerpt
set -euo pipefail
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
OUT_DIR="$DIR/recordings"
PID_FILE="$OUT_DIR/.recorder.pid"
LOG_FILE="$OUT_DIR/recorder.log"
BITRATE="${GIG_RECORD_BITRATE:-160k}"
SEGMENT_SECS="${GIG_RECORD_SEGMENT:-3600}"
mkdir -p "$OUT_DIR"
default_monitor() {
local sink
sink="$(pactl get-default-sink 2>/dev/null)"
[ -n "$sink" ] || { echo "gig_record: no default sink found" >&2; return 1; }
echo "${sink}.monitor"
}
cmd_start() {
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
echo "gig_record: already running (pid $(cat "$PID_FILE"))"
return 0
fi
local monitor
monitor="$(default_monitor)" || return 1
echo "gig_record: capturing '$monitor' -> $OUT_DIR/%Y-%m-%d_%H%M%S.opus (opus @ $BITRATE, ${SEGMENT_SECS}s segments)"
nohup ffmpeg -nostdin -loglevel warning \
-f pulse -i "$monitor" \
-c:a libopus -b:a "$BITRATE" -vbr on \
-f segment -segment_time "$SEGMENT_SECS" -strftime 1 \
"$OUT_DIR/%Y-%m-%d_%H%M%S.opus" \
>>"$LOG_FILE" 2>&1 &
echo $! > "$PID_FILE"
disown
echo "gig_record: started (pid $(cat "$PID_FILE"))"
}
cmd_stop() {
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
kill "$(cat "$PID_FILE")"
rm -f "$PID_FILE"
echo "gig_record: stopped"
else
echo "gig_record: not running"
rm -f "$PID_FILE"
fi
}
cmd_status() {
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
echo "gig_record: RUNNING (pid $(cat "$PID_FILE"))"
ls -lh "$OUT_DIR"/*.opus 2>/dev/null | tail -3
du -sh "$OUT_DIR" 2>/dev/null
else
echo "gig_record: not running"
fi
}
# Fast, lossless (stream-copy) excerpt for auditioning / slop-visuals testing.
cmd_slice() {
local file="$1" start="$2" minutes="$3" out="${4:-}"
[ -f "$file" ] || { echo "gig_record: no such file: $file" >&2; return 1; }
[ -n "$out" ] || out="$OUT_DIR/slice_$(basename "$file" .opus)_${start}s_${minutes}m.opus"
ffmpeg -nostdin -loglevel warning -y -ss "$start" -t "$((minutes * 60))" -i "$file" -c copy "$out"
echo "gig_record: wrote $out"
}
case "${1:-}" in
start) cmd_start ;;
stop) cmd_stop ;;
status) cmd_status ;;
slice) shift; cmd_slice "$@" ;;
*) echo "usage: $0 {start|stop|status|slice FILE START_SEC MINUTES [OUT]}" >&2; exit 1 ;;
esac
......@@ -18,11 +18,20 @@ a lot of work into it recently (new capabilities / capas). Two horizons for us:
(never stalls audio/editor)? Compare with HUD Feature B (#22).
## Pointers (fill in as we learn)
- Slopmotion source / repo: **TODO — get from Kevin** (path or URL)
- Slopmotion source / repo: **TODO — get from Kevin** (path or URL). Searched this
machine thoroughly (2026-07-25) — not installed here yet; this is the real blocker
on actually running a first generation.
- Install / run notes: TODO
- Input formats it wants (audio? stems? images?): TODO
- Export settings for Insta (aspect, codec, length): TODO — target H.264 mp4,
vertical 1080×1920 for stories/reels, ≤60s.
## Staged test input (ready the moment Slopmotion is available)
`samples/opal2024_mauerpark_173s-233s.flac` — 60s excerpt, 2:53–3:53 of
`Prod/Opal2024/12_Mauerpark.flac` (loudest sustained window, found via RMS sliding-sum).
Chosen deliberately: same track lineage as `live/midi/nova/techno/ete_a_mauerpark.tidal`
(currently being edited for OPAL 2026) and same event series (Opal) — the natural
first test clip once Slopmotion is in hand.
Related: `../../live/` (source tracks), HUD scene backdrop (task #22),
GLITCHWAVE source imagery.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment