Showing preview only (6,304K chars total). Download the full file or copy to clipboard to get everything.
Repository: hollance/mda-plugins-juce
Branch: master
Commit: 552702479a9e
Files: 101
Total size: 6.0 MB
Directory structure:
gitextract_e_m88lqn/
├── .gitignore
├── Ambience/
│ ├── MDAAmbience.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Bandisto/
│ ├── MDABandisto.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── BeatBox/
│ ├── MDABeatBox.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── DX10/
│ ├── DX10.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Degrade/
│ ├── MDADegrade.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Delay/
│ ├── MDADelay.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Detune/
│ ├── MDADetune.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Dither/
│ ├── MDADither.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Dynamics/
│ ├── MDADynamics.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── EPiano/
│ ├── MDAEPiano.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ ├── PluginProcessor.h
│ └── mdaEPianoData.h
├── Envelope/
│ ├── MDAEnvelope.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Image/
│ ├── MDAImage.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── JX10/
│ ├── JX10.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── LICENSE.txt
├── Limiter/
│ ├── MDALimiter.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Loudness/
│ ├── MDALoudness.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Overdrive/
│ ├── MDAOverdrive.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Piano/
│ ├── MDAPiano.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ ├── PluginProcessor.h
│ └── mdaPianoData.h
├── README.markdown
├── RezFilter/
│ ├── MDARezFilter.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── RingMod/
│ ├── MDARingMod.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Shepard/
│ ├── MDAShepard.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Splitter/
│ ├── MDASplitter.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Stereo/
│ ├── MDAStereo.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── SubSynth/
│ ├── MDASubSynth.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
└── TestTone/
├── MDATestTone.jucer
├── README.markdown
└── Source/
├── PluginProcessor.cpp
└── PluginProcessor.h
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
**/.DS_Store
**/Builds
**/JuceLibraryCode
**/*.filtergraph
================================================
FILE: Ambience/MDAAmbience.jucer
================================================
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT id="MKdKFI" name="MDAAmbience" projectType="audioplug" useAppConfig="0"
addUsingNamespaceToJuceHeader="0" displaySplashScreen="1" jucerFormatVersion="1">
<MAINGROUP id="pWCQCF" name="MDAAmbience">
<GROUP id="{C0B91FFE-2BB2-4D81-5588-C34AAE6DD5E7}" name="Source">
<FILE id="fou4g3" name="PluginProcessor.cpp" compile="1" resource="0"
file="Source/PluginProcessor.cpp"/>
<FILE id="RS09XD" name="PluginProcessor.h" compile="0" resource="0"
file="Source/PluginProcessor.h"/>
</GROUP>
</MAINGROUP>
<JUCEOPTIONS JUCE_STRICT_REFCOUNTEDPOINTER="1" JUCE_VST3_CAN_REPLACE_VST2="0"/>
<EXPORTFORMATS>
<XCODE_MAC targetFolder="Builds/MacOSX">
<CONFIGURATIONS>
<CONFIGURATION isDebug="1" name="Debug" targetName="MDAAmbience"/>
<CONFIGURATION isDebug="0" name="Release" targetName="MDAAmbience"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_devices" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_formats" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_plugin_client" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_processors" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_utils" path="../../JUCE/modules"/>
<MODULEPATH id="juce_core" path="../../JUCE/modules"/>
<MODULEPATH id="juce_data_structures" path="../../JUCE/modules"/>
<MODULEPATH id="juce_events" path="../../JUCE/modules"/>
<MODULEPATH id="juce_graphics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_gui_basics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_gui_extra" path="../../JUCE/modules"/>
</MODULEPATHS>
</XCODE_MAC>
</EXPORTFORMATS>
<MODULES>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_plugin_client" showAllCode="1" useLocalCopy="0"
useGlobalPath="1"/>
<MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
</MODULES>
</JUCERPROJECT>
================================================
FILE: Ambience/README.markdown
================================================
# Ambience
Small space reverberator. Designed to simulate a distant mic in small rooms, without the processor overhead of a full reverb plug-in. Can be used to add "softness" to drums and to simulate someone talking "off mic".
| Parameter | Description |
| --------- | ----------- |
| Size | Room size (variable from "cardboard box" through "vocal booth" to "medium studio") |
| HF Damp | Gentle low-pass filter to emulate the high frequency absorption of softer wall surfaces |
| Mix | Wet / dry mix (affects perceived distance) |
| Output | Level trim |
================================================
FILE: Ambience/Source/PluginProcessor.cpp
================================================
#include "PluginProcessor.h"
MDAAmbienceAudioProcessor::MDAAmbienceAudioProcessor()
: AudioProcessor(BusesProperties()
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
.withOutput("Output", juce::AudioChannelSet::stereo(), true))
{
}
MDAAmbienceAudioProcessor::~MDAAmbienceAudioProcessor()
{
}
const juce::String MDAAmbienceAudioProcessor::getName() const
{
return JucePlugin_Name;
}
int MDAAmbienceAudioProcessor::getNumPrograms()
{
return 1;
}
int MDAAmbienceAudioProcessor::getCurrentProgram()
{
return 0;
}
void MDAAmbienceAudioProcessor::setCurrentProgram(int index)
{
}
const juce::String MDAAmbienceAudioProcessor::getProgramName(int index)
{
return {};
}
void MDAAmbienceAudioProcessor::changeProgramName(int index, const juce::String &newName)
{
}
void MDAAmbienceAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
{
_buf1 = new float[1024];
_buf2 = new float[1024];
_buf3 = new float[1024];
_buf4 = new float[1024];
resetState();
}
void MDAAmbienceAudioProcessor::releaseResources()
{
delete [] _buf1; _buf1 = nullptr;
delete [] _buf2; _buf2 = nullptr;
delete [] _buf3; _buf3 = nullptr;
delete [] _buf4; _buf4 = nullptr;
}
void MDAAmbienceAudioProcessor::reset()
{
resetState();
}
bool MDAAmbienceAudioProcessor::isBusesLayoutSupported(const BusesLayout &layouts) const
{
return layouts.getMainOutputChannelSet() == juce::AudioChannelSet::stereo();
}
void MDAAmbienceAudioProcessor::resetState()
{
flushBuffers();
_pos = 0;
_filter = 0.0f;
}
void MDAAmbienceAudioProcessor::flushBuffers()
{
memset(_buf1, 0, 1024 * sizeof(float));
memset(_buf2, 0, 1024 * sizeof(float));
memset(_buf3, 0, 1024 * sizeof(float));
memset(_buf4, 0, 1024 * sizeof(float));
}
void MDAAmbienceAudioProcessor::update()
{
_feedback = 0.8f;
// Convert the percentage to a filter coefficient between 0.05 - 0.95.
// The higher HF Damp, the *less* filtering!
float fParam1 = apvts.getRawParameterValue("HF Damp")->load() / 100.0f;
_damp = 0.05f + 0.9f * fParam1;
// Convert the output from [-20, +20] dB into a gain of [0.1, 10.0].
float fParam3 = apvts.getRawParameterValue("Output")->load();
float tmp = juce::Decibels::decibelsToGain(fParam3);
// Calculate a dry/wet balance based on the mix setting. If mix = 0%, only
// the dry signal is used. At mix = 68%, dry and wet are both approx 0.54.
// For mix = 100%, wet is 0.8 and dry is 0. So this is slightly different
// from a regular dry/wet mix that does dry = 100% - wet.
float fParam2 = apvts.getRawParameterValue("Mix")->load() / 100.0f;
_dry = tmp - fParam2 * fParam2 * tmp;
_wet = (0.4f + 0.4f) * fParam2 * tmp;
// Convert the size from 0 - 10 meters to a value between 0.025 - 2.69.
// This size is used to set the delay times on the different delay lines:
// a larger size means a longer delay. The sample rate is not taken into
// consideration here, so the units (meters) seem to be kind of pointless,
// and running the plugin at different sample rates will give different
// results for the same size setting.
float fParam0 = apvts.getRawParameterValue("Size")->load() / 10.0f;
tmp = 0.025f + 2.665f * fParam0;
if (_size != tmp) { flushBuffers(); } // need to flush delay lines
_size = tmp;
}
void MDAAmbienceAudioProcessor::processBlock(juce::AudioBuffer<float> &buffer, juce::MidiBuffer &midiMessages)
{
juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
// Clear any output channels that don't contain input data.
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) {
buffer.clear(i, 0, buffer.getNumSamples());
}
update();
const float *in1 = buffer.getReadPointer(0);
const float *in2 = buffer.getReadPointer(1);
float *out1 = buffer.getWritePointer(0);
float *out2 = buffer.getWritePointer(1);
int p = _pos;
// The main structure of this effect is four allpass filters in series.
// Each of these is made up of a delay line with a different delay length.
// The _size variable sets the actual delay time, with 1024 samples being
// the maximum. (Note: 379 * the maximum size 2.69 is just less than 1024.)
// The d1 - d4 variables are the write indices into the four delay lines.
// We do `& 1023` to wrap around the index when it goes past the end.
int d1 = (p + int(107 * _size)) & 1023;
int d2 = (p + int(142 * _size)) & 1023;
int d3 = (p + int(277 * _size)) & 1023;
int d4 = (p + int(379 * _size)) & 1023;
const float feedback = _feedback;
const float damp = _damp;
const float dry = _dry;
const float wet = _wet;
float f = _filter;
for (int i = 0; i < buffer.getNumSamples(); ++i) {
float a = in1[i];
float b = in2[i];
// Combine the left and right stereo inputs into a single mono value.
// Also multiply by the wetness amount. We can do this here already
// because everything that follows are linear operations. Note that
// the maximum value of wet is 0.8, not 1.0.
float x = wet * (a + b);
// HF damping. This is a simple low-pass filter: f = a*x + (1 - a)*f.
f += damp * (x - f);
float r = f;
/*
Next there are four sections of allpass filters, as explained here:
https://ccrma.stanford.edu/~jos/pasp/Schroeder_Allpass_Sections.html
Each of these allpass stages does the following:
^-----------------------+
| |
| v
r ---> + --------> delay ---------> + ---> new r
^ |
| |
<---- *(-feedback) ----v
The feedback gain is always 0.8. In a typical Schroeder allpass,
feedforward gain is equal to the feedback gain (and positive) but
here it is always 1.0!
*/
// First allpass stage.
float t = _buf1[p];
r -= feedback * t;
_buf1[d1] = r;
r += t;
// Second allpass stage.
t = _buf2[p];
r -= feedback * t;
_buf2[d2] = r;
r += t;
// Third allpass stage.
t = _buf3[p];
r -= feedback * t;
_buf3[d3] = r;
r += t;
// The left channel output is a mix of the dry input with the allpass
// filtered signal. We want the wet output to be a diffuse version of
// the input, but the very first thing coming out of the delay lines
// is `f`, the low-pass filtered input. We don't want a copy of the dry
// signal in the wet signal, which is why `f` gets subtracted here.
a = dry * a + r - f;
// Fourth allpass stage.
t = _buf4[p];
r -= feedback * t;
_buf4[d4] = r;
r += t;
// The right channel output. This has one more delay stage than the
// left channel, making the reverb tail a bit longer on the right.
// You can clearly hear this on headphones; it's a little weird.
// The reverb sounds mono until the left channel fades out and the
// right keeps playing.
b = dry * b + r - f;
// Advance the read and write indices for the delay lines, wrap if needed.
++p &= 1023;
++d1 &= 1023;
++d2 &= 1023;
++d3 &= 1023;
++d4 &= 1023;
out1[i] = a;
out2[i] = b;
}
_pos = p;
_filter = f;
// N.B. The original code had denormal handling on `f` here that also resets
// the contents of the delay lines. That could interrupt the reverb tail if
// the input signal stops playing but the reverb was still ringing. It would
// have made more sense to put that denormal handling on the outputs of the
// all-pass filter sections. But we use juce::ScopedNoDenormals instead. :-)
}
juce::AudioProcessorEditor *MDAAmbienceAudioProcessor::createEditor()
{
return new juce::GenericAudioProcessorEditor(*this);
}
void MDAAmbienceAudioProcessor::getStateInformation(juce::MemoryBlock &destData)
{
copyXmlToBinary(*apvts.copyState().createXml(), destData);
}
void MDAAmbienceAudioProcessor::setStateInformation(const void *data, int sizeInBytes)
{
std::unique_ptr<juce::XmlElement> xml(getXmlFromBinary(data, sizeInBytes));
if (xml.get() != nullptr && xml->hasTagName(apvts.state.getType())) {
apvts.replaceState(juce::ValueTree::fromXml(*xml));
}
}
juce::AudioProcessorValueTreeState::ParameterLayout MDAAmbienceAudioProcessor::createParameterLayout()
{
juce::AudioProcessorValueTreeState::ParameterLayout layout;
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Size", 1),
"Size",
juce::NormalisableRange<float>(0.0f, 10.0f, 0.01f),
7.0f,
juce::AudioParameterFloatAttributes().withLabel("m")));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("HF Damp", 1),
"HF Damp",
juce::NormalisableRange<float>(0.0f, 100.0f, 0.01f),
70.0f,
juce::AudioParameterFloatAttributes().withLabel("%")));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Mix", 1),
"Mix",
juce::NormalisableRange<float>(0.0f, 100.0f, 0.01f),
90.0f,
juce::AudioParameterFloatAttributes().withLabel("%")));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Output", 1),
"Output",
juce::NormalisableRange<float>(-20.0f, 20.0f, 0.01f),
0.0f,
juce::AudioParameterFloatAttributes().withLabel("dB")));
return layout;
}
juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter()
{
return new MDAAmbienceAudioProcessor();
}
================================================
FILE: Ambience/Source/PluginProcessor.h
================================================
#pragma once
#include <JuceHeader.h>
class MDAAmbienceAudioProcessor : public juce::AudioProcessor
{
public:
MDAAmbienceAudioProcessor();
~MDAAmbienceAudioProcessor() override;
void prepareToPlay(double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
void reset() override;
bool isBusesLayoutSupported(const BusesLayout &layouts) const override;
void processBlock(juce::AudioBuffer<float> &, juce::MidiBuffer &) override;
juce::AudioProcessorEditor *createEditor() override;
bool hasEditor() const override { return true; }
const juce::String getName() const override;
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
bool isMidiEffect() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
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;
juce::AudioProcessorValueTreeState apvts { *this, nullptr, "Parameters", createParameterLayout() };
private:
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
void update();
void resetState();
void flushBuffers();
// Delay lines. The maximum length of these is hardcoded to 1024 samples.
float *_buf1, *_buf2, *_buf3, *_buf4;
// Read position in the delay buffers.
int _pos;
// This sets the length of the delays.
float _size;
// Feedback coefficient for the allpass filters.
float _feedback;
// Low-pass filter coefficient for HF damping.
float _damp;
// Low-pass filter state value.
float _filter;
// Wet/dry mix.
float _wet, _dry;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MDAAmbienceAudioProcessor)
};
================================================
FILE: Bandisto/MDABandisto.jucer
================================================
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT id="VNGyhW" name="MDABandisto" projectType="audioplug" useAppConfig="0"
addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1">
<MAINGROUP id="NKCHhF" name="MDABandisto">
<GROUP id="{9716A8D6-1C31-EE96-790F-A3F50EB8BE4D}" name="Source">
<FILE id="KNwEq9" name="PluginProcessor.cpp" compile="1" resource="0"
file="Source/PluginProcessor.cpp"/>
<FILE id="jHsBcT" name="PluginProcessor.h" compile="0" resource="0"
file="Source/PluginProcessor.h"/>
</GROUP>
</MAINGROUP>
<MODULES>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_plugin_client" showAllCode="1" useLocalCopy="0"
useGlobalPath="1"/>
<MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
</MODULES>
<JUCEOPTIONS JUCE_STRICT_REFCOUNTEDPOINTER="1" JUCE_VST3_CAN_REPLACE_VST2="0"/>
<EXPORTFORMATS>
<XCODE_MAC targetFolder="Builds/MacOSX">
<CONFIGURATIONS>
<CONFIGURATION isDebug="1" name="Debug" targetName="MDABandisto"/>
<CONFIGURATION isDebug="0" name="Release" targetName="MDABandisto"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_devices" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_formats" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_plugin_client" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_processors" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_utils" path="../../JUCE/modules"/>
<MODULEPATH id="juce_core" path="../../JUCE/modules"/>
<MODULEPATH id="juce_data_structures" path="../../JUCE/modules"/>
<MODULEPATH id="juce_events" path="../../JUCE/modules"/>
<MODULEPATH id="juce_graphics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_gui_basics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_gui_extra" path="../../JUCE/modules"/>
</MODULEPATHS>
</XCODE_MAC>
</EXPORTFORMATS>
</JUCERPROJECT>
================================================
FILE: Bandisto/README.markdown
================================================
# Bandisto
The original Multi-band distortion VST plug-in
This plug is like MultiBand but uses 3 bands of clipping instead of compression. This is unlikely to be a plug you use every day, but when you want to recreate the sound of torn bass-bins you know where to come!
| Parameter | Description |
| --------- | ----------- |
| Listen | Audition the low, mid and high bands individually |
| L <> M | Low / mid crossover frequency |
| M <> H | Mid / high crossover frequency |
| L/M/H Dist | Distortion amount for each of the 3 bands |
| L/M/H Out | Output level trims |
| Mode | Set clipping to Bipolar (top and bottom of waveform) or Unipolar (just tops) |
================================================
FILE: Bandisto/Source/PluginProcessor.cpp
================================================
#include "PluginProcessor.h"
MDABandistoAudioProcessor::MDABandistoAudioProcessor()
: AudioProcessor(BusesProperties()
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
.withOutput("Output", juce::AudioChannelSet::stereo(), true))
{
}
MDABandistoAudioProcessor::~MDABandistoAudioProcessor()
{
}
const juce::String MDABandistoAudioProcessor::getName() const
{
return JucePlugin_Name;
}
int MDABandistoAudioProcessor::getNumPrograms()
{
return 1;
}
int MDABandistoAudioProcessor::getCurrentProgram()
{
return 0;
}
void MDABandistoAudioProcessor::setCurrentProgram(int index)
{
}
const juce::String MDABandistoAudioProcessor::getProgramName(int index)
{
return {};
}
void MDABandistoAudioProcessor::changeProgramName(int index, const juce::String &newName)
{
}
void MDABandistoAudioProcessor::prepareToPlay(double newSampleRate, int samplesPerBlock)
{
sampleRate = float(newSampleRate);
resetState();
}
void MDABandistoAudioProcessor::releaseResources()
{
}
void MDABandistoAudioProcessor::reset()
{
resetState();
}
bool MDABandistoAudioProcessor::isBusesLayoutSupported(const BusesLayout &layouts) const
{
return layouts.getMainOutputChannelSet() == juce::AudioChannelSet::stereo();
}
void MDABandistoAudioProcessor::resetState()
{
fb1 = 0.0f;
fb2 = 0.0f;
fb3 = 0.0f;
}
void MDABandistoAudioProcessor::update()
{
float param4 = apvts.getRawParameterValue("L Dist")->load();
float param5 = apvts.getRawParameterValue("M Dist")->load();
float param6 = apvts.getRawParameterValue("H Dist")->load();
// The drive parameter follows a rather dramatic curve from 0.1 to 100000.
// On the UI this is shown as 0 - 60 dB, which I don't think corresponds
// to what actually happens.
driv1 = std::pow(10.0f, 6.0f * param4 * param4 - 1.0f);
driv2 = std::pow(10.0f, 6.0f * param5 * param5 - 1.0f);
driv3 = std::pow(10.0f, 6.0f * param6 * param6 - 1.0f);
// Calculate gain based on bipolar (transistor) or unipolar (value) mode.
// In unipolar mode, the gain is 0.5 because the mid-side signal is 6 dB
// too loud, and so this compensates for that. In bipolar mode, the higher
// the drive is for a band, the larger the gain for that band is too, from
// 0.3 all the way up to 3000! This is because at high drive, the output
// from the waveshaper is tiny and so it needs a big boost.
valve = int(apvts.getRawParameterValue("Mode")->load());
if (valve) { // valve (tube)
trim1 = 0.5f;
trim2 = 0.5f;
trim3 = 0.5f;
} else { // transistor
trim1 = 0.3f * std::pow(10.0f, 4.0 * std::pow(param4, 3.0f));
trim2 = 0.3f * std::pow(10.0f, 4.0 * std::pow(param5, 3.0f));
trim3 = 0.3f * std::pow(10.0f, 4.0 * std::pow(param6, 3.0f));
}
float param7 = apvts.getRawParameterValue("L Out")->load();
float param8 = apvts.getRawParameterValue("M Out")->load();
float param9 = apvts.getRawParameterValue("H Out")->load();
// The output level goes from -20 dB to 20 dB. Convert to linear gain
// and combine it with the gain that is based on the drive.
trim1 *= std::pow(10.0f, 2.0f * param7 - 1.0f);
trim2 *= std::pow(10.0f, 2.0f * param8 - 1.0f);
trim3 *= std::pow(10.0f, 2.0f * param9 - 1.0f);
// Determine which band will be output by setting the levels of the
// other bands to zero. In "Output" mode, all bands are included.
int param1 = int(apvts.getRawParameterValue("Listen")->load());
switch (param1) {
case 0: trim2 = 0.0f; trim3 = 0.0f; sideLevel = 0.0f; break;
case 1: trim1 = 0.0f; trim3 = 0.0f; sideLevel = 0.0f; break;
case 2: trim1 = 0.0f; trim2 = 0.0f; sideLevel = 0.0f; break;
default: sideLevel = 0.5f; break;
}
// Calculate the coefficients for the crossover filters. The actual
// frequencies in Hz depend on the sample rate.
float param2 = apvts.getRawParameterValue("L <> M")->load();
float param3 = apvts.getRawParameterValue("M <> H")->load();
fi1 = std::pow(10.0f, param2 - 1.70f);
fi2 = std::pow(10.0f, param3 - 1.05f);
fo1 = 1.0f - fi1;
fo2 = 1.0f - fi2;
}
void MDABandistoAudioProcessor::processBlock(juce::AudioBuffer<float> &buffer, juce::MidiBuffer &midiMessages)
{
juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
// Clear any output channels that don't contain input data.
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) {
buffer.clear(i, 0, buffer.getNumSamples());
}
update();
const float *in1 = buffer.getReadPointer(0);
const float *in2 = buffer.getReadPointer(1);
float *out1 = buffer.getWritePointer(0);
float *out2 = buffer.getWritePointer(1);
for (int i = 0; i < buffer.getNumSamples(); ++i) {
float a = in1[i];
float b = in2[i];
// Keep stereo component for later. This side signal won't be distorted.
float s = (a - b) * sideLevel;
// Create mono signal (mids). Only these mids will be distorted.
// Also add a small DC offset. The original comment said "dope filter",
// so I figured this made the filter extra dope but they probably meant
// doping as in adding small "impurities" that give the filter something
// to do?! Not sure it actually matters.
a += b + 0.00002f;
// Crossover filters. Three basic 6 dB/oct one-pole low-pass filters in
// series. Their frequency responses look a little funky, but `l + m + h`
// does reconstruct the signal, since `fb3 + (fb2 - fb3) + (a - fb2) = a`.
fb2 = fi2 * a + fo2 * fb2;
fb1 = fi1 * fb2 + fo1 * fb1;
fb3 = fi1 * fb1 + fo1 * fb3;
float l = fb3; // low band
float m = fb2 - l; // mid band
float h = a - fb2; // high band
// Distort. First rectify the signal so it's always positive, then use
// the magnitude to calculate a gain for each band. Because the formula
// is 1 / (1 + drive * amplitude), the higher the amplitude, the lower
// the gain, so this compresses the louder parts of the signal. And the
// higher the drive, the more extreme this compression is, eventually
// turning the distortion into hard clipping.
float g1 = (l > 0.0f) ? l : -l;
g1 = 1.0f / (1.0f + driv1 * g1);
float g2 = (m > 0.0f) ? m : -m;
g2 = 1.0f / (1.0f + driv2 * g2);
float g3 = (h > 0.0f) ? h : -h;
g3 = 1.0f / (1.0f + driv3 * g3);
/*
To plot the output of this waveshaper in something like Desmos,
use the following:
// the drive parameter
k: a number between 0 and 1
// the drive value (0.1 - 100000)
d = 10^(6k^2 - 1)
// the gain compensation value (trim)
t = 0.3 * 10^(4 * k^3)
// the waveshaper formula
x * t / (1 + d * x) { 0 <= x <= 1 }
This plots the transfer curve. You'll see that for low values of
`k`, the line is straight. But for higher values of `k` the curve
will bend and eventually drops in height.
*/
// In unipolar mode, only distort samples with a negative polarity.
if (valve) {
if (l > 0.0f) { g1 = 1.0f; }
if (m > 0.0f) { g2 = 1.0f; }
if (h > 0.0f) { g3 = 1.0f; }
}
// Apply the distortion to each band and recombine the bands.
a = l*g1*trim1 + m*g2*trim2 + h*g3*trim3;
// Add the side signal back in to restore the stereo nature.
out1[i] = a + s;
out2[i] = a - s;
}
}
juce::AudioProcessorEditor *MDABandistoAudioProcessor::createEditor()
{
return new juce::GenericAudioProcessorEditor(*this);
}
void MDABandistoAudioProcessor::getStateInformation(juce::MemoryBlock &destData)
{
copyXmlToBinary(*apvts.copyState().createXml(), destData);
}
void MDABandistoAudioProcessor::setStateInformation(const void *data, int sizeInBytes)
{
std::unique_ptr<juce::XmlElement> xml(getXmlFromBinary(data, sizeInBytes));
if (xml.get() != nullptr && xml->hasTagName(apvts.state.getType())) {
apvts.replaceState(juce::ValueTree::fromXml(*xml));
}
}
juce::AudioProcessorValueTreeState::ParameterLayout MDABandistoAudioProcessor::createParameterLayout()
{
juce::AudioProcessorValueTreeState::ParameterLayout layout;
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID("Listen", 1),
"Listen",
juce::StringArray { "Low", "Mid", "High", "Output" },
3));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("L <> M", 1),
"L <> M",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.4f,
juce::AudioParameterFloatAttributes()
.withLabel("Hz")
.withStringFromValueFunction([this](float value, int)
{
// Convert the filter coefficient to a frequency in Hz.
// Not sure where this formula comes from, it might just
// be an approximation of the actual cutoff frequency.
float fi1 = std::pow(10.0f, value - 1.7f);
float hz = sampleRate * fi1 * (0.098f + 0.09f*fi1 + 0.5f*std::pow(fi1, 8.2f));
return juce::String(int(hz));
})));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("M <> H", 1),
"M <> H",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.5f,
juce::AudioParameterFloatAttributes()
.withLabel("Hz")
.withStringFromValueFunction([this](float value, int)
{
float fi2 = std::pow(10.0f, value - 1.05f);
float hz = sampleRate * fi2 * (0.015f + 0.15f*fi2 + 0.9f*std::pow(fi2, 8.2f));
return juce::String(int(hz));
})));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("L Dist", 1),
"L Dist",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.5f,
juce::AudioParameterFloatAttributes()
.withLabel("dB")
.withStringFromValueFunction([this](float value, int)
{
return juce::String(int(60.0f * value));
})));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("M Dist", 1),
"M Dist",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.5f,
juce::AudioParameterFloatAttributes()
.withLabel("dB")
.withStringFromValueFunction([this](float value, int)
{
return juce::String(int(60.0f * value));
})));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("H Dist", 1),
"H Dist",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.5f,
juce::AudioParameterFloatAttributes()
.withLabel("dB")
.withStringFromValueFunction([this](float value, int)
{
return juce::String(int(60.0f * value));
})));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("L Out", 1),
"L Out",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.5f,
juce::AudioParameterFloatAttributes()
.withLabel("dB")
.withStringFromValueFunction([this](float value, int)
{
return juce::String(int(40.0f * value - 20.0f));
})));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("M Out", 1),
"M Out",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.5f,
juce::AudioParameterFloatAttributes()
.withLabel("dB")
.withStringFromValueFunction([this](float value, int)
{
return juce::String(int(40.0f * value - 20.0f));
})));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("H Out", 1),
"H Out",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.5f,
juce::AudioParameterFloatAttributes()
.withLabel("dB")
.withStringFromValueFunction([this](float value, int)
{
return juce::String(int(40.0f * value - 20.0f));
})));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID("Mode", 1),
"Mode",
juce::StringArray { "Bipolar", "Unipolar" },
3));
return layout;
}
juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter()
{
return new MDABandistoAudioProcessor();
}
================================================
FILE: Bandisto/Source/PluginProcessor.h
================================================
#pragma once
#include <JuceHeader.h>
class MDABandistoAudioProcessor : public juce::AudioProcessor
{
public:
MDABandistoAudioProcessor();
~MDABandistoAudioProcessor() override;
void prepareToPlay(double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
void reset() override;
bool isBusesLayoutSupported(const BusesLayout &layouts) const override;
void processBlock(juce::AudioBuffer<float> &, juce::MidiBuffer &) override;
juce::AudioProcessorEditor *createEditor() override;
bool hasEditor() const override { return true; }
const juce::String getName() const override;
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
bool isMidiEffect() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
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;
juce::AudioProcessorValueTreeState apvts { *this, nullptr, "Parameters", createParameterLayout() };
private:
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
void update();
void resetState();
float sampleRate;
float driv1, trim1; // drive and gain for low band
float driv2, trim2; // ... mid band
float driv3, trim3; // ... high band
float fi1, fo1; // filter coefficients
float fi2, fo2;
float fb1, fb2, fb3; // filter delays
float sideLevel; // output level for the stereo data
int valve; // 1 if unipolar mode, 0 if bipolar
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MDABandistoAudioProcessor)
};
================================================
FILE: BeatBox/MDABeatBox.jucer
================================================
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT id="bSDXpm" name="MDABeatBox" projectType="audioplug" useAppConfig="0"
addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1">
<MAINGROUP id="LonXhA" name="MDABeatBox">
<GROUP id="{CF246376-8AC8-A314-2E17-6A908752D3FE}" name="Source">
<FILE id="n0I8Up" name="PluginProcessor.cpp" compile="1" resource="0"
file="Source/PluginProcessor.cpp"/>
<FILE id="rbo6i3" name="PluginProcessor.h" compile="0" resource="0"
file="Source/PluginProcessor.h"/>
</GROUP>
</MAINGROUP>
<MODULES>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_plugin_client" showAllCode="1" useLocalCopy="0"
useGlobalPath="1"/>
<MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
</MODULES>
<JUCEOPTIONS JUCE_STRICT_REFCOUNTEDPOINTER="1" JUCE_VST3_CAN_REPLACE_VST2="0"/>
<EXPORTFORMATS>
<XCODE_MAC targetFolder="Builds/MacOSX">
<CONFIGURATIONS>
<CONFIGURATION isDebug="1" name="Debug" targetName="MDABeatBox"/>
<CONFIGURATION isDebug="0" name="Release" targetName="MDABeatBox"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_devices" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_formats" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_plugin_client" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_processors" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_utils" path="../../JUCE/modules"/>
<MODULEPATH id="juce_core" path="../../JUCE/modules"/>
<MODULEPATH id="juce_data_structures" path="../../JUCE/modules"/>
<MODULEPATH id="juce_events" path="../../JUCE/modules"/>
<MODULEPATH id="juce_graphics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_gui_basics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_gui_extra" path="../../JUCE/modules"/>
</MODULEPATHS>
</XCODE_MAC>
</EXPORTFORMATS>
</JUCERPROJECT>
================================================
FILE: BeatBox/README.markdown
================================================
# BeatBox
Drum replacer / enhancer
Contains three samples (kick, snare and hat) designed to be triggered by incoming audio in three frequency ranges. The plug-in has built-in drum sounds based on the *Roland CR-78*.
| Parameter | Description |
| --------- | ----------- |
| Hat Thr | Trigger threshold level |
| Hat Rate | Maximum trigger rate |
| Hat Mix | Sample playback level |
| Kik Thr | |
| Kik Trig | Trigger filter frequency - switches to "key listen" mode while adjusted |
| Kik Mix | |
| Snr Thr | |
| Snr Trig | Trigger filter frequency - increase if snare sample is triggered by kick drum |
| Snr Mix | |
| Dynamics | Apply input signal level variations to output |
| Thru Mix | Allow some of the input signal to be mixed with the output |
The original plug-in also had a recording mode parameter that let the (advanced) user replace the hi-hat, kick, and snare samples. I did not include this in the JUCE version as it wasn't very convenient to use.
================================================
FILE: BeatBox/Source/PluginProcessor.cpp
================================================
#include "PluginProcessor.h"
MDABeatBoxAudioProcessor::MDABeatBoxAudioProcessor()
: AudioProcessor(BusesProperties()
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
.withOutput("Output", juce::AudioChannelSet::stereo(), true))
{
sampleRate = 44100.0f;
}
MDABeatBoxAudioProcessor::~MDABeatBoxAudioProcessor()
{
}
const juce::String MDABeatBoxAudioProcessor::getName() const
{
return JucePlugin_Name;
}
int MDABeatBoxAudioProcessor::getNumPrograms()
{
return 1;
}
int MDABeatBoxAudioProcessor::getCurrentProgram()
{
return 0;
}
void MDABeatBoxAudioProcessor::setCurrentProgram(int index)
{
}
const juce::String MDABeatBoxAudioProcessor::getProgramName(int index)
{
return {};
}
void MDABeatBoxAudioProcessor::changeProgramName(int index, const juce::String &newName)
{
}
void MDABeatBoxAudioProcessor::prepareToPlay(double newSampleRate, int samplesPerBlock)
{
sampleRate = float(newSampleRate);
hbuflen = 20000;
kbuflen = 20000;
sbuflen = 60000;
// See the note in synth() on higher sampling rates.
if (sampleRate > 49000.0f) {
hbuflen *= 2;
kbuflen *= 2;
sbuflen *= 2;
}
hbuf.reset(new float[hbuflen]);
kbuf.reset(new float[kbuflen]);
sbufL.reset(new float[sbuflen]);
sbufR.reset(new float[sbuflen]);
synth();
// These variables store after how many samples the kick and snare
// are allowed to repeat. For the hi-hat this is a parameter.
kdel = int(0.10f * sampleRate);
sdel = int(0.12f * sampleRate);
// Fixed attack and release times for envelope follower.
dyna = std::pow(10.0f, -1000.0f / sampleRate);
dynr = std::pow(10.0f, -6.0f / sampleRate);
dyne = 0.0f;
resetState();
}
void MDABeatBoxAudioProcessor::releaseResources()
{
}
void MDABeatBoxAudioProcessor::reset()
{
resetState();
}
bool MDABeatBoxAudioProcessor::isBusesLayoutSupported(const BusesLayout &layouts) const
{
return layouts.getMainOutputChannelSet() == juce::AudioChannelSet::stereo();
}
void MDABeatBoxAudioProcessor::synth()
{
float o = 0.0f;
float p = 0.2f;
// Generate hi-hat. This is a burst of noise with an exponentially decaying
// envelope, and some basic filtering applied. This filter cuts around 5k
// and boosts around 10k to make it brighter.
// Note that std::rand() is bad and should be avoided in modern C++ code.
// The original plug-in never seeded the random number generator, so the
// hi-hat sounds different every time you run the plug-in. For even more
// varied results, a new sound could be synthesized on-the-fly every time
// the hi-hat is triggered.
{
std::memset(hbuf.get(), 0, hbuflen * sizeof(float));
float e = 0.00012f;
float de = std::pow(10.0f, -36.0f/sampleRate);
float o1 = 0.0f;
float o2 = 0.0f;
for (int t = 0; t < 5000; ++t) {
o = float((std::rand() % 2000) - 1000);
hbuf[t] = e * (2.0f*o1 - o2 - o);
e *= de;
o2 = o1;
o1 = o;
}
}
// Generate kick sample. This is a sine wave that decays exponentially
// in amplitude as well as in frequency.
{
std::memset(kbuf.get(), 0, kbuflen * sizeof(float));
float e = 0.5f;
float de = std::pow(10.0f, -3.8f/sampleRate);
float dp = 1588.0f / sampleRate;
for (int t = 0; t < 14000; ++t) {
kbuf[t] = e * std::sin(p);
e *= de;
p = std::fmod(p + dp * e, 6.2831853f);
}
}
// Generate snare. This is a sine wave with an exponentially decaying
// envelope and a small amount of added (filtered) noise. The snare
// buffer is stereo, although that feature was only used for recording
// your own sounds, which I did not include in the JUCE version.
{
std::memset(sbufL.get(), 0, sbuflen * sizeof(float));
std::memset(sbufR.get(), 0, sbuflen * sizeof(float));
float e = 0.38f;
float de = std::pow(10.0f, -15.0f/sampleRate);
for (int t = 0; t < 7000; ++t) {
o = 0.3f * o + float((std::rand() % 2000) - 1000);
sbufL[t] = e * (std::sin(p) + 0.0004f * o);
sbufR[t] = sbufL[t];
e *= de;
p = std::fmod(p + 0.025f, 6.2831853f);
}
}
// Note that the synthesized sounds are not independent of the sample
// rate! The higher the sample rate, the higher the pitch. Additionally,
// the durations are wrong for higher sample rates. Also not sure why
// the snare code uses the `p` and `o` variables from the kick.
}
void MDABeatBoxAudioProcessor::resetState()
{
hbufpos = hbuflen - 1;
kbufpos = kbuflen - 1;
sbufpos = sbuflen - 1;
hfil = 0.0f;
kww = 0.0f;
ksfx = 0;
ksb1 = 0.0f;
ksb2 = 0.0f;
ww = 0.0f;
sfx = 0;
sb1 = 0.0f;
sb2 = 0.0f;
}
void MDABeatBoxAudioProcessor::update()
{
// Convert from decibels (-40 dB ... 0 dB) to a linear value.
float param1 = apvts.getRawParameterValue("Hat Thr")->load();
hthr = std::pow(10.0f, 2.0f * param1 - 2.0f);
// After how many samples the hi-hat is allowed to repeat.
float param2 = apvts.getRawParameterValue("Hat Rate")->load();
hdel = int((0.04f + 0.2f * param2) * sampleRate);
// Convert from decibels (-80 dB ... +12 dB) to a linear value.
float param3 = apvts.getRawParameterValue("Hat Mix")->load();
hlev = 0.0001f + param3 * param3 * 4.0f;
// Convert from decibels (-40 dB ... 0 dB) to a linear value.
float param4 = apvts.getRawParameterValue("Kik Thr")->load();
kthr = 220.0f * std::pow(10.0f, 2.0f * param4 - 2.0f);
// Keep track of the old values so we can see if they changed.
float kwwx = kww;
float wwx = ww;
// The frequency for the kick filter.
float param5 = apvts.getRawParameterValue("Kik Trig")->load();
kww = std::pow(10.0f, -3.0f + 2.2f * param5);
ksf1 = std::cos(3.1415927f * kww); // p
ksf2 = std::sin(3.1415927f * kww); // q
// Convert from decibels (-80 dB ... +12 dB) to a linear value.
float param6 = apvts.getRawParameterValue("Kik Mix")->load();
klev = 0.0001f + param6 * param6 * 4.0f;
// Convert from decibels (-40 dB ... 0 dB) to a linear value.
float param7 = apvts.getRawParameterValue("Snr Thr")->load();
sthr = 40.0f * std::pow(10.0f, 2.0f * param7 - 2.0f);
// The frequency for the snare filter.
float param8 = apvts.getRawParameterValue("Snr Trig")->load();
ww = std::pow(10.0f, -3.0f + 2.2f * param8);
sf1 = std::cos(3.1415927f * ww); // p
sf2 = std::sin(3.1415927f * ww); // q
sf3 = 0.991f; // r
// The plug-in switches to "key listen" mode while the Kik Trig
// or Snr Trig parameter is being adjusted. This sets the counter
// to two seconds.
if (kwwx != kww) {
ksfx = int(2.0f * sampleRate);
}
if (wwx != ww) {
sfx = int(2.0f * sampleRate);
}
// Convert from decibels (-80 dB ... +12 dB) to a linear value.
float param9 = apvts.getRawParameterValue("Snr Mix")->load();
slev = 0.0001f + param9 * param9 * 4.0f;
// This parameter is a percentage.
dynm = apvts.getRawParameterValue("Dynamics")->load();
// This parameter is displayed as -inf dB ... 0 dB but is already
// 0 - 1, so we don't need to convert anything.
mix = apvts.getRawParameterValue("Thru Mix")->load();
}
void MDABeatBoxAudioProcessor::processBlock(juce::AudioBuffer<float> &buffer, juce::MidiBuffer &midiMessages)
{
juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
// Clear any output channels that don't contain input data.
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) {
buffer.clear(i, 0, buffer.getNumSamples());
}
update();
const float *in1 = buffer.getReadPointer(0);
const float *in2 = buffer.getReadPointer(1);
float *out1 = buffer.getWritePointer(0);
float *out2 = buffer.getWritePointer(1);
int hbufmax = hbuflen - 1;
int kbufmax = kbuflen - 1;
int sbufmax = sbuflen - 1;
float mix3 = 0.0f;
// Key listen (snare). This turns off everything except the snare filter
// output. This continues until two seconds worth of samples have elapsed.
if (sfx > 0) {
mix3 = 0.08f;
slev = 0.0f;
klev = 0.0f;
hlev = 0.0f;
mix = 0.0f;
sfx -= buffer.getNumSamples();
}
// Key listen (kick). This also uses the snare filter but swaps the coeffs
// to those from the kick filter.
if (ksfx > 0) {
mix3 = 0.03f;
slev = 0.0f;
klev = 0.0f;
hlev = 0.0f;
mix = 0.0f;
ksfx -= buffer.getNumSamples();
sf1 = ksf1;
sf2 = ksf2;
}
for (int i = 0; i < buffer.getNumSamples(); ++i) {
float a = in1[i];
float b = in2[i];
// Convert the stereo signal to mono. (This is 6 dB too loud.)
float e = a + b;
// Envelope follower. If the new sample is lower than the envelope,
// apply release, otherwise apply attack (using a one-pole filter).
// Note that in a regular envelope follower the `e` value would be
// absolute but here it can be negative as well. Not sure if that
// was an error in the original code or intentional.
dyne = (e < dyne) ? dyne * dynr : e - dyna * (e - dyne);
// Basic first order high-pass filter with a cutoff around 10 kHz.
// This computes the differences between two successive samples.
// This difference is close to zero at low frequencies but large
// at high frequencies.
hfil = e - hfil;
// Start playing the hi-hat sample when the filter output exceeds
// the threshold and we've waited long enough since the last hi-hat.
if ((hbufpos > hdel) && (hfil > hthr)) {
hbufpos = 0;
} else if (hbufpos < hbufmax) { // play until end and hold there
hbufpos++;
}
float o = hlev * hbuf[hbufpos];
// Low filter. This is a low-pass that gradually turns into a band
// pass. It has a massive gain for some reason... Unlike the hi-hat
// filter, which is fixed, the kick and snare filters can be changed.
float kfil = e + ksb1*ksf1 - ksb2*ksf2;
ksb2 = sf3 * (ksb1*ksf2 + ksb2*ksf1);
ksb1 = sf3 * kfil;
// Start playing the kick sample when the filter output exceeds
// the threshold and we've waited long enough since the last kick.
// Note that a short `kdel` time can cause the existing kick sample
// to be cut off suddenly when the new kick starts, which may glitch
// a little. It would be better to quickly fade out the old kick.
if ((kbufpos > kdel) && (kfil > kthr)) {
kbufpos = 0;
} else if (kbufpos < kbufmax) {
kbufpos++;
}
o += klev * kbuf[kbufpos];
// Mid filter. Similar to the kick filter; same coeffs but slightly
// different formula.
float sfil = hfil + 0.3f*e + sb1*sf1 - sb2*sf2;
sb2 = sf3 * (sb1*sf2 + sb2*sf1);
sb1 = sf3 * sfil;
// Play the snare sample.
if ((sbufpos > sdel) && (sfil > sthr)) {
sbufpos = 0;
} else if (sbufpos < sbufmax) {
sbufpos++;
}
float c = o + slev*sbufL[sbufpos];
float d = o + slev*sbufR[sbufpos];
// Dynamics. This applies the envelope of the original sound
// to the synthesized samples. It does make things a lot louder!
float mix4 = 1.0f + dynm * (dyne + dyne - 1.0f);
// The final output mixes the original input with the synthesized
// signal. In "key listen" mode, we mix in the snare / kick filter
// output, and everything else is turned off.
out1[i] = mix*a + mix3*sfil + mix4*c;
out2[i] = mix*b + mix3*sfil + mix4*d;
hfil = e;
}
}
juce::AudioProcessorEditor *MDABeatBoxAudioProcessor::createEditor()
{
return new juce::GenericAudioProcessorEditor(*this);
}
void MDABeatBoxAudioProcessor::getStateInformation(juce::MemoryBlock &destData)
{
copyXmlToBinary(*apvts.copyState().createXml(), destData);
}
void MDABeatBoxAudioProcessor::setStateInformation(const void *data, int sizeInBytes)
{
std::unique_ptr<juce::XmlElement> xml(getXmlFromBinary(data, sizeInBytes));
if (xml.get() != nullptr && xml->hasTagName(apvts.state.getType())) {
apvts.replaceState(juce::ValueTree::fromXml(*xml));
}
}
juce::AudioProcessorValueTreeState::ParameterLayout MDABeatBoxAudioProcessor::createParameterLayout()
{
juce::AudioProcessorValueTreeState::ParameterLayout layout;
// -40 dB ... 0 dB
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Hat Thr", 1),
"Hat Thr",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f),
0.3f,
juce::AudioParameterFloatAttributes().withLabel("dB")
.withStringFromValueFunction([](float value, int) {
return juce::String(40.0f*value - 40.0f, 2);
})
));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Hat Rate", 1),
"Hat Rate",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f),
0.45f,
juce::AudioParameterFloatAttributes().withLabel("ms")
.withStringFromValueFunction([](float value, int)
{
return juce::String(int(1000.0f * (0.04f + 0.2f * value)));
})
));
// -80 dB ... +12 dB
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Hat Mix", 1),
"Hat Mix",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f),
0.5f,
juce::AudioParameterFloatAttributes().withLabel("dB")
.withStringFromValueFunction([](float value, int)
{
float hlev = 0.0001f + value * value * 4.0f;
return juce::String(int(20.0f * std::log10(hlev)));
})
));
// -40 dB ... 0 dB
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Kik Thr", 1),
"Kik Thr",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f),
0.46f,
juce::AudioParameterFloatAttributes().withLabel("dB")
.withStringFromValueFunction([](float value, int)
{
return juce::String(40.0f*value - 40.0f, 2);
})
));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Kik Trig", 1),
"Kik Trig",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f),
0.15f,
juce::AudioParameterFloatAttributes().withLabel("Hz")
.withStringFromValueFunction([this](float value, int)
{
// Take this cutoff frequency with a grain of salt!
float kww = std::pow(10.0f, -3.0f + 2.2f * value);
return juce::String(int(0.5f * kww * sampleRate));
})
));
// -80 dB ... +12 dB
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Kik Mix", 1),
"Kik Mix",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f),
0.5f,
juce::AudioParameterFloatAttributes().withLabel("dB")
.withStringFromValueFunction([](float value, int)
{
float klev = 0.0001f + value * value * 4.0f;
return juce::String(int(20.0f * std::log10(klev)));
})
));
// -40 dB ... 0 dB
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Snr Thr", 1),
"Snr Thr",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f),
0.5f,
juce::AudioParameterFloatAttributes().withLabel("dB")
.withStringFromValueFunction([](float value, int) {
return juce::String(40.0f*value - 40.0f, 2);
})
));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Snr Trig", 1),
"Snr Trig",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f),
0.7f,
juce::AudioParameterFloatAttributes().withLabel("Hz")
.withStringFromValueFunction([this](float value, int)
{
// Take this cutoff frequency with a grain of salt!
float ww = std::pow(10.0f, -3.0f + 2.2f * value);
return juce::String(int(0.5f * ww * sampleRate));
})
));
// -80 dB ... +12 dB
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Snr Mix", 1),
"Snr Mix",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f),
0.5f,
juce::AudioParameterFloatAttributes().withLabel("dB")
.withStringFromValueFunction([](float value, int)
{
float slev = 0.0001f + value * value * 4.0f;
return juce::String(int(20.0f * std::log10(slev)));
})
));
// 0% ... 100%
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Dynamics", 1),
"Dynamics",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f),
0.0f,
juce::AudioParameterFloatAttributes().withLabel("%")
.withStringFromValueFunction([](float value, int)
{
return juce::String(int(100.0f * value));
})
));
// -inf dB ... 0 dB
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Thru Mix", 1),
"Thru Mix",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f),
0.0f,
juce::AudioParameterFloatAttributes().withLabel("dB")
.withStringFromValueFunction([](float value, int)
{
return juce::String(20.0f * std::log10(value), 2);
})
));
return layout;
}
juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter()
{
return new MDABeatBoxAudioProcessor();
}
================================================
FILE: BeatBox/Source/PluginProcessor.h
================================================
#pragma once
#include <JuceHeader.h>
class MDABeatBoxAudioProcessor : public juce::AudioProcessor
{
public:
MDABeatBoxAudioProcessor();
~MDABeatBoxAudioProcessor() override;
void prepareToPlay(double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
void reset() override;
bool isBusesLayoutSupported(const BusesLayout &layouts) const override;
void processBlock(juce::AudioBuffer<float> &, juce::MidiBuffer &) override;
juce::AudioProcessorEditor *createEditor() override;
bool hasEditor() const override { return true; }
const juce::String getName() const override;
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
bool isMidiEffect() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
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;
juce::AudioProcessorValueTreeState apvts { *this, nullptr, "Parameters", createParameterLayout() };
private:
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
void update();
void resetState();
void synth();
float sampleRate;
float hthr; // hi-hat threshold
float hfil; // high-pass filter state
float hlev; // hat mix
int hdel; // minimum delay in samples between successive hi-hats
float kthr; // kick threshold
float ksf1, ksf2; // filter coeffs
float ksb1, ksb2; // filter state
float klev; // kick mix
int kdel; // minimum delay in samples between successive kicks
float kww; // Kik Trig parameter
int ksfx; // if 0, normal operation; if > 0, key listen mode
float sthr; // snare threshold
float sf1, sf2, sf3; // filter coeffs
float sb1, sb2; // filter state
float slev; // snare mix
int sdel; // minimum delay in samples between successive snares
float ww; // Snr Trig parameter
int sfx; // if 0, normal operation; if > 0, key listen mode
float dyne; // current envelope level
float dyna; // envelope attack
float dynr; // envelope release
float dynm; // Dynamics parameter
float mix; // Thru Mix parameter
std::unique_ptr<float[]> hbuf; // buffer containing hi-hat sound
std::unique_ptr<float[]> kbuf; // kick sound
std::unique_ptr<float[]> sbufL; // snare is stereo
std::unique_ptr<float[]> sbufR;
int hbuflen; // hi-hat buffer length
int kbuflen; // kick buffer length
int sbuflen; // snare buffer length
int hbufpos; // current position in hi-hat buffer
int kbufpos; // in kick buffer
int sbufpos; // in snare buffer
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MDABeatBoxAudioProcessor)
};
================================================
FILE: DX10/DX10.jucer
================================================
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT id="lUYjpz" name="DX10" projectType="audioplug" useAppConfig="0"
addUsingNamespaceToJuceHeader="0" displaySplashScreen="1" jucerFormatVersion="1"
pluginCharacteristicsValue="pluginIsSynth,pluginWantsMidiIn">
<MAINGROUP id="w6zN8D" name="DX10">
<GROUP id="{6710DCDA-6646-EE06-8F56-CF4D30A75DF8}" name="Source">
<FILE id="FyCcCe" name="PluginProcessor.cpp" compile="1" resource="0"
file="Source/PluginProcessor.cpp"/>
<FILE id="OMkNkK" name="PluginProcessor.h" compile="0" resource="0"
file="Source/PluginProcessor.h"/>
</GROUP>
</MAINGROUP>
<JUCEOPTIONS JUCE_STRICT_REFCOUNTEDPOINTER="1" JUCE_VST3_CAN_REPLACE_VST2="0"/>
<EXPORTFORMATS>
<XCODE_MAC targetFolder="Builds/MacOSX">
<CONFIGURATIONS>
<CONFIGURATION isDebug="1" name="Debug"/>
<CONFIGURATION isDebug="0" name="Release"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_audio_devices" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_audio_formats" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_audio_plugin_client" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_audio_processors" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_audio_utils" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_core" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_data_structures" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_events" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_graphics" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_gui_basics" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_gui_extra" path="../../../../../JUCE/modules"/>
</MODULEPATHS>
</XCODE_MAC>
</EXPORTFORMATS>
<MODULES>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_plugin_client" showAllCode="1" useLocalCopy="0"
useGlobalPath="1"/>
<MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
</MODULES>
</JUCERPROJECT>
================================================
FILE: DX10/README.markdown
================================================
# DX10
Simple FM synthesizer. Sounds similar to the later Yamaha DX synths including the heavy bass but with a warmer, cleaner tone.
| Parameter | Description |
| --------- | ----------- |
| Attack | Envelope attack time |
| Decay | Envelope decay time |
| Release | Envelope release time |
| Ratio | Modulator frequency (as a multiple of the carrier frequency) |
| Fine Ratio | Fine control of modulator frequency for detuning and inharmonic effects |
| Mod Level 1 | Initial modulator level |
| Mod Decay| Time for modulator level to reach... |
| Mod Level 2 | Final modulator level |
| Mod Release | Time for modulator level to reach zero |
| Vel Sens | Veclocity control of modulator level (brightness) |
| Vibrato | Vibrato amount (note that heavy vibrato may also cause additional tone modulation effects) |
| Octave | Octave shift |
The plug-in is 8-voice polyphonic and is designed for high quality (low aliasing) and low processor usage - this means that some features that would increase processor usage have been left out!
================================================
FILE: DX10/Source/PluginProcessor.cpp
================================================
#include "PluginProcessor.h"
DX10Program::DX10Program(const char *name,
float p0, float p1, float p2, float p3,
float p4, float p5, float p6, float p7,
float p8, float p9, float p10, float p11,
float p12, float p13, float p14, float p15)
{
strcpy(this->name, name);
param[0] = p0; param[1] = p1; param[2] = p2; param[3] = p3;
param[4] = p4; param[5] = p5; param[6] = p6; param[7] = p7;
param[8] = p8; param[9] = p9; param[10] = p10; param[11] = p11;
param[12] = p12; param[13] = p13; param[14] = p14; param[15] = p15;
}
DX10AudioProcessor::DX10AudioProcessor()
: AudioProcessor(BusesProperties().withOutput("Output", juce::AudioChannelSet::stereo(), true))
{
_sampleRate = 44100.0f;
_inverseSampleRate = 1.0f / _sampleRate;
createPrograms();
setCurrentProgram(0);
}
DX10AudioProcessor::~DX10AudioProcessor()
{
}
const juce::String DX10AudioProcessor::getName() const
{
return JucePlugin_Name;
}
int DX10AudioProcessor::getNumPrograms()
{
return int(_programs.size());
}
int DX10AudioProcessor::getCurrentProgram()
{
return _currentProgram;
}
void DX10AudioProcessor::setCurrentProgram(int index)
{
_currentProgram = index;
const char *paramNames[] = {
"Attack",
"Decay",
"Release",
"Coarse",
"Fine",
"Mod Init",
"Mod Dec",
"Mod Sus",
"Mod Rel",
"Mod Vel",
"Vibrato",
"Octave",
"FineTune",
"Waveform",
"Mod Thru",
"LFO Rate",
};
for (int i = 0; i < NPARAMS; ++i) {
apvts.getParameter(paramNames[i])->setValueNotifyingHost(_programs[index].param[i]);
}
}
const juce::String DX10AudioProcessor::getProgramName(int index)
{
return { _programs[index].name };
}
void DX10AudioProcessor::changeProgramName(int index, const juce::String &newName)
{
// not implemented
}
void DX10AudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
{
_sampleRate = sampleRate;
_inverseSampleRate = 1.0f / _sampleRate;
resetState();
}
void DX10AudioProcessor::releaseResources()
{
}
void DX10AudioProcessor::reset()
{
resetState();
}
bool DX10AudioProcessor::isBusesLayoutSupported(const BusesLayout &layouts) const
{
return layouts.getMainOutputChannelSet() == juce::AudioChannelSet::stereo();
}
void DX10AudioProcessor::createPrograms()
{
// Att Dec Rel | Rat C Rat F Att Dec Sus Rel Vel | Vib Oct Fine Rich Thru LFO
_programs.emplace_back("Bright E.Piano", 0.000f, 0.650f, 0.441f, 0.842f, 0.329f, 0.230f, 0.800f, 0.050f, 0.800f, 0.900f, 0.000f, 0.500f, 0.500f, 0.447f, 0.000f, 0.414f);
_programs.emplace_back("Jazz E.Piano", 0.000f, 0.500f, 0.100f, 0.671f, 0.000f, 0.441f, 0.336f, 0.243f, 0.800f, 0.500f, 0.000f, 0.500f, 0.500f, 0.178f, 0.000f, 0.500f);
_programs.emplace_back("E.Piano Pad", 0.000f, 0.700f, 0.400f, 0.230f, 0.184f, 0.270f, 0.474f, 0.224f, 0.800f, 0.974f, 0.250f, 0.500f, 0.500f, 0.428f, 0.836f, 0.500f);
_programs.emplace_back("Fuzzy E.Piano", 0.000f, 0.700f, 0.400f, 0.320f, 0.217f, 0.599f, 0.670f, 0.309f, 0.800f, 0.500f, 0.263f, 0.507f, 0.500f, 0.276f, 0.638f, 0.526f);
_programs.emplace_back("Soft Chimes", 0.400f, 0.600f, 0.650f, 0.760f, 0.000f, 0.390f, 0.250f, 0.160f, 0.900f, 0.500f, 0.362f, 0.500f, 0.500f, 0.401f, 0.296f, 0.493f);
_programs.emplace_back("Harpsichord", 0.000f, 0.342f, 0.000f, 0.280f, 0.000f, 0.880f, 0.100f, 0.408f, 0.740f, 0.000f, 0.000f, 0.600f, 0.500f, 0.842f, 0.651f, 0.500f);
_programs.emplace_back("Funk Clav", 0.000f, 0.400f, 0.100f, 0.360f, 0.000f, 0.875f, 0.160f, 0.592f, 0.800f, 0.500f, 0.000f, 0.500f, 0.500f, 0.303f, 0.868f, 0.500f);
_programs.emplace_back("Sitar", 0.000f, 0.500f, 0.704f, 0.230f, 0.000f, 0.151f, 0.750f, 0.493f, 0.770f, 0.500f, 0.000f, 0.400f, 0.500f, 0.421f, 0.632f, 0.500f);
_programs.emplace_back("Chiff Organ", 0.600f, 0.990f, 0.400f, 0.320f, 0.283f, 0.570f, 0.300f, 0.050f, 0.240f, 0.500f, 0.138f, 0.500f, 0.500f, 0.283f, 0.822f, 0.500f);
_programs.emplace_back("Tinkle", 0.000f, 0.500f, 0.650f, 0.368f, 0.651f, 0.395f, 0.550f, 0.257f, 0.900f, 0.500f, 0.300f, 0.800f, 0.500f, 0.000f, 0.414f, 0.500f);
_programs.emplace_back("Space Pad", 0.000f, 0.700f, 0.520f, 0.230f, 0.197f, 0.520f, 0.720f, 0.280f, 0.730f, 0.500f, 0.250f, 0.500f, 0.500f, 0.336f, 0.428f, 0.500f);
_programs.emplace_back("Koto", 0.000f, 0.240f, 0.000f, 0.390f, 0.000f, 0.880f, 0.100f, 0.600f, 0.740f, 0.500f, 0.000f, 0.500f, 0.500f, 0.526f, 0.480f, 0.500f);
_programs.emplace_back("Harp", 0.000f, 0.500f, 0.700f, 0.160f, 0.000f, 0.158f, 0.349f, 0.000f, 0.280f, 0.900f, 0.000f, 0.618f, 0.500f, 0.401f, 0.000f, 0.500f);
_programs.emplace_back("Jazz Guitar", 0.000f, 0.500f, 0.100f, 0.390f, 0.000f, 0.490f, 0.250f, 0.250f, 0.800f, 0.500f, 0.000f, 0.500f, 0.500f, 0.263f, 0.145f, 0.500f);
_programs.emplace_back("Steel Drum", 0.000f, 0.300f, 0.507f, 0.480f, 0.730f, 0.000f, 0.100f, 0.303f, 0.730f, 1.000f, 0.000f, 0.600f, 0.500f, 0.579f, 0.000f, 0.500f);
_programs.emplace_back("Log Drum", 0.000f, 0.300f, 0.500f, 0.320f, 0.000f, 0.467f, 0.079f, 0.158f, 0.500f, 0.500f, 0.000f, 0.400f, 0.500f, 0.151f, 0.020f, 0.500f);
_programs.emplace_back("Trumpet", 0.000f, 0.990f, 0.100f, 0.230f, 0.000f, 0.000f, 0.200f, 0.450f, 0.800f, 0.000f, 0.112f, 0.600f, 0.500f, 0.711f, 0.000f, 0.401f);
_programs.emplace_back("Horn", 0.280f, 0.990f, 0.280f, 0.230f, 0.000f, 0.180f, 0.400f, 0.300f, 0.800f, 0.500f, 0.000f, 0.400f, 0.500f, 0.217f, 0.480f, 0.500f);
_programs.emplace_back("Reed 1", 0.220f, 0.990f, 0.250f, 0.170f, 0.000f, 0.240f, 0.310f, 0.257f, 0.900f, 0.757f, 0.000f, 0.500f, 0.500f, 0.697f, 0.803f, 0.500f);
_programs.emplace_back("Reed 2", 0.220f, 0.990f, 0.250f, 0.450f, 0.070f, 0.240f, 0.310f, 0.360f, 0.900f, 0.500f, 0.211f, 0.500f, 0.500f, 0.184f, 0.000f, 0.414f);
_programs.emplace_back("Violin", 0.697f, 0.990f, 0.421f, 0.230f, 0.138f, 0.750f, 0.390f, 0.513f, 0.800f, 0.316f, 0.467f, 0.678f, 0.500f, 0.743f, 0.757f, 0.487f);
_programs.emplace_back("Chunky Bass", 0.000f, 0.400f, 0.000f, 0.280f, 0.125f, 0.474f, 0.250f, 0.100f, 0.500f, 0.500f, 0.000f, 0.400f, 0.500f, 0.579f, 0.592f, 0.500f);
_programs.emplace_back("E.Bass", 0.230f, 0.500f, 0.100f, 0.395f, 0.000f, 0.388f, 0.092f, 0.250f, 0.150f, 0.500f, 0.200f, 0.200f, 0.500f, 0.178f, 0.822f, 0.500f);
_programs.emplace_back("Clunk Bass", 0.000f, 0.600f, 0.400f, 0.230f, 0.000f, 0.450f, 0.320f, 0.050f, 0.900f, 0.500f, 0.000f, 0.200f, 0.500f, 0.520f, 0.105f, 0.500f);
_programs.emplace_back("Thick Bass", 0.000f, 0.600f, 0.400f, 0.170f, 0.145f, 0.290f, 0.350f, 0.100f, 0.900f, 0.500f, 0.000f, 0.400f, 0.500f, 0.441f, 0.309f, 0.500f);
_programs.emplace_back("Sine Bass", 0.000f, 0.600f, 0.490f, 0.170f, 0.151f, 0.099f, 0.400f, 0.000f, 0.900f, 0.500f, 0.000f, 0.400f, 0.500f, 0.118f, 0.013f, 0.500f);
_programs.emplace_back("Square Bass", 0.000f, 0.600f, 0.100f, 0.320f, 0.000f, 0.350f, 0.670f, 0.100f, 0.150f, 0.500f, 0.000f, 0.200f, 0.500f, 0.303f, 0.730f, 0.500f);
_programs.emplace_back("Upright Bass 1", 0.300f, 0.500f, 0.400f, 0.280f, 0.000f, 0.180f, 0.540f, 0.000f, 0.700f, 0.500f, 0.000f, 0.400f, 0.500f, 0.296f, 0.033f, 0.500f);
_programs.emplace_back("Upright Bass 2", 0.300f, 0.500f, 0.400f, 0.360f, 0.000f, 0.461f, 0.070f, 0.070f, 0.700f, 0.500f, 0.000f, 0.400f, 0.500f, 0.546f, 0.467f, 0.500f);
_programs.emplace_back("Harmonics", 0.000f, 0.500f, 0.500f, 0.280f, 0.000f, 0.330f, 0.200f, 0.000f, 0.700f, 0.500f, 0.000f, 0.500f, 0.500f, 0.151f, 0.079f, 0.500f);
_programs.emplace_back("Scratch", 0.000f, 0.500f, 0.000f, 0.000f, 0.240f, 0.580f, 0.630f, 0.000f, 0.000f, 0.500f, 0.000f, 0.600f, 0.500f, 0.816f, 0.243f, 0.500f);
_programs.emplace_back("Syn Tom", 0.000f, 0.355f, 0.350f, 0.000f, 0.105f, 0.000f, 0.000f, 0.200f, 0.500f, 0.500f, 0.000f, 0.645f, 0.500f, 1.000f, 0.296f, 0.500f);
}
void DX10AudioProcessor::resetState()
{
// Turn off all playing voices.
for (int v = 0; v < NVOICES; ++v) {
_voices[v].env = 0.0f;
_voices[v].car = 0.0f;
_voices[v].dcar = 0.0f;
_voices[v].mod0 = 0.0f;
_voices[v].mod1 = 0.0f;
_voices[v].dmod = 0.0f;
_voices[v].cdec = 0.99f;
}
_numActiveVoices = 0;
// Clear out any pending MIDI events.
_notes[0] = EVENTS_DONE;
// These variables are changed by MIDI CC, reset to defaults.
_modWheel = 0.0f;
_pitchBend = 1.0f;
_volume = 0.0035f;
_sustain = 0;
// Reset other state.
_lfoStep = 0;
_lfo0 = 0.0f;
_lfo1 = 1.0f;
_modulationAmount = 0.0f;
}
void DX10AudioProcessor::update()
{
/*
Calculate a multiplier for the pitches of the notes based on the amount of
tuning. The number of octaves is -3 to +3. To calculate a multiplier for N
semitones, we do `2^(N/12)`. Since an octave has 12 semitones, we can just
do `2^(oct)`. Then we multiply this by 8.175 Hz, which is the pitch of the
lowest possible note (with MIDI note number 0).
It's totally not obvious, but there is also an additional factor 2 in here.
This is needed for the oscillator algorithm. Instead of [-3, +3] we treat
the number of octaves actually as [-2, +4], so `2^(oct)` is always 2x higher.
More about why this is done in noteOn().
We also divide by the sample rate already to save doing this division later
when calculating the phase increment for the carrier.
*/
float param11 = apvts.getRawParameterValue("Octave")->load();
_tune = 8.175798915644f * _inverseSampleRate * std::pow(2.0f, std::floor(param11 * 6.9f) - 2.0f);
// Fine-tuning: -100 cents to +100 cents, a value from -1.0 to +1.0.
float param12 = apvts.getRawParameterValue("FineTune")->load();
_fineTune = param12 + param12 - 1.0f;
// The carrier:modulator ratio is a whole number between 0 and 40.
float coarse = apvts.getRawParameterValue("Coarse")->load();
coarse = std::floor(40.1f * coarse * coarse);
// Fine-tuning the c:m ratio is a much smaller number, between 0 and 0.050,
// or a fixed ratio picked from a list.
float fine = apvts.getRawParameterValue("Fine")->load();
if (fine < 0.5f) {
fine = 0.2f * fine * fine;
} else {
switch (int(8.9f * fine)) {
case 4: fine = 0.25f; break;
case 5: fine = 0.33333333f; break;
case 6: fine = 0.50f; break;
case 7: fine = 0.66666667f; break;
default: fine = 0.75f;
}
}
// The final carrier:modulator ratio combines the coarse and fine settings.
// To save some multiplications later the factor PI is already included here.
// Note that this uses PI/2, which causes the modulator to be actually at
// half the pitch given by the ratio. For example, with Coarse set to 1 and
// Fine to 0, the modulator pitch will be half that of the carrier, not equal
// to the carrier pitch! (Not sure if this was intentional or a mistake.)
_ratio = 1.570796326795f * (coarse + fine);
// Velocity sensitivity: a value between 0 and 1.
_velocitySensitivity = apvts.getRawParameterValue("Mod Vel")->load();
// Vibrato amount: this is a skewed parameter that goes from 0.0 to 0.001.
float param10 = apvts.getRawParameterValue("Vibrato")->load();
_vibrato = 0.001f * param10 * param10;
// The carrier's envelope is simply a value that gets multiplied by a value
// less than one, which means it exponentially decays over time. The attack
// is made by exponentially going up rather than down.
float param0 = apvts.getRawParameterValue("Attack")->load();
_attack = 1.0f - std::exp(-_inverseSampleRate * std::exp(8.0f - 8.0f * param0));
float param1 = apvts.getRawParameterValue("Decay")->load();
if (param1 > 0.98f) {
_decay = 1.0f;
} else {
_decay = std::exp(-_inverseSampleRate * std::exp(5.0f - 8.0f * param1));
}
float param2 = apvts.getRawParameterValue("Release")->load();
_release = std::exp(-_inverseSampleRate * std::exp(5.0f - 5.0f * param2));
// The modulator envelope does not have an attack but starts out at a given
// initial level, then takes decay time to reach the sustain level. As with
// the carrier envelope, this is done using a basic smoothing filter.
float param5 = apvts.getRawParameterValue("Mod Init")->load();
_modInitialLevel = 0.0002f * param5 * param5;
float param6 = apvts.getRawParameterValue("Mod Dec")->load();
_modDecay = 1.0f - std::exp(-_inverseSampleRate * std::exp(6.0f - 7.0f * param6));
float param7 = apvts.getRawParameterValue("Mod Sus")->load();
_modSustain = 0.0002f * param7 * param7;
float param8 = apvts.getRawParameterValue("Mod Rel")->load();
_modRelease = 1.0f - std::exp(-_inverseSampleRate * std::exp(5.0f - 8.0f * param8));
// The value of this parameter changes the shape of the carrier wave.
// At 0%, it's a plain sine wave. At 100%, it's more like a sawtooth.
float param13 = apvts.getRawParameterValue("Waveform")->load();
_richness = 0.50f - 3.0f * param13 * param13;
// Modulator mix is a skewed value between 0 and 0.25.
float param14 = apvts.getRawParameterValue("Mod Thru")->load();
_modMix = 0.25f * param14 * param14;
// The LFO rate is between 0 and 25 Hz. This parameter is skewed. The factor
// 628.3 in the formula below is 100 * 2 pi, because the LFO is only updated
// every 100 samples.
float param15 = apvts.getRawParameterValue("LFO Rate")->load();
_lfoInc = 628.3f * _inverseSampleRate * 25.0f * param15 * param15;
}
void DX10AudioProcessor::processEvents(juce::MidiBuffer &midiMessages)
{
// There are different ways a synth can handle MIDI events. This plug-in does
// it by copying the events it cares about -- note on and note off -- into an
// array. In the render loop, we step through this array to process the events
// one-by-one. Controllers such as the sustain pedal are processed right away,
// at the start of the block, so these are not sample accurate.
int npos = 0;
for (const auto metadata : midiMessages) {
if (metadata.numBytes != 3) continue;
const auto data0 = metadata.data[0];
const auto data1 = metadata.data[1];
const auto data2 = metadata.data[2];
const int deltaFrames = metadata.samplePosition;
switch (data0 & 0xf0) { // status byte (all channels)
// Note off
case 0x80:
_notes[npos++] = deltaFrames; // time offset
_notes[npos++] = data1 & 0x7F; // note
_notes[npos++] = 0; // vel
break;
// Note on
case 0x90:
_notes[npos++] = deltaFrames; // time offset
_notes[npos++] = data1 & 0x7F; // note
_notes[npos++] = data2 & 0x7F; // vel
break;
// Controller
case 0xB0:
switch (data1) {
case 0x01: // mod wheel
// This maps the position of the mod wheel to a
// parabolic curve starting at 0.0 (position 0)
// up to 0.000806 (position 127). This amount is
// added to the LFO intensity for vibrato.
_modWheel = 0.00000005f * float(data2 * data2);
break;
case 0x07: // volume
// Map the position of the volume control to a
// parabolic curve starting at 0.0 (position 0)
// up to 0.00564 (position 127).
_volume = 0.00000035f * float(data2 * data2);
break;
case 0x40: // sustain pedal
// Make the variable 64 when the pedal is pressed
// and 0 when released.
_sustain = data2 & 0x40;
// Pedal released? Then end all sustained notes.
// This sends a fake note-off event with note = SUSTAIN,
// meaning all sustained notes will be moved into their
// envelope release stage.
if (_sustain == 0) {
_notes[npos++] = deltaFrames;
_notes[npos++] = SUSTAIN;
_notes[npos++] = 0;
}
break;
default: // all notes off
if (data1 > 0x7A) {
// Setting the decay to 0.99 will fade out
// the voice very quickly.
for (int v = 0; v < NVOICES; ++v) {
_voices[v].cdec = 0.99f;
}
_sustain = 0;
}
break;
}
break;
// Program change
case 0xC0:
if (data1 < _programs.size()) {
setCurrentProgram(data1);
}
break;
// Pitch bend
case 0xE0:
// This maps the pitch bend value from [-8192, 8191] to [0.89, 1.12]
// where 1.0 means the pitch wheel is centered. This value is used to
// shift the carrier up or down 2 semitones (since 2^(-2/12) = 0.89
// and 2^(2/12) = 1.12).
_pitchBend = float(data1 + 128 * data2 - 8192);
if (_pitchBend > 0.0f) {
_pitchBend = 1.0f + 0.000014951f * _pitchBend;
} else {
_pitchBend = 1.0f + 0.000013318f * _pitchBend;
}
break;
default: break;
}
// Discard events if buffer full!
if (npos > EVENTBUFFER) npos -= 3;
}
_notes[npos] = EVENTS_DONE;
midiMessages.clear();
}
void DX10AudioProcessor::processBlock(juce::AudioBuffer<float> &buffer, juce::MidiBuffer &midiMessages)
{
juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
// Clear any output channels that don't contain input data.
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) {
buffer.clear(i, 0, buffer.getNumSamples());
}
update();
processEvents(midiMessages);
int sampleFrames = buffer.getNumSamples();
float *out1 = buffer.getWritePointer(0);
float *out2 = buffer.getWritePointer(1);
int event = 0;
int frame = 0; // how many samples are already rendered
// Is there at least one active voice, or any pending MIDI event?
if (_numActiveVoices > 0 || _notes[event] < sampleFrames) {
while (frame < sampleFrames) {
// Get the timestamp of the next note on/off event. This is usually in the
// future, i.e. a number of samples after the current sample.
int frames = _notes[event++];
// This catches the EVENTS_DONE special event. There are no events left.
if (frames > sampleFrames) frames = sampleFrames;
// The timestamp for the event is relative to the start of the block.
// Make it relative to the previous event; this tells us how many samples
// to process until the new event actually happens.
frames -= frame;
// When we're done with this event, this is how many samples we will have
// processed in total.
frame += frames;
// Until it's time to process the upcoming event, render the active voices.
while (--frames >= 0) {
Voice *V = _voices;
// This variable adds up the output values of all the active voices.
// DX10 is a mono synth, so there is only one channel.
float o = 0.0f;
// The LFO and any things it modulates are updated every 100 samples.
if (--_lfoStep < 0) {
// This formula is a simple method to approximate a sine wave, but
// it only works for low frequencies such as with an LFO.
_lfo0 += _lfoInc * _lfo1;
_lfo1 -= _lfoInc * _lfo0;
// Calculate the new amount of modulation. This value swings between
// -0.001806 and +0.001806 and is directly added to the carrier phase.
_modulationAmount = _lfo1 * (_modWheel + _vibrato);
_lfoStep = 100; // reset counter
}
// Loop through all the voices...
for (int v = 0; v < NVOICES; ++v) {
// ...but only render the voices that have an active envelope.
float e = V->env;
if (e > SILENCE) {
// The envelope is always decaying.
V->env = e * V->cdec;
// To add an attack to the envelope, apply a smoothing filter that
// raises `cenv` from 0.0 to the current envelope level `env`.
// The longer the attack, the smaller `catt` and the slower this
// filter increments the level. All the while, `env` is decaying
// and pulling things downward, so with a long attack the smoothed
// envelope `cenv` never gets as high as with a shorter attack.
// When the note is released, `catt` is set to 1 so that the attack
// ends and `cenv` only decays from that point onwards.
V->cenv += V->catt * (e - V->cenv);
// Simple sine wave oscillator. This creates a sine wave that first
// goes down and then goes up, i.e. it has been phase inverted.
// The LFO also uses a sine wave oscillator but the one used for
// the modulator wave is more reliable at higher frequencies.
float y = V->dmod * V->mod0 - V->mod1;
V->mod1 = V->mod0;
V->mod0 = y;
// The envelope for the modulator is a simple smoothing filter that
// gradually moves from `menv` to the target level `mlev` in an
// exponential fashion. Which is what we want because frequencies
// are logarithmic, so to move through them at a constant speed we
// must move in exponential steps.
V->menv += V->mdec * (V->mlev - V->menv);
// Could add more modulator blocks for a wider range of FM sounds.
// Most FM synths allow for different configurations but DX10 keeps
// it simple with just one modulator.
// Calculate the new carrier phase. This phase is also changed by
// the modulator wave (FM!) and also the mouse wheel and vibrato.
float x = V->car + V->dcar + y * V->menv + _modulationAmount;
// Wrap the phase if it goes out of bounds. Note that modulation
// may cause the carrier phase to go backwards.
while (x > 1.0f) x -= 2.0f;
while (x < -1.0f) x += 2.0f;
V->car = x;
// Create a 5th-order sine approximation. If you plot this formula,
// you'll get a sine-like shape between x = -1 and x = +1. That's
// why the carrier phase is restricted to the range [-1, +1].
// The richness parameter "distorts" this shape into something that
// looks more like a saw wave (which also boosts its amplitude).
float s = x + x * x * x * (_richness * x * x - 1.0f - _richness);
// Mix in the modulator waveform and apply the amplitude envelope.
o += V->cenv * (_modMix * V->mod1 + s);
}
// Go to the next active voice.
V++;
}
// Write the result into the output buffer.
*out1++ = o;
*out2++ = o;
}
// It's time to handle the event. This starts the new note, or stops the
// note if velocity is 0. Also handles the sustain pedal being lifted.
if (frame < sampleFrames) {
int note = _notes[event++];
int vel = _notes[event++];
noteOn(note, vel);
}
}
// Turn off voices whose envelope has dropped below the minimum level.
_numActiveVoices = NVOICES;
for (int v = 0; v < NVOICES; ++v) {
if (_voices[v].env < SILENCE) {
_voices[v].env = 0.0f;
_voices[v].cenv = 0.0f;
_numActiveVoices--;
}
if (_voices[v].menv < SILENCE) { // stop modulation envelope
_voices[v].menv = 0.0f;
_voices[v].mlev = 0.0f;
}
}
} else {
// No voices playing and no events, so render an empty block.
while (--sampleFrames >= 0) {
*out1++ = 0.0f;
*out2++ = 0.0f;
}
}
// Mark the events buffer as done.
_notes[0] = EVENTS_DONE;
}
void DX10AudioProcessor::noteOn(int note, int velocity)
{
if (velocity > 0) {
// Find quietest voice. Voices that are not playing have env = 0.
float l = 1.0f;
int vl = 0;
for (int v = 0; v < NVOICES; v++) {
if (_voices[v].env < l) {
l = _voices[v].env;
vl = v;
}
}
/*
Calculate the pitch for this MIDI note.
The formula for this is: `440 * 2^((note - 69)/12)`. However, the code
below does `exp(0.05776 * note)`, which is equivalent to `2^(note/12)`.
The note number here is treated as the number of semitones away from the
base frequency. Instead of the usual 440 Hz, the base frequency is now
of the lowest possible note (MIDI note number 0), which is 8.1757989156.
That is why that factor 8.1757989156 is part of the `_tune` variable.
We also add the amount of fine-tuning to the number of semitones in the
formula. Since fine-tuning is ±100 cents, the pitch can go up or down by
one additional semitone.
*/
float p = std::exp(0.05776226505f * (float(note) + _fineTune));
_voices[vl].note = note;
// Reset the carrier oscillator phase and calculate its phase increment.
// Also apply pitch bending here already rather than during rendering,
// because apparently that is a bit tricky... which means you can't really
// pitch bend the note while it's playing.
_voices[vl].car = 0.0f;
_voices[vl].dcar = _tune * _pitchBend * p;
/*
Some more details:
The phase increment `dcar` describes how many samples it takes to count
from 0 up to 1. If the pitch is 100 Hz and the sample rate is 44100 Hz,
then dcar is 0.002268, so that it takes 441 samples to count from 0 to 1.
However... `dcar` actually only describes the length of a half period!
If you look at the computed pitch for the MIDI note number, as given by
`_tune * p * sampleRate`, you'll notice it's an octave too high. Since
the pitch is 2x higher, the period is half the size of what it should be.
(See also the comment in update() where _tune is calculated.)
Why? In processBlock() the carrier phase value does not go from 0 to 1,
but from -1 to +1. This is a distance of 2.0, and so two half-periods
make one full period.
*/
// Change the modulator's envelope levels based on the note that's played.
// We can re-use the pitch for this. If the note is higher than G4, disable
// key tracking (not sure why that is done). Keep in mind that the pitch
// is p * 8.1757, so p = 50 is 408.8 Hz, which is in between G4 and G#4.
if (p > 50.0f) p = 50.0f;
// Also modify the modulator envelope using velocity. If sensitivity = 0%,
// then velocity is always 64. If sensitivity = 100%, then the velocity is
// used unchanged. This means p gets multiplied by a value in between 1 and
// 127. The max value for p is then 6350; the minimum value is 1.
p *= (64.0f + _velocitySensitivity * (velocity - 64));
// Set the envelope levels for the modulator.
_voices[vl].menv = _modInitialLevel * p;
_voices[vl].mlev = _modSustain * p;
_voices[vl].mdec = _modDecay;
// The phase increment for the modulator is based on that of the carrier.
// As explained elsewhere, since `_ratio` contains the factor PI/2 instead
// of PI, the true ratio is actually half the ratio shown in the UI, i.e.
// if you choose Coarse = 1 and play a 440 Hz tone, the modulator is 220 Hz.
// If ratio is 0, the modulator is disabled.
_voices[vl].dmod = _ratio * _voices[vl].dcar;
// The modulator is a basic sine wave. To create this sine wave, it uses
// a simple oscillator algorithm. Here, we initialize the sine oscillator
// for the modulator.
_voices[vl].mod0 = 0.0f;
_voices[vl].mod1 = std::sin(_voices[vl].dmod);
_voices[vl].dmod = 2.0f * std::cos(_voices[vl].dmod);
// The carrier envelope starts out at its maximum level and immediately
// begins decaying. We set the initial value based on the note velocity
// and volume CC. We also scale the level with the richness of the sound,
// given by the Waveform parameter, to compensate for the amplitude boost
// that the sawtooth shape gives.
float param13 = apvts.getRawParameterValue("Waveform")->load();
_voices[vl].env = (1.5f - param13) * _volume * (velocity + 10);
_voices[vl].cdec = _decay;
// Even though `env` will always be decaying, if an attack is set, we will
// need to fade in the voice. `cenv` is derived from `env`, but with attack
// applied, and this is the actual envelope level that we'll use.
_voices[vl].catt = _attack;
_voices[vl].cenv = 0.0f;
}
else { // note off
// We also get here when the sustain pedal is released. In that case,
// the note number is SUSTAIN and any voices in SUSTAIN are released.
for (int v = 0; v < NVOICES; v++) {
// Any voices playing this note?
if (_voices[v].note == note) {
// If the sustain pedal is not pressed, then start envelope release.
if (_sustain == 0) {
_voices[v].cdec = _release;
_voices[v].env = _voices[v].cenv;
_voices[v].catt = 1.0f; // finish attack, if any
_voices[v].mlev = 0.0f;
_voices[v].mdec = _modRelease;
} else {
// Sustain pedal is pressed, so put the note in sustain mode.
_voices[v].note = SUSTAIN;
}
}
}
}
}
juce::AudioProcessorEditor *DX10AudioProcessor::createEditor()
{
auto editor = new juce::GenericAudioProcessorEditor(*this);
editor->setSize(500, 1000);
return editor;
}
void DX10AudioProcessor::getStateInformation(juce::MemoryBlock &destData)
{
copyXmlToBinary(*apvts.copyState().createXml(), destData);
}
void DX10AudioProcessor::setStateInformation(const void *data, int sizeInBytes)
{
std::unique_ptr<juce::XmlElement> xml(getXmlFromBinary(data, sizeInBytes));
if (xml.get() != nullptr && xml->hasTagName(apvts.state.getType())) {
apvts.replaceState(juce::ValueTree::fromXml(*xml));
}
}
juce::AudioProcessorValueTreeState::ParameterLayout DX10AudioProcessor::createParameterLayout()
{
juce::AudioProcessorValueTreeState::ParameterLayout layout;
// The original plug-in, being written for VST2, used parameters from 0 to 1.
// It would be nicer to change the parameters to more natural ranges, which
// JUCE allows us to do. For example, the octave setting could be -3 to +3,
// rather than having to map [0, 1] to [-3, +3]. However, the factory presets
// are specified as 0 - 1 too and I didn't feel like messing with those.
// This is why we're keeping the parameters as they were originally.
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Attack", 1),
"Attack",
juce::NormalisableRange<float>(),
0.0f,
juce::AudioParameterFloatAttributes()
.withLabel("%")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(value * 100.0f));
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Decay", 1),
"Decay",
juce::NormalisableRange<float>(),
0.65f,
juce::AudioParameterFloatAttributes()
.withLabel("%")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(value * 100.0f));
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Release", 1),
"Release",
juce::NormalisableRange<float>(),
0.441f,
juce::AudioParameterFloatAttributes()
.withLabel("%")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(value * 100.0f));
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Coarse", 1),
"Coarse",
juce::NormalisableRange<float>(),
0.842f,
juce::AudioParameterFloatAttributes()
.withLabel("ratio")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(std::floor(40.1f * value * value)));
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Fine", 1),
"Fine",
juce::NormalisableRange<float>(),
0.329f,
juce::AudioParameterFloatAttributes()
.withLabel("ratio")
.withStringFromValueFunction(
[](float value, int) {
float fine = 0.0f;
if (value < 0.5f) {
fine = 0.2f * value * value;
} else {
switch (int(8.9f * value)) {
case 4: fine = 0.25f; break;
case 5: fine = 0.33333333f; break;
case 6: fine = 0.50f; break;
case 7: fine = 0.66666667f; break;
default: fine = 0.75f;
}
}
return juce::String(fine, 3);
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Mod Init", 1),
"Mod Init",
juce::NormalisableRange<float>(),
0.23f,
juce::AudioParameterFloatAttributes()
.withLabel("%")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(value * 100.0f));
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Mod Dec", 1),
"Mod Dec",
juce::NormalisableRange<float>(),
0.8f,
juce::AudioParameterFloatAttributes()
.withLabel("%")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(value * 100.0f));
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Mod Sus", 1),
"Mod Sus",
juce::NormalisableRange<float>(),
0.05f,
juce::AudioParameterFloatAttributes()
.withLabel("%")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(value * 100.0f));
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Mod Rel", 1),
"Mod Rel",
juce::NormalisableRange<float>(),
0.8f,
juce::AudioParameterFloatAttributes()
.withLabel("%")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(value * 100.0f));
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Mod Vel", 1),
"Mod Vel",
juce::NormalisableRange<float>(),
0.9f,
juce::AudioParameterFloatAttributes()
.withLabel("%")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(value * 100.0f));
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Vibrato", 1),
"Vibrato",
juce::NormalisableRange<float>(),
0.0f,
juce::AudioParameterFloatAttributes()
.withLabel("%")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(value * 100.0f));
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Octave", 1),
"Octave",
juce::NormalisableRange<float>(),
0.5f,
juce::AudioParameterFloatAttributes()
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(value * 6.9f) - 3);
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("FineTune", 1),
"FineTune",
juce::NormalisableRange<float>(),
0.5f,
juce::AudioParameterFloatAttributes()
.withLabel("cents")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(200.0f * value - 100.0f));
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Waveform", 1),
"Waveform",
juce::NormalisableRange<float>(),
0.447f,
juce::AudioParameterFloatAttributes()
.withLabel("%")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(value * 100.0f));
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Mod Thru", 1),
"Mod Thru",
juce::NormalisableRange<float>(),
0.0f,
juce::AudioParameterFloatAttributes()
.withLabel("%")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(int(value * 100.0f));
}
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("LFO Rate", 1),
"LFO Rate",
juce::NormalisableRange<float>(),
0.414f,
juce::AudioParameterFloatAttributes()
.withLabel("Hz")
.withStringFromValueFunction(
[](float value, int) {
return juce::String(25.0f * value * value, 2);
}
)));
return layout;
}
juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter()
{
return new DX10AudioProcessor();
}
================================================
FILE: DX10/Source/PluginProcessor.h
================================================
#pragma once
#include <JuceHeader.h>
const int NPARAMS = 16; // number of parameters
const int NVOICES = 8; // max polyphony
const float SILENCE = 0.0003f; // voice choking
// Describes a factory preset.
struct DX10Program
{
DX10Program(const char *name,
float p0, float p1, float p2, float p3,
float p4, float p5, float p6, float p7,
float p8, float p9, float p10, float p11,
float p12, float p13, float p14, float p15);
char name[24];
float param[NPARAMS];
};
// State for an active voice.
struct Voice
{
// What note triggered this voice, or SUSTAIN when the key is released
// but the sustain pedal is still held down. 0 if the voice is inactive.
int note;
// Carrier oscillator
float car; // current phase value
float dcar; // phase increment
// Modulator sine oscillator
float dmod; // phase increment
float mod0;
float mod1;
// Carrier envelope
float env; // current envelope level
float cenv; // smoothed envelope that includes the attack portion
float catt; // smoothing coefficient for attack
float cdec; // decay mutiplier
// Modulator envelope
float menv; // current envelope level
float mlev; // target level
float mdec; // decay multiplier
};
class DX10AudioProcessor : public juce::AudioProcessor
{
public:
DX10AudioProcessor();
~DX10AudioProcessor() override;
void prepareToPlay(double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
void reset() override;
bool isBusesLayoutSupported(const BusesLayout &layouts) const override;
void processBlock(juce::AudioBuffer<float> &, juce::MidiBuffer &) override;
juce::AudioProcessorEditor *createEditor() override;
bool hasEditor() const override { return true; }
const juce::String getName() const override;
bool acceptsMidi() const override { return true; }
bool producesMidi() const override { return false; }
bool isMidiEffect() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
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;
juce::AudioProcessorValueTreeState apvts { *this, nullptr, "Parameters", createParameterLayout() };
private:
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
void update();
void resetState();
void createPrograms();
void processEvents(juce::MidiBuffer &midiMessages);
void noteOn(int note, int velocity);
// The factory presets.
std::vector<DX10Program> _programs;
// Index of the active preset.
int _currentProgram;
// The current sample rate and 1 / sample rate.
float _sampleRate, _inverseSampleRate;
// MIDI note on / note off events for the current block. Each event is
// described by 3 values: delta time, note number, velocity.
static const int EVENTBUFFER = 120;
int _notes[EVENTBUFFER + 8];
// Special event code that marks the end of the MIDI events list.
const int EVENTS_DONE = 99999999;
// Special "note number" that says this voice is now kept alive by the
// sustain pedal being pressed down. As soon as the pedal is released,
// this voice will fade out.
const int SUSTAIN = 128;
// List of the active voices.
Voice _voices[NVOICES] = { 0 };
// How many voices are currently in use.
int _numActiveVoices;
// The LFO only updates every 100 samples. This counter keeps track of when
// the next update is.
int _lfoStep;
// Used by the LFO to approximate a sine wave.
float _lfo0, _lfo1;
// Current amount of mod wheel + vibrato modulation. Because the LFO is only
// updated every 100 samples, we need to keep track of this across calls to
// processBlock().
float _modulationAmount;
// === Parameter values ===
// Tuning: number of octaves up or down.
float _tune;
// Fine-tuning: between -1.0 and +1.0 semitones (or -100 to +100 cents).
float _fineTune;
// Modulator ratio as a multiple of the carrier frequency.
float _ratio;
// Carrier envelope settings.
float _attack, _decay, _release;
// Modulator envelope settings.
float _modInitialLevel, _modDecay, _modSustain, _modRelease;
// Velocity sensitivity for the modulator envelope (for brightness).
float _velocitySensitivity;
// The amount of vibrato to apply.
float _vibrato;
// Amount of waveshaping to add extra harmonics.
float _richness;
// How much to mix the modulator waveform into the final sound by itself.
// Normally the modulator is only used to change the carrier, but for some
// extra snazz you can make the modulator waveform audible as well.
float _modMix;
// Phase increment for the LFO.
float _lfoInc;
// === MIDI CC values ===
// Status of the damper pedal: 64 = pressed, 0 = released.
int _sustain;
// Output gain in linear units. Can be changed by MIDI CC 7.
float _volume;
// Modulation wheel value. Used to add more vibrato.
float _modWheel;
// Pitch bend value.
float _pitchBend;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DX10AudioProcessor)
};
================================================
FILE: Degrade/MDADegrade.jucer
================================================
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT id="kTPmKy" name="MDADegrade" projectType="audioplug" useAppConfig="0"
addUsingNamespaceToJuceHeader="0" displaySplashScreen="1" jucerFormatVersion="1">
<MAINGROUP id="jyZa0X" name="MDADegrade">
<GROUP id="{8F5CBD49-543A-BEB7-C649-0249E3A268C3}" name="Source">
<FILE id="L1CDCN" name="PluginProcessor.cpp" compile="1" resource="0"
file="Source/PluginProcessor.cpp"/>
<FILE id="RtkckC" name="PluginProcessor.h" compile="0" resource="0"
file="Source/PluginProcessor.h"/>
</GROUP>
</MAINGROUP>
<JUCEOPTIONS JUCE_STRICT_REFCOUNTEDPOINTER="1" JUCE_VST3_CAN_REPLACE_VST2="0"/>
<EXPORTFORMATS>
<XCODE_MAC targetFolder="Builds/MacOSX">
<CONFIGURATIONS>
<CONFIGURATION isDebug="1" name="Debug" targetName="MDADegrade"/>
<CONFIGURATION isDebug="0" name="Release" targetName="MDADegrade"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_devices" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_formats" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_plugin_client" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_processors" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_utils" path="../../JUCE/modules"/>
<MODULEPATH id="juce_core" path="../../JUCE/modules"/>
<MODULEPATH id="juce_data_structures" path="../../JUCE/modules"/>
<MODULEPATH id="juce_events" path="../../JUCE/modules"/>
<MODULEPATH id="juce_graphics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_gui_basics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_gui_extra" path="../../JUCE/modules"/>
</MODULEPATHS>
</XCODE_MAC>
</EXPORTFORMATS>
<MODULES>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_plugin_client" showAllCode="1" useLocalCopy="0"
useGlobalPath="1"/>
<MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
</MODULES>
</JUCERPROJECT>
================================================
FILE: Degrade/README.markdown
================================================
# Degrade
This plug-in reduces the bit-depth and sample rate of the input audio (using the Quantize and Sample Rate parameters) and has some other features for attaining the sound of vintage digital hardware. The headroom control is a peak clipper, and the Non-Linearity controls add some harmonic distortion to thicken the sound. Post Filter is a low-pass filter to remove some of the grit introduced by the other controls.
| Parameter | Description |
| --------- | ----------- |
| Headroom | Peak clipping threshold |
| Quantize | Bit depth (typically 8 or below for "telephone" quality) |
| Sample Rate | Reduced sample rate |
| Sample Mode | Sample or Hold for different sound characters |
| Post Filter | Low-pass filter to muffle the distortion produced by the previous controls |
| Non-Linearity | Additional harmonic distortion "thickening" |
| Non-Linear Mode | Odd or Even for different sound characters |
| Output | Level trim |
================================================
FILE: Degrade/Source/PluginProcessor.cpp
================================================
#include "PluginProcessor.h"
MDADegradeAudioProcessor::MDADegradeAudioProcessor()
: AudioProcessor(BusesProperties()
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
.withOutput("Output", juce::AudioChannelSet::stereo(), true))
{
}
MDADegradeAudioProcessor::~MDADegradeAudioProcessor()
{
}
const juce::String MDADegradeAudioProcessor::getName() const
{
return JucePlugin_Name;
}
int MDADegradeAudioProcessor::getNumPrograms()
{
return 1;
}
int MDADegradeAudioProcessor::getCurrentProgram()
{
return 0;
}
void MDADegradeAudioProcessor::setCurrentProgram(int index)
{
}
const juce::String MDADegradeAudioProcessor::getProgramName(int index)
{
return {};
}
void MDADegradeAudioProcessor::changeProgramName(int index, const juce::String &newName)
{
}
void MDADegradeAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
{
resetState();
}
void MDADegradeAudioProcessor::releaseResources()
{
}
void MDADegradeAudioProcessor::reset()
{
resetState();
}
bool MDADegradeAudioProcessor::isBusesLayoutSupported(const BusesLayout &layouts) const
{
return layouts.getMainOutputChannelSet() == juce::AudioChannelSet::stereo();
}
void MDADegradeAudioProcessor::resetState()
{
_accum = 0.0f;
_currentSample = 0.0f;
_buf1 = _buf2 = _buf3 = _buf4 = _buf6 = _buf7 = _buf8 = _buf9 = 0.0f;
_sampleIndex = 1;
}
float MDADegradeAudioProcessor::filterFreq(float hz)
{
/*
The filter is a one-pole filter: y(n) = b*x(n) + a*y(n - 1).
This function calculates the value of the coefficient a, given a cutoff
frequency. And then we set b = (1 - a) to get unity gain.
I'm not sure where the formula used here comes from. It does not give a
response that has -3 dB at the cutoff frequency.
The formula that does give a -3 dB cutoff is:
k = 1.0 - cos(2.0 * pi * hz / sampleRate)
a = 1 + k - sqrt(k * k + 2.0 * k)
Another way to calculate the coefficient is the approximation:
a = exp(-2 * pi * hz / sampleRate)
*/
float r = 0.999f;
float j = r * r - 1.0f;
float k = 2.0f - 2.0f * r * r * std::cos(0.647f * hz / float(getSampleRate()));
return (std::sqrt(k * k - 4.0f * j * j) - k) / (2.0f * j);
}
void MDADegradeAudioProcessor::update()
{
// The sample interval is a number between 1 and 10. If 1, we read from the
// input buffer at the project's sample rate. If the interval is 2, we skip
// every other sample, cutting the effective sample rate in half; and so on.
_sampleInterval = apvts.getRawParameterValue("Sample Rate")->load();
// In sample-and-hold mode, we do read every single sample but add them up
// over the sampleInterval and take the mean once we have enough samples.
_mode = apvts.getRawParameterValue("Sample Mode")->load();
// Headroom: convert from decibels to a linear value.
float headroom = apvts.getRawParameterValue("Headroom")->load();
_clip = juce::Decibels::decibelsToGain(headroom);
// Filtering coefficients. The filter does: y(n) = fi * x(n) + fo * y(n - 1).
// fo is the coefficient for the feedback loop, fi controls the overall gain.
// We choose fi so that it keeps the amplitude of the unfiltered frequencies
// at 0 dB (unity gain). Since fi = 1 - fo, that makes this an "exponentially
// weighted moving average" or EWMA filter.
_fo = filterFreq(apvts.getRawParameterValue("PostFilter")->load());
_fi = 1.0f - _fo;
// The filter stage actually consists of several identical filters in series.
// As an optimization, make the gain coefficient (1 - fo)^4, which will save
// doing a few multiplications.
_fi = _fi * _fi;
_fi = _fi * _fi;
// The formula used for quantization is:
// x = int(x * 2^bits) / (2^bits) = int(x * g1) / g1 = int(x * g1) * g2
//
// Note that g2 = 1.0f / (2.0f * g1) instead of just 1.0f / g1. That extra
// 1/2 compensates for the signal being twice as loud because we will convert
// from stereo to mono by adding up both channels.
//
// The original code had a bug here, where it went from 2 - 14 bits instead
// of the 4 - 16 bits that was intended.
int numBits = apvts.getRawParameterValue("Quantize")->load();
_g1 = float(1 << numBits);
_g2 = 1.0f / (2.0f * _g1);
// In sample-and-hold mode, the samples get accumulated over the interval,
// which means the accumulator can grow very large. To take the mean value,
// we divide by the number of samples added. We can do this in g1 and save
// an extra instruction in the processing loop.
if (_mode == 1.0f) {
_g1 /= float(_sampleInterval);
}
// Note: the original plug-in made g1 negative which serves no purpose except
// to invert the signal's phase. Not sure if that was intended or not; enable
// it by uncommenting the line below.
// _g1 = -_g1;
// The output level is in dB, so convert to linear gain.
float outputLevel = apvts.getRawParameterValue("Output")->load();
_g3 = juce::Decibels::decibelsToGain(outputLevel);
// Non-linearity: 0 = x^1 ... 1 = x^0.707. The plug-in uses an exponential
// curve between these two points but a linear interpolation using jmap()
// between 1 and 1/sqrt(2) would give virtually the same result.
float nonlinearity = apvts.getRawParameterValue("Non-Linearity")->load();
_linNeg = std::pow(10.0f, -0.15f * nonlinearity);
// If the mode is odd, the non-linearity is not applied to positive samples.
if (apvts.getRawParameterValue("Non-Lin Mode")->load() == 0.0f) {
_linPos = 1.0f;
} else {
_linPos = _linNeg;
}
}
void MDADegradeAudioProcessor::processBlock(juce::AudioBuffer<float> &buffer, juce::MidiBuffer &midiMessages)
{
juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
// Clear any output channels that don't contain input data.
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) {
buffer.clear(i, 0, buffer.getNumSamples());
}
update();
const float *in1 = buffer.getReadPointer(0);
const float *in2 = buffer.getReadPointer(1);
float *out1 = buffer.getWritePointer(0);
float *out2 = buffer.getWritePointer(1);
// Make local copies for moar speeed!
const float linNeg = _linNeg;
const float linPos = _linPos;
const float clip = _clip;
const float mode = _mode;
const float fi = _fi, fo = _fo;
const float g1 = _g1, g2 = _g2, g3 = _g3;
const int sampleInterval = _sampleInterval;
int sampleIndex = _sampleIndex;
float accum = _accum;
float x = _currentSample;
float b1 = _buf1, b2 = _buf2, b3 = _buf3, b4 = _buf4,
b6 = _buf6, b7 = _buf7, b8 = _buf8, b9 = _buf9;
for (int i = 0; i < buffer.getNumSamples(); ++i) {
/*
Order of the FX:
1. Convert stereo to mono
2. Skip samples depending on the sampling interval
3. Quantize
4. Apply non-linearity x^lin
5. Headroom clipping
6. Apply output gain
7. Filter
*/
// Combine the stereo channels into a single mono value.
// In regular sample mode (mode = 0.0), accum holds only the most
// recent sample value. If sample-and-hold is active (mode = 1.0), we
// add the new value to accum so that it holds the sum of the previous
// samples in this sampling interval. Multiplying by mode (0 or 1) is
// simpler and possibly faster than using an if-statement.
accum = in1[i] + in2[i] + mode * accum;
// Time to take a new sample?
// The original code used == to check if the sample interval is over,
// but sampleInterval can change when the user adjusts the parameters.
// Now the current index may end up being larger than the interval, so
// we use >= to do the check.
if (sampleIndex >= sampleInterval) {
// Quantize. In sample-and-hold mode, this also takes the mean over
// the sampling interval (in g1). It also divides by 2 (in g2) to
// compensate for the stereo -> mono conversion which made the signal
// twice as loud.
x = float(g2 * int(accum * g1));
// Apply the non-linearity, followed by headroom clipping.
if (x > 0.0f) {
x = std::pow(x, linPos);
if (x > clip) x = clip;
} else {
x = -std::pow(-x, linNeg);
if (x < -clip) x = -clip;
}
// Reset the accumulator.
accum = 0.0f;
sampleIndex = 0;
}
sampleIndex += 1;
// Apply output gain (g3) and then filter.
// The filter is eight identical first-order feedback filters in a row.
// Each filter does: y(n) = fi * x(n) + fo * y(n - 1) where fi = 1 - fo.
// In the implementation below only the first filter (b1) and the fifth
// filter (b6) actually apply the fi coefficient, but the coefficient
// they're using is (1 - fo)^4. This saves some multiplications but is
// otherwise equivalent. You could also do (1 - fo)^8 and only apply it
// to b1, not b6, but that can get numerically unstable when fo is large.
b1 = fi * (x * g3) + fo * b1;
b2 = b1 + fo * b2;
b3 = b2 + fo * b3;
b4 = b3 + fo * b4;
b6 = fi * b4 + fo * b6;
b7 = b6 + fo * b7;
b8 = b7 + fo * b8;
b9 = b8 + fo * b9;
// If you're wondering what happened to b5, that's the name the original
// code used for x, but I found this confusing. b9 is the final output of
// the filter stage. The other b1-b8 are the intermediate output values
// for the individual filters and also their delay units (z^-1).
out1[i] = b9;
out2[i] = b9;
}
// Reset the state if we have numeric underflow in the output.
if (std::abs(b1) < 1.0e-10f) {
_buf1 = 0.0f; _buf2 = 0.0f; _buf3 = 0.0f; _buf4 = 0.0f;
_buf6 = 0.0f; _buf7 = 0.0f; _buf8 = 0.0f; _buf9 = 0.0f;
_accum = 0.0f;
_currentSample = 0.0f;
} else {
// Copy the local variables back into the member variables, so we can
// resume from where we left off in the next call to processBlock.
_buf1 = b1; _buf2 = b2; _buf3 = b3; _buf4 = b4;
_buf6 = b6; _buf7 = b7; _buf8 = b8; _buf9 = b9;
// We also keep track of x and accum in between calls to processBlock,
// in case the sampleInterval is greater than 1.
_accum = accum;
_currentSample = x;
}
_sampleIndex = sampleIndex;
}
juce::AudioProcessorEditor *MDADegradeAudioProcessor::createEditor()
{
return new juce::GenericAudioProcessorEditor(*this);
}
void MDADegradeAudioProcessor::getStateInformation(juce::MemoryBlock &destData)
{
copyXmlToBinary(*apvts.copyState().createXml(), destData);
}
void MDADegradeAudioProcessor::setStateInformation(const void *data, int sizeInBytes)
{
std::unique_ptr<juce::XmlElement> xml(getXmlFromBinary(data, sizeInBytes));
if (xml.get() != nullptr && xml->hasTagName(apvts.state.getType())) {
apvts.replaceState(juce::ValueTree::fromXml(*xml));
}
}
juce::AudioProcessorValueTreeState::ParameterLayout MDADegradeAudioProcessor::createParameterLayout()
{
juce::AudioProcessorValueTreeState::ParameterLayout layout;
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Headroom", 1),
"Headroom",
juce::NormalisableRange<float>(-30.0f, 0.0f, 0.01f),
-6.0f,
juce::AudioParameterFloatAttributes().withLabel("dB")));
layout.add(std::make_unique<juce::AudioParameterInt>(
juce::ParameterID("Quantize", 1),
"Quantize",
4, 16, 10,
juce::AudioParameterIntAttributes().withLabel("bits")));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Sample Rate", 1),
"Sample Rate",
juce::NormalisableRange<float>(1.0f, 10.0f, 1.0f),
3.0f));
// Note: In the original plug-in, the sample rate parameter was shown as
// getSampleRate() / sampleInterval. For an interval of 1, it would show
// 44100; for an interval of 2, it showed 22050; and so on, depending on
// the project's actual sample rate. We simply display the sample interval
// but a custom UI could display the effective sample rate.
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID("Sample Mode", 1),
"Sample Mode",
juce::StringArray({ "Sample", "Hold" }),
0));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("PostFilter", 1),
"PostFilter",
juce::NormalisableRange<float>(200.0f, 20000.0f, 1.0f, 0.5f),
10000.0f,
juce::AudioParameterFloatAttributes().withLabel("Hz")));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Non-Linearity", 1),
"Non-Linearity",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f),
0.58f));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID("Non-Lin Mode", 1),
"Non-Lin Mode",
juce::StringArray({ "Odd", "Even" }),
0));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Output", 1),
"Output",
juce::NormalisableRange<float>(-20.0f, 20.0f, 0.01f),
0.0f,
juce::AudioParameterFloatAttributes().withLabel("dB")));
return layout;
}
juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter()
{
return new MDADegradeAudioProcessor();
}
================================================
FILE: Degrade/Source/PluginProcessor.h
================================================
#pragma once
#include <JuceHeader.h>
class MDADegradeAudioProcessor : public juce::AudioProcessor
{
public:
MDADegradeAudioProcessor();
~MDADegradeAudioProcessor() override;
void prepareToPlay(double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
void reset() override;
bool isBusesLayoutSupported(const BusesLayout &layouts) const override;
void processBlock(juce::AudioBuffer<float> &, juce::MidiBuffer &) override;
juce::AudioProcessorEditor *createEditor() override;
bool hasEditor() const override { return true; }
const juce::String getName() const override;
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
bool isMidiEffect() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
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;
juce::AudioProcessorValueTreeState apvts { *this, nullptr, "Parameters", createParameterLayout() };
private:
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
void update();
float filterFreq(float hz);
void resetState();
// To reduce the sampling rate, we only read from the input buffer every
// sampleInterval samples.
int _sampleInterval, _sampleIndex;
// This is 1.0 if sample-and-hold mode is active, 0.0 if not.
float _mode;
// Used to perform quantizing.
float _g1, _g2;
// Non-linearity values for negative and positive samples.
float _linNeg, _linPos;
// Level for headroom clipping.
float _clip;
// Output gain.
float _g3;
// Filter coefficients.
float _fi, _fo;
// Sum of the previous samples in sample-and-hold mode.
float _accum;
// The most recently computed sample value, before filtering.
float _currentSample;
// Delay units for the 8 filter stages.
float _buf1, _buf2, _buf3, _buf4, _buf6, _buf7, _buf8, _buf9;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MDADegradeAudioProcessor)
};
================================================
FILE: Delay/MDADelay.jucer
================================================
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT id="DRUbMS" name="MDADelay" projectType="audioplug" useAppConfig="0"
addUsingNamespaceToJuceHeader="0" displaySplashScreen="1" jucerFormatVersion="1">
<MAINGROUP id="ovN7la" name="MDADelay">
<GROUP id="{FEFD974B-1AAE-7E93-6E35-90B616FE74B8}" name="Source">
<FILE id="MD3Pgg" name="PluginProcessor.cpp" compile="1" resource="0"
file="Source/PluginProcessor.cpp"/>
<FILE id="EUZVHN" name="PluginProcessor.h" compile="0" resource="0"
file="Source/PluginProcessor.h"/>
</GROUP>
</MAINGROUP>
<JUCEOPTIONS JUCE_STRICT_REFCOUNTEDPOINTER="1" JUCE_VST3_CAN_REPLACE_VST2="0"/>
<EXPORTFORMATS>
<XCODE_MAC targetFolder="Builds/MacOSX">
<CONFIGURATIONS>
<CONFIGURATION isDebug="1" name="Debug" targetName="MDADelay"/>
<CONFIGURATION isDebug="0" name="Release" targetName="MDADelay"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_audio_devices" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_audio_formats" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_audio_plugin_client" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_audio_processors" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_audio_utils" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_core" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_data_structures" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_events" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_graphics" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_gui_basics" path="../../../../../JUCE/modules"/>
<MODULEPATH id="juce_gui_extra" path="../../../../../JUCE/modules"/>
</MODULEPATHS>
</XCODE_MAC>
</EXPORTFORMATS>
<MODULES>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_plugin_client" showAllCode="1" useLocalCopy="0"
useGlobalPath="1"/>
<MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
</MODULES>
</JUCERPROJECT>
================================================
FILE: Delay/README.markdown
================================================
# Delay
Simple stereo delay with feedback tone control.
| Parameter | Description |
| --------- | ----------- |
| Left Delay | Left channel delay time |
| Right Delay | Right channel delay as a percentage of left channel delay - variable to left, fixed ratios to right |
| Feedback | Feedback (sum of left and right outputs) |
| FB Tone | Feedback filtering - low-pass to left, high-pass to right |
| FX Mix | Wet / dry mix |
| Output | Level trim |
================================================
FILE: Delay/Source/PluginProcessor.cpp
================================================
#include "PluginProcessor.h"
MDADelayAudioProcessor::MDADelayAudioProcessor()
: AudioProcessor(BusesProperties()
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
.withOutput("Output", juce::AudioChannelSet::stereo(), true))
{
}
MDADelayAudioProcessor::~MDADelayAudioProcessor()
{
}
const juce::String MDADelayAudioProcessor::getName() const
{
return JucePlugin_Name;
}
int MDADelayAudioProcessor::getNumPrograms()
{
return 1;
}
int MDADelayAudioProcessor::getCurrentProgram()
{
return 0;
}
void MDADelayAudioProcessor::setCurrentProgram(int index)
{
}
const juce::String MDADelayAudioProcessor::getProgramName(int index)
{
return {};
}
void MDADelayAudioProcessor::changeProgramName(int index, const juce::String &newName)
{
}
void MDADelayAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
{
// Calculate how many samples we need for the delay buffer. This depends
// on the sample rate and the maximum allowed delay time: the larger the
// sample rate, the larger the buffer must be.
_delayMax = int(std::ceil(float(sampleRate) * _delayMaxMsec / 1000.0f));
_delayBuffer.resize(_delayMax);
resetState();
}
void MDADelayAudioProcessor::releaseResources()
{
}
void MDADelayAudioProcessor::reset()
{
resetState();
}
bool MDADelayAudioProcessor::isBusesLayoutSupported(const BusesLayout &layouts) const
{
return layouts.getMainOutputChannelSet() == juce::AudioChannelSet::stereo();
}
void MDADelayAudioProcessor::resetState()
{
_pos = 0;
_filt0 = 0.0f;
// Clear out the delay buffer.
memset(_delayBuffer.data(), 0, _delayMax * sizeof(float));
}
void MDADelayAudioProcessor::update()
{
const float samplesPerMsec = float(getSampleRate()) / 1000.0f;
// In the original plug-in, the parameter for the left channel delay length
// went from 0 to 1. To compute the number of samples of delay, it used the
// formula: ldel = int(delayMax * ldelParam * ldelParam).
// The reason the parameter got squared, is that this makes it easier to
// pick smaller delays. For example, at a sample rate of 44100, the maximum
// delay length is 22050 samples. With the parameter set to 0.5, the delay
// is not 11025 (= half) but 5512 samples (= half squared). In JUCE, we can
// simply have the parameter be in milliseconds and give the slider a skew.
float ldelParam = apvts.getRawParameterValue("L Delay")->load();
_ldel = int(ldelParam * samplesPerMsec);
// Make the minimum delay time 4 samples, not 0. A delay time of 0 would be
// equal to the maximum delay because of wrap-around, so that's not very
// useful. Although I'm not sure why the minimum delay is 4 samples, not 1.
// Notice that really short delays introduce a filtering effect.
if (_ldel < 4) _ldel = 4;
// This shouldn't happen, but a bit of defensive programming never hurts.
if (_ldel > _delayMax) _ldel = _delayMax;
// The right channel delay is a percentage of the left channel delay length.
float rdelParam = apvts.getRawParameterValue("R Delay")->load();
_rdel = int(ldelParam * samplesPerMsec * rightDelayRatio(rdelParam));
// Make sure the delay time does not become too large or too small.
if (_rdel > _delayMax) _rdel = _delayMax;
if (_rdel < 4) _rdel = 4;
// The "tone" control goes from -100 to +100. Moving to the left means
// low-pass filtering and to the right means high-pass filtering. When the
// tone control is centered, there is no filtering.
float toneParam = apvts.getRawParameterValue("Fb Tone")->load();
_filt = toneParam / 200.0f + 0.5f;
// Set the crossover frequency & high/low mix. Here, lmix determines how much
// the low-pass filtered sample is combined with the unfiltered sample, whose
// proportion is given by hmix.
if (_filt > 0.5f) { // high-pass:
_filt = 0.5f * _filt - 0.25f; // filt now goes 0 to 0.25
_lmix = -2.0f * _filt; // lmix goes from 0 to -0.5
_hmix = 1.0f;
} else { // low-pass:
_hmix = 2.0f * _filt; // hmix goes from 0 to 1
_lmix = 1.0f - _hmix; // lmix goes from 1 to 0
}
// At this point, _filt is a value between 0 and 0.5 (low-pass) or 0.25
// (high-pass). Turn this value into a cutoff frequency. On the left of
// the slider, the frequency goes from 158 Hz - 28 kHz. On the right, it
// goes from 158 Hz - 2113 Hz.
float hz = std::pow(10.0f, 2.2f + 4.5f * _filt);
// The filter itself is a one-pole filter: y(n) = (1 - f)*x(n) + f*y(n - 1).
// Calculate the coefficient f using the formula exp(-2pi * hz / sampleRate).
_filt = std::exp(-6.2831853f * hz / float(getSampleRate()));
// Feedback: value between 0 and 0.49. If this is 0, the delay repeats only
// once. For higher values, the delay will keeping echoing.
float feedbackParam = apvts.getRawParameterValue("Feedback")->load() / 100.0f;
_feedback = 0.495f * feedbackParam;
// Output gain is in decibels, so convert to a linear value.
float gain = apvts.getRawParameterValue("Output")->load();
gain = juce::Decibels::decibelsToGain(gain);
// For the wet/dry mix, don't do a linear interpolation but use a curve:
// dry = (1 - x^2) and wet = (1 - (1 - x)^2). Each curve is approx -3 dB at
// mix = 0.5, but added together they increase the gain by +3 dB at 50% mix
// (i.e. it's not an equal power curve).
float mix = apvts.getRawParameterValue("FX Mix")->load() / 100.0f;
_wet = gain * (1.0f - (1.0f - mix) * (1.0f - mix)) * 0.5f;
_dry = gain * (1.0f - mix * mix);
// Note: the original plug-in had an additional factor 2.0 in the formula
// for dry, which seems wrong. That would boost the signal by 6 dB if the
// mix is all dry (i.e. mix = 0), so I removed it.
// However, during processing we add the left and right signals to create a
// mono signal, which boosts the level by 6 dB if the mix is all wet (i.e.
// when mix = 1).
// To compensate for this, I'm lowering the wet level by -6 dB; that's what
// the * 0.5 is for. This is probably why dry originally had that extra
// factor 2. Now the volume of all dry vs. all wet is the same again but at
// 0 dB instead of +6 dB.
}
void MDADelayAudioProcessor::processBlock(juce::AudioBuffer<float> &buffer, juce::MidiBuffer &midiMessages)
{
juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
// Clear any output channels that don't contain input data.
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) {
buffer.clear(i, 0, buffer.getNumSamples());
}
update();
const float *in1 = buffer.getReadPointer(0);
const float *in2 = buffer.getReadPointer(1);
float *out1 = buffer.getWritePointer(0);
float *out2 = buffer.getWritePointer(1);
const float wet = _wet;
const float dry = _dry;
const float fb = _feedback;
const float f = _filt;
const float lmix = _lmix;
const float hmix = _hmix;
const int s = _delayMax;
float f0 = _filt0;
// This keeps track of where we will write new values in the delay buffer.
int p = _pos;
// Get the read positions for the left and right channels. These need to
// wrap around when they reach the end of the buffer, which is why we use
// the modulo operator.
int l = (p + _ldel) % s;
int r = (p + _rdel) % s;
// Note: This logic cannot tell apart a delay of 0 from a delay of _delayMax
// samples. Due to the modulo operation these point to the same index in the
// delay buffer. So a delay of 0 samples is not possible.
// The original plug-in didn't so this, and I have not implemented it either,
// but we could allow the user to set the delay time to 0, and then simply
// bypass all of the delay logic in the loop.
for (int i = 0; i < buffer.getNumSamples(); ++i) {
float a = in1[i];
float b = in2[i];
// Read from the delay buffer.
float dl = _delayBuffer[l];
float dr = _delayBuffer[r];
// Combine the left and right input samples into a mono signal.
// Also add the delayed values but attenuated by the feedback factor.
// The larger the feedback, the longer the sound will keep echoing.
float tmp = wet * (a + b) + fb * (dl + dr);
// Apply the low-pass filter. As seen in the other MDA plug-ins, this
// is a simple exponentially weighted moving average filter (EWMA).
f0 = f * (f0 - tmp) + tmp;
// Do the crossover mix and write the new value back into the delay line.
//
// How this mix works: lmix determines how important the low-pass
// filtered value is, while hmix says how important the unfiltered
// sample value is.
//
// When the tone control is on the left, hmix is (1 - lmix). The further
// to the left, the larger lmix and the smaller hmix, meaning that the
// low-pass filtered sample value is more now important. The closer the
// control is to the center, the larger hmix and the less important the
// low-pass filtered value becomes.
//
// When the tone control is on the right, hmix is 1 but lmix is negative,
// which means the low-pass filtered value is subtracted from the original,
// turning this into a high-pass filter. The more to the right the control
// is, the more this happens.
//
// The high-pass mode is more subtle than the low-pass mode since the
// maximum cutoff frequency is lower. It's mostly useful for getting rid
// of a muddy low end.
//
// Note that the filtering only applies to the delayed signal, not to the
// original (dry) signal.
_delayBuffer[p] = lmix * f0 + hmix * tmp;
// Update the locations where we will write into and read from the delay
// buffer. Since l = p + ldel and r = p + rdel, it means older / delayed
// values are to the right in the buffer (higher indices) and newer values
// are to the left (lower indices). Therefore, we must decrement and wrap
// around when we have reached the end (well, beginning) of the buffer.
p--; if (p < 0) p = s - 1;
l--; if (l < 0) l = s - 1;
r--; if (r < 0) r = s - 1;
// The output for this sample is the original sample mixed with the values
// we read from the delay buffer. Note that the output is still stereo but
// the delayed signal that gets mixed into the stream is always mono.
out1[i] = dry * a + dl;
out2[i] = dry * b + dr;
}
_pos = p;
// Trap denormals
if (std::abs(f0) < 1.0e-10f) _filt0 = 0.0f; else _filt0 = f0;
}
juce::AudioProcessorEditor *MDADelayAudioProcessor::createEditor()
{
return new juce::GenericAudioProcessorEditor(*this);
}
void MDADelayAudioProcessor::getStateInformation(juce::MemoryBlock &destData)
{
copyXmlToBinary(*apvts.copyState().createXml(), destData);
}
void MDADelayAudioProcessor::setStateInformation(const void *data, int sizeInBytes)
{
std::unique_ptr<juce::XmlElement> xml(getXmlFromBinary(data, sizeInBytes));
if (xml.get() != nullptr && xml->hasTagName(apvts.state.getType())) {
apvts.replaceState(juce::ValueTree::fromXml(*xml));
}
}
float MDADelayAudioProcessor::rightDelayRatio(float param) {
switch (int(param * 17.9f)) {
case 17: return 0.5000f; // fixed left/right ratios
case 16: return 0.6667f;
case 15: return 0.7500f;
case 14: return 0.8333f;
case 13: return 1.0000f;
case 12: return 1.2000f;
case 11: return 1.3333f;
case 10: return 1.5000f;
case 9: return 2.0000f;
default: return 4.0f * param; // variable ratio (param < 0.5)
}
}
juce::AudioProcessorValueTreeState::ParameterLayout MDADelayAudioProcessor::createParameterLayout()
{
juce::AudioProcessorValueTreeState::ParameterLayout layout;
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("L Delay", 1),
"L Delay",
juce::NormalisableRange<float>(0.1f, _delayMaxMsec, 0.01f, 0.4f),
250.0f,
juce::AudioParameterFloatAttributes().withLabel("ms")));
// The right channel delay time is expressed as a percentage of the left
// channel delay time, allowing you to make the delay appear earlier or later
// on the right than on the left. However, the parameter itself is a little
// tricky. The parameter value goes between 0 and 1. Moving the slider to the
// left (parameter between 0 and 0.5) makes the right channel delay between
// 0% and 200% of the left channel delay. Moving the slider to the right
// (parameter between 0.5 and 1) will choose from a set of fixed left:right
// ratios. To make this clearer to the user, we don't display the parameter
// value but the actual percentage that has been chosen.
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("R Delay", 1),
"R Delay",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.27f,
juce::AudioParameterFloatAttributes()
.withLabel("%")
.withStringFromValueFunction(
[](float value, int) { return juce::String(rightDelayRatio(value) * 100.0f, 1); }
)));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Feedback", 1),
"Feedback",
juce::NormalisableRange<float>(0.0f, 99.0f, 0.01f),
70.0f,
juce::AudioParameterFloatAttributes().withLabel("%")));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Fb Tone", 1),
"Fb Tone",
juce::NormalisableRange<float>(-100.0f, 100.0f, 0.01f),
0.0f,
juce::AudioParameterFloatAttributes().withLabel("Lo <> Hi")));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("FX Mix", 1),
"FX Mix",
juce::NormalisableRange<float>(0.0f, 100.0f, 0.01f),
33.0f,
juce::AudioParameterFloatAttributes().withLabel("%")));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Output", 1),
"Output",
juce::NormalisableRange<float>(-30.0f, 6.0f, 0.01f),
0.0f,
juce::AudioParameterFloatAttributes().withLabel("dB")));
return layout;
}
juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter()
{
return new MDADelayAudioProcessor();
}
================================================
FILE: Delay/Source/PluginProcessor.h
================================================
#pragma once
#include <JuceHeader.h>
class MDADelayAudioProcessor : public juce::AudioProcessor
{
public:
MDADelayAudioProcessor();
~MDADelayAudioProcessor() override;
void prepareToPlay(double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
void reset() override;
bool isBusesLayoutSupported(const BusesLayout &layouts) const override;
void processBlock(juce::AudioBuffer<float> &, juce::MidiBuffer &) override;
juce::AudioProcessorEditor *createEditor() override;
bool hasEditor() const override { return true; }
const juce::String getName() const override;
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
bool isMidiEffect() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
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;
juce::AudioProcessorValueTreeState apvts { *this, nullptr, "Parameters", createParameterLayout() };
private:
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
void update();
void resetState();
// Maps the position of the right delay slider to a percentage of the left
// channel delay time. Moving the slider to the left gives you a variable
// ratio between 0% and 200%. Moving the slider to the right chooses from a
// set of fixed ratios. At the center position, the ratio is 200%.
static float rightDelayRatio(float param);
// Maximum delay time in milliseconds. Feel free to make this smaller or
// larger. The original plug-in used a fixed number of samples, but that
// would make the maximum delay time different if the sample rate changed.
static constexpr float _delayMaxMsec = 500.0f;
// Maximum length of the delay buffer in samples.
int _delayMax;
// This buffer stores the delayed samples. There is only one delay buffer,
// which means any echos from the delayed signal are actually mono.
std::vector<float> _delayBuffer;
// Delay time in samples for the left & right channels.
int _ldel, _rdel;
// Write position in the delay buffer. This is where we will write the next
// new sample value.
int _pos;
// Wet & dry mix.
float _wet, _dry;
// Amount of echo feedback.
float _feedback;
// Low & high mix for the crossover filter.
float _lmix, _hmix;
// Low-pass filter coefficient.
float _filt;
// Delay unit for the low-pass filter.
float _filt0;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MDADelayAudioProcessor)
};
================================================
FILE: Detune/MDADetune.jucer
================================================
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT id="Ndjzja" name="MDADetune" projectType="audioplug" useAppConfig="0"
addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1">
<MAINGROUP id="Nme3Pw" name="MDADetune">
<GROUP id="{11021ED1-D94A-052F-F6E5-4501B8B344CA}" name="Source">
<FILE id="lVXjCI" name="PluginProcessor.cpp" compile="1" resource="0"
file="Source/PluginProcessor.cpp"/>
<FILE id="baUpim" name="PluginProcessor.h" compile="0" resource="0"
file="Source/PluginProcessor.h"/>
</GROUP>
</MAINGROUP>
<MODULES>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_plugin_client" showAllCode="1" useLocalCopy="0"
useGlobalPath="1"/>
<MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
</MODULES>
<JUCEOPTIONS JUCE_STRICT_REFCOUNTEDPOINTER="1" JUCE_VST3_CAN_REPLACE_VST2="0"/>
<EXPORTFORMATS>
<XCODE_MAC targetFolder="Builds/MacOSX">
<CONFIGURATIONS>
<CONFIGURATION isDebug="1" name="Debug" targetName="MDADetune"/>
<CONFIGURATION isDebug="0" name="Release" targetName="MDADetune"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_devices" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_formats" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_plugin_client" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_processors" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_utils" path="../../JUCE/modules"/>
<MODULEPATH id="juce_core" path="../../JUCE/modules"/>
<MODULEPATH id="juce_data_structures" path="../../JUCE/modules"/>
<MODULEPATH id="juce_events" path="../../JUCE/modules"/>
<MODULEPATH id="juce_graphics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_gui_basics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_gui_extra" path="../../JUCE/modules"/>
</MODULEPATHS>
</XCODE_MAC>
</EXPORTFORMATS>
</JUCERPROJECT>
================================================
FILE: Detune/README.markdown
================================================
# Detune
A low-quality stereo pitch shifter for the sort of chorus and detune effects found on multi-effects hardware.
| Parameter | Description |
| --------- | ----------- |
| Detune | Detune amount in cents (left channel is lowered in pitch, right channel is raised) |
| Mix | Wet / dry mix |
| Output | Level trim |
| Latency | Trade-off between latency and low-frequency response |
This plug-in is a pitch shifter designed to produce the classic detune effect, where the left channel is shifted down in pitch and the right channel is shifted up. This sounds similar to a chorus effect, but with less obvious modulation. The delay inherent in the pitch shifting process can be adjusted with the Latency control — longer settings are needed to make low frequency signals sound smoother, but can also add a nice doubling effect to vocals.
================================================
FILE: Detune/Source/PluginProcessor.cpp
================================================
#include "PluginProcessor.h"
MDADetuneAudioProcessor::MDADetuneAudioProcessor()
: AudioProcessor(BusesProperties()
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
.withOutput("Output", juce::AudioChannelSet::stereo(), true))
{
}
MDADetuneAudioProcessor::~MDADetuneAudioProcessor()
{
}
const juce::String MDADetuneAudioProcessor::getName() const
{
return JucePlugin_Name;
}
int MDADetuneAudioProcessor::getNumPrograms()
{
return 1;
}
int MDADetuneAudioProcessor::getCurrentProgram()
{
return 0;
}
void MDADetuneAudioProcessor::setCurrentProgram(int index)
{
}
const juce::String MDADetuneAudioProcessor::getProgramName(int index)
{
return {};
}
void MDADetuneAudioProcessor::changeProgramName(int index, const juce::String &newName)
{
}
void MDADetuneAudioProcessor::prepareToPlay(double newSampleRate, int samplesPerBlock)
{
sampleRate = float(newSampleRate);
resetState();
}
void MDADetuneAudioProcessor::releaseResources()
{
}
void MDADetuneAudioProcessor::reset()
{
resetState();
}
bool MDADetuneAudioProcessor::isBusesLayoutSupported(const BusesLayout &layouts) const
{
return layouts.getMainOutputChannelSet() == juce::AudioChannelSet::stereo();
}
void MDADetuneAudioProcessor::resetState()
{
std::memset(buf, 0, sizeof(buf));
std::memset(win, 0, sizeof(win));
pos0 = 0;
pos1 = pos2 = 0.0f;
}
void MDADetuneAudioProcessor::update()
{
// Number of semitones expressed as a value between 0 and 300 cents.
// This is skewed using a x^3 curve, putting ~38 cents at the middle
// of the slider and 300 cents at the rightmost position.
float param0 = apvts.getRawParameterValue("Detune")->load();
float semi = 3.0f * param0 * param0 * param0;
// 1.0594631^semi is the same as 2^(semi/12) and gives the step size
// used to pitch the sound down by this number of semitones.
dpos2 = std::pow(1.0594631f, semi);
// This is the same as 2^(-semi/12) and is the delta used to pitch up
// the sound by the given number of semitones.
dpos1 = 1.0f / dpos2;
// Output gain is -20 to +20 dB. Convert decibels to linear.
float param2 = apvts.getRawParameterValue("Output")->load();
float gain = juce::Decibels::decibelsToGain(param2);
// Dry/wet curve of (1 - x^2) for dry and (2x - x^2) for wet, with the
// output gain amount already multiplied into it.
float param1 = apvts.getRawParameterValue("Mix")->load();
dry = gain - gain * param1 * param1;
wet = (gain + gain - gain * param1) * param1;
// The latency parameter determines the length of the delay line.
// Since this parameter is a value between 0.0f and 1.0f, the expression
// (8 + int(4.9f * param)) produces a discrete set of values: 8, 9, 10, 11,
// and 12. The bit shift converts this into buffer lengths of respectively
// 256, 512, 1024, 2048, and 4096 samples. Note that `buflen` should always
// be a power of two.
float param3 = apvts.getRawParameterValue("Latency")->load();
int tmp = 1 << (8 + int(4.9f * param3));
// Recalculate the crossfade window (hanning half-overlap-and-add).
if (tmp != buflen) {
buflen = tmp;
if (buflen > BUFMAX) { buflen = BUFMAX; }
double phase = 0.0;
double step = 6.28318530718 / buflen;
for (int i = 0; i < buflen; i++) {
win[i] = float(0.5 - 0.5 * std::cos(phase));
phase += step;
}
}
}
void MDADetuneAudioProcessor::processBlock(juce::AudioBuffer<float> &buffer, juce::MidiBuffer &midiMessages)
{
juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
// Clear any output channels that don't contain input data.
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) {
buffer.clear(i, 0, buffer.getNumSamples());
}
update();
const float *in1 = buffer.getReadPointer(0);
const float *in2 = buffer.getReadPointer(1);
float *out1 = buffer.getWritePointer(0);
float *out2 = buffer.getWritePointer(1);
/*
How this works:
If you have a delay line where you keep steadily increasing the delay
length on every timestep, this will pitch down the sound because it
slows down the reading of the waveform. Conversely, if you decrease
the delay length by the same amount on every timestep, the sound gets
pitched up (it skips parts of the waveform).
There is a practical problem with this approach: It's not possible to
have a delay length smaller than zero and so we can't keep decreasing
it forever. Likewise, we can't keep increasing the delay length either
as that requires an infinitely long delay line.
A compromise is to use a short delay length and simply reset the read
pointer when it catches up with the write head. The Latency parameter
determines the length of the delay, with a minimum of 256 samples and
a maximum of 4096 samples. (A shorter delay has less latency but sounds
worse.)
However, resetting the read pointer will produce a nasty discontinuity.
The solution is to read from two places at once and crossfade between
them. The crossfading is done using a Hann window, which tapers off at
the edges, so that the discontinuity is suppressed.
*/
// For wrapping around the write pointer in the delay line.
// This only works correctly if `buflen` is a power of two!
const int mask = buflen - 1;
// For wrapping around the read pointers, which are floats.
const float wrapAround = float(buflen);
// We'll read the second sample half the delay length ahead.
const int halfLength = buflen >> 1;
for (int i = 0; i < buffer.getNumSamples(); ++i) {
// Read the input samples.
float a = in1[i];
float b = in2[i];
// Put the dry signal into the output variables already.
float c = dry * a;
float d = dry * b;
// Update the write position. For some reason this plug-in counts
// backwards, but that shouldn't matter. The wrap-around is handled
// by bitwise-and with the mask.
--pos0 &= mask;
// Write the input as a mono signal into the delay line. This already
// applies the wet gain, so we don't have to do this later.
buf[pos0] = wet * (a + b);
// Update the read position for the left channel, wrapping around
// if necessary. Note that this is a float because `dpos1` is the
// speed at which to step through the delay line, which will be a
// fractional number of samples. (In fact, dpos1 will be less than
// 1.0 so that this read index moves slower than the write index,
// which results in the sound being pitched down.)
pos1 -= dpos1;
if (pos1 < 0.0f) { pos1 += wrapAround; }
// Read the delay line using linear interpolation. This involves
// taking a weighted average between the sample ahead and behind.
int p1i = int(pos1); // integer position
float p1f = pos1 - float(p1i); // fraction
float u = buf[p1i]; // read first sample
++p1i &= mask; // move to next sample, maybe wrap
u += p1f * (buf[p1i] - u); // read next sample and blend
// Also read the sample half the delay length away (i.e. offset by
// 180 degrees). This also uses linear interpolation, same fraction.
int p2i = (p1i + halfLength) & mask;
float v = buf[p2i];
++p2i &= mask;
v += p1f * (buf[p2i] - v);
// Crossfade between the two interpolated samples, and add to the
// left channel output. The `u` sample is multiplied by the window,
// and the `v` sample by (1 - window), so this is another linear
// interpolation. The window index is chosen so that the closer the
// read pointer gets to the write pointer, the less the `u` sample
// and the more `v` sample will contribute, and vice versa.
int xi = (p1i - pos0) & mask;
c += v + win[xi] * (u - v);
// Apply the same logic to the right channel. This will pitch up the
// sound because `dpos2` is larger than 1.0, so that this read index
// will move faster than the write index and shorten the waveform.
pos2 -= dpos2;
if (pos2 < 0.0f) { pos2 += wrapAround; }
// First sample.
p1i = int(pos2);
p1f = pos2 - float(p1i);
u = buf[p1i];
++p1i &= mask;
u += p1f * (buf[p1i] - u);
// Second sample offset by half delay length.
p2i = (p1i + halfLength) & mask;
v = buf[p2i];
++p2i &= mask;
v += p1f * (buf[p2i] - v);
// Crossfade.
p2i = (p1i - pos0) & mask;
d += v + win[p2i] * (u - v);
// Write the output samples.
out1[i] = c;
out2[i] = d;
}
}
juce::AudioProcessorEditor *MDADetuneAudioProcessor::createEditor()
{
return new juce::GenericAudioProcessorEditor(*this);
}
void MDADetuneAudioProcessor::getStateInformation(juce::MemoryBlock &destData)
{
copyXmlToBinary(*apvts.copyState().createXml(), destData);
}
void MDADetuneAudioProcessor::setStateInformation(const void *data, int sizeInBytes)
{
std::unique_ptr<juce::XmlElement> xml(getXmlFromBinary(data, sizeInBytes));
if (xml.get() != nullptr && xml->hasTagName(apvts.state.getType())) {
apvts.replaceState(juce::ValueTree::fromXml(*xml));
}
}
juce::AudioProcessorValueTreeState::ParameterLayout MDADetuneAudioProcessor::createParameterLayout()
{
juce::AudioProcessorValueTreeState::ParameterLayout layout;
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Detune", 1),
"Detune",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.2f,
juce::AudioParameterFloatAttributes()
.withLabel("cents")
.withStringFromValueFunction([](float value, int)
{
float semi = 3.0f * value * value * value;
return juce::String(100.0f * semi, 1);
})));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Mix", 1),
"Mix",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.9f,
juce::AudioParameterFloatAttributes()
.withLabel("%")
.withStringFromValueFunction([](float value, int)
{
return juce::String(int(100.0f * value));
})));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Output", 1),
"Output",
juce::NormalisableRange<float>(-20.0f, 20.0f, 0.1f),
0.0f,
juce::AudioParameterFloatAttributes().withLabel("dB")));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Latency", 1),
"Latency",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.5f,
juce::AudioParameterFloatAttributes()
.withLabel("ms")
.withStringFromValueFunction([this](float value, int)
{
// Calculate the length of the delay in samples, then convert
// this to a time in milliseconds for displaying to the user.
int buflen = 1 << (8 + int(4.9f * value));
if (buflen > BUFMAX) { buflen = BUFMAX; }
float bufres = 1000.0f * float(buflen) / sampleRate;
return juce::String(bufres, 1);
})));
return layout;
}
juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter()
{
return new MDADetuneAudioProcessor();
}
================================================
FILE: Detune/Source/PluginProcessor.h
================================================
#pragma once
#include <JuceHeader.h>
class MDADetuneAudioProcessor : public juce::AudioProcessor
{
public:
MDADetuneAudioProcessor();
~MDADetuneAudioProcessor() override;
void prepareToPlay(double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
void reset() override;
bool isBusesLayoutSupported(const BusesLayout &layouts) const override;
void processBlock(juce::AudioBuffer<float> &, juce::MidiBuffer &) override;
juce::AudioProcessorEditor *createEditor() override;
bool hasEditor() const override { return true; }
const juce::String getName() const override;
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
bool isMidiEffect() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
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;
juce::AudioProcessorValueTreeState apvts { *this, nullptr, "Parameters", createParameterLayout() };
private:
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
void update();
void resetState();
static constexpr int BUFMAX = 4096;
float buf[BUFMAX]; // circular buffer for delay line (mono)
float win[BUFMAX]; // crossfade window
float sampleRate;
int buflen; // delay length
int pos0; // write pointer in the circular buffer
float pos1, dpos1; // read pointer and step size for left channel
float pos2, dpos2; // and for right channel
float wet, dry; // output levels
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MDADetuneAudioProcessor)
};
================================================
FILE: Dither/MDADither.jucer
================================================
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT id="ngajfW" name="MDADither" projectType="audioplug" useAppConfig="0"
addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1">
<MAINGROUP id="EccBWd" name="MDADither">
<GROUP id="{ABE95447-F384-E79B-7924-6EF3C4D2A813}" name="Source">
<FILE id="S1zHI2" name="PluginProcessor.cpp" compile="1" resource="0"
file="Source/PluginProcessor.cpp"/>
<FILE id="cGSkrO" name="PluginProcessor.h" compile="0" resource="0"
file="Source/PluginProcessor.h"/>
</GROUP>
</MAINGROUP>
<MODULES>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_plugin_client" showAllCode="1" useLocalCopy="0"
useGlobalPath="1"/>
<MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_processors_headless" showAllCode="1" useLocalCopy="0"
useGlobalPath="1"/>
<MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
</MODULES>
<JUCEOPTIONS JUCE_STRICT_REFCOUNTEDPOINTER="1" JUCE_VST3_CAN_REPLACE_VST2="0"/>
<EXPORTFORMATS>
<XCODE_MAC targetFolder="Builds/MacOSX">
<CONFIGURATIONS>
<CONFIGURATION isDebug="1" name="Debug" targetName="MDADither"/>
<CONFIGURATION isDebug="0" name="Release" targetName="MDADither"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_devices" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_formats" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_plugin_client" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_processors" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_utils" path="../../JUCE/modules"/>
<MODULEPATH id="juce_core" path="../../JUCE/modules"/>
<MODULEPATH id="juce_data_structures" path="../../JUCE/modules"/>
<MODULEPATH id="juce_events" path="../../JUCE/modules"/>
<MODULEPATH id="juce_graphics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_gui_basics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_gui_extra" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_processors_headless" path="../../../../../../../JUCE/modules"/>
</MODULEPATHS>
</XCODE_MAC>
</EXPORTFORMATS>
</JUCERPROJECT>
================================================
FILE: Dither/README.markdown
================================================
# Dither
Range of dither types for word-length reduction.
| Parameter | Description |
| --------- | ----------- |
| Word Length | Output word length 8 - 24 bits |
| Dither | see below for the possible modes |
| Dither Amp | Dither amplitude - is at optimum level but can be turned down to reduce background hiss at the expense of dither level "pumping" caused by the input signal type and level |
| DC Trim | Fine tune DC offset - can help get best dither sound for silent or very quiet inputs |
| Zoom | Allows the signal to be faded into the noise floor at a clearly audible level so dither settings can be "auditioned". Note that some (perceptual) properties of dither will change when listened to in this way |
Dither Modes:
- OFF: Truncation
- TRI: Triangular PDF dither
- HP-TRI: High-pass Triangular PDF dither (a good general purpose dither)
- N-SHAPE: Second-order noise-shaped dither (for final mastering to 8 or 16 bits)
## Technical notes
When a waveform is rounded to the nearest 16 (or whatever)-bit value this causes distortion. Dither allows you to exchange this rough sounding signal-dependant distortion for a smooth background hiss.
Some sort of dither should always be used when reducing the word length of digital audio, such as from 24-bit to 16-bit. In many cases the background noise in a recording will act as dither, but dither will still be required on fades and on very clean recordings such as purely synthetic sources.
Noise shaping makes the background hiss of dither sound quieter, but adds more high-frequency noise than 'ordinary' dither. This high frequency noise can be a problem if a recording is later processed in any way (including gain changes) especially if noise shaping is applied a second time.
If you are producing an *absolutely final* master at 16 bits or less, use noise shaped dither. In all other situations use a non-noise-shaped dither such as high-pass-triangular. When mastering for MP3 or other compressed formats be aware that noise shaping may take some of the encoder's 'attention' away from the real signal at high frequencies.
No gain changes should be applied after this plug-in. Make sure any master output fader is set to 0.0 dB in the host application.
**Very technical note** This plug-in follows Steinberg's convention that a signal level of 1.0 corresponds to a 16-bit output of 32768. If your host application does not allow this exact gain setting the effectiveness of dither may be reduced (check for harmonic distortion of a 1 kHz sine wave using a spectrum analyser).
================================================
FILE: Dither/Source/PluginProcessor.cpp
================================================
#include "PluginProcessor.h"
MDADitherAudioProcessor::MDADitherAudioProcessor()
: AudioProcessor(BusesProperties()
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
.withOutput("Output", juce::AudioChannelSet::stereo(), true))
{
}
MDADitherAudioProcessor::~MDADitherAudioProcessor()
{
}
const juce::String MDADitherAudioProcessor::getName() const
{
return JucePlugin_Name;
}
int MDADitherAudioProcessor::getNumPrograms()
{
return 1;
}
int MDADitherAudioProcessor::getCurrentProgram()
{
return 0;
}
void MDADitherAudioProcessor::setCurrentProgram(int index)
{
}
const juce::String MDADitherAudioProcessor::getProgramName(int index)
{
return {};
}
void MDADitherAudioProcessor::changeProgramName(int index, const juce::String &newName)
{
}
void MDADitherAudioProcessor::prepareToPlay(double newSampleRate, int samplesPerBlock)
{
resetState();
}
void MDADitherAudioProcessor::releaseResources()
{
}
void MDADitherAudioProcessor::reset()
{
resetState();
}
bool MDADitherAudioProcessor::isBusesLayoutSupported(const BusesLayout &layouts) const
{
return layouts.getMainOutputChannelSet() == juce::AudioChannelSet::stereo();
}
void MDADitherAudioProcessor::resetState()
{
sh1 = sh2 = sh3 = sh4 = 0.0f;
rnd1 = rnd3 = 0;
}
void MDADitherAudioProcessor::update()
{
// This is a value between 8 and 24, rounded to a whole number.
float param0 = apvts.getRawParameterValue("Word Len")->load();
bits = 8.0f + 2.0f * std::floor(8.9f * param0);
// If zoom mode is enabled, the number of bits is set to 6 and the input
// audio signal is gained down so that the dither noise is clearly audible
// (around -36 dB).
gain = 1.0f;
float param4 = apvts.getRawParameterValue("Zoom...")->load();
if (param4 > 0.1f) {
wlen = 32.0f;
gain = 1.0f - param4;
gain *= gain;
} else {
wlen = std::pow(2.0f, bits - 1.0f); // word length in quanta
}
/*
If the signal has N bits, the noise floor is at -6N dB. For example,
if bits = 8, the noise floor is at 10^(-6.02 * 8/20) = 0.00391, which
is the same as 1/256.
However, `wlen` is a factor 2 smaller, so if bits = 8, wlen is not
256 but 128. Presumably this is done because TPDF dither is supposed
to add 6 dB to the noise floor.
At 8 bits, with a dither amplitude of 2 LSB or less, the dither noise
level is at -42 dB. This is correct: the noise floor is at -48 dB, plus
6 dB for the added bit.
However, a dither amplitude of 1 LSB or less does not generate noise
but silence. I think this is due to the truncation it performs, but I
don't really feel like figuring out why.
*/
// Using WaveLab 2.01 (unity gain) as a reference:
// 16-bit output is (int)floor(floating_point_value*32768.0f)
// The dither amplitude is a value between 0 and 4 LSB. Exactly how large
// the LSB is depends on the "bits" parameter: more bits means the smallest
// bit represents a smaller amplitude.
// The reason for dividing by an additional factor 32767 is that the random
// numbers used to create the dither noise will be between 0 and 32767.
float param2 = apvts.getRawParameterValue("Dith Amp")->load();
dith = 2.0f * param2 / (wlen * 32767.0f);
// DC offset is a value between -2 and +2 LSB. This formula adds 0.5 to
// round the dither instead of truncating.
float param3 = apvts.getRawParameterValue("DC Trim")->load();
offs = (4.0f * param3 - 1.5f) / wlen;
// Dither mode
mode = 1;
shap = 0.0f;
float param1 = apvts.getRawParameterValue("Dither")->load();
switch (int(param1)) {
case 0: dith = 0.0f; break; // off
case 1: mode = 0; break; // tri
case 3: shap = 0.5f; break; // noise shaping
default: break; // tri, hp-tri
}
iwlen = 1.0f / wlen;
}
void MDADitherAudioProcessor::processBlock(juce::AudioBuffer<float> &buffer, juce::MidiBuffer &midiMessages)
{
juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
// Clear any output channels that don't contain input data.
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) {
buffer.clear(i, 0, buffer.getNumSamples());
}
update();
const float *in1 = buffer.getReadPointer(0);
const float *in2 = buffer.getReadPointer(1);
float *out1 = buffer.getWritePointer(0);
float *out2 = buffer.getWritePointer(1);
float s1 = sh1, s2 = sh2, s3 = sh3, s4 = sh4; // shaping level, buffers
int r1 = rnd1, r3 = rnd3; // random numbers for dither
float aa, bb;
for (int i = 0; i < buffer.getNumSamples(); ++i) {
float a = in1[i];
float b = in2[i];
// Calculate the first random number. In HP-TRI and noise shaping
// modes, this is the previous random number. In TRI mode, this is
// a new random number.
// HP-TRI dither (also used when noise shaping)
int r2 = r1;
int r4 = r3;
// TRI dither
if (mode == 0) {
r4 = rand() & 0x7FFF;
r2 = (r4 & 0x7F) << 8;
}
// Calculate the second random number.
r1 = rand() & 0x7FFF;
r3 = (r1 & 0x7F) << 8;
/*
Processing goes like this:
1. Apply input gain. Normally this is 1.0, except in zoom mode where
we turn down the input signal.
2. Add a noise shaping filter.
3. Add the DC-offset. This is a tiny value.
4. Subtract the two random numbers (this is what creates the high
pass in HP-TRI mode) and multiply by the dither level. The random
values are 0 - 32767 but `dith` compensates for this, so that the
added noise is also a tiny value.
5. Apply the bitcrushing formula. This is done so that the signal
won't have any amplitudes smaller than the "bits" parameter.
6. Update the noise shaping filter state using the error between
the original signal and the dithered signal.
*/
a = gain * a + shap * (s1 + s1 - s2); // target level + error feedback
aa = a + offs + dith * float(r1 - r2); // + offset + dither
if (aa < 0.0f) { aa -= iwlen; } // because we truncate towards zero!
aa = iwlen * float(int(wlen * aa)); // truncate
s2 = s1;
s1 = a - aa; // error feedback: 2nd order noise shaping
// Do the same for the right channel...
b = gain * b + shap * (s3 + s3 - s4);
bb = b + offs + dith * float(r3 - r4);
if (bb < 0.0f) { bb -= iwlen; }
bb = iwlen * float(int(wlen * bb));
s4 = s3;
s3 = b - bb;
out1[i] = aa;
out2[i] = bb;
}
sh1 = s1; sh2 = s2; sh3 = s3; sh4 = s4; // doesn't actually matter if these are
rnd1 = r1; rnd3 = r3; // saved or not as effect is so small!
}
juce::AudioProcessorEditor *MDADitherAudioProcessor::createEditor()
{
return new juce::GenericAudioProcessorEditor(*this);
}
void MDADitherAudioProcessor::getStateInformation(juce::MemoryBlock &destData)
{
copyXmlToBinary(*apvts.copyState().createXml(), destData);
}
void MDADitherAudioProcessor::setStateInformation(const void *data, int sizeInBytes)
{
std::unique_ptr<juce::XmlElement> xml(getXmlFromBinary(data, sizeInBytes));
if (xml.get() != nullptr && xml->hasTagName(apvts.state.getType())) {
apvts.replaceState(juce::ValueTree::fromXml(*xml));
}
}
juce::AudioProcessorValueTreeState::ParameterLayout MDADitherAudioProcessor::createParameterLayout()
{
juce::AudioProcessorValueTreeState::ParameterLayout layout;
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Word Len", 1),
"Word Len",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.5f,
juce::AudioParameterFloatAttributes()
.withLabel("bits")
.withStringFromValueFunction([this](float value, int)
{
float bits = 8.0f + 2.0f * (float)floor(8.9f * value);
return juce::String(int(bits));
})));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID("Dither", 1),
"Dither",
juce::StringArray { "OFF", "TRI", "HP-TRI", "N-SHAPE" },
2));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Dith Amp", 1),
"Dith Amp",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.5f,
juce::AudioParameterFloatAttributes()
.withLabel("lsb")
.withStringFromValueFunction([this](float value, int)
{
return juce::String(4.0f * value, 2);
})));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("DC Trim", 1),
"DC Trim",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.5f,
juce::AudioParameterFloatAttributes()
.withLabel("lsb")
.withStringFromValueFunction([this](float value, int)
{
return juce::String(4.0f * value - 2.0f, 2);
})));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID("Zoom...", 1),
"Zoom...",
juce::NormalisableRange<float>(0.0f, 1.0f),
0.0f,
juce::AudioParameterFloatAttributes()
.withLabel("dB")
.withStringFromValueFunction([this](float value, int)
{
if (value > 0.1f) {
float gain = (1.0f - value)*(1.0f - value);
if (gain < 0.0001f) {
return juce::String("-80");
} else {
return juce::String(20.0f * std::log10(gain));
}
} else {
return juce::String("OFF");
}
})));
return layout;
}
juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter()
{
return new MDADitherAudioProcessor();
}
================================================
FILE: Dither/Source/PluginProcessor.h
================================================
#pragma once
#include <JuceHeader.h>
class MDADitherAudioProcessor : public juce::AudioProcessor
{
public:
MDADitherAudioProcessor();
~MDADitherAudioProcessor() override;
void prepareToPlay(double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
void reset() override;
bool isBusesLayoutSupported(const BusesLayout &layouts) const override;
void processBlock(juce::AudioBuffer<float> &, juce::MidiBuffer &) override;
juce::AudioProcessorEditor *createEditor() override;
bool hasEditor() const override { return true; }
const juce::String getName() const override;
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
bool isMidiEffect() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
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;
juce::AudioProcessorValueTreeState apvts { *this, nullptr, "Parameters", createParameterLayout() };
private:
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
void update();
void resetState();
// Dither level.
float dith;
// Previous random value (used in HP-TRI and N-SHAPE modes).
int rnd1, rnd3;
// Noise shaping buffers.
float shap, sh1, sh2, sh3, sh4;
// DC offset for fine-tuning.
float offs;
// Output word length in bits (8 - 24).
float bits;
// Word length. This is 2^(bits - 1) so 16 bits has wlen = 32768.
float wlen;
// Inverse word length, 1/wlen.
float iwlen;
// Input signal gain level for zoom mode.
float gain;
// Dither mode. 0 = TRI, 1 = HP-TRI or N-SHAPE.
int mode = 0;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MDADitherAudioProcessor)
};
================================================
FILE: Dynamics/MDADynamics.jucer
================================================
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT id="kyPSKJ" name="MDADynamics" projectType="audioplug" useAppConfig="0"
addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1">
<MAINGROUP id="GJ3smZ" name="MDADynamics">
<GROUP id="{C671E307-BE12-9AED-D331-B3D47456D9BB}" name="Source">
<FILE id="XJWOGb" name="PluginProcessor.cpp" compile="1" resource="0"
file="Source/PluginProcessor.cpp"/>
<FILE id="pRlD2b" name="PluginProcessor.h" compile="0" resource="0"
file="Source/PluginProcessor.h"/>
</GROUP>
</MAINGROUP>
<MODULES>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_plugin_client" showAllCode="1" useLocalCopy="0"
useGlobalPath="1"/>
<MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
</MODULES>
<JUCEOPTIONS JUCE_STRICT_REFCOUNTEDPOINTER="1" JUCE_VST3_CAN_REPLACE_VST2="0"/>
<EXPORTFORMATS>
<XCODE_MAC targetFolder="Builds/MacOSX">
<CONFIGURATIONS>
<CONFIGURATION isDebug="1" name="Debug" targetName="MDADynamics"/>
<CONFIGURATION isDebug="0" name="Release" targetName="MDADynamics"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_audio_basics" path="../../JUCE/modules"/>
<MODULEPATH id="juce_audio_devices" path="../../JUCE/modules"/>
<MODULEPATH id="
gitextract_e_m88lqn/
├── .gitignore
├── Ambience/
│ ├── MDAAmbience.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Bandisto/
│ ├── MDABandisto.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── BeatBox/
│ ├── MDABeatBox.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── DX10/
│ ├── DX10.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Degrade/
│ ├── MDADegrade.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Delay/
│ ├── MDADelay.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Detune/
│ ├── MDADetune.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Dither/
│ ├── MDADither.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Dynamics/
│ ├── MDADynamics.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── EPiano/
│ ├── MDAEPiano.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ ├── PluginProcessor.h
│ └── mdaEPianoData.h
├── Envelope/
│ ├── MDAEnvelope.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Image/
│ ├── MDAImage.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── JX10/
│ ├── JX10.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── LICENSE.txt
├── Limiter/
│ ├── MDALimiter.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Loudness/
│ ├── MDALoudness.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Overdrive/
│ ├── MDAOverdrive.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Piano/
│ ├── MDAPiano.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ ├── PluginProcessor.h
│ └── mdaPianoData.h
├── README.markdown
├── RezFilter/
│ ├── MDARezFilter.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── RingMod/
│ ├── MDARingMod.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Shepard/
│ ├── MDAShepard.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Splitter/
│ ├── MDASplitter.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── Stereo/
│ ├── MDAStereo.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
├── SubSynth/
│ ├── MDASubSynth.jucer
│ ├── README.markdown
│ └── Source/
│ ├── PluginProcessor.cpp
│ └── PluginProcessor.h
└── TestTone/
├── MDATestTone.jucer
├── README.markdown
└── Source/
├── PluginProcessor.cpp
└── PluginProcessor.h
SYMBOL INDEX (60 symbols across 24 files) FILE: Ambience/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: Bandisto/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: BeatBox/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: DX10/Source/PluginProcessor.h type DX10Program (line 11) | struct DX10Program type Voice (line 23) | struct Voice function releaseResources (line 57) | void releaseResources() override; function juce (line 67) | const juce::String getName() const override; FILE: Degrade/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: Delay/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: Detune/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: Dither/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: Dynamics/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: EPiano/Source/PluginProcessor.h type MDAEPianoProgram (line 12) | struct MDAEPianoProgram type Keygroup (line 27) | struct Keygroup type Voice (line 37) | struct Voice function releaseResources (line 78) | void releaseResources() override; function juce (line 88) | const juce::String getName() const override; FILE: Envelope/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: Image/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: JX10/Source/PluginProcessor.h type JX10Program (line 15) | struct JX10Program type Voice (line 30) | struct Voice function releaseResources (line 95) | void releaseResources() override; function juce (line 105) | const juce::String getName() const override; FILE: Limiter/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: Loudness/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: Overdrive/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: Piano/Source/PluginProcessor.h type MDAPianoProgram (line 12) | struct MDAPianoProgram type Keygroup (line 28) | struct Keygroup type Voice (line 38) | struct Voice function releaseResources (line 84) | void releaseResources() override; function juce (line 94) | const juce::String getName() const override; FILE: RezFilter/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: RingMod/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: Shepard/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: Splitter/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: Stereo/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: SubSynth/Source/PluginProcessor.h function releaseResources (line 12) | void releaseResources() override; function juce (line 22) | const juce::String getName() const override; FILE: TestTone/Source/PluginProcessor.h function releaseResources (line 14) | void releaseResources() override; function juce (line 24) | const juce::String getName() const override; function valueTreePropertyChanged (line 45) | void valueTreePropertyChanged(juce::ValueTree &, const juce::Identifier ... function parameterChanged (line 52) | void parameterChanged(const juce::String &identifier, float value) override
Condensed preview — 101 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (6,467K chars).
[
{
"path": ".gitignore",
"chars": 60,
"preview": "**/.DS_Store\n**/Builds\n**/JuceLibraryCode\n**/*.filtergraph\n\n"
},
{
"path": "Ambience/MDAAmbience.jucer",
"chars": 3026,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"MKdKFI\" name=\"MDAAmbience\" projectType=\"audioplug\" useAppCon"
},
{
"path": "Ambience/README.markdown",
"chars": 558,
"preview": "# Ambience\n\nSmall space reverberator. Designed to simulate a distant mic in small rooms, without the processor overhead "
},
{
"path": "Ambience/Source/PluginProcessor.cpp",
"chars": 10418,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDAAmbienceAudioProcessor::MDAAmbienceAudioProcessor()\r\n: AudioProcessor(BusesProperties"
},
{
"path": "Ambience/Source/PluginProcessor.h",
"chars": 2180,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDAAmbienceAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "Bandisto/MDABandisto.jucer",
"chars": 3002,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"VNGyhW\" name=\"MDABandisto\" projectType=\"audioplug\" useAppCon"
},
{
"path": "Bandisto/README.markdown",
"chars": 661,
"preview": "# Bandisto\n\nThe original Multi-band distortion VST plug-in\n\nThis plug is like MultiBand but uses 3 bands of clipping ins"
},
{
"path": "Bandisto/Source/PluginProcessor.cpp",
"chars": 13305,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDABandistoAudioProcessor::MDABandistoAudioProcessor()\r\n: AudioProcessor(BusesProperties"
},
{
"path": "Bandisto/Source/PluginProcessor.h",
"chars": 2059,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDABandistoAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "BeatBox/MDABeatBox.jucer",
"chars": 2998,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"bSDXpm\" name=\"MDABeatBox\" projectType=\"audioplug\" useAppConf"
},
{
"path": "BeatBox/README.markdown",
"chars": 969,
"preview": "# BeatBox\n\nDrum replacer / enhancer\n\nContains three samples (kick, snare and hat) designed to be triggered by incoming a"
},
{
"path": "BeatBox/Source/PluginProcessor.cpp",
"chars": 18881,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDABeatBoxAudioProcessor::MDABeatBoxAudioProcessor()\r\n : AudioProcessor(BusesProperti"
},
{
"path": "BeatBox/Source/PluginProcessor.h",
"chars": 3454,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDABeatBoxAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "DX10/DX10.jucer",
"chars": 3146,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"lUYjpz\" name=\"DX10\" projectType=\"audioplug\" useAppConfig=\"0\""
},
{
"path": "DX10/README.markdown",
"chars": 1037,
"preview": "# DX10\n\nSimple FM synthesizer. Sounds similar to the later Yamaha DX synths including the heavy bass but with a warmer, "
},
{
"path": "DX10/Source/PluginProcessor.cpp",
"chars": 42061,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nDX10Program::DX10Program(const char *name,\r\n float p0, float p1"
},
{
"path": "DX10/Source/PluginProcessor.h",
"chars": 5838,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nconst int NPARAMS = 16; // number of parameters\r\nconst int NVOICES = 8;"
},
{
"path": "Degrade/MDADegrade.jucer",
"chars": 3022,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"kTPmKy\" name=\"MDADegrade\" projectType=\"audioplug\" useAppConf"
},
{
"path": "Degrade/README.markdown",
"chars": 944,
"preview": "# Degrade\n\nThis plug-in reduces the bit-depth and sample rate of the input audio (using the Quantize and Sample Rate par"
},
{
"path": "Degrade/Source/PluginProcessor.cpp",
"chars": 14375,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDADegradeAudioProcessor::MDADegradeAudioProcessor()\r\n: AudioProcessor(BusesProperties()"
},
{
"path": "Degrade/Source/PluginProcessor.h",
"chars": 2510,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDADegradeAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "Delay/MDADelay.jucer",
"chars": 3122,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"DRUbMS\" name=\"MDADelay\" projectType=\"audioplug\" useAppConfig"
},
{
"path": "Delay/README.markdown",
"chars": 452,
"preview": "# Delay\n\nSimple stereo delay with feedback tone control.\n\n| Parameter | Description |\n| --------- | ----------- |\n| Left"
},
{
"path": "Delay/Source/PluginProcessor.cpp",
"chars": 15219,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDADelayAudioProcessor::MDADelayAudioProcessor()\r\n: AudioProcessor(BusesProperties()\r\n "
},
{
"path": "Delay/Source/PluginProcessor.h",
"chars": 3071,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDADelayAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n M"
},
{
"path": "Detune/MDADetune.jucer",
"chars": 2994,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"Ndjzja\" name=\"MDADetune\" projectType=\"audioplug\" useAppConfi"
},
{
"path": "Detune/README.markdown",
"chars": 845,
"preview": "# Detune\n\nA low-quality stereo pitch shifter for the sort of chorus and detune effects found on multi-effects hardware.\n"
},
{
"path": "Detune/Source/PluginProcessor.cpp",
"chars": 12219,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDADetuneAudioProcessor::MDADetuneAudioProcessor()\r\n: AudioProcessor(BusesProperties()\r\n"
},
{
"path": "Detune/Source/PluginProcessor.h",
"chars": 2095,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDADetuneAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "Dither/MDADither.jucer",
"chars": 3209,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"ngajfW\" name=\"MDADither\" projectType=\"audioplug\" useAppConfi"
},
{
"path": "Dither/README.markdown",
"chars": 2552,
"preview": "# Dither\n\nRange of dither types for word-length reduction.\n\n| Parameter | Description |\n| --------- | ----------- |\n| Wo"
},
{
"path": "Dither/Source/PluginProcessor.cpp",
"chars": 10687,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDADitherAudioProcessor::MDADitherAudioProcessor()\r\n: AudioProcessor(BusesProperties()\r\n"
},
{
"path": "Dither/Source/PluginProcessor.h",
"chars": 2245,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDADitherAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "Dynamics/MDADynamics.jucer",
"chars": 3002,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"kyPSKJ\" name=\"MDADynamics\" projectType=\"audioplug\" useAppCon"
},
{
"path": "Dynamics/README.markdown",
"chars": 741,
"preview": "# Dynamics\n\nModern-sounding Compressor / Limiter / Gate with low processor usage\n\nThis plug-in is a very ordinary analog"
},
{
"path": "Dynamics/Source/PluginProcessor.cpp",
"chars": 14050,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDADynamicsAudioProcessor::MDADynamicsAudioProcessor()\r\n: AudioProcessor(BusesProperties"
},
{
"path": "Dynamics/Source/PluginProcessor.h",
"chars": 2444,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDADynamicsAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "EPiano/MDAEPiano.jucer",
"chars": 3621,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"hJLT3v\" name=\"mdaEPiano\" projectType=\"audioplug\" useAppConfi"
},
{
"path": "EPiano/README.markdown",
"chars": 139,
"preview": "# EPiano\n\nRhodes piano. This plug-in is extremely similar to MDA Piano, but uses different samples. It also has some bas"
},
{
"path": "EPiano/Source/PluginProcessor.cpp",
"chars": 40049,
"preview": "#include \"PluginProcessor.h\"\r\n#include \"mdaEPianoData.h\"\r\n\r\nMDAEPianoProgram::MDAEPianoProgram(const char *name,\r\n "
},
{
"path": "EPiano/Source/PluginProcessor.h",
"chars": 7070,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nconst int NPARAMS = 12; // number of parameters\r\nconst int NPROGS = 8; "
},
{
"path": "EPiano/Source/mdaEPianoData.h",
"chars": 2313497,
"preview": "short epianoData[] = {\n-7,-23,-28,-16,-30,-17,-28,-16,-31,-15,-34,-12,-35,-6,-42,4,-58,44,-227,-1690,\n-1412,-1295,-1059,"
},
{
"path": "Envelope/MDAEnvelope.jucer",
"chars": 3002,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"GfjITR\" name=\"MDAEnvelope\" projectType=\"audioplug\" useAppCon"
},
{
"path": "Envelope/README.markdown",
"chars": 1209,
"preview": "# Envelope\n\nEnvelope follower and VCA\n\n| Parameter | Description |\n| --------- | ----------- |\n| Output | see below |\n| "
},
{
"path": "Envelope/Source/PluginProcessor.cpp",
"chars": 7624,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDAEnvelopeAudioProcessor::MDAEnvelopeAudioProcessor()\r\n: AudioProcessor(BusesProperties"
},
{
"path": "Envelope/Source/PluginProcessor.h",
"chars": 1916,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDAEnvelopeAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "Image/MDAImage.jucer",
"chars": 2990,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"dtvRNy\" name=\"MDAImage\" projectType=\"audioplug\" useAppConfig"
},
{
"path": "Image/README.markdown",
"chars": 706,
"preview": "# Image\n\nStereo image adjuster and MS matrix\n\nAllows the level and pan of mono and stereo components to be adjusted sepa"
},
{
"path": "Image/Source/PluginProcessor.cpp",
"chars": 7354,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDAImageAudioProcessor::MDAImageAudioProcessor()\r\n: AudioProcessor(BusesProperties()\r\n "
},
{
"path": "Image/Source/PluginProcessor.h",
"chars": 1669,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDAImageAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n M"
},
{
"path": "JX10/JX10.jucer",
"chars": 3146,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"lUYjpz\" name=\"JX10\" projectType=\"audioplug\" useAppConfig=\"0\""
},
{
"path": "JX10/README.markdown",
"chars": 2665,
"preview": "# JX10\n\nSimple 2-oscillator analog synthesizer. 8 voice polyphonic.\n\n> **TIP!** I cleaned up the code for this synth and"
},
{
"path": "JX10/Source/PluginProcessor.cpp",
"chars": 78177,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nJX10Program::JX10Program()\r\n{\r\n param[0] = 0.00f; // OSC Mix\r\n param[1] = 0.25f"
},
{
"path": "JX10/Source/PluginProcessor.h",
"chars": 8960,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nconst int NPARAMS = 24; // number of parameters\r\nconst int NVOICES = 8;"
},
{
"path": "LICENSE.txt",
"chars": 1138,
"preview": "mda VST plug-ins\r\n\r\nCopyright (c) 2008 Paul Kellett\r\nCopyright (c) 2021-2025 M.I. Hollemans (JUCE version)\r\n\r\nPermission"
},
{
"path": "Limiter/MDALimiter.jucer",
"chars": 3130,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"irCFgj\" name=\"MDALimiter\" projectType=\"audioplug\" useAppConf"
},
{
"path": "Limiter/README.markdown",
"chars": 355,
"preview": "# Limiter\n\nOpto-electronic style limiter.\n\n| Parameter | Description |\n| --------- | ----------- |\n| Thresh | Threshold "
},
{
"path": "Limiter/Source/PluginProcessor.cpp",
"chars": 13458,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDALimiterAudioProcessor::MDALimiterAudioProcessor()\r\n: AudioProcessor(BusesProperties()"
},
{
"path": "Limiter/Source/PluginProcessor.h",
"chars": 2912,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDALimiterAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "Loudness/MDALoudness.jucer",
"chars": 3110,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"GRvOF4\" name=\"MDALoudness\" projectType=\"audioplug\" useAppCon"
},
{
"path": "Loudness/README.markdown",
"chars": 1068,
"preview": "# Loudness\n\nEqual loudness contours for bass EQ and mix correction.\n\n| Parameter | Description |\n| --------- | ---------"
},
{
"path": "Loudness/Source/PluginProcessor.cpp",
"chars": 6740,
"preview": "#include \"PluginProcessor.h\"\r\n\r\n// Lookup table of filter coefficients.\r\nstatic float loudness[14][3] =\r\n{\r\n {402.f, "
},
{
"path": "Loudness/Source/PluginProcessor.h",
"chars": 1873,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDALoudnessAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "Overdrive/MDAOverdrive.jucer",
"chars": 3138,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"H4dYwD\" name=\"MDAOverdrive\" projectType=\"audioplug\" useAppCo"
},
{
"path": "Overdrive/README.markdown",
"chars": 426,
"preview": "# Overdrive\n\nSoft distortion plug-in.\n\nPossible uses include adding body to drum loops, fuzz guitar, and that 'standing "
},
{
"path": "Overdrive/Source/PluginProcessor.cpp",
"chars": 6552,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDAOverdriveAudioProcessor::MDAOverdriveAudioProcessor()\r\n: AudioProcessor(BusesProperti"
},
{
"path": "Overdrive/Source/PluginProcessor.h",
"chars": 2070,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDAOverdriveAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "Piano/MDAPiano.jucer",
"chars": 3614,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"lUYjpz\" name=\"mdaPiano\" projectType=\"audioplug\" useAppConfig"
},
{
"path": "Piano/README.markdown",
"chars": 980,
"preview": "# Piano\n\nAcoustic piano instrument — this was quite a [popular free piano synth](https://www.kvraudio.com/product/piano-"
},
{
"path": "Piano/Source/PluginProcessor.cpp",
"chars": 39316,
"preview": "#include \"PluginProcessor.h\"\r\n#include \"mdaPianoData.h\"\r\n\r\nMDAPianoProgram::MDAPianoProgram()\r\n{\r\n param[0] = 0.50f;"
},
{
"path": "Piano/Source/PluginProcessor.h",
"chars": 7277,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nconst int NPARAMS = 12; // number of parameters\r\nconst int NPROGS = 8; "
},
{
"path": "Piano/Source/mdaPianoData.h",
"chars": 3333663,
"preview": "short pianoData[] = {\r\n5,-7,5,-6,159,163,146,133,130,152,\r\n220,299,641,1124,1413,673,-1277,-3818,-4550,-3574,\r\n-4184,-46"
},
{
"path": "README.markdown",
"chars": 11914,
"preview": "# MDA plug-ins in JUCE\n\nThis repo contains the [MDA freeware plug-ins](http://mda.smartelectronix.com) implemented in [J"
},
{
"path": "RezFilter/MDARezFilter.jucer",
"chars": 3006,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"BPEKke\" name=\"MDARezFilter\" projectType=\"audioplug\" useAppCo"
},
{
"path": "RezFilter/README.markdown",
"chars": 730,
"preview": "# RezFilter\n\nResonant filter with LFO and envelope follower\n\n| Parameter | Description |\n| --------- | ----------- |\n| F"
},
{
"path": "RezFilter/Source/PluginProcessor.cpp",
"chars": 13758,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDARezFilterAudioProcessor::MDARezFilterAudioProcessor()\r\n: AudioProcessor(BusesProperti"
},
{
"path": "RezFilter/Source/PluginProcessor.h",
"chars": 2569,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDARezFilterAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "RingMod/MDARingMod.jucer",
"chars": 3130,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"uWU8TR\" name=\"MDARingMod\" projectType=\"audioplug\" useAppConf"
},
{
"path": "RingMod/README.markdown",
"chars": 709,
"preview": "# RingMod\n\nThis was the first \"mda\" effect, made way back in 1998. It is a simple ring modulator, multiplying the input"
},
{
"path": "RingMod/Source/PluginProcessor.cpp",
"chars": 5955,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDARingModAudioProcessor::MDARingModAudioProcessor()\r\n: AudioProcessor(BusesProperties()"
},
{
"path": "RingMod/Source/PluginProcessor.h",
"chars": 2153,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDARingModAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "Shepard/MDAShepard.jucer",
"chars": 3130,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"L6SQod\" name=\"MDAShepard\" projectType=\"audioplug\" useAppConf"
},
{
"path": "Shepard/README.markdown",
"chars": 870,
"preview": "# Shepard\n\nShepard tone generator\n\nThis plug-in generates a continuously rising or falling tone. Or rather, that's what"
},
{
"path": "Shepard/Source/PluginProcessor.cpp",
"chars": 8119,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDAShepardAudioProcessor::MDAShepardAudioProcessor()\r\n: AudioProcessor(BusesProperties()"
},
{
"path": "Shepard/Source/PluginProcessor.h",
"chars": 2115,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDAShepardAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "Splitter/MDASplitter.jucer",
"chars": 3002,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"ngajfW\" name=\"MDASplitter\" projectType=\"audioplug\" useAppCon"
},
{
"path": "Splitter/README.markdown",
"chars": 1048,
"preview": "# Splitter\n\n2-way signal splitter. Frequency / level crossover for setting up dynamic processing.\n\n| Parameter | Descrip"
},
{
"path": "Splitter/Source/PluginProcessor.cpp",
"chars": 12458,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDASplitterAudioProcessor::MDASplitterAudioProcessor()\r\n: AudioProcessor(BusesProperties"
},
{
"path": "Splitter/Source/PluginProcessor.h",
"chars": 2097,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDASplitterAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "Stereo/MDAStereo.jucer",
"chars": 3126,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"MW2QLG\" name=\"MDAStereo\" projectType=\"audioplug\" useAppConfi"
},
{
"path": "Stereo/README.markdown",
"chars": 998,
"preview": "# Stereo\n\nStereo Simulator: Add artificial width to a mono signal. Haas delay and comb filtering.\n\nThis plug converts a "
},
{
"path": "Stereo/Source/PluginProcessor.cpp",
"chars": 15995,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDAStereoAudioProcessor::MDAStereoAudioProcessor()\r\n: AudioProcessor(BusesProperties()\r\n"
},
{
"path": "Stereo/Source/PluginProcessor.h",
"chars": 2447,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDAStereoAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "SubSynth/MDASubSynth.jucer",
"chars": 3134,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"MQYslu\" name=\"MDASubSynth\" projectType=\"audioplug\" useAppCon"
},
{
"path": "SubSynth/README.markdown",
"chars": 1392,
"preview": "# SubSynth\n\nSub-Bass Synthesizer: Several low frequency enhancement methods. More bass than you could ever need!\n\nBe awa"
},
{
"path": "SubSynth/Source/PluginProcessor.cpp",
"chars": 14920,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nMDASubSynthAudioProcessor::MDASubSynthAudioProcessor()\r\n: AudioProcessor(BusesProperties"
},
{
"path": "SubSynth/Source/PluginProcessor.h",
"chars": 2545,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDASubSynthAudioProcessor : public juce::AudioProcessor\r\n{\r\npublic:\r\n "
},
{
"path": "TestTone/MDATestTone.jucer",
"chars": 3134,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<JUCERPROJECT id=\"c3a5Bn\" name=\"MDATestTone\" projectType=\"audioplug\" useAppCon"
},
{
"path": "TestTone/README.markdown",
"chars": 1082,
"preview": "# TestTone\n\nSignal generator with pink and white noise, impulses and sweeps.\n\nNote: the AU version of this plug-in has a"
},
{
"path": "TestTone/Source/PluginProcessor.cpp",
"chars": 26550,
"preview": "#include \"PluginProcessor.h\"\r\n\r\nstatic constexpr float twopi = 6.2831853f;\r\n\r\nMDATestToneAudioProcessor::MDATestToneAudi"
},
{
"path": "TestTone/Source/PluginProcessor.h",
"chars": 4715,
"preview": "#pragma once\r\n\r\n#include <JuceHeader.h>\r\n\r\nclass MDATestToneAudioProcessor : public juce::AudioProcessor,\r\n "
}
]
About this extraction
This page contains the full source code of the hollance/mda-plugins-juce GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 101 files (6.0 MB), approximately 1.6M tokens, and a symbol index with 60 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.