Added a leak detector and corrected some bugs

This commit is contained in:
paulfd 2019-08-25 00:00:50 +02:00
parent 99c3e8878f
commit dba851385d
16 changed files with 181 additions and 61 deletions

View file

@ -181,7 +181,8 @@ void ADSREnvelope<Type>::getBlock(absl::Span<Type> output) noexcept
} }
originalSpan.remove_prefix(releaseDelay); originalSpan.remove_prefix(releaseDelay);
currentValue = originalSpan.front(); if (originalSpan.size() > 0)
currentValue = originalSpan.front();
step = std::exp((std::log(config::virtuallyZero) - std::log(currentValue)) / (release > 0 ? release : 1)); step = std::exp((std::log(config::virtuallyZero) - std::log(currentValue)) / (release > 0 ? release : 1));
remainingSamples -= releaseDelay; remainingSamples -= releaseDelay;
length = min(remainingSamples, release); length = min(remainingSamples, release);

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <absl/types/span.h> #include <absl/types/span.h>
#include "Helpers.h"
namespace sfz namespace sfz
{ {
@ -31,6 +32,7 @@ private:
Type sustain { 0 }; Type sustain { 0 };
int releaseDelay { 0 }; int releaseDelay { 0 };
bool shouldRelease { false }; bool shouldRelease { false };
LEAK_DETECTOR(ADSREnvelope);
}; };
} }

View file

@ -1,11 +1,11 @@
#pragma once #pragma once
#include "Globals.h" #include "Globals.h"
#include "Helpers.h"
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
template <class Type, unsigned int Alignment = SIMDConfig::defaultAlignment> template <class Type, unsigned int Alignment = SIMDConfig::defaultAlignment>
class Buffer class Buffer
{ {
@ -135,4 +135,5 @@ private:
pointer paddedData{nullptr}; pointer paddedData{nullptr};
pointer normalEnd{nullptr}; pointer normalEnd{nullptr};
pointer _alignedEnd{nullptr}; pointer _alignedEnd{nullptr};
LEAK_DETECTOR(Buffer);
}; };

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <map> #include <map>
#include "Helpers.h"
namespace sfz namespace sfz
{ {
@ -40,5 +41,6 @@ public:
private: private:
const ValueType defaultValue; const ValueType defaultValue;
std::map<int, ValueType> container; std::map<int, ValueType> container;
LEAK_DETECTOR(CCMap);
}; };
} }

View file

@ -67,6 +67,7 @@ struct EGDescription
{ {
return ccSwitchedValue(ccValues, ccSustain, sustain) + normalizeCC(velocity)*vel2sustain; return ccSwitchedValue(ccValues, ccSustain, sustain) + normalizeCC(velocity)*vel2sustain;
} }
LEAK_DETECTOR(EGDescription);
}; };
} //namespace sfz } //namespace sfz

View file

@ -62,6 +62,7 @@ void sfz::FilePool::loadingThread()
auto readBuffer = std::make_unique<Buffer<float>>(fileToLoad.numFrames * 2); auto readBuffer = std::make_unique<Buffer<float>>(fileToLoad.numFrames * 2);
sndFile.readf(readBuffer->data(), fileToLoad.numFrames); sndFile.readf(readBuffer->data(), fileToLoad.numFrames);
fileLoaded->readInterleaved(*readBuffer); fileLoaded->readInterleaved(*readBuffer);
ASSERT(fileLoaded != nullptr);
fileToLoad.voice->setFileData(std::move(fileLoaded)); fileToLoad.voice->setFileData(std::move(fileLoaded));
} }
} }

View file

@ -55,5 +55,6 @@ private:
Buffer<float> tempReadBuffer { config::preloadSize * 2 }; Buffer<float> tempReadBuffer { config::preloadSize * 2 };
// std::map<std::string_view, std::shared_ptr<StereoBuffer<float>>> preloadedData; // std::map<std::string_view, std::shared_ptr<StereoBuffer<float>>> preloadedData;
absl::flat_hash_map<std::string_view, std::shared_ptr<StereoBuffer<float>>> preloadedData; absl::flat_hash_map<std::string_view, std::shared_ptr<StereoBuffer<float>>> preloadedData;
LEAK_DETECTOR(FilePool);
}; };
} }

View file

@ -1,19 +1,16 @@
#pragma once #pragma once
#include <string_view>
#include <signal.h>
#include <random> #include <random>
#include <signal.h>
#include <string_view>
inline void trimInPlace(std::string_view& s) inline void trimInPlace(std::string_view& s)
{ {
const auto leftPosition = s.find_first_not_of(" \r\t\n\f\v"); const auto leftPosition = s.find_first_not_of(" \r\t\n\f\v");
if (leftPosition != s.npos) if (leftPosition != s.npos) {
{
s.remove_prefix(leftPosition); s.remove_prefix(leftPosition);
const auto rightPosition = s.find_last_not_of(" \r\t\n\f\v"); const auto rightPosition = s.find_last_not_of(" \r\t\n\f\v");
s.remove_suffix(s.size() - rightPosition - 1); s.remove_suffix(s.size() - rightPosition - 1);
} } else {
else
{
s.remove_suffix(s.size()); s.remove_suffix(s.size());
} }
} }
@ -21,14 +18,11 @@ inline void trimInPlace(std::string_view& s)
inline std::string_view trim(std::string_view s) inline std::string_view trim(std::string_view s)
{ {
const auto leftPosition = s.find_first_not_of(" \r\t\n\f\v"); const auto leftPosition = s.find_first_not_of(" \r\t\n\f\v");
if (leftPosition != s.npos) if (leftPosition != s.npos) {
{
s.remove_prefix(leftPosition); s.remove_prefix(leftPosition);
const auto rightPosition = s.find_last_not_of(" \r\t\n\f\v"); const auto rightPosition = s.find_last_not_of(" \r\t\n\f\v");
s.remove_suffix(s.size() - rightPosition - 1); s.remove_suffix(s.size() - rightPosition - 1);
} } else {
else
{
s.remove_suffix(s.size()); s.remove_suffix(s.size());
} }
return s; return s;
@ -36,7 +30,7 @@ inline std::string_view trim(std::string_view s)
inline constexpr unsigned int Fnv1aBasis = 0x811C9DC5; inline constexpr unsigned int Fnv1aBasis = 0x811C9DC5;
inline constexpr unsigned int Fnv1aPrime = 0x01000193; inline constexpr unsigned int Fnv1aPrime = 0x01000193;
inline constexpr unsigned int hash(const char *s, unsigned int h = Fnv1aBasis) inline constexpr unsigned int hash(const char* s, unsigned int h = Fnv1aBasis)
{ {
return !*s ? h : hash(s + 1, static_cast<unsigned int>((h ^ *s) * static_cast<unsigned long long>(Fnv1aPrime))); return !*s ? h : hash(s + 1, static_cast<unsigned int>((h ^ *s) * static_cast<unsigned long long>(Fnv1aPrime)));
} }
@ -49,26 +43,37 @@ inline unsigned int hash(std::string_view s, unsigned int h = Fnv1aBasis)
return h; return h;
} }
template<class T> template <class T>
inline constexpr T min(T op1, T op2) { return std::min(op1, op2); } inline constexpr T min(T op1, T op2) { return std::min(op1, op2); }
template<class T> template <class T>
inline constexpr T min(T op1, T op2, T op3) { return std::min(op1, std::min(op2, op3)); } inline constexpr T min(T op1, T op2, T op3) { return std::min(op1, std::min(op2, op3)); }
template<class T> template <class T>
inline constexpr T min(T op1, T op2, T op3, T op4) { return std::min(op1, std::min(op2, std::min(op3, op4))); } inline constexpr T min(T op1, T op2, T op3, T op4) { return std::min(op1, std::min(op2, std::min(op3, op4))); }
#ifndef NDEBUG #ifndef NDEBUG
#if __linux__ || __unix__ #if __linux__ || __unix__
// These trap into the signal library rather than your own sourcecode // These trap into the signal library rather than your own sourcecode
// #define ASSERTFALSE { ::kill(0, SIGTRAP); } // #define ASSERTFALSE { ::kill(0, SIGTRAP); }
// #define ASSERTFALSE { raise(SIGTRAP); } // #define ASSERTFALSE { raise(SIGTRAP); }
#define ASSERTFALSE { __asm__("int3"); } #define ASSERTFALSE \
{ \
__asm__("int3"); \
}
#elif _WIN32 || _WIN64 #elif _WIN32 || _WIN64
#pragma intrinsic (__debugbreak) #pragma intrinsic(__debugbreak)
#define ASSERTFALSE { __debugbreak(); } #define ASSERTFALSE \
{ \
__debugbreak(); \
}
#else #else
#define ASSERTFALSE { __asm int 3; } #define ASSERTFALSE \
{ \
__asm int 3; \
}
#endif #endif
#define ASSERT(expression) if (!(expression)) ASSERTFALSE #define ASSERT(expression) \
if (!(expression)) \
ASSERTFALSE
#include <iostream> #include <iostream>
#define DBG(ostream) std::cerr << ostream << '\n' #define DBG(ostream) std::cerr << ostream << '\n'
#else #else
@ -77,34 +82,33 @@ inline constexpr T min(T op1, T op2, T op3, T op4) { return std::min(op1, std::m
#define DBG(ostream) #define DBG(ostream)
#endif #endif
template<class Type> template <class Type>
inline constexpr Type db2pow(Type in) inline constexpr Type db2pow(Type in)
{ {
return std::pow(static_cast<Type>(10.0), in * static_cast<Type>(0.1)); return std::pow(static_cast<Type>(10.0), in * static_cast<Type>(0.1));
} }
template<class Type> template <class Type>
inline constexpr Type pow2db(Type in) inline constexpr Type pow2db(Type in)
{ {
return static_cast<Type>(10.0) * std::log10(in); return static_cast<Type>(10.0) * std::log10(in);
} }
template<class Type> template <class Type>
inline constexpr Type db2mag(Type in) inline constexpr Type db2mag(Type in)
{ {
return std::pow(static_cast<Type>(10.0), in * static_cast<Type>(0.05)); return std::pow(static_cast<Type>(10.0), in * static_cast<Type>(0.05));
} }
template<class Type> template <class Type>
inline constexpr Type mag2db(Type in) inline constexpr Type mag2db(Type in)
{ {
return static_cast<Type>(20.0) * std::log10(in); return static_cast<Type>(20.0) * std::log10(in);
} }
namespace Random namespace Random {
{ static inline std::random_device randomDevice;
static inline std::random_device randomDevice; static inline std::mt19937 randomGenerator { randomDevice() };
static inline std::mt19937 randomGenerator { randomDevice() };
} }
inline float midiNoteFrequency(const int noteNumber) inline float midiNoteFrequency(const int noteNumber)
@ -112,9 +116,66 @@ inline float midiNoteFrequency(const int noteNumber)
return 440.0f * std::pow(2.0f, (noteNumber - 69) / 12.0f); return 440.0f * std::pow(2.0f, (noteNumber - 69) / 12.0f);
} }
template<class Type> template <class Type>
constexpr Type pi { 3.141592653589793238462643383279502884 }; constexpr Type pi { 3.141592653589793238462643383279502884 };
template<class Type> template <class Type>
constexpr Type twoPi { 2*pi<Type> }; constexpr Type twoPi { 2 * pi<Type> };
template<class Type> template <class Type>
constexpr Type piTwo { pi<Type>/2 }; constexpr Type piTwo { pi<Type> / 2 };
#include <atomic>
template <class Owner>
class LeakDetector {
public:
LeakDetector()
{
// auto currentCounter = objectCounter.count.load();
// auto desiredCounter = currentCounter + 1;
// while(!objectCounter.count.compare_exchange_weak(currentCounter, desiredCounter))
// desiredCounter = currentCounter + 1;
objectCounter.count++;
// DBG("Counted " << desiredCounter << " " << Owner::getClassName());
}
LeakDetector(const LeakDetector&)
{
objectCounter.count++;
}
~LeakDetector()
{
objectCounter.count--;
// auto currentCounter = objectCounter.count.load();
// auto desiredCounter = currentCounter - 1;
// while(!objectCounter.count.compare_exchange_weak(currentCounter, desiredCounter))
// desiredCounter = currentCounter - 1;
// DBG("Counted " << desiredCounter << " " << Owner::getClassName() << " left after deletion");
if (objectCounter.count.load() < 0) {
DBG("Deleted a dangling pointer for class " << Owner::getClassName());
// Deleted a dangling pointer!
// ASSERTFALSE;
}
}
private:
struct ObjectCounter {
ObjectCounter() = default;
~ObjectCounter()
{
if (auto residualCount = count.load() > 0) {
DBG("Leaked " << residualCount << " instance(s) of class " << Owner::getClassName());
// Leaked ojects
// ASSERTFALSE;
}
};
std::atomic<int> count { 0 };
};
static inline ObjectCounter objectCounter;
};
#ifndef NDEBUG
#define LEAK_DETECTOR(Class) \
friend class LeakDetector<Class>; \
static const char* getClassName() noexcept { return #Class; } \
LeakDetector<Class> leakDetector;
#else
#define LEAK_DETECTOR(Class)
#endif

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "Globals.h" #include "Globals.h"
#include "Helpers.h"
#include <type_traits> #include <type_traits>
#include <functional> #include <functional>
#include <absl/types/span.h> #include <absl/types/span.h>
@ -25,6 +26,7 @@ private:
std::vector<std::pair<int, Type>> events; std::vector<std::pair<int, Type>> events;
int maxCapacity { config::defaultSamplesPerBlock }; int maxCapacity { config::defaultSamplesPerBlock };
Type currentValue { 0.0 }; Type currentValue { 0.0 };
LEAK_DETECTOR(LinearEnvelope);
}; };
} }

View file

@ -125,12 +125,15 @@ int sampleRateChanged(jack_nframes_t nframes, void* arg [[maybe_unused]])
return 0; return 0;
} }
static bool shouldClose { false };
static void done(int sig [[maybe_unused]]) static void done(int sig [[maybe_unused]])
{ {
std::cout << "Closing..." << '\n'; std::cout << "Signal received" << '\n';
if (client != nullptr) shouldClose = true;
jack_client_close(client); // if (client != nullptr)
exit(0);
// exit(0);
} }
int main(int argc, char** argv) int main(int argc, char** argv)
@ -229,6 +232,11 @@ int main(int argc, char** argv)
signal(SIGINT, done); signal(SIGINT, done);
signal(SIGTERM, done); signal(SIGTERM, done);
signal(SIGQUIT, done); signal(SIGQUIT, done);
sleep(-1);
while (!shouldClose)
sleep(1);
std::cout << "Closing..." << '\n';
jack_client_close(client);
return 0; return 0;
} }

View file

@ -19,6 +19,7 @@ struct Opcode
std::string_view value{}; std::string_view value{};
// This is to handle the integer parameter of some opcodes // This is to handle the integer parameter of some opcodes
std::optional<uint8_t> parameter; std::optional<uint8_t> parameter;
LEAK_DETECTOR(Opcode);
}; };
template<class ValueType> template<class ValueType>

View file

@ -153,6 +153,7 @@ private:
std::uniform_real_distribution<float> gainDistribution { -sfz::Default::ampRandom, sfz::Default::ampRandom }; std::uniform_real_distribution<float> gainDistribution { -sfz::Default::ampRandom, sfz::Default::ampRandom };
std::uniform_real_distribution<float> delayDistribution { 0, sfz::Default::delayRandom }; std::uniform_real_distribution<float> delayDistribution { 0, sfz::Default::delayRandom };
std::uniform_int_distribution<uint32_t> offsetDistribution { 0, sfz::Default::offsetRandom }; std::uniform_int_distribution<uint32_t> offsetDistribution { 0, sfz::Default::offsetRandom };
LEAK_DETECTOR(Region);
}; };
} // namespace sfz } // namespace sfz

View file

@ -2,7 +2,6 @@
#include "Buffer.h" #include "Buffer.h"
#include "Globals.h" #include "Globals.h"
#include "Helpers.h" #include "Helpers.h"
#include "SIMDHelpers.h" #include "SIMDHelpers.h"
#include <array> #include <array>
#include <iostream> #include <iostream>
@ -160,4 +159,5 @@ private:
int numFrames { 0 }; int numFrames { 0 };
Buffer<Type, Alignment> leftBuffer {}; Buffer<Type, Alignment> leftBuffer {};
Buffer<Type, Alignment> rightBuffer {}; Buffer<Type, Alignment> rightBuffer {};
LEAK_DETECTOR(StereoBuffer);
}; };

View file

@ -141,4 +141,5 @@ private:
size_t numFrames { 0 }; size_t numFrames { 0 };
absl::Span<Type> leftBuffer; absl::Span<Type> leftBuffer;
absl::Span<Type> rightBuffer; absl::Span<Type> rightBuffer;
LEAK_DETECTOR(StereoSpan);
}; };

View file

@ -2,14 +2,17 @@
#include "FilePool.h" #include "FilePool.h"
#include "Parser.h" #include "Parser.h"
#include "Region.h" #include "Region.h"
#include "StereoSpan.h"
#include "SfzHelpers.h" #include "SfzHelpers.h"
#include "StereoSpan.h"
#include "absl/types/span.h" #include "absl/types/span.h"
#include <optional> #include <optional>
#include <random> #include <random>
#include <set> #include <set>
#include <string_view> #include <string_view>
#include <thread>
#include <vector> #include <vector>
#include <chrono>
using namespace std::literals;
namespace sfz { namespace sfz {
@ -20,6 +23,13 @@ public:
for (int i = 0; i < config::numVoices; ++i) for (int i = 0; i < config::numVoices; ++i)
voices.push_back(std::make_unique<Voice>(ccState)); voices.push_back(std::make_unique<Voice>(ccState));
} }
~Synth()
{
threadsShouldQuit = true;
garbageCollectionThread.join();
}
bool loadSfzFile(const std::filesystem::path& file) final; bool loadSfzFile(const std::filesystem::path& file) final;
int getNumRegions() const noexcept { return static_cast<int>(regions.size()); } int getNumRegions() const noexcept { return static_cast<int>(regions.size()); }
int getNumGroups() const noexcept { return numGroups; } int getNumGroups() const noexcept { return numGroups; }
@ -34,7 +44,7 @@ public:
DBG("[Synth] Samples per block set to " << samplesPerBlock); DBG("[Synth] Samples per block set to " << samplesPerBlock);
this->samplesPerBlock = samplesPerBlock; this->samplesPerBlock = samplesPerBlock;
this->tempBuffer.resize(samplesPerBlock); this->tempBuffer.resize(samplesPerBlock);
for (auto & voice: voices) for (auto& voice : voices)
voice->setSamplesPerBlock(samplesPerBlock); voice->setSamplesPerBlock(samplesPerBlock);
} }
@ -42,7 +52,7 @@ public:
{ {
DBG("[Synth] Sample rate set to " << sampleRate); DBG("[Synth] Sample rate set to " << sampleRate);
this->sampleRate = sampleRate; this->sampleRate = sampleRate;
for (auto & voice: voices) for (auto& voice : voices)
voice->setSampleRate(sampleRate); voice->setSampleRate(sampleRate);
} }
@ -63,8 +73,7 @@ public:
for (auto& region : regions) { for (auto& region : regions) {
if (region->registerNoteOn(channel, noteNumber, velocity, randValue)) { if (region->registerNoteOn(channel, noteNumber, velocity, randValue)) {
for (auto& voice: voices) for (auto& voice : voices) {
{
if (voice->checkOffGroup(delay, region->group)) if (voice->checkOffGroup(delay, region->group))
noteOff(delay, voice->getTriggerChannel(), voice->getTriggerNumber(), 0); noteOff(delay, voice->getTriggerChannel(), voice->getTriggerNumber(), 0);
} }
@ -164,10 +173,21 @@ private:
std::mt19937 randomGenerator { rd() }; std::mt19937 randomGenerator { rd() };
std::uniform_real_distribution<float> randomDistribution { 0, 1 }; std::uniform_real_distribution<float> randomDistribution { 0, 1 };
bool threadsShouldQuit { false };
std::thread garbageCollectionThread { [&]() {
while (!threadsShouldQuit) {
for (auto& voice : voices)
voice->garbageCollect();
std::this_thread::sleep_for(1s);
}
} };
float getUniform() float getUniform()
{ {
return randomDistribution(randomGenerator); return randomDistribution(randomGenerator);
} }
LEAK_DETECTOR(Synth);
}; };
} }

View file

@ -65,8 +65,8 @@ public:
void setFileData(std::unique_ptr<StereoBuffer<float>> file) void setFileData(std::unique_ptr<StereoBuffer<float>> file)
{ {
fileData.reset(file.release()); fileData = std::move(file);
dataReady.store(true); dataReady.store(true, std::memory_order_seq_cst);
} }
bool isFree() bool isFree()
@ -76,12 +76,19 @@ public:
void registerNoteOff(int delay, int channel, int noteNumber, uint8_t velocity [[maybe_unused]]) void registerNoteOff(int delay, int channel, int noteNumber, uint8_t velocity [[maybe_unused]])
{ {
if (state == State::playing && triggerChannel == channel && triggerNumber == noteNumber) if (state == State::playing && triggerChannel == channel && triggerNumber == noteNumber) {
egEnvelope.startRelease(delay); noteIsOff = true;
if (ccState[64] < 63)
egEnvelope.startRelease(delay);
}
} }
void registerCC(int delay [[maybe_unused]], int channel [[maybe_unused]], int ccNumber [[maybe_unused]], uint8_t ccValue [[maybe_unused]]) void registerCC(int delay [[maybe_unused]], int channel [[maybe_unused]], int ccNumber [[maybe_unused]], uint8_t ccValue [[maybe_unused]])
{ {
if (ccNumber == 64 && noteIsOff && ccValue < 63)
egEnvelope.startRelease(delay);
} }
void registerPitchWheel(int delay, int channel, int pitch); void registerPitchWheel(int delay, int channel, int pitch);
@ -126,7 +133,7 @@ public:
void fillWithData(StereoSpan<float> buffer) void fillWithData(StereoSpan<float> buffer)
{ {
const StereoSpan<const float> source([&]() -> StereoBuffer<float>& { const StereoSpan<const float> source([&]() -> StereoBuffer<float>& {
if (dataReady) if (dataReady.load(std::memory_order_seq_cst) && fileData != nullptr)
return *fileData; return *fileData;
else else
return *region->preloadedData; return *region->preloadedData;
@ -135,6 +142,7 @@ public:
const auto actualBlockSize = min(buffer.size(), source.size() - sourcePosition); const auto actualBlockSize = min(buffer.size(), source.size() - sourcePosition);
buffer.add(source.subspan(sourcePosition, actualBlockSize)); buffer.add(source.subspan(sourcePosition, actualBlockSize));
sourcePosition += actualBlockSize; sourcePosition += actualBlockSize;
if (sourcePosition == source.size()) if (sourcePosition == source.size())
egEnvelope.startRelease(buffer.size()); egEnvelope.startRelease(buffer.size());
} }
@ -189,6 +197,13 @@ public:
region = nullptr; region = nullptr;
state = State::idle; state = State::idle;
dataReady.store(false); dataReady.store(false);
noteIsOff = false;
}
void garbageCollect()
{
if (state == State::idle && region == nullptr)
fileData.reset();
} }
private: private:
@ -200,6 +215,7 @@ private:
release release
}; };
State state; State state;
bool noteIsOff { false };
TriggerType triggerType; TriggerType triggerType;
int triggerNumber; int triggerNumber;
@ -214,7 +230,7 @@ private:
uint32_t sourcePosition; uint32_t sourcePosition;
uint32_t initialDelay; uint32_t initialDelay;
std::atomic<bool> dataReady; std::atomic<bool> dataReady { false };
std::unique_ptr<StereoBuffer<float>> fileData; std::unique_ptr<StereoBuffer<float>> fileData;
Buffer<float> tempBuffer1; Buffer<float> tempBuffer1;
@ -227,6 +243,7 @@ private:
const CCValueArray& ccState; const CCValueArray& ccState;
ADSREnvelope<float> egEnvelope; ADSREnvelope<float> egEnvelope;
LEAK_DETECTOR(Voice);
}; };
} // namespace sfz } // namespace sfz