Skip to content

Instantly share code, notes, and snippets.

@mthines
Last active January 30, 2024 09:38
Show Gist options
  • Select an option

  • Save mthines/c5ae921df6f35eb6b322932a820d0f5a to your computer and use it in GitHub Desktop.

Select an option

Save mthines/c5ae921df6f35eb6b322932a820d0f5a to your computer and use it in GitHub Desktop.

Revisions

  1. mthines revised this gist Jan 30, 2024. 1 changed file with 6 additions and 1 deletion.
    7 changes: 6 additions & 1 deletion Render tracks as MIDI Output (obeying time selection).lua
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,6 @@
    -- @name Render tracks as MIDI Output (obeying time selection)
    -- @author Mads Thines Coello ([email protected])
    -- @version 0.1.0

    function saveSelectedTracks()
    local selectedTracks = {}

    @@ -112,6 +111,12 @@ function renderNestedMIDIToTrack(selectedTrack)
    -- Merge the MIDI together into the first selected
    mergeMIDI()

    -- Rename the merged MIDI item to the name of the Track
    local selectedItem = reaper.GetSelectedMediaItem(0, 0)
    local track = reaper.GetMediaItem_Track(selectedItem)
    local _, trackName = reaper.GetSetMediaTrackInfo_String(track, "P_NAME", "", false)
    reaper.GetSetMediaItemTakeInfo_String(reaper.GetActiveTake(selectedItem), "P_NAME", trackName, true)

    -- Clear the selection
    clearSelection()
    end
  2. mthines revised this gist Jan 29, 2024. 5 changed files with 106 additions and 382 deletions.
    107 changes: 0 additions & 107 deletions MIDI Render.lua
    Original file line number Diff line number Diff line change
    @@ -1,107 +0,0 @@
    -- @name MIDI Render
    -- @author Mads Thines Coello ([email protected])
    -- @version 1.0.0

    local armSelectedTrack = reaper.NamedCommandLookup("_XENAKIOS_SELTRAX_RECARMED")
    local unarmSelectedTrack = reaper.NamedCommandLookup("_XENAKIOS_SELTRAX_RECUNARMED")
    local hasExecuted = false

    function increasePlayrateBy1()
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    end

    function increasePlayrateBy4()
    reaper.Undo_BeginBlock()

    -- Reset Playrate
    reaper.Main_OnCommand(40521, 0)

    -- Increase Playrate.
    -- @note You can decrease the number of playrate increases to increase quality of the rendered MIDI, but slower renders
    increasePlayrateBy1()
    increasePlayrateBy1()
    increasePlayrateBy1()

    reaper.Undo_EndBlock("Set playrate to 4%", 0)
    end

    function handleCleanup()
    -- Reset Playrate
    reaper.Main_OnCommand(40521, 0)
    -- Unarm
    reaper.Main_OnCommandEx(unarmSelectedTrack, 0, 0)
    end

    function onStopRecordingEvent()
    if hasExecuted then
    print("Function already executed, skipping...")
    return
    end

    local isRecording = reaper.GetPlayState() & 1 == 1

    if isRecording then
    reaper.defer(onStopRecordingEvent)
    return
    end

    -- Clean up
    handleCleanup()

    -- Make sure it's not being executed in an infinite loop
    hasExecuted = true
    end

    function onEndOfTimeline()
    local playState = reaper.GetPlayState()

    if playState & 1 ~= 1 then
    return
    end

    local position = reaper.GetPlayPosition()
    local startTime, endTime = reaper.GetSet_LoopTimeRange(false, false, 0, 0, false)

    if position < endTime then
    reaper.defer(onEndOfTimeline)
    return
    end

    -- Transport: Stop
    reaper.Main_OnCommand(1016, 0)

    -- Clean up
    handleCleanup()

    -- Make sure it's not being executed in an infinite loop
    hasExecuted = true
    end

    function init()
    -- Arm Sslected Track
    reaper.Main_OnCommandEx(armSelectedTrack, 0, 0)
    -- Set Recording to MIDI Output
    reaper.Main_OnCommand(40500, 0)
    -- Go to start of time selection and rewind for buffer
    reaper.Main_OnCommand(40630, 0)
    reaper.Main_OnCommand(40084, 0)
    -- Reset Playrate
    reaper.Main_OnCommand(40521, 0)
    -- Increase Playrate to 4.0
    increasePlayrateBy4()
    -- Start Recording
    reaper.Main_OnCommand(1013, 0)
    -- Add event listener for when the recording is manually stopped
    onStopRecordingEvent()
    -- Add event listener for when the timeline end is met
    onEndOfTimeline()
    end

    init();
    38 changes: 38 additions & 0 deletions Merge MIDI.lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,38 @@
    -- @name Merge MIDI
    -- @description Merged the selected items into a single MIDI item
    -- @author Mads Thines Coello ([email protected])
    -- @reference https://www.reapertips.com/post/how-to-merge-midi-in-reaper
    -- @version 0.1.0

    function mergeMIDI()
    -- Item: Set item mix behavior to always mix
    reaper.Main_OnCommand(40919, 0);

    -- Item: Implode items across tracks into items on one track
    reaper.Main_OnCommand(40644, 0);

    -- Item: Glue items, ignoring time selection
    reaper.Main_OnCommand(40362, 0);

    -- Item: Set item mix behavior to project default
    reaper.Main_OnCommand(40922, 0);
    end

    -- Run the main function
    function main()
    -- Begin a new undo block
    reaper.Undo_BeginBlock()

    -- Unselect all items
    mergeMIDI();

    -- End the undo block without registering any changes
    reaper.Undo_EndBlock("Merge MIDI", -1)
    end

    -- Disable UI refresh to prevent flickering
    reaper.PreventUIRefresh(1)
    main();
    -- Enable UI refresh
    reaper.PreventUIRefresh(-1)
    reaper.UpdateArrange()
    23 changes: 6 additions & 17 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,21 +1,10 @@
    # Purpose
    ## These scripts makes it possible to merge MIDI in Reaper.

    There's no native way to render envelope data to a media item in Reaper.
    This script renders the selected tracks MIDI output to a media file.
    ### Explaination

    --
    - `Merge MIDI.lua`: Merges MIDI of selected items
    - `Render tracks as MIDI Output (obeying time selection).lua`: Renders all of the MIDI data of the selected tracks (including the nested child tracks) to new MIDI items (obeying time selection).

    I really like the workflow of creating CC messages using envelopes and automation items. I find it way more convenient than using MIDI items.
    ### Installation

    The issue occured when I need to export the MIDI messages, as Reaper doesn't register any MIDI on the track, as there's no media items and solely MIDI from the envelope messages - hence I need to render the MIDI to media item on the track. That's what this does. It increases the transport tempo to 4x and then renders the MIDI item.

    _@note There's tiny delay due to the rendering being done in 4x speed, but I haven't had any issues and you can compensate in the MIDI configuration by configuring offset._

    # Installation

    1. Open Reaper resource path and place the scripts in the Scripts directory (e.g. `C:\Users\youruser\AppData\Roaming\REAPER\Scripts`).

    # Usage

    1. Select the tracks you want to render the MIDI to.
    2. Execute the one of the actions
    1. Open Reaper resource path and place the scripts in the Scripts directory (e.g. `REAPER\Scripts`).
    172 changes: 62 additions & 110 deletions Render tracks as MIDI Output (obeying time selection).lua
    Original file line number Diff line number Diff line change
    @@ -1,62 +1,30 @@
    -- @name Render tracks as MIDI Output (obeying time selection)
    -- @author Mads Thines Coello ([email protected])
    -- @version 0.0.1
    -- @version 0.1.0

    -- Function to save the selected items
    function saveSelectedItems()
    local selectedItems = {}

    for i = 0, reaper.CountSelectedMediaItems(0) - 1 do
    local item = reaper.GetSelectedMediaItem(0, i)
    local itemGUID = reaper.BR_GetMediaItemGUID(item)
    table.insert(selectedItems, itemGUID)
    end

    return selectedItems
    end

    function deleteSavedItems(selectedItems)
    for _, itemGUID in ipairs(selectedItems) do
    local item = reaper.BR_GetMediaItemByGUID(0, itemGUID)
    if item then
    reaper.DeleteTrackMediaItem(reaper.GetMediaItem_Track(item), item)
    end
    end
    end

    function saveInitiallySelectedTracks()
    local initiallySelectedTracks = {}
    function saveSelectedTracks()
    local selectedTracks = {}

    for i = 0, reaper.CountSelectedTracks(0) - 1 do
    local selectedTrack = reaper.GetSelectedTrack(0, i)
    table.insert(initiallySelectedTracks, selectedTrack)
    table.insert(selectedTracks, selectedTrack)
    end

    return initiallySelectedTracks
    return selectedTracks
    end

    function restoreInitiallySelectedTracks(initiallySelectedTracks)
    reaper.Main_OnCommand(40297, 0) -- Unselect all tracks
    function setTracksAsSelected(tracks)
    -- Unselect all tracks
    reaper.Main_OnCommand(40297, 0)

    for _, track in ipairs(initiallySelectedTracks) do
    for _, track in ipairs(tracks) do
    reaper.SetTrackSelected(track, true)
    end

    reaper.UpdateArrange()
    end

    function unselectSpecificTracks(trackIndices)
    for _, index in ipairs(trackIndices) do
    local track = reaper.GetTrack(0, index - 1) -- Indices are 0-based in Reaper Lua API
    if track then
    reaper.SetTrackSelected(track, false)
    end
    end

    reaper.UpdateArrange()
    end

    -- Function to insert empty MIDI items for selected tracks
    -- Insert empty MIDI item in the time selection and select it
    function insertEmptyMIDIItemsInTimeSelection()
    local startTime, endTime = reaper.GetSet_LoopTimeRange(false, false, 0, 0, false)

    @@ -72,6 +40,8 @@ function insertEmptyMIDIItemsInTimeSelection()
    return
    end

    local newItems = {}

    -- Loop through selected tracks
    for i = 0, numSelectedTracks - 1 do
    local track = reaper.GetSelectedTrack(0, i)
    @@ -81,28 +51,13 @@ function insertEmptyMIDIItemsInTimeSelection()

    -- Select the new MIDI item
    reaper.SetMediaItemSelected(newItem, true)
    table.insert(newItems, newItem)
    end
    end

    function renameTakeToTrackName()
    local selectedItem = reaper.GetSelectedMediaItem(0, 0)

    if selectedItem then
    local activeTake = reaper.GetActiveTake(selectedItem)

    if activeTake then
    local track = reaper.GetMediaItemTake_Track(activeTake)
    local _, trackName = reaper.GetTrackName(track, "")

    reaper.GetSetMediaItemTakeInfo_String(activeTake, "P_NAME", trackName, true)
    else
    reaper.MB("Error: No active take on the selected item.", "Error", 0)
    end
    else
    reaper.MB("Error: No media item selected.", "Error", 0)
    end
    return newItems
    end

    -- Recursively select child tracks
    function selectAllChildTracks(track)
    local numChildTracks = reaper.CountTracks(0)

    @@ -117,81 +72,78 @@ function selectAllChildTracks(track)
    end
    end

    function selectAllChildTracksOfSelectedTracks()
    local selectedTracks = {}
    function clearSelection()
    -- Unselect all items
    reaper.Main_OnCommand(40289, 0);
    -- Unselect all tracks
    reaper.Main_OnCommand(40297, 0);
    end

    -- Get selected tracks
    for i = 0, reaper.CountSelectedTracks(0) - 1 do
    local selectedTrack = reaper.GetSelectedTrack(0, i)
    table.insert(selectedTracks, selectedTrack)
    end
    function mergeMIDI()
    -- Item: Set item mix behavior to always mix
    reaper.Main_OnCommand(40919, 0);

    -- Select all child tracks recursively
    for _, track in ipairs(selectedTracks) do
    selectAllChildTracks(track)
    end
    -- Item: Implode items across tracks into items on one track
    reaper.Main_OnCommand(40644, 0);

    reaper.UpdateArrange()
    -- Item: Glue items, ignoring time selection
    reaper.Main_OnCommand(40362, 0);

    -- Item: Set item mix behavior to project default
    reaper.Main_OnCommand(40922, 0);
    end

    -- Function to select the parent track
    function renderMIDIToTracks(track)
    -- Create Empty MIDI Item for all tracks
    -- Select all new MIDI items
    insertEmptyMIDIItemsInTimeSelection()
    function renderNestedMIDIToTrack(selectedTrack)
    -- Make sure the selected track is selected
    reaper.SetTrackSelected(selectedTrack, true)

    -- Recursively select all child tracks
    selectAllChildTracks(selectedTrack)

    -- Create new empty MIDI items for all tracks so we can apply the MIDI to them
    local newItems = insertEmptyMIDIItemsInTimeSelection(track)

    -- Apply track/take FX To MIDI items (MIDI Output)
    reaper.Main_OnCommand(40436, 0);

    -- Crop to Active Take
    reaper.Main_OnCommand(40131, 0);

    -- Save selected items
    local savedSelection = saveSelectedItems()

    -- Copy items
    reaper.Main_OnCommand(40698, 0);

    reaper.Main_OnCommand(40297, 0) -- Unselect all tracks
    reaper.SetTrackSelected(track, true)
    -- Merge the MIDI together into the first selected
    mergeMIDI()

    -- Paste on Parent Track
    reaper.Main_OnCommand(42398, 0);

    -- Implode MIDI to lanes
    reaper.Main_OnCommand(42596, 0);

    -- Glue MIDI
    reaper.Main_OnCommand(40362, 0);

    -- Remove emoty lanes
    reaper.Main_OnCommand(42689, 0);

    -- Restore initially selected tracks
    deleteSavedItems(savedSelection)
    -- Clear the selection
    clearSelection()
    end

    -- Run the main function
    function main()
    -- Begin a new undo block
    reaper.Undo_BeginBlock()

    -- Save initially selected tracks
    local initiallySelectedTracks = saveInitiallySelectedTracks()
    -- Unselect all items
    reaper.Main_OnCommand(40289, 0);

    local numSelectedTracks = reaper.CountSelectedTracks(0)

    -- Go to Start of Time Selection
    reaper.Main_OnCommand(40630, 0);
    if numSelectedTracks == 0 then
    reaper.MB("Error: No tracks selected.", "Error", 0)
    return
    end

    -- Select all of the child tracks
    selectAllChildTracksOfSelectedTracks()
    -- Save selected tracks for iteration and restoration later
    local selectedTracks = saveSelectedTracks()

    for _, track in ipairs(initiallySelectedTracks) do
    -- Render the MIDI (MIDI Output)
    renderMIDIToTracks(track)
    -- Iterate over the selected tracks and render them individually to MIDI
    for _, selectedTrack in ipairs(selectedTracks) do
    renderNestedMIDIToTrack(selectedTrack);
    end

    -- Restore initially selected tracks
    restoreInitiallySelectedTracks(initiallySelectedTracks)
    -- Clear the selection
    clearSelection()

    -- Restore the initially selected tracks to the selection
    setTracksAsSelected(selectedTracks)

    -- End the undo block without registering any changes
    reaper.Undo_EndBlock("Render tracks as MIDI Output (obeying time selection)", -1)
    148 changes: 0 additions & 148 deletions Render tracks to parent as MIDI Output (obeying time selection).lua
    Original file line number Diff line number Diff line change
    @@ -1,148 +0,0 @@
    -- @name Render tracks to parent as MIDI Output (obeying time selection)
    -- @author Mads Thines Coello ([email protected])
    -- @version 0.0.1

    -- Function to save the selected items
    function saveSelectedItems()
    local selectedItems = {}

    for i = 0, reaper.CountSelectedMediaItems(0) - 1 do
    local item = reaper.GetSelectedMediaItem(0, i)
    local itemGUID = reaper.BR_GetMediaItemGUID(item)
    table.insert(selectedItems, itemGUID)
    end

    return selectedItems
    end

    function deleteSavedItems(selectedItems)
    for _, itemGUID in ipairs(selectedItems) do
    local item = reaper.BR_GetMediaItemByGUID(0, itemGUID)
    if item then
    reaper.DeleteTrackMediaItem(reaper.GetMediaItem_Track(item), item)
    end
    end
    end

    -- Function to select the parent track
    function selectParentTrack()
    local selectedTrack = reaper.GetSelectedTrack(0, 0)

    local parentTrack = reaper.GetParentTrack(selectedTrack)

    if parentTrack then
    reaper.SetOnlyTrackSelected(parentTrack)
    else
    reaper.MB("Error: No parent track found.", "Error", 0)
    end
    end

    -- Function to insert empty MIDI items for selected tracks
    function insertEmptyMIDIItemsInTimeSelection()
    local startTime, endTime = reaper.GetSet_LoopTimeRange(false, false, 0, 0, false)

    if startTime == endTime then
    reaper.MB("Error: No time selection set.", "Error", 0)
    return
    end

    local numSelectedTracks = reaper.CountSelectedTracks(0)

    if numSelectedTracks == 0 then
    reaper.MB("Error: No tracks selected.", "Error", 0)
    return
    end

    -- Loop through selected tracks
    for i = 0, numSelectedTracks - 1 do
    local track = reaper.GetSelectedTrack(0, i)

    -- Insert a new MIDI item within the time selection
    local newItem = reaper.CreateNewMIDIItemInProj(track, startTime, endTime, false)

    -- Select the new MIDI item
    reaper.SetMediaItemSelected(newItem, true)
    end
    end

    function renameTakeToTrackName()
    local selectedItem = reaper.GetSelectedMediaItem(0, 0)

    if selectedItem then
    local activeTake = reaper.GetActiveTake(selectedItem)

    if activeTake then
    local track = reaper.GetMediaItemTake_Track(activeTake)
    local _, trackName = reaper.GetTrackName(track, "")

    reaper.GetSetMediaItemTakeInfo_String(activeTake, "P_NAME", trackName, true)
    else
    reaper.MB("Error: No active take on the selected item.", "Error", 0)
    end
    else
    reaper.MB("Error: No media item selected.", "Error", 0)
    end
    end

    -- Function to select the parent track
    function renderMIDIToParentTrack()
    -- Create Empty MIDI Item for all tracks
    -- Select all new MIDI items
    insertEmptyMIDIItemsInTimeSelection()

    -- Apply track/take FX To MIDI items (MIDI Output)
    reaper.Main_OnCommand(40436, 0);

    -- Crop to Active Take
    reaper.Main_OnCommand(40131, 0);

    -- Save selected items
    local savedSelection = saveSelectedItems()

    -- Copy items
    reaper.Main_OnCommand(40698, 0);

    -- Select Parent Track
    selectParentTrack()

    -- Paste on Parent Track
    reaper.Main_OnCommand(42398, 0);

    -- Implode MIDI to lanes
    reaper.Main_OnCommand(42596, 0);

    -- Glue MIDI
    reaper.Main_OnCommand(40362, 0);

    -- Remove emoty lanes
    reaper.Main_OnCommand(42689, 0);

    -- Restore initially selected tracks
    deleteSavedItems(savedSelection)

    end

    -- Run the main function
    function main()
    -- Begin a new undo block
    reaper.Undo_BeginBlock()

    -- Go to Start of Time Selection
    reaper.Main_OnCommand(40630, 0);

    -- Render the MIDI to the Parent Track (MIDI Output)
    renderMIDIToParentTrack()

    -- Rename new MIDI File to Track name
    renameTakeToTrackName()

    -- End the undo block without registering any changes
    reaper.Undo_EndBlock("Render tracks to parent as MIDI Output (obeying time selection)", -1)
    end

    -- Disable UI refresh to prevent flickering
    reaper.PreventUIRefresh(1)
    main();
    -- Enable UI refresh
    reaper.PreventUIRefresh(-1)
    reaper.UpdateArrange()
  3. mthines revised this gist Jan 23, 2024. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -13,9 +13,9 @@ _@note There's tiny delay due to the rendering being done in 4x speed, but I hav

    # Installation

    1. Open Reaper resource path and place the script (`MIDI Render.lua`) in the Scripts directory (e.g. `C:\Users\youruser\AppData\Roaming\REAPER\Scripts`).
    1. Open Reaper resource path and place the scripts in the Scripts directory (e.g. `C:\Users\youruser\AppData\Roaming\REAPER\Scripts`).

    # Usage

    1. Select the tracks you want to render the MIDI to.
    2. Execute the `MIDI Render` action
    2. Execute the one of the actions
  4. mthines revised this gist Jan 23, 2024. 2 changed files with 353 additions and 0 deletions.
    205 changes: 205 additions & 0 deletions Render tracks as MIDI Output (obeying time selection).lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,205 @@
    -- @name Render tracks as MIDI Output (obeying time selection)
    -- @author Mads Thines Coello ([email protected])
    -- @version 0.0.1

    -- Function to save the selected items
    function saveSelectedItems()
    local selectedItems = {}

    for i = 0, reaper.CountSelectedMediaItems(0) - 1 do
    local item = reaper.GetSelectedMediaItem(0, i)
    local itemGUID = reaper.BR_GetMediaItemGUID(item)
    table.insert(selectedItems, itemGUID)
    end

    return selectedItems
    end

    function deleteSavedItems(selectedItems)
    for _, itemGUID in ipairs(selectedItems) do
    local item = reaper.BR_GetMediaItemByGUID(0, itemGUID)
    if item then
    reaper.DeleteTrackMediaItem(reaper.GetMediaItem_Track(item), item)
    end
    end
    end

    function saveInitiallySelectedTracks()
    local initiallySelectedTracks = {}

    for i = 0, reaper.CountSelectedTracks(0) - 1 do
    local selectedTrack = reaper.GetSelectedTrack(0, i)
    table.insert(initiallySelectedTracks, selectedTrack)
    end

    return initiallySelectedTracks
    end

    function restoreInitiallySelectedTracks(initiallySelectedTracks)
    reaper.Main_OnCommand(40297, 0) -- Unselect all tracks

    for _, track in ipairs(initiallySelectedTracks) do
    reaper.SetTrackSelected(track, true)
    end

    reaper.UpdateArrange()
    end

    function unselectSpecificTracks(trackIndices)
    for _, index in ipairs(trackIndices) do
    local track = reaper.GetTrack(0, index - 1) -- Indices are 0-based in Reaper Lua API
    if track then
    reaper.SetTrackSelected(track, false)
    end
    end

    reaper.UpdateArrange()
    end

    -- Function to insert empty MIDI items for selected tracks
    function insertEmptyMIDIItemsInTimeSelection()
    local startTime, endTime = reaper.GetSet_LoopTimeRange(false, false, 0, 0, false)

    if startTime == endTime then
    reaper.MB("Error: No time selection set.", "Error", 0)
    return
    end

    local numSelectedTracks = reaper.CountSelectedTracks(0)

    if numSelectedTracks == 0 then
    reaper.MB("Error: No tracks selected.", "Error", 0)
    return
    end

    -- Loop through selected tracks
    for i = 0, numSelectedTracks - 1 do
    local track = reaper.GetSelectedTrack(0, i)

    -- Insert a new MIDI item within the time selection
    local newItem = reaper.CreateNewMIDIItemInProj(track, startTime, endTime, false)

    -- Select the new MIDI item
    reaper.SetMediaItemSelected(newItem, true)
    end
    end

    function renameTakeToTrackName()
    local selectedItem = reaper.GetSelectedMediaItem(0, 0)

    if selectedItem then
    local activeTake = reaper.GetActiveTake(selectedItem)

    if activeTake then
    local track = reaper.GetMediaItemTake_Track(activeTake)
    local _, trackName = reaper.GetTrackName(track, "")

    reaper.GetSetMediaItemTakeInfo_String(activeTake, "P_NAME", trackName, true)
    else
    reaper.MB("Error: No active take on the selected item.", "Error", 0)
    end
    else
    reaper.MB("Error: No media item selected.", "Error", 0)
    end
    end

    function selectAllChildTracks(track)
    local numChildTracks = reaper.CountTracks(0)

    for i = 0, numChildTracks - 1 do
    local childTrack = reaper.GetTrack(0, i)
    local parentTrack = reaper.GetParentTrack(childTrack)

    if parentTrack and parentTrack == track then
    reaper.SetTrackSelected(childTrack, true)
    selectAllChildTracks(childTrack) -- Recursively select child tracks of the current child track
    end
    end
    end

    function selectAllChildTracksOfSelectedTracks()
    local selectedTracks = {}

    -- Get selected tracks
    for i = 0, reaper.CountSelectedTracks(0) - 1 do
    local selectedTrack = reaper.GetSelectedTrack(0, i)
    table.insert(selectedTracks, selectedTrack)
    end

    -- Select all child tracks recursively
    for _, track in ipairs(selectedTracks) do
    selectAllChildTracks(track)
    end

    reaper.UpdateArrange()
    end

    -- Function to select the parent track
    function renderMIDIToTracks(track)
    -- Create Empty MIDI Item for all tracks
    -- Select all new MIDI items
    insertEmptyMIDIItemsInTimeSelection()

    -- Apply track/take FX To MIDI items (MIDI Output)
    reaper.Main_OnCommand(40436, 0);

    -- Crop to Active Take
    reaper.Main_OnCommand(40131, 0);

    -- Save selected items
    local savedSelection = saveSelectedItems()

    -- Copy items
    reaper.Main_OnCommand(40698, 0);

    reaper.Main_OnCommand(40297, 0) -- Unselect all tracks
    reaper.SetTrackSelected(track, true)

    -- Paste on Parent Track
    reaper.Main_OnCommand(42398, 0);

    -- Implode MIDI to lanes
    reaper.Main_OnCommand(42596, 0);

    -- Glue MIDI
    reaper.Main_OnCommand(40362, 0);

    -- Remove emoty lanes
    reaper.Main_OnCommand(42689, 0);

    -- Restore initially selected tracks
    deleteSavedItems(savedSelection)
    end

    -- Run the main function
    function main()
    -- Begin a new undo block
    reaper.Undo_BeginBlock()

    -- Save initially selected tracks
    local initiallySelectedTracks = saveInitiallySelectedTracks()

    -- Go to Start of Time Selection
    reaper.Main_OnCommand(40630, 0);

    -- Select all of the child tracks
    selectAllChildTracksOfSelectedTracks()

    for _, track in ipairs(initiallySelectedTracks) do
    -- Render the MIDI (MIDI Output)
    renderMIDIToTracks(track)
    end

    -- Restore initially selected tracks
    restoreInitiallySelectedTracks(initiallySelectedTracks)

    -- End the undo block without registering any changes
    reaper.Undo_EndBlock("Render tracks as MIDI Output (obeying time selection)", -1)
    end

    -- Disable UI refresh to prevent flickering
    reaper.PreventUIRefresh(1)
    main();
    -- Enable UI refresh
    reaper.PreventUIRefresh(-1)
    reaper.UpdateArrange()
    148 changes: 148 additions & 0 deletions Render tracks to parent as MIDI Output (obeying time selection).lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,148 @@
    -- @name Render tracks to parent as MIDI Output (obeying time selection)
    -- @author Mads Thines Coello ([email protected])
    -- @version 0.0.1

    -- Function to save the selected items
    function saveSelectedItems()
    local selectedItems = {}

    for i = 0, reaper.CountSelectedMediaItems(0) - 1 do
    local item = reaper.GetSelectedMediaItem(0, i)
    local itemGUID = reaper.BR_GetMediaItemGUID(item)
    table.insert(selectedItems, itemGUID)
    end

    return selectedItems
    end

    function deleteSavedItems(selectedItems)
    for _, itemGUID in ipairs(selectedItems) do
    local item = reaper.BR_GetMediaItemByGUID(0, itemGUID)
    if item then
    reaper.DeleteTrackMediaItem(reaper.GetMediaItem_Track(item), item)
    end
    end
    end

    -- Function to select the parent track
    function selectParentTrack()
    local selectedTrack = reaper.GetSelectedTrack(0, 0)

    local parentTrack = reaper.GetParentTrack(selectedTrack)

    if parentTrack then
    reaper.SetOnlyTrackSelected(parentTrack)
    else
    reaper.MB("Error: No parent track found.", "Error", 0)
    end
    end

    -- Function to insert empty MIDI items for selected tracks
    function insertEmptyMIDIItemsInTimeSelection()
    local startTime, endTime = reaper.GetSet_LoopTimeRange(false, false, 0, 0, false)

    if startTime == endTime then
    reaper.MB("Error: No time selection set.", "Error", 0)
    return
    end

    local numSelectedTracks = reaper.CountSelectedTracks(0)

    if numSelectedTracks == 0 then
    reaper.MB("Error: No tracks selected.", "Error", 0)
    return
    end

    -- Loop through selected tracks
    for i = 0, numSelectedTracks - 1 do
    local track = reaper.GetSelectedTrack(0, i)

    -- Insert a new MIDI item within the time selection
    local newItem = reaper.CreateNewMIDIItemInProj(track, startTime, endTime, false)

    -- Select the new MIDI item
    reaper.SetMediaItemSelected(newItem, true)
    end
    end

    function renameTakeToTrackName()
    local selectedItem = reaper.GetSelectedMediaItem(0, 0)

    if selectedItem then
    local activeTake = reaper.GetActiveTake(selectedItem)

    if activeTake then
    local track = reaper.GetMediaItemTake_Track(activeTake)
    local _, trackName = reaper.GetTrackName(track, "")

    reaper.GetSetMediaItemTakeInfo_String(activeTake, "P_NAME", trackName, true)
    else
    reaper.MB("Error: No active take on the selected item.", "Error", 0)
    end
    else
    reaper.MB("Error: No media item selected.", "Error", 0)
    end
    end

    -- Function to select the parent track
    function renderMIDIToParentTrack()
    -- Create Empty MIDI Item for all tracks
    -- Select all new MIDI items
    insertEmptyMIDIItemsInTimeSelection()

    -- Apply track/take FX To MIDI items (MIDI Output)
    reaper.Main_OnCommand(40436, 0);

    -- Crop to Active Take
    reaper.Main_OnCommand(40131, 0);

    -- Save selected items
    local savedSelection = saveSelectedItems()

    -- Copy items
    reaper.Main_OnCommand(40698, 0);

    -- Select Parent Track
    selectParentTrack()

    -- Paste on Parent Track
    reaper.Main_OnCommand(42398, 0);

    -- Implode MIDI to lanes
    reaper.Main_OnCommand(42596, 0);

    -- Glue MIDI
    reaper.Main_OnCommand(40362, 0);

    -- Remove emoty lanes
    reaper.Main_OnCommand(42689, 0);

    -- Restore initially selected tracks
    deleteSavedItems(savedSelection)

    end

    -- Run the main function
    function main()
    -- Begin a new undo block
    reaper.Undo_BeginBlock()

    -- Go to Start of Time Selection
    reaper.Main_OnCommand(40630, 0);

    -- Render the MIDI to the Parent Track (MIDI Output)
    renderMIDIToParentTrack()

    -- Rename new MIDI File to Track name
    renameTakeToTrackName()

    -- End the undo block without registering any changes
    reaper.Undo_EndBlock("Render tracks to parent as MIDI Output (obeying time selection)", -1)
    end

    -- Disable UI refresh to prevent flickering
    reaper.PreventUIRefresh(1)
    main();
    -- Enable UI refresh
    reaper.PreventUIRefresh(-1)
    reaper.UpdateArrange()
  5. mthines revised this gist Jan 20, 2024. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -3,6 +3,12 @@
    There's no native way to render envelope data to a media item in Reaper.
    This script renders the selected tracks MIDI output to a media file.

    --

    I really like the workflow of creating CC messages using envelopes and automation items. I find it way more convenient than using MIDI items.

    The issue occured when I need to export the MIDI messages, as Reaper doesn't register any MIDI on the track, as there's no media items and solely MIDI from the envelope messages - hence I need to render the MIDI to media item on the track. That's what this does. It increases the transport tempo to 4x and then renders the MIDI item.

    _@note There's tiny delay due to the rendering being done in 4x speed, but I haven't had any issues and you can compensate in the MIDI configuration by configuring offset._

    # Installation
  6. mthines revised this gist Jan 20, 2024. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -3,8 +3,7 @@
    There's no native way to render envelope data to a media item in Reaper.
    This script renders the selected tracks MIDI output to a media file.

    ### Note:
    _There's tiny delay due to the rendering being done in 4x speed, but I haven't had any issues and you can compensate in the MIDI configuration by configuring offset._
    _@note There's tiny delay due to the rendering being done in 4x speed, but I haven't had any issues and you can compensate in the MIDI configuration by configuring offset._

    # Installation

  7. mthines revised this gist Jan 20, 2024. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -4,13 +4,13 @@ There's no native way to render envelope data to a media item in Reaper.
    This script renders the selected tracks MIDI output to a media file.

    ### Note:
    There's tiny delay due to the rendering being done in 4x speed, but I haven't had any issues and you can compensate in the MIDI configuration by configuring offset.
    _There's tiny delay due to the rendering being done in 4x speed, but I haven't had any issues and you can compensate in the MIDI configuration by configuring offset._

    # Installation

    1. Open Reaper resource path and place the script (`MIDI Render.lua`) in the Scripts directory (e.g. C:\Users\youruser\AppData\Roaming\REAPER\Scripts).
    1. Open Reaper resource path and place the script (`MIDI Render.lua`) in the Scripts directory (e.g. `C:\Users\youruser\AppData\Roaming\REAPER\Scripts`).

    # Usage

    1. Select the tracks you want to render the MIDI to.
    2. Execute the MIDI Render action
    2. Execute the `MIDI Render` action
  8. mthines revised this gist Jan 20, 2024. 1 changed file with 16 additions and 0 deletions.
    16 changes: 16 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,16 @@
    # Purpose

    There's no native way to render envelope data to a media item in Reaper.
    This script renders the selected tracks MIDI output to a media file.

    ### Note:
    There's tiny delay due to the rendering being done in 4x speed, but I haven't had any issues and you can compensate in the MIDI configuration by configuring offset.

    # Installation

    1. Open Reaper resource path and place the script (`MIDI Render.lua`) in the Scripts directory (e.g. C:\Users\youruser\AppData\Roaming\REAPER\Scripts).

    # Usage

    1. Select the tracks you want to render the MIDI to.
    2. Execute the MIDI Render action
  9. mthines created this gist Jan 17, 2024.
    107 changes: 107 additions & 0 deletions MIDI Render.lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,107 @@
    -- @name MIDI Render
    -- @author Mads Thines Coello ([email protected])
    -- @version 1.0.0

    local armSelectedTrack = reaper.NamedCommandLookup("_XENAKIOS_SELTRAX_RECARMED")
    local unarmSelectedTrack = reaper.NamedCommandLookup("_XENAKIOS_SELTRAX_RECUNARMED")
    local hasExecuted = false

    function increasePlayrateBy1()
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    reaper.defer(reaper.Main_OnCommand(40522, 0))
    end

    function increasePlayrateBy4()
    reaper.Undo_BeginBlock()

    -- Reset Playrate
    reaper.Main_OnCommand(40521, 0)

    -- Increase Playrate.
    -- @note You can decrease the number of playrate increases to increase quality of the rendered MIDI, but slower renders
    increasePlayrateBy1()
    increasePlayrateBy1()
    increasePlayrateBy1()

    reaper.Undo_EndBlock("Set playrate to 4%", 0)
    end

    function handleCleanup()
    -- Reset Playrate
    reaper.Main_OnCommand(40521, 0)
    -- Unarm
    reaper.Main_OnCommandEx(unarmSelectedTrack, 0, 0)
    end

    function onStopRecordingEvent()
    if hasExecuted then
    print("Function already executed, skipping...")
    return
    end

    local isRecording = reaper.GetPlayState() & 1 == 1

    if isRecording then
    reaper.defer(onStopRecordingEvent)
    return
    end

    -- Clean up
    handleCleanup()

    -- Make sure it's not being executed in an infinite loop
    hasExecuted = true
    end

    function onEndOfTimeline()
    local playState = reaper.GetPlayState()

    if playState & 1 ~= 1 then
    return
    end

    local position = reaper.GetPlayPosition()
    local startTime, endTime = reaper.GetSet_LoopTimeRange(false, false, 0, 0, false)

    if position < endTime then
    reaper.defer(onEndOfTimeline)
    return
    end

    -- Transport: Stop
    reaper.Main_OnCommand(1016, 0)

    -- Clean up
    handleCleanup()

    -- Make sure it's not being executed in an infinite loop
    hasExecuted = true
    end

    function init()
    -- Arm Sslected Track
    reaper.Main_OnCommandEx(armSelectedTrack, 0, 0)
    -- Set Recording to MIDI Output
    reaper.Main_OnCommand(40500, 0)
    -- Go to start of time selection and rewind for buffer
    reaper.Main_OnCommand(40630, 0)
    reaper.Main_OnCommand(40084, 0)
    -- Reset Playrate
    reaper.Main_OnCommand(40521, 0)
    -- Increase Playrate to 4.0
    increasePlayrateBy4()
    -- Start Recording
    reaper.Main_OnCommand(1013, 0)
    -- Add event listener for when the recording is manually stopped
    onStopRecordingEvent()
    -- Add event listener for when the timeline end is met
    onEndOfTimeline()
    end

    init();