|
- /*
- ==============================================================================
-
- JUCE demo code - use at your own risk!
-
- ==============================================================================
- */
-
-
- /**
- A very basic generator of a simulated plucked string sound, implementing
- the Karplus-Strong algorithm.
-
- Not performance-optimised!
- */
- class StringSynthesiser
- {
- public:
- //=======================================================================
- /** Constructor.
-
- @param sampleRate The audio sample rate to use.
- @param frequencyInHz The fundamental frequency of the simulated string in
- Hertz.
- */
- StringSynthesiser (double sampleRate, double frequencyInHz)
- {
- doPluckForNextBuffer.set (false);
- prepareSynthesiserState (sampleRate, frequencyInHz);
- }
-
- //=======================================================================
- /** Excite the simulated string by plucking it at a given position.
-
- @param pluckPosition The position of the plucking, relative to the length
- of the string. Must be between 0 and 1.
- */
- void stringPlucked (float pluckPosition)
- {
- jassert (pluckPosition >= 0.0 && pluckPosition <= 1.0);
-
- // we choose a very simple approach to communicate with the audio thread:
- // simply tell the synth to perform the plucking excitation at the beginning
- // of the next buffer (= when generateAndAddData is called the next time).
-
- if (doPluckForNextBuffer.compareAndSetBool (1, 0))
- {
- // plucking in the middle gives the largest amplitude;
- // plucking at the very ends will do nothing.
- amplitude = std::sin (float_Pi * pluckPosition);
- }
- }
-
- //=======================================================================
- /** Generate next chunk of mono audio output and add it into a buffer.
-
- @param outBuffer Buffer to fill (one channel only). New sound will be
- added to existing content of the buffer (instead of
- replacing it).
- @param numSamples Number of samples to generate (make sure that outBuffer
- has enough space).
- */
- void generateAndAddData (float* outBuffer, int numSamples)
- {
- if (doPluckForNextBuffer.compareAndSetBool (0, 1))
- exciteInternalBuffer();
-
- // cycle through the delay line and apply a simple averaging filter
- for (int i = 0; i < numSamples; ++i)
- {
- const int nextPos = (pos + 1) % delayLine.size();
-
- delayLine[nextPos] = (float) (decay * 0.5 * (delayLine[nextPos] + delayLine[pos]));
- outBuffer[i] += delayLine[pos];
-
- pos = nextPos;
- }
- }
-
- private:
- //=======================================================================
- void prepareSynthesiserState (double sampleRate, double frequencyInHz)
- {
- std::size_t delayLineLength = std::lround (sampleRate / frequencyInHz);
-
- // we need a minimum delay line length to get a reasonable synthesis.
- // if you hit this assert, increase sample rate or decrease frequency!
- jassert (delayLineLength > 50);
-
- delayLine.resize (delayLineLength);
- std::fill (delayLine.begin(), delayLine.end(), 0.0f);
-
- excitationSample.resize (delayLineLength);
-
- // as the excitation sample we use random noise between -1 and 1
- // (as a simple approximation to a plucking excitation)
-
- std::generate (excitationSample.begin(),
- excitationSample.end(),
- [] { return (Random::getSystemRandom().nextFloat() * 2.0f) - 1.0f; } );
- }
-
- void exciteInternalBuffer()
- {
- // fill the buffer with the precomputed excitation sound (scaled with amplitude)
-
- jassert (delayLine.size() >= excitationSample.size());
-
- std::transform (excitationSample.begin(),
- excitationSample.end(),
- delayLine.begin(),
- [this] (double sample) { return amplitude * sample; } );
- };
-
- //=======================================================================
- const double decay = 0.998;
- double amplitude = 0.0;
-
- Atomic<int> doPluckForNextBuffer;
-
- std::vector<float> excitationSample, delayLine;
- int pos = 0;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StringSynthesiser)
- };
|