/**
* Copyright (C) 2013 Colorado School of Mines
*
* This file is part of the Interface Software Development Kit (SDK).
*
* The InterfaceSDK 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.
*
* The InterfaceSDK 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 the InterfaceSDK. If not, see <http://www.gnu.org/licenses/>.
*/
package edu.mines.acmX.exhibit.stdlib.sound;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* The Song class represents a collection of Tracks that are played
* synchronously to provide a final mix that is the song that is played.
*
* In order to create a song, you have two options:
* 1.) You can define a List of Tracks and initialize the Song with the List
* 2.) You can create a Song with the default constructor and add Tracks later
*
* Once a Song is created, the 'startPlaying' method needs to be called to
* start the Song, and the 'stopPlaying' method is called to stop it.
*
* @author John
*
*/
public class Song {
//Used for making sure the Tracks start at the same time
private final CountDownLatch startLatch = new CountDownLatch(1);
private List<Track> tracks;
private boolean isPlaying = false;
private boolean isLooping = false;
/**
* The default constructor for a Song. It simply initializes the list
* of Tracks to an empty list.
*/
public Song() {
tracks = new ArrayList<Track>();
}
/**
* Creates a Song and populates the list of Tracks with the given
* list of Tracks.
* @param tracks
*/
public Song(List<Track> tracks) {
for (Track track : tracks) {
addTrack(track);
}
}
/**
* Plays the Song with the current looping setting.
*/
public void startPlaying() {
startPlaying(isLooping);
}
/**
* Plays the Song, allowing the user to pass in the looping
* setting.
* @param loop
*/
public void startPlaying(boolean loop) {
isLooping = loop;
isPlaying = true;
new PlayThread().start();
}
/**
* Stops the Song.
*/
public void stopPlaying() {
isPlaying = false;
isLooping = false;
for(Track track : tracks) {
track.stopPlaying();
}
}
/**
* Adds a Track to the Song.
* @param track
*/
public void addTrack(Track track) {
insertTrack(tracks.size(), track);
}
/**
* Allows the user to add a Track to the Song at a specific
* index.
* @param index
* @param track
*/
public void insertTrack(int index, Track track) {
track.setStartLatch(startLatch);
tracks.add(index, track);
}
/*
* This method starts all of the Tracks assigned to this
* song at the same time.
*/
private void playTracks() {
for(Track track : tracks) {
track.startPlaying();
}
//Sleep for a short amount of time to give all of the tracks a chance to start playing
try {
Thread.sleep(200);
} catch (InterruptedException e) {
System.out.println("Problem sleeping before starting track playback.");
e.printStackTrace();
}
//Count down the start latch to have all tracks begin playing at the same time
startLatch.countDown();
}
/*
* Determines if all tracks are finished playing
*/
private boolean allTracksFinished() {
boolean allTracksFinished = true;
for (Track track : tracks) {
if (track.isPlaying()) {
allTracksFinished = false;
break;
}
}
return allTracksFinished;
}
/**
* Sets the looping setting.
* @param isLooping
*/
public void setLooping(boolean isLooping) {
this.isLooping = isLooping;
}
/**
* Gets the list of Tracks
* @return
*/
public List<Track> getTracks() {
return tracks;
}
/*
* This class is the thread that is used for playing the Tracks.
* It is needed so that main flow of the application is not
* interrupted while the Song plays.
*/
private class PlayThread extends Thread {
public void run() {
playTracks();
while (isPlaying && isLooping) {
if (allTracksFinished()) {
playTracks();
}
}
}
}
}