/*
* This file is part of the Haven & Hearth game client.
* Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and
* Björn Johannessen <johannessen.bjorn@gmail.com>
*
* Redistribution and/or modification of this file is subject to the
* terms of the GNU Lesser General Public License, version 3, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Other parts of this source tree adhere to other copying
* rights. Please see the file `COPYING' in the root directory of the
* source tree for details.
*
* A copy the GNU Lesser General Public License is distributed along
* with the source tree of which this file is a part in the file
* `doc/LPGL-3'. If it is missing for any reason, please see the Free
* Software Foundation's website at <http://www.fsf.org/>, or write
* to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package haven;
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.Sequencer;
import javax.sound.midi.Synthesizer;
public class Music {
private static Player player;
public static boolean enabled = true;
private static boolean debug = false;
static {
enabled = Utils.parsebool(Utils.getpref("bgmen", "true"), true);
}
private static void debug(String str) {
if (debug)
System.out.println(str);
}
public static void setVolume(int vol) {
if (player != null)
player.setVolume(vol);
}
private static class Player extends HackThread {
private Resource res;
private Thread waitfor;
private Sequencer seq;
private Synthesizer synth;
private boolean done;
private boolean loop = false;
private Player(Resource res, Thread waitfor) {
super("Music player");
setDaemon(true);
this.res = res;
this.waitfor = waitfor;
}
public void setVolume(int vol) {
// Changes the music volume
MidiChannel[] channels = synth.getChannels();
// gain is a value between 0 and 1 (loudest)
double gain = Math.sqrt((double) vol / 100);
for (int i = 0; i < channels.length; i++) {
channels[i].controlChange(7, (int) (gain * 127));
channels[i].setMute(vol == 0);
}
}
public void run() {
try {
if (waitfor != null)
waitfor.join();
res.loadwaitint();
try {
seq = MidiSystem.getSequencer(false);
synth = MidiSystem.getSynthesizer();
seq.open();
seq.setSequence(res.layer(Resource.Music.class).seq);
synth.open();
seq.getTransmitter().setReceiver(synth.getReceiver());
setVolume(Config.getMusicVolume());
} catch (MidiUnavailableException e) {
return;
} catch (InvalidMidiDataException e) {
return;
} catch (IllegalArgumentException e) {
/*
* The soft synthesizer appears to be throwing non-checked
* exceptions through from the sampled audio system. Ignore
* them and only them.
*/
if (e.getMessage().startsWith("No line matching"))
return;
throw (e);
}
seq.addMetaEventListener(new MetaEventListener() {
public void meta(MetaMessage msg) {
debug("Meta " + msg.getType());
if (msg.getType() == 47) {
synchronized (Player.this) {
done = true;
Player.this.notifyAll();
}
}
}
});
do {
debug("Start loop");
done = false;
seq.start();
synchronized (this) {
while (!done)
this.wait();
}
seq.setTickPosition(0);
} while (loop);
} catch (InterruptedException e) {
} finally {
try {
debug("Exit player");
if (seq != null)
seq.close();
try {
if (synth != null)
synth.close();
} catch (Throwable e2) {
if (e2 instanceof InterruptedException) {
/*
* XXX: There appears to be a bug in Sun's software
* MIDI implementation that throws back an unchecked
* InterruptedException here when two interrupts
* come close together (such as in the case when the
* current player is first stopped, and then another
* started immediately afterwards on a new song
* before the first one has had time to terminate
* entirely).
*/
} else {
throw (new RuntimeException(e2));
}
}
} finally {
synchronized (Music.class) {
if (player == this)
player = null;
}
}
}
}
}
public static void play(Resource res, boolean loop) {
synchronized (Music.class) {
if (player != null)
player.interrupt();
if (res != null) {
player = new Player(res, player);
player.loop = loop;
player.start();
}
}
}
public static void main(String[] args) throws Exception {
Resource.addurl(new java.net.URL("https://www.havenandhearth.com/res/"));
debug = true;
play(Resource.load(args[0]), (args.length > 1) ? args[1].equals("y") : false);
player.join();
}
public static void enable(boolean enabled) {
if (!enabled)
play(null, false);
Music.enabled = enabled;
Utils.setpref("bgmen", Boolean.toString(enabled));
}
static {
Console.setscmd("bgm", new Console.Command() {
public void run(Console cons, String[] args) {
int i = 1;
String opt;
boolean loop = false;
if (i < args.length) {
while ((opt = args[i]).charAt(0) == '-') {
i++;
if (opt.equals("-l"))
loop = true;
}
String resnm = args[i++];
int ver = -1;
if (i < args.length)
ver = Integer.parseInt(args[i++]);
Music.play(Resource.load(resnm, ver), loop);
} else {
Music.play(null, false);
}
}
});
Console.setscmd("bgmsw", new Console.Command() {
public void run(Console cons, String[] args) {
if (args.length < 2)
enable(!enabled);
else
enable(Utils.parsebool(args[1], true));
}
});
}
}