Files
2026-06-10 01:42:08 +02:00

286 lines
13 KiB
Racket

(module resampler racket/base
(require ffi/unsafe
"soxr-ffi.rkt"
"private/utils.rkt")
(provide resampler-available?
resampler-version
make-resampler
resampler?
resampler-input-rate
resampler-output-rate
resampler-channels
resampler-input-format
resampler-output-format
resampler-convert
resampler-drain
resampler-clear!
resampler-close!
resample-bytes
pcm-format?
pcm-format-sample-bytes
pcm-format->soxr-datatype
quality->soxr-recipe)
(define-struct resampler
(ctx input-rate output-rate channels input-format output-format
soxr-input-format soxr-output-format input-sample-bytes output-sample-bytes closed?)
#:mutable
#:constructor-name make-raw-resampler)
(define (resampler-available?) (soxr-available?))
(define (resampler-version) (soxr-version))
(define (normalize-format fmt who)
(cond [(or (eq? fmt 's16) (eq? fmt 'int16) (eq? fmt 'int16-interleaved)) 's16]
[(or (eq? fmt 's24) (eq? fmt 'int24) (eq? fmt 'int24-interleaved)) 's24]
[(or (eq? fmt 's32) (eq? fmt 'int32) (eq? fmt 'int32-interleaved)) 's32]
[(or (eq? fmt 'float32) (eq? fmt 'f32) (eq? fmt 'float)) 'float32]
[(or (eq? fmt 'float64) (eq? fmt 'f64) (eq? fmt 'double)) 'float64]
[else (raise-argument-error who "(or/c 's16 's24 's32 'float32 'float64)" fmt)]))
(define (pcm-format? fmt)
(with-handlers ([exn:fail? (lambda (e) #f)])
(normalize-format fmt 'pcm-format?)
#t))
(define (pcm-format-sample-bytes fmt)
(case (normalize-format fmt 'pcm-format-sample-bytes)
[(s16) 2]
[(s24) 3]
[(s32 float32) 4]
[(float64) 8]))
(define (pcm-format-actual-sample-bytes fmt)
;; SoXR has no packed 24-bit datatype. The wrapper expands s24 to s32
;; before processing and packs s32 back to s24 afterwards when requested.
(case (normalize-format fmt 'pcm-format-actual-sample-bytes)
[(s24) 4]
[else (pcm-format-sample-bytes fmt)]))
(define (pcm-format->soxr-datatype fmt)
(case (normalize-format fmt 'pcm-format->soxr-datatype)
[(s16) SOXR_INT16_I]
[(s24 s32) SOXR_INT32_I]
[(float32) SOXR_FLOAT32_I]
[(float64) SOXR_FLOAT64_I]))
(define (quality->soxr-recipe q)
;; The '*-bitq' recipes are resampling precision recipes; they do not mean
;; that the PCM output is 16/24/32-bit. Output format is controlled by
;; #:output-format.
(cond [(integer? q) q]
[(or (eq? q 'qq) (eq? q 'quick)) SOXR_QQ]
[(or (eq? q 'lq) (eq? q 'low)) SOXR_LQ]
[(or (eq? q 'mq) (eq? q 'medium)) SOXR_MQ]
[(or (eq? q 'hq) (eq? q 'high)) SOXR_HQ]
[(or (eq? q 'vhq) (eq? q 'very-high)) SOXR_VHQ]
[(eq? q '16-bit) SOXR_16_BITQ]
[(eq? q '20-bit) SOXR_20_BITQ]
[(eq? q '24-bit) SOXR_24_BITQ]
[(eq? q '28-bit) SOXR_28_BITQ]
[(eq? q '32-bit) SOXR_32_BITQ]
[else (raise-argument-error 'quality->soxr-recipe
"(or/c 'qq 'lq 'mq 'hq 'vhq '16-bit '20-bit '24-bit '28-bit '32-bit exact-integer?)" q)]))
(define (phase->flags phase)
(cond [(or (eq? phase 'linear) (eq? phase #f)) SOXR_LINEAR_PHASE]
[(or (eq? phase 'intermediate) (eq? phase 'medium)) SOXR_INTERMEDIATE_PHASE]
[(or (eq? phase 'minimum) (eq? phase 'min)) SOXR_MINIMUM_PHASE]
[else (raise-argument-error 'make-resampler "(or/c 'linear 'intermediate 'minimum)" phase)]))
(define (check-error who err)
(when err (error who "~a" err)))
(define (bytes->native bs size)
(let ((p (malloc size 'atomic-interior)))
(memcpy p 0 bs 0 size)
p))
(define (native->bytes p size)
(let ((bs (make-bytes size)))
(memcpy bs 0 p 0 size)
bs))
(define (native-signed-ref bs start bytes)
(int-bytes->integer bs #t (system-big-endian?) start (+ start bytes)))
(define (native-signed-set! bs start bytes value)
(integer->int-bytes value bytes #t (system-big-endian?) bs start))
(define (clamp-s32 v)
(cond [(< v -2147483648) -2147483648]
[(> v 2147483647) 2147483647]
[else v]))
(define (clamp-s24 v)
(cond [(< v -8388608) -8388608]
[(> v 8388607) 8388607]
[else v]))
(define (s24-bytes->s32-bytes buffer size)
(let* ((sample-count (quotient size 3))
(out (make-bytes (* sample-count 4))))
(for ([i (in-range sample-count)])
(let* ((in-off (* i 3))
(out-off (* i 4))
(sample (native-signed-ref buffer in-off 3)))
(native-signed-set! out out-off 4 (clamp-s32 (arithmetic-shift sample 8)))))
out))
(define (s32-bytes->s24-bytes buffer size)
(let* ((sample-count (quotient size 4))
(out (make-bytes (* sample-count 3))))
(for ([i (in-range sample-count)])
(let* ((in-off (* i 4))
(out-off (* i 3))
(sample (native-signed-ref buffer in-off 4)))
(native-signed-set! out out-off 3 (clamp-s24 (arithmetic-shift sample -8)))))
out))
(define (subbytes/size bs size)
(if (= size (bytes-length bs)) bs (subbytes bs 0 size)))
(define (prepare-input-bytes r buffer size)
(case (resampler-input-format r)
[(s24) (s24-bytes->s32-bytes buffer size)]
[else (subbytes/size buffer size)]))
(define (finish-output-bytes r buffer size)
(case (resampler-output-format r)
[(s24) (s32-bytes->s24-bytes buffer size)]
[else (subbytes/size buffer size)]))
(define (output-frame-capacity r in-frames)
(let* ((ratio (/ (resampler-output-rate r) (resampler-input-rate r)))
(delay (if (resampler-ctx r) (max 0.0 (soxr-delay (resampler-ctx r))) 0.0))
(n (ceiling (+ 256 delay (* in-frames ratio)))))
(max 256 (inexact->exact n))))
(define (make-resampler input-rate output-rate channels
#:input-format [input-format 's32]
#:output-format [output-format 's32]
#:quality [quality 'hq]
#:phase [phase 'linear]
#:steep-filter? [steep-filter? #f]
#:scale [scale 1.0]
#:no-dither? [no-dither? #f]
#:num-threads [num-threads 1])
(unless (resampler-available?)
(error 'make-resampler "libsoxr is not available"))
(unless (and (integer? channels) (> channels 0))
(raise-argument-error 'make-resampler "exact-positive-integer?" channels))
(let* ((in-fmt (normalize-format input-format 'make-resampler))
(out-fmt (normalize-format output-format 'make-resampler))
(itype (pcm-format->soxr-datatype in-fmt))
(otype (pcm-format->soxr-datatype out-fmt))
(io-flags (if no-dither? SOXR_NO_DITHER 0))
(quality-flags (bitwise-ior (phase->flags phase) (if steep-filter? SOXR_STEEP_FILTER 0)))
(io-spec (soxr-io-spec itype otype #:scale scale #:flags io-flags))
(quality-spec (soxr-quality-spec (quality->soxr-recipe quality) #:flags quality-flags))
(runtime-spec (soxr-runtime-spec #:num-threads num-threads)))
(let-values (((ctx err) (soxr-create input-rate output-rate channels
#:io-spec io-spec
#:quality-spec quality-spec
#:runtime-spec runtime-spec)))
(check-error 'make-resampler err)
(unless ctx (error 'make-resampler "libsoxr returned no resampler context"))
(make-raw-resampler ctx input-rate output-rate channels in-fmt out-fmt itype otype
(pcm-format-actual-sample-bytes in-fmt)
(pcm-format-actual-sample-bytes out-fmt)
#f))))
(define (ensure-open! r who)
(when (or (not (resampler? r)) (resampler-closed? r))
(error who "resampler is closed")))
(define (check-frame-size who size frame-bytes)
(unless (= (remainder size frame-bytes) 0)
(error who "buffer size ~a is not a whole number of interleaved frames of ~a bytes" size frame-bytes)))
(define (resampler-convert r buffer [size (bytes-length buffer)])
(ensure-open! r 'resampler-convert)
(let* ((nominal-in-frame-bytes (* (resampler-channels r) (pcm-format-sample-bytes (resampler-input-format r))))
(actual-in-frame-bytes (* (resampler-channels r) (resampler-input-sample-bytes r))))
(check-frame-size 'resampler-convert size nominal-in-frame-bytes)
(cond [(zero? size) (values #"" 0)]
[else
(let* ((in-frames (quotient size nominal-in-frame-bytes))
(prepared (prepare-input-bytes r buffer size))
(in-ptr0 (bytes->native prepared (bytes-length prepared))))
(let loop ((offset-frames 0) (remaining in-frames) (pieces '()) (total-frames 0))
(cond [(zero? remaining) (values (apply bytes-append (reverse pieces)) total-frames)]
[else
(let* ((out-cap (output-frame-capacity r remaining))
(out-size (* out-cap (resampler-channels r) (resampler-output-sample-bytes r)))
(out-ptr (malloc out-size 'atomic-interior))
(in-ptr (ptr-add in-ptr0 (* offset-frames actual-in-frame-bytes))))
(let-values (((err idone odone) (soxr-process (resampler-ctx r) in-ptr remaining out-ptr out-cap)))
(check-error 'resampler-convert err)
(when (and (> remaining 0) (zero? idone) (zero? odone))
(error 'resampler-convert "libsoxr made no progress"))
(let* ((raw-size (* odone (resampler-channels r) (resampler-output-sample-bytes r)))
(raw (native->bytes out-ptr raw-size))
(piece (finish-output-bytes r raw raw-size)))
(loop (+ offset-frames idone) (- remaining idone)
(if (zero? odone) pieces (cons piece pieces))
(+ total-frames odone)))))])))])))
(define (resampler-drain r)
(ensure-open! r 'resampler-drain)
(let loop ((pieces '()) (total-frames 0))
(let* ((delay (max 0.0 (soxr-delay (resampler-ctx r))))
(out-cap (max 4096 (inexact->exact (ceiling (+ delay 256)))))
(out-size (* out-cap (resampler-channels r) (resampler-output-sample-bytes r)))
(out-ptr (malloc out-size 'atomic-interior)))
(let-values (((err idone odone) (soxr-process (resampler-ctx r) #f 0 out-ptr out-cap)))
(check-error 'resampler-drain err)
(cond [(zero? odone) (values (apply bytes-append (reverse pieces)) total-frames)]
[else
(let* ((raw-size (* odone (resampler-channels r) (resampler-output-sample-bytes r)))
(raw (native->bytes out-ptr raw-size))
(piece (finish-output-bytes r raw raw-size)))
(loop (cons piece pieces) (+ total-frames odone)))])))))
(define (resampler-clear! r)
(ensure-open! r 'resampler-clear!)
(check-error 'resampler-clear! (soxr-clear (resampler-ctx r)))
#t)
(define (resampler-close! r)
(when (and (resampler? r) (not (resampler-closed? r)))
(soxr-delete (resampler-ctx r))
(set-resampler-ctx! r #f)
(set-resampler-closed?! r #t))
#t)
(define (resample-bytes buffer input-rate output-rate channels
#:size [size (bytes-length buffer)]
#:input-format [input-format 's32]
#:output-format [output-format 's32]
#:quality [quality 'hq]
#:phase [phase 'linear]
#:steep-filter? [steep-filter? #f]
#:scale [scale 1.0]
#:no-dither? [no-dither? #f]
#:num-threads [num-threads 1])
(let ((r (make-resampler input-rate output-rate channels
#:input-format input-format
#:output-format output-format
#:quality quality
#:phase phase
#:steep-filter? steep-filter?
#:scale scale
#:no-dither? no-dither?
#:num-threads num-threads)))
(dynamic-wind
void
(lambda ()
(let-values (((a af) (resampler-convert r buffer size)))
(let-values (((b bf) (resampler-drain r)))
(values (bytes-append a b) (+ af bf)))))
(lambda () (resampler-close! r)))))
) ; end of module