diff --git a/src/plugins/score-plugin-protocols/CMakeLists.txt b/src/plugins/score-plugin-protocols/CMakeLists.txt index a683b065b7..c916fd0282 100644 --- a/src/plugins/score-plugin-protocols/CMakeLists.txt +++ b/src/plugins/score-plugin-protocols/CMakeLists.txt @@ -207,6 +207,8 @@ set(ARTNET_HDRS "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/ArtnetProtocolFactory.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/ArtnetProtocolSettingsWidget.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/ArtnetSpecificSettings.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/DMXFixtureInstantiation.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/DMXProtocolCreation.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/FixtureDialog.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/LEDDialog.hpp" ) @@ -216,6 +218,8 @@ set(ARTNET_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/ArtnetProtocolFactory.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/ArtnetProtocolSettingsWidget.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/ArtnetSpecificSettingsSerialization.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/DMXFixtureInstantiation.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/DMXProtocolCreation.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/FixtureDialog.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Artnet/LEDDialog.cpp" ) diff --git a/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetDevice.cpp b/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetDevice.cpp index 86cc607e83..d38842034d 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetDevice.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetDevice.cpp @@ -1,22 +1,17 @@ #include -#include #if defined(OSSIA_PROTOCOL_ARTNET) #include "ArtnetDevice.hpp" #include "ArtnetSpecificSettings.hpp" #include +#include +#include + #include #include -#include -#include -#include -#include -#include - -#include #include W_OBJECT_IMPL(Protocols::ArtnetDevice) @@ -39,190 +34,6 @@ ArtnetDevice::ArtnetDevice( ArtnetDevice::~ArtnetDevice() { } -namespace -{ -struct fixture_setup_visitor -{ - const Artnet::Fixture& fix; - const Artnet::Channel& chan; - ossia::net::node_base& fixt_node; - ossia::net::dmx_buffer& buffer; - int dmx_channel; - int operator()(const Artnet::SingleCapability& v) const noexcept - { - auto chan_node = fixt_node.create_child(chan.name.toStdString()); - auto chan_param - = std::make_unique(*chan_node, buffer, dmx_channel); - - auto& node = *chan_node; - auto& p = *chan_param; - - // FIXME this only works if the channels are joined for now - int bytes = 1; - - for(auto& name : chan.fineChannels) - { - if(ossia::contains(fix.mode.channelNames, name)) - { - bytes++; - } - } - p.m_bytes = bytes; - - chan_node->set_parameter(std::move(chan_param)); - p.set_default_value(chan.defaultValue); - p.set_value(chan.defaultValue); - - if(!v.comment.isEmpty()) - ossia::net::set_description(node, v.comment.toStdString()); - - return bytes; - } - - int operator()(const std::vector& v) const noexcept - { - std::vector> values; - std::string comment; - std::string default_preset; - - // Parse all capabilities - for(auto& capa : v) - { - std::string name; - if(!capa.effectName.isEmpty()) - name = capa.effectName.toStdString(); - else - name = capa.type.toStdString(); - - if(chan.defaultValue >= capa.range.first && chan.defaultValue < capa.range.second) - default_preset = name; - values.push_back({name, capa.range.first}); - } - - // Make sure that all values have an unique name - { - std::vector counts; - for(int i = 0; i < std::ssize(values); i++) - { - int n = 1; - for(int j = 0; j < i; j++) - { - if(values[j].first == values[i].first) - n++; - } - counts.push_back(n); - } - for(int i = 0; i < std::ssize(values); i++) - { - if(counts[i] > 1) - values[i].first += fmt::format(" {}", counts[i]); - } - } - - // Write the comment - for(int i = 0; i < std::ssize(values); i++) - { - if(!v[i].comment.isEmpty()) - { - comment += values[i].first + ": " + v[i].comment.toStdString() + "\n"; - } - } - - if(values.empty()) - return 0; - - auto chan_node = fixt_node.create_child(chan.name.toStdString()); - auto chan_param - = std::make_unique(*chan_node, buffer, dmx_channel); - - chan_param->set_default_value(chan.defaultValue); - chan_param->set_value(chan.defaultValue); - auto& chan_param_ref = *chan_param; - - if(!comment.empty()) - ossia::net::set_description(*chan_node, comment); - - chan_node->set_parameter(std::move(chan_param)); - { - auto chan_enumnode = chan_node->create_child("preset"); - auto chan_enumparam = std::make_unique( - *chan_enumnode, chan_param_ref, values); - - auto& node = *chan_enumnode; - auto& p = *chan_enumparam; - - if(default_preset.empty()) - default_preset = values.front().first; - p.set_default_value(default_preset); - p.set_value(default_preset); - - if(!comment.empty()) - ossia::net::set_description(node, std::move(comment)); - - chan_enumnode->set_parameter(std::move(chan_enumparam)); - } - return 1; - } -}; -struct led_visitor -{ - ossia::net::node_base& fixt_node; - ossia::net::dmx_buffer& buffer; - int dmx_channel; - int operator()(const Artnet::LEDStripLayout& v) const noexcept - { - auto chan_param = std::make_unique( - fixt_node, buffer, dmx_channel, v.diodes.size(), v.length); - - fixt_node.set_parameter(std::move(chan_param)); - return v.channels(); - } - int operator()(const Artnet::LEDPaneLayout& v) const noexcept { SCORE_ABORT; } - int operator()(const Artnet::LEDVolumeLayout& v) const noexcept { SCORE_ABORT; } - int operator()(ossia::monostate) { SCORE_ABORT; } -}; - -static void addArtnetFixture( - ossia::net::generic_device& dev, ossia::net::dmx_buffer& buffer, - const Artnet::Fixture& fix, int channels_per_universe) -{ - int dmx_channel = 0; - int size = 0; - // For each fixture, we'll create a node. - auto fixt_node = dev.create_child(fix.fixtureName.toStdString()); - if(!fixt_node) - return; - - // For each channel, a sub-node that goes [0-255] or more depending on bit depth - for(auto& chan : fix.controls) - { - // Get the dmx offset of this channel: - const int channel_offset - = ossia::index_in_container(fix.mode.channelNames, chan.name); - if(channel_offset == -1) - continue; - dmx_channel = fix.address + channel_offset; - - // Then for each range-based subchannels, sub-nodes with the relevant domains. - fixture_setup_visitor vis{fix, chan, *fixt_node, buffer, dmx_channel}; - - size = ossia::visit(vis, chan.capabilities); - } - - if(fix.led) - { - dmx_channel = fix.address; - led_visitor vis{*fixt_node, buffer, dmx_channel}; - - size = ossia::visit(vis, fix.led); - } - - int max = dmx_channel + size; - int max_universe_count = 1 + max / channels_per_universe; - if(buffer.universes() < max_universe_count) - buffer.set_universe_count(max_universe_count); -} -} bool ArtnetDevice::reconnect() { disconnect(); @@ -231,146 +42,26 @@ bool ArtnetDevice::reconnect() { const auto& set = m_settings.deviceSpecificSettings.value(); - // Convert the settings to the ossia format - ossia::net::dmx_config conf; - conf.autocreate = ossia::net::dmx_config::no_auto; - if(set.fixtures.empty()) - { - if(set.transport == ArtnetSpecificSettings::ArtNet_MultiUniverse - || set.transport == ArtnetSpecificSettings::E131_MultiUniverse) - { - conf.autocreate = ossia::net::dmx_config::just_universes; - } - else if(set.transport == ArtnetSpecificSettings::ArtNet) - { - conf.autocreate = ossia::net::dmx_config::channel_index; - } - else - { - conf.autocreate = ossia::net::dmx_config::just_index; - } - } - conf.frequency = set.rate; - conf.universe = set.universe; - conf.multicast = set.multicast; - conf.channels_per_universe = set.channels_per_universe; - conf.mode = set.mode == ArtnetSpecificSettings::Source - ? ossia::net::dmx_config::source - : ossia::net::dmx_config::sink; - - // Create the protocol - std::unique_ptr artnet_proto; - switch(set.transport) - { - case ArtnetSpecificSettings::ArtNet: - case ArtnetSpecificSettings::ArtNetV2: - case ArtnetSpecificSettings::ArtNet_MultiUniverse: { - auto host = set.host.toStdString(); - if(host.empty()) - host = "0.0.0.0"; - - if(set.mode == ArtnetSpecificSettings::Source) - artnet_proto - = std::make_unique(m_ctx, conf, host); - else - artnet_proto - = std::make_unique(m_ctx, conf, host); - break; - } - case ArtnetSpecificSettings::E131: - case ArtnetSpecificSettings::E131_MultiUniverse: { - auto host = set.host.toStdString(); - if(host.empty()) - host = "0.0.0.0"; - - if(set.mode == ArtnetSpecificSettings::Source) - { - ossia::net::outbound_socket_configuration sock_conf; - sock_conf.host = host; - sock_conf.port = ossia::net::e131_protocol::default_port; - - artnet_proto - = std::make_unique(m_ctx, conf, sock_conf); - } - else - { - ossia::net::inbound_socket_configuration sock_conf; - sock_conf.bind = host; - sock_conf.port = ossia::net::e131_protocol::default_port; - - artnet_proto = std::make_unique( - m_ctx, conf, sock_conf); - } - - break; - } - case ArtnetSpecificSettings::DMXUSBPRO: - case ArtnetSpecificSettings::DMXUSBPRO_Mk2: - case ArtnetSpecificSettings::OpenDMX_USB: { - ossia::net::serial_configuration sock_conf; - - for(auto& p : QSerialPortInfo::availablePorts()) - { - if(p.portName() == set.host) - { - sock_conf.port = p.systemLocation().toStdString(); - break; - } - } - if(sock_conf.port.empty()) - sock_conf.port = set.host.toStdString(); - - int version = -1; - switch(set.transport) - { - case ArtnetSpecificSettings::DMXUSBPRO: - version = 1; - sock_conf.baud_rate = 115200; - sock_conf.stop_bits = ossia::net::serial_configuration::two; - break; - - case ArtnetSpecificSettings::DMXUSBPRO_Mk2: - version = 2; - sock_conf.baud_rate = 115200; - sock_conf.stop_bits = ossia::net::serial_configuration::two; - break; - - case ArtnetSpecificSettings::OpenDMX_USB: - version = 3; - sock_conf.baud_rate = 250000; - sock_conf.stop_bits = ossia::net::serial_configuration::two; - break; - - default: - break; - } - - artnet_proto = std::make_unique( - m_ctx, conf, sock_conf, version); - break; - } - } - // Create the device - if(artnet_proto) + if(auto artnet_proto = Protocols::instantiateDMXProtocol(m_ctx, set)) { auto& proto = *artnet_proto; + SCORE_ASSERT(proto.buffer().universes() >= 1); + auto dev = std::make_unique( std::move(artnet_proto), settings().name.toStdString()); - if(!set.fixtures.empty()) - SCORE_ASSERT(proto.buffer().universes() == 1); for(auto& fixt : set.fixtures) { - addArtnetFixture(*dev, proto.buffer(), fixt, conf.channels_per_universe); + addArtnetFixture(*dev, proto.buffer(), fixt, set.channels_per_universe); } if(set.mode == ArtnetSpecificSettings::Sink) if(auto p = dynamic_cast(&proto)) p->create_channel_map(); m_dev = std::move(dev); + deviceChanged(nullptr, m_dev.get()); } - deviceChanged(nullptr, m_dev.get()); } catch(const std::runtime_error& e) { diff --git a/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetDevice.hpp b/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetDevice.hpp index 2cd9cbcbfd..57e90a9a47 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetDevice.hpp +++ b/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetDevice.hpp @@ -7,7 +7,6 @@ namespace Protocols { class ArtnetDevice final : public Device::OwningDeviceInterface { - W_OBJECT(ArtnetDevice) public: ArtnetDevice( diff --git a/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetProtocolSettingsWidget.cpp b/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetProtocolSettingsWidget.cpp index 386632d5e2..b9a9fd4ade 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetProtocolSettingsWidget.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetProtocolSettingsWidget.cpp @@ -45,14 +45,16 @@ ArtnetProtocolSettingsWidget::ArtnetProtocolSettingsWidget(QWidget* parent) m_universe = new QSpinBox{this}; m_universe->setRange(0, 65539); + m_universe_count = new QSpinBox{this}; + m_universe_count->setRange(0, 1024); + m_channels_per_universe = new QSpinBox{this}; m_channels_per_universe->setRange(1, 512); m_channels_per_universe->setValue(512); m_transport = new QComboBox{this}; m_transport->addItems( - {"ArtNet", "ArtNet (16 universes)", "E1.31 / sACN", "E1.31 / sACN (16 universes)", - "DMX USB PRO", "DMX USB PRO Mk2", "OpenDMX USB"}); + {"ArtNet", "E1.31 / sACN", "DMX USB PRO", "DMX USB PRO Mk2", "OpenDMX USB"}); checkForChanges(m_transport); m_multicast = new QCheckBox{this}; @@ -71,6 +73,7 @@ ArtnetProtocolSettingsWidget::ArtnetProtocolSettingsWidget(QWidget* parent) layout->addRow(tr("Name"), m_deviceNameEdit); layout->addRow(tr("Rate (Hz)"), m_rate); layout->addRow(tr("Universe"), m_universe); + layout->addRow(tr("Universe count"), m_universe_count); layout->addRow(tr("Channels in universe"), m_channels_per_universe); layout->addRow(tr("Transport"), m_transport); layout->addRow(tr("Interface / Host"), m_host); @@ -126,12 +129,15 @@ ArtnetProtocolSettingsWidget::ArtnetProtocolSettingsWidget(QWidget* parent) dial->setName(newFixtureName(dial->name())); if(dial->exec() == QDialog::Accepted) { - auto fixt = dial->fixture(); - if(!fixt.fixtureName.isEmpty() && !fixt.controls.empty()) + for(auto fixt : dial->fixtures()) { - m_fixtures.push_back(fixt); - updateTable(); + m_fixtures.push_back(std::move(fixt)); + while(m_fixtures.back().address > this->m_channels_per_universe->value()) + { + // FIXME UPDATE UNIVERSES MODULO + } } + updateTable(); } }); @@ -155,10 +161,35 @@ ArtnetProtocolSettingsWidget::ArtnetProtocolSettingsWidget(QWidget* parent) QString ArtnetProtocolSettingsWidget::newFixtureName(QString name) { - std::vector brethren; + static std::vector brethren; + brethren.clear(); + brethren.reserve(m_fixtures.size()); + for(auto& strip : this->m_fixtures) brethren.push_back(strip.fixtureName); - return ossia::net::sanitize_name(name, brethren); + return ossia::net::sanitize_name(std::move(name), brethren); +} + +static QList serialPorts() +{ +#if defined(__APPLE__) || defined(_WIN32) + return QSerialPortInfo::availablePorts(); +#else + auto ports = QSerialPortInfo::availablePorts(); + std::sort( + ports.begin(), ports.end(), + [](const QSerialPortInfo& a, const QSerialPortInfo& b) { + const auto& lhs = a.portName(); + const auto& rhs = b.portName(); + bool lhs_usb = lhs.contains("USB") || lhs.contains("ACM"); + bool rhs_usb = rhs.contains("USB") || rhs.contains("ACM"); + if(lhs_usb == rhs_usb) + return lhs < rhs; + else + return (lhs_usb && !rhs_usb); + }); + return ports; +#endif } void ArtnetProtocolSettingsWidget::updateHosts(int idx) @@ -166,35 +197,54 @@ void ArtnetProtocolSettingsWidget::updateHosts(int idx) m_host->clear(); switch(idx) { - case 0: - case 1: { + case 0: { auto ips = score::list_ipv4(); ips.removeAll("0.0.0.0"); - m_host->addItems(ips); + m_host->addItems(ips); m_host->setCurrentIndex(0); - m_universe->setRange(0, 16); - m_multicast->setDisabled(true); + + m_universe->setRange(0, 256); + m_universe->setValue(0); + m_universe->setEnabled(true); + m_universe_count->setValue(std::clamp(m_universe_count->value(), 0, 256)); + m_universe_count->setEnabled(true); + m_multicast->setEnabled(false); break; } - case 2: - case 3: + case 1: m_host->addItems(score::list_ipv4()); m_host->setCurrentIndex(0); + m_universe->setRange(1, 65539); + m_universe->setValue(1); + m_universe->setEnabled(true); + m_universe_count->setValue(1); + m_universe_count->setEnabled(true); m_multicast->setEnabled(true); break; + case 2: case 4: { - m_multicast->setDisabled(true); - m_universe->setRange(0, 0); - for(const auto& port : QSerialPortInfo::availablePorts()) + for(const auto& port : serialPorts()) m_host->addItem(port.portName()); + + m_universe->setRange(0, 0); + m_universe->setValue(0); + m_universe->setEnabled(false); + m_universe_count->setValue(1); + m_universe_count->setEnabled(false); + m_multicast->setEnabled(false); break; } - case 5: { - m_multicast->setDisabled(true); - m_universe->setRange(0, 1); - for(const auto& port : QSerialPortInfo::availablePorts()) + case 3: { + for(const auto& port : serialPorts()) m_host->addItem(port.portName()); + + m_universe->setRange(0, 1); + m_universe->setValue(0); + m_universe->setEnabled(true); + m_universe_count->setValue(std::clamp(m_universe_count->value(), 0, 2)); + m_universe_count->setEnabled(true); + m_multicast->setEnabled(false); break; } } @@ -249,6 +299,11 @@ void ArtnetProtocolSettingsWidget::updateTable() ArtnetProtocolSettingsWidget::~ArtnetProtocolSettingsWidget() { } +std::pair ArtnetProtocolSettingsWidget::universeRange() const noexcept +{ + return {m_universe->value(), m_universe->value() + m_universe_count->value() - 1}; +} + Device::DeviceSettings ArtnetProtocolSettingsWidget::getSettings() const { // TODO should be = m_settings to follow the other patterns. @@ -265,27 +320,22 @@ Device::DeviceSettings ArtnetProtocolSettingsWidget::getSettings() const settings.transport = ArtnetSpecificSettings::ArtNetV2; break; case 1: - settings.transport = ArtnetSpecificSettings::ArtNet_MultiUniverse; - break; - case 2: settings.transport = ArtnetSpecificSettings::E131; break; - case 3: - settings.transport = ArtnetSpecificSettings::E131_MultiUniverse; - break; - case 4: + case 2: settings.transport = ArtnetSpecificSettings::DMXUSBPRO; break; - case 5: + case 3: settings.transport = ArtnetSpecificSettings::DMXUSBPRO_Mk2; break; - case 6: + case 4: settings.transport = ArtnetSpecificSettings::OpenDMX_USB; break; } settings.rate = this->m_rate->value(); - settings.universe = this->m_universe->value(); + settings.start_universe = this->m_universe->value(); + settings.universe_count = this->m_universe_count->value(); settings.channels_per_universe = this->m_channels_per_universe->value(); settings.multicast = this->m_multicast->isChecked(); settings.mode = this->m_source->isChecked() ? ArtnetSpecificSettings::Source @@ -307,33 +357,31 @@ void ArtnetProtocolSettingsWidget::setSettings(const Device::DeviceSettings& set case ArtnetSpecificSettings::ArtNetV2: m_transport->setCurrentIndex(0); break; - case ArtnetSpecificSettings::ArtNet_MultiUniverse: - m_transport->setCurrentIndex(1); - break; case ArtnetSpecificSettings::E131: - m_transport->setCurrentIndex(2); - break; - case ArtnetSpecificSettings::E131_MultiUniverse: - m_transport->setCurrentIndex(3); + m_transport->setCurrentIndex(1); break; case ArtnetSpecificSettings::DMXUSBPRO: - m_transport->setCurrentIndex(4); + m_transport->setCurrentIndex(2); break; case ArtnetSpecificSettings::DMXUSBPRO_Mk2: - m_transport->setCurrentIndex(5); + m_transport->setCurrentIndex(3); break; case ArtnetSpecificSettings::OpenDMX_USB: - m_transport->setCurrentIndex(6); + m_transport->setCurrentIndex(4); break; } + updateHosts(m_transport->currentIndex()); + m_rate->setValue(specif.rate); - m_universe->setValue(specif.universe); + m_universe->setValue(specif.start_universe); + m_universe_count->setValue(specif.universe_count); m_channels_per_universe->setValue(specif.channels_per_universe); - m_host->setCurrentText(specif.host); + if(!specif.host.isEmpty()) + m_host->setCurrentText(specif.host); + else + m_host->setCurrentIndex(0); m_multicast->setChecked(specif.multicast); - if(m_host->currentText().isEmpty()) - updateHosts(m_transport->currentIndex()); if(specif.mode == ArtnetSpecificSettings::Source) m_source->setChecked(true); diff --git a/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetProtocolSettingsWidget.hpp b/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetProtocolSettingsWidget.hpp index 331e92f213..4320946d41 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetProtocolSettingsWidget.hpp +++ b/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetProtocolSettingsWidget.hpp @@ -28,6 +28,8 @@ class ArtnetProtocolSettingsWidget final : public Device::ProtocolSettingsWidget Device::DeviceSettings getSettings() const override; void setSettings(const Device::DeviceSettings& settings) override; + std::pair universeRange() const noexcept; + private: void updateHosts(int protocolindex); void updateTable(); @@ -37,6 +39,7 @@ class ArtnetProtocolSettingsWidget final : public Device::ProtocolSettingsWidget QComboBox* m_host{}; QSpinBox* m_rate{}; QSpinBox* m_universe{}; + QSpinBox* m_universe_count{}; QSpinBox* m_channels_per_universe{}; QComboBox* m_transport{}; QCheckBox* m_multicast{}; diff --git a/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetSpecificSettings.hpp b/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetSpecificSettings.hpp index 8ee2978abe..63d05357f4 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetSpecificSettings.hpp +++ b/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetSpecificSettings.hpp @@ -103,6 +103,7 @@ struct Fixture std::vector controls; LEDLayout led; int address{}; + int universe{}; }; } @@ -111,8 +112,9 @@ struct ArtnetSpecificSettings std::vector fixtures; QString host; - int rate{20}; - int universe{1}; + int rate{44}; + int start_universe{1}; + int universe_count{1}; int channels_per_universe{512}; bool multicast{}; @@ -123,9 +125,7 @@ struct ArtnetSpecificSettings DMXUSBPRO, ArtNetV2, // Artnet:/{} DMXUSBPRO_Mk2, - OpenDMX_USB, - ArtNet_MultiUniverse, - E131_MultiUniverse + OpenDMX_USB } transport{ArtNetV2}; enum { diff --git a/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetSpecificSettingsSerialization.cpp b/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetSpecificSettingsSerialization.cpp index e27676ac59..3cba2ae7eb 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetSpecificSettingsSerialization.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/Artnet/ArtnetSpecificSettingsSerialization.cpp @@ -255,14 +255,16 @@ void JSONWriter::write(Protocols::Artnet::ModeInfo& n) template <> void DataStreamReader::read(const Protocols::Artnet::Fixture& n) { - m_stream << n.fixtureName << n.modeName << n.mode << n.controls << n.led << n.address; + m_stream << n.fixtureName << n.modeName << n.mode << n.controls << n.led << n.address + << n.universe; insertDelimiter(); } template <> void DataStreamWriter::write(Protocols::Artnet::Fixture& n) { - m_stream >> n.fixtureName >> n.modeName >> n.mode >> n.controls >> n.led >> n.address; + m_stream >> n.fixtureName >> n.modeName >> n.mode >> n.controls >> n.led >> n.address + >> n.universe; checkDelimiter(); } @@ -278,6 +280,7 @@ void JSONReader::read(const Protocols::Artnet::Fixture& n) obj["Channels"] = n.controls; else if(n.led) obj["LED"] = n.led; + obj["Universe"] = n.universe; stream.EndObject(); } @@ -289,6 +292,10 @@ void JSONWriter::write(Protocols::Artnet::Fixture& n) if(auto mi = obj.tryGet("ModeInfo")) n.mode <<= *mi; n.address <<= obj["Address"]; + if(auto u = obj.tryGet("Universe")) + n.universe = u->toInt(); + else + n.universe = -1; if(auto ctls = obj.tryGet("Channels")) n.controls <<= *ctls; else if(auto led = obj.tryGet("LED")) @@ -298,16 +305,16 @@ void JSONWriter::write(Protocols::Artnet::Fixture& n) template <> void DataStreamReader::read(const Protocols::ArtnetSpecificSettings& n) { - m_stream << n.fixtures << n.host << n.rate << n.universe << n.channels_per_universe - << n.multicast << n.transport << n.mode; + m_stream << n.fixtures << n.host << n.rate << n.start_universe << n.universe_count + << n.channels_per_universe << n.multicast << n.transport << n.mode; insertDelimiter(); } template <> void DataStreamWriter::write(Protocols::ArtnetSpecificSettings& n) { - m_stream >> n.fixtures >> n.host >> n.rate >> n.universe >> n.channels_per_universe - >> n.multicast >> n.transport >> n.mode; + m_stream >> n.fixtures >> n.host >> n.rate >> n.start_universe >> n.universe_count + >> n.channels_per_universe >> n.multicast >> n.transport >> n.mode; checkDelimiter(); } @@ -317,7 +324,8 @@ void JSONReader::read(const Protocols::ArtnetSpecificSettings& n) obj["Fixtures"] = n.fixtures; obj["Host"] = n.host; obj["Rate"] = n.rate; - obj["Universe"] = n.universe; + obj["Universe"] = n.start_universe; + obj["UniverseCount"] = n.universe_count; obj["ChannelsPerUniverse"] = n.channels_per_universe; if(n.multicast) obj["Multicast"] = n.multicast; @@ -334,7 +342,9 @@ void JSONWriter::write(Protocols::ArtnetSpecificSettings& n) n.host = QString::fromStdString(u->toStdString()); n.rate <<= obj["Rate"]; if(auto u = obj.tryGet("Universe")) - n.universe = u->toInt(); + n.start_universe = u->toInt(); + if(auto u = obj.tryGet("UniverseCount")) + n.universe_count = u->toInt(); if(auto u = obj.tryGet("ChannelsPerUniverse")) n.channels_per_universe = u->toInt(); if(auto u = obj.tryGet("Multicast")) @@ -343,5 +353,12 @@ void JSONWriter::write(Protocols::ArtnetSpecificSettings& n) n.transport = (decltype(n.transport))u->toInt(); if(auto u = obj.tryGet("Mode")) n.mode = (decltype(n.mode))u->toInt(); + + // score 3.4.2: fixtures can be in multiple universes + for(auto& fix : n.fixtures) + { + if(fix.universe == -1) + fix.universe = n.start_universe; + } } #endif diff --git a/src/plugins/score-plugin-protocols/Protocols/Artnet/DMXFixtureInstantiation.cpp b/src/plugins/score-plugin-protocols/Protocols/Artnet/DMXFixtureInstantiation.cpp new file mode 100644 index 0000000000..d781b69fda --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Artnet/DMXFixtureInstantiation.cpp @@ -0,0 +1,221 @@ +#include + +#if defined(OSSIA_PROTOCOL_ARTNET) +#include "DMXFixtureInstantiation.hpp" + +#include + +#include +#include +#include + +namespace Protocols +{ + +namespace +{ +struct fixture_setup_visitor +{ + const Artnet::Fixture& fix; + const Artnet::Channel& chan; + ossia::net::node_base& fixt_node; + ossia::net::dmx_buffer& buffer; + int dmx_channel; + int operator()(const Artnet::SingleCapability& v) const noexcept + { + auto chan_node = fixt_node.create_child(chan.name.toStdString()); + auto chan_param + = std::make_unique(*chan_node, buffer, dmx_channel); + + auto& node = *chan_node; + auto& p = *chan_param; + + // FIXME this only works if the channels are joined for now + int bytes = 1; + + for(auto& name : chan.fineChannels) + { + if(ossia::contains(fix.mode.channelNames, name)) + { + bytes++; + } + } + p.m_bytes = bytes; + + chan_node->set_parameter(std::move(chan_param)); + p.set_default_value(chan.defaultValue); + p.set_value(chan.defaultValue); + + if(!v.comment.isEmpty()) + ossia::net::set_description(node, v.comment.toStdString()); + + return bytes; + } + + int operator()(const std::vector& v) const noexcept + { + std::vector> values; + std::string comment; + std::string default_preset; + + // Parse all capabilities + for(auto& capa : v) + { + std::string name; + if(!capa.effectName.isEmpty()) + name = capa.effectName.toStdString(); + else + name = capa.type.toStdString(); + + if(chan.defaultValue >= capa.range.first && chan.defaultValue < capa.range.second) + default_preset = name; + values.push_back({name, capa.range.first}); + } + + // Make sure that all values have an unique name + { + std::vector counts; + for(int i = 0; i < std::ssize(values); i++) + { + int n = 1; + for(int j = 0; j < i; j++) + { + if(values[j].first == values[i].first) + n++; + } + counts.push_back(n); + } + for(int i = 0; i < std::ssize(values); i++) + { + if(counts[i] > 1) + values[i].first += fmt::format(" {}", counts[i]); + } + } + + // Write the comment + for(int i = 0; i < std::ssize(values); i++) + { + if(!v[i].comment.isEmpty()) + { + comment += values[i].first + ": " + v[i].comment.toStdString() + "\n"; + } + } + + if(values.empty()) + return 0; + + auto chan_node = fixt_node.create_child(chan.name.toStdString()); + auto chan_param + = std::make_unique(*chan_node, buffer, dmx_channel); + + chan_param->set_default_value(chan.defaultValue); + chan_param->set_value(chan.defaultValue); + auto& chan_param_ref = *chan_param; + + if(!comment.empty()) + ossia::net::set_description(*chan_node, comment); + + chan_node->set_parameter(std::move(chan_param)); + { + auto chan_enumnode = chan_node->create_child("preset"); + auto chan_enumparam = std::make_unique( + *chan_enumnode, chan_param_ref, values); + + auto& node = *chan_enumnode; + auto& p = *chan_enumparam; + + if(default_preset.empty()) + default_preset = values.front().first; + p.set_default_value(default_preset); + p.set_value(default_preset); + + if(!comment.empty()) + ossia::net::set_description(node, std::move(comment)); + + chan_enumnode->set_parameter(std::move(chan_enumparam)); + } + return 1; + } +}; + +struct led_visitor +{ + ossia::net::node_base& fixt_node; + ossia::net::dmx_buffer& buffer; + int dmx_channel; + + int operator()(const Artnet::LEDStripLayout& v) const noexcept + { + auto chan_param = std::make_unique( + fixt_node, buffer, dmx_channel, v.diodes.size(), v.length); + + fixt_node.set_parameter(std::move(chan_param)); + return v.channels(); + } + + int operator()(const Artnet::LEDPaneLayout& v) const noexcept + { + auto chan_param = std::make_unique( + fixt_node, buffer, dmx_channel, v.diodes.size(), v.width * v.height); + + fixt_node.set_parameter(std::move(chan_param)); + return v.channels(); + } + + int operator()(const Artnet::LEDVolumeLayout& v) const noexcept + { + auto chan_param = std::make_unique( + fixt_node, buffer, dmx_channel, v.diodes.size(), v.width * v.height * v.depth); + + fixt_node.set_parameter(std::move(chan_param)); + return v.channels(); + } + + int operator()(ossia::monostate) { return 0; } +}; + +} + +void addArtnetFixture( + ossia::net::generic_device& dev, ossia::net::dmx_buffer& buffer, + const Artnet::Fixture& fix, int channels_per_universe) +{ + int dmx_channel = 0; + int size = 0; + // For each fixture, we'll create a node. + auto fixt_node = dev.create_child(fix.fixtureName.toStdString()); + if(!fixt_node) + return; + + // For each channel, a sub-node that goes [0-255] or more depending on bit depth + for(auto& chan : fix.controls) + { + // Get the dmx offset of this channel: + const int channel_offset + = ossia::index_in_container(fix.mode.channelNames, chan.name); + if(channel_offset == -1) + continue; + dmx_channel = fix.address + channel_offset; + + // Then for each range-based subchannels, sub-nodes with the relevant domains. + fixture_setup_visitor vis{fix, chan, *fixt_node, buffer, dmx_channel}; + + size = ossia::visit(vis, chan.capabilities); + } + + if(fix.led) + { + dmx_channel = fix.address; + led_visitor vis{*fixt_node, buffer, dmx_channel}; + + size = ossia::visit(vis, fix.led); + } + + int max = dmx_channel + size; + int max_universe_count = 1 + max / channels_per_universe; + if(buffer.universes() < max_universe_count) + buffer.set_universe_count(max_universe_count); +} + +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/Artnet/DMXFixtureInstantiation.hpp b/src/plugins/score-plugin-protocols/Protocols/Artnet/DMXFixtureInstantiation.hpp new file mode 100644 index 0000000000..86c0ed4b97 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Artnet/DMXFixtureInstantiation.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +#if defined(OSSIA_PROTOCOL_ARTNET) + +namespace ossia::net +{ +class generic_device; +struct dmx_buffer; +} +namespace Protocols +{ +namespace Artnet +{ +struct Fixture; +} +void addArtnetFixture( + ossia::net::generic_device& dev, ossia::net::dmx_buffer& buffer, + const Artnet::Fixture& fix, int channels_per_universe); +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/Artnet/DMXProtocolCreation.cpp b/src/plugins/score-plugin-protocols/Protocols/Artnet/DMXProtocolCreation.cpp new file mode 100644 index 0000000000..38a4a64bbb --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Artnet/DMXProtocolCreation.cpp @@ -0,0 +1,170 @@ +#include + +#if defined(OSSIA_PROTOCOL_ARTNET) +#include "DMXProtocolCreation.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +namespace Protocols +{ + +std::unique_ptr instantiateDMXProtocol( + const ossia::net::network_context_ptr& ctx, const ArtnetSpecificSettings& set) +{ + // Convert the settings to the ossia format + ossia::net::dmx_config conf; + conf.autocreate = ossia::net::dmx_config::no_auto; + if(set.fixtures.empty()) + { + if(set.universe_count > 1) + { + conf.autocreate = ossia::net::dmx_config::just_universes; + } + else if(set.transport == ArtnetSpecificSettings::ArtNet) + { + conf.autocreate = ossia::net::dmx_config::channel_index; + } + else + { + conf.autocreate = ossia::net::dmx_config::just_index; + } + } + conf.frequency = set.rate; + conf.start_universe = set.start_universe; + conf.universe_count = set.universe_count; + conf.multicast = set.multicast; + conf.channels_per_universe = set.channels_per_universe; + conf.mode = set.mode == ArtnetSpecificSettings::Source ? ossia::net::dmx_config::source + : ossia::net::dmx_config::sink; + + // Create the protocol + std::unique_ptr artnet_proto; + + switch(set.transport) + { + case ArtnetSpecificSettings::ArtNet: + case ArtnetSpecificSettings::ArtNetV2: { + auto host = set.host.toStdString(); + if(host.empty()) + host = "0.0.0.0"; + + bool good = false; + for(auto iface : QNetworkInterface::allInterfaces()) + { + for(const auto& entry : iface.addressEntries()) + { + if(entry.ip().protocol() == QHostAddress::IPv4Protocol) + if(host == entry.ip().toString()) + { + host = entry.broadcast().toString().toStdString(); + good = true; + break; + } + } + if(good) + break; + } + + if(set.mode == ArtnetSpecificSettings::Source) + { + + ossia::net::outbound_socket_configuration sock_conf; + sock_conf.host = host; + sock_conf.port = 6454; + artnet_proto + = std::make_unique(ctx, conf, sock_conf); + } + else + { + + ossia::net::inbound_socket_configuration sock_conf; + sock_conf.bind = host; + sock_conf.port = 6454; + artnet_proto + = std::make_unique(ctx, conf, sock_conf); + } + break; + } + case ArtnetSpecificSettings::E131: { + auto host = set.host.toStdString(); + if(host.empty()) + host = "0.0.0.0"; + + if(set.mode == ArtnetSpecificSettings::Source) + { + ossia::net::outbound_socket_configuration sock_conf; + sock_conf.host = host; + sock_conf.port = ossia::net::e131_protocol::default_port; + + artnet_proto = std::make_unique(ctx, conf, sock_conf); + } + else + { + ossia::net::inbound_socket_configuration sock_conf; + sock_conf.bind = host; + sock_conf.port = ossia::net::e131_protocol::default_port; + + artnet_proto + = std::make_unique(ctx, conf, sock_conf); + } + + break; + } + case ArtnetSpecificSettings::DMXUSBPRO: + case ArtnetSpecificSettings::DMXUSBPRO_Mk2: + case ArtnetSpecificSettings::OpenDMX_USB: { + ossia::net::serial_configuration sock_conf; + + for(auto& p : QSerialPortInfo::availablePorts()) + { + if(p.portName() == set.host) + { + sock_conf.port = p.systemLocation().toStdString(); + break; + } + } + if(sock_conf.port.empty()) + sock_conf.port = set.host.toStdString(); + + int version = -1; + switch(set.transport) + { + case ArtnetSpecificSettings::DMXUSBPRO: + version = 1; + sock_conf.baud_rate = 115200; + sock_conf.stop_bits = ossia::net::serial_configuration::two; + break; + + case ArtnetSpecificSettings::DMXUSBPRO_Mk2: + version = 2; + sock_conf.baud_rate = 115200; + sock_conf.stop_bits = ossia::net::serial_configuration::two; + break; + + case ArtnetSpecificSettings::OpenDMX_USB: + version = 3; + sock_conf.baud_rate = 250000; + sock_conf.stop_bits = ossia::net::serial_configuration::two; + break; + + default: + break; + } + + artnet_proto = std::make_unique( + ctx, conf, sock_conf, version); + break; + } + } + + return artnet_proto; +} +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/Artnet/DMXProtocolCreation.hpp b/src/plugins/score-plugin-protocols/Protocols/Artnet/DMXProtocolCreation.hpp new file mode 100644 index 0000000000..12d0997112 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Artnet/DMXProtocolCreation.hpp @@ -0,0 +1,14 @@ +#pragma once +#include + +#if defined(OSSIA_PROTOCOL_ARTNET) +#include + +#include + +namespace Protocols +{ +std::unique_ptr instantiateDMXProtocol( + const ossia::net::network_context_ptr& ctx, const ArtnetSpecificSettings& set); +} +#endif diff --git a/src/plugins/score-plugin-protocols/Protocols/Artnet/FixtureDialog.cpp b/src/plugins/score-plugin-protocols/Protocols/Artnet/FixtureDialog.cpp index de60f454ed..ae400417c3 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Artnet/FixtureDialog.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/Artnet/FixtureDialog.cpp @@ -1124,11 +1124,18 @@ AddFixtureDialog::AddFixtureDialog(ArtnetProtocolSettingsWidget& parent) updateParameters(newFixt); }; + m_count.setRange(1, 512); + m_spacing.setRange(0, 512); m_address.setRange(1, 512); + auto [umin, umax] = parent.universeRange(); + m_universe.setRange(umin, umax); m_setupLayoutContainer.addLayout(&m_setupLayout); m_setupLayout.addRow(tr("Name"), &m_name); + m_setupLayout.addRow(tr("Count"), &m_count); m_setupLayout.addRow(tr("Address"), &m_address); + m_setupLayout.addRow(tr("Spacing"), &m_spacing); + m_setupLayout.addRow(tr("Universe"), &m_universe); m_setupLayout.addRow(tr("Mode"), &m_mode); m_setupLayout.addRow(tr("Channels"), &m_content); m_setupLayoutContainer.addStretch(0); @@ -1174,25 +1181,36 @@ QSize AddFixtureDialog::sizeHint() const return QSize{800, 600}; } -Artnet::Fixture AddFixtureDialog::fixture() const noexcept +std::vector AddFixtureDialog::fixtures() const noexcept { - Artnet::Fixture f; + std::vector res; + if(!m_currentFixture) - return f; + return res; int mode_index = m_mode.currentIndex(); if(!ossia::valid_index(mode_index, m_currentFixture->modes)) - return f; + return res; auto& mode = m_currentFixture->modes[mode_index]; + Artnet::Fixture f; f.fixtureName = m_name.text(); + f.controls = mode.channels; + if(f.fixtureName.isEmpty() || f.controls.empty()) + return res; + f.modeName = m_mode.currentText(); f.mode.channelNames = mode.allChannels; - f.address = m_address.value() - 1; - f.controls = mode.channels; - return f; + for(int i = 0; i < m_count.value(); i++) + { + f.address + = m_address.value() - 1 + i * (f.mode.channelNames.size() + m_spacing.value()); + res.push_back(f); + } + + return res; } } #endif diff --git a/src/plugins/score-plugin-protocols/Protocols/Artnet/FixtureDialog.hpp b/src/plugins/score-plugin-protocols/Protocols/Artnet/FixtureDialog.hpp index a491452215..3910a5749b 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Artnet/FixtureDialog.hpp +++ b/src/plugins/score-plugin-protocols/Protocols/Artnet/FixtureDialog.hpp @@ -33,7 +33,7 @@ class AddFixtureDialog : public QDialog void setName(QString t) { m_name.setText(t); } QSize sizeHint() const override; - Artnet::Fixture fixture() const noexcept; + std::vector fixtures() const noexcept; private: QHBoxLayout m_layout; @@ -42,7 +42,10 @@ class AddFixtureDialog : public QDialog QVBoxLayout m_setupLayoutContainer; QFormLayout m_setupLayout; State::AddressFragmentLineEdit m_name; + QSpinBox m_count; + QSpinBox m_spacing; QSpinBox m_address; + QSpinBox m_universe; QComboBox m_mode; QLabel m_content; QDialogButtonBox m_buttons;