summaryrefslogtreecommitdiff
path: root/web-timeplot/src/data/synthetic-wave-source.js
blob: df533197315cde51ed78ea72798fc1b116f6d18b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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.sourceType = 'synthetic-wave';
        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;
    }
}