Radio: Lautstaerke im Panel -- und leise reinkommen statt erschrecken
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s

Der Anlass ist eine Beobachtung aus dem Betrieb: der Bot kam mit voller
Aussteuerung in den Kanal, und wer schon drin sass, ist erschrocken. Dass
sich jeder den Bot fuer sich leiser drehen kann, hilft erst *nachdem* es zu
laut war. Also liegt die Vorgabe jetzt bei 50 %, und lauter machen kostet
zwei Knopfdruecke.

Dafuer musste die Tonkette umgebaut werden. Bisher lief Opus unveraendert
durch (ffmpeg -> Ogg/Opus -> Discord). Das war eine Stufe sparsamer, aber an
fertigen Opus-Paketen laesst sich die Lautstaerke nicht drehen -- dafuer muss
man an die Abtastwerte. Jetzt: ffmpeg gibt PCM aus, die Sprachbibliothek
regelt und kodiert.

Der alte Einwand im Dateikopf ("sonst muesste @discordjs/opus mit ins Image,
und das will gebaut werden") stimmt weiterhin -- fuer @discordjs/opus.
Nachgemessen am 14.08.2026 in node:22-slim:

  @discordjs/opus  npm i bricht ab, kein Prebuild fuer node-v127/glibc-2.36,
                   node-gyp will bauen, im Image fehlen die Werkzeuge
  opusscript       reines JavaScript, installiert in 740 ms, kein Compiler
                   0,3 % eines Kerns fuers Kodieren eines Dauerstroms

Der Umweg ueber Opus-Dekodieren entfaellt dabei ganz, ffmpeg liefert ja schon
Abtastwerte. Deshalb 0,3 % und nicht 2,9 %, was Dekodieren und Kodieren
zusammen gekostet haetten.

Die Kette ist mit echtem ffmpeg gegen einen Sinuston geprueft: 200
Opus-Pakete fuer 4,0 s Ton, kein Versatz, und die Lautstaerke laesst sich
mitten im Strom aendern. Genau darum geht der Weg ueber PCM und nicht ueber
ffmpegs volume-Filter -- der haette bei jedem Klick einen Neustart des
Prozesses gebraucht, also eine hoerbare Luecke.

Am Panel zwei Knoepfe in der bestehenden Reihe, Schritte von 10, Bereich 10
bis 200. Am Anschlag graut der Knopf aus, statt sich druecken zu lassen und
nichts zu tun. Die Rueckmeldung ist die neu gezeichnete Tafel mit dem neuen
Prozentwert.

Der Wert steht in den Einstellungen, nicht in radio_state: der Merker dort
wird beim Stoppen geloescht, die Lautstaerke soll das ueberleben.

Dazu der Kanal-Status: was laeuft, steht jetzt auch in der Statuszeile des
Sprachkanals und wird mit dem Titel nachgefuehrt.

  ACHTUNG, das ist ein UNDOKUMENTIERTER Endpunkt.
  PUT /channels/{id}/voice-status -- Discord hat ihn nie in die API-Doku
  aufgenommen, die Pull Requests dazu liegen seit 2023 offen, und discord.js
  hat die Umsetzung als "not planned" geschlossen. Roher Aufruf ueber
  client.rest also, und er kann ohne Ankuendigung verschwinden.

Deshalb ist er als Beiwerk gebaut: faellt er aus, sagt er einmal warum und
schweigt dann -- ein Fehler alle 30 Sekunden waere kein Hinweis mehr,
sondern Rauschen. Das Radio laeuft in jedem Fall weiter. Der naechste
Sender-Start versucht es erneut, falls das Recht inzwischen erteilt wurde.

Das Recht heisst "Kanalstatus festlegen" und sitzt auf Bit 48. discord.js
kennt es nicht einmal als Konstante, deshalb steht die Zahl im Quelltext.
Ungeprueft, weil dafuer ein echter Sprachkanal noetig ist: ob das Recht dem
Bot hier tatsaechlich erteilt ist. Faellt es aus, steht der Grund im Log.

Beim Stoppen wird die Zeile geleert -- sonst behauptet sie stundenlang, es
liefe ein Lied, das laengst vorbei ist.

Nebenbei: prism-media wurde direkt importiert, stand aber nie in der
package.json. Der Import ist mit dem Ogg-Auspacker weggefallen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-14 12:53:16 +02:00
co-authored by Claude Opus 5
parent f08a0e8b35
commit 1154e7a6a5
5 changed files with 201 additions and 29 deletions
+23
View File
@@ -342,3 +342,26 @@ export function panelUrl() {
export function panelZeichen() {
return getSetting('panel_zeichen') || '';
}
/**
* Lautstärke des Radios, in Prozent.
*
* Die Vorgabe ist 50 und nicht 100, und das ist eine Beobachtung aus dem
* Betrieb, keine Geschmacksfrage: mit voller Aussteuerung kommt der Bot in
* den Kanal, und wer schon drin sitzt, erschrickt. Jeder kann ihn zwar für
* sich selbst leiser drehen — aber erst *nachdem* es zu laut war. Also liegt
* die Vorgabe auf der leisen Seite; lauter machen kostet zwei Knopfdrücke.
*
* Bewusst hier und nicht in `radio_state`: der Merker dort wird beim Stoppen
* gelöscht, die eingestellte Lautstärke soll das überleben.
*/
export const LAUT_MIN = 10;
export const LAUT_MAX = 200;
export const LAUT_SCHRITT = 10;
export const LAUT_VORGABE = 50;
export function radioLautstaerke() {
const roh = Number(getSetting('radio_lautstaerke'));
if (!Number.isFinite(roh)) return LAUT_VORGABE;
return Math.min(LAUT_MAX, Math.max(LAUT_MIN, Math.round(roh)));
}