Forum
Interface Optimization Ideas for Renoise – Towards a More Intuitive and Efficient Workflow
Hey Folks!
I’m creating this thread as a living collection of ideas and suggestions aimed at improving the Renoise interface. Over years of use, I’ve developed certain workflows, as well as run into recurring friction points that often require workarounds or extra attention.
The focus here isn’t on radical redesigns, but rather on targeted enhancements that could make the interface clearer, faster, and more comfortable — without compromising the tracker philosophy.
I’ll be posting thoughts and mockups as they come. Anyone is welcome to join in — feel free to share your own observations or expand on the ideas below.
my first 5 cent
- Optimized Effect/Processor Selection
The idea is to streamline the effect/processor selection interface: more compact, visually clear, and faster to use.
- Combined Automation + Oscilloscope View
This concept integrates the automation editor with an oscilloscope — something I have wished for in Renoise for a long time.
The goal is to visually align automation changes with the actual waveform , in real-time.
This would be especially useful for working with short percussive elements, filter modulations, or any parameter-sensitive material — allowing for more accurate keyframe placement and easier fine-tuning of behavior.
It bridges the gap between what we “draw” and what we “hear”, making sound shaping more precise and intuitive.
any things!? ))
1 post - 1 participant
Serious lag with Serum 2 when playing notes while gui is open
When I open Serum 2 (synth or fx instance) and play notes with keyboard or midi device, Renoise starts lagging. Fps drops to single digits and audio playback has serious lag and some of the faster notes gets ignored.
If I close Serum 2 gui there are no problems.
If I set buffer size to 1024 the lag from Serum 2 disappears. Also I can use Serum 2 on Reaper just fine.
Using Windows 10.
These settings have no effect:
- Scaling for the display 100%/125%
- Disabling 2nd monitor and applying its scaling to 100%
- Setting “Let windows try to fix apps so they’re not blurry” on/off
- Audio ASIO/directsound
- Any gaming overlays are disabled
- Windows 10 and Renoise DAW have latest updates
- GPU and ASIO drivers have latest updates
CPU usage stays under 6% in Renoise monitor and from task manager cpu utilisation stays on 3-4% no matter if the gui is open or not.
1 post - 1 participant
Can't right drag pattern length if first track is collapsed
Description:
I can right drag the pattern length if the first track is not collapsed. If the first track is collapsed, I can’t right drag the pattern length.
Steps to reproduce:
- Launch Renoise
- Collapse first track
- Right drag pattern length
Expected results:
Pattern length increases by 10.
Actual results:
Pattern length doesn’t increase.
Video:
1 post - 1 participant
Need your feedback on one of my first songs
KeyBindings.xml gets corrupted
Sometimes when the system reboots or crashes while Renoise is running, KeyBindings.xml gets corrupted (not fully written to disk).
When I restart Renoise I get the message “Failed to parse KeyBindings.xml”.
So basically all my custom keybindings are lost.
To prevent this from happening again I set the immutable flag (chattr +i KeyBindings.xml)
But now Renoise complains on every exit that it cannot wtrite to KeyBindings.xml.
Is it possible to handle this situation more gracefully?
Thank you!
PS:
I just realised that there is a related issue from ~13 years ago:
2 posts - 2 participants
Snow White Redo Remix
1 post - 1 participant
Incorrect Reset Value Sent To LFO In Points Mode
Incorrect Reset Value Sent To LFO In Points Mode
When in Points mode and a value is sent to the Reset (either through effect commands, meta device or the API), the full range of the envelope is not taken into account. In this example, each of the points should be hit with the first five values, but this can’t happen since the end of the envelope is at CC instead of FF. The correct behaviour occurs for Lines and Curves modes.
1 post - 1 participant
How can peak detection + normalization be made faster with API?
hi, i’m wondering what the process of peak detection + normalizing could be optimized with?
i have the process slicer somehow set up, but i just can’t figure out how to optimize this better.
this function:
reads the selection of a slice, or if no selection and on slice, then that, or selection of a sample, or if no selection and on sample, then that.
it will normalize a selection of a slice, a complete slice, or a selection of a sample, or the whole sample.
but it just seems slow!
Normalizing 73119 frames (3.3 seconds at 22050Hz) Peak amplitude: 0.766388 (-2.3 dB below full scale) Will increase volume by 2.3 dB Normalization complete: Total time: 0.39 seconds (0.2M frames/sec) Reading: 16.7%, Processing: 83.3% Sample Selected is Sample Slot 1 Sample Frames Length is 1-23398260 Selection in Sample: 12451218-23398260 Normalizing: selection in sample Normalizing 10947043 frames (496.5 seconds at 22050Hz) Peak amplitude: 0.053680 (-25.4 dB below full scale) Will increase volume by 25.4 dB Normalization complete: Total time: 26.76 seconds (0.4M frames/sec) Reading: 36.1%, Processing: 63.9%so, how do i optimize this? what do i do?
@martblek - i saw you locked your thread about a faster process slicer, but i’m not 100% where or how it was posted in full.
what’s the solution? @joule ? @Raul do any of you know what a process slicer is that could handle large files and peak detect + normalize?
function NormalizeSelectedSliceInSample() local song = renoise.song() local instrument = song.selected_instrument local current_slice = song.selected_sample_index local first_sample = instrument.samples[1] local current_sample = song.selected_sample -- Check if we have valid data if not current_sample or not current_sample.sample_buffer.has_sample_data then renoise.app():show_status("No sample available") return end print(string.format("\nSample Selected is Sample Slot %d", song.selected_sample_index)) print(string.format("Sample Frames Length is 1-%d", current_sample.sample_buffer.number_of_frames)) -- Case 1: No slice markers - work on current sample if #first_sample.slice_markers == 0 then local buffer = current_sample.sample_buffer local slice_start, slice_end -- Check for selection in current sample if buffer.selection_range[1] and buffer.selection_range[2] then slice_start = buffer.selection_range[1] slice_end = buffer.selection_range[2] print(string.format("Selection in Sample: %d-%d", slice_start, slice_end)) print("Normalizing: selection in sample") else slice_start = 1 slice_end = buffer.number_of_frames print("Normalizing: entire sample") end -- Create ProcessSlicer instance and dialog local slicer = nil local dialog = nil local vb = nil -- Define the process function local function process_func() local time_start = os.clock() local time_reading = 0 local time_processing = 0 local total_frames = slice_end - slice_start + 1 print(string.format("\nNormalizing %d frames (%.1f seconds at %dHz)", total_frames, total_frames / buffer.sample_rate, buffer.sample_rate)) -- First pass: Find peak local peak = 0 local processed_frames = 0 local CHUNK_SIZE = 524288 -- 512KB worth of frames -- Pre-allocate tables for better performance local channel_peaks = {} for channel = 1, buffer.number_of_channels do channel_peaks[channel] = 0 end buffer:prepare_sample_data_changes() -- Process in blocks for frame = slice_start, slice_end, CHUNK_SIZE do local block_end = math.min(frame + CHUNK_SIZE - 1, slice_end) local block_size = block_end - frame + 1 -- Read and process each channel for channel = 1, buffer.number_of_channels do local read_start = os.clock() local channel_peak = 0 for i = 0, block_size - 1 do local sample = math.abs(buffer:sample_data(channel, frame + i)) if sample > channel_peak then channel_peak = sample end end time_reading = time_reading + (os.clock() - read_start) if channel_peak > channel_peaks[channel] then channel_peaks[channel] = channel_peak end end -- Update progress and yield processed_frames = processed_frames + block_size local progress = processed_frames / total_frames if dialog and dialog.visible then vb.views.progress_text.text = string.format("Finding peak... %.1f%%", progress * 100) end if slicer:was_cancelled() then buffer:finalize_sample_data_changes() return end coroutine.yield() end -- Find overall peak for _, channel_peak in ipairs(channel_peaks) do if channel_peak > peak then peak = channel_peak end end -- Check if sample is silent if peak == 0 then print("Sample is silent, no normalization needed") buffer:finalize_sample_data_changes() if dialog and dialog.visible then dialog:close() end return end -- Calculate and display normalization info local scale = 1.0 / peak local db_increase = 20 * math.log10(scale) print(string.format("\nPeak amplitude: %.6f (%.1f dB below full scale)", peak, -db_increase)) print(string.format("Will increase volume by %.1f dB", db_increase)) -- Reset progress for second pass processed_frames = 0 -- Second pass: Apply normalization for frame = slice_start, slice_end, CHUNK_SIZE do local block_end = math.min(frame + CHUNK_SIZE - 1, slice_end) local block_size = block_end - frame + 1 -- Process each channel for channel = 1, buffer.number_of_channels do local process_start = os.clock() for i = 0, block_size - 1 do local current_frame = frame + i buffer:set_sample_data(channel, current_frame, buffer:sample_data(channel, current_frame) * scale) end time_processing = time_processing + (os.clock() - process_start) end -- Update progress and yield processed_frames = processed_frames + block_size local progress = processed_frames / total_frames if dialog and dialog.visible then vb.views.progress_text.text = string.format("Normalizing... %.1f%%", progress * 100) end if slicer:was_cancelled() then buffer:finalize_sample_data_changes() return end coroutine.yield() end -- Finalize changes buffer:finalize_sample_data_changes() -- Calculate and display performance stats local total_time = os.clock() - time_start local frames_per_second = total_frames / total_time print(string.format("\nNormalization complete:")) print(string.format("Total time: %.2f seconds (%.1fM frames/sec)", total_time, frames_per_second / 1000000)) print(string.format("Reading: %.1f%%, Processing: %.1f%%", (time_reading/total_time) * 100, ((total_time - time_reading)/total_time) * 100)) -- Close dialog when done if dialog and dialog.visible then dialog:close() end if buffer.selection_range[1] and buffer.selection_range[2] then renoise.app():show_status("Normalized selection in " .. current_sample.name) else renoise.app():show_status("Normalized " .. current_sample.name) end end -- Create and start the ProcessSlicer slicer = ProcessSlicer(process_func) dialog, vb = slicer:create_dialog("Normalizing Sample") slicer:start() return end -- Case 2: Has slice markers local buffer = first_sample.sample_buffer local slice_start, slice_end local slice_markers = first_sample.slice_markers -- If we're on the first sample if current_slice == 1 then -- Check for selection in first sample if buffer.selection_range[1] and buffer.selection_range[2] then slice_start = buffer.selection_range[1] slice_end = buffer.selection_range[2] print(string.format("Selection in First Sample: %d-%d", slice_start, slice_end)) print("Normalizing: selection in first sample") else slice_start = 1 slice_end = buffer.number_of_frames print("Normalizing: entire first sample") end else -- Get slice boundaries slice_start = current_slice > 1 and slice_markers[current_slice - 1] or 1 local slice_end_marker = slice_markers[current_slice] or buffer.number_of_frames local slice_length = slice_end_marker - slice_start + 1 print(string.format("Selection is within Slice %d", current_slice)) print(string.format("Slice %d length is %d-%d (length: %d), within 1-%d of sample frames length", current_slice, slice_start, slice_end_marker, slice_length, buffer.number_of_frames)) -- When in a slice, check the current_sample's selection range (slice view) local current_buffer = current_sample.sample_buffer -- Debug selection values print(string.format("Current sample selection range: start=%s, end=%s", tostring(current_buffer.selection_range[1]), tostring(current_buffer.selection_range[2]))) -- Check if there's a selection in the current slice view if current_buffer.selection_range[1] and current_buffer.selection_range[2] then local rel_sel_start = current_buffer.selection_range[1] local rel_sel_end = current_buffer.selection_range[2] -- Convert slice-relative selection to absolute position in sample local abs_sel_start = slice_start + rel_sel_start - 1 local abs_sel_end = slice_start + rel_sel_end - 1 print(string.format("Selection %d-%d in slice view converts to %d-%d in sample", rel_sel_start, rel_sel_end, abs_sel_start, abs_sel_end)) -- Use the converted absolute positions slice_start = abs_sel_start slice_end = abs_sel_end print("Normalizing: selection in slice") else -- No selection in slice view - normalize whole slice slice_end = slice_end_marker print("Normalizing: entire slice (no selection in slice view)") end end -- Ensure we don't exceed buffer bounds slice_start = math.max(1, math.min(slice_start, buffer.number_of_frames)) slice_end = math.max(slice_start, math.min(slice_end, buffer.number_of_frames)) print(string.format("Final normalize range: %d-%d\n", slice_start, slice_end)) -- Create ProcessSlicer instance and dialog for sliced processing local slicer = nil local dialog = nil local vb = nil -- Define the process function for sliced processing local function process_func() local time_start = os.clock() local time_reading = 0 local time_processing = 0 local total_frames = slice_end - slice_start + 1 print(string.format("\nNormalizing %d frames (%.1f seconds at %dHz)", total_frames, total_frames / buffer.sample_rate, buffer.sample_rate)) -- First pass: Find peak local peak = 0 local processed_frames = 0 local CHUNK_SIZE = 524288 -- 512KB worth of frames -- Pre-allocate tables for better performance local channel_peaks = {} for channel = 1, buffer.number_of_channels do channel_peaks[channel] = 0 end buffer:prepare_sample_data_changes() -- Process in blocks for frame = slice_start, slice_end, CHUNK_SIZE do local block_end = math.min(frame + CHUNK_SIZE - 1, slice_end) local block_size = block_end - frame + 1 -- Read and process each channel for channel = 1, buffer.number_of_channels do local read_start = os.clock() local channel_peak = 0 for i = 0, block_size - 1 do local sample = math.abs(buffer:sample_data(channel, frame + i)) if sample > channel_peak then channel_peak = sample end end time_reading = time_reading + (os.clock() - read_start) if channel_peak > channel_peaks[channel] then channel_peaks[channel] = channel_peak end end -- Update progress and yield processed_frames = processed_frames + block_size local progress = processed_frames / total_frames if dialog and dialog.visible then vb.views.progress_text.text = string.format("Finding peak... %.1f%%", progress * 100) end if slicer:was_cancelled() then buffer:finalize_sample_data_changes() return end coroutine.yield() end -- Find overall peak for _, channel_peak in ipairs(channel_peaks) do if channel_peak > peak then peak = channel_peak end end -- Check if sample is silent if peak == 0 then print("Sample is silent, no normalization needed") buffer:finalize_sample_data_changes() if dialog and dialog.visible then dialog:close() end return end -- Calculate and display normalization info local scale = 1.0 / peak local db_increase = 20 * math.log10(scale) print(string.format("\nPeak amplitude: %.6f (%.1f dB below full scale)", peak, -db_increase)) print(string.format("Will increase volume by %.1f dB", db_increase)) -- Reset progress for second pass processed_frames = 0 -- Second pass: Apply normalization for frame = slice_start, slice_end, CHUNK_SIZE do local block_end = math.min(frame + CHUNK_SIZE - 1, slice_end) local block_size = block_end - frame + 1 -- Process each channel for channel = 1, buffer.number_of_channels do local process_start = os.clock() for i = 0, block_size - 1 do local current_frame = frame + i buffer:set_sample_data(channel, current_frame, buffer:sample_data(channel, current_frame) * scale) end time_processing = time_processing + (os.clock() - process_start) end -- Update progress and yield processed_frames = processed_frames + block_size local progress = processed_frames / total_frames if dialog and dialog.visible then vb.views.progress_text.text = string.format("Normalizing... %.1f%%", progress * 100) end if slicer:was_cancelled() then buffer:finalize_sample_data_changes() return end coroutine.yield() end -- Finalize changes buffer:finalize_sample_data_changes() -- Calculate and display performance stats local total_time = os.clock() - time_start local frames_per_second = total_frames / total_time print(string.format("\nNormalization complete:")) print(string.format("Total time: %.2f seconds (%.1fM frames/sec)", total_time, frames_per_second / 1000000)) print(string.format("Reading: %.1f%%, Processing: %.1f%%", (time_reading/total_time) * 100, ((total_time - time_reading)/total_time) * 100)) -- Close dialog when done if dialog and dialog.visible then dialog:close() end -- Show appropriate status message if current_slice == 1 then if buffer.selection_range[1] and buffer.selection_range[2] then renoise.app():show_status("Normalized selection in " .. current_sample.name) else renoise.app():show_status("Normalized entire sample") end else if buffer.selection_range[1] and buffer.selection_range[2] then renoise.app():show_status(string.format("Normalized selection in slice %d", current_slice)) else renoise.app():show_status(string.format("Normalized slice %d", current_slice)) end -- Refresh view for slices song.selected_sample_index = song.selected_sample_index - 1 song.selected_sample_index = song.selected_sample_index + 1 end end -- Create and start the ProcessSlicer for sliced processing slicer = ProcessSlicer(process_func) dialog, vb = slicer:create_dialog("Normalizing Sample") slicer:start() endand
--[[============================================================================ process_slicer.lua ============================================================================]]-- --[[ ProcessSlicer for Paketti - allows slicing up long-running operations into smaller chunks to maintain UI responsiveness and show progress. Main benefits: - Shows current progress of operations - Allows users to abort operations - Prevents Renoise from thinking the script is frozen - Maintains UI responsiveness during heavy operations ]] class "ProcessSlicer" function ProcessSlicer:__init(process_func, ...) assert(type(process_func) == "function", "Expected a function as first argument") self.__process_func = process_func self.__process_func_args = {...} self.__process_thread = nil self.__cancelled = false end -------------------------------------------------------------------------------- -- Returns true when the current process is running function ProcessSlicer:running() return (self.__process_thread ~= nil) end -------------------------------------------------------------------------------- -- Start a process function ProcessSlicer:start() assert(not self:running(), "Process already running") self.__process_thread = coroutine.create(self.__process_func) renoise.tool().app_idle_observable:add_notifier( ProcessSlicer.__on_idle, self) end -------------------------------------------------------------------------------- -- Stop a running process function ProcessSlicer:stop() assert(self:running(), "Process not running") renoise.tool().app_idle_observable:remove_notifier( ProcessSlicer.__on_idle, self) self.__process_thread = nil end -------------------------------------------------------------------------------- -- Cancel the process function ProcessSlicer:cancel() self.__cancelled = true end -------------------------------------------------------------------------------- -- Check if process was cancelled function ProcessSlicer:was_cancelled() return self.__cancelled end -------------------------------------------------------------------------------- -- Internal function called during idle to continue processing function ProcessSlicer:__on_idle() assert(self.__process_thread ~= nil, "ProcessSlicer internal error: " .. "Expected no idle call with no thread running") -- Continue or start the process while it's still active if (coroutine.status(self.__process_thread) == 'suspended') then local succeeded, error_message = coroutine.resume( self.__process_thread, unpack(self.__process_func_args)) if (not succeeded) then -- Stop the process on errors self:stop() -- Forward the error error(error_message) end -- Stop when the process function completed elseif (coroutine.status(self.__process_thread) == 'dead') then self:stop() end end -- Helper function to create a progress dialog function ProcessSlicer:create_dialog(title) local vb = renoise.ViewBuilder() local dialog = nil local DEFAULT_MARGIN = renoise.ViewBuilder.DEFAULT_CONTROL_MARGIN local DEFAULT_SPACING = renoise.ViewBuilder.DEFAULT_CONTROL_SPACING local dialog_content = vb:column { margin = DEFAULT_MARGIN, spacing = DEFAULT_SPACING, vb:text { id = "progress_text", text = "Processing..." }, vb:button { id = "cancel_button", text = "Cancel", width = 80, notifier = function() self:cancel() if dialog and dialog.visible then dialog:close() end end } } dialog = renoise.app():show_custom_dialog( title or "Processing...", dialog_content) return dialog, vb endwhat’s the right way?
@unless maybe knows something of this?
1 post - 1 participant
Device Chain vs Effect Chain
Today I realized that Device Chains and Effect Chains are both saved as xrnt files. But they are not necessarily saved in the same places. The drop down in the top right corner of the Sampler Effects section directs you to the Effect Chains folder in the User Library. But saving a Device Chain by right-clicking and selecting Device Chain / Save As… takes you to your most recent active folder, or something, but at any rate NOT necessarily the Effect Chains folder.
So Device Chain xrnt files could be saved in a separate directory than Effect Chain xrnt files.
Is it important to store Device Chains separately from Effect Chains? Is it important to store all xrnts together? Does it depend on how they are being used?
Thanks!
1 post - 1 participant
How to copy all the innards of a doofer?
hi, i’m wondering how to copy a doofer via scripting so that it is 1:1 all contents, all settings, all “within-doofer-devices”?
1 post - 1 participant
Trying to re-download Renoise and everything is missing
Hey all
Had a major computer fail and had to wipe everything. When I went to Backstage there were no options to download anything. I have Renoise paid through 4.2 but cant seem to see any builds, only the option to upgrad.
That didn’t seem right. would love any help.
4 posts - 3 participants
Mutable instrument plaits , plugdata
I’ve made a polyphonic version of mutable instruments plaits in plugdata , using the ported code ( ported code not done by me )
It’s worth having plugdata for this alone
Make sure the patch and subpatch loaded in the clone instance are ins the same folder .
The controls depend on the chosen algoritm , just like the hardware
Cpu use is verry (verry ) low compared to poly versions in vcv rack while sounding exactly the same .
The patch is mutable_2
and the patch inside the clone object is emilie_2_clo
Have fun , it sounds amazing
1 post - 1 participant
Horizontal Pattern Matrix
I’ve never seen this tip here on the forum, and it’s not something available in the default options. You can use the Pattern Matrix in horizontal mode by editing the Config.xml and setting the <PatternMatrixOrientationFlipped> option.
1 post - 1 participant
Native Renoise UI for plugin window bars
Not sure how tricky this would be, but it’d be nice if the plugin’s bottom bar (and maybe even the top one) used Renoise’s native UI, kind of like how FL Studio does it. Right now it’s using the old win32 UI, which doesn’t support dark mode.
1 post - 1 participant
(VIRAL*Sex) Lisa mariana XXX porn Video Orginal Full Hot Sex Videos
Lisa mariana XXX porn Video Orginal Full Hot Sex Videos
.
.
.
.
.
.
.
.
.
.
1 post - 1 participant
I've made a tracker in max msp
Max is fun and it 's get even more fun when using the jit cellblock for a tracker feel approach
1 post - 1 participant
A special VST tip
Hi! I just wanna recommend a VST that still is unknown to most people:
Parawave Rapid is a beast of a synthesizer. I use it several years now. It’s a very versatile synth with a big bunch of awesome features.
In my opinion it’s better than Pigments, Massive X, Vital, Avenger 2, Serum, etc. I even would say better than Serum 2 because you can do things in Rapid that are not possible in Serum/Serum 2.
It’s even 8x multitimbral and very CPU friendly.
This VST is still quite unknown because the manufacturers don’t have good marketing strategies and do not advertise it much. But it’s one of the best VST synths i ever used. It’s even my go to synth for a long time now.
https://parawave-audio.com/index.php?route=product/rapid
RAPID Synthesizer - Colorful Sounds
RAPID - Factory Sounds (Demo Showcase)
1 post - 1 participant
Improvement: stop all notes and fx
Hi,
is there a possibility of improving the following feature
track commands > mixer > stop all notes and fx
~in following scenario:
having reverb tail cutoffs cause clicks and pops (messing with the plugin preference didn’t improve too much). Only mpreverb does not introduce clicks and pops when used.
Can we eliminate click/pop here (assumption: by using minimal fade in/out) when using generic reverb plugins? (sonnox reverb, relabdevelopment lx480, TC vss3, toneboosters reverb, you name it)
no daw has this feature this easy available. It’s awesome and i just recently discovered it
Thanks
2 posts - 2 participants
Feature request: sync theme with os theme
Hi,
can we have automatic sync for light/dark theme which will follow OS settings? (eg, half of day it’s light theme, then it goes dark)?
user should specify which theme should represent default light, and another as default dark?
using mac, not sure if this makes sense for win/linux world
2 posts - 2 participants
Possible to Modulate/Automate concavity/convexity of my custom LFO shape?
When editing a custom LFO shape, say a ramp down, I can drag a red square on the waveform to change if it’s straight, convex or concave. Is it possible to modulate that little square and allow me to change the convexity/concavity of the shape throughout my song? If not, how can I try and approach this? My main goal is to open a filter and close it back down, and I’m loving the sound of doing it through changing my LFO shape’s, well, shape.
5 posts - 2 participants