It seems the Song.tracks_observable fires its notifier before the rest of the song catches up with the update?
Put this in a tool
Try to insert a new track to trigger the notifier
Even though #song.tracks is already the new length, accessing a pattern track with that fails
-- this function is expected to always succeed at accessing the last pattern track
local function print_last_pattern_track()
local song = renoise.song()
print("current track count " .. #song.tracks)
local pattern_track = song:pattern(1):track(#song.tracks)
rprint(pattern_track)
end
-- initially this works as expected
print_last_pattern_track()
-- listening to the layout changes on tracks
renoise.song().tracks_observable:add_notifier(function()
print("tracks changed!")
-- ERROR when trying to access last pattern after a change has occured
print_last_pattern_track()
end)
--------------------------------------------------------------------------------
-- OneShotIdle Class
--------------------------------------------------------------------------------
-- delay a function call by the given amount of time into a tools idle notifier
--
-- for example: ´OneShotIdleNotifier(100, my_callback, some_arg, another_arg)´
-- calls "my_callback" with the given arguments with a delay of about 100 ms
-- a delay of 0 will call the callback "as soon as possible" in idle, but never
-- immediately
class "OneShotIdleNotifier"
function OneShotIdleNotifier:__init(delay_in_ms, callback, ...)
assert(type(delay_in_ms) == "number" and delay_in_ms >= 0.0)
assert(type(callback) == "function")
self._callback = callback
self._args = {...}
self._invoke_time = os.clock() + delay_in_ms / 1000
renoise.tool().app_idle_observable:add_notifier(self, self.__on_idle)
end
function OneShotIdleNotifier:__on_idle()
if (os.clock() >= self._invoke_time) then
renoise.tool().app_idle_observable:remove_notifier(self, self.__on_idle)
self._callback(unpack(self._args))
end
end
Yeah, I’ve implemented a similar workaround for now as I already had an idle loop running with one shot tasks, but this indeed feels evil and counter-intuitive. If for some reason, the notifier still cannot be fixed, I’d make a note about this behaviour in the API reference.
This indeed isn’t nice, but apart from making all notifiers in the Lua API ‘lazy’ (so they fire at the end of batch updates), which will cause other problems, I see no other way to get this fixed.
See Conner’s linked topic for my old explanation why this behaves as it does now. This is still true, and exactly the problem.
I see, for the sake of recording this quirk into the guides/docs, what other notifiers have similar gotchas? This was the only one I’ve ran into that resulted in an inconsistency in the lua representation so far.
In theory, all notifiers that cannot be applied atomically, which are all operations that may change multiple components or properties of the song within a single user action.
Changing tracks (as seen here), which inserts tracks into the song, but also in all patterns and may update the selected track too.
Changing instruments, which may update the selected instrument.
Changing pattern sequence, which may update sequence loops and the selected patterns.
Changing phrase maps, which syncs selected phrases indices.
Basically most lists in the document tree which contain complex sub documents.
I once tried sending out notifications to the Lua API in a batch, after all operations finished, rather than sending them directly when they happen. But this will cause notifications to get mixed up, some arriving instantly and others being delayed. And if you delay them all, you may end up receiving notifications for objects that have already been removed or are no longer active.
The real fix in the code above, would have been attaching notifiers to song:pattern(1):tracks instead of song:tracks. Not assuming that the document tree changes atomically.