mirror of
https://github.com/vsariola/sointu.git
synced 2026-04-12 17:14:43 -04:00
fix(tracker): Player routes MIDImsgs so always handled in same block
This commit is contained in:
parent
cc8d737f8a
commit
77b27257fe
@ -97,19 +97,8 @@ func init() {
|
|||||||
for i := 0; i < events.NumEvents(); i++ {
|
for i := 0; i < events.NumEvents(); i++ {
|
||||||
switch ev := events.Event(i).(type) {
|
switch ev := events.Event(i).(type) {
|
||||||
case *vst2.MIDIEvent:
|
case *vst2.MIDIEvent:
|
||||||
switch {
|
if (ev.Data[0] >= 0x80 && ev.Data[0] <= 0x9F) || (ev.Data[0] >= 0xB0 && ev.Data[0] <= 0xBF) {
|
||||||
case ev.Data[0] >= 0x80 && ev.Data[0] <= 0x9F:
|
player.EmitMIDIMsg(&tracker.MIDIMessage{Timestamp: int64(ev.DeltaFrames) + totalFrames, Data: ev.Data, Source: &context})
|
||||||
channel := ev.Data[0] & 0x0F
|
|
||||||
note := ev.Data[1]
|
|
||||||
on := ev.Data[0] >= 0x90
|
|
||||||
trackerEvent := tracker.NoteEvent{Timestamp: int64(ev.DeltaFrames) + totalFrames, On: on, Channel: int(channel), Note: note, Source: &context}
|
|
||||||
tracker.TrySend(broker.ToMIDIRouter, any(&trackerEvent))
|
|
||||||
case ev.Data[0] >= 0xB0 && ev.Data[0] <= 0xBF:
|
|
||||||
channel := ev.Data[0] & 0x0F
|
|
||||||
controller := ev.Data[1]
|
|
||||||
value := ev.Data[2]
|
|
||||||
trackerEvent := tracker.ControlChange{Channel: int(channel), Control: int(controller), Value: int(value)}
|
|
||||||
tracker.TrySend(broker.ToMIDIRouter, any(&trackerEvent))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,17 +37,17 @@ type (
|
|||||||
ToDetector chan MsgToDetector
|
ToDetector chan MsgToDetector
|
||||||
ToGUI chan any
|
ToGUI chan any
|
||||||
ToSpecAn chan MsgToSpecAn
|
ToSpecAn chan MsgToSpecAn
|
||||||
ToMIDIRouter chan any
|
ToMIDIHandler chan any
|
||||||
|
|
||||||
CloseDetector chan struct{}
|
CloseDetector chan struct{}
|
||||||
CloseGUI chan struct{}
|
CloseGUI chan struct{}
|
||||||
CloseSpecAn chan struct{}
|
CloseSpecAn chan struct{}
|
||||||
CloseMIDIRouter chan struct{}
|
CloseMIDIHandler chan struct{}
|
||||||
|
|
||||||
FinishedGUI chan struct{}
|
FinishedGUI chan struct{}
|
||||||
FinishedDetector chan struct{}
|
FinishedDetector chan struct{}
|
||||||
FinishedSpecAn chan struct{}
|
FinishedSpecAn chan struct{}
|
||||||
FinishedMIDIRouter chan struct{}
|
FinishedMIDIHandler chan struct{}
|
||||||
|
|
||||||
bufferPool sync.Pool
|
bufferPool sync.Pool
|
||||||
spectrumPool sync.Pool
|
spectrumPool sync.Pool
|
||||||
@ -115,16 +115,16 @@ func NewBroker() *Broker {
|
|||||||
ToModel: make(chan MsgToModel, 1024),
|
ToModel: make(chan MsgToModel, 1024),
|
||||||
ToDetector: make(chan MsgToDetector, 1024),
|
ToDetector: make(chan MsgToDetector, 1024),
|
||||||
ToGUI: make(chan any, 1024),
|
ToGUI: make(chan any, 1024),
|
||||||
ToMIDIRouter: make(chan any, 1024),
|
ToMIDIHandler: make(chan any, 1024),
|
||||||
ToSpecAn: make(chan MsgToSpecAn, 1024),
|
ToSpecAn: make(chan MsgToSpecAn, 1024),
|
||||||
CloseDetector: make(chan struct{}, 1),
|
CloseDetector: make(chan struct{}, 1),
|
||||||
CloseGUI: make(chan struct{}, 1),
|
CloseGUI: make(chan struct{}, 1),
|
||||||
CloseSpecAn: make(chan struct{}, 1),
|
CloseSpecAn: make(chan struct{}, 1),
|
||||||
CloseMIDIRouter: make(chan struct{}, 1),
|
CloseMIDIHandler: make(chan struct{}, 1),
|
||||||
FinishedGUI: make(chan struct{}),
|
FinishedGUI: make(chan struct{}),
|
||||||
FinishedDetector: make(chan struct{}),
|
FinishedDetector: make(chan struct{}),
|
||||||
FinishedSpecAn: make(chan struct{}),
|
FinishedSpecAn: make(chan struct{}),
|
||||||
FinishedMIDIRouter: make(chan struct{}),
|
FinishedMIDIHandler: make(chan struct{}),
|
||||||
bufferPool: sync.Pool{New: func() any { return &sointu.AudioBuffer{} }},
|
bufferPool: sync.Pool{New: func() any { return &sointu.AudioBuffer{} }},
|
||||||
spectrumPool: sync.Pool{New: func() any { return &Spectrum{} }},
|
spectrumPool: sync.Pool{New: func() any { return &Spectrum{} }},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -134,16 +134,20 @@ func (te *NoteEditor) Layout(gtx layout.Context) layout.Dimensions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for gtx.Focused(te.scrollTable) && len(t.noteEvents) > 0 {
|
for gtx.Focused(te.scrollTable) && len(t.midiMsgs) > 0 {
|
||||||
ev := t.noteEvents[0]
|
ev := tracker.NoteEvent{
|
||||||
ev.IsTrack = true
|
Timestamp: t.midiMsgs[0].Timestamp,
|
||||||
ev.Channel = t.Model.Note().Cursor().X
|
Note: t.midiMsgs[0].Data[1],
|
||||||
ev.Source = te
|
On: t.midiMsgs[0].Data[0]&0xF0 != 0x80,
|
||||||
|
IsTrack: true,
|
||||||
|
Channel: t.Model.Note().Cursor().X,
|
||||||
|
Source: t.midiMsgs[0].Source,
|
||||||
|
}
|
||||||
if ev.On {
|
if ev.On {
|
||||||
t.Model.Note().Input(ev.Note)
|
t.Model.Note().Input(ev.Note)
|
||||||
}
|
}
|
||||||
copy(t.noteEvents, t.noteEvents[1:])
|
copy(t.midiMsgs, t.midiMsgs[1:])
|
||||||
t.noteEvents = t.noteEvents[:len(t.noteEvents)-1]
|
t.midiMsgs = t.midiMsgs[:len(t.midiMsgs)-1]
|
||||||
tracker.TrySend(t.Broker().ToPlayer, any(&ev))
|
tracker.TrySend(t.Broker().ToPlayer, any(&ev))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -51,7 +51,7 @@ type (
|
|||||||
SongPanel *SongPanel
|
SongPanel *SongPanel
|
||||||
|
|
||||||
filePathString tracker.String
|
filePathString tracker.String
|
||||||
noteEvents []tracker.NoteEvent
|
midiMsgs []*tracker.MIDIMessage
|
||||||
|
|
||||||
preferences Preferences
|
preferences Preferences
|
||||||
|
|
||||||
@ -145,8 +145,8 @@ func (t *Tracker) Main() {
|
|||||||
select {
|
select {
|
||||||
case e := <-t.Broker().ToGUI:
|
case e := <-t.Broker().ToGUI:
|
||||||
switch e := e.(type) {
|
switch e := e.(type) {
|
||||||
case *tracker.NoteEvent:
|
case *tracker.MIDIMessage:
|
||||||
t.noteEvents = append(t.noteEvents, *e)
|
t.midiMsgs = append(t.midiMsgs, e)
|
||||||
case tracker.MsgToGUI:
|
case tracker.MsgToGUI:
|
||||||
switch e.Kind {
|
switch e.Kind {
|
||||||
case tracker.GUIMessageCenterOnRow:
|
case tracker.GUIMessageCenterOnRow:
|
||||||
@ -267,13 +267,17 @@ func (t *Tracker) Layout(gtx layout.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if no-one else handled the note events, we handle them here
|
// if no-one else handled the note events, we handle them here
|
||||||
for len(t.noteEvents) > 0 {
|
for len(t.midiMsgs) > 0 {
|
||||||
ev := t.noteEvents[0]
|
ev := tracker.NoteEvent{
|
||||||
ev.IsTrack = false
|
Timestamp: t.midiMsgs[0].Timestamp,
|
||||||
ev.Channel = t.Model.Instrument().List().Selected()
|
Note: t.midiMsgs[0].Data[1],
|
||||||
ev.Source = t
|
On: t.midiMsgs[0].Data[0]&0xF0 != 0x80,
|
||||||
copy(t.noteEvents, t.noteEvents[1:])
|
IsTrack: false,
|
||||||
t.noteEvents = t.noteEvents[:len(t.noteEvents)-1]
|
Channel: t.Model.Instrument().List().Selected(),
|
||||||
|
Source: t.midiMsgs[0].Source,
|
||||||
|
}
|
||||||
|
copy(t.midiMsgs, t.midiMsgs[1:])
|
||||||
|
t.midiMsgs = t.midiMsgs[:len(t.midiMsgs)-1]
|
||||||
tracker.TrySend(t.Broker().ToPlayer, any(&ev))
|
tracker.TrySend(t.Broker().ToPlayer, any(&ev))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,7 +46,7 @@ func (m *RTMIDIContext) Inputs(yield func(input tracker.MIDIInputDevice) bool) {
|
|||||||
}
|
}
|
||||||
for _, in := range ins {
|
for _, in := range ins {
|
||||||
r := RTMIDIInputDevice{In: in, broker: m.broker}
|
r := RTMIDIInputDevice{In: in, broker: m.broker}
|
||||||
if !yield(r) {
|
if !yield(&r) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,27 +67,21 @@ func (c *RTMIDIContext) Support() tracker.MIDISupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Open an input device and starting the listener.
|
// Open an input device and starting the listener.
|
||||||
func (m RTMIDIInputDevice) Open() error {
|
func (m *RTMIDIInputDevice) Open(h func(msg *tracker.MIDIMessage)) error {
|
||||||
if err := m.In.Open(); err != nil {
|
if err := m.In.Open(); err != nil {
|
||||||
return fmt.Errorf("opening MIDI input failed: %w", err)
|
return fmt.Errorf("opening MIDI input failed: %w", err)
|
||||||
}
|
}
|
||||||
if _, err := midi.ListenTo(m.In, m.handleMessage); err != nil {
|
q := func(msg midi.Message, timestampms int32) {
|
||||||
|
if len(msg.Bytes()) == 0 || len(msg.Bytes()) > 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t := tracker.MIDIMessage{Timestamp: int64(timestampms) * 441 / 10, Source: m}
|
||||||
|
copy(t.Data[:], msg.Bytes())
|
||||||
|
h(&t)
|
||||||
|
}
|
||||||
|
if _, err := midi.ListenTo(m.In, q); err != nil {
|
||||||
m.In.Close()
|
m.In.Close()
|
||||||
return fmt.Errorf("listening to MIDI input failed: %w", err)
|
return fmt.Errorf("listening to MIDI input failed: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RTMIDIInputDevice) handleMessage(msg midi.Message, timestampms int32) {
|
|
||||||
var channel, key, velocity, controller, value uint8
|
|
||||||
if msg.GetNoteOn(&channel, &key, &velocity) {
|
|
||||||
ev := tracker.NoteEvent{Timestamp: int64(timestampms) * 441 / 10, On: true, Channel: int(channel), Note: key, Source: m}
|
|
||||||
tracker.TrySend(m.broker.ToMIDIRouter, any(&ev))
|
|
||||||
} else if msg.GetNoteOff(&channel, &key, &velocity) {
|
|
||||||
ev := tracker.NoteEvent{Timestamp: int64(timestampms) * 441 / 10, On: false, Channel: int(channel), Note: key, Source: m}
|
|
||||||
tracker.TrySend(m.broker.ToMIDIRouter, any(&ev))
|
|
||||||
} else if msg.GetControlChange(&channel, &controller, &value) {
|
|
||||||
ev := tracker.ControlChange{Channel: int(channel), Control: int(controller), Value: int(value)}
|
|
||||||
tracker.TrySend(m.broker.ToMIDIRouter, any(&ev))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
110
tracker/midi.go
110
tracker/midi.go
@ -11,8 +11,8 @@ func (m *Model) MIDI() *MIDIModel { return (*MIDIModel)(m) }
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
midiState struct {
|
midiState struct {
|
||||||
noteEventsToGui bool
|
|
||||||
binding bool
|
binding bool
|
||||||
|
router midiRouter
|
||||||
|
|
||||||
currentInput MIDIInputDevice
|
currentInput MIDIInputDevice
|
||||||
context MIDIContext
|
context MIDIContext
|
||||||
@ -26,7 +26,7 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
MIDIInputDevice interface {
|
MIDIInputDevice interface {
|
||||||
Open() error
|
Open(func(msg *MIDIMessage)) error
|
||||||
Close() error
|
Close() error
|
||||||
IsOpen() bool
|
IsOpen() bool
|
||||||
String() string
|
String() string
|
||||||
@ -56,7 +56,10 @@ func (m *midiRefresh) Do() {
|
|||||||
if m.midi.currentInput != nil && i.String() == m.midi.currentInput.String() {
|
if m.midi.currentInput != nil && i.String() == m.midi.currentInput.String() {
|
||||||
m.midi.currentInput.Close()
|
m.midi.currentInput.Close()
|
||||||
m.midi.currentInput = nil
|
m.midi.currentInput = nil
|
||||||
if err := i.Open(); err != nil {
|
handler := func(msg *MIDIMessage) {
|
||||||
|
TrySend(m.broker.ToMIDIHandler, any(msg))
|
||||||
|
}
|
||||||
|
if err := i.Open(handler); err != nil {
|
||||||
(*Model)(m).Alerts().Add(fmt.Sprintf("Failed to reopen MIDI input port: %s", err.Error()), Error)
|
(*Model)(m).Alerts().Add(fmt.Sprintf("Failed to reopen MIDI input port: %s", err.Error()), Error)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -96,7 +99,10 @@ func (m *midiInputDevices) SetValue(val int) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
newInput := m.midi.inputs[val-1]
|
newInput := m.midi.inputs[val-1]
|
||||||
if err := newInput.Open(); err != nil {
|
handler := func(msg *MIDIMessage) {
|
||||||
|
TrySend(m.broker.ToMIDIHandler, any(msg))
|
||||||
|
}
|
||||||
|
if err := newInput.Open(handler); err != nil {
|
||||||
(*Model)(m).Alerts().Add(fmt.Sprintf("Failed to open MIDI input port: %s", err.Error()), Error)
|
(*Model)(m).Alerts().Add(fmt.Sprintf("Failed to open MIDI input port: %s", err.Error()), Error)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -131,34 +137,82 @@ func (m *MIDIModel) InputtingNotes() Bool { return MakeBool((*midiInputtingNotes
|
|||||||
|
|
||||||
type midiInputtingNotes Model
|
type midiInputtingNotes Model
|
||||||
|
|
||||||
func (m *midiInputtingNotes) Value() bool { return m.midi.noteEventsToGui }
|
func (m *midiInputtingNotes) Value() bool { return m.midi.router.sendNoteEventsToGUI }
|
||||||
func (m *midiInputtingNotes) SetValue(val bool) {
|
func (m *midiInputtingNotes) SetValue(val bool) {
|
||||||
m.midi.noteEventsToGui = val
|
m.midi.router.sendNoteEventsToGUI = val
|
||||||
TrySend(m.broker.ToMIDIRouter, any(setNoteEventsToGUI(val)))
|
TrySend(m.broker.ToMIDIHandler, any(m.midi.router))
|
||||||
|
TrySend(m.broker.ToPlayer, any(m.midi.router))
|
||||||
}
|
}
|
||||||
|
|
||||||
type setNoteEventsToGUI bool
|
// MIDIMessage represents a MIDI message received from a MIDI input port or VST
|
||||||
|
// host.
|
||||||
|
type MIDIMessage struct {
|
||||||
|
Timestamp int64 // in samples (at 44100 Hz)
|
||||||
|
Data [3]byte
|
||||||
|
Source any // tag to identify the source of the message; any unique pointer will do
|
||||||
|
}
|
||||||
|
|
||||||
func runMIDIRouter(broker *Broker) {
|
func (m *MIDIMessage) isNoteOff() bool { return m.Data[0]&0xF0 == 0x80 }
|
||||||
noteEventsToGUI := false
|
func (m *MIDIMessage) isNoteOn() bool { return m.Data[0]&0xF0 == 0x90 }
|
||||||
|
func (m *MIDIMessage) isControlChange() bool { return m.Data[0]&0xF0 == 0xB0 }
|
||||||
|
|
||||||
|
func (m *MIDIMessage) getNoteOn() (channel, note, velocity byte, ok bool) {
|
||||||
|
if !m.isNoteOn() {
|
||||||
|
return 0, 0, 0, false
|
||||||
|
}
|
||||||
|
return m.Data[0] & 0x0F, m.Data[1], m.Data[2], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MIDIMessage) getNoteOff() (channel, note, velocity byte, ok bool) {
|
||||||
|
if !m.isNoteOff() {
|
||||||
|
return 0, 0, 0, false
|
||||||
|
}
|
||||||
|
return m.Data[0] & 0x0F, m.Data[1], m.Data[2], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MIDIMessage) getControlChange() (channel, controller, value byte, ok bool) {
|
||||||
|
if !m.isControlChange() {
|
||||||
|
return 0, 0, 0, false
|
||||||
|
}
|
||||||
|
return m.Data[0] & 0x0F, m.Data[1], m.Data[2], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// midiRouter encompasses all the necessary information where MIDIMessages
|
||||||
|
// should be forwarded. MIDIHandler and Player have their own copies of the
|
||||||
|
// midiRouter so that the messages don't have to pass through other goroutines
|
||||||
|
// to be routed. Model has also a copy to display a gui to modify it.
|
||||||
|
type midiRouter struct {
|
||||||
|
sendNoteEventsToGUI bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *midiRouter) route(b *Broker, msg *MIDIMessage) (ok bool) {
|
||||||
|
switch {
|
||||||
|
case msg.isNoteOn() || msg.isNoteOff():
|
||||||
|
if r.sendNoteEventsToGUI {
|
||||||
|
return TrySend(b.ToGUI, any(msg))
|
||||||
|
} else {
|
||||||
|
return TrySend(b.ToPlayer, any(msg))
|
||||||
|
}
|
||||||
|
case msg.isControlChange():
|
||||||
|
return TrySend(b.ToModel, MsgToModel{Data: msg})
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMIDIHandler(b *Broker) {
|
||||||
|
router := midiRouter{sendNoteEventsToGUI: false}
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-broker.CloseMIDIRouter:
|
case v := <-b.ToMIDIHandler:
|
||||||
close(broker.FinishedMIDIRouter)
|
switch msg := v.(type) {
|
||||||
|
case *MIDIMessage:
|
||||||
|
router.route(b, msg)
|
||||||
|
case midiRouter:
|
||||||
|
router = msg
|
||||||
|
}
|
||||||
|
case <-b.CloseMIDIHandler:
|
||||||
|
close(b.FinishedMIDIHandler)
|
||||||
return
|
return
|
||||||
case msg := <-broker.ToMIDIRouter:
|
|
||||||
switch m := msg.(type) {
|
|
||||||
case setNoteEventsToGUI:
|
|
||||||
noteEventsToGUI = bool(m)
|
|
||||||
case *NoteEvent:
|
|
||||||
if noteEventsToGUI {
|
|
||||||
TrySend(broker.ToGUI, msg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
TrySend(broker.ToPlayer, msg)
|
|
||||||
case *ControlChange:
|
|
||||||
TrySend(broker.ToModel, MsgToModel{Data: msg})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,8 +277,8 @@ func (m *MIDIModel) selectedParam() (MIDIParam, bool) {
|
|||||||
return value, true
|
return value, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MIDIModel) handleControlEvent(e ControlChange) {
|
func (m *MIDIModel) handleControlEvent(channel, control, value int) {
|
||||||
key := MIDIControl{Channel: e.Channel, Control: e.Control}
|
key := MIDIControl{Channel: channel, Control: control}
|
||||||
if m.midi.binding {
|
if m.midi.binding {
|
||||||
m.midi.binding = false
|
m.midi.binding = false
|
||||||
value, ok := m.selectedParam()
|
value, ok := m.selectedParam()
|
||||||
@ -246,7 +300,7 @@ func (m *MIDIModel) handleControlEvent(e ControlChange) {
|
|||||||
// +62 is chose so that the center position of a typical MIDI controller,
|
// +62 is chose so that the center position of a typical MIDI controller,
|
||||||
// which is 64, maps to 64 of a 0..128 range Sointu parameter. From there
|
// which is 64, maps to 64 of a 0..128 range Sointu parameter. From there
|
||||||
// on, 65 maps to 66 and, importantly, 127 maps to 128.
|
// on, 65 maps to 66 and, importantly, 127 maps to 128.
|
||||||
newVal := (e.Value*(t.Max-t.Min)+62)/127 + t.Min
|
newVal := (value*(t.Max-t.Min)+62)/127 + t.Min
|
||||||
if m.d.Song.Patch[i].Units[u].Parameters[t.Param] == newVal {
|
if m.d.Song.Patch[i].Units[u].Parameters[t.Param] == newVal {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -203,17 +203,17 @@ func NewModel(broker *Broker, synthers []sointu.Synther, midiContext MIDIContext
|
|||||||
m.Play().setSynther(0, false)
|
m.Play().setSynther(0, false)
|
||||||
go runDetector(broker)
|
go runDetector(broker)
|
||||||
go runSpecAnalyzer(broker)
|
go runSpecAnalyzer(broker)
|
||||||
go runMIDIRouter(broker)
|
go runMIDIHandler(broker)
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) Close() {
|
func (m *Model) Close() {
|
||||||
TrySend(m.broker.CloseDetector, struct{}{})
|
TrySend(m.broker.CloseDetector, struct{}{})
|
||||||
TrySend(m.broker.CloseSpecAn, struct{}{})
|
TrySend(m.broker.CloseSpecAn, struct{}{})
|
||||||
TrySend(m.broker.CloseMIDIRouter, struct{}{})
|
TrySend(m.broker.CloseMIDIHandler, struct{}{})
|
||||||
TimeoutReceive(m.broker.FinishedDetector, 3*time.Second)
|
TimeoutReceive(m.broker.FinishedDetector, 3*time.Second)
|
||||||
TimeoutReceive(m.broker.FinishedSpecAn, 3*time.Second)
|
TimeoutReceive(m.broker.FinishedSpecAn, 3*time.Second)
|
||||||
TimeoutReceive(m.broker.FinishedMIDIRouter, 3*time.Second)
|
TimeoutReceive(m.broker.FinishedMIDIHandler, 3*time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestQuit asks the tracker to quit, showing a dialog if there are unsaved
|
// RequestQuit asks the tracker to quit, showing a dialog if there are unsaved
|
||||||
@ -389,8 +389,10 @@ func (m *Model) ProcessMsg(msg MsgToModel) {
|
|||||||
case *Spectrum:
|
case *Spectrum:
|
||||||
m.broker.PutSpectrum(m.spectrum)
|
m.broker.PutSpectrum(m.spectrum)
|
||||||
m.spectrum = e
|
m.spectrum = e
|
||||||
case *ControlChange:
|
case *MIDIMessage:
|
||||||
m.MIDI().handleControlEvent(*e)
|
if channel, control, value, ok := e.getControlChange(); ok {
|
||||||
|
m.MIDI().handleControlEvent(int(channel), int(control), int(value))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,8 @@ type (
|
|||||||
frameDeltas map[any]int64 // Player.frame (approx.)= event.Timestamp + frameDeltas[event.Source]
|
frameDeltas map[any]int64 // Player.frame (approx.)= event.Timestamp + frameDeltas[event.Source]
|
||||||
events NoteEventList
|
events NoteEventList
|
||||||
|
|
||||||
|
midiRouter midiRouter
|
||||||
|
|
||||||
status PlayerStatus // the part of the Player state that is communicated to the model to visualize what Player is doing
|
status PlayerStatus // the part of the Player state that is communicated to the model to visualize what Player is doing
|
||||||
|
|
||||||
synther sointu.Synther // the synther used to create new synths
|
synther sointu.Synther // the synther used to create new synths
|
||||||
@ -180,6 +182,8 @@ func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext
|
|||||||
p.SendAlert("PlayerCrash", fmt.Sprintf("synth did not fill the audio buffer even with %d render calls", numRenderTries), Error)
|
p.SendAlert("PlayerCrash", fmt.Sprintf("synth did not fill the audio buffer even with %d render calls", numRenderTries), Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Player) EmitMIDIMsg(msg *MIDIMessage) bool { return p.midiRouter.route(p.broker, msg) }
|
||||||
|
|
||||||
func (p *Player) destroySynth() {
|
func (p *Player) destroySynth() {
|
||||||
if p.synth != nil {
|
if p.synth != nil {
|
||||||
p.synth.Close()
|
p.synth.Close()
|
||||||
@ -282,6 +286,20 @@ loop:
|
|||||||
TrySend(p.broker.ToModel, MsgToModel{Reset: true})
|
TrySend(p.broker.ToModel, MsgToModel{Reset: true})
|
||||||
case *NoteEvent:
|
case *NoteEvent:
|
||||||
p.events = append(p.events, *m)
|
p.events = append(p.events, *m)
|
||||||
|
case *MIDIMessage:
|
||||||
|
// In future, here we should map midi channels to various
|
||||||
|
// instruments, possible mapping the velocity to another
|
||||||
|
// instrument as well
|
||||||
|
if m.Data[0] >= 0x80 && m.Data[0] <= 0x9F {
|
||||||
|
p.events = append(p.events, NoteEvent{
|
||||||
|
Timestamp: m.Timestamp,
|
||||||
|
Channel: int(m.Data[0] & 0x0F),
|
||||||
|
Note: m.Data[1],
|
||||||
|
On: m.Data[0] >= 0x90,
|
||||||
|
Source: m.Source})
|
||||||
|
}
|
||||||
|
case midiRouter:
|
||||||
|
p.midiRouter = m
|
||||||
case RecordingMsg:
|
case RecordingMsg:
|
||||||
if m.bool {
|
if m.bool {
|
||||||
p.recording = Recording{State: RecordingWaitingForNote}
|
p.recording = Recording{State: RecordingWaitingForNote}
|
||||||
|
|||||||
Reference in New Issue
Block a user