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,84 @@
1
+ module SPCore
2
+
3
+ # Extended windowed sinc filter. Implements bandpass and bandstop using
4
+ # two SincFilterBase objects.
5
+ #
6
+ # Theoretical source: http://www.labbookpages.co.uk/audio/firWindowing.html
7
+ #
8
+ # @author James Tunnell
9
+ #
10
+ class DualSincFilter
11
+ include Hashmake::HashMakeable
12
+
13
+ # Use to process hashed args in #initialize.
14
+ ARG_SPECS = [
15
+ Hashmake::ArgSpec.new(:key => :order, :reqd => true, :type => Fixnum, :validator => ->(a){ a % 2 == 0 } ),
16
+ Hashmake::ArgSpec.new(:key => :sample_rate, :reqd => true, :type => Numeric, :validator => ->(a){ a > 0.0 } ),
17
+ Hashmake::ArgSpec.new(:key => :left_cutoff_freq, :reqd => true, :type => Numeric, :validator => ->(a){ a > 0.0 } ),
18
+ Hashmake::ArgSpec.new(:key => :right_cutoff_freq, :reqd => true, :type => Numeric, :validator => ->(a){ a > 0.0 } ),
19
+ Hashmake::ArgSpec.new(:key => :window_class, :reqd => false, :type => Class, :default => BlackmanWindow ),
20
+ ]
21
+
22
+ attr_reader :bandpass_fir, :bandstop_fir, :left_filter, :right_filter
23
+
24
+ # Given a filter order, 2 cutoff frequencies, sample rate, and window class,
25
+ # develop a FIR filter kernel that can be used for lowpass filtering.
26
+ def initialize args
27
+ hash_make DualSincFilter::ARG_SPECS, args
28
+
29
+ raise ArgumentError, "left_cutoff_freq is greater than 0.5 * sample_rate" if @left_cutoff_freq > (@sample_rate / 2)
30
+ raise ArgumentError, "right_cutoff_freq is greater than 0.5 * sample_rate" if @right_cutoff_freq > (@sample_rate / 2)
31
+ raise ArgumentError, "left_cutoff_freq is not less than right_cutoff_freq" unless @left_cutoff_freq < @right_cutoff_freq
32
+
33
+ @left_filter = SincFilter.new(
34
+ :sample_rate => @sample_rate,
35
+ :order => @order,
36
+ :cutoff_freq => @left_cutoff_freq,
37
+ :window_class => @window_class,
38
+ )
39
+
40
+ @right_filter = SincFilter.new(
41
+ :sample_rate => @sample_rate,
42
+ :order => @order,
43
+ :cutoff_freq => @right_cutoff_freq,
44
+ :window_class => @window_class,
45
+ )
46
+
47
+ size = @order + 1
48
+
49
+ # make FIR filter kernels for bandpass and bandstop
50
+
51
+ bandpass_kernel = Array.new(size)
52
+ bandstop_kernel = Array.new(size)
53
+ window = @window_class.new(size)
54
+
55
+ for n in 0...(@order / 2)
56
+ bandpass_kernel[size - 1 - n] = bandpass_kernel[n] = @right_filter.lowpass_fir.kernel[n] + @left_filter.highpass_fir.kernel[n]
57
+ bandstop_kernel[size - 1 - n] = bandstop_kernel[n] = @left_filter.lowpass_fir.kernel[n] + @right_filter.highpass_fir.kernel[n]
58
+ end
59
+
60
+ left_transition_freq = @left_cutoff_freq / @sample_rate
61
+ right_transition_freq = @right_cutoff_freq / @sample_rate
62
+ bw_times_two = 2.0 * (right_transition_freq - left_transition_freq)
63
+ window_center_val = window.data[@order / 2]
64
+
65
+ bandpass_kernel[@order / 2] = bw_times_two * window_center_val
66
+ bandstop_kernel[@order / 2] = (1.0 - bw_times_two) * window_center_val
67
+
68
+ @bandpass_fir = FIR.new bandpass_kernel, @sample_rate
69
+ @bandstop_fir = FIR.new bandstop_kernel, @sample_rate
70
+ end
71
+
72
+ # Process the input with the bandpass FIR.
73
+ # @return [Array] containing the filtered input.
74
+ def bandpass input
75
+ return @bandpass_fir.convolve input
76
+ end
77
+
78
+ # Process the input with the bandstop FIR.
79
+ # @return [Array] containing the filtered input.
80
+ def bandstop input
81
+ return @bandstop_fir.convolve input
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,87 @@
1
+ module SPCore
2
+ # General FIR filter class. Contains the filter kernel and performs
3
+ # convolution.
4
+ class FIR
5
+
6
+ attr_reader :kernel, :order, :sample_rate
7
+
8
+ # A new instance of FIR. Filter order will by kernel size - 1.
9
+ # @param [Array] kernel Filter kernel values.
10
+ # @param [Numeric] sample_rate The sample rate the filter operates at.
11
+ def initialize kernel, sample_rate
12
+ @kernel = kernel
13
+ @order = kernel.size - 1
14
+ @sample_rate = sample_rate
15
+ end
16
+
17
+ # Convolve the given input data with the filter kernel.
18
+ # @param [Array] input Array of input data to by convolved with filter kernel.
19
+ # The array size must be greater than the filter kernel size.
20
+ def convolve input
21
+ kernel_size = @kernel.size
22
+ raise ArgumentError, "input.size #{input.size} is not greater than filter kernel size #{kernel_size}" unless input.size > kernel_size
23
+
24
+ output = Array.new(input.size, 0.0)
25
+
26
+ for i in 0...kernel_size
27
+ sum = 0.0
28
+ # convolve the input with the filter kernel
29
+ for j in 0...i
30
+ sum += (input[j] * @kernel[kernel_size - (1 + i - j)])
31
+ end
32
+ output[i] = sum
33
+ end
34
+
35
+ for i in kernel_size...input.size
36
+ sum = 0.0
37
+ # convolve the input with the filter kernel
38
+ for j in 0...kernel_size
39
+ sum += (input[i-j] * @kernel[j])
40
+ end
41
+ output[i] = sum
42
+ end
43
+
44
+ return output
45
+ end
46
+
47
+ # Calculate the filter frequency magnitude response.
48
+ # @param [Numeric] use_db Calculate magnitude in dB.
49
+ def freq_response use_db = false
50
+
51
+ input = [0.0] + @kernel # make the size even
52
+ output = FFT.forward input
53
+
54
+ output = output[0...(output.size / 2)] # ignore second half (mirror image)
55
+ output = output.map {|x| x.magnitude } # calculate magnitudes from complex values
56
+
57
+ if use_db
58
+ output = output.map {|x| Gain::linear_to_db x }
59
+ end
60
+
61
+ response = {}
62
+ output.each_index do |n|
63
+ frequency = (@sample_rate * n) / (output.size * 2)
64
+ response[frequency] = output[n]
65
+ end
66
+
67
+ ## amplitude = 2 * f[j] / size
68
+ #output = output.map {|x| 2 * x / output.size }
69
+
70
+ return response
71
+ end
72
+
73
+ # Calculate the filter frequency magnitude response and
74
+ # graph the results.
75
+ # @param [Numeric] use_db Calculate magnitude in dB.
76
+ def plot_freq_response use_db = true
77
+ plotter = Plotter.new(
78
+ :title => "Freq magnitude response of #{@order}-order FIR filter",
79
+ :xlabel => "frequency (Hz)",
80
+ :ylabel => "magnitude#{use_db ? " (dB)" : ""}",
81
+ :logscale => "x"
82
+ )
83
+
84
+ plotter.plot_2d "" => freq_response(use_db)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,68 @@
1
+ require 'pry'
2
+ module SPCore
3
+
4
+ # Base windowed sinc filter. Implements lowpass and highpass. A bandpass
5
+ # and bandstop filter would be implemented using two of these.
6
+ #
7
+ # Theoretical source: http://www.labbookpages.co.uk/audio/firWindowing.html
8
+ #
9
+ # @author James Tunnell
10
+ #
11
+ class SincFilter
12
+ include Hashmake::HashMakeable
13
+
14
+ # Use to process hashed args in #initialize.
15
+ ARG_SPECS = [
16
+ Hashmake::ArgSpec.new(:key => :order, :reqd => true, :type => Fixnum, :validator => ->(a){ a % 2 == 0 } ),
17
+ Hashmake::ArgSpec.new(:key => :sample_rate, :reqd => true, :type => Numeric, :validator => ->(a){ a > 0.0 } ),
18
+ Hashmake::ArgSpec.new(:key => :cutoff_freq, :reqd => true, :type => Numeric, :validator => ->(a){ a > 0.0 } ),
19
+ Hashmake::ArgSpec.new(:key => :window_class, :reqd => false, :type => Class, :default => BlackmanWindow ),
20
+ ]
21
+
22
+ attr_reader :lowpass_fir, :highpass_fir
23
+
24
+ # Given a filter order, cutoff frequency, sample rate, and window class,
25
+ # develop a FIR filter kernel that can be used for lowpass filtering.
26
+ def initialize args
27
+ hash_make SincFilter::ARG_SPECS, args
28
+
29
+ raise ArgumentError, "cutoff_freq is greater than 0.5 * sample_rate" if @cutoff_freq > (@sample_rate / 2)
30
+
31
+ size = @order + 1
32
+ transition_freq = @cutoff_freq / @sample_rate
33
+ b = TWO_PI * transition_freq
34
+
35
+ # make FIR filter kernels for lowpass and highpass
36
+
37
+ lowpass_kernel = Array.new(size)
38
+ highpass_kernel = Array.new(size)
39
+ window = @window_class.new(size)
40
+
41
+ for n in 0...(@order / 2)
42
+ c = n - (@order / 2)
43
+ y = Math::sin(b * c) / (Math::PI * (c))
44
+ lowpass_kernel[size - 1 - n] = lowpass_kernel[n] = y * window.data[n]
45
+ highpass_kernel[size - 1 - n] = highpass_kernel[n] = -lowpass_kernel[n]
46
+ end
47
+ lowpass_kernel[@order / 2] = 2 * transition_freq * window.data[@order / 2]
48
+ highpass_kernel[@order / 2] = (1 - 2 * transition_freq) * window.data[@order / 2]
49
+
50
+ @lowpass_fir = FIR.new lowpass_kernel, @sample_rate
51
+ @highpass_fir = FIR.new highpass_kernel, @sample_rate
52
+ end
53
+
54
+ # Process the input with the lowpass FIR.
55
+ # @return [Array] containing the filtered input.
56
+ def lowpass input
57
+ return @lowpass_fir.convolve input
58
+ end
59
+
60
+ # Process the input with the highpass FIR.
61
+ # @return [Array] containing the filtered input.
62
+ def highpass input
63
+ return @highpass_fir.convolve input
64
+ end
65
+ end
66
+
67
+
68
+ end
@@ -13,6 +13,8 @@ class BiquadFilter
13
13
  # this holds the data required to update samples thru a filter
14
14
  Struct.new("BiquadState", :b0, :b1, :b2, :a0, :a1, :a2, :x1, :x2, :y1, :y2)
15
15
 
16
+ # A new instance of BiquadFilter.
17
+ # @param [Numeric] sample_rate The sample rate to use in calculating coefficients.
16
18
  def initialize sample_rate
17
19
  @sample_rate = sample_rate
18
20
  @biquad = Struct::BiquadState.new(0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0)
@@ -20,14 +22,17 @@ class BiquadFilter
20
22
  @bandwidth = 0.0
21
23
  end
22
24
 
25
+ # Set the filter critical frequency and bandwidth.
23
26
  def set_critical_freq_and_bw critical_freq, bandwidth
24
27
  raise NotImplementedError, "set_critical_freq_and_bW should be implemented in the derived class!"
25
28
  end
26
29
 
30
+ # Set the filter critical frequency.
27
31
  def critical_freq= critical_freq
28
32
  set_critical_freq_and_bw(critical_freq, @bandwidth);
29
33
  end
30
34
 
35
+ # Set the filter bandwidth.
31
36
  def bandwidth= bandwidth
32
37
  set_critical_freq_and_bw(@critical_freq, bandwidth);
33
38
  end
@@ -56,6 +61,8 @@ class BiquadFilter
56
61
  return result
57
62
  end
58
63
 
64
+ # Calculate the frequency magnitude response for the given frequency.
65
+ # @param [Numeric] test_freq The frequency to calculate magnitude response at.
59
66
  def get_freq_magnitude_response test_freq
60
67
  # Method for determining freq magnitude response is from:
61
68
  # http://rs-met.com/documents/dsp/BasicDigitalFilters.pdf
@@ -1,4 +1,6 @@
1
1
  module SPCore
2
+ # Implement a "cookbook" allpass filter using the BiquadFilter class,
3
+ # based on the well-known RBJ biquad filter.
2
4
  class CookbookAllpassFilter < BiquadFilter
3
5
  def initialize sample_rate
4
6
  super(sample_rate)
@@ -1,4 +1,6 @@
1
1
  module SPCore
2
+ # Implement a "cookbook" bandpass filter using the BiquadFilter class,
3
+ # based on the well-known RBJ biquad filter.
2
4
  class CookbookBandpassFilter < BiquadFilter
3
5
  def initialize sample_rate
4
6
  super(sample_rate)
@@ -1,4 +1,6 @@
1
1
  module SPCore
2
+ # Implement a "cookbook" highpass filter using the BiquadFilter class,
3
+ # based on the well-known RBJ biquad filter.
2
4
  class CookbookHighpassFilter < BiquadFilter
3
5
  def initialize sample_rate
4
6
  super(sample_rate)
@@ -1,4 +1,6 @@
1
1
  module SPCore
2
+ # Implement a "cookbook" lowpass filter using the BiquadFilter class,
3
+ # based on the well-known RBJ biquad filter.
2
4
  class CookbookLowpassFilter < BiquadFilter
3
5
  def initialize sample_rate
4
6
  super(sample_rate)
@@ -1,4 +1,6 @@
1
1
  module SPCore
2
+ # Implement a "cookbook" notch (bandstop) filter using the BiquadFilter class,
3
+ # based on the well-known RBJ biquad filter.
2
4
  class CookbookNotchFilter < BiquadFilter
3
5
  def initialize sample_rate
4
6
  super(sample_rate)
@@ -0,0 +1,64 @@
1
+ module SPCore
2
+ # Provide interpolation methods, including linear and polynomial.
3
+ class Interpolation
4
+ # Linear Interpolation Equation:
5
+ #
6
+ # (x3 - x1)(y2 - y1)
7
+ # y3 = ------------------ + y1
8
+ # (x2 - x1)
9
+ #
10
+ def self.linear x1, y1, x2, y2, x3
11
+ y3 = ((x3 - x1) * (y2 - y1)) / (x2 - x1);
12
+ y3 += y1;
13
+ return y3;
14
+ end
15
+
16
+ # Linear interpolator
17
+ # Given 2 sample points, interpolates a value anywhere between the two points.
18
+ #
19
+ # @param [Numeric] y0 First (left) y-value
20
+ # @param [Numeric] y0 Second (right) y-value
21
+ # @param [Numeric] x Percent distance (along the x-axis) between the two y-values
22
+ def self.linear y0, y1, x
23
+ raise ArgumentError, "x is not between 0.0 and 1.0" unless x.between?(0.0,1.0)
24
+ return y0 + x * (y1 - y0)
25
+ end
26
+
27
+ # 4-point, 3rd-order (cubic) Hermite interpolater (x-form).
28
+ #
29
+ # Given 4 evenly-spaced sample points, interpolate a value anywhere between
30
+ # the middle two points.
31
+ #
32
+ # implementation source: www.musicdsp.org/archive.php?classid=5#93
33
+ #
34
+ # @param [Float] y0 First y-value
35
+ # @param [Float] y1 Second y-value (first middle point)
36
+ # @param [Float] y2 Third y-value (second middle point)
37
+ # @param [Float] y3 Fourth y-value
38
+ # @param [Float] x Percent distance (along the x-axis) between the middle two y-values (y1 and y2)
39
+ def self.cubic_hermite y0, y1, y2, y3, x
40
+ raise ArgumentError, "x is not between 0.0 and 1.0" unless x.between?(0.0,1.0)
41
+
42
+ ## method 1 (slowest)
43
+ #c0 = y1
44
+ #c1 = 0.5 * (y2 - y0)
45
+ #c2 = y0 - 2.5 * y1 + 2*y2 - 0.5 * y3
46
+ #c3 = 1.5 * (y1 - y2) + 0.5 * (y3 - y0)
47
+
48
+ # method 2 (basically tied with method 3)
49
+ c0 = y1
50
+ c1 = 0.5 * (y2 - y0)
51
+ c3 = 1.5 * (y1 - y2) + 0.5 * (y3 - y0)
52
+ c2 = y0 - y1 + c1 - c3
53
+
54
+ ## method 3 (basically tied with method 2)
55
+ #c0 = y1
56
+ #c1 = 0.5 * (y2 - y0)
57
+ #y0my1 = y0 - y1
58
+ #c3 = (y1 - y2) + 0.5 * (y3 - y0my1 - y2)
59
+ #c2 = y0my1 + c1 - c3
60
+
61
+ return ((c3 * x + c2) * x + c1) * x + c0
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,78 @@
1
+ module SPCore
2
+ # Provide resampling methods (upsampling and downsampling) using
3
+ # discrete filtering.
4
+ class DiscreteResampling
5
+
6
+ def self.upsample input, sample_rate, upsample_factor, filter_order
7
+ raise ArgumentError, "input.size is less than four" unless input.size >= 4
8
+ raise ArgumentError, "upsample_factor is not a Fixnum" unless upsample_factor.is_a?(Fixnum)
9
+ raise ArgumentError, "upsample_factor is not greater than 1" unless upsample_factor > 1
10
+ raise ArgumentError, "sample_rate is not greater than 0" unless sample_rate > 0
11
+
12
+ output = Array.new((upsample_factor * input.size).to_i, 0.0)
13
+ input.each_index do |i|
14
+ output[i * upsample_factor] = input[i] * upsample_factor
15
+ end
16
+
17
+ filter = SincFilter.new(
18
+ :sample_rate => (sample_rate * upsample_factor),
19
+ :order => filter_order,
20
+ :cutoff_freq => (sample_rate / 2),
21
+ :window_class => NuttallWindow
22
+ )
23
+
24
+ return filter.lowpass(output)
25
+ end
26
+
27
+ def self.downsample input, sample_rate, downsample_factor, filter_order
28
+ raise ArgumentError, "input.size is less than four" unless input.size >= 4
29
+ raise ArgumentError, "downsample_factor is not a Fixnum" unless downsample_factor.is_a?(Fixnum)
30
+ raise ArgumentError, "downsample_factor is not greater than 1" unless downsample_factor > 1
31
+ raise ArgumentError, "sample_rate is not greater than 0" unless sample_rate > 0
32
+
33
+ needed_samples = input.size % downsample_factor
34
+ if needed_samples == 0
35
+ input += Array.new(needed_samples, 0.0)
36
+ end
37
+
38
+ filter = SincFilter.new(
39
+ :sample_rate => sample_rate, :order => filter_order,
40
+ :cutoff_freq => ((sample_rate / downsample_factor) / 2.0),
41
+ :window_class => NuttallWindow
42
+ )
43
+
44
+ filtered = filter.lowpass(input)
45
+ return Array.new(filtered.size / downsample_factor) { |i| filtered[i * downsample_factor] }
46
+ end
47
+
48
+ def self.resample input, sample_rate, upsample_factor, downsample_factor, filter_order
49
+ raise ArgumentError, "input.size is less than four" unless input.size >= 4
50
+ raise ArgumentError, "upsample_factor is not a Fixnum" unless upsample_factor.is_a?(Fixnum)
51
+ raise ArgumentError, "upsample_factor is not greater than 1" unless upsample_factor > 1
52
+ raise ArgumentError, "downsample_factor is not a Fixnum" unless downsample_factor.is_a?(Fixnum)
53
+ raise ArgumentError, "downsample_factor is not greater than 1" unless downsample_factor > 1
54
+ raise ArgumentError, "sample_rate is not greater than 0" unless sample_rate > 0
55
+
56
+ upsampled = Array.new((upsample_factor * input.size).to_i, 0.0)
57
+ input.each_index do |i|
58
+ upsampled[i * upsample_factor] = input[i] * upsample_factor
59
+ end
60
+
61
+ needed_samples = upsampled.size % downsample_factor
62
+ if needed_samples == 0
63
+ upsampled += Array.new(needed_samples, 0.0)
64
+ end
65
+
66
+ target_rate = sample_rate * upsample_factor / downsample_factor
67
+ cutoff = (target_rate < sample_rate) ? (target_rate / 2.0) : (sample_rate / 2.0)
68
+
69
+ filter = SincFilter.new(
70
+ :sample_rate => (sample_rate * upsample_factor), :order => filter_order,
71
+ :cutoff_freq => cutoff, :window_class => NuttallWindow
72
+ )
73
+ filtered = filter.lowpass(upsampled)
74
+ return Array.new(filtered.size / downsample_factor){ |i| filtered[i * downsample_factor] }
75
+ end
76
+
77
+ end
78
+ end