Make sure buses and tracks have unique names.
[ardour.git] / libs / ardour / route.cc
index 38cd7ad37dbf741b5bd8db0de1627b6bd55f9e03..95571bd3dab2f48254876fbcb8486547d7f8b479 100644 (file)
@@ -15,7 +15,6 @@
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-    $Id$
 */
 
 #include <cmath>
@@ -24,6 +23,7 @@
 
 #include <sigc++/bind.h>
 #include <pbd/xml++.h>
+#include <pbd/enumwriter.h>
 
 #include <ardour/timestamps.h>
 #include <ardour/buffer.h>
@@ -48,9 +48,8 @@ using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
 
-
 uint32_t Route::order_key_cnt = 0;
-
+sigc::signal<void> Route::SyncOrderKeys;
 
 Route::Route (Session& sess, string name, int input_min, int input_max, int output_min, int output_max, Flag flg, DataType default_type)
        : IO (sess, name, input_min, input_max, output_min, output_max, default_type),
@@ -78,8 +77,8 @@ Route::init ()
        _soloed = false;
        _solo_safe = false;
        _phase_invert = false;
-       order_keys[N_("signal")] = order_key_cnt++;
-       _active = true;
+       _denormal_protection = false;
+       order_keys[strdup (N_("signal"))] = order_key_cnt++;
        _silent = false;
        _meter_point = MeterPostFader;
        _initial_delay = 0;
@@ -112,7 +111,12 @@ Route::init ()
 
 Route::~Route ()
 {
-       clear_redirects (this);
+       clear_redirects (PreFader, this);
+       clear_redirects (PostFader, this);
+
+       for (OrderKeys::iterator i = order_keys.begin(); i != order_keys.end(); ++i) {
+               free ((void*)(i->first));
+       }
 
        if (_control_outs) {
                delete _control_outs;
@@ -135,24 +139,65 @@ Route::remote_control_id() const
 }
 
 long
-Route::order_key (string name) const
+Route::order_key (const char* name) const
 {
        OrderKeys::const_iterator i;
        
-       if ((i = order_keys.find (name)) == order_keys.end()) {
-               return -1;
+       for (i = order_keys.begin(); i != order_keys.end(); ++i) {
+               if (!strcmp (name, i->first)) {
+                       return i->second;
+               }
        }
 
-       return (*i).second;
+       return -1;
 }
 
 void
-Route::set_order_key (string name, long n)
+Route::set_order_key (const char* name, long n)
 {
-       order_keys[name] = n;
+       order_keys[strdup(name)] = n;
+
+       if (Config->get_sync_all_route_ordering()) {
+               for (OrderKeys::iterator x = order_keys.begin(); x != order_keys.end(); ++x) {
+                       x->second = n;
+               }
+       } 
+
        _session.set_dirty ();
 }
 
+void
+Route::sync_order_keys ()
+{
+       uint32_t key;
+       
+       if (order_keys.empty()) {
+               return;
+       }
+       
+       OrderKeys::iterator x = order_keys.begin();
+       key = x->second;
+       ++x;
+
+       for (; x != order_keys.end(); ++x) {
+               x->second = key;
+       }
+}
+
+string
+Route::ensure_track_or_route_name(string name, Session &session)
+{
+       string newname = name;
+
+       while (session.route_by_name (newname)!=NULL)
+       {
+               newname = bump_name_once (newname);
+       }
+
+       return newname;
+}
+
+
 void
 Route::inc_gain (gain_t fraction, void *src)
 {
@@ -334,6 +379,22 @@ Route::process_output_buffers (vector<Sample*>& bufs, uint32_t nbufs,
                } 
        } 
 
+       /* -----------------------------------------------------------------------------------------------------
+          DENORMAL CONTROL
+          -------------------------------------------------------------------------------------------------- */
+
+       if (_denormal_protection || Config->get_denormal_protection()) {
+
+               for (n = 0; n < nbufs; ++n)  {
+                       Sample *sp = bufs[n];
+                       
+                       for (nframes_t nx = offset; nx < nframes + offset; ++nx) {
+                               sp[nx] += 1.0e-27f;
+                       }
+               }
+       }
+
+
        /* ----------------------------------------------------------------------------------------------------
           PRE-FADER REDIRECTS
           -------------------------------------------------------------------------------------------------- */
@@ -414,7 +475,7 @@ Route::process_output_buffers (vector<Sample*>& bufs, uint32_t nbufs,
                        
                } else {
 
-                       co->deliver_output (bufs, nbufs, nframes, offset);
+                       co->deliver_output_no_pan (bufs, nbufs, nframes, offset);
                        
                } 
        } 
@@ -431,13 +492,9 @@ Route::process_output_buffers (vector<Sample*>& bufs, uint32_t nbufs,
                
            // OR recording 
                
-               // h/w monitoring not in use 
+               // AND software monitoring required
                
-               (!Config->get_monitoring_model() == HardwareMonitoring && 
-
-                // AND software monitoring required
-
-                Config->get_monitoring_model() == SoftwareMonitoring)) { 
+               (Config->get_monitoring_model() == SoftwareMonitoring)) {
                
                if (apply_gain_automation) {
                        
@@ -578,7 +635,7 @@ Route::process_output_buffers (vector<Sample*>& bufs, uint32_t nbufs,
                        (no_monitor && record_enabled() && (!Config->get_auto_input() || _session.actively_recording()))
 
                        ) {
-
+                       
                        co->silence (nframes, offset);
                        
                } else {
@@ -635,7 +692,7 @@ Route::process_output_buffers (vector<Sample*>& bufs, uint32_t nbufs,
                        if (_meter_point == MeterPostFader) {
                                reset_peak_meters ();
                        }
-                       
+
                        IO::silence (nframes, offset);
                        
                } else {
@@ -663,7 +720,6 @@ Route::process_output_buffers (vector<Sample*>& bufs, uint32_t nbufs,
           -------------------------------------------------------------------------------------------------- */
 
        if (meter && (_meter_point == MeterPostFader)) {
-//             cerr << "meter post" << endl;
 
                if ((_gain == 0 && !apply_gain_automation) || dmg == 0) {
                        uint32_t no = n_outputs();
@@ -717,8 +773,17 @@ Route::set_phase_invert (bool yn, void *src)
 {
        if (_phase_invert != yn) {
                _phase_invert = yn;
+               //  phase_invert_changed (src); /* EMIT SIGNAL */
+       }
+}
+
+void
+Route::set_denormal_protection (bool yn, void *src)
+{
+       if (_denormal_protection != yn) {
+               _denormal_protection = yn;
+               //  denormal_protection_changed (src); /* EMIT SIGNAL */
        }
-       //  phase_invert_changed (src); /* EMIT SIGNAL */
 }
 
 void
@@ -780,21 +845,6 @@ Route::set_mute (bool yn, void *src)
        }
 }
 
-uint32_t
-Route::count_sends ()
-{
-       uint32_t cnt = 0;
-       Glib::RWLock::ReaderLock lm (redirect_lock);
-
-       for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
-               if (boost::dynamic_pointer_cast<Send> (*i)) {
-                       ++cnt;
-               }
-       }
-
-       return cnt;
-}
-
 int
 Route::add_redirect (boost::shared_ptr<Redirect> redirect, void *src, uint32_t* err_streams)
 {
@@ -844,7 +894,10 @@ Route::add_redirect (boost::shared_ptr<Redirect> redirect, void *src, uint32_t*
                        _peak_power.push_back(0);
                }
                while (_visible_peak_power.size() < potential_max_streams) {
-                       _visible_peak_power.push_back(0);
+                       _visible_peak_power.push_back(-INFINITY);
+               }
+               while (_max_peak_power.size() < potential_max_streams) {
+                       _max_peak_power.push_back(-INFINITY);
                }
 
                _redirects.push_back (redirect);
@@ -873,6 +926,8 @@ Route::add_redirects (const RedirectList& others, void *src, uint32_t* err_strea
 {
        uint32_t old_rmo = redirect_max_outs;
 
+       assert (ports_legal);
+
        if (!_session.engine().connected()) {
                return 1;
        }
@@ -902,7 +957,10 @@ Route::add_redirects (const RedirectList& others, void *src, uint32_t* err_strea
                                _peak_power.push_back(0);
                        }
                        while (_visible_peak_power.size() < potential_max_streams) {
-                               _visible_peak_power.push_back(0);
+                               _visible_peak_power.push_back(-INFINITY);
+                       }
+                       while (_max_peak_power.size() < potential_max_streams) {
+                               _max_peak_power.push_back(-INFINITY);
                        }
 
                        _redirects.push_back (*i);
@@ -927,10 +985,13 @@ Route::add_redirects (const RedirectList& others, void *src, uint32_t* err_strea
        return 0;
 }
 
+/** Remove redirects with a given placement.
+ * @param p Placement of redirects to remove.
+ */
 void
-Route::clear_redirects (void *src)
+Route::clear_redirects (Placement p, void *src)
 {
-       uint32_t old_rmo = redirect_max_outs;
+       const uint32_t old_rmo = redirect_max_outs;
 
        if (!_session.engine().connected()) {
                return;
@@ -938,13 +999,22 @@ Route::clear_redirects (void *src)
 
        {
                Glib::RWLock::WriterLock lm (redirect_lock);
-               RedirectList::iterator i;
-               for (i = _redirects.begin(); i != _redirects.end(); ++i) {
-                       (*i)->drop_references ();
+               RedirectList new_list;
+               
+               for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+                       if ((*i)->placement() == p) {
+                               /* it's the placement we want to get rid of */
+                               (*i)->drop_references ();
+                       } else {
+                               /* it's a different placement, so keep it */
+                               new_list.push_back (*i);
+                       }
                }
-               _redirects.clear ();
+               
+               _redirects = new_list;
        }
 
+       /* FIXME: can't see how this test can ever fire */
        if (redirect_max_outs != old_rmo) {
                reset_panner ();
        }
@@ -959,6 +1029,8 @@ Route::remove_redirect (boost::shared_ptr<Redirect> redirect, void *src, uint32_
 {
        uint32_t old_rmo = redirect_max_outs;
 
+       assert (ports_legal);
+
        if (!_session.engine().connected()) {
                return 1;
        }
@@ -1155,9 +1227,16 @@ Route::_reset_plugin_counts (uint32_t* err_streams)
                        } else {
                                s->expect_inputs ((*prev)->output_streams());
                        }
-               }
 
-               redirect_max_outs = max ((*r)->output_streams (), redirect_max_outs);
+               } else {
+                       
+                       /* don't pay any attention to send output configuration, since it doesn't
+                          affect the route.
+                        */
+
+                       redirect_max_outs = max ((*r)->output_streams (), redirect_max_outs);
+                       
+               }
        }
 
        /* we're done */
@@ -1197,7 +1276,13 @@ Route::check_some_plugin_counts (list<InsertCount>& iclist, int32_t required_inp
                }
                
                (*i).in = required_inputs;
-               (*i).out = (*i).insert->compute_output_streams ((*i).cnt);
+
+               if (((*i).out = (*i).insert->compute_output_streams ((*i).cnt)) < 0) {
+                       if (err_streams) {
+                               *err_streams = required_inputs;
+                       }
+                       return -1;
+               }
 
                required_inputs = (*i).out;
        }
@@ -1305,8 +1390,12 @@ Route::all_redirects_flip ()
        }
 }
 
+/** Set all redirects with a given placement to a given active state.
+ * @param p Placement of redirects to change.
+ * @param state New active state for those redirects.
+ */
 void
-Route::all_redirects_active (bool state)
+Route::all_redirects_active (Placement p, bool state)
 {
        Glib::RWLock::ReaderLock lm (redirect_lock);
 
@@ -1315,7 +1404,9 @@ Route::all_redirects_active (bool state)
        }
 
        for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
-               (*i)->set_active (state, this);
+               if ((*i)->placement() == p) {
+                       (*i)->set_active (state, this);
+               }
        }
 }
 
@@ -1372,16 +1463,15 @@ Route::state(bool full_state)
        char buf[32];
 
        if (_flags) {
-               snprintf (buf, sizeof (buf), "0x%x", _flags);
-               node->add_property("flags", buf);
+               node->add_property("flags", enum_2_string (_flags));
        }
        
        node->add_property("default-type", _default_type.to_string());
 
-       node->add_property("active", _active?"yes":"no");
        node->add_property("muted", _muted?"yes":"no");
        node->add_property("soloed", _soloed?"yes":"no");
        node->add_property("phase-invert", _phase_invert?"yes":"no");
+       node->add_property("denormal-protection", _denormal_protection?"yes":"no");
        node->add_property("mute-affects-pre-fader", _mute_affects_pre_fader?"yes":"no"); 
        node->add_property("mute-affects-post-fader", _mute_affects_post_fader?"yes":"no"); 
        node->add_property("mute-affects-control-outs", _mute_affects_control_outs?"yes":"no"); 
@@ -1398,7 +1488,7 @@ Route::state(bool full_state)
        OrderKeys::iterator x = order_keys.begin(); 
 
        while (x != order_keys.end()) {
-               order_string += (*x).first;
+               order_string += string ((*x).first);
                order_string += '=';
                snprintf (buf, sizeof(buf), "%ld", (*x).second);
                order_string += buf;
@@ -1417,6 +1507,11 @@ Route::state(bool full_state)
        node->add_child_nocopy (_solo_control.get_state ());
        node->add_child_nocopy (_mute_control.get_state ());
 
+       XMLNode* remote_control_node = new XMLNode (X_("remote_control"));
+       snprintf (buf, sizeof (buf), "%d", _remote_control_id);
+       remote_control_node->add_property (X_("id"), buf);
+       node->add_child_nocopy (*remote_control_node);
+
        if (_control_outs) {
                XMLNode* cnode = new XMLNode (X_("ControlOuts"));
                cnode->add_child_nocopy (_control_outs->state (full_state));
@@ -1483,22 +1578,30 @@ Route::add_redirect_from_xml (const XMLNode& node)
                        if ((prop = node.property ("type")) != 0) {
 
                                boost::shared_ptr<Insert> insert;
+                               bool have_insert = false;
 
-                               if (prop->value() == "ladspa" || prop->value() == "Ladspa" || prop->value() == "vst") {
-
+                               if (prop->value() == "ladspa" || prop->value() == "Ladspa" || 
+                                   prop->value() == "lv2" ||
+                                   prop->value() == "vst" ||
+                                   prop->value() == "audiounit") {
+                                       
                                        insert.reset (new PluginInsert(_session, node));
+                                       have_insert = true;
                                        
                                } else if (prop->value() == "port") {
 
 
                                        insert.reset (new PortInsert (_session, node));
+                                       have_insert = true;
 
                                } else {
 
                                        error << string_compose(_("unknown Insert type \"%1\"; ignored"), prop->value()) << endmsg;
                                }
 
-                               add_redirect (insert, this);
+                               if (have_insert) {
+                                       add_redirect (insert, this);
+                               }
                                
                        } else {
                                error << _("Insert XML node has no type property") << endmsg;
@@ -1533,9 +1636,7 @@ Route::_set_state (const XMLNode& node, bool call_base)
        }
 
        if ((prop = node.property (X_("flags"))) != 0) {
-               int x;
-               sscanf (prop->value().c_str(), "0x%x", &x);
-               _flags = Flag (x);
+               _flags = Flag (string_2_enum (prop->value(), _flags));
        } else {
                _flags = Flag (0);
        }
@@ -1546,11 +1647,11 @@ Route::_set_state (const XMLNode& node, bool call_base)
        }
 
        if ((prop = node.property (X_("phase-invert"))) != 0) {
-               set_phase_invert(prop->value()=="yes"?true:false, this);
+               set_phase_invert (prop->value()=="yes"?true:false, this);
        }
 
-       if ((prop = node.property (X_("active"))) != 0) {
-               set_active (prop->value() == "yes");
+       if ((prop = node.property (X_("denormal-protection"))) != 0) {
+               set_denormal_protection (prop->value()=="yes"?true:false, this);
        }
 
        if ((prop = node.property (X_("muted"))) != 0) {
@@ -1615,7 +1716,7 @@ Route::_set_state (const XMLNode& node, bool call_base)
                                        error << string_compose (_("badly formed order key string in state file! [%1] ... ignored."), remaining)
                                              << endmsg;
                                } else {
-                                       set_order_key (remaining.substr (0, equal), n);
+                                       set_order_key (remaining.substr (0, equal).c_str(), n);
                                }
                        }
 
@@ -1649,34 +1750,27 @@ Route::_set_state (const XMLNode& node, bool call_base)
                        break;
                }
        }
-                       
-       for (niter = nlist.begin(); niter != nlist.end(); ++niter){
-
-               child = *niter;
-                       
-               if (child->name() == X_("Send")) {
 
 
-                       if (!IO::ports_legal) {
-
-                               deferred_state->add_child_copy (*child);
-
-                       } else {
-                               add_redirect_from_xml (*child);
-                       }
-
-               } else if (child->name() == X_("Insert")) {
-                       
-                       if (!IO::ports_legal) {
-                               
-                               deferred_state->add_child_copy (*child);
+       XMLNodeList redirect_nodes;
+       
+       for (niter = nlist.begin(); niter != nlist.end(); ++niter){
+               
+               child = *niter;
+               
+               if (child->name() == X_("Send") || child->name() == X_("Insert")) {
+                       redirect_nodes.push_back(child);
+               }
+               
+       }
+       
+       _set_redirect_states (redirect_nodes);
 
-                       } else {
-                               
-                               add_redirect_from_xml (*child);
-                       }
+       for (niter = nlist.begin(); niter != nlist.end(); ++niter){
+               child = *niter;
+               // All redirects (sends and inserts) have been applied already
 
-               } else if (child->name() == X_("Automation")) {
+               if (child->name() == X_("Automation")) {
                        
                        if ((prop = child->property (X_("path"))) != 0)  {
                                load_automation (prop->value());
@@ -1698,13 +1792,26 @@ Route::_set_state (const XMLNode& node, bool call_base)
                        _comment = cmt->content();
 
                } else if (child->name() == X_("extra")) {
+
                        _extra_xml = new XMLNode (*child);
-               } else if (child->name() == X_("solo")) {
-                       _solo_control.set_state (*child);
-                       _session.add_controllable (&_solo_control);
-               } else if (child->name() == X_("mute")) {
-                       _mute_control.set_state (*child);
-                       _session.add_controllable (&_mute_control);
+
+               } else if (child->name() == X_("controllable") && (prop = child->property("name")) != 0) {
+                       
+                       if (prop->value() == "solo") {
+                               _solo_control.set_state (*child);
+                               _session.add_controllable (&_solo_control);
+                       }
+                       else if (prop->value() == "mute") {
+                               _mute_control.set_state (*child);
+                               _session.add_controllable (&_mute_control);
+                       }
+               }
+               else if (child->name() == X_("remote_control")) {
+                       if ((prop = child->property (X_("id"))) != 0) {
+                               int32_t x;
+                               sscanf (prop->value().c_str(), "%d", &x);
+                               set_remote_control_id (x);
+                       }
                }
        }
 
@@ -1720,6 +1827,110 @@ Route::_set_state (const XMLNode& node, bool call_base)
        return 0;
 }
 
+void
+Route::_set_redirect_states(const XMLNodeList &nlist)
+{
+       XMLNodeConstIterator niter;
+       char buf[64];
+
+       RedirectList::iterator i, o;
+
+       if (!ports_legal) {
+
+               for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+                       deferred_state->add_child_copy (**niter);
+               }
+
+               return;
+       }
+
+       // Iterate through existing redirects, remove those which are not in the state list
+       for (i = _redirects.begin(); i != _redirects.end(); ) {
+               RedirectList::iterator tmp = i;
+               ++tmp;
+
+               bool redirectInStateList = false;
+
+               (*i)->id().print (buf, sizeof (buf));
+
+               for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+                       if (strncmp (buf,(*niter)->child(X_("Redirect"))->child(X_("IO"))->property(X_("id"))->value().c_str(), sizeof(buf)) == 0) {
+                               redirectInStateList = true;
+                               break;
+                       }
+               }
+               
+               if (!redirectInStateList) {
+                       remove_redirect ( *i, this);
+               }
+
+
+               i = tmp;
+       }
+
+
+       // Iterate through state list and make sure all redirects are on the track and in the correct order,
+       // set the state of existing redirects according to the new state on the same go
+       i = _redirects.begin();
+       for (niter = nlist.begin(); niter != nlist.end(); ++niter, ++i) {
+
+               // Check whether the next redirect in the list 
+               o = i;
+
+               while (o != _redirects.end()) {
+                       (*o)->id().print (buf, sizeof (buf));
+                       if ( strncmp(buf, (*niter)->child(X_("Redirect"))->child(X_("IO"))->property(X_("id"))->value().c_str(), sizeof(buf)) == 0)
+                               break;
+                       ++o;
+               }
+
+               if (o == _redirects.end()) {
+                       // If the redirect (*niter) is not on the route, we need to create it
+                       // and move it to the correct location
+
+                       RedirectList::iterator prev_last = _redirects.end();
+                       --prev_last; // We need this to check whether adding succeeded
+                       
+                       add_redirect_from_xml (**niter);
+
+                       RedirectList::iterator last = _redirects.end();
+                       --last;
+
+                       if (prev_last == last) {
+                               warning << _name << ": could not fully restore state as some redirects were not possible to create" << endmsg;
+                               continue;
+
+                       }
+
+                       boost::shared_ptr<Redirect> tmp = (*last);
+                       // remove the redirect from the wrong location
+                       _redirects.erase(last);
+                       // insert the new redirect at the current location
+                       _redirects.insert(i, tmp);
+
+                       --i; // move pointer to the newly inserted redirect
+                       continue;
+               }
+
+               // We found the redirect (*niter) on the route, first we must make sure the redirect
+               // is at the location provided in the XML state
+               if (i != o) {
+                       boost::shared_ptr<Redirect> tmp = (*o);
+                       // remove the old copy
+                       _redirects.erase(o);
+                       // insert the redirect at the correct location
+                       _redirects.insert(i, tmp);
+
+                       --i; // move pointer so it points to the right redirect
+               }
+
+               (*i)->set_state( (**niter) );
+       }
+       
+       redirects_changed(this);
+}
+
 void
 Route::curve_reallocate ()
 {
@@ -1768,11 +1979,17 @@ Route::set_control_outs (const vector<string>& ports)
 {
        Glib::Mutex::Lock lm (control_outs_lock);
        vector<string>::const_iterator i;
+       uint32_t limit;
 
        if (_control_outs) {
                delete _control_outs;
                _control_outs = 0;
        }
+
+       if (control() || master()) {
+               /* no control outs for these two special busses */
+               return 0;
+       }
        
        if (ports.empty()) {
                return 0;
@@ -1787,7 +2004,20 @@ Route::set_control_outs (const vector<string>& ports)
           have outputs. we track the changes in ::output_change_handler().
        */
 
-       _control_outs->ensure_io (0, n_outputs(), true, this);
+       limit = n_outputs ();
+
+       if (_control_outs->ensure_io (0, limit, true, this)) {
+               return -1;
+       }
+
+       /* now connect to the named ports */
+
+       for (uint32_t n = 0; n < limit; ++n) {
+               if (_control_outs->connect_output (_control_outs->output (n), ports[n % ports.size()], this)) {
+                       error << string_compose (_("could not connect %1 to %2"), _control_outs->output(n)->name(), ports[n]) << endmsg;
+                       return -1;
+               }
+       }
  
        return 0;
 }      
@@ -1955,13 +2185,6 @@ Route::get_mute_config (mute_type t)
        return onoff;
 }
 
-void
-Route::set_active (bool yn)
-{
-       _active = yn; 
-        active_changed(); /* EMIT SIGNAL */
-}
-
 void
 Route::handle_transport_stopped (bool abort_ignored, bool did_locate, bool can_flush_redirects)
 {
@@ -2289,8 +2512,9 @@ Route::protect_automation ()
 {
        switch (gain_automation_state()) {
        case Write:
-       case Touch:
                set_gain_automation_state (Off);
+       case Touch:
+               set_gain_automation_state (Play);
                break;
        default:
                break;
@@ -2298,9 +2522,11 @@ Route::protect_automation ()
 
        switch (panner().automation_state ()) {
        case Write:
-       case Touch:
                panner().set_automation_state (Off);
                break;
+       case Touch:
+               panner().set_automation_state (Play);
+               break;
        default:
                break;
        }