View Single Post
Old 06-01-2017, 02:29 PM   #1185
Human being with feelings
Join Date: Jul 2009
Posts: 2,933

Originally Posted by lowellben View Post
Of course I already requested that a while ago, that was my first idea.

I would love to be able to auto-assign-attach a dynamic marking depending on the velocity of a note, but no script has been created so far So, I didn't push the issue.
Here is a (beta version of) a script that can add either lyrics or dynamics to selected notes in all editable takes:

ReaScript name: js_Convert velocities of selected notes to dynamics or lyrics.lua
Version: 0.92
Author: juliansader
REAPER version: 5.30 or later
  * v0.90 (2017-06-01)
    + Initial beta release
  * v0.91 (2017-06-02)
    + Somewhat improved speed
  * v0.92 (2017-06-02)
    + Option to skip redundant dynamics

-- Settings that the user can customize

insertDynamicsOrLyrics = "Dynamics" -- Either "Dynamics" or "Lyrics"

-- In the case of dynamics, these two tables give the (lower) cut-off values and text for each dynamic
-- (The tables can be customized to any length, as long as both have the same number of entries.)
tableCutoffValues = {0, 25, 50, 75, 100} 
tableDynamics     = {"pp", "p", "mp", "f", "ff"}

-- Should redundant, repeated dynamics be skipped? If "false", each and every note will have a dynamic indication.
skipRedundantDynamics = true

-- End of USER AREA

if not reaper.APIExists("MIDI_GetAllEvts") then
    reaper.ShowMessageBox("This script requires REAPER v5.30 or higher.", "ERROR", 0)

local s_unpack = string.unpack
local s_pack   = string.pack

-- REAPER does not provide API functions to enumerate the editable takes in a MIDI editor.
-- This scripts uses a 'trick' to find these editable takes: apply a native Action that affect all editable takes,
--    and then check all takes' MIDI stings to find those that wer changed by the Action.

-- Find all takes' original, pre-Action MIDI data
local tableMIDIdata = {}

for i=0, reaper.CountMediaItems(0)-1 do 

    local curItem = reaper.GetMediaItem(0, i)
    if reaper.ValidatePtr2(0, curItem, "MediaItem*") then 
        for t=0, reaper.CountTakes(curItem)-1 do 
            local curTake = reaper.GetTake(curItem, t)    
            if reaper.ValidatePtr2(0, curTake, "MediaItem_Take*") and reaper.TakeIsMIDI(curTake) then 
                -- Should we use MIDI string or MIDI hashes to compare pre- and post-Action?  
                -- Strangely, MIDI hashes may change even if the MIDI data didn't really change.
                -- So rather use the MIDI strings.
                gotDataOK, tableMIDIdata[curTake] = reaper.MIDI_GetAllEvts(curTake, "")
                --gotDataOK, tableMIDIdata[curTake] = reaper.MIDI_GetHash(curTake, true, "")
            end -- if reaper.ValidatePtr2(0, curItem, "MediaItem_Take*") and reaper.TakeIsMIDI(curTake)
        end -- for t=0, numTakes-1
    end -- if reaper.ValidatePtr2(0, curItem, "MediaItem*")
end -- for i=0, numItems-1

-- Now apply a native Action that is guaranteed to change some MIDI in all *editable* takes with selected notes
-- The script will replace the MIDI data of all takes that are changed by the native Action, so no need to 
--    undo the Action.
-- To prevent the Action from creating an undo point, start an Undo Block before calling Action.
editor = reaper.MIDIEditor_GetActive()
reaper.MIDIEditor_OnCommand(editor, 40909) -- Edit: Invert voicing downwards for selected notes (all editable)

-- Find all takes in which the MIDI data changed, by comparing their new MIDI data with the pre-Action data
for take, oldMIDIdata in pairs(tableMIDIdata) do
    local gotDataOK, newMIDIdata = reaper.MIDI_GetAllEvts(take, "")
    --local gotDataOK, newMIDIdata = reaper.MIDI_GetHash(take, true, "")
    if newMIDIdata == oldMIDIdata then
        tableMIDIdata[take] = nil

-- Now iterate through all editable takes, inserting lyrics or dynamics at selected notes
for take, MIDIstring in pairs(tableMIDIdata) do
    local tableMIDIevents = {} -- Each take's MIDI events will be stored one-by-one in this table, until concatenated again
    local t = 0 -- Index inside tableMIDIevents
    local MIDIlen = MIDIstring:len()
    local stringPos = 1
    local offset, flags, msg, velocityMsg -- velocityMsg will depend on whether lyrics or dynamics should be inserted
    local previousDynamic
    while stringPos < MIDIlen do
        offset, flags, msg, stringPos = s_unpack("i4Bs4", MIDIstring, stringPos)
        if msg ~= "" then
            local velocity = msg:byte(3)
            -- If note-on, insert new lyric or dynamic marker
            if msg:byte(1)>>4 == 9 and velocity ~= 0 and flags&1==1 then
                if insertDynamicsOrLyrics == "Dynamics" then
                    -- Code for inserting dynamics
                    for i = #tableCutoffValues, 1, -1 do
                        if velocity >= tableCutoffValues[i] then
                            if skipRedundantDynamics and i == previousDynamic then
                                velocityMsg = ""
                                velocityMsg = string.char(0xFF, 0x0F) .. "TRAC dynamic " .. tableDynamics[i]
                            previousDynamic = i
                    -- Code for inserting a lyric
                    velocityMsg = string.char(0xFF, 0x05) .. tostring(msg:byte(3))
                t = t + 1
                tableMIDIevents[t] = s_pack("i4Bs4", offset, 0, velocityMsg)
                t = t + 1
                tableMIDIevents[t] = s_pack("i4Bs4", 0, flags, msg)
                t = t + 1
                tableMIDIevents[t] = s_pack("i4Bs4", offset, flags, msg)
    end -- while stringPos < MIDIlen
    reaper.MIDI_SetAllEvts(take, table.concat(tableMIDIevents))

-- Creat Undo point
-- flag=4 should limit the information that is included in the undo point 
--    to changes that were made to *items*, so the creation of the undo point is hopefully much faster. 
if insertDynamicsOrLyrics == "Dynamics" then 
    undoText = "Convert note velocities to dynamics"
    undoText = "Convert note velocities to lyrics"
reaper.Undo_EndBlock2(0, undoText, 4)
This code uses a newfangled "trick" to find all editable takes the MIDI editor, since REAPER's API does not provide this information natively. There might still be a bug in my trick, so this is just a beta version of the script.

In the script's USER AREA, you can select between lyrics or dynamics, and customize the dynamics.

EDIT: update 0.92: Option to skip redundant dynamics indications


Last edited by juliansader; 06-05-2017 at 10:47 AM.
juliansader is offline   Reply With Quote