spcore 0.1.2 → 0.1.3

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.
Files changed (68) hide show
  1. data/ChangeLog.rdoc +14 -1
  2. data/README.rdoc +10 -2
  3. data/lib/spcore.rb +45 -14
  4. data/lib/spcore/{lib → core}/circular_buffer.rb +28 -1
  5. data/lib/spcore/core/constants.rb +7 -1
  6. data/lib/spcore/{lib → core}/delay_line.rb +15 -5
  7. data/lib/spcore/{lib → core}/envelope_detector.rb +13 -1
  8. data/lib/spcore/{lib → core}/oscillator.rb +33 -3
  9. data/lib/spcore/core/signal.rb +350 -0
  10. data/lib/spcore/filters/fir/dual_sinc_filter.rb +84 -0
  11. data/lib/spcore/filters/fir/fir.rb +87 -0
  12. data/lib/spcore/filters/fir/sinc_filter.rb +68 -0
  13. data/lib/spcore/{lib → filters/iir}/biquad_filter.rb +7 -0
  14. data/lib/spcore/{lib → filters/iir}/cookbook_allpass_filter.rb +2 -0
  15. data/lib/spcore/{lib → filters/iir}/cookbook_bandpass_filter.rb +2 -0
  16. data/lib/spcore/{lib → filters/iir}/cookbook_highpass_filter.rb +2 -0
  17. data/lib/spcore/{lib → filters/iir}/cookbook_lowpass_filter.rb +2 -0
  18. data/lib/spcore/{lib → filters/iir}/cookbook_notch_filter.rb +2 -0
  19. data/lib/spcore/interpolation/interpolation.rb +64 -0
  20. data/lib/spcore/resampling/discrete_resampling.rb +78 -0
  21. data/lib/spcore/resampling/hybrid_resampling.rb +30 -0
  22. data/lib/spcore/resampling/polynomial_resampling.rb +56 -0
  23. data/lib/spcore/transforms/dft.rb +47 -0
  24. data/lib/spcore/transforms/fft.rb +125 -0
  25. data/lib/spcore/{lib → util}/gain.rb +3 -0
  26. data/lib/spcore/{core → util}/limiters.rb +10 -1
  27. data/lib/spcore/util/plotter.rb +91 -0
  28. data/lib/spcore/{lib → util}/saturation.rb +2 -9
  29. data/lib/spcore/util/scale.rb +41 -0
  30. data/lib/spcore/util/signal_generator.rb +48 -0
  31. data/lib/spcore/version.rb +2 -1
  32. data/lib/spcore/windows/bartlett_hann_window.rb +15 -0
  33. data/lib/spcore/windows/bartlett_window.rb +13 -0
  34. data/lib/spcore/windows/blackman_harris_window.rb +14 -0
  35. data/lib/spcore/windows/blackman_nuttall_window.rb +14 -0
  36. data/lib/spcore/windows/blackman_window.rb +18 -0
  37. data/lib/spcore/windows/cosine_window.rb +13 -0
  38. data/lib/spcore/windows/flat_top_window.rb +27 -0
  39. data/lib/spcore/windows/gaussian_window.rb +15 -0
  40. data/lib/spcore/windows/hamming_window.rb +16 -0
  41. data/lib/spcore/windows/hann_window.rb +13 -0
  42. data/lib/spcore/windows/lanczos_window.rb +15 -0
  43. data/lib/spcore/windows/nuttall_window.rb +14 -0
  44. data/lib/spcore/windows/rectangular_window.rb +10 -0
  45. data/lib/spcore/windows/triangular_window.rb +14 -0
  46. data/spcore.gemspec +11 -3
  47. data/spec/{lib → core}/circular_buffer_spec.rb +0 -0
  48. data/spec/{lib → core}/delay_line_spec.rb +1 -1
  49. data/spec/{lib → core}/envelope_detector_spec.rb +3 -3
  50. data/spec/{lib → core}/oscillator_spec.rb +0 -0
  51. data/spec/filters/fir/dual_sinc_filter_spec.rb +64 -0
  52. data/spec/filters/fir/sinc_filter_spec.rb +57 -0
  53. data/spec/filters/iir/cookbook_filter_spec.rb +30 -0
  54. data/spec/interpolation/interpolation_spec.rb +49 -0
  55. data/spec/resampling/discrete_resampling_spec.rb +81 -0
  56. data/spec/resampling/hybrid_resampling_spec.rb +31 -0
  57. data/spec/resampling/polynomial_resampling_spec.rb +30 -0
  58. data/spec/transforms/dft_spec.rb +71 -0
  59. data/spec/transforms/fft_spec.rb +84 -0
  60. data/spec/{lib → util}/gain_spec.rb +2 -2
  61. data/spec/{core → util}/limiters_spec.rb +0 -0
  62. data/spec/{lib → util}/saturate_spec.rb +0 -0
  63. data/spec/util/signal_generator_spec.rb +54 -0
  64. data/spec/windows/window_spec.rb +33 -0
  65. metadata +90 -42
  66. data/lib/spcore/lib/interpolation.rb +0 -15
  67. data/spec/lib/cookbook_filter_spec.rb +0 -44
  68. data/spec/lib/interpolation_spec.rb +0 -21
@@ -0,0 +1,48 @@
1
+ module SPCore
2
+ # Provides methods for generating a Signal that contains test waveforms or noise.
3
+ class SignalGenerator
4
+ include Hashmake::HashMakeable
5
+
6
+ # used to process hashed args in #initialize.
7
+ ARG_SPECS = [
8
+ Hashmake::ArgSpec.new(:key => :size, :reqd => true, :type => Fixnum, :validator => ->(a){ a > 0 }),
9
+ Hashmake::ArgSpec.new(:key => :sample_rate, :reqd => true, :type => Float, :validator => ->(a){ a > 0.0 })
10
+ ]
11
+
12
+ attr_reader :sample_rate, :size
13
+
14
+ # A new instance of SignalGenerator.
15
+ # @param [Hash] args Required keys are :sample_rate and :size.
16
+ def initialize args
17
+ hash_make ARG_SPECS, args
18
+ end
19
+
20
+ # Generate a Signal object with noise data.
21
+ def make_noise amplitude = 1.0
22
+ output = Array.new(@size)
23
+ output.each_index do |i|
24
+ output[i] = rand * amplitude
25
+ end
26
+
27
+ return Signal.new(:sample_rate => @sample_rate, :data => output)
28
+ end
29
+
30
+ # Generate a Signal object with waveform data at the given frequencies.
31
+ def make_signal freqs, extra_osc_args = {}
32
+ args = { :sample_rate => @sample_rate }.merge! extra_osc_args
33
+ oscs = []
34
+ freqs.each do |freq|
35
+ oscs.push Oscillator.new args.merge(:frequency => freq)
36
+ end
37
+
38
+ output = Array.new(size, 0.0)
39
+ size.times do |n|
40
+ oscs.each do |osc|
41
+ output[n] += osc.sample
42
+ end
43
+ end
44
+
45
+ return Signal.new(:sample_rate => @sample_rate, :data => output)
46
+ end
47
+ end
48
+ end
@@ -1,4 +1,5 @@
1
+ # A library of signal processing methods and classes.
1
2
  module SPCore
2
3
  # spcore version
3
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
4
5
  end
@@ -0,0 +1,15 @@
1
+ module SPCore
2
+ # Produces a Bartlett-Hann window of a given size (number of samples).
3
+ # For more info, see https://en.wikipedia.org/wiki/Window_function#Bartlett.E2.80.93Hann_window.
4
+ class BartlettHannWindow
5
+ attr_reader :data
6
+ def initialize size
7
+ @data = Array.new(size)
8
+ a0, a1, a2 = 0.62, 0.48, 0.38
9
+
10
+ size.times do |n|
11
+ @data[n] = a0 - (a1 * ((n.to_f / (size - 1)) - 0.5).abs) - (a2 * Math::cos((TWO_PI * n)/(size - 1)))
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ module SPCore
2
+ # Produces a triangular window of a given size (number of samples).
3
+ # Endpoints are zero. Midpoint is one. There is a linear slope between endpoints and midpoint.
4
+ class BartlettWindow
5
+ attr_reader :data
6
+ def initialize size
7
+ @data = Array.new(size)
8
+ size.times do |n|
9
+ @data[n] = (2.0 / (size - 1)) * (((size - 1) / 2.0) - (n - ((size - 1) / 2.0)).abs)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module SPCore
2
+ # Produces a Blackman-Harris window of a given size (number of samples).
3
+ # For more info, see https://en.wikipedia.org/wiki/Window_function#Blackman.E2.80.93Harris_window.
4
+ class BlackmanHarrisWindow
5
+ attr_reader :data
6
+ def initialize size
7
+ @data = Array.new(size)
8
+ a0, a1, a2, a3 = 0.35875, 0.48829, 0.14128, 0.01168
9
+ size.times do |n|
10
+ @data[n] = a0 - a1 * Math::cos((TWO_PI * n)/(size - 1)) + a2 * Math::cos((FOUR_PI * n)/(size - 1)) - a3 * Math::cos((SIX_PI * n)/(size - 1))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module SPCore
2
+ # Produces a Blackman-Nuttall window of a given size (number of samples).
3
+ # For more info, see https://en.wikipedia.org/wiki/Window_function#Blackman.E2.80.93Nuttall_window.
4
+ class BlackmanNuttallWindow
5
+ attr_reader :data
6
+ def initialize size
7
+ @data = Array.new(size)
8
+ a0, a1, a2, a3 = 0.3635819, 0.4891775, 0.1365995, 0.0106411
9
+ size.times do |n|
10
+ @data[n] = a0 - a1 * Math::cos((TWO_PI * n)/(size - 1)) + a2 * Math::cos((FOUR_PI * n)/(size - 1)) - a3 * Math::cos((SIX_PI * n)/(size - 1))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module SPCore
2
+ # Produces a Blackman window of a given size (number of samples).
3
+ # For more info, see https://en.wikipedia.org/wiki/Window_function#Blackman_windows.
4
+ class BlackmanWindow
5
+ attr_reader :data
6
+ def initialize size
7
+ @data = Array.new size
8
+ alpha = 0.16
9
+ a0 = (1 - alpha) / 2.0
10
+ a1 = 0.5
11
+ a2 = alpha / 2.0
12
+
13
+ size.times do |n|
14
+ @data[n] = a0 - (a1 * Math::cos((TWO_PI * n)/(size - 1))) + (a2 * Math::cos((FOUR_PI * n)/(size - 1)))
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module SPCore
2
+ # Produces a Cosine window of a given size (number of samples).
3
+ # For more info, see https://en.wikipedia.org/wiki/Window_function#Cosine_window.
4
+ class CosineWindow
5
+ attr_reader :data
6
+ def initialize size
7
+ @data = Array.new size
8
+ size.times do |n|
9
+ @data[n] = Math::sin((Math::PI * n)/(size - 1))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ module SPCore
2
+ # Produces a flat top window of a given size (number of samples).
3
+ # A flat top window is a partially negative-valued window that has a flat top in
4
+ # the frequency domain. They are designed to have a broader bandwidth and so have
5
+ # a poorer frequency resolution, leading to low amplitude measurement error suitable
6
+ # for use in in spectrum analyzers for the measurement of amplitudes of sinusoidal
7
+ # frequency components
8
+ # For more info, see https://en.wikipedia.org/wiki/Window_function#Flat_top_window.
9
+ class FlatTopWindow
10
+ attr_reader :data
11
+ def initialize size
12
+ @data = Array.new(size)
13
+ a0, a1, a2, a3, a4 = 1.0, 1.93, 1.29, 0.388, 0.032
14
+
15
+ size.times do |n|
16
+ @data[n] = a0 - a1 * Math::cos((TWO_PI * n)/(size - 1)) + a2 * Math::cos((FOUR_PI * n)/(size - 1)) - a3 * Math::cos((SIX_PI * n)/(size - 1)) + a4 * Math::cos((EIGHT_PI * n)/(size - 1))
17
+ end
18
+
19
+ max = @data.max
20
+
21
+ # normalize to max of 1.0
22
+ @data.each_index do |i|
23
+ @data[i] /= max
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ module SPCore
2
+ # Produces a Gaussian window of a given size (number of samples).
3
+ # For more info, see https://en.wikipedia.org/wiki/Window_function#Gaussian_windows.
4
+ class GaussianWindow
5
+ attr_reader :data
6
+ def initialize size
7
+ @data = Array.new size
8
+ sigma = 0.4 # must be <= 0.5
9
+ size.times do |n|
10
+ a = (n - (size - 1) / 2) / (sigma * (size - 1) / 2)
11
+ @data[n] = Math::exp(-0.5 * a**2)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ module SPCore
2
+ # Produces a Hamming window of a given size (number of samples).
3
+ # For more info, see https://en.wikipedia.org/wiki/Window_function#Hamming_window.
4
+ class HammingWindow
5
+ attr_reader :data
6
+ def initialize size
7
+ @data = Array.new size
8
+ alpha = 0.54
9
+ beta = 1.0 - alpha
10
+
11
+ size.times do |n|
12
+ @data[n] = alpha - (beta * Math::cos((TWO_PI * n) / (size - 1)))
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ module SPCore
2
+ # Produces a Hann window of a given size (number of samples).
3
+ # For more info, see https://en.wikipedia.org/wiki/Window_function#Hann_.28Hanning.29_window.
4
+ class HannWindow
5
+ attr_reader :data
6
+ def initialize size
7
+ @data = Array.new(size)
8
+ size.times do |n|
9
+ @data[n] = 0.5 * (1 - Math::cos((TWO_PI * n)/(size - 1)))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module SPCore
2
+ # Produces a Lanczos window of a given size (number of samples).
3
+ # The Lanczos window is used in Lanczos resampling.
4
+ # For more info, see https://en.wikipedia.org/wiki/Window_function#Lanczos_window.
5
+ class LanczosWindow
6
+ attr_reader :data
7
+ def initialize size
8
+ @data = Array.new size
9
+ sinc = lambda {|x| (Math::sin(Math::PI * x))/(Math::PI * x) }
10
+ size.times do |n|
11
+ @data[n] = sinc.call(((2.0*n)/(size-1)) - 1)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module SPCore
2
+ # Produces a Nuttall window of a given size (number of samples).
3
+ # For more info, see https://en.wikipedia.org/wiki/Window_function#Nuttall_window.2C_continuous_first_derivative.
4
+ class NuttallWindow
5
+ attr_reader :data
6
+ def initialize size
7
+ @data = Array.new(size)
8
+ a0, a1, a2, a3 = 0.355768, 0.487396, 0.144232, 0.012604
9
+ size.times do |n|
10
+ @data[n] = a0 - a1 * Math::cos((TWO_PI * n)/(size - 1)) + a2 * Math::cos((FOUR_PI * n)/(size - 1)) - a3 * Math::cos((SIX_PI * n)/(size - 1))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ module SPCore
2
+ # Produces a rectangular window (all ones) of a given size (number of samples).
3
+ # For more info, see https://en.wikipedia.org/wiki/Window_function#Rectangular_window.
4
+ class RectangularWindow
5
+ attr_reader :data
6
+ def initialize size
7
+ @data = Array.new(size, 1.0)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ module SPCore
2
+ # Produces a triangular window of a given size (number of samples).
3
+ # Endpoints are near zero. Midpoint is one. There is a linear slope between endpoints and midpoint.
4
+ # For more info, see https://en.wikipedia.org/wiki/Window_function#Triangular_window
5
+ class TriangularWindow
6
+ attr_reader :data
7
+ def initialize size
8
+ @data = Array.new(size)
9
+ size.times do |n|
10
+ @data[n] = (2.0 / (size + 1)) * (((size + 1) / 2.0) - (n - ((size - 1) / 2.0)).abs)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -5,15 +5,23 @@ require File.expand_path('../lib/spcore/version', __FILE__)
5
5
  Gem::Specification.new do |gem|
6
6
  gem.name = "spcore"
7
7
  gem.version = SPCore::VERSION
8
- gem.summary = %q{Perform basic signal processing functions (delay line, filters, envelope detection, etc...).}
8
+ gem.summary = %q{A library of signal processing methods and classes.}
9
9
  gem.description = <<DESCRIPTION
10
- Contains core signal processing functions, including:
10
+ Contains core signal processing methods and classes, including:
11
+ Resampling (discrete up, down and up/down, polynomial up, and hybrid up/down)
12
+ FFT transform (forward and inverse)
13
+ DFT transform (forward and inverse)
14
+ Windows (Blackman, Hamming, etc.)
15
+ Windowed sinc filter for lowpass and highpass.
16
+ Dual windowed sinc filter for bandpass and bandstop.
17
+ Interpolation (linear and polynomial)
18
+ Data plotting via gnuplot (must be installed to use).
11
19
  Delay line
12
20
  Biquad filters
13
21
  Envelope detector
14
22
  Conversion from dB-linear and linear-dB
15
- Linear interpolation
16
23
  Oscillator with selectable wave type (sine, square, triangle, sawtooth)
24
+ Signal abstraction class.
17
25
 
18
26
  DESCRIPTION
19
27
  gem.license = "MIT"
@@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
3
  describe SPCore::DelayLine do
4
4
  it 'should' do
5
- SAMPLE_RATE = 44100.0
5
+ SAMPLE_RATE = 400.0
6
6
  MAX_DELAY_SEC = 0.1
7
7
 
8
8
  5.times do
@@ -3,8 +3,8 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3
3
  describe SPCore::EnvelopeDetector do
4
4
  describe '#process_sample' do
5
5
  it 'should produce an output that follows the amplitude of the input' do
6
- sample_rate = 10000.0
7
- freqs = [20.0, 200.0, 2000.0]
6
+ sample_rate = 400.0
7
+ freqs = [20.0, 60.0]
8
8
 
9
9
  envelope_start = 1.0
10
10
  envelope_end = 0.0
@@ -26,7 +26,7 @@ describe SPCore::EnvelopeDetector do
26
26
  sample_count = (5.0 * sample_rate / freq).to_i
27
27
  sample_count.times do |n|
28
28
  percent = n.to_f / sample_count
29
- amplitude = SPCore::Interpolation.linear 0.0, envelope_start, 1.0, envelope_end, percent
29
+ amplitude = SPCore::Interpolation.linear envelope_start, envelope_end, percent
30
30
  osc.amplitude = amplitude
31
31
 
32
32
  sample = osc.sample
File without changes
@@ -0,0 +1,64 @@
1
+ require 'pry'
2
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
3
+
4
+ describe SPCore::SincFilter do
5
+ before :all do
6
+ @sample_rate = 4000.0
7
+ @orders = [62]
8
+ @left_cutoffs = Scale.exponential 300.0..1500.0, 3
9
+ @right_cutoffs = Scale.exponential 400.0..1600.0, 3
10
+ end
11
+
12
+ context '.bandpass' do
13
+ it 'should keep magnitude below -20 dB below left cutoff and above right cutoff, and close to 0 dB between' do
14
+ @orders.each do |order|
15
+ @left_cutoffs.each_index do |i|
16
+ left_cutoff = @left_cutoffs[i]
17
+ right_cutoff = @right_cutoffs[i]
18
+
19
+ if order % 2 == 1
20
+ order += 1
21
+ end
22
+ filter = DualSincFilter.new :order => order, :left_cutoff_freq => left_cutoff, :right_cutoff_freq => right_cutoff, :sample_rate => @sample_rate, :window_class => BlackmanWindow
23
+ #filter.bandpass_fir.plot_freq_response false
24
+ freq_response = filter.bandpass_fir.freq_response true
25
+
26
+ freq_response.each do |freq, magnitude|
27
+ if freq <= (0.8 * left_cutoff) || freq >= (1.2 * right_cutoff)
28
+ magnitude.should be < -20.0 # using dB
29
+ elsif freq.between?(1.2 * left_cutoff, 0.8 * right_cutoff)
30
+ magnitude.should be_within(1.0).of(0.0) # using dB
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ context '.bandstop' do
39
+ it 'should keep magnitude close to 0 dB below left cutoff and above right cutoff, and below -20 dB between' do
40
+ @orders.each do |order|
41
+ @left_cutoffs.each_index do |i|
42
+ left_cutoff = @left_cutoffs[i]
43
+ right_cutoff = @right_cutoffs[i]
44
+
45
+ if order % 2 == 1
46
+ order += 1
47
+ end
48
+ filter = DualSincFilter.new :order => order, :left_cutoff_freq => left_cutoff, :right_cutoff_freq => right_cutoff, :sample_rate => @sample_rate, :window_class => BlackmanWindow
49
+ #filter.bandstop_fir.plot_freq_response false
50
+ freq_response = filter.bandstop_fir.freq_response true
51
+
52
+ freq_response.each do |freq, magnitude|
53
+ if freq <= (0.8 * left_cutoff) || freq >= (1.2 * right_cutoff)
54
+ magnitude.should be_within(1.0).of(0.0) # using dB
55
+ elsif freq.between?(1.2 * left_cutoff, 0.8 * right_cutoff)
56
+ magnitude.should be < -20.0 # using dB
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ end
@@ -0,0 +1,57 @@
1
+ require 'pry'
2
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
3
+
4
+ describe SPCore::SincFilter do
5
+ before :all do
6
+ @sample_rate = 4000.0
7
+ @orders = [62]
8
+ @cutoffs = Scale.exponential 400.0..1600.0, 3
9
+ end
10
+
11
+ context '.highpass' do
12
+ it 'should keep magnitude below-20 dB below cutoff and close to 0 dB above cutoff' do
13
+ @orders.each do |order|
14
+ @cutoffs.each do |cutoff|
15
+ if order % 2 == 1
16
+ order += 1
17
+ end
18
+ filter = SincFilter.new :order => order, :cutoff_freq => cutoff, :sample_rate => @sample_rate, :window_class => BlackmanWindow
19
+ #filter.highpass_fir.plot_freq_response false
20
+ freq_response = filter.highpass_fir.freq_response true
21
+
22
+ freq_response.each do |freq, magnitude|
23
+ if freq <= (0.8 * cutoff)
24
+ magnitude.should be < -20.0 # using dB
25
+ elsif freq >= (1.2 * cutoff)
26
+ magnitude.should be_within(1.0).of(0.0) # using dB
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ context '.lowpass' do
35
+ it 'should keep magnitude close to 0 dB below cutoff and below-20 dB above cutoff' do
36
+ @orders.each do |order|
37
+ @cutoffs.each do |cutoff|
38
+ if order % 2 == 1
39
+ order += 1
40
+ end
41
+ filter = SincFilter.new :order => order, :cutoff_freq => cutoff, :sample_rate => @sample_rate, :window_class => BlackmanWindow
42
+ #filter.lowpass_fir.plot_freq_response false
43
+ freq_response = filter.lowpass_fir.freq_response true
44
+
45
+ freq_response.each do |freq, magnitude|
46
+ if freq <= (0.8 * cutoff)
47
+ magnitude.should be_within(1.0).of(0.0) # using dB
48
+ elsif freq >= (1.2 * cutoff)
49
+ magnitude.should be < -20.0 # using dB
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ end