main difference: music selection screen now completely functional; other

refactoring and changes to make better use of the framework were made;
some cleanup happened;
This commit is contained in:
Harrison Deng 2018-08-18 22:19:08 -05:00
parent fa8dd9622f
commit d7008796f4
21 changed files with 521 additions and 470 deletions

View File

@ -86,7 +86,7 @@ public class MusicController extends Observable implements OnCompletionListener,
public void skip() { public void skip() {
currentlyPlayingIndex++; currentlyPlayingIndex++;
if (shuffle) { if (shuffle) {
shuffle(false); shuffle();
} }
loadMusic(); loadMusic();
} }
@ -97,7 +97,7 @@ public class MusicController extends Observable implements OnCompletionListener,
public void previous() { public void previous() {
currentlyPlayingIndex--; currentlyPlayingIndex--;
if (shuffle) { if (shuffle) {
shuffle(false); shuffle();
} }
loadMusic(); loadMusic();
} }
@ -106,7 +106,7 @@ public class MusicController extends Observable implements OnCompletionListener,
public void onCompletion(Music music) { public void onCompletion(Music music) {
if (autoPlay) { if (autoPlay) {
if (shuffle) { if (shuffle) {
shuffle(false); shuffle();
} else { } else {
currentlyPlayingIndex++; currentlyPlayingIndex++;
} }
@ -117,18 +117,14 @@ public class MusicController extends Observable implements OnCompletionListener,
/** /**
* Shuffles the controller whether the shuffle boolean is true or false. * Shuffles the controller whether the shuffle boolean is true or false.
* @param load whether this method should also make sure to load the music, dispose of the last one, etc. Normally called unless you plan to manually call it elsewhere.
*/ */
public void shuffle(boolean load) { public void shuffle() {
Gdx.app.debug("MusicListController", "shuffled."); Gdx.app.debug("MusicListController", "shuffled.");
if (musicList.getTotal() == 0) { if (musicList.getTotal() == 0) {
currentlyPlayingIndex = 0; currentlyPlayingIndex = 0;
} else { } else {
currentlyPlayingIndex = rand.nextInt(musicList.getTotal()); currentlyPlayingIndex = rand.nextInt(musicList.getTotal());
} }
if (load) {
loadMusic();
}
} }
public void setAutoPlay(boolean autoPlay) { public void setAutoPlay(boolean autoPlay) {
@ -152,6 +148,7 @@ public class MusicController extends Observable implements OnCompletionListener,
*/ */
public void loadMusic() { public void loadMusic() {
Gdx.app.debug("MusicListController", "music is being loaded and listeners are being notified."); Gdx.app.debug("MusicListController", "music is being loaded and listeners are being notified.");
boolean playing = isPlaying();
musicHeader = null; musicHeader = null;
if (music != null) { if (music != null) {
music.dispose(); music.dispose();
@ -169,7 +166,7 @@ public class MusicController extends Observable implements OnCompletionListener,
setChanged(); setChanged();
notifyObservers(states.LOADED); notifyObservers(states.LOADED);
if (autoPlay) { if (playing) {
play(); play();
} }
} }
@ -195,9 +192,12 @@ public class MusicController extends Observable implements OnCompletionListener,
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
if (o == musicList) { if (o == musicList) {
if (shuffle) { if (shuffle) {
shuffle(false); shuffle();
} }
loadMusic(); loadMusic();
if (autoPlay) {
play();
}
} }
} }

View File

@ -1,10 +1,12 @@
package zero1hd.rhythmbullet.audio; package zero1hd.rhythmbullet.audio;
import java.util.Comparator;
import java.util.Observable; import java.util.Observable;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Sort;
import zero1hd.rhythmbullet.audio.processor.AudioProcessor; import zero1hd.rhythmbullet.audio.processor.AudioProcessor;
import zero1hd.rhythmbullet.audio.processor.WAVAudioProcessor; import zero1hd.rhythmbullet.audio.processor.WAVAudioProcessor;
@ -20,19 +22,30 @@ public class MusicList extends Observable {
private AudioProcessorFactory audioProcFactory; private AudioProcessorFactory audioProcFactory;
private volatile boolean searched; private volatile boolean searched;
private String searchPath; private String searchPath;
private Comparator<FileHandle> compare;
public MusicList(AudioProcessorFactory audioProcessorFactory, String searchPath) { public MusicList(AudioProcessorFactory audioProcessorFactory, String searchPath) {
this.audioProcFactory = audioProcessorFactory; this.audioProcFactory = audioProcessorFactory;
musicList = new Array<>(); musicList = new Array<>();
setSearchPath(searchPath); setSearchPath(searchPath);
compare = new Comparator<FileHandle>() {
@Override
public int compare(FileHandle o1, FileHandle o2) {
return o1.nameWithoutExtension().compareTo(o2.nameWithoutExtension());
}
};
} }
/** /**
* Wrapper method that uses async refresh. * Wrapper method that uses async refresh.
* Also notifies listeners that are on the main thread. * Also notifies listeners that are on the main thread.
* @param refresh does a search whether or not path has changed. * @param refresh does a search whether or not path has changed and whether or not this list has searched before this.
*/ */
public void asyncSearch(boolean refresh) { public void asyncSearch(boolean refresh) {
if (refresh) { if (refresh) {
if (searchThread != null) { if (searchThread != null) {
if (!searchThread.start()) { if (!searchThread.start()) {
searchThread.stop(); searchThread.stop();
@ -45,7 +58,6 @@ public class MusicList extends Observable {
} }
} else { } else {
if (searched && !hasChanged()) { if (searched && !hasChanged()) {
setChanged();
notifyObservers(); notifyObservers();
} else { } else {
asyncSearch(true); asyncSearch(true);
@ -54,7 +66,7 @@ public class MusicList extends Observable {
} }
public void setSearchPath(String searchPath) { public void setSearchPath(String searchPath) {
hasChanged(); setChanged();
this.searchPath = searchPath; this.searchPath = searchPath;
} }
@ -88,6 +100,9 @@ public class MusicList extends Observable {
Gdx.app.debug("MusicList", "Warning, this list has not completed it's search..."); Gdx.app.debug("MusicList", "Warning, this list has not completed it's search...");
Thread.dumpStack(); Thread.dumpStack();
} }
if (musicList.size == 0) {
return null;
}
return newAudioProcessor(musicList.get(index)); return newAudioProcessor(musicList.get(index));
} }
@ -97,6 +112,9 @@ public class MusicList extends Observable {
Gdx.app.debug("MusicList", "Warning, this list has not completed it's search..."); Gdx.app.debug("MusicList", "Warning, this list has not completed it's search...");
Thread.dumpStack(); Thread.dumpStack();
} }
if (musicList.size == 0) {
return null;
}
return musicList.get(index); return musicList.get(index);
} }
@ -130,24 +148,15 @@ public class MusicList extends Observable {
@Override @Override
public void run() { public void run() {
Array<FileHandle> obtainedAudioFiles = recursiveMusicSearch(directory); Array<FileHandle> obtainedAudioFiles = recursiveMusicSearch(directory);
if (Gdx.files.external("RhythmBullet").exists()) { Sort.instance().sort(obtainedAudioFiles, compare);
if (!Gdx.files.external("RhythmBullet/Alan Walker - Spectre.mp3").exists()) {
Gdx.files.internal("music/Alan Walker - Spectre.mp3").copyTo(Gdx.files.external("RhythmBullet/Alan Walker - Spectre.mp3"));
}
} else {
Gdx.files.internal("music/Alan Walker - Spectre.mp3").copyTo(Gdx.files.external("RhythmBullet/Alan Walker - Spectre.mp3"));
}
if (work) { if (work) {
musicList = obtainedAudioFiles; musicList = obtainedAudioFiles;
musicList.add(Gdx.files.external("RhythmBullet/Alan Walker - Spectre.mp3"));
if (work) {
searched = true; searched = true;
Gdx.app.debug("MusicList", "recursive async search completed."); Gdx.app.debug("MusicList", "recursive async search completed.");
setChanged(); setChanged();
notifyObservers(); notifyObservers();
} }
} }
}
public boolean start() { public boolean start() {
if (thread == null) { if (thread == null) {

View File

@ -3,6 +3,7 @@ package zero1hd.rhythmbullet.audio;
import java.util.Observable; import java.util.Observable;
import java.util.Observer; import java.util.Observer;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.Disposable;
@ -74,10 +75,11 @@ public class MusicMetadataController extends Observable implements Disposable, O
private class MetadataLoadingThread implements Runnable { private class MetadataLoadingThread implements Runnable {
private Thread thread; private Thread thread;
private String name = "Metadata-Load"; private String name = "Metadata-Load";
private volatile boolean work; private volatile boolean work = true;
@Override @Override
public void run() { public void run() {
Gdx.app.debug(name, "loading...");
searching = true; searching = true;
for (int i = 0; i < metadataArray.size; i++) { for (int i = 0; i < metadataArray.size; i++) {
metadataArray.get(i).dispose(); metadataArray.get(i).dispose();
@ -101,6 +103,8 @@ public class MusicMetadataController extends Observable implements Disposable, O
} }
if (work) { if (work) {
searching = false; searching = false;
Gdx.app.debug(name, "load complete.");
setChanged();
notifyObservers(); notifyObservers();
} }
} }
@ -123,7 +127,6 @@ public class MusicMetadataController extends Observable implements Disposable, O
@Override @Override
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
if (o == musicList) { if (o == musicList) {
loadingThread.stop();
loadAudioMetadata(); loadAudioMetadata();
} }
} }

View File

@ -46,7 +46,7 @@ public interface AudioMetadata extends Disposable {
public int getLength(); public int getLength();
/** /**
* * Requires a OpenGL context.
* @return the texture. Needs to be loaded before hand or else will return null. * @return the texture. Needs to be loaded before hand or else will return null.
*/ */
public Texture getAlbumCover(); public Texture getAlbumCover();

View File

@ -1,6 +1,8 @@
package zero1hd.rhythmbullet.audio.metadata; package zero1hd.rhythmbullet.audio.metadata;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.jaudiotagger.audio.AudioFileIO; import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.exceptions.CannotReadException; import org.jaudiotagger.audio.exceptions.CannotReadException;
@ -11,6 +13,7 @@ import org.jaudiotagger.audio.mp3.MP3File;
import org.jaudiotagger.tag.TagException; import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.id3.ID3v23FieldKey; import org.jaudiotagger.tag.id3.ID3v23FieldKey;
import org.jaudiotagger.tag.id3.ID3v23Tag; import org.jaudiotagger.tag.id3.ID3v23Tag;
import org.jaudiotagger.tag.images.Artwork;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.files.FileHandle;
@ -22,6 +25,7 @@ public class MP3Metadata implements AudioMetadata {
private int length; private int length;
private Texture albumCover; private Texture albumCover;
private FileHandle fileHandle; private FileHandle fileHandle;
private byte[] imageData;
public MP3Metadata(FileHandle fileHandle) { public MP3Metadata(FileHandle fileHandle) {
this.fileHandle = fileHandle; this.fileHandle = fileHandle;
@ -40,13 +44,15 @@ public class MP3Metadata implements AudioMetadata {
length = mp3file.getAudioHeader().getTrackLength(); length = mp3file.getAudioHeader().getTrackLength();
int min = (int) (length/60); SimpleDateFormat f = new SimpleDateFormat("m:ss");
duration = (length/60) + ":" + (length - (min*60)); duration = f.format(new Date(length*1000));
author = tag.getFirst(ID3v23FieldKey.ARTIST); author = tag.getFirst(ID3v23FieldKey.ARTIST);
genre = tag.getFirst(ID3v23FieldKey.GENRE); genre = tag.getFirst(ID3v23FieldKey.GENRE);
title = tag.getFirst(ID3v23FieldKey.TITLE); title = tag.getFirst(ID3v23FieldKey.TITLE);
if (title.isEmpty()) {
title = fileHandle.nameWithoutExtension();
}
} catch (IOException | CannotWriteException | CannotReadException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) { } catch (IOException | CannotWriteException | CannotReadException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
Gdx.app.error("MP3Metadata", "Failed to read metadata of file: " + fileHandle.name()); Gdx.app.error("MP3Metadata", "Failed to read metadata of file: " + fileHandle.name());
} }
@ -57,25 +63,17 @@ public class MP3Metadata implements AudioMetadata {
MP3File mp3file; MP3File mp3file;
try { try {
mp3file = (MP3File) AudioFileIO.read(fileHandle.file()); mp3file = (MP3File) AudioFileIO.read(fileHandle.file());
Artwork art = mp3file.getTag().getFirstArtwork();
if (art != null) {
imageData = art.getBinaryData();
byte[] imageData = mp3file.getTag().getFirstArtwork().getBinaryData(); }
Pixmap pixmap = new Pixmap(imageData, 0, imageData.length);
albumCover = new Texture(pixmap);
pixmap.dispose();
} catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) { } catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@Override
public void unloadAlbumCover() {
if (albumCover != null) {
albumCover.dispose();
albumCover = null;
}
}
@Override @Override
public String getAuthor() { public String getAuthor() {
return author; return author;
@ -101,8 +99,22 @@ public class MP3Metadata implements AudioMetadata {
return genre; return genre;
} }
@Override
public void unloadAlbumCover() {
if (albumCover != null) {
albumCover.dispose();
albumCover = null;
}
}
@Override @Override
public Texture getAlbumCover() { public Texture getAlbumCover() {
if (albumCover == null && imageData != null) {
Pixmap pixmap = new Pixmap(imageData, 0, imageData.length);
albumCover = new Texture(pixmap);
pixmap.dispose();
imageData = null;
}
return albumCover; return albumCover;
} }

View File

@ -1,6 +1,8 @@
package zero1hd.rhythmbullet.audio.metadata; package zero1hd.rhythmbullet.audio.metadata;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.jaudiotagger.audio.AudioFile; import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO; import org.jaudiotagger.audio.AudioFileIO;
@ -10,6 +12,7 @@ import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.tag.FieldKey; import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.Tag; import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.TagException; import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.images.Artwork;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.files.FileHandle;
@ -21,6 +24,7 @@ public class WAVMetadata implements AudioMetadata {
private int length; private int length;
private Texture albumCover; private Texture albumCover;
private FileHandle fileHandle; private FileHandle fileHandle;
private byte[] imageData;
public WAVMetadata(FileHandle fileHandle) { public WAVMetadata(FileHandle fileHandle) {
this.fileHandle = fileHandle; this.fileHandle = fileHandle;
@ -28,13 +32,16 @@ public class WAVMetadata implements AudioMetadata {
try { try {
AudioFile wav = AudioFileIO.read(fileHandle.file()); AudioFile wav = AudioFileIO.read(fileHandle.file());
length = wav.getAudioHeader().getTrackLength(); length = wav.getAudioHeader().getTrackLength();
int min = (int) (length/60); SimpleDateFormat f = new SimpleDateFormat("m:ss");
this.duration = (length/60) + ":" + (length - (min*60)); duration = f.format(new Date(length*1000));
Tag tag = wav.getTag(); Tag tag = wav.getTag();
title = tag.getFirst(FieldKey.TITLE); title = tag.getFirst(FieldKey.TITLE);
author = tag.getFirst(FieldKey.ARTIST); author = tag.getFirst(FieldKey.ARTIST);
genre = tag.getFirst(FieldKey.GENRE); genre = tag.getFirst(FieldKey.GENRE);
if (title.isEmpty()) {
title = fileHandle.nameWithoutExtension();
}
} catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) { } catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
Gdx.app.error("WAVMetadata", "Failed to read metadata of file: " + fileHandle.name()); Gdx.app.error("WAVMetadata", "Failed to read metadata of file: " + fileHandle.name());
} }
@ -44,11 +51,10 @@ public class WAVMetadata implements AudioMetadata {
public void loadAlbumCover() { public void loadAlbumCover() {
try { try {
AudioFile wav = AudioFileIO.read(fileHandle.file()); AudioFile wav = AudioFileIO.read(fileHandle.file());
Artwork art = wav.getTag().getFirstArtwork();
byte[] imageData = wav.getTag().getFirstArtwork().getBinaryData(); if (art != null) {
Pixmap pixmap = new Pixmap(imageData, 0, imageData.length); imageData = art.getBinaryData();
albumCover = new Texture(pixmap); }
pixmap.dispose();
} catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) { } catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
e.printStackTrace(); e.printStackTrace();
@ -82,6 +88,12 @@ public class WAVMetadata implements AudioMetadata {
@Override @Override
public Texture getAlbumCover() { public Texture getAlbumCover() {
if (albumCover == null && imageData != null) {
Pixmap pixmap = new Pixmap(imageData, 0, imageData.length);
albumCover = new Texture(pixmap);
pixmap.dispose();
imageData = null;
}
return albumCover; return albumCover;
} }
@Override @Override

View File

@ -21,13 +21,13 @@ public class DoubleHorizontalVisualizer implements Disposable {
private int binsPerBar; private int binsPerBar;
private float offset; private float offset;
private int boundaryThickness; private int boundaryThickness;
private float baseSensitivity;
private float targetDelta; private float targetDelta;
private float spacePercentage = 0.7f; private float spacePercentage = 0.7f;
private int barCount = 90; private float baseSensitivity = 0.009f;
private float barChangeRate = 14f; private int barCount = 120;
private int smoothRange = 1; private float barChangeRate = 7f;
private int binsToInclude = 180; private int smoothRange = 2;
private int binsToInclude = 120;
private Color color = new Color(0.5f, 0.6f, 0.8f, 0.46f); private Color color = new Color(0.5f, 0.6f, 0.8f, 0.46f);
/** /**
* *
@ -35,11 +35,11 @@ public class DoubleHorizontalVisualizer implements Disposable {
* @param width the width of the visualizer. * @param width the width of the visualizer.
* @param spacePercentage the percentage of a bar that should be space. * @param spacePercentage the percentage of a bar that should be space.
*/ */
public DoubleHorizontalVisualizer(int width, int height, float baseSensitivity, int boundaryThickness, int targetFPS, MusicController musicController, PCMSystem PCMSystem) { public DoubleHorizontalVisualizer(int width, int height, float heightSensitivity, int boundaryThickness, int targetFPS, MusicController musicController, PCMSystem PCMSystem) {
this.barWidth = width/barCount; this.barWidth = width/barCount;
this.spaceBetweenBars = MathUtils.round(barWidth * spacePercentage); this.spaceBetweenBars = MathUtils.round(barWidth * spacePercentage);
this.barWidth -= spaceBetweenBars; this.barWidth -= spaceBetweenBars;
this.baseSensitivity = baseSensitivity; this.baseSensitivity *= heightSensitivity;
pcm = PCMSystem; pcm = PCMSystem;
binsPerBar = (binsToInclude/barCount); binsPerBar = (binsToInclude/barCount);
this.width = width; this.width = width;
@ -55,11 +55,10 @@ public class DoubleHorizontalVisualizer implements Disposable {
public void act(float delta) { public void act(float delta) {
float[] freqBins = pcm.getFrequencyBins(); float[] freqBins = pcm.getFrequencyBins();
for (int bar = 0; bar < amplitudes.length; bar++) { for (int bar = 0; bar < amplitudes.length; bar++) {
float normalizedAmplitude = 0; amplitudes[bar] = 0;
for (int freq = bar*binsPerBar; freq < (bar*binsPerBar) + binsPerBar; freq++) { for (int freq = bar*binsPerBar; freq < (bar*binsPerBar) + binsPerBar; freq++) {
normalizedAmplitude += Math.abs(freqBins[freq]); amplitudes[bar] += Math.abs(freqBins[freq]) * baseSensitivity;
} }
amplitudes[bar] += normalizedAmplitude * baseSensitivity;
amplitudes[bar] /= binsPerBar; amplitudes[bar] /= binsPerBar;
} }
for (int bar = 0; bar < barHeights.length; bar++) { for (int bar = 0; bar < barHeights.length; bar++) {
@ -78,13 +77,22 @@ public class DoubleHorizontalVisualizer implements Disposable {
int pixelsMoved = 0; int pixelsMoved = 0;
pixelsMoved = MathUtils.round(amplitudes[bar] - barHeights[bar]); int difference = MathUtils.round(amplitudes[bar] - barHeights[bar]);
pixelsMoved = MathUtils.floor(pixelsMoved*targetDelta*barChangeRate); pixelsMoved = MathUtils.floor(difference*targetDelta*barChangeRate);
if (pixelsMoved >= 0) {
if (barHeights[bar] + pixelsMoved > amplitudes[bar]) {
barHeights[bar] += MathUtils.round(difference*targetDelta);
} else {
barHeights[bar] += pixelsMoved; barHeights[bar] += pixelsMoved;
}
} else {
if (barHeights[bar] + pixelsMoved < amplitudes[bar]) {
barHeights[bar] += MathUtils.round(difference*targetDelta);
} else {
barHeights[bar] += pixelsMoved;
}
}
if (barHeights[bar] < 0) barHeights[bar] = 0;
} }
} }
@ -103,7 +111,7 @@ public class DoubleHorizontalVisualizer implements Disposable {
for (int bar = 0; bar < barCount; bar++) { for (int bar = 0; bar < barCount; bar++) {
shapeRenderer.setColor(color); shapeRenderer.setColor(color);
shapeRenderer.rect(offset + (spaceBetweenBars+barWidth)*bar, y+height, barWidth, barHeights[bar]); shapeRenderer.rect(offset + (spaceBetweenBars+barWidth)*bar, y+height, barWidth, barHeights[bar]);
shapeRenderer.rect(offset + (spaceBetweenBars+barWidth)*bar, y-barHeights[barHeights.length - 1 - bar], barWidth, barHeights[barHeights.length - 1 - bar]); shapeRenderer.rect(offset + (spaceBetweenBars+barWidth)*bar, y, barWidth, -barHeights[barHeights.length - 1 - bar]);
} }
shapeRenderer.end(); shapeRenderer.end();
Gdx.gl.glDisable(GL20.GL_BLEND); Gdx.gl.glDisable(GL20.GL_BLEND);

View File

@ -66,4 +66,8 @@ public class Page extends Group implements Disposable {
public void dispose() { public void dispose() {
Gdx.app.debug(getClass().getSimpleName(), "Disposing..."); Gdx.app.debug(getClass().getSimpleName(), "Disposing...");
} }
public void simpleDebug(String message) {
Gdx.app.debug(getClass().getSimpleName(), message);
}
} }

View File

@ -1,4 +1,4 @@
package zero1hd.rhythmbullet.desktop.graphics.ui.components; package zero1hd.rhythmbullet.graphics.ui.components;
import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputEvent;
@ -19,11 +19,7 @@ public class MusicControls extends HorizontalGroup {
reverse.addListener(new ChangeListener() { reverse.addListener(new ChangeListener() {
@Override @Override
public void changed(ChangeEvent event, Actor actor) { public void changed(ChangeEvent event, Actor actor) {
boolean wasPlaying = sc.isPlaying();
sc.previous(); sc.previous();
if (wasPlaying) {
sc.play();
}
} }
}); });
addActor(reverse); addActor(reverse);
@ -56,11 +52,7 @@ public class MusicControls extends HorizontalGroup {
forward.addListener(new ChangeListener() { forward.addListener(new ChangeListener() {
@Override @Override
public void changed(ChangeEvent event, Actor actor) { public void changed(ChangeEvent event, Actor actor) {
boolean wasPlaying = sc.isPlaying();
sc.skip(); sc.skip();
if (wasPlaying) {
sc.play();
}
} }
}); });
addActor(forward); addActor(forward);

View File

@ -1,108 +0,0 @@
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;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
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;
public class MusicSelectable extends Button {
private Vector2 actualCoords;
private Image album;
private Table informationTable;
private ShortenedLabel name, artist;
private Label time;
private float timeSinceOnScreen;
private AudioMetadata metadata;
private Texture defaultAlbumArt;
private Array<MusicSelectable> queueList;
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();
informationTable = new Table();
name = new ShortenedLabel(metadata.getTitle(), skin, "default-font", skin.getColor("default"));
informationTable.add(name).colspan(2).expandX();
informationTable.row();
artist = new ShortenedLabel(metadata.getTitle(), skin, "sub-font", skin.getColor("default"));
informationTable.add(artist).expandX();
time = new Label(metadata.getDuration(), skin, "sub-font", skin.getColor("default"));
informationTable.add(time).expandX();
add(informationTable).expand().fill();
}
@Override
public void act(float delta) {
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()) {
offScreenAct(delta);
} else {
onScreenAct(delta);
}
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 (!queueList.contains(this, true)) {
synchronized (queueList) {
queueList.add(this);
notify();
}
}
}
public void offScreenAct(float delta) {
if (metadata.getAlbumCover() != null) {
timeSinceOnScreen += delta;
if (timeSinceOnScreen >= 2) {
album.setDrawable(new TextureRegionDrawable(new TextureRegion(defaultAlbumArt)));
metadata.unloadAlbumCover();
}
}
}
public void loadAlbumCover() {
metadata.loadAlbumCover();
Texture texture = defaultAlbumArt;
if (metadata.getAlbumCover() != null) {
texture = metadata.getAlbumCover();
}
synchronized (album) {
album.setDrawable(new TextureRegionDrawable(new TextureRegion(texture)));
}
}
public AudioMetadata getMetadata() {
return metadata;
}
public FileHandle getFileHandle() {
return metadata.getFileHandle();
}
}

View File

@ -1,59 +0,0 @@
package zero1hd.rhythmbullet.graphics.ui.components;
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;
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

@ -1,61 +0,0 @@
package zero1hd.rhythmbullet.graphics.ui.components;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
public class ShortenedLabel extends Label {
private String originalText;
private int targetWidth;
private GlyphLayout gl;
private BitmapFont font;
private Vector2 size;
public ShortenedLabel(CharSequence text, Skin skin, String fontName, Color color) {
super(null, skin, fontName, color);
originalText = text.toString();
font = skin.getFont(fontName);
if (text != null) {
gl = new GlyphLayout(skin.getFont(fontName), text);
}
size = new Vector2();
setWrap(true);
}
public void resize() {
setToOriginalText();
String text = getText().toString();
while (gl.width > targetWidth && (text.length() - 4) > 0) {
text = text.substring(0, text.length() - 4).concat("...");
gl.setText(font, text);
}
setText(text);
}
public void setToOriginalText() {
setText(originalText);
gl.setText(font, originalText);
}
@Override
public void layout() {
super.layout();
size.x = getWidth();
size.y = getHeight();
targetWidth = (int) getStage().stageToScreenCoordinates(size).x;
resize();
}
public void setOriginalText(String originalText) {
this.originalText = originalText;
gl.setText(font, originalText);
}
public int getTargetWidth() {
return targetWidth;
}
}

View File

@ -3,9 +3,12 @@ package zero1hd.rhythmbullet.util;
public interface ScreenConfiguration { public interface ScreenConfiguration {
public void setFramesPerSecond(int fps); public void setFramesPerSecond(int fps);
public void setVsync(boolean useVsync); public int getTargetFramesPerSecond();
public int getFramesPerSecond(); /**
* @param useVsync whether or not to use vSync.
*/
public void setVsync(boolean useVsync);
public boolean getVsync(); public boolean getVsync();
@ -22,4 +25,9 @@ public interface ScreenConfiguration {
public void setWindowLocationY(int y); public void setWindowLocationY(int y);
public void setWindowLocation(int x, int y); public void setWindowLocation(int x, int y);
/**
* Restarts the application.
*/
public void restart();
} }

View File

@ -1,24 +1,37 @@
package zero1hd.rhythmbullet.desktop; package zero1hd.rhythmbullet.desktop;
import java.awt.Canvas;
import java.awt.Color;
import javax.swing.JFrame;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.AWTGLCanvas;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.badlogic.gdx.backends.lwjgl.LwjglGraphics;
import zero1hd.rhythmbullet.RhythmBullet; import zero1hd.rhythmbullet.RhythmBullet;
import zero1hd.rhythmbullet.desktop.screens.SplashScreen; import zero1hd.rhythmbullet.desktop.screens.SplashScreen;
public class DesktopLauncher { public class DesktopLauncher {
public static void main (String[] arg) { public static void main (String[] arg) {
RhythmBullet core; RhythmBullet core;
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
DesktopScreenConfiguration screenConfig = new DesktopScreenConfiguration(config); DesktopScreenConfiguration screenConfig = new DesktopScreenConfiguration(config);
config.title = "Rhythm Bullet"; config.title = "Rhythm Bullet";
config.resizable = false; config.resizable = false;
config.useHDPI = true; config.useHDPI = true;
config.samples = 2; config.samples = 2;
config.width = 512; config.allowSoftwareMode = true;
config.height = 512;
core = new RhythmBullet(); core = new RhythmBullet();
core.setup(new SplashScreen(), new DesktopAssetPack(), screenConfig); core.setup(new SplashScreen(), new DesktopAssetPack(), screenConfig);
new LwjglApplication(core, config);
while (screenConfig.shouldStart()) {
LwjglApplication app = new LwjglApplication(core, config);
}
} }
} }

View File

@ -2,13 +2,14 @@ package zero1hd.rhythmbullet.desktop;
import org.lwjgl.opengl.Display; import org.lwjgl.opengl.Display;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import zero1hd.rhythmbullet.util.ScreenConfiguration; import zero1hd.rhythmbullet.util.ScreenConfiguration;
public class DesktopScreenConfiguration implements ScreenConfiguration { public class DesktopScreenConfiguration implements ScreenConfiguration {
private LwjglApplicationConfiguration configuration; private LwjglApplicationConfiguration configuration;
private boolean start = true;
public DesktopScreenConfiguration(LwjglApplicationConfiguration configuration) { public DesktopScreenConfiguration(LwjglApplicationConfiguration configuration) {
this.configuration = configuration; this.configuration = configuration;
} }
@ -18,16 +19,20 @@ public class DesktopScreenConfiguration implements ScreenConfiguration {
configuration.foregroundFPS = fps; configuration.foregroundFPS = fps;
} }
@Override
public int getTargetFramesPerSecond() {
return configuration.foregroundFPS;
}
/**
* Requires restart. Can be done by calling {@link #restart()}
*/
@Override @Override
public void setVsync(boolean useVsync) { public void setVsync(boolean useVsync) {
configuration.vSyncEnabled = useVsync; configuration.vSyncEnabled = useVsync;
} }
@Override
public int getFramesPerSecond() {
return configuration.foregroundFPS;
}
@Override @Override
public boolean getVsync() { public boolean getVsync() {
return configuration.vSyncEnabled; return configuration.vSyncEnabled;
@ -67,4 +72,16 @@ public class DesktopScreenConfiguration implements ScreenConfiguration {
public void setWindowLocation(int x, int y) { public void setWindowLocation(int x, int y) {
Display.setLocation(x, y); Display.setLocation(x, y);
} }
@Override
public void restart() {
Gdx.app.exit();
start = true;
}
public boolean shouldStart() {
boolean should = start;
start = false;
return should;
}
} }

View File

@ -34,7 +34,7 @@ public class PCMObtainer implements Observer, PCMSystem {
private ShortBuffer playingBuffer; private ShortBuffer playingBuffer;
private ShortBuffer intermediateBuffer; private ShortBuffer intermediateBuffer;
private ShortBuffer buffer; private ShortBuffer buffer;
private int sourceID; private volatile int sourceID;
private int channelCount; private int channelCount;
private MusicController mc; private MusicController mc;
private BufferStreamReadThread streamReadThread; private BufferStreamReadThread streamReadThread;
@ -177,13 +177,7 @@ public class PCMObtainer implements Observer, PCMSystem {
windowsRead++; windowsRead++;
//contemplate synchronization //contemplate synchronization
try {
currentPlaybackWindow = MathUtils.round((mc.getCurrentPosition() * sampleRate) / windowSize); currentPlaybackWindow = MathUtils.round((mc.getCurrentPosition() * sampleRate) / windowSize);
} catch (UnsatisfiedLinkError ule) {
if (run) {
ule.printStackTrace();
}
}
if (windowsRead != currentPlaybackWindow) { if (windowsRead != currentPlaybackWindow) {
synchronizeBufferWithPlayback(); synchronizeBufferWithPlayback();
} }

View File

@ -22,6 +22,7 @@ public class SplashScreen extends ScreenAdapter implements InitialScreen {
@Override @Override
public void init() { public void init() {
stage = new Stage(new ScreenViewport());
splash = new Texture(Gdx.files.internal("splashlogo.png")); splash = new Texture(Gdx.files.internal("splashlogo.png"));
zero1HD = new Image(splash); zero1HD = new Image(splash);
} }
@ -47,14 +48,19 @@ public class SplashScreen extends ScreenAdapter implements InitialScreen {
@Override @Override
public void postAssetLoad() { public void postAssetLoad() {
stage = new Stage(new ScreenViewport()); zero1HD.setScale((stage.getHeight()*0.8f)/(zero1HD.getHeight()));
stage.addActor(zero1HD);
zero1HD.setScale((Gdx.graphics.getHeight()*0.5f)/zero1HD.getHeight()*zero1HD.getScaleY());
zero1HD.setColor(0f,1f,1f,0f); zero1HD.setColor(0f,1f,1f,0f);
zero1HD.setPosition((stage.getWidth() - zero1HD.getWidth()*zero1HD.getScaleX())/2f, (stage.getHeight() - zero1HD.getHeight()*zero1HD.getScaleY())/2f); zero1HD.setPosition((stage.getWidth() - (zero1HD.getWidth()*zero1HD.getScaleX()))/2f, (stage.getHeight() - (zero1HD.getHeight()*zero1HD.getScaleY()))/2f);
stage.addActor(zero1HD);
zero1HD.addAction(Actions.sequence(Actions.color(Color.WHITE, 1f), Actions.fadeOut(0.5f))); zero1HD.addAction(Actions.sequence(Actions.color(Color.WHITE, 1f), Actions.fadeOut(0.5f)));
} }
@Override
public void resize(int width, int height) {
stage.getViewport().update(width, height);
super.resize(width, height);
}
@Override @Override
public Screen createMainScreen(RhythmBullet game) { public Screen createMainScreen(RhythmBullet game) {
return new MainScreen(game); return new MainScreen(game);

View File

@ -20,8 +20,8 @@ import zero1hd.rhythmbullet.RhythmBullet;
import zero1hd.rhythmbullet.audio.MusicController; import zero1hd.rhythmbullet.audio.MusicController;
import zero1hd.rhythmbullet.audio.visualizer.DoubleHorizontalVisualizer; import zero1hd.rhythmbullet.audio.visualizer.DoubleHorizontalVisualizer;
import zero1hd.rhythmbullet.desktop.audio.PCMObtainer; import zero1hd.rhythmbullet.desktop.audio.PCMObtainer;
import zero1hd.rhythmbullet.desktop.graphics.ui.components.MusicControls;
import zero1hd.rhythmbullet.graphics.ui.Page; import zero1hd.rhythmbullet.graphics.ui.Page;
import zero1hd.rhythmbullet.graphics.ui.components.MusicControls;
import zero1hd.rhythmbullet.graphics.ui.components.ScrollText; import zero1hd.rhythmbullet.graphics.ui.components.ScrollText;
import zero1hd.rhythmbullet.util.ScreenConfiguration; import zero1hd.rhythmbullet.util.ScreenConfiguration;
@ -46,7 +46,7 @@ public class MainPage extends Page implements Observer {
this.mc = musicController; this.mc = musicController;
this.mc.addObserver(this); this.mc.addObserver(this);
dhv = new DoubleHorizontalVisualizer((int) getWidth(), (int) 0, 2.5f, 0, screenConfiguration.getFramesPerSecond(), mc, new PCMObtainer(mc)); dhv = new DoubleHorizontalVisualizer((int) getWidth(), (int) 0, getHeight(), 0, screenConfiguration.getTargetFramesPerSecond(), mc, new PCMObtainer(mc));
dhv.setPosition(0, (int) ((getHeight() - dhv.getHeight())/2f)); dhv.setPosition(0, (int) ((getHeight() - dhv.getHeight())/2f));
title = new Image(assetManager.get("title.png", Texture.class)); title = new Image(assetManager.get("title.png", Texture.class));
@ -95,7 +95,6 @@ public class MainPage extends Page implements Observer {
scrollText.setWidth(0.5f*getWidth()); scrollText.setWidth(0.5f*getWidth());
scrollText.setPosition(15, getHeight() - scrollText.getHeight()-30f); scrollText.setPosition(15, getHeight() - scrollText.getHeight()-30f);
addActor(scrollText); addActor(scrollText);
} }
@Override @Override

View File

@ -62,7 +62,6 @@ public class MainScreen extends ScreenAdapter implements ResizeReadyScreen {
musicController = new MusicController(musicList, core.getPreferences()); musicController = new MusicController(musicList, core.getPreferences());
musicController.setAutoPlay(true); musicController.setAutoPlay(true);
musicController.setShuffle(true); musicController.setShuffle(true);
musicMetadataController = new MusicMetadataController(musicList); musicMetadataController = new MusicMetadataController(musicList);
listeners = new Listeners(); listeners = new Listeners();
@ -110,6 +109,7 @@ public class MainScreen extends ScreenAdapter implements ResizeReadyScreen {
} }
background = null; background = null;
musicController.deleteObservers(); musicController.deleteObservers();
musicMetadataController.deleteObservers();
} }
@Override @Override
@ -161,8 +161,6 @@ public class MainScreen extends ScreenAdapter implements ResizeReadyScreen {
} }
}); });
musicController.addObserver(musicSelectionPage);
musicController.addObserver(mainPage);
musicController.getMusicList().asyncSearch(false); musicController.getMusicList().asyncSearch(false);
resizing = false; resizing = false;
} }

View File

@ -10,27 +10,32 @@ import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.ui.ButtonGroup;
import com.badlogic.gdx.scenes.scene2d.ui.Image; import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane; import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
import com.badlogic.gdx.scenes.scene2d.ui.Skin; 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.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import zero1hd.rhythmbullet.audio.MusicMetadataController; import zero1hd.rhythmbullet.audio.MusicMetadataController;
import zero1hd.rhythmbullet.audio.metadata.AudioMetadata;
import zero1hd.rhythmbullet.audio.MusicController; import zero1hd.rhythmbullet.audio.MusicController;
import zero1hd.rhythmbullet.graphics.ui.Page; import zero1hd.rhythmbullet.graphics.ui.Page;
import zero1hd.rhythmbullet.graphics.ui.components.MusicSelectable;
import zero1hd.rhythmbullet.graphics.ui.components.MusicSelectableButtonGroup;
import zero1hd.rhythmbullet.graphics.ui.components.ScrollText; import zero1hd.rhythmbullet.graphics.ui.components.ScrollText;
public class MusicSelectionPage extends Page implements Observer { public class MusicSelectionPage extends Page implements Observer {
@ -39,21 +44,13 @@ public class MusicSelectionPage extends Page implements Observer {
private MusicController mc; private MusicController mc;
private MusicMetadataController mmc; private MusicMetadataController mmc;
private MusicSelectableButtonGroup selectables; private MusicSelectableButtonGroup selectables;
private Stack stackSelectables; private VerticalGroup vGroup;
private TextButton back; private TextButton back;
private ScrollPane musicTableScrollPane; private ScrollPane scrollPane;
private ClickListener selectionListener; private musicSelectionLoaderThread selectionLoaderThread;
private musicSelectionLoaderThread thread;
private Table musicInfoTable; private InformationTable musicInfoTable;
private Table musicSubInfo;
private ScrollText songTitle;
private Label author;
private Label songLength;
private Label previousTop;
private Label ratedDifficulty;
private Image albumCover;
private AssetManager assets; private AssetManager assets;
private Skin skin; private Skin skin;
@ -63,23 +60,25 @@ public class MusicSelectionPage extends Page implements Observer {
private TextButton beginButton; private TextButton beginButton;
private float scrollTimer, scrollDelay = 0.2f, scrollDelMod, songSelectionTimer; private float scrollTimer, scrollDelay = 0.2f, scrollDelMod, songSelectionTimer;
private float musicSelectDelay;
public MusicSelectionPage(AssetManager assetManager, Skin skin, MusicController musicController, MusicMetadataController musicMetadataController, ChangeListener backButtonListener, ChangeListener beginButtonListener) { public MusicSelectionPage(AssetManager assetManager, Skin skin, MusicController musicController, MusicMetadataController musicMetadataController, ChangeListener backButtonListener, ChangeListener beginButtonListener) {
super(1, 0); super(1, 0);
this.assets = assetManager; this.assets = assetManager;
this.mc = musicController; this.mc = musicController;
this.mmc = musicMetadataController; this.mmc = musicMetadataController;
this.skin = skin; this.skin = skin;
stackSelectables = new Stack(); vGroup = new VerticalGroup();
vGroup.space(10f);
selectables = new MusicSelectableButtonGroup(); selectables = new MusicSelectableButtonGroup();
selectables.setMinCheckCount(0);
musicFileAnnotation = Gdx.app.getPreferences("music_file_annotation"); musicFileAnnotation = Gdx.app.getPreferences("music_file_annotation");
musicTableScrollPane = new ScrollPane(stackSelectables, skin); scrollPane = new ScrollPane(vGroup, skin);
musicTableScrollPane.setSize(0.45f*getWidth(), getHeight()); scrollPane.setSize(0.45f*getWidth(), getHeight());
musicTableScrollPane.setFadeScrollBars(false); scrollPane.setOverscroll(false, false);
musicTableScrollPane.setOverscroll(false, false); scrollPane.setClamp(true);
musicTableScrollPane.setColor(Color.BLUE); scrollPane.setColor(Color.BLUE);
addActor(musicTableScrollPane); addActor(scrollPane);
back = new TextButton("Back", skin); back = new TextButton("Back", skin);
back.setWidth(back.getWidth()+20f); back.setWidth(back.getWidth()+20f);
back.setPosition(getWidth()-back.getWidth()-15f, getHeight() - back.getHeight() - 15f); back.setPosition(getWidth()-back.getWidth()-15f, getHeight() - back.getHeight() - 15f);
@ -114,51 +113,15 @@ public class MusicSelectionPage extends Page implements Observer {
} }
}); });
musicInfoTable = new Table(); musicInfoTable = new InformationTable(getWidth()-scrollPane.getWidth(), getHeight());
musicInfoTable.defaults().center();
musicInfoTable.setPosition(musicTableScrollPane.getWidth() + musicTableScrollPane.getX(), 0);
musicInfoTable.setSize(getWidth()-musicTableScrollPane.getWidth(), getHeight());
addActor(musicInfoTable); addActor(musicInfoTable);
musicSubInfo = new Table(skin);
musicSubInfo.setBackground("corner-panel");
songTitle = new ScrollText("", null, skin, true, true);
author = new Label(null, skin, "sub-font", skin.getColor("default"));
songLength = new Label(null, skin, "sub-font", skin.getColor("default"));
previousTop = new Label(null, skin, "sub-font", skin.getColor("default"));
ratedDifficulty = new Label(null, skin, "sub-font", skin.getColor("default"));
albumCover = new Image(assets.get("defaultCover.png", Texture.class));
beginButton = new TextButton("Begin", skin); beginButton = new TextButton("Begin", skin);
beginButton.addListener(beginButtonListener); beginButton.addListener(beginButtonListener);
mmc.addObserver(this); mmc.addObserver(this);
mc.addObserver(this);
thread = new musicSelectionLoaderThread(); selectionLoaderThread = new musicSelectionLoaderThread();
selectionListener = new ClickListener() {
public void clicked(InputEvent event, float x, float y) {
MusicSelectable selectable = (MusicSelectable) event.getListenerActor();
if (selectable.getMetadata().getTitle() != null) {
songTitle.setText(selectable.getMetadata().getTitle(), null);
} else {
songTitle.setText(selectable.getFileHandle().nameWithoutExtension(), null);
}
author.setText(selectable.getMetadata().getAuthor());
songLength.setText(selectable.getMetadata().getDuration());
previousTop.setText("...");
ratedDifficulty.setText("...");
if (selectable.getMetadata().getAlbumCover() != null) {
albumCover.setDrawable(new TextureRegionDrawable(new TextureRegion(selectable.getMetadata().getAlbumCover())));
} else {
albumCover.setDrawable(new TextureRegionDrawable(new TextureRegion(assets.get("defaultCover.png", Texture.class))));
}
}
};
} }
@Override @Override
@ -193,74 +156,58 @@ public class MusicSelectionPage extends Page implements Observer {
if (songSelectionTimer <= 0f) { if (songSelectionTimer <= 0f) {
} }
} }
if (mc.getMusicList().isSearched()) {
if (mc.getMusicList().getTotal() != 0) {
if (mc.getMusicList().isSearched() && 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)); updateList(delta);
super.act(delta);
}
private void updateList(float delta) {
if (mc.getMusicList().isSearched()) {
if (mc.getMusicList().getTotal() != 0) {
if (selectables.size() != mmc.size()) {
MusicSelectable selectable = new MusicSelectable(mmc.getMetadata(selectables.size()));
selectables.add(selectable);
} else if (selectables.size() != vGroup.getChildren().size) {
vGroup.addActor(selectables.getButtons().get(vGroup.getChildren().size));
} else { } else {
if (selectables.getChecked() == null) { if (selectables.getChecked() == null) {
selectables.setMinCheckCount(1);
selectables.setChecked(mc.getCurrentMusicFileHandle()); selectables.setChecked(mc.getCurrentMusicFileHandle());
} else if (selectables.getChecked().getFileHandle() != mc.getCurrentMusicFileHandle()) { scrollPane.scrollTo(selectables.getChecked().getX(), selectables.getChecked().getY(), selectables.getChecked().getWidth(), selectables.getChecked().getHeight());
songSelectionTimer += delta; } else if (selectables.getChecked().getMetadata().getFileHandle() != mc.getCurrentMusicFileHandle()) {
if (songSelectionTimer > 2f) { musicSelectDelay += delta;
mc.setMusicByFileHandle(selectables.getChecked().getFileHandle()); if (musicSelectDelay >= 1f) {
mc.setMusicByFileHandle(selectables.getChecked().getMetadata().getFileHandle());
musicSelectDelay = 0;
}
}
} }
} else { } else {
songSelectionTimer = 0; //TODO: Error message reporting empty music list or something
} }
} }
} }
super.act(delta);
}
private void scrollDown() { private void scrollDown() {
selectables.selectNext(); selectables.selectNext();
musicTableScrollPane.scrollTo(selectables.getChecked().getX(), selectables.getChecked().getY(), selectables.getChecked().getWidth(), selectables.getChecked().getHeight()); scrollPane.scrollTo(selectables.getChecked().getX(), selectables.getChecked().getY(), selectables.getChecked().getWidth(), selectables.getChecked().getHeight());
} }
private void scrollUp() { private void scrollUp() {
selectables.selectPrevious(); selectables.selectPrevious();
musicTableScrollPane.scrollTo(selectables.getChecked().getX(), selectables.getChecked().getY(), selectables.getChecked().getWidth(), selectables.getChecked().getHeight()); scrollPane.scrollTo(selectables.getChecked().getX(), selectables.getChecked().getY(), selectables.getChecked().getWidth(), selectables.getChecked().getHeight());
} }
public FileHandle getSelectedMusic() { public FileHandle getSelectedMusic() {
return selectables.getChecked().getMetadata().getFileHandle(); return selectables.getChecked().getMetadata().getFileHandle();
} }
public void refreshUIList() {
selectables.clear();
mmc.loadAudioMetadata();
selectables.clear();
musicInfoTable.clear();
musicSubInfo.clear();
Gdx.app.debug("MusicSelectionPage", "Refreshing...");
songTitle.setText("loading...", null);
musicInfoTable.add(songTitle).width(musicInfoTable.getWidth()*0.6f).spaceBottom(30f);
musicInfoTable.row();
author.setText("...");
musicSubInfo.add(author);
musicSubInfo.row();
songLength.setText("...");
musicSubInfo.add(songLength);
musicSubInfo.row();
previousTop.setText("...");
musicSubInfo.add(previousTop);
musicSubInfo.row();
ratedDifficulty.setText("...");
musicSubInfo.add(ratedDifficulty);
musicSubInfo.pack();
musicInfoTable.add(musicSubInfo).spaceBottom(20f);
musicInfoTable.row();
albumCover.setDrawable(new TextureRegionDrawable(new TextureRegion(assets.get("defaultCover.png", Texture.class))));
musicInfoTable.add(albumCover).size(musicInfoTable.getWidth()/2f);
musicInfoTable.row();
musicInfoTable.add(beginButton).spaceTop(20f).fillX();
}
@Override @Override
public void dispose() { public void dispose() {
super.dispose(); super.dispose();
@ -269,7 +216,12 @@ public class MusicSelectionPage extends Page implements Observer {
@Override @Override
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
if (o == mmc) { if (o == mmc) {
thread.start(); musicInfoTable.setToDefault();
selectionLoaderThread.start();
} else if (o == mc) {
if (mc.getMusicList().getTotal() == selectables.size() && mc.getCurrentMusicFileHandle() != selectables.getChecked().getMetadata().getFileHandle()) {
selectables.setChecked(mc.getCurrentMusicFileHandle());
}
} }
} }
@ -281,9 +233,9 @@ public class MusicSelectionPage extends Page implements Observer {
private class musicSelectionLoaderThread implements Runnable { private class musicSelectionLoaderThread implements Runnable {
private Thread thread; private Thread thread;
private Array<MusicSelectable> queueList; private Array<AudioMetadata> queueList;
private String name = "Music-Selection-Loader-Thread"; private String name = "Music-Selection-Loader-Thread";
private volatile boolean work; private volatile boolean work = true;
public musicSelectionLoaderThread() { public musicSelectionLoaderThread() {
queueList = new Array<>(); queueList = new Array<>();
@ -292,18 +244,23 @@ public class MusicSelectionPage extends Page implements Observer {
@Override @Override
public void run() { public void run() {
while (work) { while (work) {
while (selectables.getButtons().size != mc.getMusicList().getTotal()) { if (selectables.size() != mmc.size()) {
MusicSelectable selectable = new MusicSelectable(skin, assets.get("defaultCover.png"), mmc.getMetadata(selectables.getButtons().size), queueList); selectables.clear();
selectable.addListener(selectionListener); for (int mid = 0; mid < mmc.size(); mid++) {
selectables.add(selectable);
}
selectables.uncheckAll();
} else {
synchronized (this) {
while (queueList.size != 0) {
AudioMetadata metadata = queueList.pop();
metadata.loadAlbumCover();
}
} }
for (int i = 0; i < queueList.size; i++) {
queueList.get(i).loadAlbumCover();
queueList.removeIndex(i);
} }
synchronized (queueList) { synchronized (this) {
try { try {
wait(); wait();
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -318,12 +275,259 @@ public class MusicSelectionPage extends Page implements Observer {
thread = new Thread(this, name); thread = new Thread(this, name);
thread.start(); thread.start();
return true; return true;
} } else {
synchronized (queueList) { synchronized (this) {
notify(); notify();
} }
}
return false; return false;
} }
public synchronized void queue(AudioMetadata metadata) {
if (!queueList.contains(metadata, true)) {
queueList.add(metadata);
notify();
}
}
}
private class MusicSelectable extends Button {
private Vector2 actualCoords;
private Image albumCoverImage;
private Table informationTable;
private Label name, artist;
private Label time;
private float timeSinceOnScreen;
private AudioMetadata metadata;
private Texture defaultAlbumArt;
private TextureRegion albumArtTexture;
private boolean albumArtUsed, albumArtAttempted;
public MusicSelectable(AudioMetadata metadata) {
super(skin, "music-selectable");
this.metadata = metadata;
this.defaultAlbumArt = assets.get("defaultCover.png");
albumArtTexture = new TextureRegion(defaultAlbumArt);
albumCoverImage = new Image();
updateAlbumArtImage(defaultAlbumArt);
setSize(getPrefWidth(), getPrefHeight());
informationTable = new Table();
informationTable.row().width(0.75f*getWidth());
name = new Label(metadata.getTitle(), skin, "default-font", skin.getColor("default"));
name.setEllipsis(true);
informationTable.add(name).colspan(2).left().expand();
informationTable.row();
artist = new Label(metadata.getAuthor(), skin, "sub-font", skin.getColor("default"));
artist.setEllipsis(true);
informationTable.add(artist).left().width(getWidth()*0.375f);
time = new Label(metadata.getDuration(), skin, "sub-font", skin.getColor("default"));
informationTable.add(time).right();
add(informationTable).spaceRight(15f).padLeft(15f);
add(albumCoverImage).right().expandX().size(informationTable.getMinHeight());
albumCoverImage.setSize(100, 100);
actualCoords = new Vector2();
}
@Override
public void act(float delta) {
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()) && selectables.getChecked() != this) {
offScreenAct(delta);
} else {
onScreenAct(delta);
}
super.act(delta);
}
@Override
public void draw(Batch batch, float parentAlpha) {
synchronized (albumCoverImage) {
super.draw(batch, parentAlpha);
}
}
public void onScreenAct(float delta) {
timeSinceOnScreen = 0;
if (metadata.getAlbumCover() != null && !albumArtUsed) {
updateAlbumArtImage(metadata.getAlbumCover());
albumArtUsed = true;
} else if (!albumArtAttempted) {
selectionLoaderThread.queue(metadata);
albumArtAttempted = true;
}
}
private void updateAlbumArtImage(Texture texture) {
if (texture == null) throw new IllegalArgumentException("Texture can't be null!");
albumArtTexture.setRegion(texture);
albumCoverImage.setDrawable(new TextureRegionDrawable(albumArtTexture));
}
public void offScreenAct(float delta) {
if (metadata.getAlbumCover() != null) {
timeSinceOnScreen += delta;
if (timeSinceOnScreen >= 2) {
updateAlbumArtImage(defaultAlbumArt);
metadata.unloadAlbumCover();
albumArtUsed = false;
albumArtAttempted = false;
}
}
}
public AudioMetadata getMetadata() {
return metadata;
}
public FileHandle getFileHandle() {
return metadata.getFileHandle();
}
@Override
public float getPrefWidth() {
return scrollPane.getScrollWidth();
}
@Override
public float getPrefHeight() {
return super.getPrefHeight();
}
public TextureRegion getAlbumArtTexture() {
return albumArtTexture;
}
}
private class MusicSelectableButtonGroup extends ButtonGroup<MusicSelectable> {
private Array<MusicSelectable> buttons;
public MusicSelectableButtonGroup() {
buttons = getButtons();
}
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);
}
@Override
protected boolean canCheck(MusicSelectable button, boolean newState) {
if (newState) {
musicInfoTable.setDisplayedSelectable(button);
}
return super.canCheck(button, newState);
}
public int size() {
return buttons.size;
}
}
private class InformationTable extends Table {
private ScrollText songTitle;
private Label author;
private Label musicDuration;
private Label previousTop;
private Label ratedDifficulty;
private Image albumCover;
private Table subInformation;
private MusicSelectable displayedSelectable;
public InformationTable(float width, float height) {
defaults().center();
setPosition(scrollPane.getWidth() + scrollPane.getX(), 0);
setSize(width, height);
subInformation = new Table(skin);
subInformation.setBackground("corner-panel");
albumCover = new Image(assets.get("defaultCover.png", Texture.class));
songTitle = new ScrollText("", null, skin, true, true);
author = new Label(null, skin, "sub-font", skin.getColor("default"));
musicDuration = new Label(null, skin, "sub-font", skin.getColor("default"));
previousTop = new Label(null, skin, "sub-font", skin.getColor("default"));
ratedDifficulty = new Label(null, skin, "sub-font", skin.getColor("default"));
}
public void setDisplayedSelectable(MusicSelectable displayedSelectable) {
this.displayedSelectable = displayedSelectable;
if (displayedSelectable != null) {
AudioMetadata metadata = displayedSelectable.getMetadata();
albumCover.setDrawable(new TextureRegionDrawable(displayedSelectable.getAlbumArtTexture()));
songTitle.setText(metadata.getTitle(), null);
author.setText(metadata.getAuthor());
musicDuration.setText(metadata.getDuration());
//TODO previous top
//TODO rated difficulty
beginButton.setDisabled(false);
} else {
albumCover.setDrawable(new TextureRegionDrawable(new TextureRegion(assets.get("defaultCover.png", Texture.class))));
songTitle.setText("loading...", null);
author.setText("...");
musicDuration.setText("...");
previousTop.setText("...");
ratedDifficulty.setText("...");
}
}
public void setToDefault() {
clear();
subInformation.clear();
albumCover.setDrawable(new TextureRegionDrawable(new TextureRegion(assets.get("defaultCover.png", Texture.class))));
add(albumCover).size(getWidth()/2f).spaceBottom(25f);
row();
songTitle.setText("...", null);
add(songTitle).width(getWidth()*0.6f).spaceBottom(30f);
row();
author.setText("...");
author.setEllipsis(true);
author.setAlignment(Align.center);
subInformation.add(author).expand();
subInformation.row();
musicDuration.setText("...");
subInformation.add(musicDuration);
subInformation.row();
previousTop.setText("...");
subInformation.add(previousTop);
subInformation.row();
ratedDifficulty.setText("...");
subInformation.add(ratedDifficulty);
add(subInformation).width(0.4f*getWidth());
row();
add(beginButton).spaceTop(20f).fillX();
beginButton.setDisabled(true);
}
} }
} }

View File

@ -127,12 +127,12 @@ public class OptionsPage extends Page {
optionsTable.row(); optionsTable.row();
Label usageLabel = new Label("Current usage (lower the better): " + 100f*((float)(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/(float)Runtime.getRuntime().totalMemory()) + "%", skin) { Label usageLabel = new Label("Current usage (lower the better): " + 100f*((float)(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/(float)Runtime.getRuntime().totalMemory()) + "%", skin) {
float refreshTime = 60; float refreshTime = 20;
@Override @Override
public void act(float delta) { public void act(float delta) {
refreshTime -= delta; refreshTime -= delta;
if (refreshTime <= 0) { if (refreshTime <= 0) {
refreshTime = 60; refreshTime = 20;
setText("Current usage (lower the better): " + 100f*((float)(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/(float)Runtime.getRuntime().totalMemory()) + "%"); setText("Current usage (lower the better): " + 100f*((float)(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/(float)Runtime.getRuntime().totalMemory()) + "%");
} }
super.act(delta); super.act(delta);