I Introduction

The MIDI Maestro is a MIDI sequence recognizing and comparison entertainment device that is to be used in unison with a MIDI keyboard. The MIDI Maestro has two modes of operation. The first mode is a basic sequence building mode which reads successively longer sequences of note data into a statically allocated circular buffer, compares previous sequence values with current sequence values, kicks the current player out of the game if their sequence doesn’t match, and moves on to the next player. The second mode of operation is advanced multi-player mode. Both players play phrases over MIDI backing rhythms that are stored in statically allocated arrays and try to reproduce both the notes and the timings that their opponent has played. The players’ input notes are recorded into a circular buffer and their timings are stored in a statically allocated array of booleans corresponding to time ticks (divisions of measures). MIDI output (backing rhythms) are polled against a timer while MIDI input is interrupt driven. RPG movement and pushbuttons are also interrupt driven in advanced multi-player mode and in the menu navigation system. The body of this report details these major concepts and provides an overview of the flow of embedded software in the MIDI Maestro.

II Software Design Considerations

Memory Sections/Mappings:

The memory on the Atmel ATMega8535 is divided up into three regions. These regions are the Flash Program Memory Space, the SRAM Data Memory Space, and the EEPROM Memory Space.

Flash Program Memory Space:

  • $000 - $FFF (4Kx16bits)
  • Boot Sector resides in upper portion of Flash Program Memory Space

SRAM Data Memory Space:

  • $0000 - $001F is the 32 entry registry file
  • $0020 - $005F are the I/O registers (addressed as $00 - $3F)
  • $0060 - $025F is the internal SRAM (512x8bits)

EEPROM Memory Space:

  • 512x8bits addressed through I/O registers
  • EEAR register 8..0 is the read/write address
  • EEDR register 7..0 is the data read/written

Memory Models (Data Structures):

The primary data structures that are used in the MIDI Maestro exist to store and/or analyze MIDI data based on the current mode of game play. Since both the microcontroller generated backing rhythm and the user provided MIDI input are strongly dependant on the same real-time constraints, the MIDI streams in both cases are stored in similar ways. The slight difference between the two is that the pre-programmed backing rhythm data is stored in statically allocated arrays and the real-time user input note data is stored in circular buffers (two). Both will use a statically allocated array of booleans with entries corresponding to divisions of a measure (i.e. 16 ticks per measure for 4/4 time or 12 ticks per measure for 3/3 time). In the case of the backing rhythm, if the boolean value is true for the current time tick (based on Timer 0), the next note in the statically allocated array will be played. An example of this storage scheme is shown in Figure 1.

Figure 1: Backing Rhythm Data Storage

In advanced multi-player mode, where key sequences are compared between two users, if user input is received, the corresponding boolean will be set in the time tick array (again based on Timer 0) and the note data will be added to the current player’s circular buffer. This user data can be analyzed after it is stored.

All of the pre-programmed MIDI data will be stored in Flash Program Memory along with the users’ boolean tick arrays. The circular buffers (two) to store user input note data will reside in SRAM. All of these data structures are global.

NOTE: In the sequence building mode, the boolean tick arrays will not be needed and will be unused.

Startup Code:

The startup code below initializes the correct input and output pins on ports A through D, enables the UART with an 8 bit buffered interrupt driven receive and a baud rate of 31250, enables the SPI as master with a clock of 2MHz, and sets up Timer 0 with an initial value of 0. The UART is used to send and receive MIDI data, the SPI is used to communicate with the ISD4003 Voice Record/Playback Chip, and Timer 0 is used to keep track of timing of MIDI data.

#include <mega8535.h>
#define RXB8 1
#define TXB8 0
#define UPE 2
#define OVR 3
#define FE 4
#define UDRE 5
#define RXC 7
#define FRAMING_ERROR (1<FE)
#define PARITY_ERROR (1<UPE)
#define DATA_OVERRUN (1<OVR)
#define DATA_REGISTER_EMPTY (1<UDRE)
#define RX_COMPLETE (1<RXC)
// USART Receiver buffer
#define RX_BUFFER_SIZE 8
char rx_buffer[RX_BUFFER_SIZE];
unsigned char rx_wr_index,rx_rd_index,rx_counter;
// This flag is set on USART Receiver buffer overflow
bit rx_buffer_overflow;
// USART Receiver interrupt service routine
#pragma savereg-
interrupt [USART_RXC] void uart_rx_isr(void)
{
char status,data;
#asm
push r26
push r27
push r30
push r31
in r26,sreg
push r26
#endasm
status=UCSRA;
data=UDR;
if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0)
{
rx_buffer[rx_wr_index]=data;
if (++rx_wr_index == RX_BUFFER_SIZE) rx_wr_index=0;
if (++rx_counter == RX_BUFFER_SIZE)
{
rx_counter=0;
rx_buffer_overflow=1;
};
};
#asm
pop r26
out sreg,r26
pop r31
pop r30
pop r27
pop r26
#endasm
}
#pragma savereg+
#ifndef _DEBUG_TERMINAL_IO_
// Get a character from the USART Receiver buffer
#define _ALTERNATE_GETCHAR_
#pragma used+
char getchar(void)
{
char data;
while (rx_counter==0);
data=rx_buffer[rx_rd_index];
if (++rx_rd_index == RX_BUFFER_SIZE) rx_rd_index=0;
#asm("cli")
--rx_counter;
#asm("sei")
return data;
}
#pragma used-
#endif
// Standard Input/Output functions
#include <stdio.h>
// Declare your global variables here
void main(void)
{
// Declare your local variables here
// Input/Output Ports initialization
// Port A initialization
// Func0=Out Func1=Out Func2=Out Func3=Out Func4=Out Func5=Out Func6=Out Func7=Out
// State0=0 State1=0 State2=0 State3=0 State4=0 State5=0 State6=0 State7=0
PORTA=0x00;
DDRA=0xFF;
// Port B initialization
// Func0=In Func1=In Func2=In Func3=Out Func4=Out Func5=Out Func6=In Func7=Out
// State0=T State1=T State2=T State3=0 State4=0 State5=0 State6=T State7=0
PORTB=0x00;
DDRB=0xB8;
// Port C initialization
// Func0=Out Func1=Out Func2=Out Func3=Out Func4=Out Func5=Out Func6=Out Func7=Out
// State0=0 State1=0 State2=0 State3=0 State4=0 State5=0 State6=0 State7=0
PORTC=0x00;
DDRC=0xFF;
// Port D initialization
// Func0=In Func1=Out Func2=In Func3=In Func4=In Func5=In Func6=In Func7=In
// State0=T State1=0 State2=T State3=T State4=T State5=T State6=T State7=T
PORTD=0x00;
DDRD=0x02;
// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: 7.813 kHz
TCCR0=0x05;
TCNT0=0x00;
// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: Timer 0 Stopped
// Mode: Normal top=FFh
// OC0 output: Disconnected
TCCR0=0x00;
TCNT0=0x00;
// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: Timer 1 Stopped
// Mode: Normal top=FFFFh
// 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;
// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: Timer 2 Stopped
// Mode: Normal top=FFh
// OC2 output: Disconnected
ASSR=0x00;
TCCR2=0x00;
TCNT2=0x00;
OCR2=0x00;
// External Interrupt(s) initialization
// INT0: Off
// INT1: Off
// INT2: Off
MCUCR=0x00;
MCUCSR=0x00;
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x00;
// USART initialization
// Communication Parameters: 8 Data, 1 Stop, No Parity
// USART Receiver: On
// USART Transmitter: On
// USART Mode: Asynchronous
// USART Baud rate: 31250
UCSRA=0x00;
UCSRB=0x98;
UCSRC=0x86;
UBRRH=0x00;
UBRRL=0x0F;
// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
// Analog Comparator Output: Off
ACSR=0x80;
SFIOR=0x00;
// SPI initialization
// SPI Type: Master
// SPI Clock Rate: 2000.000 kHz
// SPI Clock Phase: Cycle Half
// SPI Clock Polarity: Low
// SPI Data Order: LSB First
SPCR=0x70;
SPSR=0x00;
// Global enable interrupts
#asm("sei")

Organization of Embedded Code:

The MIDI Maestro’s run-time software is broken down into the following functional blocks:

  • MIDI Input
  • MIDI Output
  • MIDI Data Analysis
  • LCD Output
  • Analog Audio Message Initiation
  • Menu Navigation
  • Mid-Game Control

The MIDI Input Block is responsible for obtaining MIDI data and filtering unused input, the MIDI Output Block is responsible for sending the backing rhythm to the MIDI keyboard, the MIDI Data Analysis Block will either compare users’ real-time input streams or compare non-real-time sequences (dependanton game mode), the LCD Output Block is comprised of character and graphic generation routines, the Analog Audio Message Initiation Block is responsible for sending the correct audio clip request to the ISD4003 Voice Record/Playback Chip, the Menu Navigation Block is responsible for setting up initial game settings, and the Mid-Game Control Block handles user control requests during game play.

III Software Design Narrative

MIDI Input Block:

This is a polled interrupt generating block that reads MIDI data on the UART input of the microcontroller that is sent from the MIDI keyboard. If the input is invalid (i.e. pitchbend wheel data, MIDI control knob data), it is discarded. If the input is valid, the note data is placed in one of two circular note data buffers so that it can be analyzed at a later time. In multi-player advanced mode, each player will get a buffer and in basic sequence building mode, a current buffer and a past sequence buffer exist. In advanced multi-player mode, the tick corresponding to Timer 0’s value will be set in the tick array.

NOTE: Only MIDI start messages will be recorded. MIDI stop messages need not be recorded since simultaneously requiring users to press and depress keys at the same time is not the Midi Maestro’s intention.

MIDI Output Block:

This is a polled block that works closely with Timer 0 to generate the backing rhythm in multi-player advanced mode. If Timer 0’s value has not yet reached a value allowing the MIDI Output Block to move to the next tick in the tick array, nothing is done. If Timer 0 reached the value corresponding to the next tick, the MIDI Output Blockresets Timer 0 and moves to the next index in the tick array. If the corresponding value is false, nothing is done. If that value is true, the nextMIDI message is sent to the keyboard via the UART transmit port. This block then waits again until it can move to the next index in the tick array and repeats.

NOTE: Both MIDI start and MIDI stop messages are sent. Also, the backing rhythm data is sent on MIDI ports 2 through 16 with one instrument per channel. Data is not sent on port 1 (excluding during initializations) because this software assumes that the user’s MIDI keyboard is set with the ‘Remote Input’ option set to port 1. Any sent program change MIDI messages (instrument changes) on port 1 will then result in changing the user’s selected instrument, which is not the MIDI Maestro’s intent.

MIDI Data Analysis:

The MIDI Data Analysis Block assumes two roles based on the mode of game play. In advanced multi-player mode, after one player has provided a key sequence and the other player has attempted to duplicate it, the MIDI Data Analysis Block compares the two. It does this by stepping through both the tick arrays and the circular buffers to determine if the attempted sequence duplication is sufficient in both timing and note data. This block is slightly forgiving in its comparisons since requiring exact duplication would make game play impossible and boring. More leeway, however, is given to slight timing differences than to note differences. In basic sequence building mode, the data stored in the current buffer is compared with the data stored in the past buffer. If duplication is not exact, the sequence is considered invalid.

LCD Output:

This block is a collection of routines (character generation, graphic generation, and LCD control) that aid in displaying data on the LCD. The display can be easily turned on and off, sides of the LCD can be chosen (the LCD is divided into two halves vertically), the LCD can be cleared, and lines of characters and graphics can be generated. These routines send all required instructions to the LCD, assert all data/address clock signals, set all address/data lines, and most importantly, separate the programmer from the inner workings of the LCD.

Analog Audio Message Initiation:

When user’s need to be alerted of game messages via the internal MIDI Maestro speaker, the Analog Audio Message Initiation block provides a wrapper of control for the ISD4003 Voice Record/Playback Chip. This block places the ISD4003 in play mode by sending a single ‘powerup’ instruction via the microcontroller’s SPI port, sets the play address for the desired message (based on passed parameters), and initiates playback by sending the ‘play’ instruction.

Menu Navigation:

When the MIDI Maestro is powered on, a game setup screen is displayed. The Menu Navigation Block is responsible for displaying the menus, reading interrupt driven input from the RPG, moving cursors, debouncing button presses, and selecting menu items. When information is entered by the user, the Menu Navigation block stores game data such as number of players, game mode, game personality, number of challenges, and difficulty levels in global variables that can be used by the rest of the software. Copies of these values are stored in EEPROM so that they can be set as defaults upon the next powerup.

Mid-Game Control:

The Mid-Game Control Block is responsible for initiating game play, handling user requests during game play, invoking the Analog Audio Message Initiation Block, and ending game play. In order to initiate game play, the Mid-Game Control Block sets empty conditions on circular buffers (sets head and tail pointers accordingly), initializes boolean tick arrays to false, sets the current player, and resets Timer 0. During game play, the Mid-Game Control Block handles user control requests. These can be game exit requests, tempo change requests, or challenge requests (in advanced multi-player mode). If one user plays a bogus sequence, the other player can challenge. If the original user cannot repeat the sequence, the original user loses their turn. Each player only has a small number of challenge requests, so they must be used wisely. Game messages (played on the internal MIDI Maestro speaker) may also be initiated during game play. Finally, the Mid-Game Control Block is responsible for determining the end of game play, assigning a winner, and generating appropriate game messages. After game play is complete, this block relinquishes control to the Menu Navigation Block.

NOTE: While in basic sequence building mode, the Mid-Game Control Block retains the underlying flow of control. This is different in advanced multi-player mode where the underlying flow of control is relinquished to the MIDI Output Block.

IV Software Flowcharts

BasicSequenceBuildingMode (3 or more players):

Advanced Multi-Player Mode (Offense Player):

Advanced Multi-Player Mode (Interrupt Service Offense):

Advanced Multi-Player Mode (Defense Player):

Advanced Multi-Player Mode (Interrupt Service Defense):

Advanced Multi-Player Mode (End of Current Phrase Service):

NOTE: End can be considered a path back to Game Setup Screen

V List of References

Atmel ATmega8535 Datasheet

ISD4003 Series Datasheet

CodevisionAVR C Compiler

Startup Code

Version 1.23.8d Standard