json-waveform 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.
- 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: []
|