Skip to content

Commit

Permalink
feat: copy/paste step (#17)
Browse files Browse the repository at this point in the history
* feat: copy/paste step
resolves #7

* Rename Copy/Paste Params to Copy/Paste Step

* Move sequencer helper functions to helper.go

* Remove useless comment

* Remove useless comment

---------

Co-authored-by: Xavier Godart <[email protected]>
  • Loading branch information
ajboni and xaviergodart authored Aug 13, 2024
1 parent 20e5ae6 commit 0aa7a6e
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 2 deletions.
10 changes: 10 additions & 0 deletions filesystem/keymap.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ 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"`
PreviousStep string `json:"previous_step"`
NextStep string `json:"next_step"`
PageUp string `json:"page_up"`
Expand Down Expand Up @@ -42,6 +44,8 @@ func NewDefaultAzertyKeyMap() KeyMap {
RemoveTrack: ")",
AddStep: "+",
RemoveStep: "°",
CopyStep: "ctrl+c",
PasteStep: "ctrl+v",
PreviousStep: ",",
NextStep: ";",
PageUp: "p",
Expand Down Expand Up @@ -73,6 +77,8 @@ func NewDefaultAzertyMacKeyMap() KeyMap {
RemoveTrack: ")",
AddStep: "_",
RemoveStep: "°",
CopyStep: "ctrl+c",
PasteStep: "ctrl+v",
PreviousStep: ",",
NextStep: ";",
PageUp: "p",
Expand Down Expand Up @@ -103,6 +109,8 @@ func NewDefaultQwertyKeyMap() KeyMap {
RemoveTrack: "-",
AddStep: "+",
RemoveStep: "_",
CopyStep: "ctrl+c",
PasteStep: "ctrl+v",
PreviousStep: ",",
NextStep: ".",
PageUp: "p",
Expand Down Expand Up @@ -134,6 +142,8 @@ func NewDefaultQwertyMacKeyMap() KeyMap {
RemoveTrack: "-",
AddStep: "+",
RemoveStep: "_",
CopyStep: "ctrl+c",
PasteStep: "ctrl+v",
PreviousStep: ",",
NextStep: ".",
PageUp: "p",
Expand Down
29 changes: 29 additions & 0 deletions sequencer/helper.go
Original file line number Diff line number Diff line change
@@ -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 &copy
}

func copyUint8Ptr(ptr *uint8) *uint8 {
if ptr == nil {
return nil
}
copy := *ptr
return &copy
}

func copyUint8SlicePtr(ptr *[]uint8) *[]uint8 {
if ptr == nil {
return nil
}
copy := make([]uint8, len(*ptr))
for i, v := range *ptr {
copy[i] = v
}
return &copy
}
65 changes: 65 additions & 0 deletions sequencer/sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -245,6 +249,67 @@ func (s *sequencer) ToggleStep(track, step int) {
s.tracks[track].steps[step].clearParameters()
}

// CopyStep copies a step to the 'clipboard' step.
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,
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
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,
track: s.tracks[track],
position: dstStep,
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
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.
Expand Down
14 changes: 12 additions & 2 deletions ui/keymap.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type keyMap struct {

AddStep key.Binding
RemoveStep key.Binding
CopyStep key.Binding
PasteStep key.Binding

NextStep key.Binding
PreviousStep key.Binding
Expand Down Expand Up @@ -101,6 +103,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"),
),
PreviousStep: key.NewBinding(
key.WithKeys(keys.PreviousStep),
key.WithHelp(keys.PreviousStep, "select previous step"),
Expand Down Expand Up @@ -178,8 +188,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 {
Expand Down
14 changes: 14 additions & 0 deletions ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,20 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.updateParams()
return m, nil

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.PasteStep):
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 {
Expand Down

0 comments on commit 0aa7a6e

Please sign in to comment.