Kaoru Onoe / Mbed 2 deprecated Gemini_Soloist_LPC1768

Dependencies:   ClockControl PowerControl mbed

Files at this revision

API Documentation at this revision

Comitter:
kayekss
Date:
Sun Nov 09 08:00:33 2014 +0000
Child:
1:5f0c89bffec1
Commit message:
12-polyphonic "chiptune" MIDI synthesizer for mbed LPC1768 (Standalone version)

Changed in this revision

Channels.cpp Show annotated file Show diff for this revision Revisions of this file
Channels.h Show annotated file Show diff for this revision Revisions of this file
ClockControl.lib Show annotated file Show diff for this revision Revisions of this file
GeminiCore.cpp Show annotated file Show diff for this revision Revisions of this file
GeminiCore.h Show annotated file Show diff for this revision Revisions of this file
Instrument.cpp Show annotated file Show diff for this revision Revisions of this file
Instrument.h Show annotated file Show diff for this revision Revisions of this file
PowerControl.lib Show annotated file Show diff for this revision Revisions of this file
RingBuffer.h Show annotated file Show diff for this revision Revisions of this file
Wavetable.cpp Show annotated file Show diff for this revision Revisions of this file
Wavetable.h Show annotated file Show diff for this revision Revisions of this file
debug.cpp Show annotated file Show diff for this revision Revisions of this file
debug.h Show annotated file Show diff for this revision Revisions of this file
defs.h Show annotated file Show diff for this revision Revisions of this file
events.cpp Show annotated file Show diff for this revision Revisions of this file
events.h Show annotated file Show diff for this revision Revisions of this file
frequency.h Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
note.h Show annotated file Show diff for this revision Revisions of this file
parser.cpp Show annotated file Show diff for this revision Revisions of this file
parser.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Channels.cpp	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,60 @@
+#include "defs.h"
+#include "Channels.h"
+
+/** Constructor of class Channels */
+Channels::Channels() {
+}
+
+/** Destructor of class Channels */
+Channels::~Channels() {
+}
+
+/** Initialize all 16 channels */
+void Channels::initializeAll() {
+    for (uint8_t i = 0; i < 16; i++) {
+        this->volume[i] = 100;
+        this->expression[i] = 127;
+        this->pitchBend[i] = 0;
+        this->wave[i] = Wavetable::waveDefList[4];
+    }
+}
+
+/** Set volume to channel #i (i: zero origin) */
+void Channels::setVolume(uint8_t i, uint8_t vo) {
+    this->volume[i] = vo;
+}
+
+/** Set expression to channel #i (i: zero origin) */
+void Channels::setExpression(uint8_t i, uint8_t ex) {
+    this->expression[i] = ex;
+}
+
+/** Set pitch bend to channel #i (i: zero origin) */
+void Channels::setPitchBend(uint8_t i, int16_t pb) {
+    this->pitchBend[i] = pb;
+}
+
+/** Set wave parameters of channel #i (i: zero origin) */
+void Channels::setWave(uint8_t i, Wavetable::wave_t w) {
+    this->wave[i] = w;
+}
+
+/** Get volume of channel #i (i: zero origin) */
+uint8_t Channels::getVolume(uint8_t i) {
+    return this->volume[i];
+}
+
+/** Get expression of channel #i (i: zero origin) */
+uint8_t Channels::getExpression(uint8_t i) {
+    return this->expression[i];
+}
+
+/** Get pitch bend of channel #i (i: zero origin) */
+int16_t Channels::getPitchBend(uint8_t i) {
+    return this->pitchBend[i];
+}
+
+/** Get wave parameters of channel #i (i: zero origin) */
+Wavetable::wave_t Channels::getWave(uint8_t i) {
+    return this->wave[i];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Channels.h	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,29 @@
+#ifndef CHANNELS_H_
+#define CHANNELS_H_
+
+#include <stdint.h>
+#include "Wavetable.h"
+
+class Channels {
+private:
+    uint8_t volume[16];
+    uint8_t expression[16];
+    int16_t pitchBend[16];
+    Wavetable::wave_t wave[16];
+
+public:
+    Channels();
+    ~Channels();
+    void initializeAll();
+    void setVolume(uint8_t, uint8_t);
+    void setExpression(uint8_t, uint8_t);
+    void setPitchBend(uint8_t, int16_t);
+    void setWave(uint8_t, Wavetable::wave_t);
+    
+    uint8_t getVolume(uint8_t);
+    uint8_t getExpression(uint8_t);
+    int16_t getPitchBend(uint8_t);
+    Wavetable::wave_t getWave(uint8_t);
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ClockControl.lib	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/JST2011/code/ClockControl/#a6d100de3aee
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeminiCore.cpp	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,229 @@
+#include "mbed.h"
+#include "GeminiCore.h"
+
+uint16_t const GeminiCore::samplingRate = 32000u;
+
+/** Constructor of class GeminiCore */
+GeminiCore::GeminiCore(uint8_t numInstruments) {
+    this->numInstruments = numInstruments;
+    
+    // Instantiate instrument list
+    this->instrumentList = new Instrument[numInstruments];
+    for (uint8_t i = 0; i < numInstruments; i++) {
+        this->instrumentList[i].enable();
+        this->instrumentList[i].setSamplingRate(GeminiCore::samplingRate);
+        this->instrumentList[i].setWave(Wavetable::waveDefList[4]);
+    }
+    
+    // Allocate bytes to preserve previous samples
+    prevSample = new uint8_t[numInstruments];
+    
+    // Initialize random seeds
+    this->x8 = 77u;
+    this->x16 = 5501u;
+}
+
+/** Destructor of class GeminiCore */
+GeminiCore::~GeminiCore() {
+    delete[] this->instrumentList;
+    delete[] this->prevSample;
+}
+
+/** Make a sample - should be called periodically depending on the sampling rate */
+uint16_t GeminiCore::makeSample() {
+    Instrument* iThInst;
+    Wavetable::wave_t iThWave;
+    int32_t     sample;
+    int32_t     iThSample;
+    uint16_t    phasePrev;
+    uint32_t    duration;
+    uint8_t     decayRatio;
+    
+    sample = 0;
+    for (uint8_t i = 0; i < numInstruments; i++) {
+        iThInst = &instrumentList[i];
+        iThWave = iThInst->getWave();
+        
+        // Do nothing if the instrument is disabled or not sounding
+        if (!iThInst->isEnable() || iThInst->getFrequency() == 0) {
+            continue;
+        }
+        
+        duration = iThInst->getDuration();
+        if (duration >> iThWave.decaySpeed >= 255 - iThWave.sustainLevel) {
+            decayRatio = iThWave.sustainLevel;
+        } else {
+            decayRatio = 256 - (duration >> iThWave.decaySpeed);
+        }
+        
+        switch (iThWave.wavetype) {
+        case Wavetable::Noise:
+            phasePrev = iThInst->getPhase();
+            iThInst->advancePhase();
+            
+            // If necessary, generate new random value
+            if ((iThInst->getPhase() >> 8) - (phasePrev >> 8) > 0) {
+                prevSample[i] = rand16() >> 8;
+            }
+            iThSample = prevSample[i];
+            break;
+        case Wavetable::LowPeriodNoise:
+            phasePrev = iThInst->getPhase();
+            iThInst->advancePhase();
+            
+            // If necessary, generate new random value
+            if ((iThInst->getPhase() >> 8) - (phasePrev >> 8) > 0) {
+                prevSample[i] = rand8();
+            }
+            iThSample = prevSample[i];
+            break;
+        default:
+            iThInst->advancePhase();
+            iThSample = Wavetable::waveTableList[iThWave.wavetype][iThInst->getPhase() >> 8] - 128;
+        }
+        sample += iThSample * iThInst->getMasterVolume() * decayRatio;
+    }
+    sample >>= 10;
+    if (sample >  32767) sample =  32767;
+    if (sample < -32768) sample = -32768;
+    return (uint16_t) (sample + 32768);
+}
+
+/** Enable i-th Instrument */
+bool GeminiCore::enable(uint8_t i) {
+    Instrument* iThInst = NULL;
+    
+    if (i >= numInstruments || instrumentList == NULL) {
+        return false;
+    }
+    iThInst = &instrumentList[i];
+    if (iThInst == NULL) {
+        return false;
+    }
+    
+    iThInst->enable();
+    return true;
+}
+
+/** Disable i-th Instrument */
+bool GeminiCore::disable(uint8_t i) {
+    Instrument* iThInst = NULL;
+    
+    if (i >= numInstruments || instrumentList == NULL) {
+        return false;
+    }
+    iThInst = &instrumentList[i];
+    if (iThInst == NULL) {
+        return false;
+    }
+    
+    iThInst->disable();
+    return true;
+}
+
+/** Note on i-th Instrument */
+bool GeminiCore::noteOn(uint8_t i, uint16_t frequency, uint8_t velocity) {
+    Instrument* iThInst = NULL;
+    
+    if (i >= numInstruments || instrumentList == NULL) {
+        return false;
+    }
+    iThInst = &instrumentList[i];
+    if (iThInst == NULL) {
+        return false;
+    }
+    
+    iThInst->setFrequency(frequency);  // @TODO pitch bend correction needed
+    iThInst->setVelocity(velocity);
+    return true;
+}
+
+/** Note off i-th Instrument */
+bool GeminiCore::noteOff(uint8_t i) {
+    Instrument* iThInst = NULL;
+    
+    if (i >= numInstruments || instrumentList == NULL) {
+        return false;
+    }
+    iThInst = &instrumentList[i];
+    if (iThInst == NULL) {
+        return false;
+    }
+    
+    iThInst->setFrequency(0);
+    return true;
+}
+
+/** Set volume (0..127) to i-th Instrument */
+bool GeminiCore::volume(uint8_t i, uint8_t vo) {
+    Instrument* iThInst = NULL;
+    
+    if (i >= numInstruments || instrumentList == NULL) {
+        return false;
+    }
+    iThInst = &instrumentList[i];
+    if (iThInst == NULL) {
+        return false;
+    }
+    
+    iThInst->setVolume(vo << 1);  // 0..127 => 0..254
+    return true;
+}
+
+/** Set expression (0..127) to i-th Instrument */
+bool GeminiCore::expression(uint8_t i, uint8_t ex) {
+    Instrument* iThInst = NULL;
+    
+    if (i >= numInstruments || instrumentList == NULL) {
+        return false;
+    }
+    iThInst = &instrumentList[i];
+    if (iThInst == NULL) {
+        return false;
+    }
+    
+    iThInst->setExpression(ex << 1);  // 0..127 => 0..254
+    return true;
+}
+
+bool GeminiCore::pitchBend(uint8_t i, int16_t pb) {
+    // @TODO
+    return true;
+}
+
+/** Set wave parameters to i-th Instrument */
+bool GeminiCore::setWave(uint8_t i, Wavetable::wave_t w) {
+    Instrument* iThInst = NULL;
+    
+    if (i >= numInstruments || instrumentList == NULL) {
+        return false;
+    }
+    iThInst = &instrumentList[i];
+    if (iThInst == NULL) {
+        return false;
+    }
+    
+    iThInst->setWave(w);
+    return true;
+}
+
+/** Get instrument list */
+Instrument* GeminiCore::getInstrumentList() {
+    return this->instrumentList;
+}
+
+/** Generate an 8-bit random number */
+uint8_t GeminiCore::rand8() {
+    x8 ^= x8 << 7;
+    x8 ^= x8 >> 5;
+    x8 ^= x8 << 3;
+    return x8;
+}
+
+/** Generate a 16-bit random number */
+uint16_t GeminiCore::rand16() {
+    x16 ^= x16 << 13;
+    x16 ^= x16 >> 9;
+    x16 ^= x16 << 7;
+    return x16;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeminiCore.h	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,38 @@
+#ifndef GEMINICORE_H_
+#define GEMINICORE_H_
+
+#include <stdint.h>
+#include "Instrument.h"
+
+class GeminiCore {
+private:
+    Instrument* instrumentList;
+    uint8_t     numInstruments;
+    uint8_t*    prevSample;      // previous sample value (used for noise samples)
+    uint8_t     x8;              // 8-bit random seed
+    uint16_t    x16;             // 16-bit random seed
+    
+public:
+    static uint16_t const samplingRate;
+    
+    GeminiCore(uint8_t);
+    ~GeminiCore();
+    uint16_t makeSample();
+    bool enable(uint8_t);
+    bool disable(uint8_t);
+    bool noteOn(uint8_t, uint16_t, uint8_t);
+    bool noteOff(uint8_t);
+    bool volume(uint8_t, uint8_t);
+    bool expression(uint8_t, uint8_t);
+    bool pitchBend(uint8_t, int16_t);
+    bool setWave(uint8_t, Wavetable::wave_t);
+    
+    Instrument* getInstrumentList();
+    uint32_t getPlaybackStartTime();
+
+private:
+    uint8_t rand8();
+    uint16_t rand16();
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Instrument.cpp	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,157 @@
+#include <climits>
+#include "defs.h"
+#include "Instrument.h"
+
+/** Constructor of class Instrument */
+Instrument::Instrument() {
+    this->enableFlag = false;
+    this->soundingPosition = -1;
+    this->duration = 0;
+    this->frequency = 0;
+    this->volume = 100;
+    this->expression = 127;
+    this->velocity = 0;
+    this->calculatePhaseDelta();
+    this->calculateMasterVolume();
+}
+
+/** Destructor of class Instrument */
+Instrument::~Instrument() {
+}
+
+/** Enable instrument */
+void Instrument::enable() {
+    this->enableFlag = true;
+}
+
+/** Disable instrument */
+void Instrument::disable() {
+    this->enableFlag = false;
+}
+
+/** Set sampling rate */
+void Instrument::setSamplingRate(uint16_t fsamp) {
+    this->samplingRate = fsamp;
+}
+
+/** Advance wave phase by one sample */
+void Instrument::advancePhase() {
+    this->phase += this->phaseDelta;
+}
+
+/** Reset wave phase to zero */
+void Instrument::resetPhase() {
+    this->phase = 0;
+}
+
+/** Set wave parameters */
+void Instrument::setWave(Wavetable::wave_t w) {
+    this->wave = w;
+    this->phase = 0;
+}
+
+/** Set wave frequency */
+void Instrument::setFrequency(uint16_t f) {
+    this->frequency = f;
+    this->duration = 0;     // Reset duration
+    calculatePhaseDelta();  // Recalculate phase delta
+}
+
+/** Set volume */
+void Instrument::setVolume(uint8_t vo) {
+    this->volume = vo;
+    calculateMasterVolume();
+}
+
+/** Set expression */
+void Instrument::setExpression(uint8_t ex) {
+    this->expression = ex;
+    calculateMasterVolume();
+}
+
+/** Set note velocity */
+void Instrument::setVelocity(uint8_t ve) {
+    this->velocity = ve;
+    calculateMasterVolume();
+}
+
+/** Get sampling rate */
+uint16_t Instrument::getSamplingRate() {
+    return this->samplingRate;
+}
+
+/** Get channel ID */
+uint8_t Instrument::getChannelId() {
+    return this->channelId;
+}
+
+/** Get enable flag */
+bool Instrument::isEnable() {
+    return this->enableFlag;
+}
+
+/** Get sounding note position */
+int16_t Instrument::getSoundingPosition() {
+    return this->soundingPosition;
+}
+
+/** Get sounding duration */
+uint32_t Instrument::getDuration() {
+    if (this->duration < UINT_MAX) {
+        this->duration++;
+    }
+    return this->duration;
+}
+
+/** Get wave parameters */
+Wavetable::wave_t Instrument::getWave() {
+    return this->wave;
+}
+
+/** Get wave frequency */
+uint16_t Instrument::getFrequency() {
+    return this->frequency;
+}
+
+/** Get master volume */
+uint8_t Instrument::getMasterVolume() {
+    return this->masterVolume;
+}
+
+/** Get volume */
+uint8_t Instrument::getVolume() {
+    return this->volume;
+}
+
+/** Get expression */
+uint8_t Instrument::getExpression() {
+    return this->expression;
+}
+
+/** Get note velocity */
+uint8_t Instrument::getVelocity() {
+    return this->velocity;
+}
+
+/** Get current phase */
+uint16_t Instrument::getPhase() {
+    return this->phase;
+}
+
+/** Get phase delta per sample */
+uint16_t Instrument::getPhaseDelta() {
+    return this->phaseDelta;
+}
+
+/** (Re)calculate phase delta per sample */
+void Instrument::calculatePhaseDelta() {
+    phase = 0x0000;
+    phaseDelta = (uint16_t) ((uint32_t) frequency * WAVETABLE_LENGTH * 256
+                             / samplingRate);
+}
+
+/** Calculate master volume from product of its volume, expression, and velocity */
+void Instrument::calculateMasterVolume() {
+    this->masterVolume
+        = ((uint32_t) this->volume * this->expression * this->velocity) >> 16;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Instrument.h	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,57 @@
+#ifndef INSTRUMENT_H_
+#define INSTRUMENT_H_
+
+#include <stdint.h>
+#include "Wavetable.h"
+
+class Instrument {
+private:
+    uint16_t          samplingRate;      // sampling rate
+    uint8_t           channelId;         // channel ID
+    bool              enableFlag;        // enable flag (whether this instrument is enabled)
+    int32_t           soundingPosition;  // currently sounding note position
+    uint32_t          duration;          // sounding duration
+    Wavetable::wave_t wave;          // wave parameters
+    uint16_t          frequency;         // wave frequency
+    uint8_t           masterVolume;      // + master volume
+    uint8_t           volume;            //    |- volume
+    uint8_t           expression;        //    |- expression    (volume multiplier)
+    uint8_t           velocity;          //    |- note velocity (volume multiplier)
+    uint16_t          phase;             // current phase          (x256)
+    uint16_t          phaseDelta;        // phase delta per sample (x256)
+    
+public:
+    Instrument();
+    ~Instrument();
+
+    void enable();
+    void disable();
+    void setSamplingRate(uint16_t);
+    void advancePhase();
+    void resetPhase();
+    void setWave(Wavetable::wave_t);
+    void setFrequency(uint16_t);
+    void setVolume(uint8_t);
+    void setExpression(uint8_t);
+    void setVelocity(uint8_t);
+    
+    uint16_t getSamplingRate();
+    uint8_t getChannelId();
+    bool isEnable();
+    int16_t getSoundingPosition();
+    uint32_t getDuration();
+    Wavetable::wave_t getWave();
+    uint16_t getFrequency();
+    uint8_t getMasterVolume();
+    uint8_t getVolume();
+    uint8_t getExpression();
+    uint8_t getVelocity();
+    uint16_t getPhase();
+    uint16_t getPhaseDelta();
+    
+private:
+    void calculatePhaseDelta();
+    void calculateMasterVolume();
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PowerControl.lib	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/JST2011/code/PowerControl/#d0fa2aeb02a4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RingBuffer.h	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,114 @@
+#ifndef RINGBUFFER_H_
+#define RINGBUFFER_H_
+
+#include <stdint.h>
+
+template <class T>
+class RingBuffer {
+private:
+    uint32_t readPos;
+    uint32_t writePos;
+    uint32_t itemCount;
+    uint32_t length;
+    T* buffer;
+    
+public:
+    RingBuffer(uint32_t);
+    ~RingBuffer();
+    bool isWritable();
+    bool isReadable();
+    bool write(T&);
+    T* read();
+    T* peek();
+    void flush();
+    int find(T);
+    
+    uint32_t getItemCount();
+};
+
+template <class T>
+RingBuffer<T>::RingBuffer(uint32_t len) {
+    length = len;
+    buffer = new T[length];
+    readPos = 0;
+    writePos = 0;
+    itemCount = 0;
+}
+
+template <class T>
+RingBuffer<T>::~RingBuffer() {
+    delete[] buffer;
+}
+
+template <class T>
+bool RingBuffer<T>::isWritable() {
+    return itemCount < length;
+}
+
+template <class T>
+bool RingBuffer<T>::isReadable() {
+    return itemCount > 0;
+}
+
+template <class T>
+bool RingBuffer<T>::write(T& n) {
+    if (!isWritable()) {
+        return false;
+    }
+    buffer[writePos++] = n;
+    if (writePos == length) {
+        writePos = 0;
+    }
+    itemCount++;
+    return true;
+}
+
+template <class T>
+T* RingBuffer<T>::read() {
+    uint32_t readPosTemp = readPos;
+    
+    if (!isReadable()) {
+        return NULL;
+    }
+    readPos++;
+    if (readPos == length) {
+        readPos = 0;
+    }
+    itemCount--;
+    return &buffer[readPosTemp];
+}
+
+template <class T>
+T* RingBuffer<T>::peek() {
+    if (!isReadable()) {
+        return NULL;
+    }
+    return &buffer[readPos];
+}
+
+template <class T>
+void RingBuffer<T>::flush() {
+    itemCount = 0;
+    readPos = writePos;
+}
+
+template <class T>
+int RingBuffer<T>::find(T key) {
+    uint32_t p = readPos;
+    
+    for (uint32_t i = 0; i < itemCount; i++) {
+        if (buffer[p] == key) {
+            return i;
+        }
+        p++;
+        if (p == length) p = 0;
+    }
+    return -1;
+}
+
+template <class T>
+uint32_t RingBuffer<T>::getItemCount() {
+    return this->itemCount;
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Wavetable.cpp	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,466 @@
+#include "mbed.h"
+#include "Wavetable.h"
+
+const uint8_t* const Wavetable::waveTableList[] = {
+    Wavetable::sineTable,
+    Wavetable::squareTable,
+    Wavetable::pulse1_3Table,
+    Wavetable::pulse1_7Table,
+    Wavetable::sawtoothTable,
+    Wavetable::triangleTable,
+    Wavetable::coarseTriangleTable,
+    Wavetable::spikedSineTable,
+    NULL,
+    NULL
+};
+
+Wavetable::wave_t const Wavetable::waveDefList[128] = {
+    // 00h..07h  Piano
+    { Wavetable::Sine, 8, 255 },
+    { Wavetable::Sine, 8, 128 },
+    { Wavetable::Sine, 8,  32 },
+    { Wavetable::Sine, 8,   8 },
+    { Wavetable::Sine, 7,   8 },
+    { Wavetable::Sine, 6,   8 },
+    { Wavetable::Sine, 5,   8 },
+    { Wavetable::Sine, 5,   0 },
+    // 08h..0Fh  Chromatic Percussion
+    { Wavetable::Square, 8, 255 },
+    { Wavetable::Square, 8, 128 },
+    { Wavetable::Square, 8,  32 },
+    { Wavetable::Square, 8,   8 },
+    { Wavetable::Square, 7,   8 },
+    { Wavetable::Square, 6,   8 },
+    { Wavetable::Square, 5,   8 },
+    { Wavetable::Square, 5,   0 },
+    // 10h..17h  Organ
+    { Wavetable::Pulse1_3, 8, 255 },
+    { Wavetable::Pulse1_3, 8, 128 },
+    { Wavetable::Pulse1_3, 8,  32 },
+    { Wavetable::Pulse1_3, 8,   8 },
+    { Wavetable::Pulse1_3, 7,   8 },
+    { Wavetable::Pulse1_3, 6,   8 },
+    { Wavetable::Pulse1_3, 5,   8 },
+    { Wavetable::Pulse1_3, 5,   0 },
+    // 18h..1Fh  Guitar
+    { Wavetable::Pulse1_7, 8, 255 },
+    { Wavetable::Pulse1_7, 8, 128 },
+    { Wavetable::Pulse1_7, 8,  32 },
+    { Wavetable::Pulse1_7, 8,   8 },
+    { Wavetable::Pulse1_7, 7,   8 },
+    { Wavetable::Pulse1_7, 6,   8 },
+    { Wavetable::Pulse1_7, 5,   8 },
+    { Wavetable::Pulse1_7, 5,   0 },
+    // 20h..27h  Bass
+    { Wavetable::Sawtooth, 8, 255 },
+    { Wavetable::Sawtooth, 8, 128 },
+    { Wavetable::Sawtooth, 8,  32 },
+    { Wavetable::Sawtooth, 8,   8 },
+    { Wavetable::Sawtooth, 7,   8 },
+    { Wavetable::Sawtooth, 6,   8 },
+    { Wavetable::Sawtooth, 5,   8 },
+    { Wavetable::Sawtooth, 5,   0 },
+    // 28h..2Fh  Strings
+    { Wavetable::Triangle, 8, 255 },
+    { Wavetable::Triangle, 8, 128 },
+    { Wavetable::Triangle, 8,  32 },
+    { Wavetable::Triangle, 8,   8 },
+    { Wavetable::Triangle, 7,   8 },
+    { Wavetable::Triangle, 6,   8 },
+    { Wavetable::Triangle, 5,   8 },
+    { Wavetable::Triangle, 5,   0 },
+    // 30h..37h  Emsemble
+    { Wavetable::CoarseTriangle, 8, 255 },
+    { Wavetable::CoarseTriangle, 8, 128 },
+    { Wavetable::CoarseTriangle, 8,  32 },
+    { Wavetable::CoarseTriangle, 8,   8 },
+    { Wavetable::CoarseTriangle, 7,   8 },
+    { Wavetable::CoarseTriangle, 6,   8 },
+    { Wavetable::CoarseTriangle, 5,   8 },
+    { Wavetable::CoarseTriangle, 5,   0 },
+    // 38h..3Fh  Brass
+    { Wavetable::SpikedSine, 8, 255 },
+    { Wavetable::SpikedSine, 8, 128 },
+    { Wavetable::SpikedSine, 8,  32 },
+    { Wavetable::SpikedSine, 8,   8 },
+    { Wavetable::SpikedSine, 7,   8 },
+    { Wavetable::SpikedSine, 6,   8 },
+    { Wavetable::SpikedSine, 5,   8 },
+    { Wavetable::SpikedSine, 5,   0 },
+    // 40h..47h  Reed
+    { Wavetable::Noise, 8, 255 },
+    { Wavetable::Noise, 8, 128 },
+    { Wavetable::Noise, 8,  32 },
+    { Wavetable::Noise, 8,   8 },
+    { Wavetable::Noise, 7,   8 },
+    { Wavetable::Noise, 6,   8 },
+    { Wavetable::Noise, 5,   8 },
+    { Wavetable::Noise, 5,   0 },
+    // 48h..4Fh  Pipe
+    { Wavetable::LowPeriodNoise, 8, 255 },
+    { Wavetable::LowPeriodNoise, 8, 128 },
+    { Wavetable::LowPeriodNoise, 8,  32 },
+    { Wavetable::LowPeriodNoise, 8,   8 },
+    { Wavetable::LowPeriodNoise, 7,   8 },
+    { Wavetable::LowPeriodNoise, 6,   8 },
+    { Wavetable::LowPeriodNoise, 5,   8 },
+    { Wavetable::LowPeriodNoise, 5,   0 },
+    // 50h..57h  Synth Lead
+    { Wavetable::Sine, 8, 255 },
+    { Wavetable::Sine, 8, 128 },
+    { Wavetable::Sine, 8,  32 },
+    { Wavetable::Sine, 8,   8 },
+    { Wavetable::Sine, 7,   8 },
+    { Wavetable::Sine, 6,   8 },
+    { Wavetable::Sine, 5,   8 },
+    { Wavetable::Sine, 5,   0 },
+    // 58h..5Fh  Synth Pad
+    { Wavetable::Sine, 8, 255 },
+    { Wavetable::Sine, 8, 128 },
+    { Wavetable::Sine, 8,  32 },
+    { Wavetable::Sine, 8,   8 },
+    { Wavetable::Sine, 7,   8 },
+    { Wavetable::Sine, 6,   8 },
+    { Wavetable::Sine, 5,   8 },
+    { Wavetable::Sine, 5,   0 },
+    // 60h..67h  Synth Effects
+    { Wavetable::Sine, 8, 255 },
+    { Wavetable::Sine, 8, 128 },
+    { Wavetable::Sine, 8,  32 },
+    { Wavetable::Sine, 8,   8 },
+    { Wavetable::Sine, 7,   8 },
+    { Wavetable::Sine, 6,   8 },
+    { Wavetable::Sine, 5,   8 },
+    { Wavetable::Sine, 5,   0 },
+    // 68h..6Fh  Ethnic
+    { Wavetable::Sine, 8, 255 },
+    { Wavetable::Sine, 8, 128 },
+    { Wavetable::Sine, 8,  32 },
+    { Wavetable::Sine, 8,   8 },
+    { Wavetable::Sine, 7,   8 },
+    { Wavetable::Sine, 6,   8 },
+    { Wavetable::Sine, 5,   8 },
+    { Wavetable::Sine, 5,   0 },
+    // 70h..77h  Percussive
+    { Wavetable::Sine, 8, 255 },
+    { Wavetable::Sine, 8, 128 },
+    { Wavetable::Sine, 8,  32 },
+    { Wavetable::Sine, 8,   8 },
+    { Wavetable::Sine, 7,   8 },
+    { Wavetable::Sine, 6,   8 },
+    { Wavetable::Sine, 5,   8 },
+    { Wavetable::Sine, 5,   0 },
+    // 78h..7Fh  Sound Effects
+    { Wavetable::Sine, 8, 255 },
+    { Wavetable::Sine, 8, 128 },
+    { Wavetable::Sine, 8,  32 },
+    { Wavetable::Sine, 8,   8 },
+    { Wavetable::Sine, 7,   8 },
+    { Wavetable::Sine, 6,   8 },
+    { Wavetable::Sine, 5,   8 },
+    { Wavetable::Sine, 5,   0 },
+};
+
+uint8_t const Wavetable::sineTable[WAVETABLE_LENGTH] = {
+    0x80, 0x83, 0x86, 0x89, 0x8c, 0x8f, 0x92, 0x95,
+    0x98, 0x9b, 0x9e, 0xa1, 0xa4, 0xa7, 0xaa, 0xad,
+    0xb0, 0xb3, 0xb6, 0xb9, 0xbb, 0xbe, 0xc1, 0xc3,
+    0xc6, 0xc9, 0xcb, 0xce, 0xd0, 0xd2, 0xd5, 0xd7,
+    0xd9, 0xdb, 0xde, 0xe0, 0xe2, 0xe4, 0xe6, 0xe7,
+    0xe9, 0xeb, 0xec, 0xee, 0xf0, 0xf1, 0xf2, 0xf4,
+    0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfb,
+    0xfc, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+    
+    0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd,
+    0xfc, 0xfb, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6,
+    0xf5, 0xf4, 0xf2, 0xf1, 0xf0, 0xee, 0xec, 0xeb,
+    0xe9, 0xe7, 0xe6, 0xe4, 0xe2, 0xe0, 0xde, 0xdb,
+    0xd9, 0xd7, 0xd5, 0xd2, 0xd0, 0xce, 0xcb, 0xc9,
+    0xc6, 0xc3, 0xc1, 0xbe, 0xbb, 0xb9, 0xb6, 0xb3,
+    0xb0, 0xad, 0xaa, 0xa7, 0xa4, 0xa1, 0x9e, 0x9b,
+    0x98, 0x95, 0x92, 0x8f, 0x8c, 0x89, 0x86, 0x83,
+    
+    0x7f, 0x7c, 0x79, 0x76, 0x73, 0x70, 0x6d, 0x6a,
+    0x67, 0x64, 0x61, 0x5e, 0x5b, 0x58, 0x55, 0x52,
+    0x4f, 0x4c, 0x49, 0x46, 0x44, 0x41, 0x3e, 0x3c,
+    0x39, 0x36, 0x34, 0x31, 0x2f, 0x2d, 0x2a, 0x28,
+    0x26, 0x24, 0x21, 0x1f, 0x1d, 0x1b, 0x19, 0x18,
+    0x16, 0x14, 0x13, 0x11, 0x0f, 0x0e, 0x0d, 0x0b,
+    0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x04,
+    0x03, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01,
+    
+    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02,
+    0x03, 0x04, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+    0x0a, 0x0b, 0x0d, 0x0e, 0x0f, 0x11, 0x13, 0x14,
+    0x16, 0x18, 0x19, 0x1b, 0x1d, 0x1f, 0x21, 0x24,
+    0x26, 0x28, 0x2a, 0x2d, 0x2f, 0x31, 0x34, 0x36,
+    0x39, 0x3c, 0x3e, 0x41, 0x44, 0x46, 0x49, 0x4c,
+    0x4f, 0x52, 0x55, 0x58, 0x5b, 0x5e, 0x61, 0x64,
+    0x67, 0x6a, 0x6d, 0x70, 0x73, 0x76, 0x79, 0x7c
+};
+
+uint8_t const Wavetable::squareTable[WAVETABLE_LENGTH] = {
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40
+};
+
+uint8_t const Wavetable::pulse1_3Table[WAVETABLE_LENGTH] = {
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40
+};
+
+uint8_t const Wavetable::pulse1_7Table[WAVETABLE_LENGTH] = {
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40
+};
+
+uint8_t const Wavetable::sawtoothTable[WAVETABLE_LENGTH] = {
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+    0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+    0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+    0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+    0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+    0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+    0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+    
+    0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+    0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+    0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+    0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+    0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+    0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+    0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+    
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+    
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+    0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+    0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+    0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+    0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
+};
+
+uint8_t const Wavetable::triangleTable[WAVETABLE_LENGTH] = {
+    0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e,
+    0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e,
+    0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae,
+    0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe,
+    0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce,
+    0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde,
+    0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee,
+    0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe,
+    
+    0xff, 0xfe, 0xfc, 0xfa, 0xf8, 0xf6, 0xf4, 0xf2,
+    0xf0, 0xee, 0xec, 0xea, 0xe8, 0xe6, 0xe4, 0xe2,
+    0xe0, 0xde, 0xdc, 0xda, 0xd8, 0xd6, 0xd4, 0xd2,
+    0xd0, 0xce, 0xcc, 0xca, 0xc8, 0xc6, 0xc4, 0xc2,
+    0xc0, 0xbe, 0xbc, 0xba, 0xb8, 0xb6, 0xb4, 0xb2,
+    0xb0, 0xae, 0xac, 0xaa, 0xa8, 0xa6, 0xa4, 0xa2,
+    0xa0, 0x9e, 0x9c, 0x9a, 0x98, 0x96, 0x94, 0x92,
+    0x90, 0x8e, 0x8c, 0x8a, 0x88, 0x86, 0x84, 0x82,
+
+    0x80, 0x7e, 0x7c, 0x7a, 0x78, 0x76, 0x74, 0x72,
+    0x70, 0x6e, 0x6c, 0x6a, 0x68, 0x66, 0x64, 0x62,
+    0x60, 0x5e, 0x5c, 0x5a, 0x58, 0x56, 0x54, 0x52,
+    0x50, 0x4e, 0x4c, 0x4a, 0x48, 0x46, 0x44, 0x42,
+    0x40, 0x3e, 0x3c, 0x3a, 0x38, 0x36, 0x34, 0x32,
+    0x30, 0x2e, 0x2c, 0x2a, 0x28, 0x26, 0x24, 0x22,
+    0x20, 0x1e, 0x1c, 0x1a, 0x18, 0x16, 0x14, 0x12,
+    0x10, 0x0e, 0x0c, 0x0a, 0x08, 0x06, 0x04, 0x02,
+    
+    0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e,
+    0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
+    0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e,
+    0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e,
+    0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e,
+    0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e,
+    0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e,
+    0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e
+};
+
+uint8_t const Wavetable::coarseTriangleTable[WAVETABLE_LENGTH] = {
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+    0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+    0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+    0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+    
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+    0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+    0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+    0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+    
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+    0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+    0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
+    0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60
+};
+
+uint8_t const Wavetable::spikedSineTable[WAVETABLE_LENGTH] = {
+    0xa0, 0xa2, 0xa4, 0xa7, 0xa9, 0xab, 0xae, 0xb0,
+    0x72, 0x75, 0x77, 0x79, 0x7b, 0x7e, 0x80, 0x82,
+    0xc4, 0xc6, 0xc9, 0xcb, 0xcd, 0xcf, 0xd1, 0xd3,
+    0x95, 0x97, 0x99, 0x9b, 0x9c, 0x9e, 0xa0, 0xa2,
+    0xe3, 0xe5, 0xe7, 0xe8, 0xea, 0xeb, 0xed, 0xee,
+    0xaf, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+    0xf8, 0xf9, 0xfa, 0xfb, 0xfb, 0xfc, 0xfd, 0xfd,
+    0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
+    
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe,
+    0xbe, 0xbd, 0xbd, 0xbc, 0xbb, 0xbb, 0xba, 0xb9,
+    0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1,
+    0xaf, 0xae, 0xad, 0xab, 0xaa, 0xa8, 0xa7, 0xa5,
+    0xe3, 0xe2, 0xe0, 0xde, 0xdc, 0xdb, 0xd9, 0xd7,
+    0x95, 0x93, 0x91, 0x8f, 0x8d, 0x8b, 0x89, 0x86,
+    0xc4, 0xc2, 0xc0, 0xbe, 0xbb, 0xb9, 0xb7, 0xb5,
+    0x72, 0x70, 0x6e, 0x6b, 0x69, 0x67, 0x64, 0x62,
+    
+    0x9f, 0x9d, 0x9b, 0x98, 0x96, 0x94, 0x91, 0x8f,
+    0x4d, 0x4a, 0x48, 0x46, 0x44, 0x41, 0x3f, 0x3d,
+    0x7b, 0x79, 0x76, 0x74, 0x72, 0x70, 0x6e, 0x6c,
+    0x2a, 0x28, 0x26, 0x24, 0x23, 0x21, 0x1f, 0x1d,
+    0x5c, 0x5a, 0x58, 0x57, 0x55, 0x54, 0x52, 0x51,
+    0x10, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08,
+    0x47, 0x46, 0x45, 0x44, 0x44, 0x43, 0x42, 0x42,
+    0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+    
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x41, 0x41,
+    0x01, 0x02, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06,
+    0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e,
+    0x10, 0x11, 0x12, 0x14, 0x15, 0x17, 0x18, 0x1a,
+    0x5c, 0x5d, 0x5f, 0x61, 0x63, 0x64, 0x66, 0x68,
+    0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x39,
+    0x7b, 0x7d, 0x7f, 0x81, 0x84, 0x86, 0x88, 0x8a,
+    0x4d, 0x4f, 0x51, 0x54, 0x56, 0x58, 0x5b, 0x5d
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Wavetable.h	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,38 @@
+#ifndef WAVETABLE_H_
+#define WAVETABLE_H_
+
+#define WAVETABLE_LENGTH  256
+
+class Wavetable {
+public:
+    typedef enum {
+        Sine           = 0,
+        Square         = 1,
+        Pulse1_3       = 2,
+        Pulse1_7       = 3,
+        Sawtooth       = 4,
+        Triangle       = 5,
+        CoarseTriangle = 6,
+        SpikedSine     = 7,
+        Noise          = 8,
+        LowPeriodNoise = 9
+    } wavetype_t;
+    typedef struct {
+        wavetype_t wavetype;
+        uint8_t    decaySpeed;
+        uint8_t    sustainLevel;
+    } wave_t;
+
+    static const uint8_t* const waveTableList[];
+    static wave_t const waveDefList[128];
+    static uint8_t const sineTable[WAVETABLE_LENGTH];
+    static uint8_t const squareTable[WAVETABLE_LENGTH];
+    static uint8_t const pulse1_3Table[WAVETABLE_LENGTH];
+    static uint8_t const pulse1_7Table[WAVETABLE_LENGTH];
+    static uint8_t const sawtoothTable[WAVETABLE_LENGTH];
+    static uint8_t const triangleTable[WAVETABLE_LENGTH];
+    static uint8_t const coarseTriangleTable[WAVETABLE_LENGTH];
+    static uint8_t const spikedSineTable[WAVETABLE_LENGTH];
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debug.cpp	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,61 @@
+#include <stdarg.h>
+#include "mbed.h"
+#include "defs.h"
+#include "RingBuffer.h"
+#include "debug.h"
+#include "note.h"
+
+extern Serial           console;
+extern note_t           noteSent[NUM_INSTRUMENT];
+extern RingBuffer<char> buffer;
+extern dumpmode_t       dumpMode;
+
+void checkBuffer() {
+    size_t bufferCount = buffer.getItemCount();
+    size_t bufferCountToDisplay;
+    char*  bufferPtr = NULL;
+
+    if (bufferCount) {
+        bufferPtr = buffer.peek();
+        bufferCountToDisplay = bufferCount > 16 ? 16 : bufferCount;
+        
+        console.printf("* %u byte%c in buffer", bufferCount,
+                       bufferCount == 1 ? '\0' : 's');
+        if (bufferCount > 16) {
+            console.printf(". The first 16 bytes:\r\n ");
+        } else {
+            console.printf(":\r\n ");
+        }
+        for (uint8_t i = 0; i < bufferCountToDisplay; i++) {
+            console.printf(" %02X", *bufferPtr++);
+        }
+        console.printf("h\r\n");
+    } else {
+        console.printf("* Buffer is empty.\r\n");
+    }
+}
+
+void dumpInstrumentList() {
+    char const channelLetter[] = {
+        '-', '1', '2', '3', '4', '5', '6', '7', '8',
+        '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G'
+    };
+    
+    if (dumpMode == DUMP_INST_DETAIL) {
+        console.printf("\r\n");
+        for (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
+            if (noteSent[i].channel) {
+                console.printf("#%2d  ch=%2d n=%02Xh v=%02Xh ms=%lu\r\n",
+                               i, noteSent[i].channel, noteSent[i].noteNumber,
+                               noteSent[i].velocity, noteSent[i].noteOnMs);
+            } else {
+                console.printf("#%2d         not in use\r\n", i);
+            }
+        }
+    } else if (dumpMode == DUMP_INST) {
+        for (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
+            console.printf(" %c", channelLetter[noteSent[i].channel]);
+        }
+        console.printf("\r\n");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debug.h	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,7 @@
+#ifndef DEBUG_H_
+#define DEBUG_H_
+
+void checkBuffer();
+void dumpInstrumentList();
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/defs.h	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,19 @@
+#define BUFFER_LENGTH               2048
+#define SYSEX_BUFFER_LENGTH          128
+
+#define NUM_PERFORMER                  1
+#define NUM_INSTRUMENT_IN_PERFORMER   16
+#define NUM_INSTRUMENT  ((NUM_INSTRUMENT_IN_PERFORMER) * (NUM_PERFORMER))
+
+// Power down unused uC's peripherals and Ethernet PHY chip
+#define POWER_SAVE
+// Boost system clock to 120 MHz
+// (valid only when POWER_SAVE is also defined)
+#define CLOCKUP
+
+typedef enum {
+    DUMP_NOTHING,
+    DUMP_EVENTS,
+    DUMP_INST,
+    DUMP_INST_DETAIL
+} dumpmode_t;
--- /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);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/events.h	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,13 @@
+#ifndef EVENTS_H_
+#define EVENTS_H_
+
+void dispatchNoteOff(char messageBytes[3]);
+void dispatchNoteOn(char messageBytes[3]);
+void sendControlChange(char messageBytes[3]);
+void sendProgramChange(char messageBytes[3]);
+void sendPitchBend(char messageBytes[3]);
+void sendSystemReset();
+void allNoteOff(char messageBytes[3]);
+void dumpInstrumentList();
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frequency.h	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,135 @@
+#ifndef FREQUENCY_H_
+#define FREQUENCY_H_
+
+float const frequencyTable[128] = {
+        8.175799,  //   0 (C -1)
+        8.661957,  //   1 (Db-1)
+        9.177024,  //   2 (D -1)
+        9.722718,  //   3 (Eb-1)
+       10.300861,  //   4 (E -1)
+       10.913382,  //   5 (F -1)
+       11.562326,  //   6 (Gb-1)
+       12.249857,  //   7 (G -1)
+       12.978272,  //   8 (Ab-1)
+       13.750000,  //   9 (A -1)
+       14.567618,  //  10 (Bb-1)
+       15.433853,  //  11 (B -1)
+       16.351598,  //  12 (C  0)
+       17.323914,  //  13 (Db 0)
+       18.354048,  //  14 (D  0)
+       19.445436,  //  15 (Eb 0)
+       20.601722,  //  16 (E  0)
+       21.826764,  //  17 (F  0)
+       23.124651,  //  18 (Gb 0)
+       24.499715,  //  19 (G  0)
+       25.956544,  //  20 (Ab 0)
+       27.500000,  //  21 (A  0)
+       29.135235,  //  22 (Bb 0)
+       30.867706,  //  23 (B  0)
+       32.703196,  //  24 (C  1)
+       34.647829,  //  25 (Db 1)
+       36.708096,  //  26 (D  1)
+       38.890873,  //  27 (Eb 1)
+       41.203445,  //  28 (E  1)
+       43.653529,  //  29 (F  1)
+       46.249303,  //  30 (Gb 1)
+       48.999429,  //  31 (G  1)
+       51.913087,  //  32 (Ab 1)
+       55.000000,  //  33 (A  1)
+       58.270470,  //  34 (Bb 1)
+       61.735413,  //  35 (B  1)
+       65.406391,  //  36 (C  2)
+       69.295658,  //  37 (Db 2)
+       73.416192,  //  38 (D  2)
+       77.781746,  //  39 (Eb 2)
+       82.406889,  //  40 (E  2)
+       87.307058,  //  41 (F  2)
+       92.498606,  //  42 (Gb 2)
+       97.998859,  //  43 (G  2)
+      103.826174,  //  44 (Ab 2)
+      110.000000,  //  45 (A  2)
+      116.540940,  //  46 (Bb 2)
+      123.470825,  //  47 (B  2)
+      130.812783,  //  48 (C  3)
+      138.591315,  //  49 (Db 3)
+      146.832384,  //  50 (D  3)
+      155.563492,  //  51 (Eb 3)
+      164.813778,  //  52 (E  3)
+      174.614116,  //  53 (F  3)
+      184.997211,  //  54 (Gb 3)
+      195.997718,  //  55 (G  3)
+      207.652349,  //  56 (Ab 3)
+      220.000000,  //  57 (A  3)
+      233.081881,  //  58 (Bb 3)
+      246.941651,  //  59 (B  3)
+      261.625565,  //  60 (C  4)
+      277.182631,  //  61 (Db 4)
+      293.664768,  //  62 (D  4)
+      311.126984,  //  63 (Eb 4)
+      329.627557,  //  64 (E  4)
+      349.228231,  //  65 (F  4)
+      369.994423,  //  66 (Gb 4)
+      391.995436,  //  67 (G  4)
+      415.304698,  //  68 (Ab 4)
+      440.000000,  //  69 (A  4)
+      466.163762,  //  70 (Bb 4)
+      493.883301,  //  71 (B  4)
+      523.251131,  //  72 (C  5)
+      554.365262,  //  73 (Db 5)
+      587.329536,  //  74 (D  5)
+      622.253967,  //  75 (Eb 5)
+      659.255114,  //  76 (E  5)
+      698.456463,  //  77 (F  5)
+      739.988845,  //  78 (Gb 5)
+      783.990872,  //  79 (G  5)
+      830.609395,  //  80 (Ab 5)
+      880.000000,  //  81 (A  5)
+      932.327523,  //  82 (Bb 5)
+      987.766603,  //  83 (B  5)
+     1046.502261,  //  84 (C  6)
+     1108.730524,  //  85 (Db 6)
+     1174.659072,  //  86 (D  6)
+     1244.507935,  //  87 (Eb 6)
+     1318.510228,  //  88 (E  6)
+     1396.912926,  //  89 (F  6)
+     1479.977691,  //  90 (Gb 6)
+     1567.981744,  //  91 (G  6)
+     1661.218790,  //  92 (Ab 6)
+     1760.000000,  //  93 (A  6)
+     1864.655046,  //  94 (Bb 6)
+     1975.533205,  //  95 (B  6)
+     2093.004522,  //  96 (C  7)
+     2217.461048,  //  97 (Db 7)
+     2349.318143,  //  98 (D  7)
+     2489.015870,  //  99 (Eb 7)
+     2637.020455,  // 100 (E  7)
+     2793.825851,  // 101 (F  7)
+     2959.955382,  // 102 (Gb 7)
+     3135.963488,  // 103 (G  7)
+     3322.437581,  // 104 (Ab 7)
+     3520.000000,  // 105 (A  7)
+     3729.310092,  // 106 (Bb 7)
+     3951.066410,  // 107 (B  7)
+     4186.009045,  // 108 (C  8)
+     4434.922096,  // 109 (Db 8)
+     4698.636287,  // 110 (D  8)
+     4978.031740,  // 111 (Eb 8)
+     5274.040911,  // 112 (E  8)
+     5587.651703,  // 113 (F  8)
+     5919.910763,  // 114 (Gb 8)
+     6271.926976,  // 115 (G  8)
+     6644.875161,  // 116 (Ab 8)
+     7040.000000,  // 117 (A  8)
+     7458.620184,  // 118 (Bb 8)
+     7902.132820,  // 119 (B  8)
+     8372.018090,  // 120 (C  9)
+     8869.844191,  // 121 (Db 9)
+     9397.272573,  // 122 (D  9)
+     9956.063479,  // 123 (Eb 9)
+    10548.081821,  // 124 (E  9)
+    11175.303406,  // 125 (F  9)
+    11839.821527,  // 126 (Gb 9)
+    12543.853951   // 127 (G  9)
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,222 @@
+#include "mbed.h"
+#include "ClockControl.h"
+#include "EthernetPowerControl.h"
+#include "PowerControl.h"
+#include "defs.h"
+#include "Channels.h"
+#include "GeminiCore.h"
+#include "RingBuffer.h"
+#include "debug.h"
+#include "events.h"
+#include "note.h"
+#include "parser.h"
+
+Serial            pc(/*Tx*/ USBTX, /*Rx*/ USBRX);
+Serial            midiIn(/*Tx*/ p28, /*Rx*/ p27);  // @TODO final: p13, p14
+Serial            console(/*Tx*/ p13, /*Rx*/ p14);  // @TODO final: p28, p27
+DigitalIn         swSerialSelect();
+DigitalIn         swPanic(p15);
+BusOut            led(/*Left*/ LED1, LED2, LED3, LED4 /*Right*/);
+BusOut            ledBar(p17, p18, p19, p20, p21, p22, p23, p24, p25, p26, p29, p30);
+SPI               spi(/*MOSI*/ p5, /*MISO*/ p6, /*SCK*/ p7);
+DigitalOut        dacCs(p8);
+Ticker            t1ms;
+Ticker            tSample;
+volatile uint32_t __countMs;
+bool              serSource;
+GeminiCore        gemCore(NUM_INSTRUMENT_IN_PERFORMER);
+RingBuffer<char>  buffer(BUFFER_LENGTH);
+Channels          ch;
+note_t            noteSent[NUM_INSTRUMENT];
+uint32_t          receivedBytes;
+dumpmode_t        dumpMode;
+
+void sampleOut() {
+    // soundOut.write(gemCore.makeSample() / 65536.0);
+    dacCs = 0;
+    spi.write(0x3000 | gemCore.makeSample() >> 4);
+    dacCs = 1;
+}
+
+void count1ms() {
+    __countMs++;
+}
+
+void readMidiIn() {
+    char c;
+    
+    // Put a MIDI input byte into buffer if available
+    if ((serSource ? midiIn : pc).readable()) {
+        c = (serSource ? midiIn : pc).getc();
+        receivedBytes++;
+
+        // Discard if input byte is an active sensing message
+        if (c != 0xfe) {
+            buffer.write(c);
+        }
+    }
+}
+
+void setup() {
+#ifdef POWER_SAVE
+    // Power down Ethernet PHY chip
+    PHY_PowerDown();
+    
+    // Power down unused peripherals
+    Peripheral_PowerDown(0x40067200);
+#endif
+
+#if defined(POWER_SAVE) && defined(CLOCKUP)
+    // Change clock speed to 120 MHz
+    setSystemFrequency(0x03, 0x01, 15, 1);
+#endif
+    
+    // Enable pull-up at switch inputs
+    // swSerialSelect.mode(PullUp);
+    swPanic.mode(PullUp);
+    wait(0.2);                  // Wait a moment for stability
+    
+    // Read serial selector switch
+    serSource = 1;              // @TODO final: serSource = swSerialSelect;
+    
+    // Open selected port
+    if (serSource) {
+        // Use MIDI port when high
+        midiIn.baud(38400);     // @TODO final: midiIn.baud(31250);
+        midiIn.format(8, Serial::None, 1);
+        midiIn.attach(readMidiIn, Serial::RxIrq);
+    } else {
+        // Use serial MIDI when low
+        pc.baud(38400);
+        pc.format(8, Serial::None, 1);
+        pc.attach(readMidiIn, Serial::RxIrq);
+    }
+    receivedBytes = 0;
+    
+    // Setup console
+    console.baud(115200);
+    console.format(8, Serial::None, 1);
+    dumpMode = DUMP_NOTHING;
+    
+    // Setup SPI
+    spi.format(16, 0);
+    spi.frequency(12000000);
+    dacCs = 1;
+    
+    // Initialize channels
+    ch.initializeAll();
+    
+    // Initialize note status
+    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;
+    }
+    
+    // Start Timer
+    __countMs = 0;
+    t1ms.attach_us(&count1ms, 1000);
+    
+    // Start playback & attach interrupt
+#if defined(POWER_SAVE) && defined(CLOCKUP)
+    tSample.attach_us(&sampleOut, 1250000 / GeminiCore::samplingRate);
+#else
+    tSample.attach_us(&sampleOut, 1000000 / GeminiCore::samplingRate);
+#endif
+}
+
+void consoleOperations(uint8_t c) {
+    switch (c) {
+    case 'b':
+        checkBuffer();
+        break;
+    case 'e':
+        if (dumpMode != DUMP_EVENTS) {
+            dumpMode = DUMP_EVENTS;
+        } else {
+            dumpMode = DUMP_NOTHING;
+        }
+        break;
+    case 'f':
+        buffer.flush();
+        console.printf("* Buffer flushed.\r\n");
+        break;
+    case 'i':
+        if (dumpMode == DUMP_INST) {
+            dumpMode = DUMP_INST_DETAIL;
+        } else if (dumpMode == DUMP_INST_DETAIL) {
+            dumpMode = DUMP_NOTHING;
+        } else {
+            dumpMode = DUMP_INST;
+        }
+        break;
+    case 'p':
+        // Panic
+        for (uint8_t i = 0; i < NUM_INSTRUMENT_IN_PERFORMER; i++) {
+            gemCore.noteOff(i);
+            noteSent[i].noteOnMs = 0;
+            noteSent[i].channel = 0;
+            noteSent[i].noteNumber = 0;
+            noteSent[i].velocity = 0;
+        }
+        break;
+    case 'r':
+        console.printf("* Received MIDI bytes: %u\r\n", receivedBytes);
+        break;
+    default: break;
+    }
+}
+
+void loop() {
+    static uint32_t lastPollSwitchMs = __countMs;
+    static bool     prevstatePanic = 0;
+    bool            statePanic;
+    
+    // Serial console
+    if (console.readable()) {
+        consoleOperations(console.getc());
+    }
+
+    // Poll switches
+    if (__countMs > lastPollSwitchMs + 20) {
+        lastPollSwitchMs = __countMs;
+        
+        statePanic = swPanic;
+        
+        // Panic (all note off and flush buffer)
+        if (!statePanic && prevstatePanic) {
+            buffer.flush();
+            for (uint8_t i = 0; i < NUM_INSTRUMENT_IN_PERFORMER; i++) {
+                gemCore.noteOff(i);
+                noteSent[i].noteOnMs = 0;
+                noteSent[i].channel = 0;
+                noteSent[i].noteNumber = 0;
+                noteSent[i].velocity = 0;
+            }
+        }
+        
+        // Preserve this time's switch states
+        prevstatePanic = statePanic;
+    }
+    
+    // Parse MIDI messages
+    parseMessage(buffer);
+
+    // LEDs
+    uint8_t busyCount = 0;
+    for (uint8_t i = 0; i < NUM_INSTRUMENT_IN_PERFORMER; i++) {
+        if (noteSent[i].noteOnMs) {
+            busyCount++;
+        }
+    }
+    ledBar = (1 << busyCount) - 1;
+}
+
+int main() {
+    setup();
+    
+    while (1) {
+        loop();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed/builds/024bf7f99721
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/note.h	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,11 @@
+#ifndef NOTE_H_
+#define NOTE_H_
+
+typedef struct {
+    uint32_t noteOnMs;
+    uint8_t  channel;     // channel. 0: not in use, 1..16 is valid
+    uint8_t  noteNumber;
+    uint8_t  velocity;
+} note_t;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/parser.cpp	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,236 @@
+#include <stdarg.h>
+#include "mbed.h"
+#include "defs.h"
+#include "RingBuffer.h"
+#include "debug.h"
+#include "events.h"
+#include "parser.h"
+
+extern Serial     console;
+extern bool       serSource;
+extern dumpmode_t dumpMode;
+char const    sysExGmSystemOn[6] = { 0xf0, 0x7e, 0x7f, 0x09, 0x01, 0xf7 };
+char const    sysExGsReset[11]   = { 0xf0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7f, 0x00, 0x41, 0xf7 };
+char const    sysExXgSystemOn[9] = { 0xf0, 0x43, 0x10, 0x4c, 0x00, 0x00, 0x7e, 0x00, 0xf7 };
+
+void parseMessage(RingBuffer<char>& buffer) {
+    static char lastStatusByte;
+    char c;
+    char messageBytes[3];
+    char sysExBuffer[SYSEX_BUFFER_LENGTH];
+    bool runningStatus = false;
+    
+    c = *buffer.peek();
+    
+    // Running status
+    if (!(c & 0x80)) {
+        runningStatus = true;
+        // Restore previous status byte
+        c = lastStatusByte;
+    }
+    
+    switch (c & 0xf0) {
+    case 0x80:  // Note off
+        if (buffer.getItemCount() >= 3 - runningStatus) {
+            messageBytes[0] = runningStatus ? c : *buffer.read();
+            for (uint8_t i = 1; i < 3; i++) {
+                messageBytes[i] = *buffer.read();
+            }
+            if (dumpMode == DUMP_EVENTS) {
+                console.printf("%-19s %c%02X %02X %02Xh\r\n",
+                               "Note off", runningStatus ? '*' : ' ',
+                               messageBytes[0], messageBytes[1], messageBytes[2]);
+            }
+            dispatchNoteOff(messageBytes);
+            lastStatusByte = c;
+        }
+        break;
+    case 0x90:  // Note on
+        if (buffer.getItemCount() >= 3 - runningStatus) {
+            messageBytes[0] = runningStatus ? c : *buffer.read();
+            for (uint8_t i = 1; i < 3; i++) {
+                messageBytes[i] = *buffer.read();
+            }
+            if (dumpMode == DUMP_EVENTS) {
+                console.printf("%-19s %c%02X %02X %02Xh\r\n",
+                               "Note on", runningStatus ? '*' : ' ',
+                               messageBytes[0], messageBytes[1], messageBytes[2]);
+            }
+            if (messageBytes[2] == 0x00) {
+                dispatchNoteOff(messageBytes);
+            } else {
+                dispatchNoteOn(messageBytes);
+            }
+            lastStatusByte = c;
+        }
+        break;
+    case 0xa0:  // Polyphonic pressure
+        // Not supported
+        if (buffer.getItemCount() >= 3 - runningStatus) {
+            messageBytes[0] = runningStatus ? c : *buffer.read();
+            for (uint8_t i = 1; i < 3; i++) {
+                messageBytes[i] = *buffer.read();
+            }
+            if (dumpMode == DUMP_EVENTS) {
+                console.printf("%-19s %c%02X %02X %02Xh\r\n",
+                               "Poly pressure", runningStatus ? '*' : ' ',
+                               messageBytes[0], messageBytes[1], messageBytes[2]);
+            }
+            lastStatusByte = c;
+        }
+        break;
+    case 0xb0:  // Control change
+        if (buffer.getItemCount() >= 3 - runningStatus) {
+            messageBytes[0] = runningStatus ? c : *buffer.read();
+            for (uint8_t i = 1; i < 3; i++) {
+                messageBytes[i] = *buffer.read();
+            }
+            if (dumpMode == DUMP_EVENTS) {
+                console.printf("%-19s %c%02X %02X %02Xh\r\n",
+                               "Control change", runningStatus ? '*' : ' ',
+                               messageBytes[0], messageBytes[1], messageBytes[2]);
+            }
+            switch (messageBytes[1]) {
+            case 0x78:  // All sound off
+            case 0x7b:  // All note off
+                allNoteOff(messageBytes);
+                break;
+            default:
+                sendControlChange(messageBytes);
+                break;
+            }            
+            lastStatusByte = c;
+        }
+        break;
+    case 0xc0:  // Program change
+        if (buffer.getItemCount() >= 2 - runningStatus) {
+            messageBytes[0] = runningStatus ? c : *buffer.read();
+            for (uint8_t i = 1; i < 2; i++) {
+                messageBytes[i] = *buffer.read();
+            }
+            if (dumpMode == DUMP_EVENTS) {
+                console.printf("%-19s %c%02X %02X __h\r\n",
+                               "Program change", runningStatus ? '*' : ' ',
+                               messageBytes[0], messageBytes[1]);
+            }
+            sendProgramChange(messageBytes);
+            lastStatusByte = c;
+        }
+        break;
+    case 0xd0:  // Channel pressure
+        // Not supported
+        if (buffer.getItemCount() >= 2 - runningStatus) {
+            messageBytes[0] = runningStatus ? c : *buffer.read();
+            for (uint8_t i = 1; i < 2; i++) {
+                messageBytes[i] = *buffer.read();
+            }
+            if (dumpMode == DUMP_EVENTS) {
+                console.printf("%-19s %c%02X %02X __h\r\n",
+                               "Ch pressure", runningStatus ? '*' : ' ',
+                               messageBytes[0], messageBytes[1]);
+            }
+            lastStatusByte = c;
+        }
+        break;
+    case 0xe0:  // Pitch bend
+        if (buffer.getItemCount() >= 3 - runningStatus) {
+            messageBytes[0] = runningStatus ? c : *buffer.read();
+            for (uint8_t i = 1; i < 3; i++) {
+                messageBytes[i] = *buffer.read();
+            }
+            if (dumpMode == DUMP_EVENTS) {
+                console.printf("%-19s %c%02X %02X %02Xh\r\n",
+                          "Pitch bend", runningStatus ? '*' : ' ',
+                          messageBytes[0], messageBytes[1], messageBytes[2]);
+            }
+            sendPitchBend(messageBytes);
+            lastStatusByte = c;
+        }
+        break;
+    case 0xf0:
+        switch (c) {
+        case 0xf0:  // SysEx message
+            if (buffer.find(0xf7) == -1) {
+                break;
+            }
+            
+            // Extract "F0 ** F7h" message block from buffer
+            extractSysExMessage(buffer, sysExBuffer);
+            if (strncmp(sysExBuffer, sysExGmSystemOn, 6) == 0) {
+                // Matches "GM System On" SysEx message
+                if (dumpMode == DUMP_EVENTS) {
+                    console.printf("SysEx message: GM System On\r\n");
+                }
+                sendSystemReset();
+            } else if (strncmp(sysExBuffer, sysExGsReset, 11) == 0) {
+                // Matches "GS Reset" SysEx message
+                if (dumpMode == DUMP_EVENTS) {
+                    console.printf("SysEx message: GS Reset\r\n");
+                }
+                sendSystemReset();
+            } else if (strncmp(sysExBuffer, sysExXgSystemOn, 9) == 0) {
+                // Matches "XG System On" SysEx message
+                if (dumpMode == DUMP_EVENTS) {
+                    console.printf("SysEx message: XG System On\r\n");
+                }
+                sendSystemReset();
+            } else {
+                if (dumpMode == DUMP_EVENTS) {
+                    console.printf("Unsupported SysEx message\r\n");
+                }
+            }
+            break;
+        case 0xf1:  // MTC quarter frame
+        case 0xf3:  // Song select
+            // Not supported
+            buffer.read();
+            buffer.read();
+            break;
+        case 0xf2:  // Song position
+            // Not supported
+            buffer.read();
+            buffer.read();
+            buffer.read();
+            break;
+        case 0xf4: case 0xf5: case 0xf9: case 0xfd:  // Undefined
+        case 0xf6:  // Tune request
+        case 0xfa:  // Start
+        case 0xfb:  // Continue
+        case 0xfc:  // Stop
+            buffer.read();
+            break;
+        case 0xfe:  // Active sensing
+            buffer.read();
+            break;
+        case 0xff:  // System reset
+            // Discard message (@todo)
+            buffer.read();
+            sendSystemReset();
+        }
+    }
+}
+
+uint32_t extractSysExMessage(RingBuffer<char>& buffer, char* msg) {
+    uint32_t extractedLength;
+    char* c = NULL;
+    
+    // Check if the first byte matches SysEx start byte (0xf0)
+    c = buffer.read();
+    if (!c) return 0;
+    if (*c != 0xf0) {
+        return 0;
+    } else {
+        msg[0] = *c;
+    }
+    
+    // Read buffer until reaching SysEx end byte (0xf7)
+    extractedLength = 1;
+    while (extractedLength < SYSEX_BUFFER_LENGTH) {
+        c = buffer.read();
+        if (!c) break;
+        
+        msg[extractedLength++] = *c;
+        if (*c == 0xf7) return extractedLength;
+    }
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/parser.h	Sun Nov 09 08:00:33 2014 +0000
@@ -0,0 +1,9 @@
+#ifndef PARSER_H_
+#define PARSER_H_
+
+#include "RingBuffer.h"
+
+void parseMessage(RingBuffer<char>&);
+uint32_t extractSysExMessage(RingBuffer<char>&, char*);
+
+#endif