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 modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License (version 2 or later) for more details.
19
20 You should have received a copy of the GNU General Public License (version 2)
21 along with this program; if not, write to the Free Software Foundation,
22 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
24 */
25
26 #include "Bank.h"
27 #include <string.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <dirent.h>
31 #include <sys/stat.h>
32 #include <algorithm>
33 #include <iostream>
34
35 #include <sys/types.h>
36 #include <fcntl.h>
37 #include <unistd.h>
38 #include <errno.h>
39
40 #include "Config.h"
41 #include "Util.h"
42 #include "Part.h"
43
44 #define INSTRUMENT_EXTENSION ".xiz"
45
46 //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
47 #define FORCE_BANK_DIR_FILE ".bankdir"
48
49 using namespace std;
50
Bank()51 Bank::Bank()
52 :defaultinsname(" ")
53 {
54 clearbank();
55 bankfiletitle = dirname;
56 loadbank(config.cfg.currentBankDir);
57 }
58
~Bank()59 Bank::~Bank()
60 {
61 clearbank();
62 }
63
64 /*
65 * Get the name of an instrument from the bank
66 */
getname(unsigned int ninstrument)67 string Bank::getname(unsigned int ninstrument)
68 {
69 if(emptyslot(ninstrument))
70 return defaultinsname;
71 return ins[ninstrument].name;
72 }
73
74 /*
75 * Get the numbered name of an instrument from the bank
76 */
getnamenumbered(unsigned int ninstrument)77 string Bank::getnamenumbered(unsigned int ninstrument)
78 {
79 if(emptyslot(ninstrument))
80 return defaultinsname;
81
82 return stringFrom(ninstrument + 1) + ". " + getname(ninstrument);
83 }
84
85 /*
86 * Changes the name of an instrument (and the filename)
87 */
setname(unsigned int ninstrument,const string & newname,int newslot)88 void Bank::setname(unsigned int ninstrument, const string &newname, int newslot)
89 {
90 if(emptyslot(ninstrument))
91 return;
92
93 string newfilename;
94 char tmpfilename[100 + 1];
95 tmpfilename[100] = 0;
96
97 if(newslot >= 0)
98 snprintf(tmpfilename, 100, "%4d-%s", newslot + 1, newname.c_str());
99 else
100 snprintf(tmpfilename, 100, "%4d-%s", ninstrument + 1, newname.c_str());
101
102 //add the zeroes at the start of filename
103 for(int i = 0; i < 4; ++i)
104 if(tmpfilename[i] == ' ')
105 tmpfilename[i] = '0';
106
107 newfilename = dirname + '/' + legalizeFilename(tmpfilename) + ".xiz";
108
109 rename(ins[ninstrument].filename.c_str(), newfilename.c_str());
110
111 ins[ninstrument].filename = newfilename;
112 ins[ninstrument].name = newname;
113 }
114
115 /*
116 * Check if there is no instrument on a slot from the bank
117 */
emptyslot(unsigned int ninstrument)118 bool Bank::emptyslot(unsigned int ninstrument)
119 {
120 if(ninstrument >= BANK_SIZE)
121 return true;
122 if(ins[ninstrument].filename.empty())
123 return true;
124
125 if(ins[ninstrument].used)
126 return false;
127 else
128 return true;
129 }
130
131 /*
132 * Removes the instrument from the bank
133 */
clearslot(unsigned int ninstrument)134 void Bank::clearslot(unsigned int ninstrument)
135 {
136 if(emptyslot(ninstrument))
137 return;
138
139 remove(ins[ninstrument].filename.c_str());
140 deletefrombank(ninstrument);
141 }
142
143 /*
144 * Save the instrument to a slot
145 */
savetoslot(unsigned int ninstrument,Part * part)146 void Bank::savetoslot(unsigned int ninstrument, Part *part)
147 {
148 clearslot(ninstrument);
149
150 const int maxfilename = 200;
151 char tmpfilename[maxfilename + 20];
152 ZERO(tmpfilename, maxfilename + 20);
153
154 snprintf(tmpfilename,
155 maxfilename,
156 "%4d-%s",
157 ninstrument + 1,
158 (char *)part->Pname);
159
160 //add the zeroes at the start of filename
161 for(int i = 0; i < 4; ++i)
162 if(tmpfilename[i] == ' ')
163 tmpfilename[i] = '0';
164
165 string filename = dirname + '/' + legalizeFilename(tmpfilename) + ".xiz";
166
167 remove(filename.c_str());
168 part->saveXML(filename.c_str());
169 addtobank(ninstrument, legalizeFilename(tmpfilename) + ".xiz", (char *) part->Pname);
170 }
171
172 /*
173 * Loads the instrument from the bank
174 */
loadfromslot(unsigned int ninstrument,Part * part)175 void Bank::loadfromslot(unsigned int ninstrument, Part *part)
176 {
177 if(emptyslot(ninstrument))
178 return;
179
180 part->AllNotesOff();
181 part->defaultsinstrument();
182
183 part->loadXMLinstrument(ins[ninstrument].filename.c_str());
184 }
185
186 /*
187 * Makes current a bank directory
188 */
loadbank(string bankdirname)189 int Bank::loadbank(string bankdirname)
190 {
191 normalizedirsuffix(bankdirname);
192 DIR *dir = opendir(bankdirname.c_str());
193 clearbank();
194
195 if(dir == NULL)
196 return -1;
197
198 dirname = bankdirname;
199
200 bankfiletitle = dirname;
201
202 struct dirent *fn;
203
204 while((fn = readdir(dir))) {
205 const char *filename = fn->d_name;
206
207 //check for extension
208 if(strstr(filename, INSTRUMENT_EXTENSION) == NULL)
209 continue;
210
211 //verify if the name is like this NNNN-name (where N is a digit)
212 int no = 0;
213 unsigned int startname = 0;
214
215 for(unsigned int i = 0; i < 4; ++i) {
216 if(strlen(filename) <= i)
217 break;
218
219 if((filename[i] >= '0') && (filename[i] <= '9')) {
220 no = no * 10 + (filename[i] - '0');
221 startname++;
222 }
223 }
224
225 if((startname + 1) < strlen(filename))
226 startname++; //to take out the "-"
227
228 string name = filename;
229
230 //remove the file extension
231 for(int i = name.size() - 1; i >= 2; i--)
232 if(name[i] == '.') {
233 name = name.substr(0, i);
234 break;
235 }
236
237 if(no != 0) //the instrument position in the bank is found
238 addtobank(no - 1, filename, name.substr(startname));
239 else
240 addtobank(-1, filename, name);
241 }
242
243 closedir(dir);
244
245 if(!dirname.empty())
246 config.cfg.currentBankDir = dirname;
247
248 return 0;
249 }
250
251 /*
252 * Makes a new bank, put it on a file and makes it current bank
253 */
newbank(string newbankdirname)254 int Bank::newbank(string newbankdirname)
255 {
256 string bankdir;
257 bankdir = config.cfg.bankRootDirList[0];
258
259 expanddirname(bankdir);
260 normalizedirsuffix(bankdir);
261
262 // FIXME: Zyn should automatically handle creation of parent directory
263 #ifdef WIN32
264 if(mkdir(bankdir.c_str()) < 0) return -1;
265 #else
266 if(mkdir(bankdir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) return -1;
267 #endif
268
269 bankdir += newbankdirname;
270 #ifdef WIN32
271 if(mkdir(bankdir.c_str()) < 0)
272 #else
273 if(mkdir(bankdir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0)
274 #endif
275 return -1;
276
277 const string tmpfilename = bankdir + '/' + FORCE_BANK_DIR_FILE;
278
279 FILE *tmpfile = fopen(tmpfilename.c_str(), "w+");
280 fclose(tmpfile);
281
282 return loadbank(bankdir);
283 }
284
285 /*
286 * Check if the bank is locked (i.e. the file opened was readonly)
287 */
locked()288 int Bank::locked()
289 {
290 return dirname.empty();
291 }
292
293 /*
294 * Swaps a slot with another
295 */
swapslot(unsigned int n1,unsigned int n2)296 void Bank::swapslot(unsigned int n1, unsigned int n2)
297 {
298 if((n1 == n2) || (locked()))
299 return;
300 if(emptyslot(n1) && (emptyslot(n2)))
301 return;
302 if(emptyslot(n1)) //change n1 to n2 in order to make
303 swap(n1, n2);
304
305 if(emptyslot(n2)) { //this is just a movement from slot1 to slot2
306 setname(n1, getname(n1), n2);
307 ins[n2] = ins[n1];
308 ins[n1] = ins_t();
309 }
310 else { //if both slots are used
311 if(ins[n1].name == ins[n2].name) //change the name of the second instrument if the name are equal
312 ins[n2].name += "2";
313
314 setname(n1, getname(n1), n2);
315 setname(n2, getname(n2), n1);
316 swap(ins[n2], ins[n1]);
317 }
318 }
319
320
operator <(const bankstruct & b) const321 bool Bank::bankstruct::operator<(const bankstruct &b) const
322 {
323 return name < b.name;
324 }
325
326 /*
327 * Re-scan for directories containing instrument banks
328 */
329
rescanforbanks()330 void Bank::rescanforbanks()
331 {
332 //remove old banks
333 banks.clear();
334
335 for(int i = 0; i < MAX_BANK_ROOT_DIRS; ++i)
336 if(!config.cfg.bankRootDirList[i].empty())
337 scanrootdir(config.cfg.bankRootDirList[i]);
338
339 //sort the banks
340 sort(banks.begin(), banks.end());
341
342 //remove duplicate bank names
343 int dupl = 0;
344 for(int j = 0; j < (int) banks.size() - 1; ++j)
345 for(int i = j + 1; i < (int) banks.size(); ++i) {
346 if(banks[i].name == banks[j].name) {
347 //add a [1] to the first bankname and [n] to others
348 banks[i].name = banks[i].name + '['
349 + stringFrom(dupl + 2) + ']';
350 if(dupl == 0)
351 banks[j].name += "[1]";
352
353 dupl++;
354 }
355 else
356 dupl = 0;
357 }
358 }
359
360
361 // private stuff
362
scanrootdir(string rootdir)363 void Bank::scanrootdir(string rootdir)
364 {
365 expanddirname(rootdir);
366
367 DIR *dir = opendir(rootdir.c_str());
368 if(dir == NULL)
369 return;
370
371 bankstruct bank;
372
373 const char *separator = "/";
374 if(rootdir.size()) {
375 char tmp = rootdir[rootdir.size() - 1];
376 if((tmp == '/') || (tmp == '\\'))
377 separator = "";
378 }
379
380 struct dirent *fn;
381 while((fn = readdir(dir))) {
382 const char *dirname = fn->d_name;
383 if(dirname[0] == '.')
384 continue;
385
386 bank.dir = rootdir + separator + dirname + '/';
387 bank.name = dirname;
388 //find out if the directory contains at least 1 instrument
389 bool isbank = false;
390
391 DIR *d = opendir(bank.dir.c_str());
392 if(d == NULL)
393 continue;
394
395 struct dirent *fname;
396
397 while((fname = readdir(d))) {
398 if((strstr(fname->d_name, INSTRUMENT_EXTENSION) != NULL)
399 || (strstr(fname->d_name, FORCE_BANK_DIR_FILE) != NULL)) {
400 isbank = true;
401 break; //could put a #instrument counter here instead
402 }
403 }
404
405 if(isbank)
406 banks.push_back(bank);
407
408 closedir(d);
409 }
410
411 closedir(dir);
412 }
413
clearbank()414 void Bank::clearbank()
415 {
416 for(int i = 0; i < BANK_SIZE; ++i)
417 ins[i] = ins_t();
418
419 bankfiletitle.clear();
420 dirname.clear();
421 }
422
addtobank(int pos,string filename,string name)423 int Bank::addtobank(int pos, string filename, string name)
424 {
425 if((pos >= 0) && (pos < BANK_SIZE)) {
426 if(ins[pos].used)
427 pos = -1; //force it to find a new free position
428 }
429 else
430 if(pos >= BANK_SIZE)
431 pos = -1;
432
433
434 if(pos < 0) //find a free position
435 for(int i = BANK_SIZE - 1; i >= 0; i--)
436 if(!ins[i].used) {
437 pos = i;
438 break;
439 }
440
441 if(pos < 0)
442 return -1; //the bank is full
443
444 deletefrombank(pos);
445
446 ins[pos].used = true;
447 ins[pos].name = name;
448 ins[pos].filename = dirname + '/' + filename;
449
450 //see if PADsynth is used
451 if(config.cfg.CheckPADsynth) {
452 XMLwrapper xml;
453 xml.loadXMLfile(ins[pos].filename);
454
455 ins[pos].info.PADsynth_used = xml.hasPadSynth();
456 }
457 else
458 ins[pos].info.PADsynth_used = false;
459
460 return 0;
461 }
462
isPADsynth_used(unsigned int ninstrument)463 bool Bank::isPADsynth_used(unsigned int ninstrument)
464 {
465 if(config.cfg.CheckPADsynth == 0)
466 return 0;
467 else
468 return ins[ninstrument].info.PADsynth_used;
469 }
470
471
deletefrombank(int pos)472 void Bank::deletefrombank(int pos)
473 {
474 if((pos < 0) || (pos >= BANK_SIZE))
475 return;
476 ins[pos] = ins_t();
477 }
478
ins_t()479 Bank::ins_t::ins_t()
480 :used(false), name(""), filename("")
481 {
482 info.PADsynth_used = false;
483 }
484
expanddirname(std::string & dirname)485 void Bank::expanddirname(std::string &dirname) {
486 if (dirname.empty())
487 return;
488
489 // if the directory name starts with a ~ and the $HOME variable is
490 // defined in the environment, replace ~ by the content of $HOME
491 if (dirname.at(0) == '~') {
492 char *home_dirname = getenv("HOME");
493 if (home_dirname != NULL) {
494 dirname = std::string(home_dirname) + dirname.substr(1);
495 }
496 }
497 }
498
normalizedirsuffix(string & dirname) const499 void Bank::normalizedirsuffix(string &dirname) const {
500 if(((dirname[dirname.size() - 1]) != '/')
501 && ((dirname[dirname.size() - 1]) != '\\'))
502 dirname += "/";
503 }
504