├── VERSION ├── .rspec ├── .document ├── spec ├── analyze.wav ├── spec_helper.rb └── spectrum-analyzer_spec.rb ├── lib ├── spectrum-analyzer │ ├── objects │ │ ├── file.rb │ │ ├── analysis.rb │ │ ├── spectrum.rb │ │ └── domain.rb │ ├── functions.rb │ ├── config.rb │ └── functions │ │ ├── window_functions.rb │ │ └── generator.rb └── spectrum-analyzer.rb ├── Gemfile ├── .gitignore ├── LICENSE.txt ├── Rakefile ├── Gemfile.lock ├── README.md └── spectrum-analyzer.gemspec /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.0 -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /spec/analyze.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jusroberts/spectrum-analyzer/HEAD/spec/analyze.wav -------------------------------------------------------------------------------- /lib/spectrum-analyzer/objects/file.rb: -------------------------------------------------------------------------------- 1 | module SpectrumAnalyzer 2 | module Objects 3 | class File 4 | attr_accessor :name, :sample_rate 5 | 6 | def initialize(name) 7 | @name = name || '' 8 | @sample_rate = RubyAudio::Sound.open(name).info.samplerate 9 | end 10 | 11 | def read 12 | 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/spectrum-analyzer/functions.rb: -------------------------------------------------------------------------------- 1 | module SpectrumAnalyzer 2 | module Functions 3 | 4 | def self.analyze 5 | SpectrumAnalyzer::Functions::Generator.new().analyze 6 | end 7 | 8 | def self.quick_analyze 9 | SpectrumAnalyzer::Functions::Generator.new().quick_analyze 10 | end 11 | 12 | def self.contains_frequency_range? 13 | SpectrumAnalyzer::Functions::Generator.new().contains_frequency_range? 14 | end 15 | 16 | end 17 | end -------------------------------------------------------------------------------- /lib/spectrum-analyzer/config.rb: -------------------------------------------------------------------------------- 1 | module SpectrumAnalyzer 2 | class Config 3 | attr_accessor :file_name, :window_function, :window_size, :analysis_ranges 4 | 5 | def initialize(args = {}) 6 | @window_size = args[:window_size] || 1024 7 | @window_function = args[:window_function] || :hanning 8 | @analysis_ranges = args[:analysis_ranges] || [] 9 | @file_name = args[:file_name] || "" 10 | end 11 | 12 | end 13 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | SimpleCov.start 3 | 4 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 5 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 6 | require 'rspec' 7 | require 'spectrum-analyzer' 8 | 9 | # Requires supporting files with custom matchers and macros, etc, 10 | # in ./support/ and its subdirectories. 11 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 12 | 13 | RSpec.configure do |config| 14 | 15 | end 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | # Add dependencies required to use your gem here. 3 | # Example: 4 | # gem "activesupport", ">= 2.3.5" 5 | 6 | # Add dependencies to develop your gem here. 7 | # Include everything needed to run rake, tests, features, etc. 8 | group :development do 9 | gem "rspec", "~> 2.8.0" 10 | gem "rdoc", "~> 3.12" 11 | gem "bundler", "~> 1.0" 12 | gem "jeweler", "~> 1.8.7" 13 | gem "simplecov", ">= 0" 14 | gem "ruby-audio" 15 | gem "fftw3" 16 | gem "simplecov" 17 | end 18 | -------------------------------------------------------------------------------- /lib/spectrum-analyzer/objects/analysis.rb: -------------------------------------------------------------------------------- 1 | module SpectrumAnalyzer 2 | module Objects 3 | class Analysis 4 | attr_accessor :file, :spectrum 5 | 6 | def initialize(file_name) 7 | @spectrum = SpectrumAnalyzer::Objects::Spectrum.new() 8 | @file = SpectrumAnalyzer::Objects::File.new(file_name) 9 | end 10 | 11 | def sum_spectrum 12 | @spectrum.sum_domains 13 | end 14 | 15 | def sum_occurrences 16 | @spectrum.sum_occurrences 17 | end 18 | 19 | def add_domain_to_spectrum(buffer) 20 | @spectrum.add_domain(buffer) 21 | end 22 | 23 | end 24 | end 25 | end -------------------------------------------------------------------------------- /lib/spectrum-analyzer/functions/window_functions.rb: -------------------------------------------------------------------------------- 1 | module SpectrumAnalyzer 2 | class WindowFunctions 3 | def initialize(window_size) 4 | @window_size = window_size 5 | end 6 | 7 | def windows() 8 | { 9 | :hanning => hanning(), 10 | :rectangle => rectangle() 11 | } 12 | end 13 | 14 | private 15 | 16 | def hanning() 17 | hannified_array = Array.new 18 | i=0 19 | (0..@window_size).each { |x| hannified_array[i] = 0.5 - 0.5 * Math.cos(2 * Math::PI * i / @window_size) ; i+=1} 20 | 21 | hannified_array 22 | end 23 | 24 | def rectangle() 25 | Array.new(@window_size, 1) 26 | end 27 | 28 | end 29 | end -------------------------------------------------------------------------------- /lib/spectrum-analyzer.rb: -------------------------------------------------------------------------------- 1 | require 'ruby-audio' 2 | require 'fftw3' 3 | 4 | #require_relative '../lib/spectrum-analyzer/config' 5 | 6 | Dir[File.dirname(__FILE__) + '/spectrum-analyzer/*.rb'].each {| file| load file } 7 | Dir[File.dirname(__FILE__) + '/spectrum-analyzer/functions/*.rb'].each {| file| load file } 8 | Dir[File.dirname(__FILE__) + '/spectrum-analyzer/objects/*.rb'].each {| file| load file } 9 | 10 | module SpectrumAnalyzer 11 | attr_accessor :analysis 12 | 13 | def self.configuration(args = {}) 14 | @configuration ||= SpectrumAnalyzer::Config.new(args) 15 | end 16 | 17 | def self.analyze 18 | configuration 19 | SpectrumAnalyzer::Functions.analyze 20 | end 21 | 22 | def self.quick_analyze 23 | configuration 24 | SpectrumAnalyzer::Functions.quick_analyze 25 | end 26 | 27 | def self.contains_frequency_range? 28 | configuration 29 | SpectrumAnalyzer::Functions.contains_frequency_range? 30 | end 31 | 32 | end -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | coverage.data 4 | 5 | # rdoc generated 6 | rdoc 7 | 8 | # yard generated 9 | doc 10 | .yardoc 11 | 12 | # bundler 13 | .bundle 14 | 15 | # jeweler generated 16 | pkg 17 | 18 | # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: 19 | # 20 | # * Create a file at ~/.gitignore 21 | # * Include files you want ignored 22 | # * Run: git config --global core.excludesfile ~/.gitignore 23 | # 24 | # After doing this, these files will be ignored in all your git projects, 25 | # saving you from having to 'pollute' every project you touch with them 26 | # 27 | # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) 28 | # 29 | # For MacOS: 30 | # 31 | .DS_Store 32 | 33 | # For TextMate 34 | #*.tmproj 35 | #tmtags 36 | 37 | # For emacs: 38 | #*~ 39 | #\#* 40 | #.\#* 41 | 42 | # For vim: 43 | #*.swp 44 | 45 | # For redcar: 46 | #.redcar 47 | 48 | # For rubinius: 49 | #*.rbc 50 | 51 | .idea/ 52 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Justin Roberts 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/spectrum-analyzer/objects/spectrum.rb: -------------------------------------------------------------------------------- 1 | module SpectrumAnalyzer 2 | module Objects 3 | class Spectrum 4 | attr_accessor :domains, :entire_spectrum, :num_occurrences 5 | 6 | def initialize(spectrum = []) 7 | @config = SpectrumAnalyzer.configuration 8 | @domains = spectrum 9 | end 10 | 11 | def sum_domains 12 | @entire_spectrum = Array.new(@domains[0].values.length, 0) 13 | @domains.each do |domain| 14 | @entire_spectrum.map!.with_index{ |x,i| x + domain.values[i]} 15 | end 16 | end 17 | 18 | def add_domain(buffer) 19 | domain = SpectrumAnalyzer::Objects::Domain.new(buffer) 20 | @domains.push(domain) 21 | end 22 | 23 | def sum_occurrences 24 | ranges = @config.analysis_ranges 25 | occurrence_count = 0 26 | @domains.each do |domain| 27 | ranges.each do |range| 28 | if find_occurrence(range, domain) 29 | occurrence_count += 1 30 | domain.contains_frequency_range = true 31 | end 32 | end 33 | end 34 | @num_occurrences = occurrence_count 35 | end 36 | 37 | def find_occurrence (range, domain) 38 | sum_total = 0 39 | for i in range[:b_index]..range[:t_index] 40 | sum_total += domain.values[i] if !domain.nil? 41 | end 42 | average = sum_total / (range[:t_index] - range[:b_index]) 43 | average > range[:min] and average < range[:max] 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/spectrum-analyzer/objects/domain.rb: -------------------------------------------------------------------------------- 1 | module SpectrumAnalyzer 2 | module Objects 3 | class Domain 4 | attr_accessor :values, :raw_values, :contains_frequency_range 5 | 6 | def initialize(buffer) 7 | @config = SpectrumAnalyzer.configuration 8 | @values = [] 9 | @raw_values = [] 10 | @contains_frequency_range = false 11 | analyze_buffer(buffer) 12 | 13 | end 14 | 15 | def analyze_buffer(buffer) 16 | fft_array = fft_array_builder(buffer) 17 | fft_array.each { |x| @raw_values.push(x); @values.push(x.magnitude)} 18 | end 19 | 20 | 21 | def contains_frequencies?(frequency_ranges) 22 | j=0 23 | match = Array.new() 24 | frequency_ranges.each do |frequency_range| 25 | match[j] = find_frequency_use(frequency_range) 26 | j+=1 27 | end 28 | return !match.include?(false) 29 | end 30 | 31 | private 32 | 33 | def find_frequency_use(frequency_range) 34 | sum_total = 0 35 | for i in frequency_range[:b_index]..frequency_range[:t_index] 36 | sum_total += values[i] unless values[i].nil? 37 | end 38 | average = sum_total / (frequency_range[:t_index] - frequency_range[:b_index]) 39 | average > frequency_range[:min] and average < frequency_range[:max] 40 | end 41 | 42 | 43 | def fft_array_builder(windowed_buffer) 44 | na = NArray.to_na(windowed_buffer) 45 | FFTW3.fft(na).to_a[0, @config.window_size/2] 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | require 'bundler' 5 | begin 6 | Bundler.setup(:default, :development) 7 | rescue Bundler::BundlerError => e 8 | $stderr.puts e.message 9 | $stderr.puts "Run `bundle install` to install missing gems" 10 | exit e.status_code 11 | end 12 | require 'rake' 13 | 14 | require 'jeweler' 15 | Jeweler::Tasks.new do |gem| 16 | # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options 17 | gem.name = "spectrum-analyzer" 18 | gem.homepage = "http://github.com/jusroberts/spectrum-analyzer" 19 | gem.license = "MIT" 20 | gem.summary = %Q{Analyze a wav file for specific frequency signatures} 21 | gem.description = %Q{Analyze a wav file for specific frequency signatures} 22 | gem.email = "justin.roberts@careerbuilder.com" 23 | gem.authors = ["Justin Roberts"] 24 | # dependencies defined in Gemfile 25 | end 26 | Jeweler::RubygemsDotOrgTasks.new 27 | 28 | require 'rspec/core' 29 | require 'rspec/core/rake_task' 30 | RSpec::Core::RakeTask.new(:spec) do |spec| 31 | spec.pattern = FileList['spec/**/*_spec.rb'] 32 | end 33 | 34 | RSpec::Core::RakeTask.new(:rcov) do |spec| 35 | spec.pattern = 'spec/**/*_spec.rb' 36 | spec.rcov = true 37 | end 38 | 39 | task :default => :spec 40 | 41 | require 'rdoc/task' 42 | Rake::RDocTask.new do |rdoc| 43 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 44 | 45 | rdoc.rdoc_dir = 'rdoc' 46 | rdoc.title = "spectrum-analyzer #{version}" 47 | rdoc.rdoc_files.include('README*') 48 | rdoc.rdoc_files.include('lib/**/*.rb') 49 | end 50 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | addressable (2.3.5) 5 | builder (3.2.2) 6 | diff-lcs (1.1.3) 7 | docile (1.1.0) 8 | faraday (0.8.8) 9 | multipart-post (~> 1.2.0) 10 | fftw3 (0.3) 11 | narray 12 | git (1.2.6) 13 | github_api (0.10.1) 14 | addressable 15 | faraday (~> 0.8.1) 16 | hashie (>= 1.2) 17 | multi_json (~> 1.4) 18 | nokogiri (~> 1.5.2) 19 | oauth2 20 | hashie (2.0.5) 21 | highline (1.6.20) 22 | httpauth (0.2.0) 23 | jeweler (1.8.8) 24 | builder 25 | bundler (~> 1.0) 26 | git (>= 1.2.5) 27 | github_api (= 0.10.1) 28 | highline (>= 1.6.15) 29 | nokogiri (= 1.5.10) 30 | rake 31 | rdoc 32 | json (1.8.1) 33 | jwt (0.1.8) 34 | multi_json (>= 1.5) 35 | lockfile (2.1.0) 36 | multi_json (1.8.2) 37 | multi_xml (0.5.5) 38 | multipart-post (1.2.0) 39 | narray (0.6.0.8) 40 | nokogiri (1.5.10) 41 | oauth2 (0.9.2) 42 | faraday (~> 0.8) 43 | httpauth (~> 0.2) 44 | jwt (~> 0.1.4) 45 | multi_json (~> 1.0) 46 | multi_xml (~> 0.5) 47 | rack (~> 1.2) 48 | rack (1.5.2) 49 | rake (10.1.0) 50 | rdoc (3.12.2) 51 | json (~> 1.4) 52 | rspec (2.8.0) 53 | rspec-core (~> 2.8.0) 54 | rspec-expectations (~> 2.8.0) 55 | rspec-mocks (~> 2.8.0) 56 | rspec-core (2.8.0) 57 | rspec-expectations (2.8.0) 58 | diff-lcs (~> 1.1.2) 59 | rspec-mocks (2.8.0) 60 | ruby-audio (1.6.1) 61 | simplecov (0.8.1) 62 | docile (~> 1.1.0) 63 | lockfile (>= 2.1.0) 64 | multi_json 65 | simplecov-html (~> 0.8.0) 66 | simplecov-html (0.8.0) 67 | 68 | PLATFORMS 69 | ruby 70 | 71 | DEPENDENCIES 72 | bundler (~> 1.0) 73 | fftw3 74 | jeweler (~> 1.8.7) 75 | rdoc (~> 3.12) 76 | rspec (~> 2.8.0) 77 | ruby-audio 78 | simplecov 79 | -------------------------------------------------------------------------------- /spec/spectrum-analyzer_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | 3 | describe "SpectrumAnalyzer" do 4 | 5 | before :each do 6 | @window_size = 512 7 | @file_name = 'spec/analyze.wav' 8 | SpectrumAnalyzer.configuration( 9 | { 10 | :window_size => 512, 11 | :window_function => :hanning, 12 | :analysis_ranges => [ 13 | { :b_index => 27, :t_index => 47, :min => 1, :max => 2}, #Low area 14 | { :b_index => 58, :t_index => 64, :min => 2.5, :max => 6.5}, #High peak 15 | { :b_index => 70, :t_index => 74, :min => 2.0, :max => 4.2 }, #Mid peak 16 | { :b_index => 82, :t_index => 109, :min => 0.8, :max => 2} #Low area 17 | ], 18 | :file_name => "spec/analyze.wav" 19 | } 20 | ) 21 | 22 | end 23 | it 'returns true if matches are found in the analysis' do 24 | SpectrumAnalyzer.quick_analyze.should == true 25 | end 26 | 27 | it 'returns true if matches are found in the analysis' do 28 | SpectrumAnalyzer.contains_frequency_range?.should == true 29 | end 30 | 31 | it 'creates an analysis object with information about the analysis performed' do 32 | analysis = SpectrumAnalyzer.analyze 33 | 34 | analysis.spectrum.should be_a SpectrumAnalyzer::Objects::Spectrum 35 | analysis.file.should be_a SpectrumAnalyzer::Objects::File 36 | 37 | analysis.spectrum.num_occurrences.should == 22 38 | 39 | analysis.spectrum.domains[0].should be_a SpectrumAnalyzer::Objects::Domain 40 | 41 | analysis.spectrum.domains[0].values.length.should == @window_size / 2 42 | analysis.spectrum.domains[0].raw_values.length.should == @window_size / 2 43 | analysis.spectrum.domains[0].values[0].class.should be Float 44 | analysis.spectrum.domains[0].raw_values[0].class.should be Complex 45 | 46 | analysis.file.name.should == @file_name 47 | analysis.file.sample_rate.should == 8000 48 | 49 | end 50 | 51 | it 'bombs out without a file :(' do 52 | SpectrumAnalyzer.configuration.file_name = "" 53 | 54 | expect{SpectrumAnalyzer.analyze}.to raise_error(StandardError) 55 | end 56 | # 57 | #it 'bombs out without a window :( - Analyze' do 58 | # SpectrumAnalyzer.configuration.window_function = [] 59 | # SpectrumAnalyzer.configuration.file_name = @file_name 60 | # 61 | # expect(SpectrumAnalyzer.analyze).to raise_error 62 | # 63 | #end 64 | # 65 | #it 'bombs out without a window :( - Quick Analyze' do 66 | # SpectrumAnalyzer.configuration.window_function = [] 67 | # SpectrumAnalyzer.configuration.file_name = @file_name 68 | # 69 | # expect(SpectrumAnalyzer.quick_analyze).to raise_error 70 | # 71 | #end 72 | end 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Code Climate](https://codeclimate.com/github/jusroberts/spectrum-analyzer.png)](https://codeclimate.com/github/jusroberts/spectrum-analyzer) [![Gem Version](https://badge.fury.io/rb/spectrum-analyzer.png)](http://badge.fury.io/rb/spectrum-analyzer) [![Dependency Status](https://gemnasium.com/jusroberts/spectrum-analyzer.png)](https://gemnasium.com/jusroberts/spectrum-analyzer) 2 | # Ruby Sound Spectrum Analyzer 3 | 4 | Have you ever wanted to break apart a sound file and see what frequencies make it up? Well, you're in luck! This program does JUST THAT!!!! 5 | 6 | ##How do I do? 7 | 8 | Right now, it's very basic. You need to initiate all the processes by yourself. 9 | ```ruby 10 | require 'spectrum-analyzer' 11 | ``` 12 | Setup your configuration: 13 | ```ruby 14 | c = SpectrumAnalyzer.configuration 15 | c.file_name = "whatever" 16 | c.window_function = :hanning #or :rectangular. Those are the only two implemented currently 17 | c.window_size = 512 #MUST BE A POWER OF 2!!!! 128, 256, 512, 1024, etc. If not, I cannot guarantee your results. In fact, you'll probably break FFTW3. Sorry. 18 | c.analysis_ranges = [] #This does things! Put an array with values you want to define your analysis range. 19 | ``` 20 | 21 | generate a spectrum 22 | ```ruby 23 | g = SpectrumAnalyzer.generate 24 | g.build_spectrum 25 | ``` 26 | You now have a bunch of "domains" held in the spectrum (SpectrumAnalyzer.spectrum). 27 | ```ruby 28 | s = SpectrumAnalyzer.spectrum 29 | s.domains 30 | ``` 31 | These domains are an array of frequencies that occur over the time slice defined by the window_size. The values are currently magnitudes of the complex numbers, and the complex numbers themselves. They represent the amplitude of the frequency ranges. The spectrum super class will contain the number of occurrences that match your analysis_ranges, and each domain will know if is an occurrence or not. 32 | 33 | The last feature before this is released as 0.2.0 will be quick_analyze. This will return true on the first occurrence of the analysis_ranges. This is useful if you are just trying to see if a wav file contains your ranges or not. 34 | 35 | ##Contributing to spectrum-analyzer 36 | 37 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet. 38 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it. 39 | * Fork the project. 40 | * Start a feature/bugfix branch. 41 | * Commit and push until you are happy with your contribution. 42 | * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. 43 | * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. 44 | 45 | ##Copyright 46 | 47 | Copyright (c) 2013 Justin Roberts. See LICENSE.txt for 48 | further details. 49 | 50 | -------------------------------------------------------------------------------- /lib/spectrum-analyzer/functions/generator.rb: -------------------------------------------------------------------------------- 1 | module SpectrumAnalyzer 2 | module Functions 3 | class Generator 4 | 5 | def initialize 6 | @config = SpectrumAnalyzer.configuration 7 | @window_functions = SpectrumAnalyzer::WindowFunctions.new(@config.window_size) 8 | @analysis = SpectrumAnalyzer::Objects::Analysis.new(@config.file_name) 9 | end 10 | 11 | def contains_frequency_range? 12 | quick_analyze 13 | end 14 | 15 | #rename to contains_frequency_range? 16 | def quick_analyze 17 | begin 18 | buffer = RubyAudio::Buffer.float(@config.window_size) 19 | RubyAudio::Sound.open(@config.file_name) do |snd| 20 | while snd.read(buffer) != 0 21 | return true if quick_analyze_buffer(buffer) 22 | end 23 | end 24 | 25 | rescue => err 26 | error(err) 27 | end 28 | end 29 | 30 | def analyze 31 | generate_spectrum() 32 | analyze_spectrum() 33 | return @analysis 34 | end 35 | 36 | private 37 | 38 | def quick_analyze_buffer(buffer) 39 | return true if domain_contains_frequencies?(generate_domain(buffer)) 40 | end 41 | 42 | def generate_domain(buffer) 43 | windowed_buffer = apply_window(buffer.to_a, windows[@config.window_function]) 44 | SpectrumAnalyzer::Objects::Domain.new(windowed_buffer) 45 | end 46 | 47 | def domain_contains_frequencies?(domain) 48 | domain.contains_frequencies?(@config.analysis_ranges) 49 | end 50 | 51 | def generate_spectrum 52 | begin 53 | buffer = RubyAudio::Buffer.float(@config.window_size) 54 | RubyAudio::Sound.open(@config.file_name) do |snd| 55 | while snd.read(buffer) != 0 56 | windowed_buffer = apply_window(buffer.to_a, windows[@config.window_function]) 57 | @analysis.add_domain_to_spectrum(windowed_buffer) 58 | end 59 | end 60 | 61 | rescue => err 62 | error(err) 63 | end 64 | end 65 | 66 | #this should be a function in spectrum class 67 | def sum_domains 68 | @analysis.sum_spectrum 69 | end 70 | 71 | def analyze_spectrum 72 | sum_domains 73 | find_occurrences 74 | end 75 | 76 | def find_occurrences 77 | @analysis.sum_occurrences 78 | end 79 | 80 | def windows 81 | @window_functions.windows() 82 | end 83 | 84 | def apply_window(buffer, window_type) 85 | windowed_array = Array.new() 86 | i=0 87 | buffer.each { |x| windowed_array[i] = x * window_type[i]; i+=1} 88 | end 89 | 90 | def error(err) 91 | raise StandardError, err 92 | end 93 | 94 | end 95 | end 96 | end 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /spectrum-analyzer.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | # stub: spectrum-analyzer 0.2.0 ruby lib 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "spectrum-analyzer" 9 | s.version = "0.2.1" 10 | 11 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 12 | s.authors = ["Justin Roberts"] 13 | s.date = "2013-12-12" 14 | s.description = "Analyze a wav file for specific frequency signatures" 15 | s.email = "justin.roberts@careerbuilder.com" 16 | s.extra_rdoc_files = [ 17 | "LICENSE.txt", 18 | "README.md" 19 | ] 20 | s.files = [ 21 | ".document", 22 | ".rspec", 23 | "Gemfile", 24 | "Gemfile.lock", 25 | "LICENSE.txt", 26 | "README.md", 27 | "Rakefile", 28 | "VERSION", 29 | "lib/spectrum-analyzer.rb", 30 | "lib/spectrum-analyzer/config.rb", 31 | "lib/spectrum-analyzer/functions.rb", 32 | "lib/spectrum-analyzer/functions/analyze.rb", 33 | "lib/spectrum-analyzer/functions/generator.rb", 34 | "lib/spectrum-analyzer/functions/window_functions.rb", 35 | "lib/spectrum-analyzer/objects/analysis.rb", 36 | "lib/spectrum-analyzer/objects/domain.rb", 37 | "lib/spectrum-analyzer/objects/file.rb", 38 | "lib/spectrum-analyzer/objects/spectrum.rb", 39 | "spec/analyze.wav", 40 | "spec/spec_helper.rb", 41 | "spec/spectrum-analyzer_spec.rb", 42 | "spectrum-analyzer.gemspec" 43 | ] 44 | s.homepage = "http://github.com/jusroberts/spectrum-analyzer" 45 | s.licenses = ["MIT"] 46 | s.require_paths = ["lib"] 47 | s.rubygems_version = "2.1.11" 48 | s.summary = "Analyze a wav file for specific frequency signatures" 49 | 50 | if s.respond_to? :specification_version then 51 | s.specification_version = 4 52 | 53 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 54 | s.add_development_dependency(%q, ["~> 2.8"]) 55 | s.add_development_dependency(%q, ["~> 4.0"]) 56 | s.add_development_dependency(%q, ["~> 1.0"]) 57 | s.add_development_dependency(%q, ["~> 2.0"]) 58 | s.add_development_dependency(%q, [">= 0"]) 59 | s.add_development_dependency(%q, [">= 0"]) 60 | s.add_development_dependency(%q, [">= 0"]) 61 | s.add_development_dependency(%q, [">= 0"]) 62 | else 63 | s.add_dependency(%q, ["~> 2.8"]) 64 | s.add_dependency(%q, ["~> 4.0"]) 65 | s.add_dependency(%q, ["~> 1.0"]) 66 | s.add_dependency(%q, ["~> 2.0"]) 67 | s.add_dependency(%q, [">= 0"]) 68 | s.add_dependency(%q, [">= 0"]) 69 | s.add_dependency(%q, [">= 0"]) 70 | s.add_dependency(%q, [">= 0"]) 71 | end 72 | else 73 | s.add_dependency(%q, ["~> 2.8"]) 74 | s.add_dependency(%q, ["~> 4.0"]) 75 | s.add_dependency(%q, ["~> 1.0"]) 76 | s.add_dependency(%q, ["~> 2.0"]) 77 | s.add_dependency(%q, [">= 0"]) 78 | s.add_dependency(%q, [">= 0"]) 79 | s.add_dependency(%q, [">= 0"]) 80 | s.add_dependency(%q, [">= 0"]) 81 | end 82 | end 83 | 84 | --------------------------------------------------------------------------------