1package flac
2
3// https://xiph.org/flac/format.html
4// https://wiki.hydrogenaud.io/index.php?title=FLAC_decoder_testbench
5// TODO:
6// 16 - Part 6 of Ladybug Castle (partition order 8 with escape codes)
7// 32 - Part 5 of The Four of Us Are Dying (partition order 8 with escape codes)
8
9import (
10	"bytes"
11	"crypto/md5"
12	"fmt"
13
14	"github.com/wader/fq/format"
15	"github.com/wader/fq/format/registry"
16	"github.com/wader/fq/internal/num"
17	"github.com/wader/fq/pkg/bitio"
18	"github.com/wader/fq/pkg/decode"
19	"github.com/wader/fq/pkg/scalar"
20)
21
22var flacMetadatablocksFormat decode.Group
23var flacFrameFormat decode.Group
24
25func init() {
26	registry.MustRegister(decode.Format{
27		Name:        format.FLAC,
28		Description: "Free Lossless Audio Codec file",
29		Groups:      []string{format.PROBE},
30		DecodeFn:    flacDecode,
31		Dependencies: []decode.Dependency{
32			{Names: []string{format.FLAC_METADATABLOCKS}, Group: &flacMetadatablocksFormat},
33			{Names: []string{format.FLAC_FRAME}, Group: &flacFrameFormat},
34		},
35	})
36}
37
38func flacDecode(d *decode.D, in interface{}) interface{} {
39	d.FieldUTF8("magic", 4, d.AssertStr("fLaC"))
40
41	var streamInfo format.FlacStreamInfo
42	var flacFrameIn format.FlacFrameIn
43	var framesNDecodedSamples uint64
44	var streamTotalSamples uint64
45	var streamDecodedSamples uint64
46
47	_, v := d.FieldFormat("metadatablocks", flacMetadatablocksFormat, nil)
48	flacMetadatablockOut, ok := v.(format.FlacMetadatablocksOut)
49	if !ok {
50		panic(fmt.Sprintf("expected FlacMetadatablockOut got %#+v", v))
51	}
52	if flacMetadatablockOut.HasStreamInfo {
53		streamInfo = flacMetadatablockOut.StreamInfo
54		streamTotalSamples = streamInfo.TotalSamplesInStream
55		flacFrameIn = format.FlacFrameIn{StreamInfo: streamInfo}
56	}
57
58	md5Samples := md5.New()
59	d.FieldArray("frames", func(d *decode.D) {
60		for d.NotEnd() {
61			// flac frame might need some fields from stream info to decode
62			_, v := d.FieldFormat("frame", flacFrameFormat, flacFrameIn)
63			ffo, ok := v.(format.FlacFrameOut)
64			if !ok {
65				panic(fmt.Sprintf("expected FlacFrameOut got %#+v", v))
66			}
67
68			samplesInFrame := ffo.Samples
69			if streamTotalSamples > 0 {
70				samplesInFrame = num.MinUInt64(streamTotalSamples-streamDecodedSamples, ffo.Samples)
71			}
72			frameStreamSamplesBuf := ffo.SamplesBuf[0 : samplesInFrame*uint64(ffo.Channels*ffo.BitsPerSample/8)]
73			framesNDecodedSamples += ffo.Samples
74
75			d.MustCopy(md5Samples, bytes.NewReader(frameStreamSamplesBuf))
76			streamDecodedSamples += ffo.Samples
77
78			// reuse buffer if possible
79			flacFrameIn.SamplesBuf = ffo.SamplesBuf
80		}
81	})
82
83	md5CalcValue := d.FieldRootBitBuf("md5_calculated", bitio.NewBufferFromBytes(md5Samples.Sum(nil), -1))
84	_ = md5CalcValue.TryScalarFn(d.ValidateBitBuf(streamInfo.MD5), scalar.RawHex)
85	d.FieldValueU("decoded_samples", framesNDecodedSamples)
86
87	return nil
88}
89