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