diff options
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | chickadee/audio/mpg123.scm | 229 | ||||
-rw-r--r-- | chickadee/config.scm.in | 3 | ||||
-rw-r--r-- | configure.ac | 10 |
4 files changed, 243 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am index 54839f1..6183b03 100644 --- a/Makefile.am +++ b/Makefile.am @@ -55,6 +55,7 @@ SOURCES = \ chickadee/math/grid.scm \ chickadee/math/easings.scm \ chickadee/math/path-finding.scm \ + chickadee/audio/mpg123.scm \ chickadee/audio/openal.scm \ chickadee/audio/vorbis.scm \ chickadee/audio/wav.scm \ diff --git a/chickadee/audio/mpg123.scm b/chickadee/audio/mpg123.scm new file mode 100644 index 0000000..20ff3ea --- /dev/null +++ b/chickadee/audio/mpg123.scm @@ -0,0 +1,229 @@ +;;; 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: +;; +;; libmpg123 bindings. +;; +;;; Code: + +(define-module (chickadee audio mpg123) + #:use-module (chickadee config) + #:use-module (ice-9 format) + #:use-module (ice-9 match) + #:use-module (rnrs bytevectors) + #:use-module (srfi srfi-9) + #:use-module (srfi srfi-9 gnu) + #:use-module (system foreign) + #:export (mpg123-init + mpg123-exit + mpg123-open + mpg123-close + mpg123-format + mpg123-read + mpg123-time-seek + mpg123-length)) + + +;;; +;;; Enums +;;; + +(define MPG123_VERBOSE 0) +(define MPG123_FLAGS 1) +(define MPG123_ADD_FLAGS 2) +(define MPG123_FORCE_RATE 3) +(define MPG123_DOWN_SAMPLE 4) +(define MPG123_RVA 5) +(define MPG123_DOWNSPEED 6) +(define MPG123_UPSPEED 7) +(define MPG123_START_FRAME 8) +(define MPG123_DECODE_FRAMES 9) +(define MPG123_ICY_INTERVAL 10) +(define MPG123_OUTSCALE 11) +(define MPG123_TIMEOUT 12) +(define MPG123_REMOVE_FLAGS 13) +(define MPG123_RESYNC_LIMIT 14) +(define MPG123_INDEX_SIZE 15) +(define MPG123_PREFRAMES 16) +(define MPG123_FEEDPOOL 17) +(define MPG123_FEEDBUFFER 18) + +(define MPG123_ENC_8 #x00f) +(define MPG123_ENC_16 #x040) +(define MPG123_ENC_24 #x4000) +(define MPG123_ENC_32 #x100) +(define MPG123_ENC_SIGNED #x080) +(define MPG123_ENC_FLOAT #xe00) +(define MPG123_ENC_SIGNED_16 (logior MPG123_ENC_16 MPG123_ENC_SIGNED #x10)) +(define MPG123_ENC_UNSIGNED_16 (logior MPG123_ENC_16 #x20)) +(define MPG123_ENC_UNSIGNED_8 #x01) +(define MPG123_ENC_SIGNED_8 (logior MPG123_ENC_SIGNED #x02)) +(define MPG123_ENC_ULAW_8 #x04) +(define MPG123_ENC_ALAW_8 #x08) +(define MPG123_ENC_SIGNED_32 (logior MPG123_ENC_32 MPG123_ENC_SIGNED #x1000)) +(define MPG123_ENC_UNSIGNED_32 (logior MPG123_ENC_32 #x2000)) +(define MPG123_ENC_SIGNED_24 (logior MPG123_ENC_24 MPG123_ENC_SIGNED #x1000)) +(define MPG123_ENC_UNSIGNED_24 (logior MPG123_ENC_24 #x2000)) +(define MPG123_ENC_FLOAT_32 #x200) +(define MPG123_ENC_FLOAT_64 #x400) + + + +;;; +;;; Low-level bindings +;;; + +(define mpg123-func + (let ((lib (dynamic-link %libmpg123))) + (lambda (return-type function-name arg-types) + (pointer->procedure return-type + (dynamic-func function-name lib) + arg-types)))) + +(define-syntax-rule (define-foreign name return-type func-name arg-types) + (define name + (mpg123-func return-type func-name arg-types))) + +(define off_t int) + +(define-foreign %mpg123-init + int "mpg123_init" '()) + +(define-foreign mpg123-exit + void "mpg123_exit" '()) + +(define-foreign mpg123-new + '* "mpg123_new" '(* *)) + +(define-foreign mpg123-delete + void "mpg123_delete" '(*)) + +(define-foreign mpg123-param + int "mpg123_param" (list '* int long double)) + +(define-foreign mpg123-getparam + int "mpg123_getparam" (list '* int '* '*)) + +(define-foreign mpg123-getformat + int "mpg123_getformat" '(* * * *)) + +(define-foreign mpg123-plain-strerror + '* "mpg123_plain_strerror" (list int)) + +(define-foreign mpg123-strerror + '* "mpg123_strerror" '(*)) + +(define-foreign %mpg123-open + int "mpg123_open" '(* *)) + +(define-foreign %mpg123-close + int "mpg123_close" '(*)) + +(define-foreign %mpg123-read + int "mpg123_read" (list '* '* size_t '*)) + +(define-foreign mpg123-timeframe + off_t "mpg123_timeframe" (list '* double)) + +(define-foreign mpg123-seek-frame + off_t "mpg123_seek_frame" (list '* off_t int)) + +(define-foreign %mpg123-length + off_t "mpg123_length" (list '*)) + + +;;; +;;; Error Handling +;;; + +(define (mpg123-check-error func error-id message . args) + (let* ((error-string (pointer->string (mpg123-plain-strerror error-id)))) + (unless (zero? error-id) + (apply throw 'mpg123-error func (string-append message ": ~A") + (append args (list error-string)))))) + + +;;; +;;; High-level bindings +;;; + +(define (display-mpg123-handle handle port) + (display "#<mpg123-handle>" port)) + +(define-wrapped-pointer-type <mpg123-handle> mpg123-handle? + wrap-mpg123-handle unwrap-mpg123-handle display-mpg123-handle) + +(define (mpg123-init) + (mpg123-check-error "mpg123-init" (%mpg123-init) "failed to initialize")) + +(define (mpg123-open file-name) + (let ((handle (or (mpg123-new %null-pointer %null-pointer) + (error "failed to create mpg123 handle")))) + (%mpg123-open handle (string->pointer file-name)) + (wrap-mpg123-handle handle))) + +(define (mpg123-close handle) + (let ((ptr (unwrap-mpg123-handle handle))) + (%mpg123-close ptr) + (mpg123-delete ptr))) + +(define (mpg123-format handle) + (let ((rate (make-bytevector (sizeof long))) + (channels (make-bytevector (sizeof int))) + (encoding (make-bytevector (sizeof int)))) + (mpg123-check-error "mpg123-format" + (mpg123-getformat (unwrap-mpg123-handle handle) + (bytevector->pointer rate) + (bytevector->pointer channels) + (bytevector->pointer encoding)) + "failed to get audio format info") + (let* ((encoding (bytevector-sint-ref encoding 0 (native-endianness) + (sizeof int))) + (bits-per-sample (cond + ((= encoding MPG123_ENC_SIGNED_8) + 8) + ((= encoding MPG123_ENC_SIGNED_16) + 16) + ((= encoding MPG123_ENC_SIGNED_24) + 24) + ((= encoding MPG123_ENC_SIGNED_32) + 32) + (else + (error "unsupported mp3 encoding" encoding))))) + (values (bytevector-sint-ref rate 0 (native-endianness) (sizeof long)) + (bytevector-sint-ref channels 0 (native-endianness) (sizeof int)) + bits-per-sample)))) + +(define (mpg123-read handle buffer) + (let* ((bv (make-bytevector (sizeof size_t))) + (error-code (%mpg123-read (unwrap-mpg123-handle handle) + (bytevector->pointer buffer) + (bytevector-length buffer) + (bytevector->pointer bv)))) + (when (> error-code 0) + (mpg123-check-error "mpg123-read" error-code "failed to read audio")) + (bytevector-sint-ref bv 0 (native-endianness) (sizeof size_t)))) + +(define (mpg123-time-seek handle t) + (let ((offset (mpg123-timeframe (unwrap-mpg123-handle handle) t))) + (mpg123-check-error "mpg123-time-seek" + (mpg123-seek-frame (unwrap-mpg123-handle handle) offset SEEK_SET) + "failed to seek"))) + +(define (mpg123-length handle) + (%mpg123-length (unwrap-mpg123-handle handle))) diff --git a/chickadee/config.scm.in b/chickadee/config.scm.in index ab88dbb..5a2a8f8 100644 --- a/chickadee/config.scm.in +++ b/chickadee/config.scm.in @@ -26,6 +26,7 @@ %chickadee-version %libopenal %libvorbisfile + %libmpg123 scope-datadir)) (define %datadir @@ -35,6 +36,8 @@ (define %libopenal "@OPENAL_LIBDIR@/libopenal") (define %libvorbisfile "@VORBIS_LIBDIR@/libvorbisfile") +(define %libmpg123 "@MPG123_LIBDIR@/libmpg123") + (define (scope-datadir file) "Append the Chickadee data directory to FILE." (string-append %datadir "/" file)) diff --git a/configure.ac b/configure.ac index 2bc4ad7..a28d1fe 100644 --- a/configure.ac +++ b/configure.ac @@ -43,4 +43,14 @@ AS_IF([test "VORBIS_LIBDIR" = "x"], [ ]) AC_SUBST([VORBIS_LIBDIR]) +PKG_CHECK_MODULES([mpg123], [libmpg123]) +PKG_CHECK_VAR([MPG123_LIBDIR], [libmpg123], [libdir]) +AC_MSG_CHECKING([mpg123 library path]) +AS_IF([test "MPG123_LIBDIR" = "x"], [ + AC_MSG_FAILURE([Unable to identify mpg123 lib path.]) +], [ + AC_MSG_RESULT([$MPG123_LIBDIR]) +]) +AC_SUBST([MPG123_LIBDIR]) + AC_OUTPUT |