Skip to content

Commit

Permalink
Minor improvements to Reaper utility.
Browse files Browse the repository at this point in the history
  • Loading branch information
gogins committed Nov 22, 2024
1 parent e68ad96 commit 79ed891
Showing 1 changed file with 167 additions and 0 deletions.
167 changes: 167 additions & 0 deletions CsoundAC/csoundac_reaper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
'''
This module implements facilities for supporting algorithmic composition in
the Reaper digital audio workstation, especially using Csound and the
CsoundAC library for algorithmic composition.
Generated scores, which may be either CsoundAC.Score objects or plain Python
lists of notes [time, duration, channel, key, velocity], are sent to Reaper
MidiItems.
Author: Michael Gogins
'''
import CsoundAC
from reaper_python import *

RPR_ShowConsoleMsg("Hello, Reaper!\n")

'''
Returns False if the object evaluates to False or is an empty string, or if
the object is a Python "string pointer" with a value of zero; otherwise,
returns True.
'''
def is_valid_object(object):
"""Check if a Reaper object reference is valid."""
if not object or object.strip() == "":
return False
# Check if the object string indicates a null pointer
if object.startswith("(") and "0x0000000000000000" in object:
return False
return True

'''
Clears all data from the MIDI item.
'''
def clear_midi_item(midi_item):
if not is_valid_object(midi_item):
RPR_ShowConsoleMsg("No MIDI item selected!\n")
return
# Get the active take from the selected item
midi_take = RPR_GetActiveTake(midi_item)
if not is_valid_object(midi_take):
RPR_ShowConsoleMsg("MIDI item has no active MIDI take!\n")
return
# Delete all events from a MidiItem.
p1 = int(0)
p2 = int(1)
p3 = int(2)
(result, midi_take, note_count, cc_count, text_sysex_count) = RPR_MIDI_CountEvts(midi_take, p1, p2, p3)
for i in range(note_count - 1, -1, -1): # Iterate in reverse to avoid index shifting
RPR_MIDI_DeleteNote(midi_take, i)
for i in range(cc_count - 1, -1, -1): # Iterate in reverse
RPR_MIDI_DeleteCC(midi_take, i)
for i in range(text_sysex_count - 1, -1, -1): # Iterate in reverse
RPR_MIDI_DeleteTextSysexEvt(midi_take, i)
# Commit changes
RPR_MIDI_Sort(midi_take)
RPR_ShowConsoleMsg("Cleared all contents from the MIDI item.\n")

'''
Inserts a note into the MidiTake.
'''
def note_to_miditake(midi_take, start, duration, channel, key, velocity):
start_ppq = RPR_MIDI_GetPPQPosFromProjTime(midi_take, start)
end_ppq = RPR_MIDI_GetPPQPosFromProjTime(midi_take, start + duration)
if start_ppq == -1 or end_ppq == -1:
RPR_ShowConsoleMsg(
f"Failed to calculate PPQ for note {note}: Start={start_ppq}, End={end_ppq}\n"
)
return
RPR_ShowConsoleMsg(
f"Inserting note: Start PPQ={start_ppq}, End PPQ={end_ppq}, "
f"Key={key}, Velocity={velocity}\n"
)
success = RPR_MIDI_InsertNote(
midi_take, # Take
False, # Selected
False, # Muted
start_ppq, # Start time (PPQ)
end_ppq, # End time (PPQ)
int(channel), # Channel
int(key), # Note pitch
int(velocity), # Velocity
True # NoSort
)
if not success:
RPR_ShowConsoleMsg(f"Failed to insert note: {note}\n")

'''
Sends all notes from the score to the currently selected MidiItem.
Previously existing events in the item are first deleted. The score can be
either a CsoundAC.Score object, or a plain Python list of notes
(start, duration, channel, key, velocity).
NOTE: All times are project times in seconds!
'''
def score_to_midiitem(score, key_offset=0, start_offset=0):
# Check for a selected MIDI item
selected_item = RPR_GetSelectedMediaItem(0, 0)
RPR_ShowConsoleMsg(f"Selected item: {type(selected_item)} {selected_item}\n")
if is_valid_object(selected_item):
# Get the active take from the selected item
midi_take = RPR_GetActiveTake(selected_item)
if not is_valid_object(midi_take):
RPR_ShowConsoleMsg("Selected item has no active take!\n")
return
RPR_ShowConsoleMsg("Using active take of selected MIDI item.\n")
else:
# No item selected, create a new MIDI item
track = RPR_GetTrack(0, 0)
if not is_valid_object(track):
RPR_ShowConsoleMsg("No track found to create a MIDI item!\n")
return
start_time = start_offset # Start time in seconds
end_time = start_time + 4 # End time in seconds
midi_item, media_track, d1, d2, d3 = RPR_CreateNewMIDIItemInProj(track, start_time, end_time, False)
RPR_ShowConsoleMsg(f"Created new MIDI Item: {midi_item}\n")
if not is_valid_object(midi_item):
RPR_ShowConsoleMsg("Failed to create a new MIDI item!\n")
return
# Select the newly created MIDI item
RPR_SetMediaItemSelected(midi_item, True)
midi_take = RPR_GetActiveTake(midi_item)
# Get the active take for the new item
midi_take = RPR_GetActiveTake(midi_item)
if not is_valid_object(midi_take):
RPR_ShowConsoleMsg("Failed to retrieve take for the new MIDI item!\n")
return
RPR_ShowConsoleMsg("Created, selected, and activated a new MIDI item.\n")
clear_midi_item(selected_item)
if isinstance(score, CsoundAC.Score):
for eventI in range(len(score)):
event = score[eventI]
if event.isNote():
event = score[eventI]
start = event.getTime() + start_offset
duration = event.getDuration()
channel = int(event.getChannel())
key = int(event.getKey() + key_offset)
velocity = int(event.getVelocity())
note_to_miditake(midi_take, start, duration, channel, key, velocity)
# Fit MidiItem to the duration of the score.
duration = score.getDurationFromZero()
# Set the new length
RPR_SetMediaItemInfo_Value(selected_item, "D_LENGTH", duration)
# Update the project to reflect the changes
RPR_UpdateArrange()

RPR_ShowConsoleMsg(f"Duration of score: {duration}\n")
else:
for note in notes:
start, duration, channel, key, velocity = note
note_to_miditake(midi_take, start + start_offset, duration, channel, key, velocity)
# Commit changes

RPR_MIDI_Sort(midi_take)
RPR_ShowConsoleMsg("MIDI notes added successfully! Check the editor.\n")

# Test case.
if __name__ == '__main__':
notes = [
(0, 0.5, 1, 60, 100), # (start, duration, channel, key, velocity)
(0.5, 0.5, 1, 62, 100),
(1, 0.5, 1, 64, 100),
(1.5, 1.5, 1, 67, 100),
]
score_to_midiitem(notes)


0 comments on commit 79ed891

Please sign in to comment.