A MIDI piano synthesizer that implements the Karplus Strong physical modeling algorithm.
Dependencies: mbed USBDevice PinDetect
Audio/KarplusStrong.cpp
- Committer:
- asuszek
- Date:
- 2016-04-25
- Revision:
- 22:b800e1766647
- Parent:
- 20:bf675ba2c454
File content as of revision 22:b800e1766647:
#include "KarplusStrong.h" static float noiseSeed[(C::SAMPLE_RATE / C::MIN_FREQUENCY) + 1]; // Calculate pluck damping constant variables. const float pluckDampingMin = 0.1; const float pluckDampingMax = 0.9; const float pluckDampingVariationMax = C::PLUCK_DAMPING + C::PLUCK_DAMPING_VARIATION * (pluckDampingMax - C::PLUCK_DAMPING); const float KarplusStrong::pluckDampingVariationMin = C::PLUCK_DAMPING - C::PLUCK_DAMPING_VARIATION * (C::PLUCK_DAMPING - pluckDampingMin); const float KarplusStrong::pluckDampingVariationDifference = pluckDampingVariationMax - pluckDampingVariationMin; KarplusStrong::KarplusStrong() { initializeNoiseSeed(); // These values will be overwritten under normal control flow, but give them values in case of runtime errors. lastOutput = 0.0; filterCoefficient = 0.6; nextFilterCoefficient = filterCoefficient; pluckDampingCoefficient = 0.5; } void KarplusStrong::midiNoteOn(int key, float velocity) { // Calculate the smoothing filter coefficient for this note. float noteIndex = float(key - 24) / 48.0; // normalize the lower two octaves. // Clip to 1.0 if (noteIndex > 1.0) { noteIndex = 1.0; } // The volume is always pretty quiet, even at full velocity, so use velocity as a filter cutoff instead. float stringDamping = C::STRING_DAMPING_MIN + velocity * (C::STRING_DAMPING_MAX - C::STRING_DAMPING_MIN); nextFilterCoefficient = stringDamping // Initial damping. + 0.5 * (1.0 - stringDamping) * sqrt(noteIndex) // Keyboard tracking + C::STRING_DAMPING_VARIATION * (1.0 - stringDamping) * getRand(); // Random variation // Calculate the pluck damping for this note. pluckDampingCoefficient = pluckDampingVariationMin + getRand() * pluckDampingVariationDifference; } float KarplusStrong::fillBuffer(const int bufferIndex) { // Copy over the calculated filter coefficient. filterCoefficient = nextFilterCoefficient; // Get the noise sample from the static noise seed while making room for character variation. float noiseSample = noiseSeed[bufferIndex] * (1.0 - C::CHARACTER_VARIATION); // Add in some character randomness to make it more realistic. noiseSample += getRandSample() * C::CHARACTER_VARIATION; // Filter using the pluck damping coefficient to control sprectral content. noiseSample = lowpassFilter(lastOutput, noiseSample, pluckDampingCoefficient); return processBuffer(noiseSample); } float KarplusStrong::processBuffer(const float inputSample) { // Put the sample from the last period through the lowpass filter. lastOutput = lowpassFilter(lastOutput, inputSample, filterCoefficient); return lastOutput; } void KarplusStrong::initializeNoiseSeed() { // Initialize the random function with a static seed. srand(time(NULL)); int seedLength = (C::SAMPLE_RATE / C::MIN_FREQUENCY) + 1; for (int i = 0; i < seedLength; i++) { // Create a random number in [-1.0, 1.0] noiseSeed[i] = getRandSample(); } } float KarplusStrong::lowpassFilter(const float output, const float input, const float smoothingFactor) { // Simple single pole recursive IIR lowpass filter. return input * smoothingFactor + output * (1.0 - smoothingFactor); }