/* ZynAddSubFX - a software synthesizer Microtonal.C - 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 "tuning.h" #define MAX_LINE_SIZE 512 //--------------------------------------------------------- // Tuning //--------------------------------------------------------- Tuning::Tuning() { invertupdown = false; invertupdowncenter = 60; octavesize = 12; enabled = false; aNote = 69; aFreq = 440.0; scaleshift = 64; firstkey = 0; lastkey = 127; middlenote = 60; mapsize = 12; mappingenabled = false; for (int i = 0; i < 128; i++) mapping[i] = i; for (int i = 0; i < MAX_OCTAVE_SIZE; i++) { octave[i].tuning = tmpoctave[i].tuning = pow(2,(i % octavesize + 1) / 12.0); 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; name = "12tET"; comment = "Equal Temperament 12 notes per octave"; globalfinedetune = 64; } //--------------------------------------------------------- // getoctavesize //--------------------------------------------------------- int Tuning::getoctavesize() { return enabled ? octavesize : 12; } //--------------------------------------------------------- // getnotefreq // Get the frequency according the note number //--------------------------------------------------------- float Tuning::getnotefreq(int note, int keyshift) { // 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 (invertupdown &&((!mappingenabled) || (!enabled))) note = invertupdowncenter * 2 - note; //compute global fine detune float globalfinedetunerap = pow(2.0,(globalfinedetune-64.0)/1200.0);//-64.0 .. 63.0 cents if (!enabled) { //12tET return pow(2.0,(note - aNote + keyshift) / 12.0) * aFreq * globalfinedetunerap; } int lscaleshift = (scaleshift - 64 + octavesize * 100) % octavesize; //compute the keyshift float rap_keyshift = 1.0; if (keyshift) { int kskey = (keyshift + octavesize * 100) % octavesize; int ksoct = (keyshift + octavesize * 100) /octavesize - 100; rap_keyshift = (kskey==0) ? 1.0 : octave[kskey-1].tuning; rap_keyshift *= pow(octave[octavesize-1].tuning, ksoct); } //if the mapping is enabled float freq; if (mappingenabled) { if ((note < firstkey) || (note > lastkey)) return -1.0; //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 = aNote - middlenote; bool minus = false; if (tmp < 0) { tmp = -tmp; minus = true; } int deltanote = 0; for (int i=0;i= 0) deltanote++; } float rap_anote_middlenote = (deltanote == 0) ? 1.0 : octave[(deltanote-1) % octavesize].tuning; if (deltanote!=0) rap_anote_middlenote*=pow(octave[octavesize-1].tuning,(deltanote-1)/octavesize); if (minus) rap_anote_middlenote=1.0/rap_anote_middlenote; //Convert from note (midi) to degree (note from the tuning) int degoct = (note - middlenote + mapsize*200)/mapsize - 200; int degkey = (note - middlenote+(int)mapsize*100) % mapsize; degkey = mapping[degkey]; if (degkey < 0) return -1.0; //this key is not mapped //invert the keyboard upside-down if it is asked for //TODO: do the right way by using invertupdowncenter if (invertupdown) { degkey=octavesize-degkey-1; degoct=-degoct; } //compute the frequency of the note degkey = degkey+lscaleshift; degoct += degkey/octavesize; degkey %= octavesize; freq = (degkey==0) ? (1.0):octave[degkey-1].tuning; freq *= pow(octave[octavesize-1].tuning, degoct); freq *= aFreq/rap_anote_middlenote; } else { //if the mapping is disabled int nt = note - aNote + lscaleshift; int ntkey = (nt + octavesize * 100) % octavesize; int ntoct = (nt-ntkey) / octavesize; float oct = octave[octavesize - 1].tuning; freq = octave[(ntkey + octavesize-1) % octavesize].tuning * pow(oct, ntoct) * aFreq; if (ntkey == 0) freq /= oct; } if (lscaleshift) freq /= octave[scaleshift-1].tuning; freq *= globalfinedetunerap; return freq * rap_keyshift; } //--------------------------------------------------------- // linetotunings // Convert a line to tunings; returns true if it ok //--------------------------------------------------------- bool Tuning::linetotunings(int nline, const char* line) { int x1=-1, x2=-1, type=-1; float x=-1.0,tmp,tuning=1.0; if (strstr(line,"/") == 0) { if (strstr(line,".") == 0) { // 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.000001) return false; type = 1; //float type(cents) } } else { // M/N case sscanf(line,"%d/%d",&x1,&x2); if ((x1 < 0) || (x2 < 0)) return false; 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.0); x2 = (int) (floor (tmp*1e6)); tuning = pow(2.0,x/1200.0); break; case 2: tuning = ((float)x1)/x2; break; } tmpoctave[nline].tuning = tuning; tmpoctave[nline].type = type; tmpoctave[nline].x1 = x1; tmpoctave[nline].x2 = x2; return true; } //--------------------------------------------------------- // loadLine //--------------------------------------------------------- QByteArray Tuning::loadLine(QFile* file) { QByteArray tmp; for (;;) { tmp = file->readLine(); if (tmp.isEmpty() || tmp[0] != '!') break; } return tmp; } //--------------------------------------------------------- // loadscl // return false on error //--------------------------------------------------------- bool Tuning::loadscl(const QString& filename) { QFile file(filename); if (!file.open(QIODevice::ReadOnly)) return false; //loads the short description QByteArray tmp = loadLine(&file).trimmed(); if (tmp.isEmpty()) return false; name = tmp; comment = tmp; //loads the number of the notes tmp = loadLine(&file); if (tmp.isEmpty()) return false; bool ok; int nnotes = tmp.toInt(&ok); if (!ok) nnotes = MAX_OCTAVE_SIZE; else if (nnotes > MAX_OCTAVE_SIZE) return false; //load the tunnings for (int nline = 0; nline < nnotes; nline++) { tmp = loadLine(&file); if (tmp.isEmpty()) return false; if (!linetotunings(nline, tmp.data())) return false; } 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 true; } //--------------------------------------------------------- // loadkbm // return false on error //--------------------------------------------------------- bool Tuning::loadkbm(const QString& filename) { QFile file(filename); if (!file.open(QIODevice::ReadOnly)) return false; int x; QByteArray tmp; bool ok; //loads the mapsize tmp = loadLine(&file); if (tmp.isEmpty()) return false; x = tmp.toInt(&ok); if (!ok) return false; mapsize = qBound(1, x, 127); //loads first MIDI note to retune tmp = loadLine(&file); if (tmp.isEmpty()) return false; x = tmp.toInt(&ok); if (!ok) return false; firstkey = qBound(1, x, 127); //loads last MIDI note to retune tmp = loadLine(&file); if (tmp.isEmpty()) return false; x = tmp.toInt(&ok); if (!ok) return false; lastkey = qBound(1, x, 127); //loads last the middle note where scale fro scale degree=0 tmp = loadLine(&file); if (tmp.isEmpty()) return false; x = tmp.toInt(&ok); if (!ok) return false; middlenote = qBound(1, x, 127); //loads the reference note tmp = loadLine(&file); if (tmp.isEmpty()) return false; x = tmp.toInt(&ok); if (!ok) return false; aNote = qBound(1, x, 127); //loads the reference freq. tmp = loadLine(&file); if (tmp.isEmpty()) return false; float tmpPAfreq = tmp.toFloat(&ok); if (!ok) return false; aFreq = tmpPAfreq; //the scale degree(which is the octave) is not loaded, it is obtained // by the tunnings with getoctavesize() method tmp = loadLine(&file); if (tmp.isEmpty()) return false; //load the mappings if (mapsize) { for (int nline = 0; nline < mapsize; nline++) { tmp = loadLine(&file); if (tmp.isEmpty()) return false; x = tmp.toInt(&ok); mapping[nline] = ok ? x : -1; } mappingenabled = true; } else { mappingenabled = false; mapping[0] = 0; mapsize = 1; } return true; }