/********************************************* Project : Poly Midi >CV ,4 channel Version : 1m Start Date : 18/11/2000 Author : Paul Maddox Company : modulus Electronics ;- Website : Http://www.wavesynth.com/modulus/ Copyright : (c)2000-2001 Paul Maddox Chip type : AT90S8515 Clock frequency : 8.000000 MHz Memory model : Small Internal SRAM size : 512 External SRAM size : 0 Data Stack size : 128 ----------------------------------------------------------- IO PortA 0 - 3 4067 select lines PortA 4 4067 enable PortA 5 - 7 unused (output) PortB 0 - 3 channel select switchs PortB 4 mono/poly mode switch, off is Poly PortB 5 - 7 Programmer interface PortC 0 /LOAD on DAC PortC 1 /CLR on DAC PortC 2 SDI on DAC (serial data in, MSB first) PortC 3 CLK on DAC (+ve edge clocks in SDI) PortC 4 /CS on DAC PortC 5 - 7 unused (output) PortD 0 UART RX input PortD 4 Voice mode switch, when ON It will replace oldest playing note with new note (over write), when off it will ignore new notes once all voices are in use. PortD 5 re-trig, when ON and Voice mode is ON, new notes will cause retrigger (Gate drops and goes high) PortD 6 Test Mode (ALL CV's HIGH) switch, doesn't destroy contents of CV[] array PortD 7 LED (active Low) for testing/Channel receive ----------------------------------------------------------- Error codes ;- 10101010 - Too much in rx buffer 11101000 - case error, somehow it got a value for a word received that I didnt tell it about 11101010 - fifo returned zero value ----------------------------------------------------------- If you want to use this code , or parts, please feel free to do so, but please keep my name and webpage link in your header file somewhere. (c)Paul Maddox 2000-2001 http://www.wavesynth.com/modulus/ *********************************************/ #include <90s8515.h> #include <delay.h> #include <stdio.h> #asm(".set PORTC=0x15"); /*****************************************/ /* Global constants */ /*****************************************/ #define MAX_DAC 0b0000111111111111 #define MID_DAC 0b0000011111111111 #define NOTE_ON 0b10010000 #define NOTE_OFF 0b10000000 #define CHANMODE 0b10110000 #define PITCH 0b11100000 #define AFTERTOUCH 0b11010000 #define RX_BUFFER_SIZE 0x20 /*****************************************/ /* Global variable */ /*****************************************/ unsigned char midi_chan = 0 ; // holds Midi Chanel value. unsigned char new_byte = 0 ; // byte just received by UART unsigned char rx_byte[RX_BUFFER_SIZE] = 0 ; // Byte that is ok to process unsigned char byte_counter = 0 ; // used to point into array unsigned char current_byte = 0 ; // byte being processed bit valid_byte = 0 ; // Bit used to let main loop know we've had a new byte unsigned char midi_status = 0 ; // holds latest status byte (used for running status) unsigned char midi_char_count= 0; // holds number of bytes received in full midi word (3 = word) unsigned char midi_note = 0 ; // holds note value just received from midi unsigned char midi_velocity = 0; // holds velocity value just received from midi unsigned char midi_word = 0 ; // Flag that is set when a full word is received (3 bytes) // '1' indicates NOTE // '2' Indicates PITCH BEND // '3' indicates CHANNEL MODE (modwheel/foot/reset) // '4' indicates AFTERTOUCH unsigned char work = 0; // working byte (8Bit) unsigned char work1 = 0; // working byte (8Bit) unsigned char work2 = 0; // working byte (8Bit) unsigned int work_int = 0 ; // working integer (16Bit) unsigned int work_int2 = 0 ; // working integer (16Bit) unsigned char pitch_lsb = 0 ; // pitch bend LSB half of word (7 of 14 bits) unsigned char pitch_msb = 0 ; // pitch bend MSB half of word (7 of 14 bits) unsigned char chan_mode = 0; // channel mode (tells us if its foot/mod/resets) unsigned char chan_mode_val = 0 ; // channel mode value unsigned char aftertouch_val = 0 ; // aftertouch value unsigned char current_notes[5]={0,0,0,0,0} ; // current note values unsigned char free_fifo[5]={0,1,2,3,4} ; // holds free voices // 0 indicates not available ie {0,0,0,0,0} shows // no voices free but {0,3,1,4,2} shows all // voices free but voice 3 should be used first, // the 1, then 4,etc note array position 0 not used unsigned char voice = 0 ; // holds value of voice that has been removed/added to fifo unsigned int cv[16] // output CVs = {0,0,0,0,0,0,0,0,0,0,0,0,MID_DAC,0,0,0} ; // 0 - 3 = Note CV // 4 - 7 = Velocity CV // 8 - 11 = Gate // 12 = Pitch Bend // 13 = After touch // 14 = Foot Switch // 15 = Mod wheel unsigned char cv_sel= 0 ; // CV number to be output unsigned char led_count = 1 ; // used to light LED so you can see it. /*******************************************/ /* Functions to be used */ /****************************************/ void pitch_bend(void) ; // process's pitch bend bytes void setup_io(void); // sets up IO ports void load_dac(unsigned int); // outputs data to serial DAC void process_midi(unsigned char); // Process's midi bytes void sh_loop(void) ; // Normal Cyclic load of S&Hs when no midi activity void error(unsigned char) ; // flashs LED with error code unsigned char fifo_remove(void) ; // remove byte from fifo void voice_alloc(void) ; // POLY voice allocation routine void voice_alloc_mono(void) ; // MONO voice allocation routine void fifo_add(unsigned char new_data); // add byte to fifo void channel_mode(void) ; // Channel mode processing (foot/modwheel/aftertouch) void after_touch(void); // Aftertouch processing void kill_notes(void); // Kills all notes (silences gates and resets fifo) /*********************************************/ /* UART Receiver interrupt service routine */ /*******************************************/ interrupt [UART_RXC] void uart_rx_isr(void) { new_byte = UDR ; if (new_byte == 0xFC) // if its stop byte! { kill_notes(); // turn off gates, reset fifo } if (new_byte < 0xF0) // removed active status, clock, sysex, { byte_counter++ ; rx_byte[byte_counter] = new_byte ; } else // if it is bigger then reset status byte and char count { midi_status = 0 ; midi_char_count = 0 ; new_byte = 0x00 ; } } /*********************************************/ void main(void) { /******************************************/ /* Call inital functions */ /****************************************/ setup_io() ; PORTA = 0b00000000 ; // disable mux PORTC = 0b00001101 ; // clears DAC delay_us(10) ; PORTC = 0b00011111 ; // disable DAC PORTD.7 = 1 ; for (work = 0; work < RX_BUFFER_SIZE; work ++) { rx_byte[work] = 0 ; } #asm("sei") // Global enable interrupts /*****************************************/ /* Main Loop */ /* */ /*****************************************/ while (1) { load_dac(cv[cv_sel]) ; // load DAC with value from array sh_loop(); // cyclic refresh of S&H's if (led_count) // should we counting till LED turn off? { led_count++; // increase counter if (led_count > 0xCC) // is it time to turn it off? { PORTD.7 = 1; // Turn off LED led_count = 0x00 ; // stop counting } } work = ~PINB ; // read port B and invert it midi_chan = work & 0x0F ; // mask off upper 4 bits to get channel number if (byte_counter) // if we've got data in the midi byte buffer { if (byte_counter > 1) // if we have more than one byte in the buffer we MUST { // process them in the right sequence! current_byte = rx_byte[0x01] ; // first byte in to be processed first process_midi(current_byte) ; // process it rx_byte[0x01] = rx_byte[0x02] ; // copy byte above byte_counter-- ; // decrease byte counter } else { current_byte = rx_byte[byte_counter] ; // work with lowest byte in array process_midi(current_byte) ; // process data byte_counter-- ; // decrease the array counter } } if (midi_word) // if we've had a valid byte { PORTD.7 = 0 ; // Turn on LED led_count = 0x01 ; // set LED timer switch (midi_word) { case (1): // note on/off { midi_word = 0x00 ; // clear midi word flag if (PINB.4) // if we're in POLY mode { voice_alloc(); // Do POLY voice allocation } else { voice_alloc_mono(); // Do MONO voice allocation } break ; } case (2): // Pitch bend { midi_word = 0x00 ; // clear midi word flag pitch_bend(); // Process pitch bend data break ; } case (3): // Channel mode (foot/modwheel/resets) { midi_word = 0x00 ; // clear midi word flag channel_mode() ; // Process channel_mode byte break ; } case (4): // After touch { midi_word = 0x00 ; // clear midi word flag after_touch() ; // process after touch byte } default: { error(0b11101000) ; // show case error break ; } } } while (!PIND.6) // if test mode is enabled { PORTD.7 = 0 ; // turn on LED load_dac(MAX_DAC) ; // load DAC with maximum value sh_loop() ; // load S&Hs cyclicly PORTD.7 = 1 ; // turn off LED } }; } /*****************************************/ /* Functions */ /* */ /*****************************************/ void after_touch(void) // Aftertouch processing { work_int = aftertouch_val ; // get value work_int = work_int << 5 ; // shift it left 5 times (12Bit) cv[13] = work_int ; // store it in CV's } /*********************************************/ void kill_notes(void) // Kill all notes { for (work = 1; work < 5; work ++) // Do all 4 voices { cv[work+7]=0x00 ; // clear gates free_fifo[work] = work ; // reset fifo to initial state current_notes[work] = 0x00 ; // clear notes being played register } cv[12] = MID_DAC ; // reset pitch bend cv[13] = 0x00 ; // reset after touch cv[14] = 0x00 ; // reset foot switch cv[15] = 0x00 ; // reset mod wheel } /*********************************************/ void channel_mode(void) // Channel mode processing (foot/modwheel/resets) { if ((chan_mode == 0x01) || (chan_mode == 0x21)) // if its mod wheel then { work_int = chan_mode_val ; work_int = work_int << 5 ; // shift it left 5 times cv[15] = work_int ; // Put value on modwheel CV } if ((chan_mode == 0x40) || (chan_mode == 0x42) || (chan_mode == 0x43) || (chan_mode == 0x44) || (chan_mode == 0x45)) // if its one of the hold pedals { if (chan_mode_val > 0x40) // if its ON { cv[14] = MAX_DAC ; // turn on CV } else // if its not on { cv[14] = 0x00 ; // turn it off } } if (chan_mode == 0x79) // if its reset ALL controllers { cv[12] = MID_DAC ; // reset pitch bend cv[13] = 0x00 ; // reset after touch cv[14] = 0x00 ; // reset foot switch cv[15] = 0x00 ; // reset mod wheel } if ((chan_mode == 0x78) || (chan_mode > 0x7A)) // if its 'all sound off' or any of the 'all notes off' { kill_notes(); } } /*********************************************/ void pitch_bend(void) // Picth bend processing { work_int = pitch_msb ; work_int = work_int << 5 ; // shift pitch bend value left 5 times work = pitch_lsb ; work = work >> 2 ; // shift right 2 bits (we dont care about 2LSB's) work = work & 0b00011111 ; // mask off top 3 incase it shifts through carry cv[12] = work_int + work ; // add two bytes and store them in the CV array } /*********************************************/ void voice_alloc_mono(void) // MONO voice allocation routine { if (midi_status==(midi_chan+NOTE_ON) && midi_velocity) // if NOTE ON received { if (!free_fifo[1] && !PIND.4) // if we're in 'overwrite' mode and fifo is empty { for (work2=0x01; work2 <5; work2++) // for all voices { free_fifo[work2] = work2 ; // re-create fifo current_notes[work2] = 0x00 ; // Blank current note if (!PIND.5) // if we're in 're-trig' mode { cv[work2+7] = 0x00 ; // clear Gate } } } if (free_fifo[1]) // if we have any voices free { work_int = midi_note ; work_int = work_int << 5 ; // shift note value left 5 times work_int2 = midi_velocity ; work_int2 = work_int2 << 5 ; // shift Velocity value left 5 times for (voice=0x01; voice <5; voice++) // for all voices { free_fifo[voice] = 0x00 ; // blank fifo so no more new notes get in current_notes[voice] = midi_note ; // remember which note this voice is playing cv[voice-1] = work_int ; // put note value in CV array cv[voice+3] = work_int2 ; // put velocoty value in CV array cv[voice+7] = MAX_DAC ; // set Gate on } } } if (midi_status==(midi_chan+NOTE_OFF) || !midi_velocity) // if NOTE OFF received { if (midi_note == current_notes[1]) // does it match the note playing? { for (work2=0x01; work2 <5; work2++) // Make all Voices CVs the Same { free_fifo[work2] = work2 ; // re-create fifo current_notes[work2] = 0x00 ; // Blank current note cv[work2+7] = 0x00 ; // clear Gate } } } } /*********************************************/ void voice_alloc(void) // POLY voice allocation routine { if (midi_status==(midi_chan+NOTE_ON) && midi_velocity) // if NOTE ON received { if (free_fifo[1]) // if we have any voices free { work_int = midi_note ; work_int = work_int << 5 ; // shift note value left 5 times work_int2 = midi_velocity ; work_int2 = work_int2 << 5 ; // shift Velocity value left 5 times voice = fifo_remove(); // get next free voice current_notes[voice] = midi_note ; // remember which note this voice is playing cv[voice-1] = work_int ; // put note value in CV array cv[voice+3] = work_int2 ; // put velocity value in CV array cv[voice+7] = MAX_DAC ; // set Gate on } } if ((midi_status==(midi_chan+NOTE_OFF)) || ((midi_status==(midi_chan+NOTE_ON)) && !midi_velocity)) { // if NOTE OFF received for (voice = 0x01; voice < 0x05; voice++) // find which voice is playing the note to be removed { if (midi_note == current_notes[voice]) // does it match any voice? { fifo_add(voice) ; // add it back into the fifo current_notes[voice] = 0x00 ; // makes sure note wont match any of inuse voices cv[voice+7] = 0x00 ; // clear Gate } } } } /*********************************************/ void fifo_add(unsigned char new_data) // add to the fifo { unsigned char i ; for (i=0x01; i < 0x05; i++) // find first space in fifo { if (!free_fifo[i]) // If we find zero put the voice in that place. { free_fifo[i] = new_data ; // load new data break; } } } /*********************************************/ unsigned char fifo_remove(void) // remove byte from the fifo { unsigned char out_data = 0; out_data = free_fifo[1] ; // extract next voice number to be used free_fifo[1]= free_fifo[2] ; // shuffle the fifo down free_fifo[2]= free_fifo[3] ; free_fifo[3]= free_fifo[4] ; free_fifo[4]= 0x00 ; if (!out_data) // check for errors { error(0b11101010) ; // if its zero we've got a problem! } return out_data ; } /*********************************************/ void process_midi(unsigned char byte) // process the byte that has been passed { if (byte > 0x7F) // have we had a command byte (MSB set) { midi_status = 0 ; // we have got here so its gotta be a status byte so start by clearing it. if ((byte == (NOTE_ON + midi_chan)) || (byte == (NOTE_OFF + midi_chan)) || (byte == (CHANMODE + midi_chan)) || (byte == (PITCH + midi_chan)) || (byte == (AFTERTOUCH + midi_chan)) ) { // if its a byte we want to know about midi_status = byte ; // set running status byte midi_char_count = 0 ; } } else { if ((midi_status == (NOTE_ON + midi_chan)) || (midi_status == (NOTE_OFF + midi_chan)) || (midi_status == (CHANMODE + midi_chan)) || (midi_status == (PITCH + midi_chan)) || (midi_status == (AFTERTOUCH + midi_chan)) ) { // if its not a note on/off/chanmode and we;ve had one then it must be data valid_byte = 1 ; // set valid_byte so we can process the data for this command } } if (valid_byte) { valid_byte = 0 ; if (midi_status == (midi_chan + NOTE_ON) && midi_char_count == 1) // if we've had a note on byte & counter = 1 { // then we've just received a velocity byte midi_velocity = byte ; midi_char_count = 0xFF ; // set char count to 255 to skip rest of checks midi_word = 1 ; // set valid word flag so we know we've got a whole word } if (midi_status == (midi_chan + NOTE_ON) && midi_char_count == 0) // if we've had a note on byte & counter = 0 { // then we've just had a note byte midi_note = byte ; midi_char_count = 1 ; // set counter ready to get 3rd byte } if (midi_status == (midi_chan + NOTE_OFF) && midi_char_count == 1) // if we've had a note off byte & counter = 1 { // then we've just received a velocity byte midi_velocity = byte ; midi_char_count = 0xFF ; // set char count to 255 to skip rest of checks midi_word = 1 ; // set valid word flag so we know we've got a whole word } if (midi_status == (midi_chan + NOTE_OFF) && midi_char_count == 0) // if we've had a note off byte & counter = 0 { // then we've just had a note byte midi_note = byte ; midi_char_count = 1 ; // set counter ready to get 3rd byte } if (midi_status == (PITCH + midi_chan) && midi_char_count == 1) // if we've had PITCH BEND command { // Then this must be MSB pitch_msb = byte ; midi_char_count = 0xFF ; // set char count to 255 to skip rest of checks midi_word = 2 ; // set valid word flag so we know we've got a whole word } if (midi_status == (PITCH + midi_chan) && midi_char_count == 0) // if we've had PITCH BEND command { // Then this must be LSB pitch_lsb = byte ; midi_char_count = 1 ; // set counter for 3rd byte } if (midi_status == (CHANMODE + midi_chan) && midi_char_count == 1) // if its a channel mode and we've had first byte { chan_mode_val = byte ; // remember what the value is midi_char_count = 0xFF ; // set char count to 255 to skip rest of checks midi_word = 3 ; // set valid word flag so we know we've got a whole word } if (midi_status == (CHANMODE + midi_chan) && midi_char_count == 0) // if its a channel mode and we've had noteoff/sound off { // process first byte of Channel mode message chan_mode = byte ; // Remember the mode number midi_char_count =1 ; // set counter ready to get 3rd byte } if (midi_status == (AFTERTOUCH + midi_chan) && midi_char_count == 0) // if its aftertouch get first { // and only byte aftertouch_val = byte ; midi_char_count = 0xFF ; // set char count to 255 to skip rest of checks midi_word = 4 ; // set valid word flag so we know we've got a whole word } } if (midi_char_count == 0xFF) // reset midi_char counter if we've had the last { // byte in the series. midi_char_count = 0x00; } } /*********************************************/ void sh_loop(void) // Normal cyclic loading of S&H { work = ~cv_sel; work = work &0b00001111 ; PORTA = work; // select S&H to be written to PORTA.4 = 1 ; // write to S&H (enable mux) delay_us(10) ; PORTA.4 = 0 ; delay_us(10) ; cv_sel++ ; // increase cv pointer if (cv_sel > 0x0F) { cv_sel = 0x00 ; // if we've reached last value, reset it } } /*********************************************/ void setup_io(void) // sets up IO { // Input/Output Ports initialization // Port A DDRA=0xFF; PORTA=0x00; // Port B DDRB=0x00; PORTB=0x00; // Port C DDRC=0xFF; PORTC=0x00; // Port D DDRD=0x80; PORTD=0x00; // Timer/Counter 0 initialization // Clock source: System Clock // Clock value: Timer 0 Stopped // Mode: Output Compare // OC0 output: Disconnected TCCR0=0x00; TCNT0=0x00; // Timer/Counter 1 initialization // Clock source: System Clock // Clock value: Timer 1 Stopped // Mode: Output Compare // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off // Input Capture on Falling Edge TCCR1A=0x00; TCCR1B=0x00; TCNT1H=0x00; TCNT1L=0x00; OCR1AH=0x00; OCR1AL=0x00; OCR1BH=0x00; OCR1BL=0x00; // UART initialization // Communication Parameters: 8 Data, 1 Stop, No Parity // UART Receiver: On // UART Transmitter: Off UCR=0x90; // UART Baud rate: 31250 //UBRR = 0x07 ; // for 4Mhz UBRR=0x0F; //for 8Mhz // Analog Comparator initialization // Analog Comparator: Off // Analog Comparator Input Capture by Timer/Counter 1: Off ACSR=0x80; } /*********************************************/ void error(unsigned char error_number) // error condition { asm ("cli") // disable interupts. work = ~error_number ; // get error number. work_int = 0x00 ; work1 = 0 ; while(1) // flashs LED with error code (8 bits shifted through LED) { load_dac(cv[cv_sel]) ; // load DAC with value from array sh_loop(); // cyclic refresh of S&H's work_int++; if (work_int > 5000) // once it gets big enough { PORTD = work ; // output next bit work = work << 1 ; // shuffle left work_int = 0x0000 ; // reset counter work1++ ; if (work1 == 8) { work1 = 0 ; work = ~error_number ; // reset error byte } } } } /*********************************************/ load_dac(unsigned int output) // load DAC { unsigned data; unsigned char i; data=output; // R16=LSB output R17=MSB output PORTC.0 = 1 ; // Make sure /LOAD is high PORTC.3 = 1; // makesure CLK is high PORTC.4 = 0 ; // select DAC for (i=0;i<12;i++) // Loop { #asm bst r17,3 in r30,PORTC bld r30,2 out PORTC,r30 #endasm PORTC.3 = 0; // Clock data into DAC PORTC.3 = 1; data <<= 1; // shift data left }; delay_us(5); PORTC.4 = 1 ; // De-select DAC delay_us(5); PORTC.0 = 0 ; // Load DAC delay_us(5); PORTC.0 = 1 ; // bring /LOAD high again }