RadioFreeMonkey 1.2
RadioFreeMonkey 1.2
A minor update to Monkey Radio:
- The 'boost' from being in the library for 30+days no longer can push the weighting over the original rating. This means that stuff that was rated 2 stars won't slowly creep up into 5 star range as time goes by and they aren't getting played.
- No longer use the Left function
- Actually implemented the ReduceIfPlayed flag. If this is false, all items will simply be weighted based on their ratings, no messing around with play-counts and dates.
I can see that, if you have listened to an album 50 times over the last year since you first added it to your library, the weighting for those songs will be zero or less (negative). This means they will never come up. This isn't usually a problem for me, because I have a very large library, and the work computer I usually use it on has a lot of churn, with songs being added and removed almost every day.
If this is causing a problem for you, you will have to either (a) turn off ReduceIfPlayed (set it to false) or (b) remove and re-add your tunes to reset the playcount to 0. Simply moving them to another directory out of MM's view, then back should do it.
I've looked into two-for and three-for artist blocks, but that involves extra spinning through the list of tunes, which is okay, but also a lot of checking to make sure that it's not picking the same song it just picked, and that you actually have a second song by that artist.
If you didn't mind whether the second or third song were weighted, it might be easier... I'll think about it.
Meanwhile, enjoy!
Peter
===
' RadioFreeMonkey
' Version 1.2
' A script to create a "radio station" for you based on your song ratings.
' This script creates a root node in the tree, beneath the library node, called "RadioFreeMonkey".
' Under this node, you have a Radio List node, a Done node and a Weightings node.
' - The Radio List node lists 20 songs, in random order. These are weighted, so songs
' with a higher weighting have a higher chance of getting selected.
' - If you have selected a sorting column, even though the songs are selected randomly, the
' radio list will be sorted based on that column. To return the list to "random" sorting, click
' on any playlist (Now Playing works nicely). When you visit the Radio List node, the songs will
' be unsorted, in true random order.
' - The Done list shows you which songs aren't going to be played. This includes songs that have
' passed their maximum playcount, or anything within MinDaysRepeat.
' - The Weighting node shows all tunes, sorted by their calculated weights. This can help you
' determine what the optimal weighting choices are for you. Plus, it's just nice to see what's
' more likely to be played.
' Songs are weighted as follows:
' Rating * 2 (5 stars = 10, 3.5 stars = 7, 0 stars = 0)
' - Number of Plays (if Reduce if Played is TRUE)
' + Days since added to library / DayFactor (always rounded down)
' However, the weighting can't be more than the original rating (* 2).
' TO DO: At some point, I'd like to make the main 'list' node a PlayList instead of a regular
' list of tracks, but I'm not sure how to do this.
'
' This script can be freely used and modified.
' This is an early release and there may be bugs. If it's causing you problems, simply delete
' it or move it out of the scripts\auto folder.
'
' The script does not modify the database, registry or INI file.
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' Global Variables and Declarations
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Option Explicit
' %%% The caption for the root node.
Const RootNodeCaption = "RadioFreeMonkey"
' %%% Add 1 to weighting for each X days since added to library
Const DayFactor = 30
' %%% Anything rated this or below will not receive the 'date boost'
Const DateBoostCutoff = 1.5
' %%% The minimum number of days that must pass before a song is repeated. Zero means, go ahead
' and repeat it right away.
Const MinDaysRepeat = 1
' %%% Reduce the weighting by the number of times played. This means, as songs are played more often,
' they are less likely to be played again
Const ReduceIfPlayed = True
' %%% Number of Songs in list
Const NumberOfSongs = 20
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' The Meat. Don't change anything under here.
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' the text of the formulas to be used throughout
Dim weightedRatingFormula, dateBoostFormula, ratingFormula, weightedPlayCountFormula
dateBoostFormula = "IIF(Songs.rating <= "&(DateBoostCutoff*20)&", 0, FIX(DateDiff('d',Songs.DateAdded,Now) / "&DayFactor&"))"
ratingFormula = "(IIF(Songs.rating < 0, 0, Songs.rating) / 10)"
If ReduceIfPlayed Then
weightedPlayCountFormula = "(Songs.PlayCounter - " & dateBoostFormula & ")"
Else
weightedPlayCountFormula = "0"
End If
weightedRatingFormula = ratingFormula & " - IIF(" & weightedPlayCountFormula & "<0,0," & weightedPlayCountFormula & ")"
Sub FillStandardProperties(parentNode, childNode)
With childNode
.CustomNodeId = parentNode.CustomNodeId
.CustomDataId = parentNode.CustomDataId + 1
.UseScript = Script.ScriptPath
End With
End Sub
Sub FillGoodLeaf(Node)
Randomize
Dim SplitCustomData, Tracks
Dim SQLCondition, SQLStatement
Dim SELECT_Clause, FROM_Clause, WHERE_Clause, ORDER_Clause
Dim Iter, res, i, total, sum, index, weight
Dim hold, weights
Set hold = CreateObject("Scripting.Dictionary")
Set weights = CreateObject("Scripting.Dictionary")
SELECT_Clause = " SELECT Songs.Id, "&weightedRatingFormula&" "
FROM_Clause = " FROM Songs "
' WHERE_Clause = " WHERE Songs.PlayCounter < "&weightedRatingFormula&" "
WHERE_Clause = " WHERE " & weightedPlayCountFormula &" < " & ratingFormula & " AND DateDiff('d',Songs.LastTimePlayed, Now) > " & MinDaysRepeat
SQLStatement = SELECT_Clause & FROM_Clause & WHERE_Clause
Set Iter = SDB.Database.OpenSQL(SQLStatement)
total=0
sum = 0
While Not Iter.EOF
hold.add total,Iter.StringByIndex(0)
weights.add total,Iter.StringByIndex(1)
total = total + 1
sum = sum + Iter.StringByIndex(1)
Iter.Next
Wend
Dim R, max : max = NumberOfSongs
If (total < max) Then
max = total
End If
Dim inStr : inStr = ""
Set Tracks = SDB.MainTracksWindow
While i < max
' R = Round((rnd() * (total+1))-0.5, 0)
R = rnd() * sum
For index = 0 to total
If R < cdbl(weights.item(index)) Then
Exit For
Else
R = R - cdbl(weights.item(index))
End If
Next 'index
If i = 0 Then
inStr = index
Else
inStr = inStr & ", " & hold.item(index)
End If
i = i + 1
Wend
'res = SDB.MessageBox(inStr, mtError, Array(mbOk))
Tracks.AddTracksFromQuery("AND Songs.ID IN (" & inStr & ") ORDER BY Rnd((1000*Songs.ID)*Now())")
Tracks.FinishAdding
' function WeightedRandom(const Weights : array of Double) : Integer ;
' var R : Double ;
' begin
' R := Random * Sum(Weights) ;
' for Result := 0 to High(Weights) do
' if (R < Weights[Result]) then BREAK
' else R := R - Weights[Result] ;
' end ;
End Sub
Sub FillWeightNode(Node)
Dim SplitCustomData ' Auxiliary Array used to collect the pieces of Node.CustomData
Dim SQLLinking ' Piece of SQL Code (WHERE statement) defining the relations between tables
Dim SQLTables ' Part of FROM clause indicating which tables to query
Dim SQLCondition ' String containing the part of the WHERE statement coming from the ancestor nodes
Dim fullMask, curLevelMask
Dim Tree, newNode, nextIsLeaf
Dim FldTxt ' Text identifying the field by which the created nodes will be filtered
Dim Field, IdField, OrderField ' Fields to be queried
' Variables that store qualifier values and related expressions
Dim TopQualifier, TopClause, SortCondition, minTracks, maxTracks, trimValue, doFormatting
Dim ContentIndex ' An index to the field to be displayed
Dim SELECT_Clause, FROM_Clause, WHERE_Clause, GROUP_BY_Clause, ORDER_BY_Clause, HAVING_Clause
Dim SQLStatement ' SQL query to the database
Dim CaptionPrefix ' Used to modify the caption when a sorting has been specified
Dim EscapedId ' Used to escape a text value in the database to be used as a test
Dim idArgument ' The first argument submitted to the Format function
Dim Iter ' SDBD Iterator obtained by running the SQL query to get the nodes
Set Tree = SDB.MainTree
Node.HasChildren = false ' To delete all old children
SQLStatement = "SELECT DISTINCT " & weightedRatingFormula & " from Songs "
Set Iter = SDB.Database.OpenSQL(SQLStatement)
While Not Iter.EOF
Set newNode = Tree.CreateNode
NewNode.Caption = Iter.StringByIndex(0)
NewNode.iconIndex = 32
newNode.CustomData = Iter.StringByIndex(0)
FillStandardProperties node,newNode
newNode.onFillTracksFunct = "FillWeightLeaf"
newNode.hasChildren = False
Tree.AddNode Node, NewNode, 3
Iter.Next
Wend
End Sub
Sub FillWeightLeaf(Node)
Dim Weight
Dim Tracks
Dim SELECT_Clause, FROM_Clause, WHERE_Clause
Weight = Node.CustomData
SELECT_Clause = " SELECT Songs.Id "
FROM_Clause = " FROM Songs "
WHERE_Clause = " WHERE " & weightedRatingFormula & " = " & Weight & " AND DateDiff('d',Songs.LastTimePlayed, Now) > " & MinDaysRepeat
Set Tracks = SDB.MainTracksWindow
Tracks.AddTracksFromQuery("AND Songs.ID IN (" & SELECT_Clause & FROM_Clause & WHERE_Clause & ")")
Tracks.FinishAdding
End Sub
Sub FillDoneLeaf(Node)
Dim Tracks
Dim SELECT_Clause, FROM_Clause, WHERE_Clause
SELECT_Clause = " SELECT Songs.Id "
FROM_Clause = " FROM Songs "
WHERE_Clause = " WHERE "& weightedPlayCountFormula &" >= " & ratingFormula & " OR DateDiff('d',Songs.LastTimePlayed, Now) <= " & MinDaysRepeat
Set Tracks = SDB.MainTracksWindow
Tracks.AddTracksFromQuery("AND Songs.ID IN (" & SELECT_Clause & FROM_Clause & WHERE_Clause & ")")
Tracks.FinishAdding
End Sub
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' Startup Function
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Sub onStartUp
Dim Tree, RadioFreeMonkeyRoot
Dim RadioMonkeyGood, RadioMonkeyBad, RadioMonkeyWeight
Set Tree = Sdb.MainTree
Set RadioFreeMonkeyRoot = Tree.createNode
RadioFreeMonkeyRoot.Caption = RootNodeCaption
RadioFreeMonkeyRoot.IconIndex = 14
RadioFreeMonkeyRoot.UseScript = Script.ScriptPath
RadioFreeMonkeyRoot.hasChildren = True
Tree.AddNode Tree.Node_Library, RadioFreeMonkeyRoot, 1
SDB.Objects("RadioFreeMonkeyRoot") = RadioFreeMonkeyRoot
Set RadioMonkeyGood = Tree.createNode
RadioMonkeyGood.Caption = "Radio List"
RadioMonkeyGood.IconIndex = 14
RadioMonkeyGood.UseScript = Script.ScriptPath
RadioMonkeyGood.hasChildren = False
RadioMonkeyGood.onFillTracksFunct = "FillGoodLeaf"
Tree.AddNode RadioFreeMonkeyRoot, RadioMonkeyGood, 2
SDB.Objects("RadioMonkeyGood") = RadioMonkeyGood
Set RadioMonkeyWeight = Tree.createNode
RadioMonkeyWeight.Caption = "Weightings"
RadioMonkeyWeight.IconIndex = 32
RadioMonkeyWeight.UseScript = Script.ScriptPath
RadioMonkeyWeight.hasChildren = True
RadioMonkeyWeight.onFillTracksFunct = "FillWeightNode"
Tree.AddNode RadioFreeMonkeyRoot, RadioMonkeyWeight, 3
SDB.Objects("RadioMonkeyWeight") = RadioMonkeyWeight
Set RadioMonkeyBad = Tree.createNode
RadioMonkeyBad.Caption = "Done"
RadioMonkeyBad.IconIndex = 15
RadioMonkeyBad.UseScript = Script.ScriptPath
RadioMonkeyBad.hasChildren = False
RadioMonkeyBad.onFillTracksFunct = "FillDoneLeaf"
Tree.AddNode RadioFreeMonkeyRoot, RadioMonkeyBad, 3
SDB.Objects("RadioMonkeyBad") = RadioMonkeyBad
End Sub
- The 'boost' from being in the library for 30+days no longer can push the weighting over the original rating. This means that stuff that was rated 2 stars won't slowly creep up into 5 star range as time goes by and they aren't getting played.
- No longer use the Left function
- Actually implemented the ReduceIfPlayed flag. If this is false, all items will simply be weighted based on their ratings, no messing around with play-counts and dates.
I can see that, if you have listened to an album 50 times over the last year since you first added it to your library, the weighting for those songs will be zero or less (negative). This means they will never come up. This isn't usually a problem for me, because I have a very large library, and the work computer I usually use it on has a lot of churn, with songs being added and removed almost every day.
If this is causing a problem for you, you will have to either (a) turn off ReduceIfPlayed (set it to false) or (b) remove and re-add your tunes to reset the playcount to 0. Simply moving them to another directory out of MM's view, then back should do it.
I've looked into two-for and three-for artist blocks, but that involves extra spinning through the list of tunes, which is okay, but also a lot of checking to make sure that it's not picking the same song it just picked, and that you actually have a second song by that artist.
If you didn't mind whether the second or third song were weighted, it might be easier... I'll think about it.
Meanwhile, enjoy!
Peter
===
' RadioFreeMonkey
' Version 1.2
' A script to create a "radio station" for you based on your song ratings.
' This script creates a root node in the tree, beneath the library node, called "RadioFreeMonkey".
' Under this node, you have a Radio List node, a Done node and a Weightings node.
' - The Radio List node lists 20 songs, in random order. These are weighted, so songs
' with a higher weighting have a higher chance of getting selected.
' - If you have selected a sorting column, even though the songs are selected randomly, the
' radio list will be sorted based on that column. To return the list to "random" sorting, click
' on any playlist (Now Playing works nicely). When you visit the Radio List node, the songs will
' be unsorted, in true random order.
' - The Done list shows you which songs aren't going to be played. This includes songs that have
' passed their maximum playcount, or anything within MinDaysRepeat.
' - The Weighting node shows all tunes, sorted by their calculated weights. This can help you
' determine what the optimal weighting choices are for you. Plus, it's just nice to see what's
' more likely to be played.
' Songs are weighted as follows:
' Rating * 2 (5 stars = 10, 3.5 stars = 7, 0 stars = 0)
' - Number of Plays (if Reduce if Played is TRUE)
' + Days since added to library / DayFactor (always rounded down)
' However, the weighting can't be more than the original rating (* 2).
' TO DO: At some point, I'd like to make the main 'list' node a PlayList instead of a regular
' list of tracks, but I'm not sure how to do this.
'
' This script can be freely used and modified.
' This is an early release and there may be bugs. If it's causing you problems, simply delete
' it or move it out of the scripts\auto folder.
'
' The script does not modify the database, registry or INI file.
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' Global Variables and Declarations
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Option Explicit
' %%% The caption for the root node.
Const RootNodeCaption = "RadioFreeMonkey"
' %%% Add 1 to weighting for each X days since added to library
Const DayFactor = 30
' %%% Anything rated this or below will not receive the 'date boost'
Const DateBoostCutoff = 1.5
' %%% The minimum number of days that must pass before a song is repeated. Zero means, go ahead
' and repeat it right away.
Const MinDaysRepeat = 1
' %%% Reduce the weighting by the number of times played. This means, as songs are played more often,
' they are less likely to be played again
Const ReduceIfPlayed = True
' %%% Number of Songs in list
Const NumberOfSongs = 20
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' The Meat. Don't change anything under here.
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' the text of the formulas to be used throughout
Dim weightedRatingFormula, dateBoostFormula, ratingFormula, weightedPlayCountFormula
dateBoostFormula = "IIF(Songs.rating <= "&(DateBoostCutoff*20)&", 0, FIX(DateDiff('d',Songs.DateAdded,Now) / "&DayFactor&"))"
ratingFormula = "(IIF(Songs.rating < 0, 0, Songs.rating) / 10)"
If ReduceIfPlayed Then
weightedPlayCountFormula = "(Songs.PlayCounter - " & dateBoostFormula & ")"
Else
weightedPlayCountFormula = "0"
End If
weightedRatingFormula = ratingFormula & " - IIF(" & weightedPlayCountFormula & "<0,0," & weightedPlayCountFormula & ")"
Sub FillStandardProperties(parentNode, childNode)
With childNode
.CustomNodeId = parentNode.CustomNodeId
.CustomDataId = parentNode.CustomDataId + 1
.UseScript = Script.ScriptPath
End With
End Sub
Sub FillGoodLeaf(Node)
Randomize
Dim SplitCustomData, Tracks
Dim SQLCondition, SQLStatement
Dim SELECT_Clause, FROM_Clause, WHERE_Clause, ORDER_Clause
Dim Iter, res, i, total, sum, index, weight
Dim hold, weights
Set hold = CreateObject("Scripting.Dictionary")
Set weights = CreateObject("Scripting.Dictionary")
SELECT_Clause = " SELECT Songs.Id, "&weightedRatingFormula&" "
FROM_Clause = " FROM Songs "
' WHERE_Clause = " WHERE Songs.PlayCounter < "&weightedRatingFormula&" "
WHERE_Clause = " WHERE " & weightedPlayCountFormula &" < " & ratingFormula & " AND DateDiff('d',Songs.LastTimePlayed, Now) > " & MinDaysRepeat
SQLStatement = SELECT_Clause & FROM_Clause & WHERE_Clause
Set Iter = SDB.Database.OpenSQL(SQLStatement)
total=0
sum = 0
While Not Iter.EOF
hold.add total,Iter.StringByIndex(0)
weights.add total,Iter.StringByIndex(1)
total = total + 1
sum = sum + Iter.StringByIndex(1)
Iter.Next
Wend
Dim R, max : max = NumberOfSongs
If (total < max) Then
max = total
End If
Dim inStr : inStr = ""
Set Tracks = SDB.MainTracksWindow
While i < max
' R = Round((rnd() * (total+1))-0.5, 0)
R = rnd() * sum
For index = 0 to total
If R < cdbl(weights.item(index)) Then
Exit For
Else
R = R - cdbl(weights.item(index))
End If
Next 'index
If i = 0 Then
inStr = index
Else
inStr = inStr & ", " & hold.item(index)
End If
i = i + 1
Wend
'res = SDB.MessageBox(inStr, mtError, Array(mbOk))
Tracks.AddTracksFromQuery("AND Songs.ID IN (" & inStr & ") ORDER BY Rnd((1000*Songs.ID)*Now())")
Tracks.FinishAdding
' function WeightedRandom(const Weights : array of Double) : Integer ;
' var R : Double ;
' begin
' R := Random * Sum(Weights) ;
' for Result := 0 to High(Weights) do
' if (R < Weights[Result]) then BREAK
' else R := R - Weights[Result] ;
' end ;
End Sub
Sub FillWeightNode(Node)
Dim SplitCustomData ' Auxiliary Array used to collect the pieces of Node.CustomData
Dim SQLLinking ' Piece of SQL Code (WHERE statement) defining the relations between tables
Dim SQLTables ' Part of FROM clause indicating which tables to query
Dim SQLCondition ' String containing the part of the WHERE statement coming from the ancestor nodes
Dim fullMask, curLevelMask
Dim Tree, newNode, nextIsLeaf
Dim FldTxt ' Text identifying the field by which the created nodes will be filtered
Dim Field, IdField, OrderField ' Fields to be queried
' Variables that store qualifier values and related expressions
Dim TopQualifier, TopClause, SortCondition, minTracks, maxTracks, trimValue, doFormatting
Dim ContentIndex ' An index to the field to be displayed
Dim SELECT_Clause, FROM_Clause, WHERE_Clause, GROUP_BY_Clause, ORDER_BY_Clause, HAVING_Clause
Dim SQLStatement ' SQL query to the database
Dim CaptionPrefix ' Used to modify the caption when a sorting has been specified
Dim EscapedId ' Used to escape a text value in the database to be used as a test
Dim idArgument ' The first argument submitted to the Format function
Dim Iter ' SDBD Iterator obtained by running the SQL query to get the nodes
Set Tree = SDB.MainTree
Node.HasChildren = false ' To delete all old children
SQLStatement = "SELECT DISTINCT " & weightedRatingFormula & " from Songs "
Set Iter = SDB.Database.OpenSQL(SQLStatement)
While Not Iter.EOF
Set newNode = Tree.CreateNode
NewNode.Caption = Iter.StringByIndex(0)
NewNode.iconIndex = 32
newNode.CustomData = Iter.StringByIndex(0)
FillStandardProperties node,newNode
newNode.onFillTracksFunct = "FillWeightLeaf"
newNode.hasChildren = False
Tree.AddNode Node, NewNode, 3
Iter.Next
Wend
End Sub
Sub FillWeightLeaf(Node)
Dim Weight
Dim Tracks
Dim SELECT_Clause, FROM_Clause, WHERE_Clause
Weight = Node.CustomData
SELECT_Clause = " SELECT Songs.Id "
FROM_Clause = " FROM Songs "
WHERE_Clause = " WHERE " & weightedRatingFormula & " = " & Weight & " AND DateDiff('d',Songs.LastTimePlayed, Now) > " & MinDaysRepeat
Set Tracks = SDB.MainTracksWindow
Tracks.AddTracksFromQuery("AND Songs.ID IN (" & SELECT_Clause & FROM_Clause & WHERE_Clause & ")")
Tracks.FinishAdding
End Sub
Sub FillDoneLeaf(Node)
Dim Tracks
Dim SELECT_Clause, FROM_Clause, WHERE_Clause
SELECT_Clause = " SELECT Songs.Id "
FROM_Clause = " FROM Songs "
WHERE_Clause = " WHERE "& weightedPlayCountFormula &" >= " & ratingFormula & " OR DateDiff('d',Songs.LastTimePlayed, Now) <= " & MinDaysRepeat
Set Tracks = SDB.MainTracksWindow
Tracks.AddTracksFromQuery("AND Songs.ID IN (" & SELECT_Clause & FROM_Clause & WHERE_Clause & ")")
Tracks.FinishAdding
End Sub
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' Startup Function
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Sub onStartUp
Dim Tree, RadioFreeMonkeyRoot
Dim RadioMonkeyGood, RadioMonkeyBad, RadioMonkeyWeight
Set Tree = Sdb.MainTree
Set RadioFreeMonkeyRoot = Tree.createNode
RadioFreeMonkeyRoot.Caption = RootNodeCaption
RadioFreeMonkeyRoot.IconIndex = 14
RadioFreeMonkeyRoot.UseScript = Script.ScriptPath
RadioFreeMonkeyRoot.hasChildren = True
Tree.AddNode Tree.Node_Library, RadioFreeMonkeyRoot, 1
SDB.Objects("RadioFreeMonkeyRoot") = RadioFreeMonkeyRoot
Set RadioMonkeyGood = Tree.createNode
RadioMonkeyGood.Caption = "Radio List"
RadioMonkeyGood.IconIndex = 14
RadioMonkeyGood.UseScript = Script.ScriptPath
RadioMonkeyGood.hasChildren = False
RadioMonkeyGood.onFillTracksFunct = "FillGoodLeaf"
Tree.AddNode RadioFreeMonkeyRoot, RadioMonkeyGood, 2
SDB.Objects("RadioMonkeyGood") = RadioMonkeyGood
Set RadioMonkeyWeight = Tree.createNode
RadioMonkeyWeight.Caption = "Weightings"
RadioMonkeyWeight.IconIndex = 32
RadioMonkeyWeight.UseScript = Script.ScriptPath
RadioMonkeyWeight.hasChildren = True
RadioMonkeyWeight.onFillTracksFunct = "FillWeightNode"
Tree.AddNode RadioFreeMonkeyRoot, RadioMonkeyWeight, 3
SDB.Objects("RadioMonkeyWeight") = RadioMonkeyWeight
Set RadioMonkeyBad = Tree.createNode
RadioMonkeyBad.Caption = "Done"
RadioMonkeyBad.IconIndex = 15
RadioMonkeyBad.UseScript = Script.ScriptPath
RadioMonkeyBad.hasChildren = False
RadioMonkeyBad.onFillTracksFunct = "FillDoneLeaf"
Tree.AddNode RadioFreeMonkeyRoot, RadioMonkeyBad, 3
SDB.Objects("RadioMonkeyBad") = RadioMonkeyBad
End Sub
-
- Posts: 14163
- Joined: Sat Oct 25, 2003 7:57 am
- Location: (Texas)
- Contact:
wow this is a lot to take in?
how do you put this in monkey?
right now i am using the windows scheduler script from peke and the auto play count to keep the jingles out of the top 50 list.
but i have notice that the rateing for songs was preventing some from being played at all.
so i made them all 5 stars for the whole librairy
would this script change them back to ratings on how many times they were played?
it does seem like a good idea but i also have a huge librairy but i don't remove songs i keep adding
i have not heard some in weeks.
but i then also only use the shuffle button on the monkeys player.
it is better then winamp's shuffle. and using the widows scheduler script it only works when using the monkey player not winamp.
so is this script for radio monkey going to do me any good or will it just be causing me to do the same thing i already am doing?

how do you put this in monkey?
right now i am using the windows scheduler script from peke and the auto play count to keep the jingles out of the top 50 list.
but i have notice that the rateing for songs was preventing some from being played at all.
so i made them all 5 stars for the whole librairy
would this script change them back to ratings on how many times they were played?
it does seem like a good idea but i also have a huge librairy but i don't remove songs i keep adding

but i then also only use the shuffle button on the monkeys player.
it is better then winamp's shuffle. and using the widows scheduler script it only works when using the monkey player not winamp.
so is this script for radio monkey going to do me any good or will it just be causing me to do the same thing i already am doing?

roving cowboy / keith hall. My skins http://www.mediamonkey.com/forum/viewto ... =9&t=16724 for some help check on Monkey's helpful messages at http://www.mediamonkey.com/forum/viewto ... 4008#44008 MY SYSTEMS.1.Jukebox WinXp pro sp 3 version 3.5 gigabyte mb. 281 GHz amd athlon x2 240 built by me.) 2.WinXP pro sp3, vers 2.5.5 and vers 3.5 backup storage, shuttle 32a mb,734 MHz amd athlon put together by me.) 3.Dell demension, winxp pro sp3, mm3.5 spare jukebox.) 4.WinXp pro sp3, vers 3.5, dad's computer bought from computer store. )5. Samsung Galaxy A51 5G Android ) 6. amd a8-5600 apu 3.60ghz mm version 4 windows 7 pro bought from computer store.
-
- Posts: 14163
- Joined: Sat Oct 25, 2003 7:57 am
- Location: (Texas)
- Contact:
psyxonova wrote:Why don't you give it a try and see if it does your job. After all this script doesnt modify database or files in any way so you are safe.
Don't forget you are the only one that knows what exactly wants to do, and so you are the only one that can answer your question...


so in other words you don't know either

roving cowboy / keith hall. My skins http://www.mediamonkey.com/forum/viewto ... =9&t=16724 for some help check on Monkey's helpful messages at http://www.mediamonkey.com/forum/viewto ... 4008#44008 MY SYSTEMS.1.Jukebox WinXp pro sp 3 version 3.5 gigabyte mb. 281 GHz amd athlon x2 240 built by me.) 2.WinXP pro sp3, vers 2.5.5 and vers 3.5 backup storage, shuttle 32a mb,734 MHz amd athlon put together by me.) 3.Dell demension, winxp pro sp3, mm3.5 spare jukebox.) 4.WinXp pro sp3, vers 3.5, dad's computer bought from computer store. )5. Samsung Galaxy A51 5G Android ) 6. amd a8-5600 apu 3.60ghz mm version 4 windows 7 pro bought from computer store.
-
- Posts: 176
- Joined: Fri Mar 11, 2005 5:26 am
Risser,
Thanks for the update on the RadioFreeMonkey script. I use it all the time and enjoy your programming.
A feature request for version 1.3: could you implement a 'factor' that adds random songs to the playlist as well ?
eg: unrateFactor = 5, then 5% of the songs of the RadioFreeMonkey playlist are truly random (regardless to their rating or number of times played).
The reason I ask for this is that usually not all tracks are rated. You rate songs when you hear them. But when you don't hear the tracks, you won't rate them. Hence...
Once again thanks for your script: let the monkey play FREE!
charlieMOGUL
Thanks for the update on the RadioFreeMonkey script. I use it all the time and enjoy your programming.
A feature request for version 1.3: could you implement a 'factor' that adds random songs to the playlist as well ?
eg: unrateFactor = 5, then 5% of the songs of the RadioFreeMonkey playlist are truly random (regardless to their rating or number of times played).
The reason I ask for this is that usually not all tracks are rated. You rate songs when you hear them. But when you don't hear the tracks, you won't rate them. Hence...
Once again thanks for your script: let the monkey play FREE!
charlieMOGUL
RadioFreeMonkey v1.5
I've been using this script for quite some time now. When Auto-DJ became available, I switched over to that, only to find that that it could not do the same for me as RadioFreeMonkey did! So thank you for that great script, Risser.
Still, there were some things I was missing here. Over the time, I've made some modifications and added some features to it. These are:
- added possibility to boost songs that have been added to the DB recently (like a radio station would play new songs more often)
- added possibility to define the genres that should be played (so that classic and pop won't be mixed)
- redesign of the mechanism that selects the actual songs to be played (because I did not really understand what was going on in the existing algorithm
)
- now it is possible to influence more easily what songs you will hear (e.g. 30% of songs with a weighting of 8-10 and 70% with a weighting of 5-7).
You will probably have to do some tweaking of the parameters, the ones below are giving me sensible results.
My programming skills are not very good and I don't really know VBScript, but by looking closely at the code and a lot of debugging I found out what was going on and was able to make the modifications I wanted. I learned a lot from this already, but would be glad if one of you Real Programmers(tm) out there could do some kind of code review for me to point out what could be done better.
For installation, just copy the code below into a text file, name it RadioFreeMonkey.vbs and put it in your [...]\MediaMonkey\Scripts\Auto directory. Make your modifications to the parameters at the top of the script. They all have comments so it should be easy to work out what their intention is.
Then save the script and restart MediaMonkey.
I hope you like it.
Thanks again to Risser for the original version.
popper.
Still, there were some things I was missing here. Over the time, I've made some modifications and added some features to it. These are:
- added possibility to boost songs that have been added to the DB recently (like a radio station would play new songs more often)
- added possibility to define the genres that should be played (so that classic and pop won't be mixed)
- redesign of the mechanism that selects the actual songs to be played (because I did not really understand what was going on in the existing algorithm

- now it is possible to influence more easily what songs you will hear (e.g. 30% of songs with a weighting of 8-10 and 70% with a weighting of 5-7).
You will probably have to do some tweaking of the parameters, the ones below are giving me sensible results.
My programming skills are not very good and I don't really know VBScript, but by looking closely at the code and a lot of debugging I found out what was going on and was able to make the modifications I wanted. I learned a lot from this already, but would be glad if one of you Real Programmers(tm) out there could do some kind of code review for me to point out what could be done better.
For installation, just copy the code below into a text file, name it RadioFreeMonkey.vbs and put it in your [...]\MediaMonkey\Scripts\Auto directory. Make your modifications to the parameters at the top of the script. They all have comments so it should be easy to work out what their intention is.
Then save the script and restart MediaMonkey.
I hope you like it.
Thanks again to Risser for the original version.
popper.
Code: Select all
' RadioFreeMonkey
' Version 1.5
' A script to create a "radio station" for you based on your song ratings.
' #####################
' Version 1.2 by Risser
' This script creates a root node in the tree, beneath the library node, called "RadioFreeMonkey".
' Under this node, you have a Radio List node, a Done node and a Weightings node.
' - The Radio List node lists 20 songs, in random order. These are weighted, so songs
' with a higher weighting have a higher chance of getting selected.
' - If you have selected a sorting column, even though the songs are selected randomly, the
' radio list will be sorted based on that column. To return the list to "random" sorting, click
' on any playlist (Now Playing works nicely). When you visit the Radio List node, the songs will
' be unsorted, in true random order.
' - The Done list shows you which songs aren't going to be played. This includes songs that have
' passed their maximum playcount, or anything within MinDaysRepeat.
' - The Weighting node shows all tunes, sorted by their calculated weights. This can help you
' determine what the optimal weighting choices are for you. Plus, it's just nice to see what's
' more likely to be played.
' Songs are weighted as follows:
' Rating * 2 (5 stars = 10, 3.5 stars = 7, 0 stars = 0)
' - Number of Plays (if Reduce if Played is TRUE)
' + Days since added to library / DayFactor (always rounded down)
' However, the weighting can't be more than the original rating (* 2).
' TO DO: At some point, I'd like to make the main 'list' node a PlayList instead of a regular
' list of tracks, but I'm not sure how to do this.
'
' This script can be freely used and modified.
' This is an early release and there may be bugs. If it's causing you problems, simply delete
' it or move it out of the scripts\auto folder.
'
' The script does not modify the database, registry or INI file.
' #####################
' Version 1.5 by popper
' - added possibility to boost songs that have been added to the DB recently
' - added possibility to define the genres that should be played
' - redesign of the mechanism that selects the actual songs to be played (because I did not
' really understand what was going on in the existing algorithm ;-), with the goal of making
' it easier to influence what will be played (e.g. 30% of songs with a weighting of 8-10 and
' 70% with a weighting of 5-7))
'
' Disclaimer:
' Use at your own risk. Back up your data before using.
' Changing some of the values might lead to endless loops that can only be solved by
' killing the MediaMonkey process, so take care when modifying anything!
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' Global Variables and Declarations
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Option Explicit
' %%% The caption for the root node.
Const RootNodeCaption = "RadioFreeMonkey 1.5"
' %%% Add 1 to weighting for each X days since added to library
Const DayFactor = 150
' %%% Anything rated this or below will not receive the 'date boost'
Const DateBoostCutoff = 1.5
' %%% The minimum number of days that must pass before a song is repeated. Zero means, go ahead
' and repeat it right away.
Const MinDaysRepeat = 5
' %%% Reduce the weighting by the number of times played. This means, as songs are played more often,
' they are less likely to be played again
Const ReduceIfPlayed = True
' %%% Boost Songs that have been added during the last n days (MinDaysRepeat does still apply for these)
Const BoostNewSongsDays = 75
' %%% The boost modifier for new songs (Zero means, don't boost):
' Add x to the weighting of these songs
Const BoostNewSongsModifier = 3
' %%% Anything rated this or below will not receive the 'new song boost'
Const BoostNewSongsCutoff = 2.0
' %%% Genres that should be played. Leave empty (Const PlayGenres = "") to ignore.
' "-1, 13, 17, 20, 24" means "empty genre field, Pop, Rock, Alternative, Soundtrack"
' Look up the other genre Ids in the database by using MS Access
Const PlayGenres = "-1, 13, 17, 20, 24"
' %%% Number of Songs in list
Const NumberOfSongs = 20
' %%% Influence which songs will be played. You have three sets that you can use
' Example:
' Const Set1Weight = 8.5 ' Basic weight for this set is 8.5
' Const Set1Boundary = 1.5 ' with a variation of 1.5, which means that songs between 7 and 10 will be chosen
' Const Set1Percentage = 60 ' this is how big the part of this set should be in the overall contents
Const Set1Weight = 8.5
Const Set1Boundary = 1.5
Const Set1Percentage = 60 ' make sure all 3 percentages sum up to 100!
Const Set2Weight = 6.0
Const Set2Boundary = 1
Const Set2Percentage = 40 ' make sure all 3 percentages sum up to 100!
Const Set3Weight = 2.0 ' Right now, it is not possible to choose songs with a weight < 1, so unfortunately you cannot add unrated songs
Const Set3Boundary = 1
'Const Set3Percentage = 20 ' This is not needed because it just takes what is missing to 100%
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' The Meat. Don't change anything under here.
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' the text of the formulas to be used throughout
Dim weightedRatingFormula, dateBoostFormula, ratingFormula, weightedPlayCountFormula, boostNewSongsFormula
If BoostNewSongsModifier <> 0 Then
dateBoostFormula = "IIF(Songs.rating <= " & (DateBoostCutoff*20) & " OR FIX(DateDiff('d',Songs.DateAdded,Now) <= " & BoostNewSongsDays & "), 0, FIX(DateDiff('d',Songs.DateAdded,Now) / " & DayFactor & "))"
Else
dateBoostFormula = "IIF(Songs.rating <= " & (DateBoostCutoff*20) & ", 0, FIX(DateDiff('d',Songs.DateAdded,Now) / " & DayFactor & "))"
End If
ratingFormula = "(IIF(Songs.rating < 0, 0, Songs.rating) / 10)"
If ReduceIfPlayed Then
weightedPlayCountFormula = "(Songs.PlayCounter - " & dateBoostFormula & ")"
Else
weightedPlayCountFormula = "0"
End If
If BoostNewSongsModifier <> 0 Then
boostNewSongsFormula = "IIF(Songs.rating <= " & (BoostNewSongsCutoff*20) & ", 0, (IIF(FIX(DateDiff('d',Songs.DateAdded,Now) > " & BoostNewSongsDays & "), 0, " & BoostNewSongsModifier & ")))"
End If
weightedRatingFormula = ratingFormula & " - IIF(" & weightedPlayCountFormula & " < 0, 0, " & weightedPlayCountFormula & ") + " & boostNewSongsFormula
Sub FillStandardProperties(parentNode, childNode)
With childNode
.CustomNodeId = parentNode.CustomNodeId
.CustomDataId = parentNode.CustomDataId + 1
.UseScript = Script.ScriptPath
End With
End Sub
Sub FillGoodLeaf(Node)
Randomize
Dim Tracks
Dim SQLStatement
Dim SELECT_Clause, FROM_Clause, WHERE_Clause, ORDER_Clause
Dim Iter, res, i, total, sum, index
Dim weight, minweight, maxweight
Dim hold, weights, average, start, baseweight, boundary
Dim R, max : max = NumberOfSongs
Dim inStr : inStr = ""
Set hold = CreateObject("Scripting.Dictionary") ' define an associative array or "hash array" to hold the songs
Set weights = CreateObject("Scripting.Dictionary") ' define an associative array or "hash array" to hold the weightings
SELECT_Clause = " SELECT Songs.Id, " & weightedRatingFormula & " "
FROM_Clause = " FROM Songs "
WHERE_Clause = " WHERE " & weightedPlayCountFormula &" < " & ratingFormula & " AND DateDiff('d',Songs.LastTimePlayed, Now) > " & MinDaysRepeat
If PlayGenres <> "" then
WHERE_Clause = WHERE_Clause & " AND Songs.Genre IN (" & PlayGenres & ")"
End If
ORDER_Clause = " ORDER BY Rnd((1000*Songs.ID)*Now())" ' This is important because otherwise they will all come as they are stored in the DB
SQLStatement = SELECT_Clause & FROM_Clause & WHERE_Clause & ORDER_Clause
'res = InputBox("SQL Statement: ", "debugging", SQLStatement) ' For debugging
Set Iter = SDB.Database.OpenSQL(SQLStatement) ' holds all songs that are collected by the SQL formula
total=0 ' initialise total amount of songs that can be used for the playlist (=all songs with weights from 1 to 10 minus "done" songs)
sum = 0 ' initialise sum of all weightings
maxweight = 0 ' initialise maximum weight in this iteration
minweight = 9 ' initialise minimum weight in this iteration
While (Not Iter.EOF)
hold.add total,Iter.StringByIndex(0)
weight = Iter.StringByIndex(1)
weights.add total,weight
total = total + 1
sum = sum + weight
If minweight > cdbl(weight) then
minweight = cdbl(weight)
End if
if maxweight < cdbl(weight) then
maxweight = cdbl(weight)
End if
Iter.Next
Wend
' some overflow checks
if total <= 0 or sum <= 0 then
SDB.MessageBox "Something is wrong with your ratings. Do you have any rated songs at all?", mtInformation, Array(mbOk)
Exit Sub
end if
If Set1Weight < minweight or Set2Weight < minweight or Set3Weight < minweight or Set1Weight > maxweight or Set2Weight > maxweight or Set3Weight > maxweight then
SDB.MessageBox "Something is wrong with your SetXWeight constants. They are either too high or too low.", mtInformation, Array(mbOk)
Exit Sub
end if
If (total < max) Then
max = total
End If
Set Tracks = SDB.MainTracksWindow
start = 0
While i < max ' do this until the max number of songs for the playlist is reached
If i < max * Set1Percentage / 100 then ' put in Set1Percentage percent of higher-rated songs
baseweight = Set1Weight
boundary = Set1Boundary
elseif i < max * (Set1Percentage + Set2Percentage) / 100 then ' put in Set2Percentage percent of lower-rated songs
baseweight = Set2Weight
boundary = Set2Boundary
elseif i <= max then ' and also put in some really lowly-rated songs
baseweight = Set3Weight
boundary = Set3Boundary
end if
For index = start to total ' go through all of the songs that could potentially become part of the radio node, starting where we left off last time
weight = cdbl(weights.item(index)) ' weight of the song that is being looked at right now
if weight >= (baseweight - boundary) and weight <= (baseweight + boundary) then
' This song is inside the weighting borders that are defined above
' so let's see if it is also lucky
R = rnd(1) ' this song's lucky number
if R > 0.8 then
' so let's leave this for-next loop and add it to the list!
Exit For
end if
end if
Next
start = index + 1 ' We don't want to go through the same songs again, so next time we start from here
if start > total then
if inStr = "" then
SDB.MessageBox "There does not seem to be enough stuff to work on. Try to use criteria that are easier to meet.", mtInformation, Array(mbOk)
Exit Sub
else
start = 0 ' start over again
end if
else
' We've got a winner! The song with the hash index "index" will now be added to the radio node
' This is done by collecting all songs in inStr, separating them by commas, and afterwards collecting their song.ids via SQL
If i = 0 Then
inStr = index
Else
inStr = inStr & ", " & hold.item(index)
End If
i = i + 1
end if
Wend
If inStr <> "" then
Tracks.AddTracksFromQuery("AND Songs.ID IN (" & inStr & ") ORDER BY Rnd((1000*Songs.ID)*Now())")
Tracks.FinishAdding
End If
End Sub
Sub FillWeightNode(Node)
Dim Tree, newNode
Dim SQLStatement ' SQL query to the database
Dim Iter ' SDBD Iterator obtained by running the SQL query to get the nodes
Set Tree = SDB.MainTree
Node.HasChildren = false ' To delete all old children
SQLStatement = "SELECT DISTINCT " & weightedRatingFormula & " from Songs "
'res = InputBox("SQL Statement: ", SQLStatement, SQLStatement) ' For debugging
Set Iter = SDB.Database.OpenSQL(SQLStatement)
While Not Iter.EOF
Set newNode = Tree.CreateNode
NewNode.Caption = Iter.StringByIndex(0)
NewNode.iconIndex = 32
newNode.CustomData = Iter.StringByIndex(0)
FillStandardProperties node,newNode
newNode.onFillTracksFunct = "FillWeightLeaf"
newNode.hasChildren = False
Tree.AddNode Node, NewNode, 3
Iter.Next
Wend
End Sub
Sub FillWeightLeaf(Node)
Dim Weight
Dim Tracks
Dim SELECT_Clause, FROM_Clause, WHERE_Clause
Weight = Node.CustomData
SELECT_Clause = " SELECT Songs.Id "
FROM_Clause = " FROM Songs "
WHERE_Clause = " WHERE " & weightedRatingFormula & " = " & Weight & " AND DateDiff('d',Songs.LastTimePlayed, Now) > " & MinDaysRepeat
If PlayGenres <> "" then
WHERE_Clause = WHERE_Clause & " AND Songs.Genre IN (" & PlayGenres & ")"
End If
'res = InputBox("SQL Statement: ", "Debugging", SELECT_Clause & FROM_Clause & WHERE_Clause) ' For debugging
Set Tracks = SDB.MainTracksWindow
Tracks.AddTracksFromQuery("AND Songs.ID IN (" & SELECT_Clause & FROM_Clause & WHERE_Clause & ")")
Tracks.FinishAdding
End Sub
Sub FillDoneLeaf(Node)
Dim Tracks
Dim SELECT_Clause, FROM_Clause, WHERE_Clause
SELECT_Clause = " SELECT Songs.Id "
FROM_Clause = " FROM Songs "
WHERE_Clause = " WHERE "& weightedPlayCountFormula &" >= " & ratingFormula & " OR DateDiff('d',Songs.LastTimePlayed, Now) <= " & MinDaysRepeat
If PlayGenres <> "" then
WHERE_Clause = WHERE_Clause & " OR Songs.Genre NOT IN (" & PlayGenres & ")"
End If
Set Tracks = SDB.MainTracksWindow
Tracks.AddTracksFromQuery("AND Songs.ID IN (" & SELECT_Clause & FROM_Clause & WHERE_Clause & ")")
Tracks.FinishAdding
End Sub
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' Startup Function
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Sub onStartUp
Dim Tree, RadioFreeMonkeyRoot
Dim RadioMonkeyGood, RadioMonkeyBad, RadioMonkeyWeight
Set Tree = Sdb.MainTree
Set RadioFreeMonkeyRoot = Tree.createNode
RadioFreeMonkeyRoot.Caption = RootNodeCaption
RadioFreeMonkeyRoot.IconIndex = 14
RadioFreeMonkeyRoot.UseScript = Script.ScriptPath
RadioFreeMonkeyRoot.hasChildren = True
Tree.AddNode Tree.Node_Library, RadioFreeMonkeyRoot, 1
SDB.Objects("RadioFreeMonkeyRoot") = RadioFreeMonkeyRoot
Set RadioMonkeyGood = Tree.createNode
RadioMonkeyGood.Caption = "Radio List"
RadioMonkeyGood.IconIndex = 14
RadioMonkeyGood.UseScript = Script.ScriptPath
RadioMonkeyGood.hasChildren = False
RadioMonkeyGood.onFillTracksFunct = "FillGoodLeaf"
Tree.AddNode RadioFreeMonkeyRoot, RadioMonkeyGood, 2
SDB.Objects("RadioMonkeyGood") = RadioMonkeyGood
Set RadioMonkeyWeight = Tree.createNode
RadioMonkeyWeight.Caption = "Weightings"
RadioMonkeyWeight.IconIndex = 32
RadioMonkeyWeight.UseScript = Script.ScriptPath
RadioMonkeyWeight.hasChildren = True
RadioMonkeyWeight.onFillTracksFunct = "FillWeightNode"
Tree.AddNode RadioFreeMonkeyRoot, RadioMonkeyWeight, 3
SDB.Objects("RadioMonkeyWeight") = RadioMonkeyWeight
Set RadioMonkeyBad = Tree.createNode
RadioMonkeyBad.Caption = "Done"
RadioMonkeyBad.IconIndex = 15
RadioMonkeyBad.UseScript = Script.ScriptPath
RadioMonkeyBad.hasChildren = False
RadioMonkeyBad.onFillTracksFunct = "FillDoneLeaf"
Tree.AddNode RadioFreeMonkeyRoot, RadioMonkeyBad, 3
SDB.Objects("RadioMonkeyBad") = RadioMonkeyBad
End Sub
Hmm. Several possibilities come to mind:
- Perhaps you have to play around with the settings at the beginning of the script ("Global Variables and Declarations"), especially the part where the Set1Weight etc. are set. This has to match the ratings that you have given your songs.
- How many songs do you have in your library?
- What happens when you open the node "Weightings" oder "done"?
- Perhaps you use different genres than I did? Then you should in a first step disable the genres by setting the following: Const PlayGenres = ""
Good luck & Let me know how it goes!
popper
- Perhaps you have to play around with the settings at the beginning of the script ("Global Variables and Declarations"), especially the part where the Set1Weight etc. are set. This has to match the ratings that you have given your songs.
- How many songs do you have in your library?
- What happens when you open the node "Weightings" oder "done"?
- Perhaps you use different genres than I did? Then you should in a first step disable the genres by setting the following: Const PlayGenres = ""
Good luck & Let me know how it goes!
popper
Thanks for the excellent script, risser & popper!
I'm using popper's version, but I think I've come across a small bug: BoostNewSongsCutoff is defined but never actually used. I'm no programmer, but I'll bet that
should actually read
I'm using popper's version, but I think I've come across a small bug: BoostNewSongsCutoff is defined but never actually used. I'm no programmer, but I'll bet that
Code: Select all
If BoostNewSongsModifier <> 0 Then
dateBoostFormula = "IIF(Songs.rating <= " & (DateBoostCutoff*20) & " OR FIX(DateDiff('d',Songs.DateAdded,Now) <= " & BoostNewSongsDays & "), 0, FIX(DateDiff('d',Songs.DateAdded,Now) / " & DayFactor & "))"
Else
dateBoostFormula = "IIF(Songs.rating <= " & (DateBoostCutoff*20) & ", 0, FIX(DateDiff('d',Songs.DateAdded,Now) / " & DayFactor & "))"
End If
Code: Select all
If BoostNewSongsModifier <> 0 Then
dateBoostFormula = "IIF(Songs.rating <= " & (BoostNewSongsCutoff *20) & " OR FIX(DateDiff('d',Songs.DateAdded,Now) <= " & BoostNewSongsDays & "), 0, FIX(DateDiff('d',Songs.DateAdded,Now) / " & DayFactor & "))"
Else
dateBoostFormula = "IIF(Songs.rating <= " & (DateBoostCutoff*20) & ", 0, FIX(DateDiff('d',Songs.DateAdded,Now) / " & DayFactor & "))"
End If
Some Fixes
Hi !
This script is exactly what i was looking for... Thank you very much.
I picked the last script from popper, and saw a couple of errors that i corrected.
1. There was always a song that had nothing to do in my playlists, investigated and saw the error in FillGoodLeaf :
If i=0, then It should be hold.item(index), instead of index, so the first song that was added was not good, we were taking the index, instead of the real song.id
2. There is an error in the calculation of the dateboost formula, the dateboostcutoff*20 should be dateboostcutoff*10. There were a couple of places wrong
3. By adding, the BoostNewSongsModifier, the actual weight calculated, could go higher than 10, but the algorithm was never picking songs with a weight higher than 10. So, i fixed a couple of places in the script, so that calculated weight higher than 10, are now fixed at 10.
4. There was a bug, when the BoostNewSongsModifier is set to 0... fixed it.
I paste here the complete fixed script :
[/code]
This script is exactly what i was looking for... Thank you very much.
I picked the last script from popper, and saw a couple of errors that i corrected.
1. There was always a song that had nothing to do in my playlists, investigated and saw the error in FillGoodLeaf :
Code: Select all
' We've got a winner! The song with the hash index "index" will now be added to the radio node
' This is done by collecting all songs in inStr, separating them by commas, and afterwards collecting their song.ids via SQL
If i = 0 Then
inStr = index
Else
inStr = inStr & ", " & hold.item(index)
End If
i = i + 1
2. There is an error in the calculation of the dateboost formula, the dateboostcutoff*20 should be dateboostcutoff*10. There were a couple of places wrong
Code: Select all
If BoostNewSongsModifier <> 0 Then
dateBoostFormula = "IIF(Songs.rating <= " & (DateBoostCutoff*20) & " OR FIX(DateDiff('d',Songs.DateAdded,Now) <= " & BoostNewSongsDays & "), 0, FIX(DateDiff('d',Songs.DateAdded,Now) / " & DayFactor & "))"
Else
dateBoostFormula = "IIF(Songs.rating <= " & (DateBoostCutoff*20) & ", 0, FIX(DateDiff('d',Songs.DateAdded,Now) / " & DayFactor & "))"
End If
4. There was a bug, when the BoostNewSongsModifier is set to 0... fixed it.
I paste here the complete fixed script :
Code: Select all
' RadioFreeMonkey
' Version 1.6
' A script to create a "radio station" for you based on your song ratings.
' #####################
' Version 1.2 by Risser
' This script creates a root node in the tree, beneath the library node, called "RadioFreeMonkey".
' Under this node, you have a Radio List node, a Done node and a Weightings node.
' - The Radio List node lists 20 songs, in random order. These are weighted, so songs
' with a higher weighting have a higher chance of getting selected.
' - If you have selected a sorting column, even though the songs are selected randomly, the
' radio list will be sorted based on that column. To return the list to "random" sorting, click
' on any playlist (Now Playing works nicely). When you visit the Radio List node, the songs will
' be unsorted, in true random order.
' - The Done list shows you which songs aren't going to be played. This includes songs that have
' passed their maximum playcount, or anything within MinDaysRepeat.
' - The Weighting node shows all tunes, sorted by their calculated weights. This can help you
' determine what the optimal weighting choices are for you. Plus, it's just nice to see what's
' more likely to be played.
' Songs are weighted as follows:
' Rating * 2 (5 stars = 10, 3.5 stars = 7, 0 stars = 0)
' - Number of Plays (if Reduce if Played is TRUE)
' + Days since added to library / DayFactor (always rounded down)
' However, the weighting can't be more than the original rating (* 2).
' TO DO: At some point, I'd like to make the main 'list' node a PlayList instead of a regular
' list of tracks, but I'm not sure how to do this.
'
' This script can be freely used and modified.
' This is an early release and there may be bugs. If it's causing you problems, simply delete
' it or move it out of the scripts\auto folder.
'
' The script does not modify the database, registry or INI file.
' #####################
' Version 1.5 by popper
' - added possibility to boost songs that have been added to the DB recently
' - added possibility to define the genres that should be played
' - redesign of the mechanism that selects the actual songs to be played (because I did not
' really understand what was going on in the existing algorithm ;-), with the goal of making
' it easier to influence what will be played (e.g. 30% of songs with a weighting of 8-10 and
' 70% with a weighting of 5-7))
' #####################
' Version 1.6 by ElGringo
' - Fixes to the algorithm
' - 1. There was always a song that had nothing to do in my playlists, investigated
' and saw the error in FillGoodLeaf
' - 2. There is an error in the calculation of the dateboost formula, the
' dateboostcutoff*20 should be dateboostcutoff*10. There were a couple of places wrong
' - 3. By adding, the BoostNewSongsModifier, the actual weight calculated, could go higher
' than 10, but the algorithm was never picking songs with a weight higher than 10. So,
' i fixed a couple of places in the script, so that calculated weight higher than 10,
' are now fixed at 10.
' - 4.There was a bug, when the BoostNewSongsModifier is set to 0... fixed it.
'
'
' Disclaimer:
' Use at your own risk. Back up your data before using.
' Changing some of the values might lead to endless loops that can only be solved by
' killing the MediaMonkey process, so take care when modifying anything!
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' Global Variables and Declarations
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Option Explicit
' %%% The caption for the root node.
Const RootNodeCaption = "Radio Rock2"
' %%% Add 1 to weighting for each X days since added to library
Const DayFactor = 150
' %%% Anything rated this or below will not receive the 'date boost'
Const DateBoostCutoff = 1.5
' %%% The minimum number of days that must pass before a song is repeated. Zero means, go ahead
' and repeat it right away.
Const MinDaysRepeat = 5
' %%% Reduce the weighting by the number of times played. This means, as songs are played more often,
' they are less likely to be played again
Const ReduceIfPlayed = True
' %%% Boost Songs that have been added during the last n days (MinDaysRepeat does still apply for these)
Const BoostNewSongsDays = 5
' %%% The boost modifier for new songs (Zero means, don't boost):
' Add x to the weighting of these songs
Const BoostNewSongsModifier = 3
' %%% Anything rated this or below will not receive the 'new song boost'
Const BoostNewSongsCutoff = 2.0
' %%% Genres that should not be played. Leave empty (Const PlayGenres = "") to ignore.
' Look up the other genre Ids in the database by using MS Access
Const PlayGenres = "-1, 13, 17, 20, 24"
' %%% Number of Songs in list
Const NumberOfSongs = 20
' %%% Influence which songs will be played. You have three sets that you can use
' Example:
' Const Set1Weight = 8.5 ' Basic weight for this set is 8.5
' Const Set1Boundary = 1.5 ' with a variation of 1.5, which means that songs between 7 and 10 will be chosen
' Const Set1Percentage = 60 ' this is how big the part of this set should be in the overall contents
Const Set1Weight = 8.5
Const Set1Boundary = 1.5
Const Set1Percentage = 60 ' make sure all 3 percentages sum up to 100!
Const Set2Weight = 6.0
Const Set2Boundary = 1
Const Set2Percentage = 40 ' make sure all 3 percentages sum up to 100!
Const Set3Weight = 2.0 ' Right now, it is not possible to choose songs with a weight < 1, so unfortunately you cannot add unrated songs
Const Set3Boundary = 1
'Const Set3Percentage = 20 ' This is not needed because it just takes what is missing to 100%
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' The Meat. Don't change anything under here.
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' the text of the formulas to be used throughout
Dim weightedRatingFormula, dateBoostFormula, ratingFormula, weightedPlayCountFormula, boostNewSongsFormula
If BoostNewSongsModifier <> 0 Then
dateBoostFormula = "IIF(Songs.rating <= " & (BoostNewSongsCutoff *10) & " OR FIX(DateDiff('d',Songs.DateAdded,Now) <= " & BoostNewSongsDays & "), 0, FIX(DateDiff('d',Songs.DateAdded,Now) / " & DayFactor & "))"
Else
dateBoostFormula = "IIF(Songs.rating <= " & (DateBoostCutoff*10) & ", 0, FIX(DateDiff('d',Songs.DateAdded,Now) / " & DayFactor & "))"
End If
ratingFormula = "(IIF(Songs.rating < 0, 0, Songs.rating) / 10)"
If ReduceIfPlayed Then
weightedPlayCountFormula = "(Songs.PlayCounter - " & dateBoostFormula & ")"
Else
weightedPlayCountFormula = "0"
End If
If BoostNewSongsModifier <> 0 Then
boostNewSongsFormula = "IIF(Songs.rating <= " & (BoostNewSongsCutoff*10) & ", 0, (IIF(FIX(DateDiff('d',Songs.DateAdded,Now) > " & BoostNewSongsDays & "), 0, " & BoostNewSongsModifier & ")))"
End If
weightedRatingFormula = ratingFormula & " - IIF(" & weightedPlayCountFormula & " < 0, 0, " & weightedPlayCountFormula & ") "
if boostNewSongsFormula <> "" then weightedRatingFormula = weightedRatingFormula & "+ " & boostNewSongsFormula
Sub FillStandardProperties(parentNode, childNode)
With childNode
.CustomNodeId = parentNode.CustomNodeId
.CustomDataId = parentNode.CustomDataId + 1
.UseScript = Script.ScriptPath
End With
End Sub
Sub FillGoodLeaf(Node)
Randomize
Dim Tracks
Dim SQLStatement
Dim SELECT_Clause, FROM_Clause, WHERE_Clause, ORDER_Clause
Dim Iter, res, i, total, sum, index
Dim weight, minweight, maxweight
Dim hold, weights, average, start, baseweight, boundary
Dim R, max : max = NumberOfSongs
Dim inStr : inStr = ""
Set hold = CreateObject("Scripting.Dictionary") ' define an associative array or "hash array" to hold the songs
Set weights = CreateObject("Scripting.Dictionary") ' define an associative array or "hash array" to hold the weightings
SELECT_Clause = " SELECT Songs.Id, IIF(" & weightedRatingFormula & ">10,10," & weightedRatingFormula & ") "
FROM_Clause = " FROM Songs "
WHERE_Clause = " WHERE " & weightedPlayCountFormula &" < " & ratingFormula & " AND DateDiff('d',Songs.LastTimePlayed, Now) > " & MinDaysRepeat
If PlayGenres <> "" then
WHERE_Clause = WHERE_Clause & " AND Songs.Genre IN (" & PlayGenres & ")"
End If
ORDER_Clause = " ORDER BY Rnd((1000*Songs.ID)*Now())" ' This is important because otherwise they will all come as they are stored in the DB
SQLStatement = SELECT_Clause & FROM_Clause & WHERE_Clause & ORDER_Clause
'res = InputBox("SQL Statement: ", "debugging", SQLStatement) ' For debugging
Set Iter = SDB.Database.OpenSQL(SQLStatement) ' holds all songs that are collected by the SQL formula
total=0 ' initialise total amount of songs that can be used for the playlist (=all songs with weights from 1 to 10 minus "done" songs)
sum = 0 ' initialise sum of all weightings
maxweight = 0 ' initialise maximum weight in this iteration
minweight = 9 ' initialise minimum weight in this iteration
While (Not Iter.EOF)
hold.add total,Iter.StringByIndex(0)
weight = Iter.StringByIndex(1)
weights.add total,weight
total = total + 1
sum = sum + weight
If minweight > cdbl(weight) then
minweight = cdbl(weight)
End if
if maxweight < cdbl(weight) then
maxweight = cdbl(weight)
End if
Iter.Next
Wend
' some overflow checks
if total <= 0 or sum <= 0 then
SDB.MessageBox "Something is wrong with your ratings. Do you have any rated songs at all?", mtInformation, Array(mbOk)
Exit Sub
end if
If Set1Weight < minweight or Set2Weight < minweight or Set3Weight < minweight or Set1Weight > maxweight or Set2Weight > maxweight or Set3Weight > maxweight then
SDB.MessageBox "Something is wrong with your SetXWeight constants. They are either too high or too low.", mtInformation, Array(mbOk)
Exit Sub
end if
If (total < max) Then
max = total
End If
Set Tracks = SDB.MainTracksWindow
start = 0
While i < max ' do this until the max number of songs for the playlist is reached
If i < max * Set1Percentage / 100 then ' put in Set1Percentage percent of higher-rated songs
baseweight = Set1Weight
boundary = Set1Boundary
elseif i < max * (Set1Percentage + Set2Percentage) / 100 then ' put in Set2Percentage percent of lower-rated songs
baseweight = Set2Weight
boundary = Set2Boundary
elseif i <= max then ' and also put in some really lowly-rated songs
baseweight = Set3Weight
boundary = Set3Boundary
end if
For index = start to total ' go through all of the songs that could potentially become part of the radio node, starting where we left off last time
weight = cdbl(weights.item(index)) ' weight of the song that is being looked at right now
if weight >= (baseweight - boundary) and weight <= (baseweight + boundary) then
' This song is inside the weighting borders that are defined above
' so let's see if it is also lucky
R = rnd(1) ' this song's lucky number
if R > 0.8 then
' so let's leave this for-next loop and add it to the list!
Exit For
end if
end if
Next
start = index + 1 ' We don't want to go through the same songs again, so next time we start from here
if start > total then
if inStr = "" then
SDB.MessageBox "There does not seem to be enough stuff to work on. Try to use criteria that are easier to meet.", mtInformation, Array(mbOk)
Exit Sub
else
start = 0 ' start over again
end if
else
' We've got a winner! The song with the hash index "index" will now be added to the radio node
' This is done by collecting all songs in inStr, separating them by commas, and afterwards collecting their song.ids via SQL
If i = 0 Then
inStr = hold.item(index)
Else
inStr = inStr & ", " & hold.item(index)
End If
i = i + 1
end if
Wend
If inStr <> "" then
Tracks.AddTracksFromQuery("AND Songs.ID IN (" & inStr & ") ORDER BY Rnd((1000*Songs.ID)*Now())")
Tracks.FinishAdding
End If
End Sub
Sub FillWeightNode(Node)
Dim Tree, newNode
Dim SQLStatement ' SQL query to the database
Dim Iter ' SDBD Iterator obtained by running the SQL query to get the nodes
Set Tree = SDB.MainTree
Node.HasChildren = false ' To delete all old children
SQLStatement = "SELECT DISTINCT IIF(" & weightedRatingFormula & ">10,10," & weightedRatingFormula & ") from Songs "
'res = InputBox("SQL Statement: ", SQLStatement, SQLStatement) ' For debugging
Set Iter = SDB.Database.OpenSQL(SQLStatement)
While Not Iter.EOF
Set newNode = Tree.CreateNode
NewNode.Caption = Iter.StringByIndex(0)
NewNode.iconIndex = 32
newNode.CustomData = Iter.StringByIndex(0)
FillStandardProperties node,newNode
newNode.onFillTracksFunct = "FillWeightLeaf"
newNode.hasChildren = False
Tree.AddNode Node, NewNode, 3
Iter.Next
Wend
End Sub
Sub FillWeightLeaf(Node)
Dim Weight
Dim Tracks
Dim SELECT_Clause, FROM_Clause, WHERE_Clause
Weight = Node.CustomData
SELECT_Clause = " SELECT Songs.Id "
FROM_Clause = " FROM Songs "
WHERE_Clause = " WHERE IIF(" & weightedRatingFormula & ">10,10," & weightedRatingFormula & ") = " & Weight & " AND DateDiff('d',Songs.LastTimePlayed, Now) > " & MinDaysRepeat
If PlayGenres <> "" then
WHERE_Clause = WHERE_Clause & " AND Songs.Genre IN (" & PlayGenres & ")"
End If
'res = InputBox("SQL Statement: ", "Debugging", SELECT_Clause & FROM_Clause & WHERE_Clause) ' For debugging
Set Tracks = SDB.MainTracksWindow
Tracks.AddTracksFromQuery("AND Songs.ID IN (" & SELECT_Clause & FROM_Clause & WHERE_Clause & ")")
Tracks.FinishAdding
End Sub
Sub FillDoneLeaf(Node)
Dim Tracks
Dim SELECT_Clause, FROM_Clause, WHERE_Clause
SELECT_Clause = " SELECT Songs.Id "
FROM_Clause = " FROM Songs "
WHERE_Clause = " WHERE "& weightedPlayCountFormula &" >= " & ratingFormula & " OR DateDiff('d',Songs.LastTimePlayed, Now) <= " & MinDaysRepeat
If PlayGenres <> "" then
WHERE_Clause = WHERE_Clause & " OR Songs.Genre NOT IN (" & PlayGenres & ")"
End If
Set Tracks = SDB.MainTracksWindow
Tracks.AddTracksFromQuery("AND Songs.ID IN (" & SELECT_Clause & FROM_Clause & WHERE_Clause & ")")
Tracks.FinishAdding
End Sub
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
' Startup Function
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Sub onStartUp
Dim Tree, RadioFreeMonkeyRoot
Dim RadioMonkeyGood, RadioMonkeyBad, RadioMonkeyWeight
Set Tree = Sdb.MainTree
Set RadioFreeMonkeyRoot = Tree.createNode
RadioFreeMonkeyRoot.Caption = RootNodeCaption
RadioFreeMonkeyRoot.IconIndex = 14
RadioFreeMonkeyRoot.UseScript = Script.ScriptPath
RadioFreeMonkeyRoot.hasChildren = True
Tree.AddNode Tree.Node_Library, RadioFreeMonkeyRoot, 1
SDB.Objects("RadioFreeMonkeyRoot") = RadioFreeMonkeyRoot
Set RadioMonkeyGood = Tree.createNode
RadioMonkeyGood.Caption = "Radio List"
RadioMonkeyGood.IconIndex = 14
RadioMonkeyGood.UseScript = Script.ScriptPath
RadioMonkeyGood.hasChildren = False
RadioMonkeyGood.onFillTracksFunct = "FillGoodLeaf"
Tree.AddNode RadioFreeMonkeyRoot, RadioMonkeyGood, 2
SDB.Objects("RadioMonkeyGood") = RadioMonkeyGood
Set RadioMonkeyWeight = Tree.createNode
RadioMonkeyWeight.Caption = "Weightings"
RadioMonkeyWeight.IconIndex = 32
RadioMonkeyWeight.UseScript = Script.ScriptPath
RadioMonkeyWeight.hasChildren = True
RadioMonkeyWeight.onFillTracksFunct = "FillWeightNode"
Tree.AddNode RadioFreeMonkeyRoot, RadioMonkeyWeight, 3
SDB.Objects("RadioMonkeyWeight") = RadioMonkeyWeight
Set RadioMonkeyBad = Tree.createNode
RadioMonkeyBad.Caption = "Done"
RadioMonkeyBad.IconIndex = 15
RadioMonkeyBad.UseScript = Script.ScriptPath
RadioMonkeyBad.hasChildren = False
RadioMonkeyBad.onFillTracksFunct = "FillDoneLeaf"
Tree.AddNode RadioFreeMonkeyRoot, RadioMonkeyBad, 3
SDB.Objects("RadioMonkeyBad") = RadioMonkeyBad
End Sub
Re: Some Fixes
Hi ElGringo,
thank you for pointing out my mistakes and correcting them. I was waiting all the time for someone to take a closer look at the code, because I knew that it was probably not perfect.
I have got some comments to your improvements.
I am already looking forward to using the new version of this script...
cheers,
popper.
thank you for pointing out my mistakes and correcting them. I was waiting all the time for someone to take a closer look at the code, because I knew that it was probably not perfect.
I have got some comments to your improvements.
Yes, of course you are right. Silly mistake!ElGringo wrote:Hi !
This script is exactly what i was looking for... Thank you very much.
I picked the last script from popper, and saw a couple of errors that i corrected.
1. There was always a song that had nothing to do in my playlists, investigated and saw the error in FillGoodLeaf :
(...)
If i=0, then It should be hold.item(index), instead of index, so the first song that was added was not good, we were taking the index, instead of the real song.id
I am not so sure about this one. Please correct me if I am wrong, but the ratings are stored in the database using numbers from 0 (or -1) to 100. So to calculate the number of stars from a rating in the database, you have to divide by 20, and not by ten.ElGringo wrote:2. There is an error in the calculation of the dateboost formula, the dateboostcutoff*20 should be dateboostcutoff*10. There were a couple of places wrong
Yes, these are very valid points. Thanks for correcting them.ElGringo wrote:3. By adding, the BoostNewSongsModifier, the actual weight calculated, could go higher than 10, but the algorithm was never picking songs with a weight higher than 10. So, i fixed a couple of places in the script, so that calculated weight higher than 10, are now fixed at 10.
4. There was a bug, when the BoostNewSongsModifier is set to 0... fixed it.
I am already looking forward to using the new version of this script...
cheers,
popper.