How to create a Lua script to initialize a song?

So I am trying to write a script that initializes a song with 16 tracks, a specific set of effects on each one and does midi mapping on my controller, setting up lights, listeners, etc.

It seems like this should be simple enough, but even when working with GPT, I’m getting confounded by the API and how to look up device names and how to insert them. For reference, the below is the broken script I’m working with, and it fails immediately on trying to insert an effect on a track with the error

failed to instantiate the device ‘Audio Effects/Gainer’. the device is either unknown, or an error happened while initializing it.’

I have zero idea how to start troubleshooting this. For reference, I’m an professional c++ dev and experienced renoise user, but I don’t have Lua experience or experience with the API. It seems like this should be doable ???

– Number of Tracks and Controllers
local NUM_TRACKS = 16
local MIDIMIX_CONTROLLERS = { “MIDImix 1”, “MIDImix 2” } – Adjust names if needed

– Plugin Names (the names we use to later search within a track)
local GAIN_NAME = “MIDIMIX_MUTE_GAIN” – Our renamed gainer
local FILTER_NAME = “Analog Filter”
local SEND_1_NAME = “Send 1”
local SEND_2_NAME = “Send 2”


– Ensure at least two send tracks exist.

function ensure_send_tracks()
local song = renoise.song()
local send_count = 0

– Count existing send tracks (they are part of song.tracks)
for _, track in ipairs(song.tracks) do
if track.type == renoise.Track.TRACK_TYPE_SEND then
send_count = send_count + 1
end
end

– Add send tracks if fewer than 2 exist
while send_count < 2 do
song:insert_track_at(#song.tracks + 1)
local new_track = song.tracks[#song.tracks]
new_track.type = renoise.Track.TRACK_TYPE_SEND
new_track.name = "Send Track " … (send_count + 1)
send_count = send_count + 1
end
end


– Initialize regular tracks with our effects chain.

function initialize_tracks()
local song = renoise.song()

– Make sure send tracks exist before modifying our track devices
ensure_send_tracks()

– Create regular tracks if there aren’t enough
while #song.tracks < NUM_TRACKS do
song:insert_track_at(#song.tracks + 1)
end

– Set up each regular track (first NUM_TRACKS tracks assumed to be regular)
for i = 1, NUM_TRACKS do
local track = song.tracks[i]
track.name = "MIDIMIX Track " … i
track.prefx_volume.value = 1.0 – Full volume

-- Insert devices AFTER the built-in Volume/Pan device:
-- 1. Insert Gainer (to be used as a mute switch)
local mute_gain = track:insert_device_at("Audio Effects/Gainer", 2)
mute_gain.name = GAIN_NAME
-- Instead of using -INF, use the parameter's minimum value.
mute_gain.parameters[1].value = mute_gain.parameters[1].value_range.min
mute_gain.is_active = false -- Start inactive

-- 2. Insert Lowpass Filter
local filter = track:insert_device_at("Audio Effects/Analog Filter", 3)
filter.name = FILTER_NAME

-- 3. Insert Send 1 device
local send1 = track:insert_device_at("Audio Effects/Send", 4)
send1.name = SEND_1_NAME
send1.parameters[1].value = 0.5 -- Default send amount (normalized)

-- 4. Insert Send 2 device
local send2 = track:insert_device_at("Audio Effects/Send", 5)
send2.name = SEND_2_NAME
send2.parameters[1].value = 0.5 -- Default send amount (normalized)

end
end


– MIDI Mapping for Two MIDIMixes (only basic mappings shown)

function map_midi_controls()
local song = renoise.song()

for midimix_index, device_name in ipairs(MIDIMIX_CONTROLLERS) do
for i = 1, 8 do – 8 channels per MIDIMix
local track_index = (midimix_index - 1) * 8 + i
if track_index > NUM_TRACKS then break end
local track = song.tracks[track_index]

  -- Map Fader to Track Volume
  renoise.tool():add_midi_mapping {
    name = "MIDIMix: Fader " .. i,
    invoke = function(value)
      track.prefx_volume.value = value / 127 -- Normalize to 0-1
    end
  }

  -- Map Mute Button to toggle MIDIMIX_MUTE_GAIN (the gainer)
  renoise.tool():add_midi_mapping {
    name = "MIDIMix: Mute " .. i,
    invoke = function()
      local mute_gain = nil
      for _, device in ipairs(track.devices) do
        if device.name == GAIN_NAME then
          mute_gain = device
          break
        end
      end
      if mute_gain then 
        mute_gain.is_active = not mute_gain.is_active 
      end
    end
  }

  -- Map Knob 1 to Lowpass Filter cutoff
  renoise.tool():add_midi_mapping {
    name = "MIDIMix: Knob 1 " .. i,
    invoke = function(value)
      local filter = nil
      for _, device in ipairs(track.devices) do
        if device.name == FILTER_NAME then
          filter = device
          break
        end
      end
      if filter then 
        filter.parameters[1].value = value / 127 
      end
    end
  }

  -- Map Knob 2 to Send 1 amount
  renoise.tool():add_midi_mapping {
    name = "MIDIMix: Knob 2 " .. i,
    invoke = function(value)
      local send1 = nil
      for _, device in ipairs(track.devices) do
        if device.name == SEND_1_NAME then
          send1 = device
          break
        end
      end
      if send1 then 
        send1.parameters[1].value = value / 127 
      end
    end
  }

  -- Map Knob 3 to Send 2 amount
  renoise.tool():add_midi_mapping {
    name = "MIDIMix: Knob 3 " .. i,
    invoke = function(value)
      local send2 = nil
      for _, device in ipairs(track.devices) do
        if device.name == SEND_2_NAME then
          send2 = device
          break
        end
      end
      if send2 then 
        send2.parameters[1].value = value / 127 
      end
    end
  }
  
  -- (Optionally, you can add a mapping for the Rec/Arm button here)
end

end
end


– Run the Initial Setup

initialize_tracks()
map_midi_controls()
renoise.app():show_status(“MIDIMix Setup Complete!”)

Can you paste the code using this forum option, it will make a less messy post;

Have you fed chatgpt the relevant api information (Renoise Lua API) after feeding it the terminal errors from renoise?

hi @monsignor_epoxy

ok so let’s get started.

the first thing that’s going on, there is no such thing as an Audio Effects/Gainer.

i mean, yes there is, but no, there isn’t.

the reason is, that that’s not the path where the device is.

how to figure out the full path:

rprint (renoise.song().selected_track.available_devices)

and the result is

[1] =>  Audio/Effects/Native/Delay
[2] =>  Audio/Effects/Native/Multitap
[3] =>  Audio/Effects/Native/mpReverb 2
[4] =>  Audio/Effects/Native/Reverb
[5] =>  Audio/Effects/Native/Convolver
[6] =>  Audio/Effects/Native/Bus Compressor
[7] =>  Audio/Effects/Native/Compressor
[8] =>  Audio/Effects/Native/Gate 2
[9] =>  Audio/Effects/Native/Maximizer
[10] =>  Audio/Effects/Native/Analog Filter
[11] =>  Audio/Effects/Native/Digital Filter
[12] =>  Audio/Effects/Native/Comb Filter 2
[13] =>  Audio/Effects/Native/EQ 5
[14] =>  Audio/Effects/Native/EQ 10
[15] =>  Audio/Effects/Native/Mixer EQ
[16] =>  Audio/Effects/Native/Chorus 2
[17] =>  Audio/Effects/Native/Flanger 2
[18] =>  Audio/Effects/Native/Phaser 2
[19] =>  Audio/Effects/Native/RingMod 2
[20] =>  Audio/Effects/Native/LofiMat 2
[21] =>  Audio/Effects/Native/Distortion 2
[22] =>  Audio/Effects/Native/Cabinet Simulator
[23] =>  Audio/Effects/Native/Exciter
[24] =>  Audio/Effects/Native/Stereo Expander
[25] =>  Audio/Effects/Native/DC Offset
[26] =>  Audio/Effects/Native/Gainer
[27] =>  Audio/Effects/Native/Repeater
[28] =>  Audio/Effects/Native/Doofer
[29] =>  Audio/Effects/Native/#Line Input
[30] =>  Audio/Effects/Native/#ReWire Input
[31] =>  Audio/Effects/Native/#Send
[32] =>  Audio/Effects/Native/#Multiband Send
[33] =>  Audio/Effects/Native/#Sidechain
[34] =>  Audio/Effects/Native/*Instr. Macros
[35] =>  Audio/Effects/Native/*Instr. Automation
[36] =>  Audio/Effects/Native/*Instr. MIDI Control
[37] =>  Audio/Effects/Native/*Hydra
[38] =>  Audio/Effects/Native/*Meta Mixer
[39] =>  Audio/Effects/Native/*Formula
[40] =>  Audio/Effects/Native/*XY Pad
[41] =>  Audio/Effects/Native/*LFO
[42] =>  Audio/Effects/Native/*Key Tracker
[43] =>  Audio/Effects/Native/*Velocity Tracker
[44] =>  Audio/Effects/Native/*Signal Follower

So, what you wanna do, in case you wanna load up a device, is this:

local mute_gain = track:insert_device_at("Audio/Effects/Native/Gainer", 2)

it always helps to have the right path for it, then it’ll know how to do it.

so, fix the paths and you’ll be fine.

btw, i like your thinking of using a tool to create a specific bunch of details. what i’d recommend is also looking at these:

renoise.song().tracks[].visible_note_columns
renoise.song().tracks[].visible_effect_columns
3723:renoise.song().tracks[].volume_column_visible, _observable
3724-  -> [boolean]
3725:renoise.song().tracks[].panning_column_visible, _observable
3726-  -> [boolean]
3727:renoise.song().tracks[].delay_column_visible, _observable
3728-  -> [boolean]
3729:renoise.song().tracks[].sample_effects_column_visible, _observable
3730-  -> [boolean]

this will let you configure them as you like, so you’ll get the best combos of panning, delay, sample, etc.

enjoy.

hit me up if any questions.

for the midimappings i would recommend not having a space after the : so instead of MIDIMix: Fader do MIDIMix:Fader .
same with Mute etc.

btw you can solve all of these by simply saving a template with the midimappings set up on your MIDIMix - (and saved elsewhere), and then just load the template.

because there’s no way to set up “oh, for this midimapping, i want it bound to this specific midi device CC” - that you’ll have to do with the Midi Mappings Dialog (CMD-M) and those are saved into the song. there’s no way, as far as i can see, to load a midi mapping. (i’m not talking about add_midi_mapping, i’m talking about the bound midi CC to a midimapping)

EDIT: actually, i tell a lie:

renoise.song():load_midi_mappings(filename)

you can save the midi mappings to a file inside your tool, and load it with your tool. i’ll have to set this up into Paketti :slight_smile:

2 Likes

word to the wise
there is no need to feed the lua api function to a gpt. it knows it already.

1 Like

I wrote a tool that lets you select a file from a fold and use that file as a template for a new song.

You could create the song file to your liking and save it off for re-use.

1 Like