2014-06-30 04:17:04 +02:00
|
|
|
/*
|
|
|
|
* opsu! - an open-source osu! client
|
2015-01-16 18:05:44 +01:00
|
|
|
* Copyright (C) 2014, 2015 Jeffrey Han
|
2014-06-30 04:17:04 +02:00
|
|
|
*
|
|
|
|
* opsu! is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* opsu! is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2015-01-08 01:29:51 +01:00
|
|
|
package itdelatrisu.opsu.audio;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-01-16 03:55:26 +01:00
|
|
|
import itdelatrisu.opsu.ErrorHandler;
|
2015-01-21 05:56:10 +01:00
|
|
|
import itdelatrisu.opsu.Options;
|
2015-05-17 03:25:19 +02:00
|
|
|
import itdelatrisu.opsu.beatmap.Beatmap;
|
2015-05-29 09:07:58 +02:00
|
|
|
import itdelatrisu.opsu.beatmap.BeatmapParser;
|
2016-12-10 20:15:19 +01:00
|
|
|
import itdelatrisu.opsu.beatmap.TimingPoint;
|
2016-11-20 13:49:53 +01:00
|
|
|
import itdelatrisu.opsu.states.Game;
|
2015-08-21 05:58:45 +02:00
|
|
|
import itdelatrisu.opsu.ui.UI;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
import java.io.File;
|
2015-03-16 18:16:30 +01:00
|
|
|
import java.io.IOException;
|
2014-06-30 04:17:04 +02:00
|
|
|
import java.lang.reflect.Field;
|
|
|
|
import java.nio.IntBuffer;
|
2015-03-16 18:16:30 +01:00
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
import javax.sound.sampled.AudioFileFormat;
|
|
|
|
import javax.sound.sampled.AudioSystem;
|
|
|
|
import javax.sound.sampled.UnsupportedAudioFileException;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
import org.lwjgl.BufferUtils;
|
|
|
|
import org.lwjgl.openal.AL;
|
|
|
|
import org.lwjgl.openal.AL10;
|
|
|
|
import org.newdawn.slick.Music;
|
2015-01-30 03:24:21 +01:00
|
|
|
import org.newdawn.slick.MusicListener;
|
2014-06-30 04:17:04 +02:00
|
|
|
import org.newdawn.slick.SlickException;
|
|
|
|
import org.newdawn.slick.openal.Audio;
|
|
|
|
import org.newdawn.slick.openal.SoundStore;
|
2015-08-21 05:58:45 +02:00
|
|
|
import org.newdawn.slick.util.ResourceLoader;
|
2015-03-16 18:16:30 +01:00
|
|
|
import org.tritonus.share.sampled.file.TAudioFileFormat;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Controller for all music.
|
|
|
|
*/
|
|
|
|
public class MusicController {
|
2015-01-22 06:44:45 +01:00
|
|
|
/** The current music track. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private static Music player;
|
|
|
|
|
2015-05-17 03:25:19 +02:00
|
|
|
/** The last beatmap passed to play(). */
|
|
|
|
private static Beatmap lastBeatmap;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-03-19 09:47:02 +01:00
|
|
|
/** The track duration. */
|
2015-03-19 20:58:50 +01:00
|
|
|
private static int duration = 0;
|
2015-03-19 09:47:02 +01:00
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Thread for loading tracks. */
|
2014-07-15 06:20:36 +02:00
|
|
|
private static Thread trackLoader;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-01-30 03:24:21 +01:00
|
|
|
/** Whether or not the current track has ended. */
|
|
|
|
private static boolean trackEnded;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Whether the theme song is currently playing. */
|
2014-12-21 00:17:04 +01:00
|
|
|
private static boolean themePlaying = false;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Track pause time. */
|
2015-01-15 06:56:30 +01:00
|
|
|
private static float pauseTime = 0f;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Whether the current track volume is dimmed. */
|
2015-01-16 22:02:02 +01:00
|
|
|
private static boolean trackDimmed = false;
|
|
|
|
|
2015-03-27 01:45:33 +01:00
|
|
|
/** The track dim level, if dimmed. */
|
|
|
|
private static float dimLevel = 1f;
|
|
|
|
|
2016-12-20 04:39:49 +01:00
|
|
|
/** Current timing point index in the track, advanced by {@link #getBeatProgress()}. */
|
|
|
|
private static int timingPointIndex;
|
|
|
|
|
|
|
|
/** Last non-inherited timing point. */
|
|
|
|
private static TimingPoint lastTimingPoint;
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
// This class should not be instantiated.
|
|
|
|
private MusicController() {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Plays an audio file at the preview position.
|
2015-02-12 20:52:51 +01:00
|
|
|
* If the audio file is already playing, then nothing will happen.
|
2015-05-17 03:25:19 +02:00
|
|
|
* @param beatmap the beatmap to play
|
2015-03-06 07:29:50 +01:00
|
|
|
* @param loop whether or not to loop the track
|
|
|
|
* @param preview whether to start at the preview time (true) or beginning (false)
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
2015-05-17 03:25:19 +02:00
|
|
|
public static void play(final Beatmap beatmap, final boolean loop, final boolean preview) {
|
2015-02-12 20:52:51 +01:00
|
|
|
// new track: load and play
|
2015-05-17 03:25:19 +02:00
|
|
|
if (lastBeatmap == null || !beatmap.audioFilename.equals(lastBeatmap.audioFilename)) {
|
2015-08-21 05:58:45 +02:00
|
|
|
final File audioFile = beatmap.audioFilename;
|
|
|
|
if (!audioFile.isFile() && !ResourceLoader.resourceExists(audioFile.getPath())) {
|
|
|
|
UI.sendBarNotification(String.format("Could not find track '%s'.", audioFile.getName()));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-01-18 20:29:48 +01:00
|
|
|
reset();
|
2015-01-15 22:47:55 +01:00
|
|
|
System.gc();
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-05-29 09:07:58 +02:00
|
|
|
switch (BeatmapParser.getExtension(beatmap.audioFilename.getName())) {
|
2014-06-30 04:17:04 +02:00
|
|
|
case "ogg":
|
|
|
|
case "mp3":
|
2014-07-15 06:20:36 +02:00
|
|
|
trackLoader = new Thread() {
|
2014-06-30 04:17:04 +02:00
|
|
|
@Override
|
|
|
|
public void run() {
|
2015-08-21 05:58:45 +02:00
|
|
|
loadTrack(audioFile, (preview) ? beatmap.previewTime : 0, loop);
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
};
|
2014-07-15 06:20:36 +02:00
|
|
|
trackLoader.start();
|
2014-06-30 04:17:04 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2015-02-12 20:52:51 +01:00
|
|
|
|
|
|
|
// new track position: play at position
|
2015-05-17 03:25:19 +02:00
|
|
|
else if (beatmap.previewTime != lastBeatmap.previewTime)
|
|
|
|
playAt(beatmap.previewTime, loop);
|
2015-02-12 20:52:51 +01:00
|
|
|
|
2015-05-17 03:25:19 +02:00
|
|
|
lastBeatmap = beatmap;
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads a track and plays it.
|
2015-02-12 20:52:51 +01:00
|
|
|
* @param file the audio file
|
|
|
|
* @param position the track position (in ms)
|
|
|
|
* @param loop whether or not to loop the track
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
2015-02-12 20:52:51 +01:00
|
|
|
private static void loadTrack(File file, int position, boolean loop) {
|
|
|
|
try {
|
2015-02-12 09:52:19 +01:00
|
|
|
player = new Music(file.getPath(), true);
|
2015-01-30 03:24:21 +01:00
|
|
|
player.addListener(new MusicListener() {
|
|
|
|
@Override
|
2015-03-08 21:34:28 +01:00
|
|
|
public void musicEnded(Music music) {
|
2016-12-20 04:39:49 +01:00
|
|
|
if (music == player) { // don't fire if music swapped
|
2015-03-08 21:34:28 +01:00
|
|
|
trackEnded = true;
|
2016-12-20 04:39:49 +01:00
|
|
|
resetTimingPoint();
|
|
|
|
}
|
2015-03-08 21:34:28 +01:00
|
|
|
}
|
2015-01-30 03:24:21 +01:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void musicSwapped(Music music, Music newMusic) {}
|
|
|
|
});
|
2015-02-12 20:52:51 +01:00
|
|
|
playAt(position, loop);
|
2014-06-30 04:17:04 +02:00
|
|
|
} catch (Exception e) {
|
2015-01-16 03:55:26 +01:00
|
|
|
ErrorHandler.error(String.format("Could not play track '%s'.", file.getName()), e, false);
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Plays the current track at the given position.
|
2015-02-12 20:52:51 +01:00
|
|
|
* @param position the track position (in ms)
|
|
|
|
* @param loop whether or not to loop the track
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
|
|
|
public static void playAt(final int position, final boolean loop) {
|
|
|
|
if (trackExists()) {
|
2015-01-20 20:52:02 +01:00
|
|
|
setVolume(Options.getMusicVolume() * Options.getMasterVolume());
|
2015-01-30 03:24:21 +01:00
|
|
|
trackEnded = false;
|
2015-01-21 01:01:18 +01:00
|
|
|
pauseTime = 0f;
|
2016-12-20 04:39:49 +01:00
|
|
|
resetTimingPoint();
|
2014-06-30 04:17:04 +02:00
|
|
|
if (loop)
|
|
|
|
player.loop();
|
|
|
|
else
|
|
|
|
player.play();
|
2015-02-12 20:52:51 +01:00
|
|
|
if (position >= 0)
|
|
|
|
player.setPosition(position / 1000f);
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-07-15 06:20:36 +02:00
|
|
|
* Returns true if a track is being loaded.
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
2014-07-15 06:20:36 +02:00
|
|
|
public static boolean isTrackLoading() {
|
|
|
|
return (trackLoader != null && trackLoader.isAlive());
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if a track is loaded.
|
|
|
|
*/
|
2015-02-12 20:52:51 +01:00
|
|
|
public static boolean trackExists() { return (player != null); }
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2014-07-02 09:02:11 +02:00
|
|
|
/**
|
2015-05-17 03:25:19 +02:00
|
|
|
* Returns the beatmap associated with the current track.
|
2014-07-02 09:02:11 +02:00
|
|
|
*/
|
2015-05-17 03:25:19 +02:00
|
|
|
public static Beatmap getBeatmap() { return lastBeatmap; }
|
2014-07-02 09:02:11 +02:00
|
|
|
|
2016-12-10 20:15:19 +01:00
|
|
|
/**
|
|
|
|
* Gets the progress of the current beat.
|
2016-12-20 03:02:01 +01:00
|
|
|
* @return a beat progress value [0,1) where 0 marks the current beat and
|
2016-12-23 10:03:32 +01:00
|
|
|
* 1 marks the next beat, or {@code null} if no timing information
|
2016-12-20 03:02:01 +01:00
|
|
|
* is available (e.g. music paused, no timing points)
|
2016-12-10 20:15:19 +01:00
|
|
|
*/
|
2016-12-18 23:31:17 +01:00
|
|
|
public static Float getBeatProgress() {
|
2016-12-23 10:03:32 +01:00
|
|
|
if (!updateTimingPoint())
|
2016-12-10 20:15:19 +01:00
|
|
|
return null;
|
2016-12-23 10:03:32 +01:00
|
|
|
|
|
|
|
// calculate beat progress
|
|
|
|
int trackPosition = Math.max(0, getPosition());
|
|
|
|
double beatLength = lastTimingPoint.getBeatLength() * 100.0;
|
|
|
|
int beatTime = lastTimingPoint.getTime();
|
2016-12-23 22:53:12 +01:00
|
|
|
if (trackPosition < beatTime)
|
|
|
|
trackPosition += (beatLength / 100.0) * (beatTime / lastTimingPoint.getBeatLength());
|
2016-12-23 10:03:32 +01:00
|
|
|
return (float) ((((trackPosition - beatTime) * 100.0) % beatLength) / beatLength);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the progress of the current measure.
|
|
|
|
* @return a measure progress value [0,1) where 0 marks the start of the measure and
|
|
|
|
* 1 marks the start of the next measure, or {@code null} if no timing information
|
|
|
|
* is available (e.g. music paused, no timing points)
|
|
|
|
*/
|
|
|
|
public static Float getMeasureProgress() { return getMeasureProgress(1); }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the progress of the current measure.
|
|
|
|
* @param k the meter multiplier
|
|
|
|
* @return a measure progress value [0,1) where 0 marks the start of the measure and
|
|
|
|
* 1 marks the start of the next measure, or {@code null} if no timing information
|
|
|
|
* is available (e.g. music paused, no timing points)
|
|
|
|
*/
|
|
|
|
public static Float getMeasureProgress(int k) {
|
|
|
|
if (!updateTimingPoint())
|
2016-12-10 20:15:19 +01:00
|
|
|
return null;
|
2016-12-23 10:03:32 +01:00
|
|
|
|
|
|
|
// calculate measure progress
|
|
|
|
int trackPosition = Math.max(0, getPosition());
|
|
|
|
double measureLength = lastTimingPoint.getBeatLength() * lastTimingPoint.getMeter() * k * 100.0;
|
|
|
|
int beatTime = lastTimingPoint.getTime();
|
2016-12-23 22:53:12 +01:00
|
|
|
if (trackPosition < beatTime)
|
|
|
|
trackPosition += (measureLength / 100.0) * (beatTime / lastTimingPoint.getBeatLength());
|
2016-12-23 10:03:32 +01:00
|
|
|
return (float) ((((trackPosition - beatTime) * 100.0) % measureLength) / measureLength);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the timing point information for the current track position.
|
|
|
|
* @return {@code false} if timing point information is not available, {@code true} otherwise
|
|
|
|
*/
|
|
|
|
private static boolean updateTimingPoint() {
|
2016-12-18 23:31:17 +01:00
|
|
|
Beatmap map = getBeatmap();
|
2016-12-20 04:39:49 +01:00
|
|
|
if (!isPlaying() || map == null || map.timingPoints == null || map.timingPoints.isEmpty())
|
2016-12-23 10:03:32 +01:00
|
|
|
return false;
|
2016-12-20 04:39:49 +01:00
|
|
|
|
|
|
|
// initialization
|
|
|
|
if (timingPointIndex == 0 && lastTimingPoint == null && !map.timingPoints.isEmpty()) {
|
|
|
|
TimingPoint timingPoint = map.timingPoints.get(0);
|
|
|
|
if (!timingPoint.isInherited())
|
|
|
|
lastTimingPoint = timingPoint;
|
2016-12-10 20:15:19 +01:00
|
|
|
}
|
2016-12-20 04:39:49 +01:00
|
|
|
|
|
|
|
// advance timing point index, record last non-inherited timing point
|
2016-12-20 03:02:01 +01:00
|
|
|
int trackPosition = getPosition();
|
2016-12-20 04:39:49 +01:00
|
|
|
for (int i = timingPointIndex + 1; i < map.timingPoints.size(); i++) {
|
|
|
|
TimingPoint timingPoint = map.timingPoints.get(i);
|
|
|
|
if (trackPosition < timingPoint.getTime())
|
2016-12-18 23:31:17 +01:00
|
|
|
break;
|
2016-12-20 04:39:49 +01:00
|
|
|
timingPointIndex = i;
|
|
|
|
if (!timingPoint.isInherited() && timingPoint.getBeatLength() > 0)
|
|
|
|
lastTimingPoint = timingPoint;
|
2016-12-10 20:15:19 +01:00
|
|
|
}
|
2016-12-20 04:39:49 +01:00
|
|
|
if (lastTimingPoint == null)
|
2016-12-23 10:03:32 +01:00
|
|
|
return false; // no timing info
|
2016-12-20 04:39:49 +01:00
|
|
|
|
2016-12-23 10:03:32 +01:00
|
|
|
return true;
|
2016-12-10 20:15:19 +01:00
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* Returns true if the current track is playing.
|
|
|
|
*/
|
|
|
|
public static boolean isPlaying() {
|
|
|
|
return (trackExists() && player.playing());
|
|
|
|
}
|
|
|
|
|
2015-01-15 06:56:30 +01:00
|
|
|
/**
|
|
|
|
* Returns true if the current track is paused.
|
|
|
|
*/
|
|
|
|
public static boolean isPaused() {
|
|
|
|
return (trackExists() && pauseTime > 0f);
|
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* Pauses the current track.
|
|
|
|
*/
|
|
|
|
public static void pause() {
|
2015-01-15 06:56:30 +01:00
|
|
|
if (isPlaying()) {
|
|
|
|
pauseTime = player.getPosition();
|
2014-06-30 04:17:04 +02:00
|
|
|
player.pause();
|
2015-01-15 06:56:30 +01:00
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resumes the current track.
|
|
|
|
*/
|
|
|
|
public static void resume() {
|
|
|
|
if (trackExists()) {
|
2015-01-15 06:56:30 +01:00
|
|
|
pauseTime = 0f;
|
2014-06-30 04:17:04 +02:00
|
|
|
player.resume();
|
|
|
|
player.setVolume(1.0f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stops the current track.
|
|
|
|
*/
|
|
|
|
public static void stop() {
|
|
|
|
if (isPlaying())
|
|
|
|
player.stop();
|
2016-12-20 04:39:49 +01:00
|
|
|
if (trackExists()) {
|
2015-01-21 01:01:18 +01:00
|
|
|
pauseTime = 0f;
|
2016-12-20 04:39:49 +01:00
|
|
|
resetTimingPoint();
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fades out the track.
|
2015-09-10 05:51:16 +02:00
|
|
|
* @param duration the fade time (in ms)
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
|
|
|
public static void fadeOut(int duration) {
|
|
|
|
if (isPlaying())
|
|
|
|
player.fade(duration, 0f, true);
|
|
|
|
}
|
|
|
|
|
2015-11-16 14:30:49 +01:00
|
|
|
/**
|
|
|
|
* Fades out the pitch (and speed) of the track.
|
|
|
|
* @param duration the pitch fade time (in ms)
|
|
|
|
*/
|
|
|
|
public static void pitchFadeOut(int duration) {
|
|
|
|
if (isPlaying())
|
|
|
|
player.pitchFade(duration, 0f);
|
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
2015-05-29 12:06:37 +02:00
|
|
|
* Returns the position in the current track, in milliseconds.
|
2015-01-15 06:56:30 +01:00
|
|
|
* If no track is loaded, 0 will be returned.
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
|
|
|
public static int getPosition() {
|
|
|
|
if (isPlaying())
|
2016-11-20 13:49:53 +01:00
|
|
|
return (int) (player.getPosition() * 1000 + Options.getMusicOffset() + Game.currentMapMusicOffset);
|
2015-01-15 06:56:30 +01:00
|
|
|
else if (isPaused())
|
2016-11-20 13:49:53 +01:00
|
|
|
return Math.max((int) (pauseTime * 1000 + Options.getMusicOffset() + Game.currentMapMusicOffset), 0);
|
2014-06-30 04:17:04 +02:00
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Seeks to a position in the current track.
|
2015-05-29 12:06:37 +02:00
|
|
|
* @param position the new track position (in ms)
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
|
|
|
public static boolean setPosition(int position) {
|
2016-12-20 04:39:49 +01:00
|
|
|
if (!trackExists() || position < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
resetTimingPoint();
|
|
|
|
return (player.setPosition(position / 1000f));
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
2015-02-14 07:07:17 +01:00
|
|
|
|
2015-03-16 18:16:30 +01:00
|
|
|
/**
|
|
|
|
* Returns the duration of the current track, in milliseconds.
|
|
|
|
* Currently only works for MP3s.
|
|
|
|
* @return the duration, or -1 if no track exists, else the {@code endTime}
|
2015-05-17 03:25:19 +02:00
|
|
|
* field of the beatmap loaded
|
2015-03-16 18:16:30 +01:00
|
|
|
* @author Tom Brito (http://stackoverflow.com/a/3056161)
|
|
|
|
*/
|
|
|
|
public static int getDuration() {
|
2015-05-17 03:25:19 +02:00
|
|
|
if (!trackExists() || lastBeatmap == null)
|
2015-03-16 18:16:30 +01:00
|
|
|
return -1;
|
|
|
|
|
2015-03-19 20:58:50 +01:00
|
|
|
if (duration == 0) {
|
2015-05-29 12:06:37 +02:00
|
|
|
// TAudioFileFormat method only works for MP3s
|
2015-05-17 03:25:19 +02:00
|
|
|
if (lastBeatmap.audioFilename.getName().endsWith(".mp3")) {
|
2015-03-19 09:47:02 +01:00
|
|
|
try {
|
2015-05-17 03:25:19 +02:00
|
|
|
AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(lastBeatmap.audioFilename);
|
2015-03-19 09:47:02 +01:00
|
|
|
if (fileFormat instanceof TAudioFileFormat) {
|
|
|
|
Map<?, ?> properties = ((TAudioFileFormat) fileFormat).properties();
|
|
|
|
Long microseconds = (Long) properties.get("duration");
|
|
|
|
duration = (int) (microseconds / 1000);
|
|
|
|
return duration;
|
|
|
|
}
|
|
|
|
} catch (UnsupportedAudioFileException | IOException e) {}
|
|
|
|
}
|
2015-05-29 12:06:37 +02:00
|
|
|
|
|
|
|
// fallback: use beatmap end time (often not the track duration)
|
2015-05-17 03:25:19 +02:00
|
|
|
duration = lastBeatmap.endTime;
|
2015-03-16 18:16:30 +01:00
|
|
|
}
|
2015-03-19 09:47:02 +01:00
|
|
|
return duration;
|
2015-03-16 18:16:30 +01:00
|
|
|
}
|
|
|
|
|
2015-02-09 05:48:55 +01:00
|
|
|
/**
|
|
|
|
* Plays the current track.
|
2015-02-16 07:24:22 +01:00
|
|
|
* @param loop whether or not to loop the track
|
2015-02-09 05:48:55 +01:00
|
|
|
*/
|
2015-04-02 18:38:45 +02:00
|
|
|
public static void play(boolean loop) {
|
2015-02-16 07:24:22 +01:00
|
|
|
if (trackExists()) {
|
2015-02-22 02:14:48 +01:00
|
|
|
trackEnded = false;
|
2016-12-20 04:39:49 +01:00
|
|
|
resetTimingPoint();
|
2015-02-16 07:24:22 +01:00
|
|
|
if (loop)
|
|
|
|
player.loop();
|
|
|
|
else
|
2015-04-02 18:38:45 +02:00
|
|
|
player.play();
|
2015-02-16 07:24:22 +01:00
|
|
|
}
|
2015-02-09 05:48:55 +01:00
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-01-11 02:14:39 +01:00
|
|
|
/**
|
|
|
|
* Sets the music volume.
|
2015-05-29 12:06:37 +02:00
|
|
|
* @param volume the new volume [0, 1]
|
2015-01-11 02:14:39 +01:00
|
|
|
*/
|
|
|
|
public static void setVolume(float volume) {
|
2015-03-27 01:45:33 +01:00
|
|
|
SoundStore.get().setMusicVolume((isTrackDimmed()) ? volume * dimLevel : volume);
|
2015-01-11 02:14:39 +01:00
|
|
|
}
|
|
|
|
|
2015-04-02 18:38:45 +02:00
|
|
|
/**
|
2015-04-04 00:08:35 +02:00
|
|
|
* Sets the music pitch (and speed).
|
2015-05-29 12:06:37 +02:00
|
|
|
* @param pitch the new pitch
|
2015-04-02 18:38:45 +02:00
|
|
|
*/
|
|
|
|
public static void setPitch(float pitch) {
|
2015-11-16 14:30:49 +01:00
|
|
|
player.setPitch(pitch);
|
2015-04-02 18:38:45 +02:00
|
|
|
}
|
|
|
|
|
2015-01-30 03:24:21 +01:00
|
|
|
/**
|
|
|
|
* Returns whether or not the current track has ended.
|
|
|
|
*/
|
|
|
|
public static boolean trackEnded() { return trackEnded; }
|
|
|
|
|
2015-03-08 01:04:45 +01:00
|
|
|
/**
|
|
|
|
* Loops the current track if it has ended.
|
|
|
|
* @param preview whether to start at the preview time (true) or beginning (false)
|
|
|
|
*/
|
|
|
|
public static void loopTrackIfEnded(boolean preview) {
|
|
|
|
if (trackEnded && trackExists())
|
2015-05-17 03:25:19 +02:00
|
|
|
playAt((preview) ? lastBeatmap.previewTime : 0, false);
|
2015-03-08 01:04:45 +01:00
|
|
|
}
|
|
|
|
|
2014-12-21 00:17:04 +01:00
|
|
|
/**
|
|
|
|
* Plays the theme song.
|
|
|
|
*/
|
|
|
|
public static void playThemeSong() {
|
2015-05-17 03:25:19 +02:00
|
|
|
Beatmap beatmap = Options.getThemeBeatmap();
|
|
|
|
if (beatmap != null) {
|
2016-11-13 02:45:26 +01:00
|
|
|
play(beatmap, false, false);
|
2014-12-21 00:17:04 +01:00
|
|
|
themePlaying = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-01-29 09:57:55 +01:00
|
|
|
* Returns whether or not the theme song is playing.
|
2014-12-21 00:17:04 +01:00
|
|
|
*/
|
2015-01-29 09:57:55 +01:00
|
|
|
public static boolean isThemePlaying() { return themePlaying; }
|
2014-12-21 00:17:04 +01:00
|
|
|
|
2015-01-16 22:02:02 +01:00
|
|
|
/**
|
|
|
|
* Returns whether or not the volume of the current track, if any,
|
|
|
|
* has been dimmed.
|
|
|
|
*/
|
|
|
|
public static boolean isTrackDimmed() { return trackDimmed; }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggles the volume dim state of the current track.
|
2015-01-29 02:57:43 +01:00
|
|
|
* @param multiplier the volume multiplier when the track is dimmed
|
2015-01-16 22:02:02 +01:00
|
|
|
*/
|
2015-01-29 02:57:43 +01:00
|
|
|
public static void toggleTrackDimmed(float multiplier) {
|
2015-01-20 20:52:02 +01:00
|
|
|
float volume = Options.getMusicVolume() * Options.getMasterVolume();
|
2015-03-27 01:45:33 +01:00
|
|
|
dimLevel = (isTrackDimmed()) ? 1f : multiplier;
|
2015-01-16 22:02:02 +01:00
|
|
|
trackDimmed = !trackDimmed;
|
2015-03-27 01:45:33 +01:00
|
|
|
setVolume(volume);
|
2015-01-16 22:02:02 +01:00
|
|
|
}
|
|
|
|
|
2016-12-20 04:39:49 +01:00
|
|
|
/**
|
|
|
|
* Resets timing point information.
|
|
|
|
*/
|
|
|
|
private static void resetTimingPoint() {
|
|
|
|
timingPointIndex = 0;
|
|
|
|
lastTimingPoint = null;
|
|
|
|
}
|
|
|
|
|
2015-01-18 20:29:48 +01:00
|
|
|
/**
|
|
|
|
* Completely resets MusicController state.
|
|
|
|
* <p>
|
|
|
|
* Stops the current track, cancels track conversions, erases
|
|
|
|
* temporary files, releases OpenAL sources, and resets state.
|
|
|
|
*/
|
|
|
|
public static void reset() {
|
|
|
|
stop();
|
|
|
|
|
2015-02-12 09:52:19 +01:00
|
|
|
// interrupt the track loading
|
|
|
|
// TODO: Not sure if the interrupt does anything, and the join kind of
|
|
|
|
// defeats the purpose of threading it, but it is needed since bad things
|
|
|
|
// likely happen when OpenALStreamPlayer source is released asynchronously.
|
2015-02-12 04:41:20 +01:00
|
|
|
if (isTrackLoading()){
|
|
|
|
trackLoader.interrupt();
|
2015-02-10 03:33:37 +01:00
|
|
|
try {
|
|
|
|
trackLoader.join();
|
|
|
|
} catch (InterruptedException e) {
|
2016-12-23 00:36:31 +01:00
|
|
|
ErrorHandler.error(null, e, true);
|
2015-02-10 03:33:37 +01:00
|
|
|
}
|
2015-02-12 04:41:20 +01:00
|
|
|
}
|
2015-01-18 20:29:48 +01:00
|
|
|
trackLoader = null;
|
|
|
|
|
|
|
|
// reset state
|
2015-05-17 03:25:19 +02:00
|
|
|
lastBeatmap = null;
|
2015-03-19 20:58:50 +01:00
|
|
|
duration = 0;
|
2015-01-30 03:24:21 +01:00
|
|
|
trackEnded = false;
|
2015-01-18 20:29:48 +01:00
|
|
|
themePlaying = false;
|
|
|
|
pauseTime = 0f;
|
|
|
|
trackDimmed = false;
|
2016-12-20 04:39:49 +01:00
|
|
|
resetTimingPoint();
|
2015-01-18 20:29:48 +01:00
|
|
|
|
|
|
|
// releases all sources from previous tracks
|
|
|
|
destroyOpenAL();
|
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* Stops and releases all sources, clears each of the specified Audio
|
|
|
|
* buffers, destroys the OpenAL context, and resets SoundStore for future use.
|
2015-01-16 21:44:13 +01:00
|
|
|
*
|
2014-06-30 04:17:04 +02:00
|
|
|
* Calling SoundStore.get().init() will re-initialize the OpenAL context
|
|
|
|
* after a call to destroyOpenAL (Note: AudioLoader.getXXX calls init for you).
|
2015-01-16 21:44:13 +01:00
|
|
|
*
|
2014-06-30 04:17:04 +02:00
|
|
|
* @author davedes (http://slick.ninjacave.com/forum/viewtopic.php?t=3920)
|
|
|
|
*/
|
|
|
|
private static void destroyOpenAL() {
|
|
|
|
if (!trackExists())
|
|
|
|
return;
|
|
|
|
stop();
|
|
|
|
|
|
|
|
try {
|
|
|
|
// get Music object's (private) Audio object reference
|
|
|
|
Field sound = player.getClass().getDeclaredField("sound");
|
|
|
|
sound.setAccessible(true);
|
|
|
|
Audio audio = (Audio) (sound.get(player));
|
|
|
|
|
|
|
|
// first clear the sources allocated by SoundStore
|
|
|
|
int max = SoundStore.get().getSourceCount();
|
|
|
|
IntBuffer buf = BufferUtils.createIntBuffer(max);
|
|
|
|
for (int i = 0; i < max; i++) {
|
|
|
|
int source = SoundStore.get().getSource(i);
|
|
|
|
buf.put(source);
|
|
|
|
|
|
|
|
// stop and detach any buffers at this source
|
|
|
|
AL10.alSourceStop(source);
|
|
|
|
AL10.alSourcei(source, AL10.AL_BUFFER, 0);
|
|
|
|
}
|
|
|
|
buf.flip();
|
|
|
|
AL10.alDeleteSources(buf);
|
|
|
|
int exc = AL10.alGetError();
|
|
|
|
if (exc != AL10.AL_NO_ERROR) {
|
2015-02-12 04:41:20 +01:00
|
|
|
throw new SlickException(
|
|
|
|
"Could not clear SoundStore sources, err: " + exc);
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// delete any buffer data stored in memory, too...
|
|
|
|
if (audio != null && audio.getBufferID() != 0) {
|
|
|
|
buf = BufferUtils.createIntBuffer(1).put(audio.getBufferID());
|
|
|
|
buf.flip();
|
|
|
|
AL10.alDeleteBuffers(buf);
|
|
|
|
exc = AL10.alGetError();
|
|
|
|
if (exc != AL10.AL_NO_ERROR) {
|
|
|
|
throw new SlickException("Could not clear buffer "
|
|
|
|
+ audio.getBufferID()
|
|
|
|
+ ", err: "+exc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear OpenAL
|
|
|
|
AL.destroy();
|
|
|
|
|
|
|
|
// reset SoundStore so that next time we create a Sound/Music, it will reinit
|
|
|
|
SoundStore.get().clear();
|
|
|
|
|
|
|
|
player = null;
|
|
|
|
} catch (Exception e) {
|
2015-01-21 01:01:18 +01:00
|
|
|
ErrorHandler.error("Failed to destroy OpenAL.", e, true);
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
}
|
2017-01-17 23:18:12 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the default volume for music
|
|
|
|
* @param volume the new default value for music volume
|
|
|
|
*/
|
|
|
|
public static void setMusicVolume(float volume) {
|
|
|
|
SoundStore.get().setMusicVolume(volume);
|
|
|
|
}
|
|
|
|
|
2015-11-16 14:30:49 +01:00
|
|
|
}
|