|
|
|
07-18-2018, 08:30 AM
|
#1
|
Human being with feelings
Join Date: Oct 2017
Location: Black Forest
Posts: 5,054
|
Adapting my "nudge notes" script to Julian Sader's method
Hey all!
I'm about to adapt all my MIDI scripts to Julian's method (Get/SetAllEvts), since it's more efficient with huge amounts of MIDI data.
However, I'm having some issues, when there are other events like channel pressure, program changes, text events, notation events...
When nuding the notes, they just disappear. Apparently they are not captured, when getting the channel message, since they only contain the midi notes and control changes. Is there an elegant way to include these values as well and re-write them to the take?
Code:
function nudgenotes(newPosition)
for i = 0, reaper.CountSelectedMediaItems(0)-1 do -- loop through all selected items
item = reaper.GetSelectedMediaItem(0, i) -- get current selected item
for t = 0, reaper.CountTakes(item)-1 do -- loop through all takes within each selected item
take = reaper.GetTake(item, t) -- get current take
if reaper.TakeIsMIDI(take) then -- make sure, that take is MIDI
if reaper.MIDI_EnumSelNotes(take, -1) ~= -1 then -- check, if there are selected notes
notesSelected = true -- set notesSelected to true
end
gotAllOK, MIDIstring = reaper.MIDI_GetAllEvts(take, "") -- write MIDI events to MIDIstring, get all events okay
if not gotAllOK then reaper.ShowMessageBox("Error while loading MIDI", "Error", 0) return(false) end -- if getting the MIDI data failed
MIDIlen = MIDIstring:len() -- get string length
tableEvents = {} -- initialize table, MIDI events will temporarily be stored in this table until they are concatenated into a string again
stringPos = 1 -- position in MIDIstring while parsing through events
while stringPos < MIDIlen-12 do -- parse through all events in the MIDI string, one-by-one, excluding the final 12 bytes, which provides REAPER's All-notes-off end-of-take message
offset, flags, msg, stringPos = string.unpack("i4Bs4", MIDIstring, stringPos) -- unpack MIDI-string on stringPos
if notesSelected == true then -- if there is a note selection
if msg:len() == 3 then -- if msg consists of 3 bytes (= channel message)
msg_b1_nib1 = msg:byte(1)>>4 -- save 1st nibble of status byte (contains info about the data type) to msg_b1_nib1, >>4 shifts the channel nibble into oblivion
if msg_b1_nib1 == 9 and flags == 1 then -- if status byte is a note on event
table.insert(tableEvents, string.pack("i4Bs4", offset+newPosition, flags, msg)) -- move the note on event by newPosition
table.insert(tableEvents, string.pack("i4Bs4", -newPosition, flags, "")) -- put an empty event after the note on event, to maintain the distance to the next event
elseif msg_b1_nib1 == 8 and flags == 1 then -- if status byte is a note off event
table.insert(tableEvents, string.pack("i4Bs4", offset+newPosition, flags, msg)) -- move the note off event by newPosition
table.insert(tableEvents, string.pack("i4Bs4", -newPosition, flags, "")) -- put an empty event after the note on event, to maintain the distance to the next event
else
table.insert(tableEvents, string.pack("i4Bs4", offset, flags, msg))
end
end
else
if msg:len() == 3 then -- if msg consists of 3 bytes (= channel message)
msg_b1_nib1 = msg:byte(1)>>4 -- save 1st nibble of status byte (contains info about the data type) to msg_b1_nib1, >>4 shifts the channel nibble into oblivion
if msg_b1_nib1 == 9 then -- if status byte is a note on event
table.insert(tableEvents, string.pack("i4Bs4", offset+newPosition, flags, msg)) -- move the note on event by newPosition
table.insert(tableEvents, string.pack("i4Bs4", -newPosition, flags, "")) -- put an empty event after the note on event, to maintain the distance to the next event
elseif msg_b1_nib1 == 8 then -- if status byte is a note off event
table.insert(tableEvents, string.pack("i4Bs4", offset+newPosition, flags, msg)) -- move the note off event by newPosition
table.insert(tableEvents, string.pack("i4Bs4", -newPosition, flags, "")) -- put an empty event after the note on event, to maintain the distance to the next event
else
table.insert(tableEvents, string.pack("i4Bs4", offset, flags, msg))
end
end
end
end
end
end
end
reaper.MIDI_SetAllEvts(take, table.concat(tableEvents) .. MIDIstring:sub(-12))
reaper.MIDI_Sort(take)
reaper.UpdateArrange()
end
|
|
|
07-18-2018, 09:20 AM
|
#2
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
Gah. So much nesting. Can't read.
You should really, seriously, desperately, consider breaking that into multiple functions.
|
|
|
07-18-2018, 09:23 AM
|
#3
|
Human being with feelings
Join Date: Oct 2017
Location: Black Forest
Posts: 5,054
|
Yeah, I hear ya!
Gladly taking any advice. I'm not coding for very long
I thought, nesting would be more efficient than calling functions all the time.
|
|
|
07-18-2018, 09:35 AM
|
#4
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
By a teeny, teeny, TEENY tiny amount, yes. But never enough to be noticeable.
Breaking them up into smaller functions makes the code easier to read, easier to fix, and easier to change. As a general rule of thumb (there are, of course, no Rules), consider breaking the code off into a separate function every time you're running the same code more than a couple of times, or if the code inside the loop starts getting to be (arbitrary number) more than a dozen or so lines. Our brains start having trouble remembering where they are as the function gets bigger, tracking all of the variables going on, etc.
In your case, each of the for loops could be moved out to a function, as well as the while loop in the middle. The section starting from MIDI_EnumSelNotes is a great candidate as well.
You'll probably have to start using local variables if you go this route, which is also a good thing to do all the time anyway, so breaking the functions out will require paying attention to which parameters need to be passed, where each value actually needs to be grabbed, etc.
PM me if you want, I'm happy to help.
Last edited by Lokasenna; 07-18-2018 at 09:40 AM.
|
|
|
07-18-2018, 09:55 AM
|
#5
|
Human being with feelings
Join Date: Jul 2009
Posts: 3,714
|
You are skipping (and deleting) all events that are not three bytes longs, such as channel pressure and program select, and text or sysex.
Try this:
Code:
offset, flags, msg, stringPos = string.unpack("i4Bs4", MIDIstring, stringPos) -- unpack MIDI-string on stringPos
if #msg == 3
and ((msg:byte(1)>>4) == 9 or (msg:byte(1)>>4) == 8) -- Is note-on or note-off?
and (flags&1 == 1 or not notesSelected) -- selected notes always move, unselected only move if no notes are selected
then
table.insert(tableEvents, string.pack("i4Bs4", offset+newPosition, flags, msg)) -- move the note on event by newPosition
table.insert(tableEvents, string.pack("i4Bs4", -newPosition, 0, "")) -- put an empty event after the note on event, to maintain the distance
else
table.insert(tableEvents, string.pack("i4Bs4", offset, flags, msg))
end
Last edited by juliansader; 07-18-2018 at 10:10 AM.
|
|
|
07-18-2018, 11:45 AM
|
#6
|
Banned
Join Date: Sep 2015
Posts: 1,650
|
I think something like ...
Code:
local event_tick_offset = 0
...
table.insert(table_events, string.pack("i4Bs4", offset + new_position - event_tick_offset, flags, msg)) -- move the note on event by newPosition
event_tick_offset = new_position
else
table.insert(table_events, string.pack("i4Bs4", offset - event_tick_offset, flags, msg))
event_tick_offset = 0
...
... is nicer than inserting the empty event. I have also grown to hate camelCase for variable names which should be snake_case unless forced to do otherwise.
|
|
|
07-18-2018, 11:58 AM
|
#7
|
Human being with feelings
Join Date: Oct 2017
Location: Black Forest
Posts: 5,054
|
Ah you guys rock! Thank you so much, will try that!
@Lokasenna: will pm you
@Julian, @snooks: awesome, will give it a go!
@snooks: yeah the camelcase is something I learnt during my studies.
Call it bad habit. I think I'm the only one doing it. Good idea to get rid of it :P It's really hard to read.
|
|
|
07-18-2018, 12:24 PM
|
#8
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
I'm pretty bad for mixing cases, mostly because I can never decide which looks best. Mostly snake case these days, but there's an argument to be made for using different cases for different things - snake for variables, camel for function, etc.
|
|
|
07-18-2018, 01:59 PM
|
#9
|
Banned
Join Date: Sep 2015
Posts: 1,650
|
Yeah, I've kind of settled on camelCase for methods/functions, PascalCase for class names and snake_case for variables. With two word minimum names (unless one will do) it's obvious what is what. Other people do it differently, but I just think this looks purdiest.
|
|
|
07-18-2018, 02:44 PM
|
#10
|
Human being with feelings
Join Date: Oct 2017
Location: Black Forest
Posts: 5,054
|
@Julian
Now, that you fixed the code, I feel really dumb, haha. You really simplified it to the max, awesome!
|
|
|
07-18-2018, 05:26 PM
|
#11
|
Human being with feelings
Join Date: Oct 2017
Location: Black Forest
Posts: 5,054
|
this gives me question marks:
what exactly does this mean?
And... fun fact: although, I forgot to check for muted notes (flags == 2) and muted selected events (flags == 3), they are happily moved together with the other notes (expected behavior).
|
|
|
07-18-2018, 06:43 PM
|
#12
|
Human being with feelings
Join Date: Jun 2012
Posts: 2,173
|
Lol on the comment.
I don't know the tech lingo but here goes. It uses the bits as a sort of toggle indicator. 1 bit is selected, 2 bit is muted. As far as I know, 4,8,16, etc, are not used, in this case ("flags").
...00000011(flags = 3)
&
...00000001(1)
----------------
...00000001
|
|
|
07-18-2018, 07:58 PM
|
#13
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
It's called a bitwise comparison - you're performing logic at the level of individual bits. 1 is true, 0 is false.
Operators:
Code:
&: bitwise AND
|: bitwise OR
~: bitwise exclusive OR
>>: right shift
<<: left shift
~: unary bitwise NOT
Examples from Wikipedia's article on the topic:
Code:
0101 (decimal 5)
AND 0011 (decimal 3)
= 0001 (decimal 1)
0011 (decimal 3)
AND 0010 (decimal 2)
= 0010 (decimal 2)
0101 (decimal 5)
OR 0011 (decimal 3)
= 0111 (decimal 7)
0010 (decimal 2)
OR 1000 (decimal 8)
= 1010 (decimal 10)
0101 (decimal 5)
XOR 0011 (decimal 3)
= 0110 (decimal 6)
0010 (decimal 2)
XOR 1010 (decimal 10)
= 1000 (decimal 8)
The two shift operators basically add a 0 to shift the entire binary sequence in one direction or the other. Why? No idea.
As the use of the term "flags" suggests, binary numbers are really easy way to pass around a whole bunch of boolean parameters in one value. gfx.mouse_cap gives all of the various mouse buttons and keyboard modifiers the same way.
|
|
|
07-19-2018, 03:53 AM
|
#14
|
Human being with feelings
Join Date: Oct 2017
Location: Black Forest
Posts: 5,054
|
Guys, this is incredible! So that way (on bit level), it's possible to check for 2 values at the same time? I have to digest that!
Of course this only works, since the nature of flags is binary? Or can any value
be retrieved as a binary value?
|
|
|
07-19-2018, 04:48 AM
|
#15
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
Any number can be represented as binary, but bitwise comparison is only (well, mostly. I'm sure there's another reason) going to help in situations where the individual bits represent specific information.
You can also (I think, it's been a while) use the bitwise operators on normal integers; they don't have to be formatted as binary like flags is in the code here.
|
|
|
07-19-2018, 05:29 AM
|
#16
|
Banned
Join Date: Sep 2015
Posts: 1,650
|
Yup, you can use bitwise operators on any whole number (called integers). Everything in computers is stored in binary form, but bitwise operators don't make much sense for anything other than integers so we're generally not allowed to use them with anything else. But the important thing to know is that when we write any number it becomes binary.
|
|
|
Thread Tools |
|
Display Modes |
Linear Mode
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
All times are GMT -7. The time now is 02:52 AM.
|