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.

319 lines
10KB

  1. /*
  2. Copyright (C) 2017 Xenakios
  3. This program is free software; you can redistribute it and/or modify
  4. it under the terms of version 3 of the GNU General Public License
  5. as published by the Free Software Foundation.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. GNU General Public License (version 3) for more details.
  10. www.gnu.org/licenses
  11. */
  12. #include "ps3_BufferingAudioSource.h"
  13. MyBufferingAudioSource::MyBufferingAudioSource(PositionableAudioSource* s,
  14. TimeSliceThread& thread,
  15. bool deleteSourceWhenDeleted,
  16. int bufferSizeSamples,
  17. int numChannels,
  18. bool prefillBufferOnPrepareToPlay)
  19. : source (s, deleteSourceWhenDeleted),
  20. backgroundThread (thread),
  21. numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)),
  22. numberOfChannels (numChannels),
  23. prefillBuffer (prefillBufferOnPrepareToPlay)
  24. {
  25. jassert (source != nullptr);
  26. jassert (numberOfSamplesToBuffer > 1024); // not much point using this class if you're
  27. // not using a larger buffer..
  28. }
  29. MyBufferingAudioSource::~MyBufferingAudioSource()
  30. {
  31. releaseResources();
  32. }
  33. //==============================================================================
  34. void MyBufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double newSampleRate)
  35. {
  36. auto bufferSizeNeeded = jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer);
  37. if (newSampleRate != sampleRate
  38. || bufferSizeNeeded != buffer.getNumSamples()
  39. || ! isPrepared)
  40. {
  41. backgroundThread.removeTimeSliceClient (this);
  42. isPrepared = true;
  43. sampleRate = newSampleRate;
  44. source->prepareToPlay (samplesPerBlockExpected, newSampleRate);
  45. buffer.setSize (numberOfChannels, bufferSizeNeeded);
  46. buffer.clear();
  47. bufferValidStart = 0;
  48. bufferValidEnd = 0;
  49. backgroundThread.addTimeSliceClient (this);
  50. do
  51. {
  52. backgroundThread.moveToFrontOfQueue (this);
  53. Thread::sleep (5);
  54. }
  55. while (prefillBuffer
  56. && (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, buffer.getNumSamples() / 2)));
  57. }
  58. }
  59. void MyBufferingAudioSource::releaseResources()
  60. {
  61. isPrepared = false;
  62. backgroundThread.removeTimeSliceClient (this);
  63. buffer.setSize (numberOfChannels, 0);
  64. // MSVC2015 seems to need this if statement to not generate a warning during linking.
  65. // As source is set in the constructor, there is no way that source could
  66. // ever equal this, but it seems to make MSVC2015 happy.
  67. if (source != this)
  68. source->releaseResources();
  69. }
  70. void MyBufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
  71. {
  72. const ScopedLock sl (bufferStartPosLock);
  73. auto start = bufferValidStart.load();
  74. auto end = bufferValidEnd.load();
  75. auto pos = nextPlayPos.load();
  76. auto validStart = (int) (jlimit (start, end, pos) - pos);
  77. auto validEnd = (int) (jlimit (start, end, pos + info.numSamples) - pos);
  78. if (validStart == validEnd)
  79. {
  80. // total cache miss
  81. info.clearActiveBufferRegion();
  82. }
  83. else
  84. {
  85. if (validStart > 0)
  86. info.buffer->clear (info.startSample, validStart); // partial cache miss at start
  87. if (validEnd < info.numSamples)
  88. info.buffer->clear (info.startSample + validEnd,
  89. info.numSamples - validEnd); // partial cache miss at end
  90. if (validStart < validEnd)
  91. {
  92. for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;)
  93. {
  94. jassert (buffer.getNumSamples() > 0);
  95. auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples());
  96. auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples());
  97. if (startBufferIndex < endBufferIndex)
  98. {
  99. info.buffer->copyFrom (chan, info.startSample + validStart,
  100. buffer,
  101. chan, startBufferIndex,
  102. validEnd - validStart);
  103. }
  104. else
  105. {
  106. auto initialSize = buffer.getNumSamples() - startBufferIndex;
  107. info.buffer->copyFrom (chan, info.startSample + validStart,
  108. buffer,
  109. chan, startBufferIndex,
  110. initialSize);
  111. info.buffer->copyFrom (chan, info.startSample + validStart + initialSize,
  112. buffer,
  113. chan, 0,
  114. (validEnd - validStart) - initialSize);
  115. }
  116. }
  117. }
  118. nextPlayPos += info.numSamples;
  119. }
  120. }
  121. bool MyBufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, uint32 timeout)
  122. {
  123. if (!source || source->getTotalLength() <= 0)
  124. return false;
  125. if (nextPlayPos + info.numSamples < 0)
  126. return true;
  127. if (! isLooping() && nextPlayPos > getTotalLength())
  128. return true;
  129. auto now = Time::getMillisecondCounter();
  130. auto startTime = now;
  131. auto elapsed = (now >= startTime ? now - startTime
  132. : (std::numeric_limits<uint32>::max() - startTime) + now);
  133. while (elapsed <= timeout)
  134. {
  135. {
  136. const ScopedLock sl (bufferStartPosLock);
  137. auto start = bufferValidStart.load();
  138. auto end = bufferValidEnd.load();
  139. auto pos = nextPlayPos.load();
  140. auto validStart = static_cast<int> (jlimit (start, end, pos) - pos);
  141. auto validEnd = static_cast<int> (jlimit (start, end, pos + info.numSamples) - pos);
  142. if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples)
  143. return true;
  144. }
  145. if (elapsed < timeout && (! bufferReadyEvent.wait (static_cast<int> (timeout - elapsed))))
  146. return false;
  147. now = Time::getMillisecondCounter();
  148. elapsed = (now >= startTime ? now - startTime
  149. : (std::numeric_limits<uint32>::max() - startTime) + now);
  150. }
  151. return false;
  152. }
  153. int64 MyBufferingAudioSource::getNextReadPosition() const
  154. {
  155. jassert (source->getTotalLength() > 0);
  156. auto pos = nextPlayPos.load();
  157. return (source->isLooping() && nextPlayPos > 0)
  158. ? pos % source->getTotalLength()
  159. : pos;
  160. }
  161. void MyBufferingAudioSource::setNextReadPosition (int64 newPosition)
  162. {
  163. const ScopedLock sl (bufferStartPosLock);
  164. nextPlayPos = newPosition;
  165. backgroundThread.moveToFrontOfQueue (this);
  166. }
  167. bool MyBufferingAudioSource::readNextBufferChunk()
  168. {
  169. int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd;
  170. {
  171. const ScopedLock sl (bufferStartPosLock);
  172. if (wasSourceLooping != isLooping())
  173. {
  174. wasSourceLooping = isLooping();
  175. bufferValidStart = 0;
  176. bufferValidEnd = 0;
  177. }
  178. newBVS = jmax ((int64) 0, nextPlayPos.load());
  179. newBVE = newBVS + buffer.getNumSamples() - 4;
  180. sectionToReadStart = 0;
  181. sectionToReadEnd = 0;
  182. const int maxChunkSize = 2048;
  183. if (newBVS < bufferValidStart || newBVS >= bufferValidEnd)
  184. {
  185. newBVE = jmin (newBVE, newBVS + maxChunkSize);
  186. sectionToReadStart = newBVS;
  187. sectionToReadEnd = newBVE;
  188. bufferValidStart = 0;
  189. bufferValidEnd = 0;
  190. }
  191. else if (std::abs ((int) (newBVS - bufferValidStart)) > 512
  192. || std::abs ((int) (newBVE - bufferValidEnd)) > 512)
  193. {
  194. newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize);
  195. sectionToReadStart = bufferValidEnd;
  196. sectionToReadEnd = newBVE;
  197. bufferValidStart = newBVS;
  198. bufferValidEnd = jmin (bufferValidEnd.load(), newBVE);
  199. }
  200. }
  201. if (sectionToReadStart == sectionToReadEnd)
  202. return false;
  203. jassert (buffer.getNumSamples() > 0);
  204. auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples());
  205. auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples());
  206. if (bufferIndexStart < bufferIndexEnd)
  207. {
  208. readBufferSection (sectionToReadStart,
  209. (int) (sectionToReadEnd - sectionToReadStart),
  210. bufferIndexStart);
  211. }
  212. else
  213. {
  214. auto initialSize = buffer.getNumSamples() - bufferIndexStart;
  215. readBufferSection (sectionToReadStart,
  216. initialSize,
  217. bufferIndexStart);
  218. readBufferSection (sectionToReadStart + initialSize,
  219. (int) (sectionToReadEnd - sectionToReadStart) - initialSize,
  220. 0);
  221. }
  222. {
  223. const ScopedLock sl2 (bufferStartPosLock);
  224. bufferValidStart = newBVS;
  225. bufferValidEnd = newBVE;
  226. }
  227. bufferReadyEvent.signal();
  228. return true;
  229. }
  230. void MyBufferingAudioSource::readBufferSection (int64 start, int length, int bufferOffset)
  231. {
  232. if (source->getNextReadPosition() != start)
  233. source->setNextReadPosition (start);
  234. AudioSourceChannelInfo info (&buffer, bufferOffset, length);
  235. source->getNextAudioBlock (info);
  236. }
  237. int MyBufferingAudioSource::useTimeSlice()
  238. {
  239. return readNextBufferChunk() ? 1 : 100;
  240. }
  241. double MyBufferingAudioSource::getPercentReady() const
  242. {
  243. if (bufferValidEnd == bufferValidStart)
  244. return 0.0;
  245. if (numberOfSamplesToBuffer == 0)
  246. return 0.0;
  247. return 1.0 / numberOfSamplesToBuffer * (bufferValidEnd - bufferValidStart);
  248. }