diff options
Diffstat (limited to 'libs/ardour')
75 files changed, 4407 insertions, 2590 deletions
diff --git a/libs/ardour/ardour/ardour.h b/libs/ardour/ardour/ardour.h index eaf6b572fd..311611997f 100644 --- a/libs/ardour/ardour/ardour.h +++ b/libs/ardour/ardour/ardour.h @@ -75,9 +75,7 @@ namespace ARDOUR { bool translations_are_enabled (); bool set_translations_enabled (bool); - static inline microseconds_t get_microseconds () { - return (microseconds_t) jack_get_time(); - } + microseconds_t get_microseconds (); void setup_fpu (); std::vector<SyncSource> get_available_sync_options(); diff --git a/libs/ardour/ardour/async_midi_port.h b/libs/ardour/ardour/async_midi_port.h new file mode 100644 index 0000000000..896301b398 --- /dev/null +++ b/libs/ardour/ardour/async_midi_port.h @@ -0,0 +1,99 @@ +/* + Copyright (C) 1998-2010 Paul Barton-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. + +*/ + +#ifndef __libardour_async_midiport_h__ +#define __libardour_async_midiport_h__ + +#include <string> +#include <iostream> + +#include "pbd/xml++.h" +#include "pbd/crossthread.h" +#include "pbd/signals.h" +#include "pbd/ringbuffer.h" + +#include "evoral/Event.hpp" +#include "evoral/EventRingBuffer.hpp" + +#include "midi++/types.h" +#include "midi++/parser.h" +#include "midi++/port.h" + +#include "ardour/midi_port.h" + +namespace ARDOUR { + + class AsyncMIDIPort : public ARDOUR::MidiPort, public MIDI::Port { + + public: + AsyncMIDIPort (std::string const &, PortFlags); + ~AsyncMIDIPort (); + + /* called from an RT context */ + + void cycle_start (pframes_t nframes); + void cycle_end (pframes_t nframes); + + /* called from non-RT context */ + + void parse (framecnt_t timestamp); + int write (const MIDI::byte *msg, size_t msglen, MIDI::timestamp_t timestamp); + int read (MIDI::byte *buf, size_t bufsize); + void drain (int check_interval_usecs); + int selectable () const { +#ifdef PLATFORM_WINDOWS + return false; +#else + return xthread.selectable(); +#endif + } + + static void set_process_thread (pthread_t); + static pthread_t get_process_thread () { return _process_thread; } + static bool is_process_thread(); + + private: + bool _currently_in_cycle; + MIDI::timestamp_t _last_write_timestamp; + RingBuffer< Evoral::Event<double> > output_fifo; + Evoral::EventRingBuffer<MIDI::timestamp_t> input_fifo; + Glib::Threads::Mutex output_fifo_lock; +#ifndef PLATFORM_WINDOWS + CrossThreadChannel xthread; +#endif + + int create_port (); + + /** Channel used to signal to the MidiControlUI that input has arrived */ + + std::string _connections; + PBD::ScopedConnection connect_connection; + PBD::ScopedConnection halt_connection; + void flush (void* jack_port_buffer); + void jack_halted (); + void make_connections (); + void init (std::string const &, Flags); + + void flush_output_fifo (pframes_t); + + static pthread_t _process_thread; +}; + +} // namespace ARDOUR + +#endif /* __libardour_async_midiport_h__ */ diff --git a/libs/ardour/ardour/audio_backend.h b/libs/ardour/ardour/audio_backend.h new file mode 100644 index 0000000000..ab37bea526 --- /dev/null +++ b/libs/ardour/ardour/audio_backend.h @@ -0,0 +1,432 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef __libardour_audiobackend_h__ +#define __libardour_audiobackend_h__ + +#include <string> +#include <vector> + +#include <stdint.h> +#include <stdlib.h> + +#include <boost/function.hpp> + +#include "ardour/types.h" + +namespace ARDOUR { + +class AudioEngine; +class PortEngine; +class PortManager; + +class AudioBackend { + public: + + AudioBackend (AudioEngine& e) : engine (e){} + virtual ~AudioBackend () {} + + /** Return the name of this backend. + * + * Should use a well-known, unique term. Expected examples + * might include "JACK", "CoreAudio", "ASIO" etc. + */ + virtual std::string name() const = 0; + + /** Return a private, type-free pointer to any data + * that might be useful to a concrete implementation + */ + virtual void* private_handle() const = 0; + + /** Return true if the underlying mechanism/API is still available + * for us to utilize. return false if some or all of the AudioBackend + * API can no longer be effectively used. + */ + virtual bool connected() const = 0; + + /** Return true if the callback from the underlying mechanism/API + * (CoreAudio, JACK, ASIO etc.) occurs in a thread subject to realtime + * constraints. Return false otherwise. + */ + virtual bool is_realtime () const = 0; + + /* Discovering devices and parameters */ + + /** Return true if this backend requires the selection of a "driver" + * before any device can be selected. Return false otherwise. + * + * Intended mainly to differentiate between meta-APIs like JACK + * which can still expose different backends (such as ALSA or CoreAudio + * or FFADO or netjack) and those like ASIO or CoreAudio which + * do not. + */ + virtual bool requires_driver_selection() const { return false; } + + /** If the return value of requires_driver_selection() is true, + * then this function can return the list of known driver names. + * + * If the return value of requires_driver_selection() is false, + * then this function should not be called. If it is called + * its return value is an empty vector of strings. + */ + virtual std::vector<std::string> enumerate_drivers() const { return std::vector<std::string>(); } + + /** Returns zero if the backend can successfully use @param name as the + * driver, non-zero otherwise. + * + * Should not be used unless the backend returns true from + * requires_driver_selection() + */ + virtual int set_driver (const std::string& /*drivername*/) { return 0; } + + /** used to list device names along with whether or not they are currently + * available. + */ + struct DeviceStatus { + std::string name; + bool available; + + DeviceStatus (const std::string& s, bool avail) : name (s), available (avail) {} + }; + + /** Returns a collection of DeviceStatuses identifying devices discovered + * by this backend since the start of the process. + * + * Any of the names in each DeviceStatus may be used to identify a + * device in other calls to the backend, though any of them may become + * invalid at any time. + */ + virtual std::vector<DeviceStatus> enumerate_devices () const = 0; + + /** Returns a collection of float identifying sample rates that are + * potentially usable with the hardware identified by @param device. + * Any of these values may be supplied in other calls to this backend + * as the desired sample rate to use with the name device, but the + * requested sample rate may turn out to be unavailable, or become invalid + * at any time. + */ + virtual std::vector<float> available_sample_rates (const std::string& device) const = 0; + /** Returns a collection of uint32 identifying buffer sizes that are + * potentially usable with the hardware identified by @param device. + * Any of these values may be supplied in other calls to this backend + * as the desired buffer size to use with the name device, but the + * requested buffer size may turn out to be unavailable, or become invalid + * at any time. + */ + virtual std::vector<uint32_t> available_buffer_sizes (const std::string& device) const = 0; + + /** Returns the maximum number of input channels that are potentially + * usable with the hardware identified by @param device. Any number from 1 + * to the value returned may be supplied in other calls to this backend as + * the input channel count to use with the name device, but the requested + * count may turn out to be unavailable, or become invalid at any time. + */ + virtual uint32_t available_input_channel_count (const std::string& device) const = 0; + + /** Returns the maximum number of output channels that are potentially + * usable with the hardware identified by @param device. Any number from 1 + * to the value returned may be supplied in other calls to this backend as + * the output channel count to use with the name device, but the requested + * count may turn out to be unavailable, or become invalid at any time. + */ + virtual uint32_t available_output_channel_count (const std::string& device) const = 0; + + /* Return true if the derived class can change the sample rate of the + * device in use while the device is already being used. Return false + * otherwise. (example: JACK cannot do this as of September 2013) + */ + virtual bool can_change_sample_rate_when_running () const = 0; + /* Return true if the derived class can change the buffer size of the + * device in use while the device is already being used. Return false + * otherwise. + */ + virtual bool can_change_buffer_size_when_running () const = 0; + + /* Set the hardware parameters. + * + * If called when the current state is stopped or paused, + * the changes will not take effect until the state changes to running. + * + * If called while running, the state will change as fast as the + * implementation allows. + * + * All set_*() methods return zero on success, non-zero otherwise. + */ + + /** Set the name of the device to be used + */ + virtual int set_device_name (const std::string&) = 0; + /** Set the sample rate to be used + */ + virtual int set_sample_rate (float) = 0; + /** Set the buffer size to be used. + * + * The device is assumed to use a double buffering scheme, so that one + * buffer's worth of data can be processed by hardware while software works + * on the other buffer. All known suitable audio APIs support this model + * (though ALSA allows for alternate numbers of buffers, and CoreAudio + * doesn't directly expose the concept). + */ + virtual int set_buffer_size (uint32_t) = 0; + /** Set the preferred underlying hardware sample format + * + * This does not change the sample format (32 bit float) read and + * written to the device via the Port API. + */ + virtual int set_sample_format (SampleFormat) = 0; + /** Set the preferred underlying hardware data layout. + * If @param yn is true, then the hardware will interleave + * samples for successive channels; otherwise, the hardware will store + * samples for a single channel contiguously. + * + * Setting this does not change the fact that all data streams + * to and from Ports are mono (essentially, non-interleaved) + */ + virtual int set_interleaved (bool yn) = 0; + /** Set the number of input channels that should be used + */ + virtual int set_input_channels (uint32_t) = 0; + /** Set the number of output channels that should be used + */ + virtual int set_output_channels (uint32_t) = 0; + /** Set the (additional) input latency that cannot be determined via + * the implementation's underlying code (e.g. latency from + * external D-A/D-A converters. Units are samples. + */ + virtual int set_systemic_input_latency (uint32_t) = 0; + /** Set the (additional) output latency that cannot be determined via + * the implementation's underlying code (e.g. latency from + * external D-A/D-A converters. Units are samples. + */ + virtual int set_systemic_output_latency (uint32_t) = 0; + + /* Retrieving parameters */ + + virtual std::string device_name () const = 0; + virtual float sample_rate () const = 0; + virtual uint32_t buffer_size () const = 0; + virtual SampleFormat sample_format () const = 0; + virtual bool interleaved () const = 0; + virtual uint32_t input_channels () const = 0; + virtual uint32_t output_channels () const = 0; + virtual uint32_t systemic_input_latency () const = 0; + virtual uint32_t systemic_output_latency () const = 0; + + /** Return the name of a control application for the + * selected/in-use device. If no such application exists, + * or if no device has been selected or is in-use, + * return an empty string. + */ + virtual std::string control_app_name() const = 0; + /** Launch the control app for the currently in-use or + * selected device. May do nothing if the control + * app is undefined or cannot be launched. + */ + virtual void launch_control_app () = 0; + /* Basic state control */ + + /** Start using the device named in the most recent call + * to set_device(), with the parameters set by various + * the most recent calls to set_sample_rate() etc. etc. + * + * At some undetermined time after this function is successfully called, + * the backend will start calling the ::process_callback() method of + * the AudioEngine referenced by @param engine. These calls will + * occur in a thread created by and/or under the control of the backend. + * + * Return zero if successful, negative values otherwise. + */ + virtual int start () = 0; + + /** Stop using the device currently in use. + * + * If the function is successfully called, no subsequent calls to the + * process_callback() of @param engine will be made after the function + * returns, until parameters are reset and start() are called again. + * + * The backend is considered to be un-configured after a successful + * return, and requires calls to set hardware parameters before it can be + * start()-ed again. See pause() for a way to avoid this. stop() should + * only be used when reconfiguration is required OR when there are no + * plans to use the backend in the future with a reconfiguration. + * + * Return zero if successful, 1 if the device is not in use, negative values on error + */ + virtual int stop () = 0; + + /** Temporarily cease using the device named in the most recent call to set_parameters(). + * + * If the function is successfully called, no subsequent calls to the + * process_callback() of @param engine will be made after the function + * returns, until start() is called again. + * + * The backend will retain its existing parameter configuration after a successful + * return, and does NOT require any calls to set hardware parameters before it can be + * start()-ed again. + * + * Return zero if successful, 1 if the device is not in use, negative values on error + */ + virtual int pause () = 0; + + /** While remaining connected to the device, and without changing its + * configuration, start (or stop) calling the process_callback() of @param engine + * without waiting for the device. Once process_callback() has returned, it + * will be called again immediately, thus allowing for faster-than-realtime + * processing. + * + * All registered ports remain in existence and all connections remain + * unaltered. However, any physical ports should NOT be used by the + * process_callback() during freewheeling - the data behaviour is undefined. + * + * If @param start_stop is true, begin this behaviour; otherwise cease this + * behaviour if it currently occuring, and return to calling + * process_callback() of @param engine by waiting for the device. + * + * Return zero on success, non-zero otherwise. + */ + virtual int freewheel (bool start_stop) = 0; + + /** return the fraction of the time represented by the current buffer + * size that is being used for each buffer process cycle, as a value + * from 0.0 to 1.0 + * + * E.g. if the buffer size represents 5msec and current processing + * takes 1msec, the returned value should be 0.2. + * + * Implementations can feel free to smooth the values returned over + * time (e.g. high pass filtering, or its equivalent). + */ + virtual float cpu_load() const = 0; + + /* Transport Control (JACK is the only audio API that currently offers + the concept of shared transport control) + */ + + /** Attempt to change the transport state to TransportRolling. + */ + virtual void transport_start () {} + /** Attempt to change the transport state to TransportStopped. + */ + virtual void transport_stop () {} + /** return the current transport state + */ + virtual TransportState transport_state () const { return TransportStopped; } + /** Attempt to locate the transport to @param pos + */ + virtual void transport_locate (framepos_t /*pos*/) {} + /** Return the current transport location, in samples measured + * from the origin (defined by the transport time master) + */ + virtual framepos_t transport_frame() const { return 0; } + + /** If @param yn is true, become the time master for any inter-application transport + * timebase, otherwise cease to be the time master for the same. + * + * Return zero on success, non-zero otherwise + * + * JACK is the only currently known audio API with the concept of a shared + * transport timebase. + */ + virtual int set_time_master (bool /*yn*/) { return 0; } + + virtual int usecs_per_cycle () const { return 1000000 * (buffer_size() / sample_rate()); } + virtual size_t raw_buffer_size (DataType t) = 0; + + /* Process time */ + + /** return the time according to the sample clock in use, measured in + * samples since an arbitrary zero time in the past. The value should + * increase monotonically and linearly, without interruption from any + * source (including CPU frequency scaling). + * + * It is extremely likely that any implementation will use a DLL, since + * this function can be called from any thread, at any time, and must be + * able to accurately determine the correct sample time. + * + * Can be called from any thread. + */ + virtual pframes_t sample_time () = 0; + + /** Return the time according to the sample clock in use when the most + * recent buffer process cycle began. Can be called from any thread. + */ + virtual pframes_t sample_time_at_cycle_start () = 0; + + /** Return the time since the current buffer process cycle started, + * in samples, according to the sample clock in use. + * + * Can ONLY be called from within a process() callback tree (which + * implies that it can only be called by a process thread) + */ + virtual pframes_t samples_since_cycle_start () = 0; + + /** Return true if it possible to determine the offset in samples of the + * first video frame that starts within the current buffer process cycle, + * measured from the first sample of the cycle. If returning true, + * set @param offset to that offset. + * + * Eg. if it can be determined that the first video frame within the cycle + * starts 28 samples after the first sample of the cycle, then this method + * should return true and set @param offset to 28. + * + * May be impossible to support outside of JACK, which has specific support + * (in some cases, hardware support) for this feature. + * + * Can ONLY be called from within a process() callback tree (which implies + * that it can only be called by a process thread) + */ + virtual bool get_sync_offset (pframes_t& /*offset*/) const { return false; } + + /** Create a new thread suitable for running part of the buffer process + * cycle (i.e. Realtime scheduling, memory allocation, etc. etc are all + * correctly setup), with a stack size given in bytes by specified @param + * stacksize. The thread will begin executing @param func, and will exit + * when that function returns. + */ + virtual int create_process_thread (boost::function<void()> func, pthread_t*, size_t stacksize) = 0; + + virtual void update_latencies () = 0; + + protected: + AudioEngine& engine; +}; + +struct AudioBackendInfo { + const char* name; + + int (*instantiate) (const std::string& arg1, const std::string& arg2); + int (*deinstantiate) (void); + + boost::shared_ptr<AudioBackend> (*backend_factory) (AudioEngine&); + boost::shared_ptr<PortEngine> (*portengine_factory) (PortManager&); + + /** Return true if the underlying mechanism/API has been + * configured and does not need (re)configuration in order + * to be usable. Return false otherwise. + * + * Note that this may return true if (re)configuration, even though + * not currently required, is still possible. + */ + bool (*already_configured)(); +}; + +} // namespace + +#endif /* __libardour_audiobackend_h__ */ + diff --git a/libs/ardour/ardour/audio_diskstream.h b/libs/ardour/ardour/audio_diskstream.h index b636cb4734..5a856e9b36 100644 --- a/libs/ardour/ardour/audio_diskstream.h +++ b/libs/ardour/ardour/audio_diskstream.h @@ -114,7 +114,7 @@ class AudioDiskstream : public Diskstream XMLNode& get_state(void); int set_state(const XMLNode& node, int version); - void request_jack_monitors_input (bool); + void request_input_monitoring (bool); static void swap_by_ptr (Sample *first, Sample *last) { while (first < last) { @@ -160,7 +160,7 @@ class AudioDiskstream : public Diskstream std::string name; bool is_physical () const; - void request_jack_monitors_input (bool) const; + void request_input_monitoring (bool) const; }; /** Information about one of our channels */ diff --git a/libs/ardour/ardour/audio_port.h b/libs/ardour/ardour/audio_port.h index 7f084a5c85..f87b134e9e 100644 --- a/libs/ardour/ardour/audio_port.h +++ b/libs/ardour/ardour/audio_port.h @@ -46,10 +46,10 @@ class AudioPort : public Port AudioBuffer& get_audio_buffer (pframes_t nframes); protected: - friend class AudioEngine; + friend class PortManager; + AudioPort (std::string const &, PortFlags); - AudioPort (std::string const &, Flags); - /* special access for engine only */ + /* special access for PortManager only (hah, C++) */ Sample* engine_get_whole_audio_buffer (); private: diff --git a/libs/ardour/ardour/audioengine.h b/libs/ardour/ardour/audioengine.h index ec31dd1ac2..cf4f91d4d0 100644 --- a/libs/ardour/ardour/audioengine.h +++ b/libs/ardour/ardour/audioengine.h @@ -33,29 +33,22 @@ #include <glibmm/threads.h> -#include "pbd/rcu.h" #include "pbd/signals.h" #include "pbd/stacktrace.h" -#ifndef PLATFORM_WINDOWS -#include <jack/weakjack.h> -#endif - -#include <jack/jack.h> -#include <jack/transport.h> -#include <jack/thread.h> - #include "ardour/ardour.h" - #include "ardour/data_type.h" #include "ardour/session_handle.h" #include "ardour/types.h" #include "ardour/chan_count.h" +#include "ardour/port_manager.h" #ifdef HAVE_JACK_SESSION #include <jack/session.h> #endif +class MTDM; + namespace ARDOUR { class InternalPort; @@ -63,300 +56,190 @@ class MidiPort; class Port; class Session; class ProcessThread; +class AudioBackend; +class AudioBackendInfo; -class AudioEngine : public SessionHandlePtr +class AudioEngine : public SessionHandlePtr, public PortManager { public: - typedef std::map<std::string,boost::shared_ptr<Port> > Ports; - - AudioEngine (std::string client_name, std::string session_uuid); - virtual ~AudioEngine (); - - jack_client_t* jack() const; - bool connected() const { return _jack != 0; } - - bool is_realtime () const; - - ProcessThread* main_thread() const { return _main_thread; } - - std::string client_name() const { return jack_client_name; } - - int reconnect_to_jack (); - int disconnect_from_jack(); - - int stop (bool forever = false); - int start (); - bool running() const { return _running; } - - Glib::Threads::Mutex& process_lock() { return _process_lock; } - - framecnt_t frame_rate () const; - pframes_t frames_per_cycle () const; - - size_t raw_buffer_size(DataType t); - - int usecs_per_cycle () const { return _usecs_per_cycle; } - - bool get_sync_offset (pframes_t & offset) const; - - pframes_t frames_since_cycle_start () { - jack_client_t* _priv_jack = _jack; - if (!_running || !_priv_jack) { - return 0; - } - return jack_frames_since_cycle_start (_priv_jack); - } - - pframes_t frame_time () { - jack_client_t* _priv_jack = _jack; - if (!_running || !_priv_jack) { - return 0; - } - return jack_frame_time (_priv_jack); - } - - pframes_t frame_time_at_cycle_start () { - jack_client_t* _priv_jack = _jack; - if (!_running || !_priv_jack) { - return 0; - } - return jack_last_frame_time (_priv_jack); - } - - pframes_t transport_frame () const { - const jack_client_t* _priv_jack = _jack; - if (!_running || !_priv_jack) { - return 0; - } - return jack_get_current_transport_frame (_priv_jack); - } - - int request_buffer_size (pframes_t); - - framecnt_t processed_frames() const { return _processed_frames; } - - float get_cpu_load() { - jack_client_t* _priv_jack = _jack; - if (!_running || !_priv_jack) { - return 0; - } - return jack_cpu_load (_priv_jack); - } - - void set_session (Session *); - void remove_session (); // not a replacement for SessionHandle::session_going_away() - - class PortRegistrationFailure : public std::exception { - public: - PortRegistrationFailure (std::string const & why = "") - : reason (why) {} - - ~PortRegistrationFailure () throw () {} - - virtual const char *what() const throw () { return reason.c_str(); } - - private: - std::string reason; - }; - - class NoBackendAvailable : public std::exception { - public: - virtual const char *what() const throw() { return "could not connect to engine backend"; } - }; - - boost::shared_ptr<Port> register_input_port (DataType, const std::string& portname); - boost::shared_ptr<Port> register_output_port (DataType, const std::string& portname); - int unregister_port (boost::shared_ptr<Port>); - - bool port_is_physical (const std::string&) const; - void request_jack_monitors_input (const std::string&, bool) const; - - void split_cycle (pframes_t offset); - - int connect (const std::string& source, const std::string& destination); - int disconnect (const std::string& source, const std::string& destination); - int disconnect (boost::shared_ptr<Port>); - - const char ** get_ports (const std::string& port_name_pattern, const std::string& type_name_pattern, uint32_t flags); - - bool can_request_hardware_monitoring (); - - ChanCount n_physical_outputs () const; - ChanCount n_physical_inputs () const; - - void get_physical_outputs (DataType type, std::vector<std::string>&); - void get_physical_inputs (DataType type, std::vector<std::string>&); - boost::shared_ptr<Port> get_port_by_name (const std::string &); - void port_renamed (const std::string&, const std::string&); - - enum TransportState { - TransportStopped = JackTransportStopped, - TransportRolling = JackTransportRolling, - TransportLooping = JackTransportLooping, - TransportStarting = JackTransportStarting - }; - - void transport_start (); - void transport_stop (); - void transport_locate (framepos_t); - TransportState transport_state (); - - int reset_timebase (); - - void update_latencies (); - - /* start/stop freewheeling */ - - int freewheel (bool onoff); - bool freewheeling() const { return _freewheeling; } - - /* this signal is sent for every process() cycle while freewheeling. -_ the regular process() call to session->process() is not made. - */ - - PBD::Signal1<int, pframes_t> Freewheel; - - PBD::Signal0<void> Xrun; - - /* this signal is if JACK notifies us of a graph order event */ - - PBD::Signal0<void> GraphReordered; - -#ifdef HAVE_JACK_SESSION - PBD::Signal1<void,jack_session_event_t *> JackSessionEvent; -#endif - - - /* this signal is emitted if the sample rate changes */ - - PBD::Signal1<void, framecnt_t> SampleRateChanged; - - /* this signal is sent if JACK ever disconnects us */ - - PBD::Signal1<void,const char*> Halted; - - /* these two are emitted when the engine itself is - started and stopped - */ - - PBD::Signal0<void> Running; - PBD::Signal0<void> Stopped; - - /** Emitted if a JACK port is registered or unregistered */ - PBD::Signal0<void> PortRegisteredOrUnregistered; - - /** Emitted if a JACK port is connected or disconnected. - * The Port parameters are the ports being connected / disconnected, or 0 if they are not known to Ardour. - * The std::string parameters are the (long) port names. - * The bool parameter is true if ports were connected, or false for disconnected. - */ - PBD::Signal5<void, boost::weak_ptr<Port>, std::string, boost::weak_ptr<Port>, std::string, bool> PortConnectedOrDisconnected; - - std::string make_port_name_relative (std::string) const; - std::string make_port_name_non_relative (std::string) const; - bool port_is_mine (const std::string&) const; - - static AudioEngine* instance() { return _instance; } - static void destroy(); - void died (); - - int create_process_thread (boost::function<void()>, jack_native_thread_t*, size_t stacksize); - bool stop_process_thread (jack_native_thread_t); - -private: - static AudioEngine* _instance; - - jack_client_t* volatile _jack; /* could be reset to null by SIGPIPE or another thread */ - std::string jack_client_name; - Glib::Threads::Mutex _process_lock; - Glib::Threads::Cond session_removed; - bool session_remove_pending; - frameoffset_t session_removal_countdown; - gain_t session_removal_gain; - gain_t session_removal_gain_step; - bool _running; - bool _has_run; - mutable framecnt_t _buffer_size; - std::map<DataType,size_t> _raw_buffer_sizes; - mutable framecnt_t _frame_rate; - /// number of frames between each check for changes in monitor input - framecnt_t monitor_check_interval; - /// time of the last monitor check in frames - framecnt_t last_monitor_check; - /// the number of frames processed since start() was called - framecnt_t _processed_frames; - bool _freewheeling; - bool _pre_freewheel_mmc_enabled; - int _usecs_per_cycle; - bool port_remove_in_progress; - Glib::Threads::Thread* m_meter_thread; - ProcessThread* _main_thread; - - SerializedRCUManager<Ports> ports; - - boost::shared_ptr<Port> register_port (DataType type, const std::string& portname, bool input); - - int process_callback (pframes_t nframes); - void* process_thread (); - void remove_all_ports (); - - ChanCount n_physical (unsigned long) const; - void get_physical (DataType, unsigned long, std::vector<std::string> &); - - void port_registration_failure (const std::string& portname); - - static int _xrun_callback (void *arg); -#ifdef HAVE_JACK_SESSION - static void _session_callback (jack_session_event_t *event, void *arg); -#endif - static int _graph_order_callback (void *arg); - static void* _process_thread (void *arg); - static int _sample_rate_callback (pframes_t nframes, void *arg); - static int _bufsize_callback (pframes_t nframes, void *arg); - static void _jack_timebase_callback (jack_transport_state_t, pframes_t, jack_position_t*, int, void*); - static int _jack_sync_callback (jack_transport_state_t, jack_position_t*, void *arg); - static void _freewheel_callback (int , void *arg); - static void _registration_callback (jack_port_id_t, int, void *); - static void _connect_callback (jack_port_id_t, jack_port_id_t, int, void *); - - void jack_timebase_callback (jack_transport_state_t, pframes_t, jack_position_t*, int); - int jack_sync_callback (jack_transport_state_t, jack_position_t*); - int jack_bufsize_callback (pframes_t); - int jack_sample_rate_callback (pframes_t); - void freewheel_callback (int); - void connect_callback (jack_port_id_t, jack_port_id_t, int); - - void set_jack_callbacks (); - - static void _latency_callback (jack_latency_callback_mode_t, void*); - void jack_latency_callback (jack_latency_callback_mode_t); - - int connect_to_jack (std::string client_name, std::string session_uuid); - - static void halted (void *); - static void halted_info (jack_status_t,const char*,void *); - - void meter_thread (); - void start_metering_thread (); - void stop_metering_thread (); - - static gint m_meter_exit; - - struct ThreadData { - AudioEngine* engine; - boost::function<void()> f; - size_t stacksize; - - ThreadData (AudioEngine* ae, boost::function<void()> fp, size_t stacksz) - : engine (ae) , f (fp) , stacksize (stacksz) {} - }; - - static void* _start_process_thread (void*); - void parameter_changed (const std::string&); - PBD::ScopedConnection config_connection; + static AudioEngine* create (); + + virtual ~AudioEngine (); + + int discover_backends(); + std::vector<const AudioBackendInfo*> available_backends() const; + std::string current_backend_name () const; + boost::shared_ptr<AudioBackend> set_backend (const std::string&, const std::string& arg1, const std::string& arg2); + boost::shared_ptr<AudioBackend> current_backend() const { return _backend; } + bool setup_required () const; + + ProcessThread* main_thread() const { return _main_thread; } + + /* START BACKEND PROXY API + * + * See audio_backend.h for full documentation and semantics. These wrappers + * just forward to a backend implementation. + */ + + int start (); + int stop (); + int pause (); + int freewheel (bool start_stop); + float get_cpu_load() const ; + void transport_start (); + void transport_stop (); + TransportState transport_state (); + void transport_locate (framepos_t pos); + framepos_t transport_frame(); + framecnt_t sample_rate () const; + pframes_t samples_per_cycle () const; + int usecs_per_cycle () const; + size_t raw_buffer_size (DataType t); + pframes_t sample_time (); + pframes_t sample_time_at_cycle_start (); + pframes_t samples_since_cycle_start (); + bool get_sync_offset (pframes_t& offset) const; + int create_process_thread (boost::function<void()> func, pthread_t*, size_t stacksize); + bool is_realtime() const; + bool connected() const; + + int set_device_name (const std::string&); + int set_sample_rate (float); + int set_buffer_size (uint32_t); + int set_sample_format (SampleFormat); + int set_interleaved (bool yn); + int set_input_channels (uint32_t); + int set_output_channels (uint32_t); + int set_systemic_input_latency (uint32_t); + int set_systemic_output_latency (uint32_t); + + /* END BACKEND PROXY API */ + + bool freewheeling() const { return _freewheeling; } + bool running() const { return _running; } + + Glib::Threads::Mutex& process_lock() { return _process_lock; } + + int request_buffer_size (pframes_t samples) { + return set_buffer_size (samples); + } + + framecnt_t processed_frames() const { return _processed_frames; } + + void set_session (Session *); + void remove_session (); // not a replacement for SessionHandle::session_going_away() + Session* session() const { return _session; } + + class NoBackendAvailable : public std::exception { + public: + virtual const char *what() const throw() { return "could not connect to engine backend"; } + }; + + void split_cycle (pframes_t offset); + + int reset_timebase (); + + void update_latencies (); + + /* this signal is sent for every process() cycle while freewheeling. + (the regular process() call to session->process() is not made) + */ + + PBD::Signal1<int, pframes_t> Freewheel; + + PBD::Signal0<void> Xrun; + + /* this signal is emitted if the sample rate changes */ + + PBD::Signal1<void, framecnt_t> SampleRateChanged; + + /* this signal is sent if the backend ever disconnects us */ + + PBD::Signal1<void,const char*> Halted; + + /* these two are emitted when the engine itself is + started and stopped + */ + + PBD::Signal0<void> Running; + PBD::Signal0<void> Stopped; + + static AudioEngine* instance() { return _instance; } + static void destroy(); + void died (); + + /* The backend will cause these at the appropriate time(s) + */ + int process_callback (pframes_t nframes); + int buffer_size_change (pframes_t nframes); + int sample_rate_change (pframes_t nframes); + void freewheel_callback (bool); + void timebase_callback (TransportState state, pframes_t nframes, framepos_t pos, int new_position); + int sync_callback (TransportState state, framepos_t position); + int port_registration_callback (); + void latency_callback (bool for_playback); + void halted_callback (const char* reason); + + /* sets up the process callback thread */ + static void thread_init_callback (void *); + + /* latency measurement */ + + MTDM* mtdm(); + void start_latency_detection (); + void stop_latency_detection (); + void set_latency_input_port (const std::string&); + void set_latency_output_port (const std::string&); + uint32_t latency_signal_delay () const { return _latency_signal_latency; } + + private: + AudioEngine (); + + static AudioEngine* _instance; + + boost::shared_ptr<AudioBackend> _backend; + + Glib::Threads::Mutex _process_lock; + Glib::Threads::Cond session_removed; + bool session_remove_pending; + frameoffset_t session_removal_countdown; + gain_t session_removal_gain; + gain_t session_removal_gain_step; + bool _running; + bool _freewheeling; + /// number of frames between each check for changes in monitor input + framecnt_t monitor_check_interval; + /// time of the last monitor check in frames + framecnt_t last_monitor_check; + /// the number of frames processed since start() was called + framecnt_t _processed_frames; + Glib::Threads::Thread* m_meter_thread; + ProcessThread* _main_thread; + MTDM* _mtdm; + bool _measuring_latency; + PortEngine::PortHandle _latency_input_port; + PortEngine::PortHandle _latency_output_port; + framecnt_t _latency_flush_frames; + std::string _latency_input_name; + std::string _latency_output_name; + framecnt_t _latency_signal_latency; + + void meter_thread (); + void start_metering_thread (); + void stop_metering_thread (); + + static gint m_meter_exit; + + void parameter_changed (const std::string&); + PBD::ScopedConnection config_connection; + + typedef std::map<std::string,AudioBackendInfo*> BackendMap; + BackendMap _backends; + AudioBackendInfo* backend_discover (const std::string&); + void drop_backend (); }; - + } // namespace ARDOUR #endif /* __ardour_audioengine_h__ */ diff --git a/libs/ardour/ardour/backend_search_path.h b/libs/ardour/ardour/backend_search_path.h new file mode 100644 index 0000000000..e8a5082c71 --- /dev/null +++ b/libs/ardour/ardour/backend_search_path.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2011 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. +*/ + +#ifndef __ardour_backend_search_path_h__ +#define __ardour_backend_search_path_h__ + +#include "pbd/search_path.h" + +namespace ARDOUR { + + /** + * return a SearchPath containing directories in which to look for + * backend plugins. + * + * If ARDOUR_BACKEND_PATH is defined then the SearchPath returned + * will contain only those directories specified in it, otherwise it will + * contain the user and system directories which may contain audio/MIDI + * backends. + */ + PBD::Searchpath backend_search_path (); + +} // namespace ARDOUR + +#endif /* __ardour_backend_search_path_h__ */ diff --git a/libs/ardour/ardour/buffer_set.h b/libs/ardour/ardour/buffer_set.h index 12e9dbc63b..26d47682af 100644 --- a/libs/ardour/ardour/buffer_set.h +++ b/libs/ardour/ardour/buffer_set.h @@ -70,7 +70,7 @@ public: void clear(); void attach_buffers (PortSet& ports); - void get_jack_port_addresses (PortSet &, framecnt_t); + void get_backend_port_addresses (PortSet &, framecnt_t); /* the capacity here is a size_t and has a different interpretation depending on the DataType of the buffers. for audio, its a frame count. for MIDI diff --git a/libs/ardour/ardour/data_type.h b/libs/ardour/ardour/data_type.h index 6a016ae75a..b4129e2629 100644 --- a/libs/ardour/ardour/data_type.h +++ b/libs/ardour/ardour/data_type.h @@ -21,11 +21,11 @@ #define __ardour_data_type_h__ #include <string> -#include <jack/jack.h> +#include <stdint.h> +#include <glib.h> namespace ARDOUR { - /** A type of Data Ardour is capable of processing. * * The majority of this class is dedicated to conversion to and from various @@ -61,33 +61,25 @@ public: /** Construct from a string (Used for loading from XML and Ports) * The string can be as in an XML file (eg "audio" or "midi"), or a - * Jack type string (from jack_port_type) */ + */ DataType(const std::string& str) : _symbol(NIL) { - if (str == "audio" || str == JACK_DEFAULT_AUDIO_TYPE) + if (!g_ascii_strncasecmp(str.c_str(), "audio", str.length())) { _symbol = AUDIO; - else if (str == "midi" || str == JACK_DEFAULT_MIDI_TYPE) + } else if (!g_ascii_strncasecmp(str.c_str(), "midi", str.length())) { _symbol = MIDI; - } - - /** Get the Jack type this DataType corresponds to */ - const char* to_jack_type() const { - switch (_symbol) { - case AUDIO: return JACK_DEFAULT_AUDIO_TYPE; - case MIDI: return JACK_DEFAULT_MIDI_TYPE; - default: return ""; } } /** Inverse of the from-string constructor */ const char* to_string() const { switch (_symbol) { - case AUDIO: return "audio"; - case MIDI: return "midi"; - default: return "unknown"; // reeeally shouldn't ever happen + case AUDIO: return "audio"; + case MIDI: return "midi"; + default: return "unknown"; // reeeally shouldn't ever happen } } - + const char* to_i18n_string () const; inline operator uint32_t() const { return (uint32_t)_symbol; } @@ -125,7 +117,6 @@ private: }; - } // namespace ARDOUR #endif // __ardour_data_type_h__ diff --git a/libs/ardour/ardour/debug.h b/libs/ardour/ardour/debug.h index ae18e59c04..47eee69df5 100644 --- a/libs/ardour/ardour/debug.h +++ b/libs/ardour/ardour/debug.h @@ -64,6 +64,7 @@ namespace PBD { extern uint64_t OrderKeys; extern uint64_t Automation; extern uint64_t WiimoteControl; + extern uint64_t Ports; } } diff --git a/libs/ardour/ardour/directory_names.h b/libs/ardour/ardour/directory_names.h index cb4701d336..72a456efe6 100644 --- a/libs/ardour/ardour/directory_names.h +++ b/libs/ardour/ardour/directory_names.h @@ -39,6 +39,7 @@ extern const char* const surfaces_dir_name; extern const char* const ladspa_dir_name; extern const char* const user_config_dir_name; extern const char* const panner_dir_name; +extern const char* const backend_dir_name; }; diff --git a/libs/ardour/ardour/diskstream.h b/libs/ardour/ardour/diskstream.h index 427b52b054..7a4bccbb46 100644 --- a/libs/ardour/ardour/diskstream.h +++ b/libs/ardour/ardour/diskstream.h @@ -133,8 +133,8 @@ class Diskstream : public SessionObject, public PublicDiskstream virtual XMLNode& get_state(void); virtual int set_state(const XMLNode&, int version); - virtual void request_jack_monitors_input (bool) {} - virtual void ensure_jack_monitors_input (bool) {} + virtual void request_input_monitoring (bool) {} + virtual void ensure_input_monitoring (bool) {} framecnt_t capture_offset() const { return _capture_offset; } virtual void set_capture_offset (); diff --git a/libs/ardour/ardour/graph.h b/libs/ardour/ardour/graph.h index 0a288d68d3..cac09d34af 100644 --- a/libs/ardour/ardour/graph.h +++ b/libs/ardour/ardour/graph.h @@ -92,7 +92,7 @@ protected: virtual void session_going_away (); private: - std::list<jack_native_thread_t> _thread_list; + std::list<pthread_t> _thread_list; volatile bool _quit_threads; void reset_thread_list (); diff --git a/libs/ardour/ardour/midi_buffer.h b/libs/ardour/ardour/midi_buffer.h index 5ef5e4c845..781396a598 100644 --- a/libs/ardour/ardour/midi_buffer.h +++ b/libs/ardour/ardour/midi_buffer.h @@ -44,7 +44,6 @@ public: void copy(const MidiBuffer& copy); bool push_back(const Evoral::MIDIEvent<TimeType>& event); - bool push_back(const jack_midi_event_t& event); bool push_back(TimeType time, size_t size, const uint8_t* data); uint8_t* reserve(TimeType time, size_t size); diff --git a/libs/ardour/ardour/midi_diskstream.h b/libs/ardour/ardour/midi_diskstream.h index 34fa0ae79a..b1c126b339 100644 --- a/libs/ardour/ardour/midi_diskstream.h +++ b/libs/ardour/ardour/midi_diskstream.h @@ -81,7 +81,7 @@ class MidiDiskstream : public Diskstream XMLNode& get_state(void); int set_state(const XMLNode&, int version); - void ensure_jack_monitors_input (bool); + void ensure_input_monitoring (bool); boost::shared_ptr<SMFSource> write_source () { return _write_source; } diff --git a/libs/ardour/ardour/midi_port.h b/libs/ardour/ardour/midi_port.h index 5dc55398cb..00617d90ec 100644 --- a/libs/ardour/ardour/midi_port.h +++ b/libs/ardour/ardour/midi_port.h @@ -21,6 +21,8 @@ #ifndef __ardour_midi_port_h__ #define __ardour_midi_port_h__ +#include "midi++/parser.h" + #include "ardour/port.h" #include "ardour/midi_buffer.h" #include "ardour/midi_state_tracker.h" @@ -56,18 +58,35 @@ class MidiPort : public Port { MidiBuffer& get_midi_buffer (pframes_t nframes); + void set_always_parse (bool yn); + MIDI::Parser& self_parser() { return _self_parser; } + protected: - friend class AudioEngine; + friend class PortManager; - MidiPort (const std::string& name, Flags); + MidiPort (const std::string& name, PortFlags); private: MidiBuffer* _buffer; bool _has_been_mixed_down; bool _resolve_required; bool _input_active; - - void resolve_notes (void* jack_buffer, MidiBuffer::TimeType when); + bool _always_parse; + + /* Naming this is tricky. AsyncMIDIPort inherits (for now, aug 2013) from + * both MIDI::Port, which has _parser, and this (ARDOUR::MidiPort). We + * need parsing support in this object, independently of what the + * MIDI::Port/AsyncMIDIPort stuff does. Rather than risk errors coming + * from not explicitly naming which _parser we want, we will call this + * _self_parser for now. + * + * Ultimately, MIDI::Port should probably go away or be fully integrated + * into this object, somehow. + */ + + MIDI::Parser _self_parser; + + void resolve_notes (void* buffer, MidiBuffer::TimeType when); }; } // namespace ARDOUR diff --git a/libs/ardour/ardour/midi_ui.h b/libs/ardour/ardour/midi_ui.h index 34e97494be..c15a530057 100644 --- a/libs/ardour/ardour/midi_ui.h +++ b/libs/ardour/ardour/midi_ui.h @@ -26,13 +26,11 @@ #include "pbd/signals.h" #include "pbd/stacktrace.h" -namespace MIDI { - class Port; -} namespace ARDOUR { class Session; +class AsyncMIDIPort; /* this is mostly a placeholder because I suspect that at some point we will want to add more members to accomodate @@ -67,7 +65,7 @@ class MidiControlUI : public AbstractUI<MidiUIRequest> ARDOUR::Session& _session; PBD::ScopedConnection rebind_connection; - bool midi_input_handler (Glib::IOCondition, MIDI::Port*); + bool midi_input_handler (Glib::IOCondition, AsyncMIDIPort*); void reset_ports (); void clear_ports (); diff --git a/libs/ardour/ardour/midiport_manager.h b/libs/ardour/ardour/midiport_manager.h new file mode 100644 index 0000000000..9f93c43d5a --- /dev/null +++ b/libs/ardour/ardour/midiport_manager.h @@ -0,0 +1,99 @@ +/* + Copyright (C) 1998 Paul Barton-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. + +*/ + +#ifndef __midiport_manager_h__ +#define __midiport_manager_h__ + +#include <list> + +#include <string> + +#include "pbd/rcu.h" + +#include "midi++/types.h" +#include "midi++/port.h" + +#include "ardour/types.h" + +namespace ARDOUR { + +class MidiPort; +class Port; + +class MidiPortManager { + public: + MidiPortManager(); + virtual ~MidiPortManager (); + + /* Ports used for control. These are read/written to outside of the + * process callback (asynchronously with respect to when data + * actually arrives). + * + * More detail: we do actually read/write data for these ports + * inside the process callback, but incoming data is only parsed + * and outgoing data is only generated *outside* the process + * callback. + */ + + MIDI::Port* midi_input_port () const { return _midi_input_port; } + MIDI::Port* midi_output_port () const { return _midi_output_port; } + MIDI::Port* mmc_input_port () const { return _mmc_input_port; } + MIDI::Port* mmc_output_port () const { return _mmc_output_port; } + + /* Ports used for synchronization. These have their I/O handled inside the + * process callback. + */ + + boost::shared_ptr<MidiPort> mtc_input_port() const { return _mtc_input_port; } + boost::shared_ptr<MidiPort> mtc_output_port() const { return _mtc_output_port; } + boost::shared_ptr<MidiPort> midi_clock_input_port() const { return _midi_clock_input_port; } + boost::shared_ptr<MidiPort> midi_clock_output_port() const { return _midi_clock_output_port; } + + void set_midi_port_states (const XMLNodeList&); + std::list<XMLNode*> get_midi_port_states () const; + + PBD::Signal0<void> PortsChanged; + + protected: + /* asynchronously handled ports: MIDI::Port */ + MIDI::Port* _midi_input_port; + MIDI::Port* _midi_output_port; + MIDI::Port* _mmc_input_port; + MIDI::Port* _mmc_output_port; + /* these point to the same objects as the 4 members above, + but cast to their ARDOUR::Port base class + */ + boost::shared_ptr<Port> _midi_in; + boost::shared_ptr<Port> _midi_out; + boost::shared_ptr<Port> _mmc_in; + boost::shared_ptr<Port> _mmc_out; + + /* synchronously handled ports: ARDOUR::MidiPort */ + boost::shared_ptr<MidiPort> _mtc_input_port; + boost::shared_ptr<MidiPort> _mtc_output_port; + boost::shared_ptr<MidiPort> _midi_clock_input_port; + boost::shared_ptr<MidiPort> _midi_clock_output_port; + + void create_ports (); + +}; + +} // namespace MIDI + +#endif // __midi_port_manager_h__ diff --git a/libs/ardour/ardour/port.h b/libs/ardour/ardour/port.h index e225117d94..77bf2b6f71 100644 --- a/libs/ardour/ardour/port.h +++ b/libs/ardour/ardour/port.h @@ -30,6 +30,7 @@ #include "pbd/signals.h" #include "ardour/data_type.h" +#include "ardour/port_engine.h" #include "ardour/types.h" namespace ARDOUR { @@ -40,11 +41,6 @@ class Buffer; class Port : public boost::noncopyable { public: - enum Flags { - IsInput = JackPortIsInput, - IsOutput = JackPortIsOutput, - }; - virtual ~Port (); static void set_connecting_blocked( bool yn ) { @@ -62,7 +58,7 @@ public: int set_name (std::string const &); /** @return flags */ - Flags flags () const { + PortFlags flags () const { return _flags; } @@ -90,24 +86,24 @@ public: virtual int connect (Port *); int disconnect (Port *); - void ensure_jack_monitors_input (bool); - bool jack_monitoring_input () const; + void request_input_monitoring (bool); + void ensure_input_monitoring (bool); + bool monitoring_input () const; int reestablish (); int reconnect (); - void request_jack_monitors_input (bool); bool last_monitor() const { return _last_monitor; } void set_last_monitor (bool yn) { _last_monitor = yn; } - jack_port_t* jack_port() const { return _jack_port; } + PortEngine::PortHandle port_handle() { return _port_handle; } - void get_connected_latency_range (jack_latency_range_t& range, bool playback) const; + void get_connected_latency_range (LatencyRange& range, bool playback) const; - void set_private_latency_range (jack_latency_range_t& range, bool playback); - const jack_latency_range_t& private_latency_range (bool playback) const; + void set_private_latency_range (LatencyRange& range, bool playback); + const LatencyRange& private_latency_range (bool playback) const; - void set_public_latency_range (jack_latency_range_t& range, bool playback) const; - jack_latency_range_t public_latency_range (bool playback) const; + void set_public_latency_range (LatencyRange& range, bool playback) const; + LatencyRange public_latency_range (bool playback) const; virtual void reset (); @@ -122,8 +118,6 @@ public: bool physically_connected () const; - static void set_engine (AudioEngine *); - PBD::Signal1<void,bool> MonitorInputChanged; static PBD::Signal2<void,boost::shared_ptr<Port>,boost::shared_ptr<Port> > PostDisconnect; static PBD::Signal0<void> PortDrop; @@ -141,11 +135,16 @@ public: virtual void increment_port_buffer_offset (pframes_t n); + virtual XMLNode& get_state (void) const; + virtual int set_state (const XMLNode&, int version); + + static std::string state_node_name; + protected: - Port (std::string const &, DataType, Flags); + Port (std::string const &, DataType, PortFlags); - jack_port_t* _jack_port; ///< JACK port + PortEngine::PortHandle _port_handle; static bool _connecting_blocked; static pframes_t _global_port_buffer_offset; /* access only from process() tree */ @@ -153,18 +152,16 @@ protected: framecnt_t _port_buffer_offset; /* access only from process() tree */ - jack_latency_range_t _private_playback_latency; - jack_latency_range_t _private_capture_latency; - - static AudioEngine* _engine; ///< the AudioEngine + LatencyRange _private_playback_latency; + LatencyRange _private_capture_latency; private: std::string _name; ///< port short name - Flags _flags; ///< flags + PortFlags _flags; ///< flags bool _last_monitor; /** ports that we are connected to, kept so that we can - reconnect to JACK when required + reconnect to the backend when required */ std::set<std::string> _connections; diff --git a/libs/ardour/ardour/port_engine.h b/libs/ardour/ardour/port_engine.h new file mode 100644 index 0000000000..71f93ea05e --- /dev/null +++ b/libs/ardour/ardour/port_engine.h @@ -0,0 +1,345 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef __libardour_port_engine_h__ +#define __libardour_port_engine_h__ + +#include <vector> +#include <string> + +#include <stdint.h> + +#include "ardour/data_type.h" +#include "ardour/types.h" + +namespace ARDOUR { + +class PortManager; + +/** PortEngine is an abstract base class that defines the functionality + * required by Ardour. + * + * A Port is basically an endpoint for a datastream (which can either be + * continuous, like audio, or event-based, like MIDI). Ports have buffers + * associated with them into which data can be written (if they are output + * ports) and from which data can be read (if they input ports). Ports can be + * connected together so that data written to an output port can be read from + * an input port. These connections can be 1:1, 1:N OR N:1. + * + * Ports may be associated with software only, or with hardware. Hardware + * related ports are often referred to as physical, and correspond to some + * relevant physical entity on a hardware device, such as an audio jack or a + * MIDI connector. Physical ports may be potentially asked to monitor their + * inputs, though some implementations may not support this. + * + * Most physical ports will also be considered "terminal", which means that + * data delivered there or read from there will go to or comes from a system + * outside of the PortEngine implementation's control (e.g. the analog domain + * for audio, or external MIDI devices for MIDI). Non-physical ports can also + * be considered "terminal". For example, the output port of a software + * synthesizer is a terminal port, because the data contained in its buffer + * does not and cannot be considered to come from any other port - it is + * synthesized by its owner. + * + * Ports also have latency associated with them. Each port has a playback + * latency and a capture latency: + * + * <b>capture latency</b>: how long since the data read from the buffer of a + * port arrived at at a terminal port. The data will have + * come from the "outside world" if the terminal port is also + * physical, or will have been synthesized by the entity that + * owns the terminal port. + * + * <b>playback latency</b>: how long until the data written to the buffer of + * port will reach a terminal port. + * + * + * For more detailed questions about the PortEngine API, consult the JACK API + * documentation, on which this entire object is based. + */ + +class PortEngine { + public: + PortEngine (PortManager& pm) : manager (pm) {} + virtual ~PortEngine() {} + + /* We use void* here so that the API can be defined for any implementation. + * + * We could theoretically use a template (PortEngine<T>) and define + * PortHandle as T, but this complicates the desired inheritance + * pattern in which FooPortEngine handles things for the Foo API, + * rather than being a derivative of PortEngine<Foo>. + */ + + typedef void* PortHandle; + + /** Return a typeless pointer to an object that may be of interest + * that understands the internals of a particular PortEngine + * implementation. + * + * XXX the existence of this method is a band-aid over some design + * issues and will it will be removed in the future + */ + virtual void* private_handle() const = 0; + + virtual bool connected() const = 0; + + /** Return the name of this process as used by the port manager + * when naming ports. + */ + virtual const std::string& my_name() const = 0; + + /** Return the maximum size of a port name + */ + virtual uint32_t port_name_size() const = 0; + + /** Returns zero if the port referred to by @param port was set to @param + * name. Return non-zero otherwise. + */ + virtual int set_port_name (PortHandle port, const std::string& name) = 0; + /** Return the name of the port referred to by @param port. If the port + * does not exist, return an empty string. + */ + virtual std::string get_port_name (PortHandle) const = 0; + /** Return a reference to a port with the fullname @param name. Return + * a null pointer if no such port exists. + */ + virtual PortHandle* get_port_by_name (const std::string&) const = 0; + + /** Find the set of ports whose names, types and flags match + * specified values, place the names of each port into @param ports, + * and return the count of the number found. + * + * To avoid selecting by name, pass an empty string for @param + * port_name_pattern. + * + * To avoid selecting by type, pass DataType::NIL as @param type. + * + * To avoid selecting by flags, pass PortFlags (0) as @param flags. + */ + virtual int get_ports (const std::string& port_name_pattern, DataType type, PortFlags flags, std::vector<std::string>& ports) const = 0; + + /** Return the Ardour data type handled by the port referred to by @param + * port. Returns DataType::NIL if the port does not exist. + */ + virtual DataType port_data_type (PortHandle port) const = 0; + + /** Create a new port whose fullname will be the conjuction of my_name(), + * ":" and @param shortname. The port will handle data specified by @param + * type and will have the flags given by @param flags. If successfull, + * return a reference to the port, otherwise return a null pointer. + */ + virtual PortHandle register_port (const std::string& shortname, ARDOUR::DataType type, ARDOUR::PortFlags flags) = 0; + + /* Destroy the port referred to by @param port, including all resources + * associated with it. This will also disconnect @param port from any ports it + * is connected to. + */ + virtual void unregister_port (PortHandle) = 0; + + /* Connection management */ + + /** Ensure that data written to the port named by @param src will be + * readable from the port named by @param dst. Return zero on success, + * non-zero otherwise. + */ + virtual int connect (const std::string& src, const std::string& dst) = 0; + + /** Remove any existing connection between the ports named by @param src and + * @param dst. Return zero on success, non-zero otherwise. + */ + virtual int disconnect (const std::string& src, const std::string& dst) = 0; + + + /** Ensure that data written to the port referenced by @param portwill be + * readable from the port named by @param dst. Return zero on success, + * non-zero otherwise. + */ + virtual int connect (PortHandle src, const std::string& dst) = 0; + /** Remove any existing connection between the port referenced by @param src and + * the port named @param dst. Return zero on success, non-zero otherwise. + */ + virtual int disconnect (PortHandle src, const std::string& dst) = 0; + + /** Remove all connections between the port referred to by @param port and + * any other ports. Return zero on success, non-zero otherwise. + */ + virtual int disconnect_all (PortHandle port) = 0; + + /** Return true if the port referred to by @param port has any connections + * to other ports. Return false otherwise. + */ + virtual bool connected (PortHandle port, bool process_callback_safe = true) = 0; + /** Return true if the port referred to by @param port is connected to + * the port named by @param name. Return false otherwise. + */ + virtual bool connected_to (PortHandle, const std::string& name, bool process_callback_safe = true) = 0; + + /** Return true if the port referred to by @param port has any connections + * to ports marked with the PortFlag IsPhysical. Return false otherwise. + */ + virtual bool physically_connected (PortHandle port, bool process_callback_safe = true) = 0; + + /** Place the names of all ports connected to the port named by @param + * ports into @param names, and return the number of connections. + */ + virtual int get_connections (PortHandle port, std::vector<std::string>& names, bool process_callback_safe = true) = 0; + + /* MIDI */ + + /** Retrieve a MIDI event from the data at @param port_buffer. The event + number to be retrieved is given by @param event_index (a value of zero + indicates that the first event in the port_buffer should be retrieved). + * + * The data associated with the event will be copied into the buffer at + * @param buf and the number of bytes written will be stored in @param + * size. The timestamp of the event (which is always relative to the start + * of the current process cycle, in samples) will be stored in @param + * timestamp + */ + virtual int midi_event_get (pframes_t& timestamp, size_t& size, uint8_t** buf, void* port_buffer, uint32_t event_index) = 0; + + /** Place a MIDI event consisting of @param size bytes copied from the data + * at @param buf into the port buffer referred to by @param + * port_buffer. The MIDI event will be marked with a time given by @param + * timestamp. Return zero on success, non-zero otherwise. + * + * Events must be added monotonically to a port buffer. An attempt to + * add a non-monotonic event (e.g. out-of-order) will cause this method + * to return a failure status. + */ + virtual int midi_event_put (void* port_buffer, pframes_t timestamp, const uint8_t* buffer, size_t size) = 0; + + /** Return the number of MIDI events in the data at @param port_buffer + */ + virtual uint32_t get_midi_event_count (void* port_buffer) = 0; + + /** Clear the buffer at @param port_buffer of all MIDI events. + * + * After a call to this method, an immediate, subsequent call to + * get_midi_event_count() with the same @param port_buffer argument must + * return zero. + */ + virtual void midi_clear (void* port_buffer) = 0; + + /* Monitoring */ + + /** Return true if the implementation can offer input monitoring. + * + * Input monitoring involves the (selective) routing of incoming data + * to an outgoing data stream, without the data being passed to the CPU. + * + * Only certain audio hardware can provide this, and only certain audio + * APIs can offer it. + */ + virtual bool can_monitor_input() const = 0; + /** Increment or decrement the number of requests to monitor the input + * of the hardware channel represented by the port referred to by @param + * port. + * + * If the number of requests rises above zero, input monitoring will + * be enabled (if can_monitor_input() returns true for the implementation). + * + * If the number of requests falls to zero, input monitoring will be + * disabled (if can_monitor_input() returns true for the implementation) + */ + virtual int request_input_monitoring (PortHandle port, bool yn) = 0; + /* Force input monitoring of the hardware channel represented by the port + * referred to by @param port to be on or off, depending on the true/false + * status of @param yn. The request count is ignored when using this + * method, so if this is called with yn set to false, input monitoring will + * be disabled regardless of the number of requests to enable it. + */ + virtual int ensure_input_monitoring (PortHandle port, bool yn) = 0; + /** Return true if input monitoring is enabled for the hardware channel + * represented by the port referred to by @param port. Return false + * otherwise. + */ + virtual bool monitoring_input (PortHandle port) = 0; + + /* Latency management + */ + + /** Set the latency range for the port referred to by @param port to @param + * r. The playback range will be set if @param for_playback is true, + * otherwise the capture range will be set. + */ + virtual void set_latency_range (PortHandle port, bool for_playback, LatencyRange r) = 0; + /** Return the latency range for the port referred to by @param port. + * The playback range will be returned if @param for_playback is true, + * otherwise the capture range will be returned. + */ + virtual LatencyRange get_latency_range (PortHandle port, bool for_playback) = 0; + + /* Discovering physical ports */ + + /** Return true if the port referred to by @param port has the IsPhysical + * flag set. Return false otherwise. + */ + virtual bool port_is_physical (PortHandle port) const = 0; + + /** Store into @param names the names of all ports with the IsOutput and + * IsPhysical flag set, that handle data of type @param type. + * + * This can be used to discover outputs associated with hardware devices. + */ + virtual void get_physical_outputs (DataType type, std::vector<std::string>& names) = 0; + /** Store into @param names the names of all ports with the IsInput and + * IsPhysical flags set, that handle data of type @param type. + * + * This can be used to discover inputs associated with hardware devices. + */ + virtual void get_physical_inputs (DataType type, std::vector<std::string>& names) = 0; + /** Return the total count (possibly mixed between different data types) + of the number of ports with the IsPhysical and IsOutput flags set. + */ + virtual ChanCount n_physical_outputs () const = 0; + /** Return the total count (possibly mixed between different data types) + of the number of ports with the IsPhysical and IsInput flags set. + */ + virtual ChanCount n_physical_inputs () const = 0; + + /** Return the address of the memory area where data for the port can be + * written (if the port has the PortFlag IsOutput set) or read (if the port + * has the PortFlag IsInput set). + * + * The return value is untyped because buffers containing different data + * depending on the port type. + */ + virtual void* get_buffer (PortHandle, pframes_t) = 0; + + /* MIDI ports (the ones in libmidi++) need this to be able to correctly + * schedule MIDI events within their buffers. It is a bit odd that we + * expose this here, because it is also exposed by AudioBackend, but they + * only have access to a PortEngine object, not an AudioBackend. + * + * Return the time according to the sample clock in use when the current + * buffer process cycle began. + * + * XXX to be removed after some more design cleanup. + */ + virtual pframes_t sample_time_at_cycle_start () = 0; + + protected: + PortManager& manager; +}; + +} + +#endif /* __libardour_port_engine_h__ */ diff --git a/libs/ardour/ardour/port_manager.h b/libs/ardour/ardour/port_manager.h new file mode 100644 index 0000000000..6d45597a41 --- /dev/null +++ b/libs/ardour/ardour/port_manager.h @@ -0,0 +1,169 @@ +/* + Copyright (C) 2013 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. + +*/ + +#ifndef __libardour_port_manager_h__ +#define __libardour_port_manager_h__ + +#include <vector> +#include <string> +#include <exception> +#include <map> + +#include <stdint.h> + +#include <boost/shared_ptr.hpp> + +#include "pbd/rcu.h" + +#include "ardour/chan_count.h" +#include "ardour/midiport_manager.h" +#include "ardour/port.h" +#include "ardour/port_engine.h" + +namespace ARDOUR { + +class PortManager +{ + public: + typedef std::map<std::string,boost::shared_ptr<Port> > Ports; + typedef std::list<boost::shared_ptr<Port> > PortList; + + PortManager (); + virtual ~PortManager() {} + + void set_port_engine (PortEngine& pe); + PortEngine& port_engine() { return *_impl; } + + uint32_t port_name_size() const; + std::string my_name() const; + + /* Port registration */ + + boost::shared_ptr<Port> register_input_port (DataType, const std::string& portname, bool async = false); + boost::shared_ptr<Port> register_output_port (DataType, const std::string& portname, bool async = false); + int unregister_port (boost::shared_ptr<Port>); + + /* Port connectivity */ + + int connect (const std::string& source, const std::string& destination); + int disconnect (const std::string& source, const std::string& destination); + int disconnect (boost::shared_ptr<Port>); + int reestablish_ports (); + int reconnect_ports (); + + bool connected (const std::string&); + bool connected_to (const std::string&, const std::string&); + bool physically_connected (const std::string&); + int get_connections (const std::string&, std::vector<std::string>&); + + /* Naming */ + + boost::shared_ptr<Port> get_port_by_name (const std::string &); + void port_renamed (const std::string&, const std::string&); + std::string make_port_name_relative (const std::string& name) const; + std::string make_port_name_non_relative (const std::string& name) const; + bool port_is_mine (const std::string& fullname) const; + + /* other Port management */ + + bool port_is_physical (const std::string&) const; + void get_physical_outputs (DataType type, std::vector<std::string>&); + void get_physical_inputs (DataType type, std::vector<std::string>&); + ChanCount n_physical_outputs () const; + ChanCount n_physical_inputs () const; + + int get_ports (const std::string& port_name_pattern, DataType type, PortFlags flags, std::vector<std::string>&); + int get_ports (DataType, PortList&); + + void remove_all_ports (); + + /* per-Port monitoring */ + + bool can_request_input_monitoring () const; + void request_input_monitoring (const std::string&, bool) const; + void ensure_input_monitoring (const std::string&, bool) const; + + class PortRegistrationFailure : public std::exception { + public: + PortRegistrationFailure (std::string const & why = "") + : reason (why) {} + + ~PortRegistrationFailure () throw () {} + + const char *what() const throw () { return reason.c_str(); } + + private: + std::string reason; + }; + + /* the port engine will invoke these callbacks when the time is right */ + + void registration_callback (); + int graph_order_callback (); + void connect_callback (const std::string&, const std::string&, bool connection); + + bool port_remove_in_progress() const { return _port_remove_in_progress; } + + /** Emitted if the backend notifies us of a graph order event */ + PBD::Signal0<void> GraphReordered; + + /** Emitted if a Port is registered or unregistered */ + PBD::Signal0<void> PortRegisteredOrUnregistered; + + /** Emitted if a Port is connected or disconnected. + * The Port parameters are the ports being connected / disconnected, or 0 if they are not known to Ardour. + * The std::string parameters are the (long) port names. + * The bool parameter is true if ports were connected, or false for disconnected. + */ + PBD::Signal5<void, boost::weak_ptr<Port>, std::string, boost::weak_ptr<Port>, std::string, bool> PortConnectedOrDisconnected; + + protected: + boost::shared_ptr<PortEngine> _impl; + SerializedRCUManager<Ports> ports; + bool _port_remove_in_progress; + + boost::shared_ptr<Port> register_port (DataType type, const std::string& portname, bool input, bool async = false); + void port_registration_failure (const std::string& portname); + + /** List of ports to be used between ::cycle_start() and ::cycle_end() + */ + boost::shared_ptr<Ports> _cycle_ports; + + void fade_out (gain_t, gain_t, pframes_t); + void silence (pframes_t nframes); + void check_monitoring (); + /** Signal the start of an audio cycle. + * This MUST be called before any reading/writing for this cycle. + * Realtime safe. + */ + void cycle_start (pframes_t nframes); + + /** Signal the end of an audio cycle. + * This signifies that the cycle began with @ref cycle_start has ended. + * This MUST be called at the end of each cycle. + * Realtime safe. + */ + void cycle_end (pframes_t nframes); +}; + + + +} // namespace + +#endif /* __libardour_port_manager_h__ */ diff --git a/libs/ardour/ardour/public_diskstream.h b/libs/ardour/ardour/public_diskstream.h index da481a6dee..125e1a21ce 100644 --- a/libs/ardour/ardour/public_diskstream.h +++ b/libs/ardour/ardour/public_diskstream.h @@ -33,8 +33,8 @@ public: virtual ~PublicDiskstream() {} virtual boost::shared_ptr<Playlist> playlist () = 0; - virtual void request_jack_monitors_input (bool) = 0; - virtual void ensure_jack_monitors_input (bool) = 0; + virtual void request_input_monitoring (bool) = 0; + virtual void ensure_input_monitoring (bool) = 0; virtual bool destructive () const = 0; virtual std::list<boost::shared_ptr<Source> > & last_capture_sources () = 0; virtual void set_capture_offset () = 0; diff --git a/libs/ardour/ardour/rc_configuration.h b/libs/ardour/ardour/rc_configuration.h index ff1c5f035c..e2f68477a1 100644 --- a/libs/ardour/ardour/rc_configuration.h +++ b/libs/ardour/ardour/rc_configuration.h @@ -52,7 +52,6 @@ class RCConfiguration : public Configuration XMLNode * instant_xml (const std::string& str); XMLNode* control_protocol_state () { return _control_protocol_state; } - std::list<XMLNode*> midi_port_states () { return _midi_port_states; } /* define accessor methods */ @@ -81,11 +80,6 @@ class RCConfiguration : public Configuration #undef CONFIG_VARIABLE_SPECIAL XMLNode* _control_protocol_state; - - /** MIDI port nodes from the RC configuration. We store them so that we can set their - state once the audio engine and hence ports are up. - */ - std::list<XMLNode*> _midi_port_states; }; /* XXX: rename this */ diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index 525dfbd849..43660d41cc 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -110,6 +110,8 @@ class IOProcessor; class ImportStatus; class MidiClockTicker; class MidiControlUI; +class MidiPortManager; +class MidiPort; class MidiRegion; class MidiSource; class MidiTrack; @@ -381,9 +383,6 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi framecnt_t worst_track_latency () const { return _worst_track_latency; } framecnt_t worst_playback_latency () const { return _worst_output_latency + _worst_track_latency; } -#ifdef HAVE_JACK_SESSION - void jack_session_event (jack_session_event_t* event); -#endif int save_state (std::string snapshot_name, bool pending = false, bool switch_to_snapshot = false); int restore_state (std::string snapshot_name); int save_template (std::string template_name); @@ -556,6 +555,11 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi int remove_last_capture (); + /** handlers should return 0 for "everything OK", and any other value for + * "cannot setup audioengine". + */ + static PBD::Signal1<int,uint32_t> AudioEngineSetupRequired; + /** handlers should return -1 for "stop cleanup", 0 for "yes, delete this playlist", 1 for "no, don't delete this playlist". @@ -813,8 +817,8 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi boost::shared_ptr<SessionPlaylists> playlists; void send_mmc_locate (framepos_t); - int send_full_time_code (framepos_t); - void send_song_position_pointer (framepos_t); + void queue_full_time_code () { _send_timecode_update = true; } + void queue_song_position_pointer () { /* currently does nothing */ } bool step_editing() const { return (_step_editors > 0); } @@ -863,6 +867,27 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi boost::shared_ptr<IO> ltc_input_io() { return _ltc_input; } boost::shared_ptr<IO> ltc_output_io() { return _ltc_output; } + MIDI::Port* midi_input_port () const; + MIDI::Port* midi_output_port () const; + MIDI::Port* mmc_output_port () const; + MIDI::Port* mmc_input_port () const; + + boost::shared_ptr<MidiPort> midi_clock_output_port () const; + boost::shared_ptr<MidiPort> midi_clock_input_port () const; + boost::shared_ptr<MidiPort> mtc_output_port () const; + boost::shared_ptr<MidiPort> mtc_input_port () const; + + MIDI::MachineControl& mmc() { return *_mmc; } + + /* Callbacks specifically related to JACK, and called directly + * from the JACK audio backend. + */ + +#ifdef HAVE_JACK_SESSION + void jack_session_event (jack_session_event_t* event); +#endif + void jack_timebase_callback (jack_transport_state_t, pframes_t, jack_position_t*, int); + protected: friend class AudioEngine; void set_block_size (pframes_t nframes); @@ -1046,7 +1071,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi boost::scoped_ptr<SessionDirectory> _session_dir; void hookup_io (); - void when_engine_running (); + int when_engine_running (); void graph_reordered (); /** current snapshot name, without the .ardour suffix */ @@ -1112,8 +1137,8 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi void auto_loop_changed (Location *); void auto_loop_declick_range (Location *, framepos_t &, framepos_t &); - void first_stage_init (std::string path, std::string snapshot_name); - int second_stage_init (); + void pre_engine_init (std::string path); + int post_engine_init (); void remove_empty_sounds (); void setup_midi_control (); @@ -1216,7 +1241,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi framepos_t ltc_timecode_offset; bool ltc_timecode_negative_offset; - jack_latency_range_t ltc_out_latency; + LatencyRange ltc_out_latency; void ltc_tx_initialize(); void ltc_tx_cleanup(); @@ -1261,6 +1286,13 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi void engine_halted (); void xrun_recovery (); + /* These are synchronous and so can only be called from within the process + * cycle + */ + + int send_full_time_code (framepos_t, pframes_t nframes); + void send_song_position_pointer (framepos_t); + TempoMap *_tempo_map; void tempo_map_changed (const PBD::PropertyChange&); @@ -1429,9 +1461,8 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi */ std::list<GQuark> _current_trans_quarks; - void jack_timebase_callback (jack_transport_state_t, pframes_t, jack_position_t*, int); - int jack_sync_callback (jack_transport_state_t, jack_position_t*); - void reset_jack_connection (jack_client_t* jack); + int backend_sync_callback (TransportState, framepos_t); + void process_rtop (SessionEvent*); void update_latency (bool playback); @@ -1585,6 +1616,14 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi void reconnect_ltc_input (); void reconnect_ltc_output (); + + /* persistent, non-track related MIDI ports */ + MidiPortManager* _midi_ports; + MIDI::MachineControl* _mmc; + + void setup_ltc (); + void setup_click (); + void setup_bundles (); }; } // namespace ARDOUR diff --git a/libs/ardour/ardour/slave.h b/libs/ardour/ardour/slave.h index 9862824da9..c60acb1df7 100644 --- a/libs/ardour/ardour/slave.h +++ b/libs/ardour/ardour/slave.h @@ -40,14 +40,12 @@ #define PLUSMINUS(A) ( ((A)<0) ? "-" : (((A)>0) ? "+" : "\u00B1") ) #define LEADINGZERO(A) ( (A)<10 ? " " : (A)<100 ? " " : (A)<1000 ? " " : "" ) -namespace MIDI { - class Port; -} - namespace ARDOUR { class TempoMap; class Session; +class AudioEngine; +class MidiPort; /** * @class Slave @@ -252,10 +250,10 @@ class TimecodeSlave : public Slave { class MTC_Slave : public TimecodeSlave { public: - MTC_Slave (Session&, MIDI::Port&); + MTC_Slave (Session&, MidiPort&); ~MTC_Slave (); - void rebind (MIDI::Port&); + void rebind (MidiPort&); bool speed_and_position (double&, framepos_t&); bool locked() const; @@ -273,7 +271,7 @@ class MTC_Slave : public TimecodeSlave { private: Session& session; - MIDI::Port* port; + MidiPort* port; PBD::ScopedConnectionList port_connections; PBD::ScopedConnection config_connection; bool can_notify_on_unknown_rate; @@ -354,7 +352,7 @@ public: std::string approximate_current_delta() const; private: - void parse_ltc(const jack_nframes_t, const jack_default_audio_sample_t * const, const framecnt_t); + void parse_ltc(const pframes_t, const Sample* const, const framecnt_t); void process_ltc(framepos_t const); void init_engine_dll (framepos_t, int32_t); bool detect_discontinuity(LTCFrameExt *, int, bool); @@ -391,7 +389,7 @@ public: PBD::ScopedConnectionList port_connections; PBD::ScopedConnection config_connection; - jack_latency_range_t ltc_slave_latency; + LatencyRange ltc_slave_latency; /* DLL - chase LTC */ int transport_direction; @@ -404,13 +402,13 @@ public: class MIDIClock_Slave : public Slave { public: - MIDIClock_Slave (Session&, MIDI::Port&, int ppqn = 24); + MIDIClock_Slave (Session&, MidiPort&, int ppqn = 24); /// Constructor for unit tests MIDIClock_Slave (ISlaveSessionProxy* session_proxy = 0, int ppqn = 24); ~MIDIClock_Slave (); - void rebind (MIDI::Port&); + void rebind (MidiPort&); bool speed_and_position (double&, framepos_t&); bool locked() const; @@ -426,7 +424,6 @@ class MIDIClock_Slave : public Slave { protected: ISlaveSessionProxy* session; - MIDI::Port* port; PBD::ScopedConnectionList port_connections; /// pulses per quarter note for one MIDI clock frame (default 24) @@ -492,7 +489,7 @@ class MIDIClock_Slave : public Slave { class JACK_Slave : public Slave { public: - JACK_Slave (jack_client_t*); + JACK_Slave (AudioEngine&); ~JACK_Slave (); bool speed_and_position (double& speed, framepos_t& pos); @@ -502,11 +499,10 @@ class JACK_Slave : public Slave bool ok() const; framecnt_t resolution () const { return 1; } bool requires_seekahead () const { return false; } - void reset_client (jack_client_t* jack); bool is_always_synced() const { return true; } private: - jack_client_t* jack; + AudioEngine& engine; double speed; bool _starting; }; diff --git a/libs/ardour/ardour/ticker.h b/libs/ardour/ardour/ticker.h index b6e5376c12..7f0d1987fc 100644 --- a/libs/ardour/ardour/ticker.h +++ b/libs/ardour/ardour/ticker.h @@ -27,17 +27,13 @@ #include "ardour/session_handle.h" -#ifndef TICKER_H_ -#define TICKER_H_ +#ifndef __libardour_ticker_h__ +#define __libardour_ticker_h__ -namespace MIDI { - class Port; -} - -namespace ARDOUR -{ +namespace ARDOUR { class Session; +class MidiPort; class MidiClockTicker : public SessionHandlePtr, boost::noncopyable { @@ -45,7 +41,7 @@ public: MidiClockTicker (); virtual ~MidiClockTicker(); - void tick (const framepos_t& transport_frames); + void tick (const framepos_t& transport_frames, pframes_t nframes); bool has_midi_port() const { return _midi_port != 0; } @@ -58,9 +54,6 @@ public: /// slot for the signal session::TransportStateChange void transport_state_changed(); - /// slot for the signal session::PositionChanged - void position_changed (framepos_t position); - /// slot for the signal session::TransportLooped void transport_looped(); @@ -70,23 +63,25 @@ public: /// pulses per quarter note (default 24) void set_ppqn(int ppqn) { _ppqn = ppqn; } -private: - MIDI::Port* _midi_port; - int _ppqn; - double _last_tick; - - class Position; - boost::scoped_ptr<Position> _pos; - - double one_ppqn_in_frames (framepos_t transport_position); - - void send_midi_clock_event (pframes_t offset); - void send_start_event (pframes_t offset); - void send_continue_event (pframes_t offset); - void send_stop_event (pframes_t offset); - void send_position_event (uint32_t midi_clocks, pframes_t offset); + private: + boost::shared_ptr<MidiPort> _midi_port; + int _ppqn; + double _last_tick; + bool _send_pos; + bool _send_state; + + class Position; + boost::scoped_ptr<Position> _pos; + + double one_ppqn_in_frames (framepos_t transport_position); + + void send_midi_clock_event (pframes_t offset, pframes_t nframes); + void send_start_event (pframes_t offset, pframes_t nframes); + void send_continue_event (pframes_t offset, pframes_t nframes); + void send_stop_event (pframes_t offset, pframes_t nframes); + void send_position_event (uint32_t midi_clocks, pframes_t offset, pframes_t nframes); }; - } + // namespace -#endif /* TICKER_H_ */ +#endif /* __libardour_ticker_h__ */ diff --git a/libs/ardour/ardour/track.h b/libs/ardour/ardour/track.h index 7159261b51..bdf420763b 100644 --- a/libs/ardour/ardour/track.h +++ b/libs/ardour/ardour/track.h @@ -115,8 +115,8 @@ class Track : public Route, public PublicDiskstream /* PublicDiskstream interface */ boost::shared_ptr<Playlist> playlist (); - void request_jack_monitors_input (bool); - void ensure_jack_monitors_input (bool); + void request_input_monitoring (bool); + void ensure_input_monitoring (bool); bool destructive () const; std::list<boost::shared_ptr<Source> > & last_capture_sources (); void set_capture_offset (); diff --git a/libs/ardour/ardour/types.h b/libs/ardour/ardour/types.h index 2115149872..17bffc20e4 100644 --- a/libs/ardour/ardour/types.h +++ b/libs/ardour/ardour/types.h @@ -28,8 +28,6 @@ #include <stdint.h> #include <inttypes.h> -#include <jack/types.h> -#include <jack/midiport.h> #include "timecode/bbt_time.h" #include "timecode/time.h" @@ -53,12 +51,12 @@ namespace ARDOUR { class Route; class Region; - typedef jack_default_audio_sample_t Sample; - typedef float pan_t; - typedef float gain_t; - typedef uint32_t layer_t; - typedef uint64_t microseconds_t; - typedef jack_nframes_t pframes_t; + typedef float Sample; + typedef float pan_t; + typedef float gain_t; + typedef uint32_t layer_t; + typedef uint64_t microseconds_t; + typedef uint32_t pframes_t; /* Any position measured in audio frames. Assumed to be non-negative but not enforced. @@ -585,6 +583,32 @@ namespace ARDOUR { FadeSymmetric, }; + enum TransportState { + /* these values happen to match the constants used by JACK but + this equality cannot be assumed. + */ + TransportStopped = 0, + TransportRolling = 1, + TransportLooping = 2, + TransportStarting = 3, + }; + + enum PortFlags { + /* these values happen to match the constants used by JACK but + this equality cannot be assumed. + */ + IsInput = 0x1, + IsOutput = 0x2, + IsPhysical = 0x4, + CanMonitor = 0x8, + IsTerminal = 0x10 + }; + + struct LatencyRange { + uint32_t min; //< samples + uint32_t max; //< samples + }; + } // namespace ARDOUR diff --git a/libs/ardour/async_midi_port.cc b/libs/ardour/async_midi_port.cc new file mode 100644 index 0000000000..77ca62961e --- /dev/null +++ b/libs/ardour/async_midi_port.cc @@ -0,0 +1,303 @@ +/* + Copyright (C) 1998 Paul Barton-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. + + $Id$ +*/ + +#include <iostream> + +#include "pbd/error.h" +#include "pbd/stacktrace.h" + +#include "midi++/types.h" + +#include "ardour/async_midi_port.h" +#include "ardour/audioengine.h" +#include "ardour/midi_buffer.h" + +using namespace MIDI; +using namespace ARDOUR; +using namespace std; +using namespace PBD; + +namespace Evoral { + template class EventRingBuffer<MIDI::timestamp_t>; +} + +pthread_t AsyncMIDIPort::_process_thread; + +#define port_engine AudioEngine::instance()->port_engine() + +AsyncMIDIPort::AsyncMIDIPort (string const & name, PortFlags flags) + : MidiPort (name, flags) + , MIDI::Port (name, MIDI::Port::Flags (0)) + , _currently_in_cycle (false) + , _last_write_timestamp (0) + , output_fifo (512) + , input_fifo (1024) + , xthread (true) +{ +} + +AsyncMIDIPort::~AsyncMIDIPort () +{ +} + +void +AsyncMIDIPort::flush_output_fifo (pframes_t nframes) +{ + RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0 } }; + size_t written; + + output_fifo.get_read_vector (&vec); + + MidiBuffer& mb (get_midi_buffer (nframes)); + + if (vec.len[0]) { + Evoral::Event<double>* evp = vec.buf[0]; + + for (size_t n = 0; n < vec.len[0]; ++n, ++evp) { + mb.push_back (evp->time(), evp->size(), evp->buffer()); + } + } + + if (vec.len[1]) { + Evoral::Event<double>* evp = vec.buf[1]; + + for (size_t n = 0; n < vec.len[1]; ++n, ++evp) { + mb.push_back (evp->time(), evp->size(), evp->buffer()); + } + } + + if ((written = vec.len[0] + vec.len[1]) != 0) { + output_fifo.increment_read_idx (written); + } +} + +void +AsyncMIDIPort::cycle_start (pframes_t nframes) +{ + _currently_in_cycle = true; + MidiPort::cycle_start (nframes); + + /* dump anything waiting in the output FIFO at the start of the port + * buffer + */ + + if (ARDOUR::Port::sends_output()) { + flush_output_fifo (nframes); + } + + /* copy incoming data from the port buffer into the input FIFO + and if necessary wakeup the reader + */ + + if (ARDOUR::Port::receives_input()) { + MidiBuffer& mb (get_midi_buffer (nframes)); + pframes_t when = AudioEngine::instance()->sample_time_at_cycle_start(); + + for (MidiBuffer::iterator b = mb.begin(); b != mb.end(); ++b) { + input_fifo.write (when, (Evoral::EventType) 0, (*b).size(), (*b).buffer()); + } + + if (!mb.empty()) { + xthread.wakeup (); + } + } + +} + +void +AsyncMIDIPort::cycle_end (pframes_t nframes) +{ + if (ARDOUR::Port::sends_output()) { + /* move any additional data from output FIFO into the port + buffer. + */ + flush_output_fifo (nframes); + } + + MidiPort::cycle_end (nframes); + + _currently_in_cycle = false; +} + +/** wait for the output FIFO to be emptied by successive process() callbacks. + * + * Cannot be called from a processing thread. + */ +void +AsyncMIDIPort::drain (int check_interval_usecs) +{ + RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0} }; + + if (!AudioEngine::instance()->running() || AudioEngine::instance()->session() == 0) { + /* no more process calls - it will never drain */ + return; + } + + + if (is_process_thread()) { + error << "Process thread called MIDI::AsyncMIDIPort::drain() - this cannot work" << endmsg; + return; + } + + while (1) { + output_fifo.get_write_vector (&vec); + if (vec.len[0] + vec.len[1] >= output_fifo.bufsize() - 1) { + break; + } + usleep (check_interval_usecs); + } +} + +int +AsyncMIDIPort::write (const MIDI::byte * msg, size_t msglen, MIDI::timestamp_t timestamp) +{ + int ret = 0; + + if (!ARDOUR::Port::sends_output()) { + return ret; + } + + if (!is_process_thread()) { + + /* this is the best estimate of "when" this MIDI data is being + * delivered + */ + + _parser->set_timestamp (AudioEngine::instance()->sample_time() + timestamp); + for (size_t n = 0; n < msglen; ++n) { + _parser->scanner (msg[n]); + } + + Glib::Threads::Mutex::Lock lm (output_fifo_lock); + RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0} }; + + output_fifo.get_write_vector (&vec); + + if (vec.len[0] + vec.len[1] < 1) { + error << "no space in FIFO for non-process thread MIDI write" << endmsg; + return 0; + } + + if (vec.len[0]) { + if (!vec.buf[0]->owns_buffer()) { + vec.buf[0]->set_buffer (0, 0, true); + } + vec.buf[0]->set (msg, msglen, timestamp); + } else { + if (!vec.buf[1]->owns_buffer()) { + vec.buf[1]->set_buffer (0, 0, true); + } + vec.buf[1]->set (msg, msglen, timestamp); + } + + output_fifo.increment_write_idx (1); + + ret = msglen; + + } else { + + _parser->set_timestamp (AudioEngine::instance()->sample_time_at_cycle_start() + timestamp); + for (size_t n = 0; n < msglen; ++n) { + _parser->scanner (msg[n]); + } + + if (timestamp >= _cycle_nframes) { + std::cerr << "attempting to write MIDI event of " << msglen << " MIDI::bytes at time " + << timestamp << " of " << _cycle_nframes + << " (this will not work - needs a code fix)" + << std::endl; + } + + /* This is the process thread, which makes checking + * _currently_in_cycle atomic and safe, since it is only + * set from cycle_start() and cycle_end(), also called + * only from the process thread. + */ + + if (_currently_in_cycle) { + + MidiBuffer& mb (get_midi_buffer (_cycle_nframes)); + + if (timestamp == 0) { + timestamp = _last_write_timestamp; + } + + if (mb.push_back (timestamp, msglen, msg)) { + ret = msglen; + _last_write_timestamp = timestamp; + + } else { + cerr << "AsyncMIDIPort (" << ARDOUR::Port::name() << "): write of " << msglen << " @ " << timestamp << " failed\n" << endl; + PBD::stacktrace (cerr, 20); + ret = 0; + } + } else { + cerr << "write to JACK midi port failed: not currently in a process cycle." << endl; + PBD::stacktrace (cerr, 20); + } + } + + return ret; +} + + +int +AsyncMIDIPort::read (MIDI::byte *, size_t) +{ + if (!ARDOUR::Port::receives_input()) { + return 0; + } + + timestamp_t time; + Evoral::EventType type; + uint32_t size; + MIDI::byte buffer[input_fifo.capacity()]; + + while (input_fifo.read (&time, &type, &size, buffer)) { + _parser->set_timestamp (time); + for (uint32_t i = 0; i < size; ++i) { + _parser->scanner (buffer[i]); + } + } + + return 0; +} + +void +AsyncMIDIPort::parse (framecnt_t) +{ + MIDI::byte buf[1]; + + /* see ::read() to realize why buf is not used */ + read (buf, sizeof (buf)); +} + +void +AsyncMIDIPort::set_process_thread (pthread_t thr) +{ + _process_thread = thr; +} + +bool +AsyncMIDIPort::is_process_thread() +{ + return pthread_equal (pthread_self(), _process_thread); +} + diff --git a/libs/ardour/audio_diskstream.cc b/libs/ardour/audio_diskstream.cc index 622a42edf2..f4d10c2043 100644 --- a/libs/ardour/audio_diskstream.cc +++ b/libs/ardour/audio_diskstream.cc @@ -1752,7 +1752,7 @@ AudioDiskstream::prep_record_enable () if (Config->get_monitoring_model() == HardwareMonitoring) { for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - (*chan)->source.request_jack_monitors_input (!(_session.config.get_auto_input() && rolling)); + (*chan)->source.request_input_monitoring (!(_session.config.get_auto_input() && rolling)); capturing_sources.push_back ((*chan)->write_source); (*chan)->write_source->mark_streaming_write_started (); } @@ -1773,7 +1773,7 @@ AudioDiskstream::prep_record_disable () boost::shared_ptr<ChannelList> c = channels.reader(); if (Config->get_monitoring_model() == HardwareMonitoring) { for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - (*chan)->source.request_jack_monitors_input (false); + (*chan)->source.request_input_monitoring (false); } } capturing_sources.clear (); @@ -2039,12 +2039,12 @@ AudioDiskstream::allocate_temporary_buffers () } void -AudioDiskstream::request_jack_monitors_input (bool yn) +AudioDiskstream::request_input_monitoring (bool yn) { boost::shared_ptr<ChannelList> c = channels.reader(); for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - (*chan)->source.request_jack_monitors_input (yn); + (*chan)->source.request_input_monitoring (yn); } } @@ -2367,13 +2367,13 @@ AudioDiskstream::ChannelSource::is_physical () const } void -AudioDiskstream::ChannelSource::request_jack_monitors_input (bool yn) const +AudioDiskstream::ChannelSource::request_input_monitoring (bool yn) const { if (name.empty()) { return; } - return AudioEngine::instance()->request_jack_monitors_input (name, yn); + return AudioEngine::instance()->request_input_monitoring (name, yn); } AudioDiskstream::ChannelInfo::ChannelInfo (framecnt_t playback_bufsize, framecnt_t capture_bufsize, framecnt_t speed_size, framecnt_t wrap_size) diff --git a/libs/ardour/audio_port.cc b/libs/ardour/audio_port.cc index 240224ea5e..6a86360b69 100644 --- a/libs/ardour/audio_port.cc +++ b/libs/ardour/audio_port.cc @@ -21,13 +21,17 @@ #include "pbd/stacktrace.h" #include "ardour/audio_buffer.h" +#include "ardour/audioengine.h" #include "ardour/audio_port.h" #include "ardour/data_type.h" +#include "ardour/port_engine.h" using namespace ARDOUR; using namespace std; -AudioPort::AudioPort (const std::string& name, Flags flags) +#define port_engine AudioEngine::instance()->port_engine() + +AudioPort::AudioPort (const std::string& name, PortFlags flags) : Port (name, DataType::AUDIO, flags) , _buffer (new AudioBuffer (0)) { @@ -73,7 +77,7 @@ AudioBuffer& AudioPort::get_audio_buffer (pframes_t nframes) { /* caller must hold process lock */ - _buffer->set_data ((Sample *) jack_port_get_buffer (_jack_port, _cycle_nframes) + + _buffer->set_data ((Sample *) port_engine.get_buffer (_port_handle, _cycle_nframes) + _global_port_buffer_offset + _port_buffer_offset, nframes); return *_buffer; } @@ -82,7 +86,7 @@ Sample* AudioPort::engine_get_whole_audio_buffer () { /* caller must hold process lock */ - return (Sample *) jack_port_get_buffer (_jack_port, _cycle_nframes); + return (Sample *) port_engine.get_buffer (_port_handle, _cycle_nframes); } diff --git a/libs/ardour/audio_track.cc b/libs/ardour/audio_track.cc index 0530dbfce9..a82c109cba 100644 --- a/libs/ardour/audio_track.cc +++ b/libs/ardour/audio_track.cc @@ -96,7 +96,7 @@ AudioTrack::set_diskstream (boost::shared_ptr<Diskstream> ds) } _diskstream->set_record_enabled (false); - _diskstream->request_jack_monitors_input (false); + _diskstream->request_input_monitoring (false); DiskstreamChanged (); /* EMIT SIGNAL */ } diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc index 5e3e5ba9e2..0ff8cd89ef 100644 --- a/libs/ardour/audioengine.cc +++ b/libs/ardour/audioengine.cc @@ -25,24 +25,30 @@ #include <sstream> #include <glibmm/timer.h> +#include <glibmm/pattern.h> +#include <glibmm/module.h> +#include "pbd/epa.h" +#include "pbd/file_utils.h" #include "pbd/pthread_utils.h" #include "pbd/stacktrace.h" #include "pbd/unknown_type.h" -#include "pbd/epa.h" #include "midi++/port.h" -#include "midi++/jack_midi_port.h" #include "midi++/mmc.h" -#include "midi++/manager.h" +#include "ardour/async_midi_port.h" #include "ardour/audio_port.h" +#include "ardour/audio_backend.h" #include "ardour/audioengine.h" +#include "ardour/backend_search_path.h" #include "ardour/buffer.h" #include "ardour/cycle_timer.h" #include "ardour/internal_send.h" #include "ardour/meter.h" #include "ardour/midi_port.h" +#include "ardour/midiport_manager.h" +#include "ardour/mtdm.h" #include "ardour/port.h" #include "ardour/process_thread.h" #include "ardour/session.h" @@ -56,60 +62,50 @@ using namespace PBD; gint AudioEngine::m_meter_exit; AudioEngine* AudioEngine::_instance = 0; -#define GET_PRIVATE_JACK_POINTER(j) jack_client_t* _priv_jack = (jack_client_t*) (j); if (!_priv_jack) { return; } -#define GET_PRIVATE_JACK_POINTER_RET(j,r) jack_client_t* _priv_jack = (jack_client_t*) (j); if (!_priv_jack) { return r; } - -AudioEngine::AudioEngine (string client_name, string session_uuid) - : _jack (0) - , session_remove_pending (false) +AudioEngine::AudioEngine () + : session_remove_pending (false) , session_removal_countdown (-1) , _running (false) - , _has_run (false) - , _buffer_size (0) - , _frame_rate (0) + , _freewheeling (false) , monitor_check_interval (INT32_MAX) , last_monitor_check (0) , _processed_frames (0) - , _freewheeling (false) - , _pre_freewheel_mmc_enabled (false) - , _usecs_per_cycle (0) - , port_remove_in_progress (false) , m_meter_thread (0) , _main_thread (0) - , ports (new Ports) + , _mtdm (0) + , _measuring_latency (false) + , _latency_input_port (0) + , _latency_output_port (0) + , _latency_flush_frames (0) + , _latency_signal_latency (0) { - _instance = this; /* singleton */ - g_atomic_int_set (&m_meter_exit, 0); - - if (connect_to_jack (client_name, session_uuid)) { - throw NoBackendAvailable (); - } - - Port::set_engine (this); + discover_backends (); } AudioEngine::~AudioEngine () { + drop_backend (); + config_connection.disconnect (); { Glib::Threads::Mutex::Lock tm (_process_lock); session_removed.signal (); - - if (_running) { - jack_client_close (_jack); - _jack = 0; - } - stop_metering_thread (); } } -jack_client_t* -AudioEngine::jack() const +AudioEngine* +AudioEngine::create () { - return _jack; + if (_instance) { + return _instance; + } + + _instance = new AudioEngine (); + + return _instance; } void @@ -126,290 +122,7 @@ _thread_init_callback (void * /*arg*/) SessionEvent::create_per_thread_pool (X_("Audioengine"), 512); - MIDI::JackMIDIPort::set_process_thread (pthread_self()); -} - -static void -ardour_jack_error (const char* msg) -{ - error << "JACK: " << msg << endmsg; -} - -void -AudioEngine::set_jack_callbacks () -{ - GET_PRIVATE_JACK_POINTER (_jack); - - if (jack_on_info_shutdown) { - jack_on_info_shutdown (_priv_jack, halted_info, this); - } else { - jack_on_shutdown (_priv_jack, halted, this); - } - - jack_set_thread_init_callback (_priv_jack, _thread_init_callback, this); - jack_set_process_thread (_priv_jack, _process_thread, this); - jack_set_sample_rate_callback (_priv_jack, _sample_rate_callback, this); - jack_set_buffer_size_callback (_priv_jack, _bufsize_callback, this); - jack_set_graph_order_callback (_priv_jack, _graph_order_callback, this); - jack_set_port_registration_callback (_priv_jack, _registration_callback, this); - jack_set_port_connect_callback (_priv_jack, _connect_callback, this); - jack_set_xrun_callback (_priv_jack, _xrun_callback, this); - jack_set_sync_callback (_priv_jack, _jack_sync_callback, this); - jack_set_freewheel_callback (_priv_jack, _freewheel_callback, this); - - if (_session && _session->config.get_jack_time_master()) { - jack_set_timebase_callback (_priv_jack, 0, _jack_timebase_callback, this); - } - -#ifdef HAVE_JACK_SESSION - if( jack_set_session_callback) - jack_set_session_callback (_priv_jack, _session_callback, this); -#endif - - if (jack_set_latency_callback) { - jack_set_latency_callback (_priv_jack, _latency_callback, this); - } - - jack_set_error_function (ardour_jack_error); -} - -int -AudioEngine::start () -{ - GET_PRIVATE_JACK_POINTER_RET (_jack, -1); - - if (!_running) { - - if (!jack_port_type_get_buffer_size) { - warning << _("This version of JACK is old - you should upgrade to a newer version that supports jack_port_type_get_buffer_size()") << endmsg; - } - - if (_session) { - BootMessage (_("Connect session to engine")); - _session->set_frame_rate (jack_get_sample_rate (_priv_jack)); - } - - /* a proxy for whether jack_activate() will definitely call the buffer size - * callback. with older versions of JACK, this function symbol will be null. - * this is reliable, but not clean. - */ - - if (!jack_port_type_get_buffer_size) { - jack_bufsize_callback (jack_get_buffer_size (_priv_jack)); - } - - _processed_frames = 0; - last_monitor_check = 0; - - set_jack_callbacks (); - - if (jack_activate (_priv_jack) == 0) { - _running = true; - _has_run = true; - Running(); /* EMIT SIGNAL */ - } else { - // error << _("cannot activate JACK client") << endmsg; - } - } - - return _running ? 0 : -1; -} - -int -AudioEngine::stop (bool forever) -{ - GET_PRIVATE_JACK_POINTER_RET (_jack, -1); - - if (_priv_jack) { - if (forever) { - disconnect_from_jack (); - } else { - jack_deactivate (_priv_jack); - MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */ - Stopped(); /* EMIT SIGNAL */ - } - } - - if (forever) { - stop_metering_thread (); - } - - return _running ? -1 : 0; -} - - -bool -AudioEngine::get_sync_offset (pframes_t& offset) const -{ - -#ifdef HAVE_JACK_VIDEO_SUPPORT - - GET_PRIVATE_JACK_POINTER_RET (_jack, false); - - jack_position_t pos; - - if (_priv_jack) { - (void) jack_transport_query (_priv_jack, &pos); - - if (pos.valid & JackVideoFrameOffset) { - offset = pos.video_offset; - return true; - } - } -#else - /* keep gcc happy */ - offset = 0; -#endif - - return false; -} - -void -AudioEngine::_jack_timebase_callback (jack_transport_state_t state, pframes_t nframes, - jack_position_t* pos, int new_position, void *arg) -{ - static_cast<AudioEngine*> (arg)->jack_timebase_callback (state, nframes, pos, new_position); -} - -void -AudioEngine::jack_timebase_callback (jack_transport_state_t state, pframes_t nframes, - jack_position_t* pos, int new_position) -{ - if (_jack && _session && _session->synced_to_jack()) { - _session->jack_timebase_callback (state, nframes, pos, new_position); - } -} - -int -AudioEngine::_jack_sync_callback (jack_transport_state_t state, jack_position_t* pos, void* arg) -{ - return static_cast<AudioEngine*> (arg)->jack_sync_callback (state, pos); -} - -int -AudioEngine::jack_sync_callback (jack_transport_state_t state, jack_position_t* pos) -{ - if (_jack && _session) { - return _session->jack_sync_callback (state, pos); - } - - return true; -} - -int -AudioEngine::_xrun_callback (void *arg) -{ - AudioEngine* ae = static_cast<AudioEngine*> (arg); - if (ae->connected()) { - ae->Xrun (); /* EMIT SIGNAL */ - } - return 0; -} - -#ifdef HAVE_JACK_SESSION -void -AudioEngine::_session_callback (jack_session_event_t *event, void *arg) -{ - AudioEngine* ae = static_cast<AudioEngine*> (arg); - if (ae->connected()) { - ae->JackSessionEvent ( event ); /* EMIT SIGNAL */ - } -} -#endif - -int -AudioEngine::_graph_order_callback (void *arg) -{ - AudioEngine* ae = static_cast<AudioEngine*> (arg); - - if (ae->connected() && !ae->port_remove_in_progress) { - ae->GraphReordered (); /* EMIT SIGNAL */ - } - - return 0; -} - -void* -AudioEngine::_process_thread (void *arg) -{ - return static_cast<AudioEngine *> (arg)->process_thread (); -} - -void -AudioEngine::_freewheel_callback (int onoff, void *arg) -{ - static_cast<AudioEngine*>(arg)->freewheel_callback (onoff); -} - -void -AudioEngine::freewheel_callback (int onoff) -{ - _freewheeling = onoff; - - if (onoff) { - _pre_freewheel_mmc_enabled = MIDI::Manager::instance()->mmc()->send_enabled (); - MIDI::Manager::instance()->mmc()->enable_send (false); - } else { - MIDI::Manager::instance()->mmc()->enable_send (_pre_freewheel_mmc_enabled); - } -} - -void -AudioEngine::_registration_callback (jack_port_id_t /*id*/, int /*reg*/, void* arg) -{ - AudioEngine* ae = static_cast<AudioEngine*> (arg); - - if (!ae->port_remove_in_progress) { - ae->PortRegisteredOrUnregistered (); /* EMIT SIGNAL */ - } -} - -void -AudioEngine::_latency_callback (jack_latency_callback_mode_t mode, void* arg) -{ - return static_cast<AudioEngine *> (arg)->jack_latency_callback (mode); -} - -void -AudioEngine::_connect_callback (jack_port_id_t id_a, jack_port_id_t id_b, int conn, void* arg) -{ - AudioEngine* ae = static_cast<AudioEngine*> (arg); - ae->connect_callback (id_a, id_b, conn); -} - -void -AudioEngine::connect_callback (jack_port_id_t id_a, jack_port_id_t id_b, int conn) -{ - if (port_remove_in_progress) { - return; - } - - GET_PRIVATE_JACK_POINTER (_jack); - - jack_port_t* jack_port_a = jack_port_by_id (_priv_jack, id_a); - jack_port_t* jack_port_b = jack_port_by_id (_priv_jack, id_b); - - boost::shared_ptr<Port> port_a; - boost::shared_ptr<Port> port_b; - Ports::iterator x; - boost::shared_ptr<Ports> pr = ports.reader (); - - - x = pr->find (make_port_name_relative (jack_port_name (jack_port_a))); - if (x != pr->end()) { - port_a = x->second; - } - - x = pr->find (make_port_name_relative (jack_port_name (jack_port_b))); - if (x != pr->end()) { - port_b = x->second; - } - - PortConnectedOrDisconnected ( - port_a, jack_port_name (jack_port_a), - port_b, jack_port_name (jack_port_b), - conn == 0 ? false : true - ); /* EMIT SIGNAL */ + AsyncMIDIPort::set_process_thread (pthread_self()); } void @@ -428,29 +141,32 @@ AudioEngine::split_cycle (pframes_t offset) } } -void* -AudioEngine::process_thread () +int +AudioEngine::sample_rate_change (pframes_t nframes) { - /* JACK doesn't do this for us when we use the wait API - */ - - _thread_init_callback (0); + /* check for monitor input change every 1/10th of second */ - _main_thread = new ProcessThread; + monitor_check_interval = nframes / 10; + last_monitor_check = 0; - while (1) { - GET_PRIVATE_JACK_POINTER_RET(_jack,0); + if (_session) { + _session->set_frame_rate (nframes); + } - pframes_t nframes = jack_cycle_wait (_priv_jack); + SampleRateChanged (nframes); /* EMIT SIGNAL */ - if (process_callback (nframes)) { - return 0; - } + return 0; +} - jack_cycle_signal (_priv_jack, 0); - } +int +AudioEngine::buffer_size_change (pframes_t bufsiz) +{ + if (_session) { + _session->set_block_size (bufsiz); + last_monitor_check = 0; + } - return 0; + return 0; } /** Method called by our ::process_thread when there is work to be done. @@ -459,7 +175,6 @@ AudioEngine::process_thread () int AudioEngine::process_callback (pframes_t nframes) { - GET_PRIVATE_JACK_POINTER_RET(_jack,0); Glib::Threads::Mutex::Lock tm (_process_lock, Glib::Threads::TRY_LOCK); PT_TIMING_REF; @@ -482,6 +197,43 @@ AudioEngine::process_callback (pframes_t nframes) return 0; } + bool return_after_remove_check = false; + + if (_measuring_latency && _mtdm) { + PortManager::cycle_start (nframes); + PortManager::silence (nframes); + + if (_latency_input_port && _latency_output_port) { + PortEngine& pe (port_engine()); + + Sample* in = (Sample*) pe.get_buffer (_latency_input_port, nframes); + Sample* out = (Sample*) pe.get_buffer (_latency_output_port, nframes); + + _mtdm->process (nframes, in, out); + } + + PortManager::cycle_end (nframes); + return_after_remove_check = true; + + } else if (_latency_flush_frames) { + + /* wait for the appropriate duration for the MTDM signal to + * drain from the ports before we revert to normal behaviour. + */ + + PortManager::cycle_start (nframes); + PortManager::silence (nframes); + PortManager::cycle_end (nframes); + + if (_latency_flush_frames > nframes) { + _latency_flush_frames -= nframes; + } else { + _latency_flush_frames = 0; + } + + return_after_remove_check = true; + } + if (session_remove_pending) { /* perform the actual session removal */ @@ -489,7 +241,7 @@ AudioEngine::process_callback (pframes_t nframes) if (session_removal_countdown < 0) { /* fade out over 1 second */ - session_removal_countdown = _frame_rate/2; + session_removal_countdown = sample_rate()/2; session_removal_gain = 1.0; session_removal_gain_step = 1.0/session_removal_countdown; @@ -518,11 +270,15 @@ AudioEngine::process_callback (pframes_t nframes) } } + if (return_after_remove_check) { + return 0; + } + if (_session == 0) { if (!_freewheeling) { - MIDI::Manager::instance()->cycle_start(nframes); - MIDI::Manager::instance()->cycle_end(); + PortManager::cycle_start (nframes); + PortManager::cycle_end (nframes); } _processed_frames = next_processed_frames; @@ -533,34 +289,22 @@ AudioEngine::process_callback (pframes_t nframes) /* tell all relevant objects that we're starting a new cycle */ InternalSend::CycleStart (nframes); - Port::set_global_port_buffer_offset (0); - Port::set_cycle_framecnt (nframes); /* tell all Ports that we're starting a new cycle */ - boost::shared_ptr<Ports> p = ports.reader(); - - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - i->second->cycle_start (nframes); - } + PortManager::cycle_start (nframes); /* test if we are freewheeling and there are freewheel signals connected. ardour should act normally even when freewheeling unless /it/ is - exporting + exporting (which is what Freewheel.empty() tests for). */ if (_freewheeling && !Freewheel.empty()) { - Freewheel (nframes); - } else { - MIDI::Manager::instance()->cycle_start(nframes); - if (_session) { _session->process (nframes); } - - MIDI::Manager::instance()->cycle_end(); } if (_freewheeling) { @@ -573,52 +317,18 @@ AudioEngine::process_callback (pframes_t nframes) } if (last_monitor_check + monitor_check_interval < next_processed_frames) { - - boost::shared_ptr<Ports> p = ports.reader(); - - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - - bool x; - - if (i->second->last_monitor() != (x = i->second->jack_monitoring_input ())) { - i->second->set_last_monitor (x); - /* XXX I think this is dangerous, due to - a likely mutex in the signal handlers ... - */ - i->second->MonitorInputChanged (x); /* EMIT SIGNAL */ - } - } + + PortManager::check_monitoring (); last_monitor_check = next_processed_frames; } if (_session->silent()) { - - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - - if (i->second->sends_output()) { - i->second->get_buffer(nframes).silence(nframes); - } - } + PortManager::silence (nframes); } if (session_remove_pending && session_removal_countdown) { - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - - if (i->second->sends_output()) { - - boost::shared_ptr<AudioPort> ap = boost::dynamic_pointer_cast<AudioPort> (i->second); - if (ap) { - Sample* s = ap->engine_get_whole_audio_buffer (); - gain_t g = session_removal_gain; - - for (pframes_t n = 0; n < nframes; ++n) { - *s++ *= g; - g -= session_removal_gain_step; - } - } - } - } + PortManager::fade_out (session_removal_gain, session_removal_gain_step, nframes); if (session_removal_countdown > nframes) { session_removal_countdown -= nframes; @@ -629,11 +339,7 @@ AudioEngine::process_callback (pframes_t nframes) session_removal_gain -= (nframes * session_removal_gain_step); } - // Finalize ports - - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - i->second->cycle_end (nframes); - } + PortManager::cycle_end (nframes); _processed_frames = next_processed_frames; @@ -642,97 +348,6 @@ AudioEngine::process_callback (pframes_t nframes) return 0; } -int -AudioEngine::_sample_rate_callback (pframes_t nframes, void *arg) -{ - return static_cast<AudioEngine *> (arg)->jack_sample_rate_callback (nframes); -} - -int -AudioEngine::jack_sample_rate_callback (pframes_t nframes) -{ - _frame_rate = nframes; - _usecs_per_cycle = (int) floor ((((double) frames_per_cycle() / nframes)) * 1000000.0); - - /* check for monitor input change every 1/10th of second */ - - monitor_check_interval = nframes / 10; - last_monitor_check = 0; - - if (_session) { - _session->set_frame_rate (nframes); - } - - SampleRateChanged (nframes); /* EMIT SIGNAL */ - - return 0; -} - -void -AudioEngine::jack_latency_callback (jack_latency_callback_mode_t mode) -{ - if (_session) { - _session->update_latency (mode == JackPlaybackLatency); - } -} - -int -AudioEngine::_bufsize_callback (pframes_t nframes, void *arg) -{ - return static_cast<AudioEngine *> (arg)->jack_bufsize_callback (nframes); -} - -int -AudioEngine::jack_bufsize_callback (pframes_t nframes) -{ - /* if the size has not changed, this should be a no-op */ - - if (nframes == _buffer_size) { - return 0; - } - - GET_PRIVATE_JACK_POINTER_RET (_jack, 1); - - _buffer_size = nframes; - _usecs_per_cycle = (int) floor ((((double) nframes / frame_rate())) * 1000000.0); - last_monitor_check = 0; - - if (jack_port_type_get_buffer_size) { - _raw_buffer_sizes[DataType::AUDIO] = jack_port_type_get_buffer_size (_priv_jack, JACK_DEFAULT_AUDIO_TYPE); - _raw_buffer_sizes[DataType::MIDI] = jack_port_type_get_buffer_size (_priv_jack, JACK_DEFAULT_MIDI_TYPE); - } else { - - /* Old version of JACK. - - These crude guesses, see below where we try to get the right answers. - - Note that our guess for MIDI deliberatey tries to overestimate - by a little. It would be nicer if we could get the actual - size from a port, but we have to use this estimate in the - event that there are no MIDI ports currently. If there are - the value will be adjusted below. - */ - - _raw_buffer_sizes[DataType::AUDIO] = nframes * sizeof (Sample); - _raw_buffer_sizes[DataType::MIDI] = nframes * 4 - (nframes/2); - } - - { - Glib::Threads::Mutex::Lock lm (_process_lock); - - boost::shared_ptr<Ports> p = ports.reader(); - - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - i->second->reset(); - } - } - - if (_session) { - _session->set_block_size (_buffer_size); - } - - return 0; -} void AudioEngine::stop_metering_thread () @@ -757,8 +372,9 @@ void AudioEngine::meter_thread () { pthread_set_name (X_("meter")); + while (true) { - Glib::usleep (10000); + Glib::usleep (10000); /* 1/100th sec interval */ if (g_atomic_int_get(&m_meter_exit)) { break; } @@ -775,19 +391,9 @@ AudioEngine::set_session (Session *s) if (_session) { - start_metering_thread (); - - pframes_t blocksize = jack_get_buffer_size (_jack); + pframes_t blocksize = samples_per_cycle (); - /* page in as much of the session process code as we - can before we really start running. - */ - - boost::shared_ptr<Ports> p = ports.reader(); - - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - i->second->cycle_start (blocksize); - } + PortManager::cycle_start (blocksize); _session->process (blocksize); _session->process (blocksize); @@ -798,9 +404,7 @@ AudioEngine::set_session (Session *s) _session->process (blocksize); _session->process (blocksize); - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - i->second->cycle_end (blocksize); - } + PortManager::cycle_end (blocksize); } } @@ -815,6 +419,7 @@ AudioEngine::remove_session () if (_session) { session_remove_pending = true; + session_removal_countdown = 0; session_removed.wait(_process_lock); } @@ -825,813 +430,622 @@ AudioEngine::remove_session () remove_all_ports (); } + void -AudioEngine::port_registration_failure (const std::string& portname) +AudioEngine::died () { - GET_PRIVATE_JACK_POINTER (_jack); - string full_portname = jack_client_name; - full_portname += ':'; - full_portname += portname; - - - jack_port_t* p = jack_port_by_name (_priv_jack, full_portname.c_str()); - string reason; + /* called from a signal handler for SIGPIPE */ - if (p) { - reason = string_compose (_("a port with the name \"%1\" already exists: check for duplicated track/bus names"), portname); - } else { - reason = string_compose (_("No more JACK ports are available. You will need to stop %1 and restart JACK with more ports if you need this many tracks."), PROGRAM_NAME); - } + stop_metering_thread (); - throw PortRegistrationFailure (string_compose (_("AudioEngine: cannot register port \"%1\": %2"), portname, reason).c_str()); + _running = false; } -boost::shared_ptr<Port> -AudioEngine::register_port (DataType dtype, const string& portname, bool input) +int +AudioEngine::reset_timebase () { - boost::shared_ptr<Port> newport; - - try { - if (dtype == DataType::AUDIO) { - newport.reset (new AudioPort (portname, (input ? Port::IsInput : Port::IsOutput))); - } else if (dtype == DataType::MIDI) { - newport.reset (new MidiPort (portname, (input ? Port::IsInput : Port::IsOutput))); + if (_session) { + if (_session->config.get_jack_time_master()) { + _backend->set_time_master (true); } else { - throw PortRegistrationFailure("unable to create port (unknown type)"); + _backend->set_time_master (false); } - - RCUWriter<Ports> writer (ports); - boost::shared_ptr<Ports> ps = writer.get_copy (); - ps->insert (make_pair (make_port_name_relative (portname), newport)); - - /* writer goes out of scope, forces update */ - - return newport; - } - - catch (PortRegistrationFailure& err) { - throw err; - } catch (std::exception& e) { - throw PortRegistrationFailure(string_compose( - _("unable to create port: %1"), e.what()).c_str()); - } catch (...) { - throw PortRegistrationFailure("unable to create port (unknown error)"); } + return 0; } -boost::shared_ptr<Port> -AudioEngine::register_input_port (DataType type, const string& portname) -{ - return register_port (type, portname, true); -} -boost::shared_ptr<Port> -AudioEngine::register_output_port (DataType type, const string& portname) +void +AudioEngine::destroy () { - return register_port (type, portname, false); + delete _instance; + _instance = 0; } int -AudioEngine::unregister_port (boost::shared_ptr<Port> port) +AudioEngine::discover_backends () { - /* caller must hold process lock */ + vector<std::string> backend_modules; - if (!_running) { - /* probably happening when the engine has been halted by JACK, - in which case, there is nothing we can do here. - */ - return 0; - } + _backends.clear (); - { - RCUWriter<Ports> writer (ports); - boost::shared_ptr<Ports> ps = writer.get_copy (); - Ports::iterator x = ps->find (make_port_name_relative (port->name())); + Glib::PatternSpec so_extension_pattern("*backend.so"); + Glib::PatternSpec dylib_extension_pattern("*backend.dylib"); - if (x != ps->end()) { - ps->erase (x); - } + find_matching_files_in_search_path (backend_search_path (), + so_extension_pattern, backend_modules); - /* writer goes out of scope, forces update */ - } + find_matching_files_in_search_path (backend_search_path (), + dylib_extension_pattern, backend_modules); - ports.flush (); + DEBUG_TRACE (DEBUG::Panning, string_compose (_("looking for backends in %1\n"), backend_search_path().to_string())); - return 0; -} + for (vector<std::string>::iterator i = backend_modules.begin(); i != backend_modules.end(); ++i) { -int -AudioEngine::connect (const string& source, const string& destination) -{ - int ret; + AudioBackendInfo* info; - if (!_running) { - if (!_has_run) { - fatal << _("connect called before engine was started") << endmsg; - /*NOTREACHED*/ - } else { - return -1; + if ((info = backend_discover (*i)) != 0) { + _backends.insert (make_pair (info->name, info)); } } - string s = make_port_name_non_relative (source); - string d = make_port_name_non_relative (destination); - + return _backends.size(); +} - boost::shared_ptr<Port> src = get_port_by_name (s); - boost::shared_ptr<Port> dst = get_port_by_name (d); +AudioBackendInfo* +AudioEngine::backend_discover (const string& path) +{ + Glib::Module module (path); + AudioBackendInfo* info; + void* sym = 0; - if (src) { - ret = src->connect (d); - } else if (dst) { - ret = dst->connect (s); - } else { - /* neither port is known to us, and this API isn't intended for use as a general patch bay */ - ret = -1; + if (!module) { + error << string_compose(_("AudioEngine: cannot load module \"%1\" (%2)"), path, + Glib::Module::get_last_error()) << endmsg; + return 0; } - - if (ret > 0) { - /* already exists - no error, no warning */ - } else if (ret < 0) { - error << string_compose(_("AudioEngine: cannot connect %1 (%2) to %3 (%4)"), - source, s, destination, d) - << endmsg; + + if (!module.get_symbol ("descriptor", sym)) { + error << string_compose(_("AudioEngine: backend at \"%1\" has no descriptor."), path) << endmsg; + error << Glib::Module::get_last_error() << endmsg; + return 0; } - return ret; + module.make_resident (); + + info = (AudioBackendInfo*) sym; + + return info; } -int -AudioEngine::disconnect (const string& source, const string& destination) +vector<const AudioBackendInfo*> +AudioEngine::available_backends() const { - int ret; - - if (!_running) { - if (!_has_run) { - fatal << _("disconnect called before engine was started") << endmsg; - /*NOTREACHED*/ - } else { - return -1; - } + vector<const AudioBackendInfo*> r; + + for (BackendMap::const_iterator i = _backends.begin(); i != _backends.end(); ++i) { + r.push_back (i->second); } - string s = make_port_name_non_relative (source); - string d = make_port_name_non_relative (destination); - - boost::shared_ptr<Port> src = get_port_by_name (s); - boost::shared_ptr<Port> dst = get_port_by_name (d); - - if (src) { - ret = src->disconnect (d); - } else if (dst) { - ret = dst->disconnect (s); - } else { - /* neither port is known to us, and this API isn't intended for use as a general patch bay */ - ret = -1; - } - return ret; + return r; } -int -AudioEngine::disconnect (boost::shared_ptr<Port> port) +string +AudioEngine::current_backend_name() const { - GET_PRIVATE_JACK_POINTER_RET (_jack,-1); - - if (!_running) { - if (!_has_run) { - fatal << _("disconnect called before engine was started") << endmsg; - /*NOTREACHED*/ - } else { - return -1; - } - } - - return port->disconnect_all (); + if (_backend) { + return _backend->name(); + } + return string(); } -ARDOUR::framecnt_t -AudioEngine::frame_rate () const +void +AudioEngine::drop_backend () { - GET_PRIVATE_JACK_POINTER_RET (_jack, 0); - if (_frame_rate == 0) { - return (_frame_rate = jack_get_sample_rate (_priv_jack)); - } else { - return _frame_rate; + if (_backend) { + _backend->stop (); + _backend.reset (); } } -size_t -AudioEngine::raw_buffer_size (DataType t) +boost::shared_ptr<AudioBackend> +AudioEngine::set_backend (const std::string& name, const std::string& arg1, const std::string& arg2) { - std::map<DataType,size_t>::const_iterator s = _raw_buffer_sizes.find(t); - return (s != _raw_buffer_sizes.end()) ? s->second : 0; -} + BackendMap::iterator b = _backends.find (name); -ARDOUR::pframes_t -AudioEngine::frames_per_cycle () const -{ - GET_PRIVATE_JACK_POINTER_RET (_jack,0); - if (_buffer_size == 0) { - return jack_get_buffer_size (_jack); - } else { - return _buffer_size; + if (b == _backends.end()) { + return boost::shared_ptr<AudioBackend>(); } -} - -/** @param name Full or short name of port - * @return Corresponding Port or 0. - */ -boost::shared_ptr<Port> -AudioEngine::get_port_by_name (const string& portname) -{ - if (!_running) { - if (!_has_run) { - fatal << _("get_port_by_name() called before engine was started") << endmsg; - /*NOTREACHED*/ - } else { - boost::shared_ptr<Port> (); + drop_backend (); + + try { + if (b->second->instantiate (arg1, arg2)) { + throw failed_constructor (); } - } - if (!port_is_mine (portname)) { - /* not an ardour port */ - return boost::shared_ptr<Port> (); - } + _backend = b->second->backend_factory (*this); + _impl = b->second->portengine_factory (*this); - boost::shared_ptr<Ports> pr = ports.reader(); - std::string rel = make_port_name_relative (portname); - Ports::iterator x = pr->find (rel); - - if (x != pr->end()) { - /* its possible that the port was renamed by some 3rd party and - we don't know about it. check for this (the check is quick - and cheap), and if so, rename the port (which will alter - the port map as a side effect). - */ - const std::string check = make_port_name_relative (jack_port_name (x->second->jack_port())); - if (check != rel) { - x->second->set_name (check); - } - return x->second; + } catch (exception& e) { + error << string_compose (_("Could not create backend for %1: %2"), name, e.what()) << endmsg; + return boost::shared_ptr<AudioBackend>(); } - return boost::shared_ptr<Port> (); + return _backend; } -void -AudioEngine::port_renamed (const std::string& old_relative_name, const std::string& new_relative_name) +/* BACKEND PROXY WRAPPERS */ + +int +AudioEngine::start () { - RCUWriter<Ports> writer (ports); - boost::shared_ptr<Ports> p = writer.get_copy(); - Ports::iterator x = p->find (old_relative_name); + if (!_backend) { + return -1; + } + + if (_running) { + return 0; + } + + _processed_frames = 0; + last_monitor_check = 0; - if (x != p->end()) { - boost::shared_ptr<Port> port = x->second; - p->erase (x); - p->insert (make_pair (new_relative_name, port)); + if (_backend->start()) { + return -1; } -} -const char ** -AudioEngine::get_ports (const string& port_name_pattern, const string& type_name_pattern, uint32_t flags) -{ - GET_PRIVATE_JACK_POINTER_RET (_jack,0); - if (!_running) { - if (!_has_run) { - fatal << _("get_ports called before engine was started") << endmsg; - /*NOTREACHED*/ - } else { - return 0; + _running = true; + + if (_session) { + _session->set_frame_rate (_backend->sample_rate()); + + if (_session->config.get_jack_time_master()) { + _backend->set_time_master (true); } } - return jack_get_ports (_priv_jack, port_name_pattern.c_str(), type_name_pattern.c_str(), flags); + + start_metering_thread (); + + Running(); /* EMIT SIGNAL */ + + return 0; } -void -AudioEngine::halted_info (jack_status_t code, const char* reason, void *arg) +int +AudioEngine::stop () { - /* called from jack shutdown handler */ - - AudioEngine* ae = static_cast<AudioEngine *> (arg); - bool was_running = ae->_running; - - ae->stop_metering_thread (); + if (!_backend) { + return 0; + } - ae->_running = false; - ae->_buffer_size = 0; - ae->_frame_rate = 0; - ae->_jack = 0; + Glib::Threads::Mutex::Lock lm (_process_lock); - if (was_running) { - MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */ -#ifdef HAVE_JACK_ON_INFO_SHUTDOWN - switch (code) { - case JackBackendError: - ae->Halted(reason); /* EMIT SIGNAL */ - break; - default: - ae->Halted(""); /* EMIT SIGNAL */ - } -#else - ae->Halted(""); /* EMIT SIGNAL */ -#endif - } + if (_backend->stop ()) { + return -1; + } + + _running = false; + _processed_frames = 0; + _measuring_latency = false; + _latency_output_port = 0; + _latency_input_port = 0; + stop_metering_thread (); + + Port::PortDrop (); + Stopped (); /* EMIT SIGNAL */ + + return 0; } -void -AudioEngine::halted (void *arg) +int +AudioEngine::pause () { - cerr << "HALTED by JACK\n"; + if (!_backend) { + return 0; + } + + if (_backend->pause ()) { + return -1; + } - /* called from jack shutdown handler */ + _running = false; + + Stopped(); /* EMIT SIGNAL */ + return 0; +} - AudioEngine* ae = static_cast<AudioEngine *> (arg); - bool was_running = ae->_running; +int +AudioEngine::freewheel (bool start_stop) +{ + if (!_backend) { + return -1; + } - ae->stop_metering_thread (); + /* _freewheeling will be set when first Freewheel signal occurs */ - ae->_running = false; - ae->_buffer_size = 0; - ae->_frame_rate = 0; - ae->_jack = 0; + return _backend->freewheel (start_stop); +} - if (was_running) { - MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */ - ae->Halted(""); /* EMIT SIGNAL */ +float +AudioEngine::get_cpu_load() const +{ + if (!_backend) { + return 0.0; } + return _backend->cpu_load (); } -void -AudioEngine::died () +bool +AudioEngine::is_realtime() const { - /* called from a signal handler for SIGPIPE */ - - stop_metering_thread (); + if (!_backend) { + return false; + } - _running = false; - _buffer_size = 0; - _frame_rate = 0; - _jack = 0; + return _backend->is_realtime(); } bool -AudioEngine::can_request_hardware_monitoring () +AudioEngine::connected() const { - GET_PRIVATE_JACK_POINTER_RET (_jack,false); - const char ** ports; - - if ((ports = jack_get_ports (_priv_jack, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortCanMonitor)) == 0) { + if (!_backend) { return false; } - free (ports); - - return true; + return _backend->connected(); } -ChanCount -AudioEngine::n_physical (unsigned long flags) const +void +AudioEngine::transport_start () { - ChanCount c; - - GET_PRIVATE_JACK_POINTER_RET (_jack, c); - - const char ** ports = jack_get_ports (_priv_jack, NULL, NULL, JackPortIsPhysical | flags); - if (ports == 0) { - return c; - } - - for (uint32_t i = 0; ports[i]; ++i) { - if (!strstr (ports[i], "Midi-Through")) { - DataType t (jack_port_type (jack_port_by_name (_jack, ports[i]))); - c.set (t, c.get (t) + 1); - } + if (!_backend) { + return; } - - free (ports); - - return c; + return _backend->transport_start (); } -ChanCount -AudioEngine::n_physical_inputs () const +void +AudioEngine::transport_stop () { - return n_physical (JackPortIsInput); + if (!_backend) { + return; + } + return _backend->transport_stop (); } -ChanCount -AudioEngine::n_physical_outputs () const +TransportState +AudioEngine::transport_state () { - return n_physical (JackPortIsOutput); + if (!_backend) { + return TransportStopped; + } + return _backend->transport_state (); } void -AudioEngine::get_physical (DataType type, unsigned long flags, vector<string>& phy) +AudioEngine::transport_locate (framepos_t pos) { - GET_PRIVATE_JACK_POINTER (_jack); - const char ** ports; - - if ((ports = jack_get_ports (_priv_jack, NULL, type.to_jack_type(), JackPortIsPhysical | flags)) == 0) { + if (!_backend) { return; } - - if (ports) { - for (uint32_t i = 0; ports[i]; ++i) { - if (strstr (ports[i], "Midi-Through")) { - continue; - } - phy.push_back (ports[i]); - } - free (ports); - } + return _backend->transport_locate (pos); } -/** Get physical ports for which JackPortIsOutput is set; ie those that correspond to - * a physical input connector. - */ -void -AudioEngine::get_physical_inputs (DataType type, vector<string>& ins) +framepos_t +AudioEngine::transport_frame() { - get_physical (type, JackPortIsOutput, ins); + if (!_backend) { + return 0; + } + return _backend->transport_frame (); } -/** Get physical ports for which JackPortIsInput is set; ie those that correspond to - * a physical output connector. - */ -void -AudioEngine::get_physical_outputs (DataType type, vector<string>& outs) +framecnt_t +AudioEngine::sample_rate () const { - get_physical (type, JackPortIsInput, outs); + if (!_backend) { + return 0; + } + return _backend->sample_rate (); } -void -AudioEngine::transport_stop () +pframes_t +AudioEngine::samples_per_cycle () const { - GET_PRIVATE_JACK_POINTER (_jack); - jack_transport_stop (_priv_jack); + if (!_backend) { + return 0; + } + return _backend->buffer_size (); } -void -AudioEngine::transport_start () +int +AudioEngine::usecs_per_cycle () const { - GET_PRIVATE_JACK_POINTER (_jack); - jack_transport_start (_priv_jack); + if (!_backend) { + return -1; + } + return _backend->usecs_per_cycle (); } -void -AudioEngine::transport_locate (framepos_t where) +size_t +AudioEngine::raw_buffer_size (DataType t) { - GET_PRIVATE_JACK_POINTER (_jack); - jack_transport_locate (_priv_jack, where); + if (!_backend) { + return -1; + } + return _backend->raw_buffer_size (t); } -AudioEngine::TransportState -AudioEngine::transport_state () +pframes_t +AudioEngine::sample_time () { - GET_PRIVATE_JACK_POINTER_RET (_jack, ((TransportState) JackTransportStopped)); - jack_position_t pos; - return (TransportState) jack_transport_query (_priv_jack, &pos); + if (!_backend) { + return 0; + } + return _backend->sample_time (); } -int -AudioEngine::reset_timebase () +pframes_t +AudioEngine::sample_time_at_cycle_start () { - GET_PRIVATE_JACK_POINTER_RET (_jack, -1); - if (_session) { - if (_session->config.get_jack_time_master()) { - return jack_set_timebase_callback (_priv_jack, 0, _jack_timebase_callback, this); - } else { - return jack_release_timebase (_jack); - } + if (!_backend) { + return 0; } - return 0; + return _backend->sample_time_at_cycle_start (); } -int -AudioEngine::freewheel (bool onoff) +pframes_t +AudioEngine::samples_since_cycle_start () { - GET_PRIVATE_JACK_POINTER_RET (_jack, -1); - - if (onoff != _freewheeling) { - return jack_set_freewheel (_priv_jack, onoff); - - } else { - /* already doing what has been asked for */ - return 0; + if (!_backend) { + return 0; } + return _backend->samples_since_cycle_start (); } -void -AudioEngine::remove_all_ports () +bool +AudioEngine::get_sync_offset (pframes_t& offset) const { - /* make sure that JACK callbacks that will be invoked as we cleanup - * ports know that they have nothing to do. - */ - - port_remove_in_progress = true; - - /* process lock MUST be held by caller - */ - - { - RCUWriter<Ports> writer (ports); - boost::shared_ptr<Ports> ps = writer.get_copy (); - ps->clear (); + if (!_backend) { + return false; } - - /* clear dead wood list in RCU */ - - ports.flush (); - - port_remove_in_progress = false; + return _backend->get_sync_offset (offset); } int -AudioEngine::connect_to_jack (string client_name, string session_uuid) +AudioEngine::create_process_thread (boost::function<void()> func, pthread_t* thr, size_t stacksize) { - EnvironmentalProtectionAgency* global_epa = EnvironmentalProtectionAgency::get_global_epa (); - boost::scoped_ptr<EnvironmentalProtectionAgency> current_epa; - jack_status_t status; - - /* revert all environment settings back to whatever they were when ardour started - */ - - if (global_epa) { - current_epa.reset (new EnvironmentalProtectionAgency(true)); /* will restore settings when we leave scope */ - global_epa->restore (); - } - - jack_client_name = client_name; /* might be reset below */ -#ifdef HAVE_JACK_SESSION - if (! session_uuid.empty()) - _jack = jack_client_open (jack_client_name.c_str(), JackSessionID, &status, session_uuid.c_str()); - else -#endif - _jack = jack_client_open (jack_client_name.c_str(), JackNullOption, &status, 0); - - if (_jack == NULL) { - // error message is not useful here + if (!_backend) { return -1; } + return _backend->create_process_thread (func, thr, stacksize); +} - GET_PRIVATE_JACK_POINTER_RET (_jack, -1); - if (status & JackNameNotUnique) { - jack_client_name = jack_get_client_name (_priv_jack); +int +AudioEngine::set_device_name (const std::string& name) +{ + if (!_backend) { + return -1; } - - return 0; + return _backend->set_device_name (name); } int -AudioEngine::disconnect_from_jack () +AudioEngine::set_sample_rate (float sr) { - GET_PRIVATE_JACK_POINTER_RET (_jack, 0); - - if (_running) { - stop_metering_thread (); - } - - { - Glib::Threads::Mutex::Lock lm (_process_lock); - jack_client_close (_priv_jack); - _jack = 0; - } - - _buffer_size = 0; - _frame_rate = 0; - _raw_buffer_sizes.clear(); - - if (_running) { - _running = false; - MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */ - Stopped(); /* EMIT SIGNAL */ + if (!_backend) { + return -1; } - - return 0; + return _backend->set_sample_rate (sr); } int -AudioEngine::reconnect_to_jack () +AudioEngine::set_buffer_size (uint32_t bufsiz) { - if (_running) { - disconnect_from_jack (); - /* XXX give jackd a chance */ - Glib::usleep (250000); - } - - if (connect_to_jack (jack_client_name, "")) { - error << _("failed to connect to JACK") << endmsg; + if (!_backend) { return -1; } + return _backend->set_buffer_size (bufsiz); +} - Ports::iterator i; - - boost::shared_ptr<Ports> p = ports.reader (); - - for (i = p->begin(); i != p->end(); ++i) { - if (i->second->reestablish ()) { - break; - } +int +AudioEngine::set_sample_format (SampleFormat sf) +{ + if (!_backend) { + return -1; } + return _backend->set_sample_format (sf); +} - if (i != p->end()) { - /* failed */ - remove_all_ports (); +int +AudioEngine::set_interleaved (bool yn) +{ + if (!_backend) { return -1; } + return _backend->set_interleaved (yn); +} - GET_PRIVATE_JACK_POINTER_RET (_jack,-1); - - MIDI::Manager::instance()->reestablish (_priv_jack); - - if (_session) { - _session->reset_jack_connection (_priv_jack); - jack_bufsize_callback (jack_get_buffer_size (_priv_jack)); - _session->set_frame_rate (jack_get_sample_rate (_priv_jack)); +int +AudioEngine::set_input_channels (uint32_t ic) +{ + if (!_backend) { + return -1; } + return _backend->set_input_channels (ic); +} - last_monitor_check = 0; - - set_jack_callbacks (); - - if (jack_activate (_priv_jack) == 0) { - _running = true; - _has_run = true; - } else { +int +AudioEngine::set_output_channels (uint32_t oc) +{ + if (!_backend) { return -1; } + return _backend->set_output_channels (oc); +} - /* re-establish connections */ - - for (i = p->begin(); i != p->end(); ++i) { - i->second->reconnect (); +int +AudioEngine::set_systemic_input_latency (uint32_t il) +{ + if (!_backend) { + return -1; } - - MIDI::Manager::instance()->reconnect (); - - Running (); /* EMIT SIGNAL*/ - - start_metering_thread (); - - return 0; + return _backend->set_systemic_input_latency (il); } int -AudioEngine::request_buffer_size (pframes_t nframes) +AudioEngine::set_systemic_output_latency (uint32_t ol) { - GET_PRIVATE_JACK_POINTER_RET (_jack, -1); - - if (nframes == jack_get_buffer_size (_priv_jack)) { - return 0; + if (!_backend) { + return -1; } - - return jack_set_buffer_size (_priv_jack, nframes); + return _backend->set_systemic_output_latency (ol); } -string -AudioEngine::make_port_name_relative (string portname) const +/* END OF BACKEND PROXY API */ + +void +AudioEngine::thread_init_callback (void* arg) { - string::size_type len; - string::size_type n; + /* make sure that anybody who needs to know about this thread + knows about it. + */ - len = portname.length(); + pthread_set_name (X_("audioengine")); - for (n = 0; n < len; ++n) { - if (portname[n] == ':') { - break; - } - } + PBD::notify_gui_about_thread_creation ("gui", pthread_self(), X_("AudioEngine"), 4096); + PBD::notify_gui_about_thread_creation ("midiui", pthread_self(), X_("AudioEngine"), 128); - if ((n != len) && (portname.substr (0, n) == jack_client_name)) { - return portname.substr (n+1); - } + SessionEvent::create_per_thread_pool (X_("AudioEngine"), 512); - return portname; + AsyncMIDIPort::set_process_thread (pthread_self()); + + if (arg) { + /* the special thread created/managed by the backend */ + AudioEngine::instance()->_main_thread = new ProcessThread; + } } -string -AudioEngine::make_port_name_non_relative (string portname) const +int +AudioEngine::sync_callback (TransportState state, framepos_t position) { - string str; - - if (portname.find_first_of (':') != string::npos) { - return portname; + if (_session) { + return _session->backend_sync_callback (state, position); } + return 0; +} - str = jack_client_name; - str += ':'; - str += portname; - - return str; +void +AudioEngine::freewheel_callback (bool onoff) +{ + _freewheeling = onoff; } -bool -AudioEngine::port_is_mine (const string& portname) const +void +AudioEngine::latency_callback (bool for_playback) { - if (portname.find_first_of (':') != string::npos) { - if (portname.substr (0, jack_client_name.length ()) != jack_client_name) { - return false; - } + if (_session) { + _session->update_latency (for_playback); } - return true; } -bool -AudioEngine::is_realtime () const +void +AudioEngine::update_latencies () { - GET_PRIVATE_JACK_POINTER_RET (_jack,false); - return jack_is_realtime (_priv_jack); + if (_backend) { + _backend->update_latencies (); + } } -int -AudioEngine::create_process_thread (boost::function<void()> f, jack_native_thread_t* thread, size_t stacksize) +void +AudioEngine::halted_callback (const char* why) { - GET_PRIVATE_JACK_POINTER_RET (_jack, 0); - ThreadData* td = new ThreadData (this, f, stacksize); + stop_metering_thread (); + _running = false; - if (jack_client_create_thread (_priv_jack, thread, jack_client_real_time_priority (_priv_jack), - jack_is_realtime (_priv_jack), _start_process_thread, td)) { - return -1; - } - - return 0; + Port::PortDrop (); /* EMIT SIGNAL */ + Halted (why); /* EMIT SIGNAL */ } bool -AudioEngine::stop_process_thread (jack_native_thread_t thread) +AudioEngine::setup_required () const { - /** - * can't use GET_PRIVATE_JACK_POINTER_RET (_jack, 0) here - * because _jack is 0 when this is called. At least for - * Jack 2 _jack arg is not used so it should be OK - */ - -#if defined(USING_JACK2_EXPANSION_OF_JACK_API) || defined(PLATFORM_WINDOWS) - if (jack_client_stop_thread (_jack, thread) != 0) { - error << "AudioEngine: cannot stop process thread" << endmsg; + if (_backends.size() == 1 && _backends.begin()->second->already_configured()) { return false; } -#else - void* status; - pthread_join (thread, &status); -#endif return true; } -void* -AudioEngine::_start_process_thread (void* arg) +MTDM* +AudioEngine::mtdm() { - ThreadData* td = reinterpret_cast<ThreadData*> (arg); - boost::function<void()> f = td->f; - delete td; - - f (); - - return 0; + return _mtdm; } -bool -AudioEngine::port_is_physical (const std::string& portname) const +void +AudioEngine::start_latency_detection () { - GET_PRIVATE_JACK_POINTER_RET(_jack, false); + PortEngine& pe (port_engine()); - jack_port_t *port = jack_port_by_name (_priv_jack, portname.c_str()); + delete _mtdm; + _mtdm = 0; - if (!port) { - return false; - } + /* create the ports we will use to read/write data */ + + if ((_latency_output_port = pe.register_port ("latency_out", DataType::AUDIO, IsOutput)) == 0) { + return; + } + if (pe.connect (_latency_output_port, _latency_output_name)) { + return; + } - return jack_port_flags (port) & JackPortIsPhysical; -} + const string portname ("latency_in"); + if ((_latency_input_port = pe.register_port (portname, DataType::AUDIO, IsInput)) == 0) { + pe.unregister_port (_latency_output_port); + return; + } + if (pe.connect (_latency_input_name, make_port_name_non_relative (portname))) { + pe.unregister_port (_latency_output_port); + return; + } -void -AudioEngine::request_jack_monitors_input (const std::string& portname, bool yn) const -{ - GET_PRIVATE_JACK_POINTER(_jack); + LatencyRange lr; + _latency_signal_latency = 0; + lr = pe.get_latency_range (_latency_input_port, false); + _latency_signal_latency = lr.max; + lr = pe.get_latency_range (_latency_output_port, true); + _latency_signal_latency += lr.max; - jack_port_t *port = jack_port_by_name (_priv_jack, portname.c_str()); + cerr << "latency signal pathway = " << _latency_signal_latency << endl; + + /* all created and connected, lets go */ + + _mtdm = new MTDM (sample_rate()); + _measuring_latency = true; + _latency_flush_frames = samples_per_cycle(); - if (!port) { - return; - } - jack_port_request_monitor (port, yn); } void -AudioEngine::update_latencies () +AudioEngine::stop_latency_detection () { - GET_PRIVATE_JACK_POINTER (_jack); - jack_recompute_total_latencies (_priv_jack); + port_engine().unregister_port (_latency_output_port); + port_engine().unregister_port (_latency_input_port); + _measuring_latency = false; } void -AudioEngine::destroy () +AudioEngine::set_latency_output_port (const string& name) { - delete _instance; - _instance = 0; + _latency_output_name = name; } +void +AudioEngine::set_latency_input_port (const string& name) +{ + _latency_input_name = name; +} diff --git a/libs/ardour/backend_search_path.cc b/libs/ardour/backend_search_path.cc new file mode 100644 index 0000000000..4c5ff40e7d --- /dev/null +++ b/libs/ardour/backend_search_path.cc @@ -0,0 +1,45 @@ +/* + Copyright (C) 2013 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. + +*/ + +#include <glibmm/miscutils.h> + +#include "ardour/backend_search_path.h" +#include "ardour/directory_names.h" +#include "ardour/filesystem_paths.h" + +namespace { + const char * const backend_env_variable_name = "ARDOUR_BACKEND_PATH"; +} // anonymous + +using namespace PBD; + +namespace ARDOUR { + +Searchpath +backend_search_path () +{ + Searchpath spath(user_config_directory ()); + spath += ardour_dll_directory (); + spath.add_subdirectory_to_paths(backend_dir_name); + + spath += Searchpath(Glib::getenv(backend_env_variable_name)); + return spath; +} + +} // namespace ARDOUR diff --git a/libs/ardour/buffer_set.cc b/libs/ardour/buffer_set.cc index 184e23d1af..e67aee6be7 100644 --- a/libs/ardour/buffer_set.cc +++ b/libs/ardour/buffer_set.cc @@ -90,7 +90,7 @@ BufferSet::clear() /** Set up this BufferSet so that its data structures mirror a PortSet's buffers. * This is quite expensive and not RT-safe, so it should not be called in a process context; - * get_jack_port_addresses() will fill in a structure set up by this method. + * get_backend_port_addresses() will fill in a structure set up by this method. * * XXX: this *is* called in a process context; I'm not sure quite what `should not' means above. */ @@ -114,13 +114,13 @@ BufferSet::attach_buffers (PortSet& ports) _is_mirror = true; } -/** Write the JACK port addresses from a PortSet into our data structures. This +/** Write the backend port addresses from a PortSet into our data structures. This * call assumes that attach_buffers() has already been called for the same PortSet. * Does not allocate, so RT-safe BUT you can only call Port::get_buffer() from * the process() callback tree anyway, so this has to be called in RT context. */ void -BufferSet::get_jack_port_addresses (PortSet& ports, framecnt_t nframes) +BufferSet::get_backend_port_addresses (PortSet& ports, framecnt_t nframes) { assert (_count == ports.count ()); assert (_available == ports.count ()); diff --git a/libs/ardour/bundle.cc b/libs/ardour/bundle.cc index 0ac62d7076..834a98d347 100644 --- a/libs/ardour/bundle.cc +++ b/libs/ardour/bundle.cc @@ -443,27 +443,26 @@ Bundle::connected_to (boost::shared_ptr<Bundle> other, AudioEngine & engine) return true; } -/** This must not be called in code executed as a response to a JACK event, - * as it uses jack_port_get_all_connections(). +/** This must not be called in code executed as a response to a backend event, + * as it uses the backend port_get_all_connections(). * @return true if any of this bundle's channels are connected to anything. */ bool Bundle::connected_to_anything (AudioEngine& engine) { + PortManager& pm (engine); + for (uint32_t i = 0; i < nchannels().n_total(); ++i) { Bundle::PortList const & ports = channel_ports (i); for (uint32_t j = 0; j < ports.size(); ++j) { - /* ports[j] may not be an Ardour port, so use JACK directly + + /* ports[j] may not be an Ardour port, so use the port manager directly rather than doing it with Port. */ - jack_port_t* jp = jack_port_by_name (engine.jack(), ports[j].c_str()); - if (jp) { - const char ** c = jack_port_get_all_connections (engine.jack(), jp); - if (c) { - jack_free (c); - return true; - } + + if (pm.connected (ports[j])) { + return true; } } } diff --git a/libs/ardour/capturing_processor.cc b/libs/ardour/capturing_processor.cc index 4a31d92cc8..ce4a546fb4 100644 --- a/libs/ardour/capturing_processor.cc +++ b/libs/ardour/capturing_processor.cc @@ -28,7 +28,7 @@ namespace ARDOUR { CapturingProcessor::CapturingProcessor (Session & session) : Processor (session, X_("capture point")) - , block_size (session.engine().frames_per_cycle()) + , block_size (AudioEngine::instance()->samples_per_cycle()) { realloc_buffers (); } diff --git a/libs/ardour/debug.cc b/libs/ardour/debug.cc index 51115001cb..dc762299e9 100644 --- a/libs/ardour/debug.cc +++ b/libs/ardour/debug.cc @@ -61,5 +61,6 @@ uint64_t PBD::DEBUG::TempoMap = PBD::new_debug_bit ("tempomap"); uint64_t PBD::DEBUG::OrderKeys = PBD::new_debug_bit ("orderkeys"); uint64_t PBD::DEBUG::Automation = PBD::new_debug_bit ("automation"); uint64_t PBD::DEBUG::WiimoteControl = PBD::new_debug_bit ("wiimotecontrol"); +uint64_t PBD::DEBUG::Ports = PBD::new_debug_bit ("Ports"); diff --git a/libs/ardour/delivery.cc b/libs/ardour/delivery.cc index dfbe4c960a..79c44ce94a 100644 --- a/libs/ardour/delivery.cc +++ b/libs/ardour/delivery.cc @@ -245,7 +245,7 @@ Delivery::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, pf processing pathway that wants to use this->output_buffers() for some reason. */ - output_buffers().get_jack_port_addresses (ports, nframes); + output_buffers().get_backend_port_addresses (ports, nframes); // this Delivery processor is not a derived type, and thus we assume // we really can modify the buffers passed in (it is almost certainly diff --git a/libs/ardour/directory_names.cc b/libs/ardour/directory_names.cc index 6fb15eaabd..f01c024435 100644 --- a/libs/ardour/directory_names.cc +++ b/libs/ardour/directory_names.cc @@ -38,6 +38,7 @@ const char* const route_templates_dir_name = X_("route_templates"); const char* const surfaces_dir_name = X_("surfaces"); const char* const ladspa_dir_name = X_("ladspa"); const char* const panner_dir_name = X_("panners"); +const char* const backend_dir_name = X_("backends"); /* these should end up using variants of PROGRAM_NAME */ #ifdef __APPLE__ diff --git a/libs/ardour/export_channel.cc b/libs/ardour/export_channel.cc index 9b3f50e85d..82e5d80244 100644 --- a/libs/ardour/export_channel.cc +++ b/libs/ardour/export_channel.cc @@ -117,7 +117,7 @@ RegionExportChannelFactory::RegionExportChannelFactory (Session * session, Audio : region (region) , track (track) , type (type) - , frames_per_cycle (session->engine().frames_per_cycle ()) + , frames_per_cycle (session->engine().samples_per_cycle ()) , buffers_up_to_date (false) , region_start (region.position()) , position (region_start) diff --git a/libs/ardour/export_graph_builder.cc b/libs/ardour/export_graph_builder.cc index 127546e8fc..2c0c44033d 100644 --- a/libs/ardour/export_graph_builder.cc +++ b/libs/ardour/export_graph_builder.cc @@ -56,7 +56,7 @@ ExportGraphBuilder::ExportGraphBuilder (Session const & session) : session (session) , thread_pool (hardware_concurrency()) { - process_buffer_frames = session.engine().frames_per_cycle(); + process_buffer_frames = session.engine().samples_per_cycle(); } ExportGraphBuilder::~ExportGraphBuilder () @@ -507,7 +507,7 @@ ExportGraphBuilder::ChannelConfig::ChannelConfig (ExportGraphBuilder & parent, F config = new_config; - framecnt_t max_frames = parent.session.engine().frames_per_cycle(); + framecnt_t max_frames = parent.session.engine().samples_per_cycle(); interleaver.reset (new Interleaver<Sample> ()); interleaver->init (new_config.channel_config->get_n_chans(), max_frames); diff --git a/libs/ardour/globals.cc b/libs/ardour/globals.cc index 1406bbd43a..8646b819f3 100644 --- a/libs/ardour/globals.cc +++ b/libs/ardour/globals.cc @@ -35,6 +35,7 @@ #include <unistd.h> #include <fcntl.h> #include <errno.h> +#include <time.h> #ifdef WINDOWS_VST_SUPPORT #include <fst.h> @@ -74,11 +75,11 @@ #include "pbd/basename.h" #include "midi++/port.h" -#include "midi++/manager.h" #include "midi++/mmc.h" #include "ardour/analyser.h" #include "ardour/audio_library.h" +#include "ardour/audio_backend.h" #include "ardour/audioengine.h" #include "ardour/audioplaylist.h" #include "ardour/audioregion.h" @@ -86,6 +87,7 @@ #include "ardour/control_protocol_manager.h" #include "ardour/filesystem_paths.h" #include "ardour/midi_region.h" +#include "ardour/midiport_manager.h" #include "ardour/mix.h" #include "ardour/panner_manager.h" #include "ardour/plugin_manager.h" @@ -342,6 +344,8 @@ ARDOUR::init (bool use_windows_vst, bool try_optimization, const char* localedir EventTypeMap::instance().new_parameter(EnvelopeAutomation); EventTypeMap::instance().new_parameter(MidiCCAutomation); + ARDOUR::AudioEngine::create (); + libardour_initialized = true; return true; @@ -350,9 +354,6 @@ ARDOUR::init (bool use_windows_vst, bool try_optimization, const char* localedir void ARDOUR::init_post_engine () { - /* the MIDI Manager is needed by the ControlProtocolManager */ - MIDI::Manager::create (AudioEngine::instance()->jack()); - ControlProtocolManager::instance().discover_control_protocols (); XMLNode* node; @@ -550,3 +551,43 @@ ARDOUR::get_available_sync_options () return ret; } + +/** Return a monotonic value for the number of microseconds that have elapsed + * since an arbitrary zero origin. + */ + +#ifdef __MACH__ +/* Thanks Apple for not implementing this basic SUSv2, POSIX.1-2001 function + */ +#include <mach/mach_time.h> +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 0 +int +clock_gettime (int /*clk_id*/, struct timespec *t) +{ + static bool initialized = false; + static mach_timebase_info_data_t timebase; + if (!initialized) { + mach_timebase_info(&timebase); + initialized = true; + } + uint64_t time; + time = mach_absolute_time(); + double nseconds = ((double)time * (double)timebase.numer)/((double)timebase.denom); + double seconds = ((double)time * (double)timebase.numer)/((double)timebase.denom * 1e9); + t->tv_sec = seconds; + t->tv_nsec = nseconds; + return 0; +} +#endif + +microseconds_t +ARDOUR::get_microseconds () +{ + struct timespec ts; + if (clock_gettime (CLOCK_MONOTONIC, &ts) != 0) { + /* EEEK! */ + return 0; + } + return (microseconds_t) ts.tv_sec * 1000000 + (ts.tv_nsec/1000); +} diff --git a/libs/ardour/internal_send.cc b/libs/ardour/internal_send.cc index 029a46ce53..8136985e1f 100644 --- a/libs/ardour/internal_send.cc +++ b/libs/ardour/internal_send.cc @@ -310,7 +310,7 @@ bool InternalSend::configure_io (ChanCount in, ChanCount out) { bool ret = Send::configure_io (in, out); - set_block_size (_session.engine().frames_per_cycle()); + set_block_size (_session.engine().samples_per_cycle()); return ret; } diff --git a/libs/ardour/io.cc b/libs/ardour/io.cc index a9d37d4315..ebd295411e 100644 --- a/libs/ardour/io.cc +++ b/libs/ardour/io.cc @@ -1344,7 +1344,7 @@ IO::bundle_changed (Bundle::Change /*c*/) string IO::build_legal_port_name (DataType type) { - const int name_size = jack_port_name_size(); + const int name_size = AudioEngine::instance()->port_name_size(); int limit; string suffix; @@ -1378,7 +1378,7 @@ IO::build_legal_port_name (DataType type) // allow up to 4 digits for the output port number, plus the slash, suffix and extra space - limit = name_size - _session.engine().client_name().length() - (suffix.length() + 5); + limit = name_size - AudioEngine::instance()->my_name().length() - (suffix.length() + 5); std::vector<char> buf1(name_size+1); std::vector<char> buf2(name_size+1); @@ -1411,7 +1411,7 @@ IO::find_port_hole (const char* base) */ for (n = 1; n < 9999; ++n) { - std::vector<char> buf(jack_port_name_size()); + std::vector<char> buf (AudioEngine::instance()->port_name_size()); PortSet::iterator i = _ports.begin(); snprintf (&buf[0], jack_port_name_size(), _("%s %u"), base, n); @@ -1645,7 +1645,7 @@ IO::process_input (boost::shared_ptr<Processor> proc, framepos_t start_frame, fr return; } - _buffers.get_jack_port_addresses (_ports, nframes); + _buffers.get_backend_port_addresses (_ports, nframes); if (proc) { proc->run (_buffers, start_frame, end_frame, nframes, true); } diff --git a/libs/ardour/jack_slave.cc b/libs/ardour/jack_slave.cc index 4c2da4c6c4..4b2f3b1860 100644 --- a/libs/ardour/jack_slave.cc +++ b/libs/ardour/jack_slave.cc @@ -20,16 +20,14 @@ #include <iostream> #include <cerrno> -#include <jack/jack.h> -#include <jack/transport.h> - +#include "ardour/audioengine.h" #include "ardour/slave.h" using namespace std; using namespace ARDOUR; -JACK_Slave::JACK_Slave (jack_client_t* j) - : jack (j) +JACK_Slave::JACK_Slave (AudioEngine& e) + : engine (e) { double x; framepos_t p; @@ -41,12 +39,6 @@ JACK_Slave::~JACK_Slave () { } -void -JACK_Slave::reset_client (jack_client_t* j) -{ - jack = j; -} - bool JACK_Slave::locked() const { @@ -62,33 +54,26 @@ JACK_Slave::ok() const bool JACK_Slave::speed_and_position (double& sp, framepos_t& position) { - jack_position_t pos; - jack_transport_state_t state; - - state = jack_transport_query (jack, &pos); - - switch (state) { - case JackTransportStopped: + switch (engine.transport_state()) { + case TransportStopped: speed = 0; _starting = false; break; - case JackTransportRolling: + case TransportRolling: speed = 1.0; _starting = false; break; - case JackTransportLooping: + case TransportLooping: speed = 1.0; _starting = false; break; - case JackTransportStarting: + case TransportStarting: _starting = true; // don't adjust speed here, just leave it as it was break; - default: - cerr << "WARNING: Unknown JACK transport state: " << state << endl; } sp = speed; - position = pos.frame; + position = engine.transport_frame(); return true; } diff --git a/libs/ardour/ladspa_plugin.cc b/libs/ardour/ladspa_plugin.cc index 78e16f9941..8b089929b5 100644 --- a/libs/ardour/ladspa_plugin.cc +++ b/libs/ardour/ladspa_plugin.cc @@ -44,8 +44,6 @@ #include "pbd/xml++.h" #include "pbd/stacktrace.h" -#include "midi++/manager.h" - #include "ardour/session.h" #include "ardour/ladspa_plugin.h" #include "ardour/buffer_set.h" @@ -129,7 +127,9 @@ LadspaPlugin::init (string module_path, uint32_t index, framecnt_t rate) port_cnt = parameter_count(); _control_data = new LADSPA_Data[port_cnt]; + memset (_control_data, 0, sizeof (LADSPA_Data) * port_cnt); _shadow_data = new LADSPA_Data[port_cnt]; + memset (_shadow_data, 0, sizeof (LADSPA_Data) * port_cnt); for (i = 0; i < port_cnt; ++i) { if (LADSPA_IS_PORT_CONTROL(port_descriptor (i))) { diff --git a/libs/ardour/ltc_slave.cc b/libs/ardour/ltc_slave.cc index ac93ccdb40..eb168c9554 100644 --- a/libs/ardour/ltc_slave.cc +++ b/libs/ardour/ltc_slave.cc @@ -134,7 +134,7 @@ LTC_Slave::resync_latency() if (!session.deletion_in_progress() && session.ltc_output_io()) { /* check if Port exits */ boost::shared_ptr<Port> ltcport = session.ltc_input_port(); - ltcport->get_connected_latency_range(ltc_slave_latency, false); + ltcport->get_connected_latency_range (ltc_slave_latency, false); } } @@ -150,9 +150,9 @@ LTC_Slave::reset() } void -LTC_Slave::parse_ltc(const jack_nframes_t nframes, const jack_default_audio_sample_t * const in, const ARDOUR::framecnt_t posinfo) +LTC_Slave::parse_ltc(const pframes_t nframes, const Sample* const in, const framecnt_t posinfo) { - jack_nframes_t i; + pframes_t i; unsigned char sound[8192]; if (nframes > 8192) { /* TODO warn once or wrap, loop conversion below @@ -413,22 +413,22 @@ LTC_Slave::init_engine_dll (framepos_t pos, int32_t inc) } /* main entry point from session_process.cc - * called from jack_process callback context - * so it is OK to use jack_port_get_buffer() + * called from process callback context + * so it is OK to use get_buffer() */ bool LTC_Slave::speed_and_position (double& speed, framepos_t& pos) { bool engine_init_called = false; - framepos_t now = session.engine().frame_time_at_cycle_start(); + framepos_t now = session.engine().sample_time_at_cycle_start(); framepos_t sess_pos = session.transport_frame(); // corresponds to now - framecnt_t nframes = session.engine().frames_per_cycle(); + framecnt_t nframes = session.engine().samples_per_cycle(); - jack_default_audio_sample_t *in; + Sample* in; boost::shared_ptr<Port> ltcport = session.ltc_input_port(); - in = (jack_default_audio_sample_t*) jack_port_get_buffer (ltcport->jack_port(), nframes); + in = (Sample*) AudioEngine::instance()->port_engine().get_buffer (ltcport->port_handle(), nframes); frameoffset_t skip = now - (monotonic_cnt + nframes); monotonic_cnt = now; @@ -441,7 +441,7 @@ LTC_Slave::speed_and_position (double& speed, framepos_t& pos) else if (engine_dll_initstate != transport_direction && ltc_speed != 0) { engine_dll_initstate = transport_direction; init_engine_dll(last_ltc_frame + rint(ltc_speed * double(2 * nframes + now - last_timestamp)), - session.engine().frames_per_cycle()); + session.engine().samples_per_cycle()); engine_init_called = true; } @@ -521,8 +521,8 @@ LTC_Slave::speed_and_position (double& speed, framepos_t& pos) t0 = t1; t1 += b * e + e2; e2 += c * e; - speed_flt = (t1 - t0) / double(session.engine().frames_per_cycle()); - DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", t0, t1, e, speed_flt, e2 - session.engine().frames_per_cycle() )); + speed_flt = (t1 - t0) / double(session.engine().samples_per_cycle()); + DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", t0, t1, e, speed_flt, e2 - session.engine().samples_per_cycle() )); } else { DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC adjusting elapsed (no DLL) from %1 by %2\n", elapsed, (2 * nframes * ltc_speed))); speed_flt = 0; diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc index 30134a5dbb..6288616824 100644 --- a/libs/ardour/lv2_plugin.cc +++ b/libs/ardour/lv2_plugin.cc @@ -291,7 +291,7 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) _latency_control_port = 0; _next_cycle_start = std::numeric_limits<framepos_t>::max(); _next_cycle_speed = 1.0; - _block_length = _engine.frames_per_cycle(); + _block_length = _engine.samples_per_cycle(); _seq_size = _engine.raw_buffer_size(DataType::MIDI); _state_version = 0; _was_activated = false; @@ -1925,7 +1925,7 @@ LV2Plugin::Impl::designated_input (const char* uri, void** bufptrs[], void** buf return port; } -static bool lv2_filter (const string& str, void * /* arg*/) +static bool lv2_filter (const string& str, void* /*arg*/) { /* Not a dotfile, has a prefix before a period, suffix is "lv2" */ diff --git a/libs/ardour/midi_buffer.cc b/libs/ardour/midi_buffer.cc index b47c3ca152..d75b861ea1 100644 --- a/libs/ardour/midi_buffer.cc +++ b/libs/ardour/midi_buffer.cc @@ -22,6 +22,7 @@ #include "pbd/malign.h" #include "pbd/compose.h" #include "pbd/debug.h" +#include "pbd/stacktrace.h" #include "ardour/debug.h" #include "ardour/midi_buffer.h" @@ -133,6 +134,7 @@ MidiBuffer::push_back(const Evoral::MIDIEvent<TimeType>& ev) if (_size + stamp_size + ev.size() >= _capacity) { cerr << "MidiBuffer::push_back failed (buffer is full)" << endl; + PBD::stacktrace (cerr, 20); return false; } @@ -171,7 +173,9 @@ MidiBuffer::push_back(TimeType time, size_t size, const uint8_t* data) #endif if (_size + stamp_size + size >= _capacity) { - cerr << "MidiBuffer::push_back failed (buffer is full)" << endl; + cerr << "MidiBuffer::push_back2 failed (buffer is full; _size = " << _size << " capacity " + << _capacity << " stamp " << stamp_size << " size = " << size << ")" << endl; + PBD::stacktrace (cerr, 20); return false; } @@ -190,55 +194,6 @@ MidiBuffer::push_back(TimeType time, size_t size, const uint8_t* data) return true; } - -/** Push an event into the buffer. - * - * Note that the raw MIDI pointed to by ev will be COPIED and unmodified. - * That is, the caller still owns it, if it needs freeing it's Not My Problem(TM). - * Realtime safe. - * @return false if operation failed (not enough room) - */ -bool -MidiBuffer::push_back(const jack_midi_event_t& ev) -{ - const size_t stamp_size = sizeof(TimeType); - - if (_size + stamp_size + ev.size >= _capacity) { - cerr << "MidiBuffer::push_back failed (buffer is full)" << endl; - return false; - } - - if (!Evoral::midi_event_is_valid(ev.buffer, ev.size)) { - cerr << "WARNING: MidiBuffer ignoring illegal MIDI event" << endl; - return false; - } - -#ifndef NDEBUG - if (DEBUG::MidiIO & PBD::debug_bits) { - DEBUG_STR_DECL(a); - DEBUG_STR_APPEND(a, string_compose ("midibuffer %1 push jack event @ %2 sz %3 ", this, ev.time, ev.size)); - for (size_t i=0; i < ev.size; ++i) { - DEBUG_STR_APPEND(a,hex); - DEBUG_STR_APPEND(a,"0x"); - DEBUG_STR_APPEND(a,(int)ev.buffer[i]); - DEBUG_STR_APPEND(a,' '); - } - DEBUG_STR_APPEND(a,'\n'); - DEBUG_TRACE (DEBUG::MidiIO, DEBUG_STR(a).str()); - } -#endif - - uint8_t* const write_loc = _data + _size; - *((TimeType*)write_loc) = ev.time; - memcpy(write_loc + stamp_size, ev.buffer, ev.size); - - _size += stamp_size + ev.size; - _silent = false; - - return true; -} - - /** Reserve space for a new event in the buffer. * * This call is for copying MIDI directly into the buffer, the data location diff --git a/libs/ardour/midi_clock_slave.cc b/libs/ardour/midi_clock_slave.cc index 75f3c13f1c..a52854d954 100644 --- a/libs/ardour/midi_clock_slave.cc +++ b/libs/ardour/midi_clock_slave.cc @@ -30,6 +30,8 @@ #include "midi++/port.h" #include "ardour/debug.h" +#include "ardour/midi_buffer.h" +#include "ardour/midi_port.h" #include "ardour/slave.h" #include "ardour/tempo.h" @@ -40,7 +42,7 @@ using namespace ARDOUR; using namespace MIDI; using namespace PBD; -MIDIClock_Slave::MIDIClock_Slave (Session& s, MIDI::Port& p, int ppqn) +MIDIClock_Slave::MIDIClock_Slave (Session& s, MidiPort& p, int ppqn) : ppqn (ppqn) , bandwidth (10.0 / 60.0) // 1 BpM = 1 / 60 Hz { @@ -63,19 +65,18 @@ MIDIClock_Slave::~MIDIClock_Slave() } void -MIDIClock_Slave::rebind (MIDI::Port& p) +MIDIClock_Slave::rebind (MidiPort& port) { - port_connections.drop_connections(); + DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_Slave: connecting to port %1\n", port.name())); - port = &p; + port_connections.drop_connections (); - DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_Slave: connecting to port %1\n", port->name())); + port.self_parser().timing.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::update_midi_clock, this, _1, _2)); + port.self_parser().start.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::start, this, _1, _2)); + port.self_parser().contineu.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::contineu, this, _1, _2)); + port.self_parser().stop.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::stop, this, _1, _2)); + port.self_parser().position.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::position, this, _1, _2, 3)); - port->parser()->timing.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::update_midi_clock, this, _1, _2)); - port->parser()->start.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::start, this, _1, _2)); - port->parser()->contineu.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::contineu, this, _1, _2)); - port->parser()->stop.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::stop, this, _1, _2)); - port->parser()->position.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::position, this, _1, _2, 3)); } void diff --git a/libs/ardour/midi_diskstream.cc b/libs/ardour/midi_diskstream.cc index cf143bf595..39932db8bf 100644 --- a/libs/ardour/midi_diskstream.cc +++ b/libs/ardour/midi_diskstream.cc @@ -201,8 +201,8 @@ MidiDiskstream::non_realtime_input_change () seek (_session.transport_frame()); } - g_atomic_int_set(&_frames_pending_write, 0); - g_atomic_int_set(&_num_captured_loops, 0); + g_atomic_int_set(const_cast<gint*> (&_frames_pending_write), 0); + g_atomic_int_set(const_cast<gint*> (&_num_captured_loops), 0); } int @@ -375,8 +375,8 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t } _write_source->mark_write_starting_now( capture_start_frame, capture_captured, loop_length); - g_atomic_int_set(&_frames_pending_write, 0); - g_atomic_int_set(&_num_captured_loops, 0); + g_atomic_int_set(const_cast<gint*> (&_frames_pending_write), 0); + g_atomic_int_set(const_cast<gint*> (&_num_captured_loops), 0); was_recording = true; } } @@ -445,7 +445,7 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t break; } } - g_atomic_int_add(&_frames_pending_write, nframes); + g_atomic_int_add(const_cast<gint*> (&_frames_pending_write), nframes); if (buf.size() != 0) { Glib::Threads::Mutex::Lock lm (_gui_feed_buffer_mutex, Glib::Threads::TRY_LOCK); @@ -798,7 +798,7 @@ MidiDiskstream::do_flush (RunContext /*context*/, bool force_flush) return 0; } - const framecnt_t total = g_atomic_int_get(&_frames_pending_write); + const framecnt_t total = g_atomic_int_get(const_cast<gint*> (&_frames_pending_write)); if (total == 0 || _capture_buf->read_space() == 0 || @@ -833,7 +833,7 @@ MidiDiskstream::do_flush (RunContext /*context*/, bool force_flush) error << string_compose(_("MidiDiskstream %1: cannot write to disk"), id()) << endmsg; return -1; } - g_atomic_int_add(&_frames_pending_write, -to_write); + g_atomic_int_add(const_cast<gint*> (&_frames_pending_write), -to_write); } out: @@ -1043,7 +1043,7 @@ MidiDiskstream::transport_looped (framepos_t) the Source and/or entirely after the capture is finished. */ if (was_recording) { - g_atomic_int_add(&_num_captured_loops, 1); + g_atomic_int_add(const_cast<gint*> (&_num_captured_loops), 1); } } @@ -1110,7 +1110,7 @@ MidiDiskstream::prep_record_enable () boost::shared_ptr<MidiPort> sp = _source_port.lock (); if (sp && Config->get_monitoring_model() == HardwareMonitoring) { - sp->request_jack_monitors_input (!(_session.config.get_auto_input() && rolling)); + sp->request_input_monitoring (!(_session.config.get_auto_input() && rolling)); } return true; @@ -1258,12 +1258,12 @@ MidiDiskstream::allocate_temporary_buffers () } void -MidiDiskstream::ensure_jack_monitors_input (bool yn) +MidiDiskstream::ensure_input_monitoring (bool yn) { boost::shared_ptr<MidiPort> sp = _source_port.lock (); if (sp) { - sp->ensure_jack_monitors_input (yn); + sp->ensure_input_monitoring (yn); } } diff --git a/libs/ardour/midi_port.cc b/libs/ardour/midi_port.cc index 72150f8cfc..de2263fad6 100644 --- a/libs/ardour/midi_port.cc +++ b/libs/ardour/midi_port.cc @@ -26,11 +26,14 @@ using namespace ARDOUR; using namespace std; -MidiPort::MidiPort (const std::string& name, Flags flags) +#define port_engine AudioEngine::instance()->port_engine() + +MidiPort::MidiPort (const std::string& name, PortFlags flags) : Port (name, DataType::MIDI, flags) , _has_been_mixed_down (false) , _resolve_required (false) , _input_active (true) + , _always_parse (false) { _buffer = new MidiBuffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI)); } @@ -43,12 +46,32 @@ MidiPort::~MidiPort() void MidiPort::cycle_start (pframes_t nframes) { + framepos_t now = AudioEngine::instance()->sample_time_at_cycle_start(); + Port::cycle_start (nframes); _buffer->clear (); if (sends_output ()) { - jack_midi_clear_buffer (jack_port_get_buffer (_jack_port, nframes)); + port_engine.midi_clear (port_engine.get_buffer (_port_handle, nframes)); + } + + if (_always_parse) { + MidiBuffer& mb (get_midi_buffer (nframes)); + + /* dump incoming MIDI to parser */ + + for (MidiBuffer::iterator b = mb.begin(); b != mb.end(); ++b) { + uint8_t* buf = (*b).buffer(); + + _self_parser.set_timestamp (now + (*b).time()); + + uint32_t limit = (*b).size(); + + for (size_t n = 0; n < limit; ++n) { + _self_parser.scanner (buf[n]); + } + } } } @@ -63,31 +86,33 @@ MidiPort::get_midi_buffer (pframes_t nframes) if (_input_active) { - void* jack_buffer = jack_port_get_buffer (_jack_port, nframes); - const pframes_t event_count = jack_midi_get_event_count (jack_buffer); - - /* suck all relevant MIDI events from the JACK MIDI port buffer + void* buffer = port_engine.get_buffer (_port_handle, nframes); + const pframes_t event_count = port_engine.get_midi_event_count (buffer); + + /* suck all relevant MIDI events from the MIDI port buffer into our MidiBuffer */ - + for (pframes_t i = 0; i < event_count; ++i) { - jack_midi_event_t ev; + pframes_t timestamp; + size_t size; + uint8_t* buf; - jack_midi_event_get (&ev, jack_buffer, i); + port_engine.midi_event_get (timestamp, size, &buf, buffer, i); - if (ev.buffer[0] == 0xfe) { + if (buf[0] == 0xfe) { /* throw away active sensing */ continue; } /* check that the event is in the acceptable time range */ - if ((ev.time >= (_global_port_buffer_offset + _port_buffer_offset)) && - (ev.time < (_global_port_buffer_offset + _port_buffer_offset + nframes))) { - _buffer->push_back (ev); + if ((timestamp >= (_global_port_buffer_offset + _port_buffer_offset)) && + (timestamp < (_global_port_buffer_offset + _port_buffer_offset + nframes))) { + _buffer->push_back (timestamp, size, buf); } else { - cerr << "Dropping incoming MIDI at time " << ev.time << "; offset=" + cerr << "Dropping incoming MIDI at time " << timestamp << "; offset=" << _global_port_buffer_offset << " limit=" << (_global_port_buffer_offset + _port_buffer_offset + nframes) << "\n"; } @@ -121,7 +146,7 @@ MidiPort::cycle_split () } void -MidiPort::resolve_notes (void* jack_buffer, MidiBuffer::TimeType when) +MidiPort::resolve_notes (void* port_buffer, MidiBuffer::TimeType when) { for (uint8_t channel = 0; channel <= 0xF; channel++) { @@ -132,13 +157,13 @@ MidiPort::resolve_notes (void* jack_buffer, MidiBuffer::TimeType when) * that prioritize sustain over AllNotesOff */ - if (jack_midi_event_write (jack_buffer, when, ev, 3) != 0) { + if (port_engine.midi_event_put (port_buffer, when, ev, 3) != 0) { cerr << "failed to deliver sustain-zero on channel " << channel << " on port " << name() << endl; } ev[1] = MIDI_CTL_ALL_NOTES_OFF; - if (jack_midi_event_write (jack_buffer, 0, ev, 3) != 0) { + if (port_engine.midi_event_put (port_buffer, 0, ev, 3) != 0) { cerr << "failed to deliver ALL NOTES OFF on channel " << channel << " on port " << name() << endl; } } @@ -149,11 +174,11 @@ MidiPort::flush_buffers (pframes_t nframes) { if (sends_output ()) { - void* jack_buffer = jack_port_get_buffer (_jack_port, nframes); - + void* port_buffer = port_engine.get_buffer (_port_handle, nframes); + if (_resolve_required) { /* resolve all notes at the start of the buffer */ - resolve_notes (jack_buffer, 0); + resolve_notes (port_buffer, 0); _resolve_required = false; } @@ -166,7 +191,7 @@ MidiPort::flush_buffers (pframes_t nframes) assert (ev.time() < (nframes + _global_port_buffer_offset + _port_buffer_offset)); if (ev.time() >= _global_port_buffer_offset + _port_buffer_offset) { - if (jack_midi_event_write (jack_buffer, (jack_nframes_t) ev.time(), ev.buffer(), ev.size()) != 0) { + if (port_engine.midi_event_put (port_buffer, (pframes_t) ev.time(), ev.buffer(), ev.size()) != 0) { cerr << "write failed, drop flushed note off on the floor, time " << ev.time() << " > " << _global_port_buffer_offset + _port_buffer_offset << endl; } @@ -202,6 +227,7 @@ MidiPort::reset () { Port::reset (); delete _buffer; + cerr << name() << " new MIDI buffer of size " << AudioEngine::instance()->raw_buffer_size (DataType::MIDI) << endl; _buffer = new MidiBuffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI)); } @@ -210,3 +236,9 @@ MidiPort::set_input_active (bool yn) { _input_active = yn; } + +void +MidiPort::set_always_parse (bool yn) +{ + _always_parse = yn; +} diff --git a/libs/ardour/midi_ui.cc b/libs/ardour/midi_ui.cc index 5822cc509e..e6d49175bd 100644 --- a/libs/ardour/midi_ui.cc +++ b/libs/ardour/midi_ui.cc @@ -22,11 +22,11 @@ #include "pbd/pthread_utils.h" -#include "midi++/manager.h" -#include "midi++/port.h" - +#include "ardour/async_midi_port.h" #include "ardour/debug.h" #include "ardour/audioengine.h" +#include "ardour/midi_port.h" +#include "ardour/midiport_manager.h" #include "ardour/midi_ui.h" #include "ardour/session.h" #include "ardour/session_event.h" @@ -48,7 +48,6 @@ MidiControlUI::MidiControlUI (Session& s) : AbstractUI<MidiUIRequest> (X_("midiui")) , _session (s) { - MIDI::Manager::instance()->PortsChanged.connect_same_thread (rebind_connection, boost::bind (&MidiControlUI::change_midi_ports, this)); _instance = this; } @@ -83,20 +82,10 @@ MidiControlUI::do_request (MidiUIRequest* req) } } -void -MidiControlUI::change_midi_ports () -{ - MidiUIRequest* req = get_request (PortChange); - if (req == 0) { - return; - } - send_request (req); -} - bool -MidiControlUI::midi_input_handler (IOCondition ioc, MIDI::Port* port) +MidiControlUI::midi_input_handler (IOCondition ioc, AsyncMIDIPort* port) { - DEBUG_TRACE (DEBUG::MidiIO, string_compose ("something happend on %1\n", port->name())); + DEBUG_TRACE (DEBUG::MidiIO, string_compose ("something happend on %1\n", ((ARDOUR::Port*)port)->name())); if (ioc & ~IO_IN) { return false; @@ -108,8 +97,8 @@ MidiControlUI::midi_input_handler (IOCondition ioc, MIDI::Port* port) CrossThreadChannel::drain (port->selectable()); #endif - DEBUG_TRACE (DEBUG::MidiIO, string_compose ("data available on %1\n", port->name())); - framepos_t now = _session.engine().frame_time(); + DEBUG_TRACE (DEBUG::MidiIO, string_compose ("data available on %1\n", ((ARDOUR::Port*)port)->name())); + framepos_t now = _session.engine().sample_time(); port->parse (now); } @@ -130,22 +119,19 @@ MidiControlUI::clear_ports () void MidiControlUI::reset_ports () { - clear_ports (); - - boost::shared_ptr<const MIDI::Manager::PortList> plist = MIDI::Manager::instance()->get_midi_ports (); + if (port_sources.empty()) { + AsyncMIDIPort* async = dynamic_cast<AsyncMIDIPort*> (_session.midi_input_port()); - for (MIDI::Manager::PortList::const_iterator i = plist->begin(); i != plist->end(); ++i) { - - if (!(*i)->centrally_parsed()) { - continue; + if (!async) { + return; } int fd; - if ((fd = (*i)->selectable ()) >= 0) { + if ((fd = async->selectable ()) >= 0) { Glib::RefPtr<IOSource> psrc = IOSource::create (fd, IO_IN|IO_HUP|IO_ERR); - - psrc->connect (sigc::bind (sigc::mem_fun (this, &MidiControlUI::midi_input_handler), *i)); + + psrc->connect (sigc::bind (sigc::mem_fun (this, &MidiControlUI::midi_input_handler), async)); psrc->attach (_main_loop->get_context()); // glibmm hack: for now, store only the GSource* diff --git a/libs/ardour/midiport_manager.cc b/libs/ardour/midiport_manager.cc new file mode 100644 index 0000000000..d17401cee8 --- /dev/null +++ b/libs/ardour/midiport_manager.cc @@ -0,0 +1,170 @@ +/* + Copyright (C) 1998-99 Paul Barton-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. + + $Id$ +*/ + +#include "ardour/audioengine.h" +#include "ardour/async_midi_port.h" +#include "ardour/midiport_manager.h" +#include "ardour/rc_configuration.h" + +#include "i18n.h" + +using namespace ARDOUR; +using namespace std; +using namespace MIDI; +using namespace PBD; + + +MidiPortManager::MidiPortManager () +{ + create_ports (); +} + +MidiPortManager::~MidiPortManager () +{ + if (_midi_in) { + AudioEngine::instance()->unregister_port (_midi_in); + } + if (_midi_in) { + AudioEngine::instance()->unregister_port (_midi_in); + } + if (_mtc_input_port) { + AudioEngine::instance()->unregister_port (_mtc_input_port); + } + if (_mtc_output_port) { + AudioEngine::instance()->unregister_port (_mtc_output_port); + } + if (_midi_clock_input_port) { + AudioEngine::instance()->unregister_port (_midi_clock_input_port); + } + if (_midi_clock_output_port) { + AudioEngine::instance()->unregister_port (_midi_clock_output_port); + } + +} + +void +MidiPortManager::create_ports () +{ + /* this method is idempotent + */ + + if (_midi_in) { + return; + } + + _midi_in = AudioEngine::instance()->register_input_port (DataType::MIDI, _("MIDI control in"), true); + _midi_out = AudioEngine::instance()->register_output_port (DataType::MIDI, _("MIDI control out"), true); + + _mmc_in = AudioEngine::instance()->register_input_port (DataType::MIDI, _("MMC in"), true); + _mmc_out = AudioEngine::instance()->register_output_port (DataType::MIDI, _("MMC out"), true); + + /* XXX nasty type conversion needed because of the mixed inheritance + * required to integrate MIDI::IPMidiPort and ARDOUR::AsyncMIDIPort. + * + * At some point, we'll move IPMidiPort into Ardour and make it + * inherit from ARDOUR::MidiPort not MIDI::Port, and then this + * mess can go away + */ + + _midi_input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_midi_in).get(); + _midi_output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_midi_out).get(); + + _mmc_input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_mmc_in).get(); + _mmc_output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_mmc_out).get(); + + /* Now register ports used for sync (MTC and MIDI Clock) + */ + + boost::shared_ptr<ARDOUR::Port> p; + + p = AudioEngine::instance()->register_input_port (DataType::MIDI, _("MTC in")); + _mtc_input_port = boost::dynamic_pointer_cast<MidiPort> (p); + p = AudioEngine::instance()->register_output_port (DataType::MIDI, _("MTC out")); + _mtc_output_port= boost::dynamic_pointer_cast<MidiPort> (p); + + p = AudioEngine::instance()->register_input_port (DataType::MIDI, _("MIDI Clock in")); + _midi_clock_input_port = boost::dynamic_pointer_cast<MidiPort> (p); + p = AudioEngine::instance()->register_output_port (DataType::MIDI, _("MIDI Clock out")); + _midi_clock_output_port= boost::dynamic_pointer_cast<MidiPort> (p); + + /* These ports all need their incoming data handled in + * Port::cycle_start() and so ... + */ + + _mtc_input_port->set_always_parse (true); + _mtc_output_port->set_always_parse (true); + _midi_clock_input_port->set_always_parse (true); + _midi_clock_output_port->set_always_parse (true); +} + +void +MidiPortManager::set_midi_port_states (const XMLNodeList&nodes) +{ + XMLProperty* prop; + typedef map<std::string,boost::shared_ptr<Port> > PortMap; + PortMap ports; + const int version = 0; + + ports.insert (make_pair (_mtc_input_port->name(), _mtc_input_port)); + ports.insert (make_pair (_mtc_output_port->name(), _mtc_output_port)); + ports.insert (make_pair (_midi_clock_input_port->name(), _midi_clock_input_port)); + ports.insert (make_pair (_midi_clock_output_port->name(), _midi_clock_output_port)); + ports.insert (make_pair (_midi_input_port->name(), _midi_in)); + ports.insert (make_pair (_midi_output_port->name(), _midi_out)); + ports.insert (make_pair (_mmc_input_port->name(), _mmc_in)); + ports.insert (make_pair (_mmc_output_port->name(), _mmc_out)); + + for (XMLNodeList::const_iterator n = nodes.begin(); n != nodes.end(); ++n) { + if ((prop = (*n)->property (X_("name"))) == 0) { + continue; + } + + PortMap::iterator p = ports.find (prop->value()); + if (p == ports.end()) { + continue; + } + + p->second->set_state (**n, version); + } +} + +list<XMLNode*> +MidiPortManager::get_midi_port_states () const +{ + typedef map<std::string,boost::shared_ptr<Port> > PortMap; + PortMap ports; + list<XMLNode*> s; + + ports.insert (make_pair (_mtc_input_port->name(), _mtc_input_port)); + ports.insert (make_pair (_mtc_output_port->name(), _mtc_output_port)); + ports.insert (make_pair (_midi_clock_input_port->name(), _midi_clock_input_port)); + ports.insert (make_pair (_midi_clock_output_port->name(), _midi_clock_output_port)); + ports.insert (make_pair (_midi_input_port->name(), _midi_in)); + ports.insert (make_pair (_midi_output_port->name(), _midi_out)); + ports.insert (make_pair (_mmc_input_port->name(), _mmc_in)); + ports.insert (make_pair (_mmc_output_port->name(), _mmc_out)); + + for (PortMap::const_iterator p = ports.begin(); p != ports.end(); ++p) { + s.push_back (&p->second->get_state()); + } + + return s; +} + + diff --git a/libs/ardour/mtc_slave.cc b/libs/ardour/mtc_slave.cc index c41c6d6c94..2c351ab68a 100644 --- a/libs/ardour/mtc_slave.cc +++ b/libs/ardour/mtc_slave.cc @@ -25,11 +25,12 @@ #include "pbd/error.h" #include "pbd/pthread_utils.h" -#include "midi++/port.h" +#include "ardour/audioengine.h" #include "ardour/debug.h" -#include "ardour/slave.h" +#include "ardour/midi_buffer.h" +#include "ardour/midi_port.h" #include "ardour/session.h" -#include "ardour/audioengine.h" +#include "ardour/slave.h" #include <glibmm/timer.h> @@ -50,8 +51,9 @@ using namespace Timecode; */ const int MTC_Slave::frame_tolerance = 2; -MTC_Slave::MTC_Slave (Session& s, MIDI::Port& p) +MTC_Slave::MTC_Slave (Session& s, MidiPort& p) : session (s) + , port (&p) { can_notify_on_unknown_rate = true; did_reset_tc_format = false; @@ -72,7 +74,10 @@ MTC_Slave::MTC_Slave (Session& s, MIDI::Port& p) session.config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&MTC_Slave::parameter_changed, this, _1)); parse_timecode_offset(); reset (true); - rebind (p); + + port->self_parser().mtc_time.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_time, this, _1, _2, _3)); + port->self_parser().mtc_qtr.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_qtr, this, _1, _2, _3)); + port->self_parser().mtc_status.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_status, this, _1)); } MTC_Slave::~MTC_Slave() @@ -96,15 +101,12 @@ MTC_Slave::~MTC_Slave() } void -MTC_Slave::rebind (MIDI::Port& p) +MTC_Slave::rebind (MidiPort& p) { port_connections.drop_connections (); port = &p; - port->parser()->mtc_time.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_time, this, _1, _2, _3)); - port->parser()->mtc_qtr.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_qtr, this, _1, _2, _3)); - port->parser()->mtc_status.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_status, this, _1)); } void @@ -156,7 +158,8 @@ MTC_Slave::outside_window (framepos_t pos) const bool MTC_Slave::locked () const { - return port->parser()->mtc_locked() && last_inbound_frame !=0 && engine_dll_initstate !=0; + DEBUG_TRACE (DEBUG::MTC, string_compose ("locked ? %1 last %2 initstate %3\n", port->self_parser().mtc_locked(), last_inbound_frame, engine_dll_initstate)); + return port->self_parser().mtc_locked() && last_inbound_frame !=0 && engine_dll_initstate !=0; } bool @@ -257,7 +260,6 @@ MTC_Slave::init_mtc_dll(framepos_t tme, double qtr) DEBUG_TRACE (DEBUG::MTC, string_compose ("[re-]init MTC DLL %1 %2 %3\n", t0, t1, e2)); } - /* called from MIDI parser */ void MTC_Slave::update_mtc_qtr (Parser& /*p*/, int which_qtr, framepos_t now) @@ -308,7 +310,7 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, framepos_t now a locate command via MMC. */ - //DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::update_mtc_time - TID:%1\n", pthread_name())); + DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::update_mtc_time - TID:%1\n", ::pthread_self())); TimecodeFormat tc_format; bool reset_tc = true; @@ -424,7 +426,7 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, framepos_t now now, timecode, mtc_frame, was_full, speedup_due_to_tc_mismatch)); if (was_full || outside_window (mtc_frame)) { - DEBUG_TRACE (DEBUG::MTC, string_compose ("update_mtc_time: full TC or outside window. - TID:%1\n", pthread_name())); + DEBUG_TRACE (DEBUG::MTC, string_compose ("update_mtc_time: full TC %1 or outside window %2\n", was_full, outside_window (mtc_frame))); session.request_locate (mtc_frame, false); session.request_transport_speed (0); update_mtc_status (MIDI::MTC_Stopped); @@ -448,7 +450,7 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, framepos_t now DEBUG_TRACE (DEBUG::MTC, string_compose ("new mtc_frame: %1 | MTC-FpT: %2 A3-FpT:%3\n", mtc_frame, (4.0*qtr), session.frames_per_timecode_frame())); - switch (port->parser()->mtc_running()) { + switch (port->self_parser().mtc_running()) { case MTC_Backward: mtc_frame -= mtc_off; qtr *= -1.0; @@ -528,9 +530,10 @@ MTC_Slave::reset_window (framepos_t root) of acceptable MTC frames wide open. otherwise, shrink it down to just 2 video frames ahead of the window root (taking direction into account). */ + framecnt_t const d = (quarter_frame_duration * 4 * frame_tolerance); - switch (port->parser()->mtc_running()) { + switch (port->self_parser().mtc_running()) { case MTC_Forward: window_begin = root; transport_direction = 1; @@ -553,7 +556,7 @@ MTC_Slave::reset_window (framepos_t root) break; } - DEBUG_TRACE (DEBUG::MTC, string_compose ("legal MTC window now %1 .. %2\n", window_begin, window_end)); + DEBUG_TRACE (DEBUG::MTC, string_compose ("reset MTC window @ %3, now %1 .. %2\n", window_begin, window_end, root)); } void @@ -577,11 +580,11 @@ MTC_Slave::init_engine_dll (framepos_t pos, framepos_t inc) } /* main entry point from session_process.cc - * in jack_process callback context */ +xo * in process callback context */ bool MTC_Slave::speed_and_position (double& speed, framepos_t& pos) { - framepos_t now = session.engine().frame_time_at_cycle_start(); + framepos_t now = session.engine().sample_time_at_cycle_start(); framepos_t sess_pos = session.transport_frame(); // corresponds to now //sess_pos -= session.engine().frames_since_cycle_start(); @@ -591,11 +594,21 @@ MTC_Slave::speed_and_position (double& speed, framepos_t& pos) read_current (&last); + DEBUG_TRACE (DEBUG::MTC, string_compose ("speed&pos: timestamp %1 speed %2 initstate %3 dir %4 tpos %5 now %6 last-in %7\n", + last.timestamp, + last.speed, + engine_dll_initstate, + transport_direction, + sess_pos, + now, + last_inbound_frame)); + /* re-init engine DLL here when state changed (direction, first_mtc_timestamp) */ - if (last.timestamp == 0) { engine_dll_initstate = 0; } - else if (engine_dll_initstate != transport_direction && last.speed != 0) { + if (last.timestamp == 0) { + engine_dll_initstate = 0; + } else if (engine_dll_initstate != transport_direction && last.speed != 0) { engine_dll_initstate = transport_direction; - init_engine_dll(last.position, session.engine().frames_per_cycle()); + init_engine_dll(last.position, session.engine().samples_per_cycle()); engine_dll_reinitialized = true; } @@ -627,9 +640,7 @@ MTC_Slave::speed_and_position (double& speed, framepos_t& pos) /* interpolate position according to speed and time since last quarter-frame*/ if (speed_flt == 0.0f) { elapsed = 0; - } - else - { + } else { /* scale elapsed time by the current MTC speed */ elapsed = (framecnt_t) rint (speed_flt * (now - last.timestamp)); if (give_slave_full_control_over_transport_speed() && !engine_dll_reinitialized) { @@ -645,8 +656,8 @@ MTC_Slave::speed_and_position (double& speed, framepos_t& pos) te0 = te1; te1 += be * e + ee2; ee2 += ce * e; - speed_flt = (te1 - te0) / double(session.engine().frames_per_cycle()); - DEBUG_TRACE (DEBUG::MTC, string_compose ("engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", te0, te1, e, speed_flt, ee2 - session.engine().frames_per_cycle() )); + speed_flt = (te1 - te0) / double(session.engine().samples_per_cycle()); + DEBUG_TRACE (DEBUG::MTC, string_compose ("engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", te0, te1, e, speed_flt, ee2 - session.engine().samples_per_cycle() )); } } @@ -659,14 +670,13 @@ MTC_Slave::speed_and_position (double& speed, framepos_t& pos) */ if (!session.actively_recording() && speed != 0 - && ( (pos < 0) || (labs(pos - sess_pos) > 3 * session.frame_rate()) ) - ) { + && ((pos < 0) || (labs(pos - sess_pos) > 3 * session.frame_rate()))) { engine_dll_initstate = 0; queue_reset (false); } /* provide a .1% deadzone to lock the speed */ - if (fabs(speed - 1.0) <= 0.001) + if (fabs (speed - 1.0) <= 0.001) speed = 1.0; DEBUG_TRACE (DEBUG::MTC, string_compose ("MTCsync spd: %1 pos: %2 | last-pos: %3 elapsed: %4 delta: %5\n", diff --git a/libs/ardour/panner_manager.cc b/libs/ardour/panner_manager.cc index f757279d29..8611c7bc86 100644 --- a/libs/ardour/panner_manager.cc +++ b/libs/ardour/panner_manager.cc @@ -84,6 +84,7 @@ PannerManager::discover_panners () panner_discover (*i); } } + int PannerManager::panner_discover (string path) { diff --git a/libs/ardour/port.cc b/libs/ardour/port.cc index 0f28b31b57..d8a32e3467 100644 --- a/libs/ardour/port.cc +++ b/libs/ardour/port.cc @@ -32,6 +32,7 @@ #include "ardour/audioengine.h" #include "ardour/debug.h" #include "ardour/port.h" +#include "ardour/port_engine.h" #include "i18n.h" @@ -42,13 +43,19 @@ using namespace PBD; PBD::Signal2<void,boost::shared_ptr<Port>, boost::shared_ptr<Port> > Port::PostDisconnect; PBD::Signal0<void> Port::PortDrop; -AudioEngine* Port::_engine = 0; bool Port::_connecting_blocked = false; pframes_t Port::_global_port_buffer_offset = 0; pframes_t Port::_cycle_nframes = 0; +std::string Port::state_node_name = X_("Port"); + +/* a handy define to shorten what would otherwise be a needlessly verbose + * repeated phrase + */ +#define port_engine AudioEngine::instance()->port_engine() +#define port_manager AudioEngine::instance() /** @param n Port short name */ -Port::Port (std::string const & n, DataType t, Flags f) +Port::Port (std::string const & n, DataType t, PortFlags f) : _port_buffer_offset (0) , _name (n) , _flags (f) @@ -60,21 +67,17 @@ Port::Port (std::string const & n, DataType t, Flags f) _private_capture_latency.max = 0; /* Unfortunately we have to pass the DataType into this constructor so that - we can create the right kind of JACK port; aside from this we'll use the + we can create the right kind of port; aside from this we'll use the virtual function type () to establish type. */ assert (_name.find_first_of (':') == std::string::npos); - if (!_engine->connected()) { + if ((_port_handle = port_engine.register_port (_name, t, _flags)) == 0) { + cerr << "Failed to register port \"" << _name << "\", reason is unknown from here\n"; throw failed_constructor (); } - - if ((_jack_port = jack_port_register (_engine->jack (), _name.c_str (), t.to_jack_type (), _flags, 0)) == 0) { - cerr << "Failed to register JACK port \"" << _name << "\", reason is unknown from here\n"; - throw failed_constructor (); - } - + PortDrop.connect_same_thread (drop_connection, boost::bind (&Port::drop, this)); } @@ -87,11 +90,10 @@ Port::~Port () void Port::drop () { - if (_jack_port) { - if (_engine->jack ()) { - jack_port_unregister (_engine->jack (), _jack_port); - } - _jack_port = 0; + if (_port_handle) { + DEBUG_TRACE (DEBUG::Ports, string_compose ("drop handle for port %1\n", name())); + port_engine.unregister_port (_port_handle); + _port_handle = 0; } } @@ -99,18 +101,18 @@ Port::drop () bool Port::connected () const { - return (jack_port_connected (_jack_port) != 0); + return (port_engine.connected (_port_handle) != 0); } int Port::disconnect_all () { - jack_port_disconnect (_engine->jack(), _jack_port); + port_engine.disconnect_all (_port_handle); _connections.clear (); /* a cheaper, less hacky way to do boost::shared_from_this() ... */ - boost::shared_ptr<Port> pself = _engine->get_port_by_name (name()); + boost::shared_ptr<Port> pself = port_manager->get_port_by_name (name()); PostDisconnect (pself, boost::shared_ptr<Port>()); // emit signal return 0; @@ -122,48 +124,29 @@ Port::disconnect_all () bool Port::connected_to (std::string const & o) const { - if (!_engine->connected()) { - /* in some senses, this answer isn't the right one all the time, - because we know about our connections and will re-establish - them when we reconnect to JACK. - */ + if (!port_engine.connected()) { return false; } - return jack_port_connected_to (_jack_port, - _engine->make_port_name_non_relative(o).c_str ()); + return port_engine.connected_to (_port_handle, AudioEngine::instance()->make_port_name_non_relative (o)); } -/** @param o Filled in with port full names of ports that we are connected to */ int Port::get_connections (std::vector<std::string> & c) const { - int n = 0; - - if (_engine->connected()) { - const char** jc = jack_port_get_connections (_jack_port); - if (jc) { - for (int i = 0; jc[i]; ++i) { - c.push_back (jc[i]); - ++n; - } - - if (jack_free) { - jack_free (jc); - } else { - free (jc); - } - } + if (!port_engine.connected()) { + c.insert (c.end(), _connections.begin(), _connections.end()); + return c.size(); } - return n; + return port_engine.get_connections (_port_handle, c); } int Port::connect (std::string const & other) { - std::string const other_shrt = _engine->make_port_name_non_relative (other); - std::string const this_shrt = _engine->make_port_name_non_relative (_name); + std::string const other_name = AudioEngine::instance()->make_port_name_non_relative (other); + std::string const our_name = AudioEngine::instance()->make_port_name_non_relative (_name); int r = 0; @@ -172,9 +155,11 @@ Port::connect (std::string const & other) } if (sends_output ()) { - r = jack_connect (_engine->jack (), this_shrt.c_str (), other_shrt.c_str ()); + DEBUG_TRACE (DEBUG::Ports, string_compose ("Connect %1 to %2\n", our_name, other_name)); + r = port_engine.connect (our_name, other_name); } else { - r = jack_connect (_engine->jack (), other_shrt.c_str (), this_shrt.c_str()); + DEBUG_TRACE (DEBUG::Ports, string_compose ("Connect %1 to %2\n", other_name, our_name)); + r = port_engine.connect (other_name, our_name); } if (r == 0) { @@ -187,15 +172,15 @@ Port::connect (std::string const & other) int Port::disconnect (std::string const & other) { - std::string const other_fullname = _engine->make_port_name_non_relative (other); - std::string const this_fullname = _engine->make_port_name_non_relative (_name); + std::string const other_fullname = port_manager->make_port_name_non_relative (other); + std::string const this_fullname = port_manager->make_port_name_non_relative (_name); int r = 0; if (sends_output ()) { - r = jack_disconnect (_engine->jack (), this_fullname.c_str (), other_fullname.c_str ()); + r = port_engine.disconnect (this_fullname, other_fullname); } else { - r = jack_disconnect (_engine->jack (), other_fullname.c_str (), this_fullname.c_str ()); + r = port_engine.disconnect (other_fullname, this_fullname); } if (r == 0) { @@ -204,8 +189,8 @@ Port::disconnect (std::string const & other) /* a cheaper, less hacky way to do boost::shared_from_this() ... */ - boost::shared_ptr<Port> pself = _engine->get_port_by_name (name()); - boost::shared_ptr<Port> pother = _engine->get_port_by_name (other); + boost::shared_ptr<Port> pself = AudioEngine::instance()->get_port_by_name (name()); + boost::shared_ptr<Port> pother = AudioEngine::instance()->get_port_by_name (other); if (pself && pother) { /* Disconnecting from another Ardour port: need to allow @@ -238,21 +223,22 @@ Port::disconnect (Port* o) } void -Port::set_engine (AudioEngine* e) +Port::request_input_monitoring (bool yn) { - _engine = e; + port_engine.request_input_monitoring (_port_handle, yn); } void -Port::ensure_jack_monitors_input (bool yn) +Port::ensure_input_monitoring (bool yn) { - jack_port_ensure_monitor (_jack_port, yn); + port_engine.ensure_input_monitoring (_port_handle, yn); } bool -Port::jack_monitoring_input () const +Port::monitoring_input () const { - return jack_port_monitoring_input (_jack_port); + + return port_engine.monitoring_input (_port_handle); } void @@ -274,28 +260,23 @@ Port::increment_port_buffer_offset (pframes_t nframes) } void -Port::set_public_latency_range (jack_latency_range_t& range, bool playback) const +Port::set_public_latency_range (LatencyRange& range, bool playback) const { - /* this sets the visible latency that the rest of JACK sees. because we do latency - compensation, all (most) of our visible port latency values are identical. + /* this sets the visible latency that the rest of the port system + sees. because we do latency compensation, all (most) of our visible + port latency values are identical. */ - if (!jack_port_set_latency_range) { - return; - } - DEBUG_TRACE (DEBUG::Latency, string_compose ("SET PORT %1 %4 PUBLIC latency now [%2 - %3]\n", name(), range.min, range.max, (playback ? "PLAYBACK" : "CAPTURE")));; - jack_port_set_latency_range (_jack_port, - (playback ? JackPlaybackLatency : JackCaptureLatency), - &range); + port_engine.set_latency_range (_port_handle, playback, range); } void -Port::set_private_latency_range (jack_latency_range_t& range, bool playback) +Port::set_private_latency_range (LatencyRange& range, bool playback) { if (playback) { _private_playback_latency = range; @@ -313,12 +294,12 @@ Port::set_private_latency_range (jack_latency_range_t& range, bool playback) _private_capture_latency.max)); } - /* push to public (JACK) location so that everyone else can see it */ + /* push to public (port system) location so that everyone else can see it */ set_public_latency_range (range, playback); } -const jack_latency_range_t& +const LatencyRange& Port::private_latency_range (bool playback) const { if (playback) { @@ -338,14 +319,13 @@ Port::private_latency_range (bool playback) const } } -jack_latency_range_t +LatencyRange Port::public_latency_range (bool /*playback*/) const { - jack_latency_range_t r; + LatencyRange r; + + r = port_engine.get_latency_range (_port_handle, sends_output() ? true : false); - jack_port_get_latency_range (_jack_port, - sends_output() ? JackPlaybackLatency : JackCaptureLatency, - &r); DEBUG_TRACE (DEBUG::Latency, string_compose ( "GET PORT %1: %4 PUBLIC latency range %2 .. %3\n", name(), r.min, r.max, @@ -354,27 +334,15 @@ Port::public_latency_range (bool /*playback*/) const } void -Port::get_connected_latency_range (jack_latency_range_t& range, bool playback) const +Port::get_connected_latency_range (LatencyRange& range, bool playback) const { - if (!jack_port_get_latency_range) { - return; - } - vector<string> connections; - jack_client_t* jack = _engine->jack(); - - if (!jack) { - range.min = 0; - range.max = 0; - PBD::warning << _("get_connected_latency_range() called while disconnected from JACK") << endmsg; - return; - } get_connections (connections); if (!connections.empty()) { - range.min = ~((jack_nframes_t) 0); + range.min = ~((pframes_t) 0); range.max = 0; DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: %2 connections to check for latency range\n", name(), connections.size())); @@ -382,21 +350,18 @@ Port::get_connected_latency_range (jack_latency_range_t& range, bool playback) c for (vector<string>::const_iterator c = connections.begin(); c != connections.end(); ++c) { - jack_latency_range_t lr; + LatencyRange lr; if (!AudioEngine::instance()->port_is_mine (*c)) { - /* port belongs to some other JACK client, use - * JACK to lookup its latency information. + /* port belongs to some other port-system client, use + * the port engine to lookup its latency information. */ - jack_port_t* remote_port = jack_port_by_name (_engine->jack(), (*c).c_str()); + PortEngine::PortHandle remote_port = port_engine.get_port_by_name (*c); if (remote_port) { - jack_port_get_latency_range ( - remote_port, - (playback ? JackPlaybackLatency : JackCaptureLatency), - &lr); + lr = port_engine.get_latency_range (remote_port, playback); DEBUG_TRACE (DEBUG::Latency, string_compose ( "\t%1 <-> %2 : latter has latency range %3 .. %4\n", @@ -440,15 +405,10 @@ Port::get_connected_latency_range (jack_latency_range_t& range, bool playback) c int Port::reestablish () { - jack_client_t* jack = _engine->jack(); + DEBUG_TRACE (DEBUG::Ports, string_compose ("re-establish %1 port %2\n", type().to_string(), _name)); + _port_handle = port_engine.register_port (_name, type(), _flags); - if (!jack) { - return -1; - } - - _jack_port = jack_port_register (jack, _name.c_str(), type().to_jack_type(), _flags, 0); - - if (_jack_port == 0) { + if (_port_handle == 0) { PBD::error << string_compose (_("could not reregister %1"), _name) << endmsg; return -1; } @@ -464,6 +424,8 @@ Port::reconnect () { /* caller must hold process lock; intended to be used only after reestablish() */ + DEBUG_TRACE (DEBUG::Ports, string_compose ("Connect %1 to %2 destinations\n",name(), _connections.size())); + for (std::set<string>::iterator i = _connections.begin(); i != _connections.end(); ++i) { if (connect (*i)) { return -1; @@ -473,7 +435,7 @@ Port::reconnect () return 0; } -/** @param n Short port name (no JACK client name) */ +/** @param n Short port name (no port-system client name) */ int Port::set_name (std::string const & n) { @@ -481,10 +443,10 @@ Port::set_name (std::string const & n) return 0; } - int const r = jack_port_set_name (_jack_port, n.c_str()); + int const r = port_engine.set_port_name (_port_handle, n); if (r == 0) { - _engine->port_renamed (_name, n); + AudioEngine::instance()->port_renamed (_name, n); _name = n; } @@ -492,38 +454,67 @@ Port::set_name (std::string const & n) return r; } -void -Port::request_jack_monitors_input (bool yn) +bool +Port::physically_connected () const { - jack_port_request_monitor (_jack_port, yn); + return port_engine.physically_connected (_port_handle); } -bool -Port::physically_connected () const +XMLNode& +Port::get_state () const { - const char** jc = jack_port_get_connections (_jack_port); + XMLNode* root = new XMLNode (state_node_name); - if (jc) { - for (int i = 0; jc[i]; ++i) { + root->add_property (X_("name"), AudioEngine::instance()->make_port_name_relative (name())); - jack_port_t* port = jack_port_by_name (_engine->jack(), jc[i]); + if (receives_input()) { + root->add_property (X_("direction"), X_("input")); + } else { + root->add_property (X_("direction"), X_("output")); + } - if (port && (jack_port_flags (port) & JackPortIsPhysical)) { - if (jack_free) { - jack_free (jc); - } else { - free (jc); - } - return true; - } - } - if (jack_free) { - jack_free (jc); - } else { - free (jc); - } + vector<string> c; + + get_connections (c); + + for (vector<string>::const_iterator i = c.begin(); i != c.end(); ++i) { + XMLNode* child = new XMLNode (X_("Connection")); + child->add_property (X_("other"), *i); + root->add_child_nocopy (*child); } - return false; + return *root; } +int +Port::set_state (const XMLNode& node, int) +{ + const XMLProperty* prop; + + if (node.name() != state_node_name) { + return -1; + } + + if ((prop = node.property (X_("name"))) != 0) { + set_name (prop->value()); + } + + const XMLNodeList& children (node.children()); + + _connections.clear (); + + for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) { + + if ((*c)->name() != X_("Connection")) { + continue; + } + + if ((prop = (*c)->property (X_("other"))) == 0) { + continue; + } + + _connections.insert (prop->value()); + } + + return 0; +} diff --git a/libs/ardour/port_insert.cc b/libs/ardour/port_insert.cc index b2758b2d57..2519451b89 100644 --- a/libs/ardour/port_insert.cc +++ b/libs/ardour/port_insert.cc @@ -49,7 +49,7 @@ PortInsert::PortInsert (Session& s, boost::shared_ptr<Pannable> pannable, boost: { _mtdm = 0; _latency_detect = false; - _latency_flush_frames = false; + _latency_flush_frames = 0; _measured_latency = 0; } @@ -64,7 +64,7 @@ PortInsert::start_latency_detection () { delete _mtdm; _mtdm = new MTDM (_session.frame_rate()); - _latency_flush_frames = false; + _latency_flush_frames = 0; _latency_detect = true; _measured_latency = 0; } @@ -72,7 +72,7 @@ PortInsert::start_latency_detection () void PortInsert::stop_latency_detection () { - _latency_flush_frames = signal_latency() + _session.engine().frames_per_cycle(); + _latency_flush_frames = signal_latency() + _session.engine().samples_per_cycle(); _latency_detect = false; } @@ -93,7 +93,7 @@ PortInsert::latency() const */ if (_measured_latency == 0) { - return _session.engine().frames_per_cycle() + _input->latency(); + return _session.engine().samples_per_cycle() + _input->latency(); } else { return _measured_latency; } @@ -240,7 +240,7 @@ PortInsert::signal_latency() const */ if (_measured_latency == 0) { - return _session.engine().frames_per_cycle() + _input->signal_latency(); + return _session.engine().samples_per_cycle() + _input->signal_latency(); } else { return _measured_latency; } diff --git a/libs/ardour/port_manager.cc b/libs/ardour/port_manager.cc new file mode 100644 index 0000000000..1758ea4a3f --- /dev/null +++ b/libs/ardour/port_manager.cc @@ -0,0 +1,659 @@ +/* + Copyright (C) 2013 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. + +*/ + +#include "pbd/error.h" + +#include "ardour/async_midi_port.h" +#include "ardour/debug.h" +#include "ardour/port_manager.h" +#include "ardour/audio_port.h" +#include "ardour/midi_port.h" +#include "ardour/midiport_manager.h" + +#include "i18n.h" + +using namespace ARDOUR; +using namespace PBD; +using std::string; +using std::vector; + +PortManager::PortManager () + : ports (new Ports) + , _port_remove_in_progress (false) +{ +} + +void +PortManager::remove_all_ports () +{ + /* make sure that JACK callbacks that will be invoked as we cleanup + * ports know that they have nothing to do. + */ + + _port_remove_in_progress = true; + + /* process lock MUST be held by caller + */ + + { + RCUWriter<Ports> writer (ports); + boost::shared_ptr<Ports> ps = writer.get_copy (); + ps->clear (); + } + + /* clear dead wood list in RCU */ + + ports.flush (); + + _port_remove_in_progress = false; +} + + +string +PortManager::make_port_name_relative (const string& portname) const +{ + if (!_impl) { + return portname; + } + + string::size_type len; + string::size_type n; + string self = _impl->my_name(); + + len = portname.length(); + + for (n = 0; n < len; ++n) { + if (portname[n] == ':') { + break; + } + } + + if ((n != len) && (portname.substr (0, n) == self)) { + return portname.substr (n+1); + } + + return portname; +} + +string +PortManager::make_port_name_non_relative (const string& portname) const +{ + string str; + + if (portname.find_first_of (':') != string::npos) { + return portname; + } + + str = _impl->my_name(); + str += ':'; + str += portname; + + return str; +} + +bool +PortManager::port_is_mine (const string& portname) const +{ + if (!_impl) { + return true; + } + + string self = _impl->my_name(); + + if (portname.find_first_of (':') != string::npos) { + if (portname.substr (0, self.length ()) != self) { + return false; + } + } + + return true; +} + +bool +PortManager::port_is_physical (const std::string& portname) const +{ + if (!_impl) { + return false; + } + + PortEngine::PortHandle ph = _impl->get_port_by_name (portname); + if (!ph) { + return false; + } + + return _impl->port_is_physical (ph); +} + +void +PortManager::get_physical_outputs (DataType type, std::vector<std::string>& s) +{ + if (!_impl) { + return; + } + _impl->get_physical_outputs (type, s); +} + +void +PortManager::get_physical_inputs (DataType type, std::vector<std::string>& s) +{ + if (!_impl) { + return; + } + + _impl->get_physical_inputs (type, s); +} + +ChanCount +PortManager::n_physical_outputs () const +{ + if (!_impl) { + return ChanCount::ZERO; + } + + return _impl->n_physical_outputs (); +} + +ChanCount +PortManager::n_physical_inputs () const +{ + if (!_impl) { + return ChanCount::ZERO; + } + return _impl->n_physical_inputs (); +} + +/** @param name Full or short name of port + * @return Corresponding Port or 0. + */ + +boost::shared_ptr<Port> +PortManager::get_port_by_name (const string& portname) +{ + if (!_impl) { + return boost::shared_ptr<Port>(); + } + + if (!port_is_mine (portname)) { + /* not an ardour port */ + return boost::shared_ptr<Port> (); + } + + boost::shared_ptr<Ports> pr = ports.reader(); + std::string rel = make_port_name_relative (portname); + Ports::iterator x = pr->find (rel); + + if (x != pr->end()) { + /* its possible that the port was renamed by some 3rd party and + we don't know about it. check for this (the check is quick + and cheap), and if so, rename the port (which will alter + the port map as a side effect). + */ + const std::string check = make_port_name_relative (_impl->get_port_name (x->second->port_handle())); + if (check != rel) { + x->second->set_name (check); + } + return x->second; + } + + return boost::shared_ptr<Port> (); +} + +void +PortManager::port_renamed (const std::string& old_relative_name, const std::string& new_relative_name) +{ + RCUWriter<Ports> writer (ports); + boost::shared_ptr<Ports> p = writer.get_copy(); + Ports::iterator x = p->find (old_relative_name); + + if (x != p->end()) { + boost::shared_ptr<Port> port = x->second; + p->erase (x); + p->insert (make_pair (new_relative_name, port)); + } +} + +int +PortManager::get_ports (DataType type, PortList& pl) +{ + boost::shared_ptr<Ports> plist = ports.reader(); + for (Ports::iterator p = plist->begin(); p != plist->end(); ++p) { + if (p->second->type() == type) { + pl.push_back (p->second); + } + } + return pl.size(); +} + +int +PortManager::get_ports (const string& port_name_pattern, DataType type, PortFlags flags, vector<string>& s) +{ + if (!_impl) { + return 0; + } + + return _impl->get_ports (port_name_pattern, type, flags, s); +} + +void +PortManager::port_registration_failure (const std::string& portname) +{ + if (!_impl) { + return; + } + + string full_portname = _impl->my_name(); + full_portname += ':'; + full_portname += portname; + + + PortEngine::PortHandle p = _impl->get_port_by_name (full_portname); + string reason; + + if (p) { + reason = string_compose (_("a port with the name \"%1\" already exists: check for duplicated track/bus names"), portname); + } else { + reason = string_compose (_("No more ports are available. You will need to stop %1 and restart with more ports if you need this many tracks."), PROGRAM_NAME); + } + + throw PortRegistrationFailure (string_compose (_("AudioEngine: cannot register port \"%1\": %2"), portname, reason).c_str()); +} + +boost::shared_ptr<Port> +PortManager::register_port (DataType dtype, const string& portname, bool input, bool async) +{ + boost::shared_ptr<Port> newport; + + try { + if (dtype == DataType::AUDIO) { + DEBUG_TRACE (DEBUG::Ports, string_compose ("registering AUDIO port %1, input %2\n", + portname, input)); + newport.reset (new AudioPort (portname, (input ? IsInput : IsOutput))); + } else if (dtype == DataType::MIDI) { + if (async) { + DEBUG_TRACE (DEBUG::Ports, string_compose ("registering ASYNC MIDI port %1, input %2\n", + portname, input)); + newport.reset (new AsyncMIDIPort (portname, (input ? IsInput : IsOutput))); + } else { + DEBUG_TRACE (DEBUG::Ports, string_compose ("registering MIDI port %1, input %2\n", + portname, input)); + newport.reset (new MidiPort (portname, (input ? IsInput : IsOutput))); + } + } else { + throw PortRegistrationFailure("unable to create port (unknown type)"); + } + + RCUWriter<Ports> writer (ports); + boost::shared_ptr<Ports> ps = writer.get_copy (); + ps->insert (make_pair (make_port_name_relative (portname), newport)); + + /* writer goes out of scope, forces update */ + + } + + catch (PortRegistrationFailure& err) { + throw err; + } catch (std::exception& e) { + throw PortRegistrationFailure(string_compose( + _("unable to create port: %1"), e.what()).c_str()); + } catch (...) { + throw PortRegistrationFailure("unable to create port (unknown error)"); + } + + DEBUG_TRACE (DEBUG::Ports, string_compose ("\t%2 port registration success, ports now = %1\n", ports.reader()->size(), this)); + return newport; +} + +boost::shared_ptr<Port> +PortManager::register_input_port (DataType type, const string& portname, bool async) +{ + return register_port (type, portname, true, async); +} + +boost::shared_ptr<Port> +PortManager::register_output_port (DataType type, const string& portname, bool async) +{ + return register_port (type, portname, false, async); +} + +int +PortManager::unregister_port (boost::shared_ptr<Port> port) +{ + /* caller must hold process lock */ + + { + RCUWriter<Ports> writer (ports); + boost::shared_ptr<Ports> ps = writer.get_copy (); + Ports::iterator x = ps->find (make_port_name_relative (port->name())); + + if (x != ps->end()) { + ps->erase (x); + } + + /* writer goes out of scope, forces update */ + } + + ports.flush (); + + return 0; +} + +bool +PortManager::connected (const string& port_name) +{ + if (!_impl) { + return false; + } + + PortEngine::PortHandle handle = _impl->get_port_by_name (port_name); + + if (!handle) { + return false; + } + + return _impl->connected (handle); +} + +int +PortManager::connect (const string& source, const string& destination) +{ + int ret; + + string s = make_port_name_non_relative (source); + string d = make_port_name_non_relative (destination); + + boost::shared_ptr<Port> src = get_port_by_name (s); + boost::shared_ptr<Port> dst = get_port_by_name (d); + + if (src) { + ret = src->connect (d); + } else if (dst) { + ret = dst->connect (s); + } else { + /* neither port is known to us ...hand-off to the PortEngine + */ + if (_impl) { + ret = _impl->connect (s, d); + } else { + ret = -1; + } + } + + if (ret > 0) { + /* already exists - no error, no warning */ + } else if (ret < 0) { + error << string_compose(_("AudioEngine: cannot connect %1 (%2) to %3 (%4)"), + source, s, destination, d) + << endmsg; + } + + return ret; +} + +int +PortManager::disconnect (const string& source, const string& destination) +{ + int ret; + + string s = make_port_name_non_relative (source); + string d = make_port_name_non_relative (destination); + + boost::shared_ptr<Port> src = get_port_by_name (s); + boost::shared_ptr<Port> dst = get_port_by_name (d); + + if (src) { + ret = src->disconnect (d); + } else if (dst) { + ret = dst->disconnect (s); + } else { + /* neither port is known to us ...hand-off to the PortEngine + */ + if (_impl) { + ret = _impl->disconnect (s, d); + } else { + ret = -1; + } + } + return ret; +} + +int +PortManager::disconnect (boost::shared_ptr<Port> port) +{ + return port->disconnect_all (); +} + +int +PortManager::reestablish_ports () +{ + Ports::iterator i; + + boost::shared_ptr<Ports> p = ports.reader (); + + DEBUG_TRACE (DEBUG::Ports, string_compose ("reestablish %1 ports\n", p->size())); + + for (i = p->begin(); i != p->end(); ++i) { + if (i->second->reestablish ()) { + error << string_compose (_("Re-establising port %1 failed"), i->second->name()) << endmsg; + cerr << string_compose (_("Re-establising port %1 failed"), i->second->name()) << endl; + break; + } + } + + if (i != p->end()) { + /* failed */ + remove_all_ports (); + return -1; + } + + return 0; +} + +int +PortManager::reconnect_ports () +{ + boost::shared_ptr<Ports> p = ports.reader (); + + /* re-establish connections */ + + DEBUG_TRACE (DEBUG::Ports, string_compose ("reconnect %1 ports\n", p->size())); + + for (Ports::iterator i = p->begin(); i != p->end(); ++i) { + i->second->reconnect (); + } + + return 0; +} + +void +PortManager::connect_callback (const string& a, const string& b, bool conn) +{ + boost::shared_ptr<Port> port_a; + boost::shared_ptr<Port> port_b; + Ports::iterator x; + boost::shared_ptr<Ports> pr = ports.reader (); + + x = pr->find (make_port_name_relative (a)); + if (x != pr->end()) { + port_a = x->second; + } + + x = pr->find (make_port_name_relative (b)); + if (x != pr->end()) { + port_b = x->second; + } + + PortConnectedOrDisconnected ( + port_a, a, + port_b, b, + conn + ); /* EMIT SIGNAL */ +} + +void +PortManager::registration_callback () +{ + if (!_port_remove_in_progress) { + PortRegisteredOrUnregistered (); /* EMIT SIGNAL */ + } +} + +bool +PortManager::can_request_input_monitoring () const +{ + if (!_impl) { + return false; + } + + return _impl->can_monitor_input (); +} + +void +PortManager::request_input_monitoring (const string& name, bool yn) const +{ + if (!_impl) { + return; + } + + PortEngine::PortHandle ph = _impl->get_port_by_name (name); + + if (ph) { + _impl->request_input_monitoring (ph, yn); + } +} + +void +PortManager::ensure_input_monitoring (const string& name, bool yn) const +{ + if (!_impl) { + return; + } + + PortEngine::PortHandle ph = _impl->get_port_by_name (name); + + if (ph) { + _impl->ensure_input_monitoring (ph, yn); + } +} + +uint32_t +PortManager::port_name_size() const +{ + if (!_impl) { + return 0; + } + + return _impl->port_name_size (); +} + +string +PortManager::my_name() const +{ + if (!_impl) { + return string(); + } + + return _impl->my_name(); +} + +int +PortManager::graph_order_callback () +{ + if (!_port_remove_in_progress) { + GraphReordered(); /* EMIT SIGNAL */ + } + + return 0; +} + +void +PortManager::cycle_start (pframes_t nframes) +{ + Port::set_global_port_buffer_offset (0); + Port::set_cycle_framecnt (nframes); + + _cycle_ports = ports.reader (); + + for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { + p->second->cycle_start (nframes); + } +} + +void +PortManager::cycle_end (pframes_t nframes) +{ + for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { + p->second->cycle_end (nframes); + } + + for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { + p->second->flush_buffers (nframes); + } + + _cycle_ports.reset (); + + /* we are done */ +} + +void +PortManager::silence (pframes_t nframes) +{ + for (Ports::iterator i = _cycle_ports->begin(); i != _cycle_ports->end(); ++i) { + if (i->second->sends_output()) { + i->second->get_buffer(nframes).silence(nframes); + } + } +} + +void +PortManager::check_monitoring () +{ + for (Ports::iterator i = _cycle_ports->begin(); i != _cycle_ports->end(); ++i) { + + bool x; + + if (i->second->last_monitor() != (x = i->second->monitoring_input ())) { + i->second->set_last_monitor (x); + /* XXX I think this is dangerous, due to + a likely mutex in the signal handlers ... + */ + i->second->MonitorInputChanged (x); /* EMIT SIGNAL */ + } + } +} + +void +PortManager::fade_out (gain_t base_gain, gain_t gain_step, pframes_t nframes) +{ + for (Ports::iterator i = _cycle_ports->begin(); i != _cycle_ports->end(); ++i) { + + if (i->second->sends_output()) { + + boost::shared_ptr<AudioPort> ap = boost::dynamic_pointer_cast<AudioPort> (i->second); + if (ap) { + Sample* s = ap->engine_get_whole_audio_buffer (); + gain_t g = base_gain; + + for (pframes_t n = 0; n < nframes; ++n) { + *s++ *= g; + g -= gain_step; + } + } + } + } +} diff --git a/libs/ardour/rc_configuration.cc b/libs/ardour/rc_configuration.cc index 4733587654..7fe030fb7e 100644 --- a/libs/ardour/rc_configuration.cc +++ b/libs/ardour/rc_configuration.cc @@ -27,11 +27,11 @@ #include "pbd/xml++.h" #include "pbd/file_utils.h" -#include "midi++/manager.h" - +#include "ardour/audioengine.h" #include "ardour/control_protocol_manager.h" #include "ardour/diskstream.h" #include "ardour/filesystem_paths.h" +#include "ardour/port.h" #include "ardour/rc_configuration.h" #include "ardour/session_metadata.h" @@ -63,13 +63,8 @@ RCConfiguration::RCConfiguration () { } - RCConfiguration::~RCConfiguration () { - for (list<XMLNode*>::iterator i = _midi_port_states.begin(); i != _midi_port_states.end(); ++i) { - delete *i; - } - delete _control_protocol_state; } @@ -177,16 +172,6 @@ RCConfiguration::get_state () root = new XMLNode("Ardour"); - MIDI::Manager* mm = MIDI::Manager::instance(); - - if (mm) { - boost::shared_ptr<const MIDI::Manager::PortList> ports = mm->get_midi_ports(); - - for (MIDI::Manager::PortList::const_iterator i = ports->begin(); i != ports->end(); ++i) { - root->add_child_nocopy((*i)->get_state()); - } - } - root->add_child_nocopy (get_variables ()); root->add_child_nocopy (SessionMetadata::Metadata()->get_user_state()); @@ -232,12 +217,6 @@ RCConfiguration::set_state (const XMLNode& root, int version) XMLNodeConstIterator niter; XMLNode *node; - for (list<XMLNode*>::iterator i = _midi_port_states.begin(); i != _midi_port_states.end(); ++i) { - delete *i; - } - - _midi_port_states.clear (); - Stateful::save_extra_xml (root); for (niter = nlist.begin(); niter != nlist.end(); ++niter) { @@ -250,8 +229,6 @@ RCConfiguration::set_state (const XMLNode& root, int version) SessionMetadata::Metadata()->set_state (*node, version); } else if (node->name() == ControlProtocolManager::state_node_name) { _control_protocol_state = new XMLNode (*node); - } else if (node->name() == MIDI::Port::state_node_name) { - _midi_port_states.push_back (new XMLNode (*node)); } } diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index 232f295df6..026ee4ec5c 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -971,7 +971,7 @@ Route::add_processor (boost::shared_ptr<Processor> processor, boost::shared_ptr< DEBUG_TRACE (DEBUG::Processors, string_compose ( "%1 adding processor %2\n", name(), processor->name())); - if (!_session.engine().connected() || !processor) { + if (!AudioEngine::instance()->connected() || !processor) { return 1; } @@ -3818,13 +3818,13 @@ Route::update_port_latencies (PortSet& from, PortSet& to, bool playback, framecn universally true, but the alternative is way too corner-case to worry about. */ - jack_latency_range_t all_connections; + LatencyRange all_connections; if (from.empty()) { all_connections.min = 0; all_connections.max = 0; } else { - all_connections.min = ~((jack_nframes_t) 0); + all_connections.min = ~((pframes_t) 0); all_connections.max = 0; /* iterate over all "from" ports and determine the latency range for all of their @@ -3833,7 +3833,7 @@ Route::update_port_latencies (PortSet& from, PortSet& to, bool playback, framecn for (PortSet::iterator p = from.begin(); p != from.end(); ++p) { - jack_latency_range_t range; + LatencyRange range; p->get_connected_latency_range (range, playback); @@ -3898,7 +3898,7 @@ Route::set_public_port_latencies (framecnt_t value, bool playback) const latency compensation into account. */ - jack_latency_range_t range; + LatencyRange range; range.min = value; range.max = value; diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 428a5e5274..135df7c2cd 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -49,6 +49,7 @@ #include "ardour/amp.h" #include "ardour/analyser.h" +#include "ardour/async_midi_port.h" #include "ardour/audio_buffer.h" #include "ardour/audio_diskstream.h" #include "ardour/audio_port.h" @@ -66,6 +67,7 @@ #include "ardour/debug.h" #include "ardour/filename_extensions.h" #include "ardour/graph.h" +#include "ardour/midiport_manager.h" #include "ardour/midi_track.h" #include "ardour/midi_ui.h" #include "ardour/operations.h" @@ -85,12 +87,11 @@ #include "ardour/session_playlists.h" #include "ardour/smf_source.h" #include "ardour/source_factory.h" +#include "ardour/speakers.h" #include "ardour/utils.h" #include "midi++/port.h" -#include "midi++/jack_midi_port.h" #include "midi++/mmc.h" -#include "midi++/manager.h" #include "i18n.h" @@ -106,6 +107,7 @@ using namespace PBD; bool Session::_disable_all_loaded_plugins = false; +PBD::Signal1<int,uint32_t> Session::AudioEngineSetupRequired; PBD::Signal1<void,std::string> Session::Dialog; PBD::Signal0<int> Session::AskAboutPendingState; PBD::Signal2<int, framecnt_t, framecnt_t> Session::AskAboutSampleRateMismatch; @@ -130,70 +132,184 @@ Session::Session (AudioEngine &eng, const string& snapshot_name, BusProfile* bus_profile, string mix_template) - : _engine (eng) + : playlists (new SessionPlaylists) + , _engine (eng) + , process_function (&Session::process_with_events) + , waiting_for_sync_offset (false) + , _base_frame_rate (0) + , _current_frame_rate (0) + , _nominal_frame_rate (0) + , transport_sub_state (0) + , _record_status (Disabled) + , _transport_frame (0) + , _session_range_location (0) + , _slave (0) + , _silent (false) + , _transport_speed (0) + , _default_transport_speed (1.0) + , _last_transport_speed (0) , _target_transport_speed (0.0) + , auto_play_legal (false) + , _last_slave_transport_frame (0) + , maximum_output_latency (0) , _requested_return_frame (-1) + , current_block_size (0) + , _worst_output_latency (0) + , _worst_input_latency (0) + , _worst_track_latency (0) + , _have_captured (false) + , _meter_hold (0) + , _meter_falloff (0) + , _non_soloed_outs_muted (false) + , _listen_cnt (0) + , _solo_isolated_cnt (0) + , _writable (false) + , _was_seamless (Config->get_seamless_loop ()) , _under_nsm_control (false) - , _session_dir (new SessionDirectory(fullpath)) + , delta_accumulator_cnt (0) + , average_slave_delta (1800) // !!! why 1800 ??? + , average_dir (0) + , have_first_delta_accumulator (false) + , _slave_state (Stopped) + , post_export_sync (false) + , post_export_position (0) + , _exporting (false) + , _export_started (false) + , _export_rolling (false) + , _pre_export_mmc_enabled (false) + , _name (snapshot_name) + , _is_new (true) + , _send_qf_mtc (false) + , _pframes_since_last_mtc (0) + , session_midi_feedback (0) + , play_loop (false) + , loop_changing (false) + , last_loopend (0) + , _session_dir (new SessionDirectory (fullpath)) + , _current_snapshot_name (snapshot_name) , state_tree (0) - , _state_of_the_state (Clean) + , state_was_pending (false) + , _state_of_the_state (StateOfTheState(CannotSave|InitialConnecting|Loading)) + , _last_roll_location (0) + , _last_roll_or_reversal_location (0) + , _last_record_location (0) + , pending_locate_roll (false) + , pending_locate_frame (0) + , pending_locate_flush (false) + , pending_abort (false) + , pending_auto_loop (false) , _butler (new Butler (*this)) , _post_transport_work (0) + , cumulative_rf_motion (0) + , rf_scale (1.0) + , _locations (new Locations (*this)) + , step_speed (0) + , outbound_mtc_timecode_frame (0) + , next_quarter_frame_to_send (-1) + , _frames_per_timecode_frame (0) + , _frames_per_hour (0) + , _timecode_frames_per_hour (0) + , last_timecode_valid (false) + , last_timecode_when (0) , _send_timecode_update (false) + , ltc_encoder (0) , ltc_enc_buf(0) + , ltc_buf_off (0) + , ltc_buf_len (0) + , ltc_speed (0) + , ltc_enc_byte (0) + , ltc_enc_pos (0) + , ltc_enc_cnt (0) + , ltc_enc_off (0) + , restarting (false) + , ltc_prev_cycle (0) + , ltc_timecode_offset (0) + , ltc_timecode_negative_offset (false) + , midi_control_ui (0) + , _tempo_map (0) , _all_route_group (new RouteGroup (*this, "all")) , routes (new RouteList) + , _adding_routes_in_progress (false) + , destructive_index (0) + , solo_update_disabled (false) + , default_fade_steepness (0) + , default_fade_msecs (0) , _total_free_4k_blocks (0) , _total_free_4k_blocks_uncertain (false) + , no_questions_about_missing_files (false) + , _playback_load (0) + , _capture_load (0) , _bundles (new BundleList) , _bundle_xml_node (0) , _current_trans (0) + , _clicking (false) , click_data (0) , click_emphasis_data (0) + , click_length (0) + , click_emphasis_length (0) + , _clicks_cleared (0) + , _play_range (false) , main_outs (0) + , first_file_data_format_reset (true) + , first_file_header_format_reset (true) + , have_looped (false) , _have_rec_enabled_track (false) + , _step_editors (0) , _suspend_timecode_transmission (0) + , _speakers (new Speakers) + , ignore_route_processor_changes (false) { - _locations = new Locations (*this); - ltc_encoder = NULL; - - if (how_many_dsp_threads () > 1) { - /* For now, only create the graph if we are using >1 DSP threads, as - it is a bit slower than the old code with 1 thread. - */ - _process_graph.reset (new Graph (*this)); - } - - playlists.reset (new SessionPlaylists); - - _all_route_group->set_active (true, this); - - interpolation.add_channel_to (0, 0); - - if (!eng.connected()) { - throw failed_constructor(); - } - - n_physical_outputs = _engine.n_physical_outputs (); - n_physical_inputs = _engine.n_physical_inputs (); - - first_stage_init (fullpath, snapshot_name); - - _is_new = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)); + uint32_t sr = 0; + pre_engine_init (fullpath); + if (_is_new) { if (create (mix_template, bus_profile)) { destroy (); throw failed_constructor (); } + } else { + if (load_state (_current_snapshot_name)) { + throw failed_constructor (); + } + + /* try to get sample rate from XML state so that we + * can influence the SR if we set up the audio + * engine. + */ + + if (state_tree) { + const XMLProperty* prop; + if ((prop = state_tree->root()->property (X_("sample-rate"))) != 0) { + sr = atoi (prop->value()); + } + } + } + + if (_engine.current_backend() == 0 || _engine.setup_required()) { + boost::optional<int> r = AudioEngineSetupRequired (sr); + if (r.get_value_or (-1) != 0) { + destroy (); + throw failed_constructor(); + } + } + + /* at this point the engine should be connected (i.e. interacting + with a backend device (or psuedo-device) and available to us + for determinining sample rates and other settings. + */ + + if (!_engine.connected()) { + destroy (); + throw failed_constructor(); } - if (second_stage_init ()) { + if (post_engine_init ()) { destroy (); throw failed_constructor (); } - store_recent_sessions(_name, _path); + store_recent_sessions (_name, _path); bool was_dirty = dirty(); @@ -210,6 +326,16 @@ Session::Session (AudioEngine &eng, EndTimeChanged.connect_same_thread (*this, boost::bind (&Session::end_time_changed, this, _1)); _is_new = false; + + /* hook us up to the engine since we are now completely constructed */ + + BootMessage (_("Connect to engine")); + + _engine.set_session (this); + _engine.reset_timebase (); + + BootMessage (_("Session loading complete")); + } Session::~Session () @@ -330,6 +456,8 @@ Session::destroy () /* not strictly necessary, but doing it here allows the shared_ptr debugging to work */ playlists.reset (); + delete _mmc; + delete _midi_ports; delete _locations; DEBUG_TRACE (DEBUG::Destruction, "Session::destroy() done\n"); @@ -340,135 +468,103 @@ Session::destroy () } void -Session::when_engine_running () +Session::setup_ltc () { - string first_physical_output; - - BootMessage (_("Set block size and sample rate")); - - set_block_size (_engine.frames_per_cycle()); - set_frame_rate (_engine.frame_rate()); - - BootMessage (_("Using configuration")); - - boost::function<void (std::string)> ff (boost::bind (&Session::config_changed, this, _1, false)); - boost::function<void (std::string)> ft (boost::bind (&Session::config_changed, this, _1, true)); - - Config->map_parameters (ff); - config.map_parameters (ft); - - /* every time we reconnect, recompute worst case output latencies */ - - _engine.Running.connect_same_thread (*this, boost::bind (&Session::initialize_latencies, this)); - - if (synced_to_jack()) { - _engine.transport_stop (); + XMLNode* child = 0; + + _ltc_input.reset (new IO (*this, _("LTC In"), IO::Input)); + _ltc_output.reset (new IO (*this, _("LTC Out"), IO::Output)); + + if (state_tree && (child = find_named_node (*state_tree->root(), "LTC-In")) != 0) { + _ltc_input->set_state (*(child->children().front()), Stateful::loading_state_version); + } else { + { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + _ltc_input->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); + } + reconnect_ltc_input (); } - - if (config.get_jack_time_master()) { - _engine.transport_locate (_transport_frame); + + if (state_tree && (child = find_named_node (*state_tree->root(), "LTC-Out")) != 0) { + _ltc_output->set_state (*(child->children().front()), Stateful::loading_state_version); + } else { + { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + _ltc_output->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); + } + reconnect_ltc_output (); } + + /* fix up names of LTC ports because we don't want the normal + * IO style of NAME/TYPE-{in,out}N + */ + + _ltc_input->nth (0)->set_name (_("LTC-in")); + _ltc_output->nth (0)->set_name (_("LTC-out")); +} - _clicking = false; +void +Session::setup_click () +{ + XMLNode* child = 0; - try { - XMLNode* child = 0; + _clicking = false; + _click_io.reset (new ClickIO (*this, "click")); + _click_gain.reset (new Amp (*this)); + _click_gain->activate (); + + if (state_tree && (child = find_named_node (*state_tree->root(), "Click")) != 0) { - _ltc_input.reset (new IO (*this, _("LTC In"), IO::Input)); - _ltc_output.reset (new IO (*this, _("LTC Out"), IO::Output)); - - if (state_tree && (child = find_named_node (*state_tree->root(), "LTC-In")) != 0) { - _ltc_input->set_state (*(child->children().front()), Stateful::loading_state_version); - } else { - { - Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); - _ltc_input->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); - } - reconnect_ltc_input (); - } + /* existing state for Click */ + int c = 0; - if (state_tree && (child = find_named_node (*state_tree->root(), "LTC-Out")) != 0) { - _ltc_output->set_state (*(child->children().front()), Stateful::loading_state_version); + if (Stateful::loading_state_version < 3000) { + c = _click_io->set_state_2X (*child->children().front(), Stateful::loading_state_version, false); } else { - { - Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); - _ltc_output->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); - } - reconnect_ltc_output (); - } - - /* fix up names of LTC ports because we don't want the normal - * IO style of NAME/TYPE-{in,out}N - */ - - _ltc_input->nth (0)->set_name (_("LTC-in")); - _ltc_output->nth (0)->set_name (_("LTC-out")); - - _click_io.reset (new ClickIO (*this, "click")); - _click_gain.reset (new Amp (*this)); - _click_gain->activate (); - - if (state_tree && (child = find_named_node (*state_tree->root(), "Click")) != 0) { - - /* existing state for Click */ - int c = 0; - - if (Stateful::loading_state_version < 3000) { - c = _click_io->set_state_2X (*child->children().front(), Stateful::loading_state_version, false); - } else { - const XMLNodeList& children (child->children()); - XMLNodeList::const_iterator i = children.begin(); - if ((c = _click_io->set_state (**i, Stateful::loading_state_version)) == 0) { - ++i; - if (i != children.end()) { - c = _click_gain->set_state (**i, Stateful::loading_state_version); - } + const XMLNodeList& children (child->children()); + XMLNodeList::const_iterator i = children.begin(); + if ((c = _click_io->set_state (**i, Stateful::loading_state_version)) == 0) { + ++i; + if (i != children.end()) { + c = _click_gain->set_state (**i, Stateful::loading_state_version); } } + } - if (c == 0) { - _clicking = Config->get_clicking (); + if (c == 0) { + _clicking = Config->get_clicking (); - } else { + } else { - error << _("could not setup Click I/O") << endmsg; - _clicking = false; - } + error << _("could not setup Click I/O") << endmsg; + _clicking = false; + } - } else { + } else { - /* default state for Click: dual-mono to first 2 physical outputs */ + /* default state for Click: dual-mono to first 2 physical outputs */ - vector<string> outs; - _engine.get_physical_outputs (DataType::AUDIO, outs); + vector<string> outs; + _engine.get_physical_outputs (DataType::AUDIO, outs); - for (uint32_t physport = 0; physport < 2; ++physport) { - if (outs.size() > physport) { - if (_click_io->add_port (outs[physport], this)) { - // relax, even though its an error - } + for (uint32_t physport = 0; physport < 2; ++physport) { + if (outs.size() > physport) { + if (_click_io->add_port (outs[physport], this)) { + // relax, even though its an error } } - - if (_click_io->n_ports () > ChanCount::ZERO) { - _clicking = Config->get_clicking (); - } } - } - catch (failed_constructor& err) { - error << _("cannot setup Click I/O") << endmsg; - } - - BootMessage (_("Compute I/O Latencies")); - - if (_clicking) { - // XXX HOW TO ALERT UI TO THIS ? DO WE NEED TO? + if (_click_io->n_ports () > ChanCount::ZERO) { + _clicking = Config->get_clicking (); + } } +} - BootMessage (_("Set up standard connections")); - +void +Session::setup_bundles () +{ vector<string> inputs[DataType::num_types]; vector<string> outputs[DataType::num_types]; for (uint32_t i = 0; i < DataType::num_types; ++i) { @@ -567,6 +663,37 @@ Session::when_engine_running () add_bundle (c); } +} + +int +Session::when_engine_running () +{ + /* every time we reconnect, recompute worst case output latencies */ + + _engine.Running.connect_same_thread (*this, boost::bind (&Session::initialize_latencies, this)); + + if (synced_to_jack()) { + _engine.transport_stop (); + } + + if (config.get_jack_time_master()) { + _engine.transport_locate (_transport_frame); + } + + + try { + BootMessage (_("Set up LTC")); + setup_ltc (); + BootMessage (_("Set up Click")); + setup_click (); + BootMessage (_("Set up standard connections")); + setup_bundles (); + } + + catch (failed_constructor& err) { + return -1; + } + BootMessage (_("Setup signal flow and plugins")); /* Reset all panners */ @@ -584,7 +711,8 @@ Session::when_engine_running () as it will set states for ports which the ControlProtocolManager creates. */ - MIDI::Manager::instance()->set_port_states (Config->midi_port_states ()); + // XXX set state of MIDI::Port's + // MidiPortManager::instance()->set_port_states (Config->midi_port_states ()); /* And this must be done after the MIDI::Manager::set_port_states as * it will try to make connections whose details are loaded by set_port_states. @@ -609,11 +737,7 @@ Session::when_engine_running () initialize_latencies (); - /* hook us up to the engine */ - - BootMessage (_("Connect to engine")); - _engine.set_session (this); - _engine.reset_timebase (); + return 0; } void @@ -874,7 +998,12 @@ Session::hookup_io () /* Tell all IO objects to connect themselves together */ IO::enable_connecting (); - MIDI::JackMIDIPort::MakeConnections (); + + /* Now tell all "floating" ports to connect to whatever + they should be connected to. + */ + + AudioEngine::instance()->reconnect_ports (); /* Anyone who cares about input state, wake up and do something */ @@ -936,7 +1065,7 @@ Session::set_track_monitor_input_status (bool yn) boost::shared_ptr<AudioTrack> tr = boost::dynamic_pointer_cast<AudioTrack> (*i); if (tr && tr->record_enabled ()) { //cerr << "switching to input = " << !auto_input << __FILE__ << __LINE__ << endl << endl; - tr->request_jack_monitors_input (yn); + tr->request_input_monitoring (yn); } } } @@ -1169,7 +1298,7 @@ Session::enable_record () if (g_atomic_int_compare_and_exchange (&_record_status, rs, Recording)) { _last_record_location = _transport_frame; - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe)); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe)); if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) { set_track_monitor_input_status (true); @@ -1190,7 +1319,7 @@ Session::disable_record (bool rt_context, bool force) if ((!Config->get_latched_record_enable () && !play_loop) || force) { g_atomic_int_set (&_record_status, Disabled); - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordExit)); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordExit)); } else { if (rs == Recording) { g_atomic_int_set (&_record_status, Enabled); @@ -1244,7 +1373,7 @@ Session::maybe_enable_record () enable_record (); } } else { - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause)); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause)); RecordStateChanged (); /* EMIT SIGNAL */ } @@ -1341,6 +1470,7 @@ Session::set_frame_rate (framecnt_t frames_per_second) */ _base_frame_rate = frames_per_second; + _nominal_frame_rate = frames_per_second; sync_time_vars(); diff --git a/libs/ardour/session_export.cc b/libs/ardour/session_export.cc index 0db4fc33bb..62eb61ab83 100644 --- a/libs/ardour/session_export.cc +++ b/libs/ardour/session_export.cc @@ -21,7 +21,6 @@ #include "pbd/error.h" #include <glibmm/threads.h> -#include <midi++/manager.h> #include <midi++/mmc.h> #include "ardour/audioengine.h" @@ -93,8 +92,8 @@ Session::pre_export () /* disable MMC output early */ - _pre_export_mmc_enabled = MIDI::Manager::instance()->mmc()->send_enabled (); - MIDI::Manager::instance()->mmc()->enable_send (false); + _pre_export_mmc_enabled = _mmc->send_enabled (); + _mmc->enable_send (false); return 0; } @@ -237,7 +236,7 @@ Session::finalize_audio_export () export_freewheel_connection.disconnect(); - MIDI::Manager::instance()->mmc()->enable_send (_pre_export_mmc_enabled); + _mmc->enable_send (_pre_export_mmc_enabled); /* maybe write CUE/TOC */ diff --git a/libs/ardour/session_jack.cc b/libs/ardour/session_jack.cc new file mode 100644 index 0000000000..af8a93fec3 --- /dev/null +++ b/libs/ardour/session_jack.cc @@ -0,0 +1,185 @@ +/* + Copyright (C) 1999-2013 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 <time.h> + +#include <glibmm/miscutils.h> + +#include "jack/jack.h" +#include "jack/session.h" + +#include "ardour/audioengine.h" +#include "ardour/filename_extensions.h" +#include "ardour/session.h" +#include "ardour/session_directory.h" +#include "ardour/tempo.h" + +using namespace ARDOUR; +using std::string; + +#ifdef HAVE_JACK_SESSION +void +Session::jack_session_event (jack_session_event_t* event) +{ + char timebuf[128], *tmp; + time_t n; + struct tm local_time; + + time (&n); + localtime_r (&n, &local_time); + strftime (timebuf, sizeof(timebuf), "JS_%FT%T", &local_time); + + while ((tmp = strchr(timebuf, ':'))) { *tmp = '.'; } + + if (event->type == JackSessionSaveTemplate) + { + if (save_template( timebuf )) { + event->flags = JackSessionSaveError; + } else { + string cmd ("ardour3 -P -U "); + cmd += event->client_uuid; + cmd += " -T "; + cmd += timebuf; + + event->command_line = strdup (cmd.c_str()); + } + } + else + { + if (save_state (timebuf)) { + event->flags = JackSessionSaveError; + } else { + std::string xml_path (_session_dir->root_path()); + std::string legalized_filename = legalize_for_path (timebuf) + statefile_suffix; + xml_path = Glib::build_filename (xml_path, legalized_filename); + + string cmd ("ardour3 -P -U "); + cmd += event->client_uuid; + cmd += " \""; + cmd += xml_path; + cmd += '\"'; + + event->command_line = strdup (cmd.c_str()); + } + } + + /* this won't be called if the port engine in use is not JACK, so we do + not have to worry about the type of PortEngine::private_handle() + */ + + jack_client_t* jack_client = (jack_client_t*) AudioEngine::instance()->port_engine().private_handle(); + + if (jack_client) { + jack_session_reply (jack_client, event); + } + + if (event->type == JackSessionSaveAndQuit) { + Quit (); /* EMIT SIGNAL */ + } + + jack_session_event_free( event ); +} +#endif + +void +Session::jack_timebase_callback (jack_transport_state_t /*state*/, + pframes_t /*nframes*/, + jack_position_t* pos, + int /*new_position*/) +{ + Timecode::BBT_Time bbt; + + /* BBT info */ + + if (_tempo_map) { + + TempoMetric metric (_tempo_map->metric_at (_transport_frame)); + + try { + _tempo_map->bbt_time_rt (_transport_frame, bbt); + + pos->bar = bbt.bars; + pos->beat = bbt.beats; + pos->tick = bbt.ticks; + + // XXX still need to set bar_start_tick + + pos->beats_per_bar = metric.meter().divisions_per_bar(); + pos->beat_type = metric.meter().note_divisor(); + pos->ticks_per_beat = Timecode::BBT_Time::ticks_per_beat; + pos->beats_per_minute = metric.tempo().beats_per_minute(); + + pos->valid = jack_position_bits_t (pos->valid | JackPositionBBT); + + } catch (...) { + /* no message */ + } + } + +#ifdef HAVE_JACK_VIDEO_SUPPORT + //poke audio video ratio so Ardour can track Video Sync + pos->audio_frames_per_video_frame = frame_rate() / timecode_frames_per_second(); + pos->valid = jack_position_bits_t (pos->valid | JackAudioVideoRatio); +#endif + +#if 0 + /* Timecode info */ + + pos->timecode_offset = config.get_timecode_offset(); + t.timecode_frame_rate = timecode_frames_per_second(); + pos->valid = jack_position_bits_t (pos->valid | JackPositionTimecode; + + if (_transport_speed) { + + if (play_loop) { + + Location* location = _locations.auto_loop_location(); + + if (location) { + + t.transport_state = JackTransportLooping; + t.loop_start = location->start(); + t.loop_end = location->end(); + t.valid = jack_transport_bits_t (t.valid | JackTransportLoop); + + } else { + + t.loop_start = 0; + t.loop_end = 0; + t.transport_state = JackTransportRolling; + + } + + } else { + + t.loop_start = 0; + t.loop_end = 0; + t.transport_state = JackTransportRolling; + + } + + } +#endif +} + diff --git a/libs/ardour/session_ltc.cc b/libs/ardour/session_ltc.cc index 2671bfccf4..2ce25b5a48 100644 --- a/libs/ardour/session_ltc.cc +++ b/libs/ardour/session_ltc.cc @@ -55,7 +55,7 @@ using namespace Timecode; * This filter is adaptive so that fast vari-speed signals * will not be affected by it. */ -#define LTC_RISE_TIME(speed) MIN (100, MAX(40, (4000000 / ((speed==0)?1:speed) / engine().frame_rate()))) +#define LTC_RISE_TIME(speed) MIN (100, MAX(40, (4000000 / ((speed==0)?1:speed) / engine().sample_rate()))) #define TV_STANDARD(tcf) \ (timecode_to_frames_per_second(tcf)==25.0 ? LTC_TV_625_50 : \ @@ -564,7 +564,7 @@ Session::ltc_tx_send_time_code_for_cycle (framepos_t start_frame, framepos_t end * To do better than this, resampling (or a rewrite of the * encoder) is required. */ - ltc_speed -= ((ltc_enc_pos + ltc_enc_cnt - poff) - cycle_start_frame) / engine().frame_rate(); + ltc_speed -= ((ltc_enc_pos + ltc_enc_cnt - poff) - cycle_start_frame) / engine().sample_rate(); } diff --git a/libs/ardour/session_midi.cc b/libs/ardour/session_midi.cc index 9a7ca5b734..63cba68df1 100644 --- a/libs/ardour/session_midi.cc +++ b/libs/ardour/session_midi.cc @@ -29,7 +29,6 @@ #include "midi++/mmc.h" #include "midi++/port.h" -#include "midi++/manager.h" #include "pbd/error.h" #include "pbd/pthread_utils.h" @@ -40,6 +39,7 @@ #include "ardour/audio_track.h" #include "ardour/audioengine.h" #include "ardour/debug.h" +#include "ardour/midi_port.h" #include "ardour/midi_track.h" #include "ardour/midi_ui.h" #include "ardour/session.h" @@ -348,7 +348,7 @@ Session::mmc_record_enable (MIDI::MachineControl &mmc, size_t trk, bool enabled) * @param t time to send. */ int -Session::send_full_time_code (framepos_t const t) +Session::send_full_time_code (framepos_t const t, pframes_t nframes) { /* This function could easily send at a given frame offset, but would * that be useful? Does ardour do sub-block accurate locating? [DR] */ @@ -423,10 +423,9 @@ Session::send_full_time_code (framepos_t const t) msg[8] = timecode.frames; // Send message at offset 0, sent time is for the start of this cycle - if (MIDI::Manager::instance()->mtc_output_port()->midimsg (msg, sizeof (msg), 0)) { - error << _("Session: could not send full MIDI time code") << endmsg; - return -1; - } + + MidiBuffer& mb (_midi_ports->mtc_output_port()->get_midi_buffer (nframes)); + mb.push_back (0, sizeof (msg), msg); _pframes_since_last_mtc = 0; return 0; @@ -469,7 +468,7 @@ Session::send_midi_time_code_for_cycle (framepos_t start_frame, framepos_t end_f next_quarter_frame_to_send, quarter_frame_duration)); if (rint(outbound_mtc_timecode_frame + (next_quarter_frame_to_send * quarter_frame_duration)) < _transport_frame) { - send_full_time_code (_transport_frame); + send_full_time_code (_transport_frame, nframes); return 0; } @@ -515,9 +514,10 @@ Session::send_midi_time_code_for_cycle (framepos_t start_frame, framepos_t end_f ARDOUR::pframes_t const out_stamp = (msg_time - start_frame) / _transport_speed; assert (out_stamp < nframes); - if (MIDI::Manager::instance()->mtc_output_port()->midimsg (mtc_msg, 2, out_stamp)) { + MidiBuffer& mb (_midi_ports->mtc_output_port()->get_midi_buffer(nframes)); + if (!mb.push_back (out_stamp, 2, mtc_msg)) { error << string_compose(_("Session: cannot send quarter-frame MTC message (%1)"), strerror (errno)) - << endmsg; + << endmsg; return -1; } @@ -602,3 +602,45 @@ Session::start_midi_thread () return 0; } +MIDI::Port* +Session::midi_input_port () const +{ + return _midi_ports->midi_input_port (); +} +MIDI::Port* +Session::midi_output_port () const +{ + return _midi_ports->midi_output_port (); +} +boost::shared_ptr<MidiPort> +Session::midi_clock_output_port () const +{ + return _midi_ports->midi_clock_output_port (); +} +boost::shared_ptr<MidiPort> +Session::midi_clock_input_port () const +{ + return _midi_ports->midi_clock_input_port (); +} +boost::shared_ptr<MidiPort> +Session::mtc_output_port () const +{ + return _midi_ports->mtc_output_port (); +} +boost::shared_ptr<MidiPort> +Session::mtc_input_port () const +{ + return _midi_ports->mtc_input_port (); +} + +MIDI::Port* +Session::mmc_output_port () const +{ + return _midi_ports->mmc_output_port (); +} + +MIDI::Port* +Session::mmc_input_port () const +{ + return _midi_ports->mmc_input_port (); +} diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc index cecbd88f65..3c46a2e0a6 100644 --- a/libs/ardour/session_process.cc +++ b/libs/ardour/session_process.cc @@ -40,7 +40,6 @@ #include "ardour/ticker.h" #include "ardour/types.h" -#include "midi++/manager.h" #include "midi++/mmc.h" #include "i18n.h" @@ -85,7 +84,7 @@ Session::process (pframes_t nframes) try { if (!_engine.freewheeling() && Config->get_send_midi_clock() && transport_speed() == 1.0f && midi_clock->has_midi_port()) { - midi_clock->tick (transport_at_start); + midi_clock->tick (transport_at_start, nframes); } } catch (...) { /* don't bother with a message */ @@ -325,7 +324,7 @@ Session::process_with_events (pframes_t nframes) * and prepare for rolling) */ if (_send_timecode_update) { - send_full_time_code (_transport_frame); + send_full_time_code (_transport_frame, nframes); } if (!process_can_proceed()) { diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 9699a7c37f..64f8e387be 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 1999-2002 Paul Davis + Copyright (C) 1999-2013 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 @@ -59,7 +59,6 @@ #include "midi++/mmc.h" #include "midi++/port.h" -#include "midi++/manager.h" #include "evoral/SMF.hpp" @@ -88,6 +87,7 @@ #include "ardour/control_protocol_manager.h" #include "ardour/directory_names.h" #include "ardour/filename_extensions.h" +#include "ardour/graph.h" #include "ardour/location.h" #include "ardour/midi_model.h" #include "ardour/midi_patch_manager.h" @@ -126,112 +126,47 @@ using namespace std; using namespace ARDOUR; using namespace PBD; -/** @param snapshot_name Snapshot name, without the .ardour prefix */ void -Session::first_stage_init (string fullpath, string snapshot_name) +Session::pre_engine_init (string fullpath) { - if (fullpath.length() == 0) { + if (fullpath.empty()) { destroy (); throw failed_constructor(); } - _path = canonical_path (fullpath); + /* discover canonical fullpath */ - if (_path[_path.length()-1] != G_DIR_SEPARATOR) { - _path += G_DIR_SEPARATOR; + char buf[PATH_MAX+1]; + if (!realpath (fullpath.c_str(), buf) && (errno != ENOENT)) { + error << string_compose(_("Could not use path %1 (%2)"), buf, strerror(errno)) << endmsg; + destroy (); + throw failed_constructor(); } - /* these two are just provisional settings. set_state() - will likely override them. - */ - - _name = _current_snapshot_name = snapshot_name; + _path = string(buf); + + /* we require _path to end with a dir separator */ - set_history_depth (Config->get_history_depth()); + if (_path[_path.length()-1] != G_DIR_SEPARATOR) { + _path += G_DIR_SEPARATOR; + } - _current_frame_rate = _engine.frame_rate (); - _nominal_frame_rate = _current_frame_rate; - _base_frame_rate = _current_frame_rate; + /* is it new ? */ - _tempo_map = new TempoMap (_current_frame_rate); - _tempo_map->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1)); + _is_new = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)); + /* finish initialization that can't be done in a normal C++ constructor + definition. + */ - _non_soloed_outs_muted = false; - _listen_cnt = 0; - _solo_isolated_cnt = 0; + timerclear (&last_mmc_step); g_atomic_int_set (&processing_prohibited, 0); - _transport_speed = 0; - _default_transport_speed = 1.0; - _last_transport_speed = 0; - _target_transport_speed = 0; - auto_play_legal = false; - transport_sub_state = 0; - _transport_frame = 0; - _requested_return_frame = -1; - _session_range_location = 0; g_atomic_int_set (&_record_status, Disabled); - loop_changing = false; - play_loop = false; - have_looped = false; - _last_roll_location = 0; - _last_roll_or_reversal_location = 0; - _last_record_location = 0; - pending_locate_frame = 0; - pending_locate_roll = false; - pending_locate_flush = false; - state_was_pending = false; - set_next_event (); - outbound_mtc_timecode_frame = 0; - next_quarter_frame_to_send = -1; - current_block_size = 0; - solo_update_disabled = false; - _have_captured = false; - _worst_output_latency = 0; - _worst_input_latency = 0; - _worst_track_latency = 0; - _state_of_the_state = StateOfTheState(CannotSave|InitialConnecting|Loading); - _was_seamless = Config->get_seamless_loop (); - _slave = 0; - _send_qf_mtc = false; - _pframes_since_last_mtc = 0; g_atomic_int_set (&_playback_load, 100); g_atomic_int_set (&_capture_load, 100); - _play_range = false; - _exporting = false; - pending_abort = false; - _adding_routes_in_progress = false; - destructive_index = 0; - first_file_data_format_reset = true; - first_file_header_format_reset = true; - post_export_sync = false; - midi_control_ui = 0; - _step_editors = 0; - no_questions_about_missing_files = false; - _speakers.reset (new Speakers); - _clicks_cleared = 0; - ignore_route_processor_changes = false; - _pre_export_mmc_enabled = false; - - AudioDiskstream::allocate_working_buffers(); - - /* default short fade = 15ms */ - - SndFileSource::setup_standard_crossfades (*this, frame_rate()); - - last_mmc_step.tv_sec = 0; - last_mmc_step.tv_usec = 0; - step_speed = 0.0; - - /* click sounds are unset by default, which causes us to internal - waveforms for clicks. - */ - - click_length = 0; - click_emphasis_length = 0; - _clicking = false; - - process_function = &Session::process_with_events; + set_next_event (); + _all_route_group->set_active (true, this); + interpolation.add_channel_to (0, 0); if (config.get_use_video_sync()) { waiting_for_sync_offset = true; @@ -239,32 +174,26 @@ Session::first_stage_init (string fullpath, string snapshot_name) waiting_for_sync_offset = false; } - last_timecode_when = 0; - last_timecode_valid = false; - - sync_time_vars (); - last_rr_session_dir = session_dirs.begin(); - refresh_disk_space (); + + set_history_depth (Config->get_history_depth()); + + if (how_many_dsp_threads () > 1) { + /* For now, only create the graph if we are using >1 DSP threads, as + it is a bit slower than the old code with 1 thread. + */ + _process_graph.reset (new Graph (*this)); + } /* default: assume simple stereo speaker configuration */ _speakers->setup_default_speakers (2); - /* slave stuff */ - - average_slave_delta = 1800; // !!! why 1800 ???? - have_first_delta_accumulator = false; - delta_accumulator_cnt = 0; - _slave_state = Stopped; - _solo_cut_control.reset (new ProxyControllable (_("solo cut control (dB)"), PBD::Controllable::GainLike, boost::bind (&RCConfiguration::set_solo_mute_gain, Config, _1), boost::bind (&RCConfiguration::get_solo_mute_gain, Config))); add_controllable (_solo_cut_control); - _engine.GraphReordered.connect_same_thread (*this, boost::bind (&Session::graph_reordered, this)); - /* These are all static "per-class" signals */ SourceFactory::SourceCreated.connect_same_thread (*this, boost::bind (&Session::add_source, this, _1)); @@ -277,87 +206,101 @@ Session::first_stage_init (string fullpath, string snapshot_name) Delivery::disable_panners (); IO::disable_connecting (); + + AudioFileSource::set_peak_dir (_session_dir->peak_path()); } int -Session::second_stage_init () +Session::post_engine_init () { - AudioFileSource::set_peak_dir (_session_dir->peak_path()); + BootMessage (_("Set block size and sample rate")); - if (!_is_new) { - if (load_state (_current_snapshot_name)) { - return -1; - } - } + set_block_size (_engine.samples_per_cycle()); + set_frame_rate (_engine.sample_rate()); + + n_physical_outputs = _engine.n_physical_outputs (); + n_physical_inputs = _engine.n_physical_inputs (); + BootMessage (_("Using configuration")); + + _midi_ports = new MidiPortManager; + setup_midi_machine_control (); + if (_butler->start_thread()) { return -1; } - + if (start_midi_thread ()) { return -1; } + + setup_click_sounds (0); + setup_midi_control (); - setup_midi_machine_control (); - - // set_state() will call setup_raid_path(), but if it's a new session we need - // to call setup_raid_path() here. - - if (state_tree) { - if (set_state (*state_tree->root(), Stateful::loading_state_version)) { - return -1; - } - } else { - setup_raid_path(_path); - } + _engine.Halted.connect_same_thread (*this, boost::bind (&Session::engine_halted, this)); + _engine.Xrun.connect_same_thread (*this, boost::bind (&Session::xrun_recovery, this)); - /* we can't save till after ::when_engine_running() is called, - because otherwise we save state with no connections made. - therefore, we reset _state_of_the_state because ::set_state() - will have cleared it. + try { + /* tempo map requires sample rate knowledge */ - we also have to include Loading so that any events that get - generated between here and the end of ::when_engine_running() - will be processed directly rather than queued. - */ + _tempo_map = new TempoMap (_current_frame_rate); + _tempo_map->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1)); + + /* MidiClock requires a tempo map */ - _state_of_the_state = StateOfTheState (_state_of_the_state|CannotSave|Loading); + midi_clock = new MidiClockTicker (); + midi_clock->set_session (this); - _locations->changed.connect_same_thread (*this, boost::bind (&Session::locations_changed, this)); - _locations->added.connect_same_thread (*this, boost::bind (&Session::locations_added, this, _1)); - setup_click_sounds (0); - setup_midi_control (); + /* crossfades require sample rate knowledge */ - /* Pay attention ... */ + SndFileSource::setup_standard_crossfades (*this, frame_rate()); + _engine.GraphReordered.connect_same_thread (*this, boost::bind (&Session::graph_reordered, this)); + + AudioDiskstream::allocate_working_buffers(); + refresh_disk_space (); + + /* we're finally ready to call set_state() ... all objects have + * been created, the engine is running. + */ + + if (state_tree) { + if (set_state (*state_tree->root(), Stateful::loading_state_version)) { + return -1; + } + } else { + // set_state() will call setup_raid_path(), but if it's a new session we need + // to call setup_raid_path() here. + setup_raid_path (_path); + } - _engine.Halted.connect_same_thread (*this, boost::bind (&Session::engine_halted, this)); - _engine.Xrun.connect_same_thread (*this, boost::bind (&Session::xrun_recovery, this)); + /* ENGINE */ - midi_clock = new MidiClockTicker (); - midi_clock->set_session (this); + boost::function<void (std::string)> ff (boost::bind (&Session::config_changed, this, _1, false)); + boost::function<void (std::string)> ft (boost::bind (&Session::config_changed, this, _1, true)); + + Config->map_parameters (ff); + config.map_parameters (ft); - try { when_engine_running (); - } - - /* handle this one in a different way than all others, so that its clear what happened */ - catch (AudioEngine::PortRegistrationFailure& err) { + _locations->changed.connect_same_thread (*this, boost::bind (&Session::locations_changed, this)); + _locations->added.connect_same_thread (*this, boost::bind (&Session::locations_added, this, _1)); + + } catch (AudioEngine::PortRegistrationFailure& err) { + /* handle this one in a different way than all others, so that its clear what happened */ error << err.what() << endmsg; return -1; - } - - catch (...) { + } catch (...) { return -1; } BootMessage (_("Reset Remote Controls")); - send_full_time_code (0); + // send_full_time_code (0); _engine.transport_locate (0); - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset)); - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (Timecode::Time ())); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset)); + _mmc->send (MIDI::MachineControlCommand (Timecode::Time ())); MIDI::Name::MidiPatchManager::instance().set_session (this); @@ -366,18 +309,16 @@ Session::second_stage_init () _state_of_the_state = Clean; - Port::set_connecting_blocked (false); - DirtyChanged (); /* EMIT SIGNAL */ - if (state_was_pending) { - save_state (_current_snapshot_name); + if (_is_new) { + save_state (""); + } else if (state_was_pending) { + save_state (""); remove_pending_capture_state (); state_was_pending = false; } - BootMessage (_("Session loading complete")); - return 0; } @@ -604,8 +545,6 @@ Session::create (const string& session_template, BusProfile* bus_profile) add_monitor_section (); } - save_state (""); - return 0; } @@ -684,62 +623,6 @@ Session::remove_state (string snapshot_name) } } -#ifdef HAVE_JACK_SESSION -void -Session::jack_session_event (jack_session_event_t * event) -{ - char timebuf[128], *tmp; - time_t n; - struct tm local_time; - - time (&n); - localtime_r (&n, &local_time); - strftime (timebuf, sizeof(timebuf), "JS_%FT%T", &local_time); - - while ((tmp = strchr(timebuf, ':'))) { *tmp = '.'; } - - if (event->type == JackSessionSaveTemplate) - { - if (save_template( timebuf )) { - event->flags = JackSessionSaveError; - } else { - string cmd ("ardour3 -P -U "); - cmd += event->client_uuid; - cmd += " -T "; - cmd += timebuf; - - event->command_line = strdup (cmd.c_str()); - } - } - else - { - if (save_state (timebuf)) { - event->flags = JackSessionSaveError; - } else { - std::string xml_path (_session_dir->root_path()); - std::string legalized_filename = legalize_for_path (timebuf) + statefile_suffix; - xml_path = Glib::build_filename (xml_path, legalized_filename); - - string cmd ("ardour3 -P -U "); - cmd += event->client_uuid; - cmd += " \""; - cmd += xml_path; - cmd += '\"'; - - event->command_line = strdup (cmd.c_str()); - } - } - - jack_session_reply (_engine.jack(), event); - - if (event->type == JackSessionSaveAndQuit) { - Quit (); /* EMIT SIGNAL */ - } - - jack_session_event_free( event ); -} -#endif - /** @param snapshot_name Name to save under, without .ardour / .pending prefix */ int Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot) @@ -1034,6 +917,15 @@ Session::state (bool full_state) /* various options */ + list<XMLNode*> midi_port_nodes = _midi_ports->get_midi_port_states(); + if (!midi_port_nodes.empty()) { + XMLNode* midi_port_stuff = new XMLNode ("MIDIPorts"); + for (list<XMLNode*>::const_iterator n = midi_port_nodes.begin(); n != midi_port_nodes.end(); ++n) { + midi_port_stuff->add_child_nocopy (**n); + } + node->add_child_nocopy (*midi_port_stuff); + } + node->add_child_nocopy (config.get_variables ()); node->add_child_nocopy (ARDOUR::SessionMetadata::Metadata()->get_state()); @@ -1239,6 +1131,11 @@ Session::set_state (const XMLNode& node, int version) Evoral::init_event_id_counter (atoi (prop->value())); } + + if ((child = find_named_node (node, "MIDIPorts")) != 0) { + _midi_ports->set_midi_port_states (child->children()); + } + IO::disable_connecting (); Stateful::save_extra_xml (node); @@ -3453,11 +3350,11 @@ Session::config_changed (std::string p, bool ours) } else if (p == "mmc-device-id" || p == "mmc-receive-id" || p == "mmc-receive-device-id") { - MIDI::Manager::instance()->mmc()->set_receive_device_id (Config->get_mmc_receive_device_id()); + _mmc->set_receive_device_id (Config->get_mmc_receive_device_id()); } else if (p == "mmc-send-id" || p == "mmc-send-device-id") { - MIDI::Manager::instance()->mmc()->set_send_device_id (Config->get_mmc_send_device_id()); + _mmc->set_send_device_id (Config->get_mmc_send_device_id()); } else if (p == "midi-control") { @@ -3520,7 +3417,7 @@ Session::config_changed (std::string p, bool ours) } else if (p == "send-mmc") { - MIDI::Manager::instance()->mmc()->enable_send (Config->get_send_mmc ()); + _mmc->enable_send (Config->get_send_mmc ()); } else if (p == "midi-feedback") { @@ -3578,13 +3475,13 @@ Session::config_changed (std::string p, bool ours) } else if (p == "initial-program-change") { - if (MIDI::Manager::instance()->mmc()->output_port() && Config->get_initial_program_change() >= 0) { + if (_mmc->output_port() && Config->get_initial_program_change() >= 0) { MIDI::byte buf[2]; buf[0] = MIDI::program; // channel zero by default buf[1] = (Config->get_initial_program_change() & 0x7f); - MIDI::Manager::instance()->mmc()->output_port()->midimsg (buf, sizeof (buf), 0); + _mmc->output_port()->midimsg (buf, sizeof (buf), 0); } } else if (p == "solo-mute-override") { // catch_up_on_solo_mute_override (); @@ -3648,27 +3545,28 @@ Session::load_diskstreams_2X (XMLNode const & node, int) void Session::setup_midi_machine_control () { - MIDI::MachineControl* mmc = MIDI::Manager::instance()->mmc (); - - mmc->Play.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1)); - mmc->DeferredPlay.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1)); - mmc->Stop.connect_same_thread (*this, boost::bind (&Session::mmc_stop, this, _1)); - mmc->FastForward.connect_same_thread (*this, boost::bind (&Session::mmc_fast_forward, this, _1)); - mmc->Rewind.connect_same_thread (*this, boost::bind (&Session::mmc_rewind, this, _1)); - mmc->Pause.connect_same_thread (*this, boost::bind (&Session::mmc_pause, this, _1)); - mmc->RecordPause.connect_same_thread (*this, boost::bind (&Session::mmc_record_pause, this, _1)); - mmc->RecordStrobe.connect_same_thread (*this, boost::bind (&Session::mmc_record_strobe, this, _1)); - mmc->RecordExit.connect_same_thread (*this, boost::bind (&Session::mmc_record_exit, this, _1)); - mmc->Locate.connect_same_thread (*this, boost::bind (&Session::mmc_locate, this, _1, _2)); - mmc->Step.connect_same_thread (*this, boost::bind (&Session::mmc_step, this, _1, _2)); - mmc->Shuttle.connect_same_thread (*this, boost::bind (&Session::mmc_shuttle, this, _1, _2, _3)); - mmc->TrackRecordStatusChange.connect_same_thread (*this, boost::bind (&Session::mmc_record_enable, this, _1, _2, _3)); + _mmc = new MIDI::MachineControl; + _mmc->set_ports (_midi_ports->mmc_input_port(), _midi_ports->mmc_output_port()); + + _mmc->Play.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1)); + _mmc->DeferredPlay.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1)); + _mmc->Stop.connect_same_thread (*this, boost::bind (&Session::mmc_stop, this, _1)); + _mmc->FastForward.connect_same_thread (*this, boost::bind (&Session::mmc_fast_forward, this, _1)); + _mmc->Rewind.connect_same_thread (*this, boost::bind (&Session::mmc_rewind, this, _1)); + _mmc->Pause.connect_same_thread (*this, boost::bind (&Session::mmc_pause, this, _1)); + _mmc->RecordPause.connect_same_thread (*this, boost::bind (&Session::mmc_record_pause, this, _1)); + _mmc->RecordStrobe.connect_same_thread (*this, boost::bind (&Session::mmc_record_strobe, this, _1)); + _mmc->RecordExit.connect_same_thread (*this, boost::bind (&Session::mmc_record_exit, this, _1)); + _mmc->Locate.connect_same_thread (*this, boost::bind (&Session::mmc_locate, this, _1, _2)); + _mmc->Step.connect_same_thread (*this, boost::bind (&Session::mmc_step, this, _1, _2)); + _mmc->Shuttle.connect_same_thread (*this, boost::bind (&Session::mmc_shuttle, this, _1, _2, _3)); + _mmc->TrackRecordStatusChange.connect_same_thread (*this, boost::bind (&Session::mmc_record_enable, this, _1, _2, _3)); /* also handle MIDI SPP because its so common */ - mmc->SPPStart.connect_same_thread (*this, boost::bind (&Session::spp_start, this)); - mmc->SPPContinue.connect_same_thread (*this, boost::bind (&Session::spp_continue, this)); - mmc->SPPStop.connect_same_thread (*this, boost::bind (&Session::spp_stop, this)); + _mmc->SPPStart.connect_same_thread (*this, boost::bind (&Session::spp_start, this)); + _mmc->SPPContinue.connect_same_thread (*this, boost::bind (&Session::spp_continue, this)); + _mmc->SPPStop.connect_same_thread (*this, boost::bind (&Session::spp_stop, this)); } boost::shared_ptr<Controllable> diff --git a/libs/ardour/session_time.cc b/libs/ardour/session_time.cc index 18805afa90..0f2186c09b 100644 --- a/libs/ardour/session_time.cc +++ b/libs/ardour/session_time.cc @@ -180,31 +180,30 @@ Session::timecode_time (Timecode::Time &t) } int -Session::jack_sync_callback (jack_transport_state_t state, - jack_position_t* pos) +Session::backend_sync_callback (TransportState state, framepos_t pos) { bool slave = synced_to_jack(); switch (state) { - case JackTransportStopped: - if (slave && _transport_frame != pos->frame && post_transport_work() == 0) { - request_locate (pos->frame, false); + case TransportStopped: + if (slave && _transport_frame != pos && post_transport_work() == 0) { + request_locate (pos, false); // cerr << "SYNC: stopped, locate to " << pos->frame << " from " << _transport_frame << endl; return false; } else { return true; } - case JackTransportStarting: + case TransportStarting: // cerr << "SYNC: starting @ " << pos->frame << " a@ " << _transport_frame << " our work = " << post_transport_work() << " pos matches ? " << (_transport_frame == pos->frame) << endl; if (slave) { - return _transport_frame == pos->frame && post_transport_work() == 0; + return _transport_frame == pos && post_transport_work() == 0; } else { return true; } break; - case JackTransportRolling: + case TransportRolling: // cerr << "SYNC: rolling slave = " << slave << endl; if (slave) { start_transport (); @@ -212,93 +211,13 @@ Session::jack_sync_callback (jack_transport_state_t state, break; default: - error << string_compose (_("Unknown JACK transport state %1 in sync callback"), state) + error << string_compose (_("Unknown transport state %1 in sync callback"), state) << endmsg; } return true; } -void -Session::jack_timebase_callback (jack_transport_state_t /*state*/, - pframes_t /*nframes*/, - jack_position_t* pos, - int /*new_position*/) -{ - Timecode::BBT_Time bbt; - - /* BBT info */ - - if (_tempo_map) { - - TempoMetric metric (_tempo_map->metric_at (_transport_frame)); - - try { - _tempo_map->bbt_time_rt (_transport_frame, bbt); - - pos->bar = bbt.bars; - pos->beat = bbt.beats; - pos->tick = bbt.ticks; - - // XXX still need to set bar_start_tick - - pos->beats_per_bar = metric.meter().divisions_per_bar(); - pos->beat_type = metric.meter().note_divisor(); - pos->ticks_per_beat = Timecode::BBT_Time::ticks_per_beat; - pos->beats_per_minute = metric.tempo().beats_per_minute(); - - pos->valid = jack_position_bits_t (pos->valid | JackPositionBBT); - - } catch (...) { - /* no message */ - } - } - -#ifdef HAVE_JACK_VIDEO_SUPPORT - //poke audio video ratio so Ardour can track Video Sync - pos->audio_frames_per_video_frame = frame_rate() / timecode_frames_per_second(); - pos->valid = jack_position_bits_t (pos->valid | JackAudioVideoRatio); -#endif - -#if 0 - /* Timecode info */ - - pos->timecode_offset = config.get_timecode_offset(); - t.timecode_frame_rate = timecode_frames_per_second(); - pos->valid = jack_position_bits_t (pos->valid | JackPositionTimecode; - - if (_transport_speed) { - - if (play_loop) { - - Location* location = _locations.auto_loop_location(); - - if (location) { - - t.transport_state = JackTransportLooping; - t.loop_start = location->start(); - t.loop_end = location->end(); - t.valid = jack_transport_bits_t (t.valid | JackTransportLoop); - - } else { - - t.loop_start = 0; - t.loop_end = 0; - t.transport_state = JackTransportRolling; - - } - - } else { - - t.loop_start = 0; - t.loop_end = 0; - t.transport_state = JackTransportRolling; - - } - - } -#endif -} ARDOUR::framecnt_t Session::convert_to_frames (AnyTime const & position) diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index 51d8d2c369..ab6d68fd67 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -33,7 +33,6 @@ #include "midi++/mmc.h" #include "midi++/port.h" -#include "midi++/manager.h" #include "ardour/audioengine.h" #include "ardour/auditioner.h" @@ -610,11 +609,23 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) have_looped = false; - if (!_engine.freewheeling()) { - send_full_time_code (_transport_frame); + /* don't bother with this stuff if we're disconnected from the engine, + because there will be no process callbacks to deliver stuff from + */ + + if (_engine.connected() && !_engine.freewheeling()) { + // need to queue this in the next RT cycle + _send_timecode_update = true; if (!dynamic_cast<MTC_Slave*>(_slave)) { - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop)); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop)); + + /* This (::non_realtime_stop()) gets called by main + process thread, which will lead to confusion + when calling AsyncMIDIPort::write(). + + Something must be done. XXX + */ send_mmc_locate (_transport_frame); } } @@ -1260,7 +1271,7 @@ Session::start_transport () Timecode::Time time; timecode_time_subframes (_transport_frame, time); if (!dynamic_cast<MTC_Slave*>(_slave)) { - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay)); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay)); } } @@ -1338,8 +1349,9 @@ Session::use_sync_source (Slave* new_slave) _slave = new_slave; DEBUG_TRACE (DEBUG::Slave, string_compose ("set new slave to %1\n", _slave)); - - send_full_time_code (_transport_frame); + + // need to queue this for next process() cycle + _send_timecode_update = true; boost::shared_ptr<RouteList> rl = routes.reader(); for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { @@ -1380,7 +1392,7 @@ Session::switch_to_sync_source (SyncSource src) } try { - new_slave = new MTC_Slave (*this, *MIDI::Manager::instance()->mtc_input_port()); + new_slave = new MTC_Slave (*this, *_midi_ports->mtc_input_port()); } catch (failed_constructor& err) { @@ -1409,7 +1421,7 @@ Session::switch_to_sync_source (SyncSource src) } try { - new_slave = new MIDIClock_Slave (*this, *MIDI::Manager::instance()->midi_clock_input_port(), 24); + new_slave = new MIDIClock_Slave (*this, *_midi_ports->midi_clock_input_port(), 24); } catch (failed_constructor& err) { @@ -1426,7 +1438,7 @@ Session::switch_to_sync_source (SyncSource src) return; } - new_slave = new JACK_Slave (_engine.jack()); + new_slave = new JACK_Slave (*AudioEngine::instance()); break; default: @@ -1616,16 +1628,6 @@ Session::allow_auto_play (bool yn) auto_play_legal = yn; } -void -Session::reset_jack_connection (jack_client_t* jack) -{ - JACK_Slave* js; - - if (_slave && ((js = dynamic_cast<JACK_Slave*> (_slave)) != 0)) { - js->reset_client (jack); - } -} - bool Session::maybe_stop (framepos_t limit) { @@ -1646,7 +1648,7 @@ Session::send_mmc_locate (framepos_t t) if (!_engine.freewheeling()) { Timecode::Time time; timecode_time_subframes (t, time); - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (time)); + _mmc->send (MIDI::MachineControlCommand (time)); } } diff --git a/libs/ardour/slave.cc b/libs/ardour/slave.cc index 1cbcba8ee6..e8c63b43c1 100644 --- a/libs/ardour/slave.cc +++ b/libs/ardour/slave.cc @@ -50,13 +50,13 @@ SlaveSessionProxy::transport_frame() const pframes_t SlaveSessionProxy::frames_since_cycle_start() const { - return session.engine().frames_since_cycle_start(); + return session.engine().samples_since_cycle_start(); } framepos_t SlaveSessionProxy::frame_time() const { - return session.engine().frame_time(); + return session.engine().sample_time(); } void diff --git a/libs/ardour/ticker.cc b/libs/ardour/ticker.cc index f32cdf9415..9a29df2641 100644 --- a/libs/ardour/ticker.cc +++ b/libs/ardour/ticker.cc @@ -19,13 +19,12 @@ #include "pbd/compose.h" #include "pbd/stacktrace.h" -#include "midi++/port.h" -#include "midi++/jack_midi_port.h" -#include "midi++/manager.h" - #include "evoral/midi_events.h" +#include "ardour/async_midi_port.h" #include "ardour/audioengine.h" +#include "ardour/midi_buffer.h" +#include "ardour/midi_port.h" #include "ardour/ticker.h" #include "ardour/session.h" #include "ardour/tempo.h" @@ -95,16 +94,16 @@ public: MidiClockTicker::MidiClockTicker () - : _midi_port (0) - , _ppqn (24) + : _ppqn (24) , _last_tick (0.0) + , _send_pos (false) + , _send_state (false) { _pos.reset (new Position()); } MidiClockTicker::~MidiClockTicker() { - _midi_port = 0; _pos.reset (0); } @@ -115,7 +114,6 @@ MidiClockTicker::set_session (Session* s) if (_session) { _session->TransportStateChange.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::transport_state_changed, this)); - _session->PositionChanged.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::position_changed, this, _1)); _session->TransportLooped.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::transport_looped, this)); _session->Located.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::session_located, this)); @@ -139,41 +137,20 @@ MidiClockTicker::session_located() return; } - if (_pos->speed == 0.0f) { - uint32_t where = llrint (_pos->midi_beats); - send_position_event (where, 0); - } else if (_pos->speed == 1.0f) { -#if 1 - /* Experimental. To really do this and have accuracy, the - stop/locate/continue sequence would need queued to send immediately - before the next midi clock. */ - - send_stop_event (0); - - if (_pos->frame == 0) { - send_start_event (0); - } else { - uint32_t where = llrint (_pos->midi_beats); - send_position_event (where, 0); - send_continue_event (0); - } -#endif - } else { - /* Varispeed not supported */ - } + _send_pos = true; } void MidiClockTicker::session_going_away () { SessionHandlePtr::session_going_away(); - _midi_port = 0; + _midi_port.reset (); } void MidiClockTicker::update_midi_clock_port() { - _midi_port = MIDI::Manager::instance()->midi_clock_output_port(); + _midi_port = _session->midi_clock_output_port(); } void @@ -204,49 +181,12 @@ MidiClockTicker::transport_state_changed() return; } - if (_pos->speed == 1.0f) { - - if (_session->get_play_loop()) { - assert(_session->locations()->auto_loop_location()); - - if (_pos->frame == _session->locations()->auto_loop_location()->start()) { - send_start_event(0); - } else { - send_continue_event(0); - } - - } else if (_pos->frame == 0) { - send_start_event(0); - } else { - send_continue_event(0); - } - - // send_midi_clock_event (0); - - } else if (_pos->speed == 0.0f) { - send_stop_event (0); - send_position_event (llrint (_pos->midi_beats), 0); - } + _send_state = true; // tick (_pos->frame); } void -MidiClockTicker::position_changed (framepos_t) -{ -#if 0 - const double speed = _session->transport_speed(); - DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Transport Position Change: %1, speed: %2\n", position, speed)); - - if (speed == 0.0f && Config->get_send_midi_clock()) { - send_position_event (position, 0); - } - - _last_tick = position; -#endif -} - -void MidiClockTicker::transport_looped() { Location* loop_location = _session->locations()->auto_loop_location(); @@ -270,18 +210,68 @@ MidiClockTicker::transport_looped() } void -MidiClockTicker::tick (const framepos_t& /* transport_frame */) +MidiClockTicker::tick (const framepos_t& /* transport_frame */, pframes_t nframes) { if (!Config->get_send_midi_clock() || _session == 0 || _session->transport_speed() != 1.0f || _midi_port == 0) { return; } - MIDI::JackMIDIPort* mp = dynamic_cast<MIDI::JackMIDIPort*> (_midi_port); - if (! mp) { - return; + if (_send_pos) { + if (_pos->speed == 0.0f) { + uint32_t where = llrint (_pos->midi_beats); + send_position_event (where, 0, nframes); + } else if (_pos->speed == 1.0f) { +#if 1 + /* Experimental. To really do this and have accuracy, the + stop/locate/continue sequence would need queued to send immediately + before the next midi clock. */ + + send_stop_event (0, nframes); + + if (_pos->frame == 0) { + send_start_event (0, nframes); + } else { + uint32_t where = llrint (_pos->midi_beats); + send_position_event (where, 0, nframes); + send_continue_event (0, nframes); + } +#endif + } else { + /* Varispeed not supported */ + } + + _send_pos = true; } - const framepos_t end = _pos->frame + mp->nframes_this_cycle(); + if (_send_state) { + if (_pos->speed == 1.0f) { + if (_session->get_play_loop()) { + assert(_session->locations()->auto_loop_location()); + + if (_pos->frame == _session->locations()->auto_loop_location()->start()) { + send_start_event (0, nframes); + } else { + send_continue_event (0, nframes); + } + + } else if (_pos->frame == 0) { + send_start_event (0, nframes); + } else { + send_continue_event (0, nframes); + } + + // send_midi_clock_event (0); + + } else if (_pos->speed == 0.0f) { + send_stop_event (0, nframes); + send_position_event (llrint (_pos->midi_beats), 0, nframes); + } + + _send_state = false; + } + + + const framepos_t end = _pos->frame + nframes; double iter = _last_tick; while (true) { @@ -291,14 +281,14 @@ MidiClockTicker::tick (const framepos_t& /* transport_frame */) DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Tick: iter: %1, last tick time: %2, next tick time: %3, offset: %4, cycle length: %5\n", - iter, _last_tick, next_tick, next_tick_offset, mp ? mp->nframes_this_cycle() : 0)); + iter, _last_tick, next_tick, next_tick_offset, nframes)); - if (!mp || (next_tick_offset >= mp->nframes_this_cycle())) { + if (next_tick_offset >= nframes) { break; } if (next_tick_offset >= 0) { - send_midi_clock_event (next_tick_offset); + send_midi_clock_event (next_tick_offset, nframes); } iter = next_tick; @@ -308,7 +298,6 @@ MidiClockTicker::tick (const framepos_t& /* transport_frame */) _pos->frame = end; } - double MidiClockTicker::one_ppqn_in_frames (framepos_t transport_position) { @@ -322,7 +311,7 @@ MidiClockTicker::one_ppqn_in_frames (framepos_t transport_position) } void -MidiClockTicker::send_midi_clock_event (pframes_t offset) +MidiClockTicker::send_midi_clock_event (pframes_t offset, pframes_t nframes) { if (!_midi_port) { return; @@ -330,12 +319,13 @@ MidiClockTicker::send_midi_clock_event (pframes_t offset) DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Tick with offset %1\n", offset)); - static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_CLOCK }; - _midi_port->write (_midi_clock_tick, 1, offset); + static uint8_t tick_byte = { MIDI_CMD_COMMON_CLOCK }; + MidiBuffer& mb (_midi_port->get_midi_buffer (nframes)); + mb.push_back (offset, 1, &tick_byte); } void -MidiClockTicker::send_start_event (pframes_t offset) +MidiClockTicker::send_start_event (pframes_t offset, pframes_t nframes) { if (!_midi_port) { return; @@ -343,12 +333,13 @@ MidiClockTicker::send_start_event (pframes_t offset) DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Start %1\n", _last_tick)); - static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_START }; - _midi_port->write (_midi_clock_tick, 1, offset); + static uint8_t tick_byte = { MIDI_CMD_COMMON_START }; + MidiBuffer& mb (_midi_port->get_midi_buffer (nframes)); + mb.push_back (offset, 1, &tick_byte); } void -MidiClockTicker::send_continue_event (pframes_t offset) +MidiClockTicker::send_continue_event (pframes_t offset, pframes_t nframes) { if (!_midi_port) { return; @@ -356,12 +347,13 @@ MidiClockTicker::send_continue_event (pframes_t offset) DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Continue %1\n", _last_tick)); - static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_CONTINUE }; - _midi_port->write (_midi_clock_tick, 1, offset); + static uint8_t tick_byte = { MIDI_CMD_COMMON_CONTINUE }; + MidiBuffer& mb (_midi_port->get_midi_buffer (nframes)); + mb.push_back (offset, 1, &tick_byte); } void -MidiClockTicker::send_stop_event (pframes_t offset) +MidiClockTicker::send_stop_event (pframes_t offset, pframes_t nframes) { if (!_midi_port) { return; @@ -369,12 +361,13 @@ MidiClockTicker::send_stop_event (pframes_t offset) DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Stop %1\n", _last_tick)); - static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_STOP }; - _midi_port->write (_midi_clock_tick, 1, offset); + static uint8_t tick_byte = { MIDI_CMD_COMMON_STOP }; + MidiBuffer& mb (_midi_port->get_midi_buffer (nframes)); + mb.push_back (offset, 1, &tick_byte); } void -MidiClockTicker::send_position_event (uint32_t midi_beats, pframes_t offset) +MidiClockTicker::send_position_event (uint32_t midi_beats, pframes_t offset, pframes_t nframes) { if (!_midi_port) { return; @@ -391,7 +384,8 @@ MidiClockTicker::send_position_event (uint32_t midi_beats, pframes_t offset) msg[1] = midi_beats & 0x007f; msg[2] = midi_beats >> 7; - _midi_port->midimsg (msg, sizeof (msg), offset); + MidiBuffer& mb (_midi_port->get_midi_buffer (nframes)); + mb.push_back (offset, 3, &msg[0]); DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Song Position Sent: %1\n", midi_beats)); } diff --git a/libs/ardour/track.cc b/libs/ardour/track.cc index f02863393e..57239cb841 100644 --- a/libs/ardour/track.cc +++ b/libs/ardour/track.cc @@ -550,15 +550,15 @@ Track::playlist () } void -Track::request_jack_monitors_input (bool m) +Track::request_input_monitoring (bool m) { - _diskstream->request_jack_monitors_input (m); + _diskstream->request_input_monitoring (m); } void -Track::ensure_jack_monitors_input (bool m) +Track::ensure_input_monitoring (bool m) { - _diskstream->ensure_jack_monitors_input (m); + _diskstream->ensure_input_monitoring (m); } bool diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 87dce06a2a..485a2284bc 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -1,10 +1,11 @@ -#!/usr/bin/env python +1#!/usr/bin/env python from waflib.extras import autowaf as autowaf from waflib import Options import os import sys import re import subprocess +import sys # default state file version for this build CURRENT_SESSION_FILE_VERSION = 3001 @@ -22,6 +23,7 @@ path_prefix = 'libs/ardour/' libardour_sources = [ 'amp.cc', 'analyser.cc', + 'async_midi_port.cc', 'audio_buffer.cc', 'audio_diskstream.cc', 'audio_library.cc', @@ -44,6 +46,7 @@ libardour_sources = [ 'automation_control.cc', 'automation_list.cc', 'automation_watch.cc', + 'backend_search_path.cc', 'beats_frames_converter.cc', 'broadcast_info.cc', 'buffer.cc', @@ -128,6 +131,7 @@ libardour_sources = [ 'midi_stretch.cc', 'midi_track.cc', 'midi_ui.cc', + 'midiport_manager.cc', 'mix.cc', 'monitor_processor.cc', 'mtc_slave.cc', @@ -151,6 +155,7 @@ libardour_sources = [ 'plugin_manager.cc', 'port.cc', 'port_insert.cc', + 'port_manager.cc', 'port_set.cc', 'process_thread.cc', 'processor.cc', @@ -178,6 +183,7 @@ libardour_sources = [ 'session_events.cc', 'session_export.cc', 'session_handle.cc', + 'session_jack.cc', 'session_ltc.cc', 'session_metadata.cc', 'session_midi.cc', @@ -259,6 +265,10 @@ def configure(conf): atleast_version='0.1.0') autowaf.check_pkg(conf, 'sigc++-2.0', uselib_store='SIGCPP', atleast_version='2.0') + + if re.search ("linux", sys.platform) != None: + autowaf.check_pkg(conf, 'alsa', uselib_store='ALSA') + if Options.options.lv2: autowaf.check_pkg(conf, 'lv2', uselib_store='LV2', atleast_version='1.0.0', mandatory=True) @@ -303,60 +313,6 @@ def configure(conf): conf.check(header_name='unistd.h', define_name='HAVE_UNISTD',mandatory=False) - conf.check_cc(fragment = ''' -#include <jack/jack.h> -void callback(jack_status_t code, const char* reason, void* arg) { return; } -int main(int argc, char **argv) { - jack_client_t* c; - jack_on_info_shutdown(c, callback, (void*) 0); - return 0; -}''', - uselib= [ 'JACK' ], - msg = 'Checking for jack_on_info_shutdown', - define_name = 'HAVE_JACK_ON_INFO_SHUTDOWN', - okmsg = 'present') - - missing_jack_message = 'missing - a version of JACK that supports jack_port_set_latency_range() is required to compile Ardour3.' - - conf.check_cc(fragment = ''' -#include <jack/jack.h> -int main(int argc, char **argv) { - jack_port_t* p; - jack_latency_range_t r; - jack_port_set_latency_range(p, JackCaptureLatency, &r); - return 0; -}''', - uselib = [ 'JACK' ], - msg = 'Checking for new JACK latency API', - okmsg = 'present', - mandatory = True, - errmsg = missing_jack_message) - - conf.check_cc(fragment = ''' -#include <jack/jack.h> -int main(int argc, char **argv) { - jack_port_type_get_buffer_size((jack_client_t*)0, ""); - return 0; -}''', - uselib = [ 'JACK' ], - msg = 'Checking for new jack_port_type_get_buffer_size', - okmsg = 'present', - mandatory = True, - errmsg = missing_jack_message) - - conf.check_cc(fragment = ''' -#include <jack/transport.h> -int main(int argc, char** argv) { - jack_position_t pos; - pos.valid & JackVideoFrameOffset; - return 0; -}''', - uselib= [ 'JACK' ], - msg = 'Checking for JackVideoFrameOffset', - define_name = 'HAVE_JACK_VIDEO_SUPPORT', - mandatory = False, - okmsg = 'present') - if flac_supported(): conf.define ('HAVE_FLAC', 1) if ogg_supported(): |