From fd77bf5c9459360b8d3ae1679dfab71169d4a982 Mon Sep 17 00:00:00 2001
From: Dean Camera <dean@fourwalledcubicle.com>
Date: Thu, 25 Nov 2010 02:46:35 +0000
Subject: [PATCH] Make the incomplete MIDIToneGenerator project work with up to
 three notes, using a LRU (Least Recently Used) algorithm to discard the
 oldest set note when the note table becomes full.

---
 .../MIDIToneGenerator/MIDIToneGenerator.c     | 52 +++++++++++--------
 .../MIDIToneGenerator/MIDIToneGenerator.h     | 36 ++++++++++---
 2 files changed, 61 insertions(+), 27 deletions(-)

diff --git a/Projects/Incomplete/MIDIToneGenerator/MIDIToneGenerator.c b/Projects/Incomplete/MIDIToneGenerator/MIDIToneGenerator.c
index 7c5fc9a62..c8e06bed4 100644
--- a/Projects/Incomplete/MIDIToneGenerator/MIDIToneGenerator.c
+++ b/Projects/Incomplete/MIDIToneGenerator/MIDIToneGenerator.c
@@ -78,14 +78,7 @@ const uint8_t SineTable[256] =
 };
 
 /** Array of structures describing each note being generated */
-struct
-{
-	uint8_t  Pitch;
-	uint32_t TableIncrement;
-	uint32_t TablePosition;
-} NoteData[3];
-
-uint8_t PhaseCounter;
+DDSNoteData NoteData[MAX_SIMULTANEOUS_NOTES];
 
 /** Main program entry point. This routine contains the overall program flow, including initial
  *  setup of all components and the main program loop.
@@ -104,19 +97,34 @@ int main(void)
 		{
 			if ((ReceivedMIDIEvent.Command == (MIDI_COMMAND_NOTE_ON >> 4)) && ((ReceivedMIDIEvent.Data1 & 0x0F) == 0))
 			{
+				DDSNoteData* LRUNoteStruct = &NoteData[0];
+			
 				/* Find a free entry in the note table to use for the note being turned on */
 				for (uint8_t i = 0; i < MAX_SIMULTANEOUS_NOTES; i++)
 				{
+					/* Check if the note is unused */
 					if (!(NoteData[i].Pitch))
 					{
-						NoteData[i].Pitch          = ReceivedMIDIEvent.Data2;
-						NoteData[i].TableIncrement = (uint32_t)(BASE_INCREMENT * SCALE_FACTOR) +
-						                             ((uint32_t)(BASE_INCREMENT * NOTE_OCTIVE_RATIO * SCALE_FACTOR) *
-						                              (ReceivedMIDIEvent.Data2 - BASE_PITCH_INDEX));
-						NoteData[i].TablePosition  = 0;
+						/* If a note is unused, it's age is essentially infinite - always prefer unused not entries */
+						LRUNoteStruct = &NoteData[i];
 						break;
 					}
+					else if (NoteData[i].LRUAge > LRUNoteStruct->LRUAge)
+					{
+						/* If an older entry that the current entry has been found, prefer overwriting that one */						
+						LRUNoteStruct = &NoteData[i];
+					}
+					
+					NoteData[i].LRUAge++;
 				}
+				
+				/* Update the oldest note entry with the new note data and reset its age */
+				LRUNoteStruct->Pitch          = ReceivedMIDIEvent.Data2;
+				LRUNoteStruct->TableIncrement = (uint32_t)(BASE_INCREMENT * SCALE_FACTOR) +
+						                         ((uint32_t)(BASE_INCREMENT * NOTE_OCTIVE_RATIO * SCALE_FACTOR) *
+						                          (ReceivedMIDIEvent.Data2 - BASE_PITCH_INDEX));
+				LRUNoteStruct->TablePosition  = 0;
+				LRUNoteStruct->LRUAge         = 0;
 
 				/* Turn on indicator LED to indicate note generation activity */
 				LEDs_SetAllLEDs(LEDS_LED1);
@@ -129,11 +137,9 @@ int main(void)
 				for (uint8_t i = 0; i < MAX_SIMULTANEOUS_NOTES; i++)
 				{
 					if (NoteData[i].Pitch == ReceivedMIDIEvent.Data2)
-					{
-						NoteData[i].Pitch = 0;
-						FoundActiveNote      = true;
-						break;
-					}
+					  NoteData[i].Pitch = 0;
+					else if (NoteData[i].Pitch)
+					  FoundActiveNote   = true;
 				}
 				
 				/* If all notes off, turn off the indicator LED */
@@ -182,15 +188,15 @@ void SetupHardware(void)
 
 	/* Sample reload timer initialization */
 	TIMSK0  = (1 << OCIE0A);
-	OCR0A   = 255;
+	OCR0A   = (VIRTUAL_SAMPLE_TABLE_SIZE / 8);
 	TCCR0A  = (1 << WGM01);  // CTC mode
-	TCCR0B  = (1 << CS00);   // Fcpu speed
+	TCCR0B  = (1 << CS01);   // Fcpu/8 speed
 
 	/* Set speaker as output */
 	DDRC |= (1 << 6);
 
 	/* PWM speaker timer initialization */
-	TCCR3A  = ((1 << WGM30) | (1 << COM3A1) | (1 << COM3A0)); // Set on match, clear on TOP
+	TCCR3A  = ((1 << WGM31) | (1 << COM3A1) | (1 << COM3A0)); // Set on match, clear on TOP
 	TCCR3B  = ((1 << WGM32) | (1 << CS30));  // Fast 8-Bit PWM, Fcpu speed
 }
 
@@ -208,6 +214,10 @@ void EVENT_USB_Device_Disconnect(void)
 {
 	LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY);
 
+	/* Disable any notes currently being played */
+	for (uint8_t i = 0; i < MAX_SIMULTANEOUS_NOTES; i++)
+	  NoteData[i].Pitch = 0;
+
 	/* Set speaker as input to reduce current draw */
 	DDRC &= ~(1 << 6);
 }
diff --git a/Projects/Incomplete/MIDIToneGenerator/MIDIToneGenerator.h b/Projects/Incomplete/MIDIToneGenerator/MIDIToneGenerator.h
index c0644374a..2936ca2c7 100644
--- a/Projects/Incomplete/MIDIToneGenerator/MIDIToneGenerator.h
+++ b/Projects/Incomplete/MIDIToneGenerator/MIDIToneGenerator.h
@@ -65,14 +65,38 @@
 		/** LED mask for the library LED driver, to indicate that an error has occurred in the USB interface. */
 		#define LEDMASK_USB_ERROR        (LEDS_LED1 | LEDS_LED3)
 		
-		#define SCALE_FACTOR             65536
-		#define BASE_FREQUENCY           27.5
-		#define NOTE_OCTIVE_RATIO        1.05946
-		#define BASE_PITCH_INDEX         21
-		#define MAX_SIMULTANEOUS_NOTES   3
+		/** Scale factor used to convert the floating point frequencies and ratios into a fixed point number */
+		#define SCALE_FACTOR               65536
 		
-		#define BASE_INCREMENT           (((F_CPU / 255 / 2) / BASE_FREQUENCY))
+		/** Base (lowest) allowable MIDI note frequency */
+		#define BASE_FREQUENCY             27.5
 		
+		/** Ratio between each note in an octave */
+		#define NOTE_OCTIVE_RATIO          1.05946
+		
+		/** Lowest valid MIDI pitch index */
+		#define BASE_PITCH_INDEX           21
+		
+		/** Maximum number of MIDI notes that can be played simultaneously */
+		#define MAX_SIMULTANEOUS_NOTES     3
+		
+		/** Number of samples in the virtual sample table (can be expanded to lower maximum frequency, but allow for
+		 *  more simultaneous notes due to the reduced amount of processing time needed when the samples are spaced out)
+		 */
+		#define VIRTUAL_SAMPLE_TABLE_SIZE  512
+		
+		/** Sample table increments per period for the base MIDI note frequency */
+		#define BASE_INCREMENT             (((F_CPU / VIRTUAL_SAMPLE_TABLE_SIZE / 2) / BASE_FREQUENCY))
+
+	/* Type Defines: */
+		typedef struct
+		{
+			uint8_t  LRUAge;
+			uint8_t  Pitch;
+			uint32_t TableIncrement;
+			uint32_t TablePosition;
+		} DDSNoteData;		
+
 	/* Function Prototypes: */
 		void SetupHardware(void);
 		
-- 
GitLab