Max8 wrote:Hello,
I would like to record a piano piece and then use a program to generate sheet music. However, when I record the MIDI, everything is written to one MIDI channel. Is it possible to write the notes to a different MIDI channel above a certain pitch?
Many thanks in advance,
Max
Easy way is to use a DAW that runs on your operating system and that supports scripting such as Reaper ( Windows, Mac OS, Linux) or Logic Pro ( Mac OS)
Then ask Gemini or ChatGPT or DeepSeek to write your script.
For instance here is a script in Reaper that does what you want :
Create a new action in Reaper (ReaScript) and use Python as the language
Copy paste the following code ( here generated by Gemini ) in the action
In this case the cut off pitch is C4 , and notes below will be written to channel 1, and notes above to channel 2
Load or Record your midi file in a track , and run the action. That's it.
In this example you transform the code after recording and perform the loop on midi events.
You could also write a script that works in realtime , intercept the note-on events and depending on the note on pick value assign a midi channel.
============================================
# SCRIPT: Split MIDI Notes by Pitch and Assign Channels
# LANGUAGE: Python
# AUTHOR: Gemini (Adapted from Python pretty_midi logic)
from reaper_python import *
import struct
# --- CONFIGURATION ---
# MIDI Note Number (0-127). 60 = Middle C (C4).
PITCH_CUTOFF = 60
# MIDI Channel assignments (1-16)
LOW_NOTES_CHANNEL = 1
HIGH_NOTES_CHANNEL = 2
# Note: MIDI channel is 0-indexed in the MIDI message (0-15).
# So Channel 1 is 0x00, Channel 2 is 0x01, etc.
LOW_CHANNEL_BYTE = (LOW_NOTES_CHANNEL - 1)
HIGH_CHANNEL_BYTE = (HIGH_NOTES_CHANNEL - 1)
# ---------------------
def main():
# 1. Check for selected MIDI item
item = RPR_GetSelectedMediaItem(0, 0) # Get the first selected item
if not item:
RPR_ShowConsoleMsg("Error: Select a MIDI item first.\n")
return
take = RPR_GetActiveTake(item)
if not take:
RPR_ShowConsoleMsg("Error: Selected item has no active take.\n")
return
# 2. Open MIDI item for editing (required for modification)
RPR_MIDI_SetItemTakeChannelMap(take, -1, 0) # Clear channel map to avoid conflicts
RPR_MIDI_SetItemTakeChannelMap(take, -1, 0)
RPR_MIDI_SetItemTakeChannelMap(take, -1, 0)
RPR_MIDI_SetItemTakeChannelMap(take, 0, 0)
RPR_MIDI_SetItemTakeChannelMap(take, 1, 1)
# RPR_MIDI_SetItemTakeChannelMap(take, LOW_CHANNEL_BYTE, 0) # Assign Channel 1
# RPR_MIDI_SetItemTakeChannelMap(take, HIGH_CHANNEL_BYTE, 1) # Assign Channel 2
RPR_Undo_BeginBlock2(0) # Start Undo Block
# 3. Iterate through all MIDI events
# index: 0 is the starting index.
# We use a loop and increment the index until RPR_MIDI_EnumItemInTake only returns 0
# event_idx: the index of the event
# is_selected: whether the event is selected
# is_muted: whether the event is muted
# ppq_pos: position in PPQ (Pulses Per Quarter note)
# message: the raw MIDI message bytes
event_idx = 0
while True:
(ret, take, event_idx, is_selected, is_muted, ppq_pos, message) = RPR_MIDI_GetEvt(take, event_idx)
if ret == 0:
# End of events list
break
# Check if the message is a Note-On event (Status byte 0x90 to 0x9F)
# Note-On message status byte: 0x90 + channel (0-15)
# We only care about the first byte's high nibble (0x9_)
status = message[0] & 0xF0
# Check if it's a Note-On event (0x90) and not a Note-Off with velocity 0
if status == 0x90 and len(message) >= 3 and message[2] > 0:
# Message is: [Status+Channel, Note/Pitch, Velocity]
pitch = message[1]
if pitch < PITCH_CUTOFF:
# Assign to LOW_NOTES_CHANNEL
new_status = 0x90 | LOW_CHANNEL_BYTE
else:
# Assign to HIGH_NOTES_CHANNEL
new_status = 0x90 | HIGH_CHANNEL_BYTE
# Create the new message with the modified channel
new_message = struct.pack('BBB', new_status, message[1], message[2])
# Update the event in the MIDI item
RPR_MIDI_SetEvt(take, event_idx, is_selected, is_muted, ppq_pos, new_message)
# Increment index to get the next event
event_idx += 1
# 4. Update and close the MIDI item
RPR_MIDI_Sort(take)
RPR_UpdateItemInProject(item)
RPR_Undo_EndBlock2(0, "Split MIDI Notes by Pitch and Channel", -1)
RPR_ShowConsoleMsg(f"\n✅ Successfully split notes in the selected item!")
RPR_ShowConsoleMsg(f" - Cutoff Pitch: {PITCH_CUTOFF}")
RPR_ShowConsoleMsg(f" - Notes < Cutoff assigned to MIDI Channel {LOW_NOTES_CHANNEL}")
RPR_ShowConsoleMsg(f" - Notes >= Cutoff assigned to MIDI Channel {HIGH_NOTES_CHANNEL}")
main()
Last edited by Pianistically (14-12-2025 00:53)