Is there an efficient way to swap rows or reverse selection in phrases, incl note/smp/vol/pan/dly/fx?

I’m working on some tools to build new phrases, transformed from an initial phrase, in this case, a reversed phrase.
One function I need is the ability to flip/reverse pairs of note rows (including vol/pan/etc) with their corresponding note-off rows. I manually created the screenshots below to demo.
Initially, I was trying to work with the phrase data in a table, inverting the row/col order when reading in as complete lines, and then trying to plug the data back in after swapping the note-offs and notes within the table, but this did not seem like a sane way to start things off.
I think the ideal result would be to simply reverse all column rows between the note-off and note rather than just swapping the two rows, which could be done with separate selection_in_phrase for every note, programmatically creating the selections for each pair.
(scroll up a little from here to see how phrase selections work differentl yfrom pattern selections, Renoise.Song.API.lua )
Any nudges in a sensible direction are appreciated.

EDIT:
I should note that I’m already flipping whole phrases with no prob.
Key in what I need now is flipping phrase selections likely using selection_in_phrase.
Every pair of notes and note-offs within a phrase will need to be detected and flipped individually.
Next after that will be pre-processing on the initial phrase that adds note-offs to any notes that don’t have them before going to the next step.

Thanks!

Just a couple of thoughts… First you need to esablish the principles for reversing. Simplification for my small brain: imagine it was an audio sample, and in what order events would occur if you reverse it. Try to replicate this with pattern data.
It sounds to me like you’re on the right path there… First swap each note with its corresponding noteoff/cut/newnote - and then reverse everything. That should be the best approximation to a reverse, if i’m not mistaken.

Reversing is quite a few lines of code, using ripairs as help. Keep to a simple custom table abstraction of notecolumns. No need to get fancy there.

For fun I asked the chat option in Bing in Microsoft Edge, and after some trial and error it came up with;

-- define the flip_phrase function
function flip_phrase()
  local phrase = renoise.song().selected_phrase
  if not phrase then return end

  -- get the number of lines in the phrase
  local line_count = #phrase.lines

  -- create a temporary table to store a copy of the phrase lines
  local temp_lines = {}
  
  -- iterate through all lines in the phrase and store them in reverse order in temp_lines
  for i = line_count, 1, -1 do
    temp_lines[line_count - i + 1] = {}
    temp_lines[line_count - i + 1].note_columns = {}
    temp_lines[line_count - i + 1].effect_columns = {}

    -- copy note column values
    for j = 1, #phrase:line(i).note_columns do
      temp_lines[line_count - i + 1].note_columns[j] = {
        note_string = phrase:line(i):note_column(j).note_string,
        instrument_value = phrase:line(i):note_column(j).instrument_value,
        volume_value = phrase:line(i):note_column(j).volume_value,
        panning_value = phrase:line(i):note_column(j).panning_value,
        delay_value = phrase:line(i):note_column(j).delay_value,
        effect_number_string = phrase:line(i):note_column(j).effect_number_string,
        effect_amount_value = phrase:line(i):note_column(j).effect_amount_value,
      }
    end

    -- copy effect column values
    for j=1,#phrase:line(i).effect_columns do 
      temp_lines[line_count-i+1].effect_columns[j]={
          number_string=phrase:line(i):effect_column(j).number_string,
          amount_value=phrase:line(i):effect_column(j).amount_value}
    end 
      
    
      
    
  
end
-- update phrase lines with reversed lines
for i = 1, line_count do
  local line = phrase:line(i)

  -- update note columns
  for j = 1, #line.note_columns do
    local note_column = line:note_column(j)
    local temp_note_column = temp_lines[i].note_columns[j]
    note_column.note_string = temp_note_column.note_string
    note_column.instrument_value = temp_note_column.instrument_value
    note_column.volume_value = temp_note_column.volume_value
    note_column.panning_value = temp_note_column.panning_value
    note_column.delay_value = temp_note_column.delay_value
    note_column.effect_number_string = temp_note_column.effect_number_string
    note_column.effect_amount_value = temp_note_column.effect_amount_value
  end

  -- update effect columns 
  for j=1,#line.effect_columns do 
      local effect_col=line:effect_column(j)
      local tmp_effect_col=temp_lines[i].effect_columns[j]
      effect_col.number_string=tmp_effect_col.number_string 
      effect_col.amount_value=tmp_effect_col.amount_value 
  end 
end

end

-- create a ViewBuilder instance
local vb = renoise.ViewBuilder()

-- create a button that calls the flip_phrase function when pressed
local flip_button=vb:button{
text="Flip Phrase",
released=function()flip_phrase()end}

-- show a simple dialog with the button
renoise.app():show_custom_dialog("Flip Phrase",vb:column{flip_button})

Put the above in the testpad.lua file and execute it for a small gui with flip phrase button. Not sure how efficient the code is, but it seems to work :slight_smile: ,

1 Like

If it’s just simple flipping, it’s easier like this imo

-- DEFINE THE FLIP PHRASE FUNCTION
function flip_phrase(phrase)

  -- DO ALL THE STUFF
  local get_cache = function(avoid_index, type)
    for _, line in ripairs(phrase.lines) do
      for col_index, col in ripairs(line[type]) do
        if col.is_empty and not (col_index == avoid_index) then
          return col
        end
      end
    end
  end

  local cache_col, src_col, dest_col
  for line_index = 1, math.floor(phrase.number_of_lines/2) do
    local line = phrase:line(line_index)

    for col_name, columns_amt in pairs { note_columns = 12, effect_columns = 8 } do
      for col_index = 1, columns_amt do
        dest_col = phrase:line(phrase.number_of_lines+1-line_index)[col_name][col_index]
        src_col = line[col_name][col_index]
        if not (src_col.is_empty and dest_col.is_empty) then
          cache_col = get_cache(col_index, col_name)
          cache_col:copy_from(dest_col)
          dest_col:copy_from(src_col)
          src_col:copy_from(cache_col)
          cache_col:clear()
        end
      end      
    end

  end

end

flip_phrase(renoise.song().selected_phrase)

(It has an edge case that will “never occur”).

Even better is to use string:gmatch (?) for caching and deserialization. It’s very fast and practical once you set it up. tostring(line) is the fastest way to get all pattern data, but is more hardcore.

It’s a bit surprising that chatgpt forgot some obvious caching.

Edit: added comments in the code!

Looks like the Bing engine uses something else under the hood;

Don’t ask a chatbot about itself :slight_smile:

1 Like

I should note that I’m already flipping whole phrases with no prob.
Key in what I need now is flipping phrase selections likely using selection_in_phrase.
Every pair of notes and note-offs within a phrase will need to be detected and flipped individually.
Next after that will be pre-processing on the initial phrase that adds note-offs to any notes that don’t have them before going to the next step.

That’s correct. Note that it doesn’t show granular selection, so you will have to assume that the whole column is selected. Use renoise.InstrumentPhrase.visible_note_columns to calculate whether effect columns are selected.

I had to take a step back to go forward. Rather than cloning the phrase and then applying the transformations to the new phrases, I double the length of the selected phrase and build the “reverse” version as the 2nd half of the selected phrase (always the first phrase) then can build the additional phrases using any combo of, fwd-fwd, rev-rev, fwd-rev, rev-fwd, built from the top half or bottom half of the first phrase