Skip to content

Commit 19a622d

Browse files
committed
doc(renderer): Enhance PathStrokeRenderer documentation
1 parent 352ac7e commit 19a622d

1 file changed

Lines changed: 70 additions & 18 deletions

File tree

‎src/dmt/gui/widget/PathStrokeRenderer.h‎

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,15 @@ namespace widget {
4848
* @tparam SampleType The sample type (e.g., float, double) used for audio data.
4949
*
5050
* @details
51-
* This renderer builds a continuous JUCE Path from the audio samples and
52-
* strokes it with a configurable thickness. It produces high-quality,
53-
* anti-aliased waveform visuals at the cost of higher CPU usage compared to
54-
* simpler rendering strategies.
51+
* This is a very simple renderer that draws a path with one point for each
52+
* sample. It's non-hardware-accelerated, and produces high-quality visuals at
53+
* the cost of increased CPU usage.
5554
*
5655
* The renderer maintains persistent state (currentX and currentSample) between
5756
* frames to ensure visual continuity of the waveform across render calls.
5857
* Sub-pixel positioning is preserved to avoid visual jitter.
5958
*
60-
* Samples are read directly from the ring buffer — no data is copied.
59+
* Samples are read directly from the ring buffer, no data is copied.
6160
*/
6261
template<typename SampleType>
6362
class PathStrokeRenderer : public OscilloscopeRenderer<SampleType>
@@ -89,38 +88,91 @@ class PathStrokeRenderer : public OscilloscopeRenderer<SampleType>
8988
int _channel,
9089
const RenderContext& _context) override
9190
{
92-
// Maintain sub-pixel continuity across frames
9391
currentX = currentX - static_cast<int>(currentX) + _context.drawStartX;
92+
const auto path = buildPath(_ringBuffer, _channel, _context);
93+
strokePath(_graphics, path, _context);
94+
}
9495

95-
const float startY =
96-
static_cast<float>(_context.halfHeight) +
97-
currentSample * _context.halfHeight * _context.amplitude;
98-
const auto startPoint = juce::Point<float>(currentX, startY);
96+
//============================================================================
97+
private:
98+
//============================================================================
99+
/**
100+
* @brief Converts a sample value to a Y pixel coordinate.
101+
*
102+
* @param _sample The sample value to convert.
103+
* @param _halfHeight The vertical center of the drawing area in pixels.
104+
* @param _amplitude The amplitude scaling factor.
105+
*
106+
* @return The Y coordinate in pixels.
107+
*/
108+
[[nodiscard]] inline float sampleToY(SampleType _sample,
109+
int _halfHeight,
110+
float _amplitude) const noexcept
111+
{
112+
return static_cast<float>(_halfHeight) + _sample * _halfHeight * _amplitude;
113+
}
99114

115+
//============================================================================
116+
/**
117+
* @brief Builds a continuous path from the ring buffer samples.
118+
*
119+
* @param _ringBuffer Reference to the ring buffer containing audio samples.
120+
* @param _channel The audio channel index to read from.
121+
* @param _context Pre-computed rendering parameters for this frame.
122+
*
123+
* @return The constructed JUCE Path representing the waveform segment.
124+
*
125+
* @details
126+
* Reads samples directly from the ring buffer and advances the persistent
127+
* currentX position to maintain sub-pixel continuity between frames.
128+
*/
129+
[[nodiscard]] inline juce::Path buildPath(RingBuffer& _ringBuffer,
130+
int _channel,
131+
const RenderContext& _context)
132+
{
100133
juce::Path path;
101-
path.startNewSubPath(startPoint);
134+
path.startNewSubPath(
135+
currentX,
136+
sampleToY(currentSample, _context.halfHeight, _context.amplitude));
102137

103138
for (size_t i = 0; i < static_cast<size_t>(_context.sampleCount); ++i) {
104139
const int sampleIndex = _context.firstSampleIndex + static_cast<int>(i);
105140
currentSample = _ringBuffer.getSample(_channel, sampleIndex);
106141
currentX += _context.pixelsPerSample;
107-
const float y = static_cast<float>(_context.halfHeight) +
108-
currentSample * _context.halfHeight * _context.amplitude;
109-
const auto point = juce::Point<float>(currentX, y);
110-
path.lineTo(point);
142+
path.lineTo(
143+
currentX,
144+
sampleToY(currentSample, _context.halfHeight, _context.amplitude));
111145
}
112146

147+
return path;
148+
}
149+
150+
//============================================================================
151+
/**
152+
* @brief Strokes the given path onto the graphics context.
153+
*
154+
* @param _graphics The JUCE Graphics context targeting the oscilloscope
155+
* image.
156+
* @param _path The waveform path to stroke.
157+
* @param _context Pre-computed rendering parameters for this frame.
158+
*
159+
* @details
160+
* Configures the stroke type with mitered joints and square end caps,
161+
* then paints the path in white.
162+
*/
163+
inline void strokePath(juce::Graphics& _graphics,
164+
const juce::Path& _path,
165+
const RenderContext& _context) const
166+
{
113167
juce::PathStrokeType strokeType(_context.thickness * _context.sizeFactor,
114168
juce::PathStrokeType::JointStyle::mitered,
115169
juce::PathStrokeType::EndCapStyle::square);
116170

117171
_graphics.setColour(juce::Colours::white);
118-
_graphics.strokePath(path, strokeType);
172+
_graphics.strokePath(_path, strokeType);
119173
}
120174

121175
//============================================================================
122-
private:
123-
//============================================================================
124176
/** Last sample value for waveform continuity between frames. */
125177
SampleType currentSample = static_cast<SampleType>(0.0f);
126178

0 commit comments

Comments
 (0)