mirror of
https://github.com/vsariola/sointu.git
synced 2026-02-01 21:30:19 -05:00
187 lines
6.3 KiB
Go
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
|
|
}
|