#lang racket
(require racket/class
"portaudio.rkt"
ffi/unsafe
ffi/vector)
(provide rsound-commander%)
(struct player-msg ())
(struct play-sound-msg (buffer frames sample-rate) #:super struct:player-msg)
(struct loop-sound-msg (buffer frames sample-rate) #:super struct:player-msg)
(struct stop-playing-msg () #:super struct:player-msg)
(struct change-loop-msg (buffer frames) #:super struct:player-msg)
(define channels 2)
(define (check-below-threshold buffer frames threshold)
(for ([i (in-range (* channels frames))])
(when (> (ptr-ref buffer _float i) threshold)
(error 'check-below-threshold "sound contains samples above threshold ~s."
threshold))))
(define player-evt-channel (make-channel))
(define (player-channel-put msg)
(cond [(player-msg? msg) (channel-put player-evt-channel msg)]
[else (error 'player-channel-put "expected a player message, got ~e\n"
msg)]))
(define (start-player-thread)
(thread
(lambda ()
(pa-initialize)
(let loop ([message (channel-get player-evt-channel)])
(with-handlers ([exn:fail?
(lambda (exn)
(log-error (format "play-thread exception: ~a" (exn-message exn)))
(loop (channel-get player-evt-channel)))])
(match message
[(struct stop-playing-msg ()) (loop (channel-get player-evt-channel))]
[(struct play-sound-msg (buffer frames sample-rate))
(loop (or (play-buffer buffer frames sample-rate #f) (channel-get player-evt-channel)))]
[(struct loop-sound-msg (buffer frames sample-rate))
(loop (or (play-buffer buffer frames sample-rate #t) (channel-get player-evt-channel)))]
[(struct change-loop-msg (buffer frames))
(loop (channel-get player-evt-channel))]
[other
(error 'start-player-thread "not a player message: ~e" other)]))))))
(define (play-buffer buffer frames sample-rate loop?)
(define buffer-time 0.5)
(define frames-per-buffer (inexact->exact (floor (* sample-rate buffer-time))))
(define seconds (/ frames sample-rate))
(let* ([stream (pa-open-default-stream 0 channels 'paInt16 (exact->inexact sample-rate) 0 #f #f)] [wait-time (/ buffer-time 2)])
(dynamic-wind
void
(lambda ()
(pa-start-stream stream)
(dynamic-wind
void
(lambda ()
(let outer-loop ()
(let ([this-buffer buffer]
[this-frames frames])
(let loop ([buf-offset 0]
[sleep-time 0.005])
(match (channel-try-get player-evt-channel)
[(struct stop-playing-msg ()) #f]
[(struct play-sound-msg (buffer frames sample-rate))
(play-sound-msg buffer frames sample-rate)]
[(struct loop-sound-msg (buffer frames sample-rate))
(set! loop? #t)
(loop-sound-msg buffer frames sample-rate)]
[other (match other
[(struct change-loop-msg (new-buffer new-frames))
(set! buffer new-buffer)
(set! frames new-frames)]
[#f (void)])
(if (< buf-offset this-frames) (let ([available-space (pa-get-stream-write-available stream)])
(if (= available-space 0)
(begin
(sleep sleep-time)
(loop buf-offset sleep-time))
(let ([frames-to-write (min available-space (- this-frames buf-offset))])
(with-handlers ([(lambda (exn) (and (exn:fail? exn)
(string=? (exn-message exn)
"Output underflowed")))
(lambda (exn) (log-error "ignoring output-underflowed error"))])
(pa-write-stream stream
(ptr-add this-buffer (* channels buf-offset) _sint16)
frames-to-write))
(loop (+ buf-offset frames-to-write)
(/ (/ frames-to-write sample-rate) 2)))))
(if loop?
(outer-loop)
#f))])))))
(lambda () (pa-stop-stream stream))))
(lambda () (pa-close-stream stream)))))
(define rsound-commander%
(class object%
(init master-custodian)
(parameterize ([current-custodian master-custodian])
(start-player-thread))
(define/public (play-sound buffer frames sample-rate)
(player-channel-put (play-sound-msg buffer frames sample-rate)))
(define/public (loop-sound buffer frames sample-rate)
(player-channel-put (loop-sound-msg buffer frames sample-rate)))
(define/public (stop-playing)
(player-channel-put (stop-playing-msg)))
(define/public (change-loop buffer frames)
(player-channel-put (change-loop-msg buffer frames)))
(super-new)))