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.
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 ,
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.
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