diff --git a/doc/layering/build.sh b/doc/layering/build.sh index 5abaaa5721..ad8d95e7ba 100644 --- a/doc/layering/build.sh +++ b/doc/layering/build.sh @@ -1,6 +1,7 @@ #!/bin/sh -for f in basic-layering explicit-layering1 explicit-layering2 tricky-explicit-layering; do +for f in basic-layering layering-order-1 layering-order-2; do + echo "$f" inkscape -z --export-area-drawing -f $f.svg --export-pdf $f.pdf done diff --git a/doc/layering/explicit-layering1.svg b/doc/layering/explicit-layering1.svg deleted file mode 100644 index 2a5c921ce7..0000000000 --- a/doc/layering/explicit-layering1.svg +++ /dev/null @@ -1,322 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - time - - - - - A - B - C - D - - layer - - 0 - - 1 - - 2 - - diff --git a/doc/layering/explicit-layering2.svg b/doc/layering/explicit-layering2.svg deleted file mode 100644 index 4f71a40eb9..0000000000 --- a/doc/layering/explicit-layering2.svg +++ /dev/null @@ -1,322 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - time - - - - - A - B - C - D - - layer - - 0 - - 1 - - 2 - - diff --git a/doc/layering/layering-order-1.svg b/doc/layering/layering-order-1.svg new file mode 100644 index 0000000000..25e329c0f4 --- /dev/null +++ b/doc/layering/layering-order-1.svg @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + 0 + 1 + 2 + 3 + 4 + 5 + + increasinglayeringorder + + diff --git a/doc/layering/layering-order-2.svg b/doc/layering/layering-order-2.svg new file mode 100644 index 0000000000..b4164444ba --- /dev/null +++ b/doc/layering/layering-order-2.svg @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + 0 + + + + 1 + + + + 2 + + + + + + 3 + + + + + 4 + + + + + + + + + 5 + + + + + + increasinglayer + + diff --git a/doc/layering/layering.tex b/doc/layering/layering.tex index 317aaeae43..35670cbfde 100644 --- a/doc/layering/layering.tex +++ b/doc/layering/layering.tex @@ -1,19 +1,19 @@ \documentclass{article} -\title{Region Layering} +\title{Region layering} \author{} \date{} -\usepackage{graphicx} +\usepackage{graphicx,amsmath} \begin{document} \maketitle \section{Introduction} When regions overlap in time, we need to decide which one should be -played. Ardour has a few options to set how this decision is made. +played. -\subsection{Layers} +\section{Layers} Each region on a playlist is on a \emph{layer}. All overlapping regions are on a unique layer, and when overlaps exist the highest-layered @@ -37,114 +37,168 @@ This follows the basic rule that, at any given point, the region on the highest layer will be played. -\section{Choice of layering} +\section{Which layer does a region go on?} -There are two main decisions to be made with regards to how a playlist should be layered: - -\begin{itemize} -\item Given overlapping regions, what order should they be layered in? -\item When should layering be changed? -\end{itemize} +The logic to decide which layer a region goes onto is somewhat complicated. +This section describes it in hand-wavey and more technical terms. -\subsection{Layering order} +\subsection{Hand-wavey description} -Ardour provides three-and-a-half ways to decide on the order in which -regions are layered. The most basic choice is: - -\begin{itemize} -\item \emph{Later is higher} --- regions which are later in time will - be on higher layers. -\item \emph{Most recently added is higher} --- regions which were more - recently added to the playlist will be on higher layers. -\item \emph{Most recently edited or added is higher} --- regions which - were more recently edited or added to the playlist will be on - higher layers. -\end{itemize} - -This choice can be set per-session from the \emph{Session Properties} dialogue -box. - -\subsubsection{Explicit ordering} - -There are also cases when none of these rules should apply. If, for -example, you want to put a given region at the top of the stack (on -the highest layer), this is possible using the region `raise to top' -command. Following such a command (called an `explicit layering'), -the regions on the playlist may no longer obey any of the standard -ordering rules. - -This situation also arises when editing tracks using the `stacked' layer mode. -In this mode, almost all layering is explicit. When starting a region drag, -the other regions on a track spread apart vertically to allow the dragged -region to be dropped in any position within the region stack. The normal -layering rules will only be followed if a region is dropped on top of another; -in all other cases, explicit layering will be used to put the region wherever -it was dropped. - -\subsection{When to update layering} - -There are two distinct approaches to updating layering: - -\begin{itemize} -\item Update whenever any region edit is performed. -\item Update only when a region is edited such that a new overlap has been set up. -\end{itemize} - -The approach to use is optional, and can be set in \emph{Session Properties}. - -This decision only has consequences when an explicit layering command has -been used. Consider the case in Figure~\ref{fig:explicit-layering1}. +A playlist maintains an internal \emph{layering order} for regions. This order +is not directly visible in Ardour, but it's useful to understand it +nonetheless. Figure~\ref{fig:layering-order-1} gives a rough idea of what this +means. \begin{figure}[ht] \begin{center} -\includegraphics{explicit-layering1.pdf} +\includegraphics{layering-order-1.pdf} \end{center} -\caption{Explicit layering: stage 1} -\label{fig:explicit-layering1} +\caption{Layering order} +\label{fig:layering-order-1} \end{figure} -Given that arrangement, imagine that we perform a `raise to top' on region $C$. -This results in the arrangement in Figure~\ref{fig:explicit-layering2}. +Here we see 6 regions; as the layering order value increases, the region will +be placed on a higher layer. + +Every time any region is moved, added or edited, a \emph{relayer} occurs. This +collapses the regions down into layers. For our example, this would result in +the arrangement in Figure~\ref{fig:layering-order-2}. \begin{figure}[ht] \begin{center} -\includegraphics{explicit-layering2.pdf} +\includegraphics{layering-order-2.pdf} \end{center} -\caption{Explicit layering: stage 2} -\label{fig:explicit-layering2} +\caption{Layering} +\label{fig:layering-order-2} \end{figure} -Imagine now that region $C$ is moved very slightly to the left, so -that it still overlaps both $A$ and $B$. If we are updating whenever -any region edit is performed, this will result in a relayer; the -regions' arrangement will go back to that in -Figure~\ref{fig:explicit-layering1}. +The relayer operation takes each region, in the layering order, and puts it +on the lowest possible layer that it can be on without overlap. -If, on the other hand, we only relayer when a new overlap is set up, -the region layering will remain as in -Figure~\ref{fig:explicit-layering2}. Before the edit, regions $A$, -$B$ and $C$ overlapped; after the edit, the situation is the same, so -no relayering is performed. -Another, more complex, example is shown in Figure~\ref{fig:tricky-explicit-layering}. +\subsubsection{Layering order} + +Given that arrangement, the remaining question is how the layering order is +arrived at. The rules are as follows: + +\begin{itemize} + +\item When a region is added to a playlist, it goes above the current highest + region in the layering order. + +\item In `overlaid' track mode, moving or editing regions does not change the + layering order. Hence, moving regions about will maintain their position in + the layering order. Changing overlaps may change the \emph{layer} that the + region ends up on, but not the order in which they will be layered. + +\item In `stacked' track mode, moving regions places the region on the layer + that they are dropped on. This is achieved by modifying the layering order + for the region that is moved, so that when the relayer operation happens the + region ends up on the desired layer. + +\item When regions are `raised' or `lowered' in the stack, the layering order + is modified to achieve the desired layer change. + +\end{itemize} + +The upshot of all this is that regions should maintain their expected layering +order, unless that order is explicitly change using `stacked' mode or by +explicit layering commands like `raise' or `lower'. + + + +\subsection{Technical description} + +Each region on a playlist has three layering-related properties: its current +layer $c$ (an integer) and its layering index $i$ (also an integer). It also +has an \emph{optional} pending layer $p$ which is fractional. + +Whenever a region is added, moved, trimmed, etc.\ we run a \emph{relayer}. This +does the following: + +\begin{enumerate} +\item Take a list of all regions and remove those who have a value for $p$. +\item Sort the remainder in ascending order of $i$. +\item Insert the regions which have a value for $p$ in the correct place in the + list by comparing $c$ of those in the list to $p$ of the inserted region. +\item Iterate over the resulting list, putting each region on the lowest available + layer, setting its current layer $c$, and clearing $p$. +\item If any region had a pending layer, iterate through the region list again + giving each region a new layering index $i$ ascending from 0. +\end{enumerate} + +The pending layer $p$ is set up in the following situations: +\begin{enumerate} +\item When a region is added to the playlist, $p$ is set to $\infty$. +\item When a region is raised to the top of the playlist, $p$ is set to $\infty$. +\item When a region is raised one step in the playlist, $p$ is set to $c + 1.5$. +\item When a region is lowered to the bottom of the playlist, $p$ is set to $-0.5$. +\item When a region is lowered one step int the playlist, $p$ is set to $c - 1.5$. +\item When a region is explicitly put between layers $A$ and $B$ in `stacked' + mode, $p$ is set to $(A + B) / 2$. +\end{enumerate} + +The idea of this approach is that the layering indices $i$ are used to keep a +current state of the stack, and this state is used to maintain region +relationships. Setting $p$ will alter these relationships, after which the +layering indices $i$ are updated to reflect the new status quo. + +It is not sufficient to use current layer $c$ as the state of the stack. +Consider two overlapping regions $P$ and $Q$, with $P$ on layer~0 and $Q$ on +layer~1. Now raise $P$ to the top of the stack, so that $Q$ is on layer~0 and +$P$ on layer~1. Move $P$ away from $Q$ (in overlaid mode) so that both regions +are on layer~0. Now drag $P$ back over $Q$. One would expect $P$ to return to +the top of the stack, since it was explicitly raised earlier. However, if the +relayer operation were to compare $c$ for each region, they would be identical; +the information that $P$ was once higher than $Q$ has been lost. + + +\section{Stacked mode} + +When a track is being displayed in \emph{stacked} mode, regions are spread out +vertically to indicate their layering, like in Figure~\ref{fig:stacked}. \begin{figure}[ht] \begin{center} -\includegraphics{tricky-explicit-layering.pdf} +\includegraphics[scale=0.5]{stacked.png} \end{center} -\caption{More complex explicit layering} -\label{fig:tricky-explicit-layering} +\caption{A track in stacked mode} +\label{fig:stacked} \end{figure} -% XXX: this makes no sense +In this mode, layering is performed \emph{explicitly}. In other words, the +user's immediate actions decide which layer a region should be put on. When a +region move drag is started in stacked mode, the regions separate further out +vertically, to leave space between each layer, as shown in +Figure~\ref{fig:stacked-drag}. -Here, imagine that $C$ has been moved to the top of the stack with an explicit -`raise to top' command. Now consider an extension of $C$ so that its -right-hand edge overlaps $D$. If we are relayering only on new overlaps, this -case presents one new overlap (that of $C$ with $D$). In this case, $C$ is -moved according to the current layering rules so that it is correct with -respect to $D$. In addition, $A$ and $B$ are re-layered so that the relation -of $C$ to $A$ and $B$ is preserved. +\begin{figure}[ht] +\begin{center} +\includegraphics[scale=0.5]{stacked-drag.png} +\end{center} +\caption{A track in stacked mode during a drag} +\label{fig:stacked-drag} +\end{figure} + +The region(s) being dragged can then be dropped in any location, horizontally +and vertically, and the regions will be layered accordingly. + + +\section{Overlaid mode} + +When a track is being displayed in \emph{overlaid} mode, regions are +displayed on top of one another, like in Figure~\ref{fig:overlaid}. + +\begin{figure}[ht] +\begin{center} +\includegraphics[scale=0.5]{overlaid.png} +\end{center} +\caption{A track in overlaid mode} +\label{fig:overlaid} +\end{figure} + +In this mode, drags of regions maintain the same \emph{layer ordering}, even if the layers may +change. \end{document} diff --git a/doc/layering/tricky-explicit-layering.svg b/doc/layering/tricky-explicit-layering.svg deleted file mode 100644 index f4e89f6f92..0000000000 --- a/doc/layering/tricky-explicit-layering.svg +++ /dev/null @@ -1,323 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - time - - - - A - B - - C - D - - layer - - 0 - - 1 - - 2 - - diff --git a/gtk2_ardour/audio_streamview.cc b/gtk2_ardour/audio_streamview.cc index 6dbef4484d..c7afa6e3bf 100644 --- a/gtk2_ardour/audio_streamview.cc +++ b/gtk2_ardour/audio_streamview.cc @@ -812,7 +812,7 @@ AudioStreamView::update_content_height (CrossfadeView* cv) switch (_layer_display) { case Overlaid: cv->set_y (0); - cv->set_height (height); + cv->set_heights (height, height); break; case Stacked: @@ -827,10 +827,10 @@ AudioStreamView::update_content_height (CrossfadeView* cv) if (_layer_display == Stacked) { cv->set_y ((_layers - high - 1) * h); - cv->set_height ((high - low + 1) * h); + cv->set_heights ((high - low + 1) * h, h); } else { cv->set_y (((_layers - high) * 2 - 1) * h); - cv->set_height (((high - low) * 2 + 1) * h); + cv->set_heights (((high - low) * 2 + 1) * h, h); } } } diff --git a/gtk2_ardour/crossfade_view.cc b/gtk2_ardour/crossfade_view.cc index 6db53e9440..10792e0a93 100644 --- a/gtk2_ardour/crossfade_view.cc +++ b/gtk2_ardour/crossfade_view.cc @@ -57,7 +57,8 @@ CrossfadeView::CrossfadeView (ArdourCanvas::Group *parent, crossfade (xf), left_view (lview), right_view (rview), - _all_in_view (false) + _all_in_view (false), + _child_height (0) { _valid = true; _visible = true; @@ -109,13 +110,15 @@ CrossfadeView::reset_width_dependent_items (double pixel_width) } void -CrossfadeView::set_height (double h) +CrossfadeView::set_heights (double fade_height, double child_height) { - if (h > TimeAxisView::preset_height (HeightSmall)) { - h -= NAME_HIGHLIGHT_SIZE; + if (child_height > TimeAxisViewItem::NAME_HIGHLIGHT_THRESH) { + fade_height -= NAME_HIGHLIGHT_SIZE; + child_height -= NAME_HIGHLIGHT_SIZE; } - TimeAxisViewItem::set_height (h); + TimeAxisViewItem::set_height (fade_height); + _child_height = child_height; redraw_curves (); } @@ -203,7 +206,9 @@ CrossfadeView::redraw_curves () for (int i = 0, pci = 0; i < npoints; ++i) { Art::Point &p = (*points)[pci++]; p.set_x (xoff + i + 1); - p.set_y (_height - ((_height - 2) * vec[i])); + + double const ho = crossfade->in()->layer() > crossfade->out()->layer() ? _child_height : _height; + p.set_y (ho - ((_child_height - 2) * vec[i])); } fade_in->property_points() = *points; @@ -213,7 +218,9 @@ CrossfadeView::redraw_curves () for (int i = 0, pci = 0; i < npoints; ++i) { Art::Point &p = (*points)[pci++]; p.set_x (xoff + i + 1); - p.set_y (_height - ((_height - 2) * vec[i])); + + double const ho = crossfade->in()->layer() < crossfade->out()->layer() ? _child_height : _height; + p.set_y (ho - ((_child_height - 2) * vec[i])); } fade_out->property_points() = *points; diff --git a/gtk2_ardour/crossfade_view.h b/gtk2_ardour/crossfade_view.h index d984f5d295..af33cb7296 100644 --- a/gtk2_ardour/crossfade_view.h +++ b/gtk2_ardour/crossfade_view.h @@ -48,7 +48,7 @@ public: AudioRegionView& left_view; // and these too AudioRegionView& right_view; - void set_height (double); + void set_heights (double, double); bool valid() const { return _valid; } bool visible() const { return _visible; } @@ -68,6 +68,7 @@ private: bool _valid; bool _visible; bool _all_in_view; + double _child_height; ArdourCanvas::Line *fade_in; ArdourCanvas::Line *fade_out; diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index 30bde56a68..ea8cc07209 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -2475,7 +2475,7 @@ Editor::get_state () /** @param y y offset from the top of all trackviews. * @return pair: TimeAxisView that y is over, layer index. * TimeAxisView may be 0. Layer index is the layer number if the TimeAxisView is valid and is - * in stacked region display mode, otherwise 0. + * in stacked or expanded region display mode, otherwise 0. */ std::pair Editor::trackview_by_y_position (double y) diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index f37d6e2556..d03084e2d0 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -488,10 +488,6 @@ RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) pair const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ()); _last_pointer_time_axis_view = find_time_axis_view (tv.first); _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second; - - if (tv.first->view()->layer_display() == Stacked) { - tv.first->view()->set_layer_display (Expanded); - } } double @@ -578,7 +574,6 @@ RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer) const /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded' mode to allow the user to place a region below another on layer 0. */ - if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) { /* Off the top or bottom layer; note that we only refuse if the track hasn't changed. If it has, the layers will be munged later anyway, so it's ok. @@ -599,6 +594,10 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) /* Find the TimeAxisView that the pointer is now over */ pair const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ()); + if (first_move && tv.first->view()->layer_display() == Stacked) { + tv.first->view()->set_layer_display (Expanded); + } + /* Bail early if we're not over a track */ RouteTimeAxisView* rtv = dynamic_cast (tv.first); if (!rtv || !rtv->is_track()) { @@ -646,21 +645,21 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) if (first_move) { rv->get_time_axis_view().hide_dependent_views (*rv); - + + /* Absolutely no idea why this is necessary, but it is; without + it, the region view disappears after the reparent. + */ + _editor->update_canvas_now (); + /* Reparent to a non scrolling group so that we can keep the region selection above all time axis views. Reparenting means that we will have to move the region view later, as the two parent groups have different coordinates. */ - + rv->get_canvas_group()->reparent (*(_editor->_region_motion_group)); rv->fake_set_opaque (true); - - if (!rv->get_time_axis_view().hidden()) { - /* the track that this region view is on is hidden, so hide the region too */ - rv->get_canvas_group()->hide (); - } } /* If we have moved tracks, we'll fudge the layer delta so that the @@ -680,12 +679,12 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) if (tv->view()->layer_display() == Stacked) { tv->view()->set_layer_display (Expanded); } - + /* We're only allowed to go -ve in layer on Expanded views */ if (tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) { this_delta_layer = - i->layer; } - + /* Set height */ rv->set_height (tv->view()->child_height ()); @@ -826,12 +825,13 @@ RegionMotionDrag::finished (GdkEvent *, bool) (*i)->view()->set_layer_display (Stacked); } } -} +} void RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred) { RegionMotionDrag::finished (ev, movement_occurred); + if (!movement_occurred) { /* just a click */ return; @@ -965,9 +965,6 @@ RegionMoveDrag::finished_no_copy ( RegionSelection new_views; PlaylistSet modified_playlists; PlaylistSet frozen_playlists; - - list, double> > pending_explicit_relayers; - Playlist::RegionList pending_implicit_relayers; set views_to_update; if (_brushing) { @@ -1049,12 +1046,8 @@ RegionMoveDrag::finished_no_copy ( boost::shared_ptr playlist = dest_rtv->playlist(); - bool const explicit_relayer = dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded; - - if (explicit_relayer) { - pending_explicit_relayers.push_back (make_pair (rv->region (), dest_layer)); - } else { - pending_implicit_relayers.push_back (rv->region ()); + if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) { + rv->region()->set_pending_layer (dest_layer); } /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */ @@ -1063,7 +1056,6 @@ RegionMoveDrag::finished_no_copy ( if (r.second) { playlist->freeze (); - playlist->suspend_relayer (); } /* this movement may result in a crossfade being modified, so we need to get undo @@ -1119,24 +1111,10 @@ RegionMoveDrag::finished_no_copy ( _editor->selection->set (new_views); } - /* We can't use the normal mechanism for relayering, as some regions may require an explicit relayer - rather than an implicit one. So we thaw before resuming relayering, then do the relayers - that we require. - */ - - for (PlaylistSet::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) { + for (set >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) { (*p)->thaw(); - (*p)->resume_relayer (); } - for (list, double> >::iterator i = pending_explicit_relayers.begin(); i != pending_explicit_relayers.end(); ++i) { - i->first->playlist()->relayer (i->first, i->second); - } - - for (Playlist::RegionList::iterator i = pending_implicit_relayers.begin(); i != pending_implicit_relayers.end(); ++i) { - (*i)->playlist()->relayer (*i); - } - /* write commands for the accumulated diffs for all our modified playlists */ add_stateful_diff_commands_for_playlists (modified_playlists); @@ -1144,11 +1122,11 @@ RegionMoveDrag::finished_no_copy ( /* We have futzed with the layering of canvas items on our streamviews. If any region changed layer, this will have resulted in the stream - views being asked to set up their region views, and all will be - well. If not, we might now have badly-ordered region views. Ask - the Streamviews involved to sort themselves out, just in case. + views being asked to set up their region views, and all will be well. + If not, we might now have badly-ordered region views. Ask the StreamViews + involved to sort themselves out, just in case. */ - + for (set::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) { (*i)->view()->playlist_layered ((*i)->track ()); } @@ -1214,7 +1192,7 @@ RegionMoveDrag::insert_region_into_playlist ( dest_playlist->add_region (region, where); if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) { - dest_playlist->relayer (region, dest_layer); + region->set_pending_layer (dest_layer); } c.disconnect (); @@ -1268,7 +1246,7 @@ RegionMotionDrag::aborted (bool) (*i)->view()->set_layer_display (Stacked); } } - + for (list::const_iterator i = _views.begin(); i != _views.end(); ++i) { RegionView* rv = i->view; TimeAxisView* tv = &(rv->get_time_axis_view ()); diff --git a/gtk2_ardour/editor_drag.h b/gtk2_ardour/editor_drag.h index a6f5088d43..4170315294 100644 --- a/gtk2_ardour/editor_drag.h +++ b/gtk2_ardour/editor_drag.h @@ -249,7 +249,7 @@ struct DraggingView * or -1 if it is not visible. */ int time_axis_view; - /** Layer that this region is currently being displayed on. This is a double + /** layer that this region is currently being displayed on. This is a double rather than a layer_t as we use fractional layers during drags to allow the user to indicate a new layer to put a region on. */ diff --git a/gtk2_ardour/session_option_editor.cc b/gtk2_ardour/session_option_editor.cc index 6ffb95b464..dc72e6a045 100644 --- a/gtk2_ardour/session_option_editor.cc +++ b/gtk2_ardour/session_option_editor.cc @@ -262,29 +262,6 @@ SessionOptionEditor::SessionOptionEditor (Session* s) /* Misc */ - add_option (_("Misc"), new OptionEditorHeading (_("Layering (in overlaid mode)"))); - - ComboOption* lm = new ComboOption ( - "layer-model", - _("Layering model"), - sigc::mem_fun (*_session_config, &SessionConfiguration::get_layer_model), - sigc::mem_fun (*_session_config, &SessionConfiguration::set_layer_model) - ); - - lm->add (LaterHigher, _("later is higher")); - lm->add (AddOrBoundsChangeHigher, _("most recently edited or added is higher")); - lm->add (AddHigher, _("most recently added is higher")); - - add_option (_("Misc"), new BoolOption ( - "relayer-on-all-edits", - _("Relayer regions after every edit"), - sigc::mem_fun (*_session_config, &SessionConfiguration::get_relayer_on_all_edits), - sigc::mem_fun (*_session_config, &SessionConfiguration::set_relayer_on_all_edits) - )); - - - add_option (_("Misc"), lm); - add_option (_("Misc"), new OptionEditorHeading (_("MIDI Options"))); add_option (_("Misc"), new BoolOption ( diff --git a/gtk2_ardour/streamview.cc b/gtk2_ardour/streamview.cc index 06a9d77957..2fce87fa80 100644 --- a/gtk2_ardour/streamview.cc +++ b/gtk2_ardour/streamview.cc @@ -181,7 +181,7 @@ StreamView::add_region_view (boost::weak_ptr wr) add_region_view_internal (r, true); - if (_layer_display == Stacked) { + if (_layer_display == Stacked || _layer_display == Expanded) { update_contents_height (); } } @@ -301,7 +301,7 @@ StreamView::playlist_layered (boost::weak_ptr wtr) _layers = tr->playlist()->top_layer() + 1; } - if (_layer_display == Stacked || _layer_display == Expanded) { + if (_layer_display == Stacked) { update_contents_height (); update_coverage_frames (); } else { @@ -574,7 +574,7 @@ StreamView::child_height () const case Expanded: return height / (_layers * 2 + 1); } - + /* NOTREACHED */ return height; } diff --git a/gtk2_ardour/time_axis_view.cc b/gtk2_ardour/time_axis_view.cc index fe02a8bc15..79f425f32f 100644 --- a/gtk2_ardour/time_axis_view.cc +++ b/gtk2_ardour/time_axis_view.cc @@ -1196,7 +1196,7 @@ TimeAxisView::color_handler () * If the covering object is a child axis, then the child is returned. * TimeAxisView is 0 otherwise. * Layer index is the layer number (possibly fractional) if the TimeAxisView is valid - * and is in stacked or expanded region display mode, otherwise 0. + * and is in stacked or expanded * region display mode, otherwise 0. */ std::pair TimeAxisView::covers_y_position (double y) @@ -1215,7 +1215,7 @@ TimeAxisView::covers_y_position (double y) case Stacked: if (view ()) { /* compute layer */ - l = floor ((_y_position + height - y) / (view()->child_height ())); + l = layer_t ((_y_position + height - y) / (view()->child_height ())); /* clamp to max layers to be on the safe side; sometimes the above calculation returns a too-high value */ if (l >= view()->layers ()) { @@ -1225,7 +1225,7 @@ TimeAxisView::covers_y_position (double y) break; case Expanded: if (view ()) { - int n = floor ((_y_position + height - y) / (view()->child_height ())); + int const n = floor ((_y_position + height - y) / (view()->child_height ())); l = n * 0.5 - 0.5; if (l >= (view()->layers() - 0.5)) { l = view()->layers() - 0.5; diff --git a/libs/ardour/ardour/playlist.h b/libs/ardour/ardour/playlist.h index a8be02a0b9..a7ead83eef 100644 --- a/libs/ardour/ardour/playlist.h +++ b/libs/ardour/ardour/playlist.h @@ -44,9 +44,6 @@ #include "ardour/session_object.h" #include "ardour/data_type.h" -class PlaylistOverlapCacheTest; -class PlaylistLayeringTest; - namespace ARDOUR { class Session; @@ -225,10 +222,7 @@ public: framepos_t find_next_top_layer_position (framepos_t) const; uint32_t combine_ops() const { return _combine_ops; } - void relayer (boost::shared_ptr); - void relayer (boost::shared_ptr, double); - void suspend_relayer (); - void resume_relayer (); + uint64_t highest_layering_index () const; protected: friend class Session; @@ -355,7 +349,7 @@ public: boost::shared_ptr cut (framepos_t start, framecnt_t cnt, bool result_is_hidden); boost::shared_ptr copy (framepos_t start, framecnt_t cnt, bool result_is_hidden); - void relayer (RegionList const &); + void relayer (); void begin_undo (); void end_undo (); @@ -382,71 +376,6 @@ public: with its constituent regions */ virtual void pre_uncombine (std::vector >&, boost::shared_ptr) {} - -private: - friend class ::PlaylistOverlapCacheTest; - friend class ::PlaylistLayeringTest; - - /** A class which is used to store temporary (fractional) - * layer assignments for some regions. - */ - class TemporaryLayers - { - public: - void set (boost::shared_ptr, double); - double get (boost::shared_ptr) const; - - private: - typedef std::map, double> Map; - Map _map; - }; - - /** Class to sort by temporary layer, for use with std::list<>::sort() */ - class SortByTemporaryLayer - { - public: - SortByTemporaryLayer (TemporaryLayers const & t) - : _temporary_layers (t) {} - - bool operator() (boost::shared_ptr a, boost::shared_ptr b) const { - return _temporary_layers.get (a) < _temporary_layers.get (b); - } - - private: - Playlist::TemporaryLayers const & _temporary_layers; - }; - - /** A cache of what overlaps what, for a given playlist in a given state. - * Divides a playlist up into time periods and notes which regions cover those - * periods, so that get() is reasonably quick. - */ - class OverlapCache - { - public: - OverlapCache (Playlist *); - - RegionList get (Evoral::Range) const; - - private: - std::pair cache_indices (Evoral::Range) const; - - double _division_size; - std::vector _cache; - Evoral::Range _range; - - static int const _divisions; - }; - - TemporaryLayers compute_temporary_layers (RegionList const &); - void commit_temporary_layers (TemporaryLayers const &); - - RegionList recursive_regions_touched (boost::shared_ptr, OverlapCache const &, boost::shared_ptr) const; - void recursive_regions_touched_sub (boost::shared_ptr, OverlapCache const &, boost::shared_ptr, RegionList &) const; - - void timestamp_layer_op (LayerOp, boost::shared_ptr); - uint64_t layer_op_counter; - - bool _relayer_suspended; }; } /* namespace ARDOUR */ diff --git a/libs/ardour/ardour/region.h b/libs/ardour/ardour/region.h index 4315ed7aa8..ab1559fc4a 100644 --- a/libs/ardour/ardour/region.h +++ b/libs/ardour/ardour/region.h @@ -65,6 +65,7 @@ namespace Properties { extern PBD::PropertyDescriptor stretch; extern PBD::PropertyDescriptor shift; extern PBD::PropertyDescriptor position_lock_style; + extern PBD::PropertyDescriptor layering_index; }; class Playlist; @@ -112,7 +113,6 @@ class Region framepos_t start () const { return _start; } framecnt_t length () const { return _length; } layer_t layer () const { return _layer; } - Evoral::Range bounds () const; framecnt_t source_length(uint32_t n) const; uint32_t max_source_level () const; @@ -202,7 +202,7 @@ class Region void cut_front (framepos_t new_position); void cut_end (framepos_t new_position); - void set_layer (layer_t l); /* ONLY Playlist should call this */ + void set_layer (layer_t l); /* ONLY Playlist can call this */ void raise (); void lower (); void raise_to_top (); @@ -252,8 +252,8 @@ class Region virtual boost::shared_ptr get_parent() const; - uint64_t last_layer_op (LayerOp) const; - void set_last_layer_op (LayerOp, uint64_t); + uint64_t layering_index () const { return _layering_index; } + void set_layering_index (uint64_t when) { _layering_index = when; } virtual bool is_dependent() const { return false; } virtual bool depends_on (boost::shared_ptr /*other*/) const { return false; } @@ -294,12 +294,11 @@ class Region void invalidate_transients (); - void drop_sources (); + void set_pending_layer (double); + bool reset_pending_layer (); + boost::optional pending_layer () const; - /** @return our bounds the last time our relayer() method was called */ - Evoral::Range last_relayer_bounds () const { - return Evoral::Range (_last_relayer_bounds_from, _last_relayer_bounds_to); - } + void drop_sources (); protected: friend class RegionFactory; @@ -384,18 +383,15 @@ class Region PBD::Property _stretch; PBD::Property _shift; PBD::EnumProperty _position_lock_style; + PBD::Property _layering_index; - /* XXX: could use a Evoral::Range<> but I'm too lazy to make PBD::Property serialize such a thing nicely */ - PBD::Property _last_relayer_bounds_from; ///< from of our bounds last time relayer() was called - PBD::Property _last_relayer_bounds_to; ///< to of our bounds last time relayer() was called - PBD::Property _last_layer_op_add; - PBD::Property _last_layer_op_bounds_change; - framecnt_t _last_length; framepos_t _last_position; mutable RegionEditState _first_edit; Timecode::BBT_Time _bbt_time; + boost::optional _pending_layer; + void register_properties (); void use_sources (SourceList const &); diff --git a/libs/ardour/ardour/region_sorters.h b/libs/ardour/ardour/region_sorters.h index 0e3203ef20..baa3a3417d 100644 --- a/libs/ardour/ardour/region_sorters.h +++ b/libs/ardour/ardour/region_sorters.h @@ -36,22 +36,6 @@ struct RegionSortByLayer { } }; -struct RegionSortByAdd { - bool operator() (boost::shared_ptr a, boost::shared_ptr b) { - return ( - (a->last_layer_op (LayerOpAdd) < b->last_layer_op (LayerOpAdd)) - ); - } -}; - -struct RegionSortByAddOrBounds { - bool operator() (boost::shared_ptr a, boost::shared_ptr b) { - uint64_t const p = std::max (a->last_layer_op (LayerOpAdd), a->last_layer_op (LayerOpBoundsChange)); - uint64_t const q = std::max (b->last_layer_op (LayerOpAdd), b->last_layer_op (LayerOpBoundsChange)); - return p < q; - } -}; - } // namespace #endif /* __libardour_region_sorters_h__ */ diff --git a/libs/ardour/ardour/session_configuration_vars.h b/libs/ardour/ardour/session_configuration_vars.h index 57f390f100..87b317d3e8 100644 --- a/libs/ardour/ardour/session_configuration_vars.h +++ b/libs/ardour/ardour/session_configuration_vars.h @@ -47,8 +47,6 @@ CONFIG_VARIABLE_SPECIAL(std::string, audio_search_path, "audio-search-path", "", CONFIG_VARIABLE_SPECIAL(std::string, midi_search_path, "midi-search-path", "", search_path_expand) CONFIG_VARIABLE (std::string, bwf_country_code, "bwf-country-code", "US") CONFIG_VARIABLE (std::string, bwf_organization_code, "bwf-organization-code", "US") -CONFIG_VARIABLE (LayerModel, layer_model, "layer-model", AddOrBoundsChangeHigher) -CONFIG_VARIABLE (bool, relayer_on_all_edits, "relayer-on-all-edits", true) CONFIG_VARIABLE (std::string, auditioner_output_left, "auditioner-output-left", "default") CONFIG_VARIABLE (std::string, auditioner_output_right, "auditioner-output-right", "default") CONFIG_VARIABLE (bool, timecode_source_is_synced, "timecode-source-is-synced", true) diff --git a/libs/ardour/ardour/types.h b/libs/ardour/ardour/types.h index b5e5d89cc1..c2c7289329 100644 --- a/libs/ardour/ardour/types.h +++ b/libs/ardour/ardour/types.h @@ -409,12 +409,6 @@ namespace ARDOUR { ShortCrossfade }; - enum LayerModel { - LaterHigher, - AddOrBoundsChangeHigher, - AddHigher - }; - enum ListenPosition { AfterFaderListen, PreFaderListen @@ -592,11 +586,6 @@ namespace ARDOUR { FadeLogB }; - enum LayerOp { - LayerOpAdd, - LayerOpBoundsChange - }; - } // namespace ARDOUR @@ -613,7 +602,6 @@ std::istream& operator>>(std::istream& o, ARDOUR::PFLPosition& sf); std::istream& operator>>(std::istream& o, ARDOUR::AFLPosition& sf); std::istream& operator>>(std::istream& o, ARDOUR::RemoteModel& sf); std::istream& operator>>(std::istream& o, ARDOUR::ListenPosition& sf); -std::istream& operator>>(std::istream& o, ARDOUR::LayerModel& sf); std::istream& operator>>(std::istream& o, ARDOUR::InsertMergePolicy& sf); std::istream& operator>>(std::istream& o, ARDOUR::CrossfadeModel& sf); std::istream& operator>>(std::istream& o, ARDOUR::SyncSource& sf); @@ -634,7 +622,6 @@ std::ostream& operator<<(std::ostream& o, const ARDOUR::PFLPosition& sf); std::ostream& operator<<(std::ostream& o, const ARDOUR::AFLPosition& sf); std::ostream& operator<<(std::ostream& o, const ARDOUR::RemoteModel& sf); std::ostream& operator<<(std::ostream& o, const ARDOUR::ListenPosition& sf); -std::ostream& operator<<(std::ostream& o, const ARDOUR::LayerModel& sf); std::ostream& operator<<(std::ostream& o, const ARDOUR::InsertMergePolicy& sf); std::ostream& operator<<(std::ostream& o, const ARDOUR::CrossfadeModel& sf); std::ostream& operator<<(std::ostream& o, const ARDOUR::SyncSource& sf); diff --git a/libs/ardour/audio_diskstream.cc b/libs/ardour/audio_diskstream.cc index 83399d2962..9a227bf7a5 100644 --- a/libs/ardour/audio_diskstream.cc +++ b/libs/ardour/audio_diskstream.cc @@ -1510,6 +1510,8 @@ AudioDiskstream::transport_stopped_wallclock (struct tm& when, time_t twhen, boo } i_am_the_modifier++; + + region->set_pending_layer (max_layer); _playlist->add_region (region, (*ci)->start, 1, non_layered()); i_am_the_modifier--; diff --git a/libs/ardour/enums.cc b/libs/ardour/enums.cc index 12de366c79..fcf3b031ad 100644 --- a/libs/ardour/enums.cc +++ b/libs/ardour/enums.cc @@ -73,7 +73,6 @@ setup_enum_writer () RemoteModel _RemoteModel; DenormalModel _DenormalModel; CrossfadeModel _CrossfadeModel; - LayerModel _LayerModel; InsertMergePolicy _InsertMergePolicy; ListenPosition _ListenPosition; SampleFormat _SampleFormat; @@ -264,11 +263,6 @@ setup_enum_writer () REGISTER_ENUM (ShortCrossfade); REGISTER (_CrossfadeModel); - REGISTER_ENUM (LaterHigher); - REGISTER_ENUM (AddOrBoundsChangeHigher); - REGISTER_ENUM (AddHigher); - REGISTER (_LayerModel); - REGISTER_ENUM (InsertMergeReject); REGISTER_ENUM (InsertMergeRelax); REGISTER_ENUM (InsertMergeReplace); @@ -711,19 +705,6 @@ std::ostream& operator<<(std::ostream& o, const ListenPosition& var) std::string s = enum_2_string (var); return o << s; } -std::istream& operator>>(std::istream& o, LayerModel& var) -{ - std::string s; - o >> s; - var = (LayerModel) string_2_enum (s, var); - return o; -} - -std::ostream& operator<<(std::ostream& o, const LayerModel& var) -{ - std::string s = enum_2_string (var); - return o << s; -} std::istream& operator>>(std::istream& o, InsertMergePolicy& var) { diff --git a/libs/ardour/playlist.cc b/libs/ardour/playlist.cc index a6925e444c..7ef59645d1 100644 --- a/libs/ardour/playlist.cc +++ b/libs/ardour/playlist.cc @@ -30,7 +30,6 @@ #include "pbd/convert.h" #include "pbd/failed_constructor.h" -#include "pbd/stacktrace.h" #include "pbd/stateful_diff_command.h" #include "pbd/xml++.h" @@ -187,8 +186,6 @@ Playlist::Playlist (boost::shared_ptr other, string namestr, boo in_partition = false; subcnt = 0; _frozen = other->_frozen; - - layer_op_counter = other->layer_op_counter; } Playlist::Playlist (boost::shared_ptr other, framepos_t start, framecnt_t cnt, string str, bool hide) @@ -256,6 +253,7 @@ Playlist::Playlist (boost::shared_ptr other, framepos_t start, f plist.add (Properties::length, len); plist.add (Properties::name, new_name); plist.add (Properties::layer, region->layer()); + plist.add (Properties::layering_index, region->layering_index()); new_region = RegionFactory::RegionFactory::create (region, plist); @@ -318,9 +316,7 @@ Playlist::init (bool hide) in_partition = false; subcnt = 0; _frozen = false; - layer_op_counter = 0; _combine_ops = 0; - _relayer_suspended = false; _session.history().BeginUndoRedo.connect_same_thread (*this, boost::bind (&Playlist::begin_undo, this)); _session.history().EndUndoRedo.connect_same_thread (*this, boost::bind (&Playlist::end_undo, this)); @@ -485,8 +481,6 @@ Playlist::notify_region_moved (boost::shared_ptr r) { Evoral::RangeMove const move (r->last_position (), r->length (), r->position ()); - /* We could timestamp the region's layer op here, but we're doing it in region_bounds_changed */ - if (holding_state ()) { pending_range_moves.push_back (move); @@ -503,8 +497,6 @@ Playlist::notify_region_moved (boost::shared_ptr r) void Playlist::notify_region_start_trimmed (boost::shared_ptr r) { - timestamp_layer_op (LayerOpBoundsChange, r); - if (r->position() >= r->last_position()) { /* trimmed shorter */ return; @@ -528,8 +520,6 @@ Playlist::notify_region_start_trimmed (boost::shared_ptr r) void Playlist::notify_region_end_trimmed (boost::shared_ptr r) { - timestamp_layer_op (LayerOpBoundsChange, r); - if (r->length() < r->last_length()) { /* trimmed shorter */ } @@ -556,8 +546,6 @@ Playlist::notify_region_added (boost::shared_ptr r) as though it could be. */ - timestamp_layer_op (LayerOpAdd, r); - if (holding_state()) { pending_adds.insert (r); pending_contents_change = true; @@ -566,7 +554,6 @@ Playlist::notify_region_added (boost::shared_ptr r) pending_contents_change = false; RegionAdded (boost::weak_ptr (r)); /* EMIT SIGNAL */ ContentsChanged (); /* EMIT SIGNAL */ - relayer (r); } } @@ -584,35 +571,31 @@ Playlist::flush_notifications (bool from_undo) in_flush = true; - /* We have: - - pending_bounds: regions whose bounds position and/or length changes - pending_removes: regions which were removed - pending_adds: regions which were added - pending_length: true if the playlist length might have changed - pending_contents_change: true if there was almost any change in the playlist - pending_range_moves: details of periods of time that have been moved about (when regions have been moved) - - */ - if (!pending_bounds.empty() || !pending_removes.empty() || !pending_adds.empty()) { regions_changed = true; } - /* Make a list of regions that need relayering */ - RegionList regions_to_relayer; + /* we have no idea what order the regions ended up in pending + bounds (it could be based on selection order, for example). + so, to preserve layering in the "most recently moved is higher" + model, sort them by existing layer, then timestamp them. + */ + + // RegionSortByLayer cmp; + // pending_bounds.sort (cmp); for (RegionList::iterator r = pending_bounds.begin(); r != pending_bounds.end(); ++r) { - regions_to_relayer.push_back (*r); dependent_checks_needed.insert (*r); } for (s = pending_removes.begin(); s != pending_removes.end(); ++s) { remove_dependents (*s); + // cerr << _name << " sends RegionRemoved\n"; RegionRemoved (boost::weak_ptr (*s)); /* EMIT SIGNAL */ } for (s = pending_adds.begin(); s != pending_adds.end(); ++s) { + // cerr << _name << " sends RegionAdded\n"; /* don't emit RegionAdded signal until relayering is done, so that the region is fully setup by the time anyone hear's that its been added @@ -621,14 +604,18 @@ Playlist::flush_notifications (bool from_undo) } if (regions_changed || pending_contents_change) { + if (!in_set_state) { + relayer (); + } pending_contents_change = false; + // cerr << _name << " sends 5 contents change @ " << get_microseconds() << endl; ContentsChanged (); /* EMIT SIGNAL */ + // cerr << _name << "done contents change @ " << get_microseconds() << endl; } for (s = pending_adds.begin(); s != pending_adds.end(); ++s) { (*s)->clear_changes (); RegionAdded (boost::weak_ptr (*s)); /* EMIT SIGNAL */ - regions_to_relayer.push_back (*s); } for (s = dependent_checks_needed.begin(); s != dependent_checks_needed.end(); ++s) { @@ -643,14 +630,6 @@ Playlist::flush_notifications (bool from_undo) RegionsExtended (pending_region_extensions); } - if (!regions_to_relayer.empty () && !from_undo) { - relayer (regions_to_relayer); - } - - if (pending_layering) { - LayeringChanged (); /* EMIT SIGNAL */ - } - clear_pending (); in_flush = false; @@ -686,6 +665,7 @@ Playlist::flush_notifications (bool from_undo) } if (itimes >= 1) { + region->set_pending_layer (DBL_MAX); add_region_internal (region, pos); pos += region->length(); --itimes; @@ -698,6 +678,7 @@ Playlist::flush_notifications (bool from_undo) for (int i = 0; i < itimes; ++i) { boost::shared_ptr copy = RegionFactory::create (region, true); + copy->set_pending_layer (DBL_MAX); add_region_internal (copy, pos); pos += region->length(); } @@ -718,6 +699,7 @@ Playlist::flush_notifications (bool from_undo) plist.add (Properties::layer, region->layer()); boost::shared_ptr sub = RegionFactory::create (region, plist); + sub->set_pending_layer (DBL_MAX); add_region_internal (sub, pos); } } @@ -758,6 +740,11 @@ Playlist::flush_notifications (bool from_undo) possibly_splice_unlocked (position, region->length(), region); + if (!holding_state ()) { + /* layers get assigned from XML state, and are not reset during undo/redo */ + relayer (); + } + /* we need to notify the existence of new region before checking dependents. Ick. */ notify_region_added (region); @@ -780,6 +767,7 @@ Playlist::flush_notifications (bool from_undo) _splicing = true; remove_region_internal (old); + newr->set_pending_layer (newr->layer ()); add_region_internal (newr, pos); _splicing = old_sp; @@ -817,6 +805,7 @@ Playlist::flush_notifications (bool from_undo) possibly_splice_unlocked (pos, -distance); if (!holding_state ()) { + relayer (); remove_dependents (region); } @@ -873,16 +862,12 @@ Playlist::flush_notifications (bool from_undo) * start and end if cutting == true. Regions that lie entirely within start and end are always * removed. */ + void Playlist::partition_internal (framepos_t start, framepos_t end, bool cutting, RegionList& thawlist) { RegionList new_regions; - /* Don't relayer regions that are created during this operation; leave them - on the same region as the original. - */ - suspend_relayer (); - { RegionLock rlock (this); @@ -961,7 +946,8 @@ Playlist::flush_notifications (bool from_undo) plist.add (Properties::start, current->start() + (pos2 - pos1)); plist.add (Properties::length, pos3 - pos2); plist.add (Properties::name, new_name); - plist.add (Properties::layer, current->layer()); + plist.add (Properties::layer, current->layer ()); + plist.add (Properties::layering_index, current->layering_index ()); plist.add (Properties::automatic, true); plist.add (Properties::left_of_split, true); plist.add (Properties::right_of_split, true); @@ -980,7 +966,8 @@ Playlist::flush_notifications (bool from_undo) plist.add (Properties::start, current->start() + (pos3 - pos1)); plist.add (Properties::length, pos4 - pos3); plist.add (Properties::name, new_name); - plist.add (Properties::layer, current->layer()); + plist.add (Properties::layer, current->layer ()); + plist.add (Properties::layering_index, current->layering_index ()); plist.add (Properties::automatic, true); plist.add (Properties::right_of_split, true); @@ -1018,7 +1005,8 @@ Playlist::flush_notifications (bool from_undo) plist.add (Properties::start, current->start() + (pos2 - pos1)); plist.add (Properties::length, pos4 - pos2); plist.add (Properties::name, new_name); - plist.add (Properties::layer, current->layer()); + plist.add (Properties::layer, current->layer ()); + plist.add (Properties::layering_index, current->layering_index ()); plist.add (Properties::automatic, true); plist.add (Properties::left_of_split, true); @@ -1061,7 +1049,8 @@ Playlist::flush_notifications (bool from_undo) plist.add (Properties::start, current->start()); plist.add (Properties::length, pos3 - pos1); plist.add (Properties::name, new_name); - plist.add (Properties::layer, current->layer()); + plist.add (Properties::layer, current->layer ()); + plist.add (Properties::layering_index, current->layering_index ()); plist.add (Properties::automatic, true); plist.add (Properties::right_of_split, true); @@ -1108,8 +1097,6 @@ Playlist::flush_notifications (bool from_undo) for (RegionList::iterator i = new_regions.begin(); i != new_regions.end(); ++i) { check_dependents (*i, false); } - - resume_relayer (); } boost::shared_ptr @@ -1210,7 +1197,7 @@ Playlist::flush_notifications (bool from_undo) int itimes = (int) floor (times); framepos_t pos = position; framecnt_t const shift = other->_get_extent().second; - layer_t top_layer = regions.size(); + layer_t top = top_layer (); while (itimes--) { for (RegionList::iterator i = other->regions.begin(); i != other->regions.end(); ++i) { @@ -1220,7 +1207,7 @@ Playlist::flush_notifications (bool from_undo) the ordering they had in the original playlist. */ - copy_of_region->set_layer (copy_of_region->layer() + top_layer); + copy_of_region->set_pending_layer (copy_of_region->layer() + top); add_region_internal (copy_of_region, (*i)->position() + pos); } pos += shift; @@ -1242,6 +1229,7 @@ Playlist::flush_notifications (bool from_undo) while (itimes--) { boost::shared_ptr copy = RegionFactory::create (region, true); + copy->set_pending_layer (DBL_MAX); add_region_internal (copy, pos); pos += region->length(); } @@ -1259,6 +1247,7 @@ Playlist::flush_notifications (bool from_undo) plist.add (Properties::name, name); boost::shared_ptr sub = RegionFactory::create (region, plist); + sub->set_pending_layer (DBL_MAX); add_region_internal (sub, pos); } } @@ -1360,6 +1349,8 @@ Playlist::flush_notifications (bool from_undo) plist.add (Properties::length, before); plist.add (Properties::name, before_name); plist.add (Properties::left_of_split, true); + plist.add (Properties::layering_index, region->layering_index ()); + plist.add (Properties::layer, region->layer ()); /* note: we must use the version of ::create with an offset here, since it supplies that offset to the Region constructor, which @@ -1377,6 +1368,8 @@ Playlist::flush_notifications (bool from_undo) plist.add (Properties::length, after); plist.add (Properties::name, after_name); plist.add (Properties::right_of_split, true); + plist.add (Properties::layering_index, region->layering_index ()); + plist.add (Properties::layer, region->layer ()); /* same note as above */ right = RegionFactory::create (region, before, plist); @@ -1470,8 +1463,6 @@ Playlist::flush_notifications (bool from_undo) if (what_changed.contains (Properties::position)) { - timestamp_layer_op (LayerOpBoundsChange, region); - /* remove it from the list then add it back in the right place again. */ @@ -1512,7 +1503,7 @@ Playlist::flush_notifications (bool from_undo) pending_bounds.push_back (region); } else { notify_contents_changed (); - relayer (region); + relayer (); check_dependents (region, false); } } @@ -1572,9 +1563,9 @@ Playlist::flush_notifications (bool from_undo) notify_region_start_trimmed (region); } - if (what_changed.contains (Properties::layer)) { - notify_layering_changed (); - } + /* don't notify about layer changes, since we are the only object that can initiate + them, and we notify in ::relayer() + */ if (what_changed.contains (our_interests)) { save = true; @@ -2134,7 +2125,6 @@ Playlist::flush_notifications (bool from_undo) return -1; } - suspend_relayer (); freeze (); plist = node.properties(); @@ -2196,7 +2186,7 @@ Playlist::flush_notifications (bool from_undo) } add_region (region, region->position(), 1.0); - + region->resume_property_changes (); } @@ -2217,7 +2207,6 @@ Playlist::flush_notifications (bool from_undo) thaw (); notify_contents_changed (); - resume_relayer (); in_set_state--; first_set_state = false; @@ -2345,277 +2334,189 @@ Playlist::set_edit_mode (EditMode mode) _edit_mode = mode; } -/** Relayer a region. See the other relayer() methods for commentary. */ +struct RelayerSort { + bool operator () (boost::shared_ptr a, boost::shared_ptr b) { + return a->layering_index() < b->layering_index(); + } +}; + void -Playlist::relayer (boost::shared_ptr region) +Playlist::relayer () { - if (_relayer_suspended) { + /* never compute layers when changing state for undo/redo or setting from XML */ + + if (in_update || in_set_state) { return; } - - RegionList r; - r.push_back (region); - relayer (r); -} -Playlist::TemporaryLayers -Playlist::compute_temporary_layers (RegionList const & relayer_regions) -{ - TemporaryLayers temporary_layers; - OverlapCache cache (this); + bool changed = false; - for (RegionList::const_iterator i = relayer_regions.begin(); i != relayer_regions.end(); ++i) { + /* Build up a new list of regions on each layer, stored in a set of lists + each of which represent some period of time on some layer. The idea + is to avoid having to search the entire region list to establish whether + each region overlaps another */ - DEBUG_TRACE (DEBUG::Layering, string_compose ("Compute temporary layer for %1\n", (*i)->name())); - - /* current_overlaps: regions that overlap *i now */ - RegionList current_overlaps = cache.get ((*i)->bounds ()); - current_overlaps.remove (*i); + /* how many pieces to divide this playlist's time up into */ + int const divisions = 512; - DEBUG_TRACE (DEBUG::Layering, "Current overlaps:\n"); - for (RegionList::iterator j = current_overlaps.begin(); j != current_overlaps.end(); ++j) { - DEBUG_TRACE (DEBUG::Layering, string_compose ("\t%1\n", (*j)->name())); - } + /* find the start and end positions of the regions on this playlist */ + framepos_t start = INT64_MAX; + framepos_t end = 0; + for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) { + start = min (start, (*i)->position()); + end = max (end, (*i)->position() + (*i)->length()); + } + + /* hence the size of each time division */ + double const division_size = (end - start) / double (divisions); + + vector > layers; + layers.push_back (vector (divisions)); + + RegionList copy = regions.rlist(); + RegionList pending; + + /* Remove regions with pending relayers */ + for (RegionList::iterator i = copy.begin(); i != copy.end(); ) { + + RegionList::iterator j = i; + ++j; - /* overlaps_to_preserve: regions that overlap *i now, but which aren't being - worked on during this relayer: these will have their relationship with - *i preserved. - */ - RegionList overlaps_to_preserve; + if ((*i)->pending_layer()) { + pending.push_back (*i); + copy.erase (i); + } - /* overlaps_to_check: regions that overlap *i now, and must be checked to - see if *i is still on the correct layer with respect to them (according - to current layering rules). - */ - RegionList overlaps_to_check; + i = j; + } - if (_session.config.get_relayer_on_all_edits () || (*i)->last_layer_op (LayerOpAdd) > (*i)->last_layer_op (LayerOpBoundsChange)) { - /* We're configured to relayer on all edits, or this region has had - no edit since it was added to the playlist, so we're relayering - the whole lot; in this case there are no `overlaps_to_preserve'. - */ - overlaps_to_check = current_overlaps; - } else { - /* We're only relayering new overlaps; find them */ - RegionList last_overlaps = cache.get ((*i)->last_relayer_bounds ()); - last_overlaps.remove (*i); - for (RegionList::const_iterator j = current_overlaps.begin(); j != current_overlaps.end(); ++j) { - if (find (last_overlaps.begin(), last_overlaps.end(), *j) == last_overlaps.end()) { - /* This is a new overlap, which must be checked */ - overlaps_to_check.push_back (*j); - } else { - /* This is an existing overlap, which must be preserved */ - overlaps_to_preserve.push_back (*j); - } + /* Sort the remainder */ + copy.sort (RelayerSort ()); + + /* Re-insert the pending layers in the right places */ + for (RegionList::iterator i = pending.begin(); i != pending.end(); ++i) { + RegionList::iterator j = copy.begin(); + while (j != copy.end ()) { + if ((*j)->pending_layer().get_value_or ((*j)->layer ()) > (*i)->pending_layer().get ()) { + break; } - } - - if (overlaps_to_check.empty ()) { - /* There are no overlaps to check, so just leave everything as it is */ - continue; - } - - DEBUG_TRACE (DEBUG::Layering, "Overlaps to check:\n"); - for (RegionList::iterator j = overlaps_to_check.begin(); j != overlaps_to_check.end(); ++j) { - DEBUG_TRACE (DEBUG::Layering, string_compose ("\t%1\n", (*j)->name())); - } - - /* Put *i on our overlaps_to_check_list */ - overlaps_to_check.push_back (*i); - - /* And sort it according to the current layer model */ - switch (_session.config.get_layer_model()) { - case LaterHigher: - overlaps_to_check.sort (RegionSortByPosition ()); - break; - case AddOrBoundsChangeHigher: - overlaps_to_check.sort (RegionSortByAddOrBounds ()); - break; - case AddHigher: - overlaps_to_check.sort (RegionSortByAdd ()); - break; - } - - /* Now find *i in our overlaps_to_check list; within this list it will be in the - right place wrt the current layering rules, so we can work out the layers of the - nearest regions below and above. - */ - double previous_layer = -DBL_MAX; - double next_layer = DBL_MAX; - RegionList::const_iterator j = overlaps_to_check.begin(); - while (*j != *i) { - previous_layer = temporary_layers.get (*j); ++j; } + copy.insert (j, *i); + } - /* we must have found *i */ - assert (j != overlaps_to_check.end ()); + bool had_pending = false; - ++j; - if (j != overlaps_to_check.end ()) { - next_layer = temporary_layers.get (*j); + for (RegionList::iterator i = copy.begin(); i != copy.end(); ++i) { + + /* reset the pending layer for every region now that we're relayering */ + if ((*i)->reset_pending_layer ()) { + had_pending = true; } - if (next_layer < previous_layer) { - /* If this happens, it means that it's impossible to put *i between overlaps_to_check - in a way that satisfies the current layering rule. So we'll punt and put *i - above previous_layer. - */ - next_layer = DBL_MAX; - } - - /* Now we know where *i and overlaps_to_preserve should go: between previous_layer and - next_layer. + /* find the time divisions that this region covers; if there are no regions on the list, + division_size will equal 0 and in this case we'll just say that + start_division = end_division = 0. */ + int start_division = 0; + int end_division = 0; - DEBUG_TRACE (DEBUG::Layering, string_compose ("%1 and deps need to go between %2 and %3\n", (*i)->name(), previous_layer, next_layer)); + if (division_size > 0) { + start_division = floor ( ((*i)->position() - start) / division_size); + end_division = floor ( ((*i)->position() + (*i)->length() - start) / division_size ); + if (end_division == divisions) { + end_division--; + } + } - /* Recurse into overlaps_to_preserve to find dependents */ - RegionList recursed_overlaps_to_preserve; - - for (RegionList::const_iterator k = overlaps_to_preserve.begin(); k != overlaps_to_preserve.end(); ++k) { - recursed_overlaps_to_preserve.push_back (*k); - RegionList touched = recursive_regions_touched (*k, cache, *i); - for (RegionList::iterator m = touched.begin(); m != touched.end(); ++m) { - if (find (recursed_overlaps_to_preserve.begin(), recursed_overlaps_to_preserve.end(), *m) == recursed_overlaps_to_preserve.end()) { - recursed_overlaps_to_preserve.push_back (*m); + assert (divisions == 0 || end_division < divisions); + + /* find the lowest layer that this region can go on */ + size_t j = layers.size(); + while (j > 0) { + /* try layer j - 1; it can go on if it overlaps no other region + that is already on that layer + */ + + bool overlap = false; + for (int k = start_division; k <= end_division; ++k) { + RegionList::iterator l = layers[j-1][k].begin (); + while (l != layers[j-1][k].end()) { + if ((*l)->overlap_equivalent (*i)) { + overlap = true; + break; + } + l++; + } + + if (overlap) { + break; } } - } - /* Put *i into the overlaps_to_preserve list */ - recursed_overlaps_to_preserve.push_back (*i); - - /* Sort it by layer, so that we preserve layering */ - recursed_overlaps_to_preserve.sort (SortByTemporaryLayer (temporary_layers)); - - /* Divide available space up into chunks so that we can relayer everything in that space */ - double const space = (next_layer - previous_layer) / (recursed_overlaps_to_preserve.size() + 1); - - /* And relayer */ - int m = 1; - for (RegionList::const_iterator k = recursed_overlaps_to_preserve.begin(); k != recursed_overlaps_to_preserve.end(); ++k) { - temporary_layers.set (*k, previous_layer + space * m); - ++m; - } - } - - return temporary_layers; -} - -/** Take a list of temporary layer indices and set up the layers of all regions - * based on them. - */ -void -Playlist::commit_temporary_layers (TemporaryLayers const & temporary_layers) -{ - /* Sort all the playlist's regions by layer, ascending */ - RegionList all_regions = regions.rlist (); - all_regions.sort (SortByTemporaryLayer (temporary_layers)); - - DEBUG_TRACE (DEBUG::Layering, "Commit layering:\n"); - - for (RegionList::iterator i = all_regions.begin(); i != all_regions.end(); ++i) { - - /* Go through the regions that we have already layered and hence work - out the maximum layer index that is in used at some point during - region *i. - */ - - layer_t max_layer_here = 0; - bool have_overlap = false; - for (RegionList::iterator j = all_regions.begin(); j != i; ++j) { - if ((*j)->overlap_equivalent (*i)) { - max_layer_here = max ((*j)->layer (), max_layer_here); - have_overlap = true; + if (overlap) { + /* overlap, so we must use layer j */ + break; } + + --j; } - if (have_overlap) { - /* *i overlaps something, so put it on the next available layer */ - (*i)->set_layer (max_layer_here + 1); - } else { - /* no overlap, so put on the bottom layer */ - (*i)->set_layer (0); + if (j == layers.size()) { + /* we need a new layer for this region */ + layers.push_back (vector (divisions)); } - - DEBUG_TRACE (DEBUG::Layering, string_compose ("\t%1 temporary %2 committed %3\n", (*i)->name(), temporary_layers.get (*i), (*i)->layer())); - } -} -/** Relayer a list of regions. - * - * Taking each region R in turn, this method examines the regions O that overlap R in time. - * If the session configuration option "relayer-on-all-moves" is false, we reduce O so that - * it contains only those regions with which new overlaps have been formed since the last - * relayer. - * - * We then change the layer of R and its indirect overlaps so that R meets the current - * Session layer model with respect to O. See doc/layering. - */ + /* put a reference to this region in each of the divisions that it exists in */ + for (int k = start_division; k <= end_division; ++k) { + layers[j][k].push_back (*i); + } -void -Playlist::relayer (RegionList const & relayer_regions) -{ - if (_relayer_suspended) { - return; + if ((*i)->layer() != j) { + changed = true; + } + + (*i)->set_layer (j); } - /* We do this in two parts: first; compute `temporary layer' indices for - regions on the playlist. These are (possibly) fractional indices, which - are a convenient means of working with things when you want to insert layers - between others. - */ - - TemporaryLayers temporary_layers = compute_temporary_layers (relayer_regions); - - /* Second, we fix up these temporary layers and `commit' them by writing - them to the regions involved. - */ - - commit_temporary_layers (temporary_layers); -} - -/** Put a region on some fractional layer and sort everything else out around it. - * This can be used to force a region into some layering; for example, calling - * this method with temporary_layer == -0.5 will put the region at the bottom of - * the stack. - */ - -void -Playlist::relayer (boost::shared_ptr region, double temporary_layer) -{ - if (_relayer_suspended) { - return; + if (changed) { + notify_layering_changed (); } - TemporaryLayers t; - t.set (region, temporary_layer); - commit_temporary_layers (t); + if (had_pending) { + uint64_t i = 0; + for (RegionList::iterator j = copy.begin(); j != copy.end(); ++j) { + (*j)->set_layering_index (i++); + } + } } void Playlist::raise_region (boost::shared_ptr region) { - relayer (region, region->layer() + 1.5); + region->set_pending_layer (region->layer() + 1.5); + relayer (); } void Playlist::lower_region (boost::shared_ptr region) { - relayer (region, region->layer() - 1.5); + region->set_pending_layer (region->layer() - 1.5); + relayer (); } void Playlist::raise_region_to_top (boost::shared_ptr region) { - relayer (region, max_layer); + region->set_pending_layer (DBL_MAX); + relayer (); } void Playlist::lower_region_to_bottom (boost::shared_ptr region) { - relayer (region, -0.5); + region->set_pending_layer (-0.5); + relayer (); } void @@ -2750,24 +2651,9 @@ Playlist::set_frozen (bool yn) _frozen = yn; } -void -Playlist::timestamp_layer_op (LayerOp op, boost::shared_ptr region) -{ - region->set_last_layer_op (op, ++layer_op_counter); -} - - -/** Find the next or previous region after `region' (next if dir > 0, previous otherwise) - * and swap its position with `region'. - */ void Playlist::shuffle (boost::shared_ptr region, int dir) { - /* As regards layering, the calls we make to set_position() will - perform layering as if the regions had been moved, which I think - is about right. - */ - bool moved = false; if (region->locked()) { @@ -2872,9 +2758,13 @@ Playlist::shuffle (boost::shared_ptr region, int dir) _shuffling = false; if (moved) { + + relayer (); check_dependents (region, false); + notify_contents_changed(); } + } bool @@ -3293,145 +3183,16 @@ Playlist::set_orig_track_id (const PBD::ID& id) _orig_track_id = id; } -/** Set the temporary layer for a region */ -void -Playlist::TemporaryLayers::set (boost::shared_ptr r, double l) +uint64_t +Playlist::highest_layering_index () const { - _map[r] = l; -} + RegionLock rlock (const_cast (this)); -/** Return the temporary layer for a region, if one has been specified - * to this TemporaryLayers object; if not return the region's current - * layer. - */ -double -Playlist::TemporaryLayers::get (boost::shared_ptr r) const -{ - Map::const_iterator i = _map.find (r); - if (i != _map.end ()) { - return i->second; + uint64_t h = 0; + for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) { + h = max (h, (*i)->layering_index ()); } - return double (r->layer ()); -} - -int const Playlist::OverlapCache::_divisions = 512; - -/** Set up an OverlapCache for a playlist; the cache will only be valid until - * the Playlist is changed. - */ -Playlist::OverlapCache::OverlapCache (Playlist* playlist) - : _range (0, 0) -{ - /* Find the start and end positions of the regions on this playlist */ - _range = Evoral::Range (max_framepos, 0); - RegionList const & rl = playlist->region_list().rlist (); - for (RegionList::const_iterator i = rl.begin(); i != rl.end(); ++i) { - Evoral::Range const b = (*i)->bounds (); - _range.from = min (_range.from, b.from); - _range.to = max (_range.to, b.to); - } - - /* Hence the size of each time divison */ - _division_size = (_range.to - _range.from) / double (_divisions); - - _cache.resize (_divisions); - - /* Build the cache */ - for (RegionList::const_iterator i = rl.begin(); i != rl.end(); ++i) { - pair ind = cache_indices ((*i)->bounds ()); - for (int j = ind.first; j < ind.second; ++j) { - _cache[j].push_back (*i); - } - } -} - -/** @param range Range, in frames. - * @return From and to cache indices for (to is exclusive). - */ -pair -Playlist::OverlapCache::cache_indices (Evoral::Range range) const -{ - range.from = max (range.from, _range.from); - range.to = min (range.to, _range.to); - - pair const p = make_pair ( - floor ((range.from - _range.from) / _division_size), - ceil ((range.to - _range.from) / _division_size) - ); - - assert (p.first >= 0); - assert (p.second <= _divisions); - - return p; -} - -/** Return the regions that overlap a given range. The returned list - * is not guaranteed to be in the same order as the Playlist that it was - * generated from. - */ -Playlist::RegionList -Playlist::OverlapCache::get (Evoral::Range range) const -{ - if (_range.from == max_framepos) { - return RegionList (); - } - - RegionList r; - - pair ind = cache_indices (range); - for (int i = ind.first; i < ind.second; ++i) { - for (RegionList::const_iterator j = _cache[i].begin(); j != _cache[i].end(); ++j) { - if ((*j)->coverage (range.from, range.to) != OverlapNone) { - r.push_back (*j); - } - } - } - - r.sort (); - r.unique (); - - return r; -} - -void -Playlist::suspend_relayer () -{ - _relayer_suspended = true; -} - -void -Playlist::resume_relayer () -{ - _relayer_suspended = false; -} - -/** Examine a region and return regions which overlap it, and also those which overlap those which overlap etc. - * @param ignore Optional region which should be treated as if it doesn't exist (ie not returned in the list, - * and not recursed into). - */ -Playlist::RegionList -Playlist::recursive_regions_touched (boost::shared_ptr region, OverlapCache const & cache, boost::shared_ptr ignore) const -{ - RegionList touched; - recursive_regions_touched_sub (region, cache, ignore, touched); - - touched.remove (region); - return touched; -} - -/** Recursive sub-routine of recursive_regions_touched */ -void -Playlist::recursive_regions_touched_sub ( - boost::shared_ptr region, OverlapCache const & cache, boost::shared_ptr ignore, RegionList & touched - ) const -{ - RegionList r = cache.get (region->bounds ()); - for (RegionList::iterator i = r.begin(); i != r.end(); ++i) { - if (find (touched.begin(), touched.end(), *i) == touched.end() && *i != ignore) { - touched.push_back (*i); - recursive_regions_touched_sub (*i, cache, ignore, touched); - } - } + return h; } diff --git a/libs/ardour/region.cc b/libs/ardour/region.cc index 89abdbd230..0ec8ee001b 100644 --- a/libs/ardour/region.cc +++ b/libs/ardour/region.cc @@ -73,10 +73,7 @@ namespace ARDOUR { PBD::PropertyDescriptor stretch; PBD::PropertyDescriptor shift; PBD::PropertyDescriptor position_lock_style; - PBD::PropertyDescriptor last_relayer_bounds_from; - PBD::PropertyDescriptor last_relayer_bounds_to; - PBD::PropertyDescriptor last_layer_op_add; - PBD::PropertyDescriptor last_layer_op_bounds_change; + PBD::PropertyDescriptor layering_index; } } @@ -131,14 +128,8 @@ Region::make_property_quarks () DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for shift = %1\n", Properties::shift.property_id)); Properties::position_lock_style.property_id = g_quark_from_static_string (X_("positional-lock-style")); DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for position_lock_style = %1\n", Properties::position_lock_style.property_id)); - Properties::last_relayer_bounds_from.property_id = g_quark_from_static_string (X_("last-relayer-bounds-from")); - DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for last_relayer_bounds_from = %1\n", Properties::last_relayer_bounds_from.property_id)); - Properties::last_relayer_bounds_to.property_id = g_quark_from_static_string (X_("last-relayer-bounds-to")); - DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for last_relayer_bounds_to = %1\n", Properties::last_relayer_bounds_to.property_id)); - Properties::last_layer_op_add.property_id = g_quark_from_static_string (X_("last-layer-op-add")); - DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for last_layer_op_add = %1\n", Properties::last_layer_op_add.property_id)); - Properties::last_layer_op_bounds_change.property_id = g_quark_from_static_string (X_("last-layer-op-bounds-change")); - DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for last_layer_op_bounds_change = %1\n", Properties::last_layer_op_bounds_change.property_id)); + Properties::layering_index.property_id = g_quark_from_static_string (X_("layering-index")); + DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for layering_index = %1\n", Properties::layering_index.property_id)); } void @@ -169,10 +160,7 @@ Region::register_properties () add_property (_stretch); add_property (_shift); add_property (_position_lock_style); - add_property (_last_relayer_bounds_from); - add_property (_last_relayer_bounds_to); - add_property (_last_layer_op_add); - add_property (_last_layer_op_bounds_change); + add_property (_layering_index); } #define REGION_DEFAULT_STATE(s,l) \ @@ -199,10 +187,7 @@ Region::register_properties () , _stretch (Properties::stretch, 1.0) \ , _shift (Properties::shift, 1.0) \ , _position_lock_style (Properties::position_lock_style, _type == DataType::AUDIO ? AudioTime : MusicTime) \ - , _last_relayer_bounds_from (Properties::last_relayer_bounds_from, 0) \ - , _last_relayer_bounds_to (Properties::last_relayer_bounds_to, 0) \ - , _last_layer_op_add (Properties::last_layer_op_add, 0) \ - , _last_layer_op_bounds_change (Properties::last_layer_op_bounds_change, 0) + , _layering_index (Properties::layering_index, 0) #define REGION_COPY_STATE(other) \ _sync_marked (Properties::sync_marked, other->_sync_marked) \ @@ -228,10 +213,7 @@ Region::register_properties () , _stretch (Properties::stretch, other->_stretch) \ , _shift (Properties::shift, other->_shift) \ , _position_lock_style (Properties::position_lock_style, other->_position_lock_style) \ - , _last_relayer_bounds_from (Properties::last_relayer_bounds_from, other->_last_relayer_bounds_from) \ - , _last_relayer_bounds_to (Properties::last_relayer_bounds_to, other->_last_relayer_bounds_to) \ - , _last_layer_op_add (Properties::last_layer_op_add, other->_last_layer_op_add) \ - , _last_layer_op_bounds_change (Properties::last_layer_op_bounds_change, other->_last_layer_op_bounds_change) + , _layering_index (Properties::layering_index, other->_layering_index) /* derived-from-derived constructor (no sources in constructor) */ Region::Region (Session& s, framepos_t start, framecnt_t length, const string& name, DataType type) @@ -243,6 +225,7 @@ Region::Region (Session& s, framepos_t start, framecnt_t length, const string& n , _first_edit (EditChangesNothing) { register_properties (); + /* no sources at this point */ } @@ -1134,12 +1117,9 @@ Region::set_layer (layer_t l) { if (_layer != l) { _layer = l; + send_change (Properties::layer); } - - Evoral::Range const b = bounds (); - _last_relayer_bounds_from = b.from; - _last_relayer_bounds_to = b.to; } XMLNode& @@ -1330,19 +1310,6 @@ Region::send_change (const PropertyChange& what_changed) } } -void -Region::set_last_layer_op (LayerOp op, uint64_t when) -{ - switch (op) { - case LayerOpAdd: - _last_layer_op_add = when; - break; - case LayerOpBoundsChange: - _last_layer_op_bounds_change = when; - break; - } -} - bool Region::overlap_equivalent (boost::shared_ptr other) const { @@ -1683,24 +1650,22 @@ Region::post_set (const PropertyChange& pc) } } -uint64_t -Region::last_layer_op (LayerOp op) const +void +Region::set_pending_layer (double l) { - switch (op) { - case LayerOpAdd: - return _last_layer_op_add; - case LayerOpBoundsChange: - return _last_layer_op_bounds_change; - } - - /* NOTREACHED */ - return 0; + _pending_layer = l; } -Evoral::Range -Region::bounds () const +bool +Region::reset_pending_layer () { - return Evoral::Range (_position, _position + _length); + bool const had = _pending_layer; + _pending_layer = boost::optional (); + return had; } - +boost::optional +Region::pending_layer () const +{ + return _pending_layer; +} diff --git a/libs/ardour/run-tests.sh b/libs/ardour/run-tests.sh index 76fc4f8aea..379678c968 100755 --- a/libs/ardour/run-tests.sh +++ b/libs/ardour/run-tests.sh @@ -19,8 +19,6 @@ export ARDOUR_PANNER_PATH=$libs/panners/2in2out:$libs/panners/1in2out:$libs/pann if [ "$1" == "--debug" ]; then gdb ./libs/ardour/run-tests -elif [ "$1" == "--valgrind" ]; then - valgrind --tool="memcheck" ./libs/ardour/run-tests else ./libs/ardour/run-tests fi diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 532d010680..7d6f44b786 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -974,27 +974,7 @@ int Session::load_options (const XMLNode& node) { LocaleGuard lg (X_("POSIX")); - - /* Copy the node */ - XMLNode node_copy = node; - - /* XXX: evil hack: fix up sessions from before the layering alterations - (during A3 beta) - */ - - XMLNodeList children = node_copy.children (); - for (XMLNodeIterator i = children.begin(); i != children.end(); ++i) { - XMLProperty* p = (*i)->property (X_("name")); - if (p && p->name() == X_("name") && p->value() == X_("layer-model") ) { - p = (*i)->property (X_("value")); - if (p && p->value() == X_("MoveAddHigher")) { - (*i)->add_property (X_("value"), X_("AddOrBoundsChangeHigher")); - } - } - } - - config.set_variables (node_copy); - + config.set_variables (node); return 0; } diff --git a/libs/ardour/test/playlist_layering_test.cc b/libs/ardour/test/playlist_layering_test.cc index 23b22f9d90..fd0b6bc09c 100644 --- a/libs/ardour/test/playlist_layering_test.cc +++ b/libs/ardour/test/playlist_layering_test.cc @@ -1,14 +1,13 @@ -#include "pbd/compose.h" #include "midi++/manager.h" +#include "pbd/textreceiver.h" +#include "pbd/compose.h" +#include "ardour/session.h" +#include "ardour/audioengine.h" #include "ardour/playlist_factory.h" #include "ardour/source_factory.h" #include "ardour/region.h" #include "ardour/region_factory.h" -#include "ardour/session.h" -#include "ardour/audiosource.h" -#include "ardour/audioengine.h" #include "playlist_layering_test.h" -#include "test_receiver.h" CPPUNIT_TEST_SUITE_REGISTRATION (PlaylistLayeringTest); @@ -16,28 +15,68 @@ using namespace std; using namespace ARDOUR; using namespace PBD; -int const PlaylistLayeringTest::num_regions = 6; +class TestReceiver : public Receiver +{ +protected: + void receive (Transmitter::Channel chn, const char * str) { + const char *prefix = ""; + + switch (chn) { + case Transmitter::Error: + prefix = ": [ERROR]: "; + break; + case Transmitter::Info: + /* ignore */ + return; + case Transmitter::Warning: + prefix = ": [WARNING]: "; + break; + case Transmitter::Fatal: + prefix = ": [FATAL]: "; + break; + case Transmitter::Throw: + /* this isn't supposed to happen */ + abort (); + } + + /* note: iostreams are already thread-safe: no external + lock required. + */ + + cout << prefix << str << endl; + + if (chn == Transmitter::Fatal) { + exit (9); + } + } +}; + +TestReceiver test_receiver; void PlaylistLayeringTest::setUp () { - TestNeedingSession::setUp (); - string const test_wav_path = "libs/ardour/test/test.wav"; + string const test_session_path = "libs/ardour/test/playlist_layering_test"; + string const test_wav_path = "libs/ardour/test/playlist_layering_test/playlist_layering_test.wav"; + system (string_compose ("rm -rf %1", test_session_path).c_str()); + init (false, true); + SessionEvent::create_per_thread_pool ("test", 512); + + test_receiver.listen_to (error); + test_receiver.listen_to (info); + test_receiver.listen_to (fatal); + test_receiver.listen_to (warning); + + AudioEngine* engine = new AudioEngine ("test", ""); + MIDI::Manager::create (engine->jack ()); + CPPUNIT_ASSERT (engine->start () == 0); + + _session = new Session (*engine, test_session_path, "playlist_layering_test"); + engine->set_session (_session); + _playlist = PlaylistFactory::create (DataType::AUDIO, *_session, "test"); _source = SourceFactory::createWritable (DataType::AUDIO, *_session, test_wav_path, "", false, 44100); - - system ("pwd"); - - /* Must write some data to our source, otherwise regions which use it will - be limited in whether they can be trimmed or not. - */ - boost::shared_ptr a = boost::dynamic_pointer_cast (_source); - Sample silence[512]; - memset (silence, 0, 512 * sizeof (Sample)); - a->write (silence, 512); - - _region = new boost::shared_ptr[num_regions]; } void @@ -45,303 +84,45 @@ PlaylistLayeringTest::tearDown () { _playlist.reset (); _source.reset (); - for (int i = 0; i < num_regions; ++i) { + for (int i = 0; i < 16; ++i) { _region[i].reset (); } - delete[] _region; - - TestNeedingSession::tearDown (); + AudioEngine::instance()->remove_session (); + delete _session; + EnumWriter::destroy (); + MIDI::Manager::destroy (); + AudioEngine::destroy (); } void -PlaylistLayeringTest::create_short_regions () +PlaylistLayeringTest::create_three_short_regions () { PropertyList plist; plist.add (Properties::start, 0); plist.add (Properties::length, 100); - for (int i = 0; i < num_regions; ++i) { + for (int i = 0; i < 3; ++i) { _region[i] = RegionFactory::create (_source, plist); - _region[i]->set_name (string_compose ("%1", char (int ('A') + i))); } } void -PlaylistLayeringTest::laterHigher_relayerOnAll_Test () +PlaylistLayeringTest::basicsTest () { - _session->config.set_layer_model (LaterHigher); - _session->config.set_relayer_on_all_edits (true); - - create_short_regions (); + create_three_short_regions (); - /* three overlapping regions */ - _playlist->add_region (_region[A], 0); - _playlist->add_region (_region[B], 10); - _playlist->add_region (_region[C], 20); - /* and another non-overlapping one */ - _playlist->add_region (_region[D], 200); + _playlist->add_region (_region[0], 0); + _playlist->add_region (_region[1], 10); + _playlist->add_region (_region[2], 20); - /* LaterHigher means that they should be arranged thus */ - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[0]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[1]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[2]->layer ()); - _region[A]->set_position (5); + _region[0]->set_position (5); - /* Region move should have no effect in LaterHigher mode */ - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* C -> bottom should give C A B, not touching D */ - _region[C]->lower_to_bottom (); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* C -> top should go back to A B C, not touching D */ - _region[C]->raise_to_top (); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); -} - -void -PlaylistLayeringTest::addHigher_relayerOnAll_Test () -{ - _session->config.set_layer_model (AddHigher); - _session->config.set_relayer_on_all_edits (true); - - create_short_regions (); - - /* three overlapping regions */ - _playlist->add_region (_region[A], 0); - _playlist->add_region (_region[B], 10); - _playlist->add_region (_region[C], 20); - /* and another non-overlapping one */ - _playlist->add_region (_region[D], 200); - - /* AddHigher means that they should be arranged thus */ - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - _region[A]->set_position (5); - - /* region move should have no effect in AddHigher mode */ - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* C -> bottom should give C A B, not touching D */ - _region[C]->lower_to_bottom (); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* C -> top should go back to A B C, not touching D */ - _region[C]->raise_to_top (); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); -} - -void -PlaylistLayeringTest::addOrBoundsHigher_relayerOnAll_Test () -{ - _session->config.set_layer_model (AddOrBoundsChangeHigher); - _session->config.set_relayer_on_all_edits (true); - - create_short_regions (); - - /* three overlapping regions */ - _playlist->add_region (_region[A], 0); - _playlist->add_region (_region[B], 10); - _playlist->add_region (_region[C], 20); - /* and another non-overlapping one */ - _playlist->add_region (_region[D], 200); - - /* AddOrBoundsHigher means that they should be arranged thus */ - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* region move should put A on top for B C A, not touching D */ - _region[A]->set_position (5); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* C -> bottom should give C B A, not touching D */ - _region[C]->lower_to_bottom (); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* C -> top should go back to B A C, not touching D */ - _region[C]->raise_to_top (); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* Put C on the bottom */ - _region[C]->lower_to_bottom (); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* Now move it slightly, and it should go back to the top again */ - _region[C]->set_position (21); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* Put C back on the bottom */ - _region[C]->lower_to_bottom (); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* Trim it slightly, and it should go back to the top again */ - _region[C]->trim_front (23); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* Same with the end */ - _region[C]->lower_to_bottom (); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - _region[C]->trim_end (118); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); -} - -void -PlaylistLayeringTest::addOrBoundsHigher_relayerWhenNecessary_Test () -{ - _session->config.set_layer_model (AddOrBoundsChangeHigher); - _session->config.set_relayer_on_all_edits (false); - - create_short_regions (); - - /* three overlapping regions */ - _playlist->add_region (_region[A], 0); - _playlist->add_region (_region[B], 10); - _playlist->add_region (_region[C], 20); - /* and another non-overlapping one */ - _playlist->add_region (_region[D], 200); - - /* AddOrBoundsHigher means that they should be arranged thus */ - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - _region[A]->set_position (5); - - /* region move should not have changed anything, since in - this mode we only relayer when there is a new overlap - */ - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* C -> bottom should give C A B, not touching D */ - _region[C]->lower_to_bottom (); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* C -> top should go back to A B C, not touching D */ - _region[C]->raise_to_top (); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* Put C on the bottom */ - _region[C]->lower_to_bottom (); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - /* Now move it slightly, and it should stay where it is */ - _region[C]->set_position (21); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); -} - -void -PlaylistLayeringTest::lastLayerOpTest () -{ - create_short_regions (); - - _playlist->add_region (_region[A], 0); - CPPUNIT_ASSERT_EQUAL (_playlist->layer_op_counter, _region[A]->last_layer_op (LayerOpAdd)); - uint64_t const last_add = _region[A]->last_layer_op (LayerOpAdd); - - _region[A]->set_position (42); - CPPUNIT_ASSERT_EQUAL (_playlist->layer_op_counter, _region[A]->last_layer_op (LayerOpBoundsChange)); - CPPUNIT_ASSERT_EQUAL (last_add, _region[A]->last_layer_op (LayerOpAdd)); - - _region[A]->trim_front (46); - CPPUNIT_ASSERT_EQUAL (_playlist->layer_op_counter, _region[A]->last_layer_op (LayerOpBoundsChange)); - CPPUNIT_ASSERT_EQUAL (last_add, _region[A]->last_layer_op (LayerOpAdd)); - - _region[A]->trim_end (102); - CPPUNIT_ASSERT_EQUAL (_playlist->layer_op_counter, _region[A]->last_layer_op (LayerOpBoundsChange)); - CPPUNIT_ASSERT_EQUAL (last_add, _region[A]->last_layer_op (LayerOpAdd)); -} - -void -PlaylistLayeringTest::recursiveRelayerTest () -{ - _session->config.set_layer_model (AddOrBoundsChangeHigher); - _session->config.set_relayer_on_all_edits (false); - - create_short_regions (); - - _playlist->add_region (_region[A], 100); - _playlist->add_region (_region[B], 125); - _playlist->add_region (_region[C], 50); - _playlist->add_region (_region[D], 250); - - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - - _region[A]->set_position (200); - - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[B]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (3), _region[C]->layer ()); + /* region move should have no effect */ + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[0]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[1]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[2]->layer ()); } diff --git a/libs/ardour/test/playlist_layering_test.h b/libs/ardour/test/playlist_layering_test.h index ab5fe701b1..46285459ca 100644 --- a/libs/ardour/test/playlist_layering_test.h +++ b/libs/ardour/test/playlist_layering_test.h @@ -1,6 +1,5 @@ #include #include -#include "test_needing_session.h" namespace ARDOUR { class Session; @@ -8,42 +7,23 @@ namespace ARDOUR { class Source; } -class PlaylistLayeringTest : public TestNeedingSession +class PlaylistLayeringTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE (PlaylistLayeringTest); - CPPUNIT_TEST (lastLayerOpTest); - CPPUNIT_TEST (addHigher_relayerOnAll_Test); - CPPUNIT_TEST (addOrBoundsHigher_relayerOnAll_Test); - CPPUNIT_TEST (laterHigher_relayerOnAll_Test); - CPPUNIT_TEST (addOrBoundsHigher_relayerWhenNecessary_Test); - CPPUNIT_TEST (recursiveRelayerTest); + CPPUNIT_TEST (basicsTest); CPPUNIT_TEST_SUITE_END (); public: void setUp (); void tearDown (); - void lastLayerOpTest (); - void addHigher_relayerOnAll_Test (); - void addOrBoundsHigher_relayerOnAll_Test (); - void laterHigher_relayerOnAll_Test (); - void addOrBoundsHigher_relayerWhenNecessary_Test (); - void recursiveRelayerTest (); + void basicsTest (); private: - void create_short_regions (); - - static int const num_regions; - enum { - A = 0, - B, - C, - D, - E, - F - }; + void create_three_short_regions (); + ARDOUR::Session* _session; boost::shared_ptr _playlist; boost::shared_ptr _source; - boost::shared_ptr* _region; + boost::shared_ptr _region[16]; }; diff --git a/libs/ardour/test/playlist_overlap_cache_test.cc b/libs/ardour/test/playlist_overlap_cache_test.cc deleted file mode 100644 index 9263d80d3b..0000000000 --- a/libs/ardour/test/playlist_overlap_cache_test.cc +++ /dev/null @@ -1,119 +0,0 @@ -#include "pbd/compose.h" -#include "ardour/playlist.h" -#include "ardour/playlist_factory.h" -#include "ardour/source_factory.h" -#include "ardour/region.h" -#include "ardour/region_sorters.h" -#include "ardour/region_factory.h" -#include "playlist_overlap_cache_test.h" - -using namespace std; -using namespace PBD; -using namespace ARDOUR; - -CPPUNIT_TEST_SUITE_REGISTRATION (PlaylistOverlapCacheTest); - -void -PlaylistOverlapCacheTest::tearDown () -{ - _playlist.reset (); - _source.reset (); - - TestNeedingSession::tearDown (); -} - -void -PlaylistOverlapCacheTest::basicTest () -{ - string const test_wav_path = "libs/ardour/test/test.wav"; - - _playlist = PlaylistFactory::create (DataType::AUDIO, *_session, "test"); - _source = SourceFactory::createWritable (DataType::AUDIO, *_session, test_wav_path, "", false, 44100); - - PropertyList plist; - plist.add (Properties::length, 256); - - boost::shared_ptr regionA = RegionFactory::create (_source, plist); - regionA->set_name ("A"); - _playlist->add_region (regionA, 0); - - - { - Playlist::OverlapCache cache (_playlist.get ()); - Playlist::RegionList rl = cache.get (Evoral::Range (0, 256)); - CPPUNIT_ASSERT_EQUAL (size_t (1), rl.size ()); - CPPUNIT_ASSERT_EQUAL (regionA, rl.front ()); - - rl = cache.get (Evoral::Range (-1000, 1000)); - CPPUNIT_ASSERT_EQUAL (size_t (1), rl.size ()); - CPPUNIT_ASSERT_EQUAL (regionA, rl.front ()); - } - - boost::shared_ptr regionB = RegionFactory::create (_source, plist); - regionA->set_name ("B"); - _playlist->add_region (regionB, 53); - - { - Playlist::OverlapCache cache (_playlist.get ()); - Playlist::RegionList rl = cache.get (Evoral::Range (0, 256)); - CPPUNIT_ASSERT_EQUAL (size_t (2), rl.size ()); - rl.sort (RegionSortByPosition ()); - CPPUNIT_ASSERT_EQUAL (regionA, rl.front ()); - CPPUNIT_ASSERT_EQUAL (regionB, rl.back ()); - - rl = cache.get (Evoral::Range (260, 274)); - CPPUNIT_ASSERT_EQUAL (size_t (1), rl.size ()); - CPPUNIT_ASSERT_EQUAL (regionB, rl.front ()); - } -} - -void -PlaylistOverlapCacheTest::stressTest () -{ - string const test_wav_path = "libs/ardour/test/test.wav"; - - _playlist = PlaylistFactory::create (DataType::AUDIO, *_session, "test"); - _source = SourceFactory::createWritable (DataType::AUDIO, *_session, test_wav_path, "", false, 44100); - - srand (42); - - int const num_regions = rand () % 256; - - for (int i = 0; i < num_regions; ++i) { - PropertyList plist; - plist.add (Properties::length, rand () % 32768); - boost::shared_ptr r = RegionFactory::create (_source, plist); - r->set_name (string_compose ("%1", i)); - _playlist->add_region (r, rand() % 32768); - } - - Playlist::OverlapCache cache (_playlist.get ()); - - int const tests = rand () % 256; - - for (int i = 0; i < tests; ++i) { - framepos_t const start = rand () % 32768; - framepos_t const length = rand () % 32768; - framepos_t const end = start + length; - - Playlist::RegionList cached = cache.get (Evoral::Range (start, end)); - - Playlist::RegionList actual; - Playlist::RegionList regions = _playlist->region_list().rlist(); - for (Playlist::RegionList::iterator j = regions.begin(); j != regions.end(); ++j) { - if ((*j)->coverage (start, end) != OverlapNone) { - actual.push_back (*j); - } - } - - cached.sort (RegionSortByPosition ()); - actual.sort (RegionSortByPosition ()); - - CPPUNIT_ASSERT_EQUAL (actual.size (), cached.size ()); - Playlist::RegionList::iterator j = actual.begin (); - Playlist::RegionList::iterator k = cached.begin (); - for (; j != actual.end(); ++j, ++k) { - CPPUNIT_ASSERT_EQUAL (*j, *k); - } - } -} diff --git a/libs/ardour/test/playlist_overlap_cache_test.h b/libs/ardour/test/playlist_overlap_cache_test.h deleted file mode 100644 index c75d1691d9..0000000000 --- a/libs/ardour/test/playlist_overlap_cache_test.h +++ /dev/null @@ -1,20 +0,0 @@ -#include "test_needing_session.h" - -class PlaylistOverlapCacheTest : public TestNeedingSession -{ -public: - CPPUNIT_TEST_SUITE (PlaylistOverlapCacheTest); - CPPUNIT_TEST (basicTest); - CPPUNIT_TEST (stressTest); - CPPUNIT_TEST_SUITE_END (); - -public: - void tearDown (); - - void basicTest (); - void stressTest (); - -private: - boost::shared_ptr _playlist; - boost::shared_ptr _source; -}; diff --git a/libs/ardour/test/test_needing_session.cc b/libs/ardour/test/test_needing_session.cc deleted file mode 100644 index 5981553601..0000000000 --- a/libs/ardour/test/test_needing_session.cc +++ /dev/null @@ -1,48 +0,0 @@ -#include "midi++/manager.h" -#include "pbd/textreceiver.h" -#include "pbd/compose.h" -#include "pbd/enumwriter.h" -#include "ardour/session.h" -#include "ardour/audioengine.h" -#include "test_needing_session.h" -#include "test_receiver.h" - -using namespace std; -using namespace ARDOUR; -using namespace PBD; - -TestReceiver test_receiver; - -void -TestNeedingSession::setUp () -{ - string const test_session_path = "libs/ardour/test/test_session"; - system (string_compose ("rm -rf %1", test_session_path).c_str()); - - init (false, true); - SessionEvent::create_per_thread_pool ("test", 512); - - test_receiver.listen_to (error); - test_receiver.listen_to (info); - test_receiver.listen_to (fatal); - test_receiver.listen_to (warning); - - AudioEngine* engine = new AudioEngine ("test", ""); - MIDI::Manager::create (engine->jack ()); - CPPUNIT_ASSERT (engine->start () == 0); - - _session = new Session (*engine, test_session_path, "test_session"); - engine->set_session (_session); -} - -void -TestNeedingSession::tearDown () -{ - AudioEngine::instance()->remove_session (); - - delete _session; - - EnumWriter::destroy (); - MIDI::Manager::destroy (); - AudioEngine::destroy (); -} diff --git a/libs/ardour/test/test_needing_session.h b/libs/ardour/test/test_needing_session.h deleted file mode 100644 index 3839855179..0000000000 --- a/libs/ardour/test/test_needing_session.h +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include - -namespace ARDOUR { - class Session; -} - -class TestNeedingSession : public CppUnit::TestFixture -{ -public: - void setUp (); - void tearDown (); - -protected: - ARDOUR::Session* _session; -}; diff --git a/libs/ardour/test/test_receiver.h b/libs/ardour/test/test_receiver.h deleted file mode 100644 index 537bac3cff..0000000000 --- a/libs/ardour/test/test_receiver.h +++ /dev/null @@ -1,37 +0,0 @@ -#include "pbd/receiver.h" - -class TestReceiver : public Receiver -{ -protected: - void receive (Transmitter::Channel chn, const char * str) { - const char *prefix = ""; - - switch (chn) { - case Transmitter::Error: - prefix = ": [ERROR]: "; - break; - case Transmitter::Info: - /* ignore */ - return; - case Transmitter::Warning: - prefix = ": [WARNING]: "; - break; - case Transmitter::Fatal: - prefix = ": [FATAL]: "; - break; - case Transmitter::Throw: - /* this isn't supposed to happen */ - abort (); - } - - /* note: iostreams are already thread-safe: no external - lock required. - */ - - std::cout << prefix << str << std::endl; - - if (chn == Transmitter::Fatal) { - exit (9); - } - } -}; diff --git a/libs/ardour/wscript b/libs/ardour/wscript index e49be33b8c..42c72784f9 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -431,8 +431,6 @@ def build(bld): test/framepos_plus_beats_test.cc test/framepos_minus_beats_test.cc test/playlist_layering_test.cc - test/playlist_overlap_cache_test.cc - test/test_needing_session.cc test/testrunner.cc '''.split() diff --git a/manual/xml/working_with_layers.xml b/manual/xml/working_with_layers.xml index 7027064079..bd843ca305 100644 --- a/manual/xml/working_with_layers.xml +++ b/manual/xml/working_with_layers.xml @@ -21,8 +21,8 @@ - Of course, nothing in digital audio is ever quite that simple, and so - there are some complications: + Of course, nothing in digital audio is ever quite that simple, and so of + course there are some complications:
@@ -38,14 +38,14 @@
Region Opacity - With a nod to image manipulation programs, Ardour allows you to + In a perverse nod to image manipulation programs, Ardour allows you to make regions transparent. By default, all regions are created opaque, which means that when they are playing, no region below them are audible. However, if you change the region to be transparent, the region will be audible together with any regions below it. This - capability should probably not be abused; if you really want to mix - sounds together in this way, they should probably be on their own - tracks. Occasionally though, this can be a useful trick. + capability should probably not be abused - if you really want to mix + sounds together in this way, they should probably live in their own + tracks. Occasionally though, this can be useful trick. @@ -55,74 +55,70 @@
-
- Choice of layering +
+ Layering Styles + + When you are recording new material for a track, its typical to want + to new material recorded "over" existing material in the track to be + what you hear on playback. For example, if you overdub part of a + guitar solo, you normally want the overdub to be audible, not hidden + by the old version that was already there. By contrast, when editing + using splitting/trimming/moving of regions to create a particular + arrangement along the timeline, many people find that they want + regions that start later on the timeline to be the ones that are + audible. + - There are two main decisions to be made with regard to how a playlist - should be layered: + To facilitate these two contradictory desires, Ardour features three + different styles for assigning regions to layers. + - Given overlapping regions, which order should they be layered in? + Most recently added regions are higher + + + Use this style when recording/overdubbing new material. Edits of + any kind do not modify the layering. + + + - When should layering be changed? + Most recently added/moved/trimmed regions are higher + + + Use this style when recording/overdubbing new material, but you + want basic edits to cause regions to rise to the top. + + + + + + Later regions are higher + + + Use this style when rearranging and editing regions. + + -
- Layering Order - - Ardour provides three-and-a-half ways to decide on the order in which regions are layered. The most basic choice is: - - - - - - Most recently added regions are higher - - - Regions which are later in time will be on higher layers. - - - - - - Most recently added or edited regions are higher - - - Regions which were more recently edited or added to the playlist - will be on higher layers. - - - - - - Later regions are higher - - - Regions which were more recently added to the playlist will be on higher - layers. - - - - -
- - A new session has the layering style set to "Most recently edited or - added regions are higher". To change the layering style, open the - Session Properties dialogue and choose your layering - style from the "Misc" page. Changing the layering style only affects - future edits to the playlist; the existing layering of all playlists is - preserved when changing the layering mode. + A new session has the layering style set to "Most recently + added/moved/trimmed regions are higher". To change the layering style, + open the options editor and select the + "Layers&Fades" page. There is an option there to select the style + you want. Layering style may be changed at any time. The existing + layering of all playlists is not changed when changing the layering + model.
- Modifying Layering Explicitly + Modifying Layering By Hand If you want a particular region to be the uppermost when the current layering style has put it on a lower layer, context click on the