Vorbisfile: Read sound data from .OGG Vorbis files
1 Example: Streaming Vorbis files through OpenAL
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))
There are many ways to read the decompressed sound data. By far, the easiest is to use the make-vorbis-input-port function to create a port that decodes the sound data into binary bytes on-demand.
;; 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.
(close-vorbis-file! m)
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.
2 Example: Playing vorbis files with the rsound package
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.
#lang racket/base (require (planet gcr/libvorbisfile) (planet gcr/libvorbisfile/rsound-compat) (planet clements/rsound) (planet clements/rsound/draw))
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!
(displayln "Playing 30s of sound...") (play s) (sleep 30)
RSound plays in the background, so the sleep call is necessary; else the program will quit right when it starts.
3 File management
(open-vorbis-file path) → (or/c #f any/c) path : string?
(close-vorbis-file! vorbisfile) → any/c vorbisfile : any/c
3.1 Querying information
(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.
(vorbis-frequency vorbisfile) → exact-integer? vorbisfile : any/c
(vorbis-channels vorbisfile) → exact-integer? vorbisfile : any/c
(vorbis-comments vorbisfile) → (listof string?) vorbisfile : any/c
(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-vendor vorbisfile) → string? vorbisfile : any/c
(vorbis-avg-bitrate vorbisfile) → exact-integer? vorbisfile : any/c
3.2 Seeking around
(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.
4 Decoding data
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.
4.1 The easy way
(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.
4.2 The hard way
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).
5 Rsound integration
(require (planet gcr/libvorbisfile:1:=2/rsound-compat)) |
Vorbisfile can also bake your fine music into RSounds for John Clements’ (planet clements/rsound) library.
(vorbis->rsound vorbisfile) → any/c vorbisfile : any/c
6 License
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 |
distribution. |