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.

126 lines
4.5KB

  1. /*
  2. ==============================================================================
  3. JUCE demo code - use at your own risk!
  4. ==============================================================================
  5. */
  6. /**
  7. A very basic generator of a simulated plucked string sound, implementing
  8. the Karplus-Strong algorithm.
  9. Not performance-optimised!
  10. */
  11. class StringSynthesiser
  12. {
  13. public:
  14. //=======================================================================
  15. /** Constructor.
  16. @param sampleRate The audio sample rate to use.
  17. @param frequencyInHz The fundamental frequency of the simulated string in
  18. Hertz.
  19. */
  20. StringSynthesiser (double sampleRate, double frequencyInHz)
  21. {
  22. doPluckForNextBuffer.set (false);
  23. prepareSynthesiserState (sampleRate, frequencyInHz);
  24. }
  25. //=======================================================================
  26. /** Excite the simulated string by plucking it at a given position.
  27. @param pluckPosition The position of the plucking, relative to the length
  28. of the string. Must be between 0 and 1.
  29. */
  30. void stringPlucked (float pluckPosition)
  31. {
  32. jassert (pluckPosition >= 0.0 && pluckPosition <= 1.0);
  33. // we choose a very simple approach to communicate with the audio thread:
  34. // simply tell the synth to perform the plucking excitation at the beginning
  35. // of the next buffer (= when generateAndAddData is called the next time).
  36. if (doPluckForNextBuffer.compareAndSetBool (1, 0))
  37. {
  38. // plucking in the middle gives the largest amplitude;
  39. // plucking at the very ends will do nothing.
  40. amplitude = std::sin (float_Pi * pluckPosition);
  41. }
  42. }
  43. //=======================================================================
  44. /** Generate next chunk of mono audio output and add it into a buffer.
  45. @param outBuffer Buffer to fill (one channel only). New sound will be
  46. added to existing content of the buffer (instead of
  47. replacing it).
  48. @param numSamples Number of samples to generate (make sure that outBuffer
  49. has enough space).
  50. */
  51. void generateAndAddData (float* outBuffer, int numSamples)
  52. {
  53. if (doPluckForNextBuffer.compareAndSetBool (0, 1))
  54. exciteInternalBuffer();
  55. // cycle through the delay line and apply a simple averaging filter
  56. for (int i = 0; i < numSamples; ++i)
  57. {
  58. const int nextPos = (pos + 1) % delayLine.size();
  59. delayLine[nextPos] = (float) (decay * 0.5 * (delayLine[nextPos] + delayLine[pos]));
  60. outBuffer[i] += delayLine[pos];
  61. pos = nextPos;
  62. }
  63. }
  64. private:
  65. //=======================================================================
  66. void prepareSynthesiserState (double sampleRate, double frequencyInHz)
  67. {
  68. std::size_t delayLineLength = std::lround (sampleRate / frequencyInHz);
  69. // we need a minimum delay line length to get a reasonable synthesis.
  70. // if you hit this assert, increase sample rate or decrease frequency!
  71. jassert (delayLineLength > 50);
  72. delayLine.resize (delayLineLength);
  73. std::fill (delayLine.begin(), delayLine.end(), 0.0f);
  74. excitationSample.resize (delayLineLength);
  75. // as the excitation sample we use random noise between -1 and 1
  76. // (as a simple approximation to a plucking excitation)
  77. std::generate (excitationSample.begin(),
  78. excitationSample.end(),
  79. [] { return (Random::getSystemRandom().nextFloat() * 2.0f) - 1.0f; } );
  80. }
  81. void exciteInternalBuffer()
  82. {
  83. // fill the buffer with the precomputed excitation sound (scaled with amplitude)
  84. jassert (delayLine.size() >= excitationSample.size());
  85. std::transform (excitationSample.begin(),
  86. excitationSample.end(),
  87. delayLine.begin(),
  88. [this] (double sample) { return amplitude * sample; } );
  89. };
  90. //=======================================================================
  91. const double decay = 0.998;
  92. double amplitude = 0.0;
  93. Atomic<int> doPluckForNextBuffer;
  94. std::vector<float> excitationSample, delayLine;
  95. int pos = 0;
  96. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StringSynthesiser)
  97. };