Files
racket-audio/scrbl/libao.scrbl
T
2026-05-04 13:27:02 +02:00

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[racket-audio/libao]
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.