Skip to content

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

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

Flissel wants to merge 70 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, Einzelspuren, mit denen sich die Stimme herausnehmen lässt, einem Eingang für alles, was von außen kommt — und einem Gatter, das entscheidet, was ein Agent von sich aus darf.

Alle zehn geplanten Phasen angefangen, 541 Rust-Tests und 18 für das Freigabe-Gatter, 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 mit Zuspieler, Sync, Mitschnitt, Kommandoschlange, Geräteein- und -ausgabe
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, Socket und Rückschleife
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, den Eingang prüfen und mitschreiben, eine Aufnahme in Lieder zerlegen
mcp/musik/ MCP-Brücke: sechzehn Werkzeuge, Beschreibungen aus dem Katalog erzeugt, Freigabe-Gatter davor

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 — 194 Stück. 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.

Wo es keinen Socket gibt

Auf Windows gibt es keine Unix-Sockets, und dort lief die Anlage bis vor Kurzem ohne jede Steuerung — Oberfläche, Decks und Mixer ja, Fernsteuerung nein, und damit auch kein MCP und keine Agenten. Also genau der Teil, um dessentwillen das Projekt gebaut wird.

$ musik-app --tcp
Steuerung: 127.0.0.1:7657
Schlüssel: 50270126063f57dd96e73b655ce56d5a

$ nc 127.0.0.1 7657
auth 50270126063f57dd96e73b655ce56d5a
ok angemeldet

Gebunden wird nur an 127.0.0.1 oder ::1, geprüft vor dem Binden — ein Tippfehler soll die Anlage nicht ins WLAN stellen. Und der Schlüssel ist kein Zierrat: Die Rückschleife erreicht jedes Programm auf dem Rechner, eine Webseite im Browser eingeschlossen. Nachgestellt mit einer echten HTTP-Anfrage samt Rumpf set master.crossfader 1: Jede Zeile bekam err nicht angemeldet, und der Crossfader stand hinterher unverändert.

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.

Was ein Agent von sich aus darf

Bis vor Kurzem: alles. Sechzehn Werkzeuge, alle frei. Hinter einem Unix-Socket ist das richtig — wer ihn öffnen darf, darf auch auflegen. Für einen Agenten ist es das nicht.

freigabe verweigert für: set channel1.fader 0.8
  Klasse:  mischen
  Grund:   keine gültige Freigabe (MUSIK_FREIGABE_DATEI nicht gesetzt)
  Erlaubt: `klassen` in MUSIK_FREIGABE_DATEI um mischen ergänzen

Sieben Klassen nach dem, was ein Aufruf berührt, nicht nach seinem Namen — bei 194 Controls wäre eine Namensliste nach dem nächsten Feature falsch. Alle 194 sind zugeordnet; ein Control ohne Klasse wird abgewiesen statt geraten.

Stufe Was
frei lesen — Status, Suche, list, get, plan, Ereignisse
Set-Freigabe mischen, spielen, zeit — alles Zeitkritische
einzeln datei, dramaturgie — Pfade, Bogen, Signale
nie über die Datei erzeugen — kostet je Aufruf Geld

Die Freigabe steht in einer Datei, nicht in der Umgebung, und das ist die Entscheidung, an der alles hängt: Eine Variable steht fest, sobald der Prozess läuft. Wer mitten im Set merkt, dass der Agent Unsinn macht, muss ohne Neustart widerrufen können. Nachgefahren: Datei gelöscht, nächster Aufruf abgewiesen.

Fail-closed in sechs Fällen. Zwei davon rutschen leicht durch: Ein bis ohne Zeitzone wird abgewiesen statt als Ortszeit geraten, und ein Tippfehler in klassen verwirft die ganze Datei, statt drei von vier Klassen still weiterlaufen zu lassen.

Und note <text> schreibt die Freigabe einmal je Fenster in die Mitschrift — unter welcher Vollmacht ein Set gefahren wurde, ist die Frage, die man hinterher stellt, und sie stand vorher nirgends.

Was das Gatter nicht kann, steht in der Spec: Es schützt davor, dass ein Agent seine Werkzeuge missbraucht, nicht vor etwas, das schon Socket-Zugang hat. Eine Spezifikation, die das verschweigt, verspricht Sicherheit, die sie nicht hat.

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 &gt; 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 &gt; 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.

Etwas von außen hereinlassen

Der AUX-Kanal war lange ein Kanal ohne Eingang. Kanalzug, EQ, Filter, Fader, Ringpuffer — alles da, und in den Ring hat nie jemand geschrieben; im Programm stand es wörtlich: „AUX bleibt ohne Zuspieler still."

musik-eingang --liste
musik-eingang --gerät scarlett                       # kommt was an, wie laut, lückenlos?
musik-eingang --gerät scarlett --aufnehmen roh.wav   # die Quelle, am Mixer vorbei
musik-schneiden roh.wav /musik/neu                   # daraus werden Lieder

Zwei Arten mitzuschneiden, und sie meinen Verschiedenes. do master.record nimmt die Summe auf, hinter Fader, EQ und Begrenzer — das will, wer sein Set behalten will. --aufnehmen schreibt am Mixer vorbei — das will, wer Lieder gewinnen will, denn was der Begrenzer einmal zusammengedrückt hat, bleibt zusammengedrückt.

Zwei Dinge werden nicht geraten. Die Samplerate muss genau die der Engine sein: Der Ring trägt Samples, keine Zeit, und 44,1 statt 48 kHz heißt 8 % zu langsam, ohne dass etwas es meldet. Und das Sample-Format wird nach Auflösung gewählt statt der Reihe nach — das null-Gerät von ALSA bietet i8 zuerst an, also acht Bit für Material, das analysiert werden soll. Das fiel erst beim Anschließen auf.

musik-schneiden schneidet an den Lücken; wo keine sind, am Klangwechsel, und das ist eine Schätzung. Ein gemixtes Set lässt sich nicht sauber schneiden — während einer Blende laufen zwei Lieder gleichzeitig, da gibt es keine Grenze, nur eine Stelle, an der man sie zieht. Findet sich beides nicht, wird nichts geschnitten und das gesagt.

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. Und die Ränder werden von der Stille befreit: Ohne das war jedes Stück genau eine Sekunde länger als seine Vorlage, und eine Sekunde Stille am Anfang ist genau das, wonach die Gliederung ein Intro sucht. Nachgemessen an drei aneinandergehängten Stücken: Abweichung 0,00 s.

⚠️ An echter Hardware ungeprüft. Der Weg ist gegen das null-Gerät von ALSA gelaufen — Auswahl, Rate, Format, Callback, Umrechnung auf Stereo. Ob ein Treiber tut, was seine Beschreibung sagt, weiß erst, wer ihn anschließt.

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~ &gt; ramp master.crossfader 1 32
1837056 38.272 deck1=80.049/16 deck2=31.039/16 &lt; 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.

Geprüft über einen echten MCP-Client per stdio gegen die laufende Anwendung — tools/list und tools/call, nicht an der Schnittstelle vorbei. Die Brücke nimmt beide SDK-Generationen: In mcp 2.x heißt FastMCP jetzt MCPServer unter einem anderen Modulpfad; gemessen an 1.29.1 und 2.1.1 sind tool(), Konstruktor und run() gleich geblieben, also nimmt eine Weiche von sechs Zeilen beides.

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.
  • Das null-Gerät von ALSA bietet acht Bit zuerst an. Die erste Fassung des Eingangs nahm die erste passende Konfiguration — acht Bit für Material, das analysiert werden soll. Es hätte funktioniert und niemand hätte es gemerkt.
  • 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 &#34;&#34; 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.
  • Der Eingang ist an echter Hardware ungeprüft. Gegen ALSAs null-Gerät gelaufen; ob ein Treiber Rate und Format liefert, die er verspricht, weiß erst, wer ihn anschließt.
  • 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.
  • Nichts ist je erzeugt worden. Der Generator ist nicht angefangen — keine Zeile in crates/. Suno hat keine öffentliche API; erster Adapter wäre ElevenLabs Music. Das ist die größte Lücke zwischen dem, was das Projekt tut, und dem, wofür es gebaut wird (docs/GENERATOR.md).
  • 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.
  • Auf Windows fehlt eine Named Pipe. Die Steuerung läuft dort über die Rückschleife mit Schlüssel; eine Named Pipe erbte die Rechte des Systems wie der Unix-Socket und käme ohne Schlüssel aus.
  • Pitch Bend fehlt; MIDI steht aus, ebenso Reverb und FLAC für den Mitschnitt.
  • Zwei Decks sind fest verdrahtet.

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).

Nachgemessen statt behauptet: Über alle 474 Pakete der Cargo.lock, gegen die entpackten Quellen aufgelöst — 203× MIT OR Apache-2.0, 95× MIT, 12× MPL-2.0, und kein einziges Copyleft ohne permissiven Zweig.

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.

Drei Spezifikationen für den Weg zu einem VibeMind-Space: FREIGABE.md (das Gatter, gebaut), SPACE.md (der Space), GENERATOR.md (Tracks erzeugen lassen).

Daneben: STEUERUNG.md, mcp/musik/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 29 commits August 17, 2026 22:28
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.
Auf Windows gibt es keine Unix-Sockets. Dort lief die Anlage bisher ohne
jede Steuerung — Oberfläche, Decks und Mixer ja, Fernsteuerung nein, und
damit auch kein MCP und keine Agenten. Also genau der Teil, um
dessentwillen das Projekt gebaut wird.

`musik-app --tcp` lauscht jetzt auf 127.0.0.1. Zwei Dinge sind dabei
enger gebaut als beim Socket, weil TCP von sich aus weiter offen steht:

Gebunden wird ausschließlich an die Rückschleife; eine andere Adresse
wird abgelehnt statt stillschweigend übernommen, und geprüft wird vor dem
Binden — danach läge zwischen Öffnen und Schließen genau das Fenster, das
man nicht haben will.

Die erste Zeile muss `auth <schlüssel>` sein, sonst wird nichts
ausgeführt, auch nichts Lesendes: Wer nicht hereindarf, soll nicht
erfahren, was gerade läuft. Der Schlüssel kommt aus dem Zufall des
Betriebssystems, entsteht bei jedem Start neu und steht nur im Fenster.

Er ist kein Zierrat. Die Rückschleife ist nicht privat — jedes Programm
auf dem Rechner erreicht sie, eine Webseite im Browser eingeschlossen.
Nachgestellt mit einer echten HTTP-Anfrage samt Rumpf `set
master.crossfader 1`: Jede Zeile bekam `err nicht angemeldet`, und der
Crossfader stand hinterher unverändert auf -0,5.

Die Bedienung einer Verbindung ist dafür generisch geworden statt in zwei
fast gleichen Kopien; ein kleiner Vertrag `Leitung` verlangt nur, was
gebraucht wird. Die MCP-Brücke spricht den Weg über MUSIK_TCP und
MUSIK_SCHLUESSEL und meldet einen abgelaufenen Schlüssel als solchen,
statt herumzuraten.

Am laufenden Programm nachgefahren: ohne Schlüssel abgewiesen, mit
falschem abgewiesen, mit richtigem `get` und `list` durch die echte
Brücke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Die beiden Bedingungen aus VIBEMIND.md sind jetzt nachgemessen statt
behauptet: Über alle 474 Pakete der Cargo.lock gibt es kein Copyleft ohne
permissiven Zweig. Die drei Pakete mit "GPL" im Lizenzfeld sind alle
mehrfachlizenziert.

Im echten Vibemind_V1 nachgesehen, nicht aus diesem Dokument abgeleitet:
Es gibt kein spaces/musik/, keinen Eintrag im Manifest, und der Brücke
fehlt das Freigabe-Gatter ganz — alle sechzehn Werkzeuge laufen frei.

Für ein Pult ist "jeder Schreibzugriff einzeln" unbrauchbar; sechzehn
Takte sind bei 128 BPM dreißig Sekunden. Deshalb vier Stufen mit der
Set-Freigabe als Bereich statt als Einzelfall, mit Anfang, Ende und
Eintrag in der Mitschrift.

Was der Anlage zum Ziel fehlt, nach Abhängigkeit sortiert: Der Generator
ist nicht angefangen, und Suno gibt es nicht zu haben. Dazu die Signale
ohne Quelle, zwei fest verdrahtete Decks, eine Analyse ohne gehörte
Wahrheit und zwei Modelle, die noch nie zugleich aufgelegt haben.

Die Reihenfolge stellt den Mix-Engineer nach vorn, weil seine Werkzeuge
fertig und gemessen sind, und den Generator ans Ende, weil er an einem
Anbieter, an Geld und an einer fremden Lizenzfrage hängt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Der Plan sagte, was zu tun ist. Diese drei sagen, wie, und sind so
geschrieben, dass sich hinterher prüfen lässt, ob es getan wurde.

FREIGABE.md: sieben Klassen nach dem, was ein Aufruf berührt, nicht nach
seinem Namen — bei über zweihundert Controls wäre eine Namensliste nach
dem nächsten Feature falsch. Die Set-Freigabe kommt aus einer Datei und
nicht aus der Umgebung, weil Widerrufen ohne Neustart gehen muss. Zwölf
Tests stehen als Liste dabei; der wichtigste hält fest, dass die Klasse
erzeugen über die Datei nie erreichbar ist.

Dazu die Grenze, die dazugehört: Das Gatter schützt davor, dass ein Agent
seine Werkzeuge missbraucht, nicht vor etwas mit Socket-Zugang. Eine
Spezifikation, die das verschweigt, verspricht Sicherheit, die sie nicht
hat.

SPACE.md: Aufbau nach dem Muster von spaces/marketing, samt dessen
Beweis-Disziplin — historischer Schnappschuss, konfiguriert/statisch und
verified_live sind zu trennen. Sieben Hard Rules; die erste verbietet
Shell- und Dateiwerkzeuge in diesem Space, weil sonst alle anderen
bedeutungslos sind. Crowd-Sensor und Energie-Analyst fehlen bewusst: Die
Signale haben keine Quelle, und ein Agent, der eine erfindet, ist
schlimmer als keiner.

GENERATOR.md: Trait mit getrenntem Beauftragen und Nachsehen, weil jeder
Anbieter asynchron ist. BPM und Tonart als Parameter statt in der Prosa,
und daneben gemessen.json — bestellt ist nicht geliefert, und über viele
Aufträge wird daraus eine Trefferquote je Anbieter. Die Mechanik zuerst
gegen eine Attrappe, der Anbieter zuletzt; andersherum hängt die
Architektur an Eigenheiten eines Anbieters, den es vielleicht nicht
bleibt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Bisher liefen alle sechzehn Werkzeuge frei. Hinter einem Unix-Socket ist
das richtig — wer ihn öffnen darf, darf auch auflegen. Für einen Agenten
ist es das nicht.

Sieben Klassen nach dem, was ein Aufruf berührt, nicht nach seinem Namen.
Alle 194 Controls der laufenden Anlage sind zugeordnet; die Liste liegt
als mcp/controls.txt daneben, und ein Test hält dagegen. Ein Control ohne
Klasse wird abgewiesen statt geraten — der Preis ist ein Eintrag je
Feature, und der ist kleiner als eine Freigabe, die mehr erlaubt als
gedacht.

Die Set-Freigabe steht in einer Datei und nicht in der Umgebung. Eine
Variable steht fest, sobald der Prozess läuft; wer mitten im Set merkt,
dass der Agent Unsinn macht, muss ohne Neustart widerrufen können.
Nachgefahren: Datei gelöscht, nächster Aufruf abgewiesen, kein Neustart.

Fail-closed in sechs Fällen, darunter zwei, die leicht durchrutschen: Ein
`bis` ohne Zeitzone wird abgewiesen statt als Ortszeit geraten, und ein
Tippfehler in `klassen` verwirft die ganze Datei statt drei von vier
Klassen still weiterlaufen zu lassen.

`erzeugen` ist über die Datei nicht erreichbar, auch wenn es darin steht.
Der Test dazu ist der wichtigste der achtzehn: Ohne ihn kommt die Klasse
später aus Bequemlichkeit doch hinein, und dann gibt ein Agent unbemerkt
Geld aus.

Dazu `note <text>` im Protokoll — eine Zeile in die Mitschrift, sonst
nichts. Die Bruecke schickt sie einmal je Freigabe-Fenster. Unter welcher
Vollmacht ein Set gefahren wurde, ist die Frage, die man hinterher
stellt, und sie stand bisher nirgends. Fünf Aufrufe unter einem Fenster
ergaben eine Notiz, ein zweites Fenster eine zweite.

Die achtzehn Wächter laufen jetzt in der CI. Ohne das wären sie
Dekoration: Sie liefen nur, wenn jemand daran denkt.

Was das Gatter nicht kann, steht in der Spec und im Modulkopf: Es
schützt davor, dass ein Agent seine Werkzeuge missbraucht, nicht vor
etwas, das schon Socket-Zugang hat.

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

Schritt 2 aus docs/SPACE.md: mcp/musik_mcp.py wird mcp/musik/ mit
server.py, freigabe.py, tests/ und pyproject.toml — dieselbe Form, die
mcp/docker/ in VibeMind hat, samt benanntem Einstieg musik-mcp.

Beim Umstellen auf das SDK kam ein Fund dazwischen. Die Spec sagte
`from mcp.server.fastmcp import FastMCP`, weil VibeMinds Vorbild das so
macht. Nachgesehen: In mcp 2.x heißt die Klasse MCPServer und liegt unter
mcp.server.mcpserver. VibeMinds mcp/docker/pyproject.toml deklariert
`dependencies = ["mcp"]` — ungepinnt. Die nächste frische Installation
zieht 2.x und bricht.

Statt den alten Pfad zu erben, steht hier eine Weiche. Gemessen an 1.29.1
und 2.1.1 sind tool(), Konstruktor und run() gleich geblieben, nur Name
und Modulpfad nicht; sechs Zeilen try/except nehmen beides. Der Server
ist unter beiden geladen worden, jedes Mal mit sechzehn Werkzeugen.

Abgenommen wie in der Spec verlangt: tools/list über einen echten
MCP-Client per stdio gegen die laufende Anlage zeigt dieselben sechzehn
Werkzeuge; musik_get liefert 127.986351, und musik_set wird ohne Freigabe
auch auf diesem Weg abgewiesen.

Der Fund gehört VibeMind gemeldet: mcp/docker/ sollte mcp<2 pinnen oder
dieselbe Weiche bekommen. Steht in docs/SPACE.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Beim Anlegen hat VibeMinds eigener Validator ihn abgewiesen:
invalid-evidence-state. Fuer ein Kind des Superprojekts kennt das
Manifest nur local-git mit echtem Gitlink oder pinned-remote-tree mit
dem Pin des Elternteils; "angemeldet, noch nicht gepinnt" gibt es nicht.

Ein echter Pin ginge auch nicht: main steht bei 3f4faf7, dem Stand vor
PR #1 — eine Anlage ohne Bruecke, ohne Gatter, ohne Repertoire. Den
PR-Branch zu pinnen hiesse, einen Commit festzuhalten, den ein
Squash-Merge verwaist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGFKXoHmTkrnmQa8nav65p
Gefunden beim ersten Lauf der ganzen Kette — ein Agent fährt einen Übergang,
der Kritiker soll ihn wiederfinden (siehe N4 in FAHRPLAN.md).

`pegel()` rechnet einen Wert je Sekunde. `uebergang_bericht()` suchte den
passenden Wert mit `s / 2.0` und beschrieb damit systematisch eine Stelle bei
halber Zeit. Gemeldet wurden „64 % Einbruch in der Mitte" der Blende samt Rat
zur Crossfader-Kurve; gemeint war der Break des ausgehenden Tracks, fünfzehn
Sekunden vor dem Griff, an dem kein Regler bewegt worden war.

Nichts daran sah nach einem Fehler aus. Der Bericht war vollständig, die Zahlen
plausibel, und Beat, Phrasenlage und Tempo daneben stimmten. Genau deshalb
steht die Umrechnung jetzt in `pegel_um()` und nicht mitten in einer
`println!`-Strecke: In einer Druckfolge sieht man so etwas nie, in einer
Funktion mit Rückgabewert schon.

Mit dem richtigen Fenster sind es 79 % — und die Ursache ist eine andere.

Deshalb zweitens: Die Warnung darüber trennt jetzt zwei Fälle, die im
Mitschnitt gleich aussehen und Verschiedenes wollen.

* `Kurve` — die Ränder sind gleich laut, die Mitte nicht. Dann hilft
  `master.crossfader_curve`, wie bisher gesagt.
* `Stufe` — die Ränder sind verschieden laut. Dann liegt es am Material, und
  eine härtere Kurve verdeckt den Unterschied nur. Der gemessene Fall: ein Drop
  mit Kick und Bass (0,380) ging in ein reines Akkord-Intro (0,081), zwölf
  Dezibel, und der Crossfader stand ohnehin auf Konstant-Leistung.
* `Unklar` — auf einer Seite fehlt der Vergleich, etwa bei einem Übergang in
  den ersten Sekunden eines Mitschnitts. Dann wird der Einbruch genannt und
  die Ursache offen gelassen, statt die häufigere zu raten.

Beide Ränder stehen jetzt in der Meldung, damit ein Leser das Urteil prüfen
kann, statt es zu übernehmen.

Fünf Prüfungen dazu, alle rein. Sieben Defekte einzeln wieder eingebaut, jeder
färbte den vorgesehenen Test rot — auch der Faktor zwei, der vorher von keiner
einzigen Zeile gehalten wurde.

Docs: N4 in FAHRPLAN.md; SPACE.md und VIBEMIND.md tragen den Stand der fünf
Schritte nach (1, 2, 3, 5 stehen; 4 wartet auf den Merge).

546 Tests grün, fmt und clippy sauber.

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

Flissel commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

Nachtrag: die ganze Kette ist einmal durchgelaufen — und hat einen Fehler im Kritiker gefunden

Der Beschreibungstext oben nennt 541 Tests; es sind jetzt 546. Der Unterschied ist ein Befund, und er gehört unter „Funde, die erst das laufende Programm zeigte".

Bis hierhin war jede Schicht für sich geprüft. Neu ist der erste Lauf über alle Schichten samt Agent — der Mix-Engineer liegt in spaces/musik/ drüben in VibeMind, der Rest ist diese Anlage:

Mix-Engineer → api → MCP-Client → Brücke → Freigabe-Gatter → Protokoll
  → Socket → Steuerpult → Mixer → Mitschnitt → musik-kritik

Zweimal gefahren, gleiches Ergebnis: gewählt wurde blende über 32 Beats mit genanntem Grund, gesetzt 0,04 Beats in die Phrase, gefahren 32,0 von 32 Beats in 15,0 s, Tempo 127,99 → 127,96 BPM, kein Frame verworfen. Der Kritiker fand den Übergang wieder und ordnete ihn dem Griff zu. master.transitions stand hinterher auf blende.

Der Fund: der Pegelbericht las eine Sekunde als zwei

pegel() rechnet einen Wert je Sekunde. uebergang_bericht() suchte den passenden Wert mit s / 2.0 — und beschrieb damit systematisch eine Stelle bei halber Zeit.

Konkret gemeldet wurde:

Pegel in der Blende: 0.142 gegen 0.394 außen
⚠ 64 % Einbruch in der Mitte. Das ist die Crossfader-Kurve;
   `master.crossfader_curve` härter stellen.

Keine der beiden Zahlen kam aus der Blende. 0,142 ist der Pegel zwischen Sekunde 12 und 20 des Mitschnitts — der Break des ausgehenden Tracks, sechs Sekunden vor dem Griff, an dem kein Regler bewegt worden war. Der Rat darüber betraf also eine Stelle, die niemand gefahren hat.

Nichts daran sah nach einem Fehler aus: Der Bericht war vollständig, die Zahlen plausibel, und Beat, Phrasenlage und Tempo daneben stimmten. Deshalb steht die Umrechnung jetzt in pegel_um() und nicht mitten in einer println!-Strecke — in einer Druckfolge sieht man so etwas nie, in einer Funktion mit Rückgabewert schon.

Mit dem richtigen Fenster sind es 79 %, und die Ursache ist eine andere. Die Warnung trennt jetzt drei Fälle, die im Mitschnitt gleich aussehen:

Wann Was hilft
Kurve die Ränder sind gleich laut, die Mitte nicht master.crossfader_curve, wie bisher gesagt
Stufe die Ränder sind verschieden laut Gain oder eine andere Einsatzstelle — die Kurve verdeckt es nur
Unklar auf einer Seite fehlt der Vergleich nichts; dann wird die Ursache offen gelassen statt geraten

Der gemessene Fall war eine Stufe: ein Drop mit Kick und Bass (0,380) ging in ein reines Akkord-Intro (0,081), zwölf Dezibel, und der Crossfader stand ohnehin auf Konstant-Leistung. Beide Ränder stehen jetzt in der Meldung, damit ein Leser das Urteil prüfen kann, statt es zu übernehmen.

Fünf Prüfungen dazu, alle rein. Sieben Defekte einzeln wieder eingebaut, jeder färbte den vorgesehenen Test rot — auch der Faktor zwei, den vorher keine einzige Zeile gehalten hat.

Vier Anläufe davor haben nichts gemessen

Drei davon waren die Anlage, die sich weigerte, etwas zu behaupten, und das ist der Grund, sie hier aufzuschreiben:

  1. Freigabe als JSON geschrieben → keine gültige Freigabe (klassen fehlt). Die Datei ist ein Zeilenformat.
  2. Kanalzüge stehen beim Start auf 0 → 25 s Stille, und der Kritiker sagte der Mitschnitt ist still.
  3. Beide Stücke in derselben Tonart → Kein Übergang gefunden. Der Kritiker vergleicht Chroma; für ihn war das ein Stück. musik-material baut alle Fälle auf demselben Grundton — richtig für das, was es prüft, unbrauchbar hierfür.
  4. Ein Mitschnitt von 26 s → nichts gefunden. Der Rückblick beträgt 16 s; mit vier Sekunden Vorlauf gibt es nichts zu vergleichen.

Stand der fünf Schritte aus docs/SPACE.md: 1, 2, 3 und 5 stehen; 4 (Manifest-Eintrag) wartet weiter auf den Merge dieses Zweigs, weil er einen Pin braucht und main noch bei 3f4faf7 steht. Zahlen und Reproduktion: spaces/musik/docs/ABNAHME.md in VibeMind, Zusammenfassung als N4 in docs/FAHRPLAN.md.

546 Tests grün, fmt und clippy --all-targets -D warnings sauber.


Generated by Claude Code

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