-
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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].
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user