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 auto inputData = cache->mPaintParamters.DBScale ?
147 DBRemappedColumns.data() :
148 result->Data.data();
149
150 auto envelope = cache->mEnvelope;
151
152 if (
153 envelope != nullptr && (envelope->GetNumberOfPoints() > 0 ||
154 envelope->GetDefaultValue() != 1.0))
155 {
156 envelope->GetValues(
157 EnvelopeValues.data(), static_cast<int>(EnvelopeValues.size()),
158 key.FirstSample / cache->GetScaledSampleRate(),
159 1.0 / key.PixelsPerSecond);
160
161 for (size_t column = 0; column < columnsCount; ++column)
162 {
163 const auto columnData = inputData[column];
164 const float envelopeValue = EnvelopeValues[column];
165
166 EnvRemappedColumns[column] = {
167 columnData.min * envelopeValue,
168 columnData.max * envelopeValue,
169 columnData.rms * envelopeValue
170 };
171 }
172
173 inputData = EnvRemappedColumns.data();
174 }
175
176 const bool hasTopBlankArea = cache->mPaintParamters.Max > 1.0;
177 const auto globalMaxRow = GetRowFromValue(cache->mPaintParamters.Max);
178 const auto globalMinRow = GetRowFromValue(cache->mPaintParamters.Min) + 1;
179
180 const auto blankColor = cache->mPaintParamters.BlankColor;
181
182 const auto backgroundColors = cache->mPaintParamters.BackgroundColors;
183 const auto sampleColors = cache->mPaintParamters.SampleColors;
184 const auto rmsColors = cache->mPaintParamters.RMSColors;
185 const auto clipColors = cache->mPaintParamters.ClippingColors;
186 const auto showRMS = cache->mPaintParamters.ShowRMS;
187
188 auto firstPixel = int64_t(key.FirstSample / cache->GetScaledSampleRate() * key.PixelsPerSecond + 0.5);
189
190 const auto selFirst = cache->mSelection.FirstPixel;
191 const auto selLast = cache->mSelection.LastPixel;
192
193 const bool showClipping = cache->mPaintParamters.ShowClipping;
194
195 for (size_t column = 0; column < columnsCount; ++column)
196 {
197 const bool selected = firstPixel >= selFirst && firstPixel < selLast;
198 ++firstPixel;
199
200 const auto columnData = inputData[column];
201 auto& function = ColorFunctions[column];
202
203 if (showClipping && (columnData.min <= -MAX_AUDIO || columnData.max >= MAX_AUDIO))
204 {
205 function.SetStop(
206 0, selected ? clipColors.Selected : clipColors.Normal, height);
207
208 continue;
209 }
210
211 size_t stopIndex = 0;
212
213 if (hasTopBlankArea)
214 function.SetStop(stopIndex++, blankColor, globalMaxRow);
215
216 const auto maxRow = GetRowFromValue(columnData.max);
217
218 if (maxRow > 0)
219 {
220 function.SetStop(
221 stopIndex++,
222 selected ? backgroundColors.Selected : backgroundColors.Normal,
223 maxRow);
224 }
225
226 if (maxRow >= height)
227 continue;
228
229 if (showRMS)
230 {
231 const auto positiveRMSRow = GetRowFromValue(columnData.rms);
232
233 if (maxRow < positiveRMSRow)
234 {
235 function.SetStop(
236 stopIndex++,
237 selected ? sampleColors.Selected : sampleColors.Normal,
238 positiveRMSRow);
239 }
240
241 if (positiveRMSRow >= height)
242 continue;
243
244 const auto negativeRMSRow =
245 GetRowFromValue(std::max(-columnData.rms, columnData.min));
246
247 if (positiveRMSRow < negativeRMSRow)
248 {
249 function.SetStop(
250 stopIndex++, selected ? rmsColors.Selected : rmsColors.Normal,
251 negativeRMSRow);
252 }
253
254 if (negativeRMSRow >= height)
255 continue;
256 }
257
258 const auto minRow = GetRowFromValue(columnData.min);
259
260 // if minRow == maxRow - we want to display it as a single pixel
261 function.SetStop(
262 stopIndex++, selected ? sampleColors.Selected : sampleColors.Normal,
263 minRow != maxRow ? minRow : minRow + 1);
264
265 if (minRow < globalMinRow)
266 {
267 function.SetStop(
268 stopIndex++,
269 selected ? backgroundColors.Selected : backgroundColors.Normal,
270 globalMinRow);
271 }
272
273 if (globalMinRow < height)
274 function.SetStop(stopIndex++, blankColor, height);
275 }
276
277 AvailableColumns = columnsCount;
278 IsComplete = result->IsComplete;
279
280 return true;
281 }
282
283 std::shared_ptr<WaveDataCache> DataCache;
284
285 std::array<ColorFunction, GraphicsDataCacheBase::CacheElementWidth>
287
289
290 std::array<double, GraphicsDataCacheBase::CacheElementWidth>
293
294 size_t AvailableColumns { 0 };
295 bool IsComplete { 0 };
296};
297
299 const WaveClip& waveClip, std::shared_ptr<WaveDataCache> dataCache,
300 ElementFactory elementFactory)
301 : GraphicsDataCache { waveClip.GetRate() / waveClip.GetStretchRatio(),
302 std::move(elementFactory) }
303 , mLookupHelper { std::make_unique<LookupHelper>(std::move(dataCache)) }
304 , mWaveClip { waveClip }
305 , mStretchChangedSubscription {
306 const_cast<WaveClip&>(waveClip)
307 .Observer::Publisher<StretchRatioChange>::Subscribe(
308 [this](const StretchRatioChange&) {
311 })
312 }
313{
314}
315
317
319
320
323{
324 if (mPaintParamters != params)
325 {
327 mEnvelope = params.AttachedEnvelope;
328 mEnvelopeVersion = mEnvelope != nullptr ? mEnvelope->GetVersion() : 0;
329
330 Invalidate();
331 }
332
333 return *this;
334}
335
337 const ZoomInfo& zoomInfo, double t0, double t1, bool selected)
338{
339 const auto empty = !selected || t0 > t1 ||
340 ((t1 - t0) < std::numeric_limits<double>::epsilon());
341
342 const auto first = empty ? int64_t(-1) : zoomInfo.TimeToPosition(t0);
343 const auto last =
344 empty ? int64_t(-1) : std::max(zoomInfo.TimeToPosition(t1), first + 1);
345
346 if (mSelection.FirstPixel != first || mSelection.LastPixel != last)
347 {
348 mSelection.FirstPixel = first;
349 mSelection.LastPixel = last;
350
351 Invalidate();
352 }
353
354 return *this;
355}
356
357void WaveBitmapCache::CheckCache(const ZoomInfo&, double, double)
358{
359 if (mEnvelope != nullptr && mEnvelopeVersion != mEnvelope->GetVersion())
360 {
362 Invalidate();
363 }
364}
365
368{
369 if (mPaintParamters.Height == 0)
370 return false;
371
372 if (!mLookupHelper->PerformLookup(this, key))
373 {
374 const auto width = 1;
375 const auto height = mPaintParamters.Height;
376 const auto bytes = element.Allocate(width, height);
377 std::memset(bytes, 0, width * height * 3);
378 return true;
379 }
380
383
384 const auto columnsCount = mLookupHelper->AvailableColumns;
385
386 const auto defaultColor = Triplet(mPaintParamters.BlankColor);
387
388 const auto height = static_cast<uint32_t>(mPaintParamters.Height);
389
390 auto rowData = element.Allocate(columnsCount, height);
391
392 for (uint32_t row = 0; row < height; ++row)
393 {
394 auto colorFunction = mLookupHelper->ColorFunctions.data();
395
396 for (size_t pixel = 0; pixel < columnsCount; ++pixel)
397 {
398 const auto color = colorFunction->GetColor(row, defaultColor);
399
400 *rowData++ = color.r;
401 *rowData++ = color.g;
402 *rowData++ = color.b;
403
404 ++colorFunction;
405 }
406 }
407
408 element.AvailableColumns = columnsCount;
409 element.IsComplete = mLookupHelper->IsComplete;
410
411 return true;
412}
int min(int a, int b)
EffectDistortionSettings params
Definition: Distortion.cpp:77
#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
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)
struct WaveBitmapCache::@136 mSelection
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.
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