Hax0Ring The Rubberband

Apropos: crisp should be a verbose drop down in both modes then, pitch shift and time strech.
No need to keep the number in there at all then

Making them shorter, like:
Piano
Smooth
Balanced
Unpitched percussion
Crisp instrumental
Unpitched percussion

could also make the GUI pretty again.

Yeah, agreed.

well, I actually pulled half of an all-nighter and implemented all I intended so far… mostly because I wanted to see how that would feel, usability wise, experiment with the GUI…

Maybe you can just pick out ideas from that? I changed a lot, and also made the GUI kinda ugly, so it might be less work to simply see what is a good idea and what isn’t, and then reimplement the good ideas in the original branch (I’d be willing to do that btw, and step by step, too), than to fix what I have done heh… OR you give me enough to time to pour over this a bunch of times to make it “perfect”, or at least implement your proposals, make the GUI less horrible etc. I will try that anyway, just as practice, so you don’t really have to decide.

Just know that I’m willing to do all the changes I did twice, I really mean that, and don’t panic if I totally twist the plugin… ! Especially about the GUI, I’m the equivalent of someone who has been using HTML for three days haha.

1036 rubberband.png

One thing I really like, which you don’t see in the screenshot, is that seconds/bars/lines get converted into each other when you change between them. But it doesn’t yet reread stuff that can change while the song is open, so the change to a non modal dialog isn’t 100% yet. The checkboxes in front of pitchshifting and timestretching activate themselves when you put a timestretch other than 0, or a stretch factor other than 0 or 1, and deactivate themselves otherwise. If a checkbox is not seleted, the appropriate values get ignored. Having it open and working on various samples really makes a huuuge difference, and combining them into one tool makes sense, too (IMHO).

Very good point about making percentage one of the duration types by the way, that’s much less clunky. And I was mostly being so verbose about the crispness levels because I have no clue what they are, I didn’t intend to keep that way… but shortened as taktik posted them, that could work. Or maybe tooltips?

I thought of doing this also. Is there a good reason for this though? Any real-life usecases?

I don’t see much point in non-modal dialog, makes things much more complicated. Any real-life uses for that?

no use cases actually, just makes the GUI feel better imho, feels more consistent.

Don’t know how/if adding percentage/factor in there would coexist with that though, since that always depends on the size of the selection, so I might have to throw it out then anway :( :P

There I heavily disagree! I don’t think it’s that complicated code wise? It’s “just” predictable work that has to be done.

Having the window open and being able to change the selection (or use the whole of Renoise for that matter) is great, as is being able to apply the same parameters to several selections/samples. A modal dialog resets every time it gets openened, and that can be very annoying.

Or grabbing the duration from a selection, then stretching another selection to that duration… ! No, it’s actually so good, once you used it that way you won’t go back I think. Everything that can possibly be non modal, should be IMHO.

This is probably a matter of taste, but I managed to squeeze factor/percentage into the mix and keeping the “conversion on the fly” intact. If you grab the duration from the selection while having lines/bars selected, the length of the selection get converted to the number of lines/bars. (Obviously, if you have “factor” selected and grab the selection, the factor will always be 1, but hey, at least it’s consistent ^^)

Whenever you convert from/to factor, the current song speed and selection size get taken into account, which might be confusing I dunno, but at least for myself I want to keep that to check it out further, and most importantly to think of an actual use for it. But yeah, it “feels good”, at least to me.

However, I just couldn’t help myself with the GUI. You see, I like precision and I believe in tooltips and screen estate. Learning buttons once, then using them… but I’m not under any delusions about how this fits in with, uhm, “official tools” and whatnot. But I don’t wanna lie and say I don’t like it, either:

1037 rubberband2.png

I’m a bit shy about posting the code to be honest, even after I looked at it/tested it more I mean, because I kinda treated it as if it was my own, for example I begun to convert stuff to my prefered curly braces style (each brace on its own line, everything else drives me mad after a while :P)… giving variables more verbose names, that sort of thing… I just can’t help it! If I sent you a diff there would be more changed things than not…

BUT I intend to structure and comment it (even more), so at least it shouldn’t be hard to take features out from it. So please don’t be pissed. I promise I won’t do that again, I’d rather create stuff from scratch, but now it’s kind of too late, if you know what I mean =/

i think it looks good,and im using out with great results :yeah:

EDIT:i have just had some time to play around with this again,and i must say i think this one is “harder”/slower to use

and it seems more "buggy"than the other version you made,dont know if its just me though

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.

Johann, could you post an .xrnx please?

You can open the editor in Renoise, navigate to Tools/blah.Rubberband/main.lua, replace the whole file with what I posted, save it, select “reload all tools”, and it should work… crosses fingers

but doing so, you’ll lose/replace the original script, no? Would like to have this extra, sort a like, rubberband johan edition or something :)

But that’s kinda the thing… I just don’t wanna go ahead and do this on my own just like that, I’m not sure it’s justified, and anything I could come up with this would by definition also apply to the original…

But hey, you can do that yourself: just rename the folder and give it the same id as the folder name, then reinstall the original rubberband plugin, and you have both :)

I don’t mind if you fork it, cause I am never going to include GUI looking like that in my program: :D

I will reintegrate features I see fit from there though.

You could always fetch the original from the trunk if you still want to revert back:

Note that the version in trunk is not the same as in tools.renoise.com, the trunk version is WIP, I will integrate some Johann’s changes and then release a new version. This needs a little more work as I am not the brightest GUI designer myself. :D

i love that you can apply timestretch/pitchshift to a selection in the sample

yeah it’s not that easy is it haha :D it’s kinda like HTML, the tools are all there, the specs are fine… but designing it… uhhh.

the “run cmd on selection” plugin made me realize that the middle part (saving as temp samples, reloading them) keeps the temporary files around for longer than it should (it deletes them at the end, instead deleting each after loading it), so yeah, I wanna fix that for example, and generally give it a good hard look - so I’ll give it a name and package it, since that’s okay with you.

BUT… what name? Rubberbandit? Rubberbong? I think I’ll go with RubberbandRemix ^^

Just don’t expect anything beyond this point except bugfixes, there kinda isn’t anything to add unless rubberband gets new features or something… Hmm well when Renoise allows us to set the selection, it would obviously be nice if the stretched part would get reselected again after the operation. That will be the icing on the cake :D

The selection handling could be better aswell. I have already requested save_selection_as method, and some way to load sample inside a sample. They are in the todo list of TakTik AFAIK. The way you do selection handling is bit too hacky for my taste, but I guess it’s better than nothing. :)

Yeah, well, I never said it’s not hacky :P And I never really trust myself with getting all offsets right, that is, not reading one byte too much or too little… you know, the little math errors that can go unnoticed for a long time, if not forever.

Like a person reading by putting their finger under anything they read, I am VERY slow with anything that has a lot of loops, I have to go over it in my head step by step without losing track, which means over and over again… I never have a “big picture” so to speak. I’m must not good with numbers, but I try to cope ^^

Renoise functions to do these things would be a great relief for me, not just for performance reasons or whatever, but then I’ll know that what I inted to do actually happens lol.

Most computer programming bugs can be described with two words: “Off by one”. ;)