digiproc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (175) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +8 -0
  7. data/Gemfile.lock +48 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +78 -0
  10. data/Rakefile +37 -0
  11. data/TODO.md +50 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/config/environment.rb +118 -0
  15. data/console_tests.rb +44 -0
  16. data/digiproc.gemspec +49 -0
  17. data/examples/analog_signals/analog_to_digital.rb +31 -0
  18. data/examples/analog_signals/companded-signals.png +0 -0
  19. data/examples/analog_signals/companding.rb +68 -0
  20. data/examples/analog_signals/fft-plot.png +0 -0
  21. data/examples/analog_signals/plot_Digiproc::FFT.png +0 -0
  22. data/examples/analog_signals/plot_Dsp::FFT.png +0 -0
  23. data/examples/analog_signals/quantization-outputs.png +0 -0
  24. data/examples/analog_signals/quantize_compand.rb +69 -0
  25. data/examples/binomial_distribution/bit_error.rb +14 -0
  26. data/examples/binomial_distribution/dice.rb +35 -0
  27. data/examples/digital_signals/_coded_frequency_signal,_ts_=_1_s.png +0 -0
  28. data/examples/digital_signals/_coded_frequency_signal,_ts_=_2_s.png +0 -0
  29. data/examples/digital_signals/coded_power_spectral_density,__ts_=_1_s.png +0 -0
  30. data/examples/digital_signals/coded_power_spectral_density,__ts_=_2_s.png +0 -0
  31. data/examples/digital_signals/coded_time_signal,_ts_=_1_s.png +0 -0
  32. data/examples/digital_signals/coded_time_signal,_ts_=_2_s.png +0 -0
  33. data/examples/digital_signals/freq_sig_from_eqn,_ts_=_1_s.png +0 -0
  34. data/examples/digital_signals/freq_sig_from_eqn,_ts_=_2_s.png +0 -0
  35. data/examples/digital_signals/frequency_signal,_ts_=_1_s.png +0 -0
  36. data/examples/digital_signals/frequency_signal,_ts_=_2_s.png +0 -0
  37. data/examples/digital_signals/modulate_square_pulses.rb +9 -0
  38. data/examples/digital_signals/modulated_sq._pulses.png +0 -0
  39. data/examples/digital_signals/modulated_sq._pulses_alt.png +0 -0
  40. data/examples/digital_signals/power_spectral_density,__ts_=_1_s.png +0 -0
  41. data/examples/digital_signals/power_spectral_density,__ts_=_2_s.png +0 -0
  42. data/examples/digital_signals/square_signals.rb +90 -0
  43. data/examples/digital_signals/time_signal,_ts_=_1_s.png +0 -0
  44. data/examples/digital_signals/time_signal,_ts_=_2_s.png +0 -0
  45. data/examples/encoding/gray_code.rb +22 -0
  46. data/examples/encoding/psk.rb +91 -0
  47. data/examples/encoding/system_2_phase.png +0 -0
  48. data/examples/encoding/system_2_xmit_signal.png +0 -0
  49. data/examples/encoding/system_3_phase.png +0 -0
  50. data/examples/encoding/system_3_xmit_signal.png +0 -0
  51. data/examples/encoding/system_4_xmit_signal.png +0 -0
  52. data/examples/encoding/xor-dpsk-phase-signal-(sys1).png +0 -0
  53. data/examples/encoding/xor-dpsk-xmit-signal-(sys-1).png +0 -0
  54. data/examples/factories/Quickplot Graph.png +0 -0
  55. data/examples/factories/bandpass.rb +6 -0
  56. data/examples/fft/plot_Dsp::FFT.png +0 -0
  57. data/examples/fft/recieved_data_(time_domain).png +0 -0
  58. data/examples/fft/simple_fft_example.rb +47 -0
  59. data/examples/fft/unprocessed_fft.png +0 -0
  60. data/examples/filters/bandpass_filter.png +0 -0
  61. data/examples/filters/filter_a_signal.rb +38 -0
  62. data/examples/filters/white_noise_db_out_of_bp_filter.png +0 -0
  63. data/examples/filters/white_noise_mag_out_of_bp_filter.png +0 -0
  64. data/examples/filters/white_noise_spectra.png +0 -0
  65. data/examples/functions/compute_probability.rb +29 -0
  66. data/examples/functions/gram_schmidt.rb +10 -0
  67. data/examples/functions/minimize_energy.rb +29 -0
  68. data/examples/functions/orthoganalize.rb +18 -0
  69. data/examples/functions/simple_functions.rb +81 -0
  70. data/examples/linear_algebra/diverging_sys.rb +13 -0
  71. data/examples/linear_algebra/iterative_sys_of_eqns_methods.rb +27 -0
  72. data/examples/modulation_schemes/dpsk_2.png +0 -0
  73. data/examples/modulation_schemes/dpsk_256.png +0 -0
  74. data/examples/modulation_schemes/dpsk_freq_domain.rb +119 -0
  75. data/examples/modulation_schemes/psk.rb +36 -0
  76. data/examples/modulation_schemes/psk_2.png +0 -0
  77. data/examples/modulation_schemes/psk_256.png +0 -0
  78. data/examples/modulation_schemes/psksystem_1_xmit_signal.png +0 -0
  79. data/examples/modulation_schemes/psksystem_2_xmit_signal.png +0 -0
  80. data/examples/modulation_schemes/psksystem_3_xmit_signal.png +0 -0
  81. data/examples/modulation_schemes/system_1_xmit_signal.png +0 -0
  82. data/examples/modulation_schemes/system_2_xmit_signal.png +0 -0
  83. data/examples/modulation_schemes/system_3_xmit_signal.png +0 -0
  84. data/examples/quickplot/PlottableClass_plot.png +0 -0
  85. data/examples/quickplot/decorators.rb +13 -0
  86. data/examples/quickplot/direct_gruff.png +0 -0
  87. data/examples/quickplot/plot_PlottableClass.png +0 -0
  88. data/examples/quickplot/quickplot_vs_others.rb +85 -0
  89. data/examples/quickplot/random_data_quickplot,_dark.png +0 -0
  90. data/examples/quickplot/random_data_quickplot.png +0 -0
  91. data/examples/realized_gaussian/norm_dist_plot.png +0 -0
  92. data/examples/realized_gaussian/norm_dist_spectrum.png +0 -0
  93. data/examples/realized_gaussian/realized_gaussian_example.rb +23 -0
  94. data/lib/concerns/convolvable.rb +144 -0
  95. data/lib/concerns/data_properties.rb +223 -0
  96. data/lib/concerns/fourier_transformable.rb +178 -0
  97. data/lib/concerns/initializable.rb +43 -0
  98. data/lib/concerns/multipliable.rb +22 -0
  99. data/lib/concerns/os.rb +36 -0
  100. data/lib/concerns/plottable.rb +248 -0
  101. data/lib/concerns/requires_data.rb +8 -0
  102. data/lib/digiproc/version.rb +8 -0
  103. data/lib/digiproc.rb +2 -0
  104. data/lib/extensions/array_extension.rb +23 -0
  105. data/lib/extensions/core_extensions.rb +117 -0
  106. data/lib/factories/factories.rb +3 -0
  107. data/lib/factories/filter_factory.rb +83 -0
  108. data/lib/factories/window_factory.rb +22 -0
  109. data/lib/fft.rb +255 -0
  110. data/lib/filters/bandpass_filter.rb +43 -0
  111. data/lib/filters/bandstop_filter.rb +44 -0
  112. data/lib/filters/digital_filter.rb +59 -0
  113. data/lib/filters/highpass_filter.rb +27 -0
  114. data/lib/filters/lowpass_filter.rb +27 -0
  115. data/lib/functions.rb +221 -0
  116. data/lib/probability/binomial_distribution.rb +84 -0
  117. data/lib/probability/bit_generator.rb +94 -0
  118. data/lib/probability/gaussian_distribution.rb +29 -0
  119. data/lib/probability/probability.rb +234 -0
  120. data/lib/probability/theoretical_gaussian_distribution.rb +59 -0
  121. data/lib/quick_plot.rb +96 -0
  122. data/lib/rbplot.rb +219 -0
  123. data/lib/signals/analog_signal.rb +143 -0
  124. data/lib/signals/digital_signal.rb +181 -0
  125. data/lib/strategies/code/differential_encoding_strategy.rb +69 -0
  126. data/lib/strategies/code/gray_code.rb +75 -0
  127. data/lib/strategies/code/xor_differential_encoding_strategy.rb +100 -0
  128. data/lib/strategies/code/xor_differential_encoding_zero_angle_strategy.rb +103 -0
  129. data/lib/strategies/companding/custom_companding_strategy.rb +29 -0
  130. data/lib/strategies/convolution/bf_conv.rb +57 -0
  131. data/lib/strategies/fft/brute_force_dft_strategy.rb +31 -0
  132. data/lib/strategies/fft/inverse_fft_conjugate_strategy.rb +44 -0
  133. data/lib/strategies/fft/radix2_strategy.rb +84 -0
  134. data/lib/strategies/gaussian/gaussian_generator.rb +49 -0
  135. data/lib/strategies/linear_algebra/gauss_seidel_strategy.rb +90 -0
  136. data/lib/strategies/linear_algebra/jacobi_strategy.rb +81 -0
  137. data/lib/strategies/linear_algebra/sor2_strategy.rb +98 -0
  138. data/lib/strategies/linear_algebra/sor_strategy.rb +108 -0
  139. data/lib/strategies/modulation/phase_shift_keying_strategy.rb +96 -0
  140. data/lib/strategies/orthogonalize/gram_schmidt.rb +50 -0
  141. data/lib/strategies/strategies.rb +3 -0
  142. data/lib/strategies/window/blackman_window.rb +32 -0
  143. data/lib/strategies/window/hamming_window.rb +31 -0
  144. data/lib/strategies/window/hanning_window.rb +31 -0
  145. data/lib/strategies/window/kaiser_window.rb +27 -0
  146. data/lib/strategies/window/rectangular_window.rb +22 -0
  147. data/lib/strategies/window/window.rb +42 -0
  148. data/lib/systems/custom_system.rb +13 -0
  149. data/lib/systems/hilbert_transform.rb +6 -0
  150. data/lib/systems/matched_filter.rb +21 -0
  151. data/lib/systems/raised_cosine_filter.rb +11 -0
  152. data/lib/systems/system.rb +19 -0
  153. data/lib/systems/systems.rb +3 -0
  154. data/playground.rb +323 -0
  155. data/plots/_coded_frequency_signal,_ts_=_1_s.png +0 -0
  156. data/plots/_coded_frequency_signal,_ts_=_2_s.png +0 -0
  157. data/plots/coded_freq_sig_from_eqn,_ts_=_1_s.png +0 -0
  158. data/plots/coded_freq_sig_from_eqn,_ts_=_2_s.png +0 -0
  159. data/plots/coded_power_spectral_density,__ts_=_1_s.png +0 -0
  160. data/plots/coded_power_spectral_density,__ts_=_2_s.png +0 -0
  161. data/plots/coded_time_signal,_ts_=_1_s.png +0 -0
  162. data/plots/coded_time_signal,_ts_=_2_s.png +0 -0
  163. data/plots/dpsk_2.png +0 -0
  164. data/plots/freq_sig_from_eqn,_ts_=_1_s.png +0 -0
  165. data/plots/freq_sig_from_eqn,_ts_=_2_s.png +0 -0
  166. data/plots/frequency_signal,_ts_=_1_s.png +0 -0
  167. data/plots/frequency_signal,_ts_=_2_s.png +0 -0
  168. data/plots/power_spectral_density,__ts_=_1_s.png +0 -0
  169. data/plots/power_spectral_density,__ts_=_2_s.png +0 -0
  170. data/plots/psk_2.png +0 -0
  171. data/plots/time_signal,_ts_=_1_s.png +0 -0
  172. data/plots/time_signal,_ts_=_2_s.png +0 -0
  173. data/test-title-dark.png +0 -0
  174. data/test-title.png +0 -0
  175. 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
@@ -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