waveform 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +101 -0
- data/Rakefile +5 -0
- data/bin/waveform +58 -0
- data/lib/waveform.rb +303 -0
- data/waveform.gemspec +26 -0
- metadata +80 -0
data/README.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
Waveform
|
2
|
+
========
|
3
|
+
|
4
|
+
Waveform is a class to generate waveform images from audio files. You can
|
5
|
+
combine it with jPlayer to make a soundcloud.com style MP3 player. It also
|
6
|
+
comes with a handy CLI you can use to generate waveform images on the command
|
7
|
+
line.
|
8
|
+
|
9
|
+
CLI Usage
|
10
|
+
=========
|
11
|
+
|
12
|
+
$ waveform song.mp3 waveform.png
|
13
|
+
|
14
|
+
There are some nifty options you can supply to switch things up:
|
15
|
+
|
16
|
+
-W sets the width (in pixels) of the waveform image.
|
17
|
+
-H sets the height (in pixels).
|
18
|
+
-c sets the color used to draw the waveform (in hex, can also use
|
19
|
+
'transparent').
|
20
|
+
-b sets the background color to draw the waveform on (in hex, and can use
|
21
|
+
'transparent' as well).
|
22
|
+
-m sets the method used to sample the source audio file, it can either be
|
23
|
+
'peak' or 'rms'. 'peak' is probably what you want because it looks
|
24
|
+
cooler, but 'rms' is closer to what you actually hear.
|
25
|
+
|
26
|
+
There's also some less-nifty options:
|
27
|
+
|
28
|
+
-q will generate your waveform without printing out a bunch of stuff.
|
29
|
+
-h will prit out a help screen with all this info.
|
30
|
+
|
31
|
+
Generating a small waveform "cut out" of a white background is pretty useful,
|
32
|
+
then you can overlay it on a web-gradient on the website for your new startup
|
33
|
+
and it will look really cool. To make it you could use:
|
34
|
+
|
35
|
+
$ waveform -W900 -H140 -ctransparent -b#ffffff Motley\ Crüe/Kickstart\ my\ Heart.mp3 sweet_waveforms/Kickstart\ my\ Heart.png
|
36
|
+
|
37
|
+
Usage in code
|
38
|
+
=============
|
39
|
+
|
40
|
+
The CLI is really just a thin wrapper around the Waveform class, which you can
|
41
|
+
also use in your programs for reasons I haven't thought of. The Waveform class
|
42
|
+
takes pretty much the same options as the CLI when generating waveforms.
|
43
|
+
|
44
|
+
Requirements
|
45
|
+
============
|
46
|
+
|
47
|
+
`ruby-audio`
|
48
|
+
|
49
|
+
The gem version, *not* the old outdated library listed on RAA. `ruby-audio` is
|
50
|
+
a wrapper for `libsndfile`, on my Ubuntu 10.04LTS VM I installed the necessary
|
51
|
+
libs to build `ruby-audio` via: `sudo apt-get install libsndfile1-dev`.
|
52
|
+
|
53
|
+
`chunky_png`
|
54
|
+
|
55
|
+
`chunky_png` is a pure ruby (!) PNG manipulation library. Caveat to this
|
56
|
+
requirement is that if you also install `oily_png` you will get *better
|
57
|
+
performance* as it uses some C code, and C code is fast.
|
58
|
+
|
59
|
+
`ffmpeg` (sorta)
|
60
|
+
|
61
|
+
You only need `ffmpeg` if you plan to generate waveforms from files that aren't
|
62
|
+
already WAVs (like MP3, or M4A). On my same Ubuntu VM I installed it via `sudo
|
63
|
+
apt-get install ffmpeg` and it was able to convert MP3 and M4A files out of the
|
64
|
+
box. The formats you can convert depend on which decoders you have installed.
|
65
|
+
|
66
|
+
If you don't want to install ffmpeg, you could also use one of the many audio
|
67
|
+
format converters to convert your files to WAV before generating waveforms.
|
68
|
+
|
69
|
+
Or you could be all retro and use WAV audio for everything in the first place.
|
70
|
+
|
71
|
+
Some notes
|
72
|
+
==========
|
73
|
+
|
74
|
+
I threw the original version of this together in a day, and then made this
|
75
|
+
second version in another couple. During those days I committed a cardinal sin
|
76
|
+
and didn't write any tests, because decoding sound files and drawing pictures
|
77
|
+
of them is more fun than writing tests. `ChunkyPNG` is cool though and will let
|
78
|
+
you read raw pixel data, so it should be pretty easy to write some tests that
|
79
|
+
actually read the pixel data of a waveform generated from a known source and
|
80
|
+
ensure everything went according to plan. I'll do that later.
|
81
|
+
|
82
|
+
Also, please refactor this/make it faster.
|
83
|
+
|
84
|
+
References
|
85
|
+
==========
|
86
|
+
|
87
|
+
<http://pscode.org/javadoc/src-html/org/pscode/ui/audiotrace/AudioPlotPanel.html#line.996>
|
88
|
+
<http://github.com/pangdudu/rude/blob/master/lib/waveform_narray_testing.rb>
|
89
|
+
<http://stackoverflow.com/questions/1931952/asp-net-create-waveform-image-from-mp3>
|
90
|
+
<http://codeidol.com/java/swing/Audio/Build-an-Audio-Waveform-Display>
|
91
|
+
|
92
|
+
License
|
93
|
+
=======
|
94
|
+
|
95
|
+
Copyright (c) 2010 Ben Alavi
|
96
|
+
|
97
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
98
|
+
|
99
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
100
|
+
|
101
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/bin/waveform
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "waveform"
|
3
|
+
require "optparse"
|
4
|
+
|
5
|
+
options = Waveform::DefaultOptions
|
6
|
+
optparse = OptionParser.new do |o|
|
7
|
+
o.banner = "Usage: waveform [options] source_audio [ouput.png]"
|
8
|
+
|
9
|
+
o.on("-W", "--width WIDTH", "Width (in pixels) of generated waveform image -- Default #{Waveform::DefaultOptions[:width]}.") do |width|
|
10
|
+
options[:width] = width.to_i
|
11
|
+
end
|
12
|
+
|
13
|
+
o.on("-H", "--height HEIGHT", "Height (in pixels) of generated waveform image -- Default #{Waveform::DefaultOptions[:height]}.") do |height|
|
14
|
+
options[:height] = height.to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
o.on("-c", "--color COLOR", "Color (hex code) to draw the waveform. Can also pass 'transparent' to cut it out of the background -- Default #{Waveform::DefaultOptions[:color]}.") do |color|
|
18
|
+
if color == "transparent"
|
19
|
+
options[:color] = :transparent
|
20
|
+
else
|
21
|
+
options[:color] = color.match(/#.*/) ? color : "##{color}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
o.on("-b", "--background COLOR", "Background color (hex code) to draw waveform on -- Default #{Waveform::DefaultOptions[:background_color]}.") do |color|
|
26
|
+
if color == "transparent"
|
27
|
+
options[:background_color] = :transparent
|
28
|
+
else
|
29
|
+
options[:background_color] = color.match(/#.*/) ? color : "##{color}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
o.on("-m", "--method METHOD", "Wave analyzation method (can be 'peak' or 'rms') -- Default '#{Waveform::DefaultOptions[:method]}'.") do |method|
|
34
|
+
options[:method] = method.to_sym
|
35
|
+
end
|
36
|
+
|
37
|
+
options[:quiet] = false
|
38
|
+
o.on("-q", "--quiet", "Don't print anything out when generating waveform") do
|
39
|
+
options[:quiet] = true
|
40
|
+
end
|
41
|
+
|
42
|
+
o.on("-h", "--help", "Display this screen") do
|
43
|
+
puts o
|
44
|
+
exit
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
optparse.parse!
|
49
|
+
|
50
|
+
begin
|
51
|
+
Waveform.new(ARGV[0], options[:quiet] ? nil : $stdout).generate(ARGV[1] || "waveform.png", options)
|
52
|
+
rescue Waveform::ArgumentError => e
|
53
|
+
puts e.message + "\n\n"
|
54
|
+
puts optparse
|
55
|
+
rescue Waveform::RuntimeError => e
|
56
|
+
puts e.message
|
57
|
+
end
|
58
|
+
|
data/lib/waveform.rb
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
require "ruby-audio"
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "oily_png"
|
5
|
+
rescue LoadError
|
6
|
+
require "chunky_png"
|
7
|
+
end
|
8
|
+
|
9
|
+
class Waveform
|
10
|
+
VERSION = "0.0.1"
|
11
|
+
|
12
|
+
DefaultOptions = {
|
13
|
+
:method => :peak,
|
14
|
+
:width => 1800,
|
15
|
+
:height => 280,
|
16
|
+
:background_color => "#666666",
|
17
|
+
:color => "#00ccff"
|
18
|
+
}
|
19
|
+
|
20
|
+
TransparencyMask = "#00ff00"
|
21
|
+
TransparencyAlternate = "#ffff00" # in case the mask is the background color!
|
22
|
+
|
23
|
+
attr_reader :audio
|
24
|
+
|
25
|
+
# Scope these under Waveform so you can catch the ones generated by just this
|
26
|
+
# class.
|
27
|
+
class RuntimeError < ::RuntimeError;end;
|
28
|
+
class ArgumentError < ::ArgumentError;end;
|
29
|
+
|
30
|
+
# Setup a new Waveform for the given audio file. If given anything besides a
|
31
|
+
# WAV file it will attempt to first convert the file to a WAV using ffmpeg.
|
32
|
+
#
|
33
|
+
# Optionally takes an IO stream to which it will print log/benchmarking info.
|
34
|
+
#
|
35
|
+
# See #generate for how to generate the waveform image from the given audio
|
36
|
+
# file.
|
37
|
+
#
|
38
|
+
# Available conversions depend on your installation of ffmpeg.
|
39
|
+
#
|
40
|
+
# Example:
|
41
|
+
#
|
42
|
+
# Waveform.new("mp3s/Kickstart My Heart.mp3")
|
43
|
+
# Waveform.new("mp3s/Kickstart My Heart.mp3", $stdout)
|
44
|
+
#
|
45
|
+
def initialize(audio, log=nil)
|
46
|
+
raise ArgumentError.new("No source audio filename given, must be an existing sound file.") unless audio
|
47
|
+
raise RuntimeError.new("Source audio file '#{audio}' not found.") unless File.exist?(audio)
|
48
|
+
|
49
|
+
@log = Log.new(log)
|
50
|
+
|
51
|
+
if File.extname(audio) != ".wav"
|
52
|
+
@audio = audio.sub /(.+)\.(.+)/, "\\1.wav"
|
53
|
+
raise RuntimeError.new("Unable to decode source '#{audio}' to WAV. Do you have ffmpeg installed with an appropriate decoder for your source file?") unless to_wav(audio, @audio)
|
54
|
+
else
|
55
|
+
@audio = audio
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Generate a Waveform image at the given filename with the given options.
|
60
|
+
#
|
61
|
+
# Available options are:
|
62
|
+
#
|
63
|
+
# :method => The method used to read sample frames, available methods
|
64
|
+
# are peak and rms. peak is probably what you're used to seeing, it uses
|
65
|
+
# the maximum amplitude per sample to generate the waveform, so the
|
66
|
+
# waveform looks more dynamic. RMS gives a more fluid waveform and
|
67
|
+
# probably more accurately reflects what you hear, but isn't as
|
68
|
+
# pronounced (typically).
|
69
|
+
#
|
70
|
+
# Can be :rms or :peak
|
71
|
+
# Default is :peak.
|
72
|
+
#
|
73
|
+
# :width => The width (in pixels) of the final waveform image.
|
74
|
+
# Default is 1800.
|
75
|
+
#
|
76
|
+
# :height => The height (in pixels) of the final waveform image.
|
77
|
+
# Default is 280.
|
78
|
+
#
|
79
|
+
# :background_color => Hex code of the background color of the generated
|
80
|
+
# waveform image.
|
81
|
+
# Default is #666666 (gray).
|
82
|
+
#
|
83
|
+
# :color => Hex code of the color to draw the waveform, or can pass
|
84
|
+
# :transparent to render the waveform transparent (use w/ a solid
|
85
|
+
# color background to achieve a "cutout" effect).
|
86
|
+
# Default is #00ccff (cyan-ish).
|
87
|
+
#
|
88
|
+
# Example:
|
89
|
+
# waveform = Waveform.new("mp3s/Kickstart My Heart.mp3")
|
90
|
+
#
|
91
|
+
# waveform.generate("waves/Kickstart My Heart.png")
|
92
|
+
# waveform.generate("waves/Kickstart My Heart.png", :method => :rms)
|
93
|
+
# waveform.generate("waves/Kickstart My Heart.png", :color => "#ff00ff")
|
94
|
+
#
|
95
|
+
def generate(filename, options={})
|
96
|
+
raise ArgumentError.new("No destination filename given for waveform") unless filename
|
97
|
+
raise RuntimeError.new("Destination file #{filename} exists") if File.exists?(filename)
|
98
|
+
|
99
|
+
options = DefaultOptions.merge(options)
|
100
|
+
|
101
|
+
@log.start!
|
102
|
+
|
103
|
+
# Frames gives the amplitudes for each channel, for our waveform we're
|
104
|
+
# saying the "visual" amplitude is the average of the amplitude across all
|
105
|
+
# the channels. This might be a little weird w/ the "peak" method if the
|
106
|
+
# frames are very wide (i.e. the image width is very small) -- I *think*
|
107
|
+
# the larger the frames are, the more "peaky" the waveform should get,
|
108
|
+
# perhaps to the point of inaccurately reflecting the actual sound.
|
109
|
+
samples = frames(options[:width], options[:method]).collect do |frame|
|
110
|
+
frame.inject(0.0) { |sum, peak| sum + peak } / frame.size
|
111
|
+
end
|
112
|
+
|
113
|
+
@log.timed("\nDrawing...") do
|
114
|
+
background_color = options[:background_color] == :transparent ? ChunkyPNG::Color::TRANSPARENT : options[:background_color]
|
115
|
+
|
116
|
+
if options[:color] == :transparent
|
117
|
+
color = transparent = ChunkyPNG::Color.from_hex(
|
118
|
+
# Have to do this little bit because it's possible the color we were
|
119
|
+
# intending to use a transparency mask *is* the background color, and
|
120
|
+
# then we'd end up wiping out the whole image.
|
121
|
+
options[:background_color].downcase == TransparencyMask ? TransparencyAlternate : TransparencyMask
|
122
|
+
)
|
123
|
+
else
|
124
|
+
color = ChunkyPNG::Color.from_hex(options[:color])
|
125
|
+
end
|
126
|
+
|
127
|
+
image = ChunkyPNG::Image.new(options[:width], options[:height], background_color)
|
128
|
+
# Calling "zero" the middle of the waveform, like there's positive and
|
129
|
+
# negative amplitude
|
130
|
+
zero = options[:height] / 2.0
|
131
|
+
|
132
|
+
samples.each_with_index do |sample, x|
|
133
|
+
# Half the amplitude goes above zero, half below
|
134
|
+
amplitude = sample * options[:height].to_f / 2.0
|
135
|
+
# If you give ChunkyPNG floats for pixel positions all sorts of things
|
136
|
+
# go haywire.
|
137
|
+
image.line(x, (zero - amplitude).round, x, (zero + amplitude).round, color)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Simple transparency masking, it just loops over every pixel and makes
|
141
|
+
# ones which match the transparency mask color completely clear.
|
142
|
+
if transparent
|
143
|
+
(0..image.width - 1).each do |x|
|
144
|
+
(0..image.height - 1).each do |y|
|
145
|
+
image[x, y] = ChunkyPNG::Color.rgba(0, 0, 0, 0) if image[x, y] == transparent
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
image.save(filename)
|
151
|
+
end
|
152
|
+
|
153
|
+
@log.done!("Generated waveform '#{filename}'")
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns a sampling of frames from the given wave file using the given method
|
157
|
+
# the sample size is determined by the given pixel width -- we want one sample
|
158
|
+
# frame per horizontal pixel.
|
159
|
+
def frames(width, method = :peak)
|
160
|
+
raise ArgumentError.new("Unknown sampling method #{method}") unless [ :peak, :rms ].include?(method)
|
161
|
+
|
162
|
+
frames = []
|
163
|
+
|
164
|
+
RubyAudio::Sound.open(audio) do |snd|
|
165
|
+
frames_read = 0
|
166
|
+
frames_per_sample = (snd.info.frames.to_f / width.to_f).to_i
|
167
|
+
sample = RubyAudio::Buffer.new("float", frames_per_sample, snd.info.channels)
|
168
|
+
|
169
|
+
@log.timed("Sampling #{frames_per_sample} frames per sample: ") do
|
170
|
+
while(frames_read = snd.read(sample)) > 0
|
171
|
+
frames << send(method, sample, snd.info.channels)
|
172
|
+
@log.out(".")
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
frames
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
# Decode audio to a wav file, returns true if the decode succeeded or false
|
183
|
+
# otherwise.
|
184
|
+
def to_wav(src, dest)
|
185
|
+
@log.start!
|
186
|
+
@log.out("Decoding source audio '#{src}' to WAV...")
|
187
|
+
|
188
|
+
raise RuntimeError.new("Destination WAV file '#{dest}' exists!") if File.exists?(dest)
|
189
|
+
|
190
|
+
system %Q{ffmpeg -i "#{src}" -f wav "#{dest}" > /dev/null 2>&1}
|
191
|
+
@log.done!
|
192
|
+
|
193
|
+
File.exists?(dest)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Returns an array of the peak of each channel for the given collection of
|
197
|
+
# frames -- the peak is individual to the channel, and the returned collection
|
198
|
+
# of peaks are not (necessarily) from the same frame(s).
|
199
|
+
def peak(frames, channels=1)
|
200
|
+
peak_frame = []
|
201
|
+
(0..channels-1).each do |channel|
|
202
|
+
peak_frame << channel_peak(frames, channel)
|
203
|
+
end
|
204
|
+
peak_frame
|
205
|
+
end
|
206
|
+
|
207
|
+
# Returns an array of rms values for the given frameset where each rms value is
|
208
|
+
# the rms value for that channel.
|
209
|
+
def rms(frames, channels=1)
|
210
|
+
rms_frame = []
|
211
|
+
(0..channels-1).each do |channel|
|
212
|
+
rms_frame << channel_rms(frames, channel)
|
213
|
+
end
|
214
|
+
rms_frame
|
215
|
+
end
|
216
|
+
|
217
|
+
# Returns the peak voltage reached on the given channel in the given collection
|
218
|
+
# of frames.
|
219
|
+
#
|
220
|
+
# TODO: Could lose some resolution and only sample every other frame, would
|
221
|
+
# likely still generate the same waveform as the waveform is so comparitively
|
222
|
+
# low resolution to the original input (in most cases), and would increase
|
223
|
+
# the analyzation speed (maybe).
|
224
|
+
def channel_peak(frames, channel=0)
|
225
|
+
peak = 0.0
|
226
|
+
frames.each do |frame|
|
227
|
+
next if frame.nil?
|
228
|
+
peak = frame[channel].abs if frame[channel].abs > peak
|
229
|
+
end
|
230
|
+
peak
|
231
|
+
end
|
232
|
+
|
233
|
+
# Returns the rms value across the given collection of frames for the given
|
234
|
+
# channel.
|
235
|
+
#
|
236
|
+
# FIXME: this RMS calculation might be wrong...
|
237
|
+
# refactored this from: http://pscode.org/javadoc/src-html/org/pscode/ui/audiotrace/AudioPlotPanel.html#line.996
|
238
|
+
def channel_rms(frames, channel=0)
|
239
|
+
avg = frames.inject(0.0){ |sum, frame| sum += frame ? frame[channel] : 0 }/frames.size.to_f
|
240
|
+
Math.sqrt(frames.inject(0.0){ |sum, frame| sum += frame ? (frame[channel]-avg)**2 : 0 }/frames.size.to_f)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
class Waveform
|
245
|
+
# A simple class for logging + benchmarking, nice to have good feedback on a
|
246
|
+
# long batch operation.
|
247
|
+
#
|
248
|
+
# There's probably 10,000,000 other bechmarking classes, but writing this was
|
249
|
+
# easier than using Google.
|
250
|
+
class Log
|
251
|
+
attr_accessor :io
|
252
|
+
|
253
|
+
def initialize(io=$stdout)
|
254
|
+
@io = io
|
255
|
+
end
|
256
|
+
|
257
|
+
# Prints the given message to the log
|
258
|
+
def out(msg)
|
259
|
+
io.print(msg) if io
|
260
|
+
end
|
261
|
+
|
262
|
+
# Prints the given message to the log followed by the most recent benchmark
|
263
|
+
# (note that it calls .end! which will stop the benchmark)
|
264
|
+
def done!(msg="")
|
265
|
+
out "#{msg} (#{self.end!}s)\n"
|
266
|
+
end
|
267
|
+
|
268
|
+
# Starts a new benchmark clock and returns the index of the new clock.
|
269
|
+
#
|
270
|
+
# If .start! is called again before .end! then the time returned will be
|
271
|
+
# the elapsed time from the next call to start!, and calling .end! again
|
272
|
+
# will return the time from *this* call to start! (that is, the clocks are
|
273
|
+
# LIFO)
|
274
|
+
def start!
|
275
|
+
(@benchmarks ||= []) << Time.now
|
276
|
+
@current = @benchmarks.size - 1
|
277
|
+
end
|
278
|
+
|
279
|
+
# Returns the elapsed time from the most recently started benchmark clock
|
280
|
+
# and ends the benchmark, so that a subsequent call to .end! will return
|
281
|
+
# the elapsed time from the previously started benchmark clock.
|
282
|
+
def end!
|
283
|
+
elapsed = (Time.now - @benchmarks[@current])
|
284
|
+
@current -= 1
|
285
|
+
elapsed
|
286
|
+
end
|
287
|
+
|
288
|
+
# Returns the elapsed time from the benchmark clock w/ the given index (as
|
289
|
+
# returned from when .start! was called).
|
290
|
+
def time?(index)
|
291
|
+
Time.now - @benchmarks[index]
|
292
|
+
end
|
293
|
+
|
294
|
+
# Benchmarks the given block, printing out the given message first (if
|
295
|
+
# given).
|
296
|
+
def timed(message=nil, &block)
|
297
|
+
start!
|
298
|
+
out(message) if message
|
299
|
+
yield
|
300
|
+
done!
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
data/waveform.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "./lib/waveform"
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "waveform"
|
5
|
+
s.version = Waveform::VERSION
|
6
|
+
s.summary = "Generate waveform images from WAV and MP3 files"
|
7
|
+
s.description = "Generate waveform images from WAV and MP3 files -- in your code or via included CLI."
|
8
|
+
s.authors = ["Ben Alavi"]
|
9
|
+
s.email = ["benalavi@gmail.com"]
|
10
|
+
s.homepage = "http://github.com/benalavi/waveform"
|
11
|
+
|
12
|
+
s.files = Dir[
|
13
|
+
"LICENSE",
|
14
|
+
"README.md",
|
15
|
+
"Rakefile",
|
16
|
+
"lib/**/*.rb",
|
17
|
+
"*.gemspec",
|
18
|
+
"test/**/*.rb",
|
19
|
+
"bin/*"
|
20
|
+
]
|
21
|
+
|
22
|
+
s.executables = "waveform"
|
23
|
+
|
24
|
+
s.add_dependency "ruby-audio"
|
25
|
+
s.add_dependency "chunky_png"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: waveform
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ben Alavi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-07-16 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: ruby-audio
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: chunky_png
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
description: Generate waveform images from WAV and MP3 files -- in your code or via included CLI.
|
38
|
+
email:
|
39
|
+
- benalavi@gmail.com
|
40
|
+
executables:
|
41
|
+
- waveform
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
files:
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- lib/waveform.rb
|
50
|
+
- waveform.gemspec
|
51
|
+
- bin/waveform
|
52
|
+
homepage: http://github.com/benalavi/waveform
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.8.5
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Generate waveform images from WAV and MP3 files
|
79
|
+
test_files: []
|
80
|
+
|