Software Design

 

MIDI Processing

 

MIDI Signal Reception

In the MIDI input mode, the UART (Universal Asynchronous Receiver Transmitter) receives data from the music instrument one byte at a time. To calculate the UBRR (UART Baud Rate Register) value needed for correct UART functionality we used the following equation:

 

UBRR = = = 31

 

where the MIDI Baud rate is 31.25KHz and the equation corresponds to asynchronous normal mode of communication.

 

We poll the UART once per television frame, or 60 times per second, by calling get_the_command( ) which checks the new data received flag held by the seventh bit of the UART Control Status Register A (UCSRA) register. If new data has been received, we obtain this new data by reading the contents of the UART Data Register (UDR).

 

MIDI Protocol Basics

There are three types of bytes:

Status

Note on: 0x9y, where y indicates midi channel

Note

Reference Note: Middle C = 0x60

Velocity

How fast a note is played.

 

Playing a note generates 6 bytes of information:

Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6

0x90 0xYY 0xWW 0x90 0xYY 0x00

The first 3 bytes indicate that note 0xYY started playing at velocity 0xWW. The last 3 bytes indicate that note 0xYY stopped playing. For the synthesizer we used, a NoteOff message consisted of a NoteOn status byte followed by a velocity byte with a value of 0. Note that some synthesizers send the NoteOff message using a separate status byte (0x8y) with an arbitrary velocity value.

 

MIDI Protocol Implementation

To interpret the MIDI protocol in the context of sheet music, we must be able to determine what note is playing and for how long. Once we obtain this information from the MIDI signal, we are able to draw the correct note on the TV.

To determine the note playing, we cycle through the data received by the UART six bytes at a time and extract the second and fourth bytes which hold the note information using the following flow of code.

 

 

Figure 4: MIDI Flow Diagram. Determine the note and note duration. This excerpt is from the infinite while loop in main ( ) and starts running when the line count equals 231.

 

In the flow diagram, current_note_number holds the note that is currently playing. Function draw_and_store_note( ) stores the current note and updates the screen array whose contents get painted to the screen at a rate of 60Hz. The current_state is updated at a rate of a little less 1/t or sixty times per second. To keep track of the note duration we use variable note_counter which gets incremented between the time the note starts playing and the time it stops playing in increments of 1/60 sec. Since we only care about when a note stops playing, we only process the NoteOff velocity byte.

 

Once the note number and note duration are extracted they are processed to obtain the note number and note type. To obtain the note number we can mod the note by 12 because there are only 12 different notes. The following table shows notes with their note numbers.

 

Note Number

Note

0

C

1

C#

2

D

3

D#

4

E

5

F

6

F#

7

G

8

G#

9

A

10

A#

11

B

 

We obtain the note type by comparing note_counter to duration bounds enumerated to correspond to the note types. We enumerated bounds for five types of notes: whole, half, quarter, eighths, and sixteenths. We created the bounds based on 2 beats per second timing which corresponds to 2 quarter notes per second and a bound of [22, 45] sixtieths of a second for the quarter note. The bounds for the remaining four notes can be calculated from this bound to obtain the bounds seen below.

 

 

Figure 6: Note duration bounds corresponding to the five different note types

 

 

Drawing Routines

The TV screen is modeled as a 100x16 byte array we called screen, which corresponds to a y coordinate range of [0, 99] and a x coordinate range of [0,127]. We can access this array either directly or by using functions video_pt and video_line. Function video_pt takes parameters for the x coordinate and y coordinate and whether to draw, erase or invert the intensity level (i.e. make the point blink). Function video_line takes a second pair of coordinates in addition to the parameters of video_pt. The TV screen is drawn by sending the contents of the screen array to the TV using assembly code for speed efficiency.

In order to draw sheet music, we must load the screen array with the raster array corresponding to the current sheet music. When using video_pt or video_line we must calculate the x and y coordinates needed. We organized the sheet music into the following drawing sections.

 

Draw the staff and the G-clef

The staff consists of 5 lines which we spaced at BTW_LINES = 5 along the y axis of the screen array. The G-clef starts at CLEFF_X_START = 7 along the x axis of the screen array. We draw three staffs and a G-clef on each staff at initialization time using a function draw_staff( ) and draw_clef( ) respectively. When auto scrolling or resetting the sheet music we redraw the staffs and clefs by direct memory copy of the screen values corresponding to a staff and a clef from one part of the screen to another part of the screen.

 

Draw the note

We implemented a function called draw_note( ) to draw the notes. The function takes in the note number, note type, and whether a note is to be drawn or erased. The note number is used to assign the correct starting y coordinate of the note to a variable, starting_y, using a switch statement. The note type is used to determine how the note will look. Different parts of the note are drawn based on the note type as shown in Figure 7. We split the drawing into parts and size-inefficiently repeated them because we dont want to worry about the ending x and y coordinates which would change based on the note drawn. Thus our note drawing routine is general in that it can easily draw the notes and update the x and y coordinates consistently as the three staffs fill up. Also, it is more time-efficient to have separate branches that draw all of each notes pieces, rather than have several if statements that must be evaluated for every note, regardless of whether necessary.

There are two special categories of note numbers that require special drawing routines. The first category consists of sharp notes which require a pound sign appended to the front of the note. The second category consists of notes C and C sharp which require a middle C line. To deal with these special cases we used two flags sharp and draw_c_line which when set enable the drawing routine to add the required images.

 

 

 

 

Draw stem

 

 

 

Draw stem; fill note in

 

 

 

Draw stem; fill note in; draw first flag

 

 

 

Draw stem; fill note in; draw first flag; draw second flag

 

Figure 7: Note Drawing based on note types

 

Auto scrolling

Since only three staffs, amounting to only 18 notes, fit on the TV screen, we have implemented automatic scrolling to allow the user to easily create nine staffs worth of music. The second staff is first copied onto the first staff and the third staff is then copied onto the second staff by copying from and to the proper portions of the screen array. The third staff is then erased (or cleared) by setting that portion of the screen array to 0. The third staff is then reset by copying the staff lines and G-clef from the second staff portion of the screen array. The user can continue playing the instrument until the ninth staff is filled at which point an OUT OF SPACE D FOR PRInT message pops up. We chose to do all of the scrolling with direct memory copies because it turned out to be a much faster implementation than redrawing notes. This is because function calls to draw_note() and array accesses for stored notes can be quite expensive.

Reset

The user may be unsatisfied with the sheet music just created and wish to delete it. We have implemented such functionality to satisfy this demanding user. He or she only needs to press # on the keypad, and the sheet music will be magically reset. Once the # key is pressed, the UART is enabled to receive MIDI input, and an empty sheet music is redrawn by first redrawing the third staff the same way done as when auto scrolling, and the first and second staffs are then copied from the third staff. Again, this clearing method is much faster than would be to clear the screen and redraw staff lines. The necessary variables are reinitialized as done in the initialization( ) function to ensure proper system functionality.

 

Transmission to the TV

 

Transmission to the TV is very time sensitive. That is, the screen line lengths must be the same in order not to cause jitter. We used a line length of:

The NTSC standard uses horizontal syncs to indicate the end of a line and vertical syncs to indicate the end of a frame, see Figure 8.

 

 

Figure 8: Horizontal and Vertical sync pulses

The horizontal sync pulse is approximately 5.4s. The duration of the vertical sync pulse is three times a scan line or:

The timing is scheduled using the timer1 compare on match Interrupt Service Routine (ISR). Every 1018 ticks, the timer1 compare match generates an interrupt and the ISR is entered. Here, the appropriate sync pulse is generated and a line count is maintained. The interruption rate of the ISR creates a time base of 63.625 s and a frame rate of approximately 60 Hz:

 

The actual NTSC standards for the horizontal and vertical syncs are 5s and 63.55 s respectively. The differences between our values and the NTSC values are very small. In addition, while one must be extremely careful in time-scheduling for television output signals, it turns out that as long as the signal is within a certain range of the expected values, if the signal is consistent, the output signal will function as expected. Thus, a major concern is not only that the refresh rate used (63.625 us) falls close to the NTSC value (63.55 us), but that the refresh rate is always the same.

 

The output to the TV is generated as follows. In the endless while loop of main( ), the index for the next line to print is computed and then the processor sleeps while awaiting an interrupt from timer1. Once timer1 has generated an interrupt, the processor awakens and, if printing is between lines 31 and 230 of the frame, it outputs the contents of the screen array to the screen by first loading 8 registers and quickly dumping them to Pin 6 of PORTD for each of these lines. If printing has reached the last 32 lines of the frame, the processor computes the screen array contents for the next frame.

 

Graphical User Interface

We wrote the application for PC interaction with our system using Microsoft Visual Basic. We chose this language because of the ease of creating simple GUI forms with VB. We used the MS Comm Control to communicate with the microprocessor. We then wrote code to access a binary file, write bitmap header hex values, and write the screen raster sent by the mcu to the file. Once the bitmap is created, the application allows the user to display and print the object with VBs Printer.paintpicture function.