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.
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
data/lib/rbplot.rb ADDED
@@ -0,0 +1,219 @@
1
+ ##
2
+ # Plotting API built on top of Gruff meant to have similar characterstics to matplotlib
3
+ class Digiproc::Rbplot
4
+
5
+ MIDNIGHT = {
6
+ :colors => [
7
+ '#00dae5', #teal
8
+ '#FF1A1A', # red
9
+ '#3FFC07', # green
10
+ '#FDD84E', # yellow
11
+ '#6886B4', # blue
12
+ '#8A6EAF', # purple
13
+ '#EFAA43', # orange
14
+ 'white'
15
+ ],
16
+ :marker_color => '#7e7c7f',
17
+ :font_color => '#cccccc',
18
+ :background_colors => %w(black #2a2a2a)
19
+ }
20
+
21
+ SUBMARINE = {
22
+ :colors => [
23
+ '#FF1A1A', # red
24
+ '#3FFC07', # green
25
+ '#FDD84E', # yellow
26
+ '#6886B4', # blue
27
+ '#8A6EAF', # purple
28
+ '#EFAA43', # orange
29
+ 'white'
30
+ ],
31
+ :marker_color => '#3d007a',
32
+ :font_color => '#cccccc',
33
+ :background_colors => %w(black #2a2a2a)
34
+ }
35
+
36
+ BLUESCALE = {
37
+ :colors => [
38
+ '#0b0b70', #Blue
39
+ '#981598', #Purple
40
+ '#181000', #Dark gray
41
+ '#B50202', #
42
+ '#c8c8c8', #
43
+ '#e8e8e8', #
44
+ ],
45
+ :marker_color => '#aea9a9', # Grey
46
+ :font_color => 'black',
47
+ :background_colors => 'white'
48
+ }
49
+
50
+
51
+ ##
52
+ # Constuctor for a line plot instnace
53
+ ## x = Digiproc::Functions.linspace(1,100,100)
54
+ ## y = Digiproc::Probability.nrand(100)
55
+ ## plt = Digiproc::Rbplot.line(x, y, "random vals")
56
+ def self.line(x = nil, y = nil, label="data")
57
+ LinePlot.new(x, y, label)
58
+ end
59
+
60
+ ##
61
+ # Class for a line plot
62
+ class LinePlot
63
+ include Digiproc::OS
64
+ def initialize(x, y, label = "data 1")
65
+ @methods = {
66
+ line_width: 2.5,
67
+ dot_radius: 0.1,
68
+ theme: BLUESCALE,
69
+ title: 'RbPlot'
70
+ }
71
+ @dataxy = [[label, x, y]]
72
+ @path = './'
73
+ @filename = nil
74
+ @size = '1000x1000'
75
+ self.xsteps(5)
76
+ end
77
+
78
+ ##
79
+ # sets filename for the image to be written
80
+ # downcases, strips, and replaces spaces with dashes
81
+ def filename(name)
82
+ @filename = name.downcase.strip.gsub(' ', '-')
83
+ end
84
+ ##
85
+ #Sets title for the graph
86
+ ## plt.title('Plot Title')
87
+ def title(title)
88
+ filename(title)
89
+ @methods[:title] = title
90
+ end
91
+
92
+ ##
93
+ # Sets the x label:
94
+ ## plt.xlabel('time')
95
+ def xlabel(label)
96
+ @methods[:x_axis_label] = label
97
+ end
98
+
99
+ ##
100
+ # Sets the y label
101
+ ## plt.ylabel('y axis')
102
+ def ylabel(label)
103
+ @methods[:y_axis_label] = label
104
+ end
105
+
106
+ ##
107
+ # Sets data label for the last inputted line data
108
+ ## plt.data_label('Set 3')
109
+ def data_label(label)
110
+ @dataxy.last[0] = label
111
+ end
112
+
113
+ ##
114
+ # Sets the labels for each line entered (variable array input)
115
+ ## plt.legend("Data 1", "Data 2")
116
+ def legend(*labels)
117
+ labels.each.with_index do |l, i|
118
+ @dataxy[i][0] = l
119
+ end
120
+ end
121
+
122
+ ##
123
+ # Add another line to the plot
124
+ # y2 = Digiproc::Probability.nrand(100)
125
+ ## plt.add_line(x, y2, "Data 2")
126
+ def add_line(x, y, label="data #{@dataxy.length + 1}")
127
+ @dataxy << [label, x, y]
128
+ end
129
+
130
+ ##
131
+ # Sets the number of labels on the x axis
132
+ ## plt.xsteps(5)
133
+ def xsteps(steps)
134
+ len = @dataxy.first[1].length
135
+ steps = len if(steps >= len)
136
+ labels = {}
137
+ every = (len.to_f / steps).floor
138
+ for i in 0..steps do
139
+ index = i == 0 ? 0 : (i * every) - 1
140
+ labels_val = i == 0 ? 1 : i * every
141
+ # labels[labels_val] = @dataxy.first[1][index].round(2)
142
+ labels[@dataxy.first[1][index]] = @dataxy.first[1][index].round(2)
143
+ end
144
+ @methods[:labels] = labels
145
+ end
146
+
147
+ ##
148
+ # Sets the path where the image will be written
149
+ # Defaults to "./"
150
+ ## plt.path("./")
151
+ def path(path)
152
+ @path = path
153
+ end
154
+
155
+ ##
156
+ # Sets the theme of the graph
157
+ # Accepts :dark, :light, or :deep
158
+ ## plt.theme(:dark)
159
+ def theme(theme)
160
+ case theme
161
+ when :dark
162
+ @methods[:theme] = MIDNIGHT
163
+ when :deep
164
+ @methods[:theme] = SUBMARINE
165
+ when :light
166
+ @methods[:theme] = BLUESCALE
167
+ else
168
+ throw ArgumentError.new('Not a valid theme')
169
+ end
170
+ end
171
+
172
+ ##
173
+ # Set size of the graph in pixels. Takes two integers
174
+ ## plt.size(2000, 2000). Defaults upon initialization to 1000,1000
175
+ def size(w,h)
176
+ @size = "#{w}x#{h}"
177
+ end
178
+
179
+ ##
180
+ # Writes the image and opens it with the default program depending on the os
181
+ ## plt.show
182
+ def show(path = @path)
183
+ write(path)
184
+ file = path + @filename + '.png'
185
+ if windows?
186
+ system %{cmd /c "start #{file}"}
187
+ elsif mac?
188
+ file_to_open = "/path/to/file.txt"
189
+ system %{open "#{file}"}
190
+ elsif linux?
191
+ begin
192
+ system %{xdg-open "#{file}"}
193
+ system %{cmd.exe /c "start #{file}"}
194
+ rescue
195
+ system %{cmd.exe /c "start #{file}"}
196
+ end
197
+ end
198
+ end
199
+
200
+ ##
201
+ # Writes the image to the saved path, does not open it
202
+ ## plt.write
203
+ def write(path = @path)
204
+
205
+ gline = Gruff::Line.new(@size)
206
+ @methods.each do |m, args|
207
+ gline.send("#{m}=", args)
208
+ end
209
+ @dataxy.each do |dxy|
210
+ gline.dataxy(dxy[0], dxy[1], dxy[2])
211
+ end
212
+
213
+ @filename ||= filename(@methods[:title])
214
+ @filename ||= "rbPlot"
215
+ gline.write(path + @filename + '.png')
216
+ end
217
+
218
+ end
219
+ end
@@ -0,0 +1,143 @@
1
+ ##
2
+ # Class for performing actions upon an Analog, Continuous Time signal defined by an equation
3
+ # Initialized with a lambda or proc, the signal is sampled, companded, quantized, and can be turned into a `Digiproc::DigitalSignal`
4
+ class Digiproc::AnalogSignal
5
+
6
+ attr_accessor :sample_rate, :size, :companding_strategy, :signal, :quantization_bits, :quant_max, :quant_min
7
+ attr_reader :raw_samples, :quantized_samples, :eqn
8
+
9
+ ##
10
+ # See examples/analog_signals/companding for full options in initializer
11
+ ## fn = ->(x){ Math.sin(x) }
12
+ ## asig = Digiproc::AnalogSignal.new(eqn: fn)
13
+ def initialize(eqn: ,sample_rate: 0.0001, size: 100, companding_strategy: nil, quantization_bits: Float::INFINITY, quant_max: nil, quant_min: nil)
14
+ @eqn, @sample_rate, @size, @quantization_bits, @quant_max, @quant_min = eqn, sample_rate, size, quantization_bits, quant_max, quant_min
15
+ @signal = eqn
16
+ @companding_strategy = (companding_strategy.is_a?(Class) and !!companding_strategy) ? companding_strategy.new : companding_strategy
17
+ end
18
+
19
+ ##
20
+ # No arguments, outputs [Array] digital samples
21
+ # Will sample, compand (compress, expand), and quantize samples (companding and quantizing optional)
22
+ # See analog_signals examples
23
+ def digitize
24
+ samples = sample
25
+ @raw_samples = samples
26
+ samples = compress(samples) if not companding_strategy.nil?
27
+ samples = quantize(samples, self.quantization_bits, self.quant_max, self.quant_min) if self.quantization_bits < Float::INFINITY
28
+ samples = expand(samples) if not companding_strategy.nil?
29
+ @quantized_samples = samples
30
+ samples
31
+ end
32
+
33
+ ##
34
+ # Returns a Digiproc::DigitalSignal of the digital signals
35
+ def to_ds
36
+ Digiproc::DigitalSignal.new(data: digitize)
37
+ end
38
+
39
+ ##
40
+ # Returns [Float] normalized quantization RMS error. See examples/analog_signal/companding
41
+ def normalized_quantization_rms_error
42
+ total = 0
43
+ x_max = self.raw_samples.max
44
+ self.raw_samples.each_with_index do |x, i|
45
+ total += ((x.to_f - quantized_samples[i]) / x_max) ** 2
46
+ end
47
+ (1.0 / size) * (total ** (0.5))
48
+ end
49
+
50
+ private
51
+
52
+ ##
53
+ # Quantize signals to a certain number of bits over a specified range
54
+ # == Args
55
+ # samples:: [Array] input data
56
+ # bits:: [Integer] number of bits used to reprisent each sample
57
+ # max:: maximum possible sample values (to be reprisented by max bits in quantization)
58
+ # min:: minimum possible sample value (to be reprisented by minimum bits value in quantization)
59
+ # bit quantization is mapped back to original max and min values, returning what they would look like upon
60
+ # attempted reconstruction of the analog signal.
61
+ def quantize(samples, bits, max, min)
62
+ sample_bits = quantize_bit_reprisentation(samples, bits, max, min)
63
+ process(sample_bits, map_to_eqn(bits**2 / 2.0 , -(bits**2) / 2.0 , max, min))
64
+ end
65
+
66
+
67
+ def quantize_positive_bits(samples, bits, max, min)
68
+ levels = bits ** 2
69
+ quantize_bit_reprisentation(samples, bits, max, min) + (levels / 2.0)
70
+ end
71
+
72
+ ##
73
+ # Based off max and min (defined by samples.max and samples.min if not provided), the samples are mapped from -1 to 1.
74
+ # Then they are multiplied by the number of "levels" of quantization (ie: number_of_bits ^ 2 / 2).
75
+ # Then, the samples are rounded, thus quantizing the samples to the number of bits
76
+ def quantize_bit_reprisentation(samples, bits, max, min)
77
+ max = samples.max if max.nil?
78
+ min = samples.min if min.nil?
79
+ levels = bits ** 2
80
+ mapped = map_to_unit_centered(samples, max, min)
81
+ process(mapped, ->(n){ (n * levels / 2.0).round})
82
+ end
83
+
84
+ ##
85
+ # Compress samples for companding before quantization
86
+ def compress(samples)
87
+ self.companding_strategy.compress(samples)
88
+ end
89
+
90
+ ##
91
+ # Expand samples for companding after quantization
92
+ def expand(samples)
93
+ self.companding_strategy.expand(samples)
94
+ end
95
+
96
+ ##
97
+ # Sample the signal and return [Array] of sampled values
98
+ def sample
99
+ sample_times = process(0...size, ->(n){ n * self.sample_rate })
100
+ process(sample_times, self.signal)
101
+ end
102
+
103
+ ##
104
+ # Helper for processing an array of values with a lambda function
105
+ def process(inputs , equation)
106
+ inputs.map{ |n| equation.call(n) }
107
+ end
108
+
109
+ ##
110
+ # Map values to range -1 to 1
111
+ def map_to_unit_centered(samples, max, min)
112
+ center = center_of(max, min)
113
+ range = range_of(max, min)
114
+ a_range = range / 2.0
115
+ mapping = ->(n){ (n - center) / a_range }
116
+ process(samples, mapping)
117
+ end
118
+
119
+ ##
120
+ # Return a lambda function which will map a dataset to a target maximum and minimum
121
+ def map_to_eqn(starting_max, starting_min, target_max, target_min)
122
+ target_center = center_of(target_max, target_min)
123
+ target_range = range_of(target_max, target_min)
124
+ starting_center = center_of(starting_max, starting_min)
125
+ starting_range = range_of(starting_max.to_f, starting_min)
126
+ center_map = target_center - starting_center
127
+ range_map = target_range / starting_range
128
+ ->(n){ (n - starting_center) * range_map + target_center}
129
+ end
130
+
131
+ ##
132
+ # Return center [Float]
133
+ def center_of(max, min)
134
+ (max + min) / 2.0
135
+ end
136
+
137
+ ##
138
+ # Return range [Float]
139
+ def range_of(max, min)
140
+ max.to_f - min
141
+ end
142
+
143
+ end
@@ -0,0 +1,181 @@
1
+ ##
2
+ # Class for performing actions on Digital Signals easily
3
+ class Digiproc::DigitalSignal
4
+ attr_accessor :data
5
+ include Digiproc::Convolvable::InstanceMethods, Digiproc::FourierTransformable
6
+
7
+ ##
8
+ # Construct an instance from a Proc (or a Lambda) and a size
9
+ ## ds = Digiproc::DigitalSignal.new_from_eqn(eqn: ->(t){ Math.sin(t) }, size: 100)
10
+ def self.new_from_eqn(eqn: , size: )
11
+ rng = (0...size)
12
+ self.new(data: rng.map{ |n| eqn.call(n) })
13
+ end
14
+
15
+ ##
16
+ # Make a digital signal which is defined by one equation in one range, and another eqn in another range
17
+ ## eqn1 = ->(t){ (1 - Math::E ** (-0.08*t)) }
18
+ ## eqn2 = ->(t){ Math::E ** (-0.002*(t - 100)) }
19
+ ## ds = Digiproc::DigitalSignal.new_from_equations(eqns: [eqn1, eqn2], ranges: [0...100, 100...1000])
20
+ def self.new_from_equations(eqns: , ranges: )
21
+ data = []
22
+ eqns.each_with_index do |eqn, i|
23
+ ranges[i].each do |n|
24
+ data[n] = eqn.call(n)
25
+ end
26
+ end
27
+ data.map!{ |val| val.nil? ? 0 : val }
28
+ self.new(data: data)
29
+ end
30
+
31
+
32
+ ##
33
+ # Make a new digital signal from fft data
34
+ def new_from_spectra(fft)
35
+ self.new(data: Digiproc::Functions.ifft(fft))
36
+ end
37
+
38
+ ##
39
+ # Initialize with `data`
40
+ def initialize(data: )
41
+ raise ArgumentError.new("Data must be an Array, not a #{data.class}") if not data.is_a? Array
42
+ @data = data
43
+ initialize_modules(Digiproc::FourierTransformable => {time_data: data})
44
+ end
45
+
46
+ ##
47
+ # Helper method to allow user to process a ds data using a block
48
+ ## ds = Digiproc::DigitalSignal.new_from_eqn(eqn: ->(t){ Math.sin(t) }, size: 100)
49
+ ## ds.process { |el| el * 10 } # Change signal gain from 1 to 10.
50
+ # Does not change @data, just returns processed array
51
+ def process
52
+ processed_data = []
53
+ for i in 0...data.length do
54
+ processed_data << (yield data[i])
55
+ end
56
+ processed_data
57
+ end
58
+
59
+ ##
60
+ # Same as `#process` except @data is replaced by the output
61
+ def process!
62
+ processed_data = []
63
+ for i in 0...data.length do
64
+ processed_data << (yield data[i])
65
+ end
66
+ self.data = processed_data
67
+ end
68
+
69
+ ##
70
+ # Updates data while processing, allowing recursive processing (ie using prev outputs to create new ones)
71
+ def process_in_place!
72
+ for i in 0...data.length do
73
+ self.data[i] = yield data[i]
74
+ end
75
+ self.data
76
+ end
77
+
78
+ ##
79
+ # Allows multiplication of two digital signal objects
80
+ # Performs element by element multiplicaiton of the data vectors
81
+ def *(ds)
82
+ raise ArgumentError.new("Object must have a data property") unless ds.respond_to? :data
83
+ raise ArgumentError.new("Object must have a data array") unless ds.data.respond_to? :times
84
+ self.data.length < ds.data.length ? self.class.new(data:self.data.times(ds.data.take(self.data.length))) : self.class.new(data: ds.data.times(self.data.take(ds.data.length)))
85
+ # self.data.times ds.data
86
+ end
87
+
88
+ ##
89
+ # Get data values from @data by index
90
+ ## ds = Digiproc::DigitalSignal.new_from_eqn(eqn: ->(t){ Math.sin(t) }, size: 100)
91
+ ## vals = ds.i(10..12) # => [-0.5440211108893699, -0.9999902065507035, -0.5365729180004349]
92
+ def i(*n)
93
+ indices = n.map{ |input| input.respond_to?(:to_a) ? input.to_a : input}
94
+ indices.flatten!
95
+ indices.map!{ |val| self.value_at val }
96
+ return indices.length == 1 ? indices.first : indices
97
+ end
98
+
99
+ ##
100
+ # Get the value at a specific data index. If index falls outside of the `data` array, return 0
101
+ # This is useful when simulating a signal multiplied by a unit step which is zero outside the bounds defined in the data array
102
+ def value_at(n)
103
+ data[n].nil? ? 0 : data[n]
104
+ end
105
+
106
+ ##
107
+ # Get values in the data array from a start index to a start index (inclusive). Does not turn data outside array into a 0 value
108
+ def values_between(start,stop)
109
+ data[start, stop - start + 1]
110
+ end
111
+
112
+ ##
113
+ # Convolves data with incoming signal, returns a new Digiproc::DigitalSignal whose data is the result of the convolution
114
+ def ds_convolve(signal)
115
+ Digiproc::DigitalSignal.new(data: self.conv(signal))
116
+ end
117
+
118
+ ##
119
+ # Alias to ds_convolve
120
+ def ds_conv(signal)
121
+ Digiproc::DigitalSignal.new(data: self.conv(signal))
122
+ end
123
+
124
+ ##
125
+ # Performs cross correlation with an incoming signal, returns a Digiproc::DigitalSignal whose data is the result of the cross correlation
126
+ def ds_cross_correlation(signal)
127
+ Digiproc::DigitalSignal.new(data: self.cross_correlation(signal))
128
+ end
129
+
130
+ ##
131
+ # Alias for #ds_cross_correlation
132
+ def ds_xcorr(sig)
133
+ ds_cross_correlation(sig)
134
+ end
135
+
136
+ ##
137
+ # Performs an autocorrelation of the `data` array and retursn a Digiproc::DigitalSignal whose data is the result of the autocorrelation
138
+ def ds_auto_correlation
139
+ Digiproc::DigitalSignal.new(data: self.auto_correlation)
140
+ end
141
+
142
+ ##
143
+ # Alias to #ds_auto_correlation
144
+ def ds_acorr
145
+ ds_auto_correlation
146
+ end
147
+
148
+ ##
149
+ # Returns the Power Spectral Density (PSD) of the signal by multiplying the signal's FFT by the conjugate of the FFT (ie squaring the FFT)
150
+ # The result is in the frequency spectrum (as a Digiproc::FFT object)
151
+ def power_spectral_density
152
+ self.fft * self.fft.conjugate
153
+ end
154
+
155
+ ##
156
+ # Alias to #power_spectral_density
157
+ def psd
158
+ self.power_spectral_density
159
+ end
160
+
161
+ ##
162
+ # Returns the cross_spectral_density of the digital signal with an incoming signal (accepts an array of numeric data)
163
+ # Returns a Digiproc::FFT object
164
+ def cross_spectral_density(signal)
165
+ ft = Digiproc::FFT.new(time_data: self.xcorr(signal))
166
+ ft.calculate
167
+ ft
168
+ end
169
+
170
+ ##
171
+ # Alias for #cross_spectral_density
172
+ def csd(signal)
173
+ self.cross_spectral_density(signal)
174
+ end
175
+
176
+ ##
177
+ # Returns data as an array
178
+ def to_a
179
+ self.data
180
+ end
181
+ end
@@ -0,0 +1,69 @@
1
+ ## Class/Strategy for for encoding a string of bits
2
+ # ENCODES SIGNAL PER M-ARY PSK CODING STRATEGY, SEE HW6 ELEG 635
3
+ # use the generic DPSK equation for M = 2. We will apply the ORIGINAL signal Dn
4
+ # to the equation βn = (2l−1)π % 2π, l ⩽ M for
5
+ # the phase mapping, and M then the differential signal will be
6
+ # created by saying our transmitted bit will be αn = αn−1 + βn
7
+
8
+ class Digiproc::Strategies::DifferentialEncodingStrategy
9
+
10
+
11
+ ##
12
+ # Encoding an incoming array of bits into an array of encoded phase angles
13
+ # Requires an input of an array, and has optional arguments of m (number of bits per symbol)
14
+ # And a beginning value (a starting reference phase angle). Outputs an array of
15
+ # phase angles
16
+ # Unline previous encoding strategies, this does not XOR the bits and then phase shift those
17
+ # encoded bits. Instead, the bits are transformed into frequencies which are then
18
+ # added to dealyed versions of themselves modulo 2pi
19
+ def self.encode(arr, m = 2, beginning_val = "0")
20
+ beginning_val = beginning_val.to_s(2) unless beginning_val.is_a? String
21
+ encoded = [beginning_val]
22
+ arr = arr.map(&:to_s)
23
+ for i in 0...arr.length do
24
+ curr_phase = to_phase(m)[arr[i].to_i(2).to_f]
25
+ last_phase = encoded.last.to_f
26
+ encoded << ((curr_phase + last_phase) % (2 * Math::PI)).to_s
27
+ end
28
+ encoded
29
+ end
30
+
31
+ ##
32
+ # Does not simulate a reciever, it just does the inverse algorithm
33
+ # to retrieve the original message
34
+ # Currently incorrect
35
+ # TODO: fix this method
36
+ def self.decode(bits, m = 2)
37
+ encoded = []
38
+ for i in 1...bits.length do
39
+ encoded << ((-1 * bits[i - 1].to_f + bits[i].to_f) % (2 * Math::PI)).to_s
40
+ end
41
+ encoded.map{|phase| decode_phase(m)[phase.to_f]}
42
+ end
43
+
44
+ ##
45
+ # Required via the protocol for an encoding strategy, but this
46
+ # algorithm does not require a phase shift so the lambda will return the input
47
+ def self.phase_shift_eqn(m = nil)
48
+ ->(l){ l }
49
+ end
50
+
51
+ ##
52
+ # Required via the protocol for an encoding strategy, but this
53
+ # algorithm does not require a phase shift so the lambda will return the input
54
+ def self.phase_to_sym(m = nil)
55
+ ->(l){ l }
56
+ end
57
+
58
+ private
59
+ def self.to_phase(m)
60
+ ->(l){ (((2.0 * (l+1) - 1.0) / m) % 2) * Math::PI }
61
+ end
62
+
63
+ def self.decode_phase(m)
64
+ ->(code){ (((code / (Math::PI)) * m) + 1.0) / 2.0 - 1.0 }
65
+ end
66
+
67
+ # TODO: IMPLEMENT DECODING STRATEGY PER FIGURE 4-15 in INTRODUCTION TO DIGITAL COMMUNICATIONS, ZIEMER, PETERSON
68
+
69
+ end
@@ -0,0 +1,75 @@
1
+ ##
2
+ # Strategy for creating the Gray Code of a big stream.
3
+ # Capable of encoding or decoding gray code
4
+ # Gray code ensures that all adjacent numbers only differ by
5
+ # one bit. This will reduce errors caused by a transmission/recieving process
6
+ # For information on Gray Code, try looking at https://www.allaboutcir- cuits.com/technical-articles/gray-code-basics/
7
+
8
+ class Digiproc::Strategies::GrayCode
9
+
10
+ ##
11
+ # Input argument = integer indicating how many bits you want
12
+ # The output will be an array of incrememnting gray code numbers with the
13
+ # number of specified bits. The output array's length wlll be 2^n where
14
+ # `n` is the input number.
15
+ def self.generate(size)
16
+ if size == 1
17
+ ["0", "1"]
18
+ else
19
+ prepend("0", generate(size - 1)) + prepend("1", generate(size - 1).reverse)
20
+ end
21
+ end
22
+
23
+ ##
24
+ # Accepts a n integer.
25
+ # The input integer is the decimal version of a gray code number
26
+ # This method will output a binary number in string form
27
+ # The ouput number is the "regular" number counterpart to the
28
+ # gray code input number. The output will be in binary form.
29
+ def self.to_binary(bin)
30
+ bits = bin.size
31
+ gray_code = 0
32
+ bit_number = bits - 1
33
+ bit = 0
34
+ while bit_number >= 0
35
+ bit ^= bin >> bit_number & 1
36
+ gray_code |= (1 << bit_number) * bit
37
+ bit_number -= 1
38
+ end
39
+ gray_code.to_s(2)
40
+ end
41
+
42
+ ##
43
+ # Accepts a n integer.
44
+ # The input integer is the decimal version of a gray code number
45
+ # This method will output a decimal number in string form
46
+ # The ouput number is the "regular" number counterpart to the
47
+ # gray code input number. The output will be in decimal form.
48
+ def self.to_dec(bin)
49
+ bits = bin.size
50
+ gray_code = 0
51
+ bit_number = bits - 1
52
+ bit = 0
53
+ while bit_number >= 0
54
+ bit ^= bin >> bit_number & 1
55
+ gray_code |= (1 << bit_number) * bit
56
+ bit_number -= 1
57
+ end
58
+ gray_code.to_s
59
+ end
60
+
61
+ ##
62
+ # Accepts a n integer.
63
+ # The input integer is a decimal number that you want to convert to gray code
64
+ # This method will output a binary number in string form
65
+ # The ouput number is the the gray code number counterpart to the input integer.
66
+ def self.to_gray(binary)
67
+ (binary ^ (binary >> 1)).to_s(2)
68
+ end
69
+
70
+ private
71
+
72
+ def self.prepend(prefix, code)
73
+ code.map { |bit| prefix + bit}
74
+ end
75
+ end