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);   
}