Skip to content

Commit

Permalink
Simplify and optimize Sample::visulaize()
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielKauss committed Nov 19, 2023
1 parent 4db9a17 commit 7678c41
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 75 deletions.
1 change: 1 addition & 0 deletions include/Sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class LMMS_EXPORT Sample
// if there appears problems with playback on some interpolation mode, then the value for that mode
// may need to be higher - conversely, to optimize, some may work with lower values
static constexpr auto s_interpolationMargins = std::array<int, 5>{64, 64, 64, 4, 4};
static constexpr int s_min_resolution = 512;

enum class Loop
{
Expand Down
127 changes: 52 additions & 75 deletions src/core/Sample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,95 +164,72 @@ bool Sample::play(sampleFrame* dst, PlaybackState* state, int numFrames, float d
void Sample::visualize(QPainter& p, const QRect& dr, int fromFrame, int toFrame) const
{
const auto lock = std::shared_lock{m_mutex};
const auto numFrames = static_cast<int>(m_buffer->size());

const bool focusOnRange = toFrame <= numFrames && 0 <= fromFrame && fromFrame < toFrame;
const int w = dr.width();
const int h = dr.height();

const int yb = h / 2 + dr.y();
const float ySpace = h * 0.5f;
const int nbFrames = focusOnRange ? toFrame - fromFrame : numFrames;

const double fpp = std::max(1., static_cast<double>(nbFrames) / w);
// There are 2 possibilities: Either nbFrames is bigger than
// the width, so we will have width points, or nbFrames is
// smaller than the width (fpp = 1) and we will have nbFrames
// points
const int totalPoints = std::min(nbFrames, w);
std::vector<QPointF> fEdgeMax(totalPoints);
std::vector<QPointF> fEdgeMin(totalPoints);
std::vector<QPointF> fRmsMax(totalPoints);
std::vector<QPointF> fRmsMin(totalPoints);
int curPixel = 0;
const int xb = dr.x();
const int first = focusOnRange ? fromFrame : 0;
const int last = focusOnRange ? toFrame - 1 : numFrames - 1;
// When the number of frames isn't perfectly divisible by the
// width, the remaining frames don't fit the last pixel and are
// past the visible area. lastVisibleFrame is the index number of
// the last visible frame.
const int visibleFrames = (fpp * w);
const int lastVisibleFrame = focusOnRange ? fromFrame + visibleFrames - 1 : visibleFrames - 1;

for (double frame = first; frame <= last && frame <= lastVisibleFrame; frame += fpp)
{
float maxData = -1;
float minData = 1;

auto rmsData = std::array<float, 2>{};
fromFrame = std::clamp(fromFrame, 0, (int)m_buffer->size());
toFrame = std::clamp(toFrame, 0, (int)m_buffer->size());

// Find maximum and minimum samples within range
for (int i = 0; i < fpp && frame + i <= last; ++i)
{
for (int j = 0; j < 2; ++j)
{
auto curData = m_buffer->data()[static_cast<int>(frame) + i][j];
int x = dr.x();
int y = dr.y();
int h = dr.height();
int w = dr.width();

if (curData > maxData) { maxData = curData; }
if (curData < minData) { minData = curData; }
const int halfHeight = y + h / 2.0f;
const int h2 = h / 2;

rmsData[j] += curData * curData;
}
}
const sampleFrame* buffer = m_buffer->data() + fromFrame;
int frames = toFrame - fromFrame;
if (frames == 0) { frames = m_buffer->size(); }
const float framesPerPixel = frames / static_cast<float>(w);

const float trueRmsData = (rmsData[0] + rmsData[1]) / 2 / fpp;
const float sqrtRmsData = std::sqrt(trueRmsData);
const float maxRmsData = std::clamp(sqrtRmsData, minData, maxData);
const float minRmsData = std::clamp(-sqrtRmsData, minData, maxData);
int resolution = 1;
while (framesPerPixel / resolution > s_min_resolution)
{
resolution++;
}

double x = 0;
if (m_reversed)
{
x = nbFrames >= w ? xb + w - curPixel : xb + ((1 - static_cast<double>(curPixel) / nbFrames) * w);
}
else
{
// If nbFrames >= w, we can use curPixel to calculate X
// but if nbFrames < w, we need to calculate it proportionally
// to the total number of points
x = nbFrames >= w ? xb + curPixel : xb + ((static_cast<double>(curPixel) / nbFrames) * w);
}
std::vector<float> min(w, 1);
std::vector<float> max(w, -1);
std::vector<float> rms(w, 0);

// Partial Y calculation
auto py = ySpace * m_amplification;
fEdgeMax[curPixel] = QPointF(x, (yb - (maxData * py)));
fEdgeMin[curPixel] = QPointF(x, (yb - (minData * py)));
fRmsMax[curPixel] = QPointF(x, (yb - (maxRmsData * py)));
fRmsMin[curPixel] = QPointF(x, (yb - (minRmsData * py)));
++curPixel;
int pixelIndex;
float value;
for (int i = 0; i < frames - resolution; i += resolution)
{
pixelIndex = i / framesPerPixel;
value = buffer[i][0];

if (value > max[pixelIndex]) { max[pixelIndex] = value; }
if (value < min[pixelIndex]) { min[pixelIndex] = value; }
rms[pixelIndex] += value * value;
}

for (int i = 0; i < totalPoints; ++i)
int lineX, lineY1, lineY2;
float trueRMS, minRMS, maxRMS;
for (int i = 0; i < w; i++)
{
p.drawLine(fEdgeMax[i], fEdgeMin[i]);
lineY1 = halfHeight - (max[i] * h2);
lineY2 = halfHeight - (min[i] * h2);

lineX = i + x;
if (m_reversed) { lineX = w - lineX; }

p.drawLine(lineX, lineY1, lineX, lineY2);
}

p.setPen(p.pen().color().lighter(123));

for (int i = 0; i < totalPoints; ++i)
for (int i = 0; i < w; i++)
{
p.drawLine(fRmsMax[i], fRmsMin[i]);
trueRMS = sqrt(rms[i] / (framesPerPixel / resolution));
maxRMS = std::clamp(trueRMS, min[i], max[i]);
minRMS = std::clamp(-trueRMS, min[i], max[i]);

lineY1 = halfHeight - maxRMS * h2;
lineY2 = halfHeight - minRMS * h2;

lineX = i + x;
if (m_reversed) { lineX = w - lineX; }

p.drawLine(lineX, lineY1, lineX, lineY2);
}
}

Expand Down

0 comments on commit 7678c41

Please sign in to comment.