diff --git a/drivers/intel-hda.lisp b/drivers/intel-hda.lisp index 25254324e..8f56f9e8b 100644 --- a/drivers/intel-hda.lisp +++ b/drivers/intel-hda.lisp @@ -199,6 +199,24 @@ (defconstant +dmap-entry-size+ 4) (defconstant +bdl-entry-size+ 16) (defconstant +max-bdl-entries+ 256) +(defconstant +playback-period-count+ 3) + +(defparameter *hda-min-period-bytes* 256 + "Minimum size of each HDA audio period in bytes. Must be a multiple of 128.") + +(defparameter *i-want-garbage* nil + "When true, skip the startup silence delay. The first ~100ms of audio + may be garbled while the host audio backend initializes.") + +(defparameter *hda-startup-periods* 90 + "Number of periods to run silently at startup before unmuting. + The DMA buffer is pre-filled and the SRC adapts during this window. + Total startup silence = *hda-startup-periods* × (period-bytes / 176400) seconds.") + +(defparameter *hda-idle-periods* 12 + "Number of silent periods after the sink drains before stopping the stream. + Higher values keep the HDA initialized longer, avoiding startup delays on + the next playback at the cost of keeping the IRQ active.") (defconstant +corb-offset+ 0) (defconstant +rirb-offset+ (+ +corb-offset+ +corb-max-size+)) @@ -221,7 +239,9 @@ (irq :initarg :irq :accessor hda-irq) (dma-buffer-phys :initarg :dma-buffer-phys :accessor hda-dma-buffer-phys) (dma-buffer-virt :initarg :dma-buffer-virt :accessor hda-dma-buffer-virt) - (dma-buffer-size :initarg :dma-buffer-size :accessor hda-dma-buffer-size)) + (dma-buffer-size :initarg :dma-buffer-size :accessor hda-dma-buffer-size) + (period-bytes :initarg :period-bytes :accessor hda-period-bytes) + (stable-period-bytes :initform nil :accessor hda-stable-period-bytes)) (:default-initargs :codecs (make-array 15 :initial-element nil))) (define-condition device-disconnect () ()) @@ -841,6 +861,9 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (setf (hda-irq hda) (mezzano.supervisor:make-simple-irq (pci:pci-intr-line device))) (format t "Found Intel HDA controller at ~S.~%" device) (setf (pci:pci-bus-master-enabled device) t) + ;; Clear TCSEL PCI register bits 0-2 to fix playback static on ICH. + (let ((tcel (pci:pci-config/8 device #x44))) + (setf (pci:pci-config/8 device #x44) (logand tcel #b11111000))) ;; Perform a controller reset by pulsing crst to 0. (format t "Begin reset.~%") (setf (global-reg/32 hda +gctl+) 0) @@ -929,8 +952,11 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (setf (sd-reg/32 hda stream-id +sdnbdpl+) (ldb (byte 32 0) bdl) (sd-reg/32 hda stream-id +sdnbdpu+) (ldb (byte 32 32) bdl)) (setf (sd-reg/16 hda stream-id +sdnfmt+) #x4011 - (sd-reg/16 hda stream-id +sdnlvi+) bdl-length - (sd-reg/32 hda stream-id +sdncbl+) cb-length))) + ;; LVI is the last valid descriptor index, not the descriptor count. + (sd-reg/16 hda stream-id +sdnlvi+) (1- bdl-length) + (sd-reg/32 hda stream-id +sdncbl+) cb-length) + ;; Poll FIFO size until non-zero (FIFO ready after format is set). + (loop while (zerop (sd-reg/16 hda stream-id +sdnfifos+))))) (defun stream-reset (hda stream-id) ;; Clear the run bit before doing anything. @@ -946,8 +972,13 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (setf (global-reg/32 hda +intctl+) 0)) (defun stream-go (hda stream-id) - ;; run, IoC enabled, stream number (tag) 1. - (setf (sd-reg/32 hda stream-id +sdnctlsts+) #x00100006)) + (setf (sd-reg/32 hda stream-id +sdnctlsts+) + (logior #x00100000 ; STRM = 1 (matches codec verb #x70610) + (ash 1 18) ; TP (traffic priority) + (ash 1 4) ; DEIE + (ash 1 3) ; FEIE + (ash 1 2) ; IOCE + (ash 1 1)))) ; RUN (defun first-input-stream (hda) (declare (ignore hda)) @@ -955,41 +986,78 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (defun first-output-stream (hda) (with-hda-access (hda) - (gcap-iss (global-reg/16 hda +gcap+)))) + (gcap-oss (global-reg/16 hda +gcap+)))) -(defun start-playback (hda buffer buffer-size codec dac pin &optional mixer) +(defun compute-period-bytes (hda stream-id) + (with-hda-access (hda) + (or (hda-stable-period-bytes hda) + (let* ((fifo-size (1+ (sd-reg/16 hda stream-id +sdnfifos+))) + (fifo-aligned (* (ceiling fifo-size 128) 128)) + (period-bytes (max fifo-aligned *hda-min-period-bytes*))) + (mezzano.supervisor:debug-print-line + "HDA stream " stream-id " FIFO " fifo-size " period " period-bytes) + period-bytes)))) + +(defun start-playback (hda buffer buffer-size codec dac pin &optional mixer + &key + (period-bytes (compute-period-bytes hda (first-output-stream hda))) + (period-count +playback-period-count+) + (startup-delay-seconds 0) + (mute-for-startup nil)) (with-hda-access (hda) (stream-reset hda (first-output-stream hda)) - (write-bdl hda 0 buffer (truncate buffer-size 2)) - (write-bdl hda 1 (+ buffer (truncate buffer-size 2)) (truncate buffer-size 2)) - (prep-stream hda (first-output-stream hda) 0 1 buffer-size) - (when mixer - (command hda codec mixer #x3F07F)) ; unmute L/R out/in + (clear-pending-interrupt hda (first-output-stream hda)) + (dotimes (i period-count) + (write-bdl hda i + (+ buffer (* i period-bytes)) + period-bytes)) + (prep-stream hda (first-output-stream hda) 0 period-count buffer-size) (command hda codec dac #x70610) ; stream=1 (command hda codec dac #x24011) ; format - (command hda codec dac #x3b07f) ; unmute L/R out - (command hda codec pin #x3b07f) ; unmute L/R out - (command hda codec pin #x70740) ; enable output + (when mixer + (command hda codec mixer #x3F07F)) ; unmute L/R out/in + (if mute-for-startup + (progn + ;; Mute the DAC output so the SRC can adapt silently while + ;; the host audio backend initializes. + (command hda codec dac #x3b0ff)) ; mute + (progn + (command hda codec dac #x3b07f) ; unmute L/R out + (command hda codec pin #x3b07f) ; unmute L/R out + (command hda codec pin #x70740))) ; enable output ;; Enable global and stream interrupts. (setf (global-reg/32 hda +intctl+) (logior #x80000000 (ash 1 (first-output-stream hda)))) + (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) + (sys.int::dma-write-barrier) + (when (plusp startup-delay-seconds) + (sleep startup-delay-seconds)) (stream-go hda (first-output-stream hda)))) (defun clear-pending-interrupt (hda stream) - ;; qemu is picky and requires a write to the 8-bit status part of - ;; the register. - (setf (sd-reg/8 hda stream (+ +sdnctlsts+ 3)) (ash 1 2))) ; bcis + ;; Clear any latched stream status in the status byte. qemu is picky and + ;; requires a write to the 8-bit status part of the register. + (setf (sd-reg/8 hda stream (+ +sdnctlsts+ 3)) + (logior (ash 1 4) ; dese + (ash 1 3) ; fifoe + (ash 1 2)))) ; bcis (defun wait-for-buffer-interrupt (hda) (let ((irq (hda-irq hda)) (stream (first-output-stream hda))) (loop - (sync:wait-for-objects irq (pci:pci-device-boot-id (hda-pci-device hda))) + (multiple-value-bind (objects remaining) + (sync:wait-for-objects-with-timeout 0.02 + irq + (pci:pci-device-boot-id (hda-pci-device hda))) + (declare (ignore remaining)) + (when (null objects) + (return :timeout))) (with-hda-access (hda) (when (logbitp stream (global-reg/32 hda +intsts+)) (clear-pending-interrupt hda stream) (mezzano.supervisor:simple-irq-unmask irq) - (return))) - (mezzano.supervisor:simple-irq-unmask irq)))) + (return :interrupt))) + (mezzano.supervisor:simple-irq-unmask irq)))) ;; Return a list of all pin widgets. (defun pin-widgets (hda) @@ -1041,74 +1109,158 @@ Returns NIL if there is no output path." (t (values (first direct-converters) nil))))) +(defun set-widget-power-state-d0 (node) + ;; Set Power State = D0. + (command (hda node) (cad node) (nid node) #x70500)) + +(defun maybe-enable-eapd (widget) + (when (typep widget 'audio-pin-complex) + (let ((caps (parameter widget +parameter-pin-capabilities+))) + (when (not (zerop (parameter-pin-capabilities-eapd-capable caps))) + ;; Enable EAPD/BTL. + (command (hda widget) (cad widget) (nid widget) #x70C02))))) + +(defun log-selected-output-path (pin converter mixer) + (mezzano.supervisor:debug-print-line + "HDA selected pin " (nid pin) + " (" (pin-default-device pin) " " (pin-location pin) " " (pin-colour pin) ")" + " converter " (nid converter) + (if mixer + (format nil " mixer ~D" (nid mixer)) + ""))) + +(defun hda-restart-stream (hda stream buffer period-bytes period-count buf-len) + "Reset the stream, rewrite BDL entries with the given period size, + prep the stream, and start DMA. Returns nothing." + (with-hda-access (hda) + (stream-reset hda stream) + (dotimes (i period-count) + (write-bdl hda i (+ buffer (* i period-bytes)) period-bytes)) + (prep-stream hda stream 0 period-count buf-len) + (setf (global-reg/32 hda +intctl+) + (logior #x80000000 (ash 1 stream))) + (clear-pending-interrupt hda stream) + (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) + (sys.int::dma-write-barrier) + (stream-go hda stream))) + ;; TODO: This should stream to anything that looks vaugely output-like, instead ;; of a single pin. (defmethod mezzano.driver.sound:sound-card-run ((hda hda) buffer-fill-callback) (handler-case (let* ((buffer (hda-dma-buffer-phys hda)) - (buf-len #x2000);(hda-dma-buffer-size hda)) - (half-buf-len (truncate buf-len 2)) - (n-samples (truncate half-buf-len 2)) ; buf-len is in bytes (2 per sample) and we want to fill only half the buffer at once - (float-sample-buffer (make-array n-samples :element-type 'single-float)) - (output-pin (default-output-pin hda)) - (output-stream (first-output-stream hda)) - (buffer-offset 0) - (stop-countdown nil)) - (labels ((store-sample (sample offset) - ;; Clamp to limits, don't wrap. - (let* ((sample-clamped (max (min sample 1.0f0) -1.0f0)) - (sample-rescaled (if (< sample-clamped 0.0f0) - (* sample-clamped 32768.0f0) - (* sample-clamped 32767.0f0))) - (sample-16bit (truncate sample-rescaled))) - (declare (optimize speed (safety 0)) - (type single-float sample-clamped sample-rescaled) - (type fixnum sample-16bit)) - (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset)) (ldb (byte 8 0) sample-16bit) - (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset 1)) (ldb (byte 8 8) sample-16bit)))) - (refill-fifo () - (with-hda-access (hda) - (locally - (declare (optimize speed (safety 0)) - (type (simple-array single-float (*)) float-sample-buffer) - (type fixnum n-samples)) - (dotimes (i n-samples) - (store-sample (aref float-sample-buffer i) i)))) - (cond ((eql buffer-offset 0) - (setf buffer-offset half-buf-len)) - (t - (setf buffer-offset 0))) - (cond ((funcall buffer-fill-callback float-sample-buffer 0 n-samples) - (setf stop-countdown nil)) - ((not stop-countdown) - (setf stop-countdown 4))))) - ;; Prepopulate the initial buffer. - (funcall buffer-fill-callback float-sample-buffer 0 n-samples) + (stream (first-output-stream hda)) + (pb (compute-period-bytes hda stream)) + (pc +playback-period-count+) + (bl (* pb pc)) + (n-samples (truncate pb 2)) + (buf (make-array n-samples :element-type 'single-float)) + (pin (default-output-pin hda)) + (woff 0) + (stop-ct nil) + (fifo 0) + (muted (not *i-want-garbage*)) + (startup (if *i-want-garbage* 0 *hda-startup-periods*)) + (startup-sec (if *i-want-garbage* 0.0 + (/ (* pb *hda-startup-periods*) 176400.0))) + (udac nil) (upin nil) (ucad nil) + (ucd 0) + (ucnt 0)) + (setf (hda-period-bytes hda) pb) + (unless pin + (error "No HDA output pin with a playback path found.")) + (labels ((flush () + (dotimes (i n-samples) + (let* ((s (aref buf i)) + (sc (max (min s 1.0f0) -1.0f0)) + (sr (if (< sc 0.0f0) (* sc 32768.0) (* sc 32767.0))) + (si (truncate sr))) + (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 + buffer (+ woff i i)) (ldb (byte 8 0) si) + (mezzano.supervisor::physical-memref-unsigned-byte-8 + buffer (+ woff i i 1)) (ldb (byte 8 8) si)))) + (sys.int::dma-write-barrier) + (setf woff (rem (+ woff pb) bl))) + (refill () + (cond ((plusp startup) (fill buf 0.0)) + ((funcall buffer-fill-callback buf 0 n-samples) + (setf stop-ct nil)) + ((not stop-ct) + (setf stop-ct *hda-idle-periods*))) + (flush)) + (fill-silence () + (let ((save woff)) + (setf woff 0) + (dotimes (i pc) (fill buf 0.0) (flush)) + (setf woff save))) + (try-unmute () + (when (plusp startup) + (decf startup) + (when (zerop startup) + (mezzano.supervisor:debug-print-line + "HDA startup complete, unmuting") + (with-hda-access (hda) + (command hda ucad udac #x3b07f) + (command hda ucad upin #x3b07f) + (command hda ucad upin #x70740)))))) + ;; Init. + (dotimes (i bl) (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer i) 0)) (with-hda-access (hda) - ;; Clear the whole buffer. - (dotimes (i buf-len) - (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer i) 0)) - ;; Unmask the interrupt, flush any pending IRQs. - (clear-pending-interrupt hda output-stream) - (mezzano.supervisor:simple-irq-unmask (hda-irq hda))) - ;; Begin playback. - (multiple-value-bind (converter mixer) - (output-path output-pin) - (start-playback hda buffer buf-len (cad output-pin) (nid converter) (nid output-pin) (and mixer (nid mixer)))) + (clear-pending-interrupt hda stream) + (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) + (setf fifo (1+ (sd-reg/16 hda stream +sdnfifos+)))) + (unless muted (dotimes (i pc) (refill))) + ;; Output path + start. + (multiple-value-bind (c m) (output-path pin) + (unless c (error "HDA output pin ~D has no converter." (nid pin))) + (log-selected-output-path pin c m) + (set-widget-power-state-d0 pin) + (set-widget-power-state-d0 c) + (when m (set-widget-power-state-d0 m)) + (maybe-enable-eapd pin) + (when muted + (setf udac (nid c) upin (nid pin) ucad (cad pin))) + (start-playback hda buffer bl (cad pin) (nid c) (nid pin) + (and m (nid m)) + :period-bytes pb :period-count pc + :startup-delay-seconds startup-sec + :mute-for-startup muted)) + (mezzano.supervisor:debug-print-line + "HDA entering loop, lpib=" (sd-reg/32 hda stream +sdnlpib+)) + ;; Loop. (unwind-protect (loop - ;; Wait for the dma position to move from the - ;; current buffer to the other buffer. - (let* ((dmap (dma-position hda output-stream)) - (current-offset (truncate dmap half-buf-len))) - (when (not (eql current-offset (truncate buffer-offset half-buf-len))) - (when stop-countdown - (when (zerop stop-countdown) - (return)) - (decf stop-countdown)) - ;; Refill buffer. - (refill-fifo))) - (wait-for-buffer-interrupt hda)) + (ecase (wait-for-buffer-interrupt hda) + (:timeout) + (:interrupt + (let* ((lp (sd-reg/32 hda stream +sdnlpib+)) + (lpf (mod (+ lp fifo) bl)) + (co (truncate lpf pb))) + (unless (eql co (truncate woff pb)) + (when stop-ct + (when (zerop stop-ct) (return)) + (decf stop-ct)) + (refill) + ;; If the hardware crossed into the next period + ;; while we were refilling, we're falling behind. + (let* ((lp2 (sd-reg/32 hda stream +sdnlpib+)) + (lpf2 (mod (+ lp2 fifo) bl)) + (co2 (truncate lpf2 pb))) + (when (not (eql co2 co)) + (incf ucnt))) + (try-unmute))))) + (when (plusp ucd) (decf ucd)) + (when (and (zerop ucd) (> ucnt 3)) + (let ((npb (* (ceiling (+ pb 128) 128) 128))) + (mezzano.supervisor:debug-print-line + "HDA underrun, period " pb " → " npb) + (setf (hda-stable-period-bytes hda) npb + pb npb (hda-period-bytes hda) npb + bl (* npb pc) n-samples (truncate npb 2) + buf (make-array n-samples :element-type 'single-float) + woff 0 ucd (* 8 pc) ucnt 0) + (hda-restart-stream hda stream buffer pb pc bl) + (fill-silence)))) (with-hda-access (hda) (stream-reset hda (first-output-stream hda)) (mezzano.supervisor:simple-irq-mask (hda-irq hda)))))) @@ -1121,5 +1273,6 @@ Returns NIL if there is no output path." (pci:define-pci-driver intel-hda intel-hda-probe ((#x8086 #x2668) ; ICH6 HDA - (#x8086 #x27D8)) ; ICH7 HDA + (#x8086 #x27D8) ; ICH7 HDA + (#x8086 #x293E)); ICH9 HDA ()) diff --git a/drivers/sound.lisp b/drivers/sound.lisp index 5dfc611dc..9ea8878f0 100644 --- a/drivers/sound.lisp +++ b/drivers/sound.lisp @@ -131,25 +131,27 @@ (type fixnum start end)) (fill buffer 0.0 :start start :end end)) (prog1 - (mezzano.supervisor:with-mutex (*sink-lock*) - (cond ((endp *sinks*) - ;; No more sinks, stop the card. - nil) - (t - ;; Fill buffer from sinks as much as possible. - (dolist (sink *sinks*) - (mix-out-of-sink sink buffer start end)) - ;; Remove all empty sinks. - (setf *sinks* (delete-if - (lambda (sink) - (buffer-empty sink)) - *sinks*)) - (when (endp *sinks*) - (setf (sup:event-state *sinks-present-event*) nil)) - ;; Samples have been consumed, wake fillers. - (mezzano.supervisor:condition-notify *sink-cvar* t) - ;; Leave the card running. - t))))) + ;; Try-lock the sink mutex. If the music player holds it (inside + ;; output-sound's transcode path), return T immediately — the + ;; buffer is already zeroed, so the HDA plays silence for this + ;; period. Next period will retry. + (if (mezzano.supervisor:acquire-mutex *sink-lock* nil) + (unwind-protect + (cond ((endp *sinks*) + nil) + (t + (dolist (sink *sinks*) + (mix-out-of-sink sink buffer start end)) + (setf *sinks* (delete-if + (lambda (sink) + (buffer-empty sink)) + *sinks*)) + (when (endp *sinks*) + (setf (sup:event-state *sinks-present-event*) nil)) + (mezzano.supervisor:condition-notify *sink-cvar* t) + t)) + (mezzano.supervisor:release-mutex *sink-lock*)) + t))) (defun sound-worker (device) (loop @@ -307,11 +309,13 @@ (mezzano.supervisor:with-mutex (*sink-lock*) (let ((offset start)) (loop - (when (buffer-empty sink) - ;; Buffer is currently empty, sink is not live. - (assert (not (member sink *sinks*))) - (setf (sup:event-state *sinks-present-event*) t) - (push sink *sinks*)) + (when (buffer-empty sink) + ;; Buffer is currently empty, sink is not live. + ;; Only add to the sink list if not already present — the + ;; callback may have left it if it was re-filled concurrently. + (unless (member sink *sinks*) + (setf (sup:event-state *sinks-present-event*) t) + (push sink *sinks*))) ;; Fill the buffer as much as possible. (multiple-value-bind (total-samples-copied total-elements-consumed) diff --git a/gui/music-player.lisp b/gui/music-player.lisp index f25bd74d4..42833aad8 100644 --- a/gui/music-player.lisp +++ b/gui/music-player.lisp @@ -10,6 +10,7 @@ ((%fifo :initarg :fifo :reader fifo) (%window :initarg :window :reader window) (%audio-thread :initarg :audio-thread :accessor audio-thread) + (%reader-thread :initarg :reader-thread :accessor reader-thread) (%font :initarg :font :reader font) (%frame :initarg :frame :reader frame) (%stream :initarg :stream :reader player-stream) @@ -53,8 +54,7 @@ (defun worker (player) (unwind-protect - (let* ((data-buffer (make-array #x2000 :element-type '(unsigned-byte 8))) - (stream (player-stream player)) + (let* ((stream (player-stream player)) (riff-chunks (wav:read-wav-file stream :chunk-data-reader (wrap-data-chunk-data-samples-reader))) (riff (find "RIFF" riff-chunks @@ -85,26 +85,49 @@ (member sample-size '(8 16)))) (format t "Unsupported wav file. ~S ~S ~S ~S ~S~%" fmt compression channels sample-rate sample-size) (return-from worker)) - (file-position stream data-file-position) - (let ((audio-sink (mezzano.driver.sound:make-sound-output-sink - :buffer-duration 1.0 - :format (ecase sample-size - (8 :pcm-u8) - (16 :pcm-s16le)))) - (current-position 0)) - (unwind-protect - (loop - (when (>= current-position data-size) - (format t "Reached EOF.~%") - (return)) - (let ((n-bytes (min (length data-buffer) - (- data-size current-position)))) - (read-sequence data-buffer stream :end n-bytes) - (mezzano.driver.sound:output-sound data-buffer audio-sink :end n-bytes) - (incf current-position n-bytes)))) - (mezzano.driver.sound:flush-sink audio-sink)))) - (mezzano.supervisor:fifo-push (make-instance 'worker-exited) - (fifo player)))) + (file-position stream data-file-position) + (let* ((chunk-size #x4000) + (audio-sink (mezzano.driver.sound:make-sound-output-sink + :buffer-duration 0.01 + :format (ecase sample-size + (8 :pcm-u8) + (16 :pcm-s16le)))) + (current-position 0) + (chunk-fifo (mezzano.supervisor:make-fifo 4))) + ;; Pre-fill: push first chunks so the sink has data + ;; immediately when the sound worker starts playback. + (dotimes (i 4) + (when (>= current-position data-size) (return)) + (let* ((n-bytes (min chunk-size (- data-size current-position))) + (buf (make-array n-bytes :element-type '(unsigned-byte 8)))) + (read-sequence buf stream) + (mezzano.driver.sound:output-sound buf audio-sink :end n-bytes) + (incf current-position n-bytes))) + ;; Reader thread: reads remaining chunks into FIFO while + ;; the main thread pushes them to the sink. Reading and + ;; playback overlap, so a slow read never drains the sink. + (setf (reader-thread player) + (mezzano.supervisor:make-thread + (lambda () + (unwind-protect + (loop + (when (>= current-position data-size) (return)) + (let* ((n-bytes (min chunk-size (- data-size current-position))) + (buf (make-array n-bytes :element-type '(unsigned-byte 8)))) + (read-sequence buf stream) + (incf current-position n-bytes) + (mezzano.supervisor:fifo-push buf chunk-fifo))) + (mezzano.supervisor:fifo-push nil chunk-fifo))) + :name (format nil "Audio reader for ~S" player))) + (unwind-protect + (loop + (let ((buf (mezzano.supervisor:fifo-pop chunk-fifo))) + (when (null buf) (return)) + (mezzano.driver.sound:output-sound buf audio-sink + :end (length buf)))) + (mezzano.driver.sound:flush-sink audio-sink))))) + (mezzano.supervisor:fifo-push (make-instance 'worker-exited) + (fifo player)))) (defun main (path) (with-simple-restart (abort "Close music player") @@ -165,7 +188,8 @@ (mezzano.gui.widgets:close-button-clicked () (return-from main))))) (when (audio-thread player) - (mezzano.supervisor:terminate-thread (audio-thread player))) + (mezzano.supervisor:terminate-thread (audio-thread player)) + (mezzano.supervisor:terminate-thread (reader-thread player))) (mezzano.supervisor:with-mutex ((worker-comm-lock player)) (setf (playback-state player) :exit) (mezzano.supervisor:condition-notify (worker-comm-cvar player))))))))))