From 670c09b73d81880bd6ae77d3664dcc9cd03c274d Mon Sep 17 00:00:00 2001 From: Alexis Boni Date: Sun, 11 Aug 2024 17:01:08 -0300 Subject: [PATCH 1/5] feat: copy/paste step resolves xaviergodart/sektron#7 --- filesystem/keymap.go | 10 +++++ sequencer/sequencer.go | 95 ++++++++++++++++++++++++++++++++++++++++++ ui/keymap.go | 14 ++++++- ui/ui.go | 14 +++++++ 4 files changed, 131 insertions(+), 2 deletions(-) diff --git a/filesystem/keymap.go b/filesystem/keymap.go index 277a459..db23138 100644 --- a/filesystem/keymap.go +++ b/filesystem/keymap.go @@ -18,6 +18,8 @@ type KeyMap struct { TempoDown string `json:"tempo_down"` AddParam string `json:"add_param"` RemoveParam string `json:"remove_param"` + CopyParams string `json:"copy_params"` + PasteParams string `json:"paste_params"` Validate string `json:"validate"` Left string `json:"left"` Right string `json:"right"` @@ -46,6 +48,8 @@ func NewDefaultAzertyKeyMap() KeyMap { TempoDown: "shift+down", AddParam: "ctrl+up", RemoveParam: "ctrl+down", + CopyParams: "ctrl+c", + PasteParams: "ctrl+v", Validate: "enter", Left: "left", Right: "right", @@ -75,6 +79,8 @@ func NewDefaultAzertyMacKeyMap() KeyMap { TempoDown: "shift+down", AddParam: "ctrl+up", RemoveParam: "ctrl+down", + CopyParams: "ctrl+c", + PasteParams: "ctrl+v", Validate: "enter", Left: "left", Right: "right", @@ -103,6 +109,8 @@ func NewDefaultQwertyKeyMap() KeyMap { TempoDown: "shift+down", AddParam: "ctrl+up", RemoveParam: "ctrl+down", + CopyParams: "ctrl+c", + PasteParams: "ctrl+v", Validate: "enter", Left: "left", Right: "right", @@ -132,6 +140,8 @@ func NewDefaultQwertyMacKeyMap() KeyMap { TempoDown: "shift+down", AddParam: "ctrl+up", RemoveParam: "ctrl+down", + CopyParams: "ctrl+c", + PasteParams: "ctrl+v", Validate: "enter", Left: "left", Right: "right", diff --git a/sequencer/sequencer.go b/sequencer/sequencer.go index 6d9ea4e..2addac3 100644 --- a/sequencer/sequencer.go +++ b/sequencer/sequencer.go @@ -51,6 +51,8 @@ type Sequencer interface { AddStep(track int) RemoveStep(track int) ToggleStep(track, step int) + CopyStep(track, step int) + PasteStep(track, step int) Tempo() float64 SetTempo(tempo float64) Reset() @@ -72,6 +74,8 @@ type sequencer struct { isPlaying bool isFirstTick bool + + stepClipboard step } // New creates a new sequencer. It also creates new tracks and calls the @@ -245,6 +249,69 @@ func (s *sequencer) ToggleStep(track, step int) { s.tracks[track].steps[step].clearParameters() } +// CopyStep copies a step to the 'clipboard' step. +// TODO: Double check this, had some AI help with pointers +func (s *sequencer) CopyStep(track, srcStep int) { + if track < 0 || track >= len(s.tracks) || srcStep < 0 || srcStep >= len(s.tracks[track].steps) { + return // Out of bounds, do nothing + } + + originalStep := s.tracks[track].steps[srcStep] + + // Create a deep copy of the step + s.stepClipboard = step{ + midi: originalStep.midi, // Assuming midi.Midi is safe to copy directly + track: nil, // We don't want to keep a reference to the original track + position: originalStep.position, + active: originalStep.active, + triggered: false, // Reset triggered state for the copy + controls: make(map[int]*midi.Control), + length: copyIntPtr(originalStep.length), + chord: copyUint8SlicePtr(originalStep.chord), + velocity: copyUint8Ptr(originalStep.velocity), + probability: copyIntPtr(originalStep.probability), + offset: originalStep.offset, + } + + // Deep copy the controls + for k, v := range originalStep.controls { + controlCopy := *v // Assuming midi.Control is safe to copy directly + s.stepClipboard.controls[k] = &controlCopy + } +} + +// PasteStep pastes a clipboard step into a destination step +// TODO: Double check this, had some AI help with pointers +func (s *sequencer) PasteStep(track, dstStep int) { + if track < 0 || track >= len(s.tracks) || dstStep < 0 || dstStep >= len(s.tracks[track].steps) { + return // Out of bounds, do nothing + } + + // Create a deep copy of the clipboard + newStep := step{ + midi: s.stepClipboard.midi, // Assuming midi.Midi is safe to copy directly + track: s.tracks[track], // Set the correct track + position: dstStep, // Set the correct position + active: s.stepClipboard.active, + triggered: false, // Reset triggered state + controls: make(map[int]*midi.Control), + length: copyIntPtr(s.stepClipboard.length), + chord: copyUint8SlicePtr(s.stepClipboard.chord), + velocity: copyUint8Ptr(s.stepClipboard.velocity), + probability: copyIntPtr(s.stepClipboard.probability), + offset: s.stepClipboard.offset, + } + + // Deep copy the controls + for k, v := range s.stepClipboard.controls { + controlCopy := *v // Assuming midi.Control is safe to copy directly + newStep.controls[k] = &controlCopy + } + + // Replace the step in the track + s.tracks[track].steps[dstStep] = &newStep +} + func (s *sequencer) start() { // Each time the clock ticks, we call the sequencer tick method that // basically makes every track move forward in time. @@ -280,3 +347,31 @@ func (s sequencer) sendControls() { track.sendControls() } } + +// Helper functions for deep copying pointers +func copyIntPtr(ptr *int) *int { + if ptr == nil { + return nil + } + copy := *ptr + return © +} + +func copyUint8Ptr(ptr *uint8) *uint8 { + if ptr == nil { + return nil + } + copy := *ptr + return © +} + +func copyUint8SlicePtr(ptr *[]uint8) *[]uint8 { + if ptr == nil { + return nil + } + copy := make([]uint8, len(*ptr)) + for i, v := range *ptr { + copy[i] = v + } + return © +} diff --git a/ui/keymap.go b/ui/keymap.go index 5a48457..542fa4a 100644 --- a/ui/keymap.go +++ b/ui/keymap.go @@ -40,6 +40,8 @@ type keyMap struct { AddParam key.Binding RemoveParam key.Binding + CopyParams key.Binding + PasteParams key.Binding Validate key.Binding Left key.Binding @@ -142,6 +144,14 @@ func newKeyMap(keys filesystem.KeyMap) keyMap { key.WithKeys(keys.RemoveParam), key.WithHelp(keys.RemoveParam, "remove midi control"), ), + CopyParams: key.NewBinding( + key.WithKeys(keys.CopyParams), + key.WithHelp(keys.CopyParams, "copy active step parameters"), + ), + PasteParams: key.NewBinding( + key.WithKeys(keys.PasteParams), + key.WithHelp(keys.PasteParams, "paste copied parameters into active step"), + ), Validate: key.NewBinding( key.WithKeys(keys.Validate), key.WithHelp(keys.Validate, "validate selection"), @@ -167,8 +177,8 @@ func newKeyMap(keys filesystem.KeyMap) keyMap { key.WithHelp(keys.Help, "toggle help"), ), Quit: key.NewBinding( - key.WithKeys("ctrl+c", "esc"), - key.WithHelp("ctrl+c/esc", "quit"), + key.WithKeys("ctrl+q", "esc"), + key.WithHelp("ctrl+q/esc", "quit"), ), } for i, k := range keys.Steps { diff --git a/ui/ui.go b/ui/ui.go index 9975718..f71700a 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -183,6 +183,20 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.updateParams() return m, nil + case key.Matches(msg, m.keymap.CopyParams): + m.seq.CopyStep(m.activeTrack, m.activeStep) + m.mode = stepMode + m.stepModeTimer = 0 + m.updateParams() + return m, nil + + case key.Matches(msg, m.keymap.PasteParams): + m.seq.PasteStep(m.activeTrack, m.activeStep) + m.mode = stepMode + m.stepModeTimer = 0 + m.updateParams() + return m, nil + case key.Matches(msg, m.keymap.StepToggle): number := m.keymap.StepToggleIndex[msg.String()] if m.mode == patternMode { From 21ee14df3a4583d3fec4c0d037ef52d03f32461d Mon Sep 17 00:00:00 2001 From: Alexis Boni Date: Tue, 13 Aug 2024 10:42:16 -0300 Subject: [PATCH 2/5] Rename Copy/Paste Params to Copy/Paste Step --- filesystem/keymap.go | 20 ++++++++++---------- sequencer/sequencer.go | 37 ++++--------------------------------- ui/keymap.go | 20 ++++++++++---------- ui/ui.go | 4 ++-- 4 files changed, 26 insertions(+), 55 deletions(-) diff --git a/filesystem/keymap.go b/filesystem/keymap.go index db23138..f3b951f 100644 --- a/filesystem/keymap.go +++ b/filesystem/keymap.go @@ -12,14 +12,14 @@ type KeyMap struct { RemoveTrack string `json:"remove_track"` AddStep string `json:"add_step"` RemoveStep string `json:"remove_step"` + CopyStep string `json:"copy_step"` + PasteStep string `json:"paste_step"` PageUp string `json:"page_up"` PageDown string `json:"page_down"` TempoUp string `json:"tempo_up"` TempoDown string `json:"tempo_down"` AddParam string `json:"add_param"` RemoveParam string `json:"remove_param"` - CopyParams string `json:"copy_params"` - PasteParams string `json:"paste_params"` Validate string `json:"validate"` Left string `json:"left"` Right string `json:"right"` @@ -42,14 +42,14 @@ func NewDefaultAzertyKeyMap() KeyMap { RemoveTrack: ")", AddStep: "+", RemoveStep: "°", + CopyStep: "ctrl+c", + PasteStep: "ctrl+v", PageUp: "p", PageDown: "m", TempoUp: "shift+up", TempoDown: "shift+down", AddParam: "ctrl+up", RemoveParam: "ctrl+down", - CopyParams: "ctrl+c", - PasteParams: "ctrl+v", Validate: "enter", Left: "left", Right: "right", @@ -73,14 +73,14 @@ func NewDefaultAzertyMacKeyMap() KeyMap { RemoveTrack: ")", AddStep: "_", RemoveStep: "°", + CopyStep: "ctrl+c", + PasteStep: "ctrl+v", PageUp: "p", PageDown: "m", TempoUp: "shift+up", TempoDown: "shift+down", AddParam: "ctrl+up", RemoveParam: "ctrl+down", - CopyParams: "ctrl+c", - PasteParams: "ctrl+v", Validate: "enter", Left: "left", Right: "right", @@ -103,14 +103,14 @@ func NewDefaultQwertyKeyMap() KeyMap { RemoveTrack: "-", AddStep: "+", RemoveStep: "_", + CopyStep: "ctrl+c", + PasteStep: "ctrl+v", PageUp: "p", PageDown: ";", TempoUp: "shift+up", TempoDown: "shift+down", AddParam: "ctrl+up", RemoveParam: "ctrl+down", - CopyParams: "ctrl+c", - PasteParams: "ctrl+v", Validate: "enter", Left: "left", Right: "right", @@ -134,14 +134,14 @@ func NewDefaultQwertyMacKeyMap() KeyMap { RemoveTrack: "-", AddStep: "+", RemoveStep: "_", + CopyStep: "ctrl+c", + PasteStep: "ctrl+v", PageUp: "p", PageDown: ";", TempoUp: "shift+up", TempoDown: "shift+down", AddParam: "ctrl+up", RemoveParam: "ctrl+down", - CopyParams: "ctrl+c", - PasteParams: "ctrl+v", Validate: "enter", Left: "left", Right: "right", diff --git a/sequencer/sequencer.go b/sequencer/sequencer.go index 2addac3..a3fdda1 100644 --- a/sequencer/sequencer.go +++ b/sequencer/sequencer.go @@ -281,7 +281,6 @@ func (s *sequencer) CopyStep(track, srcStep int) { } // PasteStep pastes a clipboard step into a destination step -// TODO: Double check this, had some AI help with pointers func (s *sequencer) PasteStep(track, dstStep int) { if track < 0 || track >= len(s.tracks) || dstStep < 0 || dstStep >= len(s.tracks[track].steps) { return // Out of bounds, do nothing @@ -289,9 +288,9 @@ func (s *sequencer) PasteStep(track, dstStep int) { // Create a deep copy of the clipboard newStep := step{ - midi: s.stepClipboard.midi, // Assuming midi.Midi is safe to copy directly - track: s.tracks[track], // Set the correct track - position: dstStep, // Set the correct position + midi: s.stepClipboard.midi, + track: s.tracks[track], + position: dstStep, active: s.stepClipboard.active, triggered: false, // Reset triggered state controls: make(map[int]*midi.Control), @@ -304,7 +303,7 @@ func (s *sequencer) PasteStep(track, dstStep int) { // Deep copy the controls for k, v := range s.stepClipboard.controls { - controlCopy := *v // Assuming midi.Control is safe to copy directly + controlCopy := *v newStep.controls[k] = &controlCopy } @@ -347,31 +346,3 @@ func (s sequencer) sendControls() { track.sendControls() } } - -// Helper functions for deep copying pointers -func copyIntPtr(ptr *int) *int { - if ptr == nil { - return nil - } - copy := *ptr - return © -} - -func copyUint8Ptr(ptr *uint8) *uint8 { - if ptr == nil { - return nil - } - copy := *ptr - return © -} - -func copyUint8SlicePtr(ptr *[]uint8) *[]uint8 { - if ptr == nil { - return nil - } - copy := make([]uint8, len(*ptr)) - for i, v := range *ptr { - copy[i] = v - } - return © -} diff --git a/ui/keymap.go b/ui/keymap.go index 542fa4a..7b9ad85 100644 --- a/ui/keymap.go +++ b/ui/keymap.go @@ -19,6 +19,8 @@ type keyMap struct { AddStep key.Binding RemoveStep key.Binding + CopyStep key.Binding + PasteStep key.Binding StepIndex map[string]int Step key.Binding @@ -40,8 +42,6 @@ type keyMap struct { AddParam key.Binding RemoveParam key.Binding - CopyParams key.Binding - PasteParams key.Binding Validate key.Binding Left key.Binding @@ -100,6 +100,14 @@ func newKeyMap(keys filesystem.KeyMap) keyMap { key.WithKeys(keys.RemoveStep), key.WithHelp(keys.RemoveStep, "remove step"), ), + CopyStep: key.NewBinding( + key.WithKeys(keys.CopyStep), + key.WithHelp(keys.CopyStep, "copy active step parameters"), + ), + PasteStep: key.NewBinding( + key.WithKeys(keys.PasteStep), + key.WithHelp(keys.PasteStep, "paste copied parameters into active step"), + ), StepIndex: map[string]int{}, Step: key.NewBinding( key.WithKeys(keys.Steps[:]...), @@ -144,14 +152,6 @@ func newKeyMap(keys filesystem.KeyMap) keyMap { key.WithKeys(keys.RemoveParam), key.WithHelp(keys.RemoveParam, "remove midi control"), ), - CopyParams: key.NewBinding( - key.WithKeys(keys.CopyParams), - key.WithHelp(keys.CopyParams, "copy active step parameters"), - ), - PasteParams: key.NewBinding( - key.WithKeys(keys.PasteParams), - key.WithHelp(keys.PasteParams, "paste copied parameters into active step"), - ), Validate: key.NewBinding( key.WithKeys(keys.Validate), key.WithHelp(keys.Validate, "validate selection"), diff --git a/ui/ui.go b/ui/ui.go index f71700a..cc11702 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -183,14 +183,14 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.updateParams() return m, nil - case key.Matches(msg, m.keymap.CopyParams): + case key.Matches(msg, m.keymap.CopyStep): m.seq.CopyStep(m.activeTrack, m.activeStep) m.mode = stepMode m.stepModeTimer = 0 m.updateParams() return m, nil - case key.Matches(msg, m.keymap.PasteParams): + case key.Matches(msg, m.keymap.PasteStep): m.seq.PasteStep(m.activeTrack, m.activeStep) m.mode = stepMode m.stepModeTimer = 0 From 110097dab36f31b19881d27b5195970c4745c0d6 Mon Sep 17 00:00:00 2001 From: Alexis Boni Date: Tue, 13 Aug 2024 10:42:51 -0300 Subject: [PATCH 3/5] Move sequencer helper functions to helper.go --- sequencer/helper.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 sequencer/helper.go diff --git a/sequencer/helper.go b/sequencer/helper.go new file mode 100644 index 0000000..d598159 --- /dev/null +++ b/sequencer/helper.go @@ -0,0 +1,29 @@ +package sequencer + +// Helper functions for deep copying pointers +func copyIntPtr(ptr *int) *int { + if ptr == nil { + return nil + } + copy := *ptr + return © +} + +func copyUint8Ptr(ptr *uint8) *uint8 { + if ptr == nil { + return nil + } + copy := *ptr + return © +} + +func copyUint8SlicePtr(ptr *[]uint8) *[]uint8 { + if ptr == nil { + return nil + } + copy := make([]uint8, len(*ptr)) + for i, v := range *ptr { + copy[i] = v + } + return © +} From 1f40f7bf5e356d8ce7398a7c1b5e1371962ab5f2 Mon Sep 17 00:00:00 2001 From: Xavier Godart Date: Tue, 13 Aug 2024 17:54:45 +0200 Subject: [PATCH 4/5] Remove useless comment --- sequencer/sequencer.go | 1 - 1 file changed, 1 deletion(-) diff --git a/sequencer/sequencer.go b/sequencer/sequencer.go index a3fdda1..6e80298 100644 --- a/sequencer/sequencer.go +++ b/sequencer/sequencer.go @@ -250,7 +250,6 @@ func (s *sequencer) ToggleStep(track, step int) { } // CopyStep copies a step to the 'clipboard' step. -// TODO: Double check this, had some AI help with pointers func (s *sequencer) CopyStep(track, srcStep int) { if track < 0 || track >= len(s.tracks) || srcStep < 0 || srcStep >= len(s.tracks[track].steps) { return // Out of bounds, do nothing From 7f2862d3e1e3bedff699e3be49355333ecc0079b Mon Sep 17 00:00:00 2001 From: Xavier Godart Date: Tue, 13 Aug 2024 17:54:57 +0200 Subject: [PATCH 5/5] Remove useless comment --- sequencer/sequencer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sequencer/sequencer.go b/sequencer/sequencer.go index 6e80298..c5c13cd 100644 --- a/sequencer/sequencer.go +++ b/sequencer/sequencer.go @@ -259,7 +259,7 @@ func (s *sequencer) CopyStep(track, srcStep int) { // Create a deep copy of the step s.stepClipboard = step{ - midi: originalStep.midi, // Assuming midi.Midi is safe to copy directly + midi: originalStep.midi, track: nil, // We don't want to keep a reference to the original track position: originalStep.position, active: originalStep.active,