mirror of
https://github.com/vsariola/sointu.git
synced 2025-11-12 04:46:13 -05:00
feat(tracker): panic synth if Inf or NaN, and handle these in detectors
Closes #210.
This commit is contained in:
parent
167f541a52
commit
bdfe2d37bf
@ -74,6 +74,11 @@ const (
|
||||
)
|
||||
|
||||
const MAX_INTEGRATED_DATA = 10 * 60 * 60 // 1 hour of samples at 10 Hz (100 ms per sample)
|
||||
// In the detector, we clamp the signal levels to +-MAX_SIGNAL_AMPLITUDE to
|
||||
// avoid Inf results. This is 240 dBFS. max float32 is about 3.4e38, so squaring
|
||||
// the amplitude values gives 1e24, and adding 4410 of those together (when
|
||||
// taking the mean) gives a value < 1e37, which is still < max float32.
|
||||
const MAX_SIGNAL_AMPLITUDE = 1e12
|
||||
|
||||
const (
|
||||
PeakMomentary PeakType = iota
|
||||
@ -232,7 +237,7 @@ func (d *loudnessDetector) update(chunk sointu.AudioBuffer) LoudnessResult {
|
||||
for chn := range 2 {
|
||||
// deinterleave the channels
|
||||
for i := range chunk {
|
||||
d.tmp[i] = chunk[i][chn]
|
||||
d.tmp[i] = removeNaNsAndClamp(chunk[i][chn])
|
||||
}
|
||||
// filter the signal with the weighting filter
|
||||
for k := range d.weighting {
|
||||
@ -287,6 +292,13 @@ func (d *loudnessDetector) reset() {
|
||||
d.integratedPower = 0
|
||||
}
|
||||
|
||||
func removeNaNsAndClamp(s float32) float32 {
|
||||
if s != s { // NaN
|
||||
return 0
|
||||
}
|
||||
return min(max(s, -MAX_SIGNAL_AMPLITUDE), MAX_SIGNAL_AMPLITUDE)
|
||||
}
|
||||
|
||||
func powerToDecibel(power float32) Decibel {
|
||||
return Decibel(float32(10 * math.Log10(float64(power))))
|
||||
}
|
||||
@ -382,7 +394,7 @@ func (d *peakDetector) update(buf sointu.AudioBuffer) (ret PeakResult) {
|
||||
for chn := range 2 {
|
||||
// deinterleave the channels
|
||||
for i := range buf {
|
||||
d.tmp[i] = buf[i][chn]
|
||||
d.tmp[i] = removeNaNsAndClamp(buf[i][chn])
|
||||
}
|
||||
// 4x oversample the signal
|
||||
var o []float32
|
||||
|
||||
@ -128,7 +128,12 @@ func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext
|
||||
rendered, timeAdvanced, err = p.synth.Render(buffer[:framesUntilEvent], timeUntilRowAdvance)
|
||||
if err != nil {
|
||||
p.synth = nil
|
||||
p.send(Alert{Message: fmt.Sprintf("synth.Render: %s", err.Error()), Priority: Error, Name: "PlayerCrash"})
|
||||
p.send(Alert{Message: fmt.Sprintf("synth.Render: %s", err.Error()), Priority: Error, Name: "PlayerCrash", Duration: defaultAlertDuration})
|
||||
}
|
||||
// for performance, we don't check for NaN of every sample, because typically NaNs propagate
|
||||
if rendered > 0 && (isNaN(buffer[0][0]) || isNaN(buffer[0][1]) || isInf(buffer[0][0]) || isInf(buffer[0][1])) {
|
||||
p.synth = nil
|
||||
p.send(Alert{Message: "Inf or NaN detected in synth output", Priority: Error, Name: "PlayerCrash", Duration: defaultAlertDuration})
|
||||
}
|
||||
} else {
|
||||
rendered = min(framesUntilEvent, timeUntilRowAdvance)
|
||||
@ -206,6 +211,14 @@ func (p NullPlayerProcessContext) BPM() (bpm float64, ok bool) {
|
||||
return 0, false // no BPM available
|
||||
}
|
||||
|
||||
func isNaN(f float32) bool {
|
||||
return f != f
|
||||
}
|
||||
|
||||
func isInf(f float32) bool {
|
||||
return f > math.MaxFloat32 || f < -math.MaxFloat32
|
||||
}
|
||||
|
||||
func (p *Player) processMessages(context PlayerProcessContext) {
|
||||
loop:
|
||||
for { // process new message
|
||||
|
||||
Reference in New Issue
Block a user