13
0
livetrax/gtk2_ardour/export_channel_selector.cc
Colin Fletcher 54caccf159 Don't select any routes for stem export by default
Now that there are 'select all tracks/busses' buttons, it makes sense to me
that stem export should start off with no routes selected, rather than all.

Maybe stem export should by default select routes that are selected in the
editor, but that's a task for another time.
2015-06-08 14:42:24 +01:00

720 lines
22 KiB
C++

/*
Copyright (C) 2008 Paul Davis
Author: Sakari Bergen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "export_channel_selector.h"
#include <algorithm>
#include "pbd/convert.h"
#include "ardour/audio_track.h"
#include "ardour/audioregion.h"
#include "ardour/export_channel_configuration.h"
#include "ardour/io.h"
#include "ardour/route.h"
#include "ardour/session.h"
#include <sstream>
#include "i18n.h"
using namespace std;
using namespace Glib;
using namespace ARDOUR;
using namespace PBD;
PortExportChannelSelector::PortExportChannelSelector (ARDOUR::Session * session, ProfileManagerPtr manager) :
ExportChannelSelector (session, manager),
channels_label (_("Channels:"), Gtk::ALIGN_LEFT),
split_checkbox (_("Split to mono files")),
max_channels (20),
channel_view (max_channels)
{
channels_hbox.pack_start (channels_label, false, false, 0);
channels_hbox.pack_end (channels_spinbutton, false, false, 0);
channels_vbox.pack_start (channels_hbox, false, false, 0);
channels_vbox.pack_start (split_checkbox, false, false, 6);
channel_alignment.add (channel_scroller);
channel_alignment.set_padding (0, 0, 12, 0);
channel_scroller.add (channel_view);
channel_scroller.set_size_request (-1, 130);
channel_scroller.set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
pack_start (channels_vbox, false, false, 0);
pack_start (channel_alignment, true, true, 0);
/* Channels spinbutton */
channels_spinbutton.set_digits (0);
channels_spinbutton.set_increments (1, 2);
channels_spinbutton.set_range (1, max_channels);
channels_spinbutton.set_value (2);
channels_spinbutton.signal_value_changed().connect (sigc::mem_fun (*this, &PortExportChannelSelector::update_channel_count));
/* Other signals */
split_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &PortExportChannelSelector::update_split_state));
channel_view.CriticalSelectionChanged.connect (CriticalSelectionChanged.make_slot());
/* Finalize */
sync_with_manager();
show_all_children ();
}
PortExportChannelSelector::~PortExportChannelSelector ()
{
// if (session) {
// session->add_instant_xml (get_state(), false);
// }
}
void
PortExportChannelSelector::sync_with_manager ()
{
state = manager->get_channel_configs().front();
split_checkbox.set_active (state->config->get_split());
channels_spinbutton.set_value (state->config->get_n_chans());
fill_route_list ();
channel_view.set_config (state->config);
}
void
PortExportChannelSelector::fill_route_list ()
{
channel_view.clear_routes ();
RouteList routes = *_session->get_routes();
/* Add master bus and then everything else */
if (_session->master_out()) {
ARDOUR::IO* master = _session->master_out()->output().get();
channel_view.add_route (master);
}
for (RouteList::iterator it = routes.begin(); it != routes.end(); ++it) {
if ((*it)->is_master () || (*it)->is_monitor ()) {
continue;
}
channel_view.add_route ((*it)->output().get());
}
update_channel_count ();
}
void
PortExportChannelSelector::update_channel_count ()
{
uint32_t chans = static_cast<uint32_t> (channels_spinbutton.get_value());
channel_view.set_channel_count (chans);
CriticalSelectionChanged();
}
void
PortExportChannelSelector::update_split_state ()
{
state->config->set_split (split_checkbox.get_active());
CriticalSelectionChanged();
}
void
PortExportChannelSelector::RouteCols::add_channels (uint32_t chans)
{
while (chans > 0) {
channels.push_back (Channel (*this));
++n_channels;
--chans;
}
}
PortExportChannelSelector::RouteCols::Channel &
PortExportChannelSelector::RouteCols::get_channel (uint32_t channel)
{
if (channel > n_channels) {
std::cout << "Invalid channel cout for get_channel!" << std::endl;
}
std::list<Channel>::iterator it = channels.begin();
while (channel > 1) { // Channel count starts from one!
++it;
--channel;
}
return *it;
}
PortExportChannelSelector::ChannelTreeView::ChannelTreeView (uint32_t max_channels) :
n_channels (0)
{
/* Main columns */
route_cols.add_channels (max_channels);
route_list = Gtk::ListStore::create(route_cols);
set_model (route_list);
/* Add column with toggle and text */
append_column_editable (_("Export"), route_cols.selected);
Gtk::CellRendererText* text_renderer = Gtk::manage (new Gtk::CellRendererText);
text_renderer->property_editable() = false;
text_renderer->set_alignment (0.0, 0.5);
Gtk::TreeView::Column* column = Gtk::manage (new Gtk::TreeView::Column);
column->set_title (_("Bus or Track"));
column->pack_start (*text_renderer);
column->set_expand (true);
column->add_attribute (text_renderer->property_text(), route_cols.name);
append_column (*column);
Gtk::CellRendererToggle *toggle = dynamic_cast<Gtk::CellRendererToggle *>(get_column_cell_renderer (0));
toggle->set_alignment (0.0, 0.5);
toggle->signal_toggled().connect (sigc::mem_fun (*this, &PortExportChannelSelector::ChannelTreeView::update_toggle_selection));
static_columns = get_columns().size();
}
void
PortExportChannelSelector::ChannelTreeView::set_config (ChannelConfigPtr c)
{
/* TODO Without the following line, the state might get reset.
* Pointing to the same address does not mean the state of the configuration hasn't changed.
* In the current code this is safe, but the actual cause of the problem would be good to fix
*/
if (config == c) { return; }
config = c;
uint32_t i = 1;
ExportChannelConfiguration::ChannelList chan_list = config->get_channels();
for (ExportChannelConfiguration::ChannelList::iterator c_it = chan_list.begin(); c_it != chan_list.end(); ++c_it) {
for (Gtk::ListStore::Children::iterator r_it = route_list->children().begin(); r_it != route_list->children().end(); ++r_it) {
ARDOUR::PortExportChannel * pec;
if (!(pec = dynamic_cast<ARDOUR::PortExportChannel *> (c_it->get()))) {
continue;
}
Glib::RefPtr<Gtk::ListStore> port_list = r_it->get_value (route_cols.port_list_col);
std::set<boost::weak_ptr<AudioPort> > route_ports;
std::set<boost::weak_ptr<AudioPort> > intersection;
std::map<boost::weak_ptr<AudioPort>, string> port_labels;
for (Gtk::ListStore::Children::const_iterator p_it = port_list->children().begin(); p_it != port_list->children().end(); ++p_it) {
route_ports.insert ((*p_it)->get_value (route_cols.port_cols.port));
port_labels.insert (make_pair ((*p_it)->get_value (route_cols.port_cols.port),
(*p_it)->get_value (route_cols.port_cols.label)));
}
std::set_intersection (pec->get_ports().begin(), pec->get_ports().end(),
route_ports.begin(), route_ports.end(),
std::insert_iterator<std::set<boost::weak_ptr<AudioPort> > > (intersection, intersection.begin()));
intersection.erase (boost::weak_ptr<AudioPort> ()); // Remove "none" selection
if (intersection.empty()) {
continue;
}
if (!r_it->get_value (route_cols.selected)) {
r_it->set_value (route_cols.selected, true);
/* Set previous channels (if any) to none */
for (uint32_t chn = 1; chn < i; ++chn) {
r_it->set_value (route_cols.get_channel (chn).port, boost::weak_ptr<AudioPort> ());
r_it->set_value (route_cols.get_channel (chn).label, string ("(none)"));
}
}
boost::weak_ptr<AudioPort> port = *intersection.begin();
std::map<boost::weak_ptr<AudioPort>, string>::iterator label_it = port_labels.find (port);
string label = label_it != port_labels.end() ? label_it->second : "error";
r_it->set_value (route_cols.get_channel (i).port, port);
r_it->set_value (route_cols.get_channel (i).label, label);
}
++i;
}
}
void
PortExportChannelSelector::ChannelTreeView::add_route (ARDOUR::IO * io)
{
Gtk::TreeModel::iterator iter = route_list->append();
Gtk::TreeModel::Row row = *iter;
row[route_cols.selected] = false;
row[route_cols.name] = io->name();
row[route_cols.io] = io;
/* Initialize port list */
Glib::RefPtr<Gtk::ListStore> port_list = Gtk::ListStore::create (route_cols.port_cols);
row[route_cols.port_list_col] = port_list;
uint32_t outs = io->n_ports().n_audio();
for (uint32_t i = 0; i < outs; ++i) {
iter = port_list->append();
row = *iter;
row[route_cols.port_cols.selected] = false;
row[route_cols.port_cols.port] = io->audio (i);
std::ostringstream oss;
oss << "Out-" << (i + 1);
row[route_cols.port_cols.label] = oss.str();
}
iter = port_list->append();
row = *iter;
row[route_cols.port_cols.selected] = false;
row[route_cols.port_cols.port] = boost::weak_ptr<AudioPort> ();
row[route_cols.port_cols.label] = "(none)";
}
void
PortExportChannelSelector::ChannelTreeView::set_channel_count (uint32_t channels)
{
int offset = channels - n_channels;
while (offset > 0) {
++n_channels;
std::ostringstream oss;
oss << n_channels;
/* New column */
Gtk::TreeView::Column* column = Gtk::manage (new Gtk::TreeView::Column (oss.str()));
Gtk::CellRendererCombo* combo_renderer = Gtk::manage (new Gtk::CellRendererCombo);
combo_renderer->property_text_column() = 2;
combo_renderer->property_has_entry() = false;
column->pack_start (*combo_renderer);
append_column (*column);
column->add_attribute (combo_renderer->property_text(), route_cols.get_channel(n_channels).label);
column->add_attribute (combo_renderer->property_model(), route_cols.port_list_col);
column->add_attribute (combo_renderer->property_editable(), route_cols.selected);
combo_renderer->signal_edited().connect (sigc::bind (sigc::mem_fun (*this, &PortExportChannelSelector::ChannelTreeView::update_selection_text), n_channels));
/* put data into view */
for (Gtk::ListStore::Children::iterator it = route_list->children().begin(); it != route_list->children().end(); ++it) {
std::string label = it->get_value(route_cols.selected) ? "(none)" : "";
it->set_value (route_cols.get_channel (n_channels).label, label);
it->set_value (route_cols.get_channel (n_channels).port, boost::weak_ptr<AudioPort> ());
}
/* set column width */
get_column (static_columns + n_channels - 1)->set_min_width (80);
--offset;
}
while (offset < 0) {
--n_channels;
remove_column (*get_column (n_channels + static_columns));
++offset;
}
update_config ();
}
void
PortExportChannelSelector::ChannelTreeView::update_config ()
{
if (!config) { return; }
config->clear_channels();
for (uint32_t i = 1; i <= n_channels; ++i) {
ExportChannelPtr channel (new PortExportChannel ());
PortExportChannel * pec = static_cast<PortExportChannel *> (channel.get());
for (Gtk::ListStore::Children::iterator it = route_list->children().begin(); it != route_list->children().end(); ++it) {
Gtk::TreeModel::Row row = *it;
if (!row[route_cols.selected]) {
continue;
}
boost::weak_ptr<AudioPort> weak_port = row[route_cols.get_channel (i).port];
boost::shared_ptr<AudioPort> port = weak_port.lock ();
if (port) {
pec->add_port (port);
}
}
config->register_channel (channel);
}
CriticalSelectionChanged ();
}
void
PortExportChannelSelector::ChannelTreeView::update_toggle_selection (std::string const & path)
{
Gtk::TreeModel::iterator iter = get_model ()->get_iter (path);
bool selected = iter->get_value (route_cols.selected);
for (uint32_t i = 1; i <= n_channels; ++i) {
if (!selected) {
iter->set_value (route_cols.get_channel (i).label, std::string (""));
continue;
}
iter->set_value (route_cols.get_channel (i).label, std::string("(none)"));
iter->set_value (route_cols.get_channel (i).port, boost::weak_ptr<AudioPort> ());
Glib::RefPtr<Gtk::ListStore> port_list = iter->get_value (route_cols.port_list_col);
Gtk::ListStore::Children::iterator port_it;
uint32_t port_number = 1;
for (port_it = port_list->children().begin(); port_it != port_list->children().end(); ++port_it) {
if (port_number == i) {
iter->set_value (route_cols.get_channel (i).label, (std::string) (*port_it)->get_value (route_cols.port_cols.label));
iter->set_value (route_cols.get_channel (i).port, (*port_it)->get_value (route_cols.port_cols.port));
}
++port_number;
}
}
update_config ();
}
void
PortExportChannelSelector::ChannelTreeView::update_selection_text (std::string const & path, std::string const & new_text, uint32_t channel)
{
Gtk::TreeModel::iterator iter = get_model ()->get_iter (path);
iter->set_value (route_cols.get_channel (channel).label, new_text);
Glib::RefPtr<Gtk::ListStore> port_list = iter->get_value (route_cols.port_list_col);
Gtk::ListStore::Children::iterator port_it;
for (port_it = port_list->children().begin(); port_it != port_list->children().end(); ++port_it) {
std::string label = port_it->get_value (route_cols.port_cols.label);
if (label == new_text) {
boost::weak_ptr<AudioPort> w = (*port_it)[route_cols.port_cols.port];
iter->set_value (route_cols.get_channel (channel).port, w);
}
}
update_config ();
}
RegionExportChannelSelector::RegionExportChannelSelector (ARDOUR::Session * _session,
ProfileManagerPtr manager,
ARDOUR::AudioRegion const & region,
ARDOUR::AudioTrack & track) :
ExportChannelSelector (_session, manager),
region (region),
track (track),
region_chans (region.n_channels()),
track_chans (track.n_outputs().n_audio()),
raw_button (type_group),
fades_button (type_group),
processed_button (type_group)
{
pack_start (vbox);
/* make fades+region gain be the default */
fades_button.set_active ();
raw_button.set_label (string_compose (_("Region contents without fades nor region gain (channels: %1)"), region_chans));
raw_button.signal_toggled ().connect (sigc::mem_fun (*this, &RegionExportChannelSelector::handle_selection));
vbox.pack_start (raw_button, false, false);
fades_button.set_label (string_compose (_("Region contents with fades and region gain (channels: %1)"), region_chans));
fades_button.signal_toggled ().connect (sigc::mem_fun (*this, &RegionExportChannelSelector::handle_selection));
vbox.pack_start (fades_button, false, false);
processed_button.set_label (string_compose (_("Track output (channels: %1)"), track_chans));
processed_button.signal_toggled ().connect (sigc::mem_fun (*this, &RegionExportChannelSelector::handle_selection));
vbox.pack_start (processed_button, false, false);
sync_with_manager();
vbox.show_all_children ();
show_all_children ();
}
void
RegionExportChannelSelector::sync_with_manager ()
{
state = manager->get_channel_configs().front();
if (!state) { return; }
switch (state->config->region_processing_type()) {
case RegionExportChannelFactory::None:
// Do nothing
break;
case RegionExportChannelFactory::Raw:
raw_button.set_active (true);
break;
case RegionExportChannelFactory::Fades:
fades_button.set_active (true);
break;
case RegionExportChannelFactory::Processed:
processed_button.set_active (true);
break;
}
handle_selection ();
}
void
RegionExportChannelSelector::handle_selection ()
{
if (!state) {
return;
}
state->config->clear_channels ();
RegionExportChannelFactory::Type type = RegionExportChannelFactory::None;
if (raw_button.get_active ()) {
type = RegionExportChannelFactory::Raw;
} else if (fades_button.get_active ()) {
type = RegionExportChannelFactory::Fades;
} else if (processed_button.get_active ()) {
type = RegionExportChannelFactory::Processed;
} else {
CriticalSelectionChanged ();
return;
}
factory.reset (new RegionExportChannelFactory (_session, region, track, type));
state->config->set_region_processing_type (type);
for (size_t chan = 0; chan < region_chans; ++chan) {
state->config->register_channel (factory->create (chan));
}
CriticalSelectionChanged ();
}
/* Track export channel selector */
TrackExportChannelSelector::TrackExportChannelSelector (ARDOUR::Session * session, ProfileManagerPtr manager)
: ExportChannelSelector(session, manager)
, region_contents_button(source_group, _("Export region contents"))
, track_output_button(source_group, _("Export track output"))
, select_tracks_button (_("Select all tracks"))
, select_busses_button (_("Select all busses"))
, select_none_button (_("Deselect all"))
{
pack_start(main_layout);
// Options
options_box.pack_start(region_contents_button);
options_box.pack_start(track_output_button);
options_box.pack_start (select_tracks_button);
options_box.pack_start (select_busses_button);
options_box.pack_start (select_none_button);
main_layout.pack_start(options_box, false, false);
// Track scroller
track_scroller.add (track_view);
track_scroller.set_size_request (-1, 130);
track_scroller.set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
main_layout.pack_start(track_scroller);
// Track list
track_list = Gtk::ListStore::create (track_cols);
track_list->set_sort_column (track_cols.order_key, Gtk::SORT_ASCENDING);
track_view.set_model (track_list);
track_view.set_headers_visible (true);
track_view.append_column_editable (_("Export"), track_cols.selected);
Gtk::CellRendererToggle *toggle = dynamic_cast<Gtk::CellRendererToggle *>(track_view.get_column_cell_renderer (0));
toggle->set_alignment (0.0, 0.5);
toggle->signal_toggled().connect (sigc::hide (sigc::mem_fun (*this, &TrackExportChannelSelector::update_config)));
Gtk::CellRendererText* text_renderer = Gtk::manage (new Gtk::CellRendererText);
text_renderer->property_editable() = false;
text_renderer->set_alignment (0.0, 0.5);
Gtk::TreeView::Column* column = Gtk::manage (new Gtk::TreeView::Column);
column->set_title (_("Track name"));
track_view.append_column (*column);
column->pack_start (*text_renderer, false);
column->add_attribute (text_renderer->property_text(), track_cols.label);
select_tracks_button.signal_clicked().connect (sigc::mem_fun (*this, &TrackExportChannelSelector::select_tracks));
select_busses_button.signal_clicked().connect (sigc::mem_fun (*this, &TrackExportChannelSelector::select_busses));
select_none_button.signal_clicked().connect (sigc::mem_fun (*this, &TrackExportChannelSelector::select_none));
fill_list();
show_all_children ();
}
void
TrackExportChannelSelector::sync_with_manager ()
{
// TODO implement properly
update_config();
}
void
TrackExportChannelSelector::select_tracks ()
{
for (Gtk::ListStore::Children::iterator it = track_list->children().begin(); it != track_list->children().end(); ++it) {
Gtk::TreeModel::Row row = *it;
boost::shared_ptr<Route> route = row[track_cols.route];
if (boost::dynamic_pointer_cast<Track> (route)) {
// it's a track
row[track_cols.selected] = true;
}
}
}
void
TrackExportChannelSelector::select_busses ()
{
for (Gtk::ListStore::Children::iterator it = track_list->children().begin(); it != track_list->children().end(); ++it) {
Gtk::TreeModel::Row row = *it;
boost::shared_ptr<Route> route = row[track_cols.route];
if (!boost::dynamic_pointer_cast<Track> (route)) {
// it's not a track, must be a bus
row[track_cols.selected] = true;
}
}
}
void
TrackExportChannelSelector::select_none ()
{
for (Gtk::ListStore::Children::iterator it = track_list->children().begin(); it != track_list->children().end(); ++it) {
Gtk::TreeModel::Row row = *it;
row[track_cols.selected] = false;
}
}
void
TrackExportChannelSelector::fill_list()
{
track_list->clear();
RouteList routes = *_session->get_routes();
for (RouteList::iterator it = routes.begin(); it != routes.end(); ++it) {
if (!boost::dynamic_pointer_cast<Track>(*it)) {
// not a track, must be a bus
if ((*it)->is_master () || (*it)->is_monitor ()) {
continue;
}
if (!(*it)->active ()) {
// don't include inactive busses
continue;
}
// not monitor or master bus
add_track (*it);
}
}
for (RouteList::iterator it = routes.begin(); it != routes.end(); ++it) {
if (boost::dynamic_pointer_cast<AudioTrack>(*it)) {
if (!(*it)->active ()) {
// don't include inactive tracks
continue;
}
add_track (*it);
}
}
}
void
TrackExportChannelSelector::add_track (boost::shared_ptr<Route> route)
{
Gtk::TreeModel::iterator iter = track_list->append();
Gtk::TreeModel::Row row = *iter;
row[track_cols.selected] = false;
row[track_cols.label] = route->name();
row[track_cols.route] = route;
row[track_cols.order_key] = route->order_key();
}
void
TrackExportChannelSelector::update_config()
{
manager->clear_channel_configs();
for (Gtk::ListStore::Children::iterator it = track_list->children().begin(); it != track_list->children().end(); ++it) {
Gtk::TreeModel::Row row = *it;
if (!row[track_cols.selected]) {
continue;
}
ExportProfileManager::ChannelConfigStatePtr state = manager->add_channel_config();
boost::shared_ptr<Route> route = row[track_cols.route];
if (track_output_button.get_active()) {
uint32_t outs = route->n_outputs().n_audio();
for (uint32_t i = 0; i < outs; ++i) {
boost::shared_ptr<AudioPort> port = route->output()->audio (i);
if (port) {
ExportChannelPtr channel (new PortExportChannel ());
PortExportChannel * pec = static_cast<PortExportChannel *> (channel.get());
pec->add_port(port);
state->config->register_channel(channel);
}
}
} else {
std::list<ExportChannelPtr> list;
RouteExportChannel::create_from_route (list, route);
state->config->register_channels (list);
}
state->config->set_name (route->name());
}
CriticalSelectionChanged ();
}