Resolving Notifier Feedbacks

I have a problem with a notifier feedback hanging Renoise (and eventually making my computer run out of memory).

Basically I need to remove and later add a notifier that is referring to the function it’s in. This is due to being unable to manipulate some data while a notifier is attached.

Is there a common way to solve a problem like this? I have tried switching between two sets of identical functions without any success.

Can you post some code? I’m not good with verbal abstractions.

Here is part of the code (it might contain other errors). It’s pool_patterndata_update() that contains the feedback.

  
local previously_observed = nil  
  
function pool_patterndata_update()  
 if previously_observed then release_patterntrack_notifier() end  
-- stuff happening in between here  
 if members["p" .. renoise.song().selected_pattern_index .. "t" .. renoise.song().selected_track_index] then  
 renoise.song().patterns[renoise.song().selected_pattern_index]:add_line_notifier(pool_patterndata_update)  
 print("attaching pattern " .. renoise.song().selected_pattern_index)  
 previously_observed = renoise.song().selected_pattern_index  
 else  
 previously_observed = nil  
 print("no pattern attached")  
 end  
end  
  
function attach_patterntrack_notifier()  
 if members["p" .. renoise.song().selected_pattern_index .. "t" .. renoise.song().selected_track_index] then  
 renoise.song().patterns[renoise.song().selected_pattern_index]:add_line_notifier(pool_patterndata_update)  
 print("attaching pattern " .. renoise.song().selected_pattern_index)  
 previously_observed = renoise.song().selected_pattern_index  
 else  
 previously_observed = nil  
 print("no pattern attached")  
 end  
end  
  
function release_patterntrack_notifier()  
 if previously_observed then  
 if renoise.song().patterns[previously_observed]:has_line_notifier(pool_patterndata_update) then  
 renoise.song().patterns[previously_observed]:remove_line_notifier(pool_patterndata_update)  
 print("releasing pattern " .. previously_observed)  
 end  
 else  
 print("nothing released")  
 end  
end  
  
function track_index_change()  
 release_patterntrack_notifier()  
 attach_patterntrack_notifier()  
end  
  
function init_pos_notifier()  
 renoise.song().selected_track_index_observable:add_notifier(track_index_change)  
 renoise.song().selected_pattern_index_observable:add_notifier(track_index_change)  
end  
  
init_pos_notifier()  
  

I see is that you are using a a global “previously_observed” that could change before the observer does anything. So you could, in theory, be halfway through a function and previously_observed changes from 1 to 2. I’m not sure this is the best approach. Maybe stack everything in a table and do a foreach() instead?

Another suggestion is to get ideas by looking at my grid pie’s Bootsauce routine.

-=-=-

Some things to look for:

  • Encapsulate your “…_observable:add_notifier()” in “…_observable:has_notifier()” This is done at “init” and “teardown”, not on the fly
    .

  • The “function tracks_changed(notification)” and “function idler(notificiation)” and friends has “notification” as a magic/auto-passed parameter. Maybe you could use this as extra info to make a conditional decision?

  • Most of the work is done in “(renoise.tool().app_idle_observable:has_notifier(idler))”. Every time Renoise has a spare cycle I do some work. I don’t try dynamically attach notifiers to everything and juggle conflicts.

-=-=-

From what I see you are trying to chain together a bunch of Pattern/Tracks so that, when you change something, the change is propagated. Personally, I would take a different approach and do most of the work in an idler().

I would create a table that holds info. How this table is created, naming conventions, nesting, and whatever, I leave it up to you. Example:

  
my_chain["p1t1"] = false  
my_chain["p2t3"] = false  
my_chain["p10t4"] = false  
  

This is a table of linked patterns. False means no change. If something changes, i’d do:

  
if my_chain["p10t4"] ~= nil then my_chain[p10t4] = true end  
  

Then in my idler() routine:

  
-- Look for "true" in my_chain  
local is_changed = table.find(my_chain, true)   
  
if (is_changed ~= nil) then  
 -- Parse is_changed, which contains 'p10t4'  
 local p1 = string.match(is_changed, "p(%d+)")  
 local t1 = string.match(is_changed, "t(%d+)")   
  
 -- Propagate changes  
 for k,v in pairs(my_chain) do  
 local p2 = string.match(k, "p(%d+)")  
 local t2 = string.match(k, "t(%d+)")   
 if p2 ~= p1 and t2 ~= t1 then   
 renoise.song().patterns[p2].tracks[t2]:copy_from(renoise.song().patterns[p1].tracks[t1])  
 end  
 my_chain[k] = false  
 end  
  
end  
  

And so on.

Anyway. not trying to say my way is correct. Maybe this will make things worse. Just throwing out ideas.

PS: If you do go down this road, please read Taktik’s speed-up tip.

Probably don’t want to type it out like that, I just did because I’m lazy.

Good tips all of them. But I still can’t see how to solve the notifier loop. Maybe I am wrong saying that a notifier referring to its own function will always cause feedback, and your advice will help me?

Click to view contents

Btw, here is where I set up tables and stuff, incase of interest. It might be bad to to a whole scanning of the song on every pool update instead of insertion/removal in table - but I don’t wanna use lots of notifiers and worry about bugs and workarounds.

  
local pools = { }  
local members = { }  
  
--- tables and actions ---  
  
function patterns_first_sequence(pattern)  
 for sequence in ipairs(renoise.song().sequencer.pattern_sequence) do  
 if renoise.song().sequencer.pattern_sequence[sequence] == pattern then  
 return sequence  
 end  
 end  
 return "unused"  
end  
  
function update_track_pool_tables()  
 table.clear(pools)  
 table.clear(members)  
 for pattern in ipairs(renoise.song().patterns) do  
 for track in ipairs(renoise.song().patterns[pattern].tracks) do  
 if renoise.song().patterns[pattern].tracks[track].color then  
 local color = renoise.song().patterns[pattern].tracks[track].color  
 local pool = "r" .. color[1] .. "g" .. color[2] .. "b" .. color[3]  
  
 if not pools[pool] then pools[pool] = { id = pool, name = "First instance: sequence " .. patterns_first_sequence(pattern) .. ", Track " .. track } end  
 if not pools[pool].members then pools[pool].members = { } end  
 table.insert(pools[pool].members, { pattern = pattern, track = track })  
  
 local member = "p" .. pattern .. "t" .. track  
 members[member] = pool  
 end  
 end  
 end  
end  
  
update_track_pool_tables()  
  
function unbind_member(pattern, track)  
 renoise.song().selected_pattern_track.color = nil  
 pool_is_changing()  
end  
  
function bind_member(pattern, track, pool)  
 renoise.song().patterns[pattern].tracks[track].color = renoise.song().patterns[pools[pool].members[1].pattern].tracks[pools[pool].members[1].track].color  
 pool_is_changing()  
end  
  
  
  

Right, it was more advice on how to to take a different approach so that there would be no possibility of feedback.

Also, I can’t get the code snipper you posted to create feedback. I modified it a bit so that “members” exists, but there’s no feedback for me. (i’m expecting one of the print statements to loop infinitely?) Problem isn’t in the code you posted.

[details=“Click to view contents”] ```

local previously_observed = nil

local members = { }
members [“p1t1”] = true
members [“p1t2”] = true
members [“p1t3”] = true
members [“p1t4”] = true
members [“p1t5”] = true
members [“p2t2”] = true
members [“p2t3”] = true
members [“p2t4”] = true
members [“p2t5”] = true

function pool_patterndata_update()
if previously_observed then release_patterntrack_notifier() end
– stuff happening in between here
if members[“p” … renoise.song().selected_pattern_index … “t” … renoise.song().selected_track_index] then
renoise.song().patterns[renoise.song().selected_pattern_index]:add_line_notifier(pool_patterndata_update)
print("attaching pattern " … renoise.song().selected_pattern_index)
previously_observed = renoise.song().selected_pattern_index
else
previously_observed = nil
print(“no pattern attached”)
end
end

function attach_patterntrack_notifier()
if members[“p” … renoise.song().selected_pattern_index … “t” … renoise.song().selected_track_index] then
renoise.song().patterns[renoise.song().selected_pattern_index]:add_line_notifier(pool_patterndata_update)
print("attaching pattern " … renoise.song().selected_pattern_index)
previously_observed = renoise.song().selected_pattern_index
else
previously_observed = nil
print(“no pattern attached”)
end
end

function release_patterntrack_notifier()
if previously_observed then
if renoise.song().patterns[previously_observed]:has_line_notifier(pool_patterndata_update) then
renoise.song().patterns[previously_observed]:remove_line_notifier(pool_patterndata_update)
print("releasing pattern " … previously_observed)
end
else
print(“nothing released”)
end
end

function track_index_change()
release_patterntrack_notifier()
attach_patterntrack_notifier()
end

function init_pos_notifier()
renoise.song().selected_track_index_observable:add_notifier(track_index_change)
renoise.song().selected_pattern_index_observable:add_notifier(track_index_change)
print(“OK!”)
end

init_pos_notifier()

  
Of course, if I run this code 10 times. It will create 10 different instances all fighting each other. But that isn't feedback. Just sloppy "takedown".

PS: I’ve had “notifer feedback” in the past, e.g. ‘notifier feedback loop detected. do not change values you are listening to within your notifiers’

It had to do with the GUI widget I chose. everytime I would change the checkbox, it would launch adjust_grid() I fixed it by changing the GUI.

Conner_bw,

Oh. You have to try trigger the function by entering pattern data in a custom colored slot. It will hang. (right?)

Well, I don’t know. Your code snippets are not complete. I can’t generate any feedback. Please repost some working code with steps to reproduce if you really want me to see the error.

I can save you some time though, The trick to avoid feedback loops is to not cause them. ;) See my previous post.

Good luck.