Files
sointu/tracker/track.go
2026-01-27 22:16:14 +02:00

187 lines
6.3 KiB
Go

package tracker
import (
"fmt"
"math"
"github.com/vsariola/sointu"
"github.com/vsariola/sointu/vm"
)
// Track returns the Track view of the model, containing methods to manipulate
// the tracks.
func (m *Model) Track() *TrackModel { return (*TrackModel)(m) }
type TrackModel Model
// LinkInstrument returns a Bool controlling whether instruments and tracks are
// linked.
func (m *TrackModel) LinkInstrument() Bool { return MakeBoolFromPtr(&m.linkInstrTrack) }
// Title returns the title of the track for a given index.
func (m *TrackModel) Item(index int) TrackListItem {
if index < 0 || index >= len(m.derived.tracks) {
return TrackListItem{}
}
return TrackListItem{m.derived.tracks[index].title, m.d.Song.Score.Tracks[index].Effect}
}
type TrackListItem struct {
Title string
Effect bool
}
// Add returns an Action to add a new track.
func (m *TrackModel) Add() Action { return MakeAction((*addTrack)(m)) }
type addTrack TrackModel
func (m *addTrack) Enabled() bool { return m.d.Song.Score.NumVoices() < vm.MAX_VOICES }
func (m *addTrack) Do() {
defer (*Model)(m).change("AddTrack", SongChange, MajorChange)()
voiceIndex := m.d.Song.Score.FirstVoiceForTrack(m.d.Cursor.Track)
p := sointu.Patch{defaultInstrument.Copy()}
t := []sointu.Track{{NumVoices: 1}}
_, _, ok := (*Model)(m).addVoices(voiceIndex, p, t, (*Model)(m).linkInstrTrack, true)
m.changeCancel = !ok
}
// Delete returns an Action to delete the selected track(s).
func (m *TrackModel) Delete() Action { return MakeAction((*deleteTrack)(m)) }
type deleteTrack TrackModel
func (m *deleteTrack) Enabled() bool { return len(m.d.Song.Score.Tracks) > 0 }
func (m *deleteTrack) Do() { (*TrackModel)(m).List().DeleteElements(false) }
// Split returns an Action to split the selected track into two tracks,
// distributing the voices as evenly as possible.
func (m *TrackModel) Split() Action { return MakeAction((*splitTrack)(m)) }
type splitTrack TrackModel
func (m *splitTrack) Enabled() bool {
return m.d.Cursor.Track >= 0 && m.d.Cursor.Track < len(m.d.Song.Score.Tracks) && m.d.Song.Score.Tracks[m.d.Cursor.Track].NumVoices > 1
}
func (m *splitTrack) Do() {
defer (*Model)(m).change("SplitTrack", SongChange, MajorChange)()
voiceIndex := m.d.Song.Score.FirstVoiceForTrack(m.d.Cursor.Track)
middle := voiceIndex + (m.d.Song.Score.Tracks[m.d.Cursor.Track].NumVoices+1)/2
end := voiceIndex + m.d.Song.Score.Tracks[m.d.Cursor.Track].NumVoices
left, ok := VoiceSlice(m.d.Song.Score.Tracks, Range{math.MinInt, middle})
if !ok {
m.changeCancel = true
return
}
right, ok := VoiceSlice(m.d.Song.Score.Tracks, Range{end, math.MaxInt})
if !ok {
m.changeCancel = true
return
}
newTrack := sointu.Track{NumVoices: end - middle}
m.d.Song.Score.Tracks = append(left, newTrack)
m.d.Song.Score.Tracks = append(m.d.Song.Score.Tracks, right...)
}
// Effect returns a Bool to toggle whether the currently selected track is an
// effect track and should be displayed as hexadecimals or not.
func (m *TrackModel) Effect() Bool { return MakeBool((*trackEffect)(m)) }
type trackEffect TrackModel
func (m *trackEffect) 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 *trackEffect) 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
}
// Voices returns an Int to adjust the number of voices for the currently
// selected track.
func (m *TrackModel) Voices() Int { return MakeInt((*trackVoices)(m)) }
type trackVoices TrackModel
func (v *trackVoices) Value() int {
t := v.d.Cursor.Track
if t < 0 || t >= len(v.d.Song.Score.Tracks) {
return 1
}
return max(v.d.Song.Score.Tracks[t].NumVoices, 1)
}
func (m *trackVoices) SetValue(value int) bool {
defer (*Model)(m).change("TrackVoices", SongChange, MinorChange)()
voiceIndex := m.d.Song.Score.FirstVoiceForTrack(m.d.Cursor.Track)
voiceRange := Range{voiceIndex, voiceIndex + m.d.Song.Score.Tracks[m.d.Cursor.Track].NumVoices}
ranges := MakeSetLength(voiceRange, value)
ok := (*Model)(m).sliceInstrumentsTracks(m.linkInstrTrack, true, ranges...)
if !ok {
m.changeCancel = true
}
return ok
}
func (v *trackVoices) Range() RangeInclusive {
t := v.d.Cursor.Track
if t < 0 || t >= len(v.d.Song.Score.Tracks) {
return RangeInclusive{1, 1}
}
return RangeInclusive{1, (*Model)(v).remainingVoices(v.linkInstrTrack, true) + v.d.Song.Score.Tracks[t].NumVoices}
}
// List returns a List of all the tracks, implementing MutableListData
func (m *TrackModel) List() List { return List{(*trackList)(m)} }
type trackList TrackModel
func (v *trackList) Selected() int { return v.d.Cursor.Track }
func (v *trackList) Selected2() int { return v.d.Cursor2.Track }
func (v *trackList) SetSelected(value int) { v.d.Cursor.Track = value }
func (v *trackList) SetSelected2(value int) { v.d.Cursor2.Track = value }
func (v *trackList) Count() int { return len((*Model)(v).d.Song.Score.Tracks) }
func (v *trackList) Move(r Range, delta int) (ok bool) {
voiceDelta := 0
if delta < 0 {
voiceDelta = -VoiceRange(v.d.Song.Score.Tracks, Range{r.Start + delta, r.Start}).Len()
} else if delta > 0 {
voiceDelta = VoiceRange(v.d.Song.Score.Tracks, Range{r.End, r.End + delta}).Len()
}
if voiceDelta == 0 {
return false
}
ranges := MakeMoveRanges(VoiceRange(v.d.Song.Score.Tracks, r), voiceDelta)
return (*Model)(v).sliceInstrumentsTracks(v.linkInstrTrack, true, ranges[:]...)
}
func (v *trackList) Delete(r Range) (ok bool) {
ranges := Complement(VoiceRange(v.d.Song.Score.Tracks, r))
return (*Model)(v).sliceInstrumentsTracks(v.linkInstrTrack, true, ranges[:]...)
}
func (v *trackList) Change(n string, severity ChangeSeverity) func() {
return (*Model)(v).change("TrackList."+n, SongChange, severity)
}
func (v *trackList) Cancel() {
v.changeCancel = true
}
func (v *trackList) Marshal(r Range) ([]byte, error) {
return (*Model)(v).marshalVoices(VoiceRange(v.d.Song.Score.Tracks, r))
}
func (m *trackList) Unmarshal(data []byte) (r Range, err error) {
voiceIndex := m.d.Song.Score.FirstVoiceForTrack(m.d.Cursor.Track)
_, r, ok := (*Model)(m).unmarshalVoices(voiceIndex, data, m.linkInstrTrack, true)
if !ok {
return Range{}, fmt.Errorf("unmarshal: unmarshalVoices failed")
}
return r, nil
}