;;; Chickadee Game Toolkit ;;; Copyright © 2019 David Thompson ;;; ;;; Chickadee is free software: you can redistribute it and/or modify ;;; it under the terms of the GNU General Public License as published ;;; by the Free Software Foundation, either version 3 of the License, ;;; or (at your option) any later version. ;;; ;;; Chickadee is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;;; General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program. If not, see ;;; . ;;; Commentary: ;; ;; WAV decoder. ;; ;;; Code: (define-module (chickadee audio wav) #:use-module (ice-9 binary-ports) #:use-module (ice-9 format) #:use-module (rnrs bytevectors) #:use-module (srfi srfi-9) #:use-module (srfi srfi-9 gnu) #:export (open-wav close-wav wav? wav-num-channels wav-sample-rate wav-byte-rate wav-bits-per-sample wav-length wav-read wav-time-seek)) (define-record-type (make-wav num-channels sample-rate byte-rate block-align bits-per-sample length port) wav? (num-channels wav-num-channels) (sample-rate wav-sample-rate) (byte-rate wav-byte-rate) (block-align wav-block-align) (bits-per-sample wav-bits-per-sample) (length wav-length) (port wav-port)) (define (display-wav wav port) (format port "#" (wav-num-channels wav) (wav-sample-rate wav) (wav-byte-rate wav) (wav-block-align wav) (wav-bits-per-sample wav) (wav-length wav))) (set-record-type-printer! display-wav) (define (open-wav file-name) (let* ((port (open-file file-name "r")) (bv (get-bytevector-n port 44)) (data-length (bytevector-uint-ref bv 40 'little 4))) ;; Validate the magic values that must be present in the header. (unless (and (= (bytevector-uint-ref bv 0 'big 4) #x52494646) ; RIFF (= (bytevector-uint-ref bv 8 'big 4) #x57415645) ; WAVE (= (bytevector-uint-ref bv 12 'big 4) #x666d7420) ; fmt (= (bytevector-uint-ref bv 36 'big 4) #x64617461)) ; data (error "invalid WAV header in file:" file-name)) ;; Only PCM data is supported. (unless (= (bytevector-uint-ref bv 20 'little 2) 1) (error "WAV data not in PCM format:" file-name)) (make-wav (bytevector-uint-ref bv 22 'little 2) ; num channels (bytevector-uint-ref bv 24 'little 4) ; sample rate (bytevector-uint-ref bv 28 'little 4) ; byte rate (bytevector-uint-ref bv 32 'little 2) ; block align (bytevector-uint-ref bv 34 'little 2) ; bits per sample data-length port))) (define (close-wav wav) "Close the open file port for WAV." (close-port (wav-port wav))) (define (wav-read wav bv) "Fill BV with audio samples from WAV and return the number of bytes read." (let ((bytes-read (get-bytevector-n! (wav-port wav) bv 0 (bytevector-length bv)))) (if (eof-object? bytes-read) 0 bytes-read))) (define (wav-time-seek wav time) "Change the play head to TIME in WAV." (seek (wav-port wav) (+ 44 (inexact->exact (ceiling (* time (wav-num-channels wav) (wav-byte-rate wav) (wav-sample-rate wav))))) SEEK_SET))