/* 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 #include #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; }