From 8cdc113508be338831f9a3021c400b2fb29e519e Mon Sep 17 00:00:00 2001 From: thibaudk Date: Thu, 3 Oct 2024 23:50:15 +0100 Subject: [PATCH] [repo] initial commit --- CMakeLists.txt | 41 ++++++++ Hardware/ApplicationPlugin.cpp | 8 ++ Hardware/ApplicationPlugin.hpp | 11 ++ Hardware/Controller.cpp | 2 + Hardware/Controller.hpp | 24 +++++ Hardware/Hardware.cpp | 145 ++++++++++++++++++++++++++ Hardware/Hardware.hpp | 41 ++++++++ Hardware/MidiController.cpp | 183 +++++++++++++++++++++++++++++++++ Hardware/MidiController.hpp | 77 ++++++++++++++ score_addon_hardware.cpp | 19 ++++ score_addon_hardware.hpp | 35 +++++++ 11 files changed, 586 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 Hardware/ApplicationPlugin.cpp create mode 100644 Hardware/ApplicationPlugin.hpp create mode 100644 Hardware/Controller.cpp create mode 100644 Hardware/Controller.hpp create mode 100644 Hardware/Hardware.cpp create mode 100644 Hardware/Hardware.hpp create mode 100644 Hardware/MidiController.cpp create mode 100644 Hardware/MidiController.hpp create mode 100644 score_addon_hardware.cpp create mode 100644 score_addon_hardware.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4bf8d15 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) + +if(NOT TARGET score_lib_base) + include(ScoreExternalAddon) +endif() + +if(NOT TARGET libremidi) + message("Hardware plug-in requires libremidi.") + return() +endif() + +project(score_addon_hardware LANGUAGES CXX) + +score_common_setup() + +set(HDRS + "Hardware/ApplicationPlugin.hpp" + + "Hardware/Hardware.hpp" + "Hardware/Controller.hpp" + "Hardware/MidiController.hpp" + + "score_addon_hardware.hpp" +) + +set(SRCS + "${CMAKE_CURRENT_SOURCE_DIR}/Hardware/ApplicationPlugin.cpp" + + "${CMAKE_CURRENT_SOURCE_DIR}/Hardware/Hardware.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Hardware/Controller.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Hardware/MidiController.cpp" + + "${CMAKE_CURRENT_SOURCE_DIR}/score_addon_hardware.cpp" +) + +add_library(${PROJECT_NAME} ${SRCS} ${HDRS} ${QRCS}) + +target_link_libraries(${PROJECT_NAME} PUBLIC + score_plugin_scenario + libremidi +) diff --git a/Hardware/ApplicationPlugin.cpp b/Hardware/ApplicationPlugin.cpp new file mode 100644 index 0000000..7cad36c --- /dev/null +++ b/Hardware/ApplicationPlugin.cpp @@ -0,0 +1,8 @@ +#include "ApplicationPlugin.hpp" + +namespace Hardware +{ +ApplicationPlugin::ApplicationPlugin(const score::GUIApplicationPlugin& app) + : GUIApplicationPlugin{app} +{ } +} diff --git a/Hardware/ApplicationPlugin.hpp b/Hardware/ApplicationPlugin.hpp new file mode 100644 index 0000000..7840195 --- /dev/null +++ b/Hardware/ApplicationPlugin.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace Hardware +{ +class ApplicationPlugin final : public score::GUIApplicationPlugin +{ +public: + ApplicationPlugin(const score::GUIApplicationPlugin& app); +}; diff --git a/Hardware/Controller.cpp b/Hardware/Controller.cpp new file mode 100644 index 0000000..31a025d --- /dev/null +++ b/Hardware/Controller.cpp @@ -0,0 +1,2 @@ +#include "Controller.hpp" + diff --git a/Hardware/Controller.hpp b/Hardware/Controller.hpp new file mode 100644 index 0000000..c014165 --- /dev/null +++ b/Hardware/Controller.hpp @@ -0,0 +1,24 @@ +#ifndef CONTROLLER_HPP +#define CONTROLLER_HPP + +namespace RemoteControl +{ + +struct Controller +{ + Controller() = default; + + enum Commands + { + Play = 0, + Stop, + Left, + Right, + Up, + Down + }; +}; + +} + +#endif // CONTROLLER_HPP diff --git a/Hardware/Hardware.cpp b/Hardware/Hardware.cpp new file mode 100644 index 0000000..be85c99 --- /dev/null +++ b/Hardware/Hardware.cpp @@ -0,0 +1,145 @@ +#include + +#include +#include +#include +#include + +#include "Hardware.hpp" + +namespace RemoteControl +{ +Hardware::Hardware(const score::DocumentContext& doc) + : m_dev{doc.plugin()} + , h_ofset{0.} // initial horizontal ofset + , v_ofset{0.} // initial verticalal ofset + , h_zoom{.125} // initial horizontal zoom + , v_zoom{1.1} // initial verticalal zoom +{ + commandCallback = [&doc, this](Controller::Commands com, const bool& shift) { + switch (com) + { + case Controller::Play: + if (shift) + doc.app.actions.action().action()->trigger(); + else + doc.app.actions.action().action()->trigger(); + break; + case Controller::Stop: + if (shift) + doc.app.actions.action().action()->trigger(); + else + doc.app.actions.action().action()->trigger(); + break; + case Controller::Up: + if (shift) + { + v_zoom += .2; + draw_intervals(); + } + else + { + v_ofset += .02; + draw_intervals(); + } + break; + case Controller::Down: + if (shift) + { + if (v_zoom > .2) + { + v_zoom -= .2; + draw_intervals(); + } + } + else + { + v_ofset -= .02; + draw_intervals(); + } + break; + case Controller::Left: + if (shift) + { + if (h_zoom > .02) + { + h_zoom -= .02; + draw_intervals(); + } + } + else + { + if (h_ofset > 0) + { + h_ofset -= .02; + draw_intervals(); + } + } + break; + case Controller::Right: + if (shift) + { + h_zoom += .02; + draw_intervals(); + } + else + { + h_ofset += .02; + draw_intervals(); + } + break; + default: + break; + } + + // qDebug() << "v_ofset: " << v_ofset; + // qDebug() << "v_zoom: " << v_zoom; + // qDebug() << "h_ofset: " << h_ofset; + // qDebug() << "h_zoom: " << h_ofset; + }; +} + +void Hardware::setupController() +{ + ctl = new MidiController{}; + ctl->on_command = commandCallback; + ctl->setup(); +} + +void Hardware::setup_scenario(Scenario::ProcessModel* s) +{ + scenar = s; + + // Connet interval signals + // adapted from + // src/plugins/score-plugin-scenario/Scenario/Process/MiniScenarioView.cpp#18 + scenar->intervals.added.connect<&Hardware::on_interval_changed>(this); + scenar->intervals.removed.connect<&Hardware::on_interval_changed>(this); + + connect(scenar, + &Scenario::ProcessModel::intervalMoved, + [this] { draw_intervals(); }); +} + +void Hardware::on_interval_changed(const Scenario::IntervalModel &) +{ + draw_intervals(); +} + +void Hardware::draw_intervals() +{ + if (!ctl || !scenar) return; + + for(const Scenario::IntervalModel& c : scenar->intervals) + { + auto def = c.duration.defaultDuration().sec() * h_zoom; + auto st = (c.date().sec() * h_zoom) - h_ofset; + auto y = (c.heightPercentage() * v_zoom) + v_ofset; + ctl->draw_line({st, y}, {st + def, y}); + } + + ctl->update_grid(); +} +} + +W_OBJECT_IMPL(RemoteControl::Hardware) diff --git a/Hardware/Hardware.hpp b/Hardware/Hardware.hpp new file mode 100644 index 0000000..28d165f --- /dev/null +++ b/Hardware/Hardware.hpp @@ -0,0 +1,41 @@ +#ifndef HARDWARE_HPP +#define HARDWARE_HPP + +#include + +#include +#include + +#include "MidiController.hpp" + +namespace RemoteControl +{ +class SCORE_PLUGIN_REMOTECONTROL_EXPORT Hardware + : public QObject +{ + W_OBJECT(Hardware) + +public: + explicit Hardware(const score::DocumentContext& doc); + + void setupController(); + void setup_scenario(Scenario::ProcessModel* s); + void draw_intervals(); + +private: + double h_ofset; + double v_ofset; + double h_zoom; + double v_zoom; + + MidiController* ctl; + Explorer::DeviceDocumentPlugin& m_dev; + Scenario::ProcessModel* scenar; + + std::function commandCallback; + + void on_interval_changed(const Scenario::IntervalModel &); +}; +} + +#endif // HARDWARE_HPP diff --git a/Hardware/MidiController.cpp b/Hardware/MidiController.cpp new file mode 100644 index 0000000..b7457da --- /dev/null +++ b/Hardware/MidiController.cpp @@ -0,0 +1,183 @@ +#include + +#include "MidiController.hpp" + +namespace RemoteControl { + +MidiController::MidiController() + : grid{GRID} + , previous_grid{BLACK} + , current_grid{BLACK} + , m_output{} + , m_input{{.on_message = [this] (const libremidi::message& message) +{ + if (message.get_message_type() == libremidi::message_type::CONTROL_CHANGE) + switch (message.bytes[1]) + { + case SHIFT: + shift = message.bytes[2] > 0; + break; + case PLAY: + if (message.bytes[2] > 0) + on_command(Controller::Play, shift); + break; + case STOP: + if (message.bytes[2] > 0) + on_command(Controller::Stop, shift); + break; + case UP: + if (message.bytes[2] > 0) + on_command(Controller::Up, shift); + break; + case DOWN: + if (message.bytes[2] > 0) + on_command(Controller::Down, shift); + break; + case LEFT: + if (message.bytes[2] > 0) + on_command(Controller::Left, shift); + break; + case RIGHT: + if (message.bytes[2] > 0) + on_command(Controller::Right, shift); + break; + default: + break; + } +}, + .ignore_sensing = false, + .ignore_timing = false, + .ignore_sysex = false, + } + } +{} + +MidiController::~MidiController() +{ + m_input.close_port(); + m_output.close_port(); +} + +void MidiController::setup(const QString& deviceName) +{ + open_port_by_name(deviceName); + + if (m_output.is_port_open()) + { + using namespace libremidi; + using e = channel_events; + + m_output.send_message(message PROGRAMER_MODE); + + m_output.send_message(e::note_on(1, u_int8_t{SHIFT}, u_int8_t{WHITE})); + m_output.send_message(e::note_on(1, u_int8_t{PLAY}, u_int8_t{GREEN})); + m_output.send_message(e::note_on(1, u_int8_t{STOP}, u_int8_t{ORANGE})); + + clear_grid(); + } +} + +void MidiController::update_grid() +{ + for (int i{0}; i <= MAX_COLUMN_INDEX; i++) + for (int j{0}; j <= MAX_ROW_INDEX; j++) + { + if (current_grid[i][j] != previous_grid[i][j]) + m_output.send_message(libremidi::channel_events::note_on(1, + grid[i][j], + current_grid[i][j])); + previous_grid[i][j] = current_grid[i][j]; + current_grid[i][j] = BLACK; + } +} + +void MidiController::clear_grid() +{ + for (int i{0}; i <= MAX_COLUMN_INDEX; i++) + for (int j{0}; j <= MAX_ROW_INDEX; j++) + { + current_grid[i][j] = BLACK; + m_output.send_message(libremidi::channel_events::note_on(1, + grid[i][j], + BLACK)); + previous_grid[i][j] = BLACK; + } +} + +void MidiController::draw_line(const std::array& p1, + const std::array& p2) +{ + int p1x = int(p1[0] * MAX_ROW_INDEX); + int p2x = int(p2[0] * MAX_ROW_INDEX); + int p1y = int(p1[1] * MAX_COLUMN_INDEX); + int p2y = int(p2[1] * MAX_COLUMN_INDEX); + + using namespace std; + using namespace libremidi; + using e = channel_events; + + // qDebug() << "p1y: " << p1y; + // qDebug() << "p2y: " << p2y; + + if (p1y == p2y) // draw horizontal lines + { + if (p1y < 0 || p1y > MAX_COLUMN_INDEX) // vertical position out of range + return; + else + { + int start{min(p1x, p2x)}; + int end{max(p1x, p2x)}; + + if (start > MAX_ROW_INDEX || end < 0) // horizonal position out of range + return; + + int last{min(MAX_ROW_INDEX, end)}; // last index in range + + for (int i{max(0, start)}; i <= last; i++) // itterate from first index in range + current_grid[p1y][i] = LIGHT_BLUE; + } + } + else if (p1x == p2x) // draw verticatl lines + { + if (p1x < 0 || p1x > MAX_ROW_INDEX) // horizontal position out of range + return; + else + { + int start{min(p1y, p2y)}; + int end{max(p1y, p2y)}; + + if (start > MAX_COLUMN_INDEX || end < 0) // vertical position out of range + return; + + int last{min(MAX_COLUMN_INDEX, end)}; // last index in range + + for (int i{max(0, start)}; i <= last; i++) // itterate from first index in range + current_grid[p1y][i] = LIGHT_BLUE; + } + } +} + +void MidiController::open_port_by_name(const QString& deviceName) +{ + libremidi::observer obs; + + for (const auto& input : obs.get_input_ports()) + { + if (deviceName == QString::fromStdString(input.port_name).split(":").back()) + m_input.open_port(input); + } + + // TODO : error handling here + if (!m_input.is_port_connected()) return; + + for (const auto& output : obs.get_output_ports()) + { + if (deviceName == QString::fromStdString(output.port_name).split(":").back()) + m_output.open_port(output); + } + + // TODO : error handling here + if (!m_output.is_port_connected()) return; +} + +} diff --git a/Hardware/MidiController.hpp b/Hardware/MidiController.hpp new file mode 100644 index 0000000..77bf2ff --- /dev/null +++ b/Hardware/MidiController.hpp @@ -0,0 +1,77 @@ +#ifndef MIDICONTROLLER_HPP +#define MIDICONTROLLER_HPP + +#include +#include + +#include + +#include "Controller.hpp" + +// Macros to temporarly act as the device description file +#define PROGRAMER_MODE {240, 0, 32, 41, 2, 16, 44, 3, 247} + +#define SHIFT 80 +#define PLAY 19 +#define STOP 8 +#define UP 91 +#define DOWN 92 +#define LEFT 93 +#define RIGHT 94 + +#define BLACK 0 +#define WHITE 2 +#define LIGHT_BLUE 36 +#define GREEN 64 +#define ORANGE 9 + +// Grid +#define GRID \ + {81, 82, 83, 84, 85, 86, 87, 88}, \ + {71, 72, 73, 74, 75, 76, 77, 78}, \ + {61, 62, 63, 64, 65, 66, 67, 68}, \ + {51, 52, 53, 54, 55, 56, 57, 58}, \ + {41, 42, 43, 44, 45, 46, 47, 48}, \ + {31, 32, 33, 34, 35, 36, 37, 38}, \ + {21, 22, 23, 24, 25, 26, 27, 28}, \ + {11, 12, 13, 14, 15, 16, 17, 18} + +#define MAX_ROW_INDEX 7 +#define MAX_COLUMN_INDEX 7 + +namespace Explorer +{ +class DeviceDocumentPlugin; +} + +namespace RemoteControl +{ + +class MidiController : public Controller +{ +public: + MidiController(); + ~MidiController(); + + std::function on_command; + + void setup(const QString& deviceName = "Launchpad Pro Standalone Port"); + void update_grid(); + void clear_grid(); + void draw_line(const std::array& p1, const std::array& p2); + +private: + void open_port_by_name(const QString& deviceName); + + u_int8_t grid[MAX_ROW_INDEX + 1][MAX_COLUMN_INDEX + 1]; + u_int8_t previous_grid[MAX_ROW_INDEX + 1][MAX_COLUMN_INDEX + 1]; + u_int8_t current_grid[MAX_ROW_INDEX + 1][MAX_COLUMN_INDEX + 1]; + bool shift{false}; + + libremidi::midi_out m_output; + libremidi::midi_in m_input; +}; + +} + +#endif // MIDICONTROLLER_HPP diff --git a/score_addon_hardware.cpp b/score_addon_hardware.cpp new file mode 100644 index 0000000..d6114ca --- /dev/null +++ b/score_addon_hardware.cpp @@ -0,0 +1,19 @@ +#include "score_addon_hardware.hpp" +#include "Hardware/ApplicationPlugin.hpp" + +score_addon_hardware::score_addon_hardware() { } + +score_addon_hardware::~score_addon_hardware() { } + +score::GUIApplicationPlugin *score_addon_hardware::make_guiApplicationPlugin( + const score::GUIApplicationContext& app) const +{ + return new Hardware::ApplicationPlugin{app}; +} + +std::vector> + score_addon_hardware::factoryFamilies() +{ + return make_ptr_vector< + score::InterfaceListBase>(); +} diff --git a/score_addon_hardware.hpp b/score_addon_hardware.hpp new file mode 100644 index 0000000..4a3a538 --- /dev/null +++ b/score_addon_hardware.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +class score_addon_hardware final + : public score::Plugin_QtInterface + , public score::FactoryList_QtInterface + , public score::FactoryInterface_QtInterface + , public score::ApplicationPlugin_QtInterface +{ + SCORE_PLUGIN_METADATA(1, "5cf3c2d1-b8bd-4975-b165-8dc2e547e64c") +public: + score_addon_hardware(); + virtual ~score_addon_hardware(); + +private: + score::GUIApplicationPlugin* + make_guiApplicationPlugin(const score::GUIApplicationContext& app) const; + + std::vector> factoryFamilies() override; + + std::vector factories( + const score::ApplicationContext& ctx, + const score::InterfaceKey& key) const override; + + std::vector required() const override; +}; +