# A selector weights and selects tracks. It has a rather simple public API.
class Selector
cattr_accessor :logger
# The penalty added to the weight of tracks which were last queued within
# RECENTLY_QUEUED_DURATION
RECENTLY_QUEUED_PENALTY = 5.0
# The amount of time during which a track receives the
# RECENTLY_QUEUED_PENALTY.
if RAILS_ENV == "development"
RECENTLY_QUEUED_DURATION = 15.minutes.ago
else
RECENTLY_QUEUED_DURATION = 1.hour.ago
end
MIN_TRACK_TIME = 45 # seconds
MIN_TRACK_RATING = 2
attr_accessor :new_block, :last_pblock
def initialize(options={})
@options = {:schedule => nil}.merge(options)
@last_pblock = nil
@new_pblock = false
end
def self.filter_track(track)
return false if track.time < MIN_TRACK_TIME
return false if track.disabled
return false if track.rating < MIN_TRACK_RATING && track.rating != 0 && !track.rating.nil?
true
end
def check_pblock(time=Time.now)
pblock = load_pblock(time)
@new_pblock = !pblock.equal?(@last_pblock)
@last_pblock = pblock
@new_pblock
end
# TODO refactor me out
def normalize_last_queued(*args)
self.class.normalize_last_queued(*args)
end
# TODO refactor me out
def normalize_rating(*args)
self.class.normalize_rating(*args)
end
def self.normalize_last_queued(x, min, max, inv_range)
1.0 - (x.to_f - min.to_f) * inv_range
end
def self.normalize_rating(x, min, max, inv_range)
(x.to_f - min.to_f) * inv_range
end
def select
raise NotImplementedError.new("Override me!")
end
private
def load_tracks(time)
pblock = load_pblock(time)
pblock.fetch
end
def gen_powerblock_from(track)
tracks = track.album.tracks + track.artist.tracks +
track.artist.albums.collect {|al| al.tracks}.flatten
tracks.uniq!
n_tracks = Kernel::rand(3) + 3
logger.debug("=== generating power block of size: #{n_tracks} " +
"from #{track.inspect}")
if tracks.size > n_tracks
(1..n_tracks).collect {tracks.delete_at(Kernel::rand(tracks.size))}
else
tracks
end
end
def load_pblock(time)
sched = @options[:schedule] || # make me SCHEDULE_FILE or something
File.join(RAILS_ROOT, "config", "schedule.yml")
Schedule.load(sched, time)
end
def select_type_from_track(track)
qt = @last_pblock.queue_type # TODO factor me out?
case qt
when ProgrammingBlock::QUEUE_MEDIUM
medium = track.album.media[Kernel::rand(track.album.media.size-1)]
tracks = medium.tracks.find_all {|track| self.class.filter_track(track)}
return tracks.sort_by {|t| t.position}
when ProgrammingBlock::QUEUE_POWER_BLOCK
return gen_powerblock_from(track)
when ProgrammingBlock::QUEUE_TRACK
return [track]
else
raise "Unhandled queue_type response"
end
end
end