#lang racket
(require ffi/vector)
(require/typed ffi/vector
[s16vector? (Any -> Bool)]
[s16vector-length (S16Vector -> Nat)]
[s16vector-ref (S16Vector Nat -> Nat)]
[opaque ])
(provide write-sound/s16vector)
(define-type ChunkContent
(U (List Bytes (Listof Chunk))
Bytes
S16Vector))
(struct chunk (id content))
(struct vector-chunk (vec start stop))
(struct: Chunk ([id : String] [content : ChunkContent]))
(define global-numchannels 2)
(define global-bitspersample 16)
(define global-bytespersample (* global-bitspersample 1/8))
(define global-blockalign (* global-numchannels global-bytespersample))
(define global-samplemax (exact->inexact #x8000))
(define (write-sound/s16vector data start stop sample-rate path)
(call-with-output-file* path
(lambda (port)
(write-chunk (make-chunk (vector-chunk data start stop) sample-rate) port))
#:exists 'truncate))
(define (make-chunk vec sample-rate)
(chunk #"RIFF"
(list #"WAVE"
(list (make-format-chunk sample-rate)
(chunk #"data" vec)))))
(define (make-format-chunk sample-rate)
(chunk #"fmt "
(let ([audioformat (integer->integer-bytes 1 2 #f #f)]
[numchannels (integer->integer-bytes global-numchannels 2 #f #f)]
[samplerate (integer->integer-bytes sample-rate 4 #f #f)]
[byterate (integer->integer-bytes (* sample-rate global-numchannels global-bitspersample 1/8) 4 #f #f)]
[blockalign (integer->integer-bytes (* global-numchannels global-bitspersample 1/8) 2 #f #f)]
[bitspersample (integer->integer-bytes global-bitspersample 2 #f #f)])
(bytes-append audioformat numchannels samplerate byterate blockalign bitspersample))))
(define (write-chunk ch port)
(match ch
[(struct chunk (id content))
(display id port)
(display (integer->integer-bytes (content-display-len content) 4 #f #f) port)
(write-chunk-content content port)]))
(define (write-chunk-content content port)
(match content
[(list fmt-bytes (? list? elts)) (display fmt-bytes port)
(for-each (lambda (chunk) (write-chunk chunk port)) elts)]
[(? bytes? bs) (display bs port)]
[(vector-chunk vec start stop)
(for ([i (in-range start stop)])
(define sample (* i global-numchannels))
(display (integer->integer-bytes (s16vector-ref vec sample) 2 #t #f) port)
(display (integer->integer-bytes (s16vector-ref vec (add1 sample)) 2 #t #f) port))]))
(define (chunk-display-len chunk)
(+ 8 (content-display-len (chunk-content chunk))))
(define (content-display-len content)
(match content
[(list fmt-bytes (? list? elts)) (+ (bytes-length fmt-bytes) (apply + (map chunk-display-len elts)))]
[(? bytes? bs) (bytes-length bs)]
[(vector-chunk vec start stop) (* global-bytespersample
(* global-numchannels
(- stop start)))]))