Skip to content

Instantly share code, notes, and snippets.

@potomak
Created April 17, 2010 21:37
Show Gist options
  • Save potomak/369816 to your computer and use it in GitHub Desktop.
Save potomak/369816 to your computer and use it in GitHub Desktop.
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