Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
mfep committed May 8, 2024
1 parent e473193 commit b37ce76
Show file tree
Hide file tree
Showing 14 changed files with 789 additions and 51 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
175 changes: 175 additions & 0 deletions src/LogNode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#include "LogNode.hpp"

#include "NodeSerializer.hpp"

#include "midi/MessageView.hpp"
#include "midi/Note.hpp"

#include "imgui.h"
#include "imnodes.h"

#include <format>

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<int>(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<std::size_t>(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, 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<std::chrono::seconds>(local.time_since_epoch()).count() % 60;
const auto milliseconds =
std::chrono::floor<std::chrono::milliseconds>(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<const unsigned char> 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::ControlChangeMessageViewTag, auto control_change) -> BufferElement {
return BufferElement{"Control Change"s,
control_change.get_channel_human(),
std::format("Controller: {}", control_change.get_controller()),
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));
}
72 changes: 72 additions & 0 deletions src/LogNode.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#pragma once

#include "ActivityIndicator.hpp"
#include "Node.hpp"
#include "midi/MidiGraph.hpp"

#include <array>
#include <chrono>
#include <cstdint>
#include <deque>
#include <mutex>
#include <span>
#include <string_view>

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<const unsigned char> 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<int>&& 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<std::chrono::system_clock> m_arrived;
std::string m_name;
std::optional<int> m_channel;
std::string m_data_0;
std::string m_data_1;
};

std::mutex m_buffer_mutex;
std::deque<BufferElement> m_message_buffer;

friend class NodeSerializer;
};

} // namespace mc
65 changes: 43 additions & 22 deletions src/NodeEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "imnodes.h"
#include "nlohmann/json.hpp"

#include "LogNode.hpp"
#include "MidiChannelNode.hpp"
#include "MidiInNode.hpp"
#include "MidiOutNode.hpp"
Expand Down Expand Up @@ -143,21 +144,37 @@ NodeEditor NodeEditor::from_json(NodeFactory& node_factory,
std::shared_ptr<Node> 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<Node> {
auto render_add_node = [this](auto node_builder,
const std::string& name) -> std::shared_ptr<Node> {
constexpr ImGuiTreeNodeFlags leaf_flags =
ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
ImGui::TreeNodeEx(name.c_str(), leaf_flags);
if (ImGui::IsItemClicked())
{
std::shared_ptr<Node> 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<Node> {
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> node = m_node_factory->build_midi_node(info);
m_nodes.push_back(node);

ImNodes::SetNodeScreenSpacePos(node->id(),
ImGui::GetMousePosOnOpeningCurrentPopup());
ImGui::CloseCurrentPopup();
return node;
}
}
Expand All @@ -178,19 +195,23 @@ std::shared_ptr<Node> 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;
Expand All @@ -199,7 +220,7 @@ std::shared_ptr<Node> 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;
Expand Down
6 changes: 6 additions & 0 deletions src/NodeFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "DisconnectedMidiInNode.hpp"
#include "DisconnectedMidiOutNode.hpp"
#include "LogNode.hpp"
#include "MidiChannelNode.hpp"
#include "MidiInNode.hpp"
#include "MidiOutNode.hpp"
Expand Down Expand Up @@ -90,6 +91,11 @@ std::shared_ptr<MidiChannelNode> NodeFactory::build_midi_channel_node()
});
}

std::shared_ptr<LogNode> NodeFactory::build_log_node()
{
return std::make_shared<LogNode>();
}

bool NodeFactory::is_node_instantiated(const midi::InputInfo& input_info)
{
return nullptr != get_cached_midi_node(m_input_nodes, input_info);
Expand Down
Loading

0 comments on commit b37ce76

Please sign in to comment.