Add option for record-only metronome.
[ardour.git] / libs / ardour / session.cc
index 3575a5c152c0f6ec1d4f0a00263dbcbeacabcbb2..d54dac7fd977dc645d3832a80831f4bd8334cabb 100644 (file)
@@ -240,7 +240,7 @@ Session::Session (AudioEngine &eng,
        , pending_locate_flush (false)
        , pending_abort (false)
        , pending_auto_loop (false)
-       , _mempool ("Session", 2097152)
+       , _mempool ("Session", 3145728)
        , lua (lua_newstate (&PBD::ReallocPool::lalloc, &_mempool))
        , _n_lua_scripts (0)
        , _butler (new Butler (*this))
@@ -295,14 +295,19 @@ Session::Session (AudioEngine &eng,
        , _bundle_xml_node (0)
        , _current_trans (0)
        , _clicking (false)
+       , _click_rec_only (false)
        , click_data (0)
        , click_emphasis_data (0)
        , click_length (0)
        , click_emphasis_length (0)
        , _clicks_cleared (0)
+       , _count_in_samples (0)
        , _play_range (false)
        , _range_selection (-1,-1)
        , _object_selection (-1,-1)
+       , _preroll_record_punch_pos (-1)
+       , _preroll_record_trim_len (0)
+       , _count_in_once (false)
        , main_outs (0)
        , first_file_data_format_reset (true)
        , first_file_header_format_reset (true)
@@ -415,6 +420,8 @@ Session::Session (AudioEngine &eng,
 
        _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
 
+       PresentationInfo::Change.connect_same_thread (*this, boost::bind (&Session::notify_presentation_info_change, this));
+
        Config->ParameterChanged.connect_same_thread (*this, boost::bind (&Session::config_changed, this, _1, false));
        config.ParameterChanged.connect_same_thread (*this, boost::bind (&Session::config_changed, this, _1, true));
 
@@ -619,7 +626,7 @@ Session::destroy ()
 
        ControlProtocolManager::instance().drop_protocols ();
 
-       /* stop autoconnecting */
+       /* stop auto dis/connecting */
        auto_connect_thread_terminate ();
 
        MIDI::Name::MidiPatchManager::instance().remove_search_path(session_directory().midi_patch_path());
@@ -780,6 +787,7 @@ Session::destroy ()
                        case SessionEvent::Skip:
                        case SessionEvent::PunchIn:
                        case SessionEvent::PunchOut:
+                       case SessionEvent::RecordStart:
                        case SessionEvent::StopOnce:
                        case SessionEvent::RangeStop:
                        case SessionEvent::RangeLocate:
@@ -800,6 +808,14 @@ Session::destroy ()
                }
        }
 
+       {
+               /* unregister all dropped ports, process pending port deletion. */
+               // this may call ARDOUR::Port::drop ... jack_port_unregister ()
+               // jack1 cannot cope with removing ports while processing
+               Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
+               AudioEngine::instance()->clear_pending_port_deletions ();
+       }
+
        DEBUG_TRACE (DEBUG::Destruction, "Session::destroy() done\n");
 
        BOOST_SHOW_POINTERS ();
@@ -1870,6 +1886,17 @@ Session::location_added (Location *location)
                 location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
                 location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
                 location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+               location->PositionLockStyleChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+        }
+
+       if (location->is_range_marker()) {
+                /* listen for per-location signals that require us to do any * global updates for marks */
+
+                location->StartChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+                location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+                location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+                location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+               location->PositionLockStyleChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
         }
 
         if (location->is_skip()) {
@@ -1879,6 +1906,7 @@ Session::location_added (Location *location)
                 location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true));
                 location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true));
                 location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, false));
+               location->PositionLockStyleChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
 
                 update_skips (location, true);
         }
@@ -1998,6 +2026,7 @@ Session::disable_record (bool rt_context, bool force)
                if (!rt_context) {
                        remove_pending_capture_state ();
                }
+               unset_preroll_record_punch ();
        }
 }
 
@@ -2035,7 +2064,7 @@ Session::maybe_enable_record (bool rt_context)
        }
 
        if (_transport_speed) {
-               if (!config.get_punch_in()) {
+               if (!config.get_punch_in() && !preroll_record_punch_enabled ()) {
                        enable_record ();
                }
        } else {
@@ -2047,12 +2076,15 @@ Session::maybe_enable_record (bool rt_context)
 }
 
 framepos_t
-Session::audible_frame () const
+Session::audible_frame (bool* latent_locate) const
 {
        framepos_t ret;
 
        frameoffset_t offset = worst_playback_latency (); // - _engine.samples_since_cycle_start ();
        offset *= transport_speed ();
+       if (latent_locate) {
+               *latent_locate = false;
+       }
 
        if (synced_to_engine()) {
                /* Note: this is basically just sync-to-JACK */
@@ -2077,14 +2109,24 @@ Session::audible_frame () const
 
                        if (!play_loop || !have_looped) {
                                if (ret < _last_roll_or_reversal_location) {
+                                       if (latent_locate) {
+                                               *latent_locate = true;
+                                       }
                                        return _last_roll_or_reversal_location;
                                }
                        } else {
-                               // latent loops
+                               /* the play-position wrapped at the loop-point
+                                * ardour is already playing the beginning of the loop,
+                                * but due to playback latency, the "audible frame"
+                                * is still at the end of the loop.
+                                */
                                Location *location = _locations->auto_loop_location();
                                frameoffset_t lo = location->start() - ret;
                                if (lo > 0) {
                                        ret = location->end () - lo;
+                                       if (latent_locate) {
+                                               *latent_locate = true;
+                                       }
                                }
                        }
 
@@ -2101,6 +2143,22 @@ Session::audible_frame () const
        return std::max ((framepos_t)0, ret);
 }
 
+
+framecnt_t
+Session::preroll_samples (framepos_t pos) const
+{
+       const float pr = Config->get_preroll_seconds();
+       if (pos >= 0 && pr < 0) {
+               const Tempo& tempo = _tempo_map->tempo_at_frame (pos);
+               const Meter& meter = _tempo_map->meter_at_frame (pos);
+               return meter.frames_per_bar (tempo, frame_rate()) * -pr;
+       }
+       if (pr < 0) {
+               return 0;
+       }
+       return pr * frame_rate();
+}
+
 void
 Session::set_frame_rate (framecnt_t frames_per_second)
 {
@@ -2453,9 +2511,10 @@ Session::default_track_name_pattern (DataType t)
  *  @param instrument plugin info for the instrument to insert pre-fader, if any
  */
 list<boost::shared_ptr<MidiTrack> >
-Session::new_midi_track (const ChanCount& input, const ChanCount& output,
+Session::new_midi_track (const ChanCount& input, const ChanCount& output, bool strict_io,
                          boost::shared_ptr<PluginInfo> instrument, Plugin::PresetRecord* pset,
-                         RouteGroup* route_group, uint32_t how_many, string name_template, PresentationInfo::order_t order,
+                         RouteGroup* route_group, uint32_t how_many,
+                         string name_template, PresentationInfo::order_t order,
                          TrackMode mode)
 {
        string track_name;
@@ -2482,7 +2541,7 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output,
                                goto failed;
                        }
 
-                       if (Profile->get_mixbus ()) {
+                       if (strict_io) {
                                track->set_strict_io (true);
                        }
 
@@ -2548,8 +2607,16 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output,
                                if (pset) {
                                        plugin->load_preset (*pset);
                                }
-                               boost::shared_ptr<Processor> p (new PluginInsert (*this, plugin));
-                               (*r)->add_processor (p, PreFader);
+                               boost::shared_ptr<PluginInsert> pi (new PluginInsert (*this, plugin));
+                               if (strict_io) {
+                                       pi->set_strict_io (true);
+                               }
+
+                               (*r)->add_processor (pi, PreFader);
+
+                               if (Profile->get_mixbus () && pi->configured () && pi->output_streams().n_audio() > 2) {
+                                       (*r)->move_instrument_down (false);
+                               }
                        }
                }
        }
@@ -2558,7 +2625,8 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output,
 }
 
 RouteList
-Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name_template, boost::shared_ptr<PluginInfo> instrument, Plugin::PresetRecord* pset,
+Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name_template, bool strict_io,
+                         boost::shared_ptr<PluginInfo> instrument, Plugin::PresetRecord* pset,
                          PresentationInfo::Flag flag, PresentationInfo::order_t order)
 {
        string bus_name;
@@ -2581,7 +2649,7 @@ Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name
                                goto failure;
                        }
 
-                       if (Profile->get_mixbus ()) {
+                       if (strict_io) {
                                bus->set_strict_io (true);
                        }
 
@@ -2639,8 +2707,16 @@ Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name
                                if (pset) {
                                        plugin->load_preset (*pset);
                                }
-                               boost::shared_ptr<Processor> p (new PluginInsert (*this, plugin));
-                               (*r)->add_processor (p, PreFader);
+                               boost::shared_ptr<PluginInsert> pi (new PluginInsert (*this, plugin));
+                               if (strict_io) {
+                                       pi->set_strict_io (true);
+                               }
+
+                               (*r)->add_processor (pi, PreFader);
+
+                               if (Profile->get_mixbus () && pi->configured () && pi->output_streams().n_audio() > 2) {
+                                       (*r)->move_instrument_down (false);
+                               }
                        }
                }
        }
@@ -3255,7 +3331,15 @@ Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t i
                                        boost::shared_ptr<Playlist> playlist = playlists->by_name (playlist_name);
                                        // Use same name as Route::set_name_in_state so playlist copy
                                        // is picked up when creating the Route in XMLRouteFactory below
-                                       PlaylistFactory::create (playlist, string_compose ("%1.1", name));
+                                       playlist = PlaylistFactory::create (playlist, string_compose ("%1.1", name));
+                                       playlist->reset_shares ();
+                               }
+                       } else if (pd == SharePlaylist) {
+                               XMLNode* ds_node = find_named_node (node_copy, "Diskstream");
+                               if (ds_node) {
+                                       const std::string playlist_name = ds_node->property (X_("playlist"))->value ();
+                                       boost::shared_ptr<Playlist> playlist = playlists->by_name (playlist_name);
+                                       playlist->share_with ((node_copy.property (X_("id")))->value());
                                }
                        }
 
@@ -3422,84 +3506,109 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool
                }
        }
 
+       /* auditioner and monitor routes are not part of the order */
+       if (auditioner) {
+               assert (n_routes > 0);
+               --n_routes;
+       }
+       if (_monitor_out) {
+               assert (n_routes > 0);
+               --n_routes;
+       }
+
        DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("ensure order gap starting at %1 for %2\n", order, new_routes.size()));
        ensure_route_presentation_info_gap (order, new_routes.size());
 
-       for (RouteList::iterator x = new_routes.begin(); x != new_routes.end(); ++x, ++added) {
+       {
+               PresentationInfo::ChangeSuspender cs;
 
-               boost::weak_ptr<Route> wpr (*x);
-               boost::shared_ptr<Route> r (*x);
+               for (RouteList::iterator x = new_routes.begin(); x != new_routes.end(); ++x, ++added) {
 
-               r->solo_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_solo_changed, this, _1, _2,wpr));
-               r->solo_isolate_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_solo_isolated_changed, this, wpr));
-               r->mute_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_mute_changed, this));
+                       boost::weak_ptr<Route> wpr (*x);
+                       boost::shared_ptr<Route> r (*x);
 
-               r->output()->changed.connect_same_thread (*this, boost::bind (&Session::set_worst_io_latencies_x, this, _1, _2));
-               r->processors_changed.connect_same_thread (*this, boost::bind (&Session::route_processors_changed, this, _1));
-               r->processor_latency_changed.connect_same_thread (*this, boost::bind (&Session::queue_latency_recompute, this));
+                       r->solo_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_solo_changed, this, _1, _2,wpr));
+                       r->solo_isolate_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_solo_isolated_changed, this, wpr));
+                       r->mute_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_mute_changed, this));
 
-               if (r->is_master()) {
-                       _master_out = r;
-               }
+                       r->output()->changed.connect_same_thread (*this, boost::bind (&Session::set_worst_io_latencies_x, this, _1, _2));
+                       r->processors_changed.connect_same_thread (*this, boost::bind (&Session::route_processors_changed, this, _1));
+                       r->processor_latency_changed.connect_same_thread (*this, boost::bind (&Session::queue_latency_recompute, this));
 
-               if (r->is_monitor()) {
-                       _monitor_out = r;
-               }
+                       if (r->is_master()) {
+                               _master_out = r;
+                       }
 
-               boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (r);
-               if (tr) {
-                       tr->PlaylistChanged.connect_same_thread (*this, boost::bind (&Session::track_playlist_changed, this, boost::weak_ptr<Track> (tr)));
-                       track_playlist_changed (boost::weak_ptr<Track> (tr));
-                       tr->rec_enable_control()->Changed.connect_same_thread (*this, boost::bind (&Session::update_route_record_state, this));
-
-                       boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (tr);
-                       if (mt) {
-                               mt->StepEditStatusChange.connect_same_thread (*this, boost::bind (&Session::step_edit_status_change, this, _1));
-                               mt->output()->changed.connect_same_thread (*this, boost::bind (&Session::midi_output_change_handler, this, _1, _2, boost::weak_ptr<Route>(mt)));
-                               mt->presentation_info().PropertyChanged.connect_same_thread (*this, boost::bind (&Session::midi_track_presentation_info_changed, this, _1, boost::weak_ptr<MidiTrack>(mt)));
+                       if (r->is_monitor()) {
+                               _monitor_out = r;
                        }
-               }
 
-               if (!r->presentation_info().special()) {
+                       boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (r);
+                       if (tr) {
+                               tr->PlaylistChanged.connect_same_thread (*this, boost::bind (&Session::track_playlist_changed, this, boost::weak_ptr<Track> (tr)));
+                               track_playlist_changed (boost::weak_ptr<Track> (tr));
+                               tr->rec_enable_control()->Changed.connect_same_thread (*this, boost::bind (&Session::update_route_record_state, this));
+
+                               boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (tr);
+                               if (mt) {
+                                       mt->StepEditStatusChange.connect_same_thread (*this, boost::bind (&Session::step_edit_status_change, this, _1));
+                                       mt->output()->changed.connect_same_thread (*this, boost::bind (&Session::midi_output_change_handler, this, _1, _2, boost::weak_ptr<Route>(mt)));
+                                       mt->presentation_info().PropertyChanged.connect_same_thread (*this, boost::bind (&Session::midi_track_presentation_info_changed, this, _1, boost::weak_ptr<MidiTrack>(mt)));
+                               }
+                       }
 
-                       DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("checking PI state for %1\n", r->name()));
+                       if (!r->presentation_info().special()) {
 
-                       /* presentation info order may already have been set from XML */
+                               DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("checking PI state for %1\n", r->name()));
 
-                       if (!r->presentation_info().order_set()) {
+                               /* presentation info order may already have been set from XML */
+
+                               if (!r->presentation_info().order_set()) {
+                                       /* this is only useful for headless sessions,
+                                        * Editor::add_routes() and Mixer_UI::add_routes() will
+                                        * override it following the RouteAdded signal.
+                                        *
+                                        * Also routes should be sorted before VCAs (like the GUI does).
+                                        * Session::ensure_route_presentation_info_gap() does not special case VCAs either.
+                                        *
+                                        * ... but not to worry, the GUI's
+                                        * gtk2_ardour/route_sorter.h and various ::sync_presentation_info_from_treeview()
+                                        * handle this :)
+                                        */
 
-                               if (order == PresentationInfo::max_order) {
-                                       /* just add to the end */
-                                       r->set_presentation_order (n_routes + added, false);
-                                       DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order not set, set to NR %1 + %2 = %3\n", n_routes, added, n_routes + added));
+                                       if (order == PresentationInfo::max_order) {
+                                               /* just add to the end */
+                                               r->set_presentation_order (n_routes + added);
+                                               DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order not set, set to NR %1 + %2 = %3\n", n_routes, added, n_routes + added));
+                                       } else {
+                                               r->set_presentation_order (order + added);
+                                               DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order not set, set to %1 + %2 = %3\n", order, added, order + added));
+                                       }
                                } else {
-                                       r->set_presentation_order (order + added);
-                                       DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order not set, set to %1 + %2 = %3\n", order, added, order + added));
+                                       DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order already set to %1\n", r->presentation_info().order()));
                                }
-                       } else {
-                               DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order already set to %1\n", r->presentation_info().order()));
                        }
-               }
 
 #if !defined(__APPLE__) && !defined(__FreeBSD__)
-               /* clang complains: 'operator<<' should be declared prior to the call site or in an associated namespace of one of its
-                * arguments std::ostream& operator<<(std::ostream& o, ARDOUR::PresentationInfo const& rid)"
-                */
-               DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("added route %1, group order %2 type %3 (summary: %4)\n",
-                                                              r->name(),
-                                                              r->presentation_info().order(),
-                                                              enum_2_string (r->presentation_info().flags()),
-                                                              r->presentation_info()));
+                       /* clang complains: 'operator<<' should be declared prior to the call site or in an associated namespace of one of its
+                        * arguments std::ostream& operator<<(std::ostream& o, ARDOUR::PresentationInfo const& rid)"
+                        */
+                       DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("added route %1, group order %2 type %3 (summary: %4)\n",
+                                                                      r->name(),
+                                                                      r->presentation_info().order(),
+                                                                      enum_2_string (r->presentation_info().flags()),
+                                                                      r->presentation_info()));
 #endif
 
 
-               if (input_auto_connect || output_auto_connect) {
-                       auto_connect_route (r, input_auto_connect, ChanCount (), ChanCount (), existing_inputs, existing_outputs);
-                       existing_inputs += r->n_inputs();
-                       existing_outputs += r->n_outputs();
-               }
+                       if (input_auto_connect || output_auto_connect) {
+                               auto_connect_route (r, input_auto_connect, ChanCount (), ChanCount (), existing_inputs, existing_outputs);
+                               existing_inputs += r->n_inputs();
+                               existing_outputs += r->n_outputs();
+                       }
 
-               ARDOUR::GUIIdle ();
+                       ARDOUR::GUIIdle ();
+               }
        }
 
        if (_monitor_out && IO::connecting_legal) {
@@ -3714,7 +3823,10 @@ Session::remove_routes (boost::shared_ptr<RouteList> routes_to_remove)
                return;
        }
 
-       PresentationInfo::Change(); /* EMIT SIGNAL */
+       PropertyChange so;
+       so.add (Properties::selected);
+       so.add (Properties::order);
+       PresentationInfo::Change (PropertyChange (so));
 
        /* save the new state of the world */
 
@@ -4289,18 +4401,6 @@ Session::get_remote_nth_stripable (PresentationInfo::order_t n, PresentationInfo
        return boost::shared_ptr<Stripable>();
 }
 
-boost::shared_ptr<Route>
-Session::route_by_selected_count (uint32_t id) const
-{
-       boost::shared_ptr<RouteList> r = routes.reader ();
-
-       for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
-               /* NOT IMPLEMENTED */
-       }
-
-       return boost::shared_ptr<Route> ((Route*) 0);
-}
-
 struct PresentationOrderSorter {
        bool operator() (boost::shared_ptr<Stripable> a, boost::shared_ptr<Stripable> b) {
                if (a->presentation_info().special() && !b->presentation_info().special()) {
@@ -4315,6 +4415,27 @@ struct PresentationOrderSorter {
        }
 };
 
+boost::shared_ptr<Route>
+Session::route_by_selected_count (uint32_t id) const
+{
+       RouteList r (*(routes.reader ()));
+       PresentationOrderSorter sorter;
+       r.sort (sorter);
+
+       RouteList::iterator i;
+
+       for (i = r.begin(); i != r.end(); ++i) {
+               if ((*i)->presentation_info().selected()) {
+                       if (id == 0) {
+                               return *i;
+                       }
+                       --id;
+               }
+       }
+
+       return boost::shared_ptr<Route> ();
+}
+
 void
 Session::reassign_track_numbers ()
 {
@@ -5172,6 +5293,9 @@ Session::register_lua_function (
                tbl_arg[(*i)->name] = (*i)->value;
        }
        (*_lua_add)(name, bytecode, tbl_arg); // throws luabridge::LuaException
+       lm.release();
+
+       LuaScriptsChanged (); /* EMIT SIGNAL */
        set_dirty();
 }
 
@@ -5181,6 +5305,9 @@ Session::unregister_lua_function (const std::string& name)
        Glib::Threads::Mutex::Lock lm (lua_lock);
        (*_lua_del)(name); // throws luabridge::LuaException
        lua.collect_garbage ();
+       lm.release();
+
+       LuaScriptsChanged (); /* EMIT SIGNAL */
        set_dirty();
 }
 
@@ -5570,16 +5697,6 @@ Session::tempo_map_changed (const PropertyChange&)
        set_dirty ();
 }
 
-void
-Session::gui_tempo_map_changed ()
-{
-       clear_clicks ();
-
-       playlists->update_after_tempo_map_change ();
-
-       _locations->apply (*this, &Session::update_locations_after_tempo_map_change);
-}
-
 void
 Session::update_locations_after_tempo_map_change (const Locations::LocationList& loc)
 {
@@ -6772,7 +6889,6 @@ Session::notify_presentation_info_change ()
                return;
        }
 
-       PresentationInfo::Change (); /* EMIT SIGNAL */
        reassign_track_numbers();
 
 #ifdef USE_TRACKS_CODE_FEATURES