sointu/templates/wasm/player.wat
vsariola adcf3ebce8 feat(sointu, tracker,...): restructure domain & tracker models
send targets are now by ID and Song has "Score" part, which is the notes for it. also, moved the model part separate of the actual gioui dependend stuff.

sorry to my future self about the code bomb; ended up too far and did not find an easy way to rewrite the history to make the steps smaller, so in the end, just squashed everything.
2021-02-28 14:24:54 +02:00

289 lines
12 KiB
Plaintext

(module
{{- /*
;------------------------------------------------------------------------------
; Patterns
;-------------------------------------------------------------------------------
*/}}
{{- .SetLabel "su_patterns"}}
{{- $m := .}}
{{- range .Patterns}}
{{- range .}}
{{- $.DataB .}}
{{- end}}
{{- end}}
{{- /*
;------------------------------------------------------------------------------
; Tracks
;-------------------------------------------------------------------------------
*/}}
{{- .SetLabel "su_tracks"}}
{{- $m := .}}
{{- range .Sequences}}
{{- range .}}
{{- $.DataB .}}
{{- end}}
{{- end}}
{{- /*
;------------------------------------------------------------------------------
; The code for this patch, basically indices to vm jump table
;-------------------------------------------------------------------------------
*/}}
{{- .SetLabel "su_patch_code"}}
{{- range .Commands}}
{{- $.DataB .}}
{{- end}}
{{- /*
;-------------------------------------------------------------------------------
; The parameters / inputs to each opcode
;-------------------------------------------------------------------------------
*/}}
{{- .SetLabel "su_patch_parameters"}}
{{- range .Values}}
{{- $.DataB .}}
{{- end}}
{{- /*
;-------------------------------------------------------------------------------
; Delay times
;-------------------------------------------------------------------------------
*/}}
{{- .SetLabel "su_delay_times"}}
{{- range .DelayTimes}}
{{- $.DataW .}}
{{- end}}
{{- /*
;-------------------------------------------------------------------------------
; The number of transformed parameters each opcode takes
;-------------------------------------------------------------------------------
*/}}
{{- .SetLabel "su_vm_transformcounts"}}
{{- range .Instructions}}
{{- $.TransformCount . | $.ToByte | $.DataB}}
{{- end}}
;;------------------------------------------------------------------------------
;; 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
;; TODO: Its size should be calculated just to fit, but not more
;;------------------------------------------------------------------------------
(memory (export "m") 256)
;;------------------------------------------------------------------------------
;; 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 2048))
(global $outputBufPtr (mut i32) (i32.const 8388608))
;; 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 8388608)) ;; TODO: do not hard code, layout memory somehow intelligently
(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 4160))
(global.set $voice (i32.const 4160))
(global.set $voicesRemain (i32.const {{.Song.Patch.NumVoices | printf "%v"}}))
{{- if .HasOp "delay"}}
(global.set $delayWRK (i32.const 262144)) ;; BAD IDEA: we are limited to something like 30 delay lines
;; after that, the delay lines start to overwrite the outputbuffer. Find a way to layout the memory
;; based on the song, instead of hard coding addressed.
{{- 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=4164
(i32.mul
(i32.add
(local.tee $voiceNo (i32.load8_u offset=768 (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 4160)
))
(memory.fill (local.get $di) (i32.const 0) (i32.const 4096))
(i32.store (local.get $di) (local.get $note))
(i32.store8 offset=768 (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 4160))
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