mirror of
https://github.com/vsariola/sointu.git
synced 2026-02-19 14:43:29 -05:00
drafting
This commit is contained in:
parent
fa7901c7c6
commit
f92ecb2e99
10
audio.go
10
audio.go
@ -67,11 +67,9 @@ type (
|
|||||||
// Close disposes the synth, freeing any resources. No other functions should be called after Close.
|
// Close disposes the synth, freeing any resources. No other functions should be called after Close.
|
||||||
Close()
|
Close()
|
||||||
|
|
||||||
// Returns the number of cores the synth is using
|
// Populates the given array with the current CPU load of each thread,
|
||||||
NumCores() int
|
// returning the number of threads / elements populated
|
||||||
|
CPULoad([]CPULoad) int
|
||||||
// Populates the given array with the current CPU load of each core
|
|
||||||
CPULoad([]CPULoad)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synther compiles a given Patch into a Synth, throwing errors if the
|
// Synther compiles a given Patch into a Synth, throwing errors if the
|
||||||
@ -79,7 +77,7 @@ type (
|
|||||||
Synther interface {
|
Synther interface {
|
||||||
Name() string // Name of the synther, e.g. "Go" or "Native"
|
Name() string // Name of the synther, e.g. "Go" or "Native"
|
||||||
Synth(patch Patch, bpm int) (Synth, error)
|
Synth(patch Patch, bpm int) (Synth, error)
|
||||||
SupportsParallelism() bool
|
SupportsMultithreading() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
CPULoad float32
|
CPULoad float32
|
||||||
|
|||||||
@ -7,5 +7,5 @@ import (
|
|||||||
|
|
||||||
var Synthers = []sointu.Synther{
|
var Synthers = []sointu.Synther{
|
||||||
vm.GoSynther{},
|
vm.GoSynther{},
|
||||||
vm.MakeParallelSynther(vm.GoSynther{}),
|
vm.MakeMultithreadSynther(vm.GoSynther{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,5 +9,5 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Synthers = append(Synthers, bridge.NativeSynther{})
|
Synthers = append(Synthers, bridge.NativeSynther{})
|
||||||
Synthers = append(Synthers, vm.MakeParallelSynther(bridge.NativeSynther{}))
|
Synthers = append(Synthers, vm.MakeMultithreadSynther(bridge.NativeSynther{}))
|
||||||
}
|
}
|
||||||
|
|||||||
12
patch.go
12
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 includes a list of units consisting of the instrument, and the number of polyphonic voices for this instrument
|
||||||
Instrument struct {
|
Instrument struct {
|
||||||
Name string `yaml:",omitempty"`
|
Name string `yaml:",omitempty"`
|
||||||
Comment string `yaml:",omitempty"`
|
Comment string `yaml:",omitempty"`
|
||||||
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
|
||||||
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.
|
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
|
// Unit is e.g. a filter, oscillator, envelope and its parameters
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package tracker
|
package tracker
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Bool struct {
|
Bool struct {
|
||||||
@ -31,10 +33,10 @@ type (
|
|||||||
InstrEditor Model
|
InstrEditor Model
|
||||||
InstrPresets Model
|
InstrPresets Model
|
||||||
InstrComment Model
|
InstrComment Model
|
||||||
Core1 Model
|
Thread1 Model
|
||||||
Core2 Model
|
Thread2 Model
|
||||||
Core3 Model
|
Thread3 Model
|
||||||
Core4 Model
|
Thread4 Model
|
||||||
)
|
)
|
||||||
|
|
||||||
func MakeBool(valueEnabler interface {
|
func MakeBool(valueEnabler interface {
|
||||||
@ -72,32 +74,33 @@ func (v Bool) Enabled() bool {
|
|||||||
return v.enabler.Enabled()
|
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) {
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
||||||
return false
|
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
|
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) {
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer (*Model)(m).change("CoreBitMask", PatchChange, MinorChange)()
|
defer (*Model)(m).change("ThreadBitMask", PatchChange, MinorChange)()
|
||||||
mask := m.d.Song.Patch[m.d.InstrIndex].CoreMaskM1 + 1
|
mask := m.d.Song.Patch[m.d.InstrIndex].ThreadMaskM1 + 1
|
||||||
if value {
|
if value {
|
||||||
mask |= (1 << bit)
|
mask |= (1 << bit)
|
||||||
} else {
|
} else {
|
||||||
mask &^= (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.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.warnAboutCrossCoreSends()
|
m.warnAboutCrossThreadSends()
|
||||||
|
m.warnNoMultithreadSupport()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) warnAboutCrossCoreSends() {
|
func (m *Model) warnAboutCrossThreadSends() {
|
||||||
for i, instr := range m.d.Song.Patch {
|
for i, instr := range m.d.Song.Patch {
|
||||||
for _, unit := range instr.Units {
|
for _, unit := range instr.Units {
|
||||||
if unit.Type == "send" {
|
if unit.Type == "send" {
|
||||||
@ -109,8 +112,8 @@ func (m *Model) warnAboutCrossCoreSends() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if instr.CoreMaskM1 != m.d.Song.Patch[it].CoreMaskM1 {
|
if instr.ThreadMaskM1 != m.d.Song.Patch[it].ThreadMaskM1 {
|
||||||
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)
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,21 +121,30 @@ func (m *Model) warnAboutCrossCoreSends() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) Core1() Bool { return MakeEnabledBool((*Core1)(m)) }
|
func (m *Model) warnNoMultithreadSupport() {
|
||||||
func (m *Core1) Value() bool { return (*Model)(m).getCoresBit(0) }
|
for _, instr := range m.d.Song.Patch {
|
||||||
func (m *Core1) SetValue(val bool) { (*Model)(m).setCoresBit(0, val) }
|
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 *Model) Thread1() Bool { return MakeEnabledBool((*Thread1)(m)) }
|
||||||
func (m *Core2) Value() bool { return (*Model)(m).getCoresBit(1) }
|
func (m *Thread1) Value() bool { return (*Model)(m).getThreadsBit(0) }
|
||||||
func (m *Core2) SetValue(val bool) { (*Model)(m).setCoresBit(1, val) }
|
func (m *Thread1) SetValue(val bool) { (*Model)(m).setThreadsBit(0, val) }
|
||||||
|
|
||||||
func (m *Model) Core3() Bool { return MakeEnabledBool((*Core3)(m)) }
|
func (m *Model) Thread2() Bool { return MakeEnabledBool((*Thread2)(m)) }
|
||||||
func (m *Core3) Value() bool { return (*Model)(m).getCoresBit(2) }
|
func (m *Thread2) Value() bool { return (*Model)(m).getThreadsBit(1) }
|
||||||
func (m *Core3) SetValue(val bool) { (*Model)(m).setCoresBit(2, val) }
|
func (m *Thread2) SetValue(val bool) { (*Model)(m).setThreadsBit(1, val) }
|
||||||
|
|
||||||
func (m *Model) Core4() Bool { return MakeEnabledBool((*Core4)(m)) }
|
func (m *Model) Thread3() Bool { return MakeEnabledBool((*Thread3)(m)) }
|
||||||
func (m *Core4) Value() bool { return (*Model)(m).getCoresBit(3) }
|
func (m *Thread3) Value() bool { return (*Model)(m).getThreadsBit(2) }
|
||||||
func (m *Core4) SetValue(val bool) { (*Model)(m).setCoresBit(3, val) }
|
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
|
// Panic methods
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ type (
|
|||||||
list *layout.List
|
list *layout.List
|
||||||
soloBtn *Clickable
|
soloBtn *Clickable
|
||||||
muteBtn *Clickable
|
muteBtn *Clickable
|
||||||
coreBtns [4]*Clickable
|
threadBtns [4]*Clickable
|
||||||
soloHint string
|
soloHint string
|
||||||
unsoloHint string
|
unsoloHint string
|
||||||
muteHint string
|
muteHint string
|
||||||
@ -39,7 +39,7 @@ func NewInstrumentProperties() *InstrumentProperties {
|
|||||||
muteBtn: new(Clickable),
|
muteBtn: new(Clickable),
|
||||||
voices: NewNumericUpDownState(),
|
voices: NewNumericUpDownState(),
|
||||||
splitInstrumentBtn: new(Clickable),
|
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.soloHint = makeHint("Solo", " (%s)", "SoloToggle")
|
||||||
ret.unsoloHint = makeHint("Unsolo", " (%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")
|
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")
|
||||||
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")
|
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")
|
||||||
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")
|
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")
|
||||||
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")
|
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,
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
layout.Rigid(core1btn.Layout),
|
layout.Rigid(thread1btn.Layout),
|
||||||
layout.Rigid(core2btn.Layout),
|
layout.Rigid(thread2btn.Layout),
|
||||||
layout.Rigid(core3btn.Layout),
|
layout.Rigid(thread3btn.Layout),
|
||||||
layout.Rigid(core4btn.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)
|
soloBtn := ToggleIconBtn(tr.Solo(), tr.Theme, ip.soloBtn, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, ip.soloHint, ip.unsoloHint)
|
||||||
return layoutInstrumentPropertyLine(gtx, "Solo", soloBtn.Layout)
|
return layoutInstrumentPropertyLine(gtx, "Solo", soloBtn.Layout)
|
||||||
case 8:
|
case 8:
|
||||||
return layoutInstrumentPropertyLine(gtx, "Cores", corebtnline)
|
return layoutInstrumentPropertyLine(gtx, "Thread", threadbtnline)
|
||||||
case 10:
|
case 10:
|
||||||
return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx C) D {
|
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")
|
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 {
|
func layoutInstrumentPropertyLine(gtx C, text string, content layout.Widget) D {
|
||||||
tr := TrackerFromContext(gtx)
|
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)
|
label := Label(tr.Theme, &tr.Theme.InstrumentEditor.Properties.Label, text)
|
||||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
layout.Rigid(layout.Spacer{Width: 6, Height: 36}.Layout),
|
layout.Rigid(layout.Spacer{Width: 6, Height: 36}.Layout),
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -113,25 +114,37 @@ func (t *SongPanel) layoutSongOptions(gtx C) D {
|
|||||||
}
|
}
|
||||||
oversamplingBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.OversamplingBtn, oversamplingTxt, "")
|
oversamplingBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.OversamplingBtn, oversamplingTxt, "")
|
||||||
|
|
||||||
var sb strings.Builder
|
cpuSmallLabel := func(gtx C) D {
|
||||||
var loadArr [vm.MAX_CORES]sointu.CPULoad
|
var a [vm.MAX_THREADS]sointu.CPULoad
|
||||||
tr.Model.CPULoad(loadArr[:])
|
c := tr.Model.CPULoad(a[:])
|
||||||
c := min(vm.MAX_CORES, tr.Model.NumCores())
|
load := slices.Max(a[:c])
|
||||||
high := false
|
cpuLabel := Label(tr.Theme, &tr.Theme.SongPanel.RowValue, fmt.Sprintf("%d%%", int(load*100+0.5)))
|
||||||
for i := range c {
|
if load >= 1 {
|
||||||
if i > 0 {
|
cpuLabel.Color = tr.Theme.SongPanel.ErrorColor
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
return cpuLabel.Layout(gtx)
|
||||||
}
|
}
|
||||||
cpuLabel := Label(tr.Theme, &tr.Theme.SongPanel.RowValue, sb.String())
|
|
||||||
if high {
|
cpuEnlargedWidget := func(gtx C) D {
|
||||||
cpuLabel.Color = tr.Theme.SongPanel.ErrorColor
|
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(), "")
|
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 {
|
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 {
|
func(gtx C) D {
|
||||||
return layout.Flex{Axis: layout.Vertical, Alignment: layout.End}.Layout(gtx,
|
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) }),
|
layout.Rigid(func(gtx C) D { return layoutSongOptionRow(gtx, tr.Theme, "Synth", synthBtn.Layout) }),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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) int {
|
||||||
func (m *Model) CPULoad(buf []sointu.CPULoad) { copy(buf, m.playerStatus.CPULoad[:]) }
|
return copy(buf, m.playerStatus.CPULoad[:m.playerStatus.NumThreads])
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Model) SignalAnalyzer() *ScopeModel { return m.signalAnalyzer }
|
func (m *Model) SignalAnalyzer() *ScopeModel { return m.signalAnalyzer }
|
||||||
func (m *Model) Broker() *Broker { return m.broker }
|
func (m *Model) Broker() *Broker { return m.broker }
|
||||||
|
|||||||
@ -41,8 +41,8 @@ type (
|
|||||||
PlayerStatus struct {
|
PlayerStatus struct {
|
||||||
SongPos sointu.SongPos // the current position in the score
|
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
|
VoiceLevels [vm.MAX_VOICES]float32 // a level that can be used to visualize the volume of each voice
|
||||||
NumCores int
|
NumThreads int
|
||||||
CPULoad [vm.MAX_CORES]sointu.CPULoad // current CPU load of the player, used to adjust the render rate
|
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
|
// 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
|
// when the buffer is full, return
|
||||||
if len(buffer) == 0 {
|
if len(buffer) == 0 {
|
||||||
if p.synth != nil {
|
if p.synth != nil {
|
||||||
p.status.NumCores = p.synth.NumCores()
|
p.status.NumThreads = p.synth.CPULoad(p.status.CPULoad[:])
|
||||||
p.synth.CPULoad(p.status.CPULoad[:])
|
|
||||||
}
|
}
|
||||||
p.send(nil)
|
p.send(nil)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -22,8 +22,8 @@ type NativeSynth struct {
|
|||||||
cpuLoad sointu.CPULoad
|
cpuLoad sointu.CPULoad
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s NativeSynther) Name() string { return "Native" }
|
func (s NativeSynther) Name() string { return "Native" }
|
||||||
func (s NativeSynther) SupportsParallelism() bool { return false }
|
func (s NativeSynther) SupportsMultithreading() bool { return false }
|
||||||
|
|
||||||
func (s NativeSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
func (s NativeSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
||||||
synth, err := Synth(patch, bpm)
|
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) Close() {}
|
||||||
|
|
||||||
func (s *NativeSynth) NumCores() int { return 1 }
|
func (s *NativeSynth) CPULoad(loads []sointu.CPULoad) int {
|
||||||
func (s *NativeSynth) CPULoad(loads []sointu.CPULoad) {
|
|
||||||
if len(loads) < 1 {
|
if len(loads) < 1 {
|
||||||
return
|
return 0
|
||||||
}
|
}
|
||||||
loads[0] = s.cpuLoad
|
loads[0] = s.cpuLoad
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders until the buffer is full or the modulated time is reached, whichever
|
// Render renders until the buffer is full or the modulated time is reached, whichever
|
||||||
|
|||||||
@ -95,8 +95,8 @@ success:
|
|||||||
f.Read(su_sample_table[:])
|
f.Read(su_sample_table[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s GoSynther) Name() string { return "Go" }
|
func (s GoSynther) Name() string { return "Go" }
|
||||||
func (s GoSynther) SupportsParallelism() bool { return false }
|
func (s GoSynther) SupportsMultithreading() bool { return false }
|
||||||
|
|
||||||
func (s GoSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
func (s GoSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
||||||
bytecode, err := NewBytecode(patch, AllFeatures{}, bpm)
|
bytecode, err := NewBytecode(patch, AllFeatures{}, bpm)
|
||||||
@ -120,12 +120,12 @@ func (s *GoSynth) Release(voiceIndex int) {
|
|||||||
|
|
||||||
func (s *GoSynth) Close() {}
|
func (s *GoSynth) Close() {}
|
||||||
|
|
||||||
func (s *GoSynth) NumCores() int { return 1 }
|
func (s *GoSynth) CPULoad(loads []sointu.CPULoad) int {
|
||||||
func (s *GoSynth) CPULoad(loads []sointu.CPULoad) {
|
|
||||||
if len(loads) < 1 {
|
if len(loads) < 1 {
|
||||||
return
|
return 0
|
||||||
}
|
}
|
||||||
loads[0] = s.cpuLoad
|
loads[0] = s.cpuLoad
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GoSynth) Update(patch sointu.Patch, bpm int) error {
|
func (s *GoSynth) Update(patch sointu.Patch, bpm int) error {
|
||||||
|
|||||||
@ -10,29 +10,29 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ParallelSynth struct {
|
MultithreadSynth struct {
|
||||||
voiceMapping voiceMapping
|
voiceMapping voiceMapping
|
||||||
synths []sointu.Synth
|
synths []sointu.Synth
|
||||||
commands chan<- parallelSynthCommand // maxtime
|
commands chan<- multithreadSynthCommand // maxtime
|
||||||
results <-chan parallelSynthResult // rendered buffer
|
results <-chan multithreadSynthResult // rendered buffer
|
||||||
pool sync.Pool
|
pool sync.Pool
|
||||||
synther sointu.Synther
|
synther sointu.Synther
|
||||||
}
|
}
|
||||||
|
|
||||||
ParallelSynther struct {
|
MultithreadSynther struct {
|
||||||
synther sointu.Synther
|
synther sointu.Synther
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
voiceMapping [MAX_CORES][MAX_VOICES]int
|
voiceMapping [MAX_THREADS][MAX_VOICES]int
|
||||||
|
|
||||||
parallelSynthCommand struct {
|
multithreadSynthCommand struct {
|
||||||
core int
|
core int
|
||||||
samples int
|
samples int
|
||||||
time int
|
time int
|
||||||
}
|
}
|
||||||
|
|
||||||
parallelSynthResult struct {
|
multithreadSynthResult struct {
|
||||||
buffer *sointu.AudioBuffer
|
buffer *sointu.AudioBuffer
|
||||||
samples int
|
samples int
|
||||||
time int
|
time int
|
||||||
@ -40,16 +40,16 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const MAX_CORES = 4
|
const MAX_THREADS = 4
|
||||||
|
|
||||||
func MakeParallelSynther(synther sointu.Synther) ParallelSynther {
|
func MakeMultithreadSynther(synther sointu.Synther) MultithreadSynther {
|
||||||
return ParallelSynther{synther: synther, name: "Parallel " + synther.Name()}
|
return MultithreadSynther{synther: synther, name: "Multithread " + synther.Name()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s ParallelSynther) Name() string { return s.name }
|
func (s MultithreadSynther) Name() string { return s.name }
|
||||||
func (s ParallelSynther) SupportsParallelism() bool { return true }
|
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)
|
patches, voiceMapping := splitPatchByCores(patch)
|
||||||
synths := make([]sointu.Synth, 0, len(patches))
|
synths := make([]sointu.Synth, 0, len(patches))
|
||||||
for _, p := range 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)
|
synths = append(synths, synth)
|
||||||
}
|
}
|
||||||
ret := &ParallelSynth{
|
ret := &MultithreadSynth{
|
||||||
synths: synths,
|
synths: synths,
|
||||||
voiceMapping: voiceMapping,
|
voiceMapping: voiceMapping,
|
||||||
pool: sync.Pool{New: func() any { ret := make(sointu.AudioBuffer, 0, 8096); return &ret }},
|
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
|
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)
|
patches, voiceMapping := splitPatchByCores(patch)
|
||||||
if s.voiceMapping != voiceMapping {
|
if s.voiceMapping != voiceMapping {
|
||||||
s.voiceMapping = voiceMapping
|
s.voiceMapping = voiceMapping
|
||||||
@ -93,37 +93,37 @@ func (s *ParallelSynth) Update(patch sointu.Patch, bpm int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ParallelSynth) startProcesses() {
|
func (s *MultithreadSynth) startProcesses() {
|
||||||
maxProcs := runtime.GOMAXPROCS(0)
|
maxProcs := runtime.GOMAXPROCS(0)
|
||||||
cmdChan := make(chan parallelSynthCommand, maxProcs)
|
cmdChan := make(chan multithreadSynthCommand, maxProcs)
|
||||||
s.commands = cmdChan
|
s.commands = cmdChan
|
||||||
resultsChan := make(chan parallelSynthResult, maxProcs)
|
resultsChan := make(chan multithreadSynthResult, maxProcs)
|
||||||
s.results = resultsChan
|
s.results = resultsChan
|
||||||
for i := 0; i < maxProcs; i++ {
|
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 {
|
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.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)
|
}(cmdChan, resultsChan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ParallelSynth) Close() {
|
func (s *MultithreadSynth) Close() {
|
||||||
close(s.commands)
|
close(s.commands)
|
||||||
s.closeSynths()
|
s.closeSynths()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ParallelSynth) closeSynths() {
|
func (s *MultithreadSynth) closeSynths() {
|
||||||
for _, synth := range s.synths {
|
for _, synth := range s.synths {
|
||||||
synth.Close()
|
synth.Close()
|
||||||
}
|
}
|
||||||
s.synths = s.synths[:0]
|
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 {
|
for core, synth := range s.synths {
|
||||||
if ind := s.voiceMapping[core][voiceIndex]; ind >= 0 {
|
if ind := s.voiceMapping[core][voiceIndex]; ind >= 0 {
|
||||||
synth.Trigger(ind, note)
|
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 {
|
for core, synth := range s.synths {
|
||||||
if ind := s.voiceMapping[core][voiceIndex]; ind >= 0 {
|
if ind := s.voiceMapping[core][voiceIndex]; ind >= 0 {
|
||||||
synth.Release(ind)
|
synth.Release(ind)
|
||||||
@ -139,27 +139,22 @@ func (s *ParallelSynth) Release(voiceIndex int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ParallelSynth) NumCores() (coreCount int) {
|
func (s *MultithreadSynth) CPULoad(loads []sointu.CPULoad) (elems int) {
|
||||||
for i := range s.synths {
|
for _, synth := range s.synths {
|
||||||
coreCount += s.synths[i].NumCores()
|
n := synth.CPULoad(loads)
|
||||||
|
elems += n
|
||||||
|
loads = loads[n:]
|
||||||
|
if len(loads) <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ParallelSynth) CPULoad(loads []sointu.CPULoad) {
|
func (s *MultithreadSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, time int, renderError error) {
|
||||||
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) {
|
|
||||||
count := len(s.synths)
|
count := len(s.synths)
|
||||||
for i := 0; i < count; i++ {
|
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)
|
clear(buffer)
|
||||||
samples = math.MaxInt
|
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) {
|
func splitPatchByCores(patch sointu.Patch) ([]sointu.Patch, voiceMapping) {
|
||||||
cores := 1
|
cores := 1
|
||||||
for _, instr := range patch {
|
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)
|
ret := make([]sointu.Patch, cores)
|
||||||
for c := 0; c < cores; c++ {
|
for c := 0; c < cores; c++ {
|
||||||
ret[c] = make(sointu.Patch, 0, len(patch))
|
ret[c] = make(sointu.Patch, 0, len(patch))
|
||||||
}
|
}
|
||||||
var voicemapping [MAX_CORES][MAX_VOICES]int
|
var voicemapping [MAX_THREADS][MAX_VOICES]int
|
||||||
for c := 0; c < MAX_CORES; c++ {
|
for c := 0; c < MAX_THREADS; c++ {
|
||||||
for j := 0; j < MAX_VOICES; j++ {
|
for j := 0; j < MAX_VOICES; j++ {
|
||||||
voicemapping[c][j] = -1
|
voicemapping[c][j] = -1
|
||||||
}
|
}
|
||||||
@ -204,7 +199,7 @@ func splitPatchByCores(patch sointu.Patch) ([]sointu.Patch, voiceMapping) {
|
|||||||
coreVoice := 0
|
coreVoice := 0
|
||||||
curVoice := 0
|
curVoice := 0
|
||||||
for _, instr := range patch {
|
for _, instr := range patch {
|
||||||
mask := instr.CoreMaskM1 + 1
|
mask := instr.ThreadMaskM1 + 1
|
||||||
if mask&(1<<c) != 0 {
|
if mask&(1<<c) != 0 {
|
||||||
ret[c] = append(ret[c], instr)
|
ret[c] = append(ret[c], instr)
|
||||||
for j := 0; j < instr.NumVoices; j++ {
|
for j := 0; j < instr.NumVoices; j++ {
|
||||||
Reference in New Issue
Block a user