Hotness is an algorithm that generates a score on a scale of 100 for every song in your library. It weighs frequency of play, rating, and recentness of last play against each other to determine how "hot" a song is. Because of its complex nature, its use of an absolute scale, and its awareness of time, it offers a unique alternative to other approaches to meta-rating (such as playcount × rating).
[!--sizeo:3--][span style=\"font-size:12pt;line-height:100%\"][!--/sizeo--]Update: 18Aug2007 1.7.c
[/size]
Lines 14-18 allow for easy alteration of which playback statistics tags to use. Default baselines have been greatly increased too, just because I find they work best for me. As always, experiment with them.- Paste into Columns UI's "Globals" tab in the Playlist view options
- Requires foo_cwb_hooks to calculate date differences, and to automatically add %added% tag to newly imported tracks (under Tools/New File Tagger in Preferences)
- Retrieve with $get_global(hotness)
Should work right out of the box for most setups, please let me know in this thread if there's anything I've overlooked or screwed up.
///////////////////////////////////////////
// HOTNESS - an algorithm for meta-rating
// v1.7.c (foo_cwb_hooks version)
// July 8, 2007 - by topdownjimmy@gmail.com
///////////////////////////////////////////
//
// configure baselines: define baseline playfrequency and decay period (in days)
//
$puts(baselinefrequency,90) // decrease if songs stay hot too long, or: high to accentuate success, low to accentuate recentness
$puts(baselinedecay,28) // decrease if too many songs are hot, or: high to accentuate success, low to accentuate recentness
//
// configure playback statistics
//
$puts(lp,[%last_played%])
$puts(fp,[%first_played%])
$puts(pc,[%play_count%])
$puts(rating,[%rating%])
$puts(avgrating,3)
//
// DO NOT EDIT BELOW THIS LINE //
$puts(baselinefrequency,$mul($get(baselinefrequency),24))
$puts(baselinedecay,$mul($get(baselinedecay),24))
$puts(lp_age,$add($substr($get(lp),12,13),$mul(24,$cwb_datediff($get(lp),2000-01-01))))
$puts(fp_age,$add($substr($get(fp),12,13),$mul(24,$cwb_datediff($get(fp),2000-01-01))))
$puts(age,$sub($get(lp_age),$get(fp_age)))
$puts(now_age,$add($substr(%cwb_systemdatetime%,12,13),$mul(24,$cwb_datediff(%cwb_systemdate%,2000-01-01))))
$puts(recentness,$sub($get(now_age),$get(lp_age)))
$puts(decay,$div($div($mul($get(pc),$get(baselinefrequency),$get(baselinedecay),$if2($get(rating),$get(avgrating)),100),$mul($max($get(age),$get(baselinefrequency)),$get(avgrating))),100))
$puts(rawhotness,$div($mul($max($sub($get(decay),$get(recentness)),0),100),$get(decay)))
$puts(forecast,$div($mul($max($sub($get(decay),$add($div($max(0,$sub($get(baselinedecay),$get(recentness))),2),$get(recentness))),0),100),$get(decay)))
$puts(hotness,$div($add($get(rawhotness),$get(forecast)),2))
$set_global(hotness,$get(hotness))
// END HOTNESS //
And here's a single-line version to be used in foo_playlist_tree. Put it into the "Population Order" field in a query, and limit to 20 (or whatever) subfolders.
$puts(baselinefrequency,45)$puts(baselinedecay,28)$puts(lp,[%last_played%])$puts(fp,[%first_played%])$puts(pc,[%play_count%])$puts(rating,[%rating%])$puts(avgrating,3)$puts(baselinefrequency,$mul($get(baselinefrequency),24))$puts(baselinedecay,$mul($get(baselinedecay),24))$puts(lp_age,$add($substr($get(lp),12,13),$mul(24,$cwb_datediff($get(lp),2000-01-01))))$puts(fp_age,$add($substr($get(fp),12,13),$mul(24,$cwb_datediff($get(fp),2000-01-01))))$puts(age,$sub($get(lp_age),$get(fp_age)))$puts(now_age,$add($substr(%cwb_systemdatetime%,12,13),$mul(24,$cwb_datediff(%cwb_systemdate%,2000-01-01))))$puts(recentness,$sub($get(now_age),$get(lp_age)))$puts(decay,$div($div($mul($get(pc),$get(baselinefrequency),$get(baselinedecay),$if2($get(rating),$get(avgrating)),100),$mul($max($get(age),$get(baselinefrequency)),$get(avgrating))),100))$puts(rawhotness,$div($mul($max($sub($get(decay),$get(recentness)),0),1000),$get(decay)))$puts(forecast,$div($mul($max($sub($get(decay),$add($div($max(0,$sub($get(baselinedecay),$get(recentness))),2),$get(recentness))),0),1000),$get(decay)))$num($div($add($get(rawhotness),$get(forecast)),2),4)
Log[!--sizeo:1--][span style=\"font-size:8pt;line-height:100%\"][!--/sizeo--]
1.7.c, 8Jul2007
-easy alteration of which playback statistics tags to use
-other minor cleanup
1.6.c.1, 6Dec2006
-improved softening technique to avoid spikes in hotness
Update 11Mar05
-added hourly decay version
-oops, updated again for hourly decay bugfix
Update 09Mar05
-added alternate %play_date% version
Update 22Feb05
-fixed avgrating bug
Update 18Feb05
-"begin decay immediately" is now optional
-uses new LAST_PLAYED standard
-if an %added% tag is absent, it assumes the baseline frequency for the song
-configurable "default rating" (assumes default rating when there is none)
-plan to employ FIRST_PLAYED standard once it's implemented
Code updated 10Feb05
-decay now begins immediately rather than the day after last play
[/size]
History/Approach (some info outdated)[!--sizeo:1--][span style=\"font-size:8pt;line-height:100%\"][!--/sizeo--]
Before using this code, remember that three tags are NECESSARY for it to work: %first_played%, %last_played%, and %play_counter%, provided by foo_playcount 1.9.2
%rating% will also affect things, but isn't essential.
So I had this idea that there are a number of statistics contained within each one of my tracks that are related to the overall "hotness" (or "popularity" or what have you) of a song: %play_counter%, %rating%, %last_played%, and %first_played%. But no algorithm had been designed yet that would weigh all of those factors in the calculation of one single score that measures what I'm calling "hotness."
Rating is all fine and dandy, but it's so cut and dry. Same goes for last_played and play_counter. They're such dead, linear statistics that aren't very interesting to me to watch at all. And play_counter is rendered meaningless unless it's compared to how long a song's been in your library. Complexity was the goal of this idea, the dynamic generation of a song's overall...SOMETHINGness that I couldn't easily predict. It does no good to tell me that I rated a song 5 stars. I know I did, I was there. And keep in mind, this isn't about determining a song's quality or "goodness"; goodness is given by rating. Hotness is something much more interesting, and I hope I'm on my way to a satisfactory representation of it.
To begin, I believe the importance of these factors in decreasing order is:
-recentness of last play
-overall frequency of play (play_counter ÷ days since added ("age"))
-rating
When a song is played, its hotness should be given a boost (the amount of this boost is detailed below). As time passes since the last time a song was played, that song's hotness should decrease. So now we've got to define a decay period: the time it takes before a song's hotness dwindles to 0 again. The two factors that determine the length of this decay period are rating and play frequency.
The definition of this decay period depends on two lengths of time: baseline FREQUENCY and baseline DECAY. In the code below, baselinefrequency is set at 14 days and baselinedecay at 6 days. But, depending on your listening habits, you may need to alter these settings. The effect the settings have isn't immediately intuitive, but a good rule of thumb is:
-the more often you listen to music, the LOWER baselinedecay should be
-the more variety of music you listen to, the HIGHER baselinefrequency should be
Setting baselinefrequency to 14 and baselinedecay to 6 means that a song played on average once every 14 days will decay to 0% hotness 6 days after it's played. If a song is played TWICE every 14 days, its decay period will DOUBLE (12 days). If a song is played once every 28 days, its decay period will halve (3 days).
Likewise, rating will affect a song's decay period. The absence of a rating will assume a default rating of 3. A rating of 5 will multiply the decay period by 5/3, and a rating of 1 will multiply the decay period by 1/5.
The initial boost of playing a song used to be 100% hotness. This made for boring playlists when looking at songs that were played "today," so in the current code, the immediate hotness is what the hotness would be TOMORROW if the hotness were to go up to 100% today. This makes for some interesting effects; for instance, my settings make it so that a song added today and played once today begins at a hotness of 83%. If I play it again today, I then double its play frequency, thus doubling its decay period, thus boosting its hotness.
Here's the code:
/////////////////////////////////////////
// HOTNESS - an algorithm for meta-rating
// %last_played% version
/////////////////////////////////////////
// baselines: define baseline frequency and decay periods
//
// baselinefrequency: decrease if songs stay hot too long
// baselinedecay: decrease if too many songs are hot
//
$puts(baselinefrequency,14)
$puts(baselinedecay,7)
// begin decay immediately? (1=yes, 0=no)
$puts(begindecaynow,1)
// default rating for unrated songs
$puts(avgrating,3)
// calculate "age": the number of days the song has been in the library
$if(%added%,
$puts(age,
$sub(
$add(
$mul($right(%_system_year%,2),365),
$select(%_system_month%,0,31,59,90,120,151,181,212,243,273,304,334),
$ifequal($mod(%_system_year%,4),0,$ifgreater(%_system_month%,2,1,0),0),
%_system_day%
),
$add(
$mul($substr(%added%,3,4),365),
$select($substr(%added%,5,6),0,31,59,90,120,151,181,212,243,273,304,334),
$ifequal($mod($substr(%added%,1,4),4),0,$ifgreater($substr(%added%,5,6),2,1,0),0),
$right(%added%,2)
)
)
),
$puts(age,$mul(%play_counter%,$get(baselinefrequency)))
)
// calculate "recentness": number of days since song was last played
$puts(recentness,
$sub(
$add(
$mul($right(%_system_year%,2),365),
$select(%_system_month%,0,31,59,90,120,151,181,212,243,273,304,334),
$ifequal($mod(%_system_year%,4),0,$ifgreater(%_system_month%,2,1,0),0),
%_system_day%
),
$add(
$mul($substr(%last_played%,3,4),365),
$select($substr(%last_played%,6,7),0,31,59,90,120,151,181,212,243,273,304,334),
$ifequal($mod($substr(%last_played%,1,4),4),0,$ifgreater($substr(%last_played%,6,7),2,1,0),0),
$substr(%last_played%,9,10)
)
)
)
// calculate "decay": the specific decay period
$puts(decay,$div($mul(%play_counter%,$get(baselinefrequency),$get(baselinedecay)
,$if2(%rating%,$get(avgrating))),$mul($max($get(age),$get(baselinefrequency)),$get(avgrating))))
// calculate "hotness"
hotness=$div($mul($max($sub($get(decay),$add(
$get(recentness),$get(begindecaynow))),0),100),$get(decay))
//////////////////////////////
For compatibility with foo_playcount's default play_date settings:
/////////////////////////////////////////
// HOTNESS - an algorithm for meta-rating
// %play_date% version
/////////////////////////////////////////
// baselines: define baseline frequency and decay periods
//
// baselinefrequency: decrease if songs stay hot too long
// baselinedecay: decrease if too many songs are hot
//
$puts(baselinefrequency,14)
$puts(baselinedecay,7)
// begin decay immediately? (1=yes, 0=no)
$puts(begindecaynow,1)
// default rating for unrated songs
$puts(avgrating,3)
// calculate "age": the number of days the song has been in the library
$if(%added%,
$puts(age,
$sub(
$add(
$mul($right(%_system_year%,2),365),
$select(%_system_month%,0,31,59,90,120,151,181,212,243,273,304,334),
$add($div($right(%_system_year%,2),4),$if($or($greater(%_system_month%,2),$greater($mod(%_system_year%,4),0)),1,0)),
%_system_day%
),
$add(
$mul($substr(%added%,3,4),365),
$select($substr(%added%,5,6),0,31,59,90,120,151,181,212,243,273,304,334),
$add($div($substr(%added%,3,4),4),$if($or($greater($substr(%added%,5,6),2),$greater($mod($substr(%added%,1,4),4),0)),1,0)),
$right(%added%,2)
)
)
),
$puts(age,$mul(%play_counter%,$get(baselinefrequency)))
)
// calculate "recentness": number of days since song was last played
$puts(recentness,
$sub(
$add(
$mul($right(%_system_year%,2),365),
$select(%_system_month%,0,31,59,90,120,151,181,212,243,273,304,334),
$add($div($right(%_system_year%,2),4),$if($or($greater(%_system_month%,2),$greater($mod(%_system_year%,4),0)),1,0)),
%_system_day%
),
$add(
$mul($substr(%play_date%,5,6),365),
$select($substr(%play_date%,3,4),0,31,59,90,120,151,181,212,243,273,304,334),
$add($div($substr(%play_date%,5,6),4),$if($or($greater($substr(%play_date%,3,4),2),$greater($mod($substr(%play_date%,5,6),4),0)),1,0)),
$substr(%play_date%,1,2)
)
)
)
// calculate "decay": the specific decay period
$puts(decay,$div($mul(%play_counter%,$get(baselinefrequency),$get(baselinedecay),$if2(%rating%,$get(avgrating))),$mul($max($get(age),$get(baselinefrequency)),3)))
// calculate "hotness"
hotness=$div($mul($max($sub($get(decay),$add($get(recentness),$get(begindecaynow))),0),100),$get(decay))
//////////////////////////////
And an hourly decay version...this version does not include the "begin decay immediately" option because decay will begin in an hour anyway.
/////////////////////////////////////////
// HOTNESS - an algorithm for meta-rating
// hourly decay version
/////////////////////////////////////////
// baselines: define baseline frequency and decay periods
//
// baselinefrequency: decrease if songs stay hot too long
// baselinedecay: decrease if too many songs are hot
//
$puts(baselinefrequency,14)
$puts(baselinedecay,7)
// default rating for unrated songs
$puts(avgrating,3)
// convert baselines to hours
$puts(baselinefrequency,$mul($get(baselinefrequency),24))
$puts(baselinedecay,$mul($get(baselinedecay),24))
// calculate "age": the number of hours the song has been in the library
$if(%added%,
$puts(age,
$sub(
$add(
$mul(
$add(
$mul($right(%_system_year%,2),365),
$select(%_system_month%,0,31,59,90,120,151,181,212,243,273,304,334),
$add($div($right(%_system_year%,2),4),$if($or($greater(%_system_month%,2),$greater($mod(%_system_year%,4),0)),1,0)),
%_system_day%
),
24
),
%_system_hour%
)
,
$mul(
$add(
$mul($substr(%added%,3,4),365),
$select($substr(%added%,5,6),0,31,59,90,120,151,181,212,243,273,304,334),
$add($div($substr(%added%,3,4),4),$if($or($greater($substr(%added%,5,6),2),$greater($mod($substr(%added%,1,4),4),0)),1,0)),
$right(%added%,2)
),
24
)
)
)
,
$puts(age,$mul(%play_counter%,$get(baselinefrequency)))
)
// calculate "recentness": number of hours since song was last played
$puts(recentness,
$sub(
$add(
$mul(
$add(
$mul($right(%_system_year%,2),365),
$select(%_system_month%,0,31,59,90,120,151,181,212,243,273,304,334),
$add($div($right(%_system_year%,2),4),$if($or($greater(%_system_month%,2),$greater($mod(%_system_year%,4),0)),1,0)),
%_system_day%
),
24
),
%_system_hour%
)
,
$add(
$mul(
$add(
$mul($substr(%last_played%,3,4),365),
$select($substr(%last_played%,6,7),0,31,59,90,120,151,181,212,243,273,304,334),
$add($div($substr(%last_played%,3,4),4),$if($or($greater($substr(%last_played%,6,7),2),$greater($mod($substr(%last_played%,1,4),4),0)),1,0)),
$substr(%last_played%,9,10)
),
24
),
$substr(%last_played%,12,13)
)
)
)
// calculate "decay": the specific decay period
$puts(decay,$div($mul(%play_counter%,$get(baselinefrequency),$get(baselinedecay),$if2(%rating%,$get(avgrating))),$mul($max($get(age),$get(baselinefrequency)),3)))
// calculate "hotness"
hotness=$div($mul($max($sub($get(decay),$get(recentness)),0),100),$get(decay))
//////////////////////////////
Paste into the "Variables" tab under the "Globals" tab in Columns UI settings, and make sure that "Make date info available" and "Use global variables for display" are both checked. Now you are free to use %_hotness% anywhere in your columns! It will appear on a scale of 100. Because this statistic is so effluvial, I think it will be best depicted in color, perhaps as a shade of red on a dot. To do this, create a column with ● as the display, check "Use custom colour spec," and in the "Colour" tab, type:
$blend(FFFFFF,0000FF,%_hotness%,100)|
$blend(FFFFFF,0000FF,%_hotness%,100)|
FFFFFF|
FFFFFF
You will, of course, have to change some of those colors to match your other colors.
Here's how things look for me:
[a href=\"http://img104.exs.cx/my.php?loc=img104&image=clipboard014hh.png\" target=\"_blank\"][/color][/size]