1 Example: Streaming Vorbis files through Open AL
2 Example: Playing vorbis files with the rsound package
3 File management
open-vorbis-file
close-vorbis-file!
3.1 Querying information
vorbis-length-samples
vorbis-length-time
vorbis-current-time
vorbis-current-samples
vorbis-frequency
vorbis-channels
vorbis-comments
vorbis-vendor
vorbis-avg-bitrate
3.2 Seeking around
vorbis-seek-time!
vorbis-seek-samples!
4 Decoding data
4.1 The easy way
make-vorbis-input-port
4.2 The hard way
make-bitstream-ptr
vorbis-read-to-byte-buf!
vorbis-read-bytes!
5 Rsound integration
vorbis->rsound
6 License

Vorbisfile: Read sound data from .OGG Vorbis files

gcr

 (require (planet gcr/libvorbisfile:1:=2))
This library lets Racket decode Ogg Vorbis files using libvorbisfile, Xiph’s reference Ogg decoder library.
You can play Vorbis files with OpenAL using the (planet gcr/openal) package, or you can convert them to RSounds for the (planet clements/rsound) package. You can also extract metadata (frequency, channels, comments).
Ogg Vorbis is a popular sound file format, used especially in games where CPU time comes at a premium.

    1 Example: Streaming Vorbis files through OpenAL

    2 Example: Playing vorbis files with the rsound package

    3 File management

      3.1 Querying information

      3.2 Seeking around

    4 Decoding data

      4.1 The easy way

      4.2 The hard way

    5 Rsound integration

    6 License

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?
Opens the .Ogg Vorbis given by path and returns a handle to that file if successful, or #f on any error.
(close-vorbis-file! vorbisfile)  any/c
  vorbisfile : any/c
Closes the given vorbisfile. Note: Don’t call any functions on that file after it’s gone!

3.1 Querying information

(vorbis-length-samples vorbisfile)  exact-integer?
  vorbisfile : any/c
(vorbis-length-time vorbisfile)  real?
  vorbisfile : any/c
How long is vorbisfile? Returns the length in either seconds or number of PCM samples. Feast on them all! Pour every golden sapmle and every delicious second of that vorbisfile into your starving eardrums!

(vorbis-current-time vorbisfile)  real?
  vorbisfile : any/c
(vorbis-current-samples vorbisfile)  exact-integer?
  vorbisfile : any/c
Returns your current position in the file, in seconds or samples.

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
Returns the sample rate of vorbisfile. For example, 44100 is pretty common.
(vorbis-channels vorbisfile)  exact-integer?
  vorbisfile : any/c
Returns 1 for a mono vorbisfile and 2 for stereo. May sometimes return more than 2 for surround-sound vorbis files or less than 1 for deaf vorbis files.

(vorbis-comments vorbisfile)  (listof string?)
  vorbisfile : any/c
Returns a list of comments stored inside vorbisfile. There’s a standard for their format; see here for more details.

For example, a random file from my sound collection has these comments:
(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
Returns information about the Vorbis implementation that encoded the file. For example, the same sound file above has "Vendor: Lavf53.21.0" as the vendor.

(vorbis-avg-bitrate vorbisfile)  exact-integer?
  vorbisfile : any/c
Returns the average bitrate of vorbisfile. If you’re dealing with a wretched unseekable file, this will return the nominal bitrate, or the average of the upper and lower bitrates.

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?
If you’re lost, you can seek to a given time in seconds or samples. The next reads will continue from this point. These functions return #t upon success.

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?
Creates an input port that returns raw binary PCM data in the given format. This port may misbehave if you read less than a few wordsizes at a time.

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.

This function has no use; HOWEVER, libvorbis demands that you sacrifice the result of this function to every call to vorbis-read-to-byte-buf! and vorbis-read-bytes!.

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.

Yes, it’s a pain. No, I don’t care.
(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
Reads up to len bytes of sample data, overwriting the existing buf buffer and returns the number of bytes written or 0 if you’re at the end of the file. Note that vorbisfile will decode at most one frame of audio data at a time; thus, the actual amount of bytes read will often be far lower than len.

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.

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).
(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
Slightly less stupid than vorbis-read-to-byte-buf!, this function will actually bother to return the bytestring for you, or the eof object if you’re at the end of the 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 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
Converts vorbisfile to an rsound. Note that this reads the entire file into memory first.

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.