l2736 2 週間 前
コミット
64c4d4accd

+ 1 - 0
Avalonia/ShakerApp/ShakerApp.csproj

@@ -51,6 +51,7 @@
     <ProjectReference Include="..\..\Language\ILanguage\ILanguage.csproj" />
     <ProjectReference Include="..\..\OxyPlot\OxyPlot.Avalonia\OxyPlot.Avalonia.csproj" />
     <ProjectReference Include="..\..\Shaker.Model\Shaker.Models.csproj" />
+    <ProjectReference Include="..\..\Tdms\FSharp.Data.Tdms.fsproj" />
     <ProjectReference Include="..\Avalonia.Xaml.Behaviors\Avalonia.Xaml.Behaviors.csproj" />
     <ProjectReference Include="..\SukiUI\SukiUI.csproj" />
   </ItemGroup>

+ 2 - 2
Avalonia/ShakerApp/ViewModels/MainPage/IMainPageViewModel.cs

@@ -16,7 +16,7 @@ namespace ShakerApp.ViewModels
         public void Start();
         public void Stop();
         public void UpdateData(List<IResultDataModel> model);
-        public void SaveTdmsConfig(NationalInstruments.Tdms.File file);
-        public void SaveTestData(NationalInstruments.Tdms.File file);
+        public void SaveTdmsConfig(FSharp.Data.Tdms.File file);
+        public void SaveTestData(FSharp.Data.Tdms.File file);
     }
 }

+ 2 - 2
Avalonia/ShakerApp/ViewModels/MainPage/OutSignalMainPageViewModel.cs

@@ -16,11 +16,11 @@ namespace ShakerApp.ViewModels
 {
     internal class OutSignalMainPageViewModel:ViewModelBase<IModel>,IMainPageViewModel
     {
-        public void SaveTdmsConfig(NationalInstruments.Tdms.File file)
+        public void SaveTdmsConfig(FSharp.Data.Tdms.File file)
         {
 
         }
-        public void SaveTestData(NationalInstruments.Tdms.File file)
+        public void SaveTestData(FSharp.Data.Tdms.File file)
         {
 
         }

+ 9 - 3
Avalonia/ShakerApp/ViewModels/MainPage/RandomMainPageViewModel.cs

@@ -17,11 +17,11 @@ namespace ShakerApp.ViewModels
 {
     internal class RandomMainPageViewModel:ViewModelBase<RandomDataModel>,IMainPageViewModel
     {
-        public void SaveTdmsConfig(NationalInstruments.Tdms.File file)
+        public void SaveTdmsConfig(FSharp.Data.Tdms.File file)
         {
 
         }
-        public void SaveTestData(NationalInstruments.Tdms.File file)
+        public void SaveTestData(FSharp.Data.Tdms.File file)
         {
 
         }
@@ -129,7 +129,13 @@ namespace ShakerApp.ViewModels
                 RandomTransferFunctionViewModel.Instance.Title = "TransferFunction";
                 BaseDialogWindow window = new BaseDialogWindow();
                 window.DataContext = RandomTransferFunctionViewModel.Instance;
-                RandomTransferFunctionViewModel.Instance.CloseWindowAction = () => window?.Close();
+                bool isclose = false;
+                RandomTransferFunctionViewModel.Instance.CloseWindowAction = () =>
+                {
+                    if (isclose) return;
+                    isclose = true;
+                    window?.Close();
+                };
                 await window.ShowDialog(desktop.MainWindow!);
             }
         }

+ 43 - 4
Avalonia/ShakerApp/ViewModels/MainPage/SineMainPageViewModel.cs

@@ -7,6 +7,7 @@ using Shaker.Models.Tools;
 using ShakerApp.Tools;
 using ShakerApp.Views;
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
 using System.Net.Http.Headers;
@@ -18,6 +19,8 @@ namespace ShakerApp.ViewModels
 {
     internal class SineMainPageViewModel : ViewModelBase<SineDataModel>, IMainPageViewModel
     {
+        public const string TDMSConfigName = "SweepConfig";
+        public const string TDMSDataName = "SweepData";
         List<OxyColor> oxyColors = new List<OxyColor>() {OxyColors.Black, OxyColors.Green, OxyColors.Red, OxyColors.Red, OxyColors.Yellow, OxyColors.Yellow };
         List<LineStyle> lineStyles = new List<LineStyle>() {LineStyle.Solid, LineStyle.Solid, LineStyle.Solid, LineStyle.Solid, LineStyle.LongDashDotDot, LineStyle.LongDashDotDot };
         List<string> properties = new List<string>() {nameof(SweepData.Acceleration), nameof(SweepData.TargetAcceleration), nameof(SweepData.UpStopAcceleration), nameof(SweepData.DownStopAcceleration), nameof(SweepData.UpWarnAcceleration), nameof(SweepData.DownWarnAcceleration) };
@@ -101,13 +104,49 @@ namespace ShakerApp.ViewModels
         {
 
         }
-        public void SaveTdmsConfig(NationalInstruments.Tdms.File file)
+        public void SaveTdmsConfig(FSharp.Data.Tdms.File file)
         {
-
+            if (!file.Groups.Any(x => x.Name == TDMSConfigName))
+            {
+                var g = new FSharp.Data.Tdms.Group(TDMSConfigName, new List<FSharp.Data.Tdms.Property>(), new List<FSharp.Data.Tdms.Channel>());
+                file.Groups.Append(g);
+            }
+            var group = file.Groups.First(x => x.Name == TDMSConfigName);
+            typeof(SweepConfigModel) .GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
+                .Select(x => (x, x.GetValue(SweepConfigViewModel.Instance.Model)))
+                .ToList()
+                .ForEach(x =>
+                {
+                    if (x.Item2 == null)
+                    {
+                        return;
+                    }
+                    if (x.x.FieldType.IsAssignableTo(typeof(IList)) || x.x.FieldType.IsArray)
+                    {
+                        string value = string.Join("", x.Item2.GetBytes().Select(x => $"{x:X2}"));
+                        if (!group.Properties.Any(y => y.Name == x.x.Name))
+                        {
+                            group.Properties.Append(new FSharp.Data.Tdms.Property(x.x.Name, typeof(string), value));
+                        }
+                    }
+                    else if (x.x.FieldType.IsEnum)
+                    {
+                        if (!group.Properties.Any(y => y.Name == x.x.Name))
+                        {
+                            group.Properties.Append(new FSharp.Data.Tdms.Property(x.x.Name, typeof(int), (int)x.Item2));
+                        }
+                    }
+                    else
+                    {
+                        if (!group.Properties.Any(y => y.Name == x.x.Name))
+                        {
+                            group.Properties.Append(new FSharp.Data.Tdms.Property(x.x.Name, x.x.FieldType, x.Item2));
+                        }
+                    }
+                });
         }
-        public void SaveTestData(NationalInstruments.Tdms.File file)
+        public void SaveTestData(FSharp.Data.Tdms.File file)
         {
-
         }
         public void SetRefSpectrum(List<SweepData> data)
         {

+ 2 - 2
Avalonia/ShakerApp/ViewModels/MainPage/StartPageViewModel.cs

@@ -12,11 +12,11 @@ namespace ShakerApp.ViewModels
     {
         public MainPageType PageType => MainPageType.StartPage;
 
-        public void SaveTdmsConfig(NationalInstruments.Tdms.File file)
+        public void SaveTdmsConfig(FSharp.Data.Tdms.File file)
         {
 
         }
-        public void SaveTestData(NationalInstruments.Tdms.File file)
+        public void SaveTestData(FSharp.Data.Tdms.File file)
         {
 
         }

+ 3 - 2
Avalonia/ShakerApp/ViewModels/ShakerConfig/RandomConfigViewModel.cs

@@ -169,9 +169,9 @@ namespace ShakerApp.ViewModels
                     Refresh();
                     RandomMainPageViewModel.Instance.SetRefSpectrum(datas);
                     CommunicationViewModel.Instance.ServiceCommunication?.GetEvent<RandomConfigModel>()?.Publish(this, Model);
-                });
-                
+                });                
             });
+            Identify.PropertyChanged += (_, _) => SaveIsEnabled = true;
         }
         static RandomConfigViewModel()
         {
@@ -282,6 +282,7 @@ namespace ShakerApp.ViewModels
             {
                 PlanItems.Add(new IndexValueItemViewModel<RandomPlanItemViewModel>(i + 1, new RandomPlanItemViewModel(model.PlanItems[i])));
             }
+            Identify.UpDateModel(model.Identify);
             base.UpDateModel(model);
 
         }

+ 22 - 8
Avalonia/ShakerApp/ViewModels/ShakerConfig/ShakerConfigViewModel.cs

@@ -127,14 +127,14 @@ namespace ShakerApp.ViewModels
         {
 
         }
-        public void SaveTdmsConfig(NationalInstruments.Tdms.File file)
+        public void SaveTdmsConfig(FSharp.Data.Tdms.File file)
         {
-            if(!file.Groups.ContainsKey(TDMSConfigName))
+            if(!file.Groups.Any(x=>x.Name ==TDMSConfigName))
             {
-                var g = new NationalInstruments.Tdms.Group(TDMSConfigName, new Dictionary<string, object>());
-                file.Groups.Add(TDMSConfigName, g);
+                var g = new FSharp.Data.Tdms.Group(TDMSConfigName, new List<FSharp.Data.Tdms.Property>(),new List<FSharp.Data.Tdms.Channel>());
+                file.Groups.Append(g);
             }
-            var group = file.Groups[TDMSConfigName];
+            var group = file.Groups.First(x=>x.Name == TDMSConfigName);
             typeof(ShakerConfigModel).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
                 .Select(x =>(x, x.GetValue(Model)))
                 .ToList()
@@ -144,13 +144,27 @@ namespace ShakerApp.ViewModels
                     {
                         return;
                     }
-                    if(x.x.FieldType.IsAssignableTo(typeof(IList)))
+                    if(x.x.FieldType.IsAssignableTo(typeof(IList)) || x.x.FieldType.IsArray)
                     {
-                        group.Properties[x.x.Name] = x.Item2.GetBytes();
+                        string value = string.Join("", x.Item2.GetBytes().Select(x => $"{x:X2}"));
+                        if (!group.Properties.Any(y=>y.Name == x.x.Name))
+                        {
+                            group.Properties.Append(new FSharp.Data.Tdms.Property(x.x.Name, typeof(string), value));
+                        }
+                    }
+                    else if(x.x.FieldType.IsEnum)
+                    {
+                        if (!group.Properties.Any(y => y.Name == x.x.Name))
+                        {
+                            group.Properties.Append(new FSharp.Data.Tdms.Property(x.x.Name, typeof(int), (int)x.Item2));
+                        }
                     }
                     else
                     {
-                        group.Properties[x.x.Name] = x.Item2;
+                        if (!group.Properties.Any(y => y.Name == x.x.Name))
+                        {
+                            group.Properties.Append(new FSharp.Data.Tdms.Property(x.x.Name, x.x.FieldType, x.Item2));
+                        }
                     }
                 });
         }

+ 44 - 0
Avalonia/ShakerApp/ViewModels/ShakerControl/ShakerControlViewModel.cs

@@ -1,5 +1,7 @@
 using Shaker.Models;
+using ShakerApp.Tools;
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Linq;
@@ -11,6 +13,7 @@ namespace ShakerApp.ViewModels
 {
     public class ShakerControlViewModel : ViewModelBase<ShakerControlModel>
     {
+        public const string TDMSConfigName = "ShakerControl";
         public override bool CanResize => false;
         public override double Width => 960;
         public override double Height => 560;
@@ -18,6 +21,47 @@ namespace ShakerApp.ViewModels
         static ShakerControlViewModel()
         {
 
+        }
+        public void SaveTdmsConfig(FSharp.Data.Tdms.File file)
+        {
+            if (!file.Groups.Any(x => x.Name == TDMSConfigName))
+            {
+                var g = new FSharp.Data.Tdms.Group(TDMSConfigName, new List<FSharp.Data.Tdms.Property>(), new List<FSharp.Data.Tdms.Channel>());
+                file.Groups.Append(g);
+            }
+            var group = file.Groups.First(x => x.Name == TDMSConfigName);
+            Model.GetType().GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
+                .Select(x => (x, x.GetValue(Model)))
+                .ToList()
+                .ForEach(x =>
+                {
+                    if (x.Item2 == null)
+                    {
+                        return;
+                    }
+                    if (x.x.FieldType.IsAssignableTo(typeof(IList)) || x.x.FieldType.IsArray)
+                    {
+                        string value = string.Join("", x.Item2.GetBytes().Select(x => $"{x:X2}"));
+                        if (!group.Properties.Any(y => y.Name == x.x.Name))
+                        {
+                            group.Properties.Append(new FSharp.Data.Tdms.Property(x.x.Name, typeof(string), value));
+                        }
+                    }
+                    else if (x.x.FieldType.IsEnum)
+                    {
+                        if (!group.Properties.Any(y => y.Name == x.x.Name))
+                        {
+                            group.Properties.Append(new FSharp.Data.Tdms.Property(x.x.Name, typeof(int), (int)x.Item2));
+                        }
+                    }
+                    else
+                    {
+                        if (!group.Properties.Any(y => y.Name == x.x.Name))
+                        {
+                            group.Properties.Append(new FSharp.Data.Tdms.Property(x.x.Name, x.x.FieldType, x.Item2));
+                        }
+                    }
+                });
         }
         private ShakerControlViewModel()
         {

+ 44 - 16
Avalonia/ShakerApp/ViewModels/ShakerDataViewModel.cs

@@ -13,8 +13,10 @@ namespace ShakerApp.ViewModels
 {
     internal class ShakerDataViewModel:ViewModelBase<IModel>
     {
+        //[AllowNull]
+        //private NationalInstruments.Tdms.File file;
         [AllowNull]
-        private NationalInstruments.Tdms.File file;
+        private FSharp.Data.Tdms.File tdmsfile;
         private object _datalocker = new object();
         private Dictionary<Shaker.Models.AnalogType, List<(List<DataPoint>,Models.StatisticsModel)>> AnalogDataCache = new Dictionary<AnalogType, List<(List<DataPoint>, Models.StatisticsModel)>>();
         private ShakerDataViewModel()
@@ -38,27 +40,27 @@ namespace ShakerApp.ViewModels
             {
                 case RTStatus.SignalGen:
                     {
-                        if (file == null)
-                        {
-                            file = new NationalInstruments.Tdms.File(System.IO.Path.Combine(ViewModels.ShakerSettingViewModel.Instance.DataDirectory, $"{DateTime.Now:yyyy-MM-dd HH-mm-ss}.sdat"), System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite).Open();
-                            ShakerConfigViewModel.Instance.SaveTdmsConfig(file);
-                            MainPageViewModel.Instance.MainPage.SaveTdmsConfig(file);
-                        }
+                        //if (tdmsfile == null)
+                        //{
+                        //    tdmsfile = new FSharp.Data.Tdms.File(System.IO.Path.Combine(ViewModels.ShakerSettingViewModel.Instance.DataDirectory, $"{DateTime.Now:yyyy-MM-dd HH-mm-ss}.tdms"), new List<FSharp.Data.Tdms.Property>(), new List<FSharp.Data.Tdms.Group>());
+                        //    ShakerConfigViewModel.Instance.SaveTdmsConfig(tdmsfile);
+                        //    ShakerControlViewModel.Instance.SaveTdmsConfig(tdmsfile);
+                        //    MainPageViewModel.Instance.MainPage.SaveTdmsConfig(tdmsfile);
+                        //}
                     }
                     break;
                 default:
-                    if (file != null)
-                    {
-                        file?.Dispose();
-                        file = null;
-                    }
+                    //if (tdmsfile != null)
+                    //{
+                    //    tdmsfile = null;
+                    //}
                     break;
             }
             CalcAnalog(data, row, col);
-            if(ShakerStatusViewModel.Instance.RTStatus == RTStatus.SignalGen && file!=null)
-            {
-                MainPageViewModel.Instance.MainPage.SaveTestData(file);
-            }
+            //if(ShakerStatusViewModel.Instance.RTStatus == RTStatus.SignalGen && tdmsfile!=null)
+            //{
+            //    MainPageViewModel.Instance.MainPage.SaveTestData(tdmsfile);
+            //}
             CommunicationViewModel.Instance.ServiceCommunication?.GetEvent(Topic.DATA)?.Publish(this, null);
         }
 
@@ -78,6 +80,32 @@ namespace ShakerApp.ViewModels
                 return new List<(List<DataPoint>, Models.StatisticsModel)>();
             }
         }
+        public List<double[]> GetAnalogRawData(AnalogType analogType)
+        {
+            lock (_datalocker)
+            {
+                if (AnalogDataCache.TryGetValue(analogType, out var list))
+                {
+                    if (list == null) return new List<double[]>();
+                    return list.Select(x=>x.Item1.Select(x=>x.Y).ToArray()).ToList();
+                }
+                return new List<double[]>();
+            }
+        }
+        public double[] GetAnalogRawData(int index)
+        {
+            lock (_datalocker)
+            {
+                if (index >= ShakerConfigViewModel.Instance.Model.AnalogSignalConfigs.Count) return new double[0];
+                var type = ShakerConfigViewModel.Instance.Model.AnalogSignalConfigs[index].AnalogType;
+                int seindex = 0;
+                if (index > 0)
+                {
+                    seindex = ShakerConfigViewModel.Instance.Model.AnalogSignalConfigs.Take(index).Count(x => x.AnalogType == type);
+                }
+                return AnalogDataCache[type][seindex].Item1.Select(x => x.Y).ToArray();
+            }
+        }
         private void CalcAnalog(byte[] data,uint row,uint col)
         {
             if (data.Length == 0 || row == 0 || col == 0) return;

ファイルの差分が大きいため隠しています
+ 7413 - 8493
Shaker/Shaker.lvbitx


+ 1 - 0
Shaker/ShakerService.Control.cs

@@ -27,6 +27,7 @@ namespace ShakerService
                 .Cast<MethodInfo>()
                 .ToList()
                 .ForEach(x => x.Invoke(this, null));
+            var data = ViewModel.ServiceRandomConfigViewModel.Instance.RandomData.CalcIdentifyDriver(RandomTestStep.Identify, 1);
             EventBus.EventBroker.Instance.GetEvent(nameof(ServiceShakerStatusViewModel.IsSignalGenStoped)).Subscrip((sender, args) =>
             {
                 if(args.Data.Length>0 && args.Data[0] is bool v && v)

+ 0 - 1
Shaker/ShakerService.ReadFifo.cs

@@ -93,7 +93,6 @@ namespace ShakerService
                                         if (ServiceShakerConfigViewModel.Instance.AnalogSignals[i].AnalogType == AnalogType.DivideAcceleration)
                                         {
                                             var tempdata = ServiceDataCacheViewModel.Instance.GetAnalogData(i).Select(x => (double)x).ToArray();
-                                            Log.Default.Debug($"{index}:Max{tempdata.Max()}  Min{tempdata.Min()}");
                                             tempdata = ServiceRandomConfigViewModel.Instance.RandomData.CalcPSD(tempdata);
                                             tempdata = ServiceRandomConfigViewModel.Instance.RandomData.AddAccelerationPSD(index, tempdata, false);
                                             index++;

+ 4 - 4
Shaker/ViewModel/ServiceRandomConfigViewModel.cs

@@ -120,11 +120,11 @@ namespace ShakerService.ViewModel
             FFTHalfFrameLength = FFTFrameLength >> 1;
             PSDWindow = Enumerable.Range(0, (int)FFTHalfFrameLength).Select(x => (x * FrequencyResolution >= MinFrequency && x * FrequencyResolution <= MaxFrequency) ? 1d : 0d).ToArray();
             FixedWindow = Enumerable.Repeat(1d, (int)FFTFrameLength).ToArray();
-            StartWindow = new double[RandomSampleRate];
-            StopWindow = new double[RandomSampleRate];
-            for(int i=0;i<RandomSampleRate;i++)
+            StartWindow = new double[FFTHalfFrameLength];
+            StopWindow = new double[FFTHalfFrameLength];
+            for(int i=0;i< FFTHalfFrameLength; i++)
             {
-                double d = i * double.Pi / (RandomSampleRate << 1);
+                double d = i * double.Pi / (FFTHalfFrameLength << 1);
                 StartWindow[i] = Math.Pow(Math.Sin(d),3);
                 StopWindow[i] = Math.Pow(Math.Cos(d), 3);
             }

+ 25 - 17
Shaker/ViewModel/ServiceRandomDataViewModel.cs

@@ -116,37 +116,45 @@ namespace ShakerService.ViewModel
             Random rdm = new Random();
             int n_down = 0;
             int n_up = 0;
-            var freqs = Enumerable.Range(0, (int)ServiceRandomConfigViewModel.Instance.FFTFrameLength).Select(x => x * ServiceRandomConfigViewModel.Instance.FrequencyResolution).ToArray();
-            n_down = Array.FindLastIndex(freqs, x => x < ServiceRandomConfigViewModel.Instance.SpectralTables[0].TurningFrequency);
-            n_up = Array.FindLastIndex(freqs, x => x < ServiceRandomConfigViewModel.Instance.SpectralTables[^1].TurningFrequency) + 1;
+            double freqres = ServiceRandomConfigViewModel.Instance.RandomSampleRate / (double)ServiceRandomConfigViewModel.Instance.FFTFrameLength;
+            n_down = (int)(ViewModel.ServiceRandomConfigViewModel.Instance.SpectralTables[0].TurningFrequency / freqres);
+            n_up = (int)(ViewModel.ServiceRandomConfigViewModel.Instance.SpectralTables[^1].TurningFrequency / freqres);
             double[] tempvalue = Enumerable.Repeat(8e-14, (int)ServiceRandomConfigViewModel.Instance.FFTHalfFrameLength).ToArray();
-            for (int i = 0; i < n_up - n_down; i++)
+            for(int i= n_down; i<= n_up+1; i++)
             {
-                int index = i + n_down;
-                double currentfreq = freqs[index];
-                int currenfreqindex = ServiceRandomConfigViewModel.Instance.SpectralTables.FindIndex(x => currentfreq < x.TurningFrequency);
-                if (currenfreqindex > 0)
+                double freq = i * freqres;
+                int freqindex = Enumerable.Range(0, ServiceRandomConfigViewModel.Instance.SpectralTables.Count - 1).ToList().FindIndex(x => freq >= ServiceRandomConfigViewModel.Instance.SpectralTables[x].TurningFrequency && freq <= ServiceRandomConfigViewModel.Instance.SpectralTables[x + 1].TurningFrequency);
+                if(freqindex>=0 && freqindex < ServiceRandomConfigViewModel.Instance.SpectralTables.Count-1)
                 {
-                    double v1 = Math.Log10(ServiceRandomConfigViewModel.Instance.SpectralTables[currenfreqindex].SqrtValue);
-                    double v2 = Math.Log10(ServiceRandomConfigViewModel.Instance.SpectralTables[currenfreqindex - 1].SqrtValue);
+                    double f1 = Math.Log10(ViewModel.ServiceRandomConfigViewModel.Instance.SpectralTables[freqindex].TurningFrequency);
+                    double f2 = Math.Log10(ViewModel.ServiceRandomConfigViewModel.Instance.SpectralTables[freqindex+1].TurningFrequency);
 
-                    double f1 = Math.Log10(ServiceRandomConfigViewModel.Instance.SpectralTables[currenfreqindex].TurningFrequency);
-                    double f2 = Math.Log10(ServiceRandomConfigViewModel.Instance.SpectralTables[currenfreqindex - 1].TurningFrequency);
 
-                    tempvalue[currenfreqindex] = Math.Pow(10, (v1 - v2) / (f1 - f2) * (v2 - Math.Log10(currentfreq)) + v2);
+                    double v1 = Math.Log10(ViewModel.ServiceRandomConfigViewModel.Instance.SpectralTables[freqindex].TurningValue);
+                    double v2 = Math.Log10(ViewModel.ServiceRandomConfigViewModel.Instance.SpectralTables[freqindex + 1].TurningValue);
+
+                    tempvalue[i] = Math.Pow(10, (v2 - v1) / (f2 - f1) * (Math.Log10(freq) - f1) + v1);
+                }
+                else if(freqindex == ServiceRandomConfigViewModel.Instance.SpectralTables.Count-1)
+                {
+                    tempvalue[i] = ServiceRandomConfigViewModel.Instance.SpectralTables[freqindex].TurningValue;
                 }
             }
-            tempvalue[n_up] = ServiceRandomConfigViewModel.Instance.SpectralTables[^1].SqrtValue;
-            Complex[] random = Enumerable.Range(0, (int)ServiceRandomConfigViewModel.Instance.FFTHalfFrameLength).Select(x => Complex.Exp(new Complex(0, rdm.NextDouble() * 4 * Math.PI)) * tempvalue[x]).ToArray();
+            Complex[] random = Enumerable.Range(0, (int)ServiceRandomConfigViewModel.Instance.FFTHalfFrameLength).Select(x => Complex.Exp(new Complex(0,(0.5- rdm.NextDouble()) * 4 * Math.PI)) * tempvalue[x]).ToArray();
             Complex[] value = new Complex[ServiceRandomConfigViewModel.Instance.FFTFrameLength];
             value[0] = new Complex(tempvalue[0], 0);
+            var tempva = random.Select(x => x.Real).ToArray();
             Unsafe.CopyBlock(ref Unsafe.As<Complex, byte>(ref value[1]), ref Unsafe.As<Complex, byte>(ref random[0]), (uint)(random.Length * Unsafe.SizeOf<Complex>()));
             for (int i = 1; i < random.Length; i++)
             {
                 value[^i] = new Complex(random[i].Real, -random[i].Imaginary);
             }
-            MathNet.Numerics.IntegralTransforms.Fourier.Inverse(value, options: MathNet.Numerics.IntegralTransforms.FourierOptions.Matlab);
-            IdentifyFirstDriver = value.Select(x => x.Real).ToArray();
+            double[] real = value.Select(x => x.Real).ToArray();
+            double[] img = value.Select(x => x.Imaginary).ToArray();
+            ViewModel.ServiceDataCacheViewModel.Instance.Calc.FFT.IFFT(real, img);
+            ViewModel.ServiceDataCacheViewModel.Instance.Calc.Division.Division(ref real[0], real.Length, (uint)real.Length);
+            ViewModel.ServiceDataCacheViewModel.Instance.Calc.Division.Division(ref img[0], img.Length, (uint)img.Length);
+            IdentifyFirstDriver = real;
             HastIdentifyFirstDriver = true;
             return IdentifyFirstDriver;
         }

+ 6 - 0
ShakerControl.sln

@@ -116,6 +116,8 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "NetMQ", "Communication\NetM
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeLibraryLoader", "NativeLibraryLoader\NativeLibraryLoader.csproj", "{1C9C9ED0-2215-43CC-8557-99CDFDD31219}"
 EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Data.Tdms", "Tdms\FSharp.Data.Tdms.fsproj", "{799900FA-5DB9-41ED-A4D1-74A984635718}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -242,6 +244,10 @@ Global
 		{1C9C9ED0-2215-43CC-8557-99CDFDD31219}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{1C9C9ED0-2215-43CC-8557-99CDFDD31219}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{1C9C9ED0-2215-43CC-8557-99CDFDD31219}.Release|Any CPU.Build.0 = Release|Any CPU
+		{799900FA-5DB9-41ED-A4D1-74A984635718}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{799900FA-5DB9-41ED-A4D1-74A984635718}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{799900FA-5DB9-41ED-A4D1-74A984635718}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{799900FA-5DB9-41ED-A4D1-74A984635718}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 166 - 0
Tdms/Buffer.fs

@@ -0,0 +1,166 @@
+namespace FSharp.Data.Tdms
+
+open System
+open System.Buffers.Binary
+open System.Numerics
+#if IS_DESIGNTIME
+open System.Runtime.InteropServices
+#endif
+open System.Text
+
+module Buffer =
+
+    let readInt16 (buffer: byte ReadOnlySpan byref) bigEndian =
+        let value =
+            if bigEndian then
+                BinaryPrimitives.ReadInt16BigEndian buffer
+            else
+                BinaryPrimitives.ReadInt16LittleEndian buffer
+
+        buffer <- buffer.Slice 2
+        value
+
+    let readInt (buffer: byte ReadOnlySpan byref) bigEndian =
+        let value =
+            if bigEndian then
+                BinaryPrimitives.ReadInt32BigEndian buffer
+            else
+                BinaryPrimitives.ReadInt32LittleEndian buffer
+
+        buffer <- buffer.Slice 4
+        value
+
+    let readInt64 (buffer: byte ReadOnlySpan byref) bigEndian =
+        let value =
+            if bigEndian then
+                BinaryPrimitives.ReadInt64BigEndian buffer
+            else
+                BinaryPrimitives.ReadInt64LittleEndian buffer
+
+        buffer <- buffer.Slice 8
+        value
+
+    let readUInt16 (buffer: byte ReadOnlySpan byref) bigEndian =
+        let value =
+            if bigEndian then
+                BinaryPrimitives.ReadUInt16BigEndian buffer
+            else
+                BinaryPrimitives.ReadUInt16LittleEndian buffer
+
+        buffer <- buffer.Slice 2
+        value
+
+    let readUInt (buffer: byte ReadOnlySpan byref) bigEndian =
+        let value =
+            if bigEndian then
+                BinaryPrimitives.ReadUInt32BigEndian buffer
+            else
+                BinaryPrimitives.ReadUInt32LittleEndian buffer
+
+        buffer <- buffer.Slice 4
+        value
+
+    let readUInt64 (buffer: byte ReadOnlySpan byref) bigEndian =
+        let value =
+            if bigEndian then
+                BinaryPrimitives.ReadUInt64BigEndian buffer
+            else
+                BinaryPrimitives.ReadUInt64LittleEndian buffer
+
+        buffer <- buffer.Slice 8
+        value
+
+    let readFloat32 (buffer: byte ReadOnlySpan byref) bigEndian =
+        let value =
+            if bigEndian then
+                #if IS_DESIGNTIME
+                if BitConverter.IsLittleEndian
+                then
+                   BitConverter.ToSingle(BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read<int> buffer)), 0)
+                else
+                    MemoryMarshal.Read<float32> buffer
+                #else
+                BinaryPrimitives.ReadSingleBigEndian buffer
+                #endif
+            else
+                #if IS_DESIGNTIME
+                if not BitConverter.IsLittleEndian
+                then
+                   BitConverter.ToSingle(BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read<int> buffer)), 0)
+                else
+                    MemoryMarshal.Read<float32> buffer
+                #else
+                BinaryPrimitives.ReadSingleLittleEndian buffer
+                #endif
+
+        buffer <- buffer.Slice 4
+        value
+
+    let readFloat (buffer: byte ReadOnlySpan byref) bigEndian =
+        let value =
+            if bigEndian then
+                #if IS_DESIGNTIME
+                if BitConverter.IsLittleEndian
+                then
+                   BitConverter.ToDouble(BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read<int64> buffer)), 0)
+                else
+                    MemoryMarshal.Read<float> buffer
+                #else
+                BinaryPrimitives.ReadDoubleBigEndian buffer
+                #endif
+            else
+                #if IS_DESIGNTIME
+                if not BitConverter.IsLittleEndian
+                then
+                   BitConverter.ToDouble(BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read<int64> buffer)), 0)
+                else
+                    MemoryMarshal.Read<float> buffer
+                #else
+                BinaryPrimitives.ReadDoubleLittleEndian buffer
+                #endif
+
+        buffer <- buffer.Slice 8
+        value
+
+    let readFloat80 (buffer: byte ReadOnlySpan byref) bigEndian =
+        if bigEndian then
+            { RawSignificand = readUInt64 &buffer bigEndian
+              RawSignExponent = readUInt16 &buffer bigEndian }
+        else
+            { RawSignExponent = readUInt16 &buffer bigEndian
+              RawSignificand = readUInt64 &buffer bigEndian }
+
+    let readType (buffer: byte ReadOnlySpan byref) bigEndian =
+        readUInt &buffer bigEndian
+        |> function
+        | 0u -> typeof<unit>
+        | 0x21u -> typeof<bool>
+        | 1u -> typeof<int8>
+        | 2u -> typeof<int16>
+        | 3u -> typeof<int>
+        | 4u -> typeof<int64>
+        | 5u -> typeof<uint8>
+        | 6u -> typeof<uint16>
+        | 7u -> typeof<uint>
+        | 8u -> typeof<uint64>
+        | 9u
+        | 0x19u -> typeof<float32>
+        | 10u
+        | 0x1Au -> typeof<float>
+        | 0x08000Cu -> typeof<struct (float32 * float32)>
+        | 0x10000Du -> typeof<Complex>
+        | 11u
+        | 0x1Bu -> typeof<float80>
+        | 0x20u -> typeof<string>
+        | 0x44u -> typeof<Timestamp>
+        | value -> failwithf "Unknown type: %i" value
+
+    let readString (buffer: byte ReadOnlySpan byref) bigEndian =
+        let length = readUInt &buffer bigEndian |> int
+        let bytes = buffer.Slice(0, length)
+        buffer <- buffer.Slice length
+        #if IS_DESIGNTIME
+        Encoding.UTF8.GetString (bytes.ToArray())
+        #else
+        Encoding.UTF8.GetString bytes
+        #endif

+ 312 - 0
Tdms/Channel.fs

@@ -0,0 +1,312 @@
+namespace FSharp.Data.Tdms
+
+open System
+open System.IO
+open System.Numerics
+open System.Threading
+open System.Threading.Tasks
+
+type Channel = {
+    Name: string
+    FilePath: string
+    Properties: Property seq
+    RawDataBlocks: RawDataBlocks option
+    BigEndian: bool
+}
+
+module Channel =
+    
+    let tryGetPropertyValue<'t> propertyName { Properties = properties } =
+        properties
+        |> Seq.tryFind (fun { Name = propertyName' } -> propertyName' = propertyName)
+        |> Option.bind Property.tryGet<'t>
+
+    let getPropertyValue<'t> propertyName =
+        tryGetPropertyValue<'t> propertyName >> Option.get
+        
+    let tryGetRawData<'t>
+        { FilePath = path
+          RawDataBlocks = rawDataBlocks
+          BigEndian = bigEndian }
+        =
+        let bufferSize = 65_536
+        match rawDataBlocks with
+        | None -> None
+        | Some (PrimitiveRawDataBlocks (ty, rawDataBlockArray)) ->
+
+            if typeof<'t>.IsAssignableFrom ty then
+                use fileStream =
+                    new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.None)
+
+                if ty = typeof<bool> then
+                    Reader.readPrimitiveRawData<bool> fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                elif ty = typeof<sbyte> then
+                    Reader.readPrimitiveRawData<sbyte> fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                elif ty = typeof<int16> then
+                    Reader.readPrimitiveRawData<int16> fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                elif ty = typeof<int> then
+                    Reader.readPrimitiveRawData<int> fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                elif ty = typeof<int64> then
+                    Reader.readPrimitiveRawData<int64> fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                elif ty = typeof<byte> then
+                    Reader.readPrimitiveRawData<byte> fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                elif ty = typeof<uint16> then
+                    Reader.readPrimitiveRawData<uint16> fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                elif ty = typeof<uint> then
+                    Reader.readPrimitiveRawData<uint> fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                elif ty = typeof<uint64> then
+                    Reader.readPrimitiveRawData<uint64> fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                elif ty = typeof<float32> then
+                    Reader.readPrimitiveRawData<float32> fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                elif ty = typeof<float> then
+                    Reader.readPrimitiveRawData<float> fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                elif ty = typeof<Complex> then
+                    Reader.readComplexRawData fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                elif ty = typeof<Timestamp> then
+                    Reader.readTimestampRawData fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                elif ty = typeof<float80> then
+                    Reader.readFloat80RawData fileStream rawDataBlockArray bigEndian
+                    |> box
+                    |> tryUnbox<'t []>
+                else
+                    None
+            else if ty = typeof<Timestamp> then
+                if typeof<'t> = typeof<DateTime> then
+                    use fileStream =
+                        new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.None)
+
+                    Reader.readTimestampRawData fileStream rawDataBlockArray bigEndian
+                    |> Array.map Timestamp.toDateTime
+                    |> box
+                    |> tryUnbox<'t []>
+                else if typeof<'t> = typeof<DateTimeOffset> then
+                    use fileStream =
+                        new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.None)
+
+                    Reader.readTimestampRawData fileStream rawDataBlockArray bigEndian
+                    |> Array.map Timestamp.toDateTimeOffset
+                    |> box
+                    |> tryUnbox<'t []>
+                else if typeof<'t> = typeof<TimeSpan> then
+                    use fileStream =
+                        new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.None)
+
+                    Reader.readTimestampRawData fileStream rawDataBlockArray bigEndian
+                    |> Array.map Timestamp.toTimeSpan
+                    |> box
+                    |> tryUnbox<'t []>
+                else
+                    None
+            else
+                None
+        | Some (StringRawDataBlocks stringRawDataBlockArray) ->
+            if typeof<'t> = typeof<string> then
+                use fileStream =
+                    new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.None)
+
+                Reader.readStringRawData fileStream stringRawDataBlockArray bigEndian
+                |> box
+                |> tryUnbox<'t []>
+            else
+                None
+
+    #if !IS_DESIGNTIME
+    let tryGetRawDataAsyncCt<'t>
+        ct
+        { FilePath = path
+          RawDataBlocks = rawDataBlocks
+          BigEndian = bigEndian }
+        =
+        let bufferSize = 65_536
+        match rawDataBlocks with
+        | None -> Task.FromResult None
+        | Some (PrimitiveRawDataBlocks (ty, rawDataBlockArray)) ->
+
+            if ty = typeof<'t> then
+                if ty = typeof<bool> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readPrimitiveRawDataAsync<bool> ct fileStream rawDataBlockArray bigEndian
+                        return box result |> tryUnbox<'t []>
+                    }
+                else if ty = typeof<sbyte> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readPrimitiveRawDataAsync<sbyte> ct fileStream rawDataBlockArray bigEndian
+                        return box result |> tryUnbox<'t []>
+                    }
+                else if ty = typeof<int16> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readPrimitiveRawDataAsync<int16> ct fileStream rawDataBlockArray bigEndian
+                        return box result |> tryUnbox<'t []>
+                    }
+                else if ty = typeof<int> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readPrimitiveRawDataAsync<int> ct fileStream rawDataBlockArray bigEndian
+                        return box result |> tryUnbox<'t []>
+                    }
+                else if ty = typeof<int64> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readPrimitiveRawDataAsync<int64> ct fileStream rawDataBlockArray bigEndian
+                        return box result |> tryUnbox<'t []>
+                    }
+                else if ty = typeof<byte> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readPrimitiveRawDataAsync<byte> ct fileStream rawDataBlockArray bigEndian
+                        return box result |> tryUnbox<'t []>
+                    }
+                else if ty = typeof<uint16> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readPrimitiveRawDataAsync<uint16> ct fileStream rawDataBlockArray bigEndian
+                        return box result |> tryUnbox<'t []>
+                    }
+                else if ty = typeof<uint> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readPrimitiveRawDataAsync<uint> ct fileStream rawDataBlockArray bigEndian
+                        return box result |> tryUnbox<'t []>
+                    }
+                else if ty = typeof<uint64> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readPrimitiveRawDataAsync<uint64> ct fileStream rawDataBlockArray bigEndian
+                        return box result |> tryUnbox<'t []>
+                    }
+                else if ty = typeof<float32> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readPrimitiveRawDataAsync<float32> ct fileStream rawDataBlockArray bigEndian
+                        return box result |> tryUnbox<'t []>
+                    }
+                else if ty = typeof<float> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readPrimitiveRawDataAsync<float> ct fileStream rawDataBlockArray bigEndian
+                        return box result |> tryUnbox<'t []>
+                    }
+                else if ty = typeof<float80> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readFloat80RawDataAsync ct fileStream rawDataBlockArray bigEndian
+                        return box result |> tryUnbox<'t []>
+                    }
+                else if ty = typeof<Complex> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readComplexRawDataAsync ct fileStream rawDataBlockArray bigEndian
+                        return box result |> tryUnbox<'t []>
+                    }
+                else
+                    Task.FromResult None
+            else if ty = typeof<Timestamp> then
+                if typeof<'t> = typeof<DateTime> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readTimestampRawDataAsync ct fileStream rawDataBlockArray bigEndian
+
+                        return
+                            Array.map Timestamp.toDateTime result
+                            |> box
+                            |> tryUnbox<'t []>
+                    }
+                else if typeof<'t> = typeof<DateTimeOffset> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readTimestampRawDataAsync ct fileStream rawDataBlockArray bigEndian
+
+                        return
+                            Array.map Timestamp.toDateTimeOffset result
+                            |> box
+                            |> tryUnbox<'t []>
+                    }
+                else if typeof<'t> = typeof<TimeSpan> then
+                    backgroundTask {
+                        use fileStream =
+                            new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                        let! result = Reader.readTimestampRawDataAsync ct fileStream rawDataBlockArray bigEndian
+
+                        return
+                            Array.map Timestamp.toTimeSpan result
+                            |> box
+                            |> tryUnbox<'t []>
+                    }
+                else
+                    Task.FromResult None
+            else
+                Task.FromResult None
+        | Some (StringRawDataBlocks stringRawDataBlockArray) ->
+            if typeof<'t> = typeof<string> then
+                backgroundTask {
+                    use fileStream =
+                        new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous)
+
+                    let! result = Reader.readStringRawDataAsync ct fileStream stringRawDataBlockArray bigEndian
+                    return result |> box |> tryUnbox<'t []>
+                }
+            else
+                Task.FromResult None
+
+    let tryRawDataAsync<'t> = tryGetRawDataAsyncCt<'t> CancellationToken.None
+    #endif

+ 15 - 0
Tdms/Extended.fs

@@ -0,0 +1,15 @@
+namespace FSharp.Data.Tdms
+
+open System.Runtime.CompilerServices
+open System.Runtime.InteropServices
+
+#if !IS_DESIGNTIME
+[<Struct; IsReadOnly; StructLayout(LayoutKind.Sequential, Pack = 1)>]
+#else
+[<Struct; StructLayout(LayoutKind.Sequential, Pack = 1)>]
+#endif
+type Extended =
+    { RawSignExponent: uint16
+      RawSignificand: uint64 }
+
+type float80 = Extended

+ 28 - 0
Tdms/FSharp.Data.Tdms.Runtime.fs

@@ -0,0 +1,28 @@
+#if INTERACTIVE
+#load "../../src/ProvidedTypes.fsi" "../../src/ProvidedTypes.fs"
+#endif
+
+namespace FSharp.Data
+
+open FSharp.Data.Tdms
+
+module Helpers =
+
+    type RawDataHelper =
+
+        static member RawData<'T>(group, channel, index) =
+            File.tryGetRawData<'T> group channel index
+            |> Option.defaultValue [||]
+
+    let rawData ty group channel index =
+        let generic =
+            typeof<RawDataHelper>.GetMethod "RawData"
+
+        let concrete = generic.MakeGenericMethod [| ty |]
+        concrete.Invoke(null, [| group; channel; index |])
+
+#if !IS_DESIGNTIME
+// Put the TypeProviderAssemblyAttribute in the runtime DLL, pointing to the design-time DLL
+[<assembly:CompilerServices.TypeProviderAssembly("FSharp.Data.Tdms.DesignTime.dll")>]
+do ()
+#endif

+ 43 - 0
Tdms/FSharp.Data.Tdms.fsproj

@@ -0,0 +1,43 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <TargetFramework>net6.0</TargetFramework>
+    <Authors>Dylan Meysmans</Authors>
+    <Description>TDMS 2.0 support for F# and C#</Description>
+    <PackageLicenseExpression>MIT</PackageLicenseExpression>
+    <PackageProjectUrl>https://mettekou.github.io/FSharp.Data.Tdms</PackageProjectUrl>
+    <RepositoryUrl>https://github.com/mettekou/FSharp.Data.Tdms</RepositoryUrl>
+    <RepositoryType>git</RepositoryType>
+    <PublishRepositoryUrl>true</PublishRepositoryUrl>
+    <PackageReadmeFile>README.md</PackageReadmeFile>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <FSharpToolsDirectory>typeproviders</FSharpToolsDirectory>
+    <PackagePath>typeproviders</PackagePath>
+    <EmbedUntrackedSources>true</EmbedUntrackedSources>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="Extended.fs" />
+    <Compile Include="Timestamp.fs" />
+    <Compile Include="Property.fs" />
+    <Compile Include="Buffer.fs" />
+    <Compile Include="RawDataBlock.fs" />
+    <Compile Include="Reader.fs" />
+    <Compile Include="Channel.fs" />
+    <Compile Include="Group.fs" />
+    <Compile Include="Object.fs" />
+    <Compile Include="Segment.fs" />
+    <Compile Include="File.fs" />
+    <Compile Include="FSharp.Data.Tdms.Runtime.fs" />
+  </ItemGroup>
+  <ItemGroup>
+
+    <PackageReference Include="MinVer" Version="4.2.0">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+    <PackageReference Update="FSharp.Core" Version="6.0.7" />
+  </ItemGroup>
+  <PropertyGroup>
+    <MinVerTagPrefix>v</MinVerTagPrefix>
+  </PropertyGroup>
+</Project>

+ 340 - 0
Tdms/File.fs

@@ -0,0 +1,340 @@
+namespace FSharp.Data.Tdms
+
+open System
+open System.Buffers
+open System.IO
+open System.Runtime.InteropServices
+open System.Text.RegularExpressions
+open System.Threading
+open System.Threading.Tasks
+open FSharp.Collections
+
+type File =
+    { Path: string
+      Properties: Property seq
+      Groups: FSharp.Data.Tdms.Group seq }
+
+module File =
+
+    [<Literal>]
+    let LeadInLength = 28
+
+    let ofObjects path objects =
+        let groups =
+            objects
+            |> Seq.choose
+                (fun ({ Name = groupName
+                        Properties = properties }: FSharp.Data.Tdms.Object) ->
+                    if Regex.IsMatch(groupName, @"^\/'[^\/']+'$") then
+                        Some
+                            { Name = groupName.Substring(2, String.length groupName - 3)
+                              Properties = properties
+                              Channels =
+                                  objects
+                                  |> Seq.filter
+                                      (fun { Name = channelName } ->
+                                          channelName.StartsWith groupName
+                                          && String.length channelName > String.length groupName)
+                                  |> Seq.map (Object.toChannel path) }
+                    else
+                        None)
+
+        { Path = path
+          Properties =
+              objects
+              |> Seq.tryFind (fun ({ Name = name }: FSharp.Data.Tdms.Object) -> name = "/")
+              |> Option.map (fun { Properties = properties } -> properties)
+              |> Option.toList
+              |> Seq.concat
+          Groups = groups }
+
+    /// <summary>
+    /// Opens a <see cref="File" />, reads the index from it, and closes it.
+    /// </summary>
+    /// <param name="path">the path to the <see cref="File" /> to read.</param>
+    /// <param name="writeIndex">Whether to write the TDMS index file if it does not exist.</param>
+    let read path writeIndex =
+        let indexPath =
+            Path.ChangeExtension(path, ".tdms_index")
+
+        let indexExists = File.Exists indexPath
+
+        use stream =
+            new FileStream(
+                (if indexExists then indexPath else path),
+                FileMode.Open,
+                FileAccess.Read,
+                FileShare.Read,
+                65_536,
+                if indexExists then
+                    FileOptions.SequentialScan
+                else
+                    FileOptions.None
+            )
+
+        use indexStream =
+            if not indexExists && writeIndex then
+                new FileStream(indexPath, FileMode.Create, FileAccess.Write, FileShare.None, 8_192, false)
+            else
+                null
+
+        let mutable buffer = ArrayPool<byte>.Shared.Rent LeadInLength
+        let objects = ResizeArray()
+        let mutable offset = 0uL
+
+        while stream.Position < stream.Length do
+            stream.Read(buffer, 0, LeadInLength)
+            |> ignore
+
+            if not indexExists && writeIndex then
+                indexStream.Write(Segment.tdsh, 0, 4)
+                indexStream.Write(buffer, 4, 24)
+
+            let mutable leadInSpan = ReadOnlySpan buffer
+
+            let { TableOfContents = tableOfContents
+                  NextSegmentOffset = nextSegmentOffset
+                  RawDataOffset = rawDataOffset } =
+                Segment.readLeadIn &leadInSpan
+
+            let metaDataStart = offset + 28uL
+
+            if tableOfContents.HasFlag(TableOfContents.ContainsMetaData) then
+                let remainingLength = int rawDataOffset
+
+                if remainingLength > buffer.Length then
+                    ArrayPool<byte>.Shared.Return (buffer, false)
+                    buffer <- ArrayPool<byte>.Shared.Rent remainingLength
+
+                stream.Read(buffer, 0, remainingLength) |> ignore
+
+                if not indexExists && writeIndex then
+                    indexStream.Write(buffer, 0, remainingLength)
+
+                let mutable metaDataSpan = ReadOnlySpan buffer
+
+                Segment.readMetaData
+                    objects
+                    (metaDataStart + rawDataOffset)
+                    (metaDataStart + min nextSegmentOffset (uint64 stream.Length - metaDataStart))
+                    &metaDataSpan
+                    (tableOfContents.HasFlag(TableOfContents.ContainsBigEndianData))
+                    (tableOfContents.HasFlag(TableOfContents.ContainsInterleavedData))
+
+            offset <- metaDataStart + nextSegmentOffset
+
+            if not indexExists then
+                stream.Seek(int64 offset, SeekOrigin.Begin)
+                |> ignore
+
+        ArrayPool<byte>.Shared.Return (buffer, false)
+        ofObjects path objects
+
+
+    let readAsyncCt ct path writeIndex =
+        task {
+            let indexPath =
+                Path.ChangeExtension(path, ".tdms_index")
+
+            let indexExists = File.Exists(indexPath)
+
+            use stream =
+                new FileStream(
+                    (if indexExists then indexPath else path),
+                    FileMode.Open,
+                    FileAccess.Read,
+                    FileShare.Read,
+                    65_536,
+                    if indexExists then
+                        FileOptions.SequentialScan
+                        ||| FileOptions.Asynchronous
+                    else
+                        FileOptions.Asynchronous
+                )
+
+            use indexStream =
+                if not indexExists && writeIndex then
+                    new FileStream(indexPath, FileMode.Create, FileAccess.Write, FileShare.None, 1_048_576, true)
+                else
+                    null
+
+            let mutable buffer = ArrayPool<byte>.Shared.Rent LeadInLength
+            let objects = ResizeArray()
+            let mutable offset = 0uL
+
+            while stream.Position < stream.Length do
+                let! _ = stream.ReadAsync(buffer, 0, LeadInLength, ct)
+
+                if not indexExists && writeIndex then
+                    do! indexStream.WriteAsync(Segment.tdsh, 0, 4, ct)
+                    do! indexStream.WriteAsync(buffer, 4, 24, ct)
+
+                let mutable leadInSpan = ReadOnlySpan buffer
+
+                let { TableOfContents = tableOfContents
+                      NextSegmentOffset = nextSegmentOffset
+                      RawDataOffset = rawDataOffset } =
+                    Segment.readLeadIn &leadInSpan
+
+                let metaDataStart = offset + 28uL
+
+                if tableOfContents.HasFlag(TableOfContents.ContainsMetaData) then
+                    let remainingLength = int rawDataOffset
+
+                    if remainingLength > buffer.Length then
+                        ArrayPool<byte>.Shared.Return (buffer, false)
+                        buffer <- ArrayPool<byte>.Shared.Rent remainingLength
+
+                    let! _ = stream.ReadAsync(buffer, 0, remainingLength, ct)
+
+                    if not indexExists && writeIndex then
+                        do! indexStream.WriteAsync(buffer, 0, remainingLength, ct)
+
+                    let mutable metaDataSpan = ReadOnlySpan buffer
+
+                    Segment.readMetaData
+                        objects
+                        (metaDataStart + rawDataOffset)
+                        (metaDataStart + nextSegmentOffset)
+                        &metaDataSpan
+                        (tableOfContents.HasFlag(TableOfContents.ContainsBigEndianData))
+                        (tableOfContents.HasFlag(TableOfContents.ContainsInterleavedData))
+
+                offset <- metaDataStart + nextSegmentOffset
+                
+                if not indexExists then
+                    stream.Seek(int64 offset, SeekOrigin.Begin)
+                    |> ignore
+
+            ArrayPool<byte>.Shared.Return (buffer, false)
+            return ofObjects path objects
+        }
+
+    /// <summary>
+    /// Asynchronously opens a <see cref="File" />, reads the index from it, and closes it.
+    /// </summary>
+    /// <param name="path">the path to the <see cref="File" /> to read.</param>
+    /// <param name="writeIndex">Whether to write the index file if it does not exist.</param>
+    let readAsync = readAsyncCt CancellationToken.None
+
+    let tryGetPropertyValue<'t> propertyName ({ Properties = properties }: File) =
+        properties
+        |> Seq.tryFind (fun { Name = propertyName' } -> propertyName' = propertyName)
+        |> Option.bind Property.tryGet<'t>
+
+    let getPropertyValue<'t> propertyName =
+        tryGetPropertyValue<'t> propertyName >> Option.get
+
+    /// <summary>Returns all groups within the <see cref="File" />.</summary>
+    let getGroups { Groups = groups } = groups
+    
+    /// <summary>Returns the <see cref="Group" /> with the given name within the <see cref="File" />. Returns None if it does not exist.</summary>
+    /// <param name="groupName">the name of the <see cref="Group" /> to find.</param>
+    let tryFindGroup groupName =
+        getGroups >> Seq.tryFind (fun { Name = groupName' } -> groupName' = groupName)
+
+    /// <summary>Returns the <see cref="Group" /> with the given name within the <see cref="File" />.</summary>
+    /// <param name="groupName">the name of the <see cref="Group" /> to find.</param>
+    let findGroup groupName = tryFindGroup groupName >> Option.get
+    
+    /// <summary>Returns the <see cref="Channel" /> with the given name within the <see cref="Group" /> with the given name within the <see cref="File" />. Returns None if it does not exist.</summary>
+    /// <param name="groupName">the name of the <see cref="Group" /> to find the <see cref="Channel" /> in.</param>
+    /// <param name="channelName">the name of the <see cref="Channel" /> to find.</param>
+    let tryFindChannel groupName channelName =
+        tryFindGroup groupName
+        >> Option.bind (Group.tryFindChannel channelName)
+
+    /// <summary>Returns the <see cref="Channel" /> with the given name within the <see cref="Group" /> with the given name.</summary>
+    /// <param name="groupName">the name of the <see cref="Group" /> to find the <see cref="Channel" /> in.</param>
+    /// <param name="channelName">the name of the <see cref="Channel" /> to find.</param>
+    let findChannel groupName channelName =
+        tryFindChannel groupName channelName >> Option.get
+
+    /// <summary>Returns the raw data for a <see cref="Channel" />. Returns None if the <see cref="Channel" /> does not exist, if it does not have any raw data, or if its raw data is not of the given type.</summary>
+    /// <param name="groupName">the name of the <see cref="Group" /> the <see cref="Channel" /> is in.</param>
+    /// <param name="channelName">the name of the <see cref="Channel" /> to get raw data for.</param>
+    /// <param name="file">the TDMS file to read from.</param>
+    let tryGetRawData<'t> groupName channelName file =
+        tryFindChannel groupName channelName file
+        |> Option.bind Channel.tryGetRawData<'t>
+    
+    #if !IS_DESIGNTIME
+    /// <summary>Asynchronously returns the raw data for a <see cref="Channel" />. Returns None if the <see cref="Channel" /> does not exist, if it does not have any raw data, or if its raw data is not of the given type.</summary>
+    /// <param name="groupName">the name of the <see cref="Group" /> the <see cref="Channel" /> is in.</param>
+    /// <param name="channelName">the name of the <see cref="Channel" /> to get raw data for.</param>
+    /// <param name="file">the TDMS file to read from.</param>
+    let tryGetRawDataAsyncCt<'t> ct groupName channelName file =
+        tryFindChannel groupName channelName file
+        |> Option.map (Channel.tryGetRawDataAsyncCt<'t> ct)
+        |> Option.defaultValue (Task.FromResult None)
+
+    let tryGetRawDataAsync<'t> = tryGetRawDataAsyncCt<'t> CancellationToken.None
+    #endif
+
+type File with
+
+    /// <summary>
+    /// Opens a TDMS file, reads the index from it, and closes it.
+    /// </summary>
+    /// <param name="path"> The path to the TDMS file to read.</param>
+    /// <param name="writeIndex"> Whether to write the TDMS index file.</param>
+    static member Read(path, writeIndex) = File.read path writeIndex
+
+    /// <summary>
+    /// Asynchronously opens a TDMS file, reads the index from it, and closes it.
+    /// </summary>
+    /// <param name="path"> The path to the TDMS file to read.</param>
+    /// <param name="writeIndex"> Whether to write the TDMS index file.</param>
+    static member ReadAsync(path, writeIndex, [<Optional; DefaultParameterValue(CancellationToken())>] ct) = File.readAsyncCt ct path writeIndex
+
+    /// <summary>
+    /// Tries to get the raw data for the given channel, belonging to the given group in the given TDMS file.
+    /// </summary>
+    member file.TryGetRawData<'T>(groupName, channelName, [<Out>] rawData: byref<'T []>) =
+        match File.tryGetRawData<'T> groupName channelName file with
+        | None -> false
+        | Some rd ->
+            rawData <- rd
+            true
+    #if !IS_DESIGNTIME
+    /// <summary>
+    /// Asynchronously gets the raw data for the given channel, belonging to the given group in the given TDMS file.
+    /// </summary>
+    member file.GetRawDataAsync<'t>(groupName, channelName, [<Optional; DefaultParameterValue(CancellationToken())>] ct) =
+        backgroundTask {
+            match! File.tryGetRawDataAsyncCt<'t> ct groupName channelName file with
+            | None -> return null
+            | Some rd -> return rd
+        }
+    #endif
+    
+    /// <summary>
+    /// Tries to get a property value for the given TDMS file.
+    /// </summary>
+    member file.TryGetPropertyValue<'T>(propertyName, [<Out>] propertyValue: byref<'T>) =
+        match File.tryGetPropertyValue<'T> propertyName file with
+        | None -> false
+        | Some pv ->
+            propertyValue <- pv
+            true
+
+    /// <summary>
+    /// Tries to get a property value for the given group in the given TDMS file.
+    /// </summary>
+    member file.TryGetPropertyValue<'T>(propertyName, groupName, [<Out>] propertyValue: byref<'T>) =
+        match File.tryFindGroup groupName file
+              |> Option.bind (Group.tryGetPropertyValue<'T> propertyName) with
+        | None -> false
+        | Some pv ->
+            propertyValue <- pv
+            true
+
+    /// <summary>
+    /// Tries to get a property value for the given channel, belonging to the given group in the given TDMS file.
+    /// </summary>
+    member file.TryGetPropertyValue<'T>(propertyName, groupName, channelName, [<Out>] propertyValue: byref<'T>) =
+        match File.tryGetPropertyValue<'T> ("/" + groupName + "/" + channelName) file with
+        | None -> false
+        | Some pv ->
+            propertyValue <- pv
+            true

+ 30 - 0
Tdms/Group.fs

@@ -0,0 +1,30 @@
+namespace FSharp.Data.Tdms
+
+type Group =
+    { Name: string
+      Properties: Property seq
+      Channels: Channel seq }
+
+module Group =
+
+    let tryGetPropertyValue<'t> propertyName ({ Properties = properties }: Group) =
+        properties
+        |> Seq.tryFind (fun { Name = propertyName' } -> propertyName' = propertyName)
+        |> Option.bind Property.tryGet<'t>
+
+    let getPropertyValue<'t> propertyName =
+        tryGetPropertyValue<'t> propertyName >> Option.get
+
+    /// <summary>Finds the <see cref="Channel" /> with the given name within the <see cref="Group" />. Returns None if it does not exist.</summary>
+    /// <param name="channelName">the name of the <see cref="Channel" /> to get.</param>
+    let tryFindChannel channelName { Channels = channels } =
+        channels
+        |> Seq.tryFind (fun { Name = channelName' } -> channelName = channelName')
+
+    /// <summary>Finds the <see cref="Channel" /> with the given name within the <see cref="Group" />.</summary>
+    /// <param name="channelName">the name of the <see cref="Channel" /> to get.</param>
+    let findChannel channelName =
+        tryFindChannel channelName >> Option.get
+
+    /// <summary>Returns all channels within the <see cref="Group" />.</summary>
+    let getChannels { Channels = channels } = channels

+ 167 - 0
Tdms/Object.fs

@@ -0,0 +1,167 @@
+namespace FSharp.Data.Tdms
+
+open System
+open System.IO
+open System.Numerics
+open System.Runtime.InteropServices
+open System.Threading.Tasks
+
+type Object =
+    { Name: string
+      BigEndian: bool
+      mutable RawDataBlocks: RawDataBlocks option
+      Properties: Property ResizeArray }
+
+module Object =
+
+    let addPrimitiveRawDataBlock ty primitiveRawDataBlock object =
+        match object.RawDataBlocks with
+        | None ->
+            let primitiveRawDataBlockArray = ResizeArray()
+            primitiveRawDataBlockArray.Add primitiveRawDataBlock
+            object.RawDataBlocks <- Some(PrimitiveRawDataBlocks(ty, primitiveRawDataBlockArray))
+        | Some (PrimitiveRawDataBlocks (ty', primitiveRawDataBlockArray)) ->
+            if ty <> ty' then
+                failwithf
+                    "Object %s already has type %A, cannot add a primitive data block of type %A to it; check whether the TDMS file is valid"
+                    object.Name
+                    ty'
+                    ty
+            else
+                primitiveRawDataBlockArray.Add primitiveRawDataBlock
+        | Some (StringRawDataBlocks _) ->
+            failwithf
+                "Object %s already has type string, cannot add a primitive data block of type %A to it; check whether the TDMS file is valid"
+                object.Name
+                ty
+
+    let addStringRawDataBlock stringRawDataBlock object =
+        match object.RawDataBlocks with
+        | None ->
+            let stringRawDataBlockArray = ResizeArray()
+            stringRawDataBlockArray.Add stringRawDataBlock
+            object.RawDataBlocks <- Some(StringRawDataBlocks stringRawDataBlockArray)
+        | Some (PrimitiveRawDataBlocks (ty', _)) ->
+            failwithf
+                "Object %s already has type %A, cannot add a string data block to it; check whether the TDMS file is valid"
+                object.Name
+                ty'
+        | Some (StringRawDataBlocks stringRawDataBlockArray) -> stringRawDataBlockArray.Add stringRawDataBlock
+
+    let readRawDataIndex
+        object
+        (rawDataPosition: uint64)
+        (buffer: byte ReadOnlySpan byref)
+        bigEndian
+        (interleaved: bool)
+        =
+        match Buffer.readUInt &buffer bigEndian with
+        | 0u ->
+            match object.RawDataBlocks with
+            | None -> failwithf "Missing raw data index for object %s; check whether the TDMS file is valid" object.Name
+            | Some (PrimitiveRawDataBlocks (ty, primitiveRawDataBlockArray)) ->
+                match Seq.tryLast primitiveRawDataBlockArray with
+                | None ->
+                    failwithf
+                        "Missing primitive raw data blocks for object %s; this is a bug in FSharp.Data.Tdms"
+                        object.Name
+                | Some (DecimatedPrimitiveRawDataBlock (_, bytes)) ->
+                    if interleaved then
+                        failwithf
+                            "Object %s raw data changes from decimated to interleaved without a new raw data index; check whether the TDMS file is valid"
+                            object.Name
+                    else
+                        primitiveRawDataBlockArray.Add(DecimatedPrimitiveRawDataBlock(rawDataPosition, bytes))
+                        bytes
+                | Some (InterleavedPrimitiveRawDataBlock _) ->
+                    if not interleaved then
+                        failwithf
+                            "Object %s raw data changes from interleaved to decimated without a new raw data index; check whether the TDMS file is valid"
+                            object.Name
+                    else
+                        uint64 (Marshal.SizeOf ty)
+            | Some (StringRawDataBlocks stringRawDataBlockArray) ->
+                match Seq.tryLast stringRawDataBlockArray with
+                | None ->
+                    failwithf
+                        "Missing string raw data blocks for object %s; this is a bug in FSharp.Data.Tdms"
+                        object.Name
+                | Some (_, length, bytes) ->
+                    stringRawDataBlockArray.Add(rawDataPosition, length, bytes)
+                    bytes
+        | 20u ->
+            let ty = Buffer.readType &buffer bigEndian
+            Buffer.readUInt &buffer bigEndian |> ignore
+            let length = Buffer.readUInt64 &buffer bigEndian
+            let size = Marshal.SizeOf ty
+
+            if not interleaved then
+                addPrimitiveRawDataBlock ty (DecimatedPrimitiveRawDataBlock(rawDataPosition, length)) object
+                length * uint64 size
+            else
+                addPrimitiveRawDataBlock
+                    ty
+                    (InterleavedPrimitiveRawDataBlock
+                        { Start = rawDataPosition
+                          Count = length
+                          Skip = 0uL })
+                    object
+
+                uint64 size
+        | 28u ->
+            Buffer.readType &buffer bigEndian |> ignore
+            Buffer.readUInt &buffer bigEndian |> ignore
+
+            let length = Buffer.readUInt64 &buffer bigEndian
+            let bytes = Buffer.readUInt64 &buffer bigEndian
+            addStringRawDataBlock (rawDataPosition, length, bytes) object
+            bytes
+        | 0xFFFFFFFFu -> 0uL
+        | 0x1269u
+        | 0x126Au ->
+            let ty = Buffer.readType &buffer bigEndian
+            let dimension = Buffer.readUInt &buffer bigEndian
+            let chunkSize = Buffer.readUInt64 &buffer bigEndian
+            let scalerCount = Buffer.readUInt &buffer bigEndian |> int
+
+            let scalers =
+                Array.zeroCreate<FormatChangingScaler> scalerCount
+
+            for scalerIndex = 0 to scalerCount - 1 do
+                scalers.[scalerIndex] <- RawDataBlock.readFormatChangingScaler &buffer bigEndian
+
+            let widthCount = Buffer.readUInt &buffer bigEndian |> int
+            let widths = Array.zeroCreate<uint> widthCount
+
+            for widthIndex = 0 to widthCount - 1 do
+                widths.[widthIndex] <- Buffer.readUInt &buffer bigEndian
+            //object.LastRawDataIndex <- Some (DaqMx (ty, dimension, chunkSize, scalers, widths))
+            0uL
+        | length -> failwithf "Invalid raw data index length: %i" length
+
+    let toChannel
+        filePath
+        ({ Name = name
+           Properties = properties
+           RawDataBlocks = rawDataBlocks
+           BigEndian = bigEndian }: Object)
+        =
+        { Name =
+              name.Split([| '/' |], StringSplitOptions.RemoveEmptyEntries).[1]
+                  .Trim('\'')
+          FilePath = filePath
+          Properties = properties
+          RawDataBlocks = rawDataBlocks
+          BigEndian = bigEndian }
+
+    let toGroup
+        filePath
+        channels
+        ({ Name = groupName
+           Properties = groupProperties }: Object)
+        =
+        { Name =
+              groupName.Split([| '/' |], StringSplitOptions.RemoveEmptyEntries).[0]
+                  .Trim('\'')
+          Properties = groupProperties
+          Channels = Seq.map (toChannel filePath) channels }

+ 24 - 0
Tdms/Property.fs

@@ -0,0 +1,24 @@
+namespace FSharp.Data.Tdms
+
+open System
+
+type Property = {
+  Name: string
+  Type : Type
+  Raw : obj
+}
+
+module Property =
+    
+    let tryGet<'t> { Type = ty; Raw = raw } =
+        let ty' = typeof<'t>
+        if ty'.IsAssignableFrom ty
+        then tryUnbox<'t> raw
+        else
+          if ty = typeof<Timestamp> then
+            let timestamp = raw :?> Timestamp
+            if ty' = typeof<DateTime> then tryUnbox<'t> (Timestamp.toDateTime timestamp)
+            else if ty' = typeof<DateTimeOffset> then tryUnbox<'t> (Timestamp.toDateTimeOffset timestamp)
+            else if ty' = typeof<TimeSpan> then tryUnbox<'t> (Timestamp.toTimeSpan timestamp)
+            else None
+          else None

+ 32 - 0
Tdms/RawDataBlock.fs

@@ -0,0 +1,32 @@
+namespace FSharp.Data.Tdms
+
+open System
+
+type FormatChangingScaler =
+    { DaqMxDataType: uint
+      RawBufferIndex: uint
+      RawByteOffsetWithinStride: uint
+      SampleFormatBitmap: uint
+      ScaleId: uint }
+
+type InterleavedPrimitiveRawDataBlock =
+    { Start: uint64
+      Count: uint64
+      mutable Skip: uint64 }
+
+type PrimitiveRawDataBlock =
+    | DecimatedPrimitiveRawDataBlock of (uint64 * uint64)
+    | InterleavedPrimitiveRawDataBlock of InterleavedPrimitiveRawDataBlock
+
+type RawDataBlocks =
+    | PrimitiveRawDataBlocks of Type * PrimitiveRawDataBlock ResizeArray
+    | StringRawDataBlocks of (uint64 * uint64 * uint64) ResizeArray
+
+module RawDataBlock =
+
+    let readFormatChangingScaler (buffer: byte ReadOnlySpan byref) bigEndian =
+        { DaqMxDataType = Buffer.readUInt &buffer bigEndian
+          RawBufferIndex = Buffer.readUInt &buffer bigEndian
+          RawByteOffsetWithinStride = Buffer.readUInt &buffer bigEndian
+          SampleFormatBitmap = Buffer.readUInt &buffer bigEndian
+          ScaleId = Buffer.readUInt &buffer bigEndian }

+ 547 - 0
Tdms/Reader.fs

@@ -0,0 +1,547 @@
+namespace FSharp.Data.Tdms
+
+open System
+open System.Buffers
+open System.IO
+open System.Numerics
+open System.Runtime.InteropServices
+open System.Text
+
+module Reader =
+
+    type CastMemoryManager<'tfrom, 'tto when 'tfrom: struct and 'tfrom: (new: unit -> 'tfrom) and 'tfrom :> ValueType and 'tto: struct and 'tto: (new: unit
+                                                                                                                                                       -> 'tto) and 'tto :> ValueType>(memory: Memory<'tfrom>) =
+        inherit MemoryManager<'tto>()
+
+        override _.GetSpan() =
+            MemoryMarshal.Cast<'tfrom, 'tto> memory.Span
+
+        override _.Dispose _ = ()
+        override _.Pin _ = raise (NotSupportedException())
+        override _.Unpin() = raise (NotSupportedException())
+
+    let cast<'tfrom, 'tto when 'tfrom: struct and 'tfrom: (new: unit -> 'tfrom) and 'tfrom :> ValueType and 'tto: struct and 'tto: (new: unit
+                                                                                                                                         -> 'tto) and 'tto :> ValueType>
+        (memory: Memory<'tfrom>)
+        =
+        if typeof<'tfrom> = typeof<'tto> then
+            box memory :?> Memory<'tto>
+        else
+            (new CastMemoryManager<'tfrom, 'tto>(memory))
+                .Memory
+
+    let readPrimitiveRawData<'t when 't: struct and 't: (new: unit -> 't) and 't :> ValueType>
+        (stream: Stream)
+        (segments: PrimitiveRawDataBlock seq)
+        bigEndian
+        =
+        let mutable position = 0
+        let size = sizeof<'t>
+
+        let data =
+            Seq.sumBy
+                (function
+                | DecimatedPrimitiveRawDataBlock (_, size) -> size
+                | InterleavedPrimitiveRawDataBlock { Count = count } -> count)
+                segments
+            |> int
+            |> Array.zeroCreate<'t>
+
+        let span =
+            MemoryMarshal.Cast<'t, byte>(data.AsSpan())
+
+        for segment in segments do
+            match segment with
+            | DecimatedPrimitiveRawDataBlock (start, length) ->
+                stream.Seek(int64 start, SeekOrigin.Begin)
+                |> ignore
+
+                stream.Read(span.Slice(position * size, int length * size))
+                |> ignore
+
+                position <- position + int length
+            | InterleavedPrimitiveRawDataBlock { Start = start
+                                                 Count = count
+                                                 Skip = skip } ->
+                stream.Seek(int64 start, SeekOrigin.Begin)
+                |> ignore
+
+                for _ = 0 to int count - 1 do
+                    stream.Read(span.Slice(position * size, size))
+                    |> ignore
+
+                    stream.Seek(int64 skip, SeekOrigin.Current)
+                    |> ignore
+
+                    position <- position + 1
+
+
+        if bigEndian && size > 1 then
+            span.Reverse()
+            Array.Reverse data
+
+        data
+
+    let readComplexRawData (stream: Stream) (segments: PrimitiveRawDataBlock seq) bigEndian =
+        if not bigEndian then
+            readPrimitiveRawData<Complex> stream segments false
+        else
+            let mutable position = 0
+            let size = sizeof<Complex>
+
+            let data =
+                Seq.sumBy
+                    (function
+                    | DecimatedPrimitiveRawDataBlock (_, size) -> size
+                    | InterleavedPrimitiveRawDataBlock { Count = count } -> count)
+                    segments
+                |> int
+                |> Array.zeroCreate<Complex>
+
+            let span =
+                MemoryMarshal.Cast<Complex, byte>(data.AsSpan())
+
+            for segment in segments do
+                match segment with
+                | DecimatedPrimitiveRawDataBlock (start, length) ->
+                    stream.Seek(int64 start, SeekOrigin.Begin)
+                    |> ignore
+
+                    stream.Read(span.Slice(position * size, int length * size))
+                    |> ignore
+
+                    position <- position + int length
+                | InterleavedPrimitiveRawDataBlock { Start = start
+                                                     Count = count
+                                                     Skip = skip } ->
+                    stream.Seek(int64 start, SeekOrigin.Begin)
+                    |> ignore
+
+                    for _ = 0 to int count - 1 do
+                        stream.Read(span.Slice(position * size, size))
+                        |> ignore
+
+                        stream.Seek(int64 skip, SeekOrigin.Current)
+                        |> ignore
+
+                        position <- position + 1
+
+            span.Reverse()
+
+            MemoryMarshal
+                .Cast<Complex, double>(data.AsSpan())
+                .Reverse()
+
+            data
+
+    let readTimestampRawData (stream: Stream) (segments: PrimitiveRawDataBlock seq) bigEndian =
+        if not bigEndian then
+            readPrimitiveRawData<Timestamp> stream segments false
+        else
+            let mutable position = 0
+            let size = sizeof<Timestamp>
+
+            let data =
+                Seq.sumBy
+                    (function
+                    | DecimatedPrimitiveRawDataBlock (_, size) -> size
+                    | InterleavedPrimitiveRawDataBlock { Count = count } -> count)
+                    segments
+                |> int
+                |> Array.zeroCreate<Timestamp>
+
+            let span =
+                MemoryMarshal.Cast<Timestamp, byte>(data.AsSpan())
+
+            for segment in segments do
+                match segment with
+                | DecimatedPrimitiveRawDataBlock (start, length) ->
+                    stream.Seek(int64 start, SeekOrigin.Begin)
+                    |> ignore
+
+                    stream.Read(span.Slice(position * size, int length * size))
+                    |> ignore
+
+                    position <- position + int length
+                | InterleavedPrimitiveRawDataBlock { Start = start
+                                                     Count = count
+                                                     Skip = skip } ->
+                    stream.Seek(int64 start, SeekOrigin.Begin)
+                    |> ignore
+
+                    for _ = 0 to int count - 1 do
+                        stream.Read(span.Slice(position * size, size))
+                        |> ignore
+
+                        stream.Seek(int64 skip, SeekOrigin.Current)
+                        |> ignore
+
+                        position <- position + 1
+
+            span.Reverse()
+            Array.Reverse data
+            data
+
+    let readFloat80RawData (stream: Stream) (segments: PrimitiveRawDataBlock seq) bigEndian =
+        if not bigEndian then
+            readPrimitiveRawData<float80> stream segments false
+        else
+            let mutable position = 0
+            let size = sizeof<float80>
+
+            let data =
+                Seq.sumBy
+                    (function
+                    | DecimatedPrimitiveRawDataBlock (_, size) -> size
+                    | InterleavedPrimitiveRawDataBlock { Count = count } -> count)
+                    segments
+                |> int
+                |> Array.zeroCreate<float80>
+
+            let span =
+                MemoryMarshal.Cast<float80, byte>(data.AsSpan())
+
+            for segment in segments do
+                match segment with
+                | DecimatedPrimitiveRawDataBlock (start, length) ->
+
+                    stream.Seek(int64 start, SeekOrigin.Begin)
+                    |> ignore
+
+                    stream.Read(span.Slice(position * size, int length * size))
+                    |> ignore
+
+                    position <- position + int length
+                | InterleavedPrimitiveRawDataBlock { Start = start
+                                                     Count = count
+                                                     Skip = skip } ->
+                    stream.Seek(int64 start, SeekOrigin.Begin)
+                    |> ignore
+
+                    for _ = 0 to int count - 1 do
+                        stream.Read(span.Slice(position * size, size))
+                        |> ignore
+
+                        stream.Seek(int64 skip, SeekOrigin.Current)
+                        |> ignore
+
+                        position <- position + 1
+
+            MemoryMarshal.Cast<byte, float80>(span).Reverse()
+
+            Array.Reverse data
+            data
+
+    let readStringRawData (stream: Stream) (segments: (uint64 * uint64 * uint64) seq) bigEndian =
+        let offsets =
+            Seq.map (fun (_, length, _) -> length) segments
+            |> Seq.max
+            |> int
+            |> ArrayPool<uint>.Shared.Rent
+
+        let data =
+            Seq.map (fun (_, _, bytes) -> bytes) segments
+            |> Seq.max
+            |> int
+            |> ArrayPool<uint8>.Shared.Rent
+
+        let strings =
+            Seq.sumBy (fun (_, length, _) -> length) segments
+            |> int
+            |> Array.zeroCreate<string>
+
+        let mutable position = 0
+
+        for (start, length, bytes) in segments do
+            stream.Seek(int64 start, SeekOrigin.Begin)
+            |> ignore
+
+            let offsetsSpan = offsets.AsSpan().Slice(0, int length)
+
+            let offsetsByteSpan =
+                MemoryMarshal.Cast<uint, uint8>(offsetsSpan)
+
+            stream.Read(offsetsByteSpan) |> ignore
+
+            if bigEndian then
+                offsetsByteSpan.Reverse()
+                offsetsSpan.Reverse()
+
+            let mutable dataSpan = data.AsSpan().Slice(0, int bytes)
+
+            stream.Read(dataSpan) |> ignore
+
+            let mutable offset = 0
+
+            for index = 0 to offsetsSpan.Length - 1 do
+                strings.[position] <-
+                    Encoding.UTF8.GetString(
+                        dataSpan
+                            .Slice(0, int offsets.[index] - offset)
+                            .ToArray()
+                    )
+
+                dataSpan <- dataSpan.Slice(int offsets.[index] - offset)
+                offset <- int offsets.[index]
+                position <- position + 1
+
+        ArrayPool<byte>.Shared.Return(data, false)
+        ArrayPool<uint>.Shared.Return(offsets, false)
+        strings
+
+    let readPrimitiveRawDataAsync<'t when 't: struct and 't: (new: unit -> 't) and 't :> ValueType>
+        ct
+        (stream: Stream)
+        (segments: PrimitiveRawDataBlock seq)
+        bigEndian
+        =
+        let mutable position = 0
+        let size = sizeof<'t>
+
+        let data =
+            Seq.sumBy
+                (function
+                | DecimatedPrimitiveRawDataBlock (_, size) -> size
+                | InterleavedPrimitiveRawDataBlock { Count = count } -> count)
+                segments
+            |> int
+            |> Array.zeroCreate<'t>
+
+        let memory = cast<'t, byte> (data.AsMemory())
+
+        backgroundTask {
+            for segment in segments do
+                match segment with
+                | DecimatedPrimitiveRawDataBlock (start, length) ->
+                    stream.Seek(int64 start, SeekOrigin.Begin)
+                    |> ignore
+
+                    let! _ = stream.ReadAsync(memory.Slice(position * size, int length * size), ct)
+
+                    position <- position + int length
+                | InterleavedPrimitiveRawDataBlock { Start = start
+                                                     Count = count
+                                                     Skip = skip } ->
+                    stream.Seek(int64 start, SeekOrigin.Begin)
+                    |> ignore
+
+                    for _ = 0 to int count - 1 do
+                        let! _ = stream.ReadAsync(memory.Slice(position * size, size), ct)
+
+                        stream.Seek(int64 skip, SeekOrigin.Current)
+                        |> ignore
+
+                        position <- position + 1
+
+            if bigEndian then
+                memory.Span.Reverse()
+                Array.Reverse data
+
+            return data
+        }
+
+    let readFloat80RawDataAsync ct (stream: Stream) (segments: PrimitiveRawDataBlock seq) bigEndian =
+        if not bigEndian then
+            readPrimitiveRawDataAsync<float80> ct stream segments false
+        else
+            backgroundTask {
+                let mutable position = 0
+                let size = sizeof<float80>
+
+                let data =
+                    Seq.sumBy
+                        (function
+                        | DecimatedPrimitiveRawDataBlock (_, size) -> size
+                        | InterleavedPrimitiveRawDataBlock { Count = count } -> count)
+                        segments
+                    |> int
+                    |> Array.zeroCreate<float80>
+
+                let memory = cast<float80, byte> (data.AsMemory())
+
+                for segment in segments do
+                    match segment with
+                    | DecimatedPrimitiveRawDataBlock (start, length) ->
+
+                        stream.Seek(int64 start, SeekOrigin.Begin)
+                        |> ignore
+
+                        let! _ = stream.ReadAsync(memory.Slice(position * size, int length * size), ct)
+
+                        position <- position + int length
+                    | InterleavedPrimitiveRawDataBlock { Start = start
+                                                         Count = count
+                                                         Skip = skip } ->
+                        stream.Seek(int64 start, SeekOrigin.Begin)
+                        |> ignore
+
+                        for _ = 0 to int count - 1 do
+                            let! _ = stream.ReadAsync(memory.Slice(position * size, size), ct)
+
+                            stream.Seek(int64 skip, SeekOrigin.Current)
+                            |> ignore
+
+                            position <- position + 1
+
+                MemoryMarshal
+                    .Cast<byte, float80>(memory.Span)
+                    .Reverse()
+
+                Array.Reverse data
+                return data
+            }
+
+    let readComplexRawDataAsync ct (stream: Stream) (segments: PrimitiveRawDataBlock seq) bigEndian =
+        if not bigEndian then
+            readPrimitiveRawDataAsync<Complex> ct stream segments false
+        else
+            let mutable position = 0
+            let size = sizeof<Complex>
+
+            let data =
+                Seq.sumBy
+                    (function
+                    | DecimatedPrimitiveRawDataBlock (_, size) -> size
+                    | InterleavedPrimitiveRawDataBlock { Count = count } -> count)
+                    segments
+                |> int
+                |> Array.zeroCreate<Complex>
+
+            let memory = cast<Complex, byte> (data.AsMemory())
+
+            backgroundTask {
+                for segment in segments do
+                    match segment with
+                    | DecimatedPrimitiveRawDataBlock (start, length) ->
+                        stream.Seek(int64 start, SeekOrigin.Begin)
+                        |> ignore
+
+                        let! _ = stream.ReadAsync(memory.Slice(position * size, int length * size), ct)
+
+                        position <- position + int length
+                    | InterleavedPrimitiveRawDataBlock { Start = start
+                                                         Count = count
+                                                         Skip = skip } ->
+                        stream.Seek(int64 start, SeekOrigin.Begin)
+                        |> ignore
+
+                        for _ = 0 to int count - 1 do
+                            let! _ = stream.ReadAsync(memory.Slice(position * size, size), ct)
+
+                            stream.Seek(int64 skip, SeekOrigin.Current)
+                            |> ignore
+
+                            position <- position + 1
+
+                memory.Span.Reverse()
+
+                cast<Complex, double>(data.AsMemory())
+                    .Span.Reverse()
+
+                return data
+            }
+
+    let readTimestampRawDataAsync ct (stream: Stream) (segments: PrimitiveRawDataBlock seq) bigEndian =
+        if not bigEndian then
+            readPrimitiveRawDataAsync<Timestamp> ct stream segments false
+        else
+            let mutable position = 0
+            let size = sizeof<Timestamp>
+
+            let data =
+                Seq.sumBy
+                    (function
+                    | DecimatedPrimitiveRawDataBlock (_, size) -> size
+                    | InterleavedPrimitiveRawDataBlock { Count = count } -> count)
+                    segments
+                |> int
+                |> Array.zeroCreate<Timestamp>
+
+            let memory = cast<Timestamp, byte> (data.AsMemory())
+
+            backgroundTask {
+                for segment in segments do
+                    match segment with
+                    | DecimatedPrimitiveRawDataBlock (start, length) ->
+                        stream.Seek(int64 start, SeekOrigin.Begin)
+                        |> ignore
+
+                        let! _ = stream.ReadAsync(memory.Slice(position * size, int length * size), ct)
+
+                        position <- position + int length
+                    | InterleavedPrimitiveRawDataBlock { Start = start
+                                                         Count = count
+                                                         Skip = skip } ->
+                        stream.Seek(int64 start, SeekOrigin.Begin)
+                        |> ignore
+
+                        for _ = 0 to int count - 1 do
+                            let! _ = stream.ReadAsync(memory.Slice(position * size, size), ct)
+
+                            stream.Seek(int64 skip, SeekOrigin.Current)
+                            |> ignore
+
+                            position <- position + 1
+
+                memory.Span.Reverse()
+                Array.Reverse data
+                return data
+            }
+
+    let readStringRawDataAsync ct (stream: Stream) (segments: (uint64 * uint64 * uint64) seq) bigEndian =
+        backgroundTask {
+            let offsets =
+                Seq.map (fun (_, length, _) -> length) segments
+                |> Seq.max
+                |> int
+                |> ArrayPool<uint>.Shared.Rent
+
+            let data =
+                Seq.map (fun (_, _, bytes) -> bytes) segments
+                |> Seq.max
+                |> int
+                |> ArrayPool<uint8>.Shared.Rent
+
+            let strings =
+                Seq.sumBy (fun (_, length, _) -> length) segments
+                |> int
+                |> Array.zeroCreate<string>
+
+            let mutable position = 0
+
+            for (start, length, bytes) in segments do
+                stream.Seek(int64 start, SeekOrigin.Begin)
+                |> ignore
+
+                let offsetsMemory = offsets.AsMemory().Slice(0, int length)
+
+                let offsetsByteMemory = cast<uint, uint8> offsetsMemory
+
+                let! _ = stream.ReadAsync(offsetsByteMemory, ct)
+
+                if bigEndian then
+                    offsetsByteMemory.Span.Reverse()
+                    offsetsMemory.Span.Reverse()
+
+                let mutable dataMemory = data.AsMemory().Slice(0, int bytes)
+
+                let! _ = stream.ReadAsync(dataMemory, ct)
+
+                let mutable offset = 0
+
+                for index = 0 to offsetsMemory.Length - 1 do
+                    strings.[position] <-
+                        Encoding.UTF8.GetString(
+                            dataMemory
+                                .Slice(0, int offsets.[index] - offset)
+                                .ToArray()
+                        )
+
+                    dataMemory <- dataMemory.Slice(int offsets.[index] - offset)
+                    offset <- int offsets.[index]
+                    position <- position + 1
+
+            ArrayPool<byte>.Shared.Return(data, false)
+            ArrayPool<uint>.Shared.Return(offsets, false)
+            return strings
+        }

+ 240 - 0
Tdms/Segment.fs

@@ -0,0 +1,240 @@
+namespace FSharp.Data.Tdms
+
+open System
+open System.Buffers
+open System.IO
+open System.Numerics
+open System.Runtime.InteropServices
+
+type Tag =
+    | Tdsm = 1834173524u
+    | Tdsh = 1750287444u
+
+[<Flags>]
+type TableOfContents =
+    | ContainsMetaData = 2u
+    | ContainsRawData = 8u
+    | ContainsDaqMxRawData = 128u
+    | ContainsInterleavedData = 32u
+    | ContainsBigEndianData = 64u
+    | ContainsNewObjectList = 4u
+
+type Version =
+    | ``1.0`` = 4712u
+    | ``2.0`` = 4713u
+
+[<Struct>]
+type LeadIn =
+    { Tag: Tag
+      TableOfContents: TableOfContents
+      Version: Version
+      NextSegmentOffset: uint64
+      RawDataOffset: uint64 }
+
+module Segment =
+
+    let readLeadIn (buffer: byte ReadOnlySpan byref) =
+        let tag =
+            Buffer.readUInt &buffer false
+            |> LanguagePrimitives.EnumOfValue<uint, Tag>
+
+        let tableOfContents =
+            Buffer.readUInt &buffer false
+            |> LanguagePrimitives.EnumOfValue<uint, TableOfContents>
+
+        let bigEndian =
+            tableOfContents.HasFlag(TableOfContents.ContainsBigEndianData)
+
+        { Tag = tag
+          TableOfContents = tableOfContents
+          Version =
+              Buffer.readUInt &buffer bigEndian
+              |> LanguagePrimitives.EnumOfValue<uint, Version>
+          NextSegmentOffset = Buffer.readUInt64 &buffer bigEndian
+          RawDataOffset = Buffer.readUInt64 &buffer bigEndian }
+
+    let readPropertyValue (buffer: byte ReadOnlySpan byref) bigEndian propertyType =
+        if propertyType = typeof<unit> then
+            buffer <- buffer.Slice 1
+            box ()
+        elif propertyType = typeof<bool> then
+            let value = box (buffer.[0] <> 0uy)
+            buffer <- buffer.Slice 1
+            value
+        elif propertyType = typeof<int8> then
+            let value =
+                box (MemoryMarshal.Cast<uint8, int8> buffer).[0]
+
+            buffer <- buffer.Slice 1
+            value
+        elif propertyType = typeof<int16> then
+            Buffer.readInt16 &buffer bigEndian |> box
+        elif propertyType = typeof<int> then
+            Buffer.readInt &buffer bigEndian |> box
+        elif propertyType = typeof<int64> then
+            Buffer.readInt64 &buffer bigEndian |> box
+        elif propertyType = typeof<uint8> then
+            let value = box buffer.[0]
+            buffer <- buffer.Slice 1
+            value
+        elif propertyType = typeof<uint16> then
+            Buffer.readUInt16 &buffer bigEndian |> box
+        elif propertyType = typeof<uint> then
+            Buffer.readUInt &buffer bigEndian |> box
+        elif propertyType = typeof<uint64> then
+            Buffer.readUInt64 &buffer bigEndian |> box
+        elif propertyType = typeof<float32> then
+            Buffer.readFloat32 &buffer bigEndian |> box
+        elif propertyType = typeof<float> then
+            Buffer.readFloat &buffer bigEndian |> box
+        elif propertyType = typeof<struct (float32 * float32)> then
+            struct (Buffer.readFloat32 &buffer bigEndian |> float, Buffer.readFloat32 &buffer bigEndian |> float)
+            |> box
+        elif propertyType = typeof<Complex> then
+            Complex(Buffer.readFloat &buffer bigEndian, Buffer.readFloat &buffer bigEndian)
+            |> box
+        elif propertyType = typeof<float80> then
+            Buffer.readFloat80 &buffer bigEndian |> box
+        elif propertyType = typeof<Timestamp> then
+            (if bigEndian then
+                 { SecondsSinceNiEpoch = Buffer.readInt64 &buffer bigEndian
+                   FractionsOfASecond = Buffer.readUInt64 &buffer bigEndian }
+             else
+                 { FractionsOfASecond = Buffer.readUInt64 &buffer bigEndian
+                   SecondsSinceNiEpoch = Buffer.readInt64 &buffer bigEndian })
+            |> box
+        elif propertyType = typeof<string> then
+            Buffer.readString &buffer bigEndian |> box
+        else
+            failwithf "Property type not implemented: %A" propertyType
+
+    let createObject name (objects: _ ResizeArray) bigEndian =
+        match Seq.tryFind (fun object -> object.Name = name) objects with
+        | Some object -> object
+        | None ->
+            let object =
+                { Name = name
+                  BigEndian = bigEndian
+                  RawDataBlocks = None
+                  Properties = ResizeArray() }
+
+            objects.Add object
+            object
+
+    let readMetaData objects rawDataOffset nextSegmentOffset (buffer: _ byref) bigEndian interleaved =
+        let objectCount = Buffer.readInt &buffer bigEndian
+        let newOrUpdatedObjects = Array.zeroCreate objectCount
+        let objectsWithRawData = ResizeArray()
+        let mutable rawDataPosition = rawDataOffset
+
+        for i = 0 to objectCount - 1 do
+            let object =
+                createObject (Buffer.readString &buffer bigEndian) objects bigEndian
+
+            newOrUpdatedObjects.[i] <- object
+
+            let rawDataSkip =
+                Object.readRawDataIndex object rawDataPosition &buffer bigEndian interleaved
+
+            rawDataPosition <- rawDataPosition + rawDataSkip
+
+            if rawDataSkip > 0uL then
+                objectsWithRawData.Add object
+
+            let propertyCount = Buffer.readUInt &buffer bigEndian |> int
+
+            for j = 0 to propertyCount - 1 do
+                let propertyName = Buffer.readString &buffer bigEndian
+                let propertyType = Buffer.readType &buffer bigEndian
+
+                let propertyValue =
+                    readPropertyValue &buffer bigEndian propertyType
+
+                let property =
+                    { Name = propertyName
+                      Type = propertyType
+                      Raw = propertyValue }
+
+                match Seq.tryFindIndex (fun (property: Property) -> property.Name = propertyName) object.Properties with
+                | None -> object.Properties.Add property
+                | Some index -> object.Properties.[index] <- property
+
+        let sizes, rawDataBlocksToUpdate =
+            Seq.choose
+                (fun ({ RawDataBlocks = rawDataBlocks }: FSharp.Data.Tdms.Object) ->
+                    Option.bind
+                        (fun rawDataBlocks' ->
+                            match rawDataBlocks' with
+                            | PrimitiveRawDataBlocks (ty, primitiveRawDataBlockArray) ->
+                                Seq.tryLast primitiveRawDataBlockArray
+                                |> Option.map
+                                    (function
+                                    | DecimatedPrimitiveRawDataBlock (start, count) ->
+                                        uint64 (Marshal.SizeOf ty) * count, rawDataBlocks'
+                                    | InterleavedPrimitiveRawDataBlock { Start = start; Count = count } ->
+                                        uint64 (Marshal.SizeOf ty) * count, rawDataBlocks')
+                            | StringRawDataBlocks stringRawDataBlockArray ->
+                                Seq.tryLast stringRawDataBlockArray
+                                |> Option.map (fun (_, _, bytes) -> bytes, rawDataBlocks'))
+                        rawDataBlocks)
+                objectsWithRawData
+            |> Seq.toArray
+            |> Array.unzip
+
+        let chunkSize = Array.sum sizes
+
+        if chunkSize > 0uL then
+            let chunkOffsets =
+                [ chunkSize .. chunkSize .. (nextSegmentOffset - rawDataOffset) - chunkSize ]
+
+            for rawDataBlocks in rawDataBlocksToUpdate do
+                match rawDataBlocks with
+                | PrimitiveRawDataBlocks (_, primitiveRawDataBlockArray) ->
+                    Seq.tryLast primitiveRawDataBlockArray
+                    |> Option.iter
+                        (function
+                        | DecimatedPrimitiveRawDataBlock (start, count) ->
+                            primitiveRawDataBlockArray.AddRange(
+                                List.map
+                                    (fun chunkOffset -> DecimatedPrimitiveRawDataBlock(start + chunkOffset, count))
+                                    chunkOffsets
+                            )
+                        | InterleavedPrimitiveRawDataBlock ({ Start = start } as block) ->
+                            primitiveRawDataBlockArray.AddRange(
+                                List.map
+                                    (fun chunkOffset ->
+                                        InterleavedPrimitiveRawDataBlock
+                                            { block with
+                                                  Start = start + chunkOffset })
+                                    chunkOffsets
+                            ))
+                | StringRawDataBlocks stringRawDataBlockArray ->
+                    Seq.tryLast stringRawDataBlockArray
+                    |> Option.iter
+                        (fun (start, count, bytes) ->
+                            stringRawDataBlockArray.AddRange(
+                                List.map (fun chunkOffset -> start + chunkOffset, count, bytes) chunkOffsets
+                            ))
+
+        if interleaved then
+            Array.iter
+                (fun ({ RawDataBlocks = rawDataBlocks }: FSharp.Data.Tdms.Object) ->
+                    match rawDataBlocks with
+                    | None -> ()
+                    | Some (StringRawDataBlocks _) -> ()
+                    | Some (PrimitiveRawDataBlocks (ty, primitiveRawDataBlocksArray)) ->
+                        Seq.tryLast primitiveRawDataBlocksArray
+                        |> Option.iter
+                            (function
+                            | DecimatedPrimitiveRawDataBlock _ -> ()
+                            | InterleavedPrimitiveRawDataBlock interleavedPrimitiveRawDataBlock ->
+                                interleavedPrimitiveRawDataBlock.Skip <-
+                                    (rawDataPosition - rawDataOffset)
+                                    - uint64 (Marshal.SizeOf ty)))
+                newOrUpdatedObjects
+
+    let tdsh =
+        [| 0x54uy
+           0x44uy
+           0x53uy
+           0x68uy |]

+ 26 - 0
Tdms/Timestamp.fs

@@ -0,0 +1,26 @@
+namespace FSharp.Data.Tdms
+
+open System
+open System.Runtime.CompilerServices
+
+#if !IS_DESIGNTIME
+[<Struct; IsReadOnly>]
+#else
+[<Struct>]
+#endif
+type Timestamp =
+    { FractionsOfASecond: uint64
+      SecondsSinceNiEpoch: int64 }
+
+module Timestamp =
+  
+  let niEpochDateTime = DateTime(1904, 1, 1, 0, 0, 0, DateTimeKind.Utc)
+
+  let niEpochDateTimeOffset = DateTimeOffset(1904, 1, 1, 0, 0, 0, TimeSpan.Zero)
+
+  let toTimeSpan { SecondsSinceNiEpoch = secondsSinceNiEpoch; FractionsOfASecond = fractionsOfASecond } =
+    TimeSpan.FromSeconds (float secondsSinceNiEpoch) + TimeSpan.FromSeconds (float fractionsOfASecond / float UInt64.MaxValue)
+
+  let toDateTime timestamp = (niEpochDateTime + toTimeSpan timestamp).ToLocalTime()
+
+  let toDateTimeOffset timestamp = (niEpochDateTimeOffset + toTimeSpan timestamp).ToLocalTime()

+ 4 - 4
Timer/Timer/Interop.cs

@@ -43,16 +43,16 @@ namespace Haukcode.HighResolutionTimer
             public Timespec it_value=new Timespec();       /* timer expiration */
         };
 
-        [DllImport(LibcLibrary, SetLastError = true)]
+        [DllImport(LibcLibrary, SetLastError = true,EntryPoint = "timerfd_create")]
         internal static extern int Timerfd_Create(ClockIds clockId, int flags);
 
-        [DllImport(LibcLibrary, SetLastError = true)]
+        [DllImport(LibcLibrary, SetLastError = true,EntryPoint = "timerfd_settime")]
         internal static extern int Timerfd_Settime(int fd, int flags, Itimerspec new_value, Itimerspec old_value);
 
-        [DllImport(LibcLibrary, SetLastError = true)]
+        [DllImport(LibcLibrary, SetLastError = true,EntryPoint ="read")]
         internal static extern int Read(int fd, IntPtr buf, int count);
 
-        [DllImport(LibcLibrary)]
+        [DllImport(LibcLibrary,EntryPoint ="close")]
         internal static extern int Close(int fd);
     }
 }

+ 4 - 4
Timer/Timer/Interop64.cs

@@ -43,16 +43,16 @@ namespace Haukcode.HighResolutionTimer
             public Timespec64 it_value= new Timespec64();       /* timer expiration */
         };
 
-        [DllImport(LibcLibrary, SetLastError = true)]
+        [DllImport(LibcLibrary, SetLastError = true, EntryPoint = "timerfd_create")]
         internal static extern int Timerfd_Create(ClockIds clockId, int flags);
 
-        [DllImport(LibcLibrary, SetLastError = true)]
+        [DllImport(LibcLibrary, SetLastError = true, EntryPoint = "timerfd_settime")]
         internal static extern int Timerfd_Settime(int fd, int flags, Itimerspec64 new_value, Itimerspec64 old_value);
 
-        [DllImport(LibcLibrary, SetLastError = true)]
+        [DllImport(LibcLibrary, SetLastError = true, EntryPoint = "read")]
         internal static extern long Read(int fd, IntPtr buf, ulong count);
 
-        [DllImport(LibcLibrary)]
+        [DllImport(LibcLibrary, EntryPoint = "close")]
         internal static extern int Close(int fd);
     }
 }

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません