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 CHANGED
@@ -1,5 +1,4 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem "ruby-echonest", :git => "http://github.com/bassnode/ruby-echonest.git"
4
- #gem "ruby-echonest", :path => "../ruby-echonest"
3
+ # gem "bassnode-ruby-echonest", :path => "../ruby-echonest"
5
4
  gemspec
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 mid-tempo, minor-key music
11
- mreko playlist --min-tempo 100 --max-tempo 120 --mode minor --format pls > rainy_day_suicidal_playlist.pls
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 'rake/rdoctask'
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
@@ -1 +1,2 @@
1
1
  * Figure out how to get FFMPEG included in gem (possible w/ licensing?)
2
+ * Create a 'setup' routine that migrates the DB, creates the ~/.mr_eko dir, etc. Doing it every time mr_eko runs is lame.
data/bin/mreko CHANGED
@@ -21,23 +21,31 @@ def parse_options
21
21
  options[:preset] = preset
22
22
  end
23
23
 
24
- opts.on("-m", "--min-tempo BPM", Integer, "Specify minimum tempo") do |bpm|
25
- options[:min_tempo] = bpm
24
+ opts.on("-t", "--tempo BPM", "Specify tempo") do |bpm|
25
+ options[:tempo] = bpm
26
26
  end
27
27
 
28
- opts.on("-M", "--max-tempo BPM", Integer, "Specify maximum tempo") do |bpm|
29
- options[:max_tempo] = bpm
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("-t", "--time-sig SIG", Integer, "Specify time signature") do |ts|
33
- options[:time_signature] = ts
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("-l", "--max-length SEC", Integer, "Specify maximum song length (in seconds)") do |sec|
37
- options[:max_duration] = sec
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("-d", "--mode MODE", String, "Specify mode (minor or major)") do |mode|
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. 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 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.
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 and sent to an authorized server. The authorized servers mirror with each other to share all data.
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 a filename as an argument and outputs JSON suitable for querying the ENFMP servers.
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 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 program:
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 large library of audio, choosing specific 20 seconds chunks of audio) the server will use this information but it is not required.
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, which is a series of ASCII numbers.)
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.5, Windows, Linux 64-bit & 32-bit.) This code generator has 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 rely on this. Note that you need to have ffmpeg installed and accessible on your path for this to work.
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 alpha_identify_song, for example:
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 testing the code generator with. If it doesn't work, codegen won't work.
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 illustrate how to use libcodegen.
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 enmfp@googlecode.com
106
+ Send to echoprint@googlegroups.com
95
107
 
96
108
 
@@ -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
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,11 @@
1
+ class Numeric
2
+
3
+ def in_minutes
4
+ self.to_f / 60
5
+ end
6
+
7
+ def minutes
8
+ self.to_f * 60
9
+ end
10
+
11
+ end
@@ -0,0 +1,5 @@
1
+ class Object
2
+ def blank?
3
+ self.nil? || self.size == 0
4
+ end
5
+ end
@@ -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
- pl = create(:name => options.delete(:name) || "Playlist #{rand(10000)}")
17
- prepare_options!(options)
18
-
19
- songs = MrEko::Song.where(options).all
20
- if songs.size > 0
21
- songs.each{ |song| pl.add_song(song) }
22
- pl.save
23
- else
24
- pl.delete # TODO: Look into not creating Playlist in the 1st place
25
- raise NoSongsError.new("No songs match that criteria!")
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!(options)
35
+ def self.prepare_options(options)
36
+
31
37
  if preset = options.delete(:preset)
32
- options.replace load_preset(preset)
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
- unless options[:duration].is_a? Range
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
- if options.has_key?(:mode)
47
- options[:mode] = MrEko.mode_lookup(options[:mode])
48
- end
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
- if options.has_key?(:key)
51
- options[:key] = MrEko.key_lookup(options[:key])
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 :pls
63
- create_pls
71
+ when :text
72
+ create_text
64
73
  when :m3u
65
74
  create_m3u
66
75
  else
67
- create_text
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|
@@ -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