12-polyphonic "chiptune" MIDI synthesizer for LPC1768 (Standalone version)

Dependencies:   ClockControl PowerControl mbed

Committer:
kayekss
Date:
Tue Dec 23 21:50:53 2014 +0000
Revision:
6:cda45a5e723e
Parent:
5:7bc917d03bd6
Supports "Panic on offline" feature when using MIDI-port input

Who changed what in which revision?

UserRevisionLine numberNew contents of line
kayekss 0:727737138ac5 1 #include <climits>
kayekss 0:727737138ac5 2 #include "mbed.h"
kayekss 0:727737138ac5 3 #include "defs.h"
kayekss 0:727737138ac5 4 #include "Channels.h"
kayekss 0:727737138ac5 5 #include "GeminiCore.h"
kayekss 0:727737138ac5 6 #include "Wavetable.h"
kayekss 0:727737138ac5 7 #include "debug.h"
kayekss 0:727737138ac5 8 #include "events.h"
kayekss 0:727737138ac5 9 #include "frequency.h"
kayekss 0:727737138ac5 10 #include "note.h"
kayekss 0:727737138ac5 11
kayekss 0:727737138ac5 12 extern Serial console;
kayekss 0:727737138ac5 13 extern volatile uint32_t __countMs;
kayekss 0:727737138ac5 14 extern GeminiCore gemCore;
kayekss 0:727737138ac5 15 extern Channels ch;
kayekss 0:727737138ac5 16 extern note_t noteSent[NUM_INSTRUMENT];
kayekss 0:727737138ac5 17 extern float const frequencyTable[128];
kayekss 0:727737138ac5 18 extern dumpmode_t dumpMode;
kayekss 0:727737138ac5 19
kayekss 0:727737138ac5 20 bool isImplementedDrumNoteNumber(uint8_t noteNumber) {
kayekss 0:727737138ac5 21 switch (noteNumber) {
kayekss 0:727737138ac5 22 case 0x23: // Acoustic Bass Drum
kayekss 0:727737138ac5 23 case 0x24: // Bass Drum 1
kayekss 0:727737138ac5 24 case 0x25: // Side Stick
kayekss 0:727737138ac5 25 case 0x26: // Acoustic Snare
kayekss 0:727737138ac5 26 case 0x28: // Electric Snare
kayekss 0:727737138ac5 27 case 0x29: // Low Floor Tom
kayekss 0:727737138ac5 28 case 0x2a: // Closed High-hat
kayekss 0:727737138ac5 29 case 0x2b: // High Floor Tom
kayekss 0:727737138ac5 30 case 0x2c: // Pedal High-hat
kayekss 0:727737138ac5 31 case 0x2d: // Low Tom
kayekss 0:727737138ac5 32 case 0x2e: // Open High-hat
kayekss 0:727737138ac5 33 case 0x2f: // Low-Mid Tom
kayekss 0:727737138ac5 34 case 0x30: // High-Mid Tom
kayekss 0:727737138ac5 35 case 0x31: // Crash Cymbal 1
kayekss 0:727737138ac5 36 case 0x32: // High Tom
kayekss 0:727737138ac5 37 case 0x39: // Crash Cymbal 2
kayekss 0:727737138ac5 38 return true;
kayekss 0:727737138ac5 39 default:
kayekss 0:727737138ac5 40 return false;
kayekss 0:727737138ac5 41 }
kayekss 0:727737138ac5 42 }
kayekss 0:727737138ac5 43
kayekss 0:727737138ac5 44 void dispatchNoteOff(char messageBytes[3]) {
kayekss 0:727737138ac5 45 uint16_t targetInst;
kayekss 0:727737138ac5 46 uint16_t oldest = 0xffff;
kayekss 0:727737138ac5 47 uint32_t oldestMs = UINT_MAX;
kayekss 0:727737138ac5 48 uint8_t channel = messageBytes[0] & 0x0f;
kayekss 0:727737138ac5 49 uint8_t noteNumber = messageBytes[1] & 0x7f;
kayekss 0:727737138ac5 50
kayekss 0:727737138ac5 51 // Skip if rhythm channel
kayekss 0:727737138ac5 52 if (channel == 0x09) {
kayekss 0:727737138ac5 53 return;
kayekss 0:727737138ac5 54 }
kayekss 0:727737138ac5 55
kayekss 0:727737138ac5 56 // Find an (oldest) Instrument sounding the specified note number
kayekss 0:727737138ac5 57 for (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
kayekss 0:727737138ac5 58 if (noteSent[i].channel == channel + 1
kayekss 0:727737138ac5 59 && noteSent[i].noteNumber == noteNumber) {
kayekss 0:727737138ac5 60 if (noteSent[i].noteOnMs < oldestMs) {
kayekss 0:727737138ac5 61 oldest = i;
kayekss 0:727737138ac5 62 oldestMs = noteSent[i].noteOnMs;
kayekss 0:727737138ac5 63 }
kayekss 0:727737138ac5 64 }
kayekss 0:727737138ac5 65 }
kayekss 0:727737138ac5 66 if (oldest != 0xffff) {
kayekss 0:727737138ac5 67 noteSent[oldest].noteOnMs = 0;
kayekss 0:727737138ac5 68 noteSent[oldest].channel = 0;
kayekss 0:727737138ac5 69 noteSent[oldest].noteNumber = 0;
kayekss 0:727737138ac5 70 noteSent[oldest].velocity = 0;
kayekss 0:727737138ac5 71
kayekss 0:727737138ac5 72 // Send note off message to Performer
kayekss 0:727737138ac5 73 targetInst = oldest % NUM_INSTRUMENT_IN_PERFORMER;
kayekss 0:727737138ac5 74 gemCore.noteOff(targetInst);
kayekss 0:727737138ac5 75
kayekss 0:727737138ac5 76 if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
kayekss 0:727737138ac5 77 console.printf("%-12s", "Note off");
kayekss 0:727737138ac5 78 dumpInstrumentList();
kayekss 0:727737138ac5 79 }
kayekss 0:727737138ac5 80 } else {
kayekss 0:727737138ac5 81 if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
kayekss 0:727737138ac5 82 console.printf("* No such corresponding note: [ch=%2d n=%02Xh]\r\n",
kayekss 0:727737138ac5 83 channel + 1, noteNumber);
kayekss 0:727737138ac5 84 }
kayekss 0:727737138ac5 85 }
kayekss 0:727737138ac5 86 }
kayekss 0:727737138ac5 87
kayekss 0:727737138ac5 88 void dispatchNoteOn(char messageBytes[3]) {
kayekss 0:727737138ac5 89 uint32_t currentMs = __countMs;
kayekss 0:727737138ac5 90 uint32_t oldestMs = UINT_MAX;
kayekss 0:727737138ac5 91 uint16_t oldest = 0;
kayekss 0:727737138ac5 92 uint16_t targetInst;
kayekss 0:727737138ac5 93 uint8_t channel = messageBytes[0] & 0x0f;
kayekss 0:727737138ac5 94 uint8_t noteNumber = messageBytes[1] & 0x7f;
kayekss 0:727737138ac5 95 uint8_t velocity = messageBytes[2] & 0x7f;
kayekss 0:727737138ac5 96
kayekss 0:727737138ac5 97 // Skip if unimplemented note on rhythm channel
kayekss 0:727737138ac5 98 if (channel == 0x09 && !isImplementedDrumNoteNumber(noteNumber)) {
kayekss 0:727737138ac5 99 return;
kayekss 0:727737138ac5 100 }
kayekss 0:727737138ac5 101
kayekss 0:727737138ac5 102 // Find an available Instrument or the one with the oldest note
kayekss 0:727737138ac5 103 for (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
kayekss 0:727737138ac5 104 if (noteSent[i].noteOnMs < oldestMs) {
kayekss 0:727737138ac5 105 oldest = i;
kayekss 0:727737138ac5 106 oldestMs = noteSent[i].noteOnMs;
kayekss 0:727737138ac5 107 }
kayekss 0:727737138ac5 108 }
kayekss 0:727737138ac5 109
kayekss 0:727737138ac5 110 if (noteSent[oldest].channel != 0) {
kayekss 0:727737138ac5 111 if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
kayekss 0:727737138ac5 112 console.printf("- Purged: #%2d [ch=%2d, n=%02Xh]\r\n",
kayekss 0:727737138ac5 113 oldest, noteSent[oldest].channel,
kayekss 0:727737138ac5 114 noteSent[oldest].noteNumber);
kayekss 0:727737138ac5 115 }
kayekss 0:727737138ac5 116 }
kayekss 0:727737138ac5 117
kayekss 0:727737138ac5 118 noteSent[oldest].noteOnMs = currentMs;
kayekss 0:727737138ac5 119 noteSent[oldest].channel = channel + 1;
kayekss 0:727737138ac5 120 noteSent[oldest].noteNumber = messageBytes[1];
kayekss 0:727737138ac5 121 noteSent[oldest].velocity = velocity & 0x70;
kayekss 0:727737138ac5 122
kayekss 0:727737138ac5 123 // Send note on message to Performer
kayekss 0:727737138ac5 124 targetInst = oldest % NUM_INSTRUMENT_IN_PERFORMER;
kayekss 0:727737138ac5 125 gemCore.volume(targetInst, ch.getVolume(channel));
kayekss 0:727737138ac5 126 gemCore.expression(targetInst, ch.getExpression(channel));
kayekss 0:727737138ac5 127 if (channel == 0x09) {
kayekss 0:727737138ac5 128 Wavetable::wave_t drumWave = { Wavetable::Noise, 4, 0 };
kayekss 0:727737138ac5 129 float drumFreq = 200;
kayekss 0:727737138ac5 130
kayekss 0:727737138ac5 131 switch (noteNumber) {
kayekss 0:727737138ac5 132 case 0x23: // Acoustic Bass Drum
kayekss 0:727737138ac5 133 case 0x24: // Bass Drum 1
kayekss 0:727737138ac5 134 drumWave.decaySpeed = 4;
kayekss 0:727737138ac5 135 drumFreq = 7;
kayekss 0:727737138ac5 136 break;
kayekss 0:727737138ac5 137 case 0x26: // Acoustic Snare
kayekss 0:727737138ac5 138 case 0x28: // Electric Snare
kayekss 0:727737138ac5 139 drumWave.decaySpeed = 4;
kayekss 0:727737138ac5 140 drumFreq = 35;
kayekss 0:727737138ac5 141 break;
kayekss 0:727737138ac5 142 case 0x2a: // Closed High-hat
kayekss 0:727737138ac5 143 case 0x2c: // Pedal High-hat
kayekss 0:727737138ac5 144 drumWave.decaySpeed = 3;
kayekss 0:727737138ac5 145 drumFreq = 200;
kayekss 0:727737138ac5 146 break;
kayekss 0:727737138ac5 147 case 0x2e: // Open High-hat
kayekss 0:727737138ac5 148 drumWave.decaySpeed = 5;
kayekss 0:727737138ac5 149 drumFreq = 400;
kayekss 0:727737138ac5 150 break;
kayekss 0:727737138ac5 151 case 0x31: // Crash Cymbal 1
kayekss 0:727737138ac5 152 case 0x39: // Crash Cymbal 2
kayekss 0:727737138ac5 153 drumWave.decaySpeed = 7;
kayekss 0:727737138ac5 154 drumFreq = 100;
kayekss 0:727737138ac5 155 break;
kayekss 0:727737138ac5 156 case 0x29: // Low Floor Tom
kayekss 0:727737138ac5 157 drumWave.wavetype = Wavetable::Triangle;
kayekss 0:727737138ac5 158 drumWave.decaySpeed = 5;
kayekss 0:727737138ac5 159 drumFreq = 120;
kayekss 0:727737138ac5 160 break;
kayekss 0:727737138ac5 161 case 0x2b: // High Floor Tom
kayekss 0:727737138ac5 162 drumWave.wavetype = Wavetable::Triangle;
kayekss 0:727737138ac5 163 drumWave.decaySpeed = 5;
kayekss 0:727737138ac5 164 drumFreq = 160;
kayekss 0:727737138ac5 165 break;
kayekss 0:727737138ac5 166 case 0x2d: // Low Tom
kayekss 0:727737138ac5 167 drumWave.wavetype = Wavetable::Triangle;
kayekss 0:727737138ac5 168 drumWave.decaySpeed = 5;
kayekss 0:727737138ac5 169 drumFreq = 190;
kayekss 0:727737138ac5 170 break;
kayekss 0:727737138ac5 171 case 0x2f: // Low-Mid Tom
kayekss 0:727737138ac5 172 drumWave.wavetype = Wavetable::Triangle;
kayekss 0:727737138ac5 173 drumWave.decaySpeed = 5;
kayekss 0:727737138ac5 174 drumFreq = 220;
kayekss 0:727737138ac5 175 break;
kayekss 0:727737138ac5 176 case 0x30: // High-Mid Tom
kayekss 0:727737138ac5 177 drumWave.wavetype = Wavetable::Triangle;
kayekss 0:727737138ac5 178 drumWave.decaySpeed = 5;
kayekss 0:727737138ac5 179 drumFreq = 250;
kayekss 0:727737138ac5 180 break;
kayekss 0:727737138ac5 181 case 0x32: // High Tom
kayekss 0:727737138ac5 182 drumWave.wavetype = Wavetable::Triangle;
kayekss 0:727737138ac5 183 drumWave.decaySpeed = 5;
kayekss 0:727737138ac5 184 drumFreq = 280;
kayekss 0:727737138ac5 185 break;
kayekss 0:727737138ac5 186 case 0x25: // Side Stick
kayekss 0:727737138ac5 187 drumWave.decaySpeed = 3;
kayekss 0:727737138ac5 188 drumFreq = 60;
kayekss 0:727737138ac5 189 break;
kayekss 0:727737138ac5 190 default:
kayekss 0:727737138ac5 191 break;
kayekss 0:727737138ac5 192 }
kayekss 0:727737138ac5 193 gemCore.setWave(targetInst, drumWave);
kayekss 0:727737138ac5 194 gemCore.noteOn(targetInst, drumFreq, velocity);
kayekss 0:727737138ac5 195 } else {
kayekss 4:b2423ad4b248 196 // @TODO Pitch bend sensitivity is fixed to 2
kayekss 4:b2423ad4b248 197 float pitchModifier = pow(2.0, 2.0 * ch.getPitchBend(channel) / (12 * 8192));
kayekss 0:727737138ac5 198 gemCore.setWave(targetInst, ch.getWave(channel));
kayekss 4:b2423ad4b248 199 gemCore.noteOn(targetInst,
kayekss 4:b2423ad4b248 200 frequencyTable[noteNumber] * pitchModifier,
kayekss 4:b2423ad4b248 201 velocity);
kayekss 0:727737138ac5 202 }
kayekss 0:727737138ac5 203
kayekss 0:727737138ac5 204 if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
kayekss 0:727737138ac5 205 console.printf("%-12s", "Note on");
kayekss 0:727737138ac5 206 dumpInstrumentList();
kayekss 0:727737138ac5 207 }
kayekss 0:727737138ac5 208 }
kayekss 0:727737138ac5 209
kayekss 0:727737138ac5 210 void sendControlChange(char messageBytes[3]) {
kayekss 0:727737138ac5 211 uint8_t channel = messageBytes[0] & 0x0f;
kayekss 0:727737138ac5 212 uint8_t controlNumber = messageBytes[1];
kayekss 0:727737138ac5 213 uint8_t value = messageBytes[2];
kayekss 0:727737138ac5 214
kayekss 0:727737138ac5 215 switch (controlNumber) {
kayekss 0:727737138ac5 216 case 0x07:
kayekss 0:727737138ac5 217 ch.setVolume(channel, value);
kayekss 0:727737138ac5 218 for (uint8_t i = 0; i < NUM_INSTRUMENT; i++) {
kayekss 0:727737138ac5 219 if (noteSent[i].channel == channel + 1) {
kayekss 0:727737138ac5 220 gemCore.volume(i, value);
kayekss 0:727737138ac5 221 }
kayekss 0:727737138ac5 222 }
kayekss 0:727737138ac5 223 break;
kayekss 0:727737138ac5 224 case 0x0b:
kayekss 0:727737138ac5 225 ch.setExpression(channel, value);
kayekss 0:727737138ac5 226 for (uint8_t i = 0; i < NUM_INSTRUMENT; i++) {
kayekss 0:727737138ac5 227 if (noteSent[i].channel == channel + 1) {
kayekss 0:727737138ac5 228 gemCore.expression(i, value);
kayekss 0:727737138ac5 229 }
kayekss 0:727737138ac5 230 }
kayekss 0:727737138ac5 231 break;
kayekss 0:727737138ac5 232 default:
kayekss 1:5f0c89bffec1 233 // Other program change process goes here
kayekss 0:727737138ac5 234 break;
kayekss 0:727737138ac5 235 }
kayekss 0:727737138ac5 236 }
kayekss 0:727737138ac5 237
kayekss 0:727737138ac5 238 void sendProgramChange(char messageBytes[2]) {
kayekss 0:727737138ac5 239 uint8_t channel = messageBytes[0] & 0x0f;
kayekss 0:727737138ac5 240 uint8_t value = messageBytes[1] & 0x7f;
kayekss 0:727737138ac5 241 Wavetable::wave_t wave = Wavetable::waveDefList[value];
kayekss 0:727737138ac5 242
kayekss 0:727737138ac5 243 // Change channel wave type
kayekss 0:727737138ac5 244 ch.setWave(channel, wave);
kayekss 0:727737138ac5 245 }
kayekss 0:727737138ac5 246
kayekss 0:727737138ac5 247 void sendPitchBend(char messageBytes[3]) {
kayekss 4:b2423ad4b248 248 uint8_t channel = messageBytes[0] & 0x0f;
kayekss 4:b2423ad4b248 249 int16_t value = (((messageBytes[2] & 0x7f) << 7) | (messageBytes[1] & 0x7f)) - 8192;
kayekss 4:b2423ad4b248 250 // @TODO Pitch bend sensitivity is fixed to 2
kayekss 4:b2423ad4b248 251 float pitchModifier = pow(2.0, 2.0 * value / (12 * 8192));
kayekss 4:b2423ad4b248 252
kayekss 4:b2423ad4b248 253 // Register pitch bend value for succeeding notes
kayekss 4:b2423ad4b248 254 ch.setPitchBend(channel, value);
kayekss 4:b2423ad4b248 255
kayekss 4:b2423ad4b248 256 // Modify frequency of all existing notes in selected channel
kayekss 4:b2423ad4b248 257 Instrument* instList = gemCore.getInstrumentList();
kayekss 4:b2423ad4b248 258 for (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
kayekss 4:b2423ad4b248 259 if (noteSent[i].channel == channel + 1) {
kayekss 4:b2423ad4b248 260 instList[i].setFrequency(frequencyTable[noteSent[i].noteNumber]
kayekss 4:b2423ad4b248 261 * pitchModifier);
kayekss 4:b2423ad4b248 262 }
kayekss 4:b2423ad4b248 263 }
kayekss 0:727737138ac5 264 }
kayekss 0:727737138ac5 265
kayekss 0:727737138ac5 266 void allNoteOff(char messageBytes[3]) {
kayekss 0:727737138ac5 267 for (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
kayekss 0:727737138ac5 268 if (noteSent[i].channel == (messageBytes[0] & 0x0f) + 1) {
kayekss 0:727737138ac5 269 noteSent[i].noteOnMs = 0;
kayekss 0:727737138ac5 270 noteSent[i].channel = 0;
kayekss 0:727737138ac5 271 noteSent[i].noteNumber = 0;
kayekss 0:727737138ac5 272 noteSent[i].velocity = 0;
kayekss 0:727737138ac5 273 gemCore.noteOff(i);
kayekss 0:727737138ac5 274 }
kayekss 0:727737138ac5 275 }
kayekss 0:727737138ac5 276
kayekss 0:727737138ac5 277 if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
kayekss 0:727737138ac5 278 console.printf("%-12s", "All note off");
kayekss 0:727737138ac5 279 dumpInstrumentList();
kayekss 0:727737138ac5 280 }
kayekss 0:727737138ac5 281 }
kayekss 0:727737138ac5 282
kayekss 5:7bc917d03bd6 283 void resetAllControllers(char messageBytes[3]) {
kayekss 5:7bc917d03bd6 284 uint8_t channel = messageBytes[0] & 0x0f;
kayekss 5:7bc917d03bd6 285
kayekss 5:7bc917d03bd6 286 for (uint8_t i = 0; i < 16; i++) {
kayekss 5:7bc917d03bd6 287 ch.setVolume(channel, 100);
kayekss 5:7bc917d03bd6 288 ch.setExpression(channel, 127);
kayekss 5:7bc917d03bd6 289 ch.setPitchBend(channel, 0);
kayekss 5:7bc917d03bd6 290 }
kayekss 5:7bc917d03bd6 291
kayekss 5:7bc917d03bd6 292 if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
kayekss 5:7bc917d03bd6 293 console.printf("%-12s", "Reset all ct");
kayekss 5:7bc917d03bd6 294 dumpInstrumentList();
kayekss 5:7bc917d03bd6 295 }
kayekss 5:7bc917d03bd6 296 }
kayekss 5:7bc917d03bd6 297
kayekss 0:727737138ac5 298 void sendSystemReset() {
kayekss 5:7bc917d03bd6 299 // Reset channel controller/programs
kayekss 5:7bc917d03bd6 300 for (uint8_t i = 0; i < 16; i++) {
kayekss 5:7bc917d03bd6 301 ch.setVolume(i, 100);
kayekss 5:7bc917d03bd6 302 ch.setExpression(i, 127);
kayekss 5:7bc917d03bd6 303 ch.setPitchBend(i, 0);
kayekss 5:7bc917d03bd6 304 ch.setWave(i, Wavetable::waveDefList[0]);
kayekss 5:7bc917d03bd6 305 }
kayekss 5:7bc917d03bd6 306 // Initialize all notes
kayekss 0:727737138ac5 307 for (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
kayekss 0:727737138ac5 308 noteSent[i].noteOnMs = 0;
kayekss 0:727737138ac5 309 noteSent[i].channel = 0;
kayekss 0:727737138ac5 310 noteSent[i].noteNumber = 0;
kayekss 0:727737138ac5 311 noteSent[i].velocity = 0;
kayekss 0:727737138ac5 312 }
kayekss 0:727737138ac5 313
kayekss 0:727737138ac5 314 if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
kayekss 0:727737138ac5 315 console.printf("%-12s", "System reset");
kayekss 0:727737138ac5 316 dumpInstrumentList();
kayekss 0:727737138ac5 317 }
kayekss 5:7bc917d03bd6 318 for (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
kayekss 0:727737138ac5 319 gemCore.noteOff(i);
kayekss 0:727737138ac5 320 gemCore.volume(i, 100);
kayekss 0:727737138ac5 321 gemCore.expression(i, 127);
kayekss 0:727737138ac5 322 }
kayekss 0:727737138ac5 323 }