Mercurial > hg > mep
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; + } + } +}