/*********************************************
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
#include
#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
}