waveformjson 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +97 -0
- data/Rakefile +5 -0
- data/bin/waveformjson +52 -0
- data/lib/waveformjson.rb +195 -0
- data/lib/waveformjson/version.rb +3 -0
- data/test/waveform_test.rb +54 -0
- data/waveformjson.gemspec +25 -0
- metadata +72 -0
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
Waveformjson
|
2
|
+
========
|
3
|
+
|
4
|
+
Waveformjson is based on [waveform](https://github.com/benalavi/waveform) and functions the same. The only difference is that it generates json instead of json files.
|
5
|
+
|
6
|
+
Installation
|
7
|
+
============
|
8
|
+
|
9
|
+
Waveformjson depends on `ruby-audio`, which in turn depends on libsndfile.
|
10
|
+
|
11
|
+
Build libsndfile from (http://www.mega-nerd.com/libsndfile/), install it via `apt` (`sudo apt-get install libsndfile1-dev`), `libsndfile` in macports, etc...
|
12
|
+
|
13
|
+
Then:
|
14
|
+
|
15
|
+
$ gem install waveformjson
|
16
|
+
|
17
|
+
or add following to Gemfile:
|
18
|
+
|
19
|
+
gem 'waveformjson'
|
20
|
+
|
21
|
+
CLI Usage
|
22
|
+
=========
|
23
|
+
|
24
|
+
$ waveformjson song.wav waveform.json
|
25
|
+
|
26
|
+
There are some nifty options you can supply to switch things up:
|
27
|
+
|
28
|
+
-m sets the method used to sample the source audio file, it can either be
|
29
|
+
'peak' or 'rms'. 'peak' is probably what you want because it looks
|
30
|
+
cooler, but 'rms' is closer to what you actually hear.
|
31
|
+
-W sets the width (in pixels) of the waveform.
|
32
|
+
|
33
|
+
There are also some less-nifty options:
|
34
|
+
|
35
|
+
-q will generate your waveform without printing out a bunch of stuff.
|
36
|
+
-h will print out a help screen with all this info.
|
37
|
+
-F will automatically overwrite destination file.
|
38
|
+
|
39
|
+
Usage in code
|
40
|
+
=============
|
41
|
+
|
42
|
+
The CLI is really just a thin wrapper around the Waveformjson class, which you can also use in your programs for reasons I haven't thought of. The Waveform class 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 a wrapper for `libsndfile`, on my Ubuntu 10.04LTS VM I installed the necessary libs to build `ruby-audio` via: `sudo apt-get install libsndfile1-dev`.
|
50
|
+
|
51
|
+
Converting MP3 to WAV
|
52
|
+
=====================
|
53
|
+
|
54
|
+
Waveform used to (very thinly) wrap ffmpeg to convert MP3 (and whatever other format) to WAV audio before processing the WAV and generating the waveform image. It seemed a bit presumptious for Waveform to handle that, especially since you might want to use your own conversion options (i.e. downsampling the bitrate to generate waveforms faster, etc...).
|
55
|
+
|
56
|
+
If you happen to be using ffmpeg, you can easily convert MP3 to WAV via:
|
57
|
+
|
58
|
+
ffmpeg -i "/path/to/source/file.mp3" -f wav "/path/to/output/file.wav"
|
59
|
+
|
60
|
+
Tests
|
61
|
+
=====
|
62
|
+
|
63
|
+
$ rake
|
64
|
+
|
65
|
+
|
66
|
+
Sample sound file used in tests is in the Public Domain from soundbible.com: <http://soundbible.com/1598-Electronic-Chime.html>.
|
67
|
+
|
68
|
+
References
|
69
|
+
==========
|
70
|
+
|
71
|
+
<http://pscode.org/javadoc/src-html/org/pscode/ui/audiotrace/AudioPlotPanel.html#line.996>
|
72
|
+
<http://github.com/pangdudu/rude/blob/master/lib/waveform_narray_testing.rb>
|
73
|
+
<http://stackoverflow.com/questions/1931952/asp-net-create-waveform-image-from-mp3>
|
74
|
+
<http://codeidol.com/java/swing/Audio/Build-an-Audio-Waveform-Display>
|
75
|
+
|
76
|
+
License
|
77
|
+
=======
|
78
|
+
|
79
|
+
Copyright (c) 2013 liufengyun
|
80
|
+
|
81
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
82
|
+
this software and associated documentation files (the "Software"), to deal in
|
83
|
+
the Software without restriction, including without limitation the rights to
|
84
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
85
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
86
|
+
so, subject to the following conditions:
|
87
|
+
|
88
|
+
The above copyright notice and this permission notice shall be included in all
|
89
|
+
copies or substantial portions of the Software.
|
90
|
+
|
91
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
92
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
93
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
94
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
95
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
96
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
97
|
+
SOFTWARE.
|
data/Rakefile
ADDED
data/bin/waveformjson
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "waveformjson"
|
3
|
+
require "optparse"
|
4
|
+
|
5
|
+
options = Waveformjson::DefaultOptions
|
6
|
+
optparse = OptionParser.new do |o|
|
7
|
+
o.banner = "Usage: waveformjson [options] source_audio [ouput.json]"
|
8
|
+
o.version = Waveformjson::VERSION
|
9
|
+
|
10
|
+
o.on("-m", "--method METHOD", "Wave analyzation method (can be 'peak' or 'rms') -- Default '#{Waveformjson::DefaultOptions[:method]}'.") do |method|
|
11
|
+
options[:method] = method.to_sym
|
12
|
+
end
|
13
|
+
|
14
|
+
o.on("-W", "--width WIDTH", "Width (in pixels) of generated waveform image -- Default #{Waveformjson::DefaultOptions[:width]}.") do |width|
|
15
|
+
options[:width] = width.to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
options[:logger] = $stdout
|
19
|
+
o.on("-q", "--quiet", "Don't print anything out when generating waveform") do
|
20
|
+
options[:logger] = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
options[:force] = false
|
24
|
+
o.on("-F", "--force", "Force generationg of waveform if file exists") do
|
25
|
+
options[:force] = true
|
26
|
+
end
|
27
|
+
|
28
|
+
o.on("-h", "--help", "Display this screen") do
|
29
|
+
puts o
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
optparse.parse!
|
35
|
+
|
36
|
+
begin
|
37
|
+
output = ARGV[1] || "waveform.json"
|
38
|
+
|
39
|
+
if File.exists?(output) && !options[:force]
|
40
|
+
raise RuntimeError.new("Destination file #{output} exists. Use --force if you want to automatically remove it.")
|
41
|
+
end
|
42
|
+
|
43
|
+
json = Waveformjson.generate(ARGV[0], options)
|
44
|
+
File.open(output) do |f|
|
45
|
+
f << json.to_s
|
46
|
+
end
|
47
|
+
rescue ArgumentError => e
|
48
|
+
puts e.message + "\n\n"
|
49
|
+
puts optparse
|
50
|
+
rescue RuntimeError => e
|
51
|
+
puts e.message
|
52
|
+
end
|
data/lib/waveformjson.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "waveformjson/version")
|
2
|
+
|
3
|
+
require "ruby-audio"
|
4
|
+
|
5
|
+
class Waveformjson
|
6
|
+
DefaultOptions = {
|
7
|
+
:method => :peak,
|
8
|
+
:width => 1800,
|
9
|
+
:logger => nil
|
10
|
+
}
|
11
|
+
|
12
|
+
attr_reader :source
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Generate a Waveform image at the given filename with the given options.
|
16
|
+
#
|
17
|
+
# Available options (all optional) are:
|
18
|
+
#
|
19
|
+
# :method => The method used to read sample frames, available methods
|
20
|
+
# are peak and rms. peak is probably what you're used to seeing, it uses
|
21
|
+
# the maximum amplitude per sample to generate the waveform, so the
|
22
|
+
# waveform looks more dynamic. RMS gives a more fluid waveform and
|
23
|
+
# probably more accurately reflects what you hear, but isn't as
|
24
|
+
# pronounced (typically).
|
25
|
+
#
|
26
|
+
# Can be :rms or :peak
|
27
|
+
# Default is :peak.
|
28
|
+
#
|
29
|
+
# :width => The width (in pixels) of the final waveform image.
|
30
|
+
# Default is 1800.
|
31
|
+
#
|
32
|
+
# :logger => IOStream to log progress to.
|
33
|
+
#
|
34
|
+
# Example:
|
35
|
+
# Waveformjson.generate("Kickstart My Heart.wav")
|
36
|
+
# Waveformjson.generate("Kickstart My Heart.wav", :method => :rms)
|
37
|
+
# Waveformjson.generate("Kickstart My Heart.wav", :logger => $stdout)
|
38
|
+
#
|
39
|
+
def generate(source, options={})
|
40
|
+
options = DefaultOptions.merge(options)
|
41
|
+
|
42
|
+
raise ArgumentError.new("No source audio filename given, must be an existing sound file.") unless source
|
43
|
+
raise RuntimeError.new("Source audio file '#{source}' not found.") unless File.exist?(source)
|
44
|
+
|
45
|
+
@log = Log.new(options[:logger])
|
46
|
+
@log.start!
|
47
|
+
|
48
|
+
# Frames gives the amplitudes for each channel, for our waveform we're
|
49
|
+
# saying the "visual" amplitude is the average of the amplitude across all
|
50
|
+
# the channels. This might be a little weird w/ the "peak" method if the
|
51
|
+
# frames are very wide (i.e. the image width is very small) -- I *think*
|
52
|
+
# the larger the frames are, the more "peaky" the waveform should get,
|
53
|
+
# perhaps to the point of inaccurately reflecting the actual sound.
|
54
|
+
samples = frames(source, options[:width], options[:method]).collect do |frame|
|
55
|
+
frame.inject(0.0) { |sum, peak| sum + peak } / frame.size
|
56
|
+
end
|
57
|
+
|
58
|
+
samples
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# Returns a sampling of frames from the given RubyAudio::Sound using the
|
64
|
+
# given method the sample size is determined by the given pixel width --
|
65
|
+
# we want one sample frame per horizontal pixel.
|
66
|
+
def frames(source, width, method = :peak)
|
67
|
+
raise ArgumentError.new("Unknown sampling method #{method}") unless [ :peak, :rms ].include?(method)
|
68
|
+
|
69
|
+
frames = []
|
70
|
+
|
71
|
+
RubyAudio::Sound.open(source) do |audio|
|
72
|
+
frames_read = 0
|
73
|
+
frames_per_sample = (audio.info.frames.to_f / width.to_f).floor
|
74
|
+
sample = RubyAudio::Buffer.new("float", frames_per_sample, audio.info.channels)
|
75
|
+
|
76
|
+
@log.timed("Sampling #{frames_per_sample} frames per sample: ") do
|
77
|
+
while(frames_read = audio.read(sample)) > 0
|
78
|
+
frames << send(method, sample, audio.info.channels)
|
79
|
+
@log.out(".")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
frames
|
85
|
+
rescue RubyAudio::Error => e
|
86
|
+
raise e unless e.message == "File contains data in an unknown format."
|
87
|
+
raise RuntimeError.new("Source audio file #{source} could not be read by RubyAudio library -- Hint: non-WAV files are no longer supported, convert to WAV first using something like ffmpeg (RubyAudio: #{e.message})")
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns an array of the peak of each channel for the given collection of
|
91
|
+
# frames -- the peak is individual to the channel, and the returned collection
|
92
|
+
# of peaks are not (necessarily) from the same frame(s).
|
93
|
+
def peak(frames, channels=1)
|
94
|
+
peak_frame = []
|
95
|
+
(0..channels-1).each do |channel|
|
96
|
+
peak_frame << channel_peak(frames, channel)
|
97
|
+
end
|
98
|
+
peak_frame
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns an array of rms values for the given frameset where each rms value is
|
102
|
+
# the rms value for that channel.
|
103
|
+
def rms(frames, channels=1)
|
104
|
+
rms_frame = []
|
105
|
+
(0..channels-1).each do |channel|
|
106
|
+
rms_frame << channel_rms(frames, channel)
|
107
|
+
end
|
108
|
+
rms_frame
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns the peak voltage reached on the given channel in the given collection
|
112
|
+
# of frames.
|
113
|
+
#
|
114
|
+
# TODO: Could lose some resolution and only sample every other frame, would
|
115
|
+
# likely still generate the same waveform as the waveform is so comparitively
|
116
|
+
# low resolution to the original input (in most cases), and would increase
|
117
|
+
# the analyzation speed (maybe).
|
118
|
+
def channel_peak(frames, channel=0)
|
119
|
+
peak = 0.0
|
120
|
+
frames.each do |frame|
|
121
|
+
next if frame.nil?
|
122
|
+
frame = Array(frame)
|
123
|
+
peak = frame[channel].abs if frame[channel].abs > peak
|
124
|
+
end
|
125
|
+
peak
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns the rms value across the given collection of frames for the given
|
129
|
+
# channel.
|
130
|
+
def channel_rms(frames, channel=0)
|
131
|
+
Math.sqrt(frames.inject(0.0){ |sum, frame| sum += (frame ? Array(frame)[channel] ** 2 : 0) } / frames.size)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class Waveformjson
|
137
|
+
# A simple class for logging + benchmarking, nice to have good feedback on a
|
138
|
+
# long batch operation.
|
139
|
+
#
|
140
|
+
# There's probably 10,000,000 other bechmarking classes, but writing this was
|
141
|
+
# easier than using Google.
|
142
|
+
class Log
|
143
|
+
attr_accessor :io
|
144
|
+
|
145
|
+
def initialize(io=$stdout)
|
146
|
+
@io = io
|
147
|
+
end
|
148
|
+
|
149
|
+
# Prints the given message to the log
|
150
|
+
def out(msg)
|
151
|
+
io.print(msg) if io
|
152
|
+
end
|
153
|
+
|
154
|
+
# Prints the given message to the log followed by the most recent benchmark
|
155
|
+
# (note that it calls .end! which will stop the benchmark)
|
156
|
+
def done!(msg="")
|
157
|
+
out "#{msg} (#{self.end!}s)\n"
|
158
|
+
end
|
159
|
+
|
160
|
+
# Starts a new benchmark clock and returns the index of the new clock.
|
161
|
+
#
|
162
|
+
# If .start! is called again before .end! then the time returned will be
|
163
|
+
# the elapsed time from the next call to start!, and calling .end! again
|
164
|
+
# will return the time from *this* call to start! (that is, the clocks are
|
165
|
+
# LIFO)
|
166
|
+
def start!
|
167
|
+
(@benchmarks ||= []) << Time.now
|
168
|
+
@current = @benchmarks.size - 1
|
169
|
+
end
|
170
|
+
|
171
|
+
# Returns the elapsed time from the most recently started benchmark clock
|
172
|
+
# and ends the benchmark, so that a subsequent call to .end! will return
|
173
|
+
# the elapsed time from the previously started benchmark clock.
|
174
|
+
def end!
|
175
|
+
elapsed = (Time.now - @benchmarks[@current])
|
176
|
+
@current -= 1
|
177
|
+
elapsed
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns the elapsed time from the benchmark clock w/ the given index (as
|
181
|
+
# returned from when .start! was called).
|
182
|
+
def time?(index)
|
183
|
+
Time.now - @benchmarks[index]
|
184
|
+
end
|
185
|
+
|
186
|
+
# Benchmarks the given block, printing out the given message first (if
|
187
|
+
# given).
|
188
|
+
def timed(message=nil, &block)
|
189
|
+
start!
|
190
|
+
out(message) if message
|
191
|
+
yield
|
192
|
+
done!
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "waveformjson"))
|
2
|
+
|
3
|
+
require "test/unit"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module Helpers
|
7
|
+
def fixture(file)
|
8
|
+
File.join(File.dirname(__FILE__), "fixtures", file)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class WaveformTest < Test::Unit::TestCase
|
13
|
+
include Helpers
|
14
|
+
extend Helpers
|
15
|
+
|
16
|
+
def test_generates_waveform
|
17
|
+
assert_equal Waveformjson::DefaultOptions[:width], Waveformjson.generate(fixture("sample.wav")).size
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_generates_waveform_from_mono_audio_source_via_peak
|
21
|
+
assert_equal Waveformjson::DefaultOptions[:width], Waveformjson.generate(fixture("mono_sample.wav")).size
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_generates_waveform_from_mono_audio_source_via_rms
|
25
|
+
assert_equal Waveformjson::DefaultOptions[:width], Waveformjson.generate(fixture("mono_sample.wav"), :method => :rms).size
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_uses_rms_instead_of_peak
|
29
|
+
rms = Waveformjson.generate(fixture("sample.wav"))
|
30
|
+
peak = Waveformjson.generate(fixture("sample.wav"), :method => :rms)
|
31
|
+
|
32
|
+
assert peak[44] > rms[43]
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_is_900px_wide
|
36
|
+
data = Waveformjson.generate(fixture("sample.wav"), :width => 900)
|
37
|
+
|
38
|
+
assert_equal 900, data.size
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_raises_error_if_not_given_readable_audio_source
|
42
|
+
assert_raise(RuntimeError) do
|
43
|
+
Waveformjson.generate(fixture("sample.txt"))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_raises_deprecation_exception_if_ruby_audio_fails_to_read_source_file
|
48
|
+
begin
|
49
|
+
Waveformjson.generate(fixture("sample.txt"))
|
50
|
+
rescue RuntimeError => e
|
51
|
+
assert_match /Hint: non-WAV files are no longer supported, convert to WAV first using something like ffmpeg/, e.message
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "./lib/waveformjson/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "waveformjson"
|
5
|
+
s.version = Waveformjson::VERSION
|
6
|
+
s.summary = "Generate waveform json from audio files"
|
7
|
+
s.description = "Generate waveform json from audio files. Includes a Waveform class for generating waveforms in your code as well as a simple command-line program called 'waveform' for generating on the command line."
|
8
|
+
s.authors = ["liufengyun"]
|
9
|
+
s.email = ["liufengyunchina@gmail.com"]
|
10
|
+
s.homepage = "http://github.com/liufengyun/waveformjson"
|
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 = "waveformjson"
|
23
|
+
|
24
|
+
s.add_dependency "ruby-audio"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: waveformjson
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- liufengyun
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ruby-audio
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Generate waveform json from audio files. Includes a Waveform class for
|
31
|
+
generating waveforms in your code as well as a simple command-line program called
|
32
|
+
'waveform' for generating on the command line.
|
33
|
+
email:
|
34
|
+
- liufengyunchina@gmail.com
|
35
|
+
executables:
|
36
|
+
- waveformjson
|
37
|
+
extensions: []
|
38
|
+
extra_rdoc_files: []
|
39
|
+
files:
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- lib/waveformjson/version.rb
|
43
|
+
- lib/waveformjson.rb
|
44
|
+
- waveformjson.gemspec
|
45
|
+
- test/waveform_test.rb
|
46
|
+
- bin/waveformjson
|
47
|
+
homepage: http://github.com/liufengyun/waveformjson
|
48
|
+
licenses: []
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.8.24
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Generate waveform json from audio files
|
71
|
+
test_files: []
|
72
|
+
has_rdoc:
|