| 126 | int oversamplingIndex = 2; // default is 2^oversamplingIndex == x4 oversampling |
| 127 | |
| 128 | void process(const ProcessArgs& args) override { |
| 129 | |
| 130 | // pitch inputs determine number of polyphony engines |
| 131 | const int channels = std::max({1, inputs[PITCH1_INPUT].getChannels(), inputs[PITCH2_INPUT].getChannels()}); |
| 132 | |
| 133 | const float pitchKnobs = 1.f + std::round(params[OCTAVE_PARAM].getValue()) + params[TUNE_PARAM].getValue() / 12.f; |
| 134 | const int oversamplingRatio = oversampler[0][0].getOversamplingRatio(); |
| 135 | |
| 136 | for (int c = 0; c < channels; c += 4) { |
| 137 | float_4 pw = simd::clamp(params[PWM_PARAM].getValue() + inputs[PWM_INPUT].getPolyVoltageSimd<float_4>(c) / 5.f, -1.f, 1.f); |
| 138 | if (limitPW) { |
| 139 | pw = simd::rescale(pw, -1, +1, 0.05f, 0.95f); |
| 140 | } |
| 141 | else { |
| 142 | pw = simd::rescale(pw, -1.f, +1.f, 0.f, 1.f); |
| 143 | } |
| 144 | |
| 145 | const float_4 pitch = inputs[PITCH1_INPUT].getPolyVoltageSimd<float_4>(c) + inputs[PITCH2_INPUT].getPolyVoltageSimd<float_4>(c); |
| 146 | |
| 147 | // pulsewave waveform doesn't have DC even for non 50% duty cycles, but Befaco team would like the option |
| 148 | // for it to be added back in for hardware compatibility reasons |
| 149 | const float_4 pulseDCOffset = (!removePulseDC) * 2.f * (0.5f - pw); |
| 150 | |
| 151 | // input oversampling buffers |
| 152 | float_4* osBufferSync = oversamplerInputs[SYNC_INPUT_UP][c / 4].getOSBuffer(); |
| 153 | float_4* osBufferFM = oversamplerInputs[FM_INPUT_UP][c / 4].getOSBuffer(); |
| 154 | |
| 155 | // upsample hard sync input (if connected) |
| 156 | if (inputs[SYNC_INPUT].isConnected()) { |
| 157 | oversamplerInputs[SYNC_INPUT_UP][c].upsample(inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c)); |
| 158 | } |
| 159 | else { |
| 160 | std::fill(osBufferSync, &osBufferSync[oversamplingRatio], float_4::zero()); |
| 161 | } |
| 162 | // upsample FM input (if connected) |
| 163 | if (inputs[FM_INPUT].isConnected()) { |
| 164 | oversamplerInputs[FM_INPUT_UP][c].upsample(inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c)); |
| 165 | } |
| 166 | else { |
| 167 | std::fill(osBufferFM, &osBufferFM[oversamplingRatio], float_4::zero()); |
| 168 | } |
| 169 | |
| 170 | float_4* osBufferTri = oversampler[TRI_OUTPUT][c / 4].getOSBuffer(); |
| 171 | float_4* osBufferSaw = oversampler[SAW_OUTPUT][c / 4].getOSBuffer(); |
| 172 | float_4* osBufferSin = oversampler[SINE_OUTPUT][c / 4].getOSBuffer(); |
| 173 | float_4* osBufferSquare = oversampler[SQUARE_OUTPUT][c / 4].getOSBuffer(); |
| 174 | float_4* osBufferEven = oversampler[EVEN_OUTPUT][c / 4].getOSBuffer(); |
| 175 | for (int i = 0; i < oversamplingRatio; ++i) { |
| 176 | // use upsampled FM input |
| 177 | const float_4 fmVoltage = osBufferFM[i] * 0.25f; |
| 178 | const float_4 freq = dsp::FREQ_C4 * simd::pow(2.f, pitchKnobs + pitch + fmVoltage); |
| 179 | const float_4 deltaBasePhase = simd::clamp(freq * args.sampleTime / oversamplingRatio, 1e-6, 0.5f); |
| 180 | // floating point arithmetic doesn't work well at low frequencies, specifically because the finite difference denominator |
| 181 | // becomes tiny - we check for that scenario and use naive / 1st order waveforms in that frequency regime (as aliasing isn't |
| 182 | // a problem there). With no oversampling, at 44100Hz, the threshold frequency is 44.1Hz. |
| 183 | const float_4 lowFreqRegime = simd::abs(deltaBasePhase) < 1e-3; |
| 184 | // 1 / denominator for the second-order FD |
| 185 | const float_4 denominatorInv = 0.25 / (deltaBasePhase * deltaBasePhase); |
nothing calls this directly
no test coverage detected