sointu/vm/compiler/templates/amd64-386/sources.asm
5684185+vsariola@users.noreply.github.com 1ac2ad3c75 fix(vm/compiler): invert the logic of the release flag in the voices (closes #102)
This makes all envelopes released by default, instead of attacking. Add also test to demonstrate the buggy behaviour.
2023-09-23 15:56:46 +03:00

432 lines
19 KiB
NASM

{{if .HasOp "envelope" -}}
;-------------------------------------------------------------------------------
; ENVELOPE opcode: pushes an ADSR envelope value on stack [0,1]
;-------------------------------------------------------------------------------
; Mono: push the envelope value on stack
; Stereo: push the envelope valeu on stack twice
;-------------------------------------------------------------------------------
{{.Func "su_op_envelope" "Opcode"}}
{{- if .StereoAndMono "envelope"}}
jnc su_op_envelope_mono
{{- end}}
{{- if .Stereo "envelope"}}
call su_op_envelope_mono
fld st0
ret
su_op_envelope_mono:
{{- end}}
mov eax, dword [{{.INP}}-su_voice.inputs+su_voice.sustain] ; eax = su_instrument.sustain
test eax, eax ; if (eax != 0)
jne su_op_envelope_process ; goto process
mov al, {{.InputNumber "envelope" "release"}} ; [state]=RELEASE
mov dword [{{.WRK}}], eax ; note that mov al, XXX; mov ..., eax is less bytes than doing it directly
su_op_envelope_process:
mov eax, dword [{{.WRK}}] ; al=[state]
fld dword [{{.WRK}}+4] ; x=[level]
cmp al, {{.InputNumber "envelope" "sustain"}} ; if (al==SUSTAIN)
je short su_op_envelope_leave2 ; goto leave2
su_op_envelope_attac:
cmp al, {{.InputNumber "envelope" "attack"}} ; if (al!=ATTAC)
jne short su_op_envelope_decay ; goto decay
{{.Call "su_nonlinear_map"}} ; a x, where a=attack
faddp st1, st0 ; a+x
fld1 ; 1 a+x
fucomi st1 ; if (a+x<=1) // is attack complete?
fcmovnb st0, st1 ; a+x a+x
jbe short su_op_envelope_statechange ; else goto statechange
su_op_envelope_decay:
cmp al, {{.InputNumber "envelope" "decay"}} ; if (al!=DECAY)
jne short su_op_envelope_release ; goto release
{{.Call "su_nonlinear_map"}} ; d x, where d=decay
fsubp st1, st0 ; x-d
fld dword [{{.Input "envelope" "sustain"}}] ; s x-d, where s=sustain
fucomi st1 ; if (x-d>s) // is decay complete?
fcmovb st0, st1 ; x-d x-d
jnc short su_op_envelope_statechange ; else goto statechange
su_op_envelope_release:
cmp al, {{.InputNumber "envelope" "release"}} ; if (al!=RELEASE)
jne short su_op_envelope_leave ; goto leave
{{.Call "su_nonlinear_map"}} ; r x, where r=release
fsubp st1, st0 ; x-r
fldz ; 0 x-r
fucomi st1 ; if (x-r>0) // is release complete?
fcmovb st0, st1 ; x-r x-r, then goto leave
jc short su_op_envelope_leave
su_op_envelope_statechange:
inc dword [{{.WRK}}] ; [state]++
su_op_envelope_leave:
fstp st1 ; x', where x' is the new value
fst dword [{{.WRK}}+4] ; [level]=x'
su_op_envelope_leave2:
fmul dword [{{.Input "envelope" "gain"}}] ; [gain]*x'
ret
{{end}}
{{- if .HasOp "noise"}}
;-------------------------------------------------------------------------------
; NOISE opcode: creates noise
;-------------------------------------------------------------------------------
; Mono: push a random value [-1,1] value on stack
; Stereo: push two (differeent) random values on stack
;-------------------------------------------------------------------------------
{{.Func "su_op_noise" "Opcode"}}
lea {{.CX}},[{{.Stack "RandSeed"}}]
{{- if .StereoAndMono "noise"}}
jnc su_op_noise_mono
{{- end}}
{{- if .Stereo "noise"}}
call su_op_noise_mono
su_op_noise_mono:
{{- end}}
imul eax, [{{.CX}}],16007
mov [{{.CX}}],eax
fild dword [{{.CX}}]
{{- .Prepare (.Int 2147483648)}}
fidiv dword [{{.Use (.Int 2147483648)}}] ; 65536*32768
fld dword [{{.Input "noise" "shape"}}]
{{.Call "su_waveshaper"}}
fmul dword [{{.Input "noise" "gain"}}]
ret
{{end}}
{{- if .HasOp "oscillator"}}
;-------------------------------------------------------------------------------
; OSCILLAT opcode: oscillator, the heart of the synth
;-------------------------------------------------------------------------------
; Mono: push oscillator value on stack
; Stereo: push l r on stack, where l has opposite detune compared to r
;-------------------------------------------------------------------------------
{{.Func "su_op_oscillator" "Opcode"}}
lodsb ; load the flags
{{- if .Library}}
mov {{.DI}}, [{{.Stack "SampleTable"}}]; we need to put this in a register, as the stereo & unisons screw the stack positions
; ain't we lucky that {{.DI}} was unused throughout
{{- end}}
fld dword [{{.Input "oscillator" "detune"}}] ; e, where e is the detune [0,1]
{{- .Prepare (.Float 0.5)}}
fsub dword [{{.Use (.Float 0.5)}}] ; e-.5
fadd st0, st0 ; d=2*e-.5, where d is the detune [-1,1]
{{- if .StereoAndMono "oscillator"}}
jnc su_op_oscillat_mono
{{- end}}
{{- if .Stereo "oscillator"}}
fld st0 ; d d
add {{.WRK}}, 4 ; move wrk...
call su_op_oscillat_mono ; r d
sub {{.WRK}}, 4 ; ...restore wrk
fxch ; d r
fchs ; -d r, negate the detune for second round
su_op_oscillat_mono:
{{- end}}
{{- if .SupportsParamValueOtherThan "oscillator" "unison" 0}}
{{.PushRegs .AX "" .WRK "OscWRK" .AX "OscFlags"}}
fldz ; 0 d
fxch ; d a=0, "accumulated signal"
su_op_oscillat_unison_loop:
fst dword [{{.SP}}] ; save the current detune, d. We could keep it in fpu stack but it was getting big.
call su_op_oscillat_single ; s a
faddp st1, st0 ; a+=s
test al, 3
je su_op_oscillat_unison_out
add {{.WRK}}, 8 ; this is ok after all, as there's a pop in the end of unison loop
fld dword [{{.Input "oscillator" "phase"}}] ; p s
{{.Int 0x3DAAAAAA | .Prepare}}
fadd dword [{{.Int 0x3DAAAAAA | .Use}}] ; 1/12 p s, add some little phase offset to unison oscillators so they don't start in sync
fstp dword [{{.Input "oscillator" "phase"}}] ; s note that this changes the phase for second, possible stereo run. That's probably ok
fld dword [{{.SP}}] ; d s
{{.Float 0.5 | .Prepare}}
fmul dword [{{.Float 0.5 | .Use}}] ; .5*d s // negate and halve the detune of each oscillator
fchs ; -.5*d s // negate and halve the detune of each oscillator
dec eax
jmp short su_op_oscillat_unison_loop
su_op_oscillat_unison_out:
{{.PopRegs .AX .WRK .AX}}
ret
su_op_oscillat_single:
{{- end}}
fld dword [{{.Input "oscillator" "transpose"}}]
{{- .Float 0.5 | .Prepare}}
fsub dword [{{.Float 0.5 | .Use}}]
{{- .Float 0.0078125 | .Prepare}}
fdiv dword [{{.Float 0.0078125 | .Use}}]
faddp st1
{{- if .SupportsParamValue "oscillator" "lfo" 1}}
test al, byte 0x08
jnz su_op_oscillat_skipnote
{{- end}}
fiadd dword [{{.INP}}-su_voice.inputs+su_voice.note] ; // st0 is note, st1 is t+d offset
{{- if .SupportsParamValue "oscillator" "lfo" 1}}
su_op_oscillat_skipnote:
{{- end}}
{{- .Int 0x3DAAAAAA | .Prepare}}
fmul dword [{{.Int 0x3DAAAAAA | .Use}}]
{{.Call "su_power"}}
{{- if .SupportsParamValue "oscillator" "lfo" 1}}
test al, byte 0x08
jz short su_op_oscillat_normalize_note
{{- .Float 0.000038 | .Prepare}}
fmul dword [{{.Float 0.000038 | .Use}}] ; // st0 is now frequency for lfo
jmp short su_op_oscillat_normalized
su_op_oscillat_normalize_note:
{{- end}}
{{- .Float 0.000092696138 | .Prepare}}
fmul dword [{{.Float 0.000092696138 | .Use}}] ; // st0 is now frequency
su_op_oscillat_normalized:
fadd dword [{{.WRK}}]
{{- if .SupportsParamValue "oscillator" "type" .Sample}}
test al, byte 0x80
jz short su_op_oscillat_not_sample
fst dword [{{.WRK}}] ; for samples, we store the phase without mod(p,1)
{{- if or (.SupportsParamValueOtherThan "oscillator" "phase" 0) (.SupportsModulation "oscillator" "phase")}}
fadd dword [{{.Input "oscillator" "phase"}}]
{{- end}}
{{.Call "su_oscillat_sample"}}
jmp su_op_oscillat_shaping ; skip the rest to avoid color phase normalization and colorloading
su_op_oscillat_not_sample:
{{- end}}
fld1 ; we need to take mod(p,1) so the frequency does not drift as the float
fadd st1, st0 ; make no mistake: without this, there is audible drifts in oscillator pitch
fxch ; as the actual period changes once the phase becomes too big
fprem ; we actually computed mod(p+1,1) instead of mod(p,1) as the fprem takes mod
fstp st1 ; towards zero
fst dword [{{.WRK}}] ; store back the updated phase
{{- if or (.SupportsParamValueOtherThan "oscillator" "phase" 0) (.SupportsModulation "oscillator" "phase")}}
fadd dword [{{.Input "oscillator" "phase"}}]
fld1 ; this is a bit stupid, but we need to take mod(x,1) again after phase modulations
fadd st1, st0 ; as the actual oscillator functions expect x in [0,1]
fxch
fprem
fstp st1
{{- end}}
fld dword [{{.Input "oscillator" "color"}}] ; // c p
; every oscillator test included if needed
{{- if .SupportsParamValue "oscillator" "type" .Sine}}
test al, byte 0x40
jz short su_op_oscillat_notsine
{{.Call "su_oscillat_sine"}}
su_op_oscillat_notsine:
{{- end}}
{{- if .SupportsParamValue "oscillator" "type" .Trisaw}}
test al, byte 0x20
jz short su_op_oscillat_not_trisaw
{{.Call "su_oscillat_trisaw"}}
su_op_oscillat_not_trisaw:
{{- end}}
{{- if .SupportsParamValue "oscillator" "type" .Pulse}}
test al, byte 0x10
jz short su_op_oscillat_not_pulse
{{.Call "su_oscillat_pulse"}}
su_op_oscillat_not_pulse:
{{- end}}
{{- if .SupportsParamValue "oscillator" "type" .Gate}}
test al, byte 0x04
jz short su_op_oscillat_not_gate
{{.Call "su_oscillat_gate"}}
jmp su_op_oscillat_gain ; skip waveshaping as the shape parameter is reused for gateshigh
su_op_oscillat_not_gate:
{{- end}}
su_op_oscillat_shaping:
; finally, shape the oscillator and apply gain
fld dword [{{.Input "oscillator" "shape"}}]
{{.Call "su_waveshaper"}}
su_op_oscillat_gain:
fmul dword [{{.Input "oscillator" "gain"}}]
ret
{{end}}
{{- if .HasCall "su_oscillat_pulse"}}
{{.Func "su_oscillat_pulse"}}
fucomi st1 ; // c p
fld1
jnc short su_oscillat_pulse_up ; // +1 c p
fchs ; // -1 c p
su_oscillat_pulse_up:
fstp st1 ; // +-1 p
fstp st1 ; // +-1
ret
{{end}}
{{- if .HasCall "su_oscillat_trisaw"}}
{{.Func "su_oscillat_trisaw"}}
fucomi st1 ; // c p
jnc short su_oscillat_trisaw_up
fld1 ; // 1 c p
fsubr st2, st0 ; // 1 c 1-p
fsubrp st1, st0 ; // 1-c 1-p
su_oscillat_trisaw_up:
fdivp st1, st0 ; // tp'/tc
fadd st0 ; // 2*''
fld1 ; // 1 2*''
fsubp st1, st0 ; // 2*''-1
ret
{{end}}
{{- if .HasCall "su_oscillat_sine"}}
{{.Func "su_oscillat_sine"}}
fucomi st1 ; // c p
jnc short su_oscillat_sine_do
fstp st1
fsub st0, st0 ; // 0
ret
su_oscillat_sine_do:
fdivp st1, st0 ; // p/c
fldpi ; // pi p
fadd st0 ; // 2*pi p
fmulp st1, st0 ; // 2*pi*p
fsin ; // sin(2*pi*p)
ret
{{end}}
{{- if .HasCall "su_oscillat_gate"}}
{{.Func "su_oscillat_gate"}}
fxch ; p c
fstp st1 ; p
{{- .Float 16.0 | .Prepare | indent 4}}
fmul dword [{{.Float 16.0 | .Use}}] ; 16*p
push {{.AX}}
push {{.AX}}
fistp dword [{{.SP}}] ; s=int(16*p), stack empty
fld1 ; 1
pop {{.AX}}
and al, 0xf ; ax=int(16*p) & 15, stack: 1
bt word [{{.VAL}}-4],ax ; if bit ax of the gate word is set
jc su_oscillat_gate_bit ; goto gate_bit
fsub st0, st0 ; stack: 0
su_oscillat_gate_bit: ; stack: 0/1, let's call it x
fld dword [{{.WRK}}+16] ; g x, g is gatestate, x is the input to this filter 0/1
fsub st1 ; g-x x
{{- .Float 0.99609375 | .Prepare | indent 4}}
fmul dword [{{.Float 0.99609375 | .Use}}] ; c(g-x) x
faddp st1, st0 ; x+c(g-x)
fst dword [{{.WRK}}+16]; g'=x+c(g-x) NOTE THAT UNISON 2 & UNISON 3 ALSO USE {{.WRK}}+16, so gate and unison 2 & 3 don't work. Probably should delete that low pass altogether
pop {{.AX}} ; Another way to see this (c~0.996)
ret ; g'=cg+(1-c)x
; This is a low-pass to smooth the gate transitions
{{end}}
{{- if .HasCall "su_oscillat_sample"}}
{{.Func "su_oscillat_sample"}}
{{- .PushRegs .AX "SampleAx" .DX "SampleDx" .CX "SampleCx" .BX "SampleBx" .DI "SampleDi" | indent 4}} ; edx must be saved, eax & ecx if this is stereo osc
push {{.AX}}
mov al, byte [{{.VAL}}-4] ; reuse "color" as the sample number
{{- if .Library}}
lea {{.DI}}, [{{.DI}} + {{.AX}}*8] ; edi points now to the sample table entry
{{- else}}
{{- .Prepare "su_sample_offsets" | indent 4}}
lea {{.DI}}, [{{.Use "su_sample_offsets"}} + {{.AX}}*8]; edi points now to the sample table entry
{{- end}}
{{- .Float 84.28074964676522 | .Prepare | indent 4}}
fmul dword [{{.Float 84.28074964676522 | .Use}}] ; p*r
fistp dword [{{.SP}}]
pop {{.DX}} ; edx is now the sample number
movzx ebx, word [{{.DI}} + 4] ; ecx = loopstart
sub edx, ebx ; if sample number < loop start
jl su_oscillat_sample_not_looping ; then we're not looping yet
mov eax, edx ; eax = sample number
movzx ecx, word [{{.DI}} + 6] ; edi is now the loop length
xor edx, edx ; div wants edx to be empty
div ecx ; edx is now the remainder
su_oscillat_sample_not_looping:
add edx, ebx ; sampleno += loopstart
add edx, dword [{{.DI}}]
{{- .Prepare "su_sample_table" | indent 4}}
fild word [{{.Use "su_sample_table"}} + {{.DX}}*2]
{{- .Float 32767.0 | .Prepare | indent 4}}
fdiv dword [{{.Float 32767.0 | .Use}}]
{{- .PopRegs .AX .DX .CX .BX .DI | indent 4}}
ret
{{end}}
{{- if .HasOp "loadval"}}
;-------------------------------------------------------------------------------
; LOADVAL opcode
;-------------------------------------------------------------------------------
{{- if .Mono "loadval"}}
; Mono: push 2*v-1 on stack, where v is the input to port "value"
{{- end}}
{{- if .Stereo "loadval"}}
; Stereo: push 2*v-1 twice on stack
{{- end}}
;-------------------------------------------------------------------------------
{{.Func "su_op_loadval" "Opcode"}}
{{- if .StereoAndMono "loadval" }}
jnc su_op_loadval_mono
{{- end}}
{{- if .Stereo "loadval" }}
call su_op_loadval_mono
su_op_loadval_mono:
{{- end }}
fld dword [{{.Input "loadval" "value"}}] ; v
{{- .Float 0.5 | .Prepare | indent 4}}
fsub dword [{{.Float 0.5 | .Use}}]
fadd st0 ; 2*v-1
ret
{{end}}
{{- if .HasOp "receive"}}
;-------------------------------------------------------------------------------
; RECEIVE opcode
;-------------------------------------------------------------------------------
{{- if .Mono "receive"}}
; Mono: push l on stack, where l is the left channel received
{{- end}}
{{- if .Stereo "receive"}}
; Stereo: push l r on stack
{{- end}}
;-------------------------------------------------------------------------------
{{.Func "su_op_receive" "Opcode"}}
lea {{.DI}}, [{{.WRK}}+su_unit.ports]
{{- if .StereoAndMono "receive"}}
jnc su_op_receive_mono
{{- end}}
{{- if .Stereo "receive"}}
xor ecx,ecx
fld dword [{{.DI}}+4]
mov dword [{{.DI}}+4],ecx
{{- end}}
{{- if .StereoAndMono "receive"}}
su_op_receive_mono:
xor ecx,ecx
{{- end}}
fld dword [{{.DI}}]
mov dword [{{.DI}}],ecx
ret
{{end}}
{{- if .HasOp "in"}}
;-------------------------------------------------------------------------------
; IN opcode: inputs and clears a global port
;-------------------------------------------------------------------------------
; Mono: push the left channel of a global port (out or aux)
; Stereo: also push the right channel (stack in l r order)
;-------------------------------------------------------------------------------
{{.Func "su_op_in" "Opcode"}}
lodsb
mov {{.DI}}, [{{.Stack "Synth"}}]
{{- if .StereoAndMono "in"}}
jnc su_op_in_mono
{{- end}}
{{- if .Stereo "in"}}
xor ecx, ecx ; we cannot xor before jnc, so we have to do it mono & stereo. LAHF / SAHF could do it, but is the same number of bytes with more entropy
fld dword [{{.DI}} + su_synthworkspace.right + {{.AX}}*4]
mov dword [{{.DI}} + su_synthworkspace.right + {{.AX}}*4], ecx
{{- end}}
{{- if .StereoAndMono "in"}}
su_op_in_mono:
xor ecx, ecx
{{- end}}
fld dword [{{.DI}} + su_synthworkspace.left + {{.AX}}*4]
mov dword [{{.DI}} + su_synthworkspace.left + {{.AX}}*4], ecx
ret
{{end}}