Note that this Wiki is a work in progress, items may not be updated or may disappear entirely as the pages are updated.

Custom Song Balance Script

From SpacialAudio

Jump to: navigation, search

{ CUSTOM SONG SCORING SCRIPT BY TOBY SHEETS (tobasco@riograndemud.com) FOR SAM USERS

Edited 2/28/03


Overview:

This script overrides SAM's 'balance' field values to provide a more robust and accurate song scoring system. It has so far only been tested using the category-based rotation logic. Here's how it works:

Every time a song plays, this script runs. The very first thing it does is set all song balances to zero. Then it applies a series of rules to each song based on various parameters. Every time a rule is broken, the balance value of the song is incremented by a set value. Once the script has completed its check of rules, the songs all have a final balance. The lower the balance of a song, the better choice that song is for next play because it has broken the fewest rules to the least degree.

When using category based rotation to control your station you typically use a line like:

Cat.QueueBottom('My_Category',smWeighted, prNoRules);

The parameter 'smWeighted' means SAM will choose the song with the lowest balance from the category you specify. Because WE are now controlling the balances of songs we are forcing SAM to make better decisions when selecting songs. You should see much better song selection using this PAL script.

The beauty of this script is that it can be added on to quite easily. I will be creating more rules and adding them to this script as time goes by with suggestions from you all.

Happy broadcasting!



PAL.Loop := True;


// Set some global variables to be used throughout the script
  var
   D, D2, D3 : TDataSet;
  var
   scale, points, increments,
   highest_point, songID, count : Integer;
  var
   lowest_point, point_increment, balance, penalty : Float;
   
  var
   date : DateTime;


  // SET ALL SONG balanceS TO ZERO
  ExecSQL('UPDATE songlist SET balance = 0',[]);


{*******************************************************************************
              START OF RULE 1
*******************************************************************************}
  var current_time :  DateTime = now;
  var three_hours_ago :  DateTime = T['-03:00:00'];
{******************************************************************************
RULE 1: NO SONG REPEATS WITHIN 3 HOURS
This rule applies severe penalties to songs played in the last three hours.
The most recently played tune receives the heaviest penalty. A song played
2 1/2 hours ago receives a lighter penalty. For every second closer to NOW that
a song played, its penalty increases by about 9.25 points.
******************************************************************************}
  Pal.LockExecution;
// SET PENALTY VARIABLES
scale := 100;
points := 100;
increments := 10800;      // the number of sec's in 3 hours
lowest_point := points - ((points / 100) * scale);
highest_point := points;
point_increment := (highest_point - lowest_point) / increments;


D:=Query('SELECT ID, date_played FROM songlist WHERE date_played > :now', [three_hours_ago], True);
D.First;
While Not D.EOF Do
Begin
 // SET NEW balanceS FOR ALL SONGS PLAYED WITHIN LAST 3 HOURS
 date := D['date_played'];
 penalty := ((((date * 86400) - (three_hours_ago * 86400)) * point_increment) + lowest_point);
 ExecSQL('UPDATE songlist SET balance = balance + :penalty WHERE ID = :ID', [penalty, D['ID']]);
 D.Next;
End;
D.Free;
Pal.UnlockExecution;
WriteLn('Rule 1 complete.');
{*******************************************************************************
              END OF RULE 1
*******************************************************************************}



{*******************************************************************************
              START OF RULE 2
*******************************************************************************}
  var lastgenre : String;


{*******************************************************************************
RULE NUMBER 2: DIFFERENT GENRE FOR EACH PLAY
Checks genre of the currently playing song and penalizes all songs that are
from the same genre. This just adds a little variety, especially if you are
running a Top 40 station. For instance it will make sure you don't have two
rap songs back to back, or two pop songs. For this to really work well you 
should take the time to go through all of your songs and fine tune the genre 
tags. Instead of using the "Rock" genre for all of your fast guitar songs, 
sort them into "Hard Rock", "Metal", "Death Metal" etc. Sort your "Rap" genres
into "Hip Hop", "Freestyle", "Gangsta", etc.
Also, this rule only works if you leave SAM on "Ghost Queue" mode, instead
of keeping more than one track in the queue. This rule only applies to the 
currently playing song therefore, if you're adding the 5th song to the queue, 
only the 5th song will be of a different genre from the one currently playing. 
*******************************************************************************}
  Pal.LockExecution;
  penalty := 20;
  // GET GENRE OF LAST SONG PLAYED
  D := Query('SELECT genre FROM songlist ORDER BY date_played DESC LIMIT 1', [], True);
    lastgenre := (D['genre']);
  D.Free;
  // PENALIZE ALL SONGS WITH MATCHING GENRE
  ExecSQL('UPDATE songlist SET balance = balance + :penalty WHERE genre = :lastgenre',[penalty, lastgenre]);
  PAL.UnlockExecution;
  WriteLn('Rule 2 complete.');
{*******************************************************************************
              END OF RULE 2
*******************************************************************************}



{*******************************************************************************
              START OF RULE 3
********************************************************************************
********************************************************************************
RULE 3: NO ARTIST REPEATS WITHIN AN HOUR
All artists that have played in the last 60 minutes will be penalized. Currently
a blanket value of 100 points is assigned to all artists played in the last hour.
(This rule could be made better by checking exactly how long ago that artist was
played and penalizing it differently for each second closer to NOW that the 
artist was played - much like the way RULE 1 penalizes songs.I'll probably play
with this a bit after some coffee.)
*******************************************************************************}
Pal.LockExecution;
penalty := 100;
//GRAB ALL ARTISTS FROM LAST HOUR
D := Query('SELECT DISTINCT artist FROM historylist WHERE date_played > :now ORDER BY date_played DESC',[T['-01:00:00']],True);
D.First;
while not D.EOF do
begin
  ExecSQL('UPDATE songlist SET balance = balance + :penalty WHERE artist = :artist',[penalty, D['artist']]);
  D.Next;
end;
D.Free;
PAL.UnlockExecution;
WriteLn('Rule 3 complete.');
{*******************************************************************************
                 END OF RULE 3
********************************************************************************


{*******************************************************************************
              START OF RULE 4
********************************************************************************
********************************************************************************
RULE 4: CHECK QUEUE LIST
This rule double checks the queue list. Any songs that are already queued will 
be assigned some penalty points (currently set at 100). 
*******************************************************************************}
  Pal.LockExecution;
  penalty := 100;
  //GRAB ALL SONGS FROM QUEUE LIST
  D := Query('SELECT songID FROM queuelist',[],True);
  D.First;
   while not D.EOF do
   begin
     D3 := Query('SELECT artist from songlist WHERE ID = :songID', [D['songID']], True);
     ExecSQL('UPDATE songlist SET balance = balance + :penalty WHERE artist = :artist',[penalty, D3['artist']]);
     D3.Free;
     D.Next;
   end;
  D.Free;


  PAL.UnlockExecution;
  WriteLn('Rule 4 complete.');
{*******************************************************************************
              END OF RULE 4
 ********************************************************************************}




{*******************************************************************************
              START OF RULE 5
********************************************************************************}
var x : Integer;
{*******************************************************************************
RULE 5: Check number of plays
This rule checks how many times each song has played. The more a song has been
played the higher the penalty will be. If a song has never played it will end up
with a lower balance than the one that has played the most.
********************************************************************************}


PAL.LockExecution;
scale := 100;
points := 100;


// Get highest number of song plays
D := Query('SELECT DISTINCT count_played from songlist ORDER BY count_played DESC LIMIT 1', [], True);
increments := D['count_played'];
D.Free;
lowest_point := points - ((points / 100) * scale);
highest_point := points;
point_increment := (highest_point - lowest_point) / increments;
ExecSQL('UPDATE songlist SET balance = balance + (count_played * :point_increment) ', [point_increment]);
PAL.UnlockExecution;
WriteLn('Rule 5 complete.');
{*******************************************************************************
              END OF RULE 5
********************************************************************************}




{*******************************************************************************
              START CHOOSE BEST SONG
********************************************************************************
********************************************************************************
This section grabs the song with the lowest balance. If more than one song 
shares the lowest balance, a random one will be chosen with that score.
This section is currently disabled. If you do not have a complex script set up
for category based rotation, you can enable this section to do the song
selection for you. You will be amazed at the consistent results! To enable this
section, just put a closed bracket at the end of the line below this text, just
after the long series of asterisks.
*******************************************************************************}
  WriteLn('Choosing best song...');
  Pal.LockExecution;
 // GRAB LOWEST FINAL balance
  D := Query('SELECT balance FROM songlist ORDER BY balance ASC', [], True);
    balance := D['balance'];
  D.Free;
  // Count number of songs with this balance
  D:= Query('SELECT filename, balance FROM songlist WHERE balance = :balance ORDER BY RAND() LIMIT 1', [balance], True);
  // Que up selected song 
  Queue.AddFile(D['filename'],ipBottom);
  WriteLn('Adding: ' + D['filename'] + ' to queue.');


  D.Free;
  PAL.UnlockExecution;
{*******************************************************************************
              END CHOOSE BEST SONG
*******************************************************************************}
  // Wait for song to play before running again from the top
  PAL.WaitForPlayCount(1);