stringFromValue which depends on two values?

I’m trying to implement a stringFromValue std::function (or lambda) where the output depends on the value of a second parameter, something like:

static auto stringFromOscShape = [](float v, int)
{
	switch (wave)// <- how to set 'wave'?
	{
	case SAW: return String{};
	case PWM: return "Pulse Width: " + String (1.0f + v * 98.0f, 1) + "%";
	}
}

But if ‘wave’ is a member variable of my AudioProcessor class, how do I access it from a lambda declared within ParameterLayout(), or a static std::function declared in a header somewhere?

Things like this are normally done via the capture list of the lambda, this is the part in between the [ ] which is currently empty in your lambda.

What to put there in order to make wave accessible in your lambda depends a bit on where wave is declared and where stringFromOscShape is declared. If wave is a member of AudioProcessor and stringFromOscShape is declared in the AudioProcessor too, you can simply capture the this pointer of your AudioProcessor, this makes it possible to access all member variables from your class inside the lambda. Syntax:

auto stringFromOscShape = [this] (float v, int)

I’ve tried this (by which I mean ‘this’!), but I get the error:

‘this’ may only be used inside a nonstatic member function.

That would be very dangerous anyway. this is likely to be deleted before your static variable.

What is your reason to have it static? (hint: in most cases static causes moree problems than it solves)

Also where are you using that lambda? For an AudioParameterFloat? Be aware, that the lambda is then propagated to any Slider that uses SliderAttachment, at which point lifetime of the captured variables becomes a concern (yes, I know the Processor itself will outlive the editor, still it needs close watch)

I’m following your suggestion by adding the ‘this’ capture specifier to a plain test lambda which is added as a stringFromValue function to an AudioParameterFloat inside ParameterLayout()

parameters.push_back (std::make_unique<AudioParameterFloat>
(
	"OscShape",
	"Osc Shape",
	NormalisableRange<float> (0.0f, 1.0f, 0.0f);
	0.5f,
	"",
	AudioProcessorParameter::genericParameter,
	[this] (float v, int) { return String (v); },
	nullptr
));

I don’t understand where the ‘this’ may only be used inside a nonstatic member function. warning comes from as I’m not declaring anything as static!

But you posted:

static auto stringFromOscShape = [](float v, int)

That’s a static function, which means that it has no “this” member.

1 Like

IMHO lambda captures are a dangerous tool. They are simply storing raw pointers to things that may or may not live at the time of the execution! It is absolutely your obligation to make sure these pointers are valid.

Solution 1: the thing owning the lambda owns the captured thing as well: this way it is guaranteed the lambda is executed without the captured thing.

Solution 2: capture a weakReference or a SafePointer and check for nullptr inside the lambda:

Component::SafePointer<FooComponent> safeThis (this);
auto foo = [safeThis](float v, int)
{
    if (safeThis == nullptr)
        return {};

    // now you may safely use safeThis, if there is no other thread involved
}

Yes, sorry for the confusion there. The first code I posted was indeed declared static, the second code wasn’t. It’s just a plain lambda expression with a ‘this’ capture added as per your suggestion, and it gives me the error I quoted previously.

Could the problem be that you have a semicolon there instead of a comma? Errors when using lambdas can get confusing because the parsing gets pretty complex. Often the real error happens earlier in the declaration, as appears to be the case here.

Sorry, that’s yet another transcription error on my part :frowning: The actual code uses a separately-declared (NormalisableRange<float> range0To1 (0.01f, 1.0f, 0.0f);) which I copied into the code I posted for simplicity, complete with the semicolon. Here is the real code for clarity:

NormalisableRange<float> range0To1 (0.0f, 1.0f, 0.0f);

parameters.push_back (std::make_unique<AudioParameterFloat>
(
	"OscShape",
	"Osc Shape",
	range0To1,
	0.5f,
	"",
	AudioProcessorParameter::genericParameter,
	[this] (float v, int) { return String (v); },
	percentToValue
));

Is ParameterLayout() a member of your AudioProcessor class? If not, then there is no ‘this’ pointer.

That is indeed the underlying problem:

At the time the parameters are created (i.e. as construction arguments to the AudioProcessor) the AudioProcessor (which I assume was the intended this pointer) doesn’t yet exist.

That’s the other reason, why the capture is not a good idea.

The stringToValue should only be a conversion from the value as stored on the automation lane to a textual representation. Any other visualisation in the GUI should be implemented there as derrived value of those two parameters.

Keep also in mind, that the Slider TextBox works in both directions, if you enter a number, which of the two values should change?

ParameterLayout() is a member of AudioProcessorValueTreeState(), so I see what you mean about there being no ‘this’ pointer.

I don’t get you. If it’s a member function, and not static, then there is a “this” pointer. But how can it be a member of AudioProcessorValueTreeState()? We’d need to see the constructor for your AudioProcessor-derived class, and the entire function that is used as the callback for filling out the parameters (both declaration and definition, if separate), before we could comment further. I think you might have some things mixed up here.

I’m using AudioProcessorValueTreeState::createParameterLayout() which as I understand it is a replacement for the deprecated createAndAddParameter(). Here is my exact code, but with only one parameter:

// in MyAudioProcessor.h:

auto valueToPercent = [](float v, int) { return String (v * 100.0f, 1) + " %"; };
auto percentToValue = [](const String& s) { return jlimit (0.0f, 1.0f, s.getFloatValue () * 0.01f); };

// in MyAudioProcessor.cpp:

AudioProcessorValueTreeState::ParameterLayout createParameterLayout ()
{
	std::vector<std::unique_ptr<RangedAudioParameter> > parameters;

	NormalisableRange<float> range0To1 (0.0f, 1.0f, 0.0f);

	parameters.push_back (std::make_unique<AudioParameterFloat>
	(
		"OscShape",
		"Osc Shape",
		range0To1,
		0.5f,
		"",
		AudioProcessorParameter::genericParameter,
		valueToPercent,
		percentToValue)
	);

	return { parameters.begin (), parameters.end () };
}

MyPluginAudioProcessor::MyPluginAudioProcessor () :
	undoManager (),
	parameters (*this, &undoManager, "MyPlugin", createParameterLayout ())
{
	// other constructor stuff
}

I don’t see any “[this]” used in that code. What line of code is causing that error now?

And why are you declaring that createParameterLayout() is a member of AudioProcessorValueTreeState()? It should be a member of MyPluginAudioProcessor (if you need access to the processor class). It is used to create the instance of an AudioProcessorValueTreeState.

There seems to be a misunderstanding about nested classes.

1.) The ParameterLayout is a nested class inside the AudioProcessorValueTreeState. That means the class definition is inside the class, nothing else. The reason to do this is either to keep things organised, or to make sure, whoever knows about ParameterLayout, automatically also knows about AudioProcessorValueTreeState.

2.) The return type of your createParameterLayout() is a ParameterLayout. But the function itself is a free function, not in any namespace, neither in AudioProcessorValueTreeState not in the AudioProcessor. Hence no this.

It cannot be a member of the class, at least no non-static member, because it is called exactly once and that is before the construction of the Audiorocessor.

It is in a plugin I’m working on:

// Make the APVTS object
processorState = std::make_unique<AudioProcessorValueTreeState>( *this, &undoManager, "PARAMETERS", this->createParameterLayout() );

I think the only C++ restriction is that you not call any virtual functions from the constructor, because the constructor has not completed yet. Calling non-virtual functions works fine.

1 Like

Sorry my bad. I confused the constructors.

You can create the AudioProcessorValueTreeState after creating the AudioProcessor. It is not ideal though because of the two phase initialisation. I think the recommended way is to have the AudioProcessorValueTreeState as member and initialise it in the member initializer list.

Imagine what would happen if one destroys the APVTS and recreates it with a different layout. I guess mayhem…

But having been wrong once today already… :wink:

1 Like

I think it’s legal to call a non-static member in a field initializer. this already exists, and all the fields declared before would have been constructed, but not the fields declared after. Anyway it’s a bit brittle, I’d try to always make the parameter layout in a free function.