diff --git a/flac/bitstream/bitstream.factor b/flac/bitstream/bitstream.factor new file mode 100644 index 0000000..22b0b19 --- /dev/null +++ b/flac/bitstream/bitstream.factor @@ -0,0 +1,45 @@ +! Copyright (C) 2009 Daniel Ehrenberg +! See http://factorcode.org/license.txt for BSD license. +USING: kernel system destructors accessors byte-arrays math locals io.files io.encodings.binary combinators sequences namespaces ; +QUALIFIED: bitstreams +QUALIFIED: io +USING: prettyprint ; + +IN: flac.bitstream + +CONSTANT: default-bitreader-capacity 65536 +SYMBOL: flac-input-stream + +TUPLE: flac-stream-reader stream bitstream ; + +GENERIC: read-uint ( n flac-stream-reader -- n ) +GENERIC: align-to-byte ( flac-stream-reader -- ) + +: ( path -- flac-stream-reader ) + binary B{ } bitstreams: flac-stream-reader boa ; + +M: flac-stream-reader dispose stream>> dispose ; + +: flac-align-to-byte ( -- ) + 8 flac-input-stream get bitstream>> bitstreams:align ; + +: flac-read-uint ( n -- n ) + [ dup flac-input-stream get bitstream>> bitstreams:enough-bits? not ] + [ + flac-input-stream get + [ stream>> default-bitreader-capacity swap io:stream-read ] [ bitstream>> ] bi + dup bytes>> swap [ prepend ] dip swap >>bytes drop + ] while flac-input-stream get bitstream>> bitstreams:read ; + +: flac-read-sint ( n -- n ) + ! TODO: this isn't rightt + dup flac-read-uint dup . dup 1 - neg shift swap shift ; + +: with-flac-stream-reader* ( flac-bitstream quot -- ) + flac-input-stream swap with-variable ; inline + +: with-flac-stream-reader ( flac-bitstream quot -- ) + [ with-flac-stream-reader* ] curry with-disposal ; inline + +: with-flac-file-reader ( filename quot -- ) + [ ] dip with-flac-stream-reader ; inline diff --git a/flac/decoder/decoder.factor b/flac/decoder/decoder.factor new file mode 100644 index 0000000..6003374 --- /dev/null +++ b/flac/decoder/decoder.factor @@ -0,0 +1,216 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. +USING: math io.encodings.binary kernel io io.files locals endian bit-arrays math.intervals combinators combinators.extras math.order math.bits sequences namespaces accessors byte-arrays math.bitwise arrays ; +USING: prettyprint ; +USING: flac.bitstream flac.metadata flac.format ; + +IN: flac.decoder + +! : unary-code ( q -- bit-array ) +! on-bits 1 shift integer>bit-array ; +! +! :: truncated-binary-code ( r m -- bit-array ) +! m log2 :> b +! b 1 + 2^ m - :> cutoffs +! r cutoffs < +! [ r b ] [ r cutoffs + b 1 + ] if +! >bit-array ; + +:: rice-encode ( n m -- bit-array ) + n m / >integer :> q + n m mod :> r + + q on-bits 1 shift integer>bit-array :> qcode + + m log2 :> b + b 1 + 2^ m - :> cutoffs + r cutoffs < + [ r b ] [ r cutoffs + b 1 + ] if >bit-array :> rcode + + rcode qcode append ; + + +! : rice-encode ( s k -- bit-array ) +! [ dup [ 2^ 1 - bitand ] dip >bit-array ] +! [ -1 * shift unary integer>bit-array ] 2bi append ; +! s k 2^ 1 - bitand k >bit-array +! s k -1 * shift unary integer>bit-array append ; + +:: inverted-unary ( n -- bit-array ) + n 2^ integer>bit-array :> coding + coding set-bits + f n coding set-nth + coding ; + +: read-utf8-uint ( -- n ) + 0 [ 1 flac-read-uint 1 = ] [ 1 + ] while + dup [ 7 swap - flac-read-uint ] dip + [ + drop + 2 flac-read-uint drop + 6 shift 6 flac-read-uint bitor + ] each ; + +: decode-block-size ( n -- n ) + dup + { + { [ 0b0000 = ] [ drop reserved-block-size ] } + { [ 0b0001 = ] [ drop 192 ] } + { [ 0b0010 0b0101 between? ] [ 2 - 2^ 567 * ] } + { [ 0b0110 0b0111 between? ] [ ] } + { [ 0b1000 0b1111 between? ] [ 8 - 2^ 256 * ] } + } cond-case ; + +: decode-bits-per-sample ( n -- n ) + { + { 0b000 [ "TODO" ] } + { 0b001 [ 8 ] } + { 0b010 [ 12 ] } + { 0b011 [ "TODO" ] } + { 0b100 [ 16 ] } + { 0b101 [ 20 ] } + { 0b110 [ 24 ] } + { 0b111 [ "TODO" ] } + } case ; + +: decode-sample-rate ( n -- n ) + { + { 0b0000 [ "TODO" ] } + { 0b0001 [ 88200 ] } + { 0b0010 [ 17640 ] } + { 0b0011 [ 19200 ] } + { 0b0100 [ 8000 ] } + { 0b0101 [ 16000 ] } + { 0b0110 [ 22050 ] } + { 0b0111 [ 24000 ] } + { 0b1000 [ 32000 ] } + { 0b1001 [ 44100 ] } + { 0b1010 [ 48000 ] } + { 0b1011 [ 96000 ] } + { 0b1100 [ 8 flac-read-uint 1000 * ] } + { 0b1101 [ 16 flac-read-uint ] } + { 0b1110 [ 16 flac-read-uint 10 * ] } + { 0b1111 [ invalid-sample-rate ] } + } case ; + +: decode-channels ( n -- channels channel-assignment ) + dup + { + { [ dup 0b0000 0b0111 between? ] [ 1 + ] } + { [ 0b1000 0b1010 between? ] [ 2 ] } + [ invalid-channel-assignment ] + } cond swap + ; + +: read-flac-subframe-constant ( frame-header subframe-header -- constant-subframe ) + drop bits-per-sample>> flac-read-uint flac-subframe-constant boa ; + +: read-residual-coding-method-type ( -- coding-method ) + 2 flac-read-uint dup + 2 > [ reserved-residual-coding-type ] when + ; + +:: read-residual-partitioned-rice ( frame-header subframe-header -- rice residual ) + 4 flac-read-uint :> partition-order + partition-order . + frame-header blocksize>> -1 partition-order * shift :> samples + samples . + 1 partition-order shift 4 [ + dup . + flac-read-uint :> param + param 0b1111 = + [ 5 flac-read-uint :> numbits samples numbits [ flac-read-uint ] map drop "dicks" . param ] + [ samples param [ flac-read-uint ] map first . "balls" . param dup . ] if + ] map :> parameters + parameters . + partition-order + flac-entropy-coding-method-partitioned-rice-contents new + flac-entropy-coding-method-partitioned-rice boa parameters ; + +: read-residual-partitioned-rice2 ( frame-header subframe-header -- rice residual ) + 2drop flac-entropy-coding-method-partitioned-rice-contents new f ; + +: read-flac-residuals ( frame-header subframe-header -- entropy-coding-method residual ) + read-residual-coding-method-type + [ [ read-residual-partitioned-rice ] [ read-residual-partitioned-rice2 ] if ] keep swap + flac-entropy-coding-method boa dup . ; + +: read-flac-subframe-warmup-samples ( frame-header subframe-header -- seq ) + [ bits-per-sample>> ] [ pre-order>> ] bi* swap [ flac-read-uint ] map ; + +: read-flac-subframe-fixed ( frame-header subframe-header -- fixed-subframe ) + 2dup [ read-flac-subframe-warmup-samples ] [ read-flac-residuals ] 2bi* + flac-subframe-fixed boa ; + +: decode-flac-subframe-type ( n -- type order ) + dup + { + { [ 0 = ] [ drop f 0 ] } + { [ 1 = ] [ drop f 1 ] } + { [ 8 12 between? ] [ 8 - 2 ] } + ! { [ 8 12 between? ] [ -1 shift 7 bitand 2 ] } + { [ 32 63 between? ] [ -1 shift 31 bitand 3 ] } ! TODO: is this right? + [ drop reserved-subframe-type ] + } cond-case + swap ; + +: read-flac-subframe-header ( -- subframe-header ) + 1 flac-read-uint 1 = [ invalid-subframe-sync ] when + 6 flac-read-uint decode-flac-subframe-type + 1 flac-read-uint + flac-subframe-header boa ; + +: read-flac-subframe ( frame-header -- subframe ) + read-flac-subframe-header dup dup + [ + subframe-type>> + { + { subframe-type-constant [ read-flac-subframe-constant ] } + { subframe-type-fixed [ read-flac-subframe-fixed ] } + } case + ] dip swap + flac-subframe boa ; + +: read-flac-subframes ( frame-header -- seq ) + dup channels>> swap [ read-flac-subframe ] map ; + +: read-flac-frame-header ( -- frame-header ) + 14 flac-read-uint drop ! ignore sync + 1 flac-read-uint drop ! reserved + 1 flac-read-uint + 4 flac-read-uint + 4 flac-read-uint + 4 flac-read-uint + 3 flac-read-uint + 1 flac-read-uint drop ! ignore magic sync for now + read-utf8-uint + [ + { + [ ] + [ decode-block-size ] + [ decode-sample-rate ] + [ decode-channels ] + [ decode-bits-per-sample ] + } spread + ] dip + 8 flac-read-uint + flac-frame-header boa ; + +: read-flac-frame-footer ( -- frame-footer ) + 16 flac-read-uint flac-frame-footer boa ; + +: read-flac-frame ( -- frame ) + read-flac-frame-header dup + read-flac-subframes + read-flac-frame-footer + flac-frame boa ; + +: read-flac-file ( filename -- flac-stream ) + [ + read-flac-metadata drop + read-flac-frame drop + read-flac-frame drop + read-flac-frame drop + read-flac-frame + + ] with-flac-file-reader ; diff --git a/flac/format/format.factor b/flac/format/format.factor new file mode 100644 index 0000000..d444e1b --- /dev/null +++ b/flac/format/format.factor @@ -0,0 +1,247 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. +USING: alien.syntax math byte-arrays sequences kernel strings arrays assocs ; + +IN: flac.format + +CONSTANT: FLAC-MAGIC 0x664c6143 ! fLaC +CONSTANT: sync-code 0b11111111111110 + +CONSTANT: MIN-BLOCK-SIZE 16 +CONSTANT: MAX-BLOCK-SIZE 65535 +CONSTANT: MAX-SAMPLE-SIZE: 4608 +CONSTANT: MAX-CHANNELS 8 +CONSTANT: MIN-BITS-PER-SAMPLE 4 +CONSTANT: MAX-BITS-PER-SAMPLE 32 ! The value is ((2^16) - 1) * 10 +CONSTANT: MAX-LPC-ORDER 32 +CONSTANT: MAX-LPC-ORDER-48000HZ 12 +CONSTANT: MIN-QLP-COEFF-PRECISION 5 +CONSTANT: MAX-QLP-COEEF-PRECISION 15 +CONSTANT: MAX-FIXED-ORDER 4 +CONSTANT: MAX-RICE-PARTITION-ORDER 15 + +ERROR: not-a-flac-file ; +ERROR: sync-code-error ; +ERROR: invalid-channel-assignment ; +ERROR: reserved-block-size ; +ERROR: invalid-sample-rate ; +ERROR: reserved-subframe-type ; +ERROR: invalid-subframe-sync ; +ERROR: reserved-residual-coding-type ; + +ENUM: flac-frame-number-type + frame-number-type-frame + frame-number-type-sample ; + +ENUM: flac-channel-assignment + channels-mono + channels-left/right + channels-left/right/center + channels-left/right/left-surround/right-surround + channels-left/right/center/left-surround/right-surround + channels-left/right/center/lfe/left-surround/right-surround + channels-left/right/center/lfe/center-surround/side-left/side-right + channels-left/right/center/lfe/left-surround/right-surround/side-left/side-right + channels-left + channels-right + channels-mid ; + +TUPLE: flac-frame-header + { number-type maybe{ frame-number-type-frame frame-number-type-sample } } + { blocksize integer } + { sample-rate integer } + { channels integer } + { channel-assignment + maybe{ channels-mono + channels-left/right + channels-left/right/center + channels-left/right/left-surround/right-surround + channels-left/right/center/left-surround/right-surround + channels-left/right/center/lfe/left-surround/right-surround + channels-left/right/center/lfe/center-surround/side-left/side-right + channels-left/right/center/lfe/left-surround/right-surround/side-left/side-right + channels-left + channels-right + channels-mid } } + { bits-per-sample integer } + { frame|sample-number integer } + { crc integer } ; + +ENUM: flac-subframe-type + subframe-type-constant + subframe-type-verbatim + subframe-type-fixed + subframe-type-lpc ; + +ENUM: flac-entropy-coding-method-type + entropy-coding-partitioned-rice + entropy-coding-partitioned-rice2 ; + +TUPLE: flac-entropy-coding-method-partitioned-rice-contents + { parameters sequence } + { raw-bits integer } + { capacity-by-order integer } ; + +TUPLE: flac-entropy-coding-method-partitioned-rice + { order integer } + { contents flac-entropy-coding-method-partitioned-rice-contents } ; + +TUPLE: flac-entropy-coding-method + { type maybe{ entropy-coding-partitioned-rice + entropy-coding-partitioned-rice2 } } + { data flac-entropy-coding-method-partitioned-rice } ; + +TUPLE: flac-subframe-constant + { value integer } ; + +TUPLE: flac-subframe-verbatim + { data byte-array } ; + +TUPLE: flac-subframe-fixed + { warmup sequence } + { entropy-coding-method flac-entropy-coding-method } + residual ; + +TUPLE: flac-subframe-lpc + { entropy-coding-method maybe{ entropy-coding-partitioned-rice + entropy-coding-partitioned-rice2 } } + { qlp-coeff-precision integer } + { quantization-level integer } + { qlp-coeff integer } + { warmup integer } + residual ; + +TUPLE: flac-subframe-header + { subframe-type maybe{ subframe-type-constant + subframe-type-verbatim + subframe-type-fixed + subframe-type-lpc } } + { pre-order maybe{ integer } } + { wasted-bits integer } ; + +TUPLE: flac-subframe + { header flac-subframe-header } + { data maybe{ flac-subframe-constant + flac-subframe-verbatim + flac-subframe-fixed + flac-subframe-lpc } } ; + +TUPLE: flac-frame-footer + { crc integer } ; + +TUPLE: flac-frame + { header flac-frame-header } + { subframes sequence } + { footer flac-frame-footer } ; + +ENUM: metadata-type + metadata-stream-info + metadata-padding + metadata-application + metadata-seek-table + metadata-vorbis-comment + metadata-cuesheet + metadata-picture + { metadata-invalid 127 } ; + + +TUPLE: metadata-block-header + { last? boolean } + { type maybe{ metadata-stream-info + metadata-padding + metadata-application + metadata-seek-table + metadata-vorbis-comment + metadata-cuesheet + metadata-picture + metadata-invalid } } + { length integer } ; + +TUPLE: stream-info + { min-block-size integer } + { max-block-size integer } + { min-frame-size integer } + { max-frame-size integer } + { sample-rate integer } + { channels integer } + { bits-per-sample integer } + { samples integer } + { md5 string } ; + +TUPLE: seek-table + { seek-points array } ; +TUPLE: seek-point + { sample-number integer } + { offset integer } + { total-samples } ; + +TUPLE: vorbis-comment + { vendor-string string } + { comments assoc } ; + +TUPLE: flac-padding + { length integer } ; + +TUPLE: application + { id string } + { data byte-array } ; + +ENUM: cuesheet-track-type audio non-audio ; + +TUPLE: cuesheet-track + { offset integer } + { number number } + { isrc string } + { type integer } + { pre-emphasis boolean } + { indices array } ; +TUPLE: cuesheet-index + { offset integer } + { number integer } ; +TUPLE: cuesheet + { catalog-number integer } + { lead-in integer } + { cd? boolean } + { tracks array } ; + +ENUM: picture-type + 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 + badn/artist-logotype + publisher/studio-logotype ; + +TUPLE: picture + type + { mime-type string } + { description string } + { width integer } + { height integer } + { depth integer } + { colors integer } + { data byte-array } ; + +TUPLE: metadata + { stream-info stream-info } + { padding maybe{ flac-padding } } + { application maybe{ application } } + { seek-table maybe{ seek-table } } + { vorbis-comment maybe{ vorbis-comment } } + { cuesheet maybe{ cuesheet } } + { picture maybe{ picture } } ;