/* * DISTRHO Plugin Framework (DPF) * Copyright (C) 2012-2018 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this * permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "DistrhoPluginInternal.hpp" #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT # error Cannot use MIDI Output with LADSPA or DSSI #endif #ifdef DISTRHO_PLUGIN_TARGET_DSSI # include "dssi/dssi.h" # if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT # error DSSI does not support MIDI output # endif #else # include "ladspa/ladspa.h" # if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT # error Cannot use MIDI with LADSPA # endif # if DISTRHO_PLUGIN_WANT_STATE && !defined(DISTRHO_NO_WARNINGS) # warning LADSPA cannot handle states # endif #endif #if DISTRHO_PLUGIN_WANT_TIMEPOS && !defined(DISTRHO_NO_WARNINGS) # warning LADSPA/DSSI does not support TimePos #endif START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- class PluginLadspaDssi { public: PluginLadspaDssi() : fPlugin(nullptr, nullptr), fPortControls(nullptr), fLastControlValues(nullptr) { #if DISTRHO_PLUGIN_NUM_INPUTS > 0 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i) fPortAudioIns[i] = nullptr; #else fPortAudioIns = nullptr; #endif #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) fPortAudioOuts[i] = nullptr; #else fPortAudioOuts = nullptr; #endif if (const uint32_t count = fPlugin.getParameterCount()) { fPortControls = new LADSPA_Data*[count]; fLastControlValues = new LADSPA_Data[count]; for (uint32_t i=0; i < count; ++i) { fPortControls[i] = nullptr; fLastControlValues[i] = fPlugin.getParameterValue(i); } } else { fPortControls = nullptr; fLastControlValues = nullptr; } #if DISTRHO_PLUGIN_WANT_LATENCY fPortLatency = nullptr; #endif } ~PluginLadspaDssi() noexcept { if (fPortControls != nullptr) { delete[] fPortControls; fPortControls = nullptr; } if (fLastControlValues != nullptr) { delete[] fLastControlValues; fLastControlValues = nullptr; } } // ------------------------------------------------------------------- void ladspa_activate() { fPlugin.activate(); } void ladspa_deactivate() { fPlugin.deactivate(); } // ------------------------------------------------------------------- void ladspa_connect_port(const ulong port, LADSPA_Data* const dataLocation) noexcept { ulong index = 0; #if DISTRHO_PLUGIN_NUM_INPUTS > 0 for (ulong i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i) { if (port == index++) { fPortAudioIns[i] = dataLocation; return; } } #endif #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 for (ulong i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) { if (port == index++) { fPortAudioOuts[i] = dataLocation; return; } } #endif #if DISTRHO_PLUGIN_WANT_LATENCY if (port == index++) { fPortLatency = dataLocation; return; } #endif for (ulong i=0, count=fPlugin.getParameterCount(); i < count; ++i) { if (port == index++) { fPortControls[i] = dataLocation; return; } } } // ------------------------------------------------------------------- #ifdef DISTRHO_PLUGIN_TARGET_DSSI void ladspa_run(const ulong sampleCount) { dssi_run_synth(sampleCount, nullptr, 0); } void dssi_run_synth(const ulong sampleCount, snd_seq_event_t* const events, const ulong eventCount) #else void ladspa_run(const ulong sampleCount) #endif { // pre-roll if (sampleCount == 0) return updateParameterOutputsAndTriggers(); // Check for updated parameters float curValue; for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i) { if (fPortControls[i] == nullptr) continue; curValue = *fPortControls[i]; if (fPlugin.isParameterInput(i) && d_isNotEqual(fLastControlValues[i], curValue)) { fLastControlValues[i] = curValue; fPlugin.setParameterValue(i, curValue); } } #if DISTRHO_PLUGIN_WANT_MIDI_INPUT // Get MIDI Events uint32_t midiEventCount = 0; MidiEvent midiEvents[eventCount]; for (uint32_t i=0, j; i < eventCount; ++i) { const snd_seq_event_t& seqEvent(events[i]); // FIXME if (seqEvent.data.note.channel > 0xF || seqEvent.data.control.channel > 0xF) continue; switch (seqEvent.type) { case SND_SEQ_EVENT_NOTEOFF: j = midiEventCount++; midiEvents[j].frame = seqEvent.time.tick; midiEvents[j].size = 3; midiEvents[j].data[0] = 0x80 + seqEvent.data.note.channel; midiEvents[j].data[1] = seqEvent.data.note.note; midiEvents[j].data[2] = 0; midiEvents[j].data[3] = 0; break; case SND_SEQ_EVENT_NOTEON: j = midiEventCount++; midiEvents[j].frame = seqEvent.time.tick; midiEvents[j].size = 3; midiEvents[j].data[0] = 0x90 + seqEvent.data.note.channel; midiEvents[j].data[1] = seqEvent.data.note.note; midiEvents[j].data[2] = seqEvent.data.note.velocity; midiEvents[j].data[3] = 0; break; case SND_SEQ_EVENT_KEYPRESS: j = midiEventCount++; midiEvents[j].frame = seqEvent.time.tick; midiEvents[j].size = 3; midiEvents[j].data[0] = 0xA0 + seqEvent.data.note.channel; midiEvents[j].data[1] = seqEvent.data.note.note; midiEvents[j].data[2] = seqEvent.data.note.velocity; midiEvents[j].data[3] = 0; break; case SND_SEQ_EVENT_CONTROLLER: j = midiEventCount++; midiEvents[j].frame = seqEvent.time.tick; midiEvents[j].size = 3; midiEvents[j].data[0] = 0xB0 + seqEvent.data.control.channel; midiEvents[j].data[1] = seqEvent.data.control.param; midiEvents[j].data[2] = seqEvent.data.control.value; midiEvents[j].data[3] = 0; break; case SND_SEQ_EVENT_CHANPRESS: j = midiEventCount++; midiEvents[j].frame = seqEvent.time.tick; midiEvents[j].size = 2; midiEvents[j].data[0] = 0xD0 + seqEvent.data.control.channel; midiEvents[j].data[1] = seqEvent.data.control.value; midiEvents[j].data[2] = 0; midiEvents[j].data[3] = 0; break; case SND_SEQ_EVENT_PITCHBEND: j = midiEventCount++; midiEvents[j].frame = seqEvent.time.tick; midiEvents[j].size = 3; midiEvents[j].data[0] = 0xE0 + seqEvent.data.control.channel; uint16_t tempvalue = seqEvent.data.control.value + 8192; midiEvents[j].data[1] = tempvalue & 0x7F; midiEvents[j].data[2] = tempvalue >> 7; midiEvents[j].data[3] = 0; break; } } fPlugin.run(fPortAudioIns, fPortAudioOuts, sampleCount, midiEvents, midiEventCount); #else fPlugin.run(fPortAudioIns, fPortAudioOuts, sampleCount); #endif updateParameterOutputsAndTriggers(); #if defined(DISTRHO_PLUGIN_TARGET_DSSI) && ! DISTRHO_PLUGIN_WANT_MIDI_INPUT return; // unused (void)events; (void)eventCount; #endif } // ------------------------------------------------------------------- #ifdef DISTRHO_PLUGIN_TARGET_DSSI # if DISTRHO_PLUGIN_WANT_STATE char* dssi_configure(const char* const key, const char* const value) { if (std::strncmp(key, DSSI_RESERVED_CONFIGURE_PREFIX, std::strlen(DSSI_RESERVED_CONFIGURE_PREFIX)) == 0) return nullptr; if (std::strncmp(key, DSSI_GLOBAL_CONFIGURE_PREFIX, std::strlen(DSSI_GLOBAL_CONFIGURE_PREFIX)) == 0) return nullptr; fPlugin.setState(key, value); return nullptr; } # endif # if DISTRHO_PLUGIN_WANT_PROGRAMS const DSSI_Program_Descriptor* dssi_get_program(const ulong index) { if (index >= fPlugin.getProgramCount()) return nullptr; static DSSI_Program_Descriptor desc; desc.Bank = index / 128; desc.Program = index % 128; desc.Name = fPlugin.getProgramName(index); return &desc; } void dssi_select_program(const ulong bank, const ulong program) { const ulong realProgram(bank * 128 + program); DISTRHO_SAFE_ASSERT_RETURN(realProgram < fPlugin.getProgramCount(),); fPlugin.loadProgram(realProgram); // Update control inputs for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i) { if (fPlugin.isParameterOutput(i)) continue; fLastControlValues[i] = fPlugin.getParameterValue(i); if (fPortControls[i] != nullptr) *fPortControls[i] = fLastControlValues[i]; } } # endif int dssi_get_midi_controller_for_port(const ulong port) noexcept { const uint32_t parameterOffset = fPlugin.getParameterOffset(); if (port > parameterOffset) return DSSI_NONE; const uint8_t midiCC = fPlugin.getParameterMidiCC(port-parameterOffset); if (midiCC == 0 || midiCC == 32 || midiCC >= 0x78) return DSSI_NONE; return DSSI_CC(midiCC); } #endif // ------------------------------------------------------------------- private: PluginExporter fPlugin; // LADSPA ports #if DISTRHO_PLUGIN_NUM_INPUTS > 0 const LADSPA_Data* fPortAudioIns[DISTRHO_PLUGIN_NUM_INPUTS]; #else const LADSPA_Data** fPortAudioIns; #endif #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 LADSPA_Data* fPortAudioOuts[DISTRHO_PLUGIN_NUM_OUTPUTS]; #else LADSPA_Data** fPortAudioOuts; #endif LADSPA_Data** fPortControls; #if DISTRHO_PLUGIN_WANT_LATENCY LADSPA_Data* fPortLatency; #endif // Temporary data LADSPA_Data* fLastControlValues; // ------------------------------------------------------------------- void updateParameterOutputsAndTriggers() { float value; for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i) { if (fPlugin.isParameterOutput(i)) { value = fLastControlValues[i] = fPlugin.getParameterValue(i); if (fPortControls[i] != nullptr) *fPortControls[i] = value; } else if ((fPlugin.getParameterHints(i) & kParameterIsTrigger) == kParameterIsTrigger) { // NOTE: no trigger support in LADSPA control ports, simulate it here value = fPlugin.getParameterRanges(i).def; if (d_isEqual(value, fPlugin.getParameterValue(i))) continue; fLastControlValues[i] = value; fPlugin.setParameterValue(i, value); if (fPortControls[i] != nullptr) *fPortControls[i] = value; } } #if DISTRHO_PLUGIN_WANT_LATENCY if (fPortLatency != nullptr) *fPortLatency = fPlugin.getLatency(); #endif } }; // ----------------------------------------------------------------------- static LADSPA_Handle ladspa_instantiate(const LADSPA_Descriptor*, ulong sampleRate) { if (d_lastBufferSize == 0) d_lastBufferSize = 2048; d_lastSampleRate = sampleRate; return new PluginLadspaDssi(); } #define instancePtr ((PluginLadspaDssi*)instance) static void ladspa_connect_port(LADSPA_Handle instance, ulong port, LADSPA_Data* dataLocation) { instancePtr->ladspa_connect_port(port, dataLocation); } static void ladspa_activate(LADSPA_Handle instance) { instancePtr->ladspa_activate(); } static void ladspa_run(LADSPA_Handle instance, ulong sampleCount) { instancePtr->ladspa_run(sampleCount); } static void ladspa_deactivate(LADSPA_Handle instance) { instancePtr->ladspa_deactivate(); } static void ladspa_cleanup(LADSPA_Handle instance) { delete instancePtr; } #ifdef DISTRHO_PLUGIN_TARGET_DSSI # if DISTRHO_PLUGIN_WANT_STATE static char* dssi_configure(LADSPA_Handle instance, const char* key, const char* value) { return instancePtr->dssi_configure(key, value); } # endif # if DISTRHO_PLUGIN_WANT_PROGRAMS static const DSSI_Program_Descriptor* dssi_get_program(LADSPA_Handle instance, ulong index) { return instancePtr->dssi_get_program(index); } static void dssi_select_program(LADSPA_Handle instance, ulong bank, ulong program) { instancePtr->dssi_select_program(bank, program); } # endif static int dssi_get_midi_controller_for_port(LADSPA_Handle instance, ulong port) { return instancePtr->dssi_get_midi_controller_for_port(port); } # if DISTRHO_PLUGIN_WANT_MIDI_INPUT static void dssi_run_synth(LADSPA_Handle instance, ulong sampleCount, snd_seq_event_t* events, ulong eventCount) { instancePtr->dssi_run_synth(sampleCount, events, eventCount); } # endif #endif #undef instancePtr // ----------------------------------------------------------------------- static LADSPA_Descriptor sLadspaDescriptor = { /* UniqueID */ 0, /* Label */ nullptr, #if DISTRHO_PLUGIN_IS_RT_SAFE /* Properties */ LADSPA_PROPERTY_HARD_RT_CAPABLE, #else /* Properties */ 0x0, #endif /* Name */ nullptr, /* Maker */ nullptr, /* Copyright */ nullptr, /* PortCount */ 0, /* PortDescriptors */ nullptr, /* PortNames */ nullptr, /* PortRangeHints */ nullptr, /* ImplementationData */ nullptr, ladspa_instantiate, ladspa_connect_port, ladspa_activate, ladspa_run, /* run_adding */ nullptr, /* set_run_adding_gain */ nullptr, ladspa_deactivate, ladspa_cleanup }; #ifdef DISTRHO_PLUGIN_TARGET_DSSI static DSSI_Descriptor sDssiDescriptor = { 1, &sLadspaDescriptor, # if DISTRHO_PLUGIN_WANT_STATE dssi_configure, # else /* configure */ nullptr, # endif # if DISTRHO_PLUGIN_WANT_PROGRAMS dssi_get_program, dssi_select_program, # else /* get_program */ nullptr, /* select_program */ nullptr, # endif dssi_get_midi_controller_for_port, # if DISTRHO_PLUGIN_WANT_MIDI_INPUT dssi_run_synth, # else /* run_synth */ nullptr, # endif /* run_synth_adding */ nullptr, /* run_multiple_synths */ nullptr, /* run_multiple_synths_adding */ nullptr, nullptr, nullptr }; #endif // ----------------------------------------------------------------------- class DescriptorInitializer { public: DescriptorInitializer() { // Create dummy plugin to get data from d_lastBufferSize = 512; d_lastSampleRate = 44100.0; PluginExporter plugin(nullptr, nullptr); d_lastBufferSize = 0; d_lastSampleRate = 0.0; // Get port count, init ulong port = 0; ulong portCount = DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS + plugin.getParameterCount(); #if DISTRHO_PLUGIN_WANT_LATENCY portCount += 1; #endif const char** const portNames = new const char*[portCount]; LADSPA_PortDescriptor* portDescriptors = new LADSPA_PortDescriptor[portCount]; LADSPA_PortRangeHint* portRangeHints = new LADSPA_PortRangeHint [portCount]; // Set ports #if DISTRHO_PLUGIN_NUM_INPUTS > 0 for (ulong i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i, ++port) { const AudioPort& aport(plugin.getAudioPort(true, i)); portNames[port] = strdup(aport.name); portDescriptors[port] = LADSPA_PORT_AUDIO | LADSPA_PORT_INPUT; portRangeHints[port].HintDescriptor = 0x0; portRangeHints[port].LowerBound = 0.0f; portRangeHints[port].UpperBound = 1.0f; } #endif #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 for (ulong i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i, ++port) { const AudioPort& aport(plugin.getAudioPort(false, i)); portNames[port] = strdup(aport.name); portDescriptors[port] = LADSPA_PORT_AUDIO | LADSPA_PORT_OUTPUT; portRangeHints[port].HintDescriptor = 0x0; portRangeHints[port].LowerBound = 0.0f; portRangeHints[port].UpperBound = 1.0f; } #endif #if DISTRHO_PLUGIN_WANT_LATENCY // Set latency port portNames[port] = strdup("_latency"); portDescriptors[port] = LADSPA_PORT_CONTROL | LADSPA_PORT_OUTPUT; portRangeHints[port].HintDescriptor = LADSPA_HINT_SAMPLE_RATE|LADSPA_HINT_INTEGER; portRangeHints[port].LowerBound = 0.0f; portRangeHints[port].UpperBound = 1.0f; ++port; #endif for (ulong i=0, count=plugin.getParameterCount(); i < count; ++i, ++port) { portNames[port] = strdup((const char*)plugin.getParameterName(i)); portDescriptors[port] = LADSPA_PORT_CONTROL; if (plugin.isParameterOutput(i)) portDescriptors[port] |= LADSPA_PORT_OUTPUT; else portDescriptors[port] |= LADSPA_PORT_INPUT; { const ParameterRanges& ranges(plugin.getParameterRanges(i)); const float defValue(ranges.def); portRangeHints[port].HintDescriptor = LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE; portRangeHints[port].LowerBound = ranges.min; portRangeHints[port].UpperBound = ranges.max; if (defValue == 0.0f) portRangeHints[port].HintDescriptor |= LADSPA_HINT_DEFAULT_0; else if (defValue == 1.0f) portRangeHints[port].HintDescriptor |= LADSPA_HINT_DEFAULT_1; else if (defValue == 100.0f) portRangeHints[port].HintDescriptor |= LADSPA_HINT_DEFAULT_100; else if (defValue == 440.0f) portRangeHints[port].HintDescriptor |= LADSPA_HINT_DEFAULT_440; else if (ranges.min == defValue) portRangeHints[port].HintDescriptor |= LADSPA_HINT_DEFAULT_MINIMUM; else if (ranges.max == defValue) portRangeHints[port].HintDescriptor |= LADSPA_HINT_DEFAULT_MAXIMUM; else { const float middleValue = ranges.min/2.0f + ranges.max/2.0f; const float middleLow = (ranges.min/2.0f + middleValue/2.0f)/2.0f + middleValue/2.0f; const float middleHigh = (ranges.max/2.0f + middleValue/2.0f)/2.0f + middleValue/2.0f; if (defValue < middleLow) portRangeHints[port].HintDescriptor |= LADSPA_HINT_DEFAULT_LOW; else if (defValue > middleHigh) portRangeHints[port].HintDescriptor |= LADSPA_HINT_DEFAULT_HIGH; else portRangeHints[port].HintDescriptor |= LADSPA_HINT_DEFAULT_MIDDLE; } } { const uint32_t hints(plugin.getParameterHints(i)); if (hints & kParameterIsBoolean) { portRangeHints[port].HintDescriptor |= LADSPA_HINT_TOGGLED; } else { if (hints & kParameterIsInteger) portRangeHints[port].HintDescriptor |= LADSPA_HINT_INTEGER; if (hints & kParameterIsLogarithmic) portRangeHints[port].HintDescriptor |= LADSPA_HINT_LOGARITHMIC; } } } // Set data sLadspaDescriptor.UniqueID = plugin.getUniqueId(); sLadspaDescriptor.Label = strdup(plugin.getLabel()); sLadspaDescriptor.Name = strdup(plugin.getName()); sLadspaDescriptor.Maker = strdup(plugin.getMaker()); sLadspaDescriptor.Copyright = strdup(plugin.getLicense()); sLadspaDescriptor.PortCount = portCount; sLadspaDescriptor.PortNames = portNames; sLadspaDescriptor.PortDescriptors = portDescriptors; sLadspaDescriptor.PortRangeHints = portRangeHints; } ~DescriptorInitializer() { if (sLadspaDescriptor.Label != nullptr) { std::free((void*)sLadspaDescriptor.Label); sLadspaDescriptor.Label = nullptr; } if (sLadspaDescriptor.Name != nullptr) { std::free((void*)sLadspaDescriptor.Name); sLadspaDescriptor.Name = nullptr; } if (sLadspaDescriptor.Maker != nullptr) { std::free((void*)sLadspaDescriptor.Maker); sLadspaDescriptor.Maker = nullptr; } if (sLadspaDescriptor.Copyright != nullptr) { std::free((void*)sLadspaDescriptor.Copyright); sLadspaDescriptor.Copyright = nullptr; } if (sLadspaDescriptor.PortDescriptors != nullptr) { delete[] sLadspaDescriptor.PortDescriptors; sLadspaDescriptor.PortDescriptors = nullptr; } if (sLadspaDescriptor.PortRangeHints != nullptr) { delete[] sLadspaDescriptor.PortRangeHints; sLadspaDescriptor.PortRangeHints = nullptr; } if (sLadspaDescriptor.PortNames != nullptr) { for (ulong i=0; i < sLadspaDescriptor.PortCount; ++i) { if (sLadspaDescriptor.PortNames[i] != nullptr) std::free((void*)sLadspaDescriptor.PortNames[i]); } delete[] sLadspaDescriptor.PortNames; sLadspaDescriptor.PortNames = nullptr; } } }; static DescriptorInitializer sDescInit; // ----------------------------------------------------------------------- END_NAMESPACE_DISTRHO DISTRHO_PLUGIN_EXPORT const LADSPA_Descriptor* ladspa_descriptor(ulong index) { USE_NAMESPACE_DISTRHO return (index == 0) ? &sLadspaDescriptor : nullptr; } #ifdef DISTRHO_PLUGIN_TARGET_DSSI DISTRHO_PLUGIN_EXPORT const DSSI_Descriptor* dssi_descriptor(ulong index) { USE_NAMESPACE_DISTRHO return (index == 0) ? &sDssiDescriptor : nullptr; } #endif // -----------------------------------------------------------------------