Development of "VPDpro. Metron". Some demostrations

Both things!I like to experiment with the code. But also a visual metronome can help me in certain compositions. On the other hand, I also want to build it, to see if it can really be built with precision or not. I can be guided by hate , and in certain cases by sight, and concentrate the ear on something else.

So the purpose is multiple.You can also think that creating the code is fun. The process can help build other things later.

“I can be guided by hate” :blink:

Sounds like a pretty intense metronome Raul!

“I can be guided by hate” :blink:

Sorry if I have expressed myself wrongly in English.But the context is clear. To follow a rhythm, you can use only the ear or only the view (or both).Another thing is if exist people who considers this topic stupid.

Sounds like a pretty intense metronome Raul!

What do you mean with “intense”?

Sorry if I have expressed myself wrongly in English.But the context is clear. To follow a rhythm, you can use only the ear or only the view (or both).Another thing is if exist people who considers this topic stupid.

What do you mean with “intense”?

It was only making light at a typo (meant in a friendly way), not meant as a jab at your English which I`m sure is a million times better than my rendition of any other language.

Topics great! hope you`ve seen my likes of your other posts regarding your script developments!

re: intense just means very charged in an emotional way, as in intense hatred. The typo just made me imagine a metronome built for Heavy Metal users :slight_smile:

It was only making light at a typo (meant in a friendly way), not meant as a jab at your English which I`m sure is a million times better than my rendition of any other language.

Topics great! hope you`ve seen my likes of your other posts regarding your script developments!

re: intense just means very charged in an emotional way, as in intense hatred. The typo just made me imagine a metronome built for Heavy Metal users :slight_smile:

^^ ^^ :slight_smile: I do not know if this will be useful for others, or for the styles of music used by others.When I do the tool I am a little selfish and I think of my own situations.I also use Renoise to compose orchestral music, where silences and pauses are also present. I like the idea of being able to follow a rhythm without hearing any sound, only with sight. Here, a synchronized visual metronome could help. This is not appreciated until the person tests it.

Normally, most people are guided with a metronome with sound.So I understand that other people can see this concept of visual metronomeas something useless. With a visual metronome (without sound) it is possible to imagine a melody with the head, following a visual rhythm, without needing to listen to anything. This also helps keep your mind clearer and focus on the melody you have in your head.

Believe me, I’ve also thought about adding a sound to the metronome. But this I think is not possible. It would be easier to create a phrase to build personalized metronome sounds, an idea that, I learned from DBlue, by the way.

From here, when creating a visual metronome, the appearance of the metronome can be radically different.It is even possible to build this (minute 1:24):

Just think I found the hate metronome: :lol:

Ok,I have been able to mature the focus of the tool. Now synchronize correctly according to the sequence. Wow! This means that the metronome is updated every time it reproduces a new pattern, following the sequence. This is magnificent.I followed the wise advice of Joule, to stop the main timer (for the metronome) after playing the new pattern, not when the pattern ended (the timer approximately stops and starts again on line 1 in each new pattern).But I had to use a faster secondary timer (5ms) to control the sequence (app_idle_observable is too slow, about 10ms).

“VPDpro. Metron” looks like this now… Metron is small, but bully!:

7754 vpdpro-metron_x3.gif

7755 vpdpro-metron_x4.gif

Now it supports x2, x3, x4, x5 and x6 (travel-sequence of the metronome).Repeat indicates the number of times the metronome’s travel is repeated in a pattern.

I have experimented with latency to calibrate it in faster or slower processors. In any case, it is possible to synchronize it very well in powerful processors (here it works perfect, i5, i7) and in an acceptable way in slow processors (i3 or worse).Although it has a slightly different configuration to the Renoiose sound metronome, it is possible to synchronize it perfectly with it.However, Metron will react according to the configuration of each pattern (BPM, LPB, number of lines …).Unfortunately, Renoise’s metronome is not configured to adjust in any way to changes in patterns.

This is the current code (more advanced, it is no longer in diapers). Thanks to Joule and 4Tey for your comments! They are always helpful!

Click to view contents
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
---METRON
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------

VPD_MTR_CLR = { {000,000,000},{229,160,032},{170,070,000} }

--miliseconds
function vpd_mtr_ms( var, lat, bps, lps, nol, ms )
  var = vb.views["VPD_MTR_VB_BEATS"].value
  lat = vb.views["VPD_MTR_VB_LATENCY"].value
  bps = renoise.song().transport.bpm / 60
  lps = renoise.song().transport.lpb * bps
  nol = renoise.song().selected_pattern.number_of_lines
  ms = ( 960 + lat ) * ( 1 / lps ) * ( nol / var )
  return ms
end

--timer
img_count = 1
but_count = 1
function vpd_mtr_timer( val, acc, img )
  val = vb.views["VPD_MTR_VB_TRAVEL"].value
  acc = vb.views["VPD_MTR_VB_ACCENT"].value
  img = "./icons/metron/metron_x"..img_count.."_ico.png"
  --images
  if val == 2 then
    vb.views["VPD_MTR_BM_METRON"].bitmap = img
    if img_count > 3 then img_count = 2 end
  end
  ---
  if val == 3 then
    vb.views["VPD_MTR_BM_METRON"].bitmap = img
    if img_count == 5 then img_count = 2 end
  end
  ---
  if val == 4 then
    vb.views["VPD_MTR_BM_METRON"].bitmap = img
    if img_count == 4 then img_count = 0 end
  end
  ---
  if val == 5 then
    vb.views["VPD_MTR_BM_METRON"].bitmap = img
    if img_count == 5 then img_count = 0 end
  end
  ---
  if val == 6 then
    vb.views["VPD_MTR_BM_METRON"].bitmap = img
    if img_count == 2 then img_count = 0 end
    if but_count == 4 then img_count = 2 end
    if img_count == 4 then img_count = 0 end
  end
  img_count = img_count + 1
  --buttons
  if ( but_count > val ) then but_count = 1 end
  for i = 1, val do
    vb.views["VPD_MTR_BT_X"..val.."_"..i].color = VPD_MTR_CLR[1]
  end
  if ( acc == but_count ) then
    vb.views["VPD_MTR_BT_X"..val.."_"..but_count].color = VPD_MTR_CLR[3]
  else
    vb.views["VPD_MTR_BT_X"..val.."_"..but_count].color = VPD_MTR_CLR[2]
  end
  but_count = but_count + 1  
  --start timer (after the first step)
  if not renoise.tool():has_timer( vpd_mtr_timer ) then
    renoise.tool():add_timer( vpd_mtr_timer, vpd_mtr_ms() ) --start timer
  end
end

--invoque vpd_mtr_timer
function vpd_mtr_invoque_timer( com )
  com = vb.views["VPD_MTR_VB_TRAVEL"].value
  if ( com == 2 ) then img_count = 3 end 
  if ( com == 3 ) then img_count = 5 end 
  if ( com == 4 ) then img_count = 1 end
  if ( com == 5 ) then img_count = 1 end
  if ( com == 6 ) then img_count = 1 end
  but_count = 1
  
  vpd_mtr_timer()
end

--sequence control
local vpd_current_sequence
function vpd_mtr_seq_idx( spi, com )
  spi = renoise.song().selected_sequence_index
  if ( vpd_current_sequence ~= spi ) then
    vpd_current_sequence = spi
    print( "vpd_current_sequence",vpd_current_sequence )
    ---
    if renoise.tool():has_timer( vpd_mtr_timer ) then
      renoise.tool():remove_timer( vpd_mtr_timer ) --stop timer
      --invoque vpd_mtr_timer
      vpd_mtr_invoque_timer()
    end
    ---
  else
    return
  end
end

--start
function vpd_mtr_start()
  --play song
  play_mode = renoise.Transport.PLAYMODE_RESTART_PATTERN 
  renoise.song().transport:start( play_mode )
  
  --invoque vpd_mtr_timer
  vpd_mtr_invoque_timer()
  if not renoise.tool():has_timer( vpd_mtr_seq_idx ) then
    renoise.tool():add_timer( vpd_mtr_seq_idx, 5 ) --start timer seq
  end
end

--remove timer
function vpd_mtr_remove_timer()
  if renoise.tool():has_timer( vpd_mtr_timer ) then
    renoise.tool():remove_timer( vpd_mtr_timer ) --stop timer
  end
end

--reset buttons color
function vpd_mtr_reset_buttons()
  for i = 1, 2 do
    vb.views["VPD_MTR_BT_X2_"..i].color = VPD_MTR_CLR[1]
  end
  for i = 1, 3 do
    vb.views["VPD_MTR_BT_X3_"..i].color = VPD_MTR_CLR[1]
  end
  for i = 1, 4 do
    vb.views["VPD_MTR_BT_X4_"..i].color = VPD_MTR_CLR[1]
  end
  for i = 1, 5 do
    vb.views["VPD_MTR_BT_X5_"..i].color = VPD_MTR_CLR[1]
  end
  for i = 1, 6 do
    vb.views["VPD_MTR_BT_X6_"..i].color = VPD_MTR_CLR[1]
  end
end

--reset all
function vpd_mtr_reset_all()
  vpd_mtr_remove_timer()
  vpd_mtr_reset_buttons()
  vb.views["VPD_MTR_BM_METRON"].bitmap = "./icons/metron/metron_x0_ico.png"
end

--stop
function vpd_mtr_stop()
  if renoise.tool():has_timer( vpd_mtr_seq_idx ) then
    renoise.tool():remove_timer( vpd_mtr_seq_idx ) --stop timer seq
  end
  vpd_mtr_reset_all()
  renoise.song().transport:stop()
  renoise.song().selected_line_index = 1
end

--buttons monitor '1,2...2,1,3...3,1,2,4...'
function vpd_mtr_monitor( value )
  vpd_mtr_reset_all()
  if value == 2 then
    vb.views["VPD_MTR_RW_X6"].visible = false
    vb.views["VPD_MTR_RW_X5"].visible = false
    vb.views["VPD_MTR_RW_X4"].visible = false
    vb.views["VPD_MTR_RW_X3"].visible = false
    vb.views["VPD_MTR_RW_X2"].visible = true
  end
  if value == 3 then
    vb.views["VPD_MTR_RW_X6"].visible = false
    vb.views["VPD_MTR_RW_X5"].visible = false
    vb.views["VPD_MTR_RW_X4"].visible = false
    vb.views["VPD_MTR_RW_X2"].visible = false
    vb.views["VPD_MTR_RW_X3"].visible = true  
  end
  if value == 4 then
    vb.views["VPD_MTR_RW_X6"].visible = false
    vb.views["VPD_MTR_RW_X5"].visible = false
    vb.views["VPD_MTR_RW_X3"].visible = false
    vb.views["VPD_MTR_RW_X2"].visible = false
    vb.views["VPD_MTR_RW_X4"].visible = true
  end
  if value == 5 then
    vb.views["VPD_MTR_RW_X6"].visible = false
    vb.views["VPD_MTR_RW_X4"].visible = false
    vb.views["VPD_MTR_RW_X3"].visible = false
    vb.views["VPD_MTR_RW_X2"].visible = false
    vb.views["VPD_MTR_RW_X5"].visible = true
  end
  if value == 6 then
    vb.views["VPD_MTR_RW_X2"].visible = false
    vb.views["VPD_MTR_RW_X3"].visible = false
    vb.views["VPD_MTR_RW_X4"].visible = false
    vb.views["VPD_MTR_RW_X5"].visible = false
    vb.views["VPD_MTR_RW_X6"].visible = true
  end
end

--reset values
function vpd_mtr_reset_values()
  vb.views["VPD_MTR_VB_BEATS"].value = 16
  vb.views["VPD_MTR_VB_TRAVEL"].value = 4
  vb.views["VPD_MTR_TX_REPEAT"].text = "4.000"
  vb.views["VPD_MTR_VB_ACCENT"].value = 1
  vb.views["VPD_MTR_VB_LATENCY"].value = 40
end

--repeat
function vpd_mtr_repeat( trv, bts )
  bts = vb.views["VPD_MTR_VB_BEATS"].value
  trv = vb.views["VPD_MTR_VB_TRAVEL"].value
  vb.views["VPD_MTR_TX_REPEAT"].text = ( "%.3f" ):format( bts / trv )
end

--accent
function vbp_mtr_accent( value )
  vb.views["VPD_MTR_VB_ACCENT"].value = 1
  vb.views["VPD_MTR_VB_ACCENT"].max = value
end
--GUI
---------------------------------------------------------------
metron_content = vb:row { margin = 4,
  vb:column { margin = 4, style = "plain",
    vb:row {
      vb:column { height = 101, width = 131,
       vb:bitmap { id = "VPD_MTR_BM_METRON", mode = "transparent", bitmap = "./icons/metron/metron_x0_ico.png" }
      },
      vb:column { spacing = -3,
        vb:row {
          vb:text { height = 21, width = 50, align = "right", text = "Beats" },
          vb:valuebox { id = "VPD_MTR_VB_BEATS", height = 21, width = 50, min = 2, max = 96, value = 16, notifier = function() vpd_mtr_repeat() end,
            tooltip = "Beats Per Pattern\n"..
                      "Divide the pattern according to its number of lines and the Metronome Travel\n[Range 2 to 96]"
          }
        },
        vb:row { height = 6 },
        vb:row {
          vb:text { height = 21, width = 50, align = "right", text = "Travel" },
          vb:valuebox { id = "VPD_MTR_VB_TRAVEL", height = 21, width = 50, min = 2, max = 6, value = 4, notifier = function( value ) vpd_mtr_monitor( value ) vpd_mtr_repeat() vbp_mtr_accent( value ) end,
            tooltip = "Metronome Travel (Steps)\n[Range 2 to 6 steps]"
          }
        },
        vb:row {
          vb:text { height = 21, width = 50, align = "right", text = "Repeat" },
          vb:row { tooltip = "Repetitions in the pattern\nNumber of times that the metronome repeats its travel for each pattern\n"..
                             "For correct synchronization, it is necessary that this number is an integer",
            vb:text { id = "VPD_MTR_TX_REPEAT", height = 21, width = 50, font = "bold", align = "center", text = "4.000",
              
            }
          }
        },
        vb:row {
          vb:text { height = 21, width = 46, align = "right", text = "Accent" },
          vb:row { spacing = -3,
            vb:button {height = 21, width = 7, color = VPD_MTR_CLR[3], active = false },
            vb:valuebox { id = "VPD_MTR_VB_ACCENT", height = 21, width = 50, min = 1, max = 4, value = 1,
              tooltip = "Metronome Accent\n[Range 1 to 6]"
            }
          }
        },
        vb:row { height = 6 },
        vb:row {
          vb:text { height = 21, width = 50, align = "right", text = "Latency" },
          vb:valuebox { id = "VPD_MTR_VB_LATENCY", height = 21, width = 50, min = 0, max = 80, value = 40,
            tooltip = "Metronome Latency\n"..
                      "This value increases or decreases the numerator of the division (~1000ms), so that the separation between beats is shorter or longer. Lowers the value if the CPU is slow (Metron will work faster).\n"..
                      "[Range: 0ms .. +40ms .. +80ms. Default = +40ms (960ms +40ms = 1s)]"
          }
        }
      }
    },
    vb:row { id = "VPD_MTR_RW_X2", spacing = -3, visible = false,
      vb:button { id = "VPD_MTR_BT_X2_1", height = 21, width = 117, text = "1", active = false },
      vb:button { id = "VPD_MTR_BT_X2_2", height = 21, width = 117, text = "2", active = false },
    },
    vb:row { id = "VPD_MTR_RW_X3", spacing = -3, visible = false,
      vb:button { id = "VPD_MTR_BT_X3_2", height = 21, width = 79, text = "2", active = false },
      vb:button { id = "VPD_MTR_BT_X3_1", height = 21, width = 79, text = "1", active = false },      
      vb:button { id = "VPD_MTR_BT_X3_3", height = 21, width = 79, text = "3", active = false },
    },
    vb:row { id = "VPD_MTR_RW_X4", spacing = -3, visible = true,
      vb:button { id = "VPD_MTR_BT_X4_3", height = 21, width = 60, text = "3", active = false },
      vb:button { id = "VPD_MTR_BT_X4_1", height = 21, width = 60, text = "1", active = false },
      vb:button { id = "VPD_MTR_BT_X4_2", height = 21, width = 60, text = "2", active = false },
      vb:button { id = "VPD_MTR_BT_X4_4", height = 21, width = 60, text = "4", active = false },
    },
    vb:row { id = "VPD_MTR_RW_X5", spacing = -3, visible = false,
      vb:button { id = "VPD_MTR_BT_X5_3", height = 21, width = 49, text = "3", active = false },
      vb:button { id = "VPD_MTR_BT_X5_1", height = 21, width = 48, text = "1", active = false },
      vb:button { id = "VPD_MTR_BT_X5_5", height = 21, width = 49, text = "5", active = false },
      vb:button { id = "VPD_MTR_BT_X5_2", height = 21, width = 48, text = "2", active = false },
      vb:button { id = "VPD_MTR_BT_X5_4", height = 21, width = 49, text = "4", active = false },
    },
    vb:row { id = "VPD_MTR_RW_X6", spacing = -3, visible = false,
      vb:button { id = "VPD_MTR_BT_X6_5", height = 21, width = 41, text = "5", active = false },
      vb:button { id = "VPD_MTR_BT_X6_3", height = 21, width = 41, text = "3", active = false },
      vb:button { id = "VPD_MTR_BT_X6_1", height = 21, width = 41, text = "1", active = false },
      vb:button { id = "VPD_MTR_BT_X6_2", height = 21, width = 41, text = "2", active = false },
      vb:button { id = "VPD_MTR_BT_X6_4", height = 21, width = 41, text = "4", active = false },
      vb:button { id = "VPD_MTR_BT_X6_6", height = 21, width = 41, text = "6", active = false },
    },
    vb:row {
      vb:row { spacing = -3,
        vb:button { id = "VPD_MTR_BT_START", height = 21, width = 78, bitmap = "./icons/metron/play_ico.png", notifier = function() vpd_mtr_start() end,
          tooltip = "Start Metron from the beginning of the pattern"
        },
        vb:button { id = "VPD_MTR_BT_STOP", height = 21, width = 78, bitmap = "./icons/metron/stop_first_ico.png", notifier = function() vpd_mtr_stop() end,
          tooltip = "Stop Metron / Jump to first line of the pattern"
        }
      },
      vb:button { id = "VPD_MTR_BT_RESET", height = 21, width = 78, bitmap = "./icons/metron/reset_ico.png", notifier = function() vpd_mtr_reset_values() end,
        tooltip = "Reset Metron with the default values"
      }
    }
  }
}
--dialog
function vpd_invokes_metron_window()
  dialog_metron()
end
metron_dialog = nil
function dialog_metron()
  if ( metron_dialog and metron_dialog.visible ) then metron_dialog:show() return metron_dialog:close() , foreground_dialog() end
  metron_dialog = renoise.app():show_custom_dialog( " ▛ VPDpro. Metron", metron_content, key_handler )
end

If someone is learning to build tools, this would be a pretty good practical example. Regardless of the code, the tool has many basic characteristics along with the skillful use of a pair of fast timers, avoiding the use of observables, as well as a little basic math related to timing synchronization.I also share the images used if someone wants to build and experiment (keep in mind that it is not an tool XRNX):

7757 metron_images_&_lua.zip

Another example of reduction/optimization of a function with iteration:

function vpd_mtr_monitor( value )
  vpd_mtr_reset_all()
  if value == 2 then
    vb.views["VPD_MTR_RW_X6"].visible = false
    vb.views["VPD_MTR_RW_X5"].visible = false
    vb.views["VPD_MTR_RW_X4"].visible = false
    vb.views["VPD_MTR_RW_X3"].visible = false
    vb.views["VPD_MTR_RW_X2"].visible = true
  end
  if value == 3 then
    vb.views["VPD_MTR_RW_X6"].visible = false
    vb.views["VPD_MTR_RW_X5"].visible = false
    vb.views["VPD_MTR_RW_X4"].visible = false
    vb.views["VPD_MTR_RW_X2"].visible = false
    vb.views["VPD_MTR_RW_X3"].visible = true  
  end
  if value == 4 then
    vb.views["VPD_MTR_RW_X6"].visible = false
    vb.views["VPD_MTR_RW_X5"].visible = false
    vb.views["VPD_MTR_RW_X3"].visible = false
    vb.views["VPD_MTR_RW_X2"].visible = false
    vb.views["VPD_MTR_RW_X4"].visible = true
  end
  if value == 5 then
    vb.views["VPD_MTR_RW_X6"].visible = false
    vb.views["VPD_MTR_RW_X4"].visible = false
    vb.views["VPD_MTR_RW_X3"].visible = false
    vb.views["VPD_MTR_RW_X2"].visible = false
    vb.views["VPD_MTR_RW_X5"].visible = true
  end
  if value == 6 then
    vb.views["VPD_MTR_RW_X2"].visible = false
    vb.views["VPD_MTR_RW_X3"].visible = false
    vb.views["VPD_MTR_RW_X4"].visible = false
    vb.views["VPD_MTR_RW_X5"].visible = false
    vb.views["VPD_MTR_RW_X6"].visible = true
  end
end

Is the same as:

function vpd_mtr_monitor( value )
  vpd_mtr_reset_all()
  for i = 2, 6 do
    if ( value == i ) then
      vb.views["VPD_MTR_RW_X"..value].visible = true
    else
      vb.views["VPD_MTR_RW_X"..i].visible = false
    end
  end
end

“value” is sent from a valuebox (notifier…)

In action:

7758 vpdpro-metron_travel.gif

function vpd_mtr_monitor( value )
  vpd_mtr_reset_all()
  for i = 2, 6 do
    vb.views["VPD_MTR_RW_X"..i].visible = (value == i)
  end
end

Or maybe this?

function vpd_mtr_monitor( value )
vpd_mtr_reset_all()
for i = 2, 6 do
vb.views["VPD_MTR_RW_X"..i].visible = (value == i)
end
end

Or maybe this?

Yes!It is still better! :lol:Thanks!

Note: As an additional note, it is first necessary to put all groups of buttons in visible = false, except the one that is first displayed by default in the tool.

Is the same as: (…)

Taken a few (small) steps further…

function vpd_mtr_monitor( value )
  vpd_mtr_reset_all()
  local views = vb.views
  for i = 2, 6 do
    views["VPD_MTR_RW_X" .. i].visible = false
  end
  views["VPD_MTR_RW_X" .. value].visible = true
end

Uses a local reference to vb.views to avoid wastefully accessing the same property a bunch of times.

Also avoids a bunch of unnecessary conditional if statements, and trades that for (less) wastefully setting the visibility of the same view twice instead.

If you want to take it even further still, you can also “un-wrap” the for loop and save even more CPU cycles.

function vpd_mtr_monitor( value )
  vpd_mtr_reset_all()
  local views = vb.views
  views["VPD_MTR_RW_X2"].visible = false
  views["VPD_MTR_RW_X3"].visible = false
  views["VPD_MTR_RW_X4"].visible = false
  views["VPD_MTR_RW_X5"].visible = false
  views["VPD_MTR_RW_X6"].visible = false
  views["VPD_MTR_RW_X" .. value].visible = true
end

Certainly not necessary to go quite this far all the time, but still interesting to keep in mind :slight_smile:

Edit: ffx’s suggestion is also quite elegant, too, and probably closer to what I’d personally use. Still “wastes” CPU cycles on the conditional (i == value) but code-wise it’s very easy on the eyes, which is always a bit more important for me.

@Dblue

Thanks for these tips. I am interested in optimizing the code as much as possible, although this particular tool is not too heavy with the CPU load. It is always good that the code is better to use fewer resources.

Nor do I care if the code is longer, if thanks to it, less resources are consumed.I will review these annotations with the theme of the timers. There it is worthwhile to be careful.

Yep, gladly absorbing the efficiency and style tips here too.

The former for the CPU heavy looping stuff, the latter for the mouse wheel heavy looping stuff!

I am already satisfied with the results of this code for Metron:

Click to view contents
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
---METRON
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------

VPD_MTR_CLR = { {000,000,000},{229,160,032},{170,070,000} }

--miliseconds
function vpd_mtr_ms( var, lat, rns, bps, lps, nol, ms )
  var = vws["VPD_MTR_VB_BEATS"].value
  lat = vws["VPD_MTR_VB_LATENCY"].value
  rns = renoise.song()
  bps = rns.transport.bpm / 60
  lps = rns.transport.lpb * bps
  nol = rns.selected_pattern.number_of_lines
  ms = ( 960 + lat ) * ( 1 / lps ) * ( nol / var )
  return ms
end

--timer
img_count = 1
but_count = 1
function vpd_mtr_timer( val, acc, img )
  val = vws["VPD_MTR_VB_TRAVEL"].value
  acc = vws["VPD_MTR_VB_ACCENT"].value
  img = "./icons/metron/metron_x"..img_count.."_ico.png"
  --images
  if val == 2 then
    vws["VPD_MTR_BM_METRON"].bitmap = img
    if img_count > 3 then img_count = 2 end
  end
  ---
  if val == 3 then
    vws["VPD_MTR_BM_METRON"].bitmap = img
    if img_count == 5 then img_count = 2 end
  end
  ---
  if val == 4 then
    vws["VPD_MTR_BM_METRON"].bitmap = img
    if img_count == 4 then img_count = 0 end
  end
  ---
  if val == 5 then
    vws["VPD_MTR_BM_METRON"].bitmap = img
    if img_count == 5 then img_count = 0 end
  end
  ---
  if val == 6 then
    vws["VPD_MTR_BM_METRON"].bitmap = img
    if img_count == 2 then img_count = 0 end
    if but_count == 4 then img_count = 2 end
    if img_count == 4 then img_count = 0 end
  end
  img_count = img_count + 1
  --buttons
  if ( but_count > val ) then but_count = 1 end
  for i = 1, val do
    vws["VPD_MTR_BT_X"..val.."_"..i].color = VPD_MTR_CLR[1]
  end
  if ( acc == but_count ) then
    vws["VPD_MTR_BT_X"..val.."_"..but_count].color = VPD_MTR_CLR[3]
  else
    vws["VPD_MTR_BT_X"..val.."_"..but_count].color = VPD_MTR_CLR[2]
  end
  but_count = but_count + 1  
  --start timer (after the first step)
  if not rnt:has_timer( vpd_mtr_timer ) then
    rnt:add_timer( vpd_mtr_timer, vpd_mtr_ms() ) --start timer
  end
end

--invoque vpd_mtr_timer
function vpd_mtr_invoque_timer( com )
  com = vws["VPD_MTR_VB_TRAVEL"].value
  if ( com == 2 ) then img_count = 3 end 
  if ( com == 3 ) then img_count = 5 end 
  if ( com == 4 ) then img_count = 1 end
  if ( com == 5 ) then img_count = 1 end
  if ( com == 6 ) then img_count = 1 end
  but_count = 1
  
  vpd_mtr_timer()
end

--sequence control
local vpd_current_sequence
function vpd_mtr_seq_idx( spi, com )
  spi = renoise.song().selected_sequence_index
  if ( vpd_current_sequence ~= spi ) then
    vpd_current_sequence = spi
    print( "vpd_current_sequence",vpd_current_sequence )
    ---
    if rnt:has_timer( vpd_mtr_timer ) then
      rnt:remove_timer( vpd_mtr_timer ) --stop timer
      --invoque vpd_mtr_timer
      vpd_mtr_invoque_timer()
    end
    ---
  else
    return
  end
end

--start
function vpd_mtr_start()
  --play song
  play_mode = renoise.Transport.PLAYMODE_RESTART_PATTERN 
  renoise.song().transport:start( play_mode )
  
  --invoque vpd_mtr_timer
  vpd_mtr_invoque_timer()
  if not rnt:has_timer( vpd_mtr_seq_idx ) then
    rnt:add_timer( vpd_mtr_seq_idx, 5 ) --start timer seq
  end
end

--remove timer
function vpd_mtr_remove_timer()
  if rnt:has_timer( vpd_mtr_timer ) then
    rnt:remove_timer( vpd_mtr_timer ) --stop timer
  end
end

--reset buttons color
function vpd_mtr_reset_buttons()
  for i = 1, 2 do
    vws["VPD_MTR_BT_X2_"..i].color = VPD_MTR_CLR[1]
  end
  for i = 1, 3 do
    vws["VPD_MTR_BT_X3_"..i].color = VPD_MTR_CLR[1]
  end
  for i = 1, 4 do
    vws["VPD_MTR_BT_X4_"..i].color = VPD_MTR_CLR[1]
  end
  for i = 1, 5 do
    vws["VPD_MTR_BT_X5_"..i].color = VPD_MTR_CLR[1]
  end
  for i = 1, 6 do
    vws["VPD_MTR_BT_X6_"..i].color = VPD_MTR_CLR[1]
  end
end

--reset all
function vpd_mtr_reset_all()
  vpd_mtr_remove_timer()
  vpd_mtr_reset_buttons()
  vws["VPD_MTR_BM_METRON"].bitmap = "./icons/metron/metron_x0_ico.png"
end

--stop
function vpd_mtr_stop()
  if rnt:has_timer( vpd_mtr_seq_idx ) then
    rnt:remove_timer( vpd_mtr_seq_idx ) --stop timer seq
  end
  vpd_mtr_reset_all()
  renoise.song().transport:stop()
  renoise.song().selected_line_index = 1
end

--buttons monitor '1,2...2,1,3...3,1,2,4...'
function vpd_mtr_monitor( value )
  vpd_mtr_reset_all()
  for i = 2, 6 do
    vws["VPD_MTR_RW_X"..i].visible = (value == i)
  end
end

--[[
function vpd_mtr_monitor( value )
  vpd_mtr_reset_all()
  for i = 2, 6 do
    if ( value == i ) then
      vws["VPD_MTR_RW_X"..value].visible = true
    else
      vws["VPD_MTR_RW_X"..i].visible = false
    end
  end
end
]]

--reset values
function vpd_mtr_reset_values()
  vws["VPD_MTR_VB_BEATS"].value = 16
  vws["VPD_MTR_VB_TRAVEL"].value = 4
  vws["VPD_MTR_TX_REPEAT"].text = "4.000"
  vws["VPD_MTR_VB_ACCENT"].value = 1
  vws["VPD_MTR_VB_LATENCY"].value = 40
end

--repeat
function vpd_mtr_repeat( trv, bts )
  bts = vws["VPD_MTR_VB_BEATS"].value
  trv = vws["VPD_MTR_VB_TRAVEL"].value
  vws["VPD_MTR_TX_REPEAT"].text = ( "%.3f" ):format( bts / trv )
end

--accent
function vbp_mtr_accent( value )
  vws["VPD_MTR_VB_ACCENT"].value = 1
  vws["VPD_MTR_VB_ACCENT"].max = value
end
--GUI
---------------------------------------------------------------
metron_content = vb:row { margin = 4,
  vb:column { margin = 4, style = "plain",
    vb:row {
      vb:column { height = 101, width = 131,
       vb:bitmap { id = "VPD_MTR_BM_METRON", mode = "button_color", bitmap = "./icons/metron/metron_x0_ico.png" }
      },
      vb:column { spacing = -3,
        vb:row {
          vb:text { height = 21, width = 50, align = "right", text = "Beats" },
          vb:valuebox { id = "VPD_MTR_VB_BEATS", height = 21, width = 50, min = 2, max = 96, value = 16, notifier = function() vpd_mtr_repeat() end,
            tooltip = "Beats Per Pattern\n"..
                      "Divide the pattern according to its number of lines and the Metronome Travel\n[Range 2 to 96]"
          }
        },
        vb:row { height = 6 },
        vb:row {
          vb:text { height = 21, width = 50, align = "right", text = "Travel" },
          vb:valuebox { id = "VPD_MTR_VB_TRAVEL", height = 21, width = 50, min = 2, max = 6, value = 4, notifier = function( value ) vpd_mtr_monitor( value ) vpd_mtr_repeat() vbp_mtr_accent( value ) end,
            tooltip = "Metronome Travel (Steps)\n[Range 2 to 6 steps]"
          }
        },
        vb:row {
          vb:text { height = 21, width = 50, align = "right", text = "Repeat" },
          vb:row { tooltip = "Repetitions in the pattern\nNumber of times that the metronome repeats its travel for each pattern.\n"..
                             "For correct synchronization, it is necessary that this number is an integer",
            vb:text { id = "VPD_MTR_TX_REPEAT", height = 21, width = 50, font = "bold", align = "center", text = "4.000",
              
            }
          }
        },
        vb:row {
          vb:text { height = 21, width = 46, align = "right", text = "Accent" },
          vb:row { spacing = -3,
            vb:button {height = 21, width = 7, color = VPD_MTR_CLR[3], active = false },
            vb:valuebox { id = "VPD_MTR_VB_ACCENT", height = 21, width = 50, min = 1, max = 4, value = 1,
              tooltip = "Metronome Accent\n[Range 1 to 6]"
            }
          }
        },
        vb:space { height = 6 },
        vb:row {
          vb:text { height = 21, width = 50, align = "right", text = "Latency" },
          vb:valuebox { id = "VPD_MTR_VB_LATENCY", height = 21, width = 50, min = 0, max = 80, value = 40,
            tooltip = "Metronome Latency\n"..
                      "This value increases or decreases the numerator of the division (~1000ms), so that the separation between beats is shorter or longer. Lowers the value if the CPU is slow (Metron will work faster).\n"..
                      "[Range: 0ms .. +40ms .. +80ms. Default = +40ms (960ms +40ms = 1s)]"
          }
        }
      }
    },
    vb:row { id = "VPD_MTR_RW_X2", spacing = -3, visible = false,
      vb:button { id = "VPD_MTR_BT_X2_1", height = 21, width = 117, text = "1", active = false },
      vb:button { id = "VPD_MTR_BT_X2_2", height = 21, width = 117, text = "2", active = false },
    },
    vb:row { id = "VPD_MTR_RW_X3", spacing = -3, visible = false,
      vb:button { id = "VPD_MTR_BT_X3_2", height = 21, width = 79, text = "2", active = false },
      vb:button { id = "VPD_MTR_BT_X3_1", height = 21, width = 79, text = "1", active = false },      
      vb:button { id = "VPD_MTR_BT_X3_3", height = 21, width = 79, text = "3", active = false },
    },
    vb:row { id = "VPD_MTR_RW_X4", spacing = -3, visible = true,
      vb:button { id = "VPD_MTR_BT_X4_3", height = 21, width = 60, text = "3", active = false },
      vb:button { id = "VPD_MTR_BT_X4_1", height = 21, width = 60, text = "1", active = false },
      vb:button { id = "VPD_MTR_BT_X4_2", height = 21, width = 60, text = "2", active = false },
      vb:button { id = "VPD_MTR_BT_X4_4", height = 21, width = 60, text = "4", active = false },
    },
    vb:row { id = "VPD_MTR_RW_X5", spacing = -3, visible = false,
      vb:button { id = "VPD_MTR_BT_X5_3", height = 21, width = 49, text = "3", active = false },
      vb:button { id = "VPD_MTR_BT_X5_1", height = 21, width = 48, text = "1", active = false },
      vb:button { id = "VPD_MTR_BT_X5_5", height = 21, width = 49, text = "5", active = false },
      vb:button { id = "VPD_MTR_BT_X5_2", height = 21, width = 48, text = "2", active = false },
      vb:button { id = "VPD_MTR_BT_X5_4", height = 21, width = 49, text = "4", active = false },
    },
    vb:row { id = "VPD_MTR_RW_X6", spacing = -3, visible = false,
      vb:button { id = "VPD_MTR_BT_X6_5", height = 21, width = 41, text = "5", active = false },
      vb:button { id = "VPD_MTR_BT_X6_3", height = 21, width = 41, text = "3", active = false },
      vb:button { id = "VPD_MTR_BT_X6_1", height = 21, width = 41, text = "1", active = false },
      vb:button { id = "VPD_MTR_BT_X6_2", height = 21, width = 41, text = "2", active = false },
      vb:button { id = "VPD_MTR_BT_X6_4", height = 21, width = 41, text = "4", active = false },
      vb:button { id = "VPD_MTR_BT_X6_6", height = 21, width = 41, text = "6", active = false },
    },
    vb:row {
      vb:row { spacing = -3,
        vb:button { id = "VPD_MTR_BT_START", height = 21, width = 78, bitmap = "./icons/metron/play_ico.png", notifier = function() vpd_mtr_start() end,
          tooltip = "Start Metron from the beginning of the pattern"
        },
        vb:button { id = "VPD_MTR_BT_STOP", height = 21, width = 78, bitmap = "./icons/metron/stop_first_ico.png", notifier = function() vpd_mtr_stop() end,
          tooltip = "Stop Metron / Jump to first line of the pattern"
        }
      },
      vb:button { id = "VPD_MTR_BT_RESET", height = 21, width = 78, bitmap = "./icons/metron/reset_ico.png", notifier = function() vpd_mtr_reset_values() end,
        tooltip = "Reset Metron with the default values"
      }
    }
  }
}
--dialog
function vpd_invokes_metron_window()
  dialog_metron()
end
metron_dialog = nil
function dialog_metron()
  if ( metron_dialog and metron_dialog.visible ) then metron_dialog:show() return metron_dialog:close() , foreground_dialog() end
  metron_dialog = renoise.app():show_custom_dialog( " ▛ VPDpro. Metron", metron_content, key_handler )
end

I share the code again in case someone sees something that can be further optimized and he wants to comment…

[sharedmedia=core:attachments:7754]

[sharedmedia=core:attachments:7755]

[sharedmedia=core:attachments:7758]