|
|
|
10-15-2021, 11:03 AM
|
#1
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
|
MIDI_GetRecentInputEvent how to use?
EDIT: Answers at post #4
Original post:
Hey people, so in the october 14 2021 we get this new function to get the MIDI input.
retval, buf, ts, devIdx = reaper.MIDI_GetRecentInputEvent(idx)
I am trying to understand how we can unpack the MIDI info out of it.
As far as I managed to understand it is packed into buf as a string.
So trying to use string.unpack on it but I don't know what arguments to put inside string.unpack.
Like with reaper.MIDI_GetAllEvts I normally use "i4Bs4" as there is other thread showing snippets using it. I really don't know what these characters means, nor have found any lua document talking about them. But it does return the MIDI info like in here:
Code:
function SoloChannel(take, ch)
if ch ~= 0 then -- ch 0 is omni
local retval, MIDIstring = reaper.MIDI_GetAllEvts(take, "")
local MIDIlen = MIDIstring:len()
local tableEvents = {}
local stringPos = 1
--local pos=0
while stringPos < MIDIlen do
offset, flags, ms, stringPos = string.unpack("i4Bs4", MIDIstring, stringPos) -- Unpack the MIDI[stringPos] event
--pos=pos+offset -- For keeping track of the Postion of the notes in Ticks
if ms:len() == 3 then -- if ms:len == 3 means it have 3 messages(Notes, CC,Poly Aftertouch, Pitchbend ) (ms:byte(1)>>4 == 9 or ms:byte(1)>>4 == 8) note on or off
local channel = ms:byte(1)&0x0F -- 0x0F = 0000 1111 in binary . ms is decimal. & is an and bitwise operation "have to have 1 in both to be 1". Will return channel as a decimal number
if channel ~= ch-1 then ms="" end
end
table.insert(tableEvents, string.pack("i4Bs4", offset, flags, ms))
end
reaper.MIDI_SetAllEvts(take, table.concat(tableEvents))
end
end
I would really appreciate some help, I have no clue how to unpack that info
Thanks people
EDIT: Just found a clue, here is documented what the characters do in string pack/unpack
https://q-syshelp.qsc.com/Content/Co...lation.htm#3.2
Last edited by daniellumertz; 10-16-2021 at 11:20 PM.
|
|
|
10-16-2021, 11:59 AM
|
#2
|
Human being with feelings
Join Date: May 2017
Location: Leipzig
Posts: 6,612
|
As this is still devs-release only, I think discussing it outside the pre-release thread could be confusing, if things change.
Maybe it's better to move the the thread to the pre-release forum for the time being.
Going to have a look at it as soon as I finished updating my docs so maybe I can add one thing or another.
|
|
|
10-16-2021, 12:03 PM
|
#3
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
|
I would be fine if the mods move to pre sub .
Thanks for the update mespotine
|
|
|
10-16-2021, 10:57 PM
|
#4
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
|
I asked in askjf and he gave me a hand!
Code:
Each message is a string of binary data.. so you can use string.byte(str,1) which should return 0x90, for example, etc.
so basically
Code:
local retval, buf, ts, devIdx = reaper.MIDI_GetRecentInputEvent(0)
Buf1 = string.byte(buf,1) --Status Byte
Buf2 = string.byte(buf,2) -- Data Byte 1
Buf3 = string.byte(buf,3) -- Data Byte 2
I made a small visualizer to see the midi messages
Code:
function print(val)
reaper.ShowConsoleMsg(tostring(val)..'\n')
end
function midi_loop()
local retval, buf, ts
retval, buf, ts, devIdx = reaper.MIDI_GetRecentInputEvent(0)
Buf_1 = string.byte(buf,1)
Buf_2 = string.byte(buf,2)
Buf_3 = string.byte(buf,3)
end
local ctx = reaper.ImGui_CreateContext('My scripts')
function loop()
reaper.ImGui_SetNextWindowSize(ctx, 400, 180)
local visible, open = reaper.ImGui_Begin(ctx, 'MIDI Input Log', true)
if visible then
midi_loop()
reaper.ImGui_Text(ctx, 'Status Byte '..Buf_1)
local msg_type = (tonumber(Buf_1)>>4)
reaper.ImGui_Text(ctx, ' Msg Type '..msg_type..' '..MsgTypes[msg_type])
reaper.ImGui_Text(ctx, ' Msg Channel '..((Buf_1&0x0F)+1))
reaper.ImGui_Text(ctx, 'Data Byte 1 '..Buf_2)
reaper.ImGui_Text(ctx, 'Data Byte 2 '..Buf_3)
reaper.ImGui_Text(ctx, 'Device Index '..devIdx)
if devIdx then
local retval, nameout = reaper.GetMIDIInputName( devIdx, '' )
reaper.ImGui_Text(ctx, 'Device Name '..nameout)
end
reaper.ImGui_End(ctx)
end
if open then
reaper.defer(loop)
else
reaper.ImGui_DestroyContext(ctx)
end
end
MsgTypes = { [8] = 'Note Off', [9] = 'Note On', [10] = 'Aftertouch', [11] = 'CC', [12] = 'Program Change', [13] = 'Channel Pressure', [14] = 'Pitch Bend', [15] = 'SyEx' }
reaper.defer(loop)
Last edited by daniellumertz; 07-11-2022 at 06:54 PM.
|
|
|
10-16-2021, 11:14 PM
|
#5
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
|
Ok testing with a a MIDI keyboard here (an PSR413 from Yamaha) I notice it also grabs System Messages 11111000 (248) or 254(11111110) that come CONSTANTLY from the keyboard. I don't know if other MIDI devices don't do this. This makes life a little harder
Because I need to have the luck that the last sample with a midi message from the user coincide with the defer loop with containing
Code:
local retval, buf, ts, devIdx = reaper.MIDI_GetRecentInputEvent(0)
Buf1 = string.byte(buf,1)
Buf2 = string.byte(buf,2)
Buf3 = string.byte(buf,3)
to me would make sense having a filter argument to system messages, as normally the user don't send them.
The solution for now is looping in the MIDI_GetRecentInputEvent till find a MIDI event (non system message) and compare if it is new (compare with retval maybe(?)). But would be better to just filter this kind of messages in the function.
|
|
|
10-17-2021, 04:07 AM
|
#6
|
Human being with feelings
Join Date: Sep 2021
Location: Berlin
Posts: 1,932
|
Are messages already handled by REAPER also included in the list of MIDI_GetRecentInputEvent()? If one were to attempt to make a retrospective record feature, you'd want to know if the data was already recorded.
I agree that some kind of filter would be useful, maybe as a flag mask. MIDI 248 (clock) is arguably useful to have, but 254 (active sensing) is pretty useless.
|
|
|
10-17-2021, 04:46 AM
|
#7
|
Administrator
Join Date: Jan 2005
Location: NYC
Posts: 15,716
|
Hmm yeah I think clock/active sensing should always be omitted (otherwise it will eventually clear out your note history which isn't desired IMO)
|
|
|
10-17-2021, 06:46 PM
|
#8
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
|
yeah, I am ok filtering it out. I can see someone someday trying to use clock with reascripts it probably wouldn't be with getrecentinput. As using getrecentinputevent with a defer would lose the timming precision, and probably lost the meaning of using midi clock in the first place (?).
Thanks for the reply Justin
|
|
|
10-18-2021, 05:40 AM
|
#9
|
Administrator
Join Date: Jan 2005
Location: NYC
Posts: 15,716
|
Quote:
Originally Posted by daniellumertz
yeah, I am ok filtering it out. I can see someone someday trying to use clock with reascripts it probably wouldn't be with getrecentinput. As using getrecentinputevent with a defer would lose the timming precision, and probably lost the meaning of using midi clock in the first place (?).
Thanks for the reply Justin
|
just to be clear, the next dev builds will filter out clock and active sensing internally (so you won't have to)
|
|
|
10-18-2021, 05:48 AM
|
#10
|
Human being with feelings
Join Date: Sep 2021
Location: Berlin
Posts: 1,932
|
Quote:
Originally Posted by Justin
just to be clear, the next dev builds will filter out clock and active sensing internally (so you won't have to)
|
Thanks Justin, that's great.
Is there some way to differentiate recorded vs unrecorded events in the list? Or would that need to be inferred from watching the transport state (and sample time)? I haven't had a moment to try it out yet, but that would be useful information to have.
Thanks again!
|
|
|
10-18-2021, 06:53 AM
|
#11
|
Human being with feelings
Join Date: Apr 2014
Posts: 4,171
|
Quote:
Originally Posted by Justin
just to be clear, the next dev builds will filter out clock and active sensing internally (so you won't have to)
|
Thanks from me too Justin,
With regard to using this for retrospective record - or at least rr as I see it - I would want to work out exactly where the play/edit cursor was on the timeline when each MIDI input event was received.
I cannot currently see a foolproof way of doing this armed with only the relative time between each event. It's possible I've completely missed something - but having played with this for a few hours, I've not come up with anything yet.
If the position of the exact play/edit cursor position could be added to the return values for each stored midi event - alongside the TS, then I think it could open a few more doors here...
Just thinking out loud...
|
|
|
10-18-2021, 07:07 AM
|
#12
|
Human being with feelings
Join Date: Sep 2021
Location: Berlin
Posts: 1,932
|
Quote:
Originally Posted by lb0
Thanks from me too Justin,
With regard to using this for retrospective record - or at least rr as I see it - I would want to work out exactly where the play/edit cursor was on the timeline when each MIDI input event was received.
I cannot currently see a foolproof way of doing this armed with only the relative time between each event. It's possible I've completely missed something - but having played with this for a few hours, I've not come up with anything yet.
If the position of the exact play/edit cursor position could be added to the return values for each stored midi event - alongside the TS, then I think it could open a few more doors here...
Just thinking out loud...
|
The current strategy also takes "transport is off" usage into account, where the event timestamps are completely decoupled from a transport position. I get the utility of a transport position for your use-case, though. I hope that this new interface is a side-effect of some as-yet-unrevealed native RR work, but I imagine that we can get something useful going with what's currently available.
|
|
|
10-18-2021, 07:33 AM
|
#13
|
Human being with feelings
Join Date: Apr 2014
Posts: 4,171
|
Quote:
Originally Posted by sockmonkey72
The current strategy also takes "transport is off" usage into account, where the event timestamps are completely decoupled from a transport position. I get the utility of a transport position for your use-case, though. I hope that this new interface is a side-effect of some as-yet-unrevealed native RR work, but I imagine that we can get something useful going with what's currently available.
|
I agree. And I already have transport stopped and playing RR working using a JSFX to capture the messages as part of my Smart Knobs 2 script.
But it would be even more versatile if we could get transport position alongside the relative TS with this new function. At the moment - without any event transport position value - we're definitely limited to only getting a sequential series of events - no chance of working out loops, or properly positioning received events as they were played into the project etc.
What I also like about this new function - is that it also tells us the device that the data is received from - which is not something obtainable using a JSFX AFAIK.
If the transport position was able to be included as a return parameter - (I'm definitely not suggesting removing the already returned TS variable) - then it opens up a lot more possibilities. As it is - I cannot get even close to what I've succeeded in doing with the JSFX method. But using the new API would be a lot cleaner - possibly with even more potential.
EDIT:
To clarify - I have no real idea why this API has been added - but my first thoughts were - I wonder if I could use it to replace my current RR system - do it in a neater way. But as it is - this is not possible. So my request may be ignored if I'm way of the mark - Justin and Schwa haven't really given any reasons why this new API has been created. I can see other possible uses too.
|
|
|
10-18-2021, 07:40 AM
|
#14
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
|
Quote:
Originally Posted by Justin
just to be clear, the next dev builds will filter out clock and active sensing internally (so you won't have to)
|
yeah that was what I interpreted thx
|
|
|
10-18-2021, 08:39 AM
|
#15
|
Human being with feelings
Join Date: Oct 2017
Location: Black Forest
Posts: 5,048
|
I totally agree, that having the timeline info for events would be super useful if not crucial for RR. RR should always be able to cover cases for transport stopped and transport play back.
|
|
|
10-18-2021, 10:08 AM
|
#16
|
Human being with feelings
Join Date: May 2017
Location: Leipzig
Posts: 6,612
|
How far back do the recent events go?
Since a specific event like recording-start or longer?
Edit: I get it right, that time-information is currently missing? Like project position when a certain midi-event was sent?
|
|
|
10-18-2021, 01:43 PM
|
#17
|
Human being with feelings
Join Date: Apr 2014
Posts: 4,171
|
Quote:
Originally Posted by Meo-Ada Mespotine
How far back do the recent events go?
Since a specific event like recording-start or longer?
Edit: I get it right, that time-information is currently missing? Like project position when a certain midi-event was sent?
|
Well - I've left Reaper idle for over an hour (with a handshake midi message submitted every few seconds) - and it appears to have most of them - over 5000 messages - the oldest being around 90 minutes ago. Truth be told - I don't know if any limit is more likely to be a max number of messages, or a max timeframe - but I suspect the former - although I've not hit it...
From what I can tell - the function returns each midi message as a buffer of bytes along with how many seconds ago it occurred. No positional information with regards to the timeline.
|
|
|
10-19-2021, 09:17 AM
|
#18
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
|
I also agree This fr. Would be essential for scripts that do something at a precise time point in timeline .
Also please consider osc into reascripts, with it I could connect other softwares like max MSP to reaper API. As in lua we don't really have networking, this could be a solution.
|
|
|
10-19-2021, 10:12 AM
|
#19
|
Human being with feelings
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
|
Quote:
Originally Posted by lb0
No positional information with regards to the timeline.
|
How would this work in practice if it did exist?
If I put the playhead at a random position in my song, stop, and then jam on a keyboard, would it wind up with dozens/hundreds of notes that were all marked as occurring at the current playhead position?
__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)
|
|
|
10-19-2021, 10:21 AM
|
#20
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
|
Quote:
Originally Posted by gxray
How would this work in practice if it did exist?
If I put the playhead at a random position in my song, stop, and then jam on a keyboard, would it wind up with dozens/hundreds of notes that were all marked as occurring at the current playhead position?
|
In my perspective the function will return the time the played is so in this case the position it was stopped .
|
|
|
10-21-2021, 01:55 AM
|
#21
|
Human being with feelings
Join Date: Apr 2014
Posts: 4,171
|
Quote:
Originally Posted by gxray
How would this work in practice if it did exist?
If I put the playhead at a random position in my song, stop, and then jam on a keyboard, would it wind up with dozens/hundreds of notes that were all marked as occurring at the current playhead position?
|
As daniellumertz stated - we would also have the relative time as well as playback position to work with.
There would be some required logic to work out transitions from where playback is stopped and started or vice-versa, but if you were only interested in events where playback was stopped *or* playback active - then that would be easily detectable from the data.
|
|
|
10-22-2021, 11:53 AM
|
#22
|
Human being with feelings
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
|
That seems I`m stupid, but I can`t get it to work as expected, this should Dump recent input to new MIDI item, the data is separated into different takes by device. I have lots of missing note-offs after random triggering Virtual keyboard.
(tested on REAPER v6.38+dev1018)
Code:
function DumpRetrospectiveLog_CollectEvents()
local t = {}
local item_len_spls = 0
local cnt_events, buf, ts0, devIdx = reaper.MIDI_GetRecentInputEvent(0)
if cnt_events ~= 0 then cnt_events = cnt_events~0x40000000 end
local _, _, ts_final, devIdx = reaper.MIDI_GetRecentInputEvent(cnt_events-1)
for i = 1, cnt_events do
local retval, buf, ts, devIdx = reaper.MIDI_GetRecentInputEvent(i-1)
devIdx=devIdx&0xFFFF
if devIdx and not t[devIdx] then t[devIdx] = {} end
t[devIdx][#t[devIdx] + 1] = {msg1= buf, ts = ts-ts_final}
item_len_spls = math.max(item_len_spls, t[devIdx][#t[devIdx]].ts)
end
return t, item_len_spls
end
-------------------------------------------
function ApplyMIDIdata(take, midi_data_t, itempos, SR)
local midistr = ''
local ppq_cur, ppq_cur_last
for evt = #midi_data_t,1,-1 do
local evt_t = midi_data_t[evt]
local ppq_evt = math.floor(reaper.MIDI_GetPPQPosFromProjTime( take, itempos+ evt_t.ts / SR ))
ppq_cur = ppq_evt
if not ppq_cur_last then ppq_cur_last = ppq_cur end
local str_per_msg = string.pack("i4BI4BBB", ppq_cur - ppq_cur_last, 0, 3, evt_t.msg1:byte(1),evt_t.msg1:byte(2),evt_t.msg1:byte(3))
ppq_cur_last = ppq_cur
midistr = midistr..str_per_msg
end
reaper.MIDI_SetAllEvts(take, midistr)
reaper.MIDI_Sort(take)
end
-------------------------------------------
function DumpRetrospectiveLog()
-- get data
local SR = tonumber(reaper.format_timestr_pos( 1, '', 4 ))
local midi_t, item_len_spls = DumpRetrospectiveLog_CollectEvents()
-- Create item at first selected track or new one if no track selected
local track = reaper.GetSelectedTrack(0,0)
if not track then
reaper.InsertTrackAtIndex( reaper.CountTracks( 0 ), 1 )
track = reaper.GetTrack(0,reaper.CountTracks( 0 )-1)
end
-- Add item
local itempos = reaper.GetCursorPosition()
local item = reaper.CreateNewMIDIItemInProj( track, itempos, itempos + item_len_spls / SR)
-- pass data to item
local i = 1
for devIdx in pairs(midi_t) do
local retval, nameout = reaper.GetMIDIInputName( devIdx, '' )
local take = reaper.GetActiveTake( item )
if i ~= 1 then take = reaper.AddTakeToMediaItem( item ) end
local retval, nameout = reaper.GetMIDIInputName( devIdx, '' )
reaper.GetSetMediaItemTakeInfo_String( take, 'P_NAME', nameout, 1 )
ApplyMIDIdata(take, midi_t[devIdx], itempos, SR)
i = i + 1
end
-- update arrange
reaper.UpdateArrange()
end
DumpRetrospectiveLog()
And yes, there should be additional API returning timestamp of last triggered Transport: Stop/Pause (or better: return playback position for every message). This required to work with looped playback as well.
Last edited by mpl; 10-26-2021 at 10:19 PM.
|
|
|
10-22-2021, 12:07 PM
|
#23
|
Human being with feelings
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
|
Quote:
Originally Posted by lb0
To clarify - I have no real idea why this API has been added - but my first thoughts were - I wonder if I could use it to replace my current RR system - do it in a neater way. But as it is - this is not possible. So my request may be ignored if I'm way of the mark - Justin and Schwa haven't really given any reasons why this new API has been created. I can see other possible uses too.
|
That was my personal requiest to Justin, because current implementation (mine and Eugen) is a awful workaround.
|
|
|
10-22-2021, 01:05 PM
|
#24
|
Human being with feelings
Join Date: Apr 2014
Posts: 4,171
|
Quote:
Originally Posted by mpl
That was my personal requiest to Justin, because current implementation (mine and Eugen) is a awful workaround.
|
Ahh - so it is to do with RR - and thank you for requesting this. I agree - Eugen's ingenious but rather long-winded approach is definitely a workaround - and it would be amazing for this new API to be able to replace it.
As it is - I don't see how it can replace Eugen's (and yours and my) current method - which all use the same 'clunky' techniques. The main problem for me is I don't see a way of knowing exactly where the MIDI should be placed on the timeline. So having a timeline position for each MIDI event would help to make it more useful. Without this - you're either guessing where it needs to go (which is not good) - or just able to simply create a MIDI item at the cursor which wouldn't cut it with most users as everything would need to be painstakingly realigned manually.
I also agree - for it to be perfect, there would also need to be a decent way of finding out where/when transport start and stop occurred (Lua would not be accurate enough, and having to use a JSFX - although more accurate (but still not perfect) - kind of defeats the purpose).
I really hope this gets a little bit more attention from the Devs - it indeed would be great to remove the need for a JSFX to continually monitor the incoming MIDI, and be guaranteed a sample perfect copy of the data as it was received by Reaper.
|
|
|
10-22-2021, 04:28 PM
|
#25
|
Human being with feelings
Join Date: Jul 2009
Posts: 121
|
Is there a simple tweak to MPL's script to insert item only SINCE last retrospective record?
Because now it inserts the whole session and that is really clunky...
Thanks a lot
|
|
|
10-22-2021, 06:20 PM
|
#26
|
Human being with feelings
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
|
Quote:
Originally Posted by denmla
Is there a simple tweak to MPL's script to insert item only SINCE last retrospective record?
Because now it inserts the whole session and that is really clunky...
Thanks a lot
|
Not a complex mod (using per project external state). For now I'll wait until someone point me to note-off ignoring solution.
|
|
|
10-22-2021, 06:23 PM
|
#27
|
Human being with feelings
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
|
Quote:
Originally Posted by lb0
I really hope this gets a little bit more attention from the Devs - it indeed would be great to remove the need for a JSFX to continually monitor the incoming MIDI, and be guaranteed a sample perfect copy of the data as it was received by Reaper.
|
If it is long story for reaper devs, there could be a script which write timestamp into external state. So the user create custom action: run transport stop and write timestanp.
|
|
|
10-23-2021, 01:50 AM
|
#28
|
Human being with feelings
Join Date: Jul 2009
Posts: 121
|
Quote:
Originally Posted by mpl
Not a complex mod (using per project external state).
|
@MPL
Can I do it? I guess I would writte something to Ext. State each time the script is executed to set something like a new starting point?
Yeah and I get a long notes troughout the insered MIDI item, so i guess that is another problem you mentioned....
Sure we can wait and thanks for the efforts!
|
|
|
10-23-2021, 02:23 AM
|
#29
|
Human being with feelings
Join Date: Apr 2014
Posts: 4,171
|
Quote:
Originally Posted by mpl
If it is long story for reaper devs, there could be a script which write timestamp into external state. So the user create custom action: run transport stop and write timestanp.
|
That's a very good solution and I suspect would work just fine for discovering the start/stop position.
I'd still want/need the timeline position recorded per event, for cases where the user moves the play cursor manually.
|
|
|
10-23-2021, 02:44 AM
|
#30
|
Human being with feelings
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
|
Quote:
Originally Posted by lb0
That's a very good solution and I suspect would work just fine for discovering the start/stop position.
I'd still want/need the timeline position recorded per event, for cases where the user moves the play cursor manually.
|
Yeah, or move loop points while playing in play state while repeat is on.
|
|
|
10-27-2021, 02:22 PM
|
#31
|
Human being with feelings
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
|
Justin's mod for 6.39rc2:
Code:
function DumpRetrospectiveLog_CollectEvents()
local t = {}
local i,last_ts,item_len_spls = 0,0,0
while true do
local retval, buf, tsval, devIdx, projPos = reaper.MIDI_GetRecentInputEvent(i)
if retval == 0 then break end
if i > 0 and tsval < last_ts-4*48000 then break end -- 4sec of nothing, stop looking for more events
i = i + 1
if (devIdx & 0x10000) == 0 or devIdx == 0x1003e then
devIdx=devIdx&0xFFFF
if devIdx and not t[devIdx] then t[devIdx] = {} end
t[devIdx][#t[devIdx] + 1] = {msg1= buf, ts = tsval, pp=projPos}
last_ts = tsval
end
end
for devIdx in pairs(t) do
for i = 1,#t[devIdx] do
local ts = t[devIdx][i].ts - last_ts
t[devIdx][i].ts = ts
item_len_spls = math.max(item_len_spls, ts)
end
end
return t, item_len_spls
end
-------------------------------------------
function ApplyMIDIdata(take, midi_data_t, itempos, SR)
local midistr = ''
local ppq_cur, ppq_cur_last
local lpos = -1
for evt = #midi_data_t,1,-1 do
local evt_t = midi_data_t[evt]
local ppq_evt = math.floor(reaper.MIDI_GetPPQPosFromProjTime( take, itempos+ evt_t.ts / SR ))
ppq_cur = ppq_evt
if not ppq_cur_last then ppq_cur_last = ppq_cur end
local str_per_msg = string.pack("i4BI4BBB", ppq_cur - ppq_cur_last, 0, 3, evt_t.msg1:byte(1),evt_t.msg1:byte(2),evt_t.msg1:byte(3))
ppq_cur_last = ppq_cur
midistr = midistr..str_per_msg
if evt == #midi_data_t then lpos = evt_t.pp end
end
reaper.MIDI_SetAllEvts(take, midistr)
reaper.MIDI_Sort(take)
return lpos
end
-------------------------------------------
function DumpRetrospectiveLog()
-- get data
local SR = tonumber(reaper.format_timestr_pos( 1, '', 4 ))
local midi_t, item_len_spls = DumpRetrospectiveLog_CollectEvents()
-- Create item at first selected track or new one if no track selected
local track = reaper.GetSelectedTrack(0,0)
if not track then
reaper.InsertTrackAtIndex( reaper.CountTracks( 0 ), 1 )
track = reaper.GetTrack(0,reaper.CountTracks( 0 )-1)
end
-- Add item
local itempos = reaper.GetCursorPosition()
local item = reaper.CreateNewMIDIItemInProj( track, itempos, itempos + item_len_spls / SR)
-- pass data to item
local i = 1
for devIdx in pairs(midi_t) do
local retval, nameout = reaper.GetMIDIInputName( devIdx, '' )
local take = reaper.GetActiveTake( item )
if i ~= 1 then take = reaper.AddTakeToMediaItem( item ) end
local retval, nameout = reaper.GetMIDIInputName( devIdx, '' )
reaper.GetSetMediaItemTakeInfo_String( take, 'P_NAME', nameout, 1 )
local lpos = ApplyMIDIdata(take, midi_t[devIdx], itempos, SR)
if lpos >= 0 then
reaper.SetMediaItemPosition(item,lpos,false)
end
i = i + 1
end
-- update arrange
reaper.UpdateArrange()
end
DumpRetrospectiveLog()
Much thanks to Justin.
Added to Reapack as mpl_Dump Retrospective Record log.
Last edited by mpl; 10-27-2021 at 03:31 PM.
|
|
|
10-27-2021, 03:34 PM
|
#32
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
|
Thanks Mpl and justin for the code above. I will test at next dev version!
you both rock!
|
|
|
10-27-2021, 11:38 PM
|
#33
|
Human being with feelings
Join Date: Aug 2011
Location: Los Angeles, CA
Posts: 308
|
Quote:
Originally Posted by mpl
Justin's mod for 6.39rc2:
Code:
function DumpRetrospectiveLog_CollectEvents()
local t = {}
local i,last_ts,item_len_spls = 0,0,0
while true do
local retval, buf, tsval, devIdx, projPos = reaper.MIDI_GetRecentInputEvent(i)
if retval == 0 then break end
if i > 0 and tsval < last_ts-4*48000 then break end -- 4sec of nothing, stop looking for more events
i = i + 1
if (devIdx & 0x10000) == 0 or devIdx == 0x1003e then
devIdx=devIdx&0xFFFF
if devIdx and not t[devIdx] then t[devIdx] = {} end
t[devIdx][#t[devIdx] + 1] = {msg1= buf, ts = tsval, pp=projPos}
last_ts = tsval
end
end
for devIdx in pairs(t) do
for i = 1,#t[devIdx] do
local ts = t[devIdx][i].ts - last_ts
t[devIdx][i].ts = ts
item_len_spls = math.max(item_len_spls, ts)
end
end
return t, item_len_spls
end
-------------------------------------------
function ApplyMIDIdata(take, midi_data_t, itempos, SR)
local midistr = ''
local ppq_cur, ppq_cur_last
local lpos = -1
for evt = #midi_data_t,1,-1 do
local evt_t = midi_data_t[evt]
local ppq_evt = math.floor(reaper.MIDI_GetPPQPosFromProjTime( take, itempos+ evt_t.ts / SR ))
ppq_cur = ppq_evt
if not ppq_cur_last then ppq_cur_last = ppq_cur end
local str_per_msg = string.pack("i4BI4BBB", ppq_cur - ppq_cur_last, 0, 3, evt_t.msg1:byte(1),evt_t.msg1:byte(2),evt_t.msg1:byte(3))
ppq_cur_last = ppq_cur
midistr = midistr..str_per_msg
if evt == #midi_data_t then lpos = evt_t.pp end
end
reaper.MIDI_SetAllEvts(take, midistr)
reaper.MIDI_Sort(take)
return lpos
end
-------------------------------------------
function DumpRetrospectiveLog()
-- get data
local SR = tonumber(reaper.format_timestr_pos( 1, '', 4 ))
local midi_t, item_len_spls = DumpRetrospectiveLog_CollectEvents()
-- Create item at first selected track or new one if no track selected
local track = reaper.GetSelectedTrack(0,0)
if not track then
reaper.InsertTrackAtIndex( reaper.CountTracks( 0 ), 1 )
track = reaper.GetTrack(0,reaper.CountTracks( 0 )-1)
end
-- Add item
local itempos = reaper.GetCursorPosition()
local item = reaper.CreateNewMIDIItemInProj( track, itempos, itempos + item_len_spls / SR)
-- pass data to item
local i = 1
for devIdx in pairs(midi_t) do
local retval, nameout = reaper.GetMIDIInputName( devIdx, '' )
local take = reaper.GetActiveTake( item )
if i ~= 1 then take = reaper.AddTakeToMediaItem( item ) end
local retval, nameout = reaper.GetMIDIInputName( devIdx, '' )
reaper.GetSetMediaItemTakeInfo_String( take, 'P_NAME', nameout, 1 )
local lpos = ApplyMIDIdata(take, midi_t[devIdx], itempos, SR)
if lpos >= 0 then
reaper.SetMediaItemPosition(item,lpos,false)
end
i = i + 1
end
-- update arrange
reaper.UpdateArrange()
end
DumpRetrospectiveLog()
Much thanks to Justin.
Added to Reapack as mpl_Dump Retrospective Record log.
|
Thanks Justin and mpl! Really excited for this.
I tried this on 6.39rc2 with Virtual MIDI keyboard (traveling so can't unfortunately test with non-VMK methods today) and for some reason this just creates a tiny event:
with then notes all bunched up with minimum lengths at the point where I first played a note
I checked quickly and projPos at least seems to get correct locations from the API, so I think something happens at the MIDI translation phase. Unfortunately I have to run so I can't help more today, will try to provide more details tomorrow if this doesn't happen for anyone else!
|
|
|
10-27-2021, 11:41 PM
|
#34
|
Human being with feelings
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
|
Quote:
Originally Posted by paaltio
Thanks Justin and mpl! Really excited for this.
I tried this on 6.39rc2 with Virtual MIDI keyboard (traveling so can't unfortunately test with non-VMK methods today) and for some reason this just creates a tiny event:
with then notes all bunched up with minimum lengths at the point where I first played a note
I checked quickly and projPos at least seems to get correct locations from the API, so I think something happens at the MIDI translation phase. Unfortunately I have to run so I can't help more today, will try to provide more details tomorrow if this doesn't happen for anyone else!
|
Do you tap vkb while playing or while stopped? Are you sure it is in rc2?
|
|
|
10-27-2021, 11:56 PM
|
#35
|
Human being with feelings
Join Date: Aug 2011
Location: Los Angeles, CA
Posts: 308
|
Quote:
Originally Posted by mpl
Do you tap vkb while playing or while stopped? Are you sure it is in rc2?
|
During playback mainly. I did try stopped as well, same minimum length item with notes at edit cursor there. And yes version is rc2
|
|
|
10-28-2021, 02:47 AM
|
#36
|
Human being with feelings
Join Date: Sep 2021
Location: Berlin
Posts: 1,932
|
This is working for me in RC2 on macOS. WOW, thank you!
|
|
|
10-28-2021, 05:54 AM
|
#37
|
Human being with feelings
Join Date: Aug 2011
Location: Los Angeles, CA
Posts: 308
|
Ok I think the bug has something to do with project start time. I set it to 0:00.000 and now it works. Non-zero start times cause this issue for me.
Looks like projPos does not include the offset, but it probably needs to be added there for the MIDI translation?
|
|
|
10-28-2021, 12:33 PM
|
#38
|
Human being with feelings
Join Date: Aug 2011
Location: Los Angeles, CA
Posts: 308
|
Okay, figured it out, you need to take the offset into account when you're using format_timestr_pos to find out the sample rate. Here's how I got it working here:
Code:
local SR = tonumber(reaper.format_timestr_pos( 1 - reaper.SNM_GetDoubleConfigVar("projtimeoffs", 0), '', 4 ))
I forget if there's another way to get the project start time, but anyway this at least as a proof of concept. Ideally I guess there would be another way to get the sample rate, but I guess there isn't any?
|
|
|
10-28-2021, 12:48 PM
|
#39
|
Human being with feelings
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
|
Quote:
Originally Posted by paaltio
Okay, figured it out, you need to take the offset into account when you're using format_timestr_pos to find out the sample rate. Here's how I got it working here:
Code:
local SR = tonumber(reaper.format_timestr_pos( 1 - reaper.SNM_GetDoubleConfigVar("projtimeoffs", 0), '', 4 ))
I forget if there's another way to get the project start time, but anyway this at least as a proof of concept. Ideally I guess there would be another way to get the sample rate, but I guess there isn't any?
|
Code:
SR = tonumber(reaper.format_timestr_pos( 1-reaper.GetProjectTimeOffset( 0,false ), '', 4 ))
Probably I have to rewrite code from scratch for better timing relations while playing, supporting loops etc, seems not an easy task, so I hope to release new version within a week.
|
|
|
10-28-2021, 12:54 PM
|
#40
|
Human being with feelings
Join Date: Aug 2011
Location: Los Angeles, CA
Posts: 308
|
Quote:
Originally Posted by mpl
Code:
SR = tonumber(reaper.format_timestr_pos( 1-reaper.GetProjectTimeOffset( 0,false ), '', 4 ))
Probably I have to rewrite code from scratch for better timing relations while playing, supporting loops etc, seems not an easy task, so I hope to release new version within a week.
|
Ok, thanks very much for putting in the effort, look forward to it!
|
|
|
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:50 AM.
|