changed to integer state system for notifier in music controller; music

selection page rewritten making better use of framework objects.
Untested (still)
This commit is contained in:
Harrison Deng 2018-08-03 13:17:01 -05:00
parent 45da676d0d
commit b857ffe4bd
9 changed files with 276 additions and 124 deletions

View File

@ -18,14 +18,14 @@ import com.badlogic.gdx.files.FileHandle;
*
*/
public class MusicController extends Observable implements OnCompletionListener, Observer {
public enum States {
Loaded, Playing;
public final class States {
public final Integer LOADED = 0, PLAYING = 1;
}
public final States states = new States();
private MusicList musicList;
private MinimalAudioHeader musicHeader;
private Music music;
private int currentPlaybackIndex;
private int currentlyPlayingIndex;
private boolean autoPlay;
private boolean shuffle;
private Random rand;
@ -48,7 +48,7 @@ public class MusicController extends Observable implements OnCompletionListener,
Gdx.app.debug("MusicListController", "Playing from MLC.");
music.play();
music.setVolume(prefs.getFloat("music vol", 1f));
notifyObservers(States.Playing);
notifyObservers(states.PLAYING);
} else {
Gdx.app.debug("MusicListController", "failed to begin playing. Load the music!!!");
}
@ -68,15 +68,24 @@ public class MusicController extends Observable implements OnCompletionListener,
* @param index of music to play
*/
public void setMusicByIndex(int index) {
this.currentPlaybackIndex = index;
this.currentlyPlayingIndex = index;
loadMusic();
}
/**
* Loads music using the given file. The given file must be found in the {@link MusicList}.
* This function gets the index of the given file within {@link MusicList} and passes that index to {@link #setMusicByIndex(index)}.
* @param fileHandle to use.
*/
public void setMusicByFileHandle(FileHandle fileHandle) {
setMusicByIndex(musicList.getMusicArray().indexOf(fileHandle, true));
}
/**
* Goes to the next track
*/
public void skip() {
currentPlaybackIndex++;
currentlyPlayingIndex++;
if (shuffle) {
shuffle(false);
}
@ -87,7 +96,7 @@ public class MusicController extends Observable implements OnCompletionListener,
* Goes to the previous track
*/
public void previous() {
currentPlaybackIndex--;
currentlyPlayingIndex--;
if (shuffle) {
shuffle(false);
}
@ -100,7 +109,7 @@ public class MusicController extends Observable implements OnCompletionListener,
if (shuffle) {
shuffle(false);
} else {
currentPlaybackIndex++;
currentlyPlayingIndex++;
}
loadMusic();
play();
@ -114,9 +123,9 @@ public class MusicController extends Observable implements OnCompletionListener,
public void shuffle(boolean load) {
Gdx.app.debug("MusicListController", "shuffled.");
if (musicList.getTotal() == 0) {
currentPlaybackIndex = 0;
currentlyPlayingIndex = 0;
} else {
currentPlaybackIndex = rand.nextInt(musicList.getTotal());
currentlyPlayingIndex = rand.nextInt(musicList.getTotal());
}
if (load) {
loadMusic();
@ -148,13 +157,13 @@ public class MusicController extends Observable implements OnCompletionListener,
if (music != null) {
music.dispose();
}
if (currentPlaybackIndex < 0) {
currentPlaybackIndex = musicList.getTotal()-1;
if (currentlyPlayingIndex < 0) {
currentlyPlayingIndex = musicList.getTotal()-1;
}
if (currentPlaybackIndex >= musicList.getTotal()) {
currentPlaybackIndex = 0;
if (currentlyPlayingIndex >= musicList.getTotal()) {
currentlyPlayingIndex = 0;
}
this.music = Gdx.audio.newMusic(musicList.getMusicArray().get(currentPlaybackIndex));
this.music = Gdx.audio.newMusic(musicList.getMusicArray().get(currentlyPlayingIndex));
music.setOnCompletionListener(this);
setChanged();
@ -162,7 +171,7 @@ public class MusicController extends Observable implements OnCompletionListener,
if (autoPlay) {
play();
}
notifyObservers(States.Loaded);
notifyObservers(states.LOADED);
}
public MusicList getMusicList() {
@ -170,7 +179,7 @@ public class MusicController extends Observable implements OnCompletionListener,
}
public FileHandle getCurrentMusicFileHandle() {
return musicList.getSongFileHandleFromIndex(currentPlaybackIndex);
return musicList.getSongFileHandleFromIndex(currentlyPlayingIndex);
}
public MinimalAudioHeader getCurrentMusicHeader() {
@ -220,4 +229,8 @@ public class MusicController extends Observable implements OnCompletionListener,
public Music getCurrentMusic() {
return music;
}
public int getCurrentlyPlayingIndex() {
return currentlyPlayingIndex;
}
}

View File

@ -92,6 +92,11 @@ public class MusicList extends Observable {
searched = true;
}
/**
*
* @return the amount of audio files discovered.
*/
public int getTotal() {
return musicList.size;
}

View File

@ -51,8 +51,10 @@ public class MusicMetadataController extends Observable implements Disposable, O
}
public int size() {
synchronized (loadingThread) {
return metadataArray.size;
}
}
public AudioMetadata getMetadata(int index) {
synchronized (loadingThread) {

View File

@ -1,6 +1,8 @@
package zero1hd.rhythmbullet.graphics.ui.components;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
@ -9,6 +11,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Array;
import zero1hd.rhythmbullet.audio.metadata.AudioMetadata;
@ -21,13 +24,14 @@ public class MusicSelectable extends Button {
private float timeSinceOnScreen;
private AudioMetadata metadata;
private Texture defaultAlbumArt;
private Array<MusicSelectable> queueList;
public MusicSelectable(Skin skin, Texture defaultAlbumArt, AudioMetadata metadata) {
public MusicSelectable(Skin skin, Texture defaultAlbumArt, AudioMetadata metadata,
Array<MusicSelectable> queueList) {
super(skin, "music-selectable");
this.metadata = metadata;
this.defaultAlbumArt = defaultAlbumArt;
this.queueList = queueList;
album = new Image(defaultAlbumArt);
add(album).expand().left();
@ -47,7 +51,8 @@ public class MusicSelectable extends Button {
actualCoords.x = getX() + getParent().getX();
actualCoords.y = getY() + getParent().getY();
if (actualCoords.y < 0-getHeight() || actualCoords.y > getStage().getHeight() || actualCoords.x < 0-getWidth() || actualCoords.x > getStage().getWidth()) {
if (actualCoords.y < 0 - getHeight() || actualCoords.y > getStage().getHeight()
|| actualCoords.x < 0 - getWidth() || actualCoords.x > getStage().getWidth()) {
offScreenAct(delta);
} else {
onScreenAct(delta);
@ -55,11 +60,20 @@ public class MusicSelectable extends Button {
super.act(delta);
}
@Override
public void draw(Batch batch, float parentAlpha) {
synchronized (album) {
super.draw(batch, parentAlpha);
}
}
public void onScreenAct(float delta) {
timeSinceOnScreen = 0;
if (metadata.getAlbumCover() == null) {
metadata.loadAlbumCover();
album.setDrawable(new TextureRegionDrawable(new TextureRegion(metadata.getAlbumCover())));
if (!queueList.contains(this, true)) {
synchronized (queueList) {
queueList.add(this);
notify();
}
}
}
@ -67,8 +81,24 @@ public class MusicSelectable extends Button {
if (metadata.getAlbumCover() != null) {
timeSinceOnScreen += delta;
if (timeSinceOnScreen >= 2) {
album.setDrawable(new TextureRegionDrawable(new TextureRegion(defaultAlbumArt)));
metadata.unloadAlbumCover();
}
}
}
public void loadAlbumCover() {
metadata.loadAlbumCover();
synchronized (album) {
album.setDrawable(new TextureRegionDrawable(new TextureRegion(metadata.getAlbumCover())));
}
}
public AudioMetadata getMetadata() {
return metadata;
}
public FileHandle getFileHandle() {
return metadata.getFileHandle();
}
}

View File

@ -0,0 +1,60 @@
package zero1hd.rhythmbullet.util;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.scenes.scene2d.ui.ButtonGroup;
import com.badlogic.gdx.utils.Array;
import zero1hd.rhythmbullet.audio.metadata.AudioMetadata;
import zero1hd.rhythmbullet.graphics.ui.components.MusicSelectable;
public class MusicSelectableButtonGroup extends ButtonGroup<MusicSelectable> {
private Array<MusicSelectable> buttons;
public MusicSelectableButtonGroup() {
buttons = getButtons();
}
public void setChecked(AudioMetadata metadata) {
if (metadata == null) throw new IllegalArgumentException("metadata can't be null.");
MusicSelectable button;
for (int i = 0; i < buttons.size; i++) {
button = buttons.get(i);
if (button.getMetadata() == metadata) {
button.setChecked(true);
return;
}
}
}
public void setChecked(FileHandle fileHandle) {
if (fileHandle == null) throw new IllegalArgumentException("fileHandle can't be null.");
MusicSelectable button;
for (int i = 0; i < buttons.size; i++) {
button = buttons.get(i);
if (button.getFileHandle() == fileHandle) {
button.setChecked(true);
return;
}
}
}
public void selectNext() {
int index = getCheckedIndex() + 1;
if (index == buttons.size) {
index = 0;
}
buttons.get(index).setChecked(true);
}
public void selectPrevious() {
int index = getCheckedIndex() - 1;
if (index == -1) {
index = buttons.size -1;
}
buttons.get(index).setChecked(true);
}
public void setChecked(int index) {
buttons.get(index).setChecked(true);
}
}

View File

@ -10,6 +10,7 @@ import java.util.Observer;
import org.lwjgl.openal.AL11;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.assets.AssetLoaderParameters.LoadedCallback;
import com.badlogic.gdx.backends.lwjgl.audio.OpenALMusic;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.TimeUtils;
@ -23,7 +24,7 @@ import zero1hd.rhythmbullet.audio.visualizer.BasicFFT;
public class PCMMachine implements Observer, Disposable {
private int windowSize = 1024;
private float[] PCM = new float[windowSize];
private float[] frequencyBins = new float[windowSize/2];
private float[] frequencyBins = new float[windowSize / 2];
private BasicFFT fft = new BasicFFT(windowSize);
private ShortBuffer playingBuffer;
private ShortBuffer compareBuffer;
@ -45,7 +46,7 @@ public class PCMMachine implements Observer, Disposable {
bufferField.setAccessible(true);
Field bufferSizeField = ClassReflection.getDeclaredField(OpenALMusic.class, "bufferSize");
bufferSizeField.setAccessible(true);
bufferSizeField.set(null, new Integer(4096*5));
bufferSizeField.set(null, new Integer(4096 * 5));
buffer = ((ByteBuffer) bufferField.get(null)).asShortBuffer().asReadOnlyBuffer();
} catch (IllegalArgumentException | SecurityException | ReflectionException e) {
@ -62,43 +63,43 @@ public class PCMMachine implements Observer, Disposable {
for (int sid = 0; sid < PCM.length && sid < playingBuffer.remaining(); sid++) {
PCM[sid] = 0;
for (int channel = 0; channel < channelCount; channel ++) {
for (int channel = 0; channel < channelCount; channel++) {
if (PCM[sid] < (chanVal = playingBuffer.get())) {
PCM[sid] = chanVal;
}
}
PCM[sid] /= Short.MAX_VALUE+1f;
PCM[sid] /= Short.MAX_VALUE + 1f;
}
//Take down original buffer position so we don't need to sync again after...
// Take down original buffer position so we don't need to sync again after...
int originalPos = buffer.position();
//Begin comparison
// Begin comparison
buffer.rewind();
if (compareBuffer.compareTo(buffer) != 0) {
bufferChanged();
//Begin copying current buffer to the comparison buffer
// Begin copying current buffer to the comparison buffer
compareBuffer.clear();
compareBuffer.put(buffer);
compareBuffer.flip();
}
//Reset buffer to proper position.
// Reset buffer to proper position.
buffer.position(originalPos);
}
private void bufferChanged() {
//set position to beginning to prepare for overwrite
// set position to beginning to prepare for overwrite
playingBuffer.position(0);
//if the backing playing buffer is full, we have two sections: A and B. A is the one before B, and is read from due to playback latency and offset. B is merely a buffer.
// if the backing playing buffer is full, we have two sections: A and B. A is
// the one before B, and is read from due to playback latency and offset. B is
// merely a buffer.
if (playingBuffer.limit() == playingBuffer.capacity()) {
//put the second portion into the first...
// put the second portion into the first...
playingBuffer.put(playingBuffer.array(), buffer.capacity(), buffer.capacity());
}
//put the new buffer into the remaining portion.
// put the new buffer into the remaining portion.
playingBuffer.put(compareBuffer);
synchronizeBufferWithPlayback();
@ -106,13 +107,13 @@ public class PCMMachine implements Observer, Disposable {
private int calcBufferPosition() {
int offset = (int) alGetSourcef(sourceID, AL11.AL_SAMPLE_OFFSET);
offset = (offset/windowSize) * windowSize;
offset = (offset / windowSize) * windowSize;
return offset;
}
private void synchronizeBufferWithPlayback() {
playingBuffer.position(calcBufferPosition());
windowsRead = (int) ((mc.getCurrentPosition()*sampleRate)/windowSize);
windowsRead = (int) ((mc.getCurrentPosition() * sampleRate) / windowSize);
}
private void setMusic() {
@ -127,7 +128,7 @@ public class PCMMachine implements Observer, Disposable {
channelCount = mc.getCurrentMusicHeader().getChannelCount();
sampleRate = mc.getCurrentMusicHeader().getSampleRate();
playingBuffer = ShortBuffer.allocate(buffer.capacity()*2);
playingBuffer = ShortBuffer.allocate(buffer.capacity() * 2);
buffer.rewind();
playingBuffer.put(buffer);
playingBuffer.flip();
@ -170,14 +171,14 @@ public class PCMMachine implements Observer, Disposable {
timeOfLastRead = TimeUtils.millis();
paused = false;
}
waitTime = sampleRate/windowSize/Gdx.graphics.getFramesPerSecond();
waitTime = sampleRate / windowSize / Gdx.graphics.getFramesPerSecond();
if (TimeUtils.timeSinceMillis(timeOfLastRead) >= waitTime) {
calcPCMData();
updated = true;
windowsRead++;
timeOfLastRead = TimeUtils.millis();
currentPlaybackWindow = (int) ((mc.getCurrentPosition()*sampleRate)/windowSize);
currentPlaybackWindow = (int) ((mc.getCurrentPosition() * sampleRate) / windowSize);
if (windowsRead != currentPlaybackWindow) {
synchronizeBufferWithPlayback();
}
@ -214,15 +215,10 @@ public class PCMMachine implements Observer, Disposable {
@Override
public void update(Observable o, Object arg) {
if (o == mc) {
switch ((MusicController.States) arg) {
case Loaded:
if (arg == mc.states.LOADED) {
setMusic();
break;
case Playing:
} else if (arg == mc.states.PLAYING) {
streamReadThread.start();
break;
default:
break;
}
}
}

View File

@ -110,7 +110,7 @@ public class MainPage extends Page implements Observer {
@Override
public void update(Observable o, Object arg) {
if (o == mc && arg == MusicController.States.Loaded) {
if (o == mc && arg == mc.states.LOADED) {
scrollText.setText("Currently playing: " + mc.getCurrentSongName(), null);
}
}

View File

@ -233,7 +233,6 @@ public class MainScreen extends ScreenAdapter implements ResizeReadyScreen {
ChangeListener analysisPageButtonListener = new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
musicSelectionPage.getSelectedMusic();
setDisplayedPage(analysisPage);
}
};

View File

@ -1,6 +1,5 @@
package zero1hd.rhythmbullet.desktop.screens.main;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
@ -8,23 +7,20 @@ import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Stack;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Array;
import zero1hd.rhythmbullet.audio.MusicMetadataController;
@ -32,18 +28,21 @@ import zero1hd.rhythmbullet.audio.MusicController;
import zero1hd.rhythmbullet.desktop.graphics.ui.pages.Page;
import zero1hd.rhythmbullet.graphics.ui.components.MusicSelectable;
import zero1hd.rhythmbullet.graphics.ui.components.ScrollText;
import zero1hd.rhythmbullet.util.MusicSelectableButtonGroup;
public class MusicSelectionPage extends Page implements Observer {
Preferences musicFileAnnotation;
private MusicController mc;
private MusicMetadataController mmc;
private Array<MusicSelectable> selectables;
private MusicSelectableButtonGroup selectables;
private Stack stackSelectables;
private TextButton back;
private Table musicTable;
private ScrollPane musicTableScrollPane;
private musicSelectionLoaderThread thread;
private Table musicInfoTable;
private Table musicSubInfo;
private ScrollText songTitle;
@ -54,9 +53,9 @@ public class MusicSelectionPage extends Page implements Observer {
private Image albumCover;
private AssetManager assets;
private Skin skin;
private boolean down, up;
private int musicSelectableIndex;
private TextButton beginButton;
@ -67,6 +66,9 @@ public class MusicSelectionPage extends Page implements Observer {
this.assets = assetManager;
this.mc = musicController;
this.mmc = musicMetadataController;
this.skin = skin;
stackSelectables = new Stack();
selectables = new MusicSelectableButtonGroup();
musicFileAnnotation = Gdx.app.getPreferences("music_file_annotation");
musicTable = new Table();
@ -127,6 +129,10 @@ public class MusicSelectionPage extends Page implements Observer {
beginButton = new TextButton("Begin", skin);
beginButton.addListener(beginButtonListener);
mmc.addObserver(this);
thread = new musicSelectionLoaderThread();
}
@Override
@ -159,47 +165,51 @@ public class MusicSelectionPage extends Page implements Observer {
if (songSelectionTimer > 0f) {
songSelectionTimer -= delta;
if (songSelectionTimer <= 0f) {
setCurrentMusic();
}
}
if (selectables.getButtons().size == mc.getMusicList().getTotal()) {
if (selectables.getButtons().size != stackSelectables.getChildren().size) {
int index = selectables.getButtons().size - stackSelectables.getChildren().size - 1;
stackSelectables.add(selectables.getButtons().get(index));
} else {
if (selectables.getChecked() == null) {
selectables.setChecked(mc.getCurrentMusicFileHandle());
} else if (selectables.getChecked().getFileHandle() != mc.getCurrentMusicFileHandle()) {
songSelectionTimer += delta;
if (songSelectionTimer > 2f) {
mc.setMusicByFileHandle(selectables.getChecked().getFileHandle());
}
} else {
songSelectionTimer = 0;
}
}
}
super.act(delta);
}
private void scrollDown() {
if ((musicSelectableIndex = (musicTable.getChildren().indexOf(currentlySelected, true) + 1)) == musicTable.getChildren().size) {
musicSelectableIndex = 0;
}
((MusicSelectable)musicTable.getChildren().get(musicSelectableIndex)).select();
musicTableScrollPane.scrollTo(currentlySelected.getX(), currentlySelected.getY(), currentlySelected.getWidth(), currentlySelected.getHeight());
selectables.selectNext();
musicTableScrollPane.scrollTo(selectables.getChecked().getX(), selectables.getChecked().getY(), selectables.getChecked().getWidth(), selectables.getChecked().getHeight());
}
private void scrollUp() {
if ((musicSelectableIndex = (musicTable.getChildren().indexOf(currentlySelected, true) - 1)) < 0) {
musicSelectableIndex = musicTable.getChildren().size-1;
}
((MusicSelectable)musicTable.getChildren().get(musicSelectableIndex)).select();
musicTableScrollPane.scrollTo(currentlySelected.getX(), currentlySelected.getY(), currentlySelected.getWidth(), currentlySelected.getHeight());
selectables.selectPrevious();
musicTableScrollPane.scrollTo(selectables.getChecked().getX(), selectables.getChecked().getY(), selectables.getChecked().getWidth(), selectables.getChecked().getHeight());
}
public FileHandle getSelectedMusic() {
if (currentlySelected != null) {
return currentlySelected.getMusicFile();
} else {
return null;
}
return selectables.getChecked().getMetadata().getFileHandle();
}
public void refreshUIList() {
for (int i = 0; i < selectables.size; i++) {
selectables.get(i).dispose();
}
selectables.clear();
mmc.loadAudioMetadata();
musicTable.clear();
selectables.clear();
musicInfoTable.clear();
musicSubInfo.clear();
uiSongCount = 0;
uiSongInfoCount = 0;
Gdx.app.debug("MusicSelectionPage", "Refreshing...");
@ -226,16 +236,15 @@ public class MusicSelectionPage extends Page implements Observer {
super.dispose();
}
@Override
public void update(Observable o, Object arg) {
if (o == mc && arg == MusicController.States.Loaded) {
selectMusicUI(mc.getCurrentMusicFileHandle());
if (o == mc && arg == mc.states.LOADED) {
if (selectables.getChecked().getFileHandle() != mc.getCurrentMusicFileHandle()) {
selectables.setChecked(mc.getCurrentlyPlayingIndex());
}
} else if (o == mmc) {
thread.start();
}
public void selectMusicUI(FileHandle fileHandle) {
selectables.get(mc.getMusicList().getMusicArray().indexOf(fileHandle, true)).select();
}
@Override
@ -245,11 +254,49 @@ public class MusicSelectionPage extends Page implements Observer {
}
private class musicSelectionLoaderThread implements Runnable {
private Thread thread;
private Array<MusicSelectable> queueList;
private String name = "Music-Selection-Loader-Thread";
private volatile boolean work;
public musicSelectionLoaderThread() {
queueList = new Array<>();
}
@Override
public void run() {
// TODO Auto-generated method stub
while (work) {
while (selectables.getButtons().size != mc.getMusicList().getTotal()) {
MusicSelectable selectable = new MusicSelectable(skin, assets.get("defaultCover.png"), mmc.getMetadata(selectables.getButtons().size), queueList);
selectables.add(selectable);
}
for (int i = 0; i < queueList.size; i++) {
queueList.get(i).loadAlbumCover();
queueList.removeIndex(i);
}
synchronized (queueList) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public boolean start() {
if (thread == null) {
thread = new Thread(this, name);
thread.start();
return true;
}
synchronized (queueList) {
notify();
}
return false;
}
}
}
}