[Solved] Help LUA: print in real time the value of selected Track insi

I open a new post with this issue…

How I can show in real timethe number of the track or groupof the Pattern Editor, in a floating window (a tool). See this value can be very useful for me.

A scheme:


| title tool x |

|__________________________|

| |

| |

| [12] | <— Track or Group: 12 (in the index)

| |

| |

|__________________________|

Change the track in Pattern Editor, and the number changes in this floating window.Any simple code to do this?

The value is “the selected_track_index” and range would run from 1 (index = 1) to last track or group (index = “sequencer_track_count”).Would use the BIG text size in ViewBuilder…How do I put it in ViewBuilder to building the code?

It would be like display values as does the terminal.

I guess the code might be to show other values in real time inside the floating window of the tool. It would be very great!

Is this possible?

You can use the observable pattern to achieve this.

function bpm_changed()
  print(("something changed the BPM to %s"):format(
    renoise.song().transport.bpm))
end

renoise.song().transport.bpm_observable:add_notifier(bpm_changed)
-- later on, maybe:
renoise.song().transport.bpm_observable:remove_notifier(bpm_changed)

Search the Renoise.Song.API and you’ll see that many (most) properties have an additional version that ends with “_observable”

This makes it simple to listen for changes to whatever you want and point to a function which handles the change.

In your case, by changing the text in a dialog.

You can use the observable pattern to achieve this.

function bpm_changed()
print(("something changed the BPM to %s"):format(
renoise.song().transport.bpm))
end

renoise.song().transport.bpm_observable:add_notifier(bpm_changed)
-- later on, maybe:
renoise.song().transport.bpm_observable:remove_notifier(bpm_changed)

Search the Renoise.Song.API and you’ll see that many (most) properties have an additional version that ends with “_observable”

This makes it simple to listen for changes to whatever you want and point to a function which handles the change.

In your case, by changing the text in a dialog.

Thanks!

Ok, hi have this function, located above the content:

function tracks_index()
  print(("track number: %s"):format(renoise.song().selected_track_index))
end

And inside the content:

... ...
bla bla bla 
... ...
},
vb:horizontal_aligner { 
  mode = 'right',
  swi_cl_hmenu, --hmenu
  vb:horizontal_aligner { width = 38 },
  vb:horizontal_aligner {
    height = 24,
    width = 60,
    margin = 3,
    vb:text {
    font = 'big',
    -- text = ''    
    },
    vb:textfield {
      add_notifier(tracks_index) --<<<------- here? how is this written?
      },
    },
... ...
bla bla bla

My problem is that I need the concrete code to appear in the vb:textfield (or vb:text???).I do not get to write well.

add_notifier = function()tracks_index() end ??? :blink:

notifier = tracks_index() ??? :blush:

I have apart:

renoise.song().selected_track_index_observable:add_notifier(tracks_index)

I used to appear in the terminal, work correctly.But I guess for vb, need another specific code.Sometimes I lose more time on this foolishness that more complex functions. :wacko:

It would also be great to show the value inside a disabled button colored…

You have to understand that the viewbuilder is a special syntax - it’s basically a table-alike structure which describe the hierarchy of viewbuilder widgets.

some_table = {
  some_subtable = {
    some_value = "blah",
  }
}

The difference between a regular table (like the one above) and the viewbuilder table is that you can only define properties that are allowed by the viewbuilder API.

So you can’t just define code in places unless the viewbuilder allows/expects you to. In fact, theonly place is with interactive elements that allow you to define the ‘notifier’ property as a function.

You can turn things on their head though and build views through code. Using the add_child() method you can define a basic view and, through a loop structure, add members to that view. This will allow you to create things like a grid or list, without having to define each and every element manually.

Generally speaking, it seems you are struggling a bit with the basic syntax. One of the really important parts to understand is _scope._For example, “who can access my variables and functions, where do I need to define them?”

Check out the following link:http://lua-users.org/wiki/ScopeTutorial

I can help with special concrete cases, but not take you by the hand the whole way :wink:

You need to read more about the lua language itself. Not just when used with the Renoise API, but as a programming language in general.

Once you have that basic understanding, the Renoise API will make so much more sense.

My problem is not understanding the language LUA (some things are very complex),but what can or can not do with specific codes of LUA in Renoise.

I understand that to return a specific value static within a “vb:horizontal_aligner” i need the: " text = “bla bla bla” ". Ok, “text” show “bla bla bla” inside horizontal_aligner

When the value is not fixed but varies, I need a function (with specific language LUA for Renoise) that returns (disposed above the content=):

function tracks_index()
  print(("track number: %s"):format(renoise.song().selected_track_index))
end

This is my function, which returns the value I need (number of track selected in Pattern Editor or Mixer (of the index)).

I think I should use “notifier =” inside the “vb:textfield”

vb:textfield {
 notifier = function() <--- here!
}

In Renoise ViewBuilder.API.lua are:

– Valid in the construction table only: Set up a notifier for text changes.

– See add_notifier/remove_notifier below.

textfield.notifier

-> [function()]

and below:

– Add/remove value change (text change) notifiers.

multiline_textfield:add_notifier(function or {object, function} or {object, function})

multiline_textfield:remove_notifier(function or {object, function} or {object, function})

I’m here now:

vb:multiline_textfield {
 --text = "t",
  add_notifier = tracks_index(),
   notifier = function()tracks_index() end
},

or

vb:multiline_textfield {
 --text = "t",
   notifier = function()tracks_index() end,
 add_notifier = tracks_index()
},

But the value does not appear in box inside the floating window of tool…

Sorry if I’m asking too much things. You have years of experience with LUA. I am a rookie of one month. In specific cases, I need a example that does exactly what I ask for understanding. Then, based on it, I can use it to show other values…

Let’s just reset. You can do what you want in a few lines of code, but…

vb:multiline_textfield {
--text = "t",
add_notifier = tracks_index(),
notifier = function()tracks_index() end
},

or

vb:multiline_textfield {
--text = "t",
notifier = function()tracks_index() end,
add_notifier = tracks_index()
},

That’s the thing I tried to explain. You can’t just mix expressions (code) and viewbuilder tables (structure) - this is why add_notifier won’t work here.

Also, you’re not really using the add_notifier method like it’s intended - see my original example.

Another thing to consider is that, in the case of your ‘show current track number as text’ you don’t even_need_a notifier method for the textfield. This is only needed when you allow the user to edit the text, which is not the case here.

Really, just use the snippet I posted to begin with and modify the “bpm_changed” function to change the text of your textfield. Job done?

Really, just use the snippet I posted to begin with and modify the “bpm_changed” function to change the text of your textfield. Job done?

This is the first thing I did, but I do not know him inside the ViewBuilder

function tracks_index()
 print(("track number: %s"):format(renoise.song().selected_track_index))
end
renoise.song().selected_track_index_observable:add_notifier(tracks_index)-- enable in terminal
renoise.song().selected_track_index_observable:remove_notifier(tracks_index) -- disable in terminal

How place here:

vb:textfield {

}

Sorry, I have not slept very well today ^_^.

You are dead set on stuffing the code inside the viewbuilder table for some reason. But it doesn’t work that way.

Think of it like this: lua code can control anything, anywhere, as long as it has some kind of access. This includes viewbuilder views.

So to control a view you just need to give your view an ID, or even just keep a variable around which reference the view:

-- create two textfields

local my_textfield = vb:textfield{
  text = "Foo"
}

local my_other_textfield = vb:textfield{
  id = "other_textfield",
  text = "Bar"
}

-- now you can control them using reference

my_textfield.text = "Hey"
my_other_textfield.text = "You"

-- or via the global viewbuilder "views"
-- (only works when view has an ID)

vb.views["my_other_textfield"].text = "Rocksteady Crew"

So, to use the BPM notifier as an example, you really just need to reference the view that you have created.

PS: I really don’t mind helping you out, but my best coding tip would be to take some breaks - I’m heading over to the park now :smiley:

Danoise, thanks for the help and for your patience!

I will try to study calmly.I’m in multitasking mode :wacko:

Unfortunately I have been unable solve.So far it is the only thing that resists me.I’m tired.

I have my tool almost complete. I need this issue to resolve.I think you’ll like it :slight_smile:

Now I’m polishing some details, that they are no longer building code.I have never created anything as personal as this tool.

So, I’ve created some code for you to sink your teeth into.

local vb = renoise.ViewBuilder()

-- 1. Create a view for the textfield 
-- (this would usually be the dialog content)

local dialog_content = vb:column{
  vb:row{
    vb:column{
      vb:row{
        vb:text{
          id = "track_index_display"
        }
      }
    }
  }
  
}

-- 2. Define a named notifier method 

local handle_track_index = function()
  local track_index = renoise.song().selected_track_index
  vb.views["track_index_display"].text = ("Track %d"):format(track_index)
end

-- 3. Add notifiers to the observable property:

renoise.song().selected_track_index_observable:add_notifier(handle_track_index)

It’s essentially defining everything as local variables to make things easy to read.

You will have to adapt it to your own scenario - scope might be different there.

  1. All those rows&columns just serve to demonstratethat the text view can be as deep inside a(ny) viewbuilder table as you need it to

  2. A named function is basically a variable (called ‘handle_track_index’) which refer to our notifier function. Having a variable around makes it possible to reference things later on.

  3. Usually you would register notifiers once, on startup. But if you can’t avoid that the code will be evaluated multiple times, you can check for previous existence of the notifier by calling ‘has_notifier’. This is where the previously named function becomes handy, because you have to ask ‘hey observable, do you know if this notifier is associated with you?’. Can’t ask that question without providing a reference, somehow.

  4. What’s missing? Well, for starters I didn’t create a dialog. I’ll leave that as an exercise :slight_smile:

Hi Danoise

I followed the steps and works correctly.But while loading the tool for the second time this error appears:

...
std::logic_error: 'add notifier: the given function was already registered to this notifier.'
stack traceback:
 [C]: in function 'add_notifier'
 main.lua:3728: in function <main.lua:3727>

The code "renoise.song().selected_track_index_observable:add_notifier(tracks_index)"is within the functionshow_dialog().


On the other hand, if Iplace the code"renoise.song().selected_track_index_observable:add_notifier(tracks_index)"outside of any function,I receive another error loading Renoise to start:

...
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 '?'
main.lua:3722 in main chuck

But then, after loading Renoise, the tool works perfectly. Apparently I have a problem with that particular: “renoise.song()

In a place it gives me an error, and in another, other different error…

if Iplace the code"renoise.song().selected_track_index_observable:add_notifier(tracks_index)" in the line 1 inside main.lua I receive this error loading Renoise:

...
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 '?'
main.lua:1 in main chuck

I have my tool code practically finished (missing details of filling). If you were so kind to check it to avoid this particular error, I provide you the tool, before publishing and share.

One of the most common errors when dealing with observables - due to renoise.song() changing when you open a new document.

Short answer:

  1. Use renoise.tool().app_new_document_observable to trigger a “main()”. From this main() you can set up the other observables/notifiers. Now your tool will “reinitialize” properly when starting Renoise or when loading a new song. (PS. notifiers get removed automatically when releasing a document, I think, so no need to worry about that)

  2. I think it’s generally common practice to check “has_notifier” with an if-statement before trying to add or remove a notifier. I don’t think it’s needed if you code everything properly (sometimes I don’t do it), but I guess it’s common practice to avoid some simple errors like these.

Hi Joule. Thanks for help!

Have you an example of tool where you can see 1)?How exactly would the code of step 1)?

My tool have:

  1. manifest.xml

  2. main.lua (with dialog, content, keyhandler, etc… )

  3. folder “\tools” (tool_01.lua, tool_02.lua… tool_10.lua), with order “require” inside of the main.lua:

require (“tools/tool_01”)

require (“tools/tool_02”)

etc…

This error of “renoise.song()”,has been a headache for the completion of my tool.

In main.lua document I found this:

– Invoked each time a new document (song) was created or loaded.

renoise.tool().app_new_document_observable:add_notifier(function() handle_app_new_document_notification() end)

but I do not know how to apply it…How is the exact code and where to put it?

From the top of my head, some of what I mentioned is used in https://forum.renoise.com/t/tool-3-1-capture-track-from-instrument/34036

The app_new_document_observable should simply be put in the outermost scope of the script (like I do in this script - not within a function). Let it trigger a function that will initialize all other notifiers.

This is what will happen:

  1. User is closing a song in Renoise
  2. This always make all renoise.song() notifiers destruct automatically (AFAIK)
  3. User loads new song
  4. app_new_document_observable is banging, if you have set it up. Simply speaking it’s running the function you have told it to run. Here we have the chance to “reinitialize” the script when a new song is loaded. This is especially needed if your script uses various notifiers in renoise.song().

If you just let the notifiers be, and don’t “reinitialize” them on song change, things will go bananas. I don’t 100% know the technical details, but I am guessing the notifiers will believe that you are still in the old song and things will quickly start generating errors.

Common practice example for adding a notifier:

if not renoise.song().selected_instrument_index_observable:has_notifier(a_function_name) then
 renoise.song().selected_instrument_index_observable:add_notifier(a_function_name)
end

For removing a notifier:

if renoise.song().selected_instrument_index_observable:has_notifier(a_function_name) then
 renoise.song().selected_instrument_index_observable:remove_notifier(a_function_name)
end

Good thing to put at the end of your script :slight_smile:

renoise.tool().app_new_document_observable:add_notifier(my_initialization_function_on_song_load)

– Invoked each time a new document (song) was created or loaded.
renoise.tool().app_new_document_observable:add_notifier(function() handle_app_new_document_notification() end)

Hmm. I wonder if that syntax works. Perhaps it does, but it seems redundant. This is how I would write it:

renoise.tool().app_new_document_observable:add_notifier(handle_app_new_document_notification)

In main.lua document I found this:

– Invoked each time a new document (song) was created or loaded.
renoise.tool().app_new_document_observable:add_notifier(function() handle_app_new_document_notification() end)

A function without a name it is suitably called an anonymous function. That’s exactly what you have there (highlighted in bold).

And that’s why I talked about named functions. Because, when you pass an anonymous function to a notifier you will be asking for trouble later on - you won’t be able to remove it, because the method for removing it will expect some kind of reference.

Not that it’s so damn important to remove notifiers for the song (as joule points out, you mostly have to worry about attaching new ones).

But once you start attaching and removing notifiers to other things, you’ll need a good approach.

Hi Joule and Danoise. Thanks for the help!

Sorry, but I have done 1000 things to fix, but I can not fix it.I feel as powerless lol.I’ve tried moving blocks of code, but I can not fix it.

Temporarily I share my tool here so you can see the code: …removed

  1. Open Renoise and install the tool

  2. Play with it without closing Renoise. All work correctly.

  3. Close Renoise.

  4. Open Renoise. Charging error appears!!!

  5. In main.lua, line 2776 is “renoise.song().selected_track_index_observable:add_notifier(tracks_index)”, the problem. This line is necessary to update the track number. If you erase the line, the tool work correctly always, but the number of track not appear (in tool down).Just need to fix this bug.

I really like this tool.I want to work fine.Please help to end!

I hope you like it! :slight_smile:

EDIT: Menu Tools/Reload all Toolsto display the tool again after error. This tool is a floating window.

Appearantly, you cannot add a renoise.song() notifier before the song is loaded (which is the case when Renoise is starting up). If you try it in an empty tool, you will find the following:

Works:

-- will trigger when selected_track_index is changing
function tracks_index() 
print("hello")
end

-- will trigger on song load
function initialize()
 renoise.song().selected_track_index_observable:add_notifier(tracks_index) 
end

renoise.tool().app_new_document_observable:add_notifier(initialize)

Does not work (what you are doing):

function tracks_index()
print("hello")
end

renoise.song().selected_track_index_observable:add_notifier(tracks_index)

Perfect!Already all solved!

tracks_index = function()
 local track_index = renoise.song().selected_track_index
 vb.views["tr_index"].text = ("%d"):format(track_index)
end

function initialize()
 renoise.song().selected_track_index_observable:add_notifier(tracks_index)
end
renoise.tool().app_new_document_observable:add_notifier(initialize)

I am very happy with the results!

Thank you very much, Danoise and Joule!

As the tool has finished, I do not hesitate to share for yourselves…

^^ ^^ :slight_smile:

Thaaanks!!!