Add DJ Console design document
Comprehensive design doc for the live DJ mixing feature: - Dual-deck library mixing with crossfader (constant-power curve) - External audio input: local sound card (ALSA/Pulse/JACK) and network audio (Icecast source protocol for remote DJs) - Session lifecycle with auto-playlist pause/resume and watchdog - API endpoints, backend classes, frontend layout - Phased implementation plan - Open questions for team discussion
This commit is contained in:
parent
ef3e1eab47
commit
3ddd86f8ab
|
|
@ -0,0 +1,517 @@
|
||||||
|
#+TITLE: DJ Console — Design Document
|
||||||
|
#+AUTHOR: Glenn Thompson
|
||||||
|
#+DATE: 2026-03-05
|
||||||
|
|
||||||
|
* Overview
|
||||||
|
|
||||||
|
The DJ Console is a new feature for Asteroid Radio that allows DJs to mix
|
||||||
|
tracks live from the station's music library, or connect external audio
|
||||||
|
equipment (turntables, CDJs, mixers, etc.) to broadcast live sets.
|
||||||
|
|
||||||
|
The console is a dedicated web page at =/asteroid/dj=, accessible to users
|
||||||
|
with the =:dj= or =:admin= role. It provides a dual-deck interface with
|
||||||
|
crossfader, per-deck volume, library search, and external audio input —
|
||||||
|
all feeding into the existing Harmony mixer and streaming pipeline.
|
||||||
|
|
||||||
|
** Why This Works
|
||||||
|
|
||||||
|
The current CL-Streamer architecture makes this straightforward:
|
||||||
|
|
||||||
|
- *Harmony's mixer already sums all active voices.* The streaming drain,
|
||||||
|
encoders (LAME, FDK-AAC), and broadcast buffers don't need to change.
|
||||||
|
A DJ session is just manual control over which voices are playing and
|
||||||
|
at what volume.
|
||||||
|
|
||||||
|
- *play-file already creates Harmony voices.* Each deck loads a track
|
||||||
|
the same way the auto-playlist does — the difference is that the DJ
|
||||||
|
controls when to start, stop, and crossfade rather than the
|
||||||
|
=play-list= loop.
|
||||||
|
|
||||||
|
- *External audio input* is another voice on the mixer. Whether it
|
||||||
|
comes from a local sound card (ALSA/PulseAudio/JACK) or a network
|
||||||
|
audio stream, once it's a Harmony source, it's mixed identically to
|
||||||
|
library tracks.
|
||||||
|
|
||||||
|
* Architecture
|
||||||
|
|
||||||
|
** System Diagram
|
||||||
|
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
|
│ SBCL Process │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ DJ Session (dj-session.lisp) │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌──────────┐ ┌──────────┐ │ │
|
||||||
|
│ │ │ DECK A │ │ DECK B │ │ │
|
||||||
|
│ │ │ voice-a │ │ voice-b │ │ │
|
||||||
|
│ │ │ vol: 0.8 │ │ vol: 0.6 │ │ │
|
||||||
|
│ │ │ state: │ │ state: │ │ │
|
||||||
|
│ │ │ playing │ │ cued │ │ │
|
||||||
|
│ │ └────┬─────┘ └────┬─────┘ │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ │ ┌──────────────┐ │ │ │
|
||||||
|
│ │ │ │ EXTERNAL IN │ │ │ │
|
||||||
|
│ │ │ │ (optional) │ │ │ │
|
||||||
|
│ │ │ │ voice-ext │ │ │ │
|
||||||
|
│ │ │ └──────┬───────┘ │ │ │
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
│ │ ┌────▼─────────▼─────────▼────┐ │ │
|
||||||
|
│ │ │ CROSSFADER (0.0 — 1.0) │ │ │
|
||||||
|
│ │ │ Adjusts A/B balance │ │ │
|
||||||
|
│ │ └─────────────┬───────────────┘ │ │
|
||||||
|
│ └────────────────│────────────────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌────▼────┐ │
|
||||||
|
│ │ Harmony │ │
|
||||||
|
│ │ Mixer │ (sums all active voices) │
|
||||||
|
│ └────┬────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌────────────────▼──────────────────────────────────┐ │
|
||||||
|
│ │ streaming-drain (unchanged) │ │
|
||||||
|
│ │ float→s16 → LAME (MP3) + FDK-AAC (AAC) │ │
|
||||||
|
│ │ → broadcast buffers → HTTP clients │ │
|
||||||
|
│ └────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌───────────────────────┐ ┌────────────────────────┐ │
|
||||||
|
│ │ Auto-Playlist │ │ Radiance Web Server │ │
|
||||||
|
│ │ (paused while DJ live) │ │ (port 8080) │ │
|
||||||
|
│ └───────────────────────┘ └────────────────────────┘ │
|
||||||
|
└──────────────────────────────────────────────────────────────┘
|
||||||
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
** Audio Flow
|
||||||
|
|
||||||
|
When a DJ session is active:
|
||||||
|
|
||||||
|
1. The auto-playlist (=play-list= loop) is paused — its current
|
||||||
|
position is saved for seamless resume later.
|
||||||
|
2. The DJ loads tracks onto Deck A and/or Deck B via the library search.
|
||||||
|
3. Each deck creates a Harmony voice. =play-file= is called with
|
||||||
|
=:on-end :disconnect= so the voice cleans up when the track ends.
|
||||||
|
4. The crossfader adjusts the =(mixed:volume)= of each deck's voice:
|
||||||
|
- Position 0.0: Deck A at full volume, Deck B silent
|
||||||
|
- Position 0.5: Both at equal volume
|
||||||
|
- Position 1.0: Deck B at full volume, Deck A silent
|
||||||
|
5. Per-deck volume knobs provide independent gain before the crossfader.
|
||||||
|
6. Harmony's mixer sums all active voices (both decks + external input
|
||||||
|
if connected).
|
||||||
|
7. The streaming drain captures the mixed output — identical to
|
||||||
|
auto-playlist mode. Encoders and listeners are unaware of the
|
||||||
|
source change.
|
||||||
|
|
||||||
|
When the DJ ends the session:
|
||||||
|
|
||||||
|
1. Active decks fade out over 3 seconds (=volume-ramp=).
|
||||||
|
2. The auto-playlist resumes from the saved position.
|
||||||
|
3. ICY metadata updates to show the resumed track.
|
||||||
|
|
||||||
|
* External Audio Input
|
||||||
|
|
||||||
|
** Use Cases
|
||||||
|
|
||||||
|
- *Turntable/CDJ setup*: DJ connects external hardware to the server's
|
||||||
|
sound card (line-in, USB audio interface)
|
||||||
|
- *Remote DJ*: DJ streams audio over the network from their own
|
||||||
|
equipment/software (e.g., Traktor, Serato, Ableton, BUTT)
|
||||||
|
- *Hybrid set*: DJ mixes between library tracks on the decks and
|
||||||
|
external audio input
|
||||||
|
|
||||||
|
** Option 1: Local Sound Card Input (ALSA/PulseAudio/JACK)
|
||||||
|
|
||||||
|
For DJs with physical access to the server (or a connected audio
|
||||||
|
interface), cl-mixed can capture from ALSA, PulseAudio, or JACK:
|
||||||
|
|
||||||
|
- =cl-mixed-pulse= — PulseAudio source capture
|
||||||
|
- =cl-mixed-alsa= — ALSA device capture
|
||||||
|
- =cl-mixed-jack= — JACK audio connection
|
||||||
|
|
||||||
|
The captured audio becomes a Harmony source that feeds into the mixer
|
||||||
|
like any other voice. This is the lowest-latency option.
|
||||||
|
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
┌──────────────┐ ┌───────────┐ ┌─────────────┐
|
||||||
|
│ Turntable / │───▶│ USB Audio │───▶│ ALSA/Pulse │
|
||||||
|
│ CDJ / Mixer │ │ Interface │ │ Capture │
|
||||||
|
└──────────────┘ └───────────┘ └──────┬──────┘
|
||||||
|
│
|
||||||
|
┌───────▼───────┐
|
||||||
|
│ Harmony Voice │
|
||||||
|
│ (ext-input) │
|
||||||
|
└───────┬───────┘
|
||||||
|
│
|
||||||
|
┌───────▼───────┐
|
||||||
|
│ Harmony Mixer │
|
||||||
|
│ → encoders → │
|
||||||
|
│ listeners │
|
||||||
|
└───────────────┘
|
||||||
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
** Option 2: Network Audio Input (Remote DJ)
|
||||||
|
|
||||||
|
For remote DJs, the server accepts an incoming audio stream. The DJ
|
||||||
|
sends audio from their software (BUTT, Traktor, etc.) over HTTP or
|
||||||
|
Icecast protocol to a receive endpoint on the CL-Streamer server.
|
||||||
|
|
||||||
|
The incoming compressed audio (MP3 or Ogg) is decoded and fed into
|
||||||
|
Harmony's mixer as another voice.
|
||||||
|
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
┌──────────────┐ ┌──────────────────┐
|
||||||
|
│ DJ Software │ ──HTTP──▶│ CL-Streamer │
|
||||||
|
│ (BUTT, │ or SRT │ /dj-input mount │
|
||||||
|
│ Traktor, │ │ │
|
||||||
|
│ etc.) │ │ decode → Harmony │
|
||||||
|
└──────────────┘ │ voice (ext-input)│
|
||||||
|
└────────┬─────────┘
|
||||||
|
│
|
||||||
|
┌────────▼─────────┐
|
||||||
|
│ Harmony Mixer │
|
||||||
|
│ → encoders → │
|
||||||
|
│ listeners │
|
||||||
|
└──────────────────┘
|
||||||
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
Implementation options:
|
||||||
|
- *Icecast source protocol*: DJ software connects as an Icecast source
|
||||||
|
client (=SOURCE /dj-input HTTP/1.0=). CL-Streamer accepts the
|
||||||
|
connection, decodes the incoming MP3/Ogg stream via cl-mixed, and
|
||||||
|
routes it to the mixer. Most DJ software already supports this.
|
||||||
|
- *SRT (Secure Reliable Transport)*: Lower latency for remote DJs.
|
||||||
|
Would require SRT library integration (future).
|
||||||
|
- *WebRTC*: Browser-based DJ could stream from their mic/mixer via
|
||||||
|
WebRTC. Complex but enables browser-only DJ workflow (future).
|
||||||
|
|
||||||
|
** Option 3: Hybrid
|
||||||
|
|
||||||
|
Both options can be active simultaneously. The DJ console UI shows an
|
||||||
|
"External Input" channel alongside Deck A and Deck B, with its own
|
||||||
|
volume control and mute button. The crossfader operates on Deck A/B;
|
||||||
|
the external input has independent volume.
|
||||||
|
|
||||||
|
* Access Control
|
||||||
|
|
||||||
|
** Route and Roles
|
||||||
|
|
||||||
|
- *URL*: =/asteroid/dj=
|
||||||
|
- *Required role*: =:dj= (which also grants access to =:admin= users,
|
||||||
|
since admin inherits all lower roles)
|
||||||
|
- *Single session*: Only one DJ session can be active at a time. The
|
||||||
|
session is owned by the user who started it.
|
||||||
|
- *Admin override*: Admin users can end any DJ session (e.g., if a DJ
|
||||||
|
disconnects without ending cleanly)
|
||||||
|
|
||||||
|
** Session Lifecycle
|
||||||
|
|
||||||
|
1. DJ navigates to =/asteroid/dj= and clicks "Go Live"
|
||||||
|
2. Server pauses the auto-playlist and creates a =dj-session= struct
|
||||||
|
3. DJ loads tracks, mixes, broadcasts
|
||||||
|
4. DJ clicks "End Session" — decks fade out, auto-playlist resumes
|
||||||
|
5. If DJ disconnects without ending: a watchdog timer (configurable,
|
||||||
|
default 60s of no API polls) auto-ends the session
|
||||||
|
|
||||||
|
* Backend Design
|
||||||
|
|
||||||
|
** New file: =dj-session.lisp=
|
||||||
|
|
||||||
|
*** DJ Session State
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(defclass dj-session ()
|
||||||
|
((owner :initarg :owner :accessor session-owner
|
||||||
|
:documentation "Username of the DJ who owns this session")
|
||||||
|
(started-at :initarg :started-at :accessor session-started-at
|
||||||
|
:initform (get-universal-time))
|
||||||
|
(deck-a :initform (make-instance 'dj-deck :name :a) :accessor session-deck-a)
|
||||||
|
(deck-b :initform (make-instance 'dj-deck :name :b) :accessor session-deck-b)
|
||||||
|
(crossfader :initform 0.5 :accessor session-crossfader
|
||||||
|
:documentation "0.0 = all A, 1.0 = all B")
|
||||||
|
(external-input :initform nil :accessor session-external-input
|
||||||
|
:documentation "External audio voice, or NIL if not connected")
|
||||||
|
(external-volume :initform 1.0 :accessor session-external-volume)
|
||||||
|
(last-poll :initform (get-universal-time) :accessor session-last-poll
|
||||||
|
:documentation "Timestamp of last UI poll, for watchdog")
|
||||||
|
(saved-playlist-state :initform nil :accessor session-saved-playlist-state
|
||||||
|
:documentation "Auto-playlist state saved on session start")))
|
||||||
|
|
||||||
|
(defclass dj-deck ()
|
||||||
|
((name :initarg :name :accessor deck-name :documentation ":a or :b")
|
||||||
|
(file-path :initform nil :accessor deck-file-path)
|
||||||
|
(voice :initform nil :accessor deck-voice
|
||||||
|
:documentation "Harmony voice, or NIL if not loaded/playing")
|
||||||
|
(volume :initform 1.0 :accessor deck-volume
|
||||||
|
:documentation "Per-deck volume before crossfader")
|
||||||
|
(state :initform :empty :accessor deck-state
|
||||||
|
:documentation ":empty, :loaded, :playing, :paused")
|
||||||
|
(track-info :initform nil :accessor deck-track-info
|
||||||
|
:documentation "Plist: (:artist :title :album :file :display-title)")))
|
||||||
|
|
||||||
|
(defvar *dj-session* nil
|
||||||
|
"The currently active DJ session, or NIL if no DJ is live.")
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
*** Key Functions
|
||||||
|
|
||||||
|
| Function | Description |
|
||||||
|
|---------------------------------+-----------------------------------------------------|
|
||||||
|
| =start-dj-session (user)= | Pause auto-playlist, create session |
|
||||||
|
| =end-dj-session ()= | Fade out decks, resume auto-playlist |
|
||||||
|
| =load-deck (deck file-path)= | Load a track onto deck (reads metadata, creates voice) |
|
||||||
|
| =play-deck (deck)= | Start playback on a loaded/paused deck |
|
||||||
|
| =pause-deck (deck)= | Pause a playing deck |
|
||||||
|
| =stop-deck (deck)= | Stop and unload a deck |
|
||||||
|
| =seek-deck (deck position)= | Seek to position (seconds) |
|
||||||
|
| =set-crossfader (position)= | Set crossfader 0.0–1.0, updates deck volumes |
|
||||||
|
| =set-deck-volume (deck vol)= | Set per-deck volume 0.0–1.0 |
|
||||||
|
| =connect-external-input (src)= | Connect external audio source to mixer |
|
||||||
|
| =disconnect-external-input ()= | Disconnect external audio |
|
||||||
|
| =set-external-volume (vol)= | Set external input volume |
|
||||||
|
| =dj-session-status ()= | Returns full state for UI polling |
|
||||||
|
| =dj-watchdog-check ()= | Called by cl-cron; ends stale sessions |
|
||||||
|
|
||||||
|
*** Crossfader Volume Calculation
|
||||||
|
|
||||||
|
The crossfader applies a constant-power curve so the perceived volume
|
||||||
|
stays consistent across the sweep:
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(defun crossfader-volumes (position)
|
||||||
|
"Return (values vol-a vol-b) for crossfader position 0.0–1.0.
|
||||||
|
Uses constant-power (equal-power) curve."
|
||||||
|
(let ((angle (* position (/ pi 2.0))))
|
||||||
|
(values (cos angle) ;; Deck A: 1.0 at 0.0, 0.0 at 1.0
|
||||||
|
(sin angle)))) ;; Deck B: 0.0 at 0.0, 1.0 at 1.0
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
The effective volume for each deck is:
|
||||||
|
=effective-volume = deck-volume * crossfader-component=
|
||||||
|
|
||||||
|
This is applied by setting =(mixed:volume voice)= on each deck's
|
||||||
|
Harmony voice.
|
||||||
|
|
||||||
|
*** ICY Metadata During DJ Session
|
||||||
|
|
||||||
|
When the DJ is live, ICY metadata updates to reflect the currently
|
||||||
|
audible track. Options:
|
||||||
|
|
||||||
|
- *Auto-detect*: Whichever deck is louder (based on crossfader
|
||||||
|
position) determines the displayed track
|
||||||
|
- *Manual override*: DJ can set custom metadata text (e.g., "DJ Fade
|
||||||
|
— Live Set")
|
||||||
|
- *Session default*: Show "DJ <username> — Live" when the session starts
|
||||||
|
|
||||||
|
* API Endpoints
|
||||||
|
|
||||||
|
All endpoints require =:dj= role.
|
||||||
|
|
||||||
|
** Session Management
|
||||||
|
|
||||||
|
| Endpoint | Method | Description |
|
||||||
|
|-----------------------------------+--------+--------------------------------------|
|
||||||
|
| =asteroid/dj/session/start= | POST | Start DJ session |
|
||||||
|
| =asteroid/dj/session/end= | POST | End session, resume auto-playlist |
|
||||||
|
| =asteroid/dj/session/status= | GET | Full state (polled by UI every 500ms) |
|
||||||
|
| =asteroid/dj/session/metadata= | POST | Set custom ICY metadata text |
|
||||||
|
|
||||||
|
** Deck Control
|
||||||
|
|
||||||
|
| Endpoint | Method | Params | Description |
|
||||||
|
|-----------------------------------+--------+--------------------------+----------------------|
|
||||||
|
| =asteroid/dj/deck/load= | POST | deck (a/b), track-id | Load track onto deck |
|
||||||
|
| =asteroid/dj/deck/play= | POST | deck | Play |
|
||||||
|
| =asteroid/dj/deck/pause= | POST | deck | Pause |
|
||||||
|
| =asteroid/dj/deck/stop= | POST | deck | Stop and unload |
|
||||||
|
| =asteroid/dj/deck/seek= | POST | deck, position (seconds) | Seek |
|
||||||
|
| =asteroid/dj/deck/volume= | POST | deck, volume (0.0–1.0) | Set deck volume |
|
||||||
|
|
||||||
|
** Crossfader
|
||||||
|
|
||||||
|
| Endpoint | Method | Params | Description |
|
||||||
|
|-----------------------------------+--------+---------------------+----------------------|
|
||||||
|
| =asteroid/dj/crossfader= | POST | position (0.0–1.0) | Set crossfader |
|
||||||
|
|
||||||
|
** External Input
|
||||||
|
|
||||||
|
| Endpoint | Method | Params | Description |
|
||||||
|
|-----------------------------------+--------+---------------------+----------------------------|
|
||||||
|
| =asteroid/dj/external/connect= | POST | source (alsa/pulse) | Connect local audio input |
|
||||||
|
| =asteroid/dj/external/disconnect= | POST | | Disconnect external input |
|
||||||
|
| =asteroid/dj/external/volume= | POST | volume (0.0–1.0) | Set external input volume |
|
||||||
|
|
||||||
|
** Library Search
|
||||||
|
|
||||||
|
| Endpoint | Method | Params | Description |
|
||||||
|
|-----------------------------------+--------+---------------------+----------------------------|
|
||||||
|
| =asteroid/dj/library/search= | GET | q (search query) | Search library for tracks |
|
||||||
|
|
||||||
|
* Frontend Design
|
||||||
|
|
||||||
|
** Template: =template/dj-console.ctml=
|
||||||
|
|
||||||
|
*** Layout
|
||||||
|
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 🎛️ DJ CONSOLE DJ: fade [End Session]│
|
||||||
|
├────────────────────────┬────────────────────────────────────┤
|
||||||
|
│ │ │
|
||||||
|
│ ◉ DECK A │ ◎ DECK B │
|
||||||
|
│ ───────────────── │ ───────────────── │
|
||||||
|
│ Orbital │ (empty) │
|
||||||
|
│ Halcyon + On + On │ │
|
||||||
|
│ Brown Album │ │
|
||||||
|
│ │ │
|
||||||
|
│ ▶ ⏸ ■ │ ▶ ⏸ ■ │
|
||||||
|
│ [========|------] │ [-------------] │
|
||||||
|
│ 2:14 / 9:27 │ 0:00 / 0:00 │
|
||||||
|
│ │ │
|
||||||
|
│ VOL [=========-] │ VOL [=========-] │
|
||||||
|
│ │ │
|
||||||
|
├────────────────────────┴────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ A [===================|===================] B │
|
||||||
|
│ CROSSFADER │
|
||||||
|
│ │
|
||||||
|
├──────────────────────────────────────────────────────────────┤
|
||||||
|
│ 🎤 EXTERNAL INPUT [Connect] Vol [=========-] │
|
||||||
|
│ Status: Not connected │
|
||||||
|
├──────────────────────────────────────────────────────────────┤
|
||||||
|
│ 📚 LIBRARY │
|
||||||
|
│ [____________________________] [Search] │
|
||||||
|
│ │
|
||||||
|
│ Artist Title Album Action │
|
||||||
|
│ ─────────────────── ───────────────────── ───────── ─────── │
|
||||||
|
│ Orbital Halcyon + On + On Brown.. [A] [B] │
|
||||||
|
│ Underworld Born Slippy (Nuxx) STITI [A] [B] │
|
||||||
|
│ Boards of Canada Kid for Today In a.. [A] [B] │
|
||||||
|
│ Drexciya Bubble Chamber Journey [A] [B] │
|
||||||
|
│ Model 500 Digital Solutions Digital [A] [B] │
|
||||||
|
│ ... │
|
||||||
|
│ │
|
||||||
|
│ ◀ Page 1 of 12 ▶ │
|
||||||
|
└──────────────────────────────────────────────────────────────┘
|
||||||
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
*** UI Behaviour
|
||||||
|
|
||||||
|
- *Status polling*: ParenScript polls =asteroid/dj/session/status=
|
||||||
|
every 500ms for deck positions, crossfader state, and track info.
|
||||||
|
This keeps the progress bars and time displays in sync.
|
||||||
|
- *Crossfader*: An HTML range input (slider). On change, sends the
|
||||||
|
new position to =asteroid/dj/crossfader=.
|
||||||
|
- *Deck volume*: Vertical or horizontal sliders per deck.
|
||||||
|
- *Library search*: Text input with debounced search (300ms delay).
|
||||||
|
Results show artist, title, album, and [Load A] / [Load B] buttons.
|
||||||
|
- *Deck controls*: Play/Pause/Stop buttons. Seek by clicking on the
|
||||||
|
progress bar.
|
||||||
|
- *External input*: Connect button opens a dropdown to select audio
|
||||||
|
source (ALSA device, PulseAudio). Volume slider.
|
||||||
|
- *Session indicator*: Shows who is currently DJing and session
|
||||||
|
duration. Visible to admin users from the admin dashboard as well.
|
||||||
|
|
||||||
|
** ParenScript: =parenscript/dj-console.lisp=
|
||||||
|
|
||||||
|
Key functions:
|
||||||
|
|
||||||
|
| Function | Description |
|
||||||
|
|-----------------------------+-------------------------------------------|
|
||||||
|
| =poll-session-status= | GET status, update all UI elements |
|
||||||
|
| =load-track-to-deck= | POST load, refresh deck display |
|
||||||
|
| =toggle-deck-playback= | Play/pause deck |
|
||||||
|
| =stop-deck= | Stop and unload |
|
||||||
|
| =update-crossfader= | POST new crossfader position |
|
||||||
|
| =update-deck-volume= | POST new volume |
|
||||||
|
| =search-library= | GET search results, render table |
|
||||||
|
| =start-session= | POST session start |
|
||||||
|
| =end-session= | POST session end, redirect to front page |
|
||||||
|
| =connect-external= | POST external input connection |
|
||||||
|
|
||||||
|
* Navigation
|
||||||
|
|
||||||
|
Add "DJ Console" link to =template/partial/navbar-admin.ctml=, visible
|
||||||
|
only to users with =:dj= or =:admin= role:
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
<c:unless test='(asteroid::member-string "dj" (** :navbar-exclude))'>
|
||||||
|
<a href="/asteroid/dj"
|
||||||
|
lquery='(attr :target (when framesetp "_self"))'>
|
||||||
|
🎛️ DJ
|
||||||
|
</a>
|
||||||
|
</c:unless>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
The DJ link appears between "Profile" and "Admin" in the navbar. For
|
||||||
|
=:dj= users who don't have =:admin= access, they won't see the Admin
|
||||||
|
or Users links but will see the DJ Console link.
|
||||||
|
|
||||||
|
* Files to Create / Modify
|
||||||
|
|
||||||
|
** New Files
|
||||||
|
|
||||||
|
| File | Description |
|
||||||
|
|-----------------------------------+--------------------------------------------------|
|
||||||
|
| =dj-session.lisp= | DJ session state, deck management, auto-fallback |
|
||||||
|
| =parenscript/dj-console.lisp= | DJ console UI logic (ParenScript) |
|
||||||
|
| =template/dj-console.ctml= | DJ console HTML template |
|
||||||
|
|
||||||
|
** Modified Files
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|--------------------------------------------+-----------------------------------------------|
|
||||||
|
| =asteroid.lisp= | Add =define-page dj-console=, DJ API endpoints, serve =dj-console.js= |
|
||||||
|
| =asteroid.asd= | Add =dj-session= to =:components= |
|
||||||
|
| =template/partial/navbar-admin.ctml= | Add DJ Console nav link |
|
||||||
|
| =stream-harmony.lisp= | Add pause/resume hooks for auto-playlist |
|
||||||
|
| =cl-streamer/harmony-backend.lisp= | Export deck-level play/pause/seek if needed |
|
||||||
|
| =cl-streamer/package.lisp= | Export new symbols |
|
||||||
|
|
||||||
|
* Implementation Plan
|
||||||
|
|
||||||
|
** Phase 1: Library Mixing (Core)
|
||||||
|
|
||||||
|
1. =dj-session.lisp= — session lifecycle, deck state, crossfader math
|
||||||
|
2. API endpoints in =asteroid.lisp= — session/deck/crossfader control
|
||||||
|
3. =stream-harmony.lisp= — pause/resume auto-playlist on session start/end
|
||||||
|
4. =template/dj-console.ctml= — dual-deck UI with library browser
|
||||||
|
5. =parenscript/dj-console.lisp= — polling, controls, search
|
||||||
|
6. Navigation and ASDF updates
|
||||||
|
|
||||||
|
** Phase 2: External Audio Input
|
||||||
|
|
||||||
|
7. Local audio capture via cl-mixed-pulse or cl-mixed-alsa
|
||||||
|
8. Network audio input — accept Icecast source protocol connections
|
||||||
|
9. External input UI in the DJ console
|
||||||
|
10. Hybrid mixing (decks + external simultaneously)
|
||||||
|
|
||||||
|
** Phase 3: Polish
|
||||||
|
|
||||||
|
11. Waveform display (decode audio ahead of time, render waveform in canvas)
|
||||||
|
12. Cue points / hot cues
|
||||||
|
13. BPM detection and sync
|
||||||
|
14. Effects (EQ, filter, reverb via cl-mixed effects chain)
|
||||||
|
15. Session recording (save the mixed output to a file)
|
||||||
|
16. Chat / talkback between DJ and listeners
|
||||||
|
|
||||||
|
* Open Questions
|
||||||
|
|
||||||
|
1. *Monitoring / cue preview*: Should the DJ be able to preview a
|
||||||
|
track in headphones before pushing it live? This would require a
|
||||||
|
second audio output (local sound card) separate from the streaming
|
||||||
|
drain. Possible with Harmony but adds complexity.
|
||||||
|
|
||||||
|
2. *Concurrent sessions*: Do we ever want multiple DJs? (e.g., back-
|
||||||
|
to-back sets with handover). For now, single session is simpler.
|
||||||
|
|
||||||
|
3. *Network input protocol*: Icecast source protocol is the most
|
||||||
|
compatible with existing DJ software. Should we also support SRT
|
||||||
|
for lower latency?
|
||||||
|
|
||||||
|
4. *Latency*: The current pipeline has ~50ms latency (Harmony's
|
||||||
|
buffer) plus encoding time. For remote DJ monitoring this is fine.
|
||||||
|
For local DJ monitoring with headphones, we may want a direct
|
||||||
|
monitor path that bypasses encoding.
|
||||||
|
|
||||||
|
5. *Recording*: Should DJ sets be recorded to disk automatically?
|
||||||
|
Could write the raw PCM or a high-quality FLAC alongside the
|
||||||
|
encoded streams.
|
||||||
Loading…
Reference in New Issue