This commit is contained in:
5684185+vsariola@users.noreply.github.com
2025-10-24 23:55:04 +03:00
parent fa7901c7c6
commit f92ecb2e99
12 changed files with 152 additions and 134 deletions

View File

@ -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<<bit) != 0
}
func (m *Model) setCoresBit(bit int, value bool) {
func (m *Model) setThreadsBit(bit int, value bool) {
if m.d.InstrIndex < 0 || m.d.InstrIndex >= 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

View File

@ -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),

View File

@ -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) }),
)
},

View File

@ -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 }

View File

@ -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