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