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

Dependencies:   ClockControl PowerControl mbed

Revision:
0:727737138ac5
Child:
1:5f0c89bffec1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/events.cpp	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,281 @@
+#include <climits>
+#include "mbed.h"
+#include "defs.h"
+#include "Channels.h"
+#include "GeminiCore.h"
+#include "Wavetable.h"
+#include "debug.h"
+#include "events.h"
+#include "frequency.h"
+#include "note.h"
+
+extern Serial            console;
+extern volatile uint32_t __countMs;
+extern GeminiCore        gemCore;
+extern Channels          ch;
+extern note_t            noteSent[NUM_INSTRUMENT];
+extern float const       frequencyTable[128];
+extern dumpmode_t        dumpMode;
+
+bool isImplementedDrumNoteNumber(uint8_t noteNumber) {
+    switch (noteNumber) {
+    case 0x23:  // Acoustic Bass Drum
+    case 0x24:  // Bass Drum 1
+    case 0x25:  // Side Stick
+    case 0x26:  // Acoustic Snare
+    case 0x28:  // Electric Snare
+    case 0x29:  // Low Floor Tom
+    case 0x2a:  // Closed High-hat
+    case 0x2b:  // High Floor Tom
+    case 0x2c:  // Pedal High-hat
+    case 0x2d:  // Low Tom
+    case 0x2e:  // Open High-hat
+    case 0x2f:  // Low-Mid Tom
+    case 0x30:  // High-Mid Tom
+    case 0x31:  // Crash Cymbal 1
+    case 0x32:  // High Tom
+    case 0x39:  // Crash Cymbal 2
+        return true;
+    default:
+        return false;
+    }
+}
+
+void dispatchNoteOff(char messageBytes[3]) {
+    uint16_t targetInst;
+    uint16_t oldest = 0xffff;
+    uint32_t oldestMs = UINT_MAX;
+    uint8_t channel = messageBytes[0] & 0x0f;
+    uint8_t noteNumber = messageBytes[1] & 0x7f;
+    
+    // Skip if rhythm channel
+    if (channel == 0x09) {
+        return;
+    }
+    
+    // Find an (oldest) Instrument sounding the specified note number
+    for (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
+        if (noteSent[i].channel == channel + 1
+            && noteSent[i].noteNumber == noteNumber) {
+            if (noteSent[i].noteOnMs < oldestMs) {
+                oldest = i;
+                oldestMs = noteSent[i].noteOnMs;
+            }
+        }
+    }
+    if (oldest != 0xffff) {
+        noteSent[oldest].noteOnMs = 0;
+        noteSent[oldest].channel = 0;
+        noteSent[oldest].noteNumber = 0;
+        noteSent[oldest].velocity = 0;
+        
+        // Send note off message to Performer
+        targetInst = oldest % NUM_INSTRUMENT_IN_PERFORMER;
+        gemCore.noteOff(targetInst);
+        
+        if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
+            console.printf("%-12s", "Note off");
+            dumpInstrumentList();
+        }
+    } else {
+        if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
+            console.printf("* No such corresponding note: [ch=%2d n=%02Xh]\r\n",
+                           channel + 1, noteNumber);
+        }
+    }
+}
+
+void dispatchNoteOn(char messageBytes[3]) {
+    uint32_t currentMs = __countMs;
+    uint32_t oldestMs = UINT_MAX;
+    uint16_t oldest = 0;
+    uint16_t targetInst;
+    uint8_t channel = messageBytes[0] & 0x0f;
+    uint8_t noteNumber = messageBytes[1] & 0x7f;
+    uint8_t velocity = messageBytes[2] & 0x7f;
+    
+    // Skip if unimplemented note on rhythm channel
+    if (channel == 0x09 && !isImplementedDrumNoteNumber(noteNumber)) {
+        return;
+    }
+    
+    // Find an available Instrument or the one with the oldest note
+    for (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
+        if (noteSent[i].noteOnMs < oldestMs) {
+            oldest = i;
+            oldestMs = noteSent[i].noteOnMs;
+        }
+    }
+    
+    if (noteSent[oldest].channel != 0) {
+        if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
+            console.printf("- Purged: #%2d [ch=%2d, n=%02Xh]\r\n",
+                           oldest, noteSent[oldest].channel,
+                           noteSent[oldest].noteNumber);
+        }
+    }
+    
+    noteSent[oldest].noteOnMs = currentMs;
+    noteSent[oldest].channel = channel + 1;
+    noteSent[oldest].noteNumber = messageBytes[1];
+    noteSent[oldest].velocity = velocity & 0x70;
+    
+    // Send note on message to Performer
+    targetInst = oldest % NUM_INSTRUMENT_IN_PERFORMER;
+    gemCore.volume(targetInst, ch.getVolume(channel));
+    gemCore.expression(targetInst, ch.getExpression(channel));
+    if (channel == 0x09) {
+        Wavetable::wave_t drumWave = { Wavetable::Noise, 4, 0 };
+        float drumFreq = 200;
+        
+        switch (noteNumber) {
+        case 0x23:  // Acoustic Bass Drum
+        case 0x24:  // Bass Drum 1
+            drumWave.decaySpeed = 4;
+            drumFreq = 7;
+            break;
+        case 0x26:  // Acoustic Snare
+        case 0x28:  // Electric Snare
+            drumWave.decaySpeed = 4;
+            drumFreq = 35;
+            break;
+        case 0x2a:  // Closed High-hat
+        case 0x2c:  // Pedal High-hat
+            drumWave.decaySpeed = 3;
+            drumFreq = 200;
+            break;
+        case 0x2e:  // Open High-hat
+            drumWave.decaySpeed = 5;
+            drumFreq = 400;
+            break;
+        case 0x31:  // Crash Cymbal 1
+        case 0x39:  // Crash Cymbal 2
+            drumWave.decaySpeed = 7;
+            drumFreq = 100;
+            break;
+        case 0x29:  // Low Floor Tom
+            drumWave.wavetype = Wavetable::Triangle;
+            drumWave.decaySpeed = 5;
+            drumFreq = 120;
+            break;
+        case 0x2b:  // High Floor Tom
+            drumWave.wavetype = Wavetable::Triangle;
+            drumWave.decaySpeed = 5;
+            drumFreq = 160;
+            break;
+        case 0x2d:  // Low Tom
+            drumWave.wavetype = Wavetable::Triangle;
+            drumWave.decaySpeed = 5;
+            drumFreq = 190;
+            break;
+        case 0x2f:  // Low-Mid Tom
+            drumWave.wavetype = Wavetable::Triangle;
+            drumWave.decaySpeed = 5;
+            drumFreq = 220;
+            break;
+        case 0x30:  // High-Mid Tom
+            drumWave.wavetype = Wavetable::Triangle;
+            drumWave.decaySpeed = 5;
+            drumFreq = 250;
+            break;
+        case 0x32:  // High Tom
+            drumWave.wavetype = Wavetable::Triangle;
+            drumWave.decaySpeed = 5;
+            drumFreq = 280;
+            break;
+        case 0x25:  // Side Stick
+            drumWave.decaySpeed = 3;
+            drumFreq = 60;
+            break;
+        default:
+            break;
+        }
+        gemCore.setWave(targetInst, drumWave);
+        gemCore.noteOn(targetInst, drumFreq, velocity);
+    } else {
+        gemCore.setWave(targetInst, ch.getWave(channel));
+        gemCore.noteOn(targetInst, frequencyTable[noteNumber], velocity);
+    }
+    
+    if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
+        console.printf("%-12s", "Note on");
+        dumpInstrumentList();
+    }
+}
+
+void sendControlChange(char messageBytes[3]) {
+    uint8_t channel = messageBytes[0] & 0x0f;
+    uint8_t controlNumber = messageBytes[1];
+    uint8_t value = messageBytes[2];
+    
+    switch (controlNumber) {
+    case 0x07:
+        ch.setVolume(channel, value);
+        for (uint8_t i = 0; i < NUM_INSTRUMENT; i++) {
+            if (noteSent[i].channel == channel + 1) {
+                gemCore.volume(i, value);
+            }
+        }
+        break;
+    case 0x0b:
+        ch.setExpression(channel, value);
+        for (uint8_t i = 0; i < NUM_INSTRUMENT; i++) {
+            if (noteSent[i].channel == channel + 1) {
+                gemCore.expression(i, value);
+            }
+        }
+        break;
+    default:
+        // @TODO Other program change process goes here
+        break;
+    }
+}
+
+void sendProgramChange(char messageBytes[2]) {
+    uint8_t channel = messageBytes[0] & 0x0f;
+    uint8_t value = messageBytes[1] & 0x7f;
+    Wavetable::wave_t wave = Wavetable::waveDefList[value];
+    
+    // Change channel wave type
+    ch.setWave(channel, wave);
+}
+
+void sendPitchBend(char messageBytes[3]) {
+    // @TODO
+}
+
+void allNoteOff(char messageBytes[3]) {
+    for (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
+        if (noteSent[i].channel == (messageBytes[0] & 0x0f) + 1) {
+            noteSent[i].noteOnMs = 0;
+            noteSent[i].channel = 0;
+            noteSent[i].noteNumber = 0;
+            noteSent[i].velocity = 0;
+            gemCore.noteOff(i);
+        }
+    }
+    
+    if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
+        console.printf("%-12s", "All note off");
+        dumpInstrumentList();
+    }
+}
+
+void sendSystemReset() {
+    for (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
+        noteSent[i].noteOnMs = 0;
+        noteSent[i].channel = 0;
+        noteSent[i].noteNumber = 0;
+        noteSent[i].velocity = 0;
+    }
+    
+    if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
+        console.printf("%-12s", "System reset");
+        dumpInstrumentList();
+    }
+    for (uint8_t i = 0; i < NUM_INSTRUMENT; i++) {
+        gemCore.noteOff(i);
+        gemCore.volume(i, 100);
+        gemCore.expression(i, 127);
+    }
+}