From 08ca038b6868010a18b2a265a0a8ebe5304eb7f6 Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Tue, 28 Apr 2026 18:15:02 +0200 Subject: [PATCH] - --- info.rkt | 2 +- main.rkt | 2 + scrbl/audio-decoder.scrbl | 2 +- scrbl/audio-sniffer.rkt | 126 ------------------------------------- scrbl/ffmpeg-decoder.scrbl | 8 ++- scrbl/ffmpeg-ffi.scrbl | 8 ++- scrbl/flac-decoder.scrbl | 2 +- scrbl/libao.scrbl | 12 ++-- scrbl/mp3-decoder.scrbl | 3 +- 9 files changed, 28 insertions(+), 137 deletions(-) delete mode 100644 scrbl/audio-sniffer.rkt diff --git a/info.rkt b/info.rkt index 417e4f6..4579ae9 100644 --- a/info.rkt +++ b/info.rkt @@ -1,7 +1,7 @@ #lang info (define pkg-authors '(hnmdijkema)) -(define version "0.1.4") +(define version "0.1.5") (define license 'GPL-2.0-or-later) ; The liboa library has this license (define collection "racket-sound") (define pkg-desc "racket-sound - Integration of popular music/sound related libraries in racket") diff --git a/main.rkt b/main.rkt index 40c27f4..24b3f27 100644 --- a/main.rkt +++ b/main.rkt @@ -3,10 +3,12 @@ (require "libao.rkt" "audio-decoder.rkt" "taglib.rkt" + "audio-sniffer.rkt" ) (provide (all-from-out "libao.rkt") (all-from-out "audio-decoder.rkt") (all-from-out "taglib.rkt") + (all-from-out "audio-sniffer.rkt") ) diff --git a/scrbl/audio-decoder.scrbl b/scrbl/audio-decoder.scrbl index 5b046ce..f84e528 100644 --- a/scrbl/audio-decoder.scrbl +++ b/scrbl/audio-decoder.scrbl @@ -8,7 +8,7 @@ @title{audio-decoder} @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] -@defmodule["racket-sound/audio-decoder"] +@defmodule[(file "../audio-decoder.rkt")] This module provides a small abstraction layer over concrete audio decoders. A backend is selected from the filename extension and is then diff --git a/scrbl/audio-sniffer.rkt b/scrbl/audio-sniffer.rkt deleted file mode 100644 index 9451239..0000000 --- a/scrbl/audio-sniffer.rkt +++ /dev/null @@ -1,126 +0,0 @@ -#lang scribble/manual - -@title{Audio Sniffer} -@author{@author+email["Hans Dijkema" "hans@dijkewijk.nl"]} - -@defmodule[audio-sniffer] - -This module provides utilities to determine the format of an audio file. -It combines lightweight content sniffing with extension-based fallback. - -The sniffer is designed to be robust against incomplete data and -progressively increases the amount of data inspected. - -@defproc[(audio-sniff-extension [file path-string?]) (or/c string? #f)]{ -Returns the file extension (without dot) or @racket[#f] if none could be determined. - -The result is purely based on the filename and does not inspect file -contents. -} - -@defproc[(audio-sniff-format [file path-string?]) symbol?]{ -Determines the audio format by inspecting the file contents. - -The function reads portions of the file (both head and tail) and tries -to match known signatures. - -Returns a symbol such as: - -@itemlist[ - #:style 'compact - @item{@racket['mp3]} - @item{@racket['flac]} - @item{@racket['ogg]} - @item{@racket['wav]} - @item{@racket['aiff]} - @item{@racket['mp4]} - @item{@racket['unknown]} -] - -If the file cannot be read, a filesystem-related symbol is returned -instead of raising an exception. -} - -@defproc[(audio-sniff-format/extension [file path-string?]) symbol?]{ -Determines the audio format using sniffing, with a fallback to the file -extension. - -If @racket[audio-sniff-format] returns @racket['unknown], the extension -is used as a secondary source. - -This function provides the most practical format detection. -} - -@defproc[(audio-format-known? [fmt symbol?]) boolean?]{ -Returns @racket[#t] if the given format symbol is recognized by the -sniffer. -} - -@defproc[(audio-format-matches? [file path-string?] - [formats (listof symbol?)]) - boolean?]{ -Returns @racket[#t] if the detected format of @racket[file] is a member -of @racket[formats]. - -Detection is performed using @racket[audio-sniff-format/extension]. -} - -@section{Detection Strategy} - -The sniffer follows a layered strategy: - -@itemlist[ - #:style 'compact - @item{Read a small head section of the file (starting at 4096 bytes)} - @item{Inspect tail sections for formats that store metadata at the end (e.g. mp4)} - @item{Increase head size exponentially if needed} - @item{Fallback to file extension if no signature is found} -] - -This approach balances performance with robustness, especially for -container formats where identifying markers may not be located at the -start of the file. - -@section{Error Handling} - -Instead of raising exceptions, the sniffer returns symbolic error -conditions when the file cannot be inspected: - -@itemlist[ - #:style 'compact - @item{@racket['file-not-found]} - @item{@racket['file-not-readable]} - @item{@racket['not-a-file]} -] - -These values can be handled by higher-level code without requiring -exception handling. - -@section{Supported Formats} - -The sniffer recognizes a broad range of formats, including: - -@itemlist[ - #:style 'compact - @item{mp3} - @item{flac} - @item{ogg / opus} - @item{wav} - @item{aiff} - @item{mp4 / m4a} - @item{aac} - @item{alac} - @item{ac3} - @item{ape} - @item{wavpack} - @item{wma} - @item{matroska} -] - -Extension fallback supports the same set of formats. - -@section{Notes} - -Sniffing is heuristic in nature. While probably reliable, it is possible -that malformed or unusual files are reported as @racket['unknown]. - diff --git a/scrbl/ffmpeg-decoder.scrbl b/scrbl/ffmpeg-decoder.scrbl index a93336e..a84c7d3 100644 --- a/scrbl/ffmpeg-decoder.scrbl +++ b/scrbl/ffmpeg-decoder.scrbl @@ -1,9 +1,15 @@ #lang scribble/manual +@(require racket/base + (for-label racket/base + racket/contract + racket/path + "../ffmpeg-decoder.rkt")) + @title{FFmpeg Decoder} @author{@author+email["Hans Dijkema" "hans@dijkewijk.nl"]} -@defmodule[ffmpeg-decoder] +@defmodule[(file "../ffmpeg-decoder.rkt")] This module provides an audio decoder based on the FFmpeg audio shim. It uses the lower-level @racketmodname[racket-sound/ffmpeg-ffi] module and presents a diff --git a/scrbl/ffmpeg-ffi.scrbl b/scrbl/ffmpeg-ffi.scrbl index 173e01c..fca3886 100644 --- a/scrbl/ffmpeg-ffi.scrbl +++ b/scrbl/ffmpeg-ffi.scrbl @@ -1,9 +1,15 @@ #lang scribble/manual +@(require racket/base + (for-label racket/base + racket/contract + racket/path + "../ffmpeg-ffi.rkt")) + @title{FFmpeg FFI} @author{@author+email["Hans Dijkema" "hans@dijkewijk.nl"]} -@defmodule[ffmpeg-ffi] +@defmodule[(file "../ffmpeg-ffi.rkt")] This module provides the low-level Racket FFI binding for the native FFmpeg audio shim. The native shim exposes an opaque FFmpeg instance and diff --git a/scrbl/flac-decoder.scrbl b/scrbl/flac-decoder.scrbl index e4817d8..ee69eaf 100644 --- a/scrbl/flac-decoder.scrbl +++ b/scrbl/flac-decoder.scrbl @@ -9,7 +9,7 @@ @title{flac-decoder} @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] -@defmodule["racket-sound/flac-decoder"] +@defmodule[(file "../flac-decoder.rkt")] This module provides a small decoder interface on top of the FLAC FFI layer. It opens a decoder for a file, reads stream metadata, diff --git a/scrbl/libao.scrbl b/scrbl/libao.scrbl index ee6b888..f3293cb 100644 --- a/scrbl/libao.scrbl +++ b/scrbl/libao.scrbl @@ -2,12 +2,14 @@ @(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-sound/libao"] +@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, @@ -15,8 +17,8 @@ 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] or @racket[ao-open-file]. An @racket[ao-handle] +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. @@ -25,7 +27,7 @@ by the selected libao output device. @defproc[(ao-handle? [v any/c]) boolean?]{ -Returns @racket[#t] if @racket[v] is an @racket[ao-handle] value, and +Returns @racket[#t] if @racket[v] is an @tt{ao-handle} value, and @racket[#f] otherwise. } @@ -120,7 +122,7 @@ 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 -@racket[ao-handle], but that handle is marked closed and is not valid +@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] diff --git a/scrbl/mp3-decoder.scrbl b/scrbl/mp3-decoder.scrbl index bd42541..9d9af49 100644 --- a/scrbl/mp3-decoder.scrbl +++ b/scrbl/mp3-decoder.scrbl @@ -3,12 +3,13 @@ @(require racket/base (for-label racket/base racket/path + racket/contract "../mp3-decoder.rkt")) @title{mp3-decoder} @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] -@defmodule[racket-sound/mp3-decoder] +@defmodule[(file "../mp3-decoder.rkt")] This module provides an MP3 decoder backend. It opens an MP3 file, reports stream information through a callback, streams decoded PCM