PurpleSunray wrote:ahaha, looks like this is becoming more of a workshop than forum thread xD

Hahah, yeah! Its funny. And I'm really happy of your huge (huge) helps. Thanks a loooot!!!

PurpleSunray wrote:Can you explain me how that envelope signal is beeing synthesized?

I do

Here's my

void Envelope::Process(Voice &voice) explanation.

- Code: Select all
`VoiceParameters &voiceParameters = mVoiceParameters[voice.mIndex];`

if (voiceParameters.mIsCompleted) {

return;

}

This first line take the voice parameters (array) which contains the data for that envelope (which is different for each voice). I check so if the bool mIsCompleted is true or not (meaning: if I have a envelope with type "no loop" and I reach the end of the shape, it just need to ignore the following and keep the last/calculated value).

Now, let examine the "CORE" of the envelope calculations.

- Code: Select all
`// refresh at block size`

if (voiceParameters.mBlockStep >= gBlockSize) {

}

This branch is where I calculate the "next" point value. Once 128 samples (my blocksize) have passed (I mean 128 samples of the shape... a chunk; the tick of when I process this of course depends by the rate of processing, because if I'm a 1 hz it is executed every 128 sample; if rate is 2 hz, it will happens every 64 samples, and so on), I calculate, from current block start, the next block value.

Note: the whole envelope shape is divided in sections (defined as be between two "user-gui" points; between two points I have a tension point, which determine the slope of the segment). Each section is so divided in block of 128 samples.

sectionIndex is the index of each section (if I have 10 points, there are so 9 sections). sectionStep is the step (sample) of the current section. sectionLength is the lenght of the section (in samples). Since envelope is divided into blocks, blockIndex is the index of the block of the current sectionIndex at the current sectionStep:

- Code: Select all
`unsigned int sectionIndex = RefreshSectionIndex(voiceParameters.mStep);`

double sectionStep = RefreshSectionStep(sectionIndex, voiceParameters.mStep);

unsigned int blockIndex = RefreshBlockIndex(sectionStep);

double sectionLength = RefreshSectionLength(sectionIndex);

Note that all of these need to be calculated "real time", because if in the meanwhile I move a point (which at the end are the only interface that can be modulated), section/step/block changes (finishing in the last/next point/section).

After this, I use a sort of "homographic function" to determine the start/end point value of the section (by current data, tension, etc), thus the start/end of the current block:

- Code: Select all
`int numBlocks = (int)(sectionLength / gBlockSize);`

double numBlocksFraction = 1.0 / numBlocks;

double pos0 = blockIndex * numBlocksFraction;

double pos1 = (blockIndex + 1) * numBlocksFraction;

double a = 1.0 - (1.0 / mTensions[sectionIndex]);

double p0 = pos0 / (pos0 + a * (pos0 - 1.0));

double p1 = pos1 / (pos1 + a * (pos1 - 1.0));

double sectionStartAmp = mAmps[sectionIndex];

double sectionEndAmp = mAmps[sectionIndex + 1];

double sectionDeltaAmp = sectionEndAmp - sectionStartAmp;

voiceParameters.mBlockStartAmp = sectionStartAmp + p0 * sectionDeltaAmp;

double blockEndAmp = sectionStartAmp + p1 * sectionDeltaAmp;

voiceParameters.mBlockFraction = (blockEndAmp - voiceParameters.mBlockStartAmp) * (1.0 / gBlockSize);

voiceParameters.mBlockStep = fmod(voiceParameters.mBlockStep, gBlockSize);

Once I've them, its easy: I just "interpolate" using the start, the current block step (which depends by rate) and the ending point. "Classic" linear interpolation:

- Code: Select all
`voiceParameters.mValue = (voiceParameters.mBlockStartAmp + (voiceParameters.mBlockStep * voiceParameters.mBlockFraction));`

voiceParameters.mValue = mIsEnabled * ((1 + mIsBipolar) / 2.0 * voiceParameters.mValue + (1 - mIsBipolar) / 2.0) * mAmount;

mOutputConnector_CV.mPolyValue[voice.mIndex] = voiceParameters.mValue;

(This is the optimized version of the one made this afternoon. It also multiply by amount of the envelope, polarity, and just return 0 if the envelope is !mIsEnabled.)

Once calculated, I store the output to a sort of "CV output pin" object (which will be linked to other targets later in the plugin).

Finish! I increment the phase of the envelope:

- Code: Select all
`voiceParameters.mBlockStep += mRate;`

voiceParameters.mStep += mRate;

and I'm done

That's the whole concept. Hope is it more clear now?