commit b436f3d7b14bd2d539f3f8c7eddded1aec782e9f
parent d131a39e27e2f623e42b068250ebc86d7e90e6e7
Author: Olivier Jolly <olivier@pcedev.com>
Date: Tue, 17 Nov 2015 13:43:44 +0100
Changed Allocator::alloc and Allocator::valloc api to throw exception instead of returning a nullptr in case of memory lack
Adapted the higher level caller methods to deal with this exception (by discarding new notes/legato/effect ... instead of behaving unexpectedly)
Diffstat:
10 files changed, 335 insertions(+), 128 deletions(-)
diff --git a/src/Containers/NotePool.cpp b/src/Containers/NotePool.cpp
@@ -5,6 +5,7 @@
#include "../Synth/SynthNote.h"
#include <cstring>
#include <cassert>
+#include <iostream>
NotePool::NotePool(void)
:needs_cleaning(0)
@@ -97,8 +98,12 @@ void NotePool::upgradeToLegato(void)
void NotePool::insertLegatoNote(uint8_t note, uint8_t sendto, SynthDescriptor desc)
{
assert(desc.note);
- desc.note = desc.note->cloneLegato();
- insertNote(note, sendto, desc, true);
+ try {
+ desc.note = desc.note->cloneLegato();
+ insertNote(note, sendto, desc, true);
+ } catch (std::bad_alloc &ba) {
+ std::cerr << "failed to insert legato note: " << ba.what() << std::endl;
+ }
};
//There should only be one pair of notes which are still playing
@@ -107,7 +112,11 @@ void NotePool::applyLegato(LegatoParams &par)
for(auto &desc:activeDesc()) {
desc.note = par.midinote;
for(auto &synth:activeNotes(desc))
- synth.note->legatonote(par);
+ try {
+ synth.note->legatonote(par);
+ } catch (std::bad_alloc& ba) {
+ std::cerr << "failed to create legato note: " << ba.what() << std::endl;
+ }
}
};
diff --git a/src/Effects/DynamicFilter.cpp b/src/Effects/DynamicFilter.cpp
@@ -21,6 +21,7 @@
*/
#include <cmath>
+#include <iostream>
#include "DynamicFilter.h"
#include "../DSP/Filter.h"
#include "../Misc/Allocator.h"
@@ -133,8 +134,18 @@ void DynamicFilter::reinitfilter(void)
{
memory.dealloc(filterl);
memory.dealloc(filterr);
- filterl = Filter::generate(memory, filterpars, samplerate, buffersize);
- filterr = Filter::generate(memory, filterpars, samplerate, buffersize);
+
+ try {
+ filterl = Filter::generate(memory, filterpars, samplerate, buffersize);
+ } catch(std::bad_alloc& ba) {
+ std::cerr << "failed to generate left filter for dynamic filter: " << ba.what() << std::endl;
+ }
+
+ try {
+ filterr = Filter::generate(memory, filterpars, samplerate, buffersize);
+ } catch(std::bad_alloc& ba) {
+ std::cerr << "failed to generate right filter for dynamic filter: " << ba.what() << std::endl;
+ }
}
void DynamicFilter::setpreset(unsigned char npreset)
diff --git a/src/Effects/EffectMgr.cpp b/src/Effects/EffectMgr.cpp
@@ -22,6 +22,7 @@
#include <rtosc/ports.h>
#include <rtosc/port-sugar.h>
+#include <iostream>
#include "EffectMgr.h"
@@ -178,35 +179,40 @@ void EffectMgr::changeeffectrt(int _nefx, bool avoidSmash)
memory.dealloc(efx);
EffectParams pars(memory, insertion, efxoutl, efxoutr, 0,
synth.samplerate, synth.buffersize);
- switch(nefx) {
- case 1:
- efx = memory.alloc<Reverb>(pars);
- break;
- case 2:
- efx = memory.alloc<Echo>(pars);
- break;
- case 3:
- efx = memory.alloc<Chorus>(pars);
- break;
- case 4:
- efx = memory.alloc<Phaser>(pars);
- break;
- case 5:
- efx = memory.alloc<Alienwah>(pars);
- break;
- case 6:
- efx = memory.alloc<Distorsion>(pars);
- break;
- case 7:
- efx = memory.alloc<EQ>(pars);
- break;
- case 8:
- efx = memory.alloc<DynamicFilter>(pars);
- break;
- //put more effect here
- default:
- efx = NULL;
- break; //no effect (thru)
+ try {
+ switch (nefx) {
+ case 1:
+ efx = memory.alloc<Reverb>(pars);
+ break;
+ case 2:
+ efx = memory.alloc<Echo>(pars);
+ break;
+ case 3:
+ efx = memory.alloc<Chorus>(pars);
+ break;
+ case 4:
+ efx = memory.alloc<Phaser>(pars);
+ break;
+ case 5:
+ efx = memory.alloc<Alienwah>(pars);
+ break;
+ case 6:
+ efx = memory.alloc<Distorsion>(pars);
+ break;
+ case 7:
+ efx = memory.alloc<EQ>(pars);
+ break;
+ case 8:
+ efx = memory.alloc<DynamicFilter>(pars);
+ break;
+ //put more effect here
+ default:
+ efx = NULL;
+ break; //no effect (thru)
+ }
+ } catch (std::bad_alloc &ba) {
+ std::cerr << "failed to change effect " << _nefx << ": " << ba.what() << std::endl;
+ return;
}
if(efx)
@@ -287,7 +293,11 @@ void EffectMgr::seteffectparrt(int npar, unsigned char value)
settings[npar] = value;
if(!efx)
return;
- efx->changepar(npar, value);
+ try {
+ efx->changepar(npar, value);
+ } catch (std::bad_alloc &ba) {
+ std::cerr << "failed to change effect parameter " << npar << " to " << value << ": " << ba.what() << std::endl;
+ }
}
//Change a parameter of the current effect
diff --git a/src/Misc/Allocator.h b/src/Misc/Allocator.h
@@ -1,6 +1,7 @@
#pragma once
#include <cstdlib>
#include <utility>
+#include <new>
//! Allocator Base class
//! subclasses must specify allocation and deallocation
@@ -14,21 +15,36 @@ class Allocator
virtual void *alloc_mem(size_t mem_size) = 0;
virtual void dealloc_mem(void *memory) = 0;
+ /**
+ * High level allocator method, which return a pointer to a class or struct
+ * allocated with the specialized subclass strategy
+ * @param ts argument(s) for the constructor of the type T
+ * @return a non null pointer to a new object of type T
+ * @throw std::bad_alloc is no memory could be allocated
+ */
template <typename T, typename... Ts>
T *alloc(Ts&&... ts)
{
void *data = alloc_mem(sizeof(T));
if(!data)
- return nullptr;
+ throw std::bad_alloc();
return new (data) T(std::forward<Ts>(ts)...);
}
+ /**
+ * High level allocator method, which return a pointer to an array of class or struct
+ * allocated with the specialized subclass strategy
+ * @param len the array length
+ * @param ts argument(s) for the constructor of the type T
+ * @return a non null pointer to an array of new object(s) of type T
+ * @throw std::bad_alloc is no memory could be allocated
+ */
template <typename T, typename... Ts>
T *valloc(size_t len, Ts&&... ts)
{
T *data = (T*)alloc_mem(len*sizeof(T));
if(!data)
- return nullptr;
+ throw std::bad_alloc();
for(unsigned i=0; i<len; ++i)
new ((void*)&data[i]) T(std::forward<Ts>(ts)...);
diff --git a/src/Misc/MiddleWare.cpp b/src/Misc/MiddleWare.cpp
@@ -4,6 +4,7 @@
#include <cstdio>
#include <cstdlib>
#include <fstream>
+#include <iostream>
#include <rtosc/undo-history.h>
#include <rtosc/thread-link.h>
@@ -494,12 +495,24 @@ public:
}
}
- Part *p = alloc.get();
+ Part *p;
+ try {
+ p = alloc.get();
+ } catch (std::bad_alloc &ba) {
+ std::cerr << "failed to load part: " << ba.what() << std::endl;
+ return;
+ }
#else
- Part *p = new Part(*master->memory, synth, master->time,
- config->cfg.GzipCompression,
- config->cfg.Interpolation,
- &master->microtonal, master->fft);
+ try {
+ Part *p = new Part(*master->memory, synth, master->time,
+ config->cfg.GzipCompression,
+ config->cfg.Interpolation,
+ &master->microtonal, master->fft);
+ } catch (std::bad_alloc &ba) {
+ std::cerr << "failed to load part: " << ba.what() << std::endl;
+ return;
+ }
+
if(p->loadXMLinstrument(filename))
fprintf(stderr, "Warning: failed to load part<%s>!\n", filename);
@@ -524,19 +537,24 @@ public:
{
if(npart == -1)
return;
- Part *p = new Part(*master->memory, synth,
- master->time,
- config->cfg.GzipCompression,
- config->cfg.Interpolation,
- &master->microtonal, master->fft);
- p->applyparameters();
- obj_store.extractPart(p, npart);
- kits.extractPart(p, npart);
- //Give it to the backend and wait for the old part to return for
- //deallocation
- parent->transmitMsg("/load-part", "ib", npart, sizeof(Part*), &p);
- GUI::raiseUi(ui, "/damage", "s", ("/part"+to_s(npart)+"/").c_str());
+ try {
+ Part *p = new Part(*master->memory, synth,
+ master->time,
+ config->cfg.GzipCompression,
+ config->cfg.Interpolation,
+ &master->microtonal, master->fft);
+ p->applyparameters();
+ obj_store.extractPart(p, npart);
+ kits.extractPart(p, npart);
+
+ //Give it to the backend and wait for the old part to return for
+ //deallocation
+ parent->transmitMsg("/load-part", "ib", npart, sizeof(Part *), &p);
+ GUI::raiseUi(ui, "/damage", "s", ("/part" + to_s(npart) + "/").c_str());
+ } catch (std::bad_alloc &ba) {
+ std::cerr << "failed to load part: " << ba.what() << std::endl;
+ }
}
//Well, you don't get much crazier than changing out all of your RT
diff --git a/src/Misc/Part.cpp b/src/Misc/Part.cpp
@@ -43,6 +43,7 @@
#include <rtosc/ports.h>
#include <rtosc/port-sugar.h>
+#include <iostream>
using rtosc::Ports;
using rtosc::RtData;
@@ -484,15 +485,19 @@ bool Part::NoteOn(unsigned char note,
portamento, note, false};
const int sendto = Pkitmode ? item.sendto() : 0;
- if(item.Padenabled)
- notePool.insertNote(note, sendto,
- {memory.alloc<ADnote>(kit[i].adpars, pars), 0, i});
- if(item.Psubenabled)
- notePool.insertNote(note, sendto,
- {memory.alloc<SUBnote>(kit[i].subpars, pars), 1, i});
- if(item.Ppadenabled)
- notePool.insertNote(note, sendto,
- {memory.alloc<PADnote>(kit[i].padpars, pars, interpolation), 2, i});
+ try {
+ if(item.Padenabled)
+ notePool.insertNote(note, sendto,
+ {memory.alloc<ADnote>(kit[i].adpars, pars), 0, i});
+ if(item.Psubenabled)
+ notePool.insertNote(note, sendto,
+ {memory.alloc<SUBnote>(kit[i].subpars, pars), 1, i});
+ if(item.Ppadenabled)
+ notePool.insertNote(note, sendto,
+ {memory.alloc<PADnote>(kit[i].padpars, pars, interpolation), 2, i});
+ } catch (std::bad_alloc & ba) {
+ std::cerr << "dropped new note: " << ba.what() << std::endl;
+ }
//Partial Kit Use
if(isNonKit() || (isSingleKit() && item.active()))
diff --git a/src/Synth/SUBnote.cpp b/src/Synth/SUBnote.cpp
@@ -24,6 +24,7 @@
#include <cstdlib>
#include <cstdio>
#include <cassert>
+#include <iostream>
#include "../globals.h"
#include "SUBnote.h"
#include "Envelope.h"
@@ -224,8 +225,12 @@ void SUBnote::legatonote(LegatoParams pars)
if(legato.update(pars))
return;
- setup(pars.frequency, pars.velocity, pars.portamento, pars.midinote,
- true);
+ try {
+ setup(pars.frequency, pars.velocity, pars.portamento, pars.midinote,
+ true);
+ } catch (std::bad_alloc &ba) {
+ std::cerr << "failed to set legato note parameter in SUBnote: " << ba.what() << std::endl;
+ }
}
SUBnote::~SUBnote()
diff --git a/src/Synth/SynthNote.cpp b/src/Synth/SynthNote.cpp
@@ -1,6 +1,8 @@
#include "SynthNote.h"
#include "../globals.h"
#include <cstring>
+#include <new>
+#include <iostream>
SynthNote::SynthNote(SynthParams &pars)
:memory(pars.memory),
@@ -61,72 +63,76 @@ void SynthNote::Legato::apply(SynthNote ¬e, float *outl, float *outr)
memset(outl, 0, synth.bufferbytes);
memset(outr, 0, synth.bufferbytes);
}
- switch(msg) {
- case LM_CatchUp: // Continue the catch-up...
- if(decounter == -10)
- decounter = fade.length;
- //Yea, could be done without the loop...
- for(int i = 0; i < synth.buffersize; ++i) {
- decounter--;
- if(decounter < 1) {
- // Catching-up done, we can finally set
- // the note to the actual parameters.
- decounter = -10;
- msg = LM_ToNorm;
- LegatoParams pars{param.freq, param.vel, param.portamento,
- param.midinote, false};
- note.legatonote(pars);
- break;
- }
- }
- break;
- case LM_FadeIn: // Fade-in
- if(decounter == -10)
- decounter = fade.length;
- silent = false;
- for(int i = 0; i < synth.buffersize; ++i) {
- decounter--;
- if(decounter < 1) {
- decounter = -10;
- msg = LM_Norm;
- break;
+ try {
+ switch (msg) {
+ case LM_CatchUp: // Continue the catch-up...
+ if (decounter == -10)
+ decounter = fade.length;
+ //Yea, could be done without the loop...
+ for (int i = 0; i < synth.buffersize; ++i) {
+ decounter--;
+ if (decounter < 1) {
+ // Catching-up done, we can finally set
+ // the note to the actual parameters.
+ decounter = -10;
+ msg = LM_ToNorm;
+ LegatoParams pars{param.freq, param.vel, param.portamento,
+ param.midinote, false};
+ note.legatonote(pars);
+ break;
+ }
}
- fade.m += fade.step;
- outl[i] *= fade.m;
- outr[i] *= fade.m;
- }
- break;
- case LM_FadeOut: // Fade-out, then set the catch-up
- if(decounter == -10)
- decounter = fade.length;
- for(int i = 0; i < synth.buffersize; ++i) {
- decounter--;
- if(decounter < 1) {
- for(int j = i; j < synth.buffersize; ++j) {
- outl[j] = 0.0f;
- outr[j] = 0.0f;
+ break;
+ case LM_FadeIn: // Fade-in
+ if (decounter == -10)
+ decounter = fade.length;
+ silent = false;
+ for (int i = 0; i < synth.buffersize; ++i) {
+ decounter--;
+ if (decounter < 1) {
+ decounter = -10;
+ msg = LM_Norm;
+ break;
}
- decounter = -10;
- silent = true;
- // Fading-out done, now set the catch-up :
+ fade.m += fade.step;
+ outl[i] *= fade.m;
+ outr[i] *= fade.m;
+ }
+ break;
+ case LM_FadeOut: // Fade-out, then set the catch-up
+ if (decounter == -10)
decounter = fade.length;
- msg = LM_CatchUp;
- //This freq should make this now silent note to catch-up/resync
- //with the heard note for the same length it stayed at the
- //previous freq during the fadeout.
- float catchupfreq = param.freq * (param.freq / lastfreq);
- LegatoParams pars{catchupfreq, param.vel, param.portamento,
- param.midinote, false};
- note.legatonote(pars);
- break;
+ for (int i = 0; i < synth.buffersize; ++i) {
+ decounter--;
+ if (decounter < 1) {
+ for (int j = i; j < synth.buffersize; ++j) {
+ outl[j] = 0.0f;
+ outr[j] = 0.0f;
+ }
+ decounter = -10;
+ silent = true;
+ // Fading-out done, now set the catch-up :
+ decounter = fade.length;
+ msg = LM_CatchUp;
+ //This freq should make this now silent note to catch-up/resync
+ //with the heard note for the same length it stayed at the
+ //previous freq during the fadeout.
+ float catchupfreq = param.freq * (param.freq / lastfreq);
+ LegatoParams pars{catchupfreq, param.vel, param.portamento,
+ param.midinote, false};
+ note.legatonote(pars);
+ break;
+ }
+ fade.m -= fade.step;
+ outl[i] *= fade.m;
+ outr[i] *= fade.m;
}
- fade.m -= fade.step;
- outl[i] *= fade.m;
- outr[i] *= fade.m;
- }
- break;
- default:
- break;
+ break;
+ default:
+ break;
+ }
+ } catch (std::bad_alloc &ba) {
+ std::cerr << "failed to apply legato: " << ba.what() << std::endl;
}
}
@@ -134,6 +140,10 @@ void SynthNote::setVelocity(float velocity_) {
legato.setSilent(true); //Let legato.update(...) returns 0.
LegatoParams pars{legato.getFreq(), velocity_,
legato.getPortamento(), legato.getMidinote(), true};
- legatonote(pars);
+ try {
+ legatonote(pars);
+ } catch (std::bad_alloc &ba) {
+ std::cerr << "failed to set velocity to legato note: " << ba.what() << std::endl;
+ }
legato.setDecounter(0); //avoid chopping sound due fade-in
}
diff --git a/src/Tests/CMakeLists.txt b/src/Tests/CMakeLists.txt
@@ -22,6 +22,8 @@ CXXTEST_ADD_TEST(AllocatorTest AllocatorTest.cpp
${CMAKE_CURRENT_SOURCE_DIR}/AllocatorTest.h)
CXXTEST_ADD_TEST(KitTest KitTest.cpp
${CMAKE_CURRENT_SOURCE_DIR}/KitTest.h)
+CXXTEST_ADD_TEST(MemoryStressTest MemoryStressTest.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/MemoryStressTest.h)
#Extra libraries added to make test and full compilation use the same library
#links for quirky compilers
@@ -51,6 +53,7 @@ target_link_libraries(UnisonTest ${test_lib})
#target_link_libraries(RtAllocTest ${test_lib})
target_link_libraries(AllocatorTest ${test_lib})
target_link_libraries(KitTest ${test_lib})
+target_link_libraries(MemoryStressTest ${test_lib})
#Testbed app
add_executable(ins-test InstrumentStats.cpp)
diff --git a/src/Tests/MemoryStressTest.h b/src/Tests/MemoryStressTest.h
@@ -0,0 +1,120 @@
+/*
+ ZynAddSubFX - a software synthesizer
+
+ AdNoteTest.h - CxxTest for Synth/ADnote
+ Copyright (C) 2009-2011 Mark McCurry
+ Copyright (C) 2009 Harald Hvaal
+ Authors: Mark McCurry, Harald Hvaal
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of version 2 of the GNU General Public License
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License (version 2 or later) for more details.
+
+ You should have received a copy of the GNU General Public License (version 2)
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*/
+
+
+#include <cxxtest/TestSuite.h>
+#include <iostream>
+#include <fstream>
+#include <ctime>
+#include <string>
+#include "../Misc/Master.h"
+#include "../Misc/Util.h"
+#include "../Misc/Allocator.h"
+#include "../Synth/ADnote.h"
+#include "../Params/Presets.h"
+#include "../DSP/FFTwrapper.h"
+#include "../globals.h"
+SYNTH_T *synth;
+
+class AdNoteTest:public CxxTest::TestSuite
+{
+ public:
+ ADnote *note;
+ AbsTime *time;
+ FFTwrapper *fft;
+ ADnoteParameters *defaultPreset;
+ Controller *controller;
+ Alloc memory;
+
+ void setUp() {
+ //First the sensible settings and variables that have to be set:
+ synth = new SYNTH_T;
+ synth->buffersize = 256;
+ //synth->alias();
+ time = new AbsTime(*synth);
+
+ fft = new FFTwrapper(synth->oscilsize);
+ //prepare the default settings
+ defaultPreset = new ADnoteParameters(*synth, fft);
+
+ //Assert defaults
+ TS_ASSERT(!defaultPreset->VoicePar[1].Enabled);
+
+ std::string instrument_filename = std::string(SOURCE_DIR) + "/guitar-adnote.xmz";
+ std::cout << instrument_filename << std::endl;
+
+ XMLwrapper *wrap = new XMLwrapper();
+ wrap->loadXMLfile(instrument_filename);
+ TS_ASSERT(wrap->enterbranch("MASTER"));
+ TS_ASSERT(wrap->enterbranch("PART", 0));
+ TS_ASSERT(wrap->enterbranch("INSTRUMENT"));
+ TS_ASSERT(wrap->enterbranch("INSTRUMENT_KIT"));
+ TS_ASSERT(wrap->enterbranch("INSTRUMENT_KIT_ITEM", 0));
+ TS_ASSERT(wrap->enterbranch("ADD_SYNTH_PARAMETERS"));
+ defaultPreset->getfromXML(wrap);
+
+ //verify xml was loaded
+ TS_ASSERT(defaultPreset->VoicePar[1].Enabled);
+
+ controller = new Controller(*synth);
+
+ delete wrap;
+ }
+
+ void tearDown() {
+ delete note;
+ delete controller;
+ delete defaultPreset;
+ delete fft;
+ FFT_cleanup();
+ delete synth;
+ }
+
+ void testManySimultaneousNotes() {
+
+ unsigned char testnote = 42;
+ float freq = 440.0f * powf(2.0f, (testnote - 69.0f) / 12.0f);
+ SynthParams pars{memory, *controller, *synth, *time, freq, 120, 0, testnote, false};
+
+ std::vector<ADnote*> notes;
+
+ for ( size_t note_idx = 0; note_idx < 1000; ++ note_idx ) {
+ try {
+ notes.push_back(new ADnote(defaultPreset, pars));
+ } catch (std::exception & e) {
+#if defined(DEBUG)
+ std::cerr << "couldn't push note #" << note_idx << std::endl;
+#endif
+ }
+ }
+
+ // If we made it that far, we managed to create many ADnotewithout sigsev
+
+ for (auto note_ptr: notes) {
+ delete note_ptr;
+ }
+
+
+ }
+
+};