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,30 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+ require 'gnuplot'
3
+ require 'pry'
4
+
5
+ describe 'cookbook filter' do
6
+ it 'should produce a nice frequency response graph' do
7
+ #sample_rate = 4000.0
8
+ #crit_freq = 400.0
9
+ #min_test_freq = 10.0
10
+ #max_test_freq = (sample_rate / 2.0) - 1.0
11
+ #bw = 0.3
12
+ #
13
+ #[SPCore::CookbookLowpassFilter, SPCore::CookbookHighpassFilter, SPCore::CookbookBandpassFilter].each do |filter_class|
14
+ # filter = filter_class.new sample_rate
15
+ # filter.set_critical_freq_and_bw crit_freq, bw
16
+ #
17
+ # freq_response = {}
18
+ # Scale.exponential(min_test_freq..max_test_freq, 200).each do |freq|
19
+ # mag = filter.get_freq_magnitude_response freq
20
+ # freq_response[freq] = SPCore::Gain.linear_to_db(mag)
21
+ # end
22
+ #
23
+ # plotter = Plotter.new(
24
+ # :title => "Frequency Magnitude Response for #{filter_class} with critical freq of #{crit_freq}",
25
+ # :logscale => "x"
26
+ # )
27
+ # plotter.plot_2d "magnitude (dB)" => freq_response
28
+ #end
29
+ end
30
+ end
@@ -0,0 +1,49 @@
1
+ require 'pry'
2
+ require 'benchmark'
3
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
4
+
5
+ describe SPCore::Interpolation do
6
+ context '.linear' do
7
+ it 'should interpolate floating-point values' do
8
+ result = SPCore::Interpolation.linear 2.0, 4.0, 0.5
9
+ result.should eq(3.0)
10
+ end
11
+
12
+ it 'should interpolate integer values' do
13
+ result = SPCore::Interpolation.linear 20, 40, 0.5
14
+ result.should eq(30)
15
+ end
16
+ end
17
+
18
+ context '.cubic_hermite' do
19
+ it 'should look like...' do
20
+ y0, y1, y2, y3 = 1.75, 1, 0.75, -1.5
21
+
22
+ x_ary = []
23
+ y_ary = []
24
+
25
+ (0.0..1.0).step(0.05) do |x|
26
+ y = SPCore::Interpolation.cubic_hermite y0, y1, y2, y3, x
27
+ x_ary << x
28
+ y_ary << y
29
+ end
30
+
31
+ #Gnuplot.open do |gp|
32
+ # Gnuplot::Plot.new(gp) do |plot|
33
+ # plot.title "interpolated values"
34
+ # plot.xlabel "x"
35
+ # plot.ylabel "f(x)"
36
+ #
37
+ # plot.data = [
38
+ # Gnuplot::DataSet.new( [ x_ary, y_ary ] ) { |ds|
39
+ # ds.with = "linespoints"
40
+ # ds.title = "cubic hermite"
41
+ # ds.linewidth = 1
42
+ # }
43
+ # ]
44
+ # end
45
+ #end
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,81 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'gnuplot'
3
+
4
+ describe SPCore::DiscreteResampling do
5
+
6
+ context '.upsample' do
7
+ it 'should produce output signal with the same max frequency (put through forward FFT)' do
8
+ sample_rate = 400.0
9
+ test_freq = 10.0
10
+ size = 64
11
+ upsample_factor = 4
12
+ order = (sample_rate / test_freq).to_i
13
+
14
+ signal1 = SignalGenerator.new(:sample_rate => sample_rate, :size => size).make_signal [test_freq]
15
+ signal1 *= BlackmanWindow.new(size).data
16
+ signal2 = signal1.clone.upsample_discrete upsample_factor, order
17
+
18
+ #plotter = Plotter.new(:title => "Discrete upsampling by #{upsample_factor}")
19
+ #plotter.plot_1d("original signal" => signal1.data, "upsampled signal" => signal2.data)
20
+
21
+ signal2.size.should eq(signal1.size * upsample_factor)
22
+
23
+ max_freq1 = signal1.freq_magnitudes.max_by{|freq, mag| mag }[0]
24
+ max_freq2 = signal2.freq_magnitudes.max_by{|freq, mag| mag }[0]
25
+
26
+ percent_error = (max_freq1 - max_freq2).abs / max_freq1
27
+ percent_error.should be_within(0.1).of(0.0)
28
+ end
29
+ end
30
+
31
+ context '.downsample' do
32
+ it 'should produce output signal with the same max frequency (put through forward FFT)' do
33
+ sample_rate = 400.0
34
+ test_freq = 10.0
35
+ size = 128
36
+ downsample_factor = 4
37
+ order = (sample_rate / test_freq).to_i
38
+
39
+ signal1 = SignalGenerator.new(:sample_rate => sample_rate, :size => size).make_signal [test_freq]
40
+ signal1 *= BlackmanWindow.new(size).data
41
+ signal2 = signal1.clone.downsample_discrete downsample_factor, order
42
+
43
+ #plotter = Plotter.new(:title => "Discrete downsampling by #{downsample_factor}")
44
+ #plotter.plot_1d("original signal" => signal1.data, "downsampled signal" => signal2.data)
45
+
46
+ signal2.size.should eq(signal1.size / downsample_factor)
47
+
48
+ max_freq1 = signal1.freq_magnitudes.max_by{|freq, mag| mag }[0]
49
+ max_freq2 = signal2.freq_magnitudes.max_by{|freq, mag| mag }[0]
50
+
51
+ percent_error = (max_freq1 - max_freq2).abs / max_freq1
52
+ percent_error.should be_within(0.1).of(0.0)
53
+ end
54
+ end
55
+
56
+ context '.resample' do
57
+ it 'should produce output signal with the same max frequency (put through forward FFT)' do
58
+ sample_rate = 400.0
59
+ test_freq = 10.0
60
+ size = 64
61
+ upsample_factor = 4
62
+ downsample_factor = 4
63
+ order = (sample_rate / test_freq).to_i
64
+
65
+ signal1 = SignalGenerator.new(:sample_rate => sample_rate, :size => size).make_signal [test_freq]
66
+ signal1 *= BlackmanWindow.new(size).data
67
+ signal2 = signal1.clone.resample_discrete upsample_factor, downsample_factor, order
68
+
69
+ #plotter = Plotter.new(:title => "Discrete resampling, up by #{upsampling_factor}, down by #{downsample_factor}")
70
+ #plotter.plot_1d("original signal" => signal1.data, "resampled signal" => signal2.data)
71
+
72
+ signal2.size.should eq(signal1.size * upsample_factor / downsample_factor)
73
+
74
+ max_freq1 = signal1.freq_magnitudes.max_by{|freq, mag| mag }[0]
75
+ max_freq2 = signal2.freq_magnitudes.max_by{|freq, mag| mag }[0]
76
+
77
+ percent_error = (max_freq1 - max_freq2).abs / max_freq1
78
+ percent_error.should be_within(0.1).of(0.0)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,31 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'gnuplot'
3
+
4
+ describe SPCore::HybridResampling do
5
+
6
+ context '.resample' do
7
+ it 'should produce output signal with the same max frequency (put through forward FFT)' do
8
+ sample_rate = 400.0
9
+ test_freq = 10.0
10
+ size = 64
11
+ upsample_factor = 4
12
+ downsample_factor = 4
13
+ order = (sample_rate / test_freq).to_i
14
+
15
+ signal1 = SignalGenerator.new(:sample_rate => sample_rate, :size => size).make_signal [test_freq]
16
+ signal1 *= BlackmanWindow.new(size).data
17
+ signal2 = signal1.clone.resample_hybrid upsample_factor, downsample_factor, order
18
+
19
+ #plotter = Plotter.new(:title => "Discrete resampling, up by #{upsampling_factor}, down by #{downsample_factor}")
20
+ #plotter.plot_1d("original signal" => signal1.data, "resampled signal" => signal2.data)
21
+
22
+ signal2.size.should eq(signal1.size * upsample_factor / downsample_factor)
23
+
24
+ max_freq1 = signal1.freq_magnitudes.max_by{|freq, mag| mag }[0]
25
+ max_freq2 = signal2.freq_magnitudes.max_by{|freq, mag| mag }[0]
26
+
27
+ percent_error = (max_freq1 - max_freq2).abs / max_freq1
28
+ percent_error.should be_within(0.1).of(0.0)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'gnuplot'
3
+
4
+ describe SPCore::PolynomialResampling do
5
+
6
+ context '.upsample' do
7
+ it 'should produce output signal with the same max frequency (put through forward DFT)' do
8
+ sample_rate = 400.0
9
+ test_freq = 10.0
10
+ size = (sample_rate * 5.0 / test_freq).to_i
11
+ upsample_factor = 2.5
12
+
13
+ generator = SignalGenerator.new :sample_rate => sample_rate, :size => size
14
+ signal1 = generator.make_signal [test_freq]
15
+ signal2 = signal1.clone.upsample_polynomial upsample_factor
16
+
17
+ #plotter = Plotter.new(:title => "Polynomial upsampling by #{upsample_factor}")
18
+ #plotter.plot_1d("original signal" => signal1.data, "upsampled signal" => signal2.data)
19
+
20
+ signal2.size.should eq(signal1.size * upsample_factor)
21
+
22
+ max_freq1 = signal1.freq_magnitudes.max_by{|freq, mag| mag }[0]
23
+ max_freq2 = signal2.freq_magnitudes.max_by{|freq, mag| mag }[0]
24
+
25
+ percent_error = (max_freq1 - max_freq2).abs / max_freq1
26
+ percent_error.should be_within(0.1).of(0.0)
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,71 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'pry'
3
+ require 'gnuplot'
4
+
5
+ describe SPCore::DFT do
6
+ context '.forward' do
7
+
8
+ it 'should produce a freq magnitude response peak that is within 10 percent of the test freq' do
9
+ dft_size = 64
10
+ sample_rate = 400.0
11
+
12
+ test_freqs = [
13
+ 20.0,
14
+ 40.0,
15
+ ]
16
+
17
+ test_freqs.each do |freq|
18
+ signal = SignalGenerator.new(:sample_rate => sample_rate, :size => dft_size).make_signal([freq])
19
+ signal *= BlackmanHarrisWindow.new(dft_size).data
20
+
21
+ output = DFT.forward signal.data
22
+ output = output[0...(output.size / 2)] # skip second half
23
+ output = output.map { |x| x.magnitude } # map complex values to magnitude
24
+
25
+ freq_magnitudes = {}
26
+ output.each_index do |i|
27
+ f = (sample_rate * i) / dft_size
28
+ freq_magnitudes[f] = output[i]
29
+ end
30
+
31
+ max_freq = freq_magnitudes.max_by {|f,mag| mag}[0]
32
+ percent_err = (max_freq - freq).abs / freq
33
+ percent_err.should be_within(0.10).of(0.0)
34
+ end
35
+ end
36
+ end
37
+
38
+ context '.inverse' do
39
+
40
+ it 'should produce a near-identical signal to the original sent into the forward DFT (with energy that is within 10 percent error of original signal)' do
41
+ dft_size = 32
42
+ sample_rate = 400.0
43
+
44
+ test_freqs = [
45
+ 20.0,
46
+ 40.0,
47
+ ]
48
+
49
+ test_freqs.each do |freq|
50
+ input = SignalGenerator.new(:sample_rate => sample_rate, :size => dft_size).make_signal([freq])
51
+ input *= BlackmanHarrisWindow.new(dft_size).data
52
+
53
+ output = DFT.forward input.data
54
+ input2 = DFT.inverse output
55
+
56
+ energy1 = input.energy
57
+ energy2 = input2.inject(0.0){|sum,x| sum + (x * x)}
58
+
59
+ percent_err = (energy2 - energy1).abs / energy1
60
+ percent_err.should be_within(0.10).of(0.0)
61
+
62
+ #Plotter.new(
63
+ # :title => "#{dft_size}-point DFT on #{freq} Hz sine wave signal",
64
+ # :xlabel => "frequency (Hz)",
65
+ # :ylabel => "magnitude response",
66
+ #).plot_1d("input1" => input.data, "input2" => input2)
67
+ end
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,84 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'pry'
3
+ require 'gnuplot'
4
+
5
+ describe SPCore::FFT do
6
+ context '.forward' do
7
+ it 'should produce a freq magnitude response peak that is within 10 percent of the test freq' do
8
+ fft_size = 64
9
+ sample_rate = 400.0
10
+
11
+ test_freqs = [
12
+ 20.0,
13
+ 40.0,
14
+ ]
15
+
16
+ test_freqs.each do |freq|
17
+ osc = Oscillator.new(:frequency => freq, :sample_rate => sample_rate)
18
+
19
+ input_size = fft_size
20
+
21
+ input = Array.new(input_size)
22
+ window = BlackmanHarrisWindow.new(input_size)
23
+
24
+ input_size.times do |i|
25
+ input[i] = osc.sample * window.data[i]
26
+ end
27
+
28
+ output = FFT.forward input
29
+ output = output[0...(output.size / 2)]
30
+ output = output.map { |x| x.magnitude } # map complex values to magnitude
31
+
32
+ magn_response = {}
33
+ output.each_index do |n|
34
+ f = (sample_rate * n) / (output.size * 2)
35
+ magn_response[f] = output[n]
36
+ end
37
+
38
+ max_freq = magn_response.max_by {|f,magn| magn }[0]
39
+ percent_err = (max_freq - freq).abs / freq
40
+ percent_err.should be_within(0.10).of(0.0)
41
+
42
+ #Plotter.new(
43
+ # :title => "#{input_size}-point FFT on #{freq} Hz sine wave signal",
44
+ # :xlabel => "frequency (Hz)",
45
+ # :ylabel => "magnitude response",
46
+ #).plot_2d("magnitude response" => magn_response)
47
+ end
48
+ end
49
+ end
50
+
51
+ context '.inverse' do
52
+
53
+ it 'should produce a near-identical signal to the original sent into the forward DFT (with energy that is within 10 percent error of original signal)' do
54
+ fft_size = 32
55
+ sample_rate = 400.0
56
+
57
+ test_freqs = [
58
+ 20.0,
59
+ 40.0,
60
+ ]
61
+
62
+ test_freqs.each do |freq|
63
+ input = SignalGenerator.new(:sample_rate => sample_rate, :size => fft_size).make_signal([freq])
64
+ input *= BlackmanHarrisWindow.new(fft_size).data
65
+
66
+ output = FFT.forward input.data
67
+ input2 = FFT.inverse output
68
+
69
+ energy1 = input.energy
70
+ energy2 = input2.inject(0.0){|sum,x| sum + (x * x)}
71
+
72
+ percent_err = (energy2 - energy1).abs / energy1
73
+ percent_err.should be_within(0.10).of(0.0)
74
+
75
+ #Plotter.new(
76
+ # :title => "#{fft_size}-point FFT on #{freq} Hz sine wave signal",
77
+ # :xlabel => "frequency (Hz)",
78
+ # :ylabel => "magnitude response",
79
+ #).plot_1d("input1" => input.data, "input2" => input2)
80
+ end
81
+ end
82
+ end
83
+
84
+ end
@@ -19,7 +19,7 @@ describe SPCore::Gain do
19
19
 
20
20
  it 'should prove to be the inverse of .db_to_linear' do
21
21
  20.times do
22
- x = SPCore::Interpolation.linear(0.0, -SPCore::Gain::MAX_DB_ABS, 1.0, SPCore::Gain::MAX_DB_ABS, rand)
22
+ x = SPCore::Interpolation.linear(-SPCore::Gain::MAX_DB_ABS, SPCore::Gain::MAX_DB_ABS, rand)
23
23
  y = SPCore::Gain::db_to_linear(x)
24
24
  z = SPCore::Gain::linear_to_db(y)
25
25
  ((z - x).abs / x).should be_within(1e-5).of(0.0)
@@ -38,7 +38,7 @@ describe SPCore::Gain do
38
38
  20.times do
39
39
  max_gain_linear = SPCore::Gain::db_to_linear(SPCore::Gain::MAX_DB_ABS)
40
40
  min_gain_linear = SPCore::Gain::db_to_linear(-SPCore::Gain::MAX_DB_ABS)
41
- x = SPCore::Interpolation.linear(0.0, min_gain_linear, 1.0, max_gain_linear, rand)
41
+ x = SPCore::Interpolation.linear(min_gain_linear, max_gain_linear, rand)
42
42
  y = SPCore::Gain::linear_to_db(x)
43
43
  z = SPCore::Gain::db_to_linear(y)
44
44
  ((z - x).abs / x).should be_within(1e-5).of(0.0)
File without changes
File without changes
@@ -0,0 +1,54 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe SPCore::SignalGenerator do
4
+ before :all do
5
+ @sample_rate = 600.0
6
+ @test_freqs = [
7
+ 65.0, 100.0, 250.0
8
+ ]
9
+ end
10
+
11
+ context '.make_signal' do
12
+ context 'one freq at a time' do
13
+ it 'should produce the same output as a plain Oscillator' do
14
+ size = 60
15
+ generator = SignalGenerator.new :sample_rate => @sample_rate, :size => size
16
+
17
+ @test_freqs.each do |freq|
18
+ signal = generator.make_signal [freq]
19
+
20
+ osc = Oscillator.new(:sample_rate => @sample_rate, :frequency => freq)
21
+ osc_output = Array.new(size)
22
+ size.times do |i|
23
+ osc_output[i] = osc.sample
24
+ end
25
+
26
+ signal.data.should eq(osc_output)
27
+ end
28
+ end
29
+ end
30
+
31
+ context 'many freqs at a time' do
32
+ it 'should produce the same output as equivalent plain Oscillators' do
33
+ size = 60
34
+ generator = SignalGenerator.new :sample_rate => @sample_rate, :size => size
35
+
36
+ oscs = []
37
+ @test_freqs.each do |freq|
38
+ oscs.push Oscillator.new(:sample_rate => @sample_rate, :frequency => freq)
39
+ end
40
+
41
+ signal = generator.make_signal @test_freqs
42
+
43
+ osc_output = Array.new(size, 0.0)
44
+ size.times do |i|
45
+ oscs.each do |osc|
46
+ osc_output[i] += osc.sample
47
+ end
48
+ end
49
+
50
+ signal.data.should eq(osc_output)
51
+ end
52
+ end
53
+ end
54
+ end