wav_test.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. package wav
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "path/filepath"
  6. "strings"
  7. "testing"
  8. "time"
  9. )
  10. const testdata = "../../testdata"
  11. // The WAV test pack: every file is one second long at its own rate.
  12. func TestWavFormatsPack(t *testing.T) {
  13. cases := map[string]struct{ format, ch, rate, bits int }{
  14. "MpckitW1/22k 16bit mono.wav": {FormatPCM, 1, 22050, 16},
  15. "MpckitW1/44k 16bit mono.wav": {FormatPCM, 1, 44100, 16},
  16. "MpckitW1/44k 16bit stereo.wav": {FormatPCM, 2, 44100, 16},
  17. "MpckitW1/44k 24bit stereo.wav": {FormatPCM, 2, 44100, 24},
  18. "MpckitW1/48k 16bit stereo.wav": {FormatPCM, 2, 48000, 16},
  19. "MpckitW1/48k 24bit stereo.wav": {FormatPCM, 2, 48000, 24},
  20. "MpckitW1/96k 24bit stereo.wav": {FormatPCM, 2, 96000, 24},
  21. "MpckitW2/44k 8bit mono.wav": {FormatPCM, 1, 44100, 8},
  22. "MpckitW2/44k 32bit int stereo.wav": {FormatPCM, 2, 44100, 32},
  23. "MpckitW2/44k 32bit float stereo.wav": {FormatFloat, 2, 44100, 32},
  24. }
  25. for name, want := range cases {
  26. proj, file, _ := strings.Cut(name, "/")
  27. info, err := ReadFile(filepath.Join(testdata, "wav-formats", proj+"_[ProjectData]", file))
  28. if err != nil {
  29. t.Errorf("%s: %v", name, err)
  30. continue
  31. }
  32. if info.Format != want.format || info.Channels != want.ch || info.SampleRate != want.rate || info.Bits != want.bits {
  33. t.Errorf("%s: %+v", name, info)
  34. }
  35. if info.Frames != int64(want.rate) || info.Duration() != time.Second {
  36. t.Errorf("%s: %d frames, %v", name, info.Frames, info.Duration())
  37. }
  38. if err := info.Validate(); err != nil {
  39. t.Errorf("%s: %v", name, err)
  40. }
  41. }
  42. }
  43. // Device-saved files carry extra chunks (atem, smpl, meta) around the data.
  44. func TestDeviceSavedFiles(t *testing.T) {
  45. cases := map[string]int64{
  46. "device/Demo02_[ProjectData]/Linn 9000 kick.wav": 8213,
  47. "device/DemoFile_[ProjectData]/80s-Kik-Dreamer2 SA.wav": 44100,
  48. "device/DemoFile_[ProjectData]/80s-Snr-Dreamer SA.wav": 27000,
  49. "device/DemoFile_[ProjectData]/80s-Hat-Dreamer1 SA.wav": 22446,
  50. "device/DemoFile_[ProjectData]/80s-Hat-Dreamer2 SA.wav": 108000,
  51. "device/DemoFile_[ProjectData]/PadLp-London Cm114 SA.wav": 371368,
  52. "device/DemoFile_[ProjectData]/Bass-Pure C.wav": 132557,
  53. "accepted/MpckitT2_[ProjectData]/Linn 9000 kick.wav": 8213,
  54. }
  55. for name, frames := range cases {
  56. info, err := ReadFile(filepath.Join(testdata, name))
  57. if err != nil {
  58. t.Errorf("%s: %v", name, err)
  59. continue
  60. }
  61. if info.Frames != frames {
  62. t.Errorf("%s: %d frames, want %d (sliceInfo.End + 1)", name, info.Frames, frames)
  63. }
  64. if err := info.Validate(); err != nil {
  65. t.Errorf("%s: %v", name, err)
  66. }
  67. }
  68. }
  69. type chunk struct {
  70. id string
  71. body []byte
  72. }
  73. func riff(form string, chunks ...chunk) []byte {
  74. var body bytes.Buffer
  75. body.WriteString(form)
  76. for _, c := range chunks {
  77. body.WriteString(c.id)
  78. binary.Write(&body, binary.LittleEndian, uint32(len(c.body)))
  79. body.Write(c.body)
  80. if len(c.body)%2 == 1 {
  81. body.WriteByte(0)
  82. }
  83. }
  84. var b bytes.Buffer
  85. b.WriteString("RIFF")
  86. binary.Write(&b, binary.LittleEndian, uint32(body.Len()))
  87. b.Write(body.Bytes())
  88. return b.Bytes()
  89. }
  90. func fmtChunk(format, ch, rate, bits int) []byte {
  91. var b bytes.Buffer
  92. align := ch * ((bits + 7) / 8)
  93. for _, v := range []any{uint16(format), uint16(ch), uint32(rate), uint32(rate * align), uint16(align), uint16(bits)} {
  94. binary.Write(&b, binary.LittleEndian, v)
  95. }
  96. return b.Bytes()
  97. }
  98. func extensible(sub, ch, rate, bits int) []byte {
  99. b := fmtChunk(FormatExtensible, ch, rate, bits)
  100. ext := make([]byte, 24)
  101. binary.LittleEndian.PutUint16(ext[0:2], 22)
  102. binary.LittleEndian.PutUint16(ext[2:4], uint16(bits))
  103. binary.LittleEndian.PutUint16(ext[8:10], uint16(sub))
  104. copy(ext[10:], "\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71")
  105. return append(b, ext...)
  106. }
  107. func read(b []byte) (Info, error) { return Read(bytes.NewReader(b), int64(len(b))) }
  108. func TestSyntheticHeaders(t *testing.T) {
  109. data := make([]byte, 600)
  110. ok := []struct {
  111. name string
  112. file []byte
  113. format int
  114. frames int64
  115. }{
  116. {"extensible PCM 24-bit", riff("WAVE", chunk{"fmt ", extensible(FormatPCM, 2, 48000, 24)}, chunk{"data", data}), FormatPCM, 100},
  117. {"extensible float", riff("WAVE", chunk{"fmt ", extensible(FormatFloat, 1, 44100, 32)}, chunk{"data", data}), FormatFloat, 150},
  118. {"odd chunk before fmt", riff("WAVE", chunk{"LIST", []byte("abc")}, chunk{"fmt ", fmtChunk(1, 1, 44100, 16)}, chunk{"data", data}), FormatPCM, 300},
  119. {"8-bit mono", riff("WAVE", chunk{"fmt ", fmtChunk(1, 1, 22050, 8)}, chunk{"data", data[:599]}), FormatPCM, 599},
  120. }
  121. for _, c := range ok {
  122. info, err := read(c.file)
  123. if err != nil {
  124. t.Errorf("%s: %v", c.name, err)
  125. continue
  126. }
  127. if info.Format != c.format || info.Frames != c.frames {
  128. t.Errorf("%s: %+v", c.name, info)
  129. }
  130. if err := info.Validate(); err != nil {
  131. t.Errorf("%s: %v", c.name, err)
  132. }
  133. }
  134. // Data size left at zero by a streaming writer: count what is there.
  135. f := riff("WAVE", chunk{"fmt ", fmtChunk(1, 1, 44100, 16)}, chunk{"data", data})
  136. binary.LittleEndian.PutUint32(f[len(f)-len(data)-4:], 0)
  137. if info, err := read(f); err != nil || info.Frames != 300 {
  138. t.Errorf("zero data size: %+v %v", info, err)
  139. }
  140. // Truncated data: count what is there.
  141. f = riff("WAVE", chunk{"fmt ", fmtChunk(1, 1, 44100, 16)}, chunk{"data", data})
  142. if info, err := read(f[:len(f)-100]); err != nil || info.Frames != 250 {
  143. t.Errorf("truncated: %+v %v", info, err)
  144. }
  145. unreadable := map[string][]byte{
  146. "empty": nil,
  147. "not riff": []byte("OggS0000WAVE"),
  148. "RF64": append([]byte("RF64\xff\xff\xff\xffWAVE"), make([]byte, 20)...),
  149. "no fmt": riff("WAVE", chunk{"data", data}),
  150. "no data": riff("WAVE", chunk{"fmt ", fmtChunk(1, 1, 44100, 16)}),
  151. "short fmt": riff("WAVE", chunk{"fmt ", make([]byte, 10)}, chunk{"data", data}),
  152. "zero rate": riff("WAVE", chunk{"fmt ", fmtChunk(1, 1, 0, 16)}, chunk{"data", data}),
  153. "bad align": riff("WAVE", chunk{"fmt ", append(fmtChunk(1, 2, 44100, 16)[:12], 3, 0, 16, 0)}, chunk{"data", data}),
  154. "short ext": riff("WAVE", chunk{"fmt ", fmtChunk(FormatExtensible, 1, 44100, 16)}, chunk{"data", data}),
  155. }
  156. for name, f := range unreadable {
  157. if _, err := read(f); err == nil {
  158. t.Errorf("%s: read succeeded", name)
  159. }
  160. }
  161. refused := map[string][]byte{
  162. "ADPCM": riff("WAVE", chunk{"fmt ", fmtChunk(2, 1, 44100, 4)}, chunk{"data", data}),
  163. "3 channels": riff("WAVE", chunk{"fmt ", fmtChunk(1, 3, 44100, 16)}, chunk{"data", data}),
  164. "64-bit float": riff("WAVE", chunk{"fmt ", fmtChunk(3, 1, 44100, 64)}, chunk{"data", data}),
  165. "12-bit": riff("WAVE", chunk{"fmt ", fmtChunk(1, 1, 44100, 12)}, chunk{"data", data}),
  166. "no audio": riff("WAVE", chunk{"fmt ", fmtChunk(1, 1, 44100, 16)}, chunk{"data", nil}),
  167. }
  168. for name, f := range refused {
  169. info, err := read(f)
  170. if err != nil {
  171. t.Errorf("%s: %v", name, err)
  172. continue
  173. }
  174. if info.Validate() == nil {
  175. t.Errorf("%s: accepted", name)
  176. }
  177. }
  178. long := Info{Format: FormatPCM, Channels: 1, SampleRate: 44100, Bits: 16, BlockAlign: 2, Frames: 44100*20*60 + 1}
  179. if long.Validate() == nil {
  180. t.Error("over 20 minutes accepted")
  181. }
  182. long.Frames--
  183. if err := long.Validate(); err != nil {
  184. t.Errorf("exactly 20 minutes: %v", err)
  185. }
  186. }
  187. // Every file in the WAV test pack holds the same tone; decoded, the 44.1 kHz
  188. // files must agree with the 16-bit control to within 8-bit precision.
  189. func TestDecodeAgreesAcrossFormats(t *testing.T) {
  190. dir := filepath.Join(testdata, "wav-formats")
  191. _, ref, err := DecodeFile(filepath.Join(dir, "MpckitW1_[ProjectData]", "44k 16bit mono.wav"))
  192. if err != nil {
  193. t.Fatal(err)
  194. }
  195. peak := float32(0)
  196. for _, v := range ref[0] {
  197. peak = max(peak, v)
  198. }
  199. if peak < 0.45 || peak > 0.55 {
  200. t.Errorf("control peak %v, want about 0.5", peak)
  201. }
  202. for _, name := range []string{
  203. "MpckitW1_[ProjectData]/44k 16bit stereo.wav",
  204. "MpckitW1_[ProjectData]/44k 24bit stereo.wav",
  205. "MpckitW2_[ProjectData]/44k 8bit mono.wav",
  206. "MpckitW2_[ProjectData]/44k 32bit int stereo.wav",
  207. "MpckitW2_[ProjectData]/44k 32bit float stereo.wav",
  208. } {
  209. info, ch, err := DecodeFile(filepath.Join(dir, name))
  210. if err != nil {
  211. t.Errorf("%s: %v", name, err)
  212. continue
  213. }
  214. if len(ch) != info.Channels || int64(len(ch[0])) != info.Frames {
  215. t.Errorf("%s: %d channels of %d frames", name, len(ch), len(ch[0]))
  216. continue
  217. }
  218. worst := float32(0)
  219. for c := range ch {
  220. for i, v := range ch[c] {
  221. d := v - ref[0][i]
  222. worst = max(worst, d, -d)
  223. }
  224. }
  225. if worst > 1.0/64 {
  226. t.Errorf("%s: differs from the control by up to %v", name, worst)
  227. }
  228. }
  229. }
  230. func TestDecodeExtremes(t *testing.T) {
  231. cases := []struct {
  232. bits int
  233. format int
  234. raw []byte
  235. want []float32
  236. }{
  237. {8, FormatPCM, []byte{0, 128, 255}, []float32{-1, 0, 127.0 / 128}},
  238. {16, FormatPCM, []byte{0, 0x80, 0, 0, 0xff, 0x7f}, []float32{-1, 0, 32767.0 / 32768}},
  239. {24, FormatPCM, []byte{0, 0, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, []float32{-1, -1.0 / (1 << 23), 8388607.0 / (1 << 23)}},
  240. {32, FormatPCM, []byte{0, 0, 0, 0x80, 0, 0, 0, 0}, []float32{-1, 0}},
  241. {32, FormatFloat, []byte{0, 0, 0x80, 0x3f, 0, 0, 0, 0xbf}, []float32{1, -0.5}},
  242. }
  243. for _, c := range cases {
  244. info := Info{Format: c.format, Channels: 1, Bits: c.bits, BlockAlign: c.bits / 8}
  245. got := Decode(info, c.raw)[0]
  246. for i := range c.want {
  247. if got[i] != c.want[i] {
  248. t.Errorf("%d-bit format %d sample %d = %v, want %v", c.bits, c.format, i, got[i], c.want[i])
  249. }
  250. }
  251. }
  252. }