ExamplePluginMetronome.cpp (12710B)
1 /* 2 * DISTRHO Plugin Framework (DPF) 3 * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> 4 * Copyright (C) 2020 Takamitsu Endo 5 * 6 * Permission to use, copy, modify, and/or distribute this software for any purpose with 7 * or without fee is hereby granted, provided that the above copyright notice and this 8 * permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 11 * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN 12 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 13 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 14 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 15 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include "DistrhoPlugin.hpp" 19 20 START_NAMESPACE_DISTRHO 21 22 // ----------------------------------------------------------------------------------------------------------- 23 24 /** 25 1-pole lowpass filter to smooth out parameters and envelopes. 26 This filter is guaranteed not to overshoot. 27 */ 28 class Smoother { 29 float kp; 30 31 public: 32 float value; 33 34 Smoother() 35 : kp(0.0f), 36 value(0.0f) {} 37 38 /** 39 Set kp from cutoff frequency in Hz. 40 For derivation, see the answer of Matt L. on the url below. Equation 3 is used. 41 42 Computation is done on double for accuracy. When using float, kp will be inaccurate 43 if the cutoffHz is below around 3.0 to 4.0 Hz. 44 45 Reference: 46 - Single-pole IIR low-pass filter - which is the correct formula for the decay coefficient? 47 https://dsp.stackexchange.com/questions/54086/single-pole-iir-low-pass-filter-which-is-the-correct-formula-for-the-decay-coe 48 */ 49 void setCutoff(const float sampleRate, const float cutoffHz) 50 { 51 double omega_c = 2.0 * M_PI * cutoffHz / sampleRate; 52 double y = 1.0 - std::cos(omega_c); 53 kp = float(-y + std::sqrt((y + 2.0) * y)); 54 } 55 56 inline float process(const float input) 57 { 58 return value += kp * (input - value); 59 } 60 }; 61 62 // ----------------------------------------------------------------------------------------------------------- 63 64 /** 65 Plugin that demonstrates tempo sync in DPF. 66 The tempo sync implementation is on the first if branch in run() method. 67 */ 68 class ExamplePluginMetronome : public Plugin 69 { 70 public: 71 ExamplePluginMetronome() 72 : Plugin(4, 0, 0), // 4 parameters, 0 programs, 0 states 73 sampleRate(getSampleRate()), 74 counter(0), 75 wasPlaying(false), 76 phase(0.0f), 77 envelope(1.0f), 78 decay(0.0f), 79 gain(0.5f), 80 semitone(72), 81 cent(0), 82 decayTime(0.2f) 83 { 84 sampleRateChanged(sampleRate); 85 } 86 87 protected: 88 /* -------------------------------------------------------------------------------------------------------- 89 * Information */ 90 91 /** 92 Get the plugin label. 93 A plugin label follows the same rules as Parameter::symbol, with the exception that it can start with numbers. 94 */ 95 const char* getLabel() const override 96 { 97 return "Metronome"; 98 } 99 100 /** 101 Get an extensive comment/description about the plugin. 102 */ 103 const char* getDescription() const override 104 { 105 return "Simple metronome plugin which outputs impulse at the start of every beat."; 106 } 107 108 /** 109 Get the plugin author/maker. 110 */ 111 const char* getMaker() const override 112 { 113 return "DISTRHO"; 114 } 115 116 /** 117 Get the plugin homepage. 118 */ 119 const char* getHomePage() const override 120 { 121 return "https://github.com/DISTRHO/DPF"; 122 } 123 124 /** 125 Get the plugin license name (a single line of text). 126 For commercial plugins this should return some short copyright information. 127 */ 128 const char* getLicense() const override 129 { 130 return "ISC"; 131 } 132 133 /** 134 Get the plugin version, in hexadecimal. 135 */ 136 uint32_t getVersion() const override 137 { 138 return d_version(1, 0, 0); 139 } 140 141 /** 142 Get the plugin unique Id. 143 This value is used by LADSPA, DSSI and VST plugin formats. 144 */ 145 int64_t getUniqueId() const override 146 { 147 return d_cconst('d', 'M', 'e', 't'); 148 } 149 150 /* -------------------------------------------------------------------------------------------------------- 151 * Init */ 152 153 /** 154 Initialize the audio port @a index.@n 155 This function will be called once, shortly after the plugin is created. 156 */ 157 void initAudioPort(bool input, uint32_t index, AudioPort& port) override 158 { 159 // treat meter audio ports as stereo 160 port.groupId = kPortGroupMono; 161 162 // everything else is as default 163 Plugin::initAudioPort(input, index, port); 164 } 165 166 /** 167 Initialize the parameter @a index. 168 This function will be called once, shortly after the plugin is created. 169 */ 170 void initParameter(uint32_t index, Parameter& parameter) override 171 { 172 parameter.hints = kParameterIsAutomatable; 173 174 switch (index) 175 { 176 case 0: 177 parameter.name = "Gain"; 178 parameter.hints |= kParameterIsLogarithmic; 179 parameter.ranges.min = 0.001f; 180 parameter.ranges.max = 1.0f; 181 parameter.ranges.def = 0.5f; 182 break; 183 case 1: 184 parameter.name = "DecayTime"; 185 parameter.hints |= kParameterIsLogarithmic; 186 parameter.ranges.min = 0.001f; 187 parameter.ranges.max = 1.0f; 188 parameter.ranges.def = 0.2f; 189 break; 190 case 2: 191 parameter.name = "Semitone"; 192 parameter.hints |= kParameterIsInteger; 193 parameter.ranges.min = 0; 194 parameter.ranges.max = 127; 195 parameter.ranges.def = 72; 196 break; 197 case 3: 198 parameter.name = "Cent"; 199 parameter.hints |= kParameterIsInteger; 200 parameter.ranges.min = -100; 201 parameter.ranges.max = 100; 202 parameter.ranges.def = 0; 203 break; 204 } 205 206 parameter.symbol = parameter.name; 207 } 208 209 /* -------------------------------------------------------------------------------------------------------- 210 * Internal data */ 211 212 /** 213 Get the current value of a parameter. 214 */ 215 float getParameterValue(uint32_t index) const override 216 { 217 switch (index) 218 { 219 case 0: 220 return gain; 221 case 1: 222 return decayTime; 223 case 2: 224 return semitone; 225 case 3: 226 return cent; 227 } 228 229 return 0.0f; 230 } 231 232 /** 233 Change a parameter value. 234 */ 235 void setParameterValue(uint32_t index, float value) override 236 { 237 switch (index) 238 { 239 case 0: 240 gain = value; 241 break; 242 case 1: 243 decayTime = value; 244 break; 245 case 2: 246 semitone = value; 247 break; 248 case 3: 249 cent = value; 250 break; 251 } 252 } 253 254 /* -------------------------------------------------------------------------------------------------------- 255 * Process */ 256 257 /** 258 Activate this plugin. 259 We use this to reset our filter states. 260 */ 261 void activate() override 262 { 263 deltaPhaseSmoother.value = 0.0f; 264 envelopeSmoother.value = 0.0f; 265 gainSmoother.value = gain; 266 } 267 268 /** 269 Run/process function for plugins without MIDI input. 270 `inputs` is commented out because this plugin has no inputs. 271 */ 272 void run(const float** /* inputs */, float** outputs, uint32_t frames) override 273 { 274 const TimePosition& timePos(getTimePosition()); 275 float* const output = outputs[0]; 276 277 if (timePos.playing && timePos.bbt.valid) 278 { 279 // Better to use double when manipulating time. 280 double secondsPerBeat = 60.0 / timePos.bbt.beatsPerMinute; 281 double framesPerBeat = sampleRate * secondsPerBeat; 282 double beatFraction = timePos.bbt.tick / timePos.bbt.ticksPerBeat; 283 284 // If beatFraction is zero, next beat is exactly at the start of currenct cycle. 285 // Otherwise, reset counter to the frames to the next beat. 286 counter = d_isZero(beatFraction) 287 ? 0 288 : static_cast<uint32_t>(framesPerBeat * (1.0 - beatFraction)); 289 290 // Compute deltaPhase in normalized frequency. 291 // semitone is midi note number, which is A4 (440Hz at standard tuning) at 69. 292 // Frequency goes up to 1 octave higher at the start of bar. 293 float frequency = 440.0f * std::pow(2.0f, (100.0f * (semitone - 69.0f) + cent) / 1200.0f); 294 float deltaPhase = frequency / sampleRate; 295 float octave = timePos.bbt.beat == 1 ? 2.0f : 1.0f; 296 297 // Envelope reaches 1e-5 at decayTime after triggering. 298 decay = std::pow(1e-5, 1.0 / (decayTime * sampleRate)); 299 300 // Reset phase and frequency at the start of transpose. 301 if (!wasPlaying) 302 { 303 phase = 0.0f; 304 305 deltaPhaseSmoother.value = deltaPhase; 306 envelopeSmoother.value = 0.0f; 307 gainSmoother.value = 0.0f; 308 } 309 310 for (uint32_t i = 0; i < frames; ++i) 311 { 312 if (counter <= 0) 313 { 314 envelope = 1.0f; 315 counter = static_cast<uint32_t>(framesPerBeat + 0.5); 316 octave = (!wasPlaying || timePos.bbt.beat == static_cast<int32_t>(timePos.bbt.beatsPerBar)) ? 2.0f 317 : 1.0f; 318 } 319 --counter; 320 321 envelope *= decay; 322 323 phase += octave * deltaPhaseSmoother.process(deltaPhase); 324 phase -= std::floor(phase); 325 326 output[i] = gainSmoother.process(gain) 327 * envelopeSmoother.process(envelope) 328 * std::sin(float(2.0 * M_PI) * phase); 329 } 330 } 331 else 332 { 333 // Stop metronome if not playing or timePos.bbt is invalid. 334 std::memset(output, 0, sizeof(float)*frames); 335 } 336 337 wasPlaying = timePos.playing; 338 } 339 340 /* -------------------------------------------------------------------------------------------------------- 341 * Callbacks (optional) */ 342 343 /** 344 Optional callback to inform the plugin about a sample rate change. 345 This function will only be called when the plugin is deactivated. 346 */ 347 void sampleRateChanged(double newSampleRate) override 348 { 349 sampleRate = newSampleRate; 350 351 // Cutoff value was tuned manually. 352 deltaPhaseSmoother.setCutoff(sampleRate, 100.0f); 353 gainSmoother.setCutoff(sampleRate, 500.0f); 354 envelopeSmoother.setCutoff(sampleRate, 250.0f); 355 } 356 357 // ------------------------------------------------------------------------------------------------------- 358 359 private: 360 float sampleRate; 361 uint32_t counter; // Stores number of frames to the next beat. 362 bool wasPlaying; // Used to reset phase and frequency at the start of transpose. 363 float phase; // Sine wave phase. Normalized in [0, 1). 364 float envelope; // Current value of gain envelope. 365 float decay; // Coefficient to decay envelope in a frame. 366 367 Smoother deltaPhaseSmoother; 368 Smoother envelopeSmoother; 369 Smoother gainSmoother; 370 371 // Parameters. 372 float gain; 373 float semitone; 374 float cent; 375 float decayTime; 376 377 /** 378 Set our plugin class as non-copyable and add a leak detector just in case. 379 */ 380 DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExamplePluginMetronome) 381 }; 382 383 /* ------------------------------------------------------------------------------------------------------------ 384 * Plugin entry point, called by DPF to create a new plugin instance. */ 385 386 Plugin* createPlugin() 387 { 388 return new ExamplePluginMetronome(); 389 } 390 391 // ----------------------------------------------------------------------------------------------------------- 392 393 END_NAMESPACE_DISTRHO