package gsingh.learnkirtan.keys; import static gsingh.learnkirtan.keys.LabelManager.EMPTY_KEY_SPAN_TAG; import static gsingh.learnkirtan.keys.LabelManager.EMPTY_SARGAM_SPAN_TAG; import gsingh.learnkirtan.Constants; import gsingh.learnkirtan.player.MidiPlayer; import gsingh.learnkirtan.utility.Utility; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.JButton; @SuppressWarnings("serial") public class Key extends JButton implements MouseListener { /** The default label for this key */ private static final String DEFAULT_TEXT = String.format( "<html><div style='text-align:center'>%s<br>%s</div></html>", EMPTY_SARGAM_SPAN_TAG, EMPTY_KEY_SPAN_TAG); /** A counter used to assign MIDI note numbers to the keys */ private static int noteCount = Constants.STARTING_MIDI_NOTE_ID; /** The MIDI note ID of the key */ private final int midiNoteId; /** The pressed state of this key */ private boolean pressed; /** The clicked state of this key */ private boolean clicked; public Key(LabelManager labelManager) { this.midiNoteId = noteCount++; setText(DEFAULT_TEXT); addKeyListener(new KeyboardListener(labelManager)); addMouseListener(this); } /** * Checks if the key label contains the provided text * * @param text * the text to search for * @return true if the text is found, false otherwise */ public boolean contains(String text) { return getText().contains(text); } /** * Replaces a literal string in the label * * @param target * the text to be replaced * @param replacement * the text to replace with */ public void replaceText(String target, String replacement) { setText(getText().replace(target, replacement)); } /** * Replaces all text that matches a regex expression * * @param regex * the regex expression to replace * @param replacement * the text to replace with */ public void replaceAll(String regex, String replacement) { setText(getText().replaceAll(regex, replacement)); } /** @return the midiNoteId of this key */ public int getMidiNoteId() { return midiNoteId; } /** @return true if this key is pressed, false otherwise */ public boolean isPressed() { return pressed; } /** * A wrapper for the {@code doClick} method, so that it is executed * continuously if the key is pressed or clicked until it is released */ public void startDoClick() { new Thread(new Runnable() { @Override public void run() { while (pressed || clicked) { doClick(200); } } }).start(); } // TODO: Should this be moved into another class? @Override public void mouseClicked(MouseEvent e) { System.out.println("Key clicked: " + midiNoteId); } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { // e will be null if called this function ourselves when a key was // pressed if (e == null) { pressed = true; } else { clicked = true; } MidiPlayer.play(midiNoteId); } @Override public void mouseReleased(MouseEvent e) { // e will be null if called this function ourselves when a key was // released if (e == null) { pressed = false; } else { clicked = false; } // If the note is neither pressed or clicked, stop playing it // If one of the two is true, then stop playing it and restart, // so that only one note is played at a time if (!pressed && !clicked) MidiPlayer.stop(midiNoteId); else { // TODO: Should MidiPlayer handle if the note is already being // played? MidiPlayer.stop(midiNoteId); MidiPlayer.play(midiNoteId); } } private class KeyboardListener extends KeyAdapter { private LabelManager labelManager; public KeyboardListener(LabelManager labelManager) { this.labelManager = labelManager; } // TODO: Look into an alternative for each key being responsible for // every other key. If a fix is not possible, don't worry about it. /** * Play the key that was pressed if it is playable. Note that this does * not have to be this key; it can be any playable key. */ @Override public void keyPressed(KeyEvent e) { // Get the keyId of the key that was pressed final int keyId = Utility.letterToKeyId( String.valueOf(e.getKeyChar()), labelManager.getOctave()); // If that key is valid and not already pressed, play it final Key key = KeyMapper.getInstance().getKey(keyId); if (key != null && !key.isPressed()) { key.mousePressed(null); key.startDoClick(); } } /** * Unpress the key or shift the octave depending on which key was * released */ @Override public void keyReleased(KeyEvent e) { String letter = String.valueOf(e.getKeyChar()).toUpperCase(); if (letter.equals("Z")) { labelManager.shiftOctaveDown(); labelManager.labelKeyboardNotes(); } else if (letter.equals("X")) { labelManager.shiftOctaveUp(); labelManager.labelKeyboardNotes(); } else { int keyId = Utility.letterToKeyId(letter, labelManager.getOctave()); Key key = KeyMapper.getInstance().getKey(keyId); if (key != null) key.mouseReleased(null); } } } }