audio-fingerprint 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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