Standalone app and AudioMidiSettings and AudioDeviceManager

I’m definitely not understanding something.

My goal is simply to have:

  • Standalone version of my plugin with a native title bar
  • Place a button in my app that displays the same AudioMidi dialog that the normal Standalone has

But because all the code and classes to display that dialog are private to the StandalonePluginWindow, I have to roll my own, unless I’m missing something.

And to do that, I have to get the AudioDeviceManager from the StandalonePluginHolder with this piece of ugly code:

AudioProcessorEditor* MyProcessor::createEditor()
{
	Editor* editor = new Editor(*this);
	
	if(wrapperType == wrapperType_Standalone)
	{
		if(TopLevelWindow::getNumTopLevelWindows() == 1)
		{
			TopLevelWindow* w = TopLevelWindow::getTopLevelWindow(0);
			w->setUsingNativeTitleBar(true);
			
			pluginHolder = (juce::StandalonePluginHolder*) w;
			deviceManager = &pluginHolder->deviceManager;
		}
	}
	return editor;
}

And then later when I try to recreate the AudioMidiSettings dialog,I get a crazy number for the number of available devices.

const OwnedArray<AudioIODeviceType>& types = deviceManager->getAvailableDeviceTypes();
int num = types.size();  // returns 167904

Am I missing something?

I’d much rather not recreate all that code for that AudioMidiSettings dialog, but it seems like I’m forced to and then I’m getting nonsense from the AudioDeviceManager.

I’m losing it.

I got around this by overriding the Standalone wrapper (which we’re strongly encouraged to do anyhow, and I assume you’re doing) and then including the .cpp in Processor.cpp and Editor.cpp. In this way one can access the Standalone methods directly, and one can just do all the housekeeping e.g. MIDI settings once, and the processor (and subsequently the editor) are aware of it.

I’m sure there’s a better way, but I’m a UI programmer, not a C++ Wizard like some here (“I only do eyes”) and this way seems to work.

This is helpful, Chris. Thank you.

Did you mean to override StandaloneWrapper or StanaloneWindow?

Could you elaborate more please?

I’d like to call showAudioMidiSettingsDialog() from my editor class and it seems to be buried deeper down than I can access from my processor class.

Is there some more documentation on this somewhere? I’ve read all the forum posts about creating my own standalone window and it’s very unclear to me.

Thanks again.

Okay, I’m going to use our Axon 2 for iOS product as an example.

Step 1: Override [edit: replace, I mean] juce_StandaloneFilterWindow.h and juce_StandaloneFilterApp.cpp. Since the only time we use a standalone is on iOS, I made StandaloneApp.h and StandaloneApp.mm. Copy the contents of the juce library files in to these two, and change the include in the .mm (or .cpp if that’s how you roll) and in one of the usual places, define JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1. After you’ve done these things, and build, your app will be using your standalone wrapper instead of the generated one.

  1. In PluginProcessor.cpp and PluginEditor.cpp and any other components that need access, include StandaloneApp.mm (or .cpp, whichever you used), not .h.

  2. You can now access call methods created in StandaloneApp.mm in your processor or editor.

Here is a trigger in StandaloneApp.mm that opens the audio settings:

void JUCE_CALLTYPE axon_OpenSettings()
{
	if (auto holder = StandalonePluginHolder::getInstance())
		holder->showAudioSettingsDialog();
}

In your editor, you can just have, in buttonClicked or whatever:

if (JUCEApplication::isStandaloneApp()) axon_OpenSettings();

And then in StandaloneApp.h, you can have your way with that. I am fairly convinced there is a far better way to do this, but for my needs it works fine.

(Side note: if you make your standalone .mm, everything that touches it needs to compile as Obj-C++, so if you’re not using iOS-specific calls like BlueTooth MIDI or whatever, you might want to stick to .cpp)

1 Like

Thank you very much. I’ll give this a try.

You say to "override juce_StandaloneFilterWindow.h and juce_StandaloneFilterApp.cpp"but by copying them, it sounds like you really mean copy and rewrite instead of override in the traditional C++ case, right?

Heh, yes. I’ve never taken any programming courses, so I get the terminology wrong all the time. I mean “replace entirely.” I don’t think you can just override the methods in those, as they’re private.

Ah, right. Got it.

Hey JUCE team, do you think we can get some better documentation or an example for this? None of this is trivial and straightforward and there seems to be quite a few places for stumbling. Thank you.

2 Likes

I’ve included MyStandaloneApp.cpp in my editor class (as an #include “MyStandaloneapp.cpp” and added an openSettings function like yours inside StandaloneFilterApp class, but when I add

if (JUCEApplication::isStandaloneApp()) myOpenSettings();

I get and error: Use of undeclared identifier: myOpenSettings().

You can now access call methods created in StandaloneApp.mm in your processor or editor.

But how?

Here’s my entire StandaloneApp.cpp (using one from a different app that doesn’t have company IP in it; this one’s not mm.)

#include "StandaloneApp.h"

extern AudioProcessor* JUCE_CALLTYPE createPluginFilter();

namespace juce
{
	#include "StandaloneApp.h"
}

//==============================================================================
class StandaloneFilter  : public JUCEApplication
{
public:
	StandaloneFilter()
	{
		PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_Standalone;
		
		PropertiesFile::Options options;
		
		options.applicationName     = getApplicationName();
		options.filenameSuffix      = ".settings";
		options.osxLibrarySubFolder = "Application Support";
#if JUCE_LINUX
		options.folderName          = "~/.config";
#else
		options.folderName          = "";
#endif
		
		appProperties.setStorageParameters (options);
	}
	
	const String getApplicationName() override              { return JucePlugin_Name; }
	const String getApplicationVersion() override           { return JucePlugin_VersionString; }
	bool moreThanOneInstanceAllowed() override              { return true; }
	void anotherInstanceStarted (const String&) override    {}
	
	virtual StandaloneFilterWindow* createWindow()
	{
#ifdef JucePlugin_PreferredChannelConfigurations
		StandalonePluginHolder::PluginInOuts channels[] = { JucePlugin_PreferredChannelConfigurations };
#endif
		
		return new StandaloneFilterWindow (getApplicationName(),
										   LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId),
										   appProperties.getUserSettings(),
										   false, {}, nullptr
#ifdef JucePlugin_PreferredChannelConfigurations
										   , juce::Array<StandalonePluginHolder::PluginInOuts> (channels, juce::numElementsInArray (channels))
#endif
										   );
	}
	
	//==============================================================================
	void initialise (const String&) override
	{
		mainWindow = createWindow();
		
#if JUCE_IOS || JUCE_ANDROID
		Desktop::getInstance().setKioskModeComponent (mainWindow, false);
#endif
		
		mainWindow->setVisible (true);
	}
	
	void shutdown() override
	{
		mainWindow = nullptr;
		appProperties.saveIfNeeded();
	}
	
	//==============================================================================
	void systemRequestedQuit() override
	{
		quit();
	}
	
protected:
	ApplicationProperties appProperties;
	ScopedPointer<StandaloneFilterWindow> mainWindow;
};

#if JucePlugin_Build_Standalone && JUCE_IOS

bool JUCE_CALLTYPE juce_isInterAppAudioConnected()
{
	if (auto holder = StandalonePluginHolder::getInstance())
		return holder->isInterAppAudioConnected();
	
	return false;
}

void JUCE_CALLTYPE juce_OpenSettings()
{
	//if (auto holder = StandalonePluginHolder::getInstance())
	//	holder->showAudioSettingsDialog();
	if (! RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
		RuntimePermissions::request (RuntimePermissions::bluetoothMidi, nullptr);
	
	if (RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
		BluetoothMidiDevicePairingDialogue::open();
}


void JUCE_CALLTYPE juce_switchToHostApplication()
{
	if (auto holder = StandalonePluginHolder::getInstance())
		holder->switchToHostApplication();
}
#if JUCE_MODULE_AVAILABLE_juce_gui_basics
Image JUCE_CALLTYPE juce_getIAAHostIcon (int size)
{
	if (auto holder = StandalonePluginHolder::getInstance())
		return holder->getIAAHostIcon (size);
	
	return Image();
}
#endif

#endif

START_JUCE_APPLICATION(StandaloneFilter)

Thank you, but there’s still something missing.

How can I call an openSettings() function inside StandaloneFilterApp.cpp from my editor class?

I’m not doing anything further than what I told you. Did you add JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1 to Preprocessor Definitions in ProJucer?

I’m sorry, I must not be making myself clear.

I’ve got the custom standalone working using copies of StandaloneApp.cpp and StandaloneWindow.h. It compiles and runs.

The problem is how to call the showAudioSettingsDialog() from within my editor.

You said to put this function in the StandaloneApp class…

void JUCE_CALLTYPE axon_OpenSettings()
{
	if (auto holder = StandalonePluginHolder::getInstance())
		holder->showAudioSettingsDialog();
}

…but there’s no way that the editor class can see inside the StandaloneApp class to call that function…unless you have some glue that I don’t know about.

I have just done this thing this very morning - both of you, thanks for your help!

@pizzafilms, Make sure to import #import "StandaloneFilterWindow.h" in your PluginEditor.m where you have your open settings function.
StandalonePluginHolder::getInstance() is a global method that can be called anywhere as long as you have the import.

1 Like

That was the ticket!!! Thank you!

So, for those following along at home, this is how to use your own StandaloneWindow and still call the showAudioSettingsDialog from inside the editor:

  • Make copies of juce_StandaloneFilterApp.cpp and juce_StandaloneFilterwindow.h and place them in your source tree.

  • In your StandaloneFilterApp.cpp, comment out the chunk of #includes at the top and replace them with #include <JuceHeader.h>

  • In your StandaloneFilterApp.cpp, change #include "juce_StandaloneFilterWindow.h" to
    #include "StandaloneFilterWindow.h"

  • In your editor class, add: #include "StandaloneFilterApp.cpp and include "StandaloneFilterwindow.h"

  • In your jucer file, in Preprocessor Definitions, add: JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1

  • Style your window however you like in StandaloneFilterWindow.h

  • If you want to show the audioSettingsDialog from your own editor code instead of the Options button that’s in the original, use this from inside your editor:

    • if (JUCEApplication::isStandaloneApp())
      
    • {
      
    •     StandalonePluginHolder::getInstance()->showAudioSettingsDialog();
      
    • }
      

Thank you @wtsnz and @crandall1 for all your help!

15 Likes

Thank you @pizzafilms for this summary. We should make it easier to use a custom version of the StandaloneFilterWindow.

For many, it may be easier to just roll your own Application and Window. Luckily doing this is just as easy as creating any standalone GUI app with JUCE. Just create your GUI app as you would any other JUCE standalone GUI app but add JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1 to the list of preprocessor defines in the Projucer.

You can find more info my post here (“option 2”).

1 Like

Thank you @fabian, I definitely agree that JUCE should have an easier way to make a StandaloneFilterWindow, but I would think that considering that all/most of the necessary functionality is already there, it’s more an issue of having some hooks to modify it and less of a desire to start allover from scratch.

In my quest for that desired additional functionality, I hit some major roadblocks trying to go fullscreen. Please take a look at the more complete post here: Standalone issues

Yes, we’ve seen that post and discussing this a bit internally. I’m currently quite consumed with the following one, so I probably won’t have time to answer your other post today.

Thanks for the great tutorial. I tried it and got a linker error:

ndefined symbols for architecture x86_64:
"juce_CreateApplication()", referenced from:
_main in include_juce_audio_plugin_client_Standalone.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Somehow the linker does not find the class that derivates from JUCEApplication.
I did set JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1. Is there something else i have to do?

I was able to fix it. I just had to add this line:

START_JUCE_APPLICATION(StandaloneFilterApp)
3 Likes

Yes, thanks for the great tutorial. In Juce 6.0.5 I am getting a linker error as well. Ensured that JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1 and START_JUCE_APPLICATION(StandaloneFilterApp) is added.

2>include_juce_audio_plugin_client_Standalone.obj : error LNK2019: unresolved external symbol “class juce::JUCEApplicationBase * __cdecl juce_CreateApplication(void)” (?juce_CreateApplication@@YAPEAVJUCEApplicationBase@juce@@XZ) referenced in function WinMain
2>C:\Users\mitch\Desktop\Builds\VisualStudio2019\x64\Debug\Standalone Plugin\HLConvolver.exe : fatal error LNK1120: 1 unresolved externals

Looking in juce_audio_plugin_client_Standalone.cpp I see that:

extern juce::JUCEApplicationBase* juce_CreateApplication(); says Function definition for juce_CreatApplication() not found.

Not sure if related but JUCE_MAIN_FUNCTION_DEFINITION gave a warning C2851: Inconsistent annotation from WinMain. See c:\program files(X86)\windows kits\10\include\10.0.18362.0\um\winbase.h(933).

Any ideas?

I got the same error.

Adding JUCE_CREATE_APPLICATION_DEFINE(StandaloneFilterApp) to the end of StandaloneFilterApp.cpp solved this issue.

Edit: Eventually, I had to remove the “StandaloneFilterApp.cpp” include from the editor class that made OSX build possible.