From bb28a48dde2d98cbfa95b9b20975c24c0a8aa46b Mon Sep 17 00:00:00 2001 From: Sophia Poirier <2997196+sophiapoirier@users.noreply.github.com> Date: Sat, 1 Oct 2022 18:15:08 -0700 Subject: [PATCH 1/4] Buffer Override: fix lack of pixel quantization due to implicit floating point to integer conversion (as well as a few more implicit conversions) --- bufferoverride/gui/bufferoverrideview.cpp | 12 ++++++------ dfx-library/dfx-au-utilities-preset-files.m | 6 +++--- dfx-library/dfxsettings.cpp | 6 +++--- dfxgui/dfxguislider.cpp | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bufferoverride/gui/bufferoverrideview.cpp b/bufferoverride/gui/bufferoverrideview.cpp index 8d97a8bf..d93693fe 100644 --- a/bufferoverride/gui/bufferoverrideview.cpp +++ b/bufferoverride/gui/bufferoverrideview.cpp @@ -16,7 +16,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Buffer Override. If not, see . -To contact the author, use the contact form at http://destroyfx.org/ +To contact the author, use the contact form at http://destroyfx.org ------------------------------------------------------------------------*/ #include "bufferoverrideview.h" @@ -177,20 +177,20 @@ void BufferOverrideView::draw(VSTGUI::CDrawContext *ctx) { // Place boxes in float space but round to integer coordinates // since we leave some thin pixel borders. for (CCoord xpos = MARGIN_HORIZ; xpos < width; xpos += majorbox_width) { - const int ixpos = std::lround(xpos); - const int majw = std::lround(xpos + majorbox_width) - xpos; + const auto ixpos = std::lround(xpos); + const auto majw = std::lround(xpos + majorbox_width - xpos); DrawBox(ixpos, MARGIN_TOP, majw, majorbox_height, color_lite); auto minorbox_color = color_lite; for (CCoord nxpos = 2.0; nxpos < majw - 2; nxpos += minorbox_width) { - const int inxpos = std::lround(nxpos); - const int minw = std::lround(nxpos + minorbox_width) - inxpos; + const auto inxpos = std::lround(nxpos); + const auto minw = std::lround(nxpos + minorbox_width) - inxpos; // Clip the width of the last minibuffer. // We can reuse the right margin by overlapping it with the // space after the last box, so just majw - 1. - const int w = std::min(minw, (majw - 1) - inxpos); + const auto w = std::min(minw, (majw - 1) - inxpos); DrawFilledBox(ixpos + inxpos, MARGIN_TOP + 2, // leave space between boxes diff --git a/dfx-library/dfx-au-utilities-preset-files.m b/dfx-library/dfx-au-utilities-preset-files.m index a8fea09a..7ce906af 100644 --- a/dfx-library/dfx-au-utilities-preset-files.m +++ b/dfx-library/dfx-au-utilities-preset-files.m @@ -1,7 +1,7 @@ /* Destroy FX AU Utilities is a collection of helpful utility functions for creating and hosting Audio Unit plugins. - Copyright (C) 2003-2021 Sophia Poirier + Copyright (C) 2003-2022 Sophia Poirier All rights reserved. Redistribution and use in source and binary forms, with or without @@ -942,12 +942,12 @@ OSStatus WritePropertyListToXMLFile(CFPropertyListRef inPropertyList, CFURLRef i { errorCode = CFErrorGetCode(errorRef); CFRelease(errorRef); - return errorCode; + return (OSStatus)errorCode; } return kDFX_coreFoundationUnknownErr; } - return errorCode; + return (OSStatus)errorCode; } //----------------------------------------------------------------------------- diff --git a/dfx-library/dfxsettings.cpp b/dfx-library/dfxsettings.cpp index e0fe7af6..c79881c3 100644 --- a/dfx-library/dfxsettings.cpp +++ b/dfx-library/dfxsettings.cpp @@ -808,11 +808,11 @@ bool DfxSettings::restoreMidiAssignmentsFromDictionary(CFDictionaryRef inDiction if (assignmentCFDictionary) { auto const parameterID_optional = DFX_GetNumberFromCFDictionary_i(assignmentCFDictionary, kDfxSettings_ParameterIDKey); - if (!parameterID_optional) + if (!parameterID_optional || (*parameterID_optional < 0)) { continue; } - auto const parameterID = getParameterIndexFromMap(*parameterID_optional); + auto const parameterID = getParameterIndexFromMap(static_cast(*parameterID_optional)); if (!isValidParameterID(parameterID)) { continue; @@ -1394,7 +1394,7 @@ dfx::ParameterID DfxSettings::getParameterIndexFromMap(dfx::ParameterID inParame auto const foundID = std::find(inSearchIDs.begin(), inSearchIDs.end(), inParameterID); if (foundID != inSearchIDs.end()) { - return std::distance(inSearchIDs.begin(), foundID); + return dfx::math::ToIndex(std::distance(inSearchIDs.begin(), foundID)); } return dfx::kParameterID_Invalid; } diff --git a/dfxgui/dfxguislider.cpp b/dfxgui/dfxguislider.cpp index 00cacaf5..dc8ba581 100644 --- a/dfxgui/dfxguislider.cpp +++ b/dfxgui/dfxguislider.cpp @@ -827,7 +827,7 @@ DGAnimation::DGAnimation(DfxGuiEditor* inOwnerEditor, DGImage* inAnimationImage, size_t inNumAnimationFrames) : DGControl(inRegion, inOwnerEditor, dfx::ParameterID_ToVST(inParameterID), - inNumAnimationFrames, inRegion.getHeight(), inAnimationImage) + static_cast(inNumAnimationFrames), inRegion.getHeight(), inAnimationImage) { assert(inNumAnimationFrames > 0); From 2b8886253729fec87dd0defa3c4f80f8b63f02b8 Mon Sep 17 00:00:00 2001 From: Sophia Poirier <2997196+sophiapoirier@users.noreply.github.com> Date: Sun, 2 Oct 2022 19:44:46 -0700 Subject: [PATCH 2/4] use size_t for array size arguments to dfx::math::InterpolateHermite and IIRFilter::processToCacheH2/3/4 --- dfx-library/dfxmath.h | 29 ++++--- dfx-library/iirfilter.h | 15 +++- scrubby/scrubbyprocess.cpp | 2 +- transverb/transverbprocess.cpp | 10 +-- turntablist/turntablist.cpp | 83 +++---------------- turntablist/turntablist.h | 147 ++++++++++++++++----------------- 6 files changed, 121 insertions(+), 165 deletions(-) diff --git a/dfx-library/dfxmath.h b/dfx-library/dfxmath.h index 1d5862b1..a35680e1 100644 --- a/dfx-library/dfxmath.h +++ b/dfx-library/dfxmath.h @@ -18,7 +18,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Destroy FX Library. If not, see . -To contact the author, use the contact form at http://destroyfx.org/ +To contact the author, use the contact form at http://destroyfx.org Destroy FX is a sovereign entity comprised of Sophia Poirier and Tom Murphy 7. This is our math and numerics shit. @@ -153,15 +153,19 @@ constexpr T FrequencyScalarBySemitones(T inSemitones) } //----------------------------------------------------------------------------- -constexpr float InterpolateHermite(float const* inData, double inAddress, long inBufferSize) +constexpr float InterpolateHermite(float const* inData, double inAddress, size_t inBufferSize) { - auto const pos = static_cast(inAddress); + assert(inAddress >= 0.); + assert(inBufferSize > 0); + + auto const pos = static_cast(inAddress); + assert(pos < inBufferSize); auto const posFract = static_cast(inAddress - static_cast(pos)); #if 0 // XXX test performance using fewer variables/registers - long const posMinus1 = (pos == 0) ? (inBufferSize - 1) : (pos - 1); - long const posPlus1 = (pos + 1) % inBufferSize; - long const posPlus2 = (pos + 2) % inBufferSize; + size_t const posMinus1 = (pos == 0) ? (inBufferSize - 1) : (pos - 1); + size_t const posPlus1 = (pos + 1) % inBufferSize; + size_t const posPlus2 = (pos + 2) % inBufferSize; return (((((((3.0f * (inData[pos] - inData[posPlus1])) - inData[posMinus1] + inData[posPlus2]) * 0.5f) * posFract) + ((2.0f * inData[posPlus1]) + inData[posMinus1] - (2.5f * inData[pos]) - (inData[posPlus2] * 0.5f))) * @@ -179,9 +183,9 @@ constexpr float InterpolateHermite(float const* inData, double inAddress, long i return ((((a * posFract) + b) * posFract + c) * posFract) + inData[pos]; #else - long const posMinus1 = (pos == 0) ? (inBufferSize - 1) : (pos - 1); - long const posPlus1 = (pos + 1) % inBufferSize; - long const posPlus2 = (pos + 2) % inBufferSize; + size_t const posMinus1 = (pos == 0) ? (inBufferSize - 1) : (pos - 1); + size_t const posPlus1 = (pos + 1) % inBufferSize; + size_t const posPlus2 = (pos + 2) % inBufferSize; float const a = ((3.0f * (inData[pos] - inData[posPlus1])) - inData[posMinus1] + inData[posPlus2]) * 0.5f; float const b = (2.0f * inData[posPlus1]) + inData[posMinus1] - (2.5f * inData[pos]) - (inData[posPlus2] * 0.5f); @@ -192,9 +196,12 @@ constexpr float InterpolateHermite(float const* inData, double inAddress, long i } //----------------------------------------------------------------------------- -constexpr float InterpolateHermite_NoWrap(float const* inData, double inAddress, long inBufferSize) +constexpr float InterpolateHermite_NoWrap(float const* inData, double inAddress, size_t inBufferSize) { - auto const pos = static_cast(inAddress); + assert(inAddress >= 0.); + + auto const pos = static_cast(inAddress); + assert(pos < inBufferSize); auto const posFract = static_cast(inAddress - static_cast(pos)); float const dataPosMinus1 = (pos == 0) ? 0.0f : inData[pos - 1]; diff --git a/dfx-library/iirfilter.h b/dfx-library/iirfilter.h index 51951ce3..054b2292 100644 --- a/dfx-library/iirfilter.h +++ b/dfx-library/iirfilter.h @@ -18,7 +18,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Destroy FX Library. If not, see . -To contact the author, use the contact form at http://destroyfx.org/ +To contact the author, use the contact form at http://destroyfx.org Destroy FX is a sovereign entity comprised of Sophia Poirier and Tom Murphy 7. Welcome to our Infinite Impulse Response filter. @@ -28,6 +28,7 @@ Welcome to our Infinite Impulse Response filter. #include +#include #include #include "dfxmath.h" @@ -131,8 +132,10 @@ class IIRFilter mPrevIn = inSample; } - void processToCacheH2(float * inAudio, long inPos, long inBufferSize) + void processToCacheH2(float const* inAudio, size_t inPos, size_t inBufferSize) { + assert(inBufferSize > 0); + assert(inPos < inBufferSize); auto const in0 = inAudio[inPos]; auto const in1 = inAudio[(inPos + 1) % inBufferSize]; @@ -154,8 +157,10 @@ class IIRFilter mPrevIn = in1; } - void processToCacheH3(float * inAudio, long inPos, long inBufferSize) + void processToCacheH3(float const* inAudio, size_t inPos, size_t inBufferSize) { + assert(inBufferSize > 0); + assert(inPos < inBufferSize); auto const in0 = inAudio[inPos]; auto const in1 = inAudio[(inPos + 1) % inBufferSize]; auto const in2 = inAudio[(inPos + 2) % inBufferSize]; @@ -178,8 +183,10 @@ class IIRFilter mPrevIn = in2; } - void processToCacheH4(float * inAudio, long inPos, long inBufferSize) + void processToCacheH4(float const* inAudio, size_t inPos, size_t inBufferSize) { + assert(inBufferSize > 0); + assert(inPos < inBufferSize); auto const in0 = inAudio[inPos]; auto const in1 = inAudio[(inPos + 1) % inBufferSize]; auto const in2 = inAudio[(inPos + 2) % inBufferSize]; diff --git a/scrubby/scrubbyprocess.cpp b/scrubby/scrubbyprocess.cpp index cb298da2..d65a1c6c 100644 --- a/scrubby/scrubbyprocess.cpp +++ b/scrubby/scrubbyprocess.cpp @@ -446,7 +446,7 @@ void Scrubby::processaudio(float const* const* inAudio, float* const* outAudio, for (size_t ch = 0; ch < numChannels; ch++) { auto const inputValue = (ch < getnuminputs()) ? inAudio[ch][sampleIndex] : inputValue_firstChannel; - auto outputValue = dfx::math::InterpolateHermite(mAudioBuffers[ch].data(), mReadPos[ch], mMaxAudioBufferSize); + auto outputValue = dfx::math::InterpolateHermite(mAudioBuffers[ch].data(), mReadPos[ch], dfx::math::ToUnsigned(mMaxAudioBufferSize)); outputValue = mHighpassFilters[ch].process(outputValue); outAudio[ch][sampleIndex] = (inputValue * mInputGain.getValue()) + (outputValue * mOutputGain.getValue()); } diff --git a/transverb/transverbprocess.cpp b/transverb/transverbprocess.cpp index 871c0aaa..7f2f06c8 100644 --- a/transverb/transverbprocess.cpp +++ b/transverb/transverbprocess.cpp @@ -16,7 +16,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Transverb. If not, see . -To contact the author, use the contact form at http://destroyfx.org/ +To contact the author, use the contact form at http://destroyfx.org ------------------------------------------------------------------------*/ #include "transverb.h" @@ -214,17 +214,17 @@ void TransverbDSP::process(float const* inAudio, float* outAudio, size_t numSamp lowpasscount++; break; case 2: - heads[h].filter.processToCacheH2(heads[h].buf.data(), lowpasspos[h], bsize); + heads[h].filter.processToCacheH2(heads[h].buf.data(), dfx::math::ToUnsigned(lowpasspos[h]), dfx::math::ToUnsigned(bsize)); lowpasspos[h] = (lowpasspos[h] + 2) % bsize; lowpasscount += 2; break; case 3: - heads[h].filter.processToCacheH3(heads[h].buf.data(), lowpasspos[h], bsize); + heads[h].filter.processToCacheH3(heads[h].buf.data(), dfx::math::ToUnsigned(lowpasspos[h]), dfx::math::ToUnsigned(bsize)); lowpasspos[h] = (lowpasspos[h] + 3) % bsize; lowpasscount += 3; break; default: - heads[h].filter.processToCacheH4(heads[h].buf.data(), lowpasspos[h], bsize); + heads[h].filter.processToCacheH4(heads[h].buf.data(), dfx::math::ToUnsigned(lowpasspos[h]), dfx::math::ToUnsigned(bsize)); lowpasspos[h] = (lowpasspos[h] + 4) % bsize; lowpasscount += 4; break; @@ -311,7 +311,7 @@ void TransverbDSP::process(float const* inAudio, float* outAudio, size_t numSamp delayvals[h] = interpolateLinear(buf.data(), heads[h].read, bsize); break; case kQualityMode_UltraHiFi: - delayvals[h] = dfx::math::InterpolateHermite(buf.data(), heads[h].read, bsize); + delayvals[h] = dfx::math::InterpolateHermite(buf.data(), heads[h].read, dfx::math::ToUnsigned(bsize)); break; } } diff --git a/turntablist/turntablist.cpp b/turntablist/turntablist.cpp index 47b1b8ef..6485950c 100644 --- a/turntablist/turntablist.cpp +++ b/turntablist/turntablist.cpp @@ -123,48 +123,15 @@ Turntablist::Turntablist(TARGET_API_BASE_INSTANCE_TYPE inInstance) setSupportedLogicNodeOperationMode(0); #endif - m_nNumChannels = 0; - m_nNumSamples = 0; - m_nSampleRate = 0; - m_fSampleRate = 0.0; - - - m_bPlayedReverse = false; - m_bPlay = false; - - m_fPosition = 0.0; - m_fPosOffset = 0.0; - m_fNumSamples = 0.0; m_fLastScratchAmount = getparameter_f(kParam_ScratchAmount); m_fPitchBend = k_fScratchAmountMiddlePoint; - m_bPitchBendSet = false; - m_bScratching = false; - m_bWasScratching = false; - m_bPlayForward = true; m_nScratchSubMode = 1; // speed based from scrub scratch mode - m_nScratchDelay = 0; - m_nRootKey = static_cast(getparameter_i(kParam_RootKey)); m_nCurrentNote = m_nRootKey; m_nCurrentVelocity = 0x7F; - - m_bAudioFileHasBeenLoaded = false; - m_bScratchStop = false; - - memset(&m_fsAudioFile, 0, sizeof(m_fsAudioFile)); - - m_fScratchVolumeModifier = 0.0f; - - m_bScratchAmountSet = false; - m_fDesiredScratchRate = 0.0; - - m_nScratchInterval = 0; - - m_fDesiredPosition = 0.0; - m_fPrevDesiredPosition = 0.0; } //----------------------------------------------------------------------------------------- @@ -773,7 +740,7 @@ OSStatus Turntablist::loadAudioFile(FSRef const& inFileRef) m_nNumChannels = clientStreamFormat.mChannelsPerFrame; m_nSampleRate = clientStreamFormat.mSampleRate; - m_nNumSamples = static_cast(audioFileNumFrames); + m_nNumSamples = dfx::math::ToUnsigned(audioFileNumFrames); m_fPlaySampleRate = static_cast(m_nSampleRate); m_fSampleRate = static_cast(m_nSampleRate); @@ -820,9 +787,9 @@ OSStatus Turntablist::loadAudioFile(FSRef const& inFileRef) std::unique_lock guard(m_AudioFileLock); - m_nNumChannels = sfInfo.channels; + m_nNumChannels = dfx::math::ToUnsigned(sfInfo.channels); m_nSampleRate = sfInfo.samplerate; - m_nNumSamples = static_cast(sfInfo.frames); + m_nNumSamples = dfx::math::ToUnsigned(sfInfo.frames); m_fPlaySampleRate = static_cast(m_nSampleRate); m_fSampleRate = static_cast(m_nSampleRate); @@ -953,17 +920,11 @@ void Turntablist::processaudio(float const* const* /*inAudio*/, float* const* ou // speed mode if (m_fTinyScratchAdjust > 0.0) // positive { - if (m_fPlaySampleRate > m_fDesiredScratchRate2) - { - m_fPlaySampleRate = m_fDesiredScratchRate2; - } + m_fPlaySampleRate = std::min(m_fPlaySampleRate, m_fDesiredScratchRate2); } else // negative { - if (m_fPlaySampleRate < m_fDesiredScratchRate2) - { - m_fPlaySampleRate = m_fDesiredScratchRate2; - } + m_fPlaySampleRate = std::max(m_fPlaySampleRate, m_fDesiredScratchRate2); } } } @@ -974,30 +935,18 @@ void Turntablist::processaudio(float const* const* /*inAudio*/, float* const* ou { if (m_fPlaySampleRate > 0.0) // too high, spin down { - m_fPlaySampleRate -= m_fUsedSpinDownSpeed; // adjust - if (m_fPlaySampleRate < 0.0) // too low so we past it - { - m_fPlaySampleRate = 0.0; // fix it - } + m_fPlaySampleRate = std::max(m_fPlaySampleRate - m_fUsedSpinDownSpeed, 0.); } } else // power on - spin up { if (m_fPlaySampleRate < m_fDesiredPitch) // too low so bring up { - m_fPlaySampleRate += m_fUsedSpinUpSpeed; // adjust - if (m_fPlaySampleRate > m_fDesiredPitch) // too high so we past it - { - m_fPlaySampleRate = m_fDesiredPitch; // fix it - } + m_fPlaySampleRate = std::min(m_fPlaySampleRate + m_fUsedSpinUpSpeed, m_fDesiredPitch); } else if (m_fPlaySampleRate > m_fDesiredPitch) // too high so bring down { - m_fPlaySampleRate -= m_fUsedSpinUpSpeed; // adjust - if (m_fPlaySampleRate < m_fDesiredPitch) // too low so we past it - { - m_fPlaySampleRate = m_fDesiredPitch; // fix it - } + m_fPlaySampleRate = std::max(m_fPlaySampleRate - m_fUsedSpinUpSpeed, m_fDesiredPitch); } } } @@ -1106,8 +1055,8 @@ void Turntablist::processaudio(float const* const* /*inAudio*/, float* const* ou #ifdef LINEAR_INTERPOLATION float const floating_part = m_fPosition - static_cast(static_cast(m_fPosition)); - long const big_part1 = static_cast(m_fPosition); - long big_part2 = big_part1 + 1; + auto const big_part1 = static_cast(m_fPosition); + auto big_part2 = big_part1 + 1; if (big_part2 > m_nNumSamples) { big_part2 = 0; @@ -1303,6 +1252,7 @@ void Turntablist::processScratch(bool inSetParameter) if (m_nScratchInterval == 0) { m_fPosition = m_fDesiredPosition; + assert(m_fPosition >= 0.); m_fTinyScratchAdjust = 0.0; } else @@ -1401,16 +1351,9 @@ void Turntablist::processScratch(bool inSetParameter) m_fLastScratchAmount = m_fScratchAmount; } - m_fScratchVolumeModifier = m_fPlaySampleRate / m_fSampleRate; - if (m_fScratchVolumeModifier > 1.5f) - { - m_fScratchVolumeModifier = 1.5f; - } + m_fScratchVolumeModifier = std::min(m_fPlaySampleRate / m_fSampleRate, 1.5); - if (!m_bScratching) - { - m_bScratching = true; - } + m_bScratching = true; if (m_bScratchStop) { diff --git a/turntablist/turntablist.h b/turntablist/turntablist.h index 35504926..13cb32ee 100644 --- a/turntablist/turntablist.h +++ b/turntablist/turntablist.h @@ -109,18 +109,6 @@ enum : dfx::ParameterID kNumParameters }; -enum : UInt32 -{ - kParamGroup_BaseID = kAudioUnitClumpID_System + 1, - kParamGroup_Scratching = kParamGroup_BaseID, - kParamGroup_Playback, - kParamGroup_Power, - kParamGroup_Pitch -#ifdef INCLUDE_SILLY_OUTPUT_PARAMETERS - , kParamGroup_Output -#endif -}; - enum : dfx::PropertyID { kTurntablistProperty_Play = dfx::kPluginProperty_EndOfList, @@ -159,6 +147,18 @@ class Turntablist final : public DfxPlugin UInt32 inDesiredNameLength, CFStringRef* outClumpName) override; private: + enum : UInt32 + { + kParamGroup_BaseID = kAudioUnitClumpID_System + 1, + kParamGroup_Scratching = kParamGroup_BaseID, + kParamGroup_Playback, + kParamGroup_Power, + kParamGroup_Pitch + #ifdef INCLUDE_SILLY_OUTPUT_PARAMETERS + , kParamGroup_Output + #endif + }; + OSStatus loadAudioFile(FSRef const& inFileRef); OSStatus createAudioFileAlias(AliasHandle* outAlias, Size* outDataSize = nullptr); OSStatus resolveAudioFileAlias(AliasHandle const inAlias); @@ -179,11 +179,11 @@ class Turntablist final : public DfxPlugin OSStatus PostNotification_AudioFileNotFound(CFStringRef inFileName); - int m_nCurrentNote; - int m_nCurrentVelocity; - bool m_bNoteIsOn; + int m_nCurrentNote = 0; + int m_nCurrentVelocity = 0; + bool m_bNoteIsOn = false; - FSRef m_fsAudioFile; + FSRef m_fsAudioFile {}; #ifdef USE_LIBSNDFILE std::vector m_fBuffer; @@ -194,98 +194,97 @@ class Turntablist final : public DfxPlugin #endif // our 32-bit floating point audio info - int m_nNumChannels; // 1=mono, 2=stereo, 0=yomama - int m_nNumSamples; // number of samples per channel - int m_nSampleRate; - double m_fSampleRate; + size_t m_nNumChannels = 0; // 1=mono, 2=stereo, 0=yomama + size_t m_nNumSamples = 0; // number of samples per channel + int m_nSampleRate = 0; + double m_fSampleRate = 0.; // optional - double m_fBasePitch; - double m_fPlaySampleRate; + double m_fBasePitch = 0.; + double m_fPlaySampleRate = 0.; // switches - bool m_bPower; // on/off - bool m_bNotePowerTrack; // scratch mode on/off + bool m_bPower = false; // on/off + bool m_bNotePowerTrack = false; // scratch mode on/off // TODO: use dfx::SmoothedValue for continuous parameters - double m_fScratchAmount; - double m_fLastScratchAmount; + double m_fScratchAmount = 0.; + double m_fLastScratchAmount = 0.; #ifdef INCLUDE_SILLY_OUTPUT_PARAMETERS - bool m_bMute; // on/off + bool m_bMute = false; // on/off #endif - double m_fPitchShift; - long m_nDirection; + double m_fPitchShift = 0.; + long m_nDirection = 0; // float m_fScratchSpeed; - double m_fScratchSpeed_scrub, m_fScratchSpeed_spin; + double m_fScratchSpeed_scrub = 0., m_fScratchSpeed_spin = 0.; // modifiers - long m_nNoteMode; // reset/resume - bool m_bLoop; // on/off - double m_fPitchRange; - double m_fSpinDownSpeed; - double m_fSpinUpSpeed; - bool m_bKeyTracking; + long m_nNoteMode = 0; // reset/resume + bool m_bLoop = false; // on/off + double m_fPitchRange = 0.; + double m_fSpinDownSpeed = 0.; + double m_fSpinUpSpeed = 0.; + bool m_bKeyTracking = false; #ifdef INCLUDE_SILLY_OUTPUT_PARAMETERS - float m_fVolume; + float m_fVolume = 0.f; #endif - double m_fUsedSpinDownSpeed; - double m_fUsedSpinUpSpeed; + double m_fUsedSpinDownSpeed = 0.; + double m_fUsedSpinUpSpeed = 0.; - bool m_bPlayedReverse; - int m_nRootKey; + bool m_bPlayedReverse = false; + int m_nRootKey = 0; - double m_fPosition; - double m_fPosOffset; - double m_fNumSamples; + double m_fPosition = 0.; + double m_fPosOffset = 0.; + double m_fNumSamples = 0.; - bool m_bPlay; + bool m_bPlay = false; - bool m_bPitchBendSet; - double m_fPitchBend; - int m_nScratchDir; - bool m_bScratching; - bool m_bWasScratching; - int m_nWasScratchingDir; - int m_nScratchDelay; + bool m_bPitchBendSet = false; + double m_fPitchBend = 0.; + int m_nScratchDir = 0; + bool m_bScratching = false; + bool m_bWasScratching = false; + int m_nWasScratchingDir = 0; - double m_fDesiredPitch; + double m_fDesiredPitch = 0.; - long m_nScratchMode; - long m_nScratchSubMode; + long m_nScratchMode = 0; + long m_nScratchSubMode = 0; - float m_fNoteVolume; // recalculated on note on and volume changes + float m_fNoteVolume = 0.f; // recalculated on note on and volume changes - int m_nPowerIntervalEnd; + int m_nPowerIntervalEnd = 0; - bool m_bPlayForward; + bool m_bPlayForward = true; - double m_fDesiredScratchRate; - int m_nScratchInterval; - int m_nScratchIntervalEnd; - int m_nScratchIntervalEndBase; - bool m_bScratchStop; + double m_fDesiredScratchRate = 0.; + int m_nScratchInterval = 0; + int m_nScratchIntervalEnd = 0; + int m_nScratchIntervalEndBase = 0; + bool m_bScratchStop = false; - int m_nScratchCenter; // center position where scratching starts - double m_fScratchCenter; - double m_fDesiredPosition; - double m_fPrevDesiredPosition; + int m_nScratchCenter = 0; // center position where scratching starts + double m_fScratchCenter = 0.; + double m_fDesiredPosition = 0.; + double m_fPrevDesiredPosition = 0.; - float m_fScratchVolumeModifier; + float m_fScratchVolumeModifier = 0.f; - bool m_bScratchAmountSet; + bool m_bScratchAmountSet = false; - double m_fDesiredScratchRate2; + double m_fDesiredScratchRate2 = 0.; - bool m_bAudioFileHasBeenLoaded; + bool m_bAudioFileHasBeenLoaded = false; - double m_fTinyScratchAdjust; + double m_fTinyScratchAdjust = 0.; // new power variables to do sample accurate powering up/down -// double m_fDesiredPowerRate; -// double m_fTinyPowerAdjust; +// double m_fDesiredPowerRate = 0.; +// double m_fTinyPowerAdjust = 0.; std::mutex m_AudioFileLock; }; From 928e674b7150e548e33a11796bd99588d3b8f9ef Mon Sep 17 00:00:00 2001 From: Sophia Poirier <2997196+sophiapoirier@users.noreply.github.com> Date: Sun, 2 Oct 2022 20:04:06 -0700 Subject: [PATCH 3/4] use size_t for TempoRateTable indices --- bufferoverride/bufferoverrideformalities.cpp | 10 +++++----- dfx-library/dfxplugin.cpp | 7 +++++++ dfx-library/dfxplugin.h | 1 + dfx-library/temporatetable.cpp | 8 ++++---- dfx-library/temporatetable.h | 20 ++++++++++---------- eqsync/eqsync.cpp | 4 ++-- scrubby/scrubby.h | 2 +- scrubby/scrubbyformalities.cpp | 6 +++--- skidder/skidder.h | 2 +- skidder/skidderformalities.cpp | 6 +++--- thrush/thrush.cpp | 6 +++--- 11 files changed, 40 insertions(+), 32 deletions(-) diff --git a/bufferoverride/bufferoverrideformalities.cpp b/bufferoverride/bufferoverrideformalities.cpp index 07ab5969..c789495b 100644 --- a/bufferoverride/bufferoverrideformalities.cpp +++ b/bufferoverride/bufferoverrideformalities.cpp @@ -72,7 +72,7 @@ BufferOverride::BufferOverride(TARGET_API_BASE_INSTANCE_TYPE inInstance) setparametervaluestring(kBufferLFOShape, i, shapeName); } // set the value strings for the sync rate parameters - for (long i = 0; i < mTempoRateTable.getNumRates(); i++) + for (size_t i = 0; i < numTempoRates; i++) { auto const& tempoRateName = mTempoRateTable.getDisplay(i); setparametervaluestring(kBufferSize_Sync, i, tempoRateName); @@ -339,16 +339,16 @@ void BufferOverride::processparameters() { mDivisor = getparameter_f(kDivisor); mBufferSizeMS = getparameter_f(kBufferSize_MS); - mBufferSizeSync = mTempoRateTable.getScalar(getparameter_i(kBufferSize_Sync)); + mBufferSizeSync = mTempoRateTable.getScalar(getparameter_index(kBufferSize_Sync)); mBufferTempoSync = getparameter_b(kBufferTempoSync); mBufferInterrupt = getparameter_b(kBufferInterrupt); mDivisorLFORateHz = getparameter_f(kDivisorLFORate_Hz); - mDivisorLFOTempoRate = mTempoRateTable.getScalar(getparameter_i(kDivisorLFORate_Sync)); + mDivisorLFOTempoRate = mTempoRateTable.getScalar(getparameter_index(kDivisorLFORate_Sync)); mDivisorLFO.setDepth(getparameter_scalar(kDivisorLFODepth)); mDivisorLFO.setShape(getparameter_i(kDivisorLFOShape)); mDivisorLFOTempoSync = getparameter_b(kDivisorLFOTempoSync); mBufferLFORateHz = getparameter_f(kBufferLFORate_Hz); - mBufferLFOTempoRate = mTempoRateTable.getScalar(getparameter_i(kBufferLFORate_Sync)); + mBufferLFOTempoRate = mTempoRateTable.getScalar(getparameter_index(kBufferLFORate_Sync)); mBufferLFO.setDepth(getparameter_scalar(kBufferLFODepth)); mBufferLFO.setShape(getparameter_i(kBufferLFOShape)); mBufferLFOTempoSync = getparameter_b(kBufferLFOTempoSync); @@ -424,7 +424,7 @@ void BufferOverride::updateViewDataCache() if (getparameter_b(kBufferTempoSync) && (tempoBPS > 0.)) { - viewData.mPreLFO.mForcedBufferSeconds = 1. / (tempoBPS * mTempoRateTable.getScalar(getparameter_i(kBufferSize_Sync))); + viewData.mPreLFO.mForcedBufferSeconds = 1. / (tempoBPS * mTempoRateTable.getScalar(getparameter_index(kBufferSize_Sync))); } else { diff --git a/dfx-library/dfxplugin.cpp b/dfx-library/dfxplugin.cpp index 9de35edc..e3cd1234 100644 --- a/dfx-library/dfxplugin.cpp +++ b/dfx-library/dfxplugin.cpp @@ -689,6 +689,13 @@ DfxParam::Value DfxPlugin::getparameter(dfx::ParameterID inParameterID) const return {}; } +size_t DfxPlugin::getparameter_index(dfx::ParameterID inParameterID) const +{ + auto const value = getparameter_i(inParameterID); + assert(value >= 0); + return dfx::math::ToIndex(value); +} + //----------------------------------------------------------------------------- // return a (hopefully) 0 to 1 scalar version of the parameter's current value double DfxPlugin::getparameter_scalar(dfx::ParameterID inParameterID) const diff --git a/dfx-library/dfxplugin.h b/dfx-library/dfxplugin.h index bc11fb37..ce461ada 100644 --- a/dfx-library/dfxplugin.h +++ b/dfx-library/dfxplugin.h @@ -353,6 +353,7 @@ class DfxPlugin : public TARGET_API_BASE_CLASS { return parameterisvalid(inParameterID) ? mParameters[inParameterID].get_gen() : 0.0; } + size_t getparameter_index(dfx::ParameterID inParameterID) const; // return a (hopefully) 0 to 1 scalar version of the parameter's current value double getparameter_scalar(dfx::ParameterID inParameterID) const; std::optional getparameterifchanged_f(dfx::ParameterID inParameterID) const; diff --git a/dfx-library/temporatetable.cpp b/dfx-library/temporatetable.cpp index 6cb75493..a19a862e 100644 --- a/dfx-library/temporatetable.cpp +++ b/dfx-library/temporatetable.cpp @@ -18,7 +18,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Destroy FX Library. If not, see . -To contact the author, use the contact form at http://destroyfx.org/ +To contact the author, use the contact form at http://destroyfx.org Destroy FX is a sovereign entity comprised of Sophia Poirier and Tom Murphy 7. Welcome to our tempo rate table. @@ -91,11 +91,11 @@ dfx::TempoRateTable::TempoRateTable(Rates inRates) //----------------------------------------------------------------------------- // given a tempo rate value, return the index of the tempo rate // that is closest to that requested value -long dfx::TempoRateTable::getNearestTempoRateIndex(double inTempoRateValue) const +size_t dfx::TempoRateTable::getNearestTempoRateIndex(double inTempoRateValue) const { auto bestDiff = mScalars.back(); - long bestIndex = 0; - for (long i = 0; i < getNumRates(); i++) + size_t bestIndex = 0; + for (size_t i = 0; i < getNumRates(); i++) { auto const diff = std::fabs(inTempoRateValue - mScalars[i]); if (diff < bestDiff) diff --git a/dfx-library/temporatetable.h b/dfx-library/temporatetable.h index 6dda648a..d157e64a 100644 --- a/dfx-library/temporatetable.h +++ b/dfx-library/temporatetable.h @@ -18,7 +18,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Destroy FX Library. If not, see . -To contact the author, use the contact form at http://destroyfx.org/ +To contact the author, use the contact form at http://destroyfx.org Destroy FX is a sovereign entity comprised of Sophia Poirier and Tom Murphy 7. Welcome to our tempo rate table. @@ -50,24 +50,24 @@ class TempoRateTable explicit TempoRateTable(Rates inRates = Rates::Normal); - double getScalar(long inIndex) const + double getScalar(size_t inIndex) const { - return mScalars[safeIndex(inIndex)]; + return mScalars[boundedIndex(inIndex)]; } - std::string const& getDisplay(long inIndex) const + std::string const& getDisplay(size_t inIndex) const { - return mDisplays[safeIndex(inIndex)]; + return mDisplays[boundedIndex(inIndex)]; } - long getNumRates() const noexcept + auto getNumRates() const noexcept { - return static_cast(mScalars.size()); + return mScalars.size(); } - long getNearestTempoRateIndex(double inTempoRateValue) const; + size_t getNearestTempoRateIndex(double inTempoRateValue) const; private: - size_t safeIndex(long inIndex) const noexcept + size_t boundedIndex(size_t inIndex) const noexcept { - return static_cast(std::clamp(inIndex, 0L, getNumRates() - 1)); + return std::min(inIndex, getNumRates() - 1); } std::vector mScalars; diff --git a/eqsync/eqsync.cpp b/eqsync/eqsync.cpp index ab6d5aef..69763b85 100644 --- a/eqsync/eqsync.cpp +++ b/eqsync/eqsync.cpp @@ -53,7 +53,7 @@ EQSync::EQSync(TARGET_API_BASE_INSTANCE_TYPE inInstance) initparameter_f(kB2, {"b2"}, 0.5, 0.5, 0.0, 1.0, DfxParam::Unit::Generic); // set the value strings for the sync rate parameters - for (long i = 0; i < numTempoRates; i++) + for (size_t i = 0; i < numTempoRates; i++) { setparametervaluestring(kRate_Sync, i, mTempoRateTable.getDisplay(i)); } @@ -107,7 +107,7 @@ void EQSync::reset() //----------------------------------------------------------------------------- void EQSync::processparameters() { - mRate = mTempoRateTable.getScalar(getparameter_i(kRate_Sync)); + mRate = mTempoRateTable.getScalar(getparameter_index(kRate_Sync)); mSmooth = getparameter_scalar(kSmooth); mUserTempo = getparameter_f(kTempo); mUseHostTempo = getparameter_b(kTempoAuto); diff --git a/scrubby/scrubby.h b/scrubby/scrubby.h index a092382a..b96c042f 100644 --- a/scrubby/scrubby.h +++ b/scrubby/scrubby.h @@ -129,7 +129,7 @@ class Scrubby final : public DfxPlugin // the parameters double mSeekRangeSeconds = 0.0, mSeekDur = 0.0, mSeekDurRandMin = 0.0; double mSeekRateHz = 0.0, mSeekRateSync = 0.0; - long mSeekRateIndex = 0, mSeekRateRandMinIndex = 0; + size_t mSeekRateIndex = 0, mSeekRateRandMinIndex = 0; double mUserTempo = 0.0; long mSpeedMode = kSpeedMode_Robot, mOctaveMin = 0, mOctaveMax = 0; bool mFreeze = false, mSplitChannels = false, mPitchConstraint = false, mTempoSync = false, mUseHostTempo = false; diff --git a/scrubby/scrubbyformalities.cpp b/scrubby/scrubbyformalities.cpp index c6a7bb24..d721b98d 100644 --- a/scrubby/scrubbyformalities.cpp +++ b/scrubby/scrubbyformalities.cpp @@ -81,7 +81,7 @@ Scrubby::Scrubby(TARGET_API_BASE_INSTANCE_TYPE inInstance) setparameterenforcevaluelimits(kPredelay, true); // set the value strings for the sync rate parameters - for (long i = 0; i < numTempoRates; i++) + for (size_t i = 0; i < numTempoRates; i++) { auto const& tempoRateName = mTempoRateTable.getDisplay(i); setparametervaluestring(kSeekRate_Sync, i, tempoRateName); @@ -401,10 +401,10 @@ void Scrubby::processparameters() mSeekRangeSeconds = getparameter_f(kSeekRange) * 0.001; mFreeze = getparameter_b(kFreeze); mSeekRateHz = getparameter_f(kSeekRate_Hz); - mSeekRateIndex = getparameter_i(kSeekRate_Sync); + mSeekRateIndex = getparameter_index(kSeekRate_Sync); mSeekRateSync = mTempoRateTable.getScalar(mSeekRateIndex); auto const seekRateRandMinHz = getparameter_f(kSeekRateRandMin_Hz); - mSeekRateRandMinIndex = getparameter_i(kSeekRateRandMin_Sync); + mSeekRateRandMinIndex = getparameter_index(kSeekRateRandMin_Sync); auto const seekRateRandMinSync = mTempoRateTable.getScalar(mSeekRateRandMinIndex); mTempoSync = getparameter_b(kTempoSync); mSeekDur = getparameter_scalar(kSeekDur); diff --git a/skidder/skidder.h b/skidder/skidder.h index 7afc7c75..e8970e11 100644 --- a/skidder/skidder.h +++ b/skidder/skidder.h @@ -135,7 +135,7 @@ class Skidder final : public DfxPlugin // the parameters double mRate_Hz = 1., mRate_Sync = 1.; float mPulsewidth = 0.f, mPulsewidthRandMin = 0.f; - long mRateIndex = 0, mRateRandMinIndex = 0; + size_t mRateIndex = 0, mRateRandMinIndex = 0; float mPanWidth = 0.0f, mFloor = 0.0f; dfx::SmoothedValue mNoise; double mSlopeSeconds = 0., mUserTempo = 1.; diff --git a/skidder/skidderformalities.cpp b/skidder/skidderformalities.cpp index 39623ae0..f1a6a467 100644 --- a/skidder/skidderformalities.cpp +++ b/skidder/skidderformalities.cpp @@ -59,7 +59,7 @@ Skidder::Skidder(TARGET_API_BASE_INSTANCE_TYPE inInstance) setparameterenforcevaluelimits(kCrossoverFrequency, true); // set the value strings for the sync rate parameters - for (long i = 0; i < mTempoRateTable.getNumRates(); i++) + for (size_t i = 0; i < numTempoRates; i++) { auto const& tempoRateName = mTempoRateTable.getDisplay(i); setparametervaluestring(kRate_Sync, i, tempoRateName); @@ -152,10 +152,10 @@ void Skidder::reset() void Skidder::processparameters() { mRate_Hz = getparameter_f(kRate_Hz); - mRateIndex = getparameter_i(kRate_Sync); + mRateIndex = getparameter_index(kRate_Sync); mRate_Sync = mTempoRateTable.getScalar(mRateIndex); auto const rateRandMin_Hz = getparameter_f(kRateRandMin_Hz); - mRateRandMinIndex = getparameter_i(kRateRandMin_Sync); + mRateRandMinIndex = getparameter_index(kRateRandMin_Sync); auto const rateRandMin_Sync = mTempoRateTable.getScalar(mRateRandMinIndex); mTempoSync = getparameter_b(kTempoSync); mPulsewidth = getparameter_f(kPulsewidth); diff --git a/thrush/thrush.cpp b/thrush/thrush.cpp index 11c6f764..8e3e59f2 100644 --- a/thrush/thrush.cpp +++ b/thrush/thrush.cpp @@ -78,7 +78,7 @@ Thrush::Thrush(TARGET_API_BASE_INSTANCE_TYPE inInstance) setparametervaluestring(kLFO2Shape2, i, shapeName); } // set the value strings for the sync rate parameters - for (long i = 0; i < mTempoRateTable.getNumRates(); i++) + for (size_t i = 0; i < numTempoRates; i++) { auto const& tempoRateName = mTempoRateTable.getDisplay(i); setparametervaluestring(kLFO1Rate_Sync, i, tempoRateName); @@ -284,7 +284,7 @@ void Thrush::processparameters() mDelay2_gen = getparameter_gen(kDelay2); mLFO1_2.mRateHz = getparameter_f(kLFO1Rate2_Hz); - mLFO1_2.mTempoRateScalar = mTempoRateTable.getScalar(getparameter_i(kLFO1Rate2_Sync)); + mLFO1_2.mTempoRateScalar = mTempoRateTable.getScalar(getparameter_index(kLFO1Rate2_Sync)); if (auto const value = getparameterifchanged_b(kLFO1TempoSync2)) { @@ -296,7 +296,7 @@ void Thrush::processparameters() mLFO1_2.setDepth(getparameter_scalar(kLFO1Depth2)); mLFO1_2.setShape(getparameter_i(kLFO1Shape2)); mLFO2_2.mRateHz = getparameter_f(kLFO2Rate2_Hz); - mLFO2_2.mTempoRateScalar = mTempoRateTable.getScalar(getparameter_i(kLFO2Rate2_Sync)); + mLFO2_2.mTempoRateScalar = mTempoRateTable.getScalar(getparameter_index(kLFO2Rate2_Sync)); if (auto const value = getparameterifchanged_b(kLFO2TempoSync2)) { From fe57c7feecfb0c878e971ba6b7618c5313a1911e Mon Sep 17 00:00:00 2001 From: Sophia Poirier <2997196+sophiapoirier@users.noreply.github.com> Date: Sat, 19 Feb 2022 16:28:32 -0800 Subject: [PATCH 4/4] Transverb: various approaches to adapt to "distance" changes without crackly glitches --- transverb/transverb-base.h | 3 ++ transverb/transverb.h | 18 ++++++- transverb/transverbformalities.cpp | 81 +++++++++++++++++++++++++++--- transverb/transverbprocess.cpp | 66 ++++++++++++++++-------- 4 files changed, 137 insertions(+), 31 deletions(-) diff --git a/transverb/transverb-base.h b/transverb/transverb-base.h index 941b3203..70729c24 100644 --- a/transverb/transverb-base.h +++ b/transverb/transverb-base.h @@ -48,6 +48,7 @@ enum : dfx::ParameterID kTomsound, kFreeze, kAttenuateFeedbackByMixLevel, + kDistChangeMode, kNumParameters }; @@ -66,6 +67,8 @@ enum { kQualityMode_DirtFi, kQualityMode_HiFi, kQualityMode_UltraHiFi, kQualityM enum : unsigned int { kSpeedMode_Fine, kSpeedMode_Semitone, kSpeedMode_Octave, kSpeedMode_NumModes }; static constexpr dfx::PropertyID kTransverbProperty_SpeedModeBase = dfx::kPluginProperty_EndOfList; +enum { kDistChangeMode_Reverse, kDistChangeMode_AdHocVarispeed, kDistChangeMode_DistanceVarispeed, kDistChangeMode_BufferVarispeed, kDistChangeMode_LoopingBufferVarispeed, kDistChangeMode_Count }; + dfx::PropertyID speedModeIndexToPropertyID(size_t inIndex) noexcept; size_t speedModePropertyIDToIndex(dfx::PropertyID inPropertyID) noexcept; diff --git a/transverb/transverb.h b/transverb/transverb.h index a1987b4c..71525676 100644 --- a/transverb/transverb.h +++ b/transverb/transverb.h @@ -26,6 +26,7 @@ To contact the author, use the contact form at http://destroyfx.org #include #include #include +#include #include #include "dfxplugin.h" @@ -57,6 +58,8 @@ class TransverbDSP final : public DfxPluginCore { dfx::SmoothedValue mix, feed; double read = 0.; + std::optional targetdist; + double distspeedfactor = 1.; std::vector buf; dfx::IIRFilter filter; @@ -83,17 +86,23 @@ class TransverbDSP final : public DfxPluginCore { static constexpr int mod_bipolar(int value, int modulo); static inline double fmod_bipolar(double value, double modulo); + // distance in samples of a read position from the current write position + double getdist(double read) const; + void processdist(double distnormalized, Head& head); + // these store the parameter values int bsize = 0; dfx::SmoothedValue drymix; - long quality = 0; - bool tomsound = false; + int distchangemode {}; int writer = 0; std::array heads; int const MAXBUF; // the size of the audio buffer (dependent on sampling rate) + bool firstrendersincereset = false; + std::vector& buftemp; + std::vector const firCoefficientsWindow; }; @@ -116,6 +125,10 @@ class Transverb final : public DfxPlugin { dfx::StatusCode dfx_SetProperty(dfx::PropertyID inPropertyID, dfx::Scope inScope, unsigned int inItemIndex, void const* inData, size_t inDataSize) override; + auto& getscratchbuffer() noexcept { + return buftemp; + } + protected: size_t settings_sizeOfExtendedData() const noexcept override; void settings_saveExtendedData(void* outData, bool isPreset) override; @@ -132,6 +145,7 @@ class Transverb final : public DfxPlugin { } std::array speedModeStates {}; + std::vector buftemp; // shared between all DSP cores (memory optimization) }; diff --git a/transverb/transverbformalities.cpp b/transverb/transverbformalities.cpp index 0d493b63..654c9b8f 100644 --- a/transverb/transverbformalities.cpp +++ b/transverb/transverbformalities.cpp @@ -57,6 +57,7 @@ Transverb::Transverb(TARGET_API_BASE_INSTANCE_TYPE inInstance) initparameter_b(kTomsound, {"TOMSOUND", "TomSnd", "Tom7"}, false); initparameter_b(kFreeze, dfx::MakeParameterNames(dfx::kParameterNames_Freeze), false); initparameter_b(kAttenuateFeedbackByMixLevel, {"attenuate feedback by mix level", "AtnFdbk", "AtnFdb", "-fdb"}, false); + initparameter_list(kDistChangeMode, {"distance change mode", "DistMod", "DstMod", "DMod"}, kDistChangeMode_Reverse, kDistChangeMode_Reverse, kDistChangeMode_Count); setparameterenforcevaluelimits(kBsize, true); for (auto const parameterID : kDistParameters) { @@ -67,10 +68,12 @@ Transverb::Transverb(TARGET_API_BASE_INSTANCE_TYPE inInstance) setparametervaluestring(kQuality, kQualityMode_HiFi, "hi-fi"); setparametervaluestring(kQuality, kQualityMode_UltraHiFi, "ultra hi-fi"); - // distance parameters only have meaningful effect at zero speed which probably will never occur randomly, - // and otherwise all they do is glitch a lot, so omit them - addparameterattributes(kDist1, DfxParam::kAttribute_OmitFromRandomizeAll); - addparameterattributes(kDist2, DfxParam::kAttribute_OmitFromRandomizeAll); + setparametervaluestring(kDistChangeMode, kDistChangeMode_Reverse, "reverse"); + setparametervaluestring(kDistChangeMode, kDistChangeMode_AdHocVarispeed, "ad hoc varispeed"); + setparametervaluestring(kDistChangeMode, kDistChangeMode_DistanceVarispeed, "distance varispeed"); + setparametervaluestring(kDistChangeMode, kDistChangeMode_BufferVarispeed, "buffer varispeed"); + setparametervaluestring(kDistChangeMode, kDistChangeMode_LoopingBufferVarispeed, "looping buffer varispeed"); + addparameterattributes(kFreeze, DfxParam::kAttribute_OmitFromRandomizeAll); addparameterattributes(kAttenuateFeedbackByMixLevel, DfxParam::kAttribute_OmitFromRandomizeAll); @@ -109,8 +112,11 @@ void Transverb::dfx_PostConstructor() { TransverbDSP::TransverbDSP(DfxPlugin* inDfxPlugin) : DfxPluginCore(inDfxPlugin), MAXBUF(static_cast(getparametermax_f(kBsize) * 0.001 * getsamplerate())), + buftemp(dynamic_cast(inDfxPlugin)->getscratchbuffer()), firCoefficientsWindow(dfx::FIRFilter::generateKaiserWindow(kNumFIRTaps, 60.0f)) { + buftemp.assign(MAXBUF, 0.f); + registerSmoothedAudioValue(&drymix); for (auto& head : heads) { @@ -126,12 +132,15 @@ TransverbDSP::TransverbDSP(DfxPlugin* inDfxPlugin) void TransverbDSP::reset() { std::for_each(heads.begin(), heads.end(), [](Head& head){ head.reset(); }); + + firstrendersincereset = true; } void TransverbDSP::Head::reset() { smoothcount = 0; lastdelayval = 0.f; + targetdist.reset(); filter.reset(); speedHasChanged = true; @@ -164,8 +173,6 @@ void TransverbDSP::processparameters() { heads[head].feed = *value; } } - quality = getparameter_i(kQuality); - tomsound = getparameter_b(kTomsound); if (auto const value = getparameterifchanged_f(kBsize)) { @@ -193,12 +200,12 @@ void TransverbDSP::processparameters() { std::for_each(heads.begin(), heads.end(), [bsize_f](Head& head){ head.read = fmod_bipolar(head.read, bsize_f); }); } + distchangemode = getparameter_i(kDistChangeMode); for (size_t head = 0; head < kNumDelays; head++) { if (auto const dist = getparameterifchanged_f(kDistParameters[head])) { - auto const bsize_f = static_cast(bsize); - heads[head].read = fmod_bipolar(static_cast(writer) - (*dist * bsize_f), bsize_f); + processdist(*dist, heads[head]); } } @@ -221,6 +228,64 @@ void TransverbDSP::processparameters() { heads[0].mix.setValueNow(getparametermin_f(kMix1)); } } + + firstrendersincereset = false; +} + +double TransverbDSP::getdist(double read) const { + + return fmod_bipolar(static_cast(writer) - read, static_cast(bsize)); +} + +void TransverbDSP::processdist(double distnormalized, Head& head) { + + auto const bsize_f = static_cast(bsize); + auto const distsamples = distnormalized * bsize_f; + auto const targetread = fmod_bipolar(static_cast(writer) - distsamples, bsize_f); + + if (firstrendersincereset) + { + head.read = targetread; + } + else + { + if (distchangemode == kDistChangeMode_Reverse) + { + head.targetdist = distsamples; + } + else if (auto const resamplerate = getdist(head.read) / std::max(distsamples, 1.); distchangemode == kDistChangeMode_AdHocVarispeed) + { + head.targetdist = distsamples; + head.distspeedfactor = resamplerate; + head.speedHasChanged = true; + } + else + { + constexpr double modulationthresholdsamples = 1.; + if (std::fabs(targetread - head.read) >= modulationthresholdsamples) + { + auto const bsizesteps = bsize_f / resamplerate; + bool const subslice = (distchangemode == kDistChangeMode_DistanceVarispeed); + bool const looping = (distchangemode == kDistChangeMode_LoopingBufferVarispeed); + auto const copylength_f = subslice ? distsamples : (looping ? bsize_f : std::min(bsize_f, bsizesteps)); + auto const copylength = static_cast(std::lround(copylength_f)); + assert(copylength <= buftemp.size()); + assert(static_cast(copylength) <= bsize); + auto const sourcestart = subslice ? head.read : fmod_bipolar(static_cast(writer) - (copylength_f * resamplerate), bsize_f); + for (size_t i = 0; i < copylength; i++) + { + auto const sourcepos = std::fmod(sourcestart + (resamplerate * static_cast(i)), bsize_f); + buftemp[i] = interpolateHermite(head.buf.data(), sourcepos, bsize, writer); + } + auto const destinationstart = subslice ? std::lround(targetread) : mod_bipolar(writer - static_cast(copylength), bsize); + auto const copylength1 = std::min(static_cast(bsize - destinationstart), copylength); + auto const copylength2 = copylength - copylength1; + std::copy_n(buftemp.cbegin(), copylength1, std::next(head.buf.begin(), destinationstart)); + std::copy_n(std::next(buftemp.cbegin(), copylength1), copylength2, head.buf.begin()); + } + head.read = targetread; + } + } } diff --git a/transverb/transverbprocess.cpp b/transverb/transverbprocess.cpp index 7f2f06c8..be0a50af 100644 --- a/transverb/transverbprocess.cpp +++ b/transverb/transverbprocess.cpp @@ -36,11 +36,13 @@ using namespace dfx::TV; void TransverbDSP::process(float const* inAudio, float* outAudio, size_t numSampleFrames) { - std::array delayvals {}; // delay buffer output values auto const bsize_float = static_cast(bsize); // cut down on casting + auto const quality = getparameter_i(kQuality); + auto const tomsound = getparameter_b(kTomsound); auto const freeze = getparameter_b(kFreeze); int const writerIncrement = freeze ? 0 : 1; auto const attenuateFeedbackByMixLevel = getparameter_b(kAttenuateFeedbackByMixLevel); + std::array delayvals {}; // delay buffer output values ///////////// S O P H I A S O U N D ////////////// @@ -69,6 +71,9 @@ void TransverbDSP::process(float const* inAudio, float* outAudio, size_t numSamp for (size_t h = 0; h < kNumDelays; h++) // delay heads loop { auto const read_int = static_cast(heads[h].read); + auto const reverseread = (distchangemode == kDistChangeMode_Reverse) && heads[h].targetdist.has_value(); + auto const distcatchup = (distchangemode == kDistChangeMode_AdHocVarispeed) && heads[h].targetdist.has_value(); + auto const speed = heads[h].speed.getValue() * (distcatchup ? heads[h].distspeedfactor : 1.); // filter setup if (quality == kQualityMode_UltraHiFi) @@ -77,27 +82,27 @@ void TransverbDSP::process(float const* inAudio, float* outAudio, size_t numSamp { lowpasspos[h] = read_int; // check to see if we need to lowpass the first delay head and init coefficients if so - if (heads[h].speed.getValue() > kUnitySpeed) + if (speed > kUnitySpeed) { filtermodes[h] = FilterMode::LowpassIIR; - speed_ints[h] = static_cast(heads[h].speed.getValue()); + speed_ints[h] = static_cast(speed); // it becomes too costly to try to IIR at higher speeds, so switch to FIR filtering - if (heads[h].speed.getValue() >= kFIRSpeedThreshold) + if (speed >= kFIRSpeedThreshold) { filtermodes[h] = FilterMode::LowpassFIR; // compensate for gain lost from filtering - mugs[h] = static_cast(std::pow(heads[h].speed.getValue() / kFIRSpeedThreshold, 0.78)); + mugs[h] = static_cast(std::pow(speed / kFIRSpeedThreshold, 0.78)); // update the coefficients only if necessary if (std::exchange(heads[h].speedHasChanged, false)) { - dfx::FIRFilter::calculateIdealLowpassCoefficients((samplerate / heads[h].speed.getValue()) * dfx::FIRFilter::kShelfStartLowpass, + dfx::FIRFilter::calculateIdealLowpassCoefficients((samplerate / speed) * dfx::FIRFilter::kShelfStartLowpass, samplerate, heads[h].firCoefficients, firCoefficientsWindow); heads[h].filter.reset(); } } else if (std::exchange(heads[h].speedHasChanged, false)) { - heads[h].filter.setLowpassCoefficients((samplerate / heads[h].speed.getValue()) * dfx::IIRFilter::kShelfStartLowpass); + heads[h].filter.setLowpassCoefficients((samplerate / speed) * dfx::IIRFilter::kShelfStartLowpass); } } // we need to highpass the delay head to remove mega sub bass @@ -106,7 +111,7 @@ void TransverbDSP::process(float const* inAudio, float* outAudio, size_t numSamp filtermodes[h] = FilterMode::Highpass; if (std::exchange(heads[h].speedHasChanged, false)) { - heads[h].filter.setHighpassCoefficients(kHighpassFilterCutoff / heads[h].speed.getValue()); + heads[h].filter.setHighpassCoefficients(kHighpassFilterCutoff / speed); } } } @@ -173,18 +178,18 @@ void TransverbDSP::process(float const* inAudio, float* outAudio, size_t numSamp // start smoothing if the writer has passed a reader or vice versa, // though not if reader and writer move at the same speed // (check the positions before wrapping around the heads) - auto const nextRead = static_cast(heads[h].read + heads[h].speed.getValue()); + auto const nextRead = static_cast(heads[h].read + speed); auto const nextWrite = writer + 1; bool const readCrossingAhead = (read_int < writer) && (nextRead >= nextWrite); bool const readCrossingBehind = (read_int >= writer) && (nextRead <= nextWrite); - bool const speedIsUnity = heads[h].speed.getValue() == kUnitySpeed; + bool const speedIsUnity = (speed == kUnitySpeed); if ((readCrossingAhead || readCrossingBehind) && !speedIsUnity) { // check because, at slow speeds, it's possible to go into this twice or more in a row if (heads[h].smoothcount <= 0) { // store the most recent output as the head's smoothing sample heads[h].lastdelayval = delayvals[h]; // truncate the smoothing duration if we're using too small of a buffer size - auto const bufferReadSteps = static_cast(bsize_float / heads[h].speed.getValue()); + auto const bufferReadSteps = static_cast(bsize_float / speed); auto const smoothdur = std::min(bufferReadSteps, kAudioSmoothingDur_samples); heads[h].smoothstep = 1.f / static_cast(smoothdur); // the scalar step value heads[h].smoothcount = smoothdur; // set the counter to the total duration @@ -192,9 +197,21 @@ void TransverbDSP::process(float const* inAudio, float* outAudio, size_t numSamp } // update read heads, wrapping around if they have gone past the end of the buffer - heads[h].read += heads[h].speed.getValue(); - if (heads[h].read >= bsize_float) { - heads[h].read = fmod_bipolar(heads[h].read, bsize_float); + if (reverseread) { + heads[h].read -= speed; + while (heads[h].read < 0.) { + heads[h].read += bsize_float; + } + } else { + heads[h].read += speed; + if (heads[h].read >= bsize_float) { + heads[h].read = fmod_bipolar(heads[h].read, bsize_float); + } + } + if (distcatchup || reverseread) { + if (std::fabs(getdist(heads[h].read) - *heads[h].targetdist) < speed) { + heads[h].targetdist.reset(); + } } // if we're doing IIR lowpass filtering, @@ -204,40 +221,47 @@ void TransverbDSP::process(float const* inAudio, float* outAudio, size_t numSamp if (filtermodes[h] == FilterMode::LowpassIIR) { int lowpasscount = 0; + int const direction = reverseread ? -1 : 1; while (lowpasscount < speed_ints[h]) { switch (speed_ints[h] - lowpasscount) { case 1: heads[h].filter.processToCacheH1(heads[h].buf[lowpasspos[h]]); - lowpasspos[h] = (lowpasspos[h] + 1) % bsize; + lowpasspos[h] = mod_bipolar(lowpasspos[h] + (1 * direction), bsize); lowpasscount++; break; case 2: heads[h].filter.processToCacheH2(heads[h].buf.data(), dfx::math::ToUnsigned(lowpasspos[h]), dfx::math::ToUnsigned(bsize)); - lowpasspos[h] = (lowpasspos[h] + 2) % bsize; + lowpasspos[h] = mod_bipolar(lowpasspos[h] + (2 * direction), bsize); lowpasscount += 2; break; case 3: heads[h].filter.processToCacheH3(heads[h].buf.data(), dfx::math::ToUnsigned(lowpasspos[h]), dfx::math::ToUnsigned(bsize)); - lowpasspos[h] = (lowpasspos[h] + 3) % bsize; + lowpasspos[h] = mod_bipolar(lowpasspos[h] + (3 * direction), bsize); lowpasscount += 3; break; default: heads[h].filter.processToCacheH4(heads[h].buf.data(), dfx::math::ToUnsigned(lowpasspos[h]), dfx::math::ToUnsigned(bsize)); - lowpasspos[h] = (lowpasspos[h] + 4) % bsize; + lowpasspos[h] = mod_bipolar(lowpasspos[h] + (4 * direction), bsize); lowpasscount += 4; break; } } auto const nextread_int = static_cast(heads[h].read); // check whether we need to consume one more sample - bool const extrasample = ((lowpasspos[h] < nextread_int) && ((lowpasspos[h] + 1) == nextread_int)) || - ((lowpasspos[h] == (bsize - 1)) && (nextread_int == 0)); + bool const extrasample = [=] { + if (reverseread) { + return ((lowpasspos[h] > nextread_int) && ((lowpasspos[h] - 1) == nextread_int)) || + ((lowpasspos[h] == 0) && (nextread_int == (bsize - 1))); + } + return ((lowpasspos[h] < nextread_int) && ((lowpasspos[h] + 1) == nextread_int)) || + ((lowpasspos[h] == (bsize - 1)) && (nextread_int == 0)); + }(); if (extrasample) { heads[h].filter.processToCacheH1(heads[h].buf[lowpasspos[h]]); - lowpasspos[h] = (lowpasspos[h] + 1) % bsize; + lowpasspos[h] = mod_bipolar(lowpasspos[h] + (1 * direction), bsize); } } // it's simpler for highpassing;