Spectrometer.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. /******************************************************************************
  2. !! Warning: 'Spectrometer' cannot be manually deleted/created as long as there's at least one Sound using it !!
  3. (not that clearing Spectrometer from Sound is not immediate, in case it's using it during the clear call)
  4. /******************************************************************************/
  5. #include "stdafx.h"
  6. #define SUPPORT_FFTW 0 // fastest for all sizes, supports all sizes, but needs a commercial license
  7. #define SUPPORT_KISS 0 // fast only for pow2 , supports all sizes, can be slow on non pow2 (especially prime numbers)
  8. #define SUPPORT_FFTREAL 1 // faster than KISS , supports only pow2 sizes
  9. #include "../../../ThirdPartyLibs/begin.h"
  10. #if SUPPORT_FFTW
  11. #include "../../../ThirdPartyLibs/FFTW/lib/api/fftw3.h"
  12. #endif
  13. #if SUPPORT_KISS
  14. #undef USE_SIMD
  15. #include "../../../ThirdPartyLibs/KissFFT/kiss_fft.c"
  16. #include "../../../ThirdPartyLibs/KissFFT/kiss_fftr.c"
  17. #endif
  18. #if SUPPORT_FFTREAL
  19. #undef PI
  20. #undef SQRT2
  21. #include "../../../ThirdPartyLibs/FFTReal/ffft/FFTReal.h"
  22. #endif
  23. #include "../../../ThirdPartyLibs/end.h"
  24. namespace EE{
  25. /******************************************************************************/
  26. #if SUPPORT_FFTW
  27. struct Complex
  28. {
  29. fftw_complex c;
  30. };
  31. // add these as members
  32. Memc<Dbl > fftw_in;
  33. Memc<Complex> fftw_out;
  34. #endif
  35. /******************************************************************************/
  36. Spectrometer::Spectrometer() {zero();}
  37. Spectrometer::Spectrometer(Int resolution) : Spectrometer() {create(resolution);}
  38. void Spectrometer::zero()
  39. {
  40. _frequency=0;
  41. _window_length=0;
  42. _fft=null;
  43. }
  44. void Spectrometer::free()
  45. {
  46. #if SUPPORT_FFTW
  47. fftw_plan &fft=(fftw_plan&)_fft; if(fft){fftw_destroy_plan(fft); fft=null;}
  48. #endif
  49. #if SUPPORT_KISS
  50. kiss_fftr_cfg &fft=(kiss_fftr_cfg&)_fft; if(fft){::kiss_fft_free(fft); fft=null;}
  51. #endif
  52. #if SUPPORT_FFTREAL
  53. Delete((ffft::FFTReal<Flt>*&)_fft);
  54. #endif
  55. }
  56. Spectrometer& Spectrometer::del()
  57. {
  58. free();
  59. _image .del();
  60. _samples.del();
  61. _fft_out.del();
  62. zero(); return T;
  63. }
  64. Spectrometer& Spectrometer::create(Int resolution)
  65. {
  66. del();
  67. _window_length=1.0f/20; // 20 Hz
  68. Int buffer_length_ms=SOUND_TIME,
  69. window_length_ms=Floor(_window_length*1000); // use Floor so in worst case we allocate more rows (the smaller 'window_length_ms' the more 'rows')
  70. #if SUPPORT_KISS || SUPPORT_FFTREAL // Kiss and FFTReal round to NearestPow2 size, so we need to increase number of rows for worst case scenario
  71. // biggest scale of "NearestPow2(x)" compared to "x" is 1.333333.., tested using code: "Flt max=0; REP(10000000){Flt f=Flt(NearestPow2(i))/i; MAX(max, f);}"
  72. window_length_ms=window_length_ms*3/4; // we need to divide by 1.3333.. (4/3), which is the same as multiplying by 3/4
  73. #endif
  74. Int rows=(window_length_ms ? DivCeil(buffer_length_ms, window_length_ms) : 0); // we need enough rows to fit entire sound buffer
  75. rows+=2; // because we're doing interpolation, then we need more (1 for linear, 2 for cubic)
  76. _image.createSoftTry(resolution, rows, 1, IMAGE_F32); _image.clear();
  77. return T;
  78. }
  79. /******************************************************************************/
  80. void Spectrometer::data(Ptr data, Int size, C SoundStream &stream, Long raw_pos)
  81. {
  82. if(stream.frequency()!=_frequency)
  83. {
  84. free();
  85. _frequency=stream.frequency();
  86. Int samples=Round(_window_length*_frequency);
  87. #if SUPPORT_FFTW
  88. fftw_in .setNum(samples);
  89. fftw_out.setNum(samples/2+1);
  90. _fft=fftw_plan_dft_r2c_1d(fftw_in.elms(), fftw_in.data(), (fftw_complex*)fftw_out.data(), 0);
  91. #endif
  92. #if SUPPORT_KISS
  93. if(1)samples=NearestPow2(samples); // this is optional (may improve performance)
  94. else samples= Ceil2(samples); // kiss_fftr requires even number of samples
  95. _fft=kiss_fftr_alloc(samples, 0, null, null);
  96. _fft_out.setNum((samples/2+1)*2); ASSERT(SIZE(kiss_fft_cpx)==SIZE(Flt)*2); // *2 because we need 'kiss_fft_cpx' instead of 'Flt' which is 2x bigger
  97. #endif
  98. #if SUPPORT_FFTREAL
  99. samples=NearestPow2(samples); // FFTReal requires pow2
  100. _fft=new ffft::FFTReal<Flt>(samples);
  101. _fft_out.setNum(samples);
  102. #endif
  103. _samples.setNumZero(samples); // zero in case 'sample_pos' jumps due to seeking
  104. }
  105. // checks to avoid div by zero
  106. if(! stream .block()
  107. || !_samples.elms ()
  108. || !_image .is ())return;
  109. Long sample=raw_pos/stream.block();
  110. Int sample_pos=Unsigned(sample)%_samples.elms(); // expected number of samples in the '_samples' buffer, we need to calculate it every time instead of storing, in case seeking occurs, in which case the number of samples in the buffer must be in sync with 'raw_pos'
  111. #if 0
  112. REPAO(_samples)=Cos(i/Flt(_samples.elms()-1)*Ms.pos().x*10000); sample_pos=_samples.elms();
  113. #else
  114. for(Int data_samples=size/stream.block(); data_samples>0; )
  115. {
  116. Flt *dest=_samples.data()+sample_pos;
  117. Int copy=Min(_samples.elms()-sample_pos, data_samples); data_samples-=copy;
  118. switch(stream.bytes())
  119. {
  120. case 1: // unsigned Byte
  121. {
  122. Byte* &src=(Byte*&)data;
  123. switch(stream.channels())
  124. {
  125. case 1:
  126. {
  127. REP(copy)*dest++=(*src++ - 128)/128.0f;
  128. sample_pos+=copy;
  129. }break;
  130. case 2:
  131. {
  132. REP(copy)
  133. {
  134. Byte l=src[0], r=src[1]; src+=2;
  135. *dest++=(l+r-256)/256.0f;
  136. }
  137. sample_pos+=copy;
  138. }break;
  139. }
  140. }break;
  141. case 2: // signed Short
  142. {
  143. Short* &src=(Short*&)data;
  144. switch(stream.channels())
  145. {
  146. case 1:
  147. {
  148. REP(copy)*dest++=(*src++)/32768.0f;
  149. sample_pos+=copy;
  150. }break;
  151. case 2:
  152. {
  153. REP(copy)
  154. {
  155. Short l=src[0], r=src[1]; src+=2;
  156. *dest++=(l+r)/65536.0f;
  157. }
  158. sample_pos+=copy;
  159. }break;
  160. }
  161. }break;
  162. case 3: // Int24
  163. {
  164. Int24* &src=(Int24*&)data;
  165. switch(stream.channels())
  166. {
  167. case 1:
  168. {
  169. REP(copy)*dest++=(src++)->asInt()/Flt(-INT24_MIN);
  170. sample_pos+=copy;
  171. }break;
  172. case 2:
  173. {
  174. REP(copy)
  175. {
  176. *dest++=(src[0].asInt()+src[1].asInt())/Flt(-INT24_MIN*2); src+=2;
  177. }
  178. sample_pos+=copy;
  179. }break;
  180. }
  181. }break;
  182. }
  183. if(sample_pos>=_samples.elms())
  184. {
  185. sample_pos=0;
  186. if(1) // apply window (this is needed for more precise FFT calculation)
  187. {
  188. Flt mul=2.0f/(_samples.elms()-1);
  189. REPAO(_samples)*=BlendSmoothCube(i*mul-1);
  190. }
  191. #if SUPPORT_FFTW
  192. REPAO(fftw_in)=_samples[i]; fftw_execute((fftw_plan)_fft);
  193. REPA(fftw_out)
  194. {
  195. Flt frac=Flt(i+1)/fftw_out.elms(); // use this instead of "Flt frac=i/Flt(fftw_out.elms()-1);" because that would always set frac=0 at the start
  196. fftw_complex &complex=fftw_out[i].c;
  197. Flt value=SqrtFast(Sqr(complex[0])+Sqr(complex[1]));
  198. value*=frac;
  199. //value =Log2(value+1); instead of doing log here, do it only once for all collected samples below
  200. complex[0]=value;
  201. }
  202. Int end=fftw_out.elms(), y=Unsigned(sample/_samples.elms())%_image.h(); sample+=_samples.elms();
  203. REP(_image.w())
  204. {
  205. Int start=i*fftw_out.elms()/_image.w();
  206. Flt value=fftw_out[start].c[0]; for(Int i=start+1; i<end; i++)MAX(value, fftw_out[i].c[0]); // always take at least one value, in case start==end
  207. value=Log2(value+1);
  208. //value=BlendSmoothCube(i*0.2f - 26*Flt(y)/_image.h()); // can be used for testing
  209. _image.pixF(i, y)=value;
  210. end=start;
  211. }
  212. #endif
  213. #if SUPPORT_KISS
  214. kiss_fft_cpx *out=(kiss_fft_cpx*)_fft_out.data();
  215. kiss_fftr((kiss_fftr_cfg)_fft, _samples.data(), out);
  216. Int out_elms=_fft_out.elms()/2; // div by 2 because this is Flt but we're using it as 'kiss_fft_cpx'
  217. REP(out_elms)
  218. {
  219. Flt frac=Flt(i+1)/out_elms; // use this instead of "Flt frac=i/Flt(out_elms-1);" because that would always set frac=0 at the start
  220. kiss_fft_cpx &complex=out[i];
  221. Flt value=SqrtFast(Sqr(complex.r)+Sqr(complex.i));
  222. value*=frac;
  223. //value =Log2(value+1); instead of doing log here, do it only once for all collected samples below
  224. complex.r=value;
  225. }
  226. Int end=out_elms, y=Unsigned(sample/_samples.elms())%_image.h(); sample+=_samples.elms();
  227. REP(_image.w())
  228. {
  229. Int start=i*out_elms/_image.w();
  230. Flt value=out[start].r; for(Int i=start+1; i<end; i++)MAX(value, out[i].r); // always take at least one value, in case start==end
  231. value=Log2(value+1);
  232. //value=BlendSmoothCube(i*0.2f - 26*Flt(y)/_image.h()); // can be used for testing
  233. _image.pixF(i, y)=value;
  234. end=start;
  235. }
  236. #endif
  237. #if SUPPORT_FFTREAL
  238. ((ffft::FFTReal<Flt>*)_fft)->do_fft(_fft_out.data(), _samples.data());
  239. Int out_elms=_fft_out.elms()/2; // first half of the buffer is occupied by "real" and second half by "imaginary" (complex)
  240. REP(out_elms)
  241. {
  242. Flt frac=Flt(i+1)/out_elms; // use this instead of "Flt frac=i/Flt(out_elms-1);" because that would always set frac=0 at the start
  243. Flt &real=_fft_out[i];
  244. Flt value=SqrtFast(Sqr(real)+Sqr(_fft_out[i+out_elms]));
  245. value*=frac;
  246. //value =Log2(value+1); instead of doing log here, do it only once for all collected samples below
  247. real=value;
  248. }
  249. Int end=out_elms, y=Unsigned(sample/_samples.elms())%_image.h(); sample+=_samples.elms();
  250. REP(_image.w())
  251. {
  252. Int start=i*out_elms/_image.w();
  253. Flt value=_fft_out[start]; for(Int i=start+1; i<end; i++)MAX(value, _fft_out[i]); // always take at least one value, in case start==end
  254. value=Log2(value+1);
  255. //value=BlendSmoothCube(i*0.2f - 26*Flt(y)/_image.h()); // can be used for testing
  256. _image.pixF(i, y)=value;
  257. end=start;
  258. }
  259. #endif
  260. }
  261. }
  262. #endif
  263. }
  264. /******************************************************************************/
  265. void Spectrometer::get(Flt *meter, Int meter_elms, Flt time, FILTER_TYPE filter)
  266. {
  267. if(meter && meter_elms>0)
  268. {
  269. if(Int s=_samples.elms())
  270. if(Int h=_image .h ())
  271. {
  272. Dbl sample=Dbl(time)*_frequency, frac=sample/s;
  273. Flt yf=Frac(frac, h);
  274. Int y;
  275. if(filter!=FILTER_NONE)yf-=0.5f; // need to offset by -0.5, because when we're at half row position (y=0.5) then we need to get exactly values of the first row, the same as y=0 with no interpolation, and in linear interpolation we will get it with y=0, this is because first row collects samples from start of the song, with window fading out at start and end, making most significant data coming from the middle of the window
  276. else y =Unsigned(TruncL(yf))%h;
  277. if(meter_elms==resolution())switch(filter)
  278. {
  279. case FILTER_NONE : CopyN(meter, &_image.pixF(0, y), meter_elms); break;
  280. case FILTER_LINEAR: REP(meter_elms)meter[i]=_image.pixelFLinear (i, yf, false); break;
  281. default : REP(meter_elms)meter[i]=_image.pixelFCubicFast(i, yf, false); break; // FILTER_CUBIC and others (actually use CubicFast because it uses fewer samples)
  282. }else
  283. {
  284. if(filter==FILTER_NONE)yf=y;
  285. Flt mul=Flt(resolution()-1)/(meter_elms-1); // this keeps edges
  286. switch(filter)
  287. {
  288. case FILTER_NONE : // here for FILTER_NONE we're also using 'pixelFLinear' because we need to stretch horizontally
  289. case FILTER_LINEAR: REP(meter_elms)meter[i]=_image.pixelFLinear (i*mul, yf, false); break;
  290. default : REP(meter_elms)meter[i]=_image.pixelFCubicFast(i*mul, yf, false); break; // FILTER_CUBIC and others (actually use CubicFast because it uses fewer samples)
  291. }
  292. }
  293. return;
  294. }
  295. ZeroN(meter, meter_elms); // if not available then zero
  296. }
  297. }
  298. /******************************************************************************/
  299. }
  300. /******************************************************************************/