Audio processing with normalized [0.0, 1.0] domain I've a code that process buffers of samples/audio data. Here's the code:

#include &lt;iostream&gt;
#include &lt;math.h&gt;
#include &lt;cstring&gt;
#include &lt;algorithm&gt;

#define PI 3.141592653589793238
#define TWOPI 6.283185307179586476

const int blockSize = 256;
const double mSampleRate = 44100.0;
const double mHostPitch = 2.0;
const double mRadiansPerSample = TWOPI / mSampleRate;

double mGainNormalizedValues[blockSize];
double mPitchNormalizedValues[blockSize];
double mOffsetNormalizedValues[blockSize];

class Oscillator
{
public:
    double mPhase = 0.0;
    double minPitch = -48.0;
    double maxPitch = 48.0;
    double rangePitch = maxPitch - minPitch;
    double pitchPd = log(2.0) / 12.0;
    double minOffset = -900.0;
    double maxOffset = 900.0;    
    double rangeOffset = maxOffset - minOffset;

    Oscillator() { }

    void ProcessBuffer(double voiceFrequency, int blockSize, double *left, double *right) {
        // precomputed data
        double bp0 = voiceFrequency * mHostPitch;

        // process block
        for (int sampleIndex = 0; sampleIndex &lt; blockSize; sampleIndex++) {
            double output = (sin(mPhase)) * mGainNormalizedValues[sampleIndex];

            *left++ += output;
            *right++ += output;

            // next phase
            mPhase += std::clamp(mRadiansPerSample * (bp0 * WarpPitch(mPitchNormalizedValues[sampleIndex])) + WarpOffset(mOffsetNormalizedValues[sampleIndex]), 0.0, PI);
            while (mPhase &gt;= TWOPI) { mPhase -= TWOPI; }
        }   
    }

    inline double WarpPitch(double normalizedValue) { return exp((minPitch + normalizedValue * rangePitch) * pitchPd); }
    inline double WarpOffset(double normalizedValue) { return minOffset + normalizedValue * rangeOffset; }    
};

int main(int argc, const char *argv[]) {
    int numBuffer = 1024;
    int counterBuffer = 0;    

    Oscillator oscillator;        

    // I fill the buffer often
    while (counterBuffer &lt; numBuffer) {
        // init buffer
        double bufferLeft[blockSize];
        double bufferRight[blockSize];
        memset(&amp;bufferLeft, 0, blockSize * sizeof(double));
        memset(&amp;bufferRight, 0, blockSize * sizeof(double));

        // emulate params values for this buffer
        for(int i = 0; i &lt; blockSize; i++) {
            mGainNormalizedValues[i] = i / (double)blockSize;
            mPitchNormalizedValues[i] = i / (double)blockSize;
            mOffsetNormalizedValues[i] = i / (double)blockSize;
        }

        // process osc buffer
        oscillator.ProcessBuffer(130.81278, blockSize, &amp;bufferLeft[0], &amp;bufferRight[0]);

        // do somethings with buffer

        counterBuffer++;
    }
}


Basically:


init a Oscillator object
for each buffer, fill some param arrays with values (gain, pitch, offset); gain remain normalized [0.0, 1.0], while pitch and offset range in -48/48 and -900/900
then I iterate the buffer, calculating the Oscillator's sine due to pitch and offset, and I apply a gain; later, I move the phase, incrementing it


The whole domain of operations are normalized [0.0, 1.0]. But when I need to manage pitch and offset, I need to switch domain and use different values (i.e. the Warp functions).

This required lots of computations and process. I'd like to avoid it, so I can improve the code and performances.

How would you do it? Can I keep into [0.0, 1.0]? Could I improve the performance?
Prefer C++ headers

Instead of &lt;math.h&gt;, it's better to include &lt;cmath&gt; and qualify names such as std::log.

Prefer constants to macros

Re-write pi as a strongly-typed, scoped variable rather than a preprocessor macro.  Same for 2*pi if you really feel the need.

Manage line lengths

Some lines are very long.  In many cases, they just need newlines adding (e.g. bodies of inline functions can have their own lines).

In the case of the std::clamp() call, it's probably worth using variables to give a name to the candidate value before clamping. 

Easier sizeof

Instead of recomputing the size of bufferLeft and bufferRight like this:


    memset(&amp;bufferLeft, 0, blockSize * sizeof(double));
    memset(&amp;bufferRight, 0, blockSize * sizeof(double));



It's easier and clearer to just use the whole array size:

    memset(&amp;bufferLeft, 0, sizeof bufferLeft);
    memset(&amp;bufferRight, 0, sizeof bufferRight);


Personally, I'd generally prefer std::fill to match types and ensure the intention is clear:

    std::fill(std::begin(bufferLeft), std::end(bufferLeft), 0.0);
    std::fill(std::begin(bufferRight), std::end(bufferRight), 0.0);


Then I don't need to think about whether all-bits zero is the same as 0.0 or not.