zynaddsubfx

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

commit 15e4323e623c4bcd3363776a630bdcf59e50977c
parent 8d88d4225b50822b03dd44385f6eaf0da63e9a37
Author: Johannes Lorenz <johannes89@ist-einmalig.de>
Date:   Tue, 12 Sep 2017 21:03:43 +0200

Parallelize PADSynth

Core fixes:
* Fix the last PADSynth sample to not be deleted
* Remove unused PADSynth's unused fft member variable
Features:
* Parallelize PADSynth's sample generation

Diffstat:
Msrc/Misc/MiddleWare.cpp | 24++++++++++++++----------
Msrc/Params/PADnoteParameters.cpp | 184+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/Params/PADnoteParameters.h | 46+++++++++++++++++++++++++++++++++-------------
Msrc/Tests/PadNoteTest.h | 6+++---
4 files changed, 154 insertions(+), 106 deletions(-)

diff --git a/src/Misc/MiddleWare.cpp b/src/Misc/MiddleWare.cpp @@ -18,6 +18,7 @@ #include <iostream> #include <dirent.h> #include <sys/stat.h> +#include <mutex> #include <rtosc/undo-history.h> #include <rtosc/thread-link.h> @@ -59,6 +60,7 @@ namespace zyn { using std::string; +using std::mutex; int Pexitprogram = 0; /****************************************************************************** @@ -207,17 +209,19 @@ void preparePadSynth(string path, PADnoteParameters *p, rtosc::RtData &d) assert(!path.empty()); path += "sample"; - unsigned max = 0; - p->sampleGenerator([&max,&path,&d] - (unsigned N, PADnoteParameters::Sample &s) - { - max = max<N ? N : max; - //printf("sending info to '%s'\n", (path+to_s(N)).c_str()); - d.chain((path+to_s(N)).c_str(), "ifb", - s.size, s.basefreq, sizeof(float*), &s.smp); - }, []{return false;}); + mutex rtdata_mutex; + unsigned num = p->sampleGenerator([&rtdata_mutex,&path,&d] + (unsigned N, PADnoteParameters::Sample &s) + { + //printf("sending info to '%s'\n", + // (path+to_s(N)).c_str()); + rtdata_mutex.lock(); + d.chain((path+to_s(N)).c_str(), "ifb", + s.size, s.basefreq, sizeof(float*), &s.smp); + rtdata_mutex.unlock(); + }, []{return false;}); //clear out unused samples - for(unsigned i = max+1; i < PAD_MAX_SAMPLES; ++i) { + for(unsigned i = num; i < PAD_MAX_SAMPLES; ++i) { d.chain((path+to_s(i)).c_str(), "ifb", 0, 440.0f, sizeof(float*), NULL); } diff --git a/src/Params/PADnoteParameters.cpp b/src/Params/PADnoteParameters.cpp @@ -10,6 +10,7 @@ as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ +#include <limits> #include <cmath> #include "PADnoteParameters.h" #include "FilterParams.h" @@ -20,6 +21,7 @@ #include "../Misc/WavFile.h" #include "../Misc/Time.h" #include <cstdio> +#include <thread> #include <rtosc/ports.h> #include <rtosc/port-sugar.h> @@ -271,8 +273,6 @@ PADnoteParameters::PADnoteParameters(const SYNTH_T &synth_, FFTwrapper *fft_, { setpresettype("Ppadsynth"); - fft = fft_; - resonance = new Resonance(); oscilgen = new OscilGen(synth, fft_, resonance); oscilgen->ADvsPAD = true; @@ -563,7 +563,7 @@ float PADnoteParameters::setPbandwidth(int Pbandwidth) /* * Get the harmonic(overtone) position */ -float PADnoteParameters::getNhr(int n) +float PADnoteParameters::getNhr(int n) const { float result = 1.0f; const float par1 = powf(10.0f, -(1.0f - Phrpos.par1 / 255.0f) * 3.0f); @@ -663,9 +663,9 @@ static float Pbwscale_translate(char Pbwscale) void PADnoteParameters::generatespectrum_bandwidthMode(float *spectrum, int size, float basefreq, - float *profile, + const float *profile, int profilesize, - float bwadjust) + float bwadjust) const { float harmonics[synth.oscilsize]; memset(spectrum, 0, sizeof(float) * size); @@ -679,7 +679,7 @@ void PADnoteParameters::generatespectrum_bandwidthMode(float *spectrum, //Constants across harmonics const float power = Pbwscale_translate(Pbwscale); - const float bandwidthcents = setPbandwidth(Pbandwidth); + const float bandwidthcents = const_cast<PADnoteParameters*>(this)->setPbandwidth(Pbandwidth); for(int nh = 1; nh < synth.oscilsize / 2; ++nh) { //for each harmonic const float realfreq = getNhr(nh) * basefreq; @@ -740,7 +740,7 @@ void PADnoteParameters::generatespectrum_bandwidthMode(float *spectrum, */ void PADnoteParameters::generatespectrum_otherModes(float *spectrum, int size, - float basefreq) + float basefreq) const { float harmonics[synth.oscilsize]; memset(spectrum, 0, sizeof(float) * size); @@ -797,21 +797,20 @@ void PADnoteParameters::applyparameters() applyparameters([]{return false;}); } -void PADnoteParameters::applyparameters(std::function<bool()> do_abort) +void PADnoteParameters::applyparameters(std::function<bool()> do_abort, + unsigned max_threads) { if(do_abort()) return; - unsigned max = 0; - sampleGenerator([&max,this] - (unsigned N, PADnoteParameters::Sample &smp) { - delete[] sample[N].smp; - sample[N] = smp; - max = max < N ? N : max; - }, - do_abort); + unsigned num = sampleGenerator([this] + (unsigned N, PADnoteParameters::Sample &smp) { + delete[] sample[N].smp; + sample[N] = smp; + }, + do_abort, max_threads); //Delete remaining unused samples - for(unsigned i = max; i < PAD_MAX_SAMPLES; ++i) + for(unsigned i = num; i < PAD_MAX_SAMPLES; ++i) deletesample(i); } @@ -821,13 +820,17 @@ void PADnoteParameters::applyparameters(std::function<bool()> do_abort) // - Pquality.oct // - Pquality.smpoct // - spectrum at various frequencies (oodles of data) -void PADnoteParameters::sampleGenerator(PADnoteParameters::callback cb, - std::function<bool()> do_abort) +int PADnoteParameters::sampleGenerator(PADnoteParameters::callback cb, + std::function<bool()> do_abort, + unsigned max_threads) { + if(!max_threads) + max_threads = std::numeric_limits<unsigned>::max(); + const int samplesize = (((int) 1) << (Pquality.samplesize + 14)); const int spectrumsize = samplesize / 2; - float *spectrum = new float[spectrumsize]; const int profilesize = 512; + float profile[profilesize]; @@ -852,71 +855,92 @@ void PADnoteParameters::sampleGenerator(PADnoteParameters::callback cb, if(samplemax > PAD_MAX_SAMPLES) samplemax = PAD_MAX_SAMPLES; - //prepare a BIG FFT - FFTwrapper *fft = new FFTwrapper(samplesize); - fft_t *fftfreqs = new fft_t[samplesize / 2]; - //this is used to compute frequency relation to the base frequency float adj[samplemax]; for(int nsample = 0; nsample < samplemax; ++nsample) adj[nsample] = (Pquality.oct + 1.0f) * (float)nsample / samplemax; - for(int nsample = 0; nsample < samplemax; ++nsample) { - if(do_abort()) - goto exit; - const float basefreqadjust = - powf(2.0f, adj[nsample] - adj[samplemax - 1] * 0.5f); - - if(Pmode == 0) - generatespectrum_bandwidthMode(spectrum, - spectrumsize, - basefreq * basefreqadjust, - profile, - profilesize, - bwadjust); - else - generatespectrum_otherModes(spectrum, spectrumsize, - basefreq * basefreqadjust); - - //the last samples contains the first samples - //(used for linear/cubic interpolation) - const int extra_samples = 5; - PADnoteParameters::Sample newsample; - newsample.smp = new float[samplesize + extra_samples]; - - newsample.smp[0] = 0.0f; - for(int i = 1; i < spectrumsize; ++i) //randomize the phases - fftfreqs[i] = FFTpolar(spectrum[i], (float)RND * 2 * PI); - //that's all; here is the only ifft for the whole sample; - //no windows are used ;-) - fft->freqs2smps(fftfreqs, newsample.smp); - - - //normalize(rms) - float rms = 0.0f; - for(int i = 0; i < samplesize; ++i) - rms += newsample.smp[i] * newsample.smp[i]; - rms = sqrt(rms); - if(rms < 0.000001f) - rms = 1.0f; - rms *= sqrt(262144.0f / samplesize);//262144=2^18 - for(int i = 0; i < samplesize; ++i) - newsample.smp[i] *= 1.0f / rms * 50.0f; - - //prepare extra samples used by the linear or cubic interpolation - for(int i = 0; i < extra_samples; ++i) - newsample.smp[i + samplesize] = newsample.smp[i]; - - //yield new sample - newsample.size = samplesize; - newsample.basefreq = basefreq * basefreqadjust; - cb(nsample, newsample); - } -exit: - //Cleanup - delete (fft); - delete[] fftfreqs; - delete[] spectrum; + const PADnoteParameters* this_c = this; + + auto thread_cb = [basefreq, bwadjust, &cb, do_abort, + samplesize, samplemax, spectrumsize, + &adj, &profile, this_c]( + unsigned nthreads, unsigned threadno) + { + //prepare a BIG IFFT + FFTwrapper *fft = new FFTwrapper(samplesize); + fft_t *fftfreqs = new fft_t[samplesize / 2]; + float *spectrum = new float[spectrumsize]; + + for(int nsample = 0; nsample < samplemax; ++nsample) + if(nsample % nthreads == threadno) + { + if(do_abort()) + break; + const float basefreqadjust = + powf(2.0f, adj[nsample] - adj[samplemax - 1] * 0.5f); + + if(this_c->Pmode == 0) + this_c->generatespectrum_bandwidthMode(spectrum, + spectrumsize, + basefreq*basefreqadjust, + profile, + profilesize, + bwadjust); + else + this_c->generatespectrum_otherModes(spectrum, spectrumsize, + basefreq * basefreqadjust); + + //the last samples contains the first samples + //(used for linear/cubic interpolation) + const int extra_samples = 5; + PADnoteParameters::Sample newsample; + newsample.smp = new float[samplesize + extra_samples]; + + newsample.smp[0] = 0.0f; + for(int i = 1; i < spectrumsize; ++i) //randomize the phases + fftfreqs[i] = FFTpolar(spectrum[i], (float)RND * 2 * PI); + //that's all; here is the only ifft for the whole sample; + //no windows are used ;-) + fft->freqs2smps(fftfreqs, newsample.smp); + + + //normalize(rms) + float rms = 0.0f; + for(int i = 0; i < samplesize; ++i) + rms += newsample.smp[i] * newsample.smp[i]; + rms = sqrt(rms); + if(rms < 0.000001f) + rms = 1.0f; + rms *= sqrt(262144.0f / samplesize);//262144=2^18 + for(int i = 0; i < samplesize; ++i) + newsample.smp[i] *= 1.0f / rms * 50.0f; + + //prepare extra samples used by the linear or cubic interpolation + for(int i = 0; i < extra_samples; ++i) + newsample.smp[i + samplesize] = newsample.smp[i]; + + //yield new sample + newsample.size = samplesize; + newsample.basefreq = basefreq * basefreqadjust; + cb(nsample, newsample); + } + + //Cleanup + delete (fft); + delete[] fftfreqs; + delete[] spectrum; + }; + + unsigned nthreads = std::min(max_threads, + std::thread::hardware_concurrency()); + std::vector<std::thread> threads(nthreads); + for(unsigned i = 0; i < nthreads; ++i) + threads[i] = std::thread(thread_cb, nthreads, i); + for(unsigned i = 0; i < nthreads; ++i) + threads[i].join(); + + return samplemax; } void PADnoteParameters::export2wav(std::string basefilename) diff --git a/src/Params/PADnoteParameters.h b/src/Params/PADnoteParameters.h @@ -23,9 +23,9 @@ namespace zyn { /** - * Parameters for PAD synthesis + * @brief Parameters for PAD synthesis * - * Note - unlike most other parameter objects significant portions of this + * @note unlike most other parameter objects significant portions of this * object are `owned' by the non-realtime context. The realtime context only * needs the samples generated by the PADsynth algorithm and modulators (ie * envelopes/filters/LFOs) for amplitude, frequency, and filters. @@ -146,26 +146,47 @@ class PADnoteParameters:public Presets - float setPbandwidth(int Pbandwidth); //returns the BandWidth in cents - float getNhr(int n); //gets the n-th overtone position relatively to N harmonic + float setPbandwidth(int Pbandwidth); //!< Return the BandWidth in cents + //! Get the n-th overtone position relatively to N harmonic + float getNhr(int n) const; void applyparameters(void); - void applyparameters(std::function<bool()> do_abort); + //! @brief Compute the #sample array from the other parameters. + //! + //! For the function's parameters, see sampleGenerator() + void applyparameters(std::function<bool()> do_abort, + unsigned max_threads = 0); void export2wav(std::string basefilename); OscilGen *oscilgen; Resonance *resonance; - //RT sample data struct Sample { int size; float basefreq; float *smp; - } sample[PAD_MAX_SAMPLES]; + }; + + //! RT sample data + Sample sample[PAD_MAX_SAMPLES]; typedef std::function<void(int,PADnoteParameters::Sample&)> callback; - void sampleGenerator(PADnoteParameters::callback cb, - std::function<bool()> do_abort); + + //! @brief PAD synth main function + //! + //! Generate spectrum and run IFFTs on it + //! @param cb A callback that will be executed for each sample buffer + //! Note that this function can be executed by multiple + //! threads at the same time, so make sure you use mutexes + //! etc where required + //! @param do_abort Function that decides whether the calculation should + //! be aborted (probably because of interruptions by the + //! user) + //! @param max_threads Maximum number of threads for computation, or + //! zero if no maximum shall be set + int sampleGenerator(PADnoteParameters::callback cb, + std::function<bool()> do_abort, + unsigned max_threads = 0); const AbsTime *time; int64_t last_update_timestamp; @@ -178,16 +199,15 @@ class PADnoteParameters:public Presets void generatespectrum_bandwidthMode(float *spectrum, int size, float basefreq, - float *profile, + const float *profile, int profilesize, - float bwadjust); + float bwadjust) const; void generatespectrum_otherModes(float *spectrum, int size, - float basefreq); + float basefreq) const; void deletesamples(); void deletesample(int n); - FFTwrapper *fft; public: const SYNTH_T &synth; }; diff --git a/src/Tests/PadNoteTest.h b/src/Tests/PadNoteTest.h @@ -92,7 +92,7 @@ class PadNoteTest:public CxxTest::TestSuite //defaultPreset->defaults(); - pars->applyparameters(); + pars->applyparameters([]{return false;}, 1); //verify xml was loaded ///TS_ASSERT(defaultPreset->VoicePar[1].Enabled); @@ -197,9 +197,9 @@ class PadNoteTest:public CxxTest::TestSuite TS_ASSERT_DELTA(note->NoteGlobalPar.Panning, 0.500000f, 0.01f); - for(int i=0; i<7; ++i) + for(int i=0; i<8; ++i) TS_ASSERT(pars->sample[i].smp); - for(int i=7; i<PAD_MAX_SAMPLES; ++i) + for(int i=8; i<PAD_MAX_SAMPLES; ++i) TS_ASSERT(!pars->sample[i].smp); TS_ASSERT_DELTA(pars->sample[0].smp[0], -0.057407f, 0.0005f);