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:
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);