Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Bank.cpp 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. /*
  2. ZynAddSubFX - a software synthesizer
  3. Bank.cpp - Instrument Bank
  4. Copyright (C) 2002-2005 Nasca Octavian Paul
  5. Copyright (C) 2010-2010 Mark McCurry
  6. Author: Nasca Octavian Paul
  7. Mark McCurry
  8. This program is free software; you can redistribute it and/or
  9. modify it under the terms of the GNU General Public License
  10. as published by the Free Software Foundation; either version 2
  11. of the License, or (at your option) any later version.
  12. */
  13. #include "Bank.h"
  14. #include <cstring>
  15. #include <cstdio>
  16. #include <cstdlib>
  17. #include <dirent.h>
  18. #include <sys/stat.h>
  19. #include <algorithm>
  20. #include <sys/types.h>
  21. #include <fcntl.h>
  22. #include <unistd.h>
  23. #include <errno.h>
  24. #include "Config.h"
  25. #include "Util.h"
  26. #include "Part.h"
  27. #include "BankDb.h"
  28. #ifdef WIN32
  29. #include <windows.h>
  30. #endif
  31. using namespace std;
  32. namespace zyncarla {
  33. static const char* INSTRUMENT_EXTENSION = ".xiz";
  34. //if this file exists into a directory, this make the directory to be considered as a bank, even if it not contains a instrument file
  35. const char* FORCE_BANK_DIR_FILE = ".bankdir";
  36. Bank::Bank(Config *config)
  37. :bankpos(0), defaultinsname(" "), config(config),
  38. db(new BankDb), bank_msb(0), bank_lsb(0)
  39. {
  40. clearbank();
  41. bankfiletitle = dirname;
  42. rescanforbanks();
  43. loadbank(config->cfg.currentBankDir);
  44. for(unsigned i=0; i<banks.size(); ++i) {
  45. if(banks[i].dir == config->cfg.currentBankDir) {
  46. bankpos = i;
  47. break;
  48. }
  49. }
  50. }
  51. Bank::~Bank()
  52. {
  53. clearbank();
  54. delete db;
  55. }
  56. /*
  57. * Get the name of an instrument from the bank
  58. */
  59. string Bank::getname(unsigned int ninstrument)
  60. {
  61. if(emptyslot(ninstrument))
  62. return defaultinsname;
  63. return ins[ninstrument].name;
  64. }
  65. /*
  66. * Get the numbered name of an instrument from the bank
  67. */
  68. string Bank::getnamenumbered(unsigned int ninstrument)
  69. {
  70. if(emptyslot(ninstrument))
  71. return defaultinsname;
  72. return stringFrom(ninstrument + 1) + ". " + getname(ninstrument);
  73. }
  74. /*
  75. * Changes the name of an instrument (and the filename)
  76. */
  77. int Bank::setname(unsigned int ninstrument, const string &newname, int newslot)
  78. {
  79. if(emptyslot(ninstrument))
  80. return 0;
  81. string newfilename;
  82. char tmpfilename[100 + 1];
  83. tmpfilename[100] = 0;
  84. if(newslot >= 0)
  85. snprintf(tmpfilename, 100, "%4d-%s", newslot + 1, newname.c_str());
  86. else
  87. snprintf(tmpfilename, 100, "%4d-%s", ninstrument + 1, newname.c_str());
  88. //add the zeroes at the start of filename
  89. for(int i = 0; i < 4; ++i)
  90. if(tmpfilename[i] == ' ')
  91. tmpfilename[i] = '0';
  92. newfilename = dirname + legalizeFilename(tmpfilename) + ".xiz";
  93. int err = rename(ins[ninstrument].filename.c_str(), newfilename.c_str());
  94. if(err)
  95. return err;
  96. ins[ninstrument].filename = newfilename;
  97. ins[ninstrument].name = newname;
  98. return err;
  99. }
  100. /*
  101. * Check if there is no instrument on a slot from the bank
  102. */
  103. bool Bank::emptyslot(unsigned int ninstrument)
  104. {
  105. if(ninstrument >= BANK_SIZE)
  106. return true;
  107. if(ins[ninstrument].filename.empty())
  108. return true;
  109. return false;
  110. }
  111. /*
  112. * Removes the instrument from the bank
  113. */
  114. int Bank::clearslot(unsigned int ninstrument)
  115. {
  116. if(emptyslot(ninstrument))
  117. return 0;
  118. //no error when no file
  119. FILE *f = fopen(ins[ninstrument].filename.c_str(), "r");
  120. if(!f)
  121. return 0;
  122. fclose(f);
  123. int err = remove(ins[ninstrument].filename.c_str());
  124. if(!err)
  125. deletefrombank(ninstrument);
  126. return err;
  127. }
  128. /*
  129. * Save the instrument to a slot
  130. */
  131. int Bank::savetoslot(unsigned int ninstrument, Part *part)
  132. {
  133. int err = clearslot(ninstrument);
  134. if(err)
  135. return err;
  136. const int maxfilename = 200;
  137. char tmpfilename[maxfilename + 20];
  138. ZERO(tmpfilename, maxfilename + 20);
  139. snprintf(tmpfilename,
  140. maxfilename,
  141. "%04d-%s",
  142. ninstrument + 1,
  143. (char *)part->Pname);
  144. string filename = dirname + '/' + legalizeFilename(tmpfilename) + ".xiz";
  145. FILE *f = fopen(filename.c_str(), "r");
  146. if(f) {
  147. fclose(f);
  148. err = remove(filename.c_str());
  149. if(err)
  150. return err;
  151. }
  152. err = part->saveXML(filename.c_str());
  153. if(err)
  154. return err;
  155. addtobank(ninstrument, legalizeFilename(tmpfilename) + ".xiz",
  156. (char *) part->Pname);
  157. return 0;
  158. }
  159. /*
  160. * Loads the instrument from the bank
  161. */
  162. int Bank::loadfromslot(unsigned int ninstrument, Part *part)
  163. {
  164. if(emptyslot(ninstrument))
  165. return 0;
  166. part->AllNotesOff();
  167. part->defaultsinstrument();
  168. part->loadXMLinstrument(ins[ninstrument].filename.c_str());
  169. return 0;
  170. }
  171. /*
  172. * Makes current a bank directory
  173. */
  174. int Bank::loadbank(string bankdirname)
  175. {
  176. normalizedirsuffix(bankdirname);
  177. DIR *dir = opendir(bankdirname.c_str());
  178. clearbank();
  179. if(dir == NULL)
  180. return -1;
  181. //set msb when possible
  182. bank_msb = 0;
  183. for(unsigned i=0; i<banks.size(); i++)
  184. if(banks[i].dir == bankdirname)
  185. bank_msb = i;
  186. dirname = bankdirname;
  187. bankfiletitle = dirname;
  188. struct dirent *fn;
  189. while((fn = readdir(dir))) {
  190. const char *filename = fn->d_name;
  191. //check for extension
  192. if(strstr(filename, INSTRUMENT_EXTENSION) == NULL)
  193. continue;
  194. //verify if the name is like this NNNN-name (where N is a digit)
  195. int no = 0;
  196. unsigned int startname = 0;
  197. for(unsigned int i = 0; i < 4; ++i) {
  198. if(strlen(filename) <= i)
  199. break;
  200. if((filename[i] >= '0') && (filename[i] <= '9')) {
  201. no = no * 10 + (filename[i] - '0');
  202. startname++;
  203. }
  204. }
  205. if((startname + 1) < strlen(filename))
  206. startname++; //to take out the "-"
  207. string name = filename;
  208. //remove the file extension
  209. for(int i = name.size() - 1; i >= 2; i--)
  210. if(name[i] == '.') {
  211. name = name.substr(0, i);
  212. break;
  213. }
  214. if(no != 0) //the instrument position in the bank is found
  215. addtobank(no - 1, filename, name.substr(startname));
  216. else
  217. addtobank(-1, filename, name);
  218. }
  219. closedir(dir);
  220. if(!dirname.empty())
  221. config->cfg.currentBankDir = dirname;
  222. return 0;
  223. }
  224. /*
  225. * Makes a new bank, put it on a file and makes it current bank
  226. */
  227. int Bank::newbank(string newbankdirname)
  228. {
  229. string bankdir;
  230. bankdir = config->cfg.bankRootDirList[0];
  231. expanddirname(bankdir);
  232. normalizedirsuffix(bankdir);
  233. bankdir += newbankdirname;
  234. #ifdef _WIN32
  235. if(mkdir(bankdir.c_str()) < 0)
  236. #else
  237. if(mkdir(bankdir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0)
  238. #endif
  239. return -1;
  240. const string tmpfilename = bankdir + '/' + FORCE_BANK_DIR_FILE;
  241. FILE *tmpfile = fopen(tmpfilename.c_str(), "w+");
  242. fclose(tmpfile);
  243. return loadbank(bankdir);
  244. }
  245. /*
  246. * Check if the bank is locked (i.e. the file opened was readonly)
  247. */
  248. int Bank::locked()
  249. {
  250. //XXX Fixme
  251. return dirname.empty();
  252. }
  253. /*
  254. * Swaps a slot with another
  255. */
  256. int Bank::swapslot(unsigned int n1, unsigned int n2)
  257. {
  258. int err = 0;
  259. if((n1 == n2) || (locked()))
  260. return 0;
  261. if(emptyslot(n1) && (emptyslot(n2)))
  262. return 0;
  263. if(emptyslot(n1)) //change n1 to n2 in order to make
  264. swap(n1, n2);
  265. if(emptyslot(n2)) { //this is just a movement from slot1 to slot2
  266. err |= setname(n1, getname(n1), n2);
  267. if(err)
  268. return err;
  269. ins[n2] = ins[n1];
  270. ins[n1] = ins_t();
  271. }
  272. else { //if both slots are used
  273. if(ins[n1].name == ins[n2].name) //change the name of the second instrument if the name are equal
  274. ins[n2].name += "2";
  275. err |= setname(n1, getname(n1), n2);
  276. err |= setname(n2, getname(n2), n1);
  277. if(err)
  278. return err;
  279. swap(ins[n2], ins[n1]);
  280. }
  281. return err;
  282. }
  283. bool Bank::bankstruct::operator<(const bankstruct &b) const
  284. {
  285. return name < b.name;
  286. }
  287. /*
  288. * Re-scan for directories containing instrument banks
  289. */
  290. void Bank::rescanforbanks()
  291. {
  292. db->clear();
  293. //remove old banks
  294. banks.clear();
  295. for(int i = 0; i < MAX_BANK_ROOT_DIRS; ++i)
  296. if(!config->cfg.bankRootDirList[i].empty())
  297. scanrootdir(config->cfg.bankRootDirList[i]);
  298. #ifdef WIN32
  299. {
  300. //Search the VST Directory for banks/preset/etc
  301. char path[1024];
  302. GetModuleFileName(GetModuleHandle("ZynAddSubFX.dll"), path, sizeof(path));
  303. if(strstr(path, "ZynAddSubFX.dll")) {
  304. strstr(path, "ZynAddSubFX.dll")[0] = 0;
  305. strcat(path, "banks");
  306. scanrootdir(path);
  307. }
  308. }
  309. #endif
  310. //sort the banks
  311. sort(banks.begin(), banks.end());
  312. for(int i = 0; i < (int) banks.size(); ++i)
  313. db->addBankDir(banks[i].dir);
  314. //remove duplicate bank names
  315. for(int j = 0; j < (int) banks.size() - 1; ++j) {
  316. int dupl = 0;
  317. for(int i = j + 1; i < (int) banks.size(); ++i) {
  318. if(banks[i].name == banks[j].name) {
  319. //add a [1] to the first bankname and [n] to others
  320. banks[i].name = banks[i].name + '['
  321. + stringFrom(dupl + 2) + ']';
  322. dupl++;
  323. }
  324. }
  325. if(dupl != 0)
  326. banks[j].name += "[1]";
  327. if(dupl)
  328. j += dupl;
  329. }
  330. db->scanBanks();
  331. }
  332. void Bank::setMsb(uint8_t msb)
  333. {
  334. if(msb < banks.size() && banks[msb].dir != bankfiletitle)
  335. loadbank(banks[msb].dir);
  336. }
  337. void Bank::setLsb(uint8_t lsb)
  338. {
  339. //should only involve values of 0/1 for the time being...
  340. bank_lsb = limit<uint8_t>(lsb,0,1);
  341. }
  342. // private stuff
  343. void Bank::scanrootdir(string rootdir)
  344. {
  345. expanddirname(rootdir);
  346. DIR *dir = opendir(rootdir.c_str());
  347. if(dir == NULL)
  348. return;
  349. bankstruct bank;
  350. const char *separator = "/";
  351. if(rootdir.size()) {
  352. char tmp = rootdir[rootdir.size() - 1];
  353. if((tmp == '/') || (tmp == '\\'))
  354. separator = "";
  355. }
  356. struct dirent *fn;
  357. while((fn = readdir(dir))) {
  358. const char *dirname = fn->d_name;
  359. if(dirname[0] == '.')
  360. continue;
  361. bank.dir = rootdir + separator + dirname + '/';
  362. bank.name = dirname;
  363. //find out if the directory contains at least 1 instrument
  364. bool isbank = false;
  365. DIR *d = opendir(bank.dir.c_str());
  366. if(d == NULL)
  367. continue;
  368. struct dirent *fname;
  369. while((fname = readdir(d))) {
  370. if((strstr(fname->d_name, INSTRUMENT_EXTENSION) != NULL)
  371. || (strstr(fname->d_name, FORCE_BANK_DIR_FILE) != NULL)) {
  372. isbank = true;
  373. break; //could put a #instrument counter here instead
  374. }
  375. }
  376. if(isbank)
  377. banks.push_back(bank);
  378. closedir(d);
  379. }
  380. closedir(dir);
  381. }
  382. void Bank::clearbank()
  383. {
  384. for(int i = 0; i < BANK_SIZE; ++i)
  385. ins[i] = ins_t();
  386. bankfiletitle.clear();
  387. dirname.clear();
  388. }
  389. std::vector<std::string> Bank::search(std::string s) const
  390. {
  391. std::vector<std::string> out;
  392. auto vec = db->search(s);
  393. for(auto e:vec) {
  394. out.push_back(e.name);
  395. out.push_back(e.bank+e.file);
  396. }
  397. return out;
  398. }
  399. std::vector<std::string> Bank::blist(std::string s)
  400. {
  401. std::vector<std::string> out;
  402. loadbank(s);
  403. for(int i=0; i<128; ++i) {
  404. if(ins[i].filename.empty())
  405. out.push_back("Empty Preset");
  406. else
  407. out.push_back(ins[i].name);
  408. out.push_back(to_s(i));
  409. }
  410. return out;
  411. }
  412. int Bank::addtobank(int pos, string filename, string name)
  413. {
  414. if((pos >= 0) && (pos < BANK_SIZE)) {
  415. if(!ins[pos].filename.empty())
  416. pos = -1; //force it to find a new free position
  417. }
  418. else
  419. if(pos >= BANK_SIZE)
  420. pos = -1;
  421. if(pos < 0) //find a free position
  422. for(int i = BANK_SIZE - 1; i >= 0; i--)
  423. if(ins[i].filename.empty()) {
  424. pos = i;
  425. break;
  426. }
  427. if(pos < 0)
  428. return -1; //the bank is full
  429. deletefrombank(pos);
  430. ins[pos].name = name;
  431. ins[pos].filename = dirname + filename;
  432. return 0;
  433. }
  434. void Bank::deletefrombank(int pos)
  435. {
  436. if((pos < 0) || (pos >= BANK_SIZE))
  437. return;
  438. ins[pos] = ins_t();
  439. }
  440. Bank::ins_t::ins_t()
  441. :name(""), filename("")
  442. {}
  443. void Bank::expanddirname(std::string &dirname) {
  444. if (dirname.empty())
  445. return;
  446. // if the directory name starts with a ~ and the $HOME variable is
  447. // defined in the environment, replace ~ by the content of $HOME
  448. if (dirname.at(0) == '~') {
  449. char *home_dirname = getenv("HOME");
  450. if (home_dirname != NULL) {
  451. dirname = std::string(home_dirname) + dirname.substr(1);
  452. }
  453. }
  454. }
  455. void Bank::normalizedirsuffix(string &dirname) const {
  456. if(((dirname[dirname.size() - 1]) != '/')
  457. && ((dirname[dirname.size() - 1]) != '\\'))
  458. dirname += "/";
  459. }
  460. }