140 using ChannelMap = ossia::hash_map<QString, Artnet::Channel>;
147 std::vector<Wheel> wheels{};
148 std::vector<FixtureMode> modes{};
150 static std::vector<Wheel> loadWheels(
const rapidjson::Value& value)
noexcept
152 std::vector<Wheel> wheels{};
153 if(!value.IsObject())
156 for(
auto w_it = value.MemberBegin(); w_it != value.MemberEnd(); ++w_it)
158 if(!w_it->value.IsObject())
162 wh.name = w_it->name.GetString();
164 const auto& w = w_it->value.GetObject();
165 if(
auto s_it = w.FindMember(
"slots"); s_it != w.MemberEnd())
167 if(!s_it->value.IsArray())
170 for(
const auto& s : s_it->value.GetArray())
176 if(
auto nm_it = s.FindMember(
"name"); nm_it != s.MemberEnd())
178 if(nm_it->value.IsString())
179 ws.name = nm_it->value.GetString();
181 if(
auto t_it = s.FindMember(
"type"); t_it != s.MemberEnd())
183 if(t_it->value.IsString())
184 ws.type = t_it->value.GetString();
186 wh.wheelslots.push_back(std::move(ws));
190 wheels.push_back(std::move(wh));
197 static std::function<bool(
int)> constraint_pos(std::string_view cst)
noexcept
201 return [](
int p) {
return true; };
204 return [](
int p) {
return (p % 2) == 0; };
205 else if(cst ==
"odd")
206 return [](
int p) {
return (p % 2) == 1; };
207 else if(
auto [whole, n] = leq_rex(cst); whole)
208 return [num = n.to_number()](
int p) {
return p <= num; };
209 else if(
auto [whole, n] = geq_rex(cst); whole)
210 return [num = n.to_number()](
int p) {
return p >= num; };
211 else if(
auto [whole, n] = lst_rex(cst); whole)
212 return [num = n.to_number()](
int p) {
return p < num; };
213 else if(
auto [whole, n] = gst_rex(cst); whole)
214 return [num = n.to_number()](
int p) {
return p > num; };
215 else if(
auto [whole, n] = eq_rex(cst); whole)
216 return [num = n.to_number()](
int p) {
return p == num; };
217 else if(
auto [whole, n, r] = arith_rex(cst); whole)
219 if(
int num = n.to_number(); num > 0)
221 return [num, rem = r.to_number()](
int p) {
return (p % num) == rem; };
224 else if(
auto [whole, n] = arith1_rex(cst); whole)
226 if(
int num = n.to_number(); num > 0)
228 return [num](
int p) {
return (p % num) == 0; };
232 return [](
int p) {
return false; };
235 static std::function<bool(
const QString&)>
236 constraint_name(std::string_view cst)
noexcept
240 return [](
const QString& p) {
return true; };
242 return [rexp = std::make_shared<RE2>(re2::StringPiece(cst.data(), cst.size()))](
243 const QString& p) {
return RE2::FullMatch(p.toStdString(), *rexp); };
246 static std::vector<int> filter_constraints(
247 const std::vector<Pixel>& pixels,
const std::vector<std::string_view>& x_cst,
248 const std::vector<std::string_view>& y_cst,
249 const std::vector<std::string_view>& z_cst,
250 const std::vector<std::string_view>& name_cst)
noexcept
252 std::vector<int> matching;
253 std::vector<std::function<bool(
const Pixel&)>> filters;
256 if(
auto f = constraint_pos(c))
257 filters.push_back([f](
const Pixel& p) {
return f(p.x); });
259 if(
auto f = constraint_pos(c))
260 filters.push_back([f](
const Pixel& p) {
return f(p.y); });
262 if(
auto f = constraint_pos(c))
263 filters.push_back([f](
const Pixel& p) {
return f(p.z); });
264 for(
auto& c : name_cst)
265 if(
auto f = constraint_name(c))
266 filters.push_back([f](
const Pixel& p) {
return f(p.name); });
268 for(
int i = 0; i < pixels.size(); i++)
270 if(pixels[i].name.isEmpty())
273 bool filtered =
false;
274 for(
auto& func : filters)
275 if((filtered = !func(pixels[i])))
279 matching.push_back(i);
285 void loadMatrix(
const rapidjson::Value& mat)
287 using namespace std::literals;
289 if(
auto pk_it = mat.FindMember(
"pixelKeys");
290 pk_it != mat.MemberEnd() && pk_it->value.IsArray())
292 const auto& pk_z = pk_it->value.GetArray();
293 const int16_t dim_z = pk_z.Size();
294 for(int16_t z = 0; z < dim_z; ++z)
296 if(pk_z[z].IsArray())
298 const auto& pk_y = pk_z[z].GetArray();
299 const int16_t dim_y = pk_y.Size();
300 for(int16_t y = 0; y < dim_y; ++y)
302 if(pk_y[y].IsArray())
304 const auto& pk_x = pk_y[y].GetArray();
305 const int16_t dim_x = pk_x.Size();
306 for(int16_t x = 0; x < dim_x; ++x)
308 if(pk_x[x].IsString())
310 matrix.pixels.push_back(
311 Pixel{.x = x, .y = y, .z = z, .name = pk_x[x].GetString()});
319 else if(
auto pc_it = mat.FindMember(
"pixelCount");
320 pc_it != mat.MemberEnd() && pc_it->value.IsArray())
322 const auto& pc = pc_it->value.GetArray();
325 if(!pc[0].IsInt() || !pc[1].IsInt() || !pc[2].IsInt())
327 const int16_t dim_x = pc[0].GetInt();
328 const int16_t dim_y = pc[1].GetInt();
329 const int16_t dim_z = pc[2].GetInt();
331 matrix.pixels.reserve(dim_x * dim_y * dim_z);
333 auto get_name = (dim_y == 1 && dim_z == 1)
334 ? [](
int x,
int y,
int z) {
return QString::number(x + 1); }
337 int z) {
return QString(
"%1 %2").arg(x + 1).arg(y + 1); }
338 : [](
int x,
int y,
int z) {
339 return QString(
"%1 %2 %3").arg(x + 1).arg(y + 1).arg(z + 1);
341 for(int16_t x = 0; x < dim_x; x++)
342 for(int16_t y = 0; y < dim_y; y++)
343 for(int16_t z = 0; z < dim_z; z++)
344 matrix.pixels.push_back(
345 Pixel{.x = x, .y = y, .z = z, .name = get_name(x, y, z)});
348 if(matrix.pixels.empty())
352 if(
auto gg_it = mat.FindMember(
"pixelGroups");
353 gg_it != mat.MemberEnd() && gg_it->value.IsObject())
355 for(
auto g_it = gg_it->value.MemberBegin(); g_it != gg_it->value.MemberEnd();
359 gp.name = g_it->name.GetString();
361 auto& g = g_it->value;
365 if(g.GetString() ==
"all"sv)
368 gp.pixels.reserve(matrix.pixels.size());
369 for(std::size_t i = 0; i < matrix.pixels.size(); i++)
370 gp.pixels.push_back(i);
376 const auto& pxg = g.GetArray();
381 QString px_name = px.GetString();
382 auto px_it = ossia::find_if(
383 matrix.pixels, [=](
auto& pix) { return pix.name == px_name; });
384 if(px_it != matrix.pixels.end())
386 gp.pixels.push_back(std::distance(matrix.pixels.begin(), px_it));
391 else if(g.IsObject())
394 std::vector<std::string_view> x_cst, y_cst, z_cst, name_cst;
395 if(
auto x_cst_it = g.FindMember(
"x");
396 x_cst_it != g.MemberEnd() && x_cst_it->value.IsArray())
398 for(
const auto& v : x_cst_it->value.GetArray())
400 x_cst.push_back(v.GetString());
402 if(
auto y_cst_it = g.FindMember(
"y");
403 y_cst_it != g.MemberEnd() && y_cst_it->value.IsArray())
405 for(
const auto& v : y_cst_it->value.GetArray())
407 y_cst.push_back(v.GetString());
409 if(
auto z_cst_it = g.FindMember(
"z");
410 z_cst_it != g.MemberEnd() && z_cst_it->value.IsArray())
412 for(
const auto& v : z_cst_it->value.GetArray())
414 z_cst.push_back(v.GetString());
416 if(
auto n_cst_it = g.FindMember(
"name");
417 n_cst_it != g.MemberEnd() && n_cst_it->value.IsArray())
419 for(
const auto& v : n_cst_it->value.GetArray())
421 name_cst.push_back(v.GetString());
423 gp.pixels = filter_constraints(matrix.pixels, x_cst, y_cst, z_cst, name_cst);
426 matrix.groups.push_back(std::move(gp));
431 QString getWheelName(QString channelName,
int wheelSlot)
const noexcept
437 for(
auto& wh : this->wheels)
439 if(wh.name == channelName)
441 if(wheelSlot < std::ssize(wh.wheelslots))
443 const auto& w = wh.wheelslots[wheelSlot];
444 if(!w.name.isEmpty())
446 else if(!w.type.isEmpty())
456 ChannelMap loadChannels(
const rapidjson::Value& val)
459 for(
auto chan_it = val.MemberBegin(); chan_it != val.MemberEnd(); ++chan_it)
462 chan.name = chan_it->name.GetString();
463 auto& jchan = chan_it->value;
467 if(
auto default_it = jchan.FindMember(
"defaultValue");
468 default_it != jchan.MemberEnd())
470 if(default_it->value.IsNumber())
472 chan.defaultValue = default_it->value.GetDouble();
474 else if(default_it->value.IsString())
479 std::string_view str = default_it->value.GetString();
481 chan.defaultValue = 127;
485 if(
auto fineChannels_it = jchan.FindMember(
"fineChannelAliases");
486 fineChannels_it != jchan.MemberEnd())
488 const auto& fineChannels = fineChannels_it->value;
489 if(fineChannels.IsArray())
491 const auto& fc = fineChannels.GetArray();
496 chan.fineChannels.push_back(
497 QString::fromUtf8(val.GetString(), val.GetStringLength()));
501 chan.fineChannels.clear();
508 if(
auto capability_it = jchan.FindMember(
"capability");
509 capability_it != jchan.MemberEnd())
512 if(
auto effectname_it = capability_it->value.FindMember(
"effectName");
513 effectname_it != capability_it->value.MemberEnd())
514 cap.effectName = effectname_it->value.GetString();
516 if(
auto comment_it = capability_it->value.FindMember(
"comment");
517 comment_it != capability_it->value.MemberEnd())
518 cap.comment = comment_it->value.GetString();
520 cap.type = capability_it->value[
"type"].GetString();
521 chan.capabilities = std::move(cap);
523 else if(
auto capabilities_it = jchan.FindMember(
"capabilities");
524 capabilities_it != jchan.MemberEnd())
526 std::vector<Artnet::RangeCapability> caps;
527 for(
const auto& capa : capabilities_it->value.GetArray())
529 if(!capa.HasMember(
"type"))
532 QString type = capa[
"type"].GetString();
533 if(type !=
"NoFunction")
536 if(
auto effectname_it = capa.FindMember(
"effectName");
537 effectname_it != capa.MemberEnd())
539 cap.effectName = effectname_it->value.GetString();
541 else if(type.startsWith(
"Wheel"))
543 QString whName = chan.name;
544 if(
auto wh_it = capa.FindMember(
"wheel"); wh_it != capa.MemberEnd())
546 if(wh_it->value.IsString())
547 whName = wh_it->value.GetString();
550 if(
auto idx_it = capa.FindMember(
"slotNumber");
551 idx_it != capa.MemberEnd())
553 if(idx_it->value.IsInt())
555 cap.effectName = getWheelName(whName, idx_it->value.GetInt());
557 else if(idx_it->value.IsDouble())
559 double v = idx_it->value.GetDouble();
560 double lo = std::floor(v);
561 double hi = std::ceil(v);
562 cap.effectName = QString(
"Split %1 %2")
563 .arg(getWheelName(whName, lo))
564 .arg(getWheelName(whName, hi));
569 if(
auto comment_it = capa.FindMember(
"comment");
570 comment_it != capa.MemberEnd())
571 cap.comment = comment_it->value.GetString();
573 cap.type = std::move(type);
575 const auto& range_arr = capa[
"dmxRange"].GetArray();
576 cap.range = {range_arr[0].GetInt(), range_arr[1].GetInt()};
578 caps.push_back(std::move(cap));
582 chan.capabilities = std::move(caps);
586 channels[chan.name] = std::move(chan);
591 void addTemplateToMode(
592 FixtureMode& m,
const rapidjson::Value& repeatFor, std::string_view channelOrder,
593 const std::vector<QString>& templateChannels,
const ChannelMap& templates)
596 if(channelOrder !=
"perPixel")
599 auto addTemplates = [&m, &templates, &templateChannels](
const QString& pixelKey) {
600 for(
const QString& channel : templateChannels)
603 auto template_it = templates.find(channel);
604 if(template_it != templates.end())
606 QString name = template_it->first;
607 name.replace(
"$pixelKey", pixelKey);
610 m.channels.push_back(template_it->second);
611 m.channels.back().name = name;
612 m.allChannels.push_back(name);
617 if(repeatFor.IsString())
619 std::string_view rf = repeatFor.GetString();
620 if(rf ==
"eachPixelABC")
622 auto sortedPixels = this->matrix.pixels;
623 ossia::sort(sortedPixels, [](
const Pixel& p1,
const Pixel& p2) {
624 return p1.name < p2.name;
627 for(
const Pixel& pixel : sortedPixels)
629 addTemplates(pixel.name);
632 else if(rf ==
"eachPixelGroup")
634 for(
const PixelGroup& group : this->matrix.groups)
636 addTemplates(group.name);
639 else if(ossia::string_starts_with(rf,
"eachPixel"))
641 rf = rf.substr(strlen(
"eachPixel"));
644 decltype(&Pixel::x) accessors[3];
645 for(
int i = 0; i < 3; i++)
651 accessors[i] = &Pixel::x;
655 accessors[i] = &Pixel::y;
659 accessors[i] = &Pixel::z;
666 auto sortedPixels = this->matrix.pixels;
667 ossia::sort(sortedPixels, [=](
const Pixel& p1,
const Pixel& p2) {
668 return std::array<int16_t, 3>{
669 p1.*(accessors[2]), p1.*(accessors[1]), p1.*(accessors[0])}
670 < std::array<int16_t, 3>{
671 p2.*(accessors[2]), p2.*(accessors[1]), p2.*(accessors[0])};
674 for(
const Pixel& pixel : sortedPixels)
676 addTemplates(pixel.name);
680 else if(repeatFor.IsArray())
683 for(
const auto& entity_v : repeatFor.GetArray())
685 if(!entity_v.IsString())
688 addTemplates(entity_v.GetString());
693 void loadModes(
const rapidjson::Document& doc)
698 if(
auto it = doc.FindMember(
"wheels"); it != doc.MemberEnd())
699 if(it->value.IsObject())
700 wheels = loadWheels(it->value);
703 if(
auto it = doc.FindMember(
"availableChannels"); it != doc.MemberEnd())
704 if(it->value.IsObject())
705 channels = loadChannels(it->value);
707 ChannelMap templateChannels;
708 if(
auto it = doc.FindMember(
"templateChannels"); it != doc.MemberEnd())
709 if(it->value.IsObject())
710 templateChannels = loadChannels(it->value);
712 if(channels.empty() && templateChannels.empty())
715 if(
auto it = doc.FindMember(
"matrix"); it != doc.MemberEnd())
716 if(it->value.IsObject())
717 loadMatrix(it->value);
719 using namespace std::literals;
721 auto it = doc.FindMember(
"modes");
722 if(it == doc.MemberEnd())
725 if(!it->value.IsArray())
728 for(
auto& mode : it->value.GetArray())
730 auto name_it = mode.FindMember(
"name");
731 auto channels_it = mode.FindMember(
"channels");
732 if(name_it != mode.MemberEnd() && channels_it != mode.MemberEnd())
735 if(name_it->value.IsString())
736 m.name = name_it->value.GetString();
738 if(channels_it->value.IsArray())
740 for(
auto& channel : channels_it->value.GetArray())
742 if(channel.IsString())
744 if(
auto matched_channel_it = channels.find(channel.GetString());
745 matched_channel_it != channels.end())
747 m.channels.push_back(matched_channel_it->second);
753 QString ch = channel.GetString();
754 for(
const auto& tc : templateChannels)
756 for(
const auto& g : this->matrix.groups)
758 QString repl = QString(tc.first).replace(
"$pixelKey", g.name);
761 m.channels.push_back(tc.second);
762 m.channels.back().name = ch;
763 goto could_match_channel;
769 m.allChannels.push_back(
770 QString::fromUtf8(channel.GetString(), channel.GetStringLength()));
772 else if(channel.IsObject())
774 if(
auto insert_it = channel.FindMember(
"insert");
775 insert_it != channel.MemberEnd() && insert_it->value.IsString()
776 && insert_it->value.GetString() ==
"matrixChannels"sv)
778 auto repeatFor_it = channel.FindMember(
"repeatFor");
779 if(repeatFor_it == channel.MemberEnd())
781 auto channelOrder_it = channel.FindMember(
"channelOrder");
782 auto templateChannels_it = channel.FindMember(
"templateChannels");
783 std::string channelOrder = channelOrder_it != channel.MemberEnd()
784 && channelOrder_it->value.IsString()
785 ? channelOrder_it->value.GetString()
788 std::vector<QString> modeChannels;
789 if(templateChannels_it != channel.MemberEnd()
790 && templateChannels_it->value.IsArray())
792 const auto& arr = templateChannels_it->value.GetArray();
796 modeChannels.push_back(c.GetString());
798 modeChannels.push_back({});
803 m, repeatFor_it->value, channelOrder, modeChannels,
809 m.allChannels.push_back({});
813 modes.push_back(std::move(m));
824 std::vector<QString> m_paths;
828 : iterator{std::move(dir), QDirIterator::Subdirectories | QDirIterator::FollowSymlinks}
829 , manufacturer{manufacturer}
832 QDirIterator iterator;
837 : m_paths{fixturesLibraryPaths()}
841 for(
auto& fixtures_dir : m_paths)
843 QFile f{fixtures_dir +
"/manufacturers.json"};
844 if(!f.open(QIODevice::ReadOnly))
846 auto data = f.map(0, f.size());
848 rapidjson::Document doc;
849 doc.Parse(
reinterpret_cast<const char*
>(data), f.size());
850 if(doc.HasParseError())
852 qDebug() <<
"Invalid manufacturers.json !";
856 loadManufacturer(doc, fixtures_dir);
863 void loadManufacturer(rapidjson::Document& doc,
const QString& fixtures_dir)
865 QModelIndex rootIndex;
866 auto manufacturers = readManufacturers(doc);
867 if(m_root.childCount() == 0)
871 for(
auto it = manufacturers.begin(); it != manufacturers.end(); ++it, ++k)
873 beginInsertRows(rootIndex, k, k);
874 auto& child = m_root.emplace_back(FixtureData{it->second}, &m_root);
877 QModelIndex manufacturerIndex = createIndex(k, 0, &child);
879 std::make_shared<Scan>(fixtures_dir +
"/" + it->first, child),
885 for(
auto it = manufacturers.begin(); it != manufacturers.end(); ++it)
887 auto manufacturer_node_it = ossia::find_if(
888 m_root, [&](
const FixtureNode& n) {
return n.name == it->second; });
889 if(manufacturer_node_it == m_root.end())
892 int newRowPosition = 0;
893 auto other_manufacturer_it = m_root.begin();
894 while(other_manufacturer_it != m_root.end()
896 it->second, other_manufacturer_it->name, Qt::CaseInsensitive)
899 other_manufacturer_it++;
903 beginInsertRows(rootIndex, newRowPosition, newRowPosition);
905 = m_root.emplace(other_manufacturer_it, FixtureData{it->second}, &m_root);
908 QModelIndex manufacturerIndex = createIndex(newRowPosition, 0, &child);
910 std::make_shared<Scan>(fixtures_dir +
"/" + it->first, child),
915 int distance = std::abs(std::distance(manufacturer_node_it, m_root.begin()));
916 QModelIndex manufacturerIndex
917 = createIndex(distance, 0, &*manufacturer_node_it);
919 std::make_shared<Scan>(
920 fixtures_dir +
"/" + it->first, *manufacturer_node_it),
928 std::string_view fixture_data, FixtureNode& manufacturer,
929 const QModelIndex& manufacturerIndex)
931 rapidjson::Document doc;
932 doc.Parse(fixture_data.data(), fixture_data.size());
933 if(doc.HasParseError())
935 qDebug() <<
"Invalid JSON document !";
938 if(
auto it = doc.FindMember(
"name"); it != doc.MemberEnd())
940 QString name = it->value.GetString();
942 int newRowPosition = 0;
943 auto other_fixture_it = manufacturer.begin();
944 while(other_fixture_it != manufacturer.end()
945 && QString::compare(name, other_fixture_it->name, Qt::CaseInsensitive) >= 0)
951 beginInsertRows(manufacturerIndex, newRowPosition, newRowPosition);
953 = manufacturer.emplace(other_fixture_it, FixtureData{name}, &manufacturer);
958 if(
auto it = doc.FindMember(
"categories"); it != doc.MemberEnd())
960 for(
auto& category : it->value.GetArray())
962 data.tags.push_back(category.GetString());
970 void nextFixture(std::shared_ptr<Scan> scan, QModelIndex manufacturerIndex)
972 auto& iterator = scan->iterator;
973 if(iterator.hasNext())
975 const auto filepath = iterator.next();
976 if(QFileInfo fi{filepath}; fi.suffix() ==
"json")
978 const std::string_view req{
"fixture.json"};
979 score::findStringInFile(filepath, req, [&](QFile& f) {
980 unsigned char* data = f.map(0, f.size());
982 const char* cbegin =
reinterpret_cast<char*
>(data);
985 std::string_view(cbegin, f.size()), scan->manufacturer, manufacturerIndex);
991 1,
this, [
this, scan = std::move(scan), idx = manufacturerIndex]()
mutable {
992 nextFixture(std::move(scan), idx);
998 FixtureNode& rootNode()
override {
return m_root; }
1000 const FixtureNode& rootNode()
const override {
return m_root; }
1002 int columnCount(
const QModelIndex& parent)
const override {
return 2; }
1004 QVariant data(
const QModelIndex& index,
int role)
const override
1006 const auto& node = nodeFromModelIndex(index);
1007 if(index.column() == 0)
1011 case Qt::DisplayRole:
1017 else if(index.column() == 1)
1021 case Qt::DisplayRole:
1022 return node.tags.join(
", ");
1028 QVariant headerData(
int section, Qt::Orientation orientation,
int role)
const override
1030 if(role != Qt::DisplayRole)
1035 return tr(
"Fixture");
1042 Qt::ItemFlags flags(
const QModelIndex& index)
const override
1046 f = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
1051 QModelIndex modelIndexFromNode(
const FixtureNode& n)
const
1053 node_type* parent = n.parent();
1054 SCORE_ASSERT(parent);
1055 SCORE_ASSERT(parent != &rootNode());
1057 return createIndex(parent->indexOfChild(&n), 0, &n);
1060 static FixtureDatabase& instance()
1062 static FixtureDatabase db;
1066 void onPopulated(
auto func)
1074 QTimer::singleShot(8,
this, [
this, func = std::move(func)]()
mutable {
1075 onPopulated(std::move(func));