//***************************************************************
// ECE 476 SPRING'05 FINAL PROJECT : MIDI DRUM MACHINE
// By Siew Im Low & Zheshen
//***************************************************************

#include <mega32.h>
#include <stdio.h>
#include <delay.h>
#include <stdlib.h>

// LCD driver routines
#asm
.equ __lcd_port=0x15
#endasm
#include <lcd.h>

#define begin {
#define end }
#define t1 6
#define t2 25
#define delay 50

// Debounce States
#define release 1
#define debounce 2
#define still_pressed 3
#define debounce_release 4
// Length (in bytes) for transmit buffer
#define buffer_length 32

char LCDstr[16];
// Predefined volume levels
flash char volset[5] = { 0, 0x2f, 0x4f, 0x5f, 0x7f };

//TASK 1 Variables: to capture the output volume levels from the accelerometers using ADC
char vol[4], drum[4],time[4], hardware[4], current[4], peak[4], off_flag[4];
char time1;
//TASK 2 Variables:
char vol2[4]; // Software volume controls
char prog1; // drum type
char submenu,count, k,p;
char butnum, prog, channel, channelf, n;
char time2, TheBigStates, mode, menu, maybe;
//TASK 3 Variables:
int time3, metronome;

//TX empth ISR variables
unsigned char p_send; //index of character to be sent
unsigned char p_empty; // index of the next empty slot in queue buffer
unsigned char t_buffer[buffer_length]; //output string

//the subroutines
void initialise(void);
void task1(void); // debounce drumpads
void task2(void); // User interface
void task3(void); // drum beat metronome
void note_on(char note, char volume);
void note_off(char note, char volume);
void prog_change(char prog);
void all_notes_off(char channelf);
void transmit(char data);

/**********************************************************/
// USART txmit-empty ISR: Transmit next character if t_buffer empty; else kill ISR
// Routine used to send MIDI messages queued in t_buffer.
interrupt [USART_DRE] void uart_send(void)
begin
// no more message to be sent: do nothing
if (p_send == p_empty) UCSRB.5=0; //kill ISR
else
begin
UDR = t_buffer[p_send++]; //send the char
if(p_send == buffer_length) p_send = 0;
end
end
/**********************************************************/
//timer 0 overflow ISR: 1 ms timer.
interrupt [TIM0_COMP] timer0_overflow(void)
begin
if (time[0]>0) --time[0];
if (time[1]>0) --time[1];
if (time[2]>0) --time[2];
if (time[3]>0) --time[3];
if (time2>0) --time2;
if (time3>0) --time3;
end

/**********************************************************/
// MAIN
void main (void)
{
initialise();
while(1)
{
if(time[0] ==0) task1();
if(time[1] ==0) task1();
if(time[2] ==0) task1();
if(time[2] ==0) task1();
if(time2 ==0) task2();
if(time3 ==0) task3();
}
}

//***********************************************************
// Function that sends a ALL NOTE OFF MIDI message to the queue
void all_notes_off(char channelf)
{
transmit(0xB0 | (channelf-1));
transmit(0x7B);
transmit(0x00);
}
//**********************************************************
// TASK 1: Sequentially "debounce" the 4 drum pads.
void task1(void)
{

time[n] = t1;
n--;

// The very next cycle after drum pad has been turn off, transmit NOTE_OFF MIDI message
if(off_flag[n] == 1)
{
note_off(drum[n],volset[vol[n]-1]);
off_flag[n] = 0;
}
else
{
// Get adc value from respective drum pads
if(n ==0) ADMUX = 0b01100000;
if(n ==1) ADMUX = 0b01100001;
if(n ==2) ADMUX = 0b01100010;
if(n ==3) ADMUX = 0b01100011;
ADCSR.6 = 1;
delay_us(200);
current[n] = ADCH;

// Tracks the peak voltage spike triggered by a hit on the drum pad
if(current[n] > peak[n]) peak[n] = current[n];

// When the first spike peak is clearly declining.
if(current[n] < peak[n]-20)
{
// Quantisation of the peak value to 3 given volume control
if(peak[n]-128 < 70) vol[n] = 2;
else if(peak[n]-128 < 120) vol[n] = 4;
else vol[n] = 5;

// Send the NOTE ON MIDI message
if(hardware[n] ==1)
note_on(drum[n],volset[vol[n]-1]);
else note_on(drum[n],volset[vol2[n]-1]);

// Stop sampling the specific drumpad for delay cycles to allow secondary spikes to die out.
time[n] = delay;
off_flag[n] = 1;
peak[n] = 100;

}
}
if(n==0) n = 4;
}

//**********************************************************
// TASK 2: TAKES CARE OF USER INTERFACE: Debounce the pushbuttons and update LCD
void task2(void)
{

time2 = t2;

switch (TheBigStates)
{
case release:
butnum = (~PINB)&0x0f;
maybe= butnum;
if(butnum !=0) TheBigStates= debounce;
break;

case debounce:
butnum = (~PINB)&0x0f;
if (butnum == maybe)
{
switch (butnum)
{
// Scrolling down the menus or decreasing values
case 1:
if (menu ==1 && mode>1) mode--;
if (menu ==2 && mode ==2 && channel>1) channel--;
if (menu ==2 && mode ==3 && prog1>1) { prog1--; prog_change(prog1); }
if (menu ==2 && mode ==4 && k>1) k--;
if (menu ==2 && mode ==5 && p>1) p--;
if (menu ==2 && mode ==6 && metronome >500) metronome -=100;
if (menu ==3 && mode ==4 && drum[k-1]>35) drum[k-1]--;
if (menu ==3 && mode ==5 && vol2[p-1]>1)
{
vol2[p-1]--;
if(vol2[p-1] !=5) hardware[p-1] = 0;
}
break;
// Scrolling up the menus or increasing values
case 2:
if (menu ==1 && mode<6) mode++;
if (menu ==2 && mode ==2 && channel<16) channel++;
if (menu ==2 && mode ==3 && prog1<37) { prog1++; prog_change(prog1); }
if (menu ==2 && mode ==4 && k<4) k++;
if (menu ==2 && mode ==5 && p<4) p++;
if (menu ==2 && mode ==6 && metronome <2000) metronome +=100;
if (menu ==3 && mode ==4 && drum[k-1]<81) drum[k-1]++;
if (menu ==3 && mode ==5 && vol2[p-1]<5)
{
vol2[p-1]++;
if(vol2[p-1] ==5) hardware[p-1] = 1;
}
break;

// Moving back to a previous menu
case 4:
if(menu !=1) menu--;
break;
// Go to next submenu
case 8:
if(menu == 1) menu++;
if(menu == 2 && (mode ==4 || mode ==5)) menu++;
break;
}
TheBigStates= still_pressed;

}
else TheBigStates= release;
break;

case still_pressed:
butnum = (~PINB)&0x0f;

// Refresh the LCD displayed after each button pushed
lcd_clear();
lcd_gotoxy(0,0);
if (menu == 1)
{
if (mode==1) sprintf(LCDstr,"MIDI DRUM");
if (mode==2) sprintf(LCDstr,"Select Channel");
if (mode==3) sprintf(LCDstr,"Select Drumtype");
if (mode==4) sprintf(LCDstr,"Select");
if (mode==5) sprintf(LCDstr,"Volume Control");
if (mode==6) sprintf(LCDstr,"Metronome");
}
if (menu == 2)
{
if (mode ==2) sprintf(LCDstr,"Channel:");
if (mode ==3) sprintf(LCDstr,"Drum Type:");
if (mode ==4) sprintf(LCDstr,"Select DRUM %d", k);
if (mode ==5) sprintf(LCDstr,"Select DRUM %d", p);
if (mode==6) sprintf(LCDstr,"Metronome:");
}
if( menu ==3)
{
if (mode ==4) sprintf(LCDstr,"DRUM %d", k);
if (mode ==5) sprintf(LCDstr,"DRUM %d", p);
}

lcd_puts(LCDstr);
// insert 2nd line of LCD
lcd_gotoxy(0,1);
if (menu == 1)
{
if (mode==1) sprintf(LCDstr,"CONTROLLER");
if (mode==2) sprintf(LCDstr," ");
if (mode==3) sprintf(LCDstr," ");
if (mode==4) sprintf(LCDstr,"Instrument");
if (mode==5) sprintf(LCDstr," ");
if (mode==6) sprintf(LCDstr," ");
}
if (menu == 2)
{
if (mode ==1) sprintf(LCDstr," ");
if (mode ==2) sprintf(LCDstr," %d",channel);
if (mode ==3) sprintf(LCDstr," %d",prog1);
if (mode ==4) sprintf(LCDstr,"Instrument ");
if (mode ==5) sprintf(LCDstr,"Instrument ");
if (mode ==6) sprintf(LCDstr," %d", metronome);
}
if( menu ==3)
{
if (mode ==1) sprintf(LCDstr," ");
if (mode ==2) sprintf(LCDstr," ");
if (mode ==3) sprintf(LCDstr," ");
if (mode ==4) sprintf(LCDstr,"Instrument %d", drum[k-1]);
if (mode ==5 && vol2[p-1] != 5) sprintf(LCDstr,"Volume %d", vol2[p-1]);
if (mode ==5 && vol2[p-1] == 5) sprintf(LCDstr,"Volume HARDWARE");
}
lcd_puts(LCDstr);
if (butnum != maybe)TheBigStates = debounce_release;
break;

case debounce_release:
butnum = (~PINB)&0x0f;
if (butnum != maybe )TheBigStates = release;
else TheBigStates = still_pressed;
break;
}
}
//***********************************************************
// TASK 3 : Generate a LED metronome that blinks at a rate of "metronome"
void task3(void)
{
time3 = metronome/2;
PORTB.4 = ~PORTB.4;
}

//***********************************************************
// function that sends a NOTE ON MIDI message to the queue
void note_on(char note,char volume)
{
transmit(0b10010000 | (channel-1));
transmit(note);
transmit(volume);
}
//***********************************************************
// function that sends a NOTE OFF MIDI message to the queue
void note_off(char note, char volume)
{
transmit(0b10000000 | (channel-1));
transmit(note);
transmit(0x7f);
}
//***********************************************************
// MIDI EXCLUSIVE FORMAT (Roland RS-70) that request for change of drum type to the queue
// prog: 1 - 7 corresponds to GM drum patch 112 - 119
// prog: 8 - 37 corrsponds to Roland pre-defined drum patches

void prog_change(char prog)
{
// # GM patch : 1-7
// Roland bank select : 8-37

transmit(0xB0 | (channel-1));
transmit(0x00);
if(prog <8) transmit(121);
else transmit(86);
transmit(0xB0 | (channel-1));
transmit(0x20);
if(prog <8) transmit(0x00);
else transmit(64);
transmit(0x00);
transmit(0xC0 | (channel-1));
if(prog<8) transmit(prog+111);
else transmit(prog -8);
}

//**********************************************************
// -- nonblocking print: initializes ISR-driven
// transmit. This routine merely sets up the ISR, then
//send one character, The ISR does all the work.

void transmit(char data)
{
t_buffer[p_empty++] = data;
if(p_empty == buffer_length)
p_empty = 0;
//if(p_empty == p_send)PORTB = ~0xf1;
UCSRB.5=1;
}

//**********************************************************
void initialise(void)
{

// LCD initialisation
lcd_init(16);
lcd_clear();
lcd_gotoxy(0,0);
lcd_putsf("MIDI DRUM");
lcd_gotoxy(0,1);
lcd_putsf("CONTROLLER");

//Initialising the USART
UCSRB = 0x18 ;
UBRRL = 31 ; // 31250 baud
p_send = 0;
p_empty = 0;

//set up timer 0
OCR0=249; //1 mSec
TIMSK=2; //turn on timer 0 cmp-match ISR
TCCR0=0b00001011; //prescalar to 64 and Clr-on-match

// push buttons Inputs and blinking metronome output
DDRB = 0xf0;
PORTB = 0xff;

//Initialise ADC to read from the drum pads
ADMUX = 0b01100000;
ADCSR = 0b11000111;
vol[0] =ADCH;

//init the task timers
time[0]=t1;
time[1]=t1;
time[2]=t1;
time[3]=t1;
time2=t2;
n=3;

//Default values
TheBigStates = release;
vol2[0] = 5;
vol2[1] = 5;
vol2[2] = 5;
vol2[3] = 5;
drum[0] = 35;
drum[1] = 38;
drum[2] = 42;
drum[3] = 57;
off_flag[0]=0;
off_flag[1]=0;
off_flag[2]=0;
off_flag[3]=0;
prog1 = 8 ;
channel = 10;
butnum = 0;
menu=1;
k =1;
p =1;
mode =1;
metronome = 1000;
count=1;
hardware[0] = 1;
hardware[1] = 1;
hardware[2] = 1;
hardware[3] = 1;
peak[0]= trigger[0];
peak[1]= trigger[1];
peak[2]= trigger[2];
peak[3]= trigger[3];
#asm
sei
#endasm
mode =1;
}