Sounds very similar to what I was doing.
For the curious, here is the relevant java code
snippet that does my similarity calculation.
Joe H.
public void selectNextSong()
{
try
{
_totalScore = 0;
Song nextSong = null;
// _considering is the list of songs
// that are currently being score.
Enumeration e0 = _considering.elements();
double maxdist = 0.0;
double mindist = 1.0;
double maxtime = 0.0;
double mintime = 0.0;
double now = (double)(new
java.util.Date()).getTime();
double timediff = 0.0;
double maxscore = 0.0;
Enumeration e1 = _considering.elements();
while(e1.hasMoreElements())
{
nextSong = (Song) e1.nextElement();
double dist = computeDistance(nextSong,
_baseSong);
nextSong.setDoubleValue("DistRaw",dist);
// keep track of the most and least
// similar song "distances" for proper
// scaling later.
if(dist > maxdist)
{
maxdist = dist;
}
if(dist < mindist)
{
mindist = dist;
}
Date last =
nextSong.getDateValue("LastPlayed");
if(last != null)
{
timediff = (double) now -
(last.getTime());
// keep track of the most recently
// played time and the least recently
// played time. This gives us a good
range
// over which to scale the rest of
// the songs
if(timediff > maxtime)
{
maxtime = timediff;
}
if(timediff < mintime)
{
mintime = timediff;
}
else if(mintime == 0.0)
{
mintime = timediff;
}
}
}
Enumeration e2 = _considering.elements();
// this is our denominator... we divide the
// end score of all songs by this.
// the _factors are user settable
// Some users might like preference to be most
// important, others time, others similarity.
// So we give them these scalars so they can
// adjust to their own taste.
double denom = Math.sqrt(_pref_factor *
_pref_factor + _time_factor * _time_factor +
_dist_factor * _dist_factor);
while(e2.hasMoreElements())
{
nextSong = (Song) e2.nextElement();
double score = 0.0;
String preference =
nextSong.getStringValue("Preference");
Date lastplayed =
nextSong.getDateValue("LastPlayed");
double pref_score = 0.5;
if(preference != null)
{
if(preference.equals("Excellent"))
pref_score = _pref_weight_Excellent;
if(preference.equals("Very Good"))
pref_score = _pref_weight_VeryGood;
if(preference.equals("Good"))
pref_score = _pref_weight_Good;
if(preference.equals("Fair"))
pref_score = _pref_weight_Fair;
if(preference.equals("Poor"))
pref_score = _pref_weight_Poor;
}
double time_score = 0.5;
if(lastplayed != null)
{
// Scale the time_score to be in a range
from 1 to 10;
time_score = (now - lastplayed.getTime() -
mintime) * 10.0 / (maxtime - mintime);
// Then put that on a asymptotic scale
from 0.0 to 1.0 but never quite reaching 1.0;
time_score = 1.0 - (1.0 / (1.0 +
time_score));
}
double dist_score = 1.0 -
((nextSong.getDoubleValue("DistRaw") - mindist) /
(maxdist - mindist));
nextSong.setDoubleValue("DistScore",dist_score);
nextSong.setDoubleValue("PrefScore",pref_score);
nextSong.setDoubleValue("TimeScore",time_score);
pref_score = (1.0 - pref_score) *
_pref_factor / denom;
time_score = (1.0 - time_score) *
_time_factor / denom;
dist_score = (1.0 - dist_score) *
_dist_factor / denom;
// final score = the square root of
// pref squared plus
// time squared plus
// dist squared
// Effectively a vector summation where each
// of pref, time, and dist are vectors in
// x, y and z axis
score = 1.0 - Math.sqrt(pref_score *
pref_score + time_score * time_score + dist_score *
dist_score);
if(score > maxscore)
{
maxscore = score;
}
double lastScore =
nextSong.getDoubleValue("Score");
if(lastScore > 0)
{
nextSong.setDoubleValue("Change",score -
lastScore);
}
nextSong.setDoubleValue("Score",score);
}
Enumeration e3 = _considering.elements();
while(e3.hasMoreElements())
{
nextSong = (Song) e3.nextElement();
double score =
nextSong.getDoubleValue("Score");
if(maxscore > 0)
{
score = score / maxscore;
score = 1.0 - score;
score =
java.lang.Math.exp(_randomness_factor * score *
score);
nextSong.setDoubleValue("Score",score);
}
_totalScore += score;
}
QuickSort.sort(_considering);
for(int i = 0; i < _considering.size(); i++)
{
Song temp = (Song)_considering.get(i);
long pos = temp.getLongValue("Position");
temp.setLongValue("Old Position",pos);
temp.setLongValue("Change",pos - (i + 1));
temp.setLongValue("Position",i + 1);
}
// now pick a random number from 0 to
_totalScore
double skip =
_rand.nextDouble()*_totalScore*(1.0 -
_randomness_cutoff);
int i = 0;
while(skip > 0 && i < _considering.size())
{
nextSong = (Song) _considering.get(i);
skip = skip -
nextSong.getDoubleValue("Score");
i++;
}
_pending = nextSong;
}
catch(Exception e)
{
LogUtil.log("Trouble getting song from database
"+e,2);
e.printStackTrace();
}
}
private double computeScaledDistance(String
values[], String t1, String t2)
{
if( t1 == null || t2 == null)
return 0.0;
if(t1.equals(t2))
return 0.0;
double d1 = (values.length / 2);
double d2 = (values.length / 2);
for(int i = 0; i < values.length; i++)
{
if(values[i].equals(t1))
d1 = (double)i;
if(values[i].equals(t2))
d2 = (double)i;
}
double dist = d1 - d2;
if(dist < 0.0)
dist = -dist;
if(values.length < 2)
return dist;
return (dist / (values.length - 1.0));
}
/*
computeHalfLife
if value = halflife then the return will be 0.5
if value = halflife *2 then the return will be 0.5
+ 0.25
if value = halflife *3 then the return will be 0.5
+ 0.25 + 0.125
*/
public double computeHalfLife(double value, double
halflife )
{
double retval = 0.0;
double current = value;
double range = 0.5;
while(current > halflife)
{
retval += range;
range = range / 2;
current = current - halflife;
}
if(current > 0)
{
retval += (range * (current / halflife));
}
return retval;
}
public double computeDistance(Song song1, Song
song2)
{
if(song1 == null || song2 == null)
return 0.0;
double retval = 0.0;
double denom = Math.sqrt(_artistWeight *
_artistWeight +
_albumWeight * _albumWeight +
_genreWeight * _genreWeight +
_moodWeight * _moodWeight +
_tempoWeight * _tempoWeight +
_situWeight * _situWeight +
_yearWeight * _yearWeight);
double score = 0;
if( stringCompare(song1.getStringValue("Artist"),
song2.getStringValue("Artist")) )
{
score = _artistWeight / denom;
retval += score * score;
}
if( stringCompare(song1.getStringValue("Album"),
song2.getStringValue("Album")) )
{
score = _albumWeight / denom;
retval += score * score;
}
score =
(lookupGenreValue(song1.getStringValue("Genre"),
song2.getStringValue("Genre")) * _genreWeight) /
denom;
retval += score * score;
if( stringCompare(song1.getStringValue("Mood"),
song2.getStringValue("Mood")) )
{
score = _moodWeight / denom;
retval += score * score;
}
if(
stringCompare(song1.getStringValue("Situation"),
song2.getStringValue("Situation")) )
{
score = _situWeight / denom;
retval += score * score;
}
double year_score = 1.0;
if((song1.getLongValue("Year") > 0) &&
(song2.getLongValue("Year") > 0))
{
long year_diff = song1.getLongValue("Year") -
song2.getLongValue("Year");
if(year_diff < 0)
{
year_diff = year_diff * -1;
}
year_score = computeHalfLife(year_diff,
_yearHalf);
}
score = _yearWeight * (1.0 - year_score) / denom;
retval += score * score;
score = _tempoWeight * (1.0 -
computeScaledDistance(_tempo_scale,
song1.getStringValue("Tempo"),
song2.getStringValue("Tempo"))) / denom;
retval += score * score;
retval = 1.0 - Math.sqrt(retval);
return retval;
}
--- "John A. Tamplin" <jat (AT) jaet (DOT) org> wrote:
> On Tue, 10 Aug 2004, Joseph Hines wrote:
>
> > > The way I did it is the weighted-random play
> > > replaced shuffle-play mode
> >
> > What was your weighting? How did you choose what
> was
> > next?
>
> I manually set the weightings for songs from 0-100.
> 0 meant never play it
> as a random selection, 1-100 were converted using a
> fancy curve I came up
> with via experimentation (roughly S-shaped -- there
> was only a little
> difference in probability between songs with values
> near either end of the
> range, slightly larger difference between songs
> rated in the middle of the
> range). After these values were picked up for all
> the songs in the
> playlist (and these values are generally static), I
> adjusted the values
> based on the last play timestamp (an exponential
> scale with a large
> negative adjustment for recent times, with the
> "half-life" intended to be
> adjustable by a parameter [but I never did that,
> just edited the source
> file]), and weightings based on the album and artist
> ids of
> recently-played songs (which could be positive or
> negative -- one thing I
> played around with was a positive weight for songs
> by the same artist as
> the most recently played song, and negative for any
> other recently played
> songs -- the purpose was the replicate the practice
> of local radio
> stations to periodically play blocks of songs by the
> same artists).
> The particular values were refined to meet my
> tastes, but the basic idea
> should work for anyone -- just tweak the values to
> your liking.
>
> Adding the suggested changes of trying to keep out
> "bad transitions" would
> probably be harder to setup in a user-neutral manner
> and would heavily
> depend on extra data associated with the songs.
>
> > So you've already written this? Am I missing
> something?
>
> Yes, it is a gross hack to really old SlimServer
> code -- it would need to
> be rewritten from scratch for the current code. I
> did it as part of the
> backend rewrite I was working on, but found no
> enthusiasm from anyone
> else. The web pages for the API (incompletely
> documented, and
> incompletely implemented but sufficient for my
> needs) are still available
> at http://www.jaet.org/jat/slimp3/ if you want to
> take a look at it.
>
> I never implemented anything besides the DBI
> backend, which I run from an
> Informix database (the RAM backend was partially
> done but since I wasn't
> going to use it I felt no need to finish it once it
> became clear this
> wouldn't make it into the main code, and the DB
> backend was never even
> started).
>
> Now that DBI support is being integrated into the
> main server, perhaps
> this sort of functionality will be accepted into the
> main server. If I
> were doing it again, I would have the play mode kept
> on a per-playlist
> basis, so you can have playlists that are to be
> played in sequence such as
> an album or playlists that are to be played
> weighted-random etc, and I
> would also have playlists able to contain other
> playlists (so you can have
> a playlist of complete albums to play, or to save
> work by building up
> lists from other lists [dynamically, so if a
> sub-playlist was updated you
> don't have to do anything for that change to be
> visible in the top
> playlist]).
>
> --
> John A. Tamplin jat (AT) jaet (DOT) org
> 770/436-5387 HOME 4116 Manson Ave
> Smyrna, GA 30082-3723
>
>
Results 1 to 1 of 1
Thread: Re: Say goodbye to playlists!
-
2004-08-11, 05:40 #1Joseph HinesGuest
Re: Say goodbye to playlists!

Reply With Quote
