I really wouldn’t be surprised if there is a bug in there somewhere… actually I would be more surprised if there wasn’t… and everything has to be exact as is possible, otherwise it’s worthless. so yeah, I still wouldn’t “trust” it by any means!
But I spent quite a bit of time polishing this code wise (to my best knowledge of course, and personal taste huh), and tested everything I could think of, couldn’t find any glaring bug… (but it’s hard to be sure with all those unit conversion etc…)
Still, unless I or someone else finds a bug, this is it, this is the best I can do. In respect to the code and to the GUI, so since I already posted my previous attempts in this thread I might as well post this:
[details=“Click to view contents”] ```lua
–[[============================================================================
main.lua
============================================================================]]–
if os.platform() == ‘MACINTOSH’ then
io.chmod(renoise.tool().bundle_path … ‘bin/osx/rubberband’, 755);
end
–[[GUI]]–
local dialog = nil
local DIALOG_BUTTON_HEIGHT = renoise.ViewBuilder.DEFAULT_DIALOG_BUTTON_HEIGHT
– tool registration
renoise.tool():add_menu_entry {
name = “Sample Editor:Process:Rubberband…”,
invoke = function() show_rubberband_dialog() end
}
– processing
function display_error()
if os.platform() == ‘LINUX’ then
renoise.app():show_prompt(‘Rubberband missing, or bad command!’,
“To use this feature you must install Rubberband command line utility\n”…
“On Ubuntu you must install package rubberband-cli, you can do this by\n”…
“issuing a command: sudo apt-get install rubberband-cli\n\n”…
“This error may also be triggered when you are trying to make too heavy\n”…
“stretch which overloads the system, if you are sure you have rubberband\n”…
“installed then try again with more reasonable input parameters.”
, {‘Ok’})
else
renoise.app():show_prompt(‘Something went wrong!’,
“There is something wrong with installation or your system. Try reinstalling\n”…
“the script or see Renoise error logs for more information.\n”…
“This error may also be triggered when you are trying to make too heavy\n”…
“stretch which overloads the system, if you can try again with more\n”…
“reasonable input parameters.”
, {‘Ok’})
end
end
function process_rubberband(cmd)
local exe
if os.platform() == ‘WINDOWS’ then
exe = ‘"’ … renoise.tool().bundle_path … ‘bin/win32/rubberband.exe"’
elseif os.platform() == ‘MACINTOSH’ then
exe = ‘"’ … renoise.tool().bundle_path … ‘bin/osx/rubberband"’
else
exe = ‘rubberband’
end
local ofile = os.tmpname(‘wav’)
local ifile = os.tmpname(‘wav’)
local xfile = os.tmpname(‘wav’)
local buffer = renoise.song().selected_sample.sample_buffer
print (’’)
print ('buffer.number_of_frames: ’ … buffer.number_of_frames)
– create temporary samples
local instrument = renoise.song().selected_instrument
local temp_sample = renoise.song().selected_instrument:insert_sample_at(renoise.song().selected_sample_index + 1)
local temp_buffer = temp_sample.sample_buffer
local temp_sample2 = renoise.song().selected_instrument:insert_sample_at(renoise.song().selected_sample_index + 1)
local temp_buffer2 = temp_sample2.sample_buffer
if (not temp_buffer:create_sample_data(buffer.sample_rate, buffer.bit_depth, buffer.number_of_channels, (buffer.selection_end - buffer.selection_start) + 1 )) then
renoise.app():show_error(“Couldn’t create sample!”) return
end
local int_frame
local int_chan
local int_new_frame
– copy the selection to temp sample 1
for int_chan = 1, buffer.number_of_channels do
int_new_frame = 1
for int_frame = buffer.selection_start, buffer.selection_end do
temp_buffer:set_sample_data(int_chan, int_new_frame, buffer:sample_data(int_chan, int_frame))
int_new_frame = int_new_frame + 1
end
end
– save temp sample 1
temp_buffer:save_as(ofile, ‘wav’)
– process the file
os.execute(exe … " " … cmd … " “…ofile…” "…ifile);
os.remove(ofile)
if not io.exists(ifile) then
display_error()
return
end
– load the processed result into temp sample 1
temp_buffer:load_from(ifile)
os.remove(ifile)
print ('file.number_of_frames: ’ … temp_buffer.number_of_frames)
– allocate the second sample buffer
– the size is “stuff outside the selection” + “size of processed file”
if not temp_buffer2:create_sample_data(
buffer.sample_rate,
buffer.bit_depth,
buffer.number_of_channels,
(buffer.number_of_frames - (buffer.selection_end - buffer.selection_start + 1)) + temp_buffer.number_of_frames
)
then
renoise.app():show_error(“Couldn’t create sample!”) return
end
int_new_frame = 1
– copy the part before the selection to temp sample 2
if buffer.selection_start > 1 then
for int_frame = 1, buffer.selection_start - 1 do
for int_chan = 1, buffer.number_of_channels do
temp_buffer2:set_sample_data(int_chan, int_frame, buffer:sample_data(int_chan, int_frame))
end
int_new_frame = int_new_frame + 1
end
end
– copy the processed selection to temp sample 2
for int_frame = 1, temp_buffer.number_of_frames do
for int_chan = 1, buffer.number_of_channels do
temp_buffer2:set_sample_data(int_chan, int_new_frame, temp_buffer:sample_data(int_chan, int_frame))
end
int_new_frame = int_new_frame + 1
end
– copy the part after the selection to temp sample 2
if buffer.selection_end < buffer.number_of_frames then
for int_frame = buffer.selection_end + 1, buffer.number_of_frames do
for int_chan = 1, buffer.number_of_channels do
temp_buffer2:set_sample_data(int_chan, int_new_frame, buffer:sample_data(int_chan, int_frame))
end
int_new_frame = int_new_frame + 1
end
end
– save temp sample 2
temp_buffer2:save_as(xfile, ‘wav’)
– delete the temporary samples
– TODO: are you sure this NEVER deletes the wrong samples… ?!
instrument:delete_sample_at(renoise.song().selected_sample_index + 1)
instrument:delete_sample_at(renoise.song().selected_sample_index + 1)
– load temp sample 2 into the original sample slot
– (that all sample properties get preserved automatically!)
buffer:load_from(xfile)
os.remove(xfile)
print ('buffer.number_of_frames: ’ … buffer.number_of_frames)
end
function parse_rubberband(stretch, shift, crisp, bool_precise, preserve_formant)
local cmd = " --crisp "…(crisp - 1); – rubberband crispness levels start at 0, LUA starts with 1
if stretch ~= 1.0 and stretch ~= 0.0 then
cmd = cmd … " --time "…stretch
end
if shift ~= 0.0 then
cmd = cmd … " --pitch "…shift
end
if bool_precise then
cmd = cmd … ’ -P’
end
if preserve_formant then
cmd = cmd … ’ -F’
end
print (cmd)
process_rubberband(cmd);
end
– GUI
function show_rubberband_dialog()
if (dialog and dialog.visible) then
– already showing a dialog. bring it to front:
dialog:show()
return
end
local TIMESCALE_LINES = 1
local TIMESCALE_BEATS = 2
local TIMESCALE_SECONDS = 3
local TIMESCALE_FACTOR = 4
local real_duration = 0
local bool_precise = false
local int_duration_type = TIMESCALE_BEATS
local int_last_duration_type = TIMESCALE_BEATS
– values that are prone to change while the tool is running
– get initialized by calling get_updated_values() below
local int_bpm
local int_lpb
local real_lines_per_second
local int_number_of_selected_frames
local int_sample_rate
local real_selection_in_seconds
local function get_updated_values()
int_bpm = renoise.song().transport.bpm
int_lpb = renoise.song().transport.lpb
real_lines_per_second = int_bpm * int_lpb / 60.0
if renoise.song().selected_sample.sample_buffer.has_sample_data then
int_number_of_selected_frames = renoise.song().selected_sample.sample_buffer.selection_end - renoise.song().selected_sample.sample_buffer.selection_start + 1
int_sample_rate = renoise.song().selected_sample.sample_buffer.sample_rate
else
int_number_of_selected_frames = 44100
int_sample_rate = 44100
end
real_selection_in_seconds = int_number_of_selected_frames / int_sample_rate
end
get_updated_values()
local vb = renoise.ViewBuilder()
– checks wether to turn the pitchshifting toggle on or off
local function notify_pitchshift()
if vb.views.slctShiftCents.value == 0 and vb.views.slctShiftSemitones.value == 0 then
vb.views.tglPitchshifting.value = false
else
vb.views.tglPitchshifting.value = true
end
end
– checks wether to turn the timestretching toggle on or off
local function notify_timestretch()
if real_duration == 0 or (int_duration_type == 4 and real_duration == 1) then
vb.views.tglTimestretching.value = false
else
vb.views.tglTimestretching.value = true
end
end
–[[PITCHSHIFT & TIMESTRETCH]] –
local crisp_selector = vb:popup
{
width = 40,
tooltip = “0: Mushy\n1: Piano\n2: Smooth\n3: Balanced multitimbral mixture\n4: Unpitched percussion with stable notes\n5: Crisp monophonic instrumental\n6: Unpitched solo percussion”,
items = {
‘0’,
‘1’,
‘2’,
‘3’,
‘4’,
‘5’, – default
‘6’},
value = 6
}
–[[PITCHSHIFT]] –
local semitone_selector = vb:valuebox
{
id = “slctShiftSemitones”,
min = -48,
max = 48,
value = 0,
tooltip = “Semitones”,
notifier = notify_pitchshift
}
local cent_selector = vb:valuebox
{
id = “slctShiftCents”,
min = -100,
max = 100,
value = 0,
tooltip = “Cents”,
notifier = notify_pitchshift
}
local formant_selector = vb:checkbox
{
}
–[[TIMESTRETCH]] –
local duration_input = vb:textfield
{
id = ‘txtDuration’,
tooltip = “The desired duration”,
align = “right”,
width = 90,
value = tostring(1),
notifier = function(real_value)
real_duration = tonumber(real_value)
notify_timestretch()
end
}
local function convert_timescale(from, to, value)
if from == to then return value end
– seconds to X
if from == TIMESCALE_SECONDS then
if to == TIMESCALE_LINES then
return value * real_lines_per_second
elseif to == TIMESCALE_BEATS then
return value * real_lines_per_second / int_lpb
elseif to == TIMESCALE_FACTOR then
return value / real_selection_in_seconds
end
– factor to X
elseif from == TIMESCALE_FACTOR then
if to == TIMESCALE_SECONDS then
return value * real_selection_in_seconds
elseif to == TIMESCALE_LINES then
return value * real_selection_in_seconds * real_lines_per_second
elseif to == TIMESCALE_BEATS then
return value * real_selection_in_seconds * real_lines_per_second / int_lpb
end
– lines to X
elseif from == TIMESCALE_LINES then
if to == TIMESCALE_SECONDS then
return value / real_lines_per_second
elseif to == TIMESCALE_BEATS then
return value / int_lpb
elseif to == TIMESCALE_FACTOR then
return value / real_lines_per_second / real_selection_in_seconds
end
– beats
elseif from == TIMESCALE_BEATS then
if to == TIMESCALE_SECONDS then
return value / real_lines_per_second * int_lpb
elseif to == TIMESCALE_LINES then
return value * int_lpb
elseif to == TIMESCALE_FACTOR then
return value / real_lines_per_second / real_selection_in_seconds * int_lpb
end
end
end
local duration_mode_selector = vb:popup
{
items = {‘lines’, ‘beats’, ‘seconds’, ‘factor’},
tooltip = “The measurement of the desired duration\nFactor relates to the size of the selection that is being processed.”,
value = int_duration_type,
width = 70,
notifier = function(real_value)
int_duration_type = real_value
get_updated_values()
duration_input.value = tostring(convert_timescale(int_last_duration_type, real_value, duration_input.value))
int_last_duration_type = real_value
end
}
local precise_toggle = vb:checkbox
{
id = “chkPrecise”,
tooltip = “Aim for minimal time distortion?”,
value = bool_precise,
notifier = function(boolean_value)
bool_precise = boolean_value
end
}
local grab_duration_button = vb:button
{
text = “Grab”,
tooltip = “Grab the desired duration from the length of the current sample/selection”,
height = DIALOG_BUTTON_HEIGHT - 4,
width = 60,
notifier = function()
if renoise.song().selected_sample.sample_buffer.has_sample_data then
get_updated_values()
local selection_duration = (renoise.song().selected_sample.sample_buffer.selection_end - renoise.song().selected_sample.sample_buffer.selection_start + 1) / renoise.song().selected_sample.sample_buffer.sample_rate
– factor is always 1 when grabbing from a selection…
if duration_mode_selector.value == TIMESCALE_FACTOR then
duration_input.value = tostring(1)
else
duration_input.value = tostring(convert_timescale(TIMESCALE_SECONDS, duration_mode_selector.value, selection_duration))
end
notify_timestretch()
end
end
}
local process_button = vb:button
{
text = “GO!”,
tooltip = “Apply Rubberband with selected parameters.”,
height = DIALOG_BUTTON_HEIGHT - 3,
notifier = function()
get_updated_values()
local real_stretch_factor
real_stretch_factor = convert_timescale(duration_mode_selector.value, TIMESCALE_FACTOR, real_duration)
– TODO: take selection size into account: warn if the product of that and stretch factor ist very big
if real_stretch_factor > 100 then
local conf = renoise.app():show_prompt(‘Too big stretch!’, ‘You want to multiply sample length by ‘… real_stretch_factor…’! Doing this may freeze Renoise for several minutes or even indefinitely. Are you sure you want to continue?’, {‘Sure’, ‘No way!’});
if conf ~= ‘Sure’ then
return
end
end
– only invoke rubberband if there is actually something to do.
if (vb.views.tglTimestretching.value and real_stretch_factor ~= 0 and real_stretch_factor ~= 1) or (vb.views.tglPitchshifting.value and (semitone_selector.value ~= 0 or cent_selector.value ~= 0)) then
parse_rubberband(
vb.views.tglTimestretching.value and real_stretch_factor or 1, – only apply timestretching when the toggle is set
vb.views.tglPitchshifting.value and (semitone_selector.value + (cent_selector.value / 100)) or 0, – same thing for pitchshifting
crisp_selector.value,
bool_precise,
formant_selector.value
)
end
end
}
local view = vb:row
{
vb:column
{
margin = 0,
spacing = 0,
vb:row
{
style = “body”,
width = “100%”,
vb:horizontal_aligner
{
margin = 2,
spacing = 2,
mode = “center”,
vb:checkbox{id = ‘tglPitchshifting’, value = false, tooltip = “Enable pitchshifting?”},
semitone_selector,
cent_selector,
formant_selector,
vb:text { text = ‘Preserve formant?’ }
},
},
vb:row
{
style = “body”,
width = “100%”,
vb:horizontal_aligner
{
margin = 2,
spacing = 2,
width = “100%”,
mode = “center”,
vb:checkbox { id = ‘tglTimestretching’, value = false, tooltip = “Enable timestretching?” },
precise_toggle,
duration_input,
duration_mode_selector,
grab_duration_button,
}
}
},
vb:column
{
height = “100%”,
margin = 3,
spacing = 1,
style = “body”,
uniform = true,
process_button,
crisp_selector
}
}
dialog = renoise.app():show_custom_dialog (
“Rubberband”,
view
);
end
1039 [![rubberband3.png](https://files.renoise.com/forum/uploads/1039-rubberband3.png)](https://files.renoise.com/forum/uploads/1039-rubberband3.png)
one thing I'm a bit unsure about is the creation and deletion of temporary samples, but otherwise, unless I find bugs I think I'm done with this.
I now cloned the whole thing and converted it into a frontend for [SoX](http://sox.sourceforge.net/), which seems to work fine for pitchshift and timestretch already, but it's the first time I ever did anything with SoX so I'm not really sure where I'll go with that. I just wanted to see if I could reuse all that code, and it was SO amazingly simple, I basically just had to strip stuff away and change the order of files and arguments hehe.