commit 94b748ce7e00f005b0b440d77178b78d73db2cd1 parent 15834c2dbb80ff5145abe441d0013539fcd6badb Author: dsp56300 <dsp56300@users.noreply.github.com> Date: Sun, 16 Jun 2024 01:39:23 +0200 create graphs for wavetable/frequency/phase Diffstat:
17 files changed, 380 insertions(+), 11 deletions(-)
diff --git a/source/xtJucePlugin/CMakeLists.txt b/source/xtJucePlugin/CMakeLists.txt @@ -8,6 +8,11 @@ set(SOURCES weControlTree.cpp weControlTree.h weControlTreeItem.cpp weControlTreeItem.h weData.cpp weData.h + weGraph.cpp weGraph.h + weGraphData.cpp weGraphData.h + weGraphFreq.cpp weGraphFreq.h + weGraphPhase.cpp weGraphPhase.h + weGraphTime.cpp weGraphTime.h weTablesTree.cpp weTablesTree.h weTablesTreeItem.cpp weTablesTreeItem.h weTree.cpp weTree.h @@ -38,3 +43,7 @@ include(skins/xtDefault/assets.cmake) juce_add_binary_data(xtJucePlugin_BinaryData SOURCES ${ASSETS} ${ASSETS_xtDefault}) createJucePluginWithFX(xtJucePlugin "Xenia" "Txts" "Txtf" xtJucePlugin_BinaryData xtLib) + +target_link_libraries(xtJucePlugin PUBLIC juce::juce_dsp) +target_link_libraries(xtJucePlugin_FX PUBLIC juce::juce_dsp) + diff --git a/source/xtJucePlugin/weGraph.cpp b/source/xtJucePlugin/weGraph.cpp @@ -0,0 +1,64 @@ +#include "weGraph.h" + +#include "xtWaveEditor.h" + +namespace xtJucePlugin +{ + Graph::Graph(WaveEditor& _editor) : m_editor(_editor), m_data(_editor.getGraphData()) + { + m_onSourceChanged.set(m_data.onSourceChanged, [this](const WaveData& _source) + { + onSourceChanged(); + }); + } + + void Graph::paint(juce::Graphics& g) + { + g.fillAll(findColour(juce::TreeView::ColourIds::backgroundColourId)); + + paint(g, 0, 0, getWidth(), getHeight()); + } + + void Graph::parentHierarchyChanged() + { + juce::Component::parentHierarchyChanged(); + + const auto* parent = getParentComponent(); + if(!parent) + return; + + setSize(parent->getWidth(), parent->getHeight()); + } + + 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 float scaleX = static_cast<float>(_width - 1) / static_cast<float>(_size -1); + const float scaleY = static_cast<float>(_height) / (m_maxValue - m_minValue); + + const float h = static_cast<float>(_height); + + for(uint32_t x=1; x<_size; ++x) + { + const auto x0 = static_cast<float>(x - 1) * scaleX + static_cast<float>(_x); + const auto x1 = static_cast<float>(x ) * scaleX + static_cast<float>(_x); + + const auto y0 = h - (_data[x - 1] - m_minValue) * scaleY + static_cast<float>(_y) - 1; + const auto y1 = h - (_data[x ] - m_minValue) * scaleY + static_cast<float>(_y) - 1; + + _g.drawLine(x0, y0, x1, y1, 3.0f); + } + } + + void Graph::setRange(const float _min, const float _max) + { + m_minValue = _min; + m_maxValue = _max; + } + + void Graph::onSourceChanged() + { + repaint(); + } +} diff --git a/source/xtJucePlugin/weGraph.h b/source/xtJucePlugin/weGraph.h @@ -0,0 +1,45 @@ +#pragma once + +#include "weTypes.h" +#include "juce_gui_basics/juce_gui_basics.h" + +#include "../jucePluginLib/event.h" + +namespace xtJucePlugin +{ + class GraphData; + class WaveEditor; + + class Graph : public juce::Component + { + public: + explicit Graph(WaveEditor& _editor); + + WaveEditor& getEditor() const { return m_editor; } + + void paint(juce::Graphics& g) override; + void parentHierarchyChanged() override; + + virtual void paint(juce::Graphics& _g, int _x, int _y, int _width, int _height) = 0; + + template<size_t Size> void paint(const std::array<float, Size>& _data, juce::Graphics& _g, const int _x, const int _y, const int _width, const int _height) const + { + paint(_data.data(), Size, _g, _x, _y, _width, _height); + } + + void paint(const float* _data, size_t _size, juce::Graphics& _g, int _x, int _y, int _width, int _height) const; + + void setRange(float _min, float _max); + + protected: + GraphData& getGraphData() const { return m_data; } + virtual void onSourceChanged(); + + private: + WaveEditor& m_editor; + GraphData& m_data; + pluginLib::EventListener<WaveData> m_onSourceChanged; + float m_minValue = 0.0f; + float m_maxValue = 1.0f; + }; +} diff --git a/source/xtJucePlugin/weGraphData.cpp b/source/xtJucePlugin/weGraphData.cpp @@ -0,0 +1,57 @@ +#include "weGraphData.h" + +#include "juce_dsp/juce_dsp.h" + +#include <complex> + +namespace xtJucePlugin +{ + constexpr float g_pi = 3.1415926535f; + + void GraphData::set(const WaveData& _data) + { + if(_data == m_source) + return; + + m_source = _data; + + constexpr auto size = std::tuple_size_v<WaveData>; + constexpr uint32_t order = 7; + static_assert((1 << order) == size); + + const juce::dsp::FFT fft(order); + + for(uint32_t i=0; i<m_source.size(); ++i) + { +// m_data[i] = i >= (m_source.size()>>1) ? -1.0f : 1.0f; +// m_data[i] = std::sin(static_cast<float>(i) / static_cast<float>(m_source.size()) * g_pi * 2.0f); + m_data[i] = static_cast<float>(m_source[i]) / 128.0f; + } + + std::array<juce::dsp::Complex<float>, size> inData; + std::array<juce::dsp::Complex<float>, size> outData; + + for(size_t i=0; i<m_data.size(); ++i) + inData[i] = {m_data[i], 0.0f}; + + outData.fill({0.f,0.f}); + + fft.perform(inData.data(), outData.data(), false); + + for(size_t i=0; i<m_frequencies.size(); ++i) + { + const auto re = outData[i].real() / static_cast<float>(fft.getSize()>>1); + const auto im = outData[i].imag() / static_cast<float>(fft.getSize()>>1); + + std::complex c(re,im); + + m_frequencies[i] = std::abs(c); +// if(m_frequencies[i] > 0.01f) + m_phases[i] = std::arg(c) / g_pi; +// else +// m_phases[i] = 0; + } + + onSourceChanged(m_source); + } +} diff --git a/source/xtJucePlugin/weGraphData.h b/source/xtJucePlugin/weGraphData.h @@ -0,0 +1,32 @@ +#pragma once + +#include "weTypes.h" +#include <tuple> + +#include "../jucePluginLib/event.h" + +namespace xtJucePlugin +{ + class GraphData + { + public: + pluginLib::Event<WaveData> onSourceChanged; + + GraphData() : m_source({}), m_data({}), m_frequencies({}), m_phases({}) + { + } + + void set(const WaveData& _data); + + const auto& getData() const { return m_data; } + const auto& getFrequencies() const { return m_frequencies; } + const auto& getPhases() const { return m_phases; } + + private: + WaveData m_source; + + std::array<float, std::tuple_size_v<WaveData>> m_data; + std::array<float, std::tuple_size_v<WaveData>/2> m_frequencies; + std::array<float, std::tuple_size_v<WaveData>/2> m_phases; + }; +} diff --git a/source/xtJucePlugin/weGraphFreq.cpp b/source/xtJucePlugin/weGraphFreq.cpp @@ -0,0 +1,11 @@ +#include "weGraphFreq.h" + +#include "weGraphData.h" + +namespace xtJucePlugin +{ + void GraphFreq::paint(juce::Graphics& _g, const int _x, const int _y, const int _width, const int _height) + { + Graph::paint(getGraphData().getFrequencies(), _g, _x, _y, _width, _height); + } +} diff --git a/source/xtJucePlugin/weGraphFreq.h b/source/xtJucePlugin/weGraphFreq.h @@ -0,0 +1,17 @@ +#pragma once + +#include "weGraph.h" + +namespace xtJucePlugin +{ + class GraphFreq : public Graph + { + public: + GraphFreq(WaveEditor& _editor) : Graph(_editor) + { + } + + void paint(juce::Graphics& _g, int _x, int _y, int _width, int _height) override; + private: + }; +} diff --git a/source/xtJucePlugin/weGraphPhase.cpp b/source/xtJucePlugin/weGraphPhase.cpp @@ -0,0 +1,16 @@ +#include "weGraphPhase.h" + +#include "weGraphData.h" + +namespace xtJucePlugin +{ + GraphPhase::GraphPhase(WaveEditor& _editor): Graph(_editor) + { + setRange(-1,1); + } + + void GraphPhase::paint(juce::Graphics& _g, const int _x, const int _y, const int _width, const int _height) + { + Graph::paint(getGraphData().getPhases(), _g, _x, _y, _width, _height); + } +} diff --git a/source/xtJucePlugin/weGraphPhase.h b/source/xtJucePlugin/weGraphPhase.h @@ -0,0 +1,15 @@ +#pragma once + +#include "weGraph.h" + +namespace xtJucePlugin +{ + class GraphPhase : public Graph + { + public: + explicit GraphPhase(WaveEditor& _editor); + + void paint(juce::Graphics& _g, int _x, int _y, int _width, int _height) override; + private: + }; +} diff --git a/source/xtJucePlugin/weGraphTime.cpp b/source/xtJucePlugin/weGraphTime.cpp @@ -0,0 +1,16 @@ +#include "weGraphTime.h" + +#include "weGraphData.h" + +namespace xtJucePlugin +{ + GraphTime::GraphTime(WaveEditor& _editor): Graph(_editor) + { + setRange(-1.0f, 1.0f); + } + + void GraphTime::paint(juce::Graphics& _g, const int _x, const int _y, const int _width, const int _height) + { + Graph::paint(getGraphData().getData(), _g, _x, _y, _width, _height); + } +} diff --git a/source/xtJucePlugin/weGraphTime.h b/source/xtJucePlugin/weGraphTime.h @@ -0,0 +1,15 @@ +#pragma once + +#include "weGraph.h" + +namespace xtJucePlugin +{ + class GraphTime : public Graph + { + public: + explicit GraphTime(WaveEditor& _editor); + + void paint(juce::Graphics& _g, int _x, int _y, int _width, int _height) override; + private: + }; +} diff --git a/source/xtJucePlugin/weWaveTreeItem.cpp b/source/xtJucePlugin/weWaveTreeItem.cpp @@ -30,8 +30,8 @@ namespace xtJucePlugin const auto x0 = static_cast<float>(x - 1) * scaleX + static_cast<float>(_x); const auto x1 = static_cast<float>(x ) * scaleX + static_cast<float>(_x); - const auto y0 = static_cast<float>(_data[x - 1] + 128) * scaleY + static_cast<float>(_y); - const auto y1 = static_cast<float>(_data[x ] + 128) * scaleY + static_cast<float>(_y); + const auto y0 = static_cast<float>(255 - (_data[x - 1] + 128)) * scaleY + static_cast<float>(_y); + const auto y1 = static_cast<float>(255 - (_data[x ] + 128)) * scaleY + static_cast<float>(_y); _g.drawLine(x0, y0, x1, y1); } @@ -54,6 +54,14 @@ namespace xtJucePlugin return WaveCategory::Invalid; } + void WaveTreeItem::itemSelectionChanged(bool isNowSelected) + { + TreeItem::itemSelectionChanged(isNowSelected); + + if(isNowSelected) + m_editor.setSelectedWave(m_waveIndex); + } + void WaveTreeItem::onWaveChanged(const uint32_t _index) { if(_index != m_waveIndex) diff --git a/source/xtJucePlugin/weWaveTreeItem.h b/source/xtJucePlugin/weWaveTreeItem.h @@ -22,6 +22,7 @@ namespace xtJucePlugin static std::string getWaveName(uint32_t _waveIndex); static WaveCategory getCategory(uint32_t _waveIndex); + void itemSelectionChanged(bool isNowSelected) override; private: void onWaveChanged(uint32_t _index); void onWaveChanged(); diff --git a/source/xtJucePlugin/xtEditor.cpp b/source/xtJucePlugin/xtEditor.cpp @@ -113,8 +113,9 @@ namespace xtJucePlugin Editor::~Editor() { + if(m_waveEditor) + m_waveEditor->destroy(); getXtController().setWaveEditor(nullptr); - m_waveEditor.reset(); m_frontPanel.reset(); } @@ -183,9 +184,9 @@ namespace xtJucePlugin { if(_object.getName() == "waveEditorContainer") { - m_waveEditor.reset(new WaveEditor(*this)); - getXtController().setWaveEditor(m_waveEditor.get()); - return m_waveEditor.get(); + m_waveEditor = new WaveEditor(*this); + getXtController().setWaveEditor(m_waveEditor); + return m_waveEditor; } return jucePluginEditorLib::Editor::createJuceComponent(_component, _object); } diff --git a/source/xtJucePlugin/xtEditor.h b/source/xtJucePlugin/xtEditor.h @@ -62,7 +62,7 @@ namespace xtJucePlugin std::unique_ptr<FocusedParameter> m_focusedParameter; std::unique_ptr<FrontPanel> m_frontPanel; std::unique_ptr<Parts> m_parts; - std::unique_ptr<WaveEditor> m_waveEditor; + WaveEditor* m_waveEditor = nullptr; pluginLib::EventListener<bool> m_playModeChangeListener; diff --git a/source/xtJucePlugin/xtWaveEditor.cpp b/source/xtJucePlugin/xtWaveEditor.cpp @@ -4,6 +4,10 @@ #include "weWaveTree.h" #include "weTablesTree.h" #include "weControlTree.h" +#include "weGraphFreq.h" +#include "weGraphPhase.h" +#include "weGraphTime.h" + #include "xtEditor.h" namespace xtJucePlugin @@ -11,14 +15,19 @@ namespace xtJucePlugin WaveEditor::WaveEditor(Editor& _editor) : ComponentMovementWatcher(this), m_editor(_editor), m_data(_editor.getXtController()) { addComponentListener(this); + + m_data.onWaveChanged.addListener([this](const uint32_t& _waveIndex) + { + if(_waveIndex != m_selectedWave) + return; + + setSelectedWave(_waveIndex, true); + }); } WaveEditor::~WaveEditor() { - m_waveTree.reset(); - m_controlTree.reset(); - m_tablesTree.reset(); - + destroy(); removeComponentListener(this); } @@ -27,14 +36,41 @@ namespace xtJucePlugin auto* waveListParent = m_editor.findComponent("wecWaveList"); auto* tablesListParent = m_editor.findComponent("wecWavetableList"); auto* controlListParent = m_editor.findComponent("wecWaveControlTable"); + auto* waveFreqParent = m_editor.findComponent("wecWaveFreq"); + auto* wavePhaseParent = m_editor.findComponent("wecWavePhase"); + auto* waveTimeParent = m_editor.findComponent("wecWaveTime"); m_waveTree.reset(new WaveTree(*this)); m_tablesTree.reset(new TablesTree(*this)); m_controlTree.reset(new ControlTree(*this)); + m_graphFreq.reset(new GraphFreq(*this)); + m_graphPhase.reset(new GraphPhase(*this)); + m_graphTime.reset(new GraphTime(*this)); + waveListParent->addAndMakeVisible(m_waveTree.get()); tablesListParent->addAndMakeVisible(m_tablesTree.get()); controlListParent->addAndMakeVisible(m_controlTree.get()); + + waveFreqParent->addAndMakeVisible(m_graphFreq.get()); + wavePhaseParent->addAndMakeVisible(m_graphPhase.get()); + waveTimeParent->addAndMakeVisible(m_graphTime.get()); + + constexpr auto colourId = juce::TreeView::ColourIds::backgroundColourId; + const auto colour = m_waveTree->findColour(colourId); + m_graphFreq->setColour(colourId, colour); + m_graphPhase->setColour(colourId, colour); + m_graphTime->setColour(colourId, colour); + } + + void WaveEditor::destroy() + { + m_waveTree.reset(); + m_controlTree.reset(); + m_tablesTree.reset(); + m_graphFreq.reset(); + m_graphPhase.reset(); + m_graphTime.reset(); } void WaveEditor::checkFirstTimeVisible() @@ -69,4 +105,15 @@ namespace xtJucePlugin m_selectedTable = _index; m_controlTree->setTable(_index); } + + void WaveEditor::setSelectedWave(const uint32_t _waveIndex, bool _forceRefresh/* = false*/) + { + if(m_selectedWave == _waveIndex && !_forceRefresh) + return; + + m_selectedWave = _waveIndex; + + if(const auto wave = m_data.getWave(_waveIndex)) + m_graphData.set(*wave); + } } diff --git a/source/xtJucePlugin/xtWaveEditor.h b/source/xtJucePlugin/xtWaveEditor.h @@ -1,6 +1,7 @@ #pragma once #include "weData.h" +#include "weGraphData.h" #include "juce_gui_basics/juce_gui_basics.h" @@ -8,6 +9,9 @@ namespace xtJucePlugin { + class GraphPhase; + class GraphFreq; + class GraphTime; class ControlTree; class TablesTree; class WaveTree; @@ -26,6 +30,7 @@ namespace xtJucePlugin WaveEditor& operator = (WaveEditor&&) = delete; void initialize(); + void destroy(); void onReceiveWave(const pluginLib::MidiPacket::Data& _data, const std::vector<uint8_t>& _msg); void onReceiveTable(const pluginLib::MidiPacket::Data& _data, const std::vector<uint8_t>& _msg); @@ -34,8 +39,10 @@ namespace xtJucePlugin WaveEditorData& getData() { return m_data; } Editor& getEditor() const { return m_editor; } + GraphData& getGraphData() { return m_graphData; } void setSelectedTable(uint32_t _index); + void setSelectedWave(uint32_t _waveIndex, bool _forceRefresh = false); private: // ComponentMovementWatcher @@ -47,10 +54,18 @@ namespace xtJucePlugin void onFirstTimeVisible(); Editor& m_editor; + std::unique_ptr<WaveTree> m_waveTree; std::unique_ptr<ControlTree> m_controlTree; std::unique_ptr<TablesTree> m_tablesTree; + + std::unique_ptr<GraphFreq> m_graphFreq; + std::unique_ptr<GraphPhase> m_graphPhase; + std::unique_ptr<GraphTime> m_graphTime; + WaveEditorData m_data; + GraphData m_graphData; + bool m_wasVisible = false; uint32_t m_selectedTable = ~0;