A MIDI piano synthesizer that implements the Karplus Strong physical modeling algorithm.
Dependencies: mbed USBDevice PinDetect
Audio/KarplusStrong.cpp@15:aa5a4c350251, 2016-04-17 (annotated)
- Committer:
- asuszek
- Date:
- Sun Apr 17 21:59:03 2016 +0000
- Revision:
- 15:aa5a4c350251
- Parent:
- 13:bb0ec927e458
- Child:
- 16:b25ca34a705f
Added character variation in the noise seed for a less artificial sound.
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
asuszek | 13:bb0ec927e458 | 1 | |
asuszek | 13:bb0ec927e458 | 2 | #include "KarplusStrong.h" |
asuszek | 13:bb0ec927e458 | 3 | |
asuszek | 13:bb0ec927e458 | 4 | static float noiseSeed[(C::SAMPLE_RATE / C::MIN_FREQUENCY) + 1]; |
asuszek | 13:bb0ec927e458 | 5 | |
asuszek | 13:bb0ec927e458 | 6 | KarplusStrong::KarplusStrong() { |
asuszek | 13:bb0ec927e458 | 7 | initializeNoiseSeed(); |
asuszek | 13:bb0ec927e458 | 8 | |
asuszek | 13:bb0ec927e458 | 9 | // These values will be overwritten under normal control flow, but give them values in case of runtime errors. |
asuszek | 13:bb0ec927e458 | 10 | lastOutput = 0.0; |
asuszek | 13:bb0ec927e458 | 11 | filterCoefficient = 0.6; |
asuszek | 13:bb0ec927e458 | 12 | nextFilterCoefficient = filterCoefficient; |
asuszek | 13:bb0ec927e458 | 13 | } |
asuszek | 13:bb0ec927e458 | 14 | |
asuszek | 13:bb0ec927e458 | 15 | void KarplusStrong::midiNoteOn(int key, float velocity) { |
asuszek | 13:bb0ec927e458 | 16 | // Save the velocity for later. |
asuszek | 13:bb0ec927e458 | 17 | this->velocity = velocity; |
asuszek | 13:bb0ec927e458 | 18 | |
asuszek | 13:bb0ec927e458 | 19 | // Calculate the smoothing filter coefficient for this note. |
asuszek | 13:bb0ec927e458 | 20 | float noteIndex = float(key - 24) / 52.0; // normalize the lower two octaves. |
asuszek | 13:bb0ec927e458 | 21 | // Clip to 1.0 |
asuszek | 13:bb0ec927e458 | 22 | if (noteIndex > 1.0) { |
asuszek | 13:bb0ec927e458 | 23 | noteIndex = 1.0; |
asuszek | 13:bb0ec927e458 | 24 | } |
asuszek | 13:bb0ec927e458 | 25 | |
asuszek | 13:bb0ec927e458 | 26 | nextFilterCoefficient = C::STRING_DAMPING // Initial damping. |
asuszek | 13:bb0ec927e458 | 27 | + 0.5 * (1.0 - C::STRING_DAMPING) * sqrt(noteIndex) // Keyboard tracking |
asuszek | 13:bb0ec927e458 | 28 | + C::STRING_DAMPING_VARIATION * (1.0 - C::STRING_DAMPING) * getRand(); // Random variation |
asuszek | 13:bb0ec927e458 | 29 | } |
asuszek | 13:bb0ec927e458 | 30 | |
asuszek | 13:bb0ec927e458 | 31 | float KarplusStrong::fillBuffer(const int bufferIndex) { |
asuszek | 13:bb0ec927e458 | 32 | // Copy over the calculated filter coefficient. |
asuszek | 13:bb0ec927e458 | 33 | filterCoefficient = nextFilterCoefficient; |
asuszek | 13:bb0ec927e458 | 34 | |
asuszek | 15:aa5a4c350251 | 35 | // Get the noise sample from the static noise seed while making room for character variation. |
asuszek | 15:aa5a4c350251 | 36 | float noiseSample = noiseSeed[bufferIndex] * (1.0 - C::CHARACTER_VARIATION); |
asuszek | 15:aa5a4c350251 | 37 | // Add in some character randomness to make it more realistic. |
asuszek | 15:aa5a4c350251 | 38 | noiseSample += getRandSample() * C::CHARACTER_VARIATION; |
asuszek | 15:aa5a4c350251 | 39 | // Account for velocity. |
asuszek | 15:aa5a4c350251 | 40 | noiseSample *= velocity; |
asuszek | 15:aa5a4c350251 | 41 | |
asuszek | 15:aa5a4c350251 | 42 | lastOutput = noiseSample; |
asuszek | 15:aa5a4c350251 | 43 | return noiseSample; |
asuszek | 13:bb0ec927e458 | 44 | } |
asuszek | 13:bb0ec927e458 | 45 | |
asuszek | 13:bb0ec927e458 | 46 | float KarplusStrong::processBuffer(const float inputSample) { |
asuszek | 13:bb0ec927e458 | 47 | // Put the sample from the last period through the lowpass filter. |
asuszek | 13:bb0ec927e458 | 48 | lastOutput = lowpassFilter(lastOutput, inputSample, filterCoefficient); |
asuszek | 13:bb0ec927e458 | 49 | return lastOutput; |
asuszek | 13:bb0ec927e458 | 50 | } |
asuszek | 13:bb0ec927e458 | 51 | |
asuszek | 13:bb0ec927e458 | 52 | void KarplusStrong::initializeNoiseSeed() { |
asuszek | 13:bb0ec927e458 | 53 | // Initialize the random function with a static seed. |
asuszek | 13:bb0ec927e458 | 54 | srand(time(NULL)); |
asuszek | 13:bb0ec927e458 | 55 | |
asuszek | 13:bb0ec927e458 | 56 | int seedLength = (C::SAMPLE_RATE / C::MIN_FREQUENCY) + 1; |
asuszek | 13:bb0ec927e458 | 57 | for (int i = 0; i < seedLength; i++) { |
asuszek | 13:bb0ec927e458 | 58 | // Create a random number in [-1.0, 1.0] |
asuszek | 15:aa5a4c350251 | 59 | noiseSeed[i] = getRandSample(); |
asuszek | 13:bb0ec927e458 | 60 | } |
asuszek | 13:bb0ec927e458 | 61 | } |
asuszek | 13:bb0ec927e458 | 62 | |
asuszek | 13:bb0ec927e458 | 63 | float KarplusStrong::lowpassFilter(const float output, const float input, const float smoothingFactor) { |
asuszek | 13:bb0ec927e458 | 64 | // Simple single pole recursive IIR lowpass filter. |
asuszek | 13:bb0ec927e458 | 65 | return input * smoothingFactor + output * (1.0 - smoothingFactor); |
asuszek | 13:bb0ec927e458 | 66 | } |