Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>

<VersionPrefix>5.7.4</VersionPrefix>
<BuildIncrement>46</BuildIncrement>
<VersionPrefix>5.7.5</VersionPrefix>
<BuildIncrement>47</BuildIncrement>
<VersionSuffix></VersionSuffix>
<Product>FitEdit</Product>
<Copyright>EnduraByte LLC 2024</Copyright>
Expand Down
4 changes: 2 additions & 2 deletions Infrastructure/FitEdit.Adapters.Fit/Extensions/FieldTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static class FieldTools
public static void ReadFieldValue(
FieldBase field,
byte size,
EndianBinaryReader mesgReader)
BinaryReader mesgReader)
{
byte baseType = (byte)(field.Type & Fit.BaseTypeNumMask);
// strings may be an array and are of variable length
Expand Down Expand Up @@ -76,7 +76,7 @@ public static void ReadFieldValue(
private static bool TryReadValue(
out object value,
byte type,
EndianBinaryReader mesgReader,
BinaryReader mesgReader,
byte size)
{
bool invalid = true;
Expand Down
2 changes: 1 addition & 1 deletion Infrastructure/FitEdit.Adapters.Fit/Mesg.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public Mesg(Stream fitStream, MesgDefinition defnMesg)
public void Read(Stream inStream, MesgDefinition defnMesg)
{
inStream.Position = 1;
EndianBinaryReader mesgReader = new EndianBinaryReader(inStream, defnMesg.IsBigEndian);
BinaryReader mesgReader = new EndianBinaryReader(inStream, defnMesg.IsBigEndian);

LocalNum = defnMesg.LocalMesgNum;

Expand Down
14 changes: 7 additions & 7 deletions Infrastructure/FitEdit.Adapters.Fit/MesgDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ internal IEnumerable<DeveloperFieldDefinition> DeveloperFieldDefinitions
#endregion

#region Constructors
internal MesgDefinition() : base((MesgDefinition)null)
{
LocalMesgNum = 0;
GlobalMesgNum = (ushort)MesgNum.Invalid;
architecture = Fit.LittleEndian;
}
// internal MesgDefinition() : base((MesgDefinition)null)
// {
// LocalMesgNum = 0;
// GlobalMesgNum = (ushort)MesgNum.Invalid;
// architecture = Fit.LittleEndian;
// }

internal MesgDefinition(
Stream source,
Expand Down Expand Up @@ -137,7 +137,7 @@ public MesgDefinition(MesgDefinition mesgDef) : base(mesgDef)
{
LocalMesgNum = mesgDef.LocalMesgNum;
GlobalMesgNum = mesgDef.GlobalMesgNum;
architecture = mesgDef.IsBigEndian ? Fit.BigEndian : Fit.LittleEndian;
architecture = mesgDef.architecture;
NumFields = mesgDef.NumFields;

foreach (FieldDefinition fieldDef in mesgDef.fieldDefs)
Expand Down
6 changes: 6 additions & 0 deletions Infrastructure/FitEdit.Data/Fit/Edits/EmptyEdit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace FitEdit.Data.Fit.Edits;

public class EmptyEdit : IEdit
{
public FitFile Apply() => new();
}
6 changes: 6 additions & 0 deletions Infrastructure/FitEdit.Data/Fit/Edits/IEdit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace FitEdit.Data.Fit.Edits;

public interface IEdit
{
public FitFile Apply();
}
33 changes: 33 additions & 0 deletions Infrastructure/FitEdit.Data/Fit/Edits/SplitLapEdit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Dynastream.Fit;

namespace FitEdit.Data.Fit.Edits;

public class SplitLapEdit(FitFile fit, RecordMesg rec) : IEdit
{
public FitFile Apply()
{
var lap = FindLapContaining(fit, rec);

var records1 = fit.GetRecords(lap.Start(), rec.InstantOfTime());
var records2 = fit.GetRecords(rec.InstantOfTime(), lap.End());

var start1 = records1.First().InstantOfTime();
var end1 = records1.Last().InstantOfTime();

var start2 = records2.First().InstantOfTime();
var end2 = records2.Last().InstantOfTime();

var lap1 = FitFileExtensions.ReconstructLap(records1, start1, end1);
var lap2 = FitFileExtensions.ReconstructLap(records2, start2, end2);

fit.Remove(lap);
fit.Add(lap1);
fit.Add(lap2);

fit.ForwardfillEvents();
return fit;
}

// Find the lap the RecordMesg belongs to
private static LapMesg FindLapContaining(FitFile fit, RecordMesg record) => fit.Get<LapMesg>().FirstOrDefault(lap => record.IsBetween(lap.Start(), lap.End()));
}
123 changes: 61 additions & 62 deletions Infrastructure/FitEdit.Data/Fit/FitFileExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public static FitFile BackfillEvents(this FitFile f, int resolution = 100, Actio
public static string OneLine(this FitFile f) => f.Sessions.Count switch
{
1 => $"From {f.Sessions[0].Start()} to {f.Sessions[0].End()}: {f.Sessions[0].GetTotalDistance()} m in {f.Sessions[0].GetTotalElapsedTime()}s ({f.Sessions[0].GetEnhancedAvgSpeed():0.##} m/s)",
_ when f.Sessions.Count > 1 => $"From {f.Sessions.First().Start()} to {f.Sessions.Last().End()}: {f.Sessions.TotalDistance()} m in {f.Sessions.TotalElapsedTime()}s",
> 1 => $"From {f.Sessions.First().Start()} to {f.Sessions.Last().End()}: {f.Sessions.TotalDistance()} m in {f.Sessions.TotalElapsedTime()}s",
_ => "No sessions",
};

Expand Down Expand Up @@ -223,8 +223,8 @@ private static FitFile Print(this FitFile f, Action<string> print, bool showReco

foreach (var rec in lapRecords)
{
var speed = new Speed { Unit = Unit.MetersPerSecond, Value = (double)rec.GetEnhancedSpeed() };
var distance = new Distance { Unit = Unit.Meter, Value = (double)rec.GetDistance() };
var speed = new Speed { Unit = Unit.MetersPerSecond, Value = rec.GetEnhancedSpeed() ?? 0 };
var distance = new Distance { Unit = Unit.Meter, Value = rec.GetDistance() ?? 0 };

print($" At {rec.InstantOfTime():HH:mm:ss}: {distance.Miles():0.##} mi, {speed.Convert(Unit.MinutesPerMile)}, {rec.GetHeartRate()} bpm, {(rec.GetCadence() + rec.GetFractionalCadence()) * 2} cad");
//print($" At {rec.Start():HH:mm:ss}: {rec.GetDistance():0.##} m, {rec.GetEnhancedSpeed():0.##} m/s, {rec.GetHeartRate()} bpm, {(rec.GetCadence() + rec.GetFractionalCadence()) * 2} cad");
Expand Down Expand Up @@ -371,6 +371,7 @@ public static FitFile ApplySpeeds(this FitFile fitFile, Dictionary<int, Speed> l
return merged;
}


public static (FitFile first, FitFile second) SplitAt(this FitFile source, DateTime at)
{
DateTime start = source.GetStartTime();
Expand All @@ -391,17 +392,21 @@ public static List<FitFile> SplitByLap(this FitFile? source)
.ToList();
}

private static FitFile? ExtractRecords(this FitFile? source, DateTime start, DateTime end)
{
List<RecordMesg> records = source
private static FitFile? ExtractRecords(this FitFile? source, DateTime start, DateTime end) =>
RepairAdditively(source, GetRecords(source, start, end));

public static List<LapMesg> GetLaps(this FitFile? source, DateTime start, DateTime end) => source
.Get<LapMesg>()
.InstantBetween(start, end)
.ToList()
.Sorted(MessageExtensions.SortByTimestamp);

public static List<RecordMesg> GetRecords(this FitFile? source, DateTime start, DateTime end) => source
.Get<RecordMesg>()
.InstantBetween(start, end)
.ToList()
.Sorted(MessageExtensions.SortByTimestamp);

return RepairAdditively(source, records);
}

private static List<T> FindAll<T>(this FitFile f) where T : Mesg => f.FindAll(typeof(T)).Cast<T>().ToList();

private static List<Mesg> FindAll(this FitFile f, Type t) => f.Events.OfType<MesgEventArgs>()
Expand All @@ -420,7 +425,7 @@ private static List<Mesg> FindAll(this FitFile f, Type t) => f.Events.OfType<Mes
/// Build a new FIT file from the given file by removing unnecessary messages.
///
/// <para/>
/// This method preserves more information than <see cref="RepairAdditively(FitFile?)(FitFile?)"/>,
/// This method preserves more information than <see cref="RepairAdditively(FitFile?)"/>,
/// such as individual sports in multisport events like triathlons, sessions, and laps.
/// Use that one when this one does not work.
/// </summary>
Expand Down Expand Up @@ -510,8 +515,8 @@ private static List<Mesg> FindAll(this FitFile f, Type t) => f.Events.OfType<Mes
var start = new Dynastream.Fit.DateTime(source.GetStartTime());
var end = new Dynastream.Fit.DateTime(source.GetEndTime());

var fileId = source.Get<FileIdMesg>().FirstOrDefault();
fileId!.SetTimeCreated(start);
var fileId = source.Get<FileIdMesg>().FirstOrDefault() ?? new FileIdMesg();
fileId.SetTimeCreated(start);

var fileCreator = source.Get<FileCreatorMesg>().FirstOrDefault();
var deviceInfo = source.Get<DeviceInfoMesg>().InstantBetween(start, end);
Expand All @@ -531,14 +536,14 @@ private static List<Mesg> FindAll(this FitFile f, Type t) => f.Events.OfType<Mes

List<RecordMesg> records2 = EnsureCumulativeDistance(records);
SessionMesg session = ReconstructSession(source, records2);
LapMesg lap = ReconstructLap(source, records2);
LapMesg lap = source.ReconstructLap(records2);
ActivityMesg activity = ReconstructActivity(source);

List<Mesg> mesgs = new();

if (fileId != null) { mesgs.Add(fileId); }
mesgs.Add(fileId);
if (fileCreator != null) { mesgs.Add(fileCreator); }
if (startEvent != null) { mesgs.Add(startEvent); }
mesgs.Add(startEvent);
if (deviceInfo != null) { mesgs.AddRange(deviceInfo); }
if (deviceSettings != null) { mesgs.Add(deviceSettings); }
if (userProfile != null) { mesgs.Add(userProfile); }
Expand Down Expand Up @@ -673,7 +678,6 @@ private static void AddDistance(this RecordMesg record, double distance)

var sport = source.Get<SportMesg>().FirstOrDefault();
var firstLap = source.Get<LapMesg>().FirstOrDefault();
var lastLap = source.Get<LapMesg>().LastOrDefault();
var session = source.Get<SessionMesg>().FirstOrDefault();
var activity = source.Get<ActivityMesg>().FirstOrDefault();

Expand All @@ -693,7 +697,7 @@ private static void AddDistance(this RecordMesg record, double distance)

if (firstLap is null)
{
firstLap = ReconstructLap(source, records);
firstLap = source.ReconstructLap(records);

if (sport != null)
{
Expand Down Expand Up @@ -735,26 +739,30 @@ private static void AddDistance(this RecordMesg record, double distance)
Dynastream.Fit.DateTime? lapEnd = lap.GetStartTime();
float? elapsed = lap.GetTotalElapsedTime();

if (lap != null && elapsed != null)
if (elapsed != null)
{
lapEnd.Add(new Dynastream.Fit.DateTime((uint)elapsed));
}

return lapEnd?.GetDateTime();
}

private static LapMesg ReconstructLap(FitFile source, List<RecordMesg> records)
public static LapMesg ReconstructLap(List<RecordMesg> records, DateTime start, DateTime end) =>
ReconstructLap(records, new Dynastream.Fit.DateTime(start), new Dynastream.Fit.DateTime(end));

private static LapMesg ReconstructLap(List<RecordMesg> records, Dynastream.Fit.DateTime start, Dynastream.Fit.DateTime end)
{
if (!records.Any())
{
return GetFakeLap(source);
}

var start = new Dynastream.Fit.DateTime(source.GetStartTime());
var end = new Dynastream.Fit.DateTime(source.GetEndTime());

float totalDistance = SumDistance(records);

double avgSpeed = records.Average(r => r.GetEnhancedSpeed() ?? 0);
double maxSpeed = records.Max(r => r.GetEnhancedSpeed() ?? 0);
double avgHr = records.Average(r => r.GetHeartRate() ?? 0);
double maxHr = records.Max(r => r.GetHeartRate() ?? 0);
double avgCadence = records.Average(r => r.GetCadence() ?? 0);
double maxCadence = records.Max(r => r.GetCadence() ?? 0);
double avgPower = records.Average(r => r.GetPower() ?? 0);
double maxPower = records.Max(r => r.GetPower() ?? 0);
uint totalCalories = (records.Last().GetCalories() ?? 0U) - (records.First().GetCalories() ?? 0U);
double totalDistance = (records.Last().GetDistance() ?? 0) - (records.First().GetDistance() ?? 0);

var lap = new LapMesg();
lap.SetStartTime(start);
lap.SetTimestamp(end);
Expand All @@ -766,11 +774,26 @@ private static LapMesg ReconstructLap(FitFile source, List<RecordMesg> records)
lap.SetEndPositionLong(records.Last().GetPositionLong());
lap.SetTotalElapsedTime(end.GetTimeStamp() - start.GetTimeStamp());
lap.SetTotalTimerTime(end.GetTimeStamp() - start.GetTimeStamp());
lap.SetTotalDistance(totalDistance);
lap.SetTotalDistance((float)totalDistance);
lap.SetTotalCalories((ushort)totalCalories);
lap.SetEnhancedAvgSpeed((float)avgSpeed);
lap.SetEnhancedMaxSpeed((float)maxSpeed);
lap.SetAvgHeartRate((byte)avgHr);
lap.SetMaxHeartRate((byte)maxHr);
lap.SetAvgCadence((byte)avgCadence);
lap.SetMaxCadence((byte)maxCadence);
lap.SetAvgPower((ushort)avgPower);
lap.SetMaxPower((ushort)maxPower);

lap.SetLapTrigger(LapTrigger.SessionEnd);

return lap;
}

private static LapMesg ReconstructLap(this FitFile source, List<RecordMesg> records) =>
records.Any()
? ReconstructLap(records, source.GetStartTime(), source.GetEndTime())
: GetFakeLap(source);

private static SessionMesg ReconstructSession(FitFile source, List<RecordMesg> records)
{
Expand All @@ -782,8 +805,6 @@ private static SessionMesg ReconstructSession(FitFile source, List<RecordMesg> r
var start = new Dynastream.Fit.DateTime(source.GetStartTime());
var end = new Dynastream.Fit.DateTime(source.GetEndTime());

float totalDistance = SumDistance(records);

var session = new SessionMesg();
session.SetStartTime(start);
session.SetTimestamp(end);
Expand All @@ -795,7 +816,7 @@ private static SessionMesg ReconstructSession(FitFile source, List<RecordMesg> r
session.SetEndPositionLong(records.Last().GetPositionLong());
session.SetTotalElapsedTime(end.GetTimeStamp() - start.GetTimeStamp());
session.SetTotalTimerTime(end.GetTimeStamp() - start.GetTimeStamp());
session.SetTotalDistance(totalDistance);
session.SetTotalDistance(records.Last().GetDistance() - records.First().GetDistance());
session.SetTrigger(SessionTrigger.ActivityEnd);
return session;
}
Expand Down Expand Up @@ -844,20 +865,15 @@ private static SessionMesg GetFakeSession(FitFile source)
/// <summary>
/// Sum the distance between all records
/// </summary>
private static float SumDistance(List<RecordMesg> records)
private static double SumDistance(List<RecordMesg> records)
{
double? sum = 0;

var penultimate = Math.Max(0, records.Count - 1);

foreach (int i in Enumerable.Range(1, penultimate))
{
sum += records[i].GetDistance() - (double?)records[i - 1].GetDistance();
}

return (float)(sum ?? 0);
return Enumerable.Range(1, penultimate)
.Aggregate<int, double>(0, (current, i) =>
current + ((records[i].GetDistance() ?? 0) - (records[i - 1].GetDistance() ?? 0)));
}

/// <summary>
/// Reconstruct missing Session messages from Lap messages.
///
Expand Down Expand Up @@ -988,7 +1004,7 @@ public static void RemoveAll<T>(this FitFile fit) where T : Mesg
/// <summary>
/// Remove all messages and message definitions for the given message type.
/// </summary>
public static void RemoveAll(this FitFile fit, Type t)
public static void RemoveAllOfType(this FitFile fit, Type t)
{
var matches = fit.FindAll(t);
foreach (var match in matches)
Expand Down Expand Up @@ -1026,21 +1042,4 @@ public static void RemoveAll(this FitFile fit, Type t, DateTime after = default,
private static bool HasMessageOfType(this EventArgs e, Type t) =>
e is MesgEventArgs mea && mea.mesg.Num == MessageFactory.MesgNums[t]
|| e is MesgDefinitionEventArgs mdea && mdea.mesgDef.GlobalMesgNum == MessageFactory.MesgNums[t];

/// <summary>
/// Return true if the given message occurs between the given DateTimes.
/// If either DateTime is not specified, it is not considered.
/// </summary>
private static bool IsBetween(this Mesg mesg, DateTime after = default, DateTime before = default) => mesg switch
{
_ when mesg is IDurationOfTime dur
&& (after == default || dur.GetStartTime().GetDateTime() > after)
&& (before == default || dur.GetTimestamp().GetDateTime() <= before) => true,

_ when mesg is IInstantOfTime inst
&& (after == default || inst.GetTimestamp().GetDateTime() > after)
&& (before == default || inst.GetTimestamp().GetDateTime() <= before) => true,

_ => false,
};
}
}
Loading
Loading