From 719dda77014fb7d2b21f936195d248e97e8a0623 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Sat, 14 May 2022 19:44:49 +0200 Subject: [PATCH] Implement MIDI ExportChannel Since MIDI Export is limited to Stem-Export, only a port-reader and ExportProcessor have to be implemented. --- libs/ardour/ardour/export_channel.h | 70 +++++++++++-- libs/ardour/export_channel.cc | 154 +++++++++++++++++++++++----- libs/ardour/midi_port.cc | 11 +- 3 files changed, 196 insertions(+), 39 deletions(-) diff --git a/libs/ardour/ardour/export_channel.h b/libs/ardour/ardour/export_channel.h index 273e2ec258..76b4b159e6 100644 --- a/libs/ardour/ardour/export_channel.h +++ b/libs/ardour/ardour/export_channel.h @@ -3,7 +3,7 @@ * Copyright (C) 2009-2011 Carl Hetherington * Copyright (C) 2009-2011 David Robillard * Copyright (C) 2009-2017 Paul Davis - * Copyright (C) 2017-2019 Robin Gareus + * Copyright (C) 2017-2022 Robin Gareus * * 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 @@ -35,6 +35,8 @@ #include "ardour/audio_buffer.h" #include "ardour/buffer_set.h" #include "ardour/export_pointers.h" +#include "ardour/fixed_delay.h" +#include "ardour/midi_buffer.h" namespace ARDOUR { @@ -43,6 +45,7 @@ class AudioTrack; class AudioPort; class AudioRegion; class CapturingProcessor; +class MidiPort; /// Export channel base class interface for different source types class LIBARDOUR_API ExportChannel : public boost::less_than_comparable @@ -57,6 +60,9 @@ public: virtual bool empty () const = 0; + virtual bool audio () const { return true; } + virtual bool midi () const { return false; } + /// Adds state to node passed virtual void get_state (XMLNode* node) const = 0; @@ -89,7 +95,7 @@ public: bool operator< (ExportChannel const& other) const; void add_port (boost::weak_ptr port) { ports.insert (port); } - PortSet const& get_ports () { return ports; } + PortSet const& get_ports () const { return ports; } private: PortSet ports; @@ -99,6 +105,40 @@ private: std::list>> _delaylines; }; +/// Basic export channel that reads from MIDIPorts +class LIBARDOUR_API PortExportMIDI : public ExportChannel +{ +public: + PortExportMIDI (); + ~PortExportMIDI (); + + /* ExportChannel interface */ + samplecnt_t common_port_playback_latency () const; + void prepare_export (samplecnt_t max_samples, sampleoffset_t common_latency); + + void read (Buffer const*&, samplecnt_t samples) const; + + bool empty () const { return _port.expired (); } + + bool audio () const { return false; } + bool midi () const { return true; } + + void get_state (XMLNode* node) const; + void set_state (XMLNode* node, Session& session); + + bool operator< (ExportChannel const& other) const; + + void set_port (boost::weak_ptr port) + { + _port = port; + } + +private: + boost::weak_ptr _port; + mutable FixedDelay _delayline; + mutable MidiBuffer _buf; +}; + /// Handles RegionExportChannels and does actual reading from region class LIBARDOUR_API RegionExportChannelFactory { @@ -181,8 +221,10 @@ class LIBARDOUR_API RouteExportChannel : public ExportChannel public: RouteExportChannel (boost::shared_ptr processor, - size_t channel, - boost::shared_ptr remover); + DataType type, + size_t channel, + boost::shared_ptr remover); + ~RouteExportChannel (); static void create_from_route (std::list& result, boost::shared_ptr route); @@ -194,6 +236,9 @@ public: // ExportChannel interface bool empty () const { return false; } + bool audio () const; + bool midi () const; + void get_state (XMLNode* node) const; void set_state (XMLNode* node, Session& session); @@ -205,22 +250,25 @@ private: { public: ProcessorRemover (boost::shared_ptr route, boost::shared_ptr processor) - : route (route) - , processor (processor) + : _route (route) + , _processor (processor) { } ~ProcessorRemover (); private: - boost::shared_ptr route; - boost::shared_ptr processor; + boost::shared_ptr _route; + boost::shared_ptr _processor; }; - boost::shared_ptr processor; - size_t channel; + boost::shared_ptr _processor; + + DataType _type; + size_t _channel; + // Each channel keeps a ref to the remover. Last one alive // will cause the processor to be removed on deletion. - boost::shared_ptr remover; + boost::shared_ptr _remover; }; } // namespace ARDOUR diff --git a/libs/ardour/export_channel.cc b/libs/ardour/export_channel.cc index e3e217f287..cd12216f24 100644 --- a/libs/ardour/export_channel.cc +++ b/libs/ardour/export_channel.cc @@ -3,7 +3,7 @@ * Copyright (C) 2008-2011 Sakari Bergen * Copyright (C) 2009-2011 David Robillard * Copyright (C) 2009-2017 Paul Davis - * Copyright (C) 2014-2017 Robin Gareus + * Copyright (C) 2014-2022 Robin Gareus * * 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 @@ -28,6 +28,7 @@ #include "ardour/audioregion.h" #include "ardour/capturing_processor.h" #include "ardour/export_failed.h" +#include "ardour/midi_port.h" #include "ardour/session.h" #include "pbd/error.h" @@ -107,7 +108,7 @@ PortExportChannel::read (Buffer const*& buf, samplecnt_t samples) const assert (_buffer); assert (samples <= _buffer_size); - if (ports.size () == 1 && _delaylines.size () == 1 && _delaylines.front ()->bufsize () == _buffer_size + 1) { + if (ports.size () == 1 && _delaylines.size () == 1 && !ports.begin ()->expired () && _delaylines.front ()->bufsize () == _buffer_size + 1) { boost::shared_ptr p = ports.begin ()->lock (); AudioBuffer& ab (p->get_audio_buffer (samples)); // unsets AudioBuffer::_written ab.set_written (true); @@ -144,7 +145,7 @@ PortExportChannel::read (Buffer const*& buf, samplecnt_t samples) const ++di; } - _buf.set_data (_buffer.get(), samples); + _buf.set_data (_buffer.get (), samples); buf = &_buf; } @@ -177,6 +178,91 @@ PortExportChannel::set_state (XMLNode* node, Session& session) } } +PortExportMIDI::PortExportMIDI () + : _buf (8192) +{ +} + +PortExportMIDI::~PortExportMIDI () +{ +} + +samplecnt_t +PortExportMIDI::common_port_playback_latency () const +{ + boost::shared_ptr p = _port.lock (); + if (!p) { + return 0; + } + return p->private_latency_range (true).max; +} + +void +PortExportMIDI::prepare_export (samplecnt_t max_samples, sampleoffset_t common_latency) +{ + boost::shared_ptr p = _port.lock (); + if (!p) { + return; + } + samplecnt_t latency = p->private_latency_range (true).max - common_latency; + _delayline.set (ChanCount (DataType::MIDI, 1), latency); +} + +bool +PortExportMIDI::operator< (ExportChannel const& other) const +{ + PortExportMIDI const* pem; + if (!(pem = dynamic_cast (&other))) { + return this < &other; + } + return _port < pem->_port; +} + +void +PortExportMIDI::read (Buffer const*& buf, samplecnt_t samples) const +{ + boost::shared_ptr p = _port.lock (); + if (!p) { + _buf.clear (); + buf = &_buf; + } + MidiBuffer& mb (p->get_midi_buffer (samples)); + if (_delayline.delay () == 0) { + buf = &mb; + } else { + _delayline.delay (DataType::MIDI, 0, _buf, mb, samples); + buf = &_buf; + } +} + +void +PortExportMIDI::get_state (XMLNode* node) const +{ + XMLNode* port_node; + boost::shared_ptr p = _port.lock (); + if (p && (port_node = node->add_child ("MIDIPort"))) { + port_node->set_property ("name", p->name ()); + } +} + +void +PortExportMIDI::set_state (XMLNode* node, Session& session) +{ + XMLNode* xml_port = node->child ("MIDIPort"); + if (!xml_port) { + return; + } + std::string name; + if (xml_port->get_property ("name", name)) { + boost::shared_ptr port = boost::dynamic_pointer_cast (session.engine ().get_port_by_name (name)); + if (port) { + _port = port; + } else { + PBD::warning << string_compose (_("Could not get port for export channel \"%1\", dropping the channel"), name) << endmsg; + } + } +} + RegionExportChannelFactory::RegionExportChannelFactory (Session* session, AudioRegion const& region, AudioTrack&, Type type) : region (region) , type (type) @@ -258,11 +344,14 @@ RegionExportChannelFactory::update_buffers (samplecnt_t samples) position += samples; } -RouteExportChannel::RouteExportChannel (boost::shared_ptr processor, size_t channel, - boost::shared_ptr remover) - : processor (processor) - , channel (channel) - , remover (remover) +RouteExportChannel::RouteExportChannel (boost::shared_ptr processor, + DataType type, + size_t channel, + boost::shared_ptr remover) + : _processor (processor) + , _type (type) + , _channel (channel) + , _remover (remover) { } @@ -274,34 +363,45 @@ void RouteExportChannel::create_from_route (std::list& result, boost::shared_ptr route) { boost::shared_ptr processor = route->add_export_point (); - uint32_t channels = processor->input_streams ().n_audio (); + uint32_t n_audio = processor->input_streams ().n_audio (); + uint32_t n_midi = processor->input_streams ().n_midi (); boost::shared_ptr remover (new ProcessorRemover (route, processor)); result.clear (); - for (uint32_t i = 0; i < channels; ++i) { - result.push_back (ExportChannelPtr (new RouteExportChannel (processor, i, remover))); + for (uint32_t i = 0; i < n_audio; ++i) { + result.push_back (ExportChannelPtr (new RouteExportChannel (processor, DataType::AUDIO, i, remover))); } + for (uint32_t i = 0; i < n_midi; ++i) { + result.push_back (ExportChannelPtr (new RouteExportChannel (processor, DataType::MIDI, i, remover))); + } +} + +bool +RouteExportChannel::audio () const +{ + return _processor->input_streams ().n_audio () > 0; +} + +bool +RouteExportChannel::midi () const +{ + return _processor->input_streams ().n_midi () > 0; } void RouteExportChannel::prepare_export (samplecnt_t max_samples, sampleoffset_t) { - if (processor) { - processor->set_block_size (max_samples); + if (_processor) { + _processor->set_block_size (max_samples); } } void RouteExportChannel::read (Buffer const*& buf, samplecnt_t samples) const { - assert (processor); - AudioBuffer const& buffer = processor->get_capture_buffers ().get_audio (channel); -#ifndef NDEBUG - (void)samples; -#else - assert (samples <= (samplecnt_t)buffer.capacity ()); -#endif - buf = &buffer; + assert (_processor); + Buffer const& buffer = _processor->get_capture_buffers ().get_available (_type, _channel); + buf = &buffer; } void @@ -324,13 +424,17 @@ RouteExportChannel::operator< (ExportChannel const& other) const return this < &other; } - if (processor.get () == rec->processor.get ()) { - return channel < rec->channel; + if (_processor.get () == rec->_processor.get ()) { + if (_type == rec->_type) { + return _channel < rec->_channel; + } else { + return _type < rec->_type; + } } - return processor.get () < rec->processor.get (); + return _processor.get () < rec->_processor.get (); } RouteExportChannel::ProcessorRemover::~ProcessorRemover () { - route->remove_processor (processor); + _route->remove_processor (_processor); } diff --git a/libs/ardour/midi_port.cc b/libs/ardour/midi_port.cc index a37ee5ee93..831289bc16 100644 --- a/libs/ardour/midi_port.cc +++ b/libs/ardour/midi_port.cc @@ -232,6 +232,7 @@ void MidiPort::cycle_split () { _data_fetched_for_cycle = false; + _buffer->clear (); } void @@ -345,9 +346,13 @@ MidiPort::flush_buffers (pframes_t nframes) } } - /* done.. the data has moved to the port buffer, mark it so */ - - _buffer->clear (); + /* done.. the data has moved to the port buffer, mark it so, + * unless we're exporting in which PortExportMIDI::read + * needs to read it at the end of a process cycle. + */ + if (!AudioEngine::instance()->session()->exporting ()) { + _buffer->clear (); + } } }