Skip to content

Commit

Permalink
Memory Reuse for allocating oscillators: String (surge-synthesizer#5264)
Browse files Browse the repository at this point in the history
This commit gives an API which allows us to reuse rather
than reallocate the memory buffers that objects need
at play time. Right now it only deals with the string
oscillator and still does 'extra' allocation on the audio
thread, but has a reasonable pre-allocate and re-use
strategy. In the future the overflow allocs could be
off thread but for now it's a simple save and reuse
approach.

Thanks to Luna for a review, and also glad I added
that test!

Closes surge-synthesizer#4840
  • Loading branch information
baconpaul authored Oct 17, 2021
1 parent 173cd40 commit 749fe08
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 6 deletions.
99 changes: 99 additions & 0 deletions src/common/MemoryPool.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
** Surge Synthesizer is Free and Open Source Software
**
** Surge is made available under the Gnu General Public License, v3.0
** https://www.gnu.org/licenses/gpl-3.0.en.html
**
** Copyright 2004-2021 by various individuals as described by the Git transaction log
**
** All source at: https://github.com/surge-synthesizer/surge.git
**
** Surge was a commercial product from 2004-2018, with Copyright and ownership
** in that period held by Claes Johanson at Vember Audio. Claes made Surge
** open source in September 2018.
*/

#ifndef SURGE_MEMORYPOOL_H
#define SURGE_MEMORYPOOL_H

#include <unordered_set>
#include <iostream>

namespace Surge
{
namespace Memory
{
// pre-alloc must be at least one
template <typename T, size_t preAlloc, size_t growBy, size_t capacity = 16384> struct MemoryPool
{
MemoryPool()
{
while (position < preAlloc)
refreshPool();
}
~MemoryPool()
{
std::cout << "Cleaning Up MemProol with " << position << std::endl;
for (size_t i = 0; i < position; ++i)
delete pool[i];
}
T *getItem()
{
if (position == 0)
{
refreshPool();
}
auto q = pool[position - 1];
pool[position - 1] = nullptr; // just to flag bugs
position--;
return q;
}
void returnItem(T *t)
{
pool[position] = t;
position++;
}
void refreshPool()
{
// In an ideal world, this grow would be off thread.
// For XT 1 leave it here and have a reasonable prealloc
assert(position < (growBy + capacity));
for (size_t i = 0; i < growBy; ++i)
{
pool[position] = new T();
position++;
}
}

void setupPoolToSize(size_t upTo)
{
while (position < upTo)
{
pool[position] = new T();
position++;
}
}

void returnToPreAllocSize()
{
while (position > preAlloc)
{
delete pool[position - 1];
pool[position - 1] = nullptr;
position--;
}
}

std::array<T *, capacity> pool;

/*
* Position is the location of the next *free* slot. That is
* the pointer should be returned to position, and retrieved from
* position -1. position == 0 is a sentinel to rebuild.
*/
size_t position{0};
};
} // namespace Memory
} // namespace Surge

#endif // SURGE_MEMORYPOOL_H
73 changes: 73 additions & 0 deletions src/common/SurgeMemoryPools.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
** Surge Synthesizer is Free and Open Source Software
**
** Surge is made available under the Gnu General Public License, v3.0
** https://www.gnu.org/licenses/gpl-3.0.en.html
**
** Copyright 2004-2021 by various individuals as described by the Git transaction log
**
** All source at: https://github.com/surge-synthesizer/surge.git
**
** Surge was a commercial product from 2004-2018, with Copyright and ownership
** in that period held by Claes Johanson at Vember Audio. Claes made Surge
** open source in September 2018.
*/

#ifndef SURGE_SURGEMEMORYPOOLS_H
#define SURGE_SURGEMEMORYPOOLS_H

#include "SurgeStorage.h"
#include "MemoryPool.h"
#include "SSESincDelayLine.h"

namespace Surge
{
namespace Memory
{
struct SurgeMemoryPools
{
/*
* The largest number of oscillator instances of a particlar
* type are scenes * oscs * max voices, but add some pad
*/
static constexpr int maxosc = n_scenes * n_oscs * (MAX_VOICES + 8);

/*
* The string needs 2 delay lines per oscillator
*/
MemoryPool<SSESincDelayLine<16384>, 8, 4, 2 * maxosc + 100> stringDelayLines;
void resetAllPools(SurgeStorage *storage) { resetOscillatorPools(storage); }
void resetOscillatorPools(SurgeStorage *storage)
{
bool hasString{false}, hasTwist{false};
int nString{0};
for (int s = 0; s < n_scenes; ++s)
{
for (int os = 0; os < n_oscs; ++os)
{
auto ot = storage->getPatch().scene[s].osc[os].type.val.i;

if (ot == ot_string)
{
hasString = true;
nString++;
}
hasTwist |= (ot == ot_twist);
}
}

if (hasString)
{
int maxUsed = nString * 2 * storage->getPatch().polylimit.val.i;
stringDelayLines.setupPoolToSize((int)(maxUsed * 0.5));
}
else
{
stringDelayLines.returnToPreAllocSize();
}
}
};

} // namespace Memory
} // namespace Surge
#endif // SURGE_SURGEMEMORYPOOLS_H
3 changes: 3 additions & 0 deletions src/common/SurgeStorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
#include "libMTSClient.h"
#include "FxPresetAndClipboardManager.h"
#include "ModulatorPresetManager.h"
#include "SurgeMemoryPools.h"

// FIXME probably remove this when we remove the hardcoded hack below
#include "MSEGModulationHelper.h"
Expand Down Expand Up @@ -582,6 +583,8 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0)

modulatorPreset = std::make_unique<Surge::Storage::ModulatorPreset>();
modulatorPreset->forcePresetRescan();

memoryPools = std::make_unique<Surge::Memory::SurgeMemoryPools>();
}

void SurgeStorage::createUserDirectory()
Expand Down
6 changes: 6 additions & 0 deletions src/common/SurgeStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,10 @@ namespace Storage
struct FxUserPreset;
struct ModulatorPreset;
} // namespace Storage
namespace Memory
{
struct SurgeMemoryPools;
}
} // namespace Surge

class alignas(16) SurgeStorage
Expand Down Expand Up @@ -1302,6 +1306,8 @@ class alignas(16) SurgeStorage
// whether to skip loading, desired while exporting manifests. Only used by LV2 currently.
static bool skipLoadWtAndPatch;

std::unique_ptr<Surge::Memory::SurgeMemoryPools> memoryPools;

/*
* An RNG which is decoupled from the non-Surge global state and is threadsafe.
* This RNG has the semantic that it is seeded when the first Surge in your session
Expand Down
8 changes: 8 additions & 0 deletions src/common/SurgeSynthesizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <thread>
#include <set>
#include "libMTSClient.h"
#include "SurgeMemoryPools.h"

using namespace std;

Expand Down Expand Up @@ -2328,6 +2329,7 @@ bool SurgeSynthesizer::loadFx(bool initp, bool force_reload_all)

bool SurgeSynthesizer::loadOscalgos()
{
bool algosChanged{false};
for (int s = 0; s < n_scenes; s++)
{
for (int i = 0; i < n_oscs; i++)
Expand All @@ -2336,6 +2338,7 @@ bool SurgeSynthesizer::loadOscalgos()

if (storage.getPatch().scene[s].osc[i].queue_type > -1)
{
algosChanged = true;
// clear assigned modulation if we change osc type, see issue #2224
if (storage.getPatch().scene[s].osc[i].queue_type !=
storage.getPatch().scene[s].osc[i].type.val.i)
Expand Down Expand Up @@ -2411,6 +2414,11 @@ bool SurgeSynthesizer::loadOscalgos()
}
}
}

if (algosChanged)
{
storage.memoryPools->resetOscillatorPools(&storage);
}
return true;
}

Expand Down
3 changes: 3 additions & 0 deletions src/common/SurgeSynthesizerIO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <algorithm>
#include <fstream>
#include <iterator>
#include "SurgeMemoryPools.h"

using namespace std;

Expand Down Expand Up @@ -418,6 +419,8 @@ void SurgeSynthesizer::loadRaw(const void *data, int size, bool preset)
storage.getPatch().scene[sc].f2_cutoff_is_offset.get_value_f01());
}

storage.memoryPools->resetAllPools(&storage);

halt_engine = false;
patch_loaded = true;
refresh_editor = true;
Expand Down
33 changes: 32 additions & 1 deletion src/common/dsp/oscillators/StringOscillator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
*/

#include "StringOscillator.h"
#include "SurgeMemoryPools.h"

int stringosc_excitations_count() { return 15; }

Expand Down Expand Up @@ -80,10 +81,40 @@ std::string stringosc_excitation_name(int i)
return "Unknown";
}

StringOscillator::~StringOscillator() = default;
StringOscillator::~StringOscillator()
{
if (storage && !ownDelayLines)
{
storage->memoryPools->stringDelayLines.returnItem(delayLine[0]);
storage->memoryPools->stringDelayLines.returnItem(delayLine[1]);
}
else
{
if (delayLine[0])
delete delayLine[0];
if (delayLine[1])
delete delayLine[1];
}
};

void StringOscillator::init(float pitch, bool is_display, bool nzi)
{
// fixme - alloc in is_display but for now just deal with the race
// delayLine[0] = std::make_unique<SSESincDelayLine<16384>>();
// delayLine[1] = std::make_unique<SSESincDelayLine<16384>>();
if (is_display)
{
ownDelayLines = true;
delayLine[0] = new SSESincDelayLine<16384>();
delayLine[1] = new SSESincDelayLine<16384>();
}
else
{
ownDelayLines = false;
delayLine[0] = storage->memoryPools->stringDelayLines.getItem();
delayLine[1] = storage->memoryPools->stringDelayLines.getItem();
}

memset((void *)dustBuffer, 0, 2 * (BLOCK_SIZE_OS) * sizeof(float));

id_exciterlvl = oscdata->p[str_exciter_level].param_id_in_scene;
Expand Down
5 changes: 2 additions & 3 deletions src/common/dsp/oscillators/StringOscillator.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ class StringOscillator : public Oscillator
StringOscillator(SurgeStorage *s, OscillatorStorage *o, pdata *p)
: Oscillator(s, o, p), lp(s), hp(s), noiseLp(s)
{
delayLine[0] = std::make_unique<SSESincDelayLine<16384>>();
delayLine[1] = std::make_unique<SSESincDelayLine<16384>>();
}

~StringOscillator();
Expand All @@ -82,7 +80,8 @@ class StringOscillator : public Oscillator

lag<float, true> examp, tap[2], t2level, feedback[2], tone, fmdepth;

std::array<std::unique_ptr<SSESincDelayLine<16384>>, 2> delayLine;
std::array<SSESincDelayLine<16384> *, 2> delayLine{nullptr, nullptr};
bool ownDelayLines{false};
float priorSample[2] = {0, 0};
Surge::Oscillator::DriftLFO driftLFO[2];
Surge::Oscillator::CharacterFilter<float> charFilt;
Expand Down
8 changes: 6 additions & 2 deletions src/common/dsp/utilities/SSESincDelayLine.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct SSESincDelayLine
float buffer alignas(16)[COMB_SIZE + FIRipol_N];
int wp = 0;

SSESincDelayLine() { memset((void *)buffer, 0, (COMB_SIZE + FIRipol_N) * sizeof(float)); }
SSESincDelayLine() { clear(); }

inline void write(float f)
{
Expand Down Expand Up @@ -80,7 +80,11 @@ struct SSESincDelayLine
return buffer[RP] * (1 - frac) + buffer[RPP] * frac;
}

inline void clear() { memset((void *)buffer, 0, (COMB_SIZE + FIRipol_N) * sizeof(float)); }
inline void clear()
{
memset((void *)buffer, 0, (COMB_SIZE + FIRipol_N) * sizeof(float));
wp = 0;
}
};

#endif // SURGE_SSESINCDELAYLINE_H
Loading

0 comments on commit 749fe08

Please sign in to comment.