This commit is contained in:
5684185+vsariola@users.noreply.github.com
2025-10-23 14:53:08 +03:00
parent 7f03664870
commit fa7901c7c6
9 changed files with 184 additions and 62 deletions

View File

@ -1,5 +1,7 @@
package tracker
import "fmt"
type (
Bool struct {
value BoolValue
@ -76,7 +78,8 @@ func (m *Model) getCoresBit(bit int) bool {
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
return false
}
return m.d.Song.Patch[m.d.InstrIndex].CoreBitMask&(1<<bit) != 0
mask := m.d.Song.Patch[m.d.InstrIndex].CoreMaskM1 + 1
return mask&(1<<bit) != 0
}
func (m *Model) setCoresBit(bit int, value bool) {
@ -84,10 +87,34 @@ func (m *Model) setCoresBit(bit int, value bool) {
return
}
defer (*Model)(m).change("CoreBitMask", PatchChange, MinorChange)()
mask := m.d.Song.Patch[m.d.InstrIndex].CoreMaskM1 + 1
if value {
m.d.Song.Patch[m.d.InstrIndex].CoreBitMask |= (1 << bit)
mask |= (1 << bit)
} else {
m.d.Song.Patch[m.d.InstrIndex].CoreBitMask &^= (1 << bit)
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()
}
func (m *Model) warnAboutCrossCoreSends() {
for i, instr := range m.d.Song.Patch {
for _, unit := range instr.Units {
if unit.Type == "send" {
targetID, ok := unit.Parameters["target"]
if !ok {
continue
}
it, _, err := m.d.Song.Patch.FindUnit(targetID)
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)
return
}
}
}
}
}

View File

@ -5,14 +5,17 @@ import (
"image"
"image/color"
"strconv"
"strings"
"gioui.org/gesture"
"gioui.org/layout"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"github.com/vsariola/sointu"
"github.com/vsariola/sointu/tracker"
"github.com/vsariola/sointu/version"
"github.com/vsariola/sointu/vm"
"golang.org/x/exp/shiny/materialdesign/icons"
)
@ -110,9 +113,24 @@ func (t *SongPanel) layoutSongOptions(gtx C) D {
}
oversamplingBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.OversamplingBtn, oversamplingTxt, "")
cpuload := tr.Model.CPULoad()
cpuLabel := Label(tr.Theme, &tr.Theme.SongPanel.RowValue, fmt.Sprintf("%.0f %%", cpuload*100))
if cpuload >= 1 {
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
}
}
cpuLabel := Label(tr.Theme, &tr.Theme.SongPanel.RowValue, sb.String())
if high {
cpuLabel.Color = tr.Theme.SongPanel.ErrorColor
}

View File

@ -397,7 +397,8 @@ func (m *Model) ProcessMsg(msg MsgToModel) {
}
}
func (m *Model) CPULoad() float64 { return m.playerStatus.CPULoad }
func (m *Model) NumCores() int { return m.playerStatus.NumCores }
func (m *Model) CPULoad(buf []sointu.CPULoad) { copy(buf, m.playerStatus.CPULoad[:]) }
func (m *Model) SignalAnalyzer() *ScopeModel { return m.signalAnalyzer }
func (m *Model) Broker() *Broker { return m.broker }

View File

@ -5,7 +5,6 @@ import (
"fmt"
"math"
"slices"
"time"
"github.com/vsariola/sointu"
"github.com/vsariola/sointu/vm"
@ -42,7 +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
CPULoad float64 // current CPU load of the player, used to adjust the render rate
NumCores int
CPULoad [vm.MAX_CORES]sointu.CPULoad // current CPU load of the player, used to adjust the render rate
}
// PlayerProcessContext is the context given to the player when processing
@ -97,9 +97,6 @@ func NewPlayer(broker *Broker, synther sointu.Synther) *Player {
// buffer. It is used to trigger and release notes during processing. The
// context is also used to get the current BPM from the host.
func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext) {
startTime := time.Now()
startFrame := p.frame
p.processMessages(context)
p.events.adjustTimes(p.frameDeltas, p.frame, p.frame+int64(len(buffer)))
@ -164,7 +161,10 @@ func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext
}
// when the buffer is full, return
if len(buffer) == 0 {
p.updateCPULoad(time.Since(startTime), p.frame-startFrame)
if p.synth != nil {
p.status.NumCores = p.synth.NumCores()
p.synth.CPULoad(p.status.CPULoad[:])
}
p.send(nil)
return
}
@ -448,14 +448,3 @@ func (p *Player) processNoteEvent(ev NoteEvent) {
p.synth.Trigger(oldestVoice, ev.Note)
TrySend(p.broker.ToModel, MsgToModel{TriggerChannel: instrIndex + 1})
}
func (p *Player) updateCPULoad(duration time.Duration, frames int64) {
if frames <= 0 {
return // no frames rendered, so cannot compute CPU load
}
realtime := float64(duration) / 1e9
songtime := float64(frames) / 44100
newload := realtime / songtime
alpha := math.Exp(-songtime) // smoothing factor, time constant of 1 second
p.status.CPULoad = float64(p.status.CPULoad)*alpha + newload*(1-alpha)
}