USBMIDI sampled pipe organ uses real pipe organ samples to produce a realistic sound with 16 note polyphony. A serial output will drive external TPIC6A596 shift registers that could be used to drive pipe organ magnets (Solenoid valves)

Dependencies:   TextLCD USBDevice mbed

main.cpp

Committer:
djbottrill
Date:
2015-11-07
Revision:
8:df5f305aab22
Parent:
7:e1ea1623c6c2
Child:
9:b0f110c02b1b

File content as of revision 8:df5f305aab22:

/*
Program to drive organ magnets from a MIDI input
From Midi note 36 the notes are send to a 96 bit shift register organ magnet driver board based on 12 x TPIC6B595 shift registers
The program uses the DAC to generate 16 sampled audio channel outputs at 22.05Khz that mirror the solenoid outputs, but also run from
MIDI note 24 so the bottom 16' octave can be generated electronically if 16' pipes are not feasible.
In fact the sampled output could be tailored to fill in any missing note ranges,
Repeated pressing sw3 cycles through a number of display modes.
*/
#include "mbed.h"

//#include "Rohrflute.h"
//#include "Dubbelflojt.h"
//#include "Principal.h"
//#include "Bourdon.h"                                                                                //This is the best sample
#include "Bourdon_32.h" 

#include "USBMIDI.h"

#include "TextLCD.h"
//For Linksprite LCD Shield
//TextLCD lcd(PTC12, D9, D4, D5, D6, D7); // rs, e, d4-d7
//DigitalOut bl(D10);
//D8 is wrong on K64F definition it is actually PTC12 not PTA0

//For Midas 2x16 OLED Display
TextLCD lcd (D2, D3, D4, D5,D6, D7, TextLCD::LCD16x2, NC, NC, TextLCD::WS0010 ); // 4bit bus: RS, E, D4-D7, LCDType=LCD16x2, BL=NC, E2=NC, LCDTCtrl=WS0010

//Serial pc(USBTX, USBRX); // tx, rx

AnalogOut Aout(DAC0_OUT);

DigitalOut redled(LED1);
DigitalOut greenled(LED2);
DigitalOut blueled(LED3);

DigitalOut diag(D15);
DigitalOut diag2(D14);

DigitalIn sw1(PTC6);
DigitalIn sw3(PTA4);

//Wi-Fi connector J6 note pin 3 is incorrectly idenifed in the documentation as PTC12
DigitalOut SRCK(PTD7);                                                                                  //Shift Register clock
DigitalOut RCK(PTD5);                                                                                   //Shift Register latch
DigitalOut SER1(PTB20);                                                                                 //Shift Register serial data outout


Ticker output_ticker;                                                                                   //22.05Khz Ticker for sound generation

int keybuf[128]= {};
int oldkeybuf[128]= {};
int keystat[128]= {};
int keyidx=0;                                                                                           //Key index                                                                                     //Polyphony counter
int outidx=96;                                                                                          //Serial output index

int key=0;
int velocity=0;
int chan=0;
int onoff=0;

unsigned int audio_out;                                                                                 //Audio output value accumulator

//OLELD display bar graph characters
const char udc0[] = {0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00};
const char udc1[] = {0x15, 0x10, 0x10, 0x10, 0x10, 0x10, 0x15, 0x00};
const char udc2[] = {0x15, 0x04, 0x04, 0x04, 0x04, 0x04, 0x15, 0x00};
const char udc3[] = {0x15, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x00};
const char udc4[] = {0x15, 0x01, 0x01, 0x01, 0x01, 0x01, 0x15, 0x00};
const char udc5[] = {0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x15, 0x00};
const char udc6[] = {0x15, 0x05, 0x05, 0x05, 0x05, 0x05, 0x15, 0x00};
const char udc7[] = {0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x00};

//Variables for LCD/OLED display
unsigned long int lcd_stat=0;
unsigned long int lcd_stat_old=1;
char display[2][16];                                                                                    //Display Buffer
char display_old[2][16];                                                                                //Old Display Buffer
int pos=0;                                                                                              //Display Position counter
int row=0;
int tval=0;
int keyidx_t=0;
int lcdmode=0;                                                                                          //LCD Display Mode
int x =0;
int x1=1;
            
int sw1_count=0;                                                                                        //Switch de-bounce counter
int sw1_old=0;
int sw1_phase=0;
int sw3_count=0;                                                                                        //Switch de-bounce counter
int sw3_old=0;
int sw3_phase=0;

//Sound generator variables
int tempidx=0;
int freqidx=45;
float synth[16];
float synthidx[16];
int synth_old[16];
int synthtab[16]= {};
int synthstat[16]= {};
unsigned char synthoctave[16]= {};
unsigned char synthnote[16]= {};
int noteidx=0;
int i;
int ii;


/*
Ticker routine to calculate and output the audio signal.
As the DAC is 12 bit this allows for 16 concurrennt 8 bit samples running with no additional loss of precision.
The samples should be normalised to maximise the 8 bit range as only one sample is used there is no need to generate sounds with different amplitude.
Sound Generation is in 4 states stored in synthstat:
0 = Synth Channel idle
1 = Attack / Sustain phase note running and looping if necessary
2 = Signal to start decay this may be delayed until the sound is paat the initial attack point typically 100-200 mS
3 = Sample is in the decay phase.

This routine also handles the Synchronous output to a 96 bit shift register Organ Pallet magnet driver board, one bit is sent per tick.
The data clock pulse is generated during the 16 channel synth scan and the final latch pulse is generated on the 96th Tick.
All 96 bits are clocked out in around 4.4mS.
*/
void aout()
{
    diag=1;                                                                                             //Pulse DIAG Pin for Scoping purposes IE pin high
    redled=0;                                                                                           //Pulse Red LED to indicate Ticker working
    audio_out=0;                                                                                        //Clear Audio Accumulator

    SER1=keybuf[outidx+12];                                                                             //Send bit serially

    for (i =0; i<16; i++) {                                                                             //Do 16 channels
        switch (synthstat[i]) {
            case 1:     //Sustain phase
                if (synth[i]>sample_loop_end[synthoctave[i]]) {                                         //Got to end of buffer?
                    synth[i]=sample_loop_start[synthoctave[i]];                                         //Wrap around back to start
                }
                break;
            case 2:     //Note off demand state
                if (synth[i]>=sample_attack[synthoctave[i]]) {                                          //Delay decay phase if not past end of attack
//Check if the waveform is close to zero crossing this helps eliminate clicks looks for rising edge approaching 0x80
                    tempidx=synth[i];
                    if (sample[synthoctave[i]][tempidx]<128 & sample[synthoctave[i]][tempidx]>120) {
                        if (sample[synthoctave[i]][tempidx]>synth_old[i]) {
                            synth[i]=sample_loop_off[synthoctave[i]];                                   //Jump to start of decay
                            synthstat[i]=3;                                                             //Say note in decay
                        }
                    }
                }
                break;
            case 3:    //Check if decay has completed
                if (synth[i]>=sample_len[synthoctave[i]]) {                                             //End of decay?
                    synthstat[i]=0;                                                                     //say channel free
                    synth[i]=0;                                                                         //Set sample pointer to 0
                    synthidx[i]=0;                                                                      //Set sample index to 0
                    synthtab[i]=255;                                                                    //Set to invalid
                }
                break ;
        }

//get sample and add to Audio Accumulator
        synth_old[i]=sample[synthoctave[i]][(int)(synth[i])];                                           //Get and save old sample
        audio_out=audio_out+synth_old[i];                                                               //add sample to audio out accumulator
        synth[i]=synth[i]+synthidx[i];                                                                  //Get next sample pointer


//Generate clock pulses to send data serially
        switch (i) {
            case 2:
                SRCK=1;                                                                                 //Generate serial output clock pulse
                break;
            case 5:
                SRCK=0;
                break;
            case 10:
                if (outidx==0) {
                    RCK=1;                                                                              //Generate latch strobe after 96 bits sent
                }
                break;
            case 15:
                if (outidx==0) {
                    RCK=0;                                                                              //Generate latch strobe after 96 bits sent
                    outidx=96;
                }
                break;
        }

    }                                                                                                   //Next Note
    outidx--;                                                                                           //Next serial data bit


//Output to DAC
    Aout.write_u16(audio_out*16);

    redled=1;
    diag=0;
}

/*
Interrupt routine to receive MIDI on/off message and set LED status accordingly
MIDI note on/off events directly start/stop the synth channels and store the note status in keytab.
*/
void get_message(MIDIMessage msg)
{
    greenled=0;                                                                                         //Pulse Green LED to indicate MIDI activity
    key=msg.key();
    velocity=msg.velocity();
    chan=msg.channel();
    switch (msg.type()) {
        case MIDIMessage::NoteOnType:                                                                   //MIDI note on received
            if (key>23 & key<120) {
                onoff=1;
                keybuf[(key-24)]=1;                                                                     //Store in keybuf, except for notes 24-35
//Process keys on
//Find a free note generator and start the note playing, if no slots available just ignore the note
                for (ii=0; ii<16; ii++) {
                    if (synthtab[ii]==(key-24) & synthstat[ii]>0) {                                     //Is synth already playing the note?
                        synth[ii]=0;                                                                    //Set sample pointer to 0
                        synthstat[ii]=1;                                                                //Restart the note
                        break;                                                                          //exit loop
                    }
                    if (synthstat[ii]==0) {                                                             //Is synth channel free?
                        synthtab[ii]=key-24;                                                            //Store note value
                        synthoctave[ii]=synthtab[ii]/12;                                                //Store the octave
                        synthnote[ii]= synthtab[ii]%12;                                                 //Note within the octave
                        synthstat[ii]=1;                                                                //Set status to playing
                        synthidx[ii]=freqtab[synthnote[ii]];                                            //Set the frequency
                        break;                                                                          //exit loop
                    }                                                                                   //Next Synth slot
                }
            }

            break;

//Process keys off
        case MIDIMessage::NoteOffType:                                                                  //Midi note off received
            if (key>23) {
                onoff=0;
                keybuf[(key-24)]=0;                                                                     //Update keybuf
                for (ii=0; ii<16; ii++) {
                    if (synthstat[ii]==1 & synthtab[ii]==(key-24)) {                                    //Note currently playing
                        synthstat[ii]=2;                                                                //Set Start decay
                        break;                                                                          //Exit loop
                    }
                }
            }
            break;

//Process all notes off command
        case MIDIMessage::AllNotesOffType:                                                              //Midi all notes off
            for (ii=0; ii<108; i++) {
                keybuf[ii]=0;                                                                           //Clear Keybuf
            }
            for (ii=0; ii>16; ii++) {                                                                   //Kill any sampled notes playing
                synthstat[ii]=0;                                                                        //say channel free
                synth[ii]=0;                                                                            //Set sample pointer to 0
                synthidx[ii]=0;                                                                         //Set sample index to 0
                synthtab[ii]=255;                                                                       //Set to invalid
            }
            break;

//Any other MIDI Commands just ignore
        default:
            break;
    }
    greenled=1;
}


int main()
{
    greenled=1;
    redled=1;
    blueled=1;
    SRCK=0;                                                                                             //Serial Clock low
    RCK=0;                                                                                              //Latch Clock low

    sw1.mode(PullUp);
    sw3.mode(PullUp);

    output_ticker.attach(&aout, sample_rate);                                                           //Set Sample frequency

    lcd.cls();
    wait(0.1);
    lcd.setCursor(TextLCD::CurOff_BlkOff);
    lcd.cls();
    lcd.printf("MIDI Pipe Organ Waiting for USB");

    NVIC_SetPriority(USB0_IRQn, 99);                                                                    //Reduce Interrupt priority of USB

    USBMIDI midi;                                                                                       //Start up MIDI
    midi.attach(get_message);                                                                           //callback for MIDI messages received

    lcd.cls();
    lcd.printf("MIDI Pipe Organ");
    wait(1);
    lcd.cls();
    lcd.printf("On Chan Note Vel");
//Program user defined characters into OLED display for bar graph
    lcd.setUDC(0, (char *) udc0);
    lcd.setUDC(1, (char *) udc1);
    lcd.setUDC(2, (char *) udc2);
    lcd.setUDC(3, (char *) udc3);
    lcd.setUDC(4, (char *) udc4);
    lcd.setUDC(5, (char *) udc5);
    lcd.setUDC(6, (char *) udc6);
    lcd.setUDC(7, (char *) udc7);

    key=0;
    velocity=0;
    chan=0;
    onoff=0;

//Main Loop
    while(1)  {

        /*
        Additional code to de-bounce the K64F onboard switches again using multiple ticks to de-bounce; has 4 states:
        0 - Button not pressed
        1 - Button pressed waiting for de-bounce count of 100 ticks (4.53 mS)
        2 - Button press stable, this staus can be read as the de-bounced status indicating the button press is clean
        */
// De-bounce SW1
        if (sw1_phase==0 & sw1==0) {                                                                        //Button just been pressed
            sw1_phase=1;
            sw1_count=10;
        }
        if (sw1_phase==1 & sw1_count==0 & sw1==0) {                                                         //Button still pressed after de-bounce period
            sw1_phase=2;
        }
        if (sw1_phase==2 & sw1==1) {                                                                        //Button  released
            sw1_phase=0;
        }
        sw1_count--;
// De-bounce SW1
        if (sw3_phase==0 & sw3==0) {                                                                        //Button just been pressed
            sw3_phase=1;
            sw3_count=10;
        }
        if (sw3_phase==1 & sw3_count==0 & sw3==0) {                                                         //Button still pressed after de-bounce period
            sw3_phase=2;
        }

        if (sw3_phase==2 & sw3==1) {                                                                        //Button  released
            sw3_phase=0;
        }
        sw3_count--;




        if (sw1_phase==2 | sw3_phase==2) {
            blueled=0;
        } else {
            blueled=1;
        }


//Check for display mode button being presed
        if (sw3_phase != sw3_old) {                                                                     //Mode Switch pressed
            if(sw3_phase==2) {
                lcdmode++;
                if (lcdmode>3) {
                    lcdmode=0;
                }
                lcd.cls();
                if (lcdmode==0) {
                    lcd.printf("On Chan Note Vel");
                }
                if (lcdmode==1) {
                    //Initialise bar graph display

                    for (row=0; row<2; row++) {
                        for (pos=0; pos<16; pos++) {
                            display[row][pos]=0;
                            display_old[row][pos]=1;
                        }
                    }
                }
                if (lcdmode==2) {
                    lcd.locate(0,1);
                    lcd.printf("0123456789ABCDEF");
                }
                if (lcdmode==3) {
                    lcd.cls();
                    lcd.locate(0,0);
                    lcd.printf("Utilisation:");
                }
            }
        }
        sw3_old=sw3_phase;



// OLED MIDI Status display
        if (lcdmode==0) {
            lcd_stat=key+(128*velocity)+(16384*chan)+(262144*onoff);
            if (lcd_stat!=lcd_stat_old) {

                lcd.locate(0,1);
                lcd.printf("%1d   %2d  %3d  %3d", onoff, chan, key, velocity);
                lcd_stat_old=lcd_stat;
            }
        }

// OLED MIDI Bar Display
        if (lcdmode==1) {
            keyidx=0;
            for (row=1; row>=0; row--) {
                for (pos=0; pos<16; pos++) {
                    keyidx_t=keyidx*2;
                    if(keyidx_t>94) {
                        keyidx_t=keyidx_t-95;
                    }
                    tval=keybuf[keyidx_t];
                    keyidx++;
                    keyidx_t=keyidx*2;
                    if(keyidx_t>94) {
                        keyidx_t=keyidx_t-95;
                    }
                    tval=tval+(2*keybuf[keyidx_t]);
                    keyidx++;

                    keyidx_t=keyidx*2;
                    if(keyidx_t>94) {
                        keyidx_t=keyidx_t-95;
                    }
                    tval=tval+(4*keybuf[keyidx_t]);
                    keyidx++;
                    display[row][pos]=tval;

                    if(display[row][pos]!=display_old[row][pos]) {
                        lcd.locate(pos,row);
                        lcd.putc(display[row][pos]);
                        display_old[row][pos]=display[row][pos];
                    }
                }
            }
        }

//Display status of the Synth channels
        if (lcdmode==2) {
            lcd.locate(0,0);
            for (noteidx=0; noteidx<16; noteidx++) {
                lcd.putc((synthstat[noteidx]+48));
            }
        }

//Display utilisation bar graph
        if (lcdmode==3) {
            x=0;
            for (noteidx=0; noteidx<16; noteidx++) {
                if (synthstat[noteidx] >0) {
                    x++;
                }
            }
            for (pos=0; pos<x; pos++) {
                display[1][pos]=0xff;
            }
            while(pos<16) {
                display[1][pos]=0x20;
                pos++;
            }
            row=1;
            for (pos=0; pos<16; pos++) {
                if (x!=x1) {
                    lcd.locate(13,0);
                    lcd.printf("%2d", x);
                    x1=x;
                }
                if(display[row][pos]!=display_old[row][pos]) {
                    lcd.locate(pos,row);
                    lcd.putc(display[row][pos]);
                    display_old[row][pos]=display[row][pos];
                }
            }
        }



    }                                                                                                   //End of main loop
}                                                                                                       //End of program