ibxm.OpenALMODPlayer Maven / Gradle / Ivy
The newest version!
package ibxm;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
/**
* A streaming mod/xm play back system
*
* @author Kevin Glass
*/
public class OpenALMODPlayer {
/** The size of the sections to stream from the mod file */
private static final int sectionSize = 4096 * 10;
/** Holds the OpenAL buffer names */
private IntBuffer bufferNames;
/** The IBXM reference */
private IBXM ibxm;
/** The length of the track in frames */
private int songDuration;
/** The data read for this section */
private byte[] data = new byte[sectionSize * 4];
/** The byte buffer passed to OpenAL containing the section */
private ByteBuffer bufferData = BufferUtils.createByteBuffer(sectionSize * 4);
/** The buffer holding the names of the OpenAL buffer thats been fully played back */
private IntBuffer unqueued = BufferUtils.createIntBuffer(1);
/** The source we're playing back on */
private int source;
/** True if sound works */
private boolean soundWorks = true;
/** The module being played */
private Module module;
/** True if we should loop the track */
private boolean loop;
/** True if we've completed play back */
private boolean done = true;
/** The number of buffers remaining to be played back */
private int remainingBufferCount;
/**
* Initialise OpenAL LWJGL styley
*/
public void init() {
try {
AL.create();
soundWorks = true;
} catch (LWJGLException e) {
System.err.println("Failed to initialise LWJGL OpenAL");
soundWorks = false;
return;
}
if (soundWorks) {
IntBuffer sources = BufferUtils.createIntBuffer(1);
AL10.alGenSources(sources);
if (AL10.alGetError() != AL10.AL_NO_ERROR) {
System.err.println("Failed to create sources");
soundWorks = false;
} else {
source = sources.get(0);
}
}
}
/**
* Play a mod or xm track streamed from the specified location
*
* @param in The input stream to read the music from
* @param loop True if the track should be looped
* @param start True if the music should be started
* @throws IOException The input stream to read the music from
*/
public void play(InputStream in, boolean loop, boolean start) throws IOException {
play(source, in, loop, start);
}
/**
* Play a mod or xm track streamed from the specified location
*
* @param source The OpenAL source to play the music on
* @param in The input stream to read the music from
* @param loop True if the track should be looped
* @param start True if the music should be started
* @throws IOException The input stream to read the music from
*/
public void play(int source, InputStream in, boolean loop, boolean start) throws IOException {
if (!soundWorks) {
return;
}
done = false;
this.loop = loop;
this.source = source;
module = loadModule(in);
play(module, source, loop, start);
}
/**
* Play a mod or xm track streamed from the specified location
*
* @param module The moudle to play back
* @param source The OpenAL source to play the music on
* @param start True if the music should be started
* @param loop True if the track should be looped
*/
public void play(Module module, int source, boolean loop, boolean start) {
this.source = source;
this.loop = loop;
this.module = module;
done = false;
ibxm = new IBXM(48000);
ibxm.set_module(module);
songDuration = ibxm.calculate_song_duration();
if (bufferNames != null) {
AL10.alSourceStop(source);
bufferNames.flip();
AL10.alDeleteBuffers(bufferNames);
}
bufferNames = BufferUtils.createIntBuffer(2);
AL10.alGenBuffers(bufferNames);
remainingBufferCount = 2;
for (int i=0;i<2;i++) {
stream(bufferNames.get(i));
}
AL10.alSourceQueueBuffers(source, bufferNames);
AL10.alSourcef(source, AL10.AL_PITCH, 1.0f);
AL10.alSourcef(source, AL10.AL_GAIN, 1.0f);
if (start) {
AL10.alSourcePlay(source);
}
}
/**
* Setup the playback properties
*
* @param pitch The pitch to play back at
* @param gain The volume to play back at
*/
public void setup(float pitch, float gain) {
AL10.alSourcef(source, AL10.AL_PITCH, pitch);
AL10.alSourcef(source, AL10.AL_GAIN, gain);
}
/**
* Check if the playback is complete. Note this will never
* return true if we're looping
*
* @return True if we're looping
*/
public boolean done() {
return done;
}
/**
* Load a module using the IBXM
*
* @param in The input stream to read the module from
* @return The module loaded
* @throws IOException Indicates a failure to access the module
*/
public static Module loadModule(InputStream in) throws IOException {
Module module;
DataInputStream din;
byte[] xm_header, s3m_header, mod_header, output_buffer;
int frames;
din = new DataInputStream(in);
module = null;
xm_header = new byte[ 60 ];
din.readFully( xm_header );
if( FastTracker2.is_xm( xm_header ) ) {
module = FastTracker2.load_xm( xm_header, din );
} else {
s3m_header = new byte[ 96 ];
System.arraycopy( xm_header, 0, s3m_header, 0, 60 );
din.readFully( s3m_header, 60, 36 );
if( ScreamTracker3.is_s3m( s3m_header ) ) {
module = ScreamTracker3.load_s3m( s3m_header, din );
} else {
mod_header = new byte[ 1084 ];
System.arraycopy( s3m_header, 0, mod_header, 0, 96 );
din.readFully( mod_header, 96, 988 );
module = ProTracker.load_mod( mod_header, din );
}
}
din.close();
return module;
}
/**
* Poll the bufferNames - check if we need to fill the bufferNames with another
* section.
*
* Most of the time this should be reasonably quick
*/
public void update() {
if (done) {
return;
}
int processed = AL10.alGetSourcei(source, AL10.AL_BUFFERS_PROCESSED);
while (processed > 0) {
unqueued.clear();
AL10.alSourceUnqueueBuffers(source, unqueued);
if (stream(unqueued.get(0))) {
AL10.alSourceQueueBuffers(source, unqueued);
} else {
remainingBufferCount--;
if (remainingBufferCount == 0) {
done = true;
}
}
processed--;
}
int state = AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE);
if (state != AL10.AL_PLAYING) {
AL10.alSourcePlay(source);
}
}
/**
* Stream one section from the mod/xm into an OpenAL buffer
*
* @param bufferId The ID of the buffer to fill
* @return True if another section was available
*/
public boolean stream(int bufferId) {
int frames = sectionSize;
boolean reset = false;
boolean more = true;
if (frames > songDuration) {
frames = songDuration;
reset = true;
}
ibxm.get_audio(data, frames);
bufferData.clear();
bufferData.put(data);
bufferData.limit(frames * 4);
if (reset) {
if (loop) {
ibxm.seek(0);
ibxm.set_module(module);
songDuration = ibxm.calculate_song_duration();
} else {
more = false;
songDuration -= frames;
}
} else {
songDuration -= frames;
}
bufferData.flip();
AL10.alBufferData(bufferId, AL10.AL_FORMAT_STEREO16, bufferData, 48000);
return more;
}
}