Skip to content

Commit

Permalink
Tempo Sync Delay (#335)
Browse files Browse the repository at this point in the history
* Custom Components Added to Delay Module To Allow Tempo Sync Delay Time Option

* Rough Plan for Tempo Sync Delay Calculation

* Intital Tempo Sync Delay Implementation

* Tempo Sync Calculation For Number of Samples of Delay Added

* PlayheadHelpers Added to Manage the AudioPlayHead Object

* Playhead helpers re-organization

* TempoSync Delay Times Calculation Changed to use chowdsp_rhythm

* Comments and Cout Statements Removed

* Delay Parameter Names Changed for Clarity

* Apply clang-format

* Custom Components First and PR Feedback Addressed

* Apply clang-format

* Fixing headless and small delay rhythm parameter cleanup

* More unit test fixes

---------

Co-authored-by: jatin <jatinchowdhury18@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 8, 2023
1 parent 4df9f96 commit e97727b
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 13 deletions.
1 change: 1 addition & 0 deletions modules/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ target_link_libraries(juce_plugin_modules
chowdsp::chowdsp_plugin_base
chowdsp::chowdsp_plugin_utils
chowdsp::chowdsp_reverb
chowdsp::chowdsp_rhythm
chowdsp::chowdsp_presets
chowdsp::chowdsp_serialization
chowdsp::chowdsp_units
Expand Down
3 changes: 3 additions & 0 deletions src/BYOD.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ void BYOD::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midi)
const juce::ScopedNoDenormals noDenormals {};
AudioProcessLoadMeasurer::ScopedTimer loadTimer { loadMeasurer, buffer.getNumSamples() };

//get playhead
procs->getPlayheadHelper().process (getPlayHead(), buffer.getNumSamples());

// push samples into bypass delay
bypassScratchBuffer.makeCopyOf (buffer, true);
processBypassDelay (bypassScratchBuffer);
Expand Down
3 changes: 2 additions & 1 deletion src/BYOD.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include "processors/PlayheadHelpers.h"
#include "processors/ProcessorStore.h"
#include "processors/chain/ProcessorChain.h"
#include "state/ParamForwardManager.h"
Expand Down Expand Up @@ -54,7 +55,7 @@ class BYOD : public chowdsp::PluginBase<BYOD>
[[maybe_unused]] chowdsp::SharedLNFAllocator lnfAllocator; // keep alive!

ProcessorStore procStore;
std::unique_ptr<ProcessorChain> procs;
std::unique_ptr<ProcessorChain> procs; //ptrs to processor chain
[[maybe_unused]] std::unique_ptr<ParamForwardManager> paramForwarder;

AudioBuffer<float> bypassScratchBuffer;
Expand Down
1 change: 1 addition & 0 deletions src/headless/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ target_include_directories(BYOD_headless
../../modules/chowdsp_utils/modules/gui
../../modules/chowdsp_utils/modules/plugin
../../modules/chowdsp_utils/modules/common
../../modules/chowdsp_utils/modules/music
../../modules/JUCE/modules
../../modules/chowdsp_wdf/include
../../modules/ea_variant
Expand Down
2 changes: 2 additions & 0 deletions src/headless/tests/UnitTests.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

static inline void runTestForAllProcessors (UnitTest* ut, const std::function<void (BaseProcessor*)>& testFunc, const StringArray& procsToSkip = {})
{
PlayheadHelpers playheadHelper;
for (auto [name, storeEntry] : ProcessorStore::getStoreMap())
{
if (procsToSkip.contains (name))
Expand All @@ -11,6 +12,7 @@ static inline void runTestForAllProcessors (UnitTest* ut, const std::function<vo
}

auto proc = storeEntry.factory (nullptr);
proc->playheadHelpers = &playheadHelper;
ut->beginTest (proc->getName() + " Test");
testFunc (proc.get());
proc->freeInternalMemory();
Expand Down
4 changes: 4 additions & 0 deletions src/processors/BaseProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct ProcessorUIOptions

class BaseProcessor;
class ProcessorEditor;
struct PlayheadHelpers;
namespace netlist
{
struct CircuitQuantityList;
Expand Down Expand Up @@ -197,6 +198,9 @@ class BaseProcessor : private JuceProcWrapper
*/
const MidiBuffer* midiBuffer = nullptr;

/** Provided by the processor chain */
const PlayheadHelpers* playheadHelpers = nullptr;

/** Returns a tooltip string for a given port. */
virtual String getTooltipForPort (int portIndex, bool isInput);

Expand Down
21 changes: 21 additions & 0 deletions src/processors/PlayheadHelpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#include <pch.h>

struct PlayheadHelpers
{
void process (AudioPlayHead* playHead, int numSamples)
{
ignoreUnused (numSamples);
if (playHead != nullptr)
{
bpm.store (playHead->getPosition().orFallback (AudioPlayHead::PositionInfo {}).getBpm().orFallback (120.0));
}
else
{
bpm.store (120.0);
}
}

std::atomic<double> bpm;
};
3 changes: 3 additions & 0 deletions src/processors/chain/ProcessorChain.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "../utility/InputProcessor.h"
#include "../utility/OutputProcessor.h"
#include "processors/PlayheadHelpers.h"

class ProcessorChainActionHelper;
class ProcessorChainPortMagnitudesHelper;
Expand Down Expand Up @@ -34,6 +35,7 @@ class ProcessorChain : private AudioProcessorValueTreeState::Listener
auto& getActionHelper() { return *actionHelper; }
auto& getStateHelper() { return *stateHelper; }
auto& getOversampling() { return ioProcessor.getOversampling(); }
auto& getPlayheadHelper() { return playheadHelper; }

chowdsp::Broadcaster<void (BaseProcessor*)> processorAddedBroadcaster;
chowdsp::Broadcaster<void (const BaseProcessor*)> processorRemovedBroadcaster;
Expand Down Expand Up @@ -78,6 +80,7 @@ class ProcessorChain : private AudioProcessorValueTreeState::Listener
std::unique_ptr<ParamForwardManager>& paramForwardManager;

MidiBuffer internalMidiBuffer;
PlayheadHelpers playheadHelper;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProcessorChain)
};
1 change: 1 addition & 0 deletions src/processors/chain/ProcessorChainActions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ProcChainActions
{
Logger::writeToLog (String ("Creating processor: ") + newProc->getName());

newProc->playheadHelpers = &chain.playheadHelper;
auto osFactor = chain.ioProcessor.getOversamplingFactor();
newProc->prepareProcessing (osFactor * chain.mySampleRate, osFactor * chain.mySamplesPerBlock);

Expand Down
155 changes: 144 additions & 11 deletions src/processors/other/Delay.cpp
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
#include "Delay.h"
#include "../ParameterHelpers.h"
#include "gui/utils/ModulatableSlider.h"
#include "processors/PlayheadHelpers.h"

using namespace chowdsp::RhythmUtils;
//using namespace chowdsp::RhythmParameter;

namespace
{
const String delayTypeTag = "delay_type";
const String pingPongTag = "ping_pong";
const String freqTag = "freq";
const String feedBackTag = "feedback";
const String mixTag = "mix";

const String delayTimeMsTag = "time_ms";
const String tempoSyncTag = "tempo_sync";
const String tempoSyncAmountTag = "time_tempo_sync";
} // namespace

DelayModule::DelayModule (UndoManager* um) : BaseProcessor ("Delay", createParameterLayout(), um)
{
using namespace ParameterHelpers;
loadParameterPointer (delayTimeMsParam, vts, "time_ms");
loadParameterPointer (freqParam, vts, "freq");
loadParameterPointer (feedbackParam, vts, "feedback");
loadParameterPointer (mixParam, vts, "mix");
loadParameterPointer (freqParam, vts, freqTag);
loadParameterPointer (feedbackParam, vts, feedBackTag);
loadParameterPointer (mixParam, vts, mixTag);
loadParameterPointer (delayTimeMsParam, vts, delayTimeMsTag);
loadParameterPointer (delayTimeRhythmParam, vts, tempoSyncAmountTag);
tempoSyncOnOffParam = vts.getRawParameterValue (tempoSyncTag);
delayTypeParam = vts.getRawParameterValue (delayTypeTag);
pingPongParam = vts.getRawParameterValue (pingPongTag);

addPopupMenuParameter (delayTypeTag);
addPopupMenuParameter (pingPongTag);
addPopupMenuParameter (tempoSyncTag);

uiOptions.backgroundColour = Colours::cyan.darker (0.1f);
uiOptions.powerColour = Colours::gold;
Expand All @@ -31,12 +46,17 @@ ParamLayout DelayModule::createParameterLayout()
using namespace ParameterHelpers;
auto params = createBaseParams();

createTimeMsParameter (params, "time_ms", "Delay Time", createNormalisableRange (20.0f, 2000.0f, 200.0f), 100.0f);

createFreqParameter (params, "freq", "Cutoff", 500.0f, 10000.0f, 4000.0f, 10000.0f);
createPercentParameter (params, "feedback", "Feedback", 0.0f);
createPercentParameter (params, "mix", "Mix", 0.5f);

createTimeMsParameter (params, delayTimeMsTag, "Delay Time", createNormalisableRange (20.0f, 2000.0f, 200.0f), 100.0f);
createFreqParameter (params, freqTag, "Cutoff", 500.0f, 10000.0f, 4000.0f, 10000.0f);
createPercentParameter (params, feedBackTag, "Feedback", 0.0f);
createPercentParameter (params, mixTag, "Mix", 0.5f);

emplace_param<chowdsp::RhythmParameter> (params,
tempoSyncAmountTag,
"Delay Rhythm",
std::initializer_list<Rhythm> { HALF, QUARTER, EIGHTH, EIGHTH_DOT },
HALF);
emplace_param<AudioParameterBool> (params, tempoSyncTag, "Tempo Sync", false);
emplace_param<AudioParameterChoice> (params, delayTypeTag, "Delay Type", StringArray { "Clean", "Lo-Fi" }, 0);
emplace_param<AudioParameterBool> (params, pingPongTag, "Ping-Pong", false);

Expand Down Expand Up @@ -217,8 +237,21 @@ void DelayModule::processPingPongDelay (AudioBuffer<float>& buffer, DelayType& d

void DelayModule::processAudio (AudioBuffer<float>& buffer)
{
jassert (playheadHelpers != nullptr);

feedbackSmoothBuffer.process (std::pow (feedbackParam->getCurrentValue() * 0.67f, 0.9f), buffer.getNumSamples());
delaySmooth.setTargetValue (fs * *delayTimeMsParam * 0.001f);
const auto tempo = playheadHelpers->bpm.load();
const auto tempoSync = *tempoSyncOnOffParam == 1.0f;
if (! tempoSync)
{
delaySmooth.setTargetValue (fs * *delayTimeMsParam * 0.001f);
}
else
{
const auto delayInSeconds = delayTimeRhythmParam->getRhythmTimeSeconds (tempo);
const auto delayInSamples = static_cast<float> (delayInSeconds) * fs;
delaySmooth.setTargetValue (delayInSamples);
}
freqSmooth.setTargetValue (*freqParam);

const auto delayTypeIndex = (int) *delayTypeParam;
Expand Down Expand Up @@ -261,3 +294,103 @@ void DelayModule::processAudioBypassed (AudioBuffer<float>& buffer)

outputBuffers.getReference (0) = &buffer;
}

bool DelayModule::getCustomComponents (OwnedArray<Component>& customComps, chowdsp::HostContextProvider& hcp)
{
using namespace chowdsp::ParamUtils;
class DelayTimeModeControl : public Slider
{
public:
DelayTimeModeControl (AudioProcessorValueTreeState& vtState, chowdsp::HostContextProvider& hcp)
: vts (vtState),
tempoSyncSelectorAttach (vts, tempoSyncAmountTag, tempoSyncSelector),
delayTimeSlider (*getParameterPointer<chowdsp::FloatParameter*> (vts, delayTimeMsTag), hcp),
delayTimeAttach (vts, delayTimeMsTag, delayTimeSlider),
tempoSyncOnOffAttach (
*vts.getParameter (tempoSyncTag),
[this] (float newValue)
{ updateControlVisibility (newValue == 1.0f); },
vts.undoManager)
{
addChildComponent (tempoSyncSelector);
addChildComponent (delayTimeSlider);

const auto* modeChoiceParam = getParameterPointer<chowdsp::RhythmParameter*> (vts, tempoSyncAmountTag);
tempoSyncSelector.addItemList (modeChoiceParam->choices, 1);
tempoSyncSelector.setSelectedItemIndex (0);
tempoSyncSelector.setScrollWheelEnabled (true);
hcp.registerParameterComponent (tempoSyncSelector, *modeChoiceParam);

hcp.registerParameterComponent (delayTimeSlider, delayTimeSlider.getParameter());

this->setName (tempoSyncAmountTag + "__" + delayTimeMsTag + "__");
}

void colourChanged() override
{
for (auto colourID : { Slider::textBoxOutlineColourId,
Slider::textBoxTextColourId,
Slider::textBoxBackgroundColourId,
Slider::textBoxHighlightColourId,
Slider::thumbColourId })
{
delayTimeSlider.setColour (colourID, findColour (colourID, false));
}

for (auto colourID : { ComboBox::outlineColourId,
ComboBox::textColourId,
ComboBox::arrowColourId })
{
tempoSyncSelector.setColour (colourID, findColour (Slider::textBoxTextColourId, false));
}
}

void updateControlVisibility (bool tempoSyncOn)
{
tempoSyncSelector.setVisible (tempoSyncOn);
delayTimeSlider.setVisible (! tempoSyncOn);

setName (vts.getParameter (tempoSyncOn ? tempoSyncAmountTag : delayTimeMsTag)->name);
if (auto* parent = getParentComponent())
parent->repaint();
}

void visibilityChanged() override
{
updateControlVisibility (vts.getRawParameterValue (tempoSyncTag)->load() == 1.0f);
}

void resized() override
{
delayTimeSlider.setSliderStyle (getSliderStyle());
delayTimeSlider.setTextBoxStyle (getTextBoxPosition(), false, getTextBoxWidth(), getTextBoxHeight());

const auto bounds = getLocalBounds();
tempoSyncSelector.setBounds (bounds.proportionOfWidth (0.15f),
bounds.proportionOfHeight (0.1f),
bounds.proportionOfWidth (0.7f),
bounds.proportionOfHeight (0.25f));
delayTimeSlider.setBounds (bounds);
}

private:
using SliderAttachment = AudioProcessorValueTreeState::SliderAttachment;
using BoxAttachment = AudioProcessorValueTreeState::ComboBoxAttachment;

AudioProcessorValueTreeState& vts;

ComboBox tempoSyncSelector;
BoxAttachment tempoSyncSelectorAttach;

ModulatableSlider delayTimeSlider;
SliderAttachment delayTimeAttach;

ParameterAttachment tempoSyncOnOffAttach;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayTimeModeControl)
};

customComps.add (std::make_unique<DelayTimeModeControl> (vts, hcp));

return true;
}
6 changes: 5 additions & 1 deletion src/processors/other/Delay.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class DelayModule : public BaseProcessor

ProcessorType getProcessorType() const override { return Other; }
static ParamLayout createParameterLayout();
bool getCustomComponents (OwnedArray<Component>& customComps, chowdsp::HostContextProvider& hcp) override;

void prepare (double sampleRate, int samplesPerBlock) override;
void releaseMemory() override;
Expand All @@ -21,13 +22,16 @@ class DelayModule : public BaseProcessor
template <typename DelayType>
void processPingPongDelay (AudioBuffer<float>& buffer, DelayType& delayLine);

chowdsp::FloatParameter* delayTimeMsParam = nullptr;
chowdsp::FloatParameter* freqParam = nullptr;
chowdsp::FloatParameter* feedbackParam = nullptr;
chowdsp::FloatParameter* mixParam = nullptr;
std::atomic<float>* delayTypeParam = nullptr;
std::atomic<float>* pingPongParam = nullptr;

chowdsp::FloatParameter* delayTimeMsParam = nullptr;
chowdsp::RhythmParameter* delayTimeRhythmParam = nullptr;
std::atomic<float>* tempoSyncOnOffParam = nullptr;

dsp::DryWetMixer<float> dryWetMixer;
dsp::DryWetMixer<float> dryWetMixerMono;

Expand Down

0 comments on commit e97727b

Please sign in to comment.