//=============================================================================
/*
 * Project name;
	MIDI_Bash1.c      v1.0
	(set TABS to 4)

 * Description;
	PIC16F876 based MIDI drum controller with 10 digital pads.
	This is my first MIDI-Bash (the lap drums).

	When any button is pressed it sends a MIDI note-on signal
	to MIDI-out plug, which makes a MIDI device play a drum sound.
	v1.0 has no velocity info, all drum sounds are the same
	velocity (volume). v1.0 has 10 drum hit buttons.
	There are 2 other buttons; up/down
	If a drum is hit while either button is pressed the drum
	sound changes up/down to the next note (next drum sound).
	All notes are limited to MIDI channel 10 (standard for drums).

 * Version;
	v1.0   initial testing, started 31/01/2009

 * Test configuration;
    MCU:             PIC16F876 (NOT A)
    Dev.Board:       EasyPIC4
    Oscillator:      HS, 08.0000 MHz (2pin short-can xtal)
    SW:              mikroC v7.0.0.3

 * PIC pins;
	RC6	out		serial MIDI out, manual bit banging (no usart needed)
	RC5 out		drum led, flashes at power up and when any pad is hit

	RC0 in      Note down button, pullup resistor, low=pressed
	RC1 in      Note up button, pullup resistor, low=pressed

	RB0-RB7 in  8 "drum" buttons (0-7), pullup resistor, low=pressed
	RC2 in      drum button (8), "
	RC3 in      drum button (9), "  (total 0-9 =10 drums)

*/
//=============================================================================

// global vars

char sdata;				// disposable 2 bytes used for serial bit-banging;
char sbit;				// (must be first, need low place in ram for asm use)

//char i;               	// used for anything

char the_note;			// the note to send to MIDI for this drum hit

char note0;           	// MIDI note value for each of the 10 drum buttons
char note1;           	//  (which can be changed when device is operating)
char note2;           	//
char note3;           	//
char note4;           	//
char note5;           	//
char note6;           	//
char note7;           	//
char note8;           	//
char note9;           	//

char wait_n0;			// used for debounce and afterpause for each drum note
char wait_n1;			//
char wait_n2;			//
char wait_n3;			//
char wait_n4;			//
char wait_n5;			//
char wait_n6;			//
char wait_n7;			//
char wait_n8;			//
char wait_n9;			//


// Constants used in my MIDI transmit funcions;
#define MIDI_CHANNEL 	10		// MIDI output channel (1-16) to drum machine
//#define MIDI_NOTE_ON	(0b10010000 + MIDI_CHANNEL-1) // byte used to send MIDI note-on command
#define MIDI_NOTE_ON	0b10011001 // byte used to send MIDI note-on command

#define NOTE_MIN        30      // lowest MIDI note that can be selected
#define NOTE_MAX        98		// highest " (Zoom MRS1608 range is 31-70) 40 sounds
								// (Roland D-20 drum range is 35-97) 63 sounds
								// test this a bit...

// starting values for drum sounds
#define DEFNOTE_0      	31      // kick (using Zoom MRS1608 standard drum notes)
#define DEFNOTE_1  		35      // snare
#define DEFNOTE_2 		40      // hat open
#define DEFNOTE_3 		45      // hat closed
#define DEFNOTE_4 		50      // tom1
#define DEFNOTE_5  		55      // tom2
#define DEFNOTE_6 		60      // tom3
#define DEFNOTE_7 		65      // crash
#define DEFNOTE_8 		70      // crash
#define DEFNOTE_9 		75      // crash


#define DEB_COUNT		21	// debounce wait count for debounce timer;
							// PIC 8Mhz prescale 32:1, so 21 TMR0 overflows = 84mS
#define DEB_COUNTSAFE	5	// safe period for after debounce special situations


#define PORT_MIDI		PORTC		// used for bit-banged serial out to MIDI
#define PIN_MIDI		6    		// "
#define PIN_LED_DRUM	PORTC.F5    // flashes a led when any drum button is pressed

#define PIN_DOWNBUT		PORTC.F0
#define PIN_UPBUT		PORTC.F1

// drums 0-7 input pins is PORTB.F0 - PORTB.F7
#define PIN_DRUM8		PORTC.F2    // with these 2 is 10 drums total
#define PIN_DRUM9		PORTC.F3

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//void  interrupt(void)
//{
   // is (TMR1) CCP2 capture int, so just clear TMR1 ??
//}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


//=============================================================================
//  SEND_MIDI_BYTE
//=============================================================================
void send_midi_byte(void)
{
	//----------------------------------------------------
	// This version uses TMR0 for bit-banging, no usart needed.
	// I wrote this function in PIC assembler to speed it up as the
	// MikroC compiler SUCKS when it comes to fast bit manipulation.
	//-------------------------------------------------
	// NOTE! MIDI data is electrically inverted! ie MIDI bit=1 is PIC pin=O
	//
	// start bit, 8 data bits (LSBit first), then stop bit
	//
	// PIC 8MHz = 2MHz ticks, so MIDI at 31250 baud = 64 timer ticks
	// using TMR0 prescaled at 32:1 so 31250 baud  = 2 timer ticks
	// therefore whenever TMR0 bit1 changes = a baud
	//
	// This is a zero cumulative error system, even though there
	// is a variable 0-6 instruction delay for each bit
	// it still always re-syncs to TMR0,bit1 for the next bit.
	// Given that there is 64 insts between bits and that
	// the receiving device samples the bit right in the middle
	// (which is standard) this should give reliable serial
	// transmission. It will probably still work with an
	// internal RC osc at 4MHz too.. test later.
	//----------------------------------------------------
	sbit = 0;   // this is needed to make MikroC compiler recognise
	            // variables in the asm block below...
				// weird, but it is covered in MikroC help file.

	asm {
		;----------------------------
		; PIC 31250 baud MIDI bit-banging serial code - RomanBlack 2009
		; sends one byte out to MIDI port. byte is in sdata variable.
		; PIC 8 MHz, TMR0 is free-running and prescaled at 32:1
		; it is easily adapted to 4 Mhz or 16 Mhz, just change the prescale.
		; This assembler cource code is public domain, use it as you wish.
		;----------------------------
		; pre-delay, to sync to a "baud", then send start bit

		predelay:
        	BTFSC	TMR0, 1     	; pre-delay to sync baud with TMR0
			GOTO    predelay		;

		wbStart:
        	BTFSS	TMR0, 1			; again wait to ensure sync with baud
			GOTO    wbStart			;
			BCF     PORT_MIDI,PIN_MIDI ; send start bit (inverted remember)

		;----------------------------
		; now send the 8 serial bits, as 4 pairs of 2 bits (even then odd).
		; each bit tested first, then sync to baud using TMR0, then send bit.
		; 8 MHz PIC, each baud is 64 insts. jitter per baud is 0 to 2 insts.
		; system is zero-cumulative error, ie; it re syncs with every baud.
		; this gives very solid performance without needing a usart.

			MOVLW   4				; 4 bit pairs to send
			MOVWF   sbit			;

		serial_even:
			BTFSC   sdata,0			; test serial even bit
			GOTO	even1			;

		even0:
        	BTFSC	TMR0, 1			; wait for baud
			GOTO	even0			;
			BCF		PORT_MIDI,PIN_MIDI	; send bit (inverted remember)
			GOTO	serial_odd		;

		even1:
        	BTFSC	TMR0, 1			; wait for baud
			GOTO	even1			;
			BSF		PORT_MIDI,PIN_MIDI	; send bit (inverted remember)

		serial_odd:
			RRF		sdata,f         ; load next serial bit into sdata,0
			BTFSC   sdata,0			; test serial odd bit
			GOTO	odd1            ;

		odd0:
        	BTFSS	TMR0, 1			; wait for baud
			GOTO	odd0			;
			BCF		PORT_MIDI,PIN_MIDI	; send bit (inverted remember)
			GOTO	pair_done		;

		odd1:
        	BTFSS	TMR0, 1			; wait for baud
			GOTO	odd1			;
			BSF		PORT_MIDI,PIN_MIDI	; send bit (inverted remember)

		pair_done:
			RRF		sdata,f			; load next serial bit into sdata,0
			DECFSZ	sbit,f			; another bit pair was done, so test
			GOTO	serial_even		; bits left! so send another pair

		;----------------------------
		; send the stop bit

		wbStop:
        	BTFSC	TMR0, 1			; wait to sync with baud
			GOTO    wbStop			;
			BSF     PORT_MIDI,PIN_MIDI ; send stop bit (inverted remember)
	}
}
//-----------------------------------------------------------------------------



//=============================================================================
//  SEND_NOTE_ON
//=============================================================================
void send_note_on(void)
{
	//-------------------------------------------------
	// Sends 3 serial bytes out to MIDI port at 31250 baud;
	//   byte0	MIDI note on = 1001cccc (cccc = MIDI channel(1-16) -1)
	//   byte1	note 0-127 (the specific drum sound)
	//   byte2	velocity 0-127 (volume) i'm using fixed value of 100?
	//
	// This system just ties up the PIC while it sends
	// all 3 bytes. At standard MIDI 31250 baud that is less
	// than 1mS to send all 3 bytes so it shouldn't matter.
	// That is the fastest that MIDI can send drum messages anyway.
	//----------------------------------------------------

	// byte0 = MIDI specific note-on command
	sdata = MIDI_NOTE_ON;   	// data byte to send
	send_midi_byte();

	// byte1 = the note (which drum sound to play) MIDI range 0-127
	sdata = the_note;   		// data byte to send
	send_midi_byte();

	// byte2 = velocity (note volume) MIDI range 0-127
	// for now using a fixed note velocity of 100 (near max)
	sdata = 100;   				// data byte to send
	send_midi_byte();

	//----------------------------------------------------
}
//-----------------------------------------------------------------------------


//=============================================================================
//  MAIN
//=============================================================================
void main()
{
    //-------------------------------------------------
	// Setup PIC registers etc
    //-------------------------------------------------
	// rough PIC setup for now!
	// Setup 16F876
	ADCON0 = 0;          // 0= ADC is off
	ADCON1 = 0x07;       // 0x07 = PORTA digital pins, NOT adc pins

	PORTA  = 0;
	TRISA  = 0b00000000;  	// PORTA all outs, (unused?)
	PORTB  = 0;
	TRISB  = 0b11111111;   	// PORTB all in (drum0-drum7)
	PORTC  = 0;
	TRISC  = 0b00001111;	// PORTC; RC6 = serial out to MIDI)
	                        // RC0=down button in, RC1=up button in
							// RC2=drum8 in, RC3=drum9 in
	INTCON = 0;          // clear INTCON

	//-------------------------------------------------
 	// TMR0 setup
  	/*                  ; OPTION_REG setup 16F876
	movlw b'00000100'
         ; 7         portb pullups, 1=disabled, 0=enabled
         ;  6        RBO/INT pin edge select, 1= rising, 0=falling edge
         ;   5       TMR0 clock source, 1=ext RA4, 0=int clock
         ;    4      TMR0 source ext RA4 edge, 0=rising edge
         ;     3     PSA prescaler assign, 1=WDT, 0=TMR0
         ;      2    these three are the 3 bit prescaler rate;
         ;       1   000-111 = 2-256 for TMR0, 1-128 for WDT
         ;        0   (using 100 = 32:1 prescale)      */
	OPTION_REG = 0b00000100;

	// PIC 8MHz = 2MHz ticks, so MIDI at 31250 baud = 64 timer ticks
	// using TMR0 prescaled at 32:1 so 31250 baud  = 2 timer ticks
	// therefore whenever TMR0 bit1 changes = a baud

	// I think for different PIC clock speeds you can just
	// change the prescale value and leave the rest of the code as is.
	// PIC 4MHz use 16:1
	// PIC 8MHz use 32:1     (this code)
	// PIC 16MHz use 64:1

   	//-------------------------------------------------

   	// TMR1 setup
   	/*                   ; timer1 control
   	movlw b'00000000'
         ; 7-------  na
         ; -6------  na
         ; --5-----  TIMER1 prescaler... two bits (5,4)
         ; ---4----   00=1, 01=2, 10=4, 11=8 prescale (using 00= 1:1)
         ; ----3---  T1OSCEN - timer1 osc enable, 1=en
         ; -----2--  T1SYNC - ext sync
         ; ------1-  TMR1CS - ext/inter, 0=internal clock
         ; -------0  TMR1ON - 1=timer1 enabled        */

	T1CON = 0b00000000;    // TMR1 not used


   	//-------------------------------------------------
	// Initialise vars
   	//-------------------------------------------------

	// load the starting (default) note values for the 8 drums
	note0 = DEFNOTE_0;
	note1 = DEFNOTE_1;
	note2 = DEFNOTE_2;
	note3 = DEFNOTE_3;
	note4 = DEFNOTE_4;
	note5 = DEFNOTE_5;
	note6 = DEFNOTE_6;
	note7 = DEFNOTE_7;
	note8 = DEFNOTE_7;
	note9 = DEFNOTE_7;

	// load the wait timers for each drum;
	// this gives a bootup delay before any drums can be
	// triggered, 200 gives about 0.8 seconds.
	// (the drum led will also be on for this 0.8 secs)
	wait_n0 = 200;
	wait_n1 = 200;
	wait_n2 = 200;
	wait_n3 = 200;
	wait_n4 = 200;
	wait_n5 = 200;
	wait_n6 = 200;
	wait_n7 = 200;
	wait_n8 = 200;
	wait_n9 = 200;


	// set the MIDI output to off (HI)
	PORTC = 0;					// safe; drum led off etc
	PORT_MIDI.F6 = 1;			// MIDI output pin off (HI)

   	//-------------------------------------------------
   	// MAIN RUN MODE AFTER HERE
   	//-------------------------------------------------
	// Operation;
	// * the 8 inputs are checked to see if button pressed (LO)
	// * if pressed, send 3 bytes to MIDI (make one drum sound)
	// * debounce inputs with a wait timer to stop retriggering drums!
	//
	// Details;
	// * test all inputs; only test an input if its wait timer==0
	// * if a button is pressed;
	//		* set its wait timer to a "debounce" value
	//		* send 3 MIDI bytes, do nothing else until all 3 sent ok.
	// * every TMR0 overflow;
	//		* if wait timer >1; dec wait timer
	//      * if wait timer==1, dec only if button is released (see below)
	//
	// This is about the simplest system to sense 8 buttons then fire drum
	// sounds off to MIDI, then ensure a simple delay with a countdown timer
	// to stop a drum being retriggered too soon. The simplest debounce
	// is used so it just counts a time period after the button was first
	// pressed, after a set time it is tested to make sure it is released,
	// then resets ready to be retriggered again. So if a button is held
	// down it will only make the drum sound once. This gives a faster
	// max drumroll speed then a long delay which would be needed for
	// other debounce systems.
   	//-------------------------------------------------

	TMR0 = 0;           // reset timer0
	INTCON.T0IF = 0;    // clear TMR0 overflow flag

	// main run loop
	while(1)
	{
		//---------------------------------------
		// this loop mainly just checks the 10 drum buttons.
		//---------------------------------------
		// if any button has JUST been pressed; send drum note
		if(wait_n0==0 && PORTB.F0==0)	// if debounce is reset && button pressed
		{
			// start debounce timer
			wait_n0 = DEB_COUNT;

			// TEMP!! test to led
			//PORTC.F7 = 1;

			// if up/down buttons pressed (LO), change the selected note (drum)
			if(PIN_DOWNBUT == 0)	note0--;      // change note
			if(PIN_UPBUT == 0)		note0++;
			if(note0 < NOTE_MIN) note0=NOTE_MIN;  // and limit at min/max
			if(note0 > NOTE_MAX) note0=NOTE_MAX;

			// now play the drum sound
			the_note = note0;    	// the drum note to send out to MIDI
			send_note_on();         // send it!
		}

		if(wait_n1==0 && PORTB.F1==0)
		{
			// TEMP!! test to led
			//PORTC.F7 = 0;

			wait_n1 = DEB_COUNT;
			if(PIN_DOWNBUT == 0)	note1--;
			if(PIN_UPBUT == 0)		note1++;
			if(note1 < NOTE_MIN) note1=NOTE_MIN;
			if(note1 > NOTE_MAX) note1=NOTE_MAX;
			the_note = note1;
			send_note_on();
		}

		if(wait_n2==0 && PORTB.F2==0)
		{

			wait_n2 = DEB_COUNT;
			if(PIN_DOWNBUT == 0)	note2--;
			if(PIN_UPBUT == 0)		note2++;
			if(note2 < NOTE_MIN) note2=NOTE_MIN;
			if(note2 > NOTE_MAX) note2=NOTE_MAX;
			the_note = note2;
			send_note_on();
		}

		if(wait_n3==0 && PORTB.F3==0)
		{
			wait_n3 = DEB_COUNT;
			if(PIN_DOWNBUT == 0)	note3--;
			if(PIN_UPBUT == 0)		note3++;
			if(note3 < NOTE_MIN) note3=NOTE_MIN;
			if(note3 > NOTE_MAX) note3=NOTE_MAX;
			the_note = note3;
			send_note_on();
		}

		if(wait_n4==0 && PORTB.F4==0)
		{
			wait_n4 = DEB_COUNT;
			if(PIN_DOWNBUT == 0)	note4--;
			if(PIN_UPBUT == 0)		note4++;
			if(note4 < NOTE_MIN) note4=NOTE_MIN;
			if(note4 > NOTE_MAX) note4=NOTE_MAX;
			the_note = note4;
			send_note_on();
		}

		if(wait_n5==0 && PORTB.F5==0)
		{
			wait_n5 = DEB_COUNT;
			if(PIN_DOWNBUT == 0)	note5--;
			if(PIN_UPBUT == 0)		note5++;
			if(note5 < NOTE_MIN) note5=NOTE_MIN;
			if(note5 > NOTE_MAX) note5=NOTE_MAX;
			the_note = note5;
			send_note_on();
		}

		if(wait_n6==0 && PORTB.F6==0)
		{
			wait_n6 = DEB_COUNT;
			if(PIN_DOWNBUT == 0)	note6--;
			if(PIN_UPBUT == 0)		note6++;
			if(note6 < NOTE_MIN) note6=NOTE_MIN;
			if(note6 > NOTE_MAX) note6=NOTE_MAX;
			the_note = note6;
			send_note_on();
		}

		if(wait_n7==0 && PORTB.F7==0)
		{
			wait_n7 = DEB_COUNT;
			if(PIN_DOWNBUT == 0)	note7--;
			if(PIN_UPBUT == 0)		note7++;
			if(note7 < NOTE_MIN) note7=NOTE_MIN;
			if(note7 > NOTE_MAX) note7=NOTE_MAX;
			the_note = note7;
			send_note_on();
		}


		if(wait_n8==0 && PIN_DRUM8==0)
		{
			wait_n8 = DEB_COUNT;
			if(PIN_DOWNBUT == 0)	note8--;
			if(PIN_UPBUT == 0)		note8++;
			if(note8 < NOTE_MIN) note8=NOTE_MIN;
			if(note8 > NOTE_MAX) note8=NOTE_MAX;
			the_note = note8;
			send_note_on();
		}

		if(wait_n9==0 && PIN_DRUM9==0)
		{
			wait_n9 = DEB_COUNT;
			if(PIN_DOWNBUT == 0)	note9--;
			if(PIN_UPBUT == 0)		note9++;
			if(note9 < NOTE_MIN) note9=NOTE_MIN;
			if(note9 > NOTE_MAX) note9=NOTE_MAX;
			the_note = note9;
			send_note_on();
		}


		//---------------------------------------
		// Now check for TMR0 event! if so, dec the 8 wait timers.
		//
		// PIC 8MHz needs TMR0 prescaled at 32:1
		// so each overflow occurs at 2Mhz / 32 / 256 = 244Hz
		// 1/244 = 0.004096 so time between each overflow is 4mS
		//
		// To get good drum speed for drumroll etc we need to be able
		// to get about 12 drum hits per second. However to stop
		// double triggering with one hit (that's bad!) the debounce
		// needs to be as long as possible. So we use the max debounce;
		// ie use; 1 second /12 = 83mS.
		// as each overflow is 4mS, we use 21 overflows = 84mS
		//
		// That value 21 is set in the DEB_COUNT constant.
		// if using other PIC clock speeds just change TMR0
		// prescale value - see timer0 setup.
		//---------------------------------------
		// * every TMR0 overflow;
		//		* if wait timer >1; dec wait timer
		//      * if wait timer==1, dec only if button is released (see below)
		//---------------------------------------

		// check for TMR0 event! if so, dec the 8 wait timers.
		if(INTCON.T0IF == 1)    	// if TMR0 overflowed
		{
			// first clear overflow flag
			INTCON.T0IF = 0;

			// set the drum led to off (LO) by default
			PIN_LED_DRUM = 0;

			// then process all 8 drum button timers
			if(wait_n0 > 0)
			{
				wait_n0--;          // subtract another overflow period

			    // if button is still pressed (LO), thats bad!
			    // so add some more debounce time to allow for
			    // any button release bounce to be past.
				if(wait_n0==0 && PORTB.F0==0) wait_n0=DEB_COUNTSAFE;

				// if any wait timer is >0 then set the drum led to on!
				// this makes the drum led give a quick flash
				// every time a drum button is hit.
				PIN_LED_DRUM = 1;     // led on = HI
			}

			if(wait_n1 > 0)
			{
				wait_n1--;
				if(wait_n1==0 && PORTB.F1==0) wait_n1=DEB_COUNTSAFE;
				PIN_LED_DRUM = 1;
			}

			if(wait_n2 > 0)
			{
				wait_n2--;
				if(wait_n2==0 && PORTB.F2==0) wait_n2=DEB_COUNTSAFE;
				PIN_LED_DRUM = 1;
			}

			if(wait_n3 > 0)
			{
				wait_n3--;
				if(wait_n3==0 && PORTB.F3==0) wait_n3=DEB_COUNTSAFE;
				PIN_LED_DRUM = 1;
			}

			if(wait_n4 > 0)
			{
				wait_n4--;
				if(wait_n4==0 && PORTB.F4==0) wait_n4=DEB_COUNTSAFE;
				PIN_LED_DRUM = 1;
			}

			if(wait_n5 > 0)
			{
				wait_n5--;
				if(wait_n5==0 && PORTB.F5==0) wait_n5=DEB_COUNTSAFE;
				PIN_LED_DRUM = 1;
			}

			if(wait_n6 > 0)
			{
				wait_n6--;
				if(wait_n6==0 && PORTB.F6==0) wait_n6=DEB_COUNTSAFE;
				PIN_LED_DRUM = 1;
			}

			if(wait_n7 > 0)
			{
				wait_n7--;
				if(wait_n7==0 && PORTB.F7==0) wait_n7=DEB_COUNTSAFE;
				PIN_LED_DRUM = 1;
			}


			if(wait_n8 > 0)
			{
				wait_n8--;
				if(wait_n8==0 && PIN_DRUM8==0) wait_n8=DEB_COUNTSAFE;
				PIN_LED_DRUM = 1;
			}

			if(wait_n9 > 0)
			{
				wait_n9--;
				if(wait_n9==0 && PIN_DRUM9==0) wait_n9=DEB_COUNTSAFE;
				PIN_LED_DRUM = 1;
			}

		}
		//---------------------------------------
	}
}
//-----------------------------------------------------------------------------






//=============================================================================
//  BLANK
//=============================================================================
void blank(void)
{
   //-------------------------------------------------



   //------------------------------------------------
}
//-----------------------------------------------------------------------------


















