Skip to content

Instantly share code, notes, and snippets.

@dchapes
Last active May 14, 2020 01:23
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dchapes/8bd2138376f1f571383d to your computer and use it in GitHub Desktop.
Save dchapes/8bd2138376f1f571383d to your computer and use it in GitHub Desktop.
package main
import (
"bufio"
"encoding/binary"
"flag"
"fmt"
"io"
"log"
"math"
"os"
"time"
)
// http://soundfile.sapp.org/doc/WaveFormat/
// The default byte ordering assumed for WAVE data files is little-endian.
// Files written using the big-endian byte ordering scheme have the identifier RIFX instead of RIFF.
type wavFileHeader struct {
riffTag [4]byte // offset 0; ChunkID, "RIFF" (or "RIFX")
// This is the size of the entire file in bytes minus 8 bytes for
// the two fields not included in this count: ChunkID and ChunkSize.
// Also = 4 + (8 + SubChunk1Size) + (8 + SubChunk2Size)
riffLength uint32 // offset 4; ChunkSize
waveTag [4]byte // offset 8; Format, "WAVE"
fmtTag [4]byte // offset 12; Subchunk1ID, "fmt "
fmtLength uint32 // offset 16; Subchunk1Size, 16 for PCM
// PCM = 1 (i.e. Linear quantization)
// Values other than 1 indicate some form of compression.
audioFormat uint16 // offset 20; AudioFormat
numChannels uint16 // offset 22; NumChannels
sampleRate uint32 // offset 24; SampleRate, 44100, 96000 etc
byteRate uint32 // offset 28; ByteRate = SampleRate * NumChannels * BitsPerSample/8
// The number of bytes for one sample including all channels.
blockAlign uint16 // offset 32; BlockAlign = NumChannels * BitsPerSample/8
bitsPerSample uint16 // offset 34; BitsPerSample
dataTag [4]byte // offset 36; Subchunk2ID, "data"
// This is the number of bytes in the data.
dataLength uint32 // offset 40; Subchunk2Size = NumSamples * NumChannels * BitsPerSample/8
}
// or
// var wavFileHeaderSize = int64(reflect.TypeOf(wavFileHeader{}).Size())
const wavFileHeaderSize = 44
func newWavFileHeader(n, rate, bits, channels int) *wavFileHeader {
h := &wavFileHeader{
fmtLength: 16,
audioFormat: 1,
numChannels: uint16(channels),
sampleRate: uint32(rate),
byteRate: uint32(rate) * uint32(channels) * uint32(bits/8),
blockAlign: uint16(channels) * uint16(bits/8),
bitsPerSample: uint16(bits),
dataLength: uint32(n) * uint32(channels) * uint32(bits/8),
}
h.riffLength = h.dataLength + wavFileHeaderSize - 8
copy(h.riffTag[:], "RIFF")
copy(h.waveTag[:], "WAVE")
copy(h.fmtTag[:], "fmt ")
copy(h.dataTag[:], "data")
return h
}
func (h *wavFileHeader) WriteTo(w io.Writer) (int64, error) {
return wavFileHeaderSize, binary.Write(w, binary.LittleEndian, h)
}
func (h *wavFileHeader) writeSample(w io.Writer, s float64) error {
var buf []byte
switch h.bitsPerSample {
case 8:
//v := uint8(s * math.MaxUint8)
v := uint8(s * 235)
buf = []byte{v}
case 16:
//v := uint16(s * math.MaxUint16)
v := uint16(s * 32000)
buf = []byte{uint8(v & 0xff), uint8(v >> 8)}
default:
return fmt.Errorf("invalid bits per sample %d", h.bitsPerSample)
}
_, err := w.Write(buf)
return err
}
func main() {
const progname = "sinewavegenerator"
log.SetPrefix(progname + ": ")
log.SetFlags(0)
rate := flag.Int("rate", 44100, "The sample rate, e.g. 44100, 96000, etc")
dur := flag.Duration("len", 5*time.Second, "The length, e.g. 5s, 10m12s, etc")
freq := flag.Float64("freq", 440, "The sine wave frequency in Hz")
bits := flag.Int("bits", 16, "Bits per sample, 8 or 16")
chans := flag.Int("chans", 2, "Number of channels, 1 or 2 for mono or stereo")
flag.Usage = func() {
fmt.Fprintln(os.Stderr, "usage: "+progname+" [options] [filename]")
fmt.Fprintln(os.Stderr, "options:")
flag.PrintDefaults()
}
flag.Parse()
var wc io.WriteCloser
switch flag.NArg() {
case 0:
wc = os.Stdout
case 1:
f, err := os.Create(flag.Arg(0))
if err != nil {
log.Fatal(err)
}
wc = f
default:
flag.Usage()
os.Exit(2)
}
w := bufio.NewWriter(wc)
num := int(float64(*rate) * dur.Seconds())
h := newWavFileHeader(num, *rate, *bits, *chans)
if _, err := h.WriteTo(w); err != nil {
log.Fatal(err)
}
radians := *freq * 2 * math.Pi / float64(*rate)
phase := 0.0
for i := 0; i < num; i++ {
phase += radians
s := math.Sin(phase)
if err := h.writeSample(w, s); err != nil {
log.Fatal(err)
}
}
if err := w.Flush(); err != nil {
log.Fatal(err)
}
if err := wc.Close(); err != nil {
log.Fatal(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment