mr_eko 0.2.4.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -2
- data/README.md +2 -2
- data/Rakefile +2 -2
- data/TODO +1 -0
- data/bin/mreko +23 -17
- data/ext/enmfp/README +23 -11
- data/ext/enmfp/RELEASE_NOTES +3 -3
- data/ext/enmfp/codegen.Darwin +0 -0
- data/ext/enmfp/codegen.Linux-i686 +0 -0
- data/ext/enmfp/codegen.Linux-x86_64 +0 -0
- data/ext/enmfp/codegen.windows.exe +0 -0
- data/ext/enmfp/old/codegen.Darwin +0 -0
- data/ext/enmfp/old/codegen.Linux-i686 +0 -0
- data/ext/enmfp/old/codegen.Linux-x86_64 +0 -0
- data/ext/enmfp/old/codegen.windows.exe +0 -0
- data/lib/mr_eko/ext/numeric.rb +11 -0
- data/lib/mr_eko/ext/object.rb +5 -0
- data/lib/mr_eko/playlist.rb +57 -31
- data/lib/mr_eko/presets.rb +4 -4
- data/lib/mr_eko/song.rb +181 -85
- data/lib/mr_eko/timed_playlist.rb +149 -0
- data/lib/mr_eko.rb +40 -5
- data/mr_eko.gemspec +16 -4
- data/test/mr_eko_test.rb +40 -11
- data/test/playlist_test.rb +85 -78
- data/test/song_test.rb +158 -0
- data/test/test.rb +36 -2
- data/test/timed_playlist_test.rb +130 -0
- metadata +71 -24
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -7,8 +7,8 @@ Example:
|
|
7
7
|
# Scan all your music
|
8
8
|
mreko scan ~/Music/*.mp3
|
9
9
|
|
10
|
-
# Output a PLS playlist of
|
11
|
-
mreko playlist --
|
10
|
+
# Output a PLS playlist of fast, minor-key music
|
11
|
+
mreko playlist --tempo '>120' --mode minor --format pls > rainy_day_suicidal_playlist.pls
|
12
12
|
|
13
13
|
# Output a playlist of shorter, up-tempo, danceable, major-keyed songs
|
14
14
|
mreko playlist --preset gym --format pls > sweaty_playlist.pls
|
data/Rakefile
CHANGED
@@ -63,7 +63,7 @@ task :coverage do
|
|
63
63
|
sh "open coverage/index.html"
|
64
64
|
end
|
65
65
|
|
66
|
-
require '
|
66
|
+
require 'rdoc/task'
|
67
67
|
Rake::RDocTask.new do |rdoc|
|
68
68
|
rdoc.rdoc_dir = 'rdoc'
|
69
69
|
rdoc.title = "#{name} #{version}"
|
@@ -155,7 +155,7 @@ end
|
|
155
155
|
|
156
156
|
desc 'Launch an IRB console'
|
157
157
|
task :console do
|
158
|
-
libs = "-rlib/mr_eko -rirb/completion"
|
158
|
+
libs = "-rlib/mr_eko -rirb/completion -rruby-debug"
|
159
159
|
exec "irb #{libs}"
|
160
160
|
end
|
161
161
|
|
data/TODO
CHANGED
data/bin/mreko
CHANGED
@@ -21,23 +21,31 @@ def parse_options
|
|
21
21
|
options[:preset] = preset
|
22
22
|
end
|
23
23
|
|
24
|
-
opts.on("-
|
25
|
-
options[:
|
24
|
+
opts.on("-t", "--tempo BPM", "Specify tempo") do |bpm|
|
25
|
+
options[:tempo] = bpm
|
26
26
|
end
|
27
27
|
|
28
|
-
opts.on("-
|
29
|
-
options[:
|
28
|
+
opts.on("-s", "--time-sig SIG", Integer, "Specify time signature") do |ts|
|
29
|
+
options[:time_signature] = ts
|
30
30
|
end
|
31
31
|
|
32
|
-
opts.on("-
|
33
|
-
options[:
|
32
|
+
opts.on("-l", "--duration SEC", "Specify song length (in seconds)") do |sec|
|
33
|
+
options[:duration] = sec
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("-d", "--danceability NUM", "Specify danceability (from 1 to 100)") do |num|
|
37
|
+
options[:danceability] = num
|
34
38
|
end
|
35
39
|
|
36
|
-
opts.on("-
|
37
|
-
options[:
|
40
|
+
opts.on("-e", "--energy NUM", "Specify energy (from 1 to 100)") do |num|
|
41
|
+
options[:energy] = num
|
38
42
|
end
|
39
43
|
|
40
|
-
opts.on("-
|
44
|
+
opts.on("-w", "--loudness NUM", "Specify loudness (from 1 to 100)") do |num|
|
45
|
+
options[:loudness] = num
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on("-o", "--mode MODE", String, "Specify mode (minor or major)") do |mode|
|
41
49
|
unless MrEko::MODES.include?(mode)
|
42
50
|
raise ArgumentError.new("Mode must be #{MrEko::MODES.join(' or ')}")
|
43
51
|
end
|
@@ -52,36 +60,34 @@ def parse_options
|
|
52
60
|
options[:format] = format
|
53
61
|
end
|
54
62
|
|
63
|
+
opts.on("-x", "--tags-only", "Only perform lookups using ID3 tags (no analyzing)") do
|
64
|
+
options[:tags_only] = true
|
65
|
+
end
|
55
66
|
|
56
67
|
opts.separator ""
|
57
68
|
opts.separator "Common options:"
|
58
69
|
|
59
70
|
# No argument, shows at tail. This will print an options summary.
|
60
|
-
# Try it and see!
|
61
71
|
opts.on_tail("-h", "--help", "Show this message") do
|
62
72
|
puts opts
|
63
73
|
exit
|
64
74
|
end
|
65
75
|
|
66
|
-
# Another typical switch to print the version.
|
67
|
-
# opts.on_tail("--version", "Show version") do
|
68
|
-
# puts OptionParser::Version.join('.')
|
69
|
-
# exit
|
70
|
-
# end
|
71
76
|
end.parse!
|
72
77
|
|
73
78
|
options
|
74
79
|
end
|
75
80
|
|
76
81
|
|
82
|
+
options = parse_options
|
83
|
+
|
77
84
|
case ARGV[0]
|
78
85
|
when 'scan'
|
79
86
|
ARGV.shift
|
80
87
|
ARGV.each do |file|
|
81
|
-
MrEko::Song.create_from_file!(file)
|
88
|
+
MrEko::Song.create_from_file!(file, options)
|
82
89
|
end
|
83
90
|
when 'playlist'
|
84
|
-
options = parse_options
|
85
91
|
format = options.delete(:format)
|
86
92
|
playlist = MrEko::Playlist.create_from_options(options)
|
87
93
|
puts playlist.output(format)
|
data/ext/enmfp/README
CHANGED
@@ -10,7 +10,9 @@ Read more:
|
|
10
10
|
http://notes.variogr.am/post/544559482/the-echo-nest-musical-fingerprint-enmfp
|
11
11
|
http://developer.echonest.com/docs/v4/song.html#identify
|
12
12
|
|
13
|
-
The ENMFP is a musical fingerprint that uses portions of the Echo Nest Analyze "machine listening" toolkit to extract musical features that allow copies of songs to be identified.
|
13
|
+
The ENMFP is a musical fingerprint that uses portions of the Echo Nest Analyze "machine listening" toolkit to extract musical features that allow copies of songs to be identified.
|
14
|
+
This allows you to resolve an unknown song against a database of songs. The Echo Nest currently maintains the only song database compatible with ENMFP codes, however, the design
|
15
|
+
of the service is such that anyone can run and maintain a mirror of the code database to avoid "lock in" to a specific vendor.
|
14
16
|
|
15
17
|
The ENMFP is robust to time and pitch modification, is resilient to drastic reductions in fidelity and can even detect uses of audio in remixes, samples and sometimes even cover songs.
|
16
18
|
|
@@ -18,14 +20,18 @@ You only need to query for 20 seconds of audio (or less sometimes) to get a resu
|
|
18
20
|
|
19
21
|
The server component of the ENMFP is open source and will be released shortly.
|
20
22
|
|
21
|
-
The code generator component of the ENMFP is under the Echo Nest Community Music Code License, which allows free use for all applications provided that the codes are queried for
|
23
|
+
The code generator component of the ENMFP is under the Echo Nest Community Music Code License, which allows free use for all applications provided that the codes are queried for
|
24
|
+
and sent to an authorized server. The authorized servers mirror with each other to share all data.
|
22
25
|
|
23
|
-
There are two ways to use the code generator: link the provided shared libraries with your application and pass it pointers to PCM data from songs, or use a precompiled binary that accepts
|
26
|
+
There are two ways to use the code generator: link the provided shared libraries with your application and pass it pointers to PCM data from songs, or use a precompiled binary that accepts
|
27
|
+
a filename as an argument and outputs JSON suitable for querying the ENFMP servers.
|
24
28
|
|
25
29
|
|
26
30
|
###### Dynamic library -- libcodegen
|
27
31
|
|
28
|
-
The package contains binary libraries for Linux (i686 and x86_64), Mac (10.6 only) and Windows. You can compile these libraries into your app to generate codes suitable for querying
|
32
|
+
The package contains binary libraries for Linux (i686 and x86_64), Mac (10.6 only) and Windows. You can compile these libraries into your app to generate codes suitable for querying
|
33
|
+
the ENMFP API based on PCM data. See test.cxx for a very simple test program that links with libcodegen. Put libcodegen somewhere that LD_LIBRARY_PATH knows about and compile the test
|
34
|
+
program:
|
29
35
|
|
30
36
|
(this is an example for Linux i686, for other platforms change the library name accordingly)
|
31
37
|
|
@@ -55,14 +61,18 @@ Code generation takes a buffer of floating point PCM data sampled at 22050 Hz an
|
|
55
61
|
|
56
62
|
Codegen * pCodegen = new Codegen(_pSamples, _NumberSamples, offset);
|
57
63
|
|
58
|
-
the "offset" parameter creates a hint to the server on where the sample is taken from in the original file. If you know this (for example, if you are automatically scanning a
|
64
|
+
the "offset" parameter creates a hint to the server on where the sample is taken from in the original file. If you know this (for example, if you are automatically scanning a
|
65
|
+
large library of audio, choosing specific 20 seconds chunks of audio) the server will use this information but it is not required.
|
59
66
|
|
60
|
-
After compute, you want to call pCodegen->getCodeString() to get the code string. (The code string is just a base64 encoding of a zlib compression of the original code string,
|
67
|
+
After compute, you want to call pCodegen->getCodeString() to get the code string. (The code string is just a base64 encoding of a zlib compression of the original code string,
|
68
|
+
which is a series of ASCII numbers.)
|
61
69
|
|
62
70
|
|
63
71
|
###### Example code generator
|
64
72
|
|
65
|
-
This package also contains an example code generator binary for all 3 platforms (Mac 10.6/10.
|
73
|
+
This package also contains an example code generator binary for all 3 platforms (Mac 10.5/10.6/10.7, Windows, Linux 64-bit & 32-bit.) This code generator has
|
74
|
+
more features -- it will output ID3 tag information and uses ffmpeg to decode any type of file. If you don't need to compile libcodegen into your app you can
|
75
|
+
rely on this. Note that you need to have ffmpeg installed and accessible on your path for this to work. You will also need taglib installed.
|
66
76
|
|
67
77
|
./codegen.Linux-x86_64 billie_jean.mp3 10 10
|
68
78
|
|
@@ -72,7 +82,7 @@ Will take 10 seconds of audio from 10 seconds into the file and output JSON suit
|
|
72
82
|
"sample_rate":44100, "seconds":294, "filename":"billie_jean.mp3", "samples_decoded":220598, "given_duration":10, "start_offset":10,
|
73
83
|
"version":3.14}, "code_count":76, "codes":"JxVlIuNwzAMQ1fxCDL133+xo1rnGqNAEcWy/ERa2aKeZmW...
|
74
84
|
|
75
|
-
You can POST this JSON directly to
|
85
|
+
You can POST this JSON directly to song/identify, for example:
|
76
86
|
|
77
87
|
# curl -F "query=@post_string" http://beta.developer.echonest.com/api/v4/song/identify?api_key=YOUR_KEY
|
78
88
|
{"fp_lookup_time_ms": 21, "results": [{"songID": "SOAFVGQ1280ED4E371", "match_type": "fp", "title": "Billie Jean", "artist": "Michael Jackson",
|
@@ -84,13 +94,15 @@ You can POST this JSON directly to alpha_identify_song, for example:
|
|
84
94
|
|
85
95
|
Q: I get "Couldn't decode any samples with: ffmpeg" when running the example code generator.
|
86
96
|
|
87
|
-
A1: When running the example code generator (codegen.$(PLATFORM)) make sure ffmpeg is accessible to your path. Try running ffmpeg filename.mp3 on the file you are
|
97
|
+
A1: When running the example code generator (codegen.$(PLATFORM)) make sure ffmpeg is accessible to your path. Try running ffmpeg filename.mp3 on the file you are
|
98
|
+
testing the code generator with. If it doesn't work, codegen won't work.
|
88
99
|
|
89
|
-
A2: Are you trying to decode billie.raw with the example code generator? billie.raw is only for test.cxx; it is just packed PCM, not a playable file. It is meant to
|
100
|
+
A2: Are you trying to decode billie.raw with the example code generator? billie.raw is only for test.cxx; it is just packed PCM, not a playable file. It is meant to
|
101
|
+
illustrate how to use libcodegen.
|
90
102
|
|
91
103
|
|
92
104
|
###### Questions, comments
|
93
105
|
|
94
|
-
Send to
|
106
|
+
Send to echoprint@googlegroups.com
|
95
107
|
|
96
108
|
|
data/ext/enmfp/RELEASE_NOTES
CHANGED
@@ -29,6 +29,6 @@ Fixed memory leak in FFT routine
|
|
29
29
|
Use Accelerate on iOS 4
|
30
30
|
"Too few events detected" no longer throws exception but returns empty codestring
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
3.16 Aug 18 2011
|
33
|
+
Compile without debug symbols
|
34
|
+
OSX version is now 32/64bit fat binary, compatible with OSX 10.5-up
|
data/ext/enmfp/codegen.Darwin
CHANGED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/mr_eko/playlist.rb
CHANGED
@@ -7,50 +7,59 @@ class MrEko::Playlist < Sequel::Model
|
|
7
7
|
|
8
8
|
plugin :validation_helpers
|
9
9
|
many_to_many :songs
|
10
|
-
FORMATS = [:pls, :m3u, :text]
|
10
|
+
FORMATS = [:pls, :m3u, :text].freeze
|
11
|
+
DEFAULT_OPTIONS = [ {:tempo => 0..500}, {:duration => 10..1200} ].freeze
|
12
|
+
|
11
13
|
|
12
14
|
# Creates and returns a new Playlist from the passed <tt>options</tt>.
|
13
15
|
# <tt>options</tt> should be finder options you pass to Song plus (optionally) :name.
|
14
16
|
def self.create_from_options(options)
|
15
17
|
# TODO: Is a name (or persisting) even necessary?
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
songs
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
MrEko.connection.transaction do
|
19
|
+
pl = create(:name => options.delete(:name) || "Playlist #{rand(10000)}")
|
20
|
+
options = prepare_options(options)
|
21
|
+
|
22
|
+
|
23
|
+
songs = MrEko::Song.where(options).all
|
24
|
+
if songs.size > 0
|
25
|
+
songs.each{ |song| pl.add_song(song) }
|
26
|
+
pl.save
|
27
|
+
else
|
28
|
+
# pl.delete # TODO: Look into not creating Playlist in the 1st place
|
29
|
+
raise NoSongsError.new("No songs match those criteria!")
|
30
|
+
end
|
26
31
|
end
|
27
32
|
end
|
28
33
|
|
29
34
|
# Organize and transform!
|
30
|
-
def self.prepare_options
|
35
|
+
def self.prepare_options(options)
|
36
|
+
|
31
37
|
if preset = options.delete(:preset)
|
32
|
-
|
38
|
+
new_options = load_preset(preset)
|
33
39
|
else
|
34
|
-
unless options[:tempo].is_a? Range
|
35
|
-
min_tempo = options.delete(:min_tempo) || 0
|
36
|
-
max_tempo = options.delete(:max_tempo) || 500
|
37
|
-
options[:tempo] = min_tempo..max_tempo
|
38
|
-
end
|
39
40
|
|
40
|
-
|
41
|
-
min_duration = options.delete(:min_duration) || 10 # worthless jams
|
42
|
-
max_duration = options.delete(:max_duration) || 1200 # 20 min.
|
43
|
-
options[:duration] = min_duration..max_duration
|
44
|
-
end
|
41
|
+
new_options = DEFAULT_OPTIONS.reject{ |d| options.keys.include?(d.keys.first) }
|
45
42
|
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
options.each do |key, value|
|
44
|
+
|
45
|
+
case key
|
46
|
+
|
47
|
+
when :danceability, :energy, :loudness
|
48
|
+
new_options << transform(key, value, true)
|
49
|
+
|
50
|
+
when :mode
|
51
|
+
new_options << transform(key, MrEko.mode_lookup(value))
|
49
52
|
|
50
|
-
|
51
|
-
|
53
|
+
when :key
|
54
|
+
new_options << transform(key, MrEko.key_lookup(value))
|
55
|
+
|
56
|
+
else
|
57
|
+
new_options << transform(key, value)
|
58
|
+
end
|
52
59
|
end
|
53
60
|
end
|
61
|
+
|
62
|
+
new_options
|
54
63
|
end
|
55
64
|
|
56
65
|
# Return the formatted playlist.
|
@@ -59,15 +68,32 @@ class MrEko::Playlist < Sequel::Model
|
|
59
68
|
raise ArgumentError.new("Format must be one of #{FORMATS.join(', ')}") unless FORMATS.include? format
|
60
69
|
|
61
70
|
case format
|
62
|
-
when :
|
63
|
-
|
71
|
+
when :text
|
72
|
+
create_text
|
64
73
|
when :m3u
|
65
74
|
create_m3u
|
66
75
|
else
|
67
|
-
|
76
|
+
create_pls
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def self.transform(key, value, percentage=false)
|
83
|
+
if match = value.to_s.match(/(^[<>])(\d+)/)
|
84
|
+
operator = match[1]
|
85
|
+
value = match[2]
|
86
|
+
|
87
|
+
value = value.to_f / 100.0 if percentage
|
88
|
+
|
89
|
+
"#{key} #{operator} #{value}".lit
|
90
|
+
else
|
91
|
+
value = value.to_f / 100.0 if percentage
|
92
|
+
{key => value}
|
68
93
|
end
|
69
94
|
end
|
70
95
|
|
96
|
+
|
71
97
|
# Returns a text representation of the Playlist.
|
72
98
|
def create_text
|
73
99
|
songs.inject("") do |list, song|
|
data/lib/mr_eko/presets.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
module MrEko::Presets
|
2
2
|
FACTORY = {
|
3
|
-
:gym =>
|
3
|
+
:gym => [
|
4
4
|
:tempo => 125..300, # sweat, sweat, sweat!
|
5
5
|
:mode => 'major', # bring the HappyHappy
|
6
6
|
:duration => 180..300, # shorter, poppier tunes
|
7
7
|
:energy => 0.5..1.0,
|
8
8
|
:danceability => 0.4..1.0
|
9
|
-
|
10
|
-
:chill =>
|
9
|
+
],
|
10
|
+
:chill => [
|
11
11
|
:tempo => 60..120, # mellow
|
12
12
|
:duration => 180..600, # bring the epic, long-players
|
13
13
|
:energy => 0.2..0.5,
|
14
14
|
:danceability => 0.1..0.5
|
15
|
-
|
15
|
+
]
|
16
16
|
}
|
17
17
|
|
18
18
|
module ClassMethods
|