summaryrefslogtreecommitdiff
path: root/web-timeplot/src/data
diff options
context:
space:
mode:
Diffstat (limited to 'web-timeplot/src/data')
-rw-r--r--web-timeplot/src/data/base-source.js21
-rw-r--r--web-timeplot/src/data/source-registry.js41
-rw-r--r--web-timeplot/src/data/synthetic-wave-source.js86
3 files changed, 148 insertions, 0 deletions
diff --git a/web-timeplot/src/data/base-source.js b/web-timeplot/src/data/base-source.js
new file mode 100644
index 0000000..55dbdc3
--- /dev/null
+++ b/web-timeplot/src/data/base-source.js
@@ -0,0 +1,21 @@
+export class BaseSource {
+ constructor(config = {}) {
+ this.config = { ...config };
+ this.running = false;
+ }
+
+ start() {
+ this.running = true;
+ }
+
+ stop() {
+ this.running = false;
+ }
+
+ updateConfig(nextConfig) {
+ this.config = {
+ ...this.config,
+ ...nextConfig,
+ };
+ }
+}
diff --git a/web-timeplot/src/data/source-registry.js b/web-timeplot/src/data/source-registry.js
new file mode 100644
index 0000000..06f5895
--- /dev/null
+++ b/web-timeplot/src/data/source-registry.js
@@ -0,0 +1,41 @@
+import { SyntheticWaveSource } from './synthetic-wave-source.js';
+
+export class SourceRegistry {
+ constructor(store, bus) {
+ this.store = store;
+ this.bus = bus;
+ this.sources = new Map([
+ ['synthetic-wave', new SyntheticWaveSource(store.getState().source)],
+ ]);
+ this.activeSource = this.sources.get(store.getState().source.activeId);
+ this.activeSource.start(store.getState().time.plotTimeMs);
+ }
+
+ syncFromState() {
+ const state = this.store.getState();
+ const nextSource = this.sources.get(state.source.activeId);
+
+ if (nextSource !== this.activeSource) {
+ this.activeSource?.stop();
+ this.activeSource = nextSource;
+ this.activeSource?.start(state.time.plotTimeMs);
+ }
+
+ this.activeSource?.updateConfig(state.source);
+ }
+
+ update(currentPlotTimeMs) {
+ if (!this.activeSource) {
+ return;
+ }
+
+ const points = this.activeSource.update(currentPlotTimeMs);
+ for (const point of points) {
+ this.bus.emit('data:point', point);
+ }
+ }
+
+ reset() {
+ this.activeSource?.reset(this.store.getState().time.plotTimeMs);
+ }
+}
diff --git a/web-timeplot/src/data/synthetic-wave-source.js b/web-timeplot/src/data/synthetic-wave-source.js
new file mode 100644
index 0000000..3cf7fb1
--- /dev/null
+++ b/web-timeplot/src/data/synthetic-wave-source.js
@@ -0,0 +1,86 @@
+import { BaseSource } from './base-source.js';
+
+function clamp(value, min, max) {
+ return Math.min(max, Math.max(min, value));
+}
+
+function createDeterministicNoise(seed) {
+ const x = Math.sin(seed * 12.9898) * 43758.5453;
+ return x - Math.floor(x);
+}
+
+export class SyntheticWaveSource extends BaseSource {
+ constructor(config = {}) {
+ super({
+ sampleRateHz: 60,
+ preset: 'telemetry',
+ amplitude: 1,
+ noise: 0.08,
+ ...config,
+ });
+ this.lastEmittedPlotTimeMs = 0;
+ }
+
+ start(startTimeMs = 0) {
+ super.start();
+ this.lastEmittedPlotTimeMs = startTimeMs;
+ }
+
+ stop() {
+ super.stop();
+ }
+
+ reset(startTimeMs = 0) {
+ this.lastEmittedPlotTimeMs = startTimeMs;
+ }
+
+ sampleValue(timeMs) {
+ const seconds = timeMs / 1000;
+ const amplitude = this.config.amplitude;
+ const noise = this.config.noise;
+ const grain = (createDeterministicNoise(timeMs * 0.017) - 0.5) * 2 * noise;
+
+ switch (this.config.preset) {
+ case 'chirp': {
+ const sweep = Math.sin(seconds * seconds * 1.4);
+ return amplitude * (0.7 * sweep + 0.3 * Math.sin(seconds * 7.5)) + grain;
+ }
+ case 'burst': {
+ const burstPhase = (seconds % 6) - 1.5;
+ const burst = Math.sin(seconds * 9.5) * Math.exp(-(burstPhase ** 2) * 0.8);
+ return amplitude * (0.45 * Math.sin(seconds * 2.1) + burst) + grain;
+ }
+ case 'telemetry':
+ default: {
+ const carrier = Math.sin(seconds * 2.2);
+ const secondary = 0.35 * Math.cos(seconds * 6.4 + Math.sin(seconds * 0.8));
+ const envelope = 0.15 * Math.sin(seconds * 0.33);
+ return amplitude * (carrier + secondary + envelope) + grain;
+ }
+ }
+ }
+
+ update(currentPlotTimeMs) {
+ if (!this.running) {
+ return [];
+ }
+
+ const intervalMs = 1000 / clamp(this.config.sampleRateHz, 1, 240);
+ if (currentPlotTimeMs < this.lastEmittedPlotTimeMs) {
+ this.lastEmittedPlotTimeMs = currentPlotTimeMs;
+ return [];
+ }
+
+ const points = [];
+ while (this.lastEmittedPlotTimeMs + intervalMs <= currentPlotTimeMs) {
+ this.lastEmittedPlotTimeMs += intervalMs;
+ points.push({
+ timeMs: this.lastEmittedPlotTimeMs,
+ value: this.sampleValue(this.lastEmittedPlotTimeMs),
+ sourceId: 'synthetic-wave',
+ });
+ }
+
+ return points;
+ }
+}