fix(vm): avoid NaNs in trisaw oscillator

This commit is contained in:
5684185+vsariola@users.noreply.github.com
2026-02-28 16:41:39 +02:00
parent 92859a5e58
commit 4d29a191c8
3 changed files with 19 additions and 13 deletions

View File

@ -269,7 +269,10 @@ su_oscillat_pulse_up:
{{- if .HasCall "su_oscillat_trisaw"}}
{{.Func "su_oscillat_trisaw"}}
fucomi st1 ; // c p
jnc short su_oscillat_trisaw_up
; we do jnbe instead of jnc to avoid NaN. phase cannot be < 0 or >= 1, so the important cases are
; c = 0 => the branch is never taken, because c > p can never happen, and thus results in .../(1-c)
; c = 1 => the branch is always taken, because p < c and thus always gives .../c
jnbe short su_oscillat_trisaw_up
fld1 ; // 1 c p
fsubr st2, st0 ; // 1 c 1-p
fsubrp st1, st0 ; // 1-c 1-p

View File

@ -481,10 +481,10 @@ func (s *GoSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, r
}
omega += float64(unit.ports[6]) // add frequency modulation
var amplitude float32
*statevar += float32(omega)
phase := float64(*statevar) + omega
if flags&0x80 == 0x80 { // if this is a sample oscillator
phase := *statevar
phase += params[2]
*statevar = float32(phase)
phase += float64(params[2])
sampleno := operandsAtTransform[3] // reuse color as the sample number
sampleoffset := s.bytecode.SampleOffsets[sampleno]
sampleindex := int(phase*84.28074964676522 + 0.5)
@ -497,22 +497,25 @@ func (s *GoSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, r
sampleindex += int(sampleoffset.Start)
amplitude = float32(int16(binary.LittleEndian.Uint16(su_sample_table[sampleindex*2:]))) / 32767.0
} else {
*statevar -= float32(int(*statevar+1) - 1)
phase := *statevar
phase += params[2]
phase -= float32(int(phase+1) - 1)
color := params[3]
// at this point, the native synth actually uses 80-bit precision, so emulate that as closely as possible by using 64-bit math here
phase += 1
phase -= float64(int(phase))
*statevar = float32(phase)
phase += float64(params[2])
phase += 1
phase -= float64(int(phase)) // this should guaranteee that phase is [0,1), so that the Trisaw should not nan even if color = 1
color := float64(params[3])
switch {
case flags&0x40 == 0x40: // Sine
if phase < color {
amplitude = float32(math.Sin(2 * math.Pi * float64(phase/color)))
amplitude = float32(math.Sin(2 * math.Pi * phase / color))
}
case flags&0x20 == 0x20: // Trisaw
if phase >= color {
if phase >= color { // since phase cannot be 1, if color = 1, then this condition never fires
phase = 1 - phase
color = 1 - color
}
amplitude = phase/color*2 - 1
amplitude = float32(phase/color*2 - 1)
case flags&0x10 == 0x10: // Pulse
if phase >= color {
amplitude = -1