Last active
May 14, 2020 01:23
-
-
Save dchapes/8bd2138376f1f571383d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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