/* Copyright (C) 2017-2020 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include "pbd/integer_division.h" #include "temporal/beats.h" #include "temporal/debug.h" #include "temporal/tempo.h" #include "pbd/i18n.h" using namespace PBD; using namespace Temporal; Beats Beats::round_to_subdivision (int subdivision, RoundMode dir) const { if (subdivision == 0) { /* do not round */ return *this; } if (subdivision < 0) { /* round to bar */ TempoMap::SharedPtr map (TempoMap::use()); const TempoMetric metric (map->metric_at (*this)); BBT_Time bbt (metric.bbt_at (*this)); if (dir < 0) { if (dir == RoundDownAlways && bbt.ticks == 0) { bbt = metric.bbt_subtract (bbt, Temporal::BBT_Offset (0, 0, 1)); } bbt = bbt.round_down_to_bar (); } if (dir > 0) { if (dir == RoundUpAlways && bbt.ticks == 0) { bbt.ticks += 1; } bbt = bbt.round_up_to_bar (); } else { bbt = metric.meter().round_to_bar (bbt); } return metric.quarters_at (bbt); } uint32_t ticks = to_ticks(); const uint32_t ticks_one_subdivisions_worth = ticks_per_beat / subdivision; uint32_t mod = ticks % ticks_one_subdivisions_worth; DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("%4 => round-nearest, ticks %1 1 div of ticks %2 mod %3\n", ticks, ticks_one_subdivisions_worth, mod, *this)); if (dir > 0) { if (mod == 0 && dir == RoundUpMaybe) { /* right on the subdivision, which is fine, so do nothing */ } else if (mod == 0) { /* right on the subdivision, so the difference is just the subdivision ticks */ ticks += ticks_one_subdivisions_worth; } else { /* not on subdivision, compute distance to next subdivision */ ticks += ticks_one_subdivisions_worth - mod; } // NOTE: this code intentionally limits the rounding so we don't advance to the next beat. // For the purposes of "jump-to-next-subdivision", we DO want to advance to the next beat. // And since the "prev" direction DOES move beats, I assume this code is unintended. // But I'm keeping it around, until we determine there are no terrible consequences. // if (ticks >= Temporal::ticks_per_beat) { // ticks -= Temporal::ticks_per_beat; // } } else if (dir < 0) { /* round to previous (or same iff di == RoundDownMaybe) */ if (mod == 0 && dir == RoundDownAlways) { /* right on the subdivision, but force-rounding down, so the modulo (difference) is just the subdivision ticks */ mod = ticks_one_subdivisions_worth; } if (ticks < mod) { ticks = ticks_per_beat - ticks; } else { ticks -= mod; } } else { /* round to nearest, which happens to be precisely what * PBD::int_div_round() does. Set beats to zero, since the * Beats normalization will set the values correctly in the * value we actually return */ ticks = int_div_round (ticks, ticks_one_subdivisions_worth) * ticks_one_subdivisions_worth; } DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("return %1 from %2 : %3\n", Beats (0, ticks), ticks)); return Beats (0, ticks); } std::istream& Temporal::operator>>(std::istream& istr, Beats& b) { double dbeats; int32_t beats, ticks; char d; /* delimiter, whatever it is */ /* use double first to handle pre-nutempo values that would be * serialized as double. */ istr >> dbeats; if (!istr) { throw std::invalid_argument (_("illegal or missing value for beat count")); } istr >> d; /* we don't care what the delimiter is */ if (!istr) { if (istr.eof()) { /* just a number. Convert dbeats and get out */ b = Beats::from_double (dbeats); return istr; } throw std::invalid_argument (_("illegal or missing delimiter for beat value")); } /* we just assuming, since the input format included a delimiter * character, that the numerical value was integral and convert without * checking. */ beats = (int32_t) dbeats; istr >> ticks; if (!istr) { throw std::invalid_argument (_("illegal or missing delimiter for tick count")); } b = Beats (beats, ticks); return istr; } std::ostream& Temporal::operator<<(std::ostream& os, const Beats& t) { os << t.get_beats() << ':' << t.get_ticks(); return os; }