Minor restructuring done; furthered functionality of the audio system;
further work on thread-safety of the classes.
This commit is contained in:
		@@ -1,29 +0,0 @@
 | 
			
		||||
package zero1hd.rhythmbullet.audio;
 | 
			
		||||
 | 
			
		||||
import com.badlogic.gdx.utils.Array;
 | 
			
		||||
 | 
			
		||||
import edu.emory.mathcs.jtransforms.fft.FloatFFT_1D;
 | 
			
		||||
import zero1hd.rhythmbullet.audio.processor.SampleProcessor;
 | 
			
		||||
 | 
			
		||||
class AudioFileReadingThread extends Thread {
 | 
			
		||||
	private boolean function = true;
 | 
			
		||||
	private Array<SampleProcessor> sps;
 | 
			
		||||
	SampleProcessor sp;
 | 
			
		||||
	FloatFFT_1D fft;
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public void run() {
 | 
			
		||||
		while (function) {
 | 
			
		||||
			if (sps.size > 0) {
 | 
			
		||||
				
 | 
			
		||||
			} else {
 | 
			
		||||
				try {
 | 
			
		||||
					wait();
 | 
			
		||||
				} catch (InterruptedException e) {
 | 
			
		||||
					e.printStackTrace();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		super.run();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								core/src/zero1hd/rhythmbullet/audio/RhythmBulletAudioThread.java
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										75
									
								
								core/src/zero1hd/rhythmbullet/audio/RhythmBulletAudioThread.java
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
package zero1hd.rhythmbullet.audio;
 | 
			
		||||
 | 
			
		||||
import com.badlogic.gdx.Gdx;
 | 
			
		||||
import com.badlogic.gdx.audio.AudioDevice;
 | 
			
		||||
import com.badlogic.gdx.audio.Music;
 | 
			
		||||
import com.badlogic.gdx.audio.Music.OnCompletionListener;
 | 
			
		||||
 | 
			
		||||
import zero1hd.rhythmbullet.audio.processor.SampleProcessor;
 | 
			
		||||
 | 
			
		||||
public class RhythmBulletAudioThread extends Thread {
 | 
			
		||||
	private AudioDevice ad;
 | 
			
		||||
	private SampleProcessor sp;
 | 
			
		||||
	private Music music;
 | 
			
		||||
	private volatile OnCompletionListener ocl;
 | 
			
		||||
	private short[] pcm;
 | 
			
		||||
	private volatile boolean terminated;
 | 
			
		||||
	private volatile boolean playing;
 | 
			
		||||
	
 | 
			
		||||
	public RhythmBulletAudioThread(int windowSize, VisualizableMusic vm) {
 | 
			
		||||
		music = vm;
 | 
			
		||||
		this.sp = vm.getSampleProcessor();
 | 
			
		||||
		pcm = new short[sp.getChannels()*windowSize];
 | 
			
		||||
		
 | 
			
		||||
		this.ad = Gdx.audio.newAudioDevice(sp.getSampleRate(), (sp.getChannels() > 1 ? false : true));
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public void run() {
 | 
			
		||||
		while (!terminated && sp.readSamples(pcm) > 0) {
 | 
			
		||||
			ad.writeSamples(pcm, 0, pcm.length);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (ocl != null) {
 | 
			
		||||
			ocl.onCompletion(music);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		super.run();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public synchronized void play() {
 | 
			
		||||
		playing = true;
 | 
			
		||||
		notify();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public synchronized void pause() {
 | 
			
		||||
		playing = false;
 | 
			
		||||
		while (!playing) {
 | 
			
		||||
			try {
 | 
			
		||||
				wait();
 | 
			
		||||
			} catch (InterruptedException e) {
 | 
			
		||||
				e.printStackTrace();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public synchronized short[] getPcm() {
 | 
			
		||||
		return pcm;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public void terminate() {
 | 
			
		||||
		terminated = true;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public boolean isTerminated() {
 | 
			
		||||
		return terminated;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public boolean isPlaying() {
 | 
			
		||||
		return playing;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public void setOnCompletionListener(OnCompletionListener ocl) {
 | 
			
		||||
		this.ocl = ocl;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										113
									
								
								core/src/zero1hd/rhythmbullet/audio/VisualizableMusic.java
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										113
									
								
								core/src/zero1hd/rhythmbullet/audio/VisualizableMusic.java
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,113 @@
 | 
			
		||||
package zero1hd.rhythmbullet.audio;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
import javax.sound.sampled.UnsupportedAudioFileException;
 | 
			
		||||
 | 
			
		||||
import com.badlogic.gdx.audio.Music;
 | 
			
		||||
import com.badlogic.gdx.files.FileHandle;
 | 
			
		||||
 | 
			
		||||
import zero1hd.rhythmbullet.audio.processor.MP3SampleProcessor;
 | 
			
		||||
import zero1hd.rhythmbullet.audio.processor.SampleProcessor;
 | 
			
		||||
import zero1hd.rhythmbullet.audio.processor.WAVSampleProcessor;
 | 
			
		||||
 | 
			
		||||
public class VisualizableMusic implements Music {
 | 
			
		||||
	private int windowSize = 1024;
 | 
			
		||||
	private OnCompletionListener ocl;
 | 
			
		||||
	private boolean looping;
 | 
			
		||||
	private RhythmBulletAudioThread rat;
 | 
			
		||||
	private File musicFile;
 | 
			
		||||
	private SampleProcessor sampleProcessor;
 | 
			
		||||
	
 | 
			
		||||
	public VisualizableMusic(FileHandle file) {
 | 
			
		||||
		musicFile = file.file();
 | 
			
		||||
		if (musicFile.getName().toLowerCase().endsWith("wav")) {
 | 
			
		||||
			try {
 | 
			
		||||
				sampleProcessor = new WAVSampleProcessor(musicFile, windowSize);
 | 
			
		||||
			} catch (IOException | UnsupportedAudioFileException e) {
 | 
			
		||||
				e.printStackTrace();
 | 
			
		||||
			}
 | 
			
		||||
		} else if (musicFile.getName().toLowerCase().endsWith("mp3")) {
 | 
			
		||||
			sampleProcessor = new MP3SampleProcessor();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public void play() {
 | 
			
		||||
		if (rat == null) {
 | 
			
		||||
			rat = new RhythmBulletAudioThread(windowSize, this);
 | 
			
		||||
		}
 | 
			
		||||
		rat.setOnCompletionListener(ocl);
 | 
			
		||||
		rat.play();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void pause() {
 | 
			
		||||
		rat.pause();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void stop() {
 | 
			
		||||
		rat.terminate();
 | 
			
		||||
		rat = null;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean isPlaying() {
 | 
			
		||||
		return rat.isPlaying();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setLooping(boolean isLooping) {
 | 
			
		||||
		looping = isLooping;
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean isLooping() {
 | 
			
		||||
		return looping;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setVolume(float volume) {
 | 
			
		||||
		sampleProcessor.setVolume(volume);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getVolume() {
 | 
			
		||||
		return sampleProcessor.getVolume();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setPan(float pan, float volume) {
 | 
			
		||||
		sampleProcessor.setPan(pan);
 | 
			
		||||
		sampleProcessor.setVolume(volume);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setPosition(float position) {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getPosition() {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void dispose() {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setOnCompletionListener(OnCompletionListener listener) {
 | 
			
		||||
		this.ocl = listener;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	protected SampleProcessor getSampleProcessor() {
 | 
			
		||||
		return sampleProcessor;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -20,5 +20,27 @@ public class MP3SampleProcessor implements SampleProcessor {
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setVolume(float volume) {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getVolume() {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setPan(float pan) {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getPan() {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,52 @@
 | 
			
		||||
package zero1hd.rhythmbullet.audio.processor;
 | 
			
		||||
 | 
			
		||||
public interface SampleProcessor {
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return number of channels
 | 
			
		||||
	 */
 | 
			
		||||
	public int getChannels();
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return sample rate
 | 
			
		||||
	 */
 | 
			
		||||
	public int getSampleRate();
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * <b>Should be thread-safe!</b>
 | 
			
		||||
	 * Reads samples (NOT FRAMES) with interwoven data for stereo.
 | 
			
		||||
	 * stored in 16 bit format (first 8 are the first byte of data while the second 8 are the second byte of data that composes a short value) 
 | 
			
		||||
	 * @param pcm the array the samples should fill
 | 
			
		||||
	 * @return the amount of samples read.
 | 
			
		||||
	 */
 | 
			
		||||
	public int readSamples(short[] pcm);
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * <b>Should be thread-safe!</b>
 | 
			
		||||
	 * ranges from 0 to 1.
 | 
			
		||||
	 * @param volume the volume to set
 | 
			
		||||
	 */
 | 
			
		||||
	public void setVolume(float volume);
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * <b>Should be thread-safe!</b>
 | 
			
		||||
	 * @return the volume ranging from 0 to 1
 | 
			
		||||
	 */
 | 
			
		||||
	public float getVolume();
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * <b>Should be thread-safe!</b>
 | 
			
		||||
	 * sets the pan from the left channel to the right.
 | 
			
		||||
	 * ranges from -1 to 1.
 | 
			
		||||
	 * Default is 0, -1 is left, 1 is right.
 | 
			
		||||
	 * @param pan
 | 
			
		||||
	 */
 | 
			
		||||
	public void setPan(float pan);
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * <b>Should be thread-safe!</b>
 | 
			
		||||
	 * Returns the pan value from -1 to 1.
 | 
			
		||||
	 * see {@link #setPan(float)} for more information.
 | 
			
		||||
	 * @return the pan value.
 | 
			
		||||
	 */
 | 
			
		||||
	public float getPan();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,144 +0,0 @@
 | 
			
		||||
package zero1hd.rhythmbullet.audio.processor;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
import javax.sound.sampled.UnsupportedAudioFileException;
 | 
			
		||||
 | 
			
		||||
import com.badlogic.gdx.Gdx;
 | 
			
		||||
import com.badlogic.gdx.audio.AudioDevice;
 | 
			
		||||
import com.badlogic.gdx.audio.Music;
 | 
			
		||||
import com.badlogic.gdx.files.FileHandle;
 | 
			
		||||
 | 
			
		||||
public class VisualizableMusic implements Music {
 | 
			
		||||
	private int windowSize = 1024;
 | 
			
		||||
	private OnCompletionListener ocl;
 | 
			
		||||
	private volatile boolean playing, updatedPCMData;
 | 
			
		||||
	private boolean looping;
 | 
			
		||||
	private volatile short[] pcm;
 | 
			
		||||
	private volatile float[] normalized;
 | 
			
		||||
	private volatile SampleProcessor sp;
 | 
			
		||||
	private volatile float volume = 1f;
 | 
			
		||||
 | 
			
		||||
	public VisualizableMusic(FileHandle file) {
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public void play() {
 | 
			
		||||
		playing = true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void pause() {
 | 
			
		||||
		playing = false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void stop() {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean isPlaying() {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setLooping(boolean isLooping) {
 | 
			
		||||
		looping = isLooping;
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean isLooping() {
 | 
			
		||||
		return looping;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setVolume(float volume) {
 | 
			
		||||
		this.volume = volume;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getVolume() {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setPan(float pan, float volume) {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setPosition(float position) {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getPosition() {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void dispose() {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setOnCompletionListener(OnCompletionListener listener) {
 | 
			
		||||
		this.ocl = listener;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	public void normalize(short[] pcm, float[] normalized) {
 | 
			
		||||
		int currentFrame = 0;
 | 
			
		||||
		for (int sampleID = 0; sampleID < pcm.length; sampleID++) {
 | 
			
		||||
			for (int chan = 0; chan < sp.getChannels(); chan++) {
 | 
			
		||||
				if (normalized[currentFrame] > pcm[sampleID]) {
 | 
			
		||||
					normalized[currentFrame] = pcm[sampleID];
 | 
			
		||||
				}
 | 
			
		||||
				sampleID++;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			normalized[currentFrame] /= Short.MAX_VALUE+1;
 | 
			
		||||
			currentFrame++;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	class RhythmBulletAudioThread extends Thread {
 | 
			
		||||
		private AudioDevice ad;
 | 
			
		||||
		
 | 
			
		||||
		public RhythmBulletAudioThread(int windowSize, File file) {
 | 
			
		||||
			if (file.getName().toLowerCase().endsWith("wav")) {
 | 
			
		||||
				try {
 | 
			
		||||
					sp = new WAVSampleProcessor(file, windowSize);
 | 
			
		||||
				} catch (IOException | UnsupportedAudioFileException e) {
 | 
			
		||||
					e.printStackTrace();
 | 
			
		||||
				}
 | 
			
		||||
			} else if (file.getName().toLowerCase().endsWith("mp3")) {
 | 
			
		||||
				sp = new MP3SampleProcessor();
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			pcm = new short[sp.getChannels()*windowSize];
 | 
			
		||||
			normalized = new float[windowSize];
 | 
			
		||||
			
 | 
			
		||||
			this.ad = Gdx.audio.newAudioDevice(sp.getSampleRate(), (sp.getChannels() > 1 ? true : false));
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		@Override
 | 
			
		||||
		public void run() {
 | 
			
		||||
			while (playing && sp.readSamples(pcm) > 0) {
 | 
			
		||||
				updatedPCMData = true;
 | 
			
		||||
				ad.writeSamples(pcm, 0, pcm.length);
 | 
			
		||||
			}
 | 
			
		||||
			super.run();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -12,8 +12,8 @@ public class WAVSampleProcessor implements SampleProcessor {
 | 
			
		||||
	private int sampleRate;
 | 
			
		||||
	private byte[] buffer;
 | 
			
		||||
	private AudioInputStream audioInputStream;
 | 
			
		||||
	private boolean mergeChannels;
 | 
			
		||||
	private float volume, pan;
 | 
			
		||||
	private volatile float volume, pan;
 | 
			
		||||
	
 | 
			
		||||
	public WAVSampleProcessor(File musicFile, int windowSize) throws IOException, UnsupportedAudioFileException {
 | 
			
		||||
		audioInputStream = AudioSystem.getAudioInputStream(musicFile);
 | 
			
		||||
		buffer = new byte[audioInputStream.getFormat().getFrameSize()];
 | 
			
		||||
@@ -32,7 +32,7 @@ public class WAVSampleProcessor implements SampleProcessor {
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public int readSamples(short[] pcm) {
 | 
			
		||||
	public synchronized int readSamples(short[] pcm) {
 | 
			
		||||
		int framesRead = 0;
 | 
			
		||||
		for (int sampleID = 0; sampleID < pcm.length; sampleID++) {
 | 
			
		||||
			try {
 | 
			
		||||
@@ -55,16 +55,23 @@ public class WAVSampleProcessor implements SampleProcessor {
 | 
			
		||||
		return framesRead;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public AudioInputStream getAudioInputStream() {
 | 
			
		||||
		return audioInputStream;
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setVolume(float volume) {
 | 
			
		||||
		this.volume = volume;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void setMergeChannels(boolean mergeChannels) {
 | 
			
		||||
		this.mergeChannels = mergeChannels;
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getVolume() {
 | 
			
		||||
		return volume;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean isMergeChannels() {
 | 
			
		||||
		return mergeChannels;
 | 
			
		||||
	@Override
 | 
			
		||||
	public void setPan(float pan) {
 | 
			
		||||
		this.pan = pan;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getPan() {
 | 
			
		||||
		return pan;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user