|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 |
- /*
- ZynAddSubFX - a software synthesizer
-
- Microtonal.cpp - Tuning settings and microtonal capabilities
- Copyright (C) 2002-2005 Nasca Octavian Paul
- Author: Nasca Octavian Paul
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of version 2 of the GNU General Public License
- as published by the Free Software Foundation.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License (version 2 or later) for more details.
-
- You should have received a copy of the GNU General Public License (version 2)
- along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
- */
-
- #include <math.h>
- #include <string.h>
- #include "Microtonal.h"
-
- #define MAX_LINE_SIZE 80
-
- Microtonal::Microtonal()
- {
- Pname = new unsigned char[MICROTONAL_MAX_NAME_LEN];
- Pcomment = new unsigned char[MICROTONAL_MAX_NAME_LEN];
- defaults();
- }
-
- void Microtonal::defaults()
- {
- Pinvertupdown = 0;
- Pinvertupdowncenter = 60;
- octavesize = 12;
- Penabled = 0;
- PAnote = 69;
- PAfreq = 440.0f;
- Pscaleshift = 64;
-
- Pfirstkey = 0;
- Plastkey = 127;
- Pmiddlenote = 60;
- Pmapsize = 12;
- Pmappingenabled = 0;
-
- for(int i = 0; i < 128; ++i)
- Pmapping[i] = i;
-
- for(int i = 0; i < MAX_OCTAVE_SIZE; ++i) {
- octave[i].tuning = tmpoctave[i].tuning = powf(
- 2,
- (i % octavesize
- + 1) / 12.0f);
- octave[i].type = tmpoctave[i].type = 1;
- octave[i].x1 = tmpoctave[i].x1 = (i % octavesize + 1) * 100;
- octave[i].x2 = tmpoctave[i].x2 = 0;
- }
- octave[11].type = 2;
- octave[11].x1 = 2;
- octave[11].x2 = 1;
- for(int i = 0; i < MICROTONAL_MAX_NAME_LEN; ++i) {
- Pname[i] = '\0';
- Pcomment[i] = '\0';
- }
- snprintf((char *) Pname, MICROTONAL_MAX_NAME_LEN, "12tET");
- snprintf((char *) Pcomment,
- MICROTONAL_MAX_NAME_LEN,
- "Equal Temperament 12 notes per octave");
- Pglobalfinedetune = 64;
- }
-
- Microtonal::~Microtonal()
- {
- delete [] Pname;
- delete [] Pcomment;
- }
-
- /*
- * Get the size of the octave
- */
- unsigned char Microtonal::getoctavesize() const
- {
- if(Penabled != 0)
- return octavesize;
- else
- return 12;
- }
-
- /*
- * Get the frequency according the note number
- */
- float Microtonal::getnotefreq(int note, int keyshift) const
- {
- // in this function will appears many times things like this:
- // var=(a+b*100)%b
- // I had written this way because if I use var=a%b gives unwanted results when a<0
- // This is the same with divisions.
-
- if((Pinvertupdown != 0) && ((Pmappingenabled == 0) || (Penabled == 0)))
- note = (int) Pinvertupdowncenter * 2 - note;
-
- //compute global fine detune
- float globalfinedetunerap = powf(2.0f,
- (Pglobalfinedetune - 64.0f) / 1200.0f); //-64.0f .. 63.0f cents
-
- if(Penabled == 0)
- return powf(2.0f,
- (note - PAnote
- + keyshift) / 12.0f) * PAfreq * globalfinedetunerap; //12tET
-
- int scaleshift =
- ((int)Pscaleshift - 64 + (int) octavesize * 100) % octavesize;
-
- //compute the keyshift
- float rap_keyshift = 1.0f;
- if(keyshift != 0) {
- int kskey = (keyshift + (int)octavesize * 100) % octavesize;
- int ksoct = (keyshift + (int)octavesize * 100) / octavesize - 100;
- rap_keyshift = (kskey == 0) ? (1.0f) : (octave[kskey - 1].tuning);
- rap_keyshift *= powf(octave[octavesize - 1].tuning, ksoct);
- }
-
- //if the mapping is enabled
- if(Pmappingenabled != 0) {
- if((note < Pfirstkey) || (note > Plastkey))
- return -1.0f;
- //Compute how many mapped keys are from middle note to reference note
- //and find out the proportion between the freq. of middle note and "A" note
- int tmp = PAnote - Pmiddlenote, minus = 0;
- if(tmp < 0) {
- tmp = -tmp;
- minus = 1;
- }
- int deltanote = 0;
- for(int i = 0; i < tmp; ++i)
- if(Pmapping[i % Pmapsize] >= 0)
- deltanote++;
- float rap_anote_middlenote =
- (deltanote ==
- 0) ? (1.0f) : (octave[(deltanote - 1) % octavesize].tuning);
- if(deltanote != 0)
- rap_anote_middlenote *=
- powf(octave[octavesize - 1].tuning,
- (deltanote - 1) / octavesize);
- if(minus != 0)
- rap_anote_middlenote = 1.0f / rap_anote_middlenote;
-
- //Convert from note (midi) to degree (note from the tunning)
- int degoct =
- (note - (int)Pmiddlenote + (int) Pmapsize
- * 200) / (int)Pmapsize - 200;
- int degkey = (note - Pmiddlenote + (int)Pmapsize * 100) % Pmapsize;
- degkey = Pmapping[degkey];
- if(degkey < 0)
- return -1.0f; //this key is not mapped
-
- //invert the keyboard upside-down if it is asked for
- //TODO: do the right way by using Pinvertupdowncenter
- if(Pinvertupdown != 0) {
- degkey = octavesize - degkey - 1;
- degoct = -degoct;
- }
- //compute the frequency of the note
- degkey = degkey + scaleshift;
- degoct += degkey / octavesize;
- degkey %= octavesize;
-
- float freq = (degkey == 0) ? (1.0f) : octave[degkey - 1].tuning;
- freq *= powf(octave[octavesize - 1].tuning, degoct);
- freq *= PAfreq / rap_anote_middlenote;
- freq *= globalfinedetunerap;
- if(scaleshift != 0)
- freq /= octave[scaleshift - 1].tuning;
- return freq * rap_keyshift;
- }
- else { //if the mapping is disabled
- int nt = note - PAnote + scaleshift;
- int ntkey = (nt + (int)octavesize * 100) % octavesize;
- int ntoct = (nt - ntkey) / octavesize;
-
- float oct = octave[octavesize - 1].tuning;
- float freq =
- octave[(ntkey + octavesize - 1) % octavesize].tuning * powf(oct,
- ntoct)
- * PAfreq;
- if(ntkey == 0)
- freq /= oct;
- if(scaleshift != 0)
- freq /= octave[scaleshift - 1].tuning;
- // fprintf(stderr,"note=%d freq=%.3f cents=%d\n",note,freq,(int)floor(logf(freq/PAfreq)/logf(2.0f)*1200.0f+0.5f));
- freq *= globalfinedetunerap;
- return freq * rap_keyshift;
- }
- }
-
- bool Microtonal::operator==(const Microtonal µ) const
- {
- return !(*this != micro);
- }
-
- bool Microtonal::operator!=(const Microtonal µ) const
- {
- //A simple macro to test equality MiCRotonal EQuals (not the perfect
- //approach, but good enough)
- #define MCREQ(x) if(x != micro.x) \
- return true
-
- //for floats
- #define FMCREQ(x) if(!((x < micro.x + 0.0001f) && (x > micro.x - 0.0001f))) \
- return true
-
- MCREQ(Pinvertupdown);
- MCREQ(Pinvertupdowncenter);
- MCREQ(octavesize);
- MCREQ(Penabled);
- MCREQ(PAnote);
- FMCREQ(PAfreq);
- MCREQ(Pscaleshift);
-
- MCREQ(Pfirstkey);
- MCREQ(Plastkey);
- MCREQ(Pmiddlenote);
- MCREQ(Pmapsize);
- MCREQ(Pmappingenabled);
-
- for(int i = 0; i < 128; ++i)
- MCREQ(Pmapping[i]);
-
- for(int i = 0; i < octavesize; ++i) {
- FMCREQ(octave[i].tuning);
- MCREQ(octave[i].type);
- MCREQ(octave[i].x1);
- MCREQ(octave[i].x2);
- }
- if(strcmp((const char *)this->Pname, (const char *)micro.Pname))
- return true;
- if(strcmp((const char *)this->Pcomment, (const char *)micro.Pcomment))
- return true;
- MCREQ(Pglobalfinedetune);
- return false;
-
- //undefine macros, as they are no longer needed
- #undef MCREQ
- #undef FMCREQ
- }
-
-
- /*
- * Convert a line to tunings; returns -1 if it ok
- */
- int Microtonal::linetotunings(unsigned int nline, const char *line)
- {
- int x1 = -1, x2 = -1, type = -1;
- float x = -1.0f, tmp, tuning = 1.0f;
- if(strstr(line, "/") == NULL) {
- if(strstr(line, ".") == NULL) { // M case (M=M/1)
- sscanf(line, "%d", &x1);
- x2 = 1;
- type = 2; //division
- }
- else { // float number case
- sscanf(line, "%f", &x);
- if(x < 0.000001f)
- return 1;
- type = 1; //float type(cents)
- }
- }
- else { // M/N case
- sscanf(line, "%d/%d", &x1, &x2);
- if((x1 < 0) || (x2 < 0))
- return 1;
- if(x2 == 0)
- x2 = 1;
- type = 2; //division
- }
-
- if(x1 <= 0)
- x1 = 1; //not allow zero frequency sounds (consider 0 as 1)
-
- //convert to float if the number are too big
- if((type == 2)
- && ((x1 > (128 * 128 * 128 - 1)) || (x2 > (128 * 128 * 128 - 1)))) {
- type = 1;
- x = ((float) x1) / x2;
- }
- switch(type) {
- case 1:
- x1 = (int) floor(x);
- tmp = fmod(x, 1.0f);
- x2 = (int) (floor(tmp * 1e6));
- tuning = powf(2.0f, x / 1200.0f);
- break;
- case 2:
- x = ((float)x1) / x2;
- tuning = x;
- break;
- }
-
- tmpoctave[nline].tuning = tuning;
- tmpoctave[nline].type = type;
- tmpoctave[nline].x1 = x1;
- tmpoctave[nline].x2 = x2;
-
- return -1; //ok
- }
-
- /*
- * Convert the text to tunnings
- */
- int Microtonal::texttotunings(const char *text)
- {
- unsigned int i, k = 0, nl = 0;
- char *lin;
- lin = new char[MAX_LINE_SIZE + 1];
- while(k < strlen(text)) {
- for(i = 0; i < MAX_LINE_SIZE; ++i) {
- lin[i] = text[k++];
- if(lin[i] < 0x20)
- break;
- }
- lin[i] = '\0';
- if(strlen(lin) == 0)
- continue;
- int err = linetotunings(nl, lin);
- if(err != -1) {
- delete [] lin;
- return nl; //Parse error
- }
- nl++;
- }
- delete [] lin;
- if(nl > MAX_OCTAVE_SIZE)
- nl = MAX_OCTAVE_SIZE;
- if(nl == 0)
- return -2; //the input is empty
- octavesize = nl;
- for(i = 0; i < octavesize; ++i) {
- octave[i].tuning = tmpoctave[i].tuning;
- octave[i].type = tmpoctave[i].type;
- octave[i].x1 = tmpoctave[i].x1;
- octave[i].x2 = tmpoctave[i].x2;
- }
- return -1; //ok
- }
-
- /*
- * Convert the text to mapping
- */
- void Microtonal::texttomapping(const char *text)
- {
- unsigned int i, k = 0;
- char *lin;
- lin = new char[MAX_LINE_SIZE + 1];
- for(i = 0; i < 128; ++i)
- Pmapping[i] = -1;
- int tx = 0;
- while(k < strlen(text)) {
- for(i = 0; i < MAX_LINE_SIZE; ++i) {
- lin[i] = text[k++];
- if(lin[i] < 0x20)
- break;
- }
- lin[i] = '\0';
- if(strlen(lin) == 0)
- continue;
-
- int tmp = 0;
- if(sscanf(lin, "%d", &tmp) == 0)
- tmp = -1;
- if(tmp < -1)
- tmp = -1;
- Pmapping[tx] = tmp;
-
- if((tx++) > 127)
- break;
- }
- delete [] lin;
-
- if(tx == 0)
- tx = 1;
- Pmapsize = tx;
- }
-
- /*
- * Convert tunning to text line
- */
- void Microtonal::tuningtoline(int n, char *line, int maxn)
- {
- if((n > octavesize) || (n > MAX_OCTAVE_SIZE)) {
- line[0] = '\0';
- return;
- }
- if(octave[n].type == 1)
- snprintf(line, maxn, "%d.%06d", octave[n].x1, octave[n].x2);
- if(octave[n].type == 2)
- snprintf(line, maxn, "%d/%d", octave[n].x1, octave[n].x2);
- }
-
-
- int Microtonal::loadline(FILE *file, char *line)
- {
- do {
- if(fgets(line, 500, file) == 0)
- return 1;
- } while(line[0] == '!');
- return 0;
- }
- /*
- * Loads the tunnings from a scl file
- */
- int Microtonal::loadscl(const char *filename)
- {
- FILE *file = fopen(filename, "r");
- char tmp[500];
- fseek(file, 0, SEEK_SET);
- //loads the short description
- if(loadline(file, &tmp[0]) != 0)
- return 2;
- for(int i = 0; i < 500; ++i)
- if(tmp[i] < 32)
- tmp[i] = 0;
- snprintf((char *) Pname, MICROTONAL_MAX_NAME_LEN, "%s", tmp);
- snprintf((char *) Pcomment, MICROTONAL_MAX_NAME_LEN, "%s", tmp);
- //loads the number of the notes
- if(loadline(file, &tmp[0]) != 0)
- return 2;
- int nnotes = MAX_OCTAVE_SIZE;
- sscanf(&tmp[0], "%d", &nnotes);
- if(nnotes > MAX_OCTAVE_SIZE)
- return 2;
- //load the tunnings
- for(int nline = 0; nline < nnotes; ++nline) {
- if(loadline(file, &tmp[0]) != 0)
- return 2;
- linetotunings(nline, &tmp[0]);
- }
- fclose(file);
-
- octavesize = nnotes;
- for(int i = 0; i < octavesize; ++i) {
- octave[i].tuning = tmpoctave[i].tuning;
- octave[i].type = tmpoctave[i].type;
- octave[i].x1 = tmpoctave[i].x1;
- octave[i].x2 = tmpoctave[i].x2;
- }
-
- return 0;
- }
-
- /*
- * Loads the mapping from a kbm file
- */
- int Microtonal::loadkbm(const char *filename)
- {
- FILE *file = fopen(filename, "r");
- int x;
- char tmp[500];
-
- fseek(file, 0, SEEK_SET);
- //loads the mapsize
- if(loadline(file, &tmp[0]) != 0)
- return 2;
- if(sscanf(&tmp[0], "%d", &x) == 0)
- return 2;
- if(x < 1)
- x = 0;
- if(x > 127)
- x = 127; //just in case...
- Pmapsize = x;
- //loads first MIDI note to retune
- if(loadline(file, &tmp[0]) != 0)
- return 2;
- if(sscanf(&tmp[0], "%d", &x) == 0)
- return 2;
- if(x < 1)
- x = 0;
- if(x > 127)
- x = 127; //just in case...
- Pfirstkey = x;
- //loads last MIDI note to retune
- if(loadline(file, &tmp[0]) != 0)
- return 2;
- if(sscanf(&tmp[0], "%d", &x) == 0)
- return 2;
- if(x < 1)
- x = 0;
- if(x > 127)
- x = 127; //just in case...
- Plastkey = x;
- //loads last the middle note where scale fro scale degree=0
- if(loadline(file, &tmp[0]) != 0)
- return 2;
- if(sscanf(&tmp[0], "%d", &x) == 0)
- return 2;
- if(x < 1)
- x = 0;
- if(x > 127)
- x = 127; //just in case...
- Pmiddlenote = x;
- //loads the reference note
- if(loadline(file, &tmp[0]) != 0)
- return 2;
- if(sscanf(&tmp[0], "%d", &x) == 0)
- return 2;
- if(x < 1)
- x = 0;
- if(x > 127)
- x = 127; //just in case...
- PAnote = x;
- //loads the reference freq.
- if(loadline(file, &tmp[0]) != 0)
- return 2;
- float tmpPAfreq = 440.0f;
- if(sscanf(&tmp[0], "%f", &tmpPAfreq) == 0)
- return 2;
- PAfreq = tmpPAfreq;
-
- //the scale degree(which is the octave) is not loaded, it is obtained by the tunnings with getoctavesize() method
- if(loadline(file, &tmp[0]) != 0)
- return 2;
-
- //load the mappings
- if(Pmapsize != 0) {
- for(int nline = 0; nline < Pmapsize; ++nline) {
- if(loadline(file, &tmp[0]) != 0)
- return 2;
- if(sscanf(&tmp[0], "%d", &x) == 0)
- x = -1;
- Pmapping[nline] = x;
- }
- Pmappingenabled = 1;
- }
- else {
- Pmappingenabled = 0;
- Pmapping[0] = 0;
- Pmapsize = 1;
- }
- fclose(file);
-
- return 0;
- }
-
-
-
- void Microtonal::add2XML(XMLwrapper *xml) const
- {
- xml->addparstr("name", (char *) Pname);
- xml->addparstr("comment", (char *) Pcomment);
-
- xml->addparbool("invert_up_down", Pinvertupdown);
- xml->addpar("invert_up_down_center", Pinvertupdowncenter);
-
- xml->addparbool("enabled", Penabled);
- xml->addpar("global_fine_detune", Pglobalfinedetune);
-
- xml->addpar("a_note", PAnote);
- xml->addparreal("a_freq", PAfreq);
-
- if((Penabled == 0) && (xml->minimal))
- return;
-
- xml->beginbranch("SCALE");
- xml->addpar("scale_shift", Pscaleshift);
- xml->addpar("first_key", Pfirstkey);
- xml->addpar("last_key", Plastkey);
- xml->addpar("middle_note", Pmiddlenote);
-
- xml->beginbranch("OCTAVE");
- xml->addpar("octave_size", octavesize);
- for(int i = 0; i < octavesize; ++i) {
- xml->beginbranch("DEGREE", i);
- if(octave[i].type == 1)
- xml->addparreal("cents", octave[i].tuning);
- ;
- if(octave[i].type == 2) {
- xml->addpar("numerator", octave[i].x1);
- xml->addpar("denominator", octave[i].x2);
- }
- xml->endbranch();
- }
- xml->endbranch();
-
- xml->beginbranch("KEYBOARD_MAPPING");
- xml->addpar("map_size", Pmapsize);
- xml->addpar("mapping_enabled", Pmappingenabled);
- for(int i = 0; i < Pmapsize; ++i) {
- xml->beginbranch("KEYMAP", i);
- xml->addpar("degree", Pmapping[i]);
- xml->endbranch();
- }
-
- xml->endbranch();
- xml->endbranch();
- }
-
- void Microtonal::getfromXML(XMLwrapper *xml)
- {
- xml->getparstr("name", (char *) Pname, MICROTONAL_MAX_NAME_LEN);
- xml->getparstr("comment", (char *) Pcomment, MICROTONAL_MAX_NAME_LEN);
-
- Pinvertupdown = xml->getparbool("invert_up_down", Pinvertupdown);
- Pinvertupdowncenter = xml->getpar127("invert_up_down_center",
- Pinvertupdowncenter);
-
- Penabled = xml->getparbool("enabled", Penabled);
- Pglobalfinedetune = xml->getpar127("global_fine_detune", Pglobalfinedetune);
-
- PAnote = xml->getpar127("a_note", PAnote);
- PAfreq = xml->getparreal("a_freq", PAfreq, 1.0f, 10000.0f);
-
- if(xml->enterbranch("SCALE")) {
- Pscaleshift = xml->getpar127("scale_shift", Pscaleshift);
- Pfirstkey = xml->getpar127("first_key", Pfirstkey);
- Plastkey = xml->getpar127("last_key", Plastkey);
- Pmiddlenote = xml->getpar127("middle_note", Pmiddlenote);
-
- if(xml->enterbranch("OCTAVE")) {
- octavesize = xml->getpar127("octave_size", octavesize);
- for(int i = 0; i < octavesize; ++i) {
- if(xml->enterbranch("DEGREE", i) == 0)
- continue;
- octave[i].x2 = 0;
- octave[i].tuning = xml->getparreal("cents", octave[i].tuning);
- octave[i].x1 = xml->getpar127("numerator", octave[i].x1);
- octave[i].x2 = xml->getpar127("denominator", octave[i].x2);
-
- if(octave[i].x2 != 0)
- octave[i].type = 2;
- else {
- octave[i].type = 1;
- //populate fields for display
- float x = logf(octave[i].tuning) / LOG_2 * 1200.0f;
- octave[i].x1 = (int) floor(x);
- octave[i].x2 = (int) (floor(fmodf(x, 1.0f) * 1e6));
- }
-
-
- xml->exitbranch();
- }
- xml->exitbranch();
- }
-
- if(xml->enterbranch("KEYBOARD_MAPPING")) {
- Pmapsize = xml->getpar127("map_size", Pmapsize);
- Pmappingenabled = xml->getpar127("mapping_enabled", Pmappingenabled);
- for(int i = 0; i < Pmapsize; ++i) {
- if(xml->enterbranch("KEYMAP", i) == 0)
- continue;
- Pmapping[i] = xml->getpar127("degree", Pmapping[i]);
- xml->exitbranch();
- }
- xml->exitbranch();
- }
- xml->exitbranch();
- }
- }
-
-
-
- int Microtonal::saveXML(const char *filename) const
- {
- XMLwrapper *xml = new XMLwrapper();
-
- xml->beginbranch("MICROTONAL");
- add2XML(xml);
- xml->endbranch();
-
- int result = xml->saveXMLfile(filename);
- delete (xml);
- return result;
- }
-
- int Microtonal::loadXML(const char *filename)
- {
- XMLwrapper *xml = new XMLwrapper();
- if(xml->loadXMLfile(filename) < 0) {
- delete (xml);
- return -1;
- }
-
- if(xml->enterbranch("MICROTONAL") == 0)
- return -10;
- getfromXML(xml);
- xml->exitbranch();
-
- delete (xml);
- return 0;
- }
|