View Single Post
Old 01-09-2017, 02:17 PM   #6
juliansader
Human being with feelings
 
Join Date: Jul 2009
Posts: 2,764
Default

Quote:
Originally Posted by FnA View Post
Julian, I was trying to think how to ask if you would demonstrate a simple use of the new functions. Maybe this is it!

Or can you point to something even easier to grasp? Like transpose selected notes or set velocity or something small like that.
The parsing and re-concatenation in this example is about as easy as it gets. (In my other scripts, the parsing gets much, much more complicated since I tried to optimize speed.)

A simple script to set the velocity to 50 would probably look something like:
Code:
targetVelocity = 50

take = reaper.MIDIEditor_GetTake(reaper.MIDIEditor_GetActive())

gotAllOK, MIDIstring = reaper.MIDI_GetAllEvts(take, "")

MIDIlen = MIDIstring:len()  

-- As the MIDI string is parsed one-by-one, all events will be
--    stored in this table while awaiting re-concatenation.
tableEvents = {}

stringPos = 1 -- Position in MIDIstring while parsing through events.
  
-- Iterate through all events one-by-one   
while stringPos < MIDIlen do
   
    offset, flags, msg, stringPos = string.unpack("i4Bs4", MIDIstring, stringPos)        
    if msg:len() == 3
    and msg:byte(1)>>4 == 9 -- Note-on MIDI event type
    and not (msg:byte(3) == 0) -- Remember to exclude note-offs
    then
        msg = msg:sub(1,2) .. string.char(targetVelocity) -- Replace velocity
    end
    
    table.insert(tableEvents, string.pack("i4Bs4", offset, flags, msg))
end

-- Upload the edited MIDI into the take
reaper.MIDI_SetAllEvts(take, table.concat(tableEvents))
Parsing of the MIDI string entails two complications:

* The position of each event is given by its *offset* relative to the preceding event, instead of its actual PPQ posision. So to determine the PPQ position of an event, each and every preceding event has to be parsed. And if an event is deleted, the following offset must be updated.

* Notation events are stored as 1) unselected text events, 2) after the note-on in the string. Once you reach a notation event, you must reverse the parsing to get the matching note-on, or alternatively parse the string twice, first to get the notation and then to get the notes.

Useful tips:

* REAPER can accept empty events (msg = ""), so if you want to delete an event without bothering to update the offset of the next event, simply replace the deleted event with an empty event with the same offset.

* The very last event in the MIDI string is always an All-Notes-Off message, which REAPER uses to indicate the end of the MIDI source. (This is not the same as the item length.) The PPQ position of this last event can be retrieved directly without parsing all the offsets, by using BR_GetMidiSourceLenPPQ(take).

Last edited by juliansader; 01-09-2017 at 02:33 PM.
juliansader is offline   Reply With Quote