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
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