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

Level Detective Processor - Audio Processing & Visualiser #322

Merged
merged 18 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:

- name: Build
shell: bash
run: cmake --build build --config Release --parallel
run: cmake --build build --config Release --parallel 3

- name: Unit Tests
if: runner.os == 'Linux'
Expand Down
1 change: 1 addition & 0 deletions modules/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ target_link_libraries(juce_plugin_modules
chowdsp::chowdsp_serialization
chowdsp::chowdsp_units
chowdsp::chowdsp_wdf
chowdsp::chowdsp_visualizers
RTNeural
ea_variant
PUBLIC
Expand Down
12 changes: 8 additions & 4 deletions scripts/aax_builds.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#!/bin/bash

# expand bash aliases
shopt -s expand_aliases
source ~/.bashrc

# exit on failure
set -e

Expand Down Expand Up @@ -52,8 +56,8 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
else # Windows
echo "Building for WINDOWS"

AAX_PATH=C:/SDKs/AAX_SDK/
ilok_pass=$(cat /d/ilok_pass)
AAX_PATH=C:/Users/Jatin/SDKs/AAX_SDK_clang/
ilok_pass=$(cat ~/ilok_pass)
aax_target_dir="/c/Program Files/Common Files/Avid/Audio/Plug-Ins"
TARGET_DIR="Win64"
fi
Expand All @@ -75,7 +79,7 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
cmake --build build-aax --config $build_config -j12 --target "${TARGET_NAME}_AAX" | xcpretty

else # Windows
cmake -Bbuild-aax -G"Visual Studio 16 2019" -A x64 -DBYOD_BUILD_ADD_ON_MODULES=ON
cmake -Bbuild-aax -G"Visual Studio 17 2022" -T ClangCL -A x64 -DBYOD_BUILD_ADD_ON_MODULES=ON
cmake --build build-aax --config $build_config --parallel $(nproc) --target "${TARGET_NAME}_AAX"
fi

Expand All @@ -101,7 +105,7 @@ else # Windows
--account chowdsp \
--password "$ilok_pass" \
--wcguid $wcguid \
--keyfile /c/Users/jatin/Downloads/jatin_aax_cert.p12 \
--keyfile ~/jatin_aax_cert.p12 \
--keypassword "$ilok_pass" \
--in $aax_location \
--out $aax_location
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ target_sources(BYOD PRIVATE
processors/other/Compressor.cpp
processors/other/Delay.cpp
processors/other/EnvelopeFilter.cpp
processors/other/LevelDetective.cpp
processors/other/Gate.cpp
processors/other/Octaver.cpp
processors/other/ShimmerReverb.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/processors/ProcessorStore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
#include "other/Delay.h"
#include "other/EnvelopeFilter.h"
#include "other/Gate.h"
#include "other/LevelDetective.h"
#include "other/Octaver.h"
#include "other/ShimmerReverb.h"
#include "other/SmoothReverb.h"
Expand Down Expand Up @@ -146,6 +147,7 @@ ProcessorStore::StoreMap ProcessorStore::store = {
{ "Crying Child", { &processorFactory<CryBaby>, { ProcessorType::Other, CryBaby::numInputs, CryBaby::numOutputs } } },
{ "Delay", { &processorFactory<DelayModule>, { ProcessorType::Other, 1, 1 } } },
{ "Envelope Filter", { &processorFactory<EnvelopeFilter>, { ProcessorType::Other, EnvelopeFilter::numInputs, EnvelopeFilter::numOutputs } } },
{ "Level Detective", { &processorFactory<LevelDetective>, { ProcessorType::Other, 1, 1 } } },
{ "Gate", { &processorFactory<Gate>, { ProcessorType::Other, Gate::numInputs, Gate::numOutputs } } },
{ "Octaver", { &processorFactory<Octaver>, { ProcessorType::Other, 1, 1 } } },
{ "Shimmer Reverb", { &processorFactory<ShimmerReverb>, { ProcessorType::Other, 1, 1 } } },
Expand Down
167 changes: 167 additions & 0 deletions src/processors/other/LevelDetective.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#include "LevelDetective.h"
#include "../ParameterHelpers.h"
#include "gui/utils/ModulatableSlider.h"

using namespace chowdsp::compressor;

namespace
{

const auto powerColour = Colours::gold.darker (0.1f);
const auto backgroundColour = Colours::teal.darker (0.1f);
const String attackTag = "attack";
const String releaseTag = "release";
} // namespace

LevelDetective::LevelDetective (UndoManager* um) : BaseProcessor (
"Level Detective",
createParameterLayout(),
InputPort {},
OutputPort {},
um,
[] (InputPort)
{
return PortType::audio;
},
[] (OutputPort)
{
return PortType::level;
})
{
using namespace ParameterHelpers;
loadParameterPointer (attackMsParam, vts, attackTag);
loadParameterPointer (releaseMsParam, vts, releaseTag);
uiOptions.backgroundColour = backgroundColour;
uiOptions.powerColour = powerColour;
uiOptions.info.description = "A simple envelope follower";
uiOptions.info.authors = StringArray { "Rachel Locke" };
}

ParamLayout LevelDetective::createParameterLayout()
{
using namespace ParameterHelpers;
auto params = createBaseParams();

createTimeMsParameter (params, attackTag, "Attack", createNormalisableRange (1.0f, 100.0f, 10.0f), 10.0f);
createTimeMsParameter (params, releaseTag, "Release", createNormalisableRange (10.0f, 1000.0f, 100.0f), 400.0f);

return { params.begin(), params.end() };
}

void LevelDetective::prepare (double sampleRate, int samplesPerBlock)
{
//prepare the buffers
levelOutBuffer.setSize (1, samplesPerBlock);
level.prepare ({ sampleRate, (uint32) samplesPerBlock, 1 });

levelVisualizer.setBufferSize (int (levelVisualizer.secondsToVisualize * sampleRate / (double) samplesPerBlock));
levelVisualizer.setSamplesPerBlock (samplesPerBlock);
}

void LevelDetective::processAudio (AudioBuffer<float>& buffer)
{
const auto numSamples = buffer.getNumSamples();
levelOutBuffer.setSize (1, numSamples, false, false, true);
//if audio input connected, extract level from input signal and assign to levelOutBuffer
if (inputsConnected.contains (AudioInput))
{
//create span to fill audio visualiser buffer
nonstd::span<const float> audioChannelData = { buffer.getReadPointer (0), (size_t) numSamples };
levelVisualizer.pushChannel (0, audioChannelData);
level.setParameters (*attackMsParam, *releaseMsParam);
level.processBlock (buffer, levelOutBuffer);

//create span to fill level visualiser buffer
nonstd::span<const float> levelChannelData = { levelOutBuffer.getReadPointer (0), (size_t) numSamples };
levelVisualizer.pushChannel (1, levelChannelData);
}
else
{
levelOutBuffer.clear();
}

outputBuffers.getReference (LevelOutput) = &levelOutBuffer;
}

void LevelDetective::processAudioBypassed (AudioBuffer<float>& buffer)
RachelMaryamLocke marked this conversation as resolved.
Show resolved Hide resolved
{
const auto numSamples = buffer.getNumSamples();
levelOutBuffer.setSize (1, numSamples, false, false, true);
if (inputsConnected.contains (AudioInput))
{
levelOutBuffer.clear();
outputBuffers.getReference (LevelOutput) = &levelOutBuffer;
}
}

bool LevelDetective::getCustomComponents (OwnedArray<Component>& customComps, chowdsp::HostContextProvider& hcp)
{
using namespace chowdsp::ParamUtils;
class LevelDetectiveEditor : public juce::Component
{
public:
explicit LevelDetectiveEditor (LevelDetectorVisualizer& viz, AudioProcessorValueTreeState& vtState, chowdsp::HostContextProvider& hcp)
: visualiser (viz),
vts (vtState),
attackSlider (*getParameterPointer<chowdsp::FloatParameter*> (vts, attackTag), hcp),
releaseSlider (*getParameterPointer<chowdsp::FloatParameter*> (vts, releaseTag), hcp),
attackAttach (vts, attackTag, attackSlider),
releaseAttach (vts, releaseTag, releaseSlider)
{
attackLabel.setText ("Attack", juce::dontSendNotification);
releaseLabel.setText ("Release", juce::dontSendNotification);
attackLabel.setJustificationType (Justification::centred);
releaseLabel.setJustificationType (Justification::centred);

addAndMakeVisible (attackLabel);
addAndMakeVisible (releaseLabel);

for (auto* s : { &attackSlider, &releaseSlider })
{
s->setSliderStyle (Slider::RotaryHorizontalVerticalDrag);
s->setTextBoxStyle (Slider::TextBoxBelow, false, 80, 20);
s->setColour (Slider::textBoxHighlightColourId, powerColour.withAlpha (0.55f));
s->setColour (Slider::thumbColourId, powerColour);
addAndMakeVisible (s);
}

visualiser.backgroundColour = backgroundColour.darker (0.4f);
RachelMaryamLocke marked this conversation as resolved.
Show resolved Hide resolved
visualiser.audioColour = juce::Colours::greenyellow.darker (0.4f);

hcp.registerParameterComponent (attackSlider, attackSlider.getParameter());
hcp.registerParameterComponent (releaseSlider, releaseSlider.getParameter());

this->setName (attackTag + "__" + releaseTag + "__");
addAndMakeVisible (visualiser);
}

void resized() override
{
auto bounds = getLocalBounds();
visualiser.setBounds (bounds.removeFromTop (proportionOfHeight (0.33f)));
bounds.removeFromTop (proportionOfHeight (0.03f));
auto labelRect = bounds.removeFromTop (proportionOfHeight (0.115f));
attackLabel.setBounds (labelRect.removeFromLeft (proportionOfWidth (0.5f)));
attackLabel.setFont (Font ((float) attackLabel.getHeight() - 2.0f).boldened());
releaseLabel.setBounds (labelRect);
releaseLabel.setFont (Font ((float) releaseLabel.getHeight() - 2.0f).boldened());
attackSlider.setBounds (bounds.removeFromLeft (proportionOfWidth (0.5f)));
releaseSlider.setBounds (bounds);
for (auto* s : { &attackSlider, &releaseSlider })
s->setTextBoxStyle (Slider::TextBoxBelow, false, proportionOfWidth (0.4f), proportionOfHeight (0.137f));
}

private:
using SliderAttachment = AudioProcessorValueTreeState::SliderAttachment;
LevelDetectorVisualizer& visualiser;
AudioProcessorValueTreeState& vts;
ModulatableSlider attackSlider, releaseSlider;
SliderAttachment attackAttach, releaseAttach;
Label attackLabel, releaseLabel;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LevelDetectiveEditor)
};

customComps.add (std::make_unique<LevelDetectiveEditor> (levelVisualizer, vts, hcp));
return false;
}
38 changes: 38 additions & 0 deletions src/processors/other/LevelDetective.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma once

#include "../BaseProcessor.h"

class LevelDetective : public BaseProcessor
{
public:
explicit LevelDetective (UndoManager* um);

ProcessorType getProcessorType() const override { return Other; }
static ParamLayout createParameterLayout();

bool getCustomComponents (OwnedArray<Component>& customComps, chowdsp::HostContextProvider&) override;

void prepare (double sampleRate, int samplesPerBlock) override;
void processAudio (AudioBuffer<float>& buffer) override;
void processAudioBypassed (AudioBuffer<float>& buffer) override;

private:
enum InputPort
{
AudioInput
};

enum OutputPort
{
LevelOutput
};

chowdsp::FloatParameter* attackMsParam = nullptr;
chowdsp::FloatParameter* releaseMsParam = nullptr;

AudioBuffer<float> levelOutBuffer;
chowdsp::LevelDetector<float> level;
chowdsp::compressor::LevelDetectorVisualizer levelVisualizer;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LevelDetective)
};