{{- if .HasOp "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 (param $stereo i32) {{- if .Stereo "distort"}} (call $stereoHelper (local.get $stereo) (i32.const {{div (.GetOp "distort") 2}})) {{- end}} (call $pop) (call $waveshaper (call $input (i32.const {{.InputNumber "distort" "drive"}}))) (call $push) ) {{end}} {{- if .HasOp "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 (param $stereo i32) (local $phase f32) {{- if .Stereo "hold"}} (call $stereoHelper (local.get $stereo) (i32.const {{div (.GetOp "hold") 2}})) {{- end}} (local.set $phase (f32.sub (f32.load (global.get $WRK)) (f32.mul (call $input (i32.const {{.InputNumber "hold" "holdfreq"}})) (call $input (i32.const {{.InputNumber "hold" "holdfreq"}})) ;; if we ever implement $dup, replace with that ) ) ) (if (f32.ge (f32.const 0) (local.get $phase)) (then (f32.store offset=4 (global.get $WRK) (call $peek)) ;; we start holding a new value (local.set $phase (f32.add (local.get $phase) (f32.const 1))) )) (drop (call $pop)) ;; we replace the top most signal (call $push (f32.load offset=4 (global.get $WRK))) ;; with the held value (f32.store (global.get $WRK) (local.get $phase)) ;; save back new phase ) {{end}} {{- if .HasOp "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 (param $stereo i32) (local $e f32) {{- if .Stereo "crush"}} (call $stereoHelper (local.get $stereo) (i32.const {{div (.GetOp "crush") 2}})) {{- end}} call $pop (f32.div (local.tee $e (call $nonLinearMap (i32.const {{.InputNumber "crush" "resolution"}})))) f32.nearest (f32.mul (local.get $e)) call $push ) {{end}} {{- if .HasOp "gain"}} ;;------------------------------------------------------------------------------- ;; GAIN opcode: apply gain on the signal ;;------------------------------------------------------------------------------- ;; Mono: x -> x*g ;; Stereo: l r -> l*g r*g ;;------------------------------------------------------------------------------- (func $su_op_gain (param $stereo i32) {{- if .Stereo "gain"}} (call $stereoHelper (local.get $stereo) (i32.const {{div (.GetOp "gain") 2}})) {{- end}} (call $push (f32.mul (call $pop) (call $input (i32.const {{.InputNumber "gain" "gain"}})))) ) {{end}} {{- if .HasOp "invgain"}} ;;------------------------------------------------------------------------------- ;; INVGAIN opcode: apply inverse gain on the signal ;;------------------------------------------------------------------------------- ;; Mono: x -> x/g ;; Stereo: l r -> l/g r/g ;;------------------------------------------------------------------------------- (func $su_op_invgain (param $stereo i32) {{- if .Stereo "invgain"}} (call $stereoHelper (local.get $stereo) (i32.const {{div (.GetOp "invgain") 2}})) {{- end}} (call $push (f32.div (call $pop) (call $input (i32.const {{.InputNumber "invgain" "invgain"}})))) ) {{end}} {{- if .HasOp "dbgain"}} ;;------------------------------------------------------------------------------- ;; DBGAIN opcode: apply gain on the signal, with gain given in decibels ;;------------------------------------------------------------------------------- ;; Mono: x -> x*g, where g = 2**((2*d-1)*6.643856189774724) i.e. -40dB to 40dB, d=[0..1] ;; Stereo: l r -> l*g r*g ;;------------------------------------------------------------------------------- (func $su_op_dbgain (param $stereo i32) {{- if .Stereo "dbgain"}} (call $stereoHelper (local.get $stereo) (i32.const {{div (.GetOp "dbgain") 2}})) {{- end}} (call $input (i32.const {{.InputNumber "dbgain" "decibels"}})) (f32.sub (f32.const 0.5)) (f32.mul (f32.const 13.287712379549449)) (call $pow2) (f32.mul (call $pop)) (call $push) ) {{end}} {{- if .HasOp "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 (param $stereo i32) (local $flags i32) (local $freq f32) (local $high f32) (local $low f32) (local $band f32) (local $retval f32) {{- if .Stereo "filter"}} (call $stereoHelper (local.get $stereo) (i32.const {{div (.GetOp "filter") 2}})) (if (local.get $stereo)(then ;; This is hacky: rewind the $VAL one byte backwards as the right channel already ;; scanned it once. Find a way to avoid rewind (global.set $VAL (i32.sub (global.get $VAL) (i32.const 1))) )) {{- end}} (local.set $flags (call $scanOperand)) (local.set $freq (f32.mul (call $input (i32.const {{.InputNumber "filter" "frequency"}})) (call $input (i32.const {{.InputNumber "filter" "frequency"}})) )) (local.set $low ;; l' = f2*b + l (f32.add ;; f2*b+l (f32.mul ;; f2*b (local.tee $band (f32.load offset=4 (global.get $WRK))) ;; b (local.get $freq) ;; f2 ) (f32.load (global.get $WRK)) ;; l ) ) (local.set $high ;; h' = x - l' - r*b (f32.sub ;; x - l' - r*b (f32.sub ;; x - l' (call $pop) ;; x (signal) (local.get $low) ;; l' ) (f32.mul ;; r*b (call $input (i32.const {{.InputNumber "filter" "resonance"}})) ;; r (local.get $band) ;; b ) ) ) (local.set $band ;; b' = f2 * h' + b (f32.add ;; f2 * h' + b (f32.mul ;; f2 * h' (local.get $freq) ;; f2 (local.get $high) ;; h' ) (local.get $band) ;; b ) ) (local.set $retval (f32.const 0)) {{- if .SupportsParamValue "filter" "lowpass" 1}} (if (i32.and (local.get $flags) (i32.const 0x40)) (then (local.set $retval (f32.add (local.get $retval) (local.get $low))) )) {{- end}} {{- if .SupportsParamValue "filter" "bandpass" 1}} (if (i32.and (local.get $flags) (i32.const 0x20)) (then (local.set $retval (f32.add (local.get $retval) (local.get $band))) )) {{- end}} {{- if .SupportsParamValue "filter" "highpass" 1}} (if (i32.and (local.get $flags) (i32.const 0x10)) (then (local.set $retval (f32.add (local.get $retval) (local.get $high))) )) {{- end}} {{- if .SupportsParamValue "filter" "negbandpass" 1}} (if (i32.and (local.get $flags) (i32.const 0x08)) (then (local.set $retval (f32.sub (local.get $retval) (local.get $band))) )) {{- end}} {{- if .SupportsParamValue "filter" "neghighpass" 1}} (if (i32.and (local.get $flags) (i32.const 0x04)) (then (local.set $retval (f32.sub (local.get $retval) (local.get $high))) )) {{- end}} (f32.store (global.get $WRK) (local.get $low)) (f32.store offset=4 (global.get $WRK) (local.get $band)) (call $push (local.get $retval)) ) {{end}} {{- if .HasOp "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 (param $stereo i32) {{- if .Stereo "clip"}} (call $stereoHelper (local.get $stereo) (i32.const {{div (.GetOp "clip") 2}})) {{- end}} (call $push (call $clip (call $pop))) ) {{end}} {{- if .HasOp "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 (param $stereo i32) {{- if .Stereo "pan"}} (if (i32.eqz (local.get $stereo)) (then ;; this time, if this is mono op... call $peek ;; ...we duplicate the mono into stereo first call $push )) (call $pop) ;; F: r P: l (call $pop) ;; F: P: r l (call $input (i32.const {{.InputNumber "pan" "panning"}})) ;; F: P: p r l f32.mul ;; F: P: p*r l (call $push) ;; F: p*r P: l f32.const 1 (call $input (i32.const {{.InputNumber "pan" "panning"}})) ;; F: p*r P: p 1 l f32.sub ;; F: p*r P: 1-p l f32.mul ;; F: p*r P: (1-p)*l (call $push) ;; F: (1-p)*l p*r {{- else}} (call $peek) ;; F: s P: s (f32.mul (call $input (i32.const {{.InputNumber "pan" "panning"}})) (call $pop) ) ;; F: P: p*s s (call $push) ;; F: p*s P: s (call $peek) ;; F: p*s P: p*s s f32.sub ;; F: p*s P: s-p*s (call $push) ;; F: (1-p)*s p*s {{- end}} ) {{end}} {{- if .HasOp "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 (param $stereo i32) (local $delayIndex i32) (local $delayCount i32) (local $output f32) (local $s f32) (local $filtstate f32) {{- if .Stereo "delay"}} (local $delayCountStash i32) {{- end}} {{- if or (.SupportsModulation "delay" "delaytime") (.SupportsParamValue "delay" "notetracking" 1)}} (local $delayTime f32) {{- end}} (local.set $delayIndex (i32.mul (call $scanOperand) (i32.const 2))) {{- if .Stereo "delay"}} (local.set $delayCountStash (call $scanOperand)) (if (local.get $stereo)(then (call $su_op_xch (i32.const 0)) )) loop $stereoLoop (local.set $delayCount (local.get $delayCountStash)) {{- else}} (local.set $delayCount (call $scanOperand)) {{- end}} (local.set $output (f32.mul (call $input (i32.const {{.InputNumber "delay" "dry"}})) (call $peek) )) loop $delayLoop (local.tee $s (f32.load offset=12 (i32.add ;; delayWRK + ((globalTick-delaytimes[delayIndex])&65535)*4 (i32.mul ;; ((globalTick-delaytimes[delayIndex])&65535)*4 (i32.and ;; (globalTick-delaytimes[delayIndex])&65535 (i32.sub ;; globalTick-delaytimes[delayIndex] (global.get $globaltick) {{- if or (.SupportsModulation "delay" "delaytime") (.SupportsParamValue "delay" "notetracking" 1)}} ;; delaytime modulation or note syncing require computing the delay time in floats {{- if .SupportsModulation "delay" "delaytime"}} (local.set $delayTime (f32.add (f32.convert_i32_u (i32.load16_u offset={{index .Labels "su_delay_times"}} (local.get $delayIndex) )) (f32.mul (f32.load offset={{.InputNumber "delay" "delaytime" | mul 4 | add 32}} (global.get $WRK)) (f32.const 32767) ) )) {{- else}} (local.set $delayTime (f32.convert_i32_u (i32.load16_u offset={{index .Labels "su_delay_times"}} (local.get $delayIndex) ))) {{- end}} {{- if .SupportsParamValue "delay" "notetracking" 1}} (if (i32.eqz (i32.and (local.get $delayCount) (i32.const 1)))(then (local.set $delayTime (f32.div (local.get $delayTime) (call $pow2 (f32.mul (f32.convert_i32_u (i32.load (global.get $voice))) (f32.const 0.08333333) ) ) )) )) {{- end}} (i32.trunc_f32_s (f32.add (local.get $delayTime) (f32.const 0.5))) {{- else}} (i32.load16_u offset={{index .Labels "su_delay_times"}} (local.get $delayIndex) ) {{- end}} ) (i32.const 65535) ) (i32.const 4) ) (global.get $delayWRK) ) )) (local.set $output (f32.add (local.get $output))) (f32.store (global.get $delayWRK) (local.tee $filtstate (f32.add (f32.mul (f32.sub (f32.load (global.get $delayWRK)) (local.get $s) ) (call $input (i32.const {{.InputNumber "delay" "damp"}})) ) (local.get $s) ) ) ) (f32.store offset=12 (i32.add ;; delayWRK + globalTick*4 (i32.mul ;; globalTick)&65535)*4 (i32.and ;; globalTick&65535 (global.get $globaltick) (i32.const 65535) ) (i32.const 4) ) (global.get $delayWRK) ) (f32.add (f32.mul (call $input (i32.const {{.InputNumber "delay" "feedback"}})) (local.get $filtstate) ) (f32.mul (f32.mul (call $input (i32.const {{.InputNumber "delay" "pregain"}})) (call $input (i32.const {{.InputNumber "delay" "pregain"}})) ) (call $peek) ) ) ) (global.set $delayWRK (i32.add (global.get $delayWRK) (i32.const 262156))) (local.set $delayIndex (i32.add (local.get $delayIndex) (i32.const 2))) (br_if $delayLoop (i32.gt_s (local.tee $delayCount (i32.sub (local.get $delayCount) (i32.const 2))) (i32.const 0))) end (f32.store offset=4 (global.get $delayWRK) (local.tee $filtstate (f32.add (local.get $output) (f32.sub (f32.mul (f32.const 0.99609375) (f32.load offset=4 (global.get $delayWRK)) ) (f32.load offset=8 (global.get $delayWRK)) ) ) ) ) (f32.store offset=8 (global.get $delayWRK) (local.get $output) ) (drop (call $pop)) (call $push (local.get $filtstate)) {{- if .Stereo "delay"}} (call $su_op_xch (i32.const 0)) (br_if $stereoLoop (i32.eqz (local.tee $stereo (i32.eqz (local.get $stereo))))) end (call $su_op_xch (i32.const 0)) {{- end}} {{- if .SupportsModulation "delay" "delaytime"}} (f32.store offset={{.InputNumber "delay" "delaytime" | mul 4 | add 32}} (global.get $WRK) (f32.const 0)) {{- end}} ) {{end}} {{- if .HasOp "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 (param $stereo i32) (local $x2 f32) (local $level f32) (local $t2 f32) {{- if .Stereo "compressor"}} (local.set $x2 (f32.mul (call $peek) (call $peek) )) (if (local.get $stereo)(then (call $pop) (local.set $x2 (f32.add (local.get $x2) (f32.mul (call $peek) (call $peek) ) )) (call $push) )) (local.get $x2) {{- else}} (local.tee $x2 (f32.mul (call $peek) (call $peek) )) {{- end}} (local.tee $level (f32.load (global.get $WRK))) f32.lt call $nonLinearMap ;; $nonlinearMap(x^2 $threshold, note the local.tees (call $push (call $pow ;; (t^2/l)^(r/2) (f32.div ;; t^2/l (local.get $t2) (local.get $level) ) (f32.mul ;; r/2 (call $input (i32.const {{.InputNumber "compressor" "ratio"}})) ;; r (f32.const 0.5) ;; 0.5 ) ) ) )(else (call $push (f32.const 1)) ;; unity gain if we are below threshold )) (call $push (f32.div ;; apply post-gain ("make up gain") (call $pop) (call $input (i32.const {{.InputNumber "compressor" "invgain"}})) )) {{- if .Stereo "compressor"}} (if (local.get $stereo)(then (call $push (call $peek)) )) {{- end}} (f32.store (global.get $WRK) (local.get $level)) ;; save the updated levels ) {{- end}}