mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-03 09:08:18 -04:00
126 lines
3.7 KiB
Go
126 lines
3.7 KiB
Go
//go:build plugin
|
|
|
|
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/vsariola/sointu"
|
|
"github.com/vsariola/sointu/cmd"
|
|
"github.com/vsariola/sointu/tracker"
|
|
"github.com/vsariola/sointu/tracker/gioui"
|
|
"pipelined.dev/audio/vst2"
|
|
)
|
|
|
|
type VSTIProcessContext struct {
|
|
events []vst2.MIDIEvent
|
|
host vst2.Host
|
|
}
|
|
|
|
func (c *VSTIProcessContext) NextEvent() (event tracker.MIDINoteEvent, ok bool) {
|
|
var ev vst2.MIDIEvent
|
|
for len(c.events) > 0 {
|
|
ev, c.events = c.events[0], c.events[1:]
|
|
switch {
|
|
case ev.Data[0] >= 0x80 && ev.Data[0] < 0x90:
|
|
channel := ev.Data[0] - 0x80
|
|
note := ev.Data[1]
|
|
return tracker.MIDINoteEvent{Frame: int(ev.DeltaFrames), On: false, Channel: int(channel), Note: note}, true
|
|
case ev.Data[0] >= 0x90 && ev.Data[0] < 0xA0:
|
|
channel := ev.Data[0] - 0x90
|
|
note := ev.Data[1]
|
|
return tracker.MIDINoteEvent{Frame: int(ev.DeltaFrames), On: true, Channel: int(channel), Note: note}, true
|
|
default:
|
|
// ignore all other MIDI messages
|
|
}
|
|
}
|
|
return tracker.MIDINoteEvent{}, false
|
|
}
|
|
|
|
func (c *VSTIProcessContext) BPM() (bpm float64, ok bool) {
|
|
timeInfo := c.host.GetTimeInfo(vst2.TempoValid)
|
|
if timeInfo == nil || timeInfo.Flags&vst2.TempoValid == 0 || timeInfo.Tempo == 0 {
|
|
return 0, false
|
|
}
|
|
return timeInfo.Tempo, true
|
|
}
|
|
|
|
func init() {
|
|
var (
|
|
version = int32(100)
|
|
)
|
|
vst2.PluginAllocator = func(h vst2.Host) (vst2.Plugin, vst2.Dispatcher) {
|
|
modelMessages := make(chan interface{}, 1024)
|
|
playerMessages := make(chan tracker.PlayerMessage, 1024)
|
|
recoveryFile := ""
|
|
if configDir, err := os.UserConfigDir(); err == nil {
|
|
randBytes := make([]byte, 16)
|
|
rand.Read(randBytes)
|
|
recoveryFile = filepath.Join(configDir, "Sointu", "sointu-vsti-recovery-"+hex.EncodeToString(randBytes))
|
|
}
|
|
model := tracker.NewModel(modelMessages, playerMessages, recoveryFile)
|
|
player := tracker.NewPlayer(cmd.MainSynther, playerMessages, modelMessages)
|
|
tracker := gioui.NewTracker(model, cmd.MainSynther)
|
|
tracker.SetInstrEnlarged(true) // start the vsti with the instrument editor enlarged
|
|
go tracker.Main()
|
|
context := VSTIProcessContext{make([]vst2.MIDIEvent, 100), h}
|
|
buf := make(sointu.AudioBuffer, 1024)
|
|
return vst2.Plugin{
|
|
UniqueID: PLUGIN_ID,
|
|
Version: version,
|
|
InputChannels: 0,
|
|
OutputChannels: 2,
|
|
Name: PLUGIN_NAME,
|
|
Vendor: "vsariola/sointu",
|
|
Category: vst2.PluginCategorySynth,
|
|
Flags: vst2.PluginIsSynth,
|
|
ProcessFloatFunc: func(in, out vst2.FloatBuffer) {
|
|
left := out.Channel(0)
|
|
right := out.Channel(1)
|
|
if len(buf) < out.Frames {
|
|
buf = append(buf, make(sointu.AudioBuffer, out.Frames-len(buf))...)
|
|
}
|
|
buf = buf[:out.Frames]
|
|
player.Process(buf, &context)
|
|
for i := 0; i < out.Frames; i++ {
|
|
left[i], right[i] = buf[i][0], buf[i][1]
|
|
}
|
|
context.events = context.events[:0]
|
|
},
|
|
}, vst2.Dispatcher{
|
|
CanDoFunc: func(pcds vst2.PluginCanDoString) vst2.CanDoResponse {
|
|
switch pcds {
|
|
case vst2.PluginCanReceiveEvents, vst2.PluginCanReceiveMIDIEvent, vst2.PluginCanReceiveTimeInfo:
|
|
return vst2.YesCanDo
|
|
}
|
|
return vst2.NoCanDo
|
|
},
|
|
ProcessEventsFunc: func(ev *vst2.EventsPtr) {
|
|
for i := 0; i < ev.NumEvents(); i++ {
|
|
a := ev.Event(i)
|
|
switch v := a.(type) {
|
|
case *vst2.MIDIEvent:
|
|
context.events = append(context.events, *v)
|
|
}
|
|
}
|
|
},
|
|
CloseFunc: func() {
|
|
tracker.Quit(true)
|
|
tracker.WaitQuitted()
|
|
},
|
|
GetChunkFunc: func(isPreset bool) []byte {
|
|
return tracker.SafeMarshalRecovery()
|
|
},
|
|
SetChunkFunc: func(data []byte, isPreset bool) {
|
|
tracker.SafeUnmarshalRecovery(data)
|
|
},
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func main() {}
|