New Tool (2.8): Custom Wave Generator

download

this is the “more complex thing” I was talking about in the thread about the wave generators.

on the code side, it is interesting to note that the actual creation of the wave happens in process_data() using an array of function pointers, which should ease the creation of more complex interactions between the different generators.

a lot of code has been written taking in count that each wave generator can have its own GUI (and the pulse generator actually has a “width” slider which others don’t have).

Nice tool It-Alien, look forward to development…

(same goes for a lot of other peoples efforts on the scripting so a blanket kudos to all)

version 0.2 fixes and improves lots of things. the main improvement is the ability to modulate the amplitude of an operator with another one.

known bugs:

  • modulating an operator which already modulates another does not work

I forgot to mention another interesting main feature of version 0.2: the “Wave” operator type, with which you can specify a currently loaded sample to be used as an operator, pretty much as wavetable synthesis does; the selected wave will be stretched or expanded to the number of frames of the new sample if needed.

Almost like C-sound in a box but then more limited… :P

version 0.25 fixes the bug which prevents the modulation of a modulator; the bug has been solved by preventing the user from choosing to modulate of a modulator :rolleyes:

also adds a new wave type: Noise

Btw: How do you guys manage to navigate in your sources without using any comments or separator lines (—) to separate “code blocks”?

IT: Which editor are you using, which is probably doing this for you?

in Windows I use Notepad++, which has LUA support and has the classic “[-][+]” tree collapser/expanders

in Linux I use Geany, which also has a function list from with which you can easily reach a function with a click.

in both cases, I use the standard search function a lot :)

and version 0.3 adds arcsine,arccosine and tangent to the available waves.

unless you have some more ideas about it, I think this is the final version.

The GUI looks a bit, well, strange ;) Can I help somehow here? Or could you give me some feedback why this and that doesn’t work as you’d expect it in the GUI API?

Also at least separating the GUI functions from the processing functions will not hurt. Righ now all is mixed together which makes the script hard to understand?

Regarding features it would of course be great to have realtime previews. But this will create tons of undo/redo actions in Renoise, so this might now be worth the attempt.

An auto amplification would not hurt as well. If you right now only use one operator with a single sin, you’re getting a tiny sin in result only.

Oh, and could you change the “LinToDb” “DbToLin” to lua style? I know you’ve copyied them from other scripts, but I see no reason why only those two functions should use a differnet naming style?

hmm sorry, this was actually a bug: if you pressed on a tab, the script automaticaly counted it as a valid operator even if you did not choose a wave type for it.

this and code organizing and cleaning has been published as version 0.31

the only thing I didn’t get is why the column which contains all the operator GUI does not take all 100% of available space in width.

  
  
local row_gui = vb:row {  
 id = "rowGui",  
 margin = MARGIN_DEFAULT;  
 width = "100%" -- <==  
}  
  
local column_gui = vb:column {  
 id = "columnGui",  
 style = "group",  
 margin = MARGIN_DEFAULT,  
 width = "100%", -- <==  
 text_operator,  
 row_wave,  
 column_wavetable,  
 row_amplitude,  
 row_width,  
 row_invert,  
 row_frequency_multiplier,  
 row_modulate  
 }  
  
 vb.views.rowGui:add_child(column_gui);  
  
  

There is lot of void space in the GUI because it contains all the stuff which appears and disappears depending on which type of wave you selected. I could not find an easy way to fix this because I didn’t find a way to do a sort of “repaint()” of the GUI elements. If there was (is) one, I would simply remove/add the children GUI elements from/to the parent container and reshow the dialog.

Likely the space isn’t used because of the margins you have set. 100% minus the margin, add the margin for the column as well so 100% width - 2x MARGIN_DEFAULT.
What if you would use the horizontal aligner instead in mode = ‘left’ ?

unfortunately the aligners do not allow the “style” property, so it looks a bit worse than using a column. plus, it did not help removing the blank space on the right of which I don’t know the cause

You can’t tell a row to be 100% as width as its parent, cause its width is set up by its children. Same for the height of a column.

Those relative sizes are nice to have, but often you are loosing the overview which is resizing what now.
I usually solve this by using consts for the texts, controls. This way the whole thing also looks a lot cleaner - it aligns itself.

Heres a GUI proposal:

  
function show_dialog()  
  
 if (dialog and dialog.visible) then  
 -- already showing a dialog. bring it to front:  
 dialog:show()  
 return;  
 end  
  
 local MARGIN_DEFAULT = renoise.ViewBuilder.DEFAULT_CONTROL_MARGIN;  
 local SPACING_DEFAULT = renoise.ViewBuilder.DEFAULT_CONTROL_SPACING;  
  
 local TEXT_LABEL_WIDTH = 80;  
 local CONTROL_WIDTH = 100;  
 local CONTENT_WIDTH = TEXT_LABEL_WIDTH + CONTROL_WIDTH;  
  
 local DIALOG_BUTTON_HEIGHT = renoise.ViewBuilder.DEFAULT_DIALOG_BUTTON_HEIGHT;  
  
 vb = renoise.ViewBuilder();  
  
  
 -- create_global_properties  
  
 local function create_global_properties()  
  
 local row_note = vb:row {  
 vb:text {  
 text = "Note",  
 width = TEXT_LABEL_WIDTH  
 },  
 vb:popup {  
 width = CONTROL_WIDTH,  
 value = int_note,  
 items = generate_note_matrix(1,120),  
 notifier = function(new_index)  
 int_note = new_index  
 int_frames = SAMPLE_FREQUENCY / note_to_frequency(new_index);  
 end,  
 width = CONTROL_WIDTH  
 }  
 }  
  
 local row_cycles = vb:row {  
 vb:text {   
 text = "Cycles" ,  
 width = TEXT_LABEL_WIDTH  
 },  
 vb:textfield {  
 width = CONTROL_WIDTH,  
 value = "1",  
 notifier = function(new_text)  
 real_cycles = tonumber(new_text);  
 end   
 }  
 }  
  
 local row_label = vb:row {  
 vb:text {  
 text = "Amplification",  
 width = TEXT_LABEL_WIDTH  
 },  
 vb:text {  
 id = "txtDbValue",  
 text = "0 dB",  
 width = TEXT_LABEL_WIDTH  
 }  
 }  
  
 local slider_volume = vb:slider {  
 width = CONTROL_WIDTH + TEXT_LABEL_WIDTH,  
 min = 0, -- -INF using log scale  
 max = 1, -- 0 Decibels using log scale  
 value = 1,  
 notifier = function(value)  
 real_amplification = math.pow(value,2);  
 if(real_amplification==0) then  
 vb.views.txtDbValue.text = "-INF dB";  
 else  
 vb.views.txtDbValue.text = string.format("%.2f dB",   
 convert_linear_to_db(real_amplification));   
 end  
 end  
 }  
  
 local array_string_buttons = {};  
 for i = 1, OPERATORS do  
 array_string_buttons[i] = tostring(i);  
 end  
  
 local switch_tabs = vb:switch {  
 id = "switchTabs",  
 width = CONTENT_WIDTH,  
 items = array_string_buttons,  
 notifier = function(int_index_new)  
 change_tab(int_index_new)  
 end  
 }  
  
 local column_global_properties = vb:column {  
 style = "group",  
 margin = MARGIN_DEFAULT,  
 row_note,  
 row_cycles,  
 row_label,  
 slider_volume,  
 switch_tabs  
 };  
  
 return column_global_properties  
 end  
  
  
 -- operator_gui  
  
 local function create_operator_gui()  
  
 local text_operator = vb:text {  
 id = "txtOperator",  
 text = "Operator",  
 font = "bold",  
 align = "center",  
 width = CONTENT_WIDTH,  
 }  
  
 local row_wave = vb:row {  
 vb:text {  
 text = "Wave",  
 width = TEXT_LABEL_WIDTH  
 },  
 vb:popup {  
 id = "cmbWave",  
 width = CONTROL_WIDTH,  
 value = int_wave_type_selected,  
 items = array_string_operators,  
 notifier = function(int_wave_type)  
 change_wave(int_operator_selected,int_wave_type);  
 end  
 }  
 }  
  
 local row_amplitude = vb:row {  
 vb:text {  
 text = "Amplitude",  
 width= TEXT_LABEL_WIDTH  
 },  
 vb:slider {  
 id = "sldAmplitude",  
 width = CONTROL_WIDTH,  
 min = 0,   
 max = 1,   
 value = 1,  
 notifier = function(real_value)  
 array_real_amplitudes[int_operator_selected] = real_value;  
 end  
 }  
 }  
  
 local row_width = vb:row {  
 id = "rowWidth",  
 vb:text {  
 text = "Width",  
 width = TEXT_LABEL_WIDTH  
 },  
 vb:slider {  
 id = "sldWidth",  
 width = CONTROL_WIDTH,  
 min = 0,   
 max = 0.5,   
 value = 0.5,  
 notifier = function(real_value)  
 array_variant_parameters[int_operator_selected] = real_value;  
 end  
 }  
 }  
  
 local row_invert = vb:row {  
 id = "rowInvert",  
 vb:text {  
 text = "Invert wave",  
 width= TEXT_LABEL_WIDTH  
 },  
 vb:checkbox {  
 id = "chkInvert",  
 value = false,  
 notifier = function(boolean_value)  
 array_boolean_inverts[int_operator_selected] = boolean_value;  
 end  
 }  
 }  
  
 local row_modulate = vb:row {  
 vb:text {  
 text = "Mod.Ampl. of",  
 width = TEXT_LABEL_WIDTH  
 },  
 vb:popup {  
 id = "cmbModulate",  
 width = CONTROL_WIDTH,  
 items = generate_modulator_matrix(),  
 value = 1,  
 notifier = function(new_index)  
 array_int_modulators[int_operator_selected] = new_index - 1;  
 end  
 }  
 }  
  
 local dropdown_instruments = vb:popup {  
 id = "cmbInstruments",  
 width = CONTENT_WIDTH,  
 items = generate_instrument_matrix(),  
 value = 1,  
 notifier = function(new_index)  
 vb.views.cmbSamples.items = generate_sample_matrix(new_index);  
 array_variant_parameters[int_operator_selected] = renoise.song().instruments[new_index].samples[1].sample_buffer;  
 end  
 }  
  
 local dropdown_samples = vb:popup {  
 id = "cmbSamples",  
 width = CONTENT_WIDTH,  
 items = generate_sample_matrix(1),  
 value = 1,  
 notifier = function(new_index)  
 array_variant_parameters[int_operator_selected] = renoise.song().instruments[vb.views.cmbInstruments.value].samples[new_index].sample_buffer;  
 end  
 }  
  
 local column_wavetable = vb:column {  
 id = "colWaveTable",  
 dropdown_instruments,  
 dropdown_samples  
 }  
  
 local row_frequency_multiplier = vb:row {  
 id = "rowMultiplier",  
 vb:text {  
 text = "Freq. Multiplier",  
 width = TEXT_LABEL_WIDTH  
 },  
 vb:textfield {  
 id = "txtMultiplier",  
 width = CONTROL_WIDTH,  
 value = "1",  
 notifier = function(new_text)  
 array_real_frequency_multipliers[int_operator_selected] = tonumber(new_text);  
 end  
 }  
 }  
  
 local column_gui = vb:column {  
 id = "columnGui",  
 style = "group",  
 margin = MARGIN_DEFAULT,  
 text_operator,  
 row_wave,  
 column_wavetable,  
 row_amplitude,  
 row_width,  
 row_invert,  
 row_frequency_multiplier,  
 row_modulate  
 }  
  
 return column_gui;  
 end  
  
  
 -- main layout   
  
 local button_generate = vb:button {  
 text = "Generate",  
 tooltip = "Hit this button to generate a custom wave with the specified features.",  
 width = "100%",  
 height = DIALOG_BUTTON_HEIGHT,  
 notifier = function()   
 generate()  
 end  
 }  
  
 local button_reset = vb:button {  
 text = "Reset",  
 width = "100%",  
 height = DIALOG_BUTTON_HEIGHT,  
 tooltip = "Reset all data",  
 notifier = function()  
 if renoise.app():show_prompt("Parameters Reset",   
 "Are you sure you want to reset all parameters data?",{"Yes","No"}) == "Yes" then  
 reset_gui();  
 end  
 end   
 }  
  
 local dialog_content = vb:column {  
 id = "colContainer",   
 margin = MARGIN_DEFAULT,  
 spacing = SPACING_DEFAULT,  
 create_global_properties(),  
 create_operator_gui(),  
 button_generate,  
 button_reset  
 }  
  
 change_wave(1,WAVE_SINE);  
 change_tab(1);  
  
 dialog = renoise.app():show_custom_dialog (  
 "Custom Wave Generator",  
 dialog_content  
 );  
  
end  
  

I’ve also reduced the amount of local views a bit. Although I said its great to mix the nested notation with the localy initialized ones, both in extreme are too much IMHO. For example:

THis :

  
 local row_cycles = vb:row {  
 vb:text { text = "Cycles" },  
 vb:textfield {  
 value = "1",  
 notifier = function(new_text)  
 real_cycles = tonumber(new_text);  
 end   
 }  
 }  
  

looks/reads easier to me than:

  
 local text_cycles = vb:text {  
 text = "Cycles"  
 }  
  
 local textfield_cycles = vb:textfield {  
 value = "1",  
 notifier = function(new_text)  
 real_cycles = tonumber(new_text);  
 end   
 }  
  
 local row_cycles = vb:row {  
 text_cycles,  
 textfield_cycles  
 }  
  

aka, uising one level of nesting to shorten things a bit. But well, I guess, everyone will see this different…

I had a similar problem with the arpeggiator…
I solved it by extending the width of one of the objects on the most right side of the frame and i messed around with the margins (or actually removed them)…
You can also just simply add an empty text label with a defined width that extends the group.

The width 100% is a real confusing thing, it does not seem to work consequently, so i abandoned using that option which is a pity because if it would work like expected, it would make it a perfect width aligner.

approved and adopted in version 0.32 :)

You’ve got two "create_operator_gui"s in there now…

blargh, fixed in 0.33

I have tried with two different Ubuntu 9.04 and can confirm that under Firefox 3.5.4 the LUA web viewer right mouse button context menu does not work anymore (no error reported in the error console). I have used Seamonkey 1.1.7 to change the file

There’s one small prob with the wave operators: If you replace the current sample with a synthesized one, or when something in the instrument or sample list changes while the dialog is open, the instruments & samples list in your dialog will be wrong and then later on processing will fail.
Right now there are no notifiers for “something in the instrument list has changed outside of your scripts”, so I think you should simply make the dialog modal (create_custom_prompt). Then you only have to take care of updating the list when you do change the instrument list by your own.

And if you name your menu entry:
“SampleEditor:Create Synthesized Sample…”
it will show up below Renoises internal “Create Sample…” entry.


Also I don’t want to be a pest, but shouldn’t it be:

  
function show_dialog()  
 local function create_global_properties_gui()  
 end  
end  
  
-- OR  
  
function create_global_properties_gui()  
end  
  
function show_dialog()  
end  
  
-- instead of:  
  
function show_dialog()  
local function create_global_properties_gui()  
end  
end  
  
-- which looks like its defined in the global scopes while being a local function?  
  

One small bug. I unfortunately can not easily replicate it, but:

in function change_tab(int_operator_number)
vb.views.sldWidth.value = real_width;
“real_width” sometimes is nil which will fire an error.

The slider view can not handle setting nil values or other non number values.