Can "timer zero" events be injected BEFORE any already buffered messages?

pedro.estrela

2020-03-11 10:28:57

ISSUE:
I'm using "timer zero" events to a) merge multiple triggers; b) trigger multiple actions and c) modularize my code (with global variables). This works fine for messages that arrive slowly.

But this fails when messages arrive at the same time quantum. In that case, the timer zero events are processed AFTER the buffered messages (demo below).

QUESTION:

Is there a way to make timer-zero events be injected in the engine BEFORE any buffered messages, but AFTER any buffered timer-zero events that might be already there?

 

MANUAL page 43:

A Timer will, when its time has elapsed, inject an Incoming Event into the processing engine.

A trick is to use a one-shot timer with 0 delay: this will cause the current input event to be fully processed, and the timer event will be processed immediately, too – possibly already in parallel to the current event.

 

DEMO:

Please see the annexed project. In the log below, first 2x messages arrive slowly; in case 2, they arrive back to back, and "gb" becomes 108 instead of 107

 

 

Case 1: messages arrive slowly

742241 - MIDI IN [DDJ-SZ]: 91 1E 7F
742241 - IN 0.1 Note On on any channel=2 set 'qq' to ch. with any note and 'oo' to note=30 with any velocity and 'pp' to velocity=127
742241 - RULE 0.1:1 assignment: (gb=oo) = 30
742241 - OUT 0.1 One-shot timer "process_it": 0 ms delay
742241 - IN 0.4 On timer "process_it"
742241 - RULE 0.4:1 expression: (gb=gb+1) = 31    <<<<< OK!

 

747162 - MIDI IN [DDJ-SZ]: 91 1E 00

747162 - IN 0.2 Note Off on any channel=2 set 'qq' to ch. with any note and 'oo' to note=30 with any velocity and 'pp' to velocity=0
747162 - RULE 0.2:1 assignment: (gb=oo) = 30
747162 - OUT 0.2 One-shot timer "process_it": 0 ms delay
747162 - IN 0.4 On timer "process_it"
747162 - RULE 0.4:1 expression: (gb=gb+1) = 31   <<<<< OK!

 

CASE 2: messages arrive back to back

758921 - MIDI IN [DDJ-SZ]: 91 6A 7F
758921 - IN 0.1 Note On on any channel=2 set 'qq' to ch. with any note and 'oo' to note=106 with any velocity and 'pp' to velocity=127
758921 - RULE 0.1:1 assignment: (gb=oo) = 106

758921 - MIDI IN [DDJ-SZ]: 91 6A 00
758921 - IN 0.2 Note Off on any channel=2 set 'qq' to ch. with any note and 'oo' to note=106 with any velocity and 'pp' to velocity=0
758921 - OUT 0.1 One-shot timer "process_it": 0 ms delay
758921 - RULE 0.2:1 assignment: (gb=oo) = 106

758921 - IN 0.4 On timer "process_it"
758921 - RULE 0.4:1 expression: (gb=gb+1) = 107     <<<<<<<< OK!
758921 - OUT 0.2 One-shot timer "process_it": 0 ms delay
758921 - IN 0.4 On timer "process_it"
758921 - RULE 0.4:1 expression: (gb=gb+1) = 108     <<<<<<<< BAD: This should be 107!

 


Attachments:

example back to back prcessing.bmtp

Steve-Bome Forum Moderator

2020-03-11 15:31:40

Hi, in your example you would need 2 timers and a mechanism to "queue up" the next message for later processing.  When triggering the first timer, you would need to set a global variable to indicate "timer1 busy".  On the second incoming action, you would need to  immeediately store the incoming action for later processing but with no output.  When your first timer finishes, you would set the timer busy variable back to 0 and then trip a 2nd timer to release the action you wanted with the second event. Message "queuing" is not really pretty in MT Pro so if you describe in more detail an example of what you are trying to accomplish, maybe I can assist further. 

 

Steve Caldwell
Bome Q and A Moderator and
Independent Bome Consultant/Specialist
bome@sniz.biz

pedro.estrela

2020-03-13 17:41:10

comment

thanks for the answer. Indeed I'm trying to avoid building my own internal message passing system with my own stack, my own event handler loop, etc :) Instead I'm requesting a specific change to the BOME event handler: that timer-zero events always get priority, instead of being put in the end of the queue. (regular Timers and messages would be treated as of today.) ---- About my use case: I'm have a complex map for the DDJ-1000: https://github.com/pestrela/music_scripts/blob/master/traktor/mapping_ddj_1000/DDJ-1000%20v6.7.0%20-%20BOME-side%20Mapping.bmtp and I've now extended it to ADDITIONALLY translate other Pioneer devices, using the time-zero methos you suggested here https://www.bomeloft.com/support/kb/cascaded-presets-loopback-devices

pedro.estrela

2020-03-13 17:42:28

comment

is there a way to keep basic formatting in the comments?

Steve-Bome Forum Moderator

2020-03-13 18:14:46

comment

We are looking into other forum solution alternatives.

PAC

2020-04-04 14:13:42

I have the same issue when receiving a midi dump from Cubase. Issue - a Timer can capture all input but the actioned translators don't process quick enough to keep up with messages arriving at the Timer.
Alternatives to writing your own queue manager are
1. Make your Timer more selective to reduce the number of times it 'fires'. Eg instead of monitoring for all note on messages, focus on the range of note on messages you actually want to process. If you still have the issue then 2.
2. Make the Timer store its input into global variables and have a timer related translator process those variables rather than the actual midi messages. A point to note is that although that translator may not always fire at the same time as the Timer (dropped messages), the last message in a fast stream arriving at the Timer will always be caught by the called translator so the last times the translator performs its actions it will be working on an up to date set of variables.
An example: In Cubase if a mixer solo button is actioned, Cubase will fire off a rapid stream of mixer mute button on messages as it automatically mutes all other mixer channels. If you have a Timer checking for note on messages with a called translator processing those messages, some will be dropped because the translator can't keep up. The solution is for the Timer to set a number of global variables, one for each mute button message received. Every time the translator is called it will examine all those mute variables and perform the required actions. It doesn't matter if it 'misses' a few Timer events as it's processing the variables not the input messages. When the Timer stops receiving the 'fast stream', the last message received will always trigger the underlying translator so no input has been lost.

Hope that helps.

Steve-Bome Forum Moderator

2020-04-04 16:26:41

comment

Great comments @PAC. One should also realize the translators themselves are extremely fast and can sometime work in parallel. Usually missing incoming MIDI messages is more of an issue of too many rules to process for the given incoming MIDI message. Minimize the number of rules. Maybe just set a global variable as you suggested. And do the heavy lifting later in timers. I usually use a bitmap of note "states" to reduce the number of global variables needed. It is more complex set up but you can use only 1 global variable for every 32 on/off states that you want to track since Bome MIDI Translator Pro used 32 bit signed integers. Steve Caldwell Bome Q and A Moderator and Independent Bome Consultant/Specialist bome@sniz.biz

pedro.estrela

2020-04-04 16:45:55

comment

thanks @PAC. When ONLY the last state is relevant I'm already doing this. For an example see line 547 of https://github.com/pestrela/music/blob/master/traktor/mapping_ddj_1000/Support%20files/Technical%20Info%20-%20BOME%20DDJ%201000%20Screens.txt ; code is 12.43 to 12.57 of https://github.com/pestrela/music/blob/master/traktor/mapping_ddj_1000/DDJ-1000%20v6.8.0%20-%20BOME-side%20Mapping.bmtp

pedro.estrela

2020-04-04 16:55:54

comment

@Steve No doubt that with high number of rules may lead to missing messages; but the original example of this thread is something else (only 3 trivial rules)

pedro.estrela

2020-05-04 19:51:03

Just to chime in again 6 weeks later. 

Right now I'm duplicating massive amounts of code in parallel translators to make each translator run as as a single action.
Removing timers was the only way to preserve atomicity; this damaged modularity a lot.

I'm still convinced my requested "cirurgical" OPTION would solve my probelms.

Steve-Bome Forum Moderator

2020-05-04 20:35:25

Hi Pedro,

What version of MT Pro are you running? I'm testing with 1.8.4 built 962 and cannot seem to duplication your issue.

See log below:

754450 - MIDI IN [Bome MIDI Translator 1 Virtual In]: 91 6A 7F
754450 - IN 0.1 Note On on any channel=2 set 'qq' to ch. with any note and 'oo' to note=106 with any velocity and 'pp' to velocity=127
754450 - RULE 0.1 assignment: (gb=oo) = 106
754451 - OUT 0.1 One-shot timer "process_it": 0 ms delay
754451 - MIDI IN [Bome MIDI Translator 1 Virtual In]: 91 6A 00
754451 - IN 0.4 On timer "process_it"
754451 - RULE 0.4 expression: (gb=gb+1) = 107
754451 - IN 0.2 Note Off on any channel=2 set 'qq' to ch. with any note and 'oo' to note=106 with any velocity and 'pp' to velocity=0
754451 - RULE 0.2 assignment: (gb=oo) = 106
754451 - OUT 0.2 One-shot timer "process_it": 0 ms delay
754451 - IN 0.4 On timer "process_it"
754451 - RULE 0.4 expression: (gb=gb+1) = 107

 

Your project file looks good, however be careful not to use the second delay value as if you do, the second delay will always fire

 

Steve Caldwell
Bome Q and A Moderator and
Independent Bome Consultant/Specialist
bome@sniz.biz

 


Attachments:

delay-usage.png

pedro.estrela

2020-05-05 01:12:12

comment

yes I'm using 1.8.4 built 962. I have no delayed messages, only timer=zero actions.

__QUESTION__: Did your messages came from the hardware simultaneoulsy? 
Because thats what my DDJ generates - two messages back to back.

I've reduced the log to the bare mininum.
It shows that the second message is processed BEFORE the timer triggered by the first message.

Pedro
  758921– MIDI IN [DDJ-SZ]: 91 6A 7F
  758921 – MIDI IN [DDJ-SZ]: 91 6A 00
  758921 – OUT 0.1 One-shot timer “process_it”: 0 ms delay

Steve:
  754450 - MIDI IN [Bome MIDI Translator 1 Virtual In]: 91 6A 7F
  754451 - OUT 0.1 One-shot timer "process_it": 0 ms delay
  754451 - MIDI IN [Bome MIDI Translator 1 Virtual In]: 91 6A 00

thanks,

Steve-Bome Forum Moderator

2020-05-05 01:34:17

pedro.estrela says:

yes I\'m using 1.8.4 built 962. I have no delayed messages, only timer=zero actions.

__QUESTION__: Did your messages came from the hardware simultaneoulsy?
Because thats what my DDJ generates - two messages back to back.

I\'ve reduced the log to the bare mininum.
It shows that the second message is processed BEFORE the timer triggered by the first message.

Pedro
758921– MIDI IN [DDJ-SZ]: 91 6A 7F
758921 – MIDI IN [DDJ-SZ]: 91 6A 00
758921 – OUT 0.1 One-shot timer “process_it”: 0 ms delay

Steve:
754450 - MIDI IN [Bome MIDI Translator 1 Virtual In]: 91 6A 7F
754451 - OUT 0.1 One-shot timer \"process_it\": 0 ms delay
754451 - MIDI IN [Bome MIDI Translator 1 Virtual In]: 91 6A 00

 

Interesting. Maybe the log messages themselves are delayed (not real time).  Since MIDI data is serial, the only way to send two MIDI messages simultaneously would be if they came through two separate interfaces.  I'm speculating that you have one thread processing the input messages and another thread processing the log messages and the incoming messages have higher priority than the log messages.

 

I used Bome SendSX to send the two messages I sent. (no delays).

In either case, I'm not sure what you are trying to solve.

According to the documentation, when a timer trips in succession before the timer has a chance to trigger, the second trigger will override the first.  So I think a zero timer might have some chance of not completing it's processing before the second timer triggers.   If I can reliably produce an error, I'm happy to report it as a bug.  I think.  At least I can have Florian look at it to see if there might be something wrong in the timing logic.

 

Steve Caldwell
Bome Q and A Moderator and
Independent Bome Consultant/Specialist
bome@sniz.biz

Florian Bome

2020-05-05 12:44:19

Hi,
I try to shed some light on these timers.

That explains why in your example, the timer is sometimes executed twice, sometimes once.

Maybe there is a way to restructure your project file so that it does the per-message processing in the translator directly, and then trigger the timer?

I also think that a new feature in MT Pro would be useful: Call a Timer. It would be like a one-shot, 0ms timer, but without the retrigger behavior: every invocation would trigger exactly once.

I hope this helps some... at least to explain what's happening.

pedro.estrela

2020-05-05 13:51:53

This trivial project counts 7-bit pending messages. Each message calls a zero-timer that resets the counter.

To see the race condition, just move a 14-bit high resolution fader/knob, and then searchin the log when "ga" becomes "2"

This is the crucial part of the log, see the text file for the whole log:

228461 - RULE 1.2:1 assignment: (ga=0) = 0
228465 - RULE 1.1:1 expression: (ga=ga+1) = 1
228465 - RULE 1.1:1 expression: (ga=ga+1) = 2        <<<<<<<<<<<< this is my problem
228465 - RULE 1.2:1 assignment: (ga=0) = 0
228465 - RULE 1.2:1 assignment: (ga=0) = 0

I would happily accept any lower-performance option that would remove this issue. Thank you.

 


Attachments:

zero shot timers race condition - 14-bit mesages processed as 7-bit.bmtp
zero shot timers race condition - 14-bit mesages processed as 7-bit.txt

pedro.estrela

2020-05-05 13:57:00

comment

Florian/Steve I've now made a NEW trivial example for you to recreate the issue yourself. Just inject 14-bit messages from a fader in this new project. "Maybe there is a way to restructure your project file so that it does the per-message processing in the translator directly, and then trigger the timer?" >>>> I did that and it damaged the modularity a lot.

pedro.estrela

2020-05-05 13:59:17

comment

I've marked it as "best answer" so that you can finf it in this thread.

Florian Bome

2020-05-05 14:35:25

comment

Thanks, Pedro, for the additional explanation and demo.

Florian Bome

2020-05-05 14:35:57

comment

To emphasize again: while this behavior leads to a race condition in this project, and to unwanted results, it is not a bug in MIDI Translator Pro. It is by design that MT Pro is parallelizing processing where possible to achieve the best real time performance. Very tight timing, as in your example, inevitably leads to race conditions.

Florian Bome

2020-05-05 14:40:08

comment

To analyze what's happening: in most cases, the timer is executed directly after processing the incoming MIDI message. In the case you flagged as problem, one execution of the timer is omitted, because of the "retrigger" behavior, discussed in my answer below. Therefore, ga is increased one more time, before being reset in the timer.

Florian Bome

2020-05-05 14:45:29

comment

Now if we added a "call timer", which does not have "retrigger" behavior, but executes the timer for every invocation, you will still have a race condition in the demo project, and you would still see ga=2 occasionally: there is no guarantee when the timer will be executed (because it's in parallel). On a multi-core processor, it might be executed truely in parallel. On a single-processor, it's likely to be scheduled, and possibly executed after one (or more) incoming MIDI messages get processed. On closer inspection, I think that's what's happening in your demo project, too.

pedro.estrela

2020-05-05 14:51:19

comment

Its clear this this is not a bug, but an artifact of the initial BOME design. However in my use-cases MIDI is just not competitive for high-performance cases (Scratching). As such I would gadly pay for a single threaded version that prioritizes zero-timers above processing new queued messages.

Steve-Bome Forum Moderator

2020-05-05 16:53:02

Pedro, Is this only an issue with incoming 14 bit values?

What is the behavior you want with 2 incoming CC's

1) Every 14 bit CC value increments the counter just once?
Only the first iteration increments the counter?
Only the second iteration increments the counter?

2) Are there similar situations needed (ie for note-on note off combinations coming in quickly)

3) Each incoming message is queued and increments the counter (I don't think this is what you want)

Is it important to not drop either incoming message or are you simply wanting to count them?

Rather than focus on design change, I'm trying to figure out if there is a way to make this work for you under the current design. I'll let Florian decide if he wants to make a design change while I focus on trying to meet your immediate needs using the current design.

Steve

pedro.estrela

2020-05-05 17:17:09

comment

the demo was a pure mockup to make you and Florian see the issue on your own hardware. In the real project I'm not breaking 14-bit CCs or just counting messages.

pedro.estrela

2020-05-05 17:18:14

comment

What i'm actually doing is using dozens of timer-zeros to approximate "a call subroutine" semantics. This is recursive by nature. In general it works, until messages arrive from the hardware too fast, or the specific thread CPU gets a slowdown.

pedro.estrela

2020-05-05 17:21:26

comment

This leads to my concrete request for such experimental option: anytime you you going to deque work, peek the queue and select the first timer-zero event of the queue. If there is no timerzero event, dequeue the first event as normal.

Steve-Bome Forum Moderator

2020-05-05 18:14:19

comment

Thanks Pedro! Since what you are asking is a design change. I will let Florian handle the request. If you are looking for a workaround, I might be able to help with some concrete examples of situations you want me to look at like you showed in your illustration. I think you understand the MT Pro does not have the concept of a "function call" so indeed they can be partially implemented using timers and other techniques. For your example on incoming CC, I can certainly provide a mechanism to catch and count only the first iteration (until the timer completes). I might even be able to count only the last iteration. But if this doesn't help with the big picture, it may be of limited value. One observation is that you are incrementing the value within the initial translator rather than with the timer, I would recommend, in most cases the increment/decrement happen within the timer itself. You can also set a "busy" flag in the initial translator and have the timer clear it when done so that essentially you ignore incoming actions within the first translator if the busy flag is still set. Steve Caldwell Bome Q and A Moderator and Independent Bome Consultant/Specialist bome@sniz.biz

Florian Bome

2020-05-06 00:40:35

comment

Hi Pedro, a "call timer" will be easy to add, i.e. ensure that every outgoing "call timer" will result in exactly one incoming timer event. But when that event is executed is still up to the engine...

Florian Bome

2020-05-06 00:42:34

comment

Your idea of prioritizing 0-timers will not work: they are going through a different queue than incoming MIDI messages, which is yet a different queue for normal timers... and it would not fix the issue, but only make it less likely to occur.

pedro.estrela

2020-05-14 02:19:09

Still the same issue. But now made a workaround that reduces the impact.
.
When you see events being trigered in an undesired order, delay the rule that should execute last by adding an intermediate timer (for that rule only).

.
.
BEFORE:
-------
MIDI -> Rule_1 -> Timer_A (0ms)
Timer_A -> Rule_2
Timer_A -> Rule_3

Most of the time Rule2 is executed before Rule3, but not always.

AFTER:
------
MIDI -> Rule_1 -> Timer_A (0ms)
Timer_A -> Rule_2
Timer_A -> Timer_B (30ms)
Timer_B -> Rule_3

This way the situation is significanlty improved

Steve-Bome Forum Moderator

2020-05-14 22:09:49

comment

Hmm, not sure I understand so for a change, I might reach out to you later for help if I run into the situation.

pedro.estrela

2020-05-14 23:14:12

(redacted)

pedro.estrela

2020-05-14 23:16:40

comment

you are welcome. I’ve put a small example of the idea on the comment itself