/* * Carla Backend * Copyright (C) 2012 Filipe Coelho * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For a full copy of the GNU General Public License see the COPYING file */ #ifdef CARLA_ENGINE_VST #include "carla_engine.h" #include "carla_plugin.h" #include CARLA_BACKEND_START_NAMESPACE #if 0 } /* adjust editor indent */ #endif #ifdef CARLA_ENGINE_VST class CarlaEngineVst : public CarlaEngine { public: CarlaEngineVst(audioMasterCallback callback); ~CarlaEngineVst(); // ------------------------------------- bool init(const char* const name); bool close(); bool isOnAudioThread(); bool isOffline(); bool isRunning(); CarlaEngineClient* addClient(CarlaPlugin* const plugin); // ------------------------------------- intptr_t callback(int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt) { return m_callback(&effect, opcode, index, value, ptr, opt); } const AEffect* getEffect() const { return &effect; } // ------------------------------------- intptr_t handleDispatch(int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt); float handleGetParameter(int32_t index); void handleSetParameter(int32_t index, float value); void handleProcessReplacing(float** inputs, float** outputs, int32_t frames); private: AEffect effect; const audioMasterCallback m_callback; }; #endif // ------------------------------------------------------------------------------------------------------------------- // static VST<->Engine calls static intptr_t carla_vst_dispatcher(AEffect* effect, int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt) { qDebug("CarlaEngineVst::dispatcher(%p, %i, %i, " P_INTPTR ", %p, %f)", effect, opcode, index, value, ptr, opt); CarlaEngineVst* const engine = (CarlaEngineVst*)effect->object; if (opcode == effClose) { delete engine; return 1; } return engine->handleDispatch(opcode, index, value, ptr, opt); } static float carla_vst_get_parameter(AEffect* effect, int32_t index) { CarlaEngineVst* const engine = (CarlaEngineVst*)effect->object; return engine->handleGetParameter(index); } static void carla_vst_set_parameter(AEffect* effect, int32_t index, float value) { CarlaEngineVst* const engine = (CarlaEngineVst*)effect->object; engine->handleSetParameter(index, value); } static void carla_vst_process_replacing(AEffect* effect, float** inputs, float** outputs, int32_t sampleFrames) { CarlaEngineVst* const engine = (CarlaEngineVst*)effect->object; engine->handleProcessReplacing(inputs, outputs, sampleFrames); } // ------------------------------------------------------------------------------------------------------------------- // Carla Engine (VST) CarlaEngineVst::CarlaEngineVst(audioMasterCallback callback_) : CarlaEngine(), m_callback(callback_) { memset(&effect, 0, sizeof(AEffect)); // vst fields effect.magic = kEffectMagic; effect.object = this; effect.version = kVstVersion; effect.uniqueID = CCONST('C', 'r', 'l', 'a'); // plugin fields effect.numPrograms = 0; effect.numParams = 1; effect.numInputs = 2; effect.numOutputs = 2; effect.ioRatio = 1.0f; // static calls effect.dispatcher = carla_vst_dispatcher; effect.process = carla_vst_process_replacing; effect.getParameter = carla_vst_get_parameter; effect.setParameter = carla_vst_set_parameter; effect.processReplacing = carla_vst_process_replacing; // plugin flags //effect.flags |= effFlagsHasEditor; effect.flags |= effFlagsCanReplacing; //effect.flags |= effFlagsProgramChunks; // setup engine sampleRate = callback(audioMasterGetSampleRate, 0, 0, nullptr, 0.0f); bufferSize = callback(audioMasterGetBlockSize, 0, 0, nullptr, 0.0f); // set engine name char strBuf[STR_MAX]; callback(audioMasterGetVendorString, 0, 0, strBuf, 0.0f); if (strBuf[0] == 0) strcpy(strBuf, "CarlaVST"); init(strBuf); carla_options.bridge_lv2gtk2 = strdup("/home/falktx/Personal/FOSS/GIT/Cadence/src/carla-bridge/carla-bridge-lv2-gtk2"); //carla_options.prefer_ui_bridges = true; } CarlaEngineVst::~CarlaEngineVst() { close(); } // ------------------------------------------------------------------------------------------------------------------- bool CarlaEngineVst::init(const char* const name_) { name = strdup(name_); osc_init(name); // TEST /* CarlaPlugin::initializer init = { this, "fake-filename-here", nullptr, "http://invadarecords.com/plugins/lv2/meter" // "http://nedko.aranaudov.org/soft/filter/2/stereo" }; */ //short id = CarlaPlugin::newLV2(init); //CarlaPlugins[id]->setActive(true, false, false); //CarlaPlugins[id]->showGui(true); return true; } bool CarlaEngineVst::close() { if (name) { free((void*)name); name = nullptr; } osc_close(); //CarlaPlugin* plugin = CarlaPlugins[0]; //carla_proc_lock(); //plugin->setEnabled(false); //carla_proc_unlock(); //delete plugin; return true; } bool CarlaEngineVst::isOnAudioThread() { return (callback(audioMasterGetCurrentProcessLevel, 0, 0, nullptr, 0.0f) == kVstProcessLevelRealtime); } bool CarlaEngineVst::isOffline() { return (callback(audioMasterGetCurrentProcessLevel, 0, 0, nullptr, 0.0f) == kVstProcessLevelOffline); } bool CarlaEngineVst::isRunning() { return true; } CarlaEngineClient* CarlaEngineVst::addClient(CarlaPlugin* const plugin) { bool active = true; CarlaEngineClientNativeHandle handle = { }; return new CarlaEngineClient(handle, active); Q_UNUSED(plugin); } // ------------------------------------------------------------------------------------------------------------------- intptr_t CarlaEngineVst::handleDispatch(int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt) { switch (opcode) { case effOpen: case effClose: case effSetProgram: case effGetProgram: case effSetProgramName: case effGetProgramName: case effGetParamLabel: case effGetParamDisplay: break; case effGetParamName: strncpy((char*)ptr, "Enabled", kVstMaxParamStrLen); break; case effGetVu: break; case effSetSampleRate: sampleRate = opt; // TODO - watch for changes break; case effSetBlockSize: bufferSize = value; // TODO - watch for changes break; case effMainsChanged: case effEditGetRect: case effEditOpen: case effEditClose: case effEditDraw: case effEditMouse: case effEditKey: case effEditIdle: for (unsigned short i=0; i < MAX_PLUGINS; i++) { CarlaPlugin* const plugin = getPluginByIndex(i); if (plugin && plugin->enabled()) plugin->idleGui(); } break; case effEditTop: case effEditSleep: case effIdentify: case effGetChunk: case effSetChunk: case effProcessEvents: case effCanBeAutomated: case effString2Parameter: case effGetNumProgramCategories: case effGetProgramNameIndexed: case effCopyProgram: case effConnectInput: case effConnectOutput: case effGetInputProperties: case effGetOutputProperties: case effGetPlugCategory: case effGetCurrentPosition: case effGetDestinationBuffer: case effOfflineNotify: case effOfflinePrepare: case effOfflineRun: case effProcessVarIo: case effSetSpeakerArrangement: case effSetBlockSizeAndSampleRate: case effSetBypass: case effGetEffectName: case effGetErrorText: break; case effGetVendorString: strncpy((char*)ptr, "falkTX", kVstMaxVendorStrLen); break; case effGetProductString: strncpy((char*)ptr, "Carla VST", kVstMaxProductStrLen); break; case effGetVendorVersion: return 0x050; case effVendorSpecific: return 0; case effCanDo: { const char* const feature = (char*)ptr; if (strcmp(feature, "sendVstEvents") == 0) return 1; if (strcmp(feature, "sendVstMidiEvent") == 0) return 1; if (strcmp(feature, "receiveVstEvents") == 0) return 1; if (strcmp(feature, "receiveVstMidiEvent") == 0) return 1; if (strcmp(feature, "receiveVstTimeInfo") == 0) return 1; return 0; } case effGetTailSize: case effIdle: case effGetIcon: case effSetViewPosition: case effGetParameterProperties: case effKeysRequired: break; case effGetVstVersion: return kVstVersion; case effEditKeyDown: case effEditKeyUp: case effSetEditKnobMode: case effGetMidiProgramName: case effGetCurrentMidiProgram: case effGetMidiProgramCategory: case effHasMidiProgramsChanged: case effGetMidiKeyName: case effBeginSetProgram: case effEndSetProgram: case effGetSpeakerArrangement: case effShellGetNextPlugin: case effStartProcess: case effStopProcess: case effSetTotalSampleToProcess: case effSetPanLaw: case effBeginLoadBank: case effBeginLoadProgram: case effSetProcessPrecision: case effGetNumMidiInputChannels: case effGetNumMidiOutputChannels: break; } return 0; Q_UNUSED(index); } float CarlaEngineVst::handleGetParameter(int32_t index) { return 0.0f; Q_UNUSED(index); } void CarlaEngineVst::handleSetParameter(int32_t index, float value) { return; Q_UNUSED(index); Q_UNUSED(value); } void CarlaEngineVst::handleProcessReplacing(float** inputs, float** outputs, int32_t frames) { //const VstTimeInfo_R* const vstTimeInfo = (VstTimeInfo_R*)callback(audioMasterGetTime, 0, kVstTransportPlaying, nullptr, 0.0f); //timeInfo.playing = bool(vstTimeInfo->flags & kVstTransportPlaying); // if (pos.unique_1 == pos.unique_2) // { // timeInfo.frame = pos.frame; // timeInfo.time = pos.usecs; // if (pos.valid & JackPositionBBT) // { // timeInfo.valid = CarlaEngineTimeBBT; // timeInfo.bbt.bar = pos.bar; // timeInfo.bbt.beat = pos.beat; // timeInfo.bbt.tick = pos.tick; // timeInfo.bbt.bar_start_tick = pos.bar_start_tick; // timeInfo.bbt.beats_per_bar = pos.beats_per_bar; // timeInfo.bbt.beat_type = pos.beat_type; // timeInfo.bbt.ticks_per_beat = pos.ticks_per_beat; // timeInfo.bbt.beats_per_minute = pos.beats_per_minute; // } // else // timeInfo.valid = 0; // } // else // { //timeInfo.frame = 0; //timeInfo.valid = 0; // } // create temporary audio buffers float ains_tmp_buf1[frames]; float ains_tmp_buf2[frames]; float aouts_tmp_buf1[frames]; float aouts_tmp_buf2[frames]; float* ains_tmp[2] = { ains_tmp_buf1, ains_tmp_buf2 }; float* aouts_tmp[2] = { aouts_tmp_buf1, aouts_tmp_buf2 }; // initialize audio input memcpy(ains_tmp_buf1, inputs[0], sizeof(float)*frames); memcpy(ains_tmp_buf2, inputs[1], sizeof(float)*frames); // initialize control input memset(rackControlEventsIn, 0, sizeof(CarlaEngineControlEvent)*MAX_ENGINE_CONTROL_EVENTS); { // TODO } // initialize midi input memset(rackMidiEventsIn, 0, sizeof(CarlaEngineMidiEvent)*MAX_ENGINE_MIDI_EVENTS); { // TODO } // initialize outputs (zero) memset(aouts_tmp_buf1, 0, sizeof(float)*frames); memset(aouts_tmp_buf2, 0, sizeof(float)*frames); memset(rackControlEventsOut, 0, sizeof(CarlaEngineControlEvent)*MAX_ENGINE_CONTROL_EVENTS); memset(rackMidiEventsOut, 0, sizeof(CarlaEngineMidiEvent)*MAX_ENGINE_MIDI_EVENTS); bool processed = false; // process plugins for (unsigned short i=0; i < MAX_PLUGINS; i++) { CarlaPlugin* const plugin = getPluginByIndex(i); if (plugin && plugin->enabled()) { if (processed) { // initialize inputs (from previous outputs) memcpy(ains_tmp_buf1, aouts_tmp_buf1, sizeof(float)*frames); memcpy(ains_tmp_buf2, aouts_tmp_buf2, sizeof(float)*frames); memcpy(rackMidiEventsIn, rackMidiEventsOut, sizeof(CarlaEngineMidiEvent)*MAX_ENGINE_MIDI_EVENTS); // initialize outputs (zero) memset(aouts_tmp_buf1, 0, sizeof(float)*frames); memset(aouts_tmp_buf2, 0, sizeof(float)*frames); memset(rackMidiEventsOut, 0, sizeof(CarlaEngineMidiEvent)*MAX_ENGINE_MIDI_EVENTS); } // process processLock(); plugin->initBuffers(); if (carla_options.proccess_hq) { float* ains_buffer2[2]; float* aouts_buffer2[2]; for (int32_t j=0; j < frames; j += 8) { ains_buffer2[0] = ains_tmp_buf1 + j; ains_buffer2[1] = ains_tmp_buf2 + j; aouts_buffer2[0] = aouts_tmp_buf1 + j; aouts_buffer2[1] = aouts_tmp_buf2 + j; plugin->process(ains_buffer2, aouts_buffer2, 8, j); } } else plugin->process(ains_tmp, aouts_tmp, frames); processUnlock(); // if plugin has no audio inputs, add previous buffers if (plugin->audioInCount() == 0) { for (int32_t j=0; j < frames; j++) { aouts_tmp_buf1[j] += ains_tmp_buf1[j]; aouts_tmp_buf2[j] += ains_tmp_buf2[j]; } } processed = true; } } // if no plugins in the rack, copy inputs over outputs if (! processed) { memcpy(aouts_tmp_buf1, ains_tmp_buf1, sizeof(float)*frames); memcpy(aouts_tmp_buf2, ains_tmp_buf2, sizeof(float)*frames); memcpy(rackMidiEventsOut, rackMidiEventsIn, sizeof(CarlaEngineMidiEvent)*MAX_ENGINE_MIDI_EVENTS); } // output audio memcpy(outputs[0], aouts_tmp_buf1, sizeof(float)*frames); memcpy(outputs[1], aouts_tmp_buf2, sizeof(float)*frames); // output control { // TODO } // output midi { // TODO } } CARLA_BACKEND_END_NAMESPACE #if 0 } /* adjust editor indent */ #endif // ------------------------------------------------------------------------------------------------------------------- // VST plugin entry CARLA_EXPORT const AEffect* VSTPluginMain(audioMasterCallback callback) { qDebug("VSTPluginMain(%p)", callback); using namespace CarlaBackend; CarlaEngineVst* engine = new CarlaEngineVst(callback); return engine->getEffect(); } #endif // CARLA_ENGINE_VST