mirror of
https://github.com/vsariola/sointu.git
synced 2026-02-07 16:20:21 -05:00
Rewrote most of the synth to better support stereo signals and polyphony. VSTi removed as there is no plan to update the VSTi to support the new features.
The stereo opcode variants have bit 1 of the command stream set. The polyphony is split into two parts: 1) polyphony, meaning that voices reuse the same opcodes; 2) multitrack voices, meaning that a track triggers more than voice. They both can be flexible defined in any combinations: for example voice 1 and 2 can be triggered by track 1 and use instrument 1, and voice 3 by track 2/instrument 2 and voice 4 by track 3/instrument 2. This is achieved through the use of bitmasks: in the aforementioned example, bit 1 of su_voicetrack_bitmask would be set, meaning "the voice after voice #1 will be triggered by the same track". On the other hand, bits 1 and 3 of su_polyphony_bitmask would be set to indicate that "the voices after #1 and #3 will reuse the same instruments".
This commit is contained in:
351
src/opcodes/effects.asm
Normal file
351
src/opcodes/effects.asm
Normal file
@ -0,0 +1,351 @@
|
||||
;-------------------------------------------------------------------------------
|
||||
; DISTORT Tick
|
||||
;-------------------------------------------------------------------------------
|
||||
; Input: st0 : x - input value
|
||||
; Output: st0 : x*a/(1-a+(2*a-1)*abs(x))
|
||||
; where x is clamped first
|
||||
;-------------------------------------------------------------------------------
|
||||
%if DISTORT_ID > -1
|
||||
|
||||
SECT_TEXT(sudistrt)
|
||||
|
||||
EXPORT MANGLE_FUNC(su_op_distort,0)
|
||||
%ifdef INCLUDE_STEREO_DISTORT
|
||||
jnc su_op_distort_mono
|
||||
call su_stereo_filterhelper
|
||||
%define INCLUDE_STEREO_FILTERHELPER
|
||||
su_op_distort_mono:
|
||||
%endif
|
||||
fld dword [edx+su_distort_ports.drive]
|
||||
%define SU_INCLUDE_WAVESHAPER
|
||||
; flow into waveshaper
|
||||
%endif
|
||||
|
||||
%ifdef SU_INCLUDE_WAVESHAPER
|
||||
su_waveshaper:
|
||||
fxch ; x a
|
||||
call MANGLE_FUNC(su_clip_op,0)
|
||||
fxch ; a x' (from now on just called x)
|
||||
fld st0 ; a a x
|
||||
fsub dword [c_0_5] ; a-.5 a x
|
||||
fadd st0 ; 2*a-1 a x
|
||||
fld st2 ; x 2*a-1 a x
|
||||
fabs ; abs(x) 2*a-1 a x
|
||||
fmulp st1 ; (2*a-1)*abs(x) a x
|
||||
fld1 ; 1 (2*a-1)*abs(x) a x
|
||||
faddp st1 ; 1+(2*a-1)*abs(x) a x
|
||||
fsub st1 ; 1-a+(2*a-1)*abs(x) a x
|
||||
fdivp st1, st0 ; a/(1-a+(2*a-1)*abs(x)) x
|
||||
fmulp st1 ; x*a/(1-a+(2*a-1)*abs(x))
|
||||
ret
|
||||
|
||||
%define SU_INCLUDE_CLIP
|
||||
|
||||
%endif ; SU_USE_DST
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; HOLD Tick
|
||||
;-------------------------------------------------------------------------------
|
||||
%if HOLD_ID > -1
|
||||
|
||||
SECT_TEXT(suhold)
|
||||
|
||||
EXPORT MANGLE_FUNC(su_op_hold,0)
|
||||
%ifdef INCLUDE_STEREO_HOLD
|
||||
jnc su_op_hold_mono
|
||||
call su_stereo_filterhelper
|
||||
%define INCLUDE_STEREO_FILTERHELPER
|
||||
su_op_hold_mono:
|
||||
%endif
|
||||
fld dword [edx+su_hold_ports.freq] ; f x
|
||||
fmul st0, st0 ; f^2 x
|
||||
fchs ; -f^2 x
|
||||
fadd dword [WRK+su_hold_wrk.phase] ; p-f^2 x
|
||||
fst dword [WRK+su_hold_wrk.phase] ; p <- p-f^2
|
||||
fldz ; 0 p x
|
||||
fucomip st1 ; p x
|
||||
fstp dword [esp-4] ; t=p, x
|
||||
jc short su_op_hold_holding ; if (0 < p) goto holding
|
||||
fld1 ; 1 x
|
||||
fadd dword [esp-4] ; 1+t x
|
||||
fstp dword [WRK+su_hold_wrk.phase] ; x
|
||||
fst dword [WRK+su_hold_wrk.holdval] ; save holded value
|
||||
ret ; x
|
||||
su_op_hold_holding:
|
||||
fstp st0 ;
|
||||
fld dword [WRK+su_hold_wrk.holdval] ; x
|
||||
ret
|
||||
|
||||
%endif ; HOLD_ID > -1
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_op_filter: perform low/high/band-pass filtering on the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Input: WRK : pointer to unit workspace
|
||||
; VAL : pointer to unit values as bytes
|
||||
; ecx : pointer to global workspace
|
||||
; st0 : signal
|
||||
; Output: st0 : filtered signal
|
||||
; Dirty: eax, edx
|
||||
;-------------------------------------------------------------------------------
|
||||
%if FILTER_ID > -1
|
||||
SECT_TEXT(sufilter)
|
||||
|
||||
EXPORT MANGLE_FUNC(su_op_filter,0)
|
||||
lodsb ; load the flags to al
|
||||
%ifdef INCLUDE_STEREO_FILTER
|
||||
jnc su_op_filter_mono
|
||||
call su_stereo_filterhelper
|
||||
%define INCLUDE_STEREO_FILTERHELPER
|
||||
su_op_filter_mono:
|
||||
%endif
|
||||
fld dword [edx+su_filter_ports.res] ; r x
|
||||
fld dword [edx+su_filter_ports.freq]; f r x
|
||||
fmul st0, st0 ; f2 x (square the input so we never get negative and also have a smoother behaviour in the lower frequencies)
|
||||
fst dword [esp-4] ; f2 r x
|
||||
fmul dword [WRK+su_filter_wrk.band] ; f2*b r x
|
||||
fadd dword [WRK+su_filter_wrk.low] ; f2*b+l r x
|
||||
fst dword [WRK+su_filter_wrk.low] ; l'=f2*b+l r x
|
||||
fsubp st2, st0 ; r x-l'
|
||||
fmul dword [WRK+su_filter_wrk.band] ; r*b x-l'
|
||||
fsubp st1, st0 ; x-l'-r*b
|
||||
fst dword [WRK+su_filter_wrk.high] ; h'=x-l'-r*b
|
||||
fmul dword [esp-4] ; f2*h'
|
||||
fadd dword [WRK+su_filter_wrk.band] ; f2*h'+b
|
||||
fstp dword [WRK+su_filter_wrk.band] ; b'=f2*h'+b
|
||||
fldz ; 0
|
||||
%ifdef INCLUDE_LOWPASS
|
||||
test al, byte LOWPASS
|
||||
jz short su_op_filter_skiplowpass
|
||||
fadd dword [WRK+su_filter_wrk.low]
|
||||
su_op_filter_skiplowpass:
|
||||
%endif
|
||||
%ifdef INCLUDE_BANDPASS
|
||||
test al, byte BANDPASS
|
||||
jz short su_op_filter_skipbandpass
|
||||
fadd dword [WRK+su_filter_wrk.band]
|
||||
su_op_filter_skipbandpass:
|
||||
%endif
|
||||
%ifdef INCLUDE_HIGHPASS
|
||||
test al, byte HIGHPASS
|
||||
jz short su_op_filter_skiphighpass
|
||||
fadd dword [WRK+su_filter_wrk.high]
|
||||
su_op_filter_skiphighpass:
|
||||
%endif
|
||||
%ifdef INCLUDE_NEGBANDPASS
|
||||
test al, byte NEGBANDPASS
|
||||
jz short su_op_filter_skipnegbandpass
|
||||
fsub dword [WRK+su_filter_wrk.band]
|
||||
su_op_filter_skipnegbandpass:
|
||||
%endif
|
||||
%ifdef INCLUDE_NEGHIGHPASS
|
||||
test al, byte NEGHIGHPASS
|
||||
jz short su_op_filter_skipneghighpass
|
||||
fsub dword [WRK+su_filter_wrk.high]
|
||||
su_op_filter_skipneghighpass:
|
||||
%endif
|
||||
ret
|
||||
%endif ; SU_INCLUDE_FILTER
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_clip function
|
||||
;-------------------------------------------------------------------------------
|
||||
; Input: st0 : x
|
||||
; Output: st0 : min(max(x,-1),1)
|
||||
;-------------------------------------------------------------------------------
|
||||
%if CLIP_ID > -1
|
||||
%define SU_INCLUDE_CLIP
|
||||
%endif
|
||||
|
||||
%ifdef SU_INCLUDE_CLIP
|
||||
|
||||
SECT_TEXT(suclip)
|
||||
|
||||
EXPORT MANGLE_FUNC(su_clip_op,0)
|
||||
fld1 ; 1 x a
|
||||
fucomi st1 ; if (1 <= x)
|
||||
jbe short su_clip_do ; goto Clip_Do
|
||||
fchs ; -1 x a
|
||||
fucomi st1 ; if (-1 < x)
|
||||
fcmovb st0, st1 ; x x a
|
||||
su_clip_do:
|
||||
fstp st1 ; x' a, where x' = clamp(x)
|
||||
ret
|
||||
|
||||
%endif ; SU_INCLUDE_CLIP
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; PAN Tick
|
||||
;-------------------------------------------------------------------------------
|
||||
; Input: WRK : pointer to unit workspace
|
||||
; VAL : pointer to unit values as bytes
|
||||
; ecx : pointer to global workspace
|
||||
; st0 : s, the signal
|
||||
; Output: st0 : s*(1-p), where p is the panning in [0,1] range
|
||||
; st1 : s*p
|
||||
; Dirty: eax, edx
|
||||
;-------------------------------------------------------------------------------
|
||||
%if PAN_ID > -1
|
||||
|
||||
SECT_TEXT(supan)
|
||||
|
||||
%ifdef INCLUDE_STEREO_PAN
|
||||
|
||||
EXPORT MANGLE_FUNC(su_op_pan,0)
|
||||
jc su_op_pan_do ; this time, if this is mono op...
|
||||
fld st0 ; ...we duplicate the mono into stereo first
|
||||
su_op_pan_do:
|
||||
fld dword [edx+su_pan_ports.panning] ; p l r
|
||||
fld1 ; 1 p l r
|
||||
fsub st1 ; 1-p p l r
|
||||
fmulp st2 ; p (1-p)*l r
|
||||
fmulp st2 ; (1-p)*l p*r
|
||||
ret
|
||||
|
||||
%else ; ifndef INCLUDE_STEREO_PAN
|
||||
|
||||
EXPORT MANGLE_FUNC(su_op_pan,0)
|
||||
fld dword [edx+su_pan_ports.panning] ; p s
|
||||
fmul st1 ; p*s s
|
||||
fsub st1, st0 ; p*s s-p*s
|
||||
; Equal to
|
||||
; s*p s*(1-p)
|
||||
fxch ; s*(1-p) s*p SHOULD PROBABLY DELETE, WHY BOTHER
|
||||
ret
|
||||
|
||||
%endif ; INCLUDE_STEREO_PAN
|
||||
|
||||
%endif ; SU_USE_PAN
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_stereo_filterhelper: moves the workspace to next, does the filtering for
|
||||
; right channel (pulling the calling address from stack), rewinds the
|
||||
; workspace and returns
|
||||
;-------------------------------------------------------------------------------
|
||||
%ifdef INCLUDE_STEREO_FILTERHELPER
|
||||
|
||||
su_stereo_filterhelper:
|
||||
add WRK, 16
|
||||
fxch ; r l
|
||||
call dword [esp] ; call whoever called me...
|
||||
fxch ; l r
|
||||
sub WRK, 16 ; move WRK back to where it was
|
||||
ret
|
||||
|
||||
%endif
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; Delay Tick
|
||||
;-------------------------------------------------------------------------------
|
||||
; Pseudocode:
|
||||
; q = dr*x
|
||||
; for (i = 0;i < count;i++)
|
||||
; s = b[(t-delaytime[i+offset])&65535]
|
||||
; q += s
|
||||
; o[i] = o[i]*da+s*(1-da)
|
||||
; b[t] = f*o[i] +p^2*x
|
||||
; Perform dc-filtering q and output
|
||||
;-------------------------------------------------------------------------------
|
||||
%if DELAY_ID > -1
|
||||
|
||||
SECT_TEXT(sudelay)
|
||||
|
||||
EXPORT MANGLE_FUNC(su_op_delay,0)
|
||||
lodsb ; eax = delay index
|
||||
mov edi, eax
|
||||
lodsb ; eax = delay count
|
||||
%ifdef INCLUDE_STEREO_DELAY
|
||||
jnc su_op_delay_mono
|
||||
fxch
|
||||
call su_op_delay_mono ; do right delay
|
||||
fxch
|
||||
add edi, eax ; the second delay is done with the delay time index added by count
|
||||
su_op_delay_mono:
|
||||
%endif
|
||||
pushad
|
||||
mov ebx, edi; ugly register juggling, refactor
|
||||
%ifdef DELAY_NOTE_SYNC
|
||||
test ebx, ebx ; note s
|
||||
jne su_op_delay_skipnotesync
|
||||
fld1
|
||||
fild dword [ecx+su_unit.size-su_voice.workspace+su_voice.note]
|
||||
fmul dword [c_i12]
|
||||
call MANGLE_FUNC(su_power,0)
|
||||
fmul dword [c_freq_normalize] ; // normalize
|
||||
fdivp st1, st0 ; // invert to get numer of samples
|
||||
fistp word [MANGLE_DATA(su_delay_times)] ; store current comb size
|
||||
su_op_delay_skipnotesync:
|
||||
%endif
|
||||
kmDLL_func_process:
|
||||
mov ecx, eax ;// ecx is the number of parallel delays
|
||||
mov WRK, dword [MANGLE_DATA(su_delay_buffer_ofs)] ;// ebp is current delay
|
||||
fld st0 ; x x
|
||||
fmul dword [edx+su_delay_ports.dry] ; dr*x x
|
||||
fxch ; x dr*x
|
||||
fmul dword [edx+su_delay_ports.pregain] ; p*x dr*x
|
||||
fmul dword [edx+su_delay_ports.pregain] ; p^2*x dr*x
|
||||
|
||||
kmDLL_func_loop:
|
||||
mov edi, dword [WRK + su_delayline_wrk.time]
|
||||
inc edi
|
||||
and edi, MAX_DELAY-1
|
||||
mov dword [WRK + su_delayline_wrk.time],edi
|
||||
movzx esi, word [MANGLE_DATA(su_delay_times)+ebx*2] ; esi = comb size from the delay times table
|
||||
mov eax, edi
|
||||
sub eax, esi
|
||||
and eax, MAX_DELAY-1
|
||||
fld dword [WRK+eax*4+su_delayline_wrk.buffer] ; s p^2*x dr*x, where s is the sample from delay buffer
|
||||
;// add comb output to current output
|
||||
fadd st2, st0 ; s p^2*x dr*x+s
|
||||
fld1 ; 1 s p^2*x dr*x+s
|
||||
fsub dword [edx+su_delay_ports.damp] ; 1-da s p^2*x dr*x+s
|
||||
fmulp st1, st0 ; s*(1-da) p^2*x dr*x+s
|
||||
fld dword [edx+su_delay_ports.damp] ; da s*(1-da) p^2*x dr*x+s
|
||||
fmul dword [WRK+su_delayline_wrk.filtstate] ; o*da s*(1-da) p^2*x dr*x+s, where o is stored
|
||||
faddp st1, st0 ; o*da+s*(1-da) p^2*x dr*x+s
|
||||
fst dword [WRK+su_delayline_wrk.filtstate] ; o'=o*da+s*(1-da), o' p^2*x dr*x+s
|
||||
fmul dword [edx+su_delay_ports.feedback] ; f*o' p^2*x dr*x+s
|
||||
fadd st0, st1 ; f*o'+p^2*x p^2*x dr*x+s
|
||||
fstp dword [WRK+edi*4+su_delayline_wrk.buffer]; save f*o'+p^2*x to delay buffer
|
||||
inc ebx ;// go to next delay lenkmh index
|
||||
add WRK, su_delayline_wrk.size ;// go to next delay
|
||||
mov dword [MANGLE_DATA(su_delay_buffer_ofs)], WRK ;// store next delay offset
|
||||
loopne kmDLL_func_loop
|
||||
fstp st0 ; dr*x+s1+s2+s3+...
|
||||
; DC-filtering
|
||||
sub WRK, su_delayline_wrk.size ; the reason to use the last su_delayline_wrk instead of su_delay_wrk is that su_delay_wrk is wiped by retriggering
|
||||
fld dword [WRK+su_delayline_wrk.dcout] ; o s
|
||||
fmul dword [c_dc_const] ; c*o s
|
||||
fsub dword [WRK+su_delayline_wrk.dcin] ; c*o-i s
|
||||
fxch ; s c*o-i
|
||||
fst dword [WRK+su_delayline_wrk.dcin] ; i'=s, s c*o-i
|
||||
faddp st1 ; s+c*o-i
|
||||
fadd dword [c_0_5] ;// add and sub small offset to prevent denormalization
|
||||
fsub dword [c_0_5]
|
||||
fst dword [WRK+su_delayline_wrk.dcout] ; o'=s+c*o-i
|
||||
popad
|
||||
ret
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; Delay data
|
||||
;-------------------------------------------------------------------------------
|
||||
SECT_BSS(sudelbuf)
|
||||
|
||||
EXPORT MANGLE_DATA(su_delay_buffer_ofs)
|
||||
resd 1
|
||||
EXPORT MANGLE_DATA(su_delay_buffer)
|
||||
resb NUM_DELAY_LINES*su_delayline_wrk.size
|
||||
|
||||
SECT_DATA(suconst)
|
||||
|
||||
%ifndef C_DC_CONST
|
||||
c_dc_const dd 0.99609375 ; R = 1 - (pi*2 * frequency /samplerate)
|
||||
%define C_DC_CONST
|
||||
%endif
|
||||
|
||||
%ifndef C_FREQ_NORMALIZE
|
||||
c_freq_normalize dd 0.000092696138 ; // 220.0/(2^(69/12)) / 44100.0
|
||||
%define C_FREQ_NORMALIZE
|
||||
%endif
|
||||
|
||||
%endif ; DELAY_ID > -1
|
||||
Reference in New Issue
Block a user