995 lines
51 KiB
NASM
995 lines
51 KiB
NASM
%define SU_LENGTH_IN_SAMPLES 3335488
|
|
%define SU_SAMPLE_RATE 44100
|
|
%define SU_BPM 165.
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; unit struct
|
|
;-------------------------------------------------------------------------------
|
|
struc su_unit
|
|
.state resd 8
|
|
.ports resd 8
|
|
.size:
|
|
endstruc
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; voice struct
|
|
;-------------------------------------------------------------------------------
|
|
struc su_voice
|
|
.note resd 1
|
|
.sustain resd 1
|
|
.inputs resd 8
|
|
.reserved resd 6 ; this is done to so the whole voice is 2^n long, see polyphonic player
|
|
.workspace resb 63 * su_unit.size
|
|
.size:
|
|
endstruc
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; synthworkspace struct
|
|
;-------------------------------------------------------------------------------
|
|
struc su_synthworkspace
|
|
.curvoices resb 32 ; these are used by the multitrack player to store which voice is playing on which track
|
|
.left resd 1
|
|
.right resd 1
|
|
.aux resd 6 ; 3 auxiliary signals
|
|
.voices resb 32 * su_voice.size
|
|
.size:
|
|
endstruc
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; su_delayline_wrk struct
|
|
;-------------------------------------------------------------------------------
|
|
struc su_delayline_wrk
|
|
.dcin resd 1
|
|
.dcout resd 1
|
|
.filtstate resd 1
|
|
.buffer resd 65536
|
|
.size:
|
|
endstruc
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; su_sample_offset struct
|
|
;-------------------------------------------------------------------------------
|
|
struc su_sample_offset ; length conveniently 8 bytes, so easy to index
|
|
.start resd 1
|
|
.loopstart resw 1
|
|
.looplength resw 1
|
|
.size:
|
|
endstruc
|
|
;-------------------------------------------------------------------------------
|
|
; Uninitialized data: The synth object
|
|
;-------------------------------------------------------------------------------
|
|
section .synth_object bss align=256
|
|
su_synth_obj:
|
|
resb su_synthworkspace.size
|
|
resb 34*su_delayline_wrk.size
|
|
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; su_render_song function: the entry point for the synth
|
|
;-------------------------------------------------------------------------------
|
|
; Has the signature su_render_song(void *ptr), where ptr is a pointer to
|
|
; the output buffer. Renders the compile time hard-coded song to the buffer.
|
|
; Stack: output_ptr
|
|
;-------------------------------------------------------------------------------
|
|
section .su_render_song code align=1
|
|
global _su_render_song@4
|
|
_su_render_song@4:
|
|
pushad ; Stack: edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
xor eax, eax
|
|
push 1 ; Stack: RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
push eax ; Stack: GlobalTick, RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
su_render_rowloop: ; loop through every row in the song
|
|
push eax ; Stack: Row, GlobalTick, RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
call su_update_voices ; update instruments for the new row
|
|
xor eax, eax ; ecx is the current sample within row
|
|
su_render_sampleloop: ; loop through every sample in the row
|
|
push eax ; Stack: Sample, Row, GlobalTick, RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
push 7 ; Stack: VoicesRemain, Sample, Row, GlobalTick, RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
mov edx, dword su_synth_obj ; edx points to the synth object
|
|
mov ebx, dword su_patch_opcodes ; COM points to vm code
|
|
mov esi, dword su_patch_operands ; VAL points to unit params
|
|
mov ecx, dword su_synth_obj + su_synthworkspace.size - su_delayline_wrk.filtstate
|
|
lea ebp, [edx + su_synthworkspace.voices] ; WRK points to the first voice
|
|
call su_run_vm ; run through the VM code
|
|
pop eax ; eax = VoicesRemain, Stack: Sample, Row, GlobalTick, RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
mov esi, [esp + 52] ; esi points to the output buffer
|
|
mov edi, dword su_synth_obj+su_synthworkspace.left
|
|
mov ecx, 2
|
|
output_sound16bit_loop: ; loop over two channels, left & right
|
|
fld dword [edi]
|
|
call su_clip
|
|
fmul dword [FCONST_32767_0]
|
|
push eax
|
|
fistp dword [esp]
|
|
pop eax
|
|
mov word [esi],ax ; // store integer converted right sample
|
|
xor eax,eax
|
|
stosd
|
|
add esi,2
|
|
loop output_sound16bit_loop
|
|
mov [esp + 52], esi ; save esi back to stack ; *ptr++ = left, *ptr++ = right
|
|
pop eax ; eax = Sample, Stack: Row, GlobalTick, RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
inc dword [esp + 4] ; increment global time, used by delays
|
|
inc eax
|
|
cmp eax, 4009
|
|
jl su_render_sampleloop
|
|
pop eax ; eax = Row, Stack: GlobalTick, RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr ; Stack: pushad ptr
|
|
inc eax
|
|
cmp eax, 832
|
|
jl su_render_rowloop
|
|
; rewind the stack the entropy of multiple pop eax is probably lower than add
|
|
pop eax ; eax = GlobalTick, Stack: RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
pop eax ; eax = RandSeed, Stack: edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
popad ; Popped: eax, ecx, edx, ebx, esp, ebp, esi, edi. Stack: retaddr_su_render_song, OutputBufPtr
|
|
ret 4
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; su_update_voices function: polyphonic & chord implementation
|
|
;-------------------------------------------------------------------------------
|
|
; Input: eax : current row within song
|
|
; Dirty: pretty much everything
|
|
;-------------------------------------------------------------------------------
|
|
section .su_update_voices code align=1
|
|
su_update_voices:
|
|
; The simple implementation: each track triggers always the same voice
|
|
xor edx, edx
|
|
xor ebx, ebx
|
|
mov bl, 16 ; rows per pattern
|
|
div ebx ; eax = current pattern, edx = current row in pattern
|
|
lea esi, [su_tracks+eax]; esi points to the pattern data for current track
|
|
mov edi, dword su_synth_obj+su_synthworkspace.voices
|
|
mov bl, 6 ; MAX_TRACKS is always <= 32 so this is ok
|
|
su_update_voices_trackloop:
|
|
movzx eax, byte [esi] ; eax = current pattern
|
|
imul eax, 16 ; multiply by rows per pattern, eax = offset to current pattern data
|
|
movzx eax, byte [su_patterns + eax + edx] ; ecx = note
|
|
cmp al, 1 ; anything but hold causes action
|
|
je short su_update_voices_nexttrack
|
|
mov dword [edi+su_voice.sustain], eax ; set the voice currently active to release
|
|
jb su_update_voices_nexttrack ; if cl < HLD (no new note triggered) goto nexttrack
|
|
su_update_voices_retrigger:
|
|
stosd ; save note
|
|
stosd ; save sustain
|
|
mov ecx, (su_voice.size - su_voice.inputs)/4 ; could be xor ecx, ecx; mov ch,...>>8, but will it actually be smaller after compression?
|
|
xor eax, eax
|
|
rep stosd ; clear the workspace of the new voice, retriggering oscillators
|
|
jmp short su_update_voices_skipadd
|
|
su_update_voices_nexttrack:
|
|
add edi, su_voice.size
|
|
su_update_voices_skipadd:
|
|
add esi, 52
|
|
dec ebx
|
|
jnz short su_update_voices_trackloop
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; su_run_vm function: runs the entire virtual machine once, creating 1 sample
|
|
;-------------------------------------------------------------------------------
|
|
; Input: su_synth_obj.left : Set to 0 before calling
|
|
; su_synth_obj.right : Set to 0 before calling
|
|
; _CX : Pointer to delay workspace (if needed)
|
|
; _DX : Pointer to synth object
|
|
; COM : Pointer to opcode stream
|
|
; VAL : Pointer to operand stream
|
|
; WRK : Pointer to the last workspace processed
|
|
; Output: su_synth_obj.left : left sample
|
|
; su_synth_obj.right : right sample
|
|
; Dirty: everything
|
|
;-------------------------------------------------------------------------------
|
|
section .su_run_vm code align=1
|
|
su_run_vm:
|
|
pushad ; Stack: edi, OperandStream, Voice, esp, OpcodeStream, Synth, DelayWorkSpace, eax, retaddr_su_run_vm, VoicesRemain, Sample, Row, GlobalTick, RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
su_run_vm_loop: ; loop until all voices done
|
|
movzx edi, byte [ebx] ; edi = command byte
|
|
inc ebx ; move to next instruction
|
|
add ebp, su_unit.size ; move WRK to next unit
|
|
shr edi, 1 ; shift out the LSB bit = stereo bit
|
|
je su_run_vm_advance ; the opcode is zero, jump to advance
|
|
mov edx, [esp + 8] ; reset INP to point to the inputs part of voice
|
|
pushf ; push flags to save carry = stereo bit
|
|
add edx, su_voice.inputs
|
|
xor ecx, ecx ; counter = 0
|
|
xor eax, eax ; clear out high bits of eax, as lodsb only sets al
|
|
su_transform_operands_loop:
|
|
cmp cl, byte [su_vm_transformcounts-1+edi] ; compare the counter to the value in the param count table
|
|
je su_transform_operands_out
|
|
lodsb ; load the operand from VAL stream
|
|
push eax ; push it to memory so FPU can read it
|
|
fild dword [esp] ; load the operand value to FPU stack
|
|
fmul dword [FCONST_0_00781250] ; divide it by 128 (0 => 0, 128 => 1.0)
|
|
fadd dword [ebp+su_unit.ports+ecx*4] ; add the modulations in the current workspace
|
|
fstp dword [edx+ecx*4] ; store the modulated value in the inputs section of voice
|
|
xor eax, eax
|
|
mov dword [ebp+su_unit.ports+ecx*4], eax ; clear out the modulation ports
|
|
pop eax
|
|
inc ecx
|
|
jmp su_transform_operands_loop
|
|
su_transform_operands_out:
|
|
popf ; pop flags for the carry bit = stereo bit
|
|
call [su_vm_jumptable-4+edi*4] ; call the function corresponding to the instruction
|
|
jmp su_run_vm_loop
|
|
su_run_vm_advance:
|
|
mov ebp, dword [esp + 8] ; load pointer to voice to register
|
|
add ebp, su_voice.size ; shift it to point to following voice
|
|
mov dword [esp + 8], ebp ; save back to stack
|
|
dec dword [esp + 36] ; voices--
|
|
jne su_run_vm_loop ; if there's more voices to process, goto vm_loop
|
|
popad ; Popped: eax, ecx = DelayWorkSpace, edx = Synth, ebx = OpcodeStream, esp, ebp = Voice, esi = OperandStream, edi. Stack: retaddr_su_run_vm, VoicesRemain, Sample, Row, GlobalTick, RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
ret
|
|
;-------------------------------------------------------------------------------
|
|
; 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
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_addp code align=1
|
|
su_op_addp:
|
|
jnc su_op_addp_mono
|
|
faddp st2, st0
|
|
faddp st2, st0
|
|
ret
|
|
su_op_addp_mono:
|
|
faddp st1, st0
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; 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
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_mulp code align=1
|
|
su_op_mulp:
|
|
jnc su_op_mulp_mono
|
|
fmulp st2, st0
|
|
fmulp st2, st0
|
|
ret
|
|
su_op_mulp_mono:
|
|
fmulp st1
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; XCH opcode: exchange the signals on the stack
|
|
;-------------------------------------------------------------------------------
|
|
; Mono: a b -> b a
|
|
; stereo: a b c d -> c d a b
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_xch code align=1
|
|
su_op_xch:
|
|
fxch st0, st1
|
|
ret
|
|
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; 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
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_hold code align=1
|
|
su_op_hold:
|
|
fld dword [edx] ; f x
|
|
fmul st0, st0 ; f^2 x
|
|
fchs ; -f^2 x
|
|
fadd dword [ebp] ; p-f^2 x
|
|
fst dword [ebp] ; p <- p-f^2
|
|
fldz ; 0 p x
|
|
fucomip st1 ; p x
|
|
fstp dword [esp-4] ; t=p, x
|
|
jc short su_op_hold_holding ; if (0 < p) goto holding
|
|
fld1 ; 1 x
|
|
fadd dword [esp-4] ; 1+t x
|
|
fstp dword [ebp] ; x
|
|
fst dword [ebp+4] ; save holded value
|
|
ret ; x
|
|
su_op_hold_holding:
|
|
fstp st0 ;
|
|
fld dword [ebp+4] ; x
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; FILTER opcode: perform low/high/band-pass/notch etc. filtering on the signal
|
|
;-------------------------------------------------------------------------------
|
|
; Mono: x -> filtered(x)
|
|
; Stereo: l r -> filtered(l) filtered(r)
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_filter code align=1
|
|
su_op_filter:
|
|
lodsb ; load the flags to al
|
|
call su_effects_stereohelper
|
|
fld dword [edx + 4] ; r x
|
|
fld dword [edx]; f r x
|
|
fmul st0, st0 ; f2 x (square the input so we never get negative and also have a smoother behaviour in the lower frequencies)
|
|
fst dword [ebp+12] ; f2 r x
|
|
fmul dword [ebp+8] ; f2*b r x
|
|
fadd dword [ebp] ; f2*b+l r x
|
|
fst dword [ebp] ; l'=f2*b+l r x
|
|
fsubp st2, st0 ; r x-l'
|
|
fmul dword [ebp+8] ; r*b x-l'
|
|
fsubp st1, st0 ; x-l'-r*b
|
|
fst dword [ebp+4] ; h'=x-l'-r*b
|
|
fmul dword [ebp+12] ; f2*h'
|
|
fadd dword [ebp+8] ; f2*h'+b
|
|
fstp dword [ebp+8] ; b'=f2*h'+b
|
|
fldz ; 0
|
|
test al, byte 0x40
|
|
jz short su_op_filter_skiplowpass
|
|
fadd dword [ebp]
|
|
su_op_filter_skiplowpass:
|
|
test al, byte 0x20
|
|
jz short su_op_filter_skipbandpass
|
|
fadd dword [ebp+8]
|
|
su_op_filter_skipbandpass:
|
|
test al, byte 0x10
|
|
jz short su_op_filter_skiphighpass
|
|
fadd dword [ebp+4]
|
|
su_op_filter_skiphighpass:
|
|
test al, byte 0x08
|
|
jz short su_op_filter_skipnegbandpass
|
|
fsub dword [ebp+8]
|
|
su_op_filter_skipnegbandpass:
|
|
test al, byte 0x04
|
|
jz short su_op_filter_skipneghighpass
|
|
fsub dword [ebp+4]
|
|
su_op_filter_skipneghighpass:
|
|
ret
|
|
;-------------------------------------------------------------------------------
|
|
; 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
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_pan code align=1
|
|
su_op_pan:
|
|
fld dword [edx] ; p s
|
|
fmul st1 ; p*s s
|
|
fsub st1, st0 ; p*s s-p*s
|
|
; Equal to
|
|
; s*p s*(1-p)
|
|
fxch ; s*(1-p) s*p SHOULD PROBABLY DELETE, WHY BOTHER
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; 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)
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_delay code align=1
|
|
su_op_delay:
|
|
lodsw ; al = delay index, ah = delay count
|
|
pushad ; Stack: edi, DelayVal, ebp, esp, DelayCom, edx, ecx, eax, retaddr_su_op_delay, edi, OperandStream, Voice, esp, OpcodeStream, Synth, DelayWorkSpace, eax, retaddr_su_run_vm, VoicesRemain, Sample, Row, GlobalTick, RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
movzx ebx, al
|
|
lea ebx,[su_delay_times + ebx*2] ; BX now points to the right position within delay time table
|
|
movzx esi, word [esp + 84] ; notice that we load word, so we wrap at 65536
|
|
mov ecx, dword [esp + 60] ; ebp is now the separate delay workspace, as they require a lot more space
|
|
jnc su_op_delay_mono
|
|
push eax ; save _ah (delay count)
|
|
fxch ; r l
|
|
call su_op_delay_do ; D(r) l process delay for the right channel
|
|
pop eax ; restore the count for second run
|
|
fxch ; l D(r)
|
|
su_op_delay_mono: ; flow into mono delay
|
|
call su_op_delay_do ; when stereo delay is not enabled, we could inline this to save 5 bytes, but I expect stereo delay to be farely popular so maybe not worth the hassle
|
|
mov dword [esp + 60],ecx ; move delay workspace pointer back to stack.
|
|
popad ; Popped: eax, ecx, edx, ebx = DelayCom, esp, ebp, esi = DelayVal, edi. Stack: retaddr_su_op_delay, edi, OperandStream, Voice, esp, OpcodeStream, Synth, DelayWorkSpace, eax, retaddr_su_run_vm, VoicesRemain, Sample, Row, GlobalTick, RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; su_op_delay_do: executes the actual delay
|
|
;-------------------------------------------------------------------------------
|
|
; Pseudocode:
|
|
; q = dr*x
|
|
; for (i = 0;i < count;i++)
|
|
; s = b[(t-delaytime[i+offset])&65535]
|
|
; q += s
|
|
; o[i] = o[i]*da+s*(1-da)
|
|
; b[t] = f*o[i] +p^2*x
|
|
; Perform dc-filtering q and output q
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_delay_do code align=1
|
|
su_op_delay_do: ; x y
|
|
fld st0
|
|
fmul dword [edx] ; p*x y
|
|
fmul dword [edx] ; p*p*x y
|
|
fxch ; y p*p*x
|
|
fmul dword [edx + 4] ; dr*y p*p*x
|
|
su_op_delay_loop:
|
|
mov edi, esi
|
|
sub di, word [ebx] ; we perform the math in 16-bit to wrap around
|
|
fld dword [ecx+su_delayline_wrk.buffer+edi*4]; s dr*y p*p*x, where s is the sample from delay buffer
|
|
fadd st1, st0 ; s dr*y+s p*p*x (add comb output to current output)
|
|
fld1 ; 1 s dr*y+s p*p*x
|
|
fsub dword [edx + 12] ; 1-da s dr*y+s p*p*x
|
|
fmulp st1, st0 ; s*(1-da) dr*y+s p*p*x
|
|
fld dword [edx + 12] ; da s*(1-da) dr*y+s p*p*x
|
|
fmul dword [ecx+su_delayline_wrk.filtstate] ; o*da s*(1-da) dr*y+s p*p*x, where o is stored
|
|
faddp st1, st0 ; o*da+s*(1-da) dr*y+s p*p*x
|
|
fadd dword [FCONST_0_500000] ; add and sub small offset to prevent denormalization. WARNING: this is highly important, as the damp filters might denormalize and give 100x CPU penalty
|
|
fsub dword [FCONST_0_500000] ; See for example: https://stackoverflow.com/questions/36781881/why-denormalized-floats-are-so-much-slower-than-other-floats-from-hardware-arch
|
|
fst dword [ecx+su_delayline_wrk.filtstate] ; o'=o*da+s*(1-da), o' dr*y+s p*p*x
|
|
fmul dword [edx + 8] ; f*o' dr*y+s p*p*x
|
|
fadd st0, st2 ; f*o'+p*p*x dr*y+s p*p*x
|
|
fstp dword [ecx+su_delayline_wrk.buffer+esi*4]; save f*o'+p*p*x to delay buffer
|
|
add ebx,2 ; move to next index
|
|
add ecx, su_delayline_wrk.size ; go to next delay delay workspace
|
|
sub ah, 2
|
|
jg su_op_delay_loop ; if ah > 0, goto loop
|
|
fstp st1 ; dr*y+s1+s2+s3+...
|
|
; DC-filtering
|
|
fld dword [ecx+su_delayline_wrk.dcout] ; o s
|
|
fmul dword [FCONST_0_99609375] ; c*o s
|
|
fsub dword [ecx+su_delayline_wrk.dcin] ; c*o-i s
|
|
fxch ; s c*o-i
|
|
fst dword [ecx+su_delayline_wrk.dcin] ; i'=s, s c*o-i
|
|
faddp st1 ; s+c*o-i
|
|
fadd dword [FCONST_0_500000] ; add and sub small offset to prevent denormalization. WARNING: this is highly important, as low pass filters might denormalize and give 100x CPU penalty
|
|
fsub dword [FCONST_0_500000] ; See for example: https://stackoverflow.com/questions/36781881/why-denormalized-floats-are-so-much-slower-than-other-floats-from-hardware-arch
|
|
fst dword [ecx+su_delayline_wrk.dcout] ; o'=s+c*o-i
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; 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
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_compressor code align=1
|
|
su_op_compressor:
|
|
fld st0 ; x x
|
|
fmul st0, st0 ; x^2 x
|
|
fld st2 ; r x^2 l r
|
|
fst st3 ; y x^2 l r
|
|
fmul st0, st0 ; y^2 x^2 l r
|
|
faddp st1, st0 ; y^2+x^2 l r
|
|
fld dword [ebp] ; l x^2 x
|
|
fucomi st0, st1
|
|
setnb al ; if (st0 >= st1) al = 1; else al = 0;
|
|
fsubp st1, st0 ; x^2-l x
|
|
call su_nonlinear_map ; c x^2-l x, c is either attack or release parameter mapped in a nonlinear way
|
|
fmulp st1, st0 ; c*(x^2-l) x
|
|
fadd dword [ebp] ; l+c*(x^2-l) x // we could've kept level in the stack and save a few bytes, but su_env_map uses 3 stack (c + 2 temp), so the stack was getting quite big.
|
|
; TODO: make this denormalization optional, if the user wants to save some space
|
|
fadd dword [FCONST_0_500000] ; add and sub small offset to prevent denormalization. WARNING: this is highly important, as the damp filters might denormalize and give 100x CPU penalty
|
|
fsub dword [FCONST_0_500000] ; See for example: https://stackoverflow.com/questions/36781881/why-denormalized-floats-are-so-much-slower-than-other-floats-from-hardware-arch
|
|
fst dword [ebp] ; l'=l+c*(x^2-l), l' x
|
|
fld dword [edx + 12] ; t l' x
|
|
fmul st0, st0 ; t*t l' x
|
|
fxch ; l' t*t x
|
|
fucomi st0, st1 ; if l' < t*t
|
|
fcmovb st0, st1 ; l'=t*t
|
|
fdivp st1, st0 ; t*t/l' x
|
|
fld dword [edx + 16] ; r t*t/l' x
|
|
|
|
fmul dword [FCONST_0_500000] ; p=r/2 t*t/l' x
|
|
fxch ; t*t/l' p x
|
|
fyl2x ; p*log2(t*t/l') x
|
|
call su_power ; 2^(p*log2(t*t/l')) x
|
|
; Equal to:
|
|
; (t*t/l')^p x
|
|
; if ratio is at minimum => p=0 => 1 x
|
|
; if ratio is at maximum => p=0.5 => t/x => t/x*x=t
|
|
fdiv dword [edx + 8]; this used to be pregain but that ran into problems with getting back up to 0 dB so postgain should be better at that
|
|
fld st0 ; and return the computed gain two times, ready for MULP STEREO
|
|
ret
|
|
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; OUT opcode: outputs and pops the signal
|
|
;-------------------------------------------------------------------------------
|
|
; Stereo: add ST0 to left out and ST1 to right out, then pop
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_out code align=1
|
|
su_op_out: ; l r
|
|
mov edi, [esp + 24] ; DI points to the synth object, use DI consistently in sinks/sources presumably to increase compression rate
|
|
call su_op_out_mono
|
|
add edi, 4 ; shift from left to right channel
|
|
su_op_out_mono:
|
|
fmul dword [edx] ; multiply by gain
|
|
fadd dword [edi + su_synthworkspace.left] ; add current value of the output
|
|
fstp dword [edi + su_synthworkspace.left] ; store the new value of the output
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; 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
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_outaux code align=1
|
|
su_op_outaux: ; l r
|
|
mov edi, [esp + 24]
|
|
call su_op_outaux_mono
|
|
add edi, 4
|
|
su_op_outaux_mono:
|
|
fld st0 ; l l
|
|
fmul dword [edx] ; g*l
|
|
fadd dword [edi + su_synthworkspace.left] ; g*l+o
|
|
fstp dword [edi + su_synthworkspace.left] ; o'=g*l+o
|
|
fmul dword [edx + 4] ; h*l
|
|
fadd dword [edi + su_synthworkspace.aux] ; h*l+a
|
|
fstp dword [edi + su_synthworkspace.aux] ; a'=h*l+a
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; 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
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_send code align=1
|
|
su_op_send:
|
|
lodsw
|
|
mov ecx, [esp + 12] ; load pointer to voice
|
|
pushf ; uh ugly: we save the flags just for the stereo carry bit. Doing the .CX loading later crashed the synth for stereo sends as loading the synth address from stack was f'd up by the "call su_op_send_mono"
|
|
test ah, 0x80
|
|
jz su_op_send_skipglobal
|
|
mov ecx, [esp + 24 + 4]
|
|
su_op_send_skipglobal:
|
|
popf
|
|
test al, 0x8 ; if the SEND_POP bit is not set
|
|
jnz su_op_send_skippush
|
|
fld st0 ; duplicate the signal on stack: s s
|
|
su_op_send_skippush: ; there is signal s, but maybe also another: s (s)
|
|
fld dword [edx] ; a l (l)
|
|
fsub dword [FCONST_0_500000] ; a-.5 l (l)
|
|
fadd st0 ; g=2*a-1 l (l)
|
|
and ah, 0x7f ; eax = send address, clear the global bit
|
|
or al, 0x8 ; set the POP bit always, at the same time shifting to ports instead of wrk
|
|
fmulp st1, st0 ; g*l (l)
|
|
fadd dword [ecx + eax*4] ; g*l+L (l),where L is the current value
|
|
fstp dword [ecx + eax*4] ; (l)
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; 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
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_envelope code align=1
|
|
su_op_envelope:
|
|
jnc su_op_envelope_mono
|
|
call su_op_envelope_mono
|
|
fld st0
|
|
ret
|
|
su_op_envelope_mono:
|
|
mov eax, dword [edx-su_voice.inputs+su_voice.sustain] ; eax = su_instrument.sustain
|
|
test eax, eax ; if (eax != 0)
|
|
jne su_op_envelope_process ; goto process
|
|
mov al, 3 ; [state]=RELEASE
|
|
mov dword [ebp], eax ; note that mov al, XXX; mov ..., eax is less bytes than doing it directly
|
|
su_op_envelope_process:
|
|
mov eax, dword [ebp] ; al=[state]
|
|
fld dword [ebp+4] ; x=[level]
|
|
cmp al, 2 ; if (al==SUSTAIN)
|
|
je short su_op_envelope_leave2 ; goto leave2
|
|
su_op_envelope_attac:
|
|
cmp al, 0 ; if (al!=ATTAC)
|
|
jne short su_op_envelope_decay ; goto decay
|
|
call su_nonlinear_map ; a x, where a=attack
|
|
faddp st1, st0 ; a+x
|
|
fld1 ; 1 a+x
|
|
fucomi st1 ; if (a+x<=1) // is attack complete?
|
|
fcmovnb st0, st1 ; a+x a+x
|
|
jbe short su_op_envelope_statechange ; else goto statechange
|
|
su_op_envelope_decay:
|
|
cmp al, 1 ; if (al!=DECAY)
|
|
jne short su_op_envelope_release ; goto release
|
|
call su_nonlinear_map ; d x, where d=decay
|
|
fsubp st1, st0 ; x-d
|
|
fld dword [edx + 8] ; s x-d, where s=sustain
|
|
fucomi st1 ; if (x-d>s) // is decay complete?
|
|
fcmovb st0, st1 ; x-d x-d
|
|
jnc short su_op_envelope_statechange ; else goto statechange
|
|
su_op_envelope_release:
|
|
cmp al, 3 ; if (al!=RELEASE)
|
|
jne short su_op_envelope_leave ; goto leave
|
|
call su_nonlinear_map ; r x, where r=release
|
|
fsubp st1, st0 ; x-r
|
|
fldz ; 0 x-r
|
|
fucomi st1 ; if (x-r>0) // is release complete?
|
|
fcmovb st0, st1 ; x-r x-r, then goto leave
|
|
jc short su_op_envelope_leave
|
|
su_op_envelope_statechange:
|
|
inc dword [ebp] ; [state]++
|
|
su_op_envelope_leave:
|
|
fstp st1 ; x', where x' is the new value
|
|
fst dword [ebp+4] ; [level]=x'
|
|
su_op_envelope_leave2:
|
|
fmul dword [edx + 16] ; [gain]*x'
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; NOISE opcode: creates noise
|
|
;-------------------------------------------------------------------------------
|
|
; Mono: push a random value [-1,1] value on stack
|
|
; Stereo: push two (differeent) random values on stack
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_noise code align=1
|
|
su_op_noise:
|
|
lea ecx,[esp + 56]
|
|
imul eax, [ecx],16007
|
|
mov [ecx],eax
|
|
fild dword [ecx]
|
|
fidiv dword [ICONST_2147483648] ; 65536*32768
|
|
fld dword [edx]
|
|
call su_waveshaper
|
|
fmul dword [edx + 4]
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; 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
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_oscillator code align=1
|
|
su_op_oscillator:
|
|
lodsb ; load the flags
|
|
fld dword [edx + 4] ; e, where e is the detune [0,1]
|
|
fsub dword [FCONST_0_500000] ; e-.5
|
|
fadd st0, st0 ; d=2*e-.5, where d is the detune [-1,1]
|
|
jnc su_op_oscillat_mono
|
|
fld st0 ; d d
|
|
add ebp, 4 ; move wrk...
|
|
call su_op_oscillat_mono ; r d
|
|
sub ebp, 4 ; ...restore wrk
|
|
fxch ; d r
|
|
fchs ; -d r, negate the detune for second round
|
|
su_op_oscillat_mono:
|
|
|
|
pushad ; Stack: edi, esi, OscWRK, esp, ebx, edx, ecx, , retaddr_su_op_oscillator, edi, OperandStream, Voice, esp, OpcodeStream, Synth, DelayWorkSpace, eax, retaddr_su_run_vm, VoicesRemain, Sample, Row, GlobalTick, RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
fldz ; 0 d
|
|
fxch ; d a=0, "accumulated signal"
|
|
su_op_oscillat_unison_loop:
|
|
fst dword [esp] ; save the current detune, d. We could keep it in fpu stack but it was getting big.
|
|
call su_op_oscillat_single ; s a
|
|
faddp st1, st0 ; a+=s
|
|
test al, 3
|
|
je su_op_oscillat_unison_out
|
|
add ebp, 8 ; this is ok after all, as there's a pop in the end of unison loop
|
|
fld dword [edx + 8] ; p s
|
|
|
|
fadd dword [ICONST_1034594986] ; 1/12 p s, add some little phase offset to unison oscillators so they don't start in sync
|
|
fstp dword [edx + 8] ; s note that this changes the phase for second, possible stereo run. That's probably ok
|
|
fld dword [esp] ; d s
|
|
|
|
fmul dword [FCONST_0_500000] ; .5*d s // negate and halve the detune of each oscillator
|
|
fchs ; -.5*d s // negate and halve the detune of each oscillator
|
|
dec eax
|
|
jmp short su_op_oscillat_unison_loop
|
|
su_op_oscillat_unison_out:
|
|
|
|
popad ; Popped: eax = , ecx, edx, ebx, esp, ebp = OscWRK, esi, edi. Stack: retaddr_su_op_oscillator, edi, OperandStream, Voice, esp, OpcodeStream, Synth, DelayWorkSpace, eax, retaddr_su_run_vm, VoicesRemain, Sample, Row, GlobalTick, RandSeed, edi, esi, ebp, esp, ebx, edx, ecx, eax, retaddr_su_render_song, OutputBufPtr
|
|
ret
|
|
su_op_oscillat_single:
|
|
fld dword [edx]
|
|
fsub dword [FCONST_0_500000]
|
|
fdiv dword [FCONST_0_00781250]
|
|
faddp st1
|
|
test al, byte 0x08
|
|
jnz su_op_oscillat_skipnote
|
|
fiadd dword [edx-su_voice.inputs+su_voice.note] ; // st0 is note, st1 is t+d offset
|
|
su_op_oscillat_skipnote:
|
|
fmul dword [ICONST_1034594986]
|
|
call su_power
|
|
test al, byte 0x08
|
|
jz short su_op_oscillat_normalize_note
|
|
fmul dword [FCONST_3_80000em05] ; // st0 is now frequency for lfo
|
|
jmp short su_op_oscillat_normalized
|
|
su_op_oscillat_normalize_note:
|
|
fmul dword [FCONST_9_269614em05] ; // st0 is now frequency
|
|
su_op_oscillat_normalized:
|
|
fadd dword [ebp]
|
|
fld1 ; we need to take mod(p,1) so the frequency does not drift as the float
|
|
fadd st1, st0 ; make no mistake: without this, there is audible drifts in oscillator pitch
|
|
fxch ; as the actual period changes once the phase becomes too big
|
|
fprem ; we actually computed mod(p+1,1) instead of mod(p,1) as the fprem takes mod
|
|
fstp st1 ; towards zero
|
|
fst dword [ebp] ; store back the updated phase
|
|
fadd dword [edx + 8]
|
|
fld1 ; this is a bit stupid, but we need to take mod(x,1) again after phase modulations
|
|
fadd st1, st0 ; as the actual oscillator functions expect x in [0,1]
|
|
fxch
|
|
fprem
|
|
fstp st1
|
|
fld dword [edx + 12] ; // c p
|
|
; every oscillator test included if needed
|
|
test al, byte 0x40
|
|
jz short su_op_oscillat_notsine
|
|
call su_oscillat_sine
|
|
su_op_oscillat_notsine:
|
|
test al, byte 0x20
|
|
jz short su_op_oscillat_not_trisaw
|
|
call su_oscillat_trisaw
|
|
su_op_oscillat_not_trisaw:
|
|
su_op_oscillat_shaping:
|
|
; finally, shape the oscillator and apply gain
|
|
fld dword [edx + 16]
|
|
call su_waveshaper
|
|
su_op_oscillat_gain:
|
|
fmul dword [edx + 20]
|
|
ret
|
|
|
|
section .su_oscillat_trisaw code align=1
|
|
su_oscillat_trisaw:
|
|
fucomi st1 ; // c p
|
|
jnc short su_oscillat_trisaw_up
|
|
fld1 ; // 1 c p
|
|
fsubr st2, st0 ; // 1 c 1-p
|
|
fsubrp st1, st0 ; // 1-c 1-p
|
|
su_oscillat_trisaw_up:
|
|
fdivp st1, st0 ; // tp'/tc
|
|
fadd st0 ; // 2*''
|
|
fld1 ; // 1 2*''
|
|
fsubp st1, st0 ; // 2*''-1
|
|
ret
|
|
|
|
section .su_oscillat_sine code align=1
|
|
su_oscillat_sine:
|
|
fucomi st1 ; // c p
|
|
jnc short su_oscillat_sine_do
|
|
fstp st1
|
|
fsub st0, st0 ; // 0
|
|
ret
|
|
su_oscillat_sine_do:
|
|
fdivp st1, st0 ; // p/c
|
|
fldpi ; // pi p
|
|
fadd st0 ; // 2*pi p
|
|
fmulp st1, st0 ; // 2*pi*p
|
|
fsin ; // sin(2*pi*p)
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; 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)
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_in code align=1
|
|
su_op_in:
|
|
lodsb
|
|
mov edi, [esp + 24]
|
|
xor ecx, ecx ; we cannot xor before jnc, so we have to do it mono & stereo. LAHF / SAHF could do it, but is the same number of bytes with more entropy
|
|
fld dword [edi + su_synthworkspace.right + eax*4]
|
|
mov dword [edi + su_synthworkspace.right + eax*4], ecx
|
|
fld dword [edi + su_synthworkspace.left + eax*4]
|
|
mov dword [edi + su_synthworkspace.left + eax*4], ecx
|
|
ret
|
|
|
|
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; su_nonlinear_map function: returns 2^(-24*x) of parameter number _AX
|
|
;-------------------------------------------------------------------------------
|
|
; Input: _AX : parameter number (e.g. for envelope: 0 = attac, 1 = decay...)
|
|
; INP : pointer to transformed operands
|
|
; Output: st0 : 2^(-24*x), where x is the parameter in the range 0-1
|
|
;-------------------------------------------------------------------------------
|
|
section .su_nonlinear_map code align=1
|
|
su_nonlinear_map:
|
|
fld dword [edx+eax*4] ; x, where x is the parameter in the range 0-1
|
|
|
|
fimul dword [ICONST_24] ; 24*x
|
|
fchs ; -24*x
|
|
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; su_power function: computes 2^x
|
|
;-------------------------------------------------------------------------------
|
|
; Input: st0 : x
|
|
; Output: st0 : 2^x
|
|
;-------------------------------------------------------------------------------
|
|
global _su_pow@0
|
|
_su_pow@0:
|
|
su_power:
|
|
fld1 ; 1 x
|
|
fld st1 ; x 1 x
|
|
fprem ; mod(x,1) 1 x
|
|
f2xm1 ; 2^mod(x,1)-1 1 x
|
|
faddp st1,st0 ; 2^mod(x,1) x
|
|
fscale ; 2^mod(x,1)*2^trunc(x) x
|
|
; Equal to:
|
|
; 2^x x
|
|
fstp st1 ; 2^x
|
|
ret
|
|
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; 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))
|
|
; This is placed here to be able to flow into waveshaper & also include
|
|
; wave shaper if needed by some other function; need to investigate the
|
|
; best way to do this
|
|
;-------------------------------------------------------------------------------
|
|
section .su_op_distort code align=1
|
|
su_op_distort:call su_effects_stereohelper
|
|
fld dword [edx]
|
|
|
|
su_waveshaper:
|
|
fld st0 ; a a x
|
|
|
|
fsub dword [FCONST_0_500000] ; a-.5 a x
|
|
fadd st0 ; 2*a-1 a x
|
|
fld st2 ; x 2*a-1 a x
|
|
fabs ; abs(x) 2*a-1 a x
|
|
fmulp st1 ; (2*a-1)*abs(x) a x
|
|
fld1 ; 1 (2*a-1)*abs(x) a x
|
|
faddp st1 ; 1+(2*a-1)*abs(x) a x
|
|
fsub st1 ; 1-a+(2*a-1)*abs(x) a x
|
|
fdivp st1, st0 ; a/(1-a+(2*a-1)*abs(x)) x
|
|
fmulp st1 ; x*a/(1-a+(2*a-1)*abs(x))
|
|
ret
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; su_effects_stereohelper: moves the workspace to next, does the filtering for
|
|
; right channel (pulling the calling address from stack), rewinds the
|
|
; workspace and returns
|
|
;-------------------------------------------------------------------------------
|
|
section .su_effects_stereohelper code align=1
|
|
su_effects_stereohelper:
|
|
jnc su_effects_stereohelper_mono ; carry is still the stereo bit
|
|
add ebp, 16
|
|
fxch ; r l
|
|
call [esp] ; call whoever called me...
|
|
fxch ; l r
|
|
sub ebp, 16 ; move WRK back to where it was
|
|
su_effects_stereohelper_mono:
|
|
ret ; return to process l/mono sound
|
|
|
|
|
|
section .su_clip code align=1
|
|
su_clip:
|
|
fld1 ; 1 x a
|
|
fucomi st1 ; if (1 <= x)
|
|
jbe short su_clip_do ; goto Clip_Do
|
|
fchs ; -1 x a
|
|
fucomi st1 ; if (-1 < x)
|
|
fcmovb st0, st1 ; x x a
|
|
su_clip_do:
|
|
fstp st1 ; x' a, where x' = clamp(x)
|
|
ret
|
|
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; 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.
|
|
;-------------------------------------------------------------------------------
|
|
section .su_vm_jumptable data align=1
|
|
su_vm_jumptable:
|
|
dd su_op_envelope
|
|
dd su_op_oscillator
|
|
dd su_op_distort
|
|
dd su_op_hold
|
|
dd su_op_mulp
|
|
dd su_op_noise
|
|
dd su_op_filter
|
|
dd su_op_addp
|
|
dd su_op_pan
|
|
dd su_op_delay
|
|
dd su_op_xch
|
|
dd su_op_outaux
|
|
dd su_op_send
|
|
dd su_op_compressor
|
|
dd su_op_in
|
|
dd su_op_out
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; The number of transformed parameters each opcode takes
|
|
;-------------------------------------------------------------------------------
|
|
section .su_vm_transformcounts data align=1
|
|
su_vm_transformcounts:
|
|
db 5
|
|
db 6
|
|
db 1
|
|
db 1
|
|
db 0
|
|
db 2
|
|
db 2
|
|
db 0
|
|
db 1
|
|
db 4
|
|
db 0
|
|
db 2
|
|
db 1
|
|
db 5
|
|
db 0
|
|
db 1
|
|
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; Patterns
|
|
;-------------------------------------------------------------------------------
|
|
section .su_patterns data align=1
|
|
su_patterns:
|
|
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
|
db 0,0,0,0,0,0,0,0,57,1,1,1,57,1,1,1
|
|
db 1,1,1,1,57,1,1,1,1,1,1,1,57,1,1,1
|
|
db 1,1,1,1,57,1,1,1,1,1,1,57,57,1,1,1
|
|
db 1,1,1,1,57,57,57,57,1,1,1,1,57,1,1,1
|
|
db 1,1,1,1,57,1,1,1,57,57,1,1,57,1,1,1
|
|
db 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
|
db 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0
|
|
db 0,0,0,0,0,0,0,0,0,0,46,1,1,1,46,1
|
|
db 46,1,1,1,1,1,1,1,1,1,46,1,1,1,1,1
|
|
db 46,1,1,1,1,1,1,1,46,1,46,1,1,1,1,1
|
|
db 46,1,1,1,1,1,1,1,46,46,46,46,1,1,1,1
|
|
db 46,1,1,1,1,1,1,1,1,1,46,46,1,1,1,1
|
|
db 48,1,48,1,1,1,48,1,48,1,48,1,1,1,48,1
|
|
db 50,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
|
db 48,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
|
db 46,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
|
db 1,1,1,1,1,1,1,1,1,1,48,1,50,1,53,1
|
|
db 43,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
|
db 1,1,1,1,1,1,1,1,43,1,45,1,46,1,48,1
|
|
db 1,1,1,1,1,1,1,1,1,1,1,1,48,1,50,1
|
|
db 52,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
|
db 45,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
|
db 1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0
|
|
db 62,0,64,0,65,0,69,0,65,0,64,0,62,0,64,0
|
|
db 65,0,72,0,65,0,64,0,62,0,64,0,65,0,71,0
|
|
db 65,0,69,0,65,0,64,0,62,0,64,0,65,0,72,0
|
|
db 50,0,50,1,50,0,50,1,50,0,50,1,50,0,50,1
|
|
db 48,0,48,1,48,0,48,1,48,0,48,1,48,0,48,1
|
|
db 46,0,46,1,46,0,46,1,46,0,46,1,46,0,46,1
|
|
db 43,0,43,1,43,0,43,1,43,0,43,1,43,0,43,1
|
|
db 43,0,43,1,43,0,43,1,43,0,43,1,43,0,43,0
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; Tracks
|
|
;-------------------------------------------------------------------------------
|
|
section .su_tracks data align=1
|
|
su_tracks:
|
|
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,2,2,3,2,2,2,4,2,2,2,2,2,2,2,5,2,2,2,3,2,2,2,4,2,2,2,2,2,2,2,5,6,6,7,0
|
|
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,9,9,9,10,9,9,9,11,9,9,9,10,9,9,9,12,9,9,9,10,9,9,9,11,9,9,9,10,9,9,9,12,6,6,7,0
|
|
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,6,6,7,0
|
|
db 14,6,15,6,16,17,18,19,16,20,21,22,14,6,6,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
|
db 24,25,24,26,24,25,24,26,24,25,24,26,24,25,24,26,24,25,24,26,24,25,24,26,24,25,24,26,24,25,24,26,24,25,24,26,24,25,24,26,24,25,24,26,24,25,24,26,0,0,0,0
|
|
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,27,28,28,29,29,30,30,27,27,28,28,29,29,30,30,27,27,28,28,29,29,30,30,27,27,28,28,29,29,30,31,0,0,0,0
|
|
;-------------------------------------------------------------------------------
|
|
; Delay times
|
|
;-------------------------------------------------------------------------------
|
|
section .su_delay_times data align=1
|
|
su_delay_times:
|
|
dw 1116,1188,1276,1356,1422,1492,1556,1618,1140,1212,1300,1380,1446,1516,1580,1642,21381,12027
|
|
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; The code for this patch, basically indices to vm jump table
|
|
;-------------------------------------------------------------------------------
|
|
section .su_patch_opcodes data align=1
|
|
su_patch_opcodes:
|
|
db 2,4,6,8,10,2,12,10,14,14,16,14,14,14,14,14,6,8,18,20,22,20,22,25,2,26,2,26,2,26,0,2,26,4,6,8,10,2,12,14,14,10,16,14,14,14,14,18,25,2,2,10,26,0,2,12,10,14,14,14,18,25,0,5,5,17,5,17,15,14,22,14,7,15,3,11,25,4,26,4,26,26,4,26,4,26,4,26,0,2,4,4,16,10,18,20,22,20,25,0,2,26,26,2,4,4,16,10,14,14,18,7,29,11,25,0,31,21,33,0
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; The parameters / inputs to each opcode
|
|
;-------------------------------------------------------------------------------
|
|
section .su_patch_operands data align=1
|
|
su_patch_operands:
|
|
db 22,64,0,0,38,77,64,19,128,66,70,64,78,128,0,68,0,0,70,2,128,97,128,68,108,128,68,100,128,68,19,98,16,15,128,80,40,74,80,23,42,16,97,128,59,27,128,112,0,0,15,27,128,112,0,8,15,128,0,38,49,0,0,128,70,40,0,47,54,0,0,128,19,152,0,54,68,70,0,128,26,200,0,39,71,0,0,128,7,0,149,88,64,42,115,114,54,33,58,100,28,45,0,0,128,84,102,93,63,68,75,87,64,11,66,68,53,128,80,7,76,16,13,54,16,64,128,0,71,128,74,0,128,49,71,121,0,128,0,56,0,4,53,0,0,128,102,128,87,128,16,95,128,80,89,71,32,61,45,0,64,87,64,128,65,128,34,64,60,87,128,64,128,35,64,69,0,128,106,128,66,62,91,64,103,26,88,85,31,88,96,12,128,88,25,59,106,60,64,29,21,64,64,4,128,64,128,40,52,25,0,41,42,55,54,63,128,72,75,33,0,59,43,0,74,64,0,128,64,128,72,69,73,0,40,64,0,128,64,128,72,71,120,0,40,64,0,128,64,128,72,86,152,0,39,62,0,30,71,88,62,14,53,27,72,64,88,66,64,113,64,128,64,64,71,128,86,0,16,1,73,128,90,0,17,1,84,52,39,81,0,0,128,87,144,0,90,107,0,33,57,67,57,128,76,64,0,98,64,101,64,64,48,85,78,97,64,35,39,82,64,15,128,68,64,80,48,56,87,46,54,85,35,2,50,128,114,77,0,15,128
|
|
|
|
;-------------------------------------------------------------------------------
|
|
; Constants
|
|
;-------------------------------------------------------------------------------
|
|
section .constants data align=1
|
|
FCONST_32767_0 dd 0x46fffe00
|
|
FCONST_0_00781250 dd 0x3c000000
|
|
FCONST_0_500000 dd 0x3f000000
|
|
FCONST_0_99609375 dd 0x3f7f0000
|
|
FCONST_3_80000em05 dd 0x381f6230
|
|
FCONST_9_269614em05 dd 0x38c265dc
|
|
ICONST_2147483648 dd 0x80000000
|
|
ICONST_1034594986 dd 0x3daaaaaa
|
|
ICONST_24 dd 0x18
|
|
|