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