Header vs. Source Code Files

I have a bit of a theoretical question on the structure for JUCE apps. I see all the tutorials do basically all of the coding in one header file, which seems extremely inefficient for large projects. I’m trying to recreate the Intro to DSP tutorial & was structuring it so each class is split into a header (to declare all members/functions) & a source code file (to define each). When I got to the Voice class, I found that I would have to #include Oscillator.h in both Voice.h & Voice.cpp (which to my understanding is why I’m running into a linker error as shown below). I can make the project work fine if I get rid of Voice.cpp & do all my coding in Voice.h, but I’m wondering if this is the best approach for creating JUCE projects? Are we supposed to avoid using .cpp files & just make each class one header file? I’m new to JUCE so appreciate any guidance y’all can provide.

Screenshot of Linker Error
Screen Shot 2024-08-13 at 9.14.05 AM

It’s perfectly normal to use both a .h header and a .cpp source file. You should never have to #include another header in both your header and your source file, because your source file already includes your header file, which means it already includes the other header file (Oscillator.h).

If you have a reference or pointer to a class or struct declared in Oscillator.h, then you would normally forward-declare that class/struct type in your header, and then only include the other header (Oscillator.h) in your source file.

If your Voice class includes an actual instance of a class or struct from Oscillator.h, or if it derives your class from a class in that header, though, then you need to include Oscillator.h in your header file. (But in that case, as I said above, you do not need to include it in your source file as well. It’s already included because Voice.cpp includes Voice.h.)

We cannot see your code or the specifics of that linker error, though, so it’s impossible to know for sure what the linker is complaining about, or why you think you need to include Oscillator.h in both files. That should just never be the case.

Okay that makes a ton of sense! I think you’re right that the linker error is coming from somewhere else as I rearranged my code to only include Oscillator.h in Voice.h, then have Voice.cpp include Voice.h, but there’s still a linker issue. Attaching my code below if you see anything immediately off about it.

Voice.h

#pragma once

#include "Oscillator.h"

class Voice : public juce::MPESynthesiserVoice
{
public:
    Voice();

    .............

private:
    
    enum
    {
        osc1Index,
        osc2Index,
        filterIndex,
        masterGainIndex
    };
    
    juce::dsp::ProcessorChain<Oscillator<float>, Oscillator<float>, juce::dsp::LadderFilter<float>, juce::dsp::Gain<float>> customProcessorChain;
};

Voice.cpp

#include "Voice.h"

Voice::Voice()
{
    auto& masterGain = customProcessorChain.get<masterGainIndex>();
    masterGain.setGainLinear (0.7f);

    auto& filter = customProcessorChain.get<filterIndex>();
    filter.setCutoffFrequencyHz (1500.0f);
    filter.setResonance (0.7f);
}

.............

Oscillator.h

#pragma once

#include <JuceHeader.h>

template <typename Type>
class Oscillator
{
public:
    Oscillator();
    
    void setFrequency (Type newValue, bool force = false);
    void setLevel (Type newValue);
    void reset() noexcept;
    template <typename ProcessContext> void process (const ProcessContext& context) noexcept;
    void prepare (const juce::dsp::ProcessSpec& spec);

private:
    enum
    {
        oscIndex,
        gainIndex
    };
    
    juce::dsp::ProcessorChain<juce::dsp::Oscillator<float>, juce::dsp::Gain<float>> processorChain;
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oscillator)
};

Your code doesn’t show where your Oscillator template class is implemented. The easiest way to avoid linker errors with template classes is to define and implement the template class entirely in the header file.

While it’s possible to separate the declaration and implementation of a template class into different files, doing so incorrectly can lead to linker issues.

Here’s a link that explains how to correctly use separate files for template class definitions and implementations in C++: https://www.codeproject.com/Articles/48575/How-to-Define-a-Template-Class-in-a-h-File-and-Imp

Ah, yes, I hadn’t thought about the template case, sorry.

Ohhhhhh, that helps a ton, thank you! Read through the article & ended up doing what you said (implementing the whole class in the header file), but that will super come in handy down the line. Thanks!

No need to be sorry! Your earlier response also helped a ton, thanks again

Just as an aside for anyone learning JUCE, get familiar with the awesome-juce repository:

With this repo you have access to a huge array of JUCE-based projects, and if you give yourself a few hours of code-reading each week, from this list of projects, you can learn a lot about the diverse ways to organize JUCE projects - from basic header-file-only projects, to JUCE-as-a-submodule inclusion in a larger codebase, these projects have a lot to offer for the new JUCE coder.

Take a look at, for example, the FundamentalFrequency LMN-3 project, or the Helm synthesizer. You can learn a lot from how folks have organized their JUCE projects, already …

3 Likes