Background Music options

Started by elias4444, October 26, 2004, 17:34:21

Previous topic - Next topic

elias4444

I've been searching on this for a while, but haven't found a solid solution yet. I'm going along, making my games, and then suddenly realized they were missing something: background music!

Using OpenAL, I can EASILY play wav files, but a 16 to 20mb wav file for background music seems a bit extreme to me. I read that lwjgl now supports ogg and mp3, but only through the FMOD library that costs money for commercial development (a possibility, but not until I can actually earn some money from this first - the whole chicken and the egg thing).

If I decide to go with MIDI, am I stuck with JavaSound? (Which I can't seem to get to work in Java 5.0).  Is there another compression I can use royalty free? And can OpenAL do it somehow (since I'm already using OpenAL for all my other sounds)?

Please let me know. I keep feeling like I'm so close to finishing, and then things like this seem to come up.
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

Chman

You could use JOrbis, which is an open source library.
You can find it here : http://www.jcraft.com/jorbis/

If you want examples on how to use it with LWJGL, take a look at alien flux source code :)

elias4444

Where can you get the Alien Flux source code from? I thought it was a commercial product?

Also, doesn't LWJGL have built-in support for OGG? I thought I read a forum post that it was included before FMOD was. What's the history there?
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com


elias4444

You know, I've been trying to use that same code... what's the story with AL vs AL10? Looks like a lot of the errors are that he's calling the AL object where it should be AL10.

Even after renaming all ALs to AL10s, I'm still getting the following error though:
Exception in thread "main" java.lang.UnsatisfiedLinkError: nalGenBuffers
   at org.lwjgl.openal.AL10.nalGenBuffers(Native Method)
   at org.lwjgl.openal.AL10.alGenBuffers(Unknown Source)
   at twister1.OggPlayer.open(OggPlayer.java:47)
   at twister1.Twister1.loadStuff(Twister1.java:94)
   at twister1.Twister1.startGame(Twister1.java:64)
   at twister1.Twister1.run(Twister1.java:59)
   at twister1.Twister1.main(Twister1.java:45)
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

Matzon

Works fine here...
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
import java.io.InputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;


/**
 * Plays ogg files using lwjgl's openAL10.
 * <p>
 * First open the file by calling open(OggInputStream). Then play it either by
 * using play(), or playInNewThread(long). If you use Play() you must also call
 * update() at an interval, to feed OpenAL with data.
 */
public class OggPlayer {
	
	// temporary buffer
	private ByteBuffer dataBuffer = ByteBuffer.allocateDirect(4096*8);

	// front and back buffers
	private IntBuffer buffers = createIntBuffer(2);

	// audio source
	private IntBuffer source = createIntBuffer(1);

	// is used to unpack ogg file.
	private OggInputStream oggInputStream;

	// a seperate thread that calls update.

	private PlayerThread playerThread = null;

	// set to true when player is initalized.
	private boolean initalized = false;


	/**
	 * Opens the specified ogg file in the classpath.
	 */
	public void open(OggInputStream input) {
		oggInputStream = input;

		buffers.rewind();
		AL10.alGenBuffers(buffers);
		check();
		
		source.rewind();
		AL10.alGenSources(source);
		check();

		initalized = true;

		AL10.alSource3f(source.get(0), AL10.AL_POSITION, 0, 0, 0);
		AL10.alSource3f(source.get(0), AL10.AL_VELOCITY, 0, 0, 0);
		AL10.alSource3f(source.get(0), AL10.AL_DIRECTION, 0, 0, 0);
		AL10.alSourcef(source.get(0), AL10.AL_ROLLOFF_FACTOR, 0);
		AL10.alSourcei(source.get(0), AL10.AL_SOURCE_RELATIVE, AL10.AL_TRUE);
	}


	/**
	 * release the file handle
	 */
	public void release() {
		if (initalized) {
			AL10.alSourceStop(source);
			empty();
			AL10.alDeleteSources(source);
			check();
			AL10.alDeleteBuffers(buffers);
			check();
		}
	}


	/**
	 * Plays the Ogg stream. update() must be called regularly so that the data
	 * is copied to OpenAl
	 */
	public boolean play() {
		if (playing()) {
			return true;
		}

		for (int i=0; i<buffers.capacity(); i++) {
			if (!stream(buffers.get(i))) {
				return false;
			}
		}

		AL10.alSourceQueueBuffers(source.get(0), buffers);
		AL10.alSourcePlay(source.get(0));

		return true;
	}


	/**
	 * Plays the track in a newly crated thread.
	 * @param updateInterval at which interval should the thread call update, in milliseconds.
	 */
	public boolean playInNewThread(long updateIntervalMillis) {
		if (play()) {
			playerThread = new PlayerThread(updateIntervalMillis);
            playerThread.setPriority(Thread.MAX_PRIORITY);
			playerThread.start();
			return true;
		} 

		return false;
	}


	/**
	 * check if the source is playing
	 */
	public boolean playing() {
		return (AL10.alGetSourcei(source.get(0), AL10.AL_SOURCE_STATE) == AL10.AL_PLAYING);
	}


	/**
	 * Copies data from the ogg stream to openAL10. Must be called often.
	 * @return true if sound is still playing, false if the end of file is reached.
	 */
	public synchronized boolean update() throws IOException {
		boolean active = true;
		int processed = AL10.alGetSourcei(source.get(0), AL10.AL_BUFFERS_PROCESSED);
		while (processed-- > 0) {
			IntBuffer buffer = createIntBuffer(1);
			AL10.alSourceUnqueueBuffers(source.get(0), buffer);
			check();
	
			active = stream(buffer.get(0));
			buffer.rewind();
	
			AL10.alSourceQueueBuffers(source.get(0), buffer);
			check();
		}

		return active;
	}


	/**
	 * reloads a buffer
	 * @return true if success, false if read failed or end of file.
	 */
	protected boolean stream(int buffer) {
		try {
			int bytesRead = oggInputStream.read(dataBuffer, 0, dataBuffer.capacity());
			if (bytesRead >= 0) {
				dataBuffer.rewind();
				boolean mono = (oggInputStream.getFormat() == OggInputStream.FORMAT_MONO16);
				int format = (mono ? AL10.AL_FORMAT_MONO16 : AL10.AL_FORMAT_STEREO16);
				AL10.alBufferData(buffer, format, dataBuffer, oggInputStream.getRate());
				check();
				return true;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return false;
	}


	/**
	 * empties the queue
	 */
	protected void empty() {
		int queued = AL10.alGetSourcei(source.get(0), AL10.AL_BUFFERS_QUEUED);
		while (queued-- > 0) {
			IntBuffer buffer = createIntBuffer(1);
			AL10.alSourceUnqueueBuffers(source.get(0), buffer);
			check();
		}
	}


	/**
	 * checks OpenAL error state
	 */
	protected void check() {
		int error = AL10.alGetError();
		if (error != AL10.AL_NO_ERROR) {
			System.out.println("OpenAL error was raised. errorCode="+error);
		}
	}

	
	/**
	 * Creates an integer buffer to hold specified ints
	 * - strictly a utility method
	 *
	 * @param size how many int to contain
	 * @return created IntBuffer
	 */
	protected static IntBuffer createIntBuffer(int size) {
		ByteBuffer temp = ByteBuffer.allocateDirect(4 * size);
		temp.order(ByteOrder.nativeOrder());
		return temp.asIntBuffer();
	}


	/**
	 * The thread that updates the sound.
	 */
	class PlayerThread extends Thread {
		// at what interval update is called.
		long interval;

		/** Creates the PlayerThread */
		PlayerThread(long interval) {
			this.interval = interval;
		}

		/** Calls update at an interval */
		public void run() {
			try {
				while (update()) {
					sleep(interval);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	
	/**
	 * Plays an ogg file.
	 * @param args[0] the class path containing the file to play.
	 */
	public static void main(String args[]) {
		OggPlayer ogg = new OggPlayer();
		try {
			if (args.length < 1) {
				args = new String[1];
				args[0] = "/audio/Madrugada - Ice.ogg";
			}
            
			AL.create();
			InputStream input = ogg.getClass().getResourceAsStream(args[0]);
			ogg.open(new OggInputStream(input));
			ogg.play();
    		while (ogg.update()) {
				Thread.sleep(5);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			ogg.release();
			AL.destroy();
		}
	}
}


E:\programming\Personal\Scratch>java -cp bin;lib\jogg-0.0.5.jar;lib\jorbis-0.0.12.jar;lib\lwjgl.jar; -Djava.library.path
=..\LWJGL\libs OggPlayer phero2.ogg

elias4444

Ah HA!! Found the problem!   :oops:

I had commented out my previous .wav based sound system for special effects - and that's what included the AL.create call. It's working now though. I went ahead and created volume control method in the OggPlayer file too so I can tweak around a bit.

THANKS!!!!
=-=-=-=-=-======-=-=-=-=-=-
http://www.tommytwisters.com

Matzon

Quote from: "elias4444"THANKS!!!!
Dont thank me. Thank:
tombryntesen at tiscali dot no - he has also released a gui library, and a *very* nice Quake3 example:
http://home.halden.net/tombr/

baegsi

The OggInputStream and -Player work very well.

One question though: is it possible to loop an OggInputStream? OggInputStream.reset() is not implemented, is there an other way?

tomb

OggInputStream don't support reset() because it's unlikely that the stream it wraps support it. As it was first used to stream from jars.

If you want to loop the sound you have several options. First is to detect that the sound stopped. It's when update returns false. Then first call release then open it with a newly created OggInputStream. Or you could add a setStream() function that just replaces the oggInputStream with a new one.

I have to warn you that OggPlayer is more of an example of how you could use OggInputStream to stream a sound in the background and not a fool proof finished library. As an example it's easy to make it leak resources like OpenAL sources if you dont call release().

baegsi

All right, I have a looping in place now, I recreate the input stream. Thanks for the warning. Good that all objects have names, so it's possible to see if there're any dangling sources or buffers.