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

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.

– Set the selected track to prev/next relative to the current track. Takes

– care of skipping over hidden tracks and wrapping around at the edges.

renoise.song():select_previous_track()

renoise.song():select_next_track()

With select_previous_track() I can not do anything neither.And jump directly to the left track outside the group?

I would absolutely stay away from those in this case.

If you need to keep track of your octave tracks (and group track index), you should do so by, upon creation, storing those values in variables that can later be reused.

This is the way of numbering in the index:

T 1 T 2 --------G 6 -------T 7 T 8… Master 9 Sfx 10… (index = 10)
T 3 T 4 T 5

T 1 T 2 --------G 9 --------------------- T1 0 T 11… Master 12 Sfx 13…(index = 13)
T 3 T 4 T 5 T 6 T 7 T 8

T 1 T 2 --------G 13 --------------------------------------- T 14 T 15… Master 16 Sfx 17…(index = 17)

T 3 T 4 T 5 T 6 T 7 T 8 T 9 T 10 T 11 T 12

  • Blue, random selection.
  • The selection is always within the group.
  • The only thing is true here is that T2 is ever before T3 in all cases. T2 = Original Track, Gx = Group Octaves.
  • How always select the T3? (wherein the amount of tracks within the group is variable).
  • It may be the case that T1 does not exist.

EDIT:I’ll try to implement it before creating the group, but I’m having problems in the selection as well.

By storing what index T3 has when you create it and use that for later.

Beware that as soon as you delete any previous track, such reference will become wrong and will need to get updated.

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

Finally I opted for a bungled solution.In order to select the first track (the T3) at the end of the process,is essential whenever the first track (T3) is empty. :slight_smile: So, I added an empty track, which is then removedautomaticallyin the new function delete_tracks_empty.This code works perfectly!!!:

local group_settings = {
ten_octaves = {
  name = "Octaves",
  color = { 0x80,0x80,0x80 },
  color_blend = 27,
  collapsed = false,
  tracks = {
  { name = "empty" }, -- to select first track always
  { name = "0 Oct", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0x70,0x70,0x70 }, color_blend = 10 },
  { name = "1 Oct", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0x40,0x40,0x40 }, color_blend = 10 },
  { name = "2 Oct", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0x70,0x70,0x70 }, color_blend = 10 },
  { name = "3 Oct", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0x40,0x40,0x40 }, color_blend = 10 },
  { name = "4 Oct", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0x70,0x70,0x70 }, color_blend = 10 },
  { name = "5 Oct", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0x40,0x40,0x40 }, color_blend = 10 },
  { name = "6 Oct", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0x70,0x70,0x70 }, color_blend = 10 },
  { name = "7 Oct", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0x40,0x40,0x40 }, color_blend = 10 },
  { name = "8 Oct", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0x70,0x70,0x70 }, color_blend = 10 },
  { name = "9 Oct", volume_column_visible = true, delay_column_visible = true, collapsed = true, color = { 0x40,0x40,0x40 }, color_blend = 10 },
  },
  },
}
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
          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
end

function delete_tracks_empty()
  local song = renoise.song()
  local tindex = song.selected_track_index
  song:track(tindex).collapsed = true -- original track
  song:track(tindex).color_blend = 10
  song:track(tindex).color = { 0xFF,0x00,0x00 }
  song.tracks[tindex]:mute()

  local first_octave_track = tindex + 1
  for group_index = first_octave_track + 10, first_octave_track, - 1 do
    local was_empty = false
    for _, pattern in ipairs(song.patterns) do
      if not pattern:track(group_index).is_empty then
        was_empty = true
      end
    end
    if not was_empty then
      song:delete_track_at(group_index)
    end
  end
  song:track(tindex + 1).collapsed = false -- select first track
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 + 2) delete_tracks_empty()
             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)

I do not like how are these lines:

local tindex = song.selected_track_index
  song:track(tindex).collapsed = true -- original track
  song:track(tindex).color_blend = 10
  song:track(tindex).color = { 0xFF,0x00,0x00 }

How you could arrange better? Something like the first lines of code above.

joule, do you know this code?:


– renoise.GroupTrack (inherits from renoise.Track)


-------- Functions

– All member tracks of this group (including subgroups and their tracks).

renoise.song().tracks[].members[]

-> [read-only, array of member tracks]

– Collapsed/expanded visual appearance of whole group.

renoise.song().tracks[].group_collapsed

-> [boolean]

Does this code how it works? :

renoise.song().tracks[].members[]

-> [read-only, array of member tracks]

members??? What can be done about this?

Oher question in LUA, a example:

T 1 T 2 --------G 6 -------T 7 T 8… Master 9 Sfx 10… (index = 10)
T 3 T 4 T 5

Is it possible to copy the All DSP Chain of Track T2 in the Group G6?

– Insert a new device at the given position. “device_path” must be one of

– renoise.song().tracks[].available_devices.

renoise.song().tracks[]:insert_device_at(device_path, device_index)

-> [newly created renoise.AudioDevice object]

– Delete an existing device in a track. The mixer device at index 1 can not

– be deleted from a track.

renoise.song().tracks[]:delete_device_at(device_index)

– Swap the positions of two devices in the device chain. The mixer device at

– index 1 can not be swapped or moved.

renoise.song().tracks[]:swap_devices_at(device_index1, device_index2)

– Access to a single device by index. Use properties ‘devices’ to iterate

– over all devices and to query the device count.

renoise.song().tracks:device(index)

-> [renoise.AudioDevice object]

– Selected in the track DSP chain editor. Can be nil.

renoise.song().selected_track_device, _observable

-> [read-only, renoise.AudioDevice object or nil]

renoise.song().selected_track_device_index

-> [number, index or 0 (when no device is selected)]

Moving the device chain is not very straight forward. You might get some help looking into this tool: https://forum.renoise.com/t/new-tool-2-8-move-device-chain/34475