#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? void?))
(define buffer-filler/unsafe/c (c-> cpointer? nat? nat? void?))
(define time-checker/c (c-> number?))
(define sound-killer/c (c-> void?))
(define stats/c (c-> (listof (list/c symbol? number?))))
(provide/contract [stream-play
(c-> buffer-filler/c real? real?
(list/c time-checker/c
stats/c
sound-killer/c))]
[stream-play/unsafe
(c-> procedure? real? real?
(list/c time-checker/c
stats/c
sound-killer/c))])
(define channels 2)
(define reasonable-latency 0.05)
(define sleep-interval 0.01)
(define (stream-play/unsafe buffer-filler buffer-time sample-rate)
(pa-maybe-initialize)
(define chosen-device (device-choose))
(log-debug (format "Portaudio: chosen number/name: ~s,~s"
chosen-device
(device-name chosen-device)))
(define promised-latency (device-low-output-latency chosen-device))
(define min-buffer-time (+ promised-latency (* 2 sleep-interval)))
(when (< buffer-time min-buffer-time)
(fprintf (current-error-port) "WARNING: using buffer of ~sms to satisfy API requirements.\n"
(* 1000 min-buffer-time)))
(log-debug (format "Portaudio: chosen device requested latency: ~sms" (round-to-hundredth (* 1000 promised-latency))))
(define buffer-frames (buffer-time->frames (max min-buffer-time buffer-time) sample-rate))
(match-define (list stream-info all-done-ptr)
(make-streaming-info buffer-frames))
(define stream (stream-open stream-info chosen-device promised-latency sample-rate))
(pa-set-stream-finished-callback stream
streaming-info-free)
(call-buffer-filler stream-info buffer-filler)
(define filling-thread
(thread
(lambda ()
(let loop ()
(cond [(all-done? all-done-ptr)
(free all-done-ptr)]
[else
(define start-time (pa-get-stream-time stream))
(call-buffer-filler stream-info buffer-filler)
(define time-used (- (pa-get-stream-time stream) start-time))
(sleep (max 0.0 (- sleep-interval time-used)))
(loop)])))))
(pa-start-stream stream)
(define (stream-time)
(pa-get-stream-time stream))
(define (stats)
(stream-stats stream))
(define (stopper)
(pa-maybe-stop-stream stream))
(list stream-time stats stopper))
(define (stream-play safe-buffer-filler buffer-time sample-rate)
(define buffer-frames (buffer-time->frames buffer-time 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)
(safe-buffer-filler (lambda (sample-idx sample)
(check-sample-idx sample-idx)
(ptr-set! ptr _sint16 sample-idx sample))
frames))
(stream-play/unsafe call-safe-buffer-filler buffer-time sample-rate))
(define (buffer-time->frames buffer-time sample-rate)
(unless (< 0.01 buffer-time 1.0)
(error 'stream-play "expected buffer-time between 10ms and 1 second, given ~s seconds"
buffer-time))
(inexact->exact
(ceiling (* buffer-time sample-rate))))
(define (device-choose)
(define reasonable-devices (low-latency-output-devices reasonable-latency))
(when (null? reasonable-devices)
(error 'stream-choose "no devices available with ~sms latency or less."
(* 1000 reasonable-latency)))
(define default-device (pa-get-default-output-device))
(define selected-device
(cond [(member default-device reasonable-devices) default-device]
[else (fprintf
(current-error-port)
"default output device doesn't support low-latency (~sms) output, using device ~s instead"
(* 1000 reasonable-latency)
(device-name (car reasonable-devices)))
(car reasonable-devices)]))
selected-device)
(define (stream-open stream-info device-number latency sample-rate)
(define sr/i (exact->inexact sample-rate))
(define output-stream-parameters
(make-pa-stream-parameters
device-number 2 '(paInt16) latency #f)) (pa-open-stream
#f output-stream-parameters
sr/i
0 '() streaming-callback
stream-info))
(define (round-to-hundredth x)
(/ (round (* x 100)) 100))