@@ -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 */
6261template <typename SampleType>
6362class 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