View Single Post
Old 06-01-2017, 04:53 PM   #1186
lowellben
Human being with feelings
 
lowellben's Avatar
 
Join Date: Aug 2010
Location: They put me in a home.
Posts: 3,432
Default

This looks incredible. Testing now!!! THANK YOU

Quote:
Originally Posted by juliansader View Post
Here is a (beta version of) a script that can add either lyrics or dynamics to selected notes in all editable takes:

Code:
--[[
ReaScript name: js_Convert velocties of selected notes to dynamics or lyrics.lua
Version: 0.91
Author: juliansader
Website: http://forum.cockos.com/showthread.php?p=1851179#post1851179
Donation: https://www.paypal.me/juliansader
REAPER version: 5.30 or later
]]
 
--[[
 Changelog:
  * v0.90 (2017-06-01)
    + Initial beta release
  * v.091 (2017-06-02)
    + Somewhat improved speed
]]

-------------------------------------------------------------------
-- USER AREA
-- Settings that the user can customize

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

-- In the case of dynamics, these two tables give the cut-off values and text for each dynamic
tableCutoffValues = {0, 25, 50, 75, 100} 
tableDynamics     = {"pp", "p", "mp", "f", "ff"}

-- End of USER AREA
-------------------------------------------------------------------


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

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.Undo_BeginBlock2(0)
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(curTake, true, "")
    if newMIDIdata == oldMIDIdata then
        tableMIDIdata[take] = nil
    end
end


-----------------------------------------------------------------------------------------
-- 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
    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
                            velocityMsg = string.char(0xFF, 0x0F) .. "TRAC dynamic " .. tableDynamics[i]
                            break
                        end
                    end
                else
                    -- Code for inserting a lyric
                    velocityMsg = string.char(0xFF, 0x05) .. tostring(msg:byte(3))
                end
                t = t + 1
                tableMIDIevents[t] = s_pack("i4Bs4", offset, 0, velocityMsg)
                t = t + 1
                tableMIDIevents[t] = s_pack("i4Bs4", 0, flags, msg)
            else
                t = t + 1
                tableMIDIevents[t] = s_pack("i4Bs4", offset, flags, msg)
            end
        end
    end -- while stringPos < MIDIlen
    reaper.MIDI_SetAllEvts(take, table.concat(tableMIDIevents))
end


-------------------------------------------------------------------------
-- 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"
else
    undoText = "Convert note velocities to lyrics"
end
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.
__________________
47.8% of statistics are made up.
lowellben is offline   Reply With Quote