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.

483 lines
11KB

  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 modify
  9. it under the terms of version 2 of the GNU General Public License
  10. as published by the Free Software Foundation.
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. GNU General Public License (version 2 or later) for more details.
  15. You should have received a copy of the GNU General Public License (version 2)
  16. along with this program; if not, write to the Free Software Foundation,
  17. Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  18. */
  19. #include "Bank.h"
  20. #include <cstring>
  21. #include <cstdio>
  22. #include <cstdlib>
  23. #include <dirent.h>
  24. #include <sys/stat.h>
  25. #include <algorithm>
  26. #include <sys/types.h>
  27. #include <fcntl.h>
  28. #include <unistd.h>
  29. #include <errno.h>
  30. #include "Config.h"
  31. #include "Util.h"
  32. #include "Part.h"
  33. #define 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. #define FORCE_BANK_DIR_FILE ".bankdir"
  36. using namespace std;
  37. Bank::Bank()
  38. :bankpos(0), defaultinsname(" ")
  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. }
  55. /*
  56. * Get the name of an instrument from the bank
  57. */
  58. string Bank::getname(unsigned int ninstrument)
  59. {
  60. if(emptyslot(ninstrument))
  61. return defaultinsname;
  62. return ins[ninstrument].name;
  63. }
  64. /*
  65. * Get the numbered name of an instrument from the bank
  66. */
  67. string Bank::getnamenumbered(unsigned int ninstrument)
  68. {
  69. if(emptyslot(ninstrument))
  70. return defaultinsname;
  71. return stringFrom(ninstrument + 1) + ". " + getname(ninstrument);
  72. }
  73. /*
  74. * Changes the name of an instrument (and the filename)
  75. */
  76. int Bank::setname(unsigned int ninstrument, const string &newname, int newslot)
  77. {
  78. if(emptyslot(ninstrument))
  79. return 0;
  80. string newfilename;
  81. char tmpfilename[100 + 1];
  82. tmpfilename[100] = 0;
  83. if(newslot >= 0)
  84. snprintf(tmpfilename, 100, "%4d-%s", newslot + 1, newname.c_str());
  85. else
  86. snprintf(tmpfilename, 100, "%4d-%s", ninstrument + 1, newname.c_str());
  87. //add the zeroes at the start of filename
  88. for(int i = 0; i < 4; ++i)
  89. if(tmpfilename[i] == ' ')
  90. tmpfilename[i] = '0';
  91. newfilename = dirname + legalizeFilename(tmpfilename) + ".xiz";
  92. int err = rename(ins[ninstrument].filename.c_str(), newfilename.c_str());
  93. if(err)
  94. return err;
  95. ins[ninstrument].filename = newfilename;
  96. ins[ninstrument].name = newname;
  97. return err;
  98. }
  99. /*
  100. * Check if there is no instrument on a slot from the bank
  101. */
  102. bool Bank::emptyslot(unsigned int ninstrument)
  103. {
  104. if(ninstrument >= BANK_SIZE)
  105. return true;
  106. if(ins[ninstrument].filename.empty())
  107. return true;
  108. return false;
  109. }
  110. /*
  111. * Removes the instrument from the bank
  112. */
  113. int Bank::clearslot(unsigned int ninstrument)
  114. {
  115. if(emptyslot(ninstrument))
  116. return 0;
  117. //no error when no file
  118. FILE *f = fopen(ins[ninstrument].filename.c_str(), "r");
  119. if(!f)
  120. return 0;
  121. fclose(f);
  122. int err = remove(ins[ninstrument].filename.c_str());
  123. if(!err)
  124. deletefrombank(ninstrument);
  125. return err;
  126. }
  127. /*
  128. * Save the instrument to a slot
  129. */
  130. int Bank::savetoslot(unsigned int ninstrument, Part *part)
  131. {
  132. int err = clearslot(ninstrument);
  133. if(err)
  134. return err;
  135. const int maxfilename = 200;
  136. char tmpfilename[maxfilename + 20];
  137. ZERO(tmpfilename, maxfilename + 20);
  138. snprintf(tmpfilename,
  139. maxfilename,
  140. "%4d-%s",
  141. ninstrument + 1,
  142. (char *)part->Pname);
  143. //add the zeroes at the start of filename
  144. for(int i = 0; i < 4; ++i)
  145. if(tmpfilename[i] == ' ')
  146. tmpfilename[i] = '0';
  147. string filename = dirname + '/' + legalizeFilename(tmpfilename) + ".xiz";
  148. FILE *f = fopen(filename.c_str(), "r");
  149. if(f) {
  150. fclose(f);
  151. err = remove(filename.c_str());
  152. if(err)
  153. return err;
  154. }
  155. err = part->saveXML(filename.c_str());
  156. if(err)
  157. return err;
  158. addtobank(ninstrument, legalizeFilename(tmpfilename) + ".xiz", (char *) part->Pname);
  159. return 0;
  160. }
  161. /*
  162. * Loads the instrument from the bank
  163. */
  164. int Bank::loadfromslot(unsigned int ninstrument, Part *part)
  165. {
  166. if(emptyslot(ninstrument))
  167. return 0;
  168. part->AllNotesOff();
  169. part->defaultsinstrument();
  170. part->loadXMLinstrument(ins[ninstrument].filename.c_str());
  171. return 0;
  172. }
  173. /*
  174. * Makes current a bank directory
  175. */
  176. int Bank::loadbank(string bankdirname)
  177. {
  178. DIR *dir = opendir(bankdirname.c_str());
  179. clearbank();
  180. if(dir == NULL)
  181. return -1;
  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. if(((bankdir[bankdir.size() - 1]) != '/')
  228. && ((bankdir[bankdir.size() - 1]) != '\\'))
  229. bankdir += "/";
  230. bankdir += newbankdirname;
  231. if(mkdir(bankdir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0)
  232. return -1;
  233. const string tmpfilename = bankdir + '/' + FORCE_BANK_DIR_FILE;
  234. FILE *tmpfile = fopen(tmpfilename.c_str(), "w+");
  235. fclose(tmpfile);
  236. return loadbank(bankdir);
  237. }
  238. /*
  239. * Check if the bank is locked (i.e. the file opened was readonly)
  240. */
  241. int Bank::locked()
  242. {
  243. //XXX Fixme
  244. return dirname.empty();
  245. }
  246. /*
  247. * Swaps a slot with another
  248. */
  249. int Bank::swapslot(unsigned int n1, unsigned int n2)
  250. {
  251. int err = 0;
  252. if((n1 == n2) || (locked()))
  253. return 0;
  254. if(emptyslot(n1) && (emptyslot(n2)))
  255. return 0;
  256. if(emptyslot(n1)) //change n1 to n2 in order to make
  257. swap(n1, n2);
  258. if(emptyslot(n2)) { //this is just a movement from slot1 to slot2
  259. err |= setname(n1, getname(n1), n2);
  260. if(err)
  261. return err;
  262. ins[n2] = ins[n1];
  263. ins[n1] = ins_t();
  264. }
  265. else { //if both slots are used
  266. if(ins[n1].name == ins[n2].name) //change the name of the second instrument if the name are equal
  267. ins[n2].name += "2";
  268. err |= setname(n1, getname(n1), n2);
  269. err |= setname(n2, getname(n2), n1);
  270. if(err)
  271. return err;
  272. swap(ins[n2], ins[n1]);
  273. }
  274. return err;
  275. }
  276. bool Bank::bankstruct::operator<(const bankstruct &b) const
  277. {
  278. return name < b.name;
  279. }
  280. /*
  281. * Re-scan for directories containing instrument banks
  282. */
  283. void Bank::rescanforbanks()
  284. {
  285. //remove old banks
  286. banks.clear();
  287. for(int i = 0; i < MAX_BANK_ROOT_DIRS; ++i)
  288. if(!config.cfg.bankRootDirList[i].empty())
  289. scanrootdir(config.cfg.bankRootDirList[i]);
  290. //sort the banks
  291. sort(banks.begin(), banks.end());
  292. //remove duplicate bank names
  293. int dupl = 0;
  294. for(int j = 0; j < (int) banks.size() - 1; ++j)
  295. for(int i = j + 1; i < (int) banks.size(); ++i) {
  296. if(banks[i].name == banks[j].name) {
  297. //add a [1] to the first bankname and [n] to others
  298. banks[i].name = banks[i].name + '['
  299. + stringFrom(dupl + 2) + ']';
  300. if(dupl == 0)
  301. banks[j].name += "[1]";
  302. dupl++;
  303. }
  304. else
  305. dupl = 0;
  306. }
  307. }
  308. // private stuff
  309. void Bank::scanrootdir(string rootdir)
  310. {
  311. DIR *dir = opendir(rootdir.c_str());
  312. if(dir == NULL)
  313. return;
  314. bankstruct bank;
  315. const char *separator = "/";
  316. if(rootdir.size()) {
  317. char tmp = rootdir[rootdir.size() - 1];
  318. if((tmp == '/') || (tmp == '\\'))
  319. separator = "";
  320. }
  321. struct dirent *fn;
  322. while((fn = readdir(dir))) {
  323. const char *dirname = fn->d_name;
  324. if(dirname[0] == '.')
  325. continue;
  326. bank.dir = rootdir + separator + dirname + '/';
  327. bank.name = dirname;
  328. //find out if the directory contains at least 1 instrument
  329. bool isbank = false;
  330. DIR *d = opendir(bank.dir.c_str());
  331. if(d == NULL)
  332. continue;
  333. struct dirent *fname;
  334. while((fname = readdir(d))) {
  335. if((strstr(fname->d_name, INSTRUMENT_EXTENSION) != NULL)
  336. || (strstr(fname->d_name, FORCE_BANK_DIR_FILE) != NULL)) {
  337. isbank = true;
  338. break; //could put a #instrument counter here instead
  339. }
  340. }
  341. if(isbank)
  342. banks.push_back(bank);
  343. closedir(d);
  344. }
  345. closedir(dir);
  346. }
  347. void Bank::clearbank()
  348. {
  349. for(int i = 0; i < BANK_SIZE; ++i)
  350. ins[i] = ins_t();
  351. bankfiletitle.clear();
  352. dirname.clear();
  353. }
  354. int Bank::addtobank(int pos, string filename, string name)
  355. {
  356. if((pos >= 0) && (pos < BANK_SIZE)) {
  357. if(!ins[pos].filename.empty())
  358. pos = -1; //force it to find a new free position
  359. }
  360. else
  361. if(pos >= BANK_SIZE)
  362. pos = -1;
  363. if(pos < 0) //find a free position
  364. for(int i = BANK_SIZE - 1; i >= 0; i--)
  365. if(ins[i].filename.empty()) {
  366. pos = i;
  367. break;
  368. }
  369. if(pos < 0)
  370. return -1; //the bank is full
  371. deletefrombank(pos);
  372. ins[pos].name = name;
  373. ins[pos].filename = dirname + filename;
  374. return 0;
  375. }
  376. void Bank::deletefrombank(int pos)
  377. {
  378. if((pos < 0) || (pos >= BANK_SIZE))
  379. return;
  380. ins[pos] = ins_t();
  381. }
  382. Bank::ins_t::ins_t()
  383. :name(""), filename("")
  384. {}