Stripping Down A Tool Coded By Someone Else?

Let’s say someone has done a really complex and convoluted script which runs a gui and everything. Let’s say you like one function in the tool, but have no need for the gui, but the tool also does a second thing. How would one go about stripping down a tool to it’s constituent parts in that way that one would be able to only utilize certain functions of it.

I have two examples, but I’m hoping that the first one will be less impossible to do. (Won’t mention the second tool if this thread doesn’t take off)

First example:dBlue’s pattern resizer.
It’s an amazing, GUI based, very complex and very useful method for, for instance, altering all the notes, notedelays, velocities, effect columns of one pattern from 32 to 33 rows, for instance. It can also be used in a rather simpler fashion, which is to go from 32 rows to 64 rows, or from 256 rows to 128 rows (i.e. *2 and *0.5).
I don’t really know what took me so long after dBlue recommended it ages ago that I should just modify the script to give it keyboard shortcuts that do the *2 and *0.5 (Expand and Shrink in the Adv. Edit Parameters). Anyway, after stupidly moaning about it, once again, on IRC, this time to the displeasure of one Conner_BW, I decided to check it out yet again, and this time managed to simplify the script so that one of it’s core functions is called via keyboard shortcuts. That’s yet one step closer to being able to use Adv. Edit Paramets from the keyboard shortcuts. But, dBlue’s script still does what it’s supposed to do, i.e., actually Resize the Pattern ( hence the PatternResizer name, I suppose :) ). Try as I might, I’m simply not capable of extracting one core function out of the script, without it just starting to shoot errors.

(What I’m after is destructive *2 (Expand) and *0.5 (Shrink) without the pattern actually getting resized. I must admit that I’m fairly sure dBlue’s code is elegant, and he took the time to document it well, but I don’t think I’m quite there yet to be able to read code like that and be able to reverse engineer it and recode it in such a way that all it’s features are kept apart from the actual changing of pattern size)…

Ok, result! :)
Four shortcuts. Two for Expand/Shrink the Adv.Edit Parameters way, with only minor modification of dBlue’s scriptage.
Two for Expand/Shrink with Pattern Resize, with minor modification of dBlue’s scriptage.

Add to main.lua:

  
renoise.tool():add_keybinding { name="Pattern Editor:Paketti:dBlue Shrink", invoke =function()  
resize_pattern(adjust_pattern_length(1/2),0) end}  
renoise.tool():add_keybinding { name="Pattern Editor:Paketti:dBlue Expand", invoke =function()  
resize_pattern(adjust_pattern_length(2/1),0) end}  
renoise.tool():add_keybinding { name="Pattern Editor:Paketti:dBlue Shrink + Resize Pattern", invoke =function()  
resize_pattern(adjust_pattern_length(1/2),1) end}  
renoise.tool():add_keybinding { name="Pattern Editor:Paketti:dBlue Expand + Resize Pattern", invoke =function()  
resize_pattern(adjust_pattern_length(2/1),1) end}  
  

(Don’t worry, I have no intention of simply pasting dBlue’s code into paketti.xrnx, I don’t even know how it could be incorporated without me messing stuff up, and this is some really complex stuff that dBlue’s code is doing! It might be way smaller without the GUI stuff but I really don’t think dBlue would think it nice of me to just rip off all that code of his especially since he’s already helped so many times with various and sundry IT/Schism related shortcuts, VST loading etc. He’s so nice.

Ok, so that was the main.lua modification.
Then to functions.lua
At first I was really stumped. But here it went.

Add patternresize to this line:

function resize_pattern(new_length,patternresize) (line 13 or something)  

Modify the Change pattern length ( around row 30):

  
 -- Change pattern length  
 if patternresize==1 then   
 pattern.number_of_lines = dst_length  
end  
  

And voila! Four shortcuts appear out of thin air, with minimal coding. This does exactly what ST3/IT/Schism have been doing (regular adv.edit parameters Expand/Shrink), but also the Renoise updated method of resizing the pattern too. And since dBlue is amazing, it also does automation!

Woo!

This is one less thing left to get to ImpulseTracker/Schism-like experience with Renoise, and I’m really chuffed!! :)
(It would be easy to modify the pattern resizes to also write a different LPB to the Master effect column, but the jury is out on whether that is required or not. Haven’t implemented this, yet (not sure if it’s sensible). But implementing it would be simple, since Syflom provided a “WriteBPM to Master” lua script a while ago, and the code was so simple that it could be easily modified to also writeBPM and LPB to the Master. It was a minor modification (enable two effect_columns to be visible, and copy transport.bpm code verbatim but switch transport.bpm with transport.lpb, and effect_columns[1] with effect_columns[2].

Since this code already exists, it would be possible to add a value to the function. This function would then cause a math process to be computed with the value added by the keyboard shortcut settings - and that value (current_lpb *2 or current_lpb *0.5 ) would be calculated and fed to the write_bpm/lpb modified Syflom code, and this code would then be run in the resize_pattern keyboard shortcut.
It would of course require safeguards to make sure max lpb and min lpb are not met - these are pretty minor things since it is only the scripting terminal / editor which shows the error, but these types of errors can also be easily averted by asking the script what the current number which has been modified is - and if it’s too low, set the lpb to 1, and if it’s too high, set the lpb to maximum lpb (and maybe add a handy

  
renoise.app():show_status("Max/Min reached, setting to: " .. result_lpb)  
  

at the end. :)

:D

You’re welcome to use (and modify) the code, I don’t mind at all.

I still have it on my list to eventually improve the tool, but we all know how that goes, heh. Some day…

1 Like

Really? Then I will try and figure out a way to copy the main.lua + rest of the .lua into one file. Any suggestions as to how to do it cleanly?
Btw, one question, is there actually any use for the resize_pattern.lua? (It seems that some stuff is called directly from functions.lua, and only functions.lua is required in the main.lua code :)

I think you might find Jenoki’s post interesting :)

If all you need is a single resize_pattern() function then it should be easy enough to tidy up all my crap :)

Here’s a modified version that should hopefully work for you. I modified the function a little bit so that it now accepts a pattern object plus the desired new length. This way you can tell it to resize any pattern you want, instead of just the currently selected pattern.

I’ve also cleaned it up a bit and replaced all the table accessors such as .tracks and .note_columns with their equivalent functions :track() and :note_column() respectively, which I believe will make the script run a bit faster (haven’t done any solid testing of this yet, though). Might be able to speed things up even further if I used the specially designed pattern iterators, but I haven’t really bothered to look into those much yet.

Code:

-- Pattern resizer by dblue.  
function resize_pattern(pattern, new_length)  
  
 -- We need a valid pattern object  
 if (pattern == nil) then  
 renoise.app():show_status('Need a valid pattern object!')  
 return  
 end  
  
 -- Rounding function  
 local function round(value)  
 return math.floor(value + 0.5)  
 end  
  
 -- Shortcut to the song object  
 local rs = renoise.song()  
  
 -- Get the current pattern length  
 local src_length = pattern.number_of_lines   
  
 -- Make sure new_length is within valid limits  
 local dst_length = math.min(512, math.max(1, new_length))  
  
 -- If the new length is the same as the old length, then we have nothing to do.  
 if (dst_length == src_length) then  
 return  
 end  
  
 -- Set conversation ratio  
 local ratio = dst_length / src_length  
  
 -- Change pattern length  
 pattern.number_of_lines = dst_length  
  
 -- Source  
 local src_track = nil  
 local src_line = nil  
 local src_note_column = nil  
 local src_effect_column = nil  
  
 -- Insert a new track as a temporary work area  
 rs:insert_track_at(1)  
  
 -- Destination  
 local dst_track = pattern:track(1)  
 local dst_line_index = 0  
 local dst_delay = 0  
 local dst_line = nil  
 local dst_note_column = nil  
 local dst_effect_column = nil  
  
 -- Misc  
 local tmp_line_index = 0  
 local tmp_line_delay = 0  
 local delay_column_used = false   
 local track = nil  
  
 -- Iterate through each track  
 for src_track_index = 2, #rs.tracks, 1 do  
  
 track = rs:track(src_track_index)  
  
 -- Set source track  
 src_track = pattern:track(src_track_index)  
  
 -- Reset delay check  
 delay_column_used = false  
  
 -- Iterate through source lines  
 for src_line_index = 0, src_length - 1, 1 do  
  
 -- Set source line  
 src_line = src_track:line(src_line_index + 1)  
  
 -- Only process source line if it contains data  
 if (not src_line.is_empty) then  
  
 -- Store temporary line index and delay  
 tmp_line_index = math.floor(src_line_index * ratio)  
 tmp_line_delay = math.floor(((src_line_index * ratio) - tmp_line_index) * 256)  
  
 -- Process note columns  
 for note_column_index = 1, track.visible_note_columns, 1 do  
  
 -- Set source note column  
 src_note_column = src_line:note_column(note_column_index)  
  
 -- Only process note column if it contains data   
 if (not src_note_column.is_empty) then  
  
 -- Calculate destination line and delay  
 dst_line_index = tmp_line_index  
 dst_delay = math.ceil(tmp_line_delay + (src_note_column.delay_value * ratio))  
  
 -- Wrap note to next line if necessary  
 while (dst_delay >= 256) do  
 dst_delay = dst_delay - 256  
 dst_line_index = dst_line_index + 1  
 end  
  
 -- Keep track of whether the delay column is used  
 -- so that we can make it visible later if necessary.  
 if (dst_delay > 0) then  
 delay_column_used = true  
 end  
 dst_line = dst_track:line(dst_line_index + 1)  
 dst_note_column = dst_line:note_column(note_column_index)  
  
 -- Note prioritisation   
 if (dst_note_column.is_empty) then  
  
 -- Destination is empty. Safe to copy  
 dst_note_column:copy_from(src_note_column)  
 dst_note_column.delay_value = dst_delay   
  
 else  
 -- Destination contains data. Try to prioritise...  
  
 -- If destination contains a note-off...  
 if (dst_note_column.note_value == 120) then  
 -- Source note takes priority  
 dst_note_column:copy_from(src_note_column)  
 dst_note_column.delay_value = dst_delay  
  
 else  
  
 -- If the source is louder than destination...  
 if (src_note_column.volume_value > dst_note_column.volume_value) then  
 -- Louder source note takes priority  
 dst_note_column:copy_from(src_note_column)  
 dst_note_column.delay_value = dst_delay  
  
 -- If source note is less delayed than destination...  
 elseif (src_note_column.delay_value < dst_note_column.delay_value) then  
 -- Less delayed source note takes priority  
 dst_note_column:copy_from(src_note_column)  
 dst_note_column.delay_value = dst_delay   
  
 end  
  
 end   
  
 end -- End: Note prioritisation   
  
 end -- End: Only process note column if it contains data   
  
 end -- End: Process note columns  
  
 -- Process effect columns   
 for effect_column_index = 1, track.visible_effect_columns, 1 do  
 src_effect_column = src_line:effect_column(effect_column_index)  
 if (not src_effect_column.is_empty) then  
 dst_effect_column = dst_track:line(round(src_line_index * ratio) + 1):effect_column(effect_column_index)  
 if (dst_effect_column.is_empty) then  
 dst_effect_column:copy_from(src_effect_column)  
 end  
 end  
 end  
  
 end -- End: Only process source line if it contains data  
  
 end -- End: Iterate through source lines  
  
 -- If there is automation to process...  
 if (#src_track.automation > 0) then  
  
 -- Manually copy processed lines from temporary track back to original track  
 for line_index = 1, dst_length, 1 do  
 dst_line = dst_track:line(line_index)  
 src_line = src_track:line(line_index)  
 src_line:copy_from(dst_line)  
 end  
  
 -- Process automation  
 for _, automation in ipairs(src_track.automation) do  
 local points = {}  
 for _, point in ipairs(automation.points) do  
 if (point.time <= src_length) then  
 table.insert(points, { time = math.min(dst_length - 1, math.max(0, round((point.time - 1) * ratio))), value = point.value })  
 end  
 automation:remove_point_at(point.time)  
 end  
 for _, point in ipairs(points) do  
 if (not automation:has_point_at(point.time + 1)) then  
 automation:add_point_at(point.time + 1, point.value)  
 end  
 end  
 end  
  
 else  
  
 -- No automation to process. We can save time and just copy_from()  
 src_track:copy_from(dst_track)  
  
 end  
  
 -- Clear temporary track for re-use  
 dst_track:clear()  
  
 -- Show the delay column if any note delays have been used  
 if (rs:track(src_track_index).type == 1) then  
 if (delay_column_used) then  
 rs:track(src_track_index).delay_column_visible = true  
 end  
 end  
  
 end -- End: Iterate through each track  
  
 -- Remove temporary track  
 rs:delete_track_at(1)  
  
end

Example usage:

  
local pattern = renoise.song().selected_pattern  
  
resize_pattern(pattern, pattern.number_of_lines * 2)  
  

Nope, they are identical. I simply forgot to remove resize_pattern.lua when packaging the tool. You’ll also notice a few instances where I forgot to remove some debug print() commands, or where I forgot to delete test code that is simply commented out. I did that thing in quite a rush :)

Last thing we need is another tool that I start but never finish ;)

Hi… Can preferences.xml be stripped out if the gui is stripped out?

Yep. The prefs are only used by the GUI to remember the last length that you used. You don’t need that otherwise. You shouldn’t need anything from the original tool anymore - only this new function that I’ve posted here which is totally self-contained.

PS. I edited the lua code again to remove some other crap that wasn’t even being used (the master track stuff). Kinda funny how much lazy crap gets left behind in these things :)

Thanks for the edits! Added this to paketti.xrnx after made the

  
function resize_pattern(pattern, new_length, patternresize)  
  
  
 if patternresize==1 then   
 pattern.number_of_lines = dst_length  
end  
  

modifications and then wrote the keybindings.

Ok.
Second example, the StepSequencer tool. It’s really lush. Really complex. Sends out notes, etc. nice GUI that does various stuff. Well thought out, functional, etc, except for one thing. It sends out notes. When someone doesn’t know much about LUA scripting (or anything at all) and one sees a stepsequencer which (seems to) send notes to Renoise directly when a certain step is encountered, one starts wondering if Renoise API functions could be called from within the stepsequencer. What would it do? Well, one could think of things like cycling between automation displays on a specific track (i.e., everytime Row 00 is met in the StepSequencer, switch to the next automated parameter display, then when you’re at the last automated parameter, switch to the first one.). Even a relatively useless test of having one step toggle fullscreen mode and one step toggle windowed mode. Screen updates, recording and stopping at a specific row, etc. Many things, but pretty much completely unrelated to the actual StepSequencer script.

No sweat.
I guess one of my colleque moderators moved the thread away underneath my nose.

btw:
You can use dBlue’s code in your own code by simply adding the line
require “functions.lua” (you could rename the file to dblue_pattern_resizer.lua and refer to that instead)
and then simply call the same function as you do otherwise.
It looks like all important variables and constants are generated in that function.

Stepsequencer:It doesn’t send notes in realtime, it places notes in the pattern editor and commands Renoise to play the specific line or pattern.
If you want to broadcast live notes you need to create your own routine that broadcast these events through MIDI or OSC.
Next to it, you also have to create your own pattern player that casts these note-data in the correct timing as Renoise is set to (LPB and BPM).
There are no routines in the stepsequencer that gets you on that realtime track.

But using his routines to also alter automation data etc. should be possible.

thanks for the advice on dBlue and on stepsequencer. dBlue actually swooped into the thread and simplified his code, and gave his permission for it to be added to my gigantic xrnx tool which has tons and tons of functions. It was simplified from requiring functions.lua to simply having all of the code in main.lua so no issues there anymore :)