digiproc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +48 -0
- data/LICENSE.txt +21 -0
- data/README.md +78 -0
- data/Rakefile +37 -0
- data/TODO.md +50 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config/environment.rb +118 -0
- data/console_tests.rb +44 -0
- data/digiproc.gemspec +49 -0
- data/examples/analog_signals/analog_to_digital.rb +31 -0
- data/examples/analog_signals/companded-signals.png +0 -0
- data/examples/analog_signals/companding.rb +68 -0
- data/examples/analog_signals/fft-plot.png +0 -0
- data/examples/analog_signals/plot_Digiproc::FFT.png +0 -0
- data/examples/analog_signals/plot_Dsp::FFT.png +0 -0
- data/examples/analog_signals/quantization-outputs.png +0 -0
- data/examples/analog_signals/quantize_compand.rb +69 -0
- data/examples/binomial_distribution/bit_error.rb +14 -0
- data/examples/binomial_distribution/dice.rb +35 -0
- data/examples/digital_signals/_coded_frequency_signal,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/_coded_frequency_signal,_ts_=_2_s.png +0 -0
- data/examples/digital_signals/coded_power_spectral_density,__ts_=_1_s.png +0 -0
- data/examples/digital_signals/coded_power_spectral_density,__ts_=_2_s.png +0 -0
- data/examples/digital_signals/coded_time_signal,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/coded_time_signal,_ts_=_2_s.png +0 -0
- data/examples/digital_signals/freq_sig_from_eqn,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/freq_sig_from_eqn,_ts_=_2_s.png +0 -0
- data/examples/digital_signals/frequency_signal,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/frequency_signal,_ts_=_2_s.png +0 -0
- data/examples/digital_signals/modulate_square_pulses.rb +9 -0
- data/examples/digital_signals/modulated_sq._pulses.png +0 -0
- data/examples/digital_signals/modulated_sq._pulses_alt.png +0 -0
- data/examples/digital_signals/power_spectral_density,__ts_=_1_s.png +0 -0
- data/examples/digital_signals/power_spectral_density,__ts_=_2_s.png +0 -0
- data/examples/digital_signals/square_signals.rb +90 -0
- data/examples/digital_signals/time_signal,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/time_signal,_ts_=_2_s.png +0 -0
- data/examples/encoding/gray_code.rb +22 -0
- data/examples/encoding/psk.rb +91 -0
- data/examples/encoding/system_2_phase.png +0 -0
- data/examples/encoding/system_2_xmit_signal.png +0 -0
- data/examples/encoding/system_3_phase.png +0 -0
- data/examples/encoding/system_3_xmit_signal.png +0 -0
- data/examples/encoding/system_4_xmit_signal.png +0 -0
- data/examples/encoding/xor-dpsk-phase-signal-(sys1).png +0 -0
- data/examples/encoding/xor-dpsk-xmit-signal-(sys-1).png +0 -0
- data/examples/factories/Quickplot Graph.png +0 -0
- data/examples/factories/bandpass.rb +6 -0
- data/examples/fft/plot_Dsp::FFT.png +0 -0
- data/examples/fft/recieved_data_(time_domain).png +0 -0
- data/examples/fft/simple_fft_example.rb +47 -0
- data/examples/fft/unprocessed_fft.png +0 -0
- data/examples/filters/bandpass_filter.png +0 -0
- data/examples/filters/filter_a_signal.rb +38 -0
- data/examples/filters/white_noise_db_out_of_bp_filter.png +0 -0
- data/examples/filters/white_noise_mag_out_of_bp_filter.png +0 -0
- data/examples/filters/white_noise_spectra.png +0 -0
- data/examples/functions/compute_probability.rb +29 -0
- data/examples/functions/gram_schmidt.rb +10 -0
- data/examples/functions/minimize_energy.rb +29 -0
- data/examples/functions/orthoganalize.rb +18 -0
- data/examples/functions/simple_functions.rb +81 -0
- data/examples/linear_algebra/diverging_sys.rb +13 -0
- data/examples/linear_algebra/iterative_sys_of_eqns_methods.rb +27 -0
- data/examples/modulation_schemes/dpsk_2.png +0 -0
- data/examples/modulation_schemes/dpsk_256.png +0 -0
- data/examples/modulation_schemes/dpsk_freq_domain.rb +119 -0
- data/examples/modulation_schemes/psk.rb +36 -0
- data/examples/modulation_schemes/psk_2.png +0 -0
- data/examples/modulation_schemes/psk_256.png +0 -0
- data/examples/modulation_schemes/psksystem_1_xmit_signal.png +0 -0
- data/examples/modulation_schemes/psksystem_2_xmit_signal.png +0 -0
- data/examples/modulation_schemes/psksystem_3_xmit_signal.png +0 -0
- data/examples/modulation_schemes/system_1_xmit_signal.png +0 -0
- data/examples/modulation_schemes/system_2_xmit_signal.png +0 -0
- data/examples/modulation_schemes/system_3_xmit_signal.png +0 -0
- data/examples/quickplot/PlottableClass_plot.png +0 -0
- data/examples/quickplot/decorators.rb +13 -0
- data/examples/quickplot/direct_gruff.png +0 -0
- data/examples/quickplot/plot_PlottableClass.png +0 -0
- data/examples/quickplot/quickplot_vs_others.rb +85 -0
- data/examples/quickplot/random_data_quickplot,_dark.png +0 -0
- data/examples/quickplot/random_data_quickplot.png +0 -0
- data/examples/realized_gaussian/norm_dist_plot.png +0 -0
- data/examples/realized_gaussian/norm_dist_spectrum.png +0 -0
- data/examples/realized_gaussian/realized_gaussian_example.rb +23 -0
- data/lib/concerns/convolvable.rb +144 -0
- data/lib/concerns/data_properties.rb +223 -0
- data/lib/concerns/fourier_transformable.rb +178 -0
- data/lib/concerns/initializable.rb +43 -0
- data/lib/concerns/multipliable.rb +22 -0
- data/lib/concerns/os.rb +36 -0
- data/lib/concerns/plottable.rb +248 -0
- data/lib/concerns/requires_data.rb +8 -0
- data/lib/digiproc/version.rb +8 -0
- data/lib/digiproc.rb +2 -0
- data/lib/extensions/array_extension.rb +23 -0
- data/lib/extensions/core_extensions.rb +117 -0
- data/lib/factories/factories.rb +3 -0
- data/lib/factories/filter_factory.rb +83 -0
- data/lib/factories/window_factory.rb +22 -0
- data/lib/fft.rb +255 -0
- data/lib/filters/bandpass_filter.rb +43 -0
- data/lib/filters/bandstop_filter.rb +44 -0
- data/lib/filters/digital_filter.rb +59 -0
- data/lib/filters/highpass_filter.rb +27 -0
- data/lib/filters/lowpass_filter.rb +27 -0
- data/lib/functions.rb +221 -0
- data/lib/probability/binomial_distribution.rb +84 -0
- data/lib/probability/bit_generator.rb +94 -0
- data/lib/probability/gaussian_distribution.rb +29 -0
- data/lib/probability/probability.rb +234 -0
- data/lib/probability/theoretical_gaussian_distribution.rb +59 -0
- data/lib/quick_plot.rb +96 -0
- data/lib/rbplot.rb +219 -0
- data/lib/signals/analog_signal.rb +143 -0
- data/lib/signals/digital_signal.rb +181 -0
- data/lib/strategies/code/differential_encoding_strategy.rb +69 -0
- data/lib/strategies/code/gray_code.rb +75 -0
- data/lib/strategies/code/xor_differential_encoding_strategy.rb +100 -0
- data/lib/strategies/code/xor_differential_encoding_zero_angle_strategy.rb +103 -0
- data/lib/strategies/companding/custom_companding_strategy.rb +29 -0
- data/lib/strategies/convolution/bf_conv.rb +57 -0
- data/lib/strategies/fft/brute_force_dft_strategy.rb +31 -0
- data/lib/strategies/fft/inverse_fft_conjugate_strategy.rb +44 -0
- data/lib/strategies/fft/radix2_strategy.rb +84 -0
- data/lib/strategies/gaussian/gaussian_generator.rb +49 -0
- data/lib/strategies/linear_algebra/gauss_seidel_strategy.rb +90 -0
- data/lib/strategies/linear_algebra/jacobi_strategy.rb +81 -0
- data/lib/strategies/linear_algebra/sor2_strategy.rb +98 -0
- data/lib/strategies/linear_algebra/sor_strategy.rb +108 -0
- data/lib/strategies/modulation/phase_shift_keying_strategy.rb +96 -0
- data/lib/strategies/orthogonalize/gram_schmidt.rb +50 -0
- data/lib/strategies/strategies.rb +3 -0
- data/lib/strategies/window/blackman_window.rb +32 -0
- data/lib/strategies/window/hamming_window.rb +31 -0
- data/lib/strategies/window/hanning_window.rb +31 -0
- data/lib/strategies/window/kaiser_window.rb +27 -0
- data/lib/strategies/window/rectangular_window.rb +22 -0
- data/lib/strategies/window/window.rb +42 -0
- data/lib/systems/custom_system.rb +13 -0
- data/lib/systems/hilbert_transform.rb +6 -0
- data/lib/systems/matched_filter.rb +21 -0
- data/lib/systems/raised_cosine_filter.rb +11 -0
- data/lib/systems/system.rb +19 -0
- data/lib/systems/systems.rb +3 -0
- data/playground.rb +323 -0
- data/plots/_coded_frequency_signal,_ts_=_1_s.png +0 -0
- data/plots/_coded_frequency_signal,_ts_=_2_s.png +0 -0
- data/plots/coded_freq_sig_from_eqn,_ts_=_1_s.png +0 -0
- data/plots/coded_freq_sig_from_eqn,_ts_=_2_s.png +0 -0
- data/plots/coded_power_spectral_density,__ts_=_1_s.png +0 -0
- data/plots/coded_power_spectral_density,__ts_=_2_s.png +0 -0
- data/plots/coded_time_signal,_ts_=_1_s.png +0 -0
- data/plots/coded_time_signal,_ts_=_2_s.png +0 -0
- data/plots/dpsk_2.png +0 -0
- data/plots/freq_sig_from_eqn,_ts_=_1_s.png +0 -0
- data/plots/freq_sig_from_eqn,_ts_=_2_s.png +0 -0
- data/plots/frequency_signal,_ts_=_1_s.png +0 -0
- data/plots/frequency_signal,_ts_=_2_s.png +0 -0
- data/plots/power_spectral_density,__ts_=_1_s.png +0 -0
- data/plots/power_spectral_density,__ts_=_2_s.png +0 -0
- data/plots/psk_2.png +0 -0
- data/plots/time_signal,_ts_=_1_s.png +0 -0
- data/plots/time_signal,_ts_=_2_s.png +0 -0
- data/test-title-dark.png +0 -0
- data/test-title.png +0 -0
- metadata +322 -0
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
|