Excpetion in juce_win32_Threads on close when using AudioSourcePlayer

I’m attempting to make my PluginEditor an AudioSource, so that I can play multiple tracks over themselves, independent of the DAW. I’ve got the sources playing, and everything seems okay, until I close the plugin, I get this exception:

I get the exception, even if I close the plugin BEFORE loading any sources into the mixerAudioSource.
No line numbers… haha. When I comment out the line:

//setup audio device manager with two channels, sterio
	audioDeviceManager.initialiseWithDefaultDevices(2, 2);

closing the plugin works without exception.

Here’s the call stack… Seems to be happening when mixerAudioSource is releasing it’s resources. I’m attempting to do this in the editors releaseResources, but that doesn’t help. And again, this error occurs whether I’ve actually loaded anything into mixerAudioSource or not.

/*
  ==============================================================================

	This file contains the basic framework code for a JUCE plugin editor.

  ==============================================================================
*/

#include "PluginProcessor.h"
#include "PluginEditor.h"

//==============================================================================
PolyEchoAudioProcessorEditor::PolyEchoAudioProcessorEditor(PolyEchoAudioProcessor& p)
	: AudioProcessorEditor(&p), audioProcessor(p), juce::AudioProcessorValueTreeState::Listener()
{
	mWalletIdTextbox.reset(new juce::TextEditor("Wallet Id"));
	addAndMakeVisible(mWalletIdTextbox.get());
	mWalletIdTextbox->setMultiLine(false);
	mWalletIdTextbox->setReturnKeyStartsNewLine(false);
	mWalletIdTextbox->setReadOnly(false);
	mWalletIdTextbox->setScrollbarsShown(true);
	mWalletIdTextbox->setCaretVisible(true);
	mWalletIdTextbox->setPopupMenuEnabled(true);
	mWalletIdTextbox->setColour(juce::TextEditor::backgroundColourId, juce::Colour(0xffaeaeae));
	mWalletIdTextbox->setText(juce::String());

	mWalletIdTextbox->setBounds(206, 20, 360, 24);

	mAddressLabel.reset(new juce::Label(juce::String(),
		TRANS("Address")));
	addAndMakeVisible(mAddressLabel.get());
	mAddressLabel->setFont(juce::Font(15.00f, juce::Font::plain).withTypefaceStyle("Regular"));
	mAddressLabel->setJustificationType(juce::Justification::centredLeft);
	mAddressLabel->setEditable(false, false, false);
	mAddressLabel->setColour(juce::TextEditor::textColourId, juce::Colours::black);
	mAddressLabel->setColour(juce::TextEditor::backgroundColourId, juce::Colour(0x00000000));

	mAddressLabel->setBounds(128, 20, 71, 24);

	mProjectsComboBox.reset(new juce::ComboBox("Projects Combo Box"));
	addAndMakeVisible(mProjectsComboBox.get());
	mProjectsComboBox->setEditableText(false);
	mProjectsComboBox->setJustificationType(juce::Justification::centredLeft);
	mProjectsComboBox->setTextWhenNothingSelected(juce::String());
	mProjectsComboBox->setTextWhenNoChoicesAvailable(TRANS("(no choices)"));
	mProjectsComboBox->addListener(this);

	mProjectsComboBox->setBounds(206, 48, 360, 24);

	mProjectLabel.reset(new juce::Label("Project Label",
		TRANS("Project")));
	addAndMakeVisible(mProjectLabel.get());
	mProjectLabel->setFont(juce::Font(15.00f, juce::Font::plain).withTypefaceStyle("Regular"));
	mProjectLabel->setJustificationType(juce::Justification::centredLeft);
	mProjectLabel->setEditable(false, false, false);
	mProjectLabel->setColour(juce::TextEditor::textColourId, juce::Colours::black);
	mProjectLabel->setColour(juce::TextEditor::backgroundColourId, juce::Colour(0x00000000));

	mProjectLabel->setBounds(129, 48, 56, 24);

	mLoadingProjectLabel.reset(new juce::Label("Loading Project Label",
		TRANS("Loading Project...")));
	addAndMakeVisible(mLoadingProjectLabel.get());
	mLoadingProjectLabel->setFont(juce::Font(61.90f, juce::Font::plain).withTypefaceStyle("Regular"));
	mLoadingProjectLabel->setJustificationType(juce::Justification::centredLeft);
	mLoadingProjectLabel->setEditable(false, false, false);
	mLoadingProjectLabel->setColour(juce::TextEditor::textColourId, juce::Colours::black);
	mLoadingProjectLabel->setColour(juce::TextEditor::backgroundColourId, juce::Colour(0x00000000));

	mLoadingProjectLabel->setBounds(280, 248, 248, 184);

	mPlayButton.reset(new juce::TextButton("new button"));
	addAndMakeVisible(mPlayButton.get());
	mPlayButton->setButtonText(TRANS("Play"));
	mPlayButton->addListener(this);

	mPlayButton->setBounds(656, 24, 56, 48);

	mStopButton.reset(new juce::TextButton("Stop Button"));
	addAndMakeVisible(mStopButton.get());
	mStopButton->setButtonText(TRANS("Stop"));
	mStopButton->addListener(this);

	mStopButton->setBounds(592, 24, 56, 48);

	cachedImage_polyechologo_png_1 = juce::ImageCache::getFromMemory(polyechologo_png, polyechologo_pngSize);

	//[UserPreSize]
	//[/UserPreSize]

	setSize(800, 800);

	//END GUI definitions


	// turn off Loading label
	mLoadingProjectLabel->setVisible(false);

	// init PolyEcho Service
	pes = std::make_unique<PolyEchoService>();

	audioSourcePlayer.setSource(&mixerAudioSource);
	//setup audio device manager with two channels, sterio
	audioDeviceManager.initialiseWithDefaultDevices(2, 2);
	audioDeviceManager.addAudioCallback(&audioSourcePlayer);

	//load project name from API into selection combobox
	loadProjects();

	//
	readyToPlay = false;
}

PolyEchoAudioProcessorEditor::~PolyEchoAudioProcessorEditor()
{

}

//==============================================================================
void PolyEchoAudioProcessorEditor::paint(juce::Graphics& g)
{
	g.fillAll(juce::Colour(0xff538999));
	{
		int x = 21, y = 22, width = 100, height = 50;
		//[UserPaintCustomArguments] Customize the painting arguments here..
		//[/UserPaintCustomArguments]
		g.setColour(juce::Colours::black);
		g.drawImageWithin(cachedImage_polyechologo_png_1,
			x, y, width, height,
			juce::RectanglePlacement::centred,
			false);
	}

	//g.setColour (juce::Colours::white);
	//g.setFont (15.0f);
	//g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1);
}

void PolyEchoAudioProcessorEditor::resized()
{
	// This is generally where you'll want to lay out the positions of any
	// subcomponents in your editor..
}

void PolyEchoAudioProcessorEditor::parameterChanged(const juce::String& parameterID, float newValue)
{
	//Used to update stuff on chagnes of parameters
	if (parameterID == "CONFIGWALLET")
	{
		std::cout << "Hello Wallet";
	}
}

//===============================================================================
void PolyEchoAudioProcessorEditor::loadProjects()
{
	mProjectsJson = pes->getProjects();

	if (mProjectsJson == nullptr)
		return;

	int counter = 1;
	for (auto project : mProjectsJson.at("data"))
	{
		auto projectName = project["/name"_json_pointer].get<std::string>();
		mProjectsComboBox->addItem(projectName, counter);
		counter++;
	}
}

void PolyEchoAudioProcessorEditor::onConfigWalletClicked()
{
	std::cout << "askfdjasldfjas;lf";
}

bool PolyEchoAudioProcessorEditor::isInterestedInFileDrag(const juce::StringArray& files)
{
	// this happens when the mouse is hovering in the plugin, don't process anything here
	return true;
}

void PolyEchoAudioProcessorEditor::filesDropped(const juce::StringArray& files, int x, int y)
{
	if (files.size() != 0)
	{
		auto filePath = files.begin()->toStdString();
		auto currentProjectName = mProjectsComboBox->getItemText(mProjectsComboBox->getSelectedItemIndex());

		mCurrentProject->addStem(filePath, StemType::Bass);
	}
}

void PolyEchoAudioProcessorEditor::prepareToPlay(int samplesPerBlockExpected, double sampleRate)
{
	mixerAudioSource.prepareToPlay(samplesPerBlockExpected, sampleRate);
}

void PolyEchoAudioProcessorEditor::releaseResources()
{
	mixerAudioSource.releaseResources();
}

void PolyEchoAudioProcessorEditor::getNextAudioBlock(const juce::AudioSourceChannelInfo& bufferToFill)
{
	for (std::unique_ptr<Stem>& stem : mCurrentProject->mStems)
	{
		if (stem->getReaderSource() == nullptr)
		{
			bufferToFill.clearActiveBufferRegion();
			return;
		}
	}

	mixerAudioSource.getNextAudioBlock(bufferToFill);
}

void PolyEchoAudioProcessorEditor::changeListenerCallback(juce::ChangeBroadcaster* source)
{
	for (std::unique_ptr<Stem>& stem : mCurrentProject->mStems)
	{
		if (source == stem->getTransportSource())
		{
			if (stem->getTransportSource()->isPlaying())
				changeState(Playing);
			else if ((state == Stopping) || (state == Playing))
				changeState(Stopped);
			else if (Pausing == state)
				changeState(Paused);
		}

	}

}
void PolyEchoAudioProcessorEditor::changeState(PlaybackState newState)
{
	if (state != newState)
	{
		state = newState;

		switch (state)
		{
		case Stopped:
			mPlayButton->setButtonText("Play");
			mStopButton->setButtonText("Stop");
			mStopButton->setEnabled(false);

			for (std::unique_ptr<Stem>& stem : mCurrentProject->mStems)
			{
				stem->getTransportSource()->setPosition(0.0);
				stem->startPositionIndicatorTimer();
			}

			break;

		case Starting:
			for (std::unique_ptr<Stem>& stem : mCurrentProject->mStems)
			{
				stem->getTransportSource()->start();
				stem->startPositionIndicatorTimer();
			}
			break;

		case Playing:
			mPlayButton->setButtonText("Pause");
			mStopButton->setButtonText("Stop");
			mStopButton->setEnabled(true);
			break;

		case Pausing:
			for (std::unique_ptr<Stem>& stem : mCurrentProject->mStems)
				stem->getTransportSource()->stop();
			break;

		case Paused:
			mPlayButton->setButtonText("Resume");
			mStopButton->setButtonText("Return to Zero");
			break;

		case Stopping:
			for (std::unique_ptr<Stem>& stem : mCurrentProject->mStems)
				stem->getTransportSource()->stop();
			break;
		}
	}
}


void PolyEchoAudioProcessorEditor::buttonClicked(juce::Button* buttonThatWasClicked)
{
	if (readyToPlay)
	{
		if (buttonThatWasClicked == mPlayButton.get())
		{
			if ((state == Stopped) || (state == Paused))
				changeState(Starting);
			else if (state == Playing)
				changeState(Pausing);
		}
		else if (buttonThatWasClicked == mStopButton.get())
		{
			if (state == Paused)
				changeState(Stopped);
			else
				changeState(Stopping);
		}
	}
}

void PolyEchoAudioProcessorEditor::comboBoxChanged(juce::ComboBox* comboBoxThatHasChanged)
{
	//mLoadingProjectLabel->setVisible(true);  // not working because the thread is held up TODO: get it working.

	// load current project
	mCurrentProject.reset(new Project(comboBoxThatHasChanged->getText(), mProjectsJson));
	addAndMakeVisible(mCurrentProject.get());
	auto xMargin = 24;
	auto yMargin = 100;

	//set audio player to new sources
	for (std::unique_ptr<Stem>& stem : mCurrentProject->mStems)
	{
		mixerAudioSource.addInputSource(stem->getTransportSource(), false);
		stem->getTransportSource()->addChangeListener(this);
	}

	// set main margins for project componenet
	mCurrentProject->setBounds(xMargin, 100, getWidth() - (xMargin * 2), getHeight() - (yMargin + 24));

	//reset Playback state
	state = PlaybackState::Stopped;

	readyToPlay = true;
}

This is a memory-access issue. You’re probably trying to dereference a dangling pointer - the fact that you’re trying to write to memory location 0x24 indicates that you’ve probably dereferenced a null pointer.

If you take a look at the call stack and variables at the point of the crash, you should be able to find out which variable is invalid. Then, you may be able to trace the issue up the stack to find the cause of the failure. As a starting point, if you’re writing to a very tiny pointer address, that normally indicates that you’re accessing what would normally be a data member of a null pointer. If you can find out which pointer is null, then you might also be able to work out why the pointer is null, and then add a check in your code to prevent this case.

If you have access to a Linux or macOS system, you could try enabling Address Sanitizer or Thread Sanitizer to see if you get any more helpful diagnostic messages. Address Sanitizer is also available on Windows with MSVC, but in my experience it’s less helpful than the implementations on macOS/Linux.

Yeah it makes sense, I just don’t know what’s null. I attempt to release the mixerAudioSource, remove listeners, etc. Seems like everything should be freed, so why is removing resources not checking if resources are already null before they are released? It seems definitive that the issue is in the audioSourcePlayer and mixerAudioSource. what’s inside those that I need to deal with to prevent this?

Or more to the point, am I not using these two classes correctly? I’ve tried them as unique_ptrs, even pointers that I new and delete myself… it seems to be an issue INSIDE either audioSourcePlayer or the mixerAudioSource, as when I delay, or stop the initialization of these classes, esp:

audioSourcePlayer.setSource(&mixerAudioSource);

The problem stops. How do I need to manage these classes so they can free up sources correctly.

It’d make sense that my transportSources are maybe freed before the mixerSourcePlayer. But that’s just from deconstruction of the vector that holds my transport sources. I tried releasingResources() from mixerSourcePlayer before deconstruction in the releaseResources of the editor, but to no avail.
EDIT: OH Yeah, this problem happens, even if I DO NOT load the mixerSource with transport sources.

That’s the odd thing. I only construct the editor, do no loading, and close the plugin, and those classes break. Seems odd to me.

Feel free to run through the code here, it’s open source, and I in fact want to make sure it’s secure and memory leak free:

Anyways, I’ll keep digging. I stared with C++ but work in C# now. Managing memory is not something I miss.

Bump

I think this is an order-of-destruction issue. In your constructor, you call audioSourcePlayer.setSource(&mixerAudioSource). The mixerAudioSource must remain alive for as long as the audioSourcePlayer has a pointer to it.

In the destructor of your editor, the mixerAudioSource will be destroyed before the audioSourcePlayer, due to the declaration order of the data members:

// destruction order is from bottom-to-top
juce::MixerAudioSource  mixerAudioSource;
juce::AudioDeviceManager audioDeviceManager;

You can resolve the issue by switching the order of these data members in the header, or by manually calling setSource (nullptr) in the destructor.

Ahhh freaking order of destruction got me again! that solved it. I’m def going to start my own little troubleshooting .md in my repo so I don’t forget this again. lol thanks Reuk, it’s really great to have a human help solve some of these problems.