Tool Gives Error On Start Of Renoise (Class Renoisesong)

  
std::logic_error: 'trying to access a nil object of type 'class RenoiseSong'. the object is not or no longer available.'  
stack traceback:  
 [C]: in function 'song'  
 main.lua:3: in main chunk  

Hi, how do I alter this bit of scripting so that it doesn’t shoot an error when Renoise is started for the first time - my whole script and all of it’s shortcuts are lost whenever this error happens:

  
if renoise.app().window.active_middle_frame==0 then  
renoise.song().selected_sample.sample_buffer_observable:add_notifier(function()  
renoise.app().window.active_middle_frame=4  
end)  
end  
  

I tried this, but it just doesn’t work:

if renoise.app().window.active_middle_frame==0 and renoise.song() ~= nil then  
renoise.song().selected_sample.sample_buffer_observable:add_notifier(function()  
renoise.app().window.active_middle_frame=4  
end)  
end  
  

renoise.song() isn’t available before the song is actually started. You need a Renoise.tool().app_new_document_observable notifier that calls a function where you initialise the renoise.song() call. Never call a renoise.song() routine in the header of your script, always from within a function.

More info, read the Scripts\Documentation\Renoise.ScriptingTool.API.lua

So… basically… First I call a app_new_document_observable, and within that observable i call a sample_buffer_observable with an if? Like this:

  
renoise.tool().app_new_document_observable:add_notifier(function()  
renoise.app().window:select_preset(1)  
if renoise.app().window.active_middle_frame==0 and renoise.song().selected_sample.sample_buffer_observable:add_notifier(function() renoise.app().window.active_middle_frame=4 end) then return  
end  

? well, it worked once, but not after i restarted renoise. I’m not sure how to make this work. I guess I shouldn’t use an observable for the sample_buffer, instead some other way of recognizing “no middle frame” & “sample buffer changed” -> “middle frame=4”

After you restarted Renoise or after you saved the script and reloaded it?
Notifiers should also be released
I can’t really guess what is exactly going on without seeing the full source.
But here is a snippet from IT-Aliens experimental live looper tool:

  
renoise.tool().app_new_document_observable:add_notifier(  
 function()  
  
 init()   
  
 if renoise.song() then  
  
 look_for_looper()  
  
 if renoise.song().instruments_observable:has_notifier(look_for_looper) then  
 renoise.song().instruments_observable:remove_notifier(look_for_looper)  
 end  
  
 renoise.song().instruments_observable:add_notifier(look_for_looper)  
  
 end  
  
 end  
)  
  

Check first if a notifiers already exists, using the has_notifier option and create a new one if it isn’t there.

Hi. I’m just trying to use a regular script to also do global things. i.e. it is running constantly, and if it detects that a sample has been loaded, and one is in disk browser expanded mode - this loading of sample, moves you to sample_editor. that’s all, nothing especially amazing going on :)

The other “non-function-based” script is that whenever a new document is detected, switch to global view preset #1 ( global view preset#1 -> disk browser, disk browser expanded, cursor-focus-on-disk-browser, disk browser Sample-tab selected) - since the “diskbrowser.tab” can’t yet be scripted :)

So basically I’m just trying to alter the behaviour of Renoise when it loads a sample. since the user is not afforded preferences-control (because it would be too weird, i think, for regular users) to say what kind of a screenset is “initialized upon sample load”, im trying to make one that just detects “disk browser is expanded? you loaded sample? switch to sample editor”. :)

it’s probably pretty dangerous for me to try and use observables like this outside of functions, but i have actually no way of finding out what are all the observables that have notifiers, i don’t see any “list all existing notifiers” type things.

It’s dead simple to find out what all the observables that have notifiers are. There is no automatic listing of observables and their attached notifiers. However, the notifiers don’t attach by themselves. To find out which ones have notifiers, look it up in your code. You attach them.

About the attaching: to be on the safe side I tend to wrap the notifier attaching (as Vv suggested) inside an if-then -structure. On bigger projects, where I might have lots of notifiers, I’ve used generalized functions for attaching and detaching to make this a bit snappier in practice.

  
function build_notifier(observable, notifier_function)  
 if not observable:has_notifier(notifier_function) then  
 observable:add_notifier(notifier_function)  
 end  
end  
function clear_notifier(observable, notifier_function)  
 if observable:has_notifier(notifier_function) then  
 observable:remove_notifier(notifier_function)  
 end  
end  
  

To attach a notifier I then call:

  
build_notifier(any_observable_i_want_to_attach_into, my_notifier_function)  
  

and to detach, I call:

  
clear_notifier(any_observable_i_want_to_detach_from, my_notifier_function)  
  

EDIT: one thing you should note when attaching notifiers, though, is that AFAIK while you can attach an anonymous function as a notifier, there is no way of referencing to it later, and thus no way to detach it. (Correct me if I’m wrong…)

By attaching an anonymous function i mean a procedure where you do:

  
observable:add_notifier(function()  
 ..function code here..  
 end  
)  
  

instead of:

  
function my_function()  
 ..function code here..  
end  
  
observable:add_notifier(my_function)  
  

EDIT:edited for clarity

Thanks to both of yous vV & KMaki. I got my “Record to Current Track” functioning so that it no longer runs the sample_buffer_observable_function after i edit a wavefile. cheers. i run the observable removal inside the observable function, so it autoremoves it. works like a charm.

But, moving on to the actual topic of this thread, I’m still not entirely sure how to accomplish the “if sample loaded from diskbrowser, move to sample_editor”, so that it is a non-function-based observer-function. I’m not sure what the difference is - apart from having to hide renoise.song() inside a function to not shoot an error. How do I protect the script from being disabled when Renoise starts? Is there some way of masking renoise.song() so that it doesn’t give an error saying “renoise.song() = nil, let me dump all your keyboard shortcuts for this script to oblivion and disable the whole script”?

Hi vV, this is actually all the code there is:

if renoise.app().window.active_middle_frame==0 then  
renoise.song().selected_sample.sample_buffer_observable:add_notifier(function() gotoeditor()  
renoise.song().selected_sample.sample_buffer_observable:remove_notifier(function() gotoeditor())  
end)  
end  
function gotoeditor()  
renoise.app().window.active_middle_frame=4  
end  
  

It’s not within a function, because as far as i know, there’s no way to make a .wavefile/.mp3/.flac being loaded automatically run a function. If that’s possible, then the whole thing is solved. But as long as I’m not sure how to accomplish that, it’ll be a free-floating not-inside-function bit of instructions directly to Renoise :)
(I’m hoping this modification to remove the notifier will work somehow. Without the removal, it’s still the same thing, i.e. if diskbrowser = expanded, then sample_buffer_observable (observe change in sample_editor), go to active middleframe 4 = sample_editor)))

Your code below (indented for clarity) seems strange and troublesome.

  
if renoise.app().window.active_middle_frame==0 then  
 renoise.song().selected_sample.sample_buffer_observable:add_notifier(function()  
 gotoeditor()  
 renoise.song().selected_sample.sample_buffer_observable:remove_notifier(function()  
 gotoeditor()  
 )  
 end  
 )  
end  
  
function gotoeditor()  
 renoise.app().window.active_middle_frame=4  
end  
  

If this really is a free-floating piece, here’s what happens, if I’m getting this right. I’ll try to break this down.

  1. tool loads
  2. the global function gotoeditor gets registered (I think this is done first)
  3. the if-then -structure checks whether active middle frame is 0 (whatever that 0 is. Maybe pattern editor. You really should use the constants provided with API to make stuff more reliable and readable)
  4. if the middle frame is not 0 when tool loads, nothing happens. The end, roll credits.

Provided the active middle frame happens to be 0 the story goes on.
5. you attach an anonymous function in the selected_sample_buffer_observable. What that means is that whenever the selected_sample_buffer changes, the anonymous function will run. The said function, separated from the rest of the code is:

  
function()  
 gotoeditor()  
 renoise.song().selected_sample.sample_buffer_observable:remove_notifier(function()  
 gotoeditor()  
 )  
end  
  

So you’ve got a notifier following the renoise.song().selected_sample.sample_buffer_observable now (provided that the active_middle_frame was 0 when tool loaded). The notifier will fire the above function when the selected_sample.sample_buffer changes. BTW this is where you get the renoise.song() -clash, as there is no song yet.
6. Nothing happens before the selected_sample.sample_buffer changes.

  1. You change the sample_buffer (by loading a sample for example?). Not sure if merely changing the selected sample will fire this.
  2. The above function goes off, and starts by calling the gotoeditor(). Changes the active_middle_frame to 4. (Which I’m guessing is sample editor. Again, you should use ‘renoise.ApplicationWindow.MIDDLE_FRAME_SAMPLE_EDITOR’ instead of ‘4’)
  3. After calling gotoeditor() my thoughtstream is broken. If your code is working, I cannot understand why. As you are trying to remove an anonymous function from the observable. The anonymous function broken off from the rest of the code is:
  
function()  
 gotoeditor()  
  

Notice there is no ‘end’ in there. So that should bog out immediately. But I’m guessing you haven’t got that far if the song()-thing troubles you…? The another thing that should not work is that you’re trying to remove an anonymous function. As such, it never has been attached and should throw a ‘the notifier function was not added’ -error.

  1. Conclusion: this code is wretched in many ways.

Now. What you most likely WANT to do is this.

  1. tool loads
  2. declare a variable to hold your renoise.song() -reference. Don’t point it yet to song(), as it does not exist yet.
  
--renoise.song() placeholder  
local s = nil  
  
  1. attach a notifier function my_song_load() to app_new_document_observable. Wrapped in if-then to avoid trying to attach it when it is already attached. (fires error, I think)
  
if not renoise.tool().app_new_document_observable:has_notifier(my_song_load)  
 renoise.tool().app_new_document_observable:add_notifier(my_song_load)  
end  
  
  1. point the placeholder to the renoise.song() function inside your my_song_load() function
  
function my_song_load()  
 s = renoise.song()  
end  
  

What you’ve done here, is first outside the function you have declared the variable s (effectively making it a ‘global’ variable, and then inside the function reference to that same variable and set that to point into song(). You cannot for instance do:

  
function my_song_load()  
 local s = renoise.song()  
end  
  

(Notice the ‘local’), because this just creates a new s -variable that is local to the function my_song_load().
5. You should have the renoise.song() under control now.

Final code:

  
--renoise.song() placeholder  
local s = nil  
  
function my_song_load()  
 s = renoise.song()  
end  
  
if not renoise.tool().app_new_document_observable:has_notifier(my_song_load)  
 renoise.tool().app_new_document_observable:add_notifier(my_song_load)  
end  
  
if renoise.tool().app_release_document_observable:has_notifier(my_song_load)  
 renoise.tool().app_release_document_observable:remove_notifier(my_song_load)  
end  
  

Notice there’s an added ‘remove notifier when the song is released’ -bit. I’m told this is supposed to be there.

Disclaimer: All of the above is written without renoise. It is likely to contain errors. The principle should be solid, though.

EDIT: major brainfart on the app_release_document_observable. I’ll leave it there, but it will not do anything… Extra credit for figuring out why that won’t fly! :)

Ok, end result was this (and it works on “new song” after start of Renoise)

renoise.tool().app_new_document_observable:add_notifier(function()  
renoise.app().window:select_preset(1)  
if renoise.app().window.active_middle_frame==0 and renoise.song().selected_sample.sample_buffer_observable:add_notifier(function() renoise.app().window.active_middle_frame=4 end) then return  
end  
end)   
  

I should probably name the notifier so i can remove it too, that’ll be next. It seems to work, tho. well, sporadically. well, almost never. well, hardly ever. that’s better than nothing. I’ll study your writings KMaki and see if I can make this work all the time.

That would probably remove some minor headachse that may occur on the longer term. (reinitializing the tool and constantly adding a new notifier doesn’t exactly speed up Renoise and you may get complaints from users not understanding why Renoise is getting slower over time)

hmm. now it looks like:

local s = nil  
  
function load_sample_change_to_sample_editor()  
local s=renoise.song()  
renoise.app().window:select_preset(1)  
if renoise.app().window.active_middle_frame==0 and s.selected_sample.sample_buffer_observable:add_notifier(function() renoise.app().window.active_middle_frame=4 end) then return  
end  
end  
  
if not renoise.tool().app_new_document_observable:has_notifier(load_sample_change_to_sample_editor)   
then renoise.tool().app_new_document_observable:add_notifier(load_sample_change_to_sample_editor)   
else renoise.tool().app_new_document_observable:remove_notifier(load_sample_change_to_sample_editor)   
end  

So I guess next is to make sure the samplebufferobservable is named and removed… i hope i’m doing this at least semi-right. it won’t work with the beta banner showing, on start, but when you create a new song, it will work…

Does Renoise retain these notifiers even when restarted (shut down app and restart)? Cos that would kinda explain how 2.7 was getting r e a l slow and 2.8 is sooo fast

No, Renoise is not storing some data in a file which notifiers were up and running. If Renoise dies, all dies.
But if you never close renoise and often load or create a new song, notifiers not being released in the proces might theoretically slow down the functioning yes. Depending on what observable is being enabled, one causes more mess than others, e.g. if you would create a notfier for a function that in turn creates a notifier after detecting each change in the pattern editor that a user is doing, you can very quickly experience the effect of stacking observable creations.
On every area, it is always a good habit to clean up after work is done, so it is with programming.

Hi. This seems to work everytime:

local s = nil  
  
function startup_()  
 local s=renoise.song()  
 renoise.app().window:select_preset(1)  
 if renoise.app().window.active_middle_frame==0 and s.selected_sample.sample_buffer_observable:has_notifier(sample_loaded_change_to_sample_editor) then   
 s.selected_sample.sample_buffer_observable:remove_notifier(sample_loaded_change_to_sample_editor)  
 else  
 s.selected_sample.sample_buffer_observable:add_notifier(sample_loaded_change_to_sample_editor)  
 return  
 end  
end  
  
 function sample_loaded_change_to_sample_editor()  
 renoise.app().window.active_middle_frame=4  
 end  
  
if not renoise.tool().app_new_document_observable:has_notifier(startup_)   
 then renoise.tool().app_new_document_observable:add_notifier(startup_)  
 else renoise.tool().app_new_document_observable:remove_notifier(startup_)  
end  

Thanks for all the help, kind folks!
(and the patience)

Ahh, so basically this needs to be split to 1) new app observable for “global view preset” and 2) some sort of sample_buffer observable which only runs if diskbrowser is expanded. otherwise this will only work a couple of times, then after editing in the pattern editor, or recording a sample with a different script - then it breaks down.