Audacity 3.2.0
WaveBitmapCache.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 WaveBitmapCache.h
7
8 Dmitry Vedenko
9
10**********************************************************************/
11#include "WaveBitmapCache.h"
12
13#include <cassert>
14#include <cmath>
15#include <cstring>
16
17#include "ZoomInfo.h"
18
20
21#include "Envelope.h"
22#include "FrameStatistics.h"
23
24#include "WaveClip.h"
25
26// The worst case scenario is:
27// blank -> background -> min -> rms -> max -> backgroud -> blank
28// So we have 7 stops
29
30constexpr size_t ColorFunctionStops = 7;
31
32struct Triplet final
33{
34 Triplet() = default;
35
36 explicit Triplet(graphics::Color color)
37 : r(color.GetRed())
38 , g(color.GetGreen())
39 , b(color.GetBlue())
40 {
41 }
42
44 {
45 r = color.GetRed();
46 g = color.GetGreen();
47 b = color.GetBlue();
48 }
49
50 Triplet(const Triplet&) = default;
51 Triplet(Triplet&&) = default;
52
53 uint8_t r { 0 };
54 uint8_t g { 0 };
55 uint8_t b { 0 };
56
57 uint8_t unused { 0 };
58};
59
60struct ColorFunction final
61{
62 std::array<std::pair<Triplet, uint32_t>, ColorFunctionStops> Stops;
63
64 Triplet GetColor(uint32_t row, Triplet defaultColor) const noexcept
65 {
66 for (auto stop : Stops)
67 {
68 if (row < stop.second)
69 return stop.first;
70 }
71
72 return defaultColor;
73 }
74
75 void SetStop(size_t index, graphics::Color color, uint32_t position)
76 {
77 assert(index < Stops.size());
78
79 Stops[index].first.SetColor(color);
80 Stops[index].second = position;
81 }
82};
83
85{
86 explicit LookupHelper(std::shared_ptr <WaveDataCache> dataCache)
87 : DataCache(std::move(dataCache))
88 {
89 }
90
92 {
93 DataCache->UpdateViewportWidth(cache->GetMaxViewportWidth());
94
95 auto result = DataCache->PerformLookup(key);
96
97 if (result == nullptr)
98 return false;
99
102
103 const auto columnsCount = result->AvailableColumns;
104
105 if (cache->mPaintParamters.DBScale)
106 {
107 auto GetDBValue =
108 [dbRange = cache->mPaintParamters.DBRange](float value)
109 {
110 float sign = (value >= 0 ? 1 : -1);
111
112 if (value != 0.)
113 {
114 float db = LINEAR_TO_DB(fabs(value));
115 value = (db + dbRange) / dbRange;
116
117 if (value < 0.0)
118 value = 0.0;
119
120 value *= sign;
121 }
122
123 return value;
124 };
125
126 for (size_t column = 0; column < columnsCount; ++column)
127 {
128 auto oldColumn = result->Data[column];
129
130 DBRemappedColumns[column] = { GetDBValue(oldColumn.min),
131 GetDBValue(oldColumn.max),
132 GetDBValue(oldColumn.rms) };
133 }
134 }
135
136 auto GetRowFromValue =
137 [min = cache->mPaintParamters.Min, max = cache->mPaintParamters.Max,
138 height = cache->mPaintParamters.Height](float value)
139 {
140 value = (max - value) / (max - min);
141 return static_cast<int>(value * (height - 1) + 0.5);
142 };
143
144 const auto height = cache->mPaintParamters.Height;
145
146 const auto zeroLineY = GetRowFromValue(.0f);
147
148 auto inputData = cache->mPaintParamters.DBScale ?
149 DBRemappedColumns.data() :
150 result->Data.data();
151
152 auto envelope = cache->mEnvelope;
153
154 if (
155 envelope != nullptr && (envelope->GetNumberOfPoints() > 0 ||
156 envelope->GetDefaultValue() != 1.0))
157 {
158 envelope->GetValues(
159 EnvelopeValues.data(), static_cast<int>(EnvelopeValues.size()),
160 key.FirstSample / cache->GetScaledSampleRate() +
161 envelope->GetOffset(),
162 1.0 / key.PixelsPerSecond);
163
164 for (size_t column = 0; column < columnsCount; ++column)
165 {
166 const auto columnData = inputData[column];
167 const float envelopeValue = EnvelopeValues[column];
168
169 EnvRemappedColumns[column] = {
170 columnData.min * envelopeValue,
171 columnData.max * envelopeValue,
172 columnData.rms * envelopeValue
173 };
174 }
175
176 inputData = EnvRemappedColumns.data();
177 }
178
179 const bool hasTopBlankArea = cache->mPaintParamters.Max > 1.0;
180 const auto globalMaxRow = GetRowFromValue(cache->mPaintParamters.Max);
181 const auto globalMinRow = GetRowFromValue(cache->mPaintParamters.Min) + 1;
182
183 const auto blankColor = cache->mPaintParamters.BlankColor;
184 const auto zeroLineColor = cache->mPaintParamters.ZeroLineColor;
185
186 const auto backgroundColors = cache->mPaintParamters.BackgroundColors;
187 const auto sampleColors = cache->mPaintParamters.SampleColors;
188 const auto rmsColors = cache->mPaintParamters.RMSColors;
189 const auto clipColors = cache->mPaintParamters.ClippingColors;
190 const auto showRMS = cache->mPaintParamters.ShowRMS;
191
192 auto firstPixel = int64_t(key.FirstSample / cache->GetScaledSampleRate() * key.PixelsPerSecond + 0.5);
193
194 const auto selFirst = cache->mSelection.FirstPixel;
195 const auto selLast = cache->mSelection.LastPixel;
196
197 const bool showClipping = cache->mPaintParamters.ShowClipping;
198
199 for (size_t column = 0; column < columnsCount; ++column)
200 {
201 const bool selected = firstPixel >= selFirst && firstPixel < selLast;
202 ++firstPixel;
203
204 const auto columnData = inputData[column];
205 auto& function = ColorFunctions[column];
206
207 if (showClipping && (columnData.min <= -MAX_AUDIO || columnData.max >= MAX_AUDIO))
208 {
209 function.SetStop(
210 0, selected ? clipColors.Selected : clipColors.Normal, height);
211
212 continue;
213 }
214
215 size_t stopIndex = 0;
216
217 if (hasTopBlankArea)
218 function.SetStop(stopIndex++, blankColor, globalMaxRow);
219
220 const auto maxRow = GetRowFromValue(columnData.max);
221
222 if(zeroLineY > globalMaxRow && maxRow > zeroLineY)
223 {
224 //Waveform is below 0
225 function.SetStop(
226 stopIndex++,
227 selected ? backgroundColors.Selected : backgroundColors.Normal,
228 zeroLineY);
229 function.SetStop(
230 stopIndex++,
231 zeroLineColor,
232 zeroLineY + 1
233 );
234 }
235
236 if (maxRow > 0)
237 {
238 function.SetStop(
239 stopIndex++,
240 selected ? backgroundColors.Selected : backgroundColors.Normal,
241 maxRow);
242 }
243
244 if (maxRow >= height)
245 continue;
246
247 if (showRMS)
248 {
249 const auto positiveRMSRow = GetRowFromValue(columnData.rms);
250
251 if (maxRow < positiveRMSRow)
252 {
253 function.SetStop(
254 stopIndex++,
255 selected ? sampleColors.Selected : sampleColors.Normal,
256 positiveRMSRow);
257 }
258
259 if (positiveRMSRow >= height)
260 continue;
261
262 const auto negativeRMSRow =
263 GetRowFromValue(std::max(-columnData.rms, columnData.min));
264
265 if (positiveRMSRow < negativeRMSRow)
266 {
267 function.SetStop(
268 stopIndex++, selected ? rmsColors.Selected : rmsColors.Normal,
269 negativeRMSRow);
270 }
271
272 if (negativeRMSRow >= height)
273 continue;
274 }
275
276 const auto minRow = GetRowFromValue(columnData.min);
277
278 // if minRow == maxRow - we want to display it as a single pixel
279 function.SetStop(
280 stopIndex++, selected ? sampleColors.Selected : sampleColors.Normal,
281 minRow != maxRow ? minRow : minRow + 1);
282
283 if(zeroLineY < globalMinRow && minRow < zeroLineY)
284 {
285 //Waveform is above 0
286 function.SetStop(
287 stopIndex++,
288 selected ? backgroundColors.Selected : backgroundColors.Normal,
289 zeroLineY);
290 function.SetStop(
291 stopIndex++,
292 zeroLineColor,
293 zeroLineY + 1);
294 }
295
296 if (minRow < globalMinRow)
297 {
298 function.SetStop(
299 stopIndex++,
300 selected ? backgroundColors.Selected : backgroundColors.Normal,
301 globalMinRow);
302 }
303
304 if (globalMinRow < height)
305 function.SetStop(stopIndex++, blankColor, height);
306 }
307
308 AvailableColumns = columnsCount;
309 IsComplete = result->IsComplete;
310
311 return true;
312 }
313
314 std::shared_ptr<WaveDataCache> DataCache;
315
316 std::array<ColorFunction, GraphicsDataCacheBase::CacheElementWidth>
318
320
321 std::array<double, GraphicsDataCacheBase::CacheElementWidth>
324
325 size_t AvailableColumns { 0 };
326 bool IsComplete { 0 };
327};
328
330 const WaveClip& waveClip, std::shared_ptr<WaveDataCache> dataCache,
331 ElementFactory elementFactory)
332 : GraphicsDataCache { waveClip.GetRate() / waveClip.GetStretchRatio(),
333 std::move(elementFactory) }
334 , mLookupHelper { std::make_unique<LookupHelper>(std::move(dataCache)) }
335 , mWaveClip { waveClip }
336 , mStretchChangedSubscription {
337 const_cast<WaveClip&>(waveClip)
338 .Observer::Publisher<StretchRatioChange>::Subscribe(
339 [this](const StretchRatioChange&) {
342 })
343 }
344{
345}
346
348
350
351
354{
355 if (mPaintParamters != params)
356 {
358 mEnvelope = params.AttachedEnvelope;
359 mEnvelopeVersion = mEnvelope != nullptr ? mEnvelope->GetVersion() : 0;
360
361 Invalidate();
362 }
363
364 return *this;
365}
366
368 const ZoomInfo& zoomInfo, double t0, double t1, bool selected)
369{
370 const auto empty = !selected || t0 > t1 ||
371 ((t1 - t0) < std::numeric_limits<double>::epsilon());
372
373 const auto first = empty ? int64_t(-1) : zoomInfo.TimeToPosition(t0);
374 const auto last =
375 empty ? int64_t(-1) : std::max(zoomInfo.TimeToPosition(t1), first + 1);
376
377 if (mSelection.FirstPixel != first || mSelection.LastPixel != last)
378 {
379 mSelection.FirstPixel = first;
380 mSelection.LastPixel = last;
381
382 Invalidate();
383 }
384
385 return *this;
386}
387
388void WaveBitmapCache::CheckCache(const ZoomInfo&, double, double)
389{
390 if (mEnvelope != nullptr && mEnvelopeVersion != mEnvelope->GetVersion())
391 {
393 Invalidate();
394 }
395}
396
399{
400 if (mPaintParamters.Height == 0)
401 return false;
402
403 if (!mLookupHelper->PerformLookup(this, key))
404 {
405 const auto width = 1;
406 const auto height = mPaintParamters.Height;
407 const auto bytes = element.Allocate(width, height);
408 std::memset(bytes, 0, width * height * 3);
409 return true;
410 }
411
414
415 const auto columnsCount = mLookupHelper->AvailableColumns;
416
417 const auto defaultColor = Triplet(mPaintParamters.BlankColor);
418
419 const auto height = static_cast<uint32_t>(mPaintParamters.Height);
420
421 auto rowData = element.Allocate(columnsCount, height);
422
423 for (uint32_t row = 0; row < height; ++row)
424 {
425 auto colorFunction = mLookupHelper->ColorFunctions.data();
426
427 for (size_t pixel = 0; pixel < columnsCount; ++pixel)
428 {
429 const auto color = colorFunction->GetColor(row, defaultColor);
430
431 *rowData++ = color.r;
432 *rowData++ = color.g;
433 *rowData++ = color.b;
434
435 ++colorFunction;
436 }
437 }
438
439 element.AvailableColumns = columnsCount;
440 element.IsComplete = mLookupHelper->IsComplete;
441
442 return true;
443}
int min(int a, int b)
EffectDistortionSettings params
#define MAX_AUDIO
Definition: MemoryX.h:341
#define LINEAR_TO_DB(x)
Definition: MemoryX.h:339
static const AudacityProject::AttachedObjects::RegisteredFactory key
constexpr size_t ColorFunctionStops
size_t GetVersion() const
Definition: Envelope.cpp:733
void GetValues(double *buffer, int len, double t0, double tstep) const
Get many envelope points at once.
Definition: Envelope.cpp:981
@ WaveBitmapCachePreprocess
Time required to build the structures required for the bitmap cache population.
@ WaveBitmapCache
Time required to access the wave bitmaps cache.
static Stopwatch CreateStopwatch(SectionID section) noexcept
Create a Stopwatch for the section specified.
int64_t GetMaxViewportWidth() const noexcept
void Invalidate()
Invalidate the cache content.
double GetScaledSampleRate() const noexcept
Returns the sample rate associated with cache.
void SetScaledSampleRate(double scaledSampleRate)
An element, that contains a rasterized bitmap matching the WaveDataCacheElement.
~WaveBitmapCacheElement() override
virtual uint8_t * Allocate(size_t width, size_t height)=0
Cache containing rasterized bitmaps representing the waveform.
WavePaintParameters mPaintParamters
const Envelope * mEnvelope
struct WaveBitmapCache::@133 mSelection
WaveBitmapCache & SetSelection(const ZoomInfo &zoomInfo, double t0, double t1, bool selected)
WaveBitmapCache & SetPaintParameters(const WavePaintParameters &params)
std::unique_ptr< LookupHelper > mLookupHelper
void CheckCache(const ZoomInfo &, double, double) override
~WaveBitmapCache() override
WaveBitmapCache(const WaveClip &waveClip, std::shared_ptr< WaveDataCache > dataCache, ElementFactory elementFactory)
const WaveClip & mWaveClip
bool InitializeElement(const GraphicsDataCacheKey &key, WaveBitmapCacheElement &element) override
This allows multiple clips to be a part of one WaveTrack.
Definition: WaveClip.h:238
double GetStretchRatio() const override
Definition: WaveClip.cpp:625
int GetRate() const override
Definition: WaveClip.h:337
int64 TimeToPosition(double time, int64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ZoomInfo.cpp:44
double GetRate(const Track &track)
Definition: TimeTrack.cpp:182
STL namespace.
std::array< std::pair< Triplet, uint32_t >, ColorFunctionStops > Stops
Triplet GetColor(uint32_t row, Triplet defaultColor) const noexcept
void SetStop(size_t index, graphics::Color color, uint32_t position)
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.
Triplet(Triplet &&)=default
Triplet()=default
uint8_t unused
void SetColor(graphics::Color color)
Triplet(graphics::Color color)
Triplet(const Triplet &)=default
std::array< double, GraphicsDataCacheBase::CacheElementWidth > EnvelopeValues
WaveCacheElement::Columns DBRemappedColumns
WaveCacheElement::Columns EnvRemappedColumns
bool PerformLookup(WaveBitmapCache *cache, GraphicsDataCacheKey key)
LookupHelper(std::shared_ptr< WaveDataCache > dataCache)
std::shared_ptr< WaveDataCache > DataCache
std::array< ColorFunction, GraphicsDataCacheBase::CacheElementWidth > ColorFunctions
std::array< WaveDisplayColumn, GraphicsDataCacheBase::CacheElementWidth > Columns
Definition: WaveDataCache.h:83
Parameters for the waveform painting.
bool ShowRMS
True, if we paint RMS values on top of min and max.
graphics::Color BlankColor
Color outside the waveform area.
ColorPair RMSColors
Color of the (-rms, +rms) line.
bool DBScale
True, if we paint in dB scale.
bool ShowClipping
True, if we mark clipped values.
double DBRange
Decibel range.
graphics::Color ZeroLineColor
double Max
Max value used to clip the output.
ColorPair SampleColors
Color of the (min, max) line.
ColorPair ClippingColors
Color for the columns where clipping has occurred.
ColorPair BackgroundColors
Waveform background color.
int Height
Height of the of clip on screen.
double Min
Min value used to clip the output.
Class for storing color in 32-bit format.
Definition: Color.h:29
constexpr uint8_t GetBlue() const noexcept
Returns blue component of this color.
Definition: Color.h:99
constexpr uint8_t GetGreen() const noexcept
Returns green component of this color.
Definition: Color.h:93
constexpr uint8_t GetRed() const noexcept
Returns red component of this color.
Definition: Color.h:87