package geogebra.sound; import geogebra.main.Application; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import javax.sound.midi.Instrument; import javax.sound.midi.InvalidMidiDataException; import javax.sound.midi.MetaEventListener; import javax.sound.midi.MetaMessage; import javax.sound.midi.MidiChannel; import javax.sound.midi.MidiSystem; import javax.sound.midi.MidiUnavailableException; import javax.sound.midi.Sequence; import javax.sound.midi.Sequencer; import javax.sound.midi.Soundbank; import javax.sound.midi.Synthesizer; import javax.swing.JFileChooser; import org.jfugue.Pattern; import org.jfugue.Player; /** * Class for managing and playing Midi sound. * * @author G. Sturr 2010-9-18 * */ public class MidiSound implements MetaEventListener { private final Application app; private Synthesizer synthesizer; private Instrument instruments[]; private MidiChannel channels[]; private Sequencer sequencer; private Sequence sequence; private long tickPosition; private Player player; // Midi meta event public static final int END_OF_TRACK_MESSAGE = 47; /*********************************************** * Constructor */ public MidiSound(Application app) { this.app = app; } // ================================================== // Initialization // ================================================== public boolean initialize() { boolean success = true; try { sequencer = MidiSystem.getSequencer(); sequencer.addMetaEventListener(this); if (synthesizer == null) { if ((synthesizer = MidiSystem.getSynthesizer()) == null) { Application.debug("getSynthesizer() failed!"); return false; } Soundbank sb = synthesizer.getDefaultSoundbank(); if (sb != null) { instruments = synthesizer.getDefaultSoundbank().getInstruments(); synthesizer.loadInstrument(instruments[0]); } channels = synthesizer.getChannels(); } } catch (MidiUnavailableException e) { e.printStackTrace(); return false; } catch (Exception e) { e.printStackTrace(); return false; } return success; } /** * Generates a list of available instruments in String form * */ public String getInstrumentNames() { int size = Math.min(128, instruments.length); String list = "{"; for (int i = 0; i < size; i++) { list += "\"" + i + ": " + instruments[i].getName() + "\""; if (i != (size - 1)) { list += ","; } } list += "}"; return list; } /** * Plays a midi sequence with default tempo */ public void playSequence(Sequence sequence, long tickPosition) { int tempo = 120; playSequence(sequence, tempo, tickPosition); } /** * Plays a MIDI sequence * * @param sequence * @param tempo * @param tickPosition */ public void playSequence(Sequence sequence, int tempo, long tickPosition) { if (sequence == null) { return; } try { initialize(); sequencer.open(); synthesizer.open(); // Specify the sequence, tempo, and tickPosition sequencer.setSequence(sequence); sequencer.setTempoInBPM(tempo); sequencer.setTickPosition(tickPosition); // Start playing sequencer.start(); } catch (MidiUnavailableException e) { } catch (InvalidMidiDataException e) { } } public void pause(boolean doPause) { if (sequencer == null) { return; } if (doPause) { tickPosition = sequencer.getTickPosition(); closeMidiSound(); } else { playSequence(sequence, tickPosition); } } public void stop() { closeMidiSound(); sequence = null; } public void closeMidiSound() { if (synthesizer != null) { synthesizer.close(); } instruments = null; channels = null; if ((sequencer != null) && sequencer.isOpen()) { // sequencer.stop(); sequencer.close(); } System.gc(); } /** * Midi meta event listener that closes the sequencer at end of track. */ public void meta(MetaMessage event) { // System.out.println("midi sound event " + event.getType()); if (event.getType() == END_OF_TRACK_MESSAGE) { closeMidiSound(); } } /** * Uses the Sequencer to play a single note in channel[0] * * */ public void playSequenceNote(final int note, final double duration, final int instrument, final int velocity) { tickPosition = 0; String str = "[" + note + "]/" + Double.toString(duration); this.playSequenceFromJFugueString(str, instrument); } /* * Uses the sequencer to play a Midi sequence from a .mid file or a .txt file containing a JFugue string. */ public void playMidiFile(String filePath) { try { if (filePath.equals("") && app.useFullGui()) { // launch a file chooser (just for testing) final JFileChooser fc = new JFileChooser(); int returnVal = fc.showOpenDialog(app.getMainComponent()); if (returnVal == JFileChooser.APPROVE_OPTION) { filePath = fc.getSelectedFile().getAbsolutePath(); } } File f = null; URL url = null; // allow: PlaySound["http://www.btc-bci.com/~rlewis/spider.mid"] // PlaySound["allthing.mid"] // PlaySound["c:\whiduck.mid"] if (filePath.startsWith("http://")) { url = new URL(filePath); } else if (app.isApplet()) { URL base = app.getApplet().applet.getDocumentBase(); String documentBase = base.toString(); if (filePath.startsWith("/")) { filePath = base.getProtocol() + "://" + base.getHost() + filePath; } else { String path = documentBase.substring(0, documentBase.lastIndexOf('/') + 1); filePath = path + filePath; } url = new URL(filePath); } else { f = new File(filePath); if (!f.exists()) { f = new File(app.getCurrentPath() + File.separator + filePath); } } String ext = filePath.substring(filePath.lastIndexOf(".") + 1); if (ext.equals("mid")) { // Load new sequence from .mid file tickPosition = 0; sequence = f == null ? MidiSystem.getSequence(url) : MidiSystem.getSequence(f); playSequence(sequence, tickPosition); } else if (ext.equals("txt")) { playJFugueFromFile(f, url); } else if (ext.equals("gm")) { loadSoundBank(f, url); } } catch (IOException e) { e.printStackTrace(); } catch (InvalidMidiDataException e) { e.printStackTrace(); } } private void loadSoundBank(File soundbankFile, URL soundbankURL) { try { synthesizer.close(); Soundbank sb = soundbankFile == null ? MidiSystem.getSoundbank(soundbankURL) : MidiSystem .getSoundbank(soundbankFile); synthesizer = MidiSystem.getSynthesizer(); synthesizer.open(); System.out.println("soundbank added: " + sb); if (sb != null) { System.out.println("soundbank supported: " + synthesizer.isSoundbankSupported(sb)); boolean bInstrumentsLoaded = synthesizer.loadAllInstruments(sb); System.out.println("Instruments loaded: " + bInstrumentsLoaded); } } catch (MidiUnavailableException e) { e.printStackTrace(); } catch (InvalidMidiDataException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public void playSequenceFromJFugueString(String noteString, int instrument) { initialize(); try { sequencer.open(); synthesizer.open(); } catch (MidiUnavailableException e) { e.printStackTrace(); } noteString = "I[" + instrument + "] " + noteString; player = new Player(sequencer); Pattern pattern = new Pattern(noteString); PlayerThread thread = new PlayerThread(player, pattern); thread.start(); } public void playJFugueFromFile(File file, URL url) throws IOException { StringBuffer contents = new StringBuffer(); BufferedReader reader = null; try { reader = file == null ? new BufferedReader(new InputStreamReader(url.openStream())) : new BufferedReader(new FileReader(file)); String text = null; while ((text = reader.readLine()) != null) { contents.append(text); } // System.out.println(contents.toString()); this.playSequenceFromJFugueString(contents.toString(), 0); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { try { if (reader != null) { reader.close(); } } catch (IOException e) { e.printStackTrace(); } } } /********************************************************** * Class PlayerThread Thread extension that runs a JFugue MIDI player */ private class PlayerThread extends Thread { private final Pattern pattern; private final Player player; public PlayerThread(Player player, Pattern pattern) { this.player = player; this.pattern = pattern; } @Override public void run() { player.play(pattern); player.close(); } } }