json-waveform 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/README.md +65 -0
- data/bin/json-waveform +43 -0
- data/json-waveform.gemspec +25 -0
- data/lib/json-waveform/version.rb +3 -0
- data/lib/json-waveform.rb +150 -0
- data/makefile +4 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6865a7f5c7f07c8c15f4f58a06b1c6ff840dae4e
|
4
|
+
data.tar.gz: 3b6d9f92cdc05efc309b0125e7480149620c6aba
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6147921f5a577d809f1fff7ebc1adfb5d29177c030bb1882d7043768ceb5e8705976fbb8a53e632c2a72f1eb3e4cf63591b8094ab44596ed47e69a40aaab11d3
|
7
|
+
data.tar.gz: aaeea25e81dd1e3a7d68090e8398e191b2c37234a11263f05d625f0b3708407ec741d42b9f113559d91e82e44d3f6c004bc71c75257cb74b65bb324707703b4e
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2014 Esteban Pastorino
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
JsonWaveform
|
2
|
+
========
|
3
|
+
|
4
|
+
JsonWaveform is a class to generate waveform json files from audio files. You can combine it with http://waveformjs.org/ to create awesome waveforms in webpages. It also comes with a handy CLI you can use to generate waveform json files on the command line.
|
5
|
+
|
6
|
+
It is *heavily* influenced by https://github.com/benalavi/waveform
|
7
|
+
|
8
|
+
Installation
|
9
|
+
============
|
10
|
+
|
11
|
+
Waveform depends on `ruby-audio`, which in turn depends on libsndfile.
|
12
|
+
|
13
|
+
Build libsndfile from (http://www.mega-nerd.com/libsndfile/), install it via `apt` (`sudo apt-get install libsndfile1-dev`), `libsndfile` in brew, etc...
|
14
|
+
|
15
|
+
Then:
|
16
|
+
|
17
|
+
$ gem install json-waveform
|
18
|
+
|
19
|
+
NOTE: If `ruby-audio` fails to compile and you have `libsndfile` available, it may be because of this: http://stackoverflow.com/questions/19919640/ruby-audio-1-6-1-install-error-with-installed-libsndfile-1-0-25
|
20
|
+
|
21
|
+
CLI Usage
|
22
|
+
=========
|
23
|
+
|
24
|
+
$ json-waveform song.wav
|
25
|
+
|
26
|
+
There are some nifty options you can supply to switch things up:
|
27
|
+
|
28
|
+
-s sets the samples of the waveform.
|
29
|
+
-A sets the max amplitude.
|
30
|
+
-m sets the method used to sample the source audio file, it can either be
|
31
|
+
'peak' or 'rms'. 'peak' is probably what you want because it looks
|
32
|
+
cooler, but 'rms' is closer to what you actually hear.
|
33
|
+
|
34
|
+
And to see (almost) this same info:
|
35
|
+
|
36
|
+
-h will print out a help screen with all this info.
|
37
|
+
|
38
|
+
$ json-waveform Motley\ Crüe/Kickstart\ my\ Heart.wav
|
39
|
+
|
40
|
+
Usage in code
|
41
|
+
=============
|
42
|
+
|
43
|
+
The CLI is really just a thin wrapper around the JsonWaveform class, which you can also use in your programs for reasons I haven't thought of. The JsonWaveform class takes pretty much the same options as the CLI when generating waveforms.
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
JsonWaveform("foo.wav", samples: 1000) # => [ 0, 0.1, 0.15, ... ]
|
47
|
+
```
|
48
|
+
|
49
|
+
Requirements
|
50
|
+
============
|
51
|
+
|
52
|
+
`ruby-audio`
|
53
|
+
|
54
|
+
The gem version, *not* the old outdated library listed on RAA. `ruby-audio` is a wrapper for `libsndfile`. To install the necessary libs to build `ruby-audio` you can do `sudo apt-get install libsndfile1-dev` on Ubuntu or `brew install libsndfile` on OSX.
|
55
|
+
|
56
|
+
|
57
|
+
Tests
|
58
|
+
=====
|
59
|
+
|
60
|
+
**They are not there yet, but will be run using: **
|
61
|
+
|
62
|
+
$ make
|
63
|
+
|
64
|
+
Sample sound file used in tests is in the Public Domain from soundbible.com: <http://soundbible.com/1598-Electronic-Chime.html>.
|
65
|
+
|
data/bin/json-waveform
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "json-waveform"
|
3
|
+
require "optparse"
|
4
|
+
|
5
|
+
options = JsonWaveform::DEFAULT_OPTIONS
|
6
|
+
optparse = OptionParser.new do |o|
|
7
|
+
o.banner = "Usage: waveform [options] source_audio"
|
8
|
+
o.version = Waveform::VERSION
|
9
|
+
|
10
|
+
o.on("-s", "--samples SAMPLES", "Samples generated from the original file (i.e.: waveform width) -- Default #{Waveform::DefaultOptions[:samples]}.") do |samples|
|
11
|
+
options[:samples] = samples.to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
o.on("-A", "--amplitude AMPLITUDE", "Max amplitude of the waveform values-- Default #{Waveform::DefaultOptions[:amplitude]}.") do |amplitude|
|
15
|
+
options[:amplitude] = amplitude.to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
o.on("-a", "--autosample MSEC", "Sets the width of the waveform based on the audio length, using a resolution of the given msec per sample.") do |msec|
|
19
|
+
options[:auto_sample] = msec.to_i
|
20
|
+
end
|
21
|
+
|
22
|
+
o.on("-m", "--method METHOD", "Wave analyzation method (can be 'peak' or 'rms') -- Default '#{Waveform::DefaultOptions[:method]}'.") do |method|
|
23
|
+
options[:method] = method.to_sym
|
24
|
+
end
|
25
|
+
|
26
|
+
o.on("-h", "--help", "Display this screen") do
|
27
|
+
puts o
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
optparse.parse!
|
33
|
+
|
34
|
+
begin
|
35
|
+
result = Waveform.generate(ARGV[0], options)
|
36
|
+
puts result
|
37
|
+
rescue Waveform::ArgumentError => e
|
38
|
+
puts e.message + "\n\n"
|
39
|
+
puts optparse
|
40
|
+
rescue Waveform::RuntimeError => e
|
41
|
+
puts e.message
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "./lib/json-waveform/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "json-waveform"
|
5
|
+
s.version = JsonWaveform::VERSION
|
6
|
+
s.summary = "Generate waveform JSON files from audio files"
|
7
|
+
s.description = "Generate waveform JSON information from audio files, compatible with http://waveformjs.org/."
|
8
|
+
s.authors = ["Esteban pastorino"]
|
9
|
+
s.email = ["ejpastorino@gmail.com"]
|
10
|
+
s.homepage = "http://github.com/kitop/json-waveform"
|
11
|
+
|
12
|
+
s.files = Dir[
|
13
|
+
"LICENSE",
|
14
|
+
"README.md",
|
15
|
+
"makefile",
|
16
|
+
"lib/**/*.rb",
|
17
|
+
"*.gemspec",
|
18
|
+
"test/**/*.rb",
|
19
|
+
"bin/*"
|
20
|
+
]
|
21
|
+
|
22
|
+
s.executables << "json-waveform"
|
23
|
+
|
24
|
+
s.add_dependency "ruby-audio"
|
25
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "json-waveform/version")
|
2
|
+
|
3
|
+
require "ruby-audio"
|
4
|
+
|
5
|
+
class JsonWaveform
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
:method => :peak,
|
8
|
+
:samples => 1800,
|
9
|
+
:amplitude => 1
|
10
|
+
}
|
11
|
+
|
12
|
+
# Scope these under Waveform so you can catch the ones generated by just this
|
13
|
+
# class.
|
14
|
+
class RuntimeError < ::RuntimeError;end;
|
15
|
+
class ArgumentError < ::ArgumentError;end;
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# Generate a Waveform JSON file from the given filename with the given options.
|
19
|
+
#
|
20
|
+
# Available options (all optional) are:
|
21
|
+
#
|
22
|
+
# :method => The method used to read sample frames, available methods
|
23
|
+
# are peak and rms. peak is probably what you're used to seeing, it uses
|
24
|
+
# the maximum amplitude per sample to generate the waveform, so the
|
25
|
+
# waveform looks more dynamic. RMS gives a more fluid waveform and
|
26
|
+
# probably more accurately reflects what you hear, but isn't as
|
27
|
+
# pronounced (typically).
|
28
|
+
#
|
29
|
+
# Can be :rms or :peak
|
30
|
+
# Default is :peak.
|
31
|
+
#
|
32
|
+
# :samples => The amount of samples wanted
|
33
|
+
# Default is 1800.
|
34
|
+
#
|
35
|
+
# :amplitude => The amplitude of the final values
|
36
|
+
# Default is 1.
|
37
|
+
#
|
38
|
+
# :auto_width => msec per sample. This will overwrite the sample of the
|
39
|
+
# final waveform depending on the length of the audio file.
|
40
|
+
# Example:
|
41
|
+
# 100 => 1 sample per 100 msec; a one minute audio file will result in a width of 600 samples
|
42
|
+
#
|
43
|
+
# Example:
|
44
|
+
# JsonWaveform.generate("Kickstart My Heart.wav")
|
45
|
+
# JsonWaveform.generate("Kickstart My Heart.wav", :method => :rms)
|
46
|
+
#
|
47
|
+
def generate(source, options={})
|
48
|
+
options = DEFAULT_OPTIONS.merge(options)
|
49
|
+
|
50
|
+
raise ArgumentError.new("No source audio filename given, must be an existing sound file.") unless source
|
51
|
+
raise RuntimeError.new("Source audio file '#{source}' not found.") unless File.exist?(source)
|
52
|
+
|
53
|
+
if options[:auto_samples]
|
54
|
+
RubyAudio::Sound.open(source) do |audio|
|
55
|
+
options[:samples] = (audio.info.length * 1000 / options[:auto_samples].to_i).ceil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Frames gives the amplitudes for each channel, for our waveform we're
|
60
|
+
# saying the "visual" amplitude is the average of the amplitude across all
|
61
|
+
# the channels. This might be a little weird w/ the "peak" method if the
|
62
|
+
# frames are very wide (i.e. the image width is very small) -- I *think*
|
63
|
+
# the larger the frames are, the more "peaky" the waveform should get,
|
64
|
+
# perhaps to the point of inaccurately reflecting the actual sound.
|
65
|
+
samples = frames(source, options[:samples], options[:method]).collect do |frame|
|
66
|
+
frame.inject(0.0) { |sum, peak| sum + peak } / frame.size
|
67
|
+
end
|
68
|
+
|
69
|
+
normalize(samples, options)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Returns a sampling of frames from the given RubyAudio::Sound using the
|
75
|
+
# given method
|
76
|
+
def frames(source, samples, method = :peak)
|
77
|
+
raise ArgumentError.new("Unknown sampling method #{method}") unless [ :peak, :rms ].include?(method)
|
78
|
+
|
79
|
+
frames = []
|
80
|
+
|
81
|
+
RubyAudio::Sound.open(source) do |audio|
|
82
|
+
frames_read = 0
|
83
|
+
frames_per_sample = (audio.info.frames.to_f / samples.to_f).to_i
|
84
|
+
sample = RubyAudio::Buffer.new("float", frames_per_sample, audio.info.channels)
|
85
|
+
|
86
|
+
while(frames_read = audio.read(sample)) > 0
|
87
|
+
frames << send(method, sample, audio.info.channels)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
frames
|
92
|
+
rescue RubyAudio::Error => e
|
93
|
+
raise e unless e.message == "File contains data in an unknown format."
|
94
|
+
raise JsonWaveform::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})")
|
95
|
+
end
|
96
|
+
|
97
|
+
def normalize(samples, options)
|
98
|
+
samples.map do |sample|
|
99
|
+
# Half the amplitude goes above zero, half below
|
100
|
+
amplitude = sample * options[:amplitude].to_f
|
101
|
+
rounded = amplitude.round(2)
|
102
|
+
rounded.zero? || rounded == 1 ? rounded.to_i : rounded
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns an array of the peak of each channel for the given collection of
|
107
|
+
# frames -- the peak is individual to the channel, and the returned collection
|
108
|
+
# of peaks are not (necessarily) from the same frame(s).
|
109
|
+
def peak(frames, channels=1)
|
110
|
+
peak_frame = []
|
111
|
+
(0..channels-1).each do |channel|
|
112
|
+
peak_frame << channel_peak(frames, channel)
|
113
|
+
end
|
114
|
+
peak_frame
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns an array of rms values for the given frameset where each rms value is
|
118
|
+
# the rms value for that channel.
|
119
|
+
def rms(frames, channels=1)
|
120
|
+
rms_frame = []
|
121
|
+
(0..channels-1).each do |channel|
|
122
|
+
rms_frame << channel_rms(frames, channel)
|
123
|
+
end
|
124
|
+
rms_frame
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns the peak voltage reached on the given channel in the given collection
|
128
|
+
# of frames.
|
129
|
+
#
|
130
|
+
# TODO: Could lose some resolution and only sample every other frame, would
|
131
|
+
# likely still generate the same waveform as the waveform is so comparitively
|
132
|
+
# low resolution to the original input (in most cases), and would increase
|
133
|
+
# the analyzation speed (maybe).
|
134
|
+
def channel_peak(frames, channel=0)
|
135
|
+
peak = 0.0
|
136
|
+
frames.each do |frame|
|
137
|
+
next if frame.nil?
|
138
|
+
frame = Array(frame)
|
139
|
+
peak = frame[channel].abs if frame[channel].abs > peak
|
140
|
+
end
|
141
|
+
peak
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns the rms value across the given collection of frames for the given
|
145
|
+
# channel.
|
146
|
+
def channel_rms(frames, channel=0)
|
147
|
+
Math.sqrt(frames.inject(0.0){ |sum, frame| sum += (frame ? Array(frame)[channel] ** 2 : 0) } / frames.size)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
data/makefile
ADDED
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: json-waveform
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Esteban pastorino
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ruby-audio
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Generate waveform JSON information from audio files, compatible with
|
28
|
+
http://waveformjs.org/.
|
29
|
+
email:
|
30
|
+
- ejpastorino@gmail.com
|
31
|
+
executables:
|
32
|
+
- json-waveform
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- LICENSE
|
37
|
+
- README.md
|
38
|
+
- bin/json-waveform
|
39
|
+
- json-waveform.gemspec
|
40
|
+
- lib/json-waveform.rb
|
41
|
+
- lib/json-waveform/version.rb
|
42
|
+
- makefile
|
43
|
+
homepage: http://github.com/kitop/json-waveform
|
44
|
+
licenses: []
|
45
|
+
metadata: {}
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 2.2.2
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: Generate waveform JSON files from audio files
|
66
|
+
test_files: []
|