You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

667 lines
17KB

  1. #include "StretchSource.h"
  2. #ifdef WIN32
  3. #include <ppl.h>
  4. //#define USE_PPL_TO_PROCESS_STRETCHERS
  5. #undef min
  6. #undef max
  7. #endif
  8. StretchAudioSource::StretchAudioSource(int initialnumoutchans, AudioFormatManager* afm) : m_afm(afm)
  9. {
  10. m_resampler = std::make_unique<WDL_Resampler>();
  11. m_resampler_outbuf.resize(1024*1024);
  12. m_inputfile = std::make_unique<AInputS>(m_afm);
  13. m_specproc_order = { {0,false} , { 1, false} ,{2,true},{3,true},{4,true},{5,false},{6,true},{7,true} };
  14. setNumOutChannels(initialnumoutchans);
  15. m_xfadetask.buffer.setSize(8, 65536);
  16. m_xfadetask.buffer.clear();
  17. }
  18. StretchAudioSource::~StretchAudioSource()
  19. {
  20. }
  21. void StretchAudioSource::prepareToPlay(int samplesPerBlockExpected, double sampleRate)
  22. {
  23. m_outsr = sampleRate;
  24. m_vol_smoother.reset(sampleRate, 0.5);
  25. m_lastplayrate = -1.0;
  26. m_stop_play_requested = false;
  27. m_output_counter = 0;
  28. m_output_silence_counter = 0;
  29. m_stream_end_reached = false;
  30. m_firstbuffer = true;
  31. m_output_has_begun = false;
  32. m_drypreviewbuf.setSize(m_num_outchans, 65536);
  33. initObjects();
  34. }
  35. void StretchAudioSource::releaseResources()
  36. {
  37. }
  38. AudioBuffer<float>* StretchAudioSource::getSourceAudioBuffer()
  39. {
  40. if (m_inputfile==nullptr)
  41. return nullptr;
  42. return m_inputfile->getAudioBuffer();
  43. }
  44. bool StretchAudioSource::isResampling()
  45. {
  46. if (m_inputfile==nullptr || m_inputfile->info.samplerate==0)
  47. return false;
  48. return (int)m_outsr!=m_inputfile->info.samplerate;
  49. }
  50. std::vector<SpectrumProcess> StretchAudioSource::getSpectrumProcessOrder()
  51. {
  52. return m_specproc_order;
  53. }
  54. void StretchAudioSource::setSpectrumProcessOrder(std::vector<SpectrumProcess> order)
  55. {
  56. ScopedLock locker(m_cs);
  57. m_specproc_order = order;
  58. for (int i = 0; i < m_stretchers.size(); ++i)
  59. {
  60. m_stretchers[i]->m_spectrum_processes = order;
  61. }
  62. }
  63. std::pair<Range<double>, Range<double>> StretchAudioSource::getFileCachedRangesNormalized()
  64. {
  65. if (m_inputfile == nullptr)
  66. return {};
  67. return m_inputfile->getCachedRangesNormalized();
  68. }
  69. ValueTree StretchAudioSource::getStateTree()
  70. {
  71. ValueTree tree("stretchsourcestate");
  72. storeToTreeProperties(tree, nullptr, "pitch_shift", m_ppar.pitch_shift.cents,
  73. "octaves_minus2", m_ppar.octave.om2,
  74. "octaves_minus1",m_ppar.octave.om1,
  75. "octave0",m_ppar.octave.o0,
  76. "octave_plus1",m_ppar.octave.o1,
  77. "octaves_plus15",m_ppar.octave.o15,
  78. "octaves_plus2",m_ppar.octave.o2);
  79. return tree;
  80. }
  81. void StretchAudioSource::setStateTree(ValueTree state)
  82. {
  83. ScopedLock locker(m_cs);
  84. getFromTreeProperties(state, "pitch_shift", m_ppar.pitch_shift.cents,
  85. "octaves_minus2", m_ppar.octave.om2,
  86. "octaves_minus1", m_ppar.octave.om1,
  87. "octave0", m_ppar.octave.o0,
  88. "octave_plus1", m_ppar.octave.o1,
  89. "octaves_plus15", m_ppar.octave.o15,
  90. "octaves_plus2", m_ppar.octave.o2);
  91. for (int i = 0; i < m_stretchers.size(); ++i)
  92. {
  93. m_stretchers[i]->set_parameters(&m_ppar);
  94. }
  95. }
  96. bool StretchAudioSource::isLoopingEnabled()
  97. {
  98. if (m_inputfile == nullptr || m_inputfile->info.nsamples == 0)
  99. return false;
  100. return m_inputfile->isLooping();
  101. }
  102. void StretchAudioSource::setLoopingEnabled(bool b)
  103. {
  104. ScopedLock locker(m_cs);
  105. if (m_inputfile != nullptr)
  106. {
  107. m_inputfile->setLoopEnabled(b);
  108. }
  109. }
  110. void StretchAudioSource::setAudioBufferAsInputSource(AudioBuffer<float>* buf, int sr, int len)
  111. {
  112. ScopedLock locker(m_cs);
  113. m_inputfile->setAudioBuffer(buf, sr, len);
  114. m_seekpos = 0.0;
  115. m_curfile = File();
  116. if (m_playrange.isEmpty())
  117. setPlayRange({ 0.0,1.0 }, true);
  118. ++m_param_change_count;
  119. }
  120. void StretchAudioSource::setMainVolume(double decibels)
  121. {
  122. if (decibels == m_main_volume)
  123. return;
  124. if (m_cs.tryEnter())
  125. {
  126. m_main_volume = jlimit(-144.0, 12.0, decibels);
  127. ++m_param_change_count;
  128. m_cs.exit();
  129. }
  130. }
  131. void StretchAudioSource::setLoopXFadeLength(double lenseconds)
  132. {
  133. if (lenseconds == m_loopxfadelen)
  134. return;
  135. if (m_cs.tryEnter())
  136. {
  137. m_loopxfadelen = jlimit(0.0, 1.0, lenseconds);
  138. ++m_param_change_count;
  139. m_cs.exit();
  140. }
  141. }
  142. void StretchAudioSource::setPreviewDry(bool b)
  143. {
  144. if (b == m_preview_dry)
  145. return;
  146. if (m_cs.tryEnter())
  147. {
  148. m_preview_dry = b;
  149. ++m_param_change_count;
  150. m_cs.exit();
  151. }
  152. }
  153. bool StretchAudioSource::isPreviewingDry() const
  154. {
  155. return m_preview_dry;
  156. }
  157. void StretchAudioSource::getNextAudioBlock(const AudioSourceChannelInfo & bufferToFill)
  158. {
  159. ScopedLock locker(m_cs);
  160. double maingain = Decibels::decibelsToGain(m_main_volume);
  161. if (m_preview_dry == true && m_inputfile!=nullptr && m_inputfile->info.nsamples>0)
  162. {
  163. m_inputfile->setXFadeLenSeconds(m_loopxfadelen);
  164. m_inputfile->readNextBlock(m_drypreviewbuf, bufferToFill.numSamples, m_num_outchans);
  165. for (int i = 0; i < m_num_outchans; ++i)
  166. bufferToFill.buffer->copyFrom(i, bufferToFill.startSample, m_drypreviewbuf, i, 0, bufferToFill.numSamples);
  167. bufferToFill.buffer->applyGain(bufferToFill.startSample, bufferToFill.numSamples, maingain);
  168. return;
  169. }
  170. if (m_pause_state == 2)
  171. {
  172. bufferToFill.buffer->clear(bufferToFill.startSample,bufferToFill.numSamples);
  173. return;
  174. }
  175. if (m_stretchoutringbuf.available() > 0)
  176. m_output_has_begun = true;
  177. bool freezing = m_freezing;
  178. if (m_stretchers[0]->isFreezing() != freezing)
  179. {
  180. if (freezing == true && m_inputfile!=nullptr)
  181. m_freeze_pos = 1.0/m_inputfile->info.nsamples*m_inputfile->getCurrentPosition();
  182. for (auto& e : m_stretchers)
  183. e->set_freezing(m_freezing);
  184. }
  185. if (m_vol_smoother.getTargetValue() != maingain)
  186. m_vol_smoother.setValue(maingain);
  187. FloatVectorOperations::disableDenormalisedNumberSupport();
  188. float** outarrays = bufferToFill.buffer->getArrayOfWritePointers();
  189. int outbufchans = m_num_outchans; // bufferToFill.buffer->getNumChannels();
  190. int offset = bufferToFill.startSample;
  191. if (m_stretchers.size() == 0)
  192. return;
  193. if (m_inputfile == nullptr)
  194. return;
  195. if (m_inputfile->info.nsamples == 0)
  196. return;
  197. m_inputfile->setXFadeLenSeconds(m_loopxfadelen);
  198. double silencethreshold = Decibels::decibelsToGain(-70.0);
  199. bool tempfirst = true;
  200. auto foofilepos0 = m_inputfile->getCurrentPosition();
  201. auto ringbuffilltask = [this](int framestoproduce)
  202. {
  203. while (m_stretchoutringbuf.available() < framestoproduce*m_num_outchans)
  204. {
  205. int readsize = 0;
  206. double in_pos = (double)m_inputfile->getCurrentPosition() / (double)m_inputfile->info.nsamples;
  207. if (m_firstbuffer)
  208. {
  209. readsize = m_stretchers[0]->get_nsamples_for_fill();
  210. m_firstbuffer = false;
  211. }
  212. else
  213. {
  214. readsize = m_stretchers[0]->get_nsamples(in_pos*100.0);
  215. };
  216. int readed = 0;
  217. if (readsize != 0)
  218. {
  219. readed = m_inputfile->readNextBlock(m_file_inbuf, readsize, m_num_outchans);
  220. }
  221. auto inbufptrs = m_file_inbuf.getArrayOfWritePointers();
  222. REALTYPE onset_max = std::numeric_limits<REALTYPE>::min();
  223. #ifdef USE_PPL_TO_PROCESS_STRETCHERS
  224. std::array<REALTYPE, 16> onset_values_arr;
  225. Concurrency::parallel_for(0, (int)m_stretchers.size(), [this, readed, &onset_values_arr](int i)
  226. {
  227. REALTYPE onset_val = m_stretchers[i]->process(m_inbufs[i].data(), readed);
  228. onset_values_arr[i] = onset_val;
  229. });
  230. for (int i = 0; i < m_stretchers.size(); ++i)
  231. onset_max = std::max(onset_max, onset_values_arr[i]);
  232. #else
  233. for (int i = 0; i < m_stretchers.size(); ++i)
  234. {
  235. REALTYPE onset_l = m_stretchers[i]->process(inbufptrs[i], readed);
  236. onset_max = std::max(onset_max, onset_l);
  237. }
  238. #endif
  239. for (int i = 0; i < m_stretchers.size(); ++i)
  240. m_stretchers[i]->here_is_onset(onset_max);
  241. int outbufsize = m_stretchers[0]->get_bufsize();
  242. int nskip = m_stretchers[0]->get_skip_nsamples();
  243. if (nskip > 0)
  244. m_inputfile->skip(nskip);
  245. for (int i = 0; i < outbufsize; i++)
  246. {
  247. for (int ch = 0; ch < m_num_outchans; ++ch)
  248. {
  249. REALTYPE outsa = m_stretchers[ch]->out_buf[i];
  250. m_stretchoutringbuf.push(outsa);
  251. }
  252. }
  253. }
  254. };
  255. int previousxfadestate = m_xfadetask.state;
  256. auto resamplertask = [this, &ringbuffilltask, &bufferToFill]()
  257. {
  258. double* rsinbuf = nullptr;
  259. int outsamplestoproduce = bufferToFill.numSamples;
  260. if (m_xfadetask.state == 1)
  261. outsamplestoproduce = m_xfadetask.xfade_len;
  262. int wanted = m_resampler->ResamplePrepare(outsamplestoproduce, m_num_outchans, &rsinbuf);
  263. ringbuffilltask(wanted);
  264. for (int i = 0; i < wanted*m_num_outchans; ++i)
  265. {
  266. double sample = m_stretchoutringbuf.get();
  267. rsinbuf[i] = sample;
  268. }
  269. if (outsamplestoproduce*m_num_outchans > m_resampler_outbuf.size())
  270. {
  271. m_resampler_outbuf.resize(outsamplestoproduce*m_num_outchans);
  272. }
  273. /*int produced =*/ m_resampler->ResampleOut(m_resampler_outbuf.data(), wanted, outsamplestoproduce, m_num_outchans);
  274. if (m_xfadetask.state == 1)
  275. {
  276. //Logger::writeToLog("Filling xfade buffer");
  277. for (int i = 0; i < outsamplestoproduce; ++i)
  278. {
  279. for (int j = 0; j < m_num_outchans; ++j)
  280. {
  281. m_xfadetask.buffer.setSample(j, i, m_resampler_outbuf[i*m_num_outchans + j]);
  282. }
  283. }
  284. if (m_process_fftsize != m_xfadetask.requested_fft_size)
  285. {
  286. m_process_fftsize = m_xfadetask.requested_fft_size;
  287. //Logger::writeToLog("Initing stretcher objects");
  288. initObjects();
  289. }
  290. m_xfadetask.state = 2;
  291. }
  292. };
  293. resamplertask();
  294. if (previousxfadestate == 1 && m_xfadetask.state == 2)
  295. {
  296. //Logger::writeToLog("Rerunning resampler task");
  297. resamplertask();
  298. }
  299. bool source_ended = m_inputfile->hasEnded();
  300. double samplelimit = 16384.0;
  301. if (m_clip_output == true)
  302. samplelimit = 1.0;
  303. for (int i = 0; i < bufferToFill.numSamples; ++i)
  304. {
  305. double smoothed_gain = m_vol_smoother.getNextValue();
  306. double mixed = 0.0;
  307. for (int j = 0; j < outbufchans; ++j)
  308. {
  309. double outsample = m_resampler_outbuf[i*m_num_outchans + j];
  310. if (m_xfadetask.state == 2)
  311. {
  312. double xfadegain = 1.0 / m_xfadetask.xfade_len*m_xfadetask.counter;
  313. jassert(xfadegain >= 0.0 && xfadegain <= 1.0);
  314. double outsample2 = m_xfadetask.buffer.getSample(j, m_xfadetask.counter);
  315. outsample = xfadegain * outsample + (1.0 - xfadegain)*outsample2;
  316. }
  317. outarrays[j][i + offset] = jlimit(-samplelimit,samplelimit , outsample * smoothed_gain);
  318. mixed += outsample;
  319. }
  320. if (m_xfadetask.state == 2)
  321. {
  322. ++m_xfadetask.counter;
  323. if (m_xfadetask.counter >= m_xfadetask.xfade_len)
  324. m_xfadetask.state = 0;
  325. }
  326. if (source_ended && m_output_counter>=2*m_process_fftsize)
  327. {
  328. if (fabs(mixed) < silencethreshold)
  329. ++m_output_silence_counter;
  330. else
  331. m_output_silence_counter = 0;
  332. }
  333. }
  334. if (m_pause_state == 1)
  335. {
  336. bufferToFill.buffer->applyGainRamp(bufferToFill.startSample, bufferToFill.numSamples, 1.0f, 0.0f);
  337. m_pause_state = 2;
  338. }
  339. if (m_pause_state == 3)
  340. {
  341. bufferToFill.buffer->applyGainRamp(bufferToFill.startSample, bufferToFill.numSamples, 0.0f, 1.0f);
  342. m_pause_state = 0;
  343. }
  344. m_output_counter += bufferToFill.numSamples;
  345. }
  346. void StretchAudioSource::setNextReadPosition(int64 /*newPosition*/)
  347. {
  348. }
  349. int64 StretchAudioSource::getNextReadPosition() const
  350. {
  351. return int64();
  352. }
  353. int64 StretchAudioSource::getTotalLength() const
  354. {
  355. if (m_inputfile == nullptr)
  356. return 0;
  357. return m_inputfile->info.nsamples;
  358. }
  359. bool StretchAudioSource::isLooping() const
  360. {
  361. return false;
  362. }
  363. String StretchAudioSource::setAudioFile(File file)
  364. {
  365. ScopedLock locker(m_cs);
  366. if (m_inputfile->openAudioFile(file))
  367. {
  368. m_curfile = file;
  369. return String();
  370. }
  371. return "Could not open file";
  372. }
  373. File StretchAudioSource::getAudioFile()
  374. {
  375. return m_curfile;
  376. }
  377. void StretchAudioSource::setNumOutChannels(int chans)
  378. {
  379. jassert(chans > 0 && chans < g_maxnumoutchans);
  380. m_num_outchans = chans;
  381. }
  382. void StretchAudioSource::initObjects()
  383. {
  384. ScopedLock locker(m_cs);
  385. m_inputfile->setActiveRange(m_playrange);
  386. if (m_inputfile->getActiveRange().contains(m_inputfile->getCurrentPositionPercent())==false)
  387. m_inputfile->seek(m_playrange.getStart());
  388. m_firstbuffer = true;
  389. if (m_stretchoutringbuf.getSize() < m_num_outchans*m_process_fftsize)
  390. {
  391. int newsize = m_num_outchans*m_process_fftsize*2;
  392. //Logger::writeToLog("Resizing circular buffer to " + String(newsize));
  393. m_stretchoutringbuf.resize(newsize);
  394. }
  395. m_stretchoutringbuf.clear();
  396. m_resampler->Reset();
  397. m_resampler->SetRates(m_inputfile->info.samplerate, m_outsr);
  398. REALTYPE stretchratio = m_playrate;
  399. FFTWindow windowtype = W_HAMMING;
  400. if (m_fft_window_type>=0)
  401. windowtype = (FFTWindow)m_fft_window_type;
  402. int inbufsize = m_process_fftsize;
  403. double onsetsens = m_onsetdetection;
  404. m_stretchers.resize(m_num_outchans);
  405. for (int i = 0; i < m_stretchers.size(); ++i)
  406. {
  407. if (m_stretchers[i] == nullptr)
  408. {
  409. //Logger::writeToLog("Creating stretch instance " + String(i));
  410. m_stretchers[i] = std::make_shared<ProcessedStretch>(stretchratio,
  411. m_process_fftsize, windowtype, false, (float)m_inputfile->info.samplerate, i + 1);
  412. }
  413. m_stretchers[i]->setBufferSize(m_process_fftsize);
  414. m_stretchers[i]->setSampleRate(m_inputfile->info.samplerate);
  415. m_stretchers[i]->set_onset_detection_sensitivity(onsetsens);
  416. m_stretchers[i]->set_parameters(&m_ppar);
  417. m_stretchers[i]->set_freezing(m_freezing);
  418. fill_container(m_stretchers[i]->out_buf, 0.0f);
  419. m_stretchers[i]->m_spectrum_processes = m_specproc_order;
  420. }
  421. m_file_inbuf.setSize(m_num_outchans, 3 * inbufsize);
  422. int poolsize = m_stretchers[0]->get_max_bufsize();
  423. }
  424. double StretchAudioSource::getInfilePositionPercent()
  425. {
  426. if (m_inputfile == nullptr || m_inputfile->info.nsamples == 0)
  427. return 0.0;
  428. return 1.0/m_inputfile->info.nsamples*m_inputfile->getCurrentPosition();
  429. }
  430. double StretchAudioSource::getInfilePositionSeconds()
  431. {
  432. if (m_inputfile == nullptr || m_inputfile->info.nsamples == 0)
  433. return 0.0;
  434. //return m_lastinpos*m_inputfile->getLengthSeconds();
  435. return (double)m_inputfile->getCurrentPosition() / m_inputfile->info.samplerate;
  436. }
  437. double StretchAudioSource::getInfileLengthSeconds()
  438. {
  439. if (m_inputfile == nullptr || m_inputfile->info.nsamples == 0)
  440. return 0.0;
  441. return (double)m_inputfile->info.nsamples / m_inputfile->info.samplerate;
  442. }
  443. void StretchAudioSource::setRate(double rate)
  444. {
  445. if (rate == m_playrate)
  446. return;
  447. if (m_cs.tryEnter())
  448. {
  449. m_playrate = rate;
  450. for (int i = 0; i < m_stretchers.size(); ++i)
  451. {
  452. m_stretchers[i]->set_rap((float)rate);
  453. }
  454. ++m_param_change_count;
  455. m_cs.exit();
  456. }
  457. }
  458. void StretchAudioSource::setProcessParameters(ProcessParameters * pars)
  459. {
  460. if (*pars == m_ppar)
  461. return;
  462. if (m_cs.tryEnter())
  463. {
  464. m_ppar = *pars;
  465. for (int i = 0; i < m_stretchers.size(); ++i)
  466. {
  467. m_stretchers[i]->set_parameters(pars);
  468. }
  469. ++m_param_change_count;
  470. m_cs.exit();
  471. }
  472. }
  473. const ProcessParameters& StretchAudioSource::getProcessParameters()
  474. {
  475. return m_ppar;
  476. }
  477. void StretchAudioSource::setFFTWindowingType(int windowtype)
  478. {
  479. if (windowtype==m_fft_window_type)
  480. return;
  481. if (m_cs.tryEnter())
  482. {
  483. m_fft_window_type = windowtype;
  484. for (int i = 0; i < m_stretchers.size(); ++i)
  485. {
  486. m_stretchers[i]->window_type = (FFTWindow)windowtype;
  487. }
  488. ++m_param_change_count;
  489. m_cs.exit();
  490. }
  491. }
  492. void StretchAudioSource::setFFTSize(int size)
  493. {
  494. jassert(size>0);
  495. if (m_xfadetask.state == 0 && (m_process_fftsize == 0 || size != m_process_fftsize))
  496. {
  497. ScopedLock locker(m_cs);
  498. if (m_xfadetask.buffer.getNumChannels() < m_num_outchans)
  499. {
  500. m_xfadetask.buffer.setSize(m_num_outchans, m_xfadetask.buffer.getNumSamples());
  501. }
  502. if (m_process_fftsize > 0)
  503. {
  504. m_xfadetask.state = 1;
  505. m_xfadetask.counter = 0;
  506. m_xfadetask.xfade_len = 16384;
  507. m_xfadetask.requested_fft_size = size;
  508. }
  509. else
  510. {
  511. m_process_fftsize = size;
  512. initObjects();
  513. }
  514. ++m_param_change_count;
  515. }
  516. }
  517. void StretchAudioSource::setPaused(bool b)
  518. {
  519. if (b == true && m_pause_state>0)
  520. return;
  521. if (b == false && m_pause_state == 0)
  522. return;
  523. ScopedLock locker(m_cs);
  524. if (b == true && m_pause_state == 0)
  525. {
  526. m_pause_state = 1;
  527. return;
  528. }
  529. if (b == false && m_pause_state == 2)
  530. {
  531. m_pause_state = 3;
  532. return;
  533. }
  534. }
  535. bool StretchAudioSource::isPaused() const
  536. {
  537. return m_pause_state > 0;
  538. }
  539. void StretchAudioSource::seekPercent(double pos)
  540. {
  541. ScopedLock locker(m_cs);
  542. m_seekpos = pos;
  543. m_inputfile->seek(pos);
  544. ++m_param_change_count;
  545. }
  546. double StretchAudioSource::getOutputDurationSecondsForRange(Range<double> range, int fftsize)
  547. {
  548. if (m_inputfile == nullptr || m_inputfile->info.nsamples == 0)
  549. return 0.0;
  550. int64_t play_end_pos = (fftsize * 2)+range.getLength()*m_playrate*m_inputfile->info.nsamples;
  551. return (double)play_end_pos / m_inputfile->info.samplerate;
  552. }
  553. void StretchAudioSource::setOnsetDetection(double x)
  554. {
  555. if (x == m_onsetdetection)
  556. return;
  557. if (m_cs.tryEnter())
  558. {
  559. m_onsetdetection = x;
  560. for (int i = 0; i < m_stretchers.size(); ++i)
  561. {
  562. m_stretchers[i]->set_onset_detection_sensitivity((float)x);
  563. }
  564. ++m_param_change_count;
  565. m_cs.exit();
  566. }
  567. }
  568. void StretchAudioSource::setPlayRange(Range<double> playrange, bool isloop)
  569. {
  570. if (m_playrange.isEmpty() == false && playrange == m_playrange)
  571. return;
  572. if (m_cs.tryEnter())
  573. {
  574. if (playrange.isEmpty())
  575. m_playrange = { 0.0,1.0 };
  576. else
  577. m_playrange = playrange;
  578. m_stream_end_reached = false;
  579. m_inputfile->setActiveRange(m_playrange);
  580. m_inputfile->setLoopEnabled(isloop);
  581. if (m_playrange.contains(m_seekpos) == false)
  582. m_inputfile->seek(m_playrange.getStart());
  583. m_seekpos = m_playrange.getStart();
  584. ++m_param_change_count;
  585. m_cs.exit();
  586. }
  587. }
  588. bool StretchAudioSource::isLoopEnabled()
  589. {
  590. if (m_inputfile == nullptr)
  591. return false;
  592. return m_inputfile->isLooping();
  593. }
  594. bool StretchAudioSource::hasReachedEnd()
  595. {
  596. if (m_inputfile == nullptr)
  597. return false;
  598. if (m_inputfile->isLooping() && m_maxloops == 0)
  599. return false;
  600. if (m_inputfile->isLooping() && m_inputfile->getLoopCount() > m_maxloops)
  601. return true;
  602. //return m_output_counter>=m_process_fftsize*2;
  603. return m_output_silence_counter>=65536;
  604. }