[Solved] Help LUA: function to create a Group with two Tracks, one col

Hi joule.I appreciate your dedication.Thank you very much again!!!

  1. for FX visibility:
local group_settings = {
ten_octaves = {
  name = "Octaves",
  visible_effect_columns = 1, -- 1-8 (0 not)
-- collapsed = true,
-- group_collapsed = true,
  color = { 0xFF, 0xFF, 0xFF },
  • " collapsed" and " group_collapsed" do the same, collapse the entire group.
  • " visible_effect_columns" inside the group work only with 1-8 valor, 0 no work. I guess it is only for the track, not for the group.

renoise.song().tracks[].visible_effect_columns, _observable

-> [number, 1-8 OR 0-8, depending on the track type] <------- depending on the track type???

It can work well: inserting below…song.tracks[song.selected_track_index + 1].group_parent.collapsed = true

function insert_gt(group_setting)
  local song = renoise.song()
  local i, group_index, group_setting = 1, song.selected_track_index + 1, group_settings[group_setting]
  song:insert_group_at(group_index)
  for parameter_name, parameter_value in pairs(group_setting) do
    if not (parameter_name == "tracks") then
      song:track(group_index)[parameter_name] = parameter_value
    end
  end
  for _, track_setting in ipairs(group_setting.tracks) do
    local group_index = group_index + i
    i = i + 1
    insert_track(song.selected_track_index, group_index, track_setting)
  end
  song.tracks[song.selected_track_index + 1].group_parent.collapsed = true
end

This solution is not very elegant.I do not know if can do otherwise, within the local group_settings = { :wacko:

============

I tested the code with the two new lines,and it seems to work fine! :slight_smile:

Except the parameter of the FX column in original track (I think that no matter), all other values are sorted correctly(this to me is an enormous advance, very useful for composing melodies).

I do not know how you want to proceed now for complete the code. I propose the following (after ordering the notes in 10 octave tracks):

  1. Expand automatically the all octave tracks with notes.Leave the rest collapsed.
  2. Select the first octave track containing notes. Ready!
  • Apart,a second function that eliminates the empty octaves, complying with points 1 and 2.You’ve thought the same?

At the end the composer would have 2 options in the drop-down menu.

Most melodies occupy between 1 and 3 or 4 octaves.The end result with sorted notes is not as heavy.But with the 2 options composer could expand the melody in more octaves, and then delete the empty tracks to end (or even adding another function to do so).

Personally, if this works this way,I use this future tool a lot!!!

Hi joule.I appreciate your dedication.Thank you very much again!!!

  1. for FX visibility:
local group_settings = {
ten_octaves = {
name = "Octaves",
visible_effect_columns = 1, -- 1-8 (0 not)
-- collapsed = true,
-- group_collapsed = true,
color = { 0xFF, 0xFF, 0xFF },

Yes, this should work just fine. Uncomment whatever line works. Don’t alter the insert_gt() function! Anything you add to the table is being applied in the “for parameter_name, parameter_value…” loop.

I’ll add the remaining things you mentioned and package it all…

But honestly, I don’t see how you can find this useful :slight_smile: Isn’t it just more tedious navigating thru these columns? I could possibly see some benfit if you’re inclined to really dive into dense humanized midi recordings.

Yes, this should work just fine. Uncomment whatever line works. Don’t alter the insert_gt() function! Anything you add to the table is being applied in the “for parameter_name, parameter_value…” loop.

I’ll add the remaining things you mentioned and package it all…

Okay!!! :slight_smile:

But honestly, I don’t see how you can find this useful :slight_smile: Isn’t it just more tedious navigating thru these columns? I could possibly see some benfit if you’re inclined to really dive into dense humanized midi recordings.

No, on the contrary!I have no problem navigating between tracks.The tool will help us understand the melody, and make arrangements later.When the group deployed annoying, collapse completely and go.The tool always works same, it is not hard to get used.Only used in specific instruments.For my future compositions will be of great help, especially with the style orchestral or soundtrack (yes, I use Renoise for compose this styles :yeah:).

Here is the basic tool. It doesn’t remove any unused octaves, though.

The collapsed parameter for the group doesn’t work as expected, but it works when setting it from the lua console. I wonder if this could be a bug in the API… not sure.

Here is the basic tool. It doesn’t remove any unused octaves, though.

I tested the tool and it works. Can you develop expand octaves automatically and select first with notes? …and remove any unused octaves?This seems also complicated.

The collapsed parameter for the group doesn’t work as expected, but it works when setting it from the lua console. I wonder if this could be a bug in the API… not sure.

Yes, I’ve had the same thought.That is why I have insisted so much on the theme of the collapse of the group.

Can insert as “a solution” inside_function insert_gt(group_setting)_:

  • song.tracks[song.selected_track_index + 1].group_parent.collapsed = true

… I do not like much, but if it works well.

I just found another bug on the functioning. When two notes equal, one above the other, the second is turned OFF after conversion.

For example:

If is:

==============

C-4 00 4F

C-4 00 4F <-----

OFF

==============

Converted is:

==============

C-4 00 4F

OFF00 4F <-----

OFF

==============

Can’t replicate it here. Do you have an xrns?

Yes

6761 ulneiz-Piano-Group-v2_12-OCTAVES.xrns

Look what happens with the first 2 notes.This would be in case of using an alphanumeric keyboard.

Edit: in octave 0something different happens in line 48, but here it is working properly. Maybe it has something to do.

Ah… I see what is happening here. Most of the time when scripting something, you will find that there are special cases that you forgot about. 90% of the time, such bugs are usually fixed with a simple if-statement that was forgotten :slight_smile:

In this case the error happens if a note off occur on the same line as the next note (when having the same note value as the previous). Should be possible to fix by simply never allowing the converter to overwrite anything with a note off.

Change this line:

if (notes[i].column == notes[i+1].column) then

To:

if (notes[i].column == notes[i+1].column) and (song:pattern(notes[i+1].pattern):track(dest_track):line(notes[i+1].line):note_column(dest_column).note_value == 121) then

(Appearantly, 121 is the value an empty note cell has)

Ooook! Tested in song composed with alphanumeric keyboard and other song composed with MIDI keyboard. Work ok! ^_^You are a machine!

I think all that is missing is to add a function to delete tracks without notes within the group.Scan tracks without any written parameter and eliminate them.So the result would be very clean.

Delete empty octaves whithin the group:

6762 delete-octaves-empty.png

In this case, the red tracks are empty. Remove automatically.

Suggestion to try out:

  1. Make a simple script that deletes the selected track

  2. Make a script that checks if the selected track is empty or not

  3. Make a script that checks if the selected track is empty thru-out the whole song or not.

  4. Combine 3 with 1.

  5. Implement into the tool

:slight_smile:

Suggestion to try out:

  1. Make a simple script that deletes the selected track

  2. Make a script that checks if the selected track is empty or not

  3. Make a script that checks if the selected track is empty thru-out the whole song or not.

  4. Combine 3 with 1.

  5. Implement into the tool

:slight_smile:

As from point two I get lost in a sea of doubts! :unsure: :unsure: :unsure:

I have clear ideas of what I want to do, but I need to learn a lot of code.Only with the development of this tool I’m learning a lot, because I understand which is the ultimate objective, on the basis on a code well written and tidy…

As for point 2: If you use the LUA terminal to look around … oprint(renoise.song():pattern(a pattern index):track(a track index)) you will find .is_empty … So all you have to do is check if this value is true or not.

The delete track function (method?) can be found with ctrl-f in the API docs :slight_smile:

Hi joule. Thanks for help!

Tonight I try to build it, but I think I will not be able to. :wacko:

Yes,I know search the API documents (with Ctrl+F), I’m on it constantly.

With _delete_track_at(song.selected_track_index)_delete the selected track. But I can not relate to order scripts look only within the group and find the empty tracks and only delete them.And to do this the code must do a sweep of all patterns, but only within the group.

I search also in forum. In November, fladd propose:https://forum.renoise.com/t/delete-note-columns-from-left/37580

In forum are not many examples of code to use related to this, but other related ideas and requests. ^_^Two of them:

  1. Erase all tracks empty (or specific), which says fladd.
  2. Erase all samples in instrument not used in song.

In both I have not found any solution. Would be functions related to clean quickly things left over…

First you need a major for loop going thru octave track by octave track. “track_index = first_octave_track + 9, first_octave_track, -1” is your clue. This will iterate backwards which fits this case to avoid any mismatch when deleting tracks in the process.

Within this loop you need to check track.is_empty, pattern by pattern.

Something like:

local was_empty = true

for _, pattern in ipairs(renoise.song().patterns) do

if not pattern:track(track_index).is_empty then

was_empty = false

end

end

if not was_empty then

delete_track_at(track_index)

end

I’ve pretty much given you everything here except all syntax and where to put it.

local group_settings = {
ten_octaves = {
  name = "Octaves",
  color = { 0xFF, 0xFF, 0xFF },
  color_blend = 0,
  collapsed = false,
  tracks = {
  { name = "Oct 0", volume_column_visible = true, delay_column_visible = true, collapsed = false, color = { 0xFF,0xFF,0xFF }, color_blend = 5 },
  { name = "Oct 1", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0xFF,0xFF,0xFF }, color_blend = 15 },
  { name = "Oct 2", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0xFF,0xFF,0xFF }, color_blend = 5 },
  { name = "Oct 3", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0xFF,0xFF,0xFF }, color_blend = 15 },
  { name = "Oct 4", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0xFF,0xFF,0xFF }, color_blend = 5 },
  { name = "Oct 5", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0xFF,0xFF,0xFF }, color_blend = 15 },
  { name = "Oct 6", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0xFF,0xFF,0xFF }, color_blend = 5 },
  { name = "Oct 7", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0xFF,0xFF,0xFF }, color_blend = 15 },
  { name = "Oct 8", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0xFF,0xFF,0xFF }, color_blend = 5 },
  { name = "Oct 9", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0xFF,0xFF,0xFF }, color_blend = 15 },
  },
  },
}
local column_names = {
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
}

function insert_track(track_index, group_index, track_setting)

  renoise.song():insert_track_at(track_index)
  local track = renoise.song():track(track_index)
  track.visible_effect_columns, track.visible_note_columns = 0, 12, false -- track.volume_column_visible
  for parameter_name, parameter_value in pairs(track_setting) do
   track[parameter_name] = parameter_value
  end
  for note_column = 1, 12 do
    track:set_column_name(note_column, column_names[note_column])
  end
  renoise.song():add_track_to_group(track_index, group_index)

end

        
function insert_gt(group_setting)

  local song = renoise.song()
  local i, group_index, group_setting = 1, song.selected_track_index + 1, group_settings[group_setting]
  song:insert_group_at(group_index)
  for parameter_name, parameter_value in pairs(group_setting) do
    if not (parameter_name == "tracks") then
      song:track(group_index)[parameter_name] = parameter_value
    end
  end
  for _, track_setting in ipairs(group_setting.tracks) do
    local group_index = group_index + i
    i = i + 1
    insert_track(song.selected_track_index, group_index, track_setting)
  end
  song.tracks[song.selected_track_index + 1].group_parent.collapsed = true -- collapse FX in Group
end

function explode_pitches(track_index, first_octave_track_index)

  local song = renoise.song()
  local track = song:track(song.selected_track_index)
  local pattern_iterator = song.pattern_iterator:note_columns_in_track(track_index, true)
  local notes = { }
  local sort_per_column = function(a,b)
      if a.column < b.column then return true
      elseif a.column > b.column then return false
      elseif a.pattern < b.pattern then return true
      elseif a.pattern > b.pattern then return false
      elseif a.line < b.line then return true
      elseif a.line > b.line then return false
      end
    end
    
  for pos, note_column in pattern_iterator do
    if not note_column.is_empty then
      table.insert(notes, { note_column = note_column, pattern = pos.pattern, track = pos.track, column = pos.column, line = pos.line })
    end
  end
  
  table.sort(notes, sort_per_column)

  for i, note in ipairs(notes) do
    if not (note.note_column.note_value == 120) then
      local dest_track = first_octave_track_index + math.floor(note.note_column.note_value/12)
      local dest_column = (note.note_column.note_value % 12) + 1
      song:pattern(note.pattern):track(dest_track):line(note.line):note_column(dest_column):copy_from(song:pattern(note.pattern):track(note.track):line(note.line):note_column(note.column))
      if notes[i+1] then
        if (notes[i].column == notes[i+1].column) and (song:pattern(notes[i+1].pattern):track(dest_track):line(notes[i+1].line):note_column(dest_column).note_value == 121) then
        -- if (notes[i].column == notes[i+1].column) then
          local next_note = song:pattern(notes[i+1].pattern):track(dest_track):line(notes[i+1].line):note_column(dest_column)
          next_note.note_value = 120
          next_note.delay_value = notes[i+1].note_column.delay_value
        end
      end
    end
  end

  --song:delete_track_at(song.selected_track_index) -- delete original track
  local first_octave_track = renoise.song().selected_track_index + 1
  local track_index = first_octave_track + 9, first_octave_track, -1
  local was_empty = true
    for _, pattern in ipairs(renoise.song().patterns) do
    if not pattern:track(track_index).is_empty then
      was_empty = false
    end
    end
    if not was_empty then
      delete_track_at(track_index +1)
    end
end

function rebuild_menu()

  local song, menu_entry = renoise.song(), "Pattern Editor:Track:Explode Pitches To Octaves"

  if renoise.tool():has_menu_entry(menu_entry) then
    renoise.tool():remove_menu_entry(menu_entry)  
  end

  if (song.selected_track.type == renoise.Track.TRACK_TYPE_SEQUENCER) then
    renoise.tool():add_menu_entry {
    name = menu_entry,
    invoke = function() insert_gt("ten_octaves")
               explode_pitches(song.selected_track_index, song.selected_track_index + 1)
             end }
  end
  
end

function start()
  renoise.song().selected_track_observable:add_notifier(rebuild_menu)
  rebuild_menu()
end  

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

Now I have the code in this way.I have added some details…

On the line 101I have added your loop,within the function explode_pitches, below:

local next_note = song:pattern(notes[i+1].pattern):track(dest_track):line(notes[i+1].line):note_column(dest_column)
          next_note.note_value = 120
          next_note.delay_value = notes[i+1].note_column.delay_value
        end
      end
    end
  end

  --song:delete_track_at(song.selected_track_index) -- delete original track
  local first_octave_track = renoise.song().selected_track_index + 1
  local track_index = first_octave_track + 9, first_octave_track, -1
  local was_empty = true
    for _, pattern in ipairs(renoise.song().patterns) do
    if not pattern:track(track_index).is_empty then
      was_empty = false
    end
    end
    if not was_empty then
      delete_track_at(track_index +1)
    end
end

The code does not error, but does nothing.I understand that the location of the loop is correct,just after build and sort notes by octave…

Is this correct? :

local track_index = first_octave_track + 9, first_octave_track, -1

I think you’re nearly there, but you have to make a loop stepping thru all the track_indexes. A variable statement (local…) doesn’t make sense there.

It should be something like:

for track_index = first_octave_track + 9, first_octave_track, -1 do

[[all your code added]]

end

Loops are your friend! The basic syntax is:

for i=10,1,-1 do
print(i)
end

Every time the loop is looping, all code INSIDE the loop will believe that “i” has a certain value. In this case the value will go from 10 to 1, each looping will add -1 to “i” (=subtracting 1). The last parameter is optional but useful in case you want “i” to step backwards (decrease), which is the most convenient this time.

The first time (first iteration), the loop will believe “i” to be 10. The next iteration “i” will be 9. When “i” reaches 1 the program will exit the loop and continue. Any code after the loop has no clue what “i” is - it was only a local variable within the loop scope.

Later on you will use for pairs/ipairs loops to easily go step by step thru tables. They have quite a simple syntax as well.

PS. I think it might be confusing for a beginner that the code uses variables like track_index and so on. These have nothing to do with what’s in the Renoise API, but are just names that we decide to use for clarity. You might as well name such a variable “t_i” if that suits your taste. It’s all about generating numbers (variables) that we can use for accessing data in the Renoise API or any table. Like creating our own counter that is usable for how Renoise API is indexing all its stuff…

Solved!!! :w00t: :w00t: :w00t:

local first_octave_track = renoise.song().selected_track_index +1
  for track_index = first_octave_track + 9, first_octave_track, -1 do
    local was_empty = false
    for _, pattern in ipairs(renoise.song().patterns) do
      if not pattern:track(track_index).is_empty then
        was_empty = true
      end
    end
    if not was_empty then
      song:delete_track_at(track_index)
    end
  end
    --song:delete_track_at(song.selected_track_index -1) -- delete original track (not work correctly; jump first track inside group)

Now I have exactly the tool I want!!!

I need to add a couple more details, butall the main is done.I have yet to do more tests, but I think it works fine…

^^ ^^ ^_^I am very happy with this new tool.Thank you very much for all joule!!!If I have more questions, I keep comment.When the tool is finished, I share here.

A question a little convoluted: an order that meets the following: How select always the first track inside the group,regardless of the number of tracks inside the group and regardless of the current selection in the group?

In the index, the group always isbehind the tracksthat comprise it. The group has right and left limit?

Per example:

  1. song:track(song.selected_track_index) is the track selected
  2. song:track(song.selected_track_index + 1) serve to select the next track or group (to right)
  3. song:track(song.selected_track_index - 1) serve to select the previous track or group (to left)

But these codes not serve…

Imagine a group with 5 tracks, selected the track 4. Imagine a group with 5 tracks, selected the track 2. Imagine a group with 5 tracks, selected the group.What would a code to always select the first track, regardless of all these random cases?

Yeah, referencing song.selected_track_index is a bit cringe-worthy here. It looks nicer (for more purposes) to store the absolute first octave track index and the group track index in variables that have a larger scope. These can then be used for whatever.