monkeymusic 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/bin/monkeymusic-generate +2 -2
- data/levels/bomberman2.rb +13 -15
- data/levels/demolevel.rb +23 -0
- data/levels/testlevel.rb +7 -6
- data/lib/monkey_music/game.rb +14 -10
- data/lib/monkey_music/level.rb +6 -3
- data/lib/monkey_music/level_loader.rb +5 -4
- data/lib/monkey_music/metadata/album.rb +15 -0
- data/lib/monkey_music/metadata/artist.rb +13 -0
- data/lib/monkey_music/metadata/track.rb +22 -0
- data/lib/monkey_music/player.rb +88 -20
- data/lib/monkey_music/runner.rb +8 -8
- data/lib/monkey_music/ui/{browser_ui.rb → browser.rb} +6 -7
- data/lib/monkey_music/ui/{console_ui.rb → console.rb} +5 -4
- data/lib/monkey_music/units/track.rb +17 -7
- data/lib/monkey_music/units/user.rb +3 -5
- data/lib/monkey_music.rb +6 -2
- data/lib/monkey_music_generate/recommendation_loader.rb +25 -64
- data/lib/monkey_music_generate/runner.rb +100 -0
- data/lib/monkey_music_generate/score_system.rb +24 -9
- data/lib/monkey_music_generate/toplist_loader.rb +41 -25
- data/lib/monkey_music_generate.rb +1 -1
- data/users/synth.rb +8 -4
- data/users/synth.yaml +846 -1335
- metadata +9 -5
- data/lib/monkey_music_generate/generator.rb +0 -98
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'hallon'
|
2
2
|
|
3
|
-
module MonkeyMusic
|
3
|
+
module MonkeyMusic::Generate
|
4
4
|
class RecommendationLoader
|
5
5
|
|
6
6
|
def initialize(loaded_toplists, load_factor = 5)
|
@@ -10,66 +10,30 @@ module MonkeyMusic
|
|
10
10
|
|
11
11
|
def load_for_user!(user)
|
12
12
|
@user = user
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
load_recommendations_from_artists!
|
17
|
-
puts "Loading recommendations from track toplist albums..."
|
18
|
-
load_recommendations_from_top_track_albums!
|
19
|
-
puts "Loading recommendations from track toplist..."
|
20
|
-
load_recommendations_from_already_heard!
|
21
|
-
puts "Loading recommendations from disliked artists..."
|
22
|
-
load_recommendations_from_disliked!
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def load_recommendations_from_albums!
|
28
|
-
@loaded_toplists[:top_albums].each do |album|
|
29
|
-
puts "Browsing #{album.name}..."
|
30
|
-
browse = album.browse
|
31
|
-
browse.load unless browse.loaded?
|
32
|
-
browse.tracks.first(@load_factor).each do |rec|
|
33
|
-
@user.recommendations << parse_track(rec)
|
34
|
-
end
|
13
|
+
@loaded_toplists.each do |name, list|
|
14
|
+
puts "Loading #{name}..."
|
15
|
+
list[:items].each { |item| load_recommendations_for!(list[:type], item) }
|
35
16
|
end
|
36
17
|
end
|
37
18
|
|
38
|
-
|
39
|
-
@loaded_toplists[:top_artists].each do |artist|
|
40
|
-
puts "Browsing #{artist.name}..."
|
41
|
-
browse = artist.browse(:no_albums)
|
42
|
-
browse.load unless browse.loaded?
|
43
|
-
browse.top_hits.first(@load_factor).each do |rec|
|
44
|
-
@user.recommendations << parse_track(rec)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def load_recommendations_from_top_track_albums!
|
50
|
-
@loaded_toplists[:top_track_albums].each do |album|
|
51
|
-
puts "Browsing #{album.name}..."
|
52
|
-
browse = album.browse
|
53
|
-
browse.load unless browse.loaded?
|
54
|
-
browse.tracks.first(@load_factor).each do |rec|
|
55
|
-
@user.recommendations << parse_track(rec)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def load_recommendations_from_already_heard!
|
61
|
-
@loaded_toplists[:top_tracks].each do |track|
|
62
|
-
@user.recommendations << parse_track(track)
|
63
|
-
end
|
64
|
-
end
|
19
|
+
private
|
65
20
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
|
21
|
+
def load_recommendations_for!(type, item)
|
22
|
+
if type == :tracks
|
23
|
+
@user.recommendations << parse_track(item)
|
24
|
+
else
|
25
|
+
browse = case type
|
26
|
+
when :artists then item.browse(:no_albums)
|
27
|
+
when :albums then item.browse
|
28
|
+
end
|
29
|
+
puts "Browsing #{item.name}..."
|
70
30
|
browse.load unless browse.loaded?
|
71
|
-
|
72
|
-
|
31
|
+
collection = case type
|
32
|
+
when :artists then browse.top_hits
|
33
|
+
when :albums then browse.tracks
|
34
|
+
end
|
35
|
+
collection.first(@load_factor).each do |track|
|
36
|
+
@user.recommendations << parse_track(track)
|
73
37
|
end
|
74
38
|
end
|
75
39
|
end
|
@@ -77,14 +41,11 @@ module MonkeyMusic
|
|
77
41
|
def parse_track(track)
|
78
42
|
album = track.album.load
|
79
43
|
artist = track.artist.load
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
user_track.popularity = track.popularity
|
86
|
-
user_track.year = album.release_year
|
87
|
-
user_track
|
44
|
+
MonkeyMusic::Metadata::Track.new :uri => track.to_link.to_str,
|
45
|
+
:name => track.name,
|
46
|
+
:artist => artist.name,
|
47
|
+
:album => album.name,
|
48
|
+
:year => album.release_year
|
88
49
|
end
|
89
50
|
|
90
51
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'hallon'
|
3
|
+
|
4
|
+
module MonkeyMusic
|
5
|
+
module Generate
|
6
|
+
class Runner
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
OptionParser.new do |opts|
|
10
|
+
opts.banner = 'Usage: monkeymusic-generate [options]'
|
11
|
+
|
12
|
+
opts.on('-g',
|
13
|
+
'--generate TOPLIST_FILE',
|
14
|
+
'Generate a user from a toplist file.') do |user|
|
15
|
+
@toplist_file = File.join(Dir.getwd, user)
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on('-o',
|
19
|
+
'--out OUT_FILE',
|
20
|
+
'The file to dump a generated user to.') do |file|
|
21
|
+
@out_file = File.join(Dir.getwd, file)
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on('-k',
|
25
|
+
'--app-key KEY',
|
26
|
+
'Path to libspotify application key.') do |key|
|
27
|
+
@spotify_appkey_file = File.join(Dir.getwd, key)
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on('-a',
|
31
|
+
'--account ACCOUNT',
|
32
|
+
'Username for a Spotify premium account.') do |account|
|
33
|
+
@spotify_account = account
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on('-w', '--password PASSWORD',
|
37
|
+
'Password for a Spotify premium account.') do |password|
|
38
|
+
@spotify_password = password
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on_tail('-h',
|
42
|
+
'--help',
|
43
|
+
'Show this message.') do
|
44
|
+
puts opts
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
end.parse!
|
48
|
+
end
|
49
|
+
|
50
|
+
def run
|
51
|
+
if generate_user?
|
52
|
+
# Create user
|
53
|
+
user = User.new
|
54
|
+
# Connect to libspotify
|
55
|
+
Hallon.load_timeout = 0
|
56
|
+
session = Hallon::Session.initialize(IO.read(@spotify_appkey_file))
|
57
|
+
session.login!(@spotify_account, @spotify_password)
|
58
|
+
# Load toplists
|
59
|
+
puts "Loading toplists from #{@toplist_file}..."
|
60
|
+
toplist_loader = ToplistLoader.new(@toplist_file)
|
61
|
+
toplist_loader.load_for_user!(user)
|
62
|
+
# Generate recommendations
|
63
|
+
puts "Loading recommendations..."
|
64
|
+
loaded_toplists = toplist_loader.loaded_toplists
|
65
|
+
recommendation_loader = RecommendationLoader.new(loaded_toplists)
|
66
|
+
recommendation_loader.load_for_user!(user)
|
67
|
+
## Disconnect from libspotify
|
68
|
+
session.logout!
|
69
|
+
# Evaluate recommendations
|
70
|
+
puts "Evaluating recommendations..."
|
71
|
+
score_system = ScoreSystem.new
|
72
|
+
score_system.evaluate_user_recommendations!(user)
|
73
|
+
# Dump and print the user
|
74
|
+
File.open(@out_file, 'w') do |f|
|
75
|
+
f.write(user.dump)
|
76
|
+
end
|
77
|
+
puts "====="
|
78
|
+
puts "DONE!"
|
79
|
+
puts "====="
|
80
|
+
#puts "Loaded tracks:"
|
81
|
+
#user.recommendations.group_by(&:multiplier).sort.each do |k,v|
|
82
|
+
#puts "#Multiplier #{k}:\t#{v.length}"
|
83
|
+
#end
|
84
|
+
exit
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def generate_user?
|
91
|
+
(defined? @toplist_file) &&
|
92
|
+
(defined? @out_file) &&
|
93
|
+
(defined? @spotify_appkey_file) &&
|
94
|
+
(defined? @spotify_account) &&
|
95
|
+
(defined? @spotify_password)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -1,34 +1,49 @@
|
|
1
|
-
module MonkeyMusic
|
1
|
+
module MonkeyMusic::Generate
|
2
2
|
class ScoreSystem
|
3
3
|
|
4
4
|
def evaluate_user_recommendations!(user)
|
5
5
|
@user = user
|
6
|
-
@
|
6
|
+
@top_decade = calc_top_decade(user)
|
7
|
+
@user.recommendations.each {|track| evaluate!(track) }
|
8
|
+
puts "The user's top decade is: #{@top_decade}."
|
7
9
|
end
|
8
10
|
|
9
11
|
def evaluate!(track)
|
10
12
|
multiplier = 0
|
11
|
-
if @user.toplists[:top_tracks].
|
13
|
+
if @user.toplists[:top_tracks].any? {|t| t.name == track.name }
|
12
14
|
multiplier = -1
|
13
|
-
elsif @user.toplists[:
|
15
|
+
elsif @user.toplists[:disliked_artists].any? {|ar| ar.name == track.artist }
|
14
16
|
multiplier = -2
|
15
17
|
else
|
16
|
-
if @user.toplists[:top_albums].
|
18
|
+
if @user.toplists[:top_albums].any? {|al| al.name == track.album }
|
17
19
|
multiplier += 1
|
18
20
|
end
|
19
|
-
if @user.toplists[:top_artists].
|
21
|
+
if @user.toplists[:top_artists].any? {|ar| ar.name == track.artist }
|
20
22
|
multiplier += 1
|
21
23
|
end
|
22
|
-
if @
|
24
|
+
if @top_decade == decade_of(track.year)
|
23
25
|
multiplier += 1
|
24
26
|
end
|
25
27
|
end
|
26
|
-
track.multiplier
|
27
|
-
track.value = 4**multiplier.abs * sign(multiplier)
|
28
|
+
track.set_value(multiplier, 4**multiplier.abs * sign(multiplier))
|
28
29
|
end
|
29
30
|
|
30
31
|
private
|
31
32
|
|
33
|
+
def calc_top_decade(user)
|
34
|
+
decades = {}
|
35
|
+
(user.toplists[:top_tracks] + user.toplists[:top_albums]).each do |t|
|
36
|
+
decade = decade_of(t.year)
|
37
|
+
decades[decade] ||= 0
|
38
|
+
decades[decade] += 1
|
39
|
+
end
|
40
|
+
(decades.max_by {|k,v| v }).first
|
41
|
+
end
|
42
|
+
|
43
|
+
def decade_of(year)
|
44
|
+
(year % 100) / 10
|
45
|
+
end
|
46
|
+
|
32
47
|
def sign(num)
|
33
48
|
num < 0 ? -1 : 1
|
34
49
|
end
|
@@ -1,67 +1,83 @@
|
|
1
1
|
require 'hallon'
|
2
2
|
|
3
|
-
module MonkeyMusic
|
3
|
+
module MonkeyMusic::Generate
|
4
4
|
class ToplistLoader
|
5
5
|
attr_reader :loaded_toplists
|
6
6
|
|
7
7
|
def initialize(toplist_file)
|
8
|
-
@toplist_file =
|
8
|
+
@toplist_file = toplist_file
|
9
9
|
@toplists = {}
|
10
|
+
@loaded_toplists = {}
|
10
11
|
end
|
11
12
|
|
12
13
|
def load_for_user!(user)
|
13
14
|
@user = user
|
14
15
|
# Load toplists URI:s from file
|
15
|
-
self.instance_eval(@toplist_file)
|
16
|
+
self.instance_eval(IO.read @toplist_file)
|
16
17
|
# Load toplist contents from libspotify
|
17
18
|
load_toplists!
|
18
|
-
load_top_track_albums!
|
19
19
|
end
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
23
|
# User toplist DSL
|
24
24
|
|
25
|
-
def toplist(type, uri)
|
26
|
-
@toplists[
|
25
|
+
def toplist(type, name, uri)
|
26
|
+
@toplists[name] = { :type => type, :uri => uri }
|
27
27
|
end
|
28
28
|
|
29
29
|
# Toplist loading
|
30
30
|
|
31
31
|
def load_toplists!
|
32
|
-
@
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
@toplists.each do |name, list|
|
33
|
+
@loaded_toplists[name] = {
|
34
|
+
:type => list[:type],
|
35
|
+
:items => load_from_list(list[:type], list[:uri])
|
36
|
+
}
|
37
|
+
@user.toplists[name] = parse_toplist(list[:type], @loaded_toplists[name][:items])
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
|
-
def load_top_track_albums!
|
40
|
-
@loaded_toplists[:top_track_albums] =
|
41
|
-
@loaded_toplists[:top_tracks].map(&:album).each(&:load)
|
42
|
-
@user.toplists[:top_track_albums] =
|
43
|
-
parse_toplist(@loaded_toplists[:top_track_albums])
|
44
|
-
end
|
45
|
-
|
46
41
|
def load_from_list(type, uri)
|
47
42
|
playlist = Hallon::Playlist.new(uri).load
|
48
43
|
tracks = playlist.tracks.to_a
|
49
44
|
tracks.each(&:load)
|
50
|
-
if type == :
|
45
|
+
if type == :tracks
|
51
46
|
tracks
|
52
|
-
elsif type == :
|
47
|
+
elsif type == :artists
|
53
48
|
tracks.map(&:artist).each(&:load)
|
54
|
-
elsif type == :
|
49
|
+
elsif type == :albums
|
55
50
|
tracks.map(&:album).each(&:load)
|
56
51
|
end
|
57
52
|
end
|
58
53
|
|
59
|
-
def parse_toplist(list)
|
60
|
-
|
61
|
-
|
62
|
-
|
54
|
+
def parse_toplist(type, list)
|
55
|
+
case type
|
56
|
+
when :tracks then list.map { |track| load_track track }
|
57
|
+
when :albums then list.map { |album| load_album album }
|
58
|
+
when :artists then list.map { |artist| load_artist artist }
|
63
59
|
end
|
64
|
-
|
60
|
+
end
|
61
|
+
|
62
|
+
def load_album(album)
|
63
|
+
artist = album.artist.load
|
64
|
+
MonkeyMusic::Metadata::Album.new :name => album.name,
|
65
|
+
:artist => artist.name,
|
66
|
+
:year => album.release_year
|
67
|
+
end
|
68
|
+
|
69
|
+
def load_artist(artist)
|
70
|
+
MonkeyMusic::Metadata::Artist.new :name => artist.name
|
71
|
+
end
|
72
|
+
|
73
|
+
def load_track(track)
|
74
|
+
album = track.album.load
|
75
|
+
artist = track.artist.load
|
76
|
+
MonkeyMusic::Metadata::Track.new :uri => track.to_link.to_str,
|
77
|
+
:name => track.name,
|
78
|
+
:artist => artist.name,
|
79
|
+
:album => album.name,
|
80
|
+
:year => album.release_year
|
65
81
|
end
|
66
82
|
|
67
83
|
end
|
data/users/synth.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
-
toplist :top_tracks,
|
1
|
+
toplist :tracks, :top_tracks,
|
2
|
+
"spotify:user:poscar%21:playlist:4yIhuHviCmTYjcFqunPxsa"
|
2
3
|
|
3
|
-
toplist :
|
4
|
+
toplist :albums, :top_albums,
|
5
|
+
"spotify:user:poscar%21:playlist:6MjfrIbeCKQMVCU9JQNf1u"
|
4
6
|
|
5
|
-
toplist :
|
7
|
+
toplist :artists, :top_artists,
|
8
|
+
"spotify:user:poscar%21:playlist:1ZXPeNHa8YYvY0VzxRdv3q"
|
6
9
|
|
7
|
-
toplist :
|
10
|
+
toplist :artists, :disliked_artists,
|
11
|
+
"spotify:user:poscar%21:playlist:5IEx3cooSMgrtftvw5B9Ug"
|