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,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