diff --git a/src/effects/backends/builtin/pitchshifteffect.cpp b/src/effects/backends/builtin/pitchshifteffect.cpp index 7065ce0c1b0..c840efdcd34 100644 --- a/src/effects/backends/builtin/pitchshifteffect.cpp +++ b/src/effects/backends/builtin/pitchshifteffect.cpp @@ -1,7 +1,22 @@ #include "effects/backends/builtin/pitchshifteffect.h" +#include + #include "util/sample.h" +namespace { +static constexpr SINT kSemitonesPerOctave = 12; + +static const QString kPitchParameterId = QStringLiteral("pitch"); +static const QString kRangeParameterId = QStringLiteral("range"); +static const QString kSemitonesModeParameterId = QStringLiteral("semitonesMode"); +static const QString kFormantPreservingParameterId = QStringLiteral("formantPreserving"); +} // anonymous namespace + +PitchShiftEffect::PitchShiftEffect() + : m_currentFormant(false) { +} + PitchShiftGroupState::PitchShiftGroupState( const mixxx::EngineParameters& engineParameters) : EffectState(engineParameters) { @@ -46,12 +61,12 @@ EffectManifestPointer PitchShiftEffect::getManifest() { pManifest->setName(QObject::tr("Pitch Shift")); pManifest->setShortName(QObject::tr("Pitch Shift")); pManifest->setAuthor("The Mixxx Team"); - pManifest->setVersion("1.0"); + pManifest->setVersion("2.0"); pManifest->setDescription(QObject::tr( "Raises or lowers the original pitch of a sound.")); EffectManifestParameterPointer pitch = pManifest->addParameter(); - pitch->setId("pitch"); + pitch->setId(kPitchParameterId); pitch->setName(QObject::tr("Pitch")); pitch->setShortName(QObject::tr("Pitch")); pitch->setDescription(QObject::tr( @@ -61,12 +76,48 @@ EffectManifestPointer PitchShiftEffect::getManifest() { pitch->setNeutralPointOnScale(0.0); pitch->setRange(-1.0, 0.0, 1.0); + EffectManifestParameterPointer range = pManifest->addParameter(); + range->setId(kRangeParameterId); + range->setName(QObject::tr("Range")); + range->setShortName(QObject::tr("Range")); + range->setDescription(QObject::tr( + "The range of the Pitch knob (0 - 2 octaves).\n")); + range->setValueScaler(EffectManifestParameter::ValueScaler::Linear); + range->setDefaultLinkType(EffectManifestParameter::LinkType::Linked); + range->setNeutralPointOnScale(1.0); + range->setRange(0.0, 1.0, 2.0); + + EffectManifestParameterPointer semitonesMode = pManifest->addParameter(); + semitonesMode->setId(kSemitonesModeParameterId); + semitonesMode->setName(QObject::tr("Semitones")); + semitonesMode->setShortName(QObject::tr("Semitones")); + semitonesMode->setDescription(QObject::tr( + "Change the pitch in semitone steps instead of continuously.")); + semitonesMode->setValueScaler(EffectManifestParameter::ValueScaler::Toggle); + semitonesMode->setUnitsHint(EffectManifestParameter::UnitsHint::Unknown); + semitonesMode->setRange(0, 1, 1); + + EffectManifestParameterPointer formantPreserving = pManifest->addParameter(); + formantPreserving->setId(kFormantPreservingParameterId); + formantPreserving->setName(QObject::tr("Formant")); + formantPreserving->setShortName(QObject::tr("Formant")); + formantPreserving->setDescription(QObject::tr( + "Preserve the resonant frequencies (formants) of the human vocal tract " + "and other instruments.\n" + "Hint: compensates \"chipmunk\" or \"growling\" voices")); + formantPreserving->setValueScaler(EffectManifestParameter::ValueScaler::Toggle); + formantPreserving->setUnitsHint(EffectManifestParameter::UnitsHint::Unknown); + formantPreserving->setRange(0, 0, 1); + return pManifest; } void PitchShiftEffect::loadEngineEffectParameters( const QMap& parameters) { - m_pPitchParameter = parameters.value("pitch"); + m_pPitchParameter = parameters.value(kPitchParameterId); + m_pRangeParameter = parameters.value(kRangeParameterId); + m_pSemitonesModeParameter = parameters.value(kSemitonesModeParameterId); + m_pFormantPreservingParameter = parameters.value(kFormantPreservingParameterId); } void PitchShiftEffect::processChannel( @@ -79,15 +130,24 @@ void PitchShiftEffect::processChannel( Q_UNUSED(groupFeatures); Q_UNUSED(enableState); - const double pitchParameter = m_pPitchParameter->value(); + if (const bool formantPreserving = m_pFormantPreservingParameter->toBool(); + m_currentFormant != formantPreserving) { + m_currentFormant = formantPreserving; + + pState->m_pRubberBand->setFormantOption(m_currentFormant + ? RubberBand::RubberBandStretcher:: + OptionFormantPreserved + : RubberBand::RubberBandStretcher:: + OptionFormantShifted); + } + + double pitchParameter = m_pPitchParameter->value() * m_pRangeParameter->value(); + + if (m_pSemitonesModeParameter->toBool()) { + pitchParameter = roundToFraction(pitchParameter, kSemitonesPerOctave); + } - const double pitch = 1.0 + [=] { - if (pitchParameter < 0.0) { - return pitchParameter / 2.0; - } else { - return pitchParameter; - } - }(); + const double pitch = std::pow(2.0, pitchParameter); pState->m_pRubberBand->setPitchScale(pitch); diff --git a/src/effects/backends/builtin/pitchshifteffect.h b/src/effects/backends/builtin/pitchshifteffect.h index 0069e57cd28..eb778049aad 100644 --- a/src/effects/backends/builtin/pitchshifteffect.h +++ b/src/effects/backends/builtin/pitchshifteffect.h @@ -9,6 +9,7 @@ #include "engine/effects/engineeffectparameter.h" #include "util/class.h" #include "util/defs.h" +#include "util/math.h" #include "util/sample.h" #include "util/types.h" @@ -30,7 +31,7 @@ class PitchShiftGroupState : public EffectState { class PitchShiftEffect final : public EffectProcessorImpl { public: - PitchShiftEffect() = default; + PitchShiftEffect(); static QString getId(); static EffectManifestPointer getManifest(); @@ -51,7 +52,11 @@ class PitchShiftEffect final : public EffectProcessorImpl return getId(); } + bool m_currentFormant; EngineEffectParameterPointer m_pPitchParameter; + EngineEffectParameterPointer m_pRangeParameter; + EngineEffectParameterPointer m_pSemitonesModeParameter; + EngineEffectParameterPointer m_pFormantPreservingParameter; DISALLOW_COPY_AND_ASSIGN(PitchShiftEffect); }; diff --git a/src/util/math.h b/src/util/math.h index ea9f5768bde..09334c7af7f 100644 --- a/src/util/math.h +++ b/src/util/math.h @@ -84,3 +84,15 @@ requires std::is_floating_point_v } #undef CMATH_CONSTEXPR + +/// https://en.wikipedia.org/wiki/Sign_function +template +requires std::is_arithmetic_v +constexpr T sgn(const T a) { + // silence -Wtype-limits + if constexpr (std::is_unsigned_v) { + return static_cast(a > T(0)); + } else { + return static_cast(a > T(0)) - static_cast(a < T(0)); + } +}