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
(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")
+2
View File
@@ -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")
)
+1 -1
View File
@@ -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
-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
@(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
+7 -1
View File
@@ -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
+1 -1
View File
@@ -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,
+7 -5
View File
@@ -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]
+2 -1
View File
@@ -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