mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-18 21:14:31 -04:00
feat(asm&go4k): Preprocess asm code using go text/template
The preprocessing is done sointu-cli and (almost) nothing is done by the NASM preprocessor anymore (some .strucs are still there. Now, sointu-cli loads the .yml song, defines bunch of macros (go functions / variables) and passes the struct to text/template parses. This a lot more powerful way to generate .asm code than trying to fight with the nasm preprocessor. At the moment, tests pass but the repository is a bit of monster, as the library is still compiled using the old approach. Go should generate the library also from the templates.
This commit is contained in:
205
templates/arithmetic.asm
Normal file
205
templates/arithmetic.asm
Normal file
@ -0,0 +1,205 @@
|
||||
{{- if .Opcode "pop"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; POP opcode: remove (discard) the topmost signal from the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
{{- if .Mono "pop" -}}
|
||||
; Mono: a -> (empty)
|
||||
{{- end}}
|
||||
{{- if .Stereo "pop" -}}
|
||||
; Stereo: a b -> (empty)
|
||||
{{- end}}
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_pop" "Opcode"}}
|
||||
{{- if .StereoAndMono "pop"}}
|
||||
jnc su_op_pop_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "pop"}}
|
||||
fstp st0
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "pop"}}
|
||||
su_op_pop_mono:
|
||||
{{- end}}
|
||||
fstp st0
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "add"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; ADD opcode: add the two top most signals on the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
{{- if .Mono "add"}}
|
||||
; Mono: a b -> a+b b
|
||||
{{- end}}
|
||||
{{- if .Stereo "add" -}}
|
||||
; Stereo: a b c d -> a+c b+d c d
|
||||
{{- end}}
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_add" "Opcode"}}
|
||||
{{- if .StereoAndMono "add"}}
|
||||
jnc su_op_add_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "add"}}
|
||||
fadd st0, st2
|
||||
fxch
|
||||
fadd st0, st3
|
||||
fxch
|
||||
ret
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "add"}}
|
||||
su_op_add_mono:
|
||||
{{- end}}
|
||||
{{- if .Mono "add"}}
|
||||
fadd st1
|
||||
{{- end}}
|
||||
{{- if .Mono "add"}}
|
||||
ret
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "addp"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; ADDP opcode: add the two top most signals on the stack and pop
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: a b -> a+b
|
||||
; Stereo: a b c d -> a+c b+d
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_addp" "Opcode"}}
|
||||
{{- if .StereoAndMono "addp"}}
|
||||
jnc su_op_addp_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "addp"}}
|
||||
faddp st2, st0
|
||||
faddp st2, st0
|
||||
ret
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "addp"}}
|
||||
su_op_addp_mono:
|
||||
{{- end}}
|
||||
{{- if (.Mono "addp")}}
|
||||
faddp st1, st0
|
||||
ret
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "loadnote"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; LOADNOTE opcode: load the current note, scaled to [-1,1]
|
||||
;-------------------------------------------------------------------------------
|
||||
{{if (.Mono "loadnote") -}} ; Mono: (empty) -> n, where n is the note{{end}}
|
||||
{{if (.Stereo "loadnote") -}}; Stereo: (empty) -> n n{{end}}
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_loadnote" "Opcode"}}
|
||||
{{- if .StereoAndMono "loadnote"}}
|
||||
jnc su_op_loadnote_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "loadnote"}}
|
||||
call su_op_loadnote_mono
|
||||
su_op_loadnote_mono:
|
||||
{{- end}}
|
||||
fild dword [{{.INP}}-su_voice.inputs+su_voice.note]
|
||||
{{.Prepare (.Float 0.0078125)}}
|
||||
fmul dword [{{.Use (.Float 0.0078125)}}] ; s=n/128.0
|
||||
{{.Prepare (.Float 0.5)}}
|
||||
fsub dword [{{.Use (.Float 0.5)}}] ; s-.5
|
||||
fadd st0, st0 ; 2*s-1
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "mul"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; MUL opcode: multiply the two top most signals on the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: a b -> a*b a
|
||||
; Stereo: a b c d -> a*c b*d c d
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_mul" "Opcode"}}
|
||||
jnc su_op_mul_mono
|
||||
fmul st0, st2
|
||||
fxch
|
||||
fadd st0, st3
|
||||
fxch
|
||||
ret
|
||||
su_op_mul_mono:
|
||||
fmul st1
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "mulp"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; MULP opcode: multiply the two top most signals on the stack and pop
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: a b -> a*b
|
||||
; Stereo: a b c d -> a*c b*d
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_mulp" "Opcode"}}
|
||||
{{- if .StereoAndMono "mulp"}}
|
||||
jnc su_op_mulp_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "mulp"}}
|
||||
fmulp st2, st0
|
||||
fmulp st2, st0
|
||||
ret
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "mulp"}}
|
||||
su_op_mulp_mono:
|
||||
{{- end}}
|
||||
{{- if .Mono "mulp"}}
|
||||
fmulp st1
|
||||
ret
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "push"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; PUSH opcode: push the topmost signal on the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: a -> a a
|
||||
; Stereo: a b -> a b a b
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_push" "Opcode"}}
|
||||
{{- if .StereoAndMono "push"}}
|
||||
jnc su_op_push_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "push"}}
|
||||
fld st1
|
||||
fld st1
|
||||
ret
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "push"}}
|
||||
su_op_push_mono:
|
||||
{{- end}}
|
||||
{{- if .Mono "push"}}
|
||||
fld st0
|
||||
ret
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "xch"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; XCH opcode: exchange the signals on the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: a b -> b a
|
||||
; stereo: a b c d -> c d a b
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_xch" "Opcode"}}
|
||||
{{- if .StereoAndMono "xch"}}
|
||||
jnc su_op_xch_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "xch"}}
|
||||
fxch st0, st2 ; c b a d
|
||||
fxch st0, st1 ; b c a d
|
||||
fxch st0, st3 ; d c a b
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "xch"}}
|
||||
su_op_xch_mono:
|
||||
{{- end}}
|
||||
fxch st0, st1
|
||||
ret
|
||||
{{end}}
|
393
templates/effects.asm
Normal file
393
templates/effects.asm
Normal file
@ -0,0 +1,393 @@
|
||||
{{- if .Opcode "distort"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; DISTORT opcode: apply distortion on the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: x -> x*a/(1-a+(2*a-1)*abs(x)) where x is clamped first
|
||||
; Stereo: l r -> l*a/(1-a+(2*a-1)*abs(l)) r*a/(1-a+(2*a-1)*abs(r))
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_distort" "Opcode"}}
|
||||
{{- if .Stereo "distort" -}}
|
||||
{{.Call "su_effects_stereohelper"}}
|
||||
{{- end}}
|
||||
fld dword [{{.Input "distort" "drive"}}]
|
||||
{{.TailCall "su_waveshaper"}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "hold"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; HOLD opcode: sample and hold the signal, reducing sample rate
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono version: holds the signal at a rate defined by the freq parameter
|
||||
; Stereo version: holds both channels
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_hold" "Opcode"}}
|
||||
{{- if .Stereo "hold"}}
|
||||
{{.Call "su_effects_stereohelper"}}
|
||||
{{- end}}
|
||||
fld dword [{{.Input "hold" "holdfreq"}}] ; f x
|
||||
fmul st0, st0 ; f^2 x
|
||||
fchs ; -f^2 x
|
||||
fadd dword [{{.WRK}}] ; p-f^2 x
|
||||
fst dword [{{.WRK}}] ; p <- p-f^2
|
||||
fldz ; 0 p x
|
||||
fucomip st1 ; p x
|
||||
fstp dword [{{.SP}}-4] ; t=p, x
|
||||
jc short su_op_hold_holding ; if (0 < p) goto holding
|
||||
fld1 ; 1 x
|
||||
fadd dword [{{.SP}}-4] ; 1+t x
|
||||
fstp dword [{{.WRK}}] ; x
|
||||
fst dword [{{.WRK}}+4] ; save holded value
|
||||
ret ; x
|
||||
su_op_hold_holding:
|
||||
fstp st0 ;
|
||||
fld dword [{{.WRK}}+4] ; x
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "crush"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; CRUSH opcode: quantize the signal to finite number of levels
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: x -> e*int(x/e)
|
||||
; Stereo: l r -> e*int(l/e) e*int(r/e)
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_crush" "Opcode"}}
|
||||
{{- if .Stereo "crush"}}
|
||||
{{.Call "su_effects_stereohelper"}}
|
||||
{{- end}}
|
||||
fdiv dword [{{.Input "crush" "resolution"}}]
|
||||
frndint
|
||||
fmul dword [{{.Input "crush" "resolution"}}]
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "gain"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; GAIN opcode: apply gain on the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: x -> x*g
|
||||
; Stereo: l r -> l*g r*g
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_gain" "Opcode"}}
|
||||
{{- if .Stereo "gain"}}
|
||||
fld dword [{{.Input "gain" "gain"}}] ; g l (r)
|
||||
{{- if .Mono "invgain"}}
|
||||
jnc su_op_gain_mono
|
||||
{{- end}}
|
||||
fmul st2, st0 ; g l r/g
|
||||
su_op_gain_mono:
|
||||
fmulp st1, st0 ; l/g (r/)
|
||||
ret
|
||||
{{- else}}
|
||||
fmul dword [{{.Input "gain" "gain"}}]
|
||||
ret
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "invgain"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; INVGAIN opcode: apply inverse gain on the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: x -> x/g
|
||||
; Stereo: l r -> l/g r/g
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_invgain" "Opcode"}}
|
||||
{{- if .Stereo "invgain"}}
|
||||
fld dword [{{.Input "invgain" "invgain"}}] ; g l (r)
|
||||
{{- if .Mono "invgain"}}
|
||||
jnc su_op_invgain_mono
|
||||
{{- end}}
|
||||
fdiv st2, st0 ; g l r/g
|
||||
su_op_invgain_mono:
|
||||
fdivp st1, st0 ; l/g (r/)
|
||||
ret
|
||||
{{- else}}
|
||||
fdiv dword [{{.Input "invgain" "invgain"}}]
|
||||
ret
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "filter"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; FILTER opcode: perform low/high/band-pass/notch etc. filtering on the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: x -> filtered(x)
|
||||
; Stereo: l r -> filtered(l) filtered(r)
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_filter" "Opcode"}}
|
||||
lodsb ; load the flags to al
|
||||
{{- if .Stereo "filter"}}
|
||||
{{.Call "su_effects_stereohelper"}}
|
||||
{{- end}}
|
||||
fld dword [{{.Input "filter" "resonance"}}] ; r x
|
||||
fld dword [{{.Input "filter" "frequency"}}]; 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 [{{.SP}}-4] ; f2 r x
|
||||
fmul dword [{{.WRK}}+8] ; f2*b r x
|
||||
fadd dword [{{.WRK}}] ; f2*b+l r x
|
||||
fst dword [{{.WRK}}] ; l'=f2*b+l r x
|
||||
fsubp st2, st0 ; r x-l'
|
||||
fmul dword [{{.WRK}}+8] ; r*b x-l'
|
||||
fsubp st1, st0 ; x-l'-r*b
|
||||
fst dword [{{.WRK}}+4] ; h'=x-l'-r*b
|
||||
fmul dword [{{.SP}}-4] ; f2*h'
|
||||
fadd dword [{{.WRK}}+8] ; f2*h'+b
|
||||
fstp dword [{{.WRK}}+8] ; b'=f2*h'+b
|
||||
fldz ; 0
|
||||
{{- if .HasParamValue "filter" "lowpass" 1}}
|
||||
test al, byte 0x40
|
||||
jz short su_op_filter_skiplowpass
|
||||
fadd dword [{{.WRK}}]
|
||||
su_op_filter_skiplowpass:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "filter" "bandpass" 1}}
|
||||
test al, byte 0x20
|
||||
jz short su_op_filter_skipbandpass
|
||||
fadd dword [{{.WRK}}+8]
|
||||
su_op_filter_skipbandpass:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "filter" "highpass" 1}}
|
||||
test al, byte 0x10
|
||||
jz short su_op_filter_skiphighpass
|
||||
fadd dword [{{.WRK}}+4]
|
||||
su_op_filter_skiphighpass:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "filter" "negbandpass" 1}}
|
||||
test al, byte 0x08
|
||||
jz short su_op_filter_skipnegbandpass
|
||||
fsub dword [{{.WRK}}+8]
|
||||
su_op_filter_skipnegbandpass:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "filter" "neghighpass" 1}}
|
||||
test al, byte 0x04
|
||||
jz short su_op_filter_skipneghighpass
|
||||
fsub dword [{{.WRK}}+4]
|
||||
su_op_filter_skipneghighpass:
|
||||
{{- end}}
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "clip"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; CLIP opcode: clips the signal into [-1,1] range
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: x -> min(max(x,-1),1)
|
||||
; Stereo: l r -> min(max(l,-1),1) min(max(r,-1),1)
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_clip" "Opcode"}}
|
||||
{{- if .Stereo "clip"}}
|
||||
{{.Call "su_effects_stereohelper"}}
|
||||
{{- end}}
|
||||
{{.TailCall "su_clip"}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "pan" -}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; PAN opcode: pan the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: s -> s*(1-p) s*p
|
||||
; Stereo: l r -> l*(1-p) r*p
|
||||
;
|
||||
; where p is the panning in [0,1] range
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_pan" "Opcode"}}
|
||||
{{- if .Stereo "pan"}}
|
||||
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 [{{.Input "pan" "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}}
|
||||
fld dword [{{.Input "pan" "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
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "delay"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; DELAY opcode: adds delay effect to the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: perform delay on ST0, using delaycount delaylines starting
|
||||
; at delayindex from the delaytable
|
||||
; Stereo: perform delay on ST1, using delaycount delaylines starting
|
||||
; at delayindex + delaycount from the delaytable (so the right delays
|
||||
; can be different)
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_delay" "Opcode"}}
|
||||
lodsw ; al = delay index, ah = delay count
|
||||
{{- .PushRegs .VAL "DelayVal" .COM "DelayCom" | indent 4}}
|
||||
movzx ebx, al
|
||||
; %ifdef RUNTIME_TABLES ; when using runtime tables, delaytimes is pulled from the stack so can be a pointer to heap
|
||||
; mov _SI, [{{.SP}} + su_stack.delaytimes + PUSH_REG_SIZE(2)]
|
||||
; lea _BX, [_SI + _BX*2]
|
||||
; %else
|
||||
{{.Prepare "su_delay_times" | indent 4}}
|
||||
lea {{.BX}},[{{.Use "su_delay_times"}} + {{.BX}}*2] ; BX now points to the right position within delay time table
|
||||
movzx esi, word [{{.Stack "GlobalTick"}}] ; notice that we load word, so we wrap at 65536
|
||||
mov {{.CX}}, {{.PTRWORD}} [{{.Stack "DelayWorkSpace"}}] ; {{.WRK}} is now the separate delay workspace, as they require a lot more space
|
||||
{{- if .StereoAndMono "delay"}}
|
||||
jnc su_op_delay_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "delay"}}
|
||||
push {{.AX}} ; save _ah (delay count)
|
||||
fxch ; r l
|
||||
call su_op_delay_do ; D(r) l process delay for the right channel
|
||||
pop {{.AX}} ; restore the count for second run
|
||||
fxch ; l D(r)
|
||||
su_op_delay_mono: ; flow into mono delay
|
||||
{{- end}}
|
||||
call su_op_delay_do ; when stereo delay is not enabled, we could inline this to save 5 bytes, but I expect stereo delay to be farely popular so maybe not worth the hassle
|
||||
mov {{.PTRWORD}} [{{.Stack "DelayWorkSpace"}}],{{.CX}} ; move delay workspace pointer back to stack.
|
||||
{{- .PopRegs .VAL .COM | indent 4}}
|
||||
{{- if .UsesDelayModulation}}
|
||||
xor eax, eax
|
||||
mov dword [{{.Modulation "delay" "delaytime"}}], eax
|
||||
{{- end}}
|
||||
ret
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_op_delay_do: executes the actual delay
|
||||
;-------------------------------------------------------------------------------
|
||||
; 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 q
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_delay_do"}} ; x y
|
||||
fld st0
|
||||
fmul dword [{{.Input "delay" "pregain"}}] ; p*x y
|
||||
fmul dword [{{.Input "delay" "pregain"}}] ; p*p*x y
|
||||
fxch ; y p*p*x
|
||||
fmul dword [{{.Input "delay" "dry"}}] ; dr*y p*p*x
|
||||
su_op_delay_loop:
|
||||
{{- if or .UsesDelayModulation (.HasParamValue "delay" "notetracking" 1)}} ; delaytime modulation or note syncing require computing the delay time in floats
|
||||
fild word [{{.BX}}] ; k dr*y p*p*x, where k = delay time
|
||||
{{- if .HasParamValue "delay" "notetracking" 1}}
|
||||
test ah, 1 ; note syncing is the least significant bit of ah, 0 = ON, 1 = OFF
|
||||
jne su_op_delay_skipnotesync
|
||||
fild dword [{{.INP}}-su_voice.inputs+su_voice.note]
|
||||
{{.Int 0x3DAAAAAA | .Prepare | indent 8}}
|
||||
fmul dword [{{.Int 0x3DAAAAAA | .Use}}]
|
||||
{{.Call "su_power"}}
|
||||
fdivp st1, st0 ; use 10787 for delaytime to have neutral transpose
|
||||
su_op_delay_skipnotesync:
|
||||
{{- end}}
|
||||
{{- if .UsesDelayModulation}}
|
||||
fld dword [{{.Modulation "delay" "delaytime"}}]
|
||||
{{- .Float 32767.0 | .Prepare | indent 8}}
|
||||
fmul dword [{{.Float 32767.0 | .Use}}] ; scale it up, as the modulations would be too small otherwise
|
||||
faddp st1, st0
|
||||
{{- end}}
|
||||
fistp dword [{{.SP}}-4] ; dr*y p*p*x, dword [{{.SP}}-4] = integer amount of delay (samples)
|
||||
mov edi, esi ; edi = esi = current time
|
||||
sub di, word [{{.SP}}-4] ; we perform the math in 16-bit to wrap around
|
||||
{{- else}}
|
||||
mov edi, esi
|
||||
sub di, word [{{.BX}}] ; we perform the math in 16-bit to wrap around
|
||||
{{- end}}
|
||||
fld dword [{{.CX}}+su_delayline_wrk.buffer+{{.DI}}*4]; s dr*y p*p*x, where s is the sample from delay buffer
|
||||
fadd st1, st0 ; s dr*y+s p*p*x (add comb output to current output)
|
||||
fld1 ; 1 s dr*y+s p*p*x
|
||||
fsub dword [{{.Input "delay" "damp"}}] ; 1-da s dr*y+s p*p*x
|
||||
fmulp st1, st0 ; s*(1-da) dr*y+s p*p*x
|
||||
fld dword [{{.Input "delay" "damp"}}] ; da s*(1-da) dr*y+s p*p*x
|
||||
fmul dword [{{.CX}}+su_delayline_wrk.filtstate] ; o*da s*(1-da) dr*y+s p*p*x, where o is stored
|
||||
faddp st1, st0 ; o*da+s*(1-da) dr*y+s p*p*x
|
||||
fst dword [{{.CX}}+su_delayline_wrk.filtstate] ; o'=o*da+s*(1-da), o' dr*y+s p*p*x
|
||||
fmul dword [{{.Input "delay" "feedback"}}] ; f*o' dr*y+s p*p*x
|
||||
fadd st0, st2 ; f*o'+p*p*x dr*y+s p*p*x
|
||||
fstp dword [{{.CX}}+su_delayline_wrk.buffer+{{.SI}}*4]; save f*o'+p*p*x to delay buffer
|
||||
add {{.BX}},2 ; move to next index
|
||||
add {{.CX}}, su_delayline_wrk.size ; go to next delay delay workspace
|
||||
sub ah, 2
|
||||
jg su_op_delay_loop ; if ah > 0, goto loop
|
||||
fstp st1 ; dr*y+s1+s2+s3+...
|
||||
; DC-filtering
|
||||
fld dword [{{.CX}}+su_delayline_wrk.dcout] ; o s
|
||||
{{- .Float 0.99609375 | .Prepare | indent 4}}
|
||||
fmul dword [{{.Float 0.99609375 | .Use}}] ; c*o s
|
||||
fsub dword [{{.CX}}+su_delayline_wrk.dcin] ; c*o-i s
|
||||
fxch ; s c*o-i
|
||||
fst dword [{{.CX}}+su_delayline_wrk.dcin] ; i'=s, s c*o-i
|
||||
faddp st1 ; s+c*o-i
|
||||
{{- .Float 0.5 | .Prepare | indent 4}}
|
||||
fadd dword [{{.Float 0.5 | .Use}}] ; add and sub small offset to prevent denormalization
|
||||
fsub dword [{{.Float 0.5 | .Use}}]
|
||||
fst dword [{{.CX}}+su_delayline_wrk.dcout] ; o'=s+c*o-i
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "compressor"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; COMPRES opcode: push compressor gain to stack
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: push g on stack, where g is a suitable gain for the signal
|
||||
; you can either MULP to compress the signal or SEND it to a GAIN
|
||||
; somewhere else for compressor side-chaining.
|
||||
; Stereo: push g g on stack, where g is calculated using l^2 + r^2
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_compressor" "Opcode"}}
|
||||
fdiv dword [{{.Input "compressor" "invgain"}}]; l/g, we'll call this pre inverse gained signal x from now on
|
||||
fld st0 ; x x
|
||||
fmul st0, st0 ; x^2 x
|
||||
{{- if .StereoAndMono "compressor"}}
|
||||
jnc su_op_compressor_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "compressor"}}
|
||||
fld st2 ; r x^2 l/g r
|
||||
fdiv dword [{{.Input "compressor" "invgain"}}]; r/g, we'll call this pre inverse gained signal y from now on
|
||||
fst st3 ; y x^2 l/g r/g
|
||||
fmul st0, st0 ; y^2 x^2 l/g r/g
|
||||
faddp st1, st0 ; y^2+x^2 l/g r/g
|
||||
call su_op_compressor_mono ; So, for stereo, we square both left & right and add them up
|
||||
fld st0 ; and return the computed gain two times, ready for MULP STEREO
|
||||
ret
|
||||
su_op_compressor_mono:
|
||||
{{- end}}
|
||||
fld dword [{{.WRK}}] ; l x^2 x
|
||||
fucomi st0, st1
|
||||
setnb al ; if (st0 >= st1) al = 1; else al = 0;
|
||||
fsubp st1, st0 ; x^2-l x
|
||||
{{.Call "su_nonlinear_map"}} ; c x^2-l x, c is either attack or release parameter mapped in a nonlinear way
|
||||
fmulp st1, st0 ; c*(x^2-l) x
|
||||
fadd dword [{{.WRK}}] ; l+c*(x^2-l) x // we could've kept level in the stack and save a few bytes, but su_env_map uses 3 stack (c + 2 temp), so the stack was getting quite big.
|
||||
fst dword [{{.WRK}}] ; l'=l+c*(x^2-l), l' x
|
||||
fld dword [{{.Input "compressor" "threshold"}}] ; t l' x
|
||||
fmul st0, st0 ; t*t l' x
|
||||
fxch ; l' t*t x
|
||||
fucomi st0, st1 ; if l' < t*t
|
||||
fcmovb st0, st1 ; l'=t*t
|
||||
fdivp st1, st0 ; t*t/l' x
|
||||
fld dword [{{.Input "compressor" "ratio"}}] ; r t*t/l' x
|
||||
{{.Float 0.5 | .Prepare | indent 4}}
|
||||
fmul dword [{{.Float 0.5 | .Use}}] ; p=r/2 t*t/l' x
|
||||
fxch ; t*t/l' p x
|
||||
fyl2x ; p*log2(t*t/l') x
|
||||
{{.TailCall "su_power"}} ; 2^(p*log2(t*t/l')) x
|
||||
; tail call ; Equal to:
|
||||
; (t*t/l')^p x
|
||||
; if ratio is at minimum => p=0 => 1 x
|
||||
; if ratio is at maximum => p=0.5 => t/x => t/x*x=t
|
||||
{{- end}}
|
23
templates/flowcontrol.asm
Normal file
23
templates/flowcontrol.asm
Normal file
@ -0,0 +1,23 @@
|
||||
{{- if .Opcode "speed" -}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; SPEED opcode: modulate the speed (bpm) of the song based on ST0
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: adds or subtracts the ticks, a value of 0.5 is neutral & will7
|
||||
; result in no speed change.
|
||||
; There is no STEREO version.
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_speed" "Opcode"}}
|
||||
{{- .Float 2.206896551724138 | .Prepare | indent 4}}
|
||||
fmul dword [{{.Float 2.206896551724138 | .Use}}] ; (2*s-1)*64/24, let's call this p from now on
|
||||
{{.Call "su_power"}}
|
||||
fld1 ; 1 2^p
|
||||
fsubp st1, st0 ; 2^p-1, the player is advancing 1 tick by its own
|
||||
fadd dword [{{.WRK}}] ; t+2^p-1, t is the remainder from previous rounds as ticks have to be rounded to 1
|
||||
push {{.AX}}
|
||||
fist dword [{{.SP}}] ; Main stack: k=int(t+2^p-1)
|
||||
fisub dword [{{.SP}}] ; t+2^p-1-k, the remainder
|
||||
pop {{.AX}}
|
||||
add dword [{{.Stack "Sample"}}], eax ; add the whole ticks to row tick count
|
||||
fstp dword [{{.WRK}}] ; save the remainder for future
|
||||
ret
|
||||
{{end}}
|
58
templates/gmdls.asm
Normal file
58
templates/gmdls.asm
Normal file
@ -0,0 +1,58 @@
|
||||
{{- if eq .OS "windows"}}
|
||||
{{.ExportFunc "su_load_gmdls"}}
|
||||
{{- if .Amd64}}
|
||||
extern OpenFile ; requires windows
|
||||
extern ReadFile ; requires windows
|
||||
; Win64 ABI: RCX, RDX, R8, and R9
|
||||
sub rsp, 40 ; Win64 ABI requires "shadow space" + space for one parameter.
|
||||
mov rdx, qword su_sample_table
|
||||
mov rcx, qword su_gmdls_path1
|
||||
su_gmdls_pathloop:
|
||||
xor r8,r8 ; OF_READ
|
||||
push rdx ; &ofstruct, blatantly reuse the sample table
|
||||
push rcx
|
||||
call OpenFile ; eax = OpenFile(path,&ofstruct,OF_READ)
|
||||
pop rcx
|
||||
add rcx, su_gmdls_path2 - su_gmdls_path1 ; if we ever get to third, then crash
|
||||
pop rdx
|
||||
cmp eax, -1 ; ecx == INVALID?
|
||||
je su_gmdls_pathloop
|
||||
movsxd rcx, eax
|
||||
mov qword [rsp+32], 0
|
||||
mov r9, rdx
|
||||
mov r8d, 3440660 ; number of bytes to read
|
||||
call ReadFile ; Readfile(handle,&su_sample_table,SAMPLE_TABLE_SIZE,&bytes_read,NULL)
|
||||
add rsp, 40 ; shadow space, as required by Win64 ABI
|
||||
ret
|
||||
{{- else}}
|
||||
mov edx, su_sample_table
|
||||
mov ecx, su_gmdls_path1
|
||||
su_gmdls_pathloop:
|
||||
push 0 ; OF_READ
|
||||
push edx ; &ofstruct, blatantly reuse the sample table
|
||||
push ecx ; path
|
||||
call _OpenFile@12 ; eax = OpenFile(path,&ofstruct,OF_READ)
|
||||
add ecx, su_gmdls_path2 - su_gmdls_path1 ; if we ever get to third, then crash
|
||||
cmp eax, -1 ; eax == INVALID?
|
||||
je su_gmdls_pathloop
|
||||
push 0 ; NULL
|
||||
push edx ; &bytes_read, reusing sample table again; it does not matter that the first four bytes are trashed
|
||||
push 3440660 ; number of bytes to read
|
||||
push edx ; here we actually pass the sample table to readfile
|
||||
push eax ; handle to file
|
||||
call _ReadFile@20 ; Readfile(handle,&su_sample_table,SAMPLE_TABLE_SIZE,&bytes_read,NULL)
|
||||
ret
|
||||
extern _OpenFile@12 ; requires windows
|
||||
extern _ReadFile@20 ; requires windows
|
||||
{{end}}
|
||||
|
||||
{{.Data "su_gmdls_path1"}}
|
||||
db 'drivers/gm.dls',0
|
||||
su_gmdls_path2:
|
||||
db 'drivers/etc/gm.dls',0
|
||||
|
||||
{{.SectBss "susamtable"}}
|
||||
su_sample_table:
|
||||
resb 3440660 ; size of gmdls.
|
||||
|
||||
{{end}}
|
304
templates/library.asm
Normal file
304
templates/library.asm
Normal file
@ -0,0 +1,304 @@
|
||||
; source file for compiling sointu as a library
|
||||
%define SU_DISABLE_PLAYER
|
||||
|
||||
%include "sointu/header.inc"
|
||||
|
||||
; use every opcode
|
||||
USE_ADD
|
||||
USE_ADDP
|
||||
USE_POP
|
||||
USE_LOADNOTE
|
||||
USE_MUL
|
||||
USE_MULP
|
||||
USE_PUSH
|
||||
USE_XCH
|
||||
USE_DISTORT
|
||||
USE_HOLD
|
||||
USE_CRUSH
|
||||
USE_GAIN
|
||||
USE_INVGAIN
|
||||
USE_FILTER
|
||||
USE_CLIP
|
||||
USE_PAN
|
||||
USE_DELAY
|
||||
USE_COMPRES
|
||||
USE_SPEED
|
||||
USE_OUT
|
||||
USE_OUTAUX
|
||||
USE_AUX
|
||||
USE_SEND
|
||||
USE_ENVELOPE
|
||||
USE_NOISE
|
||||
USE_OSCILLAT
|
||||
USE_LOAD_VAL
|
||||
USE_RECEIVE
|
||||
USE_IN
|
||||
|
||||
; include stereo variant of each opcode
|
||||
%define INCLUDE_STEREO_ADD
|
||||
%define INCLUDE_STEREO_ADDP
|
||||
%define INCLUDE_STEREO_POP
|
||||
%define INCLUDE_STEREO_LOADNOTE
|
||||
%define INCLUDE_STEREO_MUL
|
||||
%define INCLUDE_STEREO_MULP
|
||||
%define INCLUDE_STEREO_PUSH
|
||||
%define INCLUDE_STEREO_XCH
|
||||
%define INCLUDE_STEREO_DISTORT
|
||||
%define INCLUDE_STEREO_HOLD
|
||||
%define INCLUDE_STEREO_CRUSH
|
||||
%define INCLUDE_STEREO_GAIN
|
||||
%define INCLUDE_STEREO_INVGAIN
|
||||
%define INCLUDE_STEREO_FILTER
|
||||
%define INCLUDE_STEREO_CLIP
|
||||
%define INCLUDE_STEREO_PAN
|
||||
%define INCLUDE_STEREO_DELAY
|
||||
%define INCLUDE_STEREO_COMPRES
|
||||
%define INCLUDE_STEREO_SPEED
|
||||
%define INCLUDE_STEREO_OUT
|
||||
%define INCLUDE_STEREO_OUTAUX
|
||||
%define INCLUDE_STEREO_AUX
|
||||
%define INCLUDE_STEREO_SEND
|
||||
%define INCLUDE_STEREO_ENVELOPE
|
||||
%define INCLUDE_STEREO_NOISE
|
||||
%define INCLUDE_STEREO_OSCILLAT
|
||||
%define INCLUDE_STEREO_LOADVAL
|
||||
%define INCLUDE_STEREO_RECEIVE
|
||||
%define INCLUDE_STEREO_IN
|
||||
|
||||
; include all features inside all opcodes
|
||||
%define INCLUDE_TRISAW
|
||||
%define INCLUDE_SINE
|
||||
%define INCLUDE_PULSE
|
||||
%define INCLUDE_GATE
|
||||
%define INCLUDE_UNISONS
|
||||
%define INCLUDE_POLYPHONY
|
||||
%define INCLUDE_MULTIVOICE_TRACKS
|
||||
%define INCLUDE_DELAY_MODULATION
|
||||
%define INCLUDE_LOWPASS
|
||||
%define INCLUDE_BANDPASS
|
||||
%define INCLUDE_HIGHPASS
|
||||
%define INCLUDE_NEGBANDPASS
|
||||
%define INCLUDE_NEGHIGHPASS
|
||||
%define INCLUDE_GLOBAL_SEND
|
||||
%define INCLUDE_DELAY_NOTETRACKING
|
||||
%define INCLUDE_DELAY_FLOAT_TIME
|
||||
|
||||
%ifidn __OUTPUT_FORMAT__,win32
|
||||
%define INCLUDE_SAMPLES
|
||||
%define INCLUDE_GMDLS
|
||||
%endif
|
||||
%ifidn __OUTPUT_FORMAT__,win64
|
||||
%define INCLUDE_SAMPLES
|
||||
%define INCLUDE_GMDLS
|
||||
%endif
|
||||
|
||||
%include "sointu/footer.inc"
|
||||
|
||||
section .text
|
||||
|
||||
struc su_sampleoff
|
||||
.start resd 1
|
||||
.loopstart resw 1
|
||||
.looplength resw 1
|
||||
.size:
|
||||
endstruc
|
||||
|
||||
struc su_synth
|
||||
.synthwrk resb su_synthworkspace.size
|
||||
.delaywrks resb su_delayline_wrk.size * 64
|
||||
.delaytimes resw 768
|
||||
.sampleoffs resb su_sampleoff.size * 256
|
||||
.randseed resd 1
|
||||
.globaltime resd 1
|
||||
.commands resb 32 * 64
|
||||
.values resb 32 * 64 * 8
|
||||
.polyphony resd 1
|
||||
.numvoices resd 1
|
||||
endstruc
|
||||
|
||||
SECT_TEXT(sursampl)
|
||||
|
||||
EXPORT MANGLE_FUNC(su_render,16)
|
||||
%if BITS == 32 ; stdcall
|
||||
pushad ; push registers
|
||||
mov ecx, [esp + 4 + 32] ; ecx = &synthState
|
||||
mov edx, [esp + 8 + 32] ; edx = &buffer
|
||||
mov esi, [esp + 12 + 32] ; esi = &samples
|
||||
mov ebx, [esp + 16 + 32] ; ebx = &time
|
||||
%else
|
||||
%ifidn __OUTPUT_FORMAT__,win64 ; win64 ABI: rcx = &synth, rdx = &buffer, r8 = &bufsize, r9 = &time
|
||||
push_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile
|
||||
mov rsi, r8 ; rsi = &samples
|
||||
mov rbx, r9 ; rbx = &time
|
||||
%else ; System V ABI: rdi = &synth, rsi = &buffer, rdx = &samples, rcx = &time
|
||||
push_registers rbx, rbp ; System V ABI: these registers are non-volatile
|
||||
mov rbx, rcx ; rbx points to time
|
||||
xchg rsi, rdx ; rdx points to buffer, rsi points to samples
|
||||
mov rcx, rdi ; rcx = &Synthstate
|
||||
%endif
|
||||
%endif
|
||||
sub _SP,108 ; allocate space on stack for the FPU state
|
||||
fsave [_SP] ; save the FPU state to stack & reset the FPU
|
||||
push _SI ; push the pointer to samples
|
||||
push _BX ; push the pointer to time
|
||||
xor eax, eax ; samplenumber starts at 0
|
||||
push _AX ; push samplenumber to stack
|
||||
mov esi, [_SI] ; zero extend dereferenced pointer
|
||||
push _SI ; push bufsize
|
||||
push _DX ; push bufptr
|
||||
push _CX ; this takes place of the voicetrack
|
||||
lea _AX, [_CX + su_synth.sampleoffs]
|
||||
push _AX
|
||||
lea _AX, [_CX + su_synth.delaytimes]
|
||||
push _AX
|
||||
mov eax, [_CX + su_synth.randseed]
|
||||
push _AX ; randseed
|
||||
mov eax, [_CX + su_synth.globaltime]
|
||||
push _AX ; global tick time
|
||||
mov ebx, dword [_BX] ; zero extend dereferenced pointer
|
||||
push _BX ; the nominal rowlength should be time_in
|
||||
xor eax, eax ; rowtick starts at 0
|
||||
su_render_samples_loop:
|
||||
push _DI
|
||||
fnstsw [_SP] ; store the FPU status flag to stack top
|
||||
pop _DI ; _DI = FPU status flag
|
||||
and _DI, 0011100001000101b ; mask TOP pointer, stack error, zero divide and invalid operation
|
||||
test _DI,_DI ; all the aforementioned bits should be 0!
|
||||
jne su_render_samples_time_finish ; otherwise, we exit due to error
|
||||
cmp eax, [_SP] ; if rowtick >= maxtime
|
||||
jge su_render_samples_time_finish ; goto finish
|
||||
mov ecx, [_SP + PTRSIZE*7] ; ecx = buffer length in samples
|
||||
cmp [_SP + PTRSIZE*8], ecx ; if samples >= maxsamples
|
||||
jge su_render_samples_time_finish ; goto finish
|
||||
inc eax ; time++
|
||||
inc dword [_SP + PTRSIZE*8] ; samples++
|
||||
mov _CX, [_SP + PTRSIZE*5]
|
||||
push _AX ; push rowtick
|
||||
mov eax, [_CX + su_synth.polyphony]
|
||||
push _AX ;polyphony
|
||||
mov eax, [_CX + su_synth.numvoices]
|
||||
push _AX ;numvoices
|
||||
lea _DX, [_CX+ su_synth.synthwrk]
|
||||
lea COM, [_CX+ su_synth.commands]
|
||||
lea VAL, [_CX+ su_synth.values]
|
||||
lea WRK, [_DX + su_synthworkspace.voices]
|
||||
lea _CX, [_CX+ su_synth.delaywrks - su_delayline_wrk.filtstate]
|
||||
call MANGLE_FUNC(su_run_vm,0)
|
||||
pop _AX
|
||||
pop _AX
|
||||
mov _DI, [_SP + PTRSIZE*7] ; edi containts buffer ptr
|
||||
mov _CX, [_SP + PTRSIZE*6]
|
||||
lea _SI, [_CX + su_synth.synthwrk + su_synthworkspace.left]
|
||||
movsd ; copy left channel to output buffer
|
||||
movsd ; copy right channel to output buffer
|
||||
mov [_SP + PTRSIZE*7], _DI ; save back the updated ptr
|
||||
lea _DI, [_SI-8]
|
||||
xor eax, eax
|
||||
stosd ; clear left channel so the VM is ready to write them again
|
||||
stosd ; clear right channel so the VM is ready to write them again
|
||||
pop _AX
|
||||
inc dword [_SP + PTRSIZE] ; increment global time, used by delays
|
||||
jmp su_render_samples_loop
|
||||
su_render_samples_time_finish:
|
||||
pop _CX
|
||||
pop _BX
|
||||
pop _DX
|
||||
pop _CX ; discard delaytimes ptr
|
||||
pop _CX ; discard samplesoffs ptr
|
||||
pop _CX
|
||||
mov [_CX + su_synth.randseed], edx
|
||||
mov [_CX + su_synth.globaltime], ebx
|
||||
pop _BX
|
||||
pop _BX
|
||||
pop _DX
|
||||
pop _BX ; pop the pointer to time
|
||||
pop _SI ; pop the pointer to samples
|
||||
mov dword [_SI], edx ; *samples = samples rendered
|
||||
mov dword [_BX], eax ; *time = time ticks rendered
|
||||
mov _AX,_DI ; _DI was the masked FPU status flag, _AX is return value
|
||||
frstor [_SP] ; restore fpu state
|
||||
add _SP,108 ; rewind the stack allocate for FPU state
|
||||
%if BITS == 32 ; stdcall
|
||||
mov [_SP + 28],eax ; we want to return eax, but popad pops everything, so put eax to stack for popad to pop
|
||||
popad
|
||||
ret 16
|
||||
%else
|
||||
%ifidn __OUTPUT_FORMAT__,win64
|
||||
pop_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile
|
||||
%else
|
||||
pop_registers rbx, rbp ; System V ABI: these registers are non-volatile
|
||||
%endif
|
||||
ret
|
||||
%endif
|
||||
|
||||
SECT_DATA(opcodeid)
|
||||
|
||||
; Arithmetic opcode ids
|
||||
EXPORT MANGLE_DATA(su_add_id)
|
||||
dd ADD_ID
|
||||
EXPORT MANGLE_DATA(su_addp_id)
|
||||
dd ADDP_ID
|
||||
EXPORT MANGLE_DATA(su_pop_id)
|
||||
dd POP_ID
|
||||
EXPORT MANGLE_DATA(su_loadnote_id)
|
||||
dd LOADNOTE_ID
|
||||
EXPORT MANGLE_DATA(su_mul_id)
|
||||
dd MUL_ID
|
||||
EXPORT MANGLE_DATA(su_mulp_id)
|
||||
dd MULP_ID
|
||||
EXPORT MANGLE_DATA(su_push_id)
|
||||
dd PUSH_ID
|
||||
EXPORT MANGLE_DATA(su_xch_id)
|
||||
dd XCH_ID
|
||||
|
||||
; Effect opcode ids
|
||||
EXPORT MANGLE_DATA(su_distort_id)
|
||||
dd DISTORT_ID
|
||||
EXPORT MANGLE_DATA(su_hold_id)
|
||||
dd HOLD_ID
|
||||
EXPORT MANGLE_DATA(su_crush_id)
|
||||
dd CRUSH_ID
|
||||
EXPORT MANGLE_DATA(su_gain_id)
|
||||
dd GAIN_ID
|
||||
EXPORT MANGLE_DATA(su_invgain_id)
|
||||
dd INVGAIN_ID
|
||||
EXPORT MANGLE_DATA(su_filter_id)
|
||||
dd FILTER_ID
|
||||
EXPORT MANGLE_DATA(su_clip_id)
|
||||
dd CLIP_ID
|
||||
EXPORT MANGLE_DATA(su_pan_id)
|
||||
dd PAN_ID
|
||||
EXPORT MANGLE_DATA(su_delay_id)
|
||||
dd DELAY_ID
|
||||
EXPORT MANGLE_DATA(su_compres_id)
|
||||
dd COMPRES_ID
|
||||
|
||||
; Flowcontrol opcode ids
|
||||
EXPORT MANGLE_DATA(su_advance_id)
|
||||
dd SU_ADVANCE_ID
|
||||
EXPORT MANGLE_DATA(su_speed_id)
|
||||
dd SPEED_ID
|
||||
|
||||
; Sink opcode ids
|
||||
EXPORT MANGLE_DATA(su_out_id)
|
||||
dd OUT_ID
|
||||
EXPORT MANGLE_DATA(su_outaux_id)
|
||||
dd OUTAUX_ID
|
||||
EXPORT MANGLE_DATA(su_aux_id)
|
||||
dd AUX_ID
|
||||
EXPORT MANGLE_DATA(su_send_id)
|
||||
dd SEND_ID
|
||||
|
||||
; Source opcode ids
|
||||
EXPORT MANGLE_DATA(su_envelope_id)
|
||||
dd ENVELOPE_ID
|
||||
EXPORT MANGLE_DATA(su_noise_id)
|
||||
dd NOISE_ID
|
||||
EXPORT MANGLE_DATA(su_oscillat_id)
|
||||
dd OSCILLAT_ID
|
||||
EXPORT MANGLE_DATA(su_loadval_id)
|
||||
dd LOADVAL_ID
|
||||
EXPORT MANGLE_DATA(su_receive_id)
|
||||
dd RECEIVE_ID
|
||||
EXPORT MANGLE_DATA(su_in_id)
|
||||
dd IN_ID
|
44
templates/output_sound.asm
Normal file
44
templates/output_sound.asm
Normal file
@ -0,0 +1,44 @@
|
||||
{{- if not .Output16Bit }}
|
||||
{{- if not .Clip }}
|
||||
mov {{.DI}}, [{{.Stack "OutputBufPtr"}}] ; edi containts ptr
|
||||
mov {{.SI}}, {{.PTRWORD}} su_synth_obj + su_synthworkspace.left
|
||||
movsd ; copy left channel to output buffer
|
||||
movsd ; copy right channel to output buffer
|
||||
mov [{{.Stack "OutputBufPtr"}}], {{.DI}} ; save back the updated ptr
|
||||
lea {{.DI}}, [{{.SI}}-8]
|
||||
xor eax, eax
|
||||
stosd ; clear left channel so the VM is ready to write them again
|
||||
stosd ; clear right channel so the VM is ready to write them again
|
||||
{{ else }}
|
||||
mov {{.SI}}, qword [{{.Stack "OutputBufPtr"}}] ; esi points to the output buffer
|
||||
xor ecx,ecx
|
||||
xor eax,eax
|
||||
%%loop: ; loop over two channels, left & right
|
||||
do fld dword [,su_synth_obj+su_synthworkspace.left,_CX*4,]
|
||||
{{.Call "su_clip"}}
|
||||
fstp dword [_SI]
|
||||
do mov dword [,su_synth_obj+su_synthworkspace.left,_CX*4,{],eax} ; clear the sample so the VM is ready to write it
|
||||
add _SI,4
|
||||
cmp ecx,2
|
||||
jl %%loop
|
||||
mov dword [_SP+su_stack.bufferptr - su_stack.output_sound], _SI ; save esi back to stack
|
||||
{{ end }}
|
||||
{{- else}}
|
||||
mov {{.SI}}, [{{.Stack "OutputBufPtr"}}] ; esi points to the output buffer
|
||||
mov {{.DI}}, {{.PTRWORD}} su_synth_obj+su_synthworkspace.left
|
||||
mov ecx, 2
|
||||
output_sound16bit_loop: ; loop over two channels, left & right
|
||||
fld dword [{{.DI}}]
|
||||
{{.Call "su_clip"}}
|
||||
{{- .Float 32767.0 | .Prepare | indent 16}}
|
||||
fmul dword [{{.Float 32767.0 | .Use}}]
|
||||
push {{.AX}}
|
||||
fistp dword [{{.SP}}]
|
||||
pop {{.AX}}
|
||||
mov word [{{.SI}}],ax ; // store integer converted right sample
|
||||
xor eax,eax
|
||||
stosd
|
||||
add {{.SI}},2
|
||||
loop output_sound16bit_loop
|
||||
mov [{{.Stack "OutputBufPtr"}}], {{.SI}} ; save esi back to stack
|
||||
{{- end }}
|
190
templates/patch.asm
Normal file
190
templates/patch.asm
Normal file
@ -0,0 +1,190 @@
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_run_vm function: runs the entire virtual machine once, creating 1 sample
|
||||
;-------------------------------------------------------------------------------
|
||||
; Input: su_synth_obj.left : Set to 0 before calling
|
||||
; su_synth_obj.right : Set to 0 before calling
|
||||
; _CX : Pointer to delay workspace (if needed)
|
||||
; _DX : Pointer to synth object
|
||||
; COM : Pointer to command stream
|
||||
; VAL : Pointer to value stream
|
||||
; WRK : Pointer to the last workspace processed
|
||||
; Output: su_synth_obj.left : left sample
|
||||
; su_synth_obj.right : right sample
|
||||
; Dirty: everything
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_run_vm"}}
|
||||
{{- .PushRegs .CX "DelayWorkSpace" .DX "Synth" .COM "CommandStream" .WRK "Voice" .VAL "ValueStream" | indent 4}}
|
||||
su_run_vm_loop: ; loop until all voices done
|
||||
movzx edi, byte [{{.COM}}] ; edi = command byte
|
||||
inc {{.COM}} ; move to next instruction
|
||||
add {{.WRK}}, su_unit.size ; move WRK to next unit
|
||||
shr edi, 1 ; shift out the LSB bit = stereo bit
|
||||
je su_run_vm_advance ; the opcode is zero, jump to advance
|
||||
mov {{.INP}}, [{{.Stack "Voice"}}] ; reset INP to point to the inputs part of voice
|
||||
add {{.INP}}, su_voice.inputs
|
||||
xor ecx, ecx ; counter = 0
|
||||
xor eax, eax ; clear out high bits of eax, as lodsb only sets al
|
||||
su_transform_values_loop:
|
||||
{{- .Prepare "su_vm_transformcounts" | indent 4}}
|
||||
cmp cl, byte [{{.Use "su_vm_transformcounts"}}+{{.DI}}] ; compare the counter to the value in the param count table
|
||||
je su_transform_values_out
|
||||
lodsb ; load the byte value from VAL stream
|
||||
push {{.AX}} ; push it to memory so FPU can read it
|
||||
fild dword [{{.SP}}] ; load the value to FPU stack
|
||||
{{- .Prepare (.Float 0.0078125) | indent 4}}
|
||||
fmul dword [{{.Use (.Float 0.0078125)}}] ; divide it by 128 (0 => 0, 128 => 1.0)
|
||||
fadd dword [{{.WRK}}+su_unit.ports+{{.CX}}*4] ; add the modulations in the current workspace
|
||||
fstp dword [{{.INP}}+{{.CX}}*4] ; store the modulated value in the inputs section of voice
|
||||
xor eax, eax
|
||||
mov dword [{{.WRK}}+su_unit.ports+{{.CX}}*4], eax ; clear out the modulation ports
|
||||
pop {{.AX}}
|
||||
inc ecx
|
||||
jmp su_transform_values_loop
|
||||
su_transform_values_out:
|
||||
bt dword [{{.COM}}-1],0 ; LSB of COM = stereo bit => carry
|
||||
{{- .SaveStack "Opcode"}}
|
||||
{{- .Prepare "su_vm_jumptable" | indent 4}}
|
||||
call [{{.Use "su_vm_jumptable"}}+{{.DI}}*{{.PTRSIZE}}] ; call the function corresponding to the instruction
|
||||
jmp su_run_vm_loop
|
||||
su_run_vm_advance:
|
||||
{{- if .Polyphony}}
|
||||
mov {{.WRK}}, [{{.Stack "Voice"}}] ; WRK points to start of current voice
|
||||
add {{.WRK}}, su_voice.size ; move to next voice
|
||||
mov [{{.Stack "Voice"}}], {{.WRK}} ; update the pointer in the stack to point to the new voice
|
||||
mov ecx, [{{.Stack "VoicesRemain"}}] ; ecx = how many voices remain to process
|
||||
dec ecx ; decrement number of voices to process
|
||||
bt dword [{{.Stack "PolyphonyBitmask"}}], ecx ; if voice bit of su_polyphonism not set
|
||||
jnc su_op_advance_next_instrument ; goto next_instrument
|
||||
mov {{.VAL}}, [{{.Stack "ValueStream"}}] ; if it was set, then repeat the opcodes for the current voice
|
||||
mov {{.COM}}, [{{.Stack "CommandStream"}}]
|
||||
su_op_advance_next_instrument:
|
||||
mov [{{.Stack "ValueStream"}}], {{.VAL}} ; save current VAL as a checkpoint
|
||||
mov [{{.Stack "CommandStream"}}], {{.COM}} ; save current COM as a checkpoint
|
||||
su_op_advance_finish:
|
||||
mov [{{.Stack "VoicesRemain"}}], ecx
|
||||
jne su_run_vm_loop ; ZF was set by dec ecx
|
||||
{{- else}}
|
||||
mov {{.WRK}}, {{.PTRWORD}} [{{.Stack "Voice"}}] ; load pointer to voice to register
|
||||
add {{.WRK}}, su_voice.size ; shift it to point to following voice
|
||||
mov {{.PTRWORD}} [{{.Stack "Voice"}}], {{.WRK}} ; save back to stack
|
||||
dec dword [{{.Stack "VoicesRemain"}}] ; voices--
|
||||
jne su_run_vm_loop ; if there's more voices to process, goto vm_loop
|
||||
{{- end}}
|
||||
{{- .PopRegs .CX .DX .COM .WRK .VAL | indent 4}}
|
||||
ret
|
||||
|
||||
{{- template "arithmetic.asm" .}}
|
||||
{{- template "effects.asm" .}}
|
||||
{{- template "flowcontrol.asm" .}}
|
||||
{{- template "sinks.asm" .}}
|
||||
{{- template "sources.asm" .}}
|
||||
{{- template "gmdls.asm" .}}
|
||||
|
||||
{{- if .HasCall "su_nonlinear_map"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_nonlinear_map function: returns 2^(-24*x) of parameter number _AX
|
||||
;-------------------------------------------------------------------------------
|
||||
; Input: _AX : parameter number (e.g. for envelope: 0 = attac, 1 = decay...)
|
||||
; INP : pointer to transformed values
|
||||
; Output: st0 : 2^(-24*x), where x is the parameter in the range 0-1
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_nonlinear_map"}}
|
||||
fld dword [{{.INP}}+{{.AX}}*4] ; x, where x is the parameter in the range 0-1
|
||||
{{.Prepare (.Int 24)}}
|
||||
fimul dword [{{.Use (.Int 24)}}] ; 24*x
|
||||
fchs ; -24*x
|
||||
|
||||
{{end}}
|
||||
|
||||
{{- if or (.HasCall "su_power") (.HasCall "su_nonlinear_map")}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_power function: computes 2^x
|
||||
;-------------------------------------------------------------------------------
|
||||
; Input: st0 : x
|
||||
; Output: st0 : 2^x
|
||||
;-------------------------------------------------------------------------------
|
||||
{{- if not (.HasCall "su_nonlinear_map")}}{{.SectText "su_power"}}{{end}}
|
||||
su_power:
|
||||
fld1 ; 1 x
|
||||
fld st1 ; x 1 x
|
||||
fprem ; mod(x,1) 1 x
|
||||
f2xm1 ; 2^mod(x,1)-1 1 x
|
||||
faddp st1,st0 ; 2^mod(x,1) x
|
||||
fscale ; 2^mod(x,1)*2^trunc(x) x
|
||||
; Equal to:
|
||||
; 2^x x
|
||||
fstp st1 ; 2^x
|
||||
ret
|
||||
|
||||
{{end}}
|
||||
|
||||
{{- if .HasCall "su_effects_stereohelper" }}
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_effects_stereohelper: moves the workspace to next, does the filtering for
|
||||
; right channel (pulling the calling address from stack), rewinds the
|
||||
; workspace and returns
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_effects_stereohelper"}}
|
||||
jnc su_effects_stereohelper_mono ; carry is still the stereo bit
|
||||
add {{.WRK}}, 16
|
||||
fxch ; r l
|
||||
call [{{.SP}}] ; call whoever called me...
|
||||
fxch ; l r
|
||||
sub {{.WRK}}, 16 ; move WRK back to where it was
|
||||
su_effects_stereohelper_mono:
|
||||
ret ; return to process l/mono sound
|
||||
|
||||
{{end}}
|
||||
|
||||
{{- if .HasCall "su_waveshaper" }}
|
||||
{{.Func "su_waveshaper"}}
|
||||
fxch ; x a
|
||||
{{.Call "su_clip"}}
|
||||
fxch ; a x' (from now on just called x)
|
||||
fld st0 ; a a x
|
||||
{{.Prepare (.Float 0.5)}}
|
||||
fsub dword [{{.Use (.Float 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
|
||||
{{end}}
|
||||
|
||||
{{- if .HasCall "su_clip"}}
|
||||
{{.Func "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
|
||||
{{end}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; The opcode table jump table. This is constructed to only include the opcodes
|
||||
; that are used so that the jump table is as small as possible.
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_vm_jumptable_offset"}}
|
||||
su_vm_jumptable equ $ - {{.PTRSIZE}} ; Advance is not in the opcode table
|
||||
{{- $x := .}}
|
||||
{{- range .Opcodes}}
|
||||
{{$x.DPTR}} su_op_{{.Type}}
|
||||
{{- end}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; The number of transformed parameters each opcode takes
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_vm_transformcounts_offset"}}
|
||||
su_vm_transformcounts equ $ - 1 ; Advance is not in the opcode table
|
||||
{{- range .Opcodes}}
|
||||
db {{.NumParams}}
|
||||
{{- end}}
|
237
templates/player.asm
Normal file
237
templates/player.asm
Normal file
@ -0,0 +1,237 @@
|
||||
{{template "structs.asm" .}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; Uninitialized data: The synth object
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.SectBss "synth_object"}}
|
||||
su_synth_obj:
|
||||
resb su_synthworkspace.size
|
||||
resb {{.NumDelayLines}}*su_delayline_wrk.size
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_render_song function: the entry point for the synth
|
||||
;-------------------------------------------------------------------------------
|
||||
; Has the signature su_render_song(void *ptr), where ptr is a pointer to
|
||||
; the output buffer. Renders the compile time hard-coded song to the buffer.
|
||||
; Stack: output_ptr
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.ExportFunc "su_render_song" "OutputBufPtr"}}
|
||||
{{- if .Amd64}}
|
||||
{{- if eq .OS "windows"}}
|
||||
{{- .PushRegs "rcx" "OutputBufPtr" "rdi" "NonVolatileRsi" "rsi" "NonVolatile" "rbx" "NonVolatileRbx" "rbp" "NonVolatileRbp" | indent 4}} ; rcx = ptr to buf. rdi,rsi,rbx,rbp nonvolatile
|
||||
{{- else}} ; SystemV amd64 ABI, linux mac or hopefully something similar
|
||||
{{- .PushRegs "rdi" "OutputBufPtr" "rbx" "NonVolatileRbx" "rbp" "NonVolatileRbp" | indent 4}}
|
||||
{{- end}}
|
||||
{{- else}}
|
||||
{{- .PushRegs | indent 4}}
|
||||
{{- end}}
|
||||
{{- $prologsize := len .Stacklocs}}
|
||||
xor eax, eax
|
||||
{{- if .MultivoiceTracks}}
|
||||
{{.Push (.VoiceTrackBitmask | printf "%v") "VoiceTrackBitmask"}}
|
||||
{{- end}}
|
||||
{{.Push "1" "RandSeed"}}
|
||||
{{.Push .AX "GlobalTick"}}
|
||||
su_render_rowloop: ; loop through every row in the song
|
||||
{{.Push .AX "Row"}}
|
||||
{{.Call "su_update_voices"}} ; update instruments for the new row
|
||||
xor eax, eax ; ecx is the current sample within row
|
||||
su_render_sampleloop: ; loop through every sample in the row
|
||||
{{.Push .AX "Sample"}}
|
||||
{{- if .Polyphony}}
|
||||
{{.Push (.PolyphonyBitmask | printf "%v") "PolyphonyBitmask"}} ; does the next voice reuse the current opcodes?
|
||||
{{- end}}
|
||||
{{.Push (.Song.Patch.TotalVoices | printf "%v") "VoicesRemain"}}
|
||||
mov {{.DX}}, {{.PTRWORD}} su_synth_obj ; {{.DX}} points to the synth object
|
||||
mov {{.COM}}, {{.PTRWORD}} su_patch_code ; COM points to vm code
|
||||
mov {{.VAL}}, {{.PTRWORD}} su_patch_parameters ; VAL points to unit params
|
||||
{{- if .Opcode "delay"}}
|
||||
lea {{.CX}}, [{{.DX}} + su_synthworkspace.size - su_delayline_wrk.filtstate]
|
||||
{{- end}}
|
||||
lea {{.WRK}}, [{{.DX}} + su_synthworkspace.voices] ; WRK points to the first voice
|
||||
{{.Call "su_run_vm"}} ; run through the VM code
|
||||
{{.Pop .AX}}
|
||||
{{- if .Polyphony}}
|
||||
{{.Pop .AX}}
|
||||
{{- end}}
|
||||
{{- template "output_sound.asm" .}} ; *ptr++ = left, *ptr++ = right
|
||||
{{.Pop .AX}}
|
||||
inc dword [{{.Stack "GlobalTick"}}] ; increment global time, used by delays
|
||||
inc eax
|
||||
cmp eax, {{.Song.SamplesPerRow}}
|
||||
jl su_render_sampleloop
|
||||
{{.Pop .AX}} ; Stack: pushad ptr
|
||||
inc eax
|
||||
cmp eax, {{.Song.TotalRows}}
|
||||
jl su_render_rowloop
|
||||
; rewind the stack the entropy of multiple pop {{.AX}} is probably lower than add
|
||||
{{- $x := .}}
|
||||
{{- range (.Sub (len .Stacklocs) $prologsize | .Count)}}
|
||||
{{$x.Pop $x.AX}}
|
||||
{{- end}}
|
||||
{{- if .Amd64}}
|
||||
{{- if eq .OS "windows"}}
|
||||
; Windows64 ABI, rdi rsi rbx rbp non-volatile
|
||||
{{- .PopRegs "rcx" "rdi" "rsi" "rbx" "rbp" | indent 4}}
|
||||
{{- else}}
|
||||
; SystemV64 ABI (linux mac or hopefully something similar), rbx rbp non-volatile
|
||||
{{- .PopRegs "rdi" "rbx" "rbp" | indent 4}}
|
||||
{{- end}}
|
||||
ret
|
||||
{{- else}}
|
||||
{{- .PopRegs | indent 4}}
|
||||
ret 4
|
||||
{{- end}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_update_voices function: polyphonic & chord implementation
|
||||
;-------------------------------------------------------------------------------
|
||||
; Input: eax : current row within song
|
||||
; Dirty: pretty much everything
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_update_voices"}}
|
||||
{{- if .MultivoiceTracks}}
|
||||
; The more complicated implementation: one track can trigger multiple voices
|
||||
xor edx, edx
|
||||
mov ebx, {{.Song.PatternRows}} ; we could do xor ebx,ebx; mov bl,PATTERN_SIZE, but that would limit patternsize to 256...
|
||||
div ebx ; eax = current pattern, edx = current row in pattern
|
||||
{{.Prepare "su_tracks"}}
|
||||
lea {{.SI}}, [{{.Use "su_tracks"}}+{{.AX}}] ; esi points to the pattern data for current track
|
||||
xor eax, eax ; eax is the first voice of next track
|
||||
xor ebx, ebx ; ebx is the first voice of current track
|
||||
mov {{.BP}}, {{.PTRWORD}} su_synth_obj ; ebp points to the current_voiceno array
|
||||
su_update_voices_trackloop:
|
||||
movzx eax, byte [{{.SI}}] ; eax = current pattern
|
||||
imul eax, {{.Song.PatternRows}} ; eax = offset to current pattern data
|
||||
{{- .Prepare "su_patterns" .AX | indent 4}}
|
||||
movzx eax,byte [{{.Use "su_patterns" .AX}},{{.DX}}] ; eax = note
|
||||
push {{.DX}} ; Stack: ptrnrow
|
||||
xor edx, edx ; edx=0
|
||||
mov ecx, ebx ; ecx=first voice of the track to be done
|
||||
su_calculate_voices_loop: ; do {
|
||||
bt dword [{{.Stack "VoiceTrackBitmask"}}],ecx ; test voicetrack_bitmask// notice that the incs don't set carry
|
||||
inc edx ; edx++ // edx=numvoices
|
||||
inc ecx ; ecx++ // ecx=the first voice of next track
|
||||
jc su_calculate_voices_loop ; } while bit ecx-1 of bitmask is on
|
||||
push {{.CX}} ; Stack: next_instr ptrnrow
|
||||
cmp al, {{.Song.Hold}} ; anything but hold causes action
|
||||
je short su_update_voices_nexttrack
|
||||
mov cl, byte [{{.BP}}]
|
||||
mov edi, ecx
|
||||
add edi, ebx
|
||||
shl edi, 12 ; each unit = 64 bytes and there are 1<<MAX_UNITS_SHIFT units + small header
|
||||
{{- .Prepare "su_synth_obj" | indent 4}}
|
||||
inc dword [{{.Use "su_synth_obj"}} + su_synthworkspace.voices + su_voice.release + {{.DI}}] ; set the voice currently active to release; notice that it could increment any number of times
|
||||
cmp al, {{.Song.Hold}} ; if cl < HLD (no new note triggered)
|
||||
jl su_update_voices_nexttrack ; goto nexttrack
|
||||
inc ecx ; curvoice++
|
||||
cmp ecx, edx ; if (curvoice >= num_voices)
|
||||
jl su_update_voices_skipreset
|
||||
xor ecx,ecx ; curvoice = 0
|
||||
su_update_voices_skipreset:
|
||||
mov byte [{{.BP}}],cl
|
||||
add ecx, ebx
|
||||
shl ecx, 12 ; each unit = 64 bytes and there are 1<<6 units + small header
|
||||
lea {{.DI}},[{{.Use "su_synth_obj"}} + su_synthworkspace.voices + {{.CX}}]
|
||||
stosd ; save note
|
||||
mov ecx, (su_voice.size - su_voice.release)/4
|
||||
xor eax, eax
|
||||
rep stosd ; clear the workspace of the new voice, retriggering oscillators
|
||||
su_update_voices_nexttrack:
|
||||
pop {{.BX}} ; ebx=first voice of next instrument, Stack: ptrnrow
|
||||
pop {{.DX}} ; edx=patrnrow
|
||||
add {{.SI}}, {{.Song.SequenceLength}}
|
||||
inc {{.BP}}
|
||||
{{- $addrname := len .Song.Tracks | printf "su_synth_obj + %v"}}
|
||||
{{- .Prepare $addrname | indent 8}}
|
||||
cmp {{.BP}},{{.Use $addrname}}
|
||||
jl su_update_voices_trackloop
|
||||
ret
|
||||
{{- else}}
|
||||
; The simple implementation: each track triggers always the same voice
|
||||
xor edx, edx
|
||||
xor ebx, ebx
|
||||
mov bl, {{.Song.PatternRows}} ; rows per pattern
|
||||
div ebx ; eax = current pattern, edx = current row in pattern
|
||||
{{- .Prepare "su_tracks" | indent 4}}
|
||||
lea {{.SI}}, [{{.Use "su_tracks"}}+{{.AX}}]; esi points to the pattern data for current track
|
||||
mov {{.DI}}, {{.PTRWORD}} su_synth_obj+su_synthworkspace.voices
|
||||
mov bl, {{len .Song.Tracks}} ; MAX_TRACKS is always <= 32 so this is ok
|
||||
su_update_voices_trackloop:
|
||||
movzx eax, byte [{{.SI}}] ; eax = current pattern
|
||||
imul eax, {{.Song.PatternRows}} ; multiply by rows per pattern, eax = offset to current pattern data
|
||||
{{- .Prepare "su_patterns" .AX | indent 8}}
|
||||
movzx eax, byte [{{.Use "su_patterns" .AX}} + {{.DX}}] ; ecx = note
|
||||
cmp al, {{.Song.Hold}} ; anything but hold causes action
|
||||
je short su_update_voices_nexttrack
|
||||
inc dword [{{.DI}}+su_voice.release] ; set the voice currently active to release; notice that it could increment any number of times
|
||||
jb su_update_voices_nexttrack ; if cl < HLD (no new note triggered) goto nexttrack
|
||||
su_update_voices_retrigger:
|
||||
stosd ; save note
|
||||
mov ecx, (su_voice.size - su_voice.release)/4 ; could be xor ecx, ecx; mov ch,...>>8, but will it actually be smaller after compression?
|
||||
xor eax, eax
|
||||
rep stosd ; clear the workspace of the new voice, retriggering oscillators
|
||||
jmp short su_update_voices_skipadd
|
||||
su_update_voices_nexttrack:
|
||||
add {{.DI}}, su_voice.size
|
||||
su_update_voices_skipadd:
|
||||
add {{.SI}}, {{.Song.SequenceLength}}
|
||||
dec ebx
|
||||
jnz short su_update_voices_trackloop
|
||||
ret
|
||||
{{- end}}
|
||||
|
||||
{{template "patch.asm" .}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; Patterns
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_patterns"}}
|
||||
{{- range .Song.Patterns}}
|
||||
db {{. | toStrings | join ","}}
|
||||
{{- end}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; Tracks
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_tracks"}}
|
||||
{{- range .Song.Tracks}}
|
||||
db {{.Sequence | toStrings | join ","}}
|
||||
{{- end}}
|
||||
|
||||
{{- if gt (.Song.Patch.SampleOffsets | len) 0}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; Sample offsets
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_sample_offsets"}}
|
||||
{{- range .Song.Patch.SampleOffsets}}
|
||||
dd {{.Start}}
|
||||
dw {{.LoopStart}}
|
||||
dw {{.LoopLength}}
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
{{- if gt (.Song.Patch.DelayTimes | len ) 0}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; Delay times
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_delay_times"}}
|
||||
dw {{.Song.Patch.DelayTimes | toStrings | join ","}}
|
||||
{{end}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; The code for this patch, basically indices to vm jump table
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_patch_code"}}
|
||||
db {{.Code | toStrings | join ","}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; The parameters / inputs to each opcode
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_patch_parameters"}}
|
||||
db {{.Values | toStrings | join ","}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; Constants
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.SectData "constants"}}
|
||||
{{.Constants}}
|
126
templates/sinks.asm
Normal file
126
templates/sinks.asm
Normal file
@ -0,0 +1,126 @@
|
||||
{{- if .Opcode "out"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; OUT opcode: outputs and pops the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
{{- if .Mono "out"}}
|
||||
; Mono: add ST0 to main left port, then pop
|
||||
{{- end}}
|
||||
{{- if .Stereo "out"}}
|
||||
; Stereo: add ST0 to left out and ST1 to right out, then pop
|
||||
{{- end}}
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_out" "Opcode"}} ; l r
|
||||
mov {{.AX}}, [{{.Stack "Synth"}}] ; AX points to the synth object
|
||||
{{- if .StereoAndMono "out" }}
|
||||
jnc su_op_out_mono
|
||||
{{- end }}
|
||||
{{- if .Stereo "out" }}
|
||||
call su_op_out_mono
|
||||
add {{.AX}}, 4 ; shift from left to right channel
|
||||
su_op_out_mono:
|
||||
{{- end}}
|
||||
fmul dword [{{.Input "out" "gain"}}] ; multiply by gain
|
||||
fadd dword [{{.AX}} + su_synthworkspace.left] ; add current value of the output
|
||||
fstp dword [{{.AX}} + su_synthworkspace.left] ; store the new value of the output
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "outaux"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; OUTAUX opcode: outputs to main and aux1 outputs and pops the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: add outgain*ST0 to main left port and auxgain*ST0 to aux1 left
|
||||
; Stereo: also add outgain*ST1 to main right port and auxgain*ST1 to aux1 right
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_outaux" "Opcode"}} ; l r
|
||||
mov {{.AX}}, [{{.Stack "Synth"}}]
|
||||
{{- if .StereoAndMono "outaux" }}
|
||||
jnc su_op_outaux_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "outaux" }}
|
||||
call su_op_outaux_mono
|
||||
add {{.AX}}, 4
|
||||
su_op_outaux_mono:
|
||||
{{- end}}
|
||||
fld st0 ; l l
|
||||
fmul dword [{{.Input "outaux" "outgain"}}] ; g*l
|
||||
fadd dword [{{.AX}} + su_synthworkspace.left] ; g*l+o
|
||||
fstp dword [{{.AX}} + su_synthworkspace.left] ; o'=g*l+o
|
||||
fmul dword [{{.Input "outaux" "auxgain"}}] ; h*l
|
||||
fadd dword [{{.AX}} + su_synthworkspace.aux] ; h*l+a
|
||||
fstp dword [{{.AX}} + su_synthworkspace.aux] ; a'=h*l+a
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "aux"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; AUX opcode: outputs the signal to aux (or main) port and pops the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: add gain*ST0 to left port
|
||||
; Stereo: also add gain*ST1 to right port
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_aux" "Opcode"}} ; l r
|
||||
lodsb
|
||||
mov {{.DI}}, [{{.Stack "Synth"}}]
|
||||
{{- if .StereoAndMono "aux" }}
|
||||
jnc su_op_aux_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "aux" }}
|
||||
call su_op_aux_mono
|
||||
add {{.DI}}, 4
|
||||
su_op_aux_mono:
|
||||
{{- end}}
|
||||
fmul dword [{{.Input "aux" "gain"}}] ; g*l
|
||||
fadd dword [{{.DI}} + su_synthworkspace.left + {{.AX}}*4] ; g*l+o
|
||||
fstp dword [{{.DI}} + su_synthworkspace.left + {{.AX}}*4] ; o'=g*l+o
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "send"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; SEND opcode: adds the signal to a port
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: adds signal to a memory address, defined by a word in VAL stream
|
||||
; Stereo: also add right signal to the following address
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_send" "Opcode"}}
|
||||
lodsw
|
||||
mov {{.CX}}, [{{.Stack "Voice"}}] ; load pointer to voice
|
||||
{{- if .StereoAndMono "send"}}
|
||||
jnc su_op_send_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "send"}}
|
||||
mov {{.DI}}, {{.AX}}
|
||||
inc {{.AX}} ; send the right channel first
|
||||
fxch ; r l
|
||||
call su_op_send_mono ; (r) l
|
||||
mov {{.AX}}, {{.DI}} ; move back to original address
|
||||
test {{.AX}}, 0x8 ; if r was not popped and is still in the stack
|
||||
jnz su_op_send_mono
|
||||
fxch ; swap them back: l r
|
||||
su_op_send_mono:
|
||||
{{- end}}
|
||||
{{- if .HasParamValueOtherThan "send" "voice" 0}}
|
||||
test {{.AX}}, 0x8000
|
||||
jz su_op_send_skipglobal
|
||||
mov {{.CX}}, [{{.Stack "Synth"}}]
|
||||
su_op_send_skipglobal:
|
||||
{{- end}}
|
||||
test {{.AX}}, 0x8 ; if the SEND_POP bit is not set
|
||||
jnz su_op_send_skippush
|
||||
fld st0 ; duplicate the signal on stack: s s
|
||||
su_op_send_skippush: ; there is signal s, but maybe also another: s (s)
|
||||
fld dword [{{.Input "send" "amount"}}] ; a l (l)
|
||||
{{- .Float 0.5 | .Prepare | indent 4}}
|
||||
fsub dword [{{.Float 0.5 | .Use}}] ; a-.5 l (l)
|
||||
fadd st0 ; g=2*a-1 l (l)
|
||||
and ah, 0x7f ; eax = send address, clear the global bit
|
||||
or al, 0x8 ; set the POP bit always, at the same time shifting to ports instead of wrk
|
||||
fmulp st1, st0 ; g*l (l)
|
||||
fadd dword [{{.CX}} + {{.AX}}*4] ; g*l+L (l),where L is the current value
|
||||
fstp dword [{{.CX}} + {{.AX}}*4] ; (l)
|
||||
ret
|
||||
{{end}}
|
415
templates/sources.asm
Normal file
415
templates/sources.asm
Normal file
@ -0,0 +1,415 @@
|
||||
{{if .Opcode "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.release] ; eax = su_instrument.release
|
||||
test eax, eax ; if (eax == 0)
|
||||
je su_op_envelope_process ; goto process
|
||||
mov dword [{{.WRK}}], {{.InputNumber "envelope" "release"}} ; [state]=RELEASE
|
||||
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 .Opcode "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"}}
|
||||
fld dword [{{.Input "noise" "gain"}}]
|
||||
fmulp st1, st0
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "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
|
||||
%ifdef RUNTIME_TABLES
|
||||
%ifdef INCLUDE_SAMPLES
|
||||
mov {{.DI}}, [{{.SP}} + su_stack.sampleoffs]; we need to put this in a register, as the stereo & unisons screw the stack positions
|
||||
%endif ; ain't we lucky that {{.DI}} was unused throughout
|
||||
%endif
|
||||
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
|
||||
call su_op_oscillat_mono ; r d
|
||||
add {{.WRK}}, 4 ; state vars: r1 l1 r2 l2 r3 l3 r4 l4, for the unison osc phases
|
||||
fxch ; d r
|
||||
fchs ; -d r, negate the detune for second round
|
||||
su_op_oscillat_mono:
|
||||
{{- end}}
|
||||
{{- if .HasParamValueOtherThan "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
|
||||
fld dword [{{.Input "oscillator" "phase"}}] ; p s
|
||||
{{.Int 0x3DAAAAAA | .Prepare}}
|
||||
fadd dword [{{.Int 0x3DAAAAAA | .Use}}] ; 1/128 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
|
||||
test al, byte 0x08
|
||||
jnz su_op_oscillat_skipnote
|
||||
fiadd dword [{{.INP}}-su_voice.inputs+su_voice.note] ; // st0 is note, st1 is t+d offset
|
||||
su_op_oscillat_skipnote:
|
||||
{{- .Int 0x3DAAAAAA | .Prepare}}
|
||||
fmul dword [{{.Int 0x3DAAAAAA | .Use}}]
|
||||
{{.Call "su_power"}}
|
||||
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:
|
||||
{{- .Float 0.000092696138 | .Prepare}}
|
||||
fmul dword [{{.Float 0.000092696138 | .Use}}] ; // st0 is now frequency
|
||||
su_op_oscillat_normalized:
|
||||
fadd dword [{{.WRK}}]
|
||||
fst dword [{{.WRK}}]
|
||||
fadd dword [{{.Input "oscillator" "phase"}}]
|
||||
{{- if .HasParamValue "oscillator" "type" .Sample}}
|
||||
test al, byte 0x80
|
||||
jz short su_op_oscillat_not_sample
|
||||
{{.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
|
||||
fadd st1, st0
|
||||
fxch
|
||||
fprem
|
||||
fstp st1
|
||||
fld dword [{{.Input "oscillator" "color"}}] ; // c p
|
||||
; every oscillator test included if needed
|
||||
{{- if .HasParamValue "oscillator" "type" .Sine}}
|
||||
test al, byte 0x40
|
||||
jz short su_op_oscillat_notsine
|
||||
{{.Call "su_oscillat_sine"}}
|
||||
su_op_oscillat_notsine:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "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 .HasParamValue "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 .HasParamValue "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:
|
||||
fld dword [{{.Input "oscillator" "gain"}}]
|
||||
fmulp st1, st0
|
||||
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 go4kVCO_gate_bit ; goto gate_bit
|
||||
fsub st0, st0 ; stack: 0
|
||||
go4kVCO_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" | 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
|
||||
%ifdef RUNTIME_TABLES ; when using RUNTIME_TABLES, assumed the sample_offset ptr is in {{.DI}}
|
||||
do{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
|
||||
%endif
|
||||
{{- .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 | indent 4}}
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "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 .Opcode "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 .Opcode "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}}
|
43
templates/structs.asm
Normal file
43
templates/structs.asm
Normal file
@ -0,0 +1,43 @@
|
||||
;-------------------------------------------------------------------------------
|
||||
; unit struct
|
||||
;-------------------------------------------------------------------------------
|
||||
struc su_unit
|
||||
.state resd 8
|
||||
.ports resd 8
|
||||
.size:
|
||||
endstruc
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; voice struct
|
||||
;-------------------------------------------------------------------------------
|
||||
struc su_voice
|
||||
.note resd 1
|
||||
.release resd 1
|
||||
.inputs resd 8
|
||||
.reserved resd 6 ; this is done to so the whole voice is 2^n long, see polyphonic player
|
||||
.workspace resb 63 * su_unit.size
|
||||
.size:
|
||||
endstruc
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; synthworkspace struct
|
||||
;-------------------------------------------------------------------------------
|
||||
struc su_synthworkspace
|
||||
.curvoices resb 32 ; these are used by the multitrack player to store which voice is playing on which track
|
||||
.left resd 1
|
||||
.right resd 1
|
||||
.aux resd 6 ; 3 auxiliary signals
|
||||
.voices resb 32 * su_voice.size
|
||||
.size:
|
||||
endstruc
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_delayline_wrk struct
|
||||
;-------------------------------------------------------------------------------
|
||||
struc su_delayline_wrk
|
||||
.dcin resd 1
|
||||
.dcout resd 1
|
||||
.filtstate resd 1
|
||||
.buffer resd 65536
|
||||
.size:
|
||||
endstruc
|
Reference in New Issue
Block a user