FilterGroupState::FilterGroupState(const mixxx::EngineParameters& bufferParameters) : EffectState(bufferParameters), m_loFreq(kMaxCorner / bufferParameters.sampleRate()), m_q(0.707106781), m_hiFreq(kMinCorner / bufferParameters.sampleRate()) { m_buffer = mixxx::SampleBuffer(bufferParameters.samplesPerBuffer()); m_pLowFilter = new EngineFilterBiquad1Low(1, m_loFreq, m_q, true); m_pHighFilter = new EngineFilterBiquad1High(1, m_hiFreq, m_q, true); }
MoogLadder4FilterGroupState::MoogLadder4FilterGroupState( const mixxx::EngineParameters& bufferParameters) : EffectState(bufferParameters), m_loFreq(kMaxCorner), m_resonance(0), m_hiFreq(kMinCorner), m_samplerate(bufferParameters.sampleRate()) { m_pBuf = SampleUtil::alloc(bufferParameters.samplesPerBuffer()); m_pLowFilter = new EngineFilterMoogLadder4Low( bufferParameters.sampleRate(), m_loFreq * bufferParameters.sampleRate(), m_resonance); m_pHighFilter = new EngineFilterMoogLadder4High( bufferParameters.sampleRate(), m_hiFreq * bufferParameters.sampleRate(), m_resonance); }
void Bessel4LVMixEQEffect::processChannel(const ChannelHandle& handle, Bessel4LVMixEQEffectGroupState* pState, const CSAMPLE* pInput, CSAMPLE* pOutput, const mixxx::EngineParameters& bufferParameters, const EffectEnableState enableState, const GroupFeatureState& groupFeatures, const EffectChainMixMode mixMode) { Q_UNUSED(handle); Q_UNUSED(groupFeatures); Q_UNUSED(mixMode); if (enableState == EffectEnableState::Disabling) { // Ramp to dry, when disabling, this will ramp from dry when enabling as well pState->processChannelAndPause(pInput, pOutput, bufferParameters.samplesPerBuffer()); } else { double fLow; double fMid; double fHigh; if (!m_pKillLow->toBool()) { fLow = m_pPotLow->value(); } else { fLow = 0; } if (!m_pKillMid->toBool()) { fMid = m_pPotMid->value(); } else { fMid = 0; } if (!m_pKillHigh->toBool()) { fHigh = m_pPotHigh->value(); } else { fHigh = 0; } pState->processChannel(pInput, pOutput, bufferParameters.samplesPerBuffer(), bufferParameters.sampleRate(), fLow, fMid, fHigh, m_pLoFreqCorner->get(), m_pHiFreqCorner->get()); } }
void LinkwitzRiley8EQEffect::processChannel(const ChannelHandle& handle, LinkwitzRiley8EQEffectGroupState* pState, const CSAMPLE* pInput, CSAMPLE* pOutput, const mixxx::EngineParameters& bufferParameters, const EffectEnableState enableState, const GroupFeatureState& groupFeatures) { Q_UNUSED(handle); Q_UNUSED(groupFeatures); float fLow = 0.f, fMid = 0.f, fHigh = 0.f; if (!m_pKillLow->toBool()) { fLow = m_pPotLow->value(); } if (!m_pKillMid->toBool()) { fMid = m_pPotMid->value(); } if (!m_pKillHigh->toBool()) { fHigh = m_pPotHigh->value(); } if (pState->m_oldSampleRate != bufferParameters.sampleRate() || (pState->m_loFreq != static_cast<int>(m_pLoFreqCorner->get())) || (pState->m_hiFreq != static_cast<int>(m_pHiFreqCorner->get()))) { pState->m_loFreq = static_cast<int>(m_pLoFreqCorner->get()); pState->m_hiFreq = static_cast<int>(m_pHiFreqCorner->get()); pState->m_oldSampleRate = bufferParameters.sampleRate(); pState->setFilters(bufferParameters.sampleRate(), pState->m_loFreq, pState->m_hiFreq); } pState->m_high2->process(pInput, pState->m_pHighBuf, bufferParameters.samplesPerBuffer()); // HighPass first run pState->m_low2->process(pInput, pState->m_pLowBuf, bufferParameters.samplesPerBuffer()); // LowPass first run for low and bandpass if (fMid != pState->old_mid || fHigh != pState->old_high) { SampleUtil::copy2WithRampingGain(pState->m_pHighBuf, pState->m_pHighBuf, pState->old_high, fHigh, pState->m_pLowBuf, pState->old_mid, fMid, bufferParameters.samplesPerBuffer()); } else { SampleUtil::copy2WithGain(pState->m_pHighBuf, pState->m_pHighBuf, fHigh, pState->m_pLowBuf, fMid, bufferParameters.samplesPerBuffer()); } pState->m_high1->process(pState->m_pHighBuf, pState->m_pMidBuf, bufferParameters.samplesPerBuffer()); // HighPass + BandPass second run pState->m_low1->process(pState->m_pLowBuf, pState->m_pLowBuf, bufferParameters.samplesPerBuffer()); // LowPass second run if (fLow != pState->old_low) { SampleUtil::copy2WithRampingGain(pOutput, pState->m_pLowBuf, pState->old_low, fLow, pState->m_pMidBuf, 1, 1, bufferParameters.samplesPerBuffer()); } else { SampleUtil::copy2WithGain(pOutput, pState->m_pLowBuf, fLow, pState->m_pMidBuf, 1, bufferParameters.samplesPerBuffer()); } if (enableState == EffectEnableState::Disabling) { // we rely on the ramping to dry in EngineEffect // since this EQ is not fully dry at unity pState->m_low1->pauseFilter(); pState->m_low2->pauseFilter(); pState->m_high1->pauseFilter(); pState->m_high2->pauseFilter(); pState->old_low = 1.0; pState->old_mid = 1.0; pState->old_high = 1.0; } else { pState->old_low = fLow; pState->old_mid = fMid; pState->old_high = fHigh; } }
void AutoPanEffect::processChannel( const ChannelHandle& handle, AutoPanGroupState* pGroupState, const CSAMPLE* pInput, CSAMPLE* pOutput, const mixxx::EngineParameters& bufferParameters, const EffectEnableState enableState, const GroupFeatureState& groupFeatures) { Q_UNUSED(handle); if (enableState == EffectEnableState::Disabled) { return; } AutoPanGroupState& gs = *pGroupState; double width = m_pWidthParameter->value(); double period = m_pPeriodParameter->value(); double smoothing = 0.5 - m_pSmoothingParameter->value(); if (groupFeatures.has_beat_length_sec) { // period is a number of beats double beats = std::max(roundToFraction(period, 2), 0.25); period = beats * groupFeatures.beat_length_sec * bufferParameters.sampleRate(); // TODO(xxx) sync phase //if (groupFeatures.has_beat_fraction) { } else { // period is a number of seconds period = std::max(period, 0.25) * bufferParameters.sampleRate(); } // When the period is changed, the position of the sound shouldn't // so time need to be recalculated if (gs.m_dPreviousPeriod != -1.0) { gs.time *= period / gs.m_dPreviousPeriod; } gs.m_dPreviousPeriod = period; if (gs.time >= period || enableState == EffectEnableState::Enabling) { gs.time = 0; } // Normally, the position goes from 0 to 1 linearly. Here we make steps at // 0.25 and 0.75 to have the sound fully on the right or fully on the left. // At the end, the "position" value can describe a sinusoid or a square // curve depending of the size of those steps. // coef of the slope // a = (y2 - y1) / (x2 - x1) // 1 / ( 1 - 2 * stepfrac) float a = smoothing != 0.5f ? 1.0f / (1.0f - smoothing * 2.0f) : 1.0f; // size of a segment of slope (controlled by the "smoothing" parameter) float u = (0.5f - smoothing) / 2.0f; gs.frac.setRampingThreshold(kPositionRampingThreshold); double sinusoid = 0; // NOTE: Assuming engine is working in stereo. for (unsigned int i = 0; i + 1 < bufferParameters.samplesPerBuffer(); i += 2) { CSAMPLE periodFraction = CSAMPLE(gs.time) / period; // current quarter in the trigonometric circle float quarter = floorf(periodFraction * 4.0f); // part of the period fraction being a step (not in the slope) CSAMPLE stepsFractionPart = floorf((quarter + 1.0f) / 2.0f) * smoothing; // float inInterval = fmod( periodFraction, (period / 2.0) ); float inStepInterval = fmod(periodFraction, 0.5f); CSAMPLE angleFraction; if (inStepInterval > u && inStepInterval < (u + smoothing)) { // at full left or full right angleFraction = quarter < 2.0f ? 0.25f : 0.75f; } else { // in the slope (linear function) angleFraction = (periodFraction - stepsFractionPart) * a; } // transforms the angleFraction into a sinusoid. // The width parameter modulates the two limits. if width values 0.5, // the limits will be 0.25 and 0.75. If it's 0, it will be 0.5 and 0.5 // so the sound will be stuck at the center. If it values 1, the limits // will be 0 and 1 (full left and full right). sinusoid = sin(M_PI * 2.0f * angleFraction) * width; gs.frac.setWithRampingApplied((sinusoid + 1.0f) / 2.0f); // apply the delay gs.delay->process(&pInput[i], &pOutput[i], -0.005 * math_clamp(((gs.frac * 2.0) - 1.0f), -1.0, 1.0) * bufferParameters.sampleRate()); double lawCoef = computeLawCoefficient(sinusoid); pOutput[i] *= gs.frac * lawCoef; pOutput[i+1] *= (1.0f - gs.frac) * lawCoef; gs.time++; while (gs.time >= period) { // Click for debug //pOutput[i] = 1.0f; //pOutput[i+1] = 1.0f; // The while loop is required in case period changes the value gs.time -= period; } } }
void FilterEffect::processChannel(const ChannelHandle& handle, FilterGroupState* pState, const CSAMPLE* pInput, CSAMPLE* pOutput, const mixxx::EngineParameters& bufferParameters, const EffectEnableState enableState, const GroupFeatureState& groupFeatures, const EffectChainMixMode mixMode) { Q_UNUSED(handle); Q_UNUSED(groupFeatures); Q_UNUSED(mixMode); double hpf; double lpf; double q = m_pQ->value(); const double minCornerNormalized = kMinCorner / bufferParameters.sampleRate(); const double maxCornerNormalized = kMaxCorner / bufferParameters.sampleRate(); if (enableState == EffectEnableState::Disabling) { // Ramp to dry, when disabling, this will ramp from dry when enabling as well hpf = minCornerNormalized; lpf = maxCornerNormalized; } else { hpf = m_pHPF->value() / bufferParameters.sampleRate(); lpf = m_pLPF->value() / bufferParameters.sampleRate(); } if ((pState->m_loFreq != lpf) || (pState->m_q != q) || (pState->m_hiFreq != hpf)) { // limit Q to ~4 in case of overlap // Determined empirically at 1000 Hz double ratio = hpf / lpf; double clampedQ = q; if (ratio < 1.414 && ratio >= 1) { ratio -= 1; double qmax = 2 + ratio * ratio * ratio * 29; clampedQ = math_min(clampedQ, qmax); } else if (ratio < 1 && ratio >= 0.7) { clampedQ = math_min(clampedQ, 2.0); } else if (ratio < 0.7 && ratio > 0.1) { ratio -= 0.1; double qmax = 4 - 2 / 0.6 * ratio; clampedQ = math_min(clampedQ, qmax); } pState->m_pLowFilter->setFrequencyCorners(1, lpf, clampedQ); pState->m_pHighFilter->setFrequencyCorners(1, hpf, clampedQ); } const CSAMPLE* pLpfInput = pState->m_buffer.data(); CSAMPLE* pHpfOutput = pState->m_buffer.data(); if (lpf >= maxCornerNormalized && pState->m_loFreq >= maxCornerNormalized) { // Lpf disabled Hpf can write directly to output pHpfOutput = pOutput; pLpfInput = pHpfOutput; } if (hpf > minCornerNormalized) { // hpf enabled, fade-in is handled in the filter when starting from pause pState->m_pHighFilter->process(pInput, pHpfOutput, bufferParameters.samplesPerBuffer()); } else if (pState->m_hiFreq > minCornerNormalized) { // hpf disabling pState->m_pHighFilter->processAndPauseFilter(pInput, pHpfOutput, bufferParameters.samplesPerBuffer()); } else { // paused LP uses input directly pLpfInput = pInput; } if (lpf < maxCornerNormalized) { // lpf enabled, fade-in is handled in the filter when starting from pause pState->m_pLowFilter->process(pLpfInput, pOutput, bufferParameters.samplesPerBuffer()); } else if (pState->m_loFreq < maxCornerNormalized) { // hpf disabling pState->m_pLowFilter->processAndPauseFilter(pLpfInput, pOutput, bufferParameters.samplesPerBuffer()); } else if (pLpfInput == pInput) { // Both disabled if (pOutput != pInput) { // We need to copy pInput pOutput SampleUtil::copy(pOutput, pInput, bufferParameters.samplesPerBuffer()); } } pState->m_loFreq = lpf; pState->m_q = q; pState->m_hiFreq = hpf; }
void PhaserEffect::processChannel(const ChannelHandle& handle, PhaserGroupState* pState, const CSAMPLE* pInput, CSAMPLE* pOutput, const mixxx::EngineParameters& bufferParameters, const EffectEnableState enableState, const GroupFeatureState& groupFeatures, const EffectChainMixMode mixMode) { Q_UNUSED(handle); Q_UNUSED(mixMode); if (enableState == EffectEnableState::Enabling) { pState->clear(); } CSAMPLE depth = 0; if (enableState != EffectEnableState::Disabling) { depth = m_pDepthParameter->value(); } double periodParameter = m_pLFOPeriodParameter->value(); double periodSamples; if (groupFeatures.has_beat_length_sec) { // periodParameter is a number of beats periodParameter = std::max(roundToFraction(periodParameter, 2.0), 1/4.0); if (m_pTripletParameter->toBool()) { periodParameter /= 3.0; } periodSamples = periodParameter * groupFeatures.beat_length_sec * bufferParameters.sampleRate(); } else { // periodParameter is a number of seconds periodSamples = std::max(periodParameter, 1/4.0) * bufferParameters.sampleRate(); } // freqSkip is used to calculate the phase independently for each channel, // so do not multiply periodSamples by the number of channels. CSAMPLE freqSkip = 1.0 / periodSamples * 2.0 * M_PI; CSAMPLE feedback = m_pFeedbackParameter->value(); CSAMPLE range = m_pRangeParameter->value(); int stages = 2 * m_pStagesParameter->value(); CSAMPLE* oldInLeft = pState->oldInLeft; CSAMPLE* oldOutLeft = pState->oldOutLeft; CSAMPLE* oldInRight = pState->oldInRight; CSAMPLE* oldOutRight = pState->oldOutRight; // Using two sets of coefficients for left and right channel CSAMPLE filterCoefLeft = 0; CSAMPLE filterCoefRight = 0; CSAMPLE left = 0, right = 0; CSAMPLE_GAIN oldDepth = pState->oldDepth; const CSAMPLE_GAIN depthDelta = (depth - oldDepth) / bufferParameters.framesPerBuffer(); const CSAMPLE_GAIN depthStart = oldDepth + depthDelta; int stereoCheck = m_pStereoParameter->value(); int counter = 0; for (unsigned int i = 0; i < bufferParameters.samplesPerBuffer(); i += bufferParameters.channelCount()) { left = pInput[i] + tanh(left * feedback); right = pInput[i + 1] + tanh(right * feedback); // For stereo enabled, the channels are out of phase pState->leftPhase = fmodf(pState->leftPhase + freqSkip, 2.0 * M_PI); pState->rightPhase = fmodf(pState->rightPhase + freqSkip + M_PI * stereoCheck, 2.0 * M_PI); // Updating filter coefficients once every 'updateCoef' samples to avoid // extra computing if ((counter++) % updateCoef == 0) { CSAMPLE delayLeft = 0.5 + 0.5 * sin(pState->leftPhase); CSAMPLE delayRight = 0.5 + 0.5 * sin(pState->rightPhase); // Coefficient computing based on the following: // https://ccrma.stanford.edu/~jos/pasp/Classic_Virtual_Analog_Phase.html CSAMPLE wLeft = range * delayLeft; CSAMPLE wRight = range * delayRight; CSAMPLE tanwLeft = tanh(wLeft / 2); CSAMPLE tanwRight = tanh(wRight / 2); filterCoefLeft = (1.0 - tanwLeft) / (1.0 + tanwLeft); filterCoefRight = (1.0 - tanwRight) / (1.0 + tanwRight); } left = processSample(left, oldInLeft, oldOutLeft, filterCoefLeft, stages); right = processSample(right, oldInRight, oldOutRight, filterCoefRight, stages); const CSAMPLE_GAIN depth = depthStart + depthDelta * (i / bufferParameters.channelCount()); // Computing output combining the original and processed sample pOutput[i] = pInput[i] * (1.0 - 0.5 * depth) + left * depth * 0.5; pOutput[i + 1] = pInput[i + 1] * (1.0 - 0.5 * depth) + right * depth * 0.5; } pState->oldDepth = depth; }
void FlangerEffect::processChannel(const ChannelHandle& handle, FlangerGroupState* pState, const CSAMPLE* pInput, CSAMPLE* pOutput, const mixxx::EngineParameters& bufferParameters, const EffectEnableState enableState, const GroupFeatureState& groupFeatures) { Q_UNUSED(handle); double lfoPeriodParameter = m_pSpeedParameter->value(); double lfoPeriodFrames; if (groupFeatures.has_beat_length_sec) { // lfoPeriodParameter is a number of beats lfoPeriodParameter = std::max(roundToFraction(lfoPeriodParameter, 2.0), kMinLfoBeats); if (m_pTripletParameter->toBool()) { lfoPeriodParameter /= 3.0; } lfoPeriodFrames = lfoPeriodParameter * groupFeatures.beat_length_sec * bufferParameters.sampleRate(); } else { // lfoPeriodParameter is a number of seconds lfoPeriodFrames = std::max(lfoPeriodParameter, kMinLfoBeats) * bufferParameters.sampleRate(); } // When the period is changed, the position of the sound shouldn't // so time need to be recalculated if (pState->previousPeriodFrames != -1.0) { pState->lfoFrames *= lfoPeriodFrames / pState->previousPeriodFrames; } pState->previousPeriodFrames = lfoPeriodFrames; // lfoPeriodSamples is used to calculate the delay for each channel // independently in the loop below, so do not multiply lfoPeriodSamples by // the number of channels. CSAMPLE_GAIN mix = m_pMixParameter->value(); RampingValue<CSAMPLE_GAIN> mixRamped( pState->prev_mix, mix, bufferParameters.framesPerBuffer()); pState->prev_mix = mix; CSAMPLE_GAIN regen = m_pRegenParameter->value(); RampingValue<CSAMPLE_GAIN> regenRamped( pState->prev_regen, regen, bufferParameters.framesPerBuffer()); pState->prev_regen = regen; // With and Manual is limited by amount of amplitude that remains from width // to kMaxDelayMs double width = m_pWidthParameter->value(); double manual = m_pManualParameter->value(); double maxManual = kCenterDelayMs + (kMaxLfoWidthMs - width) / 2; double minManual = kCenterDelayMs - (kMaxLfoWidthMs - width) / 2; manual = math_clamp(manual, minManual, maxManual); RampingValue<double> widthRamped( pState->prev_width, width, bufferParameters.framesPerBuffer()); pState->prev_width = width; RampingValue<double> manualRamped( pState->prev_manual, manual, bufferParameters.framesPerBuffer()); pState->prev_manual = manual; CSAMPLE* delayLeft = pState->delayLeft; CSAMPLE* delayRight = pState->delayRight; for (unsigned int i = 0; i < bufferParameters.samplesPerBuffer(); i += bufferParameters.channelCount()) { CSAMPLE_GAIN mix_ramped = mixRamped.getNext(); CSAMPLE_GAIN regen_ramped = regenRamped.getNext(); double width_ramped = widthRamped.getNext(); double manual_ramped = manualRamped.getNext(); pState->lfoFrames++; if (pState->lfoFrames >= lfoPeriodFrames) { pState->lfoFrames = 0; } float periodFraction = static_cast<float>(pState->lfoFrames) / lfoPeriodFrames; double delayMs = manual_ramped + width_ramped / 2 * sin(M_PI * 2.0f * periodFraction); double delayFrames = delayMs * bufferParameters.sampleRate() / 1000; SINT framePrev = (pState->delayPos - static_cast<SINT>(floor(delayFrames)) + kBufferLenth) % kBufferLenth; SINT frameNext = (pState->delayPos - static_cast<SINT>(ceil(delayFrames)) + kBufferLenth) % kBufferLenth; CSAMPLE prevLeft = delayLeft[framePrev]; CSAMPLE nextLeft = delayLeft[frameNext]; CSAMPLE prevRight = delayRight[framePrev]; CSAMPLE nextRight = delayRight[frameNext]; CSAMPLE frac = delayFrames - floorf(delayFrames); CSAMPLE delayedSampleLeft = prevLeft + frac * (nextLeft - prevLeft); CSAMPLE delayedSampleRight = prevRight + frac * (nextRight - prevRight); delayLeft[pState->delayPos] = tanh_approx(pInput[i] + regen_ramped * delayedSampleLeft); delayRight[pState->delayPos] = tanh_approx(pInput[i + 1] + regen_ramped * delayedSampleRight); pState->delayPos = (pState->delayPos + 1) % kBufferLenth; double gain = (1 - mix_ramped + kGainCorrection * mix_ramped); pOutput[i] = (pInput[i] + mix_ramped * delayedSampleLeft) / gain; pOutput[i + 1] = (pInput[i + 1] + mix_ramped * delayedSampleRight) / gain; } if (enableState == EffectEnableState::Disabling) { SampleUtil::clear(delayLeft, kBufferLenth); SampleUtil::clear(delayRight, kBufferLenth); pState->previousPeriodFrames = -1; pState->prev_regen = 0; pState->prev_mix = 0; } }
void MoogLadder4FilterEffect::processChannel( const ChannelHandle& handle, MoogLadder4FilterGroupState* pState, const CSAMPLE* pInput, CSAMPLE* pOutput, const mixxx::EngineParameters& bufferParameters, const EffectEnableState enableState, const GroupFeatureState& groupFeatures, const EffectChainMixMode mixMode) { Q_UNUSED(handle); Q_UNUSED(groupFeatures); Q_UNUSED(mixMode); double resonance = m_pResonance->value(); double hpf; double lpf; if (enableState == EffectEnableState::Disabling) { // Ramp to dry, when disabling, this will ramp from dry when enabling as well hpf = kMinCorner; lpf = kMaxCorner; } else { hpf = m_pHPF->value(); lpf = m_pLPF->value(); } if (pState->m_loFreq != lpf || pState->m_resonance != resonance || pState->m_samplerate != bufferParameters.sampleRate()) { pState->m_pLowFilter->setParameter( bufferParameters.sampleRate(), lpf * bufferParameters.sampleRate(), resonance); } if (pState->m_hiFreq != hpf || pState->m_resonance != resonance || pState->m_samplerate != bufferParameters.sampleRate()) { pState->m_pHighFilter->setParameter( bufferParameters.sampleRate(), hpf * bufferParameters.sampleRate(), resonance); } const CSAMPLE* pLpfInput = pState->m_pBuf; CSAMPLE* pHpfOutput = pState->m_pBuf; if (lpf >= kMaxCorner && pState->m_loFreq >= kMaxCorner) { // Lpf disabled Hpf can write directly to output pHpfOutput = pOutput; pLpfInput = pHpfOutput; } if (hpf > kMinCorner) { // hpf enabled, fade-in is handled in the filter when starting from pause pState->m_pHighFilter->process(pInput, pHpfOutput, bufferParameters.samplesPerBuffer()); } else if (pState->m_hiFreq > kMinCorner) { // hpf disabling pState->m_pHighFilter->processAndPauseFilter(pInput, pHpfOutput, bufferParameters.samplesPerBuffer()); } else { // paused LP uses input directly pLpfInput = pInput; } if (lpf < kMaxCorner) { // lpf enabled, fade-in is handled in the filter when starting from pause pState->m_pLowFilter->process(pLpfInput, pOutput, bufferParameters.samplesPerBuffer()); } else if (pState->m_loFreq < kMaxCorner) { // hpf disabling pState->m_pLowFilter->processAndPauseFilter(pLpfInput, pOutput, bufferParameters.samplesPerBuffer()); } else if (pLpfInput == pInput) { // Both disabled if (pOutput != pInput) { // We need to copy pInput pOutput SampleUtil::copy(pOutput, pInput, bufferParameters.samplesPerBuffer()); } } pState->m_loFreq = lpf; pState->m_resonance = resonance; pState->m_hiFreq = hpf; pState->m_samplerate = bufferParameters.sampleRate(); }