Skip to content

DJ-Software: Engine, Analyse, Sammlung, Oberfläche und Fernsteuerung - #1

Draft
Flissel wants to merge 63 commits into
mainfrom
docs/grundlagen-recherche
Draft

DJ-Software: Engine, Analyse, Sammlung, Oberfläche und Fernsteuerung#1
Flissel wants to merge 63 commits into
mainfrom
docs/grundlagen-recherche

Conversation

@Flissel

@Flissel Flissel commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Der Aufbau des DJ-Werkzeugs von der Recherche bis zu einer Anwendung, die ein Set fährt: Liste, Laden, Übergang — ohne Handgriff dazwischen, von außen steuerbar, von mehreren Bedienern gleichzeitig. Samt einem Kritiker, der jeden Übergang nachmisst, einer Analyse, die weiß, wo im Stück sie ist, einem Prüfstand, der all das gegen ein menschliches Ohr hält, einem Bogen, der sagt, was das Set vorhat, einem Raum, der ihn verschiebt, und Einzelspuren, mit denen sich die Stimme herausnehmen lässt.

Alle zehn geplanten Phasen angefangen, 515 Tests, clippy sauber.

Die Oberfläche

Ein Set, das sich selbst weiterträgt

do master.queue_add /musik/mitte.wav
do master.queue_note 1 mehr Druck nach dem Aufbau
when deck1.beats_left < 200 do master.queue_next
when deck1.beats_to_outro < 0 do master.uebergang bassswap 16

Die erste Bedingung legt den nächsten Track auf — auf dem freien Deck, auf dem ersten Downbeat. Die zweite fährt den Übergang, sobald das Outro anfängt. Danach steht das ausgehende Deck, der Plan ist leer, und dieselben zwei Zeilen gelten für den nächsten Track.

Gewählt hat trotzdem ein Mensch oder ein Agent, und zwar zweimal: welcher Track in die Liste kommt — mit Notiz, warum — und welcher Griff gefahren wird. Die Anlage führt aus und begründet, was sie gerade tut; sie entscheidet nicht, was als Nächstes gut wäre.

Die einzelnen Schichten waren je für sich geprüft. Beim ersten Versuch, sie hintereinander laufen zu lassen, kamen fünf Fehler heraus, die kein Test hätte zeigen können — vier davon erst beim zweiten Griff. Sie stehen unten unter „Funde, die erst das laufende Programm zeigte".

Was drin ist

Crate Inhalt
audio-core Deck: Dekodierung, WSOLA-Zeitstreckung, Einzelspuren, Beatgrid, Tonart und Gliederung als Werte, Hot Cues, Loops
audio-engine Mixer: Kanalzüge, EQ, Filter, Effekte, Crossfader, Cue-Bus, Begrenzer, AUX, Sync, Mitschnitt, Kommandoschlange, Geräteausgabe
analysis Tempo, Beatgrid, Tonart, Wellenform-Spitzen, Gliederung in Intro/Aufbau/Drop/Break/Outro, die Schätzung, wo ein Übergang liegt, inhaltsadressierter Sidecar-Cache
library SQLite-Sammlung, Suche nach Text, Tempo und Tonart, Playlists, Hot Cues, Traktor-Import
control Benannter Steuerraum, Zeitplan auf dem Beatgrid, Phrasenbezug, Gestenformen, Übergangs-Repertoire, Zurückhaltung, Gliederung, Set-Bogen, Raum, Auswahl nach Energie, Warteschlange, Signale, Mitschrift, Ereignisse für mehrere Bediener
musik-app Oberfläche: zwei Decks mit Wellenform und Beatgrid, Mixer mit AUX, Plattenkiste, Regie-Spalte
musik-cli Deck fahren, analysieren, Offline-Mix rendern (auch vierkanalig), Sammlung verwalten, Mitschnitte kritisieren, Prüfmaterial bauen, die Analyse gegen Gehörtes prüfen, den Kritiker gegen die Anlage halten
mcp/ MCP-Brücke: sechzehn Werkzeuge, Beschreibungen aus dem Katalog erzeugt

Drei Entscheidungen, die die Architektur bestimmt haben

Der Cue-Ausgang erzwingt ein Gerät mit vier Ausgängen. Zwei Geräte heißt zwei Taktgeber heißt Drift. Das ist eine Anschaffungsentscheidung, keine Softwarefrage.

Atomics reichen nicht. Für Position und Tempo schon; für einen Trackwechsel nicht — eine Box lässt sich nicht atomar tauschen. Daher eine lock-freie SPSC-Schlange plus Rückkanal für abgelöste Quellen.

Ein Beatgrid braucht Anker und Tempo. Ohne Phase hat man BPM, aber kein Grid, und Sync klingt trotz gleicher Zahl falsch.

Fernsteuerung — der Teil, den es sonst nicht gibt

$ nc -U "$XDG_RUNTIME_DIR/musik.sock"
get deck1.key_camelot
value deck1.key_camelot 8A
do master.search_harmonic 8A
do deck2.load /musik/nachtschicht.wav
do deck2.sync
sync deck2 auf deck1 tempo 1.01570 phase -0.1178

Mixxx hat ein sehr ähnliches Control-System im Inneren, aber prozessintern; nach außen spricht es OSC nur ausgehend. Vier Dinge sind hier bewusst anders:

  1. Der Steuerraum beschreibt sich zur Laufzeit selbst. list liefert zu jedem Control Typ, Bereich, Einheit, Schreibbarkeit und Bedeutung. Die MCP-Werkzeugbeschreibungen werden daraus erzeugt statt doppelt gepflegt, und die Schieberegler der Oberfläche holen sich Bereich und Tooltip aus demselben Katalog.
  2. Werte sind typisiert, nicht durchweg double. Aktionen sind ein eigener Typ mit eigenem Verb.
  3. Auch die Oberfläche geht durch diesen Weg. Zwei Wege zum selben Ziel wären zwei Stellen, an denen es schiefgehen kann.
  4. Er kennt Zeit, Phrase, Struktur, Reihenfolge, den Raum, den Bogen, die eigene Wiederholung — und die anderen Bediener.

Unix-Socket statt TCP-Port, und das ist eine Sicherheitsentscheidung: Wer hineinschreibt, steuert die Anlage.

Ein Übergang, komplett von außen gefahren

Zeit: Übergänge statt Reglerstellungen

ramp channel1.eq_low 0.0 8            # Bass raus über 8 Beats
in 8 ramp channel2.fader 0.9 16       # danach Deck B rein über 16
in phrase ramp master.crossfader 1 32 # oder: auf der nächsten Eins
in deck2 phrase+16 set deck1.loop_active 0   # nach *dessen* Takten
plan                                  # das gemeinsame Blatt
cancel 2

Gerechnet wird in Beats, nicht in Sekunden: Ein Plan in Sekunden geht schief, sobald jemand am Tempo dreht.

in nimmt statt einer Zahl auch phrase und phrase+n. Ein Übergang beginnt auf der Eins einer Phrase, nicht nach einer runden Zahl Beats.

Der Mensch gewinnt. Eine Rampe gibt auf, sobald jemand anders denselben Regler anfasst — geprüft über den Wert selbst, was einen zweiten Agenten genauso trifft wie die Hand an der Oberfläche.

Und ein Auftrag, dessen Grundlage wegfällt, sagt das, statt still zu warten:

plan 9 abgebrochen — deck1 ist durchgelaufen
plan 3 abgebrochen — deck1 ist zurückgesprungen (Schleife oder Sprung)

Ein bloß angehaltenes Deck hält den Plan dagegen nur an. Wer pausiert, will da weitermachen, wo er aufgehört hat.

Die Form einer Bewegung

Form Verlauf Wofür
linear gleichmäßig Bass-Swaps, alles nicht als Geste Gemeinte
weich S-Kurve lange Blenden — Anfang unauffällig, Mitte entschieden
spaet lange fast nichts, dann schnell hält den ausgehenden Track präsent
frueh sofort viel, dann auslaufen macht den Wechsel zum Ereignis

Deck und Form stehen hinten in beliebiger Reihenfolge und werden daran erkannt, was sie sind. Die Wächter sind keine Behauptungen: Jede Form bildet 0 auf 0 und 1 auf 1 ab, keine läuft zwischendurch zurück, und auf halber Strecke stehen sie messbar woanders — am laufenden Regler nachgefahren.

Ein Repertoire statt eines Handgriffs

do master.uebergang bassswap 16
uebergang bassswap über 16 Beats, 7 Zeilen
  set channel2.eq_low 0 → ok
  in deck1 phrase set deck2.play 1 → ok plan 1
  in deck1 phrase ramp master.crossfader 0 16 weich deck1 → ok plan 2
  in deck1 phrase+16 ramp channel1.eq_low 0 8 deck1 → ok plan 3
  in deck1 phrase+16 ramp channel2.eq_low 1 8 deck1 → ok plan 4
  in deck1 phrase+24 ramp master.crossfader 1 16 weich deck1 → ok plan 5
  in deck1 phrase+41 set deck1.play 0 → ok plan 6

Fünf Griffe: blende (32 Beats), bassswap (16), schnitt (0), filter (16), schleife (16).

Die Schleife ist der einzige Griff, der dem Ausgehenden Zeit gibt. Ein Track, der in vier Beats zu Ende wäre, hält so noch eine ganze Phrase durch — genau dafür setzt ein Mensch am Ende eines Stücks eine Schleife.

Jeder Griff endet damit, dass das ausgehende Deck steht. Das ist die Stelle, an der aus einem Übergang ein Set wird. Getaktet wird nach dem ausgehenden Deck, das per Definition läuft, solange der Griff dauert. Und keine Bewegung endet im selben Takt, in dem ihr Deck stehenbleibt — sonst bleibt ihr Auftrag für immer im Plan liegen.

Jeder Griff ist eine Handvoll gewöhnlicher Protokollzeilen, die durch denselben Weg laufen wie alles andere — sie stehen im Plan, in der Mitschrift und bei den Ereignissen, ein zweiter Bediener sieht sie kommen, und cancel nimmt sie zurück. Es gibt nichts, was ein Agent nicht auch selbst hätte tippen können.

Die Anlage wählt nicht aus. Welcher Griff passt, hängt daran, ob der ausgehende Track ein Outro hat, der eingehende ein langes Intro, wie groß der Energieunterschied ist — und vor allem daran, was vorher schon dreimal gefahren wurde.

Der Bogen: was das Set vorhat

set master.arc 0 0.3, 20 0.7, 45 0.95, 60 0.5
do master.arc_start
get master.arc_gap    → value master.arc_gap 0.102069
get master.arc_trend  → value master.arc_trend steigt

when master.arc_gap > 0.3 do master.queue_next

Die Ist-Energie kommt aus der Art des Abschnitts, nicht aus dem Pegel. Der Pegel der Gliederung ist auf den lautesten Abschnitt desselben Tracks bezogen: Der Drop eines leisen Stücks steht dort genauso bei 0,99 wie der eines lauten. Über Tracks hinweg ist das nicht vergleichbar, und ein Bogen, der solche Zahlen addiert, rechnet mit Äpfeln.

Ohne arc_start gibt es keinen Ort auf dem Bogen, und dann wird auch keiner behauptet: leer statt null.

Der Raum schließt die Schleife

Die vier Signalplätze gab es lange, aber sie waren Deko: Werte gingen hinein, und nichts hat darauf reagiert. Ein Bogen, der von der ersten Minute an feststeht, ist ein Abspielplan.

set master.room signal1 0.5
do master.search_next

weil Andrang fällt (-1.15/min), Ziel 0.75 → 0.50; es läuft 0.55, weniger gesucht
track 128.02 - /musik/warmup.wav warmup — Energie 0.38 (-0.12 zum Ziel 0.50)
track 128.02 - /musik/mitte.wav mitte  — Energie 0.66 (+0.16 zum Ziel 0.50)
track 128.02 - /musik/peak.wav peak    — Energie 0.73 (+0.23 zum Ziel 0.50)

Am laufenden Programm nachgefahren: Bei einem Ziel von 0,75 steht peak oben. Ein fallender Raum beugt das Ziel auf 0,50 — und derselbe Track rutscht auf Platz drei.

Der Raum verschiebt das Ziel, nicht die Kurve. master.arc bleibt, was jemand aufgeschrieben hat, arc_curve zeigt es unverändert — am Ende des Abends lässt sich vergleichen, was geplant war und was geschah.

Gebeugt wird nach dem Trend, nicht nach dem Wert. Die Höhe eines Signals ist nichts Vergleichbares: Was „0,6 Andrang" bedeutet, weiß nur, wer den Sender geschrieben hat. Eine Änderung ist dagegen eine Aussage über denselben Sender. Höchstens um 0,25, damit ein hängender Sender das Set nicht übernimmt.

Zurückhaltung: ob immer dasselbe gefahren wird

get master.repeats      → value master.repeats 3
get master.transitions  → value master.transitions schnitt, schnitt, schnitt

when master.repeats > 2 do master.uebergang filter

Gezählt wird der Seitenwechsel des Crossfaders, nicht sein Wert — und nur, wenn zwei Decks laufen. Ein Bass-Swap fährt zweimal am Crossfader, eine Blende in achtzig kleinen Schritten, ein Schnitt in einem Sprung; angekommen sind alle drei einmal. Auf den Wert gezählt stünde nach einer einzigen Blende eine Wiederholung von 80 da, und die Zahl, die vor Eintönigkeit warnen soll, wäre selbst der Grund, sie zu ignorieren.

Einzelspuren: die Stimme wegnehmen

get deck1.stems        → value deck1.stems 3
get deck1.stem3_name   → value deck1.stem3_name vocals
set deck1.stem3_level 0

Gemessen am laufenden Programm, zwei Mitschnitte desselben Stücks:

Energie bei 1500 Hz Gesamtpegel
mit Stimme 9,8 · 10⁻⁵ 0,061
ohne Stimme 4,9 · 10⁻⁹ 0,039

Die Stimme ist um den Faktor 20 000 weg, Drums und Bass stehen.

Die Spuren liegen neben der Datei, in einem Ordner gleichen Namens mit der Endung .stems — kein neues Dateiformat, keine neue Abhängigkeit, und genau die Form, in der die gängigen Trennwerkzeuge ihr Ergebnis ablegen. Getrennt wird hier nicht — das ist Arbeit für ein neuronales Netz, ein eigenes Werkzeug und eine eigene Lizenzfrage.

Die schwierige Stelle war die Zeitstreckung. Sie sucht in jedem Hop die Stelle, an der die Wellenform am besten anschließt; vier Spuren getrennt zu strecken hieße vier verschiedene Entscheidungen, und was zusammen klang, klänge verwaschen. Gesucht wird deshalb auf der Summe, angewandt auf jede Spur. Im Audio-Callback wird nichts angelegt.

Der Prüfstand — und was er gefunden hat

Über der Anlage liegen drei Schichten Messwerkzeug: Kritiker, Mitschrift, Gliederung. Und jede Schwelle darin war an Material geeicht, das aus eigener Hand stammt. Ein Messwerkzeug, dem niemand widerspricht, ist ein Orakel.

musik-material /tmp/schwierig          # vier Fälle, jeder bricht eine Annahme
musik-pruefstand /tmp/schwierig/wahrheit.txt

musik-material baut Tracks mit bekannter Gliederung — einen ohne Outro, einen ohne Intro, einen mit zwei Breaks, einen mit Tempowechsel — und legt die Wahrheitsdatei daneben. Der erste Lauf: 13 von 23 Namen richtig. Die Grenzen saßen dabei fast alle auf den Frame genau; die Fehler lagen sämtlich in der Benennung, und sie waren nicht verstreut, sondern ein Muster. Viermal wurde ein Drop zu „Teil".

Die Ursache steckte in einer Zeile: Ein Drop war, wer über dem 75. Perzentil des Bassbands lag. Das ist rangbasiert, und darin steckt eine Behauptung über die Musik, die niemand aufgestellt hat — höchstens ein Viertel der Abschnitte kann je ein Drop sein. Jetzt entscheidet der Pegel als Anteil am lautesten Abschnitt: eine Höhe, kein Rang. Danach 21 von 23.

Dieselbe Umstellung trennt Aufbau und Break an dem, was sie musikalisch unterscheidet: Ein Aufbau kommt von etwas Leiserem und führt in den Drop, ein Break fällt aus einem Drop heraus. Am Pegel allein sind die beiden nicht zu trennen (0,48 gegen 0,40).

Gegen echte Musik mit gehörter Wahrheit steht der Prüfstand weiter aus — dafür gibt es docs/wahrheit-vorlage.txt; es fehlen die Tracks und ein Ohr.

Der Kritiker gegen die Anlage

Der Kritiker hört einen Mitschnitt und sagt, wann der Übergang begann. Die Mitschrift weiß es. Einmal gemessen ergab das 3,7 Sekunden zu spät — ein Wert, aus dem sich nichts folgern lässt.

musik-streuung /tmp/streuung   # 24 Sets durch die echte Anlage

Echter Mixer, echtes Pult, echtes Repertoire, echter Mitschnitt, echte Mitschrift; danach bekommt der Kritiker die Datei, so wie er sie sonst bekommt. Median +2,0 s, im Betrag 2,4 s, Spanne −1,4 bis +7,6 s. Der Einzelwert von oben stammt von einer 32-Beat-Blende und sitzt genau da, wo die Verteilung ihn erwarten lässt.

Übergang Fehler im Mittel Unschärfe, die er selbst angibt
Schnitt (0 Beats) −1,1 s 0,2 s
16 Beats +1,5 s 4,5 s
32 Beats +4,9 s 4,2 s
64 Beats +7,6 s 9,0 s

Ein harter Schnitt wird also auf ein Fenster genau gefunden, und eher zu früh als zu spät. Je länger die Blende, desto später die Schätzung — ungefähr ein Viertel der Blendendauer. Das ist kein Zufallsfehler, sondern der Preis dafür, dass der Anfang einer Blende per Konstruktion unhörbar ist. Die angegebene Unschärfe trägt: In 16 von 20 Fällen deckt sie den tatsächlichen Fehler.

Der Fund: eine lange Blende wird gar nicht bemerkt. Vier von 24 Übergängen hat der Kritiker übersehen, drei davon Blenden über 64 Beats. Der Grund steht in seiner eigenen Konstante — er vergleicht mit dem Klangbild von vor 16 Sekunden. Dauert die Blende länger, liegt der Vergleich selbst schon mitten in ihr, und der Unterschied reißt die Schwelle nie. Bei 128 BPM sind 64 Beats 30 Sekunden: eine Blende über zwei Phrasen ist für ihn unsichtbar. Mit einer größeren Zahl ist das nicht behoben — ein weiterer Rückblick verwechselt zwei aufeinanderfolgende Tracks.

Wenn einer dem anderen dazwischenkommt

Der Zweck des Projekts ist ein Team von Agenten. Gelaufen war lange ein Bediener in mehreren Rollen — und die Frage „was sieht ein zweiter?" hat vor dem ersten Test einen Fehler gezeigt: Der Taktgeber-Thread verwarf den Rückgabewert des Taktgebers. Damit ging jede Meldung des Plans verloren — fertig, abgebrochen und vor allem abgelöst. Ein Agent, dem ein anderer den Fader wegnahm, erfuhr es nie.

sub master.events
event plan 3 abgeloest channel1.fader — jemand anders hat den Regler
event plan 2 gestrichen
warnung 3 Ereignisse verloren — zu langsam gelesen

Ein Ring mit laufender Nummer, kein einzelner Wert: Der Taktgeber läuft alle 5 ms, der Server vergleicht alle 50. Zwischen zwei Blicken passen zehn Ereignisse, und neun wären weg — ausgerechnet dann, wenn viel gleichzeitig geschieht.

Die Mitschrift

Neben jedem Mitschnitt entsteht eine zweite Datei, gleicher Name, Endung .mitschrift:

1116160 23.253 deck1=48.012/16 deck2=-0.998/16~ > ramp master.crossfader 1 32
1837056 38.272 deck1=80.049/16 deck2=31.039/16 < plan 3 fertig master.crossfader 1.0000
Frage Aus dem Klang Aus der Mitschrift
Wann begann die Blende? 27,0 s (±4) 23,25 s
Auf welchem Beat? „nicht beurteilbar" 48,01 — 0,01 in die Phrase
Wie lang war sie wirklich? 18 s geschätzt 32,0 von 32 bestellten Beats

Die Schätzung aus dem Klang lag 3,7 Sekunden zu spät. Und ein Befund, den vorher niemand hätte finden können: Deck 2 setzte auf seinem Beat −1 ein, fünfzehn Beats neben seiner eigenen Eins. Ein frisch geladenes Deck steht inzwischen von selbst auf dem ersten Downbeat — die Ursache, nicht nur das Werkzeug dagegen.

Wo im Track das Deck steht

  0.5s  intro   Pegel 0.34  Bass 0.12  Dichte 0.43
 15.5s  aufbau  Pegel 0.53  Bass 0.42  Dichte 0.72
 30.5s  drop    Pegel 0.99  Bass 0.99  Dichte 0.98
 45.5s  break   Pegel 0.44  Bass 0.41  Dichte 0.56
 60.5s  drop    Pegel 0.98  Bass 0.98  Dichte 0.97
 75.5s  outro   Pegel 0.20  Bass 0.06  Dichte 0.25

Im Steuerraum stehen section, section_beats_left, beats_to_outro, intro_beats, entry und do deckN.jump_entry.

Der Anschluss für Agenten

Sechzehn Werkzeuge statt zweihundert. musik_set und musik_do erreichen alles; musik_status, musik_search und musik_next sparen die häufigsten Wege; musik_ramp (mit Form), musik_uebergang, musik_schedule, musik_when und musik_cancel reichen den Zeitplan durch, musik_queue/_add/_next die Liste, musik_signal den Raum.

## Bogen
Bei 23 min: soll 0.50, ist 0.20, Lücke **+0.30** — mehr Energie gesucht · hält

Der Raum (signal1 0.5) beugt das Ziel um **-0.25** — die Kurve selbst sagt 0.75.

**Warum:** Andrang fällt (-1.15/min), Ziel 0.75 → 0.50; es läuft 0.20 (+0.30)

## Übergänge
schnitt, schnitt, schnitt

**3× hintereinander derselbe Griff.** Fünf stehen im Repertoire.

Geprüft über einen echten MCP-Client gegen eine laufende Anwendung — tools/list und tools/call, nicht an der Schnittstelle vorbei.

Funde, die erst das laufende Programm zeigte

Fünf davon kamen beim Versuch, die Schichten hintereinander laufen zu lassen — vier erst beim zweiten Griff:

  • Der ausgehende Track lief nach dem Griff weiter. Unhörbar hinter dem geschlossenen Crossfader, aber laufend — und damit wurde der nächste uebergang abgewiesen: „es laufen mehrere Decks".
  • Getaktet wurde nach dem ersten Deck mit Beatgrid. Beim zweiten Griff ist das ausgerechnet das gerade gestoppte; ein stehendes Deck hält den Plan an. Der Übergang wurde angenommen, stand im Plan und lief nie. Sichtbar nur daran, dass der Bass des eingehenden Kanals zu blieb.
  • Eine Rampe, die im selben Takt endet, in dem ihr Deck stehenbleibt, bekam ihren letzten Schritt nicht mehr: Der Regler stand am Ziel, der Auftrag blieb für immer im Plan.
  • Ein frisch geladenes Deck stand auf Sekunde 0 statt auf dem ersten Downbeat. Die Reihenfolge war der eigentliche Fund: Der Sprung muss nach dem Stimmentausch kommen, sonst verbraucht ihn die alte Stimme auf ihrem eigenen Track (beat -1.02 gegen beat -0.03).
  • Ein durchgelaufener Track ließ seinen Plan für immer warten. Beim ersten selbstausgelösten Übergang lief der ausgehende Track mitten im Bass-Swap aus; danach stand der Crossfader für immer in der Mitte, mit fünf toten Aufträgen im Plan.

Und davor:

  • Stopp und Start eines Mitschnitts ohne Pause verloren die zweite Aufnahme. Lagen Stop und Start im selben Häppchen des Schreiber-Threads, öffnete der Start die neue Datei und der Stop schrieb den alten Mitschnitt hinein. Gefunden von der CI, an einem Test, der zwischen den Aufnahmen wartete und den Fehler damit versteckte.
  • Derselbe Fall, zweite Ursache: der Schreiber leerte den Ring ohne Deckel. Er holt erst die Befehle ab und leert dann den Ring; wer in dieser Lücke stoppt, neu startet und nachlegt, dessen zweiter Mitschnitt liegt schon im Ring, während der Stop noch im Briefkasten steckt. Wieder von der CI gesehen — 5244 statt 4044 Bytes, also 1000 + 300 Frames in einer Datei — und danach nie wieder: Vierzig Läufe mit zwanzig Runden blieben grün. Zurückgeholt hat ihn erst ein Test, der den Schreiber an genau dieser Stelle anhält; er meldet mit dem Fehler drin wieder exakt 5244 Bytes.
  • Die Benennung der Abschnitte verglich Abschnittsmittel mit Quantilen über Phrasen. Ein Stück mit 90 Sekunden Vollgas hieß „Intro, Break, Outro" und bekam die niedrigste Energie von allen — die Auswahlreihenfolge stand auf dem Kopf.
  • Ein Auftrag, dessen Taktgeber zurückspringt, lief stumm weiter. Beim ersten Schleifen-Übergang fuhr der Crossfader sieben Mal hin und her.
  • Die Zurückhaltung zählte das Einrichten mit. Nach vier Zeilen set meldete die Anlage einen Übergang, den niemand gefahren hatte.
  • set master.room "" war über MCP nicht erreichbar — dort verlangt musik_set mindestens ein Zeichen. Ein Weg, den nur der Socket kennt, ist für ein Team von Agenten keiner.
  • Beim Start über --a erreichte die Gliederung nie das Deck, und list deck1.beats_to_outro schrieb f64::MIN mit dreihundert Stellen aus.
  • Zwei Aufträge trugen dieselbe Plan-Nummer — sichtbar erst auf dem Bildschirmfoto der Regie-Spalte.

Funde, die Tests und Prüfstand aufgedeckt haben

  • Zwei Anker im Prüfstand des Kritikers waren falsch, bevor einer stimmte — und beide gaben Zahlen aus, die nach Messwerten aussahen. Der erste nahm den ersten Crossfader-Eintrag der Mitschrift; das war das Einrichten, Sekunde 0. Der zweite nahm die Bestellung des Griffs: 24,00 s, sauber, in jedem Set gleich — dazwischen liegt aber die Wartezeit auf die Phrase. Dem Kritiker wären vier Sekunden Verspätung angeschrieben worden, die in Wahrheit sein Vorsprung waren.
  • Der Prüfstand hat zuerst sich selbst gemessen. Sein erster Durchgang lief so viel schneller als die Wiedergabe, dass der Ring des Mitschnitts überlief: 1,2 von 2,1 Millionen Frames verworfen, mehr als die Hälfte. Die Datei war lesbar, die Mitschrift passte zu ihr, die Zahlen sahen aus wie Messwerte. Jetzt läuft die Schleife am Zügel, und ein Set mit auch nur einem verworfenen Frame wird gemeldet statt gemessen.
  • Sechs Entwürfe einer Schwelle in der Gliederung sind am Material gescheitert, jeder mit seiner Begründung im Code. Zuletzt der rangbasierte Drop (siehe oben); davor: ein Neuheits-Kern über zwei Phrasen, der zweiphrasige Abschnitte verschmierte; eine Schwelle aus Median und Streuung, die sich an ihrer eigenen bimodalen Verteilung überdeckte; eine z-Wert-Normierung, die aus Rauschen Struktur machte; und „Break heißt Bass weg", falsch gemessen, weil ein Kick allein reichlich Energie unter 160 Hz bringt.
  • Ein Prüfstein war selbst untauglich. Als „gleichförmiges Material" stand zuerst ein reiner Sinus da, dessen spektraler Fluss über das Stück um 45 % driftet. Beim Wächter gegen die falsche Benennung wiederholte sich das: Bei exakt gleich lauten Phrasen hält die Prüfung gerade noch, und der erste Anlauf lief daran vorbei.
  • Jedes Deck mit zugezogenem Fader stand still. Der Mixer übersprang stille Kanäle und dabei auch die Quelle.
  • Ein stehendes Deck meldete die alte Position.
  • Der Verlierer eines Griffs erfuhr nichts — gefunden beim Hinsehen für das Team.
  • master.arc nimmt Text an, aber nicht jeden Text. Der Wächter über alle schreibbaren Controls schrieb „Probe" hinein und bekam zu Recht einen Fehler.
  • Ein stehendes Deck bekam in der Mitschrift eine Phrasenlage.
  • Ein Wächter war Dekoration, bis ein Test mit zwei Leerzeichen ihn wirklich rot werden ließ.
  • beats_left war durch das Tempo geteilt — schnelleres Abspielen lässt weniger Zeit übrig, nicht weniger Beats.
  • Tempo-Erkennung, Grobsuche. Ein Kammfilter erkannte 128 BPM als 85. Jetzt Autokorrelation grob, Kamm nur fein. Oktavfehler: 174 BPM als 87 erkannt. Die Güteschwelle war an Klick-Tracks geeicht, die zwischen den Klicks still sind — dasselbe Muster bei der Tonart, dreimal.
  • Der EQ war zweimal falsch, bevor Linkwitz-Riley 4. Ordnung es löste.
  • Der Mitschnitt verlor seinen Anfang. Ein Deck hätte den Grid-Marker gelöscht. Die Zoom-Wellenform malte Musik, die es nicht gibt.

Bekannt offen

  • Phase 1 ist nicht abgenommen — es fehlt der Treiberschritt auf echter Hardware.
  • Die Analyse ist nie gegen echte Musik mit bekannter Wahrheit gelaufen. Gebautes Material findet Fehler — der letzte kostete zehn von 23 Namen —, ersetzt aber kein Ohr. Das Werkzeug steht (musik-material, musik-pruefstand, wahrheit-vorlage.txt); es fehlen die Tracks.
  • Eine Blende über zwei Phrasen sieht der Kritiker nicht. Über 64 Beats reißt seine Schwelle nie, weil sein Rückblick von 16 Sekunden dann selbst in der Blende liegt. Behoben ist das nicht mit einer größeren Zahl.
  • Ein Tempo je Track. Springt das Tempo mitten im Stück, stimmen die Phrasengrenzen danach nicht mehr, und die Gliederung findet ab dort nichts: gemessen drei von sechs Abschnitten, alle drei vor dem Sprung. Ein zweites Grid wäre eine eigene Schicht.
  • Es haben noch nie zwei unabhängige Modelle gleichzeitig aufgelegt. Zwei Verbindungen an einem Pult sind gebaut und geprüft, bis hin zum echten Socket — aber beide Seiten waren derselbe Kopf.
  • Die Signale haben noch keine Quelle. Der Raum steuert Bogen und Auswahl, aber gemeldet wird von Hand.
  • Stems streamen nicht von Platte. Vier Spuren kosten das Fünffache eines Tracks — rund 500 MB je Deck. Für zwei Decks geht das, für die vier des Plans nicht.
  • Die Track-Energie misst keine Lautstärke. Bezugspunkt bleibt der lauteste Abschnitt dieses Stücks; herauskommt, wie viel seiner Länge ein Track auf seinem eigenen Höhepunkt verbringt.
  • Unter 70 BPM meldet der Detektor falsch statt gar nichts. Nachgemessen an gebautem Material (N1): 66 BPM bekommen ein Grid mit 71,51, 68 eines mit 69,88 — deutlich, weil die beste Verschiebung innerhalb des abgeschnittenen Fensters sauber herausragt; sie sitzt nur am Rand. Das Verfahren selbst trägt bis etwa 46 BPM. Zwei Auswege wurden gebaut und beide zurückgenommen: Das Fenster zu öffnen lädt den Halbtempo-Fehler ein (schon ein Backbeat reicht, der Demo-Track mit 124 BPM bekam gar kein Grid mehr), und den Rand zu erkennen geht lokal nicht — echte 71 BPM sitzen näher am Rand als falsche 66. Der Weg führt über die Oktavwahl, nicht über die Schwelle.
  • Der Traktor-Import ist nie gegen eine echte collection.nml gelaufen.
  • Die Zeitstreckung ist nie gehört worden — nur gemessen.
  • Pitch Bend fehlt; MIDI steht aus, ebenso Reverb und FLAC für den Mitschnitt.
  • Zwei Decks sind fest verdrahtet; Windows hat keine Unix-Sockets.

Der vollständige Plan steht in docs/FAHRPLAN.md.

Lizenz

Alles im Repo ist MIT-verträglich, auch wenn das Werkzeug vorerst nicht weitergegeben wird. Grund ist der mögliche Anschluss an VibeMind (MIT, zur Veröffentlichung vorgesehen) — jede GPL-Abhängigkeit wäre eine Tür, die dahinter zufällt. Deshalb sind WSOLA, Tempo-, Tonart- und Strukturerkennung selbst gebaut, und deshalb wird Mixxx als Referenz gelesen, aber nicht eingebettet (docs/MIXXX.md).

Dokumentation

docs/PLAN.md ist der vollständige Bauplan, docs/DJ.md der Plan für einen AI-DJ, der nicht herzlos klingt, docs/FAHRPLAN.md alles, was darüber hinaus aussteht. Daneben: STEUERUNG.md, mcp/README.md, MIXXX.md, VIBEMIND.md, BAUSTEINE.md, APIS.md, AGENTEN.md, TRAKTOR-REFERENZ.md.

claude added 2 commits August 11, 2026 10:47
Groundwork so implementation can start from facts instead of guesses.

Traktor reference: deck modes (track/remix/stem), real-time stem
separation, pattern player, beatgrids, and the collection.nml format —
an existing Python parser makes Traktor library import a cheap early
feature.

API landscape: Suno has no public developer API as of August 2026, only
a curated partner program opened in July. Generation therefore goes
behind an adapter interface starting with a reachable provider, so Suno
becomes an added adapter later rather than a rewrite. Third-party
reverse-engineered Suno wrappers are documented as not recommended.

Building blocks: playback, time-stretching, beat/key detection and stem
separation options, with a license table up front — several audio
libraries are (A)GPL with commercial relicensing, which constrains the
stack before anything is chosen.

Agent concept: crowd-feedback loop with prior art, signal sources, role
sketch, and the timing constraint that breaks the naive design —
generation is too slow to close the loop live, so it plans ahead while
fast controls handle the immediate response. Camera and biometric
signals are flagged as GDPR-relevant.

README: adds a docs index, marks the Suno blocker, extends the roadmap
with dependencies, and records the open decisions. Adds .env.example.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Records the stack decision and implements phase 0 against it.

Native over browser because of the separate headphone cue output —
not latency. Serious DJing needs pre-listening on a second output,
which browsers do not offer cleanly. Rust over C++ because the whole
dependency set stays permissive (cpal Apache-2.0, symphonia MPL-2.0),
so the still-open question of whether this becomes commercial is not
pre-decided by the toolkit.

audio-core:
- track: decode mp3/flac/wav/aac/ogg to interleaved stereo, resample
  once at load so the callback carries no rate conversion
- stretch: WSOLA time-stretch, written here rather than pulling in
  Rubber Band, which is GPL. The ideal read position advances
  undisturbed and the correlation search only corrects locally —
  accumulating the found offset would drift the timeline
- deck: control via atomics only, so the audio path has no lock and
  no allocation
- player: cpal output, generic over the device sample format

musik-cli drives one deck from the shell: play/pause, tempo, keylock,
seek.

Verified by 11 tests: keylock holds pitch at 0.92x, 1.06x and 1.20x;
varispeed shifts pitch with tempo as it should; the timeline does not
drift; decode, mono-to-stereo and resampling preserve length and
pitch. Sound on real hardware is not verified — no audio device here.

Adds CI running fmt, clippy -D warnings and tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
@Flissel Flissel changed the title docs: Traktor-Referenz, API-Landschaft und Agenten-Konzept Phase 0: Recherche-Grundlage und nativer Audio-Kern mit einem Deck Aug 11, 2026
claude added 6 commits August 11, 2026 11:09
Closes the second open question. The practical effect is larger than
it looks: (A)GPL libraries are now available, which removes the main
constraint the audio ecosystem imposes.

The reason is narrower than "non-commercial" and worth stating
precisely — copyleft triggers on distribution, not on use. A tool that
stays on one machine creates no obligations at all. Two caveats now
carry the warnings instead:

- Distributing the tool later, even for free, would require releasing
  the project's own code under GPL.
- AGPL (Essentia) triggers on network use, not just distribution,
  which matters if the agent layer ever runs as a service.

Sample side: CC BY-NC material becomes usable, a large part of
Freesound. The attribution requirement survives, so the library schema
needs license and author fields before any import — that cannot be
reconstructed afterwards.

Adjusts the passages that assumed the question was still open: the
Rust choice, the self-written stretcher (Rubber Band is now an option,
worth taking only once someone has heard both), and the reason to
avoid reverse-engineered Suno wrappers — that risk was never licensing
but a banned account.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
The project is used privately and not distributed. In that state the
GPL dependencies create no obligations at all, so the two caveats
recorded earlier are demoted to notes for if that changes, and the
absence of a LICENSE file is now a stated choice rather than an
oversight.

Practical effect: library choice is unconstrained, including Rubber
Band.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Turns the coarse roadmap into an engineering plan: crate layout,
threading model, signal chain, phases with acceptance criteria, and
risks.

Three decisions shape everything else and are called out as such:

- The headphone cue output forces master and cue onto one device with
  four outputs. Two devices means two clocks, and the drift would need
  async resampling in exactly the path that is kept free of it. This is
  a hardware question that has to be settled before phase 1, not during.
- Atomics no longer suffice once tracks are swapped on a deck. A
  lock-free SPSC queue carries commands in, and a second one carries the
  old Arc back out — dropping a decoded track inside the callback frees
  a hundred megabytes and stalls the audio thread.
- A beatgrid is an anchor plus BPM, optionally grid markers, and sync
  needs phase as well as tempo. Matching BPM with the wrong phase
  sounds just as wrong as the wrong tempo.

Also fixes two things that are easy to build backwards: mixer FX belong
post-fader so closing the fader fades the effect out instead of cutting
it, and the cue tap belongs before the crossfader so a faded-out channel
is still audible in the headphones.

Records that the library schema needs license and attribution columns
from the first migration, following from the CC BY requirement.

Recommends egui for the UI over a Tauri frontend — a DJ surface redraws
every frame anyway, and the UI/audio state coupling is too tight and too
frequent to route through IPC. Crate choices verified against crates.io
for existence and license.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Phase 2 of the build plan. Runs entirely without an audio device, so
it is verifiable end to end here.

Pipeline: spectral flux onset envelope, autocorrelation for the coarse
period, a descent to the fundamental, then a comb filter for the exact
period and phase. Each stage uses a different tool on purpose —
autocorrelation compares the signal against a single shift and tolerates
a small period error, while a comb filter sums over hundreds of pulses
and a half-frame error misaligns the fiftieth pulse by twenty-five. The
first draft used the comb for the coarse search and detected 128 BPM as
85; measuring the comb curve rather than reasoning about it is what
found that.

The octave ambiguity gets its own stage. Autocorrelation peaks at every
multiple of the beat period, so 174 BPM correlates exactly as strongly
at 87, and a prior centred near 120 picks the wrong one. The half period
is a valley rather than a peak, so halving while correlation survives
lands on the shortest period of the series. What remains is a genuine
ambiguity — 87 BPM hip-hop with hats on eighths reads as 174 — which is
why DJ software has a halve/double button.

Anchors are corrected in the time domain. Spectral flux fires when a
transient enters the analysis window, not when it sounds, leaving a
systematic lead of about 15 ms — four percent of a beat at 128 BPM,
enough to make sync useless. A search on a fine energy-rise envelope,
bounded to half a period so it cannot jump to a neighbouring beat, brings
that to 3 ms.

Results are stored keyed by a hash of the decoded audio rather than the
path, so renaming a folder does not discard the work and duplicate files
are analysed once. Quantised to i16 before hashing so tag edits do not
invalidate it.

Refusing to answer is a valid answer: a sustained tone yields no tempo
rather than a guessed one, gated on how far the autocorrelation peak
exceeds the mean. The threshold separates the two measured extremes but
has never been calibrated against a real library, and says so.

Adds musik-analyze to drive it, egui recorded as the UI decision, and 24
tests covering detection, the octave descent, anchor accuracy, peak
quantisation and the sidecar cache.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
VibeMind exposes tools over MCP (FastMCP stdio servers, read tools free
and write tools behind approval) and groups domains under spaces/. That
is the seam: the Rust core stays the audio engine and gains a thin MCP
surface, so the Python/Rust split is a process boundary rather than a
problem.

Two findings that change decisions here.

The license relief recorded earlier does not survive this path.
VibeMind is MIT and headed for publication, and publication is
distribution — so GPL dependencies would once again constrain the whole
project. Everything currently vendored is MIT-compatible, which is a
side effect of writing the stretcher and the tempo detection here rather
than taking Rubber Band or aubio. That was a workaround for an open
question at the time and now happens to hold the door open. Staying
permissive is the cheaper default while this is undecided.

The agent layer sketched in AGENTEN.md largely duplicates Brain and
OpenFang. Delivering tools here and running the agents there removes an
entire stage of work; the role sketch stays valid as domain design
regardless of where it executes.

Also notes that write-tool approval and live performance are in tension
— nobody can confirm a dialog mid-mix, so time-critical calls need a
grant covering the set rather than each action, which is the safety-side
view of the open automatic-versus-assistant question.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Two passages still claimed GPL libraries were freely available, which
holds for personal use but not for the VibeMind path. Both now carry
the caveat and point at the detailed assessment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
@Flissel Flissel changed the title Phase 0: Recherche-Grundlage und nativer Audio-Kern mit einem Deck Phase 0 und 2: Fundament, nativer Audio-Kern und Analyse-Pipeline Aug 11, 2026
Phase 1. The mixer knows no decks, only sources that yield stereo — so
an AUX input is a channel like any other, and the generation layer can
attach at the same seam later.

Signal chain per channel: trim, three-band EQ, filter, then a split
into the cue tap and the fader/crossfader path. The cue tap sits before
the *fader*, not merely before the crossfader: a track is prepared in
the headphones while its fader is down, and a tap behind it would go
silent exactly when it is needed. It sits after EQ and filter because
tone changes must be audible before they reach the room.

AUX carries two differences from a deck. It defaults to Thru so a
microphone does not vanish when someone moves the crossfader, and it
reads through a lock-free ring buffer because capture and playback run
in separate callbacks with separate clocks — a late writer yields
silence and a counted underrun rather than a stall.

The three-band EQ took two wrong turns worth recording, both caught by
the neutral-passthrough test:

- Low plus high of a state-variable filter does not sum to the input;
  the k·bp term is missing. At unity gains the EQ was a notch around the
  crossover, and a 220 Hz tone lost over 80 percent of its level.
- Forming the remainder subtractively is exact in time but not in
  magnitude, because the low band is phase-shifted. At 60 Hz the shift
  already reaches ~50°, so bass kill left 0.84 of the bass standing, and
  on the crossover frequency the output exceeded the input by half.

Linkwitz-Riley of fourth order is the construction that actually holds:
both branches meet in phase at -6 dB and sum back to unity.

Adds musik-mix, which renders a transition to a WAV file offline —
tempo-matched with keylock, crossfade with bass swap. Same signal path
as the live engine, but verifiable without an audio device, which is
the only way to judge anything in this environment.

50 engine tests: filter responses, EQ band isolation and passthrough,
crossfader curves and Thru immunity, limiter ceiling and recovery, AUX
underrun and overrun accounting, and the mixer routing itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
@Flissel Flissel changed the title Phase 0 und 2: Fundament, nativer Audio-Kern und Analyse-Pipeline Phasen 0–2: Fundament, Audio-Kern, Analyse und Mixer mit AUX Aug 11, 2026
claude added 7 commits August 11, 2026 20:53
Four more phases. Everything here is verifiable without an audio device,
which is why it could be built ahead of the hardware.

Sync couples tempo *and* phase. Matching BPM alone is the half that
looks finished and isn't: two decks at exactly 128 BPM whose beats sit
an eighth apart sound as wrong as two at different tempos. A beatgrid
therefore lives on the deck as anchor plus BPM, held in atomics rather
than behind a lock because the audio thread reads it. The correction
jumps rather than eases the phase in — audible when large, but exact,
and it always takes the shorter way around the beat. Once effective
tempos match, phase stays locked on its own; no continuous regulation is
needed. Covered by the phase 4 acceptance criterion: five minutes of
rendering leaves the decks under 5 ms apart.

Loops wrap at the loop boundary, not at the end of the callback block.
Wrapping at the block would stretch each pass by up to one buffer, which
is audible as an uneven bar. Beat loops snap the start to the grid,
otherwise every pass begins mid-beat and stumbles.

The library stores positions in milliseconds rather than frames. It is
the interchange layer between analysis, Traktor import and deck, and a
frame count without its sample rate is ambiguous — a resampled track
would carry different numbers for the same instant. License and
attribution columns are in the first migration, not a later one, because
CC BY requires naming even non-commercially and the provenance of a
thousand samples cannot be reconstructed afterwards.

Upserts preserve existing values where the incoming record is empty. A
folder rescan knows only paths and tags; without that rule it would
erase the analysis, which is the expensive part.

The Traktor importer reads tempo, grid anchor and hot cues from
collection.nml. Field meanings come from public descriptions of an
undocumented format and are tested only against hand-written samples —
cue type mapping, key numbering and Windows volume handling are flagged
as needing a check against a real collection.

Adds musik-lib to scan folders, import from Traktor, and search by text
or mixable tempo. 137 tests total.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Dichtes Material wurde abgewiesen, obwohl das Tempo richtig erkannt war.
Ein Loop aus Kick, Bass und Hi-Hats kam auf eine Deutlichkeit von 2,06 und
blieb an der Schwelle von 3,0 hängen — die Grobsuche hatte 127,8 BPM längst
gefunden.

Ursache ist das Maß, nicht die Schwelle. Spitze durch Mittelwert war an
Klick-Tracks kalibriert, und die sind zwischen den Klicks still: die
Autokorrelation liegt bei fast jeder Verschiebung nahe null, das Verhältnis
wird riesig. Echte Musik hat durchgehend Energie, hebt damit den Sockel über
den ganzen Suchbereich an und drückt das Verhältnis gegen 1 — auch bei
schnurgeradem Beat.

Der z-Wert misst stattdessen, ob eine Verschiebung aus dem Feld heraussticht.
Ein konstanter Sockel verschiebt Spitze und Mittel gleich weit und kürzt sich
heraus. Gemessen trennt er doppelt so deutlich: 3,46 gegen 1,71 statt 2,06
gegen 1,65.

Der neue Test legt Klicks auf einen Dauerton — genau der Fall, an dem das
alte Maß gescheitert ist.
Die Oberfläche darf den Mixer nicht direkt anfassen — sie läuft im
UI-Thread, der Mixer im Audio-Callback. Atomics reichen für Skalare wie
Position und Tempo, nicht für einen Kanalwechsel: ein neuer Track ist eine
`Box<dyn Source>`, und die kann man nicht atomar tauschen.

Also eine lock-freie SPSC-Schlange für Kommandos und ein zweiter Kanal
zurück für abgelöste Quellen. Der Rückkanal ist kein Beiwerk: würde der
Callback die alte Quelle einfach fallen lassen, liefe dort ein `free` über
mehrere Megabyte Samples — eine Allokator-Operation im Echtzeitpfad, also
genau das, was nicht passieren darf.

`drain_loads` nimmt deshalb keinen neuen Track an, solange der Rückkanal
voll ist. Damit ist bewiesen, dass die alte Quelle immer einen Platz findet,
und im Callback wird nie etwas freigegeben.

`Output` sucht ein Gerät und übergibt ihm den Mixer. Scheitert das Öffnen,
kommt der Runner **mit** dem Fehler zurück — sonst wäre der ganze Mixer
verloren, nur weil keine Soundkarte da ist, und die Oberfläche stünde still.
Bis hierher ließ sich alles nur über die Kommandozeile und über Tests
bedienen. Jetzt gibt es die Anordnung, die man vom Pult kennt: zwei Decks
oben, Mischpult in der Mitte, Plattenkiste unten.

Decks zeigen zwei Wellenformen — Übersicht zum Springen, Ausschnitt zum
Mixen — mit Beatgrid, Hot-Cue-Marken und Abspielkopf. Gezeichnet wird aus
den vorberechneten Spitzen der Analyse; über dreizehn Millionen Rohwerte je
Deck und Bild zu laufen wäre bei 60 Bildern pro Sekunde aussichtslos.

Der Mixer hat vier Kanalzüge samt AUX, jeder mit Trim, Dreiband-EQ,
DJ-Filter, Cue und Linefader, dazu Crossfader mit Kurve, Master, Kopfhörer
und Cue-Mix. Die Plattenkiste durchsucht die Sammlung und legt Treffer auf
ein Deck; „Mischbar zu A" filtert auf Tempo-Nachbarschaft.

Drei Dinge, die beim Bauen erst sichtbar wurden:

- Ein `Frame` erbt das Layout seines Elternteils. Im Mixer ist das
  waagerecht, also standen die Regler eines Kanalzuges nebeneinander statt
  übereinander — jeder Rahmen sagt jetzt ausdrücklich `vertical`.
- Die Zoom-Ansicht ist um die Abspielposition zentriert und ragt am
  Anfang eines Tracks nach links über ihn hinaus. Dort wiederholte der
  Zeichner die erste Spitze für jede Spalte: ein solider Balken, den es
  nicht gibt. Auch das Beatgrid hörte vor Frame 0 nicht auf.
- Die mitgelieferte Schrift hat keine Pfeilzeichen. „→ A" wurde zum leeren
  Kästchen, ebenso ◄ ► und ⟳.

Ohne Audiogerät startet die Oberfläche trotzdem: der Mixer läuft dann von
einem Taktgeber im Leerlauf. Sonst wäre sie in genau der Umgebung ein
Standbild, in der man sie am ehesten prüfen möchte.

Ein geladener Track startet gestoppt mit geschlossenem Fader. Ein Deck, das
beim Laden auf die Anlage geht, ist ein Unfall.
Mixxx ist die naheliegende Frage — zwanzig Jahre freie DJ-Software, die
alles kann, was hier noch drei Phasen entfernt ist. Die Antwort ist
zweigeteilt, und beide Hälften waren bisher nirgends festgehalten.

Lizenz: die `LICENSE` sagt GPLv2+, ein gebautes Binary ist aber faktisch
GPLv3+, weil libKeyFinder (GPLv3) mitgelinkt wird. Für uns egal — Copyleft
bleibt Copyleft, und Mixxx landet damit in derselben Schublade wie Rubber
Band und aubio: nutzbar, solange nichts weitergegeben wird, ausgeschlossen,
sobald der VibeMind-Anschluss ernst wird.

Übernehmbar ist trotzdem einiges, weil Urheberrecht die Ausdrucksform
schützt und nicht die Idee: das Control-System als Vorbild für die
MCP-Schnittstelle, die zwei Beatgrid-Stufen (unser Anker+BPM ist die
konstante — variables Grid ist der benannte Aufrüstpfad), das Streaming von
Platte, und vor allem das Mapping-Verzeichnis als Dokumentation der
Geräte-Landschaft für Phase 10. Welche CC-Nummer ein Jogwheel sendet, ist
eine Tatsache über die Hardware; die Mapping-Datei ist ein Werk.

Was Mixxx nicht löst und was dieses Projekt eigentlich beiträgt: Steuerung
von außen. Mixxx spricht OSC nur ausgehend, das Control-System ist
prozessintern.

Und der ehrliche Gegenpunkt, der ins Dokument gehört statt weggelassen zu
werden: für rein persönlichen Gebrauch entstehen aus Mixxx' GPL null
Pflichten. Wer heute Abend auflegen will, nimmt Mixxx. Der Eigenbau
rechtfertigt sich über genau zwei Ziele — VibeMind und Agentensteuerung.
Fallen die weg, fällt der Grund weg.
Bis hierher lag die Bedienung an drei Stellen: Transport in den Atomics des
DeckState, der Mixer in der Kommandoschlange, und die Reglerstellungen als
Spiegel in der Oberfläche. Wer von außen steuern wollte, hätte alle drei
anfassen müssen — und die Oberfläche war die einzige Stelle, die wusste, wo
ein Fader gerade steht.

Das neue Crate `control` dreht das um. Jedes bedienbare Ding hat einen Namen
(`deck1.play`, `channel2.eq_low`, `master.crossfader`), das Pult besitzt den
Spiegel und schickt jede Änderung selbst in die Schlange. Die Oberfläche wird
damit zu einem von mehreren Bedienern, gleichberechtigt mit einem Skript am
Socket.

Die Idee stammt von Mixxx — dort laufen Tastatur, MIDI, HID und Oberfläche
über dieselben benannten Controls. Sie ist frei: geschützt ist die
Ausdrucksform, nicht der Entwurf. Drei Dinge sind bewusst anders:

Der Steuerraum beschreibt sich zur Laufzeit selbst. `list` liefert zu jedem
Control Typ, Bereich, Einheit, Schreibbarkeit und Bedeutung. Bei Mixxx steht
das im Handbuch, und ein laufendes Programm lässt sich nicht fragen. Der
Gewinn ist doppelt: MCP-Werkzeugbeschreibungen lassen sich daraus erzeugen
statt doppelt pflegen, und die Schieberegler im Mixer holen sich Bereich und
Tooltip aus demselben Katalog — ein neues Control ist ohne eine Zeile in der
Oberfläche bedienbar.

Werte sind typisiert. Bei Mixxx ist alles ein double, in das jeder Tippfehler
als gültiger Wert hineinpasst. Hier weist `set channel1.assign mitte` mit den
erlaubten Namen zurück. Zwei Ausnahmen von der Strenge sind Absicht: Werte
außerhalb des Bereichs werden begrenzt statt abgelehnt, weil ein MIDI-Regler
auf Anschlag das Maximum meint und keinen Fehler; und die Bestätigung nennt
den angekommenen Wert, nicht den gesendeten.

Er ist von außen erreichbar. Ein Unix-Socket, kein TCP-Port — wer
hineinschreibt, steuert die Anlage, und ein offener Port täte das für jeden
im selben Netz. Der Socket erbt die Rechte des Dateisystems und liegt in
XDG_RUNTIME_DIR, nicht in /tmp.

Echtzeit bleibt unangetastet: Das Pult schreibt nie in den Audio-Callback,
sondern in dieselbe lock-freie Schlange, und liest aus Atomics und Spiegel.
Der Mutex wird nur von Bedienern genommen.

Zwei Funde beim Bauen, beide von Tests aufgedeckt: Der Wortzerleger machte
aus Leerzeichenfolgen leere Felder, weil splitn nicht zusammenfasst. Und
Tempo liegt im Deck als f32 — 1.04 kommt als 1.0399999618530273 zurück. Der
Test forderte exakte Gleichheit und behauptete damit eine Genauigkeit, die
es dort nicht gibt.

Nachgewiesen an der laufenden Anwendung: zwei Decks von außen ineinander
gefahren, Deck B per `set deck2.tempo 1.032` auf Deck A gezogen (127,98 gegen
127,99 BPM). Screenshot in docs/bilder/fernsteuerung.png.
Der Steuerraum konnte Regler bewegen, aber nicht auflegen. Fünf Dinge fehlten,
und zusammen waren sie der Unterschied zwischen „ein Agent kann den Mixer
bedienen" und „ein Agent kann auflegen": laden, syncen, einen Hot Cue
auslösen, das Ende eines Tracks bemerken, die Sammlung durchsuchen.

Aktionen sind jetzt ein eigener Typ, kein Wert. Mixxx modelliert sie als
Control, in das man 1.0 schreibt (`cue_gotoandplay`), und das ist dieselbe
Schwäche wie beim durchgehenden double: Ein Auslöser hat keinen Zustand, den
man lesen könnte, und `get` darauf müsste etwas erfinden. Hier haben sie ein
eigenes Verb, und beide Richtungen weisen die Verwechslung zurück.

`sync` ist Tempo UND Phase in einem Befehl. Ohne das müsste ein Agent beide
BPM lesen, den Quotienten bilden und die Phase selbst rechnen — und die
vergisst man, weil das Ergebnis auch ohne sie plausibel aussieht.

`load` arbeitet in drei Stationen, weil Dekodieren und Analysieren Sekunden
dauern und das Pult unter einem Mutex liegt, an dem die Oberfläche hängt: Der
Auftrag wird angenommen, ein Arbeiter-Thread macht die langsame Arbeit ohne
jedes Schloss, der UI-Thread setzt das Ergebnis ein. Gemeldet wird deshalb
Annahme, nicht Erledigung; der Fortschritt steht in load_status. Ein
abgelehnter Auftrag lässt den Status unberührt, statt ein Laden vorzutäuschen,
das nie begonnen hat.

`sub` erspart das Pollen. Ehrlich gesagt vergleicht der Server alle 50 ms,
statt dass das Pult meldet — für Werte aus dem Audio-Thread wäre eine echte
Benachrichtigung auch sinnlos, die Position ändert sich mit jedem Sample. Der
Gewinn ist, dass der Bediener das nicht selbst baut und `finished` ohne
Dauerabfrage ankommt.

Damit `control` nicht von SQLite und Dekodierung abhängt, reicht der Betreiber
beides über eine Schnittstelle herein.

Nebeneffekt, der wichtiger ist als er klingt: Auch die Oberfläche geht jetzt
durch diesen Weg. Suchen und Laden hatten einen eigenen Pfad — zwei Wege zum
selben Ziel heißt zwei Stellen, an denen es schiefgehen kann, und die seltener
benutzte fällt seltener auf. Beim Umbau wäre das fast passiert: Die Library
wanderte in den Lader, und die Plattenkiste wäre still leer geblieben.

Ein Test hat sofort zugeschlagen, als `search_mixable` dazukam: Die Gegenprobe
geht jede aufgezählte Aktion durch und verlangt, dass sie auch verdrahtet ist.
Eine Aktion, die sich auflistet und dann „unbekanntes Control" sagt, wäre eine
Lüge.

Nachgewiesen an der laufenden Anwendung: ein kompletter Übergang allein über
den Socket — suchen, zwei Tracks laden, syncen (126,008 × 1,0157 = 127,986,
beide Decks gleich), Cue setzen und anspringen, Bass-Swap, Beatjump, aufs Ende
horchen. Screenshot in docs/bilder/agent-uebergang.png.
@Flissel Flissel changed the title Phasen 0–2: Fundament, Audio-Kern, Analyse und Mixer mit AUX DJ-Software: Engine, Analyse, Sammlung, Oberfläche und Fernsteuerung Aug 13, 2026
claude added 10 commits August 13, 2026 09:22
Phase 7. Vier statt vierzig: Delay, Gater, Flanger, Crusher. Der Filter sitzt
ohnehin schon fest im Kanalzug. Reverb fehlt bewusst — ein schlechter Hall ist
schlimmer als keiner, und ein guter ist ein eigenes Stück Arbeit.

Sie liegen hinter dem Fader, und das ist der eigentliche Entwurf: Zieht man
den Fader zu, während ein Delay klingt, soll die Fahne ausklingen statt
abzureißen. Genau dafür sitzen Mixer-FX auf jedem Gerät an dieser Stelle.

Damit das nicht nur auf dem Papier stimmt, musste die Stumm-Optimierung des
Mixers weichen: Sie übersprang jeden Kanal mit geschlossenem Fader und hätte
die Fahne im selben Moment abgeschnitten, in dem sie entsteht. Der Mixer fragt
jetzt nach, ob der Effekt noch klingt, bevor er überspringt. Zwei Tests halten
beides fest — dass die Fahne den zugezogenen Fader überlebt und dass ein
Kanal ohne Effekt trotzdem still bleibt.

Der Kopfhörer greift weiterhin davor ab. Man bereitet den nächsten Track im
Kopfhörer vor; ein Effekt, den man für die Anlage eingestellt hat, gehört dort
nicht hinein.

Zwei Dinge, die beim Bauen wichtig wurden und ohne die es billig klänge: Das
Gate läuft über eine Rampe von 1,5 ms, weil ein harter Sprung von 1 auf 0 ein
Klick ist. Und die Verzögerungsleitung liest zwischen zwei Frames, weil man
sonst beim Flanger jede Stufe der wandernden Verzögerung als Zwitschern hört.

Echtzeit: Alle Puffer werden beim Anlegen belegt. Die Delayzeit ist deshalb
nach oben begrenzt — vier Sekunden, bei 60 BPM ein ganzer Takt.

`fx_sync` rechnet Beats in Sekunden um, weil ein Delay, das nicht im Takt
steht, nach Fehler klingt. Die Umrechnung braucht das Beatgrid und liegt
deshalb im Steuerpult, nicht im Mixer, der von Decks nichts weiß. Ohne Deck
oder ohne Grid sagt es das, statt zu raten.

Und die Probe aufs Exempel für den Steuerraum: Die Oberfläche hat für die
Effekte keine einzige Zeile bekommen. Der Kanalzug holt sich seine Regler
jetzt aus dem Katalog — vier neue Einträge, und das FX-Feld stand da. Der
erste Versuch deckte prompt auf, dass damit auch die Reihenfolge aus dem
Katalog kommt: Der EQ stand verkehrtherum, weil dort `eq_low` vor `eq_high`
lag. Höhen oben, Bässe unten ist jetzt im Katalog festgehalten und begründet.

Ein Test war zuerst falsch gebaut: Er verglich zwei verschiedene Ausschnitte
derselben Schleife und maß damit das Signal statt den Effekt. Zwei identische
Engines, gleich weit gerendert, sind die richtige Probe.
Phase 9. Aufgenommen wird die Summe hinter dem Begrenzer — das, was auf die
Anlage geht, nicht das, was vorher da war.

Der Audio-Callback darf nicht auf die Platte schreiben. Ein write ist ein
Syscall, er kann Millisekunden dauern, und in dieser Zeit steht die
Wiedergabe — ein Aussetzer mitten im Set, und zwar genau dann, wenn man
mitschneidet. Also derselbe Weg wie beim AUX-Eingang, nur andersherum: ein
lock-freier Ringpuffer aus dem Audio-Thread heraus, ein eigener Thread, der
ihn leert und schreibt.

Kommt der Schreiber nicht hinterher, gehen Frames verloren — blockieren wäre
schlimmer. Verloren heißt aber nicht verschwiegen: Sie werden gezählt, sind
über `master.record_dropped` abfragbar, und `record_stop` sagt es von sich aus
dazu. Ein Mitschnitt mit Lücken, der aussieht wie einer ohne, wäre das
schlechteste Ergebnis von allen. Der Zähler ist mit einem winzigen Ring ohne
Leser getestet, statt auf eine überlastete Platte zu warten.

Der Kopf einer WAV-Datei enthält die Länge, die man beim Anlegen nicht kennt.
Also Platzhalter schreiben und am Ende zurückspringen — und deshalb prüft ein
Test, dass die Länge im Kopf wirklich zur Datei passt. Geschrieben wird
gerundet, nicht abgeschnitten: Abschneiden verschiebt jedes Sample in dieselbe
Richtung und klingt als leise Verzerrung mit.

Ein Fund beim Testen, der ein echtes Verhaltensproblem war: `create_dir_all`
hat als root klaglos vier Verzeichnisebenen unter / angelegt, weil im Test ein
absichtlich unsinniger Pfad stand. Jetzt wird höchstens eine Ebene angelegt —
`~/sets/heute.wav` in einem noch leeren Ordner soll gehen, ein Tippfehler soll
keinen Verzeichnisbaum hinterlassen.

Nachgewiesen an der laufenden Anwendung: über den Socket gestartet, 6,78 s
aufgenommen, null verworfene Frames — und die eigene Analyse liest die Datei
mit 128,02 BPM zurück, dem Tempo des Quelltracks.
CI hat gefangen, was hier durchgerutscht ist. Der Schreiber-Thread leerte den
Ringpuffer, sobald der Start-Befehl bei ihm ankam — um Reste eines früheren
Mitschnitts loszuwerden.

Der Aufnehmende schreibt aber schon in den Ring, sobald `starten` zurück ist,
und der Schreiber bekommt den Befehl erst, wenn er das nächste Mal an der
Reihe ist. Auf einem ausgelasteten Rechner liegen dann bereits Samples im
Ring, und das Leeren warf genau den Anfang des Mitschnitts weg. Kamen Start
und Stopp dicht hintereinander, blieb von der Aufnahme nur der WAV-Kopf übrig
— genau das ist auf dem CI-Läufer passiert.

Nötig war das Leeren an dieser Stelle ohnehin nicht: Nach dem Schließen ist
der Ring leer, und außerhalb einer laufenden Aufnahme legt niemand etwas
hinein. Also wird jetzt nach dem Schließen geleert statt vor dem Öffnen, und
die Invariante steht als Kommentar dabei.

Zwei Tests dazu. `ein_zweiter_mitschnitt_erbt_nichts_vom_ersten` fängt den
Fehler zuverlässig, weil der Schreiber-Thread nach der ersten Aufnahme
schläft und Start, Aufnehmen und Stopp deshalb gebündelt bei ihm ankommen —
gegengeprüft, indem der Fehler kurz wieder eingebaut wurde.
`ein_sofortiger_stopp_verliert_den_anfang_nicht` beschreibt denselben Fall
direkt, hängt aber am Zeitverhalten und schlägt nicht immer an.
…ogue

Der Anschluss, den docs/VIBEMIND.md seit dem ersten Tag vorsieht und den
STEUERUNG.md zweimal angekündigt hat. Ein MCP-Server, der das Zeilenprotokoll
in Werkzeuge übersetzt.

Sechs Werkzeuge statt zweihundert. Ein Werkzeug je Control wäre die
naheliegende Übersetzung und die schlechtere: Der Steuerraum hat über
zweihundert Einträge, und ein Agent, der sie alle als Werkzeuge sieht, findet
keins davon. `musik_set` und `musik_do` erreichen alles; `musik_status` und
`musik_search` sparen die Wege, die man sonst am häufigsten doppelt ginge.

Der Kern: Die Beschreibungen von `musik_set` und `musik_do` werden beim Start
aus dem laufenden Programm erzeugt, nicht von Hand gepflegt. Was ein Agent
liest, ist dasselbe, was im Katalog steht und in der Oberfläche als Tooltip
erscheint. Ein neues Control erscheint dort ohne eine Zeile Python. Läuft die
Anwendung gerade nicht, bleibt die Beschreibung allgemein und verweist auf
`musik_list_controls`.

Genau dabei fiel ein Loch in der Selbstbeschreibung auf: `list` gab das
Argument einer Aktion nicht mit. Es stand im Katalog, verließ aber den Prozess
nie — die erzeugte Beschreibung versprach „erwartetes Argument in Klammern"
und zeigte überall `(-)`. Jetzt steht es in der Bereichsspalte, und
`deck1.load` sagt von sich aus, dass es einen Pfad will.

Python neben einem Rust-Projekt, weil der Empfänger Python spricht: VibeMind
läuft auf FastMCP. Die Brücke redet nur über den Socket und weiß von Audio
nichts; der Rust-Kern bleibt unangetastet, und wer keinen Agenten braucht,
braucht den Ordner nicht.

Sicherheit: kein Netz, nur stdio und der Unix-Socket. Jedes Argument wird auf
Zeilenumbrüche geprüft — das Protokoll ist zeilenweise, ein Pfad mit `\n` wäre
sonst ein zweiter Befehl, den niemand geschickt hat. `musik_do` ist als
destruktiv gemeldet, weil `load` einen Track tauscht und `record` eine Datei
schreibt.

Geprüft über einen echten MCP-Client im selben Prozess — `tools/list` und
`tools/call`, nicht an der Schnittstelle vorbei: 16 Prüfungen, darunter die
erzeugten Beschreibungen, die Begrenzung von Werten, die Fehlerwege und die
abgewiesene Einschleusung. Ohne laufende Anwendung meldet der Test sich mit 77
ab, statt Grün zu behaupten, wo nichts geprüft wurde.
Nach dem Tempo die zweite Hälfte der Trackauswahl. Zwei Stücke im gleichen
Takt können gegeneinander klingen, wenn die Tonarten nicht zusammenpassen.

Selbst gebaut, weil libKeyFinder und der QM Key Detector — den auch Mixxx
mitlinkt — unter GPL stehen und das den Weg zu VibeMind (MIT) zugemacht
hätte. Verfahren: Chroma über eine FFT, Vergleich gegen die
Krumhansl-Kessler-Profile, Ausgabe zusätzlich als Camelot-Zahl.

Der Wert (Name, Camelot, Verwandtschaft) liegt in `audio-core`, damit
`library` und `control` ihn benutzen können, ohne den Analyseapparat
mitzuschleppen — dasselbe Argument, aus dem `Beatgrid` dort liegt.

Zwei Fehler, die erst die Demo-Oberfläche sichtbar gemacht hat:

- Das Analyseband reichte bis 65 Hz hinunter. Bei 8192 Punkten ist ein Bin
  5,4 Hz breit, ein Halbton bei C2 nur 3,9 Hz — unterhalb von etwa 200 Hz
  fielen mehrere Halbtöne in denselben Bin, ausgerechnet dort, wo Kick und
  Bass die meiste Energie haben. Der Kommentar behauptete das Gegenteil.
- Die Teiltöne eines Sägezahn-Basses wurden für eigene Töne gehalten, sodass
  ein einzelner Ton wie ein Akkord las. Jetzt werden sechs Teiltöne auf ihren
  Grundton zurückgerechnet (HPCP mit harmonic contribution, Gómez 2006).

Die Konfidenz misst jetzt die Güte der Anpassung statt den Vorsprung vor der
zweitbesten Deutung: Der Vorsprung lag bei Perkussion (0,062) über dem einer
echten Moll-Folge (0,088) und trennte nichts.

Bass und Drums ohne Akkorde bekommen bewusst **keine** Tonart. Dort steht das
Tongeschlecht nicht im Signal — der Sägezahn bringt seine eigene große Terz
mit —, und „A" statt „Am" ist auf dem Camelot-Rad 11B statt 8A und damit ein
Fehlgriff. Geprüft wird deshalb, ob die Terz überhaupt klingt. Beide Schwellen
sind an synthetischem Material gemessen und gegen eine echte Sammlung nie
geprüft, wie beim Tempo auch.

Angeschlossen:

- Sidecar-Format 2; alte Sidecars werden verworfen statt mit einem
  vorgetäuschten „keine Tonart gefunden" ergänzt
- `musik-lib scan` legt die Tonart ab, `musik-lib harmonic <tonart>` sucht
- `deckN.key` und `deckN.key_camelot` im Steuerraum, `master.search_harmonic`
- Camelot-Spalte in jeder Trefferzeile, in der Plattenkiste und über MCP
- Oberfläche: Tonart am Deck, KEY-Spalte, „Harmonisch zu A/B"
- Playlists sind über `master.playlists`/`master.playlist` und die Oberfläche
  erreichbar; in der Sammlung gab es sie längst, erreichbar waren sie nie

Die Demo-Tracks haben jetzt Akkorde über den Drums — sonst könnte die
Vorführung ohne eigene Sammlung die halbe Anzeige nicht zeigen. Sie stehen in
a-Moll (8A) und e-Moll (9A), also einem Paar, das sich mischen lässt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Fünf echte Tracks haben gezeigt, dass die Schwelle zu streng war: Sie wies
vier davon ab. Der Grund ist die Eichung an synthetischem Material —
selbstgebaute Akkordfolgen erreichen 0,92 bis 0,95, echte Aufnahmen kommen
über 0,82 kaum hinaus.

Dieselbe Falle wie beim Tempo, wo eine an Klick-Tracks geeichte Schwelle
echte Musik verwarf. Offenbar die Standardfalle bei allem, was hier an
Signalen gemessen wird: Synthetisches Material ist sauberer als alles, was
je aus einem Mikrofon kommt.

Gegengeprüft ohne Ground Truth über die Stabilität in 30-Sekunden-
Abschnitten — eine echte Tonart bleibt über den Track stehen, eine erfundene
springt. Die drei Tracks über 0,72 sind stabil (4/4, 5/7, 6/7), die beiden
darunter springen (4/6, 4/8). Die Schwelle liegt jetzt zwischen beiden
Gruppen.

Beim Nachmessen kam heraus, dass die bisherige Begründung falsch war:
Rauschen und Perkussion drücken die Güte kaum, weil sie alle zwölf
Halbtonklassen gleichmäßig anheben und eine Korrelation gegen so einen
Offset unempfindlich ist — selbst grobes Rauschen mit lauten Drums senkt
sie nur von 0,95 auf 0,90. Was sie drückt, ist fremde Harmonik.

Ein Test, der die Schwelle bewacht, ist nicht möglich und wurde deshalb
wieder entfernt: Um sie zu prüfen, bräuchte es synthetisches Material im
Bereich echter Musik, und selbst eine Tritonus-Modulation über ein Drittel
der Länge kommt nur auf 0,864. Ein Test, der das vortäuscht, wäre schlechter
als keiner. Bewacht wird stattdessen die Messtabelle im Modul.

Fünf Aufnahmen sind eine dünne Grundlage — der Abstand zwischen 0,71 und
0,77 ist schmal. Das steht so in der Dokumentation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Bisher lagen Hot Cues allein in den Atomics des Decks. Acht Cues gesetzt,
Track neu geladen — weg. Dabei hatte die Sammlung die Tabelle die ganze Zeit,
und der Traktor-Import füllte sie sogar: Diese Cues erreichten nur nie ein
Deck, weil niemand sie las.

Jetzt in beide Richtungen. Beim Setzen oder Löschen schreibt das Pult sofort
zurück, nicht beim Beenden — ein Cue, der nur im Speicher steht, ist nach
einem Absturz weg, und abgestürzt wird beim Auflegen. Acht Zeilen SQLite
dürfen den Aufrufer kurz aufhalten, anders als das Dekodieren beim Laden.

Beim Laden kommen sie zurück aufs Deck, und ein Beatgrid aus der Sammlung
schlägt dabei die frische Analyse: Was dort steht, kann aus Traktor stammen
oder von Hand korrigiert sein, und beides weiß mehr als ein Detektor.

Zwei Fallen, die beim Bauen auffielen:

- `replace_cues` löscht *alle* Marker eines Tracks. In derselben Tabelle liegt
  der Grid-Anker aus dem Traktor-Import, und ein Deck kennt nur seine acht
  Tasten — der erste gesetzte Cue hätte den Anker mitgenommen. Dafür gibt es
  jetzt `replace_hot_cues`, das nur Zeilen mit Hotcue-Nummer anfasst.
- Der Startpfad `--a datei.mp3` lädt an der Sammlung vorbei. Mit gesetztem
  Deck-Pfad, aber ohne gelesene Cues hätte er beim ersten Cue die
  gespeicherten überschrieben. Die Library wird deshalb vor der Deck-Schleife
  geöffnet, und beide Ladewege wenden dieselbe Regel an.

Ein fehlgeschlagenes Speichern verschluckt den Cue nicht: Er steht am Deck,
der Grund in `deckN.load_status`.

Geprüft über den ganzen Weg — Cues über den Socket gesetzt, Anwendung neu
gestartet, Cues kommen zurück; der Grid-Marker steht unberührt daneben, und
ein abweichendes Grid aus der Sammlung (100 BPM) schlägt die Analyse (128).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Der Detektor liegt bei sperrigem Material daneben — bei einem der fünf
Testtracks kam 180 BPM heraus, mit einer Konfidenz von 0,05. Bisher war so
ein Track damit unbrauchbar: `bpm_grid` war nur lesbar, es gab kein Tap und
kein Verschieben des Ankers. In docs/PLAN.md steht seit der Recherche, dass
manuelle Grid-Korrektur nötig ist, egal wie gut der Detektor wird.

Vier Wege, alle über den Steuerraum und alle mit Rückschreiben:

- `bpm_grid` und `grid_anchor` sind schreibbar
- `grid_scale <faktor>` für den Oktavfehler, den häufigsten Fall; der Anker
  bleibt stehen, denn bei einer Halbierung lag jede zweite Eins richtig
- `grid_here` legt den Anker auf die Abspielposition

In der Oberfläche als GRID ÷2 ×2 HIER neben den Hot Cues.

`grid_here` nimmt die **Ziel**-Position, nicht die angezeigte. `seek_frames`
setzt nur einen Wunsch, den der Audio-Thread im nächsten Block ausführt; wer
springt und sofort „hier" sagt, meint die Stelle, auf die er gesprungen ist.
Aufgefallen ist das an einem Test, der die Anzeige las und 0 statt 12,5
bekam — im Betrieb wäre es ein seltener, schwer zu findender Griff daneben
gewesen. Dafür gibt es jetzt `DeckState::ziel_position`.

Beim Bauen fiel außerdem auf, dass die Cue-Knöpfe der Oberfläche direkt ins
Deck schrieben, am Pult vorbei — die seit dem letzten Commit gespeicherten
Cues wären also ausgerechnet auf dem Weg, den ein Mensch nimmt, nicht
angekommen. Jetzt gehen auch sie durch das Pult, wie Regler, Suche und Laden.

Ohne Grid wird nichts geraten: Anker setzen und skalieren melden sich als
gescheitert. Nur `bpm_grid` nimmt auch aus dem Nichts einen Wert an, sonst
käme man aus einem fehlenden Grid nie wieder heraus.

Geprüft über den ganzen Weg: 128,02 halbiert auf 64,01, Anker auf 2,5 s
gelegt, Anwendung neu gestartet, beides kommt zurück — während die Analyse
allein weiterhin 128,02 und 0,474 s erkennt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
…ehle

Gedacht für ein Team von Agenten, das die Anlage über Chat bedient. Fünf
Fragen führten alle auf dieselbe Lücke: Der Steuerraum kannte keine Zeit.

Ein Übergang ist keine Folge von Reglerstellungen, sondern eine Bewegung über
Takte. Wer das von außen nachbaut, muss in einer engen Schleife `beat_phase`
pollen und dazwischen schlafen — über eine Leitung, deren Timing dem
Scheduler ausgeliefert ist. Das eiert hörbar, und der Agent ist die ganze
Zeit blockiert.

Vier Verben:

    ramp <control> <ziel> <beats> [deck]
    in <beats> <befehl>
    plan
    cancel [id]

Gerechnet wird in Beats, nicht in Sekunden: Ein Plan in Sekunden geht schief,
sobald jemand am Tempo dreht. Steht das Deck, steht der Plan. Den Taktgeber
erbt ein Kanalzug von seinem Deck, die Summe nimmt das erste Deck mit Grid.

**Eine Rampe gibt auf, sobald jemand anders denselben Regler anfasst.** Ohne
das wäre die Automatik stärker als der Griff daneben — man zieht den Fader zu,
und einen Wimpernschlag später steht er wieder offen. Geprüft wird über den
Wert selbst statt über eine Kennung; das trifft einen Menschen an der
Oberfläche wie einen zweiten Agenten. Damit ist `plan` zugleich das
gemeinsame Blatt: Wer mitliest, sieht, was die anderen vorhaben.

Dabei kam ein schwerer Fehler in der Engine ans Licht: Der Mixer übersprang
stille Kanäle, um EQ und Filter zu sparen — und übersprang dabei auch die
Quelle. **Jedes Deck mit zugezogenem Fader stand still.** Der Track, den man
einsetzen will, kam nicht von der Stelle; zwei gesyncte Decks liefen
auseinander, sobald eines weggeblendet war; `finished` kam nie, womit jeder
wartende Agent ins Leere lief. Beatmatching wäre damit wirkungslos gewesen.
Jetzt läuft die Quelle immer weiter, nur die Filterkette bleibt aus — der
Sparbetrag steckte ohnehin dort. Aufgefallen ist es, weil ein Zeitplan-Test
keine Beats zu sehen bekam.

Der Prüfstand hat deshalb jetzt echte Abspieler statt `SilentSource`: Ohne
laufende Position gibt es keine Beats, und ohne Beats ließe sich der Zeitplan
nicht prüfen. Gerechnet wird weiterhin nur, wenn ein Test den Runner dreht.

Der Takt liegt bei 5 ms, rund ein Prozent eines Beats bei 128 BPM. Für eine
Blende unhörbar, für einen harten Schnitt gerade noch vertretbar; sample-genau
ist er ausdrücklich nicht.

Am laufenden System geprüft: Bass raus über 8 Beats, danach Deck B rein über
16 und der Crossfader mit — drei Zeilen, dann legt der Agent auf, und die
Werte wandern von selbst.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Die vier Verben des Zeitplans — ramp, in, plan, cancel — gab es nur über das
Zeilenprotokoll. Ein Agent, der über MCP spricht, konnte Übergänge deshalb nur
nachbauen: musik_set in einer Schleife, dazwischen schlafen — über eine
Schnittstelle, deren Timing bei jedem Aufruf eine Modellantwort weit entfernt
ist. Das eiert hörbar, und der Agent kann in der Zeit nichts anderes tun.

- musik_ramp bewegt einen Regler über Beats, wahlweise erst nach einer
  Wartezeit (in_beats) und mit wählbarem Taktgeber-Deck
- musik_schedule legt eine Aktion oder einen Wert auf einen späteren Beat
- musik_cancel nimmt Vorgemerktes zurück
- musik_status trägt den Plan mit: Wer eine Momentaufnahme nimmt, bevor er
  zugreift, sieht die Absichten der anderen, statt sie aus Reglerbewegungen zu
  erraten

Der Plan ist gemeinsam, und danach richten sich zwei Entscheidungen. Er hängt
an musik_status statt an einem eigenen Werkzeug, damit ihn sieht, wer ohnehin
hinschaut. Und musik_cancel verlangt für „alles" einen eigenen Schalter — eine
vergessene Nummer soll nicht die Arbeit der anderen leeren.

Ein halber Befehl wird abgewiesen statt halb ausgeführt: musik_schedule
verlangt entweder eine Aktion oder ein Control mit Wert, nie beides und nie
keins. Sonst sähe eine Antwort aus, als wäre etwas vorgemerkt.

Geprüft über einen echten MCP-Client gegen eine laufende Anwendung, 32
Prüfungen grün. Gegengeprüft, indem in_beats stillschweigend fallen gelassen
wurde — dann steht der verzögerte Auftrag als sofortige Rampe im Plan, und der
Test wird rot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
claude and others added 30 commits August 17, 2026 11:36
Der erste vollständig automatisch gefahrene Übergang funktionierte technisch und
klang trotzdem seelenlos. Das Dokument hält fest, warum — und in welcher
Reihenfolge sich das ändern lässt. Die Reihenfolge ist dabei das Argument, nicht
die Liste.

Drei Befunde aus dem Durchlauf: Der Übergang war eine Liste aus acht
unabhängigen Schwellen, keine Geste. Die Anlage kennt Tempo und Tonart, aber
nicht Intro, Break, Drop und Outro — also nicht die Stellen, an denen ein
Übergang überhaupt sitzen darf. Und sie hört sich nie selbst, womit jede Regel
eine Behauptung bleibt.

Daraus zwei Sätze, an denen alles Weitere hängt: Jede Entscheidung trägt ihren
Grund, und jedes Set wird hinterher gemessen.

Die Schichten stehen bewusst nicht nach Klanggewinn sortiert. Der Phrasenbezug
kommt zuerst, weil er ein Verb kostet und jeden Mix betrifft; der Kritiker über
den Mitschnitt danach, weil ohne ihn jede spätere Verbesserung ein Gefühl
bleibt. Die Strukturanalyse verspricht mehr, kommt aber erst dritte — sie lässt
sich ohne Kritiker nicht belegen.

Was nicht Software ist, steht am Ende: Material, das Interface mit vier
Ausgängen, und Geschmack. Der Kritiker soll dem Menschen die Handwerksfehler
abnehmen, nicht das Urteil.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
S0 aus docs/DJ.md. Die Anlage konnte „in 32 Beats" und „wenn noch 32 Beats übrig
sind", aber nicht „auf der nächsten Eins" — und das ist die Einheit, in der ein
Übergang gedacht wird. `when beats_to_phrase < 1` trifft irgendwo im letzten
Beat, nicht auf den Schlag.

`in` nimmt jetzt statt einer Zahl auch `phrase` und `phrase+n`:

    in phrase do deck2.sync
    in phrase+16 ramp master.crossfader 1.0 32

`ramp` muss dafür nichts von Phrasen wissen. `in` trägt eine beliebige
Protokollzeile, also setzt `in phrase ramp …` die Bewegung auf die Grenze — ein
zweiter Phrasenbegriff im Rampen-Verb wäre eine zweite Stelle zum
Auseinanderlaufen. Aus demselben Grund ist die Rechnung selbst aus `lies_deck`
herausgehoben: `beats_to_phrase` und `in phrase` beantworten dieselbe Frage und
teilen sich jetzt eine Methode.

Der Bezugspunkt ist dasselbe Deck, auf dessen Takten der Auftrag dann liegt —
das erste mit Beatgrid. Sonst zählte man die Beats des einen und die Phrase des
anderen. Ohne ein solches Deck wird abgewiesen statt stillschweigend auf null zu
fallen.

Über MCP als Schalter `ab_phrase` an `musik_schedule` und `musik_ramp`, mit dem
Hinweis in der Beschreibung, dass es fast immer das Richtige ist.

Der erste automatisch gefahrene Übergang hing an acht willkürlichen Zahlen und
klang danach. Damit hängt er an einem musikalischen Zeitpunkt.

339 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
…hrieben

S1 aus docs/DJ.md. Die Anlage schreibt seit Phase 9 einen Mitschnitt, und
niemand hat ihn je gelesen. Damit blieb jede Regel im Mixen eine Behauptung und
jede Verbesserung ein Gefühl.

`musik-kritik <mitschnitt.wav>` findet Übergänge über die Änderung des
Klangbilds — Chroma und nicht Spektrum, weil ein Trackwechsel die Harmonik
ändert und ein Filterschwenk nicht. Zu jedem Übergang: Dauer, Pegelverlauf über
die Blende, Tempo davor und danach.

Am eigenen Material geprüft, dessen Antworten ich kannte: ein Mix mit Übergang
ab Sekunde 40 über 16 Sekunden. Der Kritiker findet ihn, misst die Dauer exakt
und bestätigt, dass das Tempo hält.

Zwei Dinge kann er nicht, und beide standen zuerst fälschlich im Bericht:

Den Beginn einer langen Blende findet er nicht genau. Am Anfang ist der
eingehende Track per Konstruktion unhörbar — das ist, was eine Blende ausmacht.
Die erste Fassung meldete 51 statt 40 Sekunden; mit Rückverfolgung des Anstiegs
sind es 44. Die verbleibende Unschärfe steht jetzt im Bericht, statt verschwiegen
zu werden.

Und die Phrasenlage kann er gar nicht messen. Der Anker des Tempodetektors ist
der erste starke Schlag im Analysefenster, nicht der Anfang einer Phrase — „6,1
Beats neben der Eins" war gegen einen willkürlichen Nullpunkt gemessen. Die Zahl
stand schon im Bericht, bevor mir auffiel, dass sie keinen Boden hat. Dazu kommt,
dass ein Sekundenfenster bei 126 BPM zwei Beats breit ist, also gröber als die
Frage.

Beides steht jetzt als Befund im Bericht statt als Urteil, und `--phrase` ist
weg — eine Option, die nichts mehr tut, wäre eine Halbwahrheit.

Daraus folgt eine Planänderung, in DJ.md festgehalten: Das Plan-Protokoll gehört
zum Kritiker dazu. Der Mitschnitt sagt, was herauskam; der Plan, was gemeint war.
Erst zusammen ergeben sie ein Urteil über die Phrasenlage.

347 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Der Mitschnitt sagt, was klang. Er sagt nicht, wann jemand den Fader
angefasst hat, und genau das konnte der Kritiker deshalb nur schätzen: Am
Anfang einer langen Blende ist der eingehende Track per Konstruktion
unhörbar. Die Phrasenlage konnte er gar nicht messen, weil der Anker eines
nachträglich geschätzten Rasters irgendein starker Schlag ist und nicht die
Eins.

Beides weiß die Anlage im Moment des Geschehens. Sie schreibt es jetzt mit:
neben jedem Mitschnitt eine `.mitschrift` mit Frame, Beat jedes Decks, ob
das Deck lief, und der Zeile selbst — `>` herein, `<` hinaus. Drin ist, was
verändert; `get` und `list` blieben draußen, sonst geht die Bewegung im
Nachfragen unter. `musik-kritik` findet die Datei von selbst.

Am ersten Lauf danach, ein Übergang über 32 Beats:

  Beginn laut Klang   27,00 s (±4)
  Beginn laut Mitschrift  23,25 s, Beat 48,01 — 0,01 in die Phrase
  gefahren            32,0 von 32 bestellten Beats

Die Schätzung lag 3,7 s zu spät. Das war vorher eine Vermutung; mit
`--ohne-mitschrift` misst der Kritiker sie jetzt bei jedem Set nach.

Und ein Befund, den vorher niemand hätte finden können: Deck 2 setzte auf
seinem Beat -1 ein, fünfzehn Beats neben seiner eigenen Eins. Der ausgehende
Track lag auf der Phrase, der eingehende nicht — der Auftrag für S2.

Zwei Dinge fielen beim Bauen selbst auf und sind mit repariert: Ein
stehendes Deck bekam eine Phrasenlage, die sich las wie „es kam neben der
Eins herein", obwohl es überhaupt nicht hereinkam (jetzt `~` in der Zeile
und `phrasenlage() -> Option`). Und ein Wächter für „der Rest der Zeile
bleibt am Stück" war Dekoration, bis ein Test mit zwei Leerzeichen ihn
wirklich rot werden ließ.

376 Tests grün (vorher 347).
Die Anlage kannte Tempo, Tonart und Wellenform. Sie kannte nicht die
Stellen, an denen ein Übergang sitzen darf, und deshalb war „blende aus,
während das Outro läuft" nicht ausdrückbar — das Wort Outro gab es nicht.

Gerechnet wird auf dem vorhandenen Beatgrid. Je Phrase drei Zahlen: Pegel,
Bass (zwei Einpoler bei 160 Hz) und Onset-Dichte. Eine Grenze liegt dort,
wo sich das Klangbild über eine Phrasengrenze hinweg ändert. Die Namen
kommen aus Quantilen des Tracks selbst, nie aus absoluten Zahlen.

Am gebauten Material mit bekannter Gliederung — sechs Teile, Grenzen bei
Sekunde 15, 30, 45, 60, 75:

  0.5s  intro   Pegel 0.34  Bass 0.12  Dichte 0.43
 15.5s  aufbau  Pegel 0.53  Bass 0.42  Dichte 0.72
 30.5s  drop    Pegel 0.99  Bass 0.99  Dichte 0.98
 45.5s  break   Pegel 0.44  Bass 0.41  Dichte 0.56
 60.5s  drop    Pegel 0.98  Bass 0.98  Dichte 0.97
 75.5s  outro   Pegel 0.20  Bass 0.06  Dichte 0.25

Der Versatz von einer halben Sekunde ist der Anker des Beatgrids: Das
Phrasengitter hängt an der ersten Eins, nicht an Frame 0. Genau das macht
den Einstiegspunkt zu einem Downbeat statt zu Sekunde 0 — der Fehler, den
die Mitschrift beim ersten gemessenen Übergang aufgedeckt hat.

Vier Entwürfe sind unterwegs am Material gescheitert und stehen mit ihrer
Begründung im Code:

- Ein Kern aus zwei Phrasen je Seite verschmiert genau die Abschnitte, die
  zwei Phrasen lang sind. Mit einer je Seite trennen sich die Grenzen um
  das Fünfundzwanzigfache vom Rest, mit zweien nur noch um das Doppelte.
- Median plus Streuung als Schwelle fand gar nichts: Wenn jede zweite
  Phrase eine Grenze ist, sind die Ausschläge die halbe Verteilung und
  überdecken sich selbst.
- Der z-Wert als Normierung machte aus gleichförmigem Material Struktur —
  Rauschen, auf Streuung 1 gestreckt, sieht aus wie ein Wechsel.
- „Break heißt Bass weg" war falsch gemessen: Ein Kick allein bringt
  reichlich Energie unter 160 Hz. Der Pegel trennt das zuverlässiger.

Dazu ein Prüfstein, der selbst untauglich war: Als „gleichförmiges
Material" stand zuerst ein reiner Sinus da. Dessen spektraler Fluss driftet
über das Stück um 45 %, weil die Bin-Leckage gegen den Hop schwebt — eine
Forderung an den Detektor, die in Musik nicht vorkommt. Jetzt steht dort
ein durchlaufender Takt.

Belegt sind die Grenzen. Die Benennung ist es nicht: Ob „lauteste 25 % mit
viel Bass" auf echten Produktionen der Drop ist, weiß erst, wer es an
echten Produktionen nachmisst. Bis dahin stehen die Zahlen neben jedem
Namen im Bericht, damit man ihm widersprechen kann.

Sidecar-Format auf 3. 387 Tests grün (vorher 376).
Die Erkennung stand seit dem letzten Commit, blieb aber ein Bericht. Jetzt
kennt jedes Deck seine Gliederung, und damit wird die eigentliche Regel
sagbar:

    when deck1.beats_to_outro < 0  in phrase ramp master.crossfader 1 32

Vorher hieß dieselbe Absicht „wenn noch 40 Beats übrig sind" — eine Zahl,
die man je Track neu schätzt und meistens falsch.

Neu am Deck: `section` (intro, aufbau, drop, break, outro, teil),
`section_beats_left`, `beats_to_outro` (negativ, sobald es läuft),
`intro_beats`, `entry` und die Aktion `jump_entry`. `musik_status` trägt
alles mit; im Bericht steht eine Zeile: „Abschnitt **aufbau**, noch 12
Beats · Outro in 108 Beats".

`entry` ist der Einstiegspunkt — der erste Downbeat, nicht Sekunde 0. Genau
den Fehler hat die Mitschrift beim ersten gemessenen Übergang aufgedeckt:
Deck 2 setzte fünfzehn Beats neben seiner eigenen Eins ein, weil es bei
Frame 0 anfing. `do deckN.jump_entry` räumt das in einem Griff weg.

Die Werttypen liegen jetzt in `audio-core`, die Erkennung bleibt in
`analysis` — dieselbe Aufteilung wie bei der Tonart und aus demselben
Grund: `control` soll wissen, was ein Outro ist, ohne den halben
Analysestapel mitzuziehen. Dabei ist `PHRASE_BEATS` von zwei Definitionen
auf eine zusammengefallen; die Gliederung schneidet auf Phrasengrenzen, und
`in phrase` zielt darauf, also muss es dieselbe Zahl sein.

Zwei Fehler kamen erst am laufenden Programm heraus, keiner davon in einem
Test:

- **Beim Start über `--a` kam die Gliederung nie am Deck an.** Der Vorlade-
  pfad in `main.rs` setzt die Felder einzeln, und `struktur` fehlte dort.
  Über `load` zur Laufzeit ging es, über die Kommandozeile nicht.
- **`list deck1.beats_to_outro` schrieb `f64::MIN` aus** — dreihundert
  Stellen in der Selbstbeschreibung. Ein offenes Ende wird jetzt in beide
  Richtungen als offen gemeldet, mit Wächter.

Leer heißt weiterhin leer: Im Vorlauf vor der ersten Eins gibt es keinen
Abschnitt, und ein Track ohne Outro bekommt keines angedichtet. Eine 0
hieße beides.

Nachgefahren am laufenden Programm: entry 0,466 s, intro_beats 32,
beats_to_outro zählt von 161 über 93 auf 1,7 herunter, section folgt dem
Abspielkopf. Die MCP-Prüfung gegen die laufende Anwendung ist grün.

401 Tests grün (vorher 387).
`DJ.md` plant den Weg zu einem AI-DJ und reicht bis S6. Offen ist mehr: die
ungeprüften Behauptungen, das Team, das es nie gegeben hat, und die Punkte,
die an Hardware, Musik und Ohren hängen.

Die Diagnose, aus der die Reihenfolge folgt, hat zwei Teile.

Über neun gebauten Phasen liegen inzwischen drei Schichten Messwerkzeug —
Kritiker, Mitschrift, Gliederung —, und jede hat beim Bauen sofort einen
Fehler gefunden. Nur ist **jede Schwelle darin an Material geeicht, das aus
eigener Hand stammt**. Genau das war in diesem Projekt schon viermal die
Vorstufe eines Fehlers: die Tempo-Güteschwelle an Klick-Tracks, die
Tonart-Schwelle an synthetischen Akkorden, der Struktur-Prüfstein an einem
reinen Sinus. Jedes Mal sah es vorher gut aus.

Und der Zweck des Ganzen — ein Team von Agenten — hat nie stattgefunden.
Gelaufen ist ein Bediener in mehreren Rollen. Die Schutzmechanismen dagegen,
dass einer dem anderen den Regler wegzieht, sind einzeln geprüft; ihr
Zusammenspiel nie.

Daraus zwei Schichten, die vor den musikalischen stehen:

P1, der Prüfstand — ein Programm, das Tracks und eine Angabe von Hand
nebeneinanderlegt und meldet, wo die Analyse zustimmt und wo nicht. Dasselbe
Argument, das den Kritiker vor die Strukturanalyse gestellt hat. Er hat am
ersten Tag einen Kunden: Das gebaute Material aus S2 trägt seine Wahrheit
schon. Und er verschiebt den Aufwand beim Prüfen an echter Musik vom
Nachrechnen zum Hinhören — hinhören kann nur ein Mensch.

P2, das Team — zwei Verbindungen an einem Pult, mit einem Aufbau, der die
Zusammenstöße absichtlich herbeiführt. Erst zwei Sitzungen mit einem Modell
(reproduzierbar, ohne API-Kosten), dann zwei unabhängige Modelle. Vor Bogen
und Raum, weil beides Entscheidungen darüber sind, was als Nächstes kommt —
für einen Bediener gebaut und später umgestellt, hieße zweimal gebaut.

Danach P3 Geste und Repertoire, P4 der Bogen, P5 der Raum, P6 Stems.

Getrennt davon steht, was nicht von hier aus zu erledigen ist: Musik mit
Angabe der Abschnitte, die Suno-Prompts, ein Interface mit vier Ausgängen,
einmal Hinhören bei der Zeitstreckung, eine echte collection.nml. Vier davon
sind mit denselben fünf Tracks erledigt — und diesmal landen sie sofort im
Repo statt im Container, den es dreimal zerlegt hat.

Nicht drin: Termine. Jede Schicht hier hat unterwegs mindestens einen
Entwurf verworfen, weil das Material widersprochen hat; die Strukturanalyse
allein vier.
Über der Anlage liegen drei Schichten Messwerkzeug — Kritiker, Mitschrift,
Gliederung —, und jede Schwelle darin ist an Material geeicht, das aus
eigener Hand stammt. Genau das war hier schon viermal die Vorstufe eines
Fehlers. Ein Messwerkzeug, dem niemand widerspricht, ist ein Orakel.

Der Prüfstand legt eine Wahrheitsdatei daneben und meldet den Abstand:

    Nachtschicht.mp3  bpm 124  tonart Am  intro 0:00  drop 1:04  outro 5:12

Abschnitte mit ihrem Anfang, nicht als Bereich — wer zuhört, notiert „hier
fängt das Outro an". Jede Angabe einzeln freiwillig; weglassen ist besser
als raten. Zeiten als m:ss oder in Sekunden.

Gemeldet werden Unterschiede in Art, nicht nur in Größe: ein Oktavfehler
beim Tempo ist etwas anderes als 5 BPM daneben, und eine Paralleltonart tut
beim Mischen nicht weh. Rückgabewert bleibt 0, auch wenn alles daneben
liegt: Was nicht lesbar war, ist ein Fehler; was nicht übereinstimmt, ist
ein Befund.

Der erste Kunde stand schon bereit — das gebaute Material aus S2 trägt seine
Wahrheit (128 BPM, A-Dur, Grenzen bei Sekunde 15/30/45/60/75). Tempo auf
0,01 BPM, Tonart gleich, alle sechs Namen richtig.

Und der Lauf hat sofort etwas gezeigt, das die reine Tabelle verschluckt
hätte: Jede Grenze liegt um +1,0 Beats daneben, keine streut. Ein
konstanter Abstand ist eine Tatsache und keine sechs — er sagt etwas über
den Nullpunkt (der Grid-Anker sitzt auf dem ersten erkannten Schlag, die
Wahrheitsdatei zählt ab Dateianfang) und nichts über die Segmentierung. Der
Bericht trennt das jetzt: gemeinsamer Versatz oben, Rest darunter. Streuen
die Abweichungen stärker, als sie gemeinsam verschoben sind, wird nichts
als Versatz erklärt — sonst würde aus „die Segmentierung wackelt" ein
beruhigendes „nur der Nullpunkt".

Dazu docs/wahrheit-vorlage.txt zum Ausfüllen. Damit verschiebt sich der
Aufwand beim Prüfen an echter Musik vom Nachrechnen zum Hinhören.

412 Tests grün (vorher 401). P1 aus docs/FAHRPLAN.md ist damit erledigt.
P2 aus docs/FAHRPLAN.md, Stufe 1: zwei Bediener an einem Pult.

Der Fund stand vor dem ersten Test. Die Frage „was sieht ein zweiter
Bediener?" hat gereicht: `takt_starten` gab den Rückgabewert des Taktgebers
nicht weiter, sondern verwarf ihn. Damit ging jede Meldung des Plans
verloren — fertig, abgebrochen und vor allem **abgelöst**. Ein Agent, dem
ein anderer den Fader wegnahm, erfuhr es nie und plante weiter auf einer
Blende, die seit zwanzig Sekunden tot war.

Für einen einzelnen Bediener fiel das nie auf. Er war derjenige, der den
Regler angefasst hat.

Jetzt liegen die Meldungen in einem Ring mit laufender Nummer:

    sub master.events
    event plan 3 abgeloest channel1.fader — jemand anders hat den Regler
    event plan 2 gestrichen

Kein einzelner Wert, obwohl das einfacher gewesen wäre: Der Taktgeber läuft
alle 5 ms, der Server vergleicht alle 50. Zwischen zwei Blicken passen zehn
Ereignisse, und neun davon wären weg — ausgerechnet dann, wenn viel
gleichzeitig geschieht. Wer zu langsam liest, bekommt `warnung N Ereignisse
verloren`; dieselbe Regel wie bei record_dropped und den unlesbaren Zeilen
der Mitschrift.

Auch `cancel` meldet jetzt. Streicht einer den Auftrag eines anderen,
verschwand der bisher wortlos aus dem Plan — dieselbe stille Niederlage.

Über MCP hält kein Abo, weil die Brücke je Aufruf neu verbindet. Deshalb
`master.events` und `master.event_count` zum Fragen: Springt der Zähler um
mehr als eins, sind Zeilen liegengeblieben, und auch das ist besser sichtbar
als still.

Neun Zusammenstöße stehen als Test — verlorener Regler, gewonnener Regler,
gestrichener Auftrag, zwei Aufträge auf derselben Phrasengrenze, derselbe
Track zweimal, zu langsamer Leser, Abbestellen, später Einsteigen, und dass
ohne Abo nichts ungefragt kommt. Dazu einer über den echten Socket mit
laufendem Taktgeber und zwei Verbindungen: Dort geht es nicht um die Regeln,
sondern darum, ob Mutex, Taktgeber und Abo-Thread sich gegenseitig
aushungern. Sie tun es nicht.

Die vorhandenen Schutzmechanismen hielten: Die richtige Rampe gibt auf,
derselbe Track wird nicht zweimal abgenommen, zwei Aufträge auf derselben
Eins feuern beide.

Am laufenden Programm nachgefahren, zwei echte Verbindungen: A fährt eine
Blende über 64 Beats, B greift hinein, A bekommt die Ablösung unaufgefordert.

Stufe 2 steht aus: zwei unabhängige Modelle.

428 Tests grün (vorher 412).
Eine Rampe lief bisher immer gerade. Vier gleich lange, gleich gerade
Blenden hintereinander klingen nach Maschine, auch wenn jede einzelne
sauber ist — genau das war mein erster automatisch gefahrener Übergang.

    ramp master.crossfader 1 32 weich    # S-Kurve
    ramp channel2.fader 0.9 16 spaet deck1

  linear   gleichmäßig — Bass-Swaps, alles nicht als Geste Gemeinte
  weich    S-Kurve — langsam los, durchziehen, weich ankommen
  spaet    lange fast nichts, dann schnell — hält den Ausgehenden präsent
  frueh    sofort viel, dann auslaufen — macht den Wechsel zum Ereignis

Die Namen sagen, **wann** die Bewegung passiert, nicht welche Kurve
dahintersteckt. Danach fragt man beim Auflegen.

Deck und Form stehen hinten in beliebiger Reihenfolge und werden daran
erkannt, *was* sie sind — die Namen sind disjunkt. Eine feste Reihenfolge
wäre nur eine Falle für den, der die Form angeben will, aber kein Deck, und
das ist der häufigere Fall. Ein Tippfehler wird gemeldet und nennt die
Auswahl, statt still linear zu fahren; das hätte ja auch „funktioniert".

Drei Wächter, die keine Behauptungen sind: Jede Form bildet 0 auf 0 und 1
auf 1 ab (sonst spränge der Regler beim Losfahren oder käme nicht an),
keine läuft zwischendurch zurück (ein umkehrender Fader ist kaputt, nicht
ausdrucksstark), und auf halber Strecke stehen sie messbar woanders — am
laufenden Regler nachgefahren, nicht nur in der Rechnung.

Die Form steht im Plan und in der Antwort. Ohne das sähen zwei verschieden
gemeinte Bewegungen für einen zweiten Bediener gleich aus — und seit den
Ereignissen ist das eine Frage, die sich wirklich stellt.

`musik_ramp` reicht sie durch. Offen bleibt aus P3 das Repertoire — lange
Blende, Bass-Swap, harter Schnitt, Filter-Sweep — und die
Crossfader-Kennlinie, die es schon gibt und die im ersten Lauf ungenutzt auf
weich stand.

437 Tests grün (vorher 428).
Der langweiligste DJ im Raum ist der, der immer dasselbe macht — und genau
das war der erste automatisch gefahrene Übergang hier: eine lange Blende,
linear, jedes Mal. Sauber, messbar, seelenlos.

    do master.uebergang bassswap 16
    uebergang bassswap über 16 Beats, 6 Zeilen
      set channel2.eq_low 0 → ok channel2.eq_low 0
      in phrase set deck2.play 1 → ok plan 1 in 13.5 Beats: …
      in phrase ramp master.crossfader 0 16 weich → ok plan 2 …
      in phrase+16 ramp channel1.eq_low 0 8 → ok plan 3 …
      in phrase+16 ramp channel2.eq_low 1 8 → ok plan 4 …
      in phrase+24 ramp master.crossfader 1 16 weich → ok plan 5 …

Vier Griffe: blende (32 Beats), bassswap (16), schnitt (0), filter (16).

Zwei Entscheidungen tragen das.

**Jeder Griff ist eine Handvoll gewöhnlicher Protokollzeilen**, die durch
denselben Weg laufen wie alles andere. Damit steht er im Plan, in der
Mitschrift und bei den Ereignissen, ein zweiter Bediener sieht ihn kommen,
und `cancel` nimmt ihn zurück. Die Antwort nennt jede Zeile — es gibt nichts,
was ein Agent nicht auch selbst hätte tippen können. Der Griff spart ihm
sechs Zeilen, keine Magie.

**Die Anlage wählt nicht aus.** Welcher Griff passt, hängt daran, ob der
ausgehende Track ein Outro hat, der eingehende ein langes Intro, wie groß
der Energieunterschied ist — und vor allem daran, was vorher schon dreimal
gefahren wurde. Seit S2 stehen die Zahlen dafür im Steuerraum; die
Entscheidung gehört dem, der sie begründen kann. Nähme die Anlage sie ab,
verlöre das Set genau den Teil, um den es diesem Projekt geht.

Vorausgesetzt wird, dass genau ein Deck läuft und auf dem anderen etwas
liegt. Läuft nichts, gibt es keinen Übergang; laufen beide, ist er im Gange
— und wer dann gemeint ist, kann niemand wissen. Beides wird gesagt, statt
ein Deck zu wählen.

Wächter, die zählen: Jeder Griff beginnt auf einer Phrasengrenze, und jeder
startet das eingehende Deck. Auf Stille zu blenden ist beim ersten
selbstgefahrenen Set wirklich passiert, weil `do deckN.play` gar keine
Aktion ist. Dazu: Der Crossfader geht zur Seite des eingehenden *Kanalzugs*,
nicht der Deck-Nummer — welche Seite ein Zug bedient, ist einstellbar. Und
ein Schnitt rampt nicht, sonst wäre er keiner.

Am laufenden Programm nachgefahren: bassswap plant sechs Zeilen auf dem
Phrasenraster, `plan` zählt sie herunter, `cancel` nimmt alle fünf zurück,
und die nächste Nummer ist 6 statt wieder 1.

`musik_uebergang` reicht das durch — fünfzehn Werkzeuge statt vierzehn.
Damit ist P3 aus docs/FAHRPLAN.md erledigt, bis auf den Loop-Ausstieg.

449 Tests grün (vorher 437).
Ein einzelner guter Übergang ist Handwerk. Ein gutes Set ist Architektur.
Bisher konnte die Anlage jeden Übergang begründen und keine Reihenfolge.

    set master.arc 0 0.3, 20 0.7, 45 0.95, 60 0.5
    do master.arc_start
    get master.arc_gap    → value master.arc_gap 0.102069
    get master.arc_trend  → value master.arc_trend steigt

Zeiten in Minuten, Energie 0 bis 1, dazwischen geradlinig verbunden — nicht
weil das musikalisch stimmt, sondern weil alles andere eine Genauigkeit
vortäuschte, die eine von Hand gesetzte Kurve nicht hat.

Damit ist die eigentliche Frage sagbar:

    when master.arc_gap > 0.3 do master.queue_next

**Die Ist-Energie kommt aus der Art des Abschnitts, nicht aus dem Pegel.**
Der Pegel der Gliederung ist auf den lautesten Abschnitt desselben Tracks
bezogen: Der Drop eines leisen Stücks steht dort genauso bei 0,99 wie der
eines lauten. Über Tracks hinweg ist das nicht vergleichbar, und ein Bogen,
der solche Zahlen addiert, rechnet mit Äpfeln. Stattdessen eine grobe Leiter
über die sechs Abschnittsarten — Intro 0,2, Break 0,35, Aufbau 0,55,
Drop 0,9. Sechs Stufen sind hier ehrlicher als eine Nachkommastelle, die
niemand einlösen kann.

Ohne `arc_start` gibt es keinen Ort auf dem Bogen, und dann wird auch keiner
behauptet: leer statt null. Eine Kurve ohne Uhr ist ein Bild, kein Maßstab.
Laufen zwei Decks, zählt das lautere — beim Übergang trägt der den Raum.

Für ein Team ist der Bogen das, worüber man sich einig sein muss. Ohne ihn
verhandeln zwei Agenten über den nächsten Track ohne gemeinsamen Maßstab:
Der eine will Druck, der andere Luft, und beide haben recht, weil es keinen
Satz gibt, gegen den sich das prüfen ließe.

Ein Fund vom Wächter über alle schreibbaren Controls: `master.arc` nimmt
Text an, aber nicht *jeden* Text — der Wächter schrieb „Probe" hinein und
bekam zu Recht einen Fehler. Ein Feld, das Text annimmt, ist nicht dasselbe
wie eines, das beliebigen Text annimmt. Ein unlesbarer Bogen wird
abgewiesen statt halb übernommen; ein Set gegen eine Kurve zu fahren, die
niemand gemeint hat, wäre schlimmer als eines ohne Kurve.

`musik_status` trägt den Bogen mit: „Bei 1 min: soll 0.32, ist 0.20, Lücke
**+0.12** — mehr Energie gesucht · steigt in den nächsten Minuten".

Offen bleibt aus P4 die Zurückhaltung — zu messen, ob immer dasselbe
gefahren wird. Das Repertoire macht Abwechslung möglich, erzwingt sie nicht.

465 Tests grün (vorher 449).
Das Repertoire aus vier Griffen macht Abwechslung möglich, erzwingt sie
aber nicht. Ein System, das viermal hintereinander dieselbe Blende wählt,
klingt weiterhin nach Automat, auch wenn jede einzelne sauber ist. Der
Vorwurf an den ersten selbstgefahrenen Übergang war nie mit einer Zahl
beantwortet; `master.repeats` und `master.transitions` sind sie.

Damit ist die Bedingung sagbar:

    when master.repeats > 2 do master.uebergang filter

Gezählt wird der Seitenwechsel des Crossfaders, nicht sein Wert. Eine
Rampe schreibt bei jedem Takt in denselben Regler; auf den Wert gezählt
stünde nach einer einzigen Blende eine Wiederholung von 80 da — die Zahl,
die vor Eintönigkeit warnen soll, wäre selbst der Grund, sie zu
ignorieren. Ein Bass-Swap fährt zweimal am Crossfader, kommt aber nur
einmal an.

Der letzte Schritt einer Blende und ein Schnitt schreiben denselben Wert;
unterscheiden lassen sie sich nur über den Schreiber. Deshalb sagt der
Zeitplan dem Pult um den einen Schreibvorgang herum, dass eine Rampe
fährt. Ein über `master.uebergang` angeforderter Griff wird nur
vorgemerkt und erst beim Ankommen eingetragen: Ein abgelöster Griff hat
nicht stattgefunden.

Zwei Decks müssen laufen. Das fiel erst am laufenden Programm auf — nach
vier Zeilen Einrichten meldete die Anlage bereits einen Übergang, obwohl
nur ein Deck lief. Alle vier Griffe starten das eingehende Deck, bevor
der Fader sich bewegt.

Die Grenzen stehen im Modul: Wer nur mit den Kanalfadern mischt, taucht
nicht auf; und läuft der ausgehende Track aus, bevor die Blende ankommt,
fehlt sie. Dann sagt die Zahl nichts, statt etwas Falsches zu sagen.

Geprüft am laufenden Programm über den Socket: eine Blende zählt einmal
und heißt `blende`, ein Bass-Swap mit zwei Crossfader-Rampen zählt einmal
und heißt `bassswap`, drei Schnitte melden `repeats 3`, und `musik_status`
warnt ab dreien. Beide Wächter wurden geprüft, indem der Fehler wieder
eingebaut wurde: 4 statt 1, `schnitt` statt `weich/8`, und 1 statt 0 beim
Einrichten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Die vier Signalplätze gab es lange, aber sie waren Deko: Werte gingen
hinein, und nichts hat darauf reagiert. Ein Bogen, der von der ersten
Minute an feststeht, ist ein Abspielplan — genau das sollte hier nie
herauskommen.

Jetzt beugt der Trend eines Signals das Ziel (master.room,
master.room_bend) und damit arc_gap, und `do master.search_next` sortiert
die Sammlung danach:

    weil Andrang fällt (-1.15/min), Ziel 0.75 → 0.50; es läuft 0.20
    track 128.02 - .../mitte.wav mitte — Energie 0.57 (+0.07 zum Ziel 0.50)
    track 128.02 - .../warmup.wav warmup — Energie 0.38 (-0.12 zum Ziel 0.50)
    track 128.02 - .../peak.wav peak — Energie 0.73 (+0.23 zum Ziel 0.50)

Drei Entscheidungen tragen das. Der Raum verschiebt das Ziel, nicht die
Kurve: master.arc bleibt, was jemand aufgeschrieben hat, arc_curve zeigt
es unverändert, und am Ende des Abends lässt sich vergleichen, was
geplant war und was geschah. Gebeugt wird nach dem Trend, nicht nach dem
Wert — die Höhe eines Signals ist nichts Vergleichbares, eine Änderung
schon. Und höchstens um 0,25, damit ein hängender Sender das Set nicht
übernimmt.

Die Energie eines Tracks kommt aus seiner Gliederung, nach Länge
gewichtet, und liegt in der Analyse-Datei neben dem Track statt in der
Datenbank: Sie hängt am Inhalt, nicht am Eintrag. Ein nicht analysierter
Track steht hinten und sagt das. Was sie **nicht** misst, steht im Modul:
Lautstärke und Härte nicht, weil die Abschnittsarten je Track gegen
dessen eigene Quantile benannt werden.

Ausgewählt wird trotzdem nicht. Die Anlage sortiert und begründet; welcher
Track es wird, entscheidet, wer es begründen kann.

Zwei Funde kamen erst am laufenden Programm heraus:

Die Benennung verglich Abschnitts*mittel* mit Quantilen über *Phrasen*.
Ein Mittel liegt immer unter der lautesten Phrase darin; ist die Mehrheit
der Phrasen laut, liegt der Median fast auf der Spitze, und jeder
Abschnitt fällt darunter. Ein Stück mit 90 Sekunden Vollgas hieß deshalb
"Intro, Break, Outro" und bekam die niedrigste Energie von allen — die
Reihenfolge stand auf dem Kopf. Am gebauten Prüfmaterial fiel das nie
auf, weil dort laute und leise Teile sich abwechseln. Der erste Anlauf
des Wächters lief sogar daran vorbei: Bei exakt gleich lauten Phrasen
liegt das Mittel genau auf dem Median, und die Prüfung hält gerade noch.
Er greift jetzt mit ungleichen Phrasen, wie echtes Material sie hat.

Und `set master.room ""` war über MCP nicht erreichbar — dort verlangt
musik_set mindestens ein Zeichen. Ein Weg, den nur der Socket kennt, ist
für ein Team von Agenten keiner; `-` und `aus` gehen jetzt auch.

Geprüft am laufenden Programm über den Socket und über einen echten
MCP-Client: Ein fallender Raum beugt das Ziel von 0,75 auf 0,50, und der
Peak-Track rutscht dabei von Platz eins auf Platz drei.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Zwischen zwei Stücken tut das jeder: `record_stop`, dann sofort
`record naechstes.wav`. Der Schreiber-Thread holt sich seine Befehle in
Häppchen ab, und wenn beide im selben Häppchen liegen, fasste er sie zu
Merkern zusammen und handelte erst danach. Dabei öffnete der `Start` die
neue Datei, und der `Stop` schrieb den **alten** Mitschnitt hinein und
schloss sie: Die erste Datei blieb bei 44 Bytes — nur der Kopf —, die
zweite bekam fremdes Material, und der zweite Mitschnitt landete nirgends.

Zwei Änderungen. Befehle werden der Reihe nach ausgeführt, nicht
gesammelt; ein `Start` auf eine noch offene Datei schließt sie zuerst
ordentlich ab, statt sie mit ungeschriebenem Kopf fallen zu lassen.

Und der `Stop` bringt mit, wie viele Frames noch in diese Datei gehören —
die Zahl kommt von der aufnehmenden Seite und ist die einzige, mit der
sich die Grenze zwischen zwei Mitschnitten bestimmen lässt. „Alles, was
noch im Ring liegt" war die naheliegende Regel und die falsche: Genau
darin steckt der Anfang des nächsten.

Aufgefallen ist das nicht hier, sondern in der CI, und zwar an einem
Test, der den Fehler selbst versteckte: Er wartete zwischen den beiden
Aufnahmen, bis die erste Datei Daten hatte. Auf einem freien Rechner kommt
der Schreiber in dieser Zeit dran, auf einem ausgelasteten nicht. Der neue
Wächter wartet ausdrücklich nicht und fällt ohne die Korrektur zuverlässig
um — geprüft, indem der Fehler wieder eingebaut wurde: 44 statt 4044 Bytes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
`schleife` legt den Ausgehenden in eine Schleife und wechselt darüber.
Der einzige Griff, der ihm Zeit gibt: Ein Track, der in vier Beats zu
Ende wäre, hält so noch eine ganze Phrase durch. Genau dafür setzt ein
Mensch am Ende eines Stücks eine Schleife — nicht, um etwas zu
wiederholen, sondern um nicht gehetzt wechseln zu müssen.

Der Griff hat zwei Fehler aufgedeckt, die beide erst am laufenden
Programm sichtbar wurden.

**Ein Auftrag, dessen Taktgeber zurückspringt, lief stumm weiter.** Eine
Rampe rechnet ihren Fortschritt aus dem Beat des Taktgeber-Decks; läuft
das in einer Schleife, fängt sie bei jedem Durchlauf von vorn an. Der
Crossfader fuhr sieben Mal hin und her, und `master.transitions` zählte
sieben Übergänge. Umgekehrt kommt ein Vorgemerktes hinter dem
Schleifenende nie an — auch nicht die Zeile, die die Schleife wieder
lösen soll. Jetzt bricht der Auftrag ab und sagt es: „plan 3 abgebrochen
— deck1 ist zurückgesprungen (Schleife oder Sprung)". Das still zu tun
wäre das Schlimmste von allem: Der Bediener sieht einen Plan, der läuft,
und hört etwas anderes.

**Und `in` konnte seinen Taktgeber nicht wählen.** Es nahm immer das erste
Deck mit Beatgrid — bei diesem Griff also das schleifende. `in deckN
<zeit> <befehl>` nimmt jetzt dessen Takt, auch wenn der Befehl auf ein
anderes Deck wirkt. Der Griff taktet alles nach dem eingehenden Deck; nur
das Setzen der Schleife selbst liegt noch auf dem ausgehenden.

Am laufenden Programm nachgesehen, alle zwei Sekunden: Die Schleife greift
auf der Phrasengrenze, hält den Ausgehenden über die ganze Blende (seine
Position springt einmal von 15,3 auf 10,8 zurück), wird danach gelöst, und
der Crossfader geht genau einmal hinüber. `master.transitions` zeigt einen
Übergang, nicht sieben.

Beide Wächter wurden geprüft, indem der Fehler wieder eingebaut wurde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Zwei Stimmen gleichzeitig sind der hörbarste Mixfehler überhaupt, und
ohne Trennung lässt er sich nur vermeiden, indem man gar nicht
überlagert. Jetzt geht `set deck1.stem3_level 0`: Stimme weg,
Instrumental steht.

Gemessen am laufenden Programm, zwei Mitschnitte desselben Stücks:
Energie bei 1500 Hz von 9,8e-5 auf 4,9e-9, Gesamtpegel nur von 0,061 auf
0,039. Die Stimme ist um den Faktor 20 000 weg, Drums und Bass stehen.

Die Spuren liegen neben der Datei, in einem Ordner gleichen Namens mit
der Endung `.stems` — kein neues Dateiformat, keine neue Abhängigkeit,
und genau die Form, in der die gängigen Trennwerkzeuge ihr Ergebnis
ablegen. Das Hersteller-Stem-Format wäre die Alternative gewesen: eine
MP4-Datei mit fünf AAC-Spuren, die jemandem gehört; ein Ordner mit vier
WAV-Dateien gehört niemandem. Getrennt wird hier nicht — das ist Arbeit
für ein neuronales Netz, ein eigenes Werkzeug und eine eigene
Lizenzfrage.

Die schwierige Stelle war die Zeitstreckung. Sie sucht in jedem Hop die
Stelle, an der die Wellenform am besten anschließt; vier Spuren getrennt
zu strecken hieße vier verschiedene Entscheidungen, und was zusammen
klang, klänge verwaschen. Gesucht wird deshalb auf der Summe, angewandt
auf jede Spur. Gemischt wird beim Lesen: dasselbe Ergebnis, aber einer
statt vier Akkumulatoren. Der Wächter dafür misst es am Ton — mit vollen
Pegeln muss dasselbe herauskommen wie beim Strecken der Summe; mit dem
Fehler wieder eingebaut weicht es um 0,99 ab.

Im Audio-Callback wird nichts angelegt: Die Spurzeiger stehen auf dem
Stapel, die Pegel sind Atomics. Ein Track ohne Stems geht durch denselben
Weg mit einer einzigen Spur — es gibt nur eine Stelle, die stimmen muss.

Und das Umrechnen auf die Geräterate nimmt die Spuren mit. Sie dort
fallen zu lassen wäre der stillste denkbare Fehler gewesen: Der Track
klänge richtig, nur ließe sich nichts mehr herausnehmen — ausgerechnet
auf den Geräten, deren Rate nicht zur Datei passt.

Offen bleibt das Streaming von Platte. Vier Spuren kosten das Fünffache
eines Tracks, weil die Summe daneben stehen bleibt: rund 500 MB je Deck
bei fünf Minuten. Für die zwei Decks, die es gibt, geht das; für vier
nicht. Der Abspielpfad ist der einzige Teil mit Echtzeitauflagen, und
Platten-I/O gehört dort zuletzt hinein.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Ein einzelner Übergang lief. Der zweite nicht — und das war nie das
Ziel. Drei Fehler lagen dazwischen, alle drei erst am laufenden Programm
sichtbar und alle drei erst beim *zweiten* Griff.

**Der ausgehende Track lief nach dem Griff weiter.** Unhörbar hinter dem
geschlossenen Crossfader, aber laufend — und damit wurde der nächste
`uebergang` abgewiesen: „es laufen mehrere Decks", denn wer aus- und wer
eingeht, kann dann niemand wissen. Jeder Griff endet jetzt damit, dass
das ausgehende Deck steht. Das ist die Stelle, an der aus einem Übergang
ein Set wird.

**Getaktet wurde nach dem ersten Deck mit Beatgrid.** Beim zweiten Griff
ist das ausgerechnet das gerade gestoppte, und ein stehendes Deck hält
den Plan an: Der Übergang wurde angenommen, stand im Plan und lief nie.
Sichtbar wurde er nur daran, dass der Bass des eingehenden Kanals zu
blieb — die eine Zeile des Bass-Swaps, die sofort ausgeführt wird.
Getaktet wird jetzt überall nach dem ausgehenden Deck, das per Definition
läuft, solange der Griff dauert. Nur die Schleife hängt danach am
eingehenden, weil der ausgehende gerade in einer Schleife liegt.

**Und eine Rampe, die im selben Takt endet, in dem ihr Deck
stehenbleibt, bekam ihren letzten Schritt nicht mehr.** Der Regler stand
am Ziel, der Auftrag blieb für immer im Plan stehen — sichtbar für jeden
zweiten Bediener und nicht mehr wegzubekommen, weil das Deck nicht mehr
läuft. Ein Beat Nachlauf, den niemand hört.

Nebenbei geht der Hochpass des Filter-Griffs am Ende wieder auf. Ein
Kanalzug, der beim nächsten Track noch gefiltert dasteht, ist eine
Überraschung, die man lange sucht, weil man den Regler nicht angefasst
hat.

Am laufenden Programm nachgefahren, drei Griffe hintereinander ohne
Handgriff dazwischen außer Laden und `jump_entry`: `transitions` zeigt
`blende, bassswap, schleife`, `plan` nach jedem `ok 0 vorgemerkt`, die
Decks und die EQ-Stellungen stimmen. Alle drei Wächter wurden geprüft,
indem der Fehler wieder eingebaut wurde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Wer von Hand auflegt, cued auf den ersten Schlag. `do deckN.load` und
`do master.queue_next` ließen das Deck dagegen auf Sekunde 0 stehen, und
der nächste Griff startete es dort — mitten in der Stille vor dem ersten
Schlag. Das ist genau der Fehler, den die Mitschrift beim ersten
gemessenen Übergang aufgedeckt hat: Deck 2 setzte fünfzehn Beats neben
seiner eigenen Eins ein. Behoben war bis jetzt nur das Werkzeug dagegen
(`jump_entry`), nicht die Ursache.

Die Reihenfolge war dabei der eigentliche Fund. Der erste Anlauf setzte
den Sprung im Lade-Arbeiter — und dort hängt am DeckState noch die
**alte** Stimme. Die hat ihn verbraucht, auf ihrem eigenen Track, und die
neue fing wieder bei null an. Am laufenden Programm gemessen: `beat
-1.02` statt `beat -0.03`. Jetzt meldet der Arbeiter den Einstieg nur,
und gesprungen wird, nachdem die neue Stimme im Mixer liegt.

Der Wächter dafür steht in audio-core, wo der Mechanismus liegt: Ein
vorgemerkter Sprung gehört der Stimme, die als Nächstes rendert, und
überlebt einen Stimmentausch nicht.

Damit fällt der letzte Handgriff aus der Kette: Laden, Griff, Laden,
Griff — ohne `jump_entry` dazwischen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Gefunden beim ersten Übergang, der sich selbst auslöste — `when
deck1.beats_to_outro < 0 do master.uebergang bassswap`. Der ausgehende
Track lief mitten im Swap aus, und danach stand der Crossfader für immer
in der Mitte, mit fünf toten Aufträgen im Plan. Ihr Taktgeber-Deck war zu
Ende; sein Beat rührt sich nicht wieder, also konnte keiner von ihnen je
fällig werden. Ein Bediener sieht dort einen Übergang, der läuft, und
hört einen, der steht.

Jetzt bricht so ein Auftrag ab und sagt, warum: „plan 9 abgebrochen —
deck1 ist durchgelaufen".

Der Unterschied zu einem bloß **angehaltenen** Deck ist der ganze Punkt.
Dort wartet der Plan absichtlich weiter — wer pausiert, will da
weitermachen, wo er aufgehört hat. Beide Wächter wurden in beide
Richtungen geprüft: Zählt man auch die Pause als Ende, wird der andere
rot.

Am laufenden Programm nachgestellt mit einem 20-Sekunden-Track und einem
Bass-Swap über 32 Beats: Der Plan räumt sich selbst, und die Meldung
steht in `master.events`. Gegen den Fall selbst gibt es den Griff
`schleife` — der hält den Ausgehenden so lange, wie der Wechsel dauert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Die Kette aus Liste, Laden und Griff braucht keine Hand mehr dazwischen.
Was dabei zu finden war, steht im Fahrplan: vier Fehler, die erst beim
zweiten Griff sichtbar wurden, und einer beim ersten selbstausgelösten
Übergang.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
N2 aus dem Fahrplan: die Gliederung an schwierigen Fällen. `musik-material
<ordner>` baut vier Tracks mit bekannter Gliederung und legt die
Wahrheitsdatei daneben — einer ohne Outro, einer ohne Intro, einer mit
zwei Breaks, einer mit Tempowechsel. Jeder bricht eine Annahme, die in
der Gliederung steckt.

Der erste Lauf durch `musik-pruefstand`: **13 von 23 Namen richtig**. Die
Grenzen saßen dabei fast alle auf den Frame genau — die Fehler lagen
sämtlich in der Benennung, und sie waren nicht verstreut, sondern ein
Muster. Viermal wurde ein Drop zu „Teil".

Die Ursache steckte in einer Zeile: Ein Drop war, wer über dem **75.
Perzentil** des Bassbands lag. Das ist rangbasiert, und darin steckt eine
Behauptung über die Musik, die niemand aufgestellt hat — höchstens ein
Viertel der Abschnitte kann je ein Drop sein. Ein Stück mit vier Drops
bekam einen, weil die Schwelle *zwischen* den Drops lag.

Jetzt entscheidet der Pegel als Anteil am lautesten Abschnitt: eine Höhe,
kein Rang. Dieselbe Umstellung trennt auch Aufbau und Break sauber, und
zwar an dem, was sie musikalisch unterscheidet — ein Aufbau kommt von
etwas Leiserem und führt in den Drop, ein Break fällt aus einem Drop
heraus. Am Pegel allein sind die beiden nicht zu trennen (0,48 gegen
0,40).

Danach: **21 von 23**. Die zwei Reste liegen beim Tempowechsel, und dort
steigt schon die Segmentierung aus.

Diese Grenze steht jetzt im Modul, statt still zu bleiben: **ein Tempo je
Track.** Gerechnet wird auf einem Beatgrid, und ein Beatgrid hat ein
Tempo. Springt es mitten im Stück, stimmen die Phrasengrenzen danach
nicht mehr — drei von sechs Abschnitten gefunden, alle drei davor. Ein
zweites Grid wäre eine eigene Schicht und braucht man erst, wenn solche
Dateien im Ordner liegen.

Zwei Wächter halten das fest: drei Drops in einem Stück heißen alle drei
Drop, und aus einem Schluss auf dem Höhepunkt wird kein Outro gemacht.
Beide wurden geprüft, indem die alte Regel wörtlich aus git
zurückgeholt wurde — dann fallen beide um.

Das ist der sechste Entwurf einer Schwelle in dieser Schicht, der am
Material gescheitert ist. Gebautes Material findet solche Fehler; es
ersetzt keine echte Musik mit gehörter Wahrheit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Der Schreiber holt erst die Befehle ab und leert dann den Ring — ohne
Deckel. Wer in dieser Lücke stoppt, neu startet und nachlegt, dessen
zweiter Mitschnitt liegt schon im Ring, während der Stop noch im
Briefkasten steckt. Der offene erste nimmt ihn mit; der zweite bleibt
leer.

Einmal in der CI gesehen: 5244 statt 4044 Bytes, also 1000 + 300 Frames
in einer Datei. Vierzig Läufe mit zwanzig Runden holten es nicht zurück,
die Lücke ist ein paar Maschinenbefehle breit.

Jetzt wird der Stand der aufnehmenden Seite abgelesen, *bevor* die
Befehle drankommen, und der Ring nur so weit geleert. Was danach
hineinkommt, wartet eine Runde — dann ist auch sein Befehl da. Nach einem
frischen Start gilt der Stand nicht mehr, also gar nicht leeren.

Der Test hält den Schreiber an dieser einen Stelle an. Mit dem Fehler
drin meldet er wieder genau 5244 statt 4044 Bytes.
N1 sollte klären, ob das Tempo-Verfahren unter MIN_BPM trägt. Es trägt:
zwischen 46 und 58 BPM findet die Autokorrelation die richtige Periode,
zwei unabhängige Gegenproben stimmen auf ein halbes BPM überein.

Der eigentliche Fund ist ein anderer. Material mit 66 BPM bekommt ein
Grid mit 71,51 BPM, eines mit 68 eines mit 69,88 — deutlich und falsch,
weil die beste Verschiebung innerhalb des abgeschnittenen Fensters sauber
herausragt. Sie sitzt nur am Rand. Ein Grid, das um 8 % danebenliegt, ist
schlimmer als keines.

Zwei Auswege gebaut und beide zurückgenommen: Das Fenster zu öffnen lädt
den Halbtempo-Fehler ein (der Demo-Track mit 124 BPM bekam gar kein Grid
mehr, Snare auf zwei und vier reicht dafür). Den Rand zu erkennen geht
lokal nicht — echte 71 BPM sitzen bei 0,989 näher am Rand als falsche 66
bei 0,975, und "steigt am Rand noch" trifft 92 und 128 genauso.

MIN_BPM bleibt also bei 70, und der Fehler bleibt bestehen. Das steht
jetzt an der Konstante, ein Test hält fest, dass 71, 75 und 92 BPM nicht
auf die Hälfte kippen, und die Stellen in PLAN und FAHRPLAN, die "findet
der Detektor nichts" behaupteten, sagen jetzt, was wirklich passiert.
Die CI fährt dtolnay/rust-toolchain@stable und ist damit auf 1.98
gelaufen; lokal steht 1.94. Der neue Lint chunks_exact_to_as_chunks
trifft vierzehn Stellen, keine davon neu — er war vorher nur nicht da.

Statt einer Scheibe unbekannter Länge kommt jetzt ein Array fester Größe
heraus, und `f[0]`/`f[1]` brauchen keine Bereichsprüfung mehr. Das ist
für Code, der in jedem Callback über jeden Frame läuft, ohnehin die
ehrlichere Form.

Geprüft mit derselben Toolchain wie die CI: fmt, clippy --all-targets
-D warnings und test --all sind grün, mit 1.94 ebenfalls.
…odul

Wo im Mitschnitt ein Übergang liegt, stand bisher im Kritiker selbst.
Für N3 braucht es dieselbe Schätzung ein zweites Mal — gegen die Wahrheit
der Anlage, über viele Sets. Zwei Kopien wären zwei Verfahren gewesen,
und die Streuung hätte nichts mehr über den Kritiker gesagt.

Verschoben, nicht geändert: Wechselkurve, Ähnlichkeit, Schwellen,
Zurückverfolgen des Beginns und die Ruhelage. Die sieben Wächter ziehen
mit um; einer kommt dazu, der bisher fehlte — eine ruhige Kurve darf
keinen Übergang erfinden.
Aus einem gemessenen Wert (3,7 s zu spät) wird eine Verteilung. 24 Sets
laufen durch die echte Anlage — echter Mixer, echtes Pult, echtes
Repertoire, echter Mitschnitt, echte Mitschrift —, danach bekommt der
Kritiker die Datei, so wie er sie sonst bekommt.

Median +2,0 s, im Betrag 2,4 s, Spanne -1,4 bis +7,6 s. Der Fehler hängt
an der Länge des Übergangs: ein Schnitt wird auf ein Fenster genau
gefunden und eher zu früh (-1,1 s), eine Blende über 32 Beats im Mittel
4,9 s zu spät. Die Unschärfe, die der Kritiker selbst angibt, deckt den
Fehler in 16 von 20 Fällen.

Der Fund: Vier Übergänge blieben unbemerkt, drei davon Blenden über 64
Beats. Der Kritiker vergleicht mit dem Klangbild von vor 16 s — dauert
die Blende länger, liegt der Vergleich mitten in ihr. Eine Blende über
zwei Phrasen ist für ihn unsichtbar.

Zwei Anker waren vorher falsch und gaben beide Zahlen aus, die nach
Messwerten aussahen: erst das Einrichten des Crossfaders (Sekunde 0),
dann die Bestellung des Griffs statt seiner Ausführung — dazwischen
liegt die Wartezeit auf die Phrase. Drei Wächter halten den Anker jetzt
fest.

Und der Prüfstand hat zuerst sich selbst gemessen: Der erste Durchgang
überrannte den Ring des Mitschnitts, 1,2 von 2,1 Millionen Frames
verworfen. Jetzt läuft die Schleife am Zügel, und ein Set mit auch nur
einem verworfenen Frame wird gemeldet statt gemessen.
Wer den Eingang mitlaufen lässt, hat hinterher eine Datei mit einer
Stunde Musik darin. Die Anlage kann damit wenig anfangen: Ein Beatgrid
gilt je Track, eine Tonart auch, und die Gliederung sucht Intro und Outro
eines Stücks, nicht eines Abends.

Geschnitten wird an den Lücken — das ist ein Ablesen, kein Verfahren. Wo
keine sind, bleibt der Klangwechsel, und der ist eine Schätzung mit
bekannten Grenzen (N3). Findet sich beides nicht, wird nichts geschnitten
und das gesagt; ein geratener Schnitt wäre schlechter als keiner.

Die Schwelle für Stille ist keine feste Zahl, sondern ein Anteil am
lauten Teil dieser Aufnahme. Sonst hätte sie mehr über den Aufnahmepegel
gesagt als über die Musik — derselbe Fehler, der in der Gliederung schon
sechs Entwürfe gekostet hat. Ein Test hält fest, dass dieselbe Musik
leise aufgenommen dieselben Lücken ergibt.

Die Ränder werden von der Stille befreit. Ohne das war jedes geschnittene
Stück genau eine Sekunde länger als seine Vorlage — die halbe Lücke vorn
und hinten —, und eine Sekunde Stille am Anfang ist genau das, wonach die
Gliederung ein Intro sucht. Mit dem Beschneiden trifft der Schnitt auf
0,00 s: an drei aneinandergehängten Stücken nachgemessen.
Der Kanalzug stand seit Langem, der Ringpuffer auch — nur hat nie jemand
hineingeschrieben. Im Programm stand es wörtlich: "AUX bleibt ohne
Zuspieler still." Damit war der AUX-Eingang ein Kanal ohne Eingang.

Jetzt öffnet `musik-app --aux-in` ein Aufnahmegerät und schiebt, was
hereinkommt, in den Ring. `--aux-gerät` wählt eines über einen Teil des
Namens, `--aux-liste` zeigt, welche es gibt. Ein fehlender Eingang bricht
nichts ab: Der Kanal bleibt still, alles andere läuft, und gesagt wird es.

Zwei Dinge werden nicht geraten:

Die Samplerate muss genau die der Engine sein. Der Ring trägt Samples,
keine Zeit — 44,1 statt 48 kHz heißt 8 % zu langsam, und nichts meldet
es, denn es klingt wie Musik. Umrechnen wäre ein eigener Baustein.

Das Sample-Format wird nach Auflösung gewählt, nicht der Reihe nach. Das
fiel erst beim Anschließen auf: Das null-Gerät von ALSA bietet i8 zuerst
an, also acht Bit für Material, das analysiert werden soll. Wer die Liste
abarbeitet, nimmt das und merkt nichts.

Dazu `musik-eingang`: öffnet dasselbe Gerät auf demselben Weg und sagt
vorher, was hinterher zu spät wäre — ob etwas ankommt, ob Luft unter dem
Begrenzer ist, ob es lückenlos ankommt. Der erste Block zählt dabei nicht
als Unterlauf; er ist immer leer, und jeder gesunde Eingang bekäme sonst
eine Warnung.

An echter Hardware ungeprüft. Der Weg ist gegen das null-Gerät von ALSA
gelaufen: Auswahl, Rate, Format, Callback, Umrechnung auf Stereo.
Für einen Mitschnitt des Abends ist `do master.record` richtig: Er nimmt
die Summe auf, hinter Fader, EQ und Begrenzer, so wie es auf die Anlage
geht. Wer Lieder gewinnen will, will genau das nicht — der Begrenzer
drückt zusammen, was er einmal zusammengedrückt hat, und die Analyse
misst danach eine Dynamik, die niemand gewählt hat.

`--aufnehmen <datei>` schreibt deshalb direkt vom Gerät auf die Platte,
ohne Mixer dazwischen. Ohne `--sekunden` läuft es, bis Strg-C kommt; fünf
Sekunden als stille Vorgabe wären genau die Art Fehler, die einem erst
nach dem Abend auffällt.

Der Kopf wird jede Sekunde nachgetragen. Eine WAV-Datei trägt ihre Länge
vorn, und eine Aufnahme, die bis Strg-C läuft, endet nie ordentlich —
ohne das Nachtragen bliebe eine Datei mit falschem Kopf zurück, die
manche Programme gar nicht öffnen. Nachgemessen: nach vier Sekunden
abgeschossen, lesbar bei 3,98 s. Zwei Tests halten fest, dass der Kopf
auch mitten im Lauf zu den geschriebenen Daten passt.

Dazu der Hinweis, der beim Anschließen zählt: Ein Gerät lässt sich meist
nur von einem Programm zugleich aufnehmen. Entweder musik-app --aux-in
oder dieses hier.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants