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