Added a leak detector and corrected some bugs
This commit is contained in:
parent
99c3e8878f
commit
dba851385d
16 changed files with 181 additions and 61 deletions
|
|
@ -181,7 +181,8 @@ void ADSREnvelope<Type>::getBlock(absl::Span<Type> output) noexcept
|
|||
}
|
||||
|
||||
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));
|
||||
remainingSamples -= releaseDelay;
|
||||
length = min(remainingSamples, release);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include <absl/types/span.h>
|
||||
#include "Helpers.h"
|
||||
namespace sfz
|
||||
{
|
||||
|
||||
|
|
@ -31,6 +32,7 @@ private:
|
|||
Type sustain { 0 };
|
||||
int releaseDelay { 0 };
|
||||
bool shouldRelease { false };
|
||||
LEAK_DETECTOR(ADSREnvelope);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
#pragma once
|
||||
#include "Globals.h"
|
||||
#include "Helpers.h"
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
template <class Type, unsigned int Alignment = SIMDConfig::defaultAlignment>
|
||||
class Buffer
|
||||
{
|
||||
|
|
@ -135,4 +135,5 @@ private:
|
|||
pointer paddedData{nullptr};
|
||||
pointer normalEnd{nullptr};
|
||||
pointer _alignedEnd{nullptr};
|
||||
LEAK_DETECTOR(Buffer);
|
||||
};
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include <map>
|
||||
#include "Helpers.h"
|
||||
|
||||
namespace sfz
|
||||
{
|
||||
|
|
@ -40,5 +41,6 @@ public:
|
|||
private:
|
||||
const ValueType defaultValue;
|
||||
std::map<int, ValueType> container;
|
||||
LEAK_DETECTOR(CCMap);
|
||||
};
|
||||
}
|
||||
|
|
@ -67,6 +67,7 @@ struct EGDescription
|
|||
{
|
||||
return ccSwitchedValue(ccValues, ccSustain, sustain) + normalizeCC(velocity)*vel2sustain;
|
||||
}
|
||||
LEAK_DETECTOR(EGDescription);
|
||||
};
|
||||
|
||||
} //namespace sfz
|
||||
|
|
@ -62,6 +62,7 @@ void sfz::FilePool::loadingThread()
|
|||
auto readBuffer = std::make_unique<Buffer<float>>(fileToLoad.numFrames * 2);
|
||||
sndFile.readf(readBuffer->data(), fileToLoad.numFrames);
|
||||
fileLoaded->readInterleaved(*readBuffer);
|
||||
ASSERT(fileLoaded != nullptr);
|
||||
fileToLoad.voice->setFileData(std::move(fileLoaded));
|
||||
}
|
||||
}
|
||||
|
|
@ -55,5 +55,6 @@ private:
|
|||
Buffer<float> tempReadBuffer { config::preloadSize * 2 };
|
||||
// std::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);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,34 +1,28 @@
|
|||
#pragma once
|
||||
#include <string_view>
|
||||
#include <signal.h>
|
||||
#include <random>
|
||||
#include <signal.h>
|
||||
#include <string_view>
|
||||
|
||||
inline void trimInPlace(std::string_view& s)
|
||||
{
|
||||
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);
|
||||
const auto rightPosition = s.find_last_not_of(" \r\t\n\f\v");
|
||||
s.remove_suffix(s.size() - rightPosition - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
s.remove_suffix(s.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string_view trim(std::string_view s)
|
||||
{
|
||||
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);
|
||||
const auto rightPosition = s.find_last_not_of(" \r\t\n\f\v");
|
||||
s.remove_suffix(s.size() - rightPosition - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
s.remove_suffix(s.size());
|
||||
}
|
||||
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 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)));
|
||||
}
|
||||
|
|
@ -49,72 +43,139 @@ inline unsigned int hash(std::string_view s, unsigned int h = Fnv1aBasis)
|
|||
return h;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
template <class T>
|
||||
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)); }
|
||||
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))); }
|
||||
|
||||
#ifndef NDEBUG
|
||||
#if __linux__ || __unix__
|
||||
// These trap into the signal library rather than your own sourcecode
|
||||
// #define ASSERTFALSE { ::kill(0, SIGTRAP); }
|
||||
// #define ASSERTFALSE { raise(SIGTRAP); }
|
||||
#define ASSERTFALSE { __asm__("int3"); }
|
||||
// These trap into the signal library rather than your own sourcecode
|
||||
// #define ASSERTFALSE { ::kill(0, SIGTRAP); }
|
||||
// #define ASSERTFALSE { raise(SIGTRAP); }
|
||||
#define ASSERTFALSE \
|
||||
{ \
|
||||
__asm__("int3"); \
|
||||
}
|
||||
#elif _WIN32 || _WIN64
|
||||
#pragma intrinsic (__debugbreak)
|
||||
#define ASSERTFALSE { __debugbreak(); }
|
||||
#pragma intrinsic(__debugbreak)
|
||||
#define ASSERTFALSE \
|
||||
{ \
|
||||
__debugbreak(); \
|
||||
}
|
||||
#else
|
||||
#define ASSERTFALSE { __asm int 3; }
|
||||
#define ASSERTFALSE \
|
||||
{ \
|
||||
__asm int 3; \
|
||||
}
|
||||
#endif
|
||||
#define ASSERT(expression) if (!(expression)) ASSERTFALSE
|
||||
#define ASSERT(expression) \
|
||||
if (!(expression)) \
|
||||
ASSERTFALSE
|
||||
#include <iostream>
|
||||
#define DBG(ostream) std::cerr << ostream << '\n'
|
||||
#else
|
||||
#define ASSERTFALSE
|
||||
#define ASSERTFALSE
|
||||
#define ASSERT(expression)
|
||||
#define DBG(ostream)
|
||||
#endif
|
||||
|
||||
template<class Type>
|
||||
template <class Type>
|
||||
inline constexpr Type db2pow(Type in)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return static_cast<Type>(10.0) * std::log10(in);
|
||||
}
|
||||
|
||||
template<class Type>
|
||||
template <class Type>
|
||||
inline constexpr Type db2mag(Type in)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return static_cast<Type>(20.0) * std::log10(in);
|
||||
}
|
||||
|
||||
namespace Random
|
||||
{
|
||||
static inline std::random_device randomDevice;
|
||||
static inline std::mt19937 randomGenerator { randomDevice() };
|
||||
}
|
||||
namespace Random {
|
||||
static inline std::random_device randomDevice;
|
||||
static inline std::mt19937 randomGenerator { randomDevice() };
|
||||
}
|
||||
|
||||
inline float midiNoteFrequency(const int noteNumber)
|
||||
inline float midiNoteFrequency(const int noteNumber)
|
||||
{
|
||||
return 440.0f * std::pow(2.0f, (noteNumber - 69) / 12.0f);
|
||||
}
|
||||
|
||||
template<class Type>
|
||||
template <class Type>
|
||||
constexpr Type pi { 3.141592653589793238462643383279502884 };
|
||||
template<class Type>
|
||||
constexpr Type twoPi { 2*pi<Type> };
|
||||
template<class Type>
|
||||
constexpr Type piTwo { pi<Type>/2 };
|
||||
template <class Type>
|
||||
constexpr Type twoPi { 2 * pi<Type> };
|
||||
template <class Type>
|
||||
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
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include "Globals.h"
|
||||
#include "Helpers.h"
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <absl/types/span.h>
|
||||
|
|
@ -25,6 +26,7 @@ private:
|
|||
std::vector<std::pair<int, Type>> events;
|
||||
int maxCapacity { config::defaultSamplesPerBlock };
|
||||
Type currentValue { 0.0 };
|
||||
LEAK_DETECTOR(LinearEnvelope);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -125,12 +125,15 @@ int sampleRateChanged(jack_nframes_t nframes, void* arg [[maybe_unused]])
|
|||
return 0;
|
||||
}
|
||||
|
||||
static bool shouldClose { false };
|
||||
|
||||
static void done(int sig [[maybe_unused]])
|
||||
{
|
||||
std::cout << "Closing..." << '\n';
|
||||
if (client != nullptr)
|
||||
jack_client_close(client);
|
||||
exit(0);
|
||||
std::cout << "Signal received" << '\n';
|
||||
shouldClose = true;
|
||||
// if (client != nullptr)
|
||||
|
||||
// exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
|
|
@ -229,6 +232,11 @@ int main(int argc, char** argv)
|
|||
signal(SIGINT, done);
|
||||
signal(SIGTERM, done);
|
||||
signal(SIGQUIT, done);
|
||||
sleep(-1);
|
||||
|
||||
while (!shouldClose)
|
||||
sleep(1);
|
||||
|
||||
std::cout << "Closing..." << '\n';
|
||||
jack_client_close(client);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ struct Opcode
|
|||
std::string_view value{};
|
||||
// This is to handle the integer parameter of some opcodes
|
||||
std::optional<uint8_t> parameter;
|
||||
LEAK_DETECTOR(Opcode);
|
||||
};
|
||||
|
||||
template<class ValueType>
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ private:
|
|||
std::uniform_real_distribution<float> gainDistribution { -sfz::Default::ampRandom, sfz::Default::ampRandom };
|
||||
std::uniform_real_distribution<float> delayDistribution { 0, sfz::Default::delayRandom };
|
||||
std::uniform_int_distribution<uint32_t> offsetDistribution { 0, sfz::Default::offsetRandom };
|
||||
LEAK_DETECTOR(Region);
|
||||
};
|
||||
|
||||
} // namespace sfz
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
#include "Buffer.h"
|
||||
#include "Globals.h"
|
||||
#include "Helpers.h"
|
||||
|
||||
#include "SIMDHelpers.h"
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
|
|
@ -160,4 +159,5 @@ private:
|
|||
int numFrames { 0 };
|
||||
Buffer<Type, Alignment> leftBuffer {};
|
||||
Buffer<Type, Alignment> rightBuffer {};
|
||||
LEAK_DETECTOR(StereoBuffer);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -141,4 +141,5 @@ private:
|
|||
size_t numFrames { 0 };
|
||||
absl::Span<Type> leftBuffer;
|
||||
absl::Span<Type> rightBuffer;
|
||||
LEAK_DETECTOR(StereoSpan);
|
||||
};
|
||||
|
|
@ -2,14 +2,17 @@
|
|||
#include "FilePool.h"
|
||||
#include "Parser.h"
|
||||
#include "Region.h"
|
||||
#include "StereoSpan.h"
|
||||
#include "SfzHelpers.h"
|
||||
#include "StereoSpan.h"
|
||||
#include "absl/types/span.h"
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
using namespace std::literals;
|
||||
|
||||
namespace sfz {
|
||||
|
||||
|
|
@ -20,6 +23,13 @@ public:
|
|||
for (int i = 0; i < config::numVoices; ++i)
|
||||
voices.push_back(std::make_unique<Voice>(ccState));
|
||||
}
|
||||
|
||||
~Synth()
|
||||
{
|
||||
threadsShouldQuit = true;
|
||||
garbageCollectionThread.join();
|
||||
}
|
||||
|
||||
bool loadSfzFile(const std::filesystem::path& file) final;
|
||||
int getNumRegions() const noexcept { return static_cast<int>(regions.size()); }
|
||||
int getNumGroups() const noexcept { return numGroups; }
|
||||
|
|
@ -34,7 +44,7 @@ public:
|
|||
DBG("[Synth] Samples per block set to " << samplesPerBlock);
|
||||
this->samplesPerBlock = samplesPerBlock;
|
||||
this->tempBuffer.resize(samplesPerBlock);
|
||||
for (auto & voice: voices)
|
||||
for (auto& voice : voices)
|
||||
voice->setSamplesPerBlock(samplesPerBlock);
|
||||
}
|
||||
|
||||
|
|
@ -42,14 +52,14 @@ public:
|
|||
{
|
||||
DBG("[Synth] Sample rate set to " << sampleRate);
|
||||
this->sampleRate = sampleRate;
|
||||
for (auto & voice: voices)
|
||||
for (auto& voice : voices)
|
||||
voice->setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
void renderBlock(StereoSpan<float> buffer)
|
||||
{
|
||||
buffer.fill(0.0f);
|
||||
|
||||
|
||||
StereoSpan<float> tempSpan { tempBuffer, buffer.size() };
|
||||
for (auto& voice : voices) {
|
||||
voice->renderBlock(tempSpan);
|
||||
|
|
@ -63,8 +73,7 @@ public:
|
|||
|
||||
for (auto& region : regions) {
|
||||
if (region->registerNoteOn(channel, noteNumber, velocity, randValue)) {
|
||||
for (auto& voice: voices)
|
||||
{
|
||||
for (auto& voice : voices) {
|
||||
if (voice->checkOffGroup(delay, region->group))
|
||||
noteOff(delay, voice->getTriggerChannel(), voice->getTriggerNumber(), 0);
|
||||
}
|
||||
|
|
@ -164,10 +173,21 @@ private:
|
|||
std::mt19937 randomGenerator { rd() };
|
||||
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()
|
||||
{
|
||||
return randomDistribution(randomGenerator);
|
||||
}
|
||||
|
||||
LEAK_DETECTOR(Synth);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -65,8 +65,8 @@ public:
|
|||
|
||||
void setFileData(std::unique_ptr<StereoBuffer<float>> file)
|
||||
{
|
||||
fileData.reset(file.release());
|
||||
dataReady.store(true);
|
||||
fileData = std::move(file);
|
||||
dataReady.store(true, std::memory_order_seq_cst);
|
||||
}
|
||||
|
||||
bool isFree()
|
||||
|
|
@ -76,12 +76,19 @@ public:
|
|||
|
||||
void registerNoteOff(int delay, int channel, int noteNumber, uint8_t velocity [[maybe_unused]])
|
||||
{
|
||||
if (state == State::playing && triggerChannel == channel && triggerNumber == noteNumber)
|
||||
egEnvelope.startRelease(delay);
|
||||
if (state == State::playing && triggerChannel == channel && triggerNumber == noteNumber) {
|
||||
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]])
|
||||
{
|
||||
if (ccNumber == 64 && noteIsOff && ccValue < 63)
|
||||
egEnvelope.startRelease(delay);
|
||||
}
|
||||
|
||||
void registerPitchWheel(int delay, int channel, int pitch);
|
||||
|
|
@ -126,7 +133,7 @@ public:
|
|||
void fillWithData(StereoSpan<float> buffer)
|
||||
{
|
||||
const StereoSpan<const float> source([&]() -> StereoBuffer<float>& {
|
||||
if (dataReady)
|
||||
if (dataReady.load(std::memory_order_seq_cst) && fileData != nullptr)
|
||||
return *fileData;
|
||||
else
|
||||
return *region->preloadedData;
|
||||
|
|
@ -135,6 +142,7 @@ public:
|
|||
const auto actualBlockSize = min(buffer.size(), source.size() - sourcePosition);
|
||||
buffer.add(source.subspan(sourcePosition, actualBlockSize));
|
||||
sourcePosition += actualBlockSize;
|
||||
|
||||
if (sourcePosition == source.size())
|
||||
egEnvelope.startRelease(buffer.size());
|
||||
}
|
||||
|
|
@ -189,6 +197,13 @@ public:
|
|||
region = nullptr;
|
||||
state = State::idle;
|
||||
dataReady.store(false);
|
||||
noteIsOff = false;
|
||||
}
|
||||
|
||||
void garbageCollect()
|
||||
{
|
||||
if (state == State::idle && region == nullptr)
|
||||
fileData.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
@ -200,6 +215,7 @@ private:
|
|||
release
|
||||
};
|
||||
State state;
|
||||
bool noteIsOff { false };
|
||||
|
||||
TriggerType triggerType;
|
||||
int triggerNumber;
|
||||
|
|
@ -214,7 +230,7 @@ private:
|
|||
uint32_t sourcePosition;
|
||||
uint32_t initialDelay;
|
||||
|
||||
std::atomic<bool> dataReady;
|
||||
std::atomic<bool> dataReady { false };
|
||||
std::unique_ptr<StereoBuffer<float>> fileData;
|
||||
|
||||
Buffer<float> tempBuffer1;
|
||||
|
|
@ -227,6 +243,7 @@ private:
|
|||
|
||||
const CCValueArray& ccState;
|
||||
ADSREnvelope<float> egEnvelope;
|
||||
LEAK_DETECTOR(Voice);
|
||||
};
|
||||
|
||||
} // namespace sfz
|
||||
Loading…
Add table
Reference in a new issue