digiproc 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +48 -0
- data/LICENSE.txt +21 -0
- data/README.md +78 -0
- data/Rakefile +37 -0
- data/TODO.md +50 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config/environment.rb +118 -0
- data/console_tests.rb +44 -0
- data/digiproc.gemspec +49 -0
- data/examples/analog_signals/analog_to_digital.rb +31 -0
- data/examples/analog_signals/companded-signals.png +0 -0
- data/examples/analog_signals/companding.rb +68 -0
- data/examples/analog_signals/fft-plot.png +0 -0
- data/examples/analog_signals/plot_Digiproc::FFT.png +0 -0
- data/examples/analog_signals/plot_Dsp::FFT.png +0 -0
- data/examples/analog_signals/quantization-outputs.png +0 -0
- data/examples/analog_signals/quantize_compand.rb +69 -0
- data/examples/binomial_distribution/bit_error.rb +14 -0
- data/examples/binomial_distribution/dice.rb +35 -0
- data/examples/digital_signals/_coded_frequency_signal,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/_coded_frequency_signal,_ts_=_2_s.png +0 -0
- data/examples/digital_signals/coded_power_spectral_density,__ts_=_1_s.png +0 -0
- data/examples/digital_signals/coded_power_spectral_density,__ts_=_2_s.png +0 -0
- data/examples/digital_signals/coded_time_signal,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/coded_time_signal,_ts_=_2_s.png +0 -0
- data/examples/digital_signals/freq_sig_from_eqn,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/freq_sig_from_eqn,_ts_=_2_s.png +0 -0
- data/examples/digital_signals/frequency_signal,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/frequency_signal,_ts_=_2_s.png +0 -0
- data/examples/digital_signals/modulate_square_pulses.rb +9 -0
- data/examples/digital_signals/modulated_sq._pulses.png +0 -0
- data/examples/digital_signals/modulated_sq._pulses_alt.png +0 -0
- data/examples/digital_signals/power_spectral_density,__ts_=_1_s.png +0 -0
- data/examples/digital_signals/power_spectral_density,__ts_=_2_s.png +0 -0
- data/examples/digital_signals/square_signals.rb +90 -0
- data/examples/digital_signals/time_signal,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/time_signal,_ts_=_2_s.png +0 -0
- data/examples/encoding/gray_code.rb +22 -0
- data/examples/encoding/psk.rb +91 -0
- data/examples/encoding/system_2_phase.png +0 -0
- data/examples/encoding/system_2_xmit_signal.png +0 -0
- data/examples/encoding/system_3_phase.png +0 -0
- data/examples/encoding/system_3_xmit_signal.png +0 -0
- data/examples/encoding/system_4_xmit_signal.png +0 -0
- data/examples/encoding/xor-dpsk-phase-signal-(sys1).png +0 -0
- data/examples/encoding/xor-dpsk-xmit-signal-(sys-1).png +0 -0
- data/examples/factories/Quickplot Graph.png +0 -0
- data/examples/factories/bandpass.rb +6 -0
- data/examples/fft/plot_Dsp::FFT.png +0 -0
- data/examples/fft/recieved_data_(time_domain).png +0 -0
- data/examples/fft/simple_fft_example.rb +47 -0
- data/examples/fft/unprocessed_fft.png +0 -0
- data/examples/filters/bandpass_filter.png +0 -0
- data/examples/filters/filter_a_signal.rb +38 -0
- data/examples/filters/white_noise_db_out_of_bp_filter.png +0 -0
- data/examples/filters/white_noise_mag_out_of_bp_filter.png +0 -0
- data/examples/filters/white_noise_spectra.png +0 -0
- data/examples/functions/compute_probability.rb +29 -0
- data/examples/functions/gram_schmidt.rb +10 -0
- data/examples/functions/minimize_energy.rb +29 -0
- data/examples/functions/orthoganalize.rb +18 -0
- data/examples/functions/simple_functions.rb +81 -0
- data/examples/linear_algebra/diverging_sys.rb +13 -0
- data/examples/linear_algebra/iterative_sys_of_eqns_methods.rb +27 -0
- data/examples/modulation_schemes/dpsk_2.png +0 -0
- data/examples/modulation_schemes/dpsk_256.png +0 -0
- data/examples/modulation_schemes/dpsk_freq_domain.rb +119 -0
- data/examples/modulation_schemes/psk.rb +36 -0
- data/examples/modulation_schemes/psk_2.png +0 -0
- data/examples/modulation_schemes/psk_256.png +0 -0
- data/examples/modulation_schemes/psksystem_1_xmit_signal.png +0 -0
- data/examples/modulation_schemes/psksystem_2_xmit_signal.png +0 -0
- data/examples/modulation_schemes/psksystem_3_xmit_signal.png +0 -0
- data/examples/modulation_schemes/system_1_xmit_signal.png +0 -0
- data/examples/modulation_schemes/system_2_xmit_signal.png +0 -0
- data/examples/modulation_schemes/system_3_xmit_signal.png +0 -0
- data/examples/quickplot/PlottableClass_plot.png +0 -0
- data/examples/quickplot/decorators.rb +13 -0
- data/examples/quickplot/direct_gruff.png +0 -0
- data/examples/quickplot/plot_PlottableClass.png +0 -0
- data/examples/quickplot/quickplot_vs_others.rb +85 -0
- data/examples/quickplot/random_data_quickplot,_dark.png +0 -0
- data/examples/quickplot/random_data_quickplot.png +0 -0
- data/examples/realized_gaussian/norm_dist_plot.png +0 -0
- data/examples/realized_gaussian/norm_dist_spectrum.png +0 -0
- data/examples/realized_gaussian/realized_gaussian_example.rb +23 -0
- data/lib/concerns/convolvable.rb +144 -0
- data/lib/concerns/data_properties.rb +223 -0
- data/lib/concerns/fourier_transformable.rb +178 -0
- data/lib/concerns/initializable.rb +43 -0
- data/lib/concerns/multipliable.rb +22 -0
- data/lib/concerns/os.rb +36 -0
- data/lib/concerns/plottable.rb +248 -0
- data/lib/concerns/requires_data.rb +8 -0
- data/lib/digiproc/version.rb +8 -0
- data/lib/digiproc.rb +2 -0
- data/lib/extensions/array_extension.rb +23 -0
- data/lib/extensions/core_extensions.rb +117 -0
- data/lib/factories/factories.rb +3 -0
- data/lib/factories/filter_factory.rb +83 -0
- data/lib/factories/window_factory.rb +22 -0
- data/lib/fft.rb +255 -0
- data/lib/filters/bandpass_filter.rb +43 -0
- data/lib/filters/bandstop_filter.rb +44 -0
- data/lib/filters/digital_filter.rb +59 -0
- data/lib/filters/highpass_filter.rb +27 -0
- data/lib/filters/lowpass_filter.rb +27 -0
- data/lib/functions.rb +221 -0
- data/lib/probability/binomial_distribution.rb +84 -0
- data/lib/probability/bit_generator.rb +94 -0
- data/lib/probability/gaussian_distribution.rb +29 -0
- data/lib/probability/probability.rb +234 -0
- data/lib/probability/theoretical_gaussian_distribution.rb +59 -0
- data/lib/quick_plot.rb +96 -0
- data/lib/rbplot.rb +219 -0
- data/lib/signals/analog_signal.rb +143 -0
- data/lib/signals/digital_signal.rb +181 -0
- data/lib/strategies/code/differential_encoding_strategy.rb +69 -0
- data/lib/strategies/code/gray_code.rb +75 -0
- data/lib/strategies/code/xor_differential_encoding_strategy.rb +100 -0
- data/lib/strategies/code/xor_differential_encoding_zero_angle_strategy.rb +103 -0
- data/lib/strategies/companding/custom_companding_strategy.rb +29 -0
- data/lib/strategies/convolution/bf_conv.rb +57 -0
- data/lib/strategies/fft/brute_force_dft_strategy.rb +31 -0
- data/lib/strategies/fft/inverse_fft_conjugate_strategy.rb +44 -0
- data/lib/strategies/fft/radix2_strategy.rb +84 -0
- data/lib/strategies/gaussian/gaussian_generator.rb +49 -0
- data/lib/strategies/linear_algebra/gauss_seidel_strategy.rb +90 -0
- data/lib/strategies/linear_algebra/jacobi_strategy.rb +81 -0
- data/lib/strategies/linear_algebra/sor2_strategy.rb +98 -0
- data/lib/strategies/linear_algebra/sor_strategy.rb +108 -0
- data/lib/strategies/modulation/phase_shift_keying_strategy.rb +96 -0
- data/lib/strategies/orthogonalize/gram_schmidt.rb +50 -0
- data/lib/strategies/strategies.rb +3 -0
- data/lib/strategies/window/blackman_window.rb +32 -0
- data/lib/strategies/window/hamming_window.rb +31 -0
- data/lib/strategies/window/hanning_window.rb +31 -0
- data/lib/strategies/window/kaiser_window.rb +27 -0
- data/lib/strategies/window/rectangular_window.rb +22 -0
- data/lib/strategies/window/window.rb +42 -0
- data/lib/systems/custom_system.rb +13 -0
- data/lib/systems/hilbert_transform.rb +6 -0
- data/lib/systems/matched_filter.rb +21 -0
- data/lib/systems/raised_cosine_filter.rb +11 -0
- data/lib/systems/system.rb +19 -0
- data/lib/systems/systems.rb +3 -0
- data/playground.rb +323 -0
- data/plots/_coded_frequency_signal,_ts_=_1_s.png +0 -0
- data/plots/_coded_frequency_signal,_ts_=_2_s.png +0 -0
- data/plots/coded_freq_sig_from_eqn,_ts_=_1_s.png +0 -0
- data/plots/coded_freq_sig_from_eqn,_ts_=_2_s.png +0 -0
- data/plots/coded_power_spectral_density,__ts_=_1_s.png +0 -0
- data/plots/coded_power_spectral_density,__ts_=_2_s.png +0 -0
- data/plots/coded_time_signal,_ts_=_1_s.png +0 -0
- data/plots/coded_time_signal,_ts_=_2_s.png +0 -0
- data/plots/dpsk_2.png +0 -0
- data/plots/freq_sig_from_eqn,_ts_=_1_s.png +0 -0
- data/plots/freq_sig_from_eqn,_ts_=_2_s.png +0 -0
- data/plots/frequency_signal,_ts_=_1_s.png +0 -0
- data/plots/frequency_signal,_ts_=_2_s.png +0 -0
- data/plots/power_spectral_density,__ts_=_1_s.png +0 -0
- data/plots/power_spectral_density,__ts_=_2_s.png +0 -0
- data/plots/psk_2.png +0 -0
- data/plots/time_signal,_ts_=_1_s.png +0 -0
- data/plots/time_signal,_ts_=_2_s.png +0 -0
- data/test-title-dark.png +0 -0
- data/test-title.png +0 -0
- metadata +322 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Module which can perform basic operations on 1D data arrays
|
|
3
|
+
# This module extends itself so all methods can be called via:
|
|
4
|
+
## Digiproc::DataProperties.method_i_want_to_call
|
|
5
|
+
|
|
6
|
+
module Digiproc::DataProperties
|
|
7
|
+
|
|
8
|
+
extend self
|
|
9
|
+
##
|
|
10
|
+
## all_maxima(data [Array]) => returns Array(OpenStruct<#index, #value>)
|
|
11
|
+
# returns all maximum in an array of `OpenStruct`s with #index and #value, ie:
|
|
12
|
+
## all_maxima([1,2,3,4,5,4,3,2,6,7,8,7,6,5]) # [#<OpenStruct index=4, value=5>, #<OpenStruct index=10, value=8>]
|
|
13
|
+
def all_maxima(data)
|
|
14
|
+
raise ArgumentError.new("Data must be an array") if not data.is_a? Array
|
|
15
|
+
slope = Slope::Positive
|
|
16
|
+
max = []
|
|
17
|
+
data.each_with_index do |n, i|
|
|
18
|
+
old_slope = slope
|
|
19
|
+
if i <= data.length - 2
|
|
20
|
+
new_slope = find_slope(data[i], data[i+1])
|
|
21
|
+
slope = new_slope.is?(:zero) ? old_slope : new_slope
|
|
22
|
+
max << OpenStruct.new(index: i, value: n) if old_slope.is? :positive and slope.is? :negative
|
|
23
|
+
else
|
|
24
|
+
max << OpenStruct.new(index: i, value: n) if slope.is? :positive
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
max
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# maxima(data [Array], num = 1 [Integer]) returns `num` number of largest maxima from the data array returned in #all_maxima
|
|
32
|
+
## arr = [1,2,3,4,5,4,3,2,6,7,8,7,6,5]
|
|
33
|
+
## Digiproc::DataProperties.maxima(arr, 1) # => [#<OpenStruct index=10, value=8>]
|
|
34
|
+
def maxima(data, num = 1)
|
|
35
|
+
all_maxima(data).sort{ |a, b| b.value <=> a.value }.take num
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# local_maxima(data [Array], num=1 [Integer]) => returns Array(OpenStruct(#index, #value))
|
|
40
|
+
# calculates all maxima and orders them by how much proportionally they
|
|
41
|
+
# are to any directly adjacent maxima. It then takes `num` answer and returns an array of `OpenStruct`s with #index and #value
|
|
42
|
+
# This is particularly useful to use when looking for local maxima in a FFT dB or magnitude plot.
|
|
43
|
+
## arr = [50,45,40,30,35,30,29,28,29,20,15,10,19,9,8,7,9,8,7,6,5,9,6,5,4,9,6,3,2,7,5,4,3,2,1,9,1,2,3,4]
|
|
44
|
+
## Digiproc::DataProperties.local_maxima(arr, 3) #=> [#<OpenStruct index=35, value=9>, #<OpenStruct index=0, value=50>, #<OpenStruct index=12, value=19>]
|
|
45
|
+
def local_maxima(data, num=1)
|
|
46
|
+
all_maxima = all_maxima(data)
|
|
47
|
+
all_maxima.sort do |a, b|
|
|
48
|
+
a_upper = all_maxima.find{ |maxima| maxima.index > a.index }
|
|
49
|
+
a_lower = all_maxima.reverse.find{ |maxima| maxima.index < a.index }
|
|
50
|
+
a_adjacent = 0
|
|
51
|
+
if a_upper && a_lower
|
|
52
|
+
a_adjacent = ((a.value.to_f / a_upper.value) + (a.value.to_f / a_lower.value)) / 2.0
|
|
53
|
+
else
|
|
54
|
+
a_adjacent = !!a_upper ? (a.value.to_f / a_upper.value) : (a.value.to_f / a_lower.value)
|
|
55
|
+
end
|
|
56
|
+
b_upper = all_maxima.find{ |maxima| maxima.index > b.index }
|
|
57
|
+
b_lower = all_maxima.reverse.find{ |maxima| maxima.index < b.index }
|
|
58
|
+
b_adjacent = 0
|
|
59
|
+
if b_upper && b_lower
|
|
60
|
+
b_adjacent = ((b.value.to_f / b_upper.value) + (b.value.to_f / b_lower.value)) / 2.0
|
|
61
|
+
else
|
|
62
|
+
b_adjacent = !!b_upper ? (b.value.to_f / b_upper.value) : (b.value.to_f / b_lower.value)
|
|
63
|
+
end
|
|
64
|
+
b_adjacent <=> a_adjacent
|
|
65
|
+
end
|
|
66
|
+
.take(num)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# slope(a: Numeric, b: Numeric, range=1: Numeric ) => float
|
|
71
|
+
# returns the slope of these two y values, given a change in x values by `range` which defualts to 1. Returns a float
|
|
72
|
+
## Digiproc::DataProperties.slope(0.5, 1.2, 0.5) #=> 1.4
|
|
73
|
+
def slope(a,b, range=1)
|
|
74
|
+
return (b - a) / range.to_f
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# find_slope(a: Int or Double, b: Int or Double) returns Slope::Positive, Slope::Negative, or Slope::Zero
|
|
79
|
+
# Note: Slope::Positive.is? :positive returns true, Slope::Negative.is? :negative returns true, etc.
|
|
80
|
+
def find_slope(a,b)
|
|
81
|
+
slope = nil
|
|
82
|
+
if b - a > 0
|
|
83
|
+
slope = Slope::Positive
|
|
84
|
+
elsif b - a < 0
|
|
85
|
+
slope = Slope::Negative
|
|
86
|
+
else
|
|
87
|
+
slope = Slope::Zero
|
|
88
|
+
end
|
|
89
|
+
slope
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
## Inner slope class made for pleasant syntax in the maxima methods
|
|
94
|
+
class Slope
|
|
95
|
+
|
|
96
|
+
##
|
|
97
|
+
# returns inner class Negative
|
|
98
|
+
def self.Negative
|
|
99
|
+
Negative
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
##
|
|
103
|
+
# returns inner class Positive
|
|
104
|
+
def self.Positive
|
|
105
|
+
Positive
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
##
|
|
109
|
+
# returns inner class Zero
|
|
110
|
+
def self.Zero
|
|
111
|
+
Zero
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
##
|
|
115
|
+
## Used in the Slope class to indicate a negative slope.
|
|
116
|
+
# Made for easy syntax purposes
|
|
117
|
+
class Negative
|
|
118
|
+
|
|
119
|
+
##
|
|
120
|
+
# Used for comparison in ==
|
|
121
|
+
# returns :negative
|
|
122
|
+
def self.type
|
|
123
|
+
:negative
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
##
|
|
127
|
+
# Can test equality in multiple ways:
|
|
128
|
+
## Digiproc::DataProperties::Slope::Negative == OpenStruct.new(type: :negative) # true
|
|
129
|
+
## Digiproc::DataProperties::Slope::Negative == :negative # true
|
|
130
|
+
## Digiproc::DataProperties::Slope::Negative == "negative" # true
|
|
131
|
+
def self.==(val)
|
|
132
|
+
if val.respond_to? :type
|
|
133
|
+
return true if val.type == :negative
|
|
134
|
+
end
|
|
135
|
+
return true if val == :negative
|
|
136
|
+
if val.respond_to? :downcase
|
|
137
|
+
return true if val.downcase == "negative"
|
|
138
|
+
end
|
|
139
|
+
false
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
##
|
|
143
|
+
# Alias to ==
|
|
144
|
+
# Can test equality in multiple ways:
|
|
145
|
+
## Digiproc::DataProperties::Slope::Negative.is? OpenStruct.new(type: :negative) # true
|
|
146
|
+
## Digiproc::DataProperties::Slope::Negative.is? :negative # true
|
|
147
|
+
## Digiproc::DataProperties::Slope::Negative.is? "negative" # true, not case sensitive
|
|
148
|
+
def self.is?(val)
|
|
149
|
+
self.==(val)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
##
|
|
154
|
+
## Used in the Slope class to indicate a positive slope.
|
|
155
|
+
# Made for easy syntax purposes
|
|
156
|
+
class Positive
|
|
157
|
+
def self.type
|
|
158
|
+
:positive
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
##
|
|
162
|
+
# Can test equality in multiple ways:
|
|
163
|
+
## Digiproc::DataProperties::Slope::Positive == OpenStruct.new(type: :positive) # true
|
|
164
|
+
## Digiproc::DataProperties::Slope::Positive == :positive # true
|
|
165
|
+
## Digiproc::DataProperties::Slope::Negative == "positive" # true, not case-sensitive
|
|
166
|
+
def self.==(val)
|
|
167
|
+
if val.respond_to? :type
|
|
168
|
+
return true if val.type == :positive
|
|
169
|
+
end
|
|
170
|
+
return true if val == :positive
|
|
171
|
+
if val.respond_to? :downcase
|
|
172
|
+
return true if val.downcase == "positive"
|
|
173
|
+
end
|
|
174
|
+
false
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
##
|
|
178
|
+
# Alias to ==
|
|
179
|
+
# Can test equality in multiple ways:
|
|
180
|
+
## Digiproc::DataProperties::Slope::Piositive.is? OpenStruct.new(type: :negative) # true
|
|
181
|
+
## Digiproc::DataProperties::Slope::Positive.is? :positive # true
|
|
182
|
+
## Digiproc::DataProperties::Slope::Positive.is? "positive" # true, not case-sensitive
|
|
183
|
+
def self.is?(val)
|
|
184
|
+
self.==(val)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
##
|
|
189
|
+
## Used in the Slope class to indicate a zero slope.
|
|
190
|
+
# Made for easy syntax purposes
|
|
191
|
+
class Zero
|
|
192
|
+
def self.type
|
|
193
|
+
:zero
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
##
|
|
197
|
+
# Can test equality in multiple ways:
|
|
198
|
+
## Digiproc::DataProperties::Slope::Zero == OpenStruct.new(type: :zero) # true
|
|
199
|
+
## Digiproc::DataProperties::Slope::Zero == :zero # true
|
|
200
|
+
## Digiproc::DataProperties::Slope::Zero == "zero" # true, not case sensitive
|
|
201
|
+
def self.==(val)
|
|
202
|
+
if val.respond_to? :type
|
|
203
|
+
return true if val.type == :zero
|
|
204
|
+
end
|
|
205
|
+
return true if val == :zero
|
|
206
|
+
if val.respond_to? :downcase
|
|
207
|
+
return true if val.downcase == "zero"
|
|
208
|
+
end
|
|
209
|
+
false
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
##
|
|
213
|
+
# Alias to ==
|
|
214
|
+
# Can test equality in multiple ways:
|
|
215
|
+
## Digiproc::DataProperties::Slope::Zero.is? OpenStruct.new(type: :zero) # true
|
|
216
|
+
## Digiproc::DataProperties::Slope::Zero.is? :zero # true
|
|
217
|
+
## Digiproc::DataProperties::Slope::Zero.is? "zero" # true, not case sensitive
|
|
218
|
+
def self.is?(val)
|
|
219
|
+
self.==(val)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Module for Classes which have a property `data` which we can take the Discrete Fourier Transform of.
|
|
3
|
+
# A class which wants to use methods outside of GenricMethods will also include Digiproc::Initializable, and you have to use the appropriate methods in your class constructor if you want the Digiproc::FFT class to be set up automatically
|
|
4
|
+
# You could manually set it up if your class sets up its own @fft property which is an instance of Digiproc::FFT with the class' `data` passed in.
|
|
5
|
+
# See an example of this in action in Digiproc::DigitalSignal initializer method.
|
|
6
|
+
module Digiproc::FourierTransformable
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Inner module for places where standalone functions are needed, not associated with a class which contains `data`. See Digiproc::Functions for use
|
|
10
|
+
module GenericMethods
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# Return a Fast Fourier Transform (FFT) strategy to be used for GenericMethods. Set to Digiproc::Strategies::Radix2Strategy
|
|
14
|
+
# It is important to note that the Radix2Strategy increases the size of the FFT return to the closest power of 2.
|
|
15
|
+
def fft_strategy
|
|
16
|
+
Digiproc::Strategies::Radix2Strategy
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Return an Inverse Fast Fourier Transform (IFFT) strategy to be used for GenericMethods. Set to Digiproc::Strategies::IFFTConjugateStrategy
|
|
20
|
+
def ifft_strategy
|
|
21
|
+
Digiproc::Strategies::IFFTConjugateStrategy
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
## fft(data [Array of complex Numerics]) => returns Array of data corresponding to the FFT
|
|
26
|
+
# Note that for the Radix2Strategy, the only time the return size will equal the input size is if the input size is a power of 2.
|
|
27
|
+
# Otherwise the return will be increased to the closest power of 2.
|
|
28
|
+
## Digiproc::Functions.fft([1,2,3,4,5,6,7,8]) # => [
|
|
29
|
+
## # 36,
|
|
30
|
+
## # (-4.0+9.65685424949238i),
|
|
31
|
+
## # (-4.000000000000001+4.0i),
|
|
32
|
+
## # (-4.000000000000002+1.6568542494923797i),
|
|
33
|
+
## # -4,
|
|
34
|
+
## # (-3.9999999999999996-1.6568542494923797i),
|
|
35
|
+
## # (-3.999999999999999-4.0i),
|
|
36
|
+
## # (-3.999999999999998-9.65685424949238i)]
|
|
37
|
+
|
|
38
|
+
def fft(data)
|
|
39
|
+
fft_strategy.new(data.dup).calculate
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
## ifft(data [Array of complex Numerics])
|
|
44
|
+
# Due to using the Radix2Strategy, the ifft will return the exact input if the input is a power of 2.
|
|
45
|
+
# Otherwise, there will be trailing 0s. ie:
|
|
46
|
+
## ft = Digiproc::Functions.fft([1,2,3,4,5,6,7,8])
|
|
47
|
+
## Digiproc::Functions.ifft(ft) # => [(1.0-0.0i),
|
|
48
|
+
## # (2.0000000000000004-2.718345674301793e-16i),
|
|
49
|
+
## # (3.0+4.440892098500626e-16i),
|
|
50
|
+
## # (4.0+3.8285686989269494e-16i),
|
|
51
|
+
## # (5.0-0.0i),
|
|
52
|
+
## # (6.0-4.978996250514798e-17i),
|
|
53
|
+
## # (7.0-4.440892098500626e-16i),
|
|
54
|
+
## # (8.0-6.123233995736767e-17i)]
|
|
55
|
+
## ft = Digiproc::Functions.fft([1,2,3,4,5])
|
|
56
|
+
## Digiproc::Functions.ifft(ft) # => [(1.0-0.0i),
|
|
57
|
+
## # (2.0+3.0616169978683836e-17i),
|
|
58
|
+
## # (3.0-3.3306690738754696e-16i),
|
|
59
|
+
## # (4.0+8.040613248383182e-17i),
|
|
60
|
+
## # (5.0-0.0i),
|
|
61
|
+
## # (0.0-1.9142843494634747e-16i),
|
|
62
|
+
## # (0.0+3.3306690738754696e-16i),
|
|
63
|
+
## # (0.0+8.040613248383182e-17i)]
|
|
64
|
+
def ifft(data)
|
|
65
|
+
ifft_strategy.new(data.dup).calculate
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# Include Digiproc::RequiresData when an instance is instantiated that includes Digiproc::FourierTransformable because
|
|
72
|
+
# methods require `data` (an array of Fourier Transformable data) to exist in the class
|
|
73
|
+
def self.included(base)
|
|
74
|
+
base.class_eval do
|
|
75
|
+
include Digiproc::RequiresData, Digiproc::Initializable
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
attr_writer :fft
|
|
80
|
+
attr_reader :fft_strategy
|
|
81
|
+
|
|
82
|
+
##
|
|
83
|
+
# Initialize with (time_data: [Array(Numeric)], fft_strategy: [optional, defaults to Digiproc::Strategies::Radix2Strategy])
|
|
84
|
+
# Upon instantiation, a new Digiproc::FFT class is made with the data passed in as time_data.
|
|
85
|
+
# NOTE this does not happen automatically upon instantiation of the class which includes this module.
|
|
86
|
+
# The class including this module will also include Digiproc::Initializable, so you can initialize this module in the class' #initialize method as follows:
|
|
87
|
+
## class TestClass
|
|
88
|
+
## include Digiproc::FourierTransformable
|
|
89
|
+
##
|
|
90
|
+
## attr_accessor :data
|
|
91
|
+
##
|
|
92
|
+
## def initialize(data: )
|
|
93
|
+
## @data = data
|
|
94
|
+
## initialize_modules(Digiproc::FourierTransformable => {time_data: data})
|
|
95
|
+
## end
|
|
96
|
+
## end
|
|
97
|
+
##
|
|
98
|
+
# Note that the calculation of the FFT itself is lazy and will not be calculated unless there is an
|
|
99
|
+
# attempt to access it or #calculate is called on @fft
|
|
100
|
+
def initialize(time_data: , fft_strategy: Digiproc::Strategies::Radix2Strategy)
|
|
101
|
+
@fft_strategy = fft_strategy
|
|
102
|
+
@fft = Digiproc::FFT.new(time_data: time_data.dup, strategy: fft_strategy)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
##
|
|
106
|
+
## fft_db #=> Array of decible values of the magnitude of the FFT (Float, not complex)
|
|
107
|
+
# Ensures the fft is calculated, and then returns the dB vals
|
|
108
|
+
def fft_db
|
|
109
|
+
setup_fft
|
|
110
|
+
@fft.fft.db
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
## fft_db #=> Array of values of the magnitude of the FFT (numeric, not complex)
|
|
114
|
+
# Ensures the fft is calculated, and then returns the magnitude
|
|
115
|
+
def fft_magnitude
|
|
116
|
+
setup_fft
|
|
117
|
+
@fft.magnitude
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
##
|
|
121
|
+
## fft(size [optional Integer]) #=> returns Digiproc::FFT instance
|
|
122
|
+
# Will calculate the FFT if it has not yet been calculated
|
|
123
|
+
# size is an optional parameter which allows you to delegate the size of the FFT to be calculated..
|
|
124
|
+
# This is useful if you want a particular resolution of the frequency domain, or if you are trying to match
|
|
125
|
+
# FFT sizes for calculation purposes.
|
|
126
|
+
def fft(size = @fft.data.size)
|
|
127
|
+
if @fft.data.size != size
|
|
128
|
+
@fft.calculate_at_size(size)
|
|
129
|
+
end
|
|
130
|
+
@fft
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
##
|
|
134
|
+
# setter for the FFT strategy
|
|
135
|
+
def fft_strategy=(strategy)
|
|
136
|
+
@fft.strategy = strategy
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
##
|
|
140
|
+
## fft_data # => Array of FFT vals (complex Numeric)
|
|
141
|
+
# Calculates the fft if not yet calculated, and returns the calculation
|
|
142
|
+
def fft_data
|
|
143
|
+
setup_fft
|
|
144
|
+
@fft.fft
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
##
|
|
148
|
+
## fft_data # => Array of angle vals (radians)
|
|
149
|
+
# Calculate the fft if not yet calculated, and returns Arra of the angle data in radians
|
|
150
|
+
def fft_angle
|
|
151
|
+
setup_fft
|
|
152
|
+
@fft.angle
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
##
|
|
156
|
+
## fft_real # => Array of real vals
|
|
157
|
+
# Calculate the fft if not yet calculated, and retun an Array of the real values
|
|
158
|
+
def fft_real
|
|
159
|
+
setup_fft
|
|
160
|
+
@fft.real
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
##
|
|
164
|
+
## fft_imaginary # => Array of imaginary vals
|
|
165
|
+
# Calculate the fft if not yet calculated, and retun an Array of the imaginary values
|
|
166
|
+
def fft_imaginary
|
|
167
|
+
setup_fft
|
|
168
|
+
@fft.fft.imaginary
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
##
|
|
173
|
+
# Private method which calculates the fft
|
|
174
|
+
def setup_fft
|
|
175
|
+
self.fft.calculate if self.fft.fft.nil?
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
##
|
|
3
|
+
# A module for classes which have other modules which can accept parameters from the class.
|
|
4
|
+
# For example when included in a class, `Digiproc::FourierTransformable` automatically also includes
|
|
5
|
+
# 'Digiproc::Initializable' because `Digiproc::FourierTransformable` needs time data (and an optional Strategy) to
|
|
6
|
+
# generate the FFT for the data in the class. While another appropriate pattern could be to make the `Digiproc::FFT` generation
|
|
7
|
+
# lazy, and just pull the `time_data` from the class' `data` property when any fft property is first queried, this pattern
|
|
8
|
+
# allows more flexibility by allowing more customizable setup during instantiation
|
|
9
|
+
module Digiproc::Initializable
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# Adds a `initialize_modules` method to the including class which allows you to call `initialize_modules(*modules)`
|
|
13
|
+
# where *modules can be Modules or a hash of Module => {params: params_values} for module initialization.
|
|
14
|
+
# Modules which support initialization have an `initialize` method that can be called in this way
|
|
15
|
+
# See `Digiproc::DigitalSignal` for an example
|
|
16
|
+
## initialize_modules(Module => { module_initializer_accepts_key: value, another_key: value2})
|
|
17
|
+
def self.included(base)
|
|
18
|
+
base.class_eval do
|
|
19
|
+
def initialize_modules(*modules)
|
|
20
|
+
modules.each do |m|
|
|
21
|
+
if verify_params(m) == :valid_hash
|
|
22
|
+
m.keys.first.instance_method(:initialize).bind(self).call(m.values.first)
|
|
23
|
+
elsif verify_params(m) == :valid_module
|
|
24
|
+
m.instance_method(:initialize).bind(self).call
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def verify_params(item)
|
|
32
|
+
return :valid_module if item.is_a? Module
|
|
33
|
+
if item.is_a? Hash
|
|
34
|
+
return :valid_hash if item.keys.length == 1 and item.keys.first.is_a? Module
|
|
35
|
+
end
|
|
36
|
+
raise ArgumentError.new("Each argument must either be a module or a hash of type Module => args")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Including Class instance must have a data property which is an array. Allows you to say
|
|
3
|
+
# classInstance1 * classInstance2 and the two data vectors will be multiplied on an element-by-element basis
|
|
4
|
+
# Note: the data vectors must be the same length
|
|
5
|
+
|
|
6
|
+
module Digiproc::Multipliable
|
|
7
|
+
|
|
8
|
+
def self.included(base)
|
|
9
|
+
base.class_eval do
|
|
10
|
+
include RequiresData
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# Multiplies the instance's `data` property on an element-by-element basis with the other instance's data property
|
|
17
|
+
def * (obj)
|
|
18
|
+
raise ArgumentError.new("Object must have #data reader") if not obj.respond_to?(:data)
|
|
19
|
+
raise ArgumentError.new("Object data must respond to #times, #{obj.data.class} does not") if not obj.data.respond_to?(:times)
|
|
20
|
+
self.data.times obj.data
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/concerns/os.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Deternine the OS for classes which need to perform system commands
|
|
3
|
+
module Digiproc::OS
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
##
|
|
7
|
+
# return true if in a windows env
|
|
8
|
+
def windows?
|
|
9
|
+
(/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# return true if in a mac env
|
|
14
|
+
def mac?
|
|
15
|
+
(/darwin/ =~ RUBY_PLATFORM) != nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# return true if in a unix env
|
|
20
|
+
def unix?
|
|
21
|
+
!windows?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
# return true if in a linux env
|
|
26
|
+
def linux?
|
|
27
|
+
unix? and not mac?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# return true if using jruby
|
|
32
|
+
def ruby?
|
|
33
|
+
RUBY_ENGINE == 'jruby'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|