ardour/libs/ardour/vst3_plugin.cc

3282 lines
96 KiB
C++

/*
* Copyright (C) 2019-2023 Robin Gareus <robin@gareus.org>
*
* 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
* (at your option) 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "pbd/gstdio_compat.h"
#include <glibmm.h>
#include "pbd/basename.h"
#include "pbd/compose.h"
#include "pbd/convert.h"
#include "pbd/debug.h"
#include "pbd/error.h"
#include "pbd/failed_constructor.h"
#include "pbd/file_utils.h"
#include "pbd/tokenizer.h"
#include "pbd/unwind.h"
#ifdef PLATFORM_WINDOWS
#include "pbd/windows_special_dirs.h"
#include <shlobj.h> // CSIDL_*
#endif
#include "ardour/audio_buffer.h"
#include "ardour/audioengine.h"
#include "ardour/debug.h"
#include "ardour/rc_configuration.h"
#include "ardour/selection.h"
#include "ardour/session.h"
#include "ardour/stripable.h"
#include "ardour/tempo.h"
#include "ardour/utils.h"
#include "ardour/vst3_module.h"
#include "ardour/vst3_plugin.h"
#include "pbd/i18n.h"
using namespace PBD;
using namespace ARDOUR;
using namespace Temporal;
using namespace Steinberg;
using namespace Presonus;
VST3Plugin::VST3Plugin (AudioEngine& engine, Session& session, VST3PI* plug)
: Plugin (engine, session)
, _plug (plug)
, _parameter_queue (128 + plug->parameter_count ())
{
init ();
}
VST3Plugin::VST3Plugin (const VST3Plugin& other)
: Plugin (other)
, _parameter_queue (128 + other.parameter_count ())
{
std::shared_ptr<VST3PluginInfo> nfo = std::dynamic_pointer_cast<VST3PluginInfo> (other.get_info ());
_plug = new VST3PI (nfo->m, nfo->unique_id);
init ();
XMLNode root (other.state_node_name ());
other.add_state (&root);
set_state (root, Stateful::current_state_version);
}
VST3Plugin::~VST3Plugin ()
{
delete _plug;
}
void
VST3Plugin::init ()
{
#ifndef NDEBUG
if (DEBUG_ENABLED (DEBUG::VST3Config)) {
char fuid[33];
_plug->fuid ().toString (fuid);
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3 instantiating FUID: %1\n", fuid));
}
#endif
Vst::ProcessContext& context (_plug->context ());
context.sampleRate = _session.nominal_sample_rate ();
_plug->set_block_size (_session.get_block_size ());
_plug->OnResizeView.connect_same_thread (_connections, boost::bind (&VST3Plugin::forward_resize_view, this, _1, _2));
_plug->OnParameterChange.connect_same_thread (_connections, boost::bind (&VST3Plugin::parameter_change_handler, this, _1, _2, _3));
/* assume only default active busses are connected */
for (auto const& abi : _plug->bus_info_in ()) {
for (int32_t i = 0; i < abi.second.n_chn; ++i) {
_connected_inputs.push_back (abi.second.dflt);
}
}
for (auto const& abi : _plug->bus_info_out ()) {
for (int32_t i = 0; i < abi.second.n_chn; ++i) {
_connected_outputs.push_back (abi.second.dflt);
}
}
/* pre-configure from GUI thread */
_plug->enable_io (_connected_inputs, _connected_outputs);
}
void
VST3Plugin::forward_resize_view (int w, int h)
{
OnResizeView (w, h); /* EMIT SINAL */
}
void
VST3Plugin::parameter_change_handler (VST3PI::ParameterChange t, uint32_t param, float value)
{
switch (t) {
case VST3PI::BeginGesture:
Plugin::StartTouch (param);
break;
case VST3PI::EndGesture:
Plugin::EndTouch (param);
break;
case VST3PI::ValueChange:
_parameter_queue.write_one (PV (param, value));
/* fallthrough */
case VST3PI::ParamValueChanged:
/* emit ParameterChangedExternally, mark preset dirty */
Plugin::parameter_changed_externally (param, value);
break;
case VST3PI::InternalChange:
Plugin::state_changed ();
break;
case VST3PI::PresetChange:
PresetsChanged (unique_id (), this, false); /* EMIT SIGNAL */
size_t n_presets = _plug->n_factory_presets (); // ths may be old, invalidated count
if (_plug->program_change_port ().id != Vst::kNoParamId) {
int pgm = value * (n_presets > 1 ? (n_presets - 1) : 1);
std::string uri = string_compose (X_("VST3-P:%1:%2"), unique_id (), std::setw (4), std::setfill ('0'), pgm);
const Plugin::PresetRecord* pset = Plugin::preset_by_uri (uri);
if (pset && n_presets == _plug->n_factory_presets ()) {
Plugin::load_preset (*pset);
// XXX TODO notify replicated instances, unless plugin implements ISlaveControllerHandler
}
}
break;
}
}
/* ****************************************************************************
* Parameter API
*/
uint32_t
VST3Plugin::parameter_count () const
{
return _plug->parameter_count ();
}
float
VST3Plugin::default_value (uint32_t port)
{
assert (port < parameter_count ());
return _plug->default_value (port);
}
void
VST3Plugin::set_parameter (uint32_t port, float val, sampleoffset_t when)
{
if (!_plug->active () || _plug->is_loading_state () || AudioEngine::instance ()->in_process_thread ()) {
/* directly use VST3PI::_input_param_changes */
_plug->set_parameter (port, val, when, true);
} else {
assert (when == 0);
_plug->set_parameter (port, val, when, false);
_parameter_queue.write_one (PV (port, val));
}
Plugin::set_parameter (port, val, when);
}
float
VST3Plugin::get_parameter (uint32_t port) const
{
return _plug->get_parameter (port);
}
int
VST3Plugin::get_parameter_descriptor (uint32_t port, ParameterDescriptor& desc) const
{
assert (port < parameter_count ());
_plug->get_parameter_descriptor (port, desc);
desc.update_steps ();
return 0;
}
uint32_t
VST3Plugin::nth_parameter (uint32_t port, bool& ok) const
{
if (port < parameter_count ()) {
ok = true;
return port;
}
ok = false;
return 0;
}
bool
VST3Plugin::parameter_is_input (uint32_t port) const
{
return !_plug->parameter_is_readonly (port);
}
bool
VST3Plugin::parameter_is_output (uint32_t port) const
{
return _plug->parameter_is_readonly (port);
}
uint32_t
VST3Plugin::designated_bypass_port ()
{
return _plug->designated_bypass_port ();
}
void
VST3Plugin::set_automation_control (uint32_t port, std::shared_ptr<ARDOUR::AutomationControl> ac)
{
if (!ac->alist () || !_plug->subscribe_to_automation_changes ()) {
return;
}
ac->alist ()->automation_state_changed.connect_same_thread (_connections, boost::bind (&VST3PI::automation_state_changed, _plug, port, _1, std::weak_ptr<AutomationList> (ac->alist ())));
}
std::set<Evoral::Parameter>
VST3Plugin::automatable () const
{
std::set<Evoral::Parameter> automatables;
for (uint32_t i = 0; i < parameter_count (); ++i) {
if (parameter_is_input (i) && _plug->parameter_is_automatable (i)) {
automatables.insert (automatables.end (), Evoral::Parameter (PluginAutomation, 0, i));
}
}
return automatables;
}
std::string
VST3Plugin::describe_parameter (Evoral::Parameter param)
{
if (param.type () == PluginAutomation && param.id () < parameter_count ()) {
return _plug->parameter_label (param.id ());
}
return "??";
}
bool
VST3Plugin::print_parameter (uint32_t port, std::string& rv) const
{
rv = _plug->print_parameter (port);
return rv.size () > 0;
}
Plugin::IOPortDescription
VST3Plugin::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) const
{
if ((dt == DataType::AUDIO &&
((input && id >= _plug->n_audio_inputs())
|| (!input && id >= _plug->n_audio_outputs())))
|| (dt == DataType::MIDI &&
((input && id >= _plug->n_midi_inputs())
|| (!input && id >= _plug->n_midi_outputs())))) {
return Plugin::describe_io_port(dt, input, id);
}
return _plug->describe_io_port (dt, input, id);
}
PluginOutputConfiguration
VST3Plugin::possible_output () const
{
auto const& bi (_plug->bus_info_out ());
if (bi.size () < 2) {
return Plugin::possible_output ();
}
#if 0
PluginOutputConfiguration oc;
int32_t sum = 0;
for (auto const& n : bi) {
sum += n.second.n_chn;
oc.insert (sum);
}
return oc;
#else
/* first main out, + individual stereo main outs, +all main outs, + individual aux outs */
auto i = bi.begin ();
int32_t sum = i->second.n_chn;
bool seen_aux = i->second.type == Vst::kAux;
bool seen_mono = sum == 1;
PluginOutputConfiguration oc;
oc.insert (sum);
for (++i; i != bi.end (); ++i) {
if (seen_aux || seen_mono) {
sum += i->second.n_chn;
oc.insert (sum);
} else if (i->second.type == Vst::kAux) {
oc.insert (sum);
seen_aux = true;
sum += i->second.n_chn;
oc.insert (sum);
} else {
sum += i->second.n_chn;
if (i->second.n_chn == 1) {
seen_mono = true;
oc.insert (sum);
} else if (!seen_mono) {
oc.insert (sum);
}
}
}
if (!seen_aux && seen_mono) {
oc.insert (sum);
}
return oc;
#endif
}
/* ****************************************************************************
* Plugin UI
*/
bool
VST3Plugin::has_editor () const
{
VST3PI::RouteProcessorChangeBlock rpcb (_plug);
std::shared_ptr<VST3PluginInfo> nfo = std::dynamic_pointer_cast<VST3PluginInfo> (get_info ());
if (nfo->has_editor.has_value ()) {
return nfo->has_editor.value ();
}
bool rv = _plug->has_editor ();
nfo->has_editor = rv;
return rv;
}
Steinberg::IPlugView*
VST3Plugin::view ()
{
return _plug->view ();
}
void
VST3Plugin::close_view ()
{
_plug->close_view ();
}
#if SMTG_OS_LINUX
void
VST3Plugin::set_runloop (Steinberg::Linux::IRunLoop* run_loop)
{
return _plug->set_runloop (run_loop);
}
#endif
void
VST3Plugin::update_contoller_param ()
{
/* GUI Thread */
_plug->update_contoller_param ();
}
/* ****************************************************************************
* MIDI converters
*/
bool
VST3PI::evoral_to_vst3 (Vst::Event& e, Evoral::Event<samplepos_t> const& ev, int32_t bus)
{
const uint32_t size = ev.size ();
if (size == 0) {
return false;
}
const uint8_t* data = ev.buffer ();
uint8_t status = data[0];
if (status >= 0x80 && status < 0xF0) {
status &= 0xf0;
}
if (size == 2 || size == 3) {
Vst::ParamID id = Vst::kNoParamId;
const uint8_t channel = data[0] & 0x0f;
const uint8_t data1 = data[1] & 0x7f;
const uint8_t data2 = size == 3 ? (data[2] & 0x7f) : 0;
switch (status) {
case MIDI_CMD_NOTE_OFF:
e.type = Vst::Event::kNoteOffEvent;
e.noteOff.channel = channel;
e.noteOff.noteId = -1;
e.noteOff.pitch = data1;
e.noteOff.velocity = data2 / 127.f;
e.noteOff.tuning = 0.f;
return true;
case MIDI_CMD_NOTE_ON:
e.type = Vst::Event::kNoteOnEvent;
e.noteOn.channel = channel;
e.noteOn.noteId = -1;
e.noteOn.pitch = data1;
e.noteOn.velocity = data2 / 127.f;
e.noteOn.length = 0;
e.noteOn.tuning = 0.f;
return true;
case MIDI_CMD_NOTE_PRESSURE:
e.type = Vst::Event::kPolyPressureEvent;
e.polyPressure.channel = channel;
e.polyPressure.pitch = data1;
e.polyPressure.pressure = data2 / 127.f;
e.polyPressure.noteId = -1;
return true;
case MIDI_CMD_CONTROL:
if (ev.is_live_midi () /* live input -- no playback */) {
live_midi_cc (bus, channel, data1);
}
if (midi_controller (bus, channel, data1, id)) {
set_parameter_by_id (id, data2 / 127.f, ev.time ());
}
return false;
case MIDI_CMD_PGM_CHANGE:
assert (size == 2);
set_program (data2, ev.time ());
return false;
case MIDI_CMD_CHANNEL_PRESSURE:
assert (size == 2);
if (midi_controller (bus, channel, Vst::kAfterTouch, id)) {
set_parameter_by_id (id, data1 / 127.f, ev.time ());
}
return false;
case MIDI_CMD_BENDER:
if (midi_controller (bus, channel, Vst::kPitchBend, id)) {
uint32_t m14 = (data2 << 7) | data1;
set_parameter_by_id (id, m14 / 16383.f, ev.time ());
}
return false;
}
} else if (status == MIDI_CMD_COMMON_SYSEX) {
memset (&e, 0, sizeof (Vst::Event));
e.type = Vst::Event::kDataEvent;
e.data.type = Vst::DataEvent::kMidiSysEx;
e.data.bytes = ev.buffer (); // TODO copy ?!
e.data.size = ev.size ();
return true;
}
return false;
}
#define vst_to_midi(x) (static_cast<uint8_t> ((x)*127.f) & 0x7f)
void
VST3PI::vst3_to_midi_buffers (BufferSet& bufs, ChanMapping const& out_map)
{
for (int32 i = 0; i < _output_events.getEventCount (); ++i) {
Vst::Event e;
if (_output_events.getEvent (i, e) == kResultFalse) {
continue;
}
bool valid = false;
uint32_t index = out_map.get (DataType::MIDI, e.busIndex, &valid);
if (!valid || bufs.count ().n_midi () <= index) {
DEBUG_TRACE (DEBUG::VST3Process, string_compose ("VST3PI::vst3_to_midi_buffers - Invalid MIDI Bus %1\n", e.busIndex));
continue;
}
MidiBuffer& mb = bufs.get_midi (index);
uint8_t data[3];
switch (e.type) {
case Vst::Event::kDataEvent:
/* sysex */
mb.push_back (e.sampleOffset, Evoral::MIDI_EVENT, e.data.size, (uint8_t const*)e.data.bytes);
break;
case Vst::Event::kNoteOffEvent:
data[0] = MIDI_CMD_NOTE_OFF | e.noteOff.channel;
data[1] = e.noteOff.pitch;
data[2] = vst_to_midi (e.noteOff.velocity);
mb.push_back (e.sampleOffset, Evoral::MIDI_EVENT, 3, data);
break;
case Vst::Event::kNoteOnEvent:
data[0] = MIDI_CMD_NOTE_ON | e.noteOn.channel;
data[1] = e.noteOn.pitch;
data[2] = vst_to_midi (e.noteOn.velocity);
mb.push_back (e.sampleOffset, Evoral::MIDI_EVENT, 3, data);
break;
case Vst::Event::kPolyPressureEvent:
data[0] = MIDI_CMD_NOTE_PRESSURE | e.noteOff.channel;
data[1] = e.polyPressure.pitch;
data[2] = vst_to_midi (e.polyPressure.pressure);
mb.push_back (e.sampleOffset, Evoral::MIDI_EVENT, 3, data);
break;
case Vst::Event::kLegacyMIDICCOutEvent:
switch (e.midiCCOut.controlNumber) {
case Vst::kCtrlPolyPressure:
data[0] = MIDI_CMD_NOTE_PRESSURE | e.midiCCOut.channel;
data[1] = e.midiCCOut.value;
data[2] = e.midiCCOut.value2;
break;
default: /* Control Change */
data[0] = MIDI_CMD_CONTROL | e.midiCCOut.channel;
data[1] = e.midiCCOut.controlNumber;
data[2] = e.midiCCOut.value;
break;
case Vst::kCtrlProgramChange:
data[0] = MIDI_CMD_PGM_CHANGE | e.midiCCOut.channel;
data[1] = e.midiCCOut.value;
data[2] = e.midiCCOut.value2;
break;
case Vst::kAfterTouch:
data[0] = MIDI_CMD_CHANNEL_PRESSURE | e.midiCCOut.channel;
data[1] = e.midiCCOut.value;
data[2] = e.midiCCOut.value2;
break;
case Vst::kPitchBend:
data[0] = MIDI_CMD_BENDER | e.midiCCOut.channel;
data[1] = e.midiCCOut.value;
data[2] = e.midiCCOut.value2;
break;
}
mb.push_back (e.sampleOffset, Evoral::MIDI_EVENT, e.midiCCOut.controlNumber == Vst::kCtrlProgramChange ? 2 : 3, data);
break;
case Vst::Event::kNoteExpressionValueEvent:
case Vst::Event::kNoteExpressionTextEvent:
case Vst::Event::kChordEvent:
case Vst::Event::kScaleEvent:
default:
/* unsupported, unhandled event */
break;
}
}
}
/* ****************************************************************************/
void
VST3Plugin::add_state (XMLNode* root) const
{
XMLNode* child;
for (uint32_t i = 0; i < parameter_count (); ++i) {
if (!parameter_is_input (i)) {
continue;
}
child = new XMLNode ("Port");
child->set_property ("id", (uint32_t)_plug->index_to_id (i));
child->set_property ("value", _plug->get_parameter (i));
root->add_child_nocopy (*child);
}
RAMStream stream;
if (_plug->save_state (stream)) {
gchar* data = g_base64_encode (stream.data (), stream.size ());
if (data == 0) {
return;
}
XMLNode* chunk_node = new XMLNode (X_("chunk"));
chunk_node->add_content (data);
g_free (data);
root->add_child_nocopy (*chunk_node);
}
}
int
VST3Plugin::set_state (const XMLNode& node, int version)
{
XMLNodeConstIterator iter;
if (node.name () != state_node_name ()) {
error << string_compose (_("VST3<%1>: Bad node sent to VST3Plugin::set_state"), name ()) << endmsg;
return -1;
}
const Plugin::PresetRecord* r = 0;
std::string preset_uri;
if (node.get_property (X_("last-preset-uri"), preset_uri)) {
r = preset_by_uri (preset_uri);
}
if (r && _plug->program_change_port ().id != Vst::kNoParamId) {
std::vector<std::string> tmp;
if (PBD::tokenize (r->uri, std::string (":"), std::back_inserter (tmp)) && tmp.size () == 3 && tmp[0] == "VST3-P") {
float value = PBD::atoi (tmp[2]);
size_t n_presets = _plug->n_factory_presets ();
if (n_presets > 1) {
value /= (n_presets - 1.f);
}
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3Plugin::set_state: set_program (pgm: %1 plug: %2)\n", value, name ()));
_plug->controller ()->setParamNormalized (_plug->program_change_port ().id, value);
}
}
XMLNodeList nodes = node.children ("Port");
for (iter = nodes.begin (); iter != nodes.end (); ++iter) {
XMLNode* child = *iter;
uint32_t param_id;
float value;
if (!child->get_property ("id", param_id)) {
warning << string_compose (_("VST3<%1>: Missing parameter-id in VST3Plugin::set_state"), name ()) << endmsg;
continue;
}
if (!child->get_property ("value", value)) {
warning << string_compose (_("VST3<%1>: Missing parameter value in VST3Plugin::set_state"), name ()) << endmsg;
continue;
}
/* This is not required, PluginInsert::set_state calls
* set_control_ids() which already calls VST3Plugin::set_parameter.
*
* However there /may/ not be controllables for all parameters. Doing
* this here also prevents an additional pass to synchronize the controller
* via VST3PI::load_state -> VST3PI::update_shadow_data -> addParameterData
*/
#if 1
if (!_plug->try_set_parameter_by_id (param_id, value)) {
warning << string_compose (_("VST3<%1>: Invalid Vst::ParamID in VST3Plugin::set_state"), name ()) << endmsg;
}
#endif
}
XMLNode* chunk;
if ((chunk = find_named_node (node, X_("chunk"))) != 0) {
for (iter = chunk->children ().begin (); iter != chunk->children ().end (); ++iter) {
if ((*iter)->is_content ()) {
gsize size = 0;
guchar* _data = g_base64_decode ((*iter)->content ().c_str (), &size);
RAMStream stream (_data, size);
if (!_plug->load_state (stream)) {
error << string_compose (_("VST3<%1>: failed to load chunk-data"), name ()) << endmsg;
}
}
}
}
return Plugin::set_state (node, version);
}
/* ****************************************************************************/
void
VST3Plugin::set_owner (ARDOUR::SessionObject* o)
{
Plugin::set_owner (o);
_plug->set_owner (o);
}
int
VST3Plugin::set_block_size (pframes_t n_samples)
{
_plug->set_block_size (n_samples);
return 0;
}
samplecnt_t
VST3Plugin::plugin_latency () const
{
return _plug->plugin_latency ();
}
void
VST3Plugin::add_slave (std::shared_ptr<Plugin> p, bool rt)
{
std::shared_ptr<VST3Plugin> vst = std::dynamic_pointer_cast<VST3Plugin> (p);
if (vst) {
_plug->add_slave (vst->_plug->controller (), rt);
}
}
void
VST3Plugin::remove_slave (std::shared_ptr<Plugin> p)
{
std::shared_ptr<VST3Plugin> vst = std::dynamic_pointer_cast<VST3Plugin> (p);
if (vst) {
_plug->remove_slave (vst->_plug->controller ());
}
}
void
VST3Plugin::set_non_realtime (bool yn)
{
_plug->set_non_realtime (yn);
}
int
VST3Plugin::connect_and_run (BufferSet& bufs,
samplepos_t start, samplepos_t end, double speed,
ChanMapping const& in_map, ChanMapping const& out_map,
pframes_t n_samples, samplecnt_t offset)
{
Glib::Threads::Mutex::Lock tm (_plug->process_lock (), Glib::Threads::TRY_LOCK);
if (!tm.locked ()) {
return 0;
}
DEBUG_TRACE (DEBUG::VST3Process, string_compose ("%1 run %2 offset %3\n", name (), n_samples, offset));
Plugin::connect_and_run (bufs, start, end, speed, in_map, out_map, n_samples, offset);
Vst::ProcessContext& context (_plug->context ());
/* clear event ports */
_plug->cycle_start ();
context.state =
Vst::ProcessContext::kContTimeValid | Vst::ProcessContext::kSystemTimeValid | Vst::ProcessContext::kSmpteValid | Vst::ProcessContext::kProjectTimeMusicValid | Vst::ProcessContext::kBarPositionValid | Vst::ProcessContext::kTempoValid | Vst::ProcessContext::kTimeSigValid | Vst::ProcessContext::kClockValid;
context.projectTimeSamples = start;
context.continousTimeSamples = _engine.processed_samples ();
context.systemTime = g_get_monotonic_time ();
{
TempoMap::SharedPtr tmap (TempoMap::use ());
const TempoMetric& metric (tmap->metric_at (timepos_t (start)));
const BBT_Time& bbt (metric.bbt_at (timepos_t (start)));
context.tempo = metric.tempo ().quarter_notes_per_minute ();
context.timeSigNumerator = metric.meter ().divisions_per_bar ();
context.timeSigDenominator = metric.meter ().note_value ();
context.projectTimeMusic = DoubleableBeats (metric.tempo ().quarters_at_sample (start)).to_double ();
context.barPositionMusic = (bbt.bars - 1) * 4; // PPQN, NOT tmap.metric_at(bbt).meter().divisions_per_bar()
}
const double tcfps = _session.timecode_frames_per_second ();
context.frameRate.framesPerSecond = ceil (tcfps);
context.frameRate.flags = 0;
if (_session.timecode_drop_frames ()) {
context.frameRate.flags = Vst::FrameRate::kDropRate; /* 29.97 */
} else if (tcfps > context.frameRate.framesPerSecond) {
context.frameRate.flags = Vst::FrameRate::kPullDownRate; /* 23.976 etc */
}
if (_session.get_play_loop ()) {
Location* looploc = _session.locations ()->auto_loop_location ();
try {
/* loop start/end in quarter notes */
TempoMap::SharedPtr tmap (TempoMap::use ());
context.cycleStartMusic = DoubleableBeats (tmap->quarters_at (looploc->start ())).to_double ();
context.cycleEndMusic = DoubleableBeats (tmap->quarters_at (looploc->end ())).to_double ();
context.state |= Vst::ProcessContext::kCycleValid;
context.state |= Vst::ProcessContext::kCycleActive;
} catch (...) {
}
}
if (speed != 0) {
context.state |= Vst::ProcessContext::kPlaying;
}
if (_session.actively_recording ()) {
context.state |= Vst::ProcessContext::kRecording;
}
#if 0 // TODO
context.state |= Vst::ProcessContext::kClockValid;
context.samplesToNextClock = 0 // MIDI Clock Resolution (24 Per Quarter Note), can be negative (nearest);
#endif
ChanCount bufs_count;
bufs_count.set (DataType::AUDIO, 1);
bufs_count.set (DataType::MIDI, 1);
BufferSet& silent_bufs = _session.get_silent_buffers (bufs_count);
BufferSet& scratch_bufs = _session.get_scratch_buffers (bufs_count);
uint32_t n_bin = std::max<uint32_t> (1, _plug->n_audio_inputs ());
uint32_t n_bout = std::max<uint32_t> (1, _plug->n_audio_outputs ());
float** ins = (float**)alloca (n_bin * sizeof (float*));
float** outs = (float**)alloca (n_bout * sizeof (float*));
uint32_t in_index = 0;
for (int32_t i = 0; i < (int32_t)_plug->n_audio_inputs (); ++i) {
uint32_t index;
bool valid = false;
index = in_map.get (DataType::AUDIO, in_index++, &valid);
ins[i] = (valid)
? bufs.get_audio (index).data (offset)
: silent_bufs.get_audio (0).data (0);
_connected_inputs[i] = valid;
}
uint32_t out_index = 0;
for (int32_t i = 0; i < (int32_t)_plug->n_audio_outputs (); ++i) {
uint32_t index;
bool valid = false;
index = out_map.get (DataType::AUDIO, out_index++, &valid);
outs[i] = (valid)
? bufs.get_audio (index).data (offset)
: scratch_bufs.get_audio (0).data (0);
_connected_outputs[i] = valid;
}
/* apply parameter changes */
PV pv;
while (_parameter_queue.read (&pv, 1)) {
_plug->set_parameter (pv.port, pv.val, 0, true, true);
}
in_index = 0;
for (int32_t i = 0; i < (int32_t)_plug->n_midi_inputs (); ++i) {
bool valid = false;
uint32_t index = in_map.get (DataType::MIDI, in_index++, &valid);
if (valid && bufs.count ().n_midi () > index) {
for (MidiBuffer::iterator m = bufs.get_midi (index).begin (); m != bufs.get_midi (index).end (); ++m) {
const Evoral::Event<samplepos_t> ev (*m, false);
_plug->add_event (ev, i);
}
}
}
_plug->enable_io (_connected_inputs, _connected_outputs);
_plug->process (ins, outs, n_samples);
/* handle outgoing MIDI events */
if (_plug->n_midi_outputs () > 0 && bufs.count ().n_midi () > 0) {
/* clear valid in-place MIDI buffers (forward MIDI otherwise) */
in_index = 0;
for (int32_t i = 0; i < (int32_t)_plug->n_midi_inputs (); ++i) {
bool valid = false;
uint32_t index = in_map.get (DataType::MIDI, in_index++, &valid);
if (valid && bufs.count ().n_midi () > index) {
bufs.get_midi (index).clear ();
}
}
_plug->vst3_to_midi_buffers (bufs, out_map);
}
return 0;
}
/* ****************************************************************************/
bool
VST3Plugin::load_preset (PresetRecord r)
{
bool ok = false;
/* Extract the UUID of this preset from the URI */
std::vector<std::string> tmp;
if (!PBD::tokenize (r.uri, std::string (":"), std::back_inserter (tmp))) {
return false;
}
if (tmp.size () != 3) {
return false;
}
std::string const& unique_id = tmp[1];
FUID fuid;
if (!fuid.fromString (unique_id.c_str ()) || fuid != _plug->fuid ()) {
assert (0);
return false;
}
Glib::Threads::Mutex::Lock lx (_plug->process_lock ());
if (tmp[0] == "VST3-P") {
int program = PBD::atoi (tmp[2]);
assert (!r.user);
if (!_plug->set_program (program, 0)) {
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3Plugin::load_preset: set_program failed (pgm: %1 plug: %2)\n", program, name ()));
return false;
}
ok = true;
} else if (tmp[0] == "VST3-S") {
if (_preset_uri_map.find (r.uri) == _preset_uri_map.end ()) {
/* build _preset_uri_map for replicated instances */
find_presets ();
}
assert (_preset_uri_map.find (r.uri) != _preset_uri_map.end ());
std::string const& fn = _preset_uri_map[r.uri];
if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
RAMStream stream (fn);
ok = _plug->load_state (stream);
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3Plugin::load_preset: file %1 status %2\n", fn, ok ? "OK" : "error"));
}
}
lx.release ();
if (ok) {
Plugin::load_preset (r);
}
return ok;
}
std::string
VST3Plugin::do_save_preset (std::string name)
{
std::shared_ptr<VST3PluginInfo> nfo = std::dynamic_pointer_cast<VST3PluginInfo> (get_info ());
PBD::Searchpath psp = nfo->preset_search_path ();
assert (!psp.empty ());
std::string dir = psp.front ();
std::string fn = Glib::build_filename (dir, legalize_for_universal_path (name) + ".vstpreset");
if (g_mkdir_with_parents (dir.c_str (), 0775)) {
error << string_compose (_("Could not create VST3 Preset Folder '%1'"), dir) << endmsg;
}
RAMStream stream;
if (_plug->save_state (stream)) {
GError* err = NULL;
if (!g_file_set_contents (fn.c_str (), (const gchar*)stream.data (), stream.size (), &err)) {
::g_unlink (fn.c_str ());
if (err) {
error << string_compose (_("Could not save VST3 Preset (%1)"), err->message) << endmsg;
g_error_free (err);
}
return "";
}
std::string uri = string_compose (X_("VST3-S:%1:%2"), unique_id (), PBD::basename_nosuffix (fn));
_preset_uri_map[uri] = fn;
return uri;
}
return "";
}
void
VST3Plugin::do_remove_preset (std::string name)
{
std::shared_ptr<VST3PluginInfo> nfo = std::dynamic_pointer_cast<VST3PluginInfo> (get_info ());
PBD::Searchpath psp = nfo->preset_search_path ();
assert (!psp.empty ());
std::string dir = psp.front ();
std::string fn = Glib::build_filename (dir, legalize_for_universal_path (name) + ".vstpreset");
::g_unlink (fn.c_str ());
std::string uri = string_compose (X_("VST3-S:%1:%2"), unique_id (), PBD::basename_nosuffix (fn));
if (_preset_uri_map.find (uri) != _preset_uri_map.end ()) {
_preset_uri_map.erase (_preset_uri_map.find (uri));
}
}
static bool
vst3_preset_filter (const std::string& str, void*)
{
return str[0] != '.' && (str.length () > 9 && str.find (".vstpreset") == (str.length () - 10));
}
void
VST3Plugin::find_presets ()
{
_presets.clear ();
_preset_uri_map.clear ();
/* read vst3UnitPrograms */
IPtr<Vst::IUnitInfo> nfo = _plug->unit_info ();
if (nfo && _plug->program_change_port ().id != Vst::kNoParamId) {
Vst::UnitID program_unit_id = _plug->program_change_port ().unitId;
int32 unit_count = nfo->getUnitCount ();
for (int32 idx = 0; idx < unit_count; ++idx) {
Vst::UnitInfo unit_info;
if (!(nfo->getUnitInfo (idx, unit_info) == kResultOk && unit_info.id == program_unit_id)) {
continue;
}
int32 count = nfo->getProgramListCount ();
for (int32 i = 0; i < count; ++i) {
Vst::ProgramListInfo pli;
if (nfo->getProgramListInfo (i, pli) != kResultTrue) {
continue;
}
if (pli.id != unit_info.programListId) {
continue;
}
for (int32 j = 0; j < pli.programCount; ++j) {
Vst::String128 pname;
if (nfo->getProgramName (pli.id, j, pname) == kResultTrue) {
std::string preset_name = tchar_to_utf8 (pname);
if (preset_name.empty ()) {
warning << string_compose (_("VST3<%1>: ignored unnamed factory preset/program"), name ()) << endmsg;
continue;
}
std::string uri = string_compose (X_("VST3-P:%1:%2"), unique_id (), std::setw (4), std::setfill ('0'), j);
PresetRecord r (uri, preset_name, false);
_presets.insert (make_pair (uri, r));
}
if (nfo->hasProgramPitchNames (pli.id, j)) {
// TODO -> midnam
}
}
break; // only one program list
}
break; // only one unit
}
}
if (_presets.empty () && _plug->program_change_port ().id != Vst::kNoParamId) {
/* fill in presets by number */
Vst::ParameterInfo const& pi (_plug->program_change_port ());
int32 n_programs = pi.stepCount + 1;
for (int32 i = 0; i < n_programs; ++i) {
float value = static_cast<Vst::ParamValue> (i) / static_cast<Vst::ParamValue> (pi.stepCount);
std::string preset_name = _plug->print_parameter (pi.id, value);
if (!preset_name.empty ()) {
std::string uri = string_compose (X_("VST3-P:%1:%2"), unique_id (), std::setw (4), std::setfill ('0'), i);
PresetRecord r (uri, preset_name, false);
_presets.insert (make_pair (uri, r));
}
}
}
_plug->set_n_factory_presets (_presets.size ());
// TODO check _plug->unit_data()
// IUnitData: programDataSupported -> setUnitProgramData (IBStream)
std::shared_ptr<VST3PluginInfo> info = std::dynamic_pointer_cast<VST3PluginInfo> (get_info ());
PBD::Searchpath psp = info->preset_search_path ();
std::vector<std::string> preset_files;
find_paths_matching_filter (preset_files, psp, vst3_preset_filter, 0, false, true, false);
for (std::vector<std::string>::iterator i = preset_files.begin (); i != preset_files.end (); ++i) {
bool is_user = PBD::path_is_within (psp.front (), *i);
std::string preset_name = PBD::basename_nosuffix (*i);
std::string uri = string_compose (X_("VST3-S:%1:%2"), unique_id (), preset_name);
if (_presets.find (uri) != _presets.end ()) {
continue;
}
PresetRecord r (uri, preset_name, is_user);
_presets.insert (make_pair (uri, r));
_preset_uri_map[uri] = *i;
}
}
/* ****************************************************************************/
VST3PluginInfo::VST3PluginInfo ()
{
type = ARDOUR::VST3;
}
PluginPtr
VST3PluginInfo::load (Session& session)
{
try {
if (!m) {
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3 Loading: %1\n", path));
m = VST3PluginModule::load (path);
}
PluginPtr plugin;
Steinberg::VST3PI* plug = new VST3PI (m, unique_id);
plugin.reset (new VST3Plugin (session.engine (), session, plug));
plugin->set_info (PluginInfoPtr (shared_from_this ()));
return plugin;
} catch (failed_constructor& err) {
;
}
return PluginPtr ();
}
std::vector<Plugin::PresetRecord>
VST3PluginInfo::get_presets (bool user_only) const
{
std::vector<Plugin::PresetRecord> p;
/* This only returns user-presets, which is sufficient for the time
* being. So far only Mixer_UI::sync_treeview_from_favorite_order()
* uses PluginInfo to query presets.
*
* see also VST3Plugin::find_presets
*/
assert (user_only);
PBD::Searchpath psp = preset_search_path ();
std::vector<std::string> preset_files;
find_paths_matching_filter (preset_files, psp, vst3_preset_filter, 0, false, true, false);
for (std::vector<std::string>::iterator i = preset_files.begin (); i != preset_files.end (); ++i) {
bool is_user = PBD::path_is_within (psp.front (), *i);
std::string preset_name = PBD::basename_nosuffix (*i);
std::string uri = string_compose (X_("VST3-S:%1:%2"), unique_id, preset_name);
if (!is_user) {
continue;
}
p.push_back (Plugin::PresetRecord (uri, preset_name, is_user));
}
std::sort (p.begin (), p.end ());
return p;
}
bool
VST3PluginInfo::is_instrument () const
{
if (category.find (Vst::PlugType::kInstrument) != std::string::npos) {
return true;
}
return PluginInfo::is_instrument ();
}
PBD::Searchpath
VST3PluginInfo::preset_search_path () const
{
std::string vendor = legalize_for_universal_path (creator);
std::string pname = legalize_for_universal_path (name);
/* first listed is used to save custom user-presets */
PBD::Searchpath preset_path;
#ifdef __APPLE__
preset_path += Glib::build_filename (Glib::get_home_dir (), "Library/Audio/Presets", vendor, pname);
preset_path += Glib::build_filename ("/Library/Audio/Presets", vendor, pname);
#elif defined PLATFORM_WINDOWS
std::string documents = PBD::get_win_special_folder_path (CSIDL_PERSONAL);
if (!documents.empty ()) {
preset_path += Glib::build_filename (documents, "VST3 Presets", vendor, pname);
preset_path += Glib::build_filename (documents, "vst3 presets", vendor, pname);
}
preset_path += Glib::build_filename (Glib::get_user_data_dir (), "VST3 Presets", vendor, pname);
std::string appdata = PBD::get_win_special_folder_path (CSIDL_APPDATA);
if (!appdata.empty ()) {
preset_path += Glib::build_filename (appdata, "VST3 Presets", vendor, pname);
preset_path += Glib::build_filename (appdata, "vst3 presets", vendor, pname);
}
#else
preset_path += Glib::build_filename (Glib::get_home_dir (), ".vst3", "presets", vendor, pname);
preset_path += Glib::build_filename ("/usr/share/vst3/presets", vendor, pname);
preset_path += Glib::build_filename ("/usr/local/share/vst3/presets", vendor, pname);
#endif
return preset_path;
}
/* ****************************************************************************/
VST3PI::VST3PI (std::shared_ptr<ARDOUR::VST3PluginModule> m, std::string unique_id)
: _module (m)
, _component (0)
, _controller (0)
, _view (0)
#if SMTG_OS_LINUX
, _run_loop (0)
#endif
, _is_loading_state (false)
, _is_processing (false)
, _block_size (0)
, _process_offline (false)
, _port_id_bypass (UINT32_MAX)
, _owner (0)
, _add_to_selection (false)
, _n_factory_presets (0)
, _block_rpc (0)
, _rpc_queue (RouteProcessorChange::NoProcessorChange, false)
, _no_kMono (false)
, _restart_component_is_synced (false)
{
using namespace std;
IPluginFactory* factory = m->factory ();
if (!factory) {
throw failed_constructor ();
}
if (!_fuid.fromString (unique_id.c_str ())) {
throw failed_constructor ();
}
PFactoryInfo fi;
if (factory->getFactoryInfo (&fi) == kResultTrue) {
/* work around issue with UADx VST3s not recognizing
* Vst::SpeakerArr::kMono. (see commit message for details)
*/
if (0 == strcmp (fi.vendor, "Universal Audio (UADx)")) {
_no_kMono = true;
}
}
#if !(defined PLATFORM_WINDOWS || defined __APPLE__) /* Linux only */
_restart_component_is_synced = m->has_symbol ("yabridge_version");
if (_restart_component_is_synced) {
DEBUG_TRACE (DEBUG::VST3Config, "VST3PI detected yabridge\n");
}
#endif
#ifndef NDEBUG
if (DEBUG_ENABLED (DEBUG::VST3Config)) {
char fuid[33];
_fuid.toString (fuid);
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI create instance %1\n", fuid));
}
#endif
if (factory->createInstance (_fuid.toTUID (), Vst::IComponent::iid, (void**)&_component) != kResultTrue) {
DEBUG_TRACE (DEBUG::VST3Config, "VST3PI create instance failed\n");
throw failed_constructor ();
}
if (!_component || _component->initialize (HostApplication::getHostContext ()) != kResultOk) {
DEBUG_TRACE (DEBUG::VST3Config, "VST3PI component initialize failed\n");
throw failed_constructor ();
}
_controller = FUnknownPtr<Vst::IEditController> (_component).take ();
if (!_controller) {
TUID controllerCID;
if (_component->getControllerClassId (controllerCID) == kResultTrue) {
if (factory->createInstance (controllerCID, Vst::IEditController::iid, (void**)&_controller) != kResultTrue) {
_component->terminate ();
_component->release ();
throw failed_constructor ();
}
}
}
if (!_controller) {
DEBUG_TRACE (DEBUG::VST3Config, "VST3PI no controller was found\n");
_component->terminate ();
_component->release ();
throw failed_constructor ();
}
/* The official Steinberg SDK's source/vst/hosting/plugprovider.cpp
* only initializes the controller if it is separate of the component.
*
* However some plugins expect and unconditional call and other
* hosts incl. JUCE based one initialize a controller separately because
* FUnknownPtr<> cast may return a new obeject.
*
* So do not check for errors.
* if Vst::IEditController is-a Vst::IComponent the Controller
* may or may not already be initialized.
*/
_controller->initialize (HostApplication::getHostContext ());
if (_controller->setComponentHandler (this) != kResultOk) {
_controller->terminate ();
_controller->release ();
_component->terminate ();
_component->release ();
throw failed_constructor ();
}
if (!(_processor = FUnknownPtr<Vst::IAudioProcessor> (_component))) {
_controller->terminate ();
_controller->release ();
_component->terminate ();
_component->release ();
throw failed_constructor ();
}
/* prepare process context */
memset (&_context, 0, sizeof (Vst::ProcessContext));
/* bus-count for process-context */
_n_bus_in = _component->getBusCount (Vst::kAudio, Vst::kInput);
_n_bus_out = _component->getBusCount (Vst::kAudio, Vst::kOutput);
_busbuf_in.resize (_n_bus_in);
_busbuf_out.resize (_n_bus_out);
/* do not re-order, _io_name is build in sequence */
_n_inputs = count_channels (Vst::kAudio, Vst::kInput, Vst::kMain);
_n_aux_inputs = count_channels (Vst::kAudio, Vst::kInput, Vst::kAux);
_n_outputs = count_channels (Vst::kAudio, Vst::kOutput, Vst::kMain);
_n_aux_outputs = count_channels (Vst::kAudio, Vst::kOutput, Vst::kAux);
_n_midi_inputs = count_channels (Vst::kEvent, Vst::kInput, Vst::kMain);
_n_midi_outputs = count_channels (Vst::kEvent, Vst::kOutput, Vst::kMain);
if (!connect_components ()) {
//_controller->terminate(); // XXX ?
_component->terminate ();
_component->release ();
throw failed_constructor ();
}
memset (&_program_change_port, 0, sizeof (_program_change_port));
_program_change_port.id = Vst::kNoParamId;
FUnknownPtr<Vst::IEditController2> controller2 (_controller);
if (controller2) {
controller2->setKnobMode (Vst::kLinearMode);
}
int32 n_params = _controller->getParameterCount ();
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3 parameter count: %1\n", n_params));
for (int32 i = 0; i < n_params; ++i) {
Vst::ParameterInfo pi;
if (_controller->getParameterInfo (i, pi) != kResultTrue) {
continue;
}
if (pi.flags & Vst::ParameterInfo::kIsProgramChange) {
_program_change_port = pi;
continue;
}
if (tchar_to_utf8 (pi.title).find ("MIDI CC ") != std::string::npos) {
/* Some JUCE plugins add 16 * 128 automatable MIDI CC parameters */
continue;
}
Param p;
p.id = pi.id;
p.label = tchar_to_utf8 (pi.title).c_str ();
p.unit = tchar_to_utf8 (pi.units).c_str ();
p.steps = pi.stepCount;
p.normal = pi.defaultNormalizedValue;
p.is_enum = 0 != (pi.flags & Vst::ParameterInfo::kIsList);
p.read_only = 0 != (pi.flags & Vst::ParameterInfo::kIsReadOnly);
p.automatable = 0 != (pi.flags & Vst::ParameterInfo::kCanAutomate);
if (pi.flags & /*Vst::ParameterInfo::kIsHidden*/ (1 << 4)) {
p.label = X_("hidden");
}
#if 1 // if (host_editing == 0) // FUnknownPtr<Vst::IEditControllerHostEditing> host_editing (_controller);
if (0 == (pi.flags & (Vst::ParameterInfo::kCanAutomate | Vst::ParameterInfo::kIsReadOnly))) {
/* Hide writable parameters that cannot be automated in the generic UI.
* Those are usually internal params or MIDI ctrl hacks.
* index_to_id() needs to work for those.
*/
p.label = X_("hidden");
}
#endif
uint32_t idx = _ctrl_params.size ();
_ctrl_params.push_back (p);
if (pi.flags & Vst::ParameterInfo::kIsBypass) {
_port_id_bypass = idx;
}
_ctrl_id_index[pi.id] = idx;
_ctrl_index_id[idx] = pi.id;
_shadow_data.push_back (p.normal);
_update_ctrl.push_back (false);
}
if (_n_midi_inputs > 0 || _n_midi_outputs > 0) {
n_params += 128;
} else if (n_params == 0) {
n_params = 16; /* arbitrary baseline, grows as needed */
}
_input_param_changes.set_n_params (n_params);
_output_param_changes.set_n_params (n_params);
synchronize_states ();
/* enable all MIDI busses */
set_event_bus_state (true);
}
VST3PI::~VST3PI ()
{
terminate ();
}
IPtr<Vst::IUnitInfo>
VST3PI::unit_info ()
{
IPtr<Vst::IUnitInfo> nfo = FUnknownPtr<Vst::IUnitInfo> (_component);
if (nfo) {
return nfo;
}
return FUnknownPtr<Vst::IUnitInfo> (_controller);
}
#if 0
IPtr<Vst::IUnitData>
VST3PI::unit_data ()
{
Vst::IUnitData* iud = FUnknownPtr<Vst::IUnitData> (_component);
if (iud) {
return iud;
}
return FUnknownPtr<Vst::IUnitData> (_controller);
}
#endif
void
VST3PI::terminate ()
{
assert (!_view);
/* disable all MIDI busses */
set_event_bus_state (false);
deactivate ();
_processor = 0;
disconnect_components ();
if (_controller) {
_controller->setComponentHandler (0);
_controller->terminate ();
_controller->release ();
}
if (_component) {
_component->terminate ();
_component->release ();
}
_controller = 0;
_component = 0;
}
bool
VST3PI::connect_components ()
{
if (!_component || !_controller) {
return false;
}
FUnknownPtr<Vst::IConnectionPoint> componentCP (_component);
FUnknownPtr<Vst::IConnectionPoint> controllerCP (_controller);
if (!componentCP || !controllerCP) {
return true;
}
_component_cproxy = std::shared_ptr<ConnectionProxy> (new ConnectionProxy (componentCP));
_controller_cproxy = std::shared_ptr<ConnectionProxy> (new ConnectionProxy (controllerCP));
tresult res = _component_cproxy->connect (controllerCP);
if (!(res == kResultOk || res == kNotImplemented)) {
DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::connect_components Cannot connect controller to component\n");
//return false;
}
res = _controller_cproxy->connect (componentCP);
if (!(res == kResultOk || res == kNotImplemented)) {
DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::connect_components Cannot connect component to controller\n");
}
return true;
}
bool
VST3PI::disconnect_components ()
{
if (!_component_cproxy || !_controller_cproxy) {
return false;
}
bool rv = _component_cproxy->disconnect ();
rv &= _controller_cproxy->disconnect ();
_component_cproxy.reset ();
_controller_cproxy.reset ();
return rv;
}
tresult
VST3PI::queryInterface (const TUID _iid, void** obj)
{
QUERY_INTERFACE (_iid, obj, FUnknown::iid, Vst::IComponentHandler)
QUERY_INTERFACE (_iid, obj, Vst::IComponentHandler::iid, Vst::IComponentHandler)
QUERY_INTERFACE (_iid, obj, Vst::IComponentHandler2::iid, Vst::IComponentHandler2)
QUERY_INTERFACE (_iid, obj, Vst::IUnitHandler::iid, Vst::IUnitHandler)
QUERY_INTERFACE (_iid, obj, Presonus::IContextInfoProvider::iid, Presonus::IContextInfoProvider)
QUERY_INTERFACE (_iid, obj, Presonus::IContextInfoProvider2::iid, Presonus::IContextInfoProvider2)
QUERY_INTERFACE (_iid, obj, Presonus::IContextInfoProvider3::iid, Presonus::IContextInfoProvider3)
QUERY_INTERFACE (_iid, obj, IPlugFrame::iid, IPlugFrame)
#if SMTG_OS_LINUX
if (_run_loop && FUnknownPrivate::iidEqual (_iid, Linux::IRunLoop::iid)) {
*obj = _run_loop;
return kResultOk;
}
#endif
if (DEBUG_ENABLED (DEBUG::VST3Config)) {
char fuid[33];
FUID::fromTUID (_iid).toString (fuid);
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::queryInterface not supported: %1\n", fuid));
}
*obj = nullptr;
return kNoInterface;
}
tresult
VST3PI::restartComponent (int32 flags)
{
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::restartComponent %1%2\n", std::hex, flags));
if (flags & Vst::kReloadComponent) {
Glib::Threads::Mutex::Lock pl (_process_lock, Glib::Threads::NOT_LOCK);
if (!AudioEngine::instance ()->in_process_thread () && !_is_loading_state && !_restart_component_is_synced) {
pl.acquire ();
} else {
assert (0); // a plugin should not call this while processing
}
/* according to the spec, "The host has to unload completely
* the plug-in (controller/processor) and reload it."
*
* However other implementations, in particular JUCE, only
* re-activates the plugin. So let's follow their lead for
* the time being.
*/
warning << "VST3: Vst::kReloadComponent (ignored)" << endmsg;
deactivate ();
activate ();
}
if (flags & Vst::kParamValuesChanged) {
Glib::Threads::Mutex::Lock pl (_process_lock, Glib::Threads::NOT_LOCK);
if (!AudioEngine::instance ()->in_process_thread () && !_is_loading_state && !_restart_component_is_synced) {
pl.acquire ();
}
update_shadow_data ();
}
if (flags & Vst::kLatencyChanged) {
/* https://forums.steinberg.net/t/reporting-latency-change/201601
* mentions that the host plugin should be deactivated before querying
* latency. However the official spec does not require this.
*
* However other implementations do not call setActive(false/true) when
* the latency changes, and Ardour does not require it either, latency
* changes are automatically picked up.
*/
Glib::Threads::Mutex::Lock pl (_process_lock, Glib::Threads::NOT_LOCK);
if (!AudioEngine::instance ()->in_process_thread () && !_is_loading_state && !_restart_component_is_synced) {
/* Some plugins (e.g BlendEQ) call this from the process,
* IPlugProcessor::ProcessBuffers. In that case taking the
* _process_lock would deadlock.
*/
pl.acquire ();
}
_plugin_latency.reset ();
}
if (flags & Vst::kIoTitlesChanged) {
/* Input and/or Output bus titles have changed
* compare to VST3PI::count_channels
*/
const Vst::MediaTypes medien [] = { Vst::kAudio, Vst::kEvent };
const Vst::BusDirections dirs[] = { Vst::kInput, Vst::kOutput };
for (auto const& media : medien) {
for (auto const& dir : dirs) {
int32 n_busses = _component->getBusCount (media, dir);
for (int32 i = 0; i < n_busses; ++i) {
Vst::BusInfo bus;
if (_component->getBusInfo (media, dir, i, bus) != kResultTrue) {
continue;
}
std::string bus_name = tchar_to_utf8 (bus.name);
if (media == Vst::kEvent && _io_name[media][dir].size () == 1) {
_io_name[media][dir][0].name = bus_name;
} else if (media == Vst::kAudio && _io_name[media][dir].size () == size_t(bus.channelCount)) {
for (int32_t j = 0; j < bus.channelCount; ++j) {
std::string channel_name;
if (bus.channelCount > 1) {
channel_name = string_compose ("%1 %2", bus_name, j + 1);
} else {
channel_name = bus_name;
}
_io_name[media][dir][j].name = channel_name;
}
}
}
}
}
}
if (flags & Vst::kParamTitlesChanged) {
/* titles or default values or flags have changed */
int32 n_params = _controller->getParameterCount ();
for (int32 i = 0; i < n_params; ++i) {
Vst::ParameterInfo pi;
if (_controller->getParameterInfo (i, pi) != kResultTrue) {
continue;
}
std::map<Vst::ParamID, uint32_t>::const_iterator idx = _ctrl_id_index.find (pi.id);
if (idx != _ctrl_id_index.end ()) {
Param& p (_ctrl_params[idx->second]);
#ifndef NDEBUG
if (DEBUG_ENABLED (DEBUG::VST3Config) && p.label != tchar_to_utf8 (pi.title)) {
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3 ParamTitlesChanged: '%1' -> '%2'\n", p.label, tchar_to_utf8 (pi.title)));
}
#endif
p.label = tchar_to_utf8 (pi.title).c_str ();
p.normal = pi.defaultNormalizedValue;
}
}
send_processors_changed (RouteProcessorChange ()); /* EMIT SIGNAL */
}
if (flags & Vst::kIoChanged) {
warning << "VST3: Vst::kIoChanged (not implemented)" << endmsg;
#if 0
update_processor ();
// TODO getBusArrangement(); enable_io()
#endif
return kNotImplemented;
}
return kResultOk;
}
tresult
VST3PI::notifyUnitSelection (Vst::UnitID unitId)
{
return kResultFalse;
}
tresult
VST3PI::notifyProgramListChange (Vst::ProgramListID, int32)
{
float v = 0;
Vst::ParamID id = _program_change_port.id;
if (id != Vst::kNoParamId) {
v = _controller->getParamNormalized (id);
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::notifyProgramListChange: val: %1 (norm: %2)\n", v, _controller->normalizedParamToPlain (id, v)));
}
OnParameterChange (PresetChange, 0, v); /* EMIT SIGNAL */
return kResultOk;
}
tresult
VST3PI::performEdit (Vst::ParamID id, Vst::ParamValue v)
{
std::map<Vst::ParamID, uint32_t>::const_iterator idx = _ctrl_id_index.find (id);
if (idx != _ctrl_id_index.end ()) {
float value = v;
_shadow_data[idx->second] = value;
_update_ctrl[idx->second] = true;
/* set_parameter_internal() is called via OnParameterChange */
value = _controller->normalizedParamToPlain (id, value);
OnParameterChange (ValueChange, idx->second, value); /* EMIT SIGNAL */
}
return kResultOk;
}
tresult
VST3PI::beginEdit (Vst::ParamID id)
{
std::map<Vst::ParamID, uint32_t>::const_iterator idx = _ctrl_id_index.find (id);
if (idx != _ctrl_id_index.end ()) {
OnParameterChange (BeginGesture, idx->second, 0); /* EMIT SIGNAL */
}
return kResultOk;
}
tresult
VST3PI::endEdit (Vst::ParamID id)
{
std::map<Vst::ParamID, uint32_t>::const_iterator idx = _ctrl_id_index.find (id);
if (idx != _ctrl_id_index.end ()) {
OnParameterChange (EndGesture, idx->second, 0); /* EMIT SIGNAL */
}
return kResultOk;
}
tresult
VST3PI::setDirty (TBool state)
{
if (state) {
OnParameterChange (InternalChange, 0, 0); /* EMIT SIGNAL */
}
return kResultOk;
}
tresult
VST3PI::requestOpenEditor (FIDString name)
{
if (name == Vst::ViewType::kEditor) {
/* TODO get plugin-insert (first plugin only, not replicated ones)
* call pi->ShowUI ();
*/
}
return kNotImplemented;
}
tresult
VST3PI::startGroupEdit ()
{
/* TODO:
* remember current time, update StartTouch API
* to allow passing a timestamp to PluginInsert::start_touch
* replacing .audible_sample()
*/
return kNotImplemented;
}
tresult
VST3PI::finishGroupEdit ()
{
return kNotImplemented;
}
bool
VST3PI::deactivate ()
{
if (!_is_processing) {
return true;
}
tresult res = _processor->setProcessing (false);
if (!(res == kResultOk || res == kNotImplemented)) {
return false;
}
res = _component->setActive (false);
if (!(res == kResultOk || res == kNotImplemented)) {
return false;
}
_is_processing = false;
return true;
}
bool
VST3PI::activate ()
{
if (_is_processing) {
return true;
}
tresult res = _component->setActive (true);
if (!(res == kResultOk || res == kNotImplemented)) {
return false;
}
res = _processor->setProcessing (true);
if (!(res == kResultOk || res == kNotImplemented)) {
return false;
}
_plugin_latency.reset ();
_is_processing = true;
return true;
}
bool
VST3PI::update_processor ()
{
bool was_active = _is_processing;
if (!deactivate ()) {
return false;
}
Vst::ProcessSetup setup;
setup.processMode = _process_offline || AudioEngine::instance ()->freewheeling () ? Vst::kOffline : Vst::kRealtime;
setup.symbolicSampleSize = Vst::kSample32;
setup.maxSamplesPerBlock = _block_size;
setup.sampleRate = _context.sampleRate;
if (_processor->setupProcessing (setup) != kResultOk) {
return false;
}
if (was_active) {
return activate ();
}
return true;
}
uint32_t
VST3PI::plugin_latency ()
{
if (!_plugin_latency) {
_plugin_latency = _processor->getLatencySamples ();
}
return _plugin_latency.value ();
}
void
VST3PI::set_owner (SessionObject* o)
{
_owner = o;
if (!o) {
_strip_connections.drop_connections ();
_ac_connection_list.drop_connections ();
_ac_subscriptions.clear ();
return;
}
if (!setup_psl_info_handler ()) {
setup_info_listener ();
}
}
void
VST3PI::set_non_realtime (bool yn)
{
_process_offline = yn;
}
int32
VST3PI::count_channels (Vst::MediaType media, Vst::BusDirection dir, Vst::BusType type)
{
/* see also libs/ardour/vst3_scan.cc count_channels */
int32 n_busses = _component->getBusCount (media, dir);
int32 n_channels = 0;
for (int32 i = 0; i < n_busses; ++i) {
Vst::BusInfo bus;
if (_component->getBusInfo (media, dir, i, bus) == kResultTrue && bus.busType == type) {
std::string bus_name = tchar_to_utf8 (bus.name);
bool is_sidechain = (type == Vst::kAux) && (dir == Vst::kInput);
if (media == Vst::kEvent) {
#if 0
/* Supported MIDI Channel count (for a single MIDI input) */
if (bus.channelCount > 0) {
_io_name[media][dir].push_back (Plugin::IOPortDescription (bus_name, is_sidechain));
}
return std::min<int32> (1, bus.channelCount);
#else
/* Some plugin leave it at zero, even though they accept events */
_io_name[media][dir].push_back (Plugin::IOPortDescription (bus_name, is_sidechain, "", 0, i));
return 1;
#endif
} else {
for (int32_t j = 0; j < bus.channelCount; ++j) {
std::string channel_name;
if (bus.channelCount > 1) {
channel_name = string_compose ("%1 %2", bus_name, j + 1);
} else {
channel_name = bus_name;
}
_io_name[media][dir].push_back (Plugin::IOPortDescription (channel_name, is_sidechain, bus_name, j, i));
}
n_channels += bus.channelCount;
if (dir == Vst::kInput) {
_bus_info_in.insert (std::make_pair (i, AudioBusInfo (type, bus.channelCount, bus.flags & Vst::BusInfo::kDefaultActive)));
} else {
_bus_info_out.insert (std::make_pair (i, AudioBusInfo (type, bus.channelCount, bus.flags & Vst::BusInfo::kDefaultActive)));
}
}
}
}
return n_channels;
}
Vst::ParamID
VST3PI::index_to_id (uint32_t p) const
{
assert (_ctrl_index_id.find (p) != _ctrl_index_id.end ());
return (_ctrl_index_id.find (p))->second;
}
bool
VST3PI::set_block_size (int32_t n_samples)
{
if (_block_size == n_samples) {
return true;
}
_block_size = n_samples;
return update_processor ();
}
float
VST3PI::default_value (uint32_t port) const
{
Vst::ParamID id (index_to_id (port));
return _controller->normalizedParamToPlain (id, _ctrl_params[port].normal);
}
void
VST3PI::get_parameter_descriptor (uint32_t port, ParameterDescriptor& desc) const
{
Param const& p (_ctrl_params[port]);
Vst::ParamID id (index_to_id (port));
desc.lower = _controller->normalizedParamToPlain (id, 0.f);
desc.upper = _controller->normalizedParamToPlain (id, 1.f);
desc.normal = _controller->normalizedParamToPlain (id, p.normal);
desc.toggled = 1 == p.steps;
desc.logarithmic = false;
desc.integer_step = p.steps > 1 && (desc.upper - desc.lower) == p.steps;
desc.sr_dependent = false;
desc.enumeration = p.is_enum;
desc.label = p.label;
if (p.unit == "dB") {
desc.unit = ARDOUR::ParameterDescriptor::DB;
} else if (p.unit == "Hz") {
desc.unit = ARDOUR::ParameterDescriptor::HZ;
}
if (p.steps > 1) {
desc.rangesteps = 1 + p.steps;
}
FUnknownPtr<IEditControllerExtra> extra_ctrl (_controller);
if (extra_ctrl && port != designated_bypass_port ()) {
int32 flags = extra_ctrl->getParamExtraFlags (id);
if (Config->get_show_vst3_micro_edit_inline ()) {
desc.inline_ctrl = (flags & kParamFlagMicroEdit) ? true : false;
}
}
}
std::string
VST3PI::print_parameter (uint32_t port) const
{
Vst::ParamID id (index_to_id (port));
return print_parameter (id, _shadow_data[port]);
}
std::string
VST3PI::print_parameter (Vst::ParamID id, Vst::ParamValue value) const
{
Vst::String128 rv;
if (_controller->getParamStringByValue (id, value, rv) == kResultOk) {
return tchar_to_utf8 (rv);
}
return "";
}
uint32_t
VST3PI::n_audio_inputs (bool with_aux) const
{
return _n_inputs + (with_aux ? _n_aux_inputs : 0);
}
uint32_t
VST3PI::n_audio_outputs (bool with_aux) const
{
return _n_outputs + (with_aux ? _n_aux_outputs : 0);
}
uint32_t
VST3PI::n_midi_inputs () const
{
return _n_midi_inputs;
}
uint32_t
VST3PI::n_midi_outputs () const
{
return _n_midi_outputs;
}
Plugin::IOPortDescription
VST3PI::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) const
{
switch (dt) {
case DataType::AUDIO:
return _io_name[Vst::kAudio][input ? 0 : 1][id];
break;
case DataType::MIDI:
return _io_name[Vst::kEvent][input ? 0 : 1][id];
break;
default:
return Plugin::IOPortDescription ("?");
break;
}
}
bool
VST3PI::try_set_parameter_by_id (Vst::ParamID id, float value)
{
std::map<Vst::ParamID, uint32_t>::const_iterator idx = _ctrl_id_index.find (id);
if (idx == _ctrl_id_index.end ()) {
return false;
}
set_parameter (idx->second, value, 0, true /* OK, called from set_state */);
return true;
}
void
VST3PI::set_parameter (uint32_t p, float value, int32 sample_off, bool to_list, bool force)
{
Vst::ParamID id = index_to_id (p);
value = _controller->plainParamToNormalized (id, value);
if (_shadow_data[p] == value && sample_off == 0 && to_list && !force) {
return;
}
if (to_list && (sample_off == 0 || parameter_is_automatable (p))) {
set_parameter_internal (id, value, sample_off);
}
_shadow_data[p] = value;
_update_ctrl[p] = true;
}
bool
VST3PI::set_program (int pgm, int32 sample_off)
{
if (_program_change_port.id == Vst::kNoParamId) {
return false;
}
if (_n_factory_presets < 1) {
return false;
}
if (pgm < 0 || pgm >= _n_factory_presets) {
return false;
}
Vst::ParamID id = _program_change_port.id;
#if 0
/* This fails with some plugins (e.g. waves.vst3),
* that do not use integer indexed presets.
*/
float value = _controller->plainParamToNormalized (id, pgm);
#else
float value = pgm;
if (_n_factory_presets > 1) {
value /= (_n_factory_presets - 1.f);
}
#endif
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::set_program pgm: %1 val: %2 (norm: %3)\n", pgm, value, _controller->plainParamToNormalized (id, pgm)));
/* must not be called concurrently with processing */
int32 index;
_input_param_changes.addParameterData (id, index)->addPoint (sample_off, value, index);
_controller->setParamNormalized (id, value);
#if 0
update_shadow_data ();
synchronize_states ();
#endif
return true;
}
bool
VST3PI::synchronize_states ()
{
RAMStream stream;
if (_component->getState (&stream) == kResultTrue) {
stream.rewind ();
tresult res = _controller->setComponentState (&stream);
if (!(res == kResultOk || res == kNotImplemented)) {
#ifndef NDEBUG
std::cerr << "Failed to synchronize VST3 component <> controller state\n";
stream.hexdump (0);
#endif
}
return res == kResultOk;
}
return false;
}
void
VST3PI::update_shadow_data ()
{
std::map<uint32_t, Vst::ParamID>::const_iterator i;
for (i = _ctrl_index_id.begin (); i != _ctrl_index_id.end (); ++i) {
Vst::ParamValue v = _controller->getParamNormalized (i->second);
if (_shadow_data[i->first] != v) {
#if 0 // DEBUG
printf ("VST3PI::update_shadow_data %d: %f -> %f\n", i->first,
_shadow_data[i->first], _controller->getParamNormalized (i->second));
#endif
#if 1 // needed for set_program() changes to take effect, after kParamValuesChanged
int32 index;
_input_param_changes.addParameterData (i->second, index)->addPoint (0, v, index);
#endif
_shadow_data[i->first] = v;
_update_ctrl[i->first] = true;
OnParameterChange (ParamValueChanged, i->first, v); /* EMIT SIGNAL */
}
}
}
void
VST3PI::update_contoller_param ()
{
/* GUI thread */
FUnknownPtr<Vst::IEditControllerHostEditing> host_editing (_controller);
std::map<uint32_t, Vst::ParamID>::const_iterator i;
for (i = _ctrl_index_id.begin (); i != _ctrl_index_id.end (); ++i) {
if (!_update_ctrl[i->first]) {
continue;
}
_update_ctrl[i->first] = false;
if (host_editing && !parameter_is_automatable (i->first) && !parameter_is_readonly (i->first)) {
host_editing->beginEditFromHost (i->second);
}
_controller->setParamNormalized (i->second, _shadow_data[i->first]);
if (host_editing && !parameter_is_automatable (i->first) && !parameter_is_readonly (i->first)) {
host_editing->endEditFromHost (i->second);
}
}
}
void
VST3PI::set_parameter_by_id (Vst::ParamID id, float value, int32 sample_off)
{
/* called in rt-thread from evoral_to_vst3 */
set_parameter_internal (id, value, sample_off);
std::map<Vst::ParamID, uint32_t>::const_iterator idx = _ctrl_id_index.find (id);
if (idx != _ctrl_id_index.end ()) {
_shadow_data[idx->second] = value;
_update_ctrl[idx->second] = true;
}
}
void
VST3PI::set_parameter_internal (Vst::ParamID id, float value, int32 sample_off)
{
int32 index;
/* must not be called concurrently with processing */
_input_param_changes.addParameterData (id, index)->addPoint (sample_off, value, index);
}
float
VST3PI::get_parameter (uint32_t p) const
{
Vst::ParamID id = index_to_id (p);
if (_update_ctrl[p]) {
_update_ctrl[p] = false;
FUnknownPtr<Vst::IEditControllerHostEditing> host_editing (_controller);
if (host_editing && !parameter_is_automatable (p) && !parameter_is_readonly (p)) {
host_editing->beginEditFromHost (id);
}
_controller->setParamNormalized (id, _shadow_data[p]); // GUI thread only
if (host_editing && !parameter_is_automatable (p) && !parameter_is_readonly (p)) {
host_editing->endEditFromHost (id);
}
}
return _controller->normalizedParamToPlain (id, _shadow_data[p]);
}
bool
VST3PI::live_midi_cc (int32_t bus, int16_t channel, Vst::CtrlNumber ctrl)
{
FUnknownPtr<Vst::IMidiLearn> midiLearn (_controller);
if (!midiLearn) {
return false;
}
return kResultOk == midiLearn->onLiveMIDIControllerInput (bus, channel, ctrl);
}
bool
VST3PI::midi_controller (int32_t bus, int16_t channel, Vst::CtrlNumber ctrl, Vst::ParamID& id)
{
FUnknownPtr<Vst::IMidiMapping> midiMapping (_controller);
if (!midiMapping) {
return false;
}
return kResultOk == midiMapping->getMidiControllerAssignment (bus, channel, ctrl, id);
}
void
VST3PI::cycle_start ()
{
_input_events.clear ();
_output_events.clear ();
}
void
VST3PI::add_event (Evoral::Event<samplepos_t> const& ev, int32_t bus)
{
Vst::Event e;
e.busIndex = bus;
e.flags = ev.is_live_midi () ? Vst::Event::kIsLive : 0;
e.sampleOffset = ev.time ();
e.ppqPosition = _context.projectTimeMusic;
if (evoral_to_vst3 (e, ev, bus)) {
_input_events.addEvent (e);
}
}
void
VST3PI::set_event_bus_state (bool enable)
{
int32 n_bus_in = _component->getBusCount (Vst::kEvent, Vst::kInput);
int32 n_bus_out = _component->getBusCount (Vst::kEvent, Vst::kOutput);
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::set_event_bus_state: n_bus_in = %1 n_bus_in = %2 enable = %3\n", n_bus_in, n_bus_out, enable));
for (int32 i = 0; i < n_bus_in; ++i) {
_component->activateBus (Vst::kEvent, Vst::kInput, i, enable);
}
for (int32 i = 0; i < n_bus_out; ++i) {
_component->activateBus (Vst::kEvent, Vst::kOutput, i, enable);
}
}
void
VST3PI::enable_io (std::vector<bool> const& ins, std::vector<bool> const& outs)
{
if (_enabled_audio_in == ins && _enabled_audio_out == outs) {
return;
}
/* some plugins, notably JUCE based ones, require the plugin to be
* inactive to change bus arrangements.
*/
bool was_active = _is_processing;
if (!deactivate ()) {
DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::enable_io failed to deactivate plugin\n");
}
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: ins = %1 == %3 outs = %2 == %4\n", ins.size (), outs.size (), n_audio_inputs (), n_audio_outputs ()));
_enabled_audio_in = ins;
_enabled_audio_out = outs;
assert (_enabled_audio_in.size () == n_audio_inputs ());
assert (_enabled_audio_out.size () == n_audio_outputs ());
/* check that settings have not changed */
assert (_n_bus_in == _component->getBusCount (Vst::kAudio, Vst::kInput));
assert (_n_bus_out == _component->getBusCount (Vst::kAudio, Vst::kOutput));
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: n_bus_in = %1 n_bus_out = %2\n", _n_bus_in, _n_bus_out));
typedef std::vector<Vst::SpeakerArrangement> VSTSpeakerArrangements;
VSTSpeakerArrangements sa_in;
VSTSpeakerArrangements sa_out;
size_t cnt = 0;
while (sa_in.size () < (VSTSpeakerArrangements::size_type) _n_bus_in) {
bool enable = false;
int32_t const n_chn = _bus_info_in[sa_in.size ()].n_chn;
Vst::SpeakerArrangement sa = 0;
_bus_info_in[sa_in.size ()].n_used_chn = 0;
/* use all channels before the last connected one */
for (int32_t i = n_chn - 1; i >= 0; --i) {
if (ins[cnt + i] || enable) {
sa |= (uint64_t)1 << i;
++_bus_info_in[sa_in.size ()].n_used_chn;
enable = true;
}
}
cnt += n_chn;
/* special case for Left only == Mono */
if (sa == 1 /*Vst::SpeakerArr::kSpeakerL */ && !_no_kMono) {
sa = Vst::SpeakerArr::kMono; /* 1 << 19 */
}
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: activateBus (kAudio, kInput, %1, %2) used-chn: %3 spk-arr: %4\n", sa_in.size (), enable, _bus_info_in[sa_in.size ()].n_used_chn, std::hex, sa));
_component->activateBus (Vst::kAudio, Vst::kInput, sa_in.size (), enable);
sa_in.push_back (sa);
}
cnt = 0;
while (sa_out.size () < (VSTSpeakerArrangements::size_type) _n_bus_out) {
bool enable = false;
int32_t const n_chn = _bus_info_out[sa_out.size ()].n_chn;
Vst::SpeakerArrangement sa = 0;
_bus_info_out[sa_out.size ()].n_used_chn = 0;
for (int32_t i = n_chn - 1; i >= 0; --i) {
if (outs[cnt + i] || enable) {
sa |= (uint64_t)1 << i;
++_bus_info_out[sa_out.size ()].n_used_chn;
enable = true;
}
}
cnt += n_chn;
/* special case for Left only == Mono */
if (sa == 1 /*Vst::SpeakerArr::kSpeakerL */ && !_no_kMono) {
sa = Vst::SpeakerArr::kMono; /* 1 << 19 */
}
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: activateBus (kAudio, kOutput, %1, %2) used-chn: %3 spk-arr: %4\n", sa_out.size (), enable, _bus_info_out[sa_out.size ()].n_used_chn, std::hex, sa));
_component->activateBus (Vst::kAudio, Vst::kOutput, sa_out.size (), enable);
sa_out.push_back (sa);
}
Vst::SpeakerArrangement null_arrangement = {};
#ifndef NDEBUG
tresult rv =
#endif
_processor->setBusArrangements (sa_in.size () > 0 ? &sa_in[0] : &null_arrangement, sa_in.size (),
sa_out.size () > 0 ? &sa_out[0] : &null_arrangement, sa_out.size ());
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: setBusArrangements ins = %1 outs = %2 | rv = %3\n", sa_in.size (), sa_out.size (), rv));
/* https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IAudioProcessor.html#ad3bc7bac3fd3b194122669be2a1ecc42 */
for (int32 i = 0; i < _n_bus_in; ++i) {
Vst::SpeakerArrangement arr;
if (_processor->getBusArrangement (Vst::kInput, i, arr) == kResultOk) {
int cc = Vst::SpeakerArr::getChannelCount (arr);
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI: Input BusArrangements: %1 chan: %2 bits: %3%4\n", i, cc, std::hex, arr));
assert (cc <= _bus_info_in[i].n_chn);
if (cc <= _bus_info_in[i].n_chn) {
_bus_info_in[i].n_used_chn = cc;
}
}
}
for (int32 i = 0; i < _n_bus_out; ++i) {
Vst::SpeakerArrangement arr;
if (_processor->getBusArrangement (Vst::kOutput, i, arr) == kResultOk) {
int cc = Vst::SpeakerArr::getChannelCount (arr);
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI: Output BusArrangements: %1 chan: %2 bits: %3%4\n", i, cc, std::hex, arr));
assert (cc <= _bus_info_out[i].n_chn);
if (cc <= _bus_info_out[i].n_chn) {
_bus_info_out[i].n_used_chn = cc;
}
}
}
if (was_active) {
activate ();
}
}
void
VST3PI::process (float** ins, float** outs, uint32_t n_samples)
{
Vst::AudioBusBuffers* inputs = _n_bus_in > 0 ? &_busbuf_in[0] : NULL;
Vst::AudioBusBuffers* outputs = _n_bus_out > 0 ? &_busbuf_out[0] : NULL;
Vst::ProcessData data;
data.numSamples = n_samples;
data.processMode = _process_offline || AudioEngine::instance ()->freewheeling () ? Vst::kOffline : Vst::kRealtime;
data.symbolicSampleSize = Vst::kSample32;
data.numInputs = _n_bus_in;
data.numOutputs = _n_bus_out;
data.inputs = inputs;
data.outputs = outputs;
data.processContext = &_context;
data.inputEvents = &_input_events;
data.outputEvents = &_output_events;
data.inputParameterChanges = &_input_param_changes;
data.outputParameterChanges = &_output_param_changes;
uint32_t used_ins = 0;
uint32_t used_outs = 0;
for (int i = 0; i < _n_bus_in; ++i) {
inputs[i].silenceFlags = 0;
inputs[i].numChannels = _bus_info_in[i].n_used_chn;
inputs[i].channelBuffers32 = &ins[used_ins];
used_ins += _bus_info_in[i].n_chn;
}
for (int i = 0; i < _n_bus_out; ++i) {
outputs[i].silenceFlags = 0;
outputs[i].numChannels = _bus_info_out[i].n_used_chn;
outputs[i].channelBuffers32 = &outs[used_outs];
used_outs += _bus_info_out[i].n_chn;
}
assert (used_ins == n_audio_inputs ());
assert (used_outs == n_audio_outputs ());
/* and go */
if (_processor->process (data) != kResultOk) {
DEBUG_TRACE (DEBUG::VST3Process, "VST3 process error\n");
}
/* handle output parameter changes */
int n_changes = _output_param_changes.getParameterCount ();
for (int i = 0; i < n_changes; ++i) {
Vst::IParamValueQueue* data = _output_param_changes.getParameterData (i);
if (!data) {
continue;
}
Vst::ParamID id = data->getParameterId ();
int n_points = data->getPointCount ();
if (n_points == 0) {
continue;
}
std::map<Vst::ParamID, uint32_t>::const_iterator idx = _ctrl_id_index.find (id);
if (idx != _ctrl_id_index.end ()) {
/* automatable parameter, or read-only output */
int32 offset = 0;
Vst::ParamValue value = 0;
/* only get most recent point */
if (data->getPoint (n_points - 1, offset, value) == kResultOk) {
if (_shadow_data[idx->second] != value) {
_update_ctrl[idx->second] = true;
_shadow_data[idx->second] = (float)value;
}
}
} else {
#ifndef NDEBUG
/* non-automatable parameter */
std::cerr << "VST3: TODO non-automatable output param..\n"; // TODO inform UI
#endif
}
}
_input_param_changes.clear ();
_output_param_changes.clear ();
}
/* ****************************************************************************
* State
* compare to public.sdk/source/vst/vstpresetfile.cpp
*/
namespace Steinberg {
namespace Vst {
enum ChunkType {
kHeader,
kComponentState,
kControllerState,
kProgramData,
kMetaInfo,
kChunkList,
kNumPresetChunks
};
static const ChunkID commonChunks[kNumPresetChunks] = {
{ 'V', 'S', 'T', '3' }, // kHeader
{ 'C', 'o', 'm', 'p' }, // kComponentState
{ 'C', 'o', 'n', 't' }, // kControllerState
{ 'P', 'r', 'o', 'g' }, // kProgramData
{ 'I', 'n', 'f', 'o' }, // kMetaInfo
{ 'L', 'i', 's', 't' } // kChunkList
};
static const int32 kFormatVersion = 1;
static const ChunkID&
getChunkID (ChunkType type)
{
return commonChunks[type];
}
struct ChunkEntry {
void start_chunk (const ChunkID& id, RAMStream& stream)
{
memcpy (_id, &id, sizeof (ChunkID));
stream.tell (&_offset);
_size = 0;
}
void end_chunk (RAMStream& stream)
{
int64 pos = 0;
stream.tell (&pos);
_size = pos - _offset;
}
ChunkID _id;
int64 _offset;
int64 _size;
};
} // namespace Vst
typedef std::vector<Vst::ChunkEntry> ChunkEntryVector;
} // namespace Steinberg
static bool
is_equal_ID (const Vst::ChunkID id1, const Vst::ChunkID id2)
{
return 0 == memcmp (id1, id2, sizeof (Vst::ChunkID));
}
static bool
read_equal_ID (RAMStream& stream, const Vst::ChunkID id)
{
Vst::ChunkID tmp;
return stream.read_ChunkID (tmp) && is_equal_ID (tmp, id);
}
bool
VST3PI::load_state (RAMStream& stream)
{
assert (stream.readonly ());
if (stream.size () < Vst::kHeaderSize) {
return false;
}
int32 version = 0;
int64 list_offset = 0;
TUID class_id;
if (!(read_equal_ID (stream, Vst::getChunkID (Vst::kHeader))
&& stream.read_int32 (version)
&& stream.read_TUID (class_id)
&& stream.read_int64 (list_offset)
&& list_offset > 0
)
) {
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::load_state: invalid header vers: %1 off: %2\n", version, list_offset));
return false;
}
if (_fuid != FUID::fromTUID (class_id)) {
DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::load_state: class ID mismatch\n");
return false;
}
/* read chunklist */
ChunkEntryVector entries;
int64 seek_result = 0;
stream.seek (list_offset, IBStream::kIBSeekSet, &seek_result);
if (seek_result != list_offset) {
return false;
}
if (!read_equal_ID (stream, Vst::getChunkID (Vst::kChunkList))) {
return false;
}
PBD::Unwinder<bool> uw (_is_loading_state, true);
int32 count;
stream.read_int32 (count);
for (int32 i = 0; i < count; ++i) {
Vst::ChunkEntry c;
stream.read_ChunkID (c._id);
stream.read_int64 (c._offset);
stream.read_int64 (c._size);
entries.push_back (c);
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::load_state: chunk: %1 off: %2 size: %3 type: %4\n", i, c._offset, c._size, c._id));
}
bool rv = true;
bool synced = false;
/* parse chunks */
for (ChunkEntryVector::const_iterator i = entries.begin (); i != entries.end (); ++i) {
stream.seek (i->_offset, IBStream::kIBSeekSet, &seek_result);
if (seek_result != i->_offset) {
rv = false;
continue;
}
if (is_equal_ID (i->_id, Vst::getChunkID (Vst::kComponentState))) {
ROMStream s (stream, i->_offset, i->_size);
tresult res = _component->setState (&s);
s.rewind ();
tresult re2 = _controller->setComponentState (&s);
if (re2 == kResultOk) {
synced = true;
}
if (!(re2 == kResultOk || re2 == kNotImplemented || res == kResultOk || res == kNotImplemented)) {
DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::load_state: failed to restore component state\n");
rv = false;
}
} else if (is_equal_ID (i->_id, Vst::getChunkID (Vst::kControllerState))) {
ROMStream s (stream, i->_offset, i->_size);
tresult res = _controller->setState (&s);
if (res == kResultOk) {
synced = true;
}
if (!(res == kResultOk || res == kNotImplemented)) {
DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::load_state: failed to restore controller state\n");
rv = false;
}
}
#if 0
else if (is_equal_ID (i->_id, Vst::getChunkID (Vst::kProgramData))) {
Vst::IUnitInfo* unitInfo = unit_info ();
stream.seek (i->_offset, IBStream::kIBSeekSet, &seek_result);
int32 id = -1;
if (stream.read_int32 (id)) {
ROMStream s (stream, i->_offset + sizeof (int32), i->_size - sizeof (int32));
unit_info->setUnitProgramData (id, programIndex, s);
//unit_data->setUnitData (id, programIndex, s)
}
}
#endif
else {
DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::load_state: ignored unsupported state chunk.\n");
}
}
if (rv && !synced) {
synced = synchronize_states ();
}
if (rv && synced) {
update_shadow_data ();
}
return rv;
}
bool
VST3PI::save_state (RAMStream& stream)
{
assert (!stream.readonly ());
Vst::ChunkEntry c;
ChunkEntryVector entries;
/* header */
stream.write_ChunkID (Vst::getChunkID (Vst::kHeader));
stream.write_int32 (Vst::kFormatVersion);
stream.write_TUID (_fuid.toTUID ()); // class ID
stream.write_int64 (0); // skip offset
/* state chunks */
c.start_chunk (getChunkID (Vst::kComponentState), stream);
if (_component->getState (&stream) == kResultTrue) {
c.end_chunk (stream);
entries.push_back (c);
}
c.start_chunk (getChunkID (Vst::kControllerState), stream);
if (_controller->getState (&stream) == kResultTrue) {
c.end_chunk (stream);
entries.push_back (c);
}
/* update header */
int64 pos;
stream.tell (&pos);
stream.seek (Vst::kListOffsetPos, IBStream::kIBSeekSet, NULL);
stream.write_int64 (pos);
stream.seek (pos, IBStream::kIBSeekSet, NULL);
/* write list */
stream.write_ChunkID (Vst::getChunkID (Vst::kChunkList));
stream.write_int32 (entries.size ());
for (ChunkEntryVector::const_iterator i = entries.begin (); i != entries.end (); ++i) {
stream.write_ChunkID (i->_id);
stream.write_int64 (i->_offset);
stream.write_int64 (i->_size);
}
return entries.size () > 0;
}
/* ****************************************************************************/
void
VST3PI::stripable_property_changed (PBD::PropertyChange const&)
{
FUnknownPtr<Vst::ChannelContext::IInfoListener> il (_controller);
Stripable* s = dynamic_cast<Stripable*> (_owner);
assert (il && s);
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::stripable_property_changed\n");
IPtr<HostAttributeList> al (new HostAttributeList ());
Vst::String128 tmp;
utf8_to_tchar (tmp, _owner->name (), 128);
al->setInt (Vst::ChannelContext::kChannelNameLengthKey, _owner->name ().size ());
al->setString (Vst::ChannelContext::kChannelNameKey, tmp);
utf8_to_tchar (tmp, _owner->id ().to_s (), 128);
al->setInt (Vst::ChannelContext::kChannelNameLengthKey, _owner->id ().to_s ().size ());
al->setString (Vst::ChannelContext::kChannelUIDKey, tmp);
std::string ns;
int order_key;
if (s->is_master () || s->is_surround_master ()) {
ns = _("Master");
order_key = 2;
} else if (s->is_monitor ()) {
ns = _("Monitor");
order_key = 3;
} else {
ns = _("Track");
order_key = 1;
}
al->setInt (Vst::ChannelContext::kChannelIndexNamespaceOrderKey, order_key);
al->setInt (Vst::ChannelContext::kChannelIndexKey, 1 + s->presentation_info ().order ());
utf8_to_tchar (tmp, ns, 128);
al->setInt (Vst::ChannelContext::kChannelIndexNamespaceLengthKey, ns.size ());
al->setString (Vst::ChannelContext::kChannelIndexNamespaceKey, tmp);
uint32_t rgba = s->presentation_info ().color ();
Vst::ChannelContext::ColorSpec argb = ((rgba >> 8) & 0xffffff) | ((rgba & 0xff) << 24);
al->setInt (Vst::ChannelContext::kChannelColorKey, argb);
al->setInt (Vst::ChannelContext::kChannelPluginLocationKey, Vst::ChannelContext::kPreVolumeFader); // XXX
il->setChannelContextInfos (al);
}
bool
VST3PI::setup_info_listener ()
{
FUnknownPtr<Vst::ChannelContext::IInfoListener> il (_controller);
if (!il) {
return false;
}
DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::setup_info_listener\n");
Stripable* s = dynamic_cast<Stripable*> (_owner);
s->PropertyChanged.connect_same_thread (_strip_connections, boost::bind (&VST3PI::stripable_property_changed, this, _1));
s->presentation_info ().PropertyChanged.connect_same_thread (_strip_connections, boost::bind (&VST3PI::stripable_property_changed, this, _1));
/* send initial change */
stripable_property_changed (PropertyChange ());
return true;
}
/* ****************************************************************************
* PSL Extensions
*/
bool
VST3PI::add_slave (Vst::IEditController* c, bool rt)
{
FUnknownPtr<ISlaveControllerHandler> slave_ctrl (_controller);
if (slave_ctrl) {
return slave_ctrl->addSlave (c, rt ? kSlaveModeLowLatencyClone : kSlaveModeNormal) == kResultOk;
}
return false;
}
bool
VST3PI::remove_slave (Vst::IEditController* c)
{
FUnknownPtr<ISlaveControllerHandler> slave_ctrl (_controller);
if (slave_ctrl) {
return slave_ctrl->removeSlave (c) == kResultOk;
}
return false;
}
bool
VST3PI::subscribe_to_automation_changes () const
{
FUnknownPtr<IEditControllerExtra> extra_ctrl (_controller);
return 0 != extra_ctrl ? true : false;
}
void
VST3PI::automation_state_changed (uint32_t port, AutoState s, std::weak_ptr<AutomationList> wal)
{
Vst::ParamID id (index_to_id (port));
std::shared_ptr<AutomationList> al = wal.lock ();
FUnknownPtr<IEditControllerExtra> extra_ctrl (_controller);
assert (extra_ctrl);
AutomationMode am;
switch (s) {
case ARDOUR::Off:
if (!al || al->empty ()) {
am = kAutomationNone;
} else {
am = kAutomationOff;
}
break;
case Write:
am = kAutomationWrite;
break;
case Touch:
am = kAutomationTouch;
break;
case Play:
am = kAutomationRead;
break;
case Latch:
am = kAutomationLatch;
break;
default:
return;
}
extra_ctrl->setParamAutomationMode (id, am);
}
/* ****************************************************************************/
static std::shared_ptr<AutomationControl>
lookup_ac (SessionObject* o, FIDString id)
{
Stripable* s = dynamic_cast<Stripable*> (o);
if (!s) {
return std::shared_ptr<AutomationControl> ();
}
if (0 == strcmp (id, ContextInfo::kMute)) {
return s->mute_control ();
} else if (0 == strcmp (id, ContextInfo::kSolo)) {
return s->solo_control ();
} else if (0 == strcmp (id, ContextInfo::kPan)) {
return s->pan_azimuth_control ();
} else if (0 == strcmp (id, ContextInfo::kVolume)) {
return s->gain_control ();
} else if (0 == strncmp (id, ContextInfo::kSendLevel, strlen (ContextInfo::kSendLevel))) {
#ifdef MIXBUS
/* Only use mixbus sends, which are identified by providing a
* send_enable_controllable().
*
* The main reason is that the number of Mixbus sends
* per route is fixed, but this also works around a crash:
*
* For Ardour sends, send_level_controllable() calls
* Route::nth_send() which takes the _processor_lock.
*
* However this callback can be triggered initially
* Route::add_processors () -> set_owner() ->
* setup_psl_info_handler() -> ..notify..
* with process and processor locks held, leading to
* recurive locks (deadlock, or double unlock crash).
*/
int send_id = atoi (id + strlen (ContextInfo::kSendLevel));
if (s->send_enable_controllable (send_id)) {
return s->send_level_controllable (send_id);
}
#endif
}
return std::shared_ptr<AutomationControl> ();
}
tresult
VST3PI::getContextInfoValue (int32& value, FIDString id)
{
Stripable* s = dynamic_cast<Stripable*> (_owner);
if (!s) {
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::getContextInfoValue<int>: not initialized");
return kNotInitialized;
}
if (0 == strcmp (id, ContextInfo::kIndexMode)) {
value = ContextInfo::kFlatIndex;
} else if (0 == strcmp (id, ContextInfo::kType)) {
if (s->is_singleton ()) {
value = ContextInfo::kOut;
} else if (s->presentation_info ().flags () & PresentationInfo::AudioTrack) {
value = ContextInfo::kTrack;
} else if (s->presentation_info ().flags () & PresentationInfo::MidiTrack) {
value = ContextInfo::kSynth;
} else {
value = ContextInfo::kBus;
}
} else if (0 == strcmp (id, ContextInfo::kMain)) {
value = s->is_singleton () ? 1 : 0;
} else if (0 == strcmp (id, ContextInfo::kIndex)) {
value = s->presentation_info ().order ();
} else if (0 == strcmp (id, ContextInfo::kColor)) {
value = s->presentation_info ().color ();
#if BYTEORDER == kBigEndian
SWAP_32 (value) // RGBA32 -> ABGR32
#endif
} else if (0 == strcmp (id, ContextInfo::kVisibility)) {
value = s->is_hidden () ? 0 : 1;
} else if (0 == strcmp (id, ContextInfo::kSelected)) {
value = s->is_selected () ? 1 : 0;
} else if (0 == strcmp (id, ContextInfo::kFocused)) {
std::shared_ptr<Stripable> stripable = s->session ().selection ().first_selected_stripable ();
value = stripable && stripable.get () == s ? 1 : 0;
} else if (0 == strcmp (id, ContextInfo::kSendCount)) {
value = 0;
while (s->send_enable_controllable (value)) {
++value;
}
} else if (0 == strcmp (id, ContextInfo::kMute)) {
std::shared_ptr<MuteControl> ac = s->mute_control ();
if (ac) {
psl_subscribe_to (ac, id);
value = ac->muted_by_self ();
} else {
value = 0;
}
} else if (0 == strcmp (id, ContextInfo::kSolo)) {
std::shared_ptr<SoloControl> ac = s->solo_control ();
if (ac) {
psl_subscribe_to (ac, id);
value = ac->self_soloed ();
} else {
value = 0;
}
} else {
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoValue<int> unsupported ID %1\n", id));
return kNotImplemented;
}
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoValue<int> %1 = %2\n", id, value));
return kResultOk;
}
tresult
VST3PI::getContextInfoString (Vst::TChar* string, int32 max_len, FIDString id)
{
if (!_owner) {
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::getContextInfoString: not initialized");
return kNotInitialized;
}
if (0 == strcmp (id, ContextInfo::kID)) {
utf8_to_tchar (string, _owner->id ().to_s (), max_len);
} else if (0 == strcmp (id, ContextInfo::kName)) {
utf8_to_tchar (string, _owner->name (), max_len);
} else if (0 == strcmp (id, ContextInfo::kActiveDocumentID)) {
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoString: NOT IMPLEMENTED (%1)\n", id));
return kNotImplemented; // XXX TODO
} else if (0 == strcmp (id, ContextInfo::kDocumentID)) {
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoString: NOT IMPLEMENTED (%1)\n", id));
return kNotImplemented; // XXX TODO
} else if (0 == strcmp (id, ContextInfo::kDocumentName)) {
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoString: NOT IMPLEMENTED (%1)\n", id));
return kNotImplemented; // XXX TODO
} else if (0 == strcmp (id, ContextInfo::kDocumentFolder)) {
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoString: NOT IMPLEMENTED (%1)\n", id));
return kNotImplemented; // XXX TODO
} else if (0 == strcmp (id, ContextInfo::kAudioFolder)) {
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoString: NOT IMPLEMENTED (%1)\n", id));
return kNotImplemented; // XXX TODO
} else {
std::shared_ptr<AutomationControl> ac = lookup_ac (_owner, id);
if (!ac) {
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoString unsupported ID %1\n", id));
return kInvalidArgument;
}
utf8_to_tchar (string, ac->get_user_string (), max_len);
}
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoValue<string> %1 = %2\n", id, tchar_to_utf8 (string)));
return kResultOk;
}
tresult
VST3PI::getContextInfoValue (double& value, FIDString id)
{
Stripable* s = dynamic_cast<Stripable*> (_owner);
if (!s) {
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::getContextInfoValue<double>: not initialized");
return kNotInitialized;
}
if (0 == strcmp (id, ContextInfo::kMaxVolume)) {
value = s->gain_control ()->upper ();
} else if (0 == strcmp (id, ContextInfo::kMaxSendLevel)) {
#ifdef MIXBUS
if (s->send_level_controllable (0)) {
value = s->send_level_controllable (0)->upper (); // pow (10.0, .05 * 15.0);
}
#endif
value = 2.0; // Config->get_max_gain();
} else if (0 == strcmp (id, ContextInfo::kVolume)) {
std::shared_ptr<AutomationControl> ac = s->gain_control ();
value = ac->get_value (); // gain coefficient 0..2 (1.0 = 0dB)
psl_subscribe_to (ac, id);
} else if (0 == strcmp (id, ContextInfo::kPan)) {
std::shared_ptr<AutomationControl> ac = s->pan_azimuth_control ();
if (ac) {
value = ac->internal_to_interface (ac->get_value (), true);
psl_subscribe_to (ac, id);
} else {
value = 0.5; // center
}
} else if (0 == strncmp (id, ContextInfo::kSendLevel, strlen (ContextInfo::kSendLevel))) {
std::shared_ptr<AutomationControl> ac = lookup_ac (_owner, id);
if (ac) {
value = ac->get_value (); // gain cofficient
psl_subscribe_to (ac, id);
} else {
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoValue<double> invalid AC %1\n", id));
return kInvalidArgument; // send index out of bounds
}
} else {
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoValue<double> unsupported ID %1\n", id));
return kInvalidArgument;
}
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::getContextInfoValue<double> %1 = %2\n", id, value));
return kResultOk;
}
tresult
VST3PI::setContextInfoValue (FIDString id, double value)
{
if (!_owner) {
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::setContextInfoValue<double>: not initialized");
return kNotInitialized;
}
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoValue<double> %1 to %2\n", id, value));
if (0 == strcmp (id, ContextInfo::kVolume)) {
std::shared_ptr<AutomationControl> ac = lookup_ac (_owner, id);
ac->set_value (value, Controllable::NoGroup);
} else if (0 == strcmp (id, ContextInfo::kPan)) {
std::shared_ptr<AutomationControl> ac = lookup_ac (_owner, id);
if (ac) {
ac->set_value (ac->interface_to_internal (value, true), PBD::Controllable::NoGroup);
}
} else if (0 == strncmp (id, ContextInfo::kSendLevel, strlen (ContextInfo::kSendLevel))) {
std::shared_ptr<AutomationControl> ac = lookup_ac (_owner, id);
if (ac) {
ac->set_value (value, Controllable::NoGroup);
} else {
return kInvalidArgument; // send index out of bounds
}
} else {
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::setContextInfoValue<double>: unsupported ID\n");
return kInvalidArgument;
}
return kResultOk;
}
tresult
VST3PI::setContextInfoValue (FIDString id, int32 value)
{
Stripable* s = dynamic_cast<Stripable*> (_owner);
if (!s) {
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::setContextInfoValue<int>: not initialized");
return kNotInitialized;
}
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoValue<int> %1 to %2\n", id, value));
if (0 == strcmp (id, ContextInfo::kColor)) {
#if BYTEORDER == kBigEndian
SWAP_32 (value) // ABGR32 -> RGBA32
#endif
s->presentation_info ().set_color (value);
} else if (0 == strcmp (id, ContextInfo::kSelected)) {
std::shared_ptr<Stripable> stripable = s->session ().stripable_by_id (s->id ());
assert (stripable);
if (value == 0) {
s->session ().selection ().remove (stripable, std::shared_ptr<AutomationControl> ());
} else if (_add_to_selection) {
s->session ().selection ().add (stripable, std::shared_ptr<AutomationControl> ());
} else {
s->session ().selection ().set (stripable, std::shared_ptr<AutomationControl> ());
}
} else if (0 == strcmp (id, ContextInfo::kMultiSelect)) {
_add_to_selection = value != 0;
} else if (0 == strcmp (id, ContextInfo::kMute)) {
s->session ().set_control (lookup_ac (_owner, id), value != 0 ? 1 : 0, Controllable::NoGroup);
} else if (0 == strcmp (id, ContextInfo::kSolo)) {
s->session ().set_control (lookup_ac (_owner, id), value != 0 ? 1 : 0, Controllable::NoGroup);
} else {
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::setContextInfoValue<int>: unsupported ID\n");
return kNotImplemented;
}
return kResultOk;
}
tresult
VST3PI::setContextInfoString (FIDString id, Vst::TChar* string)
{
if (!_owner) {
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::setContextInfoString: not initialized");
return kNotInitialized;
}
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::setContextInfoString %1 to %2\n", id, tchar_to_utf8 (string)));
if (0 == strcmp (id, ContextInfo::kName)) {
return _owner->set_name (tchar_to_utf8 (string)) ? kResultOk : kResultFalse;
}
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::setContextInfoString: unsupported ID\n");
return kInvalidArgument;
}
tresult
VST3PI::beginEditContextInfoValue (FIDString id)
{
if (!_owner) {
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::beginEditContextInfoValue: not initialized");
return kNotInitialized;
}
std::shared_ptr<AutomationControl> ac = lookup_ac (_owner, id);
if (!ac) {
return kInvalidArgument;
}
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::beginEditContextInfoValue %1\n", id));
ac->start_touch (timepos_t (ac->session ().transport_sample ()));
return kResultOk;
}
tresult
VST3PI::endEditContextInfoValue (FIDString id)
{
if (!_owner) {
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::endEditContextInfoValue: not initialized");
return kNotInitialized;
}
std::shared_ptr<AutomationControl> ac = lookup_ac (_owner, id);
if (!ac) {
return kInvalidArgument;
}
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::endEditContextInfoValue %1\n", id));
ac->stop_touch (timepos_t (ac->session ().transport_sample ()));
return kResultOk;
}
void
VST3PI::psl_subscribe_to (std::shared_ptr<ARDOUR::AutomationControl> ac, FIDString id)
{
FUnknownPtr<IContextInfoHandler2> nfo2 (_controller);
if (!nfo2) {
return;
}
std::pair<std::set<Evoral::Parameter>::iterator, bool> r = _ac_subscriptions.insert (ac->parameter ());
if (!r.second) {
return;
}
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::psl_subscribe_to: %1\n", ac->name ()));
ac->Changed.connect_same_thread (_ac_connection_list, boost::bind (&VST3PI::forward_signal, this, nfo2.get (), id));
}
void
VST3PI::forward_signal (IContextInfoHandler2* handler, FIDString id) const
{
assert (handler);
DEBUG_TRACE (DEBUG::VST3Callbacks, string_compose ("VST3PI::psl_subscribtion AC changed %1\n", id));
handler->notifyContextInfoChange (id);
}
void
VST3PI::psl_stripable_property_changed (PBD::PropertyChange const& what_changed)
{
FUnknownPtr<IContextInfoHandler> nfo (_controller);
FUnknownPtr<IContextInfoHandler2> nfo2 (_controller);
if (nfo && !nfo2) {
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::psl_stripable_property_changed v1\n");
nfo->notifyContextInfoChange ();
}
if (!nfo2) {
return;
}
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::psl_stripable_property_changed v2\n");
if (what_changed.contains (Properties::selected)) {
nfo2->notifyContextInfoChange (ContextInfo::kSelected);
nfo2->notifyContextInfoChange (ContextInfo::kFocused); // XXX
}
if (what_changed.contains (Properties::hidden)) {
nfo2->notifyContextInfoChange (ContextInfo::kVisibility);
}
if (what_changed.contains (Properties::name)) {
nfo2->notifyContextInfoChange (ContextInfo::kName);
}
if (what_changed.contains (Properties::color)) {
nfo2->notifyContextInfoChange (ContextInfo::kColor);
}
}
bool
VST3PI::setup_psl_info_handler ()
{
/* initial update */
FUnknownPtr<IContextInfoHandler> nfo (_controller);
FUnknownPtr<IContextInfoHandler2> nfo2 (_controller);
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::setup_psl_info_handler: (%1) (%2)\n", nfo != 0, nfo2 != 0));
if (nfo2) {
nfo2->notifyContextInfoChange ("");
} else if (nfo) {
nfo->notifyContextInfoChange ();
}
if (!nfo && !nfo2) {
return false;
}
Stripable* s = dynamic_cast<Stripable*> (_owner);
s->PropertyChanged.connect_same_thread (_strip_connections, boost::bind (&VST3PI::psl_stripable_property_changed, this, _1));
s->presentation_info ().PropertyChanged.connect_same_thread (_strip_connections, boost::bind (&VST3PI::psl_stripable_property_changed, this, _1));
return true;
}
/* ****************************************************************************
* GUI
*/
IPlugView*
VST3PI::try_create_view () const
{
IPlugView* view = _controller->createView (Vst::ViewType::kEditor);
if (!view) {
view = _controller->createView (0);
}
if (!view) {
view = FUnknownPtr<IPlugView> (_controller).take ();
if (view) {
view->addRef ();
}
}
return view;
}
IPlugView*
VST3PI::view ()
{
if (!_view) {
_view = try_create_view ();
if (_view) {
_view->setFrame (this);
}
}
return _view;
}
void
VST3PI::close_view ()
{
if (!_view) {
return;
}
_view->removed ();
_view->setFrame (0);
_view->release ();
_view = 0;
}
bool
VST3PI::has_editor () const
{
IPlugView* view = _view;
if (!view) {
view = try_create_view ();
}
bool rv = false;
if (view) {
#ifdef PLATFORM_WINDOWS
rv = kResultOk == view->isPlatformTypeSupported (kPlatformTypeHWND);
#elif defined(__APPLE__)
rv = kResultOk == view->isPlatformTypeSupported (kPlatformTypeNSView);
#else
rv = kResultOk == view->isPlatformTypeSupported (kPlatformTypeX11EmbedWindowID);
#endif
if (!_view) {
view->release ();
}
}
return rv;
}
#if SMTG_OS_LINUX
void
VST3PI::set_runloop (Linux::IRunLoop* run_loop)
{
_run_loop = run_loop;
}
#endif
tresult
VST3PI::resizeView (IPlugView* view, ViewRect* new_size)
{
OnResizeView (new_size->getWidth (), new_size->getHeight ()); /* EMIT SIGNAL */
return view->onSize (new_size);
}
void
VST3PI::block_notifications ()
{
_block_rpc.fetch_add (1);
}
void
VST3PI::resume_notifications ()
{
if (!PBD::atomic_dec_and_test (_block_rpc)) {
return;
}
ARDOUR::RouteProcessorChange rpc (RouteProcessorChange::NoProcessorChange, false);
std::swap (rpc, _rpc_queue);
Route* r = dynamic_cast<Route*> (_owner);
if (r && _rpc_queue.type != RouteProcessorChange::NoProcessorChange) {
r->processors_changed (rpc); /* EMIT SIGNAL */
}
}
void
VST3PI::send_processors_changed (RouteProcessorChange const& rpc)
{
if (_block_rpc.load () != 0) {
_rpc_queue.type = ARDOUR::RouteProcessorChange::Type (_rpc_queue.type | rpc.type);
_rpc_queue.meter_visibly_changed |= rpc.meter_visibly_changed;
return;
}
Route* r = dynamic_cast<Route*> (_owner);
if (r) {
r->processors_changed (rpc); /* EMIT SIGNAL */
}
}