Skip to content

Commit

Permalink
fix-memory-leaks (#91)
Browse files Browse the repository at this point in the history
* Fix many memory leaks (Some VSTs still have minor leaks such as TAL-NoiseMaker. Others are fine (Serum)). Fix issue with load_state: Now it works because a window is created but not shown to the user. This helps Spitfire LABS work.
  • Loading branch information
DBraun authored May 11, 2022
1 parent f0ebdfc commit e3762e1
Show file tree
Hide file tree
Showing 23 changed files with 344 additions and 285 deletions.
18 changes: 15 additions & 3 deletions .github/workflows/all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,22 @@ jobs:
- { python-version: "3.8", pythonLibPath: "/opt/python/cp39-cp38/lib", pythonInclude: "/opt/python/cp38-cp38/include/python3.8", cibwbuild: "cp38-manylinux_x86_64"}
- { python-version: "3.9", pythonLibPath: "/opt/python/cp39-cp39/lib", pythonInclude: "/opt/python/cp39-cp39/include/python3.9", cibwbuild: "cp39-manylinux_x86_64"}
- { python-version: "3.10", pythonLibPath: "/opt/python/cp310-cp310/lib", pythonInclude: "/opt/python/cp310-cp310/include/python3.10", cibwbuild: "cp310-manylinux_x86_64"}
# - { python-version: "3.7", pythonLibPath: "/opt/python/cp37-cp37m/lib", pythonInclude: "/opt/python/cp37-cp37m/include/python3.7m", cibwbuild: "cp37-manylinux_aarch64"}
# - { python-version: "3.8", pythonLibPath: "/opt/python/cp39-cp38/lib", pythonInclude: "/opt/python/cp38-cp38/include/python3.8", cibwbuild: "cp38-manylinux_aarch64"}
# - { python-version: "3.9", pythonLibPath: "/opt/python/cp39-cp39/lib", pythonInclude: "/opt/python/cp39-cp39/include/python3.9", cibwbuild: "cp39-manylinux_aarch64"}
# - { python-version: "3.10", pythonLibPath: "/opt/python/cp310-cp310/lib", pythonInclude: "/opt/python/cp310-cp310/include/python3.10", cibwbuild: "cp310-manylinux_aarch64"}

steps:
- uses: actions/checkout@v2
with:
submodules: true

- name: Set up QEMU for aarch64 on Linux
if: runner.os == 'Linux'
uses: docker/setup-qemu-action@v1
with:
platforms: all

- name: Checkout faustlibraries
uses: actions/checkout@v2
with:
Expand All @@ -45,15 +55,17 @@ jobs:
env:
CIBW_PLATFORM: linux
CIBW_BUILD: ${{ matrix.cibwbuild }}
CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
CIBW_BUILD_VERBOSITY: 3
CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
CIBW_MANYLINUX_AARCH64_IMAGE: manylinux2014
CIBW_BEFORE_BUILD_LINUX:
export PYTHONLIBPATH=${{ matrix.pythonLibPath }} && export PYTHONINCLUDEPATH=${{ matrix.pythonInclude }} && sh -v build_linux.sh
CIBW_REPAIR_WHEEL_COMMAND_LINUX: >
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/dawdreamer && pip install auditwheel-symbols && (auditwheel repair -w {dest_dir} {wheel} || auditwheel-symbols --manylinux 2014 {wheel})
CIBW_TEST_REQUIRES: -r test-requirements.txt soundfile
CIBW_TEST_COMMAND: "rm -rf dawdreamer/*.so* && cd {project}/tests && python -m pytest -v ."
CIBW_ARCHS: auto64
CIBW_ARCHS_LINUX: "auto aarch64" # On an Linux Intel runner with qemu installed, build Intel and ARM wheels

- uses: actions/upload-artifact@v2
with:
Expand Down Expand Up @@ -140,7 +152,7 @@ jobs:
PYTHONMAJOR: ${{ matrix.python-version }}
CIBW_BUILD_VERBOSITY: 1
CIBW_TEST_REQUIRES: -r test-requirements.txt
CIBW_TEST_COMMAND: "cd /D {project}\\tests && python -m pytest ."
CIBW_TEST_COMMAND: "cd /D {project}\\tests && python -m pytest -v ."
CIBW_ARCHS: auto64
CIBW_BUILD: ${{matrix.CIBW-BUILD}}

Expand Down Expand Up @@ -217,7 +229,7 @@ jobs:
install_name_tool -change @rpath/libfaust.2.dylib @loader_path/libfaust.2.dylib Builds/MacOSX/build/Release/dawdreamer.so
otool -L Builds/MacOSX/build/Release/dawdreamer.so
CIBW_TEST_REQUIRES: -r test-requirements.txt
CIBW_TEST_COMMAND: "cd {project}/tests; python -m pytest -s ."
CIBW_TEST_COMMAND: "cd {project}/tests; python -m pytest -v ."
CIBW_BUILD: ${{matrix.build}}
CIBW_ARCHS: auto64
CIBW_ARCHS_MACOS: ${{matrix.archs-macos}}
Expand Down
4 changes: 2 additions & 2 deletions Builds/LinuxMakefile/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ ifeq ($(CONFIG),Debug)

JUCE_CFLAGS += $(JUCE_CPPFLAGS) $(TARGET_ARCH) -fPIC -g -ggdb -O0 -fPIC $(CFLAGS)
JUCE_CXXFLAGS += $(JUCE_CFLAGS) -std=c++17 $(CXXFLAGS)
JUCE_LDFLAGS += $(TARGET_ARCH) -L$(JUCE_BINDIR) -L$(JUCE_LIBDIR) -L/usr/local/lib -L../../thirdparty/libfaust/ubuntu-x86_64/lib -L../../thirdparty/libsamplerate/build_release/src $(shell pkg-config --libs alsa freetype2) -fvisibility=hidden -lrt -ldl -lpthread -lGL -lsamplerate -lfaust $(LDFLAGS)
JUCE_LDFLAGS += $(TARGET_ARCH) -L$(JUCE_BINDIR) -L$(JUCE_LIBDIR) -L/usr/local/lib -L../../thirdparty/libfaust/ubuntu-x86_64/lib -L../../thirdparty/libfaust/ubuntu-aarch64/lib -L../../thirdparty/libsamplerate/build_release/src $(shell pkg-config --libs alsa freetype2) -fvisibility=hidden -lrt -ldl -lpthread -lGL -lsamplerate -lfaust $(LDFLAGS)

CLEANCMD = rm -rf $(JUCE_OUTDIR)/$(TARGET) $(JUCE_OBJDIR)
endif
Expand All @@ -66,7 +66,7 @@ ifeq ($(CONFIG),Release)

JUCE_CFLAGS += $(JUCE_CPPFLAGS) $(TARGET_ARCH) -fPIC -O3 -fPIC $(CFLAGS)
JUCE_CXXFLAGS += $(JUCE_CFLAGS) -std=c++17 $(CXXFLAGS)
JUCE_LDFLAGS += $(TARGET_ARCH) -L$(JUCE_BINDIR) -L$(JUCE_LIBDIR) -L/usr/local/lib -L../../thirdparty/libfaust/ubuntu-x86_64/lib -L../../thirdparty/libsamplerate/build_release/src $(shell pkg-config --libs alsa freetype2) -fvisibility=hidden -lrt -ldl -lpthread -lGL -lsamplerate -lfaust $(LDFLAGS)
JUCE_LDFLAGS += $(TARGET_ARCH) -L$(JUCE_BINDIR) -L$(JUCE_LIBDIR) -L/usr/local/lib -L../../thirdparty/libfaust/ubuntu-x86_64/lib -L../../thirdparty/libfaust/ubuntu-aarch64/lib -L../../thirdparty/libsamplerate/build_release/src $(shell pkg-config --libs alsa freetype2) -fvisibility=hidden -lrt -ldl -lpthread -lGL -lsamplerate -lfaust $(LDFLAGS)

CLEANCMD = rm -rf $(JUCE_OUTDIR)/$(TARGET) $(JUCE_OBJDIR)
endif
Expand Down
4 changes: 2 additions & 2 deletions DawDreamer.jucer
Original file line number Diff line number Diff line change
Expand Up @@ -279,10 +279,10 @@
<CONFIGURATIONS>
<CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="dawdreamer"
headerPath="../../thirdparty/pybind11/include;&#10;../../thirdparty/faust/architecture;&#10;../../thirdparty/faust/compiler;&#10;../../thirdparty/faust/compiler/utils;&#10;../../thirdparty/libsamplerate/src;&#10;../../thirdparty/libsamplerate/include;&#10;../../thirdparty/rubberband;&#10;../../thirdparty/rubberband/rubberband;&#10;../../thirdparty/rubberband/src/kissfft;&#10;../../thirdparty/rubberband/src;&#10;../../thirdparty/portable_endian/include;"
libraryPath="/usr/local/lib&#10;../../thirdparty/libfaust/ubuntu-x86_64/lib&#10;../../thirdparty/libsamplerate/build_release/src"/>
libraryPath="/usr/local/lib&#10;../../thirdparty/libfaust/ubuntu-x86_64/lib&#10;../../thirdparty/libfaust/ubuntu-aarch64/lib&#10;../../thirdparty/libsamplerate/build_release/src"/>
<CONFIGURATION name="Release" isDebug="0" optimisation="3" targetName="dawdreamer"
headerPath="../../thirdparty/pybind11/include;&#10;../../thirdparty/faust/architecture;&#10;../../thirdparty/faust/compiler;&#10;../../thirdparty/faust/compiler/utils;&#10;../../thirdparty/libsamplerate/src;&#10;../../thirdparty/libsamplerate/include;&#10;../../thirdparty/rubberband;&#10;../../thirdparty/rubberband/rubberband;&#10;../../thirdparty/rubberband/src/kissfft;&#10;../../thirdparty/rubberband/src;&#10;../../thirdparty/portable_endian/include;"
libraryPath="/usr/local/lib&#10;../../thirdparty/libfaust/ubuntu-x86_64/lib&#10;../../thirdparty/libsamplerate/build_release/src"/>
libraryPath="/usr/local/lib&#10;../../thirdparty/libfaust/ubuntu-x86_64/lib&#10;../../thirdparty/libfaust/ubuntu-aarch64/lib&#10;../../thirdparty/libsamplerate/build_release/src"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_core" path="JuceLibraryCode/modules"/>
Expand Down
13 changes: 2 additions & 11 deletions Source/FaustProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,8 @@ FaustProcessor::clear()
SAFE_DELETE(m_dsp_poly);

// deleteAllDSPFactories(); // don't actually do this!!
// deleteDSPFactory(m_factory);
// deleteDSPFactory(m_poly_factory);

m_factory = NULL;
m_poly_factory = NULL;
deleteDSPFactory(m_factory);
SAFE_DELETE(m_poly_factory);
}

void
Expand Down Expand Up @@ -245,7 +242,6 @@ FaustProcessor::setDSPString(const std::string& code)

if (std::strcmp(code.c_str(), "") == 0) {
throw std::runtime_error("DSP string is empty.");
return false;
}

// save
Expand Down Expand Up @@ -401,7 +397,6 @@ FaustProcessor::setDSPFile(const std::string& path)
m_isCompiled = false;
if (std::strcmp(path.c_str(), "") == 0) {
throw std::runtime_error("Path to DSP file is empty.");
return false;
}

// open file
Expand All @@ -411,7 +406,6 @@ FaustProcessor::setDSPFile(const std::string& path)
{
// error
throw std::runtime_error("FaustProcessor::setDSPFile(): ERROR opening file: '" + path + "'");
return false;
}

// clear code string
Expand All @@ -434,14 +428,12 @@ FaustProcessor::setParamWithIndex(const int index, float p)
}
if (!m_ui) {
throw std::runtime_error("No UI for FaustProcessor.");
return false;
}

auto it = m_map_juceIndex_to_parAddress.find(index);
if (it == m_map_juceIndex_to_parAddress.end())
{
throw std::runtime_error("A parameter with index " + std::to_string(index) + " is not valid for this FaustProcessor.");
return false;
}

auto& parAddress = it->second;
Expand Down Expand Up @@ -655,7 +647,6 @@ FaustProcessor::addMidiNote(uint8 midiNote,
if (midiVelocity < 0) midiVelocity = 0;
if (noteLength <= 0) {
throw std::runtime_error("The note length must be greater than zero.");
return false;
}

// Get the note on midiBuffer.
Expand Down
5 changes: 0 additions & 5 deletions Source/FaustProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ class MySoundUI : public SoundUI {

public:

~MySoundUI() {
fSoundfileMap.clear(); // todo: deallocate the Soundfiles correctly
}

virtual void addSoundfile(const char* label, const char* filename, Soundfile** sf_zone) {
// Parse the possible list
std::string saved_url_real = std::string(label);
Expand Down Expand Up @@ -54,7 +50,6 @@ class MySoundUI : public SoundUI {
numChannels = std::max(numChannels, buffer.getNumChannels());
}

// todo: used subtract path_name_list.size() instead of 1.
total_length += (MAX_SOUNDFILE_PARTS - buffers.size()) * BUFFER_SIZE;

Soundfile* soundfile = new Soundfile(numChannels, total_length, MAX_CHAN, buffers.size(), false);
Expand Down
13 changes: 7 additions & 6 deletions Source/PlaybackProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,16 @@ class PlaybackProcessor : public ProcessorBase
void setData(py::array_t<float, py::array::c_style | py::array::forcecast> input) {
float* input_ptr = (float*)input.data();

myPlaybackData.setSize(input.shape(0), input.shape(1));

int numChannels = input.shape(0);
int numSamples = input.shape(1);

myPlaybackData.setSize(numChannels, numSamples);

for (int y = 0; y < input.shape(1); y++) {
for (int x = 0; x < input.shape(0); x++) {
myPlaybackData.setSample(x, y, input_ptr[x * input.shape(1) + y]);
}
for (int chan = 0; chan < numChannels; chan++) {
myPlaybackData.copyFrom(chan, 0, input_ptr, numSamples);
input_ptr += numSamples;
}

setMainBusInputsAndOutputs(0, numChannels);
}

Expand Down
26 changes: 12 additions & 14 deletions Source/PluginProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "StandalonePluginWindow.h"

static std::mutex PLUGIN_INSTANCE_MUTEX;
using juce::ExtensionsVisitor;

struct PresetVisitor : public ExtensionsVisitor {
Expand Down Expand Up @@ -75,19 +76,19 @@ PluginProcessor::loadPlugin(double sampleRate, int samplesPerBlock) {

if (myPlugin.get())
{
myPlugin->releaseResources();
myPlugin.release();
std::lock_guard<std::mutex> lock(PLUGIN_INSTANCE_MUTEX);
myPlugin.reset();
}

// If there is a problem here first check the preprocessor definitions
// in the projucer are sensible - is it set up to scan for plugin's?
if (pluginDescriptions.size() <= 0) {
throw std::runtime_error("Unable to load plugin.");
return false;
}

String errorMessage;

std::lock_guard<std::mutex> lock(PLUGIN_INSTANCE_MUTEX);
myPlugin = pluginFormatManager.createPluginInstance(*pluginDescriptions[0],
sampleRate,
samplesPerBlock,
Expand All @@ -96,7 +97,6 @@ PluginProcessor::loadPlugin(double sampleRate, int samplesPerBlock) {
if (myPlugin.get() == nullptr)
{
throw std::runtime_error("PluginProcessor::loadPlugin error: " + errorMessage.toStdString());
return false;
}

myPlugin->enableAllBuses();
Expand Down Expand Up @@ -127,8 +127,8 @@ PluginProcessor::loadPlugin(double sampleRate, int samplesPerBlock) {
PluginProcessor::~PluginProcessor() {
if (myPlugin.get())
{
myPlugin->releaseResources();
myPlugin.release();
std::lock_guard<std::mutex> lock(PLUGIN_INSTANCE_MUTEX);
myPlugin.reset();
}
delete myMidiIterator;
}
Expand Down Expand Up @@ -235,6 +235,7 @@ PluginProcessor::reset()

delete myMidiIterator;
myMidiIterator = new MidiBuffer::Iterator(myMidiBuffer); // todo: deprecated.

myMidiEventsDoRemain = myMidiIterator->getNextEvent(myMidiMessage, myMidiMessagePosition);
myRenderMidiBuffer.clear();
}
Expand All @@ -244,13 +245,11 @@ PluginProcessor::loadPreset(const std::string& path)
{
if (!myPlugin.get()) {
throw std::runtime_error("You must load a plugin before loading a preset.");
return false;
}

try {
if (!std::filesystem::exists(path.c_str())) {
throw std::runtime_error("File not found: " + path);
return false;
}

MemoryBlock mb;
Expand All @@ -269,7 +268,6 @@ PluginProcessor::loadPreset(const std::string& path)
}
catch (std::exception& e) {
throw std::runtime_error(std::string("Error: (PluginProcessor::loadPreset) ") + e.what());
return false;
}

}
Expand All @@ -279,7 +277,6 @@ PluginProcessor::loadVST3Preset(const std::string& path)
{
if (!myPlugin.get()) {
throw std::runtime_error("You must load a plugin before loading a preset.");
return false;
}

juce::File fPath(path);
Expand All @@ -290,7 +287,6 @@ PluginProcessor::loadVST3Preset(const std::string& path)

if (!std::filesystem::exists(path.c_str())) {
throw std::runtime_error("Preset file not found: " + path);
return false;
}

PresetVisitor presetVisitor{ path };
Expand All @@ -302,7 +298,6 @@ PluginProcessor::loadVST3Preset(const std::string& path)
catch (const std::exception&)
{
throw std::runtime_error("PluginProcessor::loadVST3Preset: unknown error.");
return false;
}

for (int i = 0; i < myPlugin->getNumParameters(); i++) {
Expand Down Expand Up @@ -330,6 +325,11 @@ PluginProcessor::loadStateInformation(std::string filepath) {
std::string paramID = std::to_string(i);
ProcessorBase::setAutomationVal(paramID, myPlugin->getParameter(i));
}

// todo: this is a little hacky. We create a window because this forces the loaded state to take effect
// in certain plugins.
// This allows us to call load_state and not bother calling open_editor().
StandalonePluginWindow tmp_window(*this, *myPlugin);
}

void
Expand Down Expand Up @@ -520,7 +520,6 @@ PluginProcessor::addMidiNote(uint8 midiNote,
if (midiVelocity < 0) midiVelocity = 0;
if (noteLength <= 0) {
throw std::runtime_error("The note length must be greater than zero.");
return false;
}

// Get the note on midiBuffer.
Expand Down Expand Up @@ -588,7 +587,6 @@ PluginProcessorWrapper::wrapperSetParameter(int parameter, float value)
{
if (!myPlugin) {
throw std::runtime_error("Please load the plugin first!");
return false;
}

std::string paramID = std::to_string(parameter);
Expand Down
7 changes: 0 additions & 7 deletions Source/ProcessorBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,11 @@ bool ProcessorBase::setAutomation(std::string parameterName, py::array input) {
}
else {
throw std::runtime_error("Failed to find parameter: " + parameterName);
return false;
}
}
catch (const std::exception& e)
{
throw std::runtime_error("Failed to set '" + parameterName + "' automation: " + e.what());
return false;
}

return true;
Expand All @@ -56,13 +54,11 @@ bool ProcessorBase::setAutomationVal(std::string parameterName, float val) {
}
else {
throw std::runtime_error("Failed to find parameter: " + parameterName);
return false;
}
}
catch (const std::exception& e)
{
throw std::runtime_error("Failed to set '" + parameterName + "' automation: " + e.what());
return false;
}

return true;
Expand All @@ -76,8 +72,6 @@ std::vector<float> ProcessorBase::getAutomation(std::string parameterName) {
}
else {
throw std::runtime_error("Failed to get automation values for parameter: " + parameterName);
std::vector<float> empty;
return empty;
}
}

Expand All @@ -89,7 +83,6 @@ float ProcessorBase::getAutomationVal(std::string parameterName, int index) {
}
else {
throw std::runtime_error("Failed to get automation value for parameter: " + parameterName);
return 0.f;
}
}

Expand Down
5 changes: 4 additions & 1 deletion Source/ProcessorBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,14 @@ class ProcessorBase : public juce::AudioProcessor

auto ra = arr.mutable_unchecked();

auto chans = myRecordBuffer.getArrayOfReadPointers();
for (size_t i = 0; i < num_channels; i++)
{
auto chanPtr = chans[i];

for (size_t j = 0; j < num_samples; j++)
{
ra(i, j) = myRecordBuffer.getSample(i, j);
ra(i, j) = *(chanPtr++);
}
}

Expand Down
Loading

0 comments on commit e3762e1

Please sign in to comment.