import java.io.File; import java.io.FileNotFoundException; import java.io.PrintStream; import java.util.Scanner; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import javax.sound.midi.MidiDevice; import javax.sound.midi.MidiMessage; import javax.sound.midi.MidiSystem; import javax.sound.midi.MidiUnavailableException; import javax.sound.midi.Receiver; import javax.sound.midi.ShortMessage; // A version of RecordThatTune that also takes input from a MIDI keyboard. public class RecordThatTuneMidi implements Receiver { // A constant that maps MIDI pitches to GuitarHero pitches. A pitch of 0 in // GuitarHero is concert A, which is 69 in Midi. public static final int MIDI_PITCH_TO_PITCH = -69; // The guitar to play notes on. private Guitar guitar; // A queue of events to get pitches from, thread safe so that multiple // sources can add to it. private BlockingQueue events; // Output stream to write music to. private PrintStream output; // Returns the key for pitch or null if it's invalid. private static Character pitchToKey(int pitch) { int index = pitch + 24; if (index >= 0 && index < Guitar37.KEYBOARD.length()) { return Guitar37.KEYBOARD.charAt(pitch + 24); } else { return null; } } // Close this receiver. @Override public void close() { // Do nothing. } // Sends a MIDI message for this receiver to proccess with the given // message and timeStamp. @Override public void send(MidiMessage message, long timeStamp) { // The way my MIDI keyboard seems to work is that on release it // sends another NOTE_ON message with volume 0, so this check means // that it will only play on the first NOTE_ON message. if (message.getStatus() == ShortMessage.NOTE_ON && message.getLength() >= 3 && message.getMessage()[2] > 0) { Character key = pitchToKey( message.getMessage()[1] + MIDI_PITCH_TO_PITCH); if (key != null) { events.add(key); } else { System.err.println("Bad pitch: " + message.getMessage()[1]); } } } // Construct a recorder that will play notes on the given guitar and write // the song data to output. public RecordThatTuneMidi(Guitar guitar, PrintStream output) { this.guitar = guitar; events = new LinkedBlockingQueue(); this.output = output; } // Run the main program, that is, wait for events and play them on the // guitar as they come up. public void processEvents() { boolean done = false; int oldTime = -1; // Special value to indicate no note played. char oldKey = 'a'; while (!done) { if (StdDraw.hasNextKeyTyped()) { events.add(StdDraw.nextKeyTyped()); } Character key = events.poll(); while (key != null) { if (guitar.hasString(key)) { guitar.pluck(key); recordNote(output, guitar, oldTime, oldKey); oldKey = key; oldTime = guitar.time(); } else if (key == 's') { done = true; } else { System.out.println("bad key: " + key); } key = events.poll(); } StdAudio.play(guitar.sample()); guitar.tic(); } recordNote(output, guitar, oldTime, oldKey); } // Introduces the program to the user. public static void giveIntro() { System.out.println("This program allows you to record notes"); System.out.println("on a Guitar object and store it in an output"); System.out.println("file. Hit the 's' key to stop recording and"); System.out.println("then quit the application."); System.out.println(); } // Records the last note played (if any) and writes the information to the // given output file; assumes oldTime is -1 if there was no previous note. public static void recordNote(PrintStream output, Guitar g, int oldTime, int oldKey) { if (oldTime != -1) { int pitch = Guitar37.KEYBOARD.indexOf(oldKey) - 24; int tics = g.time() - oldTime; double duration = (double) tics / StdAudio.SAMPLE_RATE; output.println(pitch + " " + duration); } } // Prompts the user to select a MIDI device, and return the info of that // device if one was selected, or null if the user decides not to use a // device. public static MidiDevice.Info getUserDeviceSelection(Scanner console) { MidiDevice.Info[] info = MidiSystem.getMidiDeviceInfo(); if (info.length == 0) { System.err.println("No midi devices."); return null; } printDeviceSelection(info); int selection = getUserIntSelection(console, "Please choose a device. ", 1, info.length + 1); if (selection == info.length + 1) { return null; } else { return info[selection - 1]; } } // Prints a selection menu for info. private static void printDeviceSelection(MidiDevice.Info[] info) { for (int i = 1; i <= info.length; i++) { System.out.println(i + ". " + info[i - 1].getName() + ": " + info[i - 1].getDescription()); } System.out.println((info.length + 1) + ". No midi device"); } // Repeatedly prompts the user for a number until they give enter a valid // integer in the range [min, max]. private static int getUserIntSelection(Scanner console, String prompt, int min, int max) { System.out.print(prompt); boolean hasNumber = false; int number = 0; while (!hasNumber) { try { number = Integer.parseInt(console.nextLine()); if (min <= number && number <= max) { hasNumber = true; } else { System.out.print("Please enter a number from " + min + " to " + max + " "); } } catch (NumberFormatException e) { System.out.print("Please enter a valid number. "); } } return number; } // Sets up a guitar with 37 Strings that will play notes from MIDI devices // and a regular computer keyboard. public static void main(String[] args) { giveIntro(); Scanner console = new Scanner(System.in); System.out.print("Output file name? "); String fileName = console.nextLine(); try { PrintStream output = new PrintStream(new File(fileName)); MidiDevice.Info info = getUserDeviceSelection( new Scanner(System.in)); RecordThatTuneMidi player = new RecordThatTuneMidi(new Guitar37(), output); if (info != null) { try { MidiDevice device = MidiSystem.getMidiDevice(info); if (!device.isOpen()) { device.open(); } device.getTransmitter().setReceiver(player); } catch (MidiUnavailableException e) { System.err .println("Failed to get MIDI device " + info.getName()); } } player.processEvents(); } catch (FileNotFoundException e1) { System.err.println("Invalid file: " + fileName); } } }