1 module pixelperfectengine.audio.base.config; 2 3 import sdlang; 4 5 import pixelperfectengine.audio.base.handler; 6 import pixelperfectengine.audio.base.modulebase; 7 8 import collections.commons : defaultHash; 9 10 import std.algorithm.searching : countUntil; 11 import std.array : split; 12 import std.conv : to; 13 14 /** 15 * Module and audio routin configurator. 16 * Loads an SDL file, then configures the modules and sets up their routing, presets, etc. 17 * See `modulesetup.md` on documentation about how the format works internally. 18 */ 19 public class ModuleConfig { 20 /** 21 * Defines a routing node. 22 * Can contain multiple inputs and outputs. 23 */ 24 protected static struct RoutingNode { 25 string name; 26 string[] inputs; 27 string[] outputs; 28 bool opEquals(const RoutingNode other) const @nogc @safe pure nothrow { 29 return this.name == other.name; 30 } 31 ///Returns true if input is found in the routing node. 32 bool hasInput(string s) { 33 return countUntil(inputs, s) != -1; 34 } 35 ///Returns true if output is found in the routing node. 36 bool hasOutput(string s) { 37 return countUntil(outputs, s) != -1; 38 } 39 } 40 ///Registered output channel names. 41 protected static immutable string[] outChannelNames = 42 ["outputL", "outputR", "surroundL", "surroundR", "center", "lowfreq"]; 43 ///Stores most of the document data here when uncompiled. 44 protected Tag root; 45 ///The target for audio handling. 46 protected ModuleManager manager; 47 ///Routing nodes that have been parsed so far. 48 protected RoutingNode[] rns; 49 ///The audio modules stored by this configuration. 50 public AudioModule[] modules; 51 ///Track routing for MIDI devices. 52 public uint[] midiRouting; 53 ///Group identifiers for tracks. 54 public ubyte[] midiGroups; 55 ///The names of the modules. 56 public string[] modNames; 57 /** 58 * Loads an audio configuration, and parses it. Does not automatically compile it. 59 * Params: 60 * src: the text of the cconfig file. 61 * manager: the ModuleManager, that will handle audio capabilities. 62 */ 63 public this(string src, ModuleManager manager) { 64 root = parseSource(src); 65 this.manager = manager; 66 } 67 /** 68 * Creates an empty audio configuration, e.g. for editors. 69 * Params: 70 * manager: the ModuleManager, that will handle audio capabilities. 71 */ 72 public this(ModuleManager manager) { 73 this.manager = manager; 74 root = new Tag(null); 75 } 76 /** 77 * Loads a configuration file from text. 78 * Params: 79 * src = the text of the configuration file. 80 */ 81 public void loadConfig(string src) @trusted { 82 root = parseSource(src); 83 } 84 /** 85 * Loads a configuration file from file 86 * Params: 87 * path = Path to the file. 88 */ 89 public void loadConfigFromFile(string path) { 90 import std.stdio : File; 91 File f = File(path); 92 char[] c; 93 c.length = cast(size_t)f.size(); 94 f.rawRead(c); 95 loadConfig(c.idup); 96 } 97 /** 98 * Saves the configuration into a file. 99 * Params: 100 * path = the path of the file. 101 */ 102 public void save(string path) { 103 import std.file : write; 104 write(path, root.toSDLDocument()); 105 } 106 /** 107 * Compiles the current configuration, then configures the modules accordingly. 108 * Params: 109 * isRunning = If true, then the audio thread will be suspended on the duration of the configuration. 110 */ 111 public void compile(bool isRunning) { 112 rns.length = 0; 113 modules.length = 0; 114 modNames.length = 0; 115 midiRouting.length = 0; 116 midiGroups.length = 0; 117 if (isRunning) 118 manager.suspendAudioThread(); 119 foreach (Tag t0; root.tags) { 120 switch (t0.name) { 121 case "module": 122 string modName = t0.values[1].get!string; 123 AudioModule currMod; 124 switch (t0.values[0].get!string) { 125 case "qm816": 126 import pixelperfectengine.audio.modules.qm816; 127 currMod = new QM816(); 128 break; 129 case "pcm8": 130 import pixelperfectengine.audio.modules.pcm8; 131 currMod = new PCM8(); 132 break; 133 case "delaylines": 134 import pixelperfectengine.audio.modules.delaylines; 135 currMod = new DelayLines(t0.values[2].get!int(), t0.values[3].get!int()); 136 break; 137 default: 138 break; 139 } 140 modules ~= currMod; 141 modNames ~= modName; 142 foreach (Tag t1; t0.tags) { 143 switch (t1.name) { 144 case "loadSample": 145 const string dpkSource = t1.getAttribute!string("dpk", null); 146 loadAudioFile(currMod, t1.values[1].get!int(), t1.values[0].get!string(), dpkSource); 147 break; 148 case "waveformSlice": 149 currMod.waveformSlice(t1.values[0].get!int, t1.values[1].get!int, t1.values[2].get!int, t1.values[3].get!int); 150 break; 151 case "presetRecall": 152 const int presetID = t1.values[0].get!int(); 153 //const string presetName = t1.getAttribute("name", string.init); 154 foreach (Tag t2; t1.tags) { 155 uint paramID; 156 if (t2.values[0].peek!string) { 157 paramID = defaultHash(t2.values[0].get!string); 158 } else { 159 paramID = cast(uint)(t2.values[0].get!long); 160 } 161 if (t2.values[1].peek!string) { 162 currMod.writeParam_string(presetID, paramID, t2.values[1].get!string); 163 } else if (t2.values[1].peek!long) { 164 currMod.writeParam_long(presetID, paramID, t2.values[1].get!long); 165 } else if (t2.values[1].peek!int) { 166 currMod.writeParam_int(presetID, paramID, t2.values[1].get!int); 167 } else if (t2.values[1].peek!double) { 168 currMod.writeParam_double(presetID, paramID, t2.values[1].get!double); 169 } else if (t2.values[1].peek!bool) { 170 currMod.writeParam_int(presetID, paramID, t2.values[1].get!bool ? 1 : 0); 171 } 172 } 173 break; 174 default: 175 break; 176 } 177 } 178 break; 179 case "route": 180 ptrdiff_t nRoutNode = countUntil!("a.name == b")(rns, t0.values[1].get!string()); 181 if (nRoutNode == -1) { //If routing node doesn't exist yet, create it! 182 rns ~= RoutingNode(t0.values[1].get!string(), [t0.values[0].get!string()], [t0.values[1].get!string()]); 183 } else { //If does, then just add a new input. 184 rns[nRoutNode].inputs ~= t0.values[0].get!string(); 185 } 186 break; 187 case "node": 188 RoutingNode node = RoutingNode(t0.values[0].get!string(), [], []); 189 foreach (Tag t1; t0.expectTag("input").tags) { 190 node.inputs ~= t1.getValue!string(); 191 } 192 foreach (Tag t1; t0.expectTag("output").tags) { 193 node.outputs ~= t1.getValue!string(); 194 } 195 if (node.inputs.length == 0 && node.outputs.length == 0) //Node is invalidated, remove it 196 t0.remove(); 197 else if (node.inputs.length && node.outputs.length) //Only use nodes that have valid inputs and outputs 198 rns ~= node; 199 break; 200 case "midiRouting": 201 foreach (Tag t1 ; t0.tags) { 202 midiRouting ~= t1.values[0].get!int; 203 midiGroups ~= cast(ubyte)(t1.getAttribute!int("group", 0)); 204 } 205 /* midiRouting ~= t0.values[0].get!int; 206 midiGroups ~= cast(ubyte)(t0.getAttribute!int("group", 0)); */ 207 break; 208 default: 209 break; 210 } 211 } 212 manager.setBuffers(rns.length); 213 foreach (size_t i, AudioModule am; modules) { 214 string[] modIns = am.getInfo.inputChNames; 215 string[] modOuts = am.getInfo.outputChNames; 216 size_t[] inBufs, outBufs; 217 ubyte[] inChs, outChs; 218 foreach (size_t k, string key; modIns) { 219 for (size_t j ; j < rns.length ; j++) { 220 if (rns[j].hasOutput(modNames[i] ~ ":" ~ key)) { 221 inBufs ~= j; 222 inChs ~= cast(ubyte)k; 223 break; 224 } 225 } 226 } 227 foreach (size_t k, string key; modOuts) { 228 for (size_t j ; j < rns.length ; j++) { 229 if (rns[j].hasInput(modNames[i] ~ ":" ~ key)) { 230 outBufs ~= j; 231 outChs ~= cast(ubyte)k; 232 break; 233 } 234 } 235 } 236 manager.addModule(am, inBufs, inChs, outBufs, outChs); 237 } 238 if (isRunning) 239 manager.runAudioThread(); 240 } 241 /** 242 * Loads an audio file into the given audio module. 243 * This function is external, with the intent of being able to alter default voicebanks for e.g. mods, 244 * localizations, etc. 245 * Params: 246 * modID = The module identifier string, usually its name within the configuration. 247 * waveID = The waveform ID. Conflicting waveforms will be automatically overwitten. 248 * path = Path of the file to be loaded. 249 * dataPak = If a DataPak is used, then the path to it must be specified there, otherwise it's null. 250 */ 251 public void loadAudioFile(string modID, int waveID, string path, string dataPak = null) { 252 import std.path : extension; 253 loadAudioFile(modules[countUntil(modNames, modID)], waveID, path, dataPak); 254 } 255 /** 256 * Loads an audio file into the given audio module. 257 * Params: 258 * mod = The module, that needs the waveform data. 259 * waveID = The waveform ID. Conflicting waveforms will be automatically overwitten. 260 * path = Path of the file to be loaded. 261 * dataPak = If a DataPak is used, then the path to it must be specified there, otherwise it's null. 262 */ 263 protected void loadAudioFile(AudioModule mod, int waveID, string path, string dataPak = null) { 264 import std.path : extension; 265 switch (extension(path)) { 266 case ".wav": 267 loadWaveFile(mod, waveID, path, dataPak); 268 break; 269 case ".voc", ".adp", ".ad4": 270 loadVocFile(mod, waveID, path, dataPak); 271 break; 272 default: 273 break; 274 } 275 } 276 /** 277 * Loads a Microsoft Wave (wav) file into a module. 278 * Params: 279 * mod = The module, that needs the waveform data. 280 * waveID = The waveform ID. Conflicting waveforms will be automatically overwitten. 281 * path = Path of the file to be loaded. 282 * dataPak = If a DataPak is used, then the path to it must be specified there, otherwise it's null. 283 */ 284 protected void loadWaveFile(AudioModule mod, int waveID, string path, string dataPak = null) { 285 import pixelperfectengine.system.wavfile; 286 WavFile f = new WavFile(path); 287 mod.waveformDataReceive(waveID, f.rawData[52..$].dup, 288 WaveFormat(f.header.samplerate, f.header.bytesPerSecond, f.header.format, f.header.channels, 289 f.header.bytesPerSample, f.header.bitsPerSample)); 290 } 291 /** 292 * Loads a Dialogic ADPCM (voc/af4) file into a module. 293 * Params: 294 * mod = The module, that needs the waveform data. 295 * waveID = The waveform ID. Conflicting waveforms will be automatically overwitten. 296 * path = Path of the file to be loaded. 297 * dataPak = If a DataPak is used, then the path to it must be specified there, otherwise it's null. 298 */ 299 protected void loadVocFile(AudioModule mod, int waveID, string path, string dataPak = null) { 300 import std.stdio : File; 301 import std.path : extension; 302 File f = File(path); 303 ubyte[] buf; 304 buf.length = cast(size_t)f.size(); 305 f.rawRead(buf); 306 const int samplerate = extension(path) == ".voc" || extension(path) == ".adp" ? 8000 : 36_000; 307 mod.waveformDataReceive(waveID, buf, WaveFormat(samplerate, samplerate / 2, AudioFormat.DIALOGIC_OKI_ADPCM, 1, 1, 4)); 308 } 309 /** 310 * Edits a preset parameter. 311 * Params: 312 * modID = The module identifier string, usually its name within the configuration. 313 * presetID = The preset identifier number. 314 * paramID = The ID of the parameter, either the type of a string, or a long. 315 * value = The value to be written into the preset. 316 * backup = Previous value of the parameter, otherwise left unaltered. 317 * name = Optional name of the preset. 318 */ 319 public void editPresetParameter(string modID, int presetID, Value paramID, Value value, ref Value backup, 320 string name = null) { 321 foreach (Tag t0 ; root.tags) { 322 if (t0.name == "module") { 323 if (t0.values[1].get!string == modID) { 324 foreach (Tag t1 ; t0.tags) { 325 if (t1.name == "presetRecall" && t1.values[0].peek!int && t1.values[0].get!int() == presetID) { 326 foreach (Tag t2 ; t1.tags) { 327 328 if (t2.values[0] == paramID) { 329 backup = t2.values[1]; 330 t2.values[1] = value; 331 return; 332 } 333 334 } 335 new Tag(t1, null, null, [Value(paramID), Value(value)]); 336 return; 337 } 338 } 339 Attribute[] attr; 340 if (name.length) 341 attr ~= new Attribute("name", Value(name)); 342 Tag t_1 = new Tag(t0, null, "presetRecall", [Value(presetID)], attr); 343 new Tag(t_1, null, null, [paramID, value]); 344 return; 345 } 346 } 347 } 348 349 } 350 /** 351 * Adds a routing to the audio configuration. Automatically creates nodes if names are not recognized either as 352 * module ports or audio outputs. 353 * Params: 354 * from = Source of the routing. Can be either a module output or a node. 355 * to = Destination of the routing. Can be either a module input, a node, or an audio output. 356 */ 357 public void addRouting(string from, string to) { 358 const bool fromModule = from.split(":").length == 2; 359 const bool toModule = to.split(":").length == 2 || countUntil(outChannelNames, to) != -1; 360 if (fromModule && toModule) { 361 new Tag(root, null, "route", [Value(from), Value(to)]); 362 } else if (fromModule) { 363 foreach (Tag t0; root.tags) { 364 if (t0.name == "node" && t0.getValue!string == to) { 365 new Tag(t0.expectTag("input"), null, null, [Value(from)]); 366 return; 367 } 368 } 369 new Tag(root, null, "node", [Value(to)], null, 370 [ 371 new Tag(null, "input", null, null, [ 372 new Tag(null, null, from) 373 ]), 374 new Tag(null, "output") 375 ]); 376 } else { //(toModule) 377 foreach (Tag t0; root.tags) { 378 if (t0.name == "node" && t0.getValue!string == from) { 379 new Tag(t0.expectTag("output"), null, null, [Value(to)]); 380 return; 381 } 382 } 383 new Tag(root, null, "node", [Value(from)], null, 384 [ 385 new Tag(null, "input"), 386 new Tag(null, "output", null, null, [ 387 new Tag(null, null, to) 388 ]) 389 ]); 390 } 391 } 392 /** 393 * Removes a routing from the audio configuration. 394 * Params: 395 * from = Source of the routing. Can be either a module output or a node. 396 * to = Destination of the routing. Can be either a module input, a node, or an audio output. 397 * Returns: True if routing is found and then removed, false otherwise. 398 */ 399 public bool removeRouting(string from, string to) { 400 const bool fromModule = from.split(":").length == 2; 401 const bool toModule = to.split(":").length == 2 || countUntil(outChannelNames, to) != -1; 402 if (fromModule && toModule) { 403 foreach (Tag t0; root.tags) { 404 if (t0.name == "route") { 405 if (t0.values[0] == from && t0.values[1] == to) { 406 t0.remove(); 407 return true; 408 } 409 } 410 } 411 } else if (fromModule) { 412 foreach (Tag t0; root.tags) { 413 if (t0.name == "node" && t0.getValue!string == to) { 414 Tag t1 = t0.expectTag("input"); 415 foreach (Tag t2 ; t1.tags()) 416 if (t2.getValue!string == from) { 417 t2.remove(); 418 return true; 419 } 420 } 421 } 422 } else { //(toModule) 423 foreach (Tag t0; root.tags) { 424 if (t0.name == "node" && t0.getValue!string == from) { 425 Tag t1 = t0.expectTag("output"); 426 foreach (Tag t2 ; t1.tags()) 427 if (t2.getValue!string == to) { 428 t2.remove(); 429 return true; 430 } 431 } 432 } 433 } 434 return false; 435 } 436 /** 437 * Returns the routing table of this audio configuration as an array of pairs of strings. 438 */ 439 public string[2][] getRoutingTable() { 440 string[2][] result; 441 foreach (Tag t0 ; root.tags()) { 442 switch (t0.name) { 443 case "route": 444 result ~= [t0.values[0].get!string, t0.values[1].get!string]; 445 break; 446 case "node": 447 const string nodeName = t0.values[0].get!string; 448 foreach (Tag t1; t0.expectTag("input").tags) { 449 result ~= [t1.values[0].get!string, nodeName]; 450 } 451 foreach (Tag t1; t0.expectTag("output").tags) { 452 result ~= [nodeName, t1.values[0].get!string]; 453 } 454 break; 455 default: 456 break; 457 } 458 } 459 return result; 460 } 461 /** 462 * Adds a new module to the configuration. 463 * Params: 464 * type = Type of the module. 465 * name = Name and ID of the module. 466 */ 467 public void addModule(string type, string name) { 468 switch (type) { 469 case "delaylines1010": 470 new Tag(root, null, "module", [Value("delaylines"), Value(name), Value(1024), Value(1024)]); 471 break; 472 case "delaylines1012": 473 new Tag(root, null, "module", [Value("delaylines"), Value(name), Value(1024), Value(4096)]); 474 break; 475 case "delaylines1212": 476 new Tag(root, null, "module", [Value("delaylines"), Value(name), Value(4096), Value(4096)]); 477 break; 478 default: 479 new Tag(root, null, "module", [Value(type), Value(name)]); 480 break; 481 } 482 } 483 /** 484 * Adds a module from backup. 485 * Params: 486 * backup = The module tag containing all the info associated with the module. 487 */ 488 public void addModule(Tag backup) { 489 root.add(backup); 490 } 491 /** 492 * Renames a module. 493 * Params: 494 * oldName = The current name of the module. 495 * newName = the desired name of the module. 496 */ 497 public void renameModule(string oldName, string newName) { 498 foreach (Tag t0 ; root.tags) { 499 if (t0.name == "module") { 500 if (t0.values[1] == oldName) { 501 t0.values[1] = Value(newName); 502 return; 503 } 504 } 505 } 506 } 507 /** 508 * Removes a module from the configuration. 509 * Params: 510 * name = Name/ID of the module. 511 * Returns: An SDL tag containing all the information related to the module, or null if ID is invalid. 512 */ 513 public Tag removeModule(string name) { 514 foreach (Tag t0 ; root.tags) { 515 if (t0.name == "module") { 516 if (t0.values[1] == name) { 517 t0.remove(); 518 return t0; 519 } 520 } 521 } 522 return null; 523 } 524 /** 525 * Returns the module with the given `name`, or null if not found. 526 */ 527 public AudioModule getModule(string name) @safe { 528 foreach (size_t i, string n; modNames) { 529 if (n == name) 530 return modules[i]; 531 } 532 return null; 533 } 534 ///Returns the number of the module, or -1 if the module name does not exist. 535 sizediff_t getModuleNum(string name) @safe const { 536 foreach (size_t i, string n; modNames) { 537 if (n == name) 538 return i; 539 } 540 return -1; 541 } 542 /** 543 * Returns a list of modules. 544 */ 545 public string[2][] getModuleList() { 546 string[2][] result; 547 foreach (Tag t0 ; root.tags) { 548 if (t0.name == "module") { 549 result ~= [t0.values[0].get!string, t0.values[1].get!string]; 550 } 551 } 552 return result; 553 } 554 /** 555 * Removes a preset from the configuration. 556 * Params: 557 * modID = Module name/ID. 558 * presetID = Preset ID. 559 * Returns: The tag containing all the info related to the preset for backup, or null if module and/or 560 * preset ID is invalid. 561 */ 562 public Tag removePreset(string modID, int presetID) { 563 foreach (Tag t0 ; root.tags) { 564 if (t0.name == "module") { 565 if (t0.values[1] == modID) { 566 foreach (Tag t1; t0.tags) { 567 if (t1.name == "presetRecall" && t1.getValue!int == presetID) { 568 return t1.remove; 569 } 570 } 571 } 572 } 573 } 574 return null; 575 } 576 /** 577 * Adds a preset to the configuration either from a backup or an import. 578 * Params: 579 * modID = Module name/ID. 580 * backup = The preset to be (re)added. 581 */ 582 public void addPreset(string modID, Tag backup) { 583 foreach (Tag t0 ; root.tags) { 584 if (t0.name == "module") { 585 if (t0.values[1] == modID) { 586 t0.add(backup); 587 return; 588 } 589 } 590 } 591 } 592 /** 593 * Returns the list of presets associated with the module identified by `modID`. 594 */ 595 public auto getPresetList(string modID) { 596 struct PresetData { 597 string name; 598 int id; 599 } 600 PresetData[] result; 601 foreach (Tag t0 ; root.tags) { 602 if (t0.name == "module") { 603 if (t0.values[1] == modID) { 604 foreach (Tag t1; t0.tags) { 605 if (t1.name == "presetRecall") { 606 result ~= PresetData(t1.getAttribute!string("name"), t1.expectValue!int); 607 } 608 } 609 return result; 610 } 611 } 612 } 613 return result; 614 } 615 /** 616 * Adds a wave file to the given module. 617 * Params: 618 * path = the path of the wave file. 619 * modID = the ID of the module, that will use the wave file. 620 * waveID = the ID of the waveform to be loaded. 621 * dpkPath = path to the DataPak file if there's one. 622 * name = name of the waveform if there's one specified. 623 * Returns: The tag that was added to the configuration file, or null on error. 624 */ 625 public Tag addWaveFile(string path, string modID, int waveID, string dpkPath, string name) { 626 foreach (Tag t0 ; root.tags) { 627 if (t0.name == "module") { 628 if (t0.values[1] == modID) { 629 Attribute[] attr; 630 if (name.length) { 631 attr ~= new Attribute("name", Value(name)); 632 } 633 if (dpkPath.length) { 634 attr ~= new Attribute("dpkPath", Value(dpkPath)); 635 } 636 Tag t1 = new Tag(t0, null, "loadSample", [Value(path), Value(waveID)], attr); 637 return t1; 638 } 639 } 640 } 641 return null; 642 } 643 /** 644 * Creates a waveform from another by slicing. 645 * Params: 646 * modID = ID of the target module. 647 * waveID = ID of the new waveform. 648 * src = ID of the source waveform. 649 * pos = Position of the beginning of the slice. 650 * len = Length of the slice. 651 * Returns: The tag that was added to the configuration file, or null on error. 652 */ 653 public Tag addWaveSlice(string modID, int waveID, int src, int pos, int len, string name) { 654 foreach (Tag t0 ; root.tags) { 655 if (t0.name == "module") { 656 if (t0.values[1] == modID) { 657 Attribute[] attr; 658 if (name.length) { 659 attr ~= new Attribute("name", Value(name)); 660 } 661 Tag t1 = new Tag(t0, null, "waveformSlice", [Value(waveID), Value(src), Value(pos), Value(len)], attr); 662 return t1; 663 } 664 } 665 } 666 return null; 667 } 668 /** 669 * Adds a waveform data tag from `backup` to the module described by `modID`. 670 */ 671 public void addWaveFromBackup(string modID, Tag backup) { 672 foreach (Tag t0 ; root.tags) { 673 if (t0.name == "module") { 674 if (t0.values[1] == modID) { 675 t0.add(backup); 676 return; 677 } 678 } 679 } 680 } 681 /** 682 * Removes a waveform identified by `waveID` from the module described by `modID`, 683 * then returns the configuration tag as backup. Returns null if module and/or waveform not found. 684 */ 685 public Tag removeWave(string modID, int waveID) { 686 foreach (Tag t0 ; root.tags) { 687 if (t0.name == "module") { 688 if (t0.values[1] == modID) { 689 foreach (Tag t1 ; t0.tags) { 690 switch (t1.name) { 691 case "loadSample": 692 if (t1.values[1].get!int == waveID) 693 return t1.remove(); 694 break; 695 case "waveformSlice": 696 if (t1.values[0].get!int == waveID) 697 return t1.remove(); 698 break; 699 default: break; 700 } 701 } 702 } 703 } 704 } 705 return null; 706 } 707 /** 708 * Renames a wave file definition. 709 * Does not affect internal waves if they're overridden. 710 * Params: 711 * modID = module ID. 712 * waveID = Waveform ID. 713 * newName = The new name for the waveform. 714 * Returns: The old name if there's any. 715 */ 716 public string renameWave(string modID, int waveID, string newName) { 717 string oldName; 718 foreach (Tag t0 ; root.tags) { 719 if (t0.name == "module") { 720 if (t0.values[1] == modID) { 721 foreach (Tag t1 ; t0.tags) { 722 void doThing() { 723 if (t1.getAttribute!string("name")) { 724 oldName = t1.getAttribute!string("name"); 725 t1.attributes["name"][0].remove; 726 } 727 if (newName.length) { 728 t1.add(new Attribute("name", Value(newName))); 729 } 730 } 731 switch (t1.name) { 732 case "loadSample": 733 if (t1.values[1].get!int == waveID) { 734 doThing(); 735 return oldName; 736 } 737 break; 738 case "waveformSlice": 739 if (t1.values[0].get!int == waveID) { 740 doThing(); 741 return oldName; 742 } 743 break; 744 default: break; 745 } 746 } 747 } 748 } 749 } 750 return oldName; 751 } 752 /** 753 * Returns the waveform list belonging to the audio module identified by `modID`. 754 */ 755 public WaveFileData[] getWaveFileList(string modID) { 756 WaveFileData[] result; 757 foreach (Tag t0 ; root.tags) { 758 if (t0.name == "module") { 759 if (t0.values[1] == modID) { 760 foreach (Tag t1 ; t0.tags) { 761 switch (t1.name) { 762 case "loadSample": 763 result ~= WaveFileData(t1.values[1].get!int, t1.getAttribute!string("dpkPath"), t1.values[0].get!string, 764 t1.getAttribute!string("name"), false, false); 765 break; 766 case "waveformSlice": 767 result ~= WaveFileData(t1.values[0].get!int, null, "SLICE FROM:" ~ to!string(t1.values[1].get!int), 768 t1.getAttribute!string("name"), true, false); 769 break; 770 default: break; 771 } 772 } 773 AudioModule m = getModule(modID); 774 if (m !is null) { 775 uint[] internalIDList = m.getInternalWaveformIDList(); 776 string[] internalNameList = m.getInternalWaveformNames(); 777 assert (internalIDList.length == internalNameList.length); 778 for (int i ; i < internalIDList.length ; i++) { 779 result ~= WaveFileData(internalIDList[i], null, "INTERNAL", internalNameList[i], false, true); 780 } 781 } 782 return result; 783 } 784 } 785 } 786 return result; 787 } 788 ///Creates a MIDI routing table from the supplied values. 789 public void setMIDIrouting(uint[] table) { 790 Tag t0 = root.getTag("midiRouting"); 791 if (t0 is null) { 792 t0 = new Tag(root, null, "midiRouting"); 793 } 794 if (t0.tags.length) { 795 foreach (Tag t1 ; t0.tags) { 796 t1.remove(); 797 } 798 } 799 foreach (uint i ; table) { 800 new Tag(t0, null, null, [Value(cast(int)i)]); 801 } 802 } 803 } 804 /** 805 * Implements a structure for wave file data storage. 806 */ 807 struct WaveFileData { 808 int id; //Waveform ID. 809 string dpkPath; //DataPak file path if exists, null otherwise. 810 string path; //Path to the source file, null if slice of internal. 811 string name; //Name of the waveform. 812 bool isSlice; //True if waveform is a slice of another one. 813 bool isInternal; //True if waveform is internal to the module. 814 }