initial commit

main
Steve Ayerhart 2022-07-13 19:25:12 -04:00
commit 83e16877df
No known key found for this signature in database
GPG Key ID: 5C815FDF3A00B8BA
7 changed files with 581 additions and 0 deletions

0
src/flac.scm Normal file
View File

83
src/flac/decoder.scm Normal file
View File

@ -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)))

251
src/flac/format.scm Normal file
View File

@ -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 <entropy-coding-method-partitioned-rice-contents>
(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 <frame-header>
(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 <metadata-block-header>
(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 <stream-info>
(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!
<stream-info>
(λ (record port)
(format port "#<stream-info>")))
(define-record-type <seek-table>
(make-metadata-seek-table seek-points)
metadata-seek-table?
(seek-points seek-table-seek-points))
(define-record-type <seek-point>
(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 <vorbis-comment>
(make-metadata-vorbis-comment vendor comments)
metadata-vorbis-comment?
(vendor vorbis-comment-vendor)
(comments vorbis-comment-comments))
(set-record-type-printer!
<vorbis-comment>
(λ (record port)
(format port "#<<vorbis-comment> vendor: ~a comments: ~a>" (vorbis-comment-vendor record) (length (vorbis-comment-comments record)))))
(define-record-type <padding>
(make-metadata-padding length)
metadata-padding?
(length padding-length))
(define-record-type <application>
(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 <cuesheet-track>
(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 <cuesheet-index>
(make-metadata-cuesheet-index offset number)
metadata-cuesheet-index?
(offset cuesheet-index-offset)
(number cuesheet-index-number))
(define-record-type <cuesheet>
(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 <picture>
(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!
<picture>
(λ (record port)
(format port "#<<picture> type: ~a mime-type: ~a>" (picture-type record) (picture-mime-type record))))
(define-record-type <flac-metadata>
(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!
<flac-metadata>
(λ (record port)
(format port "#<<flac-metadata>")
(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 ">")))

108
src/flac/metadata.scm Normal file
View File

@ -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))

72
src/flac/reader.scm Normal file
View File

@ -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 <flac-reader>
(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)))))

57
src/flac/tests.scm Normal file
View File

@ -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)))))))

10
src/flac/tests/utils.scm Normal file
View File

@ -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))))))