Progress on mission 'async everything'

This commit is contained in:
Harrison Deng 2017-12-07 16:47:50 -06:00
parent a41923fa12
commit a858fa9159
5 changed files with 160 additions and 118 deletions

View File

@ -12,13 +12,15 @@ import org.jaudiotagger.audio.wav.WavTag;
import org.jaudiotagger.tag.FieldKey; import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.TagException; import org.jaudiotagger.tag.TagException;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Preferences; import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.PixmapIO;
import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.Disposable;
public class SongInfo implements Disposable { public class MusicInfo implements Disposable {
private long durationInSeconds; private long durationInSeconds;
private String songName; private String songName;
@ -26,16 +28,15 @@ public class SongInfo implements Disposable {
private String author; private String author;
private int previousTop; private int previousTop;
private int ratedDifficulty; private int ratedDifficulty;
private byte[] albumWorkBytes;
private boolean invalidMusic; private boolean invalidMusic;
private boolean containsInfo; private boolean containsInfo;
private FileHandle musicFile; private FileHandle musicFile;
private Preferences musicAnnotation; private Preferences musicAnnotation;
public SongInfo(FileHandle musicFile, Preferences musicData) { public MusicInfo(FileHandle musicFile, Preferences musicAnnotation) {
this.musicFile = musicFile; this.musicFile = musicFile;
this.musicAnnotation = musicData; this.musicAnnotation = musicAnnotation;
} }
/** /**
@ -50,7 +51,12 @@ public class SongInfo implements Disposable {
durationInSeconds = mp3File.getAudioHeader().getTrackLength(); durationInSeconds = mp3File.getAudioHeader().getTrackLength();
if (mp3File.getTag() != null && mp3File.getTag().getFirstArtwork() != null) { if (mp3File.getTag() != null && mp3File.getTag().getFirstArtwork() != null) {
albumWorkBytes = mp3File.getTag().getFirstArtwork().getBinaryData(); if (!Gdx.files.external("RhythmBullet/AlbumArt/" + musicFile.name() + ".png").exists()) {
byte[] albumWorkBytes = mp3File.getTag().getFirstArtwork().getBinaryData();
Pixmap pixmap = new Pixmap(albumWorkBytes, 0, albumWorkBytes.length);
PixmapIO.writePNG(Gdx.files.external("RhythmBullet/AlbumArt/" + musicFile.name() + ".png"), pixmap);
pixmap.dispose();
}
} }
songName = mp3File.getTag().getFirst(FieldKey.TITLE); songName = mp3File.getTag().getFirst(FieldKey.TITLE);
@ -91,13 +97,17 @@ public class SongInfo implements Disposable {
containsInfo = true; containsInfo = true;
} }
public void setupTexture(Texture defaultAlbumCover) { /**
if (albumWorkBytes != null) { * The album art as a texture.
Pixmap albumCoverFromBytes = new Pixmap(albumWorkBytes, 0, albumWorkBytes.length); * Can return null if contains info is false.
albumCover = new Texture(albumCoverFromBytes); * Not automatically disposed.
albumCoverFromBytes.dispose(); * @return
*/
public Texture loadTexture() {
if (Gdx.files.external("RhythmBullet/AlbumArt/" + musicFile.name() + ".png").exists()) {
return new Texture(Gdx.files.external("RhythmBullet/AlbumArt/" + musicFile.name() + ".png"));
} else { } else {
this.albumCover = defaultAlbumCover; return null;
} }
} }
@ -141,4 +151,8 @@ public class SongInfo implements Disposable {
public boolean hasInformation() { public boolean hasInformation() {
return containsInfo; return containsInfo;
} }
public FileHandle getMusicFile() {
return musicFile;
}
} }

View File

@ -0,0 +1,57 @@
package zero1hd.rhythmbullet.audio;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
public class MusicInfoController implements Disposable {
private MusicList musicList;
private ExecutorService exec;
private Array<MusicInfo> songInfoArray;
private Preferences musicAnnotation;
private boolean doneLoading;
public MusicInfoController(MusicList musicList) {
this.musicList = musicList;
songInfoArray = new Array<>();
exec = Executors.newSingleThreadExecutor();
musicAnnotation = Gdx.app.getPreferences("MusicAnnotation");
}
public MusicList getMusicList() {
return musicList;
}
/**
* Non-blocking, loads on separate thread.
*/
public void loadSongInfo() {
doneLoading = false;
songInfoArray.clear();
exec.submit(() -> {
for (int i = 0; i < musicList.getAmountOfMusic(); i++) {
MusicInfo musicInfo = new MusicInfo(musicList.getMusicList().get(i), musicAnnotation);
musicInfo.loadInfo();
songInfoArray.add(musicInfo);
}
doneLoading = true;
});
}
@Override
public void dispose() {
exec.shutdown();
}
/**
* Verify if loading song info is done.
* @return
*/
public synchronized boolean isDoneLoading() {
return doneLoading;
}
}

View File

@ -1,70 +1,51 @@
package zero1hd.rhythmbullet.graphics.ui.components; package zero1hd.rhythmbullet.graphics.ui.components;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.files.FileHandle; 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.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputEvent;
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.Skin; import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup; import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.Disposable;
import zero1hd.rhythmbullet.audio.SongInfo; import zero1hd.rhythmbullet.audio.MusicInfo;
import zero1hd.rhythmbullet.graphics.ui.pages.MusicSelectionPage; import zero1hd.rhythmbullet.graphics.ui.pages.MusicSelectionPage;
public class MusicSelectable extends WidgetGroup implements Disposable { public class MusicSelectable extends WidgetGroup implements Disposable {
private Table table; private Table table;
private Image imageIcon;
private ShortenedTextLabel displayName; private ShortenedTextLabel displayName;
private Label durationLabel; private Label durationLabel;
private ShortenedTextLabel authorLabel; private ShortenedTextLabel authorLabel;
private FileHandle musicFile;
private Texture albumCover;
private SongInfo songInfo;
private boolean selected; private boolean selected;
private VerticalGroup vGroup;
private MusicSelectionPage msp; private MusicSelectionPage msp;
private MusicInfo musicInfo;
public MusicSelectable(FileHandle musicFile, Preferences musicAnnotation, Skin skin, Texture defaultAlbumC, MusicSelectionPage msp) { private FileHandle musicFile;
public MusicSelectable(FileHandle musicFile, Skin skin, MusicSelectionPage msp) {
this.musicFile = musicFile;
table = new Table(skin); table = new Table(skin);
table.setBackground("holo-pane"); table.setBackground("holo-pane");
table.setFillParent(true); table.setFillParent(true);
vGroup = new VerticalGroup();
this.msp = msp; this.msp = msp;
setName(musicFile.name()); setName(musicFile.nameWithoutExtension());
table.defaults().pad(2f); table.defaults().pad(5f).space(15f);
this.albumCover = defaultAlbumC;
this.musicFile = musicFile;
songInfo = new SongInfo(musicFile, musicAnnotation);
imageIcon = new Image(albumCover);
table.add(imageIcon).size(180f).left().expandX();
displayName = new ShortenedTextLabel(musicFile.nameWithoutExtension().replace('_', ' '), skin, "sub-font", skin.getColor("default")); displayName = new ShortenedTextLabel(musicFile.nameWithoutExtension().replace('_', ' '), skin, "sub-font", skin.getColor("default"));
vGroup.addActor(displayName); table.add(displayName);
durationLabel = new Label("Loading...", skin, "sub-font", skin.getColor("default")); durationLabel = new Label("Loading...", skin, "sub-font", skin.getColor("default"));
vGroup.addActor(durationLabel); table.add(durationLabel);
authorLabel = new ShortenedTextLabel("Loading...", skin, "sub-font", skin.getColor("default")); authorLabel = new ShortenedTextLabel("Loading...", skin, "sub-font", skin.getColor("default"));
vGroup.addActor(authorLabel); table.add(authorLabel);
table.add(vGroup).expandX().fillX().center();
table.pack();
addActor(table); addActor(table);
addListener(new ClickListener() { addListener(new ClickListener() {
@Override @Override
@ -78,27 +59,27 @@ public class MusicSelectable extends WidgetGroup implements Disposable {
/** /**
* updates the UI side of information. * updates the UI side of information.
* needs to be called in thread with gl context. * needs to be called in thread with gl context.
* @param musicInfo the music information for this song.
*/ */
public void updateInfo() { public void updateInfo(MusicInfo musicInfo) {
displayName.setOriginalText(songInfo.getMusicName()); this.musicInfo = musicInfo;
displayName.setOriginalText(musicInfo.getMusicName());
durationLabel.setText("Runtime: " durationLabel.setText("Runtime: "
+ ((songInfo.getDurationInSeconds() / 60 < 1) ? "00" : songInfo.getDurationInSeconds() / 60) + ":" + ((musicInfo.getDurationInSeconds() / 60 < 1) ? "00" : musicInfo.getDurationInSeconds() / 60) + ":"
+ ((songInfo.getDurationInSeconds() - (songInfo.getDurationInSeconds() / 60) * 60) < 10 + ((musicInfo.getDurationInSeconds() - (musicInfo.getDurationInSeconds() / 60) * 60) < 10
? "0" + (songInfo.getDurationInSeconds() - (songInfo.getDurationInSeconds() / 60) * 60) ? "0" + (musicInfo.getDurationInSeconds() - (musicInfo.getDurationInSeconds() / 60) * 60)
: (songInfo.getDurationInSeconds() - (songInfo.getDurationInSeconds() / 60) * 60))); : (musicInfo.getDurationInSeconds() - (musicInfo.getDurationInSeconds() / 60) * 60)));
authorLabel.setOriginalText("Author: " + songInfo.getAuthor()); authorLabel.setOriginalText("Author: " + musicInfo.getAuthor());
authorLabel.setToOriginalText(); authorLabel.setToOriginalText();
songInfo.setupTexture(albumCover);
albumCover = songInfo.getAlbumCover();
imageIcon.setDrawable((new TextureRegionDrawable(new TextureRegion(albumCover))));
} }
@Override @Override
public void layout() { public void layout() {
displayName.setTargetWidth((int) (getWidth() - 300)); displayName.setTargetWidth((int) (getWidth()/3f));
authorLabel.setTargetWidth((int) (getWidth() - 300)); authorLabel.setTargetWidth((int) (getWidth()/3f));
displayName.resize(); displayName.resize();
authorLabel.resize();
super.layout(); super.layout();
} }
@ -112,21 +93,8 @@ public class MusicSelectable extends WidgetGroup implements Disposable {
super.act(delta); super.act(delta);
} }
public FileHandle getMusicFile() {
return musicFile;
}
public SongInfo getAudioInfo() {
return songInfo;
}
@Override
public void dispose() {
songInfo.dispose();
}
public boolean isMusicInvalid() { public boolean isMusicInvalid() {
return songInfo.isInvalidMusic(); return musicInfo.isInvalidMusic();
} }
/** /**
@ -152,4 +120,16 @@ public class MusicSelectable extends WidgetGroup implements Disposable {
public float getPrefHeight() { public float getPrefHeight() {
return table.getMinHeight(); return table.getMinHeight();
} }
@Override
public void dispose() {
}
public FileHandle getMusicFile() {
return musicFile;
}
public MusicInfo getMusicInfo() {
return musicInfo;
}
} }

View File

@ -2,8 +2,6 @@ package zero1hd.rhythmbullet.graphics.ui.pages;
import java.util.Observable; import java.util.Observable;
import java.util.Observer; import java.util.Observer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.Input.Keys;
@ -28,7 +26,8 @@ import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import zero1hd.rhythmbullet.audio.MusicManager; import zero1hd.rhythmbullet.audio.MusicManager;
import zero1hd.rhythmbullet.audio.SongInfo; import zero1hd.rhythmbullet.audio.MusicInfo;
import zero1hd.rhythmbullet.audio.MusicInfoController;
import zero1hd.rhythmbullet.audio.MusicListController; import zero1hd.rhythmbullet.audio.MusicListController;
import zero1hd.rhythmbullet.graphics.ui.components.MusicSelectable; import zero1hd.rhythmbullet.graphics.ui.components.MusicSelectable;
import zero1hd.rhythmbullet.graphics.ui.components.ScrollText; import zero1hd.rhythmbullet.graphics.ui.components.ScrollText;
@ -38,6 +37,7 @@ public class MusicSelectionPage extends Page implements Observer {
private boolean extraInfoDone; private boolean extraInfoDone;
private MusicListController mc; private MusicListController mc;
private MusicInfoController mic;
private Array<MusicSelectable> selectables; private Array<MusicSelectable> selectables;
private Table musicTable; private Table musicTable;
private ScrollPane musicTableScrollPane; private ScrollPane musicTableScrollPane;
@ -61,13 +61,15 @@ public class MusicSelectionPage extends Page implements Observer {
private int musicSelectableIndex; private int musicSelectableIndex;
private TextButton beginButton; private TextButton beginButton;
private int uiSongCount;
private float scrollTimer, scrollDelay = 0.2f, scrollDelMod, songSelectionTimer; private float scrollTimer, scrollDelay = 0.2f, scrollDelMod, songSelectionTimer;
private ExecutorService exec;
public MusicSelectionPage(Skin skin, MusicListController musicListController, AssetManager assetManager, Vector3 cameraTarget, AnalysisPage ap) { public MusicSelectionPage(Skin skin, MusicListController musicListController, AssetManager assetManager, Vector3 cameraTarget, AnalysisPage ap) {
setTextureBackground(assetManager.get("gradients.atlas", TextureAtlas.class).findRegion("red-round")); setTextureBackground(assetManager.get("gradients.atlas", TextureAtlas.class).findRegion("red-round"));
this.skin = skin; this.skin = skin;
this.mc = musicListController; this.mc = musicListController;
mic = new MusicInfoController(mc.getMusicList());
musicFileAnnotation = Gdx.app.getPreferences("music_file_annotation"); musicFileAnnotation = Gdx.app.getPreferences("music_file_annotation");
this.assets = assetManager; this.assets = assetManager;
musicTable = new Table(); musicTable = new Table();
@ -137,8 +139,6 @@ public class MusicSelectionPage extends Page implements Observer {
ap.processSong(mc.getMusicList().getAudioData(getSelectedMusic())); ap.processSong(mc.getMusicList().getAudioData(getSelectedMusic()));
} }
}); });
exec = Executors.newSingleThreadExecutor();
} }
@Override @Override
@ -175,14 +175,20 @@ public class MusicSelectionPage extends Page implements Observer {
} }
} }
super.act(delta); super.act(delta);
if (uiSongCount < selectables.size) {
musicTable.add(selectables.get(uiSongCount)).expandX().fillX();
uiSongCount++;
musicTable.row();
}
} }
public FileHandle getSelectedMusic() { public FileHandle getSelectedMusic() {
return currentlySelected.getMusicFile(); return currentlySelected.getMusicFile();
} }
public SongInfo getSelectedMusicInfo() { public MusicInfo getSelectedMusicInfo() {
return currentlySelected.getAudioInfo(); return currentlySelected.getMusicInfo();
} }
public void refreshUIList() { public void refreshUIList() {
@ -191,6 +197,8 @@ public class MusicSelectionPage extends Page implements Observer {
musicInfoTable.clear(); musicInfoTable.clear();
musicSubInfo.clear(); musicSubInfo.clear();
extraInfoDone = false; extraInfoDone = false;
uiSongCount = 0;
mic.loadSongInfo();
for (int i = 0; i < selectables.size; i++) { for (int i = 0; i < selectables.size; i++) {
selectables.get(i).dispose(); selectables.get(i).dispose();
@ -199,23 +207,9 @@ public class MusicSelectionPage extends Page implements Observer {
Gdx.app.debug("MusicSelectionPage", "Refreshing..."); Gdx.app.debug("MusicSelectionPage", "Refreshing...");
for (int i = 0; i < mc.getMusicList().getAmountOfMusic(); i++) { for (int i = 0; i < mc.getMusicList().getAmountOfMusic(); i++) {
MusicSelectable selectable = new MusicSelectable(mc.getMusicList().getSongFileHandleFromIndex(i), musicFileAnnotation, skin, assets.get("defaultCover.png", Texture.class), this); MusicSelectable selectable = new MusicSelectable(mc.getMusicList().getSongFileHandleFromIndex(i), skin, this);
selectables.add(selectable); selectables.add(selectable);
musicTable.add(selectable).expandX().fillX();
musicTable.row();
} }
exec.submit(() -> {
for (int i = 0; i < selectables.size; i++) {
MusicSelectable selectable = selectables.get(i);
selectable.getAudioInfo().loadInfo();
Gdx.app.postRunnable(() -> {
selectable.updateInfo();
});
}
extraInfoDone = true;
musicInfoTable.add(songTitle).width(musicInfoTable.getWidth()*0.6f).spaceBottom(30f); musicInfoTable.add(songTitle).width(musicInfoTable.getWidth()*0.6f).spaceBottom(30f);
musicInfoTable.row(); musicInfoTable.row();
musicSubInfo.add(author); musicSubInfo.add(author);
@ -231,12 +225,6 @@ public class MusicSelectionPage extends Page implements Observer {
musicInfoTable.add(albumCover).size(musicInfoTable.getWidth()/2f); musicInfoTable.add(albumCover).size(musicInfoTable.getWidth()/2f);
musicInfoTable.row(); musicInfoTable.row();
musicInfoTable.add(beginButton).spaceTop(20f).fillX(); musicInfoTable.add(beginButton).spaceTop(20f).fillX();
if (currentlySelected != null) {
updateInformation();
}
Gdx.app.debug("MusicSelectionPage", "Refresh complete. " + selectables.size + " songs loaded.");
});
} }
@Override @Override
@ -276,7 +264,6 @@ public class MusicSelectionPage extends Page implements Observer {
if ((musicSelectableIndex = (musicTable.getChildren().indexOf(currentlySelected, true) - 1)) < 0) { if ((musicSelectableIndex = (musicTable.getChildren().indexOf(currentlySelected, true) - 1)) < 0) {
musicSelectableIndex = musicTable.getChildren().size-1; musicSelectableIndex = musicTable.getChildren().size-1;
} }
deselectAll();
((MusicSelectable)musicTable.getChildren().get(musicSelectableIndex)).select(); ((MusicSelectable)musicTable.getChildren().get(musicSelectableIndex)).select();
musicTableScrollPane.scrollTo(currentlySelected.getX(), currentlySelected.getY(), currentlySelected.getWidth(), currentlySelected.getHeight()); musicTableScrollPane.scrollTo(currentlySelected.getX(), currentlySelected.getY(), currentlySelected.getWidth(), currentlySelected.getHeight());
} }
@ -312,20 +299,23 @@ public class MusicSelectionPage extends Page implements Observer {
} }
} }
/**
* This should only be called when everything is loaded.
*/
private void updateInformation() { private void updateInformation() {
songTitle.setText(currentlySelected.getAudioInfo().getMusicName(), null); songTitle.setText(currentlySelected.getMusicInfo().getMusicName(), null);
author.setText("Author: " + currentlySelected.getAudioInfo().getAuthor()); author.setText("Author: " + currentlySelected.getMusicInfo().getAuthor());
long lengthInSeconds = currentlySelected.getAudioInfo().getDurationInSeconds(); long lengthInSeconds = currentlySelected.getMusicInfo().getDurationInSeconds();
int min = (int) (lengthInSeconds/60); int min = (int) (lengthInSeconds/60);
int sec = (int) (lengthInSeconds - (min*60)); int sec = (int) (lengthInSeconds - (min*60));
songLength.setText("Length: " + min + ":" + (sec > 9 ? sec : "0" + sec)); songLength.setText("Length: " + min + ":" + (sec > 9 ? sec : "0" + sec));
previousTop.setText("Highscore: " + currentlySelected.getAudioInfo().getPreviousTop()); previousTop.setText("Highscore: " + currentlySelected.getMusicInfo().getPreviousTop());
String difficulty = (getSelectedMusicInfo().getRatedDifficulty() == -1 ? "N/A" : String.valueOf(getSelectedMusicInfo().getRatedDifficulty())); String difficulty = (getSelectedMusicInfo().getRatedDifficulty() == -1 ? "N/A" : String.valueOf(getSelectedMusicInfo().getRatedDifficulty()));
ratedDifficulty.setText("Rated Difficulty: " + difficulty); ratedDifficulty.setText("Rated Difficulty: " + difficulty);
albumCover.setDrawable((new TextureRegionDrawable(new TextureRegion(currentlySelected.getAudioInfo().getAlbumCover())))); albumCover.setDrawable((new TextureRegionDrawable(new TextureRegion(currentlySelected.getMusicInfo().getAlbumCover()))));
} }
} }

View File

@ -44,6 +44,7 @@ public class MainMenu extends ScreenAdapter implements TransitionAdapter {
private RhythmBullet core; private RhythmBullet core;
private MusicListController mlc; private MusicListController mlc;
private float lerpAlpha; private float lerpAlpha;
private ShaderProgram gaussianBlurShader; private ShaderProgram gaussianBlurShader;