|
- /*
- * DISTRHO Plugin Framework (DPF)
- * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
- * Copyright (C) 2020 Takamitsu Endo
- *
- * Permission to use, copy, modify, and/or distribute this software for any purpose with
- * or without fee is hereby granted, provided that the above copyright notice and this
- * permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
- * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
- * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
- * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
- * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
- #include "DistrhoPlugin.hpp"
-
- START_NAMESPACE_DISTRHO
-
- // -----------------------------------------------------------------------------------------------------------
-
- /**
- 1-pole lowpass filter to smooth out parameters and envelopes.
- This filter is guaranteed not to overshoot.
- */
- class Smoother {
- float kp;
-
- public:
- float value;
-
- Smoother()
- : kp(0.0f),
- value(0.0f) {}
-
- /**
- Set kp from cutoff frequency in Hz.
- For derivation, see the answer of Matt L. on the url below. Equation 3 is used.
-
- Computation is done on double for accuracy. When using float, kp will be inaccurate
- if the cutoffHz is below around 3.0 to 4.0 Hz.
-
- Reference:
- - Single-pole IIR low-pass filter - which is the correct formula for the decay coefficient?
- https://dsp.stackexchange.com/questions/54086/single-pole-iir-low-pass-filter-which-is-the-correct-formula-for-the-decay-coe
- */
- void setCutoff(float sampleRate, float cutoffHz) {
- double omega_c = 2.0 * M_PI * cutoffHz / sampleRate;
- double y = 1.0 - std::cos(omega_c);
- kp = float(-y + std::sqrt((y + 2.0) * y));
- }
-
- float process(float input) {
- return value += kp * (input - value);
- }
- };
-
- // -----------------------------------------------------------------------------------------------------------
-
- /**
- Plugin that demonstrates tempo sync in DPF.
- The tempo sync implementation is on the first if branch in run() method.
- */
- class ExamplePluginMetronome : public Plugin
- {
- public:
- ExamplePluginMetronome()
- : Plugin(4, 0, 0), // 4 parameters, 0 programs, 0 states
- sampleRate(getSampleRate()),
- counter(0),
- phase(0.0f),
- decay(0.0f),
- gain(0.5f),
- semitone(72),
- cent(0),
- decayTime(0.2f)
- {
- }
-
- protected:
- /* --------------------------------------------------------------------------------------------------------
- * Information */
-
- /**
- Get the plugin label.
- A plugin label follows the same rules as Parameter::symbol, with the exception that it can start with numbers.
- */
- const char* getLabel() const override
- {
- return "Metronome";
- }
-
- /**
- Get an extensive comment/description about the plugin.
- */
- const char* getDescription() const override
- {
- return "Simple metronome plugin which outputs impulse at the start of every beat.";
- }
-
- /**
- Get the plugin author/maker.
- */
- const char* getMaker() const override
- {
- return "DISTRHO";
- }
-
- /**
- Get the plugin homepage.
- */
- const char* getHomePage() const override
- {
- return "https://github.com/DISTRHO/DPF";
- }
-
- /**
- Get the plugin license name (a single line of text).
- For commercial plugins this should return some short copyright information.
- */
- const char* getLicense() const override
- {
- return "ISC";
- }
-
- /**
- Get the plugin version, in hexadecimal.
- */
- uint32_t getVersion() const override
- {
- return d_version(1, 0, 0);
- }
-
- /**
- Get the plugin unique Id.
- This value is used by LADSPA, DSSI and VST plugin formats.
- */
- int64_t getUniqueId() const override
- {
- return d_cconst('d', 'M', 'e', 't');
- }
-
- /* --------------------------------------------------------------------------------------------------------
- * Init */
-
- /**
- Initialize the parameter @a index.
- This function will be called once, shortly after the plugin is created.
- */
- void initParameter(uint32_t index, Parameter& parameter) override
- {
- parameter.hints = kParameterIsAutomable;
-
- switch (index)
- {
- case 0:
- parameter.name = "Gain";
- parameter.hints |= kParameterIsLogarithmic;
- parameter.ranges.min = 0.0f;
- parameter.ranges.max = 1.0f;
- parameter.ranges.def = 0.5f;
- break;
- case 1:
- parameter.name = "DecayTime";
- parameter.hints |= kParameterIsLogarithmic;
- parameter.ranges.min = 0.001f;
- parameter.ranges.max = 1.0f;
- parameter.ranges.def = 0.2f;
- break;
- case 2:
- parameter.name = "Semitone";
- parameter.hints |= kParameterIsInteger;
- parameter.ranges.min = 0;
- parameter.ranges.max = 127;
- parameter.ranges.def = 72;
- break;
- case 3:
- parameter.name = "Cent";
- parameter.hints |= kParameterIsInteger;
- parameter.ranges.min = -100;
- parameter.ranges.max = 100;
- parameter.ranges.def = 0;
- break;
- }
-
- parameter.symbol = parameter.name;
- }
-
- /* --------------------------------------------------------------------------------------------------------
- * Internal data */
-
- /**
- Get the current value of a parameter.
- */
- float getParameterValue(uint32_t index) const override
- {
- switch (index)
- {
- case 0:
- return gain;
- case 1:
- return decayTime;
- case 2:
- return semitone;
- case 3:
- return cent;
- }
-
- return 0.0f;
- }
-
- /**
- Change a parameter value.
- */
- void setParameterValue(uint32_t index, float value) override
- {
- switch (index)
- {
- case 0:
- gain = value;
- break;
- case 1:
- decayTime = value;
- break;
- case 2:
- semitone = value;
- break;
- case 3:
- cent = value;
- break;
- }
- }
-
- /* --------------------------------------------------------------------------------------------------------
- * Process */
-
- /**
- Run/process function for plugins without MIDI input.
- `inputs` is commented out because this plugin has no inputs.
- */
- void run(const float** /* inputs */, float** outputs, uint32_t frames) override
- {
- const TimePosition& timePos(getTimePosition());
- float* const output = outputs[0];
-
- if (timePos.playing && timePos.bbt.valid)
- {
- // Better to use double when manipulating time.
- double secondsPerBeat = 60.0 / timePos.bbt.beatsPerMinute;
- double framesPerBeat = sampleRate * secondsPerBeat;
- double beatFraction = timePos.bbt.tick / timePos.bbt.ticksPerBeat;
-
- // If beatFraction is zero, next beat is exactly at the start of currenct cycle.
- // Otherwise, reset counter to the frames to the next beat.
- counter = d_isZero(beatFraction)
- ? 0
- : static_cast<uint32_t>(framesPerBeat * (1.0 - beatFraction));
-
- // Compute deltaPhase in normalized frequency.
- // semitone is midi note number, which is A4 (440Hz at standard tuning) at 69.
- // Frequency goes up to 1 octave higher at the start of bar.
- float frequency = 440.0f * std::pow(2.0f, (100.0f * (semitone - 69.0f) + cent) / 1200.0f);
- float deltaPhase = frequency / sampleRate;
- float octave = timePos.bbt.beat == 1 ? 2.0f : 1.0f;
-
- // Envelope reaches 1e-5 at decayTime after triggering.
- decay = std::pow(1e-5, 1.0 / (decayTime * sampleRate));
-
- // Reset phase and frequency at the start of transpose.
- if (!wasPlaying)
- {
- phase = 0.0f;
-
- deltaPhaseSmoother.value = deltaPhase;
- gainSmoother.value = 1.0f;
- envelopeSmoother.value = 0.0f;
- }
-
- for (uint32_t i = 0; i < frames; ++i)
- {
- if (counter <= 0)
- {
- envelope = 1.0f;
- counter = static_cast<uint32_t>(framesPerBeat + 0.5);
- octave = (!wasPlaying || timePos.bbt.beat == static_cast<int32_t>(timePos.bbt.beatsPerBar)) ? 2.0f
- : 1.0f;
- }
- --counter;
-
- envelope *= decay;
-
- phase += octave * deltaPhaseSmoother.process(deltaPhase);
- phase -= std::floor(phase);
-
- output[i] = gainSmoother.process(gain)
- * envelopeSmoother.process(envelope)
- * std::sin(float(2.0 * M_PI) * phase);
- }
- }
- else
- {
- // Stop metronome if not playing or timePos.bbt is invalid.
- std::memset(output, 0, sizeof(float)*frames);
- }
-
- wasPlaying = timePos.playing;
- }
-
- /* --------------------------------------------------------------------------------------------------------
- * Callbacks (optional) */
-
- /**
- Optional callback to inform the plugin about a sample rate change.
- This function will only be called when the plugin is deactivated.
- */
- void sampleRateChanged(double newSampleRate) override
- {
- sampleRate = newSampleRate;
-
- // Cutoff value was tuned manually.
- deltaPhaseSmoother.setCutoff(sampleRate, 100.0f);
- gainSmoother.setCutoff(sampleRate, 500.0f);
- envelopeSmoother.setCutoff(sampleRate, 250.0f);
- }
-
- // -------------------------------------------------------------------------------------------------------
-
- private:
- float sampleRate;
- uint32_t counter; // Stores number of frames to the next beat.
- bool wasPlaying; // Used to reset phase and frequency at the start of transpose.
- float phase; // Sine wave phase. Normalized in [0, 1).
- float envelope; // Current value of gain envelope.
- float decay; // Coefficient to decay envelope in a frame.
-
- Smoother deltaPhaseSmoother;
- Smoother gainSmoother;
- Smoother envelopeSmoother;
-
- // Parameters.
- float gain;
- float semitone;
- float cent;
- float decayTime;
-
- /**
- Set our plugin class as non-copyable and add a leak detector just in case.
- */
- DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExamplePluginMetronome)
- };
-
- /* ------------------------------------------------------------------------------------------------------------
- * Plugin entry point, called by DPF to create a new plugin instance. */
-
- Plugin* createPlugin()
- {
- return new ExamplePluginMetronome();
- }
-
- // -----------------------------------------------------------------------------------------------------------
-
- END_NAMESPACE_DISTRHO
|