Skip to content

Commit 65bbb9d

Browse files
committed
First stage rework of Meter.
1 parent 94abb31 commit 65bbb9d

File tree

2 files changed

+158
-78
lines changed

2 files changed

+158
-78
lines changed

modules/squarepine_audio/graphics/Meter.cpp

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//==============================================================================
12
double DecibelHelpers::calculateSnapPoint (double snapDb) noexcept
23
{
34
return decibelsToMeterProportion (snapDb, minSliderLevelDb, maxSliderLevelDb);
@@ -41,60 +42,83 @@ double DecibelHelpers::curvedToLinear (double curvedValue) noexcept
4142
}
4243

4344
//==============================================================================
44-
Meter::Meter (Model* model_) :
45+
Meter::Meter (MeterModel* model_) :
4546
model (model_)
4647
{
4748
channels.resize (2);
4849
}
4950

50-
void Meter::setModel (Model* model_)
51+
//==============================================================================
52+
void Meter::setMeterModel (MeterModel* newModel)
5153
{
52-
if (model != model_)
54+
if (model != newModel)
5355
{
54-
model = model_;
55-
refresh();
56+
assignModelPtr (newModel);
57+
if (refresh())
58+
repaint();
5659
}
5760
}
5861

59-
#if 0
60-
void Meter::initVolumeGradient (int width, int height, bool isVertical)
62+
void Meter::assignModelPtr (MeterModel* newModel)
6163
{
62-
if (width <= 0 || height <= 0)
63-
return;
64+
model = newModel;
6465

65-
ColourGradient gradient;
66-
const auto low = (float) DecibelHelpers::decibelsToMeterProportion (-18.0);
67-
const auto mid = (float) DecibelHelpers::decibelsToMeterProportion (-9.0);
68-
const auto high = (float) DecibelHelpers::decibelsToMeterProportion (0.0);
66+
#if ! JUCE_DISABLE_ASSERTIONS
67+
weakModelPtr = model != nullptr ? model->sharedState : nullptr;
68+
#endif
69+
}
6970

70-
if (isVertical)
71-
{
72-
gradient = ColourGradient::vertical (colourHighIntensity, 0.0f, colourLowIntensity, (float) height);
73-
gradient.addColour (1.0f - high, colourHighIntensity);
74-
gradient.addColour (1.0f - mid, colourMediumIntensity);
75-
gradient.addColour (1.0f - low, colourLowIntensity);
76-
}
77-
else
78-
{
79-
gradient = ColourGradient::horizontal (colourLowIntensity, 0.0f, colourHighIntensity, (float) width);
80-
gradient.addColour (high, colourHighIntensity);
81-
gradient.addColour (mid, colourMediumIntensity);
82-
gradient.addColour (low, colourLowIntensity);
83-
}
71+
#if ! JUCE_DISABLE_ASSERTIONS
8472

85-
gradientImage = { Image::RGB, width, height, false };
86-
Graphics g (gradientImage);
87-
g.setGradientFill (gradient);
88-
g.fillAll();
73+
void Meter::checkModelPtrIsValid() const
74+
{
75+
/** If this is hit, the model was destroyed while the Meter was still using it.
76+
You should ensure that the model remains alive for as long as the Meter holds a pointer to it.
77+
If this assertion is hit in the destructor of a Meter instance, do one of the following:
78+
- Adjust the order in which your destructors run, so that the Meter destructor runs
79+
before the destructor of your MeterModel, or
80+
- Call Meter::setMeterModel (nullptr) before destroying your ListBoxModel.
81+
*/
82+
jassert ((model == nullptr) == (weakModelPtr.lock() == nullptr));
8983
}
84+
9085
#endif
9186

9287
void Meter::resized()
9388
{
89+
gradient = {};
90+
91+
if (model == nullptr)
92+
return;
93+
94+
auto positions = model->getColourPositions();
95+
if (positions.size() < 2)
96+
{
97+
jassertfalse;
98+
return;
99+
}
100+
101+
std::sort (std::begin (positions), std::end (positions),
102+
[] (const auto& lhs, const auto& rhs) { return lhs.decibels < rhs.decibels; });
103+
104+
const auto b = getLocalBounds();
105+
const bool isHorizontal = model->isHorizontal();
106+
if (isHorizontal)
107+
gradient = ColourGradient::horizontal (positions.front().colour, positions.back().colour, b);
108+
else
109+
gradient = ColourGradient::vertical (positions.front().colour, positions.back().colour, b);
110+
111+
for (size_t i = 1; i < (positions.size() - 1); ++i)
112+
positions[i].addToGradient (gradient, isHorizontal);
94113
}
95114

96-
void Meter::paint (Graphics&)
115+
void Meter::paint (Graphics& g)
97116
{
117+
if (gradient.getNumColours() <= 0)
118+
return; // Nothing to draw.
119+
120+
g.setGradientFill (gradient);
121+
g.fillAll();
98122
}
99123

100124
bool Meter::refresh()

modules/squarepine_audio/graphics/Meter.h

Lines changed: 102 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/** */
2-
struct DecibelHelpers
2+
struct DecibelHelpers final
33
{
44
enum
55
{
@@ -98,66 +98,114 @@ struct DecibelHelpers
9898

9999
//==============================================================================
100100
/** */
101-
class Meter final : public Component
101+
class MeterModel
102102
{
103103
public:
104-
//==============================================================================
105104
/** */
106-
class Model
107-
{
108-
public:
109-
/** */
110-
virtual ~Model() noexcept = default;
105+
MeterModel() noexcept = default;
111106

112-
/** @returns the expiration time of the maximum meter level, after which it decays. */
113-
virtual int64 getExpiryTimeMs() const noexcept { return 3000; }
107+
/** */
108+
virtual ~MeterModel() noexcept = default;
114109

115-
/** @returns */
116-
virtual bool needsMaxLevel() const noexcept { return false; }
110+
//==============================================================================
111+
/** @returns the expiration time of the maximum meter level, after which it decays. */
112+
virtual int64 getExpiryTimeMs() const noexcept { return 3000; }
117113

118-
/** @returns */
119-
virtual bool isHorizontal() const noexcept { return false; }
114+
/** @returns */
115+
virtual bool needsMaxLevel() const noexcept { return false; }
120116

121-
/** @returns */
122-
virtual Array<float> getChannelLevels() const = 0;
117+
/** @returns */
118+
virtual bool isHorizontal() const noexcept { return false; }
123119

124-
/** */
125-
struct ColourPosition final
126-
{
127-
ColourPosition() noexcept = default;
120+
//==============================================================================
121+
/** @returns an array where each index corresponds to a channel's peak
122+
(or whatever you've calculated).
128123
129-
ColourPosition (Colour c, double d) noexcept :
130-
colour (c),
131-
decibels (d)
132-
{
133-
}
124+
In other words, the first index should be the left channel's peak,
125+
the next value should be for the right channel, and so on (as needed).
126+
*/
127+
virtual Array<float> getChannelLevels() const = 0;
134128

135-
Colour colour;
136-
double decibels = 0.0;
137-
};
129+
//==============================================================================
130+
/** */
131+
struct ColourPosition final
132+
{
133+
ColourPosition() = default;
134+
ColourPosition (const ColourPosition&) = default;
135+
ColourPosition (ColourPosition&&) = default;
136+
~ColourPosition() = default;
137+
ColourPosition& operator= (const ColourPosition&) = default;
138+
ColourPosition& operator= (ColourPosition&&) = default;
139+
140+
ColourPosition (Colour c, double db) :
141+
colour (c),
142+
decibels (db)
143+
{
144+
}
138145

139-
/** @returns */
140-
virtual std::array<ColourPosition, 3> getColourPositions() const
146+
void addToGradient (ColourGradient& destination,
147+
bool isHorizontal)
141148
{
142-
return
143-
{
144-
ColourPosition (Colours::red, 0.0),
145-
ColourPosition (Colours::yellow, -9.0),
146-
ColourPosition (Colours::green, -18.0)
147-
};
149+
const auto v = DecibelHelpers::decibelsToMeterProportion (decibels);
150+
151+
if (isHorizontal)
152+
destination.addColour (v, colour);
153+
else
154+
destination.addColour (1.0 - v, colour);
148155
}
156+
157+
Colour colour = Colours::black;
158+
double decibels = 0.0;
149159
};
150160

161+
/** @returns */
162+
virtual std::vector<ColourPosition> getColourPositions() const
163+
{
164+
return
165+
{
166+
{ Colours::red, 0.0 },
167+
{ Colours::yellow, -9.0 },
168+
{ Colours::green, -18.0 }
169+
};
170+
}
171+
172+
private:
173+
#if ! JUCE_DISABLE_ASSERTIONS
174+
friend class Meter;
175+
struct Empty {};
176+
std::shared_ptr<Empty> sharedState = std::make_shared<Empty>();
177+
#endif
178+
};
179+
180+
//==============================================================================
181+
/** */
182+
class Meter final : public Component
183+
{
184+
public:
185+
151186
//==============================================================================
152187
/** */
153-
Meter (Model* model_ = nullptr);
188+
Meter (MeterModel* meterModel = nullptr);
154189

155190
//==============================================================================
156-
/** */
157-
void setModel (Model*);
191+
/** Changes the current data model to display.
158192
159-
/** */
160-
Model* getModel() const noexcept { return model; }
193+
The MeterModel instance must stay alive for as long as the Meter
194+
holds a pointer to it. Be careful to destroy the Meter before the
195+
MeterModel, or to call Meter::setMeterModel (nullptr) before destroying
196+
the MeterModel.
197+
*/
198+
void setMeterModel (MeterModel*);
199+
200+
/** Returns the current list model. */
201+
MeterModel* getMeterModel() const noexcept
202+
{
203+
#if ! JUCE_DISABLE_ASSERTIONS
204+
checkModelPtrIsValid();
205+
#endif
206+
207+
return model;
208+
}
161209

162210
//==============================================================================
163211
/** @returns true if the levels have changed. */
@@ -193,10 +241,10 @@ class Meter final : public Component
193241
const Rectangle<int>& getMeterArea() const noexcept { return meterArea; }
194242

195243
private:
196-
float level = 0.0f; // The last measured audio absolute volume level.
197-
float lastLevel = 0.0f; // The volume level of the last update, used to check if levels have changed for repainting.
198-
float maxLevel = 0.0f; // The maximum audio levels of the trailing 3 seconds.
199-
float lastMaxLevel = 0.0f; // The max volume level of the last update.
244+
float level = 0.0f, // The last measured audio absolute volume level.
245+
lastLevel = 0.0f, // The volume level of the last update, used to check if levels have changed for repainting.
246+
maxLevel = 0.0f, // The maximum audio levels of the trailing 3 seconds.
247+
lastMaxLevel = 0.0f; // The max volume level of the last update.
200248
int64 timeOfMaximumMs = 0; // The time of the last maximum audio level.
201249
Rectangle<int> meterArea; // The left/right drawable regions for the meter.
202250

@@ -236,10 +284,18 @@ class Meter final : public Component
236284

237285
private:
238286
//==============================================================================
239-
Model* model = nullptr;
287+
MeterModel* model = nullptr;
240288
Array<ChannelContext> channels;
241289
Array<float> levels;
242290
ClippingLevel clippingLevel = ClippingLevel::none;
291+
ColourGradient gradient;
292+
293+
void assignModelPtr (MeterModel*);
294+
295+
#if ! JUCE_DISABLE_ASSERTIONS
296+
std::weak_ptr<MeterModel::Empty> weakModelPtr;
297+
void checkModelPtrIsValid() const;
298+
#endif
243299

244300
//==============================================================================
245301
void updateClippingLevel (bool timeToUpdate);

0 commit comments

Comments
 (0)