[Solved] Help: check notes with follow the player position in Patt. Ed

I still do not move here!

I have tried to try other codes to register the “playback position” which is my reference at all times (renoise.song().transport.playback_pos.line).

What is my problem? If I use a app_idle_observable (or add_timer), if the BMP and LPB are very fast or slow, print the position of line skipping values or repeat values. Example:

function tnc_check_playback_line( song, stpl )
  song = renoise.song()
  stpl = song.transport.playback_pos.line
  print ( " ________ : ", stpl )
end

function line_timer()
  if vb.views['TNC_CB'].value == true then -- a checkbox to active
    --if not renoise.tool().app_idle_observable:has_notifier(tnc_check_playback_line) then
    -- renoise.tool().app_idle_observable:add_notifier(tnc_check_playback_line)
    if not renoise.tool():has_timer(tnc_check_playback_line) then
      renoise.tool():add_timer( tnc_check_playback_line, 5 )
    end
  end
end

Then, the “tnc_check_playback_line” function is inside a loop of repetition desynchronized (a machine gun).In case of repeating values because add_timer work in 5 or 10 or 20ms and BPM and LPB are reasonably low, how do I return a single value to launch a specific function?

I do not know if this approach would be correct, but I can not build a code that works by following your instructions.I’ve tried a thousand things inside the function that works with add_timer, but I can not get it to work properly. I’ve also tried to stop with remove_timer, and reactivate, but nothing.

Can you provide a code that prints in the terminal the line number each time the playback position is changed?

A minimum reference speed could be: BPM = 400 and LPB = 8 (I do not think I use a higher speed ever, but of course, the ideal is a tool that works well at any speed.)

It seems obvious that what I want to look for is something like this: "_renoise.song().transport.playback_pos.line _observable _ " , but this does not exist in the API.I’ve been searching the forums and I have found that already tried to build several tools but always with problems, and with the request to add the observable for transport.playback_pos.line.If it implies a great impact to the CPU depending on the functions to be executed, add a warning for the programmer to know.From what I see, make visual changes using the viewbuilder does not imply a great impact of the CPU.

Not having theobservable fortransport.playback_pos.line involves using add_timer with imprecision and for me, a very big mess to build such a basic tool, like changing the color of a button when the playback position changes.

Could you help me with a custom code that works?I do not know what to try.

A related topichttps://forum.renoise.com/t/executing-a-script-function-with-timing/34936

Can you provide a code that prints in the terminal the line number each time the playback position is changed?

No, not possible. Seems you’ve gone back to the old idea that you can somehow obtain each and every change in position. But you can’t.

All you need is the current position and the position it was a fraction of a second ago. Then, act on that information - it really is sufficient.

No, not possible. Seems you’ve gone back to the old idea that you can somehow obtain each and every change in position. But you can’t.

All you need is the current position and the position it was a fraction of a second ago. Then, act on that information - it really is sufficient.

Ok, and what would the code look like?I’m stuck here…

With this function, I get the same as before:

local line = 0
function cpl()
  if not ( renoise.song().transport.playback_pos.line == line) then
    line = renoise.song().transport.playback_pos.line
    print("line:"..line)
  end
end

renoise.tool().app_idle_observable:add_notifier(function() cpl() end )

All the functions I have tried end up doing the same thing…

… and the position it was a fraction of a second ago…

How would that be?

Ok, and what would the code look like?I’m stuck here…

Check 4Teys example?

Just add a local variable which stores the position. And compare against that each time the timer callback is invoked

Edit: nevermind - you have figured it out with the code above.

Raul, you are aware that there is a renoise.song().transport.edit_pos.line as well? For example:

Raul, you are aware that there is a renoise.song().transport.edit_pos.line as well? For example:

compare renoise.song().transport.playback_pos.line with renoise.song().transport.edit_pos.line ???

Edit: In this case, if following the player is not activated, in theory the final function would not run. It is not like this?

compare renoise.song().transport.playback_pos.line with renoise.song().transport.edit_pos.line ???

Errr, no, now I didn’t say anything like that Raul. What I’m just pointing out is that possibly in your case reading the edit_pos.line of the transport is going to be more useful? Remember in Renoise you can decouple the playback and the edit position.

Errr, no, now I didn’t say anything like that Raul. What I’m just pointing out is that possibly in your case reading the edit_pos.line of the transport is going to be more useful? Remember in Renoise you can decouple the playback and the edit position.

Ok, for the case is the same, edit_pos.line and playback_pos.line return the same value if following player is activated.If you print the values using vb.views.textid.text, the same value of text is written several times, although it is visually the same value in the tool.

If I’m not mistaken, your tool print this:

Playback Line. 01 - Edit Line : 01 …

Playback Line. 01 - Edit Line : 01 …

Playback Line. 01 - Edit Line : 01 …

Playback Line. 01 - Edit Line : 01 …

Playback Line. 01 - Edit Line : 01 …

Playback Line. 01 - Edit Line : 01 …

Playback Line. 02 - Edit Line : 02 …

Playback Line. 02 - Edit Line : 02 …

Playback Line. 02 - Edit Line : 02 …

Playback Line. 02 - Edit Line : 02 …

Playback Line. 02 - Edit Line : 02 …

Playback Line. 02 - Edit Line : 02 …

Playback Line. 02 - Edit Line : 02 …

Playback Line. 02 - Edit Line : 02 …

Playback Line. 02 - Edit Line : 02 …

Playback Line. 03 - Edit Line : 03 …

Playback Line. 03 - Edit Line : 03 …

Playback Line. 03 - Edit Line : 03 …

Playback Line. 03 - Edit Line : 03 …

Playback Line. 03 - Edit Line : 03 …

Playback Line. 03 - Edit Line : 03 …

… depending on the speed in BPB and LPB

When I would have to do this:

Playback Line. 01 - Edit Line : 01 …

Playback Line. 02 - Edit Line : 02 …

Playback Line. 03 - Edit Line : 03 …

… without depending onthe speed in BPB and LPB (apparently something impossible)

The issue is that I do not know how to translate this, to handle a button and its properties, color, name, to be invoked only once per line.

As I understand from Danoise’s comments…

I have the current position, I can choose between edit_pos.line or playback_pos.line.And I have to compare this variable with a fixed value, which I do not know how to fix.

I tried to do comparison tests with “if x == y then” inside the function with timer activated.But since the function does not stop repeating, the “fixed value” also changes.Do I have to set a value within a function that works with a timer?I do not understand how I should pose what Danoise comments…

Edit: In this case, if following the player is not activated, in theory the final function would not run. It is not like this?

I’m not fully sure what you mean by ‘following the player’. But what I can say is that so long as that dialog (in my example) is on the screen (and you haven’t switched to another application, because that would probably also stop the timer), that timer function is called around every 20ms, irrespective of the transport started/stopped state. All this is Raul is you have a timer function ‘in the background’ sampling the pattern every 20ms. If your timer function overruns 20ms it will probably begin to stutter and skip etc… (believe me, not good). If you set your timer to say 5ms (to sample the pattern faster) make sure that the code in the timer function executes in less than 5ms of CPU time. You can’t just keep adding code to that timer function, at some point it could/will overrun. Now you say: ‘Well I’ll just set the timer to 40ms to give it more time’. Yup, you could do this, but now you are sampling the pattern at a lower frequency, so now you run the risk of missing notes at higher LPB/BPM. See part of the game here? Becomes more and more inaccurate.

The issue is that I do not know how to translate this, to handle a button and its properties, color, name, to be invoked only once per line.

So now we’ve moved on from sampling the pattern to the question: How do I work out if the line has changed? Well if you look at this section of code:

if (CurrentLine == rs.transport.edit_pos.line) then
    return
  else
    CurrentLine = rs.transport.edit_pos.line
  end

Is there not a return statement in there? If the CurrentLine is equal to the Renoise transport edit position then leave the timer function? Otherwise set the CurrentLine to the transport edit position. In other words anything that comes after that If/else statement, is only executed on a change of line.

if (CurrentLine == rs.transport.edit_pos.line) then
    return <<--------------------------------------- RETURN!!!
  else
    CurrentLine = rs.transport.edit_pos.line
    -- here launch the desired function
  end

Ok, this matter is solved already!!! :o :o :o

Thank you very much for the help, Danoise and 4Tey!!!

However, it does not solve a very strange reading problem. I explain…

You imagine a complex piano melody written using the 12 note columns, occupying several patterns.When the tool is working, it illuminates the buttons correctly (each button represents a note column).An image of support:

[sharedmedia=core:attachments:7374]

But a concrete failure always occurs:

When Renoise read the first line of each pattern (line 001 of each note column), if the line is empty, it sometimes "read the same note (change the characteristics of the button) as the first line of the previous pattern. It happens almost 90% of the time.It is a subtle error very annoying. Always the same. If I move the note of the previous pattern on line number 002, the error no longer occurs.

A ) Example of error :wacko::-------------------------------------------------------------------------

---------------- Pattern 01, note column 03

C-0 line 001

D-2 line 002

---------------- Pattern 02, note column 03

nocing line 001 <------- ERROR, here read a C-0 (As the same note from the previous pattern (same line 001)

… line 002

B ) Example not error-------------------------------------------------------------------

---------------- Pattern 01, note column 03

nocing line 001

D-2 line 002

---------------- Pattern 02, note column 03

nocing line 001 <------- NOT ERROR, because in the previous patter in line 001 is empty )

… line 002

This is the function that executes once each time a line starts:

function change_buttons( song, spi, sti, stpl )
  song = renoise.song()
  spi, sti, stpl = song.selected_pattern_index, song.selected_track_index, song.transport.playback_pos.line

  if song.selected_track.type == renoise.Track.TRACK_TYPE_SEQUENCER then

      -- note column 1
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 1 ).note_string ~= '---' then
        vb.views["TNC_NC_01"].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( 1 ).note_string
        vb.views["TNC_NC_01"].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
      end
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 1 ).note_string == 'OFF' then
        vb.views["TNC_NC_01"].color = { 0x40,0x00,0x00 }
      end
      -- note column 2
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 2 ).note_string ~= '---' then
        vb.views["TNC_NC_02"].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( 2 ).note_string
        vb.views["TNC_NC_02"].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
      end
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 2 ).note_string == 'OFF' then
        vb.views["TNC_NC_02"].color = { 0x40,0x00,0x00 }
      end
      -- note column 3
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 3 ).note_string ~= '---' then
        vb.views["TNC_NC_03"].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( 3 ).note_string
        vb.views["TNC_NC_03"].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
      end
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 3 ).note_string == 'OFF' then
        vb.views["TNC_NC_03"].color = { 0x40,0x00,0x00 }
      end
      -- note column 4
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 4 ).note_string ~= '---' then
        vb.views["TNC_NC_04"].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( 4 ).note_string
        vb.views["TNC_NC_04"].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
      end
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 4 ).note_string == 'OFF' then
        vb.views["TNC_NC_04"].color = { 0x40,0x00,0x00 }
      end
      -- note column 5
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 5 ).note_string ~= '---' then
        vb.views["TNC_NC_05"].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( 5 ).note_string
        vb.views["TNC_NC_05"].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
      end
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 5 ).note_string == 'OFF' then
        vb.views["TNC_NC_05"].color = { 0x40,0x00,0x00 }
      end
      -- note column 6
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 6 ).note_string ~= '---' then
        vb.views["TNC_NC_06"].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( 6 ).note_string
        vb.views["TNC_NC_06"].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
      end
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 6 ).note_string == 'OFF' then
        vb.views["TNC_NC_06"].color = { 0x40,0x00,0x00 }
      end
      -- note column 7
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 7 ).note_string ~= '---' then
        vb.views["TNC_NC_07"].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( 7 ).note_string
        vb.views["TNC_NC_07"].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
      end
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 7 ).note_string == 'OFF' then
        vb.views["TNC_NC_07"].color = { 0x40,0x00,0x00 }
      end
      -- note column 8
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 8 ).note_string ~= '---' then
        vb.views["TNC_NC_08"].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( 8 ).note_string
        vb.views["TNC_NC_08"].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
      end
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 8 ).note_string == 'OFF' then
        vb.views["TNC_NC_08"].color = { 0x40,0x00,0x00 }
      end
      -- note column 9
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 9 ).note_string ~= '---' then
        vb.views["TNC_NC_09"].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( 9 ).note_string
        vb.views["TNC_NC_09"].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
      end
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 9 ).note_string == 'OFF' then
        vb.views["TNC_NC_09"].color = { 0x40,0x00,0x00 }
      end
      -- note column 10
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 10 ).note_string ~= '---' then
        vb.views["TNC_NC_10"].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( 10 ).note_string
        vb.views["TNC_NC_10"].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
      end
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 10 ).note_string == 'OFF' then
        vb.views["TNC_NC_10"].color = { 0x40,0x00,0x00 }
      end
      -- note column 11
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 11 ).note_string ~= '---' then
        vb.views["TNC_NC_11"].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( 11 ).note_string
        vb.views["TNC_NC_11"].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
      end
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 11 ).note_string == 'OFF' then
        vb.views["TNC_NC_11"].color = { 0x40,0x00,0x00 }
      end
      -- note column 12
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 12 ).note_string ~= '---' then
        vb.views["TNC_NC_12"].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( 12 ).note_string
        vb.views["TNC_NC_12"].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
      end
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( 12 ).note_string == 'OFF' then
        vb.views["TNC_NC_12"].color = { 0x40,0x00,0x00 }
      end
      --- ---
      vb.views['TN_NC_NM_01'].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( 1 ).note_string )
      vb.views['TN_NC_NM_02'].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( 2 ).note_string )
      vb.views['TN_NC_NM_03'].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( 3 ).note_string )
      vb.views['TN_NC_NM_04'].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( 4 ).note_string )
      vb.views['TN_NC_NM_05'].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( 5 ).note_string )
      vb.views['TN_NC_NM_06'].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( 6 ).note_string )
      vb.views['TN_NC_NM_07'].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( 7 ).note_string )
      vb.views['TN_NC_NM_08'].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( 8 ).note_string )
      vb.views['TN_NC_NM_09'].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( 9 ).note_string )
      vb.views['TN_NC_NM_10'].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( 10 ).note_string )
      vb.views['TN_NC_NM_11'].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( 11 ).note_string )
      vb.views['TN_NC_NM_12'].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( 12 ).note_string )
      --- --- 
  end 
end

It can probably be reduced.But, what can this error be?All notes already written on line 001 are well read, except the strange case above, which seems to read a note that is not written in the pattern editor…Really strange!!!

I’ll look to create a separate tool for them to you see…

Ok, here is the tool “finished”. Is named “Track Notes Checker”. Please test the tool for search errors! :

  1. The tool TNC “Track Notes Checker” :7378 ulneiz.TNC_v1.0b1.xrnx

Look at all the time that happens whenever you read the first line of each pattern (line 00, first line). Something does not work right here.Need to fix this little bug.Any idea of the cause of this error?

I have tried the tool even at a speed of: BPM = 400 aprox. and LPM = 8.That’s more than twice the speed I usually use.I would like to leave this perfect tool, within its limitations by the timer.

Why do you read a note on line 00 when it is not written?It is strange, because the notes that are already written on line 00 are read correctly.

To check, I think all notes have OFF at the end in the song for test…

The operation is very basic: if exist note or note off, change name and color for each button, (12 buttons, one for each note column).If you write complex melodies using the 12 note columns, following the order of one octave, it is very simple to visualize.Can be very fun!!! :slight_smile:

7380 ulneiz.TNC.png

Please prove it and help to perfect it!!!

Thank you very much for the help and the patience!!!

Note: The read is made to be continuous between patterns, let the song be a piece. You need to activate the follow player…

Look at all the time that happens whenever you read the first line of each pattern (line 00, first line). Something does not work right here.Need to fix this little bug.Any idea of the cause of this error?

I’ve had a quick look Raul. I’ll try and explain what I think the problem is here. When Renoise is playing and it changes pattern in the sequence, when you read the note data (sampled from your timer function) of the first line from the new pattern say using:

song:pattern( spi ):track( sti ):line( stpl ):note_column( 1 ).note_string

the note data you get back is probably incorrect. So let’s assume that there is a ‘setup’ time when Renoise switches patterns when playing, the timer catches it (sometimes) when it is in this ‘setup’ time and…you get incorrect note data back when you read the first line. I would say it is nothing to do with the logic in your code Raul.

Offhand (because Renoise has put a spanner in the works) the thing to try would be to say buffer up at least the next line (or a few lines ahead) in the pattern/sequence on each timer call, and read from that. Doable, but more tricky/messy to code though. Also remember that you don’t want overruns, so whatever buffering/caching system you come up with (including button/text rendering, because at the moment you have all of that gui changing stuff executed in your timer function) it has got to complete in less time than the time you set your periodic timer (which at the moment from your code is set at 5ms. I assume you are not planning on running this on a 33MHz 486 then :wink: )

I also took a quick look. Raul - I have to say, for a realtime script, you are actually being quite wasteful…

if song:pattern( spi ):track( sti ):line( stpl ):note_column( 1 ).note_string ~= '---' then
  -- do something
end
if song:pattern( spi ):track( sti ):line( stpl ):note_column( 1 ).note_string == 'OFF' then
  -- do something
end
-- etc...

There is nothing “wrong” here per se, but several things you can do better.
As per my third advice in the lua optimization thread, it’s good idea to store references to objects that you are going to call repeatedly.

First of all, it’s very simple to avoid several function calls by storing a reference to the line you’re working on:

local ln = song:pattern( spi ):track( sti ):line( stpl )

This way, you avoid the three API functions you are calling - pattern(), track() and line() - each time you need to access a note column.
And since you are using the line above a total of 48 times, this is 48*3 == 144 function calls avoided, with one simple change.

It makes the code a lot easier to read too :slight_smile:

Another improvement is to use the if … else syntax to simplify comparisons:

if (ln:note_column( 1 ).note_string ~= '---') then
-- do something
elseif (ln:note_column( 1 ).note_string == 'OFF') then
-- do something else
end

Or better still, using the previous technique and storing a reference to the note-column (additionalcalls avoided)

local col = ln:note_column( 1 )
if (col.note_string ~= '---') then
-- do something
elseif (col.note_string == 'OFF') then
-- do something else
end

Using if…else is more efficient, since the second statement - looking for the (“OFF”) - will never be evaluated if the first part was found to be true.

Now, with such a simple example there might not be a big impact. But imagine that your code was more complex than this? Skipping parts as early as possible can be a HUGE performance booster.

And both are general principles which can be applied to any programminglanguage :slight_smile:

Also remember that you don’t want overruns, so whatever buffering/caching system you come up with (including button/text rendering, because at the moment you have all of that gui changing stuff executed in your timer function) it has got to complete in less time than the time you set your periodic timer (which at the moment from your code is set at 5ms. I assume you are not planning on running this on a 33MHz 486 then :wink: )

I never used the timer, so I don’t have much experience with that.To me, idle_notifier is the natural choice, as it simply runs when Renoise has time for it.

I never used the timer, so I don’t have much experience with that.To me, idle_notifier is the natural choice, as it simply runs when Renoise has time for it.

I prefer the timer notifier system myself, but if you are more comfortable with doing it in the idle notifier danoise (when you have a dynamic time frame callback), that is fair enough :slight_smile: The reason I tend to prefer the timer system is that I always get the feeling I am more guaranteed to get a callback in my set time frame even when Renoise is say playing a song and is under more pressure. But it probably doesn’t really make much difference here which ever way you go danoise. The point is we want a guaranteed callback that happens at least between 16-20ms (even when Renoise is playing a song). But you have got to read in slightly more lines ahead of the transport (making a subtle cache of lines) on each timer/idle callback because as Raul as shown the pattern change causes a ‘glitch’ in extracting the note value/string on the first line of the new pattern if you try and read just the one line per callback. As for optimizing lua, go bananas. Optimize as much as possible in the idle/timer notifier as you specified above danoise/Joule :slight_smile:

I also took a quick look. Raul - I have to say, for a realtime script, you are actually being quite wasteful…

if song:pattern( spi ):track( sti ):line( stpl ):note_column( 1 ).note_string ~= '---' then
-- do something
end
if song:pattern( spi ):track( sti ):line( stpl ):note_column( 1 ).note_string == 'OFF' then
-- do something
end
-- etc...

There is nothing “wrong” here per se, but several things you can do better.
As per my third advice in the lua optimization thread, it’s good idea to store references to objects that you are going to call repeatedly.

First of all, it’s very simple to avoid several function calls by storing a reference to the line you’re working on:

local ln = song:pattern( spi ):track( sti ):line( stpl )

This way, you avoid the three API functions you are calling - pattern(), track() and line() - each time you need to access a note column.
And since you are using the line above a total of 48 times, this is 48*3 == 144 function calls avoided, with one simple change.

It makes the code a lot easier to read too :slight_smile:

Another improvement is to use the if … else syntax to simplify comparisons:

if (ln:note_column( 1 ).note_string ~= '---') then
-- do something
elseif (ln:note_column( 1 ).note_string == 'OFF') then
-- do something else
end

Or better still, using the previous technique and storing a reference to the note-column (additionalcalls avoided)

local col = ln:note_column( 1 )
if (col.note_string ~= '---') then
-- do something
elseif (col.note_string == 'OFF') then
-- do something else
end

Using if…else is more efficient, since the second statement - looking for the (“OFF”) - will never be evaluated if the first part was found to be true.

Now, with such a simple example there might not be a big impact. But imagine that your code was more complex than this? Skipping parts as early as possible can be a HUGE performance booster.

And both are general principles which can be applied to any programminglanguage :slight_smile:

I never used the timer, so I don’t have much experience with that.To me, idle_notifier is the natural choice, as it simply runs when Renoise has time for it.

I explain a little my current way of making code.

  1. My first objective is to test if something works.If it works, it does not matter whether the code is heavier or not, it just works.
  2. Point 1 forces me to study their parts, because it allows me to see what I can do or not with them (If I do the “compressed code”, I do not see some parts).This usually happens to me because at first I do not know if I will be able to build a concrete code. I am in the testing phase, and trial and error.
  3. After checking if the code works, I start to optimize it.I have not been with LUA for a long time, but I recognize that I have undergone an evolution, and more with the Renoise API.

Last night, I did an optimization phase using these two things:

--change_buttons
function change_buttons( song, spi, sti, stpl, i )
  song = renoise.song()
  spi, sti, stpl = song.selected_pattern_index, song.selected_track_index, song.transport.playback_pos.line
  if song.selected_track.type == renoise.Track.TRACK_TYPE_SEQUENCER then

    for i = 1, 12 do
      -- note column i
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( i ).note_string ~= '---' then
        vb.views["TNC_NC_"..i..""].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( i ).note_string
        vb.views["TNC_NC_"..i..""].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
      end
      if song:pattern( spi ):track( sti ):line( stpl ):note_column( i ).note_string == 'OFF' then
        vb.views["TNC_NC_"..i..""].color = { 0x40,0x00,0x00 }
      end
      
      vb.views["TN_NC_NM_"..i..""].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( i ).note_string )
    end
    vb.views["TNC_TXT_LNE"].text = string.format( "%.3d", stpl - 1 ) --for read number of line
  end
end

I’m calling 12 equal buttons, so I can use “for … do”.I could also use a “local” to save calls for:

song:pattern( spi ):track( sti ):line( stpl ):note_column( i ).note_string

Other Optimization:

--default values
function tnc_df( tx, cl, i )
  tx = '---'
  cl = { 0x20,0x00,0x00 }
  for i = 1, 12 do
    vb.views["TNC_NC_"..i..""].text = tx
    vb.views["TNC_NC_"..i..""].color = cl
  end
end

Other, if use song.selected_line instead of song.transport.playback_pos.line ( =stpl ) inside the

song:pattern( spi ):track( sti ):line( stpl ):note_column( i ).note_string

I can use:

song.selected_line:note_column( i ).note_string

This is in phase of code optimization. But the fact is that both the previous code and the “compressed code”, work the same, they are asking for the same thing.I have tried both situations and Renoise works exactly the same.If Renoise returns “a error”, it will continue to return, maybe less, but there it is.The subject is how to skip it.

I have read the last comment of 4Tey, and seems to read my mind.It seems that in the pattern jump to the next pattern, the request is faster perhaps in analyzing the line than in changing pattern, thus written errors of notes appear, maybe.The code seems logical and correct, only that does not prevent Renoise’s erratic behavior.

Based on theory alone, it should work perfectly just the way it is.Now I have no idea how to skip this subtle error!!!

Regarding using idle_notifier or a timer, the idle_notifier work about 100ms aprox. (I have understood). Then it is slow to read the update of each line.It happens that the duration of a single line is less than those 100 ms, even to a not very fast reproduction (below BPM 300 and LPB 8).

So I prefer to use a timer.But if the fast timer returns errors, I think it does not depend on the timer itself, but on the code under the hood of Renoise.

So now I’m dead point.How can I avoid these errors, if the code is apparently correct.

Another thing.At least to change things from the viewbuilder,I think that an observable fortransport.playback_pos.line and selected_line / selected_line_index would be very feasible.I wish it were possible.If the speed is very high, it might be appropriate to put an internal limiter to the observable, and at slower speed, that works in real time, to avoid having to invent code to solve such simple things.With a notice to the programmer in the documentation of the risks of using an observable in real time to follow the line or the playback, would be solved.I can think of a lot of useful tools thanks to this. Would be wonderful!!!

When I have a little time,I will share a v1.0b2 of the tool to see if we can find a way to make it work perfectly.It would be a good example recently. Most old tools that are related to following the line at all times, do not workor are outdated (not supported Renoise 3.1).

Thanks for all the help, lately I’m learning those more advanced tricks that allow me to think differently.

  1. It seems to me like everyone is forgetting the biggest optimization of them all here - line.is_empty. This happens so frequently and is so fast to check, that a special case in the if statement should be made for this condition.

  2. Also, if line is not empty, initially checking note_column.is_empty should probably be faster than making a string comparison on each and every ncol. The only difference between “—” and note_colum.is_empty is that the latter includes dly/pan/fx-column et c. That doesn’t matter in this case, though… You kind of gain by adding a check for “not note_column.is_empty” outside here, just because it happens so often.

Do these two to avoid a lot of extra calls… The other suggestions are good as well, but these are the real heavy ones.

  1. It seems to me like everyone is forgetting the biggest optimization of them all here - line.is_empty. This happens so frequently and is so fast to check, that a special case in the if statement should be made for this condition.

  2. Also, if line is not empty, initially checking note_column.is_empty should probably be faster than making a string comparison on each and every ncol. The only difference between “—” and note_colum.is_empty is that the latter includes dly/pan/fx-column et c. That doesn’t matter in this case, though… You kind of gain by adding a check for “not note_column.is_empty” outside here, just because it happens so often.

Do these two to avoid a lot of extra calls… The other suggestions are good as well, but these are the real heavy ones.

^^ ^^ ^_^,I thought of a patch which solves the continuous error of the first line.I do not like the result of the set code, but at least it works:

--IMPORTANT NOTE, Renoise v3.1 not read correctly the fist line of each pattern using a timer or idle_obserble. Is necessary use a filter for line 01.

--check first line only of each pattern (filter)
function tnc_check_pattern_first_line( song, tool, spi, sti, stpl )
  song = renoise.song()
  tool = renoise.tool()
  if vb.views['TNC_CB'].value == true then  
    local function check_pattern()
      
      spi, sti, stpl = song.selected_pattern_index, song.selected_track_index, song.transport.playback_pos.line
  
      if song.selected_pattern_index == spi then
        --print ( "spi:", spi )
        --print( "note line 01", song:pattern( spi ):track( sti ):line( 1 ):note_column( 6 ).note_string )
        
        --- ---
        for i = 1, 12 do
          if not ( song:pattern( spi ):track( sti ):line( 1 ).is_empty ) then
            -- line 01, note column i
            --if song:pattern( spi ):track( sti ):line( stpl ):note_column( i ).note_string ~= '---' then
            if not ( song:pattern( spi ):track( sti ):line( 1 ):note_column( i ).is_empty ) then
              if ( song:pattern( spi ):track( sti ):line( 1 ):note_column( i ).note_value < 120 ) then -- 120 = OFF, 121 = empty
                --vb.views["TNC_NC_"..i..""].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( i ).note_string
                vb.views["TNC_NC_"..i..""].text = song:pattern( spi ):track( sti ):line( 1 ):note_column( i ).note_string
                vb.views["TNC_NC_"..i..""].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
              end
            else
            end
            --if song:pattern( spi ):track( sti ):line( stpl ):note_column( i ).note_string == 'OFF' then
            if song:pattern( spi ):track( sti ):line( 1 ):note_column( i ).note_string == 'OFF' then
              vb.views["TNC_NC_"..i..""].text = 'OFF'
              vb.views["TNC_NC_"..i..""].color = { 0x40,0x00,0x00 }
            end
          end
          vb.views["TN_NC_NM_"..i..""].text = string.format(" %s", song:pattern( spi ):track( sti ):line( 1 ):note_column( 1 ).note_string )
        end
        --- ---
              
      end
    end
    song.selected_pattern_index_observable:add_notifier( check_pattern ) --observable for pattern
    tool.app_new_document_observable:add_notifier( tnc_check_pattern_first_line )
  else
    if tool.app_new_document_observable:has_notifier( tnc_check_pattern_first_line ) then
      tool.app_new_document_observable:remove_notifier( tnc_check_pattern_first_line )
    end
  end
end

--- ---

--tnc_change_buttons ( with timer: line_timer() ). Read all the lines except the first.
function tnc_change_buttons( song, spi, sti, stpl, i, note_01 )
  song = renoise.song()
  spi, sti, stpl = song.selected_pattern_index, song.selected_track_index, song.transport.playback_pos.line
  if song.selected_track.type == renoise.Track.TRACK_TYPE_SEQUENCER then

    --[[
    -- note column 1. Example:
    if song:pattern( spi ):track( sti ):line( stpl ):note_column( 1 ).note_string ~= '---' then
      vb.views["TNC_NC_01"].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( 1 ).note_string
      vb.views["TNC_NC_01"].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
    end
    if song:pattern( spi ):track( sti ):line( stpl ):note_column( 1 ).note_string == 'OFF' then
      vb.views["TNC_NC_01"].color = { 0x40,0x00,0x00 }
    end  
    ]]
  
    --- ---
    if not (song.selected_line_index == 1 ) then
      for i = 1, 12 do
        if not ( song.selected_line.is_empty ) then
          -- note column i
          --if song:pattern( spi ):track( sti ):line( stpl ):note_column( i ).note_string ~= '---' then
          if not ( song.selected_line:note_column( i ).is_empty ) then
            --if ( song.selected_line:note_column( i ).note_string ~= '---' ) then
            if ( song.selected_line:note_column( i ).note_value < 120 ) then
              --vb.views["TNC_NC_"..i..""].text = song:pattern( spi ):track( sti ):line( stpl ):note_column( i ).note_string
              vb.views["TNC_NC_"..i..""].text = song.selected_line:note_column( i ).note_string
              vb.views["TNC_NC_"..i..""].color = tnc_tr_color()--{ 0x00,0x70,0x00 }
            end
          else
          end
          --if song:pattern( spi ):track( sti ):line( stpl ):note_column( i ).note_string == 'OFF' then
          if song.selected_line:note_column( i ).note_string == 'OFF' then
            vb.views["TNC_NC_"..i..""].text = 'OFF'
            vb.views["TNC_NC_"..i..""].color = { 0x40,0x00,0x00 }
          end
        end
        vb.views["TN_NC_NM_"..i..""].text = string.format(" %s", song:pattern( spi ):track( sti ):line( stpl ):note_column( i ).note_string )
      end
    end
    --- ---
    
    vb.views["TNC_TXT_LNE"].text = string.format( "%.3d", stpl - 1 ) --for read number of line
  end
end

--activate_tnc (check_timer, ENABLED/DISABLED)
TNC_CB = vb:checkbox { id = 'TNC_CB', value = false, notifier = function() tnc_check_pattern_first_line() line_timer() end, tooltip = "Enable/Disable TNC.\n" } --checkbox hidden
---
function tnc_cb_bt_ntf( song )
  song = renoise.song()
  if vb.views['TNC_CB'].value == false then
     vb.views['TNC_CB'].value = true
     vb.views['TNC_CB_BT'].color = { 0x00,0x70,0x00 }
     vb.views['TNC_CB_BT'].text = 'ENABLED'
     --- ---
     vb.views['TNC_TXT_TR'].text = song.tracks[song.selected_track_index].name
     vb.views['TNC_TXT_TR'].color = song.tracks[song.selected_track_index].color
    else
     vb.views['TNC_CB'].value = false
     vb.views['TNC_CB_BT'].color = { 0x40,0x00,0x00 }
     vb.views['TNC_CB_BT'].text = 'DISABLED'
  end
end
---
TNC_CB_BT = vb:button {
  id = 'TNC_CB_BT',
  width = 74,
  height = 22,
  text = 'DISABLED',
  color = { 0x40,0x00,0x00 },
  notifier = function() tnc_cb_bt_ntf() end,
  tooltip = 'Enable/Disable TNC.\n'
}

local CurrentLine = -1
function tnc_check_playback_line( song, spi, stpl, stel )
  song = renoise.song()
  --print("pos.line: ", song.transport.playback_pos.line )
  ---
  if ( CurrentLine == song.transport.playback_pos.line ) then
    return --<-- Important for not repeat the same function unnecessarily. Run the "tnc_change_buttons()" function only once for each line.
  else
    CurrentLine = song.transport.playback_pos.line
    --print("pos.line: ", song.transport.playback_pos.line )
    tnc_change_buttons()
  end

  --[[

  if (CurrentLine == song.transport.edit_pos.line) then
    return
  else
    CurrentLine = song.transport.edit_pos.line
    print("x") 
  end
  
  ]]
  ---
end

function line_timer()
  if vb.views['TNC_CB'].value == true then
    if not renoise.tool():has_timer( tnc_check_playback_line ) then
      renoise.tool():add_timer( tnc_check_playback_line, 5 ) --timer (ms) 5 = fast max, 20 = low, 40 = very low
    end
    --- ---
    --renoise.tool().app_new_document_observable:add_notifier( tnc_tr_chng )
    --renoise.tool().app_new_document_observable:add_notifier( tnc_tr_color )
    --renoise.tool().app_new_document_observable:add_notifier( tnc_check_pattern_first_line )
    --- ---
  else
    if renoise.tool():has_timer( tnc_check_playback_line ) then
      renoise.tool():remove_timer( tnc_check_playback_line )
    end
    --- ---
    
    --- ---
    --tnc_df() --default values
  end
end

How it works:

  1. An observable for the pattern is responsible for reading only the notes of the first line: tnc_check_pattern_first_line().
  2. Separately, a timer ( line_timer() ) is responsible for reading the rest of lines ( if not (song.selected_line_index == 1 ) then ), through the functiontnc_change_button().

Thus, point 1 changes the button properties only on line 1, point 2 changes the button properties of the rest of the lines throughout the song.Both functions include a switch (a checkbox to manually enable or disable).

I have used .is_emty also:

(1) if not ( song:pattern( spi ):track( sti ):line( 1 ).is_empty ) then

(2) for i = 1, 12 do ...
    
    if not ( song:pattern( spi ):track( sti ):line( 1 ):note_column( i ).is_empty ) then

-------------

(1) if not ( song.selected_line.is_empty ) then

(2) for i = 1, 12 do
    
    if ( song.selected_line:note_column( i ).note_value < 120 ) then

With these lines inserted into the codeI guess it will free up more CPU processes.

To verify that the tool works well with the “timer (up to 5ms or more) + CPU used and speed (BPM and LPB)”, I do this (print with the terminal):

local CurrentLine = -1
function tnc_check_playback_line( song, spi, stpl, stel )
  song = renoise.song()
  print("pos.line: ", song.transport.playback_pos.line ) --<----- ACTIVATING THIS LINE
  ---
  if ( CurrentLine == song.transport.playback_pos.line ) then
    return --<-- Important for not repeat the same function unnecessarily. Run the "tnc_change_buttons()" function only once for each line.
  else
    CurrentLine = song.transport.playback_pos.line
    --print("pos.line: ", song.transport.playback_pos.line )
    tnc_change_buttons()
  end
end

When run the tool, looking the terminal, he prints the value of each line several times in sequence, then I can do tests by increasing the values of BPM and LPB.If a value (pos.line: X) is skipped, I understand that the tool may return an error, which may not coincide in time with this print, but there will probably be some error reading the data. Example:

Work ok! :

------------------------------------------(the terminal)

pos.line: 1

pos.line: 1

pos.line: 1

pos.line: 2

pos.line: 2

pos.line: 3

pos.line: 3

pos.line: 3

pos.line: 4

pos.line: 5

pos.line: 6

pos.line: 6

pos.line: 7

pos.line: 7

pos.line: 7


Will work with random errors:

------------------------------------------ (the terminal)

pos.line: 1

pos.line: 1

pos.line: 1

pos.line: 2

pos.line: 2

pos.line: 3

pos.line: 3

pos.line: 3

pos.line: 5 <<----(looking)------pos.line: 4 not exist!!! (the tool will probably return errors)

pos.line: 6

pos.line: 6

pos.line: 7

pos.line: 7

pos.line: 7


The second case demonstrates the lack of precision when using a fast timer:

  • Number 1 or 7 is repeated three times
  • Number 2 or 6 is repeated two times
  • Number 3 or 5 is repeated a time
  • Numeber 4 not exist! The tool will probably return random errors already.

Compiling everything, the whole situation is a sucks.

  1. To chase the line during the playback some kind of timer is needed…
  2. This timer will return errors at high speeds, at least > 200 BPM and > LPB.
  3. Under the hood of Renoise, using the timer reading the first line of each pattern returns errors, in 95% of cases.It is necessary to analyze the first line separately.The resulting code is not very elegant and mixes things with rare tricks.

Maybe Taktik could go deeper into this subject under the hood of Renoise to offer us a playback_pos.line _observable reliable enough to use at a reasonable speed, at least for those using powerful CPUs.Most related searches on the forums are from 2010/2011.It’s 2017.Have a playback_pos.line _observable would open the doors to a lot of interesting tools!

Danoise, you could discuss this with Taktik for an upcoming version of Renoise?

Everything is possible with the approaches given to you in this thread. I’ve tried to describe the problem of high LPM and how to avoid it earlier.

Simply put: If more than one line has passed, you can just ‘batch process’ the missed range with a loop.

PS. Regarding line observables… You certainly don’t need anything close to realtime for just controlling some button properties (the problem is not the API in this case).