1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 3 /* 4 Rosegarden 5 A MIDI and audio sequencer and musical notation editor. 6 Copyright 2000-2009 the Rosegarden development team. 7 8 Other copyrights also apply to some parts of this work. Please 9 see the AUTHORS file and individual file headers for details. 10 11 This program is free software; you can redistribute it and/or 12 modify it under the terms of the GNU General Public License as 13 published by the Free Software Foundation; either version 2 of the 14 License, or (at your option) any later version. See the file 15 COPYING included with this distribution for more information. 16 */ 17 18 #include "Tuning.h" 19 20 #include "base/NotationTypes.h" 21 #include "gui/general/ResourceFinder.h" 22 #include "misc/Debug.h" 23 24 #include <QtDebug> 25 #include <QXmlStreamReader> 26 #include <QXmlStreamAttributes> 27 #include <QFile> 28 29 #include <stdlib.h> 30 #include <qfile.h> 31 #include <qstring.h> 32 #include <qtextstream.h> 33 #include <math.h> 34 #include <string> 35 36 37 // Set the debug level to: 38 // 39 // 1: summary printout of tunings after they've been read (default) 40 // 2: detail while parsing tunings file (quite verbose) 41 // 3: more detail on XML parser state (rather verbose) 42 #define TUNING_DEBUG 0 43 44 using namespace Rosegarden::Accidentals; 45 46 // Map accidental number to accidental string 47 const Tuning::AccMap::value_type Tuning::accMapData[] = { 48 AccMap::value_type(-4, &DoubleFlat), 49 AccMap::value_type(-3, &ThreeQuarterFlat), 50 AccMap::value_type(-2, &Flat), 51 AccMap::value_type(-1, &QuarterFlat), 52 AccMap::value_type(0, &NoAccidental), 53 AccMap::value_type(1, &QuarterSharp), 54 AccMap::value_type(2, &Sharp), 55 AccMap::value_type(3, &ThreeQuarterSharp), 56 AccMap::value_type(4, &DoubleSharp) 57 }; 58 const unsigned int Tuning::accMapSize = 59 sizeof(Tuning::accMapData) / sizeof(Tuning::accMapData[0]); 60 Tuning::AccMap Tuning::accMap(Tuning::accMapData, 61 Tuning::accMapData + Tuning::accMapSize); 62 63 std::vector<Tuning*> Tuning::m_tunings; 64 65 std::vector<Tuning*> *Tuning::getTunings() { 66 67 // if already have tunings, return them 68 // TODO: It would be polite to check the mtime on the tunings file 69 // and to re-read it if it's been changed. For now, you need 70 // to restart Rosegarden. 71 if (!m_tunings.empty()) 72 return &m_tunings; 73 74 QString tuningsPath = 75 ResourceFinder().getResourcePath("pitches", "tunings.xml"); 76 if (tuningsPath == "") return nullptr; 77 # if (TUNING_DEBUG > 1) 78 qDebug() << "Path to tunings file:" << tuningsPath; 79 # endif 80 QFile infile(tuningsPath); 81 82 IntervalList *intervals = new IntervalList; 83 SpellingList *spellings = new SpellingList; 84 85 if (infile.open(QIODevice::ReadOnly) ) { 86 QXmlStreamReader stream(&infile); 87 QString tuningName, intervalRatio; 88 89 stream.readNextStartElement(); 90 if (stream.name().toString() != "rosegarden_scales") { 91 qDebug() << "Tunings configuration file " << tuningsPath 92 << " is not a valid rosegarden scales file. " 93 << "(Root element is " << stream.name() << ")"; 94 return nullptr; 95 } 96 97 enum {needTuning, needName, needInterval, needSpellings} state; 98 # if (TUNING_DEBUG > 2) 99 static const char * stateNames[] = { 100 "expecting <tuning>", "expecting <name>", 101 "expecting <interval>", "expecting <spelling>" 102 }; 103 # endif 104 state = needTuning; 105 106 while(!stream.atEnd()) { 107 108 // Read to the next element delimiter 109 do { 110 stream.readNext(); 111 } while(!stream.isStartElement() && 112 !stream.isEndElement() && 113 !stream.atEnd()); 114 115 if (stream.atEnd()) break; 116 117 // Perform state transistions at end elements 118 if (stream.isEndElement()) { 119 if (stream.name().toString() == "tuning") { 120 // Save the tuning and prepare for a new one 121 saveTuning(tuningName, intervals, spellings); 122 intervals = new IntervalList; 123 spellings = new SpellingList; 124 state = needTuning; 125 } else if (stream.name().toString() == "name") { 126 // End of tuning name: expect intervals 127 state = needInterval; 128 } else if (stream.name().toString() == "interval") { 129 // After an </interval>, we're expecting another interval 130 state = needInterval; 131 } else if (stream.name().toString() == "rosegarden_scales") { 132 // XML's fininshed. Don't need the current tuning 133 // or spelling lists created when the last tuning ended 134 // so let's not leak memory. 135 delete intervals; 136 delete spellings; 137 // Don't bother reading any more of the file 138 break; 139 } 140 141 # if (TUNING_DEBUG > 2) 142 qDebug() << "End of XML element " << stream.name() 143 << "New state: " << state 144 << " (" << stateNames[state] << ")"; 145 # endif 146 continue; 147 } 148 149 // So it's a start element. Parse it. 150 151 // If we are in the needSpellings state but hit a new interval, 152 // we need to process that. So force a state-change here. 153 if (state == needSpellings && stream.name().toString() == "interval") { 154 state = needInterval; 155 } 156 157 # if (TUNING_DEBUG > 2) 158 qDebug() << "XML Element: " << stream.name() 159 << " Current XML parser state: " << state 160 << " (" << stateNames[state] << ")"; 161 # endif 162 163 switch (state) { 164 case needTuning: 165 if (stream.name().toString() != "tuning") { 166 qDebug() << "Reading Tunings. Expected tuning element, " 167 << "found " << stream.name(); 168 stream.skipCurrentElement(); 169 } else { 170 // Require a name element 171 state = needName; 172 } 173 break; 174 175 case needName: 176 if (stream.name().toString() != "name") { 177 qDebug() << "Tuning must start with a <name> element, " 178 << "found <" << stream.name() << ">"; 179 stream.skipCurrentElement(); 180 } else { 181 tuningName = stream.readElementText(); 182 state = needInterval; 183 # if (TUNING_DEBUG > 1) 184 qDebug() << "\nNew Tuning: " << tuningName; 185 # endif 186 } 187 break; 188 189 case needInterval: 190 if (stream.name().toString() != "interval") { 191 qDebug() << "Expecting an <interval> element, " 192 << "found <" << stream.name() << ">"; 193 // Bail out 194 stream.skipCurrentElement(); 195 } else { 196 intervalRatio = 197 stream.attributes().value("ratio").toString(); 198 # if (TUNING_DEBUG > 1) 199 qDebug() << "New Ratio: " << intervalRatio; 200 # endif 201 const double cents = 202 scalaIntervalToCents(intervalRatio, 203 stream.lineNumber()); 204 intervals->push_back(cents); 205 state = needSpellings; 206 } 207 break; 208 209 case needSpellings: 210 if (stream.name().toString() != "spelling") { 211 qDebug() << "Intervals may contain only spellings. " 212 << "Found <" << stream.name() << ">"; 213 // Keep looking for spellings 214 stream.skipCurrentElement(); 215 } else { 216 // Parse this spelling 217 parseSpelling(stream.readElementText(), 218 intervals, spellings); 219 } 220 break; 221 222 default: 223 // Illegal state (can't happen: it's an enumerated type!) 224 qDebug() << "Something nasty happened reading tunings. " 225 "Illegal state at line " << stream.lineNumber(); 226 stream.skipCurrentElement(); 227 break; 228 } // end switch(state) 229 } // end while(!stream.atEnd()) 230 # if (TUNING_DEBUG > 1) 231 qDebug() << "Closing tunings file"; 232 # endif 233 infile.close(); 234 } // end if (m_infile.open(... 235 236 return &m_tunings; 237 } 238 239 void Tuning::parseSpelling(QString note, 240 IntervalList *intervals, 241 SpellingList *spellings) 242 { 243 QString acc = note; 244 acc.remove(0, 1); 245 note.remove(1, note.length()-1); 246 # if (TUNING_DEBUG > 1) 247 qDebug() << "Accidental: " << acc << "\tPitch Class: " << note; 248 # endif 249 if (acc.toInt() != 0) { 250 const int acc_i = atoi(acc.toStdString().c_str()); 251 note.append(accMap[acc_i]->c_str()); 252 } 253 //insert into spelling list 254 spellings->insert(Spelling(note.toStdString().c_str(), intervals->size()-1)); 255 # if (TUNING_DEBUG > 1) 256 qDebug() << "Translated variation:" << note; 257 # endif 258 } 259 260 double Tuning::scalaIntervalToCents(const QString & interval, 261 const qint64 lineNumber) 262 { 263 double cents = -1.0; 264 bool ok; 265 QString intervalString(interval.trimmed()); 266 int dotPos = intervalString.indexOf(QChar('.')); 267 if (dotPos == -1) { // interval is a ratio 268 # if (TUNING_DEBUG > 1) 269 qDebug() << "Interval is a ratio"; 270 # endif 271 int slashPos = intervalString.indexOf(QChar('/')); 272 double ratio = 1.0; 273 if (slashPos == -1) { // interval is integer ratio 274 # if (TUNING_DEBUG > 1) 275 qDebug() << "Ratio is an integer"; 276 # endif 277 ratio = intervalString.toInt(&ok); 278 if (!ok) { 279 RG_WARNING << "Syntax Error in tunings file, line " << lineNumber; 280 return -1.0; 281 } else { 282 //convert ratio to cents 283 QString numeratorString = intervalString; 284 numeratorString.remove(slashPos, 285 numeratorString.length() - slashPos); 286 # if (TUNING_DEBUG > 1) 287 qDebug() << "numerator:" << numeratorString; 288 # endif 289 int numerator = numeratorString.toInt( &ok ); 290 if (!ok) { 291 RG_WARNING << "Syntax Error in tunings file, line" << lineNumber; 292 return -1.0; 293 } 294 QString denominatorString = intervalString; 295 denominatorString.remove( 0, slashPos+1 ); 296 # if (TUNING_DEBUG > 1) 297 qDebug() << "denominator:" << denominatorString; 298 # endif 299 int denominator = denominatorString.toInt( &ok ); 300 if (!ok) { 301 RG_WARNING << "Syntax Error in tunings file, line" << lineNumber; 302 return -1.0; 303 } 304 305 # if (TUNING_DEBUG > 1) 306 qDebug() << "Ratio:" << numeratorString 307 << "/" << denominatorString; 308 # endif 309 310 ratio = (double)numerator / (double)denominator; 311 //calculate cents 312 cents = 1200.0 * log(ratio)/log(2.0); 313 # if (TUNING_DEBUG > 1) 314 qDebug() << "cents" << cents; 315 # endif 316 } 317 } 318 } else { // interval is in cents 319 # if (TUNING_DEBUG > 1) 320 qDebug() << "Interval is in cents"; 321 # endif 322 cents = intervalString.toDouble(&ok); 323 if (!ok) { 324 RG_WARNING << "Syntax Error in tunings file, line " 325 << lineNumber; 326 return -1.0; 327 } 328 # if (TUNING_DEBUG > 1) 329 qDebug() << "Cents: " << cents; 330 # endif 331 } 332 333 return cents; 334 } 335 336 void Tuning::saveTuning(const QString &tuningName, 337 const IntervalList *intervals, 338 SpellingList *spellings) 339 { 340 # if (TUNING_DEBUG > 1) 341 qDebug() << "End of tuning" << tuningName; 342 # endif 343 std::string name = tuningName.toStdString().c_str(); 344 Tuning *newTuning = new Tuning(name, intervals, spellings); 345 m_tunings.push_back(newTuning); 346 # if (TUNING_DEBUG) 347 newTuning->printTuning(); 348 # endif 349 } 350 351 352 Tuning::Tuning(const std::string name, 353 const IntervalList *intervals, 354 SpellingList *spellings) : 355 m_name(name), 356 m_rootPitch(9, 3), 357 m_refPitch(9, 3), 358 m_intervals(intervals), 359 m_spellings(spellings) 360 { 361 # if (TUNING_DEBUG > 1) 362 qDebug() << "Given name:" << name.c_str() << &name; 363 qDebug() << "Stored name:" << m_name.c_str() << &m_name; 364 # endif 365 366 m_size = intervals->size(); 367 368 //check interval & spelling list sizes match 369 for (SpellingListIterator it = spellings->begin(); 370 it != spellings->end(); 371 ++it) { 372 if (it->second > m_size) { 373 qDebug() << "Spelling list does not match " 374 "number of intervals!"; 375 // !!! This invalidates the iterator and will 376 // cause serious problems. Recommend using 377 // the "increment before use" idiom. Search 378 // the code for examples. 379 spellings->erase(it); 380 } 381 } 382 383 Rosegarden::Pitch p(9, 3); 384 385 //default A3 = 440; 386 setRootPitch(p); 387 setRefNote(p, 440); 388 } 389 390 Tuning::Tuning(const Tuning *tuning) : 391 m_name(tuning->getName()), 392 m_rootPitch(tuning->getRootPitch()), 393 m_refPitch(tuning->getRefPitch()), 394 m_intervals(tuning->getIntervalList()), 395 m_size(m_intervals->size()), 396 m_spellings(tuning->getSpellingList()) 397 { 398 # if (TUNING_DEBUG > 1) 399 qDebug() << "Given name:" << tuning->getName().c_str(); 400 qDebug() << "Stored name: " << m_name.c_str() << &m_name; 401 # endif 402 403 //default A3 = 440; 404 Rosegarden::Pitch p=tuning->getRefPitch(); 405 Rosegarden::Pitch p2=tuning->getRootPitch(); 406 407 setRootPitch(tuning->getRootPitch()); 408 setRefNote(p, tuning->getRefFreq()); 409 410 Rosegarden::Key keyofc; // use key of C to obtain unbiased accidental 411 412 RG_DEBUG << "Ref note " << p.getNoteName(keyofc) 413 << p.getDisplayAccidental( keyofc ) 414 << " " << m_refFreq; 415 416 RG_DEBUG << "Ref note " << m_refPitch.getNoteName(keyofc) 417 << m_refPitch.getDisplayAccidental( keyofc ) 418 << " " << m_refFreq; 419 420 RG_DEBUG << "Ref freq for C " << m_cRefFreq; 421 422 RG_DEBUG << "Root note " << p2.getNoteName(keyofc) 423 << p2.getDisplayAccidental(keyofc) 424 ; 425 RG_DEBUG << "Root note " << m_rootPitch.getNoteName(keyofc) 426 << m_rootPitch.getDisplayAccidental(keyofc) 427 ; 428 } 429 430 431 432 433 void Tuning::setRootPitch(Rosegarden::Pitch pitch){ 434 435 m_rootPitch = pitch; 436 437 std::string spelling = getSpelling( pitch );; 438 const SpellingListIterator sit = m_spellings->find(spelling); 439 if (sit == m_spellings->end()){ 440 RG_WARNING << "Fatal: Tuning::setRootPitch root pitch " 441 "not found in tuning!!"; 442 return; 443 } 444 # if (TUNING_DEBUG > 1) 445 qDebug() << "Root position" << m_rootPosition; 446 # endif 447 m_rootPosition = sit->second; 448 } 449 450 451 std::string Tuning::getSpelling(Rosegarden::Pitch &pitch) const { 452 453 454 const Rosegarden::Key key; 455 456 QChar qc(pitch.getNoteName(key)); 457 QString spelling(qc); 458 459 Rosegarden::Accidental acc = pitch.getDisplayAccidental(key); 460 if (acc != Rosegarden::Accidentals::NoAccidental && 461 acc != Rosegarden::Accidentals::Natural) { 462 spelling.append(acc.c_str()); 463 } 464 465 return spelling.toStdString().c_str(); 466 } 467 468 469 void Tuning::setRefNote(Rosegarden::Pitch pitch, double freq) { 470 471 m_refPitch = pitch; 472 m_refFreq = freq; 473 m_refOctave = pitch.getOctave(); 474 std::string spelling = getSpelling(pitch); 475 476 // position in chromatic scale 477 SpellingListIterator it = m_spellings->find(spelling); 478 if (it == m_spellings->end()) { 479 RG_WARNING << "Tuning::setRefNote Spelling " << spelling 480 << " not found in " << m_name 481 << " tuning!"; 482 return; 483 } 484 int refPosition = it->second; 485 486 // calculate frequency for C in reference octave 487 // this makes calculation of frequencies easier 488 489 it = m_spellings->find("C"); 490 if (it == m_spellings->end()){ 491 RG_WARNING << "Tuning::setRefNote 'C' not found in " 492 << m_name << " tuning!"; 493 return; 494 } 495 496 m_cPosition = it->second; 497 498 // find position of C relative to root note 499 int cInterval = m_cPosition - m_rootPosition; 500 if (cInterval < 0) cInterval += m_size; 501 502 // cents above root note for C 503 double cents = (*m_intervals)[cInterval]; 504 505 // cents above root note for reference note 506 int refInterval = refPosition - m_rootPosition; 507 if( refInterval < 0 ) refInterval += m_size; 508 509 double refCents = (*m_intervals)[refInterval]; 510 511 // relative cents from reference note to target note 512 double relativeCents = cents - refCents; 513 if (relativeCents > 0) relativeCents -= 1200; 514 515 //frequency ratio between reference and target notes 516 double ratio = pow( 2, relativeCents/1200 ); 517 518 m_cRefFreq = m_refFreq * ratio; 519 520 # if (TUNING_DEBUG) 521 qDebug() << "c Position" << m_cPosition 522 << "\nc interval" << cInterval 523 << "\nc Cents" << cents 524 << "\nref position" << refPosition 525 << "\nref interval" << refInterval 526 << "\nref Cents" << refCents 527 << "\nc freq" << m_cRefFreq 528 << "\nref octave" << m_refOctave; 529 # endif 530 } 531 532 /** 533 * Returns the frequency of the given pitch in the current tuning. 534 */ 535 double Tuning::getFrequency(Rosegarden::Pitch p) const { 536 537 // make note spelling 538 std::string spelling = getSpelling(p); 539 540 int octave = p.getOctave(); 541 542 // position in chromatic scale 543 const SpellingListIterator it = m_spellings->find(spelling); 544 if (it == m_spellings->end()) { 545 RG_WARNING << "Tuning::getFreq Spelling '" << spelling 546 << "' not found in " << m_name << " tuning!"; 547 return 0; 548 } 549 int position = it->second; 550 551 // find position relative to root note 552 int relativePosition = position - m_rootPosition; 553 if (relativePosition < 0) relativePosition += m_size; 554 555 // cents above root note for target note 556 double cents = (*m_intervals)[relativePosition]; 557 558 // cents above root note for reference note ( C ) 559 int refInterval = m_cPosition - m_rootPosition; 560 if (refInterval < 0) refInterval += m_size; 561 double refCents = (*m_intervals)[refInterval]; 562 563 // relative cents from reference note to target note 564 double relativeCents = cents - refCents; 565 if (relativeCents < 0) relativeCents += 1200; 566 567 //frequency ratio between reference and target notes 568 double ratio = pow(2, relativeCents/1200); 569 570 /* 571 B# occurs in the same octave as the C immediatley above them. 572 In 12ET this is true, but not in all tunings. 573 Solution: When B# and C are not equivalent spellings, 574 decrement the octave of every B#. 575 */ 576 if (spelling == "Bsharp" && position != m_cPosition) { 577 octave--; 578 } 579 580 const int octaveDifference = octave - m_refOctave; 581 582 const double octaveRatio = pow( 2, octaveDifference ); 583 584 ratio *= octaveRatio; 585 586 const double freq = m_cRefFreq * ratio; 587 588 # if (TUNING_DEBUG) 589 qDebug() << "Spelling " << spelling.c_str() 590 << "\ntarget position " << position 591 << "\nroot position " << m_rootPosition 592 << "\ntarget interval " << relativePosition 593 << "\ncents above root note " << cents 594 << "\ninterval for C " << refInterval 595 << "\ncents from reference note " << refCents 596 << "\ncents from reference to target" << relativeCents 597 << "\nrefOctave " << m_refOctave 598 << "\noctave " << octave 599 << "\noctave ratio " << octaveRatio 600 << "\nratio " << ratio 601 << "\nref freq " << m_refFreq 602 << "\nfreq " << freq; 603 # endif 604 605 return freq; 606 } 607 608 /** 609 * Prints to std out for debugging 610 */ 611 void Tuning::printTuning() const { 612 613 RG_DEBUG << "Tuning::printTuning()"; 614 RG_DEBUG << "Name: '" << m_name << "'"; 615 616 Rosegarden::Key keyofc; // use key of C to obtain unbiased accidental 617 618 RG_DEBUG << "Ref note " << m_refPitch.getNoteName(keyofc) 619 << m_refPitch.getDisplayAccidental( keyofc ) 620 << " " << m_refFreq; 621 622 RG_DEBUG << "Ref freq for C " << m_cRefFreq; 623 624 RG_DEBUG << "Root note " << m_rootPitch.getNoteName(keyofc) 625 << m_rootPitch.getDisplayAccidental( keyofc ); 626 627 for (SpellingListIterator sit = m_spellings->begin(); 628 sit != m_spellings->end(); 629 ++sit) { 630 RG_DEBUG << "Spelling '" << sit->first 631 << "'\tinterval " << sit->second; 632 } 633 634 for(unsigned int i=0; i < m_intervals->size(); i++) { 635 RG_DEBUG << "Interval '" << i 636 << "'\tinterval " << m_intervals->at(i); 637 } 638 639 RG_DEBUG << "Done."; 640 641 } 642 643 644 Rosegarden::Pitch Tuning::getRootPitch() const { return m_rootPitch; } 645 Rosegarden::Pitch Tuning::getRefPitch() const { return m_refPitch; } 646 double Tuning::getRefFreq() const{ return m_refFreq; } 647 const std::string Tuning::getName() const { return m_name; } 648 SpellingList *Tuning::getSpellingList() const{ return m_spellings; } 649 const IntervalList *Tuning::getIntervalList() const{ return m_intervals; } 650 651