Old 10-15-2021, 11:03 AM   #1
daniellumertz
Human being with feelings
 
daniellumertz's Avatar
 
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
Default 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.
daniellumertz is online now   Reply With Quote
Old 10-16-2021, 11:59 AM   #2
Meo-Ada Mespotine
Human being with feelings
 
Meo-Ada Mespotine's Avatar
 
Join Date: May 2017
Location: Leipzig
Posts: 6,612
Default

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.
__________________
Use you/she/her.Ultraschall-Api Lua Api4Reaper - Donate, if you wish

On vacation for the time being...
Meo-Ada Mespotine is offline   Reply With Quote
Old 10-16-2021, 12:03 PM   #3
daniellumertz
Human being with feelings
 
daniellumertz's Avatar
 
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
Default

I would be fine if the mods move to pre sub .

Thanks for the update mespotine
daniellumertz is online now   Reply With Quote
Old 10-16-2021, 10:57 PM   #4
daniellumertz
Human being with feelings
 
daniellumertz's Avatar
 
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
Default

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.
daniellumertz is online now   Reply With Quote
Old 10-16-2021, 11:14 PM   #5
daniellumertz
Human being with feelings
 
daniellumertz's Avatar
 
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
Default

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.
daniellumertz is online now   Reply With Quote
Old 10-17-2021, 04:07 AM   #6
sockmonkey72
Human being with feelings
 
sockmonkey72's Avatar
 
Join Date: Sep 2021
Location: Berlin
Posts: 1,932
Default

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.
sockmonkey72 is online now   Reply With Quote
Old 10-17-2021, 04:46 AM   #7
Justin
Administrator
 
Justin's Avatar
 
Join Date: Jan 2005
Location: NYC
Posts: 15,716
Default

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)
Justin is offline   Reply With Quote
Old 10-17-2021, 06:46 PM   #8
daniellumertz
Human being with feelings
 
daniellumertz's Avatar
 
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
Default

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
daniellumertz is online now   Reply With Quote
Old 10-18-2021, 05:40 AM   #9
Justin
Administrator
 
Justin's Avatar
 
Join Date: Jan 2005
Location: NYC
Posts: 15,716
Default

Quote:
Originally Posted by daniellumertz View Post
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)
Justin is offline   Reply With Quote
Old 10-18-2021, 05:48 AM   #10
sockmonkey72
Human being with feelings
 
sockmonkey72's Avatar
 
Join Date: Sep 2021
Location: Berlin
Posts: 1,932
Default

Quote:
Originally Posted by Justin View Post
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!
sockmonkey72 is online now   Reply With Quote
Old 10-18-2021, 06:53 AM   #11
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,171
Default

Quote:
Originally Posted by Justin View Post
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...
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website
lb0 is offline   Reply With Quote
Old 10-18-2021, 07:07 AM   #12
sockmonkey72
Human being with feelings
 
sockmonkey72's Avatar
 
Join Date: Sep 2021
Location: Berlin
Posts: 1,932
Default

Quote:
Originally Posted by lb0 View Post
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.
sockmonkey72 is online now   Reply With Quote
Old 10-18-2021, 07:33 AM   #13
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,171
Default

Quote:
Originally Posted by sockmonkey72 View Post
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.
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website
lb0 is offline   Reply With Quote
Old 10-18-2021, 07:40 AM   #14
daniellumertz
Human being with feelings
 
daniellumertz's Avatar
 
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
Default

Quote:
Originally Posted by Justin View Post
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
daniellumertz is online now   Reply With Quote
Old 10-18-2021, 08:39 AM   #15
_Stevie_
Human being with feelings
 
_Stevie_'s Avatar
 
Join Date: Oct 2017
Location: Black Forest
Posts: 5,048
Default

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.
__________________
My Reascripts forum thread | My Reascripts on GitHub
If you like or use my scripts, please support the Ukraine: Ukraine Crisis Relief Fund | DirectRelief | Save The Children | Razom
_Stevie_ is offline   Reply With Quote
Old 10-18-2021, 10:08 AM   #16
Meo-Ada Mespotine
Human being with feelings
 
Meo-Ada Mespotine's Avatar
 
Join Date: May 2017
Location: Leipzig
Posts: 6,612
Default

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?
__________________
Use you/she/her.Ultraschall-Api Lua Api4Reaper - Donate, if you wish

On vacation for the time being...
Meo-Ada Mespotine is offline   Reply With Quote
Old 10-18-2021, 01:43 PM   #17
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,171
Default

Quote:
Originally Posted by Meo-Ada Mespotine View Post
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.
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website
lb0 is offline   Reply With Quote
Old 10-19-2021, 09:17 AM   #18
daniellumertz
Human being with feelings
 
daniellumertz's Avatar
 
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
Default

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.
daniellumertz is online now   Reply With Quote
Old 10-19-2021, 10:12 AM   #19
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

Quote:
Originally Posted by lb0 View Post
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!)
gxray is offline   Reply With Quote
Old 10-19-2021, 10:21 AM   #20
daniellumertz
Human being with feelings
 
daniellumertz's Avatar
 
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
Default

Quote:
Originally Posted by gxray View Post
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 .
daniellumertz is online now   Reply With Quote
Old 10-21-2021, 01:55 AM   #21
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,171
Default

Quote:
Originally Posted by gxray View Post
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.
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website
lb0 is offline   Reply With Quote
Old 10-22-2021, 11:53 AM   #22
mpl
Human being with feelings
 
mpl's Avatar
 
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
Default

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.
mpl is offline   Reply With Quote
Old 10-22-2021, 12:07 PM   #23
mpl
Human being with feelings
 
mpl's Avatar
 
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
Default

Quote:
Originally Posted by lb0 View Post
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.
mpl is offline   Reply With Quote
Old 10-22-2021, 01:05 PM   #24
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,171
Default

Quote:
Originally Posted by mpl View Post
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.
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website
lb0 is offline   Reply With Quote
Old 10-22-2021, 04:28 PM   #25
denmla
Human being with feelings
 
Join Date: Jul 2009
Posts: 121
Default

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
denmla is offline   Reply With Quote
Old 10-22-2021, 06:20 PM   #26
mpl
Human being with feelings
 
mpl's Avatar
 
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
Default

Quote:
Originally Posted by denmla View Post
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.
mpl is offline   Reply With Quote
Old 10-22-2021, 06:23 PM   #27
mpl
Human being with feelings
 
mpl's Avatar
 
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
Default

Quote:
Originally Posted by lb0 View Post

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.
mpl is offline   Reply With Quote
Old 10-23-2021, 01:50 AM   #28
denmla
Human being with feelings
 
Join Date: Jul 2009
Posts: 121
Default

Quote:
Originally Posted by mpl View Post
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!
denmla is offline   Reply With Quote
Old 10-23-2021, 02:23 AM   #29
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,171
Default

Quote:
Originally Posted by mpl View Post
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.
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website
lb0 is offline   Reply With Quote
Old 10-23-2021, 02:44 AM   #30
mpl
Human being with feelings
 
mpl's Avatar
 
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
Default

Quote:
Originally Posted by lb0 View Post
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.
mpl is offline   Reply With Quote
Old 10-27-2021, 02:22 PM   #31
mpl
Human being with feelings
 
mpl's Avatar
 
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
Default

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.
mpl is offline   Reply With Quote
Old 10-27-2021, 03:34 PM   #32
daniellumertz
Human being with feelings
 
daniellumertz's Avatar
 
Join Date: Dec 2017
Location: Brazil
Posts: 1,987
Default

Thanks Mpl and justin for the code above. I will test at next dev version!

you both rock!
daniellumertz is online now   Reply With Quote
Old 10-27-2021, 11:38 PM   #33
paaltio
Human being with feelings
 
Join Date: Aug 2011
Location: Los Angeles, CA
Posts: 308
Default

Quote:
Originally Posted by mpl View Post
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!
paaltio is offline   Reply With Quote
Old 10-27-2021, 11:41 PM   #34
mpl
Human being with feelings
 
mpl's Avatar
 
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
Default

Quote:
Originally Posted by paaltio View Post
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?
mpl is offline   Reply With Quote
Old 10-27-2021, 11:56 PM   #35
paaltio
Human being with feelings
 
Join Date: Aug 2011
Location: Los Angeles, CA
Posts: 308
Default

Quote:
Originally Posted by mpl View Post
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

paaltio is offline   Reply With Quote
Old 10-28-2021, 02:47 AM   #36
sockmonkey72
Human being with feelings
 
sockmonkey72's Avatar
 
Join Date: Sep 2021
Location: Berlin
Posts: 1,932
Default

This is working for me in RC2 on macOS. WOW, thank you!
sockmonkey72 is online now   Reply With Quote
Old 10-28-2021, 05:54 AM   #37
paaltio
Human being with feelings
 
Join Date: Aug 2011
Location: Los Angeles, CA
Posts: 308
Default

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?
paaltio is offline   Reply With Quote
Old 10-28-2021, 12:33 PM   #38
paaltio
Human being with feelings
 
Join Date: Aug 2011
Location: Los Angeles, CA
Posts: 308
Default

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?
paaltio is offline   Reply With Quote
Old 10-28-2021, 12:48 PM   #39
mpl
Human being with feelings
 
mpl's Avatar
 
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,956
Default

Quote:
Originally Posted by paaltio View Post
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.
mpl is offline   Reply With Quote
Old 10-28-2021, 12:54 PM   #40
paaltio
Human being with feelings
 
Join Date: Aug 2011
Location: Los Angeles, CA
Posts: 308
Default

Quote:
Originally Posted by mpl View Post
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!
paaltio 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 02:50 AM.


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