Files
L'électron rare bbf7c5dc75 feat: import hydra-sc + portable ~base
Context: while inspecting the MBP we found ~/hydra-sc/, an earlier
prototype the user had built before AV-Live existed. It contained
working pieces we needed (Hantek firmware loader, OSC scope bridges,
SC↔Hydra ws relay, hydra patch). We also discovered that 00_load.scd
hard-coded ~base to the GrosMac dev path, which silently broke the
whole sclang engine on the MBP — every (~base ++ '...').load was
hitting a non-existent path and quietly failing. The user's existing
control/midi.scd is comprehensive but split into 5 IDE-friendly
top-level blocks, so it isn't .load-compatible and never auto-runs at
boot.

Approach: derive ~base from thisProcess.nowExecutingPath at load time
so the same project tree works from any absolute location. Migrate the
hydra-sc artifacts into AV-Live/{tools,sound_algo/control,
sound_algo/web/public/hydra} so everything lives in one repo. Add a
single-block live/midi_init.scd that does MIDIClient.init +
MIDIIn.connectAll + the default mapping in one .load-friendly chunk,
deferred 0.5s through AppClock so MIDI device discovery doesn't block
the main load chain. Old ~/hydra-sc/ on the MBP deleted, the orphan
hydra-sc-bridge node process (PID 41421 from a previous session)
killed.

Changes:
- sound_algo/00_load.scd : both ~base assignments now use
  thisProcess.nowExecutingPath.dirname with fallback to the legacy
  hard-coded path
- tools/hantek/{fx2_fwload,hantek_to_osc,hantek_osc_bridge}.py :
  imported as-is, plus a new README explaining the firmware-load /
  C++-or-sigrok choice and the macOS prerequisites (PulseView,
  sigrok-cli, python-osc)
- sound_algo/control/hantek_receiver.scd : OSCdef \hantek that
  exposes ~hPk1/~hRms1/~hPk2/~hRms2 from the Python bridge — kept as
  alternate path to oscope-of's C++ driver
- sound_algo/control/hydra_send.scd : SC→Hydra OSC sender (was
  sc_to_hydra.scd in hydra-sc) — analyzes SoundIn.ar(0) into rms /
  centroid / flatness and sends to 127.0.0.1:8000
- sound_algo/web/public/hydra/external_patch.js : Hydra patch the
  user pastes into hydra.ojack.xyz when running the external bridge
- sound_algo/live/midi_init.scd : single-block MIDI auto-init with
  the same CC/note bindings as control/midi.scd plus new note
  ranges for albums (notes 36-59 → ~playAlbum A-X), kicks
  (notes 60-71 → ~kkAt index 0..11), melodies (72-83 → ~mmAt),
  fx (84-95 → ~ffAt), CC 7 → master volume. Idempotent (frees
  previous MIDIFunc on re-load), graceful when no MIDI device is
  detected (skips mapping, prints note)

Impact: sclang now actually boots its engine on the MBP (~base path
was the silent killer). MIDI controllers connected at startup get
the default mapping automatically. The hydra-sc legacy is fully
absorbed into AV-Live so there's one place to find everything.

Note: live/midi_init.scd is NOT yet wired into live/_load.scd's
auto-load chain — that's a follow-up so we can verify the file
loads cleanly first.
2026-05-07 17:10:17 +02:00

59 lines
2.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Hantek 6022BL → OSC bridge.
Lit des blocs d'échantillons via sigrok-cli et envoie chunks OSC à SC.
Usage:
./hantek_to_osc.py [--rate 100k] [--samples 1024] [--host 127.0.0.1] [--port 57120]
Prérequis:
- sigrok-cli installé (brew install sigrok-cli)
- firmware fx2lafw chargé (lance OpenHantek une fois, OU sigrok-cli scanne 2x)
- python-osc: /usr/bin/python3 -m pip install --user python-osc
"""
import argparse, subprocess, sys, struct
from pythonosc.udp_client import SimpleUDPClient
def main():
p = argparse.ArgumentParser()
p.add_argument("--rate", default="100k")
p.add_argument("--samples", type=int, default=1024)
p.add_argument("--driver", default="hantek-6xxx")
p.add_argument("--host", default="127.0.0.1")
p.add_argument("--port", type=int, default=57120)
p.add_argument("--addr", default="/scope")
args = p.parse_args()
osc = SimpleUDPClient(args.host, args.port)
cmd = [
"sigrok-cli", "-d", args.driver,
"--config", f"samplerate={args.rate}",
"-O", "binary",
"--continuous",
"-C", "CH1,CH2",
]
print(f"[bridge] {' '.join(cmd)}{args.host}:{args.port}{args.addr}")
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=0)
chunk_bytes = args.samples * 4 * 2 # 2 channels float32
try:
while True:
buf = proc.stdout.read(chunk_bytes)
if not buf:
break
n = len(buf) // 8
ch1 = struct.unpack(f"{n}f", b"".join(buf[i*8:i*8+4] for i in range(n)))
ch2 = struct.unpack(f"{n}f", b"".join(buf[i*8+4:i*8+8] for i in range(n)))
# Send peak + RMS per channel (compact, SC-friendly)
def pk(s): return max(abs(min(s)), abs(max(s)))
def rms(s): return (sum(x*x for x in s)/len(s))**0.5
osc.send_message(args.addr, [pk(ch1), rms(ch1), pk(ch2), rms(ch2)])
except KeyboardInterrupt:
pass
finally:
proc.terminate()
if __name__ == "__main__":
main()