markusschloesser / MackieC4_P3

A Mackie C4 Midi Remote Script for Ableton 11
20 stars 2 forks source link

implement undo / redo #40

Closed markusschloesser closed 3 years ago

markusschloesser commented 3 years ago

Implement undo and redo: Device mode:

  1. use SHIFT, needs to be discussed of toggle or hold
  2. when pressing SHIFT, show a different layer on C4 screen with alternative buttons (undo / redo)
  3. show undo, with "action to undo". Undoing is done by pressing corresponding vpot. If nothing to undo, show empty
  4. show redo, with "action to redo". Redoing is done by pressing corresponding vpot. If nothing to redo, show empty
  5. release shift to return to "normal" device view

How? ableton.v2.control_surface.components has a UndoRedoComponent, which can be used like this:

def _create_undo_redo(self):
        self._undo_redo = UndoRedoComponent(name='Undo_Redo',
          is_enabled=False,
          layer=Layer(undo_button=(self._undo_button), redo_button=(self._redo_button)))
BilldarBagdar commented 3 years ago

Yes. All 4 modifier buttons could theoretically expand the meaning of every physical button click or knob twist 4 "modified" ways.

Additionally, it is possible to programmatically detect "click and hold" on the encoder buttons because the C4 sends click = NOTE ON (always velocity 127) and <let go> = NOTE OFF. (Live translates Note Off to Note On at velocity 0?)

And, the "function box" buttons could also be used to expand function-ality :). As I wrote that, it occurred to me the "function" mode currently only has Song Play and Song Stop behavior mapped and I haven't tested recently but I don't think it works; back on point, the "function" mode in combination with the "function box" buttons (Split, Lock, Spot Erase) could be well suited to the possible sequencer mode we talked about. But when I started writing about the "function box" buttons, I was thinking the "Spot Erase" button might be a better place to map a "global" behavior like Undo. If I understand your idea correctly, a user would then hold down "Spot Erase" instead of "Shift", scan the LCDs, and press the encoder button under the "action to undo" shown in the LCD over that encoder. It also seems like your idea here is not to restrict undo/redo to "last physical button click or knob turn" or some distinctly C4 behavior like that, but to more or less implement Live's undo/redo behavior. Did I get all of that correctly? If so, my only editorial comment is; I wonder how useful (comprehensible) 6 + 6 characters of text generally available over each encoder will turn out to be in this scenario. Otherwise, and anyway, it doesn't hurt to try an idea and find out. The worst that will happen if you play your cards right is you learn something, abandon the branch, and move on.

In device mode, a more "performance oriented undo/redo" could be implemented by detecting and interpreting, for example, a knob turn as "send value to Live and record new action"; a knob button click as "jump to (parameter's/encoder's) default value action" (which is also recorded as a 'new action' in terms of); and a knob button click+hold (meaning no "note off" detected within say 1500 milliseconds of detecting "note on") as "jump to (parameter's/encoder's) last "new action" (which is NOT also recorded by the script as a "new" action). If you are familiar with "stacks" in programming terms, it may help to think of the list of such "new actions" I'm describing as a stack, and then this click+hold "undo/redo" would equate to "shifting the stack index pointer" up or down elements of the stack/list. You could, for example, click+hold 3 times in a row to undo the last three "new actions" (off the stack); and then immediately shift+click+hold 3 times in a row to redo those "undone actions" (back onto the stack). However, you could also click+hold 3 times in a row to undo the last three "new actions" (off the stack); and then choose to do 3 more "new actions" which would overwrite the "redo actions" previously sitting in those "stack slots" with the "new actions" (on the stack). This would get tedious though if "every click" of a knob turn was recorded as a "new action", it would take 96 seconds to undo 64 clicks. We would probably need some kind of timer that only records "new actions" after some updated value has been "display stable" for at least 1200 milliseconds or whatever. (would have to be less than the possibly 1500 milli click+hold detection delay time. So a user could always "immediately undo/redo" their last change)

markusschloesser commented 3 years ago

Yes. All 4 modifier buttons could theoretically expand the meaning of every physical button click or knob twist 4 "modified" ways.

Additionally, it is possible to programmatically detect "click and hold" on the encoder buttons because the C4 sends click = NOTE ON (always velocity 127) and <let go> = NOTE OFF. (Live translates Note Off to Note On at velocity 0?)

I know, it's just "what do I prefer" :-)

And, the "function box" buttons could also be used to expand function-ality :). As I wrote that, it occurred to me the "function" mode currently only has Song Play and Song Stop behavior mapped and I haven't tested recently but I don't think it works;

it does work!

back on point, the "function" mode in combination with the "function box" buttons (Split, Lock, Spot Erase) could be well suited to the possible sequencer mode we talked about. But when I started writing about the "function box" buttons, I was thinking the "Spot Erase" button might be a better place to map a "global" behavior like Undo. If I understand your idea correctly, a user would then hold down "Spot Erase" instead of "Shift", scan the LCDs, and press the encoder button under the "action to undo" shown in the LCD over that encoder. It also seems like your idea here is not to restrict undo/redo to "last physical button click or knob turn" or some distinctly C4 behavior like that, but to more or less implement Live's undo/redo behavior. Did I get all of that correctly? If so, my only editorial comment is; I wonder how useful (comprehensible) 6 + 6 characters of text generally available over each encoder will turn out to be in this scenario. Otherwise, and anyway, it doesn't hurt to try an idea and find out. The worst that will happen if you play your cards right is you learn something, abandon the branch, and move on.

In device mode, a more "performance oriented undo/redo" could be implemented by detecting and interpreting, for example, a knob turn as "send value to Live and record new action"; a knob button click as "jump to (parameter's/encoder's) default value action" (which is also recorded as a 'new action' in terms of); and a knob button click+hold (meaning no "note off" detected within say 1500 milliseconds of detecting "note on") as "jump to (parameter's/encoder's) last "new action" (which is NOT also recorded by the script as a "new" action). If you are familiar with "stacks" in programming terms, it may help to think of the list of such "new actions" I'm describing as a stack, and then this click+hold "undo/redo" would equate to "shifting the stack index pointer" up or down elements of the stack/list. You could, for example, click+hold 3 times in a row to undo the last three "new actions" (off the stack); and then immediately shift+click+hold 3 times in a row to redo those "undone actions" (back onto the stack). However, you could also click+hold 3 times in a row to undo the last three "new actions" (off the stack); and then choose to do 3 more "new actions" which would overwrite the "redo actions" previously sitting in those "stack slots" with the "new actions" (on the stack). This would get tedious though if "every click" of a knob turn was recorded as a "new action", it would take 96 seconds to undo 64 clicks. We would probably need some kind of timer that only records "new actions" after some updated value has been "display stable" for at least 1200 milliseconds or whatever. (would have to be less than the possibly 1500 milli click+hold detection delay time. So a user could always "immediately undo/redo" their last change)

there is only ONE undo/redo in Live (and imho a bad one, as there is no proper undo history), and with the utilization of the LOM you don't need to take care of stacks etc (unless you want to implement separate "snapshots" just for C4?). So the ONE undo/redo is valid for ALL actions, but for me the most valuable one having on the c4 would be to undo deviceparam changes. Re timing: i am pretty sure that this is taken care of by using proper LOM objects

BilldarBagdar commented 3 years ago

ok cool. go for it. The two kinds of undo/redo (Live's as you describe and what I described), don't seem incompatible on the surface either.

I was thinking in terms of a "undo/redo history" of just the encoder movements (displayed values) on the C4. A stack of 32 steps (per encoder) would let you, for example, ramp a filter cutoff sweep where the (Low to High) knob-turn sweep is captured as 6, 12, 32 "encoder display values" in the undo history and you could "step the cutoff sweep back down Low" by "undoing" each of those steps in turn.

markusschloesser commented 3 years ago

fixed / done / implemented in "Function" mode (although code could be prettier) in https://github.com/markusschloesser/MackieC4_P3/commit/0ca488a60da9fcba3c00d30826dad279c33f7f2d