From 5b247a03191bb133f7fbf8ae943fa1a556c0eb56 Mon Sep 17 00:00:00 2001
From: Marc Lichtman
Date: Sat, 30 Nov 2024 21:24:27 -0500
Subject: [PATCH 1/9] wip
---
_templates/homepage.html | 56 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 55 insertions(+), 1 deletion(-)
diff --git a/_templates/homepage.html b/_templates/homepage.html
index 9a6b713..43512d7 100644
--- a/_templates/homepage.html
+++ b/_templates/homepage.html
@@ -46,4 +46,58 @@
-DEMO
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
From f05e726eea54d9ff022437129517244168ed767b Mon Sep 17 00:00:00 2001
From: Marc Lichtman
Date: Sat, 30 Nov 2024 22:56:37 -0500
Subject: [PATCH 2/9] basic plots working
---
_templates/homepage.html | 180 +++++++++++++++++++++++----------------
conf.py | 3 +-
2 files changed, 108 insertions(+), 75 deletions(-)
diff --git a/_templates/homepage.html b/_templates/homepage.html
index 43512d7..70d4eb2 100644
--- a/_templates/homepage.html
+++ b/_templates/homepage.html
@@ -6,98 +6,130 @@ PySDR: A Guide to SDR and DSP using Python
Dr. Marc Lichtman
-
-
+
pysdr@ vt. edu
- 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!
- 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
- here .
+ 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
+ here .
-
-
-
-
-
-
+
+
+
+ [Hz] - Tone Frequency
+
+
+
+
+ [dB] - Noise Amplitude
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/conf.py b/conf.py
index 900c348..aad9b8d 100644
--- a/conf.py
+++ b/conf.py
@@ -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
From bdf4d6a8369ca3ae1799c6c85e2b88337c796a38 Mon Sep 17 00:00:00 2001
From: Marc Lichtman
Date: Thu, 5 Dec 2024 02:24:40 -0500
Subject: [PATCH 3/9] fixed graphics lag
---
_templates/homepage.html | 35 +++++++++++++++++++++++------------
1 file changed, 23 insertions(+), 12 deletions(-)
diff --git a/_templates/homepage.html b/_templates/homepage.html
index 70d4eb2..fdd728e 100644
--- a/_templates/homepage.html
+++ b/_templates/homepage.html
@@ -76,11 +76,12 @@
}
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);
- // FFT
const fft_obj = new FFT(N);
const signal_fft = fft_obj.createComplexArray();
fft_obj.transform(signal_fft, signal);
@@ -100,36 +101,46 @@
// Plot freq
const canvas = document.getElementById("freq_plot");
- const ctx = canvas.getContext("2d");
+ 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.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // clears canvas
+ ctx.fillStyle = "white";
+ 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, signal_fft_mag_shifted[0]);
+ ctx.moveTo(0, Math.floor(-7 * signal_fft_mag_shifted_dB[0] + 200));
for (let i = 1; i < N; i++) {
- ctx.lineTo(i * 2, -7*signal_fft_mag_shifted_dB[i] + 200); // -1* to flip y-axis
- ctx.stroke();
+ ctx.lineTo(i * 2, Math.floor(-7 * signal_fft_mag_shifted_dB[i] + 200)); // -1* to flip y-axis
}
+ ctx.stroke();
// Plot time
const canvas_time = document.getElementById("time_plot");
- const ctx_time = canvas_time.getContext("2d");
+ const ctx_time = canvas_time.getContext("2d", { alpha: false });
+
ctx_time.setTransform(1, 0, 0, 1, 0, 0); // resets transform
- ctx_time.clearRect(0, 0, ctx_time.canvas.width, ctx_time.canvas.height); // clears canvas
+ ctx_time.fillStyle = "white";
+ 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, signal_fft_mag_shifted[0]);
+ ctx_time.moveTo(0, Math.floor(-20 * signal[0]));
for (let i = 1; i < N; i++) {
- ctx_time.lineTo(i * 2, -20*signal[i*2]); // -1* to flip y-axis
- ctx_time.stroke();
+ ctx_time.lineTo(i * 2, Math.floor(-20 * signal[i * 2])); // -1* to flip y-axis
+ }
+ 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();
- }, 200); // in ms
+ }, update_period); // in ms
From 81f8136590017f5222e5043b5515e50cd3837342 Mon Sep 17 00:00:00 2001
From: Marc Lichtman
Date: Thu, 5 Dec 2024 02:36:50 -0500
Subject: [PATCH 4/9] x axis for freq plot
---
_templates/homepage.html | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)
diff --git a/_templates/homepage.html b/_templates/homepage.html
index fdd728e..7b285d1 100644
--- a/_templates/homepage.html
+++ b/_templates/homepage.html
@@ -41,10 +41,12 @@
[dB] - Noise Amplitude
-
+
+
+
-
+
@@ -105,6 +107,7 @@
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();
@@ -115,6 +118,30 @@
}
ctx.stroke();
+ // Freq axis
+ ctx.setTransform(1, 0, 0, 1, 0, 0); // resets transform
+ ctx.beginPath();
+ ctx.strokeStyle = "black";
+ ctx.moveTo(0, ctx.canvas.height - 60);
+ ctx.lineTo(ctx.canvas.width, ctx.canvas.height - 60);
+ ctx.stroke();
+
+ // Freq 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";
+ for (let i = 0; i < 11; i++) {
+ ctx.moveTo(ctx.canvas.width / 10 * i, ctx.canvas.height - 80);
+ ctx.lineTo(ctx.canvas.width / 10 * i, ctx.canvas.height - 60);
+ ctx.fillText(Math.round((i - 5)*0.1*100)/100, (ctx.canvas.width / 10 * i - 0)*0.975, ctx.canvas.height - 5);
+ }
+ ctx.fillText("Hz", ctx.canvas.width / 2 + 5, ctx.canvas.height - 5);
+ ctx.stroke();
+
+
// Plot time
const canvas_time = document.getElementById("time_plot");
const ctx_time = canvas_time.getContext("2d", { alpha: false });
From 573fd6031cb21ac40ad8a4af9d051578cb817379 Mon Sep 17 00:00:00 2001
From: Marc Lichtman
Date: Thu, 5 Dec 2024 02:42:22 -0500
Subject: [PATCH 5/9] time plot x axis
---
_templates/homepage.html | 42 +++++++++++++++++++++++++++-------------
1 file changed, 29 insertions(+), 13 deletions(-)
diff --git a/_templates/homepage.html b/_templates/homepage.html
index 7b285d1..c31bf00 100644
--- a/_templates/homepage.html
+++ b/_templates/homepage.html
@@ -118,14 +118,6 @@
}
ctx.stroke();
- // Freq axis
- ctx.setTransform(1, 0, 0, 1, 0, 0); // resets transform
- ctx.beginPath();
- ctx.strokeStyle = "black";
- ctx.moveTo(0, ctx.canvas.height - 60);
- ctx.lineTo(ctx.canvas.width, ctx.canvas.height - 60);
- ctx.stroke();
-
// Freq ticks and labels
ctx.setTransform(1, 0, 0, 1, 0, 0); // resets transform
ctx.beginPath();
@@ -133,21 +125,26 @@
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 - 80);
- ctx.lineTo(ctx.canvas.width / 10 * i, ctx.canvas.height - 60);
- ctx.fillText(Math.round((i - 5)*0.1*100)/100, (ctx.canvas.width / 10 * i - 0)*0.975, ctx.canvas.height - 5);
+ 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 - 5);
+ 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();
-
// 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();
@@ -158,6 +155,25 @@
}
ctx_time.stroke();
+ // Time 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();
+
//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");
From b21fcf7062e0fc8de3b15062cd1e89669b364c3b Mon Sep 17 00:00:00 2001
From: Marc Lichtman
Date: Thu, 5 Dec 2024 02:51:05 -0500
Subject: [PATCH 6/9] y axis
---
_templates/homepage.html | 53 +++++++++++++++++++++++++++++++++++++---
1 file changed, 49 insertions(+), 4 deletions(-)
diff --git a/_templates/homepage.html b/_templates/homepage.html
index c31bf00..0a8e779 100644
--- a/_templates/homepage.html
+++ b/_templates/homepage.html
@@ -118,7 +118,7 @@
}
ctx.stroke();
- // Freq ticks and labels
+ // Freq x-axis ticks and labels
ctx.setTransform(1, 0, 0, 1, 0, 0); // resets transform
ctx.beginPath();
ctx.strokeStyle = "black";
@@ -138,6 +138,25 @@
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 });
@@ -149,13 +168,13 @@
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]));
+ 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])); // -1* to flip y-axis
+ ctx_time.lineTo(i * 2, Math.floor(-20 * signal[i * 2] - 40)); // -1* to flip y-axis
}
ctx_time.stroke();
- // Time ticks and labels
+ // Time x-axis ticks and labels
ctx_time.setTransform(1, 0, 0, 1, 0, 0); // resets transform
ctx_time.beginPath();
ctx_time.strokeStyle = "black";
@@ -174,6 +193,32 @@
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");
From 5391723574091414f067994057f30eea500023ef Mon Sep 17 00:00:00 2001
From: Marc Lichtman
Date: Thu, 5 Dec 2024 02:54:59 -0500
Subject: [PATCH 7/9] noise scaling
---
_templates/homepage.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/_templates/homepage.html b/_templates/homepage.html
index 0a8e779..e2d052b 100644
--- a/_templates/homepage.html
+++ b/_templates/homepage.html
@@ -59,7 +59,7 @@
// complex AWGN
let noise_ampl_dB = document.getElementById("noise_ampl_dB").value;
- document.getElementById("noise_value").innerHTML = noise_ampl_dB;
+ 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());
From a5a0df833b9a75500cbd1b2b06927bdc46cb6a64 Mon Sep 17 00:00:00 2001
From: Marc Lichtman
Date: Thu, 5 Dec 2024 03:04:30 -0500
Subject: [PATCH 8/9] integrated
---
_templates/homepage.html | 192 ++-------------------------------------
1 file changed, 9 insertions(+), 183 deletions(-)
diff --git a/_templates/homepage.html b/_templates/homepage.html
index e2d052b..7974022 100644
--- a/_templates/homepage.html
+++ b/_templates/homepage.html
@@ -28,7 +28,11 @@
Chapter 1: Introduction
for the textbook's purpose and target audience.
-
+
+
+ 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.
+
@@ -41,194 +45,16 @@
[dB] - Noise Amplitude
-
+
-
+
-
-
-
-
-
From 9f8c729d0b3f6c9c2b037d0a082389288c94a4a5 Mon Sep 17 00:00:00 2001
From: Marc Lichtman
Date: Thu, 5 Dec 2024 03:06:51 -0500
Subject: [PATCH 9/9] added js
---
.gitignore | 1 +
_static/js/homepage_app.js | 177 +++++++++++++++++++++++++++++++++++++
2 files changed, 178 insertions(+)
create mode 100644 _static/js/homepage_app.js
diff --git a/.gitignore b/.gitignore
index ab3cba5..bce0922 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@ distribute-*
env/
build/
_build/
+OLD_build/
dist/
Sphinx.egg-info/
doc/_build/
diff --git a/_static/js/homepage_app.js b/_static/js/homepage_app.js
new file mode 100644
index 0000000..7851756
--- /dev/null
+++ b/_static/js/homepage_app.js
@@ -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
+}