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.

187 lines
6.2KB

  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. #ifndef MPEDEMOSYNTHVOICE_H_INCLUDED
  18. #define MPEDEMOSYNTHVOICE_H_INCLUDED
  19. class MPEDemoSynthVoice : public MPESynthesiserVoice
  20. {
  21. public:
  22. //==============================================================================
  23. MPEDemoSynthVoice()
  24. : phase (0.0), phaseDelta (0.0), tailOff (0.0)
  25. {
  26. }
  27. //==============================================================================
  28. void noteStarted() override
  29. {
  30. jassert (currentlyPlayingNote.isValid());
  31. jassert (currentlyPlayingNote.keyState == MPENote::keyDown
  32. || currentlyPlayingNote.keyState == MPENote::keyDownAndSustained);
  33. level.setValue (currentlyPlayingNote.pressure.asUnsignedFloat());
  34. frequency.setValue (currentlyPlayingNote.getFrequencyInHertz());
  35. timbre.setValue (currentlyPlayingNote.timbre.asUnsignedFloat());
  36. phase = 0.0;
  37. const double cyclesPerSample = frequency.getNextValue() / currentSampleRate;
  38. phaseDelta = 2.0 * double_Pi * cyclesPerSample;
  39. tailOff = 0.0;
  40. }
  41. void noteStopped (bool allowTailOff) override
  42. {
  43. jassert (currentlyPlayingNote.keyState == MPENote::off);
  44. if (allowTailOff)
  45. {
  46. // start a tail-off by setting this flag. The render callback will pick up on
  47. // this and do a fade out, calling clearCurrentNote() when it's finished.
  48. if (tailOff == 0.0) // we only need to begin a tail-off if it's not already doing so - the
  49. // stopNote method could be called more than once.
  50. tailOff = 1.0;
  51. }
  52. else
  53. {
  54. // we're being told to stop playing immediately, so reset everything..
  55. clearCurrentNote();
  56. phaseDelta = 0.0;
  57. }
  58. }
  59. void notePressureChanged() override
  60. {
  61. level.setValue (currentlyPlayingNote.pressure.asUnsignedFloat());
  62. }
  63. void notePitchbendChanged() override
  64. {
  65. frequency.setValue (currentlyPlayingNote.getFrequencyInHertz());
  66. }
  67. void noteTimbreChanged() override
  68. {
  69. timbre.setValue (currentlyPlayingNote.timbre.asUnsignedFloat());
  70. }
  71. void noteKeyStateChanged() override
  72. {
  73. }
  74. void setCurrentSampleRate (double newRate) override
  75. {
  76. if (currentSampleRate != newRate)
  77. {
  78. noteStopped (false);
  79. currentSampleRate = newRate;
  80. level.reset (currentSampleRate, smoothingLengthInSeconds);
  81. timbre.reset (currentSampleRate, smoothingLengthInSeconds);
  82. frequency.reset (currentSampleRate, smoothingLengthInSeconds);
  83. }
  84. }
  85. //==============================================================================
  86. virtual void renderNextBlock (AudioBuffer<float>& outputBuffer,
  87. int startSample,
  88. int numSamples) override
  89. {
  90. if (phaseDelta != 0.0)
  91. {
  92. if (tailOff > 0)
  93. {
  94. while (--numSamples >= 0)
  95. {
  96. const float currentSample = getNextSample() * (float) tailOff;
  97. for (int i = outputBuffer.getNumChannels(); --i >= 0;)
  98. outputBuffer.addSample (i, startSample, currentSample);
  99. ++startSample;
  100. tailOff *= 0.99;
  101. if (tailOff <= 0.005)
  102. {
  103. clearCurrentNote();
  104. phaseDelta = 0.0;
  105. break;
  106. }
  107. }
  108. }
  109. else
  110. {
  111. while (--numSamples >= 0)
  112. {
  113. const float currentSample = getNextSample();
  114. for (int i = outputBuffer.getNumChannels(); --i >= 0;)
  115. outputBuffer.addSample (i, startSample, currentSample);
  116. ++startSample;
  117. }
  118. }
  119. }
  120. }
  121. private:
  122. //==============================================================================
  123. float getNextSample() noexcept
  124. {
  125. const double levelDb = (level.getNextValue() - 1.0) * maxLevelDb;
  126. const double amplitude = std::pow (10.0f, 0.05f * levelDb) * maxLevel;
  127. // timbre is used to blend between a sine and a square.
  128. const double f1 = std::sin (phase);
  129. const double f2 = std::copysign (1.0, f1);
  130. const double a2 = timbre.getNextValue();
  131. const double a1 = 1.0 - a2;
  132. const float nextSample = float (amplitude * ((a1 * f1) + (a2 * f2)));
  133. const double cyclesPerSample = frequency.getNextValue() / currentSampleRate;
  134. phaseDelta = 2.0 * double_Pi * cyclesPerSample;
  135. phase = std::fmod (phase + phaseDelta, 2.0 * double_Pi);
  136. return nextSample;
  137. }
  138. //==============================================================================
  139. LinearSmoothedValue<double> level, timbre, frequency;
  140. double phase, phaseDelta, tailOff;
  141. const double maxLevel = 0.05f;
  142. const double maxLevelDb = 31.0f;
  143. const double smoothingLengthInSeconds = 0.01;
  144. };
  145. #endif // MPEDEMOSYNTHVOICE_H_INCLUDED