diff --git a/sources/ADSREnvelope.cpp b/sources/ADSREnvelope.cpp index d1a0bea9..b6603527 100644 --- a/sources/ADSREnvelope.cpp +++ b/sources/ADSREnvelope.cpp @@ -181,7 +181,8 @@ void ADSREnvelope::getBlock(absl::Span 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); diff --git a/sources/ADSREnvelope.h b/sources/ADSREnvelope.h index e2a929cf..c9727292 100644 --- a/sources/ADSREnvelope.h +++ b/sources/ADSREnvelope.h @@ -1,5 +1,6 @@ #pragma once #include +#include "Helpers.h" namespace sfz { @@ -31,6 +32,7 @@ private: Type sustain { 0 }; int releaseDelay { 0 }; bool shouldRelease { false }; + LEAK_DETECTOR(ADSREnvelope); }; } \ No newline at end of file diff --git a/sources/Buffer.h b/sources/Buffer.h index 2e16579f..c3c9c60f 100644 --- a/sources/Buffer.h +++ b/sources/Buffer.h @@ -1,11 +1,11 @@ #pragma once #include "Globals.h" +#include "Helpers.h" #include #include #include #include #include - template class Buffer { @@ -135,4 +135,5 @@ private: pointer paddedData{nullptr}; pointer normalEnd{nullptr}; pointer _alignedEnd{nullptr}; + LEAK_DETECTOR(Buffer); }; \ No newline at end of file diff --git a/sources/CCMap.h b/sources/CCMap.h index 211bcb80..69fba5d3 100644 --- a/sources/CCMap.h +++ b/sources/CCMap.h @@ -1,5 +1,6 @@ #pragma once #include +#include "Helpers.h" namespace sfz { @@ -40,5 +41,6 @@ public: private: const ValueType defaultValue; std::map container; + LEAK_DETECTOR(CCMap); }; } \ No newline at end of file diff --git a/sources/EGDescription.h b/sources/EGDescription.h index 7fde245a..e4e6781d 100644 --- a/sources/EGDescription.h +++ b/sources/EGDescription.h @@ -67,6 +67,7 @@ struct EGDescription { return ccSwitchedValue(ccValues, ccSustain, sustain) + normalizeCC(velocity)*vel2sustain; } + LEAK_DETECTOR(EGDescription); }; } //namespace sfz \ No newline at end of file diff --git a/sources/FilePool.cpp b/sources/FilePool.cpp index d6304380..a776488b 100644 --- a/sources/FilePool.cpp +++ b/sources/FilePool.cpp @@ -62,6 +62,7 @@ void sfz::FilePool::loadingThread() auto readBuffer = std::make_unique>(fileToLoad.numFrames * 2); sndFile.readf(readBuffer->data(), fileToLoad.numFrames); fileLoaded->readInterleaved(*readBuffer); + ASSERT(fileLoaded != nullptr); fileToLoad.voice->setFileData(std::move(fileLoaded)); } } \ No newline at end of file diff --git a/sources/FilePool.h b/sources/FilePool.h index 2fd826db..5e0cf1ea 100644 --- a/sources/FilePool.h +++ b/sources/FilePool.h @@ -55,5 +55,6 @@ private: Buffer tempReadBuffer { config::preloadSize * 2 }; // std::map>> preloadedData; absl::flat_hash_map>> preloadedData; + LEAK_DETECTOR(FilePool); }; } \ No newline at end of file diff --git a/sources/Helpers.h b/sources/Helpers.h index 83ed602f..4cf48a6d 100644 --- a/sources/Helpers.h +++ b/sources/Helpers.h @@ -1,34 +1,28 @@ #pragma once -#include -#include #include +#include +#include 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((h ^ *s) * static_cast(Fnv1aPrime))); } @@ -49,72 +43,139 @@ inline unsigned int hash(std::string_view s, unsigned int h = Fnv1aBasis) return h; } -template +template inline constexpr T min(T op1, T op2) { return std::min(op1, op2); } -template +template inline constexpr T min(T op1, T op2, T op3) { return std::min(op1, std::min(op2, op3)); } -template +template 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 #define DBG(ostream) std::cerr << ostream << '\n' #else -#define ASSERTFALSE +#define ASSERTFALSE #define ASSERT(expression) #define DBG(ostream) #endif -template +template inline constexpr Type db2pow(Type in) { return std::pow(static_cast(10.0), in * static_cast(0.1)); } -template +template inline constexpr Type pow2db(Type in) { return static_cast(10.0) * std::log10(in); } -template +template inline constexpr Type db2mag(Type in) { return std::pow(static_cast(10.0), in * static_cast(0.05)); } -template +template inline constexpr Type mag2db(Type in) { return static_cast(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 +template constexpr Type pi { 3.141592653589793238462643383279502884 }; -template -constexpr Type twoPi { 2*pi }; -template -constexpr Type piTwo { pi/2 }; \ No newline at end of file +template +constexpr Type twoPi { 2 * pi }; +template +constexpr Type piTwo { pi / 2 }; + +#include +template +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 count { 0 }; + }; + static inline ObjectCounter objectCounter; +}; + +#ifndef NDEBUG +#define LEAK_DETECTOR(Class) \ + friend class LeakDetector; \ + static const char* getClassName() noexcept { return #Class; } \ + LeakDetector leakDetector; +#else +#define LEAK_DETECTOR(Class) +#endif \ No newline at end of file diff --git a/sources/LinearEnvelope.h b/sources/LinearEnvelope.h index 089fadfd..675b9e79 100644 --- a/sources/LinearEnvelope.h +++ b/sources/LinearEnvelope.h @@ -1,5 +1,6 @@ #pragma once #include "Globals.h" +#include "Helpers.h" #include #include #include @@ -25,6 +26,7 @@ private: std::vector> events; int maxCapacity { config::defaultSamplesPerBlock }; Type currentValue { 0.0 }; + LEAK_DETECTOR(LinearEnvelope); }; } \ No newline at end of file diff --git a/sources/Main.cpp b/sources/Main.cpp index 4072ea45..5ca3fa42 100644 --- a/sources/Main.cpp +++ b/sources/Main.cpp @@ -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; } \ No newline at end of file diff --git a/sources/Opcode.h b/sources/Opcode.h index 492b3f8f..e9070810 100644 --- a/sources/Opcode.h +++ b/sources/Opcode.h @@ -19,6 +19,7 @@ struct Opcode std::string_view value{}; // This is to handle the integer parameter of some opcodes std::optional parameter; + LEAK_DETECTOR(Opcode); }; template diff --git a/sources/Region.h b/sources/Region.h index 6f1a4c9f..a4aaaf78 100644 --- a/sources/Region.h +++ b/sources/Region.h @@ -153,6 +153,7 @@ private: std::uniform_real_distribution gainDistribution { -sfz::Default::ampRandom, sfz::Default::ampRandom }; std::uniform_real_distribution delayDistribution { 0, sfz::Default::delayRandom }; std::uniform_int_distribution offsetDistribution { 0, sfz::Default::offsetRandom }; + LEAK_DETECTOR(Region); }; } // namespace sfz \ No newline at end of file diff --git a/sources/StereoBuffer.h b/sources/StereoBuffer.h index eae984a6..cce4e520 100644 --- a/sources/StereoBuffer.h +++ b/sources/StereoBuffer.h @@ -2,7 +2,6 @@ #include "Buffer.h" #include "Globals.h" #include "Helpers.h" - #include "SIMDHelpers.h" #include #include @@ -160,4 +159,5 @@ private: int numFrames { 0 }; Buffer leftBuffer {}; Buffer rightBuffer {}; + LEAK_DETECTOR(StereoBuffer); }; diff --git a/sources/StereoSpan.h b/sources/StereoSpan.h index 40197339..b3fa3550 100644 --- a/sources/StereoSpan.h +++ b/sources/StereoSpan.h @@ -141,4 +141,5 @@ private: size_t numFrames { 0 }; absl::Span leftBuffer; absl::Span rightBuffer; + LEAK_DETECTOR(StereoSpan); }; \ No newline at end of file diff --git a/sources/Synth.h b/sources/Synth.h index 774ad652..b4d5cd68 100644 --- a/sources/Synth.h +++ b/sources/Synth.h @@ -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 #include #include #include +#include #include +#include +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(ccState)); } + + ~Synth() + { + threadsShouldQuit = true; + garbageCollectionThread.join(); + } + bool loadSfzFile(const std::filesystem::path& file) final; int getNumRegions() const noexcept { return static_cast(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 buffer) { buffer.fill(0.0f); - + StereoSpan 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 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); }; } \ No newline at end of file diff --git a/sources/Voice.h b/sources/Voice.h index 00ccc9f6..2a658072 100644 --- a/sources/Voice.h +++ b/sources/Voice.h @@ -65,8 +65,8 @@ public: void setFileData(std::unique_ptr> 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 buffer) { const StereoSpan source([&]() -> StereoBuffer& { - 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 dataReady; + std::atomic dataReady { false }; std::unique_ptr> fileData; Buffer tempBuffer1; @@ -227,6 +243,7 @@ private: const CCValueArray& ccState; ADSREnvelope egEnvelope; + LEAK_DETECTOR(Voice); }; } // namespace sfz \ No newline at end of file