Skip to content

Instantly share code, notes, and snippets.

@KELiON
Last active December 24, 2015 23:49
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save KELiON/00fd58e7437f4adf83df to your computer and use it in GitHub Desktop.
Save KELiON/00fd58e7437f4adf83df to your computer and use it in GitHub Desktop.
Audio visualization generator
module Visualizer
SAMPLE_RATE = 44100
BUFFER_SIZE = 512
READ_BUFFER_SIZE = 16*1024*1024
COLS = 30
PER_SECOND = 25
TIME_DELTA = 1.0/PER_SECOND
def self.average value, count
value.in_groups(count, false).map{|a| a.sum.to_f / a.size }
end
def self.times_enumerator_for_file(input)
info = WaveFile::Reader.info(input)
duration = info.duration
duration_s = (duration[:hours]*60 + duration[:minutes])*60 + duration[:seconds] + duration[:milliseconds]/1000.0
Enumerator.new do |yielder|
time = 0
loop do
yielder << time
time += TIME_DELTA
break if time > duration_s
end
end
end
def self.wav_reader_fiber(input)
Fiber.new do
reader = WaveFile::Reader.new(input)
buffer = reader.read(READ_BUFFER_SIZE)
begin_offset = 0
times_enumerator_for_file(input).each do |time|
offset = time * SAMPLE_RATE
if (offset-begin_offset) > READ_BUFFER_SIZE
begin_offset += READ_BUFFER_SIZE
buffer = reader.read(READ_BUFFER_SIZE)
end
offset %= READ_BUFFER_SIZE
samples = buffer.samples[offset...offset+BUFFER_SIZE]
Fiber.yield samples, time if samples.size == BUFFER_SIZE
end
Fiber.yield nil
end
end
def self.hamming(n, len)
0.56 - 0.46*Math.cos(2.0*Math::PI*n/(len-1))
end
def self.weight_function samples
window = (0...samples.length).map{|i| hamming(i, samples.length)}
samples.zip(window).map{|(s, w)| s*w}
end
def self.generate input, output
wav_fiber = wav_reader_fiber(input)
data = {}
min, max = 1.0/0.0, 0
while res = wav_fiber.resume do
samples, time = res
fft = FFTW3.fft(weight_function(samples))
result = fft[0...fft.length/2].to_enum.map{|i| v = i.abs; v*v}
logs = average(result, COLS).map{|t| (t == 0) ? 0 : (20.0*Math.log10(t)).round }
min_l, max_l = logs.minmax
min = min_l if min_l < min
max = max_l if max_l > max
data[time.round(2)] = logs
end
max -= min
data.each do |key,arr|
data[key] = arr.map{|v| ((v-min).to_f/max*100).round}
end
File.open(output,'w') do |f|
f.write data.to_json
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment