A MIDI piano synthesizer that implements the Karplus Strong physical modeling algorithm.
Dependencies: mbed USBDevice PinDetect
Audio/Synthesizer.cpp@22:b800e1766647, 2016-04-25 (annotated)
- Committer:
- asuszek
- Date:
- Mon Apr 25 23:58:15 2016 +0000
- Revision:
- 22:b800e1766647
- Parent:
- 18:26d93c5b9bb6
Fixed LED bug
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
asuszek | 9:1e012f67470c | 1 | |
asuszek | 9:1e012f67470c | 2 | #include "Synthesizer.h" |
asuszek | 9:1e012f67470c | 3 | |
asuszek | 9:1e012f67470c | 4 | const float TWO_PI = 6.28318530717959; |
asuszek | 9:1e012f67470c | 5 | |
asuszek | 9:1e012f67470c | 6 | /** |
asuszek | 9:1e012f67470c | 7 | * A function that acts as an individual synthesizer. |
asuszek | 9:1e012f67470c | 8 | * It's job is to process the next sample at the current index |
asuszek | 9:1e012f67470c | 9 | * based on the current state of the overall synthesizer. |
asuszek | 9:1e012f67470c | 10 | * Output should be in the range of [-1.0, 1.0] and saved back into the buffer. |
asuszek | 9:1e012f67470c | 11 | */ |
asuszek | 9:1e012f67470c | 12 | typedef void (Synthesizer::*processorFunction)(); |
asuszek | 13:bb0ec927e458 | 13 | const int numSynths = 5; |
asuszek | 9:1e012f67470c | 14 | |
asuszek | 9:1e012f67470c | 15 | // Array of pointers to all synthesizer functions. |
asuszek | 9:1e012f67470c | 16 | processorFunction processors[] = { |
asuszek | 13:bb0ec927e458 | 17 | &Synthesizer::processKarplusStrong, |
asuszek | 9:1e012f67470c | 18 | &Synthesizer::processSine, |
asuszek | 9:1e012f67470c | 19 | &Synthesizer::processTriangle, |
asuszek | 9:1e012f67470c | 20 | &Synthesizer::processSquare, |
asuszek | 9:1e012f67470c | 21 | &Synthesizer::processSaw, |
asuszek | 9:1e012f67470c | 22 | }; |
asuszek | 9:1e012f67470c | 23 | |
asuszek | 9:1e012f67470c | 24 | int64_t Synthesizer::nextVoiceIndex = 0; |
asuszek | 9:1e012f67470c | 25 | |
asuszek | 9:1e012f67470c | 26 | Synthesizer::Synthesizer() { |
asuszek | 9:1e012f67470c | 27 | bufferIndex = 0; |
asuszek | 9:1e012f67470c | 28 | currentState = OFF; |
asuszek | 9:1e012f67470c | 29 | nextBufferSize = -1; |
asuszek | 9:1e012f67470c | 30 | voiceIndex = -1; |
asuszek | 9:1e012f67470c | 31 | processorIndex = 0; |
asuszek | 9:1e012f67470c | 32 | } |
asuszek | 9:1e012f67470c | 33 | |
asuszek | 9:1e012f67470c | 34 | void Synthesizer::midiNoteOn(int key, int velocity) { |
asuszek | 9:1e012f67470c | 35 | float freq = 440.0 * std::pow(2.0, float(key - 69) / 12.0); |
asuszek | 13:bb0ec927e458 | 36 | float velocityFloat = float(velocity) / 127.0; |
asuszek | 9:1e012f67470c | 37 | |
asuszek | 9:1e012f67470c | 38 | // Make sure we are in bounds. |
asuszek | 9:1e012f67470c | 39 | if (freq < C::MIN_FREQUENCY) { |
asuszek | 9:1e012f67470c | 40 | #ifdef DEBUG |
asuszek | 9:1e012f67470c | 41 | printf("Error: Note is below minimum frequency"); |
asuszek | 9:1e012f67470c | 42 | #endif |
asuszek | 9:1e012f67470c | 43 | return; |
asuszek | 9:1e012f67470c | 44 | } |
asuszek | 9:1e012f67470c | 45 | |
asuszek | 9:1e012f67470c | 46 | currentKey = key; |
asuszek | 9:1e012f67470c | 47 | int size = int((float(C::SAMPLE_RATE) / freq) + 0.5); |
asuszek | 9:1e012f67470c | 48 | |
asuszek | 9:1e012f67470c | 49 | if (currentState == OFF) { |
asuszek | 9:1e012f67470c | 50 | // We can immediately play the new note. |
asuszek | 9:1e012f67470c | 51 | bufferSize = size; |
asuszek | 13:bb0ec927e458 | 52 | this->velocity = velocityFloat; |
asuszek | 9:1e012f67470c | 53 | |
asuszek | 9:1e012f67470c | 54 | voiceIndex = Synthesizer::nextVoiceIndex; |
asuszek | 9:1e012f67470c | 55 | ++Synthesizer::nextVoiceIndex; |
asuszek | 9:1e012f67470c | 56 | fromDisabled = true; |
asuszek | 9:1e012f67470c | 57 | currentState = FILL; |
asuszek | 9:1e012f67470c | 58 | } else { |
asuszek | 9:1e012f67470c | 59 | // Put the values in the queue for later. |
asuszek | 9:1e012f67470c | 60 | nextBufferSize = size; |
asuszek | 13:bb0ec927e458 | 61 | nextVelocity = velocityFloat; |
asuszek | 9:1e012f67470c | 62 | } |
asuszek | 13:bb0ec927e458 | 63 | |
asuszek | 13:bb0ec927e458 | 64 | // Signal the Karplus Strong processor to calculate a new note. |
asuszek | 13:bb0ec927e458 | 65 | karplusStrong.midiNoteOn(key, velocityFloat); |
asuszek | 9:1e012f67470c | 66 | } |
asuszek | 9:1e012f67470c | 67 | |
asuszek | 9:1e012f67470c | 68 | void Synthesizer::midiNoteOff() { |
asuszek | 9:1e012f67470c | 69 | // Begin release of current note. |
asuszek | 9:1e012f67470c | 70 | currentState = RELEASE; |
asuszek | 9:1e012f67470c | 71 | |
asuszek | 9:1e012f67470c | 72 | // Clear the queue. |
asuszek | 9:1e012f67470c | 73 | nextBufferSize = -1; |
asuszek | 9:1e012f67470c | 74 | } |
asuszek | 9:1e012f67470c | 75 | |
asuszek | 9:1e012f67470c | 76 | float Synthesizer::getSample() { |
asuszek | 13:bb0ec927e458 | 77 | float outputSample; |
asuszek | 9:1e012f67470c | 78 | if (currentState == FILL && fromDisabled) { |
asuszek | 9:1e012f67470c | 79 | // The buffer may contain the release of the previous note so just return 0.0 |
asuszek | 13:bb0ec927e458 | 80 | outputSample = 0.0; |
asuszek | 13:bb0ec927e458 | 81 | } else { |
asuszek | 13:bb0ec927e458 | 82 | // Simply return the next sample. Do not increment. |
asuszek | 13:bb0ec927e458 | 83 | outputSample = buffer[bufferIndex]; |
asuszek | 9:1e012f67470c | 84 | } |
asuszek | 9:1e012f67470c | 85 | |
asuszek | 13:bb0ec927e458 | 86 | // Process the next period. |
asuszek | 9:1e012f67470c | 87 | // Use the current processor to process the buffer. |
asuszek | 9:1e012f67470c | 88 | (this->*processors[processorIndex])(); |
asuszek | 13:bb0ec927e458 | 89 | |
asuszek | 13:bb0ec927e458 | 90 | // Return the sample. |
asuszek | 13:bb0ec927e458 | 91 | return outputSample; |
asuszek | 9:1e012f67470c | 92 | } |
asuszek | 9:1e012f67470c | 93 | |
asuszek | 9:1e012f67470c | 94 | bool Synthesizer::isPlaying() { |
asuszek | 9:1e012f67470c | 95 | return currentState != OFF; |
asuszek | 9:1e012f67470c | 96 | } |
asuszek | 9:1e012f67470c | 97 | |
asuszek | 9:1e012f67470c | 98 | int64_t Synthesizer::getVoiceIndex() { |
asuszek | 9:1e012f67470c | 99 | return voiceIndex; |
asuszek | 9:1e012f67470c | 100 | } |
asuszek | 9:1e012f67470c | 101 | |
asuszek | 18:26d93c5b9bb6 | 102 | int Synthesizer::nextSynth(int direction) { |
asuszek | 9:1e012f67470c | 103 | // Rotate to the next synth. |
asuszek | 9:1e012f67470c | 104 | processorIndex += direction; |
asuszek | 9:1e012f67470c | 105 | if (processorIndex >= numSynths) { |
asuszek | 9:1e012f67470c | 106 | processorIndex -= numSynths; |
asuszek | 9:1e012f67470c | 107 | } else if (processorIndex < 0) { |
asuszek | 9:1e012f67470c | 108 | processorIndex += numSynths; |
asuszek | 9:1e012f67470c | 109 | } |
asuszek | 9:1e012f67470c | 110 | |
asuszek | 9:1e012f67470c | 111 | // If we are playing, restart the buffer. |
asuszek | 9:1e012f67470c | 112 | if (isPlaying()) { |
asuszek | 9:1e012f67470c | 113 | fromDisabled = true; |
asuszek | 9:1e012f67470c | 114 | bufferIndex = 0; |
asuszek | 9:1e012f67470c | 115 | currentState = FILL; |
asuszek | 9:1e012f67470c | 116 | } |
asuszek | 18:26d93c5b9bb6 | 117 | |
asuszek | 18:26d93c5b9bb6 | 118 | return processorIndex; |
asuszek | 9:1e012f67470c | 119 | } |
asuszek | 9:1e012f67470c | 120 | |
asuszek | 9:1e012f67470c | 121 | int Synthesizer::getCurrentKey() { |
asuszek | 9:1e012f67470c | 122 | return currentKey; |
asuszek | 9:1e012f67470c | 123 | } |
asuszek | 9:1e012f67470c | 124 | |
asuszek | 9:1e012f67470c | 125 | // Processor Functions. |
asuszek | 9:1e012f67470c | 126 | |
asuszek | 13:bb0ec927e458 | 127 | void Synthesizer::processKarplusStrong() { |
asuszek | 13:bb0ec927e458 | 128 | if (currentState == FILL) { |
asuszek | 13:bb0ec927e458 | 129 | buffer[bufferIndex] = karplusStrong.fillBuffer(bufferIndex); |
asuszek | 13:bb0ec927e458 | 130 | ++bufferIndex; |
asuszek | 13:bb0ec927e458 | 131 | |
asuszek | 13:bb0ec927e458 | 132 | checkFillBounds(); |
asuszek | 13:bb0ec927e458 | 133 | } else { |
asuszek | 13:bb0ec927e458 | 134 | // Process for sustain or release. |
asuszek | 13:bb0ec927e458 | 135 | float inputSample = buffer[bufferIndex]; |
asuszek | 13:bb0ec927e458 | 136 | buffer[bufferIndex] = karplusStrong.processBuffer(inputSample); |
asuszek | 13:bb0ec927e458 | 137 | // Check transition logic. |
asuszek | 13:bb0ec927e458 | 138 | identityProcess(); |
asuszek | 13:bb0ec927e458 | 139 | } |
asuszek | 13:bb0ec927e458 | 140 | } |
asuszek | 13:bb0ec927e458 | 141 | |
asuszek | 9:1e012f67470c | 142 | void Synthesizer::processSine() { |
asuszek | 9:1e012f67470c | 143 | if (currentState == FILL) { |
asuszek | 9:1e012f67470c | 144 | // Create the sine wave. |
asuszek | 9:1e012f67470c | 145 | buffer[bufferIndex] = velocity * sin(TWO_PI * float(bufferIndex) / float(bufferSize)); |
asuszek | 9:1e012f67470c | 146 | ++bufferIndex; |
asuszek | 9:1e012f67470c | 147 | |
asuszek | 13:bb0ec927e458 | 148 | checkFillBounds(); |
asuszek | 9:1e012f67470c | 149 | } else { |
asuszek | 9:1e012f67470c | 150 | identityProcess(); |
asuszek | 9:1e012f67470c | 151 | } |
asuszek | 9:1e012f67470c | 152 | } |
asuszek | 9:1e012f67470c | 153 | |
asuszek | 9:1e012f67470c | 154 | void Synthesizer::processTriangle() { |
asuszek | 9:1e012f67470c | 155 | if (currentState == FILL) { |
asuszek | 9:1e012f67470c | 156 | // Create the triangle wave. |
asuszek | 9:1e012f67470c | 157 | // Rotate pi/2 to remove phase offset. |
asuszek | 9:1e012f67470c | 158 | int index = (bufferIndex + (bufferSize >> 2)) % bufferSize; |
asuszek | 9:1e012f67470c | 159 | // Calculate linear function from -1 to 3 |
asuszek | 9:1e012f67470c | 160 | int sample = -1.0 + (4.0 * float(index) / float(bufferSize)); |
asuszek | 9:1e012f67470c | 161 | // Redirect second half of the line. |
asuszek | 9:1e012f67470c | 162 | if (sample > 1.0) { |
asuszek | 9:1e012f67470c | 163 | sample = -sample + 2.0; |
asuszek | 9:1e012f67470c | 164 | } |
asuszek | 9:1e012f67470c | 165 | buffer[bufferIndex] = velocity * sample; |
asuszek | 9:1e012f67470c | 166 | ++bufferIndex; |
asuszek | 9:1e012f67470c | 167 | |
asuszek | 13:bb0ec927e458 | 168 | checkFillBounds(); |
asuszek | 9:1e012f67470c | 169 | } else { |
asuszek | 9:1e012f67470c | 170 | identityProcess(); |
asuszek | 9:1e012f67470c | 171 | } |
asuszek | 9:1e012f67470c | 172 | } |
asuszek | 9:1e012f67470c | 173 | |
asuszek | 9:1e012f67470c | 174 | void Synthesizer::processSquare() { |
asuszek | 9:1e012f67470c | 175 | if (currentState == FILL) { |
asuszek | 9:1e012f67470c | 176 | // Create the square wave. |
asuszek | 9:1e012f67470c | 177 | buffer[bufferIndex] = bufferIndex < (bufferSize >> 1) ? -velocity : velocity; |
asuszek | 9:1e012f67470c | 178 | ++bufferIndex; |
asuszek | 9:1e012f67470c | 179 | |
asuszek | 13:bb0ec927e458 | 180 | checkFillBounds(); |
asuszek | 9:1e012f67470c | 181 | } else { |
asuszek | 9:1e012f67470c | 182 | identityProcess(); |
asuszek | 9:1e012f67470c | 183 | } |
asuszek | 9:1e012f67470c | 184 | } |
asuszek | 9:1e012f67470c | 185 | |
asuszek | 9:1e012f67470c | 186 | void Synthesizer::processSaw() { |
asuszek | 9:1e012f67470c | 187 | if (currentState == FILL) { |
asuszek | 9:1e012f67470c | 188 | // Create the saw wave. |
asuszek | 9:1e012f67470c | 189 | buffer[bufferIndex] = velocity * (1.0 - 2.0 * float(bufferIndex) / float(bufferSize)); |
asuszek | 9:1e012f67470c | 190 | ++bufferIndex; |
asuszek | 9:1e012f67470c | 191 | |
asuszek | 13:bb0ec927e458 | 192 | checkFillBounds(); |
asuszek | 9:1e012f67470c | 193 | } else { |
asuszek | 9:1e012f67470c | 194 | identityProcess(); |
asuszek | 9:1e012f67470c | 195 | } |
asuszek | 9:1e012f67470c | 196 | } |
asuszek | 9:1e012f67470c | 197 | |
asuszek | 9:1e012f67470c | 198 | void Synthesizer::identityProcess() { |
asuszek | 9:1e012f67470c | 199 | if (currentState == RELEASE) { |
asuszek | 9:1e012f67470c | 200 | // Attenuate the current buffer. |
asuszek | 9:1e012f67470c | 201 | buffer[bufferIndex] *= -(float(bufferIndex) / float(bufferSize)) + 1.0; |
asuszek | 9:1e012f67470c | 202 | } |
asuszek | 9:1e012f67470c | 203 | |
asuszek | 9:1e012f67470c | 204 | ++bufferIndex; |
asuszek | 9:1e012f67470c | 205 | |
asuszek | 9:1e012f67470c | 206 | // Check if we have reached the end of the buffer. |
asuszek | 9:1e012f67470c | 207 | if (bufferIndex == bufferSize) { |
asuszek | 9:1e012f67470c | 208 | bufferIndex = 0; |
asuszek | 9:1e012f67470c | 209 | |
asuszek | 9:1e012f67470c | 210 | // Check the queue to see if we have a new note waiting. |
asuszek | 9:1e012f67470c | 211 | if (nextBufferSize != -1) { |
asuszek | 9:1e012f67470c | 212 | if (currentState == SUSTAIN) { |
asuszek | 9:1e012f67470c | 213 | currentState = RELEASE; |
asuszek | 9:1e012f67470c | 214 | } else { |
asuszek | 9:1e012f67470c | 215 | bufferSize = nextBufferSize; |
asuszek | 9:1e012f67470c | 216 | velocity = nextVelocity; |
asuszek | 9:1e012f67470c | 217 | nextBufferSize = -1; |
asuszek | 9:1e012f67470c | 218 | fromDisabled = false; |
asuszek | 9:1e012f67470c | 219 | currentState = FILL; |
asuszek | 9:1e012f67470c | 220 | } |
asuszek | 9:1e012f67470c | 221 | } else if (currentState == RELEASE) { |
asuszek | 9:1e012f67470c | 222 | // Disable the synthesizer. |
asuszek | 9:1e012f67470c | 223 | currentState = OFF; |
asuszek | 9:1e012f67470c | 224 | voiceIndex = -1; |
asuszek | 9:1e012f67470c | 225 | } |
asuszek | 9:1e012f67470c | 226 | } |
asuszek | 13:bb0ec927e458 | 227 | } |
asuszek | 13:bb0ec927e458 | 228 | |
asuszek | 13:bb0ec927e458 | 229 | void Synthesizer::checkFillBounds() { |
asuszek | 13:bb0ec927e458 | 230 | // Check if we have reached the end of the buffer. |
asuszek | 13:bb0ec927e458 | 231 | if (bufferIndex == bufferSize) { |
asuszek | 13:bb0ec927e458 | 232 | bufferIndex = 0; |
asuszek | 13:bb0ec927e458 | 233 | |
asuszek | 13:bb0ec927e458 | 234 | // Check if there are new notes waiting in the queue. |
asuszek | 13:bb0ec927e458 | 235 | if (nextBufferSize != -1) { |
asuszek | 13:bb0ec927e458 | 236 | currentState = RELEASE; |
asuszek | 13:bb0ec927e458 | 237 | } else { |
asuszek | 13:bb0ec927e458 | 238 | currentState = SUSTAIN; |
asuszek | 13:bb0ec927e458 | 239 | } |
asuszek | 13:bb0ec927e458 | 240 | } |
asuszek | 9:1e012f67470c | 241 | } |