zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

Microtonal.cpp (27401B)


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