/* * Copyright 2008-2013, ETH Zürich, Samuel Welten, Michael Kuhn, Tobias Langner, * Sandro Affentranger, Lukas Bossard, Michael Grob, Rahul Jain, * Dominic Langenegger, Sonia Mayor Alonso, Roger Odermatt, Tobias Schlueter, * Yannick Stucki, Sebastian Wendland, Samuel Zehnder, Samuel Zihlmann, * Samuel Zweifel * * This file is part of Jukefox. * * Jukefox 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 any later version. Jukefox 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 * Jukefox. If not, see <http://www.gnu.org/licenses/>. */ package ch.ethz.dcg.jukefox.playmode; import java.io.Serializable; import java.util.ArrayList; import java.util.LinkedHashSet; import ch.ethz.dcg.jukefox.commons.DataUnavailableException; import ch.ethz.dcg.jukefox.commons.utils.Log; import ch.ethz.dcg.jukefox.model.AbstractCollectionModelManager; import ch.ethz.dcg.jukefox.model.AbstractPlayerModelManager; /** * Handles all the smart shuffling logic. */ public class ContextShuffleManager extends SmartShuffleManager { protected final static String TAG = ContextShuffleManager.class.getSimpleName(); private boolean lockRegion = true; // private HashMap<Integer, ProcessedSong> recommendedBy; public ContextShuffleManager(AbstractCollectionModelManager collectionModel, AbstractPlayerModelManager playerModel) { super(collectionModel, playerModel); // recommendedBy = new HashMap<Integer, ProcessedSong>(); } public void loadPermanentState(PermanentSmartShuffleState state) { this.played = state.getPlayed(); this.processed = state.getProcessed(); this.weightThreshold = state.getWeightThreshold(); this.resetCnt = state.getResetCnt(); this.minPlayableSize = state.getMinPlayableSize(); this.sampleSize = state.getSampleSize(); this.minSampleSize = state.getMinSampleSize(); this.maxPlayedHistorySize = state.getMaxPlayedHistorySize(); this.numCandiates = state.getNumCandiates(); } public PermanentSmartShuffleState getPermanentSmartShuffleState() { return new PermanentSmartShuffleState(played, processed, weightThreshold, resetCnt, minPlayableSize, sampleSize, minSampleSize, maxPlayedHistorySize, numCandiates); } @Override public void processSong(int songId, float rating) { if (lockRegion) { Log.v(TAG, "Not processing song with id " + songId + ", as the region is locked."); return; } Log.v(TAG, "addSongToProcessed(): id: " + songId + ", rating: " + rating); float[] pcaCoords = null; try { pcaCoords = collectionModel.getOtherDataProvider().getSongPcaCoords(songId); } catch (DataUnavailableException e) { Log.w(TAG, e); } if (pcaCoords == null) { Log.v(TAG, "no pcaCoords available for song => don't add it to processed."); return; } ProcessedSong ps = new ProcessedSong(songId, Math.abs(rating), rating <= 0, pcaCoords); processed.add(ps); adjustWeights(); } @Override protected void adjustWeights() { // quickly remove existing centroids if the user stops liking some area // consecutiveSkipCnt = skipped ? consecutiveSkipCnt + 1 : 0; int n = processed.size(); for (int i = n - 1; i >= 0; i--) { ProcessedSong ps = processed.get(i); // ps.weight -= 0.1 * (consecutiveSkipCnt + 1); // don't specially treat consecutive skips at the moment ps.weight -= 0.1; if (ps.weight <= weightThreshold) { processed.remove(i); } } } public void lockRegion() { lockRegion = true; } public void unlockRegion() { lockRegion = false; } public boolean isRegionLocked() { return lockRegion; } public static class PermanentSmartShuffleState implements Serializable { private static final long serialVersionUID = 1L; public PermanentSmartShuffleState(LinkedHashSet<Integer> played, ArrayList<ProcessedSong> processed, double weightThreshold, int resetCnt, int minPlayableSize, int sampleSize, int minSampleSize, int maxPlayedHistorySize, int numCandiates) { super(); this.played = played; this.processed = processed; this.weightThreshold = weightThreshold; this.resetCnt = resetCnt; this.minPlayableSize = minPlayableSize; this.sampleSize = sampleSize; this.minSampleSize = minSampleSize; this.maxPlayedHistorySize = maxPlayedHistorySize; this.numCandiates = numCandiates; } public static long getSerialversionuid() { return serialVersionUID; } public LinkedHashSet<Integer> getPlayed() { return played; } public ArrayList<ProcessedSong> getProcessed() { return processed; } public double getWeightThreshold() { return weightThreshold; } public int getResetCnt() { return resetCnt; } public int getMinPlayableSize() { return minPlayableSize; } public int getSampleSize() { return sampleSize; } public int getMinSampleSize() { return minSampleSize; } public int getMaxPlayedHistorySize() { return maxPlayedHistorySize; } public int getNumCandiates() { return numCandiates; } protected LinkedHashSet<Integer> played = new LinkedHashSet<Integer>(); protected ArrayList<ProcessedSong> processed = new ArrayList<ProcessedSong>(); protected double weightThreshold = 0.01; protected int resetCnt = -1; // account for first call to reset... protected int minPlayableSize = 80; protected int sampleSize; protected int minSampleSize = 200; protected int maxPlayedHistorySize = 50; protected int numCandiates = 5; } }