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 return true;
84 }
85
86private:
87 const float* GetAppendBufferPointer(const WaveClip& clip, int channelIndex)
88 {
89 const auto currentFormat = clip.GetSampleFormats().Stored();
90 const auto appendBuffer =
91 static_cast<const void*>(clip.GetAppendBuffer(channelIndex));
92
93 if (currentFormat == floatSample)
94 return static_cast<const float*>(appendBuffer);
95
96 const auto samplesCount = clip.GetAppendBufferLen(channelIndex);
97 mConvertedAppendBufferData.resize(samplesCount);
98
99 if (currentFormat == int16Sample)
100 {
101 const auto int16Buffer = static_cast<const int16_t*>(appendBuffer);
102
103 for (size_t i = 0; i < samplesCount; ++i)
104 mConvertedAppendBufferData[i] = float(int16Buffer[i]) / (1 << 15);
105 }
106 else if (currentFormat == sampleFormat::int24Sample)
107 {
108 const auto int24Buffer = static_cast<const int32_t*>(appendBuffer);
109
110 for (size_t i = 0; i < samplesCount; ++i)
111 mConvertedAppendBufferData[i] = float(int24Buffer[i]) / (1 << 23);
112 }
113
114 return mConvertedAppendBufferData.data();
115 }
116
117 template<size_t blockSize>
119 const float* bufferSamples, size_t samplesCount,
120 WaveCacheSampleBlock& outBlock)
121 {
122 const size_t startingBlock = mLastProcessedSample / blockSize;
123 const size_t blocksCount = RoundUpUnsafe(samplesCount, blockSize);
124
125 for (size_t blockIndex = startingBlock; blockIndex < blocksCount;
126 ++blockIndex)
127 {
128 const size_t samplesInBlock =
129 std::min(blockSize, samplesCount - blockIndex * blockSize);
130
131 float min = std::numeric_limits<float>::infinity();
132 float max = -std::numeric_limits<float>::infinity();
133
134 double sqSum = 0.0;
135
136 auto samples = bufferSamples + blockIndex * blockSize;
137
138 for (size_t sampleIndex = 0; sampleIndex < samplesInBlock;
139 ++sampleIndex)
140 {
141 const float sample = *samples++;
142
143 min = std::min(min, sample);
144 max = std::max(max, sample);
145
146 const double dbl = sample;
147
148 sqSum += dbl * dbl;
149 }
150
151 const auto rms = static_cast<float>(std::sqrt(sqSum / samplesInBlock));
152
153 mCachedData[blockIndex] = { min, max, rms };
154 }
155
156 mLastProcessedSample = samplesCount;
157 auto ptr = outBlock.GetWritePointer(
158 sizeof(CacheItem) * blocksCount / sizeof(float));
159
160 std::memmove(ptr, mCachedData.data(), sizeof(CacheItem) * blocksCount);
161 }
162
165 };
166 sampleCount mFirstClipSampleID { 0 };
167
168 struct CacheItem final
169 {
170 float min;
171 float max;
172 float rms;
173 };
174
175 std::vector<CacheItem> mCachedData;
176 std::vector<float> mConvertedAppendBufferData;
177 size_t mLastProcessedSample { 0 };
178};
179
181MakeDefaultDataProvider(const WaveClip& clip, int channelIndex)
182{
183 return [sequence = clip.GetSequence(channelIndex), clip = &clip,
184 channelIndex, appendBufferHelper = AppendBufferHelper()](
185 int64_t requiredSample, WaveCacheSampleBlock::Type dataType,
186 WaveCacheSampleBlock& outBlock) mutable
187 {
188 if (requiredSample < 0)
189 return false;
190
191 if (requiredSample >= sequence->GetNumSamples())
192 {
193 requiredSample -= sequence->GetNumSamples().as_long_long();
194
195 if (requiredSample >= clip->GetAppendBufferLen(channelIndex))
196 return false;
197
198 outBlock.DataType = dataType;
199 outBlock.FirstSample = sequence->GetNumSamples().as_long_long();
200 outBlock.NumSamples = clip->GetAppendBufferLen(channelIndex);
201
202 return appendBufferHelper.FillBuffer(*clip, outBlock, channelIndex);
203 }
204
205 const auto blockIndex = sequence->FindBlock(requiredSample);
206 const auto& inputBlock = sequence->GetBlockArray()[blockIndex];
207
208 outBlock.FirstSample = inputBlock.start.as_long_long();
209 outBlock.NumSamples = inputBlock.sb->GetSampleCount();
210
211 switch (dataType)
212 {
214 {
215 samplePtr ptr = static_cast<samplePtr>(
216 static_cast<void*>(outBlock.GetWritePointer(outBlock.NumSamples)));
217
218 inputBlock.sb->GetSamples(
219 ptr, floatSample, 0, outBlock.NumSamples, false);
220 }
221 break;
223 {
224 size_t framesCount = RoundUpUnsafe(outBlock.NumSamples, 256);
225
226 float* ptr =
227 static_cast<float*>(outBlock.GetWritePointer(framesCount * 3));
228
229 inputBlock.sb->GetSummary256(ptr, 0, framesCount);
230 }
231 break;
233 {
234 size_t framesCount = RoundUpUnsafe(outBlock.NumSamples, 64 * 1024);
235
236 float* ptr =
237 static_cast<float*>(outBlock.GetWritePointer(framesCount * 3));
238
239 inputBlock.sb->GetSummary64k(ptr, 0, framesCount);
240 }
241 break;
242 default:
243 return false;
244 }
245
246 outBlock.DataType = dataType;
247
248 return true;
249 };
250}
251
252} // namespace
253
254WaveDataCache::WaveDataCache(const WaveClip& waveClip, int channelIndex)
256 waveClip.GetRate() / waveClip.GetStretchRatio(),
257 [] { return std::make_unique<WaveCacheElement>(); })
258 , mProvider { MakeDefaultDataProvider(waveClip, channelIndex) }
259 , mWaveClip { waveClip }
260 , mStretchChangedSubscription {
261 const_cast<WaveClip&>(waveClip)
263 [this](const StretchRatioChange&) {
264 SetScaledSampleRate(
265 mWaveClip.GetRate() / mWaveClip.GetStretchRatio());
266 })
267 }
268{
269}
270
273{
276
277 element.AvailableColumns = 0;
278
279 int64_t firstSample = key.FirstSample;
280
281 const size_t samplesPerColumn =
282 static_cast<size_t>(std::max(0.0, GetScaledSampleRate() / key.PixelsPerSecond));
283
284 const size_t elementSamplesCount =
285 samplesPerColumn * WaveDataCache::CacheElementWidth;
286 size_t processedSamples = 0;
287
288 const WaveCacheSampleBlock::Type blockType =
289 samplesPerColumn >= 64 * 1024 ?
291 (samplesPerColumn >= 256 ? WaveCacheSampleBlock::Type::MinMaxRMS256 :
293
294 if (blockType != mCachedBlock.DataType)
296
297 size_t columnIndex = 0;
298
299 for (; columnIndex < WaveDataCache::CacheElementWidth; ++columnIndex)
300 {
302 size_t samplesLeft = samplesPerColumn;
303
304 while (samplesLeft != 0)
305 {
306 if (!mCachedBlock.ContainsSample(firstSample))
307 if (!mProvider(firstSample, blockType, mCachedBlock))
308 break;
309
310 summary = mCachedBlock.GetSummary(firstSample, samplesLeft, summary);
311
312 samplesLeft -= summary.SamplesCount;
313 firstSample += summary.SamplesCount;
314 processedSamples += summary.SamplesCount;
315 }
316
317 if (summary.SamplesCount > 0)
318 {
319 auto& column = element.Data[columnIndex];
320
321 column.min = summary.Min;
322 column.max = summary.Max;
323
324 column.rms = std::sqrt(summary.SquaresSum / summary.SumItemsCount);
325 }
326
327 if (columnIndex > 0)
328 {
329 const auto prevColumn = element.Data[columnIndex - 1];
330 auto& column = element.Data[columnIndex];
331
332 bool updated = false;
333
334 if (prevColumn.min > column.max)
335 {
336 column.max = prevColumn.min;
337 updated = true;
338 }
339
340 if (prevColumn.max < column.min)
341 {
342 column.min = prevColumn.max;
343 updated = true;
344 }
345
346 if (updated)
347 column.rms = std::clamp(column.rms, column.min, column.max);
348 }
349
350 if (samplesLeft != 0)
351 {
352 ++columnIndex;
353 break;
354 }
355 }
356
357 element.AvailableColumns = columnIndex;
358 element.IsComplete = processedSamples == elementSamplesCount;
359
360 return processedSamples != 0;
361}
362
363bool WaveCacheSampleBlock::ContainsSample(int64_t sampleIndex) const noexcept
364{
365 return sampleIndex >= FirstSample &&
366 (sampleIndex < (FirstSample + NumSamples));
367}
368
370{
371 mData.resize(floatsCount);
372 return mData.data();
373}
374
376{
377 FirstSample = 0;
378 NumSamples = 0;
379}
380
381namespace
382{
383template<size_t blockSize>
385 const float* input, int64_t from, size_t count,
387{
388 input = input + 3 * (from / blockSize);
389 count = RoundUpUnsafe(count, blockSize);
390
391 float min = summary.Min;
392 float max = summary.Max;
393 double squareSum = summary.SquaresSum;
394
395 for (size_t idx = 0; idx < count; ++idx)
396 {
397 min = std::min(min, *input++);
398 max = std::max(max, *input++);
399
400 const double rms = *input++;
401
402 squareSum += rms * rms * blockSize;
403 }
404
405 // By construction, min should be always not greater than max.
406 assert(min <= max);
407
408 summary.Min = min;
409 summary.Max = max;
410 summary.SquaresSum = squareSum;
411 summary.SumItemsCount += count * blockSize;
412}
413} // namespace
414
416 int64_t from, size_t samplesCount, const Summary& initializer) const noexcept
417{
418 from = from - FirstSample;
419 samplesCount =
420 std::min<size_t>(samplesCount, std::max<int64_t>(0, NumSamples - from));
421
422 const auto to = from + samplesCount;
423
424 const float* data =
425 static_cast<const float*>(static_cast<const void*>(mData.data()));
426
427 Summary summary = initializer;
428
429 summary.SamplesCount = samplesCount;
430
431 switch (DataType)
432 {
434 summary.SumItemsCount += samplesCount;
435
436 for (auto sampleIndex = from; sampleIndex < to; ++sampleIndex)
437 {
438 const float sample = data[sampleIndex];
439
440 summary.Min = std::min(summary.Min, sample);
441 summary.Max = std::max(summary.Max, sample);
442
443 summary.SquaresSum += double(sample) * double(sample);
444 }
445
446 assert(summary.Min <= summary.Max);
447
448 break;
450 processBlock<256>(data, from, samplesCount, summary);
451 break;
453 processBlock<64 * 1024>(data, from, samplesCount, summary);
454 break;
455 default:
456 break;
457 }
458
459 return summary;
460}
461
463{
464 if (prevElement == nullptr||prevElement->AwaitsEviction || AvailableColumns == 0)
465 return;
466
467 const auto prev = static_cast<WaveCacheElement*>(prevElement);
468
469 if (prev->AvailableColumns == 0)
470 return;
471
472 const auto prevLastColumn = prev->Data[prev->AvailableColumns - 1];
473 auto& firstColumn = Data[0];
474
475 bool updated = false;
476
477 if (prevLastColumn.min > firstColumn.max)
478 {
479 firstColumn.max = prevLastColumn.min;
480 updated = true;
481 }
482
483 if (prevLastColumn.max < firstColumn.min)
484 {
485 firstColumn.min = prevLastColumn.max;
486 updated = true;
487 }
488
489 if (updated)
490 firstColumn.rms =
491 std::clamp(firstColumn.rms, firstColumn.min, firstColumn.max);
492}
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)
__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.