[repo] initial commit

This commit is contained in:
thibaud keller 2024-10-03 23:50:15 +01:00
commit 8cdc113508
11 changed files with 586 additions and 0 deletions

41
CMakeLists.txt Normal file
View file

@ -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
)

View file

@ -0,0 +1,8 @@
#include "ApplicationPlugin.hpp"
namespace Hardware
{
ApplicationPlugin::ApplicationPlugin(const score::GUIApplicationPlugin& app)
: GUIApplicationPlugin{app}
{ }
}

View file

@ -0,0 +1,11 @@
#pragma once
#include <score/plugins/application/GUIApplicationPlugin.hpp>
namespace Hardware
{
class ApplicationPlugin final : public score::GUIApplicationPlugin
{
public:
ApplicationPlugin(const score::GUIApplicationPlugin& app);
};

2
Hardware/Controller.cpp Normal file
View file

@ -0,0 +1,2 @@
#include "Controller.hpp"

24
Hardware/Controller.hpp Normal file
View file

@ -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

145
Hardware/Hardware.cpp Normal file
View file

@ -0,0 +1,145 @@
#include <wobjectimpl.h>
#include <score/application/ApplicationContext.hpp>
#include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
#include <score/actions/ActionManager.hpp>
#include <Scenario/Application/ScenarioActions.hpp>
#include "Hardware.hpp"
namespace RemoteControl
{
Hardware::Hardware(const score::DocumentContext& doc)
: m_dev{doc.plugin<Explorer::DeviceDocumentPlugin>()}
, 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<Actions::PlayGlobal>().action()->trigger();
else
doc.app.actions.action<Actions::Play>().action()->trigger();
break;
case Controller::Stop:
if (shift)
doc.app.actions.action<Actions::Reinitialize>().action()->trigger();
else
doc.app.actions.action<Actions::Stop>().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)

41
Hardware/Hardware.hpp Normal file
View file

@ -0,0 +1,41 @@
#ifndef HARDWARE_HPP
#define HARDWARE_HPP
#include <score_plugin_remotecontrol_export.h>
#include <Scenario/Document/Interval/IntervalModel.hpp>
#include <Scenario/Process/ScenarioModel.hpp>
#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<void(Controller::Commands, const bool&)> commandCallback;
void on_interval_changed(const Scenario::IntervalModel &);
};
}
#endif // HARDWARE_HPP

183
Hardware/MidiController.cpp Normal file
View file

@ -0,0 +1,183 @@
#include <libremidi/libremidi.hpp>
#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<double, 2>& p1,
const std::array<double, 2>& 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;
}
}

View file

@ -0,0 +1,77 @@
#ifndef MIDICONTROLLER_HPP
#define MIDICONTROLLER_HPP
#include <Scenario/Application/ScenarioActions.hpp>
#include <score/actions/ActionManager.hpp>
#include <libremidi/libremidi.hpp>
#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<void(Controller::Commands, const bool&)> on_command;
void setup(const QString& deviceName = "Launchpad Pro Standalone Port");
void update_grid();
void clear_grid();
void draw_line(const std::array<double, 2>& p1, const std::array<double, 2>& 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

19
score_addon_hardware.cpp Normal file
View file

@ -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<std::unique_ptr<score::InterfaceListBase>>
score_addon_hardware::factoryFamilies()
{
return make_ptr_vector<
score::InterfaceListBase>();
}

35
score_addon_hardware.hpp Normal file
View file

@ -0,0 +1,35 @@
#pragma once
#include <score/application/ApplicationContext.hpp>
#include <score/plugins//Interface.hpp>
#include <score/plugins/qt_interfaces/PluginRequirements_QtInterface.hpp>
#include <score/plugins/qt_interfaces/FactoryFamily_QtInterface.hpp>
#include <score/plugins/qt_interfaces/FactoryInterface_QtInterface.hpp>
#include <score/plugins/qt_interfaces/GUIApplicationPlugin_QtInterface.hpp>
#include <QObject>
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<std::unique_ptr<score::InterfaceBase>> factoryFamilies() override;
std::vector<score::InterfaceBase*> factories(
const score::ApplicationContext& ctx,
const score::InterfaceKey& key) const override;
std::vector<score::PluginKey> required() const override;
};