summaryrefslogtreecommitdiff
path: root/chickadee/audio/wav.scm
blob: b9b7c73969c0af1aea14c0b35ac4d73b2efe6c6c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
;;; Chickadee Game Toolkit
;;; Copyright © 2019 David Thompson <dthompson2@worcester.edu>
;;;
;;; 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))