diff options
Diffstat (limited to 'web-timeplot/src/data')
| -rw-r--r-- | web-timeplot/src/data/base-source.js | 21 | ||||
| -rw-r--r-- | web-timeplot/src/data/source-registry.js | 41 | ||||
| -rw-r--r-- | web-timeplot/src/data/synthetic-wave-source.js | 86 |
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; + } +} |
