Mixalot User Manual

Version 0.0.5

Table of Contents

  1. Introduction
  2. Installation
  3. Portability
  4. System: mixalot
  5. System: mpg123-ffi
  6. System: mixalot-mp3
  7. System: vorbisfile-ffi
  8. System: mixalot-vorbis
  9. System: flac-ffi
  10. System: mixalot-flac
  11. Future Work
  12. License

Introduction

Mixalot is a collection of systems related to audio in Common Lisp, under an MIT-style license. Currently it consists of a mixer component providing real-time audio output on Linux (via ALSA) and other platforms (using libao), CFFI bindings to the libmpg123, libFLAC, and libvorbisfile libraries, and audio stream classes for simple playback of MP3, Ogg, and FLAC files through the mixer.

Installation

The most recent development version of Mixalot is hosted on github and can be obtained as follows:

git clone git://github.com/ahefner/mixalot.git

Mixalot includes several ASDF systems which should be symlinked into the ASDF central registry in the usual fashion. It depends directly on the following systems:

You may find this library useful for the following purposes:

Portability

The CFFI and bordeaux-threads libraries are used to ease porting between lisp implementations. The mixer component of Mixalot chooses at compile time to use ALSA (on Linux) or libao (everywhere else) and should be able to produce audio on any platform supported by libao. In order to be useful in an application, the mixer must also run in its own (native) thread. Therefore, the mixalot and mixalot-mp3 systems should be usable on CL implementations capable of running multiple threads, on Linux or platforms supported by libao. It has been tested and is known to work in the following configurations:

The mpg123-ffi system is independent and should be usable on any CL supported by CFFI.

System: mixalot

Introduction

Mixalot implements an audio mixer which handles the mixing of multiple audio streams and playback through ALSA. Once created, a mixer runs in its own thread of execution, and other threads can add and remove audio streamers at any time. The terms "stream" and "streamer" are used interchangeably here, but the concrete objects in the lisp system are labelled "streamers" to avoid confusion with the Common Lisp stream classes.

All functions in this section reside in the mixalot package.

Mixers

A mixer is host to zero or more streamers. In the mixalot package, there are functions to create/destroy mixers, and add/remove streamers from a mixer.

Function main-thread-init

Initialize the platform's audio subsystem. This must be called from the main thread of the application. Under SBCL, this will be the thread containing the initial REPL, or the *inferior-lisp* buffer while using SLIME (TODO: investigate situation on Clozure CL). It is only necessary to call this once, and must be done before calling create-mixer (which can be called from any thread).

On Linux, Mixalot uses ALSA, which requires no such initialization. For consistency, this function is defined to do nothing, and calling it is unnecessary. On other platforms, which use libao for audio output, this initialization is mandatory due to arbitrary restrictions imposed by certain operating systems (notably Mac OS X).

Function create-mixer &key (rate 44100)mixer

Creates a mixer running at the specified sampling rate, and returns it. The default ALSA device is opened and a thread for the mixer is started. The chosen sample rate should match the sampling rate of the data you intend to play through the mixer - the Mixalot mixer does not perform resampling.

Function destroy-mixer mixer

Requests that mixer be shut down. This will occur after the current audio buffer has completed playback. When the mixer shuts down, each of its streams will be removed just as if mixer-remove-streamer had been called, so it is not necessary to manually clean up the streams when shutting down a mixer. Attempts to add additional streamers to mixer after calling destroy-mixer on it will result in an error.

Function mixer-add-streamer mixer streamerstreamer start-time

Add a streamer for playback by the given mixer. After being added to the mixer, it will begin calling the streamer functions (see Basic Streamer Protocol) to generate samples.

Returns two values: the streamer itself, and the time in samples since the start of the mixer when the streamer will begin playback.

Function mixer-remove-streamer mixer streamer

Request that streamer be from the mixer. The streamer-cleanup function will be called from within the mixer's thread when this occurs. This occurs asynchronously because the stream may be generating audio at the time this function is called, and it would not be safe to clean it up until it is assured that the mixer will not call the mix/write functions again.

Function mixer-remove-all-streamers mixer

Removes all streamers belonging to the mixer.

Function mixer-current-time mixertime

Returns the time, measured in samples, since the mixer was created.

Function mixer-rate mixerrate

Returns the sampling rate in Hertz of the output and inputs to the mixer.

Streamers

A streamer is any object serving as a source of samples which are mixed into the mixer's audio buffer by implementing the Basic Streamer Protocol. From the mixer thread, the functions streamer-mix-into and streamer-write-into are called periodically to generate audio. These two functions correspond to the cases of multiple or a single streamer being mixed, respectively, and it is only necessary to implement streamer-mix-into.

Defining a method on streamer-write-into allows an optimization in the case where only one stream is being mixed into the buffer, where the streamer can (and is expected to) overwrite the specified range of the buffer rather than mixing with the existing audio. The default method on streamer-write-into simply fills the specified range of the buffer to zero before calling streamer-mix-into.

In many situations, a stream has only finite duration. In this situation, when the stream has reached its end, it can call mixer-remove-streamer from its write/mix method to remove itself from the mixer. This guarantees its mix/write methods will not be called again, and the mixer will call streamer-cleanup to ensure any resources associated with the streamer are released.

The simplest example of a streamer is a function object taking the arguments of streamer-mix-into. This is enabled by pre-existing methods on the streamer functions. Here is an example of a streamer implemented as a lambda function which produces a series of six tones with increasing frequency, then removes itself from the mixer:

(defun make-test-streamer ()
  (let ((n 0)
        (phase 0.0))
    (lambda (streamer mixer buffer offset length time)
      (declare (ignore time))
      (loop for index upfrom offset
            repeat length
            with freq = (+ 200 (* n 200))
            with dp = (* 2.0 pi freq 1/44100)
            as sample = (round (* 5000 (sin phase)))
            do
            (mixalot:stereo-incf (aref buffer index) (mixalot:mono->stereo sample))
            (incf phase dp))
      (incf n)
      (when (= n 6)
        (mixalot:mixer-remove-streamer mixer streamer)))))

Here is an example of how to create a mixer and play the test streamer defined above:

CL-USER> (defparameter *mixer* (mixalot:create-mixer))
*MIXER*
CL-USER> (mixer-add-streamer *mixer* (make-test-streamer))
#<CLOSURE (LAMBDA (STREAMER MIXER BUFFER ...)) {1003F045A9}>
507904
CL-USER>

Here is the same streamer, implemented via classes and methods:

(defclass test-streamer ()
  ((n     :initform 0)
   (phase :initform 0.0)))

(defmethod mixalot:streamer-mix-into ((streamer test-streamer) mixer buffer offset length time)
  (declare (ignore time))
  (with-slots (n phase) streamer
    (loop for index upfrom offset
          repeat length
          with freq = (+ 200 (* n 200))
          with dp = (* 2.0 pi freq 1/44100)
          as sample = (round (* 5000 (sin phase)))
          do
          (mixalot:stereo-incf (aref buffer index) (mixalot:mono->stereo sample))
          (incf phase dp))
    (when (= (incf n) 6)
      (mixalot:mixer-remove-streamer mixer streamer))))

Basic Streamer Protocol

These generic functions define the essential behaviors of an audio streamer. Of them, only streamer-mix-into is mandatory to implement.

Generic Function streamer-mix-into streamer mixer buffer offset length time

Generate length samples of audio from streamer. The audio should be mixed into the provided buffer (of type sample-vector) starting from offset. Time indicates the time in samples since the creation of the mixer.

The provided mix-stereo-samples function or stereo-mixf macro can be used for mixing samples.

When a streamer has completed playback, it can call mixer-remove-streamer after generating its last batch of audio to remove itself from the mixer.

Generic Function streamer-write-into streamer mixer buffer offset length time

Generate length samples of audio from streamer. The contents of buffer (of type sample-vector) may be invalid and must be overwritten (not mixed into) starting from offset. This method exists as an optimization for the case of the first or only playing streamer, to avoid redundant zero-filling of the buffer and unnecessary mixing. The default method fills the buffer with zeros then calls streamer-mix-into, so implementing a more specific method for this is optional. Time indicates the time in samples since the creation of the mixer

When a streamer has completed playback, it can call mixer-remove-streamer after generating its last batch of audio to remove itself from the mixer.

Generic Function streamer-cleanup streamer mixer

Notification that streamer has been removed from the mixer, and its streamer functions will not be called again. This allows the streamer to release resources no longer needed, such as open file handles or foreign memory.

Pausing Protocol

Streamers are inherently pauseable, achieved by the mixer electing not to call on them to generate audio while they are paused (the mixer tracks the paused/playing state itself). Therefore, most streamers will have no reason to define more specific methods on these functions. A counterexample might be a streamer streaming audio over the network, which may want to adjust its buffering behavior when the stream is paused and unpaused.

Generic Function streamer-pause streamer mixer

Pause playback of the streamer through the mixer. If streamer is already paused, it remains paused.

Generic Function streamer-unpause streamer mixer

Unpause playback of the streamer through the mixer. If streamer is already playing, it continues to play.

Generic Function streamer-paused-p streamer mixerboolean

Returns true if playback of the streamer through the mixer is currently paused.

Seekable Protocol

Some streamers may have the ability to seek to an offset during playback. These streamers must implement the Seekable protocol, comprised of the functions below. Users can call these functions to control and query stream position and length.

Generic Function streamer-seekable-p streamer mixerboolean

Returns true is the streamer is seekable. Attempting to seek or query the stream length or position may signal an error unless this returns true.

Generic Function streamer-length streamer mixerlength-or-nil

Returns the length, in samples, of the streamer. If this can not be determined, returns nil.

Generic Function streamer-seek streamer mixer position &key &allow-other-keys

Seek to a position, measured in samples, on streamer. Streamers are permitted to seek approximately (for instance, rounding to the nearest frame of compressed data). Specific streamer types may take additional arguments.

Issue: Should this return a boolean indicated success/failure? It's feasible that on compressed or network streams, it might not always be possible to seek at all, or the circumstances in which it may fail may be subtle. On the other hand, these streams could always roll their own protocol using the condition system.

Generic Function streamer-position stream mixerposition

Returns the position, measured in samples, of playback within the streamer.

Vector Streamers

Several classes are provided for streaming audio directly from a lisp vector in memory. These vary in input format and come in regular and fast variants, where the fast version assumes the input vector is an appropriately specialized simple-array. The following functions construct these streamers:

Function make-vector-streamer-mono vector &optional (start 0) (end (length vector))
vector-streamer

Creates a vector-streamer streaming 16-bit mono audio from a range of vector defined by start and end.

Function make-fast-vector-streamer-mono vector &optional (start 0) (end (length vector))
vector-streamer

Creates a vector-streamer streaming 16-bit mono audio from a range of vector defined by start and end. Vector is assumed to be of type (simple-array (signed-byte 16) 1).

Function make-vector-streamer-interleaved-stereo vector &optional (start 0) (end (length vector))
vector-streamer

Creates a vector-streamer streaming 16-bit stereo audio from a range of vector defined by start and end. Samples are read in an interleaved format with separate array elements for the left and right channels, such that one output sample corresponds to two input elements.

Function make-fast-vector-streamer-interleaved-stereo vector &optional (start 0) (end (length vector))
vector-streamer

Creates a vector-streamer streaming 16-bit stereo audio from a range of vector defined by start and end. Samples are read in an interleaved format with separate array elements for the left and right channels, such that one output sample corresponds to two input elements. Vector is assumed to be of type (simple-array (signed-byte 16) 1).

Function make-vector-streamer-joint-stereo vector &optional (start 0) (end (length vector))
vector-streamer

Creates a vector-streamer streaming 16-bit stereo audio in joint format (see stereo-sample) from a range of vector defined by start and end.

Function make-fast-vector-streamer-joint-stereo vector &optional (start 0) (end (length vector))
vector-streamer

Creates a vector-streamer streaming 16-bit stereo audio in joint format (see stereo-sample) from a range of vector defined by start and end. Vector is assumed to be of type sample-vector.

Function make-vector-streamer-mono-single-float vector &optional (start 0) (end (length vector))
vector-streamer

Creates a vector-streamer streaming a single channel of audio in floating-point format from a range of vector defined by start and end. Samples must be in single-float format, and should be scaled to fit within the range -1.0 to 1.0. Values outside this range will be clamped. Vector is assumed to be of type (vector single-float).

Function make-vector-streamer-mono-double vector &optional (start 0) (end (length vector))
vector-streamer

Creates a vector-streamer streaming a single channel of audio in floating-point format from a range of vector defined by start and end. Samples must be in double-float format, and should be scaled to fit within the range -1.0 to 1.0. Values outside this range will be clamped. Vector is assumed to be of type (vector double-float).

Sample Manipulation

For simplicity, Mixalot deals uniformly in 16-bit stereo audio, but the sampling rate may vary. The following type definitions relate to samples and audio buffers:

Type mono-sample
(deftype mono-sample ()
  '(or (signed-byte 16)
       (unsigned-byte 16)))

A mono sample is typically represented as a (signed-byte 16). Occasionally, it is convenient to use an unsigned representation instead.

Type stereo-sample
(deftype stereo-sample () '(unsigned-byte 32))

This is the native representation of audio in Mixalot. Two 16-bit samples are combined into a single word, with the left channel stored in the low word and the right channel stored in the high word.

Type sample-vector
(deftype sample-vector () '(simple-array stereo-sample 1))

The sample-vector type denotes specialized one-dimensional simple-arrays of stereo samples.

Various functions are available for manipulating samples. All of the following functions are declared inline, with appropriately declared types, and compiled to reasonably efficient code at the time of this writing (using SBCL 1.0.28.27/x86_64).

Function stereo-sample left rightsample

Construct a stereo-sample from separate left and right components, each a mono-sample.

Function mono->stereo mono-samplestereo-sample

Expand a mono-sample to a stereo-sample by duplicating it on the left and right channel.

Function stereo->mono stereo-samplemono-sample

Convert a stereo-sample to a mono-sample by averaging the left and right channels.

Function stereo-left stereo-sampleleft

Returns the left channel component of a stereo-sample.

Function stereo-right stereo-sampleright

Returns the right channel component of a stereo-sample.

Function split-sample stereo-sampleleft right

Returns two values, the left and right components of a stereo-sample.

Function clamp-sample integermono-sample

Clamp an integer to the range -32,768...32,767.

Function clamp-sample+ x ysum

Compute the sum of integers x and y, clamped to the range -32,768...32,767.

Function add-stereo-samples x yunclamped-sum

Returns the pairwise sum of the stereo-samples x and y, without clipping. The sums of the individual channels may wrap, but will not overflow into the other channel.

Function mix-stereo-samples x yclamped-sum

Returns the pairwise sum of the stereo-samples x and y, clamped to -32,768...32,767.

Macro stereo-incf place samplesum

Increment the contents of place (a stereo-sample) by sample as if by add-stereo-samples.

Macro stereo-mixf place samplesum

Increment the contents of place (a stereo-sample) by sample as if by mix-stereo-samples.

System: mpg123-ffi

The mpg123-ffi system presents a thin wrapper around libmpg123 and should, given the source code, be self explanatory in combination with the original libmpg123 documentation. It provides bindings to most of the API, excluding the advanced parameters API and a few functions dealing with feeding MPEG frames directly into the decoder (and patches for these are welcome).

It also manages library initialization and provides helper functions for retrieving ID3 tags and decoding MP3 files. The ID3 decoding is intended to be robust in the face of badly formed or encoded input common in MP3 files, and failure to do so should be reported as a bug.

The functions are exported from the mpg123 package and comprise a thin wrapper in the sense that most of the C functions are exported directly with no extra lisp wrapper. A few exceptions involving out parameters and other awkward idioms are wrapped to use multiple return values instead; only these are documented explicitly below. For all other functions, see mpg123.lisp.

Function ensure-library-initialized

Ensures that the libmpg123 has been initialized.

Condition mpg123-error

An error from the mpg123 library.

Condition mpg123-handle-error (inherits from mpg123-error)

An error from the mpg123 library in the context of a handle.

Function check-mpg123-plain-error circumstance code

Examines the code returned by a call to libmpg123 (excluding those functions which act on handles). If it indicates an error, it translates the error to a descriptive string and signals an error of type mpg123-error. Circumstance is a string, included in the error report, intended to indicate which library call produced the error or what the programmer was attempting to do.

Function check-mh-error circumstance handle value

Examines the value returned by a call (in the context of the mpg123_handle pointer handle) to libmpg123. If it indicates an error, it translates the error to a descriptive string and signals an error of type mpg123-handle-error. Circumstance is a string, included in the error report, intended to indicate which library call produced the error or what the programmer was attempting to do. If no error occurred, value is returned.

Function mpg123-decodersdecoders

Returns a list of generally available decoders.

Function mpg123-supported-decodersdecoders

Returns a list of decoders supported by the CPU.

Function mpg123-getformat handlerate channels encoding

Given a handle with an opened file, returns three values indicating the current format: rate (in Hertz), channels (1 or 2), and encoding (an integer, one of MPG123_ENC_... constants).

Helpers

Function get-bitstream-properties handleplist

Retrieves information about an MPEG bitstream (version, mode, bitrate, etc.) from an mpg123 handle. Decodes the contents of the mpg123_frameinfo structure (as retrieved by mpg123_info) as a property list containing the following keys:

Function get-tags-from-handle handleplist bitstream-properties

Examines ID3v1 and ID3v2 tags from an mpg123 handle, returning a plist containing a subset of the following keys:

For all keys except :track, the property value will be a string. If :track is present, it will be an integer.

When present, ID3v2 tags override data from ID3v1 tags. Some heuristics are applied to correct or reject obviously incorrect property values. For instance, various forms of year are canonicalized to a four-digit form if a reasonable guess can be made, and track numbers of zero are removed from the plist.

The bitstream properties, obtained from get-bitstream-properties, are returned as a second value.

Issue: Currently only extracts track numbers from the ID3v2 TRCK field, but should definitely fall back to the ID3v1.1 track number field if this is not present.

Function get-tags-from-file filename &key verbose (character-encoding :iso-8859-1)plist bitstream-properties

Open an MP3 file, examine its tags using get-tags-from-handle, and return the resulting plist. Unless verbose is true, messages from the library are suppressed.

By default, the filename will be encoded as iso-8859-1 (latin-1) before being passed to C; to avoid encoding problems on non-ASCII filenames, it is recommended you obtain the filename string with this in mind. You can change this behavior using the :character-encoding keyword, which can take any valid Babel encoding specifier (another reasonable choice is :utf-8b, but again, this choice should be congruent with how the string was obtained from the filesystem).

The bitstream properties, obtained from get-bitstream-properties, are returned as a second value.

Function decode-mp3-file filename &key verbose (character-encoding :iso-8859-1)vector rate channels encoding

Opens and decodes an MP3 file, returning a vector of samples, a (simple-array (signed-byte 16) 1), interleaved, with one array element per channel. The :character-encoding keyword chooses how the filename is encoded as a C string. Additionally, the rate, channels, and encoding () of the file are returned (see mpg123-getformat). Unless verbose is true, messages from the library are suppressed.

System: mixalot-mp3

The mixalot-mp3 system builds on mpg123-ffi to implement a seekable streamer class for use with the mixer, allowing incremental decoding and playback of MP3 files from disk. The following functions reside in the mixalot-mp3 package:

Class mp3-streamer

A seekable Mixalot streamer for playing MP3 files.

Function make-mp3-streamer filename &rest args &key (output-rate 44100) (class 'mp3-streamer) (prescan t) &allow-other-keys
mp3-streamer

Create and return an mp3-streamer which opens and incrementally decodes from the given filename. If the file cannot be opened, an error of type mpg123-handle-error is signalled. The output is resampled according to output-rate, which for correct playback should match the rate of the mixer the stream will be used with.

The class argument allows specifying an alternate class to make-instance. This class must be a subclass of mp3-streamer. Remaining args and output-rate are passed to make-instance as initargs.

In order to ensure that the file length is correct and enable accurate seeking, it is recommended to scan through the MP3 file before beginning playback. This may result in a noticeable delay while the file is scanned. The prescan argument controls whether this prescanning occurs. By default, prescanning is enabled.

Note: While libmpg123 can perform automatic resampling, the resulting audio quality is very poor, and in practice you shouldn't do it. ALSA's dmix does a much better job, although resampling through dmix has its own issues (namely, unpredictable and often unreasonably high CPU usage when more than one program uses the device simultaneously).

Function mp3-streamer-release-resources mp3-streamer

Release foreign resources held by the mp3-streamer. In normal operation, streamer-cleanup calls this automatically when the streamer is removed from its mixer. It's only necessary to call this yourself if the mp3-streamer has never been added to a mixer, but you want to dispose of it.

Function mp3-sample-rate mp3-streamerrate

Returns the native sampling rate of the file behind an mp3-streamer. This may be different from the output sampling rate.

Example of creating and playing an MP3 stream:

CL-USER> (mixalot:mixer-add-streamer *mixer*
           (mixalot-mp3:make-mp3-streamer "/home/hefner/test.mp3"))
#<MIXALOT-MP3:MP3-STREAMER {10029B9A91}>
8463564800

System: vorbisfile-ffi

The vorbisfile-ffi system presents a thin wrapper around libvorbisfile and should, given the source code, be self explanatory in combination with the original libvorbisfile documentation. It provides bindings to most of the API that is required to decode Ogg Vorbis audio.

There are also convenience functions to extract so-called vorbis comments, but only those that are useful and equivalent to ID3 tags in MP3 files.

The functions are exported from the vorbisfile package and comprise a thin wrapper. Mostly, the exported functions call the C functions while immediately doing some error checking. For all other functions, see vorbisfile.lisp.

Condition vorbis-error

An error from the vorbisfile library.

Function vorbis-strerror codestring

Translate an integer code from the vorbisfile library into a somewhat more descriptive string.

Function check-vorbis-error circumstance code

Examines the code returned by a call to libvorbisfile. If it indicates an error, it translates the error to a descriptive string and signals an error of type vorbis-error. Circumstance is a string, included in the error report, intended to indicate which library call produced the error or what the programmer was attempting to do.

Function raise-vorbis-error circumstance message

Signal an error of type vorbis-error. Circumstance is a string, included in the error report, intended to indicate which library call produced the error or what the programmer was attempting to do. Message is a string, included in the error report, that describes why the call failed.

Function vorbis-newhandle

Allocates foreign resources for a new handle and returns it. This foreign resource must be released by the programmer using vorbis-delete.

Function vorbis-delete handle

Release the foreign resource of handle that was allocated earlier using vorbis-new.

Function vorbis-open filename handle &key (character-encoding :iso-8859-1)

Open the file with the given filename and attach it to the given handle, which must have already been created using vorbis-new. This function calls the C function ov_fopen. If an error occurs, an error of type vorbis-error is raised.

By default, the filename will be encoded as iso-8859-1 (latin-1) before being passed to C; to avoid encoding problems on non-ASCII filenames, it is recommended you obtain the filename string with this in mind. You can change this behavior using the :character-encoding keyword, which can take any valid Babel encoding specifier (another reasonable choice is :utf-8b, but again, this choice should be congruent with how the string was obtained from the filesystem).

Function vorbis-close handle

Open the file with the given filename and attach it to the given handle, using the C function ov_fopen. If an error occurs, an error of type vorbis-error is raised.

Function vorbis-seek handle position &key (accurate t)

In the Vorbis stream given by handle, seek to the given position in samples. This uses the C function ov_pcm_seek_lap when accurate is t, and ov_pcm_seek_page_lap when it is nil, which is faster. If an error occurs, an error of type vorbis-error is raised.

Function vorbis-channels handle &key (link -1)number-of-channels

Returns an integer representing the number-of-channels in the Vorbis stream given by handle and logical stream link (-1 for the current). If an error occurs, an error of type vorbis-error is raised.

Function vorbis-rate handle &key (link -1)sampling-rate

Returns an integer representing the sampling-rate in Hz of the Vorbis stream given by handle and logical stream link (-1 for the current). If an error occurs, an error of type vorbis-error is raised.

Function vorbis-length handle &key (link -1)total-number-of-samples

Returns an integer representing the total-number-of-samples in the Vorbis stream given by handle and logical stream link (-1 for the current), by calling the C function ov_pcm_total. If an error occurs, an error of type vorbis-error is raised.

Function vorbis-position handlesample-position

Returns an integer representing the current sample-position in the Vorbis stream given by handle, by calling the C function ov_pcm_tell. If an error occurs, an error of type vorbis-error is raised.

Helpers

Function get-vorbis-tags-from-handle handle &key (link -1)plist

Examines the comments from a vorbisfile handle and the logical stream link (-1 for the current), and returns a plist containing a subset of the following keys:

For all keys except :track, the property value will be a string. If :track is present, it will be an integer.

For the key :year, the property value will in fact contain the value of the DATE field in the vorbis comments. Similarly, the property value for the :track key will contain the value of the TRACKNUMBER field in the Vorbis comments. In this way, Vorbis comments are massaged to resemble ID3 tag values as retrieved using get-tags-from-handle. Some heuristics are applied to correct or reject obviously incorrect property values. For instance, track numbers of zero are removed from the plist. Additionally, for Vorbis comments it is recommended to use multiple values of the ARTIST field if necessary. Such occurences are collected under one :artist key, with the values comma-separated, and in the order in which the fields were encountered.

Issue: The Vorbis comment field DATE can contain a full date instead of just a year, in any possible form. In fact, all fields are basically free-form. This makes it a bit more difficult to cannonicalize the contents of the fields. In the specific case of DATE, the field values are copied as the property values of the :year key. As such, the value may contain more information than the key name suggests.

Function get-vorbis-tags-from-file filename &key (link -1) (character-encoding :iso-8859-1) ⇒ plist

Open an Ogg Vorbis file, examine the tags for the given logical stream link (-1 for the current), using get-vorbis-tags-from-handle, and return the resulting plist.

By default, the filename will be encoded as iso-8859-1 (latin-1) before being passed to C; to avoid encoding problems on non-ASCII filenames, it is recommended you obtain the filename string with this in mind. You can change this behavior using the :character-encoding keyword, which can take any valid Babel encoding specifier (another reasonable choice is :utf-8b, but again, this choice should be congruent with how the string was obtained from the filesystem).

System: mixalot-vorbis

The mixalot-vorbis system builds on vorbisfile-ffi to implement a seekable streamer class for use with the mixer, allowing incremental decoding and playback of Ogg Vorbis files from disk. Due to the way that mixalot works at this moment, only mono and stereo Ogg Vorbis files with a sampling rate of 44.1 kHz can be played back. The following functions reside in the mixalot-vorbis package:

Class vorbis-streamer

A seekable Mixalot streamer for playing Ogg Vorbis files.

Function make-vorbis-streamer filename &rest args &key (output-rate 44100) (class 'vorbis-streamer) &allow-other-keys
vorbis-streamer

Create and return an vorbis-streamer which opens and incrementally decodes from the given filename. If the file cannot be opened, an error of type vorbis-error is signalled.

The class argument allows specifying an alternate class to make-instance. This class must be a subclass of vorbis-streamer. Remaining args and output-rate are passed to make-instance as initargs.

Note: If output-rate does not match the sample rate of the Ogg Vorbis stream, a warning is raised, but the vorbis-streamer will be created anyway. Currently, there is no way to resample the stream on the fly.

Function vorbis-streamer-release-resources vorbis-streamer

Release foreign resources held by the vorbis-streamer. In normal operation, streamer-cleanup calls this automatically when the streamer is removed from its mixer. It's only necessary to call this yourself if the vorbis-streamer has never been added to a mixer, but you want to dispose of it.

Function vorbis-sample-rate vorbis-streamerrate

Returns the native sampling rate of the file behind a vorbis-streamer. This may be different from the output sampling rate.

Example of creating and playing an Ogg Vorbis stream:

CL-USER> (mixalot:mixer-add-streamer *mixer*
           (mixalot-vorbis:make-vorbis-streamer "/home/soemraws/test.ogg"))
#<VORBIS-STREAMER {1002D80621}>
290816

System: flac

The flac system implements an FFI wrapper and utility functions around libFLAC. These are used internally by the mixalot-flac package, but several exports may be of general interest.

Previous versions of Mixalot advertised a number of additional symbols from this package. Internal changes necessitated modifying these interfaces. If you have need of the previously offered functionality, contact the authors.

Condition flac-error

An error from the FLAC library.

Function get-flac-tags-from-file filename &key (character-encoding :iso-8859-1)plist

Examines the Vorbis comments from a FLAC file and returns a plist containing a subset of the following keys:

For all keys except :track, the property value will be a string. If :track is present, it will be an integer.

For the key :year, the property value will in fact contain the value of the DATE field in the vorbis comments. Similarly, the property value for the :track key will contain the value of the TRACKNUMBER field in the Vorbis comments. In this way, Vorbis comments are massaged to resemble ID3 tag values as retrieved using get-tags-from-handle. Some heuristics are applied to correct or reject obviously incorrect property values. For instance, track numbers of zero are removed from the plist. Additionally, for Vorbis comments it is recommended to use multiple values of the ARTIST field if necessary. Such occurences are collected under one :artist key, with the values comma-separated, and in the order in which the fields were encountered.

Issue: The Vorbis comment field DATE can contain a full date instead of just a year, in any possible form. In fact, all fields are basically free-form. This makes it a bit more difficult to cannonicalize the contents of the fields. In the specific case of DATE, the field values are copied as the property values of the :year key. As such, the value may contain more information than the key name suggests.

By default, the filename will be encoded as iso-8859-1 (latin-1) before being passed to C; to avoid encoding problems on non-ASCII filenames, it is recommended you obtain the filename string with this in mind. You can change this behavior using the :character-encoding keyword, which can take any valid Babel encoding specifier (another reasonable choice is :utf-8b, but again, this choice should be congruent with how the string was obtained from the filesystem).

System: mixalot-flac

The mixalot-flac system builds implements a seekable streamer class for use with the mixer, allowing incremental decoding and playback of FLAC files from disk. Presently, only 16-bit mono or stereo FLAC files are supported. No resampling is performed, so the file sampling rate must match that of the mixer for correct playback. The mixalot-flac package provides the following:

Class flac-streamer

A seekable Mixalot streamer for playing FLAC files.

Function make-flac-streamer filename &rest args &key (output-rate 44100) (class 'flac-streamer) &allow-other-keys
flac-streamer

Create and return a flac-streamer which opens and decodes audio from the given filename. If the file cannot be opened, an error of type flac-error is signalled.

The class argument allows specifying an alternate class to make-instance. This class must be a subclass of flac-streamer. Remaining args and output-rate are passed to make-instance as initargs.

Note: If output-rate does not match the sample rate of the FLAC stream, a warning is raised, but the flac-streamer will be created anyway. Currently, there is no way to resample the stream on the fly.

Function flac-sample-rate flac-streamerrate

Returns the native sampling rate of the file behind a flac-streamer. This may be different from the output sampling rate.

Example of creating and playing an FLAC stream:

CL-USER> (mixalot:mixer-add-streamer *mixer*
           (mixalot-flac:make-flac-streamer "/home/soemraws/test.flac"))
#<FLAC-STREAMER {1004340DB1}>
172032

Future Work

Various ideas for improvement, which might appear on an as-needed basis:

License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Report bugs to Andy Hefner <ahefner at gmail dot com>