-
Notifications
You must be signed in to change notification settings - Fork 105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pad wavetables #54
base: master
Are you sure you want to change the base?
Pad wavetables #54
Conversation
0732fd5
to
5224e81
Compare
No functional change, just cascading of `Sample` access. This is done because it makes reading the next commit more easy.
5224e81
to
881233a
Compare
There's a lot of changes towards std::vector (or similar style containers). I'd recommend against this as it does complicate looking at buffer memory placement (i.e. alignment for SSE ops), masks any reallocation, and doesn't really buy us much IMO. I'm guessing it will be easier to read the individual commits as recommended. (I should follow up on the additional readthrough fairly soon). |
Do you mean those in
Do you plan to use SSE and
Yes, that should be really be easier. |
Btw, the main reason for using |
p->sample[n].size = rtosc_argument(m,0).i; | ||
p->sample[n].basefreq = rtosc_argument(m,1).f; | ||
p->setCurSampleSize(n, rtosc_argument(m,0).i); | ||
p->setCurBaseFreq(n, rtosc_argument(m,1).f); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The whole setting a sample operation is (in my mind at least) a single atomic action. As such do you think it would make sense to have a single setter, which would keep the update somewhat more atomic (not atomic in the multi-threading sense though).
// ...take sample buffer | ||
p->sample[n].smp[idx] = ((float**)blob.data)[idx]; | ||
} | ||
//XXX TODO memory management (deallocation of current smp buffer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend against large copies on the realtime side. Just take the pointer and send the old one back to be freed.
{ | ||
float harmonics[synth.oscilsize]; | ||
memset(spectrum, 0, sizeof(float) * size); | ||
memset(harmonics, 0, sizeof(float) * synth.oscilsize); | ||
|
||
//get the harmonic structure from the oscillator (I am using the frequency amplitudes, only) | ||
oscilgen->get(harmonics, basefreq, false); | ||
oscilgen_ptr->get(harmonics, basefreq, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am unclear why this change is needed.
assert(minIdx <= oscilgen_ptr->Pbasefuncpar*Sample::num_smps_per_key + 1.0f); | ||
assert(maxIdx >= oscilgen_ptr->Pbasefuncpar*Sample::num_smps_per_key - 1.0f); | ||
#else | ||
std::size_t minIdx, maxIdx; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, the idea with Pad Synth is to create a 2D matrix where dimension one is time, dimension two is expected sampling frequency. There's some randomization so typically you can't trivially walk between the different sampling freq rows.
I would have expected wavetable synthesis to turn this into a 3D tensor where you have timeXnote-freqXnote-parameter. It looks like instead there's just timeXnote-parameter here.
Not sure if that's a misreading or not though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before this commit, we had Sample sample[PAD_MAX_SAMPLES]
(freqs), and each Sample
had one float* smp
(array for time). Now, each Sample
contains std::vector<float*> smp
instead of float* smp
- each vector element for one wavetable-parameter. So it's indeed 3D now.
// copying is deleted at all | ||
Sample(const Sample& source) = delete; | ||
Sample& operator=(const Sample& source) = delete; | ||
Sample& operator=(Sample&& source) = default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As long as the copy constructors are correctly handled, then I guess this is alright, though in my experience it's still slightly more error prone than a simple struct with raw pointers (and a bit harder to debug unless you have the gdb scripts to understand std::vectors all working and those tend to break at any non-trivial levels of optimization).
@@ -170,10 +199,10 @@ class OscilGen:public Presets | |||
oldmodulationpar3; | |||
|
|||
|
|||
fft_t *basefuncFFTfreqs; //Base Function Frequencies | |||
std::vector<fft_t> basefuncFFTfreqs; //Base Function Frequencies |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know how to attach a comment to a whole commit in this interface, but IMO this commit seems unnecessary. For the later commits, all that is needed is harmonic content given wave parameter=x, right?
Would it make sense to have a simple get_harmonics_with_param_equal_x(x)? Perhaps I'm oversimplying things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it's required because the PADnoteParameters::sample
vectors can be calculated by different threads for the different frequencies (PAD synth multithreading - "If one thread wants to use wave parameter x, the other one can't use wave parameter y"). Most parts of OscilGen::get
look easy to make thread-safe, but a call to prepare
seems always needed due to the new basefunc parameter (needPrepare
returns true if that one is changed), and I guess making prepare
thread safe is as bad as copying the whole class?
operator const FFTwrapper*() const { return fft; } | ||
FFTwrapper* operator->() { return fft; } | ||
const FFTwrapper* operator->() const { return fft; } | ||
} fft; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be really wary about this functionality. We can't have full smart pointers due to the whole multi-threaded situation and this copy would allow for a "weak pointer", which is a complexity that can bite back in unexpected ways. Also, with regards to FFTW, there are modes which assume that the library knows the address of the pointer passed to it. I don't think we're currently using one, but we may end up doing so in the future and this would complicate things as the pointer address of some of the fft_t* references would change.
void setCurSampleSize(std::size_t idx, int val) { | ||
sample[idx].size = val; | ||
} | ||
float curBaseFreq(std::size_t idx) const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like we might want to force this one to inline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could ask the compiler to inline
, but FWIR most compilers do their own thing anyways. I think in the end, some runtime tests should be done to see if there's any significant slowdown.
For general information, this PR is likely superseded by #61. |
This is a draft and basic approach to use a wheel in PAD synth to cycle through waves with different basewave parameters in realtime (those waves are what I call "table" here). So, e.g., by turning the wheel, you can smoothly turn a saw into a power wave.
Tester notes:
Review hints:
There are some issues: