Fix merging boolean automation + mute disconnect.
[ardour.git] / libs / ardour / audio_unit.cc
index 1acb5633cd050d74ffb5c860d0c18e859431723e..da18b969f583f3169fc73be1388544ee904b9d93 100644 (file)
@@ -71,7 +71,7 @@
 #define ArdourFindNext AudioComponentFindNext
 #endif
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 using namespace std;
 using namespace PBD;
@@ -430,7 +430,8 @@ AUPlugin::AUPlugin (AudioEngine& engine, Session& session, boost::shared_ptr<CAC
        , comp (_comp)
        , unit (new CAAudioUnit)
        , initialized (false)
-       , _current_block_size (0)
+       , _last_nframes (0)
+       , _current_latency (UINT_MAX)
        , _requires_fixed_size_buffers (false)
        , buffers (0)
        , variable_inputs (false)
@@ -441,13 +442,15 @@ AUPlugin::AUPlugin (AudioEngine& engine, Session& session, boost::shared_ptr<CAC
        , bus_outputs (0)
        , input_maxbuf (0)
        , input_offset (0)
+       , cb_offsets (0)
        , input_buffers (0)
        , input_map (0)
        , frames_processed (0)
        , audio_input_cnt (0)
        , _parameter_listener (0)
        , _parameter_listener_arg (0)
-       , last_transport_rolling (false)
+       , transport_frame (0)
+       , transport_speed (0)
        , last_transport_speed (0.0)
 {
        if (!preset_search_path_initialized) {
@@ -468,8 +471,8 @@ AUPlugin::AUPlugin (const AUPlugin& other)
        , comp (other.get_comp())
        , unit (new CAAudioUnit)
        , initialized (false)
-       , _current_block_size (0)
        , _last_nframes (0)
+       , _current_latency (UINT_MAX)
        , _requires_fixed_size_buffers (false)
        , buffers (0)
        , variable_inputs (false)
@@ -480,11 +483,15 @@ AUPlugin::AUPlugin (const AUPlugin& other)
        , bus_outputs (0)
        , input_maxbuf (0)
        , input_offset (0)
+       , cb_offsets (0)
        , input_buffers (0)
        , input_map (0)
        , frames_processed (0)
        , _parameter_listener (0)
        , _parameter_listener_arg (0)
+       , transport_frame (0)
+       , transport_speed (0)
+       , last_transport_speed (0.0)
 
 {
        init ();
@@ -508,6 +515,7 @@ AUPlugin::~AUPlugin ()
        free (buffers);
        free (bus_inputs);
        free (bus_outputs);
+       free (cb_offsets);
 }
 
 void
@@ -598,19 +606,19 @@ AUPlugin::init ()
        DEBUG_TRACE (DEBUG::AudioUnits, "count output elements\n");
        unit->GetElementCount (kAudioUnitScope_Output, output_elements);
 
+       cb_offsets = (framecnt_t*) calloc (input_elements, sizeof(framecnt_t));
        bus_inputs = (uint32_t*) calloc (input_elements, sizeof(uint32_t));
        bus_outputs = (uint32_t*) calloc (output_elements, sizeof(uint32_t));
 
        for (size_t i = 0; i < output_elements; ++i) {
-               AudioUnitReset (unit->AU(), kAudioUnitScope_Output, i);
+               unit->Reset (kAudioUnitScope_Output, i);
                AudioStreamBasicDescription fmt;
-               UInt32 sz = sizeof(AudioStreamBasicDescription);
-               err = AudioUnitGetProperty(unit->AU(), kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, i, &fmt, &sz);
+               err = unit->GetFormat (kAudioUnitScope_Output, i, fmt);
                if (err == noErr) {
                        bus_outputs[i] = fmt.mChannelsPerFrame;
                }
                CFStringRef name;
-               sz = sizeof (CFStringRef);
+               UInt32 sz = sizeof (CFStringRef);
                if (AudioUnitGetProperty (unit->AU(), kAudioUnitProperty_ElementName, kAudioUnitScope_Output,
                                        i, &name, &sz) == noErr
                                && sz > 0) {
@@ -622,15 +630,14 @@ AUPlugin::init ()
        }
 
        for (size_t i = 0; i < input_elements; ++i) {
-               AudioUnitReset (unit->AU(), kAudioUnitScope_Input, i);
+               unit->Reset (kAudioUnitScope_Input, i);
                AudioStreamBasicDescription fmt;
-               UInt32 sz = sizeof(AudioStreamBasicDescription);
-               err = AudioUnitGetProperty(unit->AU(), kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, i, &fmt, &sz);
+               err = unit->GetFormat (kAudioUnitScope_Input, i, fmt);
                if (err == noErr) {
                        bus_inputs[i] = fmt.mChannelsPerFrame;
                }
                CFStringRef name;
-               sz = sizeof (CFStringRef);
+               UInt32 sz = sizeof (CFStringRef);
                if (AudioUnitGetProperty (unit->AU(), kAudioUnitProperty_ElementName, kAudioUnitScope_Input,
                                        i, &name, &sz) == noErr
                                && sz > 0) {
@@ -799,8 +806,6 @@ AUPlugin::discover_parameters ()
                                break;
                        }
 
-                       d.min_unbound = 0; // lower is bound
-                       d.max_unbound = 0; // upper is bound
                        d.update_steps();
 
                        descriptors.push_back (d);
@@ -948,7 +953,12 @@ AUPlugin::default_value (uint32_t port)
 framecnt_t
 AUPlugin::signal_latency () const
 {
-       return unit->Latency() * _session.frame_rate();
+       guint lat = g_atomic_int_get (&_current_latency);;
+       if (lat == UINT_MAX) {
+               lat = unit->Latency() * _session.frame_rate();
+               g_atomic_int_set (&_current_latency, lat);
+       }
+       return lat;
 }
 
 void
@@ -1075,8 +1085,6 @@ AUPlugin::set_block_size (pframes_t nframes)
                activate ();
        }
 
-       _current_block_size = nframes;
-
        return 0;
 }
 
@@ -1125,17 +1133,17 @@ AUPlugin::configure_io (ChanCount in, ChanCount out)
        configured_output_busses = 0;
        /* reset busses */
        for (size_t i = 0; i < output_elements; ++i) {
-               AudioUnitReset (unit->AU(), kAudioUnitScope_Output, i);
+               unit->Reset (kAudioUnitScope_Output, i);
        }
        for (size_t i = 0; i < input_elements; ++i) {
-               AudioUnitReset (unit->AU(), kAudioUnitScope_Input, i);
+               unit->Reset (kAudioUnitScope_Input, i);
        }
 
        /* now assign the channels to available busses */
        uint32_t used_in = 0;
        uint32_t used_out = 0;
 
-       if (variable_inputs) {
+       if (variable_inputs || input_elements == 1) {
                // we only ever use the first bus
                if (input_elements > 1) {
                        warning << string_compose (_("AU %1 has multiple input busses and variable port count."), name()) << endmsg;
@@ -1164,7 +1172,7 @@ AUPlugin::configure_io (ChanCount in, ChanCount out)
                }
        }
 
-       if (variable_outputs) {
+       if (variable_outputs || output_elements == 1) {
                if (output_elements > 1) {
                        warning << string_compose (_("AU %1 has multiple output busses and variable port count."), name()) << endmsg;
                }
@@ -1200,8 +1208,8 @@ AUPlugin::configure_io (ChanCount in, ChanCount out)
        output_channels = used_out;
        /* reset plugin info to show currently configured state */
 
-       _info->n_inputs = in;
-       _info->n_outputs = out;
+       _info->n_inputs = ChanCount (DataType::AUDIO, used_in) + ChanCount (DataType::MIDI, _has_midi_input ? 1 : 0);
+       _info->n_outputs = ChanCount (DataType::AUDIO, used_out);
 
        if (was_initialized) {
                activate ();
@@ -1254,13 +1262,48 @@ AUPlugin::can_support_io_configuration (const ChanCount& in, ChanCount& out, Cha
                return false;
        }
 
-       vector<pair<int,int> >& io_configs = pinfo->cache.io_configs;
+       vector<pair<int,int> > io_configs = pinfo->cache.io_configs;
+
+       if (input_elements > 1) {
+               const vector<pair<int,int> >& ioc (pinfo->cache.io_configs);
+               for (vector<pair<int,int> >::const_iterator i = ioc.begin(); i != ioc.end(); ++i) {
+                       int32_t possible_in = i->first;
+                       int32_t possible_out = i->second;
+                       if (possible_in < 1 || possible_out < 1) {
+                               continue;
+                       }
+                       for (uint32_t i = 1; i < input_elements; ++i) {
+                               // can't use up-to bus_inputs[]
+                               // waves' SC-C6(s) for example fails to configure with only 1 input
+                               // on the 2nd bus.
+                               io_configs.push_back (pair<int,int> (possible_in + bus_inputs[i], possible_out));
+                       }
+               }
+       }
+
+       if (output_elements > 1) {
+               const vector<pair<int,int> >& ioc (pinfo->cache.io_configs);
+               for (vector<pair<int,int> >::const_iterator i = ioc.begin(); i != ioc.end(); ++i) {
+                       int32_t possible_in = i->first;
+                       int32_t possible_out = i->second;
+                       if (possible_in < 1 || possible_out < 1) {
+                               continue;
+                       }
+                       for (uint32_t i = 1; i < output_elements; ++i) {
+                               int32_t c = bus_outputs[i];
+                               for (uint32_t j = 1; j < i; ++j) {
+                                       c += bus_outputs [j];
+                               }
+                               io_configs.push_back (pair<int,int> (possible_in, possible_out + c));
+                       }
+               }
+       }
 
        DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("%1 has %2 IO configurations, looking for %3 in, %4 out\n",
                                                        name(), io_configs.size(), in, out));
 
 #if 0
-       printf ("AU I/O Configs %s %d\n", name().c_str(), io_configs.size());
+       printf ("AU I/O Configs %s %d\n", name(), io_configs.size());
        for (vector<pair<int,int> >::iterator i = io_configs.begin(); i != io_configs.end(); ++i) {
                printf ("- I/O  %d / %d\n", i->first, i->second);
        }
@@ -1342,7 +1385,7 @@ AUPlugin::can_support_io_configuration (const ChanCount& in, ChanCount& out, Cha
   _output_configs.insert (0);
 
 #define UPTO(nch) {                                \
-  for (int n = 1; n < nch; ++n) {                  \
+  for (int n = 1; n <= nch; ++n) {                 \
     _output_configs.insert (n);                    \
   }                                                \
 }
@@ -1543,8 +1586,6 @@ AUPlugin::render_callback(AudioUnitRenderActionFlags*,
                          AudioBufferList* ioData)
 {
        /* not much to do with audio - the data is already in the buffers given to us in connect_and_run() */
-       cerr << string_compose ("%1: render callback, frames %2 bus %3 bufs %4\n",
-                       name(), inNumberFrames, bus, ioData->mNumberBuffers);
 
        // DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("%1: render callback, frames %2 bus %3 bufs %4\n",
        // name(), inNumberFrames, bus, ioData->mNumberBuffers));
@@ -1575,21 +1616,25 @@ AUPlugin::render_callback(AudioUnitRenderActionFlags*,
                bool valid = false;
                uint32_t idx = input_map->get (DataType::AUDIO, i + busoff, &valid);
                if (valid) {
-                       ioData->mBuffers[i].mData = input_buffers->get_audio (idx).data (cb_offset + input_offset);
+                       ioData->mBuffers[i].mData = input_buffers->get_audio (idx).data (cb_offsets[bus] + input_offset);
                } else {
-                       ioData->mBuffers[i].mData = silent_bufs.get_audio(0).data (cb_offset + input_offset);
+                       ioData->mBuffers[i].mData = silent_bufs.get_audio(0).data (cb_offsets[bus] + input_offset);
                }
        }
-#if 0 // TODO  per bus
-       cb_offset += inNumberFrames;
-#endif
+       cb_offsets[bus] += inNumberFrames;
        return noErr;
 }
 
 int
-AUPlugin::connect_and_run (BufferSet& bufs, ChanMapping in_map, ChanMapping out_map, pframes_t nframes, framecnt_t offset)
+AUPlugin::connect_and_run (BufferSet& bufs,
+               framepos_t start, framepos_t end, double speed,
+               ChanMapping in_map, ChanMapping out_map,
+               pframes_t nframes, framecnt_t offset)
 {
-       Plugin::connect_and_run (bufs, in_map, out_map, nframes, offset);
+       Plugin::connect_and_run(bufs, start, end, speed, in_map, out_map, nframes, offset);
+
+       transport_frame = start;
+       transport_speed = speed;
 
        AudioUnitRenderActionFlags flags = 0;
        AudioTimeStamp ts;
@@ -1601,17 +1646,20 @@ AUPlugin::connect_and_run (BufferSet& bufs, ChanMapping in_map, ChanMapping out_
        }
 
        /* test if we can run in-place; only compare audio buffers */
-       bool inplace = true; // configured_output_busses == 1;
+       bool inplace = true; // TODO check plugin-insert in-place ?
        ChanMapping::Mappings inmap (in_map.mappings ());
        ChanMapping::Mappings outmap (out_map.mappings ());
-       assert (outmap[DataType::AUDIO].size () > 0);
+       if (outmap[DataType::AUDIO].size () == 0) {
+               inplace = false;
+       }
        if (inmap[DataType::AUDIO].size() > 0 && inmap != outmap) {
                inplace = false;
        }
 
-       DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("%1 in %2 out %3 MIDI %4 bufs %5 (available %6) Inplace: %7\n",
+       DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("%1 in %2 out %3 MIDI %4 bufs %5 (available %6) InBus %7 OutBus %8 Inplace: %9 var-i/o %10 %11\n",
                                name(), input_channels, output_channels, _has_midi_input,
-                               bufs.count(), bufs.available(), inplace));
+                               bufs.count(), bufs.available(),
+                               configured_input_busses, configured_output_busses, inplace, variable_inputs, variable_outputs));
 
        /* the apparent number of buffers matches our input configuration, but we know that the bufferset
         * has the capacity to handle our outputs.
@@ -1623,7 +1671,9 @@ AUPlugin::connect_and_run (BufferSet& bufs, ChanMapping in_map, ChanMapping out_
        input_map = &in_map;
        input_maxbuf = bufs.count().n_audio(); // number of input audio buffers
        input_offset = offset;
-       cb_offset = 0;
+       for (size_t i = 0; i < input_elements; ++i) {
+               cb_offsets[i] = 0;
+       }
 
        ChanCount bufs_count (DataType::AUDIO, 1);
        BufferSet& scratch_bufs = _session.get_scratch_buffers(bufs_count);
@@ -1634,7 +1684,7 @@ AUPlugin::connect_and_run (BufferSet& bufs, ChanMapping in_map, ChanMapping out_
                        /* one MIDI port/buffer only */
                        MidiBuffer& m = bufs.get_midi (i);
                        for (MidiBuffer::iterator i = m.begin(); i != m.end(); ++i) {
-                               Evoral::MIDIEvent<framepos_t> ev (*i);
+                               Evoral::Event<framepos_t> ev (*i);
                                if (ev.is_channel_event()) {
                                        const uint8_t* b = ev.buffer();
                                        DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("%1: MIDI event %2\n", name(), ev));
@@ -1645,11 +1695,19 @@ AUPlugin::connect_and_run (BufferSet& bufs, ChanMapping in_map, ChanMapping out_
                }
        }
 
+       assert (input_maxbuf < 512);
+       std::bitset<512> used_outputs;
+
        bool ok = true;
        uint32_t busoff = 0;
        uint32_t remain = output_channels;
        for (uint32_t bus = 0; remain > 0 && bus < configured_output_busses; ++bus) {
-               uint32_t cnt = std::min (remain, bus_outputs[bus]);
+               uint32_t cnt;
+               if (variable_outputs || (output_elements == configured_output_busses && configured_output_busses == 1)) {
+                       cnt = output_channels;
+               } else {
+                       cnt = std::min (remain, bus_outputs[bus]);
+               }
                assert (cnt > 0);
 
                buffers->mNumberBuffers = cnt;
@@ -1694,6 +1752,7 @@ AUPlugin::connect_and_run (BufferSet& bufs, ChanMapping in_map, ChanMapping out_
                                bool valid = false;
                                uint32_t idx = out_map.get (DataType::AUDIO, i + busoff, &valid);
                                if (!valid) continue;
+                               used_outputs.set (i + busoff);
                                Sample* expected_buffer_address = bufs.get_audio (idx).data (offset);
                                if (expected_buffer_address != buffers->mBuffers[i].mData) {
                                        /* plugin provided its own buffer for output so copy it back to where we want it */
@@ -1701,7 +1760,8 @@ AUPlugin::connect_and_run (BufferSet& bufs, ChanMapping in_map, ChanMapping out_
                                }
                        }
                } else {
-                       error << string_compose (_("AU: render error for %1, bus %2 status = %2"), name(), bus, err) << endmsg;
+                       DEBUG_TRACE (DEBUG::AudioUnits, string_compose (_("AU: render error for %1, bus %2 status = %3\n"), name(), bus, err));
+                       error << string_compose (_("AU: render error for %1, bus %2 status = %3"), name(), bus, err) << endmsg;
                        ok = false;
                        break;
                }
@@ -1709,17 +1769,20 @@ AUPlugin::connect_and_run (BufferSet& bufs, ChanMapping in_map, ChanMapping out_
                remain -= cnt;
                busoff += bus_outputs[bus];
        }
-#if 0
+
        /* now silence any buffers that were passed in but the that the plugin
         * did not fill/touch/use.
+        *
+        * TODO: optimize, when plugin-insert is processing in-place
+        * unconnected buffers are (also) cleared there.
         */
-       for (;i < output_channels; ++i) {
+       for (uint32_t i = 0; i < input_maxbuf; ++i) {
+               if (used_outputs.test (i)) { continue; }
                bool valid = false;
                uint32_t idx = out_map.get (DataType::AUDIO, i, &valid);
                if (!valid) continue;
                memset (bufs.get_audio (idx).data (offset), 0, nframes * sizeof (Sample));
        }
-#endif
 
        input_maxbuf = 0;
 
@@ -1738,29 +1801,12 @@ AUPlugin::get_beat_and_tempo_callback (Float64* outCurrentBeat,
 
        DEBUG_TRACE (DEBUG::AudioUnits, "AU calls ardour beat&tempo callback\n");
 
-       /* more than 1 meter or more than 1 tempo means that a simplistic computation
-          (and interpretation) of a beat position will be incorrect. So refuse to
-          offer the value.
-       */
-
-       if (tmap.n_tempos() > 1 || tmap.n_meters() > 1) {
-               return kAudioUnitErr_CannotDoInCurrentContext;
-       }
-
-       Timecode::BBT_Time bbt;
-       TempoMetric metric = tmap.metric_at (_session.transport_frame() + input_offset);
-       tmap.bbt_time (_session.transport_frame() + input_offset, bbt);
-
        if (outCurrentBeat) {
-               float beat;
-               beat = metric.meter().divisions_per_bar() * bbt.bars;
-               beat += bbt.beats;
-               beat += bbt.ticks / Timecode::BBT_Time::ticks_per_beat;
-               *outCurrentBeat = beat;
+               *outCurrentBeat = tmap.quarter_note_at_frame (transport_frame + input_offset);
        }
 
        if (outCurrentTempo) {
-               *outCurrentTempo = floor (metric.tempo().beats_per_minute());
+               *outCurrentTempo = tmap.tempo_at_frame (transport_frame + input_offset).quarter_notes_per_minute();
        }
 
        return noErr;
@@ -1777,27 +1823,18 @@ AUPlugin::get_musical_time_location_callback (UInt32*   outDeltaSampleOffsetToNe
 
        DEBUG_TRACE (DEBUG::AudioUnits, "AU calls ardour music time location callback\n");
 
-       /* more than 1 meter or more than 1 tempo means that a simplistic computation
-          (and interpretation) of a beat position will be incorrect. So refuse to
-          offer the value.
-       */
-
-       if (tmap.n_tempos() > 1 || tmap.n_meters() > 1) {
-               return kAudioUnitErr_CannotDoInCurrentContext;
-       }
-
-       Timecode::BBT_Time bbt;
-       TempoMetric metric = tmap.metric_at (_session.transport_frame() + input_offset);
-       tmap.bbt_time (_session.transport_frame() + input_offset, bbt);
+       TempoMetric metric = tmap.metric_at (transport_frame + input_offset);
+       Timecode::BBT_Time bbt = _session.tempo_map().bbt_at_frame (transport_frame + input_offset);
 
        if (outDeltaSampleOffsetToNextBeat) {
                if (bbt.ticks == 0) {
                        /* on the beat */
                        *outDeltaSampleOffsetToNextBeat = 0;
                } else {
-                       *outDeltaSampleOffsetToNextBeat = (UInt32)
-                               floor (((Timecode::BBT_Time::ticks_per_beat - bbt.ticks)/Timecode::BBT_Time::ticks_per_beat) * // fraction of a beat to next beat
-                                      metric.tempo().frames_per_beat (_session.frame_rate())); // frames per beat
+                       double const next_beat = ceil (tmap.quarter_note_at_frame (transport_frame + input_offset));
+                       framepos_t const next_beat_frame = tmap.frame_at_quarter_note (next_beat);
+
+                       *outDeltaSampleOffsetToNextBeat = next_beat_frame - (transport_frame + input_offset);
                }
        }
 
@@ -1816,8 +1853,10 @@ AUPlugin::get_musical_time_location_callback (UInt32*   outDeltaSampleOffsetToNe
                   3|1|0 -> 1 + (2 * divisions_per_bar)
                   etc.
                */
+               bbt.beats = 1;
+               bbt.ticks = 0;
 
-               *outCurrentMeasureDownBeat = 1 + metric.meter().divisions_per_bar() * (bbt.bars - 1);
+               *outCurrentMeasureDownBeat = tmap.quarter_note_at_bbt (bbt);
        }
 
        return noErr;
@@ -1831,22 +1870,20 @@ AUPlugin::get_transport_state_callback (Boolean*  outIsPlaying,
                                        Float64*  outCycleStartBeat,
                                        Float64*  outCycleEndBeat)
 {
-       bool rolling;
-       float speed;
+       const bool rolling = (transport_speed != 0);
+       const bool last_transport_rolling = (last_transport_speed != 0);
 
        DEBUG_TRACE (DEBUG::AudioUnits, "AU calls ardour transport state callback\n");
 
-       rolling = _session.transport_rolling();
-       speed = _session.transport_speed ();
 
        if (outIsPlaying) {
-               *outIsPlaying = _session.transport_rolling();
+               *outIsPlaying = rolling;
        }
 
        if (outTransportStateChanged) {
                if (rolling != last_transport_rolling) {
                        *outTransportStateChanged = true;
-               } else if (speed != last_transport_speed) {
+               } else if (transport_speed != last_transport_speed) {
                        *outTransportStateChanged = true;
                } else {
                        *outTransportStateChanged = false;
@@ -1857,13 +1894,14 @@ AUPlugin::get_transport_state_callback (Boolean*  outIsPlaying,
                /* this assumes that the AU can only call this host callback from render context,
                   where input_offset is valid.
                */
-               *outCurrentSampleInTimeLine = _session.transport_frame() + input_offset;
+               *outCurrentSampleInTimeLine = transport_frame + input_offset;
        }
 
        if (outIsCycling) {
+               // TODO check bounce-processing
                Location* loc = _session.locations()->auto_loop_location();
 
-               *outIsCycling = (loc && _session.transport_rolling() && _session.get_play_loop());
+               *outIsCycling = (loc && rolling && _session.get_play_loop());
 
                if (*outIsCycling) {
 
@@ -1871,45 +1909,20 @@ AUPlugin::get_transport_state_callback (Boolean*  outIsPlaying,
 
                                TempoMap& tmap (_session.tempo_map());
 
-                               /* more than 1 meter means that a simplistic computation (and interpretation) of
-                                  a beat position will be incorrect. so refuse to offer the value.
-                               */
-
-                               if (tmap.n_meters() > 1) {
-                                       return kAudioUnitErr_CannotDoInCurrentContext;
-                               }
-
                                Timecode::BBT_Time bbt;
 
                                if (outCycleStartBeat) {
-                                       TempoMetric metric = tmap.metric_at (loc->start() + input_offset);
-                                       _session.tempo_map().bbt_time (loc->start(), bbt);
-
-                                       float beat;
-                                       beat = metric.meter().divisions_per_bar() * bbt.bars;
-                                       beat += bbt.beats;
-                                       beat += bbt.ticks / Timecode::BBT_Time::ticks_per_beat;
-
-                                       *outCycleStartBeat = beat;
+                                       *outCycleStartBeat = tmap.quarter_note_at_frame (loc->start() + input_offset);
                                }
 
                                if (outCycleEndBeat) {
-                                       TempoMetric metric = tmap.metric_at (loc->end() + input_offset);
-                                       _session.tempo_map().bbt_time (loc->end(), bbt);
-
-                                       float beat;
-                                       beat = metric.meter().divisions_per_bar() * bbt.bars;
-                                       beat += bbt.beats;
-                                       beat += bbt.ticks / Timecode::BBT_Time::ticks_per_beat;
-
-                                       *outCycleEndBeat = beat;
+                                       *outCycleEndBeat = tmap.quarter_note_at_frame (loc->end() + input_offset);
                                }
                        }
                }
        }
 
-       last_transport_rolling = rolling;
-       last_transport_speed = speed;
+       last_transport_speed = transport_speed;
 
        return noErr;
 }
@@ -1943,6 +1956,8 @@ AUPlugin::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) const
                        break;
        }
 
+       std::string busname;
+
        if (dt == DataType::AUDIO) {
                if (input) {
                        uint32_t pid = id;
@@ -1950,6 +1965,8 @@ AUPlugin::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) const
                                if (pid < bus_inputs[bus]) {
                                        id = pid;
                                        ss << _bus_name_in[bus];
+                                       ss << " / Bus " << (1 + bus);
+                                       busname = _bus_name_in[bus];
                                        break;
                                }
                                pid -= bus_inputs[bus];
@@ -1961,6 +1978,8 @@ AUPlugin::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) const
                                if (pid < bus_outputs[bus]) {
                                        id = pid;
                                        ss << _bus_name_out[bus];
+                                       ss << " / Bus " << (1 + bus);
+                                       busname = _bus_name_out[bus];
                                        break;
                                }
                                pid -= bus_outputs[bus];
@@ -1977,6 +1996,10 @@ AUPlugin::describe_io_port (ARDOUR::DataType dt, bool input, uint32_t id) const
        ss << (id + 1);
 
        Plugin::IOPortDescription iod (ss.str());
+       if (!busname.empty()) {
+               iod.group_name = busname;
+               iod.group_channel = id;
+       }
        return iod;
 }
 
@@ -1991,9 +2014,16 @@ AUPlugin::describe_parameter (Evoral::Parameter param)
 }
 
 void
-AUPlugin::print_parameter (uint32_t /*param*/, char* /*buf*/, uint32_t /*len*/) const
+AUPlugin::print_parameter (uint32_t param, char* buf, uint32_t len) const
 {
        // NameValue stuff here
+       if (buf && len) {
+               if (param < parameter_count()) {
+                       snprintf (buf, len, "%.3f", get_parameter (param));
+               } else {
+                       strcat (buf, "0");
+               }
+       }
 }
 
 bool
@@ -2038,7 +2068,7 @@ AUPlugin::parameter_is_output (uint32_t param) const
 void
 AUPlugin::add_state (XMLNode* root) const
 {
-       LocaleGuard lg (X_("C"));
+       LocaleGuard lg;
        CFDataRef xmlData;
        CFPropertyListRef propertyList;
 
@@ -2077,7 +2107,7 @@ AUPlugin::set_state(const XMLNode& node, int version)
 {
        int ret = -1;
        CFPropertyListRef propertyList;
-       LocaleGuard lg (X_("C"));
+       LocaleGuard lg;
 
        if (node.name() != state_node_name()) {
                error << _("Bad node sent to AUPlugin::set_state") << endmsg;
@@ -2533,8 +2563,10 @@ AUPlugin::find_presets ()
        /* add factory presets */
 
        for (FactoryPresetMap::iterator i = factory_preset_map.begin(); i != factory_preset_map.end(); ++i) {
-               /* XXX: dubious */
-               string const uri = string_compose ("%1", _presets.size ());
+               /* XXX: dubious -- deleting & re-adding a preset -> same URI
+                * good that we don't support deleting AU presets :)
+                */
+               string const uri = PBD::to_string<uint32_t> (_presets.size ());
                _presets.insert (make_pair (uri, Plugin::PresetRecord (uri, i->first, false)));
                DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Adding Factory Preset: %1 > %2\n", i->first, i->second));
        }
@@ -2690,6 +2722,8 @@ AUPluginInfo::discover (bool scan_only)
 
        if (!Glib::file_test (au_cache_path(), Glib::FILE_TEST_EXISTS)) {
                ARDOUR::BootMessage (_("Discovering AudioUnit plugins (could take some time ...)"));
+               // flush RAM cache -- after clear_cache()
+               cached_info.clear();
        }
        // create crash log file
        au_start_crashlog ();
@@ -2872,24 +2906,24 @@ AUPluginInfo::discover_by_description (PluginInfoList& plugs, CAComponentDescrip
                        continue;
 
                case kAudioUnitType_Output:
-                       info->category = _("AudioUnit Outputs");
+                       info->category = _("AudioUnit Output");
                        break;
                case kAudioUnitType_MusicDevice:
-                       info->category = _("AudioUnit Instruments");
+                       info->category = _("Instrument");
                        has_midi_in = true;
                        break;
                case kAudioUnitType_MusicEffect:
-                       info->category = _("AudioUnit MusicEffects");
+                       info->category = _("Music Effect");
                        has_midi_in = true;
                        break;
                case kAudioUnitType_Effect:
-                       info->category = _("AudioUnit Effects");
+                       info->category = _("Effect");
                        break;
                case kAudioUnitType_Mixer:
-                       info->category = _("AudioUnit Mixers");
+                       info->category = _("Mixer");
                        break;
                case kAudioUnitType_Generator:
-                       info->category = _("AudioUnit Generators");
+                       info->category = _("Generator");
                        break;
                default:
                        info->category = _("AudioUnit (Unknown)");
@@ -3030,17 +3064,21 @@ AUPluginInfo::cached_io_configuration (const std::string& unique_id,
        }
 
        if (ret > 0) {
-
-               /* no explicit info available, so default to 1in/1out */
-
-               /* XXX this is wrong. we should be indicating wildcard values */
-
+               /* AU is expected to deal with same channel valance in and out */
                cinfo.io_configs.push_back (pair<int,int> (-1, -1));
-
        } else {
-               /* store each configuration */
-               if (comp.Desc().IsGenerator() || comp.Desc().IsMusicDevice()) {
-                       // incrementally add busses
+               /* CAAudioUnit::GetChannelInfo silently merges bus formats
+                * check if this was the case and if so, add
+                * bus configs as incremental options.
+                */
+               Boolean* isWritable = 0;
+               UInt32  dataSize = 0;
+               OSStatus result = AudioUnitGetPropertyInfo (unit.AU(),
+                               kAudioUnitProperty_SupportedNumChannels,
+                               kAudioUnitScope_Global, 0,
+                               &dataSize, isWritable);
+               if (result != noErr && (comp.Desc().IsGenerator() || comp.Desc().IsMusicDevice())) {
+                       /* incrementally add busses */
                        int in = 0;
                        int out = 0;
                        for (uint32_t n = 0; n < cnt; ++n) {
@@ -3049,6 +3087,7 @@ AUPluginInfo::cached_io_configuration (const std::string& unique_id,
                                cinfo.io_configs.push_back (pair<int,int> (in, out));
                        }
                } else {
+                       /* store each configuration */
                        for (uint32_t n = 0; n < cnt; ++n) {
                                cinfo.io_configs.push_back (pair<int,int> (channel_info[n].inChannels,
                                                        channel_info[n].outChannels));
@@ -3064,6 +3103,17 @@ AUPluginInfo::cached_io_configuration (const std::string& unique_id,
        return 0;
 }
 
+void
+AUPluginInfo::clear_cache ()
+{
+       const string& fn = au_cache_path();
+       if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
+               ::g_unlink(fn.c_str());
+       }
+       // keep cached_info in RAM until restart or re-scan
+       cached_info.clear();
+}
+
 void
 AUPluginInfo::add_cached_info (const std::string& id, AUPluginCachedInfo& cinfo)
 {
@@ -3078,22 +3128,19 @@ AUPluginInfo::save_cached_info ()
        XMLNode* node;
 
        node = new XMLNode (X_("AudioUnitPluginCache"));
-       node->add_property( "version", AU_CACHE_VERSION );
+       node->set_property( "version", AU_CACHE_VERSION );
 
        for (map<string,AUPluginCachedInfo>::iterator i = cached_info.begin(); i != cached_info.end(); ++i) {
                XMLNode* parent = new XMLNode (X_("plugin"));
-               parent->add_property ("id", i->first);
+               parent->set_property ("id", i->first);
                node->add_child_nocopy (*parent);
 
                for (vector<pair<int, int> >::iterator j = i->second.io_configs.begin(); j != i->second.io_configs.end(); ++j) {
 
                        XMLNode* child = new XMLNode (X_("io"));
-                       char buf[32];
 
-                       snprintf (buf, sizeof (buf), "%d", j->first);
-                       child->add_property (X_("in"), buf);
-                       snprintf (buf, sizeof (buf), "%d", j->second);
-                       child->add_property (X_("out"), buf);
+                       child->set_property (X_("in"), j->first);
+                       child->set_property (X_("out"), j->second);
                        parent->add_child_nocopy (*child);
                }
 
@@ -3132,7 +3179,7 @@ AUPluginInfo::load_cached_info ()
        }
 
        //initial version has incorrectly stored i/o info, and/or garbage chars.
-       const XMLProperty* version = root->property(X_("version"));
+       XMLProperty const * version = root->property(X_("version"));
        if (! ((version != NULL) && (version->value() == X_(AU_CACHE_VERSION)))) {
                error << "au_cache is not correct version.  AU plugins will be re-scanned" << endmsg;
                return -1;
@@ -3150,13 +3197,12 @@ AUPluginInfo::load_cached_info ()
 
                        const XMLNode* gchild;
                        const XMLNodeList gchildren = child->children();
-                       const XMLProperty* prop = child->property (X_("id"));
 
-                       if (!prop) {
+                       string id;
+                       if (!child->get_property (X_("id"), id)) {
                                continue;
                        }
 
-                       string id = prop->value();
                        string fixed;
                        string version;
 
@@ -3186,16 +3232,10 @@ AUPluginInfo::load_cached_info ()
 
                                if (gchild->name() == X_("io")) {
 
-                                       int in;
-                                       int out;
-                                       const XMLProperty* iprop;
-                                       const XMLProperty* oprop;
-
-                                       if (((iprop = gchild->property (X_("in"))) != 0) &&
-                                           ((oprop = gchild->property (X_("out"))) != 0)) {
-                                               in = atoi (iprop->value());
-                                               out = atoi (oprop->value());
+                                       int32_t in;
+                                       int32_t out;
 
+                                       if (gchild->get_property (X_("in"), in) && gchild->get_property (X_("out"), out)) {
                                                cinfo.io_configs.push_back (pair<int,int> (in, out));
                                        }
                                }
@@ -3286,6 +3326,19 @@ AUPlugin::create_parameter_listener (AUEventListenerProc cb, void* arg, float in
 
        _parameter_listener_arg = arg;
 
+       // listen for latency changes
+       AudioUnitEvent event;
+       event.mEventType = kAudioUnitEvent_PropertyChange;
+       event.mArgument.mProperty.mAudioUnit = unit->AU();
+       event.mArgument.mProperty.mPropertyID = kAudioUnitProperty_Latency;
+       event.mArgument.mProperty.mScope = kAudioUnitScope_Global;
+       event.mArgument.mProperty.mElement = 0;
+
+       if (AUEventListenerAddEventType (_parameter_listener, _parameter_listener_arg, &event) != noErr) {
+               PBD::error << "Failed to create latency event listener\n";
+               // TODO don't cache _current_latency
+       }
+
        return 0;
 }
 
@@ -3382,6 +3435,15 @@ AUPlugin::_parameter_change_listener (void* arg, void* src, const AudioUnitEvent
 void
 AUPlugin::parameter_change_listener (void* /*arg*/, void* src, const AudioUnitEvent* event, UInt64 /*host_time*/, Float32 new_value)
 {
+       if (event->mEventType == kAudioUnitEvent_PropertyChange) {
+               if (event->mArgument.mProperty.mPropertyID == kAudioUnitProperty_Latency) {
+                       DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Latency Change Event %1 <> %2\n", new_value, unit->Latency()));
+                       guint lat = unit->Latency() * _session.frame_rate();
+                       g_atomic_int_set (&_current_latency, lat);
+               }
+               return;
+       }
+
         ParameterMap::iterator i;
 
         if ((i = parameter_map.find (event->mArgument.mParameter.mParameterID)) == parameter_map.end()) {