1 /*
2 Bank.cpp - Instrument Bank
3
4 Original ZynAddSubFX author Nasca Octavian Paul
5 Copyright (C) 2002-2005 Nasca Octavian Paul
6 Copyright 2009-2010, Alan Calvert
7 Copyright 2014-2021, Will Godfrey & others
8
9 This file is part of yoshimi, which is free software: you can redistribute
10 it and/or modify it under the terms of the GNU Library General Public
11 License as published by the Free Software Foundation; either version 2 of
12 the License, or (at your option) any later version.
13
14 yoshimi is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 FOR A PARTICULAR PURPOSE. See the GNU General Public License (version 2 or
17 later) for more details.
18
19 You should have received a copy of the GNU General Public License along with
20 yoshimi; if not, write to the Free Software Foundation, Inc., 51 Franklin
21 Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
23 This file is a derivative of a ZynAddSubFX original.
24
25 */
26
27 #include <set>
28 #include <list>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <dirent.h>
32 #include <sys/stat.h>
33 #include <iostream>
34
35 #include <sys/types.h>
36 #include <fcntl.h>
37 #include <unistd.h>
38 #include <errno.h>
39
40 #include <vector>
41 #include <algorithm>
42
43 #include "Misc/XMLwrapper.h"
44 #include "Misc/Config.h"
45 #include "ConfBuild.h"
46 #include "Misc/Bank.h"
47 #include "Misc/SynthEngine.h"
48 #include "Misc/TextMsgBuffer.h"
49 #include "Misc/FileMgrFuncs.h"
50 #include "Misc/FormatFuncs.h"
51
52 const int BANKS_VERSION = 10;
53
54 using file::make_legit_filename;
55 using file::isRegularFile;
56 using file::isDirectory;
57 using file::renameDir;
58 using file::createDir;
59 using file::deleteDir;
60 using file::listDir;
61 using file::copyDir;
62 using file::copyFile;
63 using file::renameFile;
64 using file::deleteFile;
65 using file::countDir;
66 using file::findLeafName;
67 using file::setExtension;
68 using file::extendLocalPath;
69 using file::loadText;
70 using file::saveText;
71
72 using func::asString;
73 using func::string2int;
74 using func::findSplitPoint;
75 using func::isDigits;
76
77 using std::list;
78 using std::to_string;
79 using std::string;
80 using std::cout;
81 using std::endl;
82
83
Bank(SynthEngine * _synth)84 Bank::Bank(SynthEngine *_synth) :
85 defaultinsname(string(" ")),
86 synth(_synth)
87 {
88 BanksVersion = 10;
89 InstrumentsInBanks = 0,
90 BanksInRoots = 0;
91 foundLocal = file::localDir() + "/found/";
92 }
93
94 extern SynthEngine *firstSynth;
95
getBankFileTitle(size_t root,size_t bank)96 string Bank::getBankFileTitle(size_t root, size_t bank)
97 {
98 return synth->makeUniqueName("Root " + asString(root) + ", Bank " + asString(bank) + " - " + getBankPath(root, bank));
99 }
100
101
getRootFileTitle(size_t root)102 string Bank::getRootFileTitle(size_t root)
103 {
104 return synth->makeUniqueName("Root " + asString(root) + " - " + getRootPath(root));
105 }
106
107
getType(unsigned int ninstrument,size_t bank,size_t root)108 int Bank::getType(unsigned int ninstrument, size_t bank, size_t root)
109 {
110 if (emptyslot(root, bank, ninstrument))
111 return -1;
112 return getInstrumentReference(root, bank, ninstrument).type;
113 }
114
115
116 // Get the name of an instrument from the bank
getname(unsigned int ninstrument,size_t bank,size_t root)117 string Bank::getname(unsigned int ninstrument, size_t bank, size_t root)
118 {
119 if (emptyslot(root, bank, ninstrument))
120 return defaultinsname;
121 return getInstrumentReference(root, bank, ninstrument).name;
122 }
123
124
125 // Get the numbered name of an instrument from the bank
getnamenumbered(unsigned int ninstrument,size_t bank,size_t root)126 string Bank::getnamenumbered(unsigned int ninstrument, size_t bank, size_t root)
127 {
128 if (emptyslot(root, bank, ninstrument))
129 return defaultinsname;
130 string strRet = asString(ninstrument + 1) + ". " + getname(ninstrument, bank, root);
131 return strRet;
132 }
133
134
135 // Changes the instrument name in place
setInstrumentName(const string & name,int slot,size_t bank,size_t root)136 int Bank::setInstrumentName(const string& name, int slot, size_t bank, size_t root)
137 {
138 string result;
139 string slotNum = to_string(slot + 1) + ". ";
140 bool fail = false;
141 if (emptyslot(root, bank, slot))
142 {
143 result = "No instrument on slot " + slotNum;
144 fail = true;
145 }
146 else if (!moveInstrument(slot, name, slot, bank, bank, root, root))
147 {
148 result = "Could not change name of slot " + slotNum;
149 fail = true;
150 }
151 else
152 result = slotNum + name;
153 int msgID = synth->textMsgBuffer.push(result);
154 if (fail)
155 msgID |= 0xFF0000;
156 return msgID;
157 }
158
159
160 // Changes the name and location of an instrument (and the filename)
moveInstrument(unsigned int ninstrument,const string & newname,int newslot,size_t oldBank,size_t newBank,size_t oldRoot,size_t newRoot)161 bool Bank::moveInstrument(unsigned int ninstrument, const string& newname, int newslot, size_t oldBank, size_t newBank, size_t oldRoot, size_t newRoot)
162 {
163 if (emptyslot(oldRoot, oldBank, ninstrument))
164 return false;
165
166 string newfilepath = getBankPath(newRoot, newBank);
167 if (newfilepath.at(newfilepath.size() - 1) != '/')
168 newfilepath += "/";
169
170 int slot = (newslot >= 0) ? newslot + 1 : ninstrument + 1;
171 string filename = "0000" + asString(slot);
172
173 filename = filename.substr(filename.size() - 4, 4) + "-" + newname + EXTEN::zynInst;
174 make_legit_filename(filename);
175
176 bool chk = false;
177 bool chk2 = false;
178 newfilepath += filename;
179 string oldfilepath = setExtension(getFullPath(oldRoot, oldBank, ninstrument), EXTEN::zynInst);
180 chk = renameFile(oldfilepath, newfilepath);
181
182 newfilepath = setExtension(newfilepath, EXTEN::yoshInst);
183 oldfilepath = setExtension(oldfilepath, EXTEN::yoshInst);
184 chk2 = renameFile(oldfilepath, newfilepath);
185
186 if (chk == false && chk2 == false)
187 {
188 synth->getRuntime().Log("failed changing " + oldfilepath + " to " + newfilepath + ": " + string(strerror(errno)));
189 return false;
190 }
191 InstrumentEntry &instrRef = getInstrumentReference(oldRoot, oldBank, ninstrument);
192 instrRef.name = newname;
193 instrRef.filename = filename;
194 return true;
195 }
196
197
198 // Check if there is no instrument on a slot from the bank
emptyslot(size_t rootID,size_t bankID,unsigned int ninstrument)199 bool Bank::emptyslot(size_t rootID, size_t bankID, unsigned int ninstrument)
200 {
201 if (roots.count(rootID) == 0 || roots [rootID].banks.count(bankID) == 0)
202 return true;
203 InstrumentEntry &instr = roots [rootID].banks [bankID].instruments [ninstrument];
204 if (!instr.used)
205 return true;
206 if (instr.name.empty() || instr.filename.empty())
207 return true;
208 return false;
209 }
210
211
212 // Removes the instrument from the bank
clearslot(unsigned int ninstrument,size_t rootID,size_t bankID)213 string Bank::clearslot(unsigned int ninstrument, size_t rootID, size_t bankID)
214 {
215 bool chk = true;
216 bool chk2 = true; // to stop complaints
217 if (emptyslot(rootID, bankID, ninstrument)) // this is not an error
218 return (". None found at slot " + to_string(ninstrument + 1));
219
220 string tmpfile = setExtension(getFullPath(rootID, bankID, ninstrument), EXTEN::yoshInst);
221 if (isRegularFile(tmpfile))
222 chk = deleteFile(tmpfile);
223
224 tmpfile = setExtension(tmpfile, EXTEN::zynInst);
225 if (isRegularFile(tmpfile))
226 chk2 = deleteFile(tmpfile);
227 string instName = getname(ninstrument, bankID, rootID);
228 string result;
229 if (chk && chk2)
230 {
231 deletefrombank(rootID, bankID, ninstrument);
232 result = "d ";
233 }
234 else
235 {
236 result = " FAILED Could not delete ";
237 if (chk && !chk2)
238 instName += EXTEN::zynInst;
239 else if (!chk && chk2)
240 instName += EXTEN::yoshInst;
241 /*
242 * done this way so that if only one type fails
243 * it is identified, but if both are present and
244 * can't be deleted it doesn't mark the extension.
245 */
246 }
247 return (result + "'" + instName + "' from slot " + to_string(ninstrument + 1));
248 }
249
250
savetoslot(size_t rootID,size_t bankID,int ninstrument,int npart)251 bool Bank::savetoslot(size_t rootID, size_t bankID, int ninstrument, int npart)
252 {
253 string filepath = getBankPath(rootID, bankID);
254 string name = synth->part[npart]->Pname;
255 if (filepath.at(filepath.size() - 1) != '/')
256 filepath += "/";
257 clearslot(ninstrument, rootID, bankID);
258 string filename = "0000" + asString(ninstrument + 1);
259 filename = filename.substr(filename.size() - 4, 4)
260 + "-" + name + EXTEN::zynInst;
261 make_legit_filename(filename);
262
263 string fullpath = filepath + filename;
264 bool ok1 = true;
265 bool ok2 = true;
266 int saveType = synth->getRuntime().instrumentFormat;
267 if (isRegularFile(fullpath))
268 {
269 if (!deleteFile(fullpath))
270 {
271 synth->getRuntime().Log("saveToSlot failed to unlink " + fullpath);
272 return false;
273 }
274 }
275 if (saveType & 1) // legacy
276 ok2 = synth->part[npart]->saveXML(fullpath, false);
277
278 fullpath = setExtension(fullpath, EXTEN::yoshInst);
279 if (isRegularFile(fullpath))
280 {
281 if (!deleteFile(fullpath))
282 {
283 synth->getRuntime().Log("saveToSlot failed to unlink " + fullpath);
284 return false;
285 }
286 }
287
288 if (saveType & 2) // Yoshimi format
289 ok1 = synth->part[npart]->saveXML(fullpath, true);
290 if (!ok1 || !ok2)
291 return false;
292
293 saveText(string(YOSHIMI_VERSION), filepath + EXTEN::validBank);
294 addtobank(rootID, bankID, ninstrument, filename, name);
295 return true;
296 }
297
298
299 //Gets a bank name
getBankName(int bankID,size_t rootID)300 string Bank::getBankName(int bankID, size_t rootID)
301 {
302 if (rootID > 0x7f)
303 rootID = synth->getRuntime().currentRoot;
304 if (roots [rootID].banks.count(bankID) == 0)
305 return "";
306 return string(roots [rootID].banks [bankID].dirname);
307 }
308
309
isDuplicateBankName(size_t rootID,const string & name)310 bool Bank::isDuplicateBankName(size_t rootID, const string& name)
311 {
312 for (int i = 0; i < MAX_BANK_ROOT_DIRS; ++i)
313 {
314 string check = getBankName(i,rootID);
315 if (check > "" && check == name)
316 return true;
317 //if (check > "")
318 //cout << check << endl;
319 }
320 return false;
321 }
322
323
324 // finds the number of instruments in a bank
getBankSize(int bankID,size_t rootID)325 int Bank::getBankSize(int bankID, size_t rootID)
326 {
327 int found = 0;
328
329 for (int i = 0; i < MAX_INSTRUMENTS_IN_BANK; ++ i)
330 if (!roots [rootID].banks [bankID].instruments [i].name.empty())
331 found += 1;
332 return found;
333 }
334
335
changeBankName(size_t rootID,size_t bankID,const string & newName)336 int Bank::changeBankName(size_t rootID, size_t bankID, const string& newName)
337 {
338 string filename = newName;
339 string oldName = getBankName(bankID, rootID);
340 make_legit_filename(filename);
341 string newfilepath = getRootPath(synth->getRuntime().currentRoot) + "/" + filename;
342 string reply = "";
343 bool failed = false;
344 if (!renameDir(getBankPath(synth->getRuntime().currentRoot,bankID), newfilepath))
345 {
346 reply = "Could not change bank '" + oldName + "' in root " + to_string(rootID);
347 failed = true;
348 }
349 else
350 {
351 roots [synth->getRuntime().currentRoot].banks [bankID].dirname = newName;
352 reply = "Changed " + oldName + " to " + newName;
353 }
354
355 int msgID = synth->textMsgBuffer.push(reply);
356 if (failed)
357 msgID |= 0xFF0000;
358 return msgID;
359 }
360
361
checkbank(size_t rootID,size_t banknum)362 void Bank::checkbank(size_t rootID, size_t banknum)
363 {
364 string bankdirname = getBankPath(rootID, banknum);
365
366 if (bankdirname.empty())
367 {
368 return;
369 }
370
371 string chkpath;
372 list<string> thisBank;
373 uint32_t found = listDir(&thisBank, bankdirname);
374 if (found == 0xffffffff)
375 {
376 synth->getRuntime().Log("Failed to open bank directory " + bankdirname);
377 thisBank.clear();
378 return;
379 }
380 if (found == 0)
381 {
382 roots [rootID].banks.erase(banknum);
383 thisBank.clear();
384 return;
385 }
386
387 string path = getBankPath(rootID, banknum) + "/";
388 // clear missing/removed entries
389 for (int pos = 0; pos < MAX_INSTRUMENTS_IN_BANK; ++pos)
390 {
391 if (!emptyslot(rootID, banknum, pos))
392 {
393 string chkpath = path + getInstrumentReference(rootID, banknum, pos).filename;
394 if (!isRegularFile(chkpath))
395 {
396 //cout << chkpath << endl;
397 getInstrumentReference(rootID, banknum, pos).clear();
398 }
399 }
400 }
401
402 for (list<string>::iterator it = thisBank.begin(); it != thisBank.end(); ++it)
403 {
404 string candidate = *it;
405
406 if (candidate.size() <= EXTEN::zynInst.size()) // at least a single char filename
407 *it = "";
408 else
409 {
410 chkpath = bankdirname + "/" + candidate;
411 if (!isRegularFile(chkpath))
412 *it = "";
413 else
414 {
415 string exten = file::findExtension(chkpath);
416 if (exten != EXTEN::yoshInst && exten != EXTEN::zynInst)
417 *it = "";
418 else
419 {
420 string instname = candidate.substr(0, candidate.size() - exten.size());
421 int instnum = -1;
422 splitNumFromName(instnum, instname);
423 if (instnum >= 0 && !emptyslot(rootID, banknum, instnum)) // a recognised location
424 {
425 int othernum = -1;
426 string othername = roots [rootID].banks [banknum].instruments [instnum].filename;
427 othername = othername.substr(0, othername.size() - exten.size());
428 splitNumFromName(othernum, othername);
429 if (instname == othername)
430 {
431 if (exten == EXTEN::yoshInst) // yoshiType takes priority
432 getInstrumentReference(rootID, banknum, instnum).yoshiType = true;
433 *it = "";
434 }
435 }
436 }
437 }
438 }
439 }
440
441 string candidate;
442 for (list<string>::iterator it = thisBank.begin(); it != thisBank.end(); ++it)
443 {
444 candidate = *it;
445
446 if (!candidate.empty())
447 {
448 // remove extension
449 string exten = file::findExtension(chkpath);
450 string instname = candidate.substr(0, candidate.size() - exten.size());
451 int instnum = -1;
452 splitNumFromName(instnum, instname);
453 addtobank(rootID, banknum, instnum, candidate, instname);
454 }
455 }
456
457 thisBank.clear();
458 }
459
460
461 // Makes current a bank directory
loadbank(size_t rootID,size_t banknum)462 bool Bank::loadbank(size_t rootID, size_t banknum)
463 {
464 string bankdirname = getBankPath(rootID, banknum);
465
466 if (bankdirname.empty())
467 return false;
468
469 roots [rootID].banks [banknum].instruments.clear();
470
471 string chkpath;
472 string candidate;
473 list<string> thisBank;
474 uint32_t found = listDir(&thisBank, bankdirname);
475 if (found == 0xffffffff)
476 {
477 synth->getRuntime().Log("Failed to open bank directory " + bankdirname);
478 return false;
479 }
480
481 if (bankdirname.at(bankdirname.size() - 1) != '/')
482 bankdirname += '/';
483 for (list<string>::iterator it = thisBank.begin(); it != thisBank.end(); ++ it)
484 {
485 candidate = *it;
486 if (candidate.size() <= (EXTEN::zynInst.size())) // at least a 1 char filename!
487 continue;
488
489 chkpath = bankdirname + candidate;
490 if (!isRegularFile(chkpath))
491 continue;
492
493 string exten = file::findExtension(chkpath);
494 if (exten != EXTEN::yoshInst && exten != EXTEN::zynInst)
495 continue;
496
497 if (exten == EXTEN::zynInst && isRegularFile(setExtension(chkpath, EXTEN::yoshInst)))
498 continue; // don't want .xiz if there is .xiy
499
500 // remove extension
501 string instname = candidate.substr(0, candidate.size() - exten.size());
502 int instnum = -1;
503 splitNumFromName(instnum, instname);
504 addtobank(rootID, banknum, instnum, candidate, instname);
505 InstrumentsInBanks += 1;
506 }
507 thisBank.clear();
508 return true;
509 }
510
511
512 // Creates an external bank and copies in the contents of the IDd one
exportBank(const string & exportdir,size_t rootID,unsigned int bankID)513 string Bank::exportBank(const string& exportdir, size_t rootID, unsigned int bankID)
514 {
515 string name = "";
516 string sourcedir = "";
517 bool ok = true;
518 if (roots.count(rootID) == 0)
519 {
520 name = "Root ID " + to_string(int(rootID)) + " doesn't exist";
521 ok = false;
522 }
523 if (ok && roots [rootID].banks.count(bankID) == 0)
524 {
525 name = "Bank " + to_string(bankID) + " is empty";
526 ok = false;
527 }
528 else
529 sourcedir = getRootPath(rootID) + "/" + getBankName(bankID, rootID);
530
531 if (ok && isDirectory(exportdir))
532 {
533 ok = false;
534 name = "Can't overwrite existing directory";
535 }
536 if (ok)
537 {
538 int result = createDir(exportdir);
539 if (result != 0)
540 {
541 name = "Can't create external bank " + findLeafName(exportdir);
542 ok = false;
543 }
544 else
545 {
546 uint32_t result = copyDir(sourcedir, exportdir, 0);
547
548 if (result != 0)
549 {
550 name = "Copied out " + to_string(result & 0xffff) + " files to " + exportdir + ". ";
551 result = result >> 16;
552 if (result > 0)
553 name += ("Failed to transfer" + to_string(result));
554 }
555 else
556 {
557 name = "to transfer to " + exportdir; // failed
558 ok = false;
559 }
560 }
561 }
562
563 if (ok)
564 name = " " + name; // need the extra space
565 else
566 name = " FAILED " + name;
567 return name;
568 }
569
570
571 // Creates a new bank and copies in the contents of the external one
importBank(string importdir,size_t rootID,unsigned int bankID)572 string Bank::importBank(string importdir, size_t rootID, unsigned int bankID)
573 {
574 string name = "";
575 bool ok = true;
576 bool partial = false;
577 if (roots.count(rootID) == 0)
578 {
579 name = "Root ID " + to_string(int(rootID)) + " doesn't exist";
580 ok = false;
581 }
582
583 if (ok && roots [rootID].banks.count(bankID) != 0)
584 {
585 name = "Bank " + to_string(bankID) + " already contains " + getBankName(bankID, rootID);
586 ok = false;
587 }
588
589 if (ok)
590 {
591 list<string> thisBank;
592 uint32_t found = listDir(&thisBank, importdir);
593 if (found == 0xffffffff)
594 {
595 synth->getRuntime().Log("Can't find " + importdir);
596 ok = false;
597 }
598 else
599 {
600 if (importdir.back() == '/')
601 importdir = importdir.substr(0, importdir.length() - 1);
602 string bankname = findLeafName(importdir);
603 int repeats = 0;
604 string suffix = "";
605 while (isDirectory(getRootPath(rootID) + "/" + bankname + suffix))
606 {
607 ++repeats;
608 suffix = "~" + to_string(repeats);
609 }
610 bankname += suffix;
611 if (!newIDbank(bankname, bankID, rootID))
612 {
613 name = "Can't create bank " + bankname + " in root " + getRootPath(rootID);
614 ok = false;
615 }
616 else
617 {
618 int count = 0;
619 int total = 0;
620 bool missing = false;
621 string externfile = getRootPath(rootID) + "/" + getBankName(bankID, rootID);
622 for (list<string>::iterator it = thisBank.begin(); it != thisBank.end(); ++ it)
623 {
624 string nextfile = *it;
625 if (nextfile.rfind(EXTEN::validBank) != string::npos)
626 continue; // new version will be generated
627
628 string exten = file::findExtension(nextfile);
629 if (exten == EXTEN::yoshInst || exten == EXTEN::zynInst)
630 {
631 if (copyFile(importdir + "/" + nextfile, externfile + "/" + nextfile, 0))
632 missing = true;
633 // remove extension
634 string instname = nextfile.substr(0, nextfile.size() - exten.size());
635 int instnum = -2; // no number prefix
636 splitNumFromName(instnum, instname);
637 if (instnum == -1) // we don't accept a displayed prefix of zero
638 instnum = MAX_INSTRUMENTS_IN_BANK -1;
639 if (!isDuplicate(rootID, bankID, instnum, nextfile))
640 {
641 if (addtobank(rootID, bankID, instnum, nextfile, instname))
642 missing = true;
643 }
644 ++count;
645 }
646 ++total;
647 }
648 name = importdir;
649 if (count == 0)
650 {
651 partial = true;
652 name += " : No valid instruments found";
653 }
654 else if (missing)
655 {
656 partial = true;
657 name += " : Failed to copy some instruments";
658 }
659 else if (count < total)
660 {
661 partial = true;
662 name = name + " : Ignored " + to_string(total - count) + " unrecognised items";
663 }
664 }
665 }
666 thisBank.clear();
667 }
668
669 if (!ok)
670 name = " FAILED " + name;
671 else if (!partial)
672 name = "ed " + name;
673 return name;
674 }
675
676
isDuplicate(size_t rootID,size_t bankID,int pos,const string filename)677 bool Bank::isDuplicate(size_t rootID, size_t bankID, int pos, const string filename)
678 {
679 string path = getRootPath(rootID) + "/" + getBankName(bankID, rootID) + "/" + filename;
680 if (isRegularFile(setExtension(path, EXTEN::yoshInst)) && filename.rfind(EXTEN::zynInst) < string::npos)
681 return 1;
682 if (isRegularFile(setExtension(path, EXTEN::zynInst)) && filename.rfind(EXTEN::yoshInst) < string::npos)
683 {
684 InstrumentEntry &Ref = getInstrumentReference(rootID, bankID, pos);
685 Ref.yoshiType = true;
686 return 1;
687 }
688 return 0;
689 }
690
691
692 // Makes a new bank with known ID. Does *not* make it current
newIDbank(const string & newbankdir,unsigned int bankID,size_t rootID)693 bool Bank::newIDbank(const string& newbankdir, unsigned int bankID, size_t rootID)
694 {
695 if (rootID == UNUSED)
696 rootID = synth->getRuntime().currentRoot; // shouldn't be needed!
697
698 if (!newbankfile(newbankdir, rootID))
699 return false;
700 roots [synth->getRuntime().currentRoot].banks [bankID].dirname = newbankdir;
701 return true;
702 }
703
704
705 // Performs the actual file operation for new banks
newbankfile(const string & newbankdir,size_t rootID)706 bool Bank::newbankfile(const string& newbankdir, size_t rootID)
707 {
708 if (getRootPath(synth->getRuntime().currentRoot).empty())
709 return false;
710
711 string newbankpath = getRootPath(rootID);
712 if (newbankpath.at(newbankpath.size() - 1) != '/')
713 newbankpath += "/";
714 newbankpath += newbankdir;
715 int result = createDir(newbankpath);
716 if (result != 0)
717 return false;
718
719 string forcefile = newbankpath;
720 if (forcefile.at(forcefile.size() - 1) != '/')
721 forcefile += "/";
722 saveText(string(YOSHIMI_VERSION), forcefile + EXTEN::validBank);
723 return true;
724 }
725
726
727 // Removes a bank and all its contents
removebank(unsigned int bankID,size_t rootID)728 string Bank::removebank(unsigned int bankID, size_t rootID)
729 {
730 if (rootID == UNUSED)
731 rootID = synth->getRuntime().currentRoot;
732 if (roots.count(rootID) == 0) // not an error
733 return ("Root " + to_string(int(rootID)) + " is empty!");
734
735 string bankName = getBankPath(rootID, bankID);
736 // ID bank and test for writeable
737 string IDfile = bankName + "/" + EXTEN::validBank;
738 if (!saveText(string(YOSHIMI_VERSION), IDfile))
739 return (" FAILED Can't delete from this location.");
740
741 bool ck1 = true;
742 bool ck2 = true;
743 int chk = 0;
744 string name;
745 string failed;
746 for (int inst = 0; inst < MAX_INSTRUMENTS_IN_BANK; ++ inst)
747 {
748 if (!roots [rootID].banks [bankID].instruments [inst].name.empty())
749 {
750 name = setExtension(getFullPath(synth->getRuntime().currentRoot, bankID, inst), EXTEN::yoshInst);
751 if (isRegularFile(name))
752 ck1 = deleteFile(name);
753 else
754 ck1 = true;
755
756 name = setExtension(name, EXTEN::zynInst);
757 if (isRegularFile(name))
758 ck2 = deleteFile(name);
759 else
760 ck2 = true;
761
762 if (ck1 == true && ck2 == true)
763 deletefrombank(rootID, bankID, inst);
764 else
765 {
766 ++ chk;
767 if (chk == 0) // only want to name one entry
768 failed = (" FAILED Can't remove " + findLeafName(name) + ". Others may also still exist.");
769 }
770 }
771 }
772 if (chk > 0)
773 return failed;
774
775 // ID file only removed when bank cleared
776 if (deleteFile(IDfile))
777 {
778 chk = 1;
779 deleteDir(bankName);
780 }
781
782 roots [rootID].banks.erase(bankID);
783 if (rootID == synth->getRuntime().currentRoot && bankID == synth->getRuntime().currentBank)
784 setCurrentBankID(0, false);
785 int remainder = countDir(bankName);
786 if (remainder > 0)
787 return(" FAILED Bank " + bankName + " unlinked but " + to_string(remainder) + " unrecognised objects still exist.");
788 return ("d " + bankName);
789 }
790
791
792 // Swaps a slot with another
swapslot(unsigned int n1,unsigned int n2,size_t bank1,size_t bank2,size_t root1,size_t root2)793 string Bank::swapslot(unsigned int n1, unsigned int n2, size_t bank1, size_t bank2, size_t root1, size_t root2)
794 {
795 if (n1 == n2 && bank1 == bank2 && root1 == root2)
796 return " Can't swap with itself!";
797
798 /*
799 * path entries will always have either .xiy or .xiz
800 * otherwise they would not have been seen at all
801 * however we test for, and move both if they exist
802 */
803 string message = "";
804 bool ok = true;
805
806 if (emptyslot(root1, bank1, n1) && emptyslot(root2, bank2, n2))
807 return " Nothing to swap!";
808
809 if (emptyslot(root1, bank1, n1) || emptyslot(root2, bank2, n2))
810 { // this is just a movement to an empty slot
811 if (emptyslot(root1, bank1, n1)) // make the empty slot the destination
812 {
813 if (!moveInstrument(n2, getname(n2, bank2, root2), n1, bank2, bank1, root2, root1))
814 {
815 ok = false;
816 message = " Can't write to " + getname(n2, bank2, root2);
817 }
818 else
819 message = to_string(n2) + " " + getname(n2, bank2, root2);
820 getInstrumentReference(root1, bank1, n1) = getInstrumentReference(root2, bank2, n2);
821 getInstrumentReference(root2, bank2, n2).clear();
822 }
823 else
824 {
825 if (!moveInstrument(n1, getname(n1, bank1, root1), n2, bank1, bank2, root1, root2))
826 {
827 ok = false;
828 message = " Can't write to " + getname(n1, bank1, root1);
829 }
830 else
831 message = to_string(n2) + " " + getname(n1, bank1, root1);
832 getInstrumentReference(root2, bank2, n2) = getInstrumentReference(root1, bank1, n1);
833 getInstrumentReference(root1, bank1, n1).clear();
834 }
835 if (!ok)
836 return (" FAILED" + message);
837 else
838 return (" Moved to " + message);
839 }
840
841
842 // if both slots are used
843 string firstName = getname(n1, bank1, root1);
844 string secondName = getname(n2, bank2, root2);
845 if (firstName == secondName)
846 return " Can't swap instruments with identical names.";
847
848 InstrumentEntry &instrRef1 = getInstrumentReference(root1, bank1, n1);
849 InstrumentEntry &instrRef2 = getInstrumentReference(root2, bank2, n2);
850
851 if (!moveInstrument(n2, secondName, n1, bank2, bank1, root2, root1))
852 {
853 ok = false;
854 message = " Can't change " + secondName;
855 }
856
857 if (!moveInstrument(n1, firstName, n2, bank1, bank2, root1, root2))
858 {
859 ok = false;
860 message = " Can't change " + firstName;
861 }
862 else
863 {
864 InstrumentEntry instrTmp = instrRef1;
865 instrRef1 = instrRef2;
866 instrRef2 = instrTmp;
867 }
868
869 if (!ok)
870 return (" FAILED" + message);
871
872 return ("ped " + firstName + " with " + secondName);
873 }
874
875
876 // Intelligently moves or swaps banks preserving instrument details
swapbanks(unsigned int firstID,unsigned int secondID,size_t firstRoot,size_t secondRoot)877 string Bank::swapbanks(unsigned int firstID, unsigned int secondID, size_t firstRoot, size_t secondRoot)
878 {
879 string firstname;
880 string secondname;
881 int moveType = 0;
882
883 if (firstID == secondID && firstRoot == secondRoot)
884 return " Can't swap with itself!";
885
886 firstname = getBankName(firstID, firstRoot);
887 secondname = getBankName(secondID, secondRoot);
888 if (firstname.empty() && secondname.empty())
889 return " Nothing to swap!";
890
891
892 if (firstRoot != secondRoot)
893 {
894 if (isDuplicateBankName(firstRoot, secondname))
895 return (" FAILED " + secondname + " already exists in root " + to_string(firstRoot));
896
897 if (isDuplicateBankName(secondRoot, firstname))
898 return (" FAILED " + firstname + " already exists in root " + to_string(secondRoot));
899 }
900
901 if (firstRoot != secondRoot) // do physical move first
902 {
903 string firstBankPath = getBankPath(firstRoot, firstID);
904 string secondBankPath = getBankPath(secondRoot, secondID);
905 string newfirstBankPath = getRootPath(secondRoot) + "/" + firstname;
906 string newsecondBankPath = getRootPath(firstRoot) + "/" + secondname;
907 string tempBankPath = getRootPath(firstRoot) + "/tempfile";
908 if (secondBankPath == "") // move only
909 {
910 if (!renameDir(firstBankPath, (getRootPath(secondRoot) + "/" + firstname)))
911 {
912 synth->getRuntime().Log("move to " + to_string(secondRoot) + ": " + string(strerror(errno)), _SYS_::LogNotSerious);
913 return (" FAILED Can't move from root " + to_string(firstRoot) + " to " + to_string(secondRoot));
914 }
915 }
916 else if (firstBankPath == "") // move only
917 {
918 if (!renameDir(secondBankPath, (getRootPath(firstRoot) + "/" + secondname)))
919 {
920 synth->getRuntime().Log("move to " + to_string(firstRoot) + ": " + string(strerror(errno)), _SYS_::LogNotSerious);
921 return (" FAILED Can't move from root " + to_string(secondRoot) + " to " + to_string(firstRoot));
922 }
923 }
924 else // actual swap
925 {
926 // due to possible identical names we need to go via a temp file
927
928 //cout << "first " << firstBankPath << endl;
929 //cout << "second " << secondBankPath << endl;
930 //cout << "newfirst " << newfirstBankPath << endl;
931 //cout << "newsecond " << newsecondBankPath << endl;
932 deleteDir(tempBankPath); // just to be sure
933 if (!renameDir(firstBankPath, tempBankPath))
934 {
935 synth->getRuntime().Log("failed move to temp dir", _SYS_::LogNotSerious);
936 return(" FAILED Can't move from root " + to_string(firstRoot) + " to temp dir");
937 }
938
939 if (!renameDir(secondBankPath,newsecondBankPath))
940 {
941 synth->getRuntime().Log("failed move to " + to_string(firstRoot), _SYS_::LogNotSerious);
942 return(" FAILED Can't move from root " + to_string(secondRoot) + " to " + to_string(firstRoot));
943 }
944
945
946 if (!renameDir(tempBankPath, newfirstBankPath))
947 {
948 synth->getRuntime().Log("failed move to " + to_string(secondRoot), _SYS_::LogNotSerious);
949 return (" FAILED Can't move from temp dir to " + to_string(secondRoot));
950 }
951 }
952 }
953
954 // update banks
955 if (secondname.empty())
956 {
957 moveType = 1;
958 roots [secondRoot].banks [secondID] = roots [firstRoot].banks [firstID];
959 roots [firstRoot].banks.erase(firstID);
960 }
961 else if (firstname.empty())
962 {
963 moveType = 2;
964 roots [firstRoot].banks [firstID] = roots [secondRoot].banks [secondID];
965 roots [secondRoot].banks.erase(secondID);
966 }
967 else
968 {
969 roots [firstRoot].banks [firstID].dirname = secondname;
970 roots [secondRoot].banks [secondID].dirname = firstname;
971
972 for (int pos = 0; pos < MAX_INSTRUMENTS_IN_BANK; ++ pos)
973 {
974 InstrumentEntry &instrRef_1 = getInstrumentReference(firstRoot, firstID, pos);
975 InstrumentEntry &instrRef_2 = getInstrumentReference(secondRoot, secondID, pos);
976
977 InstrumentEntry tmp = instrRef_2;
978
979 if (instrRef_1.name == "")
980 roots [secondRoot].banks [secondID].instruments.erase(pos);
981 else
982 instrRef_2 = instrRef_1;
983
984 if (tmp.name == "")
985 roots [firstRoot].banks [firstID].instruments.erase(pos);
986 else
987 instrRef_1 = tmp;
988 }
989 }
990
991 if (firstRoot == synth->getRuntime().currentRoot)
992 synth->getRuntime().currentRoot = secondRoot;
993 else if (secondRoot == synth->getRuntime().currentBank)
994 synth->getRuntime().currentBank = firstRoot;
995 if (firstID == synth->getRuntime().currentBank)
996 synth->getRuntime().currentBank = secondID;
997 else if (secondID == synth->getRuntime().currentBank)
998 synth->getRuntime().currentBank = firstID;
999
1000 if (moveType == 0)
1001 return ("ped " + firstname + " with " + secondname);
1002
1003 int destination;
1004 string type = "slot ";
1005 if (firstRoot == secondRoot)
1006 {
1007 if (moveType == 1)
1008 destination = secondID;
1009 else
1010 destination = firstID;
1011 }
1012 else
1013 {
1014 type = "root ";
1015 if (moveType == 1)
1016 destination = secondRoot;
1017 else
1018 destination = firstRoot;
1019 }
1020
1021 if (moveType == 2)
1022 return (" Moved " + secondname + " to " + type + to_string(destination));
1023
1024 return (" Moved " + firstname + " to " + type + to_string(destination));
1025 }
1026
1027 // private affairs
1028
isValidBank(string chkdir)1029 bool Bank::isValidBank(string chkdir)
1030 {
1031 if (!isDirectory(chkdir))
1032 return false;
1033 // check if directory contains an instrument or EXTEN::validBank
1034 list<string> tryBank;
1035 uint32_t tried = listDir(&tryBank, chkdir);
1036 if (tried == 0xffffffff)
1037 {
1038 synth->getRuntime().Log("Failed to open bank directory candidate " + chkdir);
1039 return false;
1040 }
1041 chkdir += "/";
1042 for (list<string>::iterator it_b = tryBank.begin(); it_b != tryBank.end(); ++ it_b)
1043 {
1044 string chkpath = chkdir + *it_b;
1045 if (isRegularFile(chkpath))
1046 {
1047 string tryext = file::findExtension(chkpath);
1048 if (tryext == EXTEN::validBank || tryext == EXTEN::yoshInst || tryext == EXTEN::zynInst)
1049 return true;
1050 }
1051 }
1052 return false;
1053 }
1054
1055
addtobank(size_t rootID,size_t bankID,int _pos,string _filename,const string name)1056 bool Bank::addtobank(size_t rootID, size_t bankID, int _pos, string _filename, const string name)
1057 {
1058 if (_pos < 0 || _pos >= MAX_INSTRUMENTS_IN_BANK)
1059 return -1; //invalid location
1060
1061 int pos = _pos;
1062 string filename = _filename;
1063
1064 //cout << "add root " << rootID << " bank " << bankID << "\nname "<< name << " file " << filename << endl;
1065
1066 BankEntry &bank = roots [rootID].banks [bankID];
1067 string exten = file::findExtension(filename);
1068
1069 if (bank.instruments [pos].used)
1070 {
1071 string oldName = getname(pos, bankID, rootID);
1072 /*
1073 * We test the internal name. The file name could have been
1074 * changed, but if the internal one is changed it is most
1075 * likely to be a modified instrument.
1076 */
1077 if (name == oldName) // duplicate
1078 {
1079 //cout << "duplicate " << name << endl;
1080 if (exten == EXTEN::yoshInst) // yoshiType takes priority
1081 getInstrumentReference(rootID, bankID, pos).yoshiType = true;
1082 return 0; // no actual insertion necessary
1083 }
1084 pos = -1; // location occupied so find a new free position
1085 }
1086
1087 bool wanted = (pos >=0);
1088 if (pos < 0)
1089 {
1090 pos = MAX_INSTRUMENTS_IN_BANK;
1091 while (pos > 0 && !wanted)
1092 {
1093 --pos;
1094 if (emptyslot(rootID, bankID, pos))
1095 wanted = true;
1096 else if (name == getname(pos, bankID, rootID))
1097 {
1098 //cout << name << " already placed" << endl;
1099 pos = -1;
1100 }
1101 }
1102 }
1103 if (!wanted)
1104 return -1; // duplicated or the bank is full
1105 string bankdirname = getBankPath(rootID, bankID) + "/";
1106 string prefix = "0000" + to_string(pos + 1);
1107 prefix = prefix.substr(prefix.size() - 4);
1108 string newfile = prefix + "-" + name + exten;
1109 /*
1110 * If we are repositioning this file because it has the same
1111 * ID as an existing one but is in a non-writable location
1112 * we store its original filename while showing an offset ID.
1113 * If the location is writable we move the file.
1114 */
1115 if (renameFile(bankdirname + filename, bankdirname + newfile))
1116 filename = newfile;
1117
1118 deletefrombank(rootID, bankID, pos); // is this actually needed?
1119 InstrumentEntry &instrRef = getInstrumentReference(rootID, bankID, pos);
1120 instrRef.used = true;
1121 instrRef.name = name;
1122 instrRef.filename = filename;
1123 instrRef.PADsynth_used = 0;
1124 instrRef.ADDsynth_used = 0;
1125 instrRef.SUBsynth_used = 0;
1126 instrRef.yoshiType = 0;
1127
1128 string checkfile = setExtension(getFullPath(rootID, bankID, pos), EXTEN::yoshInst);
1129 if (!isRegularFile(checkfile))
1130 checkfile = setExtension(getFullPath(rootID, bankID, pos), EXTEN::zynInst);
1131 unsigned int names = 0;
1132 int type = 0;
1133 XMLwrapper *xml = new XMLwrapper(synth, true, false);
1134 xml->checkfileinformation(checkfile, names, type);
1135 delete xml;
1136
1137 instrRef.type = type;
1138 instrRef.ADDsynth_used = (names & 1);
1139 instrRef.SUBsynth_used = (names & 2) >> 1;
1140 instrRef.PADsynth_used = (names & 4) >> 2;
1141 instrRef.yoshiType = (exten == EXTEN::yoshInst);
1142 return 0;
1143 }
1144
1145
deletefrombank(size_t rootID,size_t bankID,unsigned int pos)1146 void Bank::deletefrombank(size_t rootID, size_t bankID, unsigned int pos)
1147 {
1148 if (pos >= MAX_INSTRUMENTS_IN_BANK)
1149 {
1150 synth->getRuntime().Log("Error, deletefrombank pos " + asString(pos) + " > MAX_INSTRUMENTS_IN_BANK"
1151 + asString(MAX_INSTRUMENTS_IN_BANK));
1152 return;
1153 }
1154 getInstrumentReference(rootID, bankID, pos).clear();
1155 }
1156
1157
getInstrumentReference(size_t rootID,size_t bankID,size_t ninstrument)1158 InstrumentEntry &Bank::getInstrumentReference(size_t rootID, size_t bankID, size_t ninstrument)
1159 {
1160 return roots [rootID].banks [bankID].instruments [ninstrument];
1161 }
1162
1163
updateShare(string bankdirs[],string baseDir,string shareID)1164 void Bank::updateShare(string bankdirs[], string baseDir, string shareID)
1165 {
1166 saveText(to_string(BUILD_NUMBER), shareID);
1167 string next = "/Will_Godfrey_Companion";
1168 string destinationDir = baseDir + "yoshimi/banks/Will_Godfrey_Companion"; // currently only concerned with this one.
1169 if (!isDirectory(destinationDir))
1170 return;
1171 //cout << bankdirs[1] << endl;
1172 if (isDirectory(bankdirs[1] + next))
1173 checkShare(bankdirs[1] + next, destinationDir);
1174
1175 if (isDirectory(bankdirs[2] + next))
1176 checkShare(bankdirs[2] + next, destinationDir);
1177 }
1178
1179
checkShare(string sourceDir,string destinationDir)1180 void Bank::checkShare(string sourceDir, string destinationDir)
1181 {
1182 copyDir(sourceDir, destinationDir, 0);
1183 }
1184
1185
transferDefaultDirs(string bankdirs[])1186 bool Bank::transferDefaultDirs(string bankdirs[])
1187 {
1188 if (!isDirectory(foundLocal))
1189 return false;
1190 bool found = false;
1191 // always want these
1192 if (isDirectory(foundLocal + "yoshimi"))
1193 found = true;
1194 else
1195 {
1196 createDir(foundLocal + "yoshimi");
1197 createDir(foundLocal + "yoshimi/banks");
1198 if (isDirectory(bankdirs[6]))
1199 if (transferOneDir(bankdirs, 0, 6))
1200 found = true;
1201 if (isDirectory(bankdirs[1]) || isDirectory(bankdirs[2]))
1202 {
1203 if (transferOneDir(bankdirs, 0, 1))
1204 found = true;
1205 if (transferOneDir(bankdirs, 0, 2))
1206 found = true;
1207 }
1208 }
1209
1210 //might not have these
1211 if (isDirectory(foundLocal + "zynaddsubfx"))
1212 found = true;
1213 else
1214 {
1215 if (isDirectory(bankdirs[3]) || isDirectory(bankdirs[4]))
1216 {
1217 createDir(foundLocal + "zynaddsubfx");
1218 createDir(foundLocal + "zynaddsubfx/banks");
1219 if (transferOneDir(bankdirs, 5, 3))
1220 found = true;
1221 if (transferOneDir(bankdirs, 5, 4))
1222 found = true;
1223 }
1224 }
1225 return found;
1226 }
1227
1228
transferOneDir(string bankdirs[],int baseNumber,int listNumber)1229 bool Bank::transferOneDir(string bankdirs[], int baseNumber, int listNumber)
1230 {
1231 bool found = false;
1232 list<string> thisBankDir;
1233 uint32_t copyList = listDir(& thisBankDir, bankdirs[listNumber]);
1234 if (copyList > 0 && copyList < 0xffffffff)
1235 {
1236 for (list<string>::iterator it = thisBankDir.begin(); it != thisBankDir.end(); ++ it)
1237 {
1238 string oldBank = bankdirs[listNumber] + "/" + *it;
1239 string newBank = bankdirs[baseNumber] + "/" + *it;
1240 createDir(newBank);
1241 uint32_t inside = copyDir(oldBank, newBank, 1);
1242 if (inside > 0 && inside < 0xffffffff)
1243 found = true;
1244 }
1245 thisBankDir.clear();
1246 }
1247 return found;
1248 }
1249
1250
checkLocalBanks()1251 void Bank::checkLocalBanks()
1252 {
1253 if (isDirectory(foundLocal + "yoshimi/banks")) // yoshi
1254 addRootDir(foundLocal + "yoshimi/banks");
1255
1256 if (isDirectory(foundLocal + "zynaddsubfx/banks"))
1257 addRootDir(foundLocal + "zynaddsubfx/banks"); // zyn
1258 }
1259
addDefaultRootDirs(string bankdirs[])1260 void Bank::addDefaultRootDirs(string bankdirs[])
1261 {
1262 int tot = 0;
1263 int i = 0;
1264 while (bankdirs[i] != "@end")
1265 {
1266 if (isDirectory(bankdirs[i]))
1267 {
1268 addRootDir(bankdirs [i]);
1269 ++tot;
1270 }
1271 ++ i;
1272 }
1273
1274 for (int i = tot; i > 0; --i)
1275 changeRootID(i, i * 5);
1276 }
1277
1278
generateSingleRoot(const string & newRoot,bool clear)1279 size_t Bank::generateSingleRoot(const string& newRoot, bool clear)
1280 {
1281 createDir(newRoot);
1282
1283 // add bank
1284 string newBank = newRoot + "newBank";
1285 createDir(newBank);
1286 string toSave = newBank + "/" + EXTEN::validBank;
1287 saveText(string(YOSHIMI_VERSION), toSave);
1288 // now generate and save an instrument
1289 int npart = 0;
1290 string instrumentName = "First Instrument";
1291 synth->interchange.generateSpecialInstrument(npart, instrumentName);
1292
1293 string filename = newBank + "/" + "0005-" + instrumentName + EXTEN::zynInst;
1294 synth->part[npart]->saveXML(filename, false);
1295
1296 // set root and tidy up
1297 size_t idx = addRootDir(newRoot);
1298
1299 if (clear)
1300 synth->part[npart]->defaultsinstrument();
1301 return idx;
1302 }
1303
1304
getNewRootIndex()1305 size_t Bank::getNewRootIndex()
1306 {
1307 size_t pos = 1;
1308 if (roots.empty())
1309 return pos;
1310 while (roots.count(pos) != 0)
1311 ++ pos;
1312 return pos;
1313 }
1314
1315
getNewBankIndex(size_t rootID)1316 size_t Bank::getNewBankIndex(size_t rootID)
1317 {
1318 if (roots [rootID].banks.empty())
1319 {
1320 if (roots [rootID].bankIdStep <= 1)
1321 {
1322 return 0;
1323 }
1324
1325 return roots [rootID].bankIdStep;
1326 }
1327
1328 size_t idStep = 1;
1329
1330 if (roots [rootID].bankIdStep == 0)
1331 {
1332 size_t startId = 127;
1333 size_t i;
1334 for (i = startId; i > 0; --i)
1335 {
1336 if (roots [rootID].banks.count(i) == 0)
1337 {
1338 break;
1339 }
1340 }
1341 if (i > 0) //id found
1342 {
1343 return i;
1344 }
1345 }
1346 else
1347 {
1348 idStep = roots [rootID].bankIdStep;
1349 }
1350 return idStep;
1351 }
1352
1353
getBankPath(size_t rootID,size_t bankID)1354 string Bank::getBankPath(size_t rootID, size_t bankID)
1355 {
1356 if (roots.count(rootID) == 0 || roots [rootID].banks.count(bankID) == 0)
1357 {
1358 return string("");
1359 }
1360 if (roots [rootID].path.empty())
1361 {
1362 return string("");
1363 }
1364 string chkdir = getRootPath(rootID) + string("/") + roots [rootID].banks [bankID].dirname;
1365 if (chkdir.at(chkdir.size() - 1) == '/')
1366 {
1367 chkdir = chkdir.substr(0, chkdir.size() - 1);
1368 }
1369 return chkdir;
1370 }
1371
1372
getRootPath(size_t rootID)1373 string Bank::getRootPath(size_t rootID)
1374 {
1375 if (roots.count(rootID) == 0 || roots [rootID].path.empty())
1376 return string("");
1377
1378 string chkdir = roots [rootID].path;
1379 if (chkdir.at(chkdir.size() - 1) == '/')
1380 chkdir = chkdir.substr(0, chkdir.size() - 1);
1381
1382 return chkdir;
1383 }
1384
1385
getFullPath(size_t rootID,size_t bankID,size_t ninstrument)1386 string Bank::getFullPath(size_t rootID, size_t bankID, size_t ninstrument)
1387 {
1388 string bankPath = getBankPath(rootID, bankID);
1389 if (!bankPath.empty())
1390 {
1391 string instrFname = getInstrumentReference(rootID, bankID, ninstrument).filename;
1392 return bankPath + string("/") + instrFname;
1393 }
1394 return string("");
1395
1396 }
1397
1398
getBanks(size_t rootID)1399 const BankEntryMap &Bank::getBanks(size_t rootID)
1400 {
1401 return roots [rootID].banks;
1402 }
1403
1404
getRoots()1405 const RootEntryMap &Bank::getRoots()
1406 {
1407 return roots;
1408 }
1409
1410
getBank(size_t bankID,size_t rootID)1411 const BankEntry &Bank::getBank(size_t bankID, size_t rootID)
1412 {
1413 if (rootID == UNUSED)
1414 rootID = synth->getRuntime().currentRoot;
1415 return roots[rootID].banks[bankID];
1416 }
1417
1418
engines_used(size_t rootID,size_t bankID,unsigned int ninstrument)1419 int Bank::engines_used(size_t rootID, size_t bankID, unsigned int ninstrument)
1420 {
1421 int tmp = getInstrumentReference(rootID, bankID, ninstrument).ADDsynth_used
1422 | (getInstrumentReference(rootID, bankID, ninstrument).SUBsynth_used << 1)
1423 | (getInstrumentReference(rootID, bankID, ninstrument).PADsynth_used << 2)
1424 | (getInstrumentReference(rootID, bankID, ninstrument).yoshiType << 3);
1425 return tmp;
1426 }
1427
1428
removeRoot(size_t rootID)1429 bool Bank::removeRoot(size_t rootID)
1430 {
1431 if (rootID == synth->getRuntime().currentRoot)
1432 {
1433 synth->getRuntime().currentRoot = 0;
1434 }
1435 else if (roots [rootID].path.empty())
1436 return true;
1437 roots.erase(rootID);
1438 synth->getRuntime().currentRoot = roots.begin()->first;
1439 setCurrentRootID(synth->getRuntime().currentRoot);
1440 return false;
1441 }
1442
1443
changeRootID(size_t oldID,size_t newID)1444 bool Bank::changeRootID(size_t oldID, size_t newID)
1445 {
1446 RootEntry oldRoot = roots [oldID];
1447 roots [oldID] = roots [newID];
1448 roots [newID] = oldRoot;
1449 setCurrentRootID(newID);
1450 RootEntryMap::iterator it = roots.begin();
1451 while (it != roots.end())
1452 {
1453 if (it->second.path.empty())
1454 roots.erase(it++);
1455 else
1456 ++it;
1457 }
1458
1459 return true;
1460 }
1461
1462
setCurrentRootID(size_t newRootID)1463 bool Bank::setCurrentRootID(size_t newRootID)
1464 {
1465 size_t oldRoot = synth->getRuntime().currentRoot;
1466 if (roots.count(newRootID) == 0)
1467 return false;
1468 else
1469 synth->getRuntime().currentRoot = newRootID;
1470 for (size_t id = 0; id < MAX_BANKS_IN_ROOT; ++id)
1471 {
1472 if (roots [newRootID].banks.count(id) == 0)
1473 {
1474 findFirstBank(newRootID);
1475 return true;
1476 }
1477 if (roots [newRootID].banks.count(id) == 1)
1478 {
1479 if (roots [newRootID].banks [id].dirname.empty())
1480 {
1481 findFirstBank(newRootID);
1482 return true;
1483 }
1484 }
1485 }
1486 if (synth->getRuntime().currentRoot != oldRoot)
1487 findFirstBank(newRootID);
1488 return true;
1489 }
1490
findFirstBank(size_t newRootID)1491 unsigned int Bank::findFirstBank(size_t newRootID)
1492 {
1493 for (size_t i = 0; i < MAX_BANKS_IN_ROOT; ++i)
1494 {
1495 if (roots [newRootID].banks.count(i) != 0)
1496 {
1497 if (!roots [newRootID].banks [i].dirname.empty())
1498 {
1499 synth->getRuntime().currentBank = i;
1500 break;
1501 }
1502 }
1503 }
1504 return 0;
1505 }
1506
1507
setCurrentBankID(size_t newBankID,bool ignoreMissing)1508 bool Bank::setCurrentBankID(size_t newBankID, bool ignoreMissing)
1509 {
1510 if (roots [synth->getRuntime().currentRoot].banks.count(newBankID) == 0)
1511 {
1512 if (ignoreMissing)
1513 return false;
1514 else
1515 newBankID = roots [synth->getRuntime().currentRoot].banks.begin()->first;
1516 }
1517 synth->getRuntime().currentBank = newBankID;
1518 return true;
1519 }
1520
1521
addRootDir(const string & newRootDir)1522 size_t Bank::addRootDir(const string& newRootDir)
1523 {
1524 // we need the size check to prevent weird behaviour if the name is just ./
1525 if (!isDirectory(newRootDir) || newRootDir.length() < 4)
1526 return 0;
1527 size_t newIndex = getNewRootIndex();
1528 roots [newIndex].path = newRootDir;
1529 return newIndex;
1530 }
1531
1532
parseBanksFile(XMLwrapper * xml)1533 bool Bank::parseBanksFile(XMLwrapper *xml)
1534 {
1535 /*
1536 * This list is used in transferDefaultDirs( to find and copy
1537 * bank lists into $HOME/.local.yoshimi
1538 * This is refreshed at each startup to update existing entries
1539 * or add new ones.
1540 *
1541 * It is also used by addDefaultRootDirs( to populate the bank
1542 * roots, in the event of a missing list.
1543 *
1544 * The list is in the order the roots will appear to the user,
1545 * and the numbering in addDefaultRootDirs is the same.
1546 */
1547
1548 string bankdirs[] = {
1549 foundLocal + "yoshimi/banks",
1550 "/usr/share/yoshimi/banks",
1551 "/usr/local/share/yoshimi/banks",
1552 "/usr/share/zynaddsubfx/banks",
1553 "/usr/local/share/zynaddsubfx/banks",
1554 foundLocal + "zynaddsubfx/banks",
1555 extendLocalPath("/banks"),
1556 "@end"
1557 };
1558
1559 bool rootsFound = transferDefaultDirs(bankdirs);
1560
1561 bool newRoots = true;
1562 roots.clear();
1563
1564 if (xml)
1565 {
1566 if (xml->enterbranch("INFORMATION"))
1567 { // going negative to catch all previous versions and to be backward compatible
1568 writeVersion(xml->getpar("Banks_Version", 0, 0, 999));
1569 xml->exitbranch();
1570 }
1571 if (xml->enterbranch("BANKLIST"))
1572 newRoots = false;
1573 }
1574
1575
1576 if (newRoots)
1577 {
1578 roots.clear();
1579 if (rootsFound)
1580 addDefaultRootDirs(bankdirs);
1581 else
1582 {
1583 //cout << "generating" << endl;
1584 string newRoot = foundLocal + "yoshimi/banks";
1585 size_t idx = generateSingleRoot(newRoot);
1586 changeRootID(idx, 5);
1587 synth->getRuntime().currentRoot = idx;
1588 synth->getRuntime().currentBank = 5;
1589
1590 }
1591 synth->getRuntime().currentRoot = 5;
1592 synth->getRuntime().banksChecked = true;
1593 }
1594
1595
1596 if (!newRoots)
1597 {
1598 string nodename = "BANKROOT";
1599 for (size_t i = 0; i < MAX_BANK_ROOT_DIRS; ++i)
1600 {
1601 if (xml->enterbranch(nodename, i))
1602 {
1603 string dir = xml->getparstr("bank_root");
1604 if (!dir.empty())
1605 {
1606 size_t newIndex = addRootDir(dir);
1607 if (newIndex != i)
1608 {
1609 changeRootID(newIndex, i);
1610 }
1611 for (size_t k = 0; k < MAX_INSTRUMENTS_IN_BANK; k++)
1612 {
1613 if (xml->enterbranch("bank_id", k))
1614 {
1615 string bankDirname = xml->getparstr("dirname");
1616 roots[i].banks[k].dirname = bankDirname;
1617 BankEntry &bank = roots [i].banks [k];
1618 size_t pos = 0;
1619 while (pos < MAX_INSTRUMENTS_IN_BANK)
1620 {
1621 if (xml->enterbranch("instrument_id", pos))
1622 {
1623 bank.instruments[pos].used = xml->getparbool("isUsed", false);
1624 bank.instruments[pos].name = xml->getparstr("listname");
1625 bank.instruments[pos].filename = xml->getparstr("filename");
1626 bank.instruments[pos].type = xml->getpar("type",0 , -50, 100);
1627 bank.instruments[pos].ADDsynth_used = xml->getparbool("ADDsynth", false);
1628 bank.instruments[pos].SUBsynth_used = xml->getparbool("SUBsynth", false);
1629 bank.instruments[pos].PADsynth_used = xml->getparbool("PADsynth", false);
1630 bank.instruments[pos].yoshiType = xml->getparbool("Yoshimi", false);
1631 xml->exitbranch();
1632 }
1633 ++pos;
1634 }
1635 xml->exitbranch();
1636 }
1637 }
1638 }
1639 xml->exitbranch();
1640 }
1641 }
1642 xml->exitbranch();
1643 }
1644
1645 if (!synth->getRuntime().rootDefine.empty())
1646 {
1647 string found = synth->getRuntime().rootDefine;
1648 synth->getRuntime().rootDefine = "";
1649 }
1650 installRoots();
1651
1652 if (isDirectory(foundLocal))
1653 {
1654 string shareID = foundLocal + "version";
1655 if (loadText(shareID) != to_string(BUILD_NUMBER))
1656 updateShare(bankdirs, foundLocal, shareID);
1657 }
1658 return newRoots;
1659 }
1660
1661
installRoots(void)1662 bool Bank::installRoots(void)
1663 {
1664 RootEntryMap::const_iterator it;
1665 for (it = roots.begin(); it != roots.end(); ++it)
1666 {
1667 size_t rootID = it->first;
1668 string rootdir = roots [rootID].path;
1669
1670 // the directory has been removed since the bank root was created
1671 if (!rootdir.size() || !isDirectory(rootdir))
1672 continue;
1673 installNewRoot(rootID, rootdir, true);
1674 }
1675 return true;
1676 }
1677
1678
installNewRoot(size_t rootID,string rootdir,bool reload)1679 bool Bank::installNewRoot(size_t rootID, string rootdir, bool reload)
1680 {
1681 list<string> thisRoot;
1682 uint32_t found = listDir(&thisRoot, rootdir);
1683 if (found == 0xffffffff)
1684 { // should never see this!
1685 synth->getRuntime().Log("No such directory, root bank entry " + rootdir);
1686 return false;
1687 }
1688
1689 if (rootdir.at(rootdir.size() - 1) != '/')
1690 rootdir += '/';
1691
1692 // it's a completely new root
1693 if (!reload)
1694 roots [rootID].banks.clear();
1695
1696 map<string, string> bankDirsMap;
1697
1698 // thin out invalid directories
1699 int validBanks = 0;
1700 list<string>::iterator r_it = thisRoot.end();
1701 while (r_it != thisRoot.begin())
1702 {
1703 string candidate = *--r_it;
1704 string chkdir = rootdir + candidate;
1705 if (isValidBank(chkdir))
1706 ++validBanks;
1707 else
1708 r_it = thisRoot.erase(r_it);
1709 }
1710 bool result = true;
1711 if (validBanks >= MAX_BANKS_IN_ROOT)
1712 synth->getRuntime().Log("Warning: There are " + to_string(validBanks - MAX_BANKS_IN_ROOT) + " too many valid bank candidates");
1713
1714 bool banksSet[MAX_BANKS_IN_ROOT];
1715 int banksFound = 0;
1716
1717 for (int i = 0; i < MAX_BANKS_IN_ROOT; ++i)
1718 banksSet[i] = false;
1719
1720 // install previously seen banks to the same references
1721 if (reload)
1722 {
1723 list<string>::iterator b_it = thisRoot.end();
1724 while (b_it != thisRoot.begin())
1725 {
1726 string trybank = *--b_it;
1727 for (size_t id = 0; id < MAX_BANKS_IN_ROOT; ++id)
1728 {
1729 if (roots [rootID].banks.count(id) == 0)
1730 continue;
1731 if (roots[rootID].banks[id].dirname == trybank)
1732 {
1733 banksSet[id] = true;
1734
1735 if (BanksVersion > 9) // all we need to do!
1736 {
1737 checkbank(rootID, id);
1738 InstrumentsInBanks += getBankSize(id, rootID);
1739 }
1740 else
1741 {
1742 roots [rootID].banks [id].dirname = trybank;
1743 loadbank(rootID, id);
1744 }
1745 b_it = thisRoot.erase(b_it);
1746 ++banksFound;
1747 break;
1748 }
1749 }
1750 if (banksFound >= MAX_BANKS_IN_ROOT)
1751 {
1752 result = false;
1753 break;
1754 }
1755 }
1756 }
1757 BanksInRoots += banksFound;
1758 size_t toFetch = thisRoot.size();
1759 if (toFetch > 0)
1760 {
1761 synth->getRuntime().Log("Found " + to_string(toFetch) + " new banks in root " + roots [rootID].path);
1762 }
1763
1764 if (thisRoot.size() != 0)
1765 {
1766 /*
1767 * install completely new banks
1768 *
1769 * This sequence spreads new banks as evenly as possible
1770 * through the root, avoiding collisions with possible
1771 * existing banks, and at the same time ensuring that
1772 * ID zero is the last possible entry.
1773 */
1774 size_t idStep = 5;
1775 size_t newIndex = idStep;
1776
1777 // try to keep new banks in a sensible order
1778 thisRoot.sort();
1779
1780 for (list<string>::iterator it = thisRoot.begin(); it != thisRoot.end(); ++it)
1781 {
1782 if (banksFound >= MAX_BANKS_IN_ROOT)
1783 {
1784 result = false;
1785 break; // root is full!
1786 }
1787 while (banksSet[newIndex] == true)
1788 {
1789 newIndex += idStep;
1790 newIndex &= (MAX_BANKS_IN_ROOT - 1);
1791 }
1792 roots [rootID].banks [newIndex].dirname = *it;
1793 loadbank(rootID, newIndex);
1794 banksSet[newIndex] = true;
1795 ++ banksFound;
1796 BanksInRoots += 1; // this is the total of all banks
1797 }
1798 }
1799
1800 // remove orphans
1801 for (size_t id = 0; id < MAX_BANKS_IN_ROOT; ++id)
1802 {
1803 if (roots [rootID].banks.count(id) == 1)
1804 {
1805 if (roots [rootID].banks [id].dirname.empty())
1806 {
1807 //cout << "Removed unnamed bank " << id << " in root " << rootID << endl;
1808 roots [rootID].banks.erase(id);
1809 }
1810 else if (!banksSet[id])
1811 {
1812 synth->getRuntime().Log("Removed orphan bank " +to_string(id) + " in root " + to_string(rootID) + " " + roots [rootID].banks [id].dirname);
1813 roots [rootID].banks.erase(id);
1814 }
1815 }
1816 }
1817 if (thisRoot.size())
1818 thisRoot.clear(); // leave it tidy
1819 return result;
1820 }
1821
1822
saveToConfigFile(XMLwrapper * xml)1823 void Bank::saveToConfigFile(XMLwrapper *xml)
1824 {
1825 writeVersion(BANKS_VERSION); // set current format
1826 for (size_t i = 0; i < MAX_BANK_ROOT_DIRS; i++)
1827 {
1828 if (roots.count(i) > 0 && !roots [i].path.empty())
1829 {
1830 string nodename = "BANKROOT";
1831
1832 xml->beginbranch(nodename, i);
1833 xml->addparstr("bank_root", roots [i].path);
1834 BankEntryMap::const_iterator it;
1835 for (it = roots [i].banks.begin(); it != roots [i].banks.end(); ++it)
1836 {
1837 xml->beginbranch("bank_id", it->first);
1838 xml->addparstr("dirname", it->second.dirname);
1839 BankEntry &bank = roots [i].banks [it->first];
1840 size_t pos = 0;
1841
1842 while (pos < MAX_INSTRUMENTS_IN_BANK)
1843 {
1844 if (bank.instruments [pos].used)
1845 {
1846 xml->beginbranch("instrument_id", pos);
1847 xml->addparbool("isUsed", bank.instruments [pos].used);
1848 xml->addparstr("listname", bank.instruments [pos].name);
1849 xml->addparstr("filename", bank.instruments [pos].filename);
1850 xml->addpar("type", bank.instruments [pos].type);
1851 xml->addparbool("ADDsynth", bank.instruments [pos].ADDsynth_used);
1852 xml->addparbool("SUBsynth", bank.instruments [pos].SUBsynth_used);
1853 xml->addparbool("PADsynth", bank.instruments [pos].PADsynth_used);
1854 xml->addparbool("Yoshimi", bank.instruments [pos].yoshiType);
1855 //cout << bank.instruments [pos].filename << endl;
1856 xml->endbranch();
1857 }
1858 ++pos;
1859 }
1860 xml->endbranch();
1861 }
1862
1863 xml->endbranch();
1864 }
1865 }
1866 }
1867