;------------------------------------------------------------------------------- ; 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 su_clip 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 ;------------------------------------------------------------------------------- ; GAIN Tick ;------------------------------------------------------------------------------- %if GAIN_ID > -1 SECT_TEXT(sugain) EXPORT MANGLE_FUNC(su_op_gain,0) %ifdef INCLUDE_STEREO_GAIN jnc su_op_gain_mono call su_stereo_filterhelper %define INCLUDE_STEREO_FILTERHELPER su_op_gain_mono: %endif fld dword [edx+su_gain_ports.gain] ; g x fmulp st1, st0 ; g*x ret %endif ; GAIN_ID > -1 ;------------------------------------------------------------------------------- ; INVGAIN Tick ;------------------------------------------------------------------------------- %if INVGAIN_ID > -1 SECT_TEXT(suingain) EXPORT MANGLE_FUNC(su_op_invgain,0) %ifdef INCLUDE_STEREO_INVGAIN jnc su_op_invgain_mono call su_stereo_filterhelper %define INCLUDE_STEREO_FILTERHELPER su_op_invgain_mono: %endif fld dword [edx+su_invgain_ports.invgain] ; g x fdivp st1, st0 ; x/g ret %endif ; INVINVGAIN_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) ;------------------------------------------------------------------------------- SECT_TEXT(suclip) %if CLIP_ID > -1 EXPORT MANGLE_FUNC(su_op_clip,0) %ifdef INCLUDE_STEREO_CLIP jnc su_op_clip_mono call su_stereo_filterhelper %define INCLUDE_STEREO_FILTERHELPER su_op_clip_mono: %endif %define SU_INCLUDE_CLIP ; flow into su_doclip %endif ; CLIP_ID > -1 %ifdef SU_INCLUDE_CLIP su_clip: 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