Selected_instrument.name_observable notifier removal issue

In my exploration of the LUA world within Renoise, I stumbled upon some functionality I cannot quite comprehend :)/>

I have defined notifiers for selected_instrument.name and selected_instrument_index.
The associated function for each notifier removes both notifiers. To be sure the notifiers sharing the same function wasn’t the issue I have defined separate functions for both notifiers.

When testing this setup I noticed a strange asymmetry:

  • whenever selected_instrument.name notifier is triggered first, both notifiers are successfully removed

  • whenever selected_instrument_index notifier is triggered first, removing the selected_instrument.name notifier leads to the following error:

*** std::logic_error: 'remove notifier: the given notifier function was not added.'  

Perhaps I have overlooked something in my code, but it seems these notifiers are somehow linked. Is this intended functionality?

As a temporary workaround I guess I could guard the notifier removal with a “has_notifier()” call… (haven’t tested this yet).

I’ve attached a Test tool that implements the described functionality.

It contains the following code:

function test()  
 print("add name notifier");  
 renoise.song().selected_instrument.name_observable:add_notifier(trigger_1);  
 print("add index notifier");  
 renoise.song().selected_instrument_index_observable:add_notifier(trigger_2);  
end  
  
function trigger_1()  
 print("rem name notifier");  
 renoise.song().selected_instrument.name_observable:remove_notifier(trigger_1);  
 print("rem index notifier");  
 renoise.song().selected_instrument_index_observable:remove_notifier(trigger_2);  
end  
  
function trigger_2()  
 print("rem name notifier");  
 renoise.song().selected_instrument.name_observable:remove_notifier(trigger_1);  
 print("rem index notifier");  
 renoise.song().selected_instrument_index_observable:remove_notifier(trigger_2);  
end  
  
renoise.tool():add_menu_entry {  
 name = "Main Menu:Tools:Test",  
 invoke = function() test() end   
}  
  

The name notifier is not added at all, it should actually have given an error in both cases, i’m not sure why the removal passes in trigger_1.
If you add sanity checks around the notifiers, you will notice that the name notifier is not being removed in either cases because it does not exist.
If you re-execute test, both notifiers are happily generated again.
If you re-execute test twice, you get an error that the name notifier was already added (while the sanity check returns none) so there is something fishy with the instrument_name notifier here.
Disregard that last remark, i made a copy/paste mistake in the sanity checks.
Still the instrument.name observable is not being removed after it had been generated.

  
function trigger_1()  
 if renoise.song().selected_instrument.name_observable:has_notifier(trigger_1) then   
 print("rem name notifier");  
 renoise.song().selected_instrument.name_observable:remove_notifier(trigger_1);  
 end  
  
 if renoise.song().selected_instrument_index_observable:has_notifier(trigger_2) then  
 print("rem index notifier");  
 renoise.song().selected_instrument_index_observable:remove_notifier(trigger_2);  
 end  
end  
  
  
function trigger_2()  
 if renoise.song().selected_instrument.name_observable:has_notifier(trigger_1) then   
 print("rem name notifier");  
 renoise.song().selected_instrument.name_observable:remove_notifier(trigger_1);  
 end  
  
 if renoise.song().selected_instrument_index_observable:has_notifier(trigger_2) then  
 print("rem index notifier");  
 renoise.song().selected_instrument_index_observable:remove_notifier(trigger_2);  
 end  
end  
  
  
function test()  
 if not renoise.song().selected_instrument_index_observable:has_notifier(trigger_2) then  
 print("add index notifier");  
 renoise.song().selected_instrument_index_observable:add_notifier(trigger_2);  
 end  
  
 if not renoise.song().selected_instrument.name_observable:has_notifier(trigger_1) then   
 print("add name notifier");  
 renoise.song().selected_instrument.name_observable:add_notifier(trigger_1);  
 end  
  
end  
  
  
renoise.tool():add_menu_entry {  
 name = "Main Menu:Tools:Test",  
 invoke = function() test() end   
}  

Use “selected_instrument_observable” instead of “selected_instrument_index_observable”. “selected_instrument_observable” is fired whenever the result of “selected_instrument” changed. “selected_instrument_index_observable” only is fired when the !index! changed.

The name notifier is added. And it also functions (gets triggered). I have added some more debug prints with has_notifier checks to my code and I found the following behavior:

  • both notifiers are successfully added. this is confirmed by the has_notifier checks.
  • both notifiers get triggered on their respective observables

so far so good…
now comes the weird stuff:

  • when the selected_instrument.name_observable notifier gets triggered, both notifiers still exist and can be successfully removed.
  • when the selected_instrument_index_observable notifier gets triggered, only the selected_instrument_index_observable notifier still exists and the selected_instrument.name_observable notifier has mysteriously disappeared! This subsequently leads to an error when trying to remove the .name notifier.

@taktik: using the selected_instrument_observable instead of the selected_instrument_index_observable gives the exact same error. The .name_observable again disappears! So clearly something fishy is going on with the administration of notifiers.

As a temporary solution I can work around this bug by protecting the notifier removals with “has_notifier” calls.


To give you some background for the reason I’m working with these specific observables:

The thing I’m trying to do in my original script with these observables is to detect when the sample recorder has successfully recorded a new piece of audio. Since the LUA API does not yet allow us access to the sample recorder, I had to work around it. According to my reasoning there are 2 cases to consider:

(1): before recording, the selected instrument is the first empty instrument (i.e. the one the sample recorder is going to use to put its newly recorded sample in). This means that there will be no selected_instrument_index_observable trigger (this also doesn’t cause a selected_instrument_observable trigger) when the sample recorder has successfully recorded an instrument. What does change is the instrument name (it becomes “Recorded Sample …”). For this reason I’m monitoring the selected_instrument.name_observable.

(2): in all other cases the sample recorder successfully recording a sample will lead to a trigger of selected_instrument_index_observable (and also to selected_instrument_observable) since the sample recorder will put the sample in a different instrument slot than the one currently selected and by doing so it changes the currently selected instrument. Therefore I’m monitoring the selected_instrument_index_observable (or for this example equivalently the selected_instrument_observable).

So now I have the tools in place to detect successful sample recording (successful in the sense that it has led to a new sample). Now the only thing I have to do is to check whether or not the sample recorder window is visible to arrive at my final conclusions:

  • sample successfully recorded and sample recorder visible → hide sample recorder, new audio is available. script is done.
  • sample not (yet) successfully recorded and sample recorder not visible → user cancelled recording by closing sample recorder, no new audio. script is done

When “trigger_2()” is called, “renoise.song().selected_instrument” already points to a new instrument, so “selected_instrument.name_observable” is a new object which has no notifier attached yet. “renoise.song().selected_instrument_index_observable” does not change with the selected instrument itself, so you don’t get this error here.

If what you want is getting notified of the currently selected instrument’s name, then you can memorize the object you’ve attached to last to detach from it:

  
last_selected_instrument = nil  
  
function name_changed()  
 print(("Instr. name changed to '%s'"):format(last_selected_instrument.name))  
end  
  
function selected_instrument_changed()  
 if last_selected_instrument ~= nil then  
 print("remove name notifier");  
 last_selected_instrument.name_observable:remove_notifier(name_changed);  
 end   
 print("add name notifier");  
 last_selected_instrument = renoise.song().selected_instrument  
 last_selected_instrument.name_observable:add_notifier(name_changed);  
end  
  
-- start listening to name changes  
renoise.song().selected_instrument_observable:add_notifier(  
 selected_instrument_changed);  
  

This will work as a hackaround for the sample recording thing too. Of course would be nice to have a “proper” notifier for this in the API.

Ah… duh! Silly I did not realize this. Thanks for the input Taktik. No bugs here then :)/>

That’d be nice for sure! It would be great if the team could also add API access to the sample recorder fields (like “input device”, sync start/stop mode, etc).