changeset 0:4031cbb02f08

Initial import. Ignore-this: 87317e384f22bde48db996355191fa5f
author Marcus Pearce <m.pearce@gold.ac.uk>
date Tue, 18 May 2010 11:37:10 +0100
parents
children 93ed757b9871
files Block.java COPYRIGHT Clock.java Experiment.java ExperimentController.java ExperimentGui.java FileList.java InstructionsPanel.java InterBlockPanel.java MelodyResults.java MidiPlayer.java ProbeID.java README StimulusPanel.java SubjectDataPanel.java SubjectResults.java runExperiment.bat
diffstat 17 files changed, 2316 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Block.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,330 @@
+/*=============================================================================
+ * File:       Block.java
+ * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
+ * Created:    <2008-01-07 10:38:45 marcusp>
+ * Time-stamp: <2010-05-17 11:42:23 marcusp>
+ *=============================================================================
+ */
+
+import java.util.Collections;
+import java.util.ArrayList; 
+import java.util.Iterator;
+import java.io.*;
+
+public class Block { 
+    
+    /* Label */ 
+    private String label; 
+    private boolean writeResults; 
+
+    /* the list of midi files, the current midi file and the midi directory*/ 
+    private FileList filelist; 
+    private String filename; 
+    private int melodyNumber; 
+
+    /* The Experiment and GUI */ 
+    private ExperimentGui gui; 
+    private Experiment exp; 
+    
+    /* The MidiPlayer */ 
+    private MidiPlayer mp; 
+
+    /* Probe positions, note onsets, durations, IOIs and pitches */ 
+    private ArrayList probes, onsets, iois, durations, pitches, clockStartTimes; 
+    private long tatum; 
+
+    /* The start of the song in nanoseconds */ 
+    private long startTime; 
+    
+    /* Results */ 
+    private MelodyResults mResults; 
+    
+    /* whether the current song has run yet or not */ 
+    private boolean hasRun;
+
+    /*
+     * Accessors 
+     */ 
+    public String getLabel() { return label; }
+    public int getMelodyNumber() { return melodyNumber; }
+    public void setMelodyNumber(int n) { melodyNumber = n; }
+    public long getTatum() { return tatum; } 
+    public MelodyResults getMelodyResults() { return mResults; }
+    public ArrayList getProbePositions() { return probes; } 
+    public ArrayList getInterOnsetIntervals() { return iois; } 
+    public ArrayList getOnsets() { return onsets; } 
+    public ArrayList getClockStartTimes() { return clockStartTimes; } 
+
+    /* set the start time in nanoseconds to NOW */ 
+    public long getStartTime() { return startTime; }
+    public void setStartTime() { startTime = System.nanoTime(); } 
+    
+    /* 
+     * constructor 
+     */ 
+    public Block(Experiment e, ExperimentGui eg, String fl, String lab, 
+                 boolean wr) { 
+        // set-up variables 
+        exp = e; 
+        gui = eg; 
+        label = lab; 
+        filelist = new FileList(fl); 
+        filename = (String)filelist.currentFile(); 
+        melodyNumber = 1; 
+        writeResults = wr; 
+        // initialise the block 
+        hasRun = false; 
+        initialiseBlock();
+        // Write out stimulus structure for all melodies in block
+        //writeStimuli(); 
+    }
+
+    /* initialise the block */ 
+    public void initialiseBlock() { 
+        // Initialise the Midiplayer 
+        mp = new MidiPlayer(exp.getMidiDirectory().concat(filename)); 
+        // Set up the melody structure and the probe positions 
+        setupSong(); 
+        setupProbes(); 
+        // Set up the results object for this melody
+        mResults = new MelodyResults(filename, probes, 
+                                     onsets, iois, durations, pitches); 
+        mResults.setSubjectID(exp.getSubjectID()); 
+        hasRun = false; 
+    }
+
+    public void storeMelodyResult() { 
+        if (writeResults && hasRun)
+            exp.addToSubjectResults(mResults); 
+    }
+    
+    private void setupSong() { 
+        // Get the note IOI and Onset lists 
+        onsets = mp.getOnsets(); 
+        durations = mp.getDurations(); 
+        iois = mp.getInterOnsetIntervals(); 
+        pitches = mp.getPitches(); 
+        tatum = mp.getTatum(); 
+    }
+
+    private void setupProbes() { 
+        ArrayList probePos = filelist.currentProbes(); 
+        Iterator ppi = probePos.iterator(); 
+        
+        // By convention, the first probe position is the unexpected
+        // one and  the second is the expected one. 
+        //probeValues.add(ProbeID.PROBE_UNEX); 
+        //probeValues.add(ProbeID.PROBE_EX); 
+
+        ArrayList probeValues = new ArrayList(probePos.size()); 
+
+        for (int i = 0; i < probePos.size(); i++)
+            probeValues.add(ProbeID.PROBE);
+        Iterator pvi = probeValues.iterator(); 
+        
+
+        // Set up probes 
+        probes = new ArrayList(onsets.size()); 
+        for (int i = 0; i < onsets.size(); i++) 
+            probes.add(ProbeID.NOT_PROBE); 
+
+        while(ppi.hasNext()) { 
+            int probe = ((Integer)ppi.next()).intValue(); 
+            // probes.set(probe - numEvents, ProbeID.START_CLOCK); 
+            probes.set(probe - 1, ProbeID.BEFORE_PROBE); 
+            probes.set(probe, (ProbeID)pvi.next()); 
+            probes.set(probe + 1, ProbeID.AFTER_PROBE); 
+        }
+        
+        // Set up the clock start times 
+        clockStartTimes = new ArrayList(probePos.size()); 
+        long clockTimeInMicroseconds = 
+            (tatum * exp.getClockUnits() * exp.getNumUnits()); 
+        for (int i = 0; i < probePos.size(); i++) { 
+            int ppos = ((Integer)probePos.get(i)).intValue(); 
+            long onset = ((Long)(onsets.get(ppos))).longValue(); 
+            clockStartTimes.add(onset - clockTimeInMicroseconds); 
+        }
+        try { 
+            Collections.sort(clockStartTimes); 
+        } catch (Exception e) { 
+            e.printStackTrace(); 
+        }
+    }
+
+    public void printProbes() { 
+        Iterator pi = probes.iterator(); 
+
+        int i = 1; 
+        while(pi.hasNext()) { 
+            System.out.println(filename + " " + i + ": " + pi.next()); 
+            i++; 
+        }
+    }
+                
+    public String nextFile() {
+        filelist.incrementFileIndex(); 
+        filename = filelist.currentFile(); 
+        melodyNumber = melodyNumber + 1; 
+        return filename;
+    }
+
+    /* add a reponse i and time t (nanoseconds) */ 
+    public void addResponse(int i, long t) { 
+        // System.out.println("answer = " + i + 
+        //                    "; responseTime = " + t + 
+        //                    "; startTime = " + startTime + 
+        //                    "; answer = " + (t - startTime) / 1000); 
+        // add relative responseTime in microseconds 
+        mResults.addResponse(i, (t - startTime) / 1000); 
+    }
+
+    public void addMelodyQA(String q, String a) { 
+        mResults.addQuestion(q); 
+        mResults.addAnswer(a); 
+    }
+
+    public void presentStimulus() { 
+        // Start the experiment 
+        setStartTime(); 
+        mp.play(); 
+        hasRun = true; 
+        
+        // Wait for midi file to stop playing 
+        //try { Thread.sleep((mp.getLength() / 1000) + 2000); } 
+        //catch (InterruptedException e) {}
+    }
+
+    public boolean isRunning() { return mp.getSequencer().isRunning(); }
+    public boolean hasRun() { return hasRun; } 
+
+
+    /* 
+     * Write out stimulus onsets and probe positions 
+     */ 
+
+    public void writeStimuli() {
+        String midiFile; 
+        while((midiFile = filelist.currentFile()) != null) { 
+            
+            Writer writer = null;
+            String composition = midiFile.substring(0, midiFile.length() - 4);
+            String outFile = 
+                exp.RESULTS_DIRECTORY + File.separator + 
+                "stimuli" + File.separator + composition 
+                + ".onsets"; 
+            File outputFile = new File(outFile); 
+            
+            MidiPlayer midip = 
+                new MidiPlayer(exp.getMidiDirectory().concat(midiFile)); 
+
+            // Setup the probes 
+            ArrayList probePos = filelist.currentProbes(); 
+            // Write the data 
+            try {
+                writer = new FileWriter (outputFile, true);
+            } catch (IOException e) {
+                System.out.println("Could not write file: " + 
+                                   outputFile.getPath());
+                return;
+            }
+            try {
+                writeResults(writer, midip, probePos, composition); 
+                writer.close();
+            } catch (IOException e) {
+                System.out.println (e.getMessage());
+                return;
+            }
+            // increment the file index 
+            filelist.incrementFileIndex(); 
+        }
+        // Reset filelist 
+        filelist.setFileIndex(0); 
+    }
+
+    private void writeResults(Writer w, MidiPlayer mipl, ArrayList probePos, 
+                              String composition) 
+        throws IOException { 
+
+        System.out.println(composition); 
+
+        ArrayList ons = mipl.getOnsets(); 
+        ArrayList pits = mipl.getPitches(); 
+        
+        Iterator oi = ons.iterator(); 
+        Iterator piti = pits.iterator(); 
+
+        // setup probes 
+        Iterator ppi = probePos.iterator(); 
+            
+        // By convention, the first probe position is the unexpected
+        // one and  the second is the expected one. 
+        // probeValues.add(ProbeID.PROBE_UNEX);
+        // probeValues.add(ProbeID.PROBE_EX);
+
+        ArrayList probeValues = new ArrayList(probePos.size()); 
+        for (int i = 0; i < probePos.size(); i++)
+            probeValues.add(ProbeID.PROBE);
+
+        Iterator pvi = probeValues.iterator(); 
+        
+        ArrayList pprobes = new ArrayList(ons.size()); 
+        for (int i = 0; i < ons.size(); i++) 
+            pprobes.add(ProbeID.NOT_PROBE); 
+        
+        while(ppi.hasNext()) { 
+            int probe = ((Integer)ppi.next()).intValue(); 
+            pprobes.set(probe - 1, ProbeID.BEFORE_PROBE); 
+            pprobes.set(probe, (ProbeID)pvi.next()); 
+            pprobes.set(probe + 1, ProbeID.AFTER_PROBE); 
+        }
+        
+        Iterator pi = pprobes.iterator(); 
+        int eventIndex = 1; 
+        
+        //writeHeader(w); 
+        
+        while(oi.hasNext()) { 
+            long onset = ((Long)oi.next()).longValue();
+            int pitch = ((Integer)piti.next()).intValue(); 
+
+            ProbeID p = (ProbeID)pi.next(); 
+            
+            int probe = 0; 
+            
+            switch(p) { 
+            case NOT_PROBE: 
+            case START_CLOCK:
+            case BEFORE_PROBE: 
+            case AFTER_PROBE: 
+                break; 
+            case PROBE:
+            case PROBE_EX: 
+            case PROBE_UNEX:
+                if (p == ProbeID.PROBE_UNEX)
+                    probe = 1; 
+                else if (p == ProbeID.PROBE_EX)
+                    probe = 2;
+                else if (p == ProbeID.PROBE)
+                    probe = 1;
+                break; 
+            default: 
+                System.out.println("Unexpected probe id: " + p); 
+                break; 
+            }
+            w.write(composition + " " +  eventIndex + " " + 
+                    onset + " " + pitch + " " + 
+                    probe + "\n"); 
+            eventIndex++; 
+        }
+    }
+    
+    private void writeHeader(Writer w) throws IOException {
+        w.write("melody note " + 
+                "onset pitch " + 
+                "probe"); 
+        w.write("\n"); 
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYRIGHT	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,19 @@
+Copyright (c) 2008 Marcus T. Pearce <m.pearce@gold.ac.uk>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Clock.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,152 @@
+import java.awt.*;
+import java.awt.geom.*;
+import javax.swing.*;
+
+/** Clocks displayed in a panel. */
+public class Clock extends JPanel implements Runnable {
+
+    /* The default colours */ 
+    static final Color BACKGROUND = Color.black; 
+    static final Color FOREGROUND = Color.white; 
+
+    /* Panel width, height and border. */
+    public static final int WIDTH = 450;
+    public static final int BORDER = 50;
+
+    /* Minutes in a day. */
+    public static final int ONE_DAY = 24 * 60;
+
+    /* Show the clock? */ 
+    public Boolean showClock = false; 
+    public Boolean showFullClock = false; 
+
+    /* minutes since midnight, range 0 to (ONE_DAY - 1) inclusive */ 
+    private int minutes = 0;
+
+    /* Milliseconds per tick of the minute hand */ 
+    private long millisecondsPerTick = 1000; 
+    public void  setMillisecondsPerTick(long n) { millisecondsPerTick = n; }
+
+    /* Create a new Clock at time midnight. */
+    public Clock() {
+        this.setBackground(BACKGROUND); 
+        this.setPreferredSize (new Dimension (WIDTH, WIDTH));
+    }
+
+    public void run() { 
+        reset(); 
+        if (showClock == false) { 
+            showClock = true; 
+            repaint(); 
+        } 
+        for (int i = 0; i < 60; i++) {
+            try { Thread.sleep (millisecondsPerTick); } 
+            catch (InterruptedException e) {}
+            tick(1);
+            repaint();
+        }
+    } 
+    
+    /* Move time on by 1 second. */
+    public void tick(int n) {
+	minutes = (minutes + n) % ONE_DAY;
+    }
+    
+    /* Reset the clock to 00:00. */ 
+    public void reset() { 
+        minutes = 0; 
+    } 
+
+    /* Draw the clock or fixation point. */
+    public void paintComponent (Graphics g) {
+	super.paintComponent (g);
+        
+        if (showClock == true) { 
+            paintClock(g); 
+        } else { 
+            paintFixationPoint(g); 
+        }
+    }
+    
+    private void paintFixationPoint(Graphics g) { 
+	int left_edge = BORDER;
+	int top = BORDER;
+	int diameter = WIDTH - (BORDER * 2);
+	int centrex = left_edge + (diameter / 2); 
+	int centrey = top + (diameter / 2); 
+
+        int linelength = diameter / 20;  
+
+        g.setColor(FOREGROUND);
+        g.drawLine(centrey, centrex - linelength, centrey, centrex + linelength);
+        g.drawLine(centrey - linelength, centrex, centrey + linelength, centrex);
+    } 
+
+    private void paintClock (Graphics g) { 
+	int minutes_angle = 90 - ((minutes % 60) * 6);
+	int hours_angle = 90 - ((minutes / 60) * 30);
+	int left_edge = BORDER;
+	int top = BORDER;
+	int diameter = WIDTH - (BORDER * 2); 
+        int radius = diameter / 2; 
+	int centrex = left_edge + radius; 
+	int centrey = top + radius; 
+
+	g.setColor(FOREGROUND);
+
+	// draw the clock as a black circle:
+	g.drawOval(left_edge, top, diameter, diameter);
+
+	// draw the hour numbers 
+        int offset = 0; 
+	for(int i = 1; i <= 12; i++) {
+	    int hours_pos = 90 - (i * 30); 
+	    int x = (centrex - offset)  + 
+                (int)((radius - 5) * Math.cos(hours_pos * Math.PI / 180));
+	    int y = (centrey + offset) - 
+                (int)((radius - 5) * Math.sin(hours_pos * Math.PI / 180));
+	    //g.drawString(String.valueOf(i), x, y); 
+            g.drawOval(x, y, diameter / 200, diameter / 200); 
+	}
+
+	// get the coordinate of the line_ends 		
+	int minutesx = (int)((radius - 5) * 
+                             Math.cos(minutes_angle * Math.PI / 180)); 
+	int minutesy = (int)((radius - 5) * 
+                             Math.sin(minutes_angle * Math.PI /180)); 
+        
+        // draw the minutes' hand
+        g.drawLine(centrex, centrey, centrex + minutesx, centrey - minutesy);
+
+        int arc_angle = minutes_angle - 90; 
+        Graphics2D g2 = (Graphics2D)g;
+        g2.setPaint(FOREGROUND);
+        
+        //System.out.println("minutes_angle = " + minutes_angle + 
+        //                   "; arc_angle = " + arc_angle); 
+
+        if (arc_angle == 0 & showFullClock == true) { 
+            g2.fillOval(left_edge, top, diameter, diameter);
+            showFullClock = false; 
+        } else { 
+            showFullClock = true; 
+            if (arc_angle >= -180) { 
+                Arc2D pie = new Arc2D.Float(centrex - radius, centrey - radius, 
+                                            2 * radius, 2 * radius, 
+                                            90, arc_angle,
+                                            Arc2D.PIE);
+                g2.fill(pie);
+            } else { 
+                Arc2D pie1 = new Arc2D.Float(centrex - radius, centrey - radius, 
+                                             2 * radius, 2 * radius, 
+                                             90, -180, Arc2D.PIE);
+                Arc2D pie2 = new Arc2D.Float(centrex - radius, centrey - radius, 
+                                             2 * radius, 2 * radius, 
+                                             270, arc_angle + 180,
+                                             Arc2D.PIE);
+                g2.fill(pie1);
+                g2.fill(pie2);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Experiment.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,157 @@
+/*=============================================================================
+ * File:       Experiment.java
+ * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
+ * Created:    <2007-02-14 11:28:27 marcusp>
+ * Time-stamp: <2010-05-18 11:13:42 marcusp>
+ *=============================================================================
+ */
+
+import java.io.*;
+import javax.swing.UIManager; 
+import java.awt.*;
+import java.awt.Color;
+
+public class Experiment { 
+
+    /* pathnames */ 
+    private final String BASE_DIRECTORY = 
+        new File("").getAbsolutePath() + File.separator; 
+    private final String DATA_DIRECTORY = 
+        BASE_DIRECTORY + "Data" + File.separator; 
+    public final String RESULTS_DIRECTORY = 
+        BASE_DIRECTORY + "Results" + File.separator; 
+
+    private final String MIDI_DIRECTORY = 
+        DATA_DIRECTORY + "Midi" + File.separator;
+
+    public final String RESULTS_EXTENSION = ".dat"; 
+    public final String SUBJECT_RESULTS_FILE = 
+        RESULTS_DIRECTORY + "subjects" + RESULTS_EXTENSION; 
+
+    public final String INSTRUCTIONS_FILE = 
+        DATA_DIRECTORY + "instructions.html"; 
+    public final String MIDIFILELIST_FILE = 
+        DATA_DIRECTORY + "filelist.txt"; 
+    public final String PRACTICE_MIDIFILELIST_FILE = 
+        DATA_DIRECTORY + "pfilelist.txt"; 
+
+    /* The GUI */ 
+    private ExperimentGui gui; 
+
+    /* the units of the clock as multiples of the tatum */
+    private int clockUnits; 
+    /* number of units that clock runs for before a probe event */ 
+    private int numUnits; 
+
+    /* the blocks of the experiment */ 
+    Block[] blocks;
+    int currentBlockID; 
+    
+    /* Subject ID */ 
+    private int subjectID; 
+
+    /* Results */ 
+    private SubjectResults results; 
+
+    /* the details of the rating scale */
+    private int scaleLength;
+    private String lowAnchor;
+    private String highAnchor;
+
+    /* accessors */ 
+    public int getScaleLength() { return scaleLength; }
+    public String getLowAnchor() { return lowAnchor; }
+    public String getHighAnchor() { return highAnchor; }
+
+    public String getMidiDirectory() { return MIDI_DIRECTORY; }
+    public SubjectResults getSubjectResults() { return results; }
+    public Block getCurrentBlock() { return blocks[currentBlockID]; }
+    public int getCurrentBlockID() { return currentBlockID + 1; }
+    public int getClockUnits() { return clockUnits; } 
+    public int getNumUnits() { return numUnits; } 
+    public String getInstructionsFile() { return INSTRUCTIONS_FILE; }
+    public int getSubjectID() { return subjectID; }
+    public void setSubjectID(int id) { 
+        subjectID = id; 
+        results.setSubjectID(id); 
+        results.setOutputFile(id); 
+        getCurrentBlock().getMelodyResults().setSubjectID(id); 
+    }
+
+    /* Constructor */ 
+    public Experiment (int cu, int nu, int sl, String la, String ha) { 
+        
+        // Setup variables 
+        results = new SubjectResults(this); 
+        clockUnits = cu; 
+        numUnits = nu; 
+        scaleLength = sl;
+        lowAnchor = la;
+        highAnchor = ha;
+
+        // Initialise the experiment 
+        Block practice = new Block(this, gui, PRACTICE_MIDIFILELIST_FILE, 
+                                   "Practice", false); 
+        Block main = new Block(this, gui, MIDIFILELIST_FILE, 
+                               "Main", true); 
+        blocks  = new Block[2]; 
+        blocks[0] = practice; 
+        blocks[1] = main; 
+        currentBlockID = 0; 
+
+        // Create the GUI 
+        gui = new ExperimentGui(this); 
+    }
+
+    public Boolean nextBlock() { 
+        boolean lastBlock = true; 
+        if (currentBlockID + 1 < blocks.length) 
+            currentBlockID = currentBlockID + 1; 
+        else 
+            lastBlock = false; 
+        return lastBlock; 
+    }
+
+    public void addToSubjectResults(MelodyResults mr) { results.addResult(mr); }
+
+    public void runExperiment () { 
+        (new Thread(gui)).start();
+        getCurrentBlock().presentStimulus(); 
+    }
+    public boolean isRunning() { return getCurrentBlock().isRunning(); }
+    public boolean hasRun() { return getCurrentBlock().hasRun(); } 
+
+    /* Show the GUI */ 
+    public void showGUI(int width, int height) { 
+        gui.pack();
+        gui.setSize(width, height); 
+        // UIManager.put("Button.focus", UIManager.get("Button.select"));
+        gui.setVisible(true);
+    } 
+
+    /* Main method */ 
+    public static void main(String[] args) {
+        if (args.length == 0) {
+            System.out.println("Usage: " + "\t" + "java Experiment " + 
+                               "<clock units> <number of units> <scale length>" + 
+                               "<low anchor> <high anchor>"); 
+            System.exit(1);
+        }
+        
+        // Parse Arguments 
+        int n = 0; 
+        int cu = Integer.parseInt(args[n++]); 
+        int nu = Integer.parseInt(args[n++]); 
+        int sl = Integer.parseInt(args[n++]); 
+        String la = args[n++];
+        String ha = args[n++];
+
+        // Create experiment 
+        Experiment exp = new Experiment(cu, nu, sl, la, ha); 
+        
+        // Show the GUI 
+        int width=(int)Toolkit.getDefaultToolkit().getScreenSize().getWidth();
+        int height=(int)Toolkit.getDefaultToolkit().getScreenSize().getHeight();
+        exp.showGUI(width, height); 
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ExperimentController.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,179 @@
+/*=============================================================================
+ * File:       ExperimentController.java
+ * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
+ * Created:    <2007-12-14 12:06:10 marcusp>
+ * Time-stamp: <2010-05-18 10:51:31 marcusp>
+ *=============================================================================
+ */
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+public class ExperimentController implements ActionListener, KeyListener { 
+    
+    /* variables */ 
+    ExperimentGui gui;
+
+    Experiment exp; 
+    SubjectResults results; 
+
+    InstructionsPanel ip; 
+    StimulusPanel sp; 
+    SubjectDataPanel sdp; 
+    InterBlockPanel ibp;
+    
+    /* constructor */ 
+    public ExperimentController(ExperimentGui eg) { 
+        gui = eg; 
+        exp = gui.getExperiment(); 
+        results = exp.getSubjectResults(); 
+
+        ip = gui.getInstructionsPanel(); 
+        ibp = gui.getInterBlockPanel(); 
+        sp = gui.getStimulusPanel();         
+        sdp = gui.getSubjectDataPanel(); 
+
+        ip.addNextButtonListener(this); 
+        sp.addAllListeners(this);
+        sdp.addFinishButtonListener(this); 
+        ibp.addContinueButtonListener(this); 
+
+        sp.setFocusable(true);
+        sp.addKeyListener(this);
+        sp.addAllKeyListeners(this);
+        gui.setFocusable(true);
+        gui.addKeyListener(this);
+    }
+
+    /* report erroneous input */ 
+    protected void reportError (String msg) {
+        JOptionPane.showMessageDialog (gui, msg);
+    }
+
+    /* actionPerformed */ 
+    public void actionPerformed(ActionEvent e) {
+        long time = System.nanoTime(); 
+        Object source = e.getSource();
+
+        ip = gui.getInstructionsPanel(); 
+        ibp = gui.getInterBlockPanel(); 
+        sp = gui.getStimulusPanel();         
+        sdp = gui.getSubjectDataPanel(); 
+ 
+        Block block = exp.getCurrentBlock(); 
+
+        // InstructionsPanel 
+        if (source == ip.getNextButton()) { 
+            int subjID = ip.getSubjectID(); 
+            if (subjID == ip.INVALID_SUBJECT_ID || subjID < 1)
+                reportError("The Participant ID should be a positive integer.");
+            // TODO: check that subjID doesn't already exist
+            else { 
+                exp.setSubjectID(subjID); 
+                gui.showCard("stimulus");
+                exp.runExperiment();  
+            }
+        }
+
+        // InterBlockPanel
+        else if (source == ibp.getContinueButton()) { 
+            block.initialiseBlock();
+            sp.setSongNumberText(); 
+            sp.defaultAnswers(); 
+            gui.setAcceptingResponses(false); 
+            gui.showCard("stimulus"); 
+            exp.runExperiment();  
+        }
+         
+       // SubjectDataPanel
+        else if (e.getSource() == sdp.getFinishButton()) { 
+            if (sdp.allDataEntered()) { 
+                sdp.storeData(); 
+                results.writeSubjectData(); 
+                System.exit(0); 
+            } else 
+                reportError("You have not filled in all the information."); 
+        }
+
+        // StimulusPanel
+        else if (source == sp.getPlayButton()) {
+//             if (!exp.isRunning() && !exp.hasRun())
+//                 exp.runExperiment(); 
+//             else 
+//                 reportError("You have already played the melody.");
+        } else if (source == sp.getNextButton()) { 
+            if (!exp.hasRun() || exp.isRunning()) 
+                reportError("You haven't finished playing the melody yet."); 
+            else if (sp.unansweredQuestions())
+                reportError("There are unanswered questions."); 
+            else {  
+                // store results 
+                String answer1 = (String)(sp.getQ1Box().getSelectedItem()); 
+                String answer2 = (String)(sp.getQ2Box().getSelectedItem()); 
+                block.addMelodyQA("known", answer1); 
+                block.addMelodyQA("liked", answer2); 
+                block.storeMelodyResult(); 
+                // proceed to ... 
+                String nextFile = block.nextFile();
+                if (nextFile == null) { 
+                    boolean nb = exp.nextBlock(); 
+                    if (nb) { 
+                        // ... next block of trials 
+                        gui.getInterBlockPanel().setText(); 
+                        gui.getInterBlockPanel().updateMessageDisplay(); 
+                        gui.showCard("interblock"); 
+                    } else {
+                        // ... write results and subject questionnaire 
+                        results.writeResults(); 
+                        gui.showCard("subject"); 
+                    }
+                } else { 
+                    // ... next melody within block
+                    sp.setSongNumberText(); 
+                    sp.defaultAnswers(); 
+                    gui.setAcceptingResponses(false); 
+                    block.initialiseBlock(); 
+                    exp.runExperiment(); 
+                }
+            }
+        } else { 
+            JButton[] rButtons = sp.getResponseButtons(); 
+            for (int i = 0; i < rButtons.length; i++) { 
+                if (source == rButtons[i] && (exp.isRunning() || exp.hasRun()) && gui.getAcceptingResponses()) { 
+                    block.addResponse(i+1, time); 
+                    gui.setAcceptingResponses(false); 
+                }
+            }
+        }
+    }
+    
+    public void keyPressed(KeyEvent e)  {
+        //System.out.println("Key pressed: " + e.getKeyChar());
+        long time = System.nanoTime();
+
+        Block block = exp.getCurrentBlock();
+        sp = gui.getStimulusPanel();
+        JButton[] rButtons = sp.getResponseButtons();
+
+        for (int i = 0; i < rButtons.length; i++) {
+            if ((exp.isRunning() || exp.hasRun()) && gui.getAcceptingResponses()) {
+                //System.out.println("Char = " + Character.forDigit(i+1, 10));
+                if (e.getKeyChar() == Character.forDigit(i+1, 10)) {
+                    //System.out.println("Got rating: " + (i + 1));
+                    block.addResponse(i+1, time);
+                    gui.setAcceptingResponses(false); 
+                }
+                //else 
+                //    block.addResponse(0, time);
+            }
+        }
+    }
+
+    public void keyReleased(KeyEvent e)  {
+        // System.out.println("Key released: " + e.getKeyChar());
+    }
+    public void keyTyped(KeyEvent e)  {
+        //System.out.println("Key typed: " + e.getKeyChar());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ExperimentGui.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,222 @@
+/*=============================================================================
+ * File:       ExperimentGui.java
+ * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
+ * Created:    <2007-02-14 16:42:31 marcusp>
+ * Time-stamp: <2010-05-18 11:12:05 marcusp>
+ *=============================================================================
+ */
+
+import java.awt.*;
+import javax.swing.*;
+import java.util.ArrayList; 
+import java.util.Iterator;
+
+public class ExperimentGui extends JFrame implements Runnable {
+
+    /* the Experiment */ 
+    private Experiment exp; 
+
+    /* The visual display indicating probe positions */ 
+    private Clock clock; 
+
+    /* The UI components */ 
+    private JPanel mainPanel; 
+    private InstructionsPanel instructionsPanel;
+    private StimulusPanel stimulusPanel; 
+    private SubjectDataPanel subjectDataPanel; 
+    private InterBlockPanel interBlockPanel; 
+    
+    /* Whether we are accepting responses */ 
+    private Boolean acceptingResponses; 
+    
+    /* accessors */ 
+    public Boolean getAcceptingResponses() { return acceptingResponses; }
+    public void setAcceptingResponses(Boolean b) { acceptingResponses = b; } 
+    public Experiment getExperiment() { return exp; }
+    public InstructionsPanel getInstructionsPanel() { return instructionsPanel; }
+    public StimulusPanel getStimulusPanel() { return stimulusPanel; } 
+    public SubjectDataPanel getSubjectDataPanel() { return subjectDataPanel; }
+    public InterBlockPanel getInterBlockPanel() { return interBlockPanel; }
+    
+    /* Constructor */ 
+    public ExperimentGui(Experiment experiment) {
+
+        // initialise experiment 
+        exp = experiment; 
+        acceptingResponses = false; 
+
+        // set up the clock 
+        clock = new Clock(); 
+        
+        // construct the frame
+        JFrame.setDefaultLookAndFeelDecorated(true);
+        this.getContentPane().setBackground (Color.black);
+        this.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
+        this.setLayout (new BorderLayout());
+                
+        // The different cards 
+        instructionsPanel = new InstructionsPanel(this);
+        stimulusPanel = new StimulusPanel(this, clock); 
+        interBlockPanel = new InterBlockPanel(exp); 
+        subjectDataPanel = new SubjectDataPanel(this, exp.getSubjectResults()); 
+        
+        // The Controller 
+        ExperimentController ec = new ExperimentController(this); 
+
+        // Show it all 
+        CardLayout cl = new CardLayout(); 
+        mainPanel = new JPanel(cl); 
+        mainPanel.add(instructionsPanel, "instructions"); 
+        mainPanel.add(interBlockPanel, "interblock"); 
+        mainPanel.add(stimulusPanel, "stimulus"); 
+        mainPanel.add(subjectDataPanel, "subject"); 
+
+        this.add(mainPanel, BorderLayout.CENTER); 
+    }
+
+    /* 
+     * Methods for changing displayed card 
+     */ 
+    
+    public void showCard(String card) { 
+        CardLayout cl = (CardLayout)(mainPanel.getLayout());
+        cl.show(mainPanel, card);
+    }
+
+    public void nextCard() { 
+        CardLayout cl = (CardLayout)(mainPanel.getLayout());
+        cl.next(mainPanel); 
+    }
+    
+    /* Advance clock by 1 minute and redisplay. */
+    public void tick(int n) {
+        clock.tick(n);
+        clock.repaint();
+    }
+
+    /* Show the Clock */ 
+    public void showClock() { 
+        clock.showClock = true; 
+        clock.showFullClock = false; 
+        clock.repaint(); 
+    } 
+
+    /* Show the Fixation Point */ 
+    public void showFixationPoint() { 
+        clock.showClock = false; 
+        clock.showFullClock = false; 
+        clock.repaint(); 
+    } 
+    
+    /* Run clock for r revolutions at a rate of 1 minute (6 degrees)
+     * every n milliseconds. 
+     */
+    public void runClock(int r, long n) {
+        clock.reset(); 
+        showClock(); 
+        for (int i = 0; i < (60 * r); i++) {
+            try { Thread.sleep (n); } catch (InterruptedException e) {}
+               clock.tick(1);
+               clock.repaint();
+        }
+        try { Thread.sleep (1000); } catch (InterruptedException e) {}
+        showFixationPoint(); 
+    }
+    
+    /* Run method for this thread */
+    public void run() { 
+
+        //showFixationPoint(); 
+        clock.reset(); 
+        showClock(); 
+
+        int clockUnits = exp.getClockUnits(); 
+        int numUnits = exp.getNumUnits(); 
+        long tatum = exp.getCurrentBlock().getTatum(); 
+        long tatumInMilliseconds = tatum / 1000; 
+        int nMinutes = 60 / numUnits; 
+        
+        ArrayList onsets = exp.getCurrentBlock().getOnsets(); 
+        ArrayList probes = exp.getCurrentBlock().getProbePositions(); 
+        ArrayList clockStartTimes = exp.getCurrentBlock().getClockStartTimes(); 
+            
+        Iterator oi = onsets.iterator(); 
+        Iterator pi = probes.iterator(); 
+        Iterator ci = clockStartTimes.iterator(); 
+        
+        ProbeID probe = ProbeID.NOT_PROBE; 
+
+        long currentOnset = 0; 
+        long nextEventOnset = ((Long)(oi.next())).longValue();
+        long nextClockStartTime = ((Long)(ci.next())).longValue();
+
+        int clockUnit = 0; 
+        boolean clockTicking = false; 
+
+        while(oi.hasNext()) { 
+            //System.out.println("Ticking = " + clockTicking + 
+            //                 "; clockUnit = " + clockUnit); 
+            if (clockTicking == true && clockUnit == 0)
+                tick(nMinutes); 
+            
+            if (currentOnset == nextClockStartTime) { 
+                //new Thread(clock).start(); 
+                clock.reset(); 
+                showClock(); 
+                clockTicking = true; 
+                if (ci.hasNext())
+                    nextClockStartTime = ((Long)(ci.next())).longValue();
+            }
+
+            if (currentOnset == nextEventOnset) { 
+                // Manipulate display depending on probe identifier
+                switch (probe) { 
+                case NOT_PROBE: 
+                    // if (clock.showClock == true) 
+                    //    tick(nMinutes); 
+                    break; 
+                case START_CLOCK: 
+                    //clock.reset(); 
+                    //showClock(); 
+                    //tick(nMinutes); 
+                    break; 
+                case BEFORE_PROBE:
+                    //System.out.println("BEFORE_PROBE: acceptingResponses = " + 
+                    //                   acceptingResponses); 
+                    if (acceptingResponses == true) 
+                        exp.getCurrentBlock().addResponse(0, System.nanoTime());
+                    else 
+                        acceptingResponses = true; 
+                    //tick(nMinutes); 
+                    clockTicking = false; 
+                    break; 
+                case PROBE:
+                case PROBE_EX:
+                case PROBE_UNEX:
+                    //System.out.println("PROBE_{UN,}EX: acceptingResponses = " 
+                    // + acceptingResponses); 
+                    clock.showFullClock = false; 
+                    clock.repaint(); 
+                    break; 
+                case AFTER_PROBE: 
+                    //clock.showFullClock = false; 
+                    //clock.repaint(); 
+                    //showFixationPoint(); 
+                    break; 
+                default: 
+                    System.out.println("Unexpected probe id: " + probe); 
+                    break; 
+                }
+                // Update probe identifier and onset 
+                probe = (ProbeID)pi.next(); 
+                nextEventOnset =((Long)(oi.next())).longValue();
+            }
+            // sleep for a tatum 
+            try { Thread.sleep(tatumInMilliseconds); }
+            catch (InterruptedException e) {}
+            currentOnset += tatum; 
+            clockUnit = (clockUnit + 1) % clockUnits; 
+        }
+        showFixationPoint(); 
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/FileList.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,118 @@
+/*=============================================================================
+ * File:       FileList.java
+ * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
+ * Created:    <2007-11-30 10:30:38 marcusp>
+ * Time-stamp: <2010-05-10 11:14:37 marcusp>
+ *=============================================================================
+ */
+
+import java.util.ArrayList; 
+import java.util.Iterator;
+
+import java.io.*;
+
+import java.util.Random;
+
+public class FileList { 
+
+    private ArrayList files; 
+    private ArrayList probes; 
+    private int numFiles; 
+    private int currentFileIndex;
+
+    public int numFiles() { return numFiles; }
+    public void setFileIndex(int i) { currentFileIndex = i; }
+
+    public String currentFile() { 
+        if (currentFileIndex < numFiles)
+            return (String)files.get(currentFileIndex); 
+        else 
+            return null; 
+    }
+
+    public ArrayList currentProbes() { 
+        if (currentFileIndex < numFiles)
+            return (ArrayList)probes.get(currentFileIndex); 
+        else 
+            return null; 
+    }
+     
+    public void incrementFileIndex() { 
+        currentFileIndex = currentFileIndex + 1; 
+    }
+
+    private void randomise() { 
+        Random rgen = new Random();  
+        for (int i = 0; i < numFiles; i++) {
+            int randomPosition = rgen.nextInt(numFiles);
+            // System.out.println("i = " + i + "; rpos = " + randomPosition + 
+            //                    "; files = " + files.size() + 
+            //                    "; probes = " + probes.size());
+            
+            String tempFile = (String)files.get(i);
+            files.set(i, files.get(randomPosition)); 
+            files.set(randomPosition, tempFile); 
+            
+            ArrayList tempProbe = (ArrayList)probes.get(i); 
+            probes.set(i, probes.get(randomPosition));
+            probes.set(randomPosition, tempProbe); 
+        }
+    }
+    
+    private void importFileList(String filename) {
+        try { 
+            LineNumberReader lnr = 
+                new LineNumberReader(new FileReader(new File(filename)));
+            lnr.setLineNumber(1);
+            StreamTokenizer stok = new StreamTokenizer(lnr);
+            stok.parseNumbers();
+            stok.wordChars('_','_'); 
+            stok.wordChars('0','9');
+            //stok.wordChars('/','/');             
+            stok.eolIsSignificant(true);
+            stok.nextToken();
+            while (stok.ttype != StreamTokenizer.TT_EOF) {
+                int lineno = lnr.getLineNumber();
+                int i = 0;
+                ArrayList iprobes = new ArrayList(); 
+                while (stok.ttype != StreamTokenizer.TT_EOL) {
+                    int j = 0; 
+                    if (stok.ttype == StreamTokenizer.TT_NUMBER) { 
+                        iprobes.add((int)stok.nval - 1); // zero indexing
+                    } else if (stok.ttype == StreamTokenizer.TT_WORD) { 
+                        files.add(stok.sval);
+                    }
+                    stok.nextToken();     
+                }
+                i++; 
+                numFiles++; 
+                probes.add(iprobes); 
+                stok.nextToken();
+            }
+        } catch(Exception e) {
+            System.out.println("Exception: " + e);
+        }
+    }
+
+    public void print() { 
+        Iterator fi = files.iterator(); 
+        Iterator pi = probes.iterator(); 
+        while (fi.hasNext()) {
+            System.out.print((String)fi.next() + ": "); 
+            ArrayList ip = (ArrayList)pi.next();
+            Iterator ipi = ip.iterator(); 
+            while (ipi.hasNext())
+                System.out.print(ipi.next() + " "); 
+            System.out.print("\n");
+        }
+    }
+
+    public FileList(String filename) { 
+        files = new ArrayList(); 
+        probes = new ArrayList(); 
+        currentFileIndex = 0; 
+        this.importFileList(filename);
+        this.randomise(); 
+        this.print(); 
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/InstructionsPanel.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,90 @@
+/*=============================================================================
+ * File:       InstructionsPanel.java
+ * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
+ * Created:    <2007-12-05 10:57:31 marcusp>
+ * Time-stamp: <2007-12-14 16:29:55 marcusp>
+ *=============================================================================
+ */
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.text.*;
+import java.io.*;
+
+public class InstructionsPanel extends JPanel { 
+
+    /* invalid subject id */ 
+    public static final int INVALID_SUBJECT_ID = -1000; 
+
+    /* variables */ 
+    private ExperimentGui gui; 
+    private JTextField subjectIDField; 
+    private JButton nextButton = new JButton(); 
+    
+    /* accessors */ 
+    public JButton getNextButton() { return nextButton; }
+    
+    /* constructor */ 
+    public InstructionsPanel (ExperimentGui eg) { 
+
+        gui = eg; 
+        Experiment exp = gui.getExperiment(); 
+        
+        String file = exp.getInstructionsFile(); 
+        JScrollPane instructionsPane = createInstructionsPane(file); 
+
+        JLabel label = new JLabel("Participant ID: ");
+        subjectIDField = new JTextField(10);
+
+        nextButton = new JButton("Continue"); 
+
+        JPanel subjectIDPane = new JPanel(); 
+        subjectIDPane.add(label);
+        subjectIDPane.add(subjectIDField); 
+        subjectIDPane.add(nextButton); 
+
+        this.setLayout (new BorderLayout());
+        this.add(instructionsPane, BorderLayout.CENTER); 
+        this.add(subjectIDPane, BorderLayout.SOUTH);
+    }
+
+    /* methods */ 
+
+    public int getSubjectID() {
+        int subjID;
+        try {
+            subjID = Integer.parseInt(subjectIDField.getText());
+        } catch (NumberFormatException e) {
+            subjID = INVALID_SUBJECT_ID; 
+        }
+        return subjID; 
+    }
+    
+    private JScrollPane createInstructionsPane(String instructionsFile) {
+        
+        JEditorPane editorPane = new JEditorPane();
+
+        try { 
+            FileReader reader = new FileReader(new File(instructionsFile)); 
+            editorPane.setEditable(false);
+            editorPane.setContentType("text/html");
+            editorPane.read(reader, null);
+        } catch (Exception e) { 
+            System.out.println(e.getMessage());
+        }
+        
+        JScrollPane editorScrollPane = new JScrollPane(editorPane);
+        editorScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        editorScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+
+        editorScrollPane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Instructions"), BorderFactory.createEmptyBorder(5,5,5,5)));
+        editorScrollPane.setPreferredSize(new Dimension(300, 250));
+        editorScrollPane.setMinimumSize(new Dimension(10, 10));
+        return editorScrollPane;
+    }
+
+    public void addNextButtonListener(ActionListener al) { 
+        nextButton.addActionListener(al); 
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/InterBlockPanel.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,75 @@
+/*=============================================================================
+ * File:       InterBlockPanel.java
+ * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
+ * Created:    <2008-01-08 15:39:18 marcusp>
+ * Time-stamp: <2008-03-04 17:16:48 marcusp>
+ *=============================================================================
+ */
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+public class InterBlockPanel extends JPanel { 
+    
+    /* variables */ 
+    private JButton continueButton;
+    private JLabel message;
+    private String previousText; 
+    private String text = ""; 
+    private Experiment exp; 
+
+    /* accessors */ 
+    public JButton getContinueButton() { return continueButton; }
+    public void setText() { 
+        previousText = text; 
+        text = exp.getCurrentBlock().getLabel(); 
+    }
+
+    /* constructor */ 
+    public InterBlockPanel(Experiment e) { 
+        
+        exp = e; 
+        setText(); 
+
+        continueButton = new JButton("Continue"); 
+        JPanel continuePane = new JPanel(); 
+        continuePane.add(continueButton); 
+
+        message = new JLabel(); 
+        message.setHorizontalAlignment(JLabel.CENTER);
+        updateMessageDisplay(); 
+        JPanel messagePane = new JPanel(new FlowLayout(FlowLayout.CENTER)); 
+        messagePane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+        messagePane.add(message); 
+        
+        this.setLayout(new BorderLayout()); 
+        this.add(messagePane, BorderLayout.CENTER); 
+        this.add(continuePane, BorderLayout.SOUTH); 
+    }
+
+    public void updateMessageDisplay() { 
+        message.setText(formatMessage()); 
+    }
+    
+    private String formatMessage() { 
+        StringBuffer sb = new StringBuffer();
+        sb.append("<html><font size=\"+1\" face=\"sans\"><p align=center>");
+        sb.append("That is the end of the ");
+        sb.append(previousText); 
+        sb.append(" block. "); 
+        sb.append("<br>");
+        sb.append("If you have any questions, you may ask them now."); 
+        sb.append("<br>"); 
+        sb.append("Otherwise, press the button below to continue on to the ");
+        sb.append(text);
+        sb.append(" block."); 
+        sb.append("</p></html>");
+
+        return sb.toString();
+    }
+
+    public void addContinueButtonListener(ActionListener al) { 
+        continueButton.addActionListener(al); 
+    }
+} 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MelodyResults.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,169 @@
+/*=============================================================================
+ * File:       MelodyResults.java
+ * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
+ * Created:    <2007-02-14 11:28:27 marcusp>
+ * Time-stamp: <2010-05-10 11:38:52 marcusp>
+ *=============================================================================
+ */
+
+import java.util.ArrayList; 
+import java.util.Iterator;
+import java.io.*; 
+
+public class MelodyResults { 
+    
+    /* Probe positions, subject responses and times, note onsets and IOIs */ 
+    private ArrayList probes, responses, responseTimes, questions, answers, 
+        onsets, iois, durations, pitches; 
+    private String composition; 
+    private int subjectID; 
+
+    /* accessors */ 
+    public int getSubjectID() { return subjectID; }
+    public void setSubjectID(int id) { subjectID = id; } 
+
+    /* constructor */ 
+    public MelodyResults (String midifile, ArrayList pr, ArrayList o, 
+                          ArrayList i, ArrayList d, ArrayList pi) {
+        questions = new ArrayList(); 
+        answers = new ArrayList(); 
+        responses = new ArrayList(); 
+        responseTimes = new ArrayList();  
+        composition = compositionName(midifile);
+        probes = pr; 
+        onsets = o; 
+        iois = i; 
+        durations = d; 
+        pitches = pi; 
+
+//         Iterator oi = onsets.iterator(); 
+//         Iterator di = durations.iterator(); 
+//         Iterator ioii = iois.iterator(); 
+//         Iterator piti = pitches.iterator(); 
+//         while(oi.hasNext()) { 
+//             long onset = ((Long)oi.next()).longValue();
+//             long duration = ((Long)di.next()).longValue(); 
+//             long ioi = ((Long)ioii.next()).longValue();
+//             int pitch = ((Integer)piti.next()).intValue(); 
+//             System.out.println(onset + " " + duration + " " + ioi + " " + 
+//                                pitch + " "); 
+//        }
+    }
+    
+    private String compositionName(String midifile) { 
+        // assuming extension is '.mid' 
+        return midifile.substring(0, midifile.length() - 4); 
+    }
+
+    /* add a reponse i and time t (nanoseconds) */ 
+    public void addResponse(int i, long t) { 
+        //System.out.println("addResponse(" + i + ", " + t + ")"); 
+        responses.add(i); 
+        responseTimes.add(t);
+    }
+
+    /* add question */ 
+    public void addQuestion(String question) { 
+        questions.add(question); 
+    }
+
+    /* add response to question */ 
+    public void addAnswer(String answer) { 
+        answers.add(answer); 
+    }
+
+    public void writeResults(File outputFile, boolean header) { 
+	Writer writer = null;
+        try {
+            writer = new FileWriter (outputFile, true);
+        } catch (IOException e) {
+            System.out.println("Could not write file: " + outputFile.getPath());
+            return;
+        }
+        
+	try {
+            writeResults(writer, header); 
+            writer.close();
+        } catch (IOException e) {
+            System.out.println (e.getMessage());
+            return;
+        }
+    }
+    
+    private void writeResults(Writer w, boolean header) throws IOException { 
+        Iterator oi = onsets.iterator(); 
+        Iterator di = durations.iterator(); 
+        Iterator ioii = iois.iterator(); 
+        Iterator piti = pitches.iterator(); 
+            
+        Iterator pi = probes.iterator(); 
+        Iterator ri = responses.iterator(); 
+        Iterator rti = responseTimes.iterator(); 
+        int eventIndex = 1; 
+        
+        if (header) 
+            writeHeader(w); 
+        
+        String answerString = new String(""); 
+        Iterator ai = answers.iterator(); 
+        while(ai.hasNext())
+            answerString = answerString + (String)ai.next() + " ";
+        
+        while(oi.hasNext()) { 
+            long onset = ((Long)oi.next()).longValue();
+            long duration = ((Long)di.next()).longValue(); 
+            long ioi = ((Long)ioii.next()).longValue();
+            int pitch = ((Integer)piti.next()).intValue(); 
+
+            ProbeID p = (ProbeID)pi.next(); 
+            
+            int probe = 0; 
+            int response = 0;  
+            long responseTime = 0; 
+            
+            switch(p) { 
+            case NOT_PROBE: 
+            case START_CLOCK:
+            case BEFORE_PROBE: 
+            case AFTER_PROBE: 
+                break; 
+            case PROBE:
+            case PROBE_EX: 
+            case PROBE_UNEX:
+                if (p == ProbeID.PROBE_UNEX) 
+                    probe = 1; 
+                else if (p == ProbeID.PROBE_EX)
+                    probe = 2; 
+                else if (p == ProbeID.PROBE)
+                    probe = 1;
+                
+                if (ri.hasNext())
+                    response = ((Integer)ri.next()).intValue();
+                if (rti.hasNext()) { 
+                    responseTime = ((Long)rti.next()).longValue();
+                }
+                break; 
+            default: 
+                System.out.println("Unexpected probe id: " + p); 
+                break; 
+            }
+            w.write(subjectID + " " + composition + " " + 
+                    eventIndex + " " + 
+                    onset + " " + duration + " " + ioi + " " + 
+                    pitch + " " + 
+                    probe + " " + response + " " + responseTime + " " + 
+                    answerString + "\n"); 
+            eventIndex++; 
+        }
+    }
+    
+    private void writeHeader(Writer w) throws IOException {
+        w.write("subject melody note " + 
+                "onset duration ioi pitch " + 
+                "probe response time"); 
+        Iterator qi = questions.iterator(); 
+        while(qi.hasNext())
+            w.write(" " + (String)qi.next());
+        w.write("\n"); 
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MidiPlayer.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,304 @@
+/*=============================================================================
+ * File:       MidiPlayer.java
+ * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
+ * Created:    <2007-02-14 12:13:56 marcusp>
+ * Time-stamp: <2008-10-20 16:52:05 marcusp>
+ *=============================================================================
+ */
+
+/* 
+ * Based on: 
+ *   http://www.jsresources.org/examples/SimpleMidiPlayer.html
+ *   http://www.jsresources.org/examples/DumpSequence.html
+ */ 
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList; 
+
+import javax.sound.midi.MidiSystem;
+import javax.sound.midi.MidiDevice; 
+import javax.sound.midi.InvalidMidiDataException;
+import javax.sound.midi.MidiUnavailableException;
+import javax.sound.midi.MetaEventListener;
+import javax.sound.midi.Sequence;
+import javax.sound.midi.Sequencer;
+import javax.sound.midi.Synthesizer;
+import javax.sound.midi.MidiChannel;
+import javax.sound.midi.Receiver;
+import javax.sound.midi.Transmitter;
+import javax.sound.midi.Track;
+import javax.sound.midi.MidiEvent;
+import javax.sound.midi.MidiMessage;
+import javax.sound.midi.ShortMessage;
+import javax.sound.midi.MetaMessage;
+import javax.sound.midi.SysexMessage;
+import javax.sound.midi.MetaMessage;
+
+public class MidiPlayer { 
+
+    /* variables */ 
+    private Sequencer   sequencer = null;
+    private Synthesizer synthesizer = null;
+    private Sequence sequence = null;
+    private ArrayList onsets, offsets, pitches = null; 
+    private int midiDevice = 0; /* 4 for usb midi device */ 
+
+    /* accessors */ 
+    public Sequencer getSequencer() { return sequencer; }
+
+    /* Constructor */ 
+    public MidiPlayer(String path) { 
+        File midiFile = new File(path);
+
+        // Get sequence 
+        try { sequence = MidiSystem.getSequence(midiFile); }
+        catch (InvalidMidiDataException e) {
+            e.printStackTrace();
+            System.exit(1);
+        }
+        catch (IOException e) {
+            e.printStackTrace();
+            System.exit(1);
+        }
+        // Get sequencer  
+        try { sequencer = MidiSystem.getSequencer(); }
+        catch (MidiUnavailableException e) {
+            e.printStackTrace();
+            System.exit(1);
+        }
+        //sequencer.setTempoInBPM(bpm); 
+        // Workaround bug in JDK 
+//         sequencer.addMetaEventListener(new MetaEventListener() {
+//                 public void meta(MetaMessage event) {
+//                     if (event.getType() == 47) {
+//                         sequencer.close();
+//                         if (synthesizer != null) {
+//                             synthesizer.close();
+//                         }
+//                     }
+//                 }
+//             });
+        // Open sequencer 
+        try { sequencer.open(); }
+        catch (MidiUnavailableException e) {
+            e.printStackTrace();
+            System.exit(1);
+        }
+        // Assign sequence to sequencer 
+        try { sequencer.setSequence(sequence); }
+        catch (InvalidMidiDataException e) {
+            e.printStackTrace();
+            System.exit(1);
+        }
+        // Set up MIDI output for sequence 
+        try {
+            MidiDevice.Info msinfo[] = MidiSystem.getMidiDeviceInfo(); 
+            for(int i = 0; i < msinfo.length; i++) { 
+                MidiDevice.Info m = msinfo[i]; 
+//                 System.out.println("Name: " + m.getName() + 
+//                                    "; Vendor: " + m.getVendor() + 
+//                                    "; Version: " + m.getVersion()); 
+            }
+            // synthesizer = MidiSystem.getSynthesizer();
+            MidiDevice synth = MidiSystem.getMidiDevice(msinfo[midiDevice]); 
+
+            // Change the patch 
+            //             MidiChannel channels[] = synthesizer.getChannels(); 
+            //             for (int i = 0; i < channels.length; i++) 
+            //                 channels[i].programChange(65);
+
+            synth.open();
+            Receiver synthReceiver = synth.getReceiver();
+            Transmitter	seqTransmitter = sequencer.getTransmitter();
+            seqTransmitter.setReceiver(synthReceiver);
+
+        }
+        catch (MidiUnavailableException e) {
+            e.printStackTrace();
+        }
+
+        // compute data from the MIDI file 
+        onsets = computeOnsets(); 
+        offsets = computeOffsets(); 
+        pitches = computePitches(); 
+
+        //String divisionType; 
+        //if (sequence.getDivisionType() == sequence.PPQ)
+        //    divisionType = "ppq"; 
+        //else 
+        //    divisionType = "smpte"; 
+        //System.out.println("division type = " + divisionType + 
+        //                   "; resolution = " + sequence.getResolution()); 
+    }
+
+    /* number of microseconds per MIDI tick */ 
+    private double microsecondsPerTick() { 
+        double seqTickLength = (float)sequence.getTickLength(); 
+        double seqMicrosecondLength = (float)sequence.getMicrosecondLength(); 
+        double microsecondsPerTick = seqMicrosecondLength / seqTickLength; 
+        //System.out.println("seqTickLength = " + seqTickLength); 
+        //System.out.println("seqMicrosecondLength = " + seqMicrosecondLength); 
+        //System.out.println("microsecondsPerTick = " + microsecondsPerTick); 
+        return microsecondsPerTick; 
+    }
+    
+    private long ticksToMicroseconds(long tick) { 
+        double microsecondsPerTick = microsecondsPerTick(); 
+        double seconds = (double)tick * microsecondsPerTick; 
+        
+        return (long)(10 * Math.floor(seconds * 0.1));
+    }
+
+    /* compute a list of note onset times (microseconds) */ 
+    private ArrayList computeOnsets() {
+        ArrayList ons = new ArrayList(); 
+        Track[]	tracks = sequence.getTracks();
+        
+        for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
+            Track track = tracks[nTrack];
+            for (int nEvent = 0; nEvent < track.size(); nEvent++) {
+                MidiEvent event = track.get(nEvent);
+                MidiMessage message = event.getMessage();
+                if (message instanceof ShortMessage && 
+                    ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON) {
+//                               System.out.println("onset in ticks = " + event.getTick()+ 
+//                                                  "; onset in microseconds = " + 
+//                                                  ticksToMicroseconds(event.getTick()));
+                              ons.add(ticksToMicroseconds(event.getTick()));
+                }
+            }
+        } 
+        return ons; 
+    }
+
+    /* compute a list of note offset times (microseconds) */ 
+    private ArrayList computeOffsets() {
+        ArrayList offs = new ArrayList(); 
+        Track[]	tracks = sequence.getTracks();
+        
+        for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
+            Track track = tracks[nTrack];
+            for (int nEvent = 0; nEvent < track.size(); nEvent++) {
+                MidiEvent event = track.get(nEvent);
+                MidiMessage message = event.getMessage();
+                if (message instanceof ShortMessage && 
+                    ((ShortMessage)message).getCommand() == ShortMessage.NOTE_OFF) {
+                    //System.out.println("offset in ticks = " + event.getTick()+ 
+                    //                   "; microsecondsPerTick = " + microsecondsPerTick +
+                    //                   "; offset in microseconds = " + 
+                    //                   (float)event.getTick() * microsecondsPerTick);
+                    offs.add(ticksToMicroseconds(event.getTick()));
+                }
+            }
+        }
+        return offs; 
+    }
+
+    /* compute a list of note pitches */ 
+    private ArrayList computePitches() {
+        ArrayList pit = new ArrayList(); 
+        Track[]	tracks = sequence.getTracks();
+        
+        for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
+            Track track = tracks[nTrack];
+            for (int nEvent = 0; nEvent < track.size(); nEvent++) {
+                MidiEvent event = track.get(nEvent);
+                MidiMessage message = event.getMessage();
+                if (message instanceof ShortMessage && 
+                    ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON)
+                    pit.add(((ShortMessage)message).getData1()); 
+            }
+        } 
+        return pit; 
+    }
+
+    /* return a list of note onset times (milliseconds) */ 
+    public ArrayList getOnsets() { return onsets; } 
+
+    /* return a list of note offset times (milliseconds) */ 
+    public ArrayList getOffsets() { return offsets; } 
+
+    /* return a list of note pitches */ 
+    public ArrayList getPitches() { return pitches; } 
+    
+    /* return a list of note durations (microseconds) */ 
+    public ArrayList getDurations() { 
+        Object[] ons = onsets.toArray(); 
+        Object[] offs = offsets.toArray(); 
+        
+        ArrayList durations = new ArrayList(); 
+        
+        for(int i = 0; i < ons.length; i++) { 
+            durations.add(((Long)offs[i]).longValue() - 
+                          ((Long)ons[i]).longValue());
+        } 
+        return durations; 
+    } 
+
+    /* return a list of inter-onset intervals (microseconds) */ 
+    public ArrayList getInterOnsetIntervals() { 
+        Object[] ons = onsets.toArray(); 
+
+        ArrayList iois = new ArrayList(); 
+        long firstIOI = 0; 
+        iois.add(firstIOI); // IOI of first note is zero 
+        
+        for(int i = 1; i < ons.length; i++) { 
+            iois.add(((Long)ons[i]).longValue() - 
+                     ((Long)ons[i-1]).longValue());
+        } 
+        return iois; 
+    } 
+
+    /* return the length of a rest preceding a note */ 
+    public ArrayList getDeltaST() { 
+        Object[] durs = getDurations().toArray(); 
+        Object[] iois = getInterOnsetIntervals().toArray(); 
+        
+        ArrayList deltaST = new ArrayList(); 
+        long firstDeltaST = 0; 
+        deltaST.add(firstDeltaST); // deltaST of first note is zero 
+
+        for(int i = 1; i < durs.length; i++) { 
+            deltaST.add(((Long)durs[i-1]).longValue() - 
+                        ((Long)iois[i]).longValue());
+        }
+        return deltaST; 
+    } 
+    
+    /* return the tatum of the midi file */ 
+    public long getTatum() { 
+        Object[] durs = getDurations().toArray(); 
+        Object[] rests = getDeltaST().toArray(); 
+        long tatum = -1; 
+
+        for(int i = 0; i < durs.length; i++) { 
+            long dur = ((Long)durs[i]).longValue(); 
+            long rest = ((Long)rests[i]).longValue(); 
+            long min = dur; 
+            if (rest != 0)
+                min = Math.min(dur, rest);
+            
+            if (tatum < 0)
+                tatum = dur; 
+            else if (min < tatum) 
+                tatum = min; 
+        }
+        return tatum; 
+    }
+
+    /* return length of sequence in microseconds */ 
+    public long getLength() { 
+        return sequence.getMicrosecondLength(); 
+    } 
+
+    /* play the midi file */ 
+    public void play() { 
+        sequencer.start();
+    } 
+
+    public void stop() { 
+        sequencer.close(); 
+    } 
+} 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProbeID.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,11 @@
+/*=============================================================================
+ * File:       ProbeID.java
+ * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
+ * Created:    <2007-03-01 13:39:43 marcusp>
+ * Time-stamp: <2010-05-10 11:27:35 marcusp>
+ *=============================================================================
+ */
+
+public enum ProbeID { 
+    NOT_PROBE, START_CLOCK, BEFORE_PROBE, PROBE_EX, PROBE_UNEX, AFTER_PROBE, PROBE
+} 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,65 @@
+AUTHOR
+
+Marcus Pearce <m.pearce@gold.ac.uk>
+
+
+USAGE 
+
+  java Experiment <multiple> <n> <scale length> <low anchor> <high anchor>
+
+where <multiple> and <n> are integers: the clock runs for <n> time
+units before a probed event where the time unit is a multiple of the
+tatum as specified by <multiple>. <scale length> is the number of
+levels of the rating scale and <low anchor> and <high anchor> are its
+high and low anchors respectively (e.g., "highly unexpected", "highly
+expected" with a scale length of 7). See runExperiment.bat for an example.
+
+To use in a study:
+
+1. put the relevant midi files in Data/Midi;
+
+2. edit pfilelist.txt and filelist.txt to contain the midi files
+played in the practice and main blocks respectively: put one file on
+each line followed by a list of note numbers to probe with the visual
+clock.
+
+3. edit Data/instructions.html if necessary.
+
+The results are written to files in the Results/ directory.
+
+
+FILES AND DIRECTORIES: 
+
+Data/: 
+  - instructions.html: the instructions file 
+  - pfilelist.txt: the list of midi filenames in the practice block 
+  - filelist.txt: the list of midi filenames in the main block 
+  - Midi/: the location of the midi files specified above
+
+Results/: 
+  - the results are written to files in this directory 
+
+Icons/: 
+  - contains icons used for the GUI
+
+
+DEVELOPMENT 
+
+The code is organised according to a MVC structure: 
+
+Model:      Experiment.java 
+             - FileList.java           
+             - ProbeID.java
+             - MidiPlayer.java
+             - MelodyResults.java   
+             - SubjectResults.java
+             - Block.java 
+
+View:       ExperimentGui.java 
+             - Clock.java
+             - InstructionsPanel.java
+             - StimulusPanel.java
+             - SubjectDataPanel.java
+             - InterBlockPanel.java
+
+Controller: ExperimentController.java 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/StimulusPanel.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,167 @@
+/*=============================================================================
+ * File:       StimulusPanel.java
+ * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
+ * Created:    <2007-12-05 11:24:00 marcusp>
+ * Time-stamp: <2010-05-18 11:12:14 marcusp>
+ *=============================================================================
+ */
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+public class StimulusPanel extends JPanel { 
+    
+    /* the Experiment */ 
+    private Experiment exp; 
+    private ExperimentGui gui; 
+
+    /* navigation buttons */ 
+    private JButton playButton, nextButton; 
+
+    /* The response buttons */ 
+    private JButton[] responseButton;
+
+    /* The song number label */ 
+    private JLabel songNumberLabel; 
+    
+    /* The questions about each melody */ 
+    private JComboBox q1Box; 
+    private JComboBox q2Box; 
+
+    /* accessors */ 
+    public JButton getPlayButton() { return playButton; }
+    public JButton getNextButton() { return nextButton; } 
+    public JButton[] getResponseButtons() { return responseButton; }
+    public JComboBox getQ1Box() { return q1Box; }
+    public JComboBox getQ2Box() { return q2Box; }
+    
+    /* constructor */ 
+    public StimulusPanel(ExperimentGui egui, Clock clock) { 
+
+        gui = egui; 
+        exp = gui.getExperiment(); 
+        responseButton = new JButton[exp.getScaleLength()];
+
+        // The display panel (Clock) 
+        JPanel displayPanel = new JPanel(); 
+        displayPanel.setBackground(Color.black); 
+        displayPanel.setBorder(BorderFactory.createRaisedBevelBorder());
+        displayPanel.setLayout(new GridBagLayout()); 
+        GridBagConstraints c = new GridBagConstraints();
+        c.fill = GridBagConstraints.HORIZONTAL;
+        c.gridx = 2;
+        c.gridy = 2;
+        displayPanel.add(clock, c); 
+        
+        // Add the response buttons 
+        JPanel responsePanel = new JPanel(); 
+        responsePanel.setLayout (new BorderLayout());
+        responsePanel.setBorder(BorderFactory.createRaisedBevelBorder());
+
+        JPanel anchorsPanel = new JPanel(); 
+        anchorsPanel.add(new JLabel(exp.getLowAnchor()), BorderLayout.WEST);
+        anchorsPanel.add(new JLabel("                           "), 
+                         BorderLayout.CENTER);
+        anchorsPanel.add(new JLabel(exp.getHighAnchor()), BorderLayout.EAST);
+        
+        JPanel ratingsPanel = new JPanel(); 
+        for (int i = 0; i < responseButton.length; i++) { 
+            responseButton[i] = new JButton(Integer.toString(i+1)); 
+            responseButton[i].setFocusPainted(false); 
+            ratingsPanel.add(responseButton[i]); 
+        }
+        responsePanel.add(ratingsPanel, BorderLayout.NORTH); 
+        responsePanel.add(anchorsPanel, BorderLayout.SOUTH); 
+
+        // Questions Panel 
+        JPanel questionsPanel = new JPanel();
+        GridLayout gl = new GridLayout(2,2); 
+        gl.setHgap(50); 
+        questionsPanel.setLayout(gl); 
+        
+        String[] q1BoxOptions = { "", "Yes", "No" }; 
+        q1Box = new JComboBox(q1BoxOptions); 
+        q1Box.setSelectedIndex(0); 
+        questionsPanel.add(new JLabel("Are you familiar with this melody?")); 
+        questionsPanel.add(q1Box); 
+
+        String[] q2BoxOptions = { "", "1", "2", "3", "4", "5"}; 
+        q2Box = new JComboBox(q2BoxOptions); 
+        q2Box.setSelectedIndex(0); 
+        questionsPanel.add(new JLabel("How pleasant do you find this melody (1 = very unpleasant, 5 = very pleasant)?")); 
+        questionsPanel.add(q2Box); 
+        
+        JPanel questionsPanel2 = new JPanel(); 
+        questionsPanel2.setBorder(BorderFactory.createRaisedBevelBorder());
+        questionsPanel2.add(questionsPanel, BorderLayout.CENTER); 
+        
+        // Navigation Panel 
+        JPanel navPanel = new JPanel(); 
+        playButton = new JButton(new ImageIcon("Icons/Play24.gif"));
+        playButton.setText("Play"); 
+
+        nextButton = new JButton(new ImageIcon("Icons/StepForward24.gif"));
+        nextButton.setText("Play next melody"); 
+        
+        songNumberLabel = new JLabel(""); 
+        setSongNumberText(); 
+        navPanel.add(songNumberLabel); 
+        //navPanel.add(playButton); 
+        navPanel.add(nextButton); 
+        navPanel.setBorder(BorderFactory.createRaisedBevelBorder());
+                
+        // Add it all 
+        JPanel southPanel = new JPanel(); 
+        southPanel.setLayout (new BorderLayout());
+        southPanel.add(responsePanel, BorderLayout.NORTH); 
+        southPanel.add(questionsPanel2, BorderLayout.CENTER); 
+        
+        this.setLayout (new BorderLayout());
+        this.add(navPanel, BorderLayout.NORTH); 
+        this.add(displayPanel, BorderLayout.CENTER); 
+        this.add(southPanel, BorderLayout.SOUTH); 
+    }
+
+    public void setSongNumberText() { 
+        String m =
+            "Block " + 
+            exp.getCurrentBlockID() + 
+            ", Melody " + 
+            Integer.toString(exp.getCurrentBlock().getMelodyNumber()) + 
+            ": "; 
+        songNumberLabel.setText(m); 
+    }
+
+    public void defaultAnswers() { 
+        q1Box.setSelectedIndex(0); 
+        q2Box.setSelectedIndex(0); 
+    }
+
+    public boolean unansweredQuestions() { 
+        if (q1Box.getSelectedItem().equals("") ||
+            q2Box.getSelectedItem().equals("")
+            )
+            return true; 
+        else 
+            return false; 
+    }
+
+    public void addAllListeners(ActionListener al) { 
+        playButton.addActionListener(al);
+        nextButton.addActionListener(al);
+               
+        for (int i = 0; i < responseButton.length; i++) { 
+            responseButton[i].addActionListener(al); 
+        }
+    }
+
+    public void addAllKeyListeners(KeyListener al) { 
+        playButton.addKeyListener(al);
+        nextButton.addKeyListener(al);
+               
+        for (int i = 0; i < responseButton.length; i++) { 
+            responseButton[i].addKeyListener(al); 
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SubjectDataPanel.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,180 @@
+/*=============================================================================
+ * File:       SubjectDataPanel.java
+ * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
+ * Created:    <2007-02-14 11:28:27 marcusp>
+ * Time-stamp: <2008-03-11 18:09:47 marcusp>
+ *=============================================================================
+ */
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.ArrayList; 
+
+public class SubjectDataPanel extends JPanel { 
+
+    /* variables */ 
+    private JTextField ageField, trainingField, topfield, instrumentField, 
+        participationField, listeningField, nationalityField; 
+    private JComboBox sexBox, handBox, hearingBox, instrumentBox, trainingBox, 
+        ethnicityBox;
+    private JButton finishButton; 
+
+    private SubjectResults results; 
+
+    /* accessors */ 
+    public JButton getFinishButton() { return finishButton; }
+
+    /* constructor */ 
+    public SubjectDataPanel(ExperimentGui gui, SubjectResults sr) { 
+	
+	results = sr; 
+
+	JPanel questionsPanel = new JPanel(); 
+	questionsPanel.setLayout(new GridLayout(12,1)); 
+	questionsPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(""),BorderFactory.createEmptyBorder(5,5,5,5)), questionsPanel.getBorder()));
+	
+	String[] yesNoOptions = { "", "Yes", "No" };
+
+        // Age 
+	ageField = new JTextField(10);
+	questionsPanel.add(new JLabel("Age: ")); 
+	questionsPanel.add(ageField); 
+
+        // Gender 
+	String[] sexBoxOptions = { "", "Male", "Female" }; 
+	sexBox = new JComboBox(sexBoxOptions); 
+	sexBox.setSelectedIndex(0); 
+	questionsPanel.add(new JLabel("Gender: ")); 
+	questionsPanel.add(sexBox); 
+
+        // Ethnicity 
+	String[] ethnicityBoxOptions = 
+            { "", "Asian", "Black", "Chinese", "Mixed", "Other", "White", 
+              "Undisclosed" }; 
+	ethnicityBox = new JComboBox(ethnicityBoxOptions); 
+	questionsPanel.add(new JLabel("Ethnicity: ")); 
+	questionsPanel.add(ethnicityBox); 
+        
+        // Nationality 
+	nationalityField = new JTextField(10);
+	questionsPanel.add(new JLabel("Nationality: ")); 
+	questionsPanel.add(nationalityField); 
+
+        // Handedness 
+	String[] handBoxOptions = { "", "Right-handed", "Left-handed" };
+	handBox = new JComboBox(handBoxOptions); 
+	handBox.setSelectedIndex(0); 
+	questionsPanel.add(new JLabel("Handedness: ")); 
+	questionsPanel.add(handBox); 
+
+        // Hearing 
+        hearingBox = new JComboBox(yesNoOptions); 
+        questionsPanel.add(new JLabel("Do you have any hearing difficulties?")); 
+	questionsPanel.add(hearingBox); 
+
+        // Instrument / Sing
+        instrumentBox = new JComboBox(yesNoOptions); 
+	instrumentField = new JTextField(10); 
+	
+        questionsPanel.add(new JLabel("Do you play an instrument/sing?")); 
+	questionsPanel.add(instrumentBox); 
+
+	questionsPanel.add(new JLabel("If so, for how many years have you played/sung? ")); 
+	questionsPanel.add(instrumentField); 
+        
+        // Musical Training
+        trainingBox = new JComboBox(yesNoOptions); 
+	trainingField = new JTextField(10); 
+
+	questionsPanel.add(new JLabel("Have you had formal musical training?")); 
+        questionsPanel.add(trainingBox); 
+
+        questionsPanel.add(new JLabel("If so, for how many years?")); 
+	questionsPanel.add(trainingField); 
+        
+        // Musical Participation 
+	participationField = new JTextField(10); 
+
+	questionsPanel.add(new JLabel("How many hours a week do you spend practising/participating in music?")); 
+        questionsPanel.add(participationField); 
+
+        // Musical Listening 
+        listeningField = new JTextField(10); 
+	questionsPanel.add(new JLabel("How many hours per day do you spend listening to music?")); 
+        questionsPanel.add(listeningField); 
+        
+        // Put it all together 
+        JPanel finishPanel = new JPanel(); 
+	finishButton = new JButton("Finish."); 
+        finishPanel.add(finishButton); 
+
+        JPanel topPanel = new JPanel(); 
+        topPanel.add(new JLabel("Please answer the following questions:"), 
+                     BorderLayout.NORTH); 
+        
+        JPanel questionsPanel2 = new JPanel(); 
+        questionsPanel2.setLayout(new BorderLayout()); 
+        questionsPanel2.add(questionsPanel, BorderLayout.NORTH); 
+
+	//getRootPane().setDefaultButton(finishButton);
+        this.setLayout (new BorderLayout());
+        add(topPanel, BorderLayout.NORTH); 
+	add(questionsPanel2,BorderLayout.CENTER); 
+	add(finishPanel,BorderLayout.SOUTH); 
+    } 
+
+    public void storeData() { 
+	ArrayList subjectData = new ArrayList();
+	String[] id = {"ID", Integer.toString(results.getSubjectID())}; 
+	subjectData.add(id);        
+	String[] age = {"Age",ageField.getText()}; 
+	subjectData.add(age);
+	String[] sex = {"Gender",(String)sexBox.getSelectedItem()}; 
+	subjectData.add(sex);
+	String[] hand = {"Hand",(String)handBox.getSelectedItem()}; 
+	subjectData.add(hand);
+	String[] ethnicity = {"Ethnic",(String)ethnicityBox.getSelectedItem()}; 
+	subjectData.add(ethnicity);
+	String[] nationality = {"Nationality",nationalityField.getText()}; 
+	subjectData.add(nationality); 
+	String[] hear = {"HearingDiff", (String)hearingBox.getSelectedItem()}; 
+	subjectData.add(hear); 
+	String[] instrument1 = 
+            {"Instrument", (String)instrumentBox.getSelectedItem()}; 
+	subjectData.add(instrument1); 
+	String[] instrument2 = {"InstrumentYears",instrumentField.getText()}; 
+	subjectData.add(instrument2); 
+	String[] training1 = {"Training", (String)trainingBox.getSelectedItem()};
+	subjectData.add(training1); 
+	String[] training2 = {"TrainingYears",trainingField.getText()}; 
+	subjectData.add(training2); 
+	String[] participation = {"Participation",participationField.getText()}; 
+	subjectData.add(participation); 
+	String[] listening = {"Listening",listeningField.getText()}; 
+	subjectData.add(listening); 
+	
+        results.setSubjectData(subjectData); 
+    } 
+
+    public boolean allDataEntered () { 
+        if (ageField.getText().equals("") || 
+            sexBox.getSelectedItem().equals("") ||
+            handBox.getSelectedItem().equals("") ||
+            ethnicityBox.getSelectedItem().equals("") || 
+            nationalityField.getText().equals("") || 
+            hearingBox.getSelectedItem().equals("") ||
+            instrumentBox.getSelectedItem().equals("") ||
+            instrumentField.getText().equals("") ||
+            trainingBox.getSelectedItem().equals("") ||
+            trainingField.getText().equals("") ||
+            participationField.getText().equals("") ||
+            listeningField.getText().equals(""))
+            return false; 
+        else return true; 
+    }
+
+    public void addFinishButtonListener(ActionListener al) { 
+        finishButton.addActionListener(al); 
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SubjectResults.java	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,77 @@
+/*=============================================================================
+ * File:       SubjectResults.java
+ * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
+ * Created:    <2007-12-12 10:44:12 marcusp>
+ * Time-stamp: <2007-12-17 11:49:35 marcusp>
+ *=============================================================================
+ */
+
+import java.util.ArrayList; 
+import java.util.Iterator;
+import java.io.*; 
+
+public class SubjectResults { 
+
+    /* variables */ 
+    private int subjectID; 
+    private File outputFile, subjectDataFile; 
+    private ArrayList results, subjectData; 
+    private Experiment exp; 
+    
+    /* accessors */ 
+    public int getSubjectID() { return subjectID; }
+    public void setSubjectID(int id) { subjectID = id; } 
+    public void setSubjectData (ArrayList sd) { subjectData = sd; } 
+    public void setOutputFile (int id) {
+	outputFile = new File(exp.RESULTS_DIRECTORY + Integer.toString(id) + 
+                              exp.RESULTS_EXTENSION); 
+    }
+
+    /* constructor */ 
+    public SubjectResults(Experiment e) { 
+        exp = e; 
+        subjectDataFile = new File(exp.SUBJECT_RESULTS_FILE); 
+        results = new ArrayList(); 
+        subjectData = new ArrayList(); 
+    }
+
+    /* methods */ 
+    public void addResult(MelodyResults mr) { 
+        results.add(mr); 
+    }
+
+    public void writeResults() { 
+        Iterator ri = results.iterator(); 
+        boolean header = true; 
+        while (ri.hasNext()) { 
+            MelodyResults mResult = (MelodyResults)ri.next(); 
+            mResult.writeResults(outputFile, header); 
+            if (header) 
+                header = false; 
+        }
+    }
+
+    public void writeSubjectData () {
+        Writer writer = null;
+        try {
+            writer = new FileWriter (subjectDataFile, true);
+        } catch (IOException e) {
+            System.out.println("Could not write file: " + outputFile.getPath());
+            return;
+        }
+        
+        try {
+            Iterator sji = subjectData.iterator(); 
+            while(sji.hasNext()) { 
+                String[] data = (String[])sji.next(); 
+                writer.write(data[1] + " ");
+            }
+            writer.write("\n"); 
+            writer.close(); 
+            
+        } catch (IOException e) {
+            System.out.println (e.getMessage());
+            return;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runExperiment.bat	Tue May 18 11:37:10 2010 +0100
@@ -0,0 +1,1 @@
+java Experiment 1 4 7 "Highly unexpected" "Highly expected"