feat!: both native & Go synths are included in the same executables

Closes #200
This commit is contained in:
5684185+vsariola@users.noreply.github.com
2025-07-10 17:46:00 +03:00
parent 13102aa7d6
commit 3163f46447
21 changed files with 106 additions and 83 deletions

View File

@ -82,7 +82,7 @@ func (m *Model) WriteWav(w io.WriteCloser, pcm16 bool) {
b := make([]byte, 32+2)
rand.Read(b)
name := fmt.Sprintf("%x", b)[2 : 32+2]
data, err := sointu.Play(m.synther, song, func(p float32) {
data, err := sointu.Play(m.synthers[m.syntherIndex], song, func(p float32) {
txt := fmt.Sprintf("Exporting song: %.0f%%", p*100)
TrySend(m.broker.ToModel, MsgToModel{Data: Alert{Message: txt, Priority: Info, Name: name, Duration: defaultAlertDuration}})
}) // render the song to calculate its length

View File

@ -21,9 +21,11 @@ type SongPanel struct {
ScopeExpander *Expander
LoudnessExpander *Expander
PeakExpander *Expander
CPUExpander *Expander
WeightingTypeBtn *Clickable
OversamplingBtn *Clickable
SynthBtn *Clickable
BPM *NumericUpDownState
RowsPerPattern *NumericUpDownState
@ -50,11 +52,13 @@ func NewSongPanel(tr *Tracker) *SongPanel {
WeightingTypeBtn: new(Clickable),
OversamplingBtn: new(Clickable),
SynthBtn: new(Clickable),
SongSettingsExpander: &Expander{Expanded: true},
ScopeExpander: &Expander{},
LoudnessExpander: &Expander{},
PeakExpander: &Expander{},
CPUExpander: &Expander{},
}
return ret
}
@ -66,6 +70,10 @@ func (s *SongPanel) Update(gtx C, t *Tracker) {
for s.OversamplingBtn.Clicked(gtx) {
t.Model.Oversampling().SetValue(!t.Oversampling().Value())
}
for s.SynthBtn.Clicked(gtx) {
r := t.Model.SyntherIndex().Range()
t.Model.SyntherIndex().SetValue((t.SyntherIndex().Value()+1)%(r.Max-r.Min+1) + r.Min)
}
}
func (s *SongPanel) Layout(gtx C) D {
@ -102,6 +110,14 @@ 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 {
cpuLabel.Color = tr.Theme.SongPanel.ErrorColor
}
synthBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.SynthBtn, tr.Model.SyntherName(), "")
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return t.SongSettingsExpander.Layout(gtx, tr.Theme, "Song",
@ -130,17 +146,19 @@ func (t *SongPanel) layoutSongOptions(gtx C) D {
step := NumUpDown(tr.Step(), tr.Theme, t.Step, "Cursor step")
return layoutSongOptionRow(gtx, tr.Theme, "Cursor step", step.Layout)
}),
layout.Rigid(func(gtx C) D {
cpuload := tr.Model.CPULoad()
label := Label(tr.Theme, &tr.Theme.SongPanel.RowValue, fmt.Sprintf("%.0f %%", cpuload*100))
if cpuload >= 1 {
label.Color = tr.Theme.SongPanel.ErrorColor
}
return layoutSongOptionRow(gtx, tr.Theme, "CPU load", label.Layout)
}),
)
})
}),
layout.Rigid(func(gtx C) D {
return t.CPUExpander.Layout(gtx, tr.Theme, "CPU", cpuLabel.Layout,
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, "Synth", synthBtn.Layout) }),
)
},
)
}),
layout.Rigid(func(gtx C) D {
return t.LoudnessExpander.Layout(gtx, tr.Theme, "Loudness",
func(gtx C) D {

View File

@ -113,7 +113,6 @@ func NewTracker(model *tracker.Model) *Tracker {
Duration: 10 * time.Second,
})
}
t.TrackEditor.scrollTable.Focus()
return t
}

View File

@ -33,6 +33,7 @@ type (
Step Model
Octave Model
DetectorWeighting Model
SyntherIndex Model
)
func MakeInt(value IntValue) Int {
@ -81,6 +82,7 @@ func (m *Model) RowsPerBeat() Int { return MakeInt((*RowsPerBeat)(m)) }
func (m *Model) Step() Int { return MakeInt((*Step)(m)) }
func (m *Model) Octave() Int { return MakeInt((*Octave)(m)) }
func (m *Model) DetectorWeighting() Int { return MakeInt((*DetectorWeighting)(m)) }
func (m *Model) SyntherIndex() Int { return MakeInt((*SyntherIndex)(m)) }
// BeatsPerMinuteInt
@ -205,3 +207,17 @@ func (v *TrackVoices) Range() IntRange {
}
return IntRange{1, (*Model)(v).remainingVoices(v.linkInstrTrack, true) + v.d.Song.Score.Tracks[t].NumVoices}
}
// SyntherIndex
func (v *SyntherIndex) Value() int { return v.syntherIndex }
func (v *SyntherIndex) Range() IntRange { return IntRange{0, len(v.synthers) - 1} }
func (v *Model) SyntherName() string { return v.synthers[v.syntherIndex].Name() }
func (v *SyntherIndex) SetValue(value int) bool {
if value < 0 || value >= len(v.synthers) {
return false
}
v.syntherIndex = value
TrySend(v.broker.ToPlayer, any(v.synthers[value]))
return true
}

View File

@ -75,9 +75,11 @@ type (
weightingType WeightingType
oversampling bool
alerts []Alert
dialog Dialog
synther sointu.Synther // the synther used to create new synths
alerts []Alert
dialog Dialog
syntherIndex int // the index of the synther used to create new synths
synthers []sointu.Synther // the synther used to create new synths
broker *Broker
@ -171,9 +173,9 @@ func (m *Model) Quitted() bool { return m.quitted }
func (m *Model) DetectorResult() DetectorResult { return m.detectorResult }
// NewModelPlayer creates a new model and a player that communicates with it
func NewModel(broker *Broker, synther sointu.Synther, midiContext MIDIContext, recoveryFilePath string) *Model {
func NewModel(broker *Broker, synthers []sointu.Synther, midiContext MIDIContext, recoveryFilePath string) *Model {
m := new(Model)
m.synther = synther
m.synthers = synthers
m.MIDI = midiContext
m.broker = broker
m.d.Octave = 4

View File

@ -7,6 +7,7 @@ import (
"io"
"testing"
"github.com/vsariola/sointu"
"github.com/vsariola/sointu/tracker"
"github.com/vsariola/sointu/vm"
)
@ -251,10 +252,10 @@ func FuzzModel(f *testing.F) {
f.Add(seed)
f.Fuzz(func(t *testing.T, slice []byte) {
reader := bytes.NewReader(slice)
synther := vm.GoSynther{}
synthers := []sointu.Synther{vm.GoSynther{}}
broker := tracker.NewBroker()
model := tracker.NewModel(broker, synther, tracker.NullMIDIContext{}, "")
player := tracker.NewPlayer(broker, synther)
model := tracker.NewModel(broker, synthers, tracker.NullMIDIContext{}, "")
player := tracker.NewPlayer(broker, synthers[0])
buf := make([][2]float32, 2048)
closeChan := make(chan struct{})
go func() {

View File

@ -268,6 +268,10 @@ loop:
}
p.recording = Recording{} // reset recording
}
case sointu.Synther:
p.synther = m
p.synth = nil
p.compileOrUpdateSynth()
default:
// ignore unknown messages
}