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

Dependencies:   ClockControl PowerControl mbed

events.cpp

Committer:
kayekss
Date:
2014-12-23
Revision:
6:cda45a5e723e
Parent:
5:7bc917d03bd6

File content as of revision 6:cda45a5e723e:

#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 {
        // @TODO Pitch bend sensitivity is fixed to 2
        float pitchModifier = pow(2.0, 2.0 * ch.getPitchBend(channel) / (12 * 8192));
        gemCore.setWave(targetInst, ch.getWave(channel));
        gemCore.noteOn(targetInst,
                       frequencyTable[noteNumber] * pitchModifier,
                       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:
        // 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]) {
    uint8_t channel = messageBytes[0] & 0x0f;
    int16_t value = (((messageBytes[2] & 0x7f) << 7) | (messageBytes[1] & 0x7f)) - 8192;
    // @TODO Pitch bend sensitivity is fixed to 2
    float pitchModifier = pow(2.0, 2.0 * value / (12 * 8192));
    
    // Register pitch bend value for succeeding notes
    ch.setPitchBend(channel, value);
    
    // Modify frequency of all existing notes in selected channel
    Instrument* instList = gemCore.getInstrumentList();
    for (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
        if (noteSent[i].channel == channel + 1) {
            instList[i].setFrequency(frequencyTable[noteSent[i].noteNumber]
                                     * pitchModifier);
        }
    }
}

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 resetAllControllers(char messageBytes[3]) {
    uint8_t channel = messageBytes[0] & 0x0f;
    
    for (uint8_t i = 0; i < 16; i++) {
        ch.setVolume(channel, 100);
        ch.setExpression(channel, 127);
        ch.setPitchBend(channel, 0);
    }
    
    if (dumpMode == DUMP_INST || dumpMode == DUMP_INST_DETAIL) {
        console.printf("%-12s", "Reset all ct");
        dumpInstrumentList();
    }
}

void sendSystemReset() {
    // Reset channel controller/programs
    for (uint8_t i = 0; i < 16; i++) {
        ch.setVolume(i, 100);
        ch.setExpression(i, 127);
        ch.setPitchBend(i, 0);
        ch.setWave(i, Wavetable::waveDefList[0]);
    }
    // Initialize all notes
    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 (uint16_t i = 0; i < NUM_INSTRUMENT; i++) {
        gemCore.noteOff(i);
        gemCore.volume(i, 100);
        gemCore.expression(i, 127);
    }
}