Lua API bug -- swap tracks

Ok managed to get something working (alpha-ish)

Choose group you want to extend from the popup, then nudge with buttons.

Bonus buttons bypass or enable all the DSPs in the selected group track.

https://forum.renoise.com/t/new-tool-3-1-group-nudge-sep-2018/49540

Shortcut Group Nudge

Menu: Tools: Ledger`s Scripts: Group Nudge


and here`s the magic function, just pass in the group index of the group that you want to nudge left:

It required juggling two blank “dummy tracks” to get around the swapping problems stated in this thread.

--extend left border of group leftwards by one track
---------------------------------------  
function extend_group_left(group_index)  
---------------------------------------
  
  local song = renoise.song()
  local group_track = song:track(group_index)

  --get the index of the track to the left of the group. This is the track we want to add to the group
  local left_of_grp_idx = (group_index - #group_track.members - 1) 
  
  --if left_of_grp_idx == 0 then there are no more tracks to the left so return early 
  if left_of_grp_idx == 0 then 
    return
  end
  
  --if group has no members already, then it is a simple add track to group operation and
  --no re-organisation is needed
  if #group_track.members == 0 then
    song:add_track_to_group(left_of_grp_idx,group_index)
    return
  end
  
  --1) Add 2 dummy tracks into group
  ----------------------------------
  --NOTE We need to insert 2 dummy tracks to account for bugs with the renoise swap tracks behaviour within groups
  --see: https://forum.renoise.com/t/lua-api-bug-swap-tracks/49534
  
  --set member_idx to the first member track of the group
  local member_idx = left_of_grp_idx + 1

  --loop group members to find insert point for dummy tracks (after first legitimate member; i.e. not a track in a further sub-group)
  for i = 1,#group_track.members do
    --check if member is not in a sub group by checking its parent
    if rawequal(song:track(member_idx).group_parent,group_track) == true then
      break
    end
    --if here then member was in a sub- group so increment member_idx until we get to a legitimate track
    member_idx = member_idx + 1
  end
  
  --again due to insert_track behaviour we need to insert the dummy tracks after the first present and legitimate member of the group
  --so + 1
  local insert_point = member_idx + 1
  
  --insert 2 dummy tracks at member_idx 
  local dummy_trk_2 = song:insert_track_at(insert_point)
  local dummy_trk_1 = song:insert_track_at(insert_point)
  
  --name the tracks for bug hunting i.e. if they are not deleted we will see them in pattern ed
  dummy_trk_1.name = "Dummy Track 1"
  dummy_trk_2.name = "Dummy Track 2"
  
  --2) Swap Dummy tracks to members 1 and 2 of group (this works re. the swap bug as we are not swapping two groups)
  song:swap_tracks_at(member_idx,member_idx+1)
  song:swap_tracks_at(member_idx+1,member_idx+2)
  
  --3) Preparation done, so bring the track we want into the group
  --Swap dummy member 2 with target track outside group (left_of_grp_idx was found near beginning of function)
  --note: if we were to swap dummy member 1 then both tracks get ejected from group
  song:swap_tracks_at(left_of_grp_idx,(left_of_grp_idx + 2))
 
  --4) Tidy up: delete dummy tracks by looping objects 
  --(as we are deleting tracks it is safer to loop and find the dummy track objects specifically as
  --we might lose wanted song tracks if we were calculating indexes and a mistake made)
  --As indexes can change when a track is deleted, it is simpler to use 2 loops
  for i = 1,#song.tracks do
    if rawequal(song:track(i),dummy_trk_1) then
      --delete dummy 1
      renoise.song():delete_track_at(i)
      break
    end
  end
  
  for i = 1,#song.tracks do
    if rawequal(song:track(i),dummy_trk_2) then
      --delete dummy 2
      renoise.song():delete_track_at(i)
      break
    end
  end
end