/* UD Music Theory Test JApplet */ import java.applet.Applet; import java.awt.*; import java.awt.event.*; import javax.sound.midi.*; import java.io.*; import java.util.*; import java.net.*; import javax.swing.*; import javax.swing.border.*; import javax.swing.table.*; import javax.swing.event.*; import java.util.Vector; public class UdmtRhythmTest extends Applet implements ActionListener, Runnable { private Button setupButton, playButton, stopButton; private Label midiInComboLabel, midiOutComboLabel, answerLabel; private Choice imntCombo; private UdmtMidiOutQueue myUdmtMidiOutQueue; private UdmtPianoDisplay myUdmtPianoDisplay; private int prevQptr = -1; private int numEventsRecorded = 0; // These note names are presented to the user as the major scales to be played private String[] NoteNames = {"C","C#","D","Eb","E","F","F#","G","Ab","A","Bb","B"}; // This array determines how each of the above major scales is spelled: sharps or flats. // "7" is used to get the correct enharmonics for C# major (7 sharps) // "6" is used to get the correct enharmonics for F# major (6 sharps) private String[] ScaleType = {"S","7" ,"S","F" ,"S","F","6", "S","F", "S","F", "S"}; // These 2 arrays are used to print the notes the user plays based on // whether the requested scale is spelled with sharps or flats private String[] SharpNames = {"C ","C#","D ","D#","E ","F ","F#","G ","G#","A ","A#","B "}; private String[] CSharpMajNames = {"B#","C#","D ","D#","E ","E#","F#","G ","G#","A ","A#","B "}; private String[] FSharpMajNames = {"C ","C#","D ","D#","E ","E#","F#","G ","G#","A ","A#","B "}; private String[] FlatNames = {"C ","Db","D ","Eb","E ","F ","Gb","G ","Ab","A ","Bb","B "}; private String audioVerify = "verifymidi.au"; private String audioRhythmQues = "playrhythm.au"; private String audioAnalysis = "analysis.au"; private String[] audioCorrect = {"excellent.au","groovy.au","yougotit.au","doingreat.au","gimmemore.au"}; private String[] audioIncorrect = {"ut-oh.au","watchthatnote.au","whoops.au","didyouhearthat.au","whatwasthat.au"}; private String audioTryAgain = "playitagain.au"; private String audioCompleted = "completed.au"; private String audioPassed = "passed.au"; private String audioFailed = "failed.au"; final int middlec = 60; private String userStatus = "READY"; private boolean userFirstGuess = false; private int[] userTestQuestions; private long[] userTimes; private long userDelta; private long[] userOnDeltas, userOffDeltas, userSumDeltas; private int[] userVariancePct, userOnPct, userOffPct; private int userTestPhase, currentTestQuestion, currentScaleNote, currentUserErrors; private String userNotes = ""; private Rectangle rect; private Thread looper; private boolean running; private long runSeconds = 0; private int runCount; //--------------------------------------------------------------------------------------------- private int[][] noteVels; private int scalenum; private int[] MajorScale = { 0, 2, 4, 5, 7, 9, 11, 12 }; private int userNumCorrect = 0, userNumQues = 0, userPercent = 0; final int passingGrade = 65; private String[] audioQueue; private int[] audioDelays; private int audioQHead = 0; private int audioQTail = 0; private int audioWaitTime = 0; final int audioQueueLength = 100; //--------------------------------------------------------------------------------------------- private int[] beatPattern; // generated beats (up to 4 measures in 4/4 time) private int numBeatPatterns; private StatFrame statFrame; //--------------------------------------------------------------------------------------------- public void init() { myUdmtMidiOutQueue = new UdmtMidiOutQueue(); //Container content = getContentPane(); //content.setLayout(new BorderLayout()); Panel msgPanel = new Panel(); msgPanel.setLayout (new FlowLayout() ); msgPanel.add(new Label("Midi In Device : "+ myUdmtMidiOutQueue.getCurrMidiInDeviceName())); msgPanel.add(new Label(" ")); msgPanel.add(new Label("Midi Out Device : "+ myUdmtMidiOutQueue.getCurrMidiOutDeviceName())); msgPanel.add(new Label(" ")); setupButton = new Button("MIDI Setup"); setupButton.addActionListener(this); setupButton.setEnabled(false); msgPanel.add(setupButton); //imntCombo = new Choice(); //for (int ix=0; ix= maxQptr) { prevQptr = 0; } t = myUdmtMidiOutQueue.myMidiInReceiver.getMidiInQueueTime(prevQptr); c = myUdmtMidiOutQueue.myMidiInReceiver.getMidiInQueueChnl(prevQptr); n = myUdmtMidiOutQueue.myMidiInReceiver.getMidiInQueueNote(prevQptr); v = myUdmtMidiOutQueue.myMidiInReceiver.getMidiInQueueVel(prevQptr); userDelta=0; if (numEventsRecorded > 0) { userDelta = t - userTimes[numEventsRecorded-1]; ixDelta = (int)(numEventsRecorded-1)/2; if ((numEventsRecorded % 2) == 0) { userOffDeltas[ixDelta] = userDelta; userSumDeltas[ixDelta] = userOnDeltas[ixDelta] + userOffDeltas[ixDelta]; //System.out.print("IxDelta="+ixDelta+" On: "+userOnDeltas[ixDelta]); //System.out.println(" Off:"+userOffDeltas[ixDelta]+" Sum:"+userSumDeltas[ixDelta]); } else { userOnDeltas[ixDelta] = userDelta; } } //System.out.println(numEventsRecorded+": T="+t+" Delta="+userDelta); userTimes[numEventsRecorded] = t; numEventsRecorded++; if (numEventsRecorded >= 66) { numEventsRecorded = 0; if (userTestPhase == 2) { userTestPhase = 3; phaseProcessing(); } } //myUdmtPianoDisplay.setBanner("Number of Events Recorded from MIDI IN: "+numEventsRecorded); //keep track of all notes that are down now - this is used for the piano display noteVels[c][n] = v; } } } //--------------------------------------------------------------------------------------------- // Animation routines: //--------------------------------------------------------------------------------------------- public void start() { if(!running) { running = true; looper = new Thread(this); looper.start(); } } public void stop() { running = false; } public void run() { try { while(running) { repaint(); looper.sleep(50); frameNext(); } } catch(InterruptedException e) { running = false; } } public void update(Graphics g) { if(running) { if(rect == null) { rect = getBounds(); frameFirst(); } myUdmtPianoDisplay.paint(g); } } private void frameFirst() { myUdmtPianoDisplay.setBanner("Please verify that your MIDI Input is working correctly by playing notes on your piano keyboard and watching the display below."); queueAudio(audioVerify, 3); } //--------------------------------------------------------------------------------------------- private void frameNext() { viewMidiInQueue(); runCount++; if (audioWaitTime == 0) { if (audioQHead != audioQTail) { audioQTail++; if (audioQTail == audioQueue.length) { audioQTail = 0; } play(getCodeBase(), audioQueue[audioQTail]); audioWaitTime = audioDelays[audioQTail]; } // if } if (runCount >= 20) { runSeconds++; runCount = 0; // process audio queue if (audioWaitTime > 0) { audioWaitTime--; } } // if } // frameNext //--------------------------------------------------------------------------------------------- private void queueAudio(String audioFileName, int seconds) { audioQHead++; if (audioQHead == audioQueue.length) { audioQHead = 0; } if (audioQHead == audioQTail) // queue full - ignore new message { System.out.println("queueAudio: Audio Queue Is Full"); System.out.println("queueAudio: audioFileName = "+audioFileName); audioQHead--; // remove item from queue if queue is full if (audioQHead < 0) { audioQHead = audioQueue.length - 1; } } audioQueue[audioQHead] = audioFileName; audioDelays[audioQHead] = seconds; } //--------------------------------------------------------------------------------------------- private String pickAudio (String[] audioArray) { return( audioArray [ (int)(Math.random() * audioArray.length) ] ); } //--------------------------------------------------------------------------------------------- class UdmtPianoDisplay extends Panel { private String banner = " "; private String userTestScore = " "; private String userNotesPlayedCorrectly = " "; Image[] symbol; String[] symfile = {"treble-staff-tr.GIF","bass-staff-tr.GIF", "barline-tr.gif", "wholenote-tr.gif", "halfnote-tr.gif", "qtrnote-tr.gif", "8thnote-tr.gif", "wh-half-rest-tr.gif", "qtrrest-tr.gif", "8threst-tr.gif", "dot-tr.gif", "common-time-tr.gif"}; String[] symId = {"treble","bass","bar","wn","hn","qn","en","hr","qr","er","dot", "comtime"}; // these offsets have to be determined experimentally for each GIF. They are the values added to the // X and Y coordinates relative to the Treble staff position to make the shape fit in the bottom space // of the treble staff ("F"), or to properly align the symbol in its default position. int[] xOffset = {0, 5, 30, 30, 30, 30, 30, 30, 30, 30, 43, 35}; int[] yOffset = {0, 80, 27, 48, 27, 27, 27, 33, 27, 33, 52, 33}; int numImages; public UdmtPianoDisplay() { // load images numImages = symfile.length; symbol = new Image[numImages]; URL url = getCodeBase(); for (int ix=0; ix < numImages; ix++) { symbol[ ix ] = getImage (url, symfile[ix]); } MediaTracker mt = new MediaTracker (this); for (int ix=0; ix < numImages; ix++) { mt.addImage (symbol[ix], ix); } try { mt.waitForAll(); } catch (Exception e) { System.err.println(e); } // test to see if any files didn't load: for (int ix=0; ix < numImages; ix++) { if (symbol[ix].getHeight(this) < 0) { System.err.println ("Image file empty: "+symfile[ix]); } } } public void setBanner(String newbanner) { banner = newbanner; } public void setUserTestScore(String newscore) { userTestScore = newscore; } public void setUserNotesPlayedCorrectly(String notelist) { userNotesPlayedCorrectly = notelist; } public void drawSym (Graphics g, String symbolId, int x, int y) { for (int ix=0; ix < numImages; ix++) { if (symbolId == symId[ix]) { g.drawImage(symbol[ix], x + xOffset[ix], y + yOffset[ix], this); } } } public void renderNotation(Graphics g) { int trebleX = 35, trebleY = 65; g.setColor(Color.black); drawSym (g, "treble", trebleX, trebleY); drawSym (g, "comtime", trebleX, trebleY); drawSym (g, "bar", trebleX+206, trebleY); drawSym (g, "bar", trebleX+341, trebleY); drawSym (g, "bar", trebleX+476, trebleY); drawSym (g, "bar", trebleX+611, trebleY); drawSym (g, "bar", trebleX+661, trebleY); for (int i=0; i<8; i++) { drawSym (g, "en", trebleX+(i*15)+71, trebleY); drawSym (g, "en", trebleX+(i*15)+219, trebleY); drawSym (g, "en", trebleX+(i*15)+351, trebleY); drawSym (g, "en", trebleX+(i*15)+486, trebleY); } drawSym (g, "wn", trebleX+630, trebleY); //Sample code for rendering each musical symbol //drawSym (g, "wn", trebleX+10, trebleY); //drawSym (g, "hn", trebleX+30, trebleY); //drawSym (g, "qn", trebleX+50, trebleY); //drawSym (g, "en", trebleX+70, trebleY); //drawSym (g, "hr", trebleX+90, trebleY-3); // to make a whole rest //drawSym (g, "hr", trebleX+110, trebleY); //drawSym (g, "qr", trebleX+130, trebleY); //drawSym (g, "er", trebleX+150, trebleY); //drawSym (g, "hn", trebleX+170, trebleY); //drawSym (g, "dot",trebleX+170, trebleY); } public void paint(Graphics g) { g.setColor(Color.white); g.fillRect(30,80,740,100); g.setColor(Color.black); g.drawString(userTestScore,50,155); //g.drawString(userNotesPlayedCorrectly,50,140); g.drawString(banner,50,170); for (int n=21; n<=108; n++) { g.setColor(Color.white); for (int c=0; c<16; c++) { if (noteVels[c][n] != 0) { g.setColor(Color.red); } } g.fillRect( (n-21)*8+50, 200, 7, 70 ); } renderNotation(g); } //--------------------------------------------------------------------------------------------- } // END OF CLASS UdmtPianoDisplay //--------------------------------------------------------------------------------------------- class StatFrame extends JFrame { Vector statTable = new Vector(); DefaultListModel listModel = new DefaultListModel(); TableModel dataModel; JTable table; public StatFrame() { super("Playback Statistics"); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {statFrame = null;} }); JPanel p1 = new JPanel(new BorderLayout()); final String[] names = { "Note", "Variance", "On", "Off" }; dataModel = new AbstractTableModel() { public int getColumnCount() { return names.length; } public int getRowCount() { return statTable.size();} public Object getValueAt(int row, int col) { //System.out.println("getValueAt: r="+row+"c="+col); if (col == 0) { //System.out.println("return="+((StatData)statTable.get(row)).note); return ((StatData) statTable.get(row)).note; } else if (col == 1) { //System.out.println("return="+((StatData)statTable.get(row)).variance); return ((StatData) statTable.get(row)).variance; } else if (col == 2) { //System.out.println("return="+((StatData)statTable.get(row)).on); return ((StatData) statTable.get(row)).on; } else if (col == 3) { //System.out.println("return="+((StatData)statTable.get(row)).off); return ((StatData) statTable.get(row)).off; } return null; } public String getColumnName(int col) {return names[col]; } public Class getColumnClass(int c) { //System.out.println("getColumnClass: c="+c); return getValueAt(0, c).getClass(); } public boolean isCellEditable(int row, int col) { return false; } public void setValueAt(Object val, int row, int col) { if (col == 0) { ((StatData) statTable.get(row)).note = (String) val; } else if (col == 1) { ((StatData) statTable.get(row)).variance = (String) val; } else if (col == 2) { ((StatData) statTable.get(row)).on = (String) val; } else if (col == 3) { ((StatData) statTable.get(row)).off = (String) val; } } }; table = new JTable(dataModel); TableColumn col = table.getColumn("Note"); col.setMaxWidth(65); table.sizeColumnsToFit(0); JScrollPane scrollPane = new JScrollPane(table); EmptyBorder eb = new EmptyBorder(0,5,5,5); scrollPane.setBorder(new CompoundBorder(eb,new EtchedBorder())); getContentPane().add("Center", scrollPane); pack(); Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); int w = 360; int h = 200; setLocation(d.width/2 - w/2, d.height/2 - h/2); setSize(w, h); setVisible(true); } void addStatRow (String note, String variance, String on, String off) { statTable.add(new StatData( note, variance, on, off) ); table.tableChanged(new TableModelEvent(dataModel)); } class StatData extends Object { String note, variance, on, off; public StatData(String Anote, String Avariance, String Aon, String Aoff) { this.note = Anote; this.variance = Avariance; this.on = Aon; this.off = Aoff; //System.out.println("StatData called:"+Anote+Avariance+Aon+Aoff); } } // End class StatData } // End class StatFrame //--------------------------------------------------------------------------------------------- } // END OF CLASS UdmtRhythmTest //---------------------------------------------------------------------------------------------