mirror of
https://github.com/vsariola/sointu.git
synced 2025-11-19 00:52:57 -05:00
drafting
This commit is contained in:
parent
f92ecb2e99
commit
a872bd3340
@ -45,6 +45,7 @@ func main() {
|
|||||||
output16bit := flag.Bool("i", false, "Compiled song should output 16-bit integers, instead of floats.")
|
output16bit := flag.Bool("i", false, "Compiled song should output 16-bit integers, instead of floats.")
|
||||||
targetOs := flag.String("os", runtime.GOOS, "Target OS. Defaults to current OS. Possible values: windows, darwin, linux. Anything else is assumed linuxy. Ignored when targeting wasm.")
|
targetOs := flag.String("os", runtime.GOOS, "Target OS. Defaults to current OS. Possible values: windows, darwin, linux. Anything else is assumed linuxy. Ignored when targeting wasm.")
|
||||||
versionFlag := flag.Bool("v", false, "Print version.")
|
versionFlag := flag.Bool("v", false, "Print version.")
|
||||||
|
forceSingleThread := flag.Bool("f", false, "Force single threaded rendering, even if patch if configured to use multiple threads.")
|
||||||
flag.Usage = printUsage
|
flag.Usage = printUsage
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *versionFlag {
|
if *versionFlag {
|
||||||
@ -60,9 +61,9 @@ func main() {
|
|||||||
if compile || *library {
|
if compile || *library {
|
||||||
var err error
|
var err error
|
||||||
if *tmplDir != "" {
|
if *tmplDir != "" {
|
||||||
comp, err = compiler.NewFromTemplates(*targetOs, *targetArch, *output16bit, *rowsync, *tmplDir)
|
comp, err = compiler.NewFromTemplates(*targetOs, *targetArch, *output16bit, *rowsync, *forceSingleThread, *tmplDir)
|
||||||
} else {
|
} else {
|
||||||
comp, err = compiler.New(*targetOs, *targetArch, *output16bit, *rowsync)
|
comp, err = compiler.New(*targetOs, *targetArch, *output16bit, *rowsync, *forceSingleThread)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, `error creating compiler: %v`, err)
|
fmt.Fprintf(os.Stderr, `error creating compiler: %v`, err)
|
||||||
@ -143,7 +144,7 @@ func main() {
|
|||||||
var compiledPlayer map[string]string
|
var compiledPlayer map[string]string
|
||||||
if compile {
|
if compile {
|
||||||
var err error
|
var err error
|
||||||
compiledPlayer, err = comp.Song(&song)
|
compiledPlayer, err = comp.Song(song)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("compiling player failed: %v", err)
|
return fmt.Errorf("compiling player failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
16
patch.go
16
patch.go
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"math/bits"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@ -21,7 +22,10 @@ type (
|
|||||||
NumVoices int
|
NumVoices int
|
||||||
Units []Unit
|
Units []Unit
|
||||||
Mute bool `yaml:",omitempty"` // Mute is only used in the tracker for soloing/muting instruments; the compiled player ignores this field
|
Mute bool `yaml:",omitempty"` // Mute is only used in the tracker for soloing/muting instruments; the compiled player ignores this field
|
||||||
ThreadMaskM1 int `yaml:",omitempty"` // ThreadMaskM1 is a bit mask of which cores are used, minus 1. Minus 1 is done so that the default value 0 means bit mask 0b0001 i.e. only core 1 is rendering the instrument.
|
// ThreadMaskM1 is a bit mask of which threads are used, minus 1. Minus
|
||||||
|
// 1 is done so that the default value 0 means bit mask 0b0001 i.e. only
|
||||||
|
// thread 1 is rendering the instrument.
|
||||||
|
ThreadMaskM1 int `yaml:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unit is e.g. a filter, oscillator, envelope and its parameters
|
// Unit is e.g. a filter, oscillator, envelope and its parameters
|
||||||
@ -539,6 +543,16 @@ func (p Patch) NumSyncs() int {
|
|||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Patch) NumThreads() int {
|
||||||
|
numThreads := 1
|
||||||
|
for _, instr := range p {
|
||||||
|
if l := bits.Len((uint)(instr.ThreadMaskM1 + 1)); l > numThreads {
|
||||||
|
numThreads = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return numThreads
|
||||||
|
}
|
||||||
|
|
||||||
// FirstVoiceForInstrument returns the index of the first voice of given
|
// FirstVoiceForInstrument returns the index of the first voice of given
|
||||||
// instrument. For example, if the Patch has three instruments (0, 1 and 2),
|
// instrument. For example, if the Patch has three instruments (0, 1 and 2),
|
||||||
// with 1, 3, 2 voices, respectively, then FirstVoiceForInstrument(0) returns 0,
|
// with 1, 3, 2 voices, respectively, then FirstVoiceForInstrument(0) returns 0,
|
||||||
|
|||||||
@ -95,7 +95,7 @@ func (m *Model) setThreadsBit(bit int, value bool) {
|
|||||||
} else {
|
} else {
|
||||||
mask &^= (1 << bit)
|
mask &^= (1 << bit)
|
||||||
}
|
}
|
||||||
m.d.Song.Patch[m.d.InstrIndex].ThreadMaskM1 = max(mask-1, 0) // -1 would have all cores disabled, so make that 0 i.e. use core 1 only
|
m.d.Song.Patch[m.d.InstrIndex].ThreadMaskM1 = max(mask-1, 0) // -1 would have all threads disabled, so make that 0 i.e. use at least thread 1
|
||||||
m.warnAboutCrossThreadSends()
|
m.warnAboutCrossThreadSends()
|
||||||
m.warnNoMultithreadSupport()
|
m.warnNoMultithreadSupport()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,13 +18,14 @@ type Compiler struct {
|
|||||||
Arch string
|
Arch string
|
||||||
Output16Bit bool
|
Output16Bit bool
|
||||||
RowSync bool
|
RowSync bool
|
||||||
|
ForceSingleThread bool
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed templates/amd64-386/* templates/wasm/*
|
//go:embed templates/amd64-386/* templates/wasm/*
|
||||||
var templateFS embed.FS
|
var templateFS embed.FS
|
||||||
|
|
||||||
// New returns a new compiler using the default .asm templates
|
// New returns a new compiler using the default .asm templates
|
||||||
func New(os string, arch string, output16Bit bool, rowsync bool) (*Compiler, error) {
|
func New(os string, arch string, output16Bit, rowsync, forceSingleThread bool) (*Compiler, error) {
|
||||||
var subdir string
|
var subdir string
|
||||||
if arch == "386" || arch == "amd64" {
|
if arch == "386" || arch == "amd64" {
|
||||||
subdir = "amd64-386"
|
subdir = "amd64-386"
|
||||||
@ -37,16 +38,16 @@ func New(os string, arch string, output16Bit bool, rowsync bool) (*Compiler, err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(`could not create templates: %v`, err)
|
return nil, fmt.Errorf(`could not create templates: %v`, err)
|
||||||
}
|
}
|
||||||
return &Compiler{Template: tmpl, OS: os, Arch: arch, RowSync: rowsync, Output16Bit: output16Bit}, nil
|
return &Compiler{Template: tmpl, OS: os, Arch: arch, RowSync: rowsync, Output16Bit: output16Bit, ForceSingleThread: forceSingleThread}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFromTemplates(os string, arch string, output16Bit bool, rowsync bool, templateDirectory string) (*Compiler, error) {
|
func NewFromTemplates(os string, arch string, output16Bit, rowsync, forceSingleThread bool, templateDirectory string) (*Compiler, error) {
|
||||||
globPtrn := filepath.Join(templateDirectory, "*.*")
|
globPtrn := filepath.Join(templateDirectory, "*.*")
|
||||||
tmpl, err := template.New("base").Funcs(sprig.TxtFuncMap()).ParseGlob(globPtrn)
|
tmpl, err := template.New("base").Funcs(sprig.TxtFuncMap()).ParseGlob(globPtrn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(`could not create template based on directory "%v": %v`, templateDirectory, err)
|
return nil, fmt.Errorf(`could not create template based on directory "%v": %v`, templateDirectory, err)
|
||||||
}
|
}
|
||||||
return &Compiler{Template: tmpl, OS: os, Arch: arch, RowSync: rowsync, Output16Bit: output16Bit}, nil
|
return &Compiler{Template: tmpl, OS: os, Arch: arch, RowSync: rowsync, Output16Bit: output16Bit, ForceSingleThread: forceSingleThread}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (com *Compiler) Library() (map[string]string, error) {
|
func (com *Compiler) Library() (map[string]string, error) {
|
||||||
@ -75,13 +76,23 @@ func (com *Compiler) Library() (map[string]string, error) {
|
|||||||
return retmap, nil
|
return retmap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (com *Compiler) Song(song *sointu.Song) (map[string]string, error) {
|
func (com *Compiler) Song(song sointu.Song) (map[string]string, error) {
|
||||||
if com.Arch != "386" && com.Arch != "amd64" && com.Arch != "wasm" {
|
if com.Arch != "386" && com.Arch != "amd64" && com.Arch != "wasm" {
|
||||||
return nil, fmt.Errorf(`compiling a song player is supported only on 386, amd64 and wasm architectures (targeted architecture was %v)`, com.Arch)
|
return nil, fmt.Errorf(`compiling a song player is supported only on 386, amd64 and wasm architectures (targeted architecture was %v)`, com.Arch)
|
||||||
}
|
}
|
||||||
|
if com.ForceSingleThread {
|
||||||
|
song = song.Copy()
|
||||||
|
for i := range song.Patch {
|
||||||
|
song.Patch[i].ThreadMaskM1 = 0 // clear all ThreadMaskM1 to indicate that all instruments are on Thread 1 i.e. force single threaded rendering
|
||||||
|
}
|
||||||
|
}
|
||||||
var templates []string
|
var templates []string
|
||||||
if com.Arch == "386" || com.Arch == "amd64" {
|
if com.Arch == "386" || com.Arch == "amd64" {
|
||||||
|
if song.Patch.NumThreads() > 1 {
|
||||||
|
templates = []string{"multithread_player.asm", "player.h", "player.inc"}
|
||||||
|
} else {
|
||||||
templates = []string{"player.asm", "player.h", "player.inc"}
|
templates = []string{"player.asm", "player.h", "player.inc"}
|
||||||
|
}
|
||||||
} else if com.Arch == "wasm" {
|
} else if com.Arch == "wasm" {
|
||||||
templates = []string{"player.wat"}
|
templates = []string{"player.wat"}
|
||||||
}
|
}
|
||||||
@ -91,14 +102,14 @@ func (com *Compiler) Song(song *sointu.Song) (map[string]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(`could not encode patch: %v`, err)
|
return nil, fmt.Errorf(`could not encode patch: %v`, err)
|
||||||
}
|
}
|
||||||
patterns, sequences, err := ConstructPatterns(song)
|
patterns, sequences, err := ConstructPatterns(&song)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(`could not encode song: %v`, err)
|
return nil, fmt.Errorf(`could not encode song: %v`, err)
|
||||||
}
|
}
|
||||||
for _, templateName := range templates {
|
for _, templateName := range templates {
|
||||||
compilerMacros := *NewCompilerMacros(*com)
|
compilerMacros := *NewCompilerMacros(*com)
|
||||||
featureSetMacros := FeatureSetMacros{features}
|
featureSetMacros := FeatureSetMacros{features}
|
||||||
songMacros := *NewSongMacros(song)
|
songMacros := *NewSongMacros(&song)
|
||||||
var populatedTemplate, extension string
|
var populatedTemplate, extension string
|
||||||
var err error
|
var err error
|
||||||
if com.Arch == "386" || com.Arch == "amd64" {
|
if com.Arch == "386" || com.Arch == "amd64" {
|
||||||
|
|||||||
286
vm/compiler/templates/amd64-386/multithread_player.asm
Normal file
286
vm/compiler/templates/amd64-386/multithread_player.asm
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
{{template "structs.asm" .}}
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; Uninitialized data: The synth object
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
{{- range $index := .Song.Patch.NumThreads}}
|
||||||
|
{{.SectBss (print "synth_object" $index)}}
|
||||||
|
su_synth_obj{{$index}}:
|
||||||
|
resb su_synthworkspace.size
|
||||||
|
resb {{.Song.Patch.NumDelayLines}}*su_delayline_wrk.size
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
{{- if or .RowSync (.HasOp "sync")}}
|
||||||
|
{{- if or (and (eq .OS "windows") (not .Amd64)) (eq .OS "darwin")}}
|
||||||
|
extern _syncBuf
|
||||||
|
{{- else}}
|
||||||
|
extern syncBuf
|
||||||
|
{{- end}}
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; su_render_songX function(s): the different entry points
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; 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
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
{{- range $index := .Song.Patch.NumThreads}}
|
||||||
|
{{- if gt $index 0 }}
|
||||||
|
{{- $entrypoint := print "su_render_song" {{add1 $index}} }}
|
||||||
|
{{- else}}
|
||||||
|
{{- $entrypoint := print "su_render_song" }}
|
||||||
|
{{- end}}
|
||||||
|
{{.ExportFunc $entrypoint "OutputBufPtr"}}
|
||||||
|
{{- if .Amd64}}
|
||||||
|
{{- if eq .OS "windows"}}
|
||||||
|
{{- .PushRegs "rcx" "OutputBufPtr" "rdi" "NonVolatileRsi" "rsi" "NonVolatile" "rbx" "NonVolatileRbx" "rbp" "NonVolatileRbp" | indent 4}} ; rcx = ptr to buf. rdi,rsi,rbx,rbp nonvolatile
|
||||||
|
{{- else}} ; SystemV amd64 ABI, linux mac or hopefully something similar
|
||||||
|
{{- .PushRegs "rdi" "OutputBufPtr" "rbx" "NonVolatileRbx" "rbp" "NonVolatileRbp" | indent 4}}
|
||||||
|
{{- end}}
|
||||||
|
{{- else}}
|
||||||
|
{{- .PushRegs | indent 4}}
|
||||||
|
{{- end}}
|
||||||
|
{{- $prologsize := len .Stacklocs}}
|
||||||
|
{{print "su_synth_obj" $index | .Prepare}}
|
||||||
|
{{.Push (print "su_synth_obj" $index | .Use) "SyncBufPtr"}}
|
||||||
|
|
||||||
|
{{print "su_synth_obj" $index | .Prepare}}
|
||||||
|
{{.Push (print "su_synth_obj" $index | .Use) "SyncBufPtr"}}
|
||||||
|
|
||||||
|
{{print "su_synth_obj" $index | .Prepare}}
|
||||||
|
{{.Push (print "su_synth_obj" $index | .Use) "SyncBufPtr"}}
|
||||||
|
|
||||||
|
{{print "su_tracks" $index | .Prepare}}
|
||||||
|
{{.Push (.print "Tracks+" (.TrackOffset $index) | .Use) "Tracks"}}
|
||||||
|
{{.Push (.NumVoices $index) "NumVoices"}}
|
||||||
|
{{.Push (.PolyPhonyBitMask $index) "PolyPhonyBitMask"}}
|
||||||
|
{{.Push (.VoiceTrackBitmask $index) "VoiceTrackBitmask"}}
|
||||||
|
{{- if or .RowSync (.HasOp "sync")}}
|
||||||
|
{{- if or (and (eq .OS "windows") (not .Amd64)) (eq .OS "darwin")}}
|
||||||
|
{{- print "_syncBuf+" (.SyncOffset $index) | .Prepare}}
|
||||||
|
{{.Push (print "_syncBuf+" (.SyncOffset $index) | .Use) "SyncBufPtr"}}
|
||||||
|
{{- else}}
|
||||||
|
{{- print "syncBuf+" (.SyncOffset $index) | .Prepare}}
|
||||||
|
{{.Push (print "syncBuf+" (.SyncOffset $index) | .Use) "SyncBufPtr"}}
|
||||||
|
{{- end}}
|
||||||
|
{{- end}}
|
||||||
|
{{.Call "actual_render_song"}}
|
||||||
|
; rewind the stack the entropy of multiple pop {{.AX}} is probably lower than add
|
||||||
|
{{- range slice .Stacklocs $prologsize}}
|
||||||
|
{{$.Pop $.AX}}
|
||||||
|
{{- end}}
|
||||||
|
{{- if .Amd64}}
|
||||||
|
{{- if eq .OS "windows"}}
|
||||||
|
; Windows64 ABI, rdi rsi rbx rbp non-volatile
|
||||||
|
{{- .PopRegs "rcx" "rdi" "rsi" "rbx" "rbp" | indent 4}}
|
||||||
|
{{- else}}
|
||||||
|
; SystemV64 ABI (linux mac or hopefully something similar), rbx rbp non-volatile
|
||||||
|
{{- .PopRegs "rdi" "rbx" "rbp" | indent 4}}
|
||||||
|
{{- end}}
|
||||||
|
ret
|
||||||
|
{{- else}}
|
||||||
|
{{- .PopRegs | indent 4}}
|
||||||
|
ret 4
|
||||||
|
{{- end}}
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; 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
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
{{.Func "actual_render_song" "syncBuf" "SyncStride" "VoiceTrackBitMask" "PolyPhonyBitMask" "NumVoices" "Tracks" "Opcodes" "Operands" "SynthObj"}}
|
||||||
|
xor eax, eax
|
||||||
|
{{.Push "1" "RandSeed"}}
|
||||||
|
{{.Push .AX "GlobalTick"}}
|
||||||
|
su_render_rowloop: ; loop through every row in the song
|
||||||
|
{{.Push .AX "Row"}}
|
||||||
|
{{.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 .AX "Sample"}}
|
||||||
|
mov {{.AX}}, {{.PTRWORD}} [{{.Stack "NumVoices"}}]
|
||||||
|
{{.Push .AX "VoicesRemain"}}
|
||||||
|
mov {{.DX}}, {{.PTRWORD}} [{{.Stack "SynthObj"}}] ; {{.DX}} points to the synth object
|
||||||
|
mov {{.COM}}, {{.PTRWORD}} [{{.Stack "Opcodes"}}] ; COM points to vm code
|
||||||
|
mov {{.VAL}}, {{.PTRWORD}} [{{.Stack "Operands"}}] ; VAL points to unit params
|
||||||
|
lea {{.CX}}, [{{.DX}} + su_synthworkspace.size - su_delayline_wrk.filtstate]
|
||||||
|
lea {{.WRK}}, [{{.DX}} + su_synthworkspace.voices] ; WRK points to the first voice
|
||||||
|
{{.Call "su_run_vm"}} ; run through the VM code
|
||||||
|
{{.Pop .AX}}
|
||||||
|
{{- template "output_sound.asm" .}} ; *ptr++ = left, *ptr++ = right
|
||||||
|
{{.Pop .AX}}
|
||||||
|
inc dword [{{.Stack "GlobalTick"}}] ; increment global time, used by delays
|
||||||
|
inc eax
|
||||||
|
cmp eax, {{.Song.SamplesPerRow}}
|
||||||
|
jl su_render_sampleloop
|
||||||
|
{{.Pop .AX}} ; Stack: pushad ptr
|
||||||
|
inc eax
|
||||||
|
cmp eax, {{mul .PatternLength .SequenceLength}}
|
||||||
|
jl su_render_rowloop
|
||||||
|
ret XXX
|
||||||
|
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; su_update_voices function: polyphonic & chord implementation
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; Input: eax : current row within song
|
||||||
|
; Dirty: pretty much everything
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
{{.Func "su_update_voices"}}
|
||||||
|
{{- if ne .VoiceTrackBitmask 0}}
|
||||||
|
; The more complicated implementation: one track can trigger multiple voices
|
||||||
|
xor edx, edx
|
||||||
|
mov ebx, {{.PatternLength}} ; we could do xor ebx,ebx; mov bl,PATTERN_SIZE, but that would limit patternsize to 256...
|
||||||
|
div ebx ; eax = current pattern, edx = current row in pattern
|
||||||
|
mov {{.SI}}, {{.PTRWORD}} [{{.Stack "Tracks"}}]
|
||||||
|
add {{.SI}}, {{.AX}} ; esi points to the pattern data for current track
|
||||||
|
xor eax, eax ; eax is the first voice of next track
|
||||||
|
xor ebx, ebx ; ebx is the first voice of current track
|
||||||
|
mov {{.BP}}, {{.PTRWORD}} [{{.Stack "SynthObj"}}] ; ebp points to the current_voiceno array
|
||||||
|
su_update_voices_trackloop:
|
||||||
|
movzx eax, byte [{{.SI}}] ; eax = current pattern
|
||||||
|
imul eax, {{.PatternLength}} ; eax = offset to current pattern data
|
||||||
|
{{- .Prepare "su_patterns" .AX | indent 4}}
|
||||||
|
movzx eax,byte [{{.Use "su_patterns" .AX}} + {{.DX}}] ; eax = note
|
||||||
|
push {{.DX}} ; Stack: ptrnrow
|
||||||
|
xor edx, edx ; edx=0
|
||||||
|
mov ecx, ebx ; ecx=first voice of the track to be done
|
||||||
|
su_calculate_voices_loop: ; do {
|
||||||
|
bt dword [{{.Stack "VoiceTrackBitmask"}} + {{.PTRSIZE}}],ecx ; test voicetrack_bitmask// notice that the incs don't set carry
|
||||||
|
inc edx ; edx++ // edx=numvoices
|
||||||
|
inc ecx ; ecx++ // ecx=the first voice of next track
|
||||||
|
jc su_calculate_voices_loop ; } while bit ecx-1 of bitmask is on
|
||||||
|
push {{.CX}} ; Stack: next_instr ptrnrow
|
||||||
|
cmp al, {{.Hold}} ; anything but hold causes action
|
||||||
|
je short su_update_voices_nexttrack
|
||||||
|
mov cl, byte [{{.BP}}]
|
||||||
|
mov edi, ecx
|
||||||
|
add edi, ebx
|
||||||
|
shl edi, 12 ; each unit = 64 bytes and there are 1<<MAX_UNITS_SHIFT units + small header
|
||||||
|
{{- .Prepare "su_synth_obj" | indent 4}}
|
||||||
|
and dword [{{.Use "su_synth_obj"}} + su_synthworkspace.voices + su_voice.sustain + {{.DI}}], 0 ; set the voice currently active to release; notice that it could increment any number of times
|
||||||
|
cmp al, {{.Hold}} ; if cl < HLD (no new note triggered)
|
||||||
|
jl su_update_voices_nexttrack ; goto nexttrack
|
||||||
|
inc ecx ; curvoice++
|
||||||
|
cmp ecx, edx ; if (curvoice >= num_voices)
|
||||||
|
jl su_update_voices_skipreset
|
||||||
|
xor ecx,ecx ; curvoice = 0
|
||||||
|
su_update_voices_skipreset:
|
||||||
|
mov byte [{{.BP}}],cl
|
||||||
|
add ecx, ebx
|
||||||
|
shl ecx, 12 ; each unit = 64 bytes and there are 1<<6 units + small header
|
||||||
|
lea {{.DI}},[{{.Use "su_synth_obj"}} + su_synthworkspace.voices + {{.CX}}]
|
||||||
|
stosd ; save note
|
||||||
|
stosd ; save release
|
||||||
|
mov ecx, (su_voice.size - su_voice.inputs)/4
|
||||||
|
xor eax, eax
|
||||||
|
rep stosd ; clear the workspace of the new voice, retriggering oscillators
|
||||||
|
su_update_voices_nexttrack:
|
||||||
|
pop {{.BX}} ; ebx=first voice of next instrument, Stack: ptrnrow
|
||||||
|
pop {{.DX}} ; edx=patrnrow
|
||||||
|
add {{.SI}}, {{.SequenceLength}}
|
||||||
|
inc {{.BP}}
|
||||||
|
{{- $addrname := len .Song.Score.Tracks | printf "su_synth_obj + %v"}}
|
||||||
|
{{- .Prepare $addrname | indent 8}}
|
||||||
|
cmp {{.BP}},{{.Use $addrname}}
|
||||||
|
jl su_update_voices_trackloop
|
||||||
|
ret
|
||||||
|
{{- else}}
|
||||||
|
; The simple implementation: each track triggers always the same voice
|
||||||
|
xor edx, edx
|
||||||
|
xor ebx, ebx
|
||||||
|
mov bl, {{.PatternLength}} ; rows per pattern
|
||||||
|
div ebx ; eax = current pattern, edx = current row in pattern
|
||||||
|
{{- .Prepare "su_tracks" | indent 4}}
|
||||||
|
lea {{.SI}}, [{{.Use "su_tracks"}}+{{.AX}}]; esi points to the pattern data for current track
|
||||||
|
mov {{.DI}}, {{.PTRWORD}} su_synth_obj+su_synthworkspace.voices
|
||||||
|
mov bl, {{len .Song.Score.Tracks}} ; MAX_TRACKS is always <= 32 so this is ok
|
||||||
|
su_update_voices_trackloop:
|
||||||
|
movzx eax, byte [{{.SI}}] ; eax = current pattern
|
||||||
|
imul eax, {{.PatternLength}} ; multiply by rows per pattern, eax = offset to current pattern data
|
||||||
|
{{- .Prepare "su_patterns" .AX | indent 8}}
|
||||||
|
movzx eax, byte [{{.Use "su_patterns" .AX}} + {{.DX}}] ; ecx = note
|
||||||
|
cmp al, {{.Hold}} ; anything but hold causes action
|
||||||
|
je short su_update_voices_nexttrack
|
||||||
|
mov dword [{{.DI}}+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 {{.DI}}, su_voice.size
|
||||||
|
su_update_voices_skipadd:
|
||||||
|
add {{.SI}}, {{.SequenceLength}}
|
||||||
|
dec ebx
|
||||||
|
jnz short su_update_voices_trackloop
|
||||||
|
ret
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
{{template "patch.asm" .}}
|
||||||
|
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; Patterns
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
{{.Data "su_patterns"}}
|
||||||
|
{{- range .Patterns}}
|
||||||
|
db {{. | toStrings | join ","}}
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; Tracks
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
{{- range $index := .Song.Patch.NumThreads}}
|
||||||
|
{{print "su_tracks" $index | .Data}}
|
||||||
|
{{- range (.Sequences $index)}}
|
||||||
|
db {{. | toStrings | join ","}}
|
||||||
|
{{- end}}
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
{{- if gt (.SampleOffsets | len) 0}}
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; Sample offsets
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
{{.Data "su_sample_offsets"}}
|
||||||
|
{{- range .SampleOffsets}}
|
||||||
|
dd {{.Start}}
|
||||||
|
dw {{.LoopStart}}
|
||||||
|
dw {{.LoopLength}}
|
||||||
|
{{- end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{- if gt (.DelayTimes | len ) 0}}
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; Delay times
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
{{.Data "su_delay_times"}}
|
||||||
|
dw {{.DelayTimes | toStrings | join ","}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; The code for this patch, basically indices to vm jump table
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
{{- range $index := .Song.Patch.NumThreads}}
|
||||||
|
{{print "su_patch_opcodes" $index | .Data}}
|
||||||
|
db {{.Opcodes $index | toStrings | join ","}}
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; The parameters / inputs to each opcode
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
{{- range $index := .Song.Patch.NumThreads}}
|
||||||
|
{{print "su_patch_operands" $index | .Data}}
|
||||||
|
db {{.Operands $index | toStrings | join ","}}
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; Constants
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
{{.SectData "constants"}}
|
||||||
|
{{.Constants}}
|
||||||
@ -13,6 +13,7 @@
|
|||||||
#define SU_LENGTH_IN_PATTERNS {{.Song.Score.Length}}
|
#define SU_LENGTH_IN_PATTERNS {{.Song.Score.Length}}
|
||||||
#define SU_LENGTH_IN_ROWS (SU_LENGTH_IN_PATTERNS*SU_PATTERN_SIZE)
|
#define SU_LENGTH_IN_ROWS (SU_LENGTH_IN_PATTERNS*SU_PATTERN_SIZE)
|
||||||
#define SU_SAMPLES_PER_ROW (SU_SAMPLE_RATE*60/(SU_BPM*SU_ROWS_PER_BEAT))
|
#define SU_SAMPLES_PER_ROW (SU_SAMPLE_RATE*60/(SU_BPM*SU_ROWS_PER_BEAT))
|
||||||
|
#define SU_NUMTHREADS {{.Song.Patch.NumThreads}}
|
||||||
|
|
||||||
{{- if or .RowSync (.HasOp "sync")}}
|
{{- if or .RowSync (.HasOp "sync")}}
|
||||||
{{- if .RowSync}}
|
{{- if .RowSync}}
|
||||||
@ -54,7 +55,9 @@ extern "C" {
|
|||||||
{{- if or .RowSync (.HasOp "sync")}}
|
{{- if or .RowSync (.HasOp "sync")}}
|
||||||
#define SU_SYNC
|
#define SU_SYNC
|
||||||
{{- end}}
|
{{- end}}
|
||||||
void SU_CALLCONV su_render_song(SUsample *buffer);
|
{{- range $index := .Song.Patch.NumThreads}}
|
||||||
|
void SU_CALLCONV su_render_song{{if gt $index 0}}{{add1 $index}}{{end}}(SUsample *buffer);
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
{{- if gt (.SampleOffsets | len) 0}}
|
{{- if gt (.SampleOffsets | len) 0}}
|
||||||
void SU_CALLCONV su_load_gmdls();
|
void SU_CALLCONV su_load_gmdls();
|
||||||
|
|||||||
@ -13,6 +13,8 @@
|
|||||||
%define SU_LENGTH_IN_PATTERNS {{.Song.Score.Length}}
|
%define SU_LENGTH_IN_PATTERNS {{.Song.Score.Length}}
|
||||||
%define SU_LENGTH_IN_ROWS (SU_LENGTH_IN_PATTERNS*SU_PATTERN_SIZE)
|
%define SU_LENGTH_IN_ROWS (SU_LENGTH_IN_PATTERNS*SU_PATTERN_SIZE)
|
||||||
%define SU_SAMPLES_PER_ROW (SU_SAMPLE_RATE*60/(SU_BPM*SU_ROWS_PER_BEAT))
|
%define SU_SAMPLES_PER_ROW (SU_SAMPLE_RATE*60/(SU_BPM*SU_ROWS_PER_BEAT))
|
||||||
|
%define SU_NUMTHREADS {{.Song.Patch.NumThreads}}
|
||||||
|
|
||||||
|
|
||||||
{{- if or .RowSync (.HasOp "sync")}}
|
{{- if or .RowSync (.HasOp "sync")}}
|
||||||
{{- if .RowSync}}
|
{{- if .RowSync}}
|
||||||
@ -38,11 +40,14 @@
|
|||||||
{{- end}}
|
{{- end}}
|
||||||
|
|
||||||
_su_symbols:
|
_su_symbols:
|
||||||
|
{{- range $index := .Song.Patch.NumThreads}}
|
||||||
%ifdef MANGLED
|
%ifdef MANGLED
|
||||||
extern _su_render_song@4
|
extern _su_render_song{{if gt $index 0}}{{add1 $index}}{{end}}@4
|
||||||
%else ; MANGLED
|
%else ; MANGLED
|
||||||
extern su_render_song
|
extern su_render_song{{if gt $index 0}}{{add1 $index}}{{end}}
|
||||||
%endif ; MANGLED
|
%endif ; MANGLED
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
|
||||||
{{- if gt (.SampleOffsets | len) 0}}
|
{{- if gt (.SampleOffsets | len) 0}}
|
||||||
extern _su_load_gmdls
|
extern _su_load_gmdls
|
||||||
|
|||||||
@ -27,7 +27,7 @@ type (
|
|||||||
voiceMapping [MAX_THREADS][MAX_VOICES]int
|
voiceMapping [MAX_THREADS][MAX_VOICES]int
|
||||||
|
|
||||||
multithreadSynthCommand struct {
|
multithreadSynthCommand struct {
|
||||||
core int
|
thread int
|
||||||
samples int
|
samples int
|
||||||
time int
|
time int
|
||||||
}
|
}
|
||||||
@ -104,7 +104,7 @@ func (s *MultithreadSynth) startProcesses() {
|
|||||||
for cmd := range commandCh {
|
for cmd := range commandCh {
|
||||||
buffer := s.pool.Get().(*sointu.AudioBuffer)
|
buffer := s.pool.Get().(*sointu.AudioBuffer)
|
||||||
*buffer = append(*buffer, make(sointu.AudioBuffer, cmd.samples)...)
|
*buffer = append(*buffer, make(sointu.AudioBuffer, cmd.samples)...)
|
||||||
samples, time, renderError := s.synths[cmd.core].Render(*buffer, cmd.time)
|
samples, time, renderError := s.synths[cmd.thread].Render(*buffer, cmd.time)
|
||||||
resultCh <- multithreadSynthResult{buffer: buffer, samples: samples, time: time, renderError: renderError}
|
resultCh <- multithreadSynthResult{buffer: buffer, samples: samples, time: time, renderError: renderError}
|
||||||
}
|
}
|
||||||
}(cmdChan, resultsChan)
|
}(cmdChan, resultsChan)
|
||||||
@ -124,16 +124,16 @@ func (s *MultithreadSynth) closeSynths() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MultithreadSynth) Trigger(voiceIndex int, note byte) {
|
func (s *MultithreadSynth) Trigger(voiceIndex int, note byte) {
|
||||||
for core, synth := range s.synths {
|
for i, synth := range s.synths {
|
||||||
if ind := s.voiceMapping[core][voiceIndex]; ind >= 0 {
|
if ind := s.voiceMapping[i][voiceIndex]; ind >= 0 {
|
||||||
synth.Trigger(ind, note)
|
synth.Trigger(ind, note)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MultithreadSynth) Release(voiceIndex int) {
|
func (s *MultithreadSynth) Release(voiceIndex int) {
|
||||||
for core, synth := range s.synths {
|
for i, synth := range s.synths {
|
||||||
if ind := s.voiceMapping[core][voiceIndex]; ind >= 0 {
|
if ind := s.voiceMapping[i][voiceIndex]; ind >= 0 {
|
||||||
synth.Release(ind)
|
synth.Release(ind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,7 +154,7 @@ func (s *MultithreadSynth) CPULoad(loads []sointu.CPULoad) (elems int) {
|
|||||||
func (s *MultithreadSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, time int, renderError error) {
|
func (s *MultithreadSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, time int, renderError error) {
|
||||||
count := len(s.synths)
|
count := len(s.synths)
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
s.commands <- multithreadSynthCommand{core: i, samples: len(buffer), time: maxtime}
|
s.commands <- multithreadSynthCommand{thread: i, samples: len(buffer), time: maxtime}
|
||||||
}
|
}
|
||||||
clear(buffer)
|
clear(buffer)
|
||||||
samples = math.MaxInt
|
samples = math.MaxInt
|
||||||
|
|||||||
Reference in New Issue
Block a user