#include "audioplugin.h" //#include //#include #include "logcategories.h" audioPlugin::audioPlugin(int maxBufferSizeNeeded, pluginPathType path, unsigned char chainPosition, QObject *parent) : QObject(parent) { // Note: the size is for "interlaced" 2-channel 8-bit data, as is used outside of this plugin. // Please do not set this to an odd number. // We need each of our 16-bit one-channel buffers to be four times smaller in size. if(maxBufferSizeNeeded==0) { bufferSize = 2728/2; // seems like an acceptable default value } else { bufferSize = maxBufferSizeNeeded/4; } this->audioChain = path; this->chainPosition = chainPosition; instanceId = (uint64_t)this; instanceString = QString("0x%1").arg(instanceId, 16, 16, QChar('0')); inBypassMode = false; info.chainPosition = chainPosition; info.instanceID = instanceId; info.path = audioChain; pluginURIAsQString = QString("unset"); externalSourceBuffer = NULL; externalSinkBuffer = NULL; sampleRate = 48000; makeByteTable(); allocateBuffers(); initPluginWorld(); } audioPlugin::~audioPlugin() { // free plugin helpers unloadPlugin(); // TODO: consider locking the runPlugin carefully // free buffers deallocateBuffers(); } void audioPlugin::setInputBuffer(char *stereoInterlacedInputBuffer) { if(stereoInterlacedInputBuffer == NULL) { emit pluginErrorMessage("Could not use NULL input buffer."); haveExternalSourceBuffer = false; externalSourceBuffer = NULL; } else { this->externalSourceBuffer = (int16_t*)stereoInterlacedInputBuffer; haveExternalSourceBuffer = true; } } void audioPlugin::setOutputBuffer(char *stereoInterlacedOutputBuffer) { if(stereoInterlacedOutputBuffer == NULL) { emit pluginErrorMessage("Could not use NULL output buffer."); haveExternalSinkBuffer = false; externalSinkBuffer = NULL; } else { this->externalSinkBuffer = (int16_t*)stereoInterlacedOutputBuffer; haveExternalSinkBuffer = true; } } int16_t* audioPlugin::getInputBuffer() { return externalSourceBuffer; } int16_t* audioPlugin::getOutputBuffer() { return externalSinkBuffer; } void audioPlugin::selectPluginByID(int pluginId) { (void)pluginId; } void audioPlugin::selectPluginByLabel(QString pluginLabel) { (void)pluginLabel; } void audioPlugin::selectPluginByName(QString pluginName) { (void)pluginName; } void audioPlugin::allocateBuffers() { // Internal use float buffers for audio // These buffers may be larger than required, but must not be smaller. // They will be accessed based on the size of the audio samples passing through. if(haveAllocatedInternalBuffers) { // realloc? emit pluginErrorMessage("WARNING: asked to reallocate buffer memory! This feature has not been implemented yet."); } else { inputBuffer[0]= (float*)malloc(sizeof(float) * bufferSize); inputBuffer[1]= (float*)malloc(sizeof(float) * bufferSize); outputBuffer[0]= (float*)malloc(sizeof(float) * bufferSize); outputBuffer[1]= (float*)malloc(sizeof(float) * bufferSize); haveAllocatedInternalBuffers = true; } } void audioPlugin::deallocateBuffers() { if(haveAllocatedInternalBuffers) { haveAllocatedInternalBuffers = false; free(inputBuffer); free(outputBuffer); } else { emit pluginErrorMessage("WARNING: asked to deallocate memory that has not been allocated!"); } } void audioPlugin::initPluginWorld() { // Setup the "world" for the plugin functions: world = lilv_world_new(); lilv_world_load_all(world); plugins = lilv_world_get_all_plugins(world); gControlPortClass = lilv_new_uri(world, LV2_CORE__ControlPort); // TODO: if thing == NULL... emit fail } bool audioPlugin::activatePlugin() { // TODO: unactivate and unload reset bools if(haveLoadedPlugin && !haveActivatedPlugin) { lilv_instance_activate(instance); haveActivatedPlugin = true; emit pluginStatusMessage(QString("[%1] activated plugin.").arg(instanceString)); return true; } else { emit pluginErrorMessage(QString("Cannot activate plugin that has not been loaded.")); return false; } } void audioPlugin::setupPlugin(char *pluginURI) { // This function loads exactly the URI it is told to load. // Future versions might include other, easier methods. bool loadedOk = false; bool activatedOk = false; this->pluginURIAsQString = QString("%1").arg(pluginURI); loadedOk = loadPlugin(pluginURI); haveLoadedPlugin = loadedOk; activatedOk = activatePlugin(); info.isLoaded = loadedOk && activatedOk; info.pluginName = pluginNameAsQString; info.plugin = this; info.pluginURI = this->pluginURIAsQString; emit pluginLoaded(info); } bool audioPlugin::loadPlugin(char *pluginURI) { bool success = false; lilv_plugins_get_by_uri(plugins, uri); uri = lilv_new_uri(world, pluginURI); plugin = lilv_plugins_get_by_uri(plugins, uri); if(plugin == NULL) { success = false; this->info.isLoaded = false; emit pluginLoaded(this->info); emit pluginErrorMessage(QString("Could not find plugin by URI [%1]").arg(pluginURI)); pluginNameAsQString = ""; haveLoadedPlugin = false; return success; } pluginName = lilv_plugin_get_name(plugin); pluginNameAsQString = QString("%1").arg(lilv_node_as_string(pluginName)); emit pluginStatusMessage(QString("Loaded plugin named [%1] from URI [%2]").arg(lilv_node_as_string(pluginName)).arg(pluginURI)); success = true; success = createPorts(); if(n_audio_in == 0 || n_audio_in > 2) { success = false; this->info.isLoaded = false; emit pluginLoaded(this->info); emit pluginErrorMessage(QString("Number of audio input ports (%1) for plugin [%2] is not compatible.")\ .arg(n_audio_in).arg(lilv_node_as_string(pluginName))); return success; } const uint32_t n_portsLoop = lilv_plugin_get_num_ports(plugin); ; feature_uri_map_data = { this, audioPlugin::urid_map }; feature_uri_map = { LV2_URID__map, &feature_uri_map_data }; feature_uri_unmap_data = { this, audioPlugin::urid_unmap }; feature_uri_unmap = { LV2_URID__unmap, &feature_uri_unmap_data }; feature_worker = { LV2_WORKER__schedule, (LV2_Worker_Schedule*)malloc(sizeof(LV2_Worker_Schedule)) }; features[0] = &feature_uri_map; features[1] = &feature_uri_unmap; features[2] = &feature_worker; instance = lilv_plugin_instantiate(plugin, sampleRate, features); // The NULL is "features" ?? if(instance == NULL) { success = false; emit pluginErrorMessage(QString("Plugin instance was NULL for plugin with name [%1]").arg(pluginNameAsQString)); return success; } // This is where the controls, input buffer, and output buffer, are connected // into the plugin instance using pointers. for (uint32_t p = 0, i = 0, o = 0; p < n_portsLoop; ++p) { if (ports[p].type == TYPE_CONTROL) { lilv_instance_connect_port(instance, p, &ports[p].value); } else if (ports[p].type == TYPE_AUDIO) { if (ports[p].is_input) { lilv_instance_connect_port(instance, p, inputBuffer[ i++ ]); } else { lilv_instance_connect_port(instance, p, outputBuffer[ o++ ]); } } else { lilv_instance_connect_port(instance, p, NULL); } } success = true; return success; } bool audioPlugin::createPorts() { bool success = false; n_ports = lilv_plugin_get_num_ports(plugin); n_audio_in = 0; n_audio_out = 0; emit pluginStatusMessage(QString("Creating ports, number of ports returned: %1").arg(n_ports)); ports = (Port*)calloc(n_ports, sizeof(Port)); float *values = (float*)calloc(n_ports, sizeof(float)); // default values actually float *minValues = (float*)calloc(n_ports, sizeof(float)); float *maxValues = (float*)calloc(n_ports, sizeof(float)); controlsType singleControl; controls.clear(); unsigned int controlNumber = 0; lilv_plugin_get_port_ranges_float(plugin, minValues, maxValues, values); // The two NULL above contain pointers to float arrays holding max and min values for the controls. LilvNode* lv2_InputPort = lilv_new_uri(world, LV2_CORE__InputPort); LilvNode* lv2_OutputPort = lilv_new_uri(world, LV2_CORE__OutputPort); LilvNode* lv2_AudioPort = lilv_new_uri(world, LV2_CORE__AudioPort); LilvNode* lv2_ControlPort = lilv_new_uri(world, LV2_CORE__ControlPort); LilvNode* lv2_EventPort = lilv_new_uri(world, LV2_EVENT__EventPort); LilvNode* lv2_AtomPort = lilv_new_uri(world, LV2_ATOM__AtomPort); LilvNode* lv2_connectionOptional = lilv_new_uri(world, LV2_CORE__connectionOptional); LilvNode* lv2_urid_map = lilv_new_uri(world, LV2_URID__map); LilvNode* lv2_urid_unmap = lilv_new_uri(world, LV2_URID__unmap); LilvNode* lv2_worker_schedule = lilv_new_uri(world, LV2_WORKER__schedule); for (uint32_t i = 0; i < n_ports; ++i) { Port *port = &ports[i]; const LilvPort *lport = lilv_plugin_get_port_by_index(plugin, i); port->lilv_port = lport; port->index = i; port->value = isnan(values[i]) ? 0.0f : values[i]; port->optional = lilv_port_has_property(plugin, lport, lv2_connectionOptional); // Check if input or output: if (lilv_port_is_a(plugin, lport, lv2_InputPort)) { port->is_input = true; } else if (!lilv_port_is_a(plugin, lport, lv2_OutputPort) && !port->optional) { success = false; emit pluginErrorMessage(QString("Port %1 is neither input nor output").arg(i)); return success; } // Check if port is audio or control: if (lilv_port_is_a(plugin, lport, lv2_ControlPort)) { port->type = TYPE_CONTROL; singleControl.instanceId = instanceId; singleControl.portIndex = i; singleControl.controlsIndex = controlNumber++; singleControl.def = isnan(values[i]) ? 0.0f : values[i]; singleControl.min = isnan(minValues[i]) ? 0.0f : minValues[i]; singleControl.max = isnan(maxValues[i]) ? 0.0f : maxValues[i]; singleControl.value = singleControl.def; // start at default value singleControl.label = QString("%1").arg(lilv_node_as_string(lilv_port_get_name(plugin, lport))); controls.push_back(singleControl); } else if (lilv_port_is_a(plugin, lport, lv2_AudioPort)) { port->type = TYPE_AUDIO; if (port->is_input) { ++n_audio_in; } else { ++n_audio_out; } } else if (lilv_port_is_a(plugin, lport, lv2_EventPort)) { emit pluginStatusMessage(QString("[%1] is an event port").arg(i)); port->type = TYPE_EVENT; } else if (lilv_port_is_a(plugin, lport, lv2_AtomPort)) { emit pluginStatusMessage(QString("[%1] is an atom port").arg(i)); port->type = TYPE_ATOM; } else if (!port->optional) { success = false; emit pluginErrorMessage(QString("Port %1 has an unsupported type.").arg(i)); return success; } } if (lilv_plugin_has_feature(plugin, lv2_urid_map)) { emit pluginStatusMessage(QString("Plugin is requesting urid_map feature")); } if (lilv_plugin_has_feature(plugin, lv2_urid_unmap)) { emit pluginStatusMessage(QString("Plugin is requesting urid_unmap feature")); } if (lilv_plugin_has_feature(plugin, lv2_worker_schedule)) { emit pluginStatusMessage(QString("Plugin is requesting worker_schedule feature")); } // At this point, the plugin is "discovered" and the temporary variables can be free'd: lilv_node_free(lv2_connectionOptional); lilv_node_free(lv2_ControlPort); lilv_node_free(lv2_AudioPort); lilv_node_free(lv2_OutputPort); lilv_node_free(lv2_InputPort); lilv_node_free(lv2_AtomPort); free(values); free(minValues); free(maxValues); // What persists is the "ports" holder, which is now populated, // as well as the counters for input and output ports. success = true; return success; } void audioPlugin::unloadPlugin() { haveActivatedPlugin = false; haveLoadedPlugin = false; lilv_instance_deactivate(instance); lilv_world_free(world); emit pluginStatusMessage(QString("[%1] unloaded plugin [%2]")\ .arg(instanceString)\ .arg(pluginNameAsQString)); } void audioPlugin::runPlugin(int samples) { // samples is the number of samples *at 8-8 bit stereo interlaced* // The interface here is designed so that one can call it with the QByteArray.length() number. if(inBypassMode) return; if(haveActivatedPlugin && haveExternalSinkBuffer && haveExternalSourceBuffer) { // Let's make these calculations only at one location in the code: externalSampleCount = samples; sourceBufferSampleCount = samples / 2; // 16-bit is half as many inputBufferSampleCount = samples / 4; // 16-bit per-channel is 1/4 as many per channel. // You can safely comment out any of these functions to determine where the crash is: convertInputBuffer(); // seems ok copyControlsToPluginInterface(); // seems ok lilv_instance_run(instance, inputBufferSampleCount); convertOutputBuffer(); // seems ok } else { emit pluginErrorMessage(QString("[%1] Tried to run without complete plugin activation.").arg(instanceString)); emit pluginErrorMessage(QString("[%1] has the following data: activated: %2, have sink: %3, have source: %4")\ .arg(instanceString)\ .arg(haveActivatedPlugin)\ .arg(haveExternalSinkBuffer)\ .arg(haveExternalSourceBuffer)); } } void audioPlugin::getPluginControlPorts(pluginInstanceInfoType info) { // For this, we should only be returning control ports, not audio or other ports, // and we should be sending a copy of the port data, not pointers. // We need to do this with an array of ports... uint64_t instanceRequested = info.instanceID; if(instanceRequested != this->instanceId) { emit pluginErrorMessage(QString("Plugin Controls Query received for instance 0x%1 but this is instance 0x%2.")\ .arg(instanceRequested, 16, 16, QChar('0'))\ .arg(instanceId, 16, 16, QChar('0'))); return; } if(haveActivatedPlugin) { emit haveControlPorts(controls); } else { emit pluginErrorMessage(QString("Cannot return plugin ports of unactivated plugin")); } } void audioPlugin::handleAdjustedPluginControls(controlsType control) { // Access the local vector of controls, called "controls". // Alter the control->controlsIndex member to have equal value. if(control.controlsIndex <= (unsigned int)controls.size()) { controls[control.controlsIndex].value = control.value; } else { emit pluginErrorMessage(QString("Error in plugin [%1][%4]: Asked to adjust control at index %2 when the max index is %3.")\ .arg(pluginNameAsQString)\ .arg(control.controlsIndex)\ .arg(controls.size())\ .arg(instanceString)); } } void audioPlugin::setBypass(bool bypass) { this->inBypassMode = bypass; } void audioPlugin::copyControlsToPluginInterface() { // Haven't figured out exactly how to do this one yet. Something like this: // Loop across all ports, modify if matching index. // Should I have used the n_params variable here instead of portIndex? // I have assumed that "value" is the control value. controlsType c; for(int i=0; i < controls.size(); i++) { c = controls.at(i); this->ports[ c.portIndex ].value = c.value; } } void audioPlugin::convertInputBuffer() { // Copy from the interleaved uint16 buffer // into the float buffer for the plugin to process // Includes amplitude scaling into -1 to +1 // externalSourceBuffer --> scale --> inputBuffer float max = 65535.0f / 2.0f; //float mid = max / 2.0f; float scalingFactor = 1.0f / max; // Note, we have already cast the 8MSB, 8LSB type data into a 16-bit holder // So accessing one member should result in a full 16-bit number. for (int n = 0; n < sourceBufferSampleCount; n += 2) { inputBuffer[0][n/2] = ( externalSourceBuffer[n]) * scalingFactor; inputBuffer[1][n/2] = ( externalSourceBuffer[n+1]) * scalingFactor; // inputBuffer[0][n/2] = (externalSourceBuffer[n] - mid) * scalingFactor; // inputBuffer[1][n/2] = (externalSourceBuffer[n+1] - mid) * scalingFactor; } } void audioPlugin::convertOutputBuffer() { // Copy from the plugin float output // to the interleaved uint16 buffer for the audioHandler. // Includes amplitude scaling back to uint16 levels. // Also converts to "dual mono" if so requested, copying // the channel 0 data over to channel 1. // Useful for mono plugins that do not create a second output stream. // outputBuffer --> scale --> externalSinkBuffer float max = 65535.0f; float mid = max / 2.0f; // set to zero for muted testing // copy through test: // Copy input to output: // for (int n = 0; n+1 < sourceBufferSampleCount; n += 1) { // externalSinkBuffer[n] = externalSourceBuffer[n]; // } // Copy float of input to output: // for (int n = 0; n+1 < sourceBufferSampleCount; n += 2) { // externalSinkBuffer[n] = (inputBuffer[0][n/2] + 1) * mid; // L // externalSinkBuffer[n+1] = (inputBuffer[0][n/2] + 1) * mid; // Copy L // } if(forceOutputMono) { for (int n = 0; n+1 < sourceBufferSampleCount; n += 2) { externalSinkBuffer[n] = (outputBuffer[0][n/2]) * mid; // L externalSinkBuffer[n+1] = (outputBuffer[0][n/2]) * mid; // Copy L } } else { for (int n = 0; n+1 < sourceBufferSampleCount; n += 2) { externalSinkBuffer[n] = (outputBuffer[0][n/2]) * mid; // L externalSinkBuffer[n+1] = (outputBuffer[1][n/2]) * mid; // R } } } void audioPlugin::makeByteTable() { for(unsigned int i=0; i < 65535; i++) { byteTable[i] = ((i&0x00ff) << 8) | ( (i&0xff00) >> 8 ) ; } } QString audioPlugin::getPLuginControlInfo() { QString info = QString("Plugin controls information for plugin named [%1] on instance [%2]: \n")\ .arg(pluginNameAsQString)\ .arg(instanceString); controlsType c; for(int i = 0; i < controls.size(); i ++) { c = controls.at(i); info.append(QString("Control %1: port index: %7, min: %2, max: %3, default: %4, current value: %5, name: [%6] \n")\ .arg(c.controlsIndex) .arg(c.min)\ .arg(c.max)\ .arg(c.def)\ .arg(c.value)\ .arg(c.label)\ .arg(c.portIndex)); } return info; } LV2_URID audioPlugin::urid_map(LV2_URID_Map_Handle handle, const char* uri) { audioPlugin* lv2 = (audioPlugin*)handle; LV2_URID urid; std::string key = uri; if (lv2->urid_map_data.find(key) == lv2->urid_map_data.end()) { urid = lv2->current_urid++; lv2->urid_map_data[key] = urid; } else { urid = lv2->urid_map_data[key]; } return urid; } const char* audioPlugin::urid_unmap(void* handle, LV2_URID urid) { audioPlugin* lv2 = (audioPlugin*)handle; const char* value = nullptr; for (auto const& p : lv2->urid_map_data) { if (p.second == urid) { value = p.first.c_str(); } } return value; } void audioPlugin::debugThis() { qDebug() << __PRETTY_FUNCTION__ << "Reached plugin debug function for instance" << instanceString; emit pluginStatusMessage(getPLuginControlInfo()); }