280 lines
9.2 KiB
Racket
280 lines
9.2 KiB
Racket
#lang scribble/manual
|
|
|
|
@(require racket/base
|
|
(for-label racket/base
|
|
racket/contract
|
|
racket/path
|
|
"../libao.rkt"))
|
|
|
|
@title{libao}
|
|
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
|
|
|
@defmodule[(file "../libao.rkt")]
|
|
|
|
This module provides a small high-level interface to an asynchronous
|
|
audio output backend. It opens a live output device or a file output,
|
|
queues audio buffers for playback, reports playback position, supports
|
|
pause and buffer clearing, and exposes a small set of validation
|
|
predicates.
|
|
|
|
The central value is an @tt{ao-handle}, created by
|
|
@racket[ao-open-live] or @racket[ao-open-file]. An @tt{ao-handle}
|
|
stores the requested playback configuration together with a native
|
|
asynchronous player handle. It also records the real bit depth accepted
|
|
by the selected libao output device.
|
|
|
|
@section{Audio handles}
|
|
|
|
@defproc[(ao-handle? [v any/c]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[v] is an @tt{ao-handle} value, and
|
|
@racket[#f] otherwise.
|
|
}
|
|
|
|
@defproc[(ao-valid? [handle ao-handle?]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[handle] still has a native asynchronous
|
|
player, and @racket[#f] otherwise.
|
|
|
|
A handle becomes invalid after @racket[ao-close], or when opening the
|
|
native player failed.
|
|
}
|
|
|
|
@defproc[(ao-device-bits [handle ao-handle?]) integer?]{
|
|
|
|
Returns the real bit depth of the opened output device.
|
|
|
|
This can differ from the bit depth requested with @racket[ao-open-live]
|
|
or @racket[ao-open-file]. For example, when 32-bit output is requested
|
|
but the libao driver only accepts 24-bit output, this function returns
|
|
@racket[24].
|
|
}
|
|
|
|
@section{Validation predicates}
|
|
|
|
@defproc[(ao-valid-bits? [bits any/c]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[bits] is one of @racket[8],
|
|
@racket[16], @racket[24], or @racket[32], and @racket[#f] otherwise.
|
|
}
|
|
|
|
@defproc[(ao-valid-rate? [rate any/c]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[rate] is one of the sample rates
|
|
accepted by this module, and @racket[#f] otherwise.
|
|
|
|
The accepted rates are:
|
|
|
|
@itemlist[
|
|
#:style 'compact
|
|
@item{@racket[8000], @racket[11025], @racket[16000], @racket[22050]}
|
|
@item{@racket[44100], @racket[48000], @racket[88200], @racket[96000]}
|
|
@item{@racket[176400], @racket[192000], @racket[352800], @racket[384000]}
|
|
]
|
|
}
|
|
|
|
@defproc[(ao-valid-channels? [channels any/c]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[channels] is an integer greater than or
|
|
equal to @racket[1], and @racket[#f] otherwise.
|
|
}
|
|
|
|
@defproc[(ao-valid-format? [format any/c]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[format] is one of
|
|
@racket['little-endian], @racket['big-endian], or
|
|
@racket['native-endian], and @racket[#f] otherwise.
|
|
}
|
|
|
|
@defproc[(ao-supported-music-format? [format any/c]) boolean?]{
|
|
|
|
Returns @racket[#t] if @racket[format] is one of @racket['ao] or
|
|
@racket['flac], and @racket[#f] otherwise.
|
|
|
|
The symbol does not describe an encoded audio format. It describes the
|
|
in-memory layout of the PCM buffer passed to @racket[ao-play].
|
|
@racket['ao] means interleaved PCM samples. @racket['flac] means
|
|
channel-oriented PCM samples, as produced by the FLAC decoder, which
|
|
must be converted to interleaved PCM before playback.
|
|
}
|
|
|
|
@section{Opening and closing}
|
|
|
|
@defproc[(ao-open-live [bits ao-valid-bits?]
|
|
[rate ao-valid-rate?]
|
|
[channels ao-valid-channels?]
|
|
[byte-format ao-valid-format?])
|
|
ao-handle?]{
|
|
|
|
Creates an audio output handle for live playback.
|
|
|
|
This is equivalent to calling @racket[ao-open-file] with
|
|
@racket[#f] as the filename.
|
|
|
|
The handle stores the requested sample size, sample rate, channel count,
|
|
and byte format. The native backend first tries to open the device with
|
|
the requested bit depth. If that fails, it may fall back to a lower bit
|
|
depth accepted by the selected libao driver.
|
|
|
|
The requested bit depth describes the buffers supplied by the Racket
|
|
side. The real device bit depth describes the format accepted by libao
|
|
and can be inspected with @racket[ao-device-bits].
|
|
|
|
If the native player is created successfully, the returned handle is
|
|
valid. If player creation fails, the function still returns an
|
|
@tt{ao-handle}, but that handle is marked closed and is not valid
|
|
for playback.
|
|
|
|
A finalizer is registered for the handle and calls @racket[ao-close]
|
|
when the handle is reclaimed.
|
|
}
|
|
|
|
@defproc[(ao-open-file [bits ao-valid-bits?]
|
|
[rate ao-valid-rate?]
|
|
[channels ao-valid-channels?]
|
|
[byte-format ao-valid-format?]
|
|
[filename (or/c path? string? #f)])
|
|
ao-handle?]{
|
|
|
|
Creates an audio output handle.
|
|
|
|
If @racket[filename] is @racket[#f], the default live libao output
|
|
device is opened. Otherwise the native backend opens a file output
|
|
target using the given filename.
|
|
|
|
The requested bit depth is stored in the handle and describes the input
|
|
buffers that will be queued with @racket[ao-play]. The native backend
|
|
also records the real bit depth accepted by the output device or file
|
|
backend. Use @racket[ao-device-bits] to inspect that value.
|
|
}
|
|
|
|
@defproc[(ao-close [handle ao-handle?]) void?]{
|
|
|
|
Stops playback for @racket[handle] and releases the native player
|
|
reference stored in the handle.
|
|
|
|
If the handle already has no native player, this procedure has no
|
|
effect.
|
|
}
|
|
|
|
@section{Playback}
|
|
|
|
@defproc[(ao-play [handle ao-handle?]
|
|
[music-id integer?]
|
|
[at-time-in-s number?]
|
|
[music-duration-s number?]
|
|
[buffer any/c]
|
|
[buf-len integer?]
|
|
[buf-type ao-supported-music-format?])
|
|
void?]{
|
|
|
|
Queues audio data for asynchronous playback.
|
|
|
|
The @racket[music-id] argument identifies the music stream associated
|
|
with the buffer. The arguments @racket[at-time-in-s] and
|
|
@racket[music-duration-s] describe the position and duration, in
|
|
seconds, associated with the buffer. The arguments @racket[buffer] and
|
|
@racket[buf-len] provide the audio data and its length. The
|
|
@racket[buf-type] argument specifies the in-memory PCM layout.
|
|
|
|
The buffer description passed to the native layer is completed with the
|
|
requested sample size, sample rate, channel count, and byte format
|
|
stored in @racket[handle].
|
|
|
|
Two buffer layouts are supported:
|
|
|
|
@itemlist[
|
|
#:style 'compact
|
|
@item{@racket['ao]: interleaved PCM samples, for example @tt{L0 R0 L1 R1}.}
|
|
@item{@racket['flac]: channel-oriented PCM samples, for example one channel buffer for left samples and one channel buffer for right samples.}
|
|
]
|
|
|
|
The native backend converts @racket['flac] buffers to interleaved PCM
|
|
before playback. It also converts between the requested bit depth and the
|
|
real device bit depth when needed. This makes it possible to keep decoder
|
|
output at 32-bit signed integer PCM while still playing on devices that
|
|
only accept 24-bit or 16-bit integer samples.
|
|
|
|
The queued buffer is copied by the native backend, so the caller does
|
|
not need to keep the original buffer alive after @racket[ao-play]
|
|
returns.
|
|
|
|
If @racket[handle] is not valid, this procedure raises an exception.
|
|
}
|
|
|
|
@defproc[(ao-pause [handle ao-handle?]
|
|
[pause boolean?])
|
|
void?]{
|
|
|
|
Pauses or resumes asynchronous playback for @racket[handle].
|
|
|
|
A true value pauses playback. @racket[#f] resumes playback.
|
|
}
|
|
|
|
@defproc[(ao-clear-async [handle ao-handle?]) void?]{
|
|
|
|
Clears buffered asynchronous playback data for @racket[handle].
|
|
}
|
|
|
|
@section{Playback state}
|
|
|
|
@defproc[(ao-at-second [handle ao-handle?]) number?]{
|
|
|
|
Returns the current playback position, in seconds, as reported by the
|
|
native asynchronous player.
|
|
}
|
|
|
|
@defproc[(ao-at-music-id [handle ao-handle?]) integer?]{
|
|
|
|
Returns the music identifier currently reported by the native
|
|
asynchronous player.
|
|
}
|
|
|
|
@defproc[(ao-music-duration [handle ao-handle?]) number?]{
|
|
|
|
Returns the duration of the current music stream, in seconds, as
|
|
reported by the native asynchronous player.
|
|
}
|
|
|
|
@defproc[(ao-bufsize-async [handle ao-handle?]) integer?]{
|
|
|
|
Returns the current buffered size in bytes for the asynchronous player.
|
|
}
|
|
|
|
@section{Volume control}
|
|
|
|
@defproc[(ao-set-volume! [handle ao-handle?]
|
|
[percentage number?])
|
|
void?]{
|
|
|
|
Sets the playback volume for @racket[handle].
|
|
|
|
If @racket[percentage] is an exact integer, it is converted to an
|
|
inexact number before it is passed to the native layer.
|
|
}
|
|
|
|
@defproc[(ao-volume [handle ao-handle?]) number?]{
|
|
|
|
Returns the current playback volume as reported by the native
|
|
asynchronous player.
|
|
}
|
|
|
|
@section{Notes}
|
|
|
|
This module is a higher-level wrapper around the asynchronous FFI layer.
|
|
It stores the playback configuration in the handle, and reuses that
|
|
configuration for each call to @racket[ao-play].
|
|
|
|
The requested bit depth and the real device bit depth are deliberately
|
|
kept separate. The requested value describes the buffers supplied by the
|
|
Racket side. The real value describes the format accepted by libao.
|
|
|
|
The module does not expose the handle fields directly. The public API
|
|
is intentionally small: create a handle, queue buffers, inspect
|
|
position and buffer state, pause or clear playback, adjust volume, and
|
|
close the handle.
|
|
|
|
A typical usage pattern is to open one live handle for a given stream
|
|
format, queue decoded buffers with @racket[ao-play], and query the
|
|
playback position with @racket[ao-at-second] while playback proceeds
|
|
asynchronously. |