From e941876fa53bfe3cd547933db4389250b73c4e0b Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Tue, 28 Apr 2026 18:00:13 +0200 Subject: [PATCH] libao documentation update --- scrbl/libao.scrbl | 109 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 21 deletions(-) diff --git a/scrbl/libao.scrbl b/scrbl/libao.scrbl index 0879fc6..ee6b888 100644 --- a/scrbl/libao.scrbl +++ b/scrbl/libao.scrbl @@ -10,13 +10,16 @@ @defmodule["racket-sound/libao"] This module provides a small high-level interface to an asynchronous -audio output backend. It opens a live output device, queues audio -buffers for playback, reports playback position, supports pause and -buffer clearing, and exposes a small set of validation predicates. +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 @racket[ao-handle], created by -@racket[ao-open-live]. An @racket[ao-handle] stores the playback -configuration together with a native asynchronous player handle. +@racket[ao-open-live] or @racket[ao-open-file]. An @racket[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} @@ -31,8 +34,18 @@ Returns @racket[#t] if @racket[v] is an @racket[ao-handle] value, and 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 -@racket[ao-open-live] failed to create the native player. +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} @@ -50,11 +63,12 @@ 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], and} - @item{@racket[384000].}] +@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?]{ @@ -72,11 +86,14 @@ Returns @racket[#t] if @racket[format] is one of @defproc[(ao-supported-music-format? [format any/c]) boolean?]{ -Returns @racket[#t] if @racket[format] is one of @racket['flac], -@racket['mp3], or @racket['ao], and @racket[#f] otherwise. +Returns @racket[#t] if @racket[format] is one of @racket['ao] or +@racket['flac], and @racket[#f] otherwise. -This value is used by @racket[ao-play] to describe the format of the -buffer being queued. +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} @@ -89,8 +106,17 @@ buffer being queued. Creates an audio output handle for live playback. -The handle stores the given sample size, sample rate, channel count, -and byte format. It then tries to create a native asynchronous player. +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 @@ -101,6 +127,25 @@ 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 @@ -128,11 +173,29 @@ 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 buffer format. +@racket[buf-type] argument specifies the in-memory PCM layout. The buffer description passed to the native layer is completed with the -sample size, sample rate, channel count, and byte format stored in -@racket[handle]. +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. } @@ -200,6 +263,10 @@ 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