|
|
|
11-20-2018, 02:05 PM
|
#1
|
Human being with feelings
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,984
|
How REAPER handles overlapping MIDI notes?
I`m triyng to build an MIDI part of QuantizeTool (advanced quantization).
Previously I use old slow API (GetSetNote), now I want to build it with 5.32+ MIDI API (GetSetAllEvnts).
What I want to do is split raw MIDI data to CC, notes, other stuff, modify it, compile it and return back to take. It is pretty easy to do with "discrete" events like CC, but goes way complex for notes.
In my point of view extracting notes looks like
Quote:
1) sort data by ppq and put it into array. if 0x9 and 0x8 on same ppq position, put 0x8 before
2) loop datafound 0x9 on index idxsearch for same note 0x8 from index idx+1 to end of arraywhen found, go to main loop
|
^^This fails on stuff like that:
How to extract MIDI notes (as note_start - note_end pair) from RAW MIDI data?
|
|
|
11-20-2018, 03:31 PM
|
#2
|
Human being with feelings
Join Date: Apr 2013
Location: France
Posts: 9,900
|
Overlapping MIDI notes is supported in the editor but it's not MIDI standard.
IMHO, you should handle that by running the Correct overlapping notes action (or an equivalent), so that you will not have overlapping notes anymore.
EDIT: just to be clear, I'm speaking about overlapping MIDI notes of same pitch.
Last edited by X-Raym; 11-20-2018 at 03:37 PM.
|
|
|
11-21-2018, 06:41 AM
|
#3
|
Human being with feelings
Join Date: Apr 2014
Posts: 4,175
|
I agree - when you look at the MIDI Editor's event list - it specifies a note and its length (no note offs).
Whereas in the pooled midi events in the chunk - you have combinations of note-ons and note-off's - so there is no way of telling from that which note off corresponds to which note on (and this detail is ignored anyway when played back - the first note off will cancel any playing note).
I would - like XRaym suggests - deal with possible overlapping notes first.
|
|
|
11-21-2018, 08:13 AM
|
#4
|
Human being with feelings
Join Date: Oct 2007
Location: home is where the heart is
Posts: 12,109
|
Agreed here too.
Though a prompt 'Do you want to correct overlapping notes so QT can work correctly ?' imo makes sense instead of running it 'silently' in case user wants to keep them for whatever reason.
|
|
|
11-21-2018, 12:26 PM
|
#5
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,781
|
"Triple sensor" keyboards do send another note-on for the same key with no Note-Off in between to denote that (with a piano) the damper does not go down but the string is hit another time. I recently checked that my Kawai VPC1 does this. IIRC, no second Note-Off is sent.
-Michael
|
|
|
11-21-2018, 03:47 PM
|
#6
|
Human being with feelings
Join Date: Apr 2013
Location: France
Posts: 9,900
|
also I think controllers send bote on with vel 0 as not off but this is a particular case.
|
|
|
11-21-2018, 10:38 PM
|
#7
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,781
|
Yep ! Any Midi software needs to handle this.
-Michael
|
|
|
11-21-2018, 10:58 PM
|
#8
|
Human being with feelings
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,984
|
Quote:
Originally Posted by nofish
Agreed here too.
Though a prompt 'Do you want to correct overlapping notes so QT can work correctly ?' imo makes sense instead of running it 'silently' in case user wants to keep them for whatever reason.
|
I'm not sure it is even possible. Even if I save project with notes like at second case on picture, REAPER load first case on next project load.
I can't apply any quantize without ruin all the MIDI data, I MUST correct overlapped notes when recalculating data, not before, so this can dangerous at any point...
|
|
|
11-23-2018, 06:43 AM
|
#9
|
Human being with feelings
Join Date: Jun 2012
Posts: 2,173
|
Quote:
Originally Posted by mpl
I'm not sure it is even possible. Even if I save project with notes like at second case on picture, REAPER load first case on next project load.
|
Second pair can only exist short term while editor is open. They may be re-sorted after a number of procedures. In other words, it is even more fragile than you state. So, yes, Reaper follows a first come first serve sorting pattern. You cannot produce a pattern like the bottom pair with SetAllEvents.
Quote:
I can't apply any quantize without ruin all the MIDI data, I MUST correct overlapped notes when recalculating data, not before, so this can dangerous at any point...
|
I don’t know if it helps, but you can do notes somewhat separate from cc by putting in negative offset.
|
|
|
11-23-2018, 10:17 AM
|
#10
|
Human being with feelings
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,984
|
Am I right, that zero offset can not exist in MIDI timing standard?
|
|
|
11-23-2018, 12:05 PM
|
#11
|
Human being with feelings
Join Date: Jun 2012
Posts: 2,173
|
I don’t know much about the protocol outside reaper. I think Justin and Schwa allow it to make scripting easier. Like how you can use empty string instead of deleting event and changing offset of another event. In other words, SetAllEvents string must be processed a bit more by reaper before item gets its info put into it.
edit. Hm. It looks like you might have to call MIDI_Sort after negative offsets. Here is simple script that puts in note at 960 ticks then the rest of events that were in item, before and after the new note. If you try -- in front of MIDI_Sort you will see that arrange peaks of MIDI item don't update correctly. (MIDI Editor seems to update ok...) I found some comments in juliansader 2-Sided Warp script about this too. Sounds like it may be costlier to sort after than before SetAllEvents in some cases. I don't know, didn't experiment that much.
Code:
take = reaper.MIDIEditor_GetTake(reaper.MIDIEditor_GetActive())
channel = 0
pitch = 60
vel = 95
start = 960
length = 240
gotAllOK, MIDIstring = reaper.MIDI_GetAllEvts(take, "")
on_msg = string.char(144 + channel) .. string.char(pitch) .. string.char(vel)
off_msg = string.char(128 + channel) .. string.char(pitch) .. string.char(0)
str =
string.pack("i4Bs4", start, 0, on_msg)
.. string.pack("i4Bs4", length, 0, off_msg)
.. string.pack("i4Bs4", (-start) - length, 0, "")
.. MIDIstring
reaper.MIDI_SetAllEvts(take, str)
reaper.MIDI_Sort(take)
Last edited by FnA; 11-23-2018 at 04:57 PM.
|
|
|
11-24-2018, 04:54 AM
|
#12
|
Human being with feelings
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,984
|
There could be couple events on with same PPQ position(offset=0), so here is MIDI parsing code I working on:
Code:
local gotAllOK, MIDIstring = MIDI_GetAllEvts(take, "")
if not gotAllOK then return end
local s_unpack = string.unpack
local s_pack = string.pack
local MIDIlen = MIDIstring:len()
local offset, flags, msg1
local ppq_pos, nextPos, prevPos, idx = 0, 1, 1 , 0
while nextPos <= MIDIlen do
prevPos = nextPos
offset, flags, msg1, nextPos = s_unpack("i4Bs4", MIDIstring, prevPos)
idx = idx + 1
ppq_pos = ppq_pos + offset
local selected = flags&1==1
local pos_sec = MIDI_GetProjTimeFromPPQPos( take, ppq_pos )
local beats, measures, cml, fullbeats, cdenom = reaper.TimeMap2_timeToBeats( 0, pos_sec )
local CClane, pitch, CCval,vel, pitch_format
local isNoteOn = msg1:byte(1)>>4 == 0x9
local isNoteOff = msg1:byte(1)>>4 == 0x8
local isCC = msg1:byte(1)>>4 == 0xB
local chan = 1+(msg1:byte(1)&0xF)
local pitch = msg1:byte(2)
local ignore_search = true
if strategy.ref_midi_msgflag&1==1 and isNoteOn then ignore_search = false end
if strategy.ref_midi_msgflag&2==2 and isNoteOff then ignore_search = false end
if not ignore_search then data[table_name].src_cnt = data[table_name].src_cnt + 1 end
if not (table_name == 'ref' and ignore_search == true) then
data[table_name][#data[table_name]+1] =
{ pos = fullbeats,
pos_beats = beats,
ignore_search = ignore_search,
GUID = BR_GetMediaItemTakeGUID( take ),
tk_offs = t_offs,
it_pos = item_pos,
tk_rate = tk_rate,
ptr = take,
pitch = msg1:byte(2)/127,
flags=flags,
msg1=msg1,
ppq_pos=ppq_pos,
offset=offset,
srctype = 'MIDIEvnt',
isNoteOn =isNoteOn,
isNoteOff=isNoteOff,
chan=chan,
pitch = pitch
}
end
-- sort stuff by ppq // sort ppq ids table
for i = 1, #take_t do
local t = take_t[i]
local ppq_pos = t.ppq_pos
if not ppq_sorted_t[ppq_pos] then
ppq_sorted_t[ppq_pos] = {}
ppq_t[#ppq_t+1] = ppq_pos
end
ppq_sorted_t[ppq_pos] [#ppq_sorted_t[ppq_pos]+1] = t
end
table.sort(ppq_t)
-- extract notes/other events
for i = 1, #ppq_t do
local ppq = ppq_t[i]
for i2 = 1, #ppq_sorted_t[ppq] do
if ppq_sorted_t[ppq][i2].isNoteOn then
-- search noteoff/add note to table
for searchid = i+1, #ppq_t do
local ppq_search = ppq_t[searchid]
for i2_search = #ppq_sorted_t[ppq_search], 1,-1 do
if ppq_sorted_t[ppq_search][i2_search].isNoteOff
and ppq_sorted_t[ppq_search][i2_search].chan == ppq_sorted_t[ppq][i2].chan
and ppq_sorted_t[ppq_search][i2_search].pitch == ppq_sorted_t[ppq][i2].pitch
then
table.remove(ppq_sorted_t[ppq_search], i2_search)
new_entry_id = #notes_t+1
notes_t[new_entry_id] = ppq_sorted_t[ppq][i2_search]
notes_t[new_entry_id].note_len = ppq_search - ppq
end
end
end
-- add other events to table
elseif not (ppq_sorted_t[ppq][i2].isNoteOn or ppq_sorted_t[ppq][i2].isNoteOff) then
evts_t[#evts_t+1]=ppq_sorted_t[ppq][i2]
end
end
end
...then modify all this shit and put back into RAW data by block (all notes, then all CCs then all other events with positive offsets except negative offsets beetween blocks)
Parser part is placed now in alignment code, but i think I need to use parsed table as source in the code for getting target/anchor points, and only return back modified values in alignment code. For now it seems an only working way to properly deal with offsets while quantizng MIDI notes starts/ends/length without messing them.
Last edited by mpl; 11-24-2018 at 05:02 AM.
|
|
|
11-24-2018, 08:13 AM
|
#13
|
Human being with feelings
Join Date: Jun 2012
Posts: 2,173
|
Hm. Is a lot to think about. I don’t know what all end goals are and features of tool etc.
A couple things I guess I should mention:
The “flags” are treated as separate entities almost like channel. They can be mixed more or less freely (I mean/refer to overlapping) while their states are different.
Have you considered a 16x128x? Reference table for each note type, where you could put information like idx numbers or whatever may be pertinent? Seems to be a way to avoid the looping, but I’m not 100% sure now...
|
|
|
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 11:44 AM.
|