ardour/libs/ardour/vst3_plugin.cc
Robin Gareus cb0b152b35
VST3: Fix MSVC related crashes
Do not simply allocate std::vector<> space but also initialize
elements. The data is later accessed as C-pointer array: &var[0].
With most compilers simply reserving space in the vector is
sufficient in order to later access the elements directly.
However actually placing objects in the vector before referencing
them is more correct.
2021-03-02 19:05:58 +01:00

2974 lines
85 KiB
C++

/*
* Copyright (C) 2019-2020 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"
#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/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 Steinberg;
using namespace Presonus;
VST3Plugin::VST3Plugin (AudioEngine& engine, Session& session, VST3PI* plug)
: Plugin (engine, session)
, _plug (plug)
{
init ();
}
VST3Plugin::VST3Plugin (const VST3Plugin& other)
: Plugin (other)
{
boost::shared_ptr<VST3PluginInfo> nfo = boost::dynamic_pointer_cast<VST3PluginInfo> (other.get_info ());
_plug = new VST3PI (nfo->m, nfo->unique_id);
init ();
}
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 all I/O is connected by default */
for (int32_t i = 0; i < (int32_t)_plug->n_audio_inputs (); ++i) {
_connected_inputs.push_back (true);
}
for (int32_t i = 0; i < (int32_t)_plug->n_audio_outputs (); ++i) {
_connected_outputs.push_back (true);
}
/* 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:
/* 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)
{
_plug->set_parameter (port, val, when);
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, boost::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, boost::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
{
return _plug->describe_io_port (dt, input, id);
}
PluginOutputConfiguration
VST3Plugin::possible_output () const
{
return Plugin::possible_output (); // TODO
}
/* ****************************************************************************
* Plugin UI
*/
bool
VST3Plugin::has_editor () const
{
return _plug->has_editor ();
}
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] = 0x80 | 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] = 0x90 | 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] = 0xa0 | 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] = 0x0a | e.midiCCOut.channel;
data[1] = e.midiCCOut.value;
data[2] = e.midiCCOut.value2;
break;
default: /* Control Change */
data[0] = 0xb0 | e.midiCCOut.channel;
data[1] = e.midiCCOut.controlNumber;
data[2] = e.midiCCOut.value;
break;
case Vst::kCtrlProgramChange:
data[0] = 0x0c | e.midiCCOut.channel;
data[1] = e.midiCCOut.value;
data[2] = e.midiCCOut.value2;
break;
case Vst::kAfterTouch:
data[0] = 0x0d | e.midiCCOut.channel;
data[1] = e.midiCCOut.value;
data[2] = e.midiCCOut.value2;
break;
case Vst::kPitchBend:
data[0] = 0x0e | 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;
}
if (!_plug->try_set_parameter_by_id (param_id, value)) {
warning << string_compose (_("VST3<%1>: Invalid Vst::ParamID in VST3Plugin::set_state"), name ()) << endmsg;
}
}
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 (boost::shared_ptr<Plugin> p, bool rt)
{
boost::shared_ptr<VST3Plugin> vst = boost::dynamic_pointer_cast<VST3Plugin> (p);
if (vst) {
_plug->add_slave (vst->_plug->controller (), rt);
}
}
void
VST3Plugin::remove_slave (boost::shared_ptr<Plugin> p)
{
boost::shared_ptr<VST3Plugin> vst = boost::dynamic_pointer_cast<VST3Plugin> (p);
if (vst) {
_plug->remove_slave (vst->_plug->controller ());
}
}
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)
{
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 const& tmap (_session.tempo_map ());
const Tempo& t (tmap.tempo_at_sample (start));
const Timecode::BBT_Time& bbt (tmap.bbt_at_sample_rt (start));
const MeterSection& ms (tmap.meter_section_at_sample (start));
context.tempo = t.quarter_notes_per_minute ();
context.timeSigNumerator = ms.divisions_per_bar ();
context.timeSigDenominator = ms.note_divisor ();
context.projectTimeMusic = tmap.quarter_note_at_sample_rt (start);
context.barPositionMusic = bbt.bars * 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 const& tmap (_session.tempo_map ());
context.cycleStartMusic = tmap.quarter_note_at_sample_rt (looploc->start ());
context.cycleEndMusic = tmap.quarter_note_at_sample_rt (looploc->end ());
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 (offset);
_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 (offset);
_connected_outputs[i] = valid;
}
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) {
_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;
}
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"));
}
}
if (ok) {
Plugin::load_preset (r);
}
return ok;
}
std::string
VST3Plugin::do_save_preset (std::string name)
{
assert (!preset_search_path ().empty ());
std::string dir = preset_search_path ().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)
{
assert (!preset_search_path ().empty ());
std::string dir = preset_search_path ().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 */
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)
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 (_presets.find (uri) != _presets.end ()) {
continue;
}
PresetRecord r (uri, preset_name, is_user);
_presets.insert (make_pair (uri, r));
_preset_uri_map[uri] = *i;
}
}
PBD::Searchpath
VST3Plugin::preset_search_path () const
{
boost::shared_ptr<VST3PluginInfo> nfo = boost::dynamic_pointer_cast<VST3PluginInfo> (get_info ());
std::string vendor = legalize_for_universal_path (nfo->creator);
std::string name = legalize_for_universal_path (nfo->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, name);
preset_path += Glib::build_filename ("/Library/Audio/Presets", vendor, name);
#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, name);
preset_path += Glib::build_filename (documents, "vst3 presets", vendor, name);
}
preset_path += Glib::build_filename (Glib::get_user_data_dir (), "VST3 Presets", vendor, name);
std::string appdata = PBD::get_win_special_folder_path (CSIDL_APPDATA);
if (!appdata.empty ()) {
preset_path += Glib::build_filename (appdata, "VST3 Presets", vendor, name);
preset_path += Glib::build_filename (appdata, "vst3 presets", vendor, name);
}
#else
preset_path += Glib::build_filename (Glib::get_home_dir (), ".vst3", "presets", vendor, name);
preset_path += Glib::build_filename ("/usr/share/vst3/presets", vendor, name);
preset_path += Glib::build_filename ("/usr/local/share/vst3/presets", vendor, name);
#endif
return preset_path;
}
/* ****************************************************************************/
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 (new VST3PluginInfo (*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;
return p;
}
bool
VST3PluginInfo::is_instrument () const
{
if (category.find (Vst::PlugType::kInstrument) != std::string::npos) {
return true;
}
return PluginInfo::is_instrument ();
}
/* ****************************************************************************/
VST3PI::VST3PI (boost::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_processing (false)
, _block_size (0)
, _port_id_bypass (UINT32_MAX)
, _owner (0)
, _add_to_selection (false)
, _n_factory_presets (0)
{
using namespace std;
IPluginFactory* factory = m->factory ();
if (!factory) {
throw failed_constructor ();
}
if (!_fuid.fromString (unique_id.c_str ())) {
throw failed_constructor ();
}
#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) {
throw failed_constructor ();
}
if (_component->initialize (HostApplication::getHostContext ()) != kResultOk) {
throw failed_constructor ();
}
_controller = FUnknownPtr<Vst::IEditController> (_component);
if (!_controller) {
TUID controllerCID;
if (_component->getControllerClassId (controllerCID) == kResultTrue) {
if (factory->createInstance (controllerCID, Vst::IEditController::iid, (void**)&_controller) != kResultTrue) {
throw failed_constructor ();
}
if (_controller && (_controller->initialize (HostApplication::getHostContext ()) != kResultOk)) {
throw failed_constructor ();
}
}
}
if (!_controller) {
_component->terminate ();
_component->release ();
throw failed_constructor ();
}
if (_controller->setComponentHandler (this) != kResultOk) {
_component->terminate ();
_component->release ();
throw failed_constructor ();
}
if (!(_processor = FUnknownPtr<Vst::IAudioProcessor> (_component))) {
_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::IEditControllerHostEditing> host_editing (_controller);
FUnknownPtr<Vst::IEditController2> controller2 (_controller);
if (controller2) {
controller2->setKnobMode (Vst::kRelativCircularMode);
}
int32 n_params = _controller->getParameterCount ();
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;
}
/* allow non-automatable parameters IFF IEditControllerHostEditing is available */
if (0 == (pi.flags & Vst::ParameterInfo::kCanAutomate) && !host_editing) {
/* but allow read-only, not automatable params (ctrl outputs) */
if (0 == (pi.flags & Vst::ParameterInfo::kIsReadOnly)) {
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");
}
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);
}
_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 ();
}
Vst::IUnitInfo*
VST3PI::unit_info ()
{
Vst::IUnitInfo* nfo = FUnknownPtr<Vst::IUnitInfo> (_component);
if (nfo) {
return nfo;
}
return FUnknownPtr<Vst::IUnitInfo> (_controller);
}
#if 0
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 ();
bool controller_is_component = false;
if (_component) {
controller_is_component = FUnknownPtr<Vst::IEditController> (_component) != 0;
_component->terminate ();
}
if (_controller) {
_controller->setComponentHandler (0);
}
if (_controller && controller_is_component == false) {
_controller->terminate ();
_controller->release ();
}
if (_component) {
_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 = boost::shared_ptr<ConnectionProxy> (new ConnectionProxy (componentCP));
_controller_cproxy = boost::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) {
/* 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) {
update_shadow_data ();
}
if (flags & Vst::kLatencyChanged) {
/* need to re-activate the plugin as per spec */
deactivate ();
activate ();
}
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 (id, value, 0, true);
value = _controller->normalizedParamToPlain (id, value);
OnParameterChange (ValueChange, idx->second, v); /* 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 = 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 ();
}
}
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) {
#if 1
if ((type == Vst::kMain && i != 0) || (type == Vst::kAux && i != 1)) {
/* For now allow we only support one main bus, and one aux-bus.
* Also an aux-bus by itself is currently N/A.
*/
continue;
}
#endif
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));
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));
}
n_channels += bus.channelCount;
}
}
}
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 ? p.steps : 0;
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;
}
FUnknownPtr<IEditControllerExtra> extra_ctrl (_controller);
if (extra_ctrl && port != designated_bypass_port ()) {
int32 flags = extra_ctrl->getParamExtraFlags (id);
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 () const
{
return _n_inputs + _n_aux_inputs;
}
uint32_t
VST3PI::n_audio_outputs () const
{
return _n_outputs + _n_aux_outputs;
}
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);
return true;
}
void
VST3PI::set_parameter (uint32_t p, float value, int32 sample_off)
{
set_parameter_internal (index_to_id (p), value, sample_off, false);
_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)));
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;
}
}
}
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 (!parameter_is_automatable (i->first) && !parameter_is_readonly (i->first)) {
assert (host_editing);
host_editing->beginEditFromHost (i->second);
}
_controller->setParamNormalized (i->second, _shadow_data[i->first]);
if (!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)
{
set_parameter_internal (id, value, sample_off, true);
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, bool normalized)
{
int32 index;
if (!normalized) {
value = _controller->plainParamToNormalized (id, value);
}
_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 (!parameter_is_automatable (p) && !parameter_is_readonly (p)) {
assert (host_editing);
host_editing->beginEditFromHost (id);
}
_controller->setParamNormalized (id, _shadow_data[p]); // GUI thread only
if (!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;
}
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_in = %2\n", _n_bus_in, _n_bus_out));
std::vector<Vst::SpeakerArrangement> sa_in;
std::vector<Vst::SpeakerArrangement> sa_out;
bool enable = false;
Vst::SpeakerArrangement sa = 0;
for (int i = 0; i < _n_inputs; ++i) {
if (ins[i]) {
enable = true;
}
sa |= (uint64_t)1 << i;
}
if (_n_inputs > 0) {
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: activateBus (kAudio, kInput, 0, %1)\n", enable));
_component->activateBus (Vst::kAudio, Vst::kInput, 0, enable);
sa_in.push_back (sa);
}
enable = false;
sa = 0;
for (int i = 0; i < _n_aux_inputs; ++i) {
if (ins[i + _n_inputs]) {
enable = true;
}
sa |= (uint64_t)1 << i;
}
if (_n_aux_inputs > 0) {
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: activateBus (kAudio, kInput, 1, %1)\n", enable));
_component->activateBus (Vst::kAudio, Vst::kInput, 1, enable);
sa_in.push_back (sa);
}
/* disable remaining input busses and set their speaker-count to zero */
while (sa_in.size () < _n_bus_in) {
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: activateBus (kAudio, kInput, %1, false)\n", sa_in.size ()));
_component->activateBus (Vst::kAudio, Vst::kInput, sa_in.size (), false);
sa_in.push_back (0);
}
enable = false;
sa = 0;
for (int i = 0; i < _n_outputs; ++i) {
if (outs[i]) {
enable = true;
}
sa |= (uint64_t)1 << i;
}
if (_n_outputs > 0) {
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: activateBus (kAudio, kOutput, 0, %1)\n", enable));
_component->activateBus (Vst::kAudio, Vst::kOutput, 0, enable);
sa_out.push_back (sa);
}
enable = false;
sa = 0;
for (int i = 0; i < _n_aux_outputs; ++i) {
if (outs[i + _n_outputs]) {
enable = true;
}
sa |= (uint64_t)1 << i;
}
if (_n_aux_outputs > 0) {
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: activateBus (kAudio, kOutput, 1, %1)\n", enable));
_component->activateBus (Vst::kAudio, Vst::kOutput, 1, enable);
sa_out.push_back (sa);
}
while (sa_out.size () < _n_bus_out) {
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: activateBus (kAudio, kOutput, %1, false)\n", sa_out.size ()));
_component->activateBus (Vst::kAudio, Vst::kOutput, sa_out.size (), false);
sa_out.push_back (0);
}
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::enable_io: setBusArrangements ins = %1 outs = %2\n", sa_in.size (), sa_out.size ()));
_processor->setBusArrangements (sa_in.size () > 0 ? &sa_in[0] : NULL, sa_in.size (),
sa_out.size () > 0 ? &sa_out[0] : NULL, sa_out.size ());
#if 0
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);
std::cerr << "VST3: Input BusArrangements: " << i << " chan: " << cc << " bits: " << arr << "\n";
}
}
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);
std::cerr << "VST3: Output BusArrangements: " << i << " chan: " << cc << " bits: " << arr << "\n";
}
}
#endif
}
static int32
used_bus_count (int auxes, int inputs)
{
if (auxes > 0 && inputs > 0) {
return 2;
}
if (auxes == 0 && inputs == 0) {
return 0;
}
return 1;
}
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 = AudioEngine::instance ()->freewheeling () ? Vst::kOffline : Vst::kRealtime;
data.symbolicSampleSize = Vst::kSample32;
data.numInputs = used_bus_count (_n_aux_inputs, _n_inputs); // _n_bus_in;
data.numOutputs = used_bus_count (_n_aux_outputs, _n_outputs); // _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;
int used_ins = 0;
int used_outs = 0;
if (_n_bus_in > 0) {
inputs[0].silenceFlags = 0;
inputs[0].numChannels = _n_inputs;
inputs[0].channelBuffers32 = ins;
++used_ins;
}
if (_n_bus_in > 1 && _n_aux_inputs > 0) {
inputs[1].silenceFlags = 0;
inputs[1].numChannels = _n_aux_inputs;
inputs[1].channelBuffers32 = &ins[_n_inputs];
++used_ins;
}
if (_n_bus_out > 0) {
outputs[0].silenceFlags = 0;
outputs[0].numChannels = _n_outputs;
outputs[0].channelBuffers32 = outs;
++used_outs;
}
if (_n_bus_out > 1 && _n_aux_outputs > 0) {
outputs[1].silenceFlags = 0;
outputs[1].numChannels = _n_outputs;
outputs[1].channelBuffers32 = &outs[_n_outputs];
++used_outs;
}
for (int i = used_ins; i < _n_bus_in; ++i) {
inputs[i].silenceFlags = 0;
inputs[i].numChannels = 0;
inputs[i].channelBuffers32 = 0;
}
for (int i = used_outs; i < _n_bus_out; ++i) {
outputs[i].silenceFlags = 0;
outputs[i].numChannels = 0;
outputs[i].channelBuffers32 = 0;
}
/* 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;
}
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))) {
stream.seek (i->_offset, IBStream::kIBSeekSet, &seek_result);
tresult res = _controller->setState (&stream);
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 ()) {
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, boost::weak_ptr<AutomationList> wal)
{
Vst::ParamID id (index_to_id (port));
boost::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:
assert (0);
}
extra_ctrl->setParamAutomationMode (id, am);
}
/* ****************************************************************************/
static boost::shared_ptr<AutomationControl>
lookup_ac (SessionObject* o, FIDString id)
{
Stripable* s = dynamic_cast<Stripable*> (o);
if (!s) {
return boost::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 boost::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_master ()) {
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_master () ? 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)) {
boost::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)) {
boost::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)) {
boost::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 {
boost::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)) {
boost::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)) {
boost::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))) {
boost::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)) {
boost::shared_ptr<AutomationControl> ac = lookup_ac (_owner, id);
ac->set_value (value, Controllable::NoGroup);
} else if (0 == strcmp (id, ContextInfo::kPan)) {
boost::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))) {
boost::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)) {
boost::shared_ptr<Stripable> stripable = s->session ().stripable_by_id (s->id ());
assert (stripable);
if (value == 0) {
s->session ().selection ().remove (stripable, boost::shared_ptr<AutomationControl> ());
} else if (_add_to_selection) {
s->session ().selection ().add (stripable, boost::shared_ptr<AutomationControl> ());
} else {
s->session ().selection ().set (stripable, boost::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;
}
boost::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 (ac->session ().transport_sample ());
return kResultOk;
}
tresult
VST3PI::endEditContextInfoValue (FIDString id)
{
if (!_owner) {
DEBUG_TRACE (DEBUG::VST3Callbacks, "VST3PI::endEditContextInfoValue: not initialized");
return kNotInitialized;
}
boost::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 (ac->session ().transport_sample ());
return kResultOk;
}
void
VST3PI::psl_subscribe_to (boost::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::foward_signal, this, nfo2.get (), id));
}
void
VST3PI::foward_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);
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);
}