Skip to content
Open
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
35 changes: 35 additions & 0 deletions plugin/src/AudioFileLoader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "AudioFileLoader.hpp"

std::vector<std::vector<double>>
AudioFileLoader::loadAudioFile (const juce::File& file)
{
juce::AudioFormatManager formatManager;
formatManager.registerBasicFormats();

std::unique_ptr<juce::AudioFormatReader> reader (
formatManager.createReaderFor (file)
);

if (! reader)
return {}; // failed to read

const int numChannels = reader->numChannels;
const int64 numSamples = reader->lengthInSamples;

juce::AudioBuffer<float> buffer ((int) numChannels, (int) numSamples);
reader->read (&buffer, 0, (int) numSamples, 0, true, true);

std::vector<std::vector<double>> 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<double> (data[i]);
}

return result;
}
10 changes: 10 additions & 0 deletions plugin/src/AudioFileLoader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once
#include <juce_audio_formats/juce_audio_formats.h>
#include <vector>

class AudioFileLoader
{
public:
static std::vector<std::vector<double>>
loadAudioFile (const juce::File& file);
};
71 changes: 68 additions & 3 deletions plugin/src/PluginEditor.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "PluginEditor.hpp"
#include "AudioFileLoader.hpp"

AdaptiveEchoAudioProcessorEditor::AdaptiveEchoAudioProcessorEditor(
AdaptiveEchoAudioProcessor &p)
Expand Down Expand Up @@ -70,18 +71,78 @@ AdaptiveEchoAudioProcessorEditor::AdaptiveEchoAudioProcessorEditor(
releaseAttachment = std::make_unique<SliderAttachment>(
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<juce::AudioProcessorValueTreeState::ComboBoxAttachment>(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<juce::AudioProcessorValueTreeState::ComboBoxAttachment>(processor.apvts, "soundCategory", soundCategoryBox);

addAndMakeVisible (openFileButton);

openFileButton.onClick = [this]
{
auto chooser = std::make_shared<juce::FileChooser>(
"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() {
Expand Down Expand Up @@ -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);
}
6 changes: 6 additions & 0 deletions plugin/src/PluginEditor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ class AdaptiveEchoAudioProcessorEditor : public juce::AudioProcessorEditor {
juce::Label decayLabel;
juce::Label sustainLabel;
juce::Label releaseLabel;
juce::ComboBox oscTypeBox;
std::unique_ptr<juce::AudioProcessorValueTreeState::ComboBoxAttachment> oscTypeAttachment;
juce::ComboBox soundCategoryBox;
std::unique_ptr<juce::AudioProcessorValueTreeState::ComboBoxAttachment> soundCategoryAttachment;

juce::TextButton openFileButton { "Open File" };

juce::MidiKeyboardComponent midiKeyboard{
processor.getMidiKeyboardState(),
Expand Down
6 changes: 3 additions & 3 deletions plugin/src/PluginEnvelope.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -80,4 +80,4 @@ class ADSREnvelope : public EnvelopeGenerator {
float a, d, s, r;
float ac, dc, rc;
int sr;
};
};
142 changes: 141 additions & 1 deletion plugin/src/PluginProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ AdaptiveEchoAudioProcessor::createParameterLayout() {
params.push_back(std::make_unique<juce::AudioParameterFloat>(
"release", "Release", ADSRrange, 0.5f));

// Oscillator type
params.push_back(std::make_unique<juce::AudioParameterChoice>(
"oscType", "Oscillator Type",
juce::StringArray{"Sine", "Square", "Saw"}, 0));

// Sound category
params.push_back(std::make_unique<juce::AudioParameterChoice>(
"soundCategory", "Sound Category",
juce::StringArray{"Happy", "Harsh", "Bright", "Dark"}, 0));

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

Expand Down Expand Up @@ -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<float> &buffer,
juce::MidiBuffer &midi) {
juce::ScopedNoDenormals noDenormals;
Expand All @@ -93,6 +144,16 @@ void AdaptiveEchoAudioProcessor::processBlock(juce::AudioBuffer<float> &buffer,
if (auto *rParam = apvts.getRawParameterValue("release"))
new_r = rParam->load();

int oscType = 0;
if (auto* param = apvts.getRawParameterValue("oscType"))
oscType = static_cast<int>(param->load());

int soundCategory = 0;
if (auto* param = apvts.getRawParameterValue("soundCategory"))
soundCategory = static_cast<int>(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;
Expand Down Expand Up @@ -139,7 +200,19 @@ void AdaptiveEchoAudioProcessor::processBlock(juce::AudioBuffer<float> &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<double>::twoPi) - 1.0f;
break;
}
out[n] = value * amp;

ph += phaseInc;
if (ph >= juce::MathConstants<double>::twoPi)
Expand Down Expand Up @@ -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<juce::AudioFormatReader> 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<juce::AudioBuffer<float>>(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];
}
}
14 changes: 13 additions & 1 deletion plugin/src/PluginProcessor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <JuceHeader.h>
#include <array>
#include <vector>
#include <juce_audio_formats/juce_audio_formats.h>

#include "Note.hpp"
#include "PluginEnvelope.hpp"
Expand Down Expand Up @@ -53,14 +54,25 @@ class AdaptiveEchoAudioProcessor : public juce::AudioProcessor {
return midiState;
}

void loadFile(const juce::File& f);
std::vector<double> 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<double, 2> phase{0.0, 0.0}; // support up to stereo
double phaseInc = 0.0; // radians per sample
double currentSampleRate = 48000.0;
Note activeNote;
ADSREnvelope env;
std::shared_ptr<ADSREnvelope> env_ptr;
std::shared_ptr<juce::AudioBuffer<float>> audioBuffer;

// Smoothed volume to avoid zipper noise
juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear>
Expand All @@ -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)
};
};