audio: Add WAV module.
authorDavid Thompson <dthompson2@worcester.edu>
Thu, 10 Jan 2019 01:25:45 +0000 (20:25 -0500)
committerDavid Thompson <dthompson2@worcester.edu>
Tue, 7 Apr 2020 20:10:23 +0000 (16:10 -0400)
* chickadee/audio/wav.scm: New file.
* Makefile.am (SOURCES): Add it.

Makefile.am
chickadee/audio/wav.scm [new file with mode: 0644]

index f9776f8..765a488 100644 (file)
@@ -56,6 +56,7 @@ SOURCES =                                     \
   chickadee/math/easings.scm                   \
   chickadee/math/path-finding.scm              \
   chickadee/audio/openal.scm                   \
+  chickadee/audio/wav.scm                      \
   chickadee/render/color.scm                   \
   chickadee/render/gl.scm                      \
   chickadee/render/gpu.scm                     \
diff --git a/chickadee/audio/wav.scm b/chickadee/audio/wav.scm
new file mode 100644 (file)
index 0000000..9b39ad4
--- /dev/null
@@ -0,0 +1,104 @@
+;;; Chickadee Game Toolkit
+;;; Copyright © 2019 David Thompson <davet@gnu.org>
+;;;
+;;; 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
+;;; <http://www.gnu.org/licenses/>.
+
+;;; 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 <wav>
+  (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: ~d sample-rate: ~d byte-rate: ~d block-align: ~d bits-per-sample: ~d length: ~d>"
+          (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! <wav> 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))