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.

183 lines
6.1KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #pragma once
  18. class MPEDemoSynthVoice : public MPESynthesiserVoice
  19. {
  20. public:
  21. //==============================================================================
  22. MPEDemoSynthVoice()
  23. : phase (0.0), phaseDelta (0.0), tailOff (0.0)
  24. {
  25. }
  26. //==============================================================================
  27. void noteStarted() override
  28. {
  29. jassert (currentlyPlayingNote.isValid());
  30. jassert (currentlyPlayingNote.keyState == MPENote::keyDown
  31. || currentlyPlayingNote.keyState == MPENote::keyDownAndSustained);
  32. level.setValue (currentlyPlayingNote.pressure.asUnsignedFloat());
  33. frequency.setValue (currentlyPlayingNote.getFrequencyInHertz());
  34. timbre.setValue (currentlyPlayingNote.timbre.asUnsignedFloat());
  35. phase = 0.0;
  36. const double cyclesPerSample = frequency.getNextValue() / currentSampleRate;
  37. phaseDelta = 2.0 * double_Pi * cyclesPerSample;
  38. tailOff = 0.0;
  39. }
  40. void noteStopped (bool allowTailOff) override
  41. {
  42. jassert (currentlyPlayingNote.keyState == MPENote::off);
  43. if (allowTailOff)
  44. {
  45. // start a tail-off by setting this flag. The render callback will pick up on
  46. // this and do a fade out, calling clearCurrentNote() when it's finished.
  47. if (tailOff == 0.0) // we only need to begin a tail-off if it's not already doing so - the
  48. // stopNote method could be called more than once.
  49. tailOff = 1.0;
  50. }
  51. else
  52. {
  53. // we're being told to stop playing immediately, so reset everything..
  54. clearCurrentNote();
  55. phaseDelta = 0.0;
  56. }
  57. }
  58. void notePressureChanged() override
  59. {
  60. level.setValue (currentlyPlayingNote.pressure.asUnsignedFloat());
  61. }
  62. void notePitchbendChanged() override
  63. {
  64. frequency.setValue (currentlyPlayingNote.getFrequencyInHertz());
  65. }
  66. void noteTimbreChanged() override
  67. {
  68. timbre.setValue (currentlyPlayingNote.timbre.asUnsignedFloat());
  69. }
  70. void noteKeyStateChanged() override
  71. {
  72. }
  73. void setCurrentSampleRate (double newRate) override
  74. {
  75. if (currentSampleRate != newRate)
  76. {
  77. noteStopped (false);
  78. currentSampleRate = newRate;
  79. level.reset (currentSampleRate, smoothingLengthInSeconds);
  80. timbre.reset (currentSampleRate, smoothingLengthInSeconds);
  81. frequency.reset (currentSampleRate, smoothingLengthInSeconds);
  82. }
  83. }
  84. //==============================================================================
  85. virtual void renderNextBlock (AudioBuffer<float>& outputBuffer,
  86. int startSample,
  87. int numSamples) override
  88. {
  89. if (phaseDelta != 0.0)
  90. {
  91. if (tailOff > 0)
  92. {
  93. while (--numSamples >= 0)
  94. {
  95. const float currentSample = getNextSample() * (float) tailOff;
  96. for (int i = outputBuffer.getNumChannels(); --i >= 0;)
  97. outputBuffer.addSample (i, startSample, currentSample);
  98. ++startSample;
  99. tailOff *= 0.99;
  100. if (tailOff <= 0.005)
  101. {
  102. clearCurrentNote();
  103. phaseDelta = 0.0;
  104. break;
  105. }
  106. }
  107. }
  108. else
  109. {
  110. while (--numSamples >= 0)
  111. {
  112. const float currentSample = getNextSample();
  113. for (int i = outputBuffer.getNumChannels(); --i >= 0;)
  114. outputBuffer.addSample (i, startSample, currentSample);
  115. ++startSample;
  116. }
  117. }
  118. }
  119. }
  120. private:
  121. //==============================================================================
  122. float getNextSample() noexcept
  123. {
  124. const double levelDb = (level.getNextValue() - 1.0) * maxLevelDb;
  125. const double amplitude = std::pow (10.0f, 0.05f * levelDb) * maxLevel;
  126. // timbre is used to blend between a sine and a square.
  127. const double f1 = std::sin (phase);
  128. const double f2 = std::copysign (1.0, f1);
  129. const double a2 = timbre.getNextValue();
  130. const double a1 = 1.0 - a2;
  131. const float nextSample = float (amplitude * ((a1 * f1) + (a2 * f2)));
  132. const double cyclesPerSample = frequency.getNextValue() / currentSampleRate;
  133. phaseDelta = 2.0 * double_Pi * cyclesPerSample;
  134. phase = std::fmod (phase + phaseDelta, 2.0 * double_Pi);
  135. return nextSample;
  136. }
  137. //==============================================================================
  138. LinearSmoothedValue<double> level, timbre, frequency;
  139. double phase, phaseDelta, tailOff;
  140. const double maxLevel = 0.05f;
  141. const double maxLevelDb = 31.0f;
  142. const double smoothingLengthInSeconds = 0.01;
  143. };