// // rhimportd // // The Radio Helsinki Rivendell Import Daemon // // // Copyright (C) 2015-2016 Christian Pointner // // 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 . // 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 := &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 }