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.

531 lines
12KB

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