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