hanvox 0.3

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -0,0 +1,22 @@
1
+ have_library: checking for main() in -lm... -------------------- yes
2
+
3
+ "gcc -o conftest -I/usr/local/rvm/rubies/ruby-1.9.2-p180/include/ruby-1.9.1/x86_64-darwin10.7.1 -I/usr/local/rvm/rubies/ruby-1.9.2-p180/include/ruby-1.9.1/ruby/backward -I/usr/local/rvm/rubies/ruby-1.9.2-p180/include/ruby-1.9.1 -I. -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -O3 -ggdb -Wextra -Wno-unused-parameter -Wno-parentheses -Wpointer-arith -Wwrite-strings -Wno-missing-field-initializers -Wshorten-64-to-32 -Wno-long-long -fno-common -pipe conftest.c -L. -L/usr/local/rvm/rubies/ruby-1.9.2-p180/lib -L. -lruby.1.9.1-static -lpthread -ldl -lobjc "
4
+ checked program was:
5
+ /* begin */
6
+ 1: #include "ruby.h"
7
+ 2:
8
+ 3: int main() {return 0;}
9
+ /* end */
10
+
11
+ "gcc -o conftest -I/usr/local/rvm/rubies/ruby-1.9.2-p180/include/ruby-1.9.1/x86_64-darwin10.7.1 -I/usr/local/rvm/rubies/ruby-1.9.2-p180/include/ruby-1.9.1/ruby/backward -I/usr/local/rvm/rubies/ruby-1.9.2-p180/include/ruby-1.9.1 -I. -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -O3 -ggdb -Wextra -Wno-unused-parameter -Wno-parentheses -Wpointer-arith -Wwrite-strings -Wno-missing-field-initializers -Wshorten-64-to-32 -Wno-long-long -fno-common -pipe conftest.c -L. -L/usr/local/rvm/rubies/ruby-1.9.2-p180/lib -L. -lruby.1.9.1-static -lm -lpthread -ldl -lobjc "
12
+ checked program was:
13
+ /* begin */
14
+ 1: #include "ruby.h"
15
+ 2:
16
+ 3: /*top*/
17
+ 4: int main() {return 0;}
18
+ 5: int t() { void ((*volatile p)()); p = (void ((*)()))main; return 0; }
19
+ /* end */
20
+
21
+ --------------------
22
+
Binary file
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/ruby
2
+
3
+ base = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
4
+ $:.unshift(File.join(File.dirname(base)))
5
+
6
+ require 'test/unit'
7
+ require 'kissfft'
8
+ require 'pp'
9
+
10
+ #
11
+ # Simple unit test
12
+ #
13
+
14
+ class KissFFT::UnitTest < Test::Unit::TestCase
15
+ def test_version
16
+ assert_equal(String, KissFFT.version.class)
17
+ puts "KissFFT version: #{KissFFT.version}"
18
+ end
19
+ def test_fftr
20
+ data = File.read('sample.data').unpack('s*')
21
+
22
+ min = 1
23
+ res = KissFFT.fftr(8192, 8000, 1, data)
24
+
25
+ tones = {}
26
+ res.each do |x|
27
+ rank = x.sort{|a,b| a[1].to_i <=> b[1].to_i }.reverse
28
+ rank[0..10].each do |t|
29
+ f = t[0].round
30
+ p = t[1].round
31
+ next if f == 0
32
+ next if p < min
33
+ tones[ f ] ||= []
34
+ tones[ f ] << t
35
+ end
36
+ end
37
+
38
+ tones.keys.sort.each do |t|
39
+ next if tones[t].length < 2
40
+ puts "#{t}hz"
41
+ tones[t].each do |x|
42
+ puts "\t#{x[0]}hz @ #{x[1]}"
43
+ end
44
+ end
45
+
46
+ end
47
+ end
data/lib/hanvox.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "hanvox/proc_audio"
2
+ require "hanvox/raw"
3
+ require "hanvox/version"
4
+
5
+ module Hanvox
6
+
7
+ end
@@ -0,0 +1,244 @@
1
+ require "kissfft/kissfft"
2
+ require "signatures"
3
+ require "tempfile"
4
+
5
+ module Hanvox
6
+ class ProcAudio
7
+ attr_accessor :channels, :data, :header, :file, :oname, :path, :processors, :results, :sample_count, :save_dir
8
+ CHUNK_IDS = {:header => "RIFF",
9
+ :format => "fmt ",
10
+ :data => "data",
11
+ :fact => "fact",
12
+ :silence => "slnt",
13
+ :cue => "cue ",
14
+ :playlist => "plst",
15
+ :list => "list",
16
+ :label => "labl",
17
+ :labeled_text => "ltxt",
18
+ :note => "note",
19
+ :sample => "smpl",
20
+ :instrument => "inst" }
21
+ PACK_CODES = {8 => "C*", 16 => "s*", 32 => "V*"}
22
+
23
+ def initialize path, save_dir=nil
24
+ @channels, @data, @results = [], [], []
25
+ @header = {}
26
+ @save_dir = save_dir || `pwd`.gsub("\n", "")
27
+ @path = path
28
+
29
+ @file = File.open path
30
+ @oname = File.basename path, ".wav"
31
+ read_header
32
+ end
33
+
34
+ def process opts={}
35
+ (@header[:channels]).times do |c|
36
+ c += 1
37
+ system "sox #{@path} -s #{oname}_#{c}.wav remix #{c}"
38
+ system "sox #{oname}_#{c}.wav -s #{oname}_#{c}.raw"
39
+ system "rm -f #{oname}_#{c}.wav"
40
+ @channels << "#{oname}_#{c}.raw"
41
+ end
42
+
43
+ if opts[:channel].nil?
44
+ @channels.each do |chan|
45
+ process_audio chan
46
+ end
47
+ else
48
+ process_audio @channels[opts[:channel]-1]
49
+ end
50
+ cleanup
51
+ end
52
+
53
+ def process_audio input
54
+ bname = File.expand_path(File.dirname(input))
55
+ num = File.basename(input, ".raw").split("_").last
56
+ res = {}
57
+
58
+ #
59
+ # Create the signature database
60
+ #
61
+ raw = Hanvox::Raw.from_file(input)
62
+ fft = KissFFT.fftr(8192, 8000, 1, raw.samples)
63
+
64
+ freq = raw.to_freq_sig_txt()
65
+
66
+ # Save the signature data
67
+ res[:fprint] = freq
68
+
69
+ #
70
+ # Create a raw decompressed file
71
+ #
72
+
73
+ # Decompress the audio file
74
+ datfile = Tempfile.new("datfile")
75
+
76
+ # Data files for audio processing and signal graph
77
+ cnt = 0
78
+ datfile.write(raw.samples.map{|val| cnt +=1; "#{cnt/8000.0} #{val}"}.join("\n"))
79
+ datfile.flush
80
+
81
+ # Data files for spectrum plotting
82
+ frefile = Tempfile.new("frefile")
83
+
84
+ # Calculate the peak frequencies for the sample
85
+ maxf = 0
86
+ maxp = 0
87
+ tones = {}
88
+ fft.each do |x|
89
+ rank = x.sort{|a,b| a[1].to_i <=> b[1].to_i }.reverse
90
+ rank[0..10].each do |t|
91
+ f = t[0].round
92
+ p = t[1].round
93
+ next if f == 0
94
+ next if p < 1
95
+ tones[ f ] ||= []
96
+ tones[ f ] << t
97
+ if(t[1] > maxp)
98
+ maxf = t[0]
99
+ maxp = t[1]
100
+ end
101
+ end
102
+ end
103
+
104
+ # Save the peak frequency
105
+ res[:peak_freq] = maxf
106
+
107
+ # Calculate average frequency and peaks over time
108
+ avg = {}
109
+ pks = []
110
+ pkz = []
111
+ fft.each do |slot|
112
+ pks << slot.sort{|a,b| a[1] <=> b[1] }.reverse[0]
113
+ pkz << slot.sort{|a,b| a[1] <=> b[1] }.reverse[0..9]
114
+ slot.each do |f|
115
+ avg[ f[0] ] ||= 0
116
+ avg[ f[0] ] += f[1]
117
+ end
118
+ end
119
+
120
+ # Save the peak frequencies over time
121
+ res[:peak_freq_data] = pks.map{|f| "#{f[0]}-#{f[1]}" }.join(" ")
122
+
123
+ # Generate the frequency file
124
+ avg.keys.sort.each do |k|
125
+ avg[k] = avg[k] / fft.length
126
+ frefile.write("#{k} #{avg[k]}\n")
127
+ end
128
+ frefile.flush
129
+
130
+ # Count significant frequencies across the sample
131
+ fcnt = {}
132
+ 0.step(4000, 5) {|f| fcnt[f] = 0 }
133
+ pkz.each do |fb|
134
+ fb.each do |f|
135
+ fdx = ((f[0] / 5.0).round * 5.0).to_i
136
+ fcnt[fdx] += 0.1
137
+ end
138
+ end
139
+
140
+ @data << { :raw => raw, :freq => freq, :fcnt => fcnt, :fft => fft,
141
+ :pks => pks, :pkz => pkz, :maxf => maxf, :maxp => maxp }
142
+
143
+ sigs = Signatures::Base.new @data.last
144
+ res[:line_type] = sigs.process
145
+
146
+ # Plot samples to a graph
147
+ plotter = Tempfile.new("gnuplot")
148
+
149
+ plotter.puts("set ylabel \"Signal\"")
150
+ plotter.puts("set xlabel \"Seconds\"")
151
+ plotter.puts("set terminal png medium size 640,480 transparent")
152
+ plotter.puts("set output \"#{save_dir}/#{oname}_#{num}_big.png\"")
153
+ plotter.puts("plot \"#{datfile.path}\" using 1:2 title \"#{num}\" with lines")
154
+ plotter.puts("set output \"#{save_dir}/#{oname}_#{num}_big_dots.png\"")
155
+ plotter.puts("plot \"#{datfile.path}\" using 1:2 title \"#{num}\" with dots")
156
+
157
+ plotter.puts("set terminal png medium size 640,480 transparent")
158
+ plotter.puts("set ylabel \"Power\"")
159
+ plotter.puts("set xlabel \"Frequency\"")
160
+ plotter.puts("set output \"#{save_dir}/#{oname}_#{num}_big_freq.png\"")
161
+ plotter.puts("plot \"#{frefile.path}\" using 1:2 title \"#{num} - Peak #{maxf.round}hz\" with lines")
162
+
163
+ plotter.puts("set ylabel \"Signal\"")
164
+ plotter.puts("set xlabel \"Seconds\"")
165
+ plotter.puts("set terminal png small size 160,120 transparent")
166
+ plotter.puts("set format x ''")
167
+ plotter.puts("set format y ''")
168
+ plotter.puts("set output \"#{save_dir}/#{oname}_#{num}_sig.png\"")
169
+ plotter.puts("plot \"#{datfile.path}\" using 1:2 notitle with lines")
170
+
171
+ plotter.puts("set ylabel \"Power\"")
172
+ plotter.puts("set xlabel \"Frequency\"")
173
+ plotter.puts("set terminal png small size 160,120 transparent")
174
+ plotter.puts("set format x ''")
175
+ plotter.puts("set format y ''")
176
+ plotter.puts("set output \"#{save_dir}/#{oname}_#{num}_sig_freq.png\"")
177
+ plotter.puts("plot \"#{frefile.path}\" using 1:2 notitle with lines")
178
+ plotter.flush
179
+
180
+ puts `gnuplot #{plotter.path}&`
181
+ File.unlink(plotter.path)
182
+ File.unlink(datfile.path)
183
+ File.unlink(frefile.path)
184
+ plotter.close
185
+ datfile.close
186
+ frefile.path
187
+
188
+ @results << res
189
+ end
190
+
191
+ def cleanup
192
+ @channels.each do |c|
193
+ system "rm -f #{c}"
194
+ end
195
+ @file.close
196
+
197
+ true
198
+ end
199
+
200
+ private
201
+ def read_header
202
+ # Read RIFF header
203
+ riff_header = @file.sysread(12).unpack("a4Va4")
204
+ @header[:chunk_id] = riff_header[0]
205
+ @header[:chunk_size] = riff_header[1]
206
+ @header[:format] = riff_header[2]
207
+
208
+ # Read format subchunk
209
+ @header[:sub_chunk1_id], @header[:sub_chunk1_size] = read_to_chunk(CHUNK_IDS[:format])
210
+ format_subchunk_str = @file.sysread(@header[:sub_chunk1_size])
211
+ format_subchunk = format_subchunk_str.unpack("vvVVvv") # Any extra parameters are ignored
212
+ @header[:audio_format] = format_subchunk[0]
213
+ @header[:channels] = format_subchunk[1]
214
+ @header[:sample_rate] = format_subchunk[2]
215
+ @header[:byte_rate] = format_subchunk[3]
216
+ @header[:block_align] = format_subchunk[4]
217
+ @header[:bits_per_sample] = format_subchunk[5]
218
+
219
+ # Read data subchunk
220
+ @header[:sub_chunk2_id], @header[:sub_chunk2_size] = read_to_chunk(CHUNK_IDS[:data])
221
+
222
+ @sample_count = @header[:sub_chunk2_size] / @header[:block_align]
223
+ end
224
+
225
+ def read_to_chunk(expected_chunk_id)
226
+ chunk_id = @file.sysread(4)
227
+ chunk_size = @file.sysread(4).unpack("V")[0]
228
+
229
+ while chunk_id != expected_chunk_id
230
+ # Skip chunk
231
+ file.sysread(chunk_size)
232
+
233
+ chunk_id = @file.sysread(4)
234
+ chunk_size = @file.sysread(4).unpack("V")[0]
235
+ end
236
+
237
+ return chunk_id, chunk_size
238
+ end
239
+
240
+ def format
241
+
242
+ end
243
+ end
244
+ end
data/lib/hanvox/raw.rb ADDED
@@ -0,0 +1,323 @@
1
+ module Hanvox
2
+ class Raw
3
+
4
+ @@kissfft_loaded = false
5
+ begin
6
+ require 'kissfft'
7
+ @@kissfft_loaded = true
8
+ rescue ::LoadError
9
+ end
10
+
11
+ require 'zlib'
12
+
13
+ ##
14
+ # RAW AUDIO - 8khz little-endian 16-bit signed
15
+ ##
16
+
17
+ ##
18
+ # Static methods
19
+ ##
20
+
21
+ def self.from_str(str)
22
+ self.class.new(str)
23
+ end
24
+
25
+ def self.from_file(path)
26
+ if(not path)
27
+ raise Error, "No audio path specified"
28
+ end
29
+
30
+ if(path == "-")
31
+ return self.new($stdin.read)
32
+ end
33
+
34
+ if(not File.readable?(path))
35
+ raise Error, "The specified audio file does not exist"
36
+ end
37
+
38
+ if(path =~ /\.gz$/)
39
+ return self.new(Zlib::GzipReader.open(path).read)
40
+ end
41
+
42
+ self.new(File.read(path, File.size(path)))
43
+ end
44
+
45
+ ##
46
+ # Class methods
47
+ ##
48
+
49
+ attr_accessor :samples
50
+
51
+ def initialize(data)
52
+ self.samples = data.unpack('v*').map do |s|
53
+ (s > 0x7fff) ? (0x10000 - s) * -1 : s
54
+ end
55
+ end
56
+
57
+ def to_raw
58
+ self.samples.pack("v*")
59
+ end
60
+
61
+ def to_wav
62
+ raw = self.to_raw
63
+ wav =
64
+ "RIFF" +
65
+ [raw.length + 36].pack("V") +
66
+ "WAVE" +
67
+ "fmt " +
68
+ [16, 1, 1, 8000, 16000, 2, 16 ].pack("VvvVVvv") +
69
+ "data" +
70
+ [ raw.length ].pack("V") +
71
+ raw
72
+ end
73
+
74
+ def to_flow(opts={})
75
+
76
+ lo_lim = (opts[:lo_lim] || 100).to_i
77
+ lo_min = (opts[:lo_min] || 5).to_i
78
+ hi_min = (opts[:hi_min] || 5).to_i
79
+ lo_cnt = 0
80
+ hi_cnt = 0
81
+
82
+ data = self.samples.map {|c| c.abs}
83
+
84
+ #
85
+ # Granular hi/low state change list
86
+ #
87
+ fprint = []
88
+ state = :lo
89
+ idx = 0
90
+ buff = []
91
+
92
+ while (idx < data.length)
93
+ case state
94
+ when :lo
95
+ while(idx < data.length and data[idx] <= lo_lim)
96
+ buff << data[idx]
97
+ idx += 1
98
+ end
99
+
100
+ # Ignore any sequence that is too small
101
+ fprint << [:lo, buff.length, buff - [0]] if buff.length > lo_min
102
+ state = :hi
103
+ buff = []
104
+ next
105
+ when :hi
106
+ while(idx < data.length and data[idx] > lo_lim)
107
+ buff << data[idx]
108
+ idx += 1
109
+ end
110
+
111
+ # Ignore any sequence that is too small
112
+ fprint << [:hi, buff.length, buff] if buff.length > hi_min
113
+ state = :lo
114
+ buff = []
115
+ next
116
+ end
117
+ end
118
+
119
+ #
120
+ # Merge similar blocks
121
+ #
122
+ final = []
123
+ prev = fprint[0]
124
+ idx = 1
125
+
126
+ while(idx < fprint.length)
127
+
128
+ if(fprint[idx][0] == prev[0])
129
+ prev[1] += fprint[idx][1]
130
+ prev[2] += fprint[idx][2]
131
+ else
132
+ final << prev
133
+ prev = fprint[idx]
134
+ end
135
+
136
+ idx += 1
137
+ end
138
+ final << prev
139
+
140
+ #
141
+ # Process results
142
+ #
143
+ sig = ""
144
+
145
+ final.each do |f|
146
+ sum = 0
147
+ f[2].each {|i| sum += i }
148
+ avg = (sum == 0) ? 0 : sum / f[2].length
149
+ sig << "#{f[0].to_s.upcase[0,1]},#{f[1]},#{avg} "
150
+ end
151
+
152
+ # Return the results
153
+ return sig
154
+ end
155
+
156
+ def to_freq(opts={})
157
+
158
+ if(not @@kissfft_loaded)
159
+ raise RuntimeError, "The KissFFT module is not availabale, raw.to_freq() failed"
160
+ end
161
+
162
+ freq_cnt = opts[:frequency_count] || 20
163
+
164
+ # Perform a DFT on the samples
165
+ ffts = KissFFT.fftr(8192, 8000, 1, self.samples)
166
+
167
+ self.class.fft_to_freq_sig(ffts, freq_cnt)
168
+ end
169
+
170
+ def to_freq_sig(opts={})
171
+ fcnt = opts[:frequency_count] || 5
172
+
173
+ ffts = []
174
+
175
+ # Obtain 20 DFTs for the sample, at 1/20th second offsets into the stream
176
+ 0.upto(19) do |i|
177
+ ffts[i] = KissFFT.fftr(8192, 8000, 1, self.samples[ i * 400, self.samples.length - (i * 400)])
178
+ end
179
+
180
+ # Create a frequency table at 100hz boundaries
181
+ f = [ *(0.step(4000, 100)) ]
182
+
183
+ # Create a worker method to find the closest frequency
184
+ barker = Proc.new do |t|
185
+ t = t.to_i
186
+ f.sort { |a,b|
187
+ (a-t).abs <=> (b-t).abs
188
+ }.first
189
+ end
190
+
191
+ # Map each slice of the audio's FFT with each FFT chunk (8k samples) and then work on it
192
+ tops = ffts.map{|x| x.map{|y| y.map{|z|
193
+
194
+ frq,pwr = z
195
+
196
+ # Toss any signals with a strength under 100
197
+ if pwr < 100.0
198
+ frq,pwr = [0,0]
199
+ # Map the signal to the closest offset of 100hz
200
+ else
201
+ frq = barker.call(frq)
202
+ end
203
+
204
+ # Make sure the strength is an integer
205
+ pwr = pwr.to_i
206
+
207
+ # Sort by signal strength and take the top fcnt items
208
+ [frq, pwr]}.sort{|a,b|
209
+ b[1] <=> a[1]
210
+ }[0, fcnt].map{|w|
211
+ # Grab just the frequency (drop the strength)
212
+ w[0]
213
+ # Remove any duplicates due to hz mapping
214
+ }.uniq
215
+
216
+ } }
217
+
218
+ # Track the generated 4-second chunk signatures
219
+ sigs = []
220
+
221
+ # Expand the list of top frequencies per sample into a flat list of each permutation
222
+ tops.each do |t|
223
+ next if t.length < 4
224
+ 0.upto(t.length - 4) { |i| t[i].each { |a| t[i+1].each { |b| t[i+2].each { |c| t[i+3].each { |d| sigs << [a,b,c,d] } } } } }
225
+ end
226
+
227
+ # Dump any duplicate signatures
228
+ sigs = sigs.uniq
229
+
230
+ # Convert each signature into a single 32-bit integer
231
+ # This is essentially [0-40, 0-40, 0-40, 0-40]
232
+ sigs.map{|x| x.map{|y| y / 100}.pack("C4").unpack("N")[0] }
233
+ end
234
+
235
+ # Converts a signature to a postgresql integer array (text) format
236
+ def to_freq_sig_txt(opts={})
237
+ "{" + to_freq_sig(opts).sort.join(",") + "}"
238
+ end
239
+
240
+ def self.fft_to_freq_sig(ffts, freq_cnt)
241
+ sig = []
242
+ ffts.each do |s|
243
+ res = []
244
+ maxp = 0
245
+ maxf = 0
246
+ s.each do |f|
247
+ if( f[1] > maxp )
248
+ maxf,maxp = f
249
+ end
250
+
251
+ if(maxf > 0 and f[1] < maxp and (maxf + 4.5 < f[0]))
252
+ res << [maxf, maxp]
253
+ maxf,maxp = [0,0]
254
+ end
255
+ end
256
+
257
+ sig << res.sort{ |a,b| # sort by signal strength
258
+ a[1] <=> b[1]
259
+ }.reverse[0,freq_cnt].sort { |a,b| # take the top 20 and sort by frequency
260
+ a[0] <=> b[0]
261
+ }.map {|a| [a[0].round, a[1].round ] } # round to whole numbers
262
+ end
263
+
264
+ sig
265
+ end
266
+
267
+ # Find pattern inside of sample
268
+ def self.compare_freq_sig(pat, zam, opts)
269
+
270
+ fuzz_f = opts[:fuzz_f] || 7
271
+ fuzz_p = opts[:fuzz_p] || 10
272
+ final = []
273
+
274
+ 0.upto(zam.length - 1) do |si|
275
+ res = []
276
+ sam = zam[si, zam.length]
277
+
278
+ 0.upto(pat.length - 1) do |pi|
279
+ diff = []
280
+ next if not pat[pi]
281
+ next if pat[pi].length == 0
282
+ pat[pi].each do |x|
283
+ next if not sam[pi]
284
+ next if sam[pi].length == 0
285
+ sam[pi].each do |y|
286
+ if(
287
+ (x[0] - fuzz_f) < y[0] and
288
+ (x[0] + fuzz_f) > y[0] and
289
+ (x[1] - fuzz_p) < y[1] and
290
+ (x[1] + fuzz_p) > y[1]
291
+ )
292
+ diff << x
293
+ break
294
+ end
295
+ end
296
+ end
297
+ res << diff
298
+ end
299
+ next if res.length == 0
300
+
301
+ prev = 0
302
+ rsum = 0
303
+ ridx = 0
304
+ res.each_index do |xi|
305
+ len = res[xi].length
306
+ if(xi == 0)
307
+ rsum += (len < 2) ? -40 : +20
308
+ else
309
+ rsum += 20 if(prev > 11 and len > 11)
310
+ rsum += len
311
+ end
312
+ prev = len
313
+ end
314
+
315
+ final << [ (rsum / res.length.to_f), res.map {|x| x.length}]
316
+ end
317
+
318
+ final
319
+ end
320
+
321
+
322
+ end
323
+ end