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