1
1
mirror of https://github.com/wader/fq.git synced 2024-09-19 07:47:14 +03:00
fq/format/riff/wav.go
2023-11-25 15:02:09 +01:00

180 lines
5.0 KiB
Go

package riff
// http://soundfile.sapp.org/doc/WaveFormat/
// https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/wavdec.c
// https://tech.ebu.ch/docs/tech/tech3285.pdf
// http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
// TODO: audio/wav
// TODO: default little endian
import (
"github.com/wader/fq/format"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/interp"
"github.com/wader/fq/pkg/scalar"
)
var wavHeaderGroup decode.Group
var wavFooterGroup decode.Group
func init() {
interp.RegisterFormat(
format.WAV,
&decode.Format{
ProbeOrder: format.ProbeOrderBinFuzzy, // after most others (overlap some with webp)
Description: "WAV file",
Groups: []*decode.Group{format.Probe},
DecodeFn: wavDecode,
Dependencies: []decode.Dependency{
{Groups: []*decode.Group{format.ID3v2}, Out: &wavHeaderGroup},
{Groups: []*decode.Group{format.ID3v1, format.ID3v11}, Out: &wavFooterGroup},
},
})
}
const (
formatExtensible = 0xfffe
)
var (
subFormatPCMBytes = [16]byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
subFormatIEEEFloat = [16]byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
)
const wavRiffType = "WAVE"
var subFormatNames = scalar.RawBytesMap{
{Bytes: subFormatPCMBytes[:], Scalar: scalar.BitBuf{Sym: "pcm"}},
{Bytes: subFormatIEEEFloat[:], Scalar: scalar.BitBuf{Sym: "ieee_float"}},
}
func wavDecode(d *decode.D) any {
d.Endian = decode.LittleEndian
// there are wav files in the wild with id3v2 header id3v1 footer
_, _, _ = d.TryFieldFormat("header", &wavHeaderGroup, nil)
var riffType string
riffDecode(
d,
nil,
func(d *decode.D, path path) (string, int64) {
id := d.FieldUTF8("id", 4, scalar.ActualTrimSpace, chunkIDDescriptions)
const restOfFileLen = 0xffffffff
size := int64(d.FieldScalarUintFn("size", func(d *decode.D) scalar.Uint {
l := d.U32()
if l == restOfFileLen {
return scalar.Uint{Actual: l, DisplayFormat: scalar.NumberHex, Description: "Rest of file"}
}
return scalar.Uint{Actual: l, DisplayFormat: scalar.NumberDecimal}
}).Actual)
if size == restOfFileLen {
size = d.BitsLeft() / 8
}
return id, size
},
func(d *decode.D, id string, path path) (bool, any) {
switch id {
case "RIFF":
riffType = d.FieldUTF8("format", 4, d.StrAssert(wavRiffType))
return true, nil
case "LIST":
d.FieldUTF8("type", 4)
return true, nil
case "fmt":
audioFormat := d.FieldU16("audio_format", format.WAVTagNames)
d.FieldU16("num_channels")
d.FieldU32("sample_rate")
d.FieldU32("byte_rate")
d.FieldU16("block_align")
d.FieldU16("bits_per_sample")
if audioFormat == formatExtensible && d.BitsLeft() > 0 {
d.FieldU16("extension_size")
d.FieldU16("valid_bits_per_sample")
d.FieldU32("channel_mask")
d.FieldRawLen("sub_format", 16*8, subFormatNames)
}
return false, nil
case "data":
d.FieldRawLen("samples", d.BitsLeft())
return false, nil
case "fact":
d.FieldU32("sample_length")
return false, nil
case "smpl":
d.FieldU32("manufacturer")
d.FieldU32("product")
d.FieldU32("sample_period")
d.FieldU32("midi_unity_note")
d.FieldU32("midi_pitch_fraction")
d.FieldU32("smpte_format")
d.FieldU32("smpte_offset")
numSampleLoops := int(d.FieldU32("number_of_sample_loops"))
samplerDataBytes := int(d.FieldU32("sampler_data_bytes"))
d.FieldArray("samples_loops", func(d *decode.D) {
for i := 0; i < numSampleLoops; i++ {
d.FieldStruct("sample_loop", func(d *decode.D) {
d.FieldUTF8("id", 4)
d.FieldU32("type", scalar.UintMapSymStr{
0: "forward",
1: "forward_backward",
2: "backward",
})
d.FieldU32("start")
d.FieldU32("end")
d.FieldU32("fraction")
d.FieldU32("number_of_times")
})
}
})
d.FieldRawLen("sampler_data", int64(samplerDataBytes)*8)
return false, nil
case "bext":
d.FieldUTF8NullFixedLen("description", 256)
d.FieldUTF8NullFixedLen("originator", 32)
d.FieldUTF8NullFixedLen("originator_reference", 32)
d.FieldUTF8NullFixedLen("originator_date", 10)
d.FieldUTF8NullFixedLen("originator_time", 8)
d.FieldU32("time_reference_low")
d.FieldU32("time_reference_high")
d.FieldU16("version")
d.FieldRawLen("umid", 64*8)
d.FieldU16("loudness_value")
d.FieldU16("loudness_range")
d.FieldU16("max_true_peak_level")
d.FieldU16("max_momentary_loudness")
d.FieldU16("max_short_term_loudness")
d.FieldRawLen("reserved", 180*8)
d.FieldRawLen("coding_history", d.BitsLeft())
return false, nil
default:
if riffIsStringChunkID(id) {
d.FieldUTF8NullFixedLen("value", int(d.BitsLeft())/8)
return false, nil
}
d.FieldRawLen("data", d.BitsLeft())
return false, nil
}
},
)
if riffType != wavRiffType {
d.Errorf("wrong or no WAV riff type found (%s)", riffType)
}
_, _, _ = d.TryFieldFormat("footer", &wavFooterGroup, nil)
return nil
}