/*
* 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));
}
});
}
}