DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

commit 30710967d15f43778b0391a9151e2cb67f7edc31
parent 0d962da768dd40485b5a24c322e469ae32fc0c79
Author: falkTX <falktx@falktx.com>
Date:   Thu, 27 May 2021 11:39:32 +0100

Add Metronome example

Signed-off-by: falkTX <falktx@falktx.com>

Diffstat:
MMakefile | 2++
Mexamples/CVPort/ExamplePluginCVPort.cpp | 1+
Aexamples/Metronome/DistrhoPluginInfo.h | 29+++++++++++++++++++++++++++++
Aexamples/Metronome/ExamplePluginMetronome.cpp | 364+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aexamples/Metronome/Makefile | 42++++++++++++++++++++++++++++++++++++++++++
Aexamples/Metronome/README.md | 34++++++++++++++++++++++++++++++++++
6 files changed, 472 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -24,6 +24,7 @@ examples: dgl $(MAKE) all -C examples/Info $(MAKE) all -C examples/Latency $(MAKE) all -C examples/Meters + $(MAKE) all -C examples/Metronome $(MAKE) all -C examples/MidiThrough $(MAKE) all -C examples/Parameters $(MAKE) all -C examples/SendNote @@ -66,6 +67,7 @@ clean: $(MAKE) clean -C examples/Info $(MAKE) clean -C examples/Latency $(MAKE) clean -C examples/Meters + $(MAKE) clean -C examples/Metronome $(MAKE) clean -C examples/MidiThrough $(MAKE) clean -C examples/Parameters $(MAKE) clean -C examples/SendNote diff --git a/examples/CVPort/ExamplePluginCVPort.cpp b/examples/CVPort/ExamplePluginCVPort.cpp @@ -1,6 +1,7 @@ /* * 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 diff --git a/examples/Metronome/DistrhoPluginInfo.h b/examples/Metronome/DistrhoPluginInfo.h @@ -0,0 +1,29 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * + * 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. + */ + +#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED +#define DISTRHO_PLUGIN_INFO_H_INCLUDED + +#define DISTRHO_PLUGIN_BRAND "DISTRHO" +#define DISTRHO_PLUGIN_NAME "Metronome" +#define DISTRHO_PLUGIN_URI "http://distrho.sf.net/examples/Metronome" + +#define DISTRHO_PLUGIN_IS_RT_SAFE 1 +#define DISTRHO_PLUGIN_NUM_INPUTS 0 +#define DISTRHO_PLUGIN_NUM_OUTPUTS 1 +#define DISTRHO_PLUGIN_WANT_TIMEPOS 1 + +#endif // DISTRHO_PLUGIN_INFO_H_INCLUDED diff --git a/examples/Metronome/ExamplePluginMetronome.cpp b/examples/Metronome/ExamplePluginMetronome.cpp @@ -0,0 +1,364 @@ +/* + * 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 diff --git a/examples/Metronome/Makefile b/examples/Metronome/Makefile @@ -0,0 +1,42 @@ +#!/usr/bin/make -f +# Makefile for DISTRHO Plugins # +# ---------------------------- # +# Created by falkTX +# + +# -------------------------------------------------------------- +# Project name, used for binaries + +NAME = d_metronome + +# -------------------------------------------------------------- +# Files to build + +FILES_DSP = \ + ExamplePluginMetronome.cpp + +# -------------------------------------------------------------- +# Do some magic + +include ../../Makefile.plugins.mk + +# -------------------------------------------------------------- +# Enable all possible plugin types + +ifeq ($(HAVE_JACK),true) +ifeq ($(HAVE_OPENGL),true) +TARGETS += jack +endif +endif + +ifeq ($(HAVE_OPENGL),true) +TARGETS += lv2_sep +else +TARGETS += lv2_dsp +endif + +TARGETS += vst + +all: $(TARGETS) + +# -------------------------------------------------------------- diff --git a/examples/Metronome/README.md b/examples/Metronome/README.md @@ -0,0 +1,34 @@ +# Metronome example + +This example will show tempo sync in DPF.<br/> + +This plugin will output sine wave at the start of every beat.<br/> +The pitch of sine wave is 1 octave higher at the start of every bar.<br/> + +4 parameters are avaialble: + +- Gain +- Decay time +- Semitone +- Cent + +To calculate frames to the next beat from the start of current audio buffer, following implementation is used.<br/> + +```c++ +const TimePosition& timePos(getTimePosition()); + +if (timePos.bbt.valid) { + double secondsPerBeat = 60.0 / timePos.bbt.beatsPerMinute; + double framesPerBeat = sampleRate * secondsPerBeat; + double beatFraction = timePos.bbt.tick / timePos.bbt.ticksPerBeat; + + uint32_t framesToNextBeat = beatFraction == 0.0 + ? 0 + : static_cast<uint32_t>(framesPerBeat * (1.0 - beatFraction)); + + // ... +} +``` + +Reference: +- [DISTRHO Plugin Framework: TimePosition::BarBeatTick Struct Reference](https://distrho.github.io/DPF/structTimePosition_1_1BarBeatTick.html)