monkeymusic 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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"
|