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.
- data/ChangeLog.rdoc +14 -1
- data/README.rdoc +10 -2
- data/lib/spcore.rb +45 -14
- data/lib/spcore/{lib → core}/circular_buffer.rb +28 -1
- data/lib/spcore/core/constants.rb +7 -1
- data/lib/spcore/{lib → core}/delay_line.rb +15 -5
- data/lib/spcore/{lib → core}/envelope_detector.rb +13 -1
- data/lib/spcore/{lib → core}/oscillator.rb +33 -3
- data/lib/spcore/core/signal.rb +350 -0
- data/lib/spcore/filters/fir/dual_sinc_filter.rb +84 -0
- data/lib/spcore/filters/fir/fir.rb +87 -0
- data/lib/spcore/filters/fir/sinc_filter.rb +68 -0
- data/lib/spcore/{lib → filters/iir}/biquad_filter.rb +7 -0
- data/lib/spcore/{lib → filters/iir}/cookbook_allpass_filter.rb +2 -0
- data/lib/spcore/{lib → filters/iir}/cookbook_bandpass_filter.rb +2 -0
- data/lib/spcore/{lib → filters/iir}/cookbook_highpass_filter.rb +2 -0
- data/lib/spcore/{lib → filters/iir}/cookbook_lowpass_filter.rb +2 -0
- data/lib/spcore/{lib → filters/iir}/cookbook_notch_filter.rb +2 -0
- data/lib/spcore/interpolation/interpolation.rb +64 -0
- data/lib/spcore/resampling/discrete_resampling.rb +78 -0
- data/lib/spcore/resampling/hybrid_resampling.rb +30 -0
- data/lib/spcore/resampling/polynomial_resampling.rb +56 -0
- data/lib/spcore/transforms/dft.rb +47 -0
- data/lib/spcore/transforms/fft.rb +125 -0
- data/lib/spcore/{lib → util}/gain.rb +3 -0
- data/lib/spcore/{core → util}/limiters.rb +10 -1
- data/lib/spcore/util/plotter.rb +91 -0
- data/lib/spcore/{lib → util}/saturation.rb +2 -9
- data/lib/spcore/util/scale.rb +41 -0
- data/lib/spcore/util/signal_generator.rb +48 -0
- data/lib/spcore/version.rb +2 -1
- data/lib/spcore/windows/bartlett_hann_window.rb +15 -0
- data/lib/spcore/windows/bartlett_window.rb +13 -0
- data/lib/spcore/windows/blackman_harris_window.rb +14 -0
- data/lib/spcore/windows/blackman_nuttall_window.rb +14 -0
- data/lib/spcore/windows/blackman_window.rb +18 -0
- data/lib/spcore/windows/cosine_window.rb +13 -0
- data/lib/spcore/windows/flat_top_window.rb +27 -0
- data/lib/spcore/windows/gaussian_window.rb +15 -0
- data/lib/spcore/windows/hamming_window.rb +16 -0
- data/lib/spcore/windows/hann_window.rb +13 -0
- data/lib/spcore/windows/lanczos_window.rb +15 -0
- data/lib/spcore/windows/nuttall_window.rb +14 -0
- data/lib/spcore/windows/rectangular_window.rb +10 -0
- data/lib/spcore/windows/triangular_window.rb +14 -0
- data/spcore.gemspec +11 -3
- data/spec/{lib → core}/circular_buffer_spec.rb +0 -0
- data/spec/{lib → core}/delay_line_spec.rb +1 -1
- data/spec/{lib → core}/envelope_detector_spec.rb +3 -3
- data/spec/{lib → core}/oscillator_spec.rb +0 -0
- data/spec/filters/fir/dual_sinc_filter_spec.rb +64 -0
- data/spec/filters/fir/sinc_filter_spec.rb +57 -0
- data/spec/filters/iir/cookbook_filter_spec.rb +30 -0
- data/spec/interpolation/interpolation_spec.rb +49 -0
- data/spec/resampling/discrete_resampling_spec.rb +81 -0
- data/spec/resampling/hybrid_resampling_spec.rb +31 -0
- data/spec/resampling/polynomial_resampling_spec.rb +30 -0
- data/spec/transforms/dft_spec.rb +71 -0
- data/spec/transforms/fft_spec.rb +84 -0
- data/spec/{lib → util}/gain_spec.rb +2 -2
- data/spec/{core → util}/limiters_spec.rb +0 -0
- data/spec/{lib → util}/saturate_spec.rb +0 -0
- data/spec/util/signal_generator_spec.rb +54 -0
- data/spec/windows/window_spec.rb +33 -0
- metadata +90 -42
- data/lib/spcore/lib/interpolation.rb +0 -15
- data/spec/lib/cookbook_filter_spec.rb +0 -44
- 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
|
@@ -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
|