diff --git a/Java/MoppyDesk/src/moppydesk/MoppyMainWindow.form b/Java/MoppyDesk/src/moppydesk/MoppyMainWindow.form
new file mode 100644
index 0000000..821a7e7
--- /dev/null
+++ b/Java/MoppyDesk/src/moppydesk/MoppyMainWindow.form
@@ -0,0 +1,172 @@
+
+
+
diff --git a/Java/MoppyDesk/src/moppydesk/MoppyMainWindow.java b/Java/MoppyDesk/src/moppydesk/MoppyMainWindow.java
new file mode 100644
index 0000000..29c119e
--- /dev/null
+++ b/Java/MoppyDesk/src/moppydesk/MoppyMainWindow.java
@@ -0,0 +1,296 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+/*
+ * MoppyMainWindow.java
+ *
+ * Created on Oct 8, 2011, 8:34:57 PM
+ */
+package moppydesk;
+
+import gnu.io.CommPortIdentifier;
+import java.io.File;
+import java.util.Enumeration;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.prefs.Preferences;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.JSlider;
+
+/**
+ *
+ * @author Sam
+ */
+public class MoppyMainWindow extends javax.swing.JFrame implements MoppyStatusConsumer{
+
+ static String PREF_COM_PORT = "comPort";
+ static String PREF_LOADED_SEQ = "loadedSequencePath";
+
+ MoppyUI app;
+ Preferences prefs = Preferences.userNodeForPackage(MoppyUI.class);
+ final JFileChooser sequenceChooser = new JFileChooser();
+ boolean comboBoxReady = false;
+
+ /** Creates new form MoppyMainWindow */
+ public MoppyMainWindow(MoppyUI app) {
+ this.app = app;
+ initComponents();
+
+ updateComSelectionMenu();
+
+ initializeSequencer((String) comSelectionMenu.getSelectedItem());
+
+ String previouslyLoadedFile = prefs.get(PREF_LOADED_SEQ, null);
+ if (previouslyLoadedFile != null){
+ loadSequenceFile(new File(previouslyLoadedFile));
+ }
+ }
+
+ private void updateComSelectionMenu() {
+ comboBoxReady = false;
+ comSelectionMenu.removeAllItems();
+ Enumeration e = CommPortIdentifier.getPortIdentifiers();
+ while (e.hasMoreElements()) {
+ comSelectionMenu.addItem(e.nextElement().getName());
+ }
+ comSelectionMenu.setSelectedItem(prefs.get(PREF_COM_PORT, ""));
+ comboBoxReady = true;
+ }
+
+ private void initializeSequencer(String comPort) {
+ statusLabel.setText("Initializing sequencer...");
+ if (app.ms != null) {
+ app.ms.closeSequencer();
+ }
+ try {
+ app.ms = new MoppySequencer(comPort);
+ app.ms.addListener(this);
+ } catch (Exception ex) {
+ Logger.getLogger(MoppyMainWindow.class.getName()).log(Level.SEVERE, null, ex);
+ statusLabel.setText("Sequencer initialization error!");
+ JOptionPane.showMessageDialog(rootPane, ex);
+ }
+ statusLabel.setText("Sequencer connected to " + comPort);
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ startButton = new javax.swing.JButton();
+ stopButton = new javax.swing.JButton();
+ comSelectionMenu = new javax.swing.JComboBox();
+ statusLabel = new javax.swing.JLabel();
+ jSeparator1 = new javax.swing.JSeparator();
+ loadButton = new javax.swing.JButton();
+ sequenceNameLabel = new javax.swing.JLabel();
+ jLabel1 = new javax.swing.JLabel();
+ jSlider1 = new javax.swing.JSlider();
+ bpmLabel = new javax.swing.JLabel();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
+ addWindowListener(new java.awt.event.WindowAdapter() {
+ public void windowClosing(java.awt.event.WindowEvent evt) {
+ MoppyMainWindow.this.windowClosing(evt);
+ }
+ });
+
+ startButton.setText("Start");
+ startButton.setEnabled(false);
+ startButton.setName("startButton"); // NOI18N
+ startButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ startButtonClicked(evt);
+ }
+ });
+
+ stopButton.setText("Stop/Reset");
+ stopButton.setName("stopButton"); // NOI18N
+ stopButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ stopResetClicked(evt);
+ }
+ });
+
+ comSelectionMenu.setName("comSelectionMenu"); // NOI18N
+ comSelectionMenu.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ comPortSelected(evt);
+ }
+ });
+
+ statusLabel.setText("Loading application...");
+ statusLabel.setName("statusLabel"); // NOI18N
+
+ jSeparator1.setName("jSeparator1"); // NOI18N
+
+ loadButton.setText("Load Sequence");
+ loadButton.setName("loadButton"); // NOI18N
+ loadButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ loadSequence(evt);
+ }
+ });
+
+ sequenceNameLabel.setText("");
+ sequenceNameLabel.setName("sequenceNameLabel"); // NOI18N
+
+ jLabel1.setText("Current Sequence:");
+ jLabel1.setName("jLabel1"); // NOI18N
+
+ jSlider1.setMajorTickSpacing(10);
+ jSlider1.setMaximum(210);
+ jSlider1.setMinimum(20);
+ jSlider1.setPaintTicks(true);
+ jSlider1.setValue(120);
+ jSlider1.setName("jSlider1"); // NOI18N
+ jSlider1.addMouseMotionListener(new java.awt.event.MouseMotionAdapter() {
+ public void mouseDragged(java.awt.event.MouseEvent evt) {
+ tempoSliderChanged(evt);
+ }
+ });
+
+ bpmLabel.setText(jSlider1.getValue() + " bpm");
+ bpmLabel.setName("bpmLabel"); // NOI18N
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(comSelectionMenu, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(statusLabel)
+ .addComponent(jSeparator1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(sequenceNameLabel)
+ .addComponent(jSlider1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(bpmLabel, javax.swing.GroupLayout.Alignment.TRAILING))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 30, Short.MAX_VALUE)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(startButton)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(stopButton))
+ .addComponent(loadButton)))
+ .addComponent(jLabel1))
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(comSelectionMenu, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 107, Short.MAX_VALUE)
+ .addComponent(jLabel1)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(sequenceNameLabel)
+ .addComponent(loadButton))
+ .addGap(18, 18, 18)
+ .addComponent(bpmLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(startButton)
+ .addComponent(stopButton))
+ .addComponent(jSlider1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addGap(11, 11, 11)
+ .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 8, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(statusLabel)
+ .addContainerGap())
+ );
+
+ pack();
+ }// //GEN-END:initComponents
+
+ private void stopResetClicked(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_stopResetClicked
+ statusLabel.setText("Stopping...");
+ app.ms.stopSequencer();
+ startButton.setEnabled(true);
+ statusLabel.setText("Stopped");
+ }//GEN-LAST:event_stopResetClicked
+
+ private void startButtonClicked(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_startButtonClicked
+ startButton.setEnabled(false);
+ app.ms.startSequencer();
+ }//GEN-LAST:event_startButtonClicked
+
+ private void loadSequence(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_loadSequence
+ String previouslyLoaded = prefs.get(PREF_LOADED_SEQ, null);
+ if (previouslyLoaded != null){
+ sequenceChooser.setCurrentDirectory(new File(previouslyLoaded));
+ }
+ int returnVal = sequenceChooser.showOpenDialog(this);
+
+ if (returnVal == JFileChooser.APPROVE_OPTION) {
+ loadSequenceFile(sequenceChooser.getSelectedFile());
+ } else {
+ //Cancelled
+ }
+ }//GEN-LAST:event_loadSequence
+
+ private void loadSequenceFile(File sequenceFile){
+ try {
+ statusLabel.setText("Loading file...");
+ app.ms.loadFile(sequenceFile.getPath());
+ sequenceNameLabel.setText(sequenceFile.getName());
+ prefs.put(PREF_LOADED_SEQ, sequenceFile.getPath());
+ statusLabel.setText("Loaded " + sequenceFile.getName());
+ startButton.setEnabled(true);
+ } catch (Exception ex) {
+ Logger.getLogger(MoppyMainWindow.class.getName()).log(Level.SEVERE, null, ex);
+ statusLabel.setText("File loading error!");
+ JOptionPane.showMessageDialog(rootPane, ex);
+ }
+ }
+
+ private void tempoSliderChanged(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_tempoSliderChanged
+ JSlider s = (JSlider) evt.getSource();
+ app.ms.setTempo(s.getValue());
+ bpmLabel.setText(s.getValue() + " bpm");
+ }//GEN-LAST:event_tempoSliderChanged
+
+ private void comPortSelected(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_comPortSelected
+ JComboBox cb = (JComboBox) evt.getSource();
+ if (comboBoxReady) {
+ String comPort = (String) cb.getSelectedItem();
+ initializeSequencer(comPort);
+ prefs.put(PREF_COM_PORT, comPort);
+ }
+ }//GEN-LAST:event_comPortSelected
+
+ private void windowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_windowClosing
+ app.ms.closeSequencer();
+ }//GEN-LAST:event_windowClosing
+
+ public void tempoChanged(int newTempo) {
+ jSlider1.setValue(newTempo);
+ bpmLabel.setText(newTempo + " bpm");
+ }
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel bpmLabel;
+ private javax.swing.JComboBox comSelectionMenu;
+ private javax.swing.JLabel jLabel1;
+ private javax.swing.JSeparator jSeparator1;
+ private javax.swing.JSlider jSlider1;
+ private javax.swing.JButton loadButton;
+ private javax.swing.JLabel sequenceNameLabel;
+ private javax.swing.JButton startButton;
+ private javax.swing.JLabel statusLabel;
+ private javax.swing.JButton stopButton;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/Java/MoppyDesk/src/moppydesk/MoppyStatusConsumer.java b/Java/MoppyDesk/src/moppydesk/MoppyStatusConsumer.java
new file mode 100644
index 0000000..98cf77f
--- /dev/null
+++ b/Java/MoppyDesk/src/moppydesk/MoppyStatusConsumer.java
@@ -0,0 +1,13 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package moppydesk;
+
+/**
+ *
+ * @author Sam
+ */
+public interface MoppyStatusConsumer {
+ public void tempoChanged(int newTempo);
+}
diff --git a/Java/MoppyDesk/src/moppydesk/MoppyUI.java b/Java/MoppyDesk/src/moppydesk/MoppyUI.java
new file mode 100644
index 0000000..e142c2d
--- /dev/null
+++ b/Java/MoppyDesk/src/moppydesk/MoppyUI.java
@@ -0,0 +1,29 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package moppydesk;
+
+import org.jdesktop.application.SingleFrameApplication;
+
+/**
+ *
+ * @author Sam
+ */
+public class MoppyUI extends SingleFrameApplication{
+
+ MoppySequencer ms = null;
+
+ @Override
+ protected void startup() {
+ show(new MoppyMainWindow(this));
+ }
+
+ /**
+ * Main method launching the application.
+ */
+ public static void main(String[] args) {
+ launch(MoppyUI.class, args);
+ }
+
+}