./duplicate.py DigitalDistortion/ Synthesis YourNameProcessDoubleReplacing function. Instead, create an Oscillator class, call its functions from ProcessDoubleReplacing , and it will fill the output buffer with double values to create a waveform. First, we use an intuitive approach. Later, faced with the shortcomings of this approach, we will find a way to achieve a better sound.


#define and #endif in Oscillator.h : #include <math.h> enum OscillatorMode { OSCILLATOR_MODE_SINE, OSCILLATOR_MODE_SAW, OSCILLATOR_MODE_SQUARE, OSCILLATOR_MODE_TRIANGLE }; class Oscillator { private: OscillatorMode mOscillatorMode; const double mPI; double mFrequency; double mPhase; double mSampleRate; double mPhaseIncrement; void updateIncrement(); public: void setMode(OscillatorMode mode); void setFrequency(double frequency); void setSampleRate(double sampleRate); void generate(double* buffer, int nFrames); Oscillator() : mOscillatorMode(OSCILLATOR_MODE_SINE), mPI(2*acos(0.0)), mFrequency(440.0), mPhase(0.0), mSampleRate(44100.0) { updateIncrement(); }; }; enum . Now the default sine will be generated, but this can be changed using the member function of the setMode class.
generate . This is the very function that fills the output buffer with values. void Oscillator::setMode(OscillatorMode mode) { mOscillatorMode = mode; } void Oscillator::setFrequency(double frequency) { mFrequency = frequency; updateIncrement(); } void Oscillator::setSampleRate(double sampleRate) { mSampleRate = sampleRate; updateIncrement(); } void Oscillator::updateIncrement() { mPhaseIncrement = mFrequency * 2 * mPI / mSampleRate; } mPhaseIncrement depends on mFrequency and mSampleRate , so that it is updated each time one of these two parameters changes. We could calculate it each sample in ProcessDoubleReplacing , but it’s much better to do it here.generate function at the moment looks like this: void Oscillator::generate(double* buffer, int nFrames) { const double twoPI = 2 * mPI; switch (mOscillatorMode) { case OSCILLATOR_MODE_SINE: // ... break; case OSCILLATOR_MODE_SAW: // ... break; case OSCILLATOR_MODE_SQUARE: // ... break; case OSCILLATOR_MODE_TRIANGLE: // ... break; } } ProcessDoubleReplacing is ProcessDoubleReplacing . Switch used so that the appropriate code is executed depending on the desired waveform. case OSCILLATOR_MODE_SINE: for (int i = 0; i < nFrames; i++) { buffer[i] = sin(mPhase); mPhase += mPhaseIncrement; while (mPhase >= twoPI) { mPhase -= twoPI; } } break; mFrequency and mSampleRate . We increment the mPhase and limit it to between 0 and twoPI . The only "difficult" operation here is the function call sin() , which is performed at the hardware level on most systems. case OSCILLATOR_MODE_SAW: for (int i = 0; i < nFrames; i++) { buffer[i] = 1.0 - (2.0 * mPhase / twoPI); mPhase += mPhaseIncrement; while (mPhase >= twoPI) { mPhase -= twoPI; } } break; mPhase increases from 0 and jumps back to zero when the twoPI value is twoPI .(mPhase / twoPI) increases from 0 to 1 and jumps back to zero.(2.0 * mPhase / twoPI) increases from 0 , and jumps back as soon as it reaches two.mPhase is 0 , the expression 1.0 - (2.0 * mPhase / twoPI) is 1 . As mPhase rises, the value of this expression drops and, as soon as it reaches -1 , jumps to 1 .while condition would seem redundant - it will occur in each case . But if you do otherwise, then you have to turn on the switch in the loop. So we even get rid of the excess for , but then the switch will be executed more often than necessary. case OSCILLATOR_MODE_SQUARE: for (int i = 0; i < nFrames; i++) { if (mPhase <= mPI) { buffer[i] = 1.0; } else { buffer[i] = -1.0; } mPhase += mPhaseIncrement; while (mPhase >= twoPI) { mPhase -= twoPI; } } break; twoPI . The body of the if sets the first half of the cycle to 1 , the second to -1 . When mPhase becomes larger than mPI , a sharp jump appears in the wave form. It looks like a meander. case OSCILLATOR_MODE_TRIANGLE: for (int i = 0; i < nFrames; i++) { double value = -1.0 + (2.0 * mPhase / twoPI); buffer[i] = 2.0 * (fabs(value) - 0.5); mPhase += mPhaseIncrement; while (mPhase >= twoPI) { mPhase -= twoPI; } } break; -1.0 + (2.0 * mPhase / twoPI) in parts, as I did before, you will see that this is the opposite of a saw. The absolute value ( fabs ) of the ascending saw means that all values below 0 will be inverted (inverted relative to the x axis).0.5 aligns the waveform with respect to zero. Multiplying by 2.0 scales the value, and it changes from -1 to 1 . Here is a triangle.Oscillator member to the Synthesis class: // ... #include "Oscillator.h" class Synthesis : public IPlug { // ... private: double mFrequency; void CreatePresets(); Oscillator mOscillator; }; mThreshold to mFrequency . GetParam(kFrequency)->InitDouble("Frequency", 440.0, 50.0, 20000.0, 0.01, "Hz"); GetParam(kFrequency)->SetShape(2.0); createPresets function: void Synthesis::CreatePresets() { MakePreset("clean", 440.0); } Reset function: void Synthesis::Reset() { TRACE; IMutexLock lock(this); mOscillator.setSampleRate(GetSampleRate()); } GetSampleRate inherited from the IPlugBase class.OnParamChange also needs to be edited so that you can change the frequency with the pen: void Synthesis::OnParamChange(int paramIdx) { IMutexLock lock(this); switch (paramIdx) { case kFrequency: mOscillator.setFrequency(GetParam(kFrequency)->Value()); break; default: break; } } ProcessDoubleReplacing : void Synthesis::ProcessDoubleReplacing(double** inputs, double** outputs, int nFrames) { // Mutex is already locked for us. double *leftOutput = outputs[0]; double *rightOutput = outputs[1]; mOscillator.generate(leftOutput, nFrames); // Copy left buffer into right buffer: for (int s = 0; s < nFrames; ++s) { rightOutput[s] = leftOutput[s]; } } mOscillator fills the left channel buffer, and we simply copy these values into the right buffer.mOscillatorMode in Oscillator.h during the initialization phase in the constructor: Oscillator() : mOscillatorMode(OSCILLATOR_MODE_SAW), mPI(2*acos(0.0)), mFrequency(440.0), mPhase(0.0), mSampleRate(44100.0) { updateIncrement(); }; OSCILLATOR_MODE_SQUARE and OSCILLATOR_MODE_TRIANGLE , they have different timbres.mPhase is subtracted from twoPI and the value of the expression again becomes less than the mPI . The basic idea is that abrupt signal jumps mean that it contains many high-frequency components.Source: https://habr.com/ru/post/226439/
All Articles