mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
feat(tracker): implement simple undo / redo
This commit is contained in:
parent
b1df5bb4d5
commit
eb25ddd864
@ -45,18 +45,17 @@ var noteMap = map[string]int{
|
|||||||
// KeyEvent handles incoming key events and returns true if repaint is needed.
|
// KeyEvent handles incoming key events and returns true if repaint is needed.
|
||||||
func (t *Tracker) KeyEvent(e key.Event) bool {
|
func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||||
if e.State == key.Press {
|
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 {
|
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":
|
case "A":
|
||||||
t.setCurrent(0)
|
t.setCurrent(0)
|
||||||
return true
|
return true
|
||||||
@ -114,6 +113,17 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
|||||||
t.CursorColumn = 0
|
t.CursorColumn = 0
|
||||||
return true
|
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
|
return false
|
||||||
}
|
}
|
||||||
@ -135,6 +145,7 @@ func (t *Tracker) moveCursor(delta int) {
|
|||||||
|
|
||||||
// setCurrent sets the (note) value in current pattern under cursor to iv
|
// setCurrent sets the (note) value in current pattern under cursor to iv
|
||||||
func (t *Tracker) setCurrent(iv byte) {
|
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
|
t.song.Tracks[t.ActiveTrack].Patterns[t.song.Tracks[t.ActiveTrack].Sequence[t.DisplayPattern]][t.CursorRow] = iv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,8 @@ type Tracker struct {
|
|||||||
synth sointu.Synth
|
synth sointu.Synth
|
||||||
playBuffer []float32
|
playBuffer []float32
|
||||||
closer chan struct{}
|
closer chan struct{}
|
||||||
|
undoStack []sointu.Song
|
||||||
|
redoStack []sointu.Song
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) LoadSong(song sointu.Song) error {
|
func (t *Tracker) LoadSong(song sointu.Song) error {
|
||||||
@ -59,6 +61,21 @@ func (t *Tracker) LoadSong(song sointu.Song) error {
|
|||||||
} else {
|
} else {
|
||||||
t.synth = synth
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +170,7 @@ func (t *Tracker) ChangeOctave(delta int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) ChangeBPM(delta int) bool {
|
func (t *Tracker) ChangeBPM(delta int) bool {
|
||||||
|
t.SaveUndo()
|
||||||
newBPM := t.song.BPM + delta
|
newBPM := t.song.BPM + delta
|
||||||
if newBPM < 1 {
|
if newBPM < 1 {
|
||||||
newBPM = 1
|
newBPM = 1
|
||||||
@ -169,6 +187,7 @@ func (t *Tracker) ChangeBPM(delta int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) AddTrack() {
|
func (t *Tracker) AddTrack() {
|
||||||
|
t.SaveUndo()
|
||||||
if t.song.TotalTrackVoices() < t.song.Patch.TotalVoices() {
|
if t.song.TotalTrackVoices() < t.song.Patch.TotalVoices() {
|
||||||
seq := make([]byte, t.song.SequenceLength())
|
seq := make([]byte, t.song.SequenceLength())
|
||||||
patterns := [][]byte{make([]byte, t.song.PatternRows())}
|
patterns := [][]byte{make([]byte, t.song.PatternRows())}
|
||||||
@ -181,6 +200,7 @@ func (t *Tracker) AddTrack() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) AddInstrument() {
|
func (t *Tracker) AddInstrument() {
|
||||||
|
t.SaveUndo()
|
||||||
if t.song.Patch.TotalVoices() < 32 {
|
if t.song.Patch.TotalVoices() < 32 {
|
||||||
units := make([]sointu.Unit, len(defaultInstrument.Units))
|
units := make([]sointu.Unit, len(defaultInstrument.Units))
|
||||||
for i, defUnit := range defaultInstrument.Units {
|
for i, defUnit := range defaultInstrument.Units {
|
||||||
@ -220,6 +240,8 @@ func New(audioContext sointu.AudioContext) *Tracker {
|
|||||||
patternJump: make(chan int),
|
patternJump: make(chan int),
|
||||||
ticked: make(chan struct{}),
|
ticked: make(chan struct{}),
|
||||||
closer: 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}
|
t.Theme.Color.Primary = color.RGBA{R: 64, G: 64, B: 64, A: 255}
|
||||||
go t.sequencerLoop(t.closer)
|
go t.sequencerLoop(t.closer)
|
||||||
|
37
tracker/undo.go
Normal file
37
tracker/undo.go
Normal 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]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user