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@15:c1854904f0d7, 2015-11-10 (annotated)
- Committer:
- djbottrill
- Date:
- Tue Nov 10 15:50:18 2015 +0000
- Revision:
- 15:c1854904f0d7
- Parent:
- 13:decb94698003
- Child:
- 16:d2694fb530c3
Added a new Bourdon sample at 44.1Khz, removed diagnostic LED and scope outputs from the Ticker routine and moved the serial output to the main loop to reduced the execution time of the ticker.
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
djbottrill | 0:4d06e0867376 | 1 | /* |
djbottrill | 0:4d06e0867376 | 2 | Program to drive organ magnets from a MIDI input |
djbottrill | 0:4d06e0867376 | 3 | 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 |
djbottrill | 9:b0f110c02b1b | 4 | The program uses the DAC to generate 16 sampled audio channel outputs at up to 32Khz that mirror the solenoid outputs, but also run from |
djbottrill | 0:4d06e0867376 | 5 | MIDI note 24 so the bottom 16' octave can be generated electronically if 16' pipes are not feasible. |
djbottrill | 0:4d06e0867376 | 6 | In fact the sampled output could be tailored to fill in any missing note ranges, |
djbottrill | 0:4d06e0867376 | 7 | Repeated pressing sw3 cycles through a number of display modes. |
djbottrill | 0:4d06e0867376 | 8 | */ |
djbottrill | 0:4d06e0867376 | 9 | #include "mbed.h" |
djbottrill | 0:4d06e0867376 | 10 | |
djbottrill | 5:13a1e5e4494d | 11 | //#include "Rohrflute.h" |
djbottrill | 2:8b22402c0b0f | 12 | //#include "Dubbelflojt.h" |
djbottrill | 2:8b22402c0b0f | 13 | //#include "Principal.h" |
djbottrill | 9:b0f110c02b1b | 14 | //#include "Bourdon.h" |
djbottrill | 15:c1854904f0d7 | 15 | //#include "Bourdon_32.h" //This is the best sample 32Khz |
djbottrill | 15:c1854904f0d7 | 16 | #include "Bourdon_44.h" |
djbottrill | 0:4d06e0867376 | 17 | |
djbottrill | 0:4d06e0867376 | 18 | #include "USBMIDI.h" |
djbottrill | 0:4d06e0867376 | 19 | |
djbottrill | 0:4d06e0867376 | 20 | #include "TextLCD.h" |
djbottrill | 0:4d06e0867376 | 21 | //For Linksprite LCD Shield |
djbottrill | 0:4d06e0867376 | 22 | //TextLCD lcd(PTC12, D9, D4, D5, D6, D7); // rs, e, d4-d7 |
djbottrill | 0:4d06e0867376 | 23 | //DigitalOut bl(D10); |
djbottrill | 0:4d06e0867376 | 24 | //D8 is wrong on K64F definition it is actually PTC12 not PTA0 |
djbottrill | 0:4d06e0867376 | 25 | |
djbottrill | 0:4d06e0867376 | 26 | //For Midas 2x16 OLED Display |
djbottrill | 0:4d06e0867376 | 27 | 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 |
djbottrill | 0:4d06e0867376 | 28 | |
djbottrill | 0:4d06e0867376 | 29 | //Serial pc(USBTX, USBRX); // tx, rx |
djbottrill | 0:4d06e0867376 | 30 | |
djbottrill | 0:4d06e0867376 | 31 | AnalogOut Aout(DAC0_OUT); |
djbottrill | 0:4d06e0867376 | 32 | |
djbottrill | 0:4d06e0867376 | 33 | DigitalOut redled(LED1); |
djbottrill | 0:4d06e0867376 | 34 | DigitalOut greenled(LED2); |
djbottrill | 0:4d06e0867376 | 35 | DigitalOut blueled(LED3); |
djbottrill | 0:4d06e0867376 | 36 | |
djbottrill | 0:4d06e0867376 | 37 | DigitalOut diag(D15); |
djbottrill | 0:4d06e0867376 | 38 | DigitalOut diag2(D14); |
djbottrill | 0:4d06e0867376 | 39 | |
djbottrill | 0:4d06e0867376 | 40 | DigitalIn sw1(PTC6); |
djbottrill | 0:4d06e0867376 | 41 | DigitalIn sw3(PTA4); |
djbottrill | 0:4d06e0867376 | 42 | |
djbottrill | 12:74b52900cddb | 43 | //Stop switches on = 0 |
djbottrill | 15:c1854904f0d7 | 44 | DigitalIn p1 (PTB2); //16' |
djbottrill | 15:c1854904f0d7 | 45 | DigitalIn p2 (PTB3); //8' |
djbottrill | 15:c1854904f0d7 | 46 | DigitalIn p3 (PTB10); //4' |
djbottrill | 15:c1854904f0d7 | 47 | DigitalIn p4 (PTB11); //2 2/3' |
djbottrill | 15:c1854904f0d7 | 48 | DigitalIn p5 (PTC11); //2' |
djbottrill | 12:74b52900cddb | 49 | |
djbottrill | 0:4d06e0867376 | 50 | //Wi-Fi connector J6 note pin 3 is incorrectly idenifed in the documentation as PTC12 |
djbottrill | 9:b0f110c02b1b | 51 | //Connects the shift register solenoid driver board |
djbottrill | 0:4d06e0867376 | 52 | DigitalOut SRCK(PTD7); //Shift Register clock |
djbottrill | 0:4d06e0867376 | 53 | DigitalOut RCK(PTD5); //Shift Register latch |
djbottrill | 0:4d06e0867376 | 54 | DigitalOut SER1(PTB20); //Shift Register serial data outout |
djbottrill | 0:4d06e0867376 | 55 | |
djbottrill | 0:4d06e0867376 | 56 | |
djbottrill | 9:b0f110c02b1b | 57 | Ticker output_ticker; //Ticker for sound generation |
djbottrill | 0:4d06e0867376 | 58 | |
djbottrill | 11:a27a71f7c149 | 59 | unsigned char keybuf[128]= {}; |
djbottrill | 11:a27a71f7c149 | 60 | unsigned char oldkeybuf[128]= {}; |
djbottrill | 0:4d06e0867376 | 61 | int keystat[128]= {}; |
djbottrill | 0:4d06e0867376 | 62 | int keyidx=0; //Key index //Polyphony counter |
djbottrill | 15:c1854904f0d7 | 63 | //int outidx=96; //Serial output index |
djbottrill | 0:4d06e0867376 | 64 | |
djbottrill | 0:4d06e0867376 | 65 | int key=0; |
djbottrill | 0:4d06e0867376 | 66 | int velocity=0; |
djbottrill | 0:4d06e0867376 | 67 | int chan=0; |
djbottrill | 0:4d06e0867376 | 68 | int onoff=0; |
djbottrill | 0:4d06e0867376 | 69 | |
djbottrill | 0:4d06e0867376 | 70 | unsigned int audio_out; //Audio output value accumulator |
djbottrill | 0:4d06e0867376 | 71 | |
djbottrill | 0:4d06e0867376 | 72 | //OLELD display bar graph characters |
djbottrill | 0:4d06e0867376 | 73 | const char udc0[] = {0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00}; |
djbottrill | 0:4d06e0867376 | 74 | const char udc1[] = {0x15, 0x10, 0x10, 0x10, 0x10, 0x10, 0x15, 0x00}; |
djbottrill | 0:4d06e0867376 | 75 | const char udc2[] = {0x15, 0x04, 0x04, 0x04, 0x04, 0x04, 0x15, 0x00}; |
djbottrill | 0:4d06e0867376 | 76 | const char udc3[] = {0x15, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x00}; |
djbottrill | 0:4d06e0867376 | 77 | const char udc4[] = {0x15, 0x01, 0x01, 0x01, 0x01, 0x01, 0x15, 0x00}; |
djbottrill | 0:4d06e0867376 | 78 | const char udc5[] = {0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x15, 0x00}; |
djbottrill | 0:4d06e0867376 | 79 | const char udc6[] = {0x15, 0x05, 0x05, 0x05, 0x05, 0x05, 0x15, 0x00}; |
djbottrill | 0:4d06e0867376 | 80 | const char udc7[] = {0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x00}; |
djbottrill | 0:4d06e0867376 | 81 | |
djbottrill | 0:4d06e0867376 | 82 | //Variables for LCD/OLED display |
djbottrill | 0:4d06e0867376 | 83 | unsigned long int lcd_stat=0; |
djbottrill | 0:4d06e0867376 | 84 | unsigned long int lcd_stat_old=1; |
djbottrill | 0:4d06e0867376 | 85 | char display[2][16]; //Display Buffer |
djbottrill | 0:4d06e0867376 | 86 | char display_old[2][16]; //Old Display Buffer |
djbottrill | 0:4d06e0867376 | 87 | int pos=0; //Display Position counter |
djbottrill | 0:4d06e0867376 | 88 | int row=0; |
djbottrill | 0:4d06e0867376 | 89 | int tval=0; |
djbottrill | 12:74b52900cddb | 90 | int tval1=0; |
djbottrill | 0:4d06e0867376 | 91 | int keyidx_t=0; |
djbottrill | 0:4d06e0867376 | 92 | int lcdmode=0; //LCD Display Mode |
djbottrill | 8:df5f305aab22 | 93 | int x =0; |
djbottrill | 8:df5f305aab22 | 94 | int x1=1; |
djbottrill | 9:b0f110c02b1b | 95 | |
djbottrill | 0:4d06e0867376 | 96 | int sw1_count=0; //Switch de-bounce counter |
djbottrill | 0:4d06e0867376 | 97 | int sw1_old=0; |
djbottrill | 0:4d06e0867376 | 98 | int sw1_phase=0; |
djbottrill | 0:4d06e0867376 | 99 | int sw3_count=0; //Switch de-bounce counter |
djbottrill | 0:4d06e0867376 | 100 | int sw3_old=0; |
djbottrill | 0:4d06e0867376 | 101 | int sw3_phase=0; |
djbottrill | 0:4d06e0867376 | 102 | |
djbottrill | 0:4d06e0867376 | 103 | //Sound generator variables |
djbottrill | 0:4d06e0867376 | 104 | int tempidx=0; |
djbottrill | 0:4d06e0867376 | 105 | int freqidx=45; |
djbottrill | 0:4d06e0867376 | 106 | float synth[16]; |
djbottrill | 0:4d06e0867376 | 107 | float synthidx[16]; |
djbottrill | 0:4d06e0867376 | 108 | int synth_old[16]; |
djbottrill | 0:4d06e0867376 | 109 | int synthtab[16]= {}; |
djbottrill | 0:4d06e0867376 | 110 | int synthstat[16]= {}; |
djbottrill | 4:9f63e68b548e | 111 | unsigned char synthoctave[16]= {}; |
djbottrill | 4:9f63e68b548e | 112 | unsigned char synthnote[16]= {}; |
djbottrill | 12:74b52900cddb | 113 | int sucess=0; |
djbottrill | 0:4d06e0867376 | 114 | int noteidx=0; |
djbottrill | 0:4d06e0867376 | 115 | int i; |
djbottrill | 0:4d06e0867376 | 116 | int ii; |
djbottrill | 9:b0f110c02b1b | 117 | int cl; |
djbottrill | 0:4d06e0867376 | 118 | |
djbottrill | 0:4d06e0867376 | 119 | /* |
djbottrill | 0:4d06e0867376 | 120 | Ticker routine to calculate and output the audio signal. |
djbottrill | 0:4d06e0867376 | 121 | As the DAC is 12 bit this allows for 16 concurrennt 8 bit samples running with no additional loss of precision. |
djbottrill | 0:4d06e0867376 | 122 | 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. |
djbottrill | 5:13a1e5e4494d | 123 | Sound Generation is in 4 states stored in synthstat: |
djbottrill | 0:4d06e0867376 | 124 | 0 = Synth Channel idle |
djbottrill | 0:4d06e0867376 | 125 | 1 = Attack / Sustain phase note running and looping if necessary |
djbottrill | 5:13a1e5e4494d | 126 | 2 = Signal to start decay this may be delayed until the sound is paat the initial attack point typically 100-200 mS |
djbottrill | 0:4d06e0867376 | 127 | 3 = Sample is in the decay phase. |
djbottrill | 0:4d06e0867376 | 128 | |
djbottrill | 5:13a1e5e4494d | 129 | This routine also handles the Synchronous output to a 96 bit shift register Organ Pallet magnet driver board, one bit is sent per tick. |
djbottrill | 0:4d06e0867376 | 130 | The data clock pulse is generated during the 16 channel synth scan and the final latch pulse is generated on the 96th Tick. |
djbottrill | 0:4d06e0867376 | 131 | All 96 bits are clocked out in around 4.4mS. |
djbottrill | 0:4d06e0867376 | 132 | */ |
djbottrill | 0:4d06e0867376 | 133 | void aout() |
djbottrill | 0:4d06e0867376 | 134 | { |
djbottrill | 15:c1854904f0d7 | 135 | // diag=1; //Pulse DIAG Pin for Scoping purposes IE pin high |
djbottrill | 15:c1854904f0d7 | 136 | // redled=0; //Pulse Red LED to indicate Ticker working |
djbottrill | 0:4d06e0867376 | 137 | audio_out=0; //Clear Audio Accumulator |
djbottrill | 0:4d06e0867376 | 138 | |
djbottrill | 0:4d06e0867376 | 139 | for (i =0; i<16; i++) { //Do 16 channels |
djbottrill | 4:9f63e68b548e | 140 | switch (synthstat[i]) { |
djbottrill | 4:9f63e68b548e | 141 | case 1: //Sustain phase |
djbottrill | 4:9f63e68b548e | 142 | if (synth[i]>sample_loop_end[synthoctave[i]]) { //Got to end of buffer? |
djbottrill | 4:9f63e68b548e | 143 | synth[i]=sample_loop_start[synthoctave[i]]; //Wrap around back to start |
djbottrill | 4:9f63e68b548e | 144 | } |
djbottrill | 4:9f63e68b548e | 145 | break; |
djbottrill | 5:13a1e5e4494d | 146 | case 2: //Note off demand state |
djbottrill | 0:4d06e0867376 | 147 | if (synth[i]>=sample_attack[synthoctave[i]]) { //Delay decay phase if not past end of attack |
djbottrill | 0:4d06e0867376 | 148 | //Check if the waveform is close to zero crossing this helps eliminate clicks looks for rising edge approaching 0x80 |
djbottrill | 0:4d06e0867376 | 149 | tempidx=synth[i]; |
djbottrill | 5:13a1e5e4494d | 150 | if (sample[synthoctave[i]][tempidx]<128 & sample[synthoctave[i]][tempidx]>120) { |
djbottrill | 0:4d06e0867376 | 151 | if (sample[synthoctave[i]][tempidx]>synth_old[i]) { |
djbottrill | 7:e1ea1623c6c2 | 152 | synth[i]=sample_loop_off[synthoctave[i]]; //Jump to start of decay |
djbottrill | 7:e1ea1623c6c2 | 153 | synthstat[i]=3; //Say note in decay |
djbottrill | 0:4d06e0867376 | 154 | } |
djbottrill | 0:4d06e0867376 | 155 | } |
djbottrill | 0:4d06e0867376 | 156 | } |
djbottrill | 4:9f63e68b548e | 157 | break; |
djbottrill | 7:e1ea1623c6c2 | 158 | case 3: //Check if decay has completed |
djbottrill | 8:df5f305aab22 | 159 | if (synth[i]>=sample_len[synthoctave[i]]) { //End of decay? |
djbottrill | 8:df5f305aab22 | 160 | synthstat[i]=0; //say channel free |
djbottrill | 8:df5f305aab22 | 161 | synth[i]=0; //Set sample pointer to 0 |
djbottrill | 8:df5f305aab22 | 162 | synthidx[i]=0; //Set sample index to 0 |
djbottrill | 8:df5f305aab22 | 163 | synthtab[i]=255; //Set to invalid |
djbottrill | 7:e1ea1623c6c2 | 164 | } |
djbottrill | 7:e1ea1623c6c2 | 165 | break ; |
djbottrill | 0:4d06e0867376 | 166 | } |
djbottrill | 4:9f63e68b548e | 167 | |
djbottrill | 4:9f63e68b548e | 168 | //get sample and add to Audio Accumulator |
djbottrill | 8:df5f305aab22 | 169 | synth_old[i]=sample[synthoctave[i]][(int)(synth[i])]; //Get and save old sample |
djbottrill | 0:4d06e0867376 | 170 | audio_out=audio_out+synth_old[i]; //add sample to audio out accumulator |
djbottrill | 0:4d06e0867376 | 171 | synth[i]=synth[i]+synthidx[i]; //Get next sample pointer |
djbottrill | 15:c1854904f0d7 | 172 | |
djbottrill | 0:4d06e0867376 | 173 | } //Next Note |
djbottrill | 8:df5f305aab22 | 174 | |
djbottrill | 0:4d06e0867376 | 175 | //Output to DAC |
djbottrill | 0:4d06e0867376 | 176 | Aout.write_u16(audio_out*16); |
djbottrill | 7:e1ea1623c6c2 | 177 | |
djbottrill | 15:c1854904f0d7 | 178 | // redled=1; |
djbottrill | 15:c1854904f0d7 | 179 | // diag=0; |
djbottrill | 0:4d06e0867376 | 180 | } |
djbottrill | 0:4d06e0867376 | 181 | |
djbottrill | 0:4d06e0867376 | 182 | /* |
djbottrill | 0:4d06e0867376 | 183 | Interrupt routine to receive MIDI on/off message and set LED status accordingly |
djbottrill | 0:4d06e0867376 | 184 | MIDI note on/off events directly start/stop the synth channels and store the note status in keytab. |
djbottrill | 0:4d06e0867376 | 185 | */ |
djbottrill | 0:4d06e0867376 | 186 | void get_message(MIDIMessage msg) |
djbottrill | 0:4d06e0867376 | 187 | { |
djbottrill | 0:4d06e0867376 | 188 | greenled=0; //Pulse Green LED to indicate MIDI activity |
djbottrill | 0:4d06e0867376 | 189 | key=msg.key(); |
djbottrill | 0:4d06e0867376 | 190 | velocity=msg.velocity(); |
djbottrill | 0:4d06e0867376 | 191 | chan=msg.channel(); |
djbottrill | 0:4d06e0867376 | 192 | switch (msg.type()) { |
djbottrill | 0:4d06e0867376 | 193 | case MIDIMessage::NoteOnType: //MIDI note on received |
djbottrill | 15:c1854904f0d7 | 194 | if (key >35 & key<97) { |
djbottrill | 0:4d06e0867376 | 195 | onoff=1; |
djbottrill | 15:c1854904f0d7 | 196 | if (p1==0) { |
djbottrill | 13:decb94698003 | 197 | keybuf[(key-36)]|= 1 << 0; //Store @ 16' pitch |
djbottrill | 12:74b52900cddb | 198 | } |
djbottrill | 15:c1854904f0d7 | 199 | if (p2==0) { |
djbottrill | 13:decb94698003 | 200 | keybuf[(key-24)]|= 1 << 1; //Store @ 8' pitch |
djbottrill | 15:c1854904f0d7 | 201 | } |
djbottrill | 15:c1854904f0d7 | 202 | if (p3==0) { |
djbottrill | 13:decb94698003 | 203 | keybuf[(key)-12]|= 1 << 2; //Store @ 4' pitch |
djbottrill | 15:c1854904f0d7 | 204 | } |
djbottrill | 15:c1854904f0d7 | 205 | if (p4==0) { |
djbottrill | 13:decb94698003 | 206 | keybuf[(key)-5]|= 1 << 3; //Store @ 2 2/3' pitch |
djbottrill | 15:c1854904f0d7 | 207 | } |
djbottrill | 15:c1854904f0d7 | 208 | if (p5==0) { |
djbottrill | 13:decb94698003 | 209 | keybuf[(key)]|= 1 << 4; //Store @ 2' pitch |
djbottrill | 15:c1854904f0d7 | 210 | } |
djbottrill | 0:4d06e0867376 | 211 | } |
djbottrill | 6:073e10217962 | 212 | |
djbottrill | 0:4d06e0867376 | 213 | break; |
djbottrill | 0:4d06e0867376 | 214 | |
djbottrill | 0:4d06e0867376 | 215 | //Process keys off |
djbottrill | 0:4d06e0867376 | 216 | case MIDIMessage::NoteOffType: //Midi note off received |
djbottrill | 15:c1854904f0d7 | 217 | if (key >35 & key<97) { |
djbottrill | 0:4d06e0867376 | 218 | onoff=0; |
djbottrill | 15:c1854904f0d7 | 219 | keybuf[(key-36)]&= ~(1 << 0); //Kill note @ 16' pitch |
djbottrill | 15:c1854904f0d7 | 220 | keybuf[(key-24)]&= ~(1 << 1); //Kill note @ 8' pitch |
djbottrill | 15:c1854904f0d7 | 221 | keybuf[(key-12)]&= ~(1 << 2); //Kill note @ 4' pitch |
djbottrill | 15:c1854904f0d7 | 222 | keybuf[(key-5)]&= ~(1 << 3); //Kill note @ 2 2/3' pitch |
djbottrill | 15:c1854904f0d7 | 223 | keybuf[(key)]&= ~(1 << 4); //Kill note @ 2' pitch |
djbottrill | 0:4d06e0867376 | 224 | } |
djbottrill | 0:4d06e0867376 | 225 | break; |
djbottrill | 0:4d06e0867376 | 226 | |
djbottrill | 0:4d06e0867376 | 227 | //Process all notes off command |
djbottrill | 0:4d06e0867376 | 228 | case MIDIMessage::AllNotesOffType: //Midi all notes off |
djbottrill | 11:a27a71f7c149 | 229 | for (int it=0; it<108; it++) { |
djbottrill | 11:a27a71f7c149 | 230 | keybuf[it]=0; //Clear Keybuf |
djbottrill | 0:4d06e0867376 | 231 | } |
djbottrill | 0:4d06e0867376 | 232 | break; |
djbottrill | 4:9f63e68b548e | 233 | |
djbottrill | 0:4d06e0867376 | 234 | //Any other MIDI Commands just ignore |
djbottrill | 0:4d06e0867376 | 235 | default: |
djbottrill | 0:4d06e0867376 | 236 | break; |
djbottrill | 0:4d06e0867376 | 237 | } |
djbottrill | 0:4d06e0867376 | 238 | greenled=1; |
djbottrill | 0:4d06e0867376 | 239 | } |
djbottrill | 0:4d06e0867376 | 240 | |
djbottrill | 0:4d06e0867376 | 241 | |
djbottrill | 0:4d06e0867376 | 242 | int main() |
djbottrill | 0:4d06e0867376 | 243 | { |
djbottrill | 0:4d06e0867376 | 244 | greenled=1; |
djbottrill | 0:4d06e0867376 | 245 | redled=1; |
djbottrill | 0:4d06e0867376 | 246 | blueled=1; |
djbottrill | 0:4d06e0867376 | 247 | SRCK=0; //Serial Clock low |
djbottrill | 0:4d06e0867376 | 248 | RCK=0; //Latch Clock low |
djbottrill | 0:4d06e0867376 | 249 | |
djbottrill | 0:4d06e0867376 | 250 | sw1.mode(PullUp); |
djbottrill | 0:4d06e0867376 | 251 | sw3.mode(PullUp); |
djbottrill | 15:c1854904f0d7 | 252 | p1.mode(PullUp); |
djbottrill | 15:c1854904f0d7 | 253 | p2.mode(PullUp); |
djbottrill | 15:c1854904f0d7 | 254 | p3.mode(PullUp); |
djbottrill | 15:c1854904f0d7 | 255 | p4.mode(PullUp); |
djbottrill | 15:c1854904f0d7 | 256 | p5.mode(PullUp); |
djbottrill | 15:c1854904f0d7 | 257 | |
djbottrill | 0:4d06e0867376 | 258 | output_ticker.attach(&aout, sample_rate); //Set Sample frequency |
djbottrill | 0:4d06e0867376 | 259 | |
djbottrill | 0:4d06e0867376 | 260 | lcd.cls(); |
djbottrill | 0:4d06e0867376 | 261 | wait(0.1); |
djbottrill | 0:4d06e0867376 | 262 | lcd.setCursor(TextLCD::CurOff_BlkOff); |
djbottrill | 0:4d06e0867376 | 263 | lcd.cls(); |
djbottrill | 0:4d06e0867376 | 264 | lcd.printf("MIDI Pipe Organ Waiting for USB"); |
djbottrill | 0:4d06e0867376 | 265 | |
djbottrill | 0:4d06e0867376 | 266 | NVIC_SetPriority(USB0_IRQn, 99); //Reduce Interrupt priority of USB |
djbottrill | 0:4d06e0867376 | 267 | |
djbottrill | 0:4d06e0867376 | 268 | USBMIDI midi; //Start up MIDI |
djbottrill | 0:4d06e0867376 | 269 | midi.attach(get_message); //callback for MIDI messages received |
djbottrill | 0:4d06e0867376 | 270 | |
djbottrill | 0:4d06e0867376 | 271 | lcd.cls(); |
djbottrill | 0:4d06e0867376 | 272 | lcd.printf("MIDI Pipe Organ"); |
djbottrill | 0:4d06e0867376 | 273 | wait(1); |
djbottrill | 0:4d06e0867376 | 274 | lcd.cls(); |
djbottrill | 0:4d06e0867376 | 275 | lcd.printf("On Chan Note Vel"); |
djbottrill | 0:4d06e0867376 | 276 | |
djbottrill | 0:4d06e0867376 | 277 | key=0; |
djbottrill | 0:4d06e0867376 | 278 | velocity=0; |
djbottrill | 0:4d06e0867376 | 279 | chan=0; |
djbottrill | 0:4d06e0867376 | 280 | onoff=0; |
djbottrill | 0:4d06e0867376 | 281 | |
djbottrill | 0:4d06e0867376 | 282 | //Main Loop |
djbottrill | 0:4d06e0867376 | 283 | while(1) { |
djbottrill | 0:4d06e0867376 | 284 | |
djbottrill | 11:a27a71f7c149 | 285 | diag2=1; |
djbottrill | 11:a27a71f7c149 | 286 | //Process notes off |
djbottrill | 11:a27a71f7c149 | 287 | for (ii=0; ii<16; ii++) { |
djbottrill | 11:a27a71f7c149 | 288 | if (synthstat[ii]==1 & keybuf[synthtab[ii]]==0) { //Note currently playing |
djbottrill | 11:a27a71f7c149 | 289 | synthstat[ii]=2; //Set Start decay |
djbottrill | 11:a27a71f7c149 | 290 | } |
djbottrill | 11:a27a71f7c149 | 291 | } |
djbottrill | 11:a27a71f7c149 | 292 | //Process keys on |
djbottrill | 11:a27a71f7c149 | 293 | //Find a free note generator and start the note playing, if no slots available just ignore the note |
djbottrill | 12:74b52900cddb | 294 | for (keyidx=95; keyidx>=0; keyidx--) { |
djbottrill | 15:c1854904f0d7 | 295 | if (keybuf[keyidx]>0 & oldkeybuf[keyidx]==0) { //Key is pressed |
djbottrill | 12:74b52900cddb | 296 | //Find and use any channel that is free |
djbottrill | 12:74b52900cddb | 297 | sucess=0; |
djbottrill | 11:a27a71f7c149 | 298 | ii=0; |
djbottrill | 11:a27a71f7c149 | 299 | while (ii<16) { //Find available synth channel |
djbottrill | 11:a27a71f7c149 | 300 | if (synthstat[ii]==0) { //Is synth channel free? |
djbottrill | 11:a27a71f7c149 | 301 | synthtab[ii]=keyidx; //Store note value |
djbottrill | 11:a27a71f7c149 | 302 | synthoctave[ii]=synthtab[ii]/12; //Store the octave |
djbottrill | 15:c1854904f0d7 | 303 | // if (synthoctave[ii]==7) { |
djbottrill | 15:c1854904f0d7 | 304 | // synthoctave[ii]--; |
djbottrill | 15:c1854904f0d7 | 305 | // } |
djbottrill | 11:a27a71f7c149 | 306 | synthnote[ii]= synthtab[ii]%12; //Note within the octave |
djbottrill | 11:a27a71f7c149 | 307 | synthstat[ii]=1; //Set status to playing |
djbottrill | 11:a27a71f7c149 | 308 | synthidx[ii]=freqtab[synthnote[ii]]; //Set the frequency |
djbottrill | 12:74b52900cddb | 309 | sucess=1; |
djbottrill | 11:a27a71f7c149 | 310 | ii=16; //exit loop |
djbottrill | 11:a27a71f7c149 | 311 | } //Next Synth slot |
djbottrill | 11:a27a71f7c149 | 312 | ii++; |
djbottrill | 11:a27a71f7c149 | 313 | } |
djbottrill | 12:74b52900cddb | 314 | //As a last resort find any channel that is in decay |
djbottrill | 12:74b52900cddb | 315 | if (sucess==0) { |
djbottrill | 12:74b52900cddb | 316 | ii=0; |
djbottrill | 15:c1854904f0d7 | 317 | while (ii<16) { //Find available synth channel |
djbottrill | 15:c1854904f0d7 | 318 | if (synthstat[ii]>1) { //Is synth channel free? |
djbottrill | 15:c1854904f0d7 | 319 | synthtab[ii]=keyidx; //Store note value |
djbottrill | 15:c1854904f0d7 | 320 | synthoctave[ii]=synthtab[ii]/12; //Store the octave |
djbottrill | 15:c1854904f0d7 | 321 | synthnote[ii]= synthtab[ii]%12; //Note within the octave |
djbottrill | 15:c1854904f0d7 | 322 | synthstat[ii]=1; //Set status to playing |
djbottrill | 15:c1854904f0d7 | 323 | synthidx[ii]=freqtab[synthnote[ii]]; //Set the frequency |
djbottrill | 12:74b52900cddb | 324 | sucess=1; |
djbottrill | 15:c1854904f0d7 | 325 | ii=16; //exit loop |
djbottrill | 15:c1854904f0d7 | 326 | } //Next Synth slot |
djbottrill | 12:74b52900cddb | 327 | ii++; |
djbottrill | 12:74b52900cddb | 328 | } |
djbottrill | 12:74b52900cddb | 329 | } |
djbottrill | 11:a27a71f7c149 | 330 | } |
djbottrill | 12:74b52900cddb | 331 | if (sucess==1) { |
djbottrill | 12:74b52900cddb | 332 | oldkeybuf[keyidx]=keybuf[keyidx]; //Store old value if note scuessfull |
djbottrill | 12:74b52900cddb | 333 | } |
djbottrill | 11:a27a71f7c149 | 334 | } |
djbottrill | 11:a27a71f7c149 | 335 | diag2=0; |
djbottrill | 11:a27a71f7c149 | 336 | wait_ms(1); |
djbottrill | 15:c1854904f0d7 | 337 | |
djbottrill | 15:c1854904f0d7 | 338 | |
djbottrill | 15:c1854904f0d7 | 339 | //Output to Shift register |
djbottrill | 15:c1854904f0d7 | 340 | for (keyidx=107; keyidx>11; keyidx--) { |
djbottrill | 15:c1854904f0d7 | 341 | if (keybuf[keyidx]>0) { |
djbottrill | 15:c1854904f0d7 | 342 | SER1=1; |
djbottrill | 15:c1854904f0d7 | 343 | } else { |
djbottrill | 15:c1854904f0d7 | 344 | SER1=0; |
djbottrill | 15:c1854904f0d7 | 345 | } |
djbottrill | 15:c1854904f0d7 | 346 | SRCK=1; //Pulse Serial clock |
djbottrill | 15:c1854904f0d7 | 347 | wait_us(4); |
djbottrill | 15:c1854904f0d7 | 348 | SRCK=0; |
djbottrill | 15:c1854904f0d7 | 349 | wait_us(4); |
djbottrill | 15:c1854904f0d7 | 350 | } //Next bit |
djbottrill | 15:c1854904f0d7 | 351 | |
djbottrill | 15:c1854904f0d7 | 352 | RCK=1; //Transfer data to outputs |
djbottrill | 15:c1854904f0d7 | 353 | wait_us(4); |
djbottrill | 15:c1854904f0d7 | 354 | RCK=0; |
djbottrill | 15:c1854904f0d7 | 355 | wait_us(4); |
djbottrill | 15:c1854904f0d7 | 356 | |
djbottrill | 15:c1854904f0d7 | 357 | |
djbottrill | 15:c1854904f0d7 | 358 | |
djbottrill | 15:c1854904f0d7 | 359 | /* |
djbottrill | 15:c1854904f0d7 | 360 | Additional code to de-bounce the K64F onboard switches again using multiple loops to de-bounce; has 3 states: |
djbottrill | 15:c1854904f0d7 | 361 | 0 - Button not pressed |
djbottrill | 15:c1854904f0d7 | 362 | 1 - Button pressed waiting for de-bounce count of 10 loops |
djbottrill | 15:c1854904f0d7 | 363 | 2 - Button press stable, this status can be read as the de-bounced status indicating the button press is clean |
djbottrill | 15:c1854904f0d7 | 364 | */ |
djbottrill | 6:073e10217962 | 365 | // De-bounce SW1 |
djbottrill | 9:b0f110c02b1b | 366 | if (sw1_phase==0 & sw1==0) { //Button just been pressed |
djbottrill | 6:073e10217962 | 367 | sw1_phase=1; |
djbottrill | 6:073e10217962 | 368 | sw1_count=10; |
djbottrill | 6:073e10217962 | 369 | } |
djbottrill | 9:b0f110c02b1b | 370 | if (sw1_phase==1 & sw1_count==0 & sw1==0) { //Button still pressed after de-bounce period |
djbottrill | 6:073e10217962 | 371 | sw1_phase=2; |
djbottrill | 6:073e10217962 | 372 | } |
djbottrill | 9:b0f110c02b1b | 373 | if (sw1_phase==2 & sw1==1) { //Button released |
djbottrill | 6:073e10217962 | 374 | sw1_phase=0; |
djbottrill | 6:073e10217962 | 375 | } |
djbottrill | 6:073e10217962 | 376 | sw1_count--; |
djbottrill | 15:c1854904f0d7 | 377 | // De-bounce SW3 |
djbottrill | 9:b0f110c02b1b | 378 | if (sw3_phase==0 & sw3==0) { //Button just been pressed |
djbottrill | 7:e1ea1623c6c2 | 379 | sw3_phase=1; |
djbottrill | 7:e1ea1623c6c2 | 380 | sw3_count=10; |
djbottrill | 7:e1ea1623c6c2 | 381 | } |
djbottrill | 9:b0f110c02b1b | 382 | if (sw3_phase==1 & sw3_count==0 & sw3==0) { //Button still pressed after de-bounce period |
djbottrill | 7:e1ea1623c6c2 | 383 | sw3_phase=2; |
djbottrill | 7:e1ea1623c6c2 | 384 | } |
djbottrill | 6:073e10217962 | 385 | |
djbottrill | 9:b0f110c02b1b | 386 | if (sw3_phase==2 & sw3==1) { //Button released |
djbottrill | 7:e1ea1623c6c2 | 387 | sw3_phase=0; |
djbottrill | 7:e1ea1623c6c2 | 388 | } |
djbottrill | 7:e1ea1623c6c2 | 389 | sw3_count--; |
djbottrill | 6:073e10217962 | 390 | |
djbottrill | 6:073e10217962 | 391 | |
djbottrill | 6:073e10217962 | 392 | |
djbottrill | 6:073e10217962 | 393 | |
djbottrill | 0:4d06e0867376 | 394 | if (sw1_phase==2 | sw3_phase==2) { |
djbottrill | 0:4d06e0867376 | 395 | blueled=0; |
djbottrill | 0:4d06e0867376 | 396 | } else { |
djbottrill | 0:4d06e0867376 | 397 | blueled=1; |
djbottrill | 0:4d06e0867376 | 398 | } |
djbottrill | 0:4d06e0867376 | 399 | |
djbottrill | 0:4d06e0867376 | 400 | |
djbottrill | 9:b0f110c02b1b | 401 | //Button 1 will clear all playing notes |
djbottrill | 9:b0f110c02b1b | 402 | if (sw1_phase != sw1_old) { |
djbottrill | 9:b0f110c02b1b | 403 | if(sw1_phase==2) { |
djbottrill | 9:b0f110c02b1b | 404 | for (cl=0; cl<108; cl++) { |
djbottrill | 12:74b52900cddb | 405 | keybuf[cl]=0; //Clear Keybuf |
djbottrill | 9:b0f110c02b1b | 406 | } |
djbottrill | 9:b0f110c02b1b | 407 | } |
djbottrill | 9:b0f110c02b1b | 408 | } |
djbottrill | 9:b0f110c02b1b | 409 | |
djbottrill | 0:4d06e0867376 | 410 | //Check for display mode button being presed |
djbottrill | 9:b0f110c02b1b | 411 | if (sw3_phase != sw3_old) { //Mode Switch pressed |
djbottrill | 0:4d06e0867376 | 412 | if(sw3_phase==2) { |
djbottrill | 0:4d06e0867376 | 413 | lcdmode++; |
djbottrill | 8:df5f305aab22 | 414 | if (lcdmode>3) { |
djbottrill | 0:4d06e0867376 | 415 | lcdmode=0; |
djbottrill | 0:4d06e0867376 | 416 | } |
djbottrill | 0:4d06e0867376 | 417 | lcd.cls(); |
djbottrill | 0:4d06e0867376 | 418 | if (lcdmode==0) { |
djbottrill | 0:4d06e0867376 | 419 | lcd.printf("On Chan Note Vel"); |
djbottrill | 0:4d06e0867376 | 420 | } |
djbottrill | 0:4d06e0867376 | 421 | if (lcdmode==1) { |
djbottrill | 0:4d06e0867376 | 422 | //Initialise bar graph display |
djbottrill | 10:fa2dee836ceb | 423 | //Program user defined characters into OLED display for bar graph |
djbottrill | 10:fa2dee836ceb | 424 | lcd.setUDC(0, (char *) udc0); |
djbottrill | 10:fa2dee836ceb | 425 | lcd.setUDC(1, (char *) udc1); |
djbottrill | 10:fa2dee836ceb | 426 | lcd.setUDC(2, (char *) udc2); |
djbottrill | 10:fa2dee836ceb | 427 | lcd.setUDC(3, (char *) udc3); |
djbottrill | 10:fa2dee836ceb | 428 | lcd.setUDC(4, (char *) udc4); |
djbottrill | 10:fa2dee836ceb | 429 | lcd.setUDC(5, (char *) udc5); |
djbottrill | 10:fa2dee836ceb | 430 | lcd.setUDC(6, (char *) udc6); |
djbottrill | 10:fa2dee836ceb | 431 | lcd.setUDC(7, (char *) udc7); |
djbottrill | 0:4d06e0867376 | 432 | for (row=0; row<2; row++) { |
djbottrill | 0:4d06e0867376 | 433 | for (pos=0; pos<16; pos++) { |
djbottrill | 0:4d06e0867376 | 434 | display[row][pos]=0; |
djbottrill | 0:4d06e0867376 | 435 | display_old[row][pos]=1; |
djbottrill | 0:4d06e0867376 | 436 | } |
djbottrill | 0:4d06e0867376 | 437 | } |
djbottrill | 0:4d06e0867376 | 438 | } |
djbottrill | 0:4d06e0867376 | 439 | if (lcdmode==2) { |
djbottrill | 0:4d06e0867376 | 440 | lcd.locate(0,1); |
djbottrill | 0:4d06e0867376 | 441 | lcd.printf("0123456789ABCDEF"); |
djbottrill | 0:4d06e0867376 | 442 | } |
djbottrill | 8:df5f305aab22 | 443 | if (lcdmode==3) { |
djbottrill | 8:df5f305aab22 | 444 | lcd.cls(); |
djbottrill | 8:df5f305aab22 | 445 | lcd.locate(0,0); |
djbottrill | 8:df5f305aab22 | 446 | lcd.printf("Utilisation:"); |
djbottrill | 8:df5f305aab22 | 447 | } |
djbottrill | 0:4d06e0867376 | 448 | } |
djbottrill | 0:4d06e0867376 | 449 | } |
djbottrill | 0:4d06e0867376 | 450 | sw3_old=sw3_phase; |
djbottrill | 0:4d06e0867376 | 451 | |
djbottrill | 0:4d06e0867376 | 452 | |
djbottrill | 0:4d06e0867376 | 453 | |
djbottrill | 0:4d06e0867376 | 454 | // OLED MIDI Status display |
djbottrill | 0:4d06e0867376 | 455 | if (lcdmode==0) { |
djbottrill | 0:4d06e0867376 | 456 | lcd_stat=key+(128*velocity)+(16384*chan)+(262144*onoff); |
djbottrill | 0:4d06e0867376 | 457 | if (lcd_stat!=lcd_stat_old) { |
djbottrill | 0:4d06e0867376 | 458 | |
djbottrill | 0:4d06e0867376 | 459 | lcd.locate(0,1); |
djbottrill | 0:4d06e0867376 | 460 | lcd.printf("%1d %2d %3d %3d", onoff, chan, key, velocity); |
djbottrill | 0:4d06e0867376 | 461 | lcd_stat_old=lcd_stat; |
djbottrill | 0:4d06e0867376 | 462 | } |
djbottrill | 0:4d06e0867376 | 463 | } |
djbottrill | 0:4d06e0867376 | 464 | |
djbottrill | 0:4d06e0867376 | 465 | // OLED MIDI Bar Display |
djbottrill | 0:4d06e0867376 | 466 | if (lcdmode==1) { |
djbottrill | 0:4d06e0867376 | 467 | keyidx=0; |
djbottrill | 0:4d06e0867376 | 468 | for (row=1; row>=0; row--) { |
djbottrill | 0:4d06e0867376 | 469 | for (pos=0; pos<16; pos++) { |
djbottrill | 0:4d06e0867376 | 470 | keyidx_t=keyidx*2; |
djbottrill | 0:4d06e0867376 | 471 | if(keyidx_t>94) { |
djbottrill | 0:4d06e0867376 | 472 | keyidx_t=keyidx_t-95; |
djbottrill | 0:4d06e0867376 | 473 | } |
djbottrill | 15:c1854904f0d7 | 474 | tval=keybuf[keyidx_t]; |
djbottrill | 12:74b52900cddb | 475 | if(tval>0) { |
djbottrill | 12:74b52900cddb | 476 | tval=1; |
djbottrill | 12:74b52900cddb | 477 | } |
djbottrill | 0:4d06e0867376 | 478 | keyidx++; |
djbottrill | 0:4d06e0867376 | 479 | keyidx_t=keyidx*2; |
djbottrill | 0:4d06e0867376 | 480 | if(keyidx_t>94) { |
djbottrill | 0:4d06e0867376 | 481 | keyidx_t=keyidx_t-95; |
djbottrill | 0:4d06e0867376 | 482 | } |
djbottrill | 15:c1854904f0d7 | 483 | tval1=keybuf[keyidx_t]; |
djbottrill | 12:74b52900cddb | 484 | if (tval1>0) { |
djbottrill | 12:74b52900cddb | 485 | tval1=1; |
djbottrill | 12:74b52900cddb | 486 | } |
djbottrill | 12:74b52900cddb | 487 | tval=tval+(2*tval1); |
djbottrill | 0:4d06e0867376 | 488 | keyidx++; |
djbottrill | 0:4d06e0867376 | 489 | |
djbottrill | 0:4d06e0867376 | 490 | keyidx_t=keyidx*2; |
djbottrill | 0:4d06e0867376 | 491 | if(keyidx_t>94) { |
djbottrill | 0:4d06e0867376 | 492 | keyidx_t=keyidx_t-95; |
djbottrill | 0:4d06e0867376 | 493 | } |
djbottrill | 15:c1854904f0d7 | 494 | tval1=keybuf[keyidx_t]; |
djbottrill | 12:74b52900cddb | 495 | if (tval1>0) { |
djbottrill | 12:74b52900cddb | 496 | tval1=1; |
djbottrill | 12:74b52900cddb | 497 | } |
djbottrill | 12:74b52900cddb | 498 | tval=tval+(4*tval1); |
djbottrill | 0:4d06e0867376 | 499 | keyidx++; |
djbottrill | 0:4d06e0867376 | 500 | display[row][pos]=tval; |
djbottrill | 0:4d06e0867376 | 501 | |
djbottrill | 0:4d06e0867376 | 502 | if(display[row][pos]!=display_old[row][pos]) { |
djbottrill | 0:4d06e0867376 | 503 | lcd.locate(pos,row); |
djbottrill | 0:4d06e0867376 | 504 | lcd.putc(display[row][pos]); |
djbottrill | 0:4d06e0867376 | 505 | display_old[row][pos]=display[row][pos]; |
djbottrill | 0:4d06e0867376 | 506 | } |
djbottrill | 0:4d06e0867376 | 507 | } |
djbottrill | 0:4d06e0867376 | 508 | } |
djbottrill | 0:4d06e0867376 | 509 | } |
djbottrill | 0:4d06e0867376 | 510 | |
djbottrill | 0:4d06e0867376 | 511 | //Display status of the Synth channels |
djbottrill | 0:4d06e0867376 | 512 | if (lcdmode==2) { |
djbottrill | 0:4d06e0867376 | 513 | lcd.locate(0,0); |
djbottrill | 0:4d06e0867376 | 514 | for (noteidx=0; noteidx<16; noteidx++) { |
djbottrill | 0:4d06e0867376 | 515 | lcd.putc((synthstat[noteidx]+48)); |
djbottrill | 0:4d06e0867376 | 516 | } |
djbottrill | 0:4d06e0867376 | 517 | } |
djbottrill | 4:9f63e68b548e | 518 | |
djbottrill | 8:df5f305aab22 | 519 | //Display utilisation bar graph |
djbottrill | 8:df5f305aab22 | 520 | if (lcdmode==3) { |
djbottrill | 8:df5f305aab22 | 521 | x=0; |
djbottrill | 8:df5f305aab22 | 522 | for (noteidx=0; noteidx<16; noteidx++) { |
djbottrill | 8:df5f305aab22 | 523 | if (synthstat[noteidx] >0) { |
djbottrill | 8:df5f305aab22 | 524 | x++; |
djbottrill | 8:df5f305aab22 | 525 | } |
djbottrill | 8:df5f305aab22 | 526 | } |
djbottrill | 8:df5f305aab22 | 527 | for (pos=0; pos<x; pos++) { |
djbottrill | 8:df5f305aab22 | 528 | display[1][pos]=0xff; |
djbottrill | 8:df5f305aab22 | 529 | } |
djbottrill | 8:df5f305aab22 | 530 | while(pos<16) { |
djbottrill | 8:df5f305aab22 | 531 | display[1][pos]=0x20; |
djbottrill | 8:df5f305aab22 | 532 | pos++; |
djbottrill | 8:df5f305aab22 | 533 | } |
djbottrill | 8:df5f305aab22 | 534 | row=1; |
djbottrill | 8:df5f305aab22 | 535 | for (pos=0; pos<16; pos++) { |
djbottrill | 8:df5f305aab22 | 536 | if (x!=x1) { |
djbottrill | 8:df5f305aab22 | 537 | lcd.locate(13,0); |
djbottrill | 8:df5f305aab22 | 538 | lcd.printf("%2d", x); |
djbottrill | 8:df5f305aab22 | 539 | x1=x; |
djbottrill | 8:df5f305aab22 | 540 | } |
djbottrill | 8:df5f305aab22 | 541 | if(display[row][pos]!=display_old[row][pos]) { |
djbottrill | 8:df5f305aab22 | 542 | lcd.locate(pos,row); |
djbottrill | 8:df5f305aab22 | 543 | lcd.putc(display[row][pos]); |
djbottrill | 8:df5f305aab22 | 544 | display_old[row][pos]=display[row][pos]; |
djbottrill | 8:df5f305aab22 | 545 | } |
djbottrill | 8:df5f305aab22 | 546 | } |
djbottrill | 8:df5f305aab22 | 547 | } |
djbottrill | 8:df5f305aab22 | 548 | |
djbottrill | 8:df5f305aab22 | 549 | |
djbottrill | 8:df5f305aab22 | 550 | |
djbottrill | 0:4d06e0867376 | 551 | } //End of main loop |
djbottrill | 0:4d06e0867376 | 552 | } //End of program |
djbottrill | 0:4d06e0867376 | 553 | |
djbottrill | 0:4d06e0867376 | 554 | |
djbottrill | 0:4d06e0867376 | 555 | |
djbottrill | 0:4d06e0867376 | 556 |