spcore 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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