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.

346 lines
7.6KB

  1. #include "BankDb.h"
  2. #include "XMLwrapper.h"
  3. #include "Util.h"
  4. #include "../globals.h"
  5. #include <cstring>
  6. #include <dirent.h>
  7. #include <sys/stat.h>
  8. #define INSTRUMENT_EXTENSION ".xiz"
  9. using std::string;
  10. typedef BankDb::svec svec;
  11. typedef BankDb::bvec bvec;
  12. BankEntry::BankEntry(void)
  13. :id(0), add(false), pad(false), sub(false), time(0)
  14. {}
  15. bool platform_strcasestr(const char *hay, const char *needle)
  16. {
  17. int n = strlen(hay);
  18. int m = strlen(needle);
  19. for(int i=0; i<n; i++) {
  20. int good = 1;
  21. for(int j=0; j<m; ++j) {
  22. if(toupper(hay[i+j]) != toupper(needle[j])) {
  23. good = 0;
  24. break;
  25. }
  26. }
  27. if(good)
  28. return 1;
  29. }
  30. return 0;
  31. }
  32. bool sfind(std::string hay, std::string needle)
  33. {
  34. //return strcasestr(hay.c_str(), needle.c_str());
  35. return platform_strcasestr(hay.c_str(), needle.c_str());
  36. return false;
  37. }
  38. bool BankEntry::match(string s) const
  39. {
  40. if(s == "#pad")
  41. return pad;
  42. else if(s == "#sub")
  43. return sub;
  44. else if(s == "#add")
  45. return add;
  46. return sfind(file,s) || sfind(name,s) || sfind(bank, s) ||
  47. sfind(type, s) || sfind(comments,s) || sfind(author,s);
  48. }
  49. bool BankEntry::operator<(const BankEntry &b) const
  50. {
  51. return this->file < b.file;
  52. }
  53. static svec split(string s)
  54. {
  55. svec vec;
  56. string ss;
  57. for(char c:s) {
  58. if(isspace(c) && !ss.empty()) {
  59. vec.push_back(ss);
  60. ss.clear();
  61. } else if(!isspace(c)) {
  62. ss.push_back(c);
  63. }
  64. }
  65. if(!ss.empty())
  66. vec.push_back(ss);
  67. return vec;
  68. }
  69. static string line(string s)
  70. {
  71. string ss;
  72. for(char c:s) {
  73. if(c != '\n')
  74. ss.push_back(c);
  75. else
  76. return ss;
  77. }
  78. return ss;
  79. }
  80. bvec BankDb::search(std::string ss) const
  81. {
  82. bvec vec;
  83. const svec sterm = split(ss);
  84. for(auto field:fields) {
  85. bool match = true;
  86. for(auto s:sterm)
  87. match &= field.match(s);
  88. if(match)
  89. vec.push_back(field);
  90. }
  91. std::sort(vec.begin(), vec.end());
  92. return vec;
  93. }
  94. void BankDb::addBankDir(std::string bnk)
  95. {
  96. bool repeat = false;
  97. for(auto b:banks)
  98. repeat |= b == bnk;
  99. if(!repeat)
  100. banks.push_back(bnk);
  101. }
  102. void BankDb::clear(void)
  103. {
  104. banks.clear();
  105. fields.clear();
  106. }
  107. static std::string getCacheName(void)
  108. {
  109. char name[512] = {0};
  110. snprintf(name, sizeof(name), "%s%s", getenv("HOME"),
  111. "/.zynaddsubfx-bank-cache.xml");
  112. return name;
  113. }
  114. static bvec loadCache(void)
  115. {
  116. bvec cache;
  117. XMLwrapper xml;
  118. xml.loadXMLfile(getCacheName());
  119. if(xml.enterbranch("bank-cache")) {
  120. auto nodes = xml.getBranch();
  121. for(auto node:nodes) {
  122. BankEntry be;
  123. #define bind(x,y) if(node.has(#x)) {be.x = y(node[#x].c_str());}
  124. bind(file, string);
  125. bind(bank, string);
  126. bind(name, string);
  127. bind(comments, string);
  128. bind(author, string);
  129. bind(type, atoi);
  130. bind(id, atoi);
  131. bind(add, atoi);
  132. bind(pad, atoi);
  133. bind(sub, atoi);
  134. bind(time, atoi);
  135. #undef bind
  136. cache.push_back(be);
  137. }
  138. }
  139. return cache;
  140. }
  141. static void saveCache(bvec vec)
  142. {
  143. XMLwrapper xml;
  144. xml.beginbranch("bank-cache");
  145. for(auto value:vec) {
  146. XmlNode binding("instrument-entry");
  147. #define bind(x) binding[#x] = to_s(value.x);
  148. bind(file);
  149. bind(bank);
  150. bind(name);
  151. bind(comments);
  152. bind(author);
  153. bind(type);
  154. bind(id);
  155. bind(add);
  156. bind(pad);
  157. bind(sub);
  158. bind(time);
  159. #undef bind
  160. xml.add(binding);
  161. }
  162. xml.endbranch();
  163. xml.saveXMLfile(getCacheName(), 0);
  164. }
  165. void BankDb::scanBanks(void)
  166. {
  167. fields.clear();
  168. bvec cache = loadCache();
  169. bmap cc;
  170. for(auto c:cache)
  171. cc[c.bank + c.file] = c;
  172. bvec ncache;
  173. for(auto bank:banks)
  174. {
  175. DIR *dir = opendir(bank.c_str());
  176. if(!dir)
  177. continue;
  178. struct dirent *fn;
  179. while((fn = readdir(dir))) {
  180. const char *filename = fn->d_name;
  181. //check for extension
  182. if(!strstr(filename, INSTRUMENT_EXTENSION))
  183. continue;
  184. auto xiz = processXiz(filename, bank, cc);
  185. fields.push_back(xiz);
  186. ncache.push_back(xiz);
  187. }
  188. closedir(dir);
  189. }
  190. saveCache(ncache);
  191. }
  192. BankEntry BankDb::processXiz(std::string filename,
  193. std::string bank, bmap &cache) const
  194. {
  195. string fname = bank+filename;
  196. #ifdef WIN32
  197. int ret, time = 0;
  198. #else
  199. //Grab a timestamp
  200. struct stat st;
  201. int ret = lstat(fname.c_str(), &st);
  202. int time = 0;
  203. if(ret != -1)
  204. time = st.st_mtim.tv_sec;
  205. #endif
  206. //quickly check if the file exists in the cache and if it is up-to-date
  207. if(cache.find(fname) != cache.end() &&
  208. cache[fname].time == time)
  209. return cache[fname];
  210. //verify if the name is like this NNNN-name (where N is a digit)
  211. int no = 0;
  212. unsigned int startname = 0;
  213. for(unsigned int i = 0; i < 4; ++i) {
  214. if(filename.length() <= i)
  215. break;
  216. if(isdigit(filename[i])) {
  217. no = no * 10 + (filename[i] - '0');
  218. startname++;
  219. }
  220. }
  221. if(startname + 1 < filename.length())
  222. startname++; //to take out the "-"
  223. std::string name = filename;
  224. //remove the file extension
  225. for(int i = name.size() - 1; i >= 2; i--) {
  226. if(name[i] == '.') {
  227. name = name.substr(0, i);
  228. break;
  229. }
  230. }
  231. BankEntry entry;
  232. entry.file = filename;
  233. entry.bank = bank;
  234. entry.id = no;
  235. entry.time = time;
  236. if(no != 0) //the instrument position in the bank is found
  237. entry.name = name.substr(startname);
  238. else
  239. entry.name = name;
  240. const char *types[] = {
  241. "None",
  242. "Piano",
  243. "Chromatic Percussion",
  244. "Organ",
  245. "Guitar",
  246. "Bass",
  247. "Solo Strings",
  248. "Ensemble",
  249. "Brass",
  250. "Reed",
  251. "Pipe",
  252. "Synth Lead",
  253. "Synth Pad",
  254. "Synth Effects",
  255. "Ethnic",
  256. "Percussive",
  257. "Sound Effects",
  258. };
  259. //Try to obtain other metadata (expensive)
  260. XMLwrapper xml;
  261. ret = xml.loadXMLfile(fname);
  262. if(xml.enterbranch("INSTRUMENT")) {
  263. if(xml.enterbranch("INFO")) {
  264. char author[1024];
  265. char comments[1024];
  266. int type = 0;
  267. xml.getparstr("author", author, 1024);
  268. xml.getparstr("comments", comments, 1024);
  269. type = xml.getpar("type", 0, 0, 16);
  270. entry.author = author;
  271. entry.comments = comments;
  272. entry.type = types[type];
  273. xml.exitbranch();
  274. }
  275. if(xml.enterbranch("INSTRUMENT_KIT")) {
  276. for(int i = 0; i < NUM_KIT_ITEMS; ++i) {
  277. if(xml.enterbranch("INSTRUMENT_KIT_ITEM", i) == 0) {
  278. entry.add |= xml.getparbool("add_enabled", false);
  279. entry.sub |= xml.getparbool("sub_enabled", false);
  280. entry.pad |= xml.getparbool("pad_enabled", false);
  281. xml.exitbranch();
  282. }
  283. }
  284. xml.exitbranch();
  285. }
  286. xml.exitbranch();
  287. }
  288. //printf("Bank Entry:\n");
  289. //printf("\tname - %s\n", entry.name.c_str());
  290. //printf("\tauthor - %s\n", line(entry.author).c_str());
  291. //printf("\tbank - %s\n", entry.bank.c_str());
  292. //printf("\tadd/pad/sub - %d/%d/%d\n", entry.add, entry.pad, entry.sub);
  293. return entry;
  294. }