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