sointu/vm/compiler/templates/wasm/effects.wat
5684185+vsariola@users.noreply.github.com 01bf409929 refactor(vm): rename Commands/Values to Opcodes/Operands
The commands and values were not very good names to what the
byte sequences actually are: opcodes and their operands. In
many other places, we were already calling the byte in the Command
stream as Opcode, so a logical name for a sequence of these is
Opcodes. Values is such a generic name that it's not immediately
clear that this sequence is related to the opcodes. Operands is not
perfect but clearly suggests that this sequence is related to
the Opcodes.
2023-10-18 19:53:47 +03:00

479 lines
19 KiB
Plaintext

{{- 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 "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
(local.set $delayTime (f32.convert_i32_u (i32.load16_u
offset={{index .Labels "su_delay_times"}}
(local.get $delayIndex)
)))
{{- 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}}
{{- if .SupportsModulation "delay" "delaytime"}}
(i32.trunc_f32_u (f32.add
(f32.add
(local.get $delayTime)
(f32.mul
(f32.load offset={{.InputNumber "delay" "delaytime" | mul 4 | add 32}} (global.get $WRK))
(f32.const 32767)
)
)
(f32.const 0.5)
))
{{- else}}
(i32.trunc_f32_u (f32.add (local.get $delayTime) (f32.const 0.5)))
{{- end}}
{{- 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<level) (let's call it c)
(local.tee $level (f32.add ;; l'=l + c*(x^2-l)
(f32.mul ;; c was already on stack, so c*(x^2-l)
(f32.sub ;; x^2-l
(local.get $x2)
(local.get $level)
)
)
(local.get $level)
))
(local.tee $t2 (f32.mul ;; t^2
(call $input (i32.const {{.InputNumber "compressor" "threshold"}}))
(call $input (i32.const {{.InputNumber "compressor" "threshold"}}))
))
(if (f32.gt) (then ;; if $level > $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}}