processBlock with float vs double?

@daniel is it possible to do this templated class technique while still splitting the class between a header and .cpp file? I tried it but I’m getting a linker error…

Here’s my header:

template <typename SampleType = float>
class EpochFinder
{
public:
    EpochFinder();
    ~EpochFinder();
    
    void extractEpochSampleIndices(const AudioBuffer<SampleType>& inputAudio, const double samplerate, Array<int>& outputArray);
    
    int averageDistanceBetweenEpochs(const Array<int>& epochIndices);
    
    // DANGER!!! FOR NON REAL TIME USE ONLY!!!
    void increaseBufferSizes(const int newMaxBlocksize);
    
private:
    CriticalSection lock;
    
    Array<SampleType> y;
    Array<SampleType> y2;
    Array<SampleType> y3;
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EpochFinder)
};

here’s my .cpp: (code-folded screenshot for simplicity)

and here’s the linker errors I’m getting:

I figured it out, I needed to change my instantiations to

template class EpochFinder<float>;
template class EpochFinder<double>;

and move them to the bottom of the cpp file, weirdly… ¯_(ツ)_/¯

So I implemented @daniel’s wrapped/templated Engine class suggestion, and it works great! Not only can I now support float & double precision, but another major advantage is that it becomes a breeze to avoid pops & clicks when bypassing/unbypassing.

For example, I added a couple of bool arguments to my Engine class’s internal “process” method:

template<typename SampleType>
void ImogenEngine<SampleType>::process (AudioBuffer<SampleType>& inBus, AudioBuffer<SampleType>& output, MidiBuffer& midiMessages,
                                        const bool applyFadeIn, const bool applyFadeOut)
{
    const ScopedLock sl (lock);
    
     // do a bunch of fancy stuff here
        
    if (applyFadeIn)
        output.applyGainRamp(0, totalNumSamples, 0.0f, 1.0f);
    
    if (applyFadeOut)
        output.applyGainRamp(0, totalNumSamples, 1.0f, 0.0f);
};

and now, in my templated processblock & processBlockBypassed, I can now do this:

template <typename SampleType>
void ImogenAudioProcessor::processBlockWrapped (AudioBuffer<SampleType>& buffer, MidiBuffer& midiMessages, ImogenEngine<SampleType>& engine)
{
    AudioBuffer<SampleType> inBus  = AudioProcessor::getBusBuffer(buffer, true, (host.isLogic() || host.isGarageBand()));
    AudioBuffer<SampleType> outBus = AudioProcessor::getBusBuffer(buffer, false, 0); // out bus must be configured to stereo
    
    engine.process (inBus, outBus, midiMessages, wasBypassedLastCallback, false);
    
    wasBypassedLastCallback = false;
};
template <typename SampleType>
void ImogenAudioProcessor::processBlockBypassedWrapped (AudioBuffer<SampleType>& buffer, MidiBuffer& midiMessages, ImogenEngine<SampleType>& engine)
{
    
    AudioBuffer<SampleType> inBus  = AudioProcessor::getBusBuffer(buffer, true, (host.isLogic() || host.isGarageBand()));
    AudioBuffer<SampleType> outBus = AudioProcessor::getBusBuffer(buffer, false, 0); // out bus must be configured to stereo
    
    if (! wasBypassedLastCallback)
    {
        // this is the first callback of processBlockBypassed() after the bypass has been activated.
        // Process one more chunk and ramp the sound to 0 instead of killing the sound instantly
        
        engine.process (inBus, outBus, midiMessages, false, true);
        wasBypassedLastCallback = true;
        return;
    }
    
    engine.processBypassed (inBus, outBus, midiMessages);
    
    wasBypassedLastCallback = true;
};

works like a charm :grinning:

Looks nice! But one thing that pops out immediately is the ScopedLock in your process function – what is that used for. Usually locks are something to avoid at all cost in the processing callback as it’s purpose is to make the thread wait for other threads that might be holding the lock, which is bad when you want your audio thread to finish its work as fast as possible :slight_smile:

1 Like

Oh, maybe I’m misunderstanding – what I want to do with that is acquire the lock and prevent any other threads from doing anything with my engine class until it’s unlocked.

What’s the proper way to do that?

I’d like to recommend choc::buffer for sample storage and manipulation: choc/choc_SampleBuffers.h at master · julianstorer/choc · GitHub

It’s by far the cleanest API to use and pretty hard to accidentally allocate as you almost exclusively pass around non-owning views.

1 Like

what are the benefits of this over using juce::AudioBuffer’s alias constructor?

Yes, you are doing that, but what do you expect to happen if another thread holds that lock at the timepoint you are trying to acquire it on the audio thread? There is no other option for the audio thread to wait until this other thread released the lock and then acquire it. If that other thread is a lower priority thread (such as the message thread) it might be scheduled back by the operating system, making your high priority task waiting for a low priority task to finish it’s work – that’s called priority inversion and will often lead to dropouts.

So the best advice is indeed to find solutions that don’t need any locking for all kind of data that should be accessed from the audio thread. There is no one-fits-all approach though. Popular options are atomic variables if you want to share small pod variables between threads or free queues if you always want to send data one direction. Pointers to data structures that are swapped in combination with spin locks are a possibility in some cases too.

What’s your exact multithreading scenario here?

this is the only line of code in my entire Engine class that ever locks/acquires that CriticalSection

so my reasoning is that, in this use case, this lock can only ever be held by the audio thread… ¯_(ツ)_/¯

In that case you don’t need any lock at all :sweat_smile:

Locks are basically flags needed to secure data that is accessed from multiple threads to become invalid in case both threads access the memory at the same time. You have to know which variables the lock refers to and have to lock every time you want to access those specific variables in a possibly multithreaded scenario – so the lock itself secures nothing. If that doesn’t happen, a lock doesn’t really make sense and produces unnecessary overhead, especially as it’s a system call tied to the os thread scheduling, so better get rid of it.

1 Like

Ah, okay. I guess I’d better read more about how these work, then…

1 Like

just found this while debugging the other day and wanted to add it to the conversation, after getting laughed at in another thread for considering the preposterous idea of 64 bit double summing…

Also great CMake talk on youtube! Watched it twice!

1 Like

Cubase and other Daws offer this as well.

Use 4 byte floats where they work fine and doubles if you need them.

Often there is no difference in speed - doubles can even be faster, due to how CPUs support them. But there are of course also downsides, with respect to allocation amounts, copying, large vectors, etc.

I use them in one algorithm, because due to the algorithm’s nature, 4 byte floats can give rounding errors in it.

But I would be mad to use doubles by default for my convolution code (which is proven to be mathematically correct, using 4 byte floats). It would make my upcoming plugins memory eaters without a cause :slight_smile:

1 Like

We have usually here a quite welcoming culture and I was very happy about that fact.
I feel this attitude in your post doesn’t contribute anything valuable, so I would ask you kindly to refrain from such comments.
It doesn’t help anyone, and eventually it tells something about you, if you need to diminish or ridicule others.

1 Like

There are people that despite evidence to the contrary will never change their mind. TypeWriter is one such person. I merely tried to warn @PeterRoos to not waste his time.

It’s OK to be inclusive and welcoming, but not at the expense of everything else.

I agree with both of you.
It is sometimes tricky to be polite and firm at the same time.
I totally agree with Michael’s “content” and also totally agree with Daniel’s call for a good “form”.

But discussions in a forum can go in ways no-one intended or expected, or carry on and on…
Check the related topic about doubles, where I was fishing about the Pro License of this person.

That went into a completely different direction. I read it back and apologized today for maybe not being clear enough.

But people also simply had not read my question well enough. As if I doubted this person himself, while I was asking about his business.

This happens on forums, and we should indeed do our best to keep it polite and friendly.

But it can also happen that people in discussions simply don’t want to listen to arguments.
And we’ve seen that in the “double” discussions.

I disagree with Michaels content, because there is non, it is simply diminishing a person. He could have said “This 64 bit processing is useless, let’s end the discussion”, but instead he chose to say simplified: “don’t talk to him, he is stupid”. And to support this is just

Haha, so that’s the standard.
Michael told me once to shut up because he didn’t like my answer. Claiming it was all wrong, just to get it confirmed from Jules in the next post (October 2018, had to refresh my mind, I wish I didn’t have to). So this is this superiority. Well, no thanks!

2 Likes

Ah, so you are already having a good time here together for a long time! That’s sweet and cool.

Well, I certainly do not wish to diminish a person myself, but sometimes people need to be explained that what comes out of them needs to be diminished in some way or another because they are repeating nonsense. That’s their behavior, not their person.

I learned a lot on this thread sorry you thought I was arguing. When I talked about bit depth I meant when mixing.

Way to make fun of someone for being ignorant, asking questions, and learning. I’m sorry thats what it sounds like in your head.

To anyone who cares… I wish to clarify.

I mix / track in 24 bit 96hz. Export in 32 bit for mastering.

When programming I use 32 bit float and 64 bit doubles for summing or when possible. Thats my opinion, I don’t take personal jabs to people who disagree. Feel free to correct me, I enjoy learning, and view being wrong as an opportunity to grow.

Dear reFx,

I like to argue points I don’t necessarily agree with or understand, to see which argument stands up. This is also known as the socratic method. You should try it sometime.

Word of advice, it’s ok to be wrong sometimes and you don’t have to be the smartest person in the room. I actually prefer not to be.

Lastly, This is all the energy I am giving to “defending myself” because honestly people with mindsets like yours are best kept at a distance and given minimum energy.