You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

131 lines
3.0 KiB

// Copyright 2020 Justine Alexandra Roberts Tunney
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Additive White Gaussian Noise Generator
//
// This is based off the SpanDSP implementation, which is based off some
// research paper.
package dsp
import (
"math"
)
const (
m1 = 259200
ia1 = 7141
ic1 = 54773
rm1 = 1.0 / m1
m2 = 134456
ia2 = 8121
ic2 = 28411
rm2 = 1.0 / m2
m3 = 243000
ia3 = 4561
ic3 = 51349
dbm0MaxPower = 3.14 + 3.02
)
// Awgn contains the white noise generator state information.
type AWGN struct {
rms float64
ix1 int64
ix2 int64
ix3 int64
r [98]float64
gset float64
iset int
}
// Returns a new comfort noise generator. The volume param is in decibels and
// -50.0 is a good value. The closer you get to 0.0, the louder the noise will
// become.
func NewAWGN(volume float64) (s *AWGN) {
return NewAWGN_DBM0(7162534, volume)
}
func NewAWGN_DBM0(idum int64, level float64) (s *AWGN) {
return NewAWGN_DBOV(idum, level-dbm0MaxPower)
}
func NewAWGN_DBOV(idum int64, level float64) (s *AWGN) {
s = new(AWGN)
if idum < 0.0 {
idum = -idum
}
s.rms = math.Pow(10.0, level/20.0) * 32768.0
s.ix1 = (ic1 + idum) % m1
s.ix1 = (ia1*s.ix1 + ic1) % m1
s.ix2 = s.ix1 % m2
s.ix1 = (ia1*s.ix1 + ic1) % m1
s.ix3 = s.ix1 % m3
s.r[0] = 0.0
for j := 1; j <= 97; j++ {
s.ix1 = (ia1*s.ix1 + ic1) % m1
s.ix2 = (ia2*s.ix2 + ic2) % m2
s.r[j] = (float64(s.ix1) + float64(s.ix2)*rm2) * rm1
}
s.gset = 0.0
s.iset = 0
return
}
func (s *AWGN) Get() int16 {
var fac, r, v1, v2, amp float64
if s.iset == 0 {
for {
v1 = 2.0*s.ran1() - 1.0
v2 = 2.0*s.ran1() - 1.0
r = v1*v1 + v2*v2
if r < 1.0 {
break
}
}
fac = math.Sqrt(-2.0 * math.Log(r) / r)
s.gset = v1 * fac
s.iset = 1
amp = v2 * fac * s.rms
} else {
s.iset = 0
amp = s.gset * s.rms
}
return fsaturate(amp)
}
func (s *AWGN) ran1() (res float64) {
var j int64
s.ix1 = (ia1*s.ix1 + ic1) % m1
s.ix2 = (ia2*s.ix2 + ic2) % m2
s.ix3 = (ia3*s.ix3 + ic3) % m3
j = 1 + ((97 * s.ix3) / m3)
if j > 97 || j < 1 {
return -1
}
res = s.r[j]
s.r[j] = (float64(s.ix1) + float64(s.ix2)*rm2) * rm1
return
}
// TODO(jart): How does FISTP work in Plan9 assembly?
func fsaturate(damp float64) int16 {
if damp > float64(math.MaxInt16) {
return math.MaxInt16
}
if damp < float64(math.MinInt16) {
return math.MinInt16
}
return int16(damp)
// return int16(lrint(damp))
}