Created
April 17, 2010 21:37
-
-
Save potomak/369816 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rubygems' | |
require 'RMagick' | |
require 'narray' | |
class WaveformRenderer | |
# image size in pixel | |
DEFAULT_WIDTH = 1024 | |
DEFAULT_HEIGHT = 512 | |
# default graph color | |
DEFAULT_COLOR = "#666" | |
# default graph background color | |
DEFAULT_BACKGROUND_COLOR = "none" | |
def initialize(input, output) | |
@input = input | |
@output = output | |
end | |
def render_waveform(width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT, color = DEFAULT_COLOR, background_color = | |
DEFAULT_BACKGROUND_COLOR) | |
buckets = fill_buckets(width, @input) | |
o = [('a'..'f'),('0'..'9')].map{ |i| i.to_a }.flatten | |
color = "#" + (0..5).map{ o[rand(o.length)] }.join | |
RAILS_DEFAULT_LOGGER.info "color: #{color}" | |
gc = build_graph(buckets, width, height, color) | |
canvas = Magick::Image.new(width, height) { self.background_color = background_color } | |
gc.draw(canvas) | |
canvas.write(@output) | |
end | |
protected | |
# fill the buckets | |
def fill_buckets(width, file) | |
buckets = NArray.int(width,2) | |
# let sox fetch the byte array | |
bytes = sox_get_bytes(file) | |
bucket_size = (((bytes.size-1).to_f / width)+0.5).to_i + 1 | |
@max = 0 | |
@min = 0 | |
(0..bytes.total-1).each do |i| | |
value = bytes[i] | |
index = i/bucket_size | |
# minimum | |
buckets[index,0] = value if value < buckets[index,0] | |
@min = value if value < @min | |
# maximum | |
buckets[index,1] = value if value > buckets[index,1] | |
@max = value if value > @max | |
end | |
return buckets | |
end | |
# open file with sox and return a byte array with sweet waveform information in it | |
def sox_get_bytes(file) | |
# | |
# Sox command help | |
# | |
# -t, --type file-type | |
# Gives the type of the audio file. This is useful when the file | |
# extension is non-standard or when the type can not be determined | |
# by looking at the header of the file. | |
# | |
# The -t option can also be used to override the type implied by | |
# an input filename extension, but if overriding with a type that | |
# has a header, SoX will exit with an appropriate error message if | |
# such a header is not actually present. | |
# | |
# -r (rate): output sample rate (4kHz) | |
# | |
# -c CHANNELS, --channels CHANNELS | |
# The number of audio channels in the audio file; this can be any | |
# number greater than zero. To cause the output file to have a | |
# different number of channels than the input file, include this | |
# option with the output file options. If the input and output | |
# file have a different number of channels then the mixer effect | |
# must be used. If the mixer effect is not specified on the com- | |
# mand line it will be invoked internally with default parameters. | |
# | |
# Alternatively, some effects (e.g. synth, remix) determine what | |
# will be the number of output channels; in this case, neither | |
# this option nor the mixer effect is necessary. | |
# | |
# -s (signed-integer) | |
# The audio encoding type. | |
# | |
# PCM data stored as signed (`two's complement') integers. | |
# Commonly used with a 16 or 24-bit encoding size. | |
# A value of 0 represents minimum signal power. | |
# | |
# -L, --endian little | |
# -B, --endian big | |
# -x, --endian swap | |
# These options specify whether the byte-order of the audio data | |
# is, respectively, `little endian', `big endian', or the opposite | |
# to that of the system on which SoX is being used. Endianness | |
# applies only to data encoded as signed or unsigned integers of | |
# 16 or more bits. It is often necessary to specify one of these | |
# options for headerless files, and sometimes necessary for (oth- | |
# erwise) self-describing files. A given endian-setting option | |
# may be ignored for an input file whose header contains a spe- | |
# cific endianness identifier, or for an output file that is actu- | |
# ally an audio device. | |
# | |
# N.B. Unlike normal format characteristics, the endianness | |
# (byte, nibble, & bit ordering) of the input file is not automat- | |
# ically used for the output file; so, for example, when the fol- | |
# lowing is run on a little-endian system: | |
# | |
# sox -B audio.s2 trimmed.s2 trim 2 | |
# | |
# trimmed.s2 will be created as little-endian; | |
# | |
# sox -B audio.s2 -B trimmed.s2 trim 2 | |
# | |
# must be used to preserve big-endianness in the output file. | |
# | |
sox_command = [ 'sox', file, '-t', 'raw', '-r', '22050', '-c', '1', '-s', '-L', '-' ] | |
x = nil | |
# we have to fork/exec to get a clean commandline | |
IO.popen('-') do |p| | |
if p.nil? then | |
$stderr.close | |
# raw 16 bit linear PCM one channel | |
exec *sox_command | |
end | |
x = p.read | |
end | |
if x.size == 0 then | |
raise "sox returned no data, command was\n> #{sox_command.join(' ')}" | |
end | |
NArray.to_na(x.unpack("s*")) | |
end | |
# build the waveform graph | |
def build_graph(buckets, width, height, color) | |
gc = Magick::Draw.new | |
offset = 0 | |
scale = ([@max, (0-1)*@min].max/height + 1)*2 | |
midpoint = height/2 | |
(0..(buckets.size/2)-1).each do |i| | |
low = buckets[i,0] | |
high = buckets[i,1] | |
gc.stroke(color) | |
gc.stroke_width(1) | |
gc.line(i+offset, midpoint+low/scale, i+offset, midpoint+high/scale) | |
end | |
return gc | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment