From ee0780073c8ff9c00fe597ce7aead4d4358aed33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc=20Serf=C5=91z=C5=91?= Date: Tue, 14 May 2024 19:10:52 +0200 Subject: [PATCH 01/12] feat(midi): Implemented MIDI message parsing --- src/midi/ChannelMapNode.cpp | 31 +- src/midi/MessageView.hpp | 728 +++++++++++++++++++++++++++++++++++- src/midi/MidiGraph.cpp | 1 + src/midi/MidiGraph.hpp | 1 - 4 files changed, 736 insertions(+), 25 deletions(-) diff --git a/src/midi/ChannelMapNode.cpp b/src/midi/ChannelMapNode.cpp index 9421452..1868b5f 100644 --- a/src/midi/ChannelMapNode.cpp +++ b/src/midi/ChannelMapNode.cpp @@ -4,6 +4,9 @@ #include "fmt/format.h" +#include +#include + mc::midi::ChannelMapNode::ChannelMapNode(const ChannelMap& map) : m_map(map) { } @@ -15,19 +18,21 @@ std::string_view mc::midi::ChannelMapNode::name() bool mc::midi::ChannelMapNode::process(data_span data) { - MessageView view(data); - if (!view.is_system()) - { - const auto current_channel = view.get_channel(); - const auto mapped_channel = m_map.get(current_channel); - if (mapped_channel != ChannelMap::no_channel) - { - view.set_channel(mapped_channel); - return true; - } - return false; - } - return true; + MessageView view(data); + midi::tag_overloads visitor{[this](ChannelMessageViewTag, auto channel_message_view) { + const auto current_channel = channel_message_view.get_channel(); + const auto mapped_channel = m_map.get(current_channel); + if (mapped_channel != ChannelMap::no_channel) + { + channel_message_view.set_channel(mapped_channel); + return true; + } + return false; + }, + [](auto /*any_tag*/, auto /*any_message_view*/) { + return true; + }}; + return std::visit(visitor, view.parse()); } const mc::midi::ChannelMap& mc::midi::ChannelMapNode::map() const diff --git a/src/midi/MessageView.hpp b/src/midi/MessageView.hpp index 3db9088..acb7f2b 100644 --- a/src/midi/MessageView.hpp +++ b/src/midi/MessageView.hpp @@ -1,42 +1,748 @@ #pragma once + +#include "../Utils.hpp" + #include #include +#include +#include +#include namespace mc::midi { -class MessageView final +template +using message_view_tag_t = typename T::tag_t; + +template +struct tag_overloads : Funs... +{ + template + decltype(auto) operator()(MessageV&& message_view) + { + return this->operator()(message_view_tag_t{}, + std::forward(message_view)); + } + +private: + using Funs::operator()...; +}; + +template +tag_overloads(Funs...) -> tag_overloads; + +struct MessageViewTag +{ +}; + +template +class ChannelMessageView; +template +class NoteOffMessageView; +template +class NoteOnMessageView; +template +class PolyKeyPressureMessageView; +template +class ControlChangeMessageView; +template +class ChannelModeMessageView; +template +class ProgramChangeMessageView; +template +class ChannelPressureMessageView; +template +class PitchBendMessageView; +class AllSoundOffMessageView; +class ResetAllControllersMessageView; +template +class LocalControlMessageView; +class AllNotesOffMessageView; +class OmniModeOffMessageView; +class OmniModeOnMessageView; +template +class MonoModeOnMessageView; +class PolyModeOnMessageView; + +template +class MessageView { public: - explicit MessageView(std::span message_data) : m_message_data(message_data) + using tag_t = MessageViewTag; + using span_t = + std::conditional_t, std::span>; + + explicit MessageView(span_t message_data) : m_message_data(message_data) { assert(!m_message_data.empty()); } + span_t get_bytes() { return m_message_data; } + bool is_system() const { // system messages start with 0b1111xxxx return (m_message_data[0] & 0xf0) == 0xf0; } + bool is_channel() const { return !is_system(); } + + std::variant, + NoteOffMessageView, + PolyKeyPressureMessageView, + ControlChangeMessageView, + ChannelModeMessageView, + ProgramChangeMessageView, + ChannelPressureMessageView, + PitchBendMessageView, + AllSoundOffMessageView, + ResetAllControllersMessageView, + LocalControlMessageView, + AllNotesOffMessageView, + OmniModeOffMessageView, + OmniModeOnMessageView, + MonoModeOnMessageView, + PolyModeOnMessageView, + ChannelMessageView, + MessageView> + parse() const + { + if (is_channel()) + { + return utils::variant_cast(ChannelMessageView(m_message_data).parse()); + } + return *this; + } + +protected: + span_t m_message_data; +}; + +MessageView(std::span) -> MessageView; +MessageView(std::span) -> MessageView; + +struct ChannelMessageViewTag +{ +}; + +template +class ChannelMessageView : public MessageView +{ +public: + using tag_t = ChannelMessageViewTag; + explicit ChannelMessageView(MessageView::span_t message_data) + : MessageView(message_data) + { + } + char get_channel() const { // channel is cccc in 0bxxxxcccc of the first byte - // if not a system message - assert(!is_system()); - return m_message_data[0] & 0x0f; + return this->m_message_data[0] & 0x0f; } - void set_channel(unsigned char ch) + char get_channel_human() const { return get_channel() + 1; } + + template + auto set_channel(unsigned char ch) -> std::enable_if_t { // channel is cccc in 0bxxxxcccc of the first byte - // if not a system message - assert(!is_system()); - m_message_data[0] = (m_message_data[0] & 0xf0) | (ch & 0x0f); + this->m_message_data[0] = (this->m_message_data[0] & 0xf0) | (ch & 0x0f); } -private: - std::span m_message_data; + std::variant, + NoteOnMessageView, + PolyKeyPressureMessageView, + ControlChangeMessageView, + ChannelModeMessageView, + ProgramChangeMessageView, + ChannelPressureMessageView, + PitchBendMessageView, + AllSoundOffMessageView, + ResetAllControllersMessageView, + LocalControlMessageView, + AllNotesOffMessageView, + OmniModeOffMessageView, + OmniModeOnMessageView, + MonoModeOnMessageView, + PolyModeOnMessageView, + ChannelMessageView> + parse() const + { + switch (this->m_message_data[0] & 0xf0) + { + case 0x80: + return NoteOffMessageView(this->m_message_data); + case 0x90: + if (this->m_message_data[2] > 0) + { + return NoteOnMessageView(this->m_message_data); + } + else + { + return NoteOffMessageView(this->m_message_data); + } + case 0xa0: + return PolyKeyPressureMessageView(this->m_message_data); + case 0xb0: + if (this->m_message_data[1] < 120) + { + return ControlChangeMessageView(this->m_message_data); + } + else + { + return utils::variant_cast( + ChannelModeMessageView(this->m_message_data).parse()); + } + case 0xc0: + return ProgramChangeMessageView(this->m_message_data); + case 0xd0: + return ChannelPressureMessageView(this->m_message_data); + case 0xe0: + return PitchBendMessageView(this->m_message_data); + default: + return *this; + } + } +}; + +struct NoteMessageViewTag +{ +}; + +template +class NoteMessageView : public ChannelMessageView +{ +public: + using tag_t = NoteMessageViewTag; + + explicit NoteMessageView(MessageView::span_t message_data) + : ChannelMessageView(message_data) + { + assert(message_data.size() == 3); + } + + unsigned char get_note() const { return this->m_message_data[1]; } + + template + auto set_note(unsigned char val) -> std::enable_if_t + { + assert(val < 128); + this->m_message_data[1] = val; + } + + unsigned char get_velocity() const { return this->m_message_data[2]; } + + template + auto set_velocity(unsigned char val) -> std::enable_if_t + { + assert(val < 128); + this->m_message_data[2] = val; + } +}; + +struct NoteOffMessageViewTag : public NoteMessageViewTag +{ +}; + +template +class NoteOffMessageView : public NoteMessageView +{ +public: + using tag_t = NoteOffMessageViewTag; + explicit NoteOffMessageView(MessageView::span_t message_data) + : NoteMessageView(message_data) + { + } +}; + +struct NoteOnMessageViewTag : public NoteMessageViewTag +{ +}; + +template +class NoteOnMessageView : public NoteMessageView +{ +public: + using tag_t = NoteOnMessageViewTag; + explicit NoteOnMessageView(MessageView::span_t message_data) + : NoteMessageView(message_data) + { + } +}; + +struct PolyKeyPressureMessageViewTag : public ChannelMessageViewTag +{ +}; + +template +class PolyKeyPressureMessageView : public ChannelMessageView +{ +public: + using tag_t = PolyKeyPressureMessageViewTag; + explicit PolyKeyPressureMessageView(MessageView::span_t message_data) + : ChannelMessageView(message_data) + { + assert(message_data.size() == 3); + } + + unsigned char get_note() const { return this->m_message_data[1]; } + + template + auto set_note(unsigned char val) -> std::enable_if_t + { + assert(val < 128); + this->m_message_data[1] = val; + } + + unsigned char get_pressure() const { return this->m_message_data[2]; } + + template + auto set_pressure(unsigned char val) -> std::enable_if_t + { + assert(val < 128); + this->m_message_data[2] = val; + } +}; + +struct ControlChangeMessageViewTag : public ChannelMessageViewTag +{ +}; + +template +class ControlChangeMessageView : public ChannelMessageView +{ +public: + using tag_t = ControlChangeMessageViewTag; + explicit ControlChangeMessageView(MessageView::span_t message_data) + : ChannelMessageView(message_data) + { + } + + unsigned char get_controller() const { return this->m_message_data[1]; } + + template + auto set_controller(unsigned char val) -> std::enable_if_t + { + assert(val < 128); + this->m_message_data[1] = val; + } + + unsigned char get_value() const { return this->m_message_data[2]; } + + template + auto set_value(unsigned char val) -> std::enable_if_t + { + assert(val < 128); + this->m_message_data[2] = val; + } + + std::string_view get_function_name() const + { + using namespace std::string_view_literals; + switch (get_controller()) + { + case 0: + return "Bank Select"sv; + case 1: + return "Modulation Wheel"sv; + case 2: + return "Breath Controller"sv; + case 4: + return "Foot Controller"sv; + case 5: + return "Portamento Time"sv; + case 6: + return "Data Entry MSB"sv; + case 7: + return "Channel Volume"sv; + case 8: + return "Balance"sv; + case 10: + return "Pan"sv; + case 11: + return "Expression"sv; + case 12: + return "Effect Control 1"sv; + case 13: + return "Effect Control 2"sv; + case 16: + return "General Purpose Controller 1"sv; + case 17: + return "General Purpose Controller 2"sv; + case 18: + return "General Purpose Controller 3"sv; + case 19: + return "General Purpose Controller 4"sv; + case 32 + 0: + return "Bank Select (LSB)"sv; + case 32 + 1: + return "Modulation Wheel (LSB)"sv; + case 32 + 2: + return "Breath Controller (LSB)"sv; + case 32 + 4: + return "Foot Controller (LSB)"sv; + case 32 + 5: + return "Portamento Time (LSB)"sv; + case 32 + 6: + return "Data Entry LSB"sv; + case 32 + 7: + return "Channel Volume (LSB)"sv; + case 32 + 8: + return "Balance (LSB)"sv; + case 32 + 10: + return "Pan (LSB)"sv; + case 32 + 11: + return "Expression (LSB)"sv; + case 32 + 12: + return "Effect Control 1 (LSB)"sv; + case 32 + 13: + return "Effect Control 2 (LSB)"sv; + case 32 + 16: + return "General Purpose Controller 1 (LSB)"sv; + case 32 + 17: + return "General Purpose Controller 2 (LSB)"sv; + case 32 + 18: + return "General Purpose Controller 3 (LSB)"sv; + case 32 + 19: + return "General Purpose Controller 4 (LSB)"sv; + case 64: + return "Damper Pedal On/Off"sv; + case 65: + return "Portamento On/Off"sv; + case 66: + return "Sostenuto On/Off"sv; + case 67: + return "Soft Pedal On/Off"sv; + case 68: + return "Legato Footswitch"sv; + case 69: + return "Hold 2"sv; + case 70: + return "Sound Controller 1"sv; + case 71: + return "Sound Controller 2"sv; + case 72: + return "Sound Controller 3"sv; + case 73: + return "Sound Controller 4"sv; + case 74: + return "Sound Controller 5"sv; + case 75: + return "Sound Controller 6"sv; + case 76: + return "Sound Controller 7"sv; + case 77: + return "Sound Controller 8"sv; + case 78: + return "Sound Controller 9"sv; + case 79: + return "Sound Controller 10"sv; + case 80: + return "General Purpose Controller 5"sv; + case 81: + return "General Purpose Controller 6"sv; + case 82: + return "General Purpose Controller 7"sv; + case 83: + return "General Purpose Controller 8"sv; + case 84: + return "Portamento Control"sv; + case 88: + return "High Resolution Velocity Prefix"sv; + case 91: + return "Effects 1 Depth"sv; + case 92: + return "Effects 2 Depth"sv; + case 93: + return "Effects 3 Depth"sv; + case 94: + return "Effects 4 Depth"sv; + case 95: + return "Effects 5 Depth"sv; + case 96: + return "Data Increment"sv; + case 97: + return "Data Decrement"sv; + case 98: + return "Non-Registered Parameter Number LSB"sv; + case 99: + return "Non-Registered Parameter Number MSB"sv; + case 100: + return "Registered Parameter Number LSB"sv; + case 101: + return "Registered Parameter Number MSB"sv; + default: + return "Undefined"sv; + } + } +}; + +struct ChannelModeMessageViewTag : public ChannelMessageViewTag +{ +}; + +template +class ChannelModeMessageView : public ChannelMessageView +{ +public: + using tag_t = ChannelModeMessageViewTag; + explicit ChannelModeMessageView(MessageView::span_t message_data) + : ChannelMessageView(message_data) + { + } + + std::variant, + AllNotesOffMessageView, + OmniModeOffMessageView, + OmniModeOnMessageView, + MonoModeOnMessageView, + PolyModeOnMessageView, + ChannelModeMessageView> + parse() const + { + switch (this->m_message_data[1]) + { + case 120: + return AllSoundOffMessageView(this->m_message_data); + case 121: + return ResetAllControllersMessageView(this->m_message_data); + case 122: + return LocalControlMessageView(this->m_message_data); + case 123: + return AllNotesOffMessageView(this->m_message_data); + case 124: + return OmniModeOffMessageView(this->m_message_data); + case 125: + return OmniModeOnMessageView(this->m_message_data); + case 126: + return MonoModeOnMessageView(this->m_message_data); + case 127: + return PolyModeOnMessageView(this->m_message_data); + default: + return *this; + } + } +}; + +struct AllSoundOffMessageViewTag : public ChannelModeMessageViewTag +{ +}; + +class AllSoundOffMessageView : public ChannelModeMessageView +{ +public: + using tag_t = AllSoundOffMessageViewTag; + + explicit AllSoundOffMessageView(MessageView::span_t message_data) + : ChannelModeMessageView(message_data) + { + } +}; + +struct ResetAllControllersMessageViewTag : public ChannelModeMessageViewTag +{ +}; + +class ResetAllControllersMessageView : public ChannelModeMessageView +{ +public: + using tag_t = ResetAllControllersMessageViewTag; + + explicit ResetAllControllersMessageView(MessageView::span_t message_data) + : ChannelModeMessageView(message_data) + { + } +}; + +struct LocalControlMessageViewTag : public ChannelModeMessageViewTag +{ +}; + +template +class LocalControlMessageView : public ChannelModeMessageView +{ +public: + using tag_t = LocalControlMessageViewTag; + + explicit LocalControlMessageView(MessageView::span_t message_data) + : ChannelModeMessageView(message_data) + { + } + + bool get_value() const { return this->m_message_data[2]; } + + template + auto set_value(bool value) -> std::enable_if_t + { + this->m_message_data[2] = value ? 127 : 0; + } +}; + +struct AllNotesOffMessageViewTag : public ChannelModeMessageViewTag +{ +}; + +class AllNotesOffMessageView : public ChannelModeMessageView +{ +public: + using tag_t = AllNotesOffMessageViewTag; + + explicit AllNotesOffMessageView(MessageView::span_t message_data) + : ChannelModeMessageView(message_data) + { + } +}; + +struct OmniModeOffMessageViewTag : public ChannelModeMessageViewTag +{ +}; + +class OmniModeOffMessageView : public ChannelModeMessageView +{ +public: + using tag_t = OmniModeOffMessageViewTag; + + explicit OmniModeOffMessageView(MessageView::span_t message_data) + : ChannelModeMessageView(message_data) + { + } +}; + +struct OmniModeOnMessageViewTag : public ChannelModeMessageViewTag +{ +}; + +class OmniModeOnMessageView : public ChannelModeMessageView +{ +public: + using tag_t = OmniModeOnMessageViewTag; + + explicit OmniModeOnMessageView(MessageView::span_t message_data) + : ChannelModeMessageView(message_data) + { + } +}; + +struct MonoModeOnMessageViewTag : public ChannelModeMessageViewTag +{ +}; + +template +class MonoModeOnMessageView : public ChannelModeMessageView +{ +public: + using tag_t = MonoModeOnMessageViewTag; + + explicit MonoModeOnMessageView(MessageView::span_t message_data) + : ChannelModeMessageView(message_data) + { + } + + unsigned char get_num_channels() const { return this->m_message_data[2]; } + + template + auto set_num_channels(unsigned char value) -> std::enable_if_t + { + this->m_message_data[2] = value; + } +}; + +struct PolyModeOnMessageViewTag : public ChannelModeMessageViewTag +{ +}; + +class PolyModeOnMessageView : public ChannelModeMessageView +{ +public: + using tag_t = PolyModeOnMessageViewTag; + + explicit PolyModeOnMessageView(MessageView::span_t message_data) + : ChannelModeMessageView(message_data) + { + } +}; + +struct ProgramChangeMessageViewTag : public ChannelMessageViewTag +{ +}; + +template +class ProgramChangeMessageView : public ChannelMessageView +{ +public: + using tag_t = ProgramChangeMessageViewTag; + explicit ProgramChangeMessageView(MessageView::span_t message_data) + : ChannelMessageView(message_data) + { + } + + unsigned char get_program_number() const { return this->m_message_data[1]; } + + template + auto set_program_number(unsigned char val) -> std::enable_if_t + { + assert(val < 128); + this->m_message_data[1] = val; + } +}; + +struct ChannelPressureMessageViewTag : public ChannelMessageViewTag +{ +}; + +template +class ChannelPressureMessageView : public ChannelMessageView +{ +public: + using tag_t = ChannelPressureMessageViewTag; + explicit ChannelPressureMessageView(MessageView::span_t message_data) + : ChannelMessageView(message_data) + { + assert(message_data.size() == 2); + } + + unsigned char get_pressure() const { return this->m_message_data[1]; } + + template + auto set_pressure(unsigned char val) -> std::enable_if_t + { + assert(val < 128); + this->m_message_data[1] = val; + } +}; + +struct PitchBendMessageViewTag : public ChannelMessageViewTag +{ +}; + +template +class PitchBendMessageView : public ChannelMessageView +{ +public: + using tag_t = PitchBendMessageViewTag; + explicit PitchBendMessageView(MessageView::span_t message_data) + : ChannelMessageView(message_data) + { + assert(message_data.size() == 3); + } + + std::uint16_t get_value() const + { + return this->m_message_data[1] + std::uint16_t{127} * this->m_message_data[2]; + } + + int get_value_human() const { return get_value() - 8128; } + + template + auto set_value(std::uint16_t val) -> std::enable_if_t + { + assert(val < 127 * 127); + this->m_message_data[1] = static_cast(val % 127); + this->m_message_data[2] = static_cast(val / 127); + } }; } // namespace mc::midi diff --git a/src/midi/MidiGraph.cpp b/src/midi/MidiGraph.cpp index fef4494..d9cc531 100644 --- a/src/midi/MidiGraph.cpp +++ b/src/midi/MidiGraph.cpp @@ -2,6 +2,7 @@ #include +#include #include namespace mc::midi diff --git a/src/midi/MidiGraph.hpp b/src/midi/MidiGraph.hpp index bf8b597..6fb3865 100644 --- a/src/midi/MidiGraph.hpp +++ b/src/midi/MidiGraph.hpp @@ -2,7 +2,6 @@ #include "GraphObserver.hpp" -#include #include #include #include From d980148b7cf07555674f4e21cb68f4c4f446b655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc=20Serf=C5=91z=C5=91?= Date: Tue, 14 May 2024 19:11:16 +0200 Subject: [PATCH 02/12] feat(midi): Added basic note formatting --- src/midi/Note.hpp | 72 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/midi/Note.hpp diff --git a/src/midi/Note.hpp b/src/midi/Note.hpp new file mode 100644 index 0000000..653618e --- /dev/null +++ b/src/midi/Note.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace mc::midi +{ + +class Note final +{ +public: + explicit Note(unsigned char note) : m_note(note) {} + +private: + unsigned char m_note; + + friend struct std::formatter; +}; + +} // namespace mc::midi + +template <> +struct std::formatter +{ + template + constexpr ParseContext::iterator parse(ParseContext& ctx) + { + return ctx.begin(); + } + + template + FmtContext::iterator format(mc::midi::Note note, FmtContext& ctx) const + { + using namespace std::string_view_literals; + const auto key = [=] { + switch (note.m_note % 12) + { + case 0: + return "C"sv; + case 1: + return "C#"sv; + case 2: + return "D"sv; + case 3: + return "D#"sv; + case 4: + return "E"sv; + case 5: + return "F"sv; + case 6: + return "F#"sv; + case 7: + return "G"sv; + case 8: + return "G#"sv; + case 9: + return "A"sv; + case 10: + return "A#"sv; + case 11: + return "B"sv; + default: + return ""sv; + } + }(); + const auto octave = note.m_note / 12; + std::ostringstream out; + out << key << octave; + return std::ranges::copy(std::move(out).str(), ctx.out()).out; + } +}; From d2788378e6a1a8aa907f3edee516e17c60b70b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc=20Serf=C5=91z=C5=91?= Date: Tue, 14 May 2024 19:11:37 +0200 Subject: [PATCH 03/12] feat(ui): Implemented LogNode --- CMakeLists.txt | 1 + src/LogNode.cpp | 209 +++++++++++++++++++++++++++++++++++++++++ src/LogNode.hpp | 72 ++++++++++++++ src/NodeEditor.cpp | 65 ++++++++----- src/NodeFactory.cpp | 6 ++ src/NodeFactory.hpp | 11 ++- src/NodeSerializer.cpp | 15 +++ src/NodeSerializer.hpp | 6 +- src/Utils.hpp | 24 +++++ 9 files changed, 383 insertions(+), 26 deletions(-) create mode 100644 src/LogNode.cpp create mode 100644 src/LogNode.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 56ac622..055c839 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,7 @@ set(sources src/DisconnectedMidiOutNode.cpp src/KeyboardShortcutAggregator.cpp src/Licenses.cpp + src/LogNode.cpp src/MidiChannelNode.cpp src/MidiInNode.cpp src/MidiOutNode.cpp diff --git a/src/LogNode.cpp b/src/LogNode.cpp new file mode 100644 index 0000000..157c45e --- /dev/null +++ b/src/LogNode.cpp @@ -0,0 +1,209 @@ +#include "LogNode.hpp" + +#include "NodeSerializer.hpp" + +#include "midi/MessageView.hpp" +#include "midi/Note.hpp" + +#include "imgui.h" +#include "imnodes.h" + +#include + +std::string_view mc::LogNode::LogMidiNode::name() +{ + return "Log Node"; +} + +mc::LogNode::LogNode() +{ + m_log_midi_node.add_observer(this); +} + +mc::LogNode::~LogNode() +{ + m_log_midi_node.remove_observer(this); +} + +void mc::LogNode::accept_serializer(nlohmann::json& j, const NodeSerializer& serializer) const +{ + serializer.serialize_node(j, *this); +} + +mc::midi::Node* mc::LogNode::get_midi_node() +{ + return &m_log_midi_node; +} + +void mc::LogNode::render_internal() +{ + ImNodes::BeginNodeTitleBar(); + ImGui::TextUnformatted("Message log"); + ImNodes::EndNodeTitleBar(); + ImNodes::BeginInputAttribute(in_id()); + m_input_indicator.render(); + ImGui::SameLine(); + ImGui::TextUnformatted("MIDI in"); + ImNodes::EndInputAttribute(); + + int new_buffer_size = static_cast(m_max_buffer_size); + ImGui::SetNextItemWidth(150.0F); // ToDo scale + if (ImGui::InputInt("Buffer Size", &new_buffer_size)) + { + if (new_buffer_size > 0 && new_buffer_size < 100000) + { + m_max_buffer_size = static_cast(new_buffer_size); + } + } + + std::unique_lock lock(m_buffer_mutex); + while (!m_message_buffer.empty() && m_message_buffer.size() > m_max_buffer_size) + { + m_message_buffer.pop_back(); + } + lock.unlock(); + + if (ImGui::BeginTable( + "Log", 5, ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable, ImVec2{800.F, 300.F})) + { // ToDo scale + + ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_WidthFixed, 140.F); + ImGui::TableSetupColumn("Type"); + ImGui::TableSetupColumn("Channel", ImGuiTableColumnFlags_WidthFixed, 70.F); + ImGui::TableSetupColumn("Data #0"); + ImGui::TableSetupColumn("Data #1"); + ImGui::TableHeadersRow(); + + lock.lock(); + for (const auto& item : m_message_buffer) + { + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + const auto local = std::chrono::zoned_time(std::chrono::current_zone(), item.m_arrived) + .get_local_time(); + const auto seconds = + std::chrono::floor(local.time_since_epoch()).count() % 60; + const auto milliseconds = + std::chrono::floor(local.time_since_epoch()).count() % + 1000; + ImGui::TextUnformatted( + std::format("{:%H:%M}:{:02}.{:03}", local, seconds, milliseconds).c_str()); + + ImGui::TableNextColumn(); + ImGui::TextUnformatted(item.m_name.c_str()); + + ImGui::TableNextColumn(); + if (item.m_channel) + { + ImGui::TextUnformatted(std::to_string(*item.m_channel).c_str()); + } + + ImGui::TableNextColumn(); + if (!item.m_data_0.empty()) + { + ImGui::TextUnformatted(item.m_data_0.c_str()); + } + + ImGui::TableNextColumn(); + if (!item.m_data_1.empty()) + { + ImGui::TextUnformatted(item.m_data_1.c_str()); + } + } + lock.unlock(); + + ImGui::EndTable(); + } +} + +void mc::LogNode::message_received(std::span message_bytes) +{ + using namespace std::string_literals; + + m_input_indicator.trigger(); + midi::MessageView message(message_bytes); + midi::tag_overloads message_visitor{ + [](midi::NoteOnMessageViewTag, auto note_on) -> BufferElement { + return BufferElement{"Note On"s, + note_on.get_channel_human(), + std::format("{}", midi::Note(note_on.get_note())), + std::format("Velocity: {}", note_on.get_velocity())}; + }, + [](midi::NoteOffMessageViewTag, auto note_off) -> BufferElement { + return BufferElement{"Note Off"s, + note_off.get_channel_human(), + std::format("{}", midi::Note(note_off.get_note())), + std::format("Velocity: {}", note_off.get_velocity())}; + }, + [](midi::PolyKeyPressureMessageViewTag, auto poly_key_pressure) -> BufferElement { + return BufferElement{"Poly Aftertouch"s, + poly_key_pressure.get_channel_human(), + std::format("{}", midi::Note(poly_key_pressure.get_note())), + std::format("Pressure: {}", poly_key_pressure.get_pressure())}; + }, + [](midi::AllSoundOffMessageViewTag, auto all_sound_off) -> BufferElement { + return BufferElement{"All Sound Off"s, all_sound_off.get_channel_human(), ""s, ""s}; + }, + [](midi::ResetAllControllersMessageViewTag, auto reset_all_controllers) -> BufferElement { + return BufferElement{ + "Reset All Controllers"s, reset_all_controllers.get_channel_human(), ""s, ""s}; + }, + [](midi::LocalControlMessageViewTag, auto local_control) -> BufferElement { + return BufferElement{"Local Control"s, + local_control.get_channel_human(), + local_control.get_value() ? "On"s : "Off"s, + ""s}; + }, + [](midi::AllNotesOffMessageViewTag, auto all_notes_off) -> BufferElement { + return BufferElement{"All Notes Off"s, all_notes_off.get_channel_human(), ""s, ""s}; + }, + [](midi::OmniModeOffMessageViewTag, auto omni_off) -> BufferElement { + return BufferElement{"Omni Mode Off"s, omni_off.get_channel_human(), ""s, ""s}; + }, + [](midi::OmniModeOnMessageViewTag, auto omni_on) -> BufferElement { + return BufferElement{"Omni Mode On"s, omni_on.get_channel_human(), ""s, ""s}; + }, + [](midi::MonoModeOnMessageViewTag, auto mono_mode) -> BufferElement { + return BufferElement{"Mono Mode On"s, + mono_mode.get_channel_human(), + std::format("Channels: {}", mono_mode.get_num_channels()), + ""s}; + }, + [](midi::PolyModeOnMessageViewTag, auto poly_mode) -> BufferElement { + return BufferElement{"Poly Mode On"s, poly_mode.get_channel_human(), ""s, ""s}; + }, + [](midi::ControlChangeMessageViewTag, auto control_change) -> BufferElement { + return BufferElement{"Control Change"s, + control_change.get_channel_human(), + std::format("CC {} ({})", + control_change.get_controller(), + control_change.get_function_name()), + std::format("Value: {}", control_change.get_value())}; + }, + [](midi::ProgramChangeMessageViewTag, auto program_change) -> BufferElement { + return BufferElement{"Program Change"s, + program_change.get_channel_human(), + std::format("Program: {}", program_change.get_program_number()), + ""s}; + }, + [](midi::ChannelPressureMessageViewTag, auto channel_pressure) -> BufferElement { + return BufferElement{"Channel Aftertouch"s, + channel_pressure.get_channel_human(), + std::format("Pressure: {}", channel_pressure.get_pressure()), + ""s}; + }, + [](midi::PitchBendMessageViewTag, auto pitch_bend) -> BufferElement { + return BufferElement{"Pitch bend"s, + pitch_bend.get_channel_human(), + std::format("Value: {}", pitch_bend.get_value_human()), + ""s}; + }, + [](auto, auto) -> BufferElement { + return BufferElement{"Unknown"s, std::nullopt, ""s, ""s}; + }}; + const BufferElement new_element = std::visit(message_visitor, message.parse()); + + std::lock_guard guard(m_buffer_mutex); + m_message_buffer.push_front(std::move(new_element)); +} diff --git a/src/LogNode.hpp b/src/LogNode.hpp new file mode 100644 index 0000000..e382e58 --- /dev/null +++ b/src/LogNode.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "ActivityIndicator.hpp" +#include "Node.hpp" +#include "midi/MidiGraph.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace mc +{ + +class LogNode final : public Node, private midi::GraphObserver +{ +private: + class LogMidiNode final : public midi::Node + { + public: + std::string_view name() override; + }; + +public: + LogNode(); + ~LogNode(); + + void accept_serializer(nlohmann::json& j, const NodeSerializer& serializer) const override; + +protected: + midi::Node* get_midi_node() override; + void render_internal() override; + +private: + void message_received(std::span message_bytes) override; + + LogMidiNode m_log_midi_node; + ActivityIndicator m_input_indicator; + + static inline constexpr std::size_t default_max_buffer_size = 500; + + std::size_t m_max_buffer_size = default_max_buffer_size; + + struct BufferElement + { + BufferElement(std::string&& name, + std::optional&& channel, + std::string&& data_0, + std::string&& data_1) + : m_arrived(std::chrono::system_clock::now()), m_name(std::move(name)), + m_channel(std::move(channel)), m_data_0(std::move(data_0)), + m_data_1(std::move(data_1)) + { + } + + std::chrono::time_point m_arrived; + std::string m_name; + std::optional m_channel; + std::string m_data_0; + std::string m_data_1; + }; + + std::mutex m_buffer_mutex; + std::deque m_message_buffer; + + friend class NodeSerializer; +}; + +} // namespace mc diff --git a/src/NodeEditor.cpp b/src/NodeEditor.cpp index 7432dfe..6f1cf75 100644 --- a/src/NodeEditor.cpp +++ b/src/NodeEditor.cpp @@ -7,6 +7,7 @@ #include "imnodes.h" #include "nlohmann/json.hpp" +#include "LogNode.hpp" #include "MidiChannelNode.hpp" #include "MidiInNode.hpp" #include "MidiOutNode.hpp" @@ -143,21 +144,37 @@ NodeEditor NodeEditor::from_json(NodeFactory& node_factory, std::shared_ptr NodeEditor::renderContextMenu(bool show_outputting_nodes, bool show_inputting_nodes) { - constexpr ImGuiTreeNodeFlags leaf_flags = - ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; - auto render_contents = [this](auto& infos) -> std::shared_ptr { + auto render_add_node = [this](auto node_builder, + const std::string& name) -> std::shared_ptr { + constexpr ImGuiTreeNodeFlags leaf_flags = + ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; + ImGui::TreeNodeEx(name.c_str(), leaf_flags); + if (ImGui::IsItemClicked()) + { + std::shared_ptr node = node_builder(); + m_nodes.push_back(node); + + ImNodes::SetNodeScreenSpacePos(node->id(), ImGui::GetMousePosOnOpeningCurrentPopup()); + ImGui::CloseCurrentPopup(); + return node; + } + else + { + return nullptr; + } + }; + + auto render_midi_inout_list = [this, render_add_node](auto& infos) -> std::shared_ptr { for (const auto& info : infos) { const std::string port_name = m_port_name_display->get_port_name(info); - ImGui::TreeNodeEx(port_name.c_str(), leaf_flags); - if (ImGui::IsItemClicked()) + if (auto node = render_add_node( + [this, &info] { + return m_node_factory->build_midi_node(info); + }, + port_name); + node != nullptr) { - std::shared_ptr node = m_node_factory->build_midi_node(info); - m_nodes.push_back(node); - - ImNodes::SetNodeScreenSpacePos(node->id(), - ImGui::GetMousePosOnOpeningCurrentPopup()); - ImGui::CloseCurrentPopup(); return node; } } @@ -178,19 +195,23 @@ std::shared_ptr NodeEditor::renderContextMenu(bool show_outputting_nodes, } if (show_outputting_nodes || show_inputting_nodes) { - ImGui::TreeNodeEx("Channel map", leaf_flags); - if (ImGui::IsItemClicked()) - { - node = m_node_factory->build_midi_channel_node(); - ImNodes::SetNodeScreenSpacePos(node->id(), - ImGui::GetMousePosOnOpeningCurrentPopup()); - m_nodes.push_back(node); - ImGui::CloseCurrentPopup(); - } + node = render_add_node( + [this] { + return m_node_factory->build_midi_channel_node(); + }, + "Channel map"); + } + if (show_inputting_nodes) + { + node = render_add_node( + [this] { + return m_node_factory->build_log_node(); + }, + "Message log"); } if (show_outputting_nodes && ImGui::TreeNode("MIDI inputs")) { - auto tmp_node = render_contents(m_input_infos); + auto tmp_node = render_midi_inout_list(m_input_infos); if (tmp_node) // new node was created { node = tmp_node; @@ -199,7 +220,7 @@ std::shared_ptr NodeEditor::renderContextMenu(bool show_outputting_nodes, } if (show_inputting_nodes && ImGui::TreeNode("MIDI outputs")) { - auto tmp_node = render_contents(m_output_infos); + auto tmp_node = render_midi_inout_list(m_output_infos); if (tmp_node) // new node was created { node = tmp_node; diff --git a/src/NodeFactory.cpp b/src/NodeFactory.cpp index 2215d92..04316a7 100644 --- a/src/NodeFactory.cpp +++ b/src/NodeFactory.cpp @@ -2,6 +2,7 @@ #include "DisconnectedMidiInNode.hpp" #include "DisconnectedMidiOutNode.hpp" +#include "LogNode.hpp" #include "MidiChannelNode.hpp" #include "MidiInNode.hpp" #include "MidiOutNode.hpp" @@ -90,6 +91,11 @@ std::shared_ptr NodeFactory::build_midi_channel_node() }); } +std::shared_ptr NodeFactory::build_log_node() +{ + return std::make_shared(); +} + bool NodeFactory::is_node_instantiated(const midi::InputInfo& input_info) { return nullptr != get_cached_midi_node(m_input_nodes, input_info); diff --git a/src/NodeFactory.hpp b/src/NodeFactory.hpp index 9b4b31c..ccf7c2e 100644 --- a/src/NodeFactory.hpp +++ b/src/NodeFactory.hpp @@ -17,6 +17,7 @@ class OutputNode; class DisconnectedMidiInNode; class DisconnectedMidiOutNode; +class LogNode; class MidiChannelNode; class MidiInNode; class MidiOutNode; @@ -27,6 +28,11 @@ class NodeFactory final { public: NodeFactory(const ThemeControl& theme_control, const PortNameDisplay& port_name_display); + NodeFactory(const NodeFactory&) = delete; + NodeFactory(NodeFactory&&) = delete; + + NodeFactory& operator=(const NodeFactory&) = delete; + NodeFactory& operator=(NodeFactory&&) = delete; std::shared_ptr build_midi_node(const midi::InputInfo& input_info); std::shared_ptr build_midi_node(const midi::OutputInfo& output_info); @@ -35,13 +41,14 @@ class NodeFactory final std::shared_ptr build_disconnected_midi_out_node( const std::string& output_name); std::shared_ptr build_midi_channel_node(); + std::shared_ptr build_log_node(); bool is_node_instantiated(const midi::InputInfo& input_info); bool is_node_instantiated(const midi::OutputInfo& output_info); private: - const ThemeControl* m_theme_control; - const PortNameDisplay* m_port_name_display; + const ThemeControl* m_theme_control; + const PortNameDisplay* m_port_name_display; std::map> m_input_nodes; std::map> m_output_nodes; }; diff --git a/src/NodeSerializer.cpp b/src/NodeSerializer.cpp index f7e8fcd..217d995 100644 --- a/src/NodeSerializer.cpp +++ b/src/NodeSerializer.cpp @@ -7,6 +7,7 @@ #include "DisconnectedMidiInNode.hpp" #include "DisconnectedMidiOutNode.hpp" +#include "LogNode.hpp" #include "MidiChannelNode.hpp" #include "MidiInNode.hpp" #include "MidiOutNode.hpp" @@ -71,6 +72,14 @@ void NodeSerializer::serialize_node(json& j, const DisconnectedMidiOutNode& node }; } +void NodeSerializer::serialize_node(nlohmann::json& j, const LogNode& node) const +{ + j = json{ + {"type", "log" }, + {"max_buffer_size", node.m_max_buffer_size}, + }; +} + void NodeSerializer::serialize_node(json& j, const MidiChannelNode& node) const { j = json{ @@ -116,6 +125,12 @@ std::shared_ptr NodeSerializer::deserialize_node(const json& j) const j.at("channels").get(); node = channel_node; } + else if (node_type == "log") + { + auto log_node = m_node_factory->build_log_node(); + j["max_buffer_size"].get_to(log_node->m_max_buffer_size); + node = log_node; + } else { throw std::logic_error("Unexpected node type"); diff --git a/src/NodeSerializer.hpp b/src/NodeSerializer.hpp index 9b69870..3b6c19a 100644 --- a/src/NodeSerializer.hpp +++ b/src/NodeSerializer.hpp @@ -8,6 +8,7 @@ namespace mc class DisconnectedMidiInNode; class DisconnectedMidiOutNode; +class LogNode; class MidiChannelNode; class MidiInNode; class MidiOutNode; @@ -21,11 +22,12 @@ class NodeSerializer final void serialize_node(nlohmann::json& j, const Node& node) const; - void serialize_node(nlohmann::json& j, const MidiInNode& node) const; void serialize_node(nlohmann::json& j, const DisconnectedMidiInNode& node) const; - void serialize_node(nlohmann::json& j, const MidiOutNode& node) const; void serialize_node(nlohmann::json& j, const DisconnectedMidiOutNode& node) const; + void serialize_node(nlohmann::json& j, const LogNode& node) const; void serialize_node(nlohmann::json& j, const MidiChannelNode& node) const; + void serialize_node(nlohmann::json& j, const MidiInNode& node) const; + void serialize_node(nlohmann::json& j, const MidiOutNode& node) const; std::shared_ptr deserialize_node(const nlohmann::json& j) const; diff --git a/src/Utils.hpp b/src/Utils.hpp index 2d3bb68..c3f3827 100644 --- a/src/Utils.hpp +++ b/src/Utils.hpp @@ -2,6 +2,8 @@ #include #include +#include +#include namespace mc::utils { @@ -51,4 +53,26 @@ inline std::string path_to_utf8str(const std::filesystem::path& path) return {u8str.begin(), u8str.end()}; } +template +struct variant_cast_proxy +{ + std::variant v; + + template + operator std::variant() const + { + return std::visit( + [](auto&& arg) -> std::variant { + return arg; + }, + v); + } +}; + +template +auto variant_cast(const std::variant& v) -> variant_cast_proxy +{ + return {v}; +} + } // namespace mc::utils From 2cf21f046930b31bc4362cb798a0c50ea2e0c340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc=20Serf=C5=91z=C5=91?= Date: Tue, 14 May 2024 19:58:38 +0200 Subject: [PATCH 04/12] feat(ui): Add MIDI filtering option per input node --- src/Application.cpp | 23 ++--------------------- src/MidiInNode.cpp | 33 ++++++++++++++++++++++++++++++++- src/MidiInNode.hpp | 4 ++++ src/NodeSerializer.cpp | 22 +++++++++++++++++++--- src/PresetManager.cpp | 12 +----------- src/PresetManager.hpp | 4 +--- 6 files changed, 59 insertions(+), 39 deletions(-) diff --git a/src/Application.cpp b/src/Application.cpp index 3ba88e9..bb4e78c 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -22,7 +22,7 @@ Application::Application(SDL_Window* window, SDL_Renderer* renderer, const std::filesystem::path& path_to_preset) : m_theme_control(m_config, window), m_node_factory(m_theme_control, m_port_name_display), - m_preset{NodeEditor(m_node_factory, m_port_name_display, m_theme_control), {}}, + m_preset{NodeEditor(m_node_factory, m_port_name_display, m_theme_control)}, m_preset_manager(m_preset, m_node_factory, m_config, m_port_name_display, m_theme_control), m_port_name_display(m_config.get_show_full_port_names()), m_welcome_enabled(m_config.get_show_welcome() && path_to_preset.empty()), @@ -109,8 +109,7 @@ void Application::new_preset(bool create_nodes) } if (new_preset) { - m_preset = {NodeEditor(m_node_factory, m_port_name_display, m_theme_control, create_nodes), - {}}; + m_preset = {NodeEditor(m_node_factory, m_port_name_display, m_theme_control, create_nodes)}; m_preset_manager = PresetManager(m_preset, m_node_factory, m_config, m_port_name_display, m_theme_control); } @@ -180,24 +179,6 @@ void Application::render_main_menu() save_preset_as(); } ImGui::Separator(); - if (ImGui::BeginMenu("MIDI message filter")) - { - bool changed = ImGui::MenuItem( - "SysEx", nullptr, &m_preset.m_message_type_mask.m_sysex_enabled); - changed = ImGui::MenuItem( - "Clock", nullptr, &m_preset.m_message_type_mask.m_time_enabled) || - changed; - changed = ImGui::MenuItem("Active Sensing", - nullptr, - &m_preset.m_message_type_mask.m_sensing_enabled) || - changed; - if (changed) - { - // TODO - } - ImGui::EndMenu(); - } - ImGui::Separator(); if (ImGui::MenuItem(" " ICON_FK_TIMES " Exit", "Alt+F4")) { exit(); diff --git a/src/MidiInNode.cpp b/src/MidiInNode.cpp index 9f5620f..838a292 100644 --- a/src/MidiInNode.cpp +++ b/src/MidiInNode.cpp @@ -19,6 +19,7 @@ MidiInNode::MidiInNode(const midi::InputInfo& input_info, : m_input_info(input_info), m_midi_input_node(midi_input_node), m_port_name_display(&port_name_display) { + m_midi_input_node->enable_message_types(m_message_type_mask); m_midi_input_node->add_observer(this); } @@ -46,9 +47,33 @@ void MidiInNode::render_internal() ImNodes::BeginOutputAttribute(out_id()); m_midi_activity.render(); ImGui::SameLine(); - ImGui::TextUnformatted("all channels"); ImNodes::EndOutputAttribute(); + + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4{}); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4{}); + if (ImGui::TreeNode("Advanced")) + { + bool message_type_mask_changed = false; + if (ImGui::Checkbox("Receive SysEx", &m_message_type_mask.m_sysex_enabled)) + { + message_type_mask_changed = true; + } + if (ImGui::Checkbox("Receive MIDI Clock", &m_message_type_mask.m_time_enabled)) + { + message_type_mask_changed = true; + } + if (ImGui::Checkbox("Receive Active Sensing", &m_message_type_mask.m_sensing_enabled)) + { + message_type_mask_changed = true; + } + if (message_type_mask_changed) + { + m_midi_input_node->enable_message_types(m_message_type_mask); + } + ImGui::TreePop(); + } + ImGui::PopStyleColor(2); } void MidiInNode::message_processed(std::span /*message_bytes*/) @@ -56,4 +81,10 @@ void MidiInNode::message_processed(std::span /*message_byte m_midi_activity.trigger(); } +void MidiInNode::set_message_type_mask(midi::MessageTypeMask new_value) +{ + m_message_type_mask = new_value; + m_midi_input_node->enable_message_types(new_value); +} + } // namespace mc diff --git a/src/MidiInNode.hpp b/src/MidiInNode.hpp index 5780971..e263b4f 100644 --- a/src/MidiInNode.hpp +++ b/src/MidiInNode.hpp @@ -3,6 +3,7 @@ #include "ActivityIndicator.hpp" #include "Node.hpp" #include "midi/GraphObserver.hpp" +#include "midi/MessageTypeMask.hpp" #include "midi/MidiInfo.hpp" namespace mc @@ -33,10 +34,13 @@ class MidiInNode final : public Node, private midi::GraphObserver void render_internal() override; void message_processed(std::span message_bytes) override; + void set_message_type_mask(midi::MessageTypeMask new_value); + midi::InputInfo m_input_info; std::shared_ptr m_midi_input_node; ActivityIndicator m_midi_activity; const PortNameDisplay* m_port_name_display; + midi::MessageTypeMask m_message_type_mask; friend class NodeSerializer; }; diff --git a/src/NodeSerializer.cpp b/src/NodeSerializer.cpp index 217d995..91dc307 100644 --- a/src/NodeSerializer.cpp +++ b/src/NodeSerializer.cpp @@ -12,11 +12,20 @@ #include "MidiInNode.hpp" #include "MidiOutNode.hpp" #include "NodeFactory.hpp" +#include "midi/MessageTypeMask.hpp" #include "midi/MidiInfo.hpp" #include "midi/MidiProbe.hpp" NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ImVec2, x, y); +namespace mc::midi +{ +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(MessageTypeMask, + m_sysex_enabled, + m_time_enabled, + m_sensing_enabled); +} + namespace mc { @@ -43,8 +52,9 @@ void NodeSerializer::serialize_node(json& j, const Node& node) const void NodeSerializer::serialize_node(json& j, const MidiInNode& node) const { j = json{ - {"type", "midi_in" }, - {"input_name", node.m_input_info.m_name} + {"type", "midi_in" }, + {"input_name", node.m_input_info.m_name}, + {"message_type_mask", node.m_message_type_mask}, }; } @@ -98,7 +108,13 @@ std::shared_ptr NodeSerializer::deserialize_node(const json& j) const const auto input_info_opt = midi::MidiProbe::get_valid_input(input_name); if (input_info_opt.has_value()) { - node = m_node_factory->build_midi_node(input_info_opt.value()); + auto midi_in_node = m_node_factory->build_midi_node(input_info_opt.value()); + if (j.contains("message_type_mask")) + { + midi_in_node->set_message_type_mask( + j.at("message_type_mask").get()); + } + node = midi_in_node; } else { diff --git a/src/PresetManager.cpp b/src/PresetManager.cpp index 36246aa..4dd3927 100644 --- a/src/PresetManager.cpp +++ b/src/PresetManager.cpp @@ -11,21 +11,12 @@ #include "Utils.hpp" #include "Version.hpp" -namespace mc::midi -{ -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(MessageTypeMask, - m_sysex_enabled, - m_time_enabled, - m_sensing_enabled); -} - namespace mc { void Preset::to_json(nlohmann::json& j) const { m_node_editor.to_json(j["editor"]); - j["enabled_message_types"] = m_message_type_mask; } Preset Preset::from_json(NodeFactory& node_factory, @@ -33,8 +24,7 @@ Preset Preset::from_json(NodeFactory& node_factory, const ThemeControl& theme_control, const nlohmann::json& j) { - return {NodeEditor::from_json(node_factory, port_name_display, theme_control, j.at("editor")), - j.at("enabled_message_types")}; + return {NodeEditor::from_json(node_factory, port_name_display, theme_control, j.at("editor"))}; } PresetManager::PresetManager(const Preset& preset, diff --git a/src/PresetManager.hpp b/src/PresetManager.hpp index 90f2a47..a7bff11 100644 --- a/src/PresetManager.hpp +++ b/src/PresetManager.hpp @@ -5,7 +5,6 @@ #include "nlohmann/json.hpp" #include "NodeEditor.hpp" -#include "midi/MessageTypeMask.hpp" namespace mc { @@ -16,8 +15,7 @@ class ThemeControl; struct Preset { - NodeEditor m_node_editor; - midi::MessageTypeMask m_message_type_mask; + NodeEditor m_node_editor; void to_json(nlohmann::json& j) const; static Preset from_json(NodeFactory& node_factory, From 0be5131935db5a6a12126a1d35bad37204312937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc=20Serf=C5=91z=C5=91?= Date: Fri, 17 May 2024 18:24:17 +0200 Subject: [PATCH 05/12] refactor: Simplified handling of disconnect/reconnect of MIDI devices --- CMakeLists.txt | 3 - src/DisconnectedMidiInNode.cpp | 28 ----- src/DisconnectedMidiInNode.hpp | 23 ---- src/DisconnectedMidiOutNode.cpp | 29 ----- src/DisconnectedMidiOutNode.hpp | 24 ----- src/MidiInNode.cpp | 95 ++++++++++++----- src/MidiInNode.hpp | 8 +- src/MidiOutNode.cpp | 58 ++++++++-- src/MidiOutNode.hpp | 7 +- src/MidiPortWatchdog.cpp | 182 -------------------------------- src/MidiPortWatchdog.hpp | 19 ---- src/Node.hpp | 3 +- src/NodeEditor.cpp | 4 +- src/NodeFactory.cpp | 43 +++++--- src/NodeFactory.hpp | 14 +-- src/NodeSerializer.cpp | 54 ++-------- src/NodeSerializer.hpp | 4 - src/PortNameDisplay.cpp | 37 ++----- src/PortNameDisplay.hpp | 6 +- src/midi/MessageTypeMask.hpp | 2 + src/midi/MidiProbe.cpp | 6 +- src/midi/MidiProbe.hpp | 6 +- 22 files changed, 193 insertions(+), 462 deletions(-) delete mode 100644 src/DisconnectedMidiInNode.cpp delete mode 100644 src/DisconnectedMidiInNode.hpp delete mode 100644 src/DisconnectedMidiOutNode.cpp delete mode 100644 src/DisconnectedMidiOutNode.hpp delete mode 100644 src/MidiPortWatchdog.cpp delete mode 100644 src/MidiPortWatchdog.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 055c839..60e19f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,15 +49,12 @@ set(sources src/ActivityIndicator.cpp src/Application.cpp src/ConfigFile.cpp - src/DisconnectedMidiInNode.cpp - src/DisconnectedMidiOutNode.cpp src/KeyboardShortcutAggregator.cpp src/Licenses.cpp src/LogNode.cpp src/MidiChannelNode.cpp src/MidiInNode.cpp src/MidiOutNode.cpp - src/MidiPortWatchdog.cpp src/Node.cpp src/NodeEditor.cpp src/NodeFactory.cpp diff --git a/src/DisconnectedMidiInNode.cpp b/src/DisconnectedMidiInNode.cpp deleted file mode 100644 index c765565..0000000 --- a/src/DisconnectedMidiInNode.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "DisconnectedMidiInNode.hpp" - -#include "imgui.h" -#include "imnodes.h" - -#include "PortNameDisplay.hpp" - -namespace mc -{ - -DisconnectedMidiInNode::DisconnectedMidiInNode(const std::string& input_name, - const PortNameDisplay& port_name_display) - : m_input_name(input_name), m_port_name_display(&port_name_display) -{ -} - -void DisconnectedMidiInNode::render_internal() -{ - ImNodes::BeginNodeTitleBar(); - const auto display_name = m_port_name_display->get_port_name(midi::InputInfo{0, m_input_name}); - ImGui::TextUnformatted(display_name.c_str()); - ImNodes::EndNodeTitleBar(); - ImNodes::BeginOutputAttribute(out_id()); - ImGui::TextUnformatted("disconnected"); - ImNodes::EndOutputAttribute(); -} - -} // namespace mc diff --git a/src/DisconnectedMidiInNode.hpp b/src/DisconnectedMidiInNode.hpp deleted file mode 100644 index d532cdf..0000000 --- a/src/DisconnectedMidiInNode.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "DisconnectedNode.hpp" - -namespace mc -{ - -class PortNameDisplay; - -class DisconnectedMidiInNode final : public DisconnectedNode -{ -public: - DisconnectedMidiInNode(const std::string& input_name, const PortNameDisplay& port_name_display); - const std::string& get_name() const { return m_input_name; } - -private: - void render_internal() override; - - std::string m_input_name; - const PortNameDisplay* m_port_name_display; - friend class NodeSerializer; -}; - -} // namespace mc diff --git a/src/DisconnectedMidiOutNode.cpp b/src/DisconnectedMidiOutNode.cpp deleted file mode 100644 index aab7ac0..0000000 --- a/src/DisconnectedMidiOutNode.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "DisconnectedMidiOutNode.hpp" - -#include "imgui.h" -#include "imnodes.h" - -#include "PortNameDisplay.hpp" - -namespace mc -{ - -DisconnectedMidiOutNode::DisconnectedMidiOutNode(const std::string& output_name, - const PortNameDisplay& port_name_display) - : m_output_name(output_name), m_port_name_display(&port_name_display) -{ -} - -void DisconnectedMidiOutNode::render_internal() -{ - ImNodes::BeginNodeTitleBar(); - const auto display_name = - m_port_name_display->get_port_name(midi::OutputInfo{0, m_output_name}); - ImGui::TextUnformatted(display_name.c_str()); - ImNodes::EndNodeTitleBar(); - ImNodes::BeginInputAttribute(in_id()); - ImGui::TextUnformatted("disconnected"); - ImNodes::EndInputAttribute(); -} - -} // namespace mc diff --git a/src/DisconnectedMidiOutNode.hpp b/src/DisconnectedMidiOutNode.hpp deleted file mode 100644 index 90e319b..0000000 --- a/src/DisconnectedMidiOutNode.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include "DisconnectedNode.hpp" - -namespace mc -{ - -class PortNameDisplay; - -class DisconnectedMidiOutNode final : public DisconnectedNode -{ -public: - DisconnectedMidiOutNode(const std::string& output_name, - const PortNameDisplay& port_name_display); - const std::string& get_name() const { return m_output_name; } - -private: - void render_internal() override; - - std::string m_output_name; - const PortNameDisplay* m_port_name_display; - friend class NodeSerializer; -}; - -} // namespace mc diff --git a/src/MidiInNode.cpp b/src/MidiInNode.cpp index 838a292..8e7dd43 100644 --- a/src/MidiInNode.cpp +++ b/src/MidiInNode.cpp @@ -13,19 +13,25 @@ namespace mc { -MidiInNode::MidiInNode(const midi::InputInfo& input_info, +MidiInNode::MidiInNode(std::string_view input_name, std::shared_ptr midi_input_node, const PortNameDisplay& port_name_display) - : m_input_info(input_info), m_midi_input_node(midi_input_node), + : m_input_name(input_name), m_midi_input_node(midi_input_node), m_port_name_display(&port_name_display) { - m_midi_input_node->enable_message_types(m_message_type_mask); - m_midi_input_node->add_observer(this); + if (m_midi_input_node) + { + m_midi_input_node->enable_message_types(m_message_type_mask); + m_midi_input_node->add_observer(this); + } } MidiInNode::~MidiInNode() { - m_midi_input_node->remove_observer(this); + if (m_midi_input_node) + { + m_midi_input_node->remove_observer(this); + } } void MidiInNode::accept_serializer(nlohmann::json& j, const NodeSerializer& serializer) const @@ -40,37 +46,39 @@ midi::Node* MidiInNode::get_midi_node() void MidiInNode::render_internal() { + check_midi_node_connected(); + ImNodes::BeginNodeTitleBar(); - const std::string node_title = m_port_name_display->get_port_name(m_input_info); + const std::string node_title = m_port_name_display->get_port_name(m_input_name); ImGui::TextUnformatted(node_title.c_str()); ImNodes::EndNodeTitleBar(); ImNodes::BeginOutputAttribute(out_id()); - m_midi_activity.render(); - ImGui::SameLine(); - ImGui::TextUnformatted("all channels"); + if (m_midi_input_node) + { + m_midi_activity.render(); + ImGui::SameLine(); + ImGui::TextUnformatted("all channels"); + } + else + { + ImGui::TextUnformatted("disconnected"); + } ImNodes::EndOutputAttribute(); + if (m_midi_input_node == nullptr) + { + return; + } + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4{}); ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4{}); if (ImGui::TreeNode("Advanced")) { - bool message_type_mask_changed = false; - if (ImGui::Checkbox("Receive SysEx", &m_message_type_mask.m_sysex_enabled)) - { - message_type_mask_changed = true; - } - if (ImGui::Checkbox("Receive MIDI Clock", &m_message_type_mask.m_time_enabled)) - { - message_type_mask_changed = true; - } - if (ImGui::Checkbox("Receive Active Sensing", &m_message_type_mask.m_sensing_enabled)) - { - message_type_mask_changed = true; - } - if (message_type_mask_changed) - { - m_midi_input_node->enable_message_types(m_message_type_mask); - } + midi::MessageTypeMask new_message_type_mask; + ImGui::Checkbox("Receive SysEx", &new_message_type_mask.m_sysex_enabled); + ImGui::Checkbox("Receive MIDI Clock", &new_message_type_mask.m_time_enabled); + ImGui::Checkbox("Receive Active Sensing", &new_message_type_mask.m_sensing_enabled); + set_message_type_mask(new_message_type_mask); ImGui::TreePop(); } ImGui::PopStyleColor(2); @@ -81,10 +89,43 @@ void MidiInNode::message_processed(std::span /*message_byte m_midi_activity.trigger(); } +void MidiInNode::check_midi_node_connected() +{ + const auto input_opt = midi::MidiProbe::get_valid_input(m_input_name); + if (input_opt && m_midi_input_node == nullptr) + { + m_midi_input_node = std::make_shared(*input_opt); + m_midi_input_node->enable_message_types(m_message_type_mask); + m_midi_input_node->add_observer(this); + for (auto output_connected_node : get_output_connections()) + { + if (auto node = output_connected_node.lock(); node != nullptr) + { + if (auto* midi_node = node->get_midi_node(); midi_node != nullptr) + { + midi::Connection::create(*m_midi_input_node, *midi_node); + } + } + } + } + else if (!input_opt && m_midi_input_node != nullptr) + { + m_midi_input_node->remove_observer(this); + m_midi_input_node = nullptr; + } +} + void MidiInNode::set_message_type_mask(midi::MessageTypeMask new_value) { + if (m_message_type_mask == new_value) + { + return; + } m_message_type_mask = new_value; - m_midi_input_node->enable_message_types(new_value); + if (m_midi_input_node) + { + m_midi_input_node->enable_message_types(new_value); + } } } // namespace mc diff --git a/src/MidiInNode.hpp b/src/MidiInNode.hpp index e263b4f..77bb9eb 100644 --- a/src/MidiInNode.hpp +++ b/src/MidiInNode.hpp @@ -6,6 +6,8 @@ #include "midi/MessageTypeMask.hpp" #include "midi/MidiInfo.hpp" +#include + namespace mc { namespace midi @@ -18,14 +20,13 @@ class PortNameDisplay; class MidiInNode final : public Node, private midi::GraphObserver { public: - MidiInNode(const midi::InputInfo& input_info, + MidiInNode(std::string_view input_name, std::shared_ptr midi_input_node, const PortNameDisplay& port_name_display); ~MidiInNode(); void accept_serializer(nlohmann::json& j, const NodeSerializer& serializer) const override; - const midi::InputInfo& get_info() const { return m_input_info; } protected: midi::Node* get_midi_node() override; @@ -34,9 +35,10 @@ class MidiInNode final : public Node, private midi::GraphObserver void render_internal() override; void message_processed(std::span message_bytes) override; + void check_midi_node_connected(); void set_message_type_mask(midi::MessageTypeMask new_value); - midi::InputInfo m_input_info; + std::string m_input_name; std::shared_ptr m_midi_input_node; ActivityIndicator m_midi_activity; const PortNameDisplay* m_port_name_display; diff --git a/src/MidiOutNode.cpp b/src/MidiOutNode.cpp index a48c5b8..b20138e 100644 --- a/src/MidiOutNode.cpp +++ b/src/MidiOutNode.cpp @@ -5,23 +5,30 @@ #include "NodeSerializer.hpp" #include "PortNameDisplay.hpp" +#include "midi/MidiProbe.hpp" #include "midi/OutputNode.hpp" namespace mc { -MidiOutNode::MidiOutNode(const midi::OutputInfo& output_info, +MidiOutNode::MidiOutNode(std::string_view output_name, std::shared_ptr midi_output_node, const PortNameDisplay& port_name_display) - : m_output_info(output_info), m_midi_output_node(midi_output_node), + : m_output_name(output_name), m_midi_output_node(midi_output_node), m_port_name_display(&port_name_display) { - m_midi_output_node->add_observer(this); + if (m_midi_output_node) + { + m_midi_output_node->add_observer(this); + } } MidiOutNode::~MidiOutNode() { - m_midi_output_node->remove_observer(this); + if (m_midi_output_node) + { + m_midi_output_node->remove_observer(this); + } } void MidiOutNode::accept_serializer(nlohmann::json& j, const NodeSerializer& serializer) const @@ -36,14 +43,24 @@ midi::Node* MidiOutNode::get_midi_node() void MidiOutNode::render_internal() { + check_midi_node_connected(); + ImNodes::BeginNodeTitleBar(); - const std::string node_title = m_port_name_display->get_port_name(m_output_info); + const std::string node_title = m_port_name_display->get_port_name(m_output_name); ImGui::TextUnformatted(node_title.c_str()); ImNodes::EndNodeTitleBar(); ImNodes::BeginInputAttribute(in_id()); - m_midi_activity.render(); - ImGui::SameLine(); - ImGui::TextUnformatted("all channels"); + + if (m_midi_output_node) + { + m_midi_activity.render(); + ImGui::SameLine(); + ImGui::TextUnformatted("all channels"); + } + else + { + ImGui::TextUnformatted("disconnected"); + } ImNodes::EndInputAttribute(); } @@ -53,4 +70,29 @@ void MidiOutNode::message_received(std::span /*message_byte m_midi_activity.trigger(); } +void MidiOutNode::check_midi_node_connected() +{ + const auto output_opt = midi::MidiProbe::get_valid_output(m_output_name); + if (output_opt && m_midi_output_node == nullptr) + { + m_midi_output_node = std::make_shared(*output_opt); + m_midi_output_node->add_observer(this); + for (auto input_connected_node : get_input_connections()) + { + if (auto node = input_connected_node.lock(); node != nullptr) + { + if (auto* midi_node = node->get_midi_node(); midi_node != nullptr) + { + midi::Connection::create(*midi_node, *m_midi_output_node); + } + } + } + } + else if (!output_opt && m_midi_output_node != nullptr) + { + m_midi_output_node->remove_observer(this); + m_midi_output_node = nullptr; + } +} + } // namespace mc diff --git a/src/MidiOutNode.hpp b/src/MidiOutNode.hpp index 2c289f4..27a3e5f 100644 --- a/src/MidiOutNode.hpp +++ b/src/MidiOutNode.hpp @@ -16,14 +16,13 @@ class PortNameDisplay; class MidiOutNode final : public Node, private midi::GraphObserver { public: - MidiOutNode(const midi::OutputInfo& output_info, + MidiOutNode(std::string_view output_name, std::shared_ptr midi_output_node, const PortNameDisplay& port_name_display); ~MidiOutNode(); void accept_serializer(nlohmann::json& j, const NodeSerializer& serializer) const override; - const midi::OutputInfo& get_info() const { return m_output_info; } protected: midi::Node* get_midi_node() override; @@ -32,7 +31,9 @@ class MidiOutNode final : public Node, private midi::GraphObserver void render_internal() override; void message_received(std::span message_bytes) override; - midi::OutputInfo m_output_info; + void check_midi_node_connected(); + + std::string m_output_name; std::shared_ptr m_midi_output_node; ActivityIndicator m_midi_activity; const PortNameDisplay* m_port_name_display; diff --git a/src/MidiPortWatchdog.cpp b/src/MidiPortWatchdog.cpp deleted file mode 100644 index 92cf846..0000000 --- a/src/MidiPortWatchdog.cpp +++ /dev/null @@ -1,182 +0,0 @@ -#include "MidiPortWatchdog.hpp" - -#include "spdlog/spdlog.h" - -#include "DisconnectedMidiInNode.hpp" -#include "DisconnectedMidiOutNode.hpp" -#include "MidiInNode.hpp" -#include "MidiOutNode.hpp" -#include "NodeFactory.hpp" -#include "midi/MidiProbe.hpp" - -namespace mc -{ -namespace -{ - -template -struct MidiNodeTraits; - -template <> -struct MidiNodeTraits -{ - static constexpr bool is_disconnected = false; -}; - -template <> -struct MidiNodeTraits -{ - static constexpr bool is_disconnected = false; -}; - -template <> -struct MidiNodeTraits -{ - static constexpr bool is_disconnected = true; -}; - -template <> -struct MidiNodeTraits -{ - static constexpr bool is_disconnected = true; -}; - -template -struct NodeBuilder -{ - NodeFactory* m_node_factory{}; -}; - -std::shared_ptr build_node(const NodeBuilder& builder, - const midi::InputInfo& input_info) -{ - return builder.m_node_factory->build_midi_node(input_info); -} - -std::shared_ptr build_node(const NodeBuilder& builder, - const std::string& input_name) -{ - return builder.m_node_factory->build_disconnected_midi_in_node(input_name); -} - -std::shared_ptr build_node(const NodeBuilder& builder, - const midi::OutputInfo& output_info) -{ - return builder.m_node_factory->build_midi_node(output_info); -} - -std::shared_ptr build_node(const NodeBuilder& builder, - const std::string& output_name) -{ - return builder.m_node_factory->build_disconnected_midi_out_node(output_name); -} - -template -bool update_node(std::shared_ptr& node_ptr, - ValidGetterFun valid_getter_fun, - const NodeBuilder& builder) -{ - if (auto* midi_ptr = dynamic_cast(node_ptr.get()); midi_ptr != nullptr) - { - std::string midi_port_name; - if constexpr (MidiNodeTraits::is_disconnected) - { - midi_port_name = midi_ptr->get_name(); - } - else - { - midi_port_name = midi_ptr->get_info().m_name; - } - - const auto get_memo = [](std::shared_ptr& old_node) { - return std::tuple{old_node->get_input_connections(), - old_node->get_output_connections(), - ImNodes::GetNodeGridSpacePos(old_node->id())}; - }; - const auto set_memo = [](std::shared_ptr& new_node, const auto& memo) { - for (const auto& outputting_node : std::get<0>(memo)) - { - auto outputting_node_ptr = outputting_node.lock(); - if (outputting_node_ptr != nullptr) - { - outputting_node_ptr->connect_output(new_node, outputting_node); - } - } - for (const auto& connected_node : std::get<1>(memo)) - { - new_node->connect_output(connected_node, new_node); - } - ImNodes::SetNodeGridSpacePos(new_node->id(), std::get<2>(memo)); - }; - - const auto valid_info = valid_getter_fun(midi_port_name); - - if constexpr (MidiNodeTraits::is_disconnected) - { - if (valid_info.has_value()) - { - spdlog::info("Re-activating node for connected device \"{}\"", midi_port_name); - const auto memo = get_memo(node_ptr); - node_ptr = build_node(builder, valid_info.value()); - set_memo(node_ptr, memo); - } - } - else - { - if (!valid_info.has_value() || valid_info.value().m_id != midi_ptr->get_info().m_id) - { - spdlog::info("Deactivating node for disconnected device \"{}\"", midi_port_name); - const auto memo = get_memo(node_ptr); - node_ptr = build_node(builder, midi_port_name); - set_memo(node_ptr, memo); - } - } - return true; - } - else - { - return false; - } -} - -} // namespace - -void MidiPortWatchdog::check_nodes(std::vector>& nodes, - NodeFactory& node_factory) -{ - - for (auto& node_ptr : nodes) - { - const auto get_valid_input = [](const auto& name) { - return midi::MidiProbe::get_valid_input(name); - }; - const auto get_valid_output = [](const auto& name) { - return midi::MidiProbe::get_valid_output(name); - }; - - if (update_node( - node_ptr, get_valid_input, NodeBuilder{&node_factory})) - { - // do nothing, update done in side effect - } - else if (update_node( - node_ptr, - get_valid_output, - NodeBuilder{&node_factory})) - { - // do nothing, update done in side effect - } - else if (update_node( - node_ptr, get_valid_input, NodeBuilder{&node_factory})) - { - // do nothing, update done in side effect - } - else if (update_node( - node_ptr, get_valid_output, NodeBuilder{&node_factory})) - { - // do nothing, update done in side effect - } - } -} - -} // namespace mc \ No newline at end of file diff --git a/src/MidiPortWatchdog.hpp b/src/MidiPortWatchdog.hpp deleted file mode 100644 index 82794f5..0000000 --- a/src/MidiPortWatchdog.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include -#include - -#include "Node.hpp" - -namespace mc -{ -class NodeFactory; - -class MidiPortWatchdog final -{ -public: - MidiPortWatchdog() = delete; - - static void check_nodes(std::vector>& nodes, NodeFactory& node_factory); -}; - -} // namespace mc diff --git a/src/Node.hpp b/src/Node.hpp index 9d38da9..312e6e6 100644 --- a/src/Node.hpp +++ b/src/Node.hpp @@ -39,8 +39,9 @@ class Node static bool is_in_id(int id) { return id % 2 == 0; } static bool is_out_id(int id) { return !is_in_id(id); } -protected: virtual midi::Node* get_midi_node() = 0; + +protected: virtual void render_internal() = 0; virtual void push_style() const {} virtual void pop_style() const {} diff --git a/src/NodeEditor.cpp b/src/NodeEditor.cpp index 6f1cf75..fb1b9cb 100644 --- a/src/NodeEditor.cpp +++ b/src/NodeEditor.cpp @@ -11,7 +11,6 @@ #include "MidiChannelNode.hpp" #include "MidiInNode.hpp" #include "MidiOutNode.hpp" -#include "MidiPortWatchdog.hpp" #include "NodeFactory.hpp" #include "NodeSerializer.hpp" #include "PortNameDisplay.hpp" @@ -167,7 +166,7 @@ std::shared_ptr NodeEditor::renderContextMenu(bool show_outputting_nodes, auto render_midi_inout_list = [this, render_add_node](auto& infos) -> std::shared_ptr { for (const auto& info : infos) { - const std::string port_name = m_port_name_display->get_port_name(info); + const std::string port_name = m_port_name_display->get_port_name(info.m_name); if (auto node = render_add_node( [this, &info] { return m_node_factory->build_midi_node(info); @@ -235,7 +234,6 @@ std::shared_ptr NodeEditor::renderContextMenu(bool show_outputting_nodes, void NodeEditor::renderNodes() { - MidiPortWatchdog::check_nodes(m_nodes, *m_node_factory); for (const auto& node : m_nodes) { node->render(); diff --git a/src/NodeFactory.cpp b/src/NodeFactory.cpp index 04316a7..893c3df 100644 --- a/src/NodeFactory.cpp +++ b/src/NodeFactory.cpp @@ -1,13 +1,12 @@ #include "NodeFactory.hpp" -#include "DisconnectedMidiInNode.hpp" -#include "DisconnectedMidiOutNode.hpp" #include "LogNode.hpp" #include "MidiChannelNode.hpp" #include "MidiInNode.hpp" #include "MidiOutNode.hpp" #include "Theme.hpp" #include "midi/InputNode.hpp" +#include "midi/MidiProbe.hpp" #include "midi/OutputNode.hpp" namespace mc @@ -62,26 +61,46 @@ NodeFactory::NodeFactory(const ThemeControl& theme_control, std::shared_ptr NodeFactory::build_midi_node(const midi::InputInfo& input_info) { - return std::make_shared( - input_info, make_cached_midi_node(m_input_nodes, input_info), *m_port_name_display); + return build_midi_in_node(input_info.m_name); } std::shared_ptr NodeFactory::build_midi_node(const midi::OutputInfo& output_info) { - return std::make_shared( - output_info, make_cached_midi_node(m_output_nodes, output_info), *m_port_name_display); + return build_midi_out_node(output_info.m_name); } -std::shared_ptr NodeFactory::build_disconnected_midi_in_node( - const std::string& input_name) +std::shared_ptr NodeFactory::build_midi_in_node(std::string_view input_name) { - return std::make_shared(input_name, *m_port_name_display); + const auto input_info_opt = midi::MidiProbe::get_valid_input(input_name); + auto midi_node = [&]() -> std::shared_ptr { + if (input_info_opt) + { + return make_cached_midi_node(m_input_nodes, *input_info_opt); + } + else + { + return nullptr; + } + }(); + + return std::make_shared(input_name, std::move(midi_node), *m_port_name_display); } -std::shared_ptr NodeFactory::build_disconnected_midi_out_node( - const std::string& output_name) +std::shared_ptr NodeFactory::build_midi_out_node(std::string_view output_name) { - return std::make_shared(output_name, *m_port_name_display); + const auto output_info_opt = midi::MidiProbe::get_valid_output(output_name); + auto midi_node = [&]() -> std::shared_ptr { + if (output_info_opt) + { + return make_cached_midi_node(m_output_nodes, *output_info_opt); + } + else + { + return nullptr; + } + }(); + + return std::make_shared(output_name, std::move(midi_node), *m_port_name_display); } std::shared_ptr NodeFactory::build_midi_channel_node() diff --git a/src/NodeFactory.hpp b/src/NodeFactory.hpp index ccf7c2e..02ad5d9 100644 --- a/src/NodeFactory.hpp +++ b/src/NodeFactory.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include namespace mc { @@ -15,8 +15,6 @@ struct OutputInfo; class OutputNode; } // namespace midi -class DisconnectedMidiInNode; -class DisconnectedMidiOutNode; class LogNode; class MidiChannelNode; class MidiInNode; @@ -34,12 +32,10 @@ class NodeFactory final NodeFactory& operator=(const NodeFactory&) = delete; NodeFactory& operator=(NodeFactory&&) = delete; - std::shared_ptr build_midi_node(const midi::InputInfo& input_info); - std::shared_ptr build_midi_node(const midi::OutputInfo& output_info); - std::shared_ptr build_disconnected_midi_in_node( - const std::string& input_name); - std::shared_ptr build_disconnected_midi_out_node( - const std::string& output_name); + std::shared_ptr build_midi_node(const midi::InputInfo& input_info); + std::shared_ptr build_midi_node(const midi::OutputInfo& output_info); + std::shared_ptr build_midi_in_node(std::string_view input_name); + std::shared_ptr build_midi_out_node(std::string_view output_name); std::shared_ptr build_midi_channel_node(); std::shared_ptr build_log_node(); diff --git a/src/NodeSerializer.cpp b/src/NodeSerializer.cpp index 91dc307..2c047af 100644 --- a/src/NodeSerializer.cpp +++ b/src/NodeSerializer.cpp @@ -5,16 +5,12 @@ #include "imnodes.h" #include "nlohmann/json.hpp" -#include "DisconnectedMidiInNode.hpp" -#include "DisconnectedMidiOutNode.hpp" #include "LogNode.hpp" #include "MidiChannelNode.hpp" #include "MidiInNode.hpp" #include "MidiOutNode.hpp" #include "NodeFactory.hpp" #include "midi/MessageTypeMask.hpp" -#include "midi/MidiInfo.hpp" -#include "midi/MidiProbe.hpp" NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ImVec2, x, y); @@ -53,28 +49,12 @@ void NodeSerializer::serialize_node(json& j, const MidiInNode& node) const { j = json{ {"type", "midi_in" }, - {"input_name", node.m_input_info.m_name}, + {"input_name", node.m_input_name }, {"message_type_mask", node.m_message_type_mask}, }; } -void NodeSerializer::serialize_node(json& j, const DisconnectedMidiInNode& node) const -{ - j = json{ - {"type", "midi_in" }, - {"input_name", node.m_input_name} - }; -} - void NodeSerializer::serialize_node(json& j, const MidiOutNode& node) const -{ - j = json{ - {"type", "midi_out" }, - {"output_name", node.m_output_info.m_name} - }; -} - -void NodeSerializer::serialize_node(json& j, const DisconnectedMidiOutNode& node) const { j = json{ {"type", "midi_out" }, @@ -104,35 +84,19 @@ std::shared_ptr NodeSerializer::deserialize_node(const json& j) const std::shared_ptr node; if (node_type == "midi_in") { - const auto input_name = j.at("input_name").get(); - const auto input_info_opt = midi::MidiProbe::get_valid_input(input_name); - if (input_info_opt.has_value()) - { - auto midi_in_node = m_node_factory->build_midi_node(input_info_opt.value()); - if (j.contains("message_type_mask")) - { - midi_in_node->set_message_type_mask( - j.at("message_type_mask").get()); - } - node = midi_in_node; - } - else + const auto input_name = j.at("input_name").get(); + auto midi_in_node = m_node_factory->build_midi_in_node(input_name); + if (j.contains("message_type_mask")) { - node = m_node_factory->build_disconnected_midi_in_node(input_name); + midi_in_node->set_message_type_mask( + j.at("message_type_mask").get()); } + node = midi_in_node; } else if (node_type == "midi_out") { - const auto output_name = j.at("output_name").get(); - const auto output_info_opt = midi::MidiProbe::get_valid_output(output_name); - if (output_info_opt.has_value()) - { - node = m_node_factory->build_midi_node(output_info_opt.value()); - } - else - { - node = m_node_factory->build_disconnected_midi_out_node(output_name); - } + const auto output_name = j.at("output_name").get(); + node = m_node_factory->build_midi_out_node(output_name); } else if (node_type == "midi_channel") { diff --git a/src/NodeSerializer.hpp b/src/NodeSerializer.hpp index 3b6c19a..48abf63 100644 --- a/src/NodeSerializer.hpp +++ b/src/NodeSerializer.hpp @@ -6,8 +6,6 @@ namespace mc { -class DisconnectedMidiInNode; -class DisconnectedMidiOutNode; class LogNode; class MidiChannelNode; class MidiInNode; @@ -22,8 +20,6 @@ class NodeSerializer final void serialize_node(nlohmann::json& j, const Node& node) const; - void serialize_node(nlohmann::json& j, const DisconnectedMidiInNode& node) const; - void serialize_node(nlohmann::json& j, const DisconnectedMidiOutNode& node) const; void serialize_node(nlohmann::json& j, const LogNode& node) const; void serialize_node(nlohmann::json& j, const MidiChannelNode& node) const; void serialize_node(nlohmann::json& j, const MidiInNode& node) const; diff --git a/src/PortNameDisplay.cpp b/src/PortNameDisplay.cpp index efddf4a..697a8dd 100644 --- a/src/PortNameDisplay.cpp +++ b/src/PortNameDisplay.cpp @@ -4,46 +4,25 @@ namespace mc { -namespace -{ - -std::string abbreviate_port_name(const std::string& port_name) -{ - const std::regex re(R"(^(.+):(\1.*) \d+:\d+$)"); - std::smatch results; - if (std::regex_match(port_name, results, re)) - { - return results[2].str(); - } - else - { - return port_name; - } -} - -} // namespace PortNameDisplay::PortNameDisplay(const bool initial_show_full_port_names) : m_show_full_port_names(initial_show_full_port_names) { } -std::string PortNameDisplay::get_port_name(const midi::InputInfo& input_info) const +std::string PortNameDisplay::get_port_name(std::string_view full_name) const { - if (m_show_full_port_names) + const std::regex re(R"(^(.+):(\1.*) \d+:\d+$)"); + std::smatch results; + std::string full_name_copy(full_name); + if (!m_show_full_port_names && std::regex_match(full_name_copy, results, re)) { - return input_info.m_name; + return results[2].str(); } - return abbreviate_port_name(input_info.m_name); -} - -std::string PortNameDisplay::get_port_name(const midi::OutputInfo& output_info) const -{ - if (m_show_full_port_names) + else { - return output_info.m_name; + return full_name_copy; } - return abbreviate_port_name(output_info.m_name); } } // namespace mc diff --git a/src/PortNameDisplay.hpp b/src/PortNameDisplay.hpp index ca3a0fb..2eec209 100644 --- a/src/PortNameDisplay.hpp +++ b/src/PortNameDisplay.hpp @@ -1,7 +1,6 @@ #pragma once #include - -#include "midi/MidiInfo.hpp" +#include namespace mc { @@ -14,8 +13,7 @@ class PortNameDisplay final const bool& get_show_full_port_names() const { return m_show_full_port_names; } void set_show_full_port_names(const bool value) { m_show_full_port_names = value; } - std::string get_port_name(const midi::InputInfo& input_info) const; - std::string get_port_name(const midi::OutputInfo& output_info) const; + std::string get_port_name(std::string_view full_name) const; private: bool m_show_full_port_names; diff --git a/src/midi/MessageTypeMask.hpp b/src/midi/MessageTypeMask.hpp index 869aee3..f04bcf5 100644 --- a/src/midi/MessageTypeMask.hpp +++ b/src/midi/MessageTypeMask.hpp @@ -8,6 +8,8 @@ struct MessageTypeMask bool m_sysex_enabled{true}; bool m_time_enabled{true}; bool m_sensing_enabled{true}; + + constexpr bool operator==(const MessageTypeMask&) const = default; }; diff --git a/src/midi/MidiProbe.cpp b/src/midi/MidiProbe.cpp index f0558c1..e6235c9 100644 --- a/src/midi/MidiProbe.cpp +++ b/src/midi/MidiProbe.cpp @@ -36,7 +36,7 @@ std::vector get_connections() } template -std::optional get_valid_info(const std::string& name, const std::vector& infos) +std::optional get_valid_info(std::string_view name, const std::vector& infos) { auto found_it = std::find_if(infos.begin(), infos.end(), [&](const auto& info) { return info.m_name == name; @@ -80,12 +80,12 @@ std::vector MidiProbe::get_outputs() return get_connections(); } -std::optional MidiProbe::get_valid_input(const std::string& input_name) +std::optional MidiProbe::get_valid_input(std::string_view input_name) { return get_valid_info(input_name, get_inputs()); } -std::optional MidiProbe::get_valid_output(const std::string& output_name) +std::optional MidiProbe::get_valid_output(std::string_view output_name) { return get_valid_info(output_name, get_outputs()); } diff --git a/src/midi/MidiProbe.hpp b/src/midi/MidiProbe.hpp index 23d0c05..2d8a567 100644 --- a/src/midi/MidiProbe.hpp +++ b/src/midi/MidiProbe.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include +#include #include #include "MidiInfo.hpp" @@ -18,8 +18,8 @@ class MidiProbe final static std::vector get_inputs(); static std::vector get_outputs(); - static std::optional get_valid_input(const std::string& input_name); - static std::optional get_valid_output(const std::string& output_name); + static std::optional get_valid_input(std::string_view input_name); + static std::optional get_valid_output(std::string_view output_name); static std::optional get_valid_input_port_name(unsigned id); static std::optional get_valid_output_port_name(unsigned id); From 1d702c214b54ec98583d45913504c3cd528f6896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc=20Serf=C5=91z=C5=91?= Date: Fri, 17 May 2024 20:27:34 +0200 Subject: [PATCH 06/12] chore(ci): Using latest image versions --- .github/workflows/ci.yml | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81261ea..922efa5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,9 +4,12 @@ on: [push, pull_request] jobs: build-linux: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 + env: + CC: gcc-13 + CXX: g++-13 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true lfs: true @@ -26,22 +29,23 @@ jobs: -D MC_COMMIT_SHA=${{github.sha}} -D MC_BUILD_NUMBER=${{github.run_number}} -D CPACK_GENERATOR=DEB - -D CPACK_SYSTEM_NAME=ubuntu-22.04 + -D CPACK_SYSTEM_NAME=$(lsb_release --codename --short) - name: Build run: cmake --build ${{github.workspace}}/build --target midiconn -j `nproc` - name: Create DEB package run: cd ${{github.workspace}}/build && cpack -C Release - name: Upload Artifacts - uses: actions/upload-artifact@v3.1.2 + uses: actions/upload-artifact@v4 with: + name: linux path: | ${{github.workspace}}/build/midiconn ${{github.workspace}}/build/*.deb build-flatpak: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true lfs: true @@ -59,17 +63,17 @@ jobs: run: flatpak-builder build/flatpak ${{github.workspace}}/data/packaging/xyz.safeworlds.midiconn.yml build-windows: - runs-on: windows-2022 + runs-on: windows-latest env: vcpkg-prereqs: nlohmann-json:x64-windows spdlog:x64-windows sdl2:x64-windows freetype[core]:x64-windows zlib:x64-windows steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true lfs: true - name: Restore vcpkg prerequisites cache id: restore-cache-vcpkg - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: C:\vcpkg key: ${{env.vcpkg-prereqs}} @@ -78,7 +82,7 @@ jobs: run: vcpkg install ${{env.vcpkg-prereqs}} - name: Save vcpkg prerequisites cache if: ${{ steps.restore-cache-vcpkg.outputs.cache-hit != 'true' }} - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: C:\vcpkg key: ${{env.vcpkg-prereqs}} @@ -97,8 +101,9 @@ jobs: - name: Build installer run: cd ${{github.workspace}}\build; cpack -C Release - name: Upload Artifacts - uses: actions/upload-artifact@v3.1.2 + uses: actions/upload-artifact@v4 with: + name: windows path: | ${{github.workspace}}\build\Release\*.exe ${{github.workspace}}\build\Release\*.dll @@ -106,15 +111,15 @@ jobs: release: if: startsWith(github.ref, 'refs/tags/v') - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest needs: - build-linux - build-windows steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 - name: Create GitHub release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: | ${{github.workspace}}/artifact/*.deb From 32c786d6a249ab1f2bd48ee06d684d26bfef98c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc=20Serf=C5=91z=C5=91?= Date: Sat, 25 May 2024 15:19:56 +0200 Subject: [PATCH 07/12] feat(ui): Setting appropriate size for the Log Node on any scale --- src/LogNode.cpp | 20 +++++++++++++------- src/LogNode.hpp | 14 +++++++------- src/MidiChannelNode.cpp | 15 +++++++++------ src/MidiChannelNode.hpp | 7 ++++--- src/NodeFactory.cpp | 6 ++---- src/ScaleProvider.hpp | 33 +++++++++++++++++++++++++++++++++ src/Theme.hpp | 23 +++++------------------ 7 files changed, 73 insertions(+), 45 deletions(-) create mode 100644 src/ScaleProvider.hpp diff --git a/src/LogNode.cpp b/src/LogNode.cpp index 157c45e..0a05585 100644 --- a/src/LogNode.cpp +++ b/src/LogNode.cpp @@ -15,7 +15,7 @@ std::string_view mc::LogNode::LogMidiNode::name() return "Log Node"; } -mc::LogNode::LogNode() +mc::LogNode::LogNode(const ScaleProvider& scale_provider) : m_scale_provider(&scale_provider) { m_log_midi_node.add_observer(this); } @@ -47,7 +47,7 @@ void mc::LogNode::render_internal() ImNodes::EndInputAttribute(); int new_buffer_size = static_cast(m_max_buffer_size); - ImGui::SetNextItemWidth(150.0F); // ToDo scale + ImGui::SetNextItemWidth(100.0F * m_scale_provider->get_scale_value()); if (ImGui::InputInt("Buffer Size", &new_buffer_size)) { if (new_buffer_size > 0 && new_buffer_size < 100000) @@ -63,13 +63,19 @@ void mc::LogNode::render_internal() } lock.unlock(); - if (ImGui::BeginTable( - "Log", 5, ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable, ImVec2{800.F, 300.F})) - { // ToDo scale + if (ImGui::BeginTable("Log", + 5, + ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable, + ImVec2{530.F * m_scale_provider->get_scale_value(), + 200.F * m_scale_provider->get_scale_value()})) + { - ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_WidthFixed, 140.F); + ImGui::TableSetupColumn( + "Time", ImGuiTableColumnFlags_WidthFixed, 100.F * m_scale_provider->get_scale_value()); ImGui::TableSetupColumn("Type"); - ImGui::TableSetupColumn("Channel", ImGuiTableColumnFlags_WidthFixed, 70.F); + ImGui::TableSetupColumn("Channel", + ImGuiTableColumnFlags_WidthFixed, + 50.F * m_scale_provider->get_scale_value()); ImGui::TableSetupColumn("Data #0"); ImGui::TableSetupColumn("Data #1"); ImGui::TableHeadersRow(); diff --git a/src/LogNode.hpp b/src/LogNode.hpp index e382e58..2d77c9e 100644 --- a/src/LogNode.hpp +++ b/src/LogNode.hpp @@ -2,6 +2,7 @@ #include "ActivityIndicator.hpp" #include "Node.hpp" +#include "ScaleProvider.hpp" #include "midi/MidiGraph.hpp" #include @@ -25,7 +26,7 @@ class LogNode final : public Node, private midi::GraphObserver }; public: - LogNode(); + explicit LogNode(const ScaleProvider& scale_provider); ~LogNode(); void accept_serializer(nlohmann::json& j, const NodeSerializer& serializer) const override; @@ -35,14 +36,9 @@ class LogNode final : public Node, private midi::GraphObserver void render_internal() override; private: - void message_received(std::span message_bytes) override; - - LogMidiNode m_log_midi_node; - ActivityIndicator m_input_indicator; - static inline constexpr std::size_t default_max_buffer_size = 500; - std::size_t m_max_buffer_size = default_max_buffer_size; + void message_received(std::span message_bytes) override; struct BufferElement { @@ -65,6 +61,10 @@ class LogNode final : public Node, private midi::GraphObserver std::mutex m_buffer_mutex; std::deque m_message_buffer; + LogMidiNode m_log_midi_node; + ActivityIndicator m_input_indicator; + std::size_t m_max_buffer_size = default_max_buffer_size; + const ScaleProvider* m_scale_provider; friend class NodeSerializer; }; diff --git a/src/MidiChannelNode.cpp b/src/MidiChannelNode.cpp index 70fe53d..d2cf713 100644 --- a/src/MidiChannelNode.cpp +++ b/src/MidiChannelNode.cpp @@ -12,7 +12,8 @@ const char* mc::MidiChannelNode::sm_combo_items[] = { "None", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"}; -mc::MidiChannelNode::MidiChannelNode(std::function get_scale) : m_get_scale(get_scale) +mc::MidiChannelNode::MidiChannelNode(const ScaleProvider& scale_provider) + : m_scale_provider(&scale_provider) { m_midi_channel_map_node.add_observer(this); } @@ -43,7 +44,7 @@ void mc::MidiChannelNode::render_internal() ImGui::SameLine(); ImGui::TextUnformatted("MIDI in"); ImNodes::EndInputAttribute(); - ImGui::SameLine(100 * m_get_scale()); + ImGui::SameLine(100 * m_scale_provider->get_scale_value()); ImNodes::BeginOutputAttribute(out_id()); ImGui::TextUnformatted("MIDI out"); ImGui::SameLine(); @@ -68,8 +69,10 @@ void mc::MidiChannelNode::render_internal() } auto updated_channels = channels; - if (ImGui::BeginTable( - "MIDI Channel table", 4, ImGuiTableFlags_SizingStretchProp, {160 * m_get_scale(), 0})) + if (ImGui::BeginTable("MIDI Channel table", + 4, + ImGuiTableFlags_SizingStretchProp, + {160 * m_scale_provider->get_scale_value(), 0})) { for (size_t i = 0; i < 8; i++) { @@ -77,7 +80,7 @@ void mc::MidiChannelNode::render_internal() ImGui::TableNextColumn(); ImGui::TextUnformatted(get_label(i * 2)); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(50 * m_get_scale()); + ImGui::SetNextItemWidth(50 * m_scale_provider->get_scale_value()); ImGui::Combo(get_hidden_label(i * 2), updated_channels.data() + i * 2, sm_combo_items, @@ -86,7 +89,7 @@ void mc::MidiChannelNode::render_internal() ImGui::TableNextColumn(); ImGui::TextUnformatted(get_label(i * 2 + 1)); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(50 * m_get_scale()); + ImGui::SetNextItemWidth(50 * m_scale_provider->get_scale_value()); ImGui::Combo(get_hidden_label(i * 2 + 1), updated_channels.data() + i * 2 + 1, sm_combo_items, diff --git a/src/MidiChannelNode.hpp b/src/MidiChannelNode.hpp index c3c31cf..eff6864 100644 --- a/src/MidiChannelNode.hpp +++ b/src/MidiChannelNode.hpp @@ -4,6 +4,7 @@ #include "ActivityIndicator.hpp" #include "Node.hpp" +#include "ScaleProvider.hpp" #include "midi/ChannelMapNode.hpp" namespace mc @@ -12,7 +13,7 @@ namespace mc class MidiChannelNode final : public Node, private midi::GraphObserver { public: - explicit MidiChannelNode(std::function get_scale); + explicit MidiChannelNode(const ScaleProvider& scale_provider); ~MidiChannelNode(); void accept_serializer(nlohmann::json& j, const NodeSerializer& serializer) const override; @@ -31,8 +32,8 @@ class MidiChannelNode final : public Node, private midi::GraphObserver static inline constexpr size_t sm_num_combo_items = 17; static const char* sm_combo_items[sm_num_combo_items]; - std::function m_get_scale; - midi::ChannelMapNode m_midi_channel_map_node; + const ScaleProvider* m_scale_provider; + midi::ChannelMapNode m_midi_channel_map_node; ActivityIndicator m_input_indicator; ActivityIndicator m_output_indicator; diff --git a/src/NodeFactory.cpp b/src/NodeFactory.cpp index 893c3df..887e0fb 100644 --- a/src/NodeFactory.cpp +++ b/src/NodeFactory.cpp @@ -105,14 +105,12 @@ std::shared_ptr NodeFactory::build_midi_out_node(std::string_view o std::shared_ptr NodeFactory::build_midi_channel_node() { - return std::make_shared([this]() { - return m_theme_control->get_scale_value(); - }); + return std::make_shared(*m_theme_control); } std::shared_ptr NodeFactory::build_log_node() { - return std::make_shared(); + return std::make_shared(*m_theme_control); } bool NodeFactory::is_node_instantiated(const midi::InputInfo& input_info) diff --git a/src/ScaleProvider.hpp b/src/ScaleProvider.hpp new file mode 100644 index 0000000..f1aa6c0 --- /dev/null +++ b/src/ScaleProvider.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +namespace mc +{ + +enum class InterfaceScale +{ + Scale_1_00, + Scale_1_25, + Scale_1_50, + Scale_1_75, + Scale_2_00, + Auto, + Size, + Undefined +}; + +constexpr inline std::array(InterfaceScale::Size)> + interface_scale_labels{"1.0", "1.25", "1.5", "1.75", "2.0", "Auto"}; + +class ScaleProvider +{ +public: + virtual ~ScaleProvider() = default; + + virtual InterfaceScale get_scale() const = 0; + virtual float get_scale_value() const = 0; +}; + +} // namespace mc diff --git a/src/Theme.hpp b/src/Theme.hpp index d0381fb..65e892d 100644 --- a/src/Theme.hpp +++ b/src/Theme.hpp @@ -4,6 +4,8 @@ #include #include +#include "ScaleProvider.hpp" + #include "imnodes.h" struct SDL_Window; @@ -21,22 +23,7 @@ enum class Theme Default = Dark }; -enum class InterfaceScale -{ - Scale_1_00, - Scale_1_25, - Scale_1_50, - Scale_1_75, - Scale_2_00, - Auto, - Size, - Undefined -}; - -constexpr inline std::array(InterfaceScale::Size)> - interface_scale_labels{"1.0", "1.25", "1.5", "1.75", "2.0", "Auto"}; - -class ThemeControl final +class ThemeControl final : public ScaleProvider { public: ThemeControl(ConfigFile& config, SDL_Window* window); @@ -44,8 +31,8 @@ class ThemeControl final void set_theme(const Theme theme); Theme get_theme() const { return m_current_theme; } void set_scale(const InterfaceScale scale); - InterfaceScale get_scale() const { return m_scale; } - float get_scale_value() const { return calculate_scale_value(m_scale); }; + InterfaceScale get_scale() const override { return m_scale; } + float get_scale_value() const override { return calculate_scale_value(m_scale); } // Call this outside of ImGui's NewFrame() and Render() void update_scale_if_needed(); From ce2512323a78e09459d7833c8a0f93ee6664924e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc=20Serf=C5=91z=C5=91?= Date: Mon, 27 May 2024 22:01:28 +0200 Subject: [PATCH 08/12] chore(ci): Restored Ubuntu 22.04 build --- .github/workflows/ci.yml | 15 +++++++++----- src/LogNode.cpp | 45 ++++++++++++++++++++-------------------- src/LogNode.hpp | 1 + src/midi/MessageView.hpp | 24 ++++++++++----------- src/midi/Note.hpp | 7 ++++--- 5 files changed, 50 insertions(+), 42 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 922efa5..7028087 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,10 +4,14 @@ on: [push, pull_request] jobs: build-linux: - runs-on: ubuntu-24.04 + strategy: + matrix: + RUNNER: + - ubuntu-22.04 + - ubuntu-24.04 + runs-on: ${{matrix.RUNNER}} env: - CC: gcc-13 - CXX: g++-13 + CXXFLAGS: -Wall -Wextra -Wno-deprecated-declarations -Werror -Wno-stringop-overflow steps: - uses: actions/checkout@v4 with: @@ -23,15 +27,16 @@ jobs: nlohmann-json3-dev - name: Configure CMake run: cmake + -G Ninja + -S ${{github.workspace}} -B ${{github.workspace}}/build - -D CMAKE_CXX_FLAGS="-Wall -Wextra" -D CMAKE_BUILD_TYPE=Release -D MC_COMMIT_SHA=${{github.sha}} -D MC_BUILD_NUMBER=${{github.run_number}} -D CPACK_GENERATOR=DEB -D CPACK_SYSTEM_NAME=$(lsb_release --codename --short) - name: Build - run: cmake --build ${{github.workspace}}/build --target midiconn -j `nproc` + run: cmake --build ${{github.workspace}}/build --target midiconn - name: Create DEB package run: cd ${{github.workspace}}/build && cpack -C Release - name: Upload Artifacts diff --git a/src/LogNode.cpp b/src/LogNode.cpp index 0a05585..59f21f1 100644 --- a/src/LogNode.cpp +++ b/src/LogNode.cpp @@ -8,7 +8,7 @@ #include "imgui.h" #include "imnodes.h" -#include +#include "fmt/format.h" std::string_view mc::LogNode::LogMidiNode::name() { @@ -86,15 +86,16 @@ void mc::LogNode::render_internal() ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - const auto local = std::chrono::zoned_time(std::chrono::current_zone(), item.m_arrived) - .get_local_time(); - const auto seconds = - std::chrono::floor(local.time_since_epoch()).count() % 60; - const auto milliseconds = - std::chrono::floor(local.time_since_epoch()).count() % - 1000; - ImGui::TextUnformatted( - std::format("{:%H:%M}:{:02}.{:03}", local, seconds, milliseconds).c_str()); + const auto arrived = std::chrono::system_clock::to_time_t(item.m_arrived); + const auto* local = std::localtime(&arrived); + std::array time_str{}; + std::strftime(time_str.data(), time_str.size(), "%T", local); + ImGui::Text("%s.%03ld", + time_str.data(), + std::chrono::duration_cast( + item.m_arrived.time_since_epoch()) + .count() % + 1000); ImGui::TableNextColumn(); ImGui::TextUnformatted(item.m_name.c_str()); @@ -133,20 +134,20 @@ void mc::LogNode::message_received(std::span message_bytes) [](midi::NoteOnMessageViewTag, auto note_on) -> BufferElement { return BufferElement{"Note On"s, note_on.get_channel_human(), - std::format("{}", midi::Note(note_on.get_note())), - std::format("Velocity: {}", note_on.get_velocity())}; + fmt::format("{}", midi::Note(note_on.get_note())), + fmt::format("Velocity: {}", note_on.get_velocity())}; }, [](midi::NoteOffMessageViewTag, auto note_off) -> BufferElement { return BufferElement{"Note Off"s, note_off.get_channel_human(), - std::format("{}", midi::Note(note_off.get_note())), - std::format("Velocity: {}", note_off.get_velocity())}; + fmt::format("{}", midi::Note(note_off.get_note())), + fmt::format("Velocity: {}", note_off.get_velocity())}; }, [](midi::PolyKeyPressureMessageViewTag, auto poly_key_pressure) -> BufferElement { return BufferElement{"Poly Aftertouch"s, poly_key_pressure.get_channel_human(), - std::format("{}", midi::Note(poly_key_pressure.get_note())), - std::format("Pressure: {}", poly_key_pressure.get_pressure())}; + fmt::format("{}", midi::Note(poly_key_pressure.get_note())), + fmt::format("Pressure: {}", poly_key_pressure.get_pressure())}; }, [](midi::AllSoundOffMessageViewTag, auto all_sound_off) -> BufferElement { return BufferElement{"All Sound Off"s, all_sound_off.get_channel_human(), ""s, ""s}; @@ -173,7 +174,7 @@ void mc::LogNode::message_received(std::span message_bytes) [](midi::MonoModeOnMessageViewTag, auto mono_mode) -> BufferElement { return BufferElement{"Mono Mode On"s, mono_mode.get_channel_human(), - std::format("Channels: {}", mono_mode.get_num_channels()), + fmt::format("Channels: {}", mono_mode.get_num_channels()), ""s}; }, [](midi::PolyModeOnMessageViewTag, auto poly_mode) -> BufferElement { @@ -182,27 +183,27 @@ void mc::LogNode::message_received(std::span message_bytes) [](midi::ControlChangeMessageViewTag, auto control_change) -> BufferElement { return BufferElement{"Control Change"s, control_change.get_channel_human(), - std::format("CC {} ({})", + fmt::format("CC {} ({})", control_change.get_controller(), control_change.get_function_name()), - std::format("Value: {}", control_change.get_value())}; + fmt::format("Value: {}", control_change.get_value())}; }, [](midi::ProgramChangeMessageViewTag, auto program_change) -> BufferElement { return BufferElement{"Program Change"s, program_change.get_channel_human(), - std::format("Program: {}", program_change.get_program_number()), + fmt::format("Program: {}", program_change.get_program_number()), ""s}; }, [](midi::ChannelPressureMessageViewTag, auto channel_pressure) -> BufferElement { return BufferElement{"Channel Aftertouch"s, channel_pressure.get_channel_human(), - std::format("Pressure: {}", channel_pressure.get_pressure()), + fmt::format("Pressure: {}", channel_pressure.get_pressure()), ""s}; }, [](midi::PitchBendMessageViewTag, auto pitch_bend) -> BufferElement { return BufferElement{"Pitch bend"s, pitch_bend.get_channel_human(), - std::format("Value: {}", pitch_bend.get_value_human()), + fmt::format("Value: {}", pitch_bend.get_value_human()), ""s}; }, [](auto, auto) -> BufferElement { diff --git a/src/LogNode.hpp b/src/LogNode.hpp index 2d77c9e..983fd94 100644 --- a/src/LogNode.hpp +++ b/src/LogNode.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include diff --git a/src/midi/MessageView.hpp b/src/midi/MessageView.hpp index acb7f2b..13e2cc7 100644 --- a/src/midi/MessageView.hpp +++ b/src/midi/MessageView.hpp @@ -130,7 +130,7 @@ class ChannelMessageView : public MessageView { public: using tag_t = ChannelMessageViewTag; - explicit ChannelMessageView(MessageView::span_t message_data) + explicit ChannelMessageView(typename MessageView::span_t message_data) : MessageView(message_data) { } @@ -216,7 +216,7 @@ class NoteMessageView : public ChannelMessageView public: using tag_t = NoteMessageViewTag; - explicit NoteMessageView(MessageView::span_t message_data) + explicit NoteMessageView(typename MessageView::span_t message_data) : ChannelMessageView(message_data) { assert(message_data.size() == 3); @@ -250,7 +250,7 @@ class NoteOffMessageView : public NoteMessageView { public: using tag_t = NoteOffMessageViewTag; - explicit NoteOffMessageView(MessageView::span_t message_data) + explicit NoteOffMessageView(typename MessageView::span_t message_data) : NoteMessageView(message_data) { } @@ -265,7 +265,7 @@ class NoteOnMessageView : public NoteMessageView { public: using tag_t = NoteOnMessageViewTag; - explicit NoteOnMessageView(MessageView::span_t message_data) + explicit NoteOnMessageView(typename MessageView::span_t message_data) : NoteMessageView(message_data) { } @@ -280,7 +280,7 @@ class PolyKeyPressureMessageView : public ChannelMessageView { public: using tag_t = PolyKeyPressureMessageViewTag; - explicit PolyKeyPressureMessageView(MessageView::span_t message_data) + explicit PolyKeyPressureMessageView(typename MessageView::span_t message_data) : ChannelMessageView(message_data) { assert(message_data.size() == 3); @@ -314,7 +314,7 @@ class ControlChangeMessageView : public ChannelMessageView { public: using tag_t = ControlChangeMessageViewTag; - explicit ControlChangeMessageView(MessageView::span_t message_data) + explicit ControlChangeMessageView(typename MessageView::span_t message_data) : ChannelMessageView(message_data) { } @@ -487,7 +487,7 @@ class ChannelModeMessageView : public ChannelMessageView { public: using tag_t = ChannelModeMessageViewTag; - explicit ChannelModeMessageView(MessageView::span_t message_data) + explicit ChannelModeMessageView(typename MessageView::span_t message_data) : ChannelMessageView(message_data) { } @@ -567,7 +567,7 @@ class LocalControlMessageView : public ChannelModeMessageView public: using tag_t = LocalControlMessageViewTag; - explicit LocalControlMessageView(MessageView::span_t message_data) + explicit LocalControlMessageView(typename MessageView::span_t message_data) : ChannelModeMessageView(message_data) { } @@ -636,7 +636,7 @@ class MonoModeOnMessageView : public ChannelModeMessageView public: using tag_t = MonoModeOnMessageViewTag; - explicit MonoModeOnMessageView(MessageView::span_t message_data) + explicit MonoModeOnMessageView(typename MessageView::span_t message_data) : ChannelModeMessageView(message_data) { } @@ -674,7 +674,7 @@ class ProgramChangeMessageView : public ChannelMessageView { public: using tag_t = ProgramChangeMessageViewTag; - explicit ProgramChangeMessageView(MessageView::span_t message_data) + explicit ProgramChangeMessageView(typename MessageView::span_t message_data) : ChannelMessageView(message_data) { } @@ -698,7 +698,7 @@ class ChannelPressureMessageView : public ChannelMessageView { public: using tag_t = ChannelPressureMessageViewTag; - explicit ChannelPressureMessageView(MessageView::span_t message_data) + explicit ChannelPressureMessageView(typename MessageView::span_t message_data) : ChannelMessageView(message_data) { assert(message_data.size() == 2); @@ -723,7 +723,7 @@ class PitchBendMessageView : public ChannelMessageView { public: using tag_t = PitchBendMessageViewTag; - explicit PitchBendMessageView(MessageView::span_t message_data) + explicit PitchBendMessageView(typename MessageView::span_t message_data) : ChannelMessageView(message_data) { assert(message_data.size() == 3); diff --git a/src/midi/Note.hpp b/src/midi/Note.hpp index 653618e..03d7fa3 100644 --- a/src/midi/Note.hpp +++ b/src/midi/Note.hpp @@ -1,6 +1,7 @@ #pragma once -#include +#include "fmt/format.h" + #include #include @@ -15,13 +16,13 @@ class Note final private: unsigned char m_note; - friend struct std::formatter; + friend struct fmt::formatter; }; } // namespace mc::midi template <> -struct std::formatter +struct fmt::formatter { template constexpr ParseContext::iterator parse(ParseContext& ctx) From 9b10d3f7495b34e32a858de9a96fda0c4112f690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc=20Serf=C5=91z=C5=91?= Date: Mon, 27 May 2024 22:05:21 +0200 Subject: [PATCH 09/12] chore(ci): Using flatpak-github-actions --- .github/workflows/ci.yml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7028087..6ed9f54 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,6 @@ jobs: nlohmann-json3-dev - name: Configure CMake run: cmake - -G Ninja -S ${{github.workspace}} -B ${{github.workspace}}/build -D CMAKE_BUILD_TYPE=Release @@ -36,7 +35,7 @@ jobs: -D CPACK_GENERATOR=DEB -D CPACK_SYSTEM_NAME=$(lsb_release --codename --short) - name: Build - run: cmake --build ${{github.workspace}}/build --target midiconn + run: cmake --build ${{github.workspace}}/build --target midiconn -j $(nproc) - name: Create DEB package run: cd ${{github.workspace}}/build && cpack -C Release - name: Upload Artifacts @@ -49,23 +48,22 @@ jobs: build-flatpak: runs-on: ubuntu-latest + container: + image: bilelmoussaoui/flatpak-github-actions:freedesktop-23.08 + options: --privileged steps: - uses: actions/checkout@v4 with: submodules: true lfs: true - - name: Install prerequisites - run: sudo apt-get update -y && sudo apt-get install -y - appstream-util - flatpak-builder - name: Validate metainfo run: appstream-util validate - ${{github.workspace}}/data/packaging/xyz.safeworlds.midiconn.metainfo.xml - - name: Install flatpak prerequisites - run: sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo && - sudo flatpak install -y flathub org.freedesktop.Sdk//23.08 org.freedesktop.Platform//23.08 + data/packaging/xyz.safeworlds.midiconn.metainfo.xml - name: Build flatpak - run: flatpak-builder build/flatpak ${{github.workspace}}/data/packaging/xyz.safeworlds.midiconn.yml + uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + manifest-path: ${{github.workspace}}/data/packaging/xyz.safeworlds.midiconn.yml + cache-key: flatpak-builder-${{ github.sha }} build-windows: runs-on: windows-latest From 397f30ab5f2a744eb7f370d83b3b7b1c0c8b485f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc=20Serf=C5=91z=C5=91?= Date: Tue, 28 May 2024 20:23:39 +0200 Subject: [PATCH 10/12] fix(build): Removed unused m_update_check_finished --- .github/workflows/ci.yml | 6 ++++-- src/UpdateChecker.cpp | 1 - src/UpdateChecker.hpp | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ed9f54..aebc739 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,8 @@ jobs: env: CXXFLAGS: -Wall -Wextra -Wno-deprecated-declarations -Werror -Wno-stringop-overflow steps: + - name: Setup system name + run: echo "SYSTEM_NAME=$(lsb_release --codename --short)" >> $GITHUB_ENV - uses: actions/checkout@v4 with: submodules: true @@ -33,7 +35,7 @@ jobs: -D MC_COMMIT_SHA=${{github.sha}} -D MC_BUILD_NUMBER=${{github.run_number}} -D CPACK_GENERATOR=DEB - -D CPACK_SYSTEM_NAME=$(lsb_release --codename --short) + -D CPACK_SYSTEM_NAME=$SYSTEM_NAME - name: Build run: cmake --build ${{github.workspace}}/build --target midiconn -j $(nproc) - name: Create DEB package @@ -41,7 +43,7 @@ jobs: - name: Upload Artifacts uses: actions/upload-artifact@v4 with: - name: linux + name: linux-${{env.SYSTEM_NAME}} path: | ${{github.workspace}}/build/midiconn ${{github.workspace}}/build/*.deb diff --git a/src/UpdateChecker.cpp b/src/UpdateChecker.cpp index 2df7f35..81cd745 100644 --- a/src/UpdateChecker.cpp +++ b/src/UpdateChecker.cpp @@ -19,7 +19,6 @@ UpdateChecker::UpdateChecker() void UpdateChecker::trigger_check() { - m_update_check_finished = false; m_latest_version_written = false; std::thread([this] { using namespace std::chrono_literals; diff --git a/src/UpdateChecker.hpp b/src/UpdateChecker.hpp index 3555ca4..33d95a5 100644 --- a/src/UpdateChecker.hpp +++ b/src/UpdateChecker.hpp @@ -34,7 +34,6 @@ struct UpdateChecker CheckResult get_latest_version() const; private: - [[maybe_unused]] bool m_update_check_finished = false; CheckResult m_latest_version_result = {}; std::atomic_bool m_latest_version_written; }; From 87f1be1d7640ba61c383c78e68a6bcabf0c30e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc=20Serf=C5=91z=C5=91?= Date: Thu, 13 Jun 2024 11:32:38 +0200 Subject: [PATCH 11/12] fix(midi): Fix setting message type mask --- src/MidiInNode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MidiInNode.cpp b/src/MidiInNode.cpp index 8e7dd43..8d38a8b 100644 --- a/src/MidiInNode.cpp +++ b/src/MidiInNode.cpp @@ -74,7 +74,7 @@ void MidiInNode::render_internal() ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4{}); if (ImGui::TreeNode("Advanced")) { - midi::MessageTypeMask new_message_type_mask; + midi::MessageTypeMask new_message_type_mask = m_message_type_mask; ImGui::Checkbox("Receive SysEx", &new_message_type_mask.m_sysex_enabled); ImGui::Checkbox("Receive MIDI Clock", &new_message_type_mask.m_time_enabled); ImGui::Checkbox("Receive Active Sensing", &new_message_type_mask.m_sensing_enabled); From e0e1dc5363fd516d8aa5372a6880768e3ea85829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc=20Serf=C5=91z=C5=91?= Date: Thu, 13 Jun 2024 12:18:50 +0200 Subject: [PATCH 12/12] feat(midi): Recognize system messages in Log Node --- src/LogNode.cpp | 115 +++++++++--- src/LogNode.hpp | 24 ++- src/midi/MessageView.hpp | 391 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 503 insertions(+), 27 deletions(-) diff --git a/src/LogNode.cpp b/src/LogNode.cpp index 59f21f1..1e97bc6 100644 --- a/src/LogNode.cpp +++ b/src/LogNode.cpp @@ -46,6 +46,12 @@ void mc::LogNode::render_internal() ImGui::TextUnformatted("MIDI in"); ImNodes::EndInputAttribute(); + bool clear = false; + if (ImGui::Button("Clear")) + { + clear = true; + } + ImGui::SameLine(); int new_buffer_size = static_cast(m_max_buffer_size); ImGui::SetNextItemWidth(100.0F * m_scale_provider->get_scale_value()); if (ImGui::InputInt("Buffer Size", &new_buffer_size)) @@ -57,6 +63,10 @@ void mc::LogNode::render_internal() } std::unique_lock lock(m_buffer_mutex); + if (clear) + { + m_message_buffer.clear(); + } while (!m_message_buffer.empty() && m_message_buffer.size() > m_max_buffer_size) { m_message_buffer.pop_back(); @@ -130,6 +140,29 @@ void mc::LogNode::message_received(std::span message_bytes) m_input_indicator.trigger(); midi::MessageView message(message_bytes); + midi::tag_overloads message_visitor{ + [](midi::ChannelMessageViewTag, auto channel_message) -> BufferElement { + return parse_channel_message(channel_message); + }, + [](midi::SystemMessageViewTag, auto system_message) -> BufferElement { + return parse_system_message(system_message); + }, + []( + Tag, auto) -> std::enable_if_t && + !std::is_base_of_v, + BufferElement> { + return BufferElement{"Unknown"s}; + }}; + const BufferElement new_element = std::visit(message_visitor, message.parse()); + + std::lock_guard guard(m_buffer_mutex); + m_message_buffer.push_front(std::move(new_element)); +} + +mc::LogNode::BufferElement mc::LogNode::parse_channel_message( + midi::ChannelMessageView message_view) +{ + using namespace std::string_literals; midi::tag_overloads message_visitor{ [](midi::NoteOnMessageViewTag, auto note_on) -> BufferElement { return BufferElement{"Note On"s, @@ -150,35 +183,33 @@ void mc::LogNode::message_received(std::span message_bytes) fmt::format("Pressure: {}", poly_key_pressure.get_pressure())}; }, [](midi::AllSoundOffMessageViewTag, auto all_sound_off) -> BufferElement { - return BufferElement{"All Sound Off"s, all_sound_off.get_channel_human(), ""s, ""s}; + return BufferElement{"All Sound Off"s, all_sound_off.get_channel_human()}; }, [](midi::ResetAllControllersMessageViewTag, auto reset_all_controllers) -> BufferElement { - return BufferElement{ - "Reset All Controllers"s, reset_all_controllers.get_channel_human(), ""s, ""s}; + return BufferElement{"Reset All Controllers"s, + reset_all_controllers.get_channel_human()}; }, [](midi::LocalControlMessageViewTag, auto local_control) -> BufferElement { return BufferElement{"Local Control"s, local_control.get_channel_human(), - local_control.get_value() ? "On"s : "Off"s, - ""s}; + local_control.get_value() ? "On"s : "Off"s}; }, [](midi::AllNotesOffMessageViewTag, auto all_notes_off) -> BufferElement { - return BufferElement{"All Notes Off"s, all_notes_off.get_channel_human(), ""s, ""s}; + return BufferElement{"All Notes Off"s, all_notes_off.get_channel_human()}; }, [](midi::OmniModeOffMessageViewTag, auto omni_off) -> BufferElement { - return BufferElement{"Omni Mode Off"s, omni_off.get_channel_human(), ""s, ""s}; + return BufferElement{"Omni Mode Off"s, omni_off.get_channel_human()}; }, [](midi::OmniModeOnMessageViewTag, auto omni_on) -> BufferElement { - return BufferElement{"Omni Mode On"s, omni_on.get_channel_human(), ""s, ""s}; + return BufferElement{"Omni Mode On"s, omni_on.get_channel_human()}; }, [](midi::MonoModeOnMessageViewTag, auto mono_mode) -> BufferElement { return BufferElement{"Mono Mode On"s, mono_mode.get_channel_human(), - fmt::format("Channels: {}", mono_mode.get_num_channels()), - ""s}; + fmt::format("Channels: {}", mono_mode.get_num_channels())}; }, [](midi::PolyModeOnMessageViewTag, auto poly_mode) -> BufferElement { - return BufferElement{"Poly Mode On"s, poly_mode.get_channel_human(), ""s, ""s}; + return BufferElement{"Poly Mode On"s, poly_mode.get_channel_human()}; }, [](midi::ControlChangeMessageViewTag, auto control_change) -> BufferElement { return BufferElement{"Control Change"s, @@ -191,26 +222,68 @@ void mc::LogNode::message_received(std::span message_bytes) [](midi::ProgramChangeMessageViewTag, auto program_change) -> BufferElement { return BufferElement{"Program Change"s, program_change.get_channel_human(), - fmt::format("Program: {}", program_change.get_program_number()), - ""s}; + fmt::format("Program: {}", program_change.get_program_number())}; }, [](midi::ChannelPressureMessageViewTag, auto channel_pressure) -> BufferElement { return BufferElement{"Channel Aftertouch"s, channel_pressure.get_channel_human(), - fmt::format("Pressure: {}", channel_pressure.get_pressure()), - ""s}; + fmt::format("Pressure: {}", channel_pressure.get_pressure())}; }, [](midi::PitchBendMessageViewTag, auto pitch_bend) -> BufferElement { return BufferElement{"Pitch bend"s, pitch_bend.get_channel_human(), - fmt::format("Value: {}", pitch_bend.get_value_human()), - ""s}; + fmt::format("Value: {}", pitch_bend.get_value_human())}; }, [](auto, auto) -> BufferElement { - return BufferElement{"Unknown"s, std::nullopt, ""s, ""s}; + return BufferElement{"Unknown channel message"s}; }}; - const BufferElement new_element = std::visit(message_visitor, message.parse()); + return std::visit(message_visitor, message_view.parse()); +} - std::lock_guard guard(m_buffer_mutex); - m_message_buffer.push_front(std::move(new_element)); +mc::LogNode::BufferElement mc::LogNode::parse_system_message( + midi::SystemMessageView message_view) +{ + using namespace std::string_literals; + midi::tag_overloads message_visitor{ + [](midi::SystemExclusiveMessageViewTag, auto system_exclusive) -> BufferElement { + return BufferElement{"System Exclusive"s, + fmt::format("ID: {}", system_exclusive.get_manufacturer_id()), + fmt::format("{} bytes", system_exclusive.get_length())}; + }, + [](midi::TimeCodeQuarterFrameMessageViewTag, auto timecode_quarter) -> BufferElement { + return BufferElement{"Timecode Quarter Frame"s, + fmt::format("Type: {}", timecode_quarter.get_type()), + fmt::format("Values: {}", timecode_quarter.get_values())}; + }, + [](midi::SongPositionPointerMessageViewTag, auto song_position) -> BufferElement { + return BufferElement{"Song Position"s, std::to_string(song_position.get_position())}; + }, + [](midi::SongSelectMessageViewTag, auto song_select) -> BufferElement { + return BufferElement{"Song Select"s, std::to_string(song_select.get_song())}; + }, + [](midi::TuneRequestMessageViewTag, auto) -> BufferElement { + return BufferElement{"Tune Request"s}; + }, + [](midi::TimingClockMessageViewTag, auto) -> BufferElement { + return BufferElement{"Timing Clock"s}; + }, + [](midi::StartSequenceMessageViewTag, auto) -> BufferElement { + return BufferElement{"Start Sequence"s}; + }, + [](midi::ContinueSequenceMessageViewTag, auto) -> BufferElement { + return BufferElement{"Continue Sequence"s}; + }, + [](midi::StopSequenceMessageViewTag, auto) -> BufferElement { + return BufferElement{"Stop Sequence"s}; + }, + [](midi::ActiveSensingMessageViewTag, auto) -> BufferElement { + return BufferElement{"Active Sensing"s}; + }, + [](midi::ResetMessageViewTag, auto) -> BufferElement { + return BufferElement{"Reset everything"s}; + }, + [](auto, auto) -> BufferElement { + return BufferElement{"Unknown system message"s}; + }}; + return std::visit(message_visitor, message_view.parse()); } diff --git a/src/LogNode.hpp b/src/LogNode.hpp index 983fd94..afde69a 100644 --- a/src/LogNode.hpp +++ b/src/LogNode.hpp @@ -16,6 +16,13 @@ namespace mc { +namespace midi +{ +template +class ChannelMessageView; +template +class SystemMessageView; +} // namespace midi class LogNode final : public Node, private midi::GraphObserver { @@ -43,16 +50,22 @@ class LogNode final : public Node, private midi::GraphObserver struct BufferElement { - BufferElement(std::string&& name, - std::optional&& channel, - std::string&& data_0, - std::string&& data_1) + explicit BufferElement(std::string&& name, + std::optional&& channel = {}, + std::string&& data_0 = {}, + std::string&& data_1 = {}) : m_arrived(std::chrono::system_clock::now()), m_name(std::move(name)), m_channel(std::move(channel)), m_data_0(std::move(data_0)), m_data_1(std::move(data_1)) { } + BufferElement(std::string&& name, std::string&& data_0, std::string&& data_1 = {}) + : m_arrived(std::chrono::system_clock::now()), m_name(std::move(name)), + m_data_0(std::move(data_0)), m_data_1(std::move(data_1)) + { + } + std::chrono::time_point m_arrived; std::string m_name; std::optional m_channel; @@ -60,6 +73,9 @@ class LogNode final : public Node, private midi::GraphObserver std::string m_data_1; }; + static BufferElement parse_channel_message(midi::ChannelMessageView message_view); + static BufferElement parse_system_message(midi::SystemMessageView message_view); + std::mutex m_buffer_mutex; std::deque m_message_buffer; LogMidiNode m_log_midi_node; diff --git a/src/midi/MessageView.hpp b/src/midi/MessageView.hpp index 13e2cc7..e3f68b5 100644 --- a/src/midi/MessageView.hpp +++ b/src/midi/MessageView.hpp @@ -3,6 +3,7 @@ #include "../Utils.hpp" #include +#include #include #include #include @@ -63,6 +64,26 @@ class OmniModeOnMessageView; template class MonoModeOnMessageView; class PolyModeOnMessageView; +template +class SystemMessageView; +template +class SystemCommonMessageView; +template +class SystemExclusiveMessageView; +template +class TimeCodeQuarterFrameMessageView; +template +class SongPositionPointerMessageView; +template +class SongSelectMessageView; +class TuneRequestMessageView; +class SystemRealTimeMessageView; +class TimingClockMessageView; +class StartSequenceMessageView; +class ContinueSequenceMessageView; +class StopSequenceMessageView; +class ActiveSensingMessageView; +class ResetMessageView; template class MessageView @@ -104,6 +125,20 @@ class MessageView MonoModeOnMessageView, PolyModeOnMessageView, ChannelMessageView, + SystemMessageView, + SystemCommonMessageView, + SystemExclusiveMessageView, + TimeCodeQuarterFrameMessageView, + SongPositionPointerMessageView, + SongSelectMessageView, + TuneRequestMessageView, + SystemRealTimeMessageView, + TimingClockMessageView, + StartSequenceMessageView, + ContinueSequenceMessageView, + StopSequenceMessageView, + ActiveSensingMessageView, + ResetMessageView, MessageView> parse() const { @@ -111,6 +146,10 @@ class MessageView { return utils::variant_cast(ChannelMessageView(m_message_data).parse()); } + else if (is_system()) + { + return utils::variant_cast(SystemMessageView(m_message_data).parse()); + } return *this; } @@ -121,7 +160,7 @@ class MessageView MessageView(std::span) -> MessageView; MessageView(std::span) -> MessageView; -struct ChannelMessageViewTag +struct ChannelMessageViewTag : public MessageViewTag { }; @@ -206,7 +245,7 @@ class ChannelMessageView : public MessageView } }; -struct NoteMessageViewTag +struct NoteMessageViewTag : public ChannelMessageViewTag { }; @@ -745,4 +784,352 @@ class PitchBendMessageView : public ChannelMessageView } }; +struct SystemMessageViewTag : public MessageViewTag +{ +}; + +template +class SystemMessageView : public MessageView +{ +public: + using tag_t = SystemMessageViewTag; + + explicit SystemMessageView(typename MessageView::span_t message_data) + : MessageView(message_data) + { + } + + bool is_common() const { return this->m_message_data[0] < 0xf8; } + + bool is_realtime() const { return !is_common(); } + + std::variant, + SystemExclusiveMessageView, + TimeCodeQuarterFrameMessageView, + SongPositionPointerMessageView, + SongSelectMessageView, + TuneRequestMessageView, + SystemRealTimeMessageView, + TimingClockMessageView, + StartSequenceMessageView, + ContinueSequenceMessageView, + StopSequenceMessageView, + ActiveSensingMessageView, + ResetMessageView, + SystemMessageView> + parse() const; +}; + +struct SystemCommonMessageViewTag : public SystemMessageViewTag +{ +}; + +template +class SystemCommonMessageView : public SystemMessageView +{ +public: + using tag_t = SystemCommonMessageViewTag; + + explicit SystemCommonMessageView(typename MessageView::span_t message_data) + : SystemMessageView(message_data) + { + } + + std::variant, + TimeCodeQuarterFrameMessageView, + SongPositionPointerMessageView, + SongSelectMessageView, + TuneRequestMessageView, + SystemCommonMessageView> + parse() const + { + switch (this->m_message_data[0]) + { + case 0xf0: + return SystemExclusiveMessageView(this->m_message_data); + case 0xf1: + return TimeCodeQuarterFrameMessageView(this->m_message_data); + case 0xf2: + return SongPositionPointerMessageView(this->m_message_data); + case 0xf3: + return SongSelectMessageView(this->m_message_data); + case 0xf6: + return TuneRequestMessageView(this->m_message_data); + default: + return *this; + } + } +}; + +struct SystemExclusiveMessageViewTag : public SystemCommonMessageViewTag +{ +}; + +template +class SystemExclusiveMessageView : public SystemCommonMessageView +{ +public: + using tag_t = SystemExclusiveMessageViewTag; + constexpr inline static unsigned char end_of_exclusive = 0xf7; + + explicit SystemExclusiveMessageView(typename MessageView::span_t message_data) + : SystemCommonMessageView(message_data) + { + } + + unsigned char get_manufacturer_id() const { return this->m_message_data[1]; } + + std::size_t get_length() const + { + assert(this->m_message_data.size() > 1); + return this->m_message_data.size() - 1; + } +}; + +struct TimeCodeQuarterFrameMessageViewTag : public SystemCommonMessageViewTag +{ +}; + +template +class TimeCodeQuarterFrameMessageView : public SystemCommonMessageView +{ +public: + using tag_t = TimeCodeQuarterFrameMessageViewTag; + + explicit TimeCodeQuarterFrameMessageView(typename MessageView::span_t message_data) + : SystemCommonMessageView(message_data) + { + } + + unsigned char get_type() const { return (this->m_message_data[1] & 0x70) >> 4; } + + unsigned char get_values() const { return this->m_message_data[1] & 0x0F; } +}; + +struct SongPositionPointerMessageViewTag : public SystemCommonMessageViewTag +{ +}; + +template +class SongPositionPointerMessageView : public SystemCommonMessageView +{ +public: + using tag_t = SongPositionPointerMessageViewTag; + + explicit SongPositionPointerMessageView(typename MessageView::span_t message_data) + : SystemCommonMessageView(message_data) + { + } + + unsigned short get_position() const + { + const unsigned char lsb = this->m_message_data[1] & 0x7f; + const unsigned char msb = this->m_message_data[2] & 0x7f; + return static_cast(127) * msb + lsb; + } +}; + +struct SongSelectMessageViewTag : public SystemCommonMessageViewTag +{ +}; + +template +class SongSelectMessageView : public SystemCommonMessageView +{ +public: + using tag_t = SongSelectMessageViewTag; + + explicit SongSelectMessageView(typename MessageView::span_t message_data) + : SystemCommonMessageView(message_data) + { + } + + unsigned char get_song() const { return this->m_message_data[1] & 0x7f; } +}; + +struct TuneRequestMessageViewTag : public SystemCommonMessageViewTag +{ +}; + +class TuneRequestMessageView : public SystemCommonMessageView +{ +public: + using tag_t = TuneRequestMessageViewTag; + + explicit TuneRequestMessageView(typename MessageView::span_t message_data) + : SystemCommonMessageView(message_data) + { + } +}; + +struct SystemRealTimeMessageViewTag : public SystemMessageViewTag +{ +}; + +class SystemRealTimeMessageView : public SystemMessageView +{ +public: + using tag_t = SystemRealTimeMessageViewTag; + + explicit SystemRealTimeMessageView(typename MessageView::span_t message_data) + : SystemMessageView(message_data) + { + } + + std::variant + parse() const; +}; + +struct TimingClockMessageViewTag : public SystemRealTimeMessageViewTag +{ +}; + +class TimingClockMessageView : public SystemRealTimeMessageView +{ +public: + using tag_t = TimingClockMessageViewTag; + + explicit TimingClockMessageView(typename MessageView::span_t message_data) + : SystemRealTimeMessageView(message_data) + { + } +}; + +struct StartSequenceMessageViewTag : public SystemRealTimeMessageViewTag +{ +}; + +class StartSequenceMessageView : public SystemRealTimeMessageView +{ +public: + using tag_t = StartSequenceMessageViewTag; + + explicit StartSequenceMessageView(typename MessageView::span_t message_data) + : SystemRealTimeMessageView(message_data) + { + } +}; + +struct ContinueSequenceMessageViewTag : public SystemRealTimeMessageViewTag +{ +}; + +class ContinueSequenceMessageView : public SystemRealTimeMessageView +{ +public: + using tag_t = ContinueSequenceMessageViewTag; + + explicit ContinueSequenceMessageView(typename MessageView::span_t message_data) + : SystemRealTimeMessageView(message_data) + { + } +}; + +struct StopSequenceMessageViewTag : public SystemRealTimeMessageViewTag +{ +}; + +class StopSequenceMessageView : public SystemRealTimeMessageView +{ +public: + using tag_t = StopSequenceMessageViewTag; + + explicit StopSequenceMessageView(typename MessageView::span_t message_data) + : SystemRealTimeMessageView(message_data) + { + } +}; + +struct ActiveSensingMessageViewTag : public SystemRealTimeMessageViewTag +{ +}; + +class ActiveSensingMessageView : public SystemRealTimeMessageView +{ +public: + using tag_t = ActiveSensingMessageViewTag; + + explicit ActiveSensingMessageView(typename MessageView::span_t message_data) + : SystemRealTimeMessageView(message_data) + { + } +}; + +struct ResetMessageViewTag : public SystemRealTimeMessageViewTag +{ +}; + +class ResetMessageView : public SystemRealTimeMessageView +{ +public: + using tag_t = ResetMessageViewTag; + + explicit ResetMessageView(typename MessageView::span_t message_data) + : SystemRealTimeMessageView(message_data) + { + } +}; + +template +std::variant, + SystemExclusiveMessageView, + TimeCodeQuarterFrameMessageView, + SongPositionPointerMessageView, + SongSelectMessageView, + TuneRequestMessageView, + SystemRealTimeMessageView, + TimingClockMessageView, + StartSequenceMessageView, + ContinueSequenceMessageView, + StopSequenceMessageView, + ActiveSensingMessageView, + ResetMessageView, + SystemMessageView> +SystemMessageView::parse() const +{ + if (is_common()) + { + return utils::variant_cast(SystemCommonMessageView(this->m_message_data).parse()); + } + else if (is_realtime()) + { + return utils::variant_cast(SystemRealTimeMessageView(this->m_message_data).parse()); + } + return *this; +} + +inline std::variant +SystemRealTimeMessageView::parse() const +{ + switch (this->m_message_data[0]) + { + case 0xf8: + return TimingClockMessageView(this->m_message_data); + case 0xfa: + return StartSequenceMessageView(this->m_message_data); + case 0xfb: + return ContinueSequenceMessageView(this->m_message_data); + case 0xfc: + return StopSequenceMessageView(this->m_message_data); + case 0xfe: + return ActiveSensingMessageView(this->m_message_data); + case 0xff: + return ResetMessageView(this->m_message_data); + default: + return *this; + } +} + } // namespace mc::midi