sointu/vm/compiler/templates/wasm/player.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

338 lines
14 KiB
Plaintext

(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_opcodes"}}
{{- range .Opcodes}}
{{- $.DataB .}}
{{- end}}
{{- /*
;-------------------------------------------------------------------------------
; The parameters / inputs to each opcode
;-------------------------------------------------------------------------------
*/}}
{{- .SetDataLabel "su_patch_operands"}}
{{- range .Operands}}
{{- $.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 operands.
;-------------------------------------------------------------------------------
*/}}
{{- .Align}}
{{- .SetBlockLabel "su_transformedoperands"}}
{{- .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 $scanOperand (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_opcodes"}}))
(global.set $VAL (i32.const {{index .Labels "su_patch_operands"}}))
{{- 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 0)
) ;; 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.store offset=4 (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 0)) ;; 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))
(i32.store offset=4 (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