Old 12-15-2017, 01:35 PM   #1
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default New script for auto piano split

Select a track with MIDI on it, then running this script (pnosplitter.eel) will:

Ask you for a split point (middle C being suggested at 60);
Create two tracks directly below the original, cloned from it - so duplicating all its properties, including levels, panning, routing and plugin instances;
Parse their MIDI content into RH and LH (chosen split point included in the upper part but not the lower);
All MIDI CC info for the full part is retained on both new tracks;
Return focus to the original and mute the MIDI item on it (so you're not left doubled up, but it's non-destructive);
It's also one-shot undoable;
It's reasonably error-trapped - won't do anything if the selected track has no MIDI on it or you enter nonsense as the split point. No user error reporting though.

Plenty of annotation in the script for those who are curious. I'm happy to accept any suggestions for improvements or adaptations.

Any optimisation tips would be welcome. I've tested it with a florid 10-min classical piano part weighing in at just shy of 5,000 notes and it chews through it in about 4s, but obviously, the slicker the better.

It's a very chunky, basic approach, but I imagine it would have some uses in quite a lot of situations.

Let me know what you think folks - but go easy. I'm a rusty old C dog and this is my first voyage into EEL and it's my first time trying to "macro" REAPER.

https://www.dropbox.com/s/9kfjdsgk77...itter.eel?dl=0

Last edited by Jason Lyon; 12-15-2017 at 01:59 PM.
Jason Lyon is offline   Reply With Quote
Old 12-15-2017, 08:42 PM   #2
FnA
Human being with feelings
 
FnA's Avatar
 
Join Date: Jun 2012
Posts: 2,173
Default

Quote:
Originally Posted by Jason Lyon View Post
Any optimisation tips would be welcome. I've tested it with a florid 10-min classical piano part weighing in at just shy of 5,000 notes and it chews through it in about 4s, but obviously, the slicker the better.
This seemed like an interesting thing to try as I'm just learning how to use the SetAllEvents method, and it seemed a relatively easy scenario for it. To compare I made an item laid out in blocks of notes for testing. 5000 exactly. Your script takes 24 seconds. Do you have a good computer? This one is kind of ragged out/spammed up. I made some simpler versions that just delete all notes above 60, which was 1/2 of the notes, to hopefully get a better idea of how they compare, since I didn't really want to rewrite the whole thing. The SetAllEvents version takes a little over two hundredths of a second. I made some modifications to the eel to try to speed it up a little, but the fastest I get is about 11 seconds.

Strangely the PreventUIRefesh makes a quite large difference. 3 seconds maybe? You should be sure of your script before using PreventUIRefresh. The script failed (edit.fail is wrong word. I guess it did what it is supposed to) for a strange reason the first time I ran it. I had the toggle action:
Options: Toggle pooled (ghost) MIDI source data when copying media items
enabled. You probably could check that with GetToggleCommandState, switch it off, then re-enable it at the end. MIDI scripts...

Code:
// delete notes over 60.eel

splitpoint = 60; /// Exclude splitpoint note in LH
 
//PreventUIRefresh(1);
tp1 = time_precise();

take = GetActiveTake(GetSelectedMediaItem(0,0));
MIDI_CountEvts(take, totalnotes, 0, 0);
counter = 0; // totalnotes-1;
loop(totalnotes,
  MIDI_GetNote(take, counter, 0, 0, 0, 0, 0, notepitch, 1);
  notepitch > splitpoint ? (
    MIDI_DeleteNote(take, counter); /// If above splitpoint remove note
  ):(
  counter+=1;
  );
);
MIDI_Sort(take);

tp2 = time_precise();
UpdateArrange();
//PreventUIRefresh(-1);
Undo_OnStateChange("eel delete notes over 60");
Code:
--delete notes over 60 SAE.lua

tp1=reaper.time_precise()
split = 60

take = reaper.GetActiveTake(reaper.GetSelectedMediaItem(0,0))
if take and reaper.TakeIsMIDI(take) then
  gotAllOK, MIDIstring = reaper.MIDI_GetAllEvts(take, "")
  MIDIlen = MIDIstring:len()  
  tableEvents = {}
  
  stringPos = 1 -- Position in MIDIstring while parsing through events.  
  while stringPos < MIDIlen do
      offset, flags, msg, stringPos = string.unpack("i4Bs4", MIDIstring, stringPos) 
      if msg:len() == 3 then
        mb1 = msg:byte(1)>>4
        if mb1 == 9 or mb1 == 8 then-- Note-on/off MIDI event type
          pitch = msg:byte(2)
          if pitch > split then msg = "" end
        end
      end
      table.insert(tableEvents, string.pack("i4Bs4", offset, flags, msg))
  end
  reaper.MIDI_SetAllEvts(take, table.concat(tableEvents))
end
reaper.UpdateArrange()
tp2=reaper.time_precise()

reaper.Undo_OnStateChange("lua delete notes over 60")

Last edited by FnA; 12-15-2017 at 10:51 PM.
FnA is offline   Reply With Quote
Old 12-16-2017, 05:50 AM   #3
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default

Quote:
Originally Posted by FnA View Post
This seemed like an interesting thing to try as I'm just learning how to use the SetAllEvents method, and it seemed a relatively easy scenario for it. To compare I made an item laid out in blocks of notes for testing. 5000 exactly. Your script takes 24 seconds. Do you have a good computer? This one is kind of ragged out/spammed up. I made some simpler versions that just delete all notes above 60, which was 1/2 of the notes, to hopefully get a better idea of how they compare, since I didn't really want to rewrite the whole thing. The SetAllEvents version takes a little over two hundredths of a second. I made some modifications to the eel to try to speed it up a little, but the fastest I get is about 11 seconds.

Strangely the PreventUIRefesh makes a quite large difference. 3 seconds maybe? You should be sure of your script before using PreventUIRefresh. The script failed (edit.fail is wrong word. I guess it did what it is supposed to) for a strange reason the first time I ran it. I had the toggle action:
Options: Toggle pooled (ghost) MIDI source data when copying media items
enabled. You probably could check that with GetToggleCommandState, switch it off, then re-enable it at the end. MIDI scripts...

Code:
// delete notes over 60.eel

splitpoint = 60; /// Exclude splitpoint note in LH
 
//PreventUIRefresh(1);
tp1 = time_precise();

take = GetActiveTake(GetSelectedMediaItem(0,0));
MIDI_CountEvts(take, totalnotes, 0, 0);
counter = 0; // totalnotes-1;
loop(totalnotes,
  MIDI_GetNote(take, counter, 0, 0, 0, 0, 0, notepitch, 1);
  notepitch > splitpoint ? (
    MIDI_DeleteNote(take, counter); /// If above splitpoint remove note
  ):(
  counter+=1;
  );
);
MIDI_Sort(take);

tp2 = time_precise();
UpdateArrange();
//PreventUIRefresh(-1);
Undo_OnStateChange("eel delete notes over 60");
Code:
--delete notes over 60 SAE.lua

tp1=reaper.time_precise()
split = 60

take = reaper.GetActiveTake(reaper.GetSelectedMediaItem(0,0))
if take and reaper.TakeIsMIDI(take) then
  gotAllOK, MIDIstring = reaper.MIDI_GetAllEvts(take, "")
  MIDIlen = MIDIstring:len()  
  tableEvents = {}
  
  stringPos = 1 -- Position in MIDIstring while parsing through events.  
  while stringPos < MIDIlen do
      offset, flags, msg, stringPos = string.unpack("i4Bs4", MIDIstring, stringPos) 
      if msg:len() == 3 then
        mb1 = msg:byte(1)>>4
        if mb1 == 9 or mb1 == 8 then-- Note-on/off MIDI event type
          pitch = msg:byte(2)
          if pitch > split then msg = "" end
        end
      end
      table.insert(tableEvents, string.pack("i4Bs4", offset, flags, msg))
  end
  reaper.MIDI_SetAllEvts(take, table.concat(tableEvents))
end
reaper.UpdateArrange()
tp2=reaper.time_precise()

reaper.Undo_OnStateChange("lua delete notes over 60")
Thanks for the suggestions.

PreventUIRefresh is a good idea anyway, since the order in which the script creates, edits and renames tracks is confusing til complete. If it gives a speed increase too, I'll take it without wondering why. I suspect it's probably because it prevents thousands of tiny redraws while the editing is going on.
Why is MIDISort necessary?
Can you also explain what the suggestion "// totalnotes-1" is for please.
The MIDI toggle change is useful error-trapping - that's a state I never use, so I'd never have noticed.

But if the SetAllEvents MIDI string parsing version really is that quick, then there's really no contest. Maybe I should migrate the whole thing to LUA anyway. I recall reading somewhere that one of the dev team is a total convert to it, and I suspect LUA documentation is rather easier to find than stuff on the bespoke EEL anyway.

I chose to work in EEL simply because it's apparently REAPER's native language. But if the EEL version gets junked, at least it will have served its purpose for indicating intention and methodology.

I'll have to go school on your code example though - I've never waded waist-deep into MIDI string format before.

Yeah, my laptop is a bit of a hotrod. OCed 6820K with 20GB DDR4 RAM and multi SSD storage, with all the W10 silliness stripped out. But I don't like inefficient code on principle, even if my hotrod can race through it.

Thanks again.
Jason Lyon is offline   Reply With Quote
Old 12-16-2017, 06:56 AM   #4
FnA
Human being with feelings
 
FnA's Avatar
 
Join Date: Jun 2012
Posts: 2,173
Default

I don't have any other programming experience besides ReaScript. The example is not to be taken as gospel for sure. As far as I know, juliansader is the only person who has posted any scripts that use the method and he was the main protagonist for an improvement over the older GetSet functions. The SWS functions were first actually. They can have some advantage over the GetSetMidiNote functions because they work on the information away from the take where as the Set note functions are more of a one at a time thing if that makes sense. Lol.

The //totalnotes-1 comment should have been deleted. Sorry. A remnant from trying the loop backwards. I don't know what the exact rules are for midisort, but notice also set the nosort equals true parameter to 1. Another speed experiment. I think it might have a slight advantage but not much.

PreventUIRefresh makes a huge difference in scripts that would otherwise draw a lot of changes. I think it might have some protection built in now but not sure. I did see what can happen when the script failed to reach the second of the pair which has the negative int. Not good. Whole screen became unresponsive etc. well I killed Reaper without saving and everything seemed ok after.

Eel is the same language used in jsfx so probably isn't going anywhere. It is also the fastest of the 3 main languages. It's advantage over lua is usually slight in comparison to lua over python. But it is known for limitations with strings and array type stuff. I find lua string handling to be an arcane art that is hard to find documentation for. So that is why I was reluctant to approach this method. But it turns out that the lines juliansader wrote that unpack an pack the information into a table can apparently be used as bread for a lot of different sandwiches. So maybe not too many other things to figure out other than midi structure. Best to look to his scripts for examples I think. But they are sort of intimidating in complexity in some ways I guess.

I was hoping some of the other more accomplished script writers would make more examples but it hasn't really caught on yet. It should. Set note sucks pretty much.
FnA is offline   Reply With Quote
Old 12-16-2017, 07:57 AM   #5
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default

Thanks again.

I think I will whack it all over to LUA. Given the speed advantage, it would be simpler than using hybrid code and importing functions, and if LUA's more flexible (yet arcane) string handling can be used in somewhat templated form it's worth it. And while I love super efficient code, I'm not going to sweat about shaving off a few CPU cycles here and there. I've a tendency to bloatphobia, but you still have to live in the real world.

Or as an old orchestration guru once told me: "don't sweat what can't be heard - concentrate on what can."

Anyway, I'm just dipping my webbed feet in the water here, but I enjoy learning on the job. I'm really just going on my rusty experience 30 years ago as a first-gen C programmer. I think I've still got a tattered copy of K&R somewhere.

Ah, those long-ago days when I had more brains and hair... When debugging involved printing out a spindle-fed concertina of paper, grabbing a pencil, making a pot of coffee and smoking yourself half to death for several hours... Then it still didn't bloody compile...

Used to mess around with assembler when I was even younger than that - but not to any great extent. Didn't go on to become a programmer - but the instinctive understanding of code structure has helped on quite a few occasions over the years.

During one stint in the City, somebody tried to convince me that COBOL was the future, but I didn't bite.

I worked on newspapers for years, and in one situation had the unique privilege that the IT department trusted me to uneff user-effed machines without their permission. Maverick fixer.

Cheers anyway, J
Merry Xmas, Chag Hannukah Sameach or whatever may float your goat. Or float your point.

PS Coder, Musician, Journalist - I think the only time I've ever followed a straight path in my life is when coding...
Jason Lyon is offline   Reply With Quote
Old 12-16-2017, 09:09 AM   #6
FnA
Human being with feelings
 
FnA's Avatar
 
Join Date: Jun 2012
Posts: 2,173
Default

Yeah, if I know some little string matching gizmo is faster than the other I will try to use it or starting at the end popping forward etc. There's more tips like that for python in my experience. But lua seems to be the popular choice now. Kind of a diy language, python having more built in functions. But slower and less compatible for various people and computers. More likely to get help with lua around here I think.
FnA is offline   Reply With Quote
Old 12-16-2017, 09:46 AM   #7
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default

The only other time I've come across LUA is in the context of XPlane, where some quite mighty things seem have been done with it. At first glance, it certainly seems more intuitively C-like than EEL.

Bit of a project on my hands to research LUA really - but that's no bad thing.

I've a feeling porting across the input handling and track manipulation to reconstruct the full function should be reasonably straightforward from here.

Might need to impose playback suspension or something to trap any code hang or interruption problems. Hey, if the LUA version can chew through 5,000 notes twice in about 5ms, I reckon that's a blip worth having to make sure.

Thanks again for your help.
Jason Lyon is offline   Reply With Quote
Old 12-16-2017, 10:57 AM   #8
FnA
Human being with feelings
 
FnA's Avatar
 
Join Date: Jun 2012
Posts: 2,173
Default

Best info on Reaper forum for lua string stuff etc that I can think of was by lokasenna. Google term maybe. Some specific tips for setallevents by juliansader on a post in general discussion section of forum front page where I am currently the last poster
FnA is offline   Reply With Quote
Old 12-17-2017, 03:50 AM   #9
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default FnA

Many thanks again for your help here, and I'm really making progress with LUA - happily my French is pretty passable, so Raymond Radet's videos are very useful. (I'm actually starting to now find myself talking to myself in French while coding...)
Unfortunately, it's time to rely on the kindness of strangers again...

I'm having arguments with my arguments.
The following stripped down excerpt is supposed to create two new duplicate tracks and extract the necessary ids from them to then use to parse the contents.
All the Gets and Sets I've tried seem to want to use the short track IDs (ie in project order 0 through n), rather than reference specific items by long ID number.
Just porting the EEL over doesn't work as before, presumably because I'm no longer (and glad of it) working with a constantly refreshing GUI so the previous "natural" track selection doesn't apply.
Another related problem is that calling the REAPER action 40062 seems to create a track at the top of the TCP, rather than under the selected one, as desired. (The 1 param is to duplicate properties, routing and contents.)
I'd be very grateful for anything you could suggest.

- - -

origtrackid = reaper.GetSelectedMediaItem(0,0)
origtakeid = reaper.GetActiveTake(origtrackid)

--create LH track
reaper.Main_OnCommand(40062, 1)
LHtrackid = ??Get
LHtakeid = ??Get

--code to process LH track goes in here

--relocate to orig and create RH track
??Set
reaper.Main_OnCommand(40062, 1)
RHtrackid = ??Get
RHtakeid = ??Get

-- code to process RH track goes in here

Last edited by Jason Lyon; 12-17-2017 at 03:57 AM.
Jason Lyon is offline   Reply With Quote
Old 12-17-2017, 05:56 AM   #10
FnA
Human being with feelings
 
FnA's Avatar
 
Join Date: Jun 2012
Posts: 2,173
Default

First of all, is the overall process of the script what you really want for a perfect scenario? Or did you compromise because of things you were able to find? For example, would it be just as good to make two child tracks that route their midi to the parent so you don't have to duplicate all the fx etc? That will probably be slow on my laptop too. Posting from phone now.
FnA is offline   Reply With Quote
Old 12-17-2017, 06:12 AM   #11
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default

The child approach could work well as default behaviour.
I'd originally chosen this approach because you might want to reroute eg the LH to a bass, or LH to trombones and RH to trumpets. To do that, I think you'd then have to unchild the tracks and rework them. But as I say - rerouted child tracks would probably be good default behaviour. It would also save time spent cloning of course.
It's just the various Selects/Gets/Sets that are foxing me. Apologies again. Leave it for now - I'll chew it and try to find my way.
It's probably quite simple - but then everything is when you know how to do it...

What would really help me out though is a way to translate between short (GUI order) and long (full shebang) IDs.

Last edited by Jason Lyon; 12-17-2017 at 07:23 AM.
Jason Lyon is offline   Reply With Quote
Old 12-17-2017, 07:23 AM   #12
FnA
Human being with feelings
 
FnA's Avatar
 
Join Date: Jun 2012
Posts: 2,173
Default

GetSelectedMediaItem (0,0) gets the pointer to the first selected media item in the project. You can use GetMediaItemTrack with that pointer to find the track (pointer) it is on.

But it looks like you want to use the first item on a selected track in your script. For the most part I think that should be portable over to lua. But I think you will get a nil error the way you use "proj" variable. Eel fills in the blanks with 0 some times. Lua won't. 0 is the active project tab usually.

I am not familiar with using 1 in MainOnCommand. I think it would work with 0.

Use a code block when posting on the forum. Like so:
{code}
yourcodehere
{/code}
But with square brackets instead of curly.

Last edited by FnA; 12-17-2017 at 07:30 AM.
FnA is offline   Reply With Quote
Old 12-17-2017, 06:45 PM   #13
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default

Thanks again for all your help, it's really appreciated.

You're right - that MainOnCommand call doesn't seem to care what second argument you give it. There's no info that I can find, and I've tried a few suck'n'see variations - no difference. I suspect the second parameter is a dummy slot - which might make sense, since it's performing a one-shot REAPER action that doesn't actually seem to have any options attached. Well, unless there are some very specific Preference Settings, perhaps.

I've also decided that while I'm trying to wrap my head around addressing at the Lua level and get it really whirring for possible future development, I'll use a tightened up version of the EEL script, since it at least works.

Suspending the display refresh does indeed make a noticeable difference. And for now, it doesn't need real-time usability.

Copying a VSTi instance doesn't actually seem to make much difference. Or at least given my current methodology - I've noticed that full-track cloning seems to be almost instantaneous anyway. As you've helped me to understand, that isn't what's taking the time and of course it's orders of magnitude more efficient to parse the MIDI at a string level...

I have adapted it to end up as muted parent with children anyway, just because it's tidier.

Speaking of time, I've adopted your testing idea of effectively embedding a stopwatch in the code and I've noticed something rather interesting. The reported values routinely report a duration of 8-10s or so from user input to done. Yet running the script actually takes about 3-4s. I know that better can be achieved, but personally, I can live with this for now. (This is actually something I intend to use - in fact, it's already come in handy.)

Maybe it's something to do with my souped-up system, I dunno.

Anyway, time is short and I will keep chewing on this. As for now, Xmas gigs are piling up and I need to get organising - or to be more precise, get organising everyone else... the state I may happen to be in on the given nights is a secondary consideration. Just got back from one tonight actually, and I'm a bit ragged already.

So thanks for taking the time. You've certainly introduced me to a completely different level of possibilities.

Last edited by Jason Lyon; 12-17-2017 at 07:19 PM.
Jason Lyon is offline   Reply With Quote
Old 12-17-2017, 08:26 PM   #14
FnA
Human being with feelings
 
FnA's Avatar
 
Join Date: Jun 2012
Posts: 2,173
Default

Quote:
Originally Posted by Jason Lyon View Post
What would really help me out though is a way to translate between short (GUI order) and long (full shebang) IDs.
GetMediaItem(0,35) gets the longer "pointer" to 36th media item in project. Left to right then on to next track. In 0/active project.
GetSelectedMediaItem(2,23) gets 24th SELECTED media item in same way. In 2nd project tab which may or may not also be active.

GetTrack/SelectedTrack similar, but look at help menu docs for master.

GetTake starts at each item.
GetActiveTake gets that without counting.


If you need to count what number of items is before a selected one
GetMediaItem
IsMediaItemSelected
combo is pretty fast alternative way to GetSelectedMediaItem.

reaper.GetMediaItemInfo_Value(MediaItem item, "IP_ITEMNUMBER")
can give you the item number on track. This is a pretty important function with a lot of different strings you can enter as parameter.

reaper.GetMediaTrackInfo_Value(MediaTrack tr, string parmname) is the one for tracks. These have a Set counterpart for a lot of the parameters.

Another ID is the GUIDs for tracks,items, and takes. Those are like other info that gets stored in each track, etc. I think pointers change each time you start the program or maybe load the project. I think it might figure in when cut/pasting and some other things.

CONTROL+F is your friend in that document after you get the hang of the lingo.

There should be a number of examples floating around, or you can install ReaPack, which has hundreds.

You can filter out that gfx stuff in the IDE window with NOT gfx NOT mouse to keep an eye on the stuff a little easier.

Turns out there may be a bug in deleting MIDI.
https://forum.cockos.com/showthread.php?t=200877
FnA is offline   Reply With Quote
Old 12-17-2017, 09:04 PM   #15
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default

Thanks again, and I'll investigate those options.

Interesting very recent hot reporting of potential bug re MIDI deletes.
Don't know if it's a related issue, but I have noticed that REAPER can happily truck along chattering away with dozens of VSTis, but sometimes when a MIDI Editor window is just open, simply displaying with no user fiddling, it gets poppy.

Knowing what the guys are like for bug fixes and updates, I think I'll hang fire on what I'm trying to do for now. Of course, I'll continue to pursue it over time as a learning exercise.

If my dopey experiments have helped in any small way to flag up a flaw and possible improvement to the software, I'll be happy enough anyway.

Here, just for info is the full ugly version that I know works. It's gash, macroish and much slower that I know it can be, but it works. No feedback necessary - I've just put it up for info.

Code:
/* EEL script for REAPER v5+
 * Jason Lyon Dec 2017
 */

/// This script is designed to take a selected track containing a MIDI piano/keyboard part 
/// and split it into RH and LH parts at the user's chosen note, without destroying the original.
/// It will create two tracks directly below the original one, parse them into RH and LH parts
/// and then mute the original track, but return focus to the original.
/// The new RH and LH tracks will be complete clones of the original.
/// Both new tracks will also retain all MIDI CC data, whether relevant to their altered content or not.
/// It's reasonably error-trapped - won't do anything if there's no track selected, or there's
/// no MIDI content on the selected track, or you enter garbage as the split point. No user error reporting though.

function PnoSplitter(proj)
 
(
 
 Undo_BeginBlock();

 PreventUIRefresh(1);
 
 trackid = GetSelectedTrack(proj, 0);
 mediaid = GetTrackMediaItem(trackid, 0);
 takeid = GetActiveTake(mediaid);
 GetSetMediaTrackInfo_String(trackid, "P_NAME", #origname, 0);

 TakeIsMIDI(takeid) ? ( /// Do nothing further if no MIDI on selected track
 
/// Get input

 GetUserInputs("MIDI Piano Splitter", 1, "Enter Split Note (middle C is 60)", #input);
 tp1 = time_precise();

 match("%u", #input, splitpoint);
 splitpoint !=0 ? (splitpoint < 127 ? ( /// Do nothing further if Split Point is not integer 1-126

/// Create LH track

 Main_OnCommand(40062, 0); 
 LHtrackid = GetSelectedTrack(proj, 0);
 LHmediaid = GetTrackMediaItem(LHtrackid, 0);
 LHtakeid = GetActiveTake(LHmediaid);
 #LHname =  strcat(#origname, " LH");
 GetSetMediaTrackInfo_String(LHtrackid, "P_NAME", #LHname, 1);
  
/// Run LH processing

 splitpoint -= 1; /// Exclude splitpoint note in LH

 MIDI_CountEvts(LHtakeid, totalnotes, 0, 0);
 counter = 0;

 loop(totalnotes,
  MIDI_GetNote(LHtakeid, counter, 0, 0, 0, 0, 0, notepitch, 1);
  notepitch > splitpoint ? (
   MIDI_DeleteNote(LHtakeid, counter);
   ):(
   counter += 1;
   );
  );

/// Relocate to master track

 SetOnlyTrackSelected(trackid);

/// Create RH track (above LH one)

 Main_OnCommand(40062, 0);
 RHtrackid = GetSelectedTrack(proj, 0);
 RHmediaid = GetTrackMediaItem(RHtrackid, 0);
 RHtakeid = GetActiveTake(RHmediaid);
 
 GetSetMediaTrackInfo_String(trackid, "P_NAME", #origname, 0);
 #RHname =  strcat(#origname, " RH");
 GetSetMediaTrackInfo_String(RHtrackid, "P_NAME", #RHname, 1);
 
/// Run RH processing

 splitpoint += 1; /// Correct from before to include splitpoint note in RH
  
 MIDI_CountEvts(RHtakeid, totalnotes, 0, 0);
 counter = 0;

 loop(totalnotes,
  MIDI_GetNote(RHtakeid, counter, 0, 0, 0, 0, 0, notepitch, 1);
  notepitch < splitpoint ? (
   MIDI_DeleteNote(RHtakeid, counter);
   ):(
   counter += 1;
   );
  ); 

/// Relocate to master track and mute MIDI item on it

 SetOnlyTrackSelected(trackid);
 SetMediaItemInfo_Value(mediaid, "B_MUTE", 1);
 
/// Unnesting for do nothing conditions
 )
 )
 );
 
 UpdateArrange();
 PreventUIRefresh(-1);
 
 tp2 = time_precise();
 Undo_EndBlock(0,0);
 
 ); 
 
 PnoSplitter();
Jason Lyon is offline   Reply With Quote
Old 12-18-2017, 12:43 AM   #16
hopi
Human being with feelings
 
hopi's Avatar
 
Join Date: Oct 2008
Location: Right Hear
Posts: 15,618
Default

Thanks Jason.... well gosh... even IF the eel is slow, it's not that slow IMHO... anyway... what's the rush? we are all moving too fast these days anyway.... LoL

I'd be it will show up as a lua before too long..
__________________
...should be fixed for the next build... http://tinyurl.com/cr7o7yl
https://soundcloud.com/hopikiva
hopi is offline   Reply With Quote
Old 12-18-2017, 07:40 AM   #17
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default hopi

Thanks for taking an interest.

To reiterate some points made further up - my system is a very chunky i7 laptop and the latest EEL version can chew through 5,000 notes twice in about 3-4s.

FnA's Lua string manipulation version for just the LH processing works virtually instantaneously. I have a WIP Lua version of the whole thing, but I'm really struggling with the track/item/take copying and addressing. Since EEL seems to effectively be telling REAPER what to do on the level the user would, these issues are more easily solved.

Things could be tightened up in EEL with some nesting, but while it's in development I think doing a then b then c = a*b is clearer for now. And I'm not really sure that nesting would provide much of a performance improvement anyway. It's the chewing that takes the time.

Just noticed that as it stands, the action leaves all MIDI items selected - open one in the MIDI editor and you open all of them. Must fix that.

Also, while it'll copy all MIDI items on a selected track, it currently only processes the first one. Need to fix that too.

Anyway, it's all fun - if you find this sort of thing fun...
Jason Lyon is offline   Reply With Quote
Old 12-18-2017, 07:58 AM   #18
hopi
Human being with feelings
 
hopi's Avatar
 
Join Date: Oct 2008
Location: Right Hear
Posts: 15,618
Default

Thanks for the details Jason.... I'm in no way a code guy... even for scripting... the first and last time I wrote any code was in basic about 30 years ago and it was definitely not fun for me... some people are good at such things and others, like myself, as just not wired for it... oh well.

I do find what the script does interesting. I was playing with from a diff perspective... in that it can split any given track into two tracks at a given note...
so one can use it to split and then pick one of those tracks and split again at a diff note...
I also did it all by hand, via the midi filter operating on two tracks that are copies of the original... certainly slower than a script but not too bad really.

anyway... I encourage you to keep going
__________________
...should be fixed for the next build... http://tinyurl.com/cr7o7yl
https://soundcloud.com/hopikiva
hopi is offline   Reply With Quote
Old 12-18-2017, 08:31 AM   #19
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default

Quote:
Originally Posted by hopi View Post
Thanks for the details Jason.... I'm in no way a code guy... even for scripting... the first and last time I wrote any code was in basic about 30 years ago and it was definitely not fun for me... some people are good at such things and others, like myself, as just not wired for it... oh well.

I do find what the script does interesting. I was playing with from a diff perspective... in that it can split any given track into two tracks at a given note...
so one can use it to split and then pick one of those tracks and split again at a diff note...
I also did it all by hand, via the midi filter operating on two tracks that are copies of the original... certainly slower than a script but not too bad really.

anyway... I encourage you to keep going
I likewise encourage you to have a look at coding. The functions and syntax can be perplexing, but if you've had even kiddie experience of BASIC, you're familiar with the process already. It mostly boils down to "I have to do A and B before I can do C".

Of course, there's nearly always a more efficient and faster way of doing things - and we strive for that. But before you get familiar with the tricks and quirks, particularly with modern hardware, if your code is a bit flabby it makes a few microseconds difference. As long as it works.

First step would be cobbling together lists of actions and saving the whole as a new custom action. But that's not really coding yet - it is in a sense, but it's like making macros.

EEL is REAPER's native tool for doing a similar thing - just now you're able to sort of get inside the items and manipulate them the way a user would (eg "I can see that track is called "Drums OH", so I'll select it, do something with it and rename it). Pros - it's a natural lead-on from the macroing approach. Cons - some of its syntax is a bit unintuitive to a newcomer and there are some things it can't do very well, or at all.

Lua allows you to do just about anything to anything, within REAPER and even in interaction with other programs. Pros - it's friendlier to read for humans. Cons - it's so damn flexible that you need to be really precise.

Python I'd leave alone. It does have its positives, but a newcomer can frustrate themselves into the ground just setting up the compiler on their system before they get to the joys of printing out "Hello world".

The main problem with all of them is getting hold of and understanding the documentation. But there are good resources out there - including plenty of examples. Since the early days of the C language, the golden rule of all programming is "don't reinvent the wheel, just go and get one".

But ultimately, just set up a test REAPER project with unimportant content and try things out, achieving some wins along the way, screwing up sometimes, hanging the system occasionally and having to kill REAPER by force, but hopefully getting closer and closer to what you want to achieve. And this is a very good community - don't be afraid to ask for help.
Jason Lyon is offline   Reply With Quote
Old 12-18-2017, 08:57 PM   #20
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default New version

More error trapping built in, stripped down a bit and will now process all MIDI items found on the selected track.

Code:
 /* EEL script for REAPER v5+
  * Jason Lyon Dec 2017
  */
 
 // This script is designed to take a selected track containing a MIDI piano/keyboard part and split it into RH and 
 // LH parts at the user's chosen note, without destroying the original.
 // It will create two tracks directly below the original one, parse them into RH and LH parts (all MIDI items will 
 // be parsed), then mute all items on the original track and finally return user focus to where it was.
 // The new RH and LH tracks are cloned from the original, so will have the same levels, panning, FX, VSTis, routing. etc.
 // Both new tracks will also retain all MIDI CC data from the original, whether relevant to their parsed content or not.
 // It's reasonably error-trapped - won't do anything if there's no MIDI content on the selected track, or you enter 
 // garbage as the split point. It makes no special provision for any audio items that happen to be on the selected track - 
 // they'll be copied to both RH and LH tracks, but obviously not processed.
 // No user error reporting, but it's one-shot undoable.
 // This is a blunt approach, and the task is possible a lot faster with deep-level MIDI string parsing in Lua, but this 
 // will do the job for now.
 // The timing is difficult to rely on, since my system is rigged rather specially. But on a modern i7 this script 
 // should process a track with 4 MIDI items containing 5,000 notes in total in about 8s.
 // There has recently been a reported potential slowdown/hang bug in REAPER's native MIDI deletion on version 5.70, 
 // so in the event that this issue gets fixed in a later release, this script might execute faster.
  
  Undo_BeginBlock();
 
  PreventUIRefresh(1);
  
  // original track info gathering

  trackid = GetSelectedTrack(0, 0);
  takeid = GetActiveTake(GetTrackMediaItem(trackid, 0));
  numofitems = GetTrackNumMediaItems(trackid);
 
  containsmidi = 0;
  itemcounter = 0; 
  while (itemcounter < numofitems) ( // begin to loop through all items
   workingitemid = GetActiveTake(GetTrackMediaItem(trackid, itemcounter));
   TakeIsMIDI(workingitemid) ? containsmidi = 1;
   itemcounter += 1;
   );
   
   containsmidi ? ( // do nothing further if no MIDI on selected track
  
  // get input
 
  GetUserInputs("MIDI Piano Splitter", 1, "Enter Split Note (middle C is 60)", #input);
  match("%u", #input, splitpoint);
  splitpoint !=0 ? (splitpoint < 127 ? ( // do nothing further if split point is not integer 1-126
 
  // timerstart = time_precise(); //uncomment this to time the script
 
 SelectAllMediaItems(proj, 0);
 
  // create and name LH track
 
  Main_OnCommand(40062, 0); 
  workingtrackid = GetSelectedTrack(proj, 0);
  GetSetMediaTrackInfo_String(trackid, "P_NAME", #name, 0);
  GetSetMediaTrackInfo_String(workingtrackid, "P_NAME", strcat(#name, " LH"), 1);
  
  splitpoint -= 1; // exclude splitpoint note in LH
  itemcounter = 0; 
  while (itemcounter < numofitems) ( // begin to loop through all MIDI items
   workingitemid = GetActiveTake(GetTrackMediaItem(workingtrackid, itemcounter));
   
   // run LH processing 
 
   MIDI_CountEvts(workingitemid, totalnotes, 0, 0);
   counter = 0;
   loop(totalnotes,
    MIDI_GetNote(workingitemid, counter, 0, 0, 0, 0, 0, notepitch, 1);
    notepitch > splitpoint ? (
     MIDI_DeleteNote(workingitemid, counter);) : (
     counter += 1;
    );
   );
   itemcounter += 1);
 
  // relocate to master track
 
  SetOnlyTrackSelected(trackid);
 
  // create and name RH track (above LH)
 
  Main_OnCommand(40062, 0); 
  workingtrackid = GetSelectedTrack(proj, 0);
  GetSetMediaTrackInfo_String(trackid, "P_NAME", #name, 0);
  GetSetMediaTrackInfo_String(workingtrackid, "P_NAME", strcat(#name, " RH"), 1);
  
  splitpoint += 1; // include splitpoint note in RH
  itemcounter = 0; 
  while (itemcounter < numofitems) ( // begin to loop through all MIDI items
   workingitemid = GetActiveTake(GetTrackMediaItem(workingtrackid, itemcounter));
   
   // run RH processing
 
   MIDI_CountEvts(workingitemid, totalnotes, 0, 0);
   counter = 0;
   loop(totalnotes,
    MIDI_GetNote(workingitemid, counter, 0, 0, 0, 0, 0, notepitch, 1);
    notepitch < splitpoint ? (
     MIDI_DeleteNote(workingitemid, counter);) : (
     counter += 1;
     );
    );
   itemcounter += 1);
 
  // relocate to master track, mute MIDI items on it and reset active selection
 
  itemcounter = 0; 
  while (itemcounter < numofitems) ( // begin to loop through all MIDI items
   SetMediaItemInfo_Value(GetTrackMediaItem(trackid, itemcounter), "B_MUTE", 1);
   itemcounter += 1);
  
  SetOnlyTrackSelected(trackid);
  
  // unnesting for "do nothing" conditions
  )
  )
  );
  
  UpdateArrange();
  PreventUIRefresh(-1);  
  
  Undo_EndBlock(0,0);
  
  // timerend = time_precise(); //uncomment this to time the script

Last edited by Jason Lyon; 12-19-2017 at 07:06 AM.
Jason Lyon is offline   Reply With Quote
Old 12-20-2017, 09:25 AM   #21
hopi
Human being with feelings
 
hopi's Avatar
 
Join Date: Oct 2008
Location: Right Hear
Posts: 15,618
Default

thanks for the encouragement about coding..... who knows, maybe I'll try it someday

anyway got your updated version and certainly works fast enough for me...

even though it's not a big deal, I could imagine it handy to operate on only the selected items of a given track

of course it is easy enough to simple pick the spit items and delete the items you don't want...

I realize you are thinking more about whole and long midi tracks that are likely mostly one long item.... still I could imagine it being even more useful if it worked on only selected items of a track....
__________________
...should be fixed for the next build... http://tinyurl.com/cr7o7yl
https://soundcloud.com/hopikiva
hopi is offline   Reply With Quote
Old 12-20-2017, 12:51 PM   #22
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default

Quote:
Originally Posted by hopi View Post
thanks for the encouragement about coding..... who knows, maybe I'll try it someday

anyway got your updated version and certainly works fast enough for me...

even though it's not a big deal, I could imagine it handy to operate on only the selected items of a given track

of course it is easy enough to simple pick the spit items and delete the items you don't want...

I realize you are thinking more about whole and long midi tracks that are likely mostly one long item.... still I could imagine it being even more useful if it worked on only selected items of a track....
Actually, I did start off thinking about whole and long MIDI tracks, because I wanted a way to work on piano parts exported from Sibelius. They come in great big unbroken chunks.

But then I realised that that isn't a typical way of working (sometimes not even for me) - it's far more common for people to chop, copy and move multiple items on a track. And things could get really confusing if you've split some items and not others.

But maybe something for the future. To be honest, I can see it being used for:
Piano parts (mostly at middle C) - it could be convenient for viewing or for exporting, some keyboards have funny weighting, some sampled instruments have funny balance and sometimes you just want to be able to give a little more or less emphasis to one hand here and there;
Occasions when you've performed bass in LH and piano in RH (at any obvious split point) - eg a typical jazz accompaniment with walking bass and chords - you might want to use different bass and keyboard samples;
Horn situations where you've maybe played a generic brass part and want something like bones in LH and tpts in RH (again at any obvious split point).

Actually, it might be quite cute to have the thing search through your selected item(s) and suggest an obvious split point if it can find one - in many situations there's at least one note between the hands that doesn't get played and there's the suggested split point. I'd really have to move to the Lua string manipulation and array handling approach for that though.

Last edited by Jason Lyon; 12-20-2017 at 12:58 PM.
Jason Lyon is offline   Reply With Quote
Old 01-14-2018, 03:33 PM   #23
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default Update

Error trapping and accurate undo reporting added.

Code:
--[[
 * ReaScript in Lua: Create two new tracks and split all MIDI takes from original into RH and LH on them at requested note. 
 * Original track is retained for safety but muted.
 * New RH and LH tracks are clones of original so inherit all its properties, including routing and plugin instances.
 * Instructions: Select track and execute script
 * Author: Jason Lyon
 * Uses code by XRaym and FnA
 * REAPER: 5.70
 * Extensions: None
 * Version: 1.3 2018-01-14
--]]

-- declare vars
local r = reaper
local trackid, numofitems, takeid, notcancelled, input, splitpoint
local LHtrackid, LHtakeid, RHtrackid, RHtakeid
local retval, trackname, itemcounter, workingitemid, split, MIDIstring, MIDIlen, allevents, stringPos, offset, flags, msg, mb1, pitch  

r.PreventUIRefresh(1)

if r.CountSelectedTracks(0) == 1 then -- error trap 1: continue if only one track is selected

-- info gathering from original track
trackid = r.GetSelectedTrack2(0, 0, 1)  
numofitems = r.GetTrackNumMediaItems(trackid)  

if numofitems > 0 then -- error trap 2: continue if items exist
 
takeid = r.GetActiveTake(r.GetTrackMediaItem(trackid, 0))  

if r.TakeIsMIDI(takeid) then -- error trap 3: continue if first item is MIDI

 -- get user input   
 notcancelled, input = r.GetUserInputs("MIDI Piano Splitter", 1, "Enter Split Note (middle C is 60)", "60")
if notcancelled and tonumber(input) then splitpoint = tonumber(input) else splitpoint = 0 end

if splitpoint > 0 and splitpoint < 127 then -- error trap 4: continue if user input valid

r.Undo_BeginBlock()
    
  -- create LH track
  r.Main_OnCommand(40062, 0) 
  LHtrackid = r.GetSelectedTrack(0, 0)
  LHtakeid = r.GetActiveTake(r.GetTrackMediaItem(LHtrackid, 0))
  retval, trackname = r.GetSetMediaTrackInfo_String(trackid, "P_NAME", "", false)
  r.GetSetMediaTrackInfo_String(LHtrackid, "P_NAME", trackname .. " — LH", true)  
    
  -- start loop to run through all MIDI items
  itemcounter = 0
  while (itemcounter < numofitems) do
   workingitemid = r.GetActiveTake(r.GetTrackMediaItem(LHtrackid, itemcounter))
    
   -- LH processing, delete notes over chosen point
   split = splitpoint - 1      
   retval, MIDIstring = r.MIDI_GetAllEvts(workingitemid, "")
   MIDIlen = MIDIstring:len()  
   allevents = {}
            
   stringPos = 1 -- position in MIDIstring while parsing through events 
   while stringPos < MIDIlen do
    offset, flags, msg, stringPos = string.unpack("i4Bs4", MIDIstring, stringPos) 
    if msg:len() == 3 then mb1 = msg:byte(1)>>4
     if mb1 == 9 or mb1 == 8 then pitch = msg:byte(2)
      if pitch > split then msg = "" end
     end
    end
    table.insert(allevents, string.pack("i4Bs4", offset, flags, msg))
   end
  r.MIDI_SetAllEvts(workingitemid, table.concat(allevents))
   
  itemcounter = itemcounter + 1
  end
      
  -- relocate to orig
  r.SetOnlyTrackSelected(trackid)
    
  -- create RH track
  r.Main_OnCommand(40062, 0) 
  RHtrackid = r.GetSelectedTrack(0, 0)
  RHtakeid = r.GetActiveTake(r.GetTrackMediaItem(RHtrackid, 0))
  retval, trackname = r.GetSetMediaTrackInfo_String(trackid, "P_NAME", "", false)
  r.GetSetMediaTrackInfo_String(RHtrackid, "P_NAME", trackname .. " — RH", true)  
    
  -- start loop to run through all MIDI items
  itemcounter = 0
  while (itemcounter < numofitems) do
   workingitemid = r.GetActiveTake(r.GetTrackMediaItem(RHtrackid, itemcounter))
    
   -- RH processing, delete notes below chosen point
   split = splitpoint   
   retval, MIDIstring = r.MIDI_GetAllEvts(workingitemid, "")
   MIDIlen = MIDIstring:len()  
   allevents = {}
            
   stringPos = 1 -- position in MIDIstring while parsing through events 
   while stringPos < MIDIlen do
    offset, flags, msg, stringPos = string.unpack("i4Bs4", MIDIstring, stringPos) 
    if msg:len() == 3 then mb1 = msg:byte(1)>>4
     if mb1 == 9 or mb1 == 8 then -- note-on/off MIDI event type
      pitch = msg:byte(2)
      if pitch < split then msg = "" end
     end
    end
    table.insert(allevents, string.pack("i4Bs4", offset, flags, msg))
   end
  r.MIDI_SetAllEvts(workingitemid, table.concat(allevents))
    
  itemcounter = itemcounter + 1
  end
    
  -- relocate to orig and mute
  r.SetOnlyTrackSelected(trackid)
  r.SetMediaTrackInfo_Value(trackid, "B_MUTE", 1)
    
r.Undo_EndBlock("Split MIDI track into RH and LH tracks", 0)    
end end end end -- close error traps

r.PreventUIRefresh(-1)
    
r.UpdateArrange()

Last edited by Jason Lyon; 01-15-2018 at 07:18 AM.
Jason Lyon is offline   Reply With Quote
Old 01-21-2018, 03:40 AM   #24
Jason Lyon
Human being with feelings
 
Join Date: May 2016
Posts: 720
Default

New version. Will now accept user input of either eg "d#4" or "63".

Code:
--[[
 * ReaScript in Lua: Create two new tracks and split all MIDI takes from original into RH and LH on them at requested note. 
 * Original track is retained for safety but muted.
 * New RH and LH tracks are clones of original so inherit all its properties, including routing and plugin instances.
 * Instructions: Select track and execute script
 * Author: Jason Lyon
 * Uses code by XRaym and FnA
 * REAPER: 5.70
 * Extensions: None
 * Version: 1.4 2018-01-21
--]]

-- declare vars
local r = reaper
local trackid, numofitems, takeid, notcancelled, input, splitpoint
local LHtrackid, LHtakeid, RHtrackid, RHtakeid
local retval, trackname, itemcounter, workingitemid, split, MIDIstring, MIDIlen, allevents, stringPos, offset, flags, msg, mb1, pitch  

r.PreventUIRefresh(1)

if r.CountSelectedTracks(0) == 1 then -- error trap 1: continue if only one track is selected

-- info gathering from original track
trackid = r.GetSelectedTrack2(0, 0, 1)  
numofitems = r.GetTrackNumMediaItems(trackid)  

if numofitems > 0 then -- error trap 2: continue if items exist
 
takeid = r.GetActiveTake(r.GetTrackMediaItem(trackid, 0))  

if r.TakeIsMIDI(takeid) then -- error trap 3: continue if first item is MIDI

 -- get user input   
 notcancelled, input = r.GetUserInputs("MIDI Piano Splitter", 1, "Enter Split Note (C4 or 60)", "C4")
 
 -- function: convert possible text input to MIDI note
 local inputlen = string.len(input), notename, octave
      
 if inputlen == 2 and string.find(input, "%a%d") then -- input is of form eg "W2"
  notename = string.sub(input, 1, 1)
  octave = tonumber(string.sub(input, 2, 2)) end
 if inputlen == 3 and string.find(input, "%a[#]%d") then -- input is of form eg "j#8"
  notename = string.sub(input, 1, 2)
  octave = tonumber(string.sub(input, 3, 3)) end
 if notename then -- function error trap 1: continue if input is confirmed as above
 
 -- declare vars
 local octavetable, i, noteval
 
 notename = string.upper(notename)
 octavetable = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}
 for i = 1, 12 do
  if notename == octavetable[i] then noteval = i - 1 end
 end
  
 if noteval then -- function error trap 2: continue if note name matches a table value
 input = (octave+1)*12 + noteval 
 
 end end --- close function error traps
 
if notcancelled and tonumber(input) then splitpoint = tonumber(input) else splitpoint = 0 end

if splitpoint > 0 and splitpoint < 127 then -- error trap 4: continue if user input valid

r.Undo_BeginBlock()
    
  -- create LH track
  r.Main_OnCommand(40062, 0) 
  LHtrackid = r.GetSelectedTrack(0, 0)
  LHtakeid = r.GetActiveTake(r.GetTrackMediaItem(LHtrackid, 0))
  retval, trackname = r.GetSetMediaTrackInfo_String(trackid, "P_NAME", "", false)
  r.GetSetMediaTrackInfo_String(LHtrackid, "P_NAME", trackname .. " — LH", true)  
    
  -- start loop to run through all MIDI items
  itemcounter = 0
  while (itemcounter < numofitems) do
   workingitemid = r.GetActiveTake(r.GetTrackMediaItem(LHtrackid, itemcounter))
    
   -- LH processing, delete notes over chosen point
   split = splitpoint - 1      
   retval, MIDIstring = r.MIDI_GetAllEvts(workingitemid, "")
   MIDIlen = MIDIstring:len()  
   allevents = {}
            
   stringPos = 1 -- position in MIDIstring while parsing through events 
   while stringPos < MIDIlen do
    offset, flags, msg, stringPos = string.unpack("i4Bs4", MIDIstring, stringPos) 
    if msg:len() == 3 then mb1 = msg:byte(1)>>4
     if mb1 == 9 or mb1 == 8 then pitch = msg:byte(2)
      if pitch > split then msg = "" end
     end
    end
    table.insert(allevents, string.pack("i4Bs4", offset, flags, msg))
   end
  r.MIDI_SetAllEvts(workingitemid, table.concat(allevents))
   
  itemcounter = itemcounter + 1
  end
      
  -- relocate to orig
  r.SetOnlyTrackSelected(trackid)
    
  -- create RH track
  r.Main_OnCommand(40062, 0) 
  RHtrackid = r.GetSelectedTrack(0, 0)
  RHtakeid = r.GetActiveTake(r.GetTrackMediaItem(RHtrackid, 0))
  retval, trackname = r.GetSetMediaTrackInfo_String(trackid, "P_NAME", "", false)
  r.GetSetMediaTrackInfo_String(RHtrackid, "P_NAME", trackname .. " — RH", true)  
    
  -- start loop to run through all MIDI items
  itemcounter = 0
  while (itemcounter < numofitems) do
   workingitemid = r.GetActiveTake(r.GetTrackMediaItem(RHtrackid, itemcounter))
    
   -- RH processing, delete notes below chosen point
   split = splitpoint   
   retval, MIDIstring = r.MIDI_GetAllEvts(workingitemid, "")
   MIDIlen = MIDIstring:len()  
   allevents = {}
            
   stringPos = 1 -- position in MIDIstring while parsing through events 
   while stringPos < MIDIlen do
    offset, flags, msg, stringPos = string.unpack("i4Bs4", MIDIstring, stringPos) 
    if msg:len() == 3 then mb1 = msg:byte(1)>>4
     if mb1 == 9 or mb1 == 8 then -- note-on/off MIDI event type
      pitch = msg:byte(2)
      if pitch < split then msg = "" end
     end
    end
    table.insert(allevents, string.pack("i4Bs4", offset, flags, msg))
   end
  r.MIDI_SetAllEvts(workingitemid, table.concat(allevents))
    
  itemcounter = itemcounter + 1
  end
    
  -- relocate to orig and mute
  r.SetOnlyTrackSelected(trackid)
  r.SetMediaTrackInfo_Value(trackid, "B_MUTE", 1)
    
r.Undo_EndBlock("Split MIDI track into RH and LH tracks", 0)    
end end end end -- close error traps

r.PreventUIRefresh(-1)
Jason Lyon is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 06:00 AM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.