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.

878 lines
26KB

  1. /*
  2. ZynAddSubFX - a software synthesizer
  3. Microtonal.cpp - Tuning settings and microtonal capabilities
  4. Copyright (C) 2002-2005 Nasca Octavian Paul
  5. Copyright (C) 2016 Mark McCurry
  6. Author: Nasca Octavian Paul
  7. This program is free software; you can redistribute it and/or
  8. modify it under the terms of the GNU General Public License
  9. as published by the Free Software Foundation; either version 2
  10. of the License, or (at your option) any later version.
  11. */
  12. #include <cmath>
  13. #include <cstring>
  14. #include <cstdio>
  15. #include <cassert>
  16. #include <rtosc/ports.h>
  17. #include <rtosc/port-sugar.h>
  18. #include "XMLwrapper.h"
  19. #include "Util.h"
  20. #include "Microtonal.h"
  21. using namespace rtosc;
  22. #define MAX_LINE_SIZE 80
  23. namespace zyncarla {
  24. #define rObject Microtonal
  25. /**
  26. * TODO
  27. * Consider how much of this should really exist on the rt side of things.
  28. * All the rt side needs is a function to map notes at various keyshifts to
  29. * frequencies, which does not require this many parameters...
  30. *
  31. * A good lookup table should be a good finalization of this
  32. */
  33. const rtosc::Ports Microtonal::ports = {
  34. rToggle(Pinvertupdown, rShort("inv."), rDefault(false),
  35. "key mapping inverse"),
  36. rParamZyn(Pinvertupdowncenter, rShort("center"), rDefault(60),
  37. "center of the inversion"),
  38. rToggle(Penabled, rShort("enable"), rDefault(false),
  39. "Enable for microtonal mode"),
  40. rParamZyn(PAnote, rShort("1/1 midi note"), rDefault(69),
  41. "The note for 'A'"),
  42. rParamF(PAfreq, rShort("ref freq"), rDefault(440.0f),
  43. "Frequency of the 'A' note"),
  44. rParamZyn(Pscaleshift, rShort("shift"), rDefault(64),
  45. "UNDOCUMENTED"),
  46. rParamZyn(Pfirstkey, rShort("first key"), rDefault(0),
  47. "First key to retune"),
  48. rParamZyn(Plastkey, rShort("last key"), rDefault(127),
  49. "Last key to retune"),
  50. rParamZyn(Pmiddlenote, rShort("middle"), rDefault(60),
  51. "Scale degree 0 note"),
  52. //TODO check to see if this should be exposed
  53. rParamZyn(Pmapsize, rDefault(12), "Size of key map"),
  54. rToggle(Pmappingenabled, rDefault(false), "Mapping Enable"),
  55. rParams(Pmapping, 128, rDefaultMissing, "Mapping of keys"),
  56. rParamZyn(Pglobalfinedetune, rShort("fine"), rDefault(64),
  57. "Fine detune for all notes"),
  58. rString(Pname, MICROTONAL_MAX_NAME_LEN, rShort("name"),
  59. rDefault("12tET"), "Microtonal Name"),
  60. rString(Pcomment, MICROTONAL_MAX_NAME_LEN, rShort("comment"),
  61. rDefault("Equal Temperament 12 notes per octave"), "Microtonal comments"),
  62. {"octavesize:", rDoc("Get octave size"), 0, [](const char*, RtData &d)
  63. {
  64. Microtonal &m = *(Microtonal*)d.obj;
  65. d.reply(d.loc, "i", m.getoctavesize());
  66. }},
  67. {"mapping::s", rDoc("Get user editable tunings"), 0, [](const char *msg, RtData &d)
  68. {
  69. char buf[100*MAX_OCTAVE_SIZE] = {0};
  70. char tmpbuf[100] = {0};
  71. Microtonal &m = *(Microtonal*)d.obj;
  72. if(rtosc_narguments(msg) == 1) {
  73. m.texttomapping(rtosc_argument(msg,0).s);
  74. } else {
  75. for (int i=0;i<m.Pmapsize;i++){
  76. if (i!=0)
  77. strncat(buf, "\n", sizeof(buf)-1);
  78. if (m.Pmapping[i]==-1)
  79. snprintf(tmpbuf,100,"x");
  80. else
  81. snprintf(tmpbuf,100,"%d",m.Pmapping[i]);
  82. strncat(buf, tmpbuf, sizeof(buf)-1);
  83. };
  84. d.reply(d.loc, "s", buf);
  85. }
  86. }},
  87. {"tunings::s", rDoc("Get user editable tunings"), 0, [](const char *msg, RtData &d)
  88. {
  89. char buf[100*MAX_OCTAVE_SIZE] = {0};
  90. char tmpbuf[100] = {0};
  91. Microtonal &m = *(Microtonal*)d.obj;
  92. if(rtosc_narguments(msg) == 1) {
  93. int err = m.texttotunings(rtosc_argument(msg,0).s);
  94. if (err>=0)
  95. d.reply("/alert", "s",
  96. "Parse Error: The input may contain only numbers (like 232.59)\n"
  97. "or divisions (like 121/64).");
  98. if (err==-2)
  99. d.reply("/alert", "s", "Parse Error: The input is empty.");
  100. } else {
  101. for (int i=0;i<m.getoctavesize();i++){
  102. if (i!=0)
  103. strncat(buf, "\n", sizeof(buf)-1);
  104. m.tuningtoline(i,tmpbuf,100);
  105. strncat(buf, tmpbuf, sizeof(buf)-1);
  106. };
  107. d.reply(d.loc, "s", buf);
  108. }
  109. }},
  110. #define COPY(x) self.x = other->x;
  111. {"paste:b", rProp(internal) rDoc("Clone Input Microtonal Object"), 0,
  112. [](const char *msg, RtData &d)
  113. {
  114. rtosc_blob_t b = rtosc_argument(msg, 0).b;
  115. assert(b.len == sizeof(void*));
  116. Microtonal *other = *(Microtonal**)b.data;
  117. Microtonal &self = *(Microtonal*)d.obj;
  118. //oh how I wish there was some darn reflection for this...
  119. COPY(Pinvertupdown);
  120. COPY(Pinvertupdowncenter);
  121. COPY(Penabled);
  122. COPY(PAnote);
  123. COPY(PAfreq);
  124. COPY(Pscaleshift);
  125. COPY(Pfirstkey);
  126. COPY(Plastkey);
  127. COPY(Pmiddlenote);
  128. COPY(Pmapsize);
  129. COPY(Pmappingenabled);
  130. for(int i=0; i<self.octavesize; ++i)
  131. self.octave[i] = other->octave[i];
  132. COPY(Pglobalfinedetune);
  133. memcpy(self.Pname, other->Pname, sizeof(self.Pname));
  134. memcpy(self.Pcomment, other->Pcomment, sizeof(self.Pcomment));
  135. COPY(octavesize);
  136. for(int i=0; i<self.octavesize; ++i)
  137. self.octave[i] = other->octave[i];
  138. d.reply("/free", "sb", "Microtonal", b.len, b.data);
  139. }},
  140. {"paste_scl:b", rProp(internal) rDoc("Clone Input scl Object"), 0,
  141. [](const char *msg, RtData &d)
  142. {
  143. rtosc_blob_t b = rtosc_argument(msg, 0).b;
  144. assert(b.len == sizeof(void*));
  145. SclInfo *other = *(SclInfo**)b.data;
  146. Microtonal &self = *(Microtonal*)d.obj;
  147. memcpy(self.Pname, other->Pname, sizeof(self.Pname));
  148. memcpy(self.Pcomment, other->Pcomment, sizeof(self.Pcomment));
  149. COPY(octavesize);
  150. for(int i=0; i<self.octavesize; ++i)
  151. self.octave[i] = other->octave[i];
  152. d.reply("/free", "sb", "SclInfo", b.len, b.data);
  153. }},
  154. {"paste_kbm:b", rProp(internal) rDoc("Clone Input kbm Object"), 0,
  155. [](const char *msg, RtData &d)
  156. {
  157. rtosc_blob_t b = rtosc_argument(msg, 0).b;
  158. assert(b.len == sizeof(void*));
  159. KbmInfo *other = *(KbmInfo**)b.data;
  160. Microtonal &self = *(Microtonal*)d.obj;
  161. COPY(Pmapsize);
  162. COPY(Pfirstkey);
  163. COPY(Plastkey);
  164. COPY(Pmiddlenote);
  165. COPY(PAnote);
  166. COPY(PAfreq);
  167. COPY(Pmappingenabled);
  168. for(int i=0; i<128; ++i)
  169. self.Pmapping[i] = other->Pmapping[i];
  170. d.reply("/free", "sb", "KbmInfo", b.len, b.data);
  171. }},
  172. #undef COPY
  173. };
  174. Microtonal::Microtonal(const int &gzip_compression)
  175. : gzip_compression(gzip_compression)
  176. {
  177. defaults();
  178. }
  179. void Microtonal::defaults()
  180. {
  181. Pinvertupdown = 0;
  182. Pinvertupdowncenter = 60;
  183. octavesize = 12;
  184. Penabled = 0;
  185. PAnote = 69;
  186. PAfreq = 440.0f;
  187. Pscaleshift = 64;
  188. Pfirstkey = 0;
  189. Plastkey = 127;
  190. Pmiddlenote = 60;
  191. Pmapsize = 12;
  192. Pmappingenabled = 0;
  193. for(int i = 0; i < 128; ++i)
  194. Pmapping[i] = i;
  195. for(int i = 0; i < MAX_OCTAVE_SIZE; ++i) {
  196. octave[i].tuning = powf(2, (i % octavesize + 1) / 12.0f);
  197. octave[i].type = 1;
  198. octave[i].x1 = (i % octavesize + 1) * 100;
  199. octave[i].x2 = 0;
  200. }
  201. octave[11].type = 2;
  202. octave[11].x1 = 2;
  203. octave[11].x2 = 1;
  204. for(int i = 0; i < MICROTONAL_MAX_NAME_LEN; ++i) {
  205. Pname[i] = '\0';
  206. Pcomment[i] = '\0';
  207. }
  208. snprintf((char *) Pname, MICROTONAL_MAX_NAME_LEN, "12tET");
  209. snprintf((char *) Pcomment,
  210. MICROTONAL_MAX_NAME_LEN,
  211. "Equal Temperament 12 notes per octave");
  212. Pglobalfinedetune = 64;
  213. }
  214. Microtonal::~Microtonal()
  215. {}
  216. /*
  217. * Get the size of the octave
  218. */
  219. unsigned char Microtonal::getoctavesize() const
  220. {
  221. if(Penabled != 0)
  222. return octavesize;
  223. else
  224. return 12;
  225. }
  226. /*
  227. * Get the frequency according the note number
  228. */
  229. float Microtonal::getnotefreq(int note, int keyshift) const
  230. {
  231. // in this function will appears many times things like this:
  232. // var=(a+b*100)%b
  233. // I had written this way because if I use var=a%b gives unwanted results when a<0
  234. // This is the same with divisions.
  235. if((Pinvertupdown != 0) && ((Pmappingenabled == 0) || (Penabled == 0)))
  236. note = (int) Pinvertupdowncenter * 2 - note;
  237. //compute global fine detune
  238. float globalfinedetunerap =
  239. powf(2.0f, (Pglobalfinedetune - 64.0f) / 1200.0f); //-64.0f .. 63.0f cents
  240. if(Penabled == 0) //12tET
  241. return powf(2.0f,
  242. (note - PAnote
  243. + keyshift) / 12.0f) * PAfreq * globalfinedetunerap;
  244. int scaleshift =
  245. ((int)Pscaleshift - 64 + (int) octavesize * 100) % octavesize;
  246. //compute the keyshift
  247. float rap_keyshift = 1.0f;
  248. if(keyshift != 0) {
  249. int kskey = (keyshift + (int)octavesize * 100) % octavesize;
  250. int ksoct = (keyshift + (int)octavesize * 100) / octavesize - 100;
  251. rap_keyshift = (kskey == 0) ? (1.0f) : (octave[kskey - 1].tuning);
  252. rap_keyshift *= powf(octave[octavesize - 1].tuning, ksoct);
  253. }
  254. //if the mapping is enabled
  255. if(Pmappingenabled) {
  256. if((note < Pfirstkey) || (note > Plastkey))
  257. return -1.0f;
  258. //Compute how many mapped keys are from middle note to reference note
  259. //and find out the proportion between the freq. of middle note and "A" note
  260. int tmp = PAnote - Pmiddlenote, minus = 0;
  261. if(tmp < 0) {
  262. tmp = -tmp;
  263. minus = 1;
  264. }
  265. int deltanote = 0;
  266. for(int i = 0; i < tmp; ++i)
  267. if(Pmapping[i % Pmapsize] >= 0)
  268. deltanote++;
  269. float rap_anote_middlenote =
  270. (deltanote ==
  271. 0) ? (1.0f) : (octave[(deltanote - 1) % octavesize].tuning);
  272. if(deltanote)
  273. rap_anote_middlenote *=
  274. powf(octave[octavesize - 1].tuning,
  275. (deltanote - 1) / octavesize);
  276. if(minus)
  277. rap_anote_middlenote = 1.0f / rap_anote_middlenote;
  278. //Convert from note (midi) to degree (note from the tunning)
  279. int degoct =
  280. (note - (int)Pmiddlenote + (int) Pmapsize
  281. * 200) / (int)Pmapsize - 200;
  282. int degkey = (note - Pmiddlenote + (int)Pmapsize * 100) % Pmapsize;
  283. degkey = Pmapping[degkey];
  284. if(degkey < 0)
  285. return -1.0f; //this key is not mapped
  286. //invert the keyboard upside-down if it is asked for
  287. //TODO: do the right way by using Pinvertupdowncenter
  288. if(Pinvertupdown != 0) {
  289. degkey = octavesize - degkey - 1;
  290. degoct = -degoct;
  291. }
  292. //compute the frequency of the note
  293. degkey = degkey + scaleshift;
  294. degoct += degkey / octavesize;
  295. degkey %= octavesize;
  296. float freq = (degkey == 0) ? (1.0f) : octave[degkey - 1].tuning;
  297. freq *= powf(octave[octavesize - 1].tuning, degoct);
  298. freq *= PAfreq / rap_anote_middlenote;
  299. freq *= globalfinedetunerap;
  300. if(scaleshift)
  301. freq /= octave[scaleshift - 1].tuning;
  302. return freq * rap_keyshift;
  303. }
  304. else { //if the mapping is disabled
  305. int nt = note - PAnote + scaleshift;
  306. int ntkey = (nt + (int)octavesize * 100) % octavesize;
  307. int ntoct = (nt - ntkey) / octavesize;
  308. float oct = octave[octavesize - 1].tuning;
  309. float freq =
  310. octave[(ntkey + octavesize - 1) % octavesize].tuning * powf(oct,
  311. ntoct)
  312. * PAfreq;
  313. if(!ntkey)
  314. freq /= oct;
  315. if(scaleshift)
  316. freq /= octave[scaleshift - 1].tuning;
  317. freq *= globalfinedetunerap;
  318. return freq * rap_keyshift;
  319. }
  320. }
  321. bool Microtonal::operator==(const Microtonal &micro) const
  322. {
  323. return !(*this != micro);
  324. }
  325. bool Microtonal::operator!=(const Microtonal &micro) const
  326. {
  327. //A simple macro to test equality MiCRotonal EQuals (not the perfect
  328. //approach, but good enough)
  329. #define MCREQ(x) if(x != micro.x) \
  330. return true
  331. //for floats
  332. #define FMCREQ(x) if(!((x < micro.x + 0.0001f) && (x > micro.x - 0.0001f))) \
  333. return true
  334. MCREQ(Pinvertupdown);
  335. MCREQ(Pinvertupdowncenter);
  336. MCREQ(octavesize);
  337. MCREQ(Penabled);
  338. MCREQ(PAnote);
  339. FMCREQ(PAfreq);
  340. MCREQ(Pscaleshift);
  341. MCREQ(Pfirstkey);
  342. MCREQ(Plastkey);
  343. MCREQ(Pmiddlenote);
  344. MCREQ(Pmapsize);
  345. MCREQ(Pmappingenabled);
  346. for(int i = 0; i < 128; ++i)
  347. MCREQ(Pmapping[i]);
  348. for(int i = 0; i < octavesize; ++i) {
  349. FMCREQ(octave[i].tuning);
  350. MCREQ(octave[i].type);
  351. MCREQ(octave[i].x1);
  352. MCREQ(octave[i].x2);
  353. }
  354. if(strcmp((const char *)this->Pname, (const char *)micro.Pname))
  355. return true;
  356. if(strcmp((const char *)this->Pcomment, (const char *)micro.Pcomment))
  357. return true;
  358. MCREQ(Pglobalfinedetune);
  359. return false;
  360. //undefine macros, as they are no longer needed
  361. #undef MCREQ
  362. #undef FMCREQ
  363. }
  364. /*
  365. * Convert a line to tunings; returns -1 if it ok
  366. */
  367. int Microtonal::linetotunings(OctaveTuning &octave, const char *line)
  368. {
  369. int x1 = -1, x2 = -1, type = -1;
  370. float x = -1.0f, tmp, tuning = 1.0f;
  371. if(strstr(line, "/") == NULL) {
  372. if(strstr(line, ".") == NULL) { // M case (M=M/1)
  373. sscanf(line, "%d", &x1);
  374. x2 = 1;
  375. type = 2; //division
  376. }
  377. else { // float number case
  378. sscanf(line, "%f", &x);
  379. if(x < 0.000001f)
  380. return 1;
  381. type = 1; //float type(cents)
  382. }
  383. }
  384. else { // M/N case
  385. sscanf(line, "%d/%d", &x1, &x2);
  386. if((x1 < 0) || (x2 < 0))
  387. return 1;
  388. if(x2 == 0)
  389. x2 = 1;
  390. type = 2; //division
  391. }
  392. if(x1 <= 0)
  393. x1 = 1; //not allow zero frequency sounds (consider 0 as 1)
  394. //convert to float if the number are too big
  395. if((type == 2)
  396. && ((x1 > (128 * 128 * 128 - 1)) || (x2 > (128 * 128 * 128 - 1)))) {
  397. type = 1;
  398. x = ((float) x1) / x2;
  399. }
  400. switch(type) {
  401. case 1:
  402. x1 = (int) floor(x);
  403. tmp = fmod(x, 1.0f);
  404. x2 = (int) (floor(tmp * 1e6));
  405. tuning = powf(2.0f, x / 1200.0f);
  406. break;
  407. case 2:
  408. x = ((float)x1) / x2;
  409. tuning = x;
  410. break;
  411. }
  412. octave.tuning = tuning;
  413. octave.type = type;
  414. octave.x1 = x1;
  415. octave.x2 = x2;
  416. return -1; //ok
  417. }
  418. /*
  419. * Convert the text to tunnings
  420. */
  421. int Microtonal::texttotunings(const char *text)
  422. {
  423. unsigned int k = 0, nl = 0;
  424. char *lin = new char[MAX_LINE_SIZE + 1];
  425. OctaveTuning tmpoctave[MAX_OCTAVE_SIZE];
  426. while(k < strlen(text)) {
  427. int i;
  428. for(i = 0; i < MAX_LINE_SIZE; ++i) {
  429. lin[i] = text[k++];
  430. if(lin[i] < 0x20)
  431. break;
  432. }
  433. lin[i] = '\0';
  434. if(strlen(lin) == 0)
  435. continue;
  436. int err = linetotunings(tmpoctave[nl], lin);
  437. if(err != -1) {
  438. delete [] lin;
  439. return nl; //Parse error
  440. }
  441. nl++;
  442. }
  443. delete [] lin;
  444. if(nl > MAX_OCTAVE_SIZE)
  445. nl = MAX_OCTAVE_SIZE;
  446. if(nl == 0)
  447. return -2; //the input is empty
  448. octavesize = nl;
  449. for(int i = 0; i < octavesize; ++i) {
  450. octave[i].tuning = tmpoctave[i].tuning;
  451. octave[i].type = tmpoctave[i].type;
  452. octave[i].x1 = tmpoctave[i].x1;
  453. octave[i].x2 = tmpoctave[i].x2;
  454. }
  455. return -1; //ok
  456. }
  457. /*
  458. * Convert the text to mapping
  459. */
  460. void Microtonal::texttomapping(const char *text)
  461. {
  462. unsigned int i, k = 0;
  463. char *lin;
  464. lin = new char[MAX_LINE_SIZE + 1];
  465. for(i = 0; i < 128; ++i)
  466. Pmapping[i] = -1;
  467. int tx = 0;
  468. while(k < strlen(text)) {
  469. for(i = 0; i < MAX_LINE_SIZE; ++i) {
  470. lin[i] = text[k++];
  471. if(lin[i] < 0x20)
  472. break;
  473. }
  474. lin[i] = '\0';
  475. if(strlen(lin) == 0)
  476. continue;
  477. int tmp = 0;
  478. if(sscanf(lin, "%d", &tmp) == 0)
  479. tmp = -1;
  480. if(tmp < -1)
  481. tmp = -1;
  482. Pmapping[tx] = tmp;
  483. if((tx++) > 127)
  484. break;
  485. }
  486. delete [] lin;
  487. if(tx == 0)
  488. tx = 1;
  489. Pmapsize = tx;
  490. }
  491. /*
  492. * Convert tunning to text line
  493. */
  494. void Microtonal::tuningtoline(int n, char *line, int maxn)
  495. {
  496. if((n > octavesize) || (n > MAX_OCTAVE_SIZE)) {
  497. line[0] = '\0';
  498. return;
  499. }
  500. if(octave[n].type == 1)
  501. snprintf(line, maxn, "%d.%06d", octave[n].x1, octave[n].x2);
  502. if(octave[n].type == 2)
  503. snprintf(line, maxn, "%d/%d", octave[n].x1, octave[n].x2);
  504. }
  505. int Microtonal::loadline(FILE *file, char *line)
  506. {
  507. memset(line, 0, 500);
  508. do {
  509. if(fgets(line, 500, file) == 0)
  510. return 1;
  511. } while(line[0] == '!');
  512. return 0;
  513. }
  514. /*
  515. * Loads the tunnings from a scl file
  516. */
  517. int Microtonal::loadscl(SclInfo &scl, const char *filename)
  518. {
  519. FILE *file = fopen(filename, "r");
  520. char tmp[500];
  521. OctaveTuning tmpoctave[MAX_OCTAVE_SIZE];
  522. if(!file)
  523. return 2;
  524. fseek(file, 0, SEEK_SET);
  525. //loads the short description
  526. if(loadline(file, &tmp[0]) != 0)
  527. return 2;
  528. for(int i = 0; i < 500; ++i)
  529. if(tmp[i] < 32)
  530. tmp[i] = 0;
  531. snprintf(scl.Pname, MICROTONAL_MAX_NAME_LEN, "%s", tmp);
  532. snprintf(scl.Pcomment, MICROTONAL_MAX_NAME_LEN, "%s", tmp);
  533. //loads the number of the notes
  534. if(loadline(file, &tmp[0]) != 0)
  535. return 2;
  536. int nnotes = MAX_OCTAVE_SIZE;
  537. sscanf(&tmp[0], "%d", &nnotes);
  538. if(nnotes > MAX_OCTAVE_SIZE)
  539. return 2;
  540. //load the tunnings
  541. for(int nline = 0; nline < nnotes; ++nline) {
  542. if(loadline(file, &tmp[0]) != 0)
  543. return 2;
  544. linetotunings(tmpoctave[nline], tmp);
  545. }
  546. fclose(file);
  547. scl.octavesize = nnotes;
  548. for(int i = 0; i < scl.octavesize; ++i) {
  549. scl.octave[i].tuning = tmpoctave[i].tuning;
  550. scl.octave[i].type = tmpoctave[i].type;
  551. scl.octave[i].x1 = tmpoctave[i].x1;
  552. scl.octave[i].x2 = tmpoctave[i].x2;
  553. }
  554. return 0;
  555. }
  556. /*
  557. * Loads the mapping from a kbm file
  558. */
  559. int Microtonal::loadkbm(KbmInfo &kbm, const char *filename)
  560. {
  561. FILE *file = fopen(filename, "r");
  562. int x;
  563. float tmpPAfreq = 440.0f;
  564. char tmp[500];
  565. if(!file)
  566. return 2;
  567. fseek(file, 0, SEEK_SET);
  568. //loads the mapsize
  569. if(loadline(file, tmp) != 0 || sscanf(tmp, "%d", &x) == 0)
  570. return 2;
  571. kbm.Pmapsize = limit(x, 0, 127);
  572. //loads first MIDI note to retune
  573. if(loadline(file, tmp) != 0 || sscanf(tmp, "%d", &x) == 0)
  574. return 2;
  575. kbm.Pfirstkey = limit(x, 0, 127);
  576. //loads last MIDI note to retune
  577. if(loadline(file, tmp) != 0 || sscanf(tmp, "%d", &x) == 0)
  578. return 2;
  579. kbm.Plastkey = limit(x, 0, 127);
  580. //loads last the middle note where scale fro scale degree=0
  581. if(loadline(file, tmp) != 0 || sscanf(tmp, "%d", &x) == 0)
  582. return 2;
  583. kbm.Pmiddlenote = limit(x, 0, 127);
  584. //loads the reference note
  585. if(loadline(file, tmp) != 0 || sscanf(tmp, "%d", &x) == 0)
  586. return 2;
  587. kbm.PAnote = limit(x,0,127);
  588. //loads the reference freq.
  589. if(loadline(file, tmp) != 0 || sscanf(tmp, "%f", &tmpPAfreq) == 0)
  590. return 2;
  591. kbm.PAfreq = tmpPAfreq;
  592. //the scale degree(which is the octave) is not loaded,
  593. //it is obtained by the tunnings with getoctavesize() method
  594. if(loadline(file, &tmp[0]) != 0)
  595. return 2;
  596. //load the mappings
  597. if(kbm.Pmapsize != 0) {
  598. for(int nline = 0; nline < kbm.Pmapsize; ++nline) {
  599. if(loadline(file, tmp) != 0)
  600. return 2;
  601. if(sscanf(tmp, "%d", &x) == 0)
  602. x = -1;
  603. kbm.Pmapping[nline] = x;
  604. }
  605. kbm.Pmappingenabled = 1;
  606. }
  607. else {
  608. kbm.Pmappingenabled = 0;
  609. kbm.Pmapping[0] = 0;
  610. kbm.Pmapsize = 1;
  611. }
  612. fclose(file);
  613. return 0;
  614. }
  615. void Microtonal::add2XML(XMLwrapper& xml) const
  616. {
  617. xml.addparstr("name", (char *) Pname);
  618. xml.addparstr("comment", (char *) Pcomment);
  619. xml.addparbool("invert_up_down", Pinvertupdown);
  620. xml.addpar("invert_up_down_center", Pinvertupdowncenter);
  621. xml.addparbool("enabled", Penabled);
  622. xml.addpar("global_fine_detune", Pglobalfinedetune);
  623. xml.addpar("a_note", PAnote);
  624. xml.addparreal("a_freq", PAfreq);
  625. if((Penabled == 0) && (xml.minimal))
  626. return;
  627. xml.beginbranch("SCALE");
  628. xml.addpar("scale_shift", Pscaleshift);
  629. xml.addpar("first_key", Pfirstkey);
  630. xml.addpar("last_key", Plastkey);
  631. xml.addpar("middle_note", Pmiddlenote);
  632. xml.beginbranch("OCTAVE");
  633. xml.addpar("octave_size", octavesize);
  634. for(int i = 0; i < octavesize; ++i) {
  635. xml.beginbranch("DEGREE", i);
  636. if(octave[i].type == 1)
  637. xml.addparreal("cents", octave[i].tuning);
  638. ;
  639. if(octave[i].type == 2) {
  640. xml.addpar("numerator", octave[i].x1);
  641. xml.addpar("denominator", octave[i].x2);
  642. }
  643. xml.endbranch();
  644. }
  645. xml.endbranch();
  646. xml.beginbranch("KEYBOARD_MAPPING");
  647. xml.addpar("map_size", Pmapsize);
  648. xml.addpar("mapping_enabled", Pmappingenabled);
  649. for(int i = 0; i < Pmapsize; ++i) {
  650. xml.beginbranch("KEYMAP", i);
  651. xml.addpar("degree", Pmapping[i]);
  652. xml.endbranch();
  653. }
  654. xml.endbranch();
  655. xml.endbranch();
  656. }
  657. void Microtonal::getfromXML(XMLwrapper& xml)
  658. {
  659. xml.getparstr("name", (char *) Pname, MICROTONAL_MAX_NAME_LEN);
  660. xml.getparstr("comment", (char *) Pcomment, MICROTONAL_MAX_NAME_LEN);
  661. Pinvertupdown = xml.getparbool("invert_up_down", Pinvertupdown);
  662. Pinvertupdowncenter = xml.getpar127("invert_up_down_center",
  663. Pinvertupdowncenter);
  664. Penabled = xml.getparbool("enabled", Penabled);
  665. Pglobalfinedetune = xml.getpar127("global_fine_detune", Pglobalfinedetune);
  666. PAnote = xml.getpar127("a_note", PAnote);
  667. PAfreq = xml.getparreal("a_freq", PAfreq, 1.0f, 10000.0f);
  668. if(xml.enterbranch("SCALE")) {
  669. Pscaleshift = xml.getpar127("scale_shift", Pscaleshift);
  670. Pfirstkey = xml.getpar127("first_key", Pfirstkey);
  671. Plastkey = xml.getpar127("last_key", Plastkey);
  672. Pmiddlenote = xml.getpar127("middle_note", Pmiddlenote);
  673. if(xml.enterbranch("OCTAVE")) {
  674. octavesize = xml.getpar127("octave_size", octavesize);
  675. for(int i = 0; i < octavesize; ++i) {
  676. if(xml.enterbranch("DEGREE", i) == 0)
  677. continue;
  678. octave[i].x2 = 0;
  679. octave[i].tuning = xml.getparreal("cents", octave[i].tuning);
  680. octave[i].x1 = xml.getpar127("numerator", octave[i].x1);
  681. octave[i].x2 = xml.getpar127("denominator", octave[i].x2);
  682. if(octave[i].x2 != 0)
  683. octave[i].type = 2;
  684. else {
  685. octave[i].type = 1;
  686. //populate fields for display
  687. float x = logf(octave[i].tuning) / LOG_2 * 1200.0f;
  688. octave[i].x1 = (int) floor(x);
  689. octave[i].x2 = (int) (floor((x-octave[i].x1) * 1.0e6));
  690. }
  691. xml.exitbranch();
  692. }
  693. xml.exitbranch();
  694. }
  695. if(xml.enterbranch("KEYBOARD_MAPPING")) {
  696. Pmapsize = xml.getpar127("map_size", Pmapsize);
  697. Pmappingenabled = xml.getpar127("mapping_enabled", Pmappingenabled);
  698. for(int i = 0; i < Pmapsize; ++i) {
  699. if(xml.enterbranch("KEYMAP", i) == 0)
  700. continue;
  701. Pmapping[i] = xml.getpar127("degree", Pmapping[i]);
  702. xml.exitbranch();
  703. }
  704. xml.exitbranch();
  705. }
  706. xml.exitbranch();
  707. }
  708. apply();
  709. }
  710. int Microtonal::saveXML(const char *filename) const
  711. {
  712. XMLwrapper xml;
  713. xml.beginbranch("MICROTONAL");
  714. add2XML(xml);
  715. xml.endbranch();
  716. return xml.saveXMLfile(filename, gzip_compression);
  717. }
  718. int Microtonal::loadXML(const char *filename)
  719. {
  720. XMLwrapper xml;
  721. if(xml.loadXMLfile(filename) < 0) {
  722. return -1;
  723. }
  724. if(xml.enterbranch("MICROTONAL") == 0)
  725. return -10;
  726. getfromXML(xml);
  727. xml.exitbranch();
  728. return 0;
  729. }
  730. //roundabout function, but it works
  731. void Microtonal::apply(void)
  732. {
  733. {
  734. char buf[100*MAX_OCTAVE_SIZE] = {0};
  735. char tmpbuf[100] = {0};
  736. for (int i=0;i<Pmapsize;i++) {
  737. if (i!=0)
  738. strncat(buf, "\n", sizeof(buf)-1);
  739. if (Pmapping[i]==-1)
  740. snprintf(tmpbuf,100,"x");
  741. else
  742. snprintf(tmpbuf,100,"%d",Pmapping[i]);
  743. strncat(buf, tmpbuf, sizeof(buf)-1);
  744. }
  745. texttomapping(buf);
  746. }
  747. {
  748. char buf[100*MAX_OCTAVE_SIZE] = {0};
  749. char tmpbuf[100] = {0};
  750. for (int i=0;i<octavesize;i++){
  751. if (i!=0)
  752. strncat(buf, "\n", sizeof(buf)-1);
  753. tuningtoline(i,tmpbuf,100);
  754. strncat(buf, tmpbuf, sizeof(buf)-1);
  755. }
  756. int err = texttotunings(buf);
  757. (void) err;
  758. }
  759. }
  760. }