Sound_and_Spectrogram.cpp 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. /* Sound_and_Spectrogram.cpp
  2. *
  3. * Copyright (C) 1992-2011,2014,2015,2017,2018 Paul Boersma
  4. *
  5. * This code is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or (at
  8. * your option) any later version.
  9. *
  10. * This code is distributed in the hope that it will be useful, but
  11. * WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  13. * See the GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this work. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /*
  19. * pb 2002/07/16 GPL
  20. * pb 2003/07/02 checks on NUMrealft
  21. * pb 2003/11/30 Sound_to_Spectrogram_windowShapeText
  22. * pb 2004/03/13 bins are a fixed number of frequency samples wide;
  23. * this improves the positioning of peaks; thanks to Gabriel Beckers for his persistence
  24. * pb 2004/10/18 use of FFT tables speeds everything up by a factor of 2.5
  25. * pb 2004/10/20 progress bar
  26. * pb 2006/12/30 new Sound_create API
  27. * pb 2007/01/01 compatible with stereo sounds
  28. * pb 2007/12/06 enums
  29. * pb 2008/01/19 double
  30. * pb 2010/02/26 fixed a message
  31. * pb 2011/06/06 C++
  32. */
  33. #include "Sound_and_Spectrogram.h"
  34. #include "NUM2.h"
  35. #include "enums_getText.h"
  36. #include "Sound_and_Spectrogram_enums.h"
  37. #include "enums_getValue.h"
  38. #include "Sound_and_Spectrogram_enums.h"
  39. autoSpectrogram Sound_to_Spectrogram (Sound me, double effectiveAnalysisWidth, double fmax,
  40. double minimumTimeStep1, double minimumFreqStep1, kSound_to_Spectrogram_windowShape windowType,
  41. double maximumTimeOversampling, double maximumFreqOversampling)
  42. {
  43. try {
  44. double nyquist = 0.5 / my dx;
  45. double physicalAnalysisWidth =
  46. windowType == kSound_to_Spectrogram_windowShape::GAUSSIAN ? 2.0 * effectiveAnalysisWidth : effectiveAnalysisWidth;
  47. double effectiveTimeWidth = effectiveAnalysisWidth / sqrt (NUMpi);
  48. double effectiveFreqWidth = 1.0 / effectiveTimeWidth;
  49. double minimumTimeStep2 = effectiveTimeWidth / maximumTimeOversampling;
  50. double minimumFreqStep2 = effectiveFreqWidth / maximumFreqOversampling;
  51. double timeStep = minimumTimeStep1 > minimumTimeStep2 ? minimumTimeStep1 : minimumTimeStep2;
  52. double freqStep = minimumFreqStep1 > minimumFreqStep2 ? minimumFreqStep1 : minimumFreqStep2;
  53. double physicalDuration = my dx * my nx, windowssq = 0.0;
  54. /*
  55. Compute the time sampling.
  56. */
  57. integer nsamp_window = Melder_ifloor (physicalAnalysisWidth / my dx);
  58. integer halfnsamp_window = nsamp_window / 2 - 1;
  59. nsamp_window = halfnsamp_window * 2;
  60. if (nsamp_window < 1)
  61. Melder_throw (U"Your analysis window is too short: less than two samples.");
  62. if (physicalAnalysisWidth > physicalDuration)
  63. Melder_throw (U"Your sound is too short:\n"
  64. U"it should be at least as long as ",
  65. windowType == kSound_to_Spectrogram_windowShape::GAUSSIAN ? U"two window lengths." : U"one window length.");
  66. integer numberOfTimes = 1 + Melder_ifloor ((physicalDuration - physicalAnalysisWidth) / timeStep); // >= 1
  67. double t1 = my x1 + 0.5 * ((my nx - 1) * my dx - (numberOfTimes - 1) * timeStep); // centre of first frame
  68. /*
  69. Compute the frequency sampling of the FFT spectrum.
  70. */
  71. if (fmax <= 0.0 || fmax > nyquist) fmax = nyquist;
  72. integer numberOfFreqs = Melder_ifloor (fmax / freqStep);
  73. if (numberOfFreqs < 1) return autoSpectrogram ();
  74. integer nsampFFT = 1;
  75. while (nsampFFT < nsamp_window || nsampFFT < 2 * numberOfFreqs * (nyquist / fmax))
  76. nsampFFT *= 2;
  77. integer half_nsampFFT = nsampFFT / 2;
  78. /*
  79. Compute the frequency sampling of the spectrogram.
  80. */
  81. integer binWidth_samples = Melder_ifloor (freqStep * my dx * nsampFFT);
  82. if (binWidth_samples < 1) binWidth_samples = 1;
  83. double binWidth_hertz = 1.0 / (my dx * nsampFFT);
  84. freqStep = binWidth_samples * binWidth_hertz;
  85. numberOfFreqs = Melder_ifloor (fmax / freqStep);
  86. if (numberOfFreqs < 1) return autoSpectrogram ();
  87. autoSpectrogram thee = Spectrogram_create (my xmin, my xmax, numberOfTimes, timeStep, t1,
  88. 0.0, fmax, numberOfFreqs, freqStep, 0.5 * (freqStep - binWidth_hertz));
  89. autoVEC frame = VECzero (nsampFFT);
  90. autoNUMvector <double> spec (1, nsampFFT);
  91. autoNUMvector <double> window (1, nsamp_window);
  92. autoNUMfft_Table fftTable;
  93. NUMfft_Table_init (& fftTable, nsampFFT);
  94. autoMelderProgress progress (U"Sound to Spectrogram...");
  95. for (integer i = 1; i <= nsamp_window; i ++) {
  96. double nSamplesPerWindow_f = physicalAnalysisWidth / my dx;
  97. double phase = (double) i / nSamplesPerWindow_f; // 0 .. 1
  98. double value;
  99. switch (windowType) {
  100. case kSound_to_Spectrogram_windowShape::SQUARE:
  101. value = 1.0;
  102. break; case kSound_to_Spectrogram_windowShape::HAMMING:
  103. value = 0.54 - 0.46 * cos (2.0 * NUMpi * phase);
  104. break; case kSound_to_Spectrogram_windowShape::BARTLETT:
  105. value = 1.0 - fabs ((2.0 * phase - 1.0));
  106. break; case kSound_to_Spectrogram_windowShape::WELCH:
  107. value = 1.0 - (2.0 * phase - 1.0) * (2.0 * phase - 1.0);
  108. break; case kSound_to_Spectrogram_windowShape::HANNING:
  109. value = 0.5 * (1.0 - cos (2.0 * NUMpi * phase));
  110. break; case kSound_to_Spectrogram_windowShape::GAUSSIAN:
  111. {
  112. double imid = 0.5 * (double) (nsamp_window + 1), edge = exp (-12.0);
  113. phase = ((double) i - imid) / nSamplesPerWindow_f; // -0.5 .. +0.5
  114. value = (exp (-48.0 * phase * phase) - edge) / (1.0 - edge);
  115. break;
  116. }
  117. break; default:
  118. value = 1.0;
  119. }
  120. window [i] = value;
  121. windowssq += value * value;
  122. }
  123. double oneByBinWidth = 1.0 / windowssq / binWidth_samples;
  124. for (integer iframe = 1; iframe <= numberOfTimes; iframe ++) {
  125. double t = Sampled_indexToX (thee.get(), iframe);
  126. integer leftSample = Sampled_xToLowIndex (me, t), rightSample = leftSample + 1;
  127. integer startSample = rightSample - halfnsamp_window;
  128. integer endSample = leftSample + halfnsamp_window;
  129. Melder_assert (startSample >= 1);
  130. Melder_assert (endSample <= my nx);
  131. for (integer i = 1; i <= half_nsampFFT; i ++) {
  132. spec [i] = 0.0;
  133. }
  134. for (integer channel = 1; channel <= my ny; channel ++) {
  135. for (integer j = 1, i = startSample; j <= nsamp_window; j ++) {
  136. frame [j] = my z [channel] [i ++] * window [j];
  137. }
  138. for (integer j = nsamp_window + 1; j <= nsampFFT; j ++) frame [j] = 0.0f;
  139. Melder_progress (iframe / (numberOfTimes + 1.0),
  140. U"Sound to Spectrogram: analysis of frame ", iframe, U" out of ", numberOfTimes);
  141. /*
  142. Compute the Fast Fourier Transform of the frame.
  143. */
  144. NUMfft_forward (& fftTable, frame.get()); // complex spectrum
  145. /*
  146. Put the power spectrum in frame [1..half_nsampFFT + 1].
  147. */
  148. spec [1] += frame [1] * frame [1]; // DC component
  149. for (integer i = 2; i <= half_nsampFFT; i ++)
  150. spec [i] += frame [i + i - 2] * frame [i + i - 2] + frame [i + i - 1] * frame [i + i - 1];
  151. spec [half_nsampFFT + 1] += frame [nsampFFT] * frame [nsampFFT]; // Nyquist frequency. Correct??
  152. }
  153. if (my ny > 1 ) for (integer i = 1; i <= half_nsampFFT; i ++) {
  154. spec [i] /= my ny;
  155. }
  156. /*
  157. Bin into frame [1..nBands].
  158. */
  159. for (integer iband = 1; iband <= numberOfFreqs; iband ++) {
  160. integer leftsample = (iband - 1) * binWidth_samples + 1, rightsample = leftsample + binWidth_samples;
  161. long double power = 0.0;
  162. for (integer i = leftsample; i < rightsample; i ++) power += spec [i];
  163. thy z [iband] [iframe] = (double) power * oneByBinWidth;
  164. }
  165. }
  166. return thee;
  167. } catch (MelderError) {
  168. Melder_throw (me, U": spectrogram analysis not performed.");
  169. }
  170. }
  171. autoSound Spectrogram_to_Sound (Spectrogram me, double fsamp) {
  172. try {
  173. double dt = 1.0 / fsamp;
  174. integer n = Melder_ifloor ((my xmax - my xmin) / dt);
  175. if (n < 0) return autoSound ();
  176. autoSound you = Sound_create (1, my xmin, my xmax, n, dt, 0.5 * dt);
  177. for (integer i = 1; i <= n; i ++) {
  178. double t = Sampled_indexToX (you.get(), i);
  179. double rframe = Sampled_xToIndex (me, t);
  180. if (rframe < 1 || rframe >= my nx) continue;
  181. integer leftFrame = Melder_ifloor (rframe);
  182. integer rightFrame = leftFrame + 1;
  183. double phase = rframe - leftFrame;
  184. long double value = 0.0;
  185. for (integer j = 1; j <= my ny; j ++) {
  186. double f = Matrix_rowToY (me, j);
  187. double power = my z [j] [leftFrame] * (1 - phase) + my z [j] [rightFrame] * phase;
  188. value += sqrt (power) * sin (2 * NUMpi * f * t);
  189. }
  190. your z [1] [i] = (double) value;
  191. }
  192. return you;
  193. } catch (MelderError) {
  194. Melder_throw (me, U": not converted to Sound.");
  195. }
  196. }
  197. /* End of file Sound_and_Spectrogram.cpp */