[Solved] swap note_column. Where to save data to keep them?


(Raul (ulneiz)) #1

I’m trying to build a function that manages to swap two consecutive note columns (only inside a line, not the entire note column).For the moment, I have built a function that changes the selected note’s position, specifically in the right note column.

function pht_msc3_move_note_r()
  local snci = song().selected_note_column_index
  local clm = snci + 1
  if ( clm <= 12 ) and ( song().selected_effect_column == nil ) then
    if not ( song().selected_note_column.is_empty ) then
      if ( song().selected_track.visible_note_columns < clm ) then
        song().selected_track.visible_note_columns = clm
      end
      song().selected_line:note_column( clm ):copy_from( song().selected_note_column )
      song().selected_note_column:clear()
      song().selected_note_column_index = clm
    else
      pht_change_status( "No data to move!" )
    end
  else
    pht_change_status( "Select a valid note column (< 12) to move data!" )
  end
end

But I have already tried to build a function derived from it that allows to invert the values of both note columns.For example, if the original data is these:

F-5 01 72 00 00* - C-4 03 56 00 00

The result would be this:

C-4 03 56 00 00 - F-5 01 72 00 00*

(*this selected)

To do this I create two tables to store the data, or even copy the entire line in a data buffer. But, if I change one of the columns of value notes, it is also saved in that buffer.How can I generate a data buffer that will not change once the data is stored in it? I could use another line or another “temporal track” to duplicate the data there, but I do not like this solution at all.

This is another almost absurd example. The function rotates itself by modifying the data. It is clear that it is badly focused:

function pht_msc3_move_note_r()
  local snci = song().selected_note_column_index
  local clm = snci + 1

  local buffer_pos_1 = song().selected_note_column
  local buffer_pos_2 = song().selected_line:note_column( clm )

  if ( clm <= 12 ) and ( song().selected_effect_column == nil ) then
    if not ( song().selected_note_column.is_empty ) then
      if ( song().selected_track.visible_note_columns < clm ) then
        song().selected_track.visible_note_columns = clm
      end
      song().selected_line:note_column( clm ).note_value = buffer_pos_1.note_value
      song().selected_line:note_column( clm ).instrument_value = buffer_pos_1.instrument_value
      song().selected_line:note_column( clm ).volume_value = buffer_pos_1.volume_value
      song().selected_line:note_column( clm ).panning_value = buffer_pos_1.panning_value
      song().selected_line:note_column( clm ).delay_value = buffer_pos_1.delay_value
      song().selected_line:note_column( clm ).effect_number_value = buffer_pos_1.effect_number_value
      song().selected_line:note_column( clm ).effect_amount_value = buffer_pos_1.effect_amount_value
      ---
      song().selected_note_column.note_value = buffer_pos_2.note_value
      song().selected_note_column.instrument_value = buffer_pos_2.instrument_value
      song().selected_note_column.volume_value = buffer_pos_2.volume_value
      song().selected_note_column.panning_value = buffer_pos_2.panning_value
      song().selected_note_column.delay_value = buffer_pos_2.delay_value
      song().selected_note_column.effect_number_value = buffer_pos_2.effect_number_value
      song().selected_note_column.effect_amount_value = buffer_pos_2.effect_amount_value

      --song().selected_line:note_column( clm ):copy_from( song().selected_note_column )
      --song().selected_note_column:clear()
      --song().selected_note_column_index = clm
    else
      pht_change_status( "No data to move!" )
    end
  else
    pht_change_status( "Select a valid note column (< 12) to move data!" )
  end
end

(It is possible to do the same without breaking down so much data, copying the entire table from note_column).

So, where do I duplicate the data in order to keep them?The fact is that, after modifying any original data in the two columns of notes, keep having the old data stored somewhere. Any ideas?

Thanks!

Note: do not confuse this issue withrenoise.song().tracks[]:swap_note_columns_at(index1, index2),that changes the entire note columns. I just want to interact within a line inside the note_columns.


(Raul (ulneiz)) #2

Ok, I have already found a solution. In LUA is possible to do this method:

local a = {}, --table 1

local b = {} --table 2

a, b = b, a

Since selected_note_column is read-only, it is necessary to swap its properties (note_value, instrument_ value…):

function pht_msc3_move_note_r()
  local snci = song().selected_note_column_index
  local clm = snci + 1
  if ( clm <= 12 ) and ( song().selected_effect_column == nil ) then
    if not ( song().selected_note_column.is_empty ) then
      if ( song().selected_track.visible_note_columns < clm ) then
        song().selected_track.visible_note_columns = clm
      end
      local pos_1 = song().selected_note_column
      local pos_2 = song().selected_line:note_column( clm )
      
      pos_1.note_value, pos_2.note_value = pos_2.note_value, pos_1.note_value
      pos_1.instrument_value, pos_2.instrument_value = pos_2.instrument_value, pos_1.instrument_value
      pos_1.volume_value, pos_2.volume_value = pos_2.volume_value, pos_1.volume_value
      pos_1.panning_value, pos_2.panning_value = pos_2.panning_value, pos_1.panning_value
      pos_1.delay_value, pos_2.delay_value = pos_2.delay_value, pos_1.delay_value
      pos_1.effect_number_value, pos_2.effect_number_value = pos_2.effect_number_value, pos_1.effect_number_value
      pos_1.effect_amount_value, pos_2.effect_amount_value = pos_2.effect_amount_value, pos_1.effect_amount_value

      --song().selected_line:note_column( clm ):copy_from( song().selected_note_column )
      --song().selected_note_column:clear()
      song().selected_note_column_index = clm
    else
      pht_change_status( "No data to move!" )
    end
  else
    pht_change_status( "Select a valid note column (< 12) to move data!" )
  end
end

I usually write by playing many notes at the same time. Then I find it very useful to sort the notes in the note columns as appropriate, where this can be very fast, something you can not do with the mouse (as far as I know)…

Solved!


(ffx) #3

Does this work?:

local indices = {"note", "instrument", "volume", "panning", "delay", "effect_number", "effect_amount"}
local pos_1 = song().selected_note_column
local pos_2 = song().selected_line:note_column( clm )
for i, val in ipairs(indices) do
 pos_1[val .. "_value"],pos_2[val .. "_value"] =pos_2[val .. "_value"],pos_1[val .. "_value"]
end

(joule) #4

Off topic…

Don’t do this:

local song = renoise.song
local something = song():track(1)

Do this:

local song = renoise.song()
local something = song:track(1)

I’m pretty sure it will save you some time both when typing and when executing :slight_smile:


(Raul (ulneiz)) #5

Off topic…

Don’t do this:

local song = renoise.song
local something = song():track(1)

Do this:

local song = renoise.song()
local something = song:track(1)

I’m pretty sure it will save you some time both when typing and when executing :slight_smile:

If I do that, it is necessary to define **local song = renoise.song()**within all the functions that need it.

I use a global: song = renoise.song for the whole tool, and then I use **song()**within the functions that are needed.Regarding the performance (number of calls) is there a difference?

song() used like this is slower or somewhat harmful?I do not mind adding 2 parentheses, if I avoid to save writing the same “local” dozens of times.The context of the tool are dozens of functions that need it.

The reason is that it is not possible to write a global or localsong = renoise.song(), unless you delay loading the tool after loading the song.


(Raul (ulneiz)) #6

Does this work?:

local indices = {"note", "instrument", "volume", "panning", "delay", "effect_number", "effect_amount"}
local pos_1 = song().selected_note_column
local pos_2 = song().selected_line:note_column( clm )
for i, val in ipairs(indices) do
pos_1[val .. "_value"],pos_2[val .. "_value"] =pos_2[val .. "_value"],pos_1[val .. "_value"]
end

Yes! Finally, the function stays that way:

function pht_msc3_move_note_r()
  local snci = song().selected_note_column_index
  local clm = snci + 1
  if ( clm <= 12 ) and ( song().selected_effect_column == nil ) then
    if not ( song().selected_note_column.is_empty ) then
      if ( song().selected_track.visible_note_columns < clm ) then
        song().selected_track.visible_note_columns = clm
      end
      if ( pht_msc3_swap_row_nc == true ) then
        local pos_1 = song().selected_note_column
        local pos_2 = song().selected_line:note_column( clm )
        local id = { "note", "instrument", "volume", "panning", "delay", "effect_number", "effect_amount" }
        for i, val in ipairs( id ) do
          pos_1[val.."_value"], pos_2[val.."_value"] = pos_2[val.."_value"], pos_1[val.."_value"]
        end
      else
        song().selected_line:note_column( clm ):copy_from( song().selected_note_column )
        song().selected_note_column:clear()
      end
      song().selected_note_column_index = clm
    else
      pht_change_status( "No data to move/swap!" )
    end
  else
    pht_change_status( "Select a valid note column (< 12) to move/swap data!" )
  end
end

Saving a few lines is almost always welcome. Thanks!This function works to the right. To the left is very similar. I have added a switch, so you can run two different things, move or swap.Yesterday I composed an orchestral base of 3 minutes, and I yearned a couple of functions like these…


(joule) #7

If I do that, it is necessary to define **local song = renoise.song()**within all the functions that need it.

It’s more than ten times faster here, so if you access it a lot in a big tool it might make a difference. I just wanted to note it since it’s so easy to do it slightly better :slight_smile:

You can have a global rns = renoise.song() and update it via a new document observable.


(Raul (ulneiz)) #8

It’s more than ten times faster here, so if you access it a lot in a big tool it might make a difference. I just wanted to note it since it’s so easy to do it slightly better :slight_smile:

You can have a global rns = renoise.song() and update it via a new document observable.

Where and how to put the “new document observable”?

This is the typical structure that I’m used to use in my recent tools:

--
-- tool name
--

--main globals
-------------------------------------------------------------------------------------------------
dialog = nil
vb = renoise.ViewBuilder()
vws = vb.views
song = renoise.song() -------------------> where and how to put the "new document observable"?????????????
rnt = renoise.tool()
rna = renoise.app()
rns_version = renoise.RENOISE_VERSION
api_version = renoise.API_VERSION

--other globals
-------------------------------------------------------------------------------------------------

--require
-------------------------------------------------------------------------------------------------
require ("lua/xxx") -- more song ???????????????????, include many functions and more windows
require ("lua/yyy") -- more song ???????????????????, include many functions and more windows
require ("lua/zzz") -- more song ???????????????????, include many functions and more windows

--functions (many functions)
-------------------------------------------------------------------------------------------------
function name_function_01()
  local sti = song.selected_track_index --song ???????????????????
  local sii = song.selected_instrument_index --song ???????????????????
  --...
  --...
end
---
function name_function_90()
  rna.window.active_middle_frame = renoise.ApplicationWindow.MIDDLE_FRAME_INSTRUMENT_PHRASE_EDITOR
  song.selected_instrument.phrase_editor_visible = true --song ???????????????????
  song.selected_instrument.phrase_playback_mode = renoise.Instrument.PHRASES_PLAY_KEYMAP --song ???????????????????
  --...
  --...
end

--main content gui
-------------------------------------------------------------------------------------------------
TOOL_GEN_CONTENT = vb:row {
  vb:button {
    text = "Button"
  },
  --...
  --...
}

--show dialog
-------------------------------------------------------------------------------------------------
function show_tool_dialog()
  if ( dialog and dialog.visible ) then dialog:show() return end
  dialog = rna:show_custom_dialog( pht_title, TOOL_GEN_CONTENT, pht_keyhandler )
end

--register menu entry
-------------------------------------------------------------------------------------------------
renoise.tool():add_menu_entry {
  name = "Main Menu:Tools:Name_Tool...",
  invoke = function() show_tool_dialog() end
}
Click to view contents

– tool name

–main globals


dialog = nil

vb = renoise.ViewBuilder()

vws = vb.views

song = renoise.song() -------------------> where and how to put the “new document observable”???

rnt = renoise.tool()

rna = renoise.app()

rns_version = renoise.RENOISE_VERSION

api_version = renoise.API_VERSION

–other globals


–require


require (“lua/xxx”) – more song???, include many functions and more windows

require (“lua/yyy”) – more song???, include many functions and more windows

require (“lua/zzz”) – more song???, include many functions and more windows

–functions (many functions)


function name_function_01()

local sti = song. selected_track_index --song ???

local sii = song. selected_instrument_index --song ???

–…

–…

end


function name_function_90()

rna.window.active_middle_frame = renoise.ApplicationWindow.MIDDLE_FRAME_INSTRUMENT_PHRASE_EDITOR

song. selected_instrument.phrase_editor_visible = true --song ???

song. selected_instrument.phrase_playback_mode = renoise.Instrument.PHRASES_PLAY_KEYMAP --song ???

–…

–…

end

–main content gui


TOOL_GEN_CONTENT = vb:row {

vb:button {

text = “Button”

},

–…

–…

}

–show dialog


function show_tool_dialog()

if ( dialog and dialog.visible ) then dialog:show() return end

dialog = rna:show_custom_dialog( pht_title, TOOL_GEN_CONTENT, pht_keyhandler )

end

–register menu entry


renoise.tool():add_menu_entry {

name = “Main Menu:Tools:Name_Tool…”,

invoke = function() show_tool_dialog() end

}

In all my previous tools I have used local song = renoise.song() within each function. But I would like to know how to make a global one to song = renoise.song(), and then within each function use song. or song: where necessary.

So, where to place and how to place the “new document observable” to avoid the loading error?

Note: inside GlobalMidiActions.lua and GlobalOSCActions.lua is usedoutside the functions a local song = renoise.song , and inside the functions is used song(). or **song():**why? Is it 10 times slower?


(danoise) #9

@Raul: just create the observable anywhere in your main.lua, just like you’re adding the “renoise.tool():add_menu_entry” ,etc.

You can have a global rns = renoise.song() and update it via a new document observable.

Yes, if you are planning to make an auto-launching tool [1], this is needed as the song is not necessarily available at startup time.

With auto-launching tools, there are two particular scenarios to be aware of (that need testing):

  • Starting Renoise with the tool already installed

  • Installing the tool for the first time

I’ve forgotten either one from time to time. Eventually, someone notices and sends me a bug report :smiley:

[1] By “auto-launching”, I mean a tool that doesn’t wait for the user to take some kind of action, but actively “does stuff” on startup.


(joule) #10

Yeah… it’s a tounge-in-cheek operation indeed. I thought of adding this to the speed optimization thread. Do you think it’s correct, or am I missing something in regards to the installation scenario?

song = nil

-- Tool code here.

function setup()
  song = renoise.song()
end

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

-- Or tool code here. It doesn't matter.

-- Optional segment below, showing the simplest way to avoid errors with tools
-- that need "auto-launching".

function main()
  -- Do stuff when the tool is loaded.
  -- Set some global bool and check, if you only want main() being executed once.
end

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

EDIT: The installation issue might be due to the bad habit of executing main() top-level in the tool? It might be better practice to do that via app_new_document_observable as well (and store a bool if you only want to do it once)

EDIT 2: This should be a safe boiler-plate for any scenario? Note that the order in which notifiers are registered is also reflected in the order by which they are executed, which is nice.


(Raul (ulneiz)) #11
local song

-- Tool code here.

function setup()
  song = renoise.song()
end

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

As I understand with this code, “song” is a local, that only serves inside the main.lua file. If you have other files through require, this “local song” would not work. I believe (this I would have to prove it).

The issue would be to find an easy way to define “song = renoise.song()” and that song is a global one that works for all attachments by require( “lua/file_1” )

The mother folder of the tool would have these files:

  • main.lua
  • manifest.xml
  • preferences.xml
  • lua (folder):
    • file_1.lua
    • file_2.lua
    • file_9.lua

Maybe for a global, should it be like that?:

song = nil

function setup()
  song = renoise.song()
end

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

-- Require files

-- Tool code here

:huh:.This should work for all the required files as well, I suppose.I understand that renoise.tool().app_new_document_observable:add_notifier(setup), only runs once when loading a new song, or when loading renoise (which creates a new song as well).

All this is true?I have always thought that in the function setup(), “song” could only be used within this function, not outside (because it must be a local).But I suppose that to declare “song” before, outside as global, will work for all functions inside the entire tool (all the files .lua).

All of that is very important. All the functions of the tool depend on it. And so it would be very simple to invoke " song." or " song:" within all the functions of the entire tool, including the .lua files invoked from require().

I would like this whole thing to be very clear.

Additional note: I would like to know why GlobalMidiAction.lua and GlobalOSCactions.lua use song(). or song(): defining the local previously like this: local song = renoise.song. It is not supposed that local song = renoise.song() is 10 times more faster???


(joule) #12

Ah sorry. I think you’re correct about song = nil being better. Fixed.

The examples you’re referring to are probably made that way not to complicate matters too much for new users - trying to stay on point. (You’ll also see examples using the pattern iterator, even though there are way faster ways to do things). More importantly, there is the rule about not overdoing optimizations. When only a few calls are made, the speed gain of optimizations are negligible.

But since you’re trying to cache renoise.song, I thought I’d share the best way of doing it.

Regarding where variables are accessible. That’s what’s called its “scope”, and is indeed important to understand. A variable will be accessible on the same “level” and all levels below from where it’s declared. For example, if you define local j inside a for loop, it is only accessible within that loop. If you define it right before the for loop, it will remain accessible after the loop (set to the last value). I think you’re correct that “song = nil” will make song accessible from all files. Fun fact: it’s really stored in the _G table as _G.song (G implying global). That’s your tools “environment” table, which is a table with its top level listing everything that is “global”.

http://lua-users.org/wiki/ScopeTutorial


(Raul (ulneiz)) #13

Ah sorry. I think you’re correct about song = nil being better. Fixed.

The examples you’re referring to are probably made that way not to complicate matters too much for new users - trying to stay on point. (You’ll also see examples using the pattern iterator, even though there are way faster ways to do things). More importantly, there is the rule about not overdoing optimizations. When only a few calls are made, the speed gain of optimizations are negligible.

But since you’re trying to cache renoise.song, I thought I’d share the best way of doing it.

Regarding where variables are accessible. That’s what’s called its “scope”, and is indeed important to understand. A variable will be accessible on the same “level” and all levels below from where it’s declared. For example, if you define local j inside a for loop, it is only accessible within that loop. If you define it right before the for loop, it will remain accessible after the loop (set to the last value). I think you’re correct that “song = nil” will make song accessible from all files. Fun fact: it’s really stored in the _G table as _G.song (G implying global). That’s your tools “environment” table, which is a table with its top level listing everything that is “global”.

http://lua-users.org/wiki/ScopeTutorial

Thanks!

Ok,I just built a tool template that has just the specific scheme that I would like to use always in my future tools:

template tool:8071 com.ulneiz.Rodent.xrnx

See how all the global ones are ordered, which can be used in all the .lua files of the tool.Finally, the global song is defined as follows:

rnt = renoise.tool()
---
song = nil
function song_obs()
  song = renoise.song()
end
rnt.app_new_document_observable:add_notifier( song_obs )

In this way, it works correctly when loading renoise for the first time, and also when loading any new song afterwards, but return an error after installing the tool, when executing any function**( …failed in one of its notifiers. main.lua ?? attempt to index global “song” a nil value ) :blush:** :blush: :blush:.What is missing here???If you reload the tools (Tools/Reload all Tools) this error disappears. :unsure: :unsure: :unsure:

In summary:

  1. start renoise: ok!
  2. new song: ok!
  3. install tool: fail!!!

Thus, within each function, it is possible to use song. or song: without defining a previous location (local song = renoise.song ()).

As it is now defined “song” as global, would you get the maximum performance (lower number of calls)?

As I understand it, the global “song” is defined only once, and it must be above the code, at the beginning.

But, how to fix the tool to avoid any error, maintaining the structure and the global song?


(Raul (ulneiz)) #14

Ok**rnt.app_new_document_observable:add_notifier( song_obs )**it only acts when loading a new song, but not when installing a new tool (here it is not loading a new song).Something is missing that says that when you install the tool, update song by reading the function song_obs()

Solving this, everything would be solved, I believe.


(joule) #15

Cool… forgot one thing it seems. You need to run song_obs() ‘manually’ ONCE from the first function you call to catch this edge case.

In your case, it would be a good choice to run it someplace in top of the function that shows your GUI.


(Raul (ulneiz)) #16

Cool… forgot one thing it seems. You need to run song_obs() ‘manually’ ONCE from the first function you call to catch this edge case.

In your case, it would be a good choice to run it someplace in top of the function that shows your GUI.

How? I can not do this:

rnt = renoise.tool()
---
song = nil
function song_obs()
  song = renoise.song()
end

song_obs() -->>>>>>>>>>>>>>>>>>>>> return error!

rnt.app_new_document_observable:add_notifier( song_obs )

I think the GUI has nothing to do, since it does not perform any function where the glogal “song” is required. The problem is executing the function song_obs()

Is not there any _observable to run once after installing the tool?


(joule) #17

Unfortunately not.

Put it at the top of rdn_main_dialog(). Otherwise you’ll try to access song before it’s available. The point being you just have to make sure it’s being ran once (via what the user does), so it also catches this installation scenario.


(Raul (ulneiz)) #18

Unfortunately not.

Put it at the top of rdn_main_dialog(). Otherwise you’ll try to access song before it’s available. The point being you just have to make sure it’s being ran once (via what the user does), so it also catches this installation scenario.

If I do that, it will be necessary to invoke the song_obs() function within all the functions that are responsible for showing a new window (of the same tool).There must be some way to force the reading of a function immediately after installing a tool.

Would this work?

rnt = renoise.tool()
---
song = nil
function song_obs()
  song = renoise.song()
end

if ( song == nil ) and not ( rnt.app_new_document_observable:has_notifier( song_obs ) ) then
  rnt.app_new_document_observable:add_notifier( song_obs )
end

(Raul (ulneiz)) #19

Perfect!!! Work!!! :slight_smile: :slight_smile: :slight_smile:


(joule) #20

Nah… I don’t think so. Is your GUI+buttons really working even when installing now?

I think this is the practical solution… put at the top of main.lua.

song = nil

function song_obs()
  song = renoise.song()
end

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

if pcall(function() return renoise.song() end) then
  song = renoise.song() -- catching installation scenario
end