Synthesia Clone "Piano Hero": Creating the Roll

Summer Project Numero Uno: Synthesia Clone for Android
In the last post, I explained very badly how to convert MidiEvents in Java to timed data which you can use to create notes. Now I am going to show you some code on how to get a roll using the times in your notes. The basic idea is to have a thread that continuously redraws while keeping track of how much time has passed.

public void drawRoll(Graphics2D g, ArrayList notes) {
A: int key_width = 16;
int key_height = 100;

// Scale falling notes so that a note of length (show_duration)
// will stretch to fit the entire space available
B: double scale_factor = (double)window_height / show_duration;
boolean drawing_black_keys = false;

for(int i = 0; i < 2; i++) {
for(int j = 0; j < notes.size(); j++) {
Note note = notes.get(j);
C: if ( note.end_time < current_time ) { notes.remove(i); continue;}
D: if ( note.start_time > current_time + show_duration) break;

E: if(notes.is_black_key != drawing_black_keys) continue;
F: int x = NoteOffset[note.note_number] * key_width;
G: int y_start = roll_height - (int)((note.start_time - current_time) * scale_factor);
int y_end = roll_height - (int)((note.end_time - current_time) * scale_factor);
int height = y_start - y_end;
if(height == 0) height = 5;
// Remember that positive and negative y is backwards
H: if(note.is_black_key) drawBlackNote(g, x, y_end, key_width, height);
else {drawWhiteNote(g, x, y_end, key_width, height); }
}
drawing_black_keys = true;
}
}
(A) - You should make other methods to draw black keys and white keys and then change the appearance of the keys inside of those.
(B) - window_height is the size of my jframe, change as desired
(C) - Here, we remove notes that we have already passed
(D) - If we have found a note that isn't in view yet, we are done with this loop
(E) - Here, we skip the black keys on the first loop so that they are drawn on top of the white keys
(F) - NoteOffset is actually an Double[], so you should cast the whole line to an int, NoteOffset contains values for each key which contains the number or white keys plus one half times the number of black keys
(G) - role_height is window_height - keyboard_height
(H) - Again, you should make your own methods for drawing keys however you like

I have found it better to draw the static images before hand by creating a BufferedImage and then drawing your notes to the BufferedImage. This way, you don't need to redraw everything in real time and can simply blit pixels to the screen to scroll. To do this:
BufferedImage r = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g1 = r.createGraphics();
drawBufferedImages(g1)

Where drawBufferedImages() draws my keyboard, the background, and the guides. I tried drawing everything before hand to one big BufferedImage only to have Java run out of memory for any song that was longer than one and a half minutes. So I don't recommend doing that.


Then to draw everything, overload paint:
public void paint(Graphics g) {
g1.drawImage( background, 0, 0, background_width, background_height, 0, 0,
background_width, background_height, null);
drawNotePass(g1, active_list);
g1.drawImage( keyboard, 0, background_height, keyboard_width, background_height
+ keyboard_height, 0, 0, keyboard_width, keyboard_height, null);
}
where background and keyboard are my BufferedImage's that have been drawn earlier.

For getting good looking notes and other stuff, use gradients. To make a simple horizontal gradient for a vertical note, set x1 so the left most x-coordinate and x2 the right most x-coordinate. (I recommend not choosing two colors that are extremely different :P )
Color one = new Color(  54, 161, 201); // Random color :D
Color two = new Color( 143, 91, 56 ); // Random color :D
GradientPaint fill = new GradientPaint( x1, 0, one , x2, 0, two );
g.setPaint( fill ); // g is Graphics2D
References: Graphics, BufferedImage, GradientPaint

No comments:

Post a Comment