waveformjson 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|