|
| 1 | +package riff |
| 2 | + |
| 3 | +// http://midi.teragonaudio.com/tech/aiff.htm |
| 4 | + |
| 5 | +import ( |
| 6 | + "github.com/wader/fq/format" |
| 7 | + "github.com/wader/fq/internal/mathex" |
| 8 | + "github.com/wader/fq/pkg/decode" |
| 9 | + "github.com/wader/fq/pkg/interp" |
| 10 | + "github.com/wader/fq/pkg/scalar" |
| 11 | +) |
| 12 | + |
| 13 | +func init() { |
| 14 | + interp.RegisterFormat(decode.Format{ |
| 15 | + Name: format.AIFF, |
| 16 | + ProbeOrder: format.ProbeOrderBinFuzzy, |
| 17 | + Description: "Audio Interchange File Format", |
| 18 | + Groups: []string{format.PROBE}, |
| 19 | + DecodeFn: aiffDecode, |
| 20 | + }) |
| 21 | +} |
| 22 | + |
| 23 | +const aiffRiffType = "AIFF" |
| 24 | + |
| 25 | +// pstring: |
| 26 | +// > 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. |
| 27 | +// > A pad byte can be added to the end of the text to accomplish this. This pad byte is not reflected in the count. |
| 28 | +func aiffPString(d *decode.D) string { |
| 29 | + l := d.U8() |
| 30 | + pad := (l + 1) % 2 |
| 31 | + s := d.UTF8(int(l + pad)) |
| 32 | + return s[0 : l+1-pad] |
| 33 | +} |
| 34 | + |
| 35 | +func aiffDecode(d *decode.D) any { |
| 36 | + var riffType string |
| 37 | + riffDecode( |
| 38 | + d, |
| 39 | + nil, |
| 40 | + func(d *decode.D, path path) (string, int64) { |
| 41 | + id := d.FieldUTF8("id", 4, chunkIDDescriptions) |
| 42 | + |
| 43 | + const restOfFileLen = 0xffffffff |
| 44 | + size := int64(d.FieldScalarUintFn("size", func(d *decode.D) scalar.Uint { |
| 45 | + l := d.U32() |
| 46 | + if l == restOfFileLen { |
| 47 | + return scalar.Uint{Actual: l, DisplayFormat: scalar.NumberHex, Description: "Rest of file"} |
| 48 | + } |
| 49 | + return scalar.Uint{Actual: l, DisplayFormat: scalar.NumberDecimal} |
| 50 | + }).Actual) |
| 51 | + |
| 52 | + if size == restOfFileLen { |
| 53 | + size = d.BitsLeft() / 8 |
| 54 | + } |
| 55 | + return id, size |
| 56 | + }, |
| 57 | + func(d *decode.D, id string, path path) (bool, any) { |
| 58 | + switch id { |
| 59 | + case "FORM": |
| 60 | + riffType = d.FieldUTF8("format", 4, d.StrAssert(aiffRiffType)) |
| 61 | + return true, nil |
| 62 | + case "COMT": |
| 63 | + numComments := d.FieldU16("num_comments") |
| 64 | + d.FieldArray("comments", func(d *decode.D) { |
| 65 | + for i := 0; i < int(numComments); i++ { |
| 66 | + d.FieldStruct("comment", func(d *decode.D) { |
| 67 | + d.FieldU32("timestamp") |
| 68 | + d.FieldU16("marker_id") |
| 69 | + count := d.FieldU16("count") |
| 70 | + pad := count % 2 |
| 71 | + d.FieldUTF8("text", int(count)) |
| 72 | + if pad != 0 { |
| 73 | + d.FieldRawLen("pad", int64(pad)*8) |
| 74 | + } |
| 75 | + }) |
| 76 | + } |
| 77 | + }) |
| 78 | + return false, nil |
| 79 | + case "COMM": |
| 80 | + d.FieldU16("num_channels") |
| 81 | + d.FieldU32("num_sample_frames") |
| 82 | + d.FieldU16("sample_size") |
| 83 | + // TODO: support big float? |
| 84 | + d.FieldFltFn("sample_rate", func(d *decode.D) float64 { |
| 85 | + return mathex.NewFloat80FromBytes(d.BytesLen(10)).Float64() |
| 86 | + }) |
| 87 | + return false, nil |
| 88 | + case "SSND": |
| 89 | + d.FieldU32("offset") |
| 90 | + d.FieldU32("block_size") |
| 91 | + d.FieldRawLen("data", d.BitsLeft()) |
| 92 | + return false, nil |
| 93 | + case "MARK": |
| 94 | + numMarkers := d.FieldU16("num_markers") |
| 95 | + d.FieldArray("markers", func(d *decode.D) { |
| 96 | + for i := 0; i < int(numMarkers); i++ { |
| 97 | + d.FieldStruct("marker", func(d *decode.D) { |
| 98 | + d.FieldU16("id") |
| 99 | + d.FieldU32("position") |
| 100 | + d.FieldStrFn("name", aiffPString) |
| 101 | + }) |
| 102 | + } |
| 103 | + }) |
| 104 | + return false, nil |
| 105 | + default: |
| 106 | + d.FieldRawLen("data", d.BitsLeft()) |
| 107 | + return false, nil |
| 108 | + } |
| 109 | + }, |
| 110 | + ) |
| 111 | + |
| 112 | + if riffType != aiffRiffType { |
| 113 | + d.Errorf("wrong or no AIFF riff type found (%s)", riffType) |
| 114 | + } |
| 115 | + |
| 116 | + return nil |
| 117 | +} |
0 commit comments