/**
*
*/
package com.soundlooper.model;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.soundlooper.audio.player.Player;
import com.soundlooper.audio.player.ThreadImageGenerator;
import com.soundlooper.exception.PlayerException;
import com.soundlooper.exception.SoundLooperException;
import com.soundlooper.exception.SoundLooperObjectAlreadyExistsException;
import com.soundlooper.exception.SoundLooperRecordNotFoundException;
import com.soundlooper.exception.SoundLooperRuntimeException;
import com.soundlooper.model.mark.Mark;
import com.soundlooper.model.song.Song;
import com.soundlooper.model.tag.Tag;
import com.soundlooper.model.tag.TagDAO;
import com.soundlooper.service.entite.mark.MarkService;
import com.soundlooper.service.entite.song.SongService;
import com.soundlooper.service.entite.tag.TagService;
import com.soundlooper.system.preferences.Preferences;
/**
* ------------------------------------------------------- Sound Looper is an
* audio player that allow user to loop between two points Copyright (C) 2014
* Alexandre NEDJARI
*
* This program 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.
*
* This program 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
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* The sound Looper general model
*
* @author Alexandre NEDJARI
* @since 17 f�vr. 2014 -------------------------------------------------------
*/
public class SoundLooperPlayer extends Player {
private Logger logger = LogManager.getLogger(this.getClass());
private static SoundLooperPlayer instance = null;
/**
* The current song
*/
private SimpleObjectProperty<Song> song = new SimpleObjectProperty<>();
/**
* The current mark This mark is NEVER an element of the mark list of the
* current song, but a copy to avoid problems when the user update it but
* without savin it
*/
private SimpleObjectProperty<Mark> mark = new SimpleObjectProperty<Mark>();
private SimpleBooleanProperty isCurrentSongFavorite = new SimpleBooleanProperty(false);
private SimpleBooleanProperty isCurrentMarkEditable = new SimpleBooleanProperty(false);
private SimpleBooleanProperty isCurrentMarkDirty = new SimpleBooleanProperty(false);
private SimpleObjectProperty<File> currentSongImage = new SimpleObjectProperty<>();
public SoundLooperPlayer() {
}
/**
* @return the song
*/
public Song getSong() {
return this.song.get();
}
/**
* Access the song property
*
* @return the song property
*/
public SimpleObjectProperty<Song> songProperty() {
return this.song;
}
/**
* Access the mark property
*
* @return the mark property
*/
public SimpleObjectProperty<Mark> markProperty() {
return this.mark;
}
/**
* Get the current Mark
*
* @return the current mark or null if there is no current mark
*/
public Mark getCurrentMark() {
return mark.get();
}
public static SoundLooperPlayer getInstance() {
if (SoundLooperPlayer.instance == null) {
SoundLooperPlayer.instance = new SoundLooperPlayer();
}
return SoundLooperPlayer.instance;
}
/**
* Switch the favorite attribute on the current loaded song
*
* @throws SoundLooperException
* if an exception is threw
*/
public void switchFavoriteOnCurrentSong() throws SoundLooperException {
if (getSong() == null) {
return;
}
setSongFavorite(getSong(), !getSong().isFavorite());
}
private void setSongFavorite(Song song, boolean isFavorite) throws SoundLooperException {
logger.info("Set favorite = " + isFavorite + " on song '" + song.getFile().getAbsolutePath() + "'");
song.setFavorite(isFavorite);
SongService.getInstance().validateSong(song);
}
/**
* @param idMark
* the mark id
* @throws SoundLooperException
* if an error es detected
*/
public void deleteMarkOnCurrentSong(String idMark) throws SoundLooperException {
logger.info("Delete mark with id '" + idMark + "' on song '" + getSong().getFile().getAbsolutePath() + "'");
Mark mark = this.getMarkFromId(idMark);
MarkService.getInstance().delete(mark);
}
/**
* @param nom
* the mark's name
* @throws SoundLooperException
* if error is detected
* @throws PlayerException
*/
public void createNewMarkAtCurrentPosition(String nom) throws SoundLooperException, PlayerException {
logger.info("Create current mark named '" + nom + "' at current position");
Mark currentMark = getCurrentMark();
if (currentMark != null) {
Mark mark = MarkService.getInstance().createMark(getSong(), nom, getLoopPointBeginMillisecond(),
getLoopPointEndMillisecond(), true);
this.selectMark(mark);
}
}
public int getLoopPointBeginMillisecond() {
Mark currentMark = getCurrentMark();
if (currentMark != null) {
return currentMark.getBeginMillisecond();
}
return 0;
}
public int getLoopPointEndMillisecond() {
Mark currentMark = getCurrentMark();
if (currentMark != null) {
return currentMark.getEndMillisecond();
}
return 0;
}
/**
*
* @param idMark
* the mark's id
* @return the finded mark
* @throws SoundLooperException
* if the mark is not found
*/
private Mark getMarkFromId(String idMark) throws SoundLooperException {
Map<String, Mark> mapMarks = this.getSong().getMarks();
Set<String> marksNames = mapMarks.keySet();
for (String name : marksNames) {
Mark mark = mapMarks.get(name);
if (mark.getId() == Integer.valueOf(idMark).intValue()) {
logger.info("The mark with id '" + idMark + "' is named '" + mark.getName() + "'");
return mark;
}
}
throw new SoundLooperException("Le marqueur avec l'id '" + idMark
+ "' n'existe pas pour la chanson actuellement charg�e");
}
public SimpleObjectProperty<File> currentSongImageProperty() {
return this.currentSongImage;
}
/**
* @return the favorite list
*/
public List<Song> getFavoriteSongList() {
return SongService.getInstance().getFavoriteSongList();
}
/**
*
*/
public void purgeSong() {
SongService.getInstance().purgeSong();
}
/**
* @param mark
* the mark to select id
* @throws PlayerException
*/
public void selectMark(Mark mark) throws PlayerException {
if (mark != null) {
logger.info("Set current mark : " + mark.getName());
Mark clone = mark.clone();
applyLoopPoints(clone.getBeginMillisecond(), clone.getEndMillisecond());
// the mark is first set to null, so, if the mark was the same than
// the old one, the binded properties will be notified.
// Usefull if a mark is modified but not saved. If the user select
// the same mark, the binded properties will get back the persisted
// state of the mark
SoundLooperPlayer.this.mark.set(null);
SoundLooperPlayer.this.mark.set(clone);
isCurrentMarkDirty.bind(getCurrentMark().dirtyProperty());
isCurrentMarkEditable.bind(getCurrentMark().editableProperty());
} else {
logger.info("Set current mark to null");
this.mark.set(null);
isCurrentMarkDirty.set(false);
isCurrentMarkEditable.set(false);
}
}
/**
* Get the default mark for this song
*
* @param song
* the default mark for this song
* @return
*/
public Mark getDefaultMark(Song song) {
logger.info("Get the default mark for song '" + song.getFile().getAbsolutePath() + "'");
Collection<Mark> marks = song.getMarks().values();
for (Mark mark : marks) {
if (!mark.isEditable()) {
logger.info("Find default mark " + mark.getName());
return mark;
}
}
logger.info("Unable to find default mark");
return null;
}
public void saveCurrentMark() throws SoundLooperException {
logger.info("Save the current mark");
if (getCurrentMark() == null) {
return;
}
// Mark clone = mark.get().clone();
MarkService.getInstance().validateMark(getCurrentMark());
getSong().getMarks().put(getCurrentMark().getName(), getCurrentMark());
}
public void setSong(Song song) {
logger.info("Set the song : " + song);
this.song.set(song);
if (song != null) {
isCurrentSongFavorite.bind(song.isFavoriteProperty());
} else {
isCurrentSongFavorite.set(false);
}
}
public SimpleBooleanProperty isCurrentSongFavoriteProperty() {
return isCurrentSongFavorite;
}
public SimpleBooleanProperty isCurrentMarkEditableProperty() {
return isCurrentMarkEditable;
}
public SimpleBooleanProperty isCurrentMarkDirtyProperty() {
return isCurrentMarkDirty;
}
/**
* Get an unique valid name for mark for this song
*
* @param song
* the song
* @param nom
* the wanted name
* @return a valid unique name
*/
public String getValidNameForMark(Song song, String name) {
String nomValide = MarkService.getInstance().getNomValide(song, name);
return nomValide;
}
// TODO passer en priv�
public void onSongLoaded(File songFile) throws PlayerException {
Preferences.getInstance().setLastPathUsed(songFile.getAbsolutePath());
Preferences.getInstance().addFileToRecentFileList(songFile.getAbsolutePath());
// recherche de la chanson dans la liste des favoris
ObservableList<Song> favoriteList = Song.getFavoriteList();
Song favoriteSong = null;
for (Song song : favoriteList) {
if (song.getFile().equals(songFile)) {
favoriteSong = song;
}
}
if (favoriteSong != null) {
setSong(favoriteSong);
} else {
try {
// Recherche l'objet en base de donn�es
setSong(SongService.getInstance().getSong(songFile));
} catch (SoundLooperRecordNotFoundException e) {
// La chanson n'est pas encore enregistr�e, on cr�e un nouvel
// objet
setSong(SongService.getInstance().createSong(songFile));
}
}
// cr�ation du marqueur par d�faut
try {
String defaultMarkName = "Piste compl�te";
if (getSong().getMarks().get(defaultMarkName) == null) {
// If the user load a favorite song 1, default mark will be
// created on it. The user then get on a song2, and load again
// Song 1. The song 1 it getted from favorite list, where it
// already has the default mark
Mark mark = new Mark(getValidNameForMark(getSong(), defaultMarkName), 0, getCurrentSound()
.getDuration(), getSong(), false);
mark.setDirty(false);
this.selectMark(mark);
}
} catch (SoundLooperObjectAlreadyExistsException e) {
// Impossible case
throw new SoundLooperRuntimeException("Unable to create default mark", e);
}
}
public void selectDefaultMark() throws PlayerException {
if (getSong() != null) {
selectMark(getDefaultMark(getSong()));
}
}
public Tag createTag(String name, Tag parent) throws SoundLooperException {
return TagService.getInstance().createTag(name, parent);
}
public void saveTag(Tag newTag) {
TagDAO.getInstance().persist(newTag);
}
/**
* Delete tag and childs recursively
*
* @param tag
* the tag
* @throws SoundLooperException
*/
public void deleteTag(Tag tag) throws SoundLooperException {
TagService.getInstance().deleteTag(tag);
}
public void moveTag(Tag movedTag, Tag destinationTag) {
TagService.getInstance().moveTag(movedTag, destinationTag);
}
public void addTagToSong(Song song, Tag newTag) {
TagService.getInstance().addTagToSong(song, newTag);
}
public void removeTagFromSong(Song song, Tag tagToRemove) {
TagService.getInstance().removeTagFromSong(song, tagToRemove);
}
@Override
public void desallocate() throws PlayerException {
if (isSystemInitialized()) {
if (isSoundInitialized()) {
stop();
}
super.desallocate();
}
}
public void setLoopPointEnd(int position) throws PlayerException {
Mark currentMark = this.getCurrentMark();
if (isSoundInitialized() && currentMark != null) {
if (position > getCurrentSound().getDuration()) {
position = getCurrentSound().getDuration();
}
if (position < getLoopPointBeginMillisecond() + MINIMAL_MS_LOOP) {
position = getLoopPointBeginMillisecond() + MINIMAL_MS_LOOP;
}
setLoopPoints(getLoopPointBeginMillisecond(), position);
}
}
public void setLoopPointBegin(int position) throws PlayerException {
Mark currentMark = this.getCurrentMark();
if (isSoundInitialized() && currentMark != null) {
if (position < 0) {
position = 0;
}
int loopPointEndMillisecond = getLoopPointEndMillisecond();
if (loopPointEndMillisecond > getCurrentSound().getDuration()) {
// Possible in we are in file loading
loopPointEndMillisecond = getCurrentSound().getDuration();
}
if (position > loopPointEndMillisecond - MINIMAL_MS_LOOP) {
position = loopPointEndMillisecond - MINIMAL_MS_LOOP;
}
setLoopPoints(position, loopPointEndMillisecond);
}
}
public void setLoopPoints(int beginPosition, int endPosition) throws PlayerException {
Mark currentMark = this.getCurrentMark();
if (isSoundInitialized() && currentMark != null) {
if (beginPosition < 0) {
beginPosition = 0;
}
if (endPosition > getCurrentSound().getDuration()) {
beginPosition = getCurrentSound().getDuration();
}
// Unable to know if the begin or the end was moved, arbitrary try
// first to move the begin one
if (beginPosition > endPosition) {
if (endPosition > MINIMAL_MS_LOOP) {
beginPosition = endPosition - MINIMAL_MS_LOOP;
} else {
endPosition = beginPosition + MINIMAL_MS_LOOP;
}
}
applyLoopPoints(beginPosition, endPosition);
currentMark.setLoopPoints(beginPosition, endPosition);
}
}
@Override
public void loadSong(File file) throws PlayerException {
super.loadSong(file);
SoundLooperPlayer.getInstance().onSongLoaded(file);
new ThreadImageGenerator(SoundLooperPlayer.getInstance().getCurrentSound(), new Consumer<File>() {
@Override
public void accept(File file) {
logger.info("Image " + file.getAbsolutePath() + " generated");
currentSongImage.set(file);
}
}).start();
}
}