A MIDI piano synthesizer that implements the Karplus Strong physical modeling algorithm.

Dependencies:   mbed USBDevice PinDetect

Audio/Synthesizer.cpp

Committer:
asuszek
Date:
2016-04-25
Revision:
22:b800e1766647
Parent:
18:26d93c5b9bb6

File content as of revision 22:b800e1766647:


#include "Synthesizer.h"

const float TWO_PI = 6.28318530717959;

/**
 * A function that acts as an individual synthesizer.
 * It's job is to process the next sample at the current index
 *   based on the current state of the overall synthesizer.
 * Output should be in the range of [-1.0, 1.0] and saved back into the buffer.
 */
typedef void (Synthesizer::*processorFunction)();
const int numSynths = 5;

// Array of pointers to all synthesizer functions.
processorFunction processors[] = {
        &Synthesizer::processKarplusStrong,
        &Synthesizer::processSine,
        &Synthesizer::processTriangle,
        &Synthesizer::processSquare,
        &Synthesizer::processSaw,
};

int64_t Synthesizer::nextVoiceIndex = 0;

Synthesizer::Synthesizer() {
    bufferIndex = 0;
    currentState = OFF;
    nextBufferSize = -1;
    voiceIndex = -1;
    processorIndex = 0;
}

void Synthesizer::midiNoteOn(int key, int velocity) {
    float freq = 440.0 * std::pow(2.0, float(key - 69) / 12.0);
    float velocityFloat = float(velocity) / 127.0;
    
    // Make sure we are in bounds.
    if (freq < C::MIN_FREQUENCY) {
        #ifdef DEBUG
        printf("Error: Note is below minimum frequency");
        #endif
        return;   
    }
    
    currentKey = key;
    int size = int((float(C::SAMPLE_RATE) / freq) + 0.5);
    
    if (currentState == OFF) {
        // We can immediately play the new note.
        bufferSize = size;
        this->velocity = velocityFloat;
        
        voiceIndex = Synthesizer::nextVoiceIndex;
        ++Synthesizer::nextVoiceIndex;
        fromDisabled = true;
        currentState = FILL;
    } else {
         // Put the values in the queue for later.
         nextBufferSize = size;  
         nextVelocity = velocityFloat;
    } 
    
    // Signal the Karplus Strong processor to calculate a new note.
    karplusStrong.midiNoteOn(key, velocityFloat);
}

void Synthesizer::midiNoteOff() {
    // Begin release of current note.
    currentState = RELEASE;
    
    // Clear the queue.
    nextBufferSize = -1; 
}

float Synthesizer::getSample() {
    float outputSample;
    if (currentState == FILL && fromDisabled) {
        // The buffer may contain the release of the previous note so just return 0.0
        outputSample = 0.0; 
    } else {
        // Simply return the next sample. Do not increment.
        outputSample = buffer[bufferIndex]; 
    }
    
    // Process the next period.
    // Use the current processor to process the buffer.
    (this->*processors[processorIndex])();
    
    // Return the sample.
    return outputSample;
}

bool Synthesizer::isPlaying() {
    return currentState != OFF;
}

int64_t Synthesizer::getVoiceIndex() {
    return voiceIndex;   
}

int Synthesizer::nextSynth(int direction) {
    // Rotate to the next synth.
    processorIndex += direction;
    if (processorIndex >= numSynths) {
        processorIndex -= numSynths;
    } else if (processorIndex < 0) {
        processorIndex += numSynths;
    }
    
    // If we are playing, restart the buffer.
    if (isPlaying()) {
        fromDisabled = true;
        bufferIndex = 0;
        currentState = FILL;  
    }
    
    return processorIndex;
}

int Synthesizer::getCurrentKey() {
    return currentKey;   
}

// Processor Functions.

void Synthesizer::processKarplusStrong() {
    if (currentState == FILL) {
        buffer[bufferIndex] = karplusStrong.fillBuffer(bufferIndex);
        ++bufferIndex;
        
        checkFillBounds();
    } else {
       // Process for sustain or release.
       float inputSample = buffer[bufferIndex];
       buffer[bufferIndex] = karplusStrong.processBuffer(inputSample);
       // Check transition logic.
       identityProcess(); 
    }  
}

void Synthesizer::processSine() {
    if (currentState == FILL) {
        // Create the sine wave.
        buffer[bufferIndex] = velocity * sin(TWO_PI * float(bufferIndex) / float(bufferSize));
        ++bufferIndex;
        
        checkFillBounds();
    } else {
        identityProcess();    
    }
}

void Synthesizer::processTriangle() {
    if (currentState == FILL) {
        // Create the triangle wave.
        // Rotate pi/2 to remove phase offset.
        int index = (bufferIndex + (bufferSize >> 2)) % bufferSize;
        // Calculate linear function from -1 to 3
        int sample = -1.0 + (4.0 * float(index) / float(bufferSize));
        // Redirect second half of the line.
        if (sample > 1.0) {
            sample = -sample + 2.0;   
        }
        buffer[bufferIndex] = velocity * sample;
        ++bufferIndex;
        
        checkFillBounds();
    } else {
        identityProcess();    
    }
}

void Synthesizer::processSquare() {
    if (currentState == FILL) {
        // Create the square wave.
        buffer[bufferIndex] = bufferIndex < (bufferSize >> 1) ? -velocity : velocity;
        ++bufferIndex;
        
        checkFillBounds();
    } else {
        identityProcess();    
    }
}

void Synthesizer::processSaw() {
    if (currentState == FILL) {
        // Create the saw wave.
        buffer[bufferIndex] = velocity * (1.0 - 2.0 * float(bufferIndex) / float(bufferSize));
        ++bufferIndex;
        
        checkFillBounds();
    } else {
        identityProcess();    
    }
}

void Synthesizer::identityProcess() {
    if (currentState == RELEASE) {
        // Attenuate the current buffer.
        buffer[bufferIndex] *= -(float(bufferIndex) / float(bufferSize)) + 1.0;
    }
    
    ++bufferIndex;
        
    // Check if we have reached the end of the buffer.
    if (bufferIndex == bufferSize) {
        bufferIndex = 0;
        
        // Check the queue to see if we have a new note waiting.
        if (nextBufferSize != -1) {
            if (currentState == SUSTAIN) {
                currentState = RELEASE;   
            } else {
                bufferSize = nextBufferSize;
                velocity = nextVelocity;
                nextBufferSize = -1;
                fromDisabled = false;
                currentState = FILL;
            }
        } else if (currentState == RELEASE) {
            // Disable the synthesizer.
            currentState = OFF;
            voiceIndex = -1;
        }
    }
}

void Synthesizer::checkFillBounds() {
    // Check if we have reached the end of the buffer.
    if (bufferIndex == bufferSize) {
        bufferIndex = 0;
        
        // Check if there are new notes waiting in the queue.
        if (nextBufferSize != -1) {
            currentState = RELEASE;
        } else {
            currentState = SUSTAIN;
        }   
    }    
}