Adding Editing Operations
This document provides a hand-holding guide through the process of adding an editing operation to Ardour. It uses the "fade-range" operation as an example.
Understanding the target
Decide what the target for the operation is. The answer is typically one or more regions, but this doesn't address some important question:
- How is the target defined? Region selection? Range selection? Intersection with an edit point?
- What type of derived class? Is this an audio editing operation? Does it apply to all types of data?
Of course, at present, the selection is a GUI concept, which means that these iterations often have to start with GUI level objects (e.g. TimeAxisView rather than Track).
Find a good example
Before beginning work on code, think about a similar operation that already exists. Similar should cover the target of the operation and how many time points it requires (e.g. a split requires a single position in time, whereas separate requires two). Find the code for this operation and follow it into libardour to get some sense for how it works.
A good way to start this is to find the text for the name of the
operation as it appears in a menu, then grep in
gtk2_ardour/editor_actions.cc
for that text. You will find
something like:
reg_sens (editor_actions, "editor-fade-range", _("Fade Range Selection"), sigc::mem_fun(*this, &Editor::fade_range));The final argument tells you that the method implementing the operation is
Editor::fade_range
.
Three stages
You don't have to do things in this order, but it is often a convenient approach.
1: Skeleton in the GUI
Add the basic method to Editor, complete with undo/redo framework. Comment out the imagined call to libardour. Here is what things looked like when I started working on fade range, after I added
void Editor::fade_range () { TrackViewList ts = selection->tracks.filter_to_unique_playlists (); begin_reversible_command (_("fade range")); for (TrackViewList::iterator i = ts.begin(); i != ts.end(); ++i) { (*i)->fade_range (selection->time); } commit_reversible_command (); }
This was largely copy-and-pasted from some similar operation, in
this case Editor::cut_copy_ranges
.
There is a complication in the first line: when implementing operations that alter regions or playlists, we have to take into account that tracks can share playlists. We don't want to apply the operation twice, so we first filter the track selection to include a set of tracks that represent each affected playlist just once.
As you can see, we're iterating over a set of track views
(TimeAxisView
). So we need to add code there as
well. In general, we don't want to be doing type-testing all over,
so we make the method in TimeAxisView
an empty
virtual method. This way, just the right kind
of TimeAxisView
(in this case, RouteTimeAxisView
rather than, say, AutomationTimeAxisView) can implement something
real:
virtual void fade_range (TimeSelection&) {}
Finally, we create a stub/skeleton in RouteTimeAxisView
where
the action will really happen. It might look something like
this:
void RouteTimeAxisView::fade_range (TimeSelection& selection) { boost::shared_ptrWe have commented out the actual "action" call to
Playlist::fade_range()
because we haven't defined that
yet. But this function contains the rest of the infrastructure
needed to make undo/redo work, and to check that we have a Track
rather than a Bus to work with (remember, Busses don't have
playlists, so they don't have regions, which means they are not
targets for this operation).
Now try a compile to make sure that this all fits together correctly.
2: Implement required infrastructure in libardour
Once we got the skeleton in RouteTimeAxisView
implemented, we
can see that we need a new method for ARDOUR::Playlist
(which is
where almost all editing operations can be found). This one is
very easy to create:
void Playlist::fade_range (list<AudioRange>& ranges) { for (list<AudioRange>::iterator r = ranges.begin(); r != ranges.end(); ++r) { for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) { (*i)->fade_range ((*r).start, (*r).end); } } }The code is simple: for each part of the range selection (remember: in Ardour, range selections can be discontiguous), iterate over all regions and call
Region::fade_range()
on each
one.
One complication that we could have considered was whether this is
an audio-only operation, in which case it might have been better
to add it to AudioPlaylist
. However in this specific case, it was
possible to imagine some potential use for applying a "fade" to
MIDI data, so we added it to Playlist
.
Now we know we also need Region::fade_range()
, so
we add that next. But first we note that whatever the operation
does, it is likely to be different depending on the type of data
involved. We don't have anything useful to do with MIDI data and
"fades" at present, so we make it an empty virtual method in Region:
virtual void fade_range (framepos_t, framepos_t) {}Our "real" implementation in AudioRegion:
void AudioRegion::fade_range (framepos_t start, framepos_t end) { framepos_t s, e; switch (coverage (start, end)) { case Evoral::OverlapStart: s = _position; e = end; set_fade_in (FadeConstantPower, e - s); break; case Evoral::OverlapEnd: s = start; e = _position + _length; set_fade_out (FadeConstantPower, e - s); break; default: return; } }The important thing to notice in the above code is not the actual logic, but there are already methods existing to modify every aspect of a Region's properties. The goal in new editing operations is typically to provide new ways to manipulate those properties, but in most cases, once you get into
Region
or AudioRegion
, you will be mostly just calling
existing methods to modify the region's properties. In this case
that means set_fade_in()
and set_fade_out()
.
3: Finalize GUI aspects
- Uncomment calls into libardour
- Compile, test and debug till it works.
- Consider adding a default key binding (currently in
mnemonic-us.bindings.in
) - Write documentation for the manual
Questions
Find people who know on the Ardour IRC channel and ask a question. Be patient and wait for an answer, especially if you're asking during the night time for the eastern USA timezones.