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