diff --git a/CMakeLists.txt b/CMakeLists.txt index b17c401..cfef54d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,8 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") add_compile_options( -Wall -Wextra -Wpedantic -Werror - -Wreorder -Wdeprecated-declarations + $<$:-Wreorder> + -Wdeprecated-declarations -Wunused -Wswitch -Wreturn-type ) elseif(MSVC) diff --git a/peaks/100475__iluppai__saxophone-weep.wav.reapeaks b/peaks/100475__iluppai__saxophone-weep.wav.reapeaks new file mode 100644 index 0000000..f954bab Binary files /dev/null and b/peaks/100475__iluppai__saxophone-weep.wav.reapeaks differ diff --git a/peaks/707236__arseniiv__stereo-noise-from-detergent-foam-sparkling.flac.reapeaks b/peaks/707236__arseniiv__stereo-noise-from-detergent-foam-sparkling.flac.reapeaks new file mode 100644 index 0000000..a9c31bb Binary files /dev/null and b/peaks/707236__arseniiv__stereo-noise-from-detergent-foam-sparkling.flac.reapeaks differ diff --git a/plugins/Audio2Midi/CMakeLists.txt b/plugins/Audio2Midi/CMakeLists.txt index fd93d2a..4398e69 100644 --- a/plugins/Audio2Midi/CMakeLists.txt +++ b/plugins/Audio2Midi/CMakeLists.txt @@ -56,6 +56,7 @@ target_link_libraries(${PROJECT_NAME} Audio2MidiBinaryData essentia Eigen3::Eigen + curl juce::juce_audio_processors juce::juce_audio_utils juce::juce_dsp diff --git a/plugins/Energy/CMakeLists.txt b/plugins/Energy/CMakeLists.txt index e0dd55c..f108328 100644 --- a/plugins/Energy/CMakeLists.txt +++ b/plugins/Energy/CMakeLists.txt @@ -47,6 +47,7 @@ target_link_libraries(${PROJECT_NAME} EnergyBinaryData essentia Eigen3::Eigen + curl juce::juce_audio_processors juce::juce_audio_utils juce::juce_dsp diff --git a/plugins/RMS/CMakeLists.txt b/plugins/RMS/CMakeLists.txt index 901a3bc..4b81bb1 100644 --- a/plugins/RMS/CMakeLists.txt +++ b/plugins/RMS/CMakeLists.txt @@ -47,6 +47,7 @@ target_link_libraries(${PROJECT_NAME} RMSBinaryData essentia Eigen3::Eigen + curl juce::juce_audio_processors juce::juce_audio_utils juce::juce_dsp diff --git a/plugins/ZeroCrossingRate/CMakeLists.txt b/plugins/ZeroCrossingRate/CMakeLists.txt index 8cee3ad..e439d21 100644 --- a/plugins/ZeroCrossingRate/CMakeLists.txt +++ b/plugins/ZeroCrossingRate/CMakeLists.txt @@ -47,6 +47,7 @@ target_link_libraries(${PROJECT_NAME} ZCRAnalyzerBinaryData essentia Eigen3::Eigen + curl juce::juce_audio_processors juce::juce_audio_utils juce::juce_dsp diff --git a/plugins/essentia-flatness_projucer/Source/PluginEditor.cpp b/plugins/essentia-flatness_projucer/Source/PluginEditor.cpp new file mode 100644 index 0000000..a0004a2 --- /dev/null +++ b/plugins/essentia-flatness_projucer/Source/PluginEditor.cpp @@ -0,0 +1,139 @@ +#include "PluginEditor.h" + +ModernLookAndFeel::ModernLookAndFeel() +{ + setColour(juce::Label::textColourId, juce::Colours::white); +} + +EssentiaPluginAudioProcessorEditor::EssentiaPluginAudioProcessorEditor(EssentiaPluginAudioProcessor& p) + : AudioProcessorEditor(&p) + , processorRef(p) +{ + setLookAndFeel(&modernLF); + + titleLabel.setText("Flatness Analyzer", juce::dontSendNotification); + titleLabel.setFont(juce::FontOptions(24.0f)); + titleLabel.setColour(juce::Label::textColourId, juce::Colours::white); + titleLabel.setJustificationType(juce::Justification::centred); + titleLabel.setInterceptsMouseClicks(false, false); + addAndMakeVisible(titleLabel); + + subtitleLabel.setText("Real-time spectral flatness analysis", juce::dontSendNotification); + subtitleLabel.setFont(juce::FontOptions(14.0f)); + subtitleLabel.setColour(juce::Label::textColourId, juce::Colour(0xffa0aec0)); + subtitleLabel.setJustificationType(juce::Justification::centred); + addAndMakeVisible(subtitleLabel); + + // Main flatness value + rmsLinearValueLabel.setText("0.000", juce::dontSendNotification); + rmsLinearValueLabel.setFont(juce::FontOptions(48.0f, juce::Font::bold)); + rmsLinearValueLabel.setColour(juce::Label::textColourId, juce::Colour(0xffed64a6)); + rmsLinearValueLabel.setJustificationType(juce::Justification::centred); + addAndMakeVisible(rmsLinearValueLabel); + + rmsLinearUnitLabel.setText("Flatness (0=tonal, 1=noise)", juce::dontSendNotification); + rmsLinearUnitLabel.setFont(juce::FontOptions(14.0f)); + rmsLinearUnitLabel.setColour(juce::Label::textColourId, juce::Colour(0xffa0aec0)); + rmsLinearUnitLabel.setJustificationType(juce::Justification::centred); + addAndMakeVisible(rmsLinearUnitLabel); + + // Repurpose dB label to show noise/tonal character + rmsDbValueLabel.setText("Tonal", juce::dontSendNotification); + rmsDbValueLabel.setFont(juce::FontOptions(48.0f, juce::Font::bold)); + rmsDbValueLabel.setColour(juce::Label::textColourId, juce::Colour(0xffb794f6)); + rmsDbValueLabel.setJustificationType(juce::Justification::centred); + addAndMakeVisible(rmsDbValueLabel); + + rmsDbUnitLabel.setText("Character", juce::dontSendNotification); + rmsDbUnitLabel.setFont(juce::FontOptions(14.0f)); + rmsDbUnitLabel.setColour(juce::Label::textColourId, juce::Colour(0xffa0aec0)); + rmsDbUnitLabel.setJustificationType(juce::Justification::centred); + addAndMakeVisible(rmsDbUnitLabel); + + upfLogo = juce::ImageCache::getFromMemory(BinaryData::upflogo_png, BinaryData::upflogo_pngSize); + essentiaLogo = juce::ImageCache::getFromMemory(BinaryData::essentia_logo_png, BinaryData::essentia_logo_pngSize); + + poweredByLabel.setText("powered by", juce::dontSendNotification); + poweredByLabel.setFont(juce::FontOptions(8.30437f)); + poweredByLabel.setColour(juce::Label::textColourId, juce::Colour(0xff718096)); + poweredByLabel.setJustificationType(juce::Justification::centred); + addAndMakeVisible(poweredByLabel); + + setSize(400, 350); + setResizable(false, false); + startTimerHz(30); +} + +EssentiaPluginAudioProcessorEditor::~EssentiaPluginAudioProcessorEditor() +{ + setLookAndFeel(nullptr); +} + +void EssentiaPluginAudioProcessorEditor::timerCallback() +{ + const float flatness = processorRef.getFlatness(); + + rmsLinearValueLabel.setText(juce::String(flatness, 3), juce::dontSendNotification); + + // Show character based on flatness value + juce::String character; + if (flatness < 0.2f) + character = "Tonal"; + else if (flatness < 0.5f) + character = "Mixed"; + else + character = "Noise"; + + rmsDbValueLabel.setText(character, juce::dontSendNotification); + + repaint(); +} + +void EssentiaPluginAudioProcessorEditor::paint(juce::Graphics& g) +{ + g.fillAll(juce::Colour(0xff1a202c)); + + // Draw a bar showing flatness level + const float flatness = processorRef.getFlatness(); + const auto barArea = getLocalBounds().reduced(20).removeFromBottom(20); + g.setColour(juce::Colour(0xff2d3748)); + g.fillRect(barArea); + g.setColour(juce::Colour(0xffed64a6)); + g.fillRect(barArea.withWidth((int)(barArea.getWidth() * flatness))); + + // Draw logos + if (upfLogo.isValid()) + g.drawImageWithin(upfLogo, 10, getHeight() - 40, 60, 30, juce::RectanglePlacement::centred); + if (essentiaLogo.isValid()) + g.drawImageWithin(essentiaLogo, getWidth() - 80, getHeight() - 40, 70, 30, juce::RectanglePlacement::centred); +} + +void EssentiaPluginAudioProcessorEditor::resized() +{ + auto area = getLocalBounds().reduced(20); + + titleLabel.setBounds(area.removeFromTop(40)); + subtitleLabel.setBounds(area.removeFromTop(25)); + area.removeFromTop(10); + + auto row1 = area.removeFromTop(80); + rmsLinearValueLabel.setBounds(row1.removeFromLeft(row1.getWidth() / 2)); + rmsDbValueLabel.setBounds(row1); + + auto row2 = area.removeFromTop(25); + rmsLinearUnitLabel.setBounds(row2.removeFromLeft(row2.getWidth() / 2)); + rmsDbUnitLabel.setBounds(row2); + + area.removeFromTop(10); + poweredByLabel.setBounds(area.removeFromTop(20)); +} + +void EssentiaPluginAudioProcessorEditor::mouseDown(const juce::MouseEvent& event) +{ + juce::ignoreUnused(event); +} + +void EssentiaPluginAudioProcessorEditor::mouseMove(const juce::MouseEvent& event) +{ + juce::ignoreUnused(event); +} \ No newline at end of file diff --git a/plugins/essentia-flatness_projucer/Source/PluginEditor.h b/plugins/essentia-flatness_projucer/Source/PluginEditor.h new file mode 100644 index 0000000..2ea4beb --- /dev/null +++ b/plugins/essentia-flatness_projucer/Source/PluginEditor.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include "BinaryData.h" +#include "PluginProcessor.h" + +//============================================================================== +// Custom Look and Feel for modern styling +class ModernLookAndFeel : public juce::LookAndFeel_V4 +{ +public: + ModernLookAndFeel(); +}; + +//============================================================================== +class EssentiaPluginAudioProcessorEditor final : public juce::AudioProcessorEditor, private juce::Timer +{ +public: + explicit EssentiaPluginAudioProcessorEditor(EssentiaPluginAudioProcessor&); + ~EssentiaPluginAudioProcessorEditor() override; + + //============================================================================== + void paint(juce::Graphics&) override; + void resized() override; + void mouseDown(const juce::MouseEvent& event) override; + void mouseMove(const juce::MouseEvent& event) override; + +private: + // This reference is provided as a quick way for your editor to + // access the processor object that created it. + EssentiaPluginAudioProcessor& processorRef; + void timerCallback() override; + + // UI Components + juce::Label titleLabel; + juce::Label subtitleLabel; + juce::Label rmsLinearValueLabel; + juce::Label rmsDbValueLabel; + juce::Label rmsLinearUnitLabel; + juce::Label rmsDbUnitLabel; + + // Custom styling + ModernLookAndFeel modernLF; + + // Logos + juce::Image upfLogo; + juce::Image essentiaLogo; + + // Additional label for "powered by" text + juce::Label poweredByLabel; + + // Easter egg method + void showDeveloperInfo(); + + // Hover state for title + bool titleHovered = false; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EssentiaPluginAudioProcessorEditor) +}; diff --git a/plugins/essentia-flatness_projucer/Source/PluginProcessor.cpp b/plugins/essentia-flatness_projucer/Source/PluginProcessor.cpp new file mode 100644 index 0000000..1e682a6 --- /dev/null +++ b/plugins/essentia-flatness_projucer/Source/PluginProcessor.cpp @@ -0,0 +1,210 @@ +/* + ============================================================================== + + This file contains the basic framework code for a JUCE plugin processor. + + ============================================================================== +*/ + +#include "PluginProcessor.h" +#include "PluginEditor.h" + +using essentia::standard::AlgorithmFactory; +using essentia::Real; + + +//============================================================================== +// clang-format off + +EssentiaPluginAudioProcessor::EssentiaPluginAudioProcessor() + : AudioProcessor (BusesProperties() + #if ! JucePlugin_IsMidiEffect + #if ! JucePlugin_IsSynth + .withInput ("Input", juce::AudioChannelSet::stereo(), true) + #endif + .withOutput ("Output", juce::AudioChannelSet::stereo(), true) + #endif + ) +{ +} +// clang-format on + +EssentiaPluginAudioProcessor::~EssentiaPluginAudioProcessor() {} + +//============================================================================== +const juce::String EssentiaPluginAudioProcessor::getName() const +{ + return JucePlugin_Name; +} + +bool EssentiaPluginAudioProcessor::acceptsMidi() const +{ + #if JucePlugin_WantsMidiInput + return true; + #else + return false; + #endif +} + +bool EssentiaPluginAudioProcessor::producesMidi() const +{ + #if JucePlugin_ProducesMidiOutput + return true; + #else + return false; + #endif +} + +bool EssentiaPluginAudioProcessor::isMidiEffect() const +{ + #if JucePlugin_IsMidiEffect + return true; + #else + return false; + #endif +} + +double EssentiaPluginAudioProcessor::getTailLengthSeconds() const +{ + return 0.0; +} + +int EssentiaPluginAudioProcessor::getNumPrograms() +{ + return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, + // so this should be at least 1, even if you're not really implementing programs. +} + +int EssentiaPluginAudioProcessor::getCurrentProgram() +{ + return 0; +} + +void EssentiaPluginAudioProcessor::setCurrentProgram (int index) +{ + juce::ignoreUnused(index); +} + +const juce::String EssentiaPluginAudioProcessor::getProgramName (int index) +{ + juce::ignoreUnused(index); + return {}; +} + +void EssentiaPluginAudioProcessor::changeProgramName (int index, const juce::String& newName) +{ + juce::ignoreUnused(index, newName); +} + +void EssentiaPluginAudioProcessor::applyZeroPadding(std::vector& buffer, int maxSampleSize) +{ + const int currentSize = static_cast(buffer.size()); + + if (currentSize < maxSampleSize) + { + // Reserve and fill with zeros - much faster than push_back loop + buffer.reserve(maxSampleSize); + buffer.resize(maxSampleSize, 0.0f); // Single operation! + } + else if (currentSize > maxSampleSize) + { + // Trim to desired size + buffer.resize(maxSampleSize); + } + // If equal, do nothing +} + +//============================================================================== +void EssentiaPluginAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) +{ + juce::ignoreUnused(sampleRate, samplesPerBlock); + + essentia::init(); + + auto& factory = essentia::standard::AlgorithmFactory::instance(); + + windowing = factory.create("Windowing", "type", "hann", "size", frameSize); + spectrum = factory.create("Spectrum", "size", frameSize); + flatness = factory.create("Flatness"); + + essentiaBuffer.assign(frameSize, 0.f); + windowedBuffer.assign(frameSize, 0.f); + spectrumBuffer.assign(frameSize / 2 + 1, 0.f); + + windowing->input("frame").set(essentiaBuffer); + windowing->output("frame").set(windowedBuffer); + + spectrum->input("frame").set(windowedBuffer); + spectrum->output("spectrum").set(spectrumBuffer); + + flatness->input("array").set(spectrumBuffer); + flatness->output("flatness").set(flatnessValue); + + windowing->compute(); + spectrum->compute(); + flatness->compute(); +} + +void EssentiaPluginAudioProcessor::releaseResources() +{ + delete windowing; windowing = nullptr; + delete spectrum; spectrum = nullptr; + delete flatness; flatness = nullptr; + essentiaBuffer.clear(); + essentia::shutdown(); +} + +void EssentiaPluginAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) +{ + juce::ignoreUnused(midiMessages); + + const float* in = buffer.getReadPointer(0); + essentiaBuffer.assign(in, in + buffer.getNumSamples()); + applyZeroPadding(essentiaBuffer, frameSize); + + windowing->compute(); + spectrum->compute(); + flatness->compute(); +} + +//============================================================================== +bool EssentiaPluginAudioProcessor::hasEditor() const +{ + return true; // (change this to false if you choose to not supply an editor) +} + +juce::AudioProcessorEditor* EssentiaPluginAudioProcessor::createEditor() +{ + return new EssentiaPluginAudioProcessorEditor (*this); +} + +//============================================================================== +void EssentiaPluginAudioProcessor::getStateInformation (juce::MemoryBlock& destData) +{ + // You should use this method to store your parameters in the memory block. + // You could do that either as raw data, or use the XML or ValueTree classes + // as intermediaries to make it easy to save and load complex data. + juce::ignoreUnused(destData); +} + +void EssentiaPluginAudioProcessor::setStateInformation (const void* data, int sizeInBytes) +{ + // You should use this method to restore your parameters from this memory block, + // whose contents will have been created by the getStateInformation() call. + juce::ignoreUnused(data, sizeInBytes); +} + +bool EssentiaPluginAudioProcessor::isBusesLayoutSupported(const BusesLayout& layouts) const +{ + if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() + && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) + return false; + return true; +} + +//============================================================================== +// This creates new instances of the plugin.. +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new EssentiaPluginAudioProcessor(); +} diff --git a/plugins/essentia-flatness_projucer/Source/PluginProcessor.h b/plugins/essentia-flatness_projucer/Source/PluginProcessor.h new file mode 100644 index 0000000..88af2ce --- /dev/null +++ b/plugins/essentia-flatness_projucer/Source/PluginProcessor.h @@ -0,0 +1,59 @@ +/* + ============================================================================== + + This file contains the basic framework code for a JUCE plugin processor. + + ============================================================================== +*/ +#pragma once +#include +#include +#include +#include + +class EssentiaPluginAudioProcessor : public juce::AudioProcessor +{ +public: + EssentiaPluginAudioProcessor(); + ~EssentiaPluginAudioProcessor() override; + + void prepareToPlay (double sampleRate, int samplesPerBlock) override; + void releaseResources() override; + #ifndef JucePlugin_PreferredChannelConfigurations + bool isBusesLayoutSupported (const BusesLayout& layouts) const override; + #endif + void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; + + juce::AudioProcessorEditor* createEditor() override; + bool hasEditor() const override; + const juce::String getName() const override; + bool acceptsMidi() const override; + bool producesMidi() const override; + bool isMidiEffect() const override; + double getTailLengthSeconds() const override; + int getNumPrograms() override; + int getCurrentProgram() override; + void setCurrentProgram (int index) override; + const juce::String getProgramName (int index) override; + void changeProgramName (int index, const juce::String& newName) override; + void getStateInformation (juce::MemoryBlock& destData) override; + void setStateInformation (const void* data, int sizeInBytes) override; + + void applyZeroPadding(std::vector& buffer, int maxSampleSize); + + float getFlatness() const { return flatnessValue; } + +private: + essentia::standard::Algorithm* windowing = nullptr; + essentia::standard::Algorithm* spectrum = nullptr; + essentia::standard::Algorithm* flatness = nullptr; + + essentia::Real flatnessValue { 0.0f }; + std::vector essentiaBuffer; + std::vector windowedBuffer; + std::vector spectrumBuffer; + + int frameSize { 1024 }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EssentiaPluginAudioProcessor) +}; \ No newline at end of file diff --git a/plugins/essentia-flatness_projucer/essentia-flatness.jucer b/plugins/essentia-flatness_projucer/essentia-flatness.jucer new file mode 100644 index 0000000..e56af2f --- /dev/null +++ b/plugins/essentia-flatness_projucer/essentia-flatness.jucer @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +