commit 2014c5eb6f3c69372eddaf0962aad1fc66b70581
parent 96a172eb22f833744cc17a4e6d401ea0a67410ae
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Sat, 22 Jun 2024 18:38:00 +0200
graph editing WIP
Diffstat:
14 files changed, 322 insertions(+), 6 deletions(-)
diff --git a/source/xtJucePlugin/CMakeLists.txt b/source/xtJucePlugin/CMakeLists.txt
@@ -32,6 +32,7 @@ set(SOURCES
xtParts.cpp xtParts.h
xtPatchManager.cpp xtPatchManager.h
xtWaveEditor.cpp xtWaveEditor.h
+ xtWaveEditorStyle.cpp xtWaveEditorStyle.h
)
# https://forum.juce.com/t/help-needed-using-binarydata-with-cmake-juce-6/40486
diff --git a/source/xtJucePlugin/weGraph.cpp b/source/xtJucePlugin/weGraph.cpp
@@ -1,6 +1,7 @@
#include "weGraph.h"
#include "xtWaveEditor.h"
+#include "dsp56kEmu/fastmath.h"
namespace xtJucePlugin
{
@@ -32,12 +33,12 @@ namespace xtJucePlugin
void Graph::paint(const float* _data, const size_t _size, juce::Graphics& _g, const int _x, const int _y, const int _width, const int _height) const
{
- _g.setColour(juce::Colour(0xffffffff));
+ const auto& style = m_editor.getStyle();
- const float scaleX = static_cast<float>(_width - 1) / static_cast<float>(_size -1);
+ const float scaleX = static_cast<float>(_width) / static_cast<float>(_size);
const float scaleY = static_cast<float>(_height);
- const float x = static_cast<float>(_x);
+ const float x = static_cast<float>(_x) + scaleX * 0.5f;
const float y = static_cast<float>(_y);
const float h = static_cast<float>(_height);
@@ -51,14 +52,216 @@ namespace xtJucePlugin
const auto y1 = h - (normalize(_data[i ])) * scaleY + y - 1;
if(i)
- _g.drawLine(x0, y0, x1, y1, 3.0f);
+ {
+ _g.setColour(juce::Colour(style.colGraphLine));
+ _g.drawLine(x0, y0, x1, y1, style.graphLineThickness);
+ }
- _g.fillEllipse(x0 - 5, y0 - 5, 10, 10);
+ if(m_highlightedIndices.find(i) != m_highlightedIndices.end())
+ {
+ _g.setColour(style.colGraphLineHighlighted);
+ const auto s = style.graphPointSizeHighlighted;
+ _g.fillEllipse(x1 - s * 0.5f, y1 - s * 0.5f, s, s);
+ }
+ else
+ {
+ _g.setColour(style.colGraphLine);
+ const auto s = style.graphPointSize;
+ _g.fillEllipse(x1 - s * 0.5f, y1 - s * 0.5f, s, s);
+ }
}
}
+ bool Graph::updateHoveredIndex(const juce::MouseEvent& _e)
+ {
+ const auto index = mouseToIndex(_e);
+ if(!isValidIndex(index))
+ {
+ setHoveredIndex(InvalidIndex);
+ return false;
+ }
+ setHoveredIndex(static_cast<uint32_t>(index));
+ return true;
+ }
+
+ void Graph::mouseDown(const juce::MouseEvent& _e)
+ {
+ Component::mouseDown(_e);
+ setLastMouseEvent(_e);
+ const auto i = mouseToIndex(_e);
+ if(isValidIndex(i))
+ modifyValue(static_cast<uint32_t>(i), mouseToUnnormalizedValue(_e));
+ }
+
+ void Graph::mouseMove(const juce::MouseEvent& _e)
+ {
+ Component::mouseMove(_e);
+ updateHoveredIndex(_e);
+ }
+
+ void Graph::mouseEnter(const juce::MouseEvent& _e)
+ {
+ Component::mouseEnter(_e);
+ updateHoveredIndex(_e);
+ }
+
+ void Graph::mouseExit(const juce::MouseEvent& _e)
+ {
+ Component::mouseExit(_e);
+ setHoveredIndex(InvalidIndex);
+ }
+
+ void Graph::mouseDrag(const juce::MouseEvent& _e)
+ {
+ Component::mouseDrag(_e);
+
+ if(!_e.mods.isShiftDown())
+ {
+ updateHoveredIndex(_e);
+
+ if(m_lastMouseEvent)
+ {
+ modifyValuesForRange(*m_lastMouseEvent, _e);
+ }
+ else if(isValidIndex(m_hoveredIndex))
+ {
+ modifyValue(m_hoveredIndex, mouseToUnnormalizedValue(_e));
+ }
+ }
+ else
+ {
+ if(isValidIndex(m_hoveredIndex))
+ modifyValue(m_hoveredIndex, mouseToUnnormalizedValue(_e));
+ }
+
+ setLastMouseEvent(_e);
+ }
+
+ int32_t Graph::mouseToIndex(const juce::MouseEvent& _e) const
+ {
+ return _e.x * static_cast<int32_t>(getDataSize()) / (int32_t)getWidth();
+ }
+
+ float Graph::mouseToNormalizedValue(const juce::MouseEvent& _e) const
+ {
+ const auto v = 1.0f - static_cast<float>(_e.y) / static_cast<float>(getHeight() - 1);
+ return dsp56k::clamp(v, 0.0f, 1.0f);
+ }
+
+ float Graph::mouseToUnnormalizedValue(const juce::MouseEvent& _e) const
+ {
+ return unnormalize(mouseToNormalizedValue(_e));
+ }
+
+ const juce::MouseEvent* Graph::lastMouseEvent() const
+ {
+ return m_lastMouseEvent.get();
+ }
+
void Graph::onSourceChanged()
{
repaint();
}
+
+ void Graph::onHoveredIndexChanged(const uint32_t _index)
+ {
+ if(_index == InvalidIndex)
+ {
+ if(!m_highlightedIndices.empty())
+ m_highlightedIndices.clear();
+
+ onHighlightedIndicesChanged(m_highlightedIndices);
+ }
+
+ setHighlightedIndices({_index});
+ }
+
+ void Graph::onHighlightedIndicesChanged(const std::set<uint32_t>& _indices)
+ {
+ repaint();
+ }
+
+ void Graph::setHighlightedIndices(const std::set<uint32_t>& _indices)
+ {
+ bool changed = false;
+
+ for (auto existing : m_highlightedIndices)
+ {
+ if(_indices.find(existing) == _indices.end())
+ {
+ changed = true;
+ break;
+ }
+ }
+
+ if(!changed)
+ {
+ for (uint32_t newIndex : _indices)
+ {
+ if(m_highlightedIndices.find(newIndex) == m_highlightedIndices.end())
+ {
+ changed = true;
+ break;
+ }
+ }
+ }
+
+ if(!changed)
+ return;
+
+ m_highlightedIndices = _indices;
+
+ onHighlightedIndicesChanged(m_highlightedIndices);
+ }
+
+ void Graph::modifyValue(uint32_t _index, float _unnormalizedValue)
+ {
+ }
+
+ void Graph::setHoveredIndex(uint32_t _index)
+ {
+ if(_index == m_hoveredIndex)
+ return;
+ m_hoveredIndex = _index;
+ onHoveredIndexChanged(m_hoveredIndex);
+ }
+
+ void Graph::setLastMouseEvent(const juce::MouseEvent& _e)
+ {
+ m_lastMouseEvent = std::make_unique<juce::MouseEvent>(_e);
+ }
+
+ void Graph::modifyValuesForRange(const juce::MouseEvent& _a, const juce::MouseEvent& _b)
+ {
+ auto indexA = mouseToIndex(_a);
+ auto indexB = mouseToIndex(_b);
+
+ auto valueA = mouseToUnnormalizedValue(_a);
+ auto valueB = mouseToUnnormalizedValue(_b);
+
+ if(indexA > indexB)
+ {
+ std::swap(indexA, indexB);
+ std::swap(valueA, valueB);
+ }
+
+ const auto count = indexB - indexA + 1;
+
+ const auto valueDiff = valueB - valueA;
+ const auto factor = valueDiff / static_cast<float>(count);
+
+ auto v = valueA;
+
+ for(auto i = indexA; i <= indexB; ++i)
+ {
+ if(i < 0)
+ continue;
+
+ if(i >= getDataSize())
+ return;
+
+ modifyValue(i, v);
+ v += factor;
+ }
+ }
}
diff --git a/source/xtJucePlugin/weGraph.h b/source/xtJucePlugin/weGraph.h
@@ -13,6 +13,8 @@ namespace xtJucePlugin
class Graph : public juce::Component
{
public:
+ static constexpr uint32_t InvalidIndex = ~0;
+
explicit Graph(WaveEditor& _editor);
WaveEditor& getEditor() const { return m_editor; }
@@ -33,13 +35,54 @@ namespace xtJucePlugin
virtual const float* getData() const = 0;
virtual size_t getDataSize() const = 0;
- protected:
+ void mouseDown(const juce::MouseEvent& _e) override;
+ void mouseMove(const juce::MouseEvent& _e) override;
+ void mouseEnter(const juce::MouseEvent& _e) override;
+ void mouseExit(const juce::MouseEvent& _e) override;
+ void mouseDrag(const juce::MouseEvent& _e) override;
+
GraphData& getGraphData() const { return m_data; }
+
+ int32_t mouseToIndex(const juce::MouseEvent& _e) const;
+ float mouseToNormalizedValue(const juce::MouseEvent& _e) const;
+ float mouseToUnnormalizedValue(const juce::MouseEvent& _e) const;
+
+ bool isValidIndex(const uint32_t _index) const { return _index < getDataSize(); }
+ bool isValidIndex(const int32_t _index) const { return _index >= 0 && _index < getDataSize(); }
+
+ const juce::MouseEvent* lastMouseEvent() const;
+
+ void setUpdateHoveredPositionWhileDragging(bool _enable)
+ {
+ m_updateHoveredPositionWhileDragging = _enable;
+ }
+
+ protected:
virtual void onSourceChanged();
+ virtual void onHoveredIndexChanged(uint32_t _index);
+ virtual void onHighlightedIndicesChanged(const std::set<uint32_t>& _indices);
+
+ void setHighlightedIndices(const std::set<uint32_t>& _indices);
+
+ virtual void modifyValue(uint32_t _index, float _unnormalizedValue) = 0;
+
+ bool updateHoveredIndex(const juce::MouseEvent& _e);
+
+ uint32_t getHoveredIndex() const { return m_hoveredIndex; }
private:
+ void setHoveredIndex(uint32_t _index);
+ void setLastMouseEvent(const juce::MouseEvent& _e);
+ void modifyValuesForRange(const juce::MouseEvent& _a, const juce::MouseEvent& _b);
+
WaveEditor& m_editor;
GraphData& m_data;
+
pluginLib::EventListener<WaveData> m_onSourceChanged;
+
+ std::set<uint32_t> m_highlightedIndices;
+ uint32_t m_hoveredIndex = InvalidIndex;
+ std::unique_ptr<juce::MouseEvent> m_lastMouseEvent;
+ bool m_updateHoveredPositionWhileDragging = false;
};
}
diff --git a/source/xtJucePlugin/weGraphData.cpp b/source/xtJucePlugin/weGraphData.cpp
@@ -33,6 +33,32 @@ namespace xtJucePlugin
onSourceChanged(m_source);
}
+ void GraphData::setData(const uint32_t _index, const float _value)
+ {
+ if(m_data[_index] == _value)
+ return;
+ m_data[_index] = _value;
+ m_data[m_data.size() - _index - 1] = -_value;
+ updateFrequenciesAndPhases();
+ onSourceChanged(m_source);
+ }
+
+ void GraphData::setFreq(const uint32_t _index, const float _value)
+ {
+ if(m_frequencies[_index] == _value)
+ return;
+ m_frequencies[_index] = _value;
+ onSourceChanged(m_source);
+ }
+
+ void GraphData::setPhase(const uint32_t _index, const float _value)
+ {
+ if(m_phases[_index] == _value)
+ return;
+ m_phases[_index] = _value;
+ onSourceChanged(m_source);
+ }
+
void GraphData::updateFrequenciesAndPhases()
{
for(size_t i=0; i<m_data.size(); ++i)
diff --git a/source/xtJucePlugin/weGraphData.h b/source/xtJucePlugin/weGraphData.h
@@ -22,6 +22,10 @@ namespace xtJucePlugin
const auto& getFrequencies() const { return m_frequencies; }
const auto& getPhases() const { return m_phases; }
+ void setData(uint32_t _index, float _value);
+ void setFreq(uint32_t _index, float _value);
+ void setPhase(uint32_t _index, float _value);
+
private:
void updateFrequenciesAndPhases();
diff --git a/source/xtJucePlugin/weGraphFreq.cpp b/source/xtJucePlugin/weGraphFreq.cpp
@@ -23,4 +23,9 @@ namespace xtJucePlugin
{
return getGraphData().getFrequencies().size();
}
+
+ void GraphFreq::modifyValue(uint32_t _index, float _unnormalizedValue)
+ {
+ getGraphData().setFreq(_index, _unnormalizedValue);
+ }
}
diff --git a/source/xtJucePlugin/weGraphFreq.h b/source/xtJucePlugin/weGraphFreq.h
@@ -16,6 +16,7 @@ namespace xtJucePlugin
const float* getData() const override;
size_t getDataSize() const override;
+ void modifyValue(uint32_t _index, float _unnormalizedValue) override;
private:
};
}
diff --git a/source/xtJucePlugin/weGraphPhase.cpp b/source/xtJucePlugin/weGraphPhase.cpp
@@ -27,4 +27,9 @@ namespace xtJucePlugin
{
return getGraphData().getPhases().size();
}
+
+ void GraphPhase::modifyValue(uint32_t _index, float _unnormalizedValue)
+ {
+ getGraphData().setPhase(_index, _unnormalizedValue);
+ }
}
diff --git a/source/xtJucePlugin/weGraphPhase.h b/source/xtJucePlugin/weGraphPhase.h
@@ -13,6 +13,7 @@ namespace xtJucePlugin
float unnormalize(float _in) const override;
const float* getData() const override;
size_t getDataSize() const override;
+ void modifyValue(uint32_t _index, float _unnormalizedValue) override;
private:
};
}
diff --git a/source/xtJucePlugin/weGraphTime.cpp b/source/xtJucePlugin/weGraphTime.cpp
@@ -6,6 +6,7 @@ namespace xtJucePlugin
{
GraphTime::GraphTime(WaveEditor& _editor): Graph(_editor)
{
+ setUpdateHoveredPositionWhileDragging(true);
}
float GraphTime::normalize(const float _in) const
@@ -27,4 +28,9 @@ namespace xtJucePlugin
{
return getGraphData().getData().size();
}
+
+ void GraphTime::modifyValue(const uint32_t _index, const float _unnormalizedValue)
+ {
+ getGraphData().setData(_index, _unnormalizedValue);
+ }
}
diff --git a/source/xtJucePlugin/weGraphTime.h b/source/xtJucePlugin/weGraphTime.h
@@ -13,6 +13,7 @@ namespace xtJucePlugin
float unnormalize(float _in) const override;
const float* getData() const override;
size_t getDataSize() const override;
+ void modifyValue(uint32_t _index, float _unnormalizedValue) override;
private:
};
}
diff --git a/source/xtJucePlugin/xtWaveEditor.h b/source/xtJucePlugin/xtWaveEditor.h
@@ -2,6 +2,7 @@
#include "weData.h"
#include "weGraphData.h"
+#include "xtWaveEditorStyle.h"
#include "juce_gui_basics/juce_gui_basics.h"
@@ -38,6 +39,8 @@ namespace xtJucePlugin
const WaveEditorData& getData() const { return m_data; }
WaveEditorData& getData() { return m_data; }
+ const WaveEditorStyle& getStyle() const { return m_style; }
+
Editor& getEditor() const { return m_editor; }
GraphData& getGraphData() { return m_graphData; }
@@ -70,5 +73,7 @@ namespace xtJucePlugin
uint32_t m_selectedTable = ~0;
uint32_t m_selectedWave = ~0;
+
+ WaveEditorStyle m_style;
};
}
diff --git a/source/xtJucePlugin/xtWaveEditorStyle.cpp b/source/xtJucePlugin/xtWaveEditorStyle.cpp
diff --git a/source/xtJucePlugin/xtWaveEditorStyle.h b/source/xtJucePlugin/xtWaveEditorStyle.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "juce_gui_basics/juce_gui_basics.h"
+
+namespace xtJucePlugin
+{
+ struct WaveEditorStyle
+ {
+ juce::Colour colGraphLine = juce::Colour(0xffffffff);
+ juce::Colour colGraphLineHighlighted = juce::Colour(0xffffaa00);
+ float graphLineThickness = 3.0f;
+ float graphPointSize = 10.0f;
+ float graphPointSizeHighlighted = 30.0f;
+ };
+}