1 /*
2     Copyright © 2022, Inochi2D Project
3     Distributed under the 2-Clause BSD License, see LICENSE file.
4     
5     Authors: Luna Nielsen
6 */
7 module inochi2d.core.automation;
8 import inochi2d.math.serialization;
9 import inochi2d;
10 
11 /**
12     Automation binding
13 */
14 struct AutomationBinding {
15     
16     /**
17         Used for serialization.
18         Name of parameter
19     */
20     string paramId;
21 
22     /**
23         Parameter to bind to
24     */
25     Parameter param;
26 
27     /**
28         Axis to bind to
29         0 = X
30         1 = Y
31     */
32     int axis;
33 
34     /**
35         Min/max range of binding
36     */
37     vec2 range;
38 
39     /**
40         Gets the value at the specified axis
41     */
42     float getAxisValue() {
43         switch(axis) {
44             case 0:
45                 return param.value.x;
46             case 1:
47                 return param.value.y;
48             default: return float.nan;
49         }
50     }
51 
52     /**
53         Sets axis value (WITHOUT REMAPPING)
54     */
55     void setAxisValue(float value) {
56         switch(axis) {
57             case 0:
58                 param.value.x = value;
59                 break;
60             case 1:
61                 param.value.y = value;
62                 break;
63             default: assert(0);
64         }
65     }
66 
67     /**
68         Sets axis value (WITHOUT REMAPPING)
69     */
70     void addAxisOffset(float value) {
71         param.pushIOffsetAxis(axis, value);
72     }
73 
74     /**
75         Serializes a parameter
76     */
77     void serialize(S)(ref S serializer) {
78         auto state = serializer.objectBegin;
79             serializer.putKey("param");
80             serializer.putValue(param.name);
81             serializer.putKey("axis");
82             serializer.putValue(axis);
83             serializer.putKey("range");
84             range.serialize(serializer);
85         serializer.objectEnd(state);
86     }
87 
88     /**
89         Deserializes a parameter
90     */
91     SerdeException deserializeFromFghj(Fghj data) {
92         data["param"].deserializeValue(this.paramId);
93         data["axis"].deserializeValue(this.axis);
94         this.range.deserialize(data["axis"]);
95         return null;
96     }
97 
98     void reconstruct(Puppet puppet) { }
99 
100     void finalize(Puppet puppet) {
101         foreach(ref parameter; puppet.parameters) {
102             if (parameter.name == paramId) {
103                 param = parameter;
104                 return;
105             }
106         }
107     }
108 
109 }
110 
111 class Automation {
112 private:
113     @Ignore
114     Puppet parent;
115 
116 protected:
117 
118     @Ignore
119     AutomationBinding[] bindings;
120 
121     /**
122         Helper function to remap range from 0.0-1.0
123         to min-max
124     */
125     final
126     float remapRange(float value, vec2 range) {
127         return range.x + value * (range.y - range.x);
128     }
129 
130     /**
131         Called on update to update a single binding.
132 
133         Use currTime() to get the current time
134         Use deltaTime() to get delta time
135         Use binding.range to get the range to apply the automation within.
136     */
137     void onUpdate() { }
138 
139     void serializeSelf(ref InochiSerializer serializer) { }
140     void deserializeSelf(Fghj data) { }
141 
142 public:
143     /**
144         Human readable name of automation
145     */
146     string name;
147 
148     /**
149         Whether the automation is enabled
150     */
151     bool enabled = true;
152 
153     /**
154         Type ID of the automation
155     */
156     string typeId;
157 
158     /**
159         Instantiates a new Automation
160     */
161     this(Puppet parent) {
162         this.parent = parent;
163     }
164 
165     /**
166         Adds a binding
167     */
168     void bind(AutomationBinding binding) {
169         this.bindings ~= binding;
170     }
171 
172 
173     void reconstruct(Puppet puppet) {
174         foreach(ref binding; bindings.dup) {
175             binding.reconstruct(parent);
176         }
177     }
178 
179     /**
180         Finalizes the loading of the automation
181     */
182     final void finalize(Puppet parent) {
183         this.parent = parent;
184         foreach(ref binding; bindings) {
185             binding.finalize(parent);
186         }
187     }
188 
189     /**
190         Updates and applies the automation to all the parameters
191         that this automation is bound to
192     */
193     final void update() {
194         if (!enabled) return;
195         this.onUpdate();
196     }
197 
198     /**
199         Serializes a parameter
200     */
201     void serialize(S)(ref S serializer) {
202         auto state = serializer.objectBegin;
203             serializer.putKey("type");
204             serializer.serializeValue(typeId);
205             serializer.putKey("name");
206             serializer.serializeValue(name);
207             serializer.putKey("bindings");
208             serializer.serializeValue(bindings);
209             this.serializeSelf(serializer);
210         serializer.objectEnd(state);
211     }
212 
213     /**
214         Deserializes a parameter
215     */
216     SerdeException deserializeFromFghj(Fghj data) {
217         data["name"].deserializeValue(this.name);
218         data["bindings"].deserializeValue(this.bindings);
219         this.deserializeSelf(data);
220         return null;
221     }
222 }
223 
224 //
225 //  SERIALIZATION SHENNANIGANS
226 //
227 
228 private {
229     Automation delegate(Puppet parent)[string] typeFactories;
230 }
231 
232 void inRegisterAutomationType(T)() if (is(T : Automation)) {
233     import std.traits : getUDAs;
234     typeFactories[getUDAs!(T, TypeId)[0].id] = (Puppet parent) {
235         return new T(parent);
236     };
237 }
238 
239 /**
240     Instantiates automation
241 */
242 Automation inInstantiateAutomation(string id, Puppet parent) {
243     return typeFactories[id](parent);
244 }
245 
246 /**
247     Gets whether a node type is present in the factories
248 */
249 bool inHasAutomationType(string id) {
250     return (id in typeFactories) !is null;
251 }
252 
253 mixin template InAutomation(T) {
254     static this() {
255         inRegisterAutomationType!(T);
256     }
257 }