1 /**
2  * Definitions of presets and preset banks.
3  *
4  * Copyright: Copyright Auburn Sounds 2015 and later.
5  * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6  * Authors:   Guillaume Piolat
7  */
8 module dplug.client.preset;
9 
10 import core.stdc.stdlib: free;
11 
12 import std.range.primitives;
13 import std.math: isFinite;
14 
15 import dplug.core.vec;
16 import dplug.core.nogc;
17 import dplug.core.binrange;
18 
19 import dplug.client.client;
20 import dplug.client.params;
21 
22 // The current situation is quite complicated.
23 // 
24 // See https://github.com/AuburnSounds/Dplug/wiki/Roles-of-the-PresetBank
25 // for an explanation of the bizarre "PresetBank".
26 
27 /// Dplug chunks start with this.
28 enum uint DPLUG_MAGIC = 0xB20BA92;
29 
30 /// I can see no reason why Dplug shouldn't be able to maintain
31 /// state chunks backward-compatibility with older versions in the future.
32 /// And indeed the major version was bumped for "binState" change but was
33 /// compatible in the end, it could have been a minor change.
34 version(legacyBinState)
35 {
36     enum int DPLUG_SERIALIZATION_MAJOR_VERSION = 0;
37 }
38 else
39 {
40     enum int DPLUG_SERIALIZATION_MAJOR_VERSION = 1;
41 }
42 
43 /// This number will be incremented for every backward-compatible change
44 /// that is significant enough to bump a version number
45 enum int DPLUG_SERIALIZATION_MINOR_VERSION = 0;
46 
47 // Compilation failures for combinations that can't work
48 // binState needs VST2 chunk if VST2 is used.
49 version(legacyBinState)
50 {}
51 else
52 {
53     version(VST2)
54     {
55         version(legacyVST2Chunks) static assert(0, "bin state requires VST2 chunks for VST2!");
56     }
57 }
58 
59 /// A preset is a slot in a plugin preset list
60 final class Preset
61 {
62 public:
63 
64     this(const(char)[] name, 
65          const(float)[] normalizedParams, 
66          const(ubyte)[] stateData = null) nothrow @nogc
67     {
68         _name = name.mallocDupZ; // add terminal zero
69         _normalizedParams = normalizedParams.mallocDup;
70         if (stateData) 
71             _stateData = stateData.mallocDup;
72     }
73 
74     ~this() nothrow @nogc
75     {
76         clearName();
77         free(_normalizedParams.ptr);
78         clearData();
79     }
80 
81     void setNormalized(int paramIndex, float value) nothrow @nogc
82     {
83         _normalizedParams[paramIndex] = value;
84     }
85 
86     /// Returns: preset name. Guaranteed to be followed by a terminal
87     ///          zero for C compatibility.
88     const(char)[] name() pure nothrow @nogc
89     {
90         return _name;
91     }
92 
93     void setName(const(char)[] newName) nothrow @nogc
94     {
95         clearName();
96         _name = newName.mallocDupZ;
97     }
98 
99     version(legacyBinState)
100     {}
101     else
102     {
103         void setStateData(ubyte[] data) nothrow @nogc
104         {
105             clearData();
106             _stateData = data.mallocDup;
107         }
108     }
109 
110     void saveFromHost(Client client) nothrow @nogc
111     {
112         auto params = client.params();
113         foreach(size_t i, param; params)
114         {
115             _normalizedParams[i] = param.getNormalized();
116         }
117 
118         version(legacyBinState)
119         {}
120         else
121         {{
122             clearData(); // Forget possible existing _stateData
123             Vec!ubyte chunk;
124             client.saveState(chunk);
125             _stateData = chunk.releaseData;
126         }}
127     }
128 
129     // TODO: should this return an error if any encountered?
130     void loadFromHost(Client client) nothrow @nogc
131     {
132         auto params = client.params();
133         foreach(size_t i, param; params)
134         {
135             if (i < _normalizedParams.length)
136                 param.setFromHost(_normalizedParams[i]);
137             else
138             {
139                 // this is a new parameter that old presets don't know, set default
140                 param.setFromHost(param.getNormalizedDefault());
141             }
142         }
143 
144         version(legacyBinState)
145         {}
146         else
147         {
148             // loadFromHost seems to best effort, no error reported on parse failure
149             client.loadState(_stateData); 
150         }
151     }
152 
153     static bool isValidNormalizedParam(float f) nothrow @nogc
154     {
155         return (isFinite(f) && f >= 0 && f <= 1);
156     }
157 
158     inout(float)[] getNormalizedParamValues() inout nothrow @nogc
159     {
160         return _normalizedParams;
161     }
162 
163     version(legacyBinState)
164     {}
165     else
166     {
167         ubyte[] getStateData() nothrow @nogc
168         {
169             return _stateData;
170         }
171     }
172 
173 private:
174     char[] _name;
175     float[] _normalizedParams;
176     ubyte[] _stateData;
177 
178     void clearName() nothrow @nogc
179     {
180         free(_name.ptr);
181         _name = null;
182     }
183 
184     void clearData() nothrow @nogc
185     {
186         if (_stateData !is null)
187         {
188             free(_stateData.ptr);
189             _stateData = null;
190         }
191     }
192 }
193 
194 /// A preset bank is a collection of presets
195 final class PresetBank
196 {
197 public:
198 nothrow:
199 @nogc:
200 
201     // Extends an array or Preset
202     Vec!Preset presets;
203 
204     // Create a preset bank
205     // Takes ownership of this slice, which must be allocated with `malloc`,
206     // containing presets allocated with `mallocEmplace`.
207     this(Client client, Preset[] presets_)
208     {
209         _client = client;
210 
211         // Copy presets to own them
212         presets = makeVec!Preset(presets_.length);
213         foreach(size_t i; 0..presets_.length)
214             presets[i] = presets_[i];
215 
216         // free input slice with `free`
217         free(presets_.ptr);
218 
219         _current = 0;
220     }
221 
222     ~this()
223     {
224         // free all presets
225         foreach(p; presets)
226         {
227             // if you hit a break-point here, maybe your
228             // presets weren't allocated with `mallocEmplace`
229             p.destroyFree();
230         }
231     }
232 
233     inout(Preset) preset(int i) inout
234     {
235         return presets[i];
236     }
237 
238     int numPresets()
239     {
240         return cast(int)presets.length;
241     }
242 
243     int currentPresetIndex()
244     {
245         return _current;
246     }
247 
248     Preset currentPreset()
249     {
250         int ind = currentPresetIndex();
251         if (!isValidPresetIndex(ind))
252             return null;
253         return presets[ind];
254     }
255 
256     bool isValidPresetIndex(int index)
257     {
258         return index >= 0 && index < numPresets();
259     }
260 
261     // Save current state to current preset. This updates the preset bank to reflect the state change.
262     // This will be unnecessary once we haver internal preset management.
263     void putCurrentStateInCurrentPreset()
264     {
265         presets[_current].saveFromHost(_client);
266     }
267 
268     void loadPresetByNameFromHost(string name)
269     {
270         foreach(size_t index, preset; presets)
271             if (preset.name == name)
272                 loadPresetFromHost(cast(int)index);
273     }
274 
275     void loadPresetFromHost(int index)
276     {
277         putCurrentStateInCurrentPreset();
278         presets[index].loadFromHost(_client);
279         _current = index;
280     }
281 
282     /// Enqueue a new preset and load it
283     void addNewDefaultPresetFromHost(string presetName)
284     {
285         Parameter[] params = _client.params;
286         float[] values = mallocSlice!float(params.length);
287         scope(exit) values.freeSlice();
288 
289         foreach(size_t i, param; _client.params)
290             values[i] = param.getNormalizedDefault();
291 
292         presets.pushBack(mallocNew!Preset(presetName, values));
293         loadPresetFromHost(cast(int)(presets.length) - 1);
294     }
295   
296     /// Gets a state chunk to save the current state.
297     /// The returned state chunk should be freed with `free`.
298     ubyte[] getStateChunkFromCurrentState()
299     {
300         auto chunk = makeVec!ubyte();
301         this.writeStateChunk(chunk);
302         return chunk.releaseData;
303     }
304 
305     /// Gets a state chunk to save the current state, but provide a `Vec` to append to.
306     ///
307     /// Existing `chunk` content is preserved and appended to.
308     ///
309     /// This is faster than allocating a new state chunk everytime.
310     void appendStateChunkFromCurrentState(ref Vec!ubyte chunk)
311     {
312         this.writeStateChunk(chunk);
313     }
314 
315     /// Gets a state chunk that would be the current state _if_
316     /// preset `presetIndex` was made current first. So it's not
317     /// changing the client state.
318     /// The returned state chunk should be freed with `free()`.
319     ubyte[] getStateChunkFromPreset(int presetIndex)
320     {
321         auto chunk = makeVec!ubyte();
322         this.writePresetChunkData(chunk, presetIndex);
323         return chunk.releaseData;
324     }
325 
326     /// Loads a chunk state, update current state.
327     /// Return: *err set to true in case of error.
328     void loadStateChunk(const(ubyte)[] chunk, bool* err)
329     {
330         int mVersion = checkChunkHeader(chunk, err);
331         if (*err)
332             return;
333 
334         // This avoid to overwrite the preset 0 while we modified preset N
335         int presetIndex = chunk.popLE!int(err);
336         if (*err)
337             return;
338 
339         if (!isValidPresetIndex(presetIndex))
340         {
341             *err = true; // Invalid preset index in state chunk
342             return;
343         }
344         else
345             _current = presetIndex;
346 
347         version(legacyBinState)
348         {
349             loadChunkV1(chunk, err);
350             if (*err)
351                 return;
352         }
353         else
354         {
355             // Binary state future tag.
356             if (mVersion == 0)
357             {
358                 loadChunkV1(chunk, err);
359                 if (*err)
360                     return;
361             }
362             else
363             {
364                 loadChunkV2(chunk, err);
365                 if (*err)
366                     return;
367             }
368         }
369 
370         *err = false;
371     }
372 
373 private:
374     Client _client;
375     int _current; // should this be only in VST client?
376 
377     // Loads chunk without binary save state
378     void loadChunkV1(ref const(ubyte)[] chunk, bool* err) nothrow @nogc
379     {
380         // Load parameters values
381         auto params = _client.params();
382         int numParams = chunk.popLE!int(err); if (*err) return;
383         foreach(int i; 0..numParams)
384         {
385             float normalized = chunk.popLE!float(err); if (*err) return;
386             if (i < params.length)
387                 params[i].setFromHost(normalized);
388         }
389         *err = false;
390     }
391 
392     version(legacyBinState)
393     {}
394     else
395     {
396         // Loads chunk with binary save state
397         void loadChunkV2(ref const(ubyte)[] chunk, bool* err) nothrow @nogc
398         {
399             // Load parameters values
400             auto params = _client.params();
401             int numParams = chunk.popLE!int(err); if (*err) return;
402             foreach(int i; 0..numParams)
403             {
404                 float normalized = chunk.popLE!float(err); if (*err) return;
405                 if (i < params.length)
406                     params[i].setFromHost(normalized);
407             }
408 
409             int dataLength = chunk.popLE!int(err); if (*err) return;
410             bool parseSuccess = _client.loadState(chunk[0..dataLength]);
411             if (!parseSuccess)
412             {
413                 *err = true; // Invalid user-defined state chunk
414                 return;
415             }
416 
417             *err = false;
418         }
419     }
420 
421     version(legacyBinState)
422     {
423         // Writes chunk with V1 method
424         void writeStateChunk(ref Vec!ubyte chunk) nothrow @nogc
425         {
426             writeChunkHeader(chunk, 0);
427             auto params = _client.params();
428             chunk.writeLE!int(_current);
429             chunk.writeLE!int(cast(int)params.length);
430             foreach(param; params)
431                 chunk.writeLE!float(param.getNormalized());
432         }
433     }
434     else
435     {
436         // Writes preset chunk with V2 method
437         void writeStateChunk(ref Vec!ubyte chunk) nothrow @nogc
438         {
439             writeChunkHeader(chunk, 1);
440 
441             auto params = _client.params();
442 
443             chunk.writeLE!int(_current);
444 
445             chunk.writeLE!int(cast(int)params.length);
446             foreach(param; params)
447                 chunk.writeLE!float(param.getNormalized());
448 
449             size_t chunkLenIndex = chunk.length;
450             chunk.writeLE!int(0); // temporary chunk length value
451 
452             size_t before = chunk.length;
453             _client.saveState(chunk); // append state to chunk
454             size_t after = chunk.length;
455 
456             // Write actual value for chunk length.
457             ubyte[] indexPos = chunk.ptr[chunkLenIndex.. chunkLenIndex+4];
458             indexPos.writeLE!int(cast(int)(after - before));
459         }
460     }
461 
462     version(legacyBinState)
463     {
464         // Writes chunk with V1 method
465         void writePresetChunkData(ref Vec!ubyte chunk, int presetIndex) nothrow @nogc
466         {
467             writeChunkHeader(chunk, 0);
468 
469             auto p = preset(presetIndex);
470             chunk.writeLE!int(presetIndex);
471 
472             chunk.writeLE!int(cast(int)p._normalizedParams.length);
473             foreach(param; p._normalizedParams)
474                 chunk.writeLE!float(param);
475         }
476     }
477     else
478     {
479         // Writes preset chunk with V2 method
480         void writePresetChunkData(ref Vec!ubyte chunk, int presetIndex) nothrow @nogc
481         {
482             writeChunkHeader(chunk, 1);
483 
484             auto p = preset(presetIndex);
485             chunk.writeLE!int(presetIndex);
486 
487             chunk.writeLE!int(cast(int)p._normalizedParams.length);
488             foreach(param; p._normalizedParams)
489                 chunk.writeLE!float(param);
490 
491             chunk.writeLE!int(cast(int)p._stateData.length);
492             chunk.pushBack(p._stateData[0..$]);
493         }
494     }
495 
496     void writeChunkHeader(O)(auto ref O output, int version_=DPLUG_SERIALIZATION_MAJOR_VERSION) const @nogc if (isOutputRange!(O, ubyte))
497     {
498         // write magic number and dplug version information (not the tag version)
499         output.writeBE!uint(DPLUG_MAGIC);
500         output.writeLE!int(DPLUG_SERIALIZATION_MAJOR_VERSION);
501         output.writeLE!int(DPLUG_SERIALIZATION_MINOR_VERSION);
502 
503         // write plugin version
504         output.writeLE!int(_client.getPublicVersion().toAUVersion());
505     }
506 
507     // Return Dplug chunk major version, or -1 in case of error.
508     // *err set accordingly.
509     int checkChunkHeader(ref const(ubyte)[] input, bool* err) nothrow @nogc
510     {
511         // nothing to check with minor version
512         uint magic = input.popBE!uint(err); if (*err) return -1;
513         if (magic !=  DPLUG_MAGIC)
514         {
515             *err = true; // Can not load, magic number didn't match
516             return -1;
517         }
518 
519         // nothing to check with minor version
520         int dplugMajor = input.popLE!int(err); if (*err) return -1;
521         if (dplugMajor > DPLUG_SERIALIZATION_MAJOR_VERSION)
522         {
523             *err = true; // Can not load chunk done with a newer, incompatible dplug library
524             return -1;
525         }
526 
527         int dplugMinor = input.popLE!int(err); if (*err) return -1;
528         // nothing to check with minor version
529 
530         // TODO: how to handle breaking binary compatibility here?
531         int pluginVersion = input.popLE!int(err);
532         if (*err)
533             return -1;
534 
535         *err = false;
536         return dplugMajor;
537     }
538 }
539 
540 /// Loads an array of `Preset` from a FXB file content.
541 /// Gives ownership of the result, in a way that can be returned by `buildPresets`.
542 /// IMPORTANT: if you store your presets in FXB form, the following limitations 
543 ///   * One _add_ new parameters to the plug-in, no reorder or deletion
544 ///   * Don't remap the parameter (in a way that changes its normalized value)
545 /// They are the same limitations that exist in Dplug in minor plugin version.
546 ///
547 /// Params:
548 ///    maxCount Maximum number of presets to take, -1 for all of them
549 ///
550 /// Example:
551 ///       override Preset[] buildPresets()
552 ///       {
553 ///           return loadPresetsFromFXB(this, import("factory-presets.fxb"));
554 ///       }
555 ///
556 /// Note: It is **heavily recommended** to create the FXB chunk with the Dplug `presetbank` tool.
557 ///       Else it is unknown if it will work.
558 Preset[] loadPresetsFromFXB(Client client, string inputFBXData, int maxCount = -1) nothrow @nogc
559 {
560     ubyte[] fbxCopy = cast(ubyte[]) mallocDup(inputFBXData);
561     const(ubyte)[] inputFXB = fbxCopy;
562     scope(exit) free(fbxCopy.ptr);
563 
564     Vec!Preset result = makeVec!Preset();
565 
566     static int CCONST(int a, int b, int c, int d) pure nothrow @nogc
567     {
568         return (a << 24) | (b << 16) | (c << 8) | (d << 0);
569     }
570 
571     void parse(bool* err)
572     {
573         uint bankChunkID;
574         uint bankChunkLen;
575         inputFXB.readRIFFChunkHeader(bankChunkID, bankChunkLen, err); if (*err) return;
576 
577         if (bankChunkID != CCONST('C', 'c', 'n', 'K'))
578         {
579             *err = true;
580             return;
581         }
582 
583         inputFXB.skipBytes(bankChunkLen, err); if (*err) return;
584         uint fbxChunkID = inputFXB.popBE!uint(err); if (*err) return;
585 
586         if (fbxChunkID != CCONST('F', 'x', 'B', 'k'))
587         {
588             *err = true;
589             return;
590         }
591         inputFXB.skipBytes(4, err); if (*err) return; // parse fxVersion
592 
593         // if uniqueID has changed, then the bank is not compatible and should error
594         char[4] uid = client.getPluginUniqueID();
595         uint uidChunk = inputFXB.popBE!uint(err); if (*err) return;
596         if (uidChunk != CCONST(uid[0], uid[1], uid[2], uid[3])) 
597         {
598             *err = true; // chunk is not for this plugin
599             return;
600         }
601 
602         // fxVersion. We ignore it, since compat is supposed
603         // to be encoded in the unique ID already
604         inputFXB.popBE!uint(err); if (*err) return;
605 
606         int numPresets = inputFXB.popBE!int(err); if (*err) return;
607         if ((maxCount != -1) && (numPresets > maxCount))
608             numPresets = maxCount;
609         if (numPresets < 1)
610         {
611             *err = true; // no preset in bank, probably not what you want
612             return;
613         }
614 
615         inputFXB.skipBytes(128, err); if (*err) return;
616 
617         // Create presets
618         for(int presetIndex = 0; presetIndex < numPresets; ++presetIndex)
619         {
620             Preset p = client.makeDefaultPreset();
621             uint presetChunkID;
622             uint presetChunkLen;
623             inputFXB.readRIFFChunkHeader(presetChunkID, presetChunkLen, err); if (*err) return;
624             if (presetChunkID != CCONST('C', 'c', 'n', 'K')) 
625             {
626                 *err = true;
627                 return;
628             }
629             inputFXB.skipBytes(presetChunkLen, err); if (*err) return;
630 
631             presetChunkID = inputFXB.popBE!uint(err); if (*err) return;
632             if (presetChunkID != CCONST('F', 'x', 'C', 'k'))
633             {
634                 *err = true;
635                 return;
636             }
637             int presetVersion = inputFXB.popBE!uint(err); if (*err) return;
638             if (presetVersion != 1)
639             {
640                 *err = true;
641                 return;
642             }
643 
644             uint uidChunk2 = inputFXB.popBE!uint(err); if (*err) return;
645             if (uidChunk2 != CCONST(uid[0], uid[1], uid[2], uid[3])) 
646             {
647                 *err = true;
648                 return;
649             }
650 
651             // fxVersion. We ignore it, since compat is supposed
652             // to be encoded in the unique ID already
653             inputFXB.skipBytes(4, err); if (*err) return;
654 
655             int numParams = inputFXB.popBE!int(err); if (*err) return;
656             if (numParams < 0)
657             {
658                 *err = true;
659                 return;
660             }
661 
662             // parse name
663             char[28] nameBuf;
664             int nameLen = 28;
665             foreach(nch; 0..28)
666             {
667                 char c = cast(char) inputFXB.popBE!ubyte(err); if (*err) return;
668                 nameBuf[nch] = c;
669                 if (c == '\0' && nameLen == 28) 
670                     nameLen = nch;
671             }
672             p.setName(nameBuf[0..nameLen]);
673 
674             // parse parameter normalized values
675             int paramRead = numParams;
676             if (paramRead > cast(int)(client.params.length))
677                 paramRead = cast(int)(client.params.length);
678             for (int param = 0; param < paramRead; ++param)
679             {
680                 float pval = inputFXB.popBE!float(err); if (*err) return;
681                 p.setNormalized(param, pval);
682             }
683 
684             // skip excess parameters (this case should never happen so not sure if it's to be handled)
685             for (int param = paramRead; param < numParams; ++param)
686             {
687                 inputFXB.skipBytes(4, err); if (*err) return;
688             }
689 
690             result.pushBack(p);
691         }
692     }
693 
694     bool err;
695     parse(&err);
696 
697     if (err)
698     {
699         // Your preset file for the plugin is not meant to be invalid, so this is a bug.
700         // If you fail here, parsing has created an `error()` call.
701         assert(false); 
702     }
703 
704     return result.releaseData();
705 }