1 /**
2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 */
6 module my.signal_theory.simulate;
7 
8 import core.time : Duration, dur;
9 
10 @safe:
11 
12 struct Simulator {
13     import std.random;
14 
15     // how much we have drifted from the desired period.
16     Duration pv;
17 
18     // The current time.
19     Duration currTime = 16666667.dur!"nsecs";
20     // Simulated length of a tick
21     Duration simTick = 100.dur!"nsecs";
22 
23     // Last time the PV where updated.
24     Duration lastUpdate;
25 
26     // Next time the PID should be updated
27     Duration wakeupTime = 100.dur!"nsecs";
28 
29     // True if the inputFn+outputFn where called.
30     bool updated;
31 
32     // The desired period.
33     Duration period = 16666667.dur!"nsecs";
34     // The target time that should be as close as possible to currTime.
35     // starting at -2ms simulate a static offset
36     Duration targetTime = 14666667.dur!"nsecs";
37 
38     double gain0 = 0;
39 
40     MinstdRand0 g = MinstdRand0(42);
41     double posRn() {
42         return uniform01(g);
43     }
44 
45     double spreadRn() {
46         return uniform!"[]"(-1.0, 1.0, g);
47     }
48 
49     void tick(string TsUnit)(void delegate(Duration) @safe inputFn, double delegate() @safe outputFn) {
50         currTime += simTick;
51         updated = false;
52 
53         if (currTime < wakeupTime)
54             return;
55         pv = currTime - targetTime;
56 
57         inputFn(pv);
58 
59         double gain = spreadRn() * period.total!TsUnit / 10000;
60 
61         if (posRn > 0.8) {
62             gain0 = spreadRn * period.total!TsUnit / 1000;
63         }
64         gain += gain0;
65 
66         // simulate that high frequency jitter only sometimes occur.
67         if (posRn > 0.99) {
68             gain += posRn * period.total!TsUnit * 0.5;
69         }
70 
71         double output = outputFn();
72 
73         // the output is a time delay and thus can never be negative.
74         wakeupTime = currTime + period + (cast(long)(output + gain)).dur!TsUnit;
75 
76         lastUpdate = targetTime;
77         targetTime += period;
78         updated = true;
79     }
80 };