Old 06-24-2022, 06:24 AM   #1
jack461
Human being with feelings
 
jack461's Avatar
 
Join Date: Nov 2013
Location: France
Posts: 181
Default Precise date of samples in a JSFX

Precise date of samples in a JSFX

Request:

Get the precise date in the @block section of a JSFX effect of the first sample of the "block to generate" on that track.

Details:

This information could be given inside the @block section in the form of a special variable whose name could be first_sample_precise_time; a value of "723.5187327" meaning that the first sample of the current block will be sent to the audio interface at exactly that many seconds after the start of this REAPER session. It is therefore a kind of real-time information, and must be valid whether REAPER is playing or not.
In fact, I'm assuming here that as soon as REAPER is started, it continues to send audio-rate sample tuples to the connected audio card. Let's say we are using 10 channels at 48kHz, that would be 48,000 tuples of 10 samples per second. And I also assume that REAPER knows when these samples will be sent to the audio interface.

Purpose:

Allow two or more JSFX effects to synchronize their outputs, so that the samples they generate are sent to the audio interface (or file system) at the same time.

Example:

I write in JSFX an application which is a kind of synthesizer, composed of several JSFX modules, with a specific JSFX, the "controller" which manages the user interface, and several JSFX "generators", each on its own track, which produce sound, all plugins communicating with global shared memory, using the "gmem[]" construct.
Sometimes I need two generators to start producing their sounds at exactly the same time, even if one of them has other effects on its track after it. This has nothing to do with the concept of "beat" or "bpm", because REAPER is not in "play/play/record" mode when I use this app.

Thanks for your attention, help or suggestion.

J.Jack.
jack461 is online now   Reply With Quote
Old 06-24-2022, 07:03 AM   #2
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,686
Default

This is impossible, as @block and @sample is processed as much ahead of time as possible, filling the sample queue towards the audio interface with as much data as possible to prevent glitches due to the work not being able to be done because the CPU is busy with other stuff. This is necessary to prevent pops and dropouts.

This is none for each thread separately (e.g. to allow for multiple cores to work in parallel). Hence there is no synchronicity at among the @block calls and between those and realtime / Time of day / the point in time the samples had been generated.

-Michael
mschnell is offline   Reply With Quote
Old 06-24-2022, 09:41 AM   #3
jack461
Human being with feelings
 
jack461's Avatar
 
Join Date: Nov 2013
Location: France
Posts: 181
Default Impossible is not REAPER !

Michael, thanks for your answer. You are totally right about the processing ahead, but still I don't think this is impossible. You describe this as if everything was being done at random... Fortunately, it is not.

Just think of this example:

Track "A" contains clip "s1". Track "B" contains clip "s2". "s1" and "s2" are temporally aligned. At one time (say 3'17"345.66ms), effect E on track A receives the 512 samples it needs to handle. It generates its output. At another time (say 3'17"425.33ms), possibly on a different core, effect F on track B receives the 512 samples it must handle. It also generates its output. However, a little later, REAPER mixes the results and sends this mix exactly when it is supposed to be played, say 3'17"804.21ms. So REAPER knows that the result must be played at 3'17"804.21ms (otherwise, it couldn't sync its outputs), and of course it knows that before it sends the sample block to E and F. So I guess it's not that complicated for REAPER to send the sample block to the effect E, and to indicate that this block will be played at 3'17"804.21ms.

Maybe someone who knows exactly how this is implemented in REAPER can tell me if I'm right or wrong. However, this feature would let you generate synchronized outputs precise to the sample, and, for example, those who have been working with ChucK know how this underestimated feature is essential when designing a synthesizer, or more generally, any large application.

Best regards.

J.Jack.
jack461 is online now   Reply With Quote
Old 06-24-2022, 09:49 PM   #4
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,686
Default

Even it Reaper know when a sample is to be played (or at which virtual timestamp it is to be placed in a rendered file), this does not help much for synchronizing (e.g. via shared memory). The plugin in different tracks and FX chain positions might work at completely unrelated audio blocks and you don't know if your "partner" might be on many blocks later or already have been done many blocks in the (time of day) past. And a track is not allowed to "wait" for such block to be be at work in some other track.

-Michael
mschnell is offline   Reply With Quote
Old 06-25-2022, 12:38 AM   #5
jack461
Human being with feelings
 
jack461's Avatar
 
Join Date: Nov 2013
Location: France
Posts: 181
Default Programmers are Heros !

Michael, don't worry about that ! Programmers are Heros ! But thanks for your interventions, which help a lot clarifying the various aspects. If I ask for this feature, it is because, having thought about the problem for months, I know I can manage synchronicity between effects with it (an equivalent feature would be to get the number of the sample - I mean that, if I know that some sample is number 9494602 to be played, and the rate is 48kHz, this means that it will be played at 3'17"804.21ms. And the next sample will be played at this time plus 1/48000, which is 3'17"804.2308ms, etc).

Just imagine this scenario. The "controller" decides that "generator 3" (on track 3) will produce sound "X" at 3'19"36, and "generator 7" (on track 7) will produce sound "Y" also at 3'19"36. It writes these commands in the shared memory. Each generator, while producing zero valued samples, waits for the block where the sample to be played at (or around) 3'19"36 is, and starts generating its sound at that moment.

Actually, the whole system already works now, except that I am reduced to a complicated guess work to get sample numbers, and it doesn't work always correctly.

Now, why do I want to have separate plugins cooperate, instead of having just one ? Except for the challenge of programming a large cooperative system (60000 lines of JSFX code for now), I see three reasons:
- each generator is much simpler and efficient if it has to produce one sound only, and not 12 or 20 simultaneously.
- the memory available for a JSFX is "only" 32M words of 64bits, which means that I cannot have for example two stereo files of 3' simultaneously resident in memory in the same JSFX (for some granular generation, I need the whole file to be present in memory).
- REAPER can allocate more than one core for the whole process, for example, one core for each JSFX generator, which gives more computing power for the application.

By the way, for people wanting to know a litte more about this "application", which is called for now "The Game Master", I did a very short presentation (5') of this work at the Sound and Music Computing 2022 conference, which can be seen (starting at 8') on this video https://www.youtube.com/watch?v=DuXSpb2CbWE, and a long description of the implementation, in french, at the MiXiT 2022 conference https://mixitconf.org/2022/-un-exerc...ctroacoustique, but the hour long video is not yet available on the conference site (probably in some weeks from now).

Thanks for your interest.

J.Jack.

PS: still waiting for someone from the REAPER team to say "yes, the feature you ask for is interesting, it will be in the next release !" :-)

Last edited by jack461; 06-25-2022 at 03:05 AM. Reason: Typos.
jack461 is online now   Reply With Quote
Old 06-25-2022, 05:00 AM   #6
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,686
Default

As you are creating the audiostream you might consider adding a channel that holds a time signature.

-Michael
mschnell is offline   Reply With Quote
Old 06-26-2022, 03:37 AM   #7
souk21
Human being with feelings
 
souk21's Avatar
 
Join Date: Mar 2021
Posts: 463
Default

I'm not sure I understand correctly but can't you just

Code:
@init
spl_count = 0;
@block
//Stuff
spl_count += samplesblock;
or

Code:
@init
spl_count = 0;
@sample
//Stuff
spl_count += 1;
souk21 is offline   Reply With Quote
Old 06-26-2022, 12:35 PM   #8
jack461
Human being with feelings
 
jack461's Avatar
 
Join Date: Nov 2013
Location: France
Posts: 181
Default

souk21,

You are perfectly right ! In fact it's the basis of what I use, and it works well, except that I lose this synchronization when I solo/mute a track, activate/deactivate plugins, create a duplicate generator. Then I'm again stuck with uncorrelated outputs. This is where I need to use more complex operations that do not work properly in some cases... The alternative is to save and reload the session. I'm still looking for something automatic...

Michael,

This sounds like an interesting idea. I will think about it.

Thanks to the both of you !

J.Jack
jack461 is online now   Reply With Quote
Old 06-27-2022, 07:55 AM   #9
jack461
Human being with feelings
 
jack461's Avatar
 
Join Date: Nov 2013
Location: France
Posts: 181
Default A solution...

Michael, souk21, thanks for your help.

I used your two suggestions and found an acceptable solution.
I used a first JSFX plugin on a separate track, which count samples, and sends this value through its audio output. This output is redirected to the other tracks, where the plugins to synchronize are installed, and by decoding the audio input, they get for each sample its correct number.

This is the code of the plugin, with some comments.
Code:
desc:Synchronizer
/*
   Allows sample precise synchronization between JSFX effects

    1/ Put Synchronizer.jsfx on a single stereo track -- lets name it "synchro"

    It generates in its outputs, spl0 and spl1, 
    an accurate representation of the
    current sample number.

    In the "Route" dialog box of the "synchro" track,
        - disable "master send"
        - add a new send to all the tracks you want to synchronize

    2/ In every JSFX effect you want to synchronize,
    
        -add, in @init :

            spl_count = -1; // so spl_count is known in all sections

        - add, at the beginning of @sample :

            spl_count = (0|(spl0 * 33554432)) + (0|(spl1 * 33554432)) * 16777216;
            spl0 = spl1 = 0; // reset samples.

        spl_count is now the actual number of the current sample 
        since the start of reaper.
        This value is accurate to 48 bits, and, at 48kHz, 
        is precise for sessions of up to about 6000 days...

        - if you need in @block the number of the first sample of the incoming
        buffer, use (spl_count + 1).

*/

option:no_meter

@init
ext_noinit = 1;
// Current sample is T0 + 2^24 * T1
// T0 and T1 are represented by S0 and S1, with
// S0 = T0 / 2^25, and S1 = T1 / 2^25
// S0 and S1 are sent using spl0 and spl1 in @sample
T0 = T1 = S0 = S1 = 0;
maxk = 4096 * 4096 ; // 2^24 = 16777216
delta = 1 / 4096 / 4096 / 2; // 1/2^25 = 1/33554432

@block

(T0 >= maxk) ? (
    T0 = T0 - maxk;
    T1 += 1;
    S1 = T1 * delta;
    // S0 = T0 * delta; // done in @sample
);

@sample

spl0 = T0 * delta;
spl1 = S1;
T0 += 1;
I hope this can help. The solution is not perfect. It works correctly if I mute/solo some tracks, but if I inactivate a plugin, the values of spl0/spl1 will leak in the output. And, of course, it involves some CPU to send the output of the "Synchronizer" to other plugins. So I still hope to have some day a first_sample_precise_time or equivalent special variable !

J.Jack.
jack461 is online now   Reply With Quote
Old 06-27-2022, 12:05 PM   #10
souk21
Human being with feelings
 
souk21's Avatar
 
Join Date: Mar 2021
Posts: 463
Default

That's clever !

Am I right you are only using samples between 0 and 1? Any reason?
souk21 is offline   Reply With Quote
Old 06-27-2022, 02:17 PM   #11
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,686
Default

Quote:
Originally Posted by jack461 View Post
I used a first JSFX plugin on a separate track, which count samples, and sends this value through its audio output. .
GREAT !
-Michael
mschnell is offline   Reply With Quote
Old 06-27-2022, 02:30 PM   #12
Justin
Administrator
 
Justin's Avatar
 
Join Date: Jan 2005
Location: NYC
Posts: 15,721
Default

If you want a precise wall clock time, you can use time_precise()
Justin is offline   Reply With Quote
Old 06-27-2022, 02:51 PM   #13
jack461
Human being with feelings
 
jack461's Avatar
 
Join Date: Nov 2013
Location: France
Posts: 181
Default

Thanks !

Using positive numbers for spl0 & spl1 simplify the computations.
Also, since it is an audio signal, the output of a track should not be greater than 1, otherwise it causes a saturation in the track VU-meters. And a value greater than 2 or 3 mutes the track, which is not what we want.

The spl count, as said, is T0 + 2^24 * T1, T0 being incremented for each sample. As soon as it becomes greater that 2^24, 2^24 is subtracted from its value, and T1 is incremented. To save CPU time, I do this test only in the @block section, so in fact T0 can reach values greater than 2^24 (up to 2^24 + 4095 for audio block size of 4096). Therefore, I divide T0 by 2^25 instead of 2^24, so spl0 is never much greater than 0.5.

I mentioned that the output of "Synchronizer.jsfx" will leak in the audio if the plugin that expects the spl count is desactivated. To correct this, you can send the output to other channels than 0 and 1 (but I use all 64 channels). Also, you can divide the value of T0 and T1 by a value much greater than 2^25. Since 64 bit floats represent correctly integers values up to 2^52 or so, you can probably divide T0 and T1 by a value up to 2^48 or 2^50. Such a signal with values less than 2^-25, even sent to the output, will be inaudible, but the computation to get the spl count would still be correct. Also, even a small number like this is not perturbated by the "anti denormal noise" values that REAPER adds on the output of the track, which are probably around 2^-1000. Note finally that you can't use the << or >> operators, but you need the actual multiplications and divisions. Only after that, you can round to "integers" with the (0|X) idiom.

Best regards.

J.Jack.
jack461 is online now   Reply With Quote
Old 06-27-2022, 03:08 PM   #14
jack461
Human being with feelings
 
jack461's Avatar
 
Join Date: Nov 2013
Location: France
Posts: 181
Default

Justin,

Thanks for following the thread.
In fact, I want for example that two or three generators, located on different tracks start their outputs exactly on the same sample. As I use sometimes 40 or 50 generators, they can't all be allocated on different CPU cores, so some of them will start when other have finished, and their values for time_precise() will be different, while their outputs will be merged starting on the same sample (not sure that what I mean is that clear). So up to now, I was trying to handle a "virtual time" based on counting samples and using that to slightly "correct" time_precise(). But the solution that Michael and souk21 help me find is quite satisfying (except for having to send the buffer with timing information to all the generators, which of course involves some additionnal CPU charge for REAPER), and gives me now the results that I expected.

Best regards. And REAPER is still the best !

J.Jack.

Last edited by jack461; 06-27-2022 at 03:13 PM. Reason: Typos, as always !
jack461 is online now   Reply With Quote
Old 06-27-2022, 09:50 PM   #15
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,686
Default

Quote:
Originally Posted by jack461 View Post
(not sure that what I mean is that clear)
Obviously by "the same sample" you mean that the samples in questions are supposed in the mix to meet at the output (or the rendering), be there PDC or whatever circumstances..

I do hope your algorithm will lead to that behalf (but I am not absolutely sure).

-Michael
mschnell is offline   Reply With Quote
Old 06-28-2022, 12:06 AM   #16
jack461
Human being with feelings
 
jack461's Avatar
 
Join Date: Nov 2013
Location: France
Posts: 181
Default

Quote:
Originally Posted by mschnell View Post
I do hope your algorithm will lead to that behalf (but I am not absolutely sure).
Yes it is OK. I have verified this, for example, by sending a command to 50 generators to produce a pulse about 5' later, each on a specific channel, then stopping some generators, muting tracks, soloing some, reactivating all the tracks and finally restarting all the stopped generators, and voilà (actually we don't say that in French), at the precise moment given, the 50 pulses have all been generated, each on its own channel, and perfectly aligned.

J. Jack.
jack461 is online now   Reply With Quote
Old 06-28-2022, 10:24 AM   #17
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,686
Default

Fantastic !
-Michael
mschnell is offline   Reply With Quote
Old 06-30-2022, 02:07 AM   #18
jack461
Human being with feelings
 
jack461's Avatar
 
Join Date: Nov 2013
Location: France
Posts: 181
Default Almost definitive solution...

So I finally changed the factor from 2^25 to 2^48. The algorithm still works fine, but it has the added benefit that if the synchronizer output is accidentally merged into the mix, its maximum amplitude is -144 dB, which is inaudible. Even when merging 64 outputs, the signal is -108 dB, which is still not noticeable. [in fact, this signal is rather a slowly changing DC value, which you can't hear anyway]

Here is the new code, for those who might be interested :
Code:
desc:Synchronizer
/*
   Allows sample precise synchronization between JSFX effects

    1/ Put Synchronizer.jsfx on a single stereo track -- lets name it "synchro"

    It generates in its outputs, spl0 and spl1, 
    an accurate representation of the
    current sample number.

    In the "Route" dialog box of the "synchro" track,
        - disable "master send"
        - add a new send to all the tracks you want to synchronize

    2/ In every JSFX effect you want to synchronize,
    
        -add, in @init :

            spl_count = -1; T_fact = 4096 * 4096 * 4096 * 4096  ; // 2^48

        - add, at the beginning of @sample :

            spl_count = (0|(spl0 * T_fact)) + (0|(spl1 * T_fact)) * 16777216;
            spl0 = spl1 = 0; // reset samples.

        spl_count is now the actual number of the current sample 
        since the start of reaper.
        This value is accurate to 48 bits, and, at 48kHz, 
        is precise for sessions of up to about 6000 days...

        - if you need in @block the number of the first sample of the incoming
        buffer, use "(spl_count + 1)" -- this is why the initial value is set to -1.
*/

option:no_meter

@init
ext_noinit = 1;
// Current sample number is T0 + 2^24 * T1
// T0 and T1 are represented by S0 and S1, with
// S0 = T0 / 2^48, and S1 = T1 / 2^48
// S0 and S1 are sent using spl0 and spl1 in @sample
T0 = T1 = S0 = S1 = 0;
maxk = 4096 * 4096 ; // 2^24 = 16777216
T_fact = 4096 * 4096 * 4096 * 4096  ; // 2^48
T_div = 1 / T_fact; // 2^-48

@block

(T0 >= maxk) ? (
    T0 = T0 - maxk;
    T1 += 1; // we don't really care for overflows in T1 :-)
    S1 = T1 * T_div;
);

@sample

spl0 = T0 * T_div; // spl0 is also S0, not used.
spl1 = S1;
T0 += 1;
Note that the value of "spl_count" might be temporarily incorrect when a synchronized plug-in is stopped and restarted, or when a RT-xrun occurs.

Also, this value you get for "spl_count" is a float representing a 2^48 bit integer, that may becomes greater than 2^31 after a few hours. So, instead of a test like
Code:
((spl_count % X) == 0)
you may use
Code:
((0|(spl_count - X * floor(spl_count/X))) == 0)
Hope that this approach can help someone.

Best regards.

J.Jack.
jack461 is online now   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 11:37 AM.


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