Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tempo Sync Delay #335

Merged
merged 16 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
RachelMaryamLocke marked this conversation as resolved.
Show resolved Hide resolved
#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
Loading