Skip to content

Commit

Permalink
Homepage mini simulation (#57)
Browse files Browse the repository at this point in the history
* wip

* basic plots working

* fixed graphics lag

* x axis for freq plot

* time plot x axis

* y axis

* noise scaling

* integrated

* added js
  • Loading branch information
777arc authored Dec 5, 2024
1 parent 8c36eb1 commit ceefdf0
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 26 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ distribute-*
env/
build/
_build/
OLD_build/
dist/
Sphinx.egg-info/
doc/_build/
Expand Down
177 changes: 177 additions & 0 deletions _static/js/homepage_app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
function homepage_app() {
function createSignal(N) {
const x = new Array(N * 2);

// complex AWGN
let noise_ampl_dB = document.getElementById("noise_ampl_dB").value;
document.getElementById("noise_value").innerHTML = noise_ampl_dB - 30;
let noise_ampl = Math.pow(10, noise_ampl_dB / 10);
for (let i = 0; i < N; i++) {
x[i * 2] = noise_ampl * Math.sqrt(-2.0 * Math.log(Math.random())) * Math.cos(2.0 * Math.PI * Math.random());
x[i * 2 + 1] = noise_ampl * Math.sqrt(-2.0 * Math.log(Math.random())) * Math.cos(2.0 * Math.PI * Math.random());
}

// Tone
let freq = document.getElementById("freq").value;
document.getElementById("freq_value").innerHTML = freq;
for (let i = 0; i < N; i++) {
x[i * 2] += Math.cos(2 * Math.PI * i * freq);
x[i * 2 + 1] += Math.sin(2 * Math.PI * i * freq);
}

return x;
}

const N = 1024;
let update_period = 50; // in ms, gets doubled every time refersh is too slow to keep up

function updatePlot() {
const start_t = performance.now();
const signal = createSignal(N);

const fft_obj = new FFT(N);
const signal_fft = fft_obj.createComplexArray();
fft_obj.transform(signal_fft, signal);

// Take magnitude of FFT
const signal_fft_mag = new Array(N);
for (let i = 0; i < N; i++) {
signal_fft_mag[i] = signal_fft[2 * i] * signal_fft[2 * i] + signal_fft[2 * i + 1] * signal_fft[2 * i + 1];
}
let signal_fft_mag_shifted = fftshift(signal_fft_mag);

// Convert to dB
const signal_fft_mag_shifted_dB = new Array(N);
for (let i = 0; i < N; i++) {
signal_fft_mag_shifted_dB[i] = 10 * Math.log10(signal_fft_mag_shifted[i]);
}

// Plot freq
const canvas = document.getElementById("freq_plot");
const ctx = canvas.getContext("2d", { alpha: false }); // apparently turning off transparency makes it faster

ctx.setTransform(1, 0, 0, 1, 0, 0); // resets transform
ctx.fillStyle = "white";
ctx.lineWidth = 1;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.translate(0, 400); // move half of canvas height so y=0 in middle
ctx.beginPath();
ctx.strokeStyle = "blue";
ctx.moveTo(0, Math.floor(-7 * signal_fft_mag_shifted_dB[0] + 200));
for (let i = 1; i < N; i++) {
ctx.lineTo(i * 2, Math.floor(-7 * signal_fft_mag_shifted_dB[i] + 200)); // -1* to flip y-axis
}
ctx.stroke();

// Freq x-axis ticks and labels
ctx.setTransform(1, 0, 0, 1, 0, 0); // resets transform
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 3;
ctx.font = "36px Arial";
ctx.fillStyle = "black";
// axis line
ctx.moveTo(0, ctx.canvas.height - 80);
ctx.lineTo(ctx.canvas.width, ctx.canvas.height - 80);
// ticks
for (let i = 0; i < 11; i++) {
ctx.moveTo((ctx.canvas.width / 10) * i, ctx.canvas.height - 100);
ctx.lineTo((ctx.canvas.width / 10) * i, ctx.canvas.height - 80);
ctx.fillText(Math.round((i - 5) * 0.1 * 100) / 100, ((ctx.canvas.width / 10) * i - 0) * 0.975, ctx.canvas.height - 35);
}
ctx.fillText("Hz", ctx.canvas.width / 2 + 5, ctx.canvas.height - 35);
ctx.fillText("Frequency", ctx.canvas.width / 2 - 70, ctx.canvas.height - 7);
ctx.stroke();

// Freq y-axis ticks and labels
ctx.setTransform(1, 0, 0, 1, 0, 0); // resets transform
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 3;
ctx.font = "36px Arial";
ctx.fillStyle = "black";
// axis line
ctx.moveTo(0, 0);
ctx.lineTo(0, ctx.canvas.height - 80);
// ticks
for (let i = 1; i < 6; i++) {
ctx.moveTo(0, ((ctx.canvas.height - 80) / 6) * i);
ctx.lineTo(20, ((ctx.canvas.height - 80) / 6) * i);
ctx.fillText(i * -10, 30, ((ctx.canvas.height - 80) / 6) * i + 10);
}
ctx.fillText("dB", 20, 36);
ctx.stroke();

// Plot time
const canvas_time = document.getElementById("time_plot");
const ctx_time = canvas_time.getContext("2d", { alpha: false });

ctx_time.setTransform(1, 0, 0, 1, 0, 0); // resets transform
ctx_time.fillStyle = "white";
ctx_time.lineWidth = 1;
ctx_time.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx_time.translate(0, 400); // move half of canvas height so y=0 in middle
ctx_time.beginPath();
ctx_time.strokeStyle = "blue";
ctx_time.moveTo(0, Math.floor(-20 * signal[0] - 40));
for (let i = 1; i < N; i++) {
ctx_time.lineTo(i * 2, Math.floor(-20 * signal[i * 2] - 40)); // -1* to flip y-axis
}
ctx_time.stroke();

// Time x-axis ticks and labels
ctx_time.setTransform(1, 0, 0, 1, 0, 0); // resets transform
ctx_time.beginPath();
ctx_time.strokeStyle = "black";
ctx_time.lineWidth = 3;
ctx_time.font = "36px Arial";
ctx_time.fillStyle = "black";
// axis line
ctx_time.moveTo(0, ctx_time.canvas.height - 60);
ctx_time.lineTo(ctx_time.canvas.width, ctx_time.canvas.height - 60);
// ticks
for (let i = 0; i < 11; i++) {
ctx_time.moveTo((ctx_time.canvas.width / 10) * i, ctx_time.canvas.height - 80);
ctx_time.lineTo((ctx_time.canvas.width / 10) * i, ctx_time.canvas.height - 60);
//ctx_time.fillText(Math.round(i* 0.1 * 100) / 100, ((ctx_time.canvas.width / 10) * i - 0) * 0.98, ctx_time.canvas.height - 5);
}
ctx_time.fillText("Time", ctx_time.canvas.width / 2, ctx_time.canvas.height - 5);
ctx_time.stroke();

// Time y-axis ticks and labels
ctx_time.setTransform(1, 0, 0, 1, 0, 0); // resets transform
ctx_time.beginPath();
ctx_time.strokeStyle = "black";
ctx_time.lineWidth = 3;
ctx_time.font = "36px Arial";
ctx_time.fillStyle = "black";
// axis line
ctx_time.moveTo(0, 0);
ctx_time.lineTo(0, ctx_time.canvas.height - 80);
// ticks
for (let i = 1; i < 6; i++) {
ctx_time.moveTo(0, ((ctx_time.canvas.height - 80) / 6) * i);
ctx_time.lineTo(20, ((ctx_time.canvas.height - 80) / 6) * i);
}
ctx_time.fillText("1", 20, 36);
ctx_time.fillText("-1", 20, ctx_time.canvas.height - 80);
ctx_time.fillText("0", 30, ctx_time.canvas.height / 2 - 40);
ctx_time.stroke();
// y=0 line
ctx_time.strokeStyle = "grey";
ctx_time.lineWidth = 1;
ctx_time.moveTo(0, (ctx_time.canvas.height - 80) / 2);
ctx_time.lineTo(ctx_time.canvas.width, (ctx_time.canvas.height - 80) / 2);
ctx_time.stroke();

//console.log("Time taken to update frame: " + (performance.now() - start_t) + " ms");
if (performance.now() - start_t > update_period) {
console.log("Warning: browser is not able to keep up, doubling update period");
update_period = update_period * 2;
}
}

setInterval(function () {
updatePlot();
}, update_period); // in ms
}
61 changes: 36 additions & 25 deletions _templates/homepage.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,55 @@ <h1 style="text-align: center">PySDR: A Guide to SDR and DSP using Python</h1>
<span class="std std-ref">Dr. Marc Lichtman</span>
</a>
-
<a
class="reference external"
href="mailto:pysdr&#37;&#52;&#48;vt&#46;edu"
rel="noopener noreferrer"
target="_blank"
>
<a class="reference external" href="mailto:pysdr&#37;&#52;&#48;vt&#46;edu" rel="noopener noreferrer" target="_blank">
pysdr<span>&#64;</span>vt<span>&#46;</span>edu
</a>
</p>

<h4 style="line-height: 1.4">
Welcome to PySDR, a free online textbook (not a Python library!) that provides a gentle introduction
to wireless communications and software-defined radio (SDR) using an abundance
of diagrams, animations, and Python code examples. From FFTs to filters to
digital modulation to receiving and transmitting from SDRs in Python, PySDR
has you covered!
Welcome to PySDR, a free online textbook (not a Python library!) that provides a gentle introduction to wireless communications and software-defined
radio (SDR) using an abundance of diagrams, animations, and Python code examples. From FFTs to filters to digital modulation to receiving and
transmitting from SDRs in Python, PySDR has you covered!
</h4>

<h4 style="line-height: 1.4">
The goal of PySDR is to increase accessibility to topics traditionally covered
in a math-intensive manner and within a relatively small set of universities.
All content used to generate PySDR is open source, and can be found
<a
class="reference external"
href="https://github.com/777arc/PySDR"
rel="noopener noreferrer"
target="_blank"
>here</a
>.
The goal of PySDR is to increase accessibility to topics traditionally covered in a math-intensive manner and within a relatively small set of
universities. All content used to generate PySDR is open source, and can be found
<a class="reference external" href="https://github.com/777arc/PySDR" rel="noopener noreferrer" target="_blank">here</a>.
</h4>

<h4>
See
<a class="reference internal" href="content/intro.html#intro-chapter"
><span class="std std-ref">Chapter 1: Introduction</span></a
>
<a class="reference internal" href="content/intro.html#intro-chapter"><span class="std std-ref">Chapter 1: Introduction</span></a>
for the textbook's purpose and target audience.
</h4>

<h4>
To get a quick taste of RF signal processing, try playing with the simulation below which shows the frequency and time domain of a signal
consisting of a tone and white Gaussian noise.
</h4>

<div>
<input type="range" min="-0.4" max="0.4" step="0.0001" value="0.01" class="slider" id="freq" />
<span id="freq_value"></span>
<label>[Hz] - Tone Frequency</label>
</div>
<div>
<input type="range" min="-20" max="10" step="0.1" value="0" class="slider" id="noise_ampl_dB" />
<span id="noise_value"></span>
<label>[dB] - Noise Amplitude</label>
</div>
<div>
<canvas width="2048" height="800" id="freq_plot" style="width: 512px; height: 200px; border: 0px; image-rendering: auto"></canvas>
</div>
<br />
<br />
<div>
<canvas width="2048" height="800" id="time_plot" style="width: 512px; height: 200px; border: 0px; image-rendering: auto"></canvas>
</div>

<script>
homepage_app();
</script>

DEMO
<br />
3 changes: 2 additions & 1 deletion conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,8 @@ def setup(app):
html_js_files = [
'js/beamforming_slider_app.js',
'js/FFT.js',
'js/cyclostationary_app.js'
'js/cyclostationary_app.js',
'js/homepage_app.js'
]

# Add any extra paths that contain custom files (such as robots.txt or
Expand Down

0 comments on commit ceefdf0

Please sign in to comment.