# Extending Renoise, with a Clip Launcher We will learn how to make Renoise fit with your workflow by extending it with Renoise Tools. In addition to learning how to make a Renoise tool, we will build something that Renoise can't do normally, which is act as a clip launcher. > [!info] > Renoise already has a Pattern Matrix and allows for one pattern to be queued up after another. So why would a clip launcher be useful? > > A producer sequences songs in Renoise by creating patterns and arranging them, one after another, in time. Patterns consist of slots, one per instrument track, and slots can be populated with blocks, which contain note data. > > Blocks can be muted and unmuted in the Pattern Matrix. However, patterns must be played in whole—unlike Ableton Live, a producer cannot play blocks from different patterns simultaneously. > > Therefore, being able to queue up various blocks to play, and playing them, would be a useful addition to Renoise, allowing for easier improvisational performance. ## Setup Make sure you have Renoise installed and running. Download the XRNX Starter Pack from their [Renoise Lua Scripting repository](https://github.com/renoise/xrnx), unzip it, and keep track of the folder for later reference. Follow [these instructions](https://github.com/renoise/xrnx#how-to-enable-the-scripting-developer-tools-in-renoise) to enable the Scripting Developer Tools in Renoise. Open Renoise, and under the "Tools" menu in the menu bar, there should be a "Scripting Terminal and Editor" menu item. Click it to open up the Scripting Editor. ## Getting Started First, we will create the necessary files for the tool. Open the "XrnxStarterPack330" folder (the name may be slightly different, depending on your version of Renoise). Open the "Tools" folder. Duplicate the `com.renoise.ExampleTool.xrnx` folder. Rename the duplicate to something like `phasor.space.ClipLauncher.xrnx`. Drag your `phasor.space.ClipLauncher.xrnx` folder on top of Visual Studio Code to open it as a project for editing. Open the `manifest.xml` file, and change `<Id>com.renoise.ExampleTool</Id>` to `<Id>phasor.space.ClipLauncher</Id>`. Copy the `phasor.space.ClipLauncher.xrnx` folder on top of Renoise while it is running. It will add the new tool to your list of tools, in the sidebar of the Scripting Editor. Double-click on `phasor.space.ClipLauncher` in the sidebar. Double-click on `manifest.xml` and edit the fields. "Version", "Author", "Name", "Category", "Description" and "Homepage" should be changed at some point. In addition, the `icon.png` in the project should be replaced with a similarly-sized PNG at some point, as well. ## Trying Things Out In the Scripting Editor, there should be a `Resource\ Scripts/TestPad.lua` file, and if not, create one. First, there's some example code which should already be in `TestPad.lua`, but if not, add it: ```lua -- dummy: recursively prints all available functions and classes rprint(_G) ``` When you click "Execute", you should see a ton of output in the Scripting Terminal. `rprint` is a useful function to deeply examine an object, and `_G` is a global object, and is useful to see what you have access to. You can use `TestPad.lua` together with the Scripting Terminal to try stuff out before adding it to your tool. > [!info] > > ### Accessing the Log > > On OS X, you can use the `Console.app` in your `/Applications` folder to open `~/Library/Logs/Renoise.log`. > > On Windows, there is an "Event Viewer" program which can be opened from the Start menu. The Renoise logs are located at `C:\Users\phasorspace\AppData\Roaming\Renoise\V3.3.2\Log.txt`, where `phasorspace` should be replaced with the name of your user directory, and `V3.3.2` should be replaced with the version of Renoise you are using. ## What Do We Need to Build? A basic clip launcher needs to do two things: * Mark blocks to launch * Queue marked blocks to play We will start by building a block marking system. ## A Block Marking System Add a note anywhere in the block located at $1, 1$. The block in the pattern matrix should change color. This indicates that there is note data on that block. What we are going to do is use Renoise's ability to change colors of blocks to improvise a "marking" system. Let's give it a shot. Replace the contents of `phasor.space.ClipLauncher/main.lua` to: ```lua -- Are two colors (representented by 3-tuples) equal? function color_eq(a, b) if a == nil and b == nil then return true end if a == nil then return false end if b == nil then return false end if #a ~= 3 then return false end if #b ~= 3 then return false end local v = (a[1] == b[1] and a[2] == b[2] and a[3] == b[3]) return v end ``` Click "Execute" and then click over to the Scripting Terminal. Try entering the following: ```lua local red = { 0xff, 0x00, 0x00 } local green = { 0x00, 0xff, 0x00 } print( color_eq(red, green) ) -- false print( color_eq(green, green) ) -- true ``` Next, to get a list of marked blocks. ```lua -- Color used to mark blocks local MARK_COLOR = { 0xff, 0x00, 0x00 } -- Get all blocks marked with the above color function _marked_blocks() local slots = {} local song = renoise.song() for j=1,#song.patterns do for i=1,#song.tracks do local pattern_index = song.sequencer:pattern(j) local pattern = song.patterns[pattern_index] local slot = pattern.tracks[i] if (color_eq(MARK_COLOR, slot.color)) then table.insert(slots, {i, j}) end end end return slots end ``` Click "Execute". To test it out, switch over to the Pattern View, and expand the Pattern Matrix. Make a new block, right-click it, choose "Set Slot Color...", and use the RGB inputs to specify `#FF0000`. Click away from the color picker. In the Scripting Terminal, ```lua rprint( _marked_blocks() ) -- [1] => table -- [1] => 1 -- [2] => 1 ``` In `main.lua`: ```lua -- Mark a block with our chosen color function _mark_block(i, j) local song = renoise.song() local pattern_index = song.sequencer:pattern(j) local pattern = song.patterns[pattern_index] local slot = pattern.tracks[i] slot.color = MARK_COLOR end -- Unmark block function _unmark_block(i, j) local song = renoise.song() local pattern_index = song.sequencer:pattern(j) local pattern = song.patterns[pattern_index] local slot = pattern.tracks[i] slot.color = nil end -- Unmark all blocks function unmark_all_blocks() local song = renoise.song() for j=1,#song.patterns do for i=1,#song.tracks do _unmark_block(i, j) end end end ``` Click "Execute". This will form the basis for the next parts of our functionality. Try running the following in the Scripting Terminal: ```lua -- Make sure there's a block in leftmost slot of first pattern. -- Run in Scripting Terminal: _unmark_block(1, 1) -- Visit Pattern Matrix and make sure block is unmarked. -- Run in Scripting Terminal: _mark_block(1, 1) -- Visit Pattern Matrix and make sure block is marked again. -- Run in Scripting Terminal: unmark_all_blocks() -- All blocks should be unmarked ``` Now we want to be able to mark and unmark all selected blocks. Add the following to `main.lua`: ```lua -- Mark all selected blocks function mark_blocks() local song = renoise.song() for j=1,#song.patterns do for i=1,#song.tracks do local is_selected = song.sequencer:track_sequence_slot_is_selected(i, j) if is_selected then _mark_block(i, j) end end end end -- Unmark all selected blocks function unmark_blocks() local song = renoise.song() for j=1,#song.patterns do for i=1,#song.tracks do local is_selected = song.sequencer:track_sequence_slot_is_selected(i, j) if is_selected then _unmark_block(i, j) end end end end ``` Click "Execute". It's very straightforward to manually test: ```lua -- Visit Pattern Matrix and select one or more blocks. -- Run in Scripting Terminal: mark_blocks() -- Visit Pattern Matrix and verify selected blocks are marked. -- Select a different set of blocks. -- Run in Scripting Terminal: unmark_blocks() -- The selected blocks should be unmarked. -- Run in Scripting Terminal: unmark_all_blocks() -- All blocks should be unmarked. ``` ## The "Scratch Loop" We need to come up with a notion of a "scratch loop". This is a pattern with loop enabled, where blocks are sent to, for playing, using our script. Let's create the scratch loop. From the Pattern Matrix, create a new pattern. From the Pattern Sequencer, set the pattern to loop. To find the scratch loop from code, We need to be able to answer, what pattern has loop enabled? ```lua -- Get the index of our "scratch" loop pattern "scene" function _get_loop_pattern_index() local song = renoise.song() return renoise.song().transport.loop_start.sequence end ``` Click "Execute". Try manipulating the UI and evaluating the above function. ## Copying and Clearing Blocks Almost there. We need to be able to copy a block from one slot to another. ```lua -- Copy a block from one slot to another function _copy_block(source_track_index, source_pattern_index, dest_track_index, dest_pattern_index) rprint({ '_copy_block', source_track_index, source_pattern_index, dest_track_index, dest_pattern_index }) local song = renoise.song() local source = song.patterns[song.sequencer:pattern(source_pattern_index)] local dest = song.patterns[song.sequencer:pattern(dest_pattern_index)] dest.tracks[dest_track_index]:copy_from(source.tracks[source_track_index]) end -- Clear the slot of a block function _clear_block(i, j) local song = renoise.song() local pattern_index = song.sequencer:pattern(j) local pattern = song.patterns[pattern_index] local slot = pattern.tracks[i] slot:clear() end ``` Click "Execute". To test: ```lua -- Copy the block at slot {1, 1} to slot {1, 2} _copy_block(1, 1, 2, 1) -- Visit the Pattern Matrix to verify -- Clear block at slot {1, 2} _copy_block(2, 1) -- Visit the Pattern Matrix to verify ``` ## Launching Blocks The absolute final piece of functionality we need, is something which will do the following: * Identify the "scratch loop" pattern * Clear the scratch loop pattern * Gather one marked block per track * Copy the marked blocks onto the scratch loop pattern By doing the above, we have launched our marked clips. ```lua -- Copy all marked blocks to corresponding slot in "scratch loop" pattern function launch_blocks() local song = renoise.song() local loop_block_index = _get_loop_block_index() -- Clear existing for i=1,#song.tracks do _clear_block(i, loop_block_index) end -- Populate from marks local marks = _marked_blocks() for i, mark in ipairs(marks) do _unmark_block(mark[1], mark[2]) _copy_block(mark[1], mark[2], mark[1], loop_block_index) end end ``` Click "Execute". In the Scripting Terminal: ```lua -- Run in Scripting Terminal: _copy_block(1, 1, 2, 1) _copy_block(1, 1, 3, 1) _copy_block(1, 1, 3, 2) _mark_block(1, 1) _mark_block(2, 1) _mark_block(3, 2) launch_blocks() -- The blocks originally at {1, 1}, {2, 1}, and {3, 2} should be playing ``` ## Adding Menu Items ```lua renoise.tool():add_menu_entry { name = "Main Menu:Tools:Copy Pattern:Mark Blocks", invoke = function() mark_blocks() end } renoise.tool():add_menu_entry { name = "Main Menu:Tools:Copy Pattern:Unmark Blocks", invoke = function() unmark_blocks() end } renoise.tool():add_menu_entry { name = "Main Menu:Tools:Copy Pattern:Unmark All Blocks", invoke = function() unmark_all_blocks() end } renoise.tool():add_menu_entry { name = "Main Menu:Tools:Copy Pattern:Launch Blocks", invoke = function() launch_blocks() end } ``` Click "Execute". You should see the menu items added to the Tools menu. ## Adding Keybindings ```lua renoise.tool():add_keybinding { name = "Global:Tools:Copy Pattern Mark Blocks", invoke = function() mark_blocks() end } renoise.tool():add_keybinding { name = "Global:Tools:Copy Pattern Unmark Blocks", invoke = function() unmark_blocks() end } renoise.tool():add_keybinding { name = "Global:Tools:Copy Pattern Unmark All Blocks", invoke = function() unmark_all_blocks() end } renoise.tool():add_keybinding { name = "Global:Tools:Copy Pattern Launch Blocks", invoke = function() launch_blocks() end } ``` Click "Execute". After adding the keybindings, visit the [Keys section](https://tutorials.renoise.com/wiki/Preferences#Keys) of the Settings page in the UI, and you should see your keybindings listed there. Assign keys to each of the above keybindings. On my Mac, I chose the following: * Copy Pattern Mark Blocks — `cmd-ctrl-m` * Copy Pattern Unmark Blocks — `cmd-ctrl-u` * Copy Pattern Unmark All Blocks — `cmd-ctrl-shift-u` * Copy Pattern Launch Blocks — `cmd-ctrl-l` ## Usage I will post a performance video here shortly. ## Links * [Renoise Lua Scripting repository](https://github.com/renoise/xrnx) * [Renoise Lua API docs](https://files.renoise.com/xrnx/documentation/) * [Renoise Tool Development Subforum](https://forum.renoise.com/c/renoise-tool-development/19)