/// <summary> /// Построение комплексной сонограммы /// </summary> /// <param name="FFT_S"> Вектор входных данных /// ("левый" и "правый" каналы - чет./нечет.). </param> /// <param name="FFT_S_Offset"> Смещение данных для анализа во /// входном векторе FFT_S. </param> /// <param name="useTaperWindow"> Использовать взвешивающее окно? </param> /// <param name="recoverAfterTaperWindow"> Аннигилировать действие /// взвешивающего окна на обратном проходе? </param> /// <param name="useNorm"> Использовать нормализацию 1/N? </param> /// <param name="direction"> Направление преобразования (true - прямое). </param> /// <param name="usePolyphase"> Использовать полифазное FFT? </param> /// <param name="remainArrayItemsLRCount"> Остаток необработанных данных в исходном /// массиве (количество элементов, включая Re/Im). </param> /// <param name="fftObj"> Объект FFT, для которого вызывается функция. </param> /// <returns> Сонограмма. </returns> public static double[][] Process(double[] FFT_S, int FFT_S_Offset, bool useTaperWindow, bool recoverAfterTaperWindow, bool useNorm, bool direction, bool usePolyphase, out int remainArrayItemsLRCount, ExactFFT.CFFT_Object fftObj) { int plotRowsCount, frameOffset; double[][] FFT_T; if (FFT_S == null) { throw new Exception("ExactPlotter::Process(): (FFT_S == null)"); } // Вычисляем количество строк сонограммы... plotRowsCount = GetPlotRowsCount(FFT_S, FFT_S_Offset, out remainArrayItemsLRCount, fftObj); // Обрабатываем все фреймы... FFT_T = new double[plotRowsCount][]; Parallel.For(0, plotRowsCount, frame => { // Умножение на 2 треб. для real -> complex FFT_T[frame] = new double[fftObj.N << 1]; frameOffset = FFT_S_Offset + frame * fftObj.WindowStep << 1; ExactFFT.CFFT_Process(FFT_S, frameOffset, FFT_T[frame], useTaperWindow, recoverAfterTaperWindow, useNorm, direction, usePolyphase, fftObj); }); return(FFT_T); }
/// <summary> /// Выделение одного момента времени из сонограммы /// </summary> /// <param name="sonogram"> Исходная сонограмма. </param> /// <param name="time"> Время среза (кадра FFT). </param> /// <param name="timeIdx"> Индекс временного среза (кадра FFT). </param> /// <param name="fftObj"> Объект FFT, для которого вызывается функция. </param> /// <param name="sampleRate"> Частота семплирования. </param> /// <returns> Выделенный поддиапазон сонограммы. </returns> public static double[] TimeSlice(double[][] sonogram, double time, out int timeIdx, ExactFFT.CFFT_Object fftObj, int sampleRate) { // Выделяем один временной срез (выделяем вектор, привязанный к ОДНОЙ строке)... return(SubTime(sonogram, time, time, out timeIdx, out timeIdx, fftObj, sampleRate)[0]); }
/// <summary> /// Вычисление количества строк в "водопаде" /// </summary> /// <param name="FFT_S"> Вектор входных данных /// ("левый" и "правый" каналы - чет./нечет.). </param> /// <param name="FFT_S_Offset"> Смещение данных для анализа во /// входном векторе FFT_S. </param> /// <param name="remainArrayItemsLRCount"> Остаток необработанных данных в исходном /// массиве (количество элементов, включая Re/Im). </param> /// <param name="fftObj"> Объект FFT, для которого вызывается функция. </param> public static int GetPlotRowsCount(double[] FFT_S, int FFT_S_Offset, out int remainArrayItemsLRCount, ExactFFT.CFFT_Object fftObj) { if (FFT_S == null) { throw new Exception("ExactPlotter::GetPlotRowsCount(): (FFT_S == null)"); } return(GetPlotRowsCount(FFT_S.Length, FFT_S_Offset, out remainArrayItemsLRCount, fftObj)); }
/// <summary> /// Выделение поддиапазона времени из сонограммы /// </summary> /// <param name="sonogram"> Исходная сонограмма. </param> /// <param name="startTime"> Стартовый кадр FFT (время строки сонограммы). </param> /// <param name="finishTime"> Конечный кадр FFT (время строки сонограммы). </param> /// <param name="startFrameIdx"> Стартовый кадр FFT (строка сонограммы). </param> /// <param name="finishFrameIdx"> Конечный кадр FFT (строка сонограммы). </param> /// <param name="fftObj"> Объект FFT, для которого вызывается функция. </param> /// <param name="sampleRate"> Частота семплирования. </param> /// <param name="harmReverse"> Требуется реверс гармоник? </param> /// <returns> Выделенный поддиапазон сонограммы. </returns> public static double[][] SubTime(double[][] sonogram, double startTime, double finishTime, out int startFrameIdx, out int finishFrameIdx, ExactFFT.CFFT_Object fftObj, int sampleRate, bool harmReverse = false) { double frameDuration, sleepCoeff, timeSliceDuration; GetFrameParameters(fftObj.N, fftObj.WindowStep, sampleRate, out frameDuration, out sleepCoeff, out timeSliceDuration); startFrameIdx = (int)(startTime / timeSliceDuration); finishFrameIdx = (int)(finishTime / timeSliceDuration); return(SubTime(sonogram, startFrameIdx, finishFrameIdx, harmReverse)); }
/// <summary> /// Выделение поддиапазона гармоник из сонограммы /// </summary> /// <param name="sonogram"> Исходная сонограмма. </param> /// <param name="lowFreq"> Частота нижней гармоники выделяемого диапазона. </param> /// <param name="highFreq"> Частота верхней гармоники выделяемого диапазона. </param> /// <param name="lowHarmIdx"> Нижний индекс гармоники. </param> /// <param name="highHarmIdx"> Верхний индекс гармоники. </param> /// <param name="fftObj"> Объект FFT, для которого вызывается функция. </param> /// <param name="sampleRate"> Частота семплирования. </param> /// <param name="harmReverse"> Требуется реверс гармоник? </param> /// <returns> Выделенный поддиапазон сонограммы. </returns> public static double[][] SubBand(double[][] sonogram, double lowFreq, double highFreq, out int lowHarmIdx, out int highHarmIdx, ExactFFT.CFFT_Object fftObj, int sampleRate, bool harmReverse = false) { if (sonogram == null) { throw new Exception("ExactPlotter::SubBand(): (sonogram == null)"); } lowHarmIdx = (int)ExactFFT.FFT_Node(lowFreq, sampleRate, fftObj.N, fftObj.IsComplex); highHarmIdx = (int)ExactFFT.FFT_Node(highFreq, sampleRate, fftObj.N, fftObj.IsComplex); return(SubBand(sonogram, lowHarmIdx, highHarmIdx, harmReverse)); }
/// <summary> /// Извлечение данных из комплексной сонограммы (L+R) /// </summary> /// <param name="FFT_T"> Выходной набор векторов коэффициентов. </param> /// <param name="usePolyphase"> Использовать полифазное FFT? </param> /// <param name="fftObj"> Объект FFT, для которого вызывается функция. </param> /// <returns> Результат разбора выходных данных CFFT. </returns> public static CFFT_ExploreResult Explore(double[][] FFT_T, bool usePolyphase, ExactFFT.CFFT_Object fftObj) { int plotRowsCount; CFFT_ExploreResult res; res = new CFFT_ExploreResult(); if (FFT_T == null) { throw new Exception("ExactPlotter::Explore(): (FFT_T == null)"); } // Считываем количество строк, которые имеет сонограмма... plotRowsCount = FFT_T.Length; // Подготавливаем выходные массивы... res.MagL = new double[plotRowsCount][]; res.MagR = new double[plotRowsCount][]; res.ACH = new double[plotRowsCount][]; res.ArgL = new double[plotRowsCount][]; res.ArgR = new double[plotRowsCount][]; res.PhaseLR = new double[plotRowsCount][]; // Работаем по всем строкам сонограммы... Parallel.For(0, plotRowsCount, frame => { // Количество гармоник в два раза меньше размера кадра FFT res.MagL[frame] = new double[fftObj.N >> 1]; res.MagR[frame] = new double[fftObj.N >> 1]; res.ACH[frame] = new double[fftObj.N >> 1]; res.ArgL[frame] = new double[fftObj.N >> 1]; res.ArgR[frame] = new double[fftObj.N >> 1]; res.PhaseLR[frame] = new double[fftObj.N >> 1]; // Извлечение данных FFT из комплексной сонограммы ExactFFT.CFFT_Explore(FFT_T[frame], res.MagL[frame], res.MagR[frame], res.ACH[frame], res.ArgL[frame], res.ArgR[frame], res.PhaseLR[frame], usePolyphase, fftObj); }); return(res); }
/// <summary> /// Вычисление количества строк в "водопаде" /// </summary> /// <param name="FFT_S"> Вектор входных данных /// ("левый" и "правый" каналы - чет./нечет.). </param> /// <param name="FFT_S_Offset"> Смещение данных для анализа во /// входном векторе FFT_S. </param> /// <param name="remainArrayItemsLRCount"> Остаток необработанных данных в исходном /// массиве (количество элементов, включая Re/Im). </param> /// <param name="fftObj"> Объект FFT, для которого вызывается функция. </param> public static int GetPlotRowsCount(int FFT_S_Length, int FFT_S_Offset, out int remainArrayItemsLRCount, ExactFFT.CFFT_Object fftObj) { int stepAreaLength_Real, nSteps, areaWithoutFFT_Real, intersectFFTArea_Real; // Вычисляем размер поля данных для "поглощения" шагами FFT. // Деление на 2 требуется для перехода от комплексной "чет-нечет" формы к "вещественной" stepAreaLength_Real = ((FFT_S_Length - FFT_S_Offset) >> 1) - fftObj.N; // Невозможно обработать столь малый блок (некуда шагать)! if (stepAreaLength_Real < 0) { throw new Exception("ExactPlotter::GetPlotRowsCount: (stepAreaLength_Real < 0)"); } // Количество шагов взвешивающим окном равно размеру области, первоначально не лежащей // под окном FFT, деленному на шаг окна FFT. Так-как шаги делаются целиком, нельзя // осуществить полшага, поэтому берется только целая часть числа. // Оставшееся количество попадает в необработанный остаток! // +1 учитывает "нулевой" шаг, при первоначальном положении на исходных данных. nSteps = ((stepAreaLength_Real / fftObj.WindowStep) + 1); // Когда вычисляется необработанный остаток, следует понимать, что следующий шаг FFT // покроет остаток, и выйдет даже за пределы поля исходных данных, поэтому процесс // и останавливается. Но если сделать шаг FFT, то оно захватит не только всё поле // необработанных данных, если его шаг меньше его самого, произойдет частичный // захват области под ним самим (в состоянии итерации при останове). // Размер этой области равен размеру окна FFT минус его шаг. И эта величина также // требует, чтобы её учитывали! // Если шаг FFT больше его самого, получится отрицательная величина. areaWithoutFFT_Real = stepAreaLength_Real - ((nSteps - 1) * fftObj.WindowStep); intersectFFTArea_Real = fftObj.N - fftObj.WindowStep; remainArrayItemsLRCount = (intersectFFTArea_Real + areaWithoutFFT_Real) << 1; // Отрицательный остаток обработки дает ошибку при построении сонограммы! if (remainArrayItemsLRCount < 0) { throw new Exception("ExactPlotter::GetPlotRowsCount: (remainArrayItemsLRCount < 0)"); } return(nSteps); }
/// <summary> /// Перевод значений массива double в форму dB /// </summary> /// <param name="sonogram"> Данные для обработки. </param> /// <param name="zero_db_level"> Значение "нулевого" уровня. </param> /// <param name="fftObj"> Объект FFT, для которого вызывается функция. </param> public static void dB_Scale(double[][] sonogram, double zero_db_level, ExactFFT.CFFT_Object fftObj) { int plotRowsCount; if (sonogram == null) { throw new Exception("ExactPlotter::dB_Scale(): (sonogram == null)"); } // Считываем количество строк, которые имеет сонограмма... plotRowsCount = sonogram.Length; // Работаем по всем строкам сонограммы... Parallel.For(0, plotRowsCount, frame => { ExactFFT.dB_Scale(sonogram[frame], zero_db_level, fftObj); }); }
/// <summary> /// Выделение одной гармоники из сонограммы /// </summary> /// <param name="sonogram"> Исходная сонограмма. </param> /// <param name="harmFreq"> Частота выделяемой гармоники. </param> /// <param name="harmIdx"> Индекс выделяемой гармоники. </param> /// <param name="fftObj"> Объект FFT, для которого вызывается функция. </param> /// <param name="sampleRate"> Частота семплирования. </param> /// <returns> Выделенный поддиапазон сонограммы. </returns> public static double[] HarmSlice(double[][] sonogram, double harmFreq, out int harmIdx, ExactFFT.CFFT_Object fftObj, int sampleRate) { double[] harmVector; double[][] harmColumn; // Выделяем ОДНУ гармонику (она размещается в одном столбце и множестве строк)... harmColumn = SubBand(sonogram, harmFreq, harmFreq, out harmIdx, out harmIdx, fftObj, sampleRate); // Данные вектора-столбца размещаем в векторе-строке... harmVector = new double[harmColumn.Length]; // Работаем по всем строкам сонограммы... Parallel.For(0, harmColumn.Length, frame => { harmVector[frame] = harmColumn[frame][0]; }); //...и возвращаем результат return(harmVector); }
/// <summary> /// Исследование результатов комплексного FFT (идентично CFFT из MathCAD) /// </summary> /// <param name="FFT_T"> Выходной набор векторов коэффициентов. </param> /// <param name="usePolyphase"> Использовать полифазное FFT? </param> /// <param name="isMirror"> Зеркальное отображение спектра? </param> /// <param name="fftObj"> Объект FFT, для которого вызывается функция. </param> /// <returns> Результат разбора выходных данных CFFT. </returns> public static CFFT_ExploreResult ComplexExplore(double[][] FFT_T, bool usePolyphase, bool isMirror, ExactFFT.CFFT_Object fftObj) { int plotRowsCount; CFFT_ExploreResult res; res = new CFFT_ExploreResult(); if (FFT_T == null) { throw new Exception("ExactPlotter::ComplexExplore(): (FFT_T == null)"); } // Считываем количество строк, которые имеет сонограмма... plotRowsCount = FFT_T.Length; // Подготавливаем выходные массивы... res.Mag = new double[plotRowsCount][]; res.Arg = new double[plotRowsCount][]; // Работаем по всем строкам сонограммы... Parallel.For(0, plotRowsCount, frame => { // Количество гармоник равно размеру кадра FFT res.Mag[frame] = new double[fftObj.N]; res.Arg[frame] = new double[fftObj.N]; // Извлечение данных FFT из комплексной сонограммы (режим COMPLEX) ExactFFT.CFFT_ComplexExplore(FFT_T[frame], res.Mag[frame], res.Arg[frame], usePolyphase, isMirror, fftObj); }); return(res); }