[repo] initial commit
This commit is contained in:
commit
8cdc113508
11 changed files with 586 additions and 0 deletions
41
CMakeLists.txt
Normal file
41
CMakeLists.txt
Normal 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
|
||||
)
|
8
Hardware/ApplicationPlugin.cpp
Normal file
8
Hardware/ApplicationPlugin.cpp
Normal file
|
@ -0,0 +1,8 @@
|
|||
#include "ApplicationPlugin.hpp"
|
||||
|
||||
namespace Hardware
|
||||
{
|
||||
ApplicationPlugin::ApplicationPlugin(const score::GUIApplicationPlugin& app)
|
||||
: GUIApplicationPlugin{app}
|
||||
{ }
|
||||
}
|
11
Hardware/ApplicationPlugin.hpp
Normal file
11
Hardware/ApplicationPlugin.hpp
Normal 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
2
Hardware/Controller.cpp
Normal file
|
@ -0,0 +1,2 @@
|
|||
#include "Controller.hpp"
|
||||
|
24
Hardware/Controller.hpp
Normal file
24
Hardware/Controller.hpp
Normal 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
145
Hardware/Hardware.cpp
Normal 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
41
Hardware/Hardware.hpp
Normal 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
183
Hardware/MidiController.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
77
Hardware/MidiController.hpp
Normal file
77
Hardware/MidiController.hpp
Normal 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
19
score_addon_hardware.cpp
Normal 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
35
score_addon_hardware.hpp
Normal 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;
|
||||
};
|
||||
|
Loading…
Add table
Reference in a new issue