diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml index d104255..7c46552 100644 --- a/.github/workflows/binaries.yml +++ b/.github/workflows/binaries.yml @@ -39,33 +39,19 @@ jobs: strategy: matrix: config: - - os: windows-latest - asmnasm: C:\Users\runneradmin\nasm\nasm - output: sointu-track.exe - params: cmd/sointu-track/main.go - ldflags: -H=windowsgui - os: windows-latest asmnasm: C:\Users\runneradmin\nasm\nasm output: sointu-compile.exe params: cmd/sointu-compile/main.go - os: windows-latest asmnasm: C:\Users\runneradmin\nasm\nasm - output: sointu-track-native.exe + output: sointu-track.exe params: -tags=native cmd/sointu-track/main.go ldflags: -H=windowsgui - os: windows-latest asmnasm: C:\Users\runneradmin\nasm\nasm output: sointu-vsti.dll - params: -buildmode=c-shared -tags=plugin ./cmd/sointu-vsti/ - - os: windows-latest - asmnasm: C:\Users\runneradmin\nasm\nasm - output: sointu-vsti-native.dll params: -buildmode=c-shared -tags="plugin,native" ./cmd/sointu-vsti/ - - os: ubuntu-latest - asmnasm: /home/runner/nasm/nasm - output: sointu-track - params: cmd/sointu-track/main.go - packages: libegl-dev libvulkan-dev libxkbcommon-x11-dev libwayland-dev libasound2-dev libx11-xcb-dev libxcursor-dev libxfixes-dev - os: ubuntu-latest asmnasm: /home/runner/nasm/nasm output: sointu-compile @@ -73,43 +59,27 @@ jobs: packages: libegl-dev libvulkan-dev libxkbcommon-x11-dev libwayland-dev libasound2-dev libx11-xcb-dev libxcursor-dev libxfixes-dev - os: ubuntu-latest asmnasm: /home/runner/nasm/nasm - output: sointu-track-native + output: sointu-track params: -tags=native cmd/sointu-track/main.go packages: libegl-dev libvulkan-dev libxkbcommon-x11-dev libwayland-dev libasound2-dev libx11-xcb-dev libxcursor-dev libxfixes-dev - os: ubuntu-latest asmnasm: /home/runner/nasm/nasm output: sointu-vsti.so - params: -buildmode=c-shared -tags=plugin ./cmd/sointu-vsti/ - packages: libegl-dev libvulkan-dev libxkbcommon-x11-dev libwayland-dev libasound2-dev libx11-xcb-dev libxcursor-dev libxfixes-dev - - os: ubuntu-latest - asmnasm: /home/runner/nasm/nasm - output: sointu-vsti-native.so params: -buildmode=c-shared -tags="plugin,native" ./cmd/sointu-vsti/ packages: libegl-dev libvulkan-dev libxkbcommon-x11-dev libwayland-dev libasound2-dev libx11-xcb-dev libxcursor-dev libxfixes-dev - - os: macos-latest - asmnasm: /Users/runner/nasm/nasm - output: sointu-track - params: cmd/sointu-track/main.go - os: macos-latest asmnasm: /Users/runner/nasm/nasm output: sointu-compile params: cmd/sointu-compile/main.go - os: macos-13 asmnasm: /Users/runner/nasm/nasm - output: sointu-track-native + output: sointu-track params: -tags=native cmd/sointu-track/main.go - os: macos-13 asmnasm: /Users/runner/nasm/nasm output: sointu-vsti.a bundleoutput: sointu-vsti MACOSX_DEPLOYMENT_TARGET: 11 - params: -buildmode=c-archive -tags=plugin ./cmd/sointu-vsti/ - bundle: true - - os: macos-13 - asmnasm: /Users/runner/nasm/nasm - output: sointu-vsti-native.a - bundleoutput: sointu-vsti-native - MACOSX_DEPLOYMENT_TARGET: 11 params: -buildmode=c-archive -tags="plugin,native" ./cmd/sointu-vsti/ bundle: true steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d071d9..c7dc149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ([#156][i156]) ### Changed +- Native version of the tracker/VSTi was removed. Instead, you can change + between the two versions of the synth on the fly, by clicking on the "Synth" + option under the CPU group in the song panel ([#200][i200]) - Send amount defaults to 64 = 0.0 ([#178][i178]) - BREAKING CHANGE: the negbandpass and neghighpass parameters of the filter unit were removed. Setting bandpass or highpass to -1 achieves now the same end @@ -360,3 +363,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). [i186]: https://github.com/vsariola/sointu/issues/186 [i192]: https://github.com/vsariola/sointu/issues/192 [i196]: https://github.com/vsariola/sointu/issues/196 +[i200]: https://github.com/vsariola/sointu/issues/200 diff --git a/README.md b/README.md index dccf28a..61ea367 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ for the audio, so the portability is currently limited by these. #### Prerequisites - [go](https://golang.org/) -- If you want to use the x86 assembly written synthesizer, to test that the +- If you want to also use the x86 assembly written synthesizer, to test that the patch also works once compiled: - Follow the instructions to build the [x86 native virtual machine](#native-virtual-machine) before building the tracker. @@ -110,7 +110,7 @@ go build -o sointu-track.exe cmd/sointu-track/main.go On other platforms than Windows, replace `-o sointu-track.exe` with `-o sointu-track`. -If you want to use the [x86 native virtual machine](#native-virtual-machine), +If you want to include the [x86 native virtual machine](#native-virtual-machine), add `-tags=native` to all the commands e.g. ``` @@ -133,7 +133,7 @@ a dynamically linked library and ran inside a VST host. if it is not set and go fails to find the compiler, go just excludes all files with `import "C"` from the build, resulting in lots of errors about missing types. -- If you want to use the x86 assembly written synthesizer: +- If you want to build the VSTI with the native x86 assembly written synthesizer: - Follow the instructions to build the [x86 native virtual machine](#native-virtual-machine) before building the plugin itself @@ -218,7 +218,9 @@ machine, through cgo, instead of using the Go written bytecode interpreter. With the latest Go compiler, the native virtual machine is actually slower than the Go-written one, but importantly, the native virtual machine allows you to test that the patch also works within the stack limits of the x87 virtual machine, -which is the VM used in the compiled intros. +which is the VM used in the compiled intros. In the tracker/VSTi, you can switch +between the native synth and the Go synth under the CPU panel in the Song +settings. Before you can actually run it, you need to build the bridge using CMake (thus, ***this will not work with go get***). diff --git a/audio.go b/audio.go index dcc34ed..4c7f347 100644 --- a/audio.go +++ b/audio.go @@ -67,6 +67,7 @@ type ( // Synther compiles a given Patch into a Synth, throwing errors if the // Patch is malformed. Synther interface { + Name() string // Name of the synther, e.g. "Go" or "Native" Synth(patch Patch, bpm int) (Synth, error) } ) diff --git a/cmd/main_synther_not_native.go b/cmd/main_synther_not_native.go deleted file mode 100644 index f4a1c65..0000000 --- a/cmd/main_synther_not_native.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !native - -package cmd - -import "github.com/vsariola/sointu/vm" - -var MainSynther = vm.GoSynther{} diff --git a/cmd/sointu-play/main.go b/cmd/sointu-play/main.go index 52359e3..93d62f6 100644 --- a/cmd/sointu-play/main.go +++ b/cmd/sointu-play/main.go @@ -4,12 +4,13 @@ import ( "encoding/json" "flag" "fmt" - "github.com/vsariola/sointu/cmd" "io/ioutil" "os" "path/filepath" "strings" + "github.com/vsariola/sointu/cmd" + "gopkg.in/yaml.v3" "github.com/vsariola/sointu" @@ -29,12 +30,20 @@ func main() { wavOut := flag.Bool("w", false, "Output the rendered song as .wav file. By default, saves stereo float32 buffer to disk.") pcm := flag.Bool("c", false, "Convert audio to 16-bit signed PCM when outputting.") versionFlag := flag.Bool("v", false, "Print version.") + syntherInt := flag.Int("synth", 0, "Select the synther to use. By default, uses the first one in the list of available synthers.") flag.Usage = printUsage flag.Parse() if *versionFlag { fmt.Println(version.VersionOrHash) os.Exit(0) } + if *syntherInt < 0 || *syntherInt >= len(cmd.Synthers) { + fmt.Fprintf(os.Stderr, "synth index %d is out of range; available synthers:\n", *syntherInt) + for i, s := range cmd.Synthers { + fmt.Fprintf(os.Stderr, " %d: %s\n", i, s.Name()) + } + os.Exit(1) + } if flag.NArg() == 0 || *help { flag.Usage() os.Exit(0) @@ -93,7 +102,7 @@ func main() { return fmt.Errorf("the song could not be parsed as .json (%v) or .yml (%v)", errJSON, errYaml) } } - buffer, err := sointu.Play(cmd.MainSynther, song, nil) // render the song to calculate its length + buffer, err := sointu.Play(cmd.Synthers[*syntherInt], song, nil) // render the song to calculate its length if err != nil { return fmt.Errorf("sointu.Play failed: %v", err) } diff --git a/cmd/sointu-track/main.go b/cmd/sointu-track/main.go index 9a331ba..47cc635 100644 --- a/cmd/sointu-track/main.go +++ b/cmd/sointu-track/main.go @@ -49,8 +49,8 @@ func main() { midiContext := cmd.NewMidiContext(broker) defer midiContext.Close() midiContext.TryToOpenBy(*defaultMidiInput, *firstMidiInput) - model := tracker.NewModel(broker, cmd.MainSynther, midiContext, recoveryFile) - player := tracker.NewPlayer(broker, cmd.MainSynther) + model := tracker.NewModel(broker, cmd.Synthers, midiContext, recoveryFile) + player := tracker.NewPlayer(broker, cmd.Synthers[0]) detector := tracker.NewDetector(broker) go detector.Run() diff --git a/cmd/sointu-vsti/main.go b/cmd/sointu-vsti/main.go index b2f20f6..28a3e3b 100644 --- a/cmd/sointu-vsti/main.go +++ b/cmd/sointu-vsti/main.go @@ -46,8 +46,8 @@ func init() { recoveryFile = filepath.Join(configDir, "sointu", "sointu-vsti-recovery-"+hex.EncodeToString(randBytes)) } broker := tracker.NewBroker() - model := tracker.NewModel(broker, cmd.MainSynther, cmd.NewMidiContext(broker), recoveryFile) - player := tracker.NewPlayer(broker, cmd.MainSynther) + model := tracker.NewModel(broker, cmd.Synthers, cmd.NewMidiContext(broker), recoveryFile) + player := tracker.NewPlayer(broker, cmd.Synthers[0]) detector := tracker.NewDetector(broker) go detector.Run() @@ -63,11 +63,11 @@ func init() { buf := make(sointu.AudioBuffer, 1024) var totalFrames int64 = 0 return vst2.Plugin{ - UniqueID: PLUGIN_ID, + UniqueID: [4]byte{'S', 'n', 't', 'u'}, Version: version, InputChannels: 0, OutputChannels: 2, - Name: PLUGIN_NAME, + Name: "Sointu", Vendor: "vsariola/sointu", Category: vst2.PluginCategorySynth, Flags: vst2.PluginIsSynth, diff --git a/cmd/sointu-vsti/nameid_native.go b/cmd/sointu-vsti/nameid_native.go deleted file mode 100644 index a42772a..0000000 --- a/cmd/sointu-vsti/nameid_native.go +++ /dev/null @@ -1,6 +0,0 @@ -//go:build native - -package main - -var PLUGIN_ID = [4]byte{'S', 'n', 't', 'N'} -var PLUGIN_NAME = "Sointu Native" diff --git a/cmd/sointu-vsti/nameid_not_native.go b/cmd/sointu-vsti/nameid_not_native.go deleted file mode 100644 index 6a89fc3..0000000 --- a/cmd/sointu-vsti/nameid_not_native.go +++ /dev/null @@ -1,6 +0,0 @@ -//go:build !native - -package main - -var PLUGIN_ID = [4]byte{'S', 'n', 't', 'u'} -var PLUGIN_NAME = "Sointu" diff --git a/cmd/synthers.go b/cmd/synthers.go new file mode 100644 index 0000000..1022c1e --- /dev/null +++ b/cmd/synthers.go @@ -0,0 +1,10 @@ +package cmd + +import ( + "github.com/vsariola/sointu" + "github.com/vsariola/sointu/vm" +) + +var Synthers = []sointu.Synther{ + vm.GoSynther{}, +} diff --git a/cmd/main_synther_native.go b/cmd/synthers_native.go similarity index 56% rename from cmd/main_synther_native.go rename to cmd/synthers_native.go index 10f07e0..144ff87 100644 --- a/cmd/main_synther_native.go +++ b/cmd/synthers_native.go @@ -4,4 +4,6 @@ package cmd import "github.com/vsariola/sointu/vm/compiler/bridge" -var MainSynther = bridge.NativeSynther{} +func init() { + Synthers = append(Synthers, bridge.NativeSynther{}) +} diff --git a/tracker/files.go b/tracker/files.go index 61f5754..819053b 100644 --- a/tracker/files.go +++ b/tracker/files.go @@ -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 diff --git a/tracker/gioui/song_panel.go b/tracker/gioui/song_panel.go index 7b04d5d..fefa3bc 100644 --- a/tracker/gioui/song_panel.go +++ b/tracker/gioui/song_panel.go @@ -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 { diff --git a/tracker/gioui/tracker.go b/tracker/gioui/tracker.go index 257dc73..6bce0f5 100644 --- a/tracker/gioui/tracker.go +++ b/tracker/gioui/tracker.go @@ -113,7 +113,6 @@ func NewTracker(model *tracker.Model) *Tracker { Duration: 10 * time.Second, }) } - t.TrackEditor.scrollTable.Focus() return t } diff --git a/tracker/int.go b/tracker/int.go index 0c1459a..7fada4a 100644 --- a/tracker/int.go +++ b/tracker/int.go @@ -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 +} diff --git a/tracker/model.go b/tracker/model.go index f012ff5..973fb47 100644 --- a/tracker/model.go +++ b/tracker/model.go @@ -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 diff --git a/tracker/model_test.go b/tracker/model_test.go index e639814..ab8de5e 100644 --- a/tracker/model_test.go +++ b/tracker/model_test.go @@ -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() { diff --git a/tracker/player.go b/tracker/player.go index c759f56..2adbcb8 100644 --- a/tracker/player.go +++ b/tracker/player.go @@ -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 } diff --git a/vm/compiler/bridge/native_synth.go b/vm/compiler/bridge/native_synth.go index 0eea423..4c3c087 100644 --- a/vm/compiler/bridge/native_synth.go +++ b/vm/compiler/bridge/native_synth.go @@ -18,6 +18,8 @@ type NativeSynther struct { type NativeSynth C.Synth +func (s NativeSynther) Name() string { return "Native" } + func (s NativeSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) { synth, err := Synth(patch, bpm) return synth, err diff --git a/vm/go_synth.go b/vm/go_synth.go index e288ef2..9d0dbfc 100644 --- a/vm/go_synth.go +++ b/vm/go_synth.go @@ -93,6 +93,8 @@ success: f.Read(su_sample_table[:]) } +func (s GoSynther) Name() string { return "Go" } + func (s GoSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) { bytecode, err := NewBytecode(patch, AllFeatures{}, bpm) if err != nil {