├── test ├── fixtures │ ├── sample.txt │ ├── sample.wav │ ├── sample.rms.json │ └── sample.peak.json └── json_waveform_test.rb ├── .gitignore ├── lib ├── json-waveform │ └── version.rb └── json-waveform.rb ├── makefile ├── json-waveform.gemspec ├── LICENSE ├── bin └── json-waveform └── README.md /test/fixtures/sample.txt: -------------------------------------------------------------------------------- 1 | foo bar baz -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.m4a 2 | *.mp3 3 | *.wav 4 | *.png 5 | test/output/* 6 | .DS_Store 7 | *.gem 8 | -------------------------------------------------------------------------------- /lib/json-waveform/version.rb: -------------------------------------------------------------------------------- 1 | class JsonWaveform 2 | VERSION = "0.2.1".freeze 3 | end 4 | -------------------------------------------------------------------------------- /test/fixtures/sample.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kitop/json-waveform/HEAD/test/fixtures/sample.wav -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | .PHONY: console test 2 | 3 | test: 4 | ruby test/json_waveform_test.rb 5 | 6 | console: 7 | irb -I./lib -r json-waveform 8 | -------------------------------------------------------------------------------- /json-waveform.gemspec: -------------------------------------------------------------------------------- 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", "~> 1.6.0" 25 | s.add_development_dependency "minitest", "~> 5" 26 | end 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. 20 | -------------------------------------------------------------------------------- /bin/json-waveform: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | require "json-waveform" 4 | require "json" 5 | require "optparse" 6 | 7 | options = JsonWaveform::DEFAULT_OPTIONS 8 | optparse = OptionParser.new do |o| 9 | o.banner = "Usage: json-waveform [options] source_audio [output]" 10 | o.version = JsonWaveform::VERSION 11 | 12 | o.on("-s", "--samples SAMPLES", "Samples generated from the original file (i.e.: waveform width). The result may have ±10% of the samples requested. -- Default #{JsonWaveform::DEFAULT_OPTIONS[:samples]}.") do |samples| 13 | options[:samples] = samples.to_i 14 | end 15 | 16 | o.on("-A", "--amplitude AMPLITUDE", "Max amplitude of the waveform values-- Default #{JsonWaveform::DEFAULT_OPTIONS[:amplitude]}.") do |amplitude| 17 | options[:amplitude] = amplitude.to_i 18 | end 19 | 20 | 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| 21 | options[:auto_sample] = msec.to_i 22 | end 23 | 24 | o.on("-m", "--method METHOD", "Wave analyzation method (can be 'peak' or 'rms') -- Default '#{JsonWaveform::DEFAULT_OPTIONS[:method]}'.") do |method| 25 | options[:method] = method.to_sym 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 | result = JSON.dump(JsonWaveform.generate(ARGV[0], options)) 38 | if ARGV[1] 39 | File.open(ARGV[1], 'w') do |file| 40 | file.write result 41 | end 42 | else 43 | puts result 44 | end 45 | rescue JsonWaveform::ArgumentError => e 46 | puts e.message + "\n\n" 47 | puts optparse 48 | rescue JsonWaveform::RuntimeError => e 49 | puts e.message 50 | end 51 | 52 | -------------------------------------------------------------------------------- /test/json_waveform_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "json-waveform")) 2 | 3 | require "minitest" 4 | require "minitest/autorun" 5 | require "fileutils" 6 | require 'json' 7 | 8 | module Helpers 9 | def fixture(file) 10 | File.join(File.dirname(__FILE__), "fixtures", file) 11 | end 12 | end 13 | 14 | class JsonWaveformTest < Minitest::Test 15 | include Helpers 16 | extend Helpers 17 | 18 | def test_generates_waveform 19 | result = JsonWaveform.generate(fixture("sample.wav")) 20 | 21 | assert_equal File.read(fixture("sample.peak.json")), JSON.dump(result) 22 | end 23 | 24 | def test_generates_rms_waveform 25 | result = JsonWaveform.generate(fixture("sample.wav"), method: :rms) 26 | 27 | assert_equal File.read(fixture("sample.rms.json")), JSON.dump(result) 28 | end 29 | 30 | def test_changes_samples 31 | result = JsonWaveform.generate(fixture("sample.wav"), samples: 100) 32 | 33 | assert_in_delta 100, result.size, 1 34 | end 35 | 36 | def test_changes_amplitude 37 | result = JsonWaveform.generate(fixture("sample.wav"), amplitude: 100) 38 | 39 | original = JSON.parse(File.read(fixture("sample.peak.json"))) 40 | 41 | assert_in_delta original.max * 100, result.max, 1 42 | end 43 | 44 | def test_has_auto_width 45 | result = JsonWaveform.generate(fixture("sample.wav"), auto_samples: 10) 46 | 47 | assert_equal 210, result.size 48 | end 49 | 50 | def test_raises_error_if_not_given_readable_audio_source 51 | assert_raises JsonWaveform::RuntimeError do 52 | JsonWaveform.generate(fixture("sample.txt")) 53 | end 54 | end 55 | 56 | def test_raises_exception_if_audio_not_exists 57 | assert_raises JsonWaveform::RuntimeError do 58 | JsonWaveform.generate(fixture("foo.wav")) 59 | end 60 | end 61 | 62 | #def test_raises_deprecation_exception_if_ruby_audio_fails_to_read_source_file 63 | # begin 64 | # Waveform.generate(fixture("sample.txt"), output("shouldnt_exist.png")) 65 | # rescue Waveform::RuntimeError => e 66 | # assert_match /Hint: non-WAV files are no longer supported, convert to WAV first using something like ffmpeg/, e.message 67 | # end 68 | #end 69 | end 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 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.generate("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 | $ make 61 | 62 | Sample sound file used in tests is in the Public Domain from soundbible.com: . 63 | 64 | -------------------------------------------------------------------------------- /test/fixtures/sample.rms.json: -------------------------------------------------------------------------------- 1 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.01,0.01,0,0,0.01,0.45,0.45,0.48,0.52,0.52,0.48,0.45,0.47,0.52,0.52,0.47,0.45,0.49,0.52,0.51,0.46,0.45,0.48,0.52,0.5,0.46,0.44,0.48,0.52,0.5,0.45,0.44,0.48,0.51,0.49,0.45,0.44,0.48,0.51,0.49,0.44,0.44,0.48,0.51,0.48,0.44,0.44,0.48,0.5,0.48,0.43,0.44,0.48,0.5,0.47,0.43,0.44,0.48,0.5,0.46,0.42,0.44,0.48,0.49,0.46,0.42,0.44,0.48,0.49,0.45,0.42,0.44,0.48,0.48,0.44,0.41,0.44,0.48,0.48,0.44,0.41,0.44,0.47,0.47,0.43,0.41,0.44,0.47,0.47,0.43,0.4,0.44,0.47,0.46,0.42,0.4,0.44,0.47,0.46,0.41,0.4,0.43,0.46,0.45,0.41,0.4,0.43,0.46,0.44,0.4,0.4,0.43,0.46,0.44,0.4,0.39,0.43,0.45,0.43,0.39,0.39,0.43,0.45,0.43,0.39,0.39,0.43,0.45,0.42,0.38,0.39,0.43,0.44,0.41,0.38,0.39,0.43,0.44,0.41,0.37,0.39,0.43,0.43,0.4,0.37,0.39,0.42,0.43,0.39,0.37,0.39,0.42,0.42,0.39,0.36,0.39,0.42,0.42,0.38,0.36,0.39,0.42,0.41,0.38,0.36,0.38,0.41,0.41,0.37,0.35,0.38,0.41,0.4,0.36,0.35,0.38,0.41,0.39,0.36,0.35,0.38,0.4,0.39,0.35,0.35,0.38,0.4,0.38,0.35,0.35,0.38,0.4,0.38,0.34,0.34,0.38,0.39,0.37,0.34,0.34,0.38,0.39,0.36,0.33,0.34,0.37,0.38,0.36,0.33,0.34,0.37,0.38,0.35,0.32,0.34,0.37,0.37,0.35,0.32,0.34,0.37,0.37,0.34,0.32,0.33,0.36,0.36,0.33,0.31,0.33,0.36,0.36,0.33,0.31,0.33,0.36,0.35,0.32,0.31,0.33,0.36,0.35,0.32,0.3,0.33,0.35,0.34,0.31,0.3,0.33,0.35,0.34,0.31,0.3,0.33,0.35,0.33,0.3,0.3,0.32,0.34,0.33,0.3,0.29,0.32,0.34,0.32,0.29,0.29,0.32,0.33,0.31,0.29,0.29,0.32,0.33,0.31,0.28,0.29,0.32,0.32,0.3,0.28,0.29,0.31,0.32,0.3,0.27,0.28,0.31,0.31,0.29,0.27,0.28,0.31,0.31,0.28,0.27,0.28,0.31,0.31,0.28,0.26,0.28,0.3,0.3,0.27,0.26,0.28,0.3,0.29,0.27,0.26,0.28,0.3,0.29,0.26,0.25,0.27,0.29,0.28,0.26,0.25,0.27,0.29,0.28,0.25,0.25,0.27,0.29,0.27,0.25,0.24,0.27,0.28,0.27,0.24,0.24,0.26,0.28,0.26,0.24,0.24,0.26,0.27,0.26,0.23,0.24,0.26,0.27,0.25,0.23,0.24,0.26,0.26,0.25,0.23,0.23,0.25,0.26,0.24,0.22,0.23,0.25,0.26,0.24,0.22,0.23,0.25,0.25,0.23,0.21,0.23,0.25,0.25,0.22,0.21,0.22,0.24,0.24,0.22,0.21,0.22,0.24,0.24,0.21,0.2,0.22,0.24,0.23,0.21,0.2,0.22,0.23,0.23,0.2,0.2,0.22,0.23,0.22,0.2,0.2,0.21,0.23,0.22,0.2,0.19,0.21,0.22,0.21,0.19,0.19,0.21,0.22,0.21,0.19,0.19,0.21,0.21,0.2,0.18,0.19,0.2,0.21,0.2,0.18,0.18,0.2,0.21,0.19,0.18,0.18,0.2,0.2,0.19,0.17,0.18,0.2,0.2,0.18,0.17,0.18,0.19,0.19,0.18,0.16,0.17,0.19,0.19,0.17,0.16,0.17,0.19,0.18,0.17,0.16,0.17,0.18,0.18,0.16,0.16,0.17,0.18,0.17,0.16,0.15,0.16,0.18,0.17,0.15,0.15,0.16,0.17,0.17,0.15,0.15,0.16,0.17,0.16,0.15,0.14,0.16,0.17,0.16,0.14,0.14,0.16,0.16,0.15,0.14,0.14,0.15,0.16,0.15,0.13,0.14,0.15,0.15,0.14,0.13,0.13,0.15,0.15,0.14,0.13,0.13,0.14,0.15,0.14,0.12,0.13,0.14,0.14,0.13,0.12,0.13,0.14,0.14,0.13,0.12,0.13,0.14,0.13,0.12,0.12,0.12,0.13,0.13,0.12,0.11,0.12,0.13,0.13,0.12,0.11,0.12,0.13,0.12,0.11,0.11,0.12,0.12,0.12,0.11,0.11,0.11,0.12,0.12,0.1,0.1,0.11,0.12,0.11,0.1,0.1,0.11,0.11,0.11,0.1,0.1,0.11,0.11,0.1,0.09,0.1,0.1,0.11,0.1,0.09,0.09,0.1,0.1,0.1,0.09,0.09,0.1,0.1,0.09,0.09,0.09,0.1,0.1,0.09,0.08,0.09,0.09,0.09,0.09,0.08,0.08,0.09,0.09,0.08,0.08,0.08,0.09,0.09,0.08,0.08,0.08,0.09,0.08,0.08,0.07,0.08,0.08,0.08,0.07,0.07,0.08,0.08,0.08,0.07,0.07,0.07,0.08,0.08,0.07,0.07,0.07,0.08,0.07,0.07,0.06,0.07,0.07,0.07,0.06,0.06,0.07,0.07,0.07,0.06,0.06,0.06,0.07,0.06,0.06,0.06,0.06,0.06,0.06,0.05,0.06,0.06,0.06,0.06,0.05,0.05,0.06,0.06,0.05,0.05,0.05,0.06,0.06,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.04,0.05,0.05,0.05,0.04,0.04,0.04,0.05,0.05,0.04,0.04,0.04,0.05,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.03,0.04,0.04,0.04,0.03,0.03,0.04,0.04,0.04,0.03,0.03,0.03,0.04,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.02,0.02,0.03,0.03,0.03,0.02,0.02,0.03,0.03,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.01,0.02,0.02,0.02,0.01,0.01,0.01,0.02,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0,0.01,0.01,0.01,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] -------------------------------------------------------------------------------- /test/fixtures/sample.peak.json: -------------------------------------------------------------------------------- 1 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.7,0.7,0.7,0.7,0.7,0.69,0.69,0.68,0.69,0.71,0.69,0.7,0.7,0.69,0.69,0.68,0.68,0.68,0.68,0.68,0.68,0.68,0.68,0.68,0.68,0.68,0.68,0.68,0.67,0.67,0.67,0.67,0.67,0.67,0.67,0.67,0.67,0.67,0.67,0.67,0.66,0.66,0.66,0.66,0.66,0.66,0.66,0.66,0.66,0.66,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.64,0.64,0.64,0.64,0.64,0.64,0.64,0.64,0.64,0.64,0.64,0.63,0.63,0.63,0.63,0.63,0.63,0.63,0.63,0.63,0.63,0.62,0.62,0.62,0.62,0.62,0.62,0.62,0.62,0.62,0.62,0.61,0.61,0.61,0.61,0.61,0.61,0.61,0.61,0.61,0.61,0.61,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.59,0.59,0.59,0.59,0.59,0.59,0.59,0.59,0.59,0.59,0.59,0.58,0.58,0.58,0.58,0.58,0.58,0.58,0.58,0.58,0.58,0.57,0.57,0.57,0.57,0.57,0.57,0.57,0.57,0.57,0.57,0.56,0.56,0.56,0.56,0.56,0.56,0.56,0.56,0.56,0.56,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.54,0.54,0.54,0.54,0.54,0.54,0.54,0.54,0.54,0.54,0.53,0.53,0.53,0.53,0.53,0.53,0.53,0.53,0.53,0.53,0.52,0.52,0.52,0.52,0.52,0.52,0.52,0.52,0.52,0.51,0.51,0.51,0.51,0.51,0.51,0.51,0.51,0.51,0.51,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.49,0.49,0.49,0.49,0.49,0.49,0.49,0.49,0.49,0.49,0.48,0.48,0.48,0.48,0.48,0.48,0.48,0.48,0.48,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.46,0.46,0.46,0.46,0.46,0.46,0.46,0.46,0.46,0.45,0.45,0.45,0.45,0.45,0.45,0.45,0.45,0.45,0.45,0.44,0.44,0.44,0.44,0.44,0.44,0.44,0.44,0.44,0.43,0.43,0.43,0.43,0.43,0.43,0.43,0.43,0.43,0.42,0.42,0.42,0.42,0.42,0.42,0.42,0.42,0.42,0.41,0.41,0.41,0.41,0.41,0.41,0.41,0.41,0.41,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.39,0.39,0.39,0.39,0.39,0.39,0.39,0.39,0.39,0.39,0.38,0.38,0.38,0.38,0.38,0.38,0.38,0.38,0.38,0.38,0.37,0.37,0.37,0.37,0.37,0.37,0.37,0.37,0.37,0.36,0.36,0.36,0.36,0.36,0.36,0.36,0.36,0.36,0.36,0.35,0.35,0.35,0.35,0.35,0.35,0.35,0.35,0.35,0.34,0.34,0.34,0.34,0.34,0.34,0.34,0.34,0.34,0.34,0.33,0.33,0.33,0.33,0.33,0.33,0.33,0.33,0.32,0.32,0.32,0.32,0.32,0.32,0.32,0.32,0.32,0.32,0.31,0.31,0.31,0.31,0.31,0.31,0.31,0.31,0.31,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.29,0.29,0.29,0.29,0.29,0.29,0.29,0.29,0.29,0.28,0.28,0.28,0.28,0.28,0.28,0.28,0.28,0.28,0.28,0.27,0.27,0.27,0.27,0.27,0.27,0.27,0.27,0.27,0.27,0.26,0.26,0.26,0.26,0.26,0.26,0.26,0.26,0.26,0.26,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.24,0.24,0.24,0.24,0.24,0.24,0.24,0.24,0.24,0.24,0.23,0.23,0.23,0.23,0.23,0.23,0.23,0.23,0.23,0.23,0.22,0.22,0.22,0.22,0.22,0.22,0.22,0.22,0.22,0.22,0.22,0.21,0.21,0.21,0.21,0.21,0.21,0.21,0.21,0.21,0.21,0.21,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.19,0.19,0.19,0.19,0.19,0.19,0.19,0.19,0.19,0.19,0.19,0.18,0.18,0.18,0.18,0.18,0.18,0.18,0.18,0.18,0.18,0.18,0.17,0.17,0.17,0.17,0.17,0.17,0.17,0.17,0.17,0.17,0.17,0.16,0.16,0.16,0.16,0.16,0.16,0.16,0.16,0.16,0.16,0.16,0.16,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.14,0.14,0.14,0.14,0.14,0.14,0.14,0.14,0.14,0.14,0.14,0.14,0.14,0.13,0.13,0.13,0.13,0.13,0.13,0.13,0.13,0.13,0.13,0.13,0.13,0.12,0.12,0.12,0.12,0.12,0.12,0.12,0.12,0.12,0.12,0.12,0.12,0.12,0.11,0.11,0.11,0.11,0.11,0.11,0.11,0.11,0.11,0.11,0.11,0.11,0.11,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.09,0.09,0.09,0.09,0.09,0.09,0.09,0.09,0.09,0.09,0.09,0.09,0.09,0.09,0.09,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.07,0.07,0.07,0.07,0.07,0.07,0.07,0.07,0.07,0.07,0.07,0.07,0.07,0.07,0.07,0.07,0.06,0.06,0.06,0.06,0.06,0.06,0.06,0.06,0.06,0.06,0.06,0.06,0.06,0.06,0.06,0.06,0.06,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.02,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] -------------------------------------------------------------------------------- /lib/json-waveform.rb: -------------------------------------------------------------------------------- 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. The may have ±10% of the 33 | # samples requested. 34 | # 35 | # Default is 1800. 36 | # 37 | # :amplitude => The amplitude of the final values 38 | # Default is 1. 39 | # 40 | # :auto_width => msec per sample. This will overwrite the sample of the 41 | # final waveform depending on the length of the audio file. 42 | # Example: 43 | # 100 => 1 sample per 100 msec; a one minute audio file will result in a width of 600 samples 44 | # 45 | # Example: 46 | # JsonWaveform.generate("Kickstart My Heart.wav") 47 | # JsonWaveform.generate("Kickstart My Heart.wav", :method => :rms) 48 | # 49 | def generate(source, options={}) 50 | options = DEFAULT_OPTIONS.merge(options) 51 | 52 | raise ArgumentError.new("No source audio filename given, must be an existing sound file.") unless source 53 | raise RuntimeError.new("Source audio file '#{source}' not found.") unless File.exist?(source) 54 | 55 | if options[:auto_samples] 56 | RubyAudio::Sound.open(source) do |audio| 57 | options[:samples] = (audio.info.length * 1000 / options[:auto_samples].to_i).ceil 58 | end 59 | end 60 | 61 | # Frames gives the amplitudes for each channel, for our waveform we're 62 | # saying the "visual" amplitude is the average of the amplitude across all 63 | # the channels. This might be a little weird w/ the "peak" method if the 64 | # frames are very wide (i.e. the image width is very small) -- I *think* 65 | # the larger the frames are, the more "peaky" the waveform should get, 66 | # perhaps to the point of inaccurately reflecting the actual sound. 67 | samples = frames(source, options[:samples], options[:method]).collect do |frame| 68 | frame.inject(0.0) { |sum, peak| sum + peak } / frame.size 69 | end 70 | 71 | normalize(samples, options) 72 | end 73 | 74 | private 75 | 76 | # Returns a sampling of frames from the given RubyAudio::Sound using the 77 | # given method 78 | def frames(source, samples, method = :peak) 79 | raise ArgumentError.new("Unknown sampling method #{method}") unless [ :peak, :rms ].include?(method) 80 | 81 | frames = [] 82 | 83 | RubyAudio::Sound.open(source) do |audio| 84 | frames_read = 0 85 | frames_per_sample = (audio.info.frames.to_f / samples.to_f).to_i 86 | sample = RubyAudio::Buffer.new("float", frames_per_sample, audio.info.channels) 87 | 88 | while(frames_read = audio.read(sample)) > 0 89 | frames << send(method, sample, audio.info.channels) 90 | end 91 | end 92 | 93 | frames 94 | rescue RubyAudio::Error => e 95 | raise e unless e.message == "File contains data in an unknown format." 96 | 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})") 97 | end 98 | 99 | def normalize(samples, options) 100 | samples.map do |sample| 101 | # Half the amplitude goes above zero, half below 102 | amplitude = sample * options[:amplitude].to_f 103 | rounded = amplitude.round(2) 104 | rounded.zero? || rounded == 1 ? rounded.to_i : rounded 105 | end 106 | end 107 | 108 | # Returns an array of the peak of each channel for the given collection of 109 | # frames -- the peak is individual to the channel, and the returned collection 110 | # of peaks are not (necessarily) from the same frame(s). 111 | def peak(frames, channels=1) 112 | peak_frame = [] 113 | (0..channels-1).each do |channel| 114 | peak_frame << channel_peak(frames, channel) 115 | end 116 | peak_frame 117 | end 118 | 119 | # Returns an array of rms values for the given frameset where each rms value is 120 | # the rms value for that channel. 121 | def rms(frames, channels=1) 122 | rms_frame = [] 123 | (0..channels-1).each do |channel| 124 | rms_frame << channel_rms(frames, channel) 125 | end 126 | rms_frame 127 | end 128 | 129 | # Returns the peak voltage reached on the given channel in the given collection 130 | # of frames. 131 | # 132 | # TODO: Could lose some resolution and only sample every other frame, would 133 | # likely still generate the same waveform as the waveform is so comparitively 134 | # low resolution to the original input (in most cases), and would increase 135 | # the analyzation speed (maybe). 136 | def channel_peak(frames, channel=0) 137 | peak = 0.0 138 | frames.each do |frame| 139 | next if frame.nil? 140 | frame = Array(frame) 141 | peak = frame[channel].abs if frame[channel].abs > peak 142 | end 143 | peak 144 | end 145 | 146 | # Returns the rms value across the given collection of frames for the given 147 | # channel. 148 | def channel_rms(frames, channel=0) 149 | Math.sqrt(frames.inject(0.0){ |sum, frame| sum += (frame ? Array(frame)[channel] ** 2 : 0) } / frames.size) 150 | end 151 | end 152 | end 153 | --------------------------------------------------------------------------------