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.

143 lines
5.3KB

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