mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-18 21:14:31 -04:00
feat(vm/compiler): embed templates to executable
This commit is contained in:
parent
d2ddba3944
commit
8ffe4a70dd
239
vm/compiler/templates/wasm/arithmetic.wat
Normal file
239
vm/compiler/templates/wasm/arithmetic.wat
Normal file
@ -0,0 +1,239 @@
|
||||
{{- if .HasOp "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 (param $stereo i32)
|
||||
{{- if .Stereo "pop"}}
|
||||
(if (local.get $stereo) (then
|
||||
(drop (call $pop))
|
||||
))
|
||||
{{- end}}
|
||||
(drop (call $pop))
|
||||
)
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasOp "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 (param $stereo i32)
|
||||
{{- if .StereoAndMono "add"}}
|
||||
(if (local.get $stereo) (then
|
||||
{{- end}}
|
||||
{{- if .Stereo "add"}}
|
||||
call $pop ;; F: b c d P: a
|
||||
call $pop ;; F: c d P: b a
|
||||
call $peek2;; F: c d P: d b a
|
||||
f32.add ;; F: c d P: b+d a
|
||||
call $push ;; F: b+d c d P: a
|
||||
call $peek2;; F: b+d c d P: c a
|
||||
f32.add ;; F: b+d c d P: a+c
|
||||
call $push ;; F: a+c b+d c d P:
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "add"}}
|
||||
)(else
|
||||
{{- end}}
|
||||
{{- if .Mono "add"}}
|
||||
(call $push (f32.add (call $pop) (call $peek)))
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "add"}}
|
||||
))
|
||||
{{- end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasOp "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 (param $stereo i32)
|
||||
{{- if .StereoAndMono "addp"}}
|
||||
(if (local.get $stereo) (then
|
||||
{{- end}}
|
||||
{{- if .Stereo "addp"}}
|
||||
call $pop ;; a
|
||||
call $pop ;; b a
|
||||
call $swap ;; a b
|
||||
call $pop ;; c a b
|
||||
f32.add ;; c+a b
|
||||
call $swap ;; b c+a
|
||||
call $pop ;; d b c+a
|
||||
f32.add ;; d+b c+a
|
||||
call $push ;; c+a
|
||||
call $push
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "addp"}}
|
||||
)(else
|
||||
{{- end}}
|
||||
{{- if .Mono "addp"}}
|
||||
(call $push (f32.add (call $pop) (call $pop)))
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "addp"}}
|
||||
))
|
||||
{{- end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasOp "loadnote"}}
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; LOADNOTE opcode: load the current note, scaled to [-1,1]
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $su_op_loadnote (param $stereo i32)
|
||||
{{- if .Stereo "loadnote"}}
|
||||
(if (local.get $stereo) (then
|
||||
(call $su_op_loadnote (i32.const 0))
|
||||
))
|
||||
{{- end}}
|
||||
(f32.convert_i32_u (i32.load (global.get $voice)))
|
||||
(f32.mul (f32.const 0.015625))
|
||||
(f32.sub (f32.const 1))
|
||||
(call $push)
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{- if .HasOp "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 (param $stereo i32)
|
||||
{{- if .StereoAndMono "mul"}}
|
||||
(if (local.get $stereo) (then
|
||||
{{- end}}
|
||||
{{- if .Stereo "mul"}}
|
||||
call $pop ;; F: b c d P: a
|
||||
call $pop ;; F: c d P: b a
|
||||
call $peek2;; F: c d P: d b a
|
||||
f32.mul ;; F: c d P: b*d a
|
||||
call $push ;; F: b*d c d P: a
|
||||
call $peek2;; F: b*d c d P: c a
|
||||
f32.mul ;; F: b*d c d P: a*c
|
||||
call $push ;; F: a*c b*d c d P:
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "mul"}}
|
||||
)(else
|
||||
{{- end}}
|
||||
{{- if .Mono "mul"}}
|
||||
(call $push (f32.mul (call $pop) (call $peek)))
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "mul"}}
|
||||
))
|
||||
{{- end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasOp "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 (param $stereo i32)
|
||||
{{- if .StereoAndMono "mulp"}}
|
||||
(if (local.get $stereo) (then
|
||||
{{- end}}
|
||||
{{- if .Stereo "mulp"}}
|
||||
call $pop ;; a
|
||||
call $pop ;; b a
|
||||
call $swap ;; a b
|
||||
call $pop ;; c a b
|
||||
f32.mul ;; c*a b
|
||||
call $swap ;; b c*a
|
||||
call $pop ;; d b c*a
|
||||
f32.mul ;; d*b c*a
|
||||
call $push ;; c*a
|
||||
call $push
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "mulp"}}
|
||||
)(else
|
||||
{{- end}}
|
||||
{{- if .Mono "mulp"}}
|
||||
(call $push (f32.mul (call $pop) (call $pop)))
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "mulp"}}
|
||||
))
|
||||
{{- end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasOp "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 (param $stereo i32)
|
||||
{{- if .Stereo "push"}}
|
||||
(if (local.get $stereo) (then
|
||||
(call $push (call $peek))
|
||||
))
|
||||
{{- end}}
|
||||
(call $push (call $peek))
|
||||
)
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if or (.HasOp "xch") (.Stereo "delay")}}
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; 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 (param $stereo i32)
|
||||
call $pop
|
||||
call $pop
|
||||
{{- if .StereoAndMono "xch"}}
|
||||
(if (local.get $stereo) (then
|
||||
{{- end}}
|
||||
{{- if .Stereo "xch"}}
|
||||
call $pop ;; F: d P: c b a
|
||||
call $swap ;; F: d P: b c a
|
||||
call $pop ;; F: P: d b c a
|
||||
call $swap ;; F: P: b d c a
|
||||
call $push ;; F: b P: d c a
|
||||
call $push ;; F: d b P: c a
|
||||
call $swap ;; F: d b P: a c
|
||||
call $pop ;; F: b P: d a c
|
||||
call $swap ;; F: b P: a d c
|
||||
call $push ;; F: a b P: d c
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "xch"}}
|
||||
)(else
|
||||
{{- end}}
|
||||
{{- if or (.Mono "xch") (.Stereo "delay")}}
|
||||
call $swap
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "xch"}}
|
||||
))
|
||||
{{- end}}
|
||||
call $push
|
||||
call $push
|
||||
)
|
||||
{{end}}
|
478
vm/compiler/templates/wasm/effects.wat
Normal file
478
vm/compiler/templates/wasm/effects.wat
Normal file
@ -0,0 +1,478 @@
|
||||
{{- 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)
|
||||
{{- if .Stereo "crush"}}
|
||||
(call $stereoHelper (local.get $stereo) (i32.const {{div (.GetOp "crush") 2}}))
|
||||
{{- end}}
|
||||
call $pop
|
||||
(f32.div (call $input (i32.const {{.InputNumber "crush" "resolution"}})))
|
||||
f32.nearest
|
||||
(f32.mul (call $input (i32.const {{.InputNumber "crush" "resolution"}})))
|
||||
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 $scanValueByte))
|
||||
(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 $scanValueByte) (i32.const 2)))
|
||||
{{- if .Stereo "delay"}}
|
||||
(local.set $delayCountStash (call $scanValueByte))
|
||||
(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 $scanValueByte))
|
||||
{{- 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}}
|
19
vm/compiler/templates/wasm/output_sound.wat
Normal file
19
vm/compiler/templates/wasm/output_sound.wat
Normal file
@ -0,0 +1,19 @@
|
||||
{{- if not .Output16Bit }}
|
||||
(i64.store (global.get $outputBufPtr) (i64.load (i32.const {{index .Labels "su_globalports"}}))) ;; load the sample from left & right channels as one 64bit int and store it in the address pointed by outputBufPtr
|
||||
(global.set $outputBufPtr (i32.add (global.get $outputBufPtr) (i32.const 8))) ;; advance outputbufptr
|
||||
{{- else }}
|
||||
(local.set $channel (i32.const 0))
|
||||
loop $channelLoop
|
||||
(i32.store16 (global.get $outputBufPtr) (i32.trunc_f32_s
|
||||
(f32.mul
|
||||
(call $clip
|
||||
(f32.load offset={{index .Labels "su_globalports"}} (i32.mul (local.get $channel) (i32.const 4)))
|
||||
)
|
||||
(f32.const 32767)
|
||||
)
|
||||
))
|
||||
(global.set $outputBufPtr (i32.add (global.get $outputBufPtr) (i32.const 2)))
|
||||
(br_if $channelLoop (local.tee $channel (i32.eqz (local.get $channel))))
|
||||
end
|
||||
{{- end }}
|
||||
(i64.store (i32.const {{index .Labels "su_globalports"}}) (i64.const 0)) ;; clear the left and right ports
|
144
vm/compiler/templates/wasm/patch.wat
Normal file
144
vm/compiler/templates/wasm/patch.wat
Normal file
@ -0,0 +1,144 @@
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; su_run_vm function: runs the entire virtual machine once, creating 1 sample
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $su_run_vm (local $opcodeWithStereo i32) (local $opcode i32) (local $paramNum i32) (local $paramX4 i32) (local $WRKplusparam i32)
|
||||
loop $vm_loop
|
||||
(local.set $opcodeWithStereo (i32.load8_u (global.get $COM)))
|
||||
(global.set $COM (i32.add (global.get $COM) (i32.const 1))) ;; move to next instruction
|
||||
(global.set $WRK (i32.add (global.get $WRK) (i32.const 64))) ;; move WRK to next unit
|
||||
(if (local.tee $opcode (i32.shr_u (local.get $opcodeWithStereo) (i32.const 1)))(then ;; if $opcode = $opcodeStereo >> 1; $opcode != 0 {
|
||||
(local.set $paramNum (i32.const 0))
|
||||
(local.set $paramX4 (i32.const 0))
|
||||
loop $transform_values_loop
|
||||
{{- $addr := sub (index .Labels "su_vm_transformcounts") 1}}
|
||||
(if (i32.lt_u (local.get $paramNum) (i32.load8_u offset={{$addr}} (local.get $opcode)))(then ;;(i32.ge (local.get $paramNum) (i32.load8_u (local.get $opcode))) /*TODO: offset to transformvalues
|
||||
(local.set $WRKplusparam (i32.add (global.get $WRK) (local.get $paramX4)))
|
||||
(f32.store offset={{index .Labels "su_transformedvalues"}}
|
||||
(local.get $paramX4)
|
||||
(f32.add
|
||||
(f32.mul
|
||||
(f32.convert_i32_u (call $scanValueByte))
|
||||
(f32.const 0.0078125) ;; scale from 0-128 to 0.0 - 1.0
|
||||
)
|
||||
(f32.load offset=32 (local.get $WRKplusparam)) ;; add modulation
|
||||
)
|
||||
)
|
||||
(f32.store offset=32 (local.get $WRKplusparam) (f32.const 0.0)) ;; clear modulations
|
||||
(local.set $paramNum (i32.add (local.get $paramNum) (i32.const 1))) ;; $paramNum++
|
||||
(local.set $paramX4 (i32.add (local.get $paramX4) (i32.const 4)))
|
||||
br $transform_values_loop ;; continue looping
|
||||
))
|
||||
;; paramNum was >= the number of parameters to transform, exiting loop
|
||||
end
|
||||
(call_indirect (type $opcode_func_signature) (i32.and (local.get $opcodeWithStereo) (i32.const 1)) (local.get $opcode))
|
||||
)(else ;; advance to next voice
|
||||
(global.set $voice (i32.add (global.get $voice) (i32.const 4096))) ;; advance to next voice
|
||||
(global.set $WRK (global.get $voice)) ;; set WRK point to beginning of voice
|
||||
(global.set $voicesRemain (i32.sub (global.get $voicesRemain) (i32.const 1)))
|
||||
{{- if .SupportsPolyphony}}
|
||||
(if (i32.and (i32.shr_u (i32.const {{.PolyphonyBitmask | printf "%v"}}) (global.get $voicesRemain)) (i32.const 1))(then
|
||||
(global.set $VAL (global.get $VAL_instr_start))
|
||||
(global.set $COM (global.get $COM_instr_start))
|
||||
))
|
||||
(global.set $VAL_instr_start (global.get $VAL))
|
||||
(global.set $COM_instr_start (global.get $COM))
|
||||
{{- end}}
|
||||
(br_if 2 (i32.eqz (global.get $voicesRemain))) ;; if no more voices remain, return from function
|
||||
))
|
||||
br $vm_loop
|
||||
end
|
||||
)
|
||||
|
||||
{{- template "arithmetic.wat" .}}
|
||||
{{- template "effects.wat" .}}
|
||||
{{- template "sources.wat" .}}
|
||||
{{- template "sinks.wat" .}}
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; $input returns the float value of a transformed to 0.0 - 1.0f range.
|
||||
;; The transformed values start at 512 (TODO: change magic constants somehow)
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $input (param $inputNumber i32) (result f32)
|
||||
(f32.load offset={{index .Labels "su_transformedvalues"}} (i32.mul (local.get $inputNumber) (i32.const 4)))
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; $inputSigned returns the float value of a transformed to -1.0 - 1.0f range.
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $inputSigned (param $inputNumber i32) (result f32)
|
||||
(f32.sub (f32.mul (call $input (local.get $inputNumber)) (f32.const 2)) (f32.const 1))
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; $nonLinearMap: x -> 2^(-24*input[x])
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $nonLinearMap (param $value i32) (result f32)
|
||||
(call $pow2
|
||||
(f32.mul
|
||||
(f32.const -24)
|
||||
(call $input (local.get $value))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; $pow2: x -> 2^x
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $pow2 (param $value f32) (result f32)
|
||||
(call $pow (f32.const 2) (local.get $value))
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; Waveshaper(x,a): "distorts" signal x by amount a
|
||||
;; Returns x*a/(1-a+(2*a-1)*abs(x))
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $waveshaper (param $signal f32) (param $amount f32) (result f32)
|
||||
(local.set $signal (call $clip (local.get $signal)))
|
||||
(f32.mul
|
||||
(local.get $signal)
|
||||
(f32.div
|
||||
(local.get $amount)
|
||||
(f32.add
|
||||
(f32.const 1)
|
||||
(f32.sub
|
||||
(f32.mul
|
||||
(f32.sub
|
||||
(f32.add (local.get $amount) (local.get $amount))
|
||||
(f32.const 1)
|
||||
)
|
||||
(f32.abs (local.get $signal))
|
||||
)
|
||||
(local.get $amount)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; Clip(a : f32) returns min(max(a,-1),1)
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $clip (param $value f32) (result f32)
|
||||
(f32.min (f32.max (local.get $value) (f32.const -1.0)) (f32.const 1.0))
|
||||
)
|
||||
|
||||
(func $stereoHelper (param $stereo i32) (param $tableIndex i32)
|
||||
(if (local.get $stereo)(then
|
||||
(call $pop)
|
||||
(global.set $WRK (i32.add (global.get $WRK) (i32.const 16)))
|
||||
(call_indirect (type $opcode_func_signature) (i32.const 0) (local.get $tableIndex))
|
||||
(global.set $WRK (i32.sub (global.get $WRK) (i32.const 16)))
|
||||
(call $push)
|
||||
))
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; 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.
|
||||
;;-------------------------------------------------------------------------------
|
||||
(table {{.Instructions | len | add 1}} funcref)
|
||||
(elem (i32.const 1) ;; start the indices at 1, as 0 is reserved for advance
|
||||
{{- range .Instructions}}
|
||||
$su_op_{{.}}
|
||||
{{- end}}
|
||||
)
|
335
vm/compiler/templates/wasm/player.wat
Normal file
335
vm/compiler/templates/wasm/player.wat
Normal file
@ -0,0 +1,335 @@
|
||||
(module
|
||||
|
||||
{{- /*
|
||||
;------------------------------------------------------------------------------
|
||||
; Patterns
|
||||
;-------------------------------------------------------------------------------
|
||||
*/}}
|
||||
{{- .SetDataLabel "su_patterns"}}
|
||||
{{- $m := .}}
|
||||
{{- range .Patterns}}
|
||||
{{- range .}}
|
||||
{{- $.DataB .}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
|
||||
{{- /*
|
||||
;------------------------------------------------------------------------------
|
||||
; Tracks
|
||||
;-------------------------------------------------------------------------------
|
||||
*/}}
|
||||
{{- .SetDataLabel "su_tracks"}}
|
||||
{{- $m := .}}
|
||||
{{- range .Sequences}}
|
||||
{{- range .}}
|
||||
{{- $.DataB .}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
|
||||
{{- /*
|
||||
;------------------------------------------------------------------------------
|
||||
; The code for this patch, basically indices to vm jump table
|
||||
;-------------------------------------------------------------------------------
|
||||
*/}}
|
||||
{{- .SetDataLabel "su_patch_code"}}
|
||||
{{- range .Commands}}
|
||||
{{- $.DataB .}}
|
||||
{{- end}}
|
||||
|
||||
{{- /*
|
||||
;-------------------------------------------------------------------------------
|
||||
; The parameters / inputs to each opcode
|
||||
;-------------------------------------------------------------------------------
|
||||
*/}}
|
||||
{{- .SetDataLabel "su_patch_parameters"}}
|
||||
{{- range .Values}}
|
||||
{{- $.DataB .}}
|
||||
{{- end}}
|
||||
|
||||
{{- /*
|
||||
;-------------------------------------------------------------------------------
|
||||
; Delay times
|
||||
;-------------------------------------------------------------------------------
|
||||
*/}}
|
||||
{{- .SetDataLabel "su_delay_times"}}
|
||||
{{- range .DelayTimes}}
|
||||
{{- $.DataW .}}
|
||||
{{- end}}
|
||||
|
||||
{{- /*
|
||||
;-------------------------------------------------------------------------------
|
||||
; The number of transformed parameters each opcode takes
|
||||
;-------------------------------------------------------------------------------
|
||||
*/}}
|
||||
{{- .SetDataLabel "su_vm_transformcounts"}}
|
||||
{{- range .Instructions}}
|
||||
{{- $.TransformCount . | $.ToByte | $.DataB}}
|
||||
{{- end}}
|
||||
|
||||
{{- /*
|
||||
;-------------------------------------------------------------------------------
|
||||
; Allocate memory for stack.
|
||||
; Stack of 64 float signals is enough for everybody... right?
|
||||
; Note: as the stack grows _downwards_ the label is _after_ stack
|
||||
;-------------------------------------------------------------------------------
|
||||
*/}}
|
||||
{{- .Align}}
|
||||
{{- .Block 256}}
|
||||
{{- .SetBlockLabel "su_stack"}}
|
||||
|
||||
{{- /*
|
||||
;-------------------------------------------------------------------------------
|
||||
; Allocate memory for transformed values.
|
||||
;-------------------------------------------------------------------------------
|
||||
*/}}
|
||||
{{- .Align}}
|
||||
{{- .SetBlockLabel "su_transformedvalues"}}
|
||||
{{- .Block 32}}
|
||||
|
||||
{{- /*
|
||||
;-------------------------------------------------------------------------------
|
||||
; Uninitialized memory for synth, delaylines & outputbuffer
|
||||
;-------------------------------------------------------------------------------
|
||||
*/}}
|
||||
{{- .Align}}
|
||||
{{- if ne .VoiceTrackBitmask 0}}
|
||||
{{- .SetBlockLabel "su_trackcurrentvoice"}}
|
||||
{{- .Block 32}}
|
||||
{{- end}}
|
||||
{{- .Align}}
|
||||
{{- .SetBlockLabel "su_synth"}}
|
||||
{{- .Block 32}}
|
||||
{{- .SetBlockLabel "su_globalports"}}
|
||||
{{- .Block 32}}
|
||||
{{- .SetBlockLabel "su_voices"}}
|
||||
{{- .Block 131072}}
|
||||
{{- .Align}}
|
||||
{{- .SetBlockLabel "su_delaylines"}}
|
||||
{{- .Block (int (mul 262156 .Song.Patch.NumDelayLines))}}
|
||||
{{- .Align}}
|
||||
{{- .SetBlockLabel "su_outputbuffer"}}
|
||||
{{- if .Output16Bit}}
|
||||
{{- .Block (int (mul .PatternLength .SequenceLength .Song.SamplesPerRow 4))}}
|
||||
{{- else}}
|
||||
{{- .Block (int (mul .PatternLength .SequenceLength .Song.SamplesPerRow 8))}}
|
||||
{{- end}}
|
||||
{{- .SetBlockLabel "su_outputend"}}
|
||||
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Import the difficult math functions from javascript
|
||||
;; (seriously now, it's 2020)
|
||||
;;------------------------------------------------------------------------------
|
||||
(func $pow (import "m" "pow") (param f32) (param f32) (result f32))
|
||||
(func $log2 (import "m" "log2") (param f32) (result f32))
|
||||
(func $sin (import "m" "sin") (param f32) (result f32))
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Types. Only useful to define the jump table type, which is
|
||||
;; (int stereo) void
|
||||
;;------------------------------------------------------------------------------
|
||||
(type $opcode_func_signature (func (param i32)))
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; The one and only memory
|
||||
;;------------------------------------------------------------------------------
|
||||
(memory (export "m") {{.MemoryPages}})
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Globals. Putting all with same initialization value should compress most
|
||||
;;------------------------------------------------------------------------------
|
||||
(global $WRK (mut i32) (i32.const 0))
|
||||
(global $COM (mut i32) (i32.const 0))
|
||||
(global $VAL (mut i32) (i32.const 0))
|
||||
{{- if .SupportsPolyphony}}
|
||||
(global $COM_instr_start (mut i32) (i32.const 0))
|
||||
(global $VAL_instr_start (mut i32) (i32.const 0))
|
||||
{{- end}}
|
||||
{{- if .HasOp "delay"}}
|
||||
(global $delayWRK (mut i32) (i32.const 0))
|
||||
{{- end}}
|
||||
(global $globaltick (mut i32) (i32.const 0))
|
||||
(global $row (mut i32) (i32.const 0))
|
||||
(global $pattern (mut i32) (i32.const 0))
|
||||
(global $sample (mut i32) (i32.const 0))
|
||||
(global $voice (mut i32) (i32.const 0))
|
||||
(global $voicesRemain (mut i32) (i32.const 0))
|
||||
(global $randseed (mut i32) (i32.const 1))
|
||||
(global $sp (mut i32) (i32.const {{index .Labels "su_stack"}}))
|
||||
(global $outputBufPtr (mut i32) (i32.const {{index .Labels "su_outputbuffer"}}))
|
||||
;; TODO: only export start and length with certain compiler options; in demo use, they can be hard coded
|
||||
;; in the intro
|
||||
(global $outputStart (export "s") i32 (i32.const {{index .Labels "su_outputbuffer"}}))
|
||||
(global $outputLength (export "l") i32 (i32.const {{if .Output16Bit}}{{mul .PatternLength .SequenceLength .Song.SamplesPerRow 4}}{{else}}{{mul .PatternLength .SequenceLength .Song.SamplesPerRow 8}}{{end}}))
|
||||
(global $output16bit (export "t") i32 (i32.const {{if .Output16Bit}}1{{else}}0{{end}}))
|
||||
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Functions to emulate FPU stack in software
|
||||
;;------------------------------------------------------------------------------
|
||||
(func $peek (result f32)
|
||||
(f32.load (global.get $sp))
|
||||
)
|
||||
|
||||
(func $peek2 (result f32)
|
||||
(f32.load offset=4 (global.get $sp))
|
||||
)
|
||||
|
||||
(func $pop (result f32)
|
||||
(call $peek)
|
||||
(global.set $sp (i32.add (global.get $sp) (i32.const 4)))
|
||||
)
|
||||
|
||||
(func $push (param $value f32)
|
||||
(global.set $sp (i32.sub (global.get $sp) (i32.const 4)))
|
||||
(f32.store (global.get $sp) (local.get $value))
|
||||
)
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Helper functions
|
||||
;;------------------------------------------------------------------------------
|
||||
(func $swap (param f32 f32) (result f32 f32) ;; x,y -> y,x
|
||||
local.get 1
|
||||
local.get 0
|
||||
)
|
||||
|
||||
(func $scanValueByte (result i32) ;; scans positions $VAL for a byte, incrementing $VAL afterwards
|
||||
(i32.load8_u (global.get $VAL)) ;; in other words: returns byte [$VAL++]
|
||||
(global.set $VAL (i32.add (global.get $VAL) (i32.const 1))) ;; $VAL++
|
||||
)
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; "Entry point" for the player
|
||||
;;------------------------------------------------------------------------------
|
||||
(start $render) ;; we run render automagically when the module is instantiated
|
||||
|
||||
(func $render (param)
|
||||
{{- if .Output16Bit }} (local $channel i32) {{- end }}
|
||||
loop $pattern_loop
|
||||
(global.set $row (i32.const 0))
|
||||
loop $row_loop
|
||||
(call $su_update_voices)
|
||||
(global.set $sample (i32.const 0))
|
||||
loop $sample_loop
|
||||
(global.set $COM (i32.const {{index .Labels "su_patch_code"}}))
|
||||
(global.set $VAL (i32.const {{index .Labels "su_patch_parameters"}}))
|
||||
{{- if .SupportsPolyphony}}
|
||||
(global.set $COM_instr_start (global.get $COM))
|
||||
(global.set $VAL_instr_start (global.get $VAL))
|
||||
{{- end}}
|
||||
(global.set $WRK (i32.const {{index .Labels "su_voices"}}))
|
||||
(global.set $voice (i32.const {{index .Labels "su_voices"}}))
|
||||
(global.set $voicesRemain (i32.const {{.Song.Patch.NumVoices | printf "%v"}}))
|
||||
{{- if .HasOp "delay"}}
|
||||
(global.set $delayWRK (i32.const {{index .Labels "su_delaylines"}}))
|
||||
{{- end}}
|
||||
(call $su_run_vm)
|
||||
{{- template "output_sound.wat" .}}
|
||||
(global.set $sample (i32.add (global.get $sample) (i32.const 1)))
|
||||
(global.set $globaltick (i32.add (global.get $globaltick) (i32.const 1)))
|
||||
(br_if $sample_loop (i32.lt_s (global.get $sample) (i32.const {{.Song.SamplesPerRow}})))
|
||||
end
|
||||
(global.set $row (i32.add (global.get $row) (i32.const 1)))
|
||||
(br_if $row_loop (i32.lt_s (global.get $row) (i32.const {{.PatternLength}})))
|
||||
end
|
||||
(global.set $pattern (i32.add (global.get $pattern) (i32.const 1)))
|
||||
(br_if $pattern_loop (i32.lt_s (global.get $pattern) (i32.const {{.SequenceLength}})))
|
||||
end
|
||||
)
|
||||
|
||||
{{- if ne .VoiceTrackBitmask 0}}
|
||||
;; the complex implementation of update_voices: at least one track has more than one voice
|
||||
(func $su_update_voices (local $si i32) (local $di i32) (local $tracksRemaining i32) (local $note i32) (local $firstVoice i32) (local $nextTrackStartsAt i32) (local $numVoices i32) (local $voiceNo i32)
|
||||
(local.set $tracksRemaining (i32.const {{len .Sequences}}))
|
||||
(local.set $si (global.get $pattern))
|
||||
(local.set $nextTrackStartsAt (i32.const 0))
|
||||
loop $track_loop
|
||||
(local.set $numVoices (i32.const 0))
|
||||
(local.set $firstVoice (local.get $nextTrackStartsAt))
|
||||
loop $voiceLoop
|
||||
(i32.and
|
||||
(i32.shr_u
|
||||
(i32.const {{.VoiceTrackBitmask | printf "%v"}})
|
||||
(local.get $nextTrackStartsAt)
|
||||
)
|
||||
(i32.const 1)
|
||||
)
|
||||
(local.set $nextTrackStartsAt (i32.add (local.get $nextTrackStartsAt) (i32.const 1)))
|
||||
(local.set $numVoices (i32.add (local.get $numVoices) (i32.const 1)))
|
||||
br_if $voiceLoop
|
||||
end
|
||||
(i32.load8_u offset={{index .Labels "su_tracks"}} (local.get $si))
|
||||
(i32.mul (i32.const {{.PatternLength}}))
|
||||
(i32.add (global.get $row))
|
||||
(i32.load8_u offset={{index .Labels "su_patterns"}})
|
||||
(local.tee $note)
|
||||
(if (i32.ne (i32.const {{.Hold}}))(then
|
||||
(i32.store offset={{add (index .Labels "su_voices") 4}}
|
||||
(i32.mul
|
||||
(i32.add
|
||||
(local.tee $voiceNo (i32.load8_u offset={{index .Labels "su_trackcurrentvoice"}} (local.get $tracksRemaining)))
|
||||
(local.get $firstVoice)
|
||||
)
|
||||
(i32.const 4096)
|
||||
)
|
||||
(i32.const 1)
|
||||
) ;; release the note
|
||||
(if (i32.gt_u (local.get $note) (i32.const {{.Hold}}))(then
|
||||
(local.set $di (i32.add
|
||||
(i32.mul
|
||||
(i32.add
|
||||
(local.tee $voiceNo (i32.rem_u
|
||||
(i32.add (local.get $voiceNo) (i32.const 1))
|
||||
(local.get $numVoices)
|
||||
))
|
||||
(local.get $firstVoice)
|
||||
)
|
||||
(i32.const 4096)
|
||||
)
|
||||
(i32.const {{index .Labels "su_voices"}})
|
||||
))
|
||||
(memory.fill (local.get $di) (i32.const 0) (i32.const 4096))
|
||||
(i32.store (local.get $di) (local.get $note))
|
||||
(i32.store8 offset={{index .Labels "su_trackcurrentvoice"}} (local.get $tracksRemaining) (local.get $voiceNo))
|
||||
))
|
||||
))
|
||||
(local.set $si (i32.add (local.get $si) (i32.const {{.SequenceLength}})))
|
||||
(br_if $track_loop (local.tee $tracksRemaining (i32.sub (local.get $tracksRemaining) (i32.const 1))))
|
||||
end
|
||||
)
|
||||
|
||||
{{- else}}
|
||||
;; the simple implementation of update_voices: each track has exactly one voice
|
||||
(func $su_update_voices (local $si i32) (local $di i32) (local $tracksRemaining i32) (local $note i32)
|
||||
(local.set $tracksRemaining (i32.const {{len .Sequences}}))
|
||||
(local.set $si (global.get $pattern))
|
||||
(local.set $di (i32.const {{index .Labels "su_voices"}}))
|
||||
loop $track_loop
|
||||
(i32.load8_u offset={{index .Labels "su_tracks"}} (local.get $si))
|
||||
(i32.mul (i32.const {{.PatternLength}}))
|
||||
(i32.add (global.get $row))
|
||||
(i32.load8_u offset={{index .Labels "su_patterns"}})
|
||||
(local.tee $note)
|
||||
(if (i32.ne (i32.const {{.Hold}}))(then
|
||||
(i32.store offset=4 (local.get $di) (i32.const 1)) ;; release the note
|
||||
(if (i32.gt_u (local.get $note) (i32.const {{.Hold}}))(then
|
||||
(memory.fill (local.get $di) (i32.const 0) (i32.const 4096))
|
||||
(i32.store (local.get $di) (local.get $note))
|
||||
))
|
||||
))
|
||||
(local.set $di (i32.add (local.get $di) (i32.const 4096)))
|
||||
(local.set $si (i32.add (local.get $si) (i32.const {{.SequenceLength}})))
|
||||
(br_if $track_loop (local.tee $tracksRemaining (i32.sub (local.get $tracksRemaining) (i32.const 1))))
|
||||
end
|
||||
)
|
||||
{{- end}}
|
||||
|
||||
{{template "patch.wat" .}}
|
||||
|
||||
|
||||
;; All data is collected into a byte buffer and emitted at once
|
||||
(data (i32.const 0) "{{range .Data}}\{{. | printf "%02x"}}{{end}}")
|
||||
|
||||
;;(data (i32.const 8388610) "\52\49\46\46\b2\eb\0c\20\57\41\56\45\66\6d\74\20\12\20\20\20\03\20\02\20\44\ac\20\20\20\62\05\20\08\20\20\20\20\20\66\61\63\74\04\20\20\20\e0\3a\03\20\64\61\74\61\80\eb\0c\20")
|
||||
|
||||
) ;; END MODULE
|
198
vm/compiler/templates/wasm/sinks.wat
Normal file
198
vm/compiler/templates/wasm/sinks.wat
Normal file
@ -0,0 +1,198 @@
|
||||
{{- if .HasOp "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 (param $stereo i32) (local $addr i32)
|
||||
(local.set $addr (i32.const {{index .Labels "su_globalports"}}))
|
||||
{{- if .Stereo "outaux"}}
|
||||
loop $stereoLoop
|
||||
{{- end}}
|
||||
(f32.store ;; send
|
||||
(local.get $addr)
|
||||
(f32.add
|
||||
(f32.mul
|
||||
(call $peek)
|
||||
(call $input (i32.const {{.InputNumber "outaux" "outgain"}}))
|
||||
)
|
||||
(f32.load (local.get $addr))
|
||||
)
|
||||
)
|
||||
(f32.store offset=8
|
||||
(local.get $addr)
|
||||
(f32.add
|
||||
(f32.mul
|
||||
(call $pop)
|
||||
(call $input (i32.const {{.InputNumber "outaux" "auxgain"}}))
|
||||
)
|
||||
(f32.load offset=8 (local.get $addr))
|
||||
)
|
||||
)
|
||||
{{- if .Stereo "outaux"}}
|
||||
(local.set $addr (i32.add (local.get $addr) (i32.const 4)))
|
||||
(br_if $stereoLoop (i32.eqz (local.tee $stereo (i32.eqz (local.get $stereo)))))
|
||||
end
|
||||
{{- end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasOp "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 (param $stereo i32) (local $addr i32)
|
||||
(local.set $addr (i32.add (i32.mul (call $scanValueByte) (i32.const 4)) (i32.const {{index .Labels "su_globalports"}})))
|
||||
{{- if .Stereo "aux"}}
|
||||
loop $stereoLoop
|
||||
{{- end}}
|
||||
(f32.store
|
||||
(local.get $addr)
|
||||
(f32.add
|
||||
(f32.mul
|
||||
(call $pop)
|
||||
(call $input (i32.const {{.InputNumber "aux" "gain"}}))
|
||||
)
|
||||
(f32.load (local.get $addr))
|
||||
)
|
||||
)
|
||||
{{- if .Stereo "aux"}}
|
||||
(local.set $addr (i32.add (local.get $addr) (i32.const 4)))
|
||||
(br_if $stereoLoop (i32.eqz (local.tee $stereo (i32.eqz (local.get $stereo)))))
|
||||
end
|
||||
{{- end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasOp "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 (param $stereo i32) (local $address i32) (local $scaledAddress i32)
|
||||
(local.set $address (i32.add (call $scanValueByte) (i32.shl (call $scanValueByte) (i32.const 8))))
|
||||
(if (i32.eqz (i32.and (local.get $address) (i32.const 8)))(then
|
||||
{{- if .Stereo "send"}}
|
||||
(if (local.get $stereo)(then
|
||||
(call $push (call $peek2))
|
||||
(call $push (call $peek2))
|
||||
)(else
|
||||
{{- end}}
|
||||
(call $push (call $peek))
|
||||
{{- if .Stereo "send"}}
|
||||
))
|
||||
{{- end}}
|
||||
))
|
||||
{{- if .Stereo "send"}}
|
||||
loop $stereoLoop
|
||||
{{- end}}
|
||||
(local.set $scaledAddress (i32.add (i32.mul (i32.and (local.get $address) (i32.const 0x7FF7)) (i32.const 4))
|
||||
{{- if .SupportsGlobalSend}}
|
||||
(select
|
||||
(i32.const {{index .Labels "su_synth"}})
|
||||
{{- end}}
|
||||
(global.get $voice)
|
||||
{{- if .SupportsGlobalSend}}
|
||||
(i32.and (local.get $address)(i32.const 0x8000))
|
||||
)
|
||||
{{- end}}
|
||||
))
|
||||
(f32.store offset=32
|
||||
(local.get $scaledAddress)
|
||||
(f32.add
|
||||
(f32.load offset=32 (local.get $scaledAddress))
|
||||
(f32.mul
|
||||
(call $inputSigned (i32.const {{.InputNumber "send" "amount"}}))
|
||||
(call $pop)
|
||||
)
|
||||
)
|
||||
)
|
||||
{{- if .Stereo "send"}}
|
||||
(local.set $address (i32.add (local.get $address) (i32.const 1)))
|
||||
(br_if $stereoLoop (i32.eqz (local.tee $stereo (i32.eqz (local.get $stereo)))))
|
||||
end
|
||||
{{- end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
|
||||
|
||||
{{- if .HasOp "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 (param $stereo i32) (local $ptr i32)
|
||||
(local.set $ptr (i32.const {{index .Labels "su_globalports"}})) ;; synth.left
|
||||
(f32.store (local.get $ptr)
|
||||
(f32.add
|
||||
(f32.mul
|
||||
(call $pop)
|
||||
(call $input (i32.const {{.InputNumber "out" "gain"}}))
|
||||
)
|
||||
(f32.load (local.get $ptr))
|
||||
)
|
||||
)
|
||||
(local.set $ptr (i32.const {{add (index .Labels "su_globalports") 4}})) ;; synth.right, note that ATM does not seem to support mono ocpode at all
|
||||
(f32.store (local.get $ptr)
|
||||
(f32.add
|
||||
(f32.mul
|
||||
(call $pop)
|
||||
(call $input (i32.const {{.InputNumber "out" "gain"}}))
|
||||
)
|
||||
(f32.load (local.get $ptr))
|
||||
)
|
||||
)
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{- if .HasOp "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 (param $stereo i32) (local $r f32) (local $w i32)
|
||||
(f32.store
|
||||
(global.get $WRK)
|
||||
(local.tee $r
|
||||
(f32.sub
|
||||
(local.tee $r
|
||||
(f32.add
|
||||
(f32.load (global.get $WRK))
|
||||
(f32.sub
|
||||
(call $pow2
|
||||
(f32.mul
|
||||
(call $pop)
|
||||
(f32.const 2.206896551724138)
|
||||
)
|
||||
)
|
||||
(f32.const 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
(f32.convert_i32_s
|
||||
(local.tee $w (i32.trunc_f32_s (local.get $r))) ;; note: small difference from x86, as this is trunc; x86 rounds to nearest)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(global.set $sample (i32.add (global.get $sample) (local.get $w)))
|
||||
)
|
||||
{{end}}
|
350
vm/compiler/templates/wasm/sources.wat
Normal file
350
vm/compiler/templates/wasm/sources.wat
Normal file
@ -0,0 +1,350 @@
|
||||
{{- if .HasOp "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 (param $stereo i32)
|
||||
{{- if .Stereo "loadval"}}
|
||||
(if (local.get $stereo) (then
|
||||
(call $su_op_loadval (i32.const 0))
|
||||
))
|
||||
{{- end}}
|
||||
(f32.sub (call $input (i32.const {{.InputNumber "loadval" "value"}})) (f32.const 0.5))
|
||||
(f32.mul (f32.const 2.0))
|
||||
(call $push)
|
||||
)
|
||||
{{end}}
|
||||
|
||||
|
||||
{{if .HasOp "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 (param $stereo i32) (local $state i32) (local $level f32) (local $delta f32)
|
||||
(if (i32.load offset=4 (global.get $voice)) (then ;; if voice.release > 0
|
||||
(i32.store (global.get $WRK) (i32.const {{.InputNumber "envelope" "release"}})) ;; set envelope state to release
|
||||
))
|
||||
(local.set $state (i32.load (global.get $WRK)))
|
||||
(local.set $level (f32.load offset=4 (global.get $WRK)))
|
||||
(local.set $delta (call $nonLinearMap (local.get $state)))
|
||||
(if (local.get $state) (then
|
||||
(if (i32.eq (local.get $state) (i32.const 1))(then ;; state is 1 aka decay
|
||||
(local.set $level (f32.sub (local.get $level) (local.get $delta)))
|
||||
(if (f32.le (local.get $level) (call $input (i32.const 2)))(then
|
||||
(local.set $level (call $input (i32.const 2)))
|
||||
(local.set $state (i32.const {{.InputNumber "envelope" "sustain"}}))
|
||||
))
|
||||
))
|
||||
(if (i32.eq (local.get $state) (i32.const {{.InputNumber "envelope" "release"}}))(then ;; state is 3 aka release
|
||||
(local.set $level (f32.sub (local.get $level) (local.get $delta)))
|
||||
(if (f32.le (local.get $level) (f32.const 0)) (then
|
||||
(local.set $level (f32.const 0))
|
||||
))
|
||||
))
|
||||
)(else ;; the state is 0 aka attack
|
||||
(local.set $level (f32.add (local.get $level) (local.get $delta)))
|
||||
(if (f32.ge (local.get $level) (f32.const 1))(then
|
||||
(local.set $level (f32.const 1))
|
||||
(local.set $state (i32.const 1))
|
||||
))
|
||||
))
|
||||
(i32.store (global.get $WRK) (local.get $state))
|
||||
(f32.store offset=4 (global.get $WRK) (local.get $level))
|
||||
(call $push (f32.mul (local.get $level) (call $input (i32.const {{.InputNumber "envelope" "gain"}}))))
|
||||
{{- if .Stereo "envelope"}}
|
||||
(if (local.get $stereo)(then
|
||||
(call $push (call $peek))
|
||||
))
|
||||
{{- end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasOp "noise"}}
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; NOISE opcode: creates noise
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; Mono: push a random value [-1,1] value on stack
|
||||
;; Stereo: push two (different) random values on stack
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $su_op_noise (param $stereo i32)
|
||||
{{- if .Stereo "noise" }}
|
||||
(if (local.get $stereo) (then
|
||||
(call $su_op_noise (i32.const 0))
|
||||
))
|
||||
{{- end}}
|
||||
(global.set $randseed (i32.mul (global.get $randseed) (i32.const 16007)))
|
||||
(f32.mul
|
||||
(call $waveshaper
|
||||
;; Note: in x86 code, the constant looks like a positive integer, but has actually the MSB set i.e. is considered negative by the FPU. This tripped me big time.
|
||||
(f32.div (f32.convert_i32_s (global.get $randseed)) (f32.const -2147483648))
|
||||
(call $input (i32.const {{.InputNumber "noise" "shape"}}))
|
||||
)
|
||||
(call $input (i32.const {{.InputNumber "noise" "gain"}}))
|
||||
)
|
||||
(call $push)
|
||||
)
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasOp "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 (param $stereo i32) (local $flags i32) (local $detune f32) (local $phase f32) (local $color f32) (local $amplitude f32)
|
||||
{{- if .SupportsParamValueOtherThan "oscillator" "unison" 0}}
|
||||
(local $unison i32) (local $WRK_stash i32) (local $detune_stash f32)
|
||||
{{- end}}
|
||||
{{- if .Stereo "oscillator"}}
|
||||
(local $WRK_stereostash i32)
|
||||
(local.set $WRK_stereostash (global.get $WRK))
|
||||
{{- end}}
|
||||
(local.set $flags (call $scanValueByte))
|
||||
(local.set $detune (call $inputSigned (i32.const {{.InputNumber "oscillator" "detune"}})))
|
||||
{{- if .Stereo "oscillator"}}
|
||||
loop $stereoLoop
|
||||
{{- end}}
|
||||
{{- if .SupportsParamValueOtherThan "oscillator" "unison" 0}}
|
||||
(local.set $unison (i32.add (i32.and (local.get $flags) (i32.const 3)) (i32.const 1)))
|
||||
(local.set $WRK_stash (global.get $WRK))
|
||||
(local.set $detune_stash (local.get $detune))
|
||||
(call $push (f32.const 0))
|
||||
loop $unisonLoop
|
||||
{{- end}}
|
||||
(f32.store ;; update phase
|
||||
(global.get $WRK)
|
||||
(local.tee $phase
|
||||
(f32.sub
|
||||
(local.tee $phase
|
||||
;; Transpose calculation starts
|
||||
(f32.div
|
||||
(call $inputSigned (i32.const {{.InputNumber "oscillator" "transpose"}}))
|
||||
(f32.const 0.015625)
|
||||
) ;; scale back to 0 - 128
|
||||
(f32.add (local.get $detune)) ;; add detune. detune is -1 to 1 so can detune a full note up or down at max
|
||||
(f32.add (select
|
||||
(f32.const 0)
|
||||
(f32.convert_i32_u (i32.load (global.get $voice)))
|
||||
(i32.and (local.get $flags) (i32.const 0x8))
|
||||
)) ;; if lfo is not enabled, add the note number to it
|
||||
(f32.mul (f32.const 0.0833333)) ;; /12, in full octaves
|
||||
(call $pow2)
|
||||
(f32.mul (select
|
||||
(f32.const 0.000038) ;; pretty random scaling constant to get LFOs into reasonable range. Historical reasons, goes all the way back to 4klang
|
||||
(f32.const 0.000092696138) ;; scaling constant to get middle-C to where it should be
|
||||
(i32.and (local.get $flags) (i32.const 0x8))
|
||||
))
|
||||
(f32.add (f32.load (global.get $WRK))) ;; add the current phase of the oscillator
|
||||
)
|
||||
(f32.floor (local.get $phase))
|
||||
)
|
||||
)
|
||||
)
|
||||
(f32.add (local.get $phase) (call $input (i32.const {{.InputNumber "oscillator" "phase"}})))
|
||||
(local.set $phase (f32.sub (local.tee $phase) (f32.floor (local.get $phase)))) ;; phase = phase mod 1.0
|
||||
(local.set $color (call $input (i32.const {{.InputNumber "oscillator" "color"}})))
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Sine}}
|
||||
(if (i32.and (local.get $flags) (i32.const 0x40)) (then
|
||||
(local.set $amplitude (call $oscillator_sine (local.get $phase) (local.get $color)))
|
||||
))
|
||||
{{- end}}
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Trisaw}}
|
||||
(if (i32.and (local.get $flags) (i32.const 0x20)) (then
|
||||
(local.set $amplitude (call $oscillator_trisaw (local.get $phase) (local.get $color)))
|
||||
))
|
||||
{{- end}}
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Pulse}}
|
||||
(if (i32.and (local.get $flags) (i32.const 0x10)) (then
|
||||
(local.set $amplitude (call $oscillator_pulse (local.get $phase) (local.get $color)))
|
||||
))
|
||||
{{- end}}
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Gate}}
|
||||
(if (i32.and (local.get $flags) (i32.const 0x04)) (then
|
||||
(local.set $amplitude (call $oscillator_gate (local.get $phase)))
|
||||
;; wave shaping is skipped with gate
|
||||
)(else
|
||||
(local.set $amplitude (call $waveshaper (local.get $amplitude) (call $input (i32.const {{.InputNumber "oscillator" "shape"}}))))
|
||||
))
|
||||
(local.get $amplitude)
|
||||
{{- else}}
|
||||
(call $waveshaper (local.get $amplitude) (call $input (i32.const {{.InputNumber "oscillator" "shape"}})))
|
||||
{{- end}}
|
||||
(call $push (f32.mul
|
||||
(call $input (i32.const {{.InputNumber "oscillator" "gain"}}))
|
||||
))
|
||||
{{- if .SupportsParamValueOtherThan "oscillator" "unison" 0}}
|
||||
(call $push (f32.add (call $pop) (call $pop)))
|
||||
(if (local.tee $unison (i32.sub (local.get $unison) (i32.const 1)))(then
|
||||
(f32.store offset={{.InputNumber "oscillator" "phase" | mul 4 | add (index .Labels "su_transformedvalues")}} (i32.const 0)
|
||||
(f32.add
|
||||
(call $input (i32.const {{.InputNumber "oscillator" "phase"}}))
|
||||
(f32.const 0.08333333) ;; 1/12, add small phase shift so all oscillators don't start in phase
|
||||
)
|
||||
)
|
||||
(global.set $WRK (i32.add (global.get $WRK) (i32.const 8)))
|
||||
(local.set $detune (f32.neg (f32.mul
|
||||
(local.get $detune) ;; each unison oscillator has a detune with flipped sign and halved amount... this creates detunes that concentrate around the fundamental
|
||||
(f32.const 0.5)
|
||||
)))
|
||||
br $unisonLoop
|
||||
))
|
||||
end
|
||||
(global.set $WRK (local.get $WRK_stash))
|
||||
(local.set $detune (local.get $detune_stash))
|
||||
{{- end}}
|
||||
{{- if .Stereo "oscillator"}}
|
||||
(local.set $detune (f32.neg (local.get $detune))) ;; flip the detune for secon round
|
||||
(global.set $WRK (i32.add (global.get $WRK) (i32.const 4)))
|
||||
(br_if $stereoLoop (i32.eqz (local.tee $stereo (i32.eqz (local.get $stereo)))))
|
||||
end
|
||||
(global.set $WRK (local.get $WRK_stereostash))
|
||||
;; TODO: all this "save WRK to local variable, modify it and then restore it" could be better thought out
|
||||
;; however, it is now done like this as a quick bug fix to the issue of stereo oscillators touching WRK and not restoring it
|
||||
{{- end}}
|
||||
)
|
||||
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Pulse}}
|
||||
(func $oscillator_pulse (param $phase f32) (param $color f32) (result f32)
|
||||
(select
|
||||
(f32.const -1)
|
||||
(f32.const 1)
|
||||
(f32.ge (local.get $phase) (local.get $color))
|
||||
)
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Sine}}
|
||||
(func $oscillator_sine (param $phase f32) (param $color f32) (result f32)
|
||||
(select
|
||||
(f32.const 0)
|
||||
(call $sin (f32.mul
|
||||
(f32.div
|
||||
(local.get $phase)
|
||||
(local.get $color)
|
||||
)
|
||||
(f32.const 6.28318530718)
|
||||
))
|
||||
(f32.ge (local.get $phase) (local.get $color))
|
||||
)
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Trisaw}}
|
||||
(func $oscillator_trisaw (param $phase f32) (param $color f32) (result f32)
|
||||
(if (f32.ge (local.get $phase) (local.get $color)) (then
|
||||
(local.set $phase (f32.sub (f32.const 1) (local.get $phase)))
|
||||
(local.set $color (f32.sub (f32.const 1) (local.get $color)))
|
||||
))
|
||||
(f32.div (local.get $phase) (local.get $color))
|
||||
(f32.mul (f32.const 2))
|
||||
(f32.sub (f32.const 1))
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Gate}}
|
||||
(func $oscillator_gate (param $phase f32) (result f32) (local $x f32)
|
||||
(f32.store offset=16 (global.get $WRK)
|
||||
(local.tee $x
|
||||
(f32.add ;; c*(g-x)+x
|
||||
(f32.mul ;; c*(g-x)
|
||||
(f32.sub ;; g - x
|
||||
(f32.load offset=16 (global.get $WRK)) ;; g
|
||||
(local.tee $x
|
||||
(f32.convert_i32_u ;; 'x' gate bit = float((gatebits >> (int(p*16+.5)&15)) & 1)
|
||||
(i32.and ;; (int(p*16+.5)&15)&1
|
||||
(i32.shr_u ;; int(p*16+.5)&15
|
||||
(i32.load16_u (i32.sub (global.get $VAL) (i32.const 4)))
|
||||
(i32.and ;; int(p*16+.5) & 15
|
||||
(i32.trunc_f32_s (f32.add
|
||||
(f32.mul
|
||||
(local.get $phase)
|
||||
(f32.const 16.0)
|
||||
)
|
||||
(f32.const 0.5) ;; well, x86 rounds to integer by default; on wasm, we have only trunc.
|
||||
)) ;; This is just for rendering similar to x86, should probably delete when optimizing size.
|
||||
(i32.const 15)
|
||||
)
|
||||
)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(f32.const 0.99609375) ;; 'c'
|
||||
)
|
||||
(local.get $x)
|
||||
)
|
||||
)
|
||||
)
|
||||
local.get $x
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasOp "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 (param $stereo i32)
|
||||
{{- if .Stereo "receive"}}
|
||||
(if (local.get $stereo) (then
|
||||
(call $push
|
||||
(f32.load offset=36 (global.get $WRK))
|
||||
)
|
||||
(f32.store offset=36 (global.get $WRK) (f32.const 0))
|
||||
))
|
||||
{{- end}}
|
||||
(call $push
|
||||
(f32.load offset=32 (global.get $WRK))
|
||||
)
|
||||
(f32.store offset=32 (global.get $WRK) (f32.const 0))
|
||||
)
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasOp "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 (param $stereo i32) (local $addr i32)
|
||||
call $scanValueByte
|
||||
{{- if .Stereo "in"}}
|
||||
(i32.add (local.get $stereo)) ;; start from right channel if stereo
|
||||
{{- end}}
|
||||
(local.set $addr (i32.add (i32.mul (i32.const 4)) (i32.const {{index .Labels "su_globalports"}})))
|
||||
{{- if .Stereo "in"}}
|
||||
loop $stereoLoop
|
||||
{{- end}}
|
||||
(call $push (f32.load (local.get $addr)))
|
||||
(f32.store (local.get $addr) (f32.const 0))
|
||||
{{- if .Stereo "in"}}
|
||||
(local.set $addr (i32.sub (local.get $addr) (i32.const 4)))
|
||||
(br_if $stereoLoop (i32.eqz (local.tee $stereo (i32.eqz (local.get $stereo)))))
|
||||
end
|
||||
{{- end}}
|
||||
)
|
||||
{{end}}
|
Reference in New Issue
Block a user