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:
- 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.
- 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))