summaryrefslogtreecommitdiff
path: root/rhimport/wave_generator.go
diff options
context:
space:
mode:
Diffstat (limited to 'rhimport/wave_generator.go')
-rw-r--r--rhimport/wave_generator.go221
1 files changed, 221 insertions, 0 deletions
diff --git a/rhimport/wave_generator.go b/rhimport/wave_generator.go
new file mode 100644
index 0000000..f4c3261
--- /dev/null
+++ b/rhimport/wave_generator.go
@@ -0,0 +1,221 @@
+//
+// rhimportd
+//
+// The Radio Helsinki Rivendell Import Daemon
+//
+//
+// Copyright (C) 2015-2016 Christian Pointner <equinox@helsinki.at>
+//
+// This file is part of rhimportd.
+//
+// rhimportd is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// rhimportd is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with rhimportd. If not, see <http://www.gnu.org/licenses/>.
+//
+
+package rhimport
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "io"
+ "math"
+ "time"
+)
+
+const (
+ RIFF_TAG = "RIFF"
+ WAVE_TAG = "WAVE"
+ FMT_TAG = "fmt "
+ FMT_ID_PCM = 0x0001
+ DATA_TAG = "data"
+)
+
+type wavHeader struct {
+ riffTag [4]uint8
+ riffLength uint32
+ waveTag [4]uint8
+
+ fmtTag [4]uint8
+ fmtLength uint32
+ fmtID uint16
+ nChannels uint16
+ sampleRate uint32
+ byteRate uint32
+ blockAlign uint16
+ sampleDepth uint16
+
+ dataTag [4]uint8
+ dataLength uint32
+}
+
+func (h *wavHeader) Bytes() []byte {
+ buf := new(bytes.Buffer)
+ binary.Write(buf, binary.LittleEndian, h)
+ return buf.Bytes()
+}
+
+type SampleGenerator interface {
+ Reset(samplePeriod float64)
+ GetSamples(nSamples uint32, channels uint16) []float64 // this needs to be normalized, aka -1 <= value <= 1
+}
+
+type WavFile struct {
+ header wavHeader
+ headerSize uint32
+ pcmSampleMax float64
+ pcmSampleBytes uint32
+ samplePeriod float64
+ Generator SampleGenerator
+ readOffset uint32
+}
+
+func (wav *WavFile) GetFileSize() (size uint32) {
+ return wav.headerSize + wav.header.dataLength
+}
+
+func (wav *WavFile) Read(p []byte) (n int, err error) {
+ n = 0
+ if wav.readOffset >= (wav.header.riffLength + 8) {
+ return n, io.EOF
+ }
+
+ if wav.readOffset < wav.headerSize {
+ n = copy(p, wav.header.Bytes()[wav.readOffset:])
+ wav.readOffset += uint32(n)
+ wav.Generator.Reset(wav.samplePeriod)
+ }
+ if n >= len(p) {
+ return
+ }
+ nsamples := uint32(len(p)-n) / uint32(wav.header.blockAlign)
+ data := wav.Generator.GetSamples(nsamples, wav.header.nChannels)
+
+ switch wav.header.fmtID {
+ case FMT_ID_PCM:
+ idx := 0
+ for _, normalized := range data {
+ scaled := wav.pcmSampleMax * normalized
+ sample := uint64(math.Trunc(math.Min(scaled, wav.pcmSampleMax)))
+ var b [8]byte
+ binary.LittleEndian.PutUint64(b[:], sample)
+ copy(p[n:n+int(wav.pcmSampleBytes)], b[:])
+ n += int(wav.pcmSampleBytes)
+ wav.readOffset += wav.pcmSampleBytes
+ if wav.readOffset >= (wav.header.riffLength + 8) {
+ return n, io.EOF
+ }
+ idx++
+ }
+ default:
+ return n, errors.New("unknown sample format ID")
+ }
+ return
+}
+
+func NewPCMWavFile(sampleRate uint32, sampleDepth uint16, channels uint16, length time.Duration) (wav *WavFile, err error) {
+ wav = &WavFile{}
+ wav.headerSize = 8 + 4 + 8 + 16 + 8
+ if length <= 0 {
+ return nil, errors.New("invalid length: must be > 0")
+ }
+ wav.pcmSampleMax = float64(uint64(1<<(sampleDepth-1)) - 1)
+ wav.pcmSampleBytes = ((uint32(sampleDepth) + 7) / 8)
+ wav.samplePeriod = 1.0 / float64(sampleRate)
+
+ frameSize32 := uint32(channels) * wav.pcmSampleBytes
+ if frameSize32 > math.MaxUint16 {
+ return nil, errors.New("frame size exceeds 16bit values (64kB)")
+ }
+ frameSize := uint16(frameSize32)
+
+ period_ns := float64(time.Second) / float64(sampleRate)
+ nFramesF64 := (float64(length) / period_ns)
+ if nFramesF64 < 1 {
+ nFramesF64 = 1
+ }
+ if nFramesF64 > math.MaxUint32 {
+ return nil, errors.New("number of frames exceeds limit (reduce length and/or sample-rate)")
+ }
+
+ nFrames := uint32(nFramesF64)
+ if nFrames > ((math.MaxUint32 - wav.headerSize) / uint32(frameSize)) {
+ return nil, errors.New("file length exceeds 32bit values (4GB)")
+ }
+ dataLen := nFrames * uint32(frameSize)
+
+ copy(wav.header.riffTag[:], RIFF_TAG)
+ wav.header.riffLength = wav.headerSize - 8 + dataLen
+ copy(wav.header.waveTag[:], WAVE_TAG)
+
+ copy(wav.header.fmtTag[:], FMT_TAG)
+ wav.header.fmtLength = 16
+ wav.header.fmtID = FMT_ID_PCM
+ wav.header.nChannels = channels
+ wav.header.sampleRate = sampleRate
+ wav.header.byteRate = sampleRate * uint32(frameSize)
+ wav.header.blockAlign = frameSize
+ wav.header.sampleDepth = sampleDepth
+
+ copy(wav.header.dataTag[:], DATA_TAG)
+ wav.header.dataLength = dataLen
+
+ return
+}
+
+type SilenceGenerator struct {
+}
+
+func NewSilenceGenerator() *SilenceGenerator {
+ return &SilenceGenerator{}
+}
+
+func (s *SilenceGenerator) Reset(samplePeriod float64) {
+ // nothing here
+}
+
+func (s *SilenceGenerator) GetSamples(nSamples uint32, nChannels uint16) (data []float64) {
+ data = make([]float64, int(nSamples)*int(nChannels))
+ return
+}
+
+type SinusGenerator struct {
+ amp float64
+ freq float64
+ sp float64
+ t float64
+}
+
+func NewSinusGenerator(ampDB, freq float64) (sin *SinusGenerator) {
+ sin = &SinusGenerator{}
+ sin.amp = math.Pow(10.0, (ampDB / 20.0))
+ sin.freq = freq
+ return
+}
+
+func (sin *SinusGenerator) Reset(samplePeriod float64) {
+ sin.sp = samplePeriod
+ sin.t = 0
+}
+
+func (sin *SinusGenerator) GetSamples(nSamples uint32, nChannels uint16) (data []float64) {
+ data = make([]float64, int(nSamples)*int(nChannels))
+ for i := 0; i < int(nSamples); i++ {
+ val := sin.amp * math.Sin(2*math.Pi*sin.freq*sin.t)
+ for j := 0; j < int(nChannels); j++ {
+ data[i*int(nChannels)+j] = val
+ }
+ sin.t += sin.sp
+ }
+ return
+}