/// <summary> /// Shifts events forward inside <see cref="TrackChunk"/> by the specified distance. /// </summary> /// <param name="trackChunk"><see cref="TrackChunk"/> containing events to shift.</param> /// <param name="distance">Distance to shift events by.</param> /// <param name="tempoMap">Tempo map used for internal distance conversions.</param> /// <exception cref="ArgumentNullException"><paramref name="trackChunk"/> is null. -or- /// <paramref name="distance"/> is null. -or- <paramref name="tempoMap"/> is null.</exception> public static void ShiftEvents(this TrackChunk trackChunk, ITimeSpan distance, TempoMap tempoMap) { ThrowIfArgument.IsNull(nameof(trackChunk), trackChunk); ThrowIfArgument.IsNull(nameof(distance), distance); ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); var convertedDistance = TimeConverter.ConvertFrom(distance, TempoMap.Create(tempoMap.TimeDivision)); var firstEvent = trackChunk.Events.FirstOrDefault(); if (firstEvent == null) { return; } firstEvent.DeltaTime += convertedDistance; }
private static void ResizeNotesByRatio(IEnumerable <Note> notes, double ratio, TimeSpanType distanceCalculationType, TempoMap tempoMap, ITimeSpan startTime) { foreach (var note in notes) { var noteLength = note.LengthAs(distanceCalculationType, tempoMap); var noteTime = note.TimeAs(distanceCalculationType, tempoMap); var scaledShiftFromStart = noteTime.Subtract(startTime, TimeSpanMode.TimeTime).Multiply(ratio); note.Time = TimeConverter.ConvertFrom(startTime.Add(scaledShiftFromStart, TimeSpanMode.TimeLength), tempoMap); var scaledLength = noteLength.Multiply(ratio); note.Length = LengthConverter.ConvertFrom(scaledLength, note.Time, tempoMap); } }
public long ConvertFrom(ILength length, long time, TempoMap tempoMap) { ThrowIfArgument.IsNull(nameof(length), length); ThrowIfTimeArgument.IsNegative(nameof(time), time); ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); var metricLength = length as MetricLength; if (metricLength == null) { throw new ArgumentException($"Length is not an instance of the {nameof(MetricLength)}.", nameof(length)); } var startTime = TimeConverter.ConvertTo <MetricTime>(time, tempoMap); var endTime = new MetricTime(startTime.TotalMicroseconds + metricLength.TotalMicroseconds); return(TimeConverter.ConvertFrom(endTime, tempoMap) - time); }
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 MusicalTimeSpan()); } var xy = MathUtilities.SolveDiophantineEquation(4 * ticksPerQuarterNoteTimeDivision.TicksPerQuarterNote, -timeSpan); return(new MusicalTimeSpan(Math.Abs(xy.Item1), Math.Abs(xy.Item2))); }
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 musicalTimeSpan = (MusicalTimeSpan)timeSpan; if (musicalTimeSpan.Numerator == 0) { return(0); } return(MathUtilities.RoundToLong(4.0 * musicalTimeSpan.Numerator * ticksPerQuarterNoteTimeDivision.TicksPerQuarterNote / musicalTimeSpan.Denominator)); }
private static long MetricTimeSpanToTicks(MetricTimeSpan timeSpan, TempoMap tempoMap) { var timeMicroseconds = timeSpan.TotalMicroseconds; if (timeMicroseconds == 0) { return(0); } var valuesCache = tempoMap.GetValuesCache <MetricTempoMapValuesCache>(); var accumulatedMicroseconds = valuesCache.Microseconds.TakeWhile(m => m.Microseconds < timeMicroseconds).LastOrDefault(); var lastAccumulatedMicroseconds = accumulatedMicroseconds?.Microseconds ?? 0; var lastTime = accumulatedMicroseconds?.Time ?? 0; var lastTicksPerMicrosecond = accumulatedMicroseconds?.TicksPerMicrosecond ?? valuesCache.DefaultTicksPerMicrosecond; return(RoundMicroseconds(lastTime + (timeMicroseconds - lastAccumulatedMicroseconds) * lastTicksPerMicrosecond)); }
/// <summary> /// Initializes a new instance of the <see cref="TempoMapManager"/> with the specified time division /// and events collections. /// </summary> /// <param name="timeDivision">MIDI file time division which specifies the meaning of the time /// used by events of the file.</param> /// <param name="eventsCollections">Collection of <see cref="EventsCollection"/> which hold events that /// represent tempo map of a MIDI file.</param> /// <exception cref="ArgumentNullException"><paramref name="timeDivision"/> is null. -or- /// <paramref name="eventsCollections"/> is null.</exception> /// <exception cref="ArgumentException"><paramref name="eventsCollections"/> is empty.</exception> public TempoMapManager(TimeDivision timeDivision, IEnumerable <EventsCollection> eventsCollections) { ThrowIfArgument.IsNull(nameof(timeDivision), timeDivision); ThrowIfArgument.IsNull(nameof(eventsCollections), eventsCollections); ThrowIfArgument.IsEmptyCollection(nameof(eventsCollections), eventsCollections, $"Collection of {nameof(EventsCollection)} is empty."); _timedEventsManagers = eventsCollections.Where(events => events != null) .Select(events => events.ManageTimedEvents()) .ToList(); // TempoMap = new TempoMap(timeDivision); CollectTimeSignatureChanges(); CollectTempoChanges(); }
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 MetricTimeSpan()); } var startTimeSpan = TicksToMetricTimeSpan(time, tempoMap); var endTimeSpan = TicksToMetricTimeSpan(time + timeSpan, tempoMap); return(endTimeSpan - startTimeSpan); }
/// <summary> /// Exports the current <see cref="Pattern"/> to track chunk. /// </summary> /// <param name="tempoMap">Tempo map to process pattern data according with.</param> /// <param name="channel">Channel of notes that will be generated by pattern.</param> /// <returns>The <see cref="TrackChunk"/> containing notes events generated by the current <see cref="Pattern"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="tempoMap"/> is null.</exception> public TrackChunk ToTrackChunk(TempoMap tempoMap, FourBitNumber channel) { ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); var context = new PatternContext(tempoMap, channel); var result = InvokeActions(0, context); // var trackChunk = new TrackChunk(); using (var notesManager = trackChunk.ManageNotes()) { notesManager.Notes.Add(result.Notes ?? Enumerable.Empty <Note>()); } // return(trackChunk); }
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 metricTimeSpan = (MetricTimeSpan)timeSpan; if ((TimeSpan)metricTimeSpan == TimeSpan.Zero) { return(0); } var startTimeSpan = TicksToMetricTimeSpan(time, tempoMap); var endTimeSpan = startTimeSpan + metricTimeSpan; return(MetricTimeSpanToTicks(endTimeSpan, tempoMap) - time); }
/// <summary> /// Gets points in time of the current grid. /// </summary> /// <param name="tempoMap">Tempo map used to get grid's times.</param> /// <returns>Collection of points in time of the current grid.</returns> /// <exception cref="ArgumentNullException"><paramref name="tempoMap"/> is null.</exception> public IEnumerable <long> GetTimes(TempoMap tempoMap) { ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); if (!Steps.Any()) { yield break; } var time = TimeConverter.ConvertFrom(Start, tempoMap); yield return(time); while (true) { foreach (var step in Steps) { time += LengthConverter.ConvertFrom(step, time, tempoMap); yield return(time); } } }
public long ConvertFrom(ITime time, TempoMap tempoMap) { ThrowIfArgument.IsNull(nameof(time), time); ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); var metricTime = time as MetricTime; if (metricTime == null) { throw new ArgumentException($"Time is not an instance of the {nameof(MetricTime)}.", nameof(time)); } var ticksPerQuarterNoteTimeDivision = tempoMap.TimeDivision as TicksPerQuarterNoteTimeDivision; if (ticksPerQuarterNoteTimeDivision != null) { return(ConvertFromByTicksPerQuarterNote(metricTime, ticksPerQuarterNoteTimeDivision.TicksPerQuarterNote, tempoMap)); } ThrowIfTimeDivision.IsNotSupportedForTimeConversion(tempoMap.TimeDivision); return(0); }
public long ConvertFrom(ILength length, long time, TempoMap tempoMap) { ThrowIfArgument.IsNull(nameof(length), length); ThrowIfTimeArgument.IsNegative(nameof(time), time); ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); var musicalLength = length as MusicalLength; if (musicalLength == null) { throw new ArgumentException($"Length is not an instance of the {nameof(MusicalLength)}.", nameof(length)); } var ticksPerQuarterNoteTimeDivision = tempoMap.TimeDivision as TicksPerQuarterNoteTimeDivision; if (ticksPerQuarterNoteTimeDivision != null) { return(musicalLength.Fraction.ToTicks(ticksPerQuarterNoteTimeDivision.TicksPerQuarterNote)); } ThrowIfTimeDivision.IsNotSupportedForLengthConversion(tempoMap.TimeDivision); return(0); }
/// <summary> /// Replaces tempo map contained in the specified collection of the <see cref="TrackChunk" /> with /// another one. /// </summary> /// <param name="trackChunks">Collection of the <see cref="TrackChunk" /> holding a tempo map to replace.</param> /// <param name="tempoMap">Tempo map to replace the one contained in the <paramref name="trackChunks" />.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="trackChunks" /> is null. -or- /// <paramref name="tempoMap" /> is null. /// </exception> /// <exception cref="ArgumentException"><paramref name="trackChunks" /> is empty.</exception> public static void ReplaceTempoMap(this IEnumerable <TrackChunk> trackChunks, TempoMap tempoMap) { ThrowIfArgument.IsNull(nameof(trackChunks), trackChunks); ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); ThrowIfArgument.IsEmptyCollection(nameof(trackChunks), trackChunks, $"Collection of {nameof(TrackChunk)} is empty."); trackChunks.Select(c => c.Events).ReplaceTempoMap(tempoMap); }
/// <summary> /// Adds a <see cref="MidiEvent"/> into a <see cref="TimedEventsCollection"/> with the specified /// absolute time. /// </summary> /// <param name="eventsCollection"><see cref="TimedEventsCollection"/> to add an event into.</param> /// <param name="midiEvent">Event to add into the <paramref name="eventsCollection"/>.</param> /// <param name="time">Absolute time that will be assigned to the <paramref name="midiEvent"/> /// when it will be placed into the <paramref name="eventsCollection"/>.</param> /// <param name="tempoMap">Tempo map used to place <paramref name="midiEvent"/> into the /// <paramref name="eventsCollection"/> with the specified time.</param> /// <exception cref="ArgumentNullException"><paramref name="eventsCollection"/> is null. -or- /// <paramref name="midiEvent"/> is null. -or- <paramref name="time"/> is null. -or- /// <paramref name="tempoMap"/> is null.</exception> public static void AddEvent(this TimedEventsCollection eventsCollection, MidiEvent midiEvent, ITimeSpan time, TempoMap tempoMap) { ThrowIfArgument.IsNull(nameof(eventsCollection), eventsCollection); ThrowIfArgument.IsNull(nameof(midiEvent), midiEvent); ThrowIfArgument.IsNull(nameof(time), time); ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); eventsCollection.AddEvent(midiEvent, TimeConverter.ConvertFrom(time, tempoMap)); }
private static long ConvertFromTimeLength(MathTimeSpan mathTimeSpan, long time, TempoMap tempoMap) { var convertedTimeSpan1 = TimeConverter.ConvertFrom(mathTimeSpan.TimeSpan1, tempoMap); switch (mathTimeSpan.Operation) { case MathOperation.Add: return(convertedTimeSpan1 + LengthConverter.ConvertFrom(mathTimeSpan.TimeSpan2, convertedTimeSpan1, tempoMap)); case MathOperation.Subtract: return(convertedTimeSpan1 - LengthConverter.ConvertFrom(mathTimeSpan.TimeSpan2, convertedTimeSpan1, tempoMap.Flip(convertedTimeSpan1))); default: throw new ArgumentException($"{mathTimeSpan.Operation} is not supported by the converter.", nameof(mathTimeSpan)); } }
/// <summary> /// Replaces tempo map contained in the specified collection of the <see cref="EventsCollection" /> with /// another one. /// </summary> /// <param name="eventsCollections">Collection of the <see cref="EventsCollection" /> holding a tempo map to replace.</param> /// <param name="tempoMap">Tempo map to replace the one contained in the <paramref name="eventsCollections" />.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="eventsCollections" /> is null. -or- /// <paramref name="tempoMap" /> is null. /// </exception> /// <exception cref="ArgumentException"><paramref name="eventsCollections" /> is empty.</exception> public static void ReplaceTempoMap(this IEnumerable <EventsCollection> eventsCollections, TempoMap tempoMap) { ThrowIfArgument.IsNull(nameof(eventsCollections), eventsCollections); ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); ThrowIfArgument.IsEmptyCollection(nameof(eventsCollections), eventsCollections, $"Collection of {nameof(EventsCollection)} is empty."); using (var tempoMapManager = eventsCollections.ManageTempoMap(tempoMap.TimeDivision)) { tempoMapManager.ReplaceTempoMap(tempoMap); } }
private static long ConvertFromTimeTime(MathTimeSpan mathTimeSpan, long time, TempoMap tempoMap) { var timeSpan1 = mathTimeSpan.TimeSpan1; var timeSpan2 = mathTimeSpan.TimeSpan2; // Ensure that the first time span is not an instance of the MathTimeSpan. If it is, // convert it to the type of its first time span. For example, if we the following // time span was passed to the method: // // (a1 + b1) + c // // the time span will be transformed to: // // a2 + c var timeSpan1AsMath = mathTimeSpan.TimeSpan1 as MathTimeSpan; if (timeSpan1AsMath != null) { timeSpan1 = TimeSpanConverter.ConvertTo(timeSpan1AsMath, timeSpan1AsMath.TimeSpan1.GetType(), time, tempoMap); } // To subtract one time from another one we need to convert the second time span // to the type of the first one. After that result of subtraction will be of the // first time span type. And finally we convert result time span according to the // specified time. For example, time span shown above will be transformed first to // // a3 // // and then will be converted to MIDI time. switch (mathTimeSpan.Operation) { case MathOperation.Subtract: { var convertedTimeSpan2 = TimeConverter.ConvertTo(timeSpan2, timeSpan1.GetType(), tempoMap); return(TimeSpanConverter.ConvertFrom(timeSpan1.Subtract(convertedTimeSpan2, TimeSpanMode.TimeTime), time, tempoMap)); } default: throw new ArgumentException($"{mathTimeSpan.Operation} is not supported by the converter.", nameof(mathTimeSpan)); } }
public long ConvertFrom(ILength length, ITime time, TempoMap tempoMap) { return(ConvertFrom(length, TimeConverter.ConvertFrom(time, tempoMap), tempoMap)); }
public static TTimeSpan ConvertTo <TTimeSpan>(long timeSpan, long time, TempoMap tempoMap) where TTimeSpan : ITimeSpan { return((TTimeSpan)GetConverter <TTimeSpan>().ConvertTo(timeSpan, time, tempoMap)); }
public ILength ConvertTo(long length, ITime time, TempoMap tempoMap) { return(ConvertTo(length, TimeConverter.ConvertFrom(time, tempoMap), tempoMap)); }
/// <summary> /// Exports the current <see cref="Pattern"/> to track chunk using zero channel. /// </summary> /// <param name="tempoMap">Tempo map to process pattern data according with.</param> /// <returns>The <see cref="TrackChunk"/> containing notes events generated by the current <see cref="Pattern"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="tempoMap"/> is null.</exception> public TrackChunk ToTrackChunk(TempoMap tempoMap) { return(ToTrackChunk(tempoMap, FourBitNumber.MinValue)); }
private static Tuple <TimeSignature, short> GetTimeSignatureAndTicksPerQuarterNote(long bars, TempoMap tempoMap) { var ticksPerQuarterNoteTimeDivision = tempoMap.TimeDivision as TicksPerQuarterNoteTimeDivision; if (ticksPerQuarterNoteTimeDivision == null) { throw new ArgumentException("Time division of the tempo map is not supported.", nameof(tempoMap)); } var ticks = TimeConverter.ConvertFrom(new BarBeatTicksTimeSpan(bars), tempoMap); var timeSignature = tempoMap.TimeSignature.AtTime(ticks); var ticksPerQuarterNote = ticksPerQuarterNoteTimeDivision.TicksPerQuarterNote; return(Tuple.Create(timeSignature, ticksPerQuarterNote)); }
/// <summary> /// Exports the current <see cref="Pattern"/> to MIDI file using zero channel. /// </summary> /// <param name="tempoMap">Tempo map to process pattern data according with.</param> /// <returns>The <see cref="MidiFile"/> containing notes events generated by the current <see cref="Pattern"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="tempoMap"/> is null.</exception> public MidiFile ToFile(TempoMap tempoMap) { return(ToFile(tempoMap, FourBitNumber.MinValue)); }
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)); } var ticksPerQuarterNote = ticksPerQuarterNoteTimeDivision.TicksPerQuarterNote; var endTime = time + timeSpan; // var timeSignatureLine = tempoMap.TimeSignature; var timeSignatureChanges = timeSignatureLine .Values .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 = 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 = GetBeatLength(firstTimeSignature, ticksPerQuarterNote); if (ticksBefore > 0 && ticks >= beatLength) { beats++; ticks -= beatLength; } } // return(new BarBeatTimeSpan((int)bars, (int)beats, (int)ticks)); }
public static ITimeSpan ConvertTo(ITimeSpan timeSpan, Type timeSpanType, long time, TempoMap tempoMap) { if (timeSpan.GetType() == timeSpanType) { return(timeSpan.Clone()); } return(GetConverter(timeSpanType).ConvertTo(ConvertFrom(timeSpan, time, tempoMap), time, tempoMap)); }
/// <summary> /// Filters collection of <see cref="ILengthedObject"/> to return objects at the specified time. /// </summary> /// <typeparam name="TObject">The type of the elements of <paramref name="objects"/>.</typeparam> /// <param name="objects">A collection to filter.</param> /// <param name="time">Time to filter objects by.</param> /// <param name="tempoMap">Tempo map to filter <paramref name="objects"/> by <paramref name="time"/>.</param> /// <param name="matchBy">Part of an object which have to be at <paramref name="time"/>.</param> /// <returns>A collection that contains objects from the input sequence that are at the specified time.</returns> /// <remarks> /// Note that changes made on the objects returned by this method will not be saved to an underlying /// data source (events collection, track chunk, file). To change properties of lengthed objects and /// save them you need to use a manager appropriate for an object's type. /// </remarks> /// <exception cref="ArgumentNullException"><paramref name="objects"/> is null. -or- <paramref name="time"/> is null. -or- /// <paramref name="tempoMap"/> is null. -or- One of the objects is null.</exception> /// <exception cref="InvalidEnumArgumentException"><paramref name="matchBy"/> specified an invalid value.</exception> public static IEnumerable <TObject> AtTime <TObject>(this IEnumerable <TObject> objects, ITime time, TempoMap tempoMap, LengthedObjectPart matchBy) where TObject : ILengthedObject { ThrowIfArgument.IsNull(nameof(objects), objects); ThrowIfArgument.IsNull(nameof(time), time); ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); ThrowIfArgument.IsInvalidEnumValue(nameof(matchBy), matchBy); var convertedTime = TimeConverter.ConvertFrom(time, tempoMap); return(AtTime(objects, convertedTime, matchBy)); }
public static long ConvertFrom(ITimeSpan timeSpan, long time, TempoMap tempoMap) { return(GetConverter(timeSpan.GetType()).ConvertFrom(timeSpan, time, tempoMap)); }
/// <summary> /// Filters collection of <see cref="ILengthedObject"/> to return objects that start at the specified time. /// </summary> /// <typeparam name="TObject">The type of the elements of <paramref name="objects"/>.</typeparam> /// <param name="objects">A collection to filter.</param> /// <param name="time">Start time to filter objects by.</param> /// <param name="tempoMap">Tempo map to filter <paramref name="objects"/> by <paramref name="time"/>.</param> /// <returns>A collection that contains objects from the input sequence that start at the specified time.</returns> /// <remarks> /// Note that changes made on the objects returned by this method will not be saved to an underlying /// data source (events collection, track chunk, file). To change properties of lengthed objects and /// save them you need to use a manager appropriate for an object's type. /// </remarks> /// <exception cref="ArgumentNullException"><paramref name="objects"/> is null. -or- <paramref name="time"/> is null. -or- /// <paramref name="tempoMap"/> is null. -or- One of the objects is null.</exception> public static IEnumerable <TObject> StartAtTime <TObject>(this IEnumerable <TObject> objects, ITime time, TempoMap tempoMap) where TObject : ILengthedObject { return(AtTime(objects, time, tempoMap, LengthedObjectPart.Start)); }
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 barBeatTimeSpan = (BarBeatTimeSpan)timeSpan; var ticksPerQuarterNote = ticksPerQuarterNoteTimeDivision.TicksPerQuarterNote; var timeSignatureLine = tempoMap.TimeSignature; // long bars = barBeatTimeSpan.Bars; long beats = barBeatTimeSpan.Beats; long ticks = barBeatTimeSpan.Ticks; var startTimeSignature = timeSignatureLine.AtTime(time); var startBarLength = GetBarLength(startTimeSignature, ticksPerQuarterNote); var startBeatLength = GetBeatLength(startTimeSignature, ticksPerQuarterNote); var totalTicks = bars * startBarLength + beats * startBeatLength + ticks; var timeSignatureChanges = timeSignatureLine.Values .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.Values.Where(v => v.Time > lastTime).ToList()) { var deltaTime = timeSignatureChange.Time - lastTime; lastBarLength = GetBarLength(lastTimeSignature, ticksPerQuarterNote); lastBeatLength = 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 = GetBarLength(lastTimeSignature, ticksPerQuarterNote); lastBeatLength = 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 = 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); }