This commit is contained in:
2026-04-28 18:15:02 +02:00
parent e941876fa5
commit 08ca038b68
9 changed files with 28 additions and 137 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
#lang info #lang info
(define pkg-authors '(hnmdijkema)) (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 license 'GPL-2.0-or-later) ; The liboa library has this license
(define collection "racket-sound") (define collection "racket-sound")
(define pkg-desc "racket-sound - Integration of popular music/sound related libraries in racket") (define pkg-desc "racket-sound - Integration of popular music/sound related libraries in racket")
+2
View File
@@ -3,10 +3,12 @@
(require "libao.rkt" (require "libao.rkt"
"audio-decoder.rkt" "audio-decoder.rkt"
"taglib.rkt" "taglib.rkt"
"audio-sniffer.rkt"
) )
(provide (all-from-out "libao.rkt") (provide (all-from-out "libao.rkt")
(all-from-out "audio-decoder.rkt") (all-from-out "audio-decoder.rkt")
(all-from-out "taglib.rkt") (all-from-out "taglib.rkt")
(all-from-out "audio-sniffer.rkt")
) )
+1 -1
View File
@@ -8,7 +8,7 @@
@title{audio-decoder} @title{audio-decoder}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] @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 This module provides a small abstraction layer over concrete audio
decoders. A backend is selected from the filename extension and is then decoders. A backend is selected from the filename extension and is then
-126
View File
@@ -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].
+7 -1
View File
@@ -1,9 +1,15 @@
#lang scribble/manual #lang scribble/manual
@(require racket/base
(for-label racket/base
racket/contract
racket/path
"../ffmpeg-decoder.rkt"))
@title{FFmpeg Decoder} @title{FFmpeg Decoder}
@author{@author+email["Hans Dijkema" "hans@dijkewijk.nl"]} @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 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 uses the lower-level @racketmodname[racket-sound/ffmpeg-ffi] module and presents a
+7 -1
View File
@@ -1,9 +1,15 @@
#lang scribble/manual #lang scribble/manual
@(require racket/base
(for-label racket/base
racket/contract
racket/path
"../ffmpeg-ffi.rkt"))
@title{FFmpeg FFI} @title{FFmpeg FFI}
@author{@author+email["Hans Dijkema" "hans@dijkewijk.nl"]} @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 This module provides the low-level Racket FFI binding for the native
FFmpeg audio shim. The native shim exposes an opaque FFmpeg instance and FFmpeg audio shim. The native shim exposes an opaque FFmpeg instance and
+1 -1
View File
@@ -9,7 +9,7 @@
@title{flac-decoder} @title{flac-decoder}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] @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 This module provides a small decoder interface on top of the FLAC
FFI layer. It opens a decoder for a file, reads stream metadata, FFI layer. It opens a decoder for a file, reads stream metadata,
+7 -5
View File
@@ -2,12 +2,14 @@
@(require racket/base @(require racket/base
(for-label racket/base (for-label racket/base
racket/contract
racket/path
"../libao.rkt")) "../libao.rkt"))
@title{libao} @title{libao}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] @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 This module provides a small high-level interface to an asynchronous
audio output backend. It opens a live output device or a file output, 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 pause and buffer clearing, and exposes a small set of validation
predicates. predicates.
The central value is an @racket[ao-handle], created by The central value is an @tt{ao-handle}, created by
@racket[ao-open-live] or @racket[ao-open-file]. An @racket[ao-handle] @racket[ao-open-live] or @racket[ao-open-file]. An @tt{ao-handle}
stores the requested playback configuration together with a native stores the requested playback configuration together with a native
asynchronous player handle. It also records the real bit depth accepted asynchronous player handle. It also records the real bit depth accepted
by the selected libao output device. by the selected libao output device.
@@ -25,7 +27,7 @@ by the selected libao output device.
@defproc[(ao-handle? [v any/c]) boolean?]{ @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. @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 If the native player is created successfully, the returned handle is
valid. If player creation fails, the function still returns an 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. for playback.
A finalizer is registered for the handle and calls @racket[ao-close] A finalizer is registered for the handle and calls @racket[ao-close]
+2 -1
View File
@@ -3,12 +3,13 @@
@(require racket/base @(require racket/base
(for-label racket/base (for-label racket/base
racket/path racket/path
racket/contract
"../mp3-decoder.rkt")) "../mp3-decoder.rkt"))
@title{mp3-decoder} @title{mp3-decoder}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] @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, This module provides an MP3 decoder backend. It opens an MP3 file,
reports stream information through a callback, streams decoded PCM reports stream information through a callback, streams decoded PCM