View Single Post
Old 04-14-2019, 01:08 AM   #90
Human being with feelings
mschnell's Avatar
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 7,241
Default Offset parameter in Midi send and receive with JSFX

Within Reaper, any processing is done strictly in terms of sample-blocks. Complete sample blocks are transferred to and from VSTs. And the environment, that is constructed for any JSFX instance, communicates with the Reaper audio/Midi infrastructure in terms of complete sample blocks.

Any "instance" in Reaper works on a sample block as soon as this is available (e.g. by the previous station in the FX chain), as long as the complete processing latency is not exceeded, which is relevant for blocks read from a file. Hence usually any plugin always sees a block way ahead of the time it is output to an audio device. At max as early as the complete processing latency which is denoted by the sampling frequency, block size and block (buffer) count set with the audio interface (the total can be several seconds).

In fact, with any track (FX chain), Reaper uses a single block buffer at the time, and a single OS thread, and calls the plugins one after the other, providing the memory address of this block, and each plugin just modifies the content of that block. On return from one plugin, Reaper just continues with the next one. "Normal" plugins simply modify the block content when they are called. Complex plugins (such as Kontakt) that create their own OS threads, also only are allowed to do exactly that, but they can work in the background to prepare the information they are going to write in the next block, once they are called by Reaper.

Regarding JSFX, Reaper calls @block as soon as a block is available from the previous thingy in the FX chain. After @block returns, Reaper performs a loop with "samplesblock" iterations to iteratively call @sample. With each iteration it provides values smpl0 ...smpl63 from the appropriate sample block, and after return from @sample updates the modified values of smpl0 ...smpl63 into the sample block.

Hence there is no such thing as "timing" @block vs @sample. They need to be considered to be called "at the same point in time", and that point in time is not in any way related to other points in time throughout Reaper. That is why it does not make any sense to transfer realtime (block- or even sample-accurate) information between multiple instances of plugins via global memory. The "nearby" calls to the communicating plugins can be done with different blocks, the timing can differ by the total "complete processing latency". If in the same track, the "global" communication can overtake the block sequence, if the plugins are located in different tracks, the timing correlation is just random.

Regarding Midi, Reaper marks each Midi message by the samples block the time of duration of which the message is to be located in, and the offset in samples (i.e. 1 / sample frequency) to exactly denote the "virtual" point in time.

Now when receiving Midi in a JSFX (no matter if in @block or in @sample), midirecv() will provide the next Midi message associated with the sample block the JSFX infrastructure is just working on, and provide the correct offset (regarding that sample block) in the "offset" variable.

When sending a Midi message by midisend(), the message is associated to the sample block the JSFX infrastructure is just working on, and the offset given is stored with that message.

Hence it is not necessary to do midirecv() or midisend() in @sample. In fact doing midirecv() in @sample crates a lot of unnecessary overhead - you need to do it an a loop anyway, and the first call of @sample after an @block will output all messages associated with that sample block. Regarding midisend(), I understand that there is not much difference between doing your own loop of "samplesblock" iterations, or using @sample. (In fact a loop of "samplesblock" iterations might be avoidable to decrease CPU overhead, but would often need a much more complex algorithm to calculate the (virtual) timing (e.g. of a ramp of parameter values).)

This is why for sample accurate Midi "timing", only the correct management of the "offset" is critical, creating a "virtual timing", while the "physical timing" (when a message is received or sent) does not matter at all.

Reaper might be hoped to do special handling with midisend() in @sample events (e.g. ignore the offset given and replace it by the offset affiliated to the audio sample to be handled in that loop cycle (i.e. the loop counter), I did a test verifying that the offset value is just left as it is even when sending a midi message in @sample. Hence it does not make much sense to do midisend() in @sample.

If for the same sample block multiple Midi messages with the same offset are sent, Reaper will handle all of them, but supposedly the sequence they will occur in is undefined.


Last edited by mschnell; 04-29-2019 at 11:52 AM.
mschnell is offline   Reply With Quote