--[[
Note Abstraction Layer
for linear DAW export
Generates an event table of the whole linear song,
sorted by track number and absolute line number.
Such a note event also contains merged data as:
- duration, exact timestamp. Takes care of missing note-offs, disabled sequence slots etc.
- normalized per-note-data like “velocity”, "pan", etc.
- Elimates pattern abstraction, linear output
]]
class "NoteAbstraction"
function NoteAbstraction:__init()
end
function NoteAbstraction:generateSongNoteEvents()
local noteEvents = {}
local activeNotes = {}
local lastVelocities = {}
local noteKey = nil
local patternIndex = nil
local patternLines = nil
local seqIsMuted = {}
local lineOffset = 0
for seqIndex = 1, #Song.sequencer.pattern_sequence do
patternIndex = Song.sequencer.pattern_sequence[seqIndex]
patternLines = Song.patterns[patternIndex].number_of_lines
for position, noteColumn in Song.pattern_iterator:note_columns_in_pattern(patternIndex) do
noteKey = position.column .. "_" .. position.track
local checkForNoteEvent = function(_noteKey)
-- Note-Off / Cut
local noteOn = activeNotes[_noteKey]
if noteOn then
noteOn.lineDuration = (lineOffset + position.line - 1) - noteOn.absLineNum
noteOn.duration = ((lineOffset + position.line - 1) * 256 + noteColumn.delay_value) -
noteOn.timestamp
noteOn.releaseVelocity = noteColumn.volume_value < 128 and (noteColumn.volume_value / 127) or nil
table.insert(noteEvents, noteOn)
-- remove note-on
activeNotes[_noteKey] = nil
end
end
if (position.line == 1 and position.column == 1) then
local _seqIsMuted = Song.sequencer:track_sequence_slot_is_muted(position.track, seqIndex)
if (seqIsMuted[noteKey] ~= _seqIsMuted) then
seqIsMuted[noteKey] = _seqIsMuted
checkForNoteEvent(noteKey)
end
end
if noteColumn.is_empty then
goto continue
end
if noteColumn.note_value >= 0 and noteColumn.note_value < renoise.PatternLine.NOTE_OFF then
checkForNoteEvent(noteKey)
-- add note-on
activeNotes[noteKey] = {
key = noteColumn.note_value,
noteString = noteColumn.note_string,
velocity = noteColumn.volume_value < 128 and (noteColumn.volume_value / 127) or
lastVelocities[noteKey] or
1,
pan = noteColumn.panning_value < 128 and (noteColumn.panning_value / 127) or nil,
delay = noteColumn.delay_value,
seqNum = seqIndex,
patternNum = position.pattern,
patternDuration = patternLines * 256,
columnNum = position.column,
trackNum = position.track,
lineNum = position.line - 1,
absLineNum = lineOffset + position.line - 1,
timestamp = (lineOffset + position.line - 1) * 256 + noteColumn.delay_value,
patternRelTimestamp = (position.line - 1) * 256 + noteColumn.delay_value,
patternTimestamp = lineOffset * 256
}
if (noteColumn.volume_value < 128) then
lastVelocities[noteKey] = noteColumn.volume_value
end
elseif noteColumn.note_value == renoise.PatternLine.NOTE_OFF then
checkForNoteEvent(noteKey)
end
::continue::
end
lineOffset = lineOffset + patternLines
end
-- end playing notes
for _, noteOn in pairs(activeNotes) do
if noteOn then
noteOn.lineDuration = lineOffset - noteOn.absLineNum
noteOn.duration = (lineOffset * 256) - noteOn.timestamp
table.insert(noteEvents, noteOn)
end
end
table.sort(noteEvents, function(a, b)
if (a.trackNum == b.trackNum) then
return a.timestamp < b.timestamp
end
return a.trackNum < b.trackNum
end)
return noteEvents
end
Simply have a look at the code of related tools for inspiration? Most tools out there are open source. If you want MIDI, have a look at the MIDI export tool.
If that’s still too much effort, ask some LLM to write that for you. That’s how the kids do it now.
Ok, well LLM is very imprecise and most of the time gives a raw scaffolding, based on often not so good public internet knowledge… At least in my experience.
Maybe the kids are right though, and I just need to learn how to properly use the LLM, or use the paid versions instead…
I know joule already did that, and yes, will look into other tools. The design pattern in midi export tool seems to be a bit oldschool, I was looking for an abstraction which ultimatively is easy to use.
Currently trying to make some “note abstraction service” class or something.
Any hints for a modern tool, maybe I should have a look into Simple Pianoroll?
Rarely is it wise to begin from an abstraction. Abstractions necessarily arise from concrete examples. Examine what you’re trying to do, list all your requirement, and find the shortest path to satisfy them. I would not be over-worried about “reinventing the wheel”. Sometimes as programmers we need a slightly different wheel, and the easiest path is to invent it rather than reshape an existing one.
Yeah, if 100% optimization isn’t needed, this can be made quite quickly using renoise.PatternIterator already. Using ripairs, keeping a little track of lengths and storing the data any way you want.
Don’t agree, Renoise pattern, track, line logic is very iterative…
If I do not build a proper abstraction first, details mentioned in first post, I will end up will extremely over-complicated logic, just to stay in the vanilla Renoise logic. So it is wise to build an abstraction layer first. You should test and debug the layer first.
But this is on my point of view, please read further here:
Actually did you test copilot in VSCode once? I never so far… I doubt that it will give you nice results specifically for Renoise LUA api, but maybe I am wrong. I am a bit overhelmed by VSCode configuration, usually working in PHPStorm and other JetBrains IDEs Those are slow, but very comfortable.
Currently some weird AI is changing my code on the fly in my Renoise project while coding, mostly adding nasty bugs, giving stupid suggestions or removing parts of lines. Really weird. No idea what AI that is, maybe Musks, just here to destroy. Can’t be copilot, because it seems you have to enter login data for that first. (I bet it’s just two code formatters running at once or so)
I believe he’s been trying to convert xrns files into DAWproject+Redux files. So he wanted a song representation that would make that easier. I think he’s worked it out.
You are certainly right. I was writing here just about my personal expectations, it’s not that I don’t love the Renoise API - which other DAW has such a complete API at all? Recently Steinberg added some JS API for controllers, and also Bitwig still is on the way (where Taktik was 2010 or so already), via two APIs, but none of these is nowhere near the feature set of the Renoise API.
If I am “criticizing” here, it is mostly about my expection or inability to properly code an everlasting tool (which I still can maintain 10 years later easily) without an object-oriented (oo) style approach/abstraction. If I look at my older tools, where I mostly do iterate directly over those array structures of the API, it seems to be obvious that I designed those not nicely. Having even a hard time to understand what’s going on in my own tool. This wouldn’t been the case for me, if I created an abstraction layer beforehand and named the functions, parameters, variables properly. I know that a lot of guys of you have no problem with that at all.
Also it makes a lot of sense that the api is representing the pattern data structure as it actually is. It’s a “near the metal” approach certainly. Still I was also “dreaming” of a tracker which is not bound to absolute numbers / indices, a bit like it started with Aodix, and then all only objects only, oo style structures, e.g. a note object containing all note related data automatically, e.g. per-note-automation, only normalized values, etc, already. I would imagine once you had this kind of song structure objects, how easy it would be to code a pattern view which would be zoomable for example. So everything then very elastic, flexible, and loosly bounded. But making a fast API for that certainly would be a difficult task then.
I am very thankful for what we have with Renoise. It’s the best tracker and one of the best daws existing. Simple as that.
Don’t get me wrong, I know where you’re coming from. I often dream of a tracker that isn’t so beholden to tradition; indeed, of any DAW that isn’t hung up on one particular data representation. Aodix is a good shout—I never used it but it strikes me that it was exploring territories that remain largely unmapped to this day.
I am ambivalent about Renoise following these paths, as I think it gains much from its traditionalism, and the API concerns to me are irrelevant. Yes, if we want to do something un-Renoise-like, we have to work for it a bit.
But in general I resonate with your points about flexible structures, etc. These are things I think about a lot when pondering the DAWs of the future.