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-20
- Revision:
- 30:f7aa90a1e443
- Parent:
- 28:753d70280c19
File content as of revision 30:f7aa90a1e443:
/* 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 up to 44.1Khz 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. Pressing sw1 switches between two sound samples Bourdon and Montre only one rank is availble at any one time. This program is currently only compatible with the K64F as it's one of the few MBED boards that has sufficient speed, Flash ROM storage and of course a DAC. The sounds samples are derived from the Jeux D'Orgue 2 sample set http://www.jeuxdorgues.com and are used with permission. */ #include "mbed.h" #include "Bourdon.h" #include "Montre.h" /* As the ticker cannot be accurately controlled it is necessary to apply a fine adjustment capability hence the fine tune below that is used to multiply the step index as read from freqtab. freqtab is based on the assumption the ticker is running at exactly 44.1Khz */ const float sample_rate = 22.68; //44.1KHz const float freqfine=0.970; const float freqtab[] = {1, 1.0596, 1.1227, 1.1893, 1.2599, 1.3348, 1.4144, 1.4985, 1.5872, 1.6819, 1.782, 1.888, 2}; #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); //Stop switches mommentary connection to 0V will toggle on and off. 8' pitch defaults to on at power up. DigitalIn p1 (PTB2); //16' DigitalIn p2 (PTB3); //8' DigitalIn p3 (PTB10); //4' DigitalIn p4 (PTB11); //2 2/3' DigitalIn p5 (PTC11); //2' //Wi-Fi connector J6 note pin 3 is incorrectly idenifed in the documentation as PTC12 //Connects the shift register solenoid driver board DigitalOut SRCK(PTD7); //Shift Register clock DigitalOut RCK(PTD5); //Shift Register latch DigitalOut SER1(PTB20); //Shift Register serial data outout Ticker output_ticker; //Ticker for sound generation unsigned char keybuf[128]= {}; //Note buffer unsigned char oldkeybuf[128]= {}; //Last note buffer int keyidx=0; //Key index //Received MIDI note variables 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 tval1=0; int keyidx_t=0; int lcdmode=0; //LCD Display Mode int x =0; int x1=1; //Button and stop variables unsigned int swi; unsigned int pressed; unsigned int sw[8]; unsigned int sw_count[8]; //Switch de-bounce counter unsigned int sw_phase[8]; unsigned int sw_status[8]= {0,1,0,0,0,0,0,0}; //Stop status 8' on by default unsigned int sw_old[8]; int oldstops=1; //Sound generator variables int sampleset=0; 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 sucess=0; int noteidx=0; int i; int ii; int cl; /* Ticker routine to calculate and output the audio signal. As the DAC is 12 bit this allows for 16 concurrent 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 release this may be delayed until the sound is paat the initial attack/decay point typically 100-200 mS 3 = Sample is in the release phase. */ void aout() { //The lines below are commented out and actually add to much time to the ticker routine each command adds around 1 uS which is significant //when the whole routine only takes around 16uS and is repeated every 23mS // 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 if (sampleset==0) { for (i =0; i<16; i++) { //Do 16 channels switch (synthstat[i]) { case 1: //Sustain phase if (synth[i]>sample1_loop_end[synthoctave[i]]) { //Got to end of buffer? synth[i]=sample1_loop_start[synthoctave[i]]; //Wrap around back to start } break; case 2: //Note off demand state if (synth[i]>=sample1_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 (sample1[synthoctave[i]][tempidx]<128 & sample1[synthoctave[i]][tempidx]>120) { if (sample1[synthoctave[i]][tempidx]>synth_old[i]) { synth[i]=sample1_loop_off[synthoctave[i]]; //Jump to start of release synthstat[i]=3; //Say note in release } } } break; case 3: //Check if decay has completed if (synth[i]>=sample1_len[synthoctave[i]]) { //End of release? 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]=sample1[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 } //Next Note } else { //Sample Set 2 for (i =0; i<16; i++) { //Do 16 channels switch (synthstat[i]) { case 1: //Sustain phase if (synth[i]>sample2_loop_end[synthoctave[i]]) { //Got to end of buffer? synth[i]=sample2_loop_start[synthoctave[i]]; //Wrap around back to start } break; case 2: //Note off demand state if (synth[i]>=sample2_attack[synthoctave[i]]) { //Delay release 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 (sample2[synthoctave[i]][tempidx]<128 & sample2[synthoctave[i]][tempidx]>120) { if (sample2[synthoctave[i]][tempidx]>synth_old[i]) { synth[i]=sample2_loop_off[synthoctave[i]]; //Jump to start of release synthstat[i]=3; //Say note in release } } } break; case 3: //Check if release has completed if (synth[i]>=sample2_len[synthoctave[i]]) { //End of release? 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]=sample2[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 } //Next Note } //Output to DAC Aout.write_u16(audio_out*16); // redled=1; // diag=0; } /* Interrupt routine to receive MIDI on/off message MIDI note on/off events are stored in the note status buffer keytab. the lower 5 bits of keytab represent a note being on or off Bit 0 = 16' Bit 1 = 8' Bit 2 = 4' Bit 3 = 2 2/3' (12th) Bit 4 = 2' (15th) Thus if keytab >0 then the note should be played Currently MIDI note on/off messages are accepted on any channel */ 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; if (sw_status[0]==1 & key >35) { keybuf[(key-36)]|= 1 << 0; //Store @ 16' pitch } if (sw_status[1]==1) { keybuf[(key-24)]|= 1 << 1; //Store @ 8' pitch } if (sw_status[2]==1& key<108) { keybuf[(key)-12]|= 1 << 2; //Store @ 4' pitch } if (sw_status[3]==1 & key<101) { keybuf[(key)-5]|= 1 << 3; //Store @ 2 2/3' pitch } if (sw_status[4]==1 & key <96) { keybuf[(key)]|= 1 << 4; //Store @ 2' pitch } } break; //Process keys off case MIDIMessage::NoteOffType: //Midi note off received if (key >23 & key<120) { onoff=0; if (key>35) { keybuf[(key-36)]&= ~(1 << 0); //Kill note @ 16' pitch } keybuf[(key-24)]&= ~(1 << 1); //Kill note @ 8' pitch if (key<108) { keybuf[(key-12)]&= ~(1 << 2); //Kill note @ 4' pitch } if (key<101) { keybuf[(key-5)]&= ~(1 << 3); //Kill note @ 2 2/3' pitch } if (key<96) { keybuf[(key)]&= ~(1 << 4); //Kill note @ 2' pitch } } break; //Process all notes off command case MIDIMessage::AllNotesOffType: //Midi all notes off for (int it=0; it<108; it++) { keybuf[it]=0; //Clear Keybuf } 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); p1.mode(PullUp); p2.mode(PullUp); p3.mode(PullUp); p4.mode(PullUp); p5.mode(PullUp); output_ticker.attach_us(&aout, sample_rate); //Start the ticker lcd.cls(); wait(0.1); lcd.setCursor(TextLCD::CurOff_BlkOff); lcd.cls(); lcd.printf("MIDI Pipe Organ Waiting for USB"); /* I couldn't figure out which interrupt is used for the ticker so I lowered the USB interrupt priority so that it didn't interfere with the smooth operation of the sound generation ticker */ 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"); key=0; velocity=0; chan=0; onoff=0; //Main Loop while(1) { //Process notes off for (ii=0; ii<16; ii++) { //Scan through 16 channels if (synthstat[ii]==1 & keybuf[synthtab[ii]]==0) { //Note currently playing but should be off synthstat[ii]=2; //Set Start release } } //Process keys on //Find a free note generator and start the note playing, if no slots available just ignore the note for (keyidx=95; keyidx>=0; keyidx--) { if (keybuf[keyidx]>0 & oldkeybuf[keyidx]==0) { //Key is pressed //Find and use any channel that is free sucess=0; ii=0; while (ii<16) { //Find available synth channel if (synthstat[ii]==0) { //Is synth channel free? synthtab[ii]=keyidx; //Store note value synthoctave[ii]=synthtab[ii]/12; //Store the octave //Uncomment the following lines if you want to wrap around the top octave // if (synthoctave[ii]==7) { //Wrap around the top octave // synthoctave[ii]--; // } synthnote[ii]= synthtab[ii]%12; //Note within the octave synthstat[ii]=1; //Set status to playing synthidx[ii]=freqtab[synthnote[ii]]*freqfine; //Set the frequency sucess=1; ii=16; //exit loop } //Next Synth slot ii++; } //As a last resort find any channel that is in release if (sucess==0) { ii=0; while (ii<16) { //Find available synth channel if (synthstat[ii]==3) { //Is synth channel free? synthtab[ii]=keyidx; //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]]*freqfine; //Set the frequency sucess=1; ii=16; //exit loop } //Next Synth slot ii++; } } } if (sucess==1) { oldkeybuf[keyidx]=keybuf[keyidx]; //Store old value if note sucessfull } //If not sucessfull try on the next scan } //Output to Shift register pallet magnet driver board for (keyidx=107; keyidx>11; keyidx--) { if (keybuf[keyidx]>0) { SER1=1; } else { SER1=0; } SRCK=1; //Pulse Serial clock wait_us(4); SRCK=0; wait_us(4); } //Next bit RCK=1; //Transfer data to outputs wait_us(4); RCK=0; wait_us(4); //Read Stop inputs and onboard switches sw[0]=p1; sw[1]=p2; sw[2]=p3; sw[3]=p4; sw[4]=p5; sw[6]=sw1; sw[7]=sw3; /* code to de-bounce the K64F onboard and external switches using multiple loops to de-bounce; has 3 states: 0 - Button not pressed 1 - Button pressed waiting for de-bounce count of 5 loops 2 - Button press stable, this status can be read as the de-bounced status indicating the button press is clean sw_status is toggled by each sucessive key press 0 / 1 */ pressed=0; for (swi=0; swi<8; swi++) { //Process 8 push switches if (sw_phase[swi]==0 & sw[swi]==0) { //Button just been pressed sw_phase[swi]=1; sw_count[swi]=5; } if (sw_phase[swi]==1 & sw_count[swi]==0 & sw[swi]==0) { //Button still pressed after de-bounce period sw_phase[swi]=2; pressed++; //Inc button pressed count sw_status[swi]=!sw_status[swi]; //Toggle switch status } if (sw_phase[swi]==1 & sw_count[swi]==0 & sw[swi]==1) { //Button no longer pressed after de-bounce period sw_phase[swi]=0; } if (sw_phase[swi]==2 & sw[swi]==1) { //Button released sw_phase[swi]=0; } sw_count[swi]--; } //light blue LED if any button presed if (pressed>0) { blueled=0; } else { blueled=1; } //Button 1 will clear all playing notes & switch sampleset if (sw_status[6] != sw_old[6]) { for (cl=0; cl<108; cl++) { keybuf[cl]=0; //Clear Keybuf } sampleset=sw_status[6]; oldstops=255; //Force a display refresh sw_old[6]=sw_status[6]; } //Check for display mode button being presed if (sw_status[7] != sw_old[7]) { //Mode Switch pressed lcdmode++; if (lcdmode>4) { lcdmode=0; } lcd.cls(); if (lcdmode==0) { lcd.printf("On Chan Note Vel"); } if (lcdmode==1) { //Initialise bar graph display //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); for (row=0; row<2; row++) { for (pos=0; pos<16; pos++) { display[row][pos]=0; display_old[row][pos]=1; } } } if (lcdmode==2) { //Initilise synth channel status display lcd.locate(0,1); lcd.printf("0123456789ABCDEF"); } //Initialise synth channel unilisation if (lcdmode==3) { lcd.cls(); lcd.locate(0,0); lcd.printf("Utilisation:"); } if (lcdmode==4) { //Initialise Stops selection display oldstops=255; //Force a refresh } sw_old[7]=sw_status[7]; } // 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]; if(tval>0) { tval=1; } keyidx++; keyidx_t=keyidx*2; if(keyidx_t>94) { keyidx_t=keyidx_t-95; } tval1=keybuf[keyidx_t]; if (tval1>0) { tval1=1; } tval=tval+(2*tval1); keyidx++; keyidx_t=keyidx*2; if(keyidx_t>94) { keyidx_t=keyidx_t-95; } tval1=keybuf[keyidx_t]; if (tval1>0) { tval1=1; } tval=tval+(4*tval1); 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]; } } } //Stop display if (lcdmode==4) { pos=sw_status[0] + sw_status[1]+sw_status[2]+sw_status[3]+sw_status[4]; if (oldstops != pos) { oldstops=pos; lcd.cls(); for (pos=0; pos<16; pos++) { lcd.locate(pos,0); if (sampleset==0) { lcd.putc(stopname1[pos]); } else { lcd.putc(stopname2[pos]); } } lcd.locate(0,1); if (sw_status[0]==1) { lcd.printf("16 "); } if (sw_status[1]==1) { lcd.printf("8 "); } if (sw_status[2]==1) { lcd.printf("4 "); } if (sw_status[3]==1) { lcd.printf("2-2/3 "); } if (sw_status[4]==1) { lcd.printf("2"); } } } } //End of main loop } //End of program