Skip to content

Metronome – Java Source Code

Download images required for the metronome here.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Properties;

import javax.imageio.ImageIO;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Synthesizer;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;


public class Metronome extends Thread{
   
    public static int defaultMsPbpm = 5000;
    // by testing
    /* (emperical)
     * 5000ms = 1bpm
     * Therefore,
     * timerDelay(ms) = 5000/bpm
     */
    public int wy = 342; // initial position of weight
    public static int bpmInt;
    public static int msPbpm = defaultMsPbpm; // number ms at 1bpm
    public int timerDelay;
   
    public static int sliderKnob_width = 42;
    public static int sliderKnob_height = 19;
    public static int weight_width = 84;
    public static int weight_height = 68;
    final static int MAX_WY = 550;
   
    //public Point cursorPressedPosition;
    public int X = 0;
    public int Y = 0;
    boolean volUprWasHit = false;
    boolean volLwrWasHit = false;
    boolean subBeatWasHit = false;
    public int deltaX = 0;
    boolean weightWasHit;
    public int deltaY = 0;
   
    public Timer timer;
    public boolean hasStarted = false;
    public boolean stopAnimation = false;
   
    // Stuff for Midi
    public int volUprX = (int)(110 +80*(140/127));
    public int volLwrX = (int)(110 +80*(140/127));
    public int subBeatX = 395;
    public int numSubBeats;
    private MidiChannel channel = null;
    private static int majorVelocity = 80; // initial volume
    private static int minorVelocity = 80; // initial volume
   
   
    final static int HAND_CLAP = 39;
    final static int RIM_SHOT = 37;
    final static int COWBELL = 56;
    final static int HIGH_BONGO = 60;
    final static int LOW_BONGO = 61;
    final static int HIGH_TIMBALE = 65;
    final static int LOW_TIMBALE = 66;
    final static int HIGH_AGOGO = 67;
    final static int LOW_AGOGO = 68;
    final static int CLAVES = 75;
    final static int HIGH_WOOD_BLOCK = 76;
    final static int LOW_WOOD_BLOCK = 77;
    final static int CUICA = 78;
    final static int MUTE_TRIANGLE = 81;
   
    static int noteForMainBeat = CLAVES;
    static int noteForSubBeat = HIGH_WOOD_BLOCK;
    static int NUM_SUB_BEAT = 1;
   
    private JFrame frame;
    private JMenuBar menuBar;
    private JMenuItem settingsMenuItem;
    private JLabel soundLabel1;
    private JLabel soundLabel2;
    private JLabel timerDelayLabel = new JLabel();
    private JMenuItem helpMenuItem;
   
    public static void main(String[] args) {
        (new Metronome()).start();
    }

    public Metronome() {
        /**
         * Get properties that were save when the app was last closed.
        **/
        Properties prop = new Properties();
        InputStream input = null;

        try {
            input = new FileInputStream(“metronome.properties”);

            // load a properties file
            prop.load(input);

            // get the property values that were save when application was closed
            noteForMainBeat = Integer.parseInt(prop.getProperty(“noteForMainBeat”));
            noteForSubBeat = Integer.parseInt(prop.getProperty(“noteForSubBeat”));
            majorVelocity = Integer.parseInt(prop.getProperty(“majorVelocity”));
            //System.out.println(“majorVelocity: “+majorVelocity);
            volUprX = (int)(124 +majorVelocity*(140/127));
            minorVelocity = Integer.parseInt(prop.getProperty(“minorVelocity”));
            //System.out.println(“majorVelocity: “+majorVelocity);
            volLwrX = (int)(124 +minorVelocity*(140/127));
            subBeatX = Integer.parseInt(prop.getProperty(“subBeatX”));
            numSubBeats = Integer.parseInt(prop.getProperty(“numSubBeats”));
            msPbpm = Integer.parseInt(prop.getProperty(“msPerBPM”));
           
            bpmInt = Integer.parseInt(prop.getProperty(“tempo”));
            wy = (int)(bpmInt -24)*2 + 150;
           

        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
       
        /**
         *  Get Synthesizer
        **/
        try {
            final Synthesizer synthesizer = MidiSystem.getSynthesizer();
            synthesizer.open();
            channel = synthesizer.getChannels()[9];
           
        } catch (MidiUnavailableException ex) {
            //log.error(ex);
        }
       
        /**
         * Java Frame set-up
         */
        EventQueue.invokeLater(new Runnable() {
            //@Override
            public void run() {
               
                JFrame frame = new JFrame(“DaveS Metronome”);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
               
                // Add icon for the frame
                URL iconURL = getClass().getResource(“images/DavesMetronome.png”);
                // iconURL is null when not found
                ImageIcon icon = new ImageIcon(iconURL);
                frame.setIconImage(icon.getImage());
               
                // Add the menu
                settingsMenuItem = new JMenuItem(“Settings”);
                settingsMenuItem.addActionListener(new ActionListener() {
                    
                    public void actionPerformed(ActionEvent e) {
                        //Execute when UP button is pressed
                        SettingsDialog dialog = new SettingsDialog();
                        dialog.setModal(true);
                        dialog.setVisible(true);
                    }
                });
                helpMenuItem = new JMenuItem(“Help”);
                helpMenuItem.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
                helpMenuItem.addActionListener(new ActionListener() {
                    
                    public void actionPerformed(ActionEvent e) {
                        //Execute when UP button is pressed
                        HelpDialog dialog = new HelpDialog();
                        dialog.setModal(true);
                        dialog.setVisible(true);
                    }
                });
               
                // define the KeyListener
                JTextField timerTrigger = new JTextField();
                timerTrigger.addKeyListener(new MyKeyListener());
                frame.add(timerTrigger);
               
                // add graphics pane
                frame.add(new AnimationPane());
               
                menuBar = new JMenuBar();
               
                // add menus to menubar
                menuBar.add(settingsMenuItem);
                menuBar.add(helpMenuItem);
            
                // put the menubar on the frame
                frame.setJMenuBar(menuBar);
               
                frame.addWindowListener(new WindowAdapter() {
                    public void windowClosing(WindowEvent ev) {
                        // Save variables to File
                        Properties prop = new Properties();
                        OutputStream output = null;

                        try {

                            output = new FileOutputStream(“metronome.properties”);

                            // set the properties value
                            prop.setProperty(“noteForMainBeat”, Integer.toString(noteForMainBeat));
                            prop.setProperty(“noteForSubBeat”, Integer.toString(noteForSubBeat));
                            prop.setProperty(“majorVelocity”, Integer.toString(majorVelocity));
                            prop.setProperty(“minorVelocity”, Integer.toString(minorVelocity));
                            prop.setProperty(“tempo”, Integer.toString(bpmInt));
                            prop.setProperty(“subBeatX”, Integer.toString(subBeatX));
                            prop.setProperty(“numSubBeats”, Integer.toString(numSubBeats));
                            prop.setProperty(“msPerBPM”, Integer.toString(msPbpm));

                            // save properties to project root folder
                            prop.store(output, null);

                        } catch (IOException io) {
                            io.printStackTrace();
                        } finally {
                            if (output != null) {
                                try {
                                    output.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }

                        }
                    }
                });
                frame.setMinimumSize(new Dimension(600, 880));
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
   
    /**
     *  Help menu item was clicked
     */
    public void actionPerformed(ActionEvent ev)
    {
      HelpDialog dialog = new HelpDialog();
      dialog.setModal(true);
      dialog.setVisible(true);
    }
   

    /**
     * Java JPanel set-up
     */
    public class AnimationPane extends JPanel  {
       
        private static final long serialVersionUID = 8113062312141634760L;
        // Load Basic images
        BufferedImage baseBack = loadImage(“base-back”);
        BufferedImage baseLower = loadImage(“base-lower”);
        BufferedImage baseUpper = loadImage(“base-upper”);
        BufferedImage wand = loadImage(“wand”);
        // Load Button images
        ImageIcon upButtonIcon = createImageIcon(“images/up.gif”);
        ImageIcon downButtonIcon = createImageIcon(“images/down.gif”);
        protected  JButton up = new JButton();
        protected  JButton down = new JButton();
        // Load Slider images
        BufferedImage volumePlate = loadImage(“volumePlate”);
        BufferedImage volumeKnobUpper = loadImage(“volumeKnobUpper”);
        BufferedImage volumeKnobLower = loadImage(“volumeKnobLower”);
        // Sub-beat switch
        BufferedImage subBeatPlate = loadImage(“subBeatPlate”);
        BufferedImage subBeatKnob = loadImage(“subBeatKnob”);
       
        // Here we go
        private BufferedImage weight = loadImage(“weight”);
        private int xPos = 258;
        private int direction = 1;
       
        int angle = 360;
        int MAX_ANGLE = 378;
        int MIN_ANGLE = 342;
        int angleIncrement = 3;
       
       
        // Here is the ActionListener and Animation definition
        public AnimationPane() {
           
            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = getActionMap();
           
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), “pressed.up”);
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), “pressed.down”);

            am.put(“pressed.up”, new MoveAction(“up”));
            am.put(“pressed.down”, new MoveAction(“down”));
           
            timer = new Timer(timerDelay, new ActionListener() {
               
                public void actionPerformed(ActionEvent e) {

                    // Animation
                    switch (numSubBeats){
                        case 0:
                            angle = angle + angleIncrement*direction;
                            if (angle >= MAX_ANGLE){
                                direction = -1;
                               
                                // Midi
                                if (angle == 378){
                                    channel.noteOn(noteForMainBeat, majorVelocity);
                                }
                               
                            }
                            else if (angle <= MIN_ANGLE){
                                direction = 1;
                               
                                if (angle == 342){
                                    channel.noteOn(noteForMainBeat, majorVelocity);
                                }
                            }
                       
                            repaint();
                            break;
                        case 1:
                            angle = angle + angleIncrement*direction;
                            if (angle >= MAX_ANGLE){
                                direction = -1;
                               
                                // Midi
                                if (angle == 378){
                                    channel.noteOn(noteForMainBeat, majorVelocity);
                                }
                               
                            }
                            else if (angle <= MIN_ANGLE){
                                direction = 1;
                               
                                if (angle == 342){
                                    channel.noteOn(noteForMainBeat, majorVelocity);
                                }
                            }
                            else if (angle == 360){
                                channel.noteOn(noteForSubBeat, minorVelocity);
                            }
                       
                            repaint();
                            break;
                        case 2:
                            angle = angle + angleIncrement*direction;
                            if (angle >= MAX_ANGLE){
                                direction = -1;
                               
                                // Midi
                                if (angle == 378){
                                    channel.noteOn(noteForMainBeat, majorVelocity);
                                }
                               
                            }
                            else if (angle <= MIN_ANGLE){
                                direction = 1;
                               
                                if (angle == 342){
                                    channel.noteOn(noteForMainBeat, majorVelocity);
                                }
                            }
                            else if (angle == 354){
                                channel.noteOn(noteForSubBeat, minorVelocity);
                            }
                            else if (angle == 366){
                                channel.noteOn(noteForSubBeat, minorVelocity);
                            }
                       
                            repaint();
                            break;
                    }
                }

            });
            timer.setRepeats(true);
            timer.setCoalesce(true);
           
            // add buttons to fine tune tempo
            up = new JButton(null, upButtonIcon);
            up.setMnemonic(KeyEvent.VK_UP);
            up.setPreferredSize(new Dimension(20, 26));
           
            down = new JButton(null, downButtonIcon);
            down.setMnemonic(KeyEvent.VK_DOWN);
            down.setPreferredSize(new Dimension(20, 26));
           
            up.addActionListener(new ActionListener() {
                 
                public void actionPerformed(ActionEvent e) {
                    //Execute when UP button is pressed
                    if (!hasStarted){
                        bpmInt = bpmInt + 1;
                        if (bpmInt > 224){bpmInt = 224;};
                        wy = (int)(bpmInt -24)*2 + 150;
                        timerDelay = msPbpm/bpmInt;
                        down.requestFocus(false);
                        repaint();
                    }
                }
            });
           
            down.addActionListener(new ActionListener() {
                 
                public void actionPerformed(ActionEvent e) {
                    //Execute when DOWN button is pressed
                    if (!hasStarted){
                        bpmInt = bpmInt – 1;
                        if (bpmInt < 24){bpmInt = 24;};
                        wy = (int)(bpmInt -24)*2 + 150;
                        timerDelay = msPbpm/bpmInt;
                        repaint();
                    }
                }
            });
        }

        // Graphics definition
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
           
            addMouseMotionListener(new ChangeTempo());
            addMouseListener (new GetCurPos());
           
            Graphics2D g2d = (Graphics2D) g;

            AffineTransform original = g2d.getTransform(); // save for original animation state
       
            // Static images
            g2d.drawImage(baseBack, 0, 0, null);
            g2d.drawImage(baseUpper, 0, 0, null);
           
            // The BPM box
            int thickness = 4;
            Color myColor = Color.decode(“#905838”); //(“#C1E0FF”);
            g2d.setColor(myColor);
            g2d.fill3DRect(270, 70, 60, 26, false); //(X, Y, length, width)
            for (int i = 1; i <= thickness; i++)
              g2d.draw3DRect(270 – i, 70 – i, 60 + 2 * i – 1, 26 + 2 * i – 1, true);
            //  Display BPM
            Font courierBold10 = new Font(“Droid”, Font.BOLD, 20);
            Color textColor = Color.decode(“#ECEFFF”);//(“#62F35B”);
            g2d.setColor(textColor);
            g2d.setFont(courierBold10);
            String textBpm = Integer.toString((wy-150)/2 + 24);
            g2d.drawString(textBpm, 280, 90);
           
            if (!hasStarted) {
                g2d.setTransform(original);
                g2d.drawImage(wand, 290, 116, this);
                g2d.drawImage(weight, xPos, wy, this);
                if (stopAnimation){  // spacebar was pressed to stop the animation
                    timer.stop();
                }
            }
            else {

                // Animated images
                g2d.rotate(Math.toRadians(Math.abs(angle)), 298, 700); // 298, 700 is X, Y coordinate for center of rotation
                g2d.drawImage(wand, 290, 116, null);
                g2d.drawImage(weight, xPos, wy, this);
            }
            // Now draw the base with volume slider etc…
            g2d.setTransform(original);
            g2d.drawImage(baseLower, 0, 0, null);
            g2d.drawImage(volumePlate, 100,670, null); // was 200
            g2d.drawImage(volumeKnobUpper, volUprX,680, null);
            g2d.drawImage(volumeKnobLower, volLwrX,702, null);
            g2d.drawImage(subBeatPlate, 360, 685, null);
            g2d.drawImage(subBeatKnob, subBeatX, 700, null);
           
            // Add the buttons
            up.setForeground(Color.decode(“#643D27”));
            up.setBackground(Color.decode(“#CD7D50”));
            up.setLocation(246, 70);
            add(up);
           
            down.setForeground(Color.decode(“#643D27”));
            down.setBackground(Color.decode(“#CD7D50”));
            down.setLocation(334, 70);
            add(down);
        }

     
        public class GetCurPos extends MouseAdapter {
            public void mousePressed(MouseEvent e){
                X = e.getX();
                Y = e.getY();
               
                // What object was hit?
                Rectangle weightRect = new Rectangle(290, wy, weight_width, weight_height);
                Rectangle volUprRect = new Rectangle(volUprX, 680, sliderKnob_width, sliderKnob_height);
                Rectangle volLwrRect = new Rectangle(volLwrX, 702, sliderKnob_width, sliderKnob_height);
                Rectangle subBeatRect = new Rectangle(subBeatX, 700, sliderKnob_width, sliderKnob_height);
               
                // Weight
                if (weightRect.contains(X,Y)) {
                    weightWasHit = true;
                    deltaY = Y – wy;
                }
                // Main beat volume
                else if (volUprRect.contains(X,Y)){
                    volUprWasHit = true;
                    deltaX = X – volUprX;
                }
                // Sub-Beat Volume
                else if (volLwrRect.contains(X,Y)) {
                    volLwrWasHit = true;
                    deltaX = X – volLwrX;
                }
                // Sub-Beat switch
                else if (subBeatRect.contains(X,Y)) {
                    subBeatWasHit = true;
                    deltaX = X – subBeatX;
                }
            }
           
            public void mouseReleased(MouseEvent e){
                volUprWasHit = false;
                volLwrWasHit = false;
                subBeatWasHit = false;
                weightWasHit = false;
            }
        }

       
        public class ChangeTempo extends MouseAdapter {
           
            public void mouseDragged(MouseEvent e) {
                if (weightWasHit){
                    if (!hasStarted){
                        //if (e.getY() < 570) {
                          wy = e.getY() – deltaY;
                          if (wy > MAX_WY){
                              wy = MAX_WY;
                          }
                          if (wy < 150){
                              wy = 150;
                          }
                         
                          bpmInt = (int)((wy-150)/2 + 24);
                          timerDelay = msPbpm/bpmInt;
                          repaint();
                    }
                }
                else if (volUprWasHit){
                        volUprX = e.getX()-deltaX;
                          if (volUprX > 250){
                              volUprX = 250;
                          }
                          if (volUprX <= 110){
                              volUprX = 110;
                          }
                         
                          majorVelocity = (int)((volUprX-110)*127/140);
                            repaint();
                    //}
                }
                else if (volLwrWasHit){
                    volLwrX = e.getX()-deltaX;
                    if (volLwrX > 250){
                          volLwrX = 250;
                    }
                    if (volLwrX <= 110){
                        volLwrX = 110;
                    }
                    minorVelocity = (int)((volLwrX-110)*127/140);
                    repaint();
                }
                else if (subBeatWasHit){
                    subBeatX = e.getX();
                    if (subBeatX <420){
                        subBeatX = 390;
                        numSubBeats = 0;
                    }
                    // number of sub-beats = 1
                    if (subBeatX >420 && subBeatX<445){
                        subBeatX = 412;
                    numSubBeats = 1;
                    }
                    // number of sub-beats = 2
                    if (subBeatX > 445){
                          subBeatX = 432;
                          numSubBeats = 2;
                    }
                    repaint();
                }
               
            }
               
          }
   
        public class MoveAction extends AbstractAction {

            private static final long serialVersionUID = 3308815138155077750L;
            private int direction;

            public MoveAction(String whichWay) {
               
                if (whichWay == “up”) {
                    direction = 1;
                }
                else if(whichWay == “down”){
                    direction = -1;
                }
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                if (!hasStarted){
                    bpmInt = bpmInt + direction;
                    if (bpmInt > 224){bpmInt = 224;}
                    else if (bpmInt < 24) {bpmInt = 24;}
                    wy = (int)(bpmInt -24)*2 + 150;
                    timerDelay = msPbpm/bpmInt;
                    repaint();
                }
            }
        }
    }
   
   
    private BufferedImage loadImage(String name) {
        //BufferedImage bi;
        String imgFileName = “images/Metronome-“+name+”.png”;
        URL url = Metronome.class.getResource(imgFileName);
        BufferedImage img = null;
        try {
            img =  ImageIO.read(url);
        } catch (Exception e) {
        }
       
        return img;
    }
   
   
    /**
     * Returns an ImageIcon, or null if the path was invalid.
     */
    protected static ImageIcon createImageIcon(String path) {
        java.net.URL imgURL = Metronome.class.getResource(path);
        if (imgURL != null) {
            return new ImageIcon(imgURL);
        } else {
            System.err.println(“Couldn’t find file: ” + path);
            return null;
        }
    }
   
 
    /**
     * Start/Stop animation
    **/
    class MyKeyListener extends KeyAdapter {
            public void keyPressed(KeyEvent evt) {
                if (evt.getKeyChar() == KeyEvent.VK_SPACE) {
                     
                      if (!hasStarted){
                          bpmInt = (int)((wy-150)/2 + 24);
                          timerDelay = msPbpm/bpmInt;
                          timer.setDelay(timerDelay);
                          timer.start();
                          }
                       else {
                           stopAnimation = true;
                       }
                     hasStarted = !hasStarted;
                }
                // Sub-Beat Switch
                else if (evt.getKeyChar() == KeyEvent.VK_0 && hasStarted) {
                    subBeatX = 390;
                    numSubBeats = 0;
                }
                else if (evt.getKeyChar() == KeyEvent.VK_1 && hasStarted) {
                    subBeatX = 412;
                    numSubBeats = 1;
                }
                else if (evt.getKeyChar() == KeyEvent.VK_2 && hasStarted) {
                    subBeatX = 432;
                      numSubBeats = 2;
                }
          }
    }


    // Help dialog box
    private class HelpDialog extends JDialog implements ActionListener {

      private static final long serialVersionUID = -273240361506739164L;
  
      private HelpDialog() {
         
        super(frame, “Help”, true);
        // Stop the Metronome
        if (hasStarted) {
            timer.stop();
            (hasStarted) = false;
        }
       
        JPanel panel = new JPanel(new FlowLayout());
        getContentPane().add(panel);
        JLabel helpText = new JLabel();
        String helpContent = “<html>”
+”<head>”
+”<meta charset=’UTF-8′>”
+”<title>Insert title here</title>”
+”</head>”
+”<body>”
+”<div align=’center’ style=’font-size:1.2em’><b>How to use Metronome</b></div>”
+”<ul>”
+”    <li>Drag the weight to set tempo.</li><br/>”
+”    <li>Use up/down arrows on either side of the BPM display<br/>”
+”    or use up/down arrow keys on your keyboard to fine adjust tempo.</li>”
+”    <ul style=’margin-top: -0.5em’>”
+”        <li>Up increases tempo – weight moves down</li>”
+”        <li>Down decreases tempo – weight moves up</li>”
+”    </ul>”
+”    <li>Press spacebar to Start/Stop the metronome.</li><br/>”
+”    <li>Volume Sliders:</li>”
+”    <ul style=’margin-top: -0.5em’>”
+”        <li>Top slider controls the major beat volume.</li>”
+”        <li>Bottom slider controls the sub-beat volume.</li>”
+”    </ul>”
+”    <li>Select 0, 1 or 2 sub-beats with the Sub-Beat switch.<br/>”
+”        Or while the metronome is running,<br/>”
+”        use keypad number 0, 1, 2 to change number of sub-beats.</li><br/>”
+”    <li>Major beat and sub-beat sounds can be changed in the ‘Settings’ menu item.</li>”
+”</ul>”
+”<ul>”
+”<li>Timer Delay (Optional adjustment in Settings panel.)</li></br>”
+”Tempo accuracy may vary by 1/4 beat per minute at 60BPM<br/>depending on your computer configuration.<br/>”
+”To calibrate:<br/>”
+    “<ul style=’margin-top: -0.5em’>”
+        “<li>Set tempo to 60BPM and use a stop watch<br/>to count the number of beats that you hear in one minute.</li>”
+        “<li>If you count more than 60 beats,<br/>then the metronome is running to fast.<br/>”
+        “Increase the delay by moving the slider to the right<br/>or use right arrow key on your keyboard.</li>”
+        “<li>If you count less than 60 beats,<br/>the metronome is running slow.<br/>”
+         “Decrease the delay by moving the slider to the left<br/>or use left arrow key on your keyboard.</li>”
+    “</ul>”
+”</ul>”
+”</body>”
+”</html>”;
        helpText.setText(helpContent);
        panel.add(helpText);

        this.setBounds(300, 200, 640, 620);

        panel.setLocation(20, 50);
      }
  
      public void actionPerformed(ActionEvent ev)
      {
        setVisible(false);
      }
    }
 
   
    /**
     * The Settings dialog box
    **/
    private class SettingsDialog extends JDialog implements ActionListener, ChangeListener {

      private static final long serialVersionUID = 147052588095926828L;
     
      private SettingsDialog()
      {
        // Stop Metronome
        if (hasStarted) {
          timer.stop();
          hasStarted = false;
        }
         
        // Add main panel for Settings
        JPanel contentPane = new JPanel();
        contentPane.setLayout(new FlowLayout());
        contentPane.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 2));

       
        // panel for sound
        JPanel soundsContainer = new JPanel();
        soundsContainer.setLayout(new GridLayout(1, 2, 10, 10));

        // LEFT side of panel
        JPanel buttonPanel1 = new JPanel();
        buttonPanel1.setLayout(new GridLayout(16, 1, 10, 10));
        JLabel Left = new JLabel(“Major Beat”, JLabel.CENTER);
        Left.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));
        buttonPanel1.add(Left);
       
        // SELECT SOUND PANEL 1
        soundLabel1 = new JLabel(getSoundName(noteForMainBeat), JLabel.CENTER);
        buttonPanel1.add(soundLabel1);
       
        // Sound buttons
        JButton hc1 = new JButton(“Hand Clap”);
        hc1.addActionListener(new soundButtonPanel1(“Hand Clap”, HAND_CLAP ));
        buttonPanel1.add(hc1);
       
        JButton rs1 = new JButton(“Side Stick/Rimshot”);
        rs1.addActionListener(new soundButtonPanel1(“Side Stick/Rimshot”, RIM_SHOT));
        buttonPanel1.add(rs1);
       
        JButton cb1 = new JButton(“Cowbell”);
        cb1.addActionListener(new soundButtonPanel1(“CowBell”, COWBELL ));
        buttonPanel1.add(cb1);
       
        JButton hb1 = new JButton(“High Bongo”);
        hb1.addActionListener(new soundButtonPanel1(“High Bongo”, HIGH_BONGO ));
        buttonPanel1.add(hb1);
       
        JButton lb1 = new JButton(“Low Bongo”);
        lb1.addActionListener(new soundButtonPanel1(“Low Bongo”, LOW_BONGO ));
        buttonPanel1.add(lb1);
       
        JButton ht1 = new JButton(“High Timbale”);
        ht1.addActionListener(new soundButtonPanel1(“High Timbale”, HIGH_TIMBALE ));
        buttonPanel1.add(ht1);
       
        JButton lt1 = new JButton(“Low Timbale”);
        lt1.addActionListener(new soundButtonPanel1(“Low Timbale”, LOW_TIMBALE ));
        buttonPanel1.add(lt1);
       
        JButton ha1 = new JButton(“High Agogo”);
        ha1.addActionListener(new soundButtonPanel1(“High Agogo”, HIGH_AGOGO ));
        buttonPanel1.add(ha1);
       
        JButton la1 = new JButton(“Low Agogo”);
        la1.addActionListener(new soundButtonPanel1(“Low Agogo”, LOW_AGOGO ));
        buttonPanel1.add(la1);
       
        JButton c1 = new JButton(“Claves”);
        c1.addActionListener(new soundButtonPanel1(“Claves”, CLAVES ));
        buttonPanel1.add(c1);
       
        JButton hwb1 = new JButton(“High Wood Block”);
        hwb1.addActionListener(new soundButtonPanel1(“High Wood Block”, HIGH_WOOD_BLOCK ));
        buttonPanel1.add(hwb1);
       
        JButton lwb1 = new JButton(“Low Wood Block”);
        lwb1.addActionListener(new soundButtonPanel1(“Low Wood Block”, LOW_WOOD_BLOCK ));
        buttonPanel1.add(lwb1);
       
        JButton mc1 = new JButton(“Mute Cuica”);
        mc1.addActionListener(new soundButtonPanel1(“Mute Cuica”, CUICA ));
        buttonPanel1.add(mc1);
       
        JButton mt1 = new JButton(“Mute Triangle”);
        mt1.addActionListener(new soundButtonPanel1(“Mute Triangle”, MUTE_TRIANGLE ));
        buttonPanel1.add(mt1);
       
        soundsContainer.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
        soundsContainer.add(buttonPanel1, BorderLayout.NORTH);
       
        // RIGHT side of panel
        JPanel buttonPanel2 = new JPanel();
        buttonPanel2.setLayout(new GridLayout(16, 1, 10, 10));
        JLabel Right = new JLabel(“Sub-Beat”, JLabel.CENTER);
        Right.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));
        buttonPanel2.add(Right);
       
        // Selected sound
        soundLabel2 = new JLabel(getSoundName(noteForSubBeat), JLabel.CENTER);
        buttonPanel2.add(soundLabel2);
       
        // Sound buttons
        JButton hc2 = new JButton(“Hand Clap”);
        hc2.addActionListener(new soundButtonPanel2(“Hand Clap”, HAND_CLAP ));
        buttonPanel2.add(hc2);
       
        JButton rs2 = new JButton(“Side Stick/Rimshot”);
        rs2.addActionListener(new soundButtonPanel2(“Side Stick/Rimshot”, RIM_SHOT ));
        buttonPanel2.add(rs2);
       
        JButton cb2 = new JButton(“Cowbell”);
        cb2.addActionListener(new soundButtonPanel2(“Cowbell”, COWBELL ));
        buttonPanel2.add(cb2);
       
        JButton hb2 = new JButton(“High Bongo”);
        hb2.addActionListener(new soundButtonPanel2(“High Bongo”, HIGH_BONGO ));
        buttonPanel2.add(hb2);
       
        JButton lb2 = new JButton(“Low Bongo”);
        lb2.addActionListener(new soundButtonPanel2(“Low Bongo”, LOW_BONGO ));
        buttonPanel2.add(lb2);
       
        JButton ht2 = new JButton(“High Timbale”);
        ht2.addActionListener(new soundButtonPanel2(“High Timbale”, HIGH_TIMBALE ));
        buttonPanel2.add(ht2);
       
        JButton lt2 = new JButton(“Low Timbale”);
        lt2.addActionListener(new soundButtonPanel2(“Low Timbale”, LOW_TIMBALE ));
        buttonPanel2.add(lt2);
       
        JButton ha2 = new JButton(“High Agogo”);
        ha2.addActionListener(new soundButtonPanel2(“High Agogo”, HIGH_AGOGO ));
        buttonPanel2.add(ha2);
       
        JButton la2 = new JButton(“Low Agogo”);
        la2.addActionListener(new soundButtonPanel2(“Low Agogo”, LOW_AGOGO ));
        buttonPanel2.add(la2);
       
        JButton c2 = new JButton(“Claves”);
        c2.addActionListener(new soundButtonPanel2(“Claves”, CLAVES ));
        buttonPanel2.add(c2);
       
        JButton hwb2 = new JButton(“High Wood Block”);
        hwb2.addActionListener(new soundButtonPanel2(“High Wood Block”, HIGH_WOOD_BLOCK ));
        buttonPanel2.add(hwb2);
       
        JButton lwb2 = new JButton(“Low Wood Block”);
        lwb2.addActionListener(new soundButtonPanel2(“Low Wood Block”, LOW_WOOD_BLOCK ));
        buttonPanel2.add(lwb2);
       
        JButton mc2 = new JButton(“Mute Cuica”);
        mc2.addActionListener(new soundButtonPanel2(“Mute Cuica”, CUICA ));
        buttonPanel2.add(mc2);
       
        JButton mt2 = new JButton(“Mute Triangle”);
        mt2.addActionListener(new soundButtonPanel2(“Mute Triangle”, MUTE_TRIANGLE ));
        buttonPanel2.add(mt2);
       
        soundsContainer.add(buttonPanel2, BorderLayout.NORTH);
       
        contentPane.add(soundsContainer);
       
       
        // Bottom Panel for Fine Timing Adjustment
        JPanel timingPanel = new JPanel(new BorderLayout());
        timingPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
        timingPanel.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));
        timingPanel.setLayout(new GridLayout(3, 1));
       
        final JSlider timingSlider = new JSlider(JSlider.HORIZONTAL, 4500, 5500, msPbpm);
        timingSlider.addChangeListener(this);
       
        timingSlider.setMinorTickSpacing(50);
        timingSlider.setMajorTickSpacing(500);
        timingSlider.setPaintTicks(true);
        timingSlider.setPaintLabels(true);
       
        JPanel timerDelayPanel = new JPanel();
        timerDelayLabel.setText(“Timer delay is “+Integer.toString(msPbpm)+”ms”);
        timerDelayPanel.add(timerDelayLabel);
        timingPanel.add(timerDelayPanel);
       
        JButton reset = new JButton(“Reset to Default”);
        reset.addActionListener(new ActionListener() {
           
            @Override
            public void actionPerformed(ActionEvent e) {
                msPbpm = defaultMsPbpm;
                timerDelayLabel.setText(“Timer delay is “+Integer.toString(msPbpm)+”ms”);
                timingSlider.setValue(msPbpm);
            }
        });
       
        timingPanel.add(timingSlider);
        timingPanel.add(reset);
       
       
        contentPane.add(timingPanel);
       
        setContentPane(contentPane);

        setSize(480, 800);
        setLocationByPlatform(true);
      }
     
      public void stateChanged(ChangeEvent e) {
            JSlider timingJSlider = (JSlider)e.getSource();
            if (!timingJSlider.getValueIsAdjusting()) {
                msPbpm = timingJSlider.getValue();
                timerDelayLabel.setText(“Timer delay is “+Integer.toString(msPbpm)+”ms”);
            }
        }
 
     
      private String getSoundName(int noteNumber) {
        String retString = “not found”;
        switch (noteNumber){
        case 39:
            retString = “Hand Clap”;
            break;
        case 37:
            retString = “Sidestick/Rimshot”;
            break;
        case 56:
            retString = “Cowbell”;
            break;
        case 60:
            retString = “High Bongo”;
            break;
        case 61:
            retString = “Low Bongo”;
            break;
        case 65:
            retString = “High Timbale”;
            break;
        case 66:
            retString = “Low Timbale”;
            break;
        case 67:
            retString = “High Agogo”;
            break;
        case 68:
            retString = “Low Agogo”;
            break;
        case 75:
            retString = “Claves”;
            break;
        case 76:
            retString = “High Wood Block”;
            break;
        case 77:
            retString = “Low Wood Block”;
            break;
        case 78:
            retString = “Mute Cuica”;
            break;
        case 81:
            retString = “Mute Triangle”;
            break;
        }
        return retString;
    }

    public void actionPerformed(ActionEvent ev){
        setVisible(false);
      }
    }
   
   
    class soundButtonPanel1 implements ActionListener{

        private String noteDescription = null;
        private int noteNumber = 0;
       
        public soundButtonPanel1(String desc, int note) {
            noteDescription = desc;
            noteNumber = note;
        }
       
        public void actionPerformed(ActionEvent e) {
            soundLabel1.setText(noteDescription);
            channel.noteOn(noteNumber, majorVelocity);
            noteForMainBeat = noteNumber;
        }
     }
   
    class soundButtonPanel2 implements ActionListener{

        private String noteDescription = null;
        private int noteNumber = 0;
       
        public soundButtonPanel2(String desc, int note) {
            noteDescription = desc;
            noteNumber = note;
        }
       
        public void actionPerformed(ActionEvent e) {
            soundLabel2.setText(noteDescription);
            channel.noteOn(noteNumber, minorVelocity);
            noteForSubBeat = noteNumber;
        }
     }
}