gboolean
gst_lv2_check_required_features (const LilvPlugin * lv2plugin)
{
  LilvNodes *required_features = lilv_plugin_get_required_features (lv2plugin);
  if (required_features) {
    LilvIter *i;
    gint j;
    gboolean missing = FALSE;

    for (i = lilv_nodes_begin (required_features);
        !lilv_nodes_is_end (required_features, i);
        i = lilv_nodes_next (required_features, i)) {
      const LilvNode *required_feature = lilv_nodes_get (required_features, i);
      const char *required_feature_uri = lilv_node_as_uri (required_feature);
      missing = TRUE;

      for (j = 0; lv2_features[j]; j++) {
        if (!strcmp (lv2_features[j]->URI, required_feature_uri)) {
          missing = FALSE;
          break;
        }
      }
      if (missing) {
        GST_FIXME ("lv2 plugin %s needs host feature: %s",
            lilv_node_as_uri (lilv_plugin_get_uri (lv2plugin)),
            required_feature_uri);
        break;
      }
    }
    lilv_nodes_free (required_features);
    return (!missing);
  }
  return TRUE;
}
Exemple #2
0
bool
LV2World::isPluginSupported (const LilvPlugin* plugin)
{
    // Required features support
    LilvNodes* nodes = lilv_plugin_get_required_features (plugin);
    LILV_FOREACH (nodes, iter, nodes)
    {
        const LilvNode* node (lilv_nodes_get (nodes, iter));
        if (! isFeatureSupported (CharPointer_UTF8 (lilv_node_as_uri (node)))) {
            return false; // Feature not supported
        }
    }
    lilv_nodes_free (nodes); nodes = nullptr;
    
    
    // Check this plugin's port types are supported
    const uint32 numPorts = lilv_plugin_get_num_ports (plugin);
    for (uint32 i = 0; i < numPorts; ++i)
    {
        // const LilvPort* port (lilv_plugin_get_port_by_index (plugin, i));
        // nothing here yet (or ever)
    }
    
    return true;
}
Exemple #3
0
void
lilv_port_free(const LilvPlugin* plugin, LilvPort* port)
{
	if (port) {
		sord_node_free(plugin->world->world, port->node);
		lilv_nodes_free(port->classes);
		lilv_node_free(port->symbol);
		free(port);
	}
}
Exemple #4
0
LILV_API
LilvNodes*
lilv_ui_get_supported_features(const LilvUI* ui)
{
    LilvNodes* optional = lilv_ui_get_optional_features(ui);
    LilvNodes* required = lilv_ui_get_required_features(ui);
    LilvNodes* result   = lilv_nodes_new();

    LILV_FOREACH(nodes, i, optional)
        zix_tree_insert((ZixTree*)result,
                        lilv_node_duplicate(lilv_nodes_get(optional, i)),
                        NULL);
    LILV_FOREACH(nodes, i, required)
        zix_tree_insert((ZixTree*)result,
                        lilv_node_duplicate(lilv_nodes_get(required, i)),
                        NULL);

    lilv_nodes_free(optional);
    lilv_nodes_free(required);

    return result;
}
Exemple #5
0
URISet
properties(World* world, SPtr<const Client::ObjectModel> model)
{
	URISet properties;
	URISet types = RDFS::types(world, model);

	LilvNode* rdf_type = lilv_new_uri(world->lilv_world(),
	                                  LILV_NS_RDF "type");
	LilvNode* rdf_Property = lilv_new_uri(world->lilv_world(),
	                                      LILV_NS_RDF "Property");
	LilvNode* rdfs_domain = lilv_new_uri(world->lilv_world(),
	                                     LILV_NS_RDFS "domain");

	LilvNodes* props = lilv_world_find_nodes(
		world->lilv_world(), NULL, rdf_type, rdf_Property);
	LILV_FOREACH(nodes, p, props) {
		const LilvNode* prop = lilv_nodes_get(props, p);
		if (lilv_node_is_uri(prop)) {
			LilvNodes* domains = lilv_world_find_nodes(
				world->lilv_world(), prop, rdfs_domain, NULL);
			unsigned n_matching_domains = 0;
			LILV_FOREACH(nodes, d, domains) {
				const LilvNode* domain_node = lilv_nodes_get(domains, d);
				if (!lilv_node_is_uri(domain_node)) {
					// TODO: Blank node domains (e.g. unions)
					continue;
				}

				const Raul::URI domain(lilv_node_as_uri(domain_node));
				if (types.count(domain)) {
					++n_matching_domains;
				}
			}

			if (lilv_nodes_size(domains) == 0 || (
				    n_matching_domains > 0 &&
				    n_matching_domains == lilv_nodes_size(domains))) {
				properties.insert(Raul::URI(lilv_node_as_uri(prop)));
			}

			lilv_nodes_free(domains);
		}
	}

	lilv_node_free(rdfs_domain);
	lilv_node_free(rdf_Property);
	lilv_node_free(rdf_type);

	return properties;
}
Exemple #6
0
void
lilv_ui_free(LilvUI* ui)
{
	lilv_node_free(ui->uri);
	ui->uri = NULL;

	lilv_node_free(ui->bundle_uri);
	ui->bundle_uri = NULL;

	lilv_node_free(ui->binary_uri);
	ui->binary_uri = NULL;

	lilv_nodes_free(ui->classes);

	free(ui);
}
Exemple #7
0
Result
LV2Module::instantiate (double samplerate)
{
    freeInstance();
    currentSampleRate = samplerate;
    
    features.clearQuick();
    world.getFeatures (features);
    
    // check for a worker interface
    LilvNodes* nodes = lilv_plugin_get_extension_data (plugin);
    LILV_FOREACH (nodes, iter, nodes)
    {
        const LilvNode* node = lilv_nodes_get (nodes, iter);
        if (lilv_node_equals (node, world.work_interface))
        {
            worker = new LV2Worker (world.getWorkThread(), 1);
            features.add (worker->getFeature());
        }
    }
    lilv_nodes_free (nodes); nodes = nullptr;        
    
    features.add (nullptr);
    instance = lilv_plugin_instantiate (plugin, samplerate,
                                        features.getRawDataPointer());
    if (instance == nullptr) {
        features.clearQuick();
        worker = nullptr;
        return Result::fail ("Could not instantiate plugin.");
    }
    
    if (const void* data = getExtensionData (LV2_WORKER__interface))
    {
        assert (worker != nullptr);
        worker->setSize (2048);
        worker->setInterface (lilv_instance_get_handle (instance),
                              (LV2_Worker_Interface*) data);
    }
    else if (worker)
    {
        features.removeFirstMatchingValue (worker->getFeature());
        worker = nullptr;
    }
    
    return Result::ok();
}
Exemple #8
0
void
print_port(const LilvPlugin* p,
           uint32_t          index,
           float*            mins,
           float*            maxes,
           float*            defaults)
{
    const LilvPort* port = lilv_plugin_get_port_by_index(p, index);

    printf("\n\tPort %d:\n", index);

    if (!port) {
        printf("\t\tERROR: Illegal/nonexistent port\n");
        return;
    }

    bool first = true;

    const LilvNodes* classes = lilv_port_get_classes(p, port);
    printf("\t\tType:       ");
    LILV_FOREACH(nodes, i, classes) {
        const LilvNode* value = lilv_nodes_get(classes, i);
        if (!first) {
            printf("\n\t\t            ");
        }
        printf("%s", lilv_node_as_uri(value));
        first = false;
    }

    if (lilv_port_is_a(p, port, event_class)) {
        LilvNodes* supported = lilv_port_get_value(
                                   p, port, supports_event_pred);
        if (lilv_nodes_size(supported) > 0) {
            printf("\n\t\tSupported events:\n");
            LILV_FOREACH(nodes, i, supported) {
                const LilvNode* value = lilv_nodes_get(supported, i);
                printf("\t\t\t%s\n", lilv_node_as_uri(value));
            }
        }
        lilv_nodes_free(supported);
    }
Exemple #9
0
LV2Effect::LV2Effect(const LilvPlugin *data,
                     const std::set<wxString> & categories)
:  mValid(true),
   mCategories(categories),
   mMidiInput(0),
   mLatencyPortIndex(-1)
{
   
   // We don't support any features at all, so if the plugin requires
   // any we skip it.
   LilvNodes *req = lilv_plugin_get_required_features(data);
   size_t nFeatures = lilv_nodes_size(req);
   lilv_nodes_free(req);
   if (nFeatures > 0)
   {
      mValid = false;
      return;
   }

   mData = data;
   pluginName = GetString(lilv_plugin_get_name(mData), true);

   fInBuffer = NULL;
   fOutBuffer = NULL;
   
   mLength = 0;

   // Allocate buffers for the port indices and the default control values
   int numPorts = lilv_plugin_get_num_ports(mData);
   float *minimumValues = new float [numPorts];
   float *maximumValues = new float [numPorts];
   float *defaultValues = new float [numPorts];

   // Retrieve the port ranges for all ports (some values may be NaN)
   lilv_plugin_get_port_ranges_float(mData, minimumValues, 
                                     maximumValues, defaultValues);

   // Get info about all ports
   for (int i = 0; i < numPorts; i++)
   {
      const LilvPort *port = lilv_plugin_get_port_by_index(mData, i);
      LV2Port internalPort;
      internalPort.mIndex = lilv_port_get_index(mData, port);

      // Get the port name
      LilvNode *tmpName = lilv_port_get_name(mData, port);
      internalPort.mName = GetString(tmpName);
      lilv_node_free(tmpName);

      // Get the scale points
      LilvScalePoints* points = lilv_port_get_scale_points(mData, port);
      LILV_FOREACH(scale_points, j, points)
      {
         const LilvScalePoint *point = lilv_scale_points_get(points, j);

         internalPort.mScaleValues.Add(lilv_node_as_float(lilv_scale_point_get_value(point)));
         internalPort.mScaleLabels.Add(GetString(lilv_scale_point_get_label(point)));
      }
      lilv_scale_points_free(points);

      // Get the groups
      LilvNodes *groups = lilv_port_get_value(mData, port, gPortGroup);
      if (groups)
      {
         LilvNode *group = lilv_nodes_get_first(groups);
         wxString uri = GetString(group);

         wxString label;
         const LilvNode *name = lilv_world_get(gWorld, group, gName, NULL);
         if (name)
         {
            label = GetString(name);
         }
         else
         {
            // Shouldn't happen, but provide something
            label = uri;
         }
         lilv_nodes_free(groups);

         // Check for new group
         if (mPortGroups.find(uri) == mPortGroups.end())
         {
            mPortGroups[uri] = LV2PortGroup(label);
         }
#if 0
         // Get subgroup
         //
         // LLL:  This isn't right...must find or construct a plugin with
         //       subgroups.
         LilvNodes *subgroup = lilv_node_get_value(mData, port, gSubGroupOf);
         if (subgroups)
         {
            LilvNode *subgroup = lilv_nodes_get_first(subgroups);
            wxString uri = GetString(subgroup);
            const LilvNode *subgroup = lilv_world_get(gWorld, group, gSubGroupOf, NULL);
            wxString label = GetString(name);
            lilv_nodes_free(subgroup);
         }
         else
#endif
         {
            mRootGroup.AddSubGroup(mPortGroups[uri]);
         }
         mPortGroups[uri].AddParameter(i);

      }
      else
      {
         mRootGroup.AddParameter(i);
      }

      // Get the port type
      if (lilv_port_is_a(mData, port, gAudioPortClass))
      {
         if (lilv_port_is_a(mData, port, gInputPortClass))
         {
            mAudioInputs.Add(internalPort);
         }
         else if (lilv_port_is_a(mData, port, gOutputPortClass))
         {
            mAudioOutputs.Add(internalPort);
         }
      }
      else if (lilv_port_is_a(mData, port, gControlPortClass) &&
               lilv_port_is_a(mData, port, gInputPortClass))
      {
         internalPort.mControlBuffer = float(1.0);
         internalPort.mMin = minimumValues[i];
         internalPort.mMax = maximumValues[i];
         internalPort.mDefault = defaultValues[i];
         if (isfinite(defaultValues[i]))
         {
            internalPort.mControlBuffer = defaultValues[i];
         }
         else if (isfinite(minimumValues[i]))
         {
            internalPort.mControlBuffer = minimumValues[i];
         }
         else if (isfinite(maximumValues[i]))
         {
            internalPort.mControlBuffer = maximumValues[i];
         }

         if (lilv_port_has_property(mData, port, gPortToggled))
         {
            internalPort.mToggle = true;
         }
         else if (lilv_port_has_property(mData, port, gPortIsInteger))
         {
            internalPort.mInteger = true;
         }
         else if (lilv_port_has_property(mData, port, gPortIsSampleRate))
         {
            internalPort.mSampleRate = true;
         }
         else if (lilv_port_has_property(mData, port, gPortIsEnumeration))
         {
            internalPort.mEnumeration = true;
         }

         mControlInputs.Add(internalPort);
      }
      else if (lilv_port_is_a(mData, port, gControlPortClass) &&
               lilv_port_is_a(mData, port, gOutputPortClass))
      {
         // If there is more than one latency port, the plugin is invalid
         if (lilv_port_has_property(mData, port, gPortIsLatency))
         {
            if (mLatencyPortIndex >= 0)
            {
               mValid = false;
               continue;
            }
            mLatencyPortIndex = i;
         }
         else if (!lilv_port_has_property(mData, port, gPortIsOptional))
         {
            mControlOutputs.Add(internalPort);
         }
      }
      else if (lilv_port_is_a(mData, port, gMidiPortClass) &&
               lilv_port_is_a(mData, port, gInputPortClass))
      {
         // If there is more than one MIDI input port, the plugin is invalid
         if (mMidiInput)
         {
            mValid = false;
            continue;
         }
         mMidiInput = new LV2Port(internalPort);
      }
      else
      {
         // Unknown port type, we set the invalid flag
 //        mValid = false;
      }
   }
   
   delete [] minimumValues;
   delete [] maximumValues;
   delete [] defaultValues;
   
   // MIDI synths may not have any audio inputs.
   if (mMidiInput && mAudioInputs.GetCount() > 0)
   {
      mValid = false;
   }
   
   // Determine whether the plugin is a generator, effect or analyser 
   // depending on the number of ports of each type (not completely accurate,
   // but works most of the time)
   int flags = PLUGIN_EFFECT;
   if (mAudioInputs.GetCount() == 0)
   {
      flags |= INSERT_EFFECT;
   }
   else if (mAudioOutputs.GetCount() == 0)
   {
      flags |= ANALYZE_EFFECT;
   }
   else
   {
      flags |= PROCESS_EFFECT;
   }

   SetEffectFlags(flags);
}
Exemple #10
0
static LilvNodes*
lilv_nodes_from_stream_objects_i18n(LilvWorld*    world,
                                    SordIter*     stream,
                                    SordQuadIndex field)
{
	LilvNodes*      values  = lilv_nodes_new();
	const SordNode* nolang  = NULL;  // Untranslated value
	const SordNode* partial = NULL;  // Partial language match
	char*           syslang = lilv_get_lang();
	FOREACH_MATCH(stream) {
		const SordNode* value = sord_iter_get_node(stream, field);
		if (sord_node_get_type(value) == SORD_LITERAL) {
			const char*   lang = sord_node_get_language(value);
			LilvLangMatch lm   = LILV_LANG_MATCH_NONE;
			if (lang) {
				lm = (syslang)
					? lilv_lang_matches(lang, syslang)
					: LILV_LANG_MATCH_PARTIAL;
			} else {
				nolang = value;
				if (!syslang) {
					lm = LILV_LANG_MATCH_EXACT;
				}
			}

			if (lm == LILV_LANG_MATCH_EXACT) {
				// Exact language match, add to results
				zix_tree_insert((ZixTree*)values,
				                lilv_node_new_from_node(world, value),
				                NULL);
			} else if (lm == LILV_LANG_MATCH_PARTIAL) {
				// Partial language match, save in case we find no exact
				partial = value;
			}
		} else {
			zix_tree_insert((ZixTree*)values,
			                lilv_node_new_from_node(world, value),
			                NULL);
		}
	}
	sord_iter_free(stream);
	free(syslang);

	if (lilv_nodes_size(values) > 0) {
		return values;
	}

	const SordNode* best = nolang;
	if (syslang && partial) {
		// Partial language match for system language
		best = partial;
	} else if (!best) {
		// No languages matches at all, and no untranslated value
		// Use any value, if possible
		best = partial;
	}

	if (best) {
		zix_tree_insert(
			(ZixTree*)values, lilv_node_new_from_node(world, best), NULL);
	} else {
		// No matches whatsoever
		lilv_nodes_free(values);
		values = NULL;
	}

	return values;
}
Exemple #11
0
Lv2Plugin *Lv2PluginCache::getPlugin(const char *uri, const char *preset, Lv2State *state) {
    // create a unique key for uri/preset/state
    std::string keyString(uri);
    if(preset) {
        keyString.append(":");
        keyString.append(preset);
    }
    std::size_t hash = std::hash<std::string>()(keyString);
    if(state) {
        hash = hash ^ (state->getHash() << 1);
    }

    // count tells how many instances of this type of plugin
    int count = ++instanceCount[hash];

    // return cached plugin instance if available
    int key = hash + count;
    Lv2Plugin *cachedPlugin = findObject(key);
    if(cachedPlugin) {
        cachedPlugin->restore();
        return cachedPlugin;
    }

    // look in cache for plugin type by uri
    std::string uriString(uri);
    const LilvPlugin *lilvPlugin = pluginMap[uriString];
    if(!lilvPlugin) {
        // find lv2 plugin in lilv
        LilvNode *lilvUri = lilv_new_uri(world, uri);
        lilvPlugin = lilv_plugins_get_by_uri(plugins, lilvUri);
        lilv_node_free(lilvUri);

        if(!lilvPlugin) {
            throw std::logic_error(std::string("Plugin is not installed on this system: ") + uriString);
        }

        // check that required features are supported
        LilvNodes* features = lilv_plugin_get_required_features(lilvPlugin);
        for (LilvIter* f = lilv_nodes_begin(features); !lilv_nodes_is_end(features, f); f = lilv_nodes_next(features, f)) {
            const char* featureUri = lilv_node_as_uri(lilv_nodes_get(features, f));
            if(!supported[featureUri]) {
                throw std::logic_error(std::string("Plugin ") + uriString + " requires unsupported feature: " + featureUri);
            }
        }
        lilv_nodes_free(features);
        pluginMap[uriString] = lilvPlugin;
    }

    // create worker if required
    Lv2Worker *worker = 0;
    if (lilv_plugin_has_feature(lilvPlugin, lv2Constants.lv2WorkerSchedule)
        && lilv_plugin_has_extension_data(lilvPlugin, lv2Constants.lv2WorkerInterface)) {
        worker = new Lv2Worker();
        ((LV2_Worker_Schedule*)lv2Features[5]->data)->handle = worker;
    }

    // instantiate
    LilvInstance *instance = lilv_plugin_instantiate(lilvPlugin, sampleRate, lv2Features);

    // connect worker with plugin instance
    if(worker) {
        worker->setInstance(instance);
    }

    // create plugin object
    Lv2Plugin *plugin = new Lv2Plugin(lilvPlugin, instance, lv2Constants, worker);

    // restore baseline default state
    LilvState *defaultState =  lilv_state_new_from_world(world, &map, lilv_plugin_get_uri(lilvPlugin));
    lilv_state_restore(defaultState, instance, setPortValue, plugin, 0, lv2Features);

    // find and restore preset
    if(preset) {
        LilvNodes* presets = lilv_plugin_get_related(lilvPlugin, lv2Constants.lv2Presets);
        LilvNode *myPreset = 0;
        LILV_FOREACH(nodes, i, presets) {
            const LilvNode* presetNode = lilv_nodes_get(presets, i);
            lilv_world_load_resource(world, presetNode);
            LilvNodes* labels = lilv_world_find_nodes(world, presetNode, lv2Constants.lv2RdfsLabel, NULL);
            if (labels) {
                const LilvNode* label = lilv_nodes_get_first(labels);
                const char *labelString = lilv_node_as_string(label); // TODO: free?
                if(!strcmp(labelString, preset)) {
                    myPreset = lilv_node_duplicate(presetNode);
                }
                lilv_nodes_free(labels);
            }
        }
        lilv_nodes_free(presets);
        if(myPreset) {
            LilvState* presetState = lilv_state_new_from_world(world, &map, myPreset);
            lilv_state_restore(presetState, instance, setPortValue, plugin, 0, NULL);
            // lilv_state_free(state); // TODO
        }
        else {
            throw std::logic_error(std::string("Plugin ") + uriString + " has no such preset: " + preset);
        }
    }
    // restore state
    else if(state) {    // TODO: what if state is requested on a plugin that doesn't support?
Exemple #12
0
Lv2Plugin::Lv2Plugin(const LilvPlugin *plugin, LilvInstance *instance,
                     const Lv2Constants &uris, Lv2Worker *worker) :
    plugin(plugin), instance(instance), midiOutputCount(0),
    controlConnections(4), newControlMappingsQueue(16), worker(worker)
{
    // audio inputs
    audioInputCount = lilv_plugin_get_num_ports_of_class(plugin, uris.lv2AudioPort, uris.lv2InputPort, 0);
    audioInputIndex = new uint32_t[audioInputCount];
    audioInput = new AudioConnector[audioInputCount];

    // audio outputs
    audioOutputCount = lilv_plugin_get_num_ports_of_class(plugin, uris.lv2AudioPort, uris.lv2OutputPort, 0);
    audioOutputIndex = new uint32_t[audioOutputCount];
    audioOutput = new AudioConnection*[audioOutputCount];
    for(uint32_t i = 0; i < audioOutputCount; i++) {
        audioOutput[i] = new AudioConnection(this);
        audioOutput[i]->clear();
    }

    // initialize port structures
    uint32_t numPorts = lilv_plugin_get_num_ports(plugin);
    uint32_t audioInputCounter = 0;
    uint32_t audioOutputCounter = 0;
    for(uint32_t i = 0; i < numPorts; i++) {
        const LilvPort *port = lilv_plugin_get_port_by_index(plugin, i);
        if(lilv_port_is_a(plugin, port, uris.lv2AudioPort)) {
            if(lilv_port_is_a(plugin, port, uris.lv2InputPort)) {
                audioInputIndex[audioInputCounter++] = i;
            }
            else if(lilv_port_is_a(plugin, port, uris.lv2OutputPort)) {
                audioOutputIndex[audioOutputCounter++] = i;
            }
        } else if(lilv_port_is_a(plugin, port, uris.lv2ControlPort)
                  && lilv_port_is_a(plugin, port, uris.lv2InputPort)) {
            // get control name
            const LilvNode* symbol = lilv_port_get_symbol(plugin, port);
            std::string portName(lilv_node_as_string(symbol));

            // create, connect and hash new control port object
            Lv2ControlPort *newPort = new Lv2ControlPort();
            LilvNode *dfault, *minimum, *maximum;
            lilv_port_get_range(plugin, port, &dfault, &minimum, &maximum);
            newPort->dfault = dfault ? lilv_node_as_float(dfault) : 0;
            newPort->minimum = lilv_node_as_float(minimum);
            newPort->maximum = lilv_node_as_float(maximum);
            lilv_instance_connect_port(instance, i, &(newPort->value));
            controlMap[portName] = newPort;

        } else if(lilv_port_is_a(plugin, port, uris.lv2AtomPort)) {
            // is it a MIDI/atom input?
            LilvNodes *atomBufferType = lilv_port_get_value(plugin, port, uris.lv2AtomBufferType);
            LilvNodes* atomSupports = lilv_port_get_value(plugin, port, uris.lv2AtomSupports);
            if (lilv_port_is_a(plugin, port, uris.lv2InputPort) &&
                    lilv_nodes_contains(atomBufferType, uris.lv2AtomSequence)
                    && lilv_nodes_contains(atomSupports, uris.lv2MidiEvent)) {
                // create new inputs and connect to atom sequence location
                Lv2MidiInput *newAtomPort = new Lv2MidiInput();
                lilv_instance_connect_port(instance, i, newAtomPort->getAtomSequence());
                midiInputList.add(newAtomPort);
            }
            else if (lilv_port_is_a(plugin, port, uris.lv2OutputPort)) {
                //atomSequence->atom.type = Lv2PluginFactory::instance()->uridMapper.uriToId(LV2_ATOM__Sequence);
                Lv2MidiOutput *midiOutput = new Lv2MidiOutput(this);
                lilv_instance_connect_port(instance, i, midiOutput->getAtomSequence());
                midiOutputList.add(midiOutput);
                midiOutputCount++;
            }
            else {
                // warn
                std::cout << "!!! unknown atom port at index " << i << ": " << lilv_node_as_string(lilv_port_get_name(plugin, port)) << std::endl;
            }
            lilv_nodes_free(atomSupports);
            lilv_nodes_free(atomBufferType);
        } else {
            lilv_instance_connect_port(instance, i, NULL);
            std::cout << "!!! unknown port at index " << i << ": " << lilv_node_as_string(lilv_port_get_name(plugin, port)) << std::endl;
        }
    }
}