digiproc 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|