From f92ecb2e99aeda7fb1ab9d3a7710d3a00eed1678 Mon Sep 17 00:00:00 2001 From: "5684185+vsariola@users.noreply.github.com" <5684185+vsariola@users.noreply.github.com> Date: Fri, 24 Oct 2025 23:55:04 +0300 Subject: [PATCH] drafting --- audio.go | 10 +-- cmd/synthers.go | 2 +- cmd/synthers_native.go | 2 +- patch.go | 12 +-- tracker/bool.go | 68 ++++++++------- tracker/gioui/instrument_properties.go | 26 +++--- tracker/gioui/song_panel.go | 51 +++++++----- tracker/model.go | 5 +- tracker/player.go | 7 +- vm/compiler/bridge/native_synth.go | 10 +-- vm/go_synth.go | 10 +-- ...parallel_synth.go => multithread_synth.go} | 83 +++++++++---------- 12 files changed, 152 insertions(+), 134 deletions(-) rename vm/{parallel_synth.go => multithread_synth.go} (64%) diff --git a/audio.go b/audio.go index d990ba3..cc2f12c 100644 --- a/audio.go +++ b/audio.go @@ -67,11 +67,9 @@ type ( // Close disposes the synth, freeing any resources. No other functions should be called after Close. Close() - // Returns the number of cores the synth is using - NumCores() int - - // Populates the given array with the current CPU load of each core - CPULoad([]CPULoad) + // Populates the given array with the current CPU load of each thread, + // returning the number of threads / elements populated + CPULoad([]CPULoad) int } // Synther compiles a given Patch into a Synth, throwing errors if the @@ -79,7 +77,7 @@ type ( Synther interface { Name() string // Name of the synther, e.g. "Go" or "Native" Synth(patch Patch, bpm int) (Synth, error) - SupportsParallelism() bool + SupportsMultithreading() bool } CPULoad float32 diff --git a/cmd/synthers.go b/cmd/synthers.go index a0526ee..7da1d1c 100644 --- a/cmd/synthers.go +++ b/cmd/synthers.go @@ -7,5 +7,5 @@ import ( var Synthers = []sointu.Synther{ vm.GoSynther{}, - vm.MakeParallelSynther(vm.GoSynther{}), + vm.MakeMultithreadSynther(vm.GoSynther{}), } diff --git a/cmd/synthers_native.go b/cmd/synthers_native.go index 60dce0b..3ef7009 100644 --- a/cmd/synthers_native.go +++ b/cmd/synthers_native.go @@ -9,5 +9,5 @@ import ( func init() { Synthers = append(Synthers, bridge.NativeSynther{}) - Synthers = append(Synthers, vm.MakeParallelSynther(bridge.NativeSynther{})) + Synthers = append(Synthers, vm.MakeMultithreadSynther(bridge.NativeSynther{})) } diff --git a/patch.go b/patch.go index f08dc6c..c51aa50 100644 --- a/patch.go +++ b/patch.go @@ -16,12 +16,12 @@ 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 - CoreMaskM1 int `yaml:",omitempty"` // CoreMaskM1 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 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. } // Unit is e.g. a filter, oscillator, envelope and its parameters diff --git a/tracker/bool.go b/tracker/bool.go index c6f0a7a..54e8d8a 100644 --- a/tracker/bool.go +++ b/tracker/bool.go @@ -1,6 +1,8 @@ package tracker -import "fmt" +import ( + "fmt" +) type ( Bool struct { @@ -31,10 +33,10 @@ type ( InstrEditor Model InstrPresets Model InstrComment Model - Core1 Model - Core2 Model - Core3 Model - Core4 Model + Thread1 Model + Thread2 Model + Thread3 Model + Thread4 Model ) func MakeBool(valueEnabler interface { @@ -72,32 +74,33 @@ func (v Bool) Enabled() bool { return v.enabler.Enabled() } -// Core methods +// Thread methods -func (m *Model) getCoresBit(bit int) bool { +func (m *Model) getThreadsBit(bit int) bool { if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) { return false } - mask := m.d.Song.Patch[m.d.InstrIndex].CoreMaskM1 + 1 + mask := m.d.Song.Patch[m.d.InstrIndex].ThreadMaskM1 + 1 return mask&(1<= len(m.d.Song.Patch) { return } - defer (*Model)(m).change("CoreBitMask", PatchChange, MinorChange)() - mask := m.d.Song.Patch[m.d.InstrIndex].CoreMaskM1 + 1 + defer (*Model)(m).change("ThreadBitMask", PatchChange, MinorChange)() + mask := m.d.Song.Patch[m.d.InstrIndex].ThreadMaskM1 + 1 if value { mask |= (1 << bit) } else { mask &^= (1 << bit) } - m.d.Song.Patch[m.d.InstrIndex].CoreMaskM1 = max(mask-1, 0) // -1 would have all cores disabled, so make that 0 i.e. use core 1 only - m.warnAboutCrossCoreSends() + 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.warnAboutCrossThreadSends() + m.warnNoMultithreadSupport() } -func (m *Model) warnAboutCrossCoreSends() { +func (m *Model) warnAboutCrossThreadSends() { for i, instr := range m.d.Song.Patch { for _, unit := range instr.Units { if unit.Type == "send" { @@ -109,8 +112,8 @@ func (m *Model) warnAboutCrossCoreSends() { if err != nil { continue } - if instr.CoreMaskM1 != m.d.Song.Patch[it].CoreMaskM1 { - m.Alerts().AddNamed("CrossCoreSend", fmt.Sprintf("Instrument %d '%s' has a send to instrument %d '%s' but they are not on the same cores, which may cause issues", i+1, instr.Name, it+1, m.d.Song.Patch[it].Name), Warning) + if instr.ThreadMaskM1 != m.d.Song.Patch[it].ThreadMaskM1 { + m.Alerts().AddNamed("CrossThreadSend", fmt.Sprintf("Instrument %d '%s' has a send to instrument %d '%s' but they are not on the same threads, which may cause issues", i+1, instr.Name, it+1, m.d.Song.Patch[it].Name), Warning) return } } @@ -118,21 +121,30 @@ func (m *Model) warnAboutCrossCoreSends() { } } -func (m *Model) Core1() Bool { return MakeEnabledBool((*Core1)(m)) } -func (m *Core1) Value() bool { return (*Model)(m).getCoresBit(0) } -func (m *Core1) SetValue(val bool) { (*Model)(m).setCoresBit(0, val) } +func (m *Model) warnNoMultithreadSupport() { + for _, instr := range m.d.Song.Patch { + if instr.ThreadMaskM1 > 0 && !m.synthers[m.syntherIndex].SupportsMultithreading() { + m.Alerts().AddNamed("NoMultithreadSupport", "The current synth does not support multithreading and the patch was configured to use more than one thread", Warning) + return + } + } +} -func (m *Model) Core2() Bool { return MakeEnabledBool((*Core2)(m)) } -func (m *Core2) Value() bool { return (*Model)(m).getCoresBit(1) } -func (m *Core2) SetValue(val bool) { (*Model)(m).setCoresBit(1, val) } +func (m *Model) Thread1() Bool { return MakeEnabledBool((*Thread1)(m)) } +func (m *Thread1) Value() bool { return (*Model)(m).getThreadsBit(0) } +func (m *Thread1) SetValue(val bool) { (*Model)(m).setThreadsBit(0, val) } -func (m *Model) Core3() Bool { return MakeEnabledBool((*Core3)(m)) } -func (m *Core3) Value() bool { return (*Model)(m).getCoresBit(2) } -func (m *Core3) SetValue(val bool) { (*Model)(m).setCoresBit(2, val) } +func (m *Model) Thread2() Bool { return MakeEnabledBool((*Thread2)(m)) } +func (m *Thread2) Value() bool { return (*Model)(m).getThreadsBit(1) } +func (m *Thread2) SetValue(val bool) { (*Model)(m).setThreadsBit(1, val) } -func (m *Model) Core4() Bool { return MakeEnabledBool((*Core4)(m)) } -func (m *Core4) Value() bool { return (*Model)(m).getCoresBit(3) } -func (m *Core4) SetValue(val bool) { (*Model)(m).setCoresBit(3, val) } +func (m *Model) Thread3() Bool { return MakeEnabledBool((*Thread3)(m)) } +func (m *Thread3) Value() bool { return (*Model)(m).getThreadsBit(2) } +func (m *Thread3) SetValue(val bool) { (*Model)(m).setThreadsBit(2, val) } + +func (m *Model) Thread4() Bool { return MakeEnabledBool((*Thread4)(m)) } +func (m *Thread4) Value() bool { return (*Model)(m).getThreadsBit(3) } +func (m *Thread4) SetValue(val bool) { (*Model)(m).setThreadsBit(3, val) } // Panic methods diff --git a/tracker/gioui/instrument_properties.go b/tracker/gioui/instrument_properties.go index 9220f40..a544033 100644 --- a/tracker/gioui/instrument_properties.go +++ b/tracker/gioui/instrument_properties.go @@ -19,7 +19,7 @@ type ( list *layout.List soloBtn *Clickable muteBtn *Clickable - coreBtns [4]*Clickable + threadBtns [4]*Clickable soloHint string unsoloHint string muteHint string @@ -39,7 +39,7 @@ func NewInstrumentProperties() *InstrumentProperties { muteBtn: new(Clickable), voices: NewNumericUpDownState(), splitInstrumentBtn: new(Clickable), - coreBtns: [4]*Clickable{new(Clickable), new(Clickable), new(Clickable), new(Clickable)}, + threadBtns: [4]*Clickable{new(Clickable), new(Clickable), new(Clickable), new(Clickable)}, } ret.soloHint = makeHint("Solo", " (%s)", "SoloToggle") ret.unsoloHint = makeHint("Unsolo", " (%s)", "SoloToggle") @@ -68,17 +68,17 @@ func (ip *InstrumentProperties) layout(gtx C) D { ) } - core1btn := ToggleIconBtn(tr.Core1(), tr.Theme, ip.coreBtns[0], icons.ImageCropSquare, icons.ImageFilter1, "Do not render instrument on core 1", "Render instrument on core 1") - core2btn := ToggleIconBtn(tr.Core2(), tr.Theme, ip.coreBtns[1], icons.ImageCropSquare, icons.ImageFilter2, "Do not render instrument on core 2", "Render instrument on core 2") - core3btn := ToggleIconBtn(tr.Core3(), tr.Theme, ip.coreBtns[2], icons.ImageCropSquare, icons.ImageFilter3, "Do not render instrument on core 3", "Render instrument on core 3") - core4btn := ToggleIconBtn(tr.Core4(), tr.Theme, ip.coreBtns[3], icons.ImageCropSquare, icons.ImageFilter4, "Do not render instrument on core 4", "Render instrument on core 4") + thread1btn := ToggleIconBtn(tr.Thread1(), tr.Theme, ip.threadBtns[0], icons.ImageCropSquare, icons.ImageFilter1, "Do not render instrument on thread 1", "Render instrument on thread 1") + thread2btn := ToggleIconBtn(tr.Thread2(), tr.Theme, ip.threadBtns[1], icons.ImageCropSquare, icons.ImageFilter2, "Do not render instrument on thread 2", "Render instrument on thread 2") + thread3btn := ToggleIconBtn(tr.Thread3(), tr.Theme, ip.threadBtns[2], icons.ImageCropSquare, icons.ImageFilter3, "Do not render instrument on thread 3", "Render instrument on thread 3") + thread4btn := ToggleIconBtn(tr.Thread4(), tr.Theme, ip.threadBtns[3], icons.ImageCropSquare, icons.ImageFilter4, "Do not render instrument on thread 4", "Render instrument on thread 4") - corebtnline := func(gtx C) D { + threadbtnline := func(gtx C) D { return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(core1btn.Layout), - layout.Rigid(core2btn.Layout), - layout.Rigid(core3btn.Layout), - layout.Rigid(core4btn.Layout), + layout.Rigid(thread1btn.Layout), + layout.Rigid(thread2btn.Layout), + layout.Rigid(thread3btn.Layout), + layout.Rigid(thread4btn.Layout), ) } @@ -97,7 +97,7 @@ func (ip *InstrumentProperties) layout(gtx C) D { soloBtn := ToggleIconBtn(tr.Solo(), tr.Theme, ip.soloBtn, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, ip.soloHint, ip.unsoloHint) return layoutInstrumentPropertyLine(gtx, "Solo", soloBtn.Layout) case 8: - return layoutInstrumentPropertyLine(gtx, "Cores", corebtnline) + return layoutInstrumentPropertyLine(gtx, "Thread", threadbtnline) case 10: return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx C) D { return ip.commentEditor.Layout(gtx, tr.InstrumentComment(), tr.Theme, &tr.Theme.InstrumentEditor.InstrumentComment, "Comment") @@ -112,7 +112,7 @@ func (ip *InstrumentProperties) layout(gtx C) D { func layoutInstrumentPropertyLine(gtx C, text string, content layout.Widget) D { tr := TrackerFromContext(gtx) - gtx.Constraints.Max.X = min(gtx.Dp(unit.Dp(200)), gtx.Constraints.Max.X) + gtx.Constraints.Max.X = min(gtx.Dp(300), gtx.Constraints.Max.X) label := Label(tr.Theme, &tr.Theme.InstrumentEditor.Properties.Label, text) return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(layout.Spacer{Width: 6, Height: 36}.Layout), diff --git a/tracker/gioui/song_panel.go b/tracker/gioui/song_panel.go index 1b96380..807c8e2 100644 --- a/tracker/gioui/song_panel.go +++ b/tracker/gioui/song_panel.go @@ -4,6 +4,7 @@ import ( "fmt" "image" "image/color" + "slices" "strconv" "strings" @@ -113,25 +114,37 @@ func (t *SongPanel) layoutSongOptions(gtx C) D { } oversamplingBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.OversamplingBtn, oversamplingTxt, "") - var sb strings.Builder - var loadArr [vm.MAX_CORES]sointu.CPULoad - tr.Model.CPULoad(loadArr[:]) - c := min(vm.MAX_CORES, tr.Model.NumCores()) - high := false - for i := range c { - if i > 0 { - // unless this is the first item, add the separator before it. - fmt.Fprint(&sb, ", ") - } - cpuLoad := loadArr[i] - fmt.Fprintf(&sb, "%.0f %%", cpuLoad*100) - if cpuLoad >= 1 { - high = true + cpuSmallLabel := func(gtx C) D { + var a [vm.MAX_THREADS]sointu.CPULoad + c := tr.Model.CPULoad(a[:]) + load := slices.Max(a[:c]) + cpuLabel := Label(tr.Theme, &tr.Theme.SongPanel.RowValue, fmt.Sprintf("%d%%", int(load*100+0.5))) + if load >= 1 { + cpuLabel.Color = tr.Theme.SongPanel.ErrorColor } + return cpuLabel.Layout(gtx) } - cpuLabel := Label(tr.Theme, &tr.Theme.SongPanel.RowValue, sb.String()) - if high { - cpuLabel.Color = tr.Theme.SongPanel.ErrorColor + + cpuEnlargedWidget := func(gtx C) D { + var sb strings.Builder + var a [vm.MAX_THREADS]sointu.CPULoad + c := tr.Model.CPULoad(a[:]) + high := false + for i := range c { + if i > 0 { + fmt.Fprint(&sb, ", ") + } + cpuLoad := a[i] + fmt.Fprintf(&sb, "%d%%", int(cpuLoad*100+0.5)) + if cpuLoad >= 1 { + high = true + } + } + cpuLabel := Label(tr.Theme, &tr.Theme.SongPanel.RowValue, sb.String()) + if high { + cpuLabel.Color = tr.Theme.SongPanel.ErrorColor + } + return cpuLabel.Layout(gtx) } synthBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.SynthBtn, tr.Model.SyntherName(), "") @@ -168,10 +181,10 @@ func (t *SongPanel) layoutSongOptions(gtx C) D { }) }), layout.Rigid(func(gtx C) D { - return t.CPUExpander.Layout(gtx, tr.Theme, "CPU", cpuLabel.Layout, + return t.CPUExpander.Layout(gtx, tr.Theme, "CPU", cpuSmallLabel, func(gtx C) D { return layout.Flex{Axis: layout.Vertical, Alignment: layout.End}.Layout(gtx, - layout.Rigid(func(gtx C) D { return layoutSongOptionRow(gtx, tr.Theme, "Load", cpuLabel.Layout) }), + layout.Rigid(func(gtx C) D { return layoutSongOptionRow(gtx, tr.Theme, "Load", cpuEnlargedWidget) }), layout.Rigid(func(gtx C) D { return layoutSongOptionRow(gtx, tr.Theme, "Synth", synthBtn.Layout) }), ) }, diff --git a/tracker/model.go b/tracker/model.go index 358dd81..6d37ca4 100644 --- a/tracker/model.go +++ b/tracker/model.go @@ -397,8 +397,9 @@ func (m *Model) ProcessMsg(msg MsgToModel) { } } -func (m *Model) NumCores() int { return m.playerStatus.NumCores } -func (m *Model) CPULoad(buf []sointu.CPULoad) { copy(buf, m.playerStatus.CPULoad[:]) } +func (m *Model) CPULoad(buf []sointu.CPULoad) int { + return copy(buf, m.playerStatus.CPULoad[:m.playerStatus.NumThreads]) +} func (m *Model) SignalAnalyzer() *ScopeModel { return m.signalAnalyzer } func (m *Model) Broker() *Broker { return m.broker } diff --git a/tracker/player.go b/tracker/player.go index 2414b6e..54eb944 100644 --- a/tracker/player.go +++ b/tracker/player.go @@ -41,8 +41,8 @@ type ( PlayerStatus struct { SongPos sointu.SongPos // the current position in the score VoiceLevels [vm.MAX_VOICES]float32 // a level that can be used to visualize the volume of each voice - NumCores int - CPULoad [vm.MAX_CORES]sointu.CPULoad // current CPU load of the player, used to adjust the render rate + NumThreads int + CPULoad [vm.MAX_THREADS]sointu.CPULoad // current CPU load of the player, used to adjust the render rate } // PlayerProcessContext is the context given to the player when processing @@ -162,8 +162,7 @@ func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext // when the buffer is full, return if len(buffer) == 0 { if p.synth != nil { - p.status.NumCores = p.synth.NumCores() - p.synth.CPULoad(p.status.CPULoad[:]) + p.status.NumThreads = p.synth.CPULoad(p.status.CPULoad[:]) } p.send(nil) return diff --git a/vm/compiler/bridge/native_synth.go b/vm/compiler/bridge/native_synth.go index ec5b966..9ffcd93 100644 --- a/vm/compiler/bridge/native_synth.go +++ b/vm/compiler/bridge/native_synth.go @@ -22,8 +22,8 @@ type NativeSynth struct { cpuLoad sointu.CPULoad } -func (s NativeSynther) Name() string { return "Native" } -func (s NativeSynther) SupportsParallelism() bool { return false } +func (s NativeSynther) Name() string { return "Native" } +func (s NativeSynther) SupportsMultithreading() bool { return false } func (s NativeSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) { synth, err := Synth(patch, bpm) @@ -74,12 +74,12 @@ func Synth(patch sointu.Patch, bpm int) (*NativeSynth, error) { func (s *NativeSynth) Close() {} -func (s *NativeSynth) NumCores() int { return 1 } -func (s *NativeSynth) CPULoad(loads []sointu.CPULoad) { +func (s *NativeSynth) CPULoad(loads []sointu.CPULoad) int { if len(loads) < 1 { - return + return 0 } loads[0] = s.cpuLoad + return 1 } // Render renders until the buffer is full or the modulated time is reached, whichever diff --git a/vm/go_synth.go b/vm/go_synth.go index 268c862..89a3e08 100644 --- a/vm/go_synth.go +++ b/vm/go_synth.go @@ -95,8 +95,8 @@ success: f.Read(su_sample_table[:]) } -func (s GoSynther) Name() string { return "Go" } -func (s GoSynther) SupportsParallelism() bool { return false } +func (s GoSynther) Name() string { return "Go" } +func (s GoSynther) SupportsMultithreading() bool { return false } func (s GoSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) { bytecode, err := NewBytecode(patch, AllFeatures{}, bpm) @@ -120,12 +120,12 @@ func (s *GoSynth) Release(voiceIndex int) { func (s *GoSynth) Close() {} -func (s *GoSynth) NumCores() int { return 1 } -func (s *GoSynth) CPULoad(loads []sointu.CPULoad) { +func (s *GoSynth) CPULoad(loads []sointu.CPULoad) int { if len(loads) < 1 { - return + return 0 } loads[0] = s.cpuLoad + return 1 } func (s *GoSynth) Update(patch sointu.Patch, bpm int) error { diff --git a/vm/parallel_synth.go b/vm/multithread_synth.go similarity index 64% rename from vm/parallel_synth.go rename to vm/multithread_synth.go index 6f1b0e9..1a59ba1 100644 --- a/vm/parallel_synth.go +++ b/vm/multithread_synth.go @@ -10,29 +10,29 @@ import ( ) type ( - ParallelSynth struct { + MultithreadSynth struct { voiceMapping voiceMapping synths []sointu.Synth - commands chan<- parallelSynthCommand // maxtime - results <-chan parallelSynthResult // rendered buffer + commands chan<- multithreadSynthCommand // maxtime + results <-chan multithreadSynthResult // rendered buffer pool sync.Pool synther sointu.Synther } - ParallelSynther struct { + MultithreadSynther struct { synther sointu.Synther name string } - voiceMapping [MAX_CORES][MAX_VOICES]int + voiceMapping [MAX_THREADS][MAX_VOICES]int - parallelSynthCommand struct { + multithreadSynthCommand struct { core int samples int time int } - parallelSynthResult struct { + multithreadSynthResult struct { buffer *sointu.AudioBuffer samples int time int @@ -40,16 +40,16 @@ type ( } ) -const MAX_CORES = 4 +const MAX_THREADS = 4 -func MakeParallelSynther(synther sointu.Synther) ParallelSynther { - return ParallelSynther{synther: synther, name: "Parallel " + synther.Name()} +func MakeMultithreadSynther(synther sointu.Synther) MultithreadSynther { + return MultithreadSynther{synther: synther, name: "Multithread " + synther.Name()} } -func (s ParallelSynther) Name() string { return s.name } -func (s ParallelSynther) SupportsParallelism() bool { return true } +func (s MultithreadSynther) Name() string { return s.name } +func (s MultithreadSynther) SupportsMultithreading() bool { return true } -func (s ParallelSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) { +func (s MultithreadSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) { patches, voiceMapping := splitPatchByCores(patch) synths := make([]sointu.Synth, 0, len(patches)) for _, p := range patches { @@ -59,7 +59,7 @@ func (s ParallelSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error } synths = append(synths, synth) } - ret := &ParallelSynth{ + ret := &MultithreadSynth{ synths: synths, voiceMapping: voiceMapping, pool: sync.Pool{New: func() any { ret := make(sointu.AudioBuffer, 0, 8096); return &ret }}, @@ -69,7 +69,7 @@ func (s ParallelSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error return ret, nil } -func (s *ParallelSynth) Update(patch sointu.Patch, bpm int) error { +func (s *MultithreadSynth) Update(patch sointu.Patch, bpm int) error { patches, voiceMapping := splitPatchByCores(patch) if s.voiceMapping != voiceMapping { s.voiceMapping = voiceMapping @@ -93,37 +93,37 @@ func (s *ParallelSynth) Update(patch sointu.Patch, bpm int) error { return nil } -func (s *ParallelSynth) startProcesses() { +func (s *MultithreadSynth) startProcesses() { maxProcs := runtime.GOMAXPROCS(0) - cmdChan := make(chan parallelSynthCommand, maxProcs) + cmdChan := make(chan multithreadSynthCommand, maxProcs) s.commands = cmdChan - resultsChan := make(chan parallelSynthResult, maxProcs) + resultsChan := make(chan multithreadSynthResult, maxProcs) s.results = resultsChan for i := 0; i < maxProcs; i++ { - go func(commandCh <-chan parallelSynthCommand, resultCh chan<- parallelSynthResult) { + go func(commandCh <-chan multithreadSynthCommand, resultCh chan<- multithreadSynthResult) { 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) - resultCh <- parallelSynthResult{buffer: buffer, samples: samples, time: time, renderError: renderError} + resultCh <- multithreadSynthResult{buffer: buffer, samples: samples, time: time, renderError: renderError} } }(cmdChan, resultsChan) } } -func (s *ParallelSynth) Close() { +func (s *MultithreadSynth) Close() { close(s.commands) s.closeSynths() } -func (s *ParallelSynth) closeSynths() { +func (s *MultithreadSynth) closeSynths() { for _, synth := range s.synths { synth.Close() } s.synths = s.synths[:0] } -func (s *ParallelSynth) Trigger(voiceIndex int, note byte) { +func (s *MultithreadSynth) Trigger(voiceIndex int, note byte) { for core, synth := range s.synths { if ind := s.voiceMapping[core][voiceIndex]; ind >= 0 { synth.Trigger(ind, note) @@ -131,7 +131,7 @@ func (s *ParallelSynth) Trigger(voiceIndex int, note byte) { } } -func (s *ParallelSynth) Release(voiceIndex int) { +func (s *MultithreadSynth) Release(voiceIndex int) { for core, synth := range s.synths { if ind := s.voiceMapping[core][voiceIndex]; ind >= 0 { synth.Release(ind) @@ -139,27 +139,22 @@ func (s *ParallelSynth) Release(voiceIndex int) { } } -func (s *ParallelSynth) NumCores() (coreCount int) { - for i := range s.synths { - coreCount += s.synths[i].NumCores() +func (s *MultithreadSynth) CPULoad(loads []sointu.CPULoad) (elems int) { + for _, synth := range s.synths { + n := synth.CPULoad(loads) + elems += n + loads = loads[n:] + if len(loads) <= 0 { + return + } } return } -func (s *ParallelSynth) CPULoad(loads []sointu.CPULoad) { - for _, synth := range s.synths { - synth.CPULoad(loads) - if len(loads) <= synth.NumCores() { - return - } - loads = loads[synth.NumCores():] - } -} - -func (s *ParallelSynth) 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) for i := 0; i < count; i++ { - s.commands <- parallelSynthCommand{core: i, samples: len(buffer), time: maxtime} + s.commands <- multithreadSynthCommand{core: i, samples: len(buffer), time: maxtime} } clear(buffer) samples = math.MaxInt @@ -187,15 +182,15 @@ func (s *ParallelSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples func splitPatchByCores(patch sointu.Patch) ([]sointu.Patch, voiceMapping) { cores := 1 for _, instr := range patch { - cores = max(bits.Len((uint)(instr.CoreMaskM1+1)), cores) + cores = max(bits.Len((uint)(instr.ThreadMaskM1+1)), cores) } - cores = min(cores, MAX_CORES) + cores = min(cores, MAX_THREADS) ret := make([]sointu.Patch, cores) for c := 0; c < cores; c++ { ret[c] = make(sointu.Patch, 0, len(patch)) } - var voicemapping [MAX_CORES][MAX_VOICES]int - for c := 0; c < MAX_CORES; c++ { + var voicemapping [MAX_THREADS][MAX_VOICES]int + for c := 0; c < MAX_THREADS; c++ { for j := 0; j < MAX_VOICES; j++ { voicemapping[c][j] = -1 } @@ -204,7 +199,7 @@ func splitPatchByCores(patch sointu.Patch) ([]sointu.Patch, voiceMapping) { coreVoice := 0 curVoice := 0 for _, instr := range patch { - mask := instr.CoreMaskM1 + 1 + mask := instr.ThreadMaskM1 + 1 if mask&(1<