From a858fa915907721de9cd5fdec4df6b5b4970c48a Mon Sep 17 00:00:00 2001 From: Recrown Date: Thu, 7 Dec 2017 16:47:50 -0600 Subject: [PATCH] Progress on mission 'async everything' --- .../audio/{SongInfo.java => MusicInfo.java} | 36 ++++--- .../audio/MusicInfoController.java | 57 +++++++++++ .../ui/components/MusicSelectable.java | 94 ++++++++----------- .../graphics/ui/pages/MusicSelectionPage.java | 90 ++++++++---------- .../rhythmbullet/screens/MainMenu.java | 1 + 5 files changed, 160 insertions(+), 118 deletions(-) rename core/src/zero1hd/rhythmbullet/audio/{SongInfo.java => MusicInfo.java} (73%) create mode 100644 core/src/zero1hd/rhythmbullet/audio/MusicInfoController.java diff --git a/core/src/zero1hd/rhythmbullet/audio/SongInfo.java b/core/src/zero1hd/rhythmbullet/audio/MusicInfo.java similarity index 73% rename from core/src/zero1hd/rhythmbullet/audio/SongInfo.java rename to core/src/zero1hd/rhythmbullet/audio/MusicInfo.java index 175dfbd..746efc9 100755 --- a/core/src/zero1hd/rhythmbullet/audio/SongInfo.java +++ b/core/src/zero1hd/rhythmbullet/audio/MusicInfo.java @@ -12,13 +12,15 @@ import org.jaudiotagger.audio.wav.WavTag; import org.jaudiotagger.tag.FieldKey; import org.jaudiotagger.tag.TagException; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Preferences; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.PixmapIO; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.utils.Disposable; -public class SongInfo implements Disposable { +public class MusicInfo implements Disposable { private long durationInSeconds; private String songName; @@ -26,16 +28,15 @@ public class SongInfo implements Disposable { private String author; private int previousTop; private int ratedDifficulty; - private byte[] albumWorkBytes; private boolean invalidMusic; private boolean containsInfo; private FileHandle musicFile; private Preferences musicAnnotation; - public SongInfo(FileHandle musicFile, Preferences musicData) { + public MusicInfo(FileHandle musicFile, Preferences musicAnnotation) { this.musicFile = musicFile; - this.musicAnnotation = musicData; + this.musicAnnotation = musicAnnotation; } /** @@ -50,7 +51,12 @@ public class SongInfo implements Disposable { durationInSeconds = mp3File.getAudioHeader().getTrackLength(); 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); @@ -91,13 +97,17 @@ public class SongInfo implements Disposable { containsInfo = true; } - public void setupTexture(Texture defaultAlbumCover) { - if (albumWorkBytes != null) { - Pixmap albumCoverFromBytes = new Pixmap(albumWorkBytes, 0, albumWorkBytes.length); - albumCover = new Texture(albumCoverFromBytes); - albumCoverFromBytes.dispose(); + /** + * The album art as a texture. + * Can return null if contains info is false. + * Not automatically disposed. + * @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 { - this.albumCover = defaultAlbumCover; + return null; } } @@ -141,4 +151,8 @@ public class SongInfo implements Disposable { public boolean hasInformation() { return containsInfo; } + + public FileHandle getMusicFile() { + return musicFile; + } } diff --git a/core/src/zero1hd/rhythmbullet/audio/MusicInfoController.java b/core/src/zero1hd/rhythmbullet/audio/MusicInfoController.java new file mode 100644 index 0000000..55ecc2d --- /dev/null +++ b/core/src/zero1hd/rhythmbullet/audio/MusicInfoController.java @@ -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 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; + } +} diff --git a/core/src/zero1hd/rhythmbullet/graphics/ui/components/MusicSelectable.java b/core/src/zero1hd/rhythmbullet/graphics/ui/components/MusicSelectable.java index e15fb3f..cc648ad 100755 --- a/core/src/zero1hd/rhythmbullet/graphics/ui/components/MusicSelectable.java +++ b/core/src/zero1hd/rhythmbullet/graphics/ui/components/MusicSelectable.java @@ -1,70 +1,51 @@ package zero1hd.rhythmbullet.graphics.ui.components; -import com.badlogic.gdx.Preferences; 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.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.Skin; 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.utils.ClickListener; -import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; import com.badlogic.gdx.utils.Disposable; -import zero1hd.rhythmbullet.audio.SongInfo; +import zero1hd.rhythmbullet.audio.MusicInfo; import zero1hd.rhythmbullet.graphics.ui.pages.MusicSelectionPage; public class MusicSelectable extends WidgetGroup implements Disposable { private Table table; - private Image imageIcon; private ShortenedTextLabel displayName; private Label durationLabel; private ShortenedTextLabel authorLabel; - private FileHandle musicFile; - - private Texture albumCover; - private SongInfo songInfo; - private boolean selected; - private VerticalGroup vGroup; - 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.setBackground("holo-pane"); table.setFillParent(true); - vGroup = new VerticalGroup(); this.msp = msp; - setName(musicFile.name()); - table.defaults().pad(2f); - - this.albumCover = defaultAlbumC; - this.musicFile = musicFile; - songInfo = new SongInfo(musicFile, musicAnnotation); - - imageIcon = new Image(albumCover); - table.add(imageIcon).size(180f).left().expandX(); + setName(musicFile.nameWithoutExtension()); + table.defaults().pad(5f).space(15f); 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")); - vGroup.addActor(durationLabel); + table.add(durationLabel); 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); addListener(new ClickListener() { @Override @@ -78,27 +59,27 @@ public class MusicSelectable extends WidgetGroup implements Disposable { /** * updates the UI side of information. * needs to be called in thread with gl context. + * @param musicInfo the music information for this song. */ - public void updateInfo() { - displayName.setOriginalText(songInfo.getMusicName()); + public void updateInfo(MusicInfo musicInfo) { + this.musicInfo = musicInfo; + displayName.setOriginalText(musicInfo.getMusicName()); durationLabel.setText("Runtime: " - + ((songInfo.getDurationInSeconds() / 60 < 1) ? "00" : songInfo.getDurationInSeconds() / 60) + ":" - + ((songInfo.getDurationInSeconds() - (songInfo.getDurationInSeconds() / 60) * 60) < 10 - ? "0" + (songInfo.getDurationInSeconds() - (songInfo.getDurationInSeconds() / 60) * 60) - : (songInfo.getDurationInSeconds() - (songInfo.getDurationInSeconds() / 60) * 60))); - authorLabel.setOriginalText("Author: " + songInfo.getAuthor()); + + ((musicInfo.getDurationInSeconds() / 60 < 1) ? "00" : musicInfo.getDurationInSeconds() / 60) + ":" + + ((musicInfo.getDurationInSeconds() - (musicInfo.getDurationInSeconds() / 60) * 60) < 10 + ? "0" + (musicInfo.getDurationInSeconds() - (musicInfo.getDurationInSeconds() / 60) * 60) + : (musicInfo.getDurationInSeconds() - (musicInfo.getDurationInSeconds() / 60) * 60))); + authorLabel.setOriginalText("Author: " + musicInfo.getAuthor()); authorLabel.setToOriginalText(); - songInfo.setupTexture(albumCover); - albumCover = songInfo.getAlbumCover(); - imageIcon.setDrawable((new TextureRegionDrawable(new TextureRegion(albumCover)))); } @Override public void layout() { - displayName.setTargetWidth((int) (getWidth() - 300)); - authorLabel.setTargetWidth((int) (getWidth() - 300)); + displayName.setTargetWidth((int) (getWidth()/3f)); + authorLabel.setTargetWidth((int) (getWidth()/3f)); displayName.resize(); + authorLabel.resize(); super.layout(); } @@ -111,22 +92,9 @@ public class MusicSelectable extends WidgetGroup implements Disposable { public void act(float delta) { super.act(delta); } - - public FileHandle getMusicFile() { - return musicFile; - } - - public SongInfo getAudioInfo() { - return songInfo; - } - - @Override - public void dispose() { - songInfo.dispose(); - } public boolean isMusicInvalid() { - return songInfo.isInvalidMusic(); + return musicInfo.isInvalidMusic(); } /** @@ -152,4 +120,16 @@ public class MusicSelectable extends WidgetGroup implements Disposable { public float getPrefHeight() { return table.getMinHeight(); } + + @Override + public void dispose() { + } + + public FileHandle getMusicFile() { + return musicFile; + } + + public MusicInfo getMusicInfo() { + return musicInfo; + } } diff --git a/core/src/zero1hd/rhythmbullet/graphics/ui/pages/MusicSelectionPage.java b/core/src/zero1hd/rhythmbullet/graphics/ui/pages/MusicSelectionPage.java index 4f6b373..607a87a 100755 --- a/core/src/zero1hd/rhythmbullet/graphics/ui/pages/MusicSelectionPage.java +++ b/core/src/zero1hd/rhythmbullet/graphics/ui/pages/MusicSelectionPage.java @@ -2,8 +2,6 @@ package zero1hd.rhythmbullet.graphics.ui.pages; import java.util.Observable; import java.util.Observer; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import com.badlogic.gdx.Gdx; 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 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.graphics.ui.components.MusicSelectable; import zero1hd.rhythmbullet.graphics.ui.components.ScrollText; @@ -38,6 +37,7 @@ public class MusicSelectionPage extends Page implements Observer { private boolean extraInfoDone; private MusicListController mc; + private MusicInfoController mic; private Array selectables; private Table musicTable; private ScrollPane musicTableScrollPane; @@ -61,13 +61,15 @@ public class MusicSelectionPage extends Page implements Observer { private int musicSelectableIndex; private TextButton beginButton; + + private int uiSongCount; private float scrollTimer, scrollDelay = 0.2f, scrollDelMod, songSelectionTimer; - private ExecutorService exec; public MusicSelectionPage(Skin skin, MusicListController musicListController, AssetManager assetManager, Vector3 cameraTarget, AnalysisPage ap) { setTextureBackground(assetManager.get("gradients.atlas", TextureAtlas.class).findRegion("red-round")); this.skin = skin; this.mc = musicListController; + mic = new MusicInfoController(mc.getMusicList()); musicFileAnnotation = Gdx.app.getPreferences("music_file_annotation"); this.assets = assetManager; musicTable = new Table(); @@ -137,8 +139,6 @@ public class MusicSelectionPage extends Page implements Observer { ap.processSong(mc.getMusicList().getAudioData(getSelectedMusic())); } }); - - exec = Executors.newSingleThreadExecutor(); } @Override @@ -175,14 +175,20 @@ public class MusicSelectionPage extends Page implements Observer { } } super.act(delta); + + if (uiSongCount < selectables.size) { + musicTable.add(selectables.get(uiSongCount)).expandX().fillX(); + uiSongCount++; + musicTable.row(); + } } public FileHandle getSelectedMusic() { return currentlySelected.getMusicFile(); } - public SongInfo getSelectedMusicInfo() { - return currentlySelected.getAudioInfo(); + public MusicInfo getSelectedMusicInfo() { + return currentlySelected.getMusicInfo(); } public void refreshUIList() { @@ -191,6 +197,8 @@ public class MusicSelectionPage extends Page implements Observer { musicInfoTable.clear(); musicSubInfo.clear(); extraInfoDone = false; + uiSongCount = 0; + mic.loadSongInfo(); for (int i = 0; i < selectables.size; i++) { selectables.get(i).dispose(); @@ -199,44 +207,24 @@ public class MusicSelectionPage extends Page implements Observer { Gdx.app.debug("MusicSelectionPage", "Refreshing..."); 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); - - 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.row(); - musicSubInfo.add(author); - musicSubInfo.row(); - musicSubInfo.add(songLength); - musicSubInfo.row(); - musicSubInfo.add(previousTop); - musicSubInfo.row(); - musicSubInfo.add(ratedDifficulty); - musicSubInfo.pack(); - musicInfoTable.add(musicSubInfo).spaceBottom(20f); - musicInfoTable.row(); - musicInfoTable.add(albumCover).size(musicInfoTable.getWidth()/2f); - musicInfoTable.row(); - musicInfoTable.add(beginButton).spaceTop(20f).fillX(); - - if (currentlySelected != null) { - updateInformation(); - } - Gdx.app.debug("MusicSelectionPage", "Refresh complete. " + selectables.size + " songs loaded."); - }); + musicInfoTable.add(songTitle).width(musicInfoTable.getWidth()*0.6f).spaceBottom(30f); + musicInfoTable.row(); + musicSubInfo.add(author); + musicSubInfo.row(); + musicSubInfo.add(songLength); + musicSubInfo.row(); + musicSubInfo.add(previousTop); + musicSubInfo.row(); + musicSubInfo.add(ratedDifficulty); + musicSubInfo.pack(); + musicInfoTable.add(musicSubInfo).spaceBottom(20f); + musicInfoTable.row(); + musicInfoTable.add(albumCover).size(musicInfoTable.getWidth()/2f); + musicInfoTable.row(); + musicInfoTable.add(beginButton).spaceTop(20f).fillX(); } @Override @@ -276,7 +264,6 @@ public class MusicSelectionPage extends Page implements Observer { if ((musicSelectableIndex = (musicTable.getChildren().indexOf(currentlySelected, true) - 1)) < 0) { musicSelectableIndex = musicTable.getChildren().size-1; } - deselectAll(); ((MusicSelectable)musicTable.getChildren().get(musicSelectableIndex)).select(); 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() { - songTitle.setText(currentlySelected.getAudioInfo().getMusicName(), null); - author.setText("Author: " + currentlySelected.getAudioInfo().getAuthor()); + songTitle.setText(currentlySelected.getMusicInfo().getMusicName(), null); + author.setText("Author: " + currentlySelected.getMusicInfo().getAuthor()); - long lengthInSeconds = currentlySelected.getAudioInfo().getDurationInSeconds(); + long lengthInSeconds = currentlySelected.getMusicInfo().getDurationInSeconds(); int min = (int) (lengthInSeconds/60); int sec = (int) (lengthInSeconds - (min*60)); 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())); ratedDifficulty.setText("Rated Difficulty: " + difficulty); - albumCover.setDrawable((new TextureRegionDrawable(new TextureRegion(currentlySelected.getAudioInfo().getAlbumCover())))); + albumCover.setDrawable((new TextureRegionDrawable(new TextureRegion(currentlySelected.getMusicInfo().getAlbumCover())))); } } diff --git a/core/src/zero1hd/rhythmbullet/screens/MainMenu.java b/core/src/zero1hd/rhythmbullet/screens/MainMenu.java index f419943..41c5827 100755 --- a/core/src/zero1hd/rhythmbullet/screens/MainMenu.java +++ b/core/src/zero1hd/rhythmbullet/screens/MainMenu.java @@ -44,6 +44,7 @@ public class MainMenu extends ScreenAdapter implements TransitionAdapter { private RhythmBullet core; private MusicListController mlc; + private float lerpAlpha; private ShaderProgram gaussianBlurShader;