feat(tracker): change keyboard shortcuts to mimic old trackers

This commit is contained in:
5684185+vsariola@users.noreply.github.com 2024-10-11 13:44:06 +03:00
parent b4a63ce362
commit 91b7850bf7
7 changed files with 122 additions and 39 deletions

View File

@ -28,6 +28,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Empty patch should not crash the native synth ([#148][i148])
### Changed
- The keyboard shortcuts are now again closer to what they were old trackers
([#151][i151])
- The stand-alone apps now output floating point sound, as made possible by
upgrading oto-library to latest version. This way the tracker sound output
matches the compiled output better, as usually compiled intros output sound in
@ -237,6 +239,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
[i148]: https://github.com/vsariola/sointu/issues/148
[i149]: https://github.com/vsariola/sointu/issues/149
[i150]: https://github.com/vsariola/sointu/issues/150
[i151]: https://github.com/vsariola/sointu/issues/151
[i154]: https://github.com/vsariola/sointu/issues/154
[i158]: https://github.com/vsariola/sointu/issues/158
[i162]: https://github.com/vsariola/sointu/issues/162

View File

@ -275,18 +275,75 @@ func (m *Model) RemoveUnused() Action {
})
}
func (m *Model) Rewind() Action {
func (m *Model) PlayFromCurrentPosition() Action {
return Action{
allowed: func() bool {
return m.playing || !m.instrEnlarged
},
allowed: func() bool { return !m.instrEnlarged },
do: func() {
m.setPanic(false)
m.setLoop(Loop{})
m.playing = true
m.send(StartPlayMsg{m.d.Cursor.SongPos})
},
}
}
func (m *Model) PlayFromSongStart() Action {
return Action{
allowed: func() bool { return !m.instrEnlarged },
do: func() {
m.setPanic(false)
m.setLoop(Loop{})
m.playing = true
m.send(StartPlayMsg{})
},
}
}
func (m *Model) PlaySelected() Action {
return Action{
allowed: func() bool { return !m.instrEnlarged },
do: func() {
m.setPanic(false)
m.playing = true
l := m.OrderRows().List()
a, b := l.listRange()
newLoop := Loop{a, b - a + 1}
m.setLoop(newLoop)
m.send(StartPlayMsg{sointu.SongPos{OrderRow: a, PatternRow: 0}})
},
}
}
func (m *Model) PlayFromLoopStart() Action {
return Action{
allowed: func() bool { return !m.instrEnlarged },
do: func() {
m.setPanic(false)
if m.loop == (Loop{}) {
m.PlaySelected().Do()
return
}
m.playing = true
m.send(StartPlayMsg{sointu.SongPos{OrderRow: m.loop.Start, PatternRow: 0}})
},
}
}
func (m *Model) StopPlaying() Action {
return Action{
allowed: func() bool { return true },
do: func() {
if !m.playing {
m.setPanic(true)
m.setLoop(Loop{})
return
}
m.playing = false
(*Model)(m).send(IsPlayingMsg{false})
},
}
}
func (m *Model) AddOrderRow(before bool) Action {
return Allow(func() {
defer m.change("AddOrderRowAction", ScoreChange, MinorChange)()
@ -394,8 +451,9 @@ func (m *Model) completeAction(checkSave bool) {
}
switch m.dialog {
case NewSongChanges, NewSongSaveExplorer:
c := m.change("NewSong", SongChange|LoopChange, MajorChange)
c := m.change("NewSong", SongChange, MajorChange)
m.resetSong()
m.setLoop(Loop{})
c()
m.d.ChangedSinceSave = false
m.dialog = NoDialog
@ -408,3 +466,17 @@ func (m *Model) completeAction(checkSave bool) {
m.dialog = NoDialog
}
}
func (m *Model) setPanic(val bool) {
if m.panic != val {
m.panic = val
m.send(PanicMsg{val})
}
}
func (m *Model) setLoop(newLoop Loop) {
if m.loop != newLoop {
m.loop = newLoop
m.send(newLoop)
}
}

View File

@ -51,8 +51,7 @@ func (m *Model) LoopToggle() *LoopToggle { return (*LoopToggle)(m) }
func (m *Panic) Bool() Bool { return Bool{m} }
func (m *Panic) Value() bool { return m.panic }
func (m *Panic) setValue(val bool) {
m.panic = val
(*Model)(m).send(PanicMsg{val})
(*Model)(m).setPanic(val)
}
func (m *Panic) Enabled() bool { return true }
@ -74,6 +73,7 @@ func (m *Playing) Value() bool { return m.playing }
func (m *Playing) setValue(val bool) {
m.playing = val
if m.playing {
(*Model)(m).setPanic(false)
(*Model)(m).send(StartPlayMsg{m.d.Cursor.SongPos})
} else {
(*Model)(m).send(IsPlayingMsg{val})
@ -98,9 +98,9 @@ func (m *CommentExpanded) Enabled() bool { return true }
// NoteTracking methods
func (m *NoteTracking) Bool() Bool { return Bool{m} }
func (m *NoteTracking) Value() bool { return m.playing && m.noteTracking }
func (m *NoteTracking) Value() bool { return m.noteTracking }
func (m *NoteTracking) setValue(val bool) { m.noteTracking = val }
func (m *NoteTracking) Enabled() bool { return m.playing }
func (m *NoteTracking) Enabled() bool { return true }
// Effect methods
@ -173,16 +173,15 @@ func (m *UnitDisabled) Enabled() bool {
// LoopToggle methods
func (m *LoopToggle) Bool() Bool { return Bool{m} }
func (m *LoopToggle) Value() bool { return m.d.Loop.Length > 0 }
func (m *LoopToggle) Value() bool { return m.loop.Length > 0 }
func (t *LoopToggle) setValue(val bool) {
m := (*Model)(t)
defer m.change("SetLoopAction", LoopChange, MinorChange)()
if !val {
m.d.Loop = Loop{}
return
}
newLoop := Loop{}
if val {
l := m.OrderRows().List()
a, b := l.listRange()
m.d.Loop = Loop{a, b - a + 1}
newLoop = Loop{a, b - a + 1}
}
m.setLoop(newLoop)
}
func (m *LoopToggle) Enabled() bool { return true }

View File

@ -121,19 +121,34 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
case "F3":
t.InstrumentEditor.Focus()
return
case "F5":
t.SongPanel.RewindBtn.Action.Do()
t.SongPanel.NoteTracking.Bool.Set(!e.Modifiers.Contain(key.ModCtrl))
case "Space":
t.NoteTracking().Bool().Set(e.Modifiers.Contain(key.ModShift))
t.Playing().Bool().Toggle()
return
case "F6", "Space":
t.SongPanel.PlayingBtn.Bool.Toggle()
t.SongPanel.NoteTracking.Bool.Set(!e.Modifiers.Contain(key.ModCtrl))
case "F5":
t.NoteTracking().Bool().Set(e.Modifiers.Contain(key.ModShift))
if e.Modifiers.Contain(key.ModCtrl) {
t.Model.PlayFromSongStart().Do()
} else {
t.Model.PlayFromCurrentPosition().Do()
}
return
case "F6":
t.NoteTracking().Bool().Set(e.Modifiers.Contain(key.ModShift))
if e.Modifiers.Contain(key.ModCtrl) {
t.Model.PlayFromLoopStart().Do()
} else {
t.Model.PlaySelected().Do()
}
return
case "F7":
t.SongPanel.RecordBtn.Bool.Toggle()
t.IsRecording().Bool().Toggle()
return
case "F8":
t.SongPanel.NoteTracking.Bool.Toggle()
t.StopPlaying().Do()
return
case "F9":
t.NoteTracking().Bool().Toggle()
return
case "F12":
t.Panic().Bool().Toggle()

View File

@ -56,7 +56,7 @@ func NewSongPanel(model *tracker.Model) *SongPanel {
RecordBtn: NewBoolClickable(model.IsRecording().Bool()),
NoteTracking: NewBoolClickable(model.NoteTracking().Bool()),
PlayingBtn: NewBoolClickable(model.Playing().Bool()),
RewindBtn: NewActionClickable(model.Rewind()),
RewindBtn: NewActionClickable(model.PlayFromSongStart()),
}
ret.fileMenuItems = []MenuItem{
{IconBytes: icons.ContentClear, Text: "New Song", ShortcutText: shortcutKey + "N", Doer: model.NewSong()},
@ -105,10 +105,10 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D {
in := layout.UniformInset(unit.Dp(1))
panicBtnStyle := ToggleButton(gtx, tr.Theme, t.PanicBtn, "Panic (F12)")
rewindBtnStyle := ActionIcon(gtx, tr.Theme, t.RewindBtn, icons.AVFastRewind, "Rewind\n(F5)")
playBtnStyle := ToggleIcon(gtx, tr.Theme, t.PlayingBtn, icons.AVPlayArrow, icons.AVStop, "Play (F6 / Space)", "Stop (F6 / Space)")
rewindBtnStyle := ActionIcon(gtx, tr.Theme, t.RewindBtn, icons.AVFastRewind, "Rewind\n(Ctrl+F5)")
playBtnStyle := ToggleIcon(gtx, tr.Theme, t.PlayingBtn, icons.AVPlayArrow, icons.AVStop, "Play (F5 / Space)", "Stop (F8)")
recordBtnStyle := ToggleIcon(gtx, tr.Theme, t.RecordBtn, icons.AVFiberManualRecord, icons.AVFiberSmartRecord, "Record (F7)", "Stop (F7)")
noteTrackBtnStyle := ToggleIcon(gtx, tr.Theme, t.NoteTracking, icons.ActionSpeakerNotesOff, icons.ActionSpeakerNotes, "Follow\nOff\n(F8)", "Follow\nOn\n(F8)")
noteTrackBtnStyle := ToggleIcon(gtx, tr.Theme, t.NoteTracking, icons.ActionSpeakerNotesOff, icons.ActionSpeakerNotes, "Follow\nOff", "Follow\nOn")
loopBtnStyle := ToggleIcon(gtx, tr.Theme, t.LoopBtn, icons.NavigationArrowForward, icons.AVLoop, "Loop\nOff\n(Ctrl+L)", "Loop\nOn\n(Ctrl+L)")
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,

View File

@ -36,7 +36,6 @@ type (
ChangedSinceSave bool
RecoveryFilePath string
ChangedSinceRecovery bool
Loop Loop
}
Model struct {
@ -59,6 +58,7 @@ type (
recording bool
playing bool
playPosition sointu.SongPos
loop Loop
noteTracking bool
quitted bool
@ -132,7 +132,6 @@ const (
ScoreChange
BPMChange
RowsPerBeatChange
LoopChange
SongChange ChangeType = PatchChange | ScoreChange | BPMChange | RowsPerBeatChange
)
@ -156,7 +155,7 @@ const maxUndo = 64
func (m *Model) AverageVolume() Volume { return m.avgVolume }
func (m *Model) PeakVolume() Volume { return m.peakVolume }
func (m *Model) PlayPosition() sointu.SongPos { return m.playPosition }
func (m *Model) Loop() Loop { return m.d.Loop }
func (m *Model) Loop() Loop { return m.loop }
func (m *Model) PlaySongRow() int { return m.d.Song.Score.SongRow(m.playPosition) }
func (m *Model) ChangedSinceSave() bool { return m.d.ChangedSinceSave }
func (m *Model) Dialog() Dialog { return m.dialog }
@ -183,7 +182,7 @@ func NewModelPlayer(synther sointu.Synther, recoveryFilePath string) (*Model, *P
modelMsgs: modelMessages,
synther: synther,
song: m.d.Song.Copy(),
loop: m.d.Loop,
loop: m.loop,
avgVolumeMeter: VolumeAnalyzer{Attack: 0.3, Release: 0.3, Min: -100, Max: 20},
peakVolumeMeter: VolumeAnalyzer{Attack: 1e-4, Release: 1, Min: -100, Max: 20},
}
@ -244,9 +243,6 @@ func (m *Model) change(kind string, t ChangeType, severity ChangeSeverity) func(
if m.changeType&RowsPerBeatChange != 0 {
m.send(RowsPerBeatMsg{m.d.Song.RowsPerBeat})
}
if m.changeType&LoopChange != 0 {
m.send(m.d.Loop)
}
m.undoSkipCounter++
var limit int
switch m.changeSeverity {
@ -322,7 +318,6 @@ func (m *Model) UnmarshalRecovery(bytes []byte) {
}
m.d.ChangedSinceRecovery = false
m.send(m.d.Song.Copy())
m.send(m.d.Loop)
m.updatePatternUseCount()
}
@ -402,7 +397,6 @@ func (m *Model) resetSong() {
}
m.d.FilePath = ""
m.d.ChangedSinceSave = false
m.d.Loop = Loop{}
}
// send sends a message to the player

View File

@ -82,7 +82,7 @@ func (s *modelFuzzState) Iterate(yield func(string, func(p string, t *testing.T)
s.IterateAction("AddOctave", s.model.AddOctave(), yield, seed)
s.IterateAction("SubtractOctave", s.model.SubtractOctave(), yield, seed)
s.IterateAction("EditNoteOff", s.model.EditNoteOff(), yield, seed)
s.IterateAction("Rewind", s.model.Rewind(), yield, seed)
s.IterateAction("Rewind", s.model.PlayFromSongStart(), yield, seed)
s.IterateAction("AddOrderRowAfter", s.model.AddOrderRow(false), yield, seed)
s.IterateAction("AddOrderRowBefore", s.model.AddOrderRow(true), yield, seed)
s.IterateAction("DeleteOrderRowForward", s.model.DeleteOrderRow(false), yield, seed)