From 792fa79119050016b992f1947e93abdd17d0a6d3 Mon Sep 17 00:00:00 2001 From: Christopher Baines Date: Fri, 16 Sep 2011 12:07:52 +0100 Subject: Started working on midi stuff --- .../src/uk/ac/open/punchingbag/PunchingBagGUI.java | 6 +- .../open/punchingbag/examples/SimpleKeyboard.java | 209 ++++++++++++++++++++- 2 files changed, 206 insertions(+), 9 deletions(-) diff --git a/PunchingBag/src/uk/ac/open/punchingbag/PunchingBagGUI.java b/PunchingBag/src/uk/ac/open/punchingbag/PunchingBagGUI.java index 4acb2d0..e03c4e6 100644 --- a/PunchingBag/src/uk/ac/open/punchingbag/PunchingBagGUI.java +++ b/PunchingBag/src/uk/ac/open/punchingbag/PunchingBagGUI.java @@ -64,7 +64,7 @@ public class PunchingBagGUI implements MouseListener, MouseMotionListener, Debug, Menu }; - Mode currentMode = Mode.Menu; + Mode currentMode = Mode.Debug; JFrame frame; @@ -146,8 +146,8 @@ public class PunchingBagGUI implements MouseListener, MouseMotionListener, programOptionsPanel.add(new JButton("Debug")); frame.add(bagDebugingPanel); - frame.add(new DisplayPanel(), BorderLayout.CENTER); - frame.add(programOptionsPanel, BorderLayout.SOUTH); + //frame.add(new DisplayPanel(), BorderLayout.CENTER); + //frame.add(programOptionsPanel, BorderLayout.SOUTH); setMode(currentMode); diff --git a/PunchingBag/src/uk/ac/open/punchingbag/examples/SimpleKeyboard.java b/PunchingBag/src/uk/ac/open/punchingbag/examples/SimpleKeyboard.java index a8d32ff..2d91735 100644 --- a/PunchingBag/src/uk/ac/open/punchingbag/examples/SimpleKeyboard.java +++ b/PunchingBag/src/uk/ac/open/punchingbag/examples/SimpleKeyboard.java @@ -4,6 +4,17 @@ import java.awt.Color; import java.io.File; import java.io.IOException; +import javax.sound.midi.InvalidMidiDataException; +import javax.sound.midi.MetaEventListener; +import javax.sound.midi.MetaMessage; +import javax.sound.midi.MidiEvent; +import javax.sound.midi.MidiSystem; +import javax.sound.midi.MidiUnavailableException; +import javax.sound.midi.Sequence; +import javax.sound.midi.Sequencer; +import javax.sound.midi.ShortMessage; +import javax.sound.midi.Synthesizer; +import javax.sound.midi.Track; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.Clip; @@ -16,7 +27,7 @@ import uk.ac.open.punchingbag.Contact; import uk.ac.open.punchingbag.PunchingBag; public class SimpleKeyboard implements ButtonListener { - + String soundDir = System.getProperty("user.dir") + System.getProperty("file.separator"); @@ -24,13 +35,89 @@ public class SimpleKeyboard implements ButtonListener { new File(soundDir + "C5.wav"), new File(soundDir + "D5.wav"), new File(soundDir + "E5.wav"), new File(soundDir + "F5.wav"), new File(soundDir + "G5.wav") }; - + static PunchingBag bag = PunchingBag.getBag(); - public static void main(String[] args) { + public static final int DAMPER_PEDAL = 64; + + public static final int DAMPER_ON = 127; + + public static final int DAMPER_OFF = 0; + + public static final int END_OF_TRACK = 47; + + static final int[] offsets = { // add these amounts to the base value + // A B C D E F G + -4, -2, 0, 1, 3, 5, 7 }; + + public static void main(String[] args) throws InvalidMidiDataException, MidiUnavailableException, IOException { new SimpleKeyboard(); + + int instrument = 0; + int tempo = 120; + String filename = null; + + // Parse the options + // -i default 0, a piano. Allowed values: 0-127 + // -t default tempo is 120 quarter notes per minute + // -o save to a midi file instead of playing + int a = 0; + while (a < args.length) { + if (args[a].equals("-i")) { + instrument = Integer.parseInt(args[a + 1]); + a += 2; + } else if (args[a].equals("-t")) { + tempo = Integer.parseInt(args[a + 1]); + a += 2; + } else if (args[a].equals("-o")) { + filename = args[a + 1]; + a += 2; + } else + break; + } + + char[] notes = args[a].toCharArray(); + + // 16 ticks per quarter note. + Sequence sequence = new Sequence(Sequence.PPQ, 16); + + // Add the specified notes to the track + addTrack(sequence, instrument, tempo, notes); + + if (filename == null) { // no filename, so play the notes + // Set up the Sequencer and Synthesizer objects + Sequencer sequencer = MidiSystem.getSequencer(); + sequencer.open(); + Synthesizer synthesizer = MidiSystem.getSynthesizer(); + synthesizer.open(); + sequencer.getTransmitter().setReceiver(synthesizer.getReceiver()); + + // Specify the sequence to play, and the tempo to play it at + sequencer.setSequence(sequence); + sequencer.setTempoInBPM(tempo); + + // Let us know when it is done playing + sequencer.addMetaEventListener(new MetaEventListener() { + public void meta(MetaMessage m) { + // A message of this type is automatically sent + // when we reach the end of the track + if (m.getType() == END_OF_TRACK) + System.exit(0); + } + }); + // And start playing now. + sequencer.start(); + } else { // A file name was specified, so save the notes + int[] allowedTypes = MidiSystem.getMidiFileTypes(sequence); + if (allowedTypes.length == 0) { + System.err.println("No supported MIDI file types."); + } else { + MidiSystem.write(sequence, allowedTypes[0], new File(filename)); + System.exit(0); + } + } } - + public SimpleKeyboard() { bag.addButtonListener(this); try { @@ -63,7 +150,7 @@ public class SimpleKeyboard implements ButtonListener { playKey(0); } } - + void playKey(int key) { try { AudioInputStream sound = AudioSystem.getAudioInputStream(keys[key]); @@ -87,8 +174,118 @@ public class SimpleKeyboard implements ButtonListener { @Override public void contact(Contact c) { // TODO Auto-generated method stub - + } + /* + * This method parses the specified char[] of notes into a Track. The + * musical notation is the following: A-G: A named note; Add b for flat and + * # for sharp. +: Move up one octave. Persists. -: Move down one octave. + * Persists. /1: Notes are whole notes. Persists 'till changed /2: Half + * notes /4: Quarter notes /n: N can also be, 8, 16, 32, 64. s: Toggle + * sustain pedal on or off (initially off) >: Louder. Persists <: Softer. + * Persists .: Rest. Length depends on current length setting Space: Play + * the previous note or notes; notes not separated by spaces are played at + * the same time + */ + public static void addTrack(Sequence s, int instrument, int tempo, + char[] notes) throws InvalidMidiDataException { + Track track = s.createTrack(); // Begin with a new track + + // Set the instrument on channel 0 + ShortMessage sm = new ShortMessage(); + sm.setMessage(ShortMessage.PROGRAM_CHANGE, 0, instrument, 0); + track.add(new MidiEvent(sm, 0)); + + int n = 0; // current character in notes[] array + int t = 0; // time in ticks for the composition + + // These values persist and apply to all notes 'till changed + int notelength = 16; // default to quarter notes + int velocity = 64; // default to middle volume + int basekey = 60; // 60 is middle C. Adjusted up and down by octave + boolean sustain = false; // is the sustain pedal depressed? + int numnotes = 0; // How many notes in current chord? + + while (n < notes.length) { + char c = notes[n++]; + + if (c == '+') + basekey += 12; // increase octave + else if (c == '-') + basekey -= 12; // decrease octave + else if (c == '>') + velocity += 16; // increase volume; + else if (c == '<') + velocity -= 16; // decrease volume; + else if (c == '/') { + char d = notes[n++]; + if (d == '2') + notelength = 32; // half note + else if (d == '4') + notelength = 16; // quarter note + else if (d == '8') + notelength = 8; // eighth note + else if (d == '3' && notes[n++] == '2') + notelength = 2; + else if (d == '6' && notes[n++] == '4') + notelength = 1; + else if (d == '1') { + if (n < notes.length && notes[n] == '6') + notelength = 4; // 1/16th note + else + notelength = 64; // whole note + } + } else if (c == 's') { + sustain = !sustain; + // Change the sustain setting for channel 0 + ShortMessage m = new ShortMessage(); + m.setMessage(ShortMessage.CONTROL_CHANGE, 0, DAMPER_PEDAL, + sustain ? DAMPER_ON : DAMPER_OFF); + track.add(new MidiEvent(m, t)); + } else if (c >= 'A' && c <= 'G') { + int key = basekey + offsets[c - 'A']; + if (n < notes.length) { + if (notes[n] == 'b') { // flat + key--; + n++; + } else if (notes[n] == '#') { // sharp + key++; + n++; + } + } + + addNote(track, t, notelength, key, velocity); + numnotes++; + } else if (c == ' ') { + // Spaces separate groups of notes played at the same time. + // But we ignore them unless they follow a note or notes. + if (numnotes > 0) { + t += notelength; + numnotes = 0; + } + } else if (c == '.') { + // Rests are like spaces in that they force any previous + // note to be output (since they are never part of chords) + if (numnotes > 0) { + t += notelength; + numnotes = 0; + } + // Now add additional rest time + t += notelength; + } + } + } + + // A convenience method to add a note to the track on channel 0 + public static void addNote(Track track, int startTick, int tickLength, + int key, int velocity) throws InvalidMidiDataException { + ShortMessage on = new ShortMessage(); + on.setMessage(ShortMessage.NOTE_ON, 0, key, velocity); + ShortMessage off = new ShortMessage(); + off.setMessage(ShortMessage.NOTE_OFF, 0, key, velocity); + track.add(new MidiEvent(on, startTick)); + track.add(new MidiEvent(off, startTick + tickLength)); + } } -- cgit v1.2.3