diff --git a/Plug-in/Source/GamelanizerParameters.cpp b/Plug-in/Source/GamelanizerParameters.cpp index fd76955..233d590 100644 --- a/Plug-in/Source/GamelanizerParameters.cpp +++ b/Plug-in/Source/GamelanizerParameters.cpp @@ -25,19 +25,19 @@ GamelanizerParameters::GamelanizerParameters() { // initialize parameterId arrays - for (int i = 0; i < GamelanizerConstants::maxLevels + 1; ++i) + for (auto i = 0; i < GamelanizerConstants::maxLevels + 1; ++i) { gainIds[i] = "gain" + String(i); muteIds[i] = "mute" + String(i); panIds[i] = "pan" + String(i); } - for (int i = 0; i < GamelanizerConstants::maxLevels; ++i) + for (auto i = 0; i < GamelanizerConstants::maxLevels; ++i) { pitchIds[i] = "pitch" + String(i); taperIds[i] = "taper" + String(i); lpfIds[i] = "lpf" + String(i); hpfIds[i] = "hpf" + String(i); - for (int j = 0; j < 4; ++j) + for (auto j = 0; j < 4; ++j) dropIds[i][j] = "drop" + String(i) + "_" + String(j); } } @@ -104,7 +104,7 @@ void GamelanizerParameters::addGainsToLayout(std::vector( getGainId(i), @@ -121,7 +121,7 @@ void GamelanizerParameters::addGainsToLayout(std::vector>& params) { - for (int i = 0; i < GamelanizerConstants::maxLevels + 1; ++i) + for (auto i = 0; i < GamelanizerConstants::maxLevels + 1; ++i) { params.push_back(std::make_unique( getMuteId(i), @@ -147,7 +147,7 @@ void GamelanizerParameters::addPansToLayout(std::vector( getPanId(i), @@ -172,7 +172,7 @@ void GamelanizerParameters::addTapersToLayout(std::vector taperDefaults{ 0.0f, 0.0f, 0.1f, 0.2f }; - for (int i = 0; i < GamelanizerConstants::maxLevels; ++i) + for (auto i = 0; i < GamelanizerConstants::maxLevels; ++i) { params.push_back(std::make_unique( getTaperId(i), @@ -198,7 +198,7 @@ void GamelanizerParameters::addFiltersToLayout(std::vector( getHpfId(i), @@ -214,7 +214,7 @@ void GamelanizerParameters::addFiltersToLayout(std::vector lpfDefaults{ 10000.0f, 9000.0f, 6000.0f, 5000.0f }; - for (int i = 0; i < GamelanizerConstants::maxLevels; ++i) + for (auto i = 0; i < GamelanizerConstants::maxLevels; ++i) { params.push_back(std::make_unique( getLpfId(i), @@ -236,7 +236,7 @@ void GamelanizerParameters::addPitchesToLayout(std::vector( getPitchId(i), diff --git a/Plug-in/Source/PhaseVocoder.cpp b/Plug-in/Source/PhaseVocoder.cpp index 15d5f26..1c41214 100644 --- a/Plug-in/Source/PhaseVocoder.cpp +++ b/Plug-in/Source/PhaseVocoder.cpp @@ -59,7 +59,8 @@ void PhaseVocoder::setParams(const float newPitchShiftFactor, const float newPit resampler.maxNeedSamples = calculateMaximumNeededNumSamples(AnalysisFrames::analysisHopSize, oldPitchShiftFactor); - fft.window.amplitudeCompensationScale = static_cast(synthesisHopSize / FftStruct::FftWindow::squaredWindowSum); + fft.window.amplitudeCompensationScale = static_cast(synthesisHopSize / FftStruct::FftWindow::squaredWindowSum + ); } void PhaseVocoder::loadNextParams() @@ -86,11 +87,19 @@ int PhaseVocoder::calculateMaximumNeededNumSamples(const int desiredNumOut, cons } -void PhaseVocoder::resampleHop() +void PhaseVocoder::resampleHop(const bool skipProcessing) { - const auto numUsed = resampler.interpolator.process(pitchShiftFactor, resampler.queue.data.data(), - resampler.resamplerAnalysisHopBuffer, - AnalysisFrames::analysisHopSize); + int numUsed; + if (!skipProcessing) + { + numUsed = resampler.interpolator.process(pitchShiftFactor, resampler.queue.data.data(), + resampler.resamplerAnalysisHopBuffer, + AnalysisFrames::analysisHopSize); + } + else + { + numUsed = static_cast(AnalysisFrames::analysisHopSize * pitchShiftFactor); + } jassert(numUsed <= resampler.queue.writePosition); popUsedSamples(numUsed); } @@ -136,8 +145,6 @@ void PhaseVocoder::copyAnalysisFrameToFftInOut() void PhaseVocoder::storePhasesInBuffer() { - // this is the first frame we've processed of this beat, so just store the phases without scaling them - // reinterpret_cast the fft buffer to complex auto* complexBins = reinterpret_cast*>(fft.inOut); @@ -220,33 +227,31 @@ void PhaseVocoder::scaleAnalysisFrame() FloatVectorOperations::multiply(fft.inOut, fft.window.amplitudeCompensationScale, fftSize); } -void PhaseVocoder::reset() +void PhaseVocoder::resetBetweenBeats() { previousFramePhases.isInitialized = false; analysisFrames.initialized = false; analysisFrames.writePosition = 0; resampler.interpolator.reset(); + loadNextParams(); } void PhaseVocoder::fullReset() { - // TODO why not in reset? - loadNextParams(); - //TODO why not in reset? + // throw away the data on the resampler queue resampler.queue.writePosition = 0; - reset(); + resetBetweenBeats(); } int PhaseVocoder::processSample(const float sampleValue, const bool skipProcessing) { - jassert(static_cast(resampler.queue.writePosition) < resampler.queue.data.size()); resampler.queue.data[resampler.queue.writePosition] = sampleValue; resampler.queue.writePosition += 1; // if the number of samples on the queue is at least the number needed to get the desired output length if (resampler.queue.writePosition > resampler.maxNeedSamples) { - resampleHop(); + resampleHop(skipProcessing); pushResampledHopOnToAnalysisFrameBuffer(); if (analysisFrames.initialized) { diff --git a/Plug-in/Source/PhaseVocoder.h b/Plug-in/Source/PhaseVocoder.h index 1e06fa8..fdc9663 100644 --- a/Plug-in/Source/PhaseVocoder.h +++ b/Plug-in/Source/PhaseVocoder.h @@ -58,7 +58,7 @@ class PhaseVocoder /** * \brief Call this at the beginning of each new beat to reinitialize the phases. */ - void reset(); + void resetBetweenBeats(); /** * \brief Call this at the beginning of playback or if the timeline position jumps around. */ @@ -73,6 +73,7 @@ class PhaseVocoder int processSample(float sampleValue, bool skipProcessing); const float* getFftInOutReadPointer() const { return fft.inOut; } + static int getFftSize() { return fftSize; } //============================================================================== private: @@ -275,22 +276,56 @@ class PhaseVocoder } previousFramePhases; //============================================================================== - void setParams(const float newPitchShiftFactor, const float newPitchShiftFactorCents); + /** + * \brief Set new pitch shift factor and related member variables + * \param newPitchShiftFactor + * \param newPitchShiftFactorCents + */ + void setParams(float newPitchShiftFactor, float newPitchShiftFactorCents); + + /** + * \brief Calculate the maximum number of samples the resampler might need to produce desiredNumOut + * \param desiredNumOut the analysis hop size + * \param oldPitchShiftFactor the previous pitch shift factor + * \return The maximum number of samples the resampler might need + */ int calculateMaximumNeededNumSamples(int desiredNumOut, double oldPitchShiftFactor) const; //============================================================================== - void resampleHop(); + void resampleHop(bool skipProcessing); + void popUsedSamples(int numUsed); + void pushResampledHopOnToAnalysisFrameBuffer(); + //============================================================================== void scaleAnalysisFrame(); + void copyAnalysisFrameToFftInOut(); + void scaleAllFrequencyBinsAndStorePhaseBuffers(); + std::complex scaleFrequencyBin(int k, float mag, float currentPhase, float oldPhase); + + /** + * \brief if this is the first frame we've processed of this beat, just store the phases without scaling them + */ void storePhasesInBuffer(); + //============================================================================== + /** + * \brief Calculate the phase of a complex frequency bin + * \return The phase + */ static float complexBinPhase(const std::complex& complexBin); + + /** + * \brief Calculate the magnitude of a complex frequency bin + * \return The magnitude + */ static float complexBinMag(const std::complex& complexBin); + static float calculateFrequencyDeviation(float oldPhase, float currentPhase, int k); + static float wrapPhase(float phaseIn); //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PhaseVocoder) diff --git a/Plug-in/Source/PluginProcessor.cpp b/Plug-in/Source/PluginProcessor.cpp index 18378e9..e6d6087 100644 --- a/Plug-in/Source/PluginProcessor.cpp +++ b/Plug-in/Source/PluginProcessor.cpp @@ -276,7 +276,7 @@ void GamelanizerAudioProcessor::processSamples(const int64 numSamples, const flo const bool skipProcessing) { #if MeasurePerformance - const auto startingTime = performanceMeasures.getNewStartingTime(); + const auto startingTime = PerformanceMeasures::getNewStartingTime(); #endif for (auto sample = 0; sample < numSamples; ++sample) { @@ -390,7 +390,7 @@ void GamelanizerAudioProcessor::processSample(const int level, const float sampl //============================================================================== void GamelanizerAudioProcessor::addSamplesToLevelsOutputBuffer(const int level, const float* samples, - const int nSamples) + const int nSamples) { const auto power = levelPowers[level]; const auto noteLength = static_cast(beatSampleInfo.getBeatSampleLength()) / power; @@ -497,7 +497,7 @@ void GamelanizerAudioProcessor::nextBeat() //reset all phase vocoders for (auto& pv : pvs) - pv.reset(); + pv.resetBetweenBeats(); if (beatSampleInfo.isBeatB()) moveWritePosOnBeatB(); @@ -645,6 +645,9 @@ void GamelanizerAudioProcessor::setStateInformation(const void* data, const int // because #setStateInformation will be called after #gamelanizerParametersVtsHelper is constructed, // we need to force the smoothers to be at the correct values again gamelanizerParametersVtsHelper.instantlyUpdateSmoothers(); + // and then queue those new pitch shift parameters so they'll take effect at the beginning of playback + for (auto level = 0; level < GamelanizerConstants::maxLevels; ++level) + queuePhaseVocoderNextParams(level); } } } diff --git a/Plug-in/Source/PluginProcessor.h b/Plug-in/Source/PluginProcessor.h index d74c0ac..e404285 100644 --- a/Plug-in/Source/PluginProcessor.h +++ b/Plug-in/Source/PluginProcessor.h @@ -44,7 +44,15 @@ class GamelanizerAudioProcessor : public AudioProcessor ~GamelanizerAudioProcessor(); //============================================================================== + /** + * \brief initialize the time and pitch shift settings of all the subdivision levels + */ void initAllPhaseVocoders(); + + /** + * \brief queue the current pitch shift parameter for the subdivision level to change to when it's ready + * \param level the subdivision level + */ void queuePhaseVocoderNextParams(int level); //==============================================================================