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