Prevent a tool from accumulating changes in undo / redo

Is there a way to prevent undo / redo from accumulating changes made from a tool?

For example, if a tool changes the transposition value of the selected sample (Sample Properties) only once through the API, a step will be saved in the undo / redo. If you change it 100 times in a row, there will be 100 steps in undo / redo.

How to prevent this step from being saved? Is there any way to do it?

1 Like

Thanks for finding that @EatMe !

I have discovered that the best way to avoid abusing the undo steps, and is nothing more than avoiding timers to update or change values. Using custom timers is a mistake. If possible, it is much better to use the notifiers, in this case.

1 Like

But… If you hammer the GUI with a lot of changes, a timer (and deletion of last timer) is required, or the GUI will start to stutter. For example, if your algorithm many times updates the status bar, you will have to ensure it only does so a bunch of times per second. In javascript there often is a real per-frame function used, to draw anything onto the GUI. A timer can simulate this behaviour.

I think the undo catching for scripts still is not optimal, it also should catch timers.

I do not know if Renoise years ago behaved as in the current version.

The issue here is how Renoise saves the steps in the undo tail. The GUI you use doesn’t matter here. But if you are in these two cases to change property values of the song:

  1. You use a timer. If you repeat several times (depending on the duration of the timer) an operation to change a specific value of some property, it will be saved as a separate step. This can massively fill the tail of the undo.
  2. You use an observable. Here it seems that the notifier has these things more controlled. You can order the change of a value several times in a row, and it will only be saved as a single step, so using CTRL+Z or CTRL+Y does not become a problem.

Investigating this, the API only allows you to change the description of the step, nothing more. So you have to be careful when using one thing or the other depending on what.

Related documentation:

Renoise.Song.API.lua

-- Test if something in the song can be undone.
renoise.song():can_undo() --> [boolean]

-- Undo the last performed action. Will do nothing if nothing can be undone.
renoise.song():undo()

-- Test if something in the song can be redone.
renoise.song():can_redo() --> [boolean]

-- Redo a previously undo action. Will do nothing if nothing can be redone.
renoise.song():redo()

-- When modifying the song, Renoise will automatically add descriptions for
-- undo/redo by looking at what first changed (a track was inserted, a pattern
-- line changed, and so on). When the song is changed from an action in a menu
-- entry callback, the menu entry's label will automatically be used for the
-- undo description.
-- If those auto-generated names do not work for you, or you want  to use
-- something more descriptive, you can (!before changing anything in the song!)
-- give your changes a custom undo description (like: "Generate Synth Sample")
renoise.song():describe_undo(description)

Renoise.ScriptingTool.API.lua

--[[

Register a timer function or table with a function and context (a method)
that periodically gets called by the app_idle_observable for your tool.

Modal dialogs will avoid that timers are called. To create a one-shot timer,
simply call remove_timer at the end of your timer function. Timer_interval_in_ms
must be > 0. The exact interval your function is called will vary
a bit, depending on workload; e.g. when enough CPU time is available the
rounding error will be around +/- 5 ms.

]]

-- Returns true when the given function or method was registered as a timer.
renoise.tool():has_timer(function or {object, function} or {function, object}) --> [boolean]

-- Add a new timer as described above.
renoise.tool():add_timer(function or {object, function} or {function, object}, timer_interval_in_ms)

-- Remove a previously registered timer.
renoise.tool():remove_timer(timer_func)

Renoise.Document.API.lua

-- Checks if the given function, method was already registered as notifier.
observable:has_notifier(function or (object, function) or (function, object)) --> [boolean]

-- Register a function or method as a notifier, which will be called as soon as
-- the observable's value changed.
observable:add_notifier(function or (object, function) or (function, object))

-- Unregister a previously registered notifier. When only passing an object to
-- remove_notifier, all notifier functions that match the given object will be
-- removed; a.k.a. all methods of the given object are removed. They will not
-- fire errors when none are attached.
observable:remove_notifier(function or (object, function) or (function, object) or (object))