Skip to content

[DRAFT] waveform type 'centroid' #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: waveform-amplitude-fix
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,7 @@ else()
src/waveform/renderers/allshader/waveformrenderbeat.cpp
src/waveform/renderers/allshader/waveformrenderer.cpp
src/waveform/renderers/allshader/waveformrendererendoftrack.cpp
src/waveform/renderers/allshader/waveformrenderercentroid.cpp
src/waveform/renderers/allshader/waveformrendererfiltered.cpp
src/waveform/renderers/allshader/waveformrendererhsv.cpp
src/waveform/renderers/allshader/waveformrendererlrrgb.cpp
Expand All @@ -1452,6 +1453,7 @@ else()
src/waveform/renderers/allshader/waveformrenderersimple.cpp
src/waveform/renderers/allshader/waveformrendermark.cpp
src/waveform/renderers/allshader/waveformrendermarkrange.cpp
src/waveform/widgets/allshader/centroidwaveformwidget.cpp
src/waveform/widgets/allshader/filteredwaveformwidget.cpp
src/waveform/widgets/allshader/hsvwaveformwidget.cpp
src/waveform/widgets/allshader/lrrgbwaveformwidget.cpp
Expand Down
1 change: 1 addition & 0 deletions src/preferences/upgrade.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ WaveformWidgetType::Type upgradeToAllShaders(WaveformWidgetType::Type waveformTy
case WWT::AllShaderFilteredWaveform:
case WWT::AllShaderSimpleWaveform:
case WWT::AllShaderHSVWaveform:
case WWT::AllShaderCentroidWaveform:
case WWT::Count_WaveformwidgetType:
return waveformType;
case WWT::QtSimpleWaveform:
Expand Down
248 changes: 248 additions & 0 deletions src/waveform/renderers/allshader/waveformrenderercentroid.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
#include "waveform/renderers/allshader/waveformrenderercentroid.h"

#include "track/track.h"
#include "util/math.h"
#include "waveform/renderers/allshader/matrixforwidgetgeometry.h"
#include "waveform/waveform.h"
#include "waveform/waveformwidgetfactory.h"
#include "waveform/widgets/allshader/waveformwidget.h"
#include "widget/wskincolor.h"
#include "widget/wwidget.h"

namespace allshader {

namespace {
inline float math_pow2(float x) {
return x * x;
}
} // namespace

WaveformRendererCentroid::WaveformRendererCentroid(
WaveformWidgetRenderer* waveformWidget)
: WaveformRendererSignalBase(waveformWidget) {
}

void WaveformRendererCentroid::onSetup(const QDomNode& node) {
Q_UNUSED(node);
}

void WaveformRendererCentroid::initializeGL() {
WaveformRendererSignalBase::initializeGL();
m_shader.init();
}

void WaveformRendererCentroid::paintGL() {
TrackPointer pTrack = m_waveformRenderer->getTrackInfo();
if (!pTrack) {
return;
}

ConstWaveformPointer waveform = pTrack->getWaveform();
if (waveform.isNull()) {
return;
}

const int dataSize = waveform->getDataSize();
if (dataSize <= 1) {
return;
}

const WaveformData* data = waveform->data();
if (data == nullptr) {
return;
}

const float devicePixelRatio = m_waveformRenderer->getDevicePixelRatio();
const int length = static_cast<int>(m_waveformRenderer->getLength() * devicePixelRatio);

// Not multiplying with devicePixelRatio will also work. In that case, on
// High-DPI-Display the lines will be devicePixelRatio pixels wide (which is
// also what is used for the beat grid and the markers), or in other words
// each block of samples is represented by devicePixelRatio pixels (width).

const double firstVisualIndex = m_waveformRenderer->getFirstDisplayedPosition() * dataSize;
const double lastVisualIndex = m_waveformRenderer->getLastDisplayedPosition() * dataSize;

// Represents the # of waveform data points per horizontal pixel.
const double visualIncrementPerPixel =
(lastVisualIndex - firstVisualIndex) / static_cast<double>(length);

// Per-band gain from the EQ knobs.
float allGain(1.0), lowGain(1.0), midGain(1.0), highGain(1.0);
getGains(&allGain, &lowGain, &midGain, &highGain);

const float breadth = static_cast<float>(m_waveformRenderer->getBreadth()) * devicePixelRatio;
const float halfBreadth = breadth / 2.0f;

const float heightFactor = allGain * halfBreadth / 255.f;

// Effective visual index of x
double xVisualSampleIndex = firstVisualIndex;

const int numVerticesPerLine = 6; // 2 triangles

const int reserved = numVerticesPerLine * (length + 1);

m_vertices.clear();
m_vertices.reserve(reserved);
m_colors.clear();
m_colors.reserve(reserved);

m_vertices.addRectangle(0.f,
halfBreadth - 0.5f * devicePixelRatio,
static_cast<float>(length),
halfBreadth + 0.5f * devicePixelRatio);
m_colors.addForRectangle(
static_cast<float>(m_axesColor_r),
static_cast<float>(m_axesColor_g),
static_cast<float>(m_axesColor_b));

for (int pos = 0; pos < length; ++pos) {
// Our current pixel (x) corresponds to a number of visual samples
// (visualSamplerPerPixel) in our waveform object. We take the max of
// all the data points on either side of xVisualSampleIndex within a
// window of 'maxSamplingRange' visual samples to measure the maximum
// data point contained by this pixel.
double maxSamplingRange = visualIncrementPerPixel / 2.0;

// Since xVisualSampleIndex is in visual-samples (e.g. R,L,R,L) we want
// to check +/- maxSamplingRange frames, not samples. To do this, divide
// xVisualSampleIndex by 2. Since frames indices are integers, we round
// to the nearest integer by adding 0.5 before casting to int.
int visualFrameStart = int(xVisualSampleIndex / 2.0 - maxSamplingRange + 0.5);
int visualFrameStop = int(xVisualSampleIndex / 2.0 + maxSamplingRange + 0.5);
const int lastVisualFrame = dataSize / 2 - 1;

// We now know that some subset of [visualFrameStart, visualFrameStop]
// lies within the valid range of visual frames. Clamp
// visualFrameStart/Stop to within [0, lastVisualFrame].
visualFrameStart = math_clamp(visualFrameStart, 0, lastVisualFrame);
visualFrameStop = math_clamp(visualFrameStop, 0, lastVisualFrame);

int visualIndexStart = visualFrameStart * 2;
int visualIndexStop = visualFrameStop * 2;

visualIndexStart = std::max(visualIndexStart, 0);
visualIndexStop = std::min(visualIndexStop, dataSize);

const float fpos = static_cast<float>(pos);

// Find the max values for low, mid, high and all in the waveform data.
// - Max of left and right
uchar u8maxLow{};
uchar u8maxMid{};
uchar u8maxHigh{};
// - Per channel
uchar u8maxAllChn[2]{};
for (int chn = 0; chn < 2; chn++) {
// data is interleaved left / right
for (int i = visualIndexStart + chn; i < visualIndexStop + chn; i += 2) {
const WaveformData& waveformData = data[i];

u8maxLow = math_max(u8maxLow, waveformData.filtered.low);
u8maxMid = math_max(u8maxMid, waveformData.filtered.mid);
u8maxHigh = math_max(u8maxHigh, waveformData.filtered.high);
u8maxAllChn[chn] = math_max(u8maxAllChn[chn], waveformData.filtered.all);
}
}

// Cast to float
float maxLow = static_cast<float>(u8maxLow);
float maxMid = static_cast<float>(u8maxMid);
float maxHigh = static_cast<float>(u8maxHigh);
float maxAllChn[2]{static_cast<float>(u8maxAllChn[0]), static_cast<float>(u8maxAllChn[1])};

// Calculate the magnitude of the maxLow, maxMid and maxHigh values
const float magnitude = std::sqrt(
math_pow2(maxLow) + math_pow2(maxMid) + math_pow2(maxHigh));

// Apply the gains
maxLow *= lowGain;
maxMid *= midGain;
maxHigh *= highGain;

// Calculate the magnitude of the gained maxLow, maxMid and maxHigh values
const float magnitudeGained = std::sqrt(
math_pow2(maxLow) + math_pow2(maxMid) + math_pow2(maxHigh));

// The maxAll values will be used to draw the amplitude. We scale them according to
// magnitude of the gained maxLow, maxMid and maxHigh values
if (magnitude != 0.f) {
const float factor = magnitudeGained / magnitude;
maxAllChn[0] *= factor;
maxAllChn[1] *= factor;
}

// Calculate the centroid, between 0 and 1, where 0 corresponds with
// only amplitude in the low band, and 1 with only amplitude in the high
// band. See https://en.wikipedia.org/wiki/Spectral_centroid
const float f[3]{0.f, 0.5f, 1.f};
const float centroid =
(f[0] * maxLow + f[1] * maxMid + f[2] * maxHigh) /
(maxLow + maxMid + maxHigh);

// Calculate the spectral flatness. The offset of 1 (on a range 0..255)
// is to avoid a flatness of 0 when any of the bands as amplitude 0. See
// https://en.wikipedia.org/wiki/Spectral_flatness
const float geoMean = std::pow(
(maxLow + 1.f) * (maxMid + 1.f) * (maxHigh + 1.f), 1.f / 3.f);
const float ariMean = (maxLow + 1.f + maxMid + 1.f + maxHigh + 1.f) / 3.f;
const float flatness = geoMean / ariMean;

// Map the centroid to hue, resulting in a continuous color scale
// (red - yellow - green - cyan - blue) where 0 is red and 2/3 is blue.
// Displace and scale (and clamp) the centroid for more color contrast,
// based on trial error.
const float hue = 2.f / 3.f * std::max(0.f, std::min(1.f, centroid * 2.0f - 0.5f));

// See https://doc.qt.io/qt-6/qcolor.html#the-hsv-color-model
//
// Map the flatness to saturation: the flatter the
// spectrum, the less pronounced the centroid color
QColor color;
color.setHsvF(hue,
1.0f - flatness * 0.75,
1.0f);

// Lines are thin rectangles
m_vertices.addRectangle(fpos - 0.5f,
halfBreadth - heightFactor * maxAllChn[0],
fpos + 0.5f,
halfBreadth + heightFactor * maxAllChn[1]);
// m_colors.addForRectangle(red, green, blue);
m_colors.addForRectangle(
static_cast<float>(color.redF()),
static_cast<float>(color.greenF()),
static_cast<float>(color.blueF()));

xVisualSampleIndex += visualIncrementPerPixel;
}

DEBUG_ASSERT(reserved == m_vertices.size());
DEBUG_ASSERT(reserved == m_colors.size());

const QMatrix4x4 matrix = matrixForWidgetGeometry(m_waveformRenderer, true);

const int matrixLocation = m_shader.matrixLocation();
const int positionLocation = m_shader.positionLocation();
const int colorLocation = m_shader.colorLocation();

m_shader.bind();
m_shader.enableAttributeArray(positionLocation);
m_shader.enableAttributeArray(colorLocation);

m_shader.setUniformValue(matrixLocation, matrix);

m_shader.setAttributeArray(
positionLocation, GL_FLOAT, m_vertices.constData(), 2);
m_shader.setAttributeArray(
colorLocation, GL_FLOAT, m_colors.constData(), 3);

glDrawArrays(GL_TRIANGLES, 0, m_vertices.size());

m_shader.disableAttributeArray(positionLocation);
m_shader.disableAttributeArray(colorLocation);
m_shader.release();
}

} // namespace allshader
29 changes: 29 additions & 0 deletions src/waveform/renderers/allshader/waveformrenderercentroid.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include "shaders/rgbshader.h"
#include "util/class.h"
#include "waveform/renderers/allshader/rgbdata.h"
#include "waveform/renderers/allshader/vertexdata.h"
#include "waveform/renderers/allshader/waveformrenderersignalbase.h"

namespace allshader {
class WaveformRendererCentroid;
}

class allshader::WaveformRendererCentroid final : public allshader::WaveformRendererSignalBase {
public:
explicit WaveformRendererCentroid(WaveformWidgetRenderer* waveformWidget);

// override ::WaveformRendererSignalBase
void onSetup(const QDomNode& node) override;

void initializeGL() override;
void paintGL() override;

private:
mixxx::RGBShader m_shader;
VertexData m_vertices;
RGBData m_colors;

DISALLOW_COPY_AND_ASSIGN(WaveformRendererCentroid);
};
11 changes: 11 additions & 0 deletions src/waveform/waveformwidgetfactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "waveform/visualsmanager.h"
#include "waveform/vsyncthread.h"
#ifdef MIXXX_USE_QOPENGL
#include "waveform/widgets/allshader/centroidwaveformwidget.h"
#include "waveform/widgets/allshader/filteredwaveformwidget.h"
#include "waveform/widgets/allshader/hsvwaveformwidget.h"
#include "waveform/widgets/allshader/lrrgbwaveformwidget.h"
Expand Down Expand Up @@ -975,6 +976,13 @@ void WaveformWidgetFactory::evaluateWidgets() {
#else
setWaveformVarsByType.operator()<allshader::HSVWaveformWidget>();
break;
#endif
case WaveformWidgetType::AllShaderCentroidWaveform:
#ifndef MIXXX_USE_QOPENGL
continue;
#else
setWaveformVarsByType.operator()<allshader::CentroidWaveformWidget>();
break;
#endif
default:
DEBUG_ASSERT(!"Unexpected WaveformWidgetType");
Expand Down Expand Up @@ -1072,6 +1080,9 @@ WaveformWidgetAbstract* WaveformWidgetFactory::createWaveformWidget(
case WaveformWidgetType::AllShaderHSVWaveform:
widget = new allshader::HSVWaveformWidget(viewer->getGroup(), viewer);
break;
case WaveformWidgetType::AllShaderCentroidWaveform:
widget = new allshader::CentroidWaveformWidget(viewer->getGroup(), viewer);
break;
#else
case WaveformWidgetType::QtSimpleWaveform:
widget = new QtSimpleWaveformWidget(viewer->getGroup(), viewer);
Expand Down
35 changes: 35 additions & 0 deletions src/waveform/widgets/allshader/centroidwaveformwidget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "waveform/widgets/allshader/centroidwaveformwidget.h"

#include "waveform/renderers/allshader/waveformrenderbackground.h"
#include "waveform/renderers/allshader/waveformrenderbeat.h"
#include "waveform/renderers/allshader/waveformrenderercentroid.h"
#include "waveform/renderers/allshader/waveformrendererendoftrack.h"
#include "waveform/renderers/allshader/waveformrendererpreroll.h"
#include "waveform/renderers/allshader/waveformrendermark.h"
#include "waveform/renderers/allshader/waveformrendermarkrange.h"
#include "waveform/widgets/allshader/moc_centroidwaveformwidget.cpp"

namespace allshader {

CentroidWaveformWidget::CentroidWaveformWidget(const QString& group, QWidget* parent)
: WaveformWidget(group, parent) {
addRenderer<WaveformRenderBackground>();
addRenderer<WaveformRendererEndOfTrack>();
addRenderer<WaveformRendererPreroll>();
addRenderer<WaveformRenderMarkRange>();
addRenderer<WaveformRendererCentroid>();
addRenderer<WaveformRenderBeat>();
addRenderer<WaveformRenderMark>();

m_initSuccess = init();
}

void CentroidWaveformWidget::castToQWidget() {
m_widget = this;
}

void CentroidWaveformWidget::paintEvent(QPaintEvent* event) {
Q_UNUSED(event);
}

} // namespace allshader
Loading