mirror of
https://github.com/vsariola/sointu.git
synced 2025-11-08 19:04:25 -05:00
368 lines
10 KiB
Go
368 lines
10 KiB
Go
package tracker
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
type (
|
|
Bool struct {
|
|
value BoolValue
|
|
enabler Enabler
|
|
}
|
|
|
|
BoolValue interface {
|
|
Value() bool
|
|
SetValue(bool)
|
|
}
|
|
|
|
Panic Model
|
|
IsRecording Model
|
|
Playing Model
|
|
Effect Model
|
|
TrackMidiIn Model
|
|
UnitSearching Model
|
|
UnitDisabled Model
|
|
LoopToggle Model
|
|
Mute Model
|
|
Solo Model
|
|
Oversampling Model
|
|
InstrEditor Model
|
|
InstrPresets Model
|
|
InstrComment Model
|
|
Thread1 Model
|
|
Thread2 Model
|
|
Thread3 Model
|
|
Thread4 Model
|
|
|
|
simpleBool bool
|
|
)
|
|
|
|
func MakeBool(valueEnabler interface {
|
|
BoolValue
|
|
Enabler
|
|
}) Bool {
|
|
return Bool{value: valueEnabler, enabler: valueEnabler}
|
|
}
|
|
|
|
func MakeEnabledBool(value BoolValue) Bool {
|
|
return Bool{value: value, enabler: nil}
|
|
}
|
|
|
|
func (v Bool) Toggle() {
|
|
v.SetValue(!v.Value())
|
|
}
|
|
|
|
func (v Bool) SetValue(value bool) {
|
|
if v.Enabled() && v.Value() != value {
|
|
v.value.SetValue(value)
|
|
}
|
|
}
|
|
|
|
func (v Bool) Value() bool {
|
|
if v.value == nil {
|
|
return false
|
|
}
|
|
return v.value.Value()
|
|
}
|
|
|
|
func (v Bool) Enabled() bool {
|
|
if v.enabler == nil {
|
|
return true
|
|
}
|
|
return v.enabler.Enabled()
|
|
}
|
|
|
|
func (v *simpleBool) Value() bool { return bool(*v) }
|
|
func (v *simpleBool) SetValue(value bool) { *v = simpleBool(value) }
|
|
|
|
// Thread methods
|
|
|
|
func (m *Model) getThreadsBit(bit int) bool {
|
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
return false
|
|
}
|
|
mask := m.d.Song.Patch[m.d.InstrIndex].ThreadMaskM1 + 1
|
|
return mask&(1<<bit) != 0
|
|
}
|
|
|
|
func (m *Model) setThreadsBit(bit int, value bool) {
|
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
return
|
|
}
|
|
defer (*Model)(m).change("ThreadBitMask", PatchChange, MinorChange)()
|
|
mask := m.d.Song.Patch[m.d.InstrIndex].ThreadMaskM1 + 1
|
|
if value {
|
|
mask |= (1 << bit)
|
|
} else {
|
|
mask &^= (1 << bit)
|
|
}
|
|
m.d.Song.Patch[m.d.InstrIndex].ThreadMaskM1 = max(mask-1, 0) // -1 would have all threads disabled, so make that 0 i.e. use at least thread 1
|
|
m.warnAboutCrossThreadSends()
|
|
m.warnNoMultithreadSupport()
|
|
}
|
|
|
|
func (m *Model) warnAboutCrossThreadSends() {
|
|
for i, instr := range m.d.Song.Patch {
|
|
for _, unit := range instr.Units {
|
|
if unit.Type == "send" {
|
|
targetID, ok := unit.Parameters["target"]
|
|
if !ok {
|
|
continue
|
|
}
|
|
it, _, err := m.d.Song.Patch.FindUnit(targetID)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if instr.ThreadMaskM1 != m.d.Song.Patch[it].ThreadMaskM1 {
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *Model) warnNoMultithreadSupport() {
|
|
for _, instr := range m.d.Song.Patch {
|
|
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) Thread1() Bool { return MakeEnabledBool((*Thread1)(m)) }
|
|
func (m *Thread1) Value() bool { return (*Model)(m).getThreadsBit(0) }
|
|
func (m *Thread1) SetValue(val bool) { (*Model)(m).setThreadsBit(0, val) }
|
|
|
|
func (m *Model) Thread2() Bool { return MakeEnabledBool((*Thread2)(m)) }
|
|
func (m *Thread2) Value() bool { return (*Model)(m).getThreadsBit(1) }
|
|
func (m *Thread2) SetValue(val bool) { (*Model)(m).setThreadsBit(1, val) }
|
|
|
|
func (m *Model) Thread3() Bool { return MakeEnabledBool((*Thread3)(m)) }
|
|
func (m *Thread3) Value() bool { return (*Model)(m).getThreadsBit(2) }
|
|
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
|
|
|
|
func (m *Model) Panic() Bool { return MakeEnabledBool((*Panic)(m)) }
|
|
func (m *Panic) Value() bool { return m.panic }
|
|
func (m *Panic) SetValue(val bool) { (*Model)(m).setPanic(val) }
|
|
|
|
// IsRecording methods
|
|
|
|
func (m *Model) IsRecording() Bool { return MakeEnabledBool((*IsRecording)(m)) }
|
|
func (m *IsRecording) Value() bool { return (*Model)(m).recording }
|
|
func (m *IsRecording) SetValue(val bool) {
|
|
m.recording = val
|
|
m.instrEnlarged = val
|
|
TrySend(m.broker.ToPlayer, any(RecordingMsg{val}))
|
|
}
|
|
|
|
// Playing methods
|
|
|
|
func (m *Model) Playing() Bool { return MakeBool((*Playing)(m)) }
|
|
func (m *Playing) Value() bool { return m.playing }
|
|
func (m *Playing) SetValue(val bool) {
|
|
m.playing = val
|
|
if m.playing {
|
|
(*Model)(m).setPanic(false)
|
|
TrySend(m.broker.ToPlayer, any(StartPlayMsg{m.d.Cursor.SongPos}))
|
|
} else {
|
|
TrySend(m.broker.ToPlayer, any(IsPlayingMsg{val}))
|
|
}
|
|
}
|
|
func (m *Playing) Enabled() bool { return m.playing || !m.instrEnlarged }
|
|
|
|
// InstrEnlarged methods
|
|
|
|
func (m *Model) InstrEnlarged() Bool { return MakeEnabledBool((*simpleBool)(&m.instrEnlarged)) }
|
|
|
|
// InstrEditor methods
|
|
|
|
func (m *Model) InstrEditor() Bool { return MakeEnabledBool((*InstrEditor)(m)) }
|
|
func (m *InstrEditor) Value() bool { return m.d.InstrumentTab == InstrumentEditorTab }
|
|
func (m *InstrEditor) SetValue(val bool) {
|
|
if val {
|
|
m.d.InstrumentTab = InstrumentEditorTab
|
|
}
|
|
}
|
|
|
|
func (m *Model) InstrComment() Bool { return MakeEnabledBool((*InstrComment)(m)) }
|
|
func (m *InstrComment) Value() bool { return m.d.InstrumentTab == InstrumentCommentTab }
|
|
func (m *InstrComment) SetValue(val bool) {
|
|
if val {
|
|
m.d.InstrumentTab = InstrumentCommentTab
|
|
}
|
|
}
|
|
|
|
func (m *Model) InstrPresets() Bool { return MakeEnabledBool((*InstrPresets)(m)) }
|
|
func (m *InstrPresets) Value() bool { return m.d.InstrumentTab == InstrumentPresetsTab }
|
|
func (m *InstrPresets) SetValue(val bool) {
|
|
if val {
|
|
m.d.InstrumentTab = InstrumentPresetsTab
|
|
}
|
|
}
|
|
|
|
// Follow methods
|
|
|
|
func (m *Model) Follow() Bool { return MakeEnabledBool((*simpleBool)(&m.follow)) }
|
|
|
|
// TrackMidiIn (Midi Input for notes in the tracks)
|
|
|
|
func (m *Model) TrackMidiIn() Bool { return MakeEnabledBool((*TrackMidiIn)(m)) }
|
|
func (m *TrackMidiIn) Value() bool { return m.broker.mIDIEventsToGUI.Load() }
|
|
func (m *TrackMidiIn) SetValue(val bool) { m.broker.mIDIEventsToGUI.Store(val) }
|
|
|
|
// Effect methods
|
|
|
|
func (m *Model) Effect() Bool { return MakeEnabledBool((*Effect)(m)) }
|
|
func (m *Effect) Value() bool {
|
|
if m.d.Cursor.Track < 0 || m.d.Cursor.Track >= len(m.d.Song.Score.Tracks) {
|
|
return false
|
|
}
|
|
return m.d.Song.Score.Tracks[m.d.Cursor.Track].Effect
|
|
}
|
|
func (m *Effect) SetValue(val bool) {
|
|
if m.d.Cursor.Track < 0 || m.d.Cursor.Track >= len(m.d.Song.Score.Tracks) {
|
|
return
|
|
}
|
|
m.d.Song.Score.Tracks[m.d.Cursor.Track].Effect = val
|
|
}
|
|
|
|
// Oversampling methods
|
|
|
|
func (m *Model) Oversampling() Bool { return MakeEnabledBool((*Oversampling)(m)) }
|
|
func (m *Oversampling) Value() bool { return m.oversampling }
|
|
func (m *Oversampling) SetValue(val bool) {
|
|
m.oversampling = val
|
|
TrySend(m.broker.ToDetector, MsgToDetector{HasOversampling: true, Oversampling: val})
|
|
}
|
|
|
|
// UnitSearching methods
|
|
|
|
func (m *Model) UnitSearching() Bool { return MakeEnabledBool((*UnitSearching)(m)) }
|
|
func (m *UnitSearching) Value() bool { return m.d.UnitSearching }
|
|
func (m *UnitSearching) SetValue(val bool) {
|
|
m.d.UnitSearching = val
|
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
m.d.UnitSearchString = ""
|
|
return
|
|
}
|
|
if m.d.UnitIndex < 0 || m.d.UnitIndex >= len(m.d.Song.Patch[m.d.InstrIndex].Units) {
|
|
m.d.UnitSearchString = ""
|
|
return
|
|
}
|
|
m.d.UnitSearchString = m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].Type
|
|
}
|
|
|
|
// UnitDisabled methods
|
|
|
|
func (m *Model) UnitDisabled() Bool { return MakeBool((*UnitDisabled)(m)) }
|
|
func (m *UnitDisabled) Value() bool {
|
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
return false
|
|
}
|
|
if m.d.UnitIndex < 0 || m.d.UnitIndex >= len(m.d.Song.Patch[m.d.InstrIndex].Units) {
|
|
return false
|
|
}
|
|
return m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].Disabled
|
|
}
|
|
func (m *UnitDisabled) SetValue(val bool) {
|
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
return
|
|
}
|
|
l := ((*Model)(m)).Units().List()
|
|
r := l.listRange()
|
|
defer (*Model)(m).change("UnitDisabledSet", PatchChange, MajorChange)()
|
|
for i := r.Start; i < r.End; i++ {
|
|
m.d.Song.Patch[m.d.InstrIndex].Units[i].Disabled = val
|
|
}
|
|
}
|
|
func (m *UnitDisabled) Enabled() bool {
|
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
return false
|
|
}
|
|
if len(m.d.Song.Patch[m.d.InstrIndex].Units) == 0 {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// LoopToggle methods
|
|
|
|
func (m *Model) LoopToggle() Bool { return MakeEnabledBool((*LoopToggle)(m)) }
|
|
func (m *LoopToggle) Value() bool { return m.loop.Length > 0 }
|
|
func (t *LoopToggle) SetValue(val bool) {
|
|
m := (*Model)(t)
|
|
newLoop := Loop{}
|
|
if val {
|
|
l := m.OrderRows().List()
|
|
r := l.listRange()
|
|
newLoop = Loop{r.Start, r.End - r.Start}
|
|
}
|
|
m.setLoop(newLoop)
|
|
}
|
|
|
|
// UniquePatterns methods
|
|
|
|
func (m *Model) UniquePatterns() Bool { return MakeEnabledBool((*simpleBool)(&m.uniquePatterns)) }
|
|
|
|
// Mute methods
|
|
func (m *Model) Mute() Bool { return MakeBool((*Mute)(m)) }
|
|
func (m *Mute) Value() bool {
|
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
return false
|
|
}
|
|
return m.d.Song.Patch[m.d.InstrIndex].Mute
|
|
}
|
|
func (m *Mute) SetValue(val bool) {
|
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
return
|
|
}
|
|
defer (*Model)(m).change("Mute", PatchChange, MinorChange)()
|
|
a, b := min(m.d.InstrIndex, m.d.InstrIndex2), max(m.d.InstrIndex, m.d.InstrIndex2)
|
|
for i := a; i <= b; i++ {
|
|
if i < 0 || i >= len(m.d.Song.Patch) {
|
|
continue
|
|
}
|
|
m.d.Song.Patch[i].Mute = val
|
|
}
|
|
}
|
|
func (m *Mute) Enabled() bool { return m.d.InstrIndex >= 0 && m.d.InstrIndex < len(m.d.Song.Patch) }
|
|
|
|
// Solo methods
|
|
|
|
func (m *Model) Solo() Bool { return MakeBool((*Solo)(m)) }
|
|
func (m *Solo) Value() bool {
|
|
a, b := min(m.d.InstrIndex, m.d.InstrIndex2), max(m.d.InstrIndex, m.d.InstrIndex2)
|
|
for i := range m.d.Song.Patch {
|
|
if i < 0 || i >= len(m.d.Song.Patch) {
|
|
continue
|
|
}
|
|
if (i >= a && i <= b) == m.d.Song.Patch[i].Mute {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
func (m *Solo) SetValue(val bool) {
|
|
defer (*Model)(m).change("Solo", PatchChange, MinorChange)()
|
|
a, b := min(m.d.InstrIndex, m.d.InstrIndex2), max(m.d.InstrIndex, m.d.InstrIndex2)
|
|
for i := range m.d.Song.Patch {
|
|
if i < 0 || i >= len(m.d.Song.Patch) {
|
|
continue
|
|
}
|
|
m.d.Song.Patch[i].Mute = !(i >= a && i <= b) && val
|
|
}
|
|
}
|
|
func (m *Solo) Enabled() bool { return m.d.InstrIndex >= 0 && m.d.InstrIndex < len(m.d.Song.Patch) }
|
|
|
|
// LinkInstrTrack methods
|
|
|
|
func (m *Model) LinkInstrTrack() Bool { return MakeEnabledBool((*simpleBool)(&m.linkInstrTrack)) }
|