Its so useful you have to complete it!!!
rfgd.garbanzo.InterpolateBeyond_V1.5.xrnx (4.4 KB)
Mr garbanzo always delivers.
- Now works on panning, delay, and volume columns
- fixed a bug with sample effect column
I challenge you to interpolate faster than this gif.

n
What a beast,serious kudos.
Had the following notice, running the tool through a keyboard shortcut, triggering the shortcut whilst on the panning column of a track where the first value was a 1 and no further values were present in that particular panning column;
"'C:\Users\pluge\AppData\Roaming\Renoise\V3.4.3\Scripts\Tools\rfgd.garbanzo.InterpolateBeyond.xrnx' failed to execute in one of its key binding functions.
Please contact the author (GARBANZO) for assistance…
std::logic_error: ‘Unexpected panning value: ‘130’. Expected a value below 127 or an effect byte (9-Z) followed by a value byte (0-F)’
stack traceback:
[C]: ?
[C]: in function ‘__newindex’
[string “do…”]:22: in function <[string “do…”]:9>
main.lua:263: in function ‘processModulationPoints’
main.lua:391: in function ‘InterpolateBeyond’
main.lua:397: in function main.lua:395"
latest tool version, latest renoise, windows 10
Truly going beyond with this one! ![]()
interesting, i will play with that. it should be taking 127- your value of 1 and getting 126, I’m not sure where its creating 130.
I’m sorry, I’m unable to recreate this, it works for me. Is there any more information you could provide? perhaps a snip of the whole screen?


Here I made a pattern with a length of 32, put a 1 in the panning column and hit the keyboard shortcut for the interpolation;
Perhaps, it is because I have the “hide 0’s in the effect column” in the preferences gui tab unchecked?
rfgd.garbanzo.InterpolateBeyond_V1.6.xrnx (4.5 KB)
Thank you, I had a math error. should be fixed now, try this. I never noticed, it worked fine on big patterns and I forgot to check small patterns.
Boom! Cheers, seems to work fine now. Only way I can make the tool crash is through setting a pattern length of one, which doesn’t make sense anyway :).
The pan/vol/delay columns do not work properly for me Sample commands work fine.
I have 16 patterns,64 lines each
Vol 10 on line 00 in pattern one
Vol 80 on line 63 in pattern 16.
Nothing happens.
rfgd.garbanzo.InterpolateBeyond_V1.61.xrnx (4.5 KB)
sorry, all fixed. i had set it to not run if a value was > “80” (because you can mis type higher values and i didn’t want to throw an error). i needed to set it to >=“80”. Should be fixed now. I’m always mixing up whether something goes between 0-127 and 1-128.
Fixed a few minor bugs while I was in there. Hopefully this saves another issue haha.
I used the delay column this time and put 01 on a pattern and F0 on the 24th pattern.
It tried to interpolate but it just put 01 all the way up to F0
Volume it puts 00 all the way to 80
Also it does not interpolate at all if the end command is on the last line of a pattern.
rfgd.garbanzo.InterpolateBeyond_V1.62.xrnx (4.4 KB)
another missed variable, this time when it was finding the next point on the next pattern it was using the current pattern, not the next pattern. thanks for testing so much and your patience, there’s a lot for me to check. Let me know what else is wrong lol.
No problems so far, I’m wondering if it would be easy to add some shapes to it.
For example you have linear and logarithmic.
Could you add like sinewave,squarewave,sawtooth??
Great idea. Would also be awesome to have randomization within a selectable range of values, at a given line frequency, but maybe that’s asking too much?
rfgd.garbanzo.InterpolateBeyond_V1.7.xrnx (5.0 KB)
behold! sin, square, saw, tri, and random. within your determined range. The ultimate one stop shop for automation in the pattern editor. Until Toybox suggests something else.
Some notes:
- Uses your step length you determine how the rate of the “LFO”. A 1 will be one cycle, and so on. A 0 will be bumped up to 1, so don’t be afraid to use it with 0.
- Sin, Tri, Square will prioritize ending on their end value. Only every OTHER step length will change its rate, because I found it annoying when it wouldn’t finish on the value I specified. Saw always finishes on its high point so it doesn’t matter.
- Random is random between the specified values, like the other waves.

Pretty cool new features!
However your waveform functions have multiple problems. As a structural suggestion I think it would be much easier to debug an extend these different waveforms if you unified all of them to just receive a time value between 0 and 1 and a frequency (what you can numOsc in your code) and return a modified time value that contains the wave, still between 0 and 1.
This way you could then input this value into the same old interpolation function to get the final value. The waveform functions have no need to know about the start and end range, in fact, them handling this just makes them harder to write and reason about.
I also see that you don’t allow for odd frequency inputs and your sin and triangle wave has some issues in general. I understand that it is a bit confusing to apply these waves in a way that their cycle ends on the peak instead of returning to zero so let me provide a working implementation for each wave, allowing the frequency to be 0 for triangle and sine (which will result in a half-wave) while letting both odd and even numbers to be used. Essentially this will result in always having cycles of (edit step + a half wave) to ensure you always end on the highest point of the waveform (and so correctly reach your endValue).
Exploration time!
Here is a graph on desmos.com that shows the sinewave modified for your purpose, you can use thefslider to see how it behaves for different frequency values. (thexaxis there is essentiallytfrom below)
In general I highly recommend this site as a quick way to test out equations especially when it comes to waveforms. It’s much easier to see if something is off than for example in a hex pattern generated inside renoise. One downside is it’s using math notation instead of code, but most things are easy to transfer. Sure, it has 𝜏 (tau), the constant so radical it got its own manifestobut that’s just math.pi * 2 in lua.
Now lets see some waves…
-- math.max here returns the larger from the two numbers it receives.
-- this is to make sure f is always at least 1 without an "if" statement
local function saw(t, f)
local tf = t * math.max(1, f)
return tf - math.floor(tf)
end
-- lets reuse the saw function to have an easy squarewave.
-- in a sense a squarewave is just a rounded sawtooth
-- (not the most efficient way but it's fine for our purpose)
local function sqr(t, f)
return math.floor(saw(t, f) + 0.5)
end
-- this is the modified sinewave, shown on the desmos graph above
local function sin(t, f)
local tf = t * math.pi * 2 * (f + 0.5)
return math.sin(tf - math.pi / 2) * 0.5 + 0.5
end
-- tri gets the same treatment as the sinewave
local function tri(t, f)
local tf = t * (f + 0.5) * 2 + 1
return math.abs((tf % 2) - 1.0)
end
-- random is dead simple, maybe it could make use of f somehow?
local function ran(t, f)
return math.random()
end
Ok, so how would you actually use these functions? You just call them with your numOsc (without doing any modification to it) and the t value you were already calculating in each of your original implementations.
λ
A cool thing about lua is that functions are first-class citizens, they can be passed to other functions just like numbers, strings or what-have-you. Instead of passing a string like “saw” and then checking that string inside another function to decide what function to call, you can just pass the
sawfunction itself.
Since each of our waveform functions have the same shape (that is, they expect the same kind of inputs and return the same output) this receiving function can just call any of them with the same inputs without having to know anything more.
-- our old friend the linear interpolation included for completeness
function lerp(a, b, t)
return a + t * (b - a)
end
-- originally your wave functions each received the following values
-- (startValue, endValue, stepCounter, numOsc, curStep)
-- and had to deal with them all alone
-- our new function will get these as well but also a wave function to apply
function interpolate_with(waveFun, startValue, endValue, curStep, stepCounter, numOsc)
-- do the prep-work of normalizing the time
-- so that our waves can stay clean unlike our ocean
local t = curStep / (stepCounter + 1)
-- here we call whatever wave function we got
-- it can be sin, saw etc, it's all groovy!
local wavyTime = waveFun(t, numOsc)
-- do the remapping from 0..1 range to our actual values using lerp
return lerp(startValue, endValue, wavyTime)
end
-- you can just call this with any of the functions we defined
-- to get back the value in the start/end range, waving included.
local a_test_value = interpolate_with(saw, 0, 10, 5, 20, 3)
local another_value = interpolate_with(tri, 0, 10, 5, 20, 12)
While it requires a bit of adaptation both in code and in your head, in the end this can make your program considerably simpler to follow and extend.
I recommend using this to achieve the regular interpolation modes as well so that you can just use the same apply function to do everything. This way you can get rid of the parts where you check for the other variable to see what function to call. Instead you’ll be passing in a different wave function for each type and use it without any further checks.
For example
-- your "wave" function for the default linear interpolation
local function lin(t, f)
return t
end
But wait, where is the log function?
Rabbit hole alert!
Check out different easing functions to get more ideas on easings.net.
Click on an easing, scroll down and you’ll be presented with a math function in code, conveniently it will always be implemented to expect a value (x) in the range 0…1, just like our other waves! Bouncing ball interpolation when?
Doing this will also fix the bug (maybe introduced with this last update?) with your linear interpolation mode where the last value will be repeated. You need to add +1 to the stepCounter there as well, doing it one place is also a good way to make these kinds of issues easier to handle.
A few more things to note regarding cleaning up the code. ![]()
In lua you can access the length of arrays with the # sign. Your countSequences function isn’t necessary, you can simply get the length of the pattern sequence with
local number_of_sequences = #renoise.song().sequencer.pattern_sequence
-- ^--this thing here will return the length like 10
The way you have written the column operations to each get the same line makes the code quite noisy as the actual calculations you care about get lost in the sea of renoise… calls, just store the thing that all cases use in a local variable to clean up a bit, just like you do with other values.
Instead of
-- ....
if not beginningOfProject then
if subColumnID == 3 then
parameterStart = renoise.song().patterns[patternID_from_sequenceID(seqID)]:track(trackID):line(lineID).note_columns[columnID].volume_value
elseif subColumnID == 4 then
parameterStart = renoise.song().patterns[patternID_from_sequenceID(seqID)]:track(trackID):line(lineID).note_columns[columnID].panning_value
elseif subColumnID == 5 then
parameterStart = renoise.song().patterns[patternID_from_sequenceID(seqID)]:track(trackID):line(lineID).note_columns[columnID].delay_value
else
parameterStart = renoise.song().patterns[patternID_from_sequenceID(seqID)]:track(trackID):line(lineID).effect_columns[columnID].amount_value
end
elseif beginningOfProject and isSample(deviceStringEnd) then
-- .... and so on
Do it like
-- ...
-- see what gets used repeatedly and bring it out for easy access
-- you could do it with the .note_column as well
-- but this is already much more readable.
local line = renoise.song().patterns[patternID_from_sequenceID(seqID)]:track(trackID):line(lineID)
if not beginningOfProject then
if subColumnID == 3 then
parameterStart = line.note_columns[columnID].volume_value
elseif subColumnID == 4 then
parameterStart = line.note_columns[columnID].panning_value
elseif subColumnID == 5 then
parameterStart = line.note_columns[columnID].delay_value
else
parameterStart = line.effect_columns[columnID].amount_value
end
elseif beginningOfProject and isSample(deviceStringEnd) then
--- ... and so on
You can do the same thing in the getParameterValues function.
Consider using constants instead of magic numbers, for example instead of
subColumnID == 3
you could use
subColumnID == renoise.SUB_COLUMN_VOLUME
it might look more verbose but you are less likely to make mistakes because of a mistyped number and if you look at the start of the case, you can immediately understand what is it about, not a big deal in these cases with a single line each, but it’s something that might come handy at some point.
Not sure what you use to write your tools with, but I highly recommend checking out VSCode and the new lua definitions for Renoise, this can help you by
- providing autocompletion for renoise’ built-in functions, constants and other values
- popups with descriptions about functions, what they expect and what they return
- handy warnings about your own code, like unused or redefined variables etc
- syntax errors highlighted right in the editor
- auto-indent your entire code by right-click → “Format Document”
This makes it so that you can catch a lot of bugs before even running your code. If you want to try this but need help figuring out how, just ask!
And again, sorry for the long talk, got a bit carried away, hope I at least managed to keep it easy to follow and not too annoying.
Have fun and good luck!
Thank you, I will try to digest this
Nice!
I’m really happy to see how people are contributing to the tool with suggestions and mathematical observations (thanks @unless). This gives me hope for the future of Renoise :')
