package org.geogebra.desktop.sound;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
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.geogebra.common.GeoGebraConstants;
import org.geogebra.common.util.Charsets;
import org.geogebra.common.util.debug.Log;
import org.geogebra.desktop.main.AppD;
import org.jfugue.Pattern;
import org.jfugue.Player;
/**
* Class for managing and playing Midi sound.
*
* @author G. Sturr 2010-9-18
*
*/
public class MidiSoundD implements MetaEventListener {
private final AppD app;
private Synthesizer synthesizer;
private Instrument instruments[];
private MidiChannel channels[];
private Sequencer sequencer;
private Sequence seq;
private long tickPos;
private Player player;
// Midi meta event
public static final int END_OF_TRACK_MESSAGE = 47;
/***********************************************
* Constructor
*/
public MidiSoundD(AppD app) {
this.app = app;
}
// ==================================================
// Initialization
// ==================================================
public boolean initialize() {
boolean success = true;
try {
sequencer = MidiSystem.getSequencer();
sequencer.addMetaEventListener(this);
if (synthesizer == null) {
synthesizer = MidiSystem.getSynthesizer();
Soundbank sb = synthesizer.getDefaultSoundbank();
if (sb != null) {
instruments = synthesizer.getDefaultSoundbank()
.getInstruments();
synthesizer.loadInstrument(instruments[0]);
}
setChannels(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);
StringBuilder list = new StringBuilder("{");
for (int i = 0; i < size; i++) {
list.append("\"" + i + ": " + instruments[i].getName() + "\"");
if (i != (size - 1)) {
list.append(",");
}
}
list.append("}");
return list.toString();
}
/**
* 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) {
// ignore
} catch (InvalidMidiDataException e) {
// ignore
}
}
public void pause(boolean doPause) {
if (sequencer == null) {
return;
}
if (doPause) {
tickPos = sequencer.getTickPosition();
closeMidiSound();
} else {
playSequence(seq, tickPos);
}
}
public void stop() {
closeMidiSound();
seq = null;
}
public void closeMidiSound() {
if (synthesizer != null) {
synthesizer.close();
}
instruments = null;
setChannels(null);
if ((sequencer != null) && sequencer.isOpen()) {
// sequencer.stop();
sequencer.close();
}
}
/**
* Midi meta event listener that closes the sequencer at end of track.
*/
@Override
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) {
tickPos = 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.
*/
/*
* Uses the sequencer to play a Midi sequence from a .mid file or a .txt
* file containing a JFugue string.
*/
public void playMidiFile(String filePath0) {
String filePath = filePath0;
try {
if ("".equals(filePath) && app.isUsingFullGui()) {
// 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;
String ext = null;
// allow: PlaySound["http://www.btc-bci.com/~rlewis/spider.mid"]
// PlaySound["allthing.mid"]
// PlaySound["c:\whiduck.mid"]
// PlaySound["http://cs.uccs.edu/~cs525/midi/vivi.mid"]
// PlaySound["@1296677"]
// PlaySound["http://tube-beta.geogebra.org/material/download/format/file/id/1296677"]
// PlaySound["http://tube-beta.geogebra.org/files/material-1296677.mid"]
if (filePath.startsWith("@")) {
String id = filePath.substring(1);
String urlStr;
// if (app.has(Feature.TUBE_BETA)) {
urlStr = GeoGebraConstants.GEOGEBRA_WEBSITE_BETA;
// } else {
// urlStr = GeoGebraConstants.GEOGEBRATUBE_WEBSITE;
// }
// something like
// http://tube-beta.geogebra.org/files/material-1296677.mid
urlStr = urlStr + "files/material-" + id + ".mid";
url = new URL(urlStr);
ext = "mid";
} else 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);
}
}
if (ext == null) {
ext = filePath.substring(filePath.lastIndexOf(".") + 1);
}
if ("txt".equals(ext)) {
playJFugueFromFile(f, url);
} else if ("gm".equals(ext)) {
loadSoundBank(f, url);
} else {
// Load new sequence from .mid file
tickPos = 0;
seq = f == null ? MidiSystem.getSequence(url)
: MidiSystem.getSequence(f);
playSequence(seq, tickPos);
}
} 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();
Log.debug("soundbank added: " + sb);
if (sb != null) {
Log.debug("soundbank supported: "
+ synthesizer.isSoundbankSupported(sb));
boolean bInstrumentsLoaded = synthesizer.loadAllInstruments(sb);
Log.debug("Instruments loaded: " + bInstrumentsLoaded);
}
} catch (MidiUnavailableException e) {
e.printStackTrace();
} catch (InvalidMidiDataException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void playSequenceFromJFugueString(String noteString0,
int instrument) {
initialize();
try {
sequencer.open();
synthesizer.open();
} catch (MidiUnavailableException e) {
e.printStackTrace();
}
String noteString = "I[" + instrument + "] " + noteString0;
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(),
Charsets.UTF_8))
: new BufferedReader(new InputStreamReader(
new FileInputStream(file), Charsets.UTF_8));
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();
}
}
}
public MidiChannel[] getChannels() {
return channels;
}
private void setChannels(MidiChannel channels[]) {
this.channels = channels;
}
/**********************************************************
* Class PlayerThread Thread extension that runs a JFugue MIDI player
*/
private static 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();
}
}
}