diff --git a/plugin/src/AudioFileLoader.cpp b/plugin/src/AudioFileLoader.cpp new file mode 100644 index 0000000..0c21b5e --- /dev/null +++ b/plugin/src/AudioFileLoader.cpp @@ -0,0 +1,35 @@ +#include "AudioFileLoader.hpp" + +std::vector> +AudioFileLoader::loadAudioFile (const juce::File& file) +{ + juce::AudioFormatManager formatManager; + formatManager.registerBasicFormats(); + + std::unique_ptr reader ( + formatManager.createReaderFor (file) + ); + + if (! reader) + return {}; // failed to read + + const int numChannels = reader->numChannels; + const int64 numSamples = reader->lengthInSamples; + + juce::AudioBuffer buffer ((int) numChannels, (int) numSamples); + reader->read (&buffer, 0, (int) numSamples, 0, true, true); + + std::vector> result; + result.resize (numChannels); + + for (int ch = 0; ch < numChannels; ++ch) + { + result[ch].resize (numSamples); + const float* data = buffer.getReadPointer (ch); + + for (int i = 0; i < numSamples; ++i) + result[ch][i] = static_cast (data[i]); + } + + return result; +} diff --git a/plugin/src/AudioFileLoader.hpp b/plugin/src/AudioFileLoader.hpp new file mode 100644 index 0000000..8662ab8 --- /dev/null +++ b/plugin/src/AudioFileLoader.hpp @@ -0,0 +1,10 @@ +#pragma once +#include +#include + +class AudioFileLoader +{ +public: + static std::vector> + loadAudioFile (const juce::File& file); +}; diff --git a/plugin/src/PluginEditor.cpp b/plugin/src/PluginEditor.cpp index 778201d..6800401 100644 --- a/plugin/src/PluginEditor.cpp +++ b/plugin/src/PluginEditor.cpp @@ -1,4 +1,5 @@ #include "PluginEditor.hpp" +#include "AudioFileLoader.hpp" AdaptiveEchoAudioProcessorEditor::AdaptiveEchoAudioProcessorEditor( AdaptiveEchoAudioProcessor &p) @@ -70,18 +71,78 @@ AdaptiveEchoAudioProcessorEditor::AdaptiveEchoAudioProcessorEditor( releaseAttachment = std::make_unique( processor.apvts, "release", releaseSlider); + // Oscillator type combo box + oscTypeBox.addItem("Sine", 1); + oscTypeBox.addItem("Square", 2); + oscTypeBox.addItem("Saw", 3); + addAndMakeVisible(oscTypeBox); + + // Attach to processor parameter + oscTypeAttachment = std::make_unique(processor.apvts, "oscType", oscTypeBox); + + // Sound category combo box + soundCategoryBox.addItem("Happy", 1); + soundCategoryBox.addItem("Harsh", 2); + soundCategoryBox.addItem("Bright", 3); + soundCategoryBox.addItem("Dark", 4); + addAndMakeVisible(soundCategoryBox); + + // Attach to processor parameter + soundCategoryAttachment = + std::make_unique(processor.apvts, "soundCategory", soundCategoryBox); + + addAndMakeVisible (openFileButton); + + openFileButton.onClick = [this] + { + auto chooser = std::make_shared( + "Select an audio file", + juce::File{}, + "*.wav;*.aiff;*.mp3" + ); + + chooser->launchAsync ( + juce::FileBrowserComponent::openMode + | juce::FileBrowserComponent::canSelectFiles, + [this, chooser] (const juce::FileChooser& fc) + { + auto file = fc.getResult(); + if (! file.existsAsFile()) + return; + processor.loadFile (file); + repaint(); // forces paint() to update with new sample text + } + ); + }; + addAndMakeVisible(midiKeyboard); midiKeyboard.setAvailableRange(24, 108); } void AdaptiveEchoAudioProcessorEditor::paint(juce::Graphics &g) { - g.fillAll( - getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId)); + g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId)); g.setColour(juce::Colours::white); g.setFont(16.0f); g.drawFittedText("Adaptive Echo - Sine Generator Example (w/ MIDI)", getLocalBounds().reduced(10, 6), juce::Justification::centredTop, 1); + + // Only display first 10 samples + g.setFont(14.0f); + auto y = 50; + g.drawText("First 10 samples:", 10, y, 400, 20, juce::Justification::left); + y += 20; + + juce::String s; + int numToShow = std::min(10, (int)processor.loadedSamples.size()); + for (int i = 0; i < numToShow; ++i) + s += juce::String(processor.loadedSamples[i], 5) + " "; + g.drawText(s, 10, y, 400, 20, juce::Justification::left); + + // Debug: print a few samples to console only, NOT all + int numToPrint = std::min(1000, (int)processor.loadedSamples.size()); + for (int i = 0; i < numToPrint; ++i) + DBG("Sample " << i << ": " << processor.loadedSamples[i]); } void AdaptiveEchoAudioProcessorEditor::resized() { @@ -114,4 +175,8 @@ void AdaptiveEchoAudioProcessorEditor::resized() { releaseSlider.setBounds(controlArea.removeFromLeft(sliderWidth).reduced(4)); releaseLabel.setBounds(releaseSlider.getX(), releaseSlider.getBottom(), releaseSlider.getWidth(), 20); -} + + openFileButton.setBounds(10, 10, 120, 24); + oscTypeBox.setBounds(openFileButton.getRight() + 10, 10, 100, 24); + soundCategoryBox.setBounds(oscTypeBox.getRight() + 10, 10, 120, 24); +} \ No newline at end of file diff --git a/plugin/src/PluginEditor.hpp b/plugin/src/PluginEditor.hpp index 3b8ff85..d509a8f 100644 --- a/plugin/src/PluginEditor.hpp +++ b/plugin/src/PluginEditor.hpp @@ -27,6 +27,12 @@ class AdaptiveEchoAudioProcessorEditor : public juce::AudioProcessorEditor { juce::Label decayLabel; juce::Label sustainLabel; juce::Label releaseLabel; + juce::ComboBox oscTypeBox; + std::unique_ptr oscTypeAttachment; + juce::ComboBox soundCategoryBox; + std::unique_ptr soundCategoryAttachment; + + juce::TextButton openFileButton { "Open File" }; juce::MidiKeyboardComponent midiKeyboard{ processor.getMidiKeyboardState(), diff --git a/plugin/src/PluginEnvelope.hpp b/plugin/src/PluginEnvelope.hpp index 3c88089..1b29979 100644 --- a/plugin/src/PluginEnvelope.hpp +++ b/plugin/src/PluginEnvelope.hpp @@ -11,13 +11,13 @@ class EnvelopeGenerator { float initialValue; float finalValue; float curvature; - unsigned int lengthSamples; // 0 for sustain + int lengthSamples; // 0 for sustain Segment(float iv, float fv, float c, unsigned int l) : initialValue(iv), finalValue(fv), curvature(c), lengthSamples(l) { } - float fx(unsigned int x) const { + float fx(int x) const { if (lengthSamples == 0) return finalValue; // sustain if (x > lengthSamples) @@ -80,4 +80,4 @@ class ADSREnvelope : public EnvelopeGenerator { float a, d, s, r; float ac, dc, rc; int sr; -}; \ No newline at end of file +}; diff --git a/plugin/src/PluginProcessor.cpp b/plugin/src/PluginProcessor.cpp index 3a228af..9b76c50 100644 --- a/plugin/src/PluginProcessor.cpp +++ b/plugin/src/PluginProcessor.cpp @@ -30,6 +30,16 @@ AdaptiveEchoAudioProcessor::createParameterLayout() { params.push_back(std::make_unique( "release", "Release", ADSRrange, 0.5f)); + // Oscillator type + params.push_back(std::make_unique( + "oscType", "Oscillator Type", + juce::StringArray{"Sine", "Square", "Saw"}, 0)); + + // Sound category + params.push_back(std::make_unique( + "soundCategory", "Sound Category", + juce::StringArray{"Happy", "Harsh", "Bright", "Dark"}, 0)); + return {params.begin(), params.end()}; } @@ -72,6 +82,47 @@ bool AdaptiveEchoAudioProcessor::isBusesLayoutSupported( } #endif +void AdaptiveEchoAudioProcessor::applySoundCategory( + int category, + float& a, float& d, float& s, float& r, + int& oscType) +{ + switch (category) + { + case 0: // Happy + a *= 0.6f; // faster attack + d *= 0.7f; + s = std::max(s, 0.7f); + r *= 0.8f; + oscType = 0; // Sine + break; + + case 1: // Harsh + a *= 0.3f; + d *= 0.5f; + s = 1.0f; + r *= 1.2f; + oscType = 1; // Square + break; + + case 2: // Bright + a *= 0.5f; + d *= 0.8f; + oscType = 2; // Saw + break; + + case 3: // Dark + a *= 1.5f; + d *= 1.2f; + s *= 0.8f; + oscType = 0; // Sine + break; + + default: + break; + } +} + void AdaptiveEchoAudioProcessor::processBlock(juce::AudioBuffer &buffer, juce::MidiBuffer &midi) { juce::ScopedNoDenormals noDenormals; @@ -93,6 +144,16 @@ void AdaptiveEchoAudioProcessor::processBlock(juce::AudioBuffer &buffer, if (auto *rParam = apvts.getRawParameterValue("release")) new_r = rParam->load(); + int oscType = 0; + if (auto* param = apvts.getRawParameterValue("oscType")) + oscType = static_cast(param->load()); + + int soundCategory = 0; + if (auto* param = apvts.getRawParameterValue("soundCategory")) + soundCategory = static_cast(param->load()); + + applySoundCategory(soundCategory, new_a, new_d, new_s, new_r, oscType); + // Update ADSR parameters if (new_a != a || new_d != d || new_s != s || new_r != r) { a = new_a; @@ -139,7 +200,19 @@ void AdaptiveEchoAudioProcessor::processBlock(juce::AudioBuffer &buffer, float globalVol = volumeSmoothed.getNextValue(); float amp = noteLevel * globalVol; - out[n] = std::sin(ph) * amp; + float value = 0.0f; + switch (oscType) { + case 0: // Sine + value = std::sin(ph); + break; + case 1: // Square + value = (std::sin(ph) >= 0.0 ? 1.0f : -1.0f); + break; + case 2: // Saw + value = 2.0f * (ph / juce::MathConstants::twoPi) - 1.0f; + break; + } + out[n] = value * amp; ph += phaseInc; if (ph >= juce::MathConstants::twoPi) @@ -189,4 +262,71 @@ juce::AudioProcessorEditor *AdaptiveEchoAudioProcessor::createEditor() { // This factory must be present in the TU with the processor class. juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter() { return new AdaptiveEchoAudioProcessor(); +} + +void AdaptiveEchoAudioProcessor::loadFile(const juce::File& f) +{ + juce::AudioFormatManager formatManager; + formatManager.registerBasicFormats(); + + std::unique_ptr reader(formatManager.createReaderFor(f)); + /* Adding a debug message here. Right now we are able to load wav files, + * but not able to load mp3 files. More specifically, our program will load the waveform from a wav file + * and print the first few samples; but for mp3 files, we do not print any samples; + * the reason for that is JUCE does not have an MP3 decoder available, at least on Linux systems. + * Therefore: reader is nullptr, loadFile() exits early, audioBuffer is never set, and loadedSamples remains empty + * */ + if (!reader) { + // DBG("Could not create reader for file: " + f.getFullPathName()); + juce::AlertWindow::showMessageBoxAsync( + juce::AlertWindow::WarningIcon, + "Unsupported Audio Format", + "This plugin supports WAV and AIFF files only.\n\n" + "MP3 decoding is not available on this system." + ); + return; + } + + auto tempBuffer = std::make_shared>(reader->numChannels, (int)reader->lengthInSamples); + reader->read(tempBuffer.get(), 0, (int)reader->lengthInSamples, 0, true, true); + audioBuffer = tempBuffer; + + // Populate loadedSamples for GUI display + loadedSamples.clear(); + if (audioBuffer) + { + auto& buf = *audioBuffer; + for (int ch = 0; ch < buf.getNumChannels(); ++ch) + { + auto* ptr = buf.getReadPointer(ch); + loadedSamples.insert(loadedSamples.end(), ptr, ptr + buf.getNumSamples()); + } + } + + DBG(juce::String::formatted("Loaded %d samples from %d channels", + (int)reader->lengthInSamples, + reader->numChannels)); +} + +void AdaptiveEchoAudioProcessor::getNextAudioBlock(const juce::AudioSourceChannelInfo& bufferToFill) +{ + if (!audioBuffer) + { + bufferToFill.clearActiveBufferRegion(); + return; + } + + const auto& buf = *audioBuffer; // Dereference shared_ptr + + int numChannels = std::min(bufferToFill.buffer->getNumChannels(), buf.getNumChannels()); + int numSamples = std::min(bufferToFill.buffer->getNumSamples(), buf.getNumSamples()); + + for (int ch = 0; ch < numChannels; ++ch) + { + auto* dst = bufferToFill.buffer->getWritePointer(ch); + const float* src = buf.getReadPointer(ch); + + for (int i = 0; i < numSamples; ++i) + dst[i] = src[i]; + } } \ No newline at end of file diff --git a/plugin/src/PluginProcessor.hpp b/plugin/src/PluginProcessor.hpp index 0d48418..8819855 100644 --- a/plugin/src/PluginProcessor.hpp +++ b/plugin/src/PluginProcessor.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "Note.hpp" #include "PluginEnvelope.hpp" @@ -53,7 +54,17 @@ class AdaptiveEchoAudioProcessor : public juce::AudioProcessor { return midiState; } + void loadFile(const juce::File& f); + std::vector loadedSamples; + // audio callback + void getNextAudioBlock(const juce::AudioSourceChannelInfo& bufferToFill); + private: + void applySoundCategory( + int category, + float& a, float& d, float& s, float& r, + int& oscType + ); // Simple sine generator state per channel std::array phase{0.0, 0.0}; // support up to stereo double phaseInc = 0.0; // radians per sample @@ -61,6 +72,7 @@ class AdaptiveEchoAudioProcessor : public juce::AudioProcessor { Note activeNote; ADSREnvelope env; std::shared_ptr env_ptr; + std::shared_ptr> audioBuffer; // Smoothed volume to avoid zipper noise juce::SmoothedValue @@ -72,4 +84,4 @@ class AdaptiveEchoAudioProcessor : public juce::AudioProcessor { float a, d, s, r, ac, dc, rc; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AdaptiveEchoAudioProcessor) -}; +}; \ No newline at end of file