How can I extract certain data from a song?

Hello everyone!

I was wondering if the following suggestion might be possible: I would like to be able to output time stamps (triggered by certain effects) from my Renoise songs.
By time stamp I mean how long the song has been playing from the beginning of the song.
For example:
If I place a predefined effect values (could be something like FFFF in the effect column) in my tracks, the tool would pick these positions and would output the time stamps (perhaps combined with track number).

So the ideal output from finished tool would look something like this:

  
// track number -- time stamps for each track containing predefined sync-effects  
[track1][0.344][2.254][6.254][7.224][12.444] ...  
[track4][0.566][6.254][7.254][9.254][12.254] ...  
...  
  

It would be cool if this could be outputted as a text file, but as I have noticed, Renoise’s scripting terminal could serve the purpose as well.

I was planning to use this data to sync my computer programs (demos mostly). I could be planning the syncing for x amount of tracks while composing the music in Renoise, then I could just feed my program with this data.
This would allow syncing multiple objects with little effort. For example, I could set one object to follow hihat’s syncs and one object to follow snare’s syncs.

I figured it might be possible with Renoise’s lua functions like:

  
-- Iterate over all note/effect_ columns in the song.  
renoise.song().pattern_iterator:effect_columns_in_song(boolean visible_only)  
 -> [iterator with pos, column (renoise.EffectColumn object)]  
  

Though, if possible, capturing the effects with time stamps while playing the song in Renoise (realtime, I suppose) might be more painless solution?

I managed to output some data from my song’s effect column with:

pos = 0  
line = 0  
for pos, column in renoise.song().pattern_iterator:effect_columns_in_song(false) do print("Pos:", pos," Column:", column) end  

This is what I get as output from song that has 1 track + master. Track 1 has these effects from the start of the effect column: FFFF, XX00, FFFF, XX00

  
Pos: table: 000000000BE02B70 Column: FFFF  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: XX00  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: FFFF  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: 0000  
Pos: table: 000000000BE02B70 Column: XXFF  

It might be worth mentioning that I am fairly new to lua programming, not to mention Renoise’s scripting functionality, so any help, tips and ideas are very welcome!

Realtime is not possible with scripting. This is more something for LuaJIT which is currently not yet part of the Renoise scripting engine.
Offline calculation should however be more interesting, you could add an on-going calculator based on using the idle-notification API (a function routine that is given a chance to run every 10 milliseconds) to reharvest throughout the whole song to get the particular data you need and recalculate and export the time-stamps, but i would be very careful by really executing that thing every 10 milliseconds. (invoking os.clock() to add some delay of a couple of minutes or so to prevent the script from hogging up other processes might do better)

any help, tips and ideas are very welcome!

Hi, and welcome to the forums!

Not long ago, I helped a friend with a very similar problem to yours. He needed to export timestamps for certain instruments in his song, and then feed that data into a song visualizer he was coding.

Here is an updated version of that code, adjusted for your need to detect command strings. Right now, you can just run it in the scripting terminal and it will print the output, but you can of course modify it however you wish :]

-- ============================================================================
-- SONG TIMESTAMP EXPORTER.
-- 2014-02-02 by Kieran Foster / dblue.
--
-- Exports timestamps and other properties for certain instruments and/or
-- pattern commands that we want to detect. Useful for creating visualizers,
-- time-synced demo effects, and so on.
--
-- Edit the log_instrument() and log_command() functions to log the data using
-- your desired method.
--
-- Note: This code assumes that the BPM and LPB settings remain constant
-- throughout the entire song. It will NOT calculate correct timestamps if the
-- song has any kind of tempo automation.

-- ============================================================================
-- SETTINGS.

-- List of instrument numbers to detect.
-- Min 0x00, Max 0xFE
local instruments_to_detect = { 0x00, 0x01, 0x69 }

-- List of pattern command strings to detect.
local commands_to_detect = { "FFFF", "XX00" }

-- ============================================================================
-- HELPER FUNCTIONS.

-- Function to check if a value exists in a table/array.
-- If Lua has a native function for this, I could not find it!
function in_table(item, table)
for key, value in pairs(table) do
if value == item then
return true
end
end
return false
end

-- Function to log a detected instrument.
function log_instrument(song_line, song_beat, song_second, sequence_index,
pattern_line, track_index, instrument_index, note_index, note_volume)

-- Do something useful with the values.
print(string.format("Line: %d | Beat: %f | Track: %d | Instr: %d",
song_line, song_beat, track_index, instrument_index))
end

-- Function to log a detected command.
function log_command(song_line, song_beat, song_second, sequence_index,
pattern_line, track_index, command_string)

-- Do something useful with the values.
print(string.format("Line: %d | Beat: %f | Track: %d | Command: %s",
song_line, song_beat, track_index, command_string))
end

-- ============================================================================
-- MAIN CODE.

-- Song properties.
local song = renoise.song()
local transport = song.transport
local beats_per_minute = transport.bpm
local lines_per_beat = transport.lpb
local seconds_per_line = (60 / beats_per_minute) / lines_per_beat
local sequence = song.sequencer.pattern_sequence
local num_tracks = #song.tracks
local num_sequences = #sequence

-- Pattern sequence loop.
local song_line = 0
for sequence_index = 1, num_sequences do

-- Pattern line loop.
local pattern = song:pattern(sequence[sequence_index])
local num_lines = pattern.number_of_lines
for line_index = 1, num_lines do

-- Calculate the current beat and second.
-- song_line gets incremented at the end of the loop.
local song_beat = song_line / lines_per_beat
local song_second = song_line * seconds_per_line

-- Track loop.
for track_index = 1, num_tracks do

-- Track properties.
local track = song:track(track_index)
local num_note_columns = track.visible_note_columns
local num_effect_columns = track.visible_effect_columns
local pattern_track = pattern:track(track_index)
local line = pattern_track:line(line_index)

-- Note column loop.
for column_index = 1, num_note_columns do

-- If we found a note...
local column = line:note_column(column_index)
if (not column.is_empty) then

-- Ignore note offs.
if (column.note_value ~= renoise.PatternTrackLine.NOTE_OFF) then

-- If we want to detect this instrument...
local instrument_index = column.instrument_value
if (in_table(instrument_index, instruments_to_detect)) then

local note_index = column.note_value
local note_volume = column.volume_value

-- Log it.
log_instrument(song_line, song_beat, song_second, sequence_index,
line_index, track_index, instrument_index, note_index,
note_volume)

end -- End: Detected instrument check.

end -- End: Note off check.

end -- End: Empty column check.

end -- End: Note column loop

-- Effect column loop.
for column_index = 1, num_effect_columns do

-- If we found a command...
local column = line:effect_column(column_index)
if (not column.is_empty) then

-- If we want to detect this command...
local command_string = column.number_string .. column.amount_string
if (in_table(command_string, commands_to_detect)) then

-- Log it.
log_command(song_line, song_beat, song_second, sequence_index,
line_index, track_index, command_string)

end -- End: Detected command check.

end -- End: Empty column check.

end -- End: Effect column loop.

end -- End: Track loop.

-- Increment song line count.
song_line = song_line + 1

end -- End: Pattern line loop.

end -- End: Pattern sequence loop.

Wow! Thanks for responding so quickly, guys!

This is exactly what I was looking for! Seriously, thank you so much for sharing! I will be just fine with this script :)

I was aware that handling changes to the speed of the song might be impossible or quite hard to implement with this offline calculation approach.

If it becomes a problem in the future, maybe realtime timestamps could be implemented with a custom VST plugin. What do you think?