commit d0a2114cd0f9dda0212b0195364e81fb5e69a68d from: Oliver Lowe <o@olowe.co> date: Sat Dec 21 06:07:49 2024 UTC add package for WAVE files commit - 74364bb7c33ecf3fdef8c8a361310c2f1992c1bd commit + d0a2114cd0f9dda0212b0195364e81fb5e69a68d blob - /dev/null blob + 96d779bdc07609d730676c1c3c48bd1f0beaa542 (mode 644) Binary files /dev/null and wav/test.wav differ blob - /dev/null blob + f1b47d3c3c7c83af3ccdd5e90da8ee7d827d04ef (mode 644) --- /dev/null +++ wav/wav.go @@ -0,0 +1,114 @@ +// Package wav provides an encoder and decoder of WAVE data. +// https://en.wikipedia.org/wiki/WAV +package wav + +import ( + "encoding/binary" + "fmt" + "io" +) + +type File struct { + Header Header + Bitstream io.Reader +} + +type Header struct { + FileSize uint32 + + BlocSize uint32 + AudioFormat uint16 + ChannelCount uint16 + Frequency uint32 + BytesPerSecond uint32 + BytesPerBloc uint16 + BitsPerSample uint16 + + DataSize uint32 +} + +type header struct { + FileBlocID [4]byte + FileSize uint32 + FileFormatID [4]byte + + FormatBlocID [4]byte + BlocSize uint32 + AudioFormat uint16 + ChannelCount uint16 + Frequency uint32 + BytesPerSecond uint32 + BytesPerBloc uint16 + BitsPerSample uint16 + + DataBlocID [4]byte + DataSize uint32 +} + +var ( + fileBlocID = [4]byte{'R', 'I', 'F', 'F'} + fileFormatID = [4]byte{'W', 'A', 'V', 'E'} +) + +var formatBlocID = [4]byte{'f', 'm', 't', ' '} + +var dataBlocID = [4]byte{'d', 'a', 't', 'a'} + +func readHeader(rd io.Reader) (*header, error) { + var h header + if err := binary.Read(rd, binary.LittleEndian, &h); err != nil { + return nil, err + } + if h.FileBlocID != fileBlocID { + return nil, fmt.Errorf("bad file block id %x", h.FileBlocID) + } else if h.FileFormatID != fileFormatID { + return nil, fmt.Errorf("bad file format id %x", h.FileFormatID) + } else if h.FormatBlocID != formatBlocID { + return nil, fmt.Errorf("bad format block id %x", h.FileFormatID) + } else if h.DataBlocID != dataBlocID { + return nil, fmt.Errorf("bad data block id %x", h.DataBlocID) + } + return &h, nil +} + +func EncodeHeader(hdr Header) [44]byte { + var buf [44]byte + h := header{ + fileBlocID, + hdr.FileSize, + fileFormatID, + formatBlocID, + hdr.BlocSize, + hdr.AudioFormat, + hdr.ChannelCount, + hdr.Frequency, + hdr.BytesPerSecond, + hdr.BytesPerBloc, + hdr.BitsPerSample, + dataBlocID, + hdr.DataSize, + } + binary.Encode(buf[:], binary.LittleEndian, h) + return buf +} + +func ReadFile(r io.Reader) (*File, error) { + h, err := readHeader(r) + if err != nil { + return nil, fmt.Errorf("read header: %w", err) + } + return &File{ + Header: Header{ + h.FileSize, + h.BlocSize, + h.AudioFormat, + h.ChannelCount, + h.Frequency, + h.BytesPerSecond, + h.BytesPerBloc, + h.BitsPerSample, + h.DataSize, + }, + Bitstream: r, + }, nil +} blob - /dev/null blob + 17d0e50710ffa13942ad76429c8fcc586e3d87e7 (mode 644) --- /dev/null +++ wav/wav_test.go @@ -0,0 +1,31 @@ +package wav + +import ( + "io" + "os" + "testing" +) + +func TestDecodeEncode(t *testing.T) { + var source [44]byte + f, err := os.Open("test.wav") + if err != nil { + t.Fatal(err) + } + defer f.Close() + if _, err := io.ReadFull(f, source[:]); err != nil { + t.Fatalf("copy header bytes: %v", err) + } + + if _, err := f.Seek(0, 0); err != nil { + t.Fatal(err) + } + file, err := ReadFile(f) + if err != nil { + t.Fatal(err) + } + header := EncodeHeader(file.Header) + if source != header { + t.Errorf("encode header: want %v, got %v", source, header) + } +}