From 4c5ef5318edb4e1cdc76288860c27358155efe17 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 17 Nov 2012 18:41:19 +0000 Subject: [PATCH] Sent precise transport information to LV2 plugins via events. We send the full transport state (frame position, BBT time, transport speed, meter) to the plugin: * At the start of a cycle whenever a relocate or transport speed change has occurred * On every occurrence of a meter change within a cycle This means the plugin gets a sample accurate meter/tempo map, even if the meter changes in the middle of a cycle. However, this is not quite right yet: things can get wonky if the tempo map is edited while rolling, since this code will not detect the change and fail to update the plugin at the start of the cycle. Other changes: * Factor out TempoMetric::set_metric() and simplify some tempo functions * Clean up LV2 URID stuff git-svn-id: svn://localhost/ardour2/branches/3.0@13513 d708f5d6-7413-0410-9779-e7cbd77b26cf --- gtk2_ardour/lv2_plugin_ui.cc | 2 +- libs/ardour/ardour/lv2_plugin.h | 35 +++++--- libs/ardour/ardour/tempo.h | 21 ++++- libs/ardour/buffer_set.cc | 6 +- libs/ardour/lv2_plugin.cc | 155 ++++++++++++++++++++++++-------- libs/ardour/tempo.cc | 26 ++---- 6 files changed, 170 insertions(+), 75 deletions(-) diff --git a/gtk2_ardour/lv2_plugin_ui.cc b/gtk2_ardour/lv2_plugin_ui.cc index 177d27fa47..839bc6a4b8 100644 --- a/gtk2_ardour/lv2_plugin_ui.cc +++ b/gtk2_ardour/lv2_plugin_ui.cc @@ -56,7 +56,7 @@ LV2PluginUI::write_from_ui(void* controller, if (ac) { ac->set_value(*(const float*)buffer); } - } else if (format == me->_lv2->atom_eventTransfer()) { + } else if (format == me->_lv2->urids.atom_eventTransfer) { const int cnt = me->_pi->get_count(); for (int i=0; i < cnt; i++ ) { diff --git a/libs/ardour/ardour/lv2_plugin.h b/libs/ardour/ardour/lv2_plugin.h index 1090f30307..a4b89a5841 100644 --- a/libs/ardour/ardour/lv2_plugin.h +++ b/libs/ardour/ardour/lv2_plugin.h @@ -111,8 +111,6 @@ class LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee boost::shared_ptr get_scale_points(uint32_t port_index) const; - static uint32_t midi_event_type() { return _midi_event_type; } - void set_insert_info(const PluginInsert* insert); int set_state (const XMLNode& node, int version); @@ -124,8 +122,6 @@ class LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee bool has_editor () const; bool has_message_output () const; - uint32_t atom_eventTransfer() const; - void write_from_ui(uint32_t index, uint32_t protocol, uint32_t size, @@ -147,14 +143,26 @@ class LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee static URIMap _uri_map; - static uint32_t _midi_event_type; - static uint32_t _chunk_type; - static uint32_t _sequence_type; - static uint32_t _event_transfer_type; - static uint32_t _path_type; - static uint32_t _log_Error; - static uint32_t _log_Warning; - static uint32_t _log_Note; + struct URIDs { + uint32_t atom_Chunk; + uint32_t atom_Path; + uint32_t atom_Sequence; + uint32_t atom_eventTransfer; + uint32_t log_Error; + uint32_t log_Note; + uint32_t log_Warning; + uint32_t midi_MidiEvent; + uint32_t time_Position; + uint32_t time_bar; + uint32_t time_barBeat; + uint32_t time_beatUnit; + uint32_t time_beatsPerBar; + uint32_t time_beatsPerMinute; + uint32_t time_frame; + uint32_t time_speed; + }; + + static URIDs urids; private: struct Impl; @@ -171,6 +179,9 @@ class LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee float* _bpm_control_port; ///< Special input set by ardour float* _freewheel_control_port; ///< Special input set by ardour float* _latency_control_port; ///< Special output set by ardour + uint32_t _position_seq_port_idx; ///< Index of Sequence port for position + framepos_t _next_cycle_start; ///< Expected start frame of next run cycle + double _next_cycle_speed; ///< Expected start frame of next run cycle PBD::ID _insert_id; friend const void* lv2plugin_get_port_value(const char* port_symbol, diff --git a/libs/ardour/ardour/tempo.h b/libs/ardour/ardour/tempo.h index 65e488d9a5..bedc825288 100644 --- a/libs/ardour/ardour/tempo.h +++ b/libs/ardour/ardour/tempo.h @@ -180,6 +180,19 @@ class TempoMetric { void set_frame (framepos_t f) { _frame = f; } void set_start (const Timecode::BBT_Time& t) { _start = t; } + void set_metric (const MetricSection* section) { + const MeterSection* meter; + const TempoSection* tempo; + if ((meter = dynamic_cast(section))) { + set_meter(*meter); + } else if ((tempo = dynamic_cast(section))) { + set_tempo(*tempo); + } + + set_frame(section->frame()); + set_start(section->start()); + } + const Meter& meter() const { return *_meter; } const Tempo& tempo() const { return *_tempo; } framepos_t frame() const { return _frame; } @@ -298,7 +311,13 @@ class TempoMap : public PBD::StatefulDestructible void clear (); TempoMetric metric_at (Timecode::BBT_Time bbt) const; - TempoMetric metric_at (framepos_t) const; + + /** Return the TempoMetric at frame @p t, and point @p last to the latest + * metric change <= t, if it is non-NULL. + */ + TempoMetric metric_at (framepos_t, Metrics::const_iterator* last=NULL) const; + + Metrics::const_iterator metrics_end() { return metrics.end(); } void change_existing_tempo_at (framepos_t, double bpm, double note_type); void change_initial_tempo (double bpm, double note_type); diff --git a/libs/ardour/buffer_set.cc b/libs/ardour/buffer_set.cc index f9e683cdda..c2be5ca007 100644 --- a/libs/ardour/buffer_set.cc +++ b/libs/ardour/buffer_set.cc @@ -192,8 +192,8 @@ BufferSet::ensure_buffers(DataType type, size_t num_buffers, size_t buffer_capac _lv2_buffers.push_back( std::make_pair(false, lv2_evbuf_new(buffer_capacity, LV2_EVBUF_EVENT, - LV2Plugin::_chunk_type, - LV2Plugin::_sequence_type))); + LV2Plugin::urids.atom_Chunk, + LV2Plugin::urids.atom_Sequence))); } } #endif @@ -269,7 +269,7 @@ BufferSet::get_lv2_midi(bool input, size_t i, bool old_api) mbuf.size(), (void*) mbuf.data())); LV2_Evbuf_Iterator i = lv2_evbuf_begin(evbuf); - const uint32_t type = LV2Plugin::midi_event_type(); + const uint32_t type = LV2Plugin::urids.midi_MidiEvent; for (MidiBuffer::iterator e = mbuf.begin(); e != mbuf.end(); ++e) { const Evoral::MIDIEvent ev(*e, false); #ifndef NDEBUG diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc index c7d363e512..71039ba845 100644 --- a/libs/ardour/lv2_plugin.cc +++ b/libs/ardour/lv2_plugin.cc @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -51,7 +52,9 @@ #include #include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/atom/forge.h" #include "lv2/lv2plug.in/ns/ext/log/log.h" +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" #include "lv2/lv2plug.in/ns/ext/port-props/port-props.h" #include "lv2/lv2plug.in/ns/ext/presets/presets.h" #include "lv2/lv2plug.in/ns/ext/state/state.h" @@ -74,22 +77,25 @@ using namespace ARDOUR; using namespace PBD; URIMap LV2Plugin::_uri_map; -uint32_t LV2Plugin::_midi_event_type = _uri_map.uri_to_id( - "http://lv2plug.in/ns/ext/midi#MidiEvent"); -uint32_t LV2Plugin::_chunk_type = _uri_map.uri_to_id( - LV2_ATOM__Chunk); -uint32_t LV2Plugin::_sequence_type = _uri_map.uri_to_id( - LV2_ATOM__Sequence); -uint32_t LV2Plugin::_event_transfer_type = _uri_map.uri_to_id( - LV2_ATOM__eventTransfer); -uint32_t LV2Plugin::_path_type = _uri_map.uri_to_id( - LV2_ATOM__Path); -uint32_t LV2Plugin::_log_Error = _uri_map.uri_to_id( - LV2_LOG__Error); -uint32_t LV2Plugin::_log_Warning = _uri_map.uri_to_id( - LV2_LOG__Warning); -uint32_t LV2Plugin::_log_Note = _uri_map.uri_to_id( - LV2_LOG__Note); + +LV2Plugin::URIDs LV2Plugin::urids = { + _uri_map.uri_to_id(LV2_ATOM__Chunk), + _uri_map.uri_to_id(LV2_ATOM__Path), + _uri_map.uri_to_id(LV2_ATOM__Sequence), + _uri_map.uri_to_id(LV2_ATOM__eventTransfer), + _uri_map.uri_to_id(LV2_LOG__Error), + _uri_map.uri_to_id(LV2_LOG__Note), + _uri_map.uri_to_id(LV2_LOG__Warning), + _uri_map.uri_to_id(LV2_MIDI__MidiEvent), + _uri_map.uri_to_id(LV2_TIME__Position), + _uri_map.uri_to_id(LV2_TIME__bar), + _uri_map.uri_to_id(LV2_TIME__barBeat), + _uri_map.uri_to_id(LV2_TIME__beatUnit), + _uri_map.uri_to_id(LV2_TIME__beatsPerBar), + _uri_map.uri_to_id(LV2_TIME__beatsPerMinute), + _uri_map.uri_to_id(LV2_TIME__frame), + _uri_map.uri_to_id(LV2_TIME__speed) +}; class LV2World : boost::noncopyable { public: @@ -102,8 +108,8 @@ public: LilvNode* atom_Chunk; LilvNode* atom_Sequence; LilvNode* atom_bufferType; - LilvNode* atom_supports; LilvNode* atom_eventTransfer; + LilvNode* atom_supports; LilvNode* ev_EventPort; LilvNode* ext_logarithmic; LilvNode* lv2_AudioPort; @@ -117,6 +123,7 @@ public: LilvNode* lv2_toggled; LilvNode* midi_MidiEvent; LilvNode* rdfs_comment; + LilvNode* time_Position; LilvNode* ui_GtkUI; LilvNode* ui_external; }; @@ -169,14 +176,14 @@ log_vprintf(LV2_Log_Handle handle, { char* str = NULL; const int ret = g_vasprintf(&str, fmt, args); - if (type == LV2Plugin::_log_Error) { + if (type == LV2Plugin::urids.log_Error) { error << str << endmsg; - } else if (type == LV2Plugin::_log_Warning) { + } else if (type == LV2Plugin::urids.log_Warning) { warning << str << endmsg; - } else if (type == LV2Plugin::_log_Note) { + } else if (type == LV2Plugin::urids.log_Note) { info << str << endmsg; } - // TODO: Togglable log:Trace message support + // TODO: Toggleable log:Trace message support return ret; } @@ -211,6 +218,7 @@ struct LV2Plugin::Impl { LilvInstance* instance; const LV2_Worker_Interface* work_iface; LilvState* state; + LV2_Atom_Forge forge; }; LV2Plugin::LV2Plugin (AudioEngine& engine, @@ -260,6 +268,9 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) _bpm_control_port = 0; _freewheel_control_port = 0; _latency_control_port = 0; + _position_seq_port_idx = std::numeric_limits::max(); + _next_cycle_start = std::numeric_limits::max(); + _next_cycle_speed = 1.0; _block_length = _engine.frames_per_cycle(); _seq_size = _engine.raw_buffer_size(DataType::MIDI); _state_version = 0; @@ -294,6 +305,8 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) _features[5] = _uri_map.urid_unmap_feature(); _features[6] = &_log_feature; + lv2_atom_forge_init(&_impl->forge, _uri_map.urid_map()); + unsigned n_features = 7; #ifdef HAVE_NEW_LV2 LV2_URID atom_Int = _uri_map.uri_to_id(LV2_ATOM__Int); @@ -391,13 +404,16 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) LilvNodes* atom_supports = lilv_port_get_value( _impl->plugin, port, _world.atom_supports); - if (lilv_nodes_contains(buffer_types, _world.atom_Sequence) - && lilv_nodes_contains(atom_supports, _world.midi_MidiEvent) - ) { + if (lilv_nodes_contains(buffer_types, _world.atom_Sequence)) { + if (lilv_nodes_contains(atom_supports, _world.midi_MidiEvent)) { flags |= PORT_MESSAGE; } else { flags |= PORT_ATOM; } + if (lilv_nodes_contains(atom_supports, _world.time_Position)) { + _position_seq_port_idx = i; + } + } lilv_nodes_free(buffer_types); lilv_nodes_free(atom_supports); } else { @@ -862,7 +878,7 @@ LV2Plugin::add_state(XMLNode* root) const _session.externals_dir().c_str(), new_dir.c_str(), NULL, - (void*)this, + const_cast(this), 0, NULL); @@ -1067,12 +1083,6 @@ LV2Plugin::has_message_output() const return false; } -uint32_t -LV2Plugin::atom_eventTransfer() const -{ - return _event_transfer_type; -} - void LV2Plugin::write_to(RingBuffer* dest, uint32_t index, @@ -1406,12 +1416,50 @@ LV2Plugin::allocate_atom_event_buffers() _atom_ev_buffers = (LV2_Evbuf**) malloc((total_atom_buffers + 1) * sizeof(LV2_Evbuf*)); for (int i = 0; i < total_atom_buffers; ++i ) { _atom_ev_buffers[i] = lv2_evbuf_new(32768, LV2_EVBUF_ATOM, - LV2Plugin::_chunk_type, LV2Plugin::_sequence_type); + LV2Plugin::urids.atom_Chunk, LV2Plugin::urids.atom_Sequence); } _atom_ev_buffers[total_atom_buffers] = 0; return; } +/** Write an ardour position/time/tempo/meter as an LV2 event. + * @return true on success. + */ +static bool +write_position(LV2_Atom_Forge* forge, + LV2_Evbuf* buf, + const TempoMetric& t, + Timecode::BBT_Time& bbt, + double speed, + framepos_t position, + framecnt_t offset) +{ + uint8_t pos_buf[256]; + lv2_atom_forge_set_buffer(forge, pos_buf, sizeof(pos_buf)); + LV2_Atom_Forge_Frame frame; + lv2_atom_forge_blank(forge, &frame, 1, LV2Plugin::urids.time_Position); + lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_frame, 0); + lv2_atom_forge_long(forge, position); + lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_speed, 0); + lv2_atom_forge_float(forge, speed); + lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_barBeat, 0); + lv2_atom_forge_float(forge, bbt.beats - 1 + + (bbt.ticks / Timecode::BBT_Time::ticks_per_beat)); + lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_bar, 0); + lv2_atom_forge_float(forge, bbt.bars - 1); + lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatUnit, 0); + lv2_atom_forge_float(forge, t.meter().note_divisor()); + lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatsPerBar, 0); + lv2_atom_forge_float(forge, t.meter().divisions_per_bar()); + lv2_atom_forge_property_head(forge, LV2Plugin::urids.time_beatsPerMinute, 0); + lv2_atom_forge_float(forge, t.tempo().beats_per_minute()); + + LV2_Evbuf_Iterator end = lv2_evbuf_end(buf); + const LV2_Atom* const atom = (const LV2_Atom*)pos_buf; + return lv2_evbuf_write(&end, offset, 0, atom->type, atom->size, + (const uint8_t*)(atom + 1)); +} + int LV2Plugin::connect_and_run(BufferSet& bufs, ChanMapping in_map, ChanMapping out_map, @@ -1422,14 +1470,16 @@ LV2Plugin::connect_and_run(BufferSet& bufs, cycles_t then = get_cycles(); + TempoMap& tmap = _session.tempo_map(); + Metrics::const_iterator metric_i = tmap.metrics_end(); + TempoMetric tmetric = tmap.metric_at(_session.transport_frame(), &metric_i); + if (_freewheel_control_port) { - *_freewheel_control_port = _session.engine().freewheeling (); + *_freewheel_control_port = _session.engine().freewheeling(); } if (_bpm_control_port) { - TempoMap& tmap (_session.tempo_map ()); - Tempo tempo = tmap.tempo_at (_session.transport_frame () + offset); - *_bpm_control_port = tempo.beats_per_minute (); + *_bpm_control_port = tmetric.tempo().beats_per_minute(); } ChanCount bufs_count; @@ -1484,6 +1534,30 @@ LV2Plugin::connect_and_run(BufferSet& bufs, if (flags & PORT_INPUT) { lv2_evbuf_reset(_atom_ev_buffers[atom_port_index], true); _ev_buffers[port_index] = _atom_ev_buffers[atom_port_index++]; + + if (port_index == _position_seq_port_idx) { + Timecode::BBT_Time bbt; + if (_session.transport_frame() != _next_cycle_start || + _session.transport_speed() != _next_cycle_speed) { + // Something has changed, write the position at cycle start + tmap.bbt_time(_session.transport_frame(), bbt); + write_position(&_impl->forge, _ev_buffers[port_index], + tmetric, bbt, _session.transport_speed(), + _session.transport_frame(), 0); + } + + // Write a position event for every metric change within this cycle + while (++metric_i != tmap.metrics_end() && + (*metric_i)->frame() < _session.transport_frame() + nframes) { + MetricSection* section = *metric_i; + tmetric.set_metric(section); + bbt = section->start(); + write_position(&_impl->forge, _ev_buffers[port_index], + tmetric, bbt, _session.transport_speed(), + section->frame(), + section->frame() - _session.transport_frame()); + } + } } else { lv2_evbuf_reset(_atom_ev_buffers[atom_port_index], false); _ev_buffers[port_index] = _atom_ev_buffers[atom_port_index++]; @@ -1510,7 +1584,7 @@ LV2Plugin::connect_and_run(BufferSet& bufs, error << "Error reading from UI=>Plugin RingBuffer" << endmsg; break; } - if (msg.protocol == _event_transfer_type) { + if (msg.protocol == urids.atom_eventTransfer) { LV2_Evbuf* buf = _ev_buffers[msg.index]; LV2_Evbuf_Iterator i = lv2_evbuf_end(buf); const LV2_Atom* const atom = (const LV2_Atom*)body; @@ -1549,7 +1623,7 @@ LV2Plugin::connect_and_run(BufferSet& bufs, uint32_t frames, subframes, type, size; uint8_t* data; lv2_evbuf_get(i, &frames, &subframes, &type, &size, &data); - write_to_ui(port_index, _event_transfer_type, + write_to_ui(port_index, urids.atom_eventTransfer, size + sizeof(LV2_Atom), data - sizeof(LV2_Atom)); } @@ -1559,6 +1633,10 @@ LV2Plugin::connect_and_run(BufferSet& bufs, cycles_t now = get_cycles(); set_cycles((uint32_t)(now - then)); + // Update expected transport information for next cycle so we can detect changes + _next_cycle_speed = _session.transport_speed(); + _next_cycle_start = _session.transport_frame() + (nframes * _next_cycle_speed); + return 0; } @@ -1734,6 +1812,7 @@ LV2World::LV2World() lv2_enumeration = lilv_new_uri(world, LV2_CORE__enumeration); midi_MidiEvent = lilv_new_uri(world, LILV_URI_MIDI_EVENT); rdfs_comment = lilv_new_uri(world, LILV_NS_RDFS "comment"); + time_Position = lilv_new_uri(world, LV2_TIME__Position); ui_GtkUI = lilv_new_uri(world, LV2_UI__GtkUI); ui_external = lilv_new_uri(world, "http://lv2plug.in/ns/extensions/ui#external"); } diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index 80091e7eec..0bb2fea0cf 100644 --- a/libs/ardour/tempo.cc +++ b/libs/ardour/tempo.cc @@ -1031,12 +1031,10 @@ TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter, } TempoMetric -TempoMap::metric_at (framepos_t frame) const +TempoMap::metric_at (framepos_t frame, Metrics::const_iterator* last) const { Glib::Threads::RWLock::ReaderLock lm (lock); TempoMetric m (first_meter(), first_tempo()); - const Meter* meter; - const Tempo* tempo; /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing at something, because we insert the default tempo and meter during @@ -1051,14 +1049,11 @@ TempoMap::metric_at (framepos_t frame) const break; } - if ((tempo = dynamic_cast(*i)) != 0) { - m.set_tempo (*tempo); - } else if ((meter = dynamic_cast(*i)) != 0) { - m.set_meter (*meter); - } + m.set_metric(*i); - m.set_frame ((*i)->frame ()); - m.set_start ((*i)->start ()); + if (last) { + *last = i; + } } return m; @@ -1069,8 +1064,6 @@ TempoMap::metric_at (BBT_Time bbt) const { Glib::Threads::RWLock::ReaderLock lm (lock); TempoMetric m (first_meter(), first_tempo()); - const Meter* meter; - const Tempo* tempo; /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing at something, because we insert the default tempo and meter during @@ -1087,14 +1080,7 @@ TempoMap::metric_at (BBT_Time bbt) const break; } - if ((tempo = dynamic_cast(*i)) != 0) { - m.set_tempo (*tempo); - } else if ((meter = dynamic_cast(*i)) != 0) { - m.set_meter (*meter); - } - - m.set_frame ((*i)->frame ()); - m.set_start (section_start); + m.set_metric (*i); } return m;