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.

185 lines
6.1KB

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