#region Copyright and License
/*
This file is part of FFTW.NET, a wrapper around the FFTW library
for the .NET framework.
Copyright (C) 2017 Tobias Meyer
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#endregion
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Text;
using NativeLibraryLoader;
namespace FFTW.NET
{
public enum DftDirection : int
{
Forwards = -1,
Backwards = 1
}
[Flags]
public enum PlannerFlags : uint
{
Default = Measure,
///
/// tells FFTW to find an optimized plan by actually
/// computing several FFTs and measuring their execution time.
/// Depending on your machine, this can take some time (often a few seconds).
///
Measure = (0U),
///
/// is like ,
/// but considers an even wider range of algorithms,
/// including many that we think are unlikely to be fast,
/// to produce the most optimal plan but with a substantially increased planning time.
///
Exhaustive = (1U << 3),
///
/// is like ,
/// but considers a wider range of algorithms and often produces
/// a “more optimal” plan (especially for large transforms),
/// but at the expense of several times longer planning time
/// (especially for large transforms).
///
Patient = (1U << 5),
///
/// specifies that,
/// instead of actual measurements of different algorithms,
/// a simple heuristic is used to pick a (probably sub-optimal) plan quickly.
/// With this flag, the input/output arrays are not overwritten during planning.
///
Estimate = (1U << 6),
///
/// is a special planning mode in which
/// the plan is only created if wisdom is available for the given problem,
/// and otherwise a null plan is returned. This can be combined
/// with other flags, e.g. ' | '
/// creates a plan only if wisdom is available that was created in
/// or mode.
/// The flag is intended for users who need to
/// detect whether wisdom is available; for example, if wisdom is not
/// available one may wish to allocate new arrays for planning so that
/// user data is not overwritten.
///
WisdomOnly = (1U << 21)
}
public static class FftwInterop
{
public delegate bool fftw_init_threads();
private static readonly string windowsdllname = "libfftw3";
private static readonly string linuxdllname = "libfftw3.so";
static FftwInterop()
{
}
public static void Initialize()
{
if (_NativeLoader != null) return;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) _NativeLoader = new NativeLibraryLoader.NativeLoader(windowsdllname);
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
_NativeLoader = new NativeLibraryLoader.NativeLoader(linuxdllname);
}
else
{
throw new PlatformNotSupportedException();
}
_NativeLoader.SearchDirectories.Add(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs"));
_NativeLoader.Init();
_version = GetVersionAndInitialize();
}
[AllowNull]
private static NativeLibraryLoader.NativeLoader _NativeLoader;
private static Version _version = new Version();
public static Version Version => _version;
public static bool IsAvailable => _version != null;
public static T GetDelegate() where T : Delegate
{
return _NativeLoader.LoadFunction(typeof(T).Name);
}
internal static object Lock
{
get
{
if (!IsAvailable)
throw new InvalidOperationException($"{nameof(FftwInterop.IsAvailable)} is false.");
return _version;
}
}
public delegate void WriteCharHandler(byte c, IntPtr ptr);
static Version GetVersionAndInitialize()
{
try
{
//GetDelegate()();
}
catch (DllNotFoundException) { return new Version(); }
string version = GetVersion();
return new Version(version);
}
public delegate int fftw_alignment_of(IntPtr ptr);
public delegate void fftw_free(IntPtr ptr);
public delegate void fftw_plan_with_nthreads(int nthreads);
public delegate IntPtr fftw_plan_dft_r2c(int rank, [MarshalAs(UnmanagedType.LPArray)] int[] n, IntPtr arrIn, IntPtr arrOut, PlannerFlags flags);
public delegate void fftw_forget_wisdom();
public delegate IntPtr fftw_malloc(IntPtr size);
public delegate bool fftw_export_wisdom_to_filename([MarshalAs(UnmanagedType.LPStr)] string filename);
public delegate IntPtr fftw_plan_dft(int rank, [MarshalAs(UnmanagedType.LPArray)] int[] n, IntPtr arrIn, IntPtr arrOut, DftDirection direction, PlannerFlags flags);
public delegate void fftw_execute(IntPtr plan);
public delegate IntPtr fftw_plan_dft_c2r(int rank, [MarshalAs(UnmanagedType.LPArray)] int[] n, IntPtr arrIn, IntPtr arrOut, PlannerFlags flags);
public delegate void fftw_destroy_plan(IntPtr plan);
public delegate bool fftw_import_wisdom_from_string([MarshalAs(UnmanagedType.LPStr)] string wisdom);
public delegate bool fftw_import_wisdom_from_filename([MarshalAs(UnmanagedType.LPStr)] string filename);
public delegate void fftw_export_wisdom([MarshalAs(UnmanagedType.FunctionPtr)] WriteCharHandler writeChar, IntPtr data);
public static string fftw_export_wisdom_to_string()
{
// We cannot use the fftw_export_wisdom_to_string function here
// because we have no way of releasing the returned memory.
StringBuilder sb = new StringBuilder();
FftwInterop.WriteCharHandler writeChar = (c, ptr) => sb.Append((char)c);
GetDelegate()(writeChar, IntPtr.Zero);
return sb.ToString();
}
static string GetVersion()
{
const string VersionPrefix = "fftw-";
const byte WhiteSpace = (byte)' ';
byte[] prefix = Encoding.ASCII.GetBytes(VersionPrefix);
int i = 0;
StringBuilder sb = new StringBuilder();
FftwInterop.WriteCharHandler writeChar = (c, ptr) =>
{
if (i < 0)
return;
if (i == VersionPrefix.Length)
{
if (c == WhiteSpace)
i = -1;
else
sb.Append((char)c);
}
else if (c == prefix[i])
i++;
else
i = 0;
};
// This is only called on initialization, so no synchronization/lock is required
GetDelegate()(writeChar, IntPtr.Zero);
return sb.ToString();
}
}
}