Renoise Song
The renoise.song()
function is the main entry point for scripting in Renoise. It returns a renoise.Song
object that represents the entire project currently loaded in the application.
Here is a simplified tree view of the song object model:
renoise.song()
├── transport (BPM, LPB, playback control)
├── tracks[] (List of all tracks in the song)
│ ├── devices[]
│ │ └── parameters[]
│ └── ...
├── instruments[] (List of all instruments)
│ ├── samples[]
│ │ └── sample_buffer
│ └── ...
├── patterns[] (The pool of all available patterns)
│ └── tracks[]
│ ├── lines[]
│ │ ├── note_columns[]
│ │ └── ...
│ └── automation[]
├── sequencer (The pattern sequence matrix)
│ └── pattern_sequence[]
└── selected_track (And other `selected_...` properties)
Let's look at some important components in more detail.
Transport
The renoise.Transport
object controls global song properties related to timing and playback.
local song = renoise.song()
local transport = song.transport
-- Read properties
print("Current BPM: " .. transport.bpm)
print("Current LinesPerBeat: " .. transport.lpb)
-- Change properties
transport.bpm = 140
-- Start playback
if not transport.playing then
transport:start(renoise.Transport.PLAYMODE_RESTART_PATTERN)
end
Tracks
The renoise.song().tracks
property is a list of all tracks in the song, including sequencer tracks, group tracks, send tracks, the master track. Each renoise.Track
has its own device chain and other properties.
local song = renoise.song()
-- Iterate over all tracks
for _, track in ipairs(song.tracks) do
-- Check the track type
if track.type == renoise.Track.TRACK_TYPE_SEQUENCER then
print("Sequencer Track: " .. track.name)
-- Access the track's device chain
local devices = track.devices
print(" - Devices: " .. #devices)
end
end
-- Access a specific track by its index (1-based)
local first_track = song.tracks[1]
first_track.name = "New Name" -- Set the track's name
Track Devices and Parameters
Once you have a device chain, you can iterate over its devices renoise.AudioDevice
and parameters renoise.DeviceParameter
. The first device in a track's chain is always the "Mixer" device, which contains the track's built-in parameters like volume and panning.
local selected_track = renoise.song().selected_track
local mixer_device = selected_track.devices[1]
print("Parameters for '" .. mixer_device.display_name .. "':")
for _, param in ipairs(mixer_device.parameters) do
print(string.format(" - %s: %s", param.name, param.value_string))
end
Modifying Parameter Values
You can read and write a parameter's value using its .value
(a number from 0.0 to 1.0) or .value_string
(a formatted string like "1.000 dB") properties.
Also, the Mixer device properties can also be accessed directly from the track object.
local selected_track = renoise.song().selected_track
-- Access a parameter by its property name on the track object
local pre_volume = selected_track.prefx_volume
print("Pre-Mixer Volume is: " .. pre_volume.value_string)
-- Set the value using a string
pre_volume.value_string = "1.0 dB"
-- Note: When reading the value back, the string may be formatted differently
assert(pre_volume.value_string == "1.000 dB")
-- Set the value using a normalized number
local pre_width = selected_track.prefx_width
pre_width.value = 1.0 -- 100% wide
assert(pre_width.value == 1.0)
Adding, Removing, and Swapping Devices
You can dynamically manage the devices in a chain. To add a device, you need its identifier path, which you can query via track.available_devices
.
local track = renoise.song().selected_track
print("Available devices:")
-- `rprint` is a Renoise extension which also pretty prints tables
rprint(track.available_devices)
-- Get a random device path (excluding plugins to avoid popups)
local device_path
repeat
device_path = track.available_devices[
math.random(1, #track.available_devices)]
until device_path:find("Native/")
-- Insert the device at the end of the chain
local device_count = #track.devices
local new_device = track:insert_device_at(
device_path, device_count + 1)
assert(#track.devices == device_count + 1)
print("Added device: " .. new_device.display_name)
-- Insert another one
track:insert_device_at(device_path, #track.devices + 1)
-- Swap the last two devices
track:swap_devices_at(#track.devices, #track.devices - 1)
print("Swapped the last two devices.")
-- Remove the last device
track:delete_device_at(#track.devices)
print("Removed the last added device.")
PatternTrack Lines
A renoise.PatternTrack
holds pattern lines and automations for a single track in a single pattern.
The following example writes a C-4
note in the first pattern's track.
local song = renoise.song()
-- Get the first pattern in the song
local pattern = song.patterns[1]
-- Get the first pattern track within that pattern
local pattern_track = pattern.tracks[1]
-- Access a specific line (1-based)
local line = pattern_track:line(1)
-- Access the first note column in that line
local note_column = line.note_columns[1]
if note_column then
-- Set a C-4 note
note_column.note_string = "C-4"
end
For efficiently iterating over pattern data, it's recommended to use the renoise.PatternIterator
.
This example changes all C-4
notes to E-4
within the current selection in the pattern editor.
local song = renoise.song()
local pattern_iter = song.pattern_iterator
local pattern_index = song.selected_pattern_index
for pos,line in pattern_iter:lines_in_pattern(pattern_index) do
for _,note_column in ipairs(line.note_columns) do
if (note_column.is_selected and
note_column.note_string == "C-4") then
note_column.note_string = "E-4"
end
end
end
PatternTrack Automation
A renoise.PatternTrackAutomation
automates track device parameters.
This example accesses the automation for the parameter currently selected in the "Automation" tab in Renoise.
local song = renoise.song()
local selected_parameter = song.selected_automation_parameter
local selected_pattern_track = song.selected_pattern_track
-- Is a parameter selected?
if selected_parameter then
local automation = selected_pattern_track:find_automation(
selected_parameter)
-- Check if automation for the selected parameter already exists
if not automation then
-- If not, create it for the current pattern/track
automation = selected_pattern_track:create_automation(
selected_parameter)
end
---- Do something with the automation ----
-- Iterate over all existing automation points
for _,point in ipairs(automation.points) do
print(("track automation: time=%s, value=%s"):format(
point.time, point.value))
end
-- Clear all points
automation.points = {}
-- Insert a single new point at line 2
automation:add_point_at(2, 0.5)
-- Change its value if it already exists
automation:add_point_at(2, 0.8)
-- Remove it again (a point must exist at this time)
automation:remove_point_at(2)
-- Batch creation/insertion of points
local new_points = {}
for i=1, #selected_pattern_track.lines do
table.insert(new_points, {
time = i,
value = i / automation.length
})
end
-- Assign them (note: new_points must be sorted by time)
automation.points = new_points
-- Change the automation's interpolation mode
automation.playmode =
renoise.PatternTrackAutomation.PLAYMODE_POINTS
end
Instruments
The renoise.song().instruments
property contains a list of all instruments. An renoise.Instrument
may contain phrases, midi in/out properties, plugin devices, samples, sample modulation, and sample DSP FX chains.
local song = renoise.song()
-- Get the selected instrument
local instrument = song.selected_instrument
if instrument then
print("Selected instrument: " .. instrument.name)
-- Access its samples
if #instrument.samples > 0 then
print(" - It has " .. #instrument.samples .. " samples.")
local first_sample = instrument.samples[1]
print(" - First sample name: " .. first_sample.name)
end
end
Samples
The renoise.SampleBuffer
object provides low-level access to the audio data of a sample.
This example inverts the phase of the selected sample data.
local sample = renoise.song().selected_sample
if not sample then
print("No sample selected")
return
end
local sample_buffer = sample.sample_buffer
if not sample_buffer.has_sample_data then
print("No sample buffer present")
return
end
-- Before modifying sample data, let Renoise prepare for undo/redo actions
sample_buffer:prepare_sample_data_changes()
-- Modify sample data in the selection (defaults to the whole sample)
for channel = 1, sample_buffer.number_of_channels do
for frame = sample_buffer.selection_start, sample_buffer.selection_end do
local value = sample_buffer:sample_data(channel, frame)
value = -value -- Invert the sample value
sample_buffer:set_sample_data(channel, frame, value)
end
end
-- Let Renoise know the changes are done. This updates UI overviews,
-- applies bit-depth quantization, and finalizes the undo/redo data.
sample_buffer:finalize_sample_data_changes()
This example creates a new sample from scratch and generates a simple sine wave.
local selected_sample = renoise.song().selected_sample
if not selected_sample then return end
local sample_buffer = selected_sample.sample_buffer
-- Define properties for the new sample data
local sample_rate = 44100
local num_channels = 1
local bit_depth = 32
local num_frames = sample_rate / 2 -- half a second
-- Create new or overwrite existing sample data
local allocation_succeeded = sample_buffer:create_sample_data(
sample_rate, bit_depth, num_channels, num_frames)
-- Check for allocation failures
if not allocation_succeeded then
renoise.app():show_error("Out of memory. Failed to allocate sample data.")
return
end
-- Let Renoise know we are about to change the sample buffer
sample_buffer:prepare_sample_data_changes()
-- Fill the sample data with a sine wave
for frame = 1, num_frames do
-- We only have one channel, so we use 1
local sample_value = math.sin((frame / num_frames) * math.pi * 2)
sample_buffer:set_sample_data(1, frame, sample_value)
end
-- Finalize the changes
sample_buffer:finalize_sample_data_changes()
-- Set up a ping-pong loop for our new sample
selected_sample.loop_mode = renoise.Sample.LOOP_MODE_PING_PONG
selected_sample.loop_start = 1
selected_sample.loop_end = num_frames
Sequencer
The renoise.PatternSequencer
manages the arrangement of patterns in the song.
The pattern_sequence
property is a list of pattern indices that defines the playback order.
local song = renoise.song()
local sequencer = song.sequencer
print("Song sequence:")
for position, pattern_index in ipairs(sequencer.pattern_sequence) do
print(string.format(" Position %d: Pattern %d", position, pattern_index))
end
-- Set a new sequence
sequencer.pattern_sequence = { 1, 1 }
-- Change the sequence: make the second slot play pattern 2
sequencer:set_pattern(2, 2)
-- Add a new slot at the end of the sequence, playing pattern 3
sequencer:insert_sequence_at(#sequencer.pattern_sequence + 1, 3)
Selected Elements
The song object also provides convenient properties to directly access elements that are currently selected in the Renoise user interface. This is very useful for creating tools that operate on the user's current context.
local song = renoise.song()
-- Get the currently selected track object
local selected_track = song.selected_track
print("Selected track: " .. selected_track.name)
-- Get the index of the selected pattern
local selected_pattern_index = song.selected_pattern_index
print("Selected pattern index: " .. selected_pattern_index)
-- Get the selected device in a track's device chain
local selected_device = song.selected_track_device
if selected_device then
print("Selected device: " .. selected_device.display_name)
end