Stack L/R waveform lanes in stereo mode, with overlays drawn once at full band height

This commit is contained in:
2026-07-30 09:04:57 -04:00
parent 81f5861ba4
commit 2a0d10fab5
9 changed files with 341 additions and 77 deletions
+20
View File
@@ -3,6 +3,7 @@
#include "core/instrument/ui/waveform_view.h"
#include <algorithm>
#include <cstddef>
#include <cstdlib> // std::abs (int overload)
namespace reasampler::instrument::ui {
@@ -17,6 +18,25 @@ std::int64_t clampFrame(std::int64_t f, std::int64_t frameCount) {
} // namespace
Rect waveformOverlayArea(const Rect& band) { return band.empty() ? Rect{} : band; }
WaveformSurface waveformSurface(const Rect& band, bool stereoMode, int sourceChannels) {
WaveformSurface s;
if (band.empty()) return s;
s.overlay = waveformOverlayArea(band);
const bool twoLanes = stereoMode && sourceChannels >= 2;
const WaveformLanes lanes = waveformLanes(band, twoLanes);
s.upper = lanes.upper;
s.lower = lanes.lower;
s.laneCount = twoLanes ? 2 : 1;
return s;
}
audio::Envelope laneEnvelope(const audio::Envelope& env, int lane) {
if (lane < 0 || static_cast<std::size_t>(lane) >= env.size()) return {};
return audio::Envelope{env[static_cast<std::size_t>(lane)]};
}
int frameToX(const Rect& area, std::int64_t frameCount, std::int64_t frame) {
const int w = std::max(0, area.width);
if (frameCount <= 0 || w <= 0) return area.x;
+40 -7
View File
@@ -1,22 +1,55 @@
// waveform_view.h — waveform/marker geometry + zero-crossing snap. Mirror of keyboard_strip/
// editor_geometry: frame<->pixel + marker hit-test + snap arithmetic lives here, unit-tested
// outside the DAW; the shell draws and marshals mouse events into it.
// waveform_view.h — the WAVEFORM band's interior: the drawn lane/overlay surface, plus
// frame<->pixel mapping, marker hit-test and zero-crossing snap. Unit-tested outside the
// DAW; the shell draws and marshals mouse events into it.
//
// The surface maps a sample's full frame span [0, frameCount] linearly across a horizontal
// waveform rect. Markers are a generic N-named-marker set (not hardcoded specials), so a
// different mode (e.g. start + %-length end + fades) can repurpose the same machinery.
// The band maps a sample's full frame span [0, frameCount] linearly across its width.
// Markers are a generic N-named-marker set (not hardcoded specials), so a different mode
// (e.g. start + %-length end + fades) can repurpose the same machinery.
#pragma once
#include <cstdint>
#include "core/instrument/ui/editor_geometry.h" // Rect, contains
#include "core/audio/peaks.h" // AudioSample (float)
#include "core/instrument/ui/sample_bands.h" // waveformLanes (the band's lane inventory)
#include "core/audio/peaks.h" // AudioSample (float), Envelope
namespace reasampler::instrument::ui {
using audio::AudioSample;
// What the waveform band actually draws: the channel lane(s), and THE rect every overlay
// riding the waveform occupies.
//
// OVERLAY CONTRACT — `overlay` is the whole band in BOTH modes, never a lane. The amp
// envelope trace and its node handles, the start/loop markers, and the loop region draw
// ONCE into `overlay`, spanning both stacked lanes in stereo. Hit-testing reads the same
// rect, so a grab in the lower lane resolves to the same overlay item as one in the upper.
// Anything that draws per lane is a duplicate and a defect.
struct WaveformSurface {
Rect upper; // lane 0 -> channel 0 (LEFT); the whole band when single-lane
Rect lower; // lane 1 -> channel 1 (RIGHT); empty() when single-lane
Rect overlay; // the full band, both modes
int laneCount = 0; // 0 on a degenerate band, else 1 or 2
};
// Resolves the surface for a waveform band. Two lanes need BOTH stereo mode and a source
// that has a second channel to show: a mono source under stereo mode is dual-mono, so a
// second lane would be the redundant duplicate single-lane mode exists to avoid.
WaveformSurface waveformSurface(const Rect& band, bool stereoMode, int sourceChannels);
// THE overlay rect, standalone — same value as WaveformSurface::overlay, for the hit-test
// paths that have no channel count to hand. An overlay's rect never depends on the lane
// split, which is exactly the contract.
Rect waveformOverlayArea(const Rect& band);
// The single-channel envelope lane `lane` draws, taken from a multi-channel envelope
// computed in ONE computeEnvelope pass (it already envelopes channels independently, so a
// second lane costs no second scan of the PCM). Lane 0 is the upper lane and takes channel
// 0, lane 1 the lower and channel 1 — the L-above-R order. An out-of-range lane yields an
// empty envelope, which draws as a bare midline.
audio::Envelope laneEnvelope(const audio::Envelope& env, int lane);
// Pixel width of a marker's grab region either side of its x line. Mirrors keyboard_strip's
// edge-grab idiom.
inline constexpr int kMarkerGrabWidth = 5;