mirror of
https://github.com/wader/fq.git
synced 2024-11-25 23:13:19 +03:00
116 lines
3.0 KiB
Go
116 lines
3.0 KiB
Go
package riff
|
|
|
|
// http://midi.teragonaudio.com/tech/aiff.htm
|
|
|
|
import (
|
|
"github.com/wader/fq/format"
|
|
"github.com/wader/fq/pkg/decode"
|
|
"github.com/wader/fq/pkg/interp"
|
|
"github.com/wader/fq/pkg/scalar"
|
|
)
|
|
|
|
func init() {
|
|
interp.RegisterFormat(
|
|
format.AIFF,
|
|
&decode.Format{
|
|
ProbeOrder: format.ProbeOrderBinFuzzy,
|
|
Description: "Audio Interchange File Format",
|
|
Groups: []*decode.Group{format.Probe},
|
|
DecodeFn: aiffDecode,
|
|
})
|
|
}
|
|
|
|
const aiffRiffType = "AIFF"
|
|
|
|
// pstring:
|
|
// > Pascal-style string, a one-byte count followed by that many text bytes. The total number of bytes in this data type should be even.
|
|
// > A pad byte can be added to the end of the text to accomplish this. This pad byte is not reflected in the count.
|
|
func aiffPString(d *decode.D) string {
|
|
l := d.U8()
|
|
pad := (l + 1) % 2
|
|
s := d.UTF8(int(l + pad))
|
|
return s[0 : l+1-pad]
|
|
}
|
|
|
|
func aiffDecode(d *decode.D) any {
|
|
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 "FORM":
|
|
riffType = d.FieldUTF8("format", 4, d.StrAssert(aiffRiffType))
|
|
return true, nil
|
|
case "COMT":
|
|
numComments := d.FieldU16("num_comments")
|
|
d.FieldArray("comments", func(d *decode.D) {
|
|
for i := 0; i < int(numComments); i++ {
|
|
d.FieldStruct("comment", func(d *decode.D) {
|
|
d.FieldU32("timestamp")
|
|
d.FieldU16("marker_id")
|
|
count := d.FieldU16("count")
|
|
pad := count % 2
|
|
d.FieldUTF8("text", int(count))
|
|
if pad != 0 {
|
|
d.FieldRawLen("pad", int64(pad)*8)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
return false, nil
|
|
case "COMM":
|
|
d.FieldU16("num_channels")
|
|
d.FieldU32("num_sample_frames")
|
|
d.FieldU16("sample_size")
|
|
// TODO: support big float?
|
|
d.FieldF80("sample_rate")
|
|
return false, nil
|
|
case "SSND":
|
|
d.FieldU32("offset")
|
|
d.FieldU32("block_size")
|
|
d.FieldRawLen("data", d.BitsLeft())
|
|
return false, nil
|
|
case "MARK":
|
|
numMarkers := d.FieldU16("num_markers")
|
|
d.FieldArray("markers", func(d *decode.D) {
|
|
for i := 0; i < int(numMarkers); i++ {
|
|
d.FieldStruct("marker", func(d *decode.D) {
|
|
d.FieldU16("id")
|
|
d.FieldU32("position")
|
|
d.FieldStrFn("name", aiffPString)
|
|
})
|
|
}
|
|
})
|
|
return false, nil
|
|
default:
|
|
d.FieldRawLen("data", d.BitsLeft())
|
|
return false, nil
|
|
}
|
|
},
|
|
)
|
|
|
|
if riffType != aiffRiffType {
|
|
d.Errorf("wrong or no AIFF riff type found (%s)", riffType)
|
|
}
|
|
|
|
return nil
|
|
}
|