One way of playing Vorbis files is by using the (planet gcr/openal) package. This is more efficient than loading megabytes of sound data into memory before playing it, but it’s also a bit more complicated than the RSound example.
First, one must dig out the spellbook and incant the OpenAL Summoning Mantra:
#lang racket (require (planet gcr/openal) (planet gcr/libvorbisfile)) ;; Initialize OpenAL (see the docs for (planet gcr/openal)) (define device (open-device #f)) (define context (create-context device)) (set-current-context context)
From here, it’s not rocket science to open a vorbis file and query basic information about it.
(define filename "/home/gcr/Music/Lights/Siberia/11 Flux and Flow.ogg") (printf "Playing file ~a\n" filename) ;; Open the file! (define m (open-vorbis-file filename)) (printf "Rate: ~a Channels: ~a Length: ~a sec\n" (vorbis-frequency m) (vorbis-channels m) (vorbis-length-time m))
;; To read the PCM samples, we make a port that supplies us with ;; the binary decompressed data. (define vorbis-binary (make-vorbis-input-port m 0 2 1)) ;; OpenAL expects: ;; 0 (Little-endian) ;; 2 (Word size; 16 bits) ;; 1 (Signed)
Now that we have a port that gives us raw sample data, we can stream it straight to an OpenAL source. This avoids reading the entire file into memory – each block of sound is decoded right as it’s needed.
;; Make our OpenAL source (define source (car (gen-sources 1))) ;; Start streaming (define stream-thread (stream-port-to-source vorbis-binary source AL_FORMAT_STEREO16 (vorbis-frequency m))) ;; Start playing (play-source source) ;; OpenAL's stream-port-to-source returns a thread, so wait until we're ;; finished playing (thread-wait stream-thread)
Once we’re done, we should clean up our OpenAL mess:
(set-current-context #f) (destroy-context! context) (close-device! device)
You should probably close the vorbis file when you’re finished.
Well, to be honest, I haven’t tested whether this is really necessary, but do be a good citizen, OK? Memory corruption city is never a nice place to be.
Vorbisfile can also work with John Clements’ (planet clements/rsound) library. Of course, rsound is far more mature and easier to use than (planet gcr/openal). Unfortunately, with the current implementation, converting a vorbis sound to an rsound is quite inefficient because the entire sound must be loaded into memory before playing it, which costs many needless megabytes of RAM and many needless seconds of CPU time.
With rsound, no extra setup is necessary – just load the libraries and you’re good to go. Note that to keep dependencies small, the rsound converter lives inside its own helper module, (planet gcr/libvorbisfile-rsound).
#lang racket/base (require (planet gcr/libvorbisfile) (planet gcr/libvorbisfile-rsound) (planet clements/rsound))
Open and load a sound file to play:
(define filename "/home/gcr/Music/Lights/Siberia/14 Day One.ogg") (displayln "Loading sound...") (define o (open-vorbis-file filename)) (define s (vorbis->rsound o))
And finally, play that sound!
RSound plays in the background, so the sleep call is necessary; else the program will quit right when it starts.
(vorbis-length-samples vorbisfile) → exact-integer? vorbisfile : any/c
(vorbis-length-time vorbisfile) → real? vorbisfile : any/c
(vorbis-current-time vorbisfile) → real? vorbisfile : any/c
(vorbis-current-samples vorbisfile) → exact-integer? vorbisfile : any/c
Note that the reported position could be a few fractions of a second behind because we aggressively buffer the sound data to minimize the number of FFI calls. Don’t be sad if the result doesn’t change after reading a few samples of data.
(list "minor_version=0" "compatible_brands=M4A mp42isom" "creation_time=2034-07-04 05:42:34" "title=Siberia" "artist=Lights" "ALBUMARTIST=Lights" "album=Siberia" "genre=Pop" "TRACKNUMBER=1/16" "DISCNUMBER=1/1" "gapless_playback=0" "date=2011-10-04T07:00:00Z" "copyright=℗ 2011 Lights Music Inc. Distributed exclusively through Last Gang Records Inc" "media_type=1" "encoder=Lavf53.21.0")
(vorbis-seek-time! vorbisfile sec) → boolean? vorbisfile : any/c sec : real?
(vorbis-seek-samples! vorbisfile samples) → boolean? vorbisfile : any/c samples : exact-integer?
To avoid loud pops and clicks when seeking around, libvorbisfile uses a process called crow-slapping to smooth out the sound gap between where you left and your new position in the file. This makes the result sound much nicer, but do understand that the next two samples of data won’t be an exact reproduction of the original file.
Certain heretics enjoy assembling unseekable vorbis files. Such tainted, blasphemous creatures will cause this function to return #f. Shun them to preserve your own sanity.
Unless you’re dealing with internet radio or something i guess.
There are two ways of decoding data. The hard way involves reading raw sample data to an existing byte buffer; the easy way allows you to merely create an input port that returns the binary decoded sample data.
(make-vorbis-input-port vorbisfile big-endian? wordsize signed?) → input-port? vorbisfile : any/c big-endian? : exact-integer wordsize : exact-integer? signed? : exact-integer?
Beware! Closing this input port will also close the vorbis file.
The big-endian? parameter should be 1 if you want your samples big-endian or 0 if you prefer little-endian. The wordsize parameter specifies how many bytes each sample should be – 1 for 8-bit samples, 2 for 16-bit. The signed? parameter should be 1 if you wish for signed integers and 0 if your delicate heart yearns for unsigned integers.
Here’s your one-way ticket to Memory Corruption City. Not for the faint of heart.
I’m just kidding of course. These functions aren’t that hard to work with, but I feel bad that you’re reading all the way down here because I spent a lot of hard work on make-vorbis-input-port so you should probably use that loving craftsmanship instead of these crappy bindings.
Unless you’re the kind of person who cares about multiple vorbis bitstreams inside a single file. Heretic.
Technically, Ogg Vorbis files allow certain unscrupled people to cram several streams together into one .ogg container, essentially concatenating several ogg files together. Unfortunately, our humble library makes no meaningful distinction between them, instead forcing innocent developers like you to needlessly keep track of it. As such, we don’t automatically handle the case when the sample rate changes for no reason. Thankfully, there aren’t many Vorbis files that have multiple bitstreams inside them, and obviously, if any such files did exist, they are obvious contraband and you should immediately dispose of them.
(vorbis-read-to-byte-buf! vorbisfile buf len big-endian? wordsize signed? bitstream) → exact-integer? vorbisfile : any/c buf : bytes? len : exact-integer? big-endian? : exact-integer? wordsize : exact-integer? signed? : exact-integer? bitstream : any/c
Remember what I said earlier about Memory Corruption City? If buf is smaller than len, you’re just begging for an all-expense-paid season pass. Don’t even try it.
(vorbis-read-bytes! vf len bigendianp wordsize signedp bitstream) → (or/c bytes? eof-object?) vf : any/c len : exact-integer? bigendianp : exact-integer? wordsize : exact-integer? signedp : exact-integer? bitstream : any/c
The big-endian? parameter should be 1 if you want your samples big-endian or 0 if you prefer little-endian. The wordsize parameter specifies how many bytes each sample should be – 1 for 8-bit samples, 2 for 16-bit. The signed? parameter should be 1 if wish for signed integers and 0 if you yearn for unsigned integers. Finally, bitstream should be the result of the call to (make-bitstream-ptr).
Vorbisfile can also bake your fine music into RSounds for John Clements’ (planet clements/rsound) library. To avoid large dependencies, this functionality lives in a small helper package. To use it, put (require (planet gcr/libvorbisfile-rsound)) at the top of your code.
Keep in mind that libvorbisfile itself is licensed under a BSD-style license; see here for details. I don’t know what license RSound has.
The code in this (planet gcr/libvorbisfile) package and this documentation is under the zlib license, reproduced below.
Copyright (c) 2012 gcr
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source