Skip to content

Commit

Permalink
feat(midi): Implemented a data oriented model&graph for processing MI…
Browse files Browse the repository at this point in the history
…DI messages
  • Loading branch information
mfep committed Apr 17, 2024
1 parent 836a2d7 commit b7a90d8
Show file tree
Hide file tree
Showing 45 changed files with 910 additions and 826 deletions.
10 changes: 4 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ add_subdirectory(external/libspng EXCLUDE_FROM_ALL)
set(RTMIDI_API_JACK OFF)
set(RTMIDI_BUILD_STATIC_LIBS ON)
add_subdirectory(external/rtmidi EXCLUDE_FROM_ALL)
add_subdirectory(src/midi)

set(sources
${g_font_cpp_path}
Expand All @@ -45,6 +46,7 @@ set(sources
external/imgui/backends/imgui_impl_sdlrenderer.cpp
external/imgui/misc/freetype/imgui_freetype.cpp
external/imnodes/imnodes.cpp
src/ActivityIndicator.cpp
src/Application.cpp
src/ConfigFile.cpp
src/DisconnectedMidiInNode.cpp
Expand All @@ -66,10 +68,6 @@ set(sources
src/ResourceLoader.cpp
src/Theme.cpp
src/Version.cpp
src/midi/InputObserver.cpp
src/midi/MidiEngine.cpp
src/midi/MidiProbe.cpp
src/midi/OutputObserver.cpp
)

if(WIN32)
Expand Down Expand Up @@ -105,15 +103,15 @@ target_link_libraries(midiconn PRIVATE
SDL2::SDL2
fmt::fmt
freetype
resources)
resources
midi)
if(NOT WIN32 AND MC_CHECK_FOR_UPDATES)
target_link_libraries(midiconn PRIVATE curl)
endif()
target_compile_definitions(midiconn PRIVATE
SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG
IMGUI_ENABLE_FREETYPE
)
target_compile_features(midiconn PRIVATE cxx_std_20)

set(CPACK_PACKAGE_NAME "midiconn")
set(CPACK_PACKAGE_VENDOR "safeworlds")
Expand Down
29 changes: 29 additions & 0 deletions src/ActivityIndicator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "ActivityIndicator.hpp"

#include "imgui.h"

#include <algorithm>

void mc::ActivityIndicator::render() const
{
constexpr double fade_time_ms = 1000;
const auto ms_since_last_message = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - m_last_trigger)
.count();
const double percent = 1 - std::min(1., ms_since_last_message / fade_time_ms);
const auto color = ImVec4(0, 1, 0, percent);
ImGui::PushStyleColor(ImGuiCol_Button, color);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, color);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, color);
ImGui::PushStyleVar(ImGuiStyleVar_DisabledAlpha, 1);
ImGui::BeginDisabled();
ImGui::Button(" ");
ImGui::EndDisabled();
ImGui::PopStyleVar();
ImGui::PopStyleColor(3);
}

void mc::ActivityIndicator::trigger()
{
m_last_trigger = std::chrono::system_clock::now();
}
17 changes: 17 additions & 0 deletions src/ActivityIndicator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once
#include <chrono>

namespace mc
{

class ActivityIndicator
{
public:
void render() const;
void trigger();

private:
std::chrono::time_point<std::chrono::system_clock> m_last_trigger;
};

} // namespace mc
5 changes: 2 additions & 3 deletions src/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ namespace mc
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_midi_engine, m_theme_control, m_port_name_display),
: 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_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()),
Expand Down Expand Up @@ -194,7 +193,7 @@ void Application::render_main_menu()
changed;
if (changed)
{
m_midi_engine.enable_message_types(m_preset.m_message_type_mask);
// TODO
}
ImGui::EndMenu();
}
Expand Down
2 changes: 0 additions & 2 deletions src/Application.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
#include "ResourceLoader.hpp"
#include "Theme.hpp"
#include "UpdateChecker.hpp"
#include "midi/MidiEngine.hpp"

namespace mc
{
Expand Down Expand Up @@ -46,7 +45,6 @@ class Application final
bool m_is_done{};
ConfigFile m_config;
ThemeControl m_theme_control;
midi::Engine m_midi_engine;
NodeFactory m_node_factory;
Preset m_preset;
PresetManager m_preset_manager;
Expand Down
3 changes: 3 additions & 0 deletions src/DisconnectedNode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class DisconnectedNode : public Node
ImNodes::PopColorStyle();
ImNodes::PopColorStyle();
}

protected:
midi::Node* get_midi_node() override { return nullptr; }
};

} // namespace mc
79 changes: 46 additions & 33 deletions src/MidiChannelNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,25 @@

#include "NodeSerializer.hpp"

namespace mc
{

const char* MidiChannelNode::sm_combo_items[] = {
const char* mc::MidiChannelNode::sm_combo_items[] = {
"None", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"};

MidiChannelNode::MidiChannelNode(std::function<float()> get_scale) : m_get_scale(get_scale)
mc::MidiChannelNode::MidiChannelNode(std::function<float()> get_scale) : m_get_scale(get_scale)
{
std::iota(m_channels.begin(), m_channels.end(), 1);
}

void MidiChannelNode::accept_serializer(nlohmann::json& j, const NodeSerializer& serializer) const
void mc::MidiChannelNode::accept_serializer(nlohmann::json& j,
const NodeSerializer& serializer) const
{
serializer.serialize_node(j, *this);
}

void MidiChannelNode::render_internal()
mc::midi::Node* mc::MidiChannelNode::get_midi_node()
{
return &m_midi_channel_map_node;
}

void mc::MidiChannelNode::render_internal()
{
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted("Channel map");
Expand All @@ -38,9 +40,26 @@ void MidiChannelNode::render_internal()
ImGui::TextUnformatted("MIDI out");
ImNodes::EndOutputAttribute();

const auto previous_channels = m_channels;
std::array<int, midi::ChannelMap::num_channels> channels;
{
std::size_t idx = 0;
for (auto& channel_item : channels)
{
const auto channel = m_midi_channel_map_node.map().get(idx++);
if (channel == midi::ChannelMap::no_channel)
{
channel_item = 0;
}
else
{
channel_item = channel + 1;
}
}
}
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_get_scale(), 0}))
{
for (size_t i = 0; i < 8; i++)
{
Expand All @@ -50,7 +69,7 @@ void MidiChannelNode::render_internal()
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(50 * m_get_scale());
ImGui::Combo(get_hidden_label(i * 2),
m_channels.data() + i * 2,
updated_channels.data() + i * 2,
sm_combo_items,
sm_num_combo_items);

Expand All @@ -59,46 +78,42 @@ void MidiChannelNode::render_internal()
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(50 * m_get_scale());
ImGui::Combo(get_hidden_label(i * 2 + 1),
m_channels.data() + i * 2 + 1,
updated_channels.data() + i * 2 + 1,
sm_combo_items,
sm_num_combo_items);
}
ImGui::EndTable();
}
if (ImGui::Button("Default"))
{
std::iota(m_channels.begin(), m_channels.end(), 1);
std::iota(updated_channels.begin(), updated_channels.end(), 1);
}
ImGui::SameLine();
if (ImGui::Button("Disable all"))
{
std::fill(m_channels.begin(), m_channels.end(), 0);
}
if (m_channels != previous_channels)
{
update_outputs_with_sources();
std::fill(updated_channels.begin(), updated_channels.end(), 0);
}
}

midi::channel_map MidiChannelNode::transform_channel_map(const midi::channel_map& in_map)
{
midi::channel_map out_map;
for (size_t channel_input_index = 0; channel_input_index < in_map.size(); channel_input_index++)
if (channels != updated_channels)
{
const auto channel_output_value = m_channels[channel_input_index];
const auto channel_output_index = static_cast<char>(channel_output_value - 1);
for (size_t i = 0; i < in_map.size(); i++)
midi::ChannelMap new_map;
std::size_t idx = 0;
for (const auto& channel_item : updated_channels)
{
if (in_map[i] == static_cast<char>(channel_input_index))
if (channel_item == 0)
{
out_map[i] = channel_output_index;
m_midi_channel_map_node.map().set(idx, midi::ChannelMap::no_channel);
}
else
{
m_midi_channel_map_node.map().set(idx, channel_item - 1);
}
++idx;
}
}
return out_map;
}

const char* MidiChannelNode::get_label(size_t index)
const char* mc::MidiChannelNode::get_label(size_t index)
{
static std::vector<std::string> labels;
if (labels.empty())
Expand All @@ -113,7 +128,7 @@ const char* MidiChannelNode::get_label(size_t index)
return labels.at(index).c_str();
}

const char* MidiChannelNode::get_hidden_label(size_t index)
const char* mc::MidiChannelNode::get_hidden_label(size_t index)
{
static std::vector<std::string> labels;
if (labels.empty())
Expand All @@ -125,5 +140,3 @@ const char* MidiChannelNode::get_hidden_label(size_t index)
}
return labels.at(index).c_str();
}

} // namespace mc
10 changes: 6 additions & 4 deletions src/MidiChannelNode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include <functional>

#include "Node.hpp"
#include "midi/MidiInfo.hpp"
#include "midi/ChannelMapNode.hpp"

namespace mc
{
Expand All @@ -15,18 +15,20 @@ class MidiChannelNode final : public Node

void accept_serializer(nlohmann::json& j, const NodeSerializer& serializer) const override;

protected:
midi::Node* get_midi_node() override;

private:
void render_internal() override;
midi::channel_map transform_channel_map(const midi::channel_map& in_map) override;
void render_internal() override;

static const char* get_label(size_t index);
static const char* get_hidden_label(size_t index);

static inline constexpr size_t sm_num_combo_items = 17;
static const char* sm_combo_items[sm_num_combo_items];

std::array<int, 16> m_channels;
std::function<float()> m_get_scale;
midi::ChannelMapNode m_midi_channel_map_node;

friend class NodeSerializer;
};
Expand Down
42 changes: 16 additions & 26 deletions src/MidiInNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,63 +7,53 @@

#include "NodeSerializer.hpp"
#include "PortNameDisplay.hpp"
#include "midi/InputNode.hpp"
#include "midi/MidiProbe.hpp"

namespace mc
{

MidiInNode::MidiInNode(const midi::InputInfo& input_info,
midi::Engine& midi_engine,
const PortNameDisplay& port_name_display)
: m_input_info(input_info), m_midi_engine(&midi_engine), m_port_name_display(&port_name_display)
MidiInNode::MidiInNode(const midi::InputInfo& input_info,
std::shared_ptr<midi::InputNode> midi_input_node,
const PortNameDisplay& port_name_display)
: m_input_info(input_info), m_midi_input_node(midi_input_node),
m_port_name_display(&port_name_display)
{
m_midi_engine->create(input_info, this);
auto& map = m_input_sources[input_info.m_id] = {};
std::iota(map.begin(), map.end(), 0);
m_midi_input_node->add_observer(this);
}

MidiInNode::~MidiInNode()
{
m_midi_engine->remove(m_input_info, this);
m_midi_input_node->remove_observer(this);
}

void MidiInNode::accept_serializer(nlohmann::json& j, const NodeSerializer& serializer) const
{
serializer.serialize_node(j, *this);
}

midi::Node* MidiInNode::get_midi_node()
{
return m_midi_input_node.get();
}

void MidiInNode::render_internal()
{
ImNodes::BeginNodeTitleBar();
const std::string node_title = m_port_name_display->get_port_name(m_input_info);
ImGui::TextUnformatted(node_title.c_str());
ImNodes::EndNodeTitleBar();
ImNodes::BeginOutputAttribute(out_id());
constexpr double fade_time_ms = 1000;
const auto ms_since_last_message =
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() -
m_last_message_received)
.count();
const double percent = 1 - std::min(1., ms_since_last_message / fade_time_ms);
const auto color = ImVec4(0, 1, 0, percent);
ImGui::PushStyleColor(ImGuiCol_Button, color);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, color);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, color);
ImGui::PushStyleVar(ImGuiStyleVar_DisabledAlpha, 1);
ImGui::BeginDisabled();
ImGui::Button(" ");
ImGui::EndDisabled();
ImGui::PopStyleVar();
ImGui::PopStyleColor(3);
m_midi_activity.render();
ImGui::SameLine();

ImGui::TextUnformatted("all channels");
ImNodes::EndOutputAttribute();
}

void MidiInNode::message_received(size_t /*id*/, std::vector<unsigned char>& /*message_bytes*/)
void MidiInNode::message_processed(std::span<const unsigned char> /*message_bytes*/)
{
m_last_message_received = std::chrono::system_clock::now();
m_midi_activity.trigger();
}

} // namespace mc
Loading

0 comments on commit b7a90d8

Please sign in to comment.