Region export dialog: Make export channel and -selector polymorphic, add the region related classes and a dialog specialization
git-svn-id: svn://localhost/ardour2/branches/3.0@3915 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
01b1790c98
commit
98389f7da4
@ -91,17 +91,25 @@ Editor::export_range ()
|
||||
void
|
||||
Editor::export_region ()
|
||||
{
|
||||
// if (selection->regions.empty()) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// boost::shared_ptr<Region> r = selection->regions.front()->region();
|
||||
//
|
||||
// ExportDialog* dialog = new ExportRegionDialog (*this, r);
|
||||
//
|
||||
// dialog->connect_to_session (session);
|
||||
// dialog->set_range (clicked_regionview->region()->first_frame(), clicked_regionview->region()->last_frame());
|
||||
// dialog->start_export();
|
||||
if (selection->regions.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
boost::shared_ptr<Region> r = selection->regions.front()->region();
|
||||
AudioRegion & region (dynamic_cast<AudioRegion &> (*r));
|
||||
|
||||
RouteTimeAxisView & rtv (dynamic_cast<RouteTimeAxisView &> (selection->regions.front()->get_time_axis_view()));
|
||||
AudioTrack & track (dynamic_cast<AudioTrack &> (*rtv.route()));
|
||||
|
||||
ExportRegionDialog dialog (*this, region, track);
|
||||
dialog.set_session (session);
|
||||
dialog.run();
|
||||
|
||||
} catch (std::bad_cast & e) {
|
||||
error << "Exporting Region failed!" << endmsg;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -39,7 +39,7 @@
|
||||
using namespace ARDOUR;
|
||||
using namespace PBD;
|
||||
|
||||
ExportChannelSelector::ExportChannelSelector () :
|
||||
PortExportChannelSelector::PortExportChannelSelector () :
|
||||
channels_label (_("Channels:"), Gtk::ALIGN_LEFT),
|
||||
split_checkbox (_("Split to mono files")),
|
||||
max_channels (20),
|
||||
@ -68,11 +68,11 @@ ExportChannelSelector::ExportChannelSelector () :
|
||||
channels_spinbutton.set_range (1, max_channels);
|
||||
channels_spinbutton.set_value (2);
|
||||
|
||||
channels_spinbutton.signal_value_changed().connect (sigc::mem_fun (*this, &ExportChannelSelector::update_channel_count));
|
||||
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, &ExportChannelSelector::update_split_state));
|
||||
split_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &PortExportChannelSelector::update_split_state));
|
||||
channel_view.CriticalSelectionChanged.connect (CriticalSelectionChanged.make_slot());
|
||||
|
||||
/* Finalize */
|
||||
@ -81,7 +81,7 @@ ExportChannelSelector::ExportChannelSelector () :
|
||||
|
||||
}
|
||||
|
||||
ExportChannelSelector::~ExportChannelSelector ()
|
||||
PortExportChannelSelector::~PortExportChannelSelector ()
|
||||
{
|
||||
// if (session) {
|
||||
// session->add_instant_xml (get_state(), false);
|
||||
@ -89,7 +89,7 @@ ExportChannelSelector::~ExportChannelSelector ()
|
||||
}
|
||||
|
||||
void
|
||||
ExportChannelSelector::set_state (ARDOUR::ExportProfileManager::ChannelConfigStatePtr const state_, ARDOUR::Session * session_)
|
||||
PortExportChannelSelector::set_state (ARDOUR::ExportProfileManager::ChannelConfigStatePtr const state_, ARDOUR::Session * session_)
|
||||
{
|
||||
state = state_;
|
||||
session = session_;
|
||||
@ -102,7 +102,7 @@ ExportChannelSelector::set_state (ARDOUR::ExportProfileManager::ChannelConfigSta
|
||||
}
|
||||
|
||||
void
|
||||
ExportChannelSelector::fill_route_list ()
|
||||
PortExportChannelSelector::fill_route_list ()
|
||||
{
|
||||
channel_view.clear_routes ();
|
||||
Session::RouteList routes = *session->get_routes();
|
||||
@ -123,7 +123,7 @@ ExportChannelSelector::fill_route_list ()
|
||||
}
|
||||
|
||||
void
|
||||
ExportChannelSelector::update_channel_count ()
|
||||
PortExportChannelSelector::update_channel_count ()
|
||||
{
|
||||
uint32_t chans = static_cast<uint32_t> (channels_spinbutton.get_value());
|
||||
channel_view.set_channel_count (chans);
|
||||
@ -131,14 +131,14 @@ ExportChannelSelector::update_channel_count ()
|
||||
}
|
||||
|
||||
void
|
||||
ExportChannelSelector::update_split_state ()
|
||||
PortExportChannelSelector::update_split_state ()
|
||||
{
|
||||
state->config->set_split (split_checkbox.get_active());
|
||||
CriticalSelectionChanged();
|
||||
}
|
||||
|
||||
void
|
||||
ExportChannelSelector::RouteCols::add_channels (uint32_t chans)
|
||||
PortExportChannelSelector::RouteCols::add_channels (uint32_t chans)
|
||||
{
|
||||
while (chans > 0) {
|
||||
channels.push_back (Channel (*this));
|
||||
@ -147,8 +147,8 @@ ExportChannelSelector::RouteCols::add_channels (uint32_t chans)
|
||||
}
|
||||
}
|
||||
|
||||
ExportChannelSelector::RouteCols::Channel &
|
||||
ExportChannelSelector::RouteCols::get_channel (uint32_t channel)
|
||||
PortExportChannelSelector::RouteCols::Channel &
|
||||
PortExportChannelSelector::RouteCols::get_channel (uint32_t channel)
|
||||
{
|
||||
if (channel > n_channels) {
|
||||
std::cout << "Invalid channel cout for get_channel!" << std::endl;
|
||||
@ -164,7 +164,7 @@ ExportChannelSelector::RouteCols::get_channel (uint32_t channel)
|
||||
return *it;
|
||||
}
|
||||
|
||||
ExportChannelSelector::ChannelTreeView::ChannelTreeView (uint32_t max_channels) :
|
||||
PortExportChannelSelector::ChannelTreeView::ChannelTreeView (uint32_t max_channels) :
|
||||
n_channels (0)
|
||||
{
|
||||
/* Main columns */
|
||||
@ -186,13 +186,13 @@ ExportChannelSelector::ChannelTreeView::ChannelTreeView (uint32_t max_channels)
|
||||
column->add_attribute (text_renderer->property_text(), route_cols.name);
|
||||
|
||||
Gtk::CellRendererToggle *toggle = dynamic_cast<Gtk::CellRendererToggle *>(get_column_cell_renderer (0));
|
||||
toggle->signal_toggled().connect (mem_fun (*this, &ExportChannelSelector::ChannelTreeView::update_toggle_selection));
|
||||
toggle->signal_toggled().connect (mem_fun (*this, &PortExportChannelSelector::ChannelTreeView::update_toggle_selection));
|
||||
|
||||
static_columns = get_columns().size();
|
||||
}
|
||||
|
||||
void
|
||||
ExportChannelSelector::ChannelTreeView::set_config (ChannelConfigPtr c)
|
||||
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.
|
||||
@ -208,6 +208,11 @@ ExportChannelSelector::ChannelTreeView::set_config (ChannelConfigPtr c)
|
||||
|
||||
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<AudioPort *> route_ports;
|
||||
std::set<AudioPort *> intersection;
|
||||
@ -219,7 +224,7 @@ ExportChannelSelector::ChannelTreeView::set_config (ChannelConfigPtr c)
|
||||
(*p_it)->get_value (route_cols.port_cols.label)));
|
||||
}
|
||||
|
||||
std::set_intersection ((*c_it)->begin(), (*c_it)->end(),
|
||||
std::set_intersection (pec->get_ports().begin(), pec->get_ports().end(),
|
||||
route_ports.begin(), route_ports.end(),
|
||||
std::insert_iterator<std::set<AudioPort *> > (intersection, intersection.begin()));
|
||||
|
||||
@ -253,7 +258,7 @@ ExportChannelSelector::ChannelTreeView::set_config (ChannelConfigPtr c)
|
||||
}
|
||||
|
||||
void
|
||||
ExportChannelSelector::ChannelTreeView::add_route (ARDOUR::IO * route)
|
||||
PortExportChannelSelector::ChannelTreeView::add_route (ARDOUR::IO * route)
|
||||
{
|
||||
Gtk::TreeModel::iterator iter = route_list->append();
|
||||
Gtk::TreeModel::Row row = *iter;
|
||||
@ -291,7 +296,7 @@ ExportChannelSelector::ChannelTreeView::add_route (ARDOUR::IO * route)
|
||||
}
|
||||
|
||||
void
|
||||
ExportChannelSelector::ChannelTreeView::set_channel_count (uint32_t channels)
|
||||
PortExportChannelSelector::ChannelTreeView::set_channel_count (uint32_t channels)
|
||||
{
|
||||
int offset = channels - n_channels;
|
||||
|
||||
@ -315,7 +320,7 @@ ExportChannelSelector::ChannelTreeView::set_channel_count (uint32_t channels)
|
||||
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, &ExportChannelSelector::ChannelTreeView::update_selection_text), n_channels));
|
||||
combo_renderer->signal_edited().connect (sigc::bind (sigc::mem_fun (*this, &PortExportChannelSelector::ChannelTreeView::update_selection_text), n_channels));
|
||||
|
||||
/* put data into view */
|
||||
|
||||
@ -344,7 +349,7 @@ ExportChannelSelector::ChannelTreeView::set_channel_count (uint32_t channels)
|
||||
}
|
||||
|
||||
void
|
||||
ExportChannelSelector::ChannelTreeView::update_config ()
|
||||
PortExportChannelSelector::ChannelTreeView::update_config ()
|
||||
{
|
||||
|
||||
if (!config) { return; }
|
||||
@ -353,7 +358,8 @@ ExportChannelSelector::ChannelTreeView::update_config ()
|
||||
|
||||
for (uint32_t i = 1; i <= n_channels; ++i) {
|
||||
|
||||
boost::shared_ptr<ExportChannel> channel (new ExportChannel ());
|
||||
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;
|
||||
@ -364,7 +370,7 @@ ExportChannelSelector::ChannelTreeView::update_config ()
|
||||
|
||||
AudioPort * port = row[route_cols.get_channel (i).port];
|
||||
if (port) {
|
||||
channel->add_port (port);
|
||||
pec->add_port (port);
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,7 +381,7 @@ ExportChannelSelector::ChannelTreeView::update_config ()
|
||||
}
|
||||
|
||||
void
|
||||
ExportChannelSelector::ChannelTreeView::update_toggle_selection (Glib::ustring const & path)
|
||||
PortExportChannelSelector::ChannelTreeView::update_toggle_selection (Glib::ustring const & path)
|
||||
{
|
||||
Gtk::TreeModel::iterator iter = get_model ()->get_iter (path);
|
||||
bool selected = iter->get_value (route_cols.selected);
|
||||
@ -408,7 +414,7 @@ ExportChannelSelector::ChannelTreeView::update_toggle_selection (Glib::ustring c
|
||||
}
|
||||
|
||||
void
|
||||
ExportChannelSelector::ChannelTreeView::update_selection_text (Glib::ustring const & path, Glib::ustring const & new_text, uint32_t channel)
|
||||
PortExportChannelSelector::ChannelTreeView::update_selection_text (Glib::ustring const & path, Glib::ustring 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);
|
||||
@ -425,3 +431,66 @@ ExportChannelSelector::ChannelTreeView::update_selection_text (Glib::ustring con
|
||||
|
||||
update_config ();
|
||||
}
|
||||
|
||||
RegionExportChannelSelector::RegionExportChannelSelector (ARDOUR::AudioRegion const & region, ARDOUR::AudioTrack & track) :
|
||||
session (0),
|
||||
region (region),
|
||||
track (track),
|
||||
region_chans (region.n_channels()),
|
||||
track_chans (track.n_outputs().n_audio()),
|
||||
|
||||
raw_button (type_group),
|
||||
processed_button (type_group)
|
||||
{
|
||||
pack_start (vbox);
|
||||
|
||||
raw_button.set_label (string_compose (_("Raw region export, no fades or plugins (%1 channels)"), region_chans));
|
||||
raw_button.signal_toggled ().connect (sigc::mem_fun (*this, &RegionExportChannelSelector::handle_selection));
|
||||
vbox.pack_start (raw_button);
|
||||
|
||||
processed_button.set_label (string_compose (_("Processed region export with fades and plugins applied (%1 channels)"), track_chans));
|
||||
processed_button.signal_toggled ().connect (sigc::mem_fun (*this, &RegionExportChannelSelector::handle_selection));
|
||||
vbox.pack_start (processed_button);
|
||||
|
||||
vbox.show_all_children ();
|
||||
show_all_children ();
|
||||
}
|
||||
|
||||
void
|
||||
RegionExportChannelSelector::set_state (ARDOUR::ExportProfileManager::ChannelConfigStatePtr const state_, ARDOUR::Session * session_)
|
||||
{
|
||||
state = state_;
|
||||
session = session_;
|
||||
|
||||
handle_selection ();
|
||||
}
|
||||
|
||||
void
|
||||
RegionExportChannelSelector::handle_selection ()
|
||||
{
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
state->config->clear_channels ();
|
||||
|
||||
if (raw_button.get_active ()) {
|
||||
|
||||
factory.reset (new RegionExportChannelFactory (session, region, track, RegionExportChannelFactory::Raw));
|
||||
|
||||
for (size_t chan = 0; chan < region_chans; ++chan) {
|
||||
state->config->register_channel (factory->create (chan));
|
||||
}
|
||||
|
||||
} else if (processed_button.get_active ()) {
|
||||
|
||||
factory.reset (new RegionExportChannelFactory(session, region, track, RegionExportChannelFactory::Processed));
|
||||
|
||||
for (size_t chan = 0; chan < region_chans; ++chan) {
|
||||
state->config->register_channel (factory->create (chan));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CriticalSelectionChanged ();
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <list>
|
||||
|
||||
#include <ardour/export_profile_manager.h>
|
||||
#include <ardour/export_channel.h>
|
||||
|
||||
#include <gtkmm.h>
|
||||
#include <sigc++/signal.h>
|
||||
@ -31,31 +32,39 @@
|
||||
|
||||
namespace ARDOUR {
|
||||
class Session;
|
||||
class ExportChannel;
|
||||
class ExportChannelConfiguration;
|
||||
class RegionExportChannelFactory;
|
||||
class ExportHandler;
|
||||
class AudioPort;
|
||||
class IO;
|
||||
class AudioRegion;
|
||||
class AudioTrack;
|
||||
}
|
||||
|
||||
class XMLNode;
|
||||
|
||||
///
|
||||
class ExportChannelSelector : public Gtk::HBox {
|
||||
private:
|
||||
|
||||
class ExportChannelSelector : public Gtk::HBox
|
||||
{
|
||||
protected:
|
||||
typedef boost::shared_ptr<ARDOUR::ExportChannelConfiguration> ChannelConfigPtr;
|
||||
|
||||
typedef boost::shared_ptr<ARDOUR::ExportHandler> HandlerPtr;
|
||||
|
||||
public:
|
||||
virtual ~ExportChannelSelector () {}
|
||||
|
||||
virtual void set_state (ARDOUR::ExportProfileManager::ChannelConfigStatePtr const state_, ARDOUR::Session * session_) = 0;
|
||||
sigc::signal<void> CriticalSelectionChanged;
|
||||
};
|
||||
|
||||
ExportChannelSelector ();
|
||||
~ExportChannelSelector ();
|
||||
class PortExportChannelSelector : public ExportChannelSelector
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
PortExportChannelSelector ();
|
||||
~PortExportChannelSelector ();
|
||||
|
||||
void set_state (ARDOUR::ExportProfileManager::ChannelConfigStatePtr const state_, ARDOUR::Session * session_);
|
||||
|
||||
sigc::signal<void> CriticalSelectionChanged;
|
||||
|
||||
private:
|
||||
|
||||
@ -63,8 +72,7 @@ class ExportChannelSelector : public Gtk::HBox {
|
||||
void update_channel_count ();
|
||||
void update_split_state ();
|
||||
|
||||
typedef boost::shared_ptr<ARDOUR::ExportChannel> ChannelPtr;
|
||||
typedef std::list<ChannelPtr> CahnnelList;
|
||||
typedef std::list<ARDOUR::ExportChannelPtr> CahnnelList;
|
||||
|
||||
ARDOUR::Session * session;
|
||||
ARDOUR::ExportProfileManager::ChannelConfigStatePtr state;
|
||||
@ -178,4 +186,33 @@ class ExportChannelSelector : public Gtk::HBox {
|
||||
|
||||
};
|
||||
|
||||
class RegionExportChannelSelector : public ExportChannelSelector
|
||||
{
|
||||
public:
|
||||
RegionExportChannelSelector (ARDOUR::AudioRegion const & region, ARDOUR::AudioTrack & track);
|
||||
|
||||
virtual void set_state (ARDOUR::ExportProfileManager::ChannelConfigStatePtr const state_, ARDOUR::Session * session_);
|
||||
|
||||
private:
|
||||
|
||||
void handle_selection ();
|
||||
|
||||
ARDOUR::Session * session;
|
||||
ARDOUR::ExportProfileManager::ChannelConfigStatePtr state;
|
||||
boost::shared_ptr<ARDOUR::RegionExportChannelFactory> factory;
|
||||
ARDOUR::AudioRegion const & region;
|
||||
ARDOUR::AudioTrack & track;
|
||||
|
||||
uint32_t region_chans;
|
||||
uint32_t track_chans;
|
||||
|
||||
/*** GUI components ***/
|
||||
|
||||
Gtk::VBox vbox;
|
||||
|
||||
Gtk::RadioButtonGroup type_group;
|
||||
Gtk::RadioButton raw_button;
|
||||
Gtk::RadioButton processed_button;
|
||||
};
|
||||
|
||||
#endif /* __export_channel_selector_h__ */
|
||||
|
@ -45,8 +45,6 @@ ExportDialog::~ExportDialog ()
|
||||
void
|
||||
ExportDialog::set_session (ARDOUR::Session* s)
|
||||
{
|
||||
init ();
|
||||
|
||||
session = s;
|
||||
|
||||
/* Init handler and profile manager */
|
||||
@ -55,6 +53,12 @@ ExportDialog::set_session (ARDOUR::Session* s)
|
||||
status = session->get_export_status ();
|
||||
profile_manager.reset (new ExportProfileManager (*session));
|
||||
|
||||
/* Possibly init stuff in derived classes */
|
||||
|
||||
init ();
|
||||
|
||||
/* Rest of session related initialization */
|
||||
|
||||
preset_selector->set_manager (profile_manager);
|
||||
file_notebook->set_session_and_manager (session, profile_manager);
|
||||
|
||||
@ -163,7 +167,7 @@ ExportDialog::init_components ()
|
||||
{
|
||||
preset_selector.reset (new ExportPresetSelector ());
|
||||
timespan_selector.reset (new ExportTimespanSelectorMultiple ());
|
||||
channel_selector.reset (new ExportChannelSelector ());
|
||||
channel_selector.reset (new PortExportChannelSelector ());
|
||||
file_notebook.reset (new ExportFileNotebook ());
|
||||
}
|
||||
|
||||
@ -358,7 +362,7 @@ ExportRangeDialog::init_components ()
|
||||
{
|
||||
preset_selector.reset (new ExportPresetSelector ());
|
||||
timespan_selector.reset (new ExportTimespanSelectorSingle (range_id));
|
||||
channel_selector.reset (new ExportChannelSelector ());
|
||||
channel_selector.reset (new PortExportChannelSelector ());
|
||||
file_notebook.reset (new ExportFileNotebook ());
|
||||
}
|
||||
|
||||
@ -371,6 +375,23 @@ ExportSelectionDialog::init_components ()
|
||||
{
|
||||
preset_selector.reset (new ExportPresetSelector ());
|
||||
timespan_selector.reset (new ExportTimespanSelectorSingle (X_("selection")));
|
||||
channel_selector.reset (new ExportChannelSelector ());
|
||||
channel_selector.reset (new PortExportChannelSelector ());
|
||||
file_notebook.reset (new ExportFileNotebook ());
|
||||
}
|
||||
|
||||
ExportRegionDialog::ExportRegionDialog (PublicEditor & editor, ARDOUR::AudioRegion const & region, ARDOUR::AudioTrack & track) :
|
||||
ExportDialog (editor, _("Export Region")),
|
||||
region (region),
|
||||
track (track)
|
||||
{}
|
||||
|
||||
void
|
||||
ExportRegionDialog::init_components ()
|
||||
{
|
||||
Glib::ustring loc_id = profile_manager->set_single_range (region.position(), region.position() + region.length(), region.name());
|
||||
|
||||
preset_selector.reset (new ExportPresetSelector ());
|
||||
timespan_selector.reset (new ExportTimespanSelectorSingle (loc_id));
|
||||
channel_selector.reset (new RegionExportChannelSelector (region, track));
|
||||
file_notebook.reset (new ExportFileNotebook ());
|
||||
}
|
||||
|
@ -63,6 +63,12 @@ class ExportDialog : public ArdourDialog {
|
||||
|
||||
protected:
|
||||
|
||||
typedef boost::shared_ptr<ARDOUR::ExportHandler> HandlerPtr;
|
||||
typedef boost::shared_ptr<ARDOUR::ExportProfileManager> ManagerPtr;
|
||||
|
||||
HandlerPtr handler;
|
||||
ManagerPtr profile_manager;
|
||||
|
||||
// initializes GUI layout
|
||||
virtual void init_gui ();
|
||||
|
||||
@ -94,13 +100,9 @@ class ExportDialog : public ArdourDialog {
|
||||
void show_progress ();
|
||||
gint progress_timeout ();
|
||||
|
||||
typedef boost::shared_ptr<ARDOUR::ExportHandler> HandlerPtr;
|
||||
typedef boost::shared_ptr<ARDOUR::ExportProfileManager> ManagerPtr;
|
||||
typedef boost::shared_ptr<ARDOUR::ExportStatus> StatusPtr;
|
||||
|
||||
PublicEditor & editor;
|
||||
HandlerPtr handler;
|
||||
ManagerPtr profile_manager;
|
||||
StatusPtr status;
|
||||
|
||||
/*** GUI components ***/
|
||||
@ -153,4 +155,16 @@ class ExportSelectionDialog : public ExportDialog
|
||||
void init_components ();
|
||||
};
|
||||
|
||||
class ExportRegionDialog : public ExportDialog
|
||||
{
|
||||
public:
|
||||
ExportRegionDialog (PublicEditor & editor, ARDOUR::AudioRegion const & region, ARDOUR::AudioTrack & track);
|
||||
|
||||
private:
|
||||
void init_components ();
|
||||
|
||||
ARDOUR::AudioRegion const & region;
|
||||
ARDOUR::AudioTrack & track;
|
||||
};
|
||||
|
||||
#endif /* __ardour_export_dialog_h__ */
|
||||
|
@ -70,6 +70,7 @@ element_importer.cc
|
||||
element_import_handler.cc
|
||||
enums.cc
|
||||
event_type_map.cc
|
||||
export_channel.cc
|
||||
export_channel_configuration.cc
|
||||
export_file_io.cc
|
||||
export_filename.cc
|
||||
|
144
libs/ardour/ardour/export_channel.h
Normal file
144
libs/ardour/ardour/export_channel.h
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __ardour_export_channel_h__
|
||||
#define __ardour_export_channel_h__
|
||||
|
||||
#include <ardour/audioregion.h>
|
||||
#include <ardour/audio_track.h>
|
||||
#include <ardour/buffer_set.h>
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <sigc++/signal.h>
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
class Session;
|
||||
|
||||
/// Export channel base class interface for different source types
|
||||
class ExportChannel
|
||||
{
|
||||
public:
|
||||
virtual void read (Sample * data, nframes_t frames) const = 0;
|
||||
virtual bool empty () const = 0;
|
||||
|
||||
/// Adds state to node passed
|
||||
virtual void get_state (XMLNode * node) const = 0;
|
||||
|
||||
/// Sets state from node passed
|
||||
virtual void set_state (XMLNode * node, Session & session) = 0;
|
||||
|
||||
// Operator< must be defined for usage in e.g. std::map or std::set to disallow duplicates when necessary
|
||||
virtual bool operator< (ExportChannel const & other) const = 0;
|
||||
};
|
||||
|
||||
/// Safe pointer for storing ExportChannels in ordered STL containers
|
||||
class ExportChannelPtr : public boost::shared_ptr<ExportChannel>
|
||||
{
|
||||
public:
|
||||
ExportChannelPtr () {}
|
||||
template<typename Y> explicit ExportChannelPtr (Y * ptr) : boost::shared_ptr<ExportChannel> (ptr) {}
|
||||
|
||||
bool operator< (ExportChannelPtr const & other) const { return **this < *other; }
|
||||
};
|
||||
|
||||
/// Basic export channel that reads from AudioPorts
|
||||
class PortExportChannel : public ExportChannel
|
||||
{
|
||||
public:
|
||||
typedef std::set<AudioPort *> PortSet;
|
||||
|
||||
PortExportChannel () {}
|
||||
|
||||
virtual void read (Sample * data, nframes_t frames) const;
|
||||
virtual bool empty () const { return ports.empty(); }
|
||||
|
||||
virtual void get_state (XMLNode * node) const;
|
||||
virtual void set_state (XMLNode * node, Session & session);
|
||||
|
||||
virtual bool operator< (ExportChannel const & other) const;
|
||||
|
||||
void add_port (AudioPort * port) { ports.insert (port); }
|
||||
PortSet const & get_ports () { return ports; }
|
||||
|
||||
private:
|
||||
PortSet ports;
|
||||
};
|
||||
|
||||
/// Handles RegionExportChannels and does actual reading from region
|
||||
class RegionExportChannelFactory : public sigc::trackable
|
||||
{
|
||||
public:
|
||||
enum Type {
|
||||
Raw,
|
||||
Processed
|
||||
};
|
||||
|
||||
RegionExportChannelFactory (Session * session, AudioRegion const & region, AudioTrack & track, Type type);
|
||||
|
||||
ExportChannelPtr create (uint32_t channel);
|
||||
void read (uint32_t channel, Sample * data, nframes_t frames_to_read);
|
||||
|
||||
private:
|
||||
|
||||
int new_cycle_started () { buffers_up_to_date = false; return 0; }
|
||||
void update_buffers (nframes_t frames);
|
||||
|
||||
AudioRegion const & region;
|
||||
AudioTrack & track;
|
||||
Type type;
|
||||
|
||||
nframes_t frames_per_cycle;
|
||||
size_t n_channels;
|
||||
BufferSet buffers;
|
||||
bool buffers_up_to_date;
|
||||
nframes_t region_start;
|
||||
nframes_t position;
|
||||
};
|
||||
|
||||
/// Export channel that reads from region channel
|
||||
class RegionExportChannel : public ExportChannel
|
||||
{
|
||||
friend class RegionExportChannelFactory;
|
||||
|
||||
public:
|
||||
virtual void read (Sample * data, nframes_t frames_to_read) const { factory.read (channel, data, frames_to_read); }
|
||||
virtual void get_state (XMLNode * node) const {};
|
||||
virtual void set_state (XMLNode * node, Session & session) {};
|
||||
virtual bool empty () const { return false; }
|
||||
// Region export should never have duplicate channels, so there need not be any semantics here
|
||||
virtual bool operator< (ExportChannel const & other) const { return this < &other; }
|
||||
|
||||
private:
|
||||
|
||||
RegionExportChannel (RegionExportChannelFactory & factory, uint32_t channel) :
|
||||
factory (factory),
|
||||
channel (channel)
|
||||
{}
|
||||
|
||||
RegionExportChannelFactory & factory;
|
||||
uint32_t channel;
|
||||
};
|
||||
|
||||
} // namespace ARDOUR
|
||||
|
||||
#endif
|
@ -21,12 +21,12 @@
|
||||
#ifndef __ardour_export_channel_configuration_h__
|
||||
#define __ardour_export_channel_configuration_h__
|
||||
|
||||
#include <set>
|
||||
#include <list>
|
||||
|
||||
#include <glibmm/ustring.h>
|
||||
#include <sigc++/signal.h>
|
||||
|
||||
#include <ardour/export_channel.h>
|
||||
#include <ardour/export_status.h>
|
||||
#include <ardour/ardour.h>
|
||||
|
||||
@ -46,13 +46,6 @@ class ExportProcessor;
|
||||
class ExportTimespan;
|
||||
class Session;
|
||||
|
||||
class ExportChannel : public std::set<AudioPort *>
|
||||
{
|
||||
public:
|
||||
void add_port (AudioPort * port) { if (port) { insert (port); } }
|
||||
void read_ports (float * data, nframes_t frames) const;
|
||||
};
|
||||
|
||||
class ExportChannelConfiguration
|
||||
{
|
||||
private:
|
||||
@ -86,20 +79,19 @@ class ExportChannelConfiguration
|
||||
XMLNode & get_state ();
|
||||
int set_state (const XMLNode &);
|
||||
|
||||
typedef boost::shared_ptr<ExportChannel const> ChannelPtr;
|
||||
typedef std::list<ChannelPtr> ChannelList;
|
||||
typedef std::list<ExportChannelPtr> ChannelList;
|
||||
|
||||
ChannelList const & get_channels () { return channels; }
|
||||
bool all_channels_have_ports ();
|
||||
ChannelList const & get_channels () const { return channels; }
|
||||
bool all_channels_have_ports () const;
|
||||
|
||||
ustring name () const { return _name; }
|
||||
void set_name (ustring name) { _name = name; }
|
||||
void set_split (bool value) { split = value; }
|
||||
|
||||
bool get_split () { return split; }
|
||||
uint32_t get_n_chans () { return channels.size(); }
|
||||
bool get_split () const { return split; }
|
||||
uint32_t get_n_chans () const { return channels.size(); }
|
||||
|
||||
void register_channel (ChannelPtr channel) { channels.push_back (channel); }
|
||||
void register_channel (ExportChannelPtr channel) { channels.push_back (channel); }
|
||||
void register_file_config (FormatPtr format, FilenamePtr filename) { file_configs.push_back (FileConfig (format, filename)); }
|
||||
|
||||
void clear_channels () { channels.clear (); }
|
||||
|
@ -144,6 +144,7 @@ class ExportProfileManager
|
||||
typedef std::list<TimespanStatePtr> TimespanStateList;
|
||||
|
||||
void set_selection_range (nframes_t start = 0, nframes_t end = 0);
|
||||
std::string set_single_range (nframes_t start, nframes_t end, Glib::ustring name);
|
||||
TimespanStateList const & get_timespans () { return check_list (timespans); }
|
||||
|
||||
private:
|
||||
@ -162,6 +163,9 @@ class ExportProfileManager
|
||||
boost::shared_ptr<Location> session_range;
|
||||
boost::shared_ptr<Location> selection_range;
|
||||
boost::shared_ptr<LocationList> ranges;
|
||||
|
||||
bool single_range_mode;
|
||||
boost::shared_ptr<Location> single_range;
|
||||
|
||||
/* Channel Configs */
|
||||
public:
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <glibmm/ustring.h>
|
||||
|
||||
#include <ardour/export_status.h>
|
||||
#include <ardour/export_channel.h>
|
||||
#include <ardour/ardour.h>
|
||||
|
||||
using Glib::ustring;
|
||||
@ -41,8 +42,8 @@ class ExportTimespan : public sigc::trackable
|
||||
{
|
||||
private:
|
||||
typedef boost::shared_ptr<ExportTempFile> TempFilePtr;
|
||||
typedef std::pair<ExportChannel const, TempFilePtr> ChannelFilePair;
|
||||
typedef std::map<ExportChannel const, TempFilePtr> TempFileMap;
|
||||
typedef std::pair<ExportChannelPtr, TempFilePtr> ChannelFilePair;
|
||||
typedef std::map<ExportChannelPtr, TempFilePtr> TempFileMap;
|
||||
typedef boost::shared_ptr<ExportStatus> ExportStatusPtr;
|
||||
|
||||
private:
|
||||
@ -59,13 +60,13 @@ class ExportTimespan : public sigc::trackable
|
||||
void set_range_id (ustring range_id) { _range_id = range_id; }
|
||||
|
||||
/// Registers a channel to be read when export starts rolling
|
||||
void register_channel (ExportChannel const & channel);
|
||||
void register_channel (ExportChannelPtr channel);
|
||||
|
||||
/// "Rewinds" the tempfiles to start reading the beginnings again
|
||||
void rewind ();
|
||||
|
||||
/// Reads data from the tempfile belonging to channel into data
|
||||
nframes_t get_data (float * data, nframes_t frames, ExportChannel const & channel);
|
||||
nframes_t get_data (float * data, nframes_t frames, ExportChannelPtr channel);
|
||||
|
||||
/// Reads data from each channel and writes to tempfile
|
||||
int process (nframes_t frames);
|
||||
|
142
libs/ardour/export_channel.cc
Normal file
142
libs/ardour/export_channel.cc
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
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 <ardour/export_channel.h>
|
||||
|
||||
#include <ardour/export_failed.h>
|
||||
#include <ardour/audioengine.h>
|
||||
|
||||
using namespace ARDOUR;
|
||||
|
||||
bool
|
||||
PortExportChannel::operator< (ExportChannel const & other) const
|
||||
{
|
||||
PortExportChannel const * pec;
|
||||
if (!(pec = dynamic_cast<PortExportChannel const *> (&other))) {
|
||||
return this < &other;
|
||||
}
|
||||
return ports < pec->ports;
|
||||
}
|
||||
|
||||
void
|
||||
PortExportChannel::read (Sample * data, nframes_t frames) const
|
||||
{
|
||||
memset (data, 0, frames * sizeof (float));
|
||||
|
||||
for (PortSet::const_iterator it = ports.begin(); it != ports.end(); ++it) {
|
||||
if (*it != 0) {
|
||||
Sample* port_buffer = (*it)->get_audio_buffer(frames, 0).data();
|
||||
|
||||
for (uint32_t i = 0; i < frames; ++i) {
|
||||
data[i] += (float) port_buffer[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PortExportChannel::get_state (XMLNode * node) const
|
||||
{
|
||||
XMLNode * port_node;
|
||||
for (PortSet::const_iterator it = ports.begin(); it != ports.end(); ++it) {
|
||||
if ((port_node = node->add_child ("Port"))) {
|
||||
port_node->add_property ("name", (*it)->name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PortExportChannel::set_state (XMLNode * node, Session & session)
|
||||
{
|
||||
XMLProperty * prop;
|
||||
XMLNodeList xml_ports = node->children ("Port");
|
||||
for (XMLNodeList::iterator it = xml_ports.begin(); it != xml_ports.end(); ++it) {
|
||||
if ((prop = (*it)->property ("name"))) {
|
||||
ports.insert (dynamic_cast<AudioPort *> (session.engine().get_port_by_name (prop->value())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RegionExportChannelFactory::RegionExportChannelFactory (Session * session, AudioRegion const & region, AudioTrack & track, Type type) :
|
||||
region (region),
|
||||
track (track),
|
||||
type (type),
|
||||
frames_per_cycle (session->engine().frames_per_cycle ()),
|
||||
buffers_up_to_date (false),
|
||||
region_start (region.position()),
|
||||
position (region_start)
|
||||
{
|
||||
switch (type) {
|
||||
case Raw:
|
||||
n_channels = region.n_channels();
|
||||
break;
|
||||
case Processed:
|
||||
n_channels = track.n_outputs().n_audio();
|
||||
break;
|
||||
default:
|
||||
throw ExportFailed ("Unhandled type in ExportChannelFactory constructor");
|
||||
}
|
||||
|
||||
session->ProcessExport.connect (sigc::hide (sigc::mem_fun (*this, &RegionExportChannelFactory::new_cycle_started)));
|
||||
|
||||
buffers.set_count (ChanCount (DataType::AUDIO, n_channels));
|
||||
buffers.ensure_buffers (DataType::AUDIO, n_channels, frames_per_cycle);
|
||||
}
|
||||
|
||||
ExportChannelPtr
|
||||
RegionExportChannelFactory::create (uint32_t channel)
|
||||
{
|
||||
assert (channel < n_channels);
|
||||
return ExportChannelPtr (new RegionExportChannel (*this, channel));
|
||||
}
|
||||
|
||||
void
|
||||
RegionExportChannelFactory::read (uint32_t channel, Sample * data, nframes_t frames_to_read)
|
||||
{
|
||||
assert (channel < n_channels);
|
||||
assert (frames_to_read <= frames_per_cycle);
|
||||
|
||||
if (!buffers_up_to_date) {
|
||||
update_buffers(frames_to_read);
|
||||
buffers_up_to_date = true;
|
||||
}
|
||||
|
||||
memcpy (data, buffers.get_audio (channel).data(), frames_to_read * sizeof (Sample));
|
||||
}
|
||||
|
||||
void
|
||||
RegionExportChannelFactory::update_buffers (nframes_t frames)
|
||||
{
|
||||
switch (type) {
|
||||
case Raw:
|
||||
for (size_t channel = 0; channel < n_channels; ++channel) {
|
||||
region.read (buffers.get_audio (channel).data(), position - region_start, frames, channel);
|
||||
}
|
||||
break;
|
||||
case Processed:
|
||||
std::cout << "exporting " << frames << " frames from position " << position << std::endl;
|
||||
track.export_stuff (buffers, position, frames);
|
||||
break;
|
||||
default:
|
||||
throw ExportFailed ("Unhandled type in ExportChannelFactory::update_buffers");
|
||||
}
|
||||
|
||||
position += frames;
|
||||
}
|
@ -37,24 +37,6 @@
|
||||
namespace ARDOUR
|
||||
{
|
||||
|
||||
/* ExportChannel */
|
||||
|
||||
void
|
||||
ExportChannel::read_ports (float * data, nframes_t frames) const
|
||||
{
|
||||
memset (data, 0, frames * sizeof (float));
|
||||
|
||||
for (iterator it = begin(); it != end(); ++it) {
|
||||
if (*it != 0) {
|
||||
Sample* port_buffer = (*it)->get_audio_buffer( frames, 0).data();
|
||||
|
||||
for (uint32_t i = 0; i < frames; ++i) {
|
||||
data[i] += (float) port_buffer[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ExportChannelConfiguration */
|
||||
|
||||
ExportChannelConfiguration::ExportChannelConfiguration (Session & session) :
|
||||
@ -73,7 +55,6 @@ ExportChannelConfiguration::get_state ()
|
||||
{
|
||||
XMLNode * root = new XMLNode ("ExportChannelConfiguration");
|
||||
XMLNode * channel;
|
||||
XMLNode * port_node;
|
||||
|
||||
root->add_property ("split", get_split() ? "true" : "false");
|
||||
root->add_property ("channels", to_string (get_n_chans(), std::dec));
|
||||
@ -84,12 +65,7 @@ ExportChannelConfiguration::get_state ()
|
||||
if (!channel) { continue; }
|
||||
|
||||
channel->add_property ("number", to_string (i, std::dec));
|
||||
|
||||
for (ExportChannel::const_iterator p_it = (*c_it)->begin(); p_it != (*c_it)->end(); ++p_it) {
|
||||
if ((port_node = channel->add_child ("Port"))) {
|
||||
port_node->add_property ("name", (*p_it)->name());
|
||||
}
|
||||
}
|
||||
(*c_it)->get_state (channel);
|
||||
|
||||
++i;
|
||||
}
|
||||
@ -108,15 +84,8 @@ ExportChannelConfiguration::set_state (const XMLNode & root)
|
||||
|
||||
XMLNodeList channels = root.children ("Channel");
|
||||
for (XMLNodeList::iterator it = channels.begin(); it != channels.end(); ++it) {
|
||||
boost::shared_ptr<ExportChannel> channel (new ExportChannel ());
|
||||
|
||||
XMLNodeList ports = (*it)->children ("Port");
|
||||
for (XMLNodeList::iterator p_it = ports.begin(); p_it != ports.end(); ++p_it) {
|
||||
if ((prop = (*p_it)->property ("name"))) {
|
||||
channel->add_port (dynamic_cast<AudioPort *> (session.engine().get_port_by_name (prop->value())));
|
||||
}
|
||||
}
|
||||
|
||||
ExportChannelPtr channel (new PortExportChannel ());
|
||||
channel->set_state (*it, session);
|
||||
register_channel (channel);
|
||||
}
|
||||
|
||||
@ -124,9 +93,9 @@ ExportChannelConfiguration::set_state (const XMLNode & root)
|
||||
}
|
||||
|
||||
bool
|
||||
ExportChannelConfiguration::all_channels_have_ports ()
|
||||
ExportChannelConfiguration::all_channels_have_ports () const
|
||||
{
|
||||
for (ChannelList::iterator it = channels.begin(); it != channels.end(); ++it) {
|
||||
for (ChannelList::const_iterator it = channels.begin(); it != channels.end(); ++it) {
|
||||
if ((*it)->empty ()) { return false; }
|
||||
}
|
||||
|
||||
@ -182,7 +151,7 @@ ExportChannelConfiguration::write_file ()
|
||||
|
||||
/* Get channel data */
|
||||
|
||||
frames_read = timespan->get_data (channel_buffer, frames, **it);
|
||||
frames_read = timespan->get_data (channel_buffer, frames, *it);
|
||||
|
||||
/* Interleave into file buffer */
|
||||
|
||||
@ -238,7 +207,7 @@ ExportChannelConfiguration::register_with_timespan (TimespanPtr new_timespan)
|
||||
timespan = new_timespan;
|
||||
|
||||
for (ChannelList::iterator it = channels.begin(); it != channels.end(); ++it) {
|
||||
timespan->register_channel (**it);
|
||||
timespan->register_channel (*it);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,7 @@ ExportProfileManager::ExportProfileManager (Session & s) :
|
||||
|
||||
session_range (new Location ()),
|
||||
ranges (new LocationList ()),
|
||||
single_range_mode (false),
|
||||
|
||||
format_list (new FormatList ())
|
||||
{
|
||||
@ -299,6 +300,20 @@ ExportProfileManager::set_selection_range (nframes_t start, nframes_t end)
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
ExportProfileManager::set_single_range (nframes_t start, nframes_t end, Glib::ustring name)
|
||||
{
|
||||
single_range_mode = true;
|
||||
|
||||
single_range.reset (new Location());
|
||||
single_range->set_name (name);
|
||||
single_range->set (start, end);
|
||||
|
||||
update_ranges ();
|
||||
|
||||
return single_range->id().to_s();
|
||||
}
|
||||
|
||||
bool
|
||||
ExportProfileManager::init_timespans (XMLNodeList nodes)
|
||||
{
|
||||
@ -377,6 +392,11 @@ ExportProfileManager::serialize_timespan (TimespanStatePtr state)
|
||||
void
|
||||
ExportProfileManager::update_ranges () {
|
||||
ranges->clear();
|
||||
|
||||
if (single_range_mode) {
|
||||
ranges->push_back (single_range.get());
|
||||
return;
|
||||
}
|
||||
|
||||
/* Session */
|
||||
|
||||
|
@ -42,7 +42,7 @@ ExportTimespan::~ExportTimespan ()
|
||||
}
|
||||
|
||||
void
|
||||
ExportTimespan::register_channel (ExportChannel const & channel)
|
||||
ExportTimespan::register_channel (ExportChannelPtr channel)
|
||||
{
|
||||
TempFilePtr ptr (new ExportTempFile (1, frame_rate));
|
||||
ChannelFilePair pair (channel, ptr);
|
||||
@ -58,7 +58,7 @@ ExportTimespan::rewind ()
|
||||
}
|
||||
|
||||
nframes_t
|
||||
ExportTimespan::get_data (float * data, nframes_t frames, ExportChannel const & channel)
|
||||
ExportTimespan::get_data (float * data, nframes_t frames, ExportChannelPtr channel)
|
||||
{
|
||||
TempFileMap::iterator it = filemap.find (channel);
|
||||
if (it == filemap.end()) {
|
||||
@ -100,7 +100,7 @@ ExportTimespan::process (nframes_t frames)
|
||||
float * data = new float[frames_to_read];
|
||||
|
||||
for (TempFileMap::iterator it = filemap.begin(); it != filemap.end(); ++it) {
|
||||
it->first.read_ports (data, frames_to_read);
|
||||
it->first->read (data, frames_to_read);
|
||||
it->second->write (data, frames_to_read);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user