Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: TextLCD USBDevice mbed
main.cpp
- Committer:
- djbottrill
- Date:
- 2015-11-15
- Revision:
- 27:98dad49081ff
- Parent:
- 26:7b9267a5b3c3
- Child:
- 28:753d70280c19
File content as of revision 27:98dad49081ff:
/*
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.
*/
#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 on = 0
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]= {};
unsigned char oldkeybuf[128]= {};
int keystat[128]= {};
int keyidx=0; //Key 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 tval1=0;
int keyidx_t=0;
int lcdmode=0; //LCD Display Mode
int x =0;
int x1=1;
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};
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 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.
*/
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
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 decay
synthstat[i]=3; //Say note in decay
}
}
}
break;
case 3: //Check if decay has completed
if (synth[i]>=sample1_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]=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 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 (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 decay
synthstat[i]=3; //Say note in decay
}
}
}
break;
case 3: //Check if decay has completed
if (synth[i]>=sample2_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]=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 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;
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); //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");
key=0;
velocity=0;
chan=0;
onoff=0;
//Main Loop
while(1) {
diag2=1;
//Process notes off
for (ii=0; ii<16; ii++) {
if (synthstat[ii]==1 & keybuf[synthtab[ii]]==0) { //Note currently playing
synthstat[ii]=2; //Set Start decay
}
}
//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 decay
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
}
}
diag2=0;
wait_ms(1);
//Output to Shift register
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
sw[0]=p1;
sw[1]=p2;
sw[2]=p3;
sw[3]=p4;
sw[4]=p5;
sw[6]=sw1;
sw[7]=sw3;
//Additional code to de-bounce the K64F onboard switches again using multiple loops to de-bounce; has 3 states:
//0 - Button not pressed
//1 - Button pressed waiting for de-bounce count of 10 loops
//2 - Button press stable, this status can be read as the de-bounced status indicating the button press is clean
// De-bounce Switches
pressed=0;
for (swi=0; swi<8; swi++) { //Process 8 witches
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) {
lcd.locate(0,1);
lcd.printf("0123456789ABCDEF");
}
if (lcdmode==3) {
lcd.cls();
lcd.locate(0,0);
lcd.printf("Utilisation:");
}
if (lcdmode==4) {
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