diff --git a/cmd/sointu-compile/main.go b/cmd/sointu-compile/main.go index 90480d0..5a14664 100644 --- a/cmd/sointu-compile/main.go +++ b/cmd/sointu-compile/main.go @@ -45,6 +45,7 @@ func main() { 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.") 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.Parse() if *versionFlag { @@ -60,9 +61,9 @@ func main() { if compile || *library { var err error if *tmplDir != "" { - comp, err = compiler.NewFromTemplates(*targetOs, *targetArch, *output16bit, *rowsync, *tmplDir) + comp, err = compiler.NewFromTemplates(*targetOs, *targetArch, *output16bit, *rowsync, *forceSingleThread, *tmplDir) } else { - comp, err = compiler.New(*targetOs, *targetArch, *output16bit, *rowsync) + comp, err = compiler.New(*targetOs, *targetArch, *output16bit, *rowsync, *forceSingleThread) } if err != nil { fmt.Fprintf(os.Stderr, `error creating compiler: %v`, err) @@ -143,7 +144,7 @@ func main() { var compiledPlayer map[string]string if compile { var err error - compiledPlayer, err = comp.Song(&song) + compiledPlayer, err = comp.Song(song) if err != nil { return fmt.Errorf("compiling player failed: %v", err) } diff --git a/patch.go b/patch.go index c51aa50..87219db 100644 --- a/patch.go +++ b/patch.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math" + "math/bits" "sort" "strconv" @@ -16,12 +17,15 @@ type ( // Instrument includes a list of units consisting of the instrument, and the number of polyphonic voices for this instrument Instrument struct { - Name string `yaml:",omitempty"` - Comment string `yaml:",omitempty"` - NumVoices int - Units []Unit - 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. + Name string `yaml:",omitempty"` + Comment string `yaml:",omitempty"` + NumVoices int + Units []Unit + Mute bool `yaml:",omitempty"` // Mute is only used in the tracker for soloing/muting instruments; the compiled player ignores this field + // 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 @@ -539,6 +543,16 @@ func (p Patch) NumSyncs() int { 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 // instrument. For example, if the Patch has three instruments (0, 1 and 2), // with 1, 3, 2 voices, respectively, then FirstVoiceForInstrument(0) returns 0, diff --git a/tracker/bool.go b/tracker/bool.go index 54e8d8a..353a1bf 100644 --- a/tracker/bool.go +++ b/tracker/bool.go @@ -95,7 +95,7 @@ func (m *Model) setThreadsBit(bit int, value bool) { } else { 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.warnNoMultithreadSupport() } diff --git a/vm/compiler/compiler.go b/vm/compiler/compiler.go index f6a8faf..5d86d46 100644 --- a/vm/compiler/compiler.go +++ b/vm/compiler/compiler.go @@ -13,18 +13,19 @@ import ( ) type Compiler struct { - Template *template.Template - OS string - Arch string - Output16Bit bool - RowSync bool + Template *template.Template + OS string + Arch string + Output16Bit bool + RowSync bool + ForceSingleThread bool } //go:embed templates/amd64-386/* templates/wasm/* var templateFS embed.FS // 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 if arch == "386" || arch == "amd64" { subdir = "amd64-386" @@ -37,16 +38,16 @@ func New(os string, arch string, output16Bit bool, rowsync bool) (*Compiler, err if err != nil { 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, "*.*") tmpl, err := template.New("base").Funcs(sprig.TxtFuncMap()).ParseGlob(globPtrn) if err != nil { 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) { @@ -75,13 +76,23 @@ func (com *Compiler) Library() (map[string]string, error) { 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" { 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 if com.Arch == "386" || com.Arch == "amd64" { - templates = []string{"player.asm", "player.h", "player.inc"} + if song.Patch.NumThreads() > 1 { + templates = []string{"multithread_player.asm", "player.h", "player.inc"} + } else { + templates = []string{"player.asm", "player.h", "player.inc"} + } } else if com.Arch == "wasm" { templates = []string{"player.wat"} } @@ -91,14 +102,14 @@ func (com *Compiler) Song(song *sointu.Song) (map[string]string, error) { if err != nil { return nil, fmt.Errorf(`could not encode patch: %v`, err) } - patterns, sequences, err := ConstructPatterns(song) + patterns, sequences, err := ConstructPatterns(&song) if err != nil { return nil, fmt.Errorf(`could not encode song: %v`, err) } for _, templateName := range templates { compilerMacros := *NewCompilerMacros(*com) featureSetMacros := FeatureSetMacros{features} - songMacros := *NewSongMacros(song) + songMacros := *NewSongMacros(&song) var populatedTemplate, extension string var err error if com.Arch == "386" || com.Arch == "amd64" { diff --git a/vm/compiler/templates/amd64-386/multithread_player.asm b/vm/compiler/templates/amd64-386/multithread_player.asm new file mode 100644 index 0000000..096f312 --- /dev/null +++ b/vm/compiler/templates/amd64-386/multithread_player.asm @@ -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<= 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}} diff --git a/vm/compiler/templates/amd64-386/player.h b/vm/compiler/templates/amd64-386/player.h index 35de63f..c8732d7 100644 --- a/vm/compiler/templates/amd64-386/player.h +++ b/vm/compiler/templates/amd64-386/player.h @@ -13,6 +13,7 @@ #define SU_LENGTH_IN_PATTERNS {{.Song.Score.Length}} #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_NUMTHREADS {{.Song.Patch.NumThreads}} {{- if or .RowSync (.HasOp "sync")}} {{- if .RowSync}} @@ -54,7 +55,9 @@ extern "C" { {{- if or .RowSync (.HasOp "sync")}} #define SU_SYNC {{- 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}} void SU_CALLCONV su_load_gmdls(); diff --git a/vm/compiler/templates/amd64-386/player.inc b/vm/compiler/templates/amd64-386/player.inc index d2e3f99..b707a22 100644 --- a/vm/compiler/templates/amd64-386/player.inc +++ b/vm/compiler/templates/amd64-386/player.inc @@ -13,6 +13,8 @@ %define SU_LENGTH_IN_PATTERNS {{.Song.Score.Length}} %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_NUMTHREADS {{.Song.Patch.NumThreads}} + {{- if or .RowSync (.HasOp "sync")}} {{- if .RowSync}} @@ -38,11 +40,14 @@ {{- end}} _su_symbols: +{{- range $index := .Song.Patch.NumThreads}} %ifdef MANGLED - extern _su_render_song@4 + extern _su_render_song{{if gt $index 0}}{{add1 $index}}{{end}}@4 %else ; MANGLED - extern su_render_song + extern su_render_song{{if gt $index 0}}{{add1 $index}}{{end}} %endif ; MANGLED +{{- end}} + {{- if gt (.SampleOffsets | len) 0}} extern _su_load_gmdls diff --git a/vm/multithread_synth.go b/vm/multithread_synth.go index 1a59ba1..026084d 100644 --- a/vm/multithread_synth.go +++ b/vm/multithread_synth.go @@ -27,7 +27,7 @@ type ( voiceMapping [MAX_THREADS][MAX_VOICES]int multithreadSynthCommand struct { - core int + thread int samples int time int } @@ -104,7 +104,7 @@ func (s *MultithreadSynth) startProcesses() { for cmd := range commandCh { buffer := s.pool.Get().(*sointu.AudioBuffer) *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} } }(cmdChan, resultsChan) @@ -124,16 +124,16 @@ func (s *MultithreadSynth) closeSynths() { } func (s *MultithreadSynth) Trigger(voiceIndex int, note byte) { - for core, synth := range s.synths { - if ind := s.voiceMapping[core][voiceIndex]; ind >= 0 { + for i, synth := range s.synths { + if ind := s.voiceMapping[i][voiceIndex]; ind >= 0 { synth.Trigger(ind, note) } } } func (s *MultithreadSynth) Release(voiceIndex int) { - for core, synth := range s.synths { - if ind := s.voiceMapping[core][voiceIndex]; ind >= 0 { + for i, synth := range s.synths { + if ind := s.voiceMapping[i][voiceIndex]; ind >= 0 { 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) { count := len(s.synths) 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) samples = math.MaxInt