mirror of
https://github.com/vsariola/sointu.git
synced 2025-11-13 13:22:51 -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
@ -3,8 +3,12 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## [0.5.0]
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- Panic the synth if it outputs NaN or Inf, and handle these more gracefully in
|
||||||
|
the loudness and peak detector. ([#210][i210])
|
||||||
|
|
||||||
|
## [0.5.0]
|
||||||
### BREAKING CHANGES
|
### BREAKING CHANGES
|
||||||
- BREAKING CHANGE: always first modulate delay time, then apply notetracking. In
|
- BREAKING CHANGE: always first modulate delay time, then apply notetracking. In
|
||||||
a delay unit, modulation adds to the delay time, while note tracking
|
a delay unit, modulation adds to the delay time, while note tracking
|
||||||
@ -368,3 +372,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
[i192]: https://github.com/vsariola/sointu/issues/192
|
[i192]: https://github.com/vsariola/sointu/issues/192
|
||||||
[i196]: https://github.com/vsariola/sointu/issues/196
|
[i196]: https://github.com/vsariola/sointu/issues/196
|
||||||
[i200]: https://github.com/vsariola/sointu/issues/200
|
[i200]: https://github.com/vsariola/sointu/issues/200
|
||||||
|
[i210]: https://github.com/vsariola/sointu/issues/210
|
||||||
|
|||||||
@ -74,6 +74,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const MAX_INTEGRATED_DATA = 10 * 60 * 60 // 1 hour of samples at 10 Hz (100 ms per sample)
|
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 (
|
const (
|
||||||
PeakMomentary PeakType = iota
|
PeakMomentary PeakType = iota
|
||||||
@ -232,7 +237,7 @@ func (d *loudnessDetector) update(chunk sointu.AudioBuffer) LoudnessResult {
|
|||||||
for chn := range 2 {
|
for chn := range 2 {
|
||||||
// deinterleave the channels
|
// deinterleave the channels
|
||||||
for i := range chunk {
|
for i := range chunk {
|
||||||
d.tmp[i] = chunk[i][chn]
|
d.tmp[i] = removeNaNsAndClamp(chunk[i][chn])
|
||||||
}
|
}
|
||||||
// filter the signal with the weighting filter
|
// filter the signal with the weighting filter
|
||||||
for k := range d.weighting {
|
for k := range d.weighting {
|
||||||
@ -287,6 +292,13 @@ func (d *loudnessDetector) reset() {
|
|||||||
d.integratedPower = 0
|
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 {
|
func powerToDecibel(power float32) Decibel {
|
||||||
return Decibel(float32(10 * math.Log10(float64(power))))
|
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 {
|
for chn := range 2 {
|
||||||
// deinterleave the channels
|
// deinterleave the channels
|
||||||
for i := range buf {
|
for i := range buf {
|
||||||
d.tmp[i] = buf[i][chn]
|
d.tmp[i] = removeNaNsAndClamp(buf[i][chn])
|
||||||
}
|
}
|
||||||
// 4x oversample the signal
|
// 4x oversample the signal
|
||||||
var o []float32
|
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)
|
rendered, timeAdvanced, err = p.synth.Render(buffer[:framesUntilEvent], timeUntilRowAdvance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.synth = 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 {
|
} else {
|
||||||
rendered = min(framesUntilEvent, timeUntilRowAdvance)
|
rendered = min(framesUntilEvent, timeUntilRowAdvance)
|
||||||
@ -206,6 +211,14 @@ func (p NullPlayerProcessContext) BPM() (bpm float64, ok bool) {
|
|||||||
return 0, false // no BPM available
|
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) {
|
func (p *Player) processMessages(context PlayerProcessContext) {
|
||||||
loop:
|
loop:
|
||||||
for { // process new message
|
for { // process new message
|
||||||
|
|||||||
Reference in New Issue
Block a user