json-waveform 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,3 @@
1
+ class JsonWaveform
2
+ VERSION = "0.0.1".freeze
3
+ 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
@@ -0,0 +1,4 @@
1
+ .PHONY: console
2
+
3
+ console:
4
+ irb -I./lib -r json-waveform
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: []