mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-27 19:00:25 -04:00
336 lines
13 KiB
Plaintext
336 lines
13 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_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
|