Files
vimix/ControlManager.cpp
Bruno Herbelin 706c72fda8 More OSC control
Grab and resize dynamically, select source by index, etc.
2021-12-21 00:19:39 +01:00

317 lines
12 KiB
C++

/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2020-2021 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <thread>
#include <mutex>
#include <sstream>
#include "osc/OscOutboundPacketStream.h"
#include "Log.h"
#include "Settings.h"
#include "BaseToolkit.h"
#include "Mixer.h"
#include "Source.h"
#include "ControlManager.h"
#ifndef NDEBUG
#define CONTROL_DEBUG
#endif
#define CONTROL_OSC_MSG "OSC: "
#define OSC_SEPARATOR '/'
void Control::RequestListener::ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint )
{
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
remoteEndpoint.AddressAndPortAsString(sender);
try{
#ifdef CONTROL_DEBUG
Log::Info(CONTROL_OSC_MSG "received '%s' from %s", m.AddressPattern(), sender);
#endif
// TODO Preprocessing with Translator
// structured OSC address
std::list<std::string> address = BaseToolkit::splitted(m.AddressPattern(), OSC_SEPARATOR);
//
// A wellformed OSC address is in the form '/vimix/target/attribute {arguments}'
// First test: should have 3 elements and start with APP_NAME ('vimix')
//
if (address.size() == 3 && address.front().compare(APP_NAME) == 0 ){
// done with the first part of the OSC address
address.pop_front();
//
// Execute next part of the OSC message according to target
//
std::string target = address.front();
std::string attribute = address.back();
// Log target: just print text in log window
if ( target.compare(OSC_LOG) == 0 )
{
if ( attribute.compare(OSC_LOG_INFO) == 0)
Log::Info(CONTROL_OSC_MSG "received '%s' from %s", m.AddressPattern(), sender);
}
// Output target: concerns attributes of the rendering output
else if ( target.compare(OSC_OUTPUT) == 0 )
{
Control::manager().setOutputAttribute(attribute, m.ArgumentStream());
}
// ALL sources target: apply attribute to all sources of the session
else if ( target.compare(OSC_ALL) == 0 )
{
// Loop over selected sources
for (SourceList::iterator it = Mixer::manager().session()->begin(); it != Mixer::manager().session()->end(); ++it) {
// attributes operate on current source
Control::manager().setSourceAttribute( *it, attribute, m.ArgumentStream());
}
}
// Selected sources target: apply attribute to all sources of the selection
else if ( target.compare(OSC_SELECTED) == 0 )
{
// Loop over selected sources
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
// attributes operate on current source
Control::manager().setSourceAttribute( *it, attribute, m.ArgumentStream());
}
}
// Current source target: apply attribute to the current sources
else if ( target.compare(OSC_CURRENT) == 0 )
{
// attributes to change current
if ( attribute.compare(OSC_SET) == 0) {
int index = 0;
m.ArgumentStream() >> index >> osc::EndMessage;
Mixer::manager().setCurrentIndex(index);
}
else if ( attribute.compare(OSC_NEXT) == 0)
Mixer::manager().setCurrentNext();
else if ( attribute.compare(OSC_PREVIOUS) == 0)
Mixer::manager().setCurrentPrevious();
// all other attributes operate on current source
else
Control::manager().setSourceAttribute( Mixer::manager().currentSource(), attribute, m.ArgumentStream());
}
// General case: try to identify the target
else {
// try to find source by name
Source *s = Mixer::manager().findSource(target);
// if failed, try to find source by index
if (s == nullptr) {
int N = -1;
try {
N = std::stoi(target);
} catch (const std::invalid_argument&) {
N = -1;
}
if (N>=0)
s = Mixer::manager().sourceAtIndex(N);
}
if (s)
Control::manager().setSourceAttribute(s, attribute, m.ArgumentStream());
else
Log::Info(CONTROL_OSC_MSG "Unknown target '%s' requested by %s.", target.c_str(), sender);
}
}
#ifdef CONTROL_DEBUG
else {
Log::Info(CONTROL_OSC_MSG "Ignoring malformed message '%s' from %s.", m.AddressPattern(), sender);
}
#endif
}
catch( osc::Exception& e ){
// any parsing errors such as unexpected argument types, or
// missing arguments get thrown as exceptions.
Log::Info(CONTROL_OSC_MSG "Ignoring error in message '%s' from %s : %s", m.AddressPattern(), sender, e.what());
}
}
void Control::listen()
{
#ifdef CONTROL_DEBUG
Log::Info(CONTROL_OSC_MSG "Accepting messages on port %d", Settings::application.control.osc_port_receive);
#endif
if (Control::manager().receiver_)
Control::manager().receiver_->Run();
}
Control::Control() : receiver_(nullptr)
{
}
Control::~Control()
{
if (receiver_!=nullptr) {
receiver_->Break();
delete receiver_;
}
}
bool Control::init()
{
try {
// try to create listenning socket
// through exception runtime if fails
receiver_ = new UdpListeningReceiveSocket( IpEndpointName( IpEndpointName::ANY_ADDRESS,
Settings::application.control.osc_port_receive ), &listener_ );
}
catch (const std::runtime_error&) {
// arg, the receiver could not be initialized
// because the port was not available
receiver_ = nullptr;
}
// listen for answers
std::thread(listen).detach();
return receiver_ != nullptr;
}
void Control::terminate()
{
if (receiver_!=nullptr)
receiver_->AsynchronousBreak();
}
void Control::setOutputAttribute(const std::string &attribute,
osc::ReceivedMessageArgumentStream arguments)
{
try {
/// e.g. '/vimix/output/enable' or '/vimix/output/enable T' or '/vimix/output/enable F'
if ( attribute.compare(OSC_OUTPUT_ENABLE) == 0) {
bool on = true;
if ( !arguments.Eos()) {
arguments >> on >> osc::EndMessage;
}
Settings::application.render.disabled = !on;
}
/// e.g. '/vimix/output/disable' or '/vimix/output/disable T' or '/vimix/output/disable F'
else if ( attribute.compare(OSC_OUTPUT_DISABLE) == 0) {
bool on = true;
if ( !arguments.Eos()) {
arguments >> on >> osc::EndMessage;
}
Settings::application.render.disabled = on;
}
/// e.g. '/vimix/output/fading f 0.2'
else if ( attribute.compare(OSC_OUTPUT_FADING) == 0) {
float fading = 0.f;
arguments >> fading >> osc::EndMessage;
Mixer::manager().session()->setFading(fading); // TODO move cursor when in Mixing view
}
#ifdef CONTROL_DEBUG
else {
Log::Info(CONTROL_OSC_MSG "Ignoring attribute '%s' for target 'output'", attribute.c_str());
}
#endif
}
catch (osc::MissingArgumentException &e) {
Log::Info(CONTROL_OSC_MSG "Missing argument for attribute '%s' for target 'output'", attribute.c_str());
}
catch (osc::ExcessArgumentException &e) {
Log::Info(CONTROL_OSC_MSG "Too many arguments for attribute '%s' for target 'output'", attribute.c_str());
}
catch (osc::WrongArgumentTypeException &e) {
Log::Info(CONTROL_OSC_MSG "Invalid argument for attribute '%s' for target 'output'", attribute.c_str());
}
}
void Control::setSourceAttribute(Source *target, const std::string &attribute,
osc::ReceivedMessageArgumentStream arguments)
{
if (target == nullptr)
return;
try {
/// e.g. '/vimix/current/play' or '/vimix/current/play T' or '/vimix/current/play F'
if ( attribute.compare(OSC_SOURCE_PLAY) == 0) {
bool on = true;
if ( !arguments.Eos()) {
arguments >> on >> osc::EndMessage;
}
target->call( new SetPlay(on) );
}
/// e.g. '/vimix/current/pause' or '/vimix/current/pause T' or '/vimix/current/pause F'
else if ( attribute.compare(OSC_SOURCE_PAUSE) == 0) {
bool on = true;
if ( !arguments.Eos()) {
arguments >> on >> osc::EndMessage;
}
target->call( new SetPlay(!on) );
}
/// e.g. '/vimix/current/replay'
else if ( attribute.compare(OSC_SOURCE_REPLAY) == 0) {
target->call( new RePlay() );
}
/// e.g. '/vimix/current/alpha f 0.3'
else if ( attribute.compare(OSC_SOURCE_ALPHA) == 0) {
float x = 1.f;
arguments >> x >> osc::EndMessage;
target->call( new SetAlpha(x), true );
}
/// e.g. '/vimix/current/transparency f 0.7'
else if ( attribute.compare(OSC_SOURCE_TRANSPARENCY) == 0) {
float x = 0.f;
arguments >> x >> osc::EndMessage;
target->call( new SetAlpha(1.f - x), true );
}
/// e.g. '/vimix/current/depth f 5.0'
else if ( attribute.compare(OSC_SOURCE_DEPTH) == 0) {
float x = 0.f;
arguments >> x >> osc::EndMessage;
target->call( new SetDepth(x) );
}
/// e.g. '/vimix/current/translation ff 10.0 2.2'
else if ( attribute.compare(OSC_SOURCE_GRAB) == 0) {
float x = 0.f, y = 0.f;
arguments >> x >> y >> osc::EndMessage;
target->call( new Grab( x, y), true );
}
/// e.g. '/vimix/current/scale ff 10.0 2.2'
else if ( attribute.compare(OSC_SOURCE_RESIZE) == 0) {
float x = 0.f, y = 0.f;
arguments >> x >> y >> osc::EndMessage;
target->call( new Resize( x, y), true );
}
#ifdef CONTROL_DEBUG
else {
Log::Info(CONTROL_OSC_MSG "Ignoring attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
}
#endif
}
catch (osc::MissingArgumentException &e) {
Log::Info(CONTROL_OSC_MSG "Missing argument for attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
}
catch (osc::ExcessArgumentException &e) {
Log::Info(CONTROL_OSC_MSG "Too many arguments for attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
}
catch (osc::WrongArgumentTypeException &e) {
Log::Info(CONTROL_OSC_MSG "Invalid argument for attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
}
}