2022-10-29 20:23:50 +03:00
|
|
|
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"
|
|
|
|
)
|
|
|
|
|
2023-03-29 01:36:55 +03:00
|
|
|
var wavHeaderGroup decode.Group
|
|
|
|
var wavFooterGroup decode.Group
|
2022-10-29 20:23:50 +03:00
|
|
|
|
|
|
|
func init() {
|
2023-03-29 01:36:55 +03:00
|
|
|
interp.RegisterFormat(
|
2023-05-01 14:19:04 +03:00
|
|
|
format.WAV,
|
2023-03-29 01:36:55 +03:00
|
|
|
&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{
|
2023-05-01 14:19:04 +03:00
|
|
|
{Groups: []*decode.Group{format.ID3v2}, Out: &wavHeaderGroup},
|
|
|
|
{Groups: []*decode.Group{format.ID3v1, format.ID3v11}, Out: &wavFooterGroup},
|
2023-03-29 01:36:55 +03:00
|
|
|
},
|
|
|
|
})
|
2022-10-29 20:23:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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"
|
|
|
|
|
2022-09-30 14:58:23 +03:00
|
|
|
var subFormatNames = scalar.RawBytesMap{
|
|
|
|
{Bytes: subFormatPCMBytes[:], Scalar: scalar.BitBuf{Sym: "pcm"}},
|
|
|
|
{Bytes: subFormatIEEEFloat[:], Scalar: scalar.BitBuf{Sym: "ieee_float"}},
|
2022-10-29 20:23:50 +03:00
|
|
|
}
|
|
|
|
|
2023-02-18 23:10:48 +03:00
|
|
|
func wavDecode(d *decode.D) any {
|
2022-10-29 20:23:50 +03:00
|
|
|
d.Endian = decode.LittleEndian
|
|
|
|
|
|
|
|
// there are wav files in the wild with id3v2 header id3v1 footer
|
2023-03-29 01:36:55 +03:00
|
|
|
_, _, _ = d.TryFieldFormat("header", &wavHeaderGroup, nil)
|
2022-10-29 20:23:50 +03:00
|
|
|
|
|
|
|
var riffType string
|
|
|
|
riffDecode(
|
|
|
|
d,
|
|
|
|
nil,
|
|
|
|
func(d *decode.D, path path) (string, int64) {
|
2023-11-25 17:01:24 +03:00
|
|
|
id := d.FieldUTF8("id", 4, scalar.ActualTrimSpace, chunkIDDescriptions)
|
2022-10-29 20:23:50 +03:00
|
|
|
|
|
|
|
const restOfFileLen = 0xffffffff
|
2022-09-30 14:58:23 +03:00
|
|
|
size := int64(d.FieldScalarUintFn("size", func(d *decode.D) scalar.Uint {
|
2022-10-29 20:23:50 +03:00
|
|
|
l := d.U32()
|
|
|
|
if l == restOfFileLen {
|
2022-09-30 14:58:23 +03:00
|
|
|
return scalar.Uint{Actual: l, DisplayFormat: scalar.NumberHex, Description: "Rest of file"}
|
2022-10-29 20:23:50 +03:00
|
|
|
}
|
2022-09-30 14:58:23 +03:00
|
|
|
return scalar.Uint{Actual: l, DisplayFormat: scalar.NumberDecimal}
|
|
|
|
}).Actual)
|
2022-10-29 20:23:50 +03:00
|
|
|
|
|
|
|
if size == restOfFileLen {
|
|
|
|
size = d.BitsLeft() / 8
|
|
|
|
}
|
|
|
|
|
|
|
|
return id, size
|
|
|
|
},
|
|
|
|
func(d *decode.D, id string, path path) (bool, any) {
|
|
|
|
switch id {
|
|
|
|
case "RIFF":
|
2022-09-30 14:58:23 +03:00
|
|
|
riffType = d.FieldUTF8("format", 4, d.StrAssert(wavRiffType))
|
2022-10-29 20:23:50 +03:00
|
|
|
return true, nil
|
|
|
|
|
|
|
|
case "LIST":
|
2023-04-11 20:32:41 +03:00
|
|
|
d.FieldUTF8("type", 4)
|
2022-10-29 20:23:50 +03:00
|
|
|
return true, nil
|
|
|
|
|
2023-11-25 17:01:24 +03:00
|
|
|
case "fmt":
|
2022-10-29 20:23:50 +03:00
|
|
|
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)
|
2022-09-30 14:58:23 +03:00
|
|
|
d.FieldU32("type", scalar.UintMapSymStr{
|
2022-10-29 20:23:50 +03:00
|
|
|
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
|
|
|
|
|
2023-07-03 12:17:50 +03:00
|
|
|
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
|
|
|
|
|
2022-10-29 20:23:50 +03:00
|
|
|
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 {
|
2023-04-11 20:32:41 +03:00
|
|
|
d.Errorf("wrong or no WAV riff type found (%s)", riffType)
|
2022-10-29 20:23:50 +03:00
|
|
|
}
|
2023-03-29 01:36:55 +03:00
|
|
|
_, _, _ = d.TryFieldFormat("footer", &wavFooterGroup, nil)
|
2022-10-29 20:23:50 +03:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|