audio-fingerprint 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7676a6ff2be66e896893e5c3eb10646c7f5298ed
4
+ data.tar.gz: 48b9e6d77ae290b6ec8c7bfef0f203eec29cb36f
5
+ SHA512:
6
+ metadata.gz: 5099311c77a1a6b43b20c6a5e02383cd2cfd47e934f2f5060ffbc0c1243b02d9863064670245a75946dfbca2471db975422f4860e3e628083516970c36dad496
7
+ data.tar.gz: 3faf8c32f9db80c063cc482b1cee21198503e026f91f224beb8f8548e0e1e77ed3ce7c443b6973a6c0a1031354ca87b71061a77882ba26e746ac6b9614f583a5
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'audio_fingerprint/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "audio-fingerprint"
8
+ spec.version = AudioFingerprint::VERSION
9
+ spec.authors = ["Rafael Fragoso"]
10
+ spec.email = ["rafaelfragosom@gmail.com"]
11
+ spec.summary = %q{Small gem to fingerprint .wav audio files and compare them}
12
+ spec.description = %q{This gem can fingerprint from small to large pieces of wav audio and run a math to compare them (this is very handy to compare audio notes)}
13
+ spec.homepage = "https://github.com/rafaelfragosom/audio-fingerprint"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = Dir["{lib,bin,test}/**/*", "Rakefile", "README.rdoc", "*.gemspec"]
17
+ spec.executables = %w(audio_fingerprint)
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake", '~> 0'
23
+ spec.add_dependency "fftw3", '~> 0'
24
+ end
@@ -0,0 +1,15 @@
1
+ #!/this/will/be/replaced/by/rubygems
2
+ # -*- encoding: binary -*-
3
+
4
+ require 'audio_fingerprint/version'
5
+ require 'audio_fingerprint/fingerprint'
6
+
7
+ if ARGV[0].nil?
8
+ STDERR.puts "Version: #{AudioFingerprint::VERSION}"
9
+ STDERR.puts "Usage: <input file|url>"
10
+ exit(1)
11
+ else
12
+ f = AudioFingerprint::Fingerprint.new(ARGV[0])
13
+ f.create_fingerprint
14
+ STDERR.puts f.fingerprint.inspect
15
+ end
@@ -0,0 +1,7 @@
1
+ require 'audio_fingerprint/version'
2
+ require 'audio_fingerprint/fingerprint'
3
+ require 'audio_fingerprint/wave_file'
4
+ require 'fftw3'
5
+
6
+ module AudioFingerprint
7
+ end
@@ -0,0 +1,74 @@
1
+ require 'audio_fingerprint/wave_file'
2
+ require 'fftw3'
3
+
4
+ class Float
5
+ def to_near
6
+ (self+0.5).to_i
7
+ end
8
+ end
9
+
10
+ module AudioFingerprint
11
+ class Fingerprint
12
+
13
+ BITS_PER_RAW_ITEM = 32
14
+ THRESHOLD = 0.85
15
+
16
+ attr_accessor :fingerprint
17
+ attr_accessor :file
18
+
19
+ def initialize(file)
20
+ @fingerprint = []
21
+ @file = file
22
+ end
23
+
24
+ def create_fingerprint
25
+ w = AudioFingerprint::WaveFile.open(@file)
26
+ samples = w.sample_data[0, [w.sample_rate * 10, w.sample_data.size].min]
27
+ duration = samples.size / w.sample_rate
28
+ na = NArray.float(2, samples.size)
29
+ @fingerprint ||= []
30
+
31
+ samples.each_with_index do |v, i|
32
+ na[0, i - 1] = i.to_f / w.sample_rate.to_f
33
+ na[1, i - 1] = v
34
+ end
35
+
36
+ fa = FFTW3.fft(na)
37
+ fa = fa.real.abs
38
+
39
+ fa.each do |f|
40
+ @fingerprint << f
41
+ end
42
+ end
43
+
44
+ def compare(fp)
45
+ max_raw_size = [@fingerprint.size, fp.size].max
46
+ bit_size = max_raw_size * BITS_PER_RAW_ITEM
47
+
48
+ distance = hamming_distance(@fingerprint, fp)
49
+
50
+ match = 1 - distance.to_f / bit_size
51
+
52
+ if match >= THRESHOLD
53
+ match
54
+ else
55
+ false
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def hamming_distance(raw1, raw2)
62
+ distance = 0
63
+
64
+ min_size, max_size = [raw1, raw2].map(&:size).sort
65
+
66
+ min_size.times do |i|
67
+ distance += (raw1[i].to_near ^ raw2[i].to_near).to_s(2).count('1')
68
+ end
69
+
70
+ distance += (max_size - min_size) * BITS_PER_RAW_ITEM
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ module AudioFingerprint
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,416 @@
1
+ ###############################################################################
2
+ ### This class was took from the wavefile gem version 0.3.0 to fit my needs ###
3
+ ### https://github.com/jstrait/wavefile #######################################
4
+ ###############################################################################
5
+
6
+ module AudioFingerprint
7
+ class WaveFile
8
+ CHUNK_ID = "RIFF"
9
+ FORMAT = "WAVE"
10
+ FORMAT_CHUNK_ID = "fmt "
11
+ SUB_CHUNK1_SIZE = 16
12
+ PCM = 1
13
+ DATA_CHUNK_ID = "data"
14
+ HEADER_SIZE = 36
15
+
16
+ def initialize(num_channels, sample_rate, bits_per_sample, sample_data = [])
17
+ if num_channels == :mono
18
+ @num_channels = 1
19
+ elsif num_channels == :stereo
20
+ @num_channels = 2
21
+ else
22
+ @num_channels = num_channels
23
+ end
24
+ @sample_rate = sample_rate
25
+ @bits_per_sample = bits_per_sample
26
+ @sample_data = sample_data
27
+
28
+ @byte_rate = sample_rate * @num_channels * (bits_per_sample / 8)
29
+ @block_align = @num_channels * (bits_per_sample / 8)
30
+ end
31
+
32
+ def self.open(path)
33
+ file = File.open(path, "rb")
34
+
35
+ begin
36
+ header = read_header(file)
37
+ errors = validate_header(header)
38
+
39
+ if errors == []
40
+ sample_data = read_sample_data(file,
41
+ header[:num_channels],
42
+ header[:bits_per_sample],
43
+ header[:sub_chunk2_size])
44
+
45
+ wave_file = self.new(header[:num_channels],
46
+ header[:sample_rate],
47
+ header[:bits_per_sample],
48
+ sample_data)
49
+ else
50
+ error_msg = "#{path} can't be opened, due to the following errors:\n"
51
+ errors.each {|error| error_msg += " * #{error}\n" }
52
+ raise StandardError, error_msg
53
+ end
54
+ rescue EOFError
55
+ raise StandardError, "An error occured while reading #{path}."
56
+ ensure
57
+ file.close()
58
+ end
59
+
60
+ return wave_file
61
+ end
62
+
63
+ def save(path)
64
+ # All numeric values should be saved in little-endian format
65
+
66
+ sample_data_size = @sample_data.length * @num_channels * (@bits_per_sample / 8)
67
+
68
+ # Write the header
69
+ file_contents = CHUNK_ID
70
+ file_contents += [HEADER_SIZE + sample_data_size].pack("V")
71
+ file_contents += FORMAT
72
+ file_contents += FORMAT_CHUNK_ID
73
+ file_contents += [SUB_CHUNK1_SIZE].pack("V")
74
+ file_contents += [PCM].pack("v")
75
+ file_contents += [@num_channels].pack("v")
76
+ file_contents += [@sample_rate].pack("V")
77
+ file_contents += [@byte_rate].pack("V")
78
+ file_contents += [@block_align].pack("v")
79
+ file_contents += [@bits_per_sample].pack("v")
80
+ file_contents += DATA_CHUNK_ID
81
+ file_contents += [sample_data_size].pack("V")
82
+
83
+ # Write the sample data
84
+ if !mono?
85
+ output_sample_data = []
86
+ @sample_data.each{|sample|
87
+ sample.each{|sub_sample|
88
+ output_sample_data << sub_sample
89
+ }
90
+ }
91
+ else
92
+ output_sample_data = @sample_data
93
+ end
94
+
95
+ if @bits_per_sample == 8
96
+ file_contents += output_sample_data.pack("C*")
97
+ elsif @bits_per_sample == 16
98
+ file_contents += output_sample_data.pack("s*")
99
+ else
100
+ raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported"
101
+ end
102
+
103
+ file = File.open(path, "w")
104
+ file.syswrite(file_contents)
105
+ file.close
106
+ end
107
+
108
+ def sample_data()
109
+ return @sample_data
110
+ end
111
+
112
+ def normalized_sample_data()
113
+ if @bits_per_sample == 8
114
+ min_value = 128.0
115
+ max_value = 127.0
116
+ midpoint = 128
117
+ elsif @bits_per_sample == 16
118
+ min_value = 32768.0
119
+ max_value = 32767.0
120
+ midpoint = 0
121
+ else
122
+ raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported"
123
+ end
124
+
125
+ if mono?
126
+ normalized_sample_data = @sample_data.map {|sample|
127
+ sample -= midpoint
128
+ if sample < 0
129
+ sample.to_f / min_value
130
+ else
131
+ sample.to_f / max_value
132
+ end
133
+ }
134
+ else
135
+ normalized_sample_data = @sample_data.map {|sample|
136
+ sample.map {|sub_sample|
137
+ sub_sample -= midpoint
138
+ if sub_sample < 0
139
+ sub_sample.to_f / min_value
140
+ else
141
+ sub_sample.to_f / max_value
142
+ end
143
+ }
144
+ }
145
+ end
146
+
147
+ return normalized_sample_data
148
+ end
149
+
150
+ def sample_data=(sample_data)
151
+ if sample_data.length > 0 && ((mono? && sample_data[0].class == Float) ||
152
+ (!mono? && sample_data[0][0].class == Float))
153
+ if @bits_per_sample == 8
154
+ # Samples in 8-bit wave files are stored as a unsigned byte
155
+ # Effective values are 0 to 255, midpoint at 128
156
+ min_value = 128.0
157
+ max_value = 127.0
158
+ midpoint = 128
159
+ elsif @bits_per_sample == 16
160
+ # Samples in 16-bit wave files are stored as a signed little-endian short
161
+ # Effective values are -32768 to 32767, midpoint at 0
162
+ min_value = 32768.0
163
+ max_value = 32767.0
164
+ midpoint = 0
165
+ else
166
+ raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported"
167
+ end
168
+
169
+ if mono?
170
+ @sample_data = sample_data.map {|sample|
171
+ if(sample < 0.0)
172
+ (sample * min_value).round + midpoint
173
+ else
174
+ (sample * max_value).round + midpoint
175
+ end
176
+ }
177
+ else
178
+ @sample_data = sample_data.map {|sample|
179
+ sample.map {|sub_sample|
180
+ if(sub_sample < 0.0)
181
+ (sub_sample * min_value).round + midpoint
182
+ else
183
+ (sub_sample * max_value).round + midpoint
184
+ end
185
+ }
186
+ }
187
+ end
188
+ else
189
+ @sample_data = sample_data
190
+ end
191
+ end
192
+
193
+ def mono?()
194
+ return num_channels == 1
195
+ end
196
+
197
+ def stereo?()
198
+ return num_channels == 2
199
+ end
200
+
201
+ def reverse()
202
+ sample_data.reverse!()
203
+ end
204
+
205
+ def duration()
206
+ total_samples = sample_data.length
207
+ samples_per_millisecond = @sample_rate / 1000.0
208
+ samples_per_second = @sample_rate
209
+ samples_per_minute = samples_per_second * 60
210
+ samples_per_hour = samples_per_minute * 60
211
+ hours, minutes, seconds, milliseconds = 0, 0, 0, 0
212
+
213
+ if(total_samples >= samples_per_hour)
214
+ hours = total_samples / samples_per_hour
215
+ total_samples -= samples_per_hour * hours
216
+ end
217
+
218
+ if(total_samples >= samples_per_minute)
219
+ minutes = total_samples / samples_per_minute
220
+ total_samples -= samples_per_minute * minutes
221
+ end
222
+
223
+ if(total_samples >= samples_per_second)
224
+ seconds = total_samples / samples_per_second
225
+ total_samples -= samples_per_second * seconds
226
+ end
227
+
228
+ milliseconds = (total_samples / samples_per_millisecond).floor
229
+
230
+ return { :hours => hours, :minutes => minutes, :seconds => seconds, :milliseconds => milliseconds }
231
+ end
232
+
233
+ def bits_per_sample=(new_bits_per_sample)
234
+ if new_bits_per_sample != 8 && new_bits_per_sample != 16
235
+ raise StandardError, "Bits per sample of #{@bits_per_samples} is invalid, only 8 or 16 are supported"
236
+ end
237
+
238
+ if @bits_per_sample == 16 && new_bits_per_sample == 8
239
+ conversion_func = lambda {|sample|
240
+ if(sample < 0)
241
+ (sample / 256) + 128
242
+ else
243
+ # Faster to just divide by integer 258?
244
+ (sample / 258.007874015748031).round + 128
245
+ end
246
+ }
247
+
248
+ if mono?
249
+ @sample_data.map! &conversion_func
250
+ else
251
+ sample_data.map! {|sample| sample.map! &conversion_func }
252
+ end
253
+ elsif @bits_per_sample == 8 && new_bits_per_sample == 16
254
+ conversion_func = lambda {|sample|
255
+ sample -= 128
256
+ if(sample < 0)
257
+ sample * 256
258
+ else
259
+ # Faster to just multiply by integer 258?
260
+ (sample * 258.007874015748031).round
261
+ end
262
+ }
263
+
264
+ if mono?
265
+ @sample_data.map! &conversion_func
266
+ else
267
+ sample_data.map! {|sample| sample.map! &conversion_func }
268
+ end
269
+ end
270
+
271
+ @bits_per_sample = new_bits_per_sample
272
+ end
273
+
274
+ def num_channels=(new_num_channels)
275
+ if new_num_channels == :mono
276
+ new_num_channels = 1
277
+ elsif new_num_channels == :stereo
278
+ new_num_channels = 2
279
+ end
280
+
281
+ # The cases of mono -> stereo and vice-versa are handled in specially,
282
+ # because those conversion methods are faster than the general methods,
283
+ # and the large majority of wave files are expected to be either mono or stereo.
284
+ if @num_channels == 1 && new_num_channels == 2
285
+ sample_data.map! {|sample| [sample, sample]}
286
+ elsif @num_channels == 2 && new_num_channels == 1
287
+ sample_data.map! {|sample| (sample[0] + sample[1]) / 2}
288
+ elsif @num_channels == 1 && new_num_channels >= 2
289
+ sample_data.map! {|sample| [].fill(sample, 0, new_num_channels)}
290
+ elsif @num_channels >= 2 && new_num_channels == 1
291
+ sample_data.map! {|sample| sample.inject(0) {|sub_sample, sum| sum + sub_sample } / @num_channels }
292
+ elsif @num_channels > 2 && new_num_channels == 2
293
+ sample_data.map! {|sample| [sample[0], sample[1]]}
294
+ end
295
+
296
+ @num_channels = new_num_channels
297
+ end
298
+
299
+ def inspect()
300
+ duration = self.duration()
301
+
302
+ result = "Channels: #{@num_channels}\n" +
303
+ "Sample rate: #{@sample_rate}\n" +
304
+ "Bits per sample: #{@bits_per_sample}\n" +
305
+ "Block align: #{@block_align}\n" +
306
+ "Byte rate: #{@byte_rate}\n" +
307
+ "Sample count: #{@sample_data.length}\n" +
308
+ "Duration: #{duration[:hours]}h:#{duration[:minutes]}m:#{duration[:seconds]}s:#{duration[:milliseconds]}ms\n"
309
+ end
310
+
311
+ attr_reader :num_channels, :bits_per_sample, :byte_rate, :block_align
312
+ attr_accessor :sample_rate
313
+
314
+ private
315
+
316
+ def self.read_header(file)
317
+ header = {}
318
+
319
+ # Read RIFF header
320
+ riff_header = file.sysread(12).unpack("a4Va4")
321
+ header[:chunk_id] = riff_header[0]
322
+ header[:chunk_size] = riff_header[1]
323
+ header[:format] = riff_header[2]
324
+
325
+ # Read format subchunk
326
+ header[:sub_chunk1_id], header[:sub_chunk1_size] = self.read_to_chunk(file, FORMAT_CHUNK_ID)
327
+ format_subchunk_str = file.sysread(header[:sub_chunk1_size])
328
+ format_subchunk = format_subchunk_str.unpack("vvVVvv") # Any extra parameters are ignored
329
+ header[:audio_format] = format_subchunk[0]
330
+ header[:num_channels] = format_subchunk[1]
331
+ header[:sample_rate] = format_subchunk[2]
332
+ header[:byte_rate] = format_subchunk[3]
333
+ header[:block_align] = format_subchunk[4]
334
+ header[:bits_per_sample] = format_subchunk[5]
335
+
336
+ # Read data subchunk
337
+ header[:sub_chunk2_id], header[:sub_chunk2_size] = self.read_to_chunk(file, DATA_CHUNK_ID)
338
+
339
+ return header
340
+ end
341
+
342
+ def self.read_to_chunk(file, expected_chunk_id)
343
+ chunk_id = file.sysread(4)
344
+ chunk_size = file.sysread(4).unpack("V")[0]
345
+
346
+ while chunk_id != expected_chunk_id
347
+ # Skip chunk
348
+ file.sysread(chunk_size)
349
+
350
+ chunk_id = file.sysread(4)
351
+ chunk_size = file.sysread(4).unpack("V")[0]
352
+ end
353
+
354
+ return chunk_id, chunk_size
355
+ end
356
+
357
+ def self.validate_header(header)
358
+ errors = []
359
+
360
+ unless header[:bits_per_sample] == 8 || header[:bits_per_sample] == 16
361
+ errors << "Invalid bits per sample of #{header[:bits_per_sample]}. Only 8 and 16 are supported."
362
+ end
363
+
364
+ unless (1..65535) === header[:num_channels]
365
+ errors << "Invalid number of channels. Must be between 1 and 65535."
366
+ end
367
+
368
+ unless header[:chunk_id] == CHUNK_ID
369
+ errors << "Unsupported chunk ID: '#{header[:chunk_id]}'"
370
+ end
371
+
372
+ unless header[:format] == FORMAT
373
+ errors << "Unsupported format: '#{header[:format]}'"
374
+ end
375
+
376
+ unless header[:sub_chunk1_id] == FORMAT_CHUNK_ID
377
+ errors << "Unsupported chunk id: '#{header[:sub_chunk1_id]}'"
378
+ end
379
+
380
+ unless header[:audio_format] == PCM
381
+ errors << "Unsupported audio format code: '#{header[:audio_format]}'"
382
+ end
383
+
384
+ unless header[:sub_chunk2_id] == DATA_CHUNK_ID
385
+ errors << "Unsupported chunk id: '#{header[:sub_chunk2_id]}'"
386
+ end
387
+
388
+ return errors
389
+ end
390
+
391
+ # Assumes that file is "queued up" to the first sample
392
+ def self.read_sample_data(file, num_channels, bits_per_sample, sample_data_size)
393
+ if(bits_per_sample == 8)
394
+ data = file.sysread(sample_data_size).unpack("C*")
395
+ elsif(bits_per_sample == 16)
396
+ data = file.sysread(sample_data_size).unpack("s*")
397
+ else
398
+ data = []
399
+ end
400
+
401
+ if(num_channels > 1)
402
+ multichannel_data = []
403
+
404
+ i = 0
405
+ while i < data.length
406
+ multichannel_data << data[i...(num_channels + i)]
407
+ i += num_channels
408
+ end
409
+
410
+ data = multichannel_data
411
+ end
412
+
413
+ return data
414
+ end
415
+ end
416
+ end
@@ -0,0 +1,44 @@
1
+ require 'test/unit'
2
+ require 'audio_fingerprint/fingerprint'
3
+
4
+ class TestFingerprint < Test::Unit::TestCase
5
+
6
+ FILE_PATH1 = '/Users/Rafael/Desktop/previsao.wav'
7
+ FILE_PATH2 = '/Users/Rafael/Desktop/bomdia.wav'
8
+
9
+ def test_get_fingerprint
10
+ # Create the instance
11
+ f = AudioFingerprint::Fingerprint.new(FILE_PATH1)
12
+ # Generate Fingerprint
13
+ f.create_fingerprint
14
+ # Fingerprint should be generated in a huge array
15
+ assert_kind_of(Array, f.fingerprint, "The audio fingerprinted was not correct.")
16
+ end
17
+
18
+ def test_compare_true
19
+ # Create the instance
20
+ f1 = AudioFingerprint::Fingerprint.new(FILE_PATH1)
21
+ f2 = AudioFingerprint::Fingerprint.new(FILE_PATH1)
22
+ # Generate Fingerprint
23
+ f1.create_fingerprint
24
+ f2.create_fingerprint
25
+
26
+ compare = f1.compare(f2.fingerprint)
27
+
28
+ assert_kind_of(Float, compare, "The compare math didn't worked for equal signature.")
29
+ end
30
+
31
+ def test_compare_false
32
+ # Create the instance
33
+ f1 = AudioFingerprint::Fingerprint.new(FILE_PATH1)
34
+ f2 = AudioFingerprint::Fingerprint.new(FILE_PATH2)
35
+ # Generate Fingerprint
36
+ f1.create_fingerprint
37
+ f2.create_fingerprint
38
+
39
+ compare = f1.compare(f2.fingerprint)
40
+
41
+ assert(!compare, "The compare math didn't worked for a different signature.")
42
+ end
43
+
44
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: audio-fingerprint
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Rafael Fragoso
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: fftw3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: This gem can fingerprint from small to large pieces of wav audio and
56
+ run a math to compare them (this is very handy to compare audio notes)
57
+ email:
58
+ - rafaelfragosom@gmail.com
59
+ executables:
60
+ - audio_fingerprint
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - Rakefile
65
+ - audio-fingerprint.gemspec
66
+ - bin/audio_fingerprint
67
+ - lib/audio_fingerprint.rb
68
+ - lib/audio_fingerprint/fingerprint.rb
69
+ - lib/audio_fingerprint/version.rb
70
+ - lib/audio_fingerprint/wave_file.rb
71
+ - test/test_fingerprint.rb
72
+ homepage: https://github.com/rafaelfragosom/audio-fingerprint
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.2.2
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Small gem to fingerprint .wav audio files and compare them
96
+ test_files:
97
+ - test/test_fingerprint.rb