zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

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:
Msrc/Containers/NotePool.cpp | 15++++++++++++---
Msrc/Effects/DynamicFilter.cpp | 15+++++++++++++--
Msrc/Effects/EffectMgr.cpp | 70++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/Misc/Allocator.h | 20++++++++++++++++++--
Msrc/Misc/MiddleWare.cpp | 52+++++++++++++++++++++++++++++++++++-----------------
Msrc/Misc/Part.cpp | 23++++++++++++++---------
Msrc/Synth/SUBnote.cpp | 9+++++++--
Msrc/Synth/SynthNote.cpp | 136++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/Tests/CMakeLists.txt | 3+++
Asrc/Tests/MemoryStressTest.h | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 &note, 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; + } + + + } + +};