-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
789 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.