commit 83e16877df075549a8e057857092d1177c859b9d Author: Steve Ayerhart Date: Wed Jul 13 19:25:12 2022 -0400 initial commit diff --git a/src/flac.scm b/src/flac.scm new file mode 100644 index 0000000..e69de29 diff --git a/src/flac/decoder.scm b/src/flac/decoder.scm new file mode 100644 index 0000000..4ca5032 --- /dev/null +++ b/src/flac/decoder.scm @@ -0,0 +1,83 @@ +(define-module (flac decoder) + #:use-module (flac reader) + #:use-module (flac format)) + +(define (read/assert-frame-sync-code) + (unless (= #b111111111111100 (flac-read-uint 15)) + #f)) + +(define (decode-blocking-strategy raw) + (enum-lookup flac-frame-blocking-strategy-type raw)) + +(define (between? n a b) + (and (>= n a) (<= n b))) + +(define (decode-channel-assignment raw) + (enum-lookup + flac-channel-assignment-type + (cond + [(between? raw #b0000 #b0111) 0] + [(= raw #b1000) 1] + [(= raw #b1001) 2] + [(= raw #b1010) 3] + [else #f]))) + +(define (decode-block-size raw) + (cond + [(= raw #b0000) 'reserved] + [(= raw #b0001) 192] + [(between? raw #b0010 #b0101) (* 576 (expt 2 (- raw 2)))] + [(= raw #b0110) (+ 1 (flac-read-uint 8))] + [(= raw #b0111) (+ 1 (flac-read-uint 16))] + [(between? raw #b1000 #b1111) (* 256 (expt 2 (- raw 8)))])) + +(define (decode-sample-rate stream-info raw) + (case raw + [(#b0000) (stream-info-sample-rate stream-info)] + [(#b0001) 88200] + [(#b0010) 17640] + [(#b0011) 19200] + [(#b0100) 8000] + [(#b0101) 16000] + [(#b0110) 22050] + [(#b0111) 24000] + [(#b1000) 32000] + [(#b1001) 44100] + [(#b1010) 48000] + [(#b1011) 96000] + [(#b1100) (* 1000 (flac-read-uint 8))] ; sample rate in kHz + [(#b1101) (flac-read-uint 16)] ; sample rate in Hz + [(#b1110) (* 10 (flac-read-uint 16))] ; sample rate in tens of Hz + [(#b1111) 'invalid])) + +(define (decode-bits-per-sample stream-info raw) + (case raw + [(#b000) (stream-info-bits-per-sample stream-info)] + [(#b001) 8] + [(#b010) 12] + [(#b011) 'reserved] + [(#b100) 16] + [(#b101) 20] + [(#b110) 24] + [(#b111) 'reserved])) + +(define (read-frame-header stream-info) + (read/assert-frame-sync-code) + (let* ([blocking-strategy (decode-blocking-strategy (flac-read-uint 1))] + [raw-blocksize (flac-read-uint 4)] + [raw-sample-rate (flac-read-uint 4)] + [channel-assignment (decode-channel-assignment (flac-read-uint 4))] + [bits-per-sample (decode-bits-per-sample stream-info (flac-read-uint 3))] + [ignore-reserved (flac-read-uint 1)] + [frame/sample-number (flac-read-coded-number)] + [blocksize (decode-block-size raw-blocksize)] + [sample-rate (decode-sample-rate stream-info raw-sample-rate)] + [crc (flac-read-uint 8)]) + (make-frame-header + blocking-strategy + blocksize + sample-rate + channel-assignment + bits-per-sample + frame/sample-number + crc))) diff --git a/src/flac/format.scm b/src/flac/format.scm new file mode 100644 index 0000000..f0a5232 --- /dev/null +++ b/src/flac/format.scm @@ -0,0 +1,251 @@ +(define-module (flac format) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-9) + #:use-module (srfi srfi-9 gnu) + #:use-module (ice-9 regex) + #:use-module (rnrs enums) + #:export (FLAC-MAGIC + enum-lookup + + flac-frame-blocking-strategy-type + flac-frame-number-type + flac-channel-assignment-type + flac-subframe-type + flac-entropy-coding-method-type + + make-frame-header + frame-header-strategy frame-header-blocksize frame-header-sample-rate + frame-header-channel-assignment frame-header-bits-per-sample + frame-header-frame/sample-number frame-header-crc + + + make-metadata-block-header + metadata-block-header-last? + metadata-block-header-type + metadata-block-header-length + + make-metadata-padding + + make-metadata-stream-info metadata-stream-info? + stream-info-min-block-size stream-info-max-block-size + stream-info-min-frame-size stream-info-max-frame-size + stream-info-sample-rate stream-info-channels + stream-info-bits-per-sample stream-info-samples stream-info-md5 + + make-metadata-vorbis-comment + + make-flac-metadata + flac-metadata-stream-info set-flac-metadata-stream-info! + flac-metadata-seek-table set-flac-metadata-seek-table! + flac-metadata-vorbis-comment set-flac-metadata-vorbis-comment! + flac-metadata-padding set-flac-metadata-padding! + flac-metadata-pictures set-flac-metadata-pictures! + flac-metadata-application set-flac-metadata-application! + flac-metadata-cuesheet set-flac-metadata-cuesheet! + + + make-metadata-picture + flac-picture-type + + make-metadata-seek-point make-metadata-seek-table + + flac-metadata-type flac-metadata-type-index)) + +(define FLAC-MAGIC #x664c6143) ; fLaC + + +(define (enum-lookup enum int) + (list-ref (enum-set->list enum) int)) + +(define flac-frame-blocking-strategy-type + (make-enumeration '(fixed variable))) + +(define flac-frame-number-type + (make-enumeration '(frame sample))) + +(define channel-assignment-type + (make-enumeration '(independent left right mid))) + +(define flac-subframe-type + (make-enumeration '(constant verbatim fix lpc))) + +(define flac-entropy-coding-method-type + (make-enumeration '(rice rice2))) + +(define-record-type + (make-entropy-coding-method-partitioned-rice-contents parameters raw-bits capacity-by-order) + entropy-coding-method-partitioned-rice-contents? + (parameters entropy-coding-method-partitioned-rice-contents-parameters) + (raw-bits entropy-coding-method-partitioned-rice-contents-raw-bits) + (capacity-by-order entropy-coding-method-partitioned-rice-contents-capacity-by-order)) + +(define-record-type + (make-frame-header blocking-strategy blocksize sample-rate channel-assignment bits-per-sample frame/sample-number crc) + frame-header? + (blocking-strategy frame-header-blocking-strategy) + (blocksize frame-header-blocksize) + (sample-rate frame-header-sample-rate) + (channel-assignment frame-header-channel-assignment) + (bits-per-sample frame-header-bits-per-sample) + (frame/sample-number frame-header-frame/sample-number) + (crc frame-header-crc)) + +; metadata + +(define flac-metadata-type + (make-enumeration '(stream-info + padding + application + seek-table + vorbis-comment + cuesheet + picture + invalid))) + +(define-record-type + (make-metadata-block-header last? type length) + metadata-block-header? + (last? metadata-block-header-last?) + (type metadata-block-header-type) + (length metadata-block-header-length)) + +(define-record-type + (make-metadata-stream-info min-block-size max-block-size min-frame-size max-frame-size sample-rate channels bits-per-sample samples md5) + metadata-stream-info? + (min-block-size stream-info-min-block-size) + (max-block-size stream-info-max-block-size) + (min-frame-size stream-info-min-frame-size) + (max-frame-size stream-info-max-frame-size) + (sample-rate stream-info-sample-rate) + (channels stream-info-channels) + (bits-per-sample stream-info-bits-per-sample) + (samples stream-info-samples) + (md5 stream-info-md5)) + +(set-record-type-printer! + + (λ (record port) + (format port "#"))) + +(define-record-type + (make-metadata-seek-table seek-points) + metadata-seek-table? + (seek-points seek-table-seek-points)) + +(define-record-type + (make-metadata-seek-point sample-number offset total-samples) + metadata-seek-point? + (sample-number seek-point-sample-number) + (offset seek-point-offset) + (total-samples seek-point-total-samples)) + +(define-record-type + (make-metadata-vorbis-comment vendor comments) + metadata-vorbis-comment? + (vendor vorbis-comment-vendor) + (comments vorbis-comment-comments)) + +(set-record-type-printer! + + (λ (record port) + (format port "#< vendor: ~a comments: ~a>" (vorbis-comment-vendor record) (length (vorbis-comment-comments record))))) + +(define-record-type + (make-metadata-padding length) + metadata-padding? + (length padding-length)) + +(define-record-type + (make-metadata-application id data) + metadata-application? + (id application-id) + (data application-data)) + +(define flac-cuesheet-track-type (make-enumeration '(audio non-audio))) + +(define-record-type + (make-metadata-cuesheet-track offset number isrc type pre-emphasis indices) + metadata-cuesheet-track? + (offset cuesheet-track-offset) + (number cuesheet-track-number) + (isrc cuesheet-track-isrc) + (type cuesheet-track-type) + (pre-emphasis cuesheet-track-pre-emphasis) + (indices cuesheet-track-indices)) + +(define-record-type + (make-metadata-cuesheet-index offset number) + metadata-cuesheet-index? + (offset cuesheet-index-offset) + (number cuesheet-index-number)) + +(define-record-type + (make-metadata-cuesheet catalog-number lead-in cd? tracks) + metadata-cuesheet? + (catalog-number cuesheet-catalog-number) + (lead-in cuesheet-lead-in) + (cd? cuesheet-cd?) + (tracks cuesheet-tracks)) + +(define flac-picture-type + (make-enumeration + '(other + file-icon + other-file-icon + front-cover + back-cover + leaflet-page + media + lead-artist/performer/soloist + artist/performer + conductor + band/orchestra + composer + lyricist/text-writer + recording-location + during-recording + during-performance + movie/video-screen-capture + bright-coloured-fish + illustration + band/artist-logotype + publisher/studio-logotype))) + +(define-record-type + (make-metadata-picture type mime-type description width height depth colors data) + metadata-picture? + (type picture-type) + (mime-type picture-mime-type) + (description picture-description) + (width picture-width) + (height picture-height) + (depth picture-depth) + (colors picture-colors) + (data picture-data)) + +(set-record-type-printer! + + (λ (record port) + (format port "#< type: ~a mime-type: ~a>" (picture-type record) (picture-mime-type record)))) + +(define-record-type + (make-flac-metadata stream-info padding application seek-table vorbis-comment cuesheet pictures) + flac-metadata? + (stream-info flac-metadata-stream-info set-flac-metadata-stream-info!) + (padding flac-metadata-padding set-flac-metadata-padding!) + (application flac-metadata-application set-flac-metadata-application!) + (seek-table flac-metadata-seek-table set-flac-metadata-seek-table!) + (vorbis-comment flac-metadata-vorbis-comment set-flac-metadata-vorbis-comment!) + (cuesheet flac-metadata-cuesheet set-flac-metadata-cuesheet!) + (pictures flac-metadata-pictures set-flac-metadata-pictures!)) + +(set-record-type-printer! + + (λ (record port) + (format port "#<") + (let ((getters '(flac-metadata-stream-info flac-metadata-vorbis-comment flac-metadata-application flac-metadata-cuesheet flac-metadata-pictures flac-metadata-seek-table))) + (for-each (λ (getter) + (when ((primitive-eval getter) record) + (regexp-substitute/global port "flac-metadata-" (symbol->string getter) 'pre " " 'post))) + getters)) + (format port ">"))) diff --git a/src/flac/metadata.scm b/src/flac/metadata.scm new file mode 100644 index 0000000..823a4a0 --- /dev/null +++ b/src/flac/metadata.scm @@ -0,0 +1,108 @@ +(define-module (flac metadata) + #:use-module (flac format) + #:use-module (flac reader) + + #:use-module (ice-9 match) + #:use-module (ice-9 optargs) + #:use-module (rnrs enums) + #:use-module (rnrs bytevectors) + #:use-module (srfi srfi-1) + + #:export (flac-metadata flac-file-metadata)) + +(define (read-metadata-block-header) + (make-metadata-block-header + (= 1 (flac-read-uint 1)) + (list-ref (enum-set->list flac-metadata-type) (flac-read-uint 7)) + (flac-read-uint 24))) + +(define (read-metadata-block-stream-info) + (make-metadata-stream-info + (flac-read-uint 16) + (flac-read-uint 16) + (flac-read-uint 24) + (flac-read-uint 24) + (flac-read-uint 20) + (+ 1 (flac-read-uint 3)) + (+ 1 (flac-read-uint 5)) + (flac-read-uint 36) + (flac-read-bytes 16))) + +(define (read-metadata-block-seek-table length) + (make-metadata-seek-table + (map (λ (_) + (make-metadata-seek-point + (flac-read-uint 64) + (flac-read-uint 64) + (flac-read-uint 16))) + (iota (/ length 18))))) + +(define (read-metadata-block-vorbis-comment) + (define (read-native-u32) (bytevector-u32-native-ref (flac-read-bytes 4) 0)) + (make-metadata-vorbis-comment + (utf8->string (flac-read-bytes (read-native-u32))) + (map (λ (_) (string-split (utf8->string (flac-read-bytes (read-native-u32))) #\=)) (iota (read-native-u32))))) + +(define (read-metadata-block-padding length) + (flac-read-uint length) + (make-metadata-padding length)) + +(define (read-metadata-block-picture) + (make-metadata-picture + (list-ref (enum-set->list flac-picture-type) (flac-read-uint 32)) + (utf8->string (flac-read-bytes (flac-read-uint 32))) + (utf8->string (flac-read-bytes (flac-read-uint 32))) + (flac-read-uint 32) + (flac-read-uint 32) + (flac-read-uint 32) + (flac-read-uint 32) + (flac-read-bytes (flac-read-uint 32)))) + +(define (read-metadata-block metadata length type) + (match type + ('stream-info (set-flac-metadata-stream-info! metadata (read-metadata-block-stream-info))) + ('seek-table (set-flac-metadata-seek-table! metadata (read-metadata-block-seek-table length))) + ('vorbis-comment (set-flac-metadata-vorbis-comment! metadata (read-metadata-block-vorbis-comment))) + ('picture (set-flac-metadata-pictures! + metadata + (cons (read-metadata-block-picture) + (flac-metadata-pictures metadata)))) + ('padding (set-flac-metadata-padding! metadata (read-metadata-block-padding length)))) + metadata) + +(define (read-flac-metadata) + (flac-read/assert-magic) + (let metadata-loop ((metadata (make-flac-metadata #f #f #f #f #f #f '())) + (header (read-metadata-block-header))) + (if (metadata-block-header-last? header) + (read-metadata-block metadata (metadata-block-header-length header) (metadata-block-header-type header)) + (metadata-loop (read-metadata-block + metadata + (metadata-block-header-length header) + (metadata-block-header-type header)) + (read-metadata-block-header))))) + + ; FIXME: bail early if not in type +(define (read-flac-metadata-type type) + (flac-read/assert-magic) + (let metadata-loop ((header (read-metadata-block-header))) + (if (or (metadata-block-header-last? header) + (equal? type (metadata-block-header-type header))) + (match type + ('stream-info (read-metadata-block-stream-info)) + ('vorbis-comment (read-metadata-block-vorbis-comment)) + (_ #f)) + (begin + (flac-read-bytes (metadata-block-header-length header)) + (metadata-loop (read-metadata-block-header)))))) + +(define* (flac-metadata port #:optional (type #f)) + (with-flac-input-port port + (λ () + (if (symbol? type) + (read-flac-metadata-type type) + (read-flac-metadata))))) + +(define* (flac-file-metadata filename #:optional (type #f)) + (with-flac-input-port (open-input-file filename #:binary #t) + (λ () (flac-metadata (current-input-port) type)) #:binary #t)) diff --git a/src/flac/reader.scm b/src/flac/reader.scm new file mode 100644 index 0000000..e65e267 --- /dev/null +++ b/src/flac/reader.scm @@ -0,0 +1,72 @@ +(define-module (flac reader) + #:use-module (flac format) + #:use-module (srfi srfi-9) + + #:use-module (ice-9 binary-ports) + + #:use-module (rnrs bytevectors) + #:use-module (rnrs arithmetic bitwise) + + #:export (flac-read-uint + flac-read-bytes + flac-read-coded-number + with-flac-input-port + new-flac-reader + make-flac-reader + flac-read/assert-magic + current-flac-reader)) + +(define current-flac-reader (make-parameter #f)) + +(define-record-type + (make-flac-reader port bit-buffer bit-buffer-length) + flac-reader? + (port flac-reader-port) + (bit-buffer flac-reader-bit-buffer set-flac-reader-bit-buffer!) + (bit-buffer-length flac-reader-bit-buffer-length set-flac-reader-bit-buffer-length!)) + +(define (with-flac-input-port port thunk) + (with-input-from-port port + (λ () + (parameterize ((current-flac-reader (make-flac-reader (current-input-port) 0 0))) + (thunk))))) + +(define (new-flac-reader port) + (current-flac-reader (make-flac-reader port 0 0)) + (current-flac-reader)) + +(define (flac-read-bits reader bits) + (while + (< (flac-reader-bit-buffer-length reader) bits) + (let ((byte-read (get-u8 (flac-reader-port reader)))) + (set-flac-reader-bit-buffer! reader + (bitwise-ior (bitwise-arithmetic-shift (flac-reader-bit-buffer reader) 8) + byte-read)) + (set-flac-reader-bit-buffer-length! reader (+ (flac-reader-bit-buffer-length reader) 8)))) + + (set-flac-reader-bit-buffer-length! reader (- (flac-reader-bit-buffer-length reader) bits)) + + (let ((uint (bitwise-and (bitwise-arithmetic-shift-right + (flac-reader-bit-buffer reader) (flac-reader-bit-buffer-length reader)) + (- (bitwise-arithmetic-shift 1 bits) 1)))) + (set-flac-reader-bit-buffer! reader + (bitwise-and + (flac-reader-bit-buffer reader) + (- (bitwise-arithmetic-shift 1 (flac-reader-bit-buffer-length reader)) 1))) + uint)) + +(define (flac-read-bytes n) + (u8-list->bytevector (map (λ (_) (flac-read-uint 8)) (iota n)))) + +(define (flac-read-uint bits) + (flac-read-bits (current-flac-reader) bits)) + +(define (flac-read/assert-magic) + (unless (= FLAC-MAGIC (flac-read-uint 32)) + #f)) + +(define (flac-read-coded-number) + (let coded-number-loop ((coded-sample-number (flac-read-uint 8))) + (if (< coded-sample-number #b11000000) + coded-sample-number + (coded-number-loop (bitwise-and (bitwise-arithmetic-shift coded-sample-number 1) #xff))))) diff --git a/src/flac/tests.scm b/src/flac/tests.scm new file mode 100644 index 0000000..d89e8d7 --- /dev/null +++ b/src/flac/tests.scm @@ -0,0 +1,57 @@ +(define-module (flac tests) + #:use-module (flac tests utils) + #:use-module (flac reader) + #:use-module (flac format) + #:use-module (flac metadata) + + #:use-module (rnrs bytevectors) + #:use-module (ice-9 binary-ports) + + #:use-module (srfi srfi-64)) + +(define example-2 + #vu8(#x66 #x4c #x61 #x43 #x00 #x00 #x00 #x22 #x00 #x10 #x00 #x10 + #x00 #x00 #x17 #x00 #x00 #x44 #x0a #xc4 #x42 #xf0 #x00 #x00 + #x00 #x13 #xd5 #xb0 #x56 #x49 #x75 #xe9 #x8b #x8d #x8b #x93 + #x04 #x22 #x75 #x7b #x81 #x03 #x03 #x00 #x00 #x12 #x00 #x00 + #x00 #x00 #x00 #x00 #x00 #x00 #x00 #x00 #x00 #x00 #x00 #x00 + #x00 #x00 #x00 #x10 #x04 #x00 #x00 #x3a #x20 #x00 #x00 #x00 + #x72 #x65 #x66 #x65 #x72 #x65 #x6e #x63 #x65 #x20 #x6c #x69 + #x62 #x46 #x4c #x41 #x43 #x20 #x31 #x2e #x33 #x2e #x33 #x20 + #x32 #x30 #x31 #x39 #x30 #x38 #x30 #x34 #x01 #x00 #x00 #x00 + #x0e #x00 #x00 #x00 #x54 #x49 #x54 #x4c #x45 #x3d #xd7 #xa9 + #xd7 #x9c #xd7 #x95 #xd7 #x9d #x81 #x00 #x00 #x06 #x00 #x00 + #x00 #x00 #x00 #x00 #xff #xf8 #x69 #x98 #x00 #x0f #x99 #x12 + #x08 #x67 #x01 #x62 #x3d #x14 #x42 #x99 #x8f #x5d #xf7 #x0d + #x6f #xe0 #x0c #x17 #xca #xeb #x21 #x00 #x0e #xe7 #xa7 #x7a + #x24 #xa1 #x59 #x0c #x12 #x17 #xb6 #x03 #x09 #x7b #x78 #x4f + #xaa #x9a #x33 #xd2 #x85 #xe0 #x70 #xad #x5b #x1b #x48 #x51 + #xb4 #x01 #x0d #x99 #xd2 #xcd #x1a #x68 #xf1 #xe6 #xb8 #x10 + #xff #xf8 #x69 #x18 #x01 #x02 #xa4 #x02 #xc3 #x82 #xc4 #x0b + #xc1 #x4a #x03 #xee #x48 #xdd #x03 #xb6 #x7c #x13 #x30)) + +(define example-1 + #vu8(#x66 #x4c #x61 #x43 #x80 #x00 #x00 #x22 #x10 #x00 #x10 #x00 + #x00 #x00 #x0f #x00 #x00 #x0f #x0a #xc4 #x42 #xf0 #x00 #x00 + #x00 #x01 #x3e #x84 #xb4 #x18 #x07 #xdc #x69 #x03 #x07 #x58 + #x6a #x3d #xad #x1a #x2e #x0f #xff #xf8 #x69 #x18 #x00 #x00 + #xbf #x03 #x58 #xfd #x03 #x12 #x8b #xaa #x9a)) + +; (test-begin "RFC Examples") +; +; + + +(with-tests + "RFC Examples" + (test-group "Example 1" + (with-flac-input-port (open-bytevector-input-port example-1) + (λ () + (define expected-stream-info + (make-metadata-stream-info 4096 4096 15 15 44100 2 16 1 #vu8(62 132 180 24 7 220 105 3 7 88 106 61 173 26 46 15))) + (define expected-metadata + (make-flac-metadata expected-stream-info #f #f #f #f #f '())) + (test-group "Metadata" + (let ((actual-metadata ((@@ (flac metadata) read-flac-metadata)))) + (test-equal "stream info" (flac-metadata-stream-info actual-metadata) expected-stream-info) + (test-equal "metadata" actual-metadata expected-metadata))))))) diff --git a/src/flac/tests/utils.scm b/src/flac/tests/utils.scm new file mode 100644 index 0000000..94685b1 --- /dev/null +++ b/src/flac/tests/utils.scm @@ -0,0 +1,10 @@ +(define-module (flac tests utils) + #:use-module (srfi srfi-64) + #:export (with-tests)) + +(define-syntax-rule (with-tests name body ...) + (begin + (test-begin name) + body ... + (test-end name))) +; (exit (zero? (test-runner-fail-count (test-end name))))))