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