The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
6.0KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #pragma once
  20. class MPEDemoSynthVoice : public MPESynthesiserVoice
  21. {
  22. public:
  23. //==============================================================================
  24. MPEDemoSynthVoice() {}
  25. //==============================================================================
  26. void noteStarted() override
  27. {
  28. jassert (currentlyPlayingNote.isValid());
  29. jassert (currentlyPlayingNote.keyState == MPENote::keyDown
  30. || currentlyPlayingNote.keyState == MPENote::keyDownAndSustained);
  31. level.setValue (currentlyPlayingNote.pressure.asUnsignedFloat());
  32. frequency.setValue (currentlyPlayingNote.getFrequencyInHertz());
  33. timbre.setValue (currentlyPlayingNote.timbre.asUnsignedFloat());
  34. phase = 0.0;
  35. auto cyclesPerSample = frequency.getNextValue() / currentSampleRate;
  36. phaseDelta = MathConstants<double>::twoPi * cyclesPerSample;
  37. tailOff = 0.0;
  38. }
  39. void noteStopped (bool allowTailOff) override
  40. {
  41. jassert (currentlyPlayingNote.keyState == MPENote::off);
  42. if (allowTailOff)
  43. {
  44. // start a tail-off by setting this flag. The render callback will pick up on
  45. // this and do a fade out, calling clearCurrentNote() when it's finished.
  46. if (tailOff == 0.0) // we only need to begin a tail-off if it's not already doing so - the
  47. tailOff = 1.0; // stopNote method could be called more than once.
  48. }
  49. else
  50. {
  51. // we're being told to stop playing immediately, so reset everything..
  52. clearCurrentNote();
  53. phaseDelta = 0.0;
  54. }
  55. }
  56. void notePressureChanged() override
  57. {
  58. level.setValue (currentlyPlayingNote.pressure.asUnsignedFloat());
  59. }
  60. void notePitchbendChanged() override
  61. {
  62. frequency.setValue (currentlyPlayingNote.getFrequencyInHertz());
  63. }
  64. void noteTimbreChanged() override
  65. {
  66. timbre.setValue (currentlyPlayingNote.timbre.asUnsignedFloat());
  67. }
  68. void noteKeyStateChanged() override
  69. {
  70. }
  71. void setCurrentSampleRate (double newRate) override
  72. {
  73. if (currentSampleRate != newRate)
  74. {
  75. noteStopped (false);
  76. currentSampleRate = newRate;
  77. level.reset (currentSampleRate, smoothingLengthInSeconds);
  78. timbre.reset (currentSampleRate, smoothingLengthInSeconds);
  79. frequency.reset (currentSampleRate, smoothingLengthInSeconds);
  80. }
  81. }
  82. //==============================================================================
  83. virtual void renderNextBlock (AudioBuffer<float>& outputBuffer,
  84. int startSample,
  85. int numSamples) override
  86. {
  87. if (phaseDelta != 0.0)
  88. {
  89. if (tailOff > 0)
  90. {
  91. while (--numSamples >= 0)
  92. {
  93. auto currentSample = getNextSample() * (float) tailOff;
  94. for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
  95. outputBuffer.addSample (i, startSample, currentSample);
  96. ++startSample;
  97. tailOff *= 0.99;
  98. if (tailOff <= 0.005)
  99. {
  100. clearCurrentNote();
  101. phaseDelta = 0.0;
  102. break;
  103. }
  104. }
  105. }
  106. else
  107. {
  108. while (--numSamples >= 0)
  109. {
  110. auto currentSample = getNextSample();
  111. for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
  112. outputBuffer.addSample (i, startSample, currentSample);
  113. ++startSample;
  114. }
  115. }
  116. }
  117. }
  118. private:
  119. //==============================================================================
  120. float getNextSample() noexcept
  121. {
  122. auto levelDb = (level.getNextValue() - 1.0) * maxLevelDb;
  123. auto amplitude = std::pow (10.0f, 0.05f * levelDb) * maxLevel;
  124. // timbre is used to blend between a sine and a square.
  125. auto f1 = std::sin (phase);
  126. auto f2 = std::copysign (1.0, f1);
  127. auto a2 = timbre.getNextValue();
  128. auto a1 = 1.0 - a2;
  129. auto nextSample = float (amplitude * ((a1 * f1) + (a2 * f2)));
  130. auto cyclesPerSample = frequency.getNextValue() / currentSampleRate;
  131. phaseDelta = MathConstants<double>::twoPi * cyclesPerSample;
  132. phase = std::fmod (phase + phaseDelta, MathConstants<double>::twoPi);
  133. return nextSample;
  134. }
  135. //==============================================================================
  136. LinearSmoothedValue<double> level, timbre, frequency;
  137. double phase = 0.0;
  138. double phaseDelta = 0.0;
  139. double tailOff = 0.0;
  140. const double maxLevel = 0.05f;
  141. const double maxLevelDb = 31.0f;
  142. const double smoothingLengthInSeconds = 0.01;
  143. };