Restructuring starting from the launcher up to obtaining PCM data from

the audio backend is complete but untested.
This commit is contained in:
2018-07-22 12:35:08 -05:00
parent 7782a6a44b
commit 0dce05050a
33 changed files with 1114 additions and 908 deletions

View File

@@ -0,0 +1,45 @@
package zero1hd.rhythmbullet;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.utils.Disposable;
public interface AssetPack extends Disposable {
/**
* Called right after the game instance is created and passed to LWJGL. This method is called once for you to instantiate things for later use but require Libgdx functions.
*/
public void initiateResources();
/**
* Game manager calls this when it needs to load textures.
*/
public void queueTextures(AssetManager assetManager);
/**
* Game manager calls this when it needs to load sound effects.
*/
public void queueSFX(AssetManager assetManager);
/**
* Game manager calls this when it needs to load particles.
*/
public void queueParticles(AssetManager assetManager);
/**
* Game manager calls when it needs to load particles. Usually called after textures are loaded since the skin requires the other assets.
* @param skin the skin object to set up.
*/
public void setupSkin(Skin skin);
/**
* Game manager calls when it needs the fonts to be generated. Usually called right before setting up the skin itself since items in the skin need fonts.
*/
public void generateFonts(Skin skin);
/**
* Game manager calls this once all assets are loaded. This function should be used to make some in-code adjustments to assets that will be consistent throughout the game for that run.
* @param assetManager gives you access to the assets to modify.
*/
public void complete(AssetManager assetManager);
}

View File

@@ -0,0 +1,7 @@
package zero1hd.rhythmbullet;
import com.badlogic.gdx.Screen;
public interface InitialScreen {
public Screen createMainScreen(RhythmBullet game);
}

View File

@@ -13,28 +13,14 @@ import com.badlogic.gdx.assets.loaders.TextureLoader;
import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver;
import com.badlogic.gdx.assets.loaders.resolvers.ResolutionFileResolver.Resolution;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.ParticleEffect;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox.CheckBoxStyle;
import com.badlogic.gdx.scenes.scene2d.ui.ImageButton.ImageButtonStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle;
import com.badlogic.gdx.scenes.scene2d.ui.List.ListStyle;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane.ScrollPaneStyle;
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox.SelectBoxStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Slider.SliderStyle;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle;
import com.badlogic.gdx.scenes.scene2d.ui.TextField.TextFieldStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Window.WindowStyle;
import zero1hd.rhythmbullet.util.GenericFileTypeHandler;
import zero1hd.rhythmbullet.util.RoundingResolutionHandler;
import zero1hd.rhythmbullet.util.AdvancedResizeScreen;
import zero1hd.rhythmbullet.util.ResizeReadyScreen;
public class RhythmBullet extends Game {
@@ -42,34 +28,37 @@ public class RhythmBullet extends Game {
public static final int WORLD_HEIGHT = 48;
public static final int SPAWN_CIRCLE_RADIUS = 6;
public static int pixels_per_unit;
private boolean initComplete = false;
private boolean initiated;
private boolean resizing;
private int screenWidth, screenHeight;
public static final String VERSION = "(0.1)R1-PreAlpha";
private AssetManager assetManager = new AssetManager();
private Skin defaultSkin = new Skin();
private FreeTypeFontGenerator default_fontGenerator;
private FreeTypeFontGenerator darktech_ldr_fontGenerator;
private Skin skin;
TextureAtlas skinAtlas;
private Preferences prefs;
private RoundingResolutionHandler rRHandler;
private Screen initialScreen;
private AssetPack assetPack;
public void setInitialScreen(Screen initialScreen) {
/**
* This should be called before passed to LWJGL. Setup for system-dependent items such as UI and assets.
* @param initialScreen the first screen to go to.
* @param assetPack the asset package to be used.
*/
public void setup(Screen initialScreen, AssetPack assetPack) {
this.initialScreen = initialScreen;
this.assetPack = assetPack;
}
@Override
public void create() {
Gdx.app.setLogLevel(Application.LOG_DEBUG);
prefs = Gdx.app.getPreferences("RhythmBullet Preferences");
setScreen(initialScreen);
assetPack.initiateResources();
if (getPrefs().getBoolean("fullscreen", true)) {
Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode());
} else {
Gdx.graphics.setWindowedMode(getPrefs().getInteger("screen-width"), getPrefs().getInteger("screen-height"));
}
prefs = Gdx.app.getPreferences("RhythmBullet Preferences");
Resolution[] resolution = {
new Resolution(1280, 720, "1280x720"),
@@ -88,57 +77,68 @@ public class RhythmBullet extends Game {
assetManager.setLoader(Texture.class, new TextureLoader(rRHandler));
assetManager.setLoader(ParticleEffect.class, new ParticleEffectLoader(genericFileFinder));
assetManager.setLoader(Sound.class, new SoundLoader(genericFileFinder));
default_fontGenerator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/Gasalt-Regular.ttf"));
darktech_ldr_fontGenerator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/darktech_ldr.ttf"));
getrRHandler().setResolution(getPrefs().getInteger("screen-width"), getPrefs().getInteger("screen-height"));
rRHandler.setResolution(getPrefs().getInteger("screen-width"), getPrefs().getInteger("screen-height"));
queueAssets();
setScreen(initialScreen);
screenWidth = Gdx.graphics.getWidth();
screenHeight = Gdx.graphics.getHeight();
pixels_per_unit = (int) (Float.valueOf(screenHeight)/Float.valueOf(WORLD_HEIGHT));
}
public void checkAssetQueue() {
if (!initComplete) {
if (assetManager.update()) {
generateFonts(Gdx.graphics.getHeight());
defineSkinStyles();
setInitComplete();
}
}
}
public void checkResize() {
if (resizing) {
if (assetManager.update()) {
Gdx.app.debug("Resize", "Post transition is happening");
resizing = false;
generateFonts(Gdx.graphics.getHeight());
defineSkinStyles();
assetManager.get("standard_thrust.p", ParticleEffect.class).flipY();
((AdvancedResizeScreen) getScreen()).postAssetLoad();
}
if (getPrefs().getBoolean("fullscreen", true)) {
Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode());
} else {
Gdx.graphics.setWindowedMode(getPrefs().getInteger("screen-width"), getPrefs().getInteger("screen-height"));
}
}
@Override
public void render() {
checkResize();
checkAssetQueue();
super.render();
}
public boolean checkAssetQueue() {
if (assetManager.update()) {
if (skin != null) skin.dispose();
skin = new Skin();
skinAtlas = assetManager.get("uiskin.atlas", TextureAtlas.class);
getSkinSkin().addRegions(skinAtlas);
assetPack.generateFonts(skin);
assetPack.setupSkin(skin);
assetPack.complete(assetManager);
if (resizing) {
Gdx.app.debug("Resize", "Post resize is starting...");
if (getScreen() instanceof ResizeReadyScreen) {
((ResizeReadyScreen) getScreen()).postAssetLoad();
} else {
throw new IllegalStateException("Cannot perform window resize on a screen that isn't using a resize ready screen.");
}
Gdx.app.debug("Resize", "Post resize has ended.");
if (!initiated) {
setScreen(((InitialScreen) initialScreen).createMainScreen(this));
initiated = true;
}
resizing = false;
}
return true;
}
return false;
}
@Override
public void setScreen(Screen screen) {
if (screen instanceof AdvancedResizeScreen) {
AdvancedResizeScreen advancedResizeScreen = (AdvancedResizeScreen) screen;
if (screen instanceof ResizeReadyScreen) {
ResizeReadyScreen advancedResizeScreen = (ResizeReadyScreen) screen;
try {
advancedResizeScreen.preAssetLoad();
} catch (NullPointerException cleanScreen) {
//Tried to perform pre-asset reload, but had uninitialized objects, meaning this is a new screen, or "clean" screen.
} finally {
advancedResizeScreen.postAssetLoad();
}
@@ -146,235 +146,61 @@ public class RhythmBullet extends Game {
super.setScreen(screen);
}
@Override
public void dispose() {
Gdx.app.debug("Core", "disposing...");
if (initComplete) {
skinAtlas.dispose();
getDefaultSkin().dispose();
default_fontGenerator.dispose();
darktech_ldr_fontGenerator.dispose();
assetManager.dispose();
getScreen().dispose();
}
super.dispose();
}
@Override
public void resize(int width, int height) {
Gdx.app.debug("RhythmBullet/resize", "Previous size:" + screenWidth + "x" + screenHeight + " new size: " + width + "x" + height);
Gdx.app.debug("RhythmBullet/resize", "Current size:" + screenWidth + "x" + screenHeight + " new size: " + width + "x" + height);
if (width != screenWidth || height != screenHeight) {
screenWidth = Gdx.graphics.getWidth();
screenHeight = Gdx.graphics.getHeight();
pixels_per_unit = (int) (Float.valueOf(screenHeight)/Float.valueOf(WORLD_HEIGHT));
if (initComplete) {
Gdx.app.debug("Resize", "Pre-transition is happening. Using resolution " + width + "x" + height);
rRHandler.setResolution(width, height);
((AdvancedResizeScreen) getScreen()).preAssetLoad();
assetManager.clear();
prefs.putInteger("screen-width", width);
prefs.putInteger("screen-height", height);
prefs.flush();
resizing = true;
queueAssets();
Gdx.app.debug("Resize", "Pre-resize is happening. Resizing to " + width + "x" + height);
rRHandler.setResolution(width, height);
if (getScreen() instanceof ResizeReadyScreen) {
((ResizeReadyScreen) getScreen()).preAssetLoad();
} else {
throw new IllegalStateException("Cannot perform window resize on a screen that isn't using a resize ready screen.");
}
prefs.putInteger("screen-width", width);
prefs.putInteger("screen-height", height);
prefs.flush();
resizing = true;
assetManager.clear();
queueAssets();
}
super.resize(width, height);
}
public int fontScale(float fontSize, int height) {
int size = MathUtils.round(Gdx.graphics.getDensity()*(fontSize*height));
if (size >= 200) {
size = 200;
}
return size;
public void queueAssets() {
assetPack.queueTextures(assetManager);
assetPack.queueSFX(assetManager);
assetPack.queueParticles(assetManager);
}
public AssetManager getAssetManager() {
return assetManager;
}
public Skin getDefaultSkin() {
return defaultSkin;
public Skin getSkinSkin() {
return skin;
}
public Preferences getPrefs() {
return prefs;
}
public void setInitComplete() {
initComplete = true;
}
public boolean isInitComplete() {
return initComplete;
}
public RoundingResolutionHandler getrRHandler() {
return rRHandler;
}
public void queueAssets() {
assetManager.load("uiskin.atlas", TextureAtlas.class);
assetManager.load("Tech-Circle1.png", Texture.class);
assetManager.load("polyjet-standard.png", Texture.class);
assetManager.load("standard_thrust.p", ParticleEffect.class);
assetManager.load("keyboard.atlas", TextureAtlas.class);
assetManager.load("cybercircle3B.png", Texture.class);
assetManager.load("title.png", Texture.class);
assetManager.load("cybercircle1.png", Texture.class);
assetManager.load("defaultCover.png", Texture.class);
assetManager.load("teleport-cloak.p", ParticleEffect.class);
assetManager.load("pop_open.ogg", Sound.class);
assetManager.load("pop_close.ogg", Sound.class);
assetManager.load("laser.png", Texture.class);
assetManager.load("pellet.png", Texture.class);
assetManager.load("shard.png", Texture.class);
assetManager.load("bar.png", Texture.class);
assetManager.load("flake.png", Texture.class);
assetManager.load("void_circle.png", Texture.class);
assetManager.load("laser.ogg", Sound.class);
assetManager.load("explosion.ogg", Sound.class);
assetManager.load("disintegrate.ogg", Sound.class);
assetManager.load("explosion-s.p", ParticleEffect.class);
assetManager.load("beateffect.p", ParticleEffect.class);
assetManager.load("tpSelector.png", Texture.class);
assetManager.load("magic1.png", Texture.class);
assetManager.load("backgrounds/mainBG.png", Texture.class);
}
public void generateFonts(final int height) {
defaultSkin = new Skin();
Gdx.app.debug("Prelaunch Debug Info", "Generating fonts with screen height of " + height);
skinAtlas = assetManager.get("uiskin.atlas", TextureAtlas.class);
getDefaultSkin().addRegions(skinAtlas);
getDefaultSkin().add("window-font", default_fontGenerator.generateFont(new FreeTypeFontParameter() {
{
size = 18;
}
}));
getDefaultSkin().add("sub-font", default_fontGenerator.generateFont(new FreeTypeFontParameter() {
{
size = fontScale(0.05f, height);
}
}));
getDefaultSkin().add("default-font", default_fontGenerator.generateFont(new FreeTypeFontParameter() {
{
size = fontScale(0.07f, height);
}
}));
getDefaultSkin().add("large-font", default_fontGenerator.generateFont(new FreeTypeFontParameter() {
{
size = fontScale(0.085f, height);
}
}));
getDefaultSkin().add("special-font", darktech_ldr_fontGenerator.generateFont(new FreeTypeFontParameter() {
{
size = fontScale(0.075f, height);
}
}));
}
public void defineSkinStyles() {
getDefaultSkin().add("default", Color.WHITE);
getDefaultSkin().add("inverse", Color.BLACK);
TextButtonStyle defaultTextButton = new TextButtonStyle();
defaultTextButton.up = getDefaultSkin().getDrawable("rect");
defaultTextButton.down = getDefaultSkin().getDrawable("rect-down");
defaultTextButton.font = getDefaultSkin().getFont("default-font");
defaultTextButton.fontColor = getDefaultSkin().getColor("default");
defaultTextButton.disabled = getDefaultSkin().getDrawable("rect-disabled");
getDefaultSkin().add("default", defaultTextButton);
TextButtonStyle subTextbutton = new TextButtonStyle(defaultTextButton);
subTextbutton.font = getDefaultSkin().getFont("sub-font");
getDefaultSkin().add("sub", subTextbutton);
TextButtonStyle windowTextButton = new TextButtonStyle(defaultTextButton);
windowTextButton.font = getDefaultSkin().getFont("window-font");
getDefaultSkin().add("window", windowTextButton);
TextButtonStyle textButtonLeft = new TextButtonStyle();
textButtonLeft.up = getDefaultSkin().getDrawable("left-button");
textButtonLeft.down = getDefaultSkin().getDrawable("left-button-down");
textButtonLeft.font = getDefaultSkin().getFont("default-font");
textButtonLeft.fontColor = getDefaultSkin().getColor("default");
getDefaultSkin().add("left", textButtonLeft);
SliderStyle defaultSlider = new SliderStyle(getDefaultSkin().getDrawable("default-slider"), getDefaultSkin().getDrawable("default-slider-knob"));
getDefaultSkin().add("default-horizontal", defaultSlider);
SliderStyle vertSlider = new SliderStyle(defaultSlider);
vertSlider.knob = getDefaultSkin().getDrawable("vertical-slider-knob");
getDefaultSkin().add("default-vertical", vertSlider);
LabelStyle defaultLabel = new LabelStyle();
defaultLabel.font = getDefaultSkin().getFont("default-font");
defaultLabel.fontColor = getDefaultSkin().getColor("default");
getDefaultSkin().add("default", defaultLabel);
TextFieldStyle defaultTextField = new TextFieldStyle(getDefaultSkin().getFont("sub-font"), getDefaultSkin().getColor("default"), getDefaultSkin().getDrawable("cursor"), getDefaultSkin().getDrawable("selection"), getDefaultSkin().getDrawable("textfield"));
getDefaultSkin().add("default", defaultTextField);
TextFieldStyle uiTextField = new TextFieldStyle(defaultTextField);
uiTextField.font = getDefaultSkin().getFont("window-font");
getDefaultSkin().add("ui", uiTextField);
WindowStyle defaultWindow = new WindowStyle(getDefaultSkin().getFont("window-font"), getDefaultSkin().getColor("default"), getDefaultSkin().getDrawable("default-window"));
getDefaultSkin().add("default", defaultWindow);
WindowStyle tintedWindow = new WindowStyle(defaultWindow);
tintedWindow.titleFontColor = getDefaultSkin().getColor("inverse");
tintedWindow.background = getDefaultSkin().getDrawable("tinted-window");
getDefaultSkin().add("tinted", tintedWindow);
ListStyle defaultList = new ListStyle(getDefaultSkin().getFont("window-font"), getDefaultSkin().getColor("inverse"), getDefaultSkin().getColor("default"), getDefaultSkin().getDrawable("selection"));
getDefaultSkin().add("default", defaultList);
ScrollPaneStyle defaultScrollPane = new ScrollPaneStyle();
defaultScrollPane.vScroll = getDefaultSkin().getDrawable("default-scroll");
defaultScrollPane.hScrollKnob = getDefaultSkin().getDrawable("default-round-large");
defaultScrollPane.hScroll = getDefaultSkin().getDrawable("default-scroll");
defaultScrollPane.vScrollKnob = getDefaultSkin().getDrawable("default-round-large");
getDefaultSkin().add("default", defaultScrollPane);
CheckBoxStyle defaultCheckBox = new CheckBoxStyle(getDefaultSkin().getDrawable("check-off"), getDefaultSkin().getDrawable("check-on"), getDefaultSkin().getFont("window-font"), getDefaultSkin().getColor("default"));
defaultCheckBox.checkboxOffDisabled = getDefaultSkin().getDrawable("check-disabled");
getDefaultSkin().add("default", defaultCheckBox);
SelectBoxStyle defaultSelectBox = new SelectBoxStyle(getDefaultSkin().getFont("default-font"), getDefaultSkin().getColor("default"), getDefaultSkin().getDrawable("default-select"), defaultScrollPane, defaultList);
getDefaultSkin().add("default", defaultSelectBox);
Gdx.app.debug("Prelaunch Debug Info", "UI Skin has been defined.");
CheckBoxStyle playButtonStyle = new CheckBoxStyle(defaultCheckBox);
playButtonStyle.checkboxOn = getDefaultSkin().getDrawable("play-down");
playButtonStyle.checkboxOff = getDefaultSkin().getDrawable("play");
getDefaultSkin().add("play-button", playButtonStyle);
ImageButtonStyle pauseButtonStyle = new ImageButtonStyle();
pauseButtonStyle.down = getDefaultSkin().getDrawable("pause-down");
pauseButtonStyle.up = getDefaultSkin().getDrawable("pause");
getDefaultSkin().add("pause-button", pauseButtonStyle);
ImageButtonStyle fastForwardButtonStyle = new ImageButtonStyle();
fastForwardButtonStyle.down = getDefaultSkin().getDrawable("fast-forward-down");
fastForwardButtonStyle.up = getDefaultSkin().getDrawable("fast-forward");
getDefaultSkin().add("fast-forward-button", fastForwardButtonStyle);
ImageButtonStyle reverseButtonStyle = new ImageButtonStyle();
reverseButtonStyle.down = getDefaultSkin().getDrawable("rewind-down");
reverseButtonStyle.up = getDefaultSkin().getDrawable("rewind");
getDefaultSkin().add("rewind-button", reverseButtonStyle);
CheckBoxStyle shuffleButtonStyle = new CheckBoxStyle(defaultCheckBox);
shuffleButtonStyle.checkboxOff = getDefaultSkin().getDrawable("shuffle");
shuffleButtonStyle.checkboxOn = getDefaultSkin().getDrawable("shuffle-down");
getDefaultSkin().add("shuffle-button", shuffleButtonStyle);
@Override
public void dispose() {
Gdx.app.debug("Core", "disposing...");
try {
skinAtlas.dispose();
getSkinSkin().dispose();
assetManager.dispose();
getScreen().dispose();
assetPack.dispose();
} catch (NullPointerException npe) {
//Means the game was closed before everything was initiated.
}
super.dispose();
}
}

View File

@@ -0,0 +1,12 @@
package zero1hd.rhythmbullet.audio;
import com.badlogic.gdx.files.FileHandle;
import zero1hd.rhythmbullet.audio.processor.AudioProcessor;
public interface AudioProcessorFactory {
/**
* @return a new {@link #zero1hd.rhythmbullet.audio.processor.AudioProcessor()} from the appropriate platform.
*/
public AudioProcessor newMP3AudioProcessor(FileHandle fileHandle);
}

View File

@@ -0,0 +1,37 @@
package zero1hd.rhythmbullet.audio;
import java.io.IOException;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.AudioHeader;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.tag.TagException;
import com.badlogic.gdx.files.FileHandle;
public class MinimalAudioHeader {
private int sampleRate, channelCount;
public MinimalAudioHeader(FileHandle musicFile) {
try {
AudioFile file = AudioFileIO.read(musicFile.file());
AudioHeader header = file.getAudioHeader();
sampleRate = header.getSampleRateAsNumber();
channelCount = (header.getChannels().equals("Mono") ? 1 : 2);
} catch (CannotReadException | IOException | TagException | ReadOnlyFileException
| InvalidAudioFrameException e) {
e.printStackTrace();
}
}
public int getSampleRate() {
return sampleRate;
}
public int getChannelCount() {
return channelCount;
}
}

View File

@@ -0,0 +1,223 @@
package zero1hd.rhythmbullet.audio;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Music.OnCompletionListener;
import com.badlogic.gdx.files.FileHandle;
/**
* Manages current games music playback and does this in tandem with the {@link MusicList} by asking to retrieve files and then feeding it to LibGDX.
* Notifies observers when a new song is loaded.
* The loading model is like taking a disk and loading it into a player. It doesn't necessarily mean it'll play right away, but its ready and the only track that has a stream opened.
* @author yunya
*
*/
public class MusicController extends Observable implements OnCompletionListener, Observer {
public enum States {
Loaded, Playing;
}
private MusicList musicList;
private MinimalAudioHeader musicHeader;
private Music music;
private int currentPlaybackIndex;
private boolean autoPlay;
private boolean shuffle;
private Random rand;
private Preferences prefs;
public MusicController(MusicList musicList, Preferences prefs) {
if (prefs == null) throw new NullPointerException("preferences can't be null...");
if (musicList == null) throw new NullPointerException("music list can't be null...");
musicList.addObserver(this);
this.prefs = prefs;
this.musicList = musicList;
rand = new Random();
}
/**
* This play method automatically sets the volume.
*/
public void play() {
if (music != null) {
Gdx.app.debug("MusicListController", "Playing from MLC.");
music.play();
music.setVolume(prefs.getFloat("music vol", 1f));
notifyObservers(States.Playing);
} else {
Gdx.app.debug("MusicListController", "failed to begin playing. Load the music!!!");
}
}
/**
* Called to pause current song. Does nothing if no song is playing or loaded.
*/
public void pause() {
if (music != null) {
music.pause();
}
}
/**
* Loads music based on the index in the {@link MusicList}.
* @param index of music to play
*/
public void setMusicByIndex(int index) {
this.currentPlaybackIndex = index;
loadMusic();
}
/**
* Goes to the next track
*/
public void skip() {
currentPlaybackIndex++;
if (shuffle) {
shuffle(false);
}
loadMusic();
}
/**
* Goes to the previous track
*/
public void previous() {
currentPlaybackIndex--;
if (shuffle) {
shuffle(false);
}
loadMusic();
}
@Override
public void onCompletion(Music music) {
if (autoPlay) {
if (shuffle) {
shuffle(false);
} else {
currentPlaybackIndex++;
}
loadMusic();
play();
}
}
/**
* 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) {
Gdx.app.debug("MusicListController", "shuffled.");
if (musicList.getTotal() == 0) {
currentPlaybackIndex = 0;
} else {
currentPlaybackIndex = rand.nextInt(musicList.getTotal());
}
if (load) {
loadMusic();
}
}
public void setAutoPlay(boolean autoPlay) {
this.autoPlay = autoPlay;
}
public void setShuffle(boolean shuffle) {
this.shuffle = shuffle;
}
public boolean isShuffle() {
return shuffle;
}
public boolean isAutoPlay() {
return autoPlay;
}
/**
* Loads the current selected song.
*/
public void loadMusic() {
Gdx.app.debug("MusicListController", "music is being loaded and listeners are being notified.");
musicHeader = null;
if (music != null) {
music.dispose();
}
if (currentPlaybackIndex < 0) {
currentPlaybackIndex = musicList.getTotal()-1;
}
if (currentPlaybackIndex >= musicList.getTotal()) {
currentPlaybackIndex = 0;
}
this.music = Gdx.audio.newMusic(musicList.getMusicArray().get(currentPlaybackIndex));
music.setOnCompletionListener(this);
setChanged();
if (autoPlay) {
play();
}
notifyObservers(States.Loaded);
}
public MusicList getMusicList() {
return musicList;
}
public FileHandle getCurrentMusicFileHandle() {
return musicList.getSongFileHandleFromIndex(currentPlaybackIndex);
}
public MinimalAudioHeader getCurrentMusicHeader() {
if (musicHeader != null) {
return musicHeader;
} else {
return musicList.newMinimalAudioHeader(getCurrentMusicFileHandle());
}
}
@Override
public void update(Observable o, Object arg) {
if (o == musicList) {
loadMusic();
if (autoPlay) {
play();
}
}
}
public String getCurrentSongName() {
return getCurrentMusicFileHandle().nameWithoutExtension();
}
public float getCurrentPosition() {
if (music != null) {
return music.getPosition();
}
return 0;
}
public void setMusicPosition(float position) {
if (music != null) {
music.setPosition(position);
}
}
public boolean isPlaying() {
return music.isPlaying();
}
/**
* Returns the current music. In no circumstances should this be used to begin playing or it would be playing independent of the controller.
* Doing otherwise would mean you are playing the music separately of the controller.
* @return
*/
public Music getCurrentMusic() {
return music;
}
}

View File

@@ -0,0 +1,135 @@
package zero1hd.rhythmbullet.audio;
import java.util.Observable;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Array;
import zero1hd.rhythmbullet.audio.processor.AudioProcessor;
import zero1hd.rhythmbullet.audio.processor.WAVAudioProcessor;
/**
* A music list made to store paths to all the songs within a given directory. Can activate the music calling {@link #newAudioProcessor(FileHandle)} and its derivatives.
* Is observable, meaning, given there are observers, will notify when the list completes a refresh.
* @author yunya
*/
public class MusicList extends Observable {
private Array<FileHandle> musicList;
private RecursiveMusicSearchThread searchThread;
private AudioProcessorFactory audioProcFactory;
private boolean searched;
public MusicList(AudioProcessorFactory audioProcessorFactory) {
this.audioProcFactory = audioProcessorFactory;
musicList = new Array<>();
searchThread = new RecursiveMusicSearchThread("Music Search Thread");
}
/**
* Wrapper method that uses async refresh.
* Also notifies listeners that are on the main thread.
*/
public void asyncSearch() {
searchThread.start();
}
public void setSearchPath(String searchPath) {
searchThread.setSearchDirectory(Gdx.files.absolute(searchPath));
setChanged();
}
/**
* @param file
* @return a {@link #zero1hd.rhythmbullet.audio.processor.AudioProcessor()} of the given music file. Will return null if theres a format error.
*/
public AudioProcessor newAudioProcessor(FileHandle file) {
if (file.extension().equalsIgnoreCase("wav")) {
return new WAVAudioProcessor(file);
} else if (file.extension().equalsIgnoreCase("mp3")) {
return audioProcFactory.newMP3AudioProcessor(file);
}
return null;
}
/**
*
* @param file the music file that you need the header for.
* @return the header containing minimal info of the song.
*/
public MinimalAudioHeader newMinimalAudioHeader(FileHandle file) {
return new MinimalAudioHeader(file);
}
public AudioProcessor newAudioProcessorFromIndex(int index) {
if (!searched) Gdx.app.debug("SongList", "Warning, this list hasn't even searched yet...");
return newAudioProcessor(musicList.get(index));
}
public FileHandle getSongFileHandleFromIndex(int index) {
if (!searched) Gdx.app.debug("SongList", "Warning, this list hasn't even searched yet...");
return musicList.get(index);
}
public Array<FileHandle> getMusicArray() {
return musicList;
}
public boolean isSearched() {
return searched;
}
private void searchComplete() {
notifyObservers();
searched = true;
}
public int getTotal() {
return musicList.size;
}
private class RecursiveMusicSearchThread implements Runnable {
private Thread thread;
private String threadName;
private FileHandle directory;
public RecursiveMusicSearchThread(String name) {
this.threadName = name;
}
public void setSearchDirectory(FileHandle directory) {
this.directory = directory;
}
@Override
public void run() {
musicList = recursiveMusicSearch(directory);
searchComplete();
}
public void start() {
if (thread == null && !thread.isAlive()) {
thread = new Thread(this, threadName);
thread.start();
} else {
throw new IllegalStateException("Two " + threadName + " instances (threads) were created. This is not allowed for optimization as there is no reason to have two running.");
}
}
private Array<FileHandle> recursiveMusicSearch(FileHandle fileHandle) {
Array<FileHandle> musicFiles = new Array<>();
FileHandle[] files = fileHandle.list();
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
musicFiles.addAll(recursiveMusicSearch(files[i]));
} else {
if (files[i].extension().equalsIgnoreCase("wav") || files[i].extension().equalsIgnoreCase("mp3")) {
musicFiles.add(files[i]);
}
}
}
return musicFiles;
}
}
}

View File

@@ -0,0 +1,102 @@
package zero1hd.rhythmbullet.audio;
import java.util.Observable;
import java.util.Observer;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import zero1hd.rhythmbullet.audio.metadata.AudioMetadata;
import zero1hd.rhythmbullet.audio.metadata.MP3Metadata;
import zero1hd.rhythmbullet.audio.metadata.WAVMetadata;
public class MusicMetadataController implements Disposable, Observer {
private MusicList musicList;
private Array<AudioMetadata> metadataArray;
private MetadataLoadingThread loadingThread;
private volatile boolean searching;
public MusicMetadataController(MusicList musicList) {
this.musicList = musicList;
metadataArray = new Array<>();
}
public MusicList getMusicList() {
return musicList;
}
/**
* Non-blocking, loads on separate thread.
*/
public void loadSongInfo() {
loadingThread.start();
}
public boolean isDone() {
return (metadataArray.size == musicList.getMusicArray().size);
}
@Override
public void dispose() {
for (int i = 0; i < metadataArray.size; i++) {
metadataArray.get(i).dispose();
}
}
public int size() {
return metadataArray.size;
}
public AudioMetadata getMetadata(int index) {
synchronized (loadingThread) {
return metadataArray.get(index);
}
}
public AudioMetadata getInfo(FileHandle filehandle) {
return metadataArray.get(musicList.getMusicArray().indexOf(filehandle, true));
}
public boolean isSearching() {
return searching;
}
private class MetadataLoadingThread implements Runnable {
private Thread thread;
private String name = "Metadata-Load";
@Override
public void run() {
searching = true;
for (int i = 0; i < metadataArray.size; i++) {
metadataArray.get(i).dispose();
}
metadataArray.clear();
for (int i = 0; i < musicList.getTotal(); i++) {
FileHandle musicFile = musicList.getMusicArray().get(i);
synchronized (this) {
if (musicFile.extension().equalsIgnoreCase("wav")) {
metadataArray.add(new WAVMetadata(musicFile));
} else if (musicFile.extension().equalsIgnoreCase("mp3")) {
metadataArray.add(new MP3Metadata(musicFile));
}
}
}
searching = false;
}
public void start() {
if (thread != null && !thread.isAlive()) {
thread = new Thread(this, name);
thread.start();
}
}
}
@Override
public void update(Observable o, Object arg) {
// TODO Auto-generated method stub
}
}

View File

@@ -1,29 +0,0 @@
package zero1hd.rhythmbullet.audio;
public class RhythmBulletMetadata {
private String highScore = "0", lastPlayed = "N/A", difficulty = "N/A";
public void setHighScore(String highScore) {
this.highScore = highScore;
}
public void setLastPlayed(String lastPlayed) {
this.lastPlayed = lastPlayed;
}
public void setDifficulty(String difficulty) {
this.difficulty = difficulty;
}
public String getHighScore() {
return highScore;
}
public String getDifficulty() {
return difficulty;
}
public String getLastPlayed() {
return lastPlayed;
}
}

View File

@@ -4,7 +4,7 @@
*
*/
package zero1hd.rhythmbullet.audio;
package zero1hd.rhythmbullet.audio.metadata;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;

View File

@@ -1,4 +1,4 @@
package zero1hd.rhythmbullet.audio;
package zero1hd.rhythmbullet.audio.metadata;
import java.io.IOException;

View File

@@ -1,4 +1,4 @@
package zero1hd.rhythmbullet.audio;
package zero1hd.rhythmbullet.audio.metadata;
import java.io.IOException;

View File

@@ -7,6 +7,7 @@ import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
public class WAVAudioProcessor implements AudioProcessor {
@@ -17,11 +18,18 @@ public class WAVAudioProcessor implements AudioProcessor {
private AudioInputStream audioInputStream;
private boolean initiated;
public WAVAudioProcessor(FileHandle fileHandle, int windowSize) throws IOException, UnsupportedAudioFileException {
public WAVAudioProcessor(FileHandle fileHandle) {
this.fileHandle = fileHandle;
AudioFormat format = AudioSystem.getAudioFileFormat(fileHandle.file()).getFormat();
stereo = format.getChannels() > 1 ? true : false;
sampleRate = (int) format.getSampleRate();
AudioFormat format;
try {
format = AudioSystem.getAudioFileFormat(fileHandle.file()).getFormat();
stereo = format.getChannels() > 1 ? true : false;
sampleRate = (int) format.getSampleRate();
} catch (UnsupportedAudioFileException | IOException e) {
Gdx.app.debug("WAVAudioProcessor", "Couldn't instantiate WAVAUdioProcessor due to error.");
e.printStackTrace();
}
}
@Override

View File

@@ -12,7 +12,6 @@ import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import zero1hd.rhythmbullet.audio.MusicManager;
public class HorizontalVisualizer implements Disposable {
private Pixmap pixmap;

View File

@@ -1,6 +1,6 @@
package zero1hd.rhythmbullet.util;
public interface AdvancedResizeScreen {
public interface ResizeReadyScreen {
/**
* called before assets are cleared from memory.
*/