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 }