1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00
JUCE/examples/PluckedStringsDemo/Source/StringSynthesiser.h

142 lines
5.3 KiB
C++

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
/**
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 (MathConstants<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)
{
size_t delayLineLength = (size_t) roundToInt (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)
};