private static void CalculateComponents(long totalTicks,
                                                TimeSignature timeSignature,
                                                short ticksPerQuarterNote,
                                                out long bars,
                                                out long beats,
                                                out long ticks)
        {
            var barLength = BarBeatTimeSpanUtilities.GetBarLength(timeSignature, ticksPerQuarterNote);

            bars = Math.DivRem(totalTicks, barLength, out ticks);

            var beatLength = BarBeatTimeSpanUtilities.GetBeatLength(timeSignature, ticksPerQuarterNote);

            beats = Math.DivRem(ticks, beatLength, out ticks);
        }
        public ITimeSpan ConvertTo(long timeSpan, long time, TempoMap tempoMap)
        {
            var ticksPerQuarterNoteTimeDivision = tempoMap.TimeDivision as TicksPerQuarterNoteTimeDivision;

            if (ticksPerQuarterNoteTimeDivision == null)
            {
                throw new ArgumentException("Time division is not supported for time span conversion.", nameof(tempoMap));
            }

            if (timeSpan == 0)
            {
                return(new BarBeatTicksTimeSpan());
            }

            var ticksPerQuarterNote = ticksPerQuarterNoteTimeDivision.TicksPerQuarterNote;
            var endTime             = time + timeSpan;

            //

            var timeSignatureLine    = tempoMap.TimeSignature;
            var timeSignatureChanges = timeSignatureLine
                                       .Where(v => v.Time > time && v.Time < endTime)
                                       .ToList();

            var bars = 0L;

            // Calculate count of complete bars between time signature changes

            for (int i = 0; i < timeSignatureChanges.Count - 1; i++)
            {
                var timeSignatureChange = timeSignatureChanges[i];
                var nextTime            = timeSignatureChanges[i + 1].Time;

                var barLength = BarBeatTimeSpanUtilities.GetBarLength(timeSignatureChange.Value, ticksPerQuarterNote);
                bars += (nextTime - timeSignatureChange.Time) / barLength;
            }

            // Calculate components before first time signature change and after last time signature change

            var firstTime = timeSignatureChanges.FirstOrDefault()?.Time ?? time;
            var lastTime  = timeSignatureChanges.LastOrDefault()?.Time ?? time;

            var firstTimeSignature = timeSignatureLine.AtTime(time);
            var lastTimeSignature  = timeSignatureLine.AtTime(lastTime);

            long barsBefore, beatsBefore, ticksBefore;

            CalculateComponents(firstTime - time,
                                firstTimeSignature,
                                ticksPerQuarterNote,
                                out barsBefore,
                                out beatsBefore,
                                out ticksBefore);

            long barsAfter, beatsAfter, ticksAfter;

            CalculateComponents(time + timeSpan - lastTime,
                                lastTimeSignature,
                                ticksPerQuarterNote,
                                out barsAfter,
                                out beatsAfter,
                                out ticksAfter);

            bars += barsBefore + barsAfter;

            // Try to complete a bar

            var beats = beatsBefore + beatsAfter;

            if (beats > 0)
            {
                if (beatsBefore > 0 && beats >= firstTimeSignature.Numerator)
                {
                    bars++;
                    beats -= firstTimeSignature.Numerator;
                }
            }

            // Try to complete a beat

            var ticks = ticksBefore + ticksAfter;

            if (ticks > 0)
            {
                var beatLength = BarBeatTimeSpanUtilities.GetBeatLength(firstTimeSignature, ticksPerQuarterNote);
                if (ticksBefore > 0 && ticks >= beatLength)
                {
                    beats++;
                    ticks -= beatLength;
                }
            }

            //

            return(new BarBeatTicksTimeSpan(bars, beats, ticks));
        }
        public long ConvertFrom(ITimeSpan timeSpan, long time, TempoMap tempoMap)
        {
            var ticksPerQuarterNoteTimeDivision = tempoMap.TimeDivision as TicksPerQuarterNoteTimeDivision;

            if (ticksPerQuarterNoteTimeDivision == null)
            {
                throw new ArgumentException("Time division is not supported for time span conversion.", nameof(tempoMap));
            }

            var barBeatTicksTimeSpan = (BarBeatTicksTimeSpan)timeSpan;

            if (barBeatTicksTimeSpan.Bars == 0 && barBeatTicksTimeSpan.Beats == 0 && barBeatTicksTimeSpan.Ticks == 0)
            {
                return(0);
            }

            var ticksPerQuarterNote = ticksPerQuarterNoteTimeDivision.TicksPerQuarterNote;
            var timeSignatureLine   = tempoMap.TimeSignature;

            //

            long bars  = barBeatTicksTimeSpan.Bars;
            long beats = barBeatTicksTimeSpan.Beats;
            long ticks = barBeatTicksTimeSpan.Ticks;

            var startTimeSignature = timeSignatureLine.AtTime(time);
            var startBarLength     = BarBeatTimeSpanUtilities.GetBarLength(startTimeSignature, ticksPerQuarterNote);
            var startBeatLength    = BarBeatTimeSpanUtilities.GetBeatLength(startTimeSignature, ticksPerQuarterNote);

            var totalTicks           = bars * startBarLength + beats * startBeatLength + ticks;
            var timeSignatureChanges = timeSignatureLine.Where(v => v.Time > time && v.Time < time + totalTicks).ToList();

            var lastBarLength  = 0L;
            var lastBeatLength = 0L;

            var firstTimeSignatureChange = timeSignatureChanges.FirstOrDefault();
            var lastTimeSignature        = firstTimeSignatureChange?.Value ?? startTimeSignature;
            var lastTime = firstTimeSignatureChange?.Time ?? time;

            long barsBefore, beatsBefore, ticksBefore;

            CalculateComponents(lastTime - time,
                                startTimeSignature,
                                ticksPerQuarterNote,
                                out barsBefore,
                                out beatsBefore,
                                out ticksBefore);

            bars -= barsBefore;

            // Balance bars

            foreach (var timeSignatureChange in timeSignatureLine.Where(v => v.Time > lastTime).ToList())
            {
                var deltaTime = timeSignatureChange.Time - lastTime;

                lastBarLength  = BarBeatTimeSpanUtilities.GetBarLength(lastTimeSignature, ticksPerQuarterNote);
                lastBeatLength = BarBeatTimeSpanUtilities.GetBeatLength(lastTimeSignature, ticksPerQuarterNote);

                var currentBars = Math.Min(deltaTime / lastBarLength, bars);
                bars     -= currentBars;
                lastTime += currentBars * lastBarLength;

                if (bars == 0)
                {
                    break;
                }

                lastTimeSignature = timeSignatureChange.Value;
            }

            if (bars > 0)
            {
                lastBarLength  = BarBeatTimeSpanUtilities.GetBarLength(lastTimeSignature, ticksPerQuarterNote);
                lastBeatLength = BarBeatTimeSpanUtilities.GetBeatLength(lastTimeSignature, ticksPerQuarterNote);
                lastTime      += bars * lastBarLength;
            }

            if (beats == beatsBefore && ticks == ticksBefore)
            {
                return(lastTime - time);
            }

            // Balance beats

            if (beatsBefore > beats && lastBarLength > 0)
            {
                lastTime   += -lastBarLength + (startTimeSignature.Numerator - beatsBefore) * lastBeatLength;
                beatsBefore = 0;
            }

            if (beatsBefore < beats)
            {
                lastBeatLength = BarBeatTimeSpanUtilities.GetBeatLength(timeSignatureLine.AtTime(lastTime), ticksPerQuarterNote);
                lastTime      += (beats - beatsBefore) * lastBeatLength;
            }

            // Balance ticks

            if (ticksBefore > ticks && lastBeatLength > 0)
            {
                lastTime   += -lastBeatLength + startBeatLength - ticksBefore;
                ticksBefore = 0;
            }

            if (ticksBefore < ticks)
            {
                lastTime += ticks - ticksBefore;
            }

            //

            return(lastTime - time);
        }