Multiple timers? [SOLVED]

Hello,

If I want a timer in my component I make it inherit from the timer class, I call startTimerHz() and then override the timerCallback() function with whatever I want to do.

Simple!

What If I now want another timer with its own timerCallback() function to do something else?

I can use composition to include a Timer object in my private: section let’s say: Timer anotherTimer

Then in the constructor of my component: anotherTimer.startTimer(100);

and then somewhere I’m going to need to write the callBack function.

I’ve tried both:

anotherTimer::timerCallback()
    {
        DBG("hello");
    }

and

anotherTimer.timerCallback()
    {
        DBG("hello");
    }

Both return the error:

error: ‘anotherTimer’ does not name a type

How do you add a second timer?? Thanks y’all

If you want several timers for a single component/object, inherit from MultiTimer. If you want a single timer or multiple timers for multiple components/objects, it is more complicated, I’ve never properly tested and benchmarked how it should be done. (I suspect there is some overhead and the multiple objects should probably use a shared timer or shared multitimers, but it does complicate things and one probably should not do it without some evidence the extraneous timers actually use a lot of resources…)

But MultiTimer just triggers the same timerCallback(int timerID) function, right?

So I would need to put a costly if (timerID == anotherTimerID) in timerCallback(int timerID) in order to separate the triggers (interrupts?).

The other way I can think to do it is to create a std::thread with a callback to a function that looks like this

void gooldOldThreadding()
{
    while (true)
    {
        // do stuff
        Time::waitForMillisecondCounter(Time::getMillisecondCounter() + 50);
    }
}

Costly if? You do understand that just a single timer already uses thousands, if not tens of thousands, of CPU cycles to make the callback, right? The if comparison in the MultiTimer callback doesn’t add anything of consequence.

3 Likes

Okay, I just figured because the internet is full of the echoes of “branching is sloooow, avoid branching” etc. And you certainly notice the effect of an if statement in your audio thread (but yes, 60hz ain’t got nothing on 44,100hz!!)

Okay, so I guess you’re right, but just to be totally clear, I should drop my fear of if statements? And people won’'t look at my code and cringe if I they see a bunch of ifs in a 60hz function? :sweat_smile:

Yes, in principle conditionals (“if” etc) are “slow”, but it should be seen in the context where it is happening. For example, if you have some stuff running a couple of thousands of times per second, it probably doesn’t matter at all.

But even if you do it much faster, like for each audio sample, (so starting from something like 44100 times per second) it might not matter that much. Modern CPUs do branch prediction and stuff, so in the end the only way to see how it works is to try it out, and make some judgment call if it is going to work fast enough or not. (Disclaimer : I have myself stopped caring about branching in my code for performance reasons a long time ago. It might not be the right thing to do, but oh well…However, excessive branching can make the code difficult to read and understand, so if things look like they are going that way, then I might do something about it.)

1 Like

There are many articles on the interwebs about ‘premature optimization’, and you will find Jules warning against it many times on this forum. Get your code working, and profile it if you think there are performance issues. As well, I just watched a good talk on ‘Design For Performance’, which talks about having good designs, so they can be optimized if needed.

3 Likes

On the topic of timers and CPU cycles, if I have 1 critical timing solution I need that writes 300 bytes too MemoryMappedFile every 10ms, what would the most efficient means to do this – use of getMillisecondCounterHiRes() or std::chrono::high_resolution_clock to calculate if 10ms have passed?

In general “time critical” and “write to a file” are two tasks that are mutually exclusive, you can never be sure that writing to a file – even if only writing a few bytes – does not block for an unexpected long time period and I’m pretty sure that this also is the case for a memory mapped file. So whatever you plan should be handled by two threads and a fifo you put the data into.

Besides this, I don’t think that there should be a real difference between the juce implementation and the std library (correct me if I’m wrong), I would prefer the juce one because of the much nicer API. However, if you give a bit more details on what you actually plan, you might get some better input from the community as well :wink:

You have some kind busy loop that constantly checks if 10ms has passed?

It would be on I/O memory for a Linux device driver that supports fiber optics. The device is not released yet, but I need to simulate the timing as close as possible. See below in general,

I referenced the Juce memorymappedfile because that’s all I could find, but I would definitely need high resolution timing to write to registers of i/o memory within a certain time limit.

Well, I don’t prefer any kind of loop. I would prefer to use the audio sample or playback rate to determine how often processBlock is called in its own thread to see if the elapsed time can be checked there; if not there, then it would be nice to have recommendations by WeAreRoli JUCE development.

You mention the methods to check the time, and I think they will both query the same source, getMillisecondCounterHiRes() and std::chrono::high_resolution_clock.

But much more interesting is the question, how to be called at the right time, if you don’t spin in a loop of an extra Thread.

The processBlock has the disadvantage, that you don’t know, if audio is running at all. processBlock is usually present in plugins, and some hosts stop calling processBlock on certain conditions, or when the transport is moved, it might not be called, and after a short break it is called with different playhead information.

The juce::Timer uses messages on the message queue, so it is the worst option. The HighResolutionTimer has already a thread implemented, that does nothing than calling at specific times:

HighResolutionTimer
A high-resolution periodic timer.

This provides accurately-timed regular callbacks. Unlike the normal Timer class, this one uses a dedicated thread, not the message thread, so is far more stable and precise.

You should only use this class in situations where you really need accuracy, because unlike the normal Timer class, which is very lightweight and cheap to start/stop, the HighResolutionTimer will use far more resources, and starting/stopping it may involve launching and killing threads.