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.

See - Chapter 1: Introduction + Chapter 1: Introduction for the textbook's purpose and target audience.


- - - - -
- +
+ + + +
+
+ + + +
+
+
+
+ +
+ + + + -
\ 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 @@

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

- +


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