Maybe I am the only one of us that writes pretty bad code
You have seen my tools, right?
Maybe I am the only one of us that writes pretty bad code
You have seen my tools, right?
I haven’t looked at any of your code, and maybe I shouldn’t then
Ugliness aside, naturally what you would want to focus the most on is to optimize the loops. This evening, I will have a go at trying to dump pattern data to strings and parsing/searching from there instead of the simple way by intensively accessing tables. I’ll post any results if beneficial.
Nah, don’t think code optimization within lua will bring much benefits. Imo totally waste of time. Don’t waste your time.
Much more efficient this can be done from renoise’s api, lua interpreter, xml song parser or gui side.
There are lot of examples where you see that the lua api in renoise is really slow. Examples:
My findings:
renoise.song().pattern_iterator:lines_in_song() is much “faster” than renoise.song().pattern_iterator:note_columns_in_song(). I would prefer using lines_in_song() if application permits.
Making a custom pattern iterator seems to be a better choice than using the renoise pattern_iterator.
local x = os.clock()
function test() -- standard iteration
local iter = renoise.song().pattern_iterator:lines_in_song()
for _, val in iter do
end
end
function test2() -- custom iteration, optimization commented out
for seq, pattern_index in ipairs(renoise.song().sequencer.pattern_sequence) do
for track_index = 1, renoise.song().sequencer_track_count do
-- if not renoise.song():pattern(pattern_index):track(track_index).is_empty then
for line_index, line in ipairs(renoise.song():pattern(pattern_index):track(track_index).lines) do
-- end
end
end
end
end
--test()
test2()
print(string.format("elapsed time: %.2f\n", os.clock() - x))
The custom iteration also allows for further optimization (like i’ve commented out in test2()), if application permits. Depending on application you might also want to optimize further by filtering out track types (groups, sends, master)
Benchmarks on my system (song: Medievil Music - Access Pwd.xrns)
Standard iteration: 1.59s
Custom iteration: 1.36s
Custom iteration optimized: 0.62s
Anyone disagrees?
To focus on performance is a very nice idea. I’m happy to share my own findings
1. If you need to random access note/effect columns, do it via methods
Documentation clearly states that random access to the following objects is inefficient:
renoise.song().patterns[].tracks[].lines[]:note_columns[index]
renoise.song().patterns[].tracks[].lines[]:effect_columns[index]
renoise.song().instruments[].phrases[]:lines[index]
renoise.song().patterns[].tracks[]:lines[index]
Reason is, that the when asked for an item within an array, the API will first need construct the whole array and return it. After which, you then select single entry.
With such a simple case, it’s much better to access the object via it’s associated getter method:
renoise.song().patterns[].tracks[].lines[]:note_column(index)
renoise.song().patterns[].tracks[].lines[]:effect_column(index)
renoise.song().instruments[].phrases[]:line(index)
renoise.song().patterns[].tracks[]:line(index)
2. Schedule your updates
This also happens to be the golden rule of the Lua Gems book: Performance tip #1: “Don’t do it”, followed by #2: “Do it later”.
I do this throughout all my realtime-performing tools - it’s not hard, just involves a bit more code.
Basically, what you need to do is to centralize the updates that your tools needs to perform, and call this method through the idle notifier.
For instance, a simple tool might have a update method which would set user interface components to their most current state.
A more advanced tool could split the update method into several smaller bits, making each update a lighter task to perform.
The trick is that you raise a flag (a boolean variable) when the update needs to happen.
For example, the flag could be raised by specifying something like this:update_requested = true
Then, in a fraction of a second the idle notifier will pick this up and call the update method - and reset the flag
if (update_requested) then
-- do something cool, then reset flag
update_requested = false
end
The advantage is that you could ask for updates any number of times between idle notifiers, but the scheduled update will only happen once.
This approach can also be adopted to those situations where a notifier gets triggered very often. For example, the line notifier is famously flooding you with messages when you e.g. clear a pattern - one message for each line in each track. This can easily get into the hundreds - you would definitely want to schedule/delay the response in such a case.
3. Cache Renoise API methods and objects when possible
It’s a fact that the Lua language itself is extremely fast. Same is true for C++. The bottleneck is mostly_between_the two: everything has to be looked up, converted by luabind.
So, another way to improve performance of scripts would be to cache those objects that doesn’t change during the execution of a script.
For example, the call to renoise.song() is obviously a method call. And yes, this comes with a cost, albeit a very small one.
I’m pretty sure you wouldn’t see much of an impact with simple tools. But the larger a tool becomes, the more calls needs to be done, and things start to accumulate.
For this reason, it’s recommended that you avoid code like this:
for i = 1,100 do
if(renoise.song().patterns[i]) then
-- there was a pattern here
end
end
It can be very easily be changed into
local rns = renoise.song()
for i = 1,100 do
if(rns.patterns[i]) then
-- there was a pattern here
end
end
Some of my own tools have taken this to the extreme and contains just a single reference to the renoise song object used throughout the tool.
But this comes with a different kind of complexity as you’d then have to watch out that you don’t invoke any anonymous code that contain such references.
Why? Because the references will become invalid the moment that the song (or whatever you were referencing) is gone.
If you have worked with notifiers, you will know that referencing objects can be tricky - making sure that notifiers are always suppressed or removedwhen their associated objects have gone.
4. Profile your code
Urhg, this is the one aspect of programming that I find intensely boring. I actually prefer writing unit tests.
But yeah, it’s the scientific approach
–
- renoise.song().pattern_iterator:lines_in_song() is much “faster” than renoise.song().pattern_iterator:note_columns_in_song()
Well, they are different - the lines_in_song one doesn’t go as “deep” as the “note_columns_in_song”.
I would be surprised if the latter one performed worse than making the first one do what the second one does.
But you have a point with the “custom iterator”, if you know exactly what kind of task you set out to do you can optimize the hell out of it.
Reminds me a bit of ffx’ idea for a “jquery-alike” syntax… but I am sure he would find it too slow :lol:
Edit:
There are lot of examples where you see that the lua api in renoise is really slow
You’re right that in some cases, optimizing lua code is not the way forward. But when creating a gazillion menu entries, the API is not to blame - this is simply Renoise being slow in doing this particular type of task. I would view this as an opportunity to optimize Renoise - especially as this particular type of UI update can’t be scheduled (unlike others, such as updating the status bar)
Another way to thinking about improving API performance is to consider which things could be pulled into Lua, processed there, and then delivered back once done. Most obviously, this is true when processing samples - it’s done_frame by frame._If we could instead process chunks, we could avoid the overhead caused by the lua / C++ bindings.
Even with something like luajit (which otherwise promises a massive increase in speed), this overhead is causing that increase to be not so overwhelming - unless you are doing “pure” lua stuff.
Regarding ipairs iteration, something that might be worth mentioning is that example 1 is faster than example 2.
Note that this doesn’t seem true for renoise.song() tables (,lines, .note_columns et c). I am guessing that iterations of these are customized and optimized behind the scenes. Otherwise:
Example 1:
local my_table = { 1, 42, 3, 7, 10 }
for i = 1, #my_table do
local val = my_table[i]
-- code
end
Example 2:
local my_table = { 1, 42, 3, 7, 10 }
for i, val in ipairs(my_table) do
-- code
end
I found something recently that is worth mentioning regarding line iteration (scanning for stuff) (again!). This can speed up some things pretty drastically when doing heavy iterations:
If your application is accessing more than one property within a pattern line (scanning more than one note column, or searching for values in the whole song), strongly consider to use :lines_in_range(). The tostring(line) can be used with string pattern matching for very fast scanning of pattern data in comparison to the standard nested iterations. You will access one string intsead of iterating all note and effect columns.
This can be a huge optimizaton under the right circumstances and has perhaps been a bit neglected (?). A code example can be seen here: https://forum.renoise.com/t/new-tool-2-7-3-1-set-track-width-to-active-columns/31078
(The reason why it’s faster is, I guess, that native tostring methods in the song object are not just simple LUA metamethods, but internal C++ functions that are a lot faster than a LUA table concatenation would be, for example. So it’s often worth taking advantage of when possible. Perhaps there are also other usable “C++ metamethods” that have been neglected and can be exploited for optimizations?)
Regarding caching (point 3 in Danoise’s post, taken to the extreme):
An interesting technique I’ve tried speeds up API pattern data read access by app. 10 times. Theoretically, you can quite easily cache the whole song data in a friendly format and update the cache dynamically with notifiers (negligable overhead, unless every tool starts using this technique to speed up any read access…)
For now, this technique is practically very useful for example in realtime tools reading heavily on specific tracks (iterating for voices, chords…), as all read access will happen in internal lua tables/objects that are 100% synced to the actual song data. Just replace any read access to target the cache instead of renoise.song().
PS. Again… it would be great if tools could access each others environments. We could then make a general speed-up tool, acting as a “dependency”, that would cache objects for other tools. Anyhow, I’ve made a simple framework for pattern data in case anyone is interested.
I have consulted this topic several times with some hope. What FFX says is true. There are some parts of the API that work slow when trying to span many patterns at once.It includes 512 lines per pattern and multiply the patterns to a high figure, 500 or 700. You will see that many iteration functions are much slower. For example, delete the entire volume column of the selected note column. Or all the volume columns of an entire track.
However, building tools, I have noticed marked improvements according to the code used. I mean, that a function may be better written to run it faster, especially with the iteration.
So while it sounds obvious, both things are needed, optimizing the Renoise API in certain cases, and also using better-optimized “LUA tricks” to improve performance.
On the other hand, I just want to leave a feeling. With a tool of 100 or 200KB with polished code, the tool can offer a lot of controls or specific functions. It is very Libyan. When I did not have much idea of LUA, I thought that a good tool needed a lot more code, and therefore many more KB.
But one thing is what I want and another very different what I expect (I think some of you feel the same way).I have been reading many topics on the forums, which have been ignored over and over again. There are many things…
This seems a topic directly addressed to taktik. It is not here.But it would be great to have more examples of optimization, because they really do their job for build best tools.
My findings:
renoise.song().pattern_iterator:lines_in_song() is much “faster” than renoise.song().pattern_iterator:note_columns_in_song(). I would prefer using lines_in_song() if application permits.
Making a custom pattern iterator seems to be a better choice than using the renoise pattern_iterator.
Click to view contents
local x = os.clock() function test() -- standard iteration local iter = renoise.song().pattern_iterator:lines_in_song() for _, val in iter do end end function test2() -- custom iteration, optimization commented out for seq, pattern_index in ipairs(renoise.song().sequencer.pattern_sequence) do for track_index = 1, renoise.song().sequencer_track_count do -- if not renoise.song():pattern(pattern_index):track(track_index).is_empty then for line_index, line in ipairs(renoise.song():pattern(pattern_index):track(track_index).lines) do -- end end end end end --test() test2() print(string.format("elapsed time: %.2f\n", os.clock() - x))
The custom iteration also allows for further optimization (like i’ve commented out in test2()), if application permits. Depending on application you might also want to optimize further by filtering out track types (groups, sends, master)
Benchmarks on my system (song: Medievil Music - Access Pwd.xrns)
Standard iteration: 1.59s
Custom iteration: 1.36s
Custom iteration optimized: 0.62sAnyone disagrees?
Just working my way through this thread. Added another loop, which is just a standard lua for loop rather than ipairs. It seems to be as quick with the optimization, of weeding out empty tracks.
I also modiifed so that the tests run one after the other, resetting x to os.clock() at the start of each function
I never really liked the ipairs syntax but maybe I`m missing out on some adavantages by not using it?
local x -- os.clock()
------------------------------------------------------------------
------------------------------------------------------------------
function test() -- standard iteration
x = os.clock() -- reset x (clock stamp start)
local iter = renoise.song().pattern_iterator:lines_in_song()
for _, val in iter do
-- do nothing
end
--print time elapsed
print(string.format("elapsed time test(): %.2f\n", os.clock() - x))
end
-------------------------------------------------------------------
-------------------------------------------------------------------
function test2() -- custom iteration
x = os.clock() -- reset x (clock stamp start)
for seq, pattern_index in ipairs(renoise.song().sequencer.pattern_sequence) do
for track_index = 1, renoise.song().sequencer_track_count do
if not renoise.song():pattern(pattern_index):track(track_index).is_empty then --optimization
for line_index, line in ipairs(renoise.song():pattern(pattern_index):track(track_index).lines) do
-- do nothing
end
end
end
end
--print time elapsed
print(string.format("elapsed time test(2): %.2f\n", os.clock() - x))
end
-------------------------------------------------------------------
-------------------------------------------------------------------
function test3()--standard Lua loop
x = os.clock() -- reset x (clock stamp start)
for pattern_index = 1,#renoise.song().sequencer.pattern_sequence do
for track_index = 1, renoise.song().sequencer_track_count do
if not renoise.song():pattern(pattern_index):track(track_index).is_empty then --optimization
for line_index, line in ipairs(renoise.song():pattern(pattern_index):track(track_index).lines) do
-- do nothing
end
end
end
end
--print time elapsed
print(string.format("elapsed time test(3): %.2f\n", os.clock() - x))
end
--run tests
test()
test2()
test3()
I never really liked the ipairs syntax but maybe I`m missing out on some adavantages by not using it?
ipairs has a “nice syntax” where you get both the key and the value in one go, but I don’t use it anymore for the reason mentioned above.
This is my default practice, and should be the fastest:
local my_table = { 1, 42, 3, 7, 10 }
local val
for i = 1, #my_table do
val = my_table[i]
-- code
end
Of course, pairs is still useful, as it iterates non-indexed tables.
I see. Yes I don`t use non-indexed tables much (think I maybe tried a couple in some early scripts).
As the API doesn`t use them aswell the standard for-loop seems to cover most needs nicely.
Still have to disagree on the ipairs being a nice syntax though
Example of optimization of a function with iteration and avoiding access to tables, among other things…
I realized that it was common for me to write the functions a long time ago without knowing very well how to optimize them. As I have been trying to optimize some of my functions, I try to share a clear example of optimizing a function.
Function A ) poorly optimized (copy a pattern-track into a phrase):
function vpd_copy_pattr_into_phrase( song, sphi, sph, pt_nol, pt_vnc, pt_vec, pt_vcv, pt_pcv, pt_dcv, pt_ecv, in_ph, ph_ln, pt_tr, pt_ln, pt_nc, ph_nc, pt_ec, ph_ec )
song = renoise.song()
sphi = song.selected_phrase_index
sph = song.selected_phrase
pt_nol = song.selected_pattern.number_of_lines
pt_vnc = song.selected_track.visible_note_columns
pt_vec = song.selected_track.visible_effect_columns
pt_vcv = song.selected_track.volume_column_visible
pt_pcv = song.selected_track.panning_column_visible
pt_dcv = song.selected_track.delay_column_visible
pt_ecv = song.selected_track.sample_effects_column_visible
if song.selected_phrase == nil then
renoise.app().window.active_middle_frame = renoise.ApplicationWindow.MIDDLE_FRAME_INSTRUMENT_PHRASE_EDITOR
return
else
if ( song.selected_track.type == renoise.Track.TRACK_TYPE_SEQUENCER ) then
renoise.app().window.active_middle_frame = renoise.ApplicationWindow.MIDDLE_FRAME_INSTRUMENT_PHRASE_EDITOR
sph.number_of_lines = pt_nol
sph.visible_note_columns = pt_vnc
sph.visible_effect_columns = pt_vec
sph.volume_column_visible = pt_vcv
sph.panning_column_visible = pt_pcv
sph.delay_column_visible = pt_dcv
sph.sample_effects_column_visible = pt_ecv
in_ph = song.selected_instrument.phrases[sphi]
pt_tr = song.selected_pattern_track
for ln = 1, pt_nol do
ph_ln = in_ph.lines[ln]
pt_ln = pt_tr.lines[ln]
if not pt_ln.is_empty then
--for note columns
for nc = 1, 12 do
pt_nc = pt_ln.note_columns[nc]
ph_nc = ph_ln.note_columns[nc]
ph_nc.note_value = pt_nc.note_value
ph_nc.volume_value = pt_nc.volume_value
ph_nc.panning_value = pt_nc.panning_value
ph_nc.delay_value = pt_nc.delay_value
ph_nc.effect_number_value = pt_nc.effect_number_value
ph_nc.effect_amount_value = pt_nc.effect_amount_value
end
end
--for effect columns
for ec = 1, 8 do
pt_ec = pt_ln.effect_columns[ec]
ph_ec = ph_ln.effect_columns[ec]
ph_ec.number_value = pt_ec.number_value
ph_ec.amount_value = pt_ec.amount_value
end
end
end
end
end
Function B ) optimized (copy a pattern-track into a phrase):
function vpd_copy_pattr_into_phrase( song, st, sti, spi, snci, sphi, sph, iter, pt_nol, pt_vnc, pt_vec, pt_vcv, pt_pcv, pt_dcv, pt_ecv, in_ph, ph_ln, pt_tr, pt_ln, pt_nc, ph_nc, pt_ec, ph_ec )
song = renoise.song()
st = song.selected_track
sti = song.selected_track_index
spi = song.selected_pattern_index
snci = song.selected_note_column_index
sphi = song.selected_phrase_index
sph = song.selected_phrase
pt_nol = song.selected_pattern.number_of_lines
pt_vnc = st.visible_note_columns
pt_vec = st.visible_effect_columns
pt_vcv = st.volume_column_visible
pt_pcv = st.panning_column_visible
pt_dcv = st.delay_column_visible
pt_ecv = st.sample_effects_column_visible
if ( st.type == renoise.Track.TRACK_TYPE_SEQUENCER ) then
if song.selected_phrase == nil then
renoise.app().window.active_middle_frame = renoise.ApplicationWindow.MIDDLE_FRAME_INSTRUMENT_PHRASE_EDITOR
return
else
renoise.app().window.active_middle_frame = renoise.ApplicationWindow.MIDDLE_FRAME_INSTRUMENT_PHRASE_EDITOR
sph.number_of_lines = pt_nol
sph.visible_note_columns = pt_vnc
sph.visible_effect_columns = pt_vec
sph.volume_column_visible = pt_vcv
sph.panning_column_visible = pt_pcv
sph.delay_column_visible = pt_dcv
sph.sample_effects_column_visible = pt_ecv
in_ph = song.selected_phrase
pt_tr = song.selected_pattern_track
iter = song.pattern_iterator
for pos, line in iter:lines_in_pattern_track( spi, sti ) do
ph_ln = in_ph:line( pos.line )
pt_ln = line
if not pt_ln.is_empty then
--for note columns
for nc = 1, 12 do
pt_nc = pt_ln:note_column( nc )
ph_nc = ph_ln:note_column( nc )
ph_nc.note_value = pt_nc.note_value
ph_nc.volume_value = pt_nc.volume_value
ph_nc.panning_value = pt_nc.panning_value
ph_nc.delay_value = pt_nc.delay_value
ph_nc.effect_number_value = pt_nc.effect_number_value
ph_nc.effect_amount_value = pt_nc.effect_amount_value
end
end
--for effect columns
for ec = 1, 8 do
pt_ec = pt_ln:effect_column( ec )
ph_ec = ph_ln:effect_column( ec )
ph_ec.number_value = pt_ec.number_value
ph_ec.amount_value = pt_ec.amount_value
end
end
end
end
end
Both functions, A ) and B )work correctly and achieve the same results, but they do not work the same.If we look at the code of both functions, they are very similar.Some optimizations:
OPTIMIZATION 1 (whenever possible)
In function B )uses an iteration of type pattern_iterator:
- for pos, line in iter:lines_in_pattern_track( spi, sti ) do (best)
instead of…
- for ln = 1, pt_nol do (worse)
The first option, which uses pattern_iterator , is much more direct when accessing each line.Since the intention is to copy in a phrase, it is correct to use “lines_in_ pattern_track”
OPTIMIZATION 2
Avoid access to tables, which is clearly slower. In function B )uses:
instead of…
- ph_ln = in_ph.lines[ln] (very slow)
There are other 4 or 5 similar cases. The objective is to eliminate access to the tables.
OPTIMIZATION 3
Minimize the number of calls. Both functions are full of equalities, be it an object or a number, like this:
song = renoise.song()
st = song.selected_track
sti = song.selected_track_index
...
It does not matter if the function is very long or has many characters. The fastest access to the values or objects needed will achieve greater speed.Some of these little tricks help prevent a function from taking too long. Also to avoid the famous window of stopping a tool for being too slow.
OPTIMIZATION 4 (rather a habit to avoid repeating)
Another craze of mine is to avoid writing “local” continuously.For this, we can place the value of the room inside the parenthesis of the function, separated by commas:
insteat of…
function vpd_copy_pattr_into_phrase()
local song = renoise.song()
local st = song.selected_track
local sti = song.selected_track_index
local spi = song.selected_pattern_index
local snci = song.selected_note_column_index
local sphi = song.selected_phrase_index
local sph = song.selected_phrase
local pt_nol = song.selected_pattern.number_of_lines
...
Another way to do this:
function vpd_copy_pattr_into_phrase( variable_1, variable_2 )
local song = renoise.song()
local st, sti, spi = song.selected_track, song.selected_track_index, song.selected_pattern_index
...
Note that it is necessary to define first:local song = renoise.song(),to be able to continue with this:local st, sti, spi = …
You can leave the parenthesis of the free function to transport the variables (variable_1, variable_2, … local 1, local 2…) and then add the locals inside the parentheses, at the end
Examples of this type there are many. Being able to migrate a pattern-track to a phrase is a good comparison exercise. The case in reverse, it should be very similar.
It is a good habit to try all your functions with these tricks, so that the user has a better experience in the most extreme cases, when there are tracks with many lines, or want to have access to all the columns at the same time.
Using the provided pattern iterators is “never” a speed optimized solution.
Personally I never use them. There might be a trick or two where they can be beneficial, but I don’t even think that’s the case, so they have never become part of my habits.
Please see https://forum.renoise.com/t/renoise-tools-speed-optimization-initiative/45510
Using the provided pattern iterators is “never” a speed optimized solution.
Personally I never use them. There might be a trick or two where they can be beneficial, but I don’t even think that’s the case, so they have never become part of my habits.
Please see https://forum.renoise.com/t/renoise-tools-speed-optimization-initiative/45510
Reviewing this again, I can confirm that you are right:
function vpd_copy_pattr_into_phrase_( song, sphi, sph, pt_nol, pt_vnc, pt_vec, pt_vcv, pt_pcv, pt_dcv, pt_ecv, in_ph, ph_ln, pt_tr, pt_ln, pt_nc, ph_nc, pt_ec, ph_ec )
local x = os.clock()
song = renoise.song()
sphi = song.selected_phrase_index
sph = song.selected_phrase
pt_nol = song.selected_pattern.number_of_lines
pt_vnc = song.selected_track.visible_note_columns
pt_vec = song.selected_track.visible_effect_columns
pt_vcv = song.selected_track.volume_column_visible
pt_pcv = song.selected_track.panning_column_visible
pt_dcv = song.selected_track.delay_column_visible
pt_ecv = song.selected_track.sample_effects_column_visible
if song.selected_phrase == nil then
renoise.app().window.active_middle_frame = renoise.ApplicationWindow.MIDDLE_FRAME_INSTRUMENT_PHRASE_EDITOR
return
else
if ( song.selected_track.type == renoise.Track.TRACK_TYPE_SEQUENCER ) then
renoise.app().window.active_middle_frame = renoise.ApplicationWindow.MIDDLE_FRAME_INSTRUMENT_PHRASE_EDITOR
sph.number_of_lines = pt_nol
sph.visible_note_columns = pt_vnc
sph.visible_effect_columns = pt_vec
sph.volume_column_visible = pt_vcv
sph.panning_column_visible = pt_pcv
sph.delay_column_visible = pt_dcv
sph.sample_effects_column_visible = pt_ecv
in_ph = song.selected_phrase --in_ph = song.selected_instrument.phrases[sphi]
pt_tr = song.selected_pattern_track
for ln = 1, pt_nol do
ph_ln = in_ph:line( ln ) --ph_ln = in_ph.lines[ln]
pt_ln = pt_tr:line( ln ) --pt_ln = pt_tr.lines[ln]
if not pt_ln.is_empty then
--for note columns
for nc = 1, 12 do
pt_nc = pt_ln:note_column( nc ) --pt_nc = pt_ln.note_columns[nc]
ph_nc = ph_ln:note_column( nc ) --ph_nc = ph_ln.note_columns[nc]
ph_nc.note_value = pt_nc.note_value
ph_nc.volume_value = pt_nc.volume_value
ph_nc.panning_value = pt_nc.panning_value
ph_nc.delay_value = pt_nc.delay_value
ph_nc.effect_number_value = pt_nc.effect_number_value
ph_nc.effect_amount_value = pt_nc.effect_amount_value
end
end
--for effect columns
for ec = 1, 8 do
pt_ec = pt_ln:effect_column( ec ) --pt_ec = pt_ln.effect_columns[ec]
ph_ec = ph_ln:effect_column( ec ) --ph_ec = ph_ln.effect_columns[ec]
ph_ec.number_value = pt_ec.number_value
ph_ec.amount_value = pt_ec.amount_value
end
end
end
end
print( string.format("elapsed time: %.4f ms", ( os.clock() - x ) * 1000) )
end
I guess I used the clock correctly here… It seems that it does not matter to use a pattern_iterator that a “for ln = val_1, val2 do”.Finally I am clear about where I make an optimization failure, and is accessing the tables, like this:
ph_ln = in_ph:line( ln ) --ph_ln = in_ph.lines[ln]
Of course, when using a patter_iterator you avoid using a table directly. With the other case you can use tables or not like this:ph_ln = in_ph:line( ln ) or ph_ln = in_ph.lines[ln]
So I’m liking this more. I use more for i = x, y do,instead of the pattern_iterator, and I like the first way…
What I do not know for sure is if there is any quicker way to do this:
if not pt_ln.is_empty then
--for note columns
for nc = 1, 12 do
pt_nc = pt_ln:note_column( nc ) --pt_nc = pt_ln.note_columns[nc]
ph_nc = ph_ln:note_column( nc ) --ph_nc = ph_ln.note_columns[nc]
ph_nc.note_value = pt_nc.note_value
ph_nc.volume_value = pt_nc.volume_value
ph_nc.panning_value = pt_nc.panning_value
ph_nc.delay_value = pt_nc.delay_value
ph_nc.effect_number_value = pt_nc.effect_number_value
ph_nc.effect_amount_value = pt_nc.effect_amount_value
end
end
Iterate in the 12 note columns, repeating “ph_nc” all the time (or pt_nc)…
It shouldn’t matter, so I’d suggest using ipairs due to readability and less lines. As long as you can minimize any song access/lookup in frequent loops.
Regarding note column iteration, you should use an .is_empty check for these as well. If they are empty, you can set the values manually to default empty values. This will typically minimize song access a lot.
It shouldn’t matter, so I’d suggest using ipairs due to readability and less lines. As long as you can minimize any song access/lookup in frequent loops.
Regarding note column iteration, you should use an .is_empty check for these as well. If they are empty, you can set the values manually to default empty values. This will typically minimize song access a lot.
Thank you! I’ll have it in mind.
For reference, here is a solution to the renoise.song() caching that seems to work universally without any errors. Just put it in the top of main.lua
song = nil
function song_update() song = renoise.song() end
renoise.tool().app_new_document_observable:add_notifier(song_update)
pcall(song_update)
Thanks to Raul for provoking and testing
EDIT due to comment below: Even though it’s elementary, it might be worth mentioning that you always need to be sure that the song exists (not song == nil) before trying to access it. An auto-launching tool trying to access renoise.song() via the song variable before it’s accessible will still fire an error otherwise.
For reference, here is a solution to the renoise.song() caching that seems to work universally without any errors. Just put it in the top of main.lua
song = nil function song_update() song = renoise.song() end renoise.tool().app_new_document_observable:add_notifier(song_update) pcall(song_update)
Thanks to Raul for provoking and testing
This is gold!
In the only scenario that I have not tried yet is in the one of an update, using the tool ~Find Update Tool…I suppose it will act as a new installation.then pcall (song_update) will act correctly, since Renoise will not have created a new song, and there will be no problem.
For me this solves one of my biggest problems: having to define a local song = renoise.song() within each of the functions that are needed, which was quite uncomfortable.If after testing it thoroughly there is no strange problem, I will not hesitate to use it in my other tools.
Thank you very much for reaching the end with this matter!
On the other hand, the Renoise API could not fix this in any way so that the programmer does not have to do these things?Apparently, this only happens with renoise.song().Look at this!!!.. To define some important globals:
Apparently, this only happens with renoise.song().
Yes, because all the other objects are always present. But a song might not always be present - especially, during startup.
It used to be, but (if memory serves me right) tool initialization was pushed to an earlier point in the startup process to ensure that a “meta tool” such as the tool updater was able to intercept and do it’s thing.
In any case, while joule’s example is certainly compact in it’s syntax, it doesn’t entirely solve the problem. I mentioned “auto-launching” tools - that is, tools that are “doing stuff” on startup, without any user interaction (a common example: registering observables). Such a tool absolutely depends on the presence of a song object - and joule’s code doesn’t guarantee that the renoise.song() actually exists - only that it’s kept up to date.
So the workaround is still: for code that executes on it’s own (auto-launching), ensure that this is only performed_after_app_new_document_observable has fired.
For every part of a tool which is invoked as a consequence of a user action, there is no such problem or complexity to take care of.