13
0
livetrax/libs/ardour/plugin.cc
Robin Gareus 7a5cea45e1 fix seamless midi-looping - fixes #5438
well, now...
 - Midi-Ports have a midi-buffer.
 - Midi-Tracks have a midi-buffer.
 - Midi-tracks have a diskstream.
 - Midi-diskstream has a midi-ring-buffer.
 - Midi-tracks have a delivery
 - The delivery can get a reference to the actual backend-ports
 - The delivery calls the Midi-Port's flush() buffer to send out queued events
   at the end of a cycle

all clear ? :)

 - when splitting the process-cycle: only the Ports are informed.
   all other objects see a "normal" short process cycle starting at "0".

The offset needs to be applied early on, so that internally routed buffers
push the event at the correct time when combining the buffer with
immediate and async events.

Luckily Port::port_offset() is a static member, available to all, objects,
which allows to bridge the conceptual gap between the diskstream and
the delivery.

There's a snag:
When there's a note-on directly at the beginning of the loop it coincides
with the panic message sent when looping.
The panic comes before note events, so it *should* be good.

Also the final note-offs (state tracker end of loop/region) are sent
1 sample too early (smells like an off-by-one), and are hence dropped.
(no matter we send a panic right after it).
It should really be at the same time, just before the panic.
2015-11-29 01:27:18 +01:00

429 lines
9.0 KiB
C++

/*
Copyright (C) 2000-2002 Paul Davis
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifdef WAF_BUILD
#include "libardour-config.h"
#endif
#include <vector>
#include <string>
#include <cstdlib>
#include <cstdio> // so libraptor doesn't complain
#include <cmath>
#ifndef COMPILER_MSVC
#include <dirent.h>
#endif
#include <sys/stat.h>
#include <cerrno>
#include <utility>
#ifdef HAVE_LRDF
#include <lrdf.h>
#endif
#include "pbd/compose.h"
#include "pbd/error.h"
#include "pbd/xml++.h"
#include "ardour/buffer_set.h"
#include "ardour/chan_count.h"
#include "ardour/chan_mapping.h"
#include "ardour/data_type.h"
#include "ardour/midi_buffer.h"
#include "ardour/midi_state_tracker.h"
#include "ardour/plugin.h"
#include "ardour/plugin_manager.h"
#include "ardour/port.h"
#include "ardour/session.h"
#include "ardour/types.h"
#ifdef AUDIOUNIT_SUPPORT
#include "ardour/audio_unit.h"
#endif
#ifdef LV2_SUPPORT
#include "ardour/lv2_plugin.h"
#endif
#include "pbd/stl_delete.h"
#include "i18n.h"
#include <locale.h>
using namespace std;
using namespace ARDOUR;
using namespace PBD;
namespace ARDOUR { class AudioEngine; }
#ifdef NO_PLUGIN_STATE
static bool seen_get_state_message = false;
static bool seen_set_state_message = false;
#endif
bool
PluginInfo::is_instrument () const
{
return (n_inputs.n_midi() != 0) && (n_outputs.n_audio() > 0);
}
Plugin::Plugin (AudioEngine& e, Session& s)
: _engine (e)
, _session (s)
, _cycles (0)
, _have_presets (false)
, _have_pending_stop_events (false)
, _parameter_changed_since_last_preset (false)
{
_pending_stop_events.ensure_buffers (DataType::MIDI, 1, 4096);
}
Plugin::Plugin (const Plugin& other)
: StatefulDestructible()
, Latent()
, _engine (other._engine)
, _session (other._session)
, _info (other._info)
, _cycles (0)
, _have_presets (false)
, _have_pending_stop_events (false)
, _parameter_changed_since_last_preset (false)
{
_pending_stop_events.ensure_buffers (DataType::MIDI, 1, 4096);
}
Plugin::~Plugin ()
{
}
void
Plugin::remove_preset (string name)
{
do_remove_preset (name);
_presets.erase (preset_by_label (name)->uri);
_last_preset.uri = "";
_parameter_changed_since_last_preset = false;
PresetRemoved (); /* EMIT SIGNAL */
}
/** @return PresetRecord with empty URI on failure */
Plugin::PresetRecord
Plugin::save_preset (string name)
{
string const uri = do_save_preset (name);
if (!uri.empty()) {
_presets.insert (make_pair (uri, PresetRecord (uri, name)));
PresetAdded (); /* EMIT SIGNAL */
}
return PresetRecord (uri, name);
}
PluginPtr
ARDOUR::find_plugin(Session& session, string identifier, PluginType type)
{
PluginManager& mgr (PluginManager::instance());
PluginInfoList plugs;
switch (type) {
case ARDOUR::LADSPA:
plugs = mgr.ladspa_plugin_info();
break;
#ifdef LV2_SUPPORT
case ARDOUR::LV2:
plugs = mgr.lv2_plugin_info();
break;
#endif
#ifdef WINDOWS_VST_SUPPORT
case ARDOUR::Windows_VST:
plugs = mgr.windows_vst_plugin_info();
break;
#endif
#ifdef LXVST_SUPPORT
case ARDOUR::LXVST:
plugs = mgr.lxvst_plugin_info();
break;
#endif
#ifdef AUDIOUNIT_SUPPORT
case ARDOUR::AudioUnit:
plugs = mgr.au_plugin_info();
break;
#endif
default:
return PluginPtr ((Plugin *) 0);
}
PluginInfoList::iterator i;
for (i = plugs.begin(); i != plugs.end(); ++i) {
if (identifier == (*i)->unique_id){
return (*i)->load (session);
}
}
#ifdef WINDOWS_VST_SUPPORT
/* hmm, we didn't find it. could be because in older versions of Ardour.
we used to store the name of a VST plugin, not its unique ID. so try
again.
*/
for (i = plugs.begin(); i != plugs.end(); ++i) {
if (identifier == (*i)->name){
return (*i)->load (session);
}
}
#endif
#ifdef LXVST_SUPPORT
/* hmm, we didn't find it. could be because in older versions of Ardour.
we used to store the name of a VST plugin, not its unique ID. so try
again.
*/
for (i = plugs.begin(); i != plugs.end(); ++i) {
if (identifier == (*i)->name){
return (*i)->load (session);
}
}
#endif
return PluginPtr ((Plugin*) 0);
}
ChanCount
Plugin::output_streams () const
{
/* LADSPA & VST should not get here because they do not
return "infinite" i/o counts.
*/
return ChanCount::ZERO;
}
ChanCount
Plugin::input_streams () const
{
/* LADSPA & VST should not get here because they do not
return "infinite" i/o counts.
*/
return ChanCount::ZERO;
}
const Plugin::PresetRecord *
Plugin::preset_by_label (const string& label)
{
// FIXME: O(n)
for (map<string, PresetRecord>::const_iterator i = _presets.begin(); i != _presets.end(); ++i) {
if (i->second.label == label) {
return &i->second;
}
}
return 0;
}
const Plugin::PresetRecord *
Plugin::preset_by_uri (const string& uri)
{
map<string, PresetRecord>::const_iterator pr = _presets.find (uri);
if (pr != _presets.end()) {
return &pr->second;
} else {
return 0;
}
}
int
Plugin::connect_and_run (BufferSet& bufs,
ChanMapping /*in_map*/, ChanMapping /*out_map*/,
pframes_t /* nframes */, framecnt_t /*offset*/)
{
if (bufs.count().n_midi() > 0) {
/* Track notes that we are sending to the plugin */
const MidiBuffer& b = bufs.get_midi (0);
_tracker.track (b.begin(), b.end());
if (_have_pending_stop_events) {
/* Transmit note-offs that are pending from the last transport stop */
bufs.merge_from (_pending_stop_events, 0);
_have_pending_stop_events = false;
}
}
return 0;
}
void
Plugin::realtime_handle_transport_stopped ()
{
resolve_midi ();
}
void
Plugin::realtime_locate ()
{
resolve_midi ();
}
void
Plugin::monitoring_changed ()
{
resolve_midi ();
}
void
Plugin::resolve_midi ()
{
/* Create note-offs for any active notes and put them in _pending_stop_events, to be picked
up on the next call to connect_and_run ().
*/
_pending_stop_events.get_midi(0).clear ();
_tracker.resolve_notes (_pending_stop_events.get_midi (0), /* split cycle offset*/ Port::port_offset());
_have_pending_stop_events = true;
}
vector<Plugin::PresetRecord>
Plugin::get_presets ()
{
vector<PresetRecord> p;
#ifndef NO_PLUGIN_STATE
if (!_have_presets) {
find_presets ();
_have_presets = true;
}
for (map<string, PresetRecord>::const_iterator i = _presets.begin(); i != _presets.end(); ++i) {
p.push_back (i->second);
}
#else
if (!seen_set_state_message) {
info << string_compose (_("Plugin presets are not supported in this build of %1. Consider paying for a full version"),
PROGRAM_NAME)
<< endmsg;
seen_set_state_message = true;
}
#endif
return p;
}
/** Set parameters using a preset */
bool
Plugin::load_preset (PresetRecord r)
{
_last_preset = r;
_parameter_changed_since_last_preset = false;
PresetLoaded (); /* EMIT SIGNAL */
return true;
}
void
Plugin::clear_preset ()
{
_last_preset.uri = "";
_last_preset.label = "";
_parameter_changed_since_last_preset = false;
PresetLoaded (); /* EMIT SIGNAL */
}
void
Plugin::set_parameter (uint32_t /* which */, float /* value */)
{
_parameter_changed_since_last_preset = true;
_session.set_dirty ();
PresetDirty (); /* EMIT SIGNAL */
}
void
Plugin::parameter_changed_externally (uint32_t which, float /* value */)
{
_parameter_changed_since_last_preset = true;
_session.set_dirty ();
ParameterChangedExternally (which, get_parameter (which)); /* EMIT SIGNAL */
PresetDirty (); /* EMIT SIGNAL */
}
int
Plugin::set_state (const XMLNode& node, int /*version*/)
{
XMLProperty const * p = node.property (X_("last-preset-uri"));
if (p) {
_last_preset.uri = p->value ();
}
p = node.property (X_("last-preset-label"));
if (p) {
_last_preset.label = p->value ();
}
p = node.property (X_("parameter-changed-since-last-preset"));
if (p) {
_parameter_changed_since_last_preset = string_is_affirmative (p->value ());
}
return 0;
}
XMLNode &
Plugin::get_state ()
{
XMLNode* root = new XMLNode (state_node_name ());
LocaleGuard lg (X_("C"));
root->add_property (X_("last-preset-uri"), _last_preset.uri);
root->add_property (X_("last-preset-label"), _last_preset.label);
root->add_property (X_("parameter-changed-since-last-preset"), _parameter_changed_since_last_preset ? X_("yes") : X_("no"));
#ifndef NO_PLUGIN_STATE
add_state (root);
#else
if (!seen_get_state_message) {
info << string_compose (_("Saving plugin settings is not supported in this build of %1. Consider paying for the full version"),
PROGRAM_NAME)
<< endmsg;
seen_get_state_message = true;
}
#endif
return *root;
}
void
Plugin::set_info (PluginInfoPtr info)
{
_info = info;
}