Commit Graph

18 Commits

Author SHA1 Message Date
Glenn Thompson b7266e3ac2 Phase 2: Clean package boundaries — declarative make-pipeline DSL
- New make-pipeline function: single declarative call creates server,
  mounts, encoders, and pipeline wiring from an output spec
- Pipeline owns encoder lifecycle (pipeline-encoders slot, auto-cleanup)
- Pipeline owns server when it creates one (pipeline-owns-server-p)
- Hook system wired: pipeline-add-hook fires on track-change and
  playlist-change via pipeline-fire-hook
- stream-harmony.lisp slimmed: start is 1 make-pipeline + 2 hooks,
  stop is 1 pipeline-stop call (cleanup automatic)
- Removed global encoder variables from Asteroid glue layer
- Backward-compatible: dj-session.lisp unchanged, cl-streamer:*server*
  still set for legacy callers

Runtime verified: audio streams, metadata displays, crossfades work.
2026-03-08 11:23:40 +03:00
Glenn Thompson c79cebcfa7 Phase 1: Define CLOS protocol layer for cl-streamer
- New cl-streamer/protocol.lisp with generic functions for server,
  pipeline, encoder, and hook protocols
- harmony-backend.lisp: convert defuns to defmethod on protocol generics,
  import/re-export protocol symbols, add hook system, backward-compat aliases
- encoder.lisp, aac-encoder.lisp: add encoder-encode/encoder-close methods
- package.lisp: export all protocol symbols
- cl-streamer.asd: add protocol.lisp to components

Runtime verified: audio streams, metadata displays, crossfades work.
2026-03-08 11:09:50 +03:00
Glenn Thompson bcfda2ebb6 int16 pack encoding + fix playlist resume across schedule changes
- Replace default float packer with int16 packer (mixed:make-packer :encoding :int16)
  cl-mixed now handles float→s16 conversion in optimized C code instead of
  per-sample Lisp loop. Halves pack buffer memory (2 vs 4 bytes/sample).
- Remove float-to-s16 helper (no longer needed)
- Fix resume-from-saved-state: when saved playlist differs from currently
  scheduled playlist, use the scheduled one from the beginning instead of
  continuing the old playlist. Prevents stale playlist playing after restart.
2026-03-06 18:56:10 +03:00
Glenn Thompson df9d939a2f Revert int16 pack encoding (caused static), keep float-to-s16 drain
Setting (mixed:encoding pack) :int16 after server creation did not
change the pack's internal buffer format — data was still written as
float but read as int16, producing garbage audio.

Added TODO comment to investigate correct API for setting pack encoding
at creation time. The float→s16 conversion in Lisp works correctly.
2026-03-06 11:30:53 +03:00
Glenn Thompson f594daabf8 Fix DJ Console: 400 POST error, library search SQL, auto-playlist pause, now-playing override
- Fix api-post: skip Content-Type header on empty-body POSTs (Hunchentoot 400)
- Fix api-post/api-get: unwrap Radiance data wrapper, add try/catch + console logging
- Fix search-library-tracks: use raw SQL with parameterized ILIKE (S-SQL :offset broken)
- Fix search-library-tracks: quote file-path column name for Radiance hyphenated columns
- Add pipeline-stop-all-voices: immediately silence all Harmony voices on mixer
- Fix pause-auto-playlist: clear queue + skip + stop all voices (no more overlap)
- Override get-now-playing-stats during DJ session to show active deck info
2026-03-06 08:06:02 +03:00
Glenn Thompson e712009d79 Implement DJ Console Phase 1 — dual-deck library mixing
New files:
- dj-session.lisp: DJ session state, deck management, crossfader
  (constant-power curve), auto-playlist pause/resume, ICY metadata
  auto-detect, library search, watchdog timer
- parenscript/dj-console.lisp: UI polling (500ms), deck controls,
  crossfader, library search with load-to-deck, session management
- template/dj-console.ctml: Dark hacker-themed dual-deck interface
  with progress bars, transport controls, crossfader slider, metadata
  override, and library search table

Modified files:
- asteroid.lisp: 14 DJ API endpoints (session, deck, crossfader,
  library search), define-page dj-console, dj-console.js serving
- asteroid.asd: Add dj-session and dj-console components
- cl-streamer/harmony-backend.lisp: Export update-all-mounts-metadata,
  volume-ramp, pipeline-harmony-server for DJ deck control
- navbar-admin.ctml: DJ Console link (role-gated to :dj/:admin)

API endpoints all require :dj role. Session lifecycle:
  GO LIVE -> pause auto-playlist -> mix -> END SESSION -> resume

External audio input stubbed for Phase 2.
2026-03-05 21:22:09 +03:00
Glenn Thompson f39abeb8f8 Fix playlist resume and SIMPLE-ARRAY pathname errors
- Add *resumed-from-saved-state* flag to prevent scheduler's db:connected
  trigger from overwriting resumed playlist position with full playlist
- Use sb-ext:parse-native-namestring in play-file to prevent SBCL from
  interpreting brackets in directory names (e.g. [WEB FLAC]) as wildcard
  patterns, which caused non-simple-string pathname components that broke
  cl-flac's CFFI calls
2026-03-05 18:21:32 +03:00
Glenn Thompson 1807e58971 Remove Icecast/Liquidsoap, migrate fully to Harmony/CL-Streamer
- Delete all Icecast/Liquidsoap config files, Dockerfiles, and .liq scripts
- Remove icecast/liquidsoap services from docker-compose.yml (keep postgres)
- Remove liquidsoap-command, parse-liquidsoap-metadata, format-remaining-time
- Remove liquidsoap-command-succeeded-p, liquidsoap-reload-and-skip
- Remove icecast-now-playing, check-icecast-status, check-liquidsoap-status
- Remove icecast XML polling from listener-stats.lisp
- Replace asteroid/liquidsoap/* APIs with asteroid/stream/* APIs
- Simplify all if-harmony-else-liquidsoap branches to Harmony-only
- Update admin template: single Stream Status card, pipeline controls
- Update about.ctml credits: Harmony + CL-Mixed replace Icecast + Liquidsoap
- Update status.ctml server name to CL-Streamer / Harmony
- Update parenscript/admin.lisp: stream-* functions, new API endpoints
- Export pipeline-running-p from cl-streamer/harmony package
2026-03-05 17:24:12 +03:00
Glenn Thompson 47e6c5da46 Fix scheduler timezone and taglib type errors
- cl-cron uses local time, not UTC: add utc-hour-to-local-hour
  conversion so schedule hours fire at correct UTC times
  (e.g. 12:00 UTC now fires at 15:00 local on UTC+3)

- Wrap each taglib field read in safe-tag with per-field error
  handling so a type error in one field (e.g. album with non-simple
  string) doesn't crash play-file or skip the track

- Use (coerce ... 'simple-string) instead of copy-seq for
  guaranteed simple-string output from ensure-simple-string
2026-03-05 15:16:21 +03:00
Glenn Thompson 9ae7546466 Fix playlist stalls, FLAC/taglib errors, and playback state resume
- Prevent play-list thread death on scheduler playlist change:
  drain-queue-into-remaining drains full scheduler queue at once,
  updates loop-queue reference so repeat replays correct playlist,
  top-level handler-case prevents thread from dying silently

- Fix taglib SIMPLE-ARRAY CHARACTER type errors:
  ensure-simple-string coerces metadata strings and trims whitespace

- Fix FLAC::FILE slot unbound errors:
  retry once with 200ms delay for transient init failures

- Improve playback state persistence:
  save playlist path alongside track file so restart loads the
  correct playlist instead of always falling back to stream-queue.m3u

- Startup now uses resume-from-saved-state to resolve saved playlist
  and track position, falls back to stream-queue.m3u only if no state
2026-03-05 13:25:15 +03:00
Glenn Thompson 16da880822 Fix playlist repeat: loop playlist when tracks exhaust, scheduler takes priority
- Enable loop-queue in play-list: repeats current playlist when tracks run out
- next-entry checks queue first so scheduler-queued playlists override repeat cycle
- Prevents silent stream / client reconnect loops between scheduled playlist changes
2026-03-04 10:20:57 +03:00
Glenn Thompson d66d4fe46c Crossfade timing, scheduler fix, playback resume, auth noise cleanup
- Delay metadata/track-change notification by 1s after crossfade completes
- Log 'Loading next:' instead of 'Now playing:' during crossfade prep
- Add diagnostic logging: track duration check, crossfade trigger time
- harmony-load-playlist defaults to skip=nil: scheduler queues tracks
  without interrupting current playback
- Save current track to .playback-state.lisp on each track change,
  resume from saved position on restart
- Replace ~50 format-t debug statements in auth with log:info/log:warn
- Remove password hash logging for security
- Add .playback-state.lisp to .gitignore
2026-03-04 00:23:25 +03:00
Glenn Thompson dad1418bf8 Integrate cl-streamer into Asteroid Radio (replaces Icecast + Liquidsoap)
New files:
- stream-harmony.lisp: Bridge between cl-streamer pipeline and Asteroid app
  - start-harmony-streaming / stop-harmony-streaming lifecycle
  - on-harmony-track-change callback: feeds recently-played, DB track lookup
  - harmony-now-playing: returns same alist format as icecast-now-playing
  - harmony-load-playlist: loads M3U, converts Docker paths, feeds queue
  - harmony-skip-track / harmony-get-status

Pipeline control (harmony-backend.lisp):
- Add pipeline-current-track, pipeline-on-track-change callback
- Add pipeline-skip, pipeline-queue-files, pipeline-get-queue, pipeline-clear-queue
- play-list now supports skip flag, queue consumption, loop-queue mode
- notify-track-change fires callback after crossfade completes

Graceful fallback - all touch points check *harmony-pipeline*:
- frontend-partials.lisp: now-playing endpoints try Harmony first, fall back to Icecast
- asteroid.lisp: admin APIs (status/skip/reload/restart) try Harmony first
- playlist-scheduler.lisp: load-scheduled-playlist tries Harmony first
- asteroid.asd: added cl-streamer subsystem dependencies

Docker scripts updated:
- start.sh / stop.sh: only start/stop postgres (cl-streamer replaces streaming)
2026-03-03 21:27:29 +03:00
Glenn Thompson edf9326007 Taglib metadata reading, crossfade metadata timing fix
- Add taglib dependency to cl-streamer/harmony system
- Add read-audio-metadata: reads artist/title/album from FLAC/MP3 tags
- Add format-display-title: builds 'Artist - Title' from tags, falls back to filename
- Add update-all-mounts-metadata: updates ICY metadata on all mount points
- Defer metadata update during crossfade until fade completes (listeners hear correct track)
- Fix play-list wait loop: was nested inside crossfade conditional, first track never waited
- Remove filename-derived :title from test playlist (taglib reads real tags now)
2026-03-03 21:10:44 +03:00
Glenn Thompson 2649a8169a AAC encoding fixes, crossfade, buffer tuning, README rewrite
- Fix FDK-AAC C shim: use proper OUT_BITSTREAM_DATA=3 constant and INT types
- Add frame-aligned PCM accumulation buffer to AAC encoder for clean output
- Add fdkaac_encode and fdkaac_close to C shim (all FDK-AAC calls in C)
- Implement crossfade between tracks (3s overlap, 2s fade-in/out)
- Tune buffer: 90% drain sleep for encoding headroom, 64KB burst-on-connect
- Add FFI bindings for new shim functions
- Rewrite README.org with full architecture docs and integration plan
2026-03-03 20:20:43 +03:00
Glenn Thompson fcda723577 feat: Broadcast buffer, sequential playlist, ICY metadata
- Broadcast buffer: single-producer multi-consumer ring buffer with
  per-client read cursors. 32KB burst-on-connect for fast playback.
  Never blocks producer (overwrites old data for slow clients).
- Sequential playlist: play-list runs tracks one at a time using
  Harmony's on-end callback + condition variable for completion.
- ICY metadata: set-now-playing called on each track change.
- Fixed string vs pathname bug in harmony:play (etypecase mismatch).
- Debug logging for client disconnect diagnosis.

Verified: browser plays shuffled FLAC playlist via 128kbps MP3 stream.
2026-03-03 18:15:31 +03:00
Glenn Thompson a9e8276e9a feat: End-to-end streaming working! Custom streaming-drain + fixes
Major changes:
- streaming-drain: Custom drain that captures PCM from Harmony's pack
  buffer (raw IEEE 754 floats in unsigned-byte-8 array), converts to
  signed-16 PCM via CFFI, encodes to MP3 via LAME, and writes to
  stream server's ring buffer
- Fixed ring buffer deadlock: buffer-read/buffer-write held lock then
  called buffer-available which tried to acquire same lock. Created
  internal %buffer-available/%buffer-free-space without locking.
- Fixed ring buffer zero-length guard for unbound variable in finally
- Fixed sleep duration in drain: was dividing raw byte count by
  samplerate, now correctly converts to frames first
- Added flexi-streams wrapper for bivalent HTTP socket I/O
- Exported all public API symbols from cl-streamer package
- Added test-stream.lisp end-to-end test script

Verified: Amon Tobin FLAC -> 128kbps MP3 stream at localhost:8000
  file reports: MPEG ADTS, layer III, v1, 128kbps, 44.1kHz, JntStereo
2026-03-03 17:46:55 +03:00
Glenn Thompson 9d5166c562 feat: Add Harmony backend with dummy drain for streaming pipeline
- harmony-backend.lisp: Audio pipeline connecting Harmony to encoders
- Uses :dummy drain to capture PCM without audio output
- Reads from packer bip-buffer via request-read/finish-read API
- Encodes PCM to MP3 via LAME and feeds to stream server

All three systems compile cleanly:
  cl-streamer, cl-streamer/encoder, cl-streamer/harmony
2026-03-03 16:56:24 +03:00