#lang racket/base
(require racket/match
racket/place
ffi/unsafe
"portaudio.rkt"
"callback-support.rkt"
(rename-in racket/contract [-> c->]))
(define nat? exact-nonnegative-integer?)
(define sample-setter/c (c-> nat? nat? void?))
(define buffer-filler/c (c-> procedure? nat? nat? void?))
(define buffer-filler/unsafe/c (c-> cpointer? nat? nat? void?))
(define time-checker/c (c-> number?))
(define sound-killer/c (c-> void?))
(provide/contract [stream-play
(c-> buffer-filler/c nat? integer?
(list/c time-checker/c
sound-killer/c))]
[stream-play/unsafe
(c-> procedure? nat? integer?
(list/c time-checker/c
sound-killer/c))])
(define channels 2)
(define (stream-play/unsafe buffer-filler buffer-frames sample-rate)
(pa-maybe-initialize)
(match-define (list stream-info signal-channel)
(make-streaming-info buffer-frames))
(define sr/i (exact->inexact sample-rate))
(define stream
(pa-open-default-stream
0 2 'paInt16 sr/i buffer-frames streaming-callback stream-info))
(pa-set-stream-finished-callback stream
streaming-info-free)
(call-fill-buf stream-info buffer-filler)
(define filling-thread
(thread
(lambda ()
(let loop ()
(place-channel-get signal-channel)
(call-fill-buf stream-info buffer-filler)
(loop)))))
(pa-start-stream stream)
(define (stream-time)
(pa-get-stream-time stream))
(define (stopper)
(kill-thread filling-thread)
(place-kill signal-channel)
(pa-maybe-stop-stream stream))
(list stream-time stopper))
(define (stream-play safe-buffer-filler buffer-frames sample-rate)
(define buffer-samples (* channels buffer-frames))
(define (check-sample-idx sample-idx)
(unless (<= 0 sample-idx (sub1 buffer-samples))
(error 'check-sample-idx
(format "must have 0<=sample-index<~s, given ~s"
buffer-samples sample-idx))))
(define (call-safe-buffer-filler ptr frames idx)
(safe-buffer-filler (lambda (sample-idx sample)
(check-sample-idx sample-idx)
(ptr-set! ptr _sint16 sample-idx sample))
frames
idx))
(stream-play/unsafe call-safe-buffer-filler buffer-frames sample-rate))
(define (call-fill-buf streaming-info-ptr buffer-filler)
(match (buffer-if-waiting streaming-info-ptr)
[#f #f]
[(list ptr frames idx finished-thunk)
(buffer-filler ptr frames idx)
(finished-thunk)]))