A³ Mixer Development
Python script a3-mixer.py
software/scripts/a3-mixer.py in a3-mixer.
Direction |
What |
|---|---|
panel → script (USB serial) |
buttons, faders, encoders |
script → Core (OSC) |
the same, as A³ addresses |
Core, beat-analyzer → script |
input meters |
script → panel (USB serial) |
LEDs and VU meters |
script → displays (I2C) |
five OLEDs, below |
Input meters feed the LED VUs and the channel display’s A (Displays.note_input).
The displays
What they show: the desk’s input selectors. Two parts:
a3-mixer-set-display/display_panel.py, pure and tested without a Pi: display ↔ multiplexer channel (PANELS), reading Core’s announcements, meter movement, layout.channel_picture()turns a cursor, nine levels and the stem mask into headings D1 | D2 | A, nine bars, dividers, the arrow and the bracket (playing stem = lowest mask bit, else A =ANALOG_INPUT, 8). The ninth slot isLAST_SLOT_METERS(2) wide, so bars keep 10 of 11 pixels on 128 px; A’s bar is centred in it, and the return’s CUE field uses the same slot.return_picture()draws STEM and ANALOG bars, bracket, CUE field and arrow._bands()gives three row bands: headings, arrow (ARROW_ROWS5, dark row above and below), meters to the bottom (rows 19–63 of 64). Any display size.a3_mixer_displays.pydraws it with PIL onto the SSD1306s via the TCA9548A; imports the Pi libraries only when opening. Without them the desk runs without displays.
Element |
Drawing |
|---|---|
cursor |
solid down-arrow, |
bracket |
“]” turned 90° CCW, |
A’s bar |
the channel’s input meter, only while analog plays ( |
CUE field |
filled with dark letters when cued, outline otherwise; letters stacked |
How:
Own thread: the OSC thread notes, the display thread draws the latest state; turns and announcements before meter redraws.
Meters on a clock:
METER_STEPS_PER_SECOND(10) steps with the peak since the last step; silent after 0.5 s unheard; stereo shows the louder side.Ballistics: instant rise,METER_FALL_DB_PER_SECOND(20) over 48 dB, time-based; no peak marks.STEM uses
stem_aux_L/Ronce heard, else the loudest stem on the return.Redraw only on pixel change (
pixel_key(): bars, cursor, CUE state, bracket, headings).show_channel()posts only when the playing stem changes. About 17 draws/s instead of 50.Partial updates:
a3_mixer_oled.pysends only changed windows.A failed display: one journal line, retried after 2 s, drawn whole.
a3-mixer-set-display.py writes each display’s name once at boot; the main
script draws over it.
Addresses, ports and where the desk gets them
No address of its own: it fetches a3-osc.json from Core
(truth).
Cache
~/.cache/a3/a3-osc.json(root’s home: the service runs as root).Announcement: listens on
devices.announcefor Core’s/core/here(every 2 s, truth URL + fingerprint; no router crossing).Update: on a new fingerprint it fetches
/api/truth, checks body sha256,X-A3-Truthand announcement agree, writes the file whole and exits; systemd restarts it. A refusal is logged; it runs on.At start:
$A3_OSC_TRUTH, the cache, the oldsoftware/scripts/a3-osc.json(one more release), else it waits. It never loops on a missing truth.
Deploying needs no copy step; address changes happen on Core
(how). Meters are found by name. With every state request it
sends /device/hello (name, sha256) for Core’s comparison.
What it is sent and does not listen for
Core sends gain, EQ, volume, sends, master, filter and flags too; pythonosc drops them silently. The pots are analog, so a returned value could only be shown, and how to show a disagreement is undecided (The way back).
Panel firmware
PlatformIO, hardware/mainboard/firmware/,
for the controller on A³ Mixer hardware; build: Building and testing.
Input VUs: 8 LEDs (1–4 green, 5–6 yellow, 7–8 red). The script sends the top lit index (
channel_leds()ina3_mixer_meters.py) atCHANNEL_LED_THRESHOLDS_DB−36, −24, −18, −12, −9, −6, −3, 0 dBFS; no peak dot. Older firmware: green bar, red top.Main VU: eight columns across four modules, 32 rows.
Encoder switches: all eight
EBchannels reported; unused ones ignored.
A Teensy with older firmware needs reflashing for these.