feat(tracker): implement simple undo / redo

This commit is contained in:
vsariola 2021-01-08 22:00:15 +02:00
parent b1df5bb4d5
commit eb25ddd864
3 changed files with 81 additions and 11 deletions

View File

@ -45,18 +45,17 @@ var noteMap = map[string]int{
// KeyEvent handles incoming key events and returns true if repaint is needed.
func (t *Tracker) KeyEvent(e key.Event) bool {
if e.State == key.Press {
if t.CursorColumn == 0 {
if val, ok := noteMap[e.Name]; ok {
t.NotePressed(val)
return true
}
} else {
if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil {
t.NumberPressed(byte(iv))
return true
}
}
switch e.Name {
case "Z":
if e.Modifiers.Contain(key.ModCtrl) {
t.Undo()
return true
}
case "Y":
if e.Modifiers.Contain(key.ModCtrl) {
t.Redo()
return true
}
case "A":
t.setCurrent(0)
return true
@ -114,6 +113,17 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
t.CursorColumn = 0
return true
}
if t.CursorColumn == 0 {
if val, ok := noteMap[e.Name]; ok {
t.NotePressed(val)
return true
}
} else {
if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil {
t.NumberPressed(byte(iv))
return true
}
}
}
return false
}
@ -135,6 +145,7 @@ func (t *Tracker) moveCursor(delta int) {
// setCurrent sets the (note) value in current pattern under cursor to iv
func (t *Tracker) setCurrent(iv byte) {
t.SaveUndo()
t.song.Tracks[t.ActiveTrack].Patterns[t.song.Tracks[t.ActiveTrack].Sequence[t.DisplayPattern]][t.CursorRow] = iv
}

View File

@ -44,6 +44,8 @@ type Tracker struct {
synth sointu.Synth
playBuffer []float32
closer chan struct{}
undoStack []sointu.Song
redoStack []sointu.Song
}
func (t *Tracker) LoadSong(song sointu.Song) error {
@ -59,6 +61,21 @@ func (t *Tracker) LoadSong(song sointu.Song) error {
} else {
t.synth = synth
}
if t.DisplayPattern >= song.SequenceLength() {
t.DisplayPattern = song.SequenceLength() - 1
}
if t.CursorRow >= song.PatternRows() {
t.CursorRow = song.PatternRows() - 1
}
if t.PlayPattern >= song.SequenceLength() {
t.PlayPattern = song.SequenceLength() - 1
}
if t.PlayRow >= song.PatternRows() {
t.PlayRow = song.PatternRows() - 1
}
if t.ActiveTrack >= len(song.Tracks) {
t.ActiveTrack = len(song.Tracks) - 1
}
return nil
}
@ -153,6 +170,7 @@ func (t *Tracker) ChangeOctave(delta int) bool {
}
func (t *Tracker) ChangeBPM(delta int) bool {
t.SaveUndo()
newBPM := t.song.BPM + delta
if newBPM < 1 {
newBPM = 1
@ -169,6 +187,7 @@ func (t *Tracker) ChangeBPM(delta int) bool {
}
func (t *Tracker) AddTrack() {
t.SaveUndo()
if t.song.TotalTrackVoices() < t.song.Patch.TotalVoices() {
seq := make([]byte, t.song.SequenceLength())
patterns := [][]byte{make([]byte, t.song.PatternRows())}
@ -181,6 +200,7 @@ func (t *Tracker) AddTrack() {
}
func (t *Tracker) AddInstrument() {
t.SaveUndo()
if t.song.Patch.TotalVoices() < 32 {
units := make([]sointu.Unit, len(defaultInstrument.Units))
for i, defUnit := range defaultInstrument.Units {
@ -220,6 +240,8 @@ func New(audioContext sointu.AudioContext) *Tracker {
patternJump: make(chan int),
ticked: make(chan struct{}),
closer: make(chan struct{}),
undoStack: []sointu.Song{},
redoStack: []sointu.Song{},
}
t.Theme.Color.Primary = color.RGBA{R: 64, G: 64, B: 64, A: 255}
go t.sequencerLoop(t.closer)

37
tracker/undo.go Normal file
View File

@ -0,0 +1,37 @@
package tracker
var undoSkip = map[string]int{
"setNote": 10,
}
const maxUndo = 256
func (t *Tracker) SaveUndo() {
if len(t.undoStack) >= maxUndo {
t.undoStack = t.undoStack[1:]
}
t.undoStack = append(t.undoStack, t.song.Copy())
t.redoStack = t.redoStack[:0]
}
func (t *Tracker) Undo() {
if len(t.undoStack) > 0 {
if len(t.redoStack) >= maxUndo {
t.redoStack = t.redoStack[1:]
}
t.redoStack = append(t.redoStack, t.song.Copy())
t.LoadSong(t.undoStack[len(t.undoStack)-1])
t.undoStack = t.undoStack[:len(t.undoStack)-1]
}
}
func (t *Tracker) Redo() {
if len(t.redoStack) > 0 {
if len(t.undoStack) >= maxUndo {
t.undoStack = t.undoStack[1:]
}
t.undoStack = append(t.undoStack, t.song.Copy())
t.LoadSong(t.redoStack[len(t.redoStack)-1])
t.redoStack = t.redoStack[:len(t.redoStack)-1]
}
}