So on Windows the original solution did not compile. I had to figure out what was going on. The central issue was that the 'Self' reference we setup before was not accessible at the stage that MSVC wanted during compile.
So the solution. All of the following happens inside the node struct:
Remove this:
using Self = BBDProcessor<NV>;
We simply don't need it now. Also remove the kSpecs array that exists inside the node class - probably best to copy it to another file for now.
Update our ParamSpec struct so that the apply method takes an instance of the node class:
struct ParamSpec
{
int index;
const char* name;
float min, max, step, def;
void (*apply)(BBDProcessor<NV>&, double); // this line here was updated
};
Establish some statics:
static const std::array<ParamSpec, 1>& specs() noexcept; // <- size 1 for now
static constexpr int paramCount() noexcept { return (int)specs().size(); }
static constexpr size_t kParamCount = 1;
The first line defines a compile time static function called specs() - this is linked to an array type, with the data type of our ParamSpec object. We also need to tell it how many. I've gone for '1' in this example, but in my full node it is '71'.
The second line defines a static helper function that essentially returns the size of specs().
The third line sets up a constexpr for paramCount - essentially the same thing as our size. This specs() function will be further defined inside of the node class later.
Then we need to adjust how we create the parameter. So back to this function:
template <std::size_t... Is>
void createParametersImpl(ParameterDataList& data, std::index_sequence<Is...>)
{
( [&] {
const auto& s = specs()[Is];
parameter::data p(juce::String::fromUTF8(s.name), {s.min, s.max, s.step});
this->template registerCallback<Is>(p);
p.setDefaultValue(s.def);
data.add(std::move(p));
}(), ... );
}
This is very similar to before. But we get a reference to our specs() object, assign it to s. This becomes the parameter source. Is is still our compile-time iterator.
Now we want to adjust how we set a parameter:
template <int P>
void setParameter(double v)
{
static_assert(P < kParamCount, "Parameter index out of range");
const auto& spec = specs()[P];
jassert(spec.apply != nullptr);
spec.apply(*this, v);
}
There's an assert to catch whenever our parameter index goes out of range - ie; when adding extra parameters, did we update the ParamCount? If not... go back and do it, idiot. (speaking from experience!!)
We get an instance of specs() and assign it to spec. But since we're indexing with the template parameter, the compiler knows which parameter we're looking for inside specs. We also assert if the particular parameter we're working on right now doesn't have an apply method associated with it.
The key bit is spec.apply(*this, v);
This is a function pointer that looks in our parameter table for the relevant callback.
Now... at this point, we don't actually have a set of ParamSpecs anymore.... so we need to do that.... but it CANNOT be inside of the node's class. It needs to be its own separate struct, but still inside the project namespace. So at the bottom, you can add something like this:
template<int NV>
const std::array<typename BBDProcessor<NV>::ParamSpec, 1>&
BBDProcessor<NV>::specs() noexcept
{
static const std::array<ParamSpec, 1> tbl = {{
// --- Core Delay ---
{ 0, "Delay Time (ms)", 5.0f, 10000.0f, 0.01f, 500.0f,
+[](BBDProcessor<NV>& s, double v){ s.effect.setDelayMs(v); } },
}};
return tbl;
};
So this is a templated static function. Each instance of BBDProcessor (or rather, our nodes struct) gets its own version of this. Which is fine, because for our node, we only want one set of this anyway. If we use two nodes in parallel, each will obviously get its own internal set of parameters.
const std::array<ParamSpec, 1>&
This bit sets up a reference to a compile-time fixed size array, with the data type ParamSpec. All told, we're basically overriding the specs() function inside of the BBDProcessor struct (our node class).
The rest of it just constructs a table of parameters much as we did before, but it returns the table at the end. So specs() inside the node class becomes a version of the table.
Note the changes here:
+[](BBDProcessor<NV>& s, double v){ s.effect.setDelayMs(v); } },
No longer using self as the type for s. Instead we're using the node class as the type.
This all happens at compile time, so once you have it plumbed in correctly, you don't really need to worry too much about anything except getting the table correct, and the param counts correct.