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.
This commit is contained in:
parent
df9d939a2f
commit
bcfda2ebb6
|
|
@ -55,30 +55,23 @@
|
|||
|
||||
(defmethod mixed:start ((drain streaming-drain)))
|
||||
|
||||
(declaim (inline float-to-s16))
|
||||
(defun float-to-s16 (sample)
|
||||
"Convert a float sample (-1.0 to 1.0) to signed 16-bit integer."
|
||||
(let ((clamped (max -1.0 (min 1.0 sample))))
|
||||
(the (signed-byte 16) (round (* clamped 32767.0)))))
|
||||
|
||||
(defmethod mixed:mix ((drain streaming-drain))
|
||||
"Read interleaved float PCM from the pack buffer, encode to all outputs.
|
||||
The pack buffer is (unsigned-byte 8) with IEEE 754 single-floats (4 bytes each).
|
||||
Layout: L0b0 L0b1 L0b2 L0b3 R0b0 R0b1 R0b2 R0b3 L1b0 ... (interleaved stereo)"
|
||||
"Read interleaved s16 PCM from the pack buffer, encode to all outputs.
|
||||
The pack is created with :encoding :int16, so cl-mixed converts float→s16 in C.
|
||||
Layout: L0lo L0hi R0lo R0hi L1lo L1hi R1lo R1hi ... (interleaved stereo, 2 bytes/sample)"
|
||||
(mixed:with-buffer-tx (data start size (mixed:pack drain))
|
||||
(when (> size 0)
|
||||
(let* ((channels (drain-channels drain))
|
||||
(bytes-per-sample 4) ; single-float = 4 bytes
|
||||
(total-floats (floor size bytes-per-sample))
|
||||
(num-samples (floor total-floats channels))
|
||||
(bytes-per-sample 2) ; int16 = 2 bytes
|
||||
(total-samples (floor size bytes-per-sample))
|
||||
(num-samples (floor total-samples channels))
|
||||
(pcm-buffer (make-array (* num-samples channels)
|
||||
:element-type '(signed-byte 16))))
|
||||
;; Convert raw bytes -> single-float -> signed-16
|
||||
;; Read s16 PCM directly — no conversion needed, cl-mixed did it
|
||||
(cffi:with-pointer-to-vector-data (ptr data)
|
||||
(loop for i below (* num-samples channels)
|
||||
for byte-offset = (+ start (* i bytes-per-sample))
|
||||
for sample = (cffi:mem-ref ptr :float byte-offset)
|
||||
do (setf (aref pcm-buffer i) (float-to-s16 sample))))
|
||||
do (setf (aref pcm-buffer i) (cffi:mem-ref ptr :int16 byte-offset))))
|
||||
;; Feed PCM to all encoder/mount pairs
|
||||
(dolist (output (drain-outputs drain))
|
||||
(let ((encoder (car output))
|
||||
|
|
@ -91,7 +84,7 @@
|
|||
(log:warn "Encode error for ~A: ~A" mount-path e)))))))
|
||||
;; Sleep for most of the audio duration (leave headroom for encoding)
|
||||
(let* ((channels (drain-channels drain))
|
||||
(bytes-per-frame (* channels 4))
|
||||
(bytes-per-frame (* channels 2)) ; 2 bytes per sample (int16)
|
||||
(frames (floor size bytes-per-frame))
|
||||
(samplerate (mixed:samplerate (mixed:pack drain))))
|
||||
(when (> frames 0)
|
||||
|
|
@ -175,16 +168,23 @@
|
|||
:output-channels (pipeline-channels pipeline)))
|
||||
(output (harmony:segment :output server))
|
||||
(old-drain (harmony:segment :drain output))
|
||||
(pack (mixed:pack old-drain))
|
||||
(drain (pipeline-drain pipeline)))
|
||||
;; TODO: Investigate setting (mixed:encoding pack) :int16 to let cl-mixed
|
||||
;; handle float→s16 in C. Currently causes static — may need to be set
|
||||
;; before server start, or pack may need recreation with correct encoding.
|
||||
;; Wire our streaming drain to the same pack buffer
|
||||
(setf (mixed:pack drain) pack)
|
||||
;; Swap: withdraw old dummy drain, add our streaming drain
|
||||
;; Replace the default float packer with an int16 packer.
|
||||
;; cl-mixed handles float→s16 conversion in C (faster than our Lisp loop).
|
||||
(let* ((old-packer (harmony:segment :packer output))
|
||||
(new-packer (mixed:make-packer
|
||||
:encoding :int16
|
||||
:channels (pipeline-channels pipeline)
|
||||
:samplerate (pipeline-sample-rate pipeline)
|
||||
:frames (* 2 (harmony::buffersize server)))))
|
||||
;; Connect upmix → new packer (same wiring as old)
|
||||
(harmony:connect (harmony:segment :upmix output) T new-packer T)
|
||||
;; Withdraw old packer and float drain, add new int16 packer and our drain
|
||||
(mixed:withdraw old-drain output)
|
||||
(mixed:add drain output)
|
||||
(mixed:withdraw old-packer output)
|
||||
(mixed:add new-packer output)
|
||||
(setf (mixed:pack drain) (mixed:pack new-packer))
|
||||
(mixed:add drain output))
|
||||
(setf (pipeline-harmony-server pipeline) server)
|
||||
(mixed:start server))
|
||||
(setf (pipeline-running-p pipeline) t)
|
||||
|
|
|
|||
|
|
@ -60,23 +60,40 @@
|
|||
(defun resume-from-saved-state ()
|
||||
"Load saved playback state, resolve the correct playlist, and return
|
||||
(values file-list playlist-path) starting after the saved track.
|
||||
If the currently scheduled playlist differs from the saved one,
|
||||
uses the scheduled playlist from the beginning instead.
|
||||
Returns NIL if no state or playlist found."
|
||||
(let ((state (load-playback-state)))
|
||||
(when state
|
||||
(let* ((saved-file (getf state :track-file))
|
||||
(saved-playlist (getf state :playlist))
|
||||
;; Use saved playlist if it exists, otherwise fall back to current scheduled
|
||||
(playlist-path (or (and saved-playlist
|
||||
(saved-playlist-name (when saved-playlist
|
||||
(file-namestring (pathname saved-playlist))))
|
||||
;; Check what should be playing right now
|
||||
(scheduled-name (get-current-scheduled-playlist))
|
||||
(scheduled-path (when scheduled-name
|
||||
(let ((p (merge-pathnames scheduled-name (get-playlists-directory))))
|
||||
(probe-file p))))
|
||||
;; If scheduled playlist differs from saved, use scheduled (start fresh)
|
||||
(playlist-changed-p (and scheduled-name saved-playlist-name
|
||||
(not (string= scheduled-name saved-playlist-name))))
|
||||
(playlist-path (if playlist-changed-p
|
||||
scheduled-path
|
||||
(or (and saved-playlist
|
||||
(probe-file (pathname saved-playlist)))
|
||||
(let ((scheduled (get-current-scheduled-playlist)))
|
||||
(when scheduled
|
||||
(let ((p (merge-pathnames scheduled (get-playlists-directory))))
|
||||
(probe-file p))))))
|
||||
scheduled-path)))
|
||||
(file-list (when playlist-path
|
||||
(m3u-to-file-list playlist-path))))
|
||||
(when file-list
|
||||
(setf *current-playlist-path* playlist-path)
|
||||
(setf *resumed-from-saved-state* t)
|
||||
(if playlist-changed-p
|
||||
;; Different playlist should be active — start from beginning
|
||||
(progn
|
||||
(log:info "Scheduled playlist changed: ~A -> ~A, starting from beginning"
|
||||
saved-playlist-name scheduled-name)
|
||||
(values file-list playlist-path))
|
||||
;; Same playlist — resume from saved position
|
||||
(let ((pos (when saved-file
|
||||
(position saved-file file-list :test #'string=))))
|
||||
(if pos
|
||||
|
|
@ -94,7 +111,7 @@
|
|||
(progn
|
||||
(log:info "Saved track not found, starting ~A from beginning"
|
||||
(file-namestring playlist-path))
|
||||
(values file-list playlist-path)))))))))
|
||||
(values file-list playlist-path))))))))))
|
||||
|
||||
;;; ---- M3U Playlist Loading ----
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue