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:
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.
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:
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.
- When you start a timer ("set" a timer), it will always retrigger that timer. So if a timer with that name is already pending, it is killed first.
- One-Shot, 0ms timers are optimized, but are still enqueued asynchronously and executed concurrently to other processing
- As Steve said, two MIDI simultaneous messages from one MIDI port are really coming one after another, even if they appear to arrive at once.
- Now if two "simultaneous" MIDI messages trigger the same one-shot, 0ms timer, then the behavior is not defined.Either one of these two happens:
- The first timer gets enqueued and executes before the second MIDI message is processed. Then the second message will trigger the timer again. The timer gets executed twice.
- The first timer gets enqueued, then the second MIDI message is processed. Because the timer has not yet executed, it is first killed (dequeued), and then the timer for the second MIDI message is enqueued (which is the same). Eventually, the timer is only executed once.
- In many other use cases, the retriggering behavior of timers is very useful. However, here, it leads to inconsistent results here (depending on processor speed, number of processor cores, overall system load, etc.).
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:
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
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