//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2002-2011 Werner Schweer
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2
//  as published by the Free Software Foundation and appearing in
//  the file LICENCE.GPL
//=============================================================================

#include "durationtype.h"
#include "mscore.h"
#include "note.h"
#include "sig.h"
#include "measure.h"

namespace Ms {

//---------------------------------------------------------
//   dots
//---------------------------------------------------------

static int getDots(int base, int rest, char* dots)
      {
      if (base < 16)
           return rest;
      *dots = 0;
      if (rest >= base / 2) {
            *dots = *dots + 1;
            rest -= base / 2;
            }
      if (rest >= base / 4) {
            *dots = *dots + 1;
            rest -= base / 4;
            }
      if (rest >= base / 8) {
            *dots = *dots + 1;
            rest -= base / 8;
            }
      if (rest >= base / 16) {
            *dots = *dots + 1;
            rest -= base / 16;
            }
      if (*dots > MAX_DOTS)
            *dots = MAX_DOTS;
      return rest;
      }

//---------------------------------------------------------
//   setDots
//---------------------------------------------------------

void TDuration::setDots(int v)
      {
      if (v > MAX_DOTS)
            v = MAX_DOTS;
      _dots = v;
      }

//---------------------------------------------------------
//   setVal
//---------------------------------------------------------

void TDuration::setVal(int ticks)
      {
      if (ticks == 0)
            _val = DurationType::V_MEASURE;
      else {
            TDuration dt;
            for (int i = 0; i < int(TDuration::DurationType::V_ZERO); ++i) {
                  dt.setType(TDuration::DurationType(i));
                  int t = dt.ticks().ticks();
                  if (ticks / t) {
                        int remain = ticks % t;
                        if ((t - remain) < (t/4)) {
                              _val = DurationType(i - 1);
                              return;
                              }
                        _val = DurationType(i);
                        getDots(t, remain, &_dots);
                        return;
                        }
                  }
            qDebug("2: no duration type for ticks %d", ticks);
            _val = DurationType::V_QUARTER;       // fallback default value
            }
      }

//---------------------------------------------------------
//   ticks
//---------------------------------------------------------

Fraction TDuration::ticks() const
      {
      Fraction t;
      switch(_val) {
            case DurationType::V_QUARTER: t = Fraction(1, 4);    break;
            case DurationType::V_1024TH:  t = Fraction(1, 1024); break;
            case DurationType::V_512TH:   t = Fraction(1, 512);  break;
            case DurationType::V_256TH:   t = Fraction(1, 256);  break;
            case DurationType::V_128TH:   t = Fraction(1, 128);  break;
            case DurationType::V_64TH:    t = Fraction(1, 64);   break;
            case DurationType::V_32ND:    t = Fraction(1, 32);   break;
            case DurationType::V_16TH:    t = Fraction(1, 16);   break;
            case DurationType::V_EIGHTH:  t = Fraction(1, 8);    break;
            case DurationType::V_HALF:    t = Fraction(1, 2);    break;
            case DurationType::V_WHOLE:   t = Fraction(1, 1);    break;
            case DurationType::V_BREVE:   t = Fraction(2, 1);    break;
            case DurationType::V_LONG:    t = Fraction(4, 1);    break;
            case DurationType::V_ZERO:
            case DurationType::V_MEASURE:
                  return Fraction(0,1);
            default:
            case DurationType::V_INVALID:
                  return Fraction(-1,1);
            }
      Fraction tmp = t;
      for (int i = 0; i < _dots; ++i) {
            tmp *= Fraction(1,2);
            t += tmp;
            }
      return t;
      }

//---------------------------------------------------------
//   name
//---------------------------------------------------------

QString TDuration::name() const
      {
      switch(_val) {
            case DurationType::V_QUARTER:   return "quarter";
            case DurationType::V_EIGHTH:    return "eighth";
            case DurationType::V_1024TH:    return "1024th";
            case DurationType::V_512TH:     return "512th";
            case DurationType::V_256TH:     return "256th";
            case DurationType::V_128TH:     return "128th";
            case DurationType::V_64TH:      return "64th";
            case DurationType::V_32ND:      return "32nd";
            case DurationType::V_16TH:      return "16th";
            case DurationType::V_HALF:      return "half";
            case DurationType::V_WHOLE:     return "whole";
            case DurationType::V_MEASURE:   return "measure";
            case DurationType::V_BREVE:     return "breve";
            case DurationType::V_LONG:      return "long";
            default:
qDebug("TDuration::name(): invalid duration type %d", static_cast<int>(_val));
                 // fall through
            case DurationType::V_ZERO:
            case DurationType::V_INVALID:   return "";
            }
      }

//---------------------------------------------------------
//   headType
//---------------------------------------------------------

NoteHead::Type TDuration::headType() const
      {
      NoteHead::Type headType = NoteHead::Type::HEAD_WHOLE;
      switch(_val) {
            case DurationType::V_1024TH:
            case DurationType::V_512TH:
            case DurationType::V_256TH:
            case DurationType::V_128TH:
            case DurationType::V_64TH:
            case DurationType::V_32ND:
            case DurationType::V_16TH:
            case DurationType::V_EIGHTH:
            case DurationType::V_QUARTER:
                  headType = NoteHead::Type::HEAD_QUARTER;
                  break;
            case DurationType::V_HALF:
                  headType = NoteHead::Type::HEAD_HALF;
                  break;
            case DurationType::V_MEASURE:
            case DurationType::V_WHOLE:
                  headType = NoteHead::Type::HEAD_WHOLE;
                  break;
            case DurationType::V_BREVE:
                  headType = NoteHead::Type::HEAD_BREVIS;
                  break;
            case DurationType::V_LONG:
                  headType = NoteHead::Type::HEAD_BREVIS;
                  break;
            default:
            case DurationType::V_INVALID:
            case DurationType::V_ZERO:
                  headType = NoteHead::Type::HEAD_QUARTER;
                  break;
            }
      return headType;
      }

//---------------------------------------------------------
//   hooks
//---------------------------------------------------------

int TDuration::hooks() const
      {
      static const int table[] = {
         // V_LONG, V_BREVE, V_WHOLE, V_HALF, V_QUARTER, V_EIGHTH, V_16TH,
            0,      0,       0,       0,      0,         1,        2,
         // V_32ND, V_64TH, V_128TH, V_256TH, V_512TH, V_1024TH,
            3,      4,       5,       6,      7,       8,
         // V_ZERO, V_MEASURE, V_INVALID
            0,      0,       0
            };
      return table[int(_val)];
      }

//---------------------------------------------------------
//   hasStem
//---------------------------------------------------------

bool TDuration::hasStem() const
      {
      switch(_val) {
            case DurationType::V_1024TH:
            case DurationType::V_512TH:
            case DurationType::V_256TH:
            case DurationType::V_128TH:
            case DurationType::V_64TH:
            case DurationType::V_32ND:
            case DurationType::V_16TH:
            case DurationType::V_EIGHTH:
            case DurationType::V_QUARTER:
            case DurationType::V_HALF:
            case DurationType::V_LONG:
                  return true;
            default:
                  return false;
            }
      }

//---------------------------------------------------------
//   setVal
//---------------------------------------------------------

TDuration::TDuration(const QString& s)
      {
      setType(s);
      _dots = 0;
      }

//---------------------------------------------------------
//   setType
//---------------------------------------------------------

void TDuration::setType(const QString& s)
      {
      if (s == "quarter")
            _val = DurationType::V_QUARTER;
      else if (s == "eighth")
            _val = DurationType::V_EIGHTH;
      else if (s == "1024th")
            _val = DurationType::V_1024TH;
      else if (s == "512th")
            _val = DurationType::V_512TH;
      else if (s == "256th")
            _val = DurationType::V_256TH;
      else if (s == "128th")
            _val = DurationType::V_128TH;
      else if (s == "64th")
            _val = DurationType::V_64TH;
      else if (s == "32nd")
            _val = DurationType::V_32ND;
      else if (s == "16th")
            _val = DurationType::V_16TH;
      else if (s == "half")
            _val = DurationType::V_HALF;
      else if (s == "whole")
            _val = DurationType::V_WHOLE;
      else if (s == "breve")
            _val = DurationType::V_BREVE;
      else if (s == "long")
            _val = DurationType::V_LONG;
      else if (s == "measure")
            _val = DurationType::V_MEASURE;
      else {
            // _val = V_INVALID;
            _val = DurationType::V_QUARTER;
            qDebug("TDuration::setType(%s): unknown, assume \"quarter\"", qPrintable(s));
            }
      }

//---------------------------------------------------------
//   shiftType
//    If stepDotted = false, duration type will inc/dec by
//    nSteps with _dots remaining same.
//
//    If stepDotted = true, duration will round toward zero
//    to next single-dotted or undotted duration and then
//    will included dotted durations when stepping
//---------------------------------------------------------

void TDuration::shiftType(int nSteps, bool stepDotted)
      {
      if (_val == DurationType::V_MEASURE || _val == DurationType::V_INVALID || _val == DurationType::V_ZERO)
            setType(DurationType::V_INVALID);
      else {
            int newValue;
            int newDots;
            if (stepDotted) {
                  // figure out the new duration in terms of the number of single dotted or undotted steps from DurationType::V_LONG
                  int roundDownSingleDots = (_dots > 0) ? -1 : 0;
                  int newValAsNumSingleDotSteps = int(_val) * 2 + roundDownSingleDots + nSteps;

                  // convert that new duration back into terms of DurationType integer value and number of dots
                  newDots  = newValAsNumSingleDotSteps % 2; // odd means there is a dot
                  newValue = newValAsNumSingleDotSteps / 2 + newDots; // if new duration has a dot, then that
                  }
            else {
                  newDots = _dots;
                  newValue = int(_val) + nSteps;
                  }

            if ((newValue < int(DurationType::V_LONG)) || (newValue > int(DurationType::V_1024TH)) ||
                 ((newValue >= int(DurationType::V_1024TH)) && (newDots >= 1)) ||
                 ((newValue >= int(DurationType::V_512TH))  && (newDots >= 2)) ||
                 ((newValue >= int(DurationType::V_256TH))  && (newDots >= 3)) ||
                 ((newValue >= int(DurationType::V_128TH))  && (newDots >= 4)))
                  setType(DurationType::V_INVALID);
            else {
                  setType(DurationType(newValue));
                  setDots(newDots);
                  }
            }
      }

//---------------------------------------------------------
//   operator<
//---------------------------------------------------------

bool TDuration::operator<(const TDuration& t) const
      {
      if (t._val < _val)
            return true;
      if (t._val == _val) {
            if (_dots < t._dots)
                  return true;
            }
      return false;
      }

//---------------------------------------------------------
//   operator>=
//---------------------------------------------------------

bool TDuration::operator>=(const TDuration& t) const
      {
      if (t._val > _val)
            return true;
      if (t._val == _val) {
            if (_dots >= t._dots)
                  return true;
            }
      return false;
      }

//---------------------------------------------------------
//   operator<=
//---------------------------------------------------------

bool TDuration::operator<=(const TDuration& t) const
      {
      if (t._val < _val)
            return true;
      if (t._val == _val) {
            if (_dots <= t._dots)
                  return true;
            }
      return false;
      }

//---------------------------------------------------------
//   operator>
//---------------------------------------------------------

bool TDuration::operator>(const TDuration& t) const
      {
      if (t._val > _val)
            return true;
      if (t._val == _val) {
            if (_dots > t._dots)
                  return true;
            }
      return false;
      }

//---------------------------------------------------------
//   fraction
//---------------------------------------------------------

Fraction TDuration::fraction() const
      {
      int z = 1;
      unsigned n;
      switch(_val) {
            case DurationType::V_1024TH:    n = 1024;     break;
            case DurationType::V_512TH:     n = 512;      break;
            case DurationType::V_256TH:     n = 256;      break;
            case DurationType::V_128TH:     n = 128;      break;
            case DurationType::V_64TH:      n = 64;       break;
            case DurationType::V_32ND:      n = 32;       break;
            case DurationType::V_16TH:      n = 16;       break;
            case DurationType::V_EIGHTH:    n = 8;        break;
            case DurationType::V_QUARTER:   n = 4;        break;
            case DurationType::V_HALF:      n = 2;        break;
            case DurationType::V_WHOLE:     n = 1;        break;
            case DurationType::V_BREVE:     z = 2; n = 1; break;
            case DurationType::V_LONG:      z = 4; n = 1; break;
            case DurationType::V_ZERO:      z = 0; n = 1; break;
            default:          z = 0; n = 0; break;    // zero+invalid fraction
            }
      Fraction a(z, n);
      if (a.isValid()) {
            for (int i = 0; i < _dots; ++i) {
                  n *= 2;
                  a += Fraction(z, n);
                  }
            }
      return a;
      }

// Longest TDuration that fits into Fraction. Must fit exactly if truncate = false.
TDuration::TDuration(const Fraction& l, bool truncate, int maxDots, DurationType maxType)
      {
#ifdef NDEBUG
      Q_UNUSED(truncate);
#endif
      setType(maxType); // use maxType to avoid testing all types if you know that l is smaller than a certain DurationType
      setDots(maxDots);
      truncateToFraction(l, maxDots);
      Q_ASSERT(truncate || (fraction() - l).numerator() == 0); // check for exact fit
      }

//---------------------------------------------------------
//   truncateToFraction
//---------------------------------------------------------

void TDuration::truncateToFraction(const Fraction& l, int maxDots)
      {
      // try to fit in l by reducing number of duration dots
      if (setDotsToFitFraction(l, _dots))
            return;

      // that wasn't enough so now change type too
      for (shiftType(1); isValid(); shiftType(1)) {
            if (setDotsToFitFraction(l, maxDots))
                  return; // duration fits fits in l
            }

      }

//---------------------------------------------------------
//   setDotsToFitFraction
//---------------------------------------------------------

bool TDuration::setDotsToFitFraction(const Fraction& l, int maxDots)
      {
      for ( ; maxDots >= 0; maxDots--) {
            _dots = maxDots; // ensures _dots >= 0 if function returns false.
            if ((fraction() - l).numerator() <= 0)
                  return true; // duration fits in l
            }

      return false; // doesn't fit by changing dots alone (type needs to be changed too)
      }

//---------------------------------------------------------
//   operator -=
//---------------------------------------------------------

TDuration& TDuration::operator-=(const TDuration& t)
      {
      Fraction f1 = fraction() - t.fraction();
      TDuration d(f1);
      _val  = d._val;
      _dots = d._dots;
      return *this;
      }

//---------------------------------------------------------
//   operator +=
//---------------------------------------------------------

TDuration& TDuration::operator+=(const TDuration& t)
      {
      Fraction f1 = fraction() + t.fraction();
      TDuration d(f1);
      _val  = d._val;
      _dots = d._dots;
      return *this;
      }

//---------------------------------------------------------
//   toDurationList
//---------------------------------------------------------

std::vector<TDuration> toDurationList(Fraction l, bool useDots, int maxDots, bool printRestRemains)
      {
      std::vector<TDuration> dList;
      dList.reserve(8);

      if (!useDots)
            maxDots = 0;

      for (TDuration dd(l, true, maxDots); dd.isValid() && l.numerator() > 0; dd = TDuration(l, true, maxDots, dd.type())) {
            dList.push_back(dd);
            l -= dd.fraction();
            }

      if (printRestRemains && l.numerator() != 0)
            qDebug("toDurationList:: rest remains %d/%d", l.numerator(), l.denominator());

      return dList;
      }

//---------------------------------------------------------
//   toRhythmicDurationList
//---------------------------------------------------------

std::vector<TDuration> toRhythmicDurationList(const Fraction& l, bool isRest, Fraction rtickStart, const TimeSigFrac& nominal, Measure* msr, int maxDots)
      {
      std::vector<TDuration> dList;
      dList.reserve(8);

      if (msr->isAnacrusis())
            rtickStart = Fraction::fromTicks(nominal.ticksPerMeasure()) - rtickStart;
      else if (isRest && l == msr->ticks()) {
            TDuration d = TDuration(TDuration::DurationType::V_MEASURE);
            dList.push_back(d);
            return dList;
            }

      if (nominal.isCompound())
            splitCompoundBeatsForList(&dList, l, isRest, rtickStart, nominal, maxDots);
      else
            populateRhythmicList(&dList, l, isRest, rtickStart, nominal, maxDots);

      return dList;
      }

//---------------------------------------------------------
//   populateRhythmicList
//---------------------------------------------------------

void populateRhythmicList(std::vector<TDuration>* dList, const Fraction& l, bool isRest, const Fraction& rtickStart, const TimeSigFrac& nominal, int maxDots)
      {
      Fraction rtickEnd = rtickStart + l;

      bool needToSplit = false; // do we need to split?
      int rtickSplit = 0; // tick to split on if we need to

      // CHECK AT SUBBEAT LEVEL

      int startLevel            = nominal.rtick2subbeatLevel(rtickStart.ticks());
      int endLevel              = nominal.rtick2subbeatLevel(rtickEnd.ticks());
      int strongestLevelCrossed = nominal.strongestSubbeatLevelInRange(rtickStart.ticks(), rtickEnd.ticks(), &rtickSplit); // sets rtickSplit

      if ((startLevel < 0) || (endLevel < 0) || (strongestLevelCrossed < 0)) {
            // Beyond maximum subbeat level so just split into largest possible durations.
            std::vector<TDuration> dList2 = toDurationList(l, maxDots > 0, maxDots, false);
            dList->insert(dList->end(), dList2.begin(), dList2.end());
            return;
            }

      // split if we cross something stronger than where we start and end
      if ((strongestLevelCrossed < startLevel) && (strongestLevelCrossed < endLevel))
            needToSplit = true;
      // but don't split for level 1 syncopation (allow eight-note, quarter, quarter... to cross unstressed beats)
      if (startLevel == endLevel && strongestLevelCrossed == startLevel - 1)
            needToSplit = false;
      // nor for the next simplest case of level 2 syncopation (allow sixteenth-note, eighth, eighth... to cross unstressed beats)
      if (startLevel == endLevel && strongestLevelCrossed == startLevel - 2) {
            // but disallow sixteenth-note, quarter, quarter...
            int ticksToNext = nominal.ticksToNextSubbeat(rtickStart.ticks(), startLevel - 1);
            int ticksPastPrev = nominal.ticksPastSubbeat(rtickStart.ticks(), startLevel - 1);
            needToSplit = ticksToNext != ticksPastPrev;
            }

      if (!needToSplit && strongestLevelCrossed == 0) {
            // NOW CHECK AT DENOMINATOR UNIT LEVEL AND BEAT LEVEL
            BeatType startBeat = nominal.rtick2beatType(rtickStart.ticks());
            BeatType endBeat   = nominal.rtick2beatType(rtickEnd.ticks());

            int dUnitsCrossed = 0; // number of timeSig denominator units the note/rest crosses
            // if there is a choice of which beat to split on, should we use the first or last?
            bool useLast = startBeat <= BeatType::SIMPLE_UNSTRESSED; // split on the later beat if starting on a beat

            BeatType strongestBeatCrossed = nominal.strongestBeatInRange(rtickStart.ticks(), rtickEnd.ticks(), &dUnitsCrossed, &rtickSplit, useLast);

            needToSplit = forceRhythmicSplit(isRest, startBeat, endBeat, dUnitsCrossed, strongestBeatCrossed, nominal);
            }

      if (!needToSplit) {
            // CHECK THERE IS A DURATION THAT FITS
            // crossed beats/subbeats were not important so try to avoid splitting
            TDuration d = TDuration(l, true, maxDots);
            if (d.fraction() == l) {
                  // we can use a single duration - no need to split!
                  dList->push_back(l);
                  return;
                  }
            // no single TDuration fits so must split anyway
            }

      // Split on the strongest beat or subbeat crossed
      Fraction leftSplit   = Fraction::fromTicks(rtickSplit) - rtickStart;
      Fraction rightSplit = l - leftSplit;

      // Recurse to see if we need to split further before adding to list
      populateRhythmicList(dList, leftSplit, isRest, rtickStart, nominal, maxDots);
      populateRhythmicList(dList, rightSplit, isRest, Fraction::fromTicks(rtickSplit) , nominal, maxDots);
      }

//---------------------------------------------------------
//   splitCompoundBeatsForList
//    Split compound notes/rests where they enter a compound beat.
//---------------------------------------------------------

void splitCompoundBeatsForList(std::vector<TDuration>* dList, const Fraction& l, bool isRest,
   const Fraction& rtickStart, const TimeSigFrac& nominal, int maxDots)
      {
      Fraction rtickEnd = rtickStart + l;

      BeatType startBeat = nominal.rtick2beatType(rtickStart.ticks());
      BeatType endBeat = nominal.rtick2beatType(rtickEnd.ticks());

      if (startBeat > BeatType::COMPOUND_UNSTRESSED) {
            // Not starting on a compound beat so mustn't extend into next compound beat
            int splitTicks = nominal.ticksToNextBeat(rtickStart.ticks());

            if ((rtickEnd - rtickStart).ticks() > splitTicks) {
                  // Duration extends into next beat so must split
                  Fraction leftSplit = Fraction::fromTicks(splitTicks);
                  Fraction rightSplit = l - leftSplit;
                  populateRhythmicList(dList, leftSplit, isRest, rtickStart, nominal, maxDots); // this side is ok to proceed
                  splitCompoundBeatsForList(dList, rightSplit, isRest, rtickStart + Fraction::fromTicks(splitTicks), nominal, maxDots); // not checked yet
                  return;
                  }
            }

      if (endBeat > BeatType::COMPOUND_UNSTRESSED) {
            // Not ending on a compound beat so mustn't extend into previous compound beat
            int splitTicks = nominal.ticksPastBeat(rtickEnd.ticks());

            if ((rtickEnd - rtickStart).ticks() > splitTicks) {
                  // Duration extends into previous beat so must split
                  Fraction rightSplit = Fraction::fromTicks(splitTicks);
                  Fraction leftSplit = l - rightSplit;
                  populateRhythmicList(dList, leftSplit, isRest, rtickStart, nominal, maxDots); // must add leftSplit to list first
                  populateRhythmicList(dList, rightSplit, isRest, rtickEnd - Fraction::fromTicks(splitTicks), nominal, maxDots);
                  return;
                  }
            }

      // Duration either starts and ends on compound beats, or it remains within a single compound beat
      populateRhythmicList(dList, l, isRest, rtickStart, nominal, maxDots);
      }

//---------------------------------------------------------
//   forceRhythmicSplit
//    Where to split notes and force a tie to indicate rhythm.
//
//    The function assumes the following:
//      * Note/rest has already been split at measure boundaries.
//      * Full measure rest was used if possible
//      * Note/rest already split where it enters a compound beat.
//
//    Usage: Set crossedBeat to the strongest BeatType crossed by
//    the note. Split note if function returns true. Repeat with
//    the two new notes, and so on, until function returns false.
//
//    Note: If no single TDuration can fill the gap then the note
//    *has* to be split, regardless of what this function returns.
//    Non-rhythmic splits should still occur on the strongest beat.
//
//    Implementation: When comparing BeatTypes use <, <=, >, >=
//    instead of == and != as appropriate (see sig.h). E.g. to match
//    all full beats: "<= SIMPLE_UNSTRESSED". This will match for
//    all compound and simple full beats, and not any subbeats.
//---------------------------------------------------------

bool forceRhythmicSplit(bool isRest, BeatType startBeat, BeatType endBeat,
   int dUnitsCrossed, BeatType strongestBeatCrossed, const TimeSigFrac& nominal) {

      // Assumption: Notes were split at measure boundary before this function was
      // called. (Necessary because timeSig might be different in next measure.)
      Q_ASSERT(strongestBeatCrossed != BeatType::DOWNBEAT);
      // Assumption: compound notes have already been split where they enter a compound beat.
      // (Necessary because the split beat is not always the strongest beat in this case.)
      Q_ASSERT(!nominal.isCompound() || strongestBeatCrossed >= BeatType::COMPOUND_SUBBEAT
            || (startBeat <= BeatType::COMPOUND_UNSTRESSED && endBeat <= BeatType::COMPOUND_UNSTRESSED));

      // SPECIAL CASES

      // nothing can cross a stressed beat in an irregular time signature
      if (strongestBeatCrossed <= BeatType::SIMPLE_STRESSED && !nominal.isTriple() && !nominal.isDuple())
            return true;
      if (isRest) {
            // rests must not cross the middle of a bar with numerator == 2 (e.g. 2/4 bar) even though the beat is unstressed
            if (strongestBeatCrossed <= BeatType::SIMPLE_UNSTRESSED && nominal.numerator() == 2)
                  return true;
            // rests must not cross a beat in a triple meter - simple (e.g. 3/4) or compound (e.g. 9/8)
            if (strongestBeatCrossed <= BeatType::SIMPLE_UNSTRESSED && nominal.isTriple())
                  return true;
            }

      // GENERAL RULES

      if (nominal.isCompound())
            return forceRhythmicSplitCompound(isRest, startBeat, endBeat, dUnitsCrossed, strongestBeatCrossed);
      else
            return forceRhythmicSplitSimple(isRest, startBeat, endBeat, dUnitsCrossed, strongestBeatCrossed);
      }

//---------------------------------------------------------
//   forceRhythmicSplitCompound
//---------------------------------------------------------

bool forceRhythmicSplitCompound(bool isRest, BeatType startBeat, BeatType endBeat, int dUnitsCrossed, BeatType strongestBeatCrossed)
      {
      switch (strongestBeatCrossed) {
            case BeatType::COMPOUND_STRESSED:
                  // Assumption: compound notes have already been split where they enter a compound beat.
                  Q_ASSERT(startBeat <= BeatType::COMPOUND_UNSTRESSED && endBeat <= BeatType::COMPOUND_UNSTRESSED);
                  // Notes are guaranteed to and start on a compound beat so we can pretend we have a simple measure.
                  return forceRhythmicSplitSimple(isRest, startBeat, endBeat, dUnitsCrossed / 3, BeatType::SIMPLE_STRESSED);
            case BeatType::COMPOUND_UNSTRESSED:
                  // Same assumption as before
                  Q_ASSERT(startBeat <= BeatType::COMPOUND_UNSTRESSED && endBeat <= BeatType::COMPOUND_UNSTRESSED);
                  // No further conditions since note are guaranteed to start and end on a compound beat.
                  return false;
            case BeatType::COMPOUND_SUBBEAT:
                  // don't split anything that takes up a full compound beat
                  if (startBeat <= BeatType::COMPOUND_UNSTRESSED && endBeat <= BeatType::COMPOUND_UNSTRESSED)
                        return false;
                  // split rests that don't start on a compound beat
                  if (isRest && startBeat > BeatType::COMPOUND_UNSTRESSED)
                        return true;
                  // Remaining groupings within compound triplets are the same as for simple triple (3/4, 3/8, etc.)
                  return forceRhythmicSplitSimple(isRest, startBeat, endBeat, dUnitsCrossed, BeatType::SIMPLE_UNSTRESSED);
            default: // BeatType::SUBBEAT
                  return forceRhythmicSplitSimple(isRest, startBeat, endBeat, dUnitsCrossed, strongestBeatCrossed);
            }
      }

//---------------------------------------------------------
//   forceRhythmicSplitSimple
//    Implementation: This function is also called for compound
//    measures so be careful when comparing BeatTypes. Use <, <=,
//    >, >= instead of == and != when appropriate. (See sig.h)
//---------------------------------------------------------

bool forceRhythmicSplitSimple(bool isRest, BeatType startBeat, BeatType endBeat, int beatsCrossed, BeatType strongestBeatCrossed)
      {
      switch (strongestBeatCrossed) {
            case BeatType::SIMPLE_STRESSED:
                  // Must split rests on a stressed beat.
                  if (isRest)
                        return true;
                  // Don't split notes that start or end on a stressed beat. Enables double-dotting in 4/4.
                  // (Don't remove this to disable double-dotting, instead set maxDots = 1 elsewhere.)
                  if (startBeat <= BeatType::SIMPLE_STRESSED || endBeat <= BeatType::SIMPLE_STRESSED)
                        return false;
                  // Don't split notes that both start and end on unstressed beats or stronger.
                  if (startBeat <= BeatType::SIMPLE_UNSTRESSED && endBeat <= BeatType::SIMPLE_UNSTRESSED)
                        return false;
                  // anything else must split on stressed beat.
                  return true;
            case BeatType::SIMPLE_UNSTRESSED:
                  // Don't split notes or rests if starting and ending on stressed beat.
                  if (startBeat <= BeatType::SIMPLE_STRESSED && endBeat <= BeatType::SIMPLE_STRESSED)
                        return false;
                  // Split rests that don't start or end on a beat. Notes may cross 1 unstressed beat.
                  if (startBeat == BeatType::SUBBEAT || endBeat == BeatType::SUBBEAT)
                        return isRest || (beatsCrossed > 1);
                  return false;
            default: // BeatType::SUBBEAT
                  return false;
            }
      }

//---------------------------------------------------------
//   print
//---------------------------------------------------------

QString TDuration::durationTypeUserName() const
      {
      QString s = QObject::tr("Custom");
      switch(_val) {
            case DurationType::V_LONG:      s = QObject::tr("Longa"  ); break;
            case DurationType::V_BREVE:     s = QObject::tr("Breve"  ); break;
            case DurationType::V_WHOLE:     s = QObject::tr("Whole"  ); break;
            case DurationType::V_HALF:      s = QObject::tr("Half"   ); break;
            case DurationType::V_QUARTER:   s = QObject::tr("Quarter"); break;
            case DurationType::V_EIGHTH:    s = QObject::tr("Eighth" ); break;
            case DurationType::V_16TH:      s = QObject::tr("16th"   ); break;
            case DurationType::V_32ND:      s = QObject::tr("32nd"   ); break;
            case DurationType::V_64TH:      s = QObject::tr("64th"   ); break;
            case DurationType::V_128TH:     s = QObject::tr("128th"  ); break;
            case DurationType::V_256TH:     s = QObject::tr("256th"  ); break;
            case DurationType::V_512TH:     s = QObject::tr("512th"  ); break;
            case DurationType::V_1024TH:    s = QObject::tr("1024th" ); break;
            case DurationType::V_ZERO:      s = QObject::tr("Zero"   ); break;
            case DurationType::V_MEASURE:   s = QObject::tr("Measure"); break;
            case DurationType::V_INVALID:   s = QObject::tr("Invalid"); break;
            };
      return s;
      }

//---------------------------------------------------------
//   setType
//---------------------------------------------------------

void TDuration::setType(DurationType t)
      {
      _val = t;
      if (_val == DurationType::V_MEASURE)
            _dots = 0;
      }

//---------------------------------------------------------
//   isValid
//---------------------------------------------------------

bool TDuration::isValid(Fraction f)
     {
     TDuration t;
     t.setType(DurationType::V_LONG);
     t.setDots(4);
     t.truncateToFraction(f, 4);
     return (t.isValid() && (t.fraction() - f).numerator() == 0);
     }
}

