Audacity 3.2.0
WaveDataCache.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/*!********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 WaveDataCache.cpp
7
8 Dmitry Vedenko
9
10**********************************************************************/
11#include "WaveDataCache.h"
12#include "FrameStatistics.h"
13
14#include <algorithm>
15#include <cassert>
16#include <cmath>
17#include <cstring>
18
19#include "SampleBlock.h"
20#include "SampleFormat.h"
21#include "Sequence.h"
22#include "WaveClip.h"
23
24#include "RoundUpUnsafe.h"
25
26namespace
27{
28//
29// Getting high-level data from the track for screen display and
30// clipping calculations
31//
33{
34public:
36 const WaveClip& clip, WaveCacheSampleBlock& outBlock,
37 int channelIndex) noexcept
38 {
39 if (
40 mFirstClipSampleID != clip.GetSequence(0)->GetNumSamples() ||
41 mSampleType != outBlock.DataType)
42 {
43 mFirstClipSampleID = clip.GetSequence(0)->GetNumSamples();
44 mLastProcessedSample = 0;
45 mSampleType = outBlock.DataType;
46
47 if (mSampleType != WaveCacheSampleBlock::Type::Samples)
48 {
49 mCachedData.clear();
50 mCachedData.resize(
51 RoundUpUnsafe(clip.GetSequence(0)->GetMaxBlockSize(), 256));
52 }
53 }
54
55 const size_t appendedSamples = clip.GetAppendBufferLen(channelIndex);
56
57 if (appendedSamples == 0)
58 return false;
59
60 const auto appendBuffer = GetAppendBufferPointer(clip, channelIndex);
61
62 switch (mSampleType)
63 {
65 {
66 float* outBuffer = outBlock.GetWritePointer(appendedSamples);
67
68 std::copy(appendBuffer, appendBuffer + appendedSamples, outBuffer);
69 }
70 break;
72 FillBlocksFromAppendBuffer<256>(
73 appendBuffer, appendedSamples, outBlock);
74 break;
76 FillBlocksFromAppendBuffer<64 * 1024>(
77 appendBuffer, appendedSamples, outBlock);
78 break;
79 default:
80 return false;
81 }
82
83 // If the append buffer was flushed during the copy operation,
84 // then outBlock content is no longer valid
85 if (mFirstClipSampleID != clip.GetSequence(0)->GetNumSamples() ||
86 appendedSamples > clip.GetAppendBufferLen(channelIndex)) {
87 return false;
88 }
89
90 return true;
91 }
92
93private:
94 const float* GetAppendBufferPointer(const WaveClip& clip, int channelIndex)
95 {
96 const auto currentFormat = clip.GetSampleFormats().Stored();
97 const auto appendBuffer =
98 static_cast<const void*>(clip.GetAppendBuffer(channelIndex));
99
100 if (currentFormat == floatSample)
101 return static_cast<const float*>(appendBuffer);
102
103 const auto samplesCount = clip.GetAppendBufferLen(channelIndex);
104 mConvertedAppendBufferData.resize(samplesCount);
105
106 if (currentFormat == int16Sample)
107 {
108 const auto int16Buffer = static_cast<const int16_t*>(appendBuffer);
109
110 for (size_t i = 0; i < samplesCount; ++i)
111 mConvertedAppendBufferData[i] = float(int16Buffer[i]) / (1 << 15);
112 }
113 else if (currentFormat == sampleFormat::int24Sample)
114 {
115 const auto int24Buffer = static_cast<const int32_t*>(appendBuffer);
116
117 for (size_t i = 0; i < samplesCount; ++i)
118 mConvertedAppendBufferData[i] = float(int24Buffer[i]) / (1 << 23);
119 }
120
121 return mConvertedAppendBufferData.data();
122 }
123
124 template<size_t blockSize>
126 const float* bufferSamples, size_t samplesCount,
127 WaveCacheSampleBlock& outBlock)
128 {
129 const size_t startingBlock = mLastProcessedSample / blockSize;
130 const size_t blocksCount = RoundUpUnsafe(samplesCount, blockSize);
131
132 for (size_t blockIndex = startingBlock; blockIndex < blocksCount;
133 ++blockIndex)
134 {
135 const size_t samplesInBlock =
136 std::min(blockSize, samplesCount - blockIndex * blockSize);
137
138 float min = std::numeric_limits<float>::infinity();
139 float max = -std::numeric_limits<float>::infinity();
140
141 double sqSum = 0.0;
142
143 auto samples = bufferSamples + blockIndex * blockSize;
144
145 for (size_t sampleIndex = 0; sampleIndex < samplesInBlock;
146 ++sampleIndex)
147 {
148 const float sample = *samples++;
149
150 min = std::min(min, sample);
151 max = std::max(max, sample);
152
153 const double dbl = sample;
154
155 sqSum += dbl * dbl;
156 }
157
158 const auto rms = static_cast<float>(std::sqrt(sqSum / samplesInBlock));
159
160 mCachedData[blockIndex] = { min, max, rms };
161 }
162
163 mLastProcessedSample = samplesCount;
164 auto ptr = outBlock.GetWritePointer(
165 sizeof(CacheItem) * blocksCount / sizeof(float));
166
167 std::memmove(ptr, mCachedData.data(), sizeof(CacheItem) * blocksCount);
168 }
169
172 };
173 sampleCount mFirstClipSampleID { 0 };
174
175 struct CacheItem final
176 {
177 float min;
178 float max;
179 float rms;
180 };
181
182 std::vector<CacheItem> mCachedData;
183 std::vector<float> mConvertedAppendBufferData;
184 size_t mLastProcessedSample { 0 };
185};
186
188MakeDefaultDataProvider(const WaveClip& clip, int channelIndex)
189{
190 return [sequence = clip.GetSequence(channelIndex), clip = &clip,
191 channelIndex, appendBufferHelper = AppendBufferHelper()](
192 int64_t requiredSample, WaveCacheSampleBlock::Type dataType,
193 WaveCacheSampleBlock& outBlock) mutable
194 {
195 if (requiredSample < 0)
196 return false;
197
198 auto sequenceSampleCount = sequence->GetNumSamples();
199 if (requiredSample >= sequenceSampleCount)
200 {
201 auto appendBufferOffset = requiredSample - sequence->GetNumSamples().as_long_long();
202
203 if (appendBufferOffset >= clip->GetAppendBufferLen(channelIndex))
204 return false;
205
206 outBlock.DataType = dataType;
207 outBlock.FirstSample = sequenceSampleCount.as_long_long();
208 outBlock.NumSamples = clip->GetAppendBufferLen(channelIndex);
209
210 bool success = appendBufferHelper.FillBuffer(*clip, outBlock, channelIndex);
211
212 // While we were filling outBlock from the append buffer
213 // it wasn't flushed to sequence, we are good to go
214 if (sequenceSampleCount == sequence->GetNumSamples()) {
215 return success;
216 }
217
218 if (requiredSample >= sequence->GetNumSamples()) {
219 return false;
220 }
221 // if the data was moved to sequence,
222 // then continue with the rest of the function
223 }
224
225 const auto blockIndex = sequence->FindBlock(requiredSample);
226 const auto& inputBlock = sequence->GetBlockArray()[blockIndex];
227
228 outBlock.FirstSample = inputBlock.start.as_long_long();
229 outBlock.NumSamples = inputBlock.sb->GetSampleCount();
230
231 switch (dataType)
232 {
234 {
235 samplePtr ptr = static_cast<samplePtr>(
236 static_cast<void*>(outBlock.GetWritePointer(outBlock.NumSamples)));
237
238 inputBlock.sb->GetSamples(
239 ptr, floatSample, 0, outBlock.NumSamples, false);
240 }
241 break;
243 {
244 size_t framesCount = RoundUpUnsafe(outBlock.NumSamples, 256);
245
246 float* ptr =
247 static_cast<float*>(outBlock.GetWritePointer(framesCount * 3));
248
249 inputBlock.sb->GetSummary256(ptr, 0, framesCount);
250 }
251 break;
253 {
254 size_t framesCount = RoundUpUnsafe(outBlock.NumSamples, 64 * 1024);
255
256 float* ptr =
257 static_cast<float*>(outBlock.GetWritePointer(framesCount * 3));
258
259 inputBlock.sb->GetSummary64k(ptr, 0, framesCount);
260 }
261 break;
262 default:
263 return false;
264 }
265
266 outBlock.DataType = dataType;
267
268 return true;
269 };
270}
271
272} // namespace
273
274WaveDataCache::WaveDataCache(const WaveClip& waveClip, int channelIndex)
276 waveClip.GetRate() / waveClip.GetStretchRatio(),
277 [] { return std::make_unique<WaveCacheElement>(); })
278 , mProvider { MakeDefaultDataProvider(waveClip, channelIndex) }
279 , mWaveClip { waveClip }
280 , mStretchChangedSubscription {
281 const_cast<WaveClip&>(waveClip)
283 [this](const StretchRatioChange&) {
284 SetScaledSampleRate(
285 mWaveClip.GetRate() / mWaveClip.GetStretchRatio());
286 })
287 }
288{
289}
290
293{
296
297 element.AvailableColumns = 0;
298
299 int64_t firstSample = key.FirstSample;
300
301 const auto samplesPerColumn =
302 std::max(0.0, GetScaledSampleRate() / key.PixelsPerSecond);
303
304 const size_t elementSamplesCount =
305 samplesPerColumn * WaveDataCache::CacheElementWidth;
306 size_t processedSamples = 0;
307
308 const WaveCacheSampleBlock::Type blockType =
309 samplesPerColumn >= 64 * 1024 ?
311 (samplesPerColumn >= 256 ? WaveCacheSampleBlock::Type::MinMaxRMS256 :
313
314 if (blockType != mCachedBlock.DataType)
316
317 size_t columnIndex = 0;
318
319 for (; columnIndex < WaveDataCache::CacheElementWidth; ++columnIndex)
320 {
322
323 auto samplesLeft =
324 static_cast<size_t>(std::round(samplesPerColumn * (columnIndex + 1)) - std::round(samplesPerColumn * columnIndex));
325
326 while (samplesLeft != 0)
327 {
328 if (!mCachedBlock.ContainsSample(firstSample))
329 if (!mProvider(firstSample, blockType, mCachedBlock))
330 break;
331
332 summary = mCachedBlock.GetSummary(firstSample, samplesLeft, summary);
333 if(summary.SamplesCount == 0)
334 break;
335
336 samplesLeft -= summary.SamplesCount;
337 firstSample += summary.SamplesCount;
338 processedSamples += summary.SamplesCount;
339 }
340
341 if (summary.SamplesCount > 0)
342 {
343 auto& column = element.Data[columnIndex];
344
345 column.min = summary.Min;
346 column.max = summary.Max;
347
348 column.rms = std::sqrt(summary.SquaresSum / summary.SumItemsCount);
349 }
350
351 if (columnIndex > 0)
352 {
353 const auto prevColumn = element.Data[columnIndex - 1];
354 auto& column = element.Data[columnIndex];
355
356 bool updated = false;
357
358 if (prevColumn.min > column.max)
359 {
360 column.max = prevColumn.min;
361 updated = true;
362 }
363
364 if (prevColumn.max < column.min)
365 {
366 column.min = prevColumn.max;
367 updated = true;
368 }
369
370 if (updated)
371 column.rms = std::clamp(column.rms, column.min, column.max);
372 }
373
374 if (samplesLeft != 0)
375 {
376 ++columnIndex;
377 break;
378 }
379 }
380
381 element.AvailableColumns = columnIndex;
382 element.IsComplete = processedSamples == elementSamplesCount;
383
384 return processedSamples != 0;
385}
386
387bool WaveCacheSampleBlock::ContainsSample(int64_t sampleIndex) const noexcept
388{
389 return sampleIndex >= FirstSample &&
390 (sampleIndex < (FirstSample + NumSamples));
391}
392
394{
395 mData.resize(floatsCount);
396 return mData.data();
397}
398
400{
401 FirstSample = 0;
402 NumSamples = 0;
403}
404
405namespace
406{
407template<size_t blockSize>
409 const float* input, int64_t from, size_t count,
411{
412 input = input + 3 * (from / blockSize);
413 count = RoundUpUnsafe(count, blockSize);
414
415 float min = summary.Min;
416 float max = summary.Max;
417 double squareSum = summary.SquaresSum;
418
419 for (size_t idx = 0; idx < count; ++idx)
420 {
421 min = std::min(min, *input++);
422 max = std::max(max, *input++);
423
424 const double rms = *input++;
425
426 squareSum += rms * rms * blockSize;
427 }
428
429 // By construction, min should be always not greater than max.
430 assert(min <= max);
431
432 summary.Min = min;
433 summary.Max = max;
434 summary.SquaresSum = squareSum;
435 summary.SumItemsCount += count * blockSize;
436}
437} // namespace
438
440 int64_t from, size_t samplesCount, const Summary& initializer) const noexcept
441{
442 from = from - FirstSample;
443 samplesCount =
444 std::min<size_t>(samplesCount, std::max<int64_t>(0, NumSamples - from));
445
446 const auto to = from + samplesCount;
447
448 const float* data =
449 static_cast<const float*>(static_cast<const void*>(mData.data()));
450
451 Summary summary = initializer;
452
453 summary.SamplesCount = samplesCount;
454
455 switch (DataType)
456 {
458 summary.SumItemsCount += samplesCount;
459
460 for (auto sampleIndex = from; sampleIndex < to; ++sampleIndex)
461 {
462 const float sample = data[sampleIndex];
463
464 summary.Min = std::min(summary.Min, sample);
465 summary.Max = std::max(summary.Max, sample);
466
467 summary.SquaresSum += double(sample) * double(sample);
468 }
469
470 assert(summary.Min <= summary.Max);
471
472 break;
474 processBlock<256>(data, from, samplesCount, summary);
475 break;
477 processBlock<64 * 1024>(data, from, samplesCount, summary);
478 break;
479 default:
480 break;
481 }
482
483 return summary;
484}
485
487{
488 if (prevElement == nullptr||prevElement->AwaitsEviction || AvailableColumns == 0)
489 return;
490
491 const auto prev = static_cast<WaveCacheElement*>(prevElement);
492
493 if (prev->AvailableColumns == 0)
494 return;
495
496 const auto prevLastColumn = prev->Data[prev->AvailableColumns - 1];
497 auto& firstColumn = Data[0];
498
499 bool updated = false;
500
501 if (prevLastColumn.min > firstColumn.max)
502 {
503 firstColumn.max = prevLastColumn.min;
504 updated = true;
505 }
506
507 if (prevLastColumn.max < firstColumn.min)
508 {
509 firstColumn.min = prevLastColumn.max;
510 updated = true;
511 }
512
513 if (updated)
514 firstColumn.rms =
515 std::clamp(firstColumn.rms, firstColumn.min, firstColumn.max);
516}
int min(int a, int b)
static const AudacityProject::AttachedObjects::RegisteredFactory key
auto RoundUpUnsafe(LType numerator, RType denominator) noexcept
Returns a rounded up integer result for numerator / denominator.
Definition: RoundUpUnsafe.h:21
constexpr sampleFormat floatSample
Definition: SampleFormat.h:45
char * samplePtr
Definition: SampleFormat.h:57
@ WaveDataCache
Time required to access the data cache.
static Stopwatch CreateStopwatch(SectionID section) noexcept
Create a Stopwatch for the section specified.
static constexpr uint32_t CacheElementWidth
double GetScaledSampleRate() const noexcept
Returns the sample rate associated with cache.
An object that sends messages to an open-ended list of subscribed callbacks.
Definition: Observer.h:108
sampleFormat Stored() const
Definition: SampleFormat.h:91
This allows multiple clips to be a part of one WaveTrack.
Definition: WaveClip.h:238
constSamplePtr GetAppendBuffer(size_t ii) const
Get one channel of the append buffer.
Definition: WaveClip.cpp:729
SampleFormats GetSampleFormats() const
Definition: WaveClip.cpp:687
size_t GetAppendBufferLen(size_t ii) const
Definition: WaveClip.cpp:423
Sequence * GetSequence(size_t ii)
Definition: WaveClip.h:571
std::function< bool(int64_t requiredSample, WaveCacheSampleBlock::Type dataType, WaveCacheSampleBlock &block)> DataProvider
Definition: WaveDataCache.h:96
DataProvider mProvider
WaveCacheSampleBlock mCachedBlock
WaveDataCache(const WaveClip &waveClip, int channelIndex)
bool InitializeElement(const GraphicsDataCacheKey &key, WaveCacheElement &element) override
const float * GetAppendBufferPointer(const WaveClip &clip, int channelIndex)
void FillBlocksFromAppendBuffer(const float *bufferSamples, size_t samplesCount, WaveCacheSampleBlock &outBlock)
bool FillBuffer(const WaveClip &clip, WaveCacheSampleBlock &outBlock, int channelIndex) noexcept
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
double GetRate(const Track &track)
Definition: TimeTrack.cpp:182
WaveDataCache::DataProvider MakeDefaultDataProvider(const WaveClip &clip, int channelIndex)
void processBlock(const float *input, int64_t from, size_t count, WaveCacheSampleBlock::Summary &summary)
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512
__finl float_x4 __vecc sqrt(const float_x4 &a)
void copy(const T *src, T *dst, int32_t n)
Definition: VectorOps.h:40
A base class for the for cache elements.
bool AwaitsEviction
This flag is used to simplify the eviction algorithm.
bool IsComplete
Cache implementation is responsible to set this flag when all the data of the item is filled.
A key into the graphics data cache.
An element of a cache that contains the waveform data.
Definition: WaveDataCache.h:81
size_t AvailableColumns
Definition: WaveDataCache.h:86
void Smooth(GraphicsDataCacheElementBase *prevElement) override
This method is called during the lookup when new items are inserted. prevElement can be nullptr....
Summary calculated over the requested range.
Definition: WaveDataCache.h:47
Helper structure used to transfer the data between the data and graphics layers.
Definition: WaveDataCache.h:27
Summary GetSummary(int64_t from, size_t samplesCount, const Summary &initializer) const noexcept
std::vector< float > mData
Definition: WaveDataCache.h:74
float * GetWritePointer(size_t floatsCount)
Gets a pointer to a data buffer enough to store floatsCount floats.
void Reset() noexcept
Type
Type of the data of the request.
Definition: WaveDataCache.h:30
@ Samples
Each element of the resulting array is a sample.
bool ContainsSample(int64_t sampleIndex) const noexcept
Checks if sample is in the range represented by this block.