#lang racket
(require racket/class
"portaudio.rkt"
ffi/unsafe
ffi/vector
racket/async-channel)
(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)
(struct play-signal-msg (signal sample-rate) #: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 play-signal-msg (signal sample-rate))
(loop (or (play-signal signal sample-rate) (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 response-channel (make-channel))
(define-values (abort-flag callback) (make-copying-callback frames buffer response-channel))
(play-using-callback response-channel callback abort-flag sample-rate))
(define (play-signal signal sample-rate)
(define response-channel (make-channel))
(define-values (abort-flag callback) (make-generating-callback signal response-channel))
(play-using-callback response-channel callback abort-flag sample-rate))
(define (play-using-callback response-channel callback abort-flag sample-rate)
(set! current-stop-playing-box abort-flag)
(let* ([stream (pa-open-default-stream 0 channels 'paInt16 (exact->inexact sample-rate) 1000 callback #f)]) (dynamic-wind
void
(lambda ()
(pa-start-stream stream)
(let loop ()
(let ([response (sync player-evt-channel response-channel)])
(match response
[(? play-sound-msg? p) (begin (pa-stop-stream stream)
p)]
[(? loop-sound-msg? p) (begin (pa-stop-stream stream)
p)]
[(? stop-playing-msg? p) (begin (pa-stop-stream stream)
#f)]
[(? change-loop-msg? p) (loop)]
[(? exn? e) (begin (pa-stop-stream stream)
(raise e))]
['finished (begin (pa-stop-stream stream) #f)]
['abort-flag (begin (pa-stop-stream stream) #f)]))))
(lambda () (pa-close-stream stream)))))
(define current-stop-playing-box (box #f))
(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 (play-signal signal sample-rate)
(player-channel-put (play-signal-msg signal sample-rate)))
(define/public (stop-playing)
(set-box! current-stop-playing-box #t))
(define/public (change-loop buffer frames)
(player-channel-put (change-loop-msg buffer frames)))
(super-new)))