2 Copyright (C) 2005-2006 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include <sys/param.h>
28 #include <gtkmm/box.h>
29 #include <gtkmm/stock.h>
30 #include <glibmm/fileutils.h>
32 #include <pbd/convert.h>
33 #include <pbd/tokenizer.h>
35 #include <gtkmm2ext/utils.h>
37 #include <ardour/audio_library.h>
38 #include <ardour/audioregion.h>
39 #include <ardour/audiofilesource.h>
40 #include <ardour/region_factory.h>
41 #include <ardour/source_factory.h>
42 #include <ardour/profile.h>
44 #include "ardour_ui.h"
46 #include "gui_thread.h"
54 using namespace ARDOUR;
58 using namespace Gtkmm2ext;
59 using namespace Editing;
61 Glib::ustring SoundFileBrowser::persistent_folder;
63 SoundFileBox::SoundFileBox ()
68 length_clock ("sfboxLengthClock", true, "EditCursorClock", false, true, false),
69 timecode_clock ("sfboxTimecodeClock", true, "EditCursorClock", false, false, false),
72 stop_btn (Stock::MEDIA_STOP)
74 set_name (X_("SoundFileBox"));
76 // set_size_request (250, 250);
78 preview_label.set_markup (_("<b>Soundfile Info</b>"));
80 border_frame.set_label_widget (preview_label);
81 border_frame.add (main_box);
83 pack_start (border_frame, true, true);
86 main_box.set_border_width (6);
88 length.set_text (_("Length:"));
89 timecode.set_text (_("Timestamp:"));
90 format.set_text (_("Format:"));
91 channels.set_text (_("Channels:"));
92 samplerate.set_text (_("Sample rate:"));
94 format_text.set_editable (false);
96 table.set_col_spacings (6);
97 table.set_homogeneous (false);
99 table.attach (channels, 0, 1, 0, 1, FILL|EXPAND, (AttachOptions) 0);
100 table.attach (samplerate, 0, 1, 1, 2, FILL|EXPAND, (AttachOptions) 0);
101 table.attach (format, 0, 1, 2, 4, FILL|EXPAND, (AttachOptions) 0);
102 table.attach (length, 0, 1, 4, 5, FILL|EXPAND, (AttachOptions) 0);
103 table.attach (timecode, 0, 1, 5, 6, FILL|EXPAND, (AttachOptions) 0);
105 table.attach (channels_value, 1, 2, 0, 1, FILL, (AttachOptions) 0);
106 table.attach (samplerate_value, 1, 2, 1, 2, FILL, (AttachOptions) 0);
107 table.attach (format_text, 1, 2, 2, 4, FILL, AttachOptions (0));
108 table.attach (length_clock, 1, 2, 4, 5, FILL, (AttachOptions) 0);
109 table.attach (timecode_clock, 1, 2, 5, 6, FILL, (AttachOptions) 0);
111 length_clock.set_mode (AudioClock::MinSec);
112 timecode_clock.set_mode (AudioClock::SMPTE);
114 tags_entry.set_editable (true);
115 tags_entry.signal_focus_out_event().connect (mem_fun (*this, &SoundFileBox::tags_entry_left));
116 HBox* hbox = manage (new HBox);
117 hbox->pack_start (tags_entry, true, true);
119 main_box.pack_start (table, false, false);
121 VBox* vbox = manage (new VBox);
123 Label* label = manage (new Label (_("Tags:")));
124 label->set_alignment (0.0f, 0.5f);
125 vbox->set_spacing (6);
126 vbox->pack_start(*label, false, false);
127 vbox->pack_start(*hbox, true, true);
129 main_box.pack_start(*vbox, true, true);
130 main_box.pack_start(bottom_box, false, false);
132 play_btn.set_image (*(manage (new Image (Stock::MEDIA_PLAY, ICON_SIZE_BUTTON))));
133 play_btn.set_label (_("Play (double click)"));
135 bottom_box.set_homogeneous(true);
136 bottom_box.pack_start(play_btn);
137 bottom_box.pack_start(stop_btn);
139 play_btn.signal_clicked().connect (mem_fun (*this, &SoundFileBox::audition));
140 stop_btn.signal_clicked().connect (mem_fun (*this, &SoundFileBox::stop_btn_clicked));
142 length.set_alignment (0.0f, 0.5f);
143 format.set_alignment (0.0f, 0.5f);
144 channels.set_alignment (0.0f, 0.5f);
145 samplerate.set_alignment (0.0f, 0.5f);
146 timecode.set_alignment (0.0f, 0.5f);
148 channels_value.set_alignment (0.0f, 0.5f);
149 samplerate_value.set_alignment (0.0f, 0.5f);
151 stop_btn.set_no_show_all (true);
156 SoundFileBox::set_session(Session* s)
158 audition_connection.disconnect ();
163 play_btn.set_sensitive(false);
165 audition_connection = _session->AuditionActive.connect(mem_fun (*this, &SoundFileBox::audition_status_changed));
168 length_clock.set_session (s);
169 timecode_clock.set_session (s);
173 SoundFileBox::setup_labels (const Glib::ustring& filename)
179 if(!AudioFileSource::get_soundfile_info (filename, sf_info, error_msg)) {
181 preview_label.set_markup (_("<b>Soundfile Info</b>"));
182 format_text.get_buffer()->set_text (_("n/a"));
183 channels_value.set_text (_("n/a"));
184 samplerate_value.set_text (_("n/a"));
185 tags_entry.get_buffer()->set_text ("");
187 length_clock.set (0);
188 timecode_clock.set (0);
190 tags_entry.set_sensitive (false);
191 play_btn.set_sensitive (false);
196 preview_label.set_markup (string_compose ("<b>%1</b>", Glib::path_get_basename (filename)));
197 format_text.get_buffer()->set_text (sf_info.format_name);
198 channels_value.set_text (to_string (sf_info.channels, std::dec));
199 samplerate_value.set_text (string_compose (X_("%1 Hz"), sf_info.samplerate));
201 length_clock.set (sf_info.length, true);
202 timecode_clock.set (sf_info.timecode, true);
204 // this is a hack that is fixed in trunk, i think (august 26th, 2007)
206 vector<string> tags = Library->get_tags (string ("//") + filename);
208 stringstream tag_string;
209 for (vector<string>::iterator i = tags.begin(); i != tags.end(); ++i) {
210 if (i != tags.begin()) {
215 tags_entry.get_buffer()->set_text (tag_string.str());
217 tags_entry.set_sensitive (true);
219 play_btn.set_sensitive (true);
226 SoundFileBox::audition ()
232 _session->cancel_audition();
234 if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)) {
235 warning << string_compose(_("Could not read file: %1 (%2)."), path, strerror(errno)) << endmsg;
239 typedef std::map<Glib::ustring, boost::shared_ptr<AudioRegion> > RegionCache;
240 static RegionCache region_cache;
241 RegionCache::iterator the_region;
243 if ((the_region = region_cache.find (path)) == region_cache.end()) {
246 boost::shared_ptr<AudioFileSource> afs;
248 for (int n = 0; n < sf_info.channels; ++n) {
250 afs = boost::dynamic_pointer_cast<AudioFileSource> (SourceFactory::createReadable (*_session, path, n, AudioFileSource::Flag (0)));
251 srclist.push_back(afs);
253 } catch (failed_constructor& err) {
254 error << _("Could not access soundfile: ") << path << endmsg;
259 if (srclist.empty()) {
265 _session->region_name (rname, Glib::path_get_basename(srclist[0]->name()), false);
267 pair<string,boost::shared_ptr<AudioRegion> > newpair;
268 pair<RegionCache::iterator,bool> res;
270 newpair.first = path;
271 newpair.second = boost::dynamic_pointer_cast<AudioRegion> (RegionFactory::create (srclist, 0, srclist[0]->length(), rname, 0, Region::DefaultFlags, false));
273 res = region_cache.insert (newpair);
274 the_region = res.first;
280 boost::shared_ptr<Region> r = boost::static_pointer_cast<Region> (the_region->second);
282 _session->audition_region(r);
286 SoundFileBox::stop_btn_clicked ()
289 _session->cancel_audition();
296 SoundFileBox::tags_entry_left (GdkEventFocus *ev)
303 SoundFileBox::tags_changed ()
305 string tag_string = tags_entry.get_buffer()->get_text ();
307 if (tag_string.empty()) {
313 if (!PBD::tokenize (tag_string, string(",\n"), std::back_inserter (tags), true)) {
314 warning << _("SoundFileBox: Could not tokenize string: ") << tag_string << endmsg;
318 cerr << "save new tags: ";
319 for (vector<string>::iterator x = tags.begin(); x != tags.end(); ++x) {
324 Library->set_tags (string ("//") + path, tags);
325 Library->save_changes ();
329 SoundFileBox::audition_status_changed (bool active)
331 ENSURE_GUI_THREAD(bind (mem_fun (*this, &SoundFileBox::audition_status_changed), active));
338 SoundFileBrowser::SoundFileBrowser (Gtk::Window& parent, string title, ARDOUR::Session* s, int selected_tracks)
339 : ArdourDialog (parent, title, false, false),
340 found_list (ListStore::create(found_list_columns)),
341 chooser (FILE_CHOOSER_ACTION_OPEN),
342 found_list_view (found_list),
343 import (rgroup2, _("Copy to Ardour-native files")),
344 embed (rgroup2, _("Use file without copying")),
345 found_search_btn (_("Search")),
346 selected_track_cnt (selected_tracks)
353 resetting_ourselves = false;
355 hpacker = manage (new HBox);
356 hpacker->set_spacing (6);
357 hpacker->pack_start (notebook, true, true);
358 hpacker->pack_start (preview, false, false);
360 block_two.set_border_width (12);
361 block_three.set_border_width (12);
362 block_four.set_border_width (12);
364 options.set_spacing (12);
366 vector<string> action_strings;
367 vector<string> where_strings;
369 action_strings.push_back (_("as new tracks"));
370 if (selected_track_cnt > 0) {
371 action_strings.push_back (_("to selected tracks"));
373 action_strings.push_back (_("to the region list"));
374 action_strings.push_back (_("as new tape tracks"));
375 set_popdown_strings (action_combo, action_strings);
376 action_combo.set_active_text (action_strings.front());
378 where_strings.push_back (_("use file timestamp"));
379 where_strings.push_back (_("at edit cursor"));
380 where_strings.push_back (_("at playhead"));
381 where_strings.push_back (_("at session start"));
382 set_popdown_strings (where_combo, where_strings);
383 where_combo.set_active_text (where_strings.front());
385 Label* l = manage (new Label);
386 l->set_text (_("Add files:"));
388 hbox = manage (new HBox);
389 hbox->set_border_width (12);
390 hbox->set_spacing (6);
391 hbox->pack_start (*l, false, false);
392 hbox->pack_start (action_combo, false, false);
393 vbox = manage (new VBox);
394 vbox->pack_start (*hbox, false, false);
395 options.pack_start (*vbox, false, false);
397 l = manage (new Label);
398 l->set_text (_("Insert:"));
400 hbox = manage (new HBox);
401 hbox->set_border_width (12);
402 hbox->set_spacing (6);
403 hbox->pack_start (*l, false, false);
404 hbox->pack_start (where_combo, false, false);
405 vbox = manage (new VBox);
406 vbox->pack_start (*hbox, false, false);
407 options.pack_start (*vbox, false, false);
410 l = manage (new Label);
411 l->set_text (_("Mapping:"));
413 hbox = manage (new HBox);
414 hbox->set_border_width (12);
415 hbox->set_spacing (6);
416 hbox->pack_start (*l, false, false);
417 hbox->pack_start (channel_combo, false, false);
418 vbox = manage (new VBox);
419 vbox->pack_start (*hbox, false, false);
420 options.pack_start (*vbox, false, false);
424 action_combo.signal_changed().connect (mem_fun (*this, &SoundFileBrowser::reset_options_noret));
426 block_four.pack_start (import, false, false);
427 block_four.pack_start (embed, false, false);
429 options.pack_start (block_four, false, false);
431 get_vbox()->pack_start (*hpacker, true, true);
432 get_vbox()->pack_start (options, false, false);
434 hbox = manage(new HBox);
435 hbox->pack_start (found_entry);
436 hbox->pack_start (found_search_btn);
438 vbox = manage(new VBox);
439 vbox->pack_start (*hbox, PACK_SHRINK);
440 vbox->pack_start (found_list_view);
441 found_list_view.append_column(_("Paths"), found_list_columns.pathname);
443 chooser.set_border_width (12);
445 notebook.append_page (chooser, _("Browse Files"));
446 notebook.append_page (*vbox, _("Search Tags"));
448 found_list_view.get_selection()->set_mode (SELECTION_MULTIPLE);
449 found_list_view.signal_row_activated().connect (mem_fun (*this, &SoundFileBrowser::found_list_view_activated));
451 custom_filter.add_custom (FILE_FILTER_FILENAME, mem_fun(*this, &SoundFileBrowser::on_custom));
452 custom_filter.set_name (_("Audio files"));
454 matchall_filter.add_pattern ("*.*");
455 matchall_filter.set_name (_("All files"));
457 chooser.add_filter (custom_filter);
458 chooser.add_filter (matchall_filter);
459 chooser.set_select_multiple (true);
460 chooser.signal_update_preview().connect(mem_fun(*this, &SoundFileBrowser::update_preview));
461 chooser.signal_selection_changed().connect (mem_fun (*this, &SoundFileBrowser::file_selection_changed));
462 chooser.signal_file_activated().connect (mem_fun (*this, &SoundFileBrowser::chooser_file_activated));
464 if (!persistent_folder.empty()) {
465 chooser.set_current_folder (persistent_folder);
468 found_list_view.get_selection()->signal_changed().connect(mem_fun(*this, &SoundFileBrowser::found_list_view_selected));
470 found_search_btn.signal_clicked().connect(mem_fun(*this, &SoundFileBrowser::found_search_clicked));
471 found_entry.signal_activate().connect(mem_fun(*this, &SoundFileBrowser::found_search_clicked));
473 add_button (Stock::CANCEL, RESPONSE_CANCEL);
474 add_button (Stock::OK, RESPONSE_OK);
476 /* setup disposition map */
478 disposition_map.insert (pair<Glib::ustring,ImportChannel>(_("one track per file"), ImportDistinctFiles));
479 disposition_map.insert (pair<Glib::ustring,ImportChannel>(_("one track per channel"), ImportDistinctChannels));
480 disposition_map.insert (pair<Glib::ustring,ImportChannel>(_("merge files"), ImportMergeFiles));
481 disposition_map.insert (pair<Glib::ustring,ImportChannel>(_("sequence files"), ImportSerializeFiles));
483 disposition_map.insert (pair<Glib::ustring,ImportChannel>(_("one region per file"), ImportDistinctFiles));
484 disposition_map.insert (pair<Glib::ustring,ImportChannel>(_("one region per channel"), ImportDistinctChannels));
485 disposition_map.insert (pair<Glib::ustring,ImportChannel>(_("all files in one region"), ImportMergeFiles));
488 SoundFileBrowser::~SoundFileBrowser ()
490 persistent_folder = chooser.get_current_folder();
494 SoundFileBrowser::file_selection_changed ()
496 if (resetting_ourselves) {
500 if (!reset_options ()) {
501 set_response_sensitive (RESPONSE_OK, false);
503 if (chooser.get_filenames().size() > 0) {
504 set_response_sensitive (RESPONSE_OK, true);
506 set_response_sensitive (RESPONSE_OK, false);
512 SoundFileBrowser::chooser_file_activated ()
518 SoundFileBrowser::found_list_view_activated (const TreeModel::Path& path, TreeViewColumn* col)
524 SoundFileBrowser::set_session (Session* s)
526 ArdourDialog::set_session (s);
527 preview.set_session (s);
532 SoundFileBrowser::on_custom (const FileFilter::Info& filter_info)
534 return AudioFileSource::safe_file_extension (filter_info.filename);
538 SoundFileBrowser::update_preview ()
540 preview.setup_labels (chooser.get_filename());
544 SoundFileBrowser::found_list_view_selected ()
546 if (!reset_options ()) {
547 set_response_sensitive (RESPONSE_OK, false);
551 TreeView::Selection::ListHandle_Path rows = found_list_view.get_selection()->get_selected_rows ();
554 TreeIter iter = found_list->get_iter(*rows.begin());
555 file = (*iter)[found_list_columns.pathname];
556 chooser.set_filename (file);
557 set_response_sensitive (RESPONSE_OK, true);
559 set_response_sensitive (RESPONSE_OK, false);
562 preview.setup_labels (file);
567 SoundFileBrowser::found_search_clicked ()
569 string tag_string = found_entry.get_text ();
573 if (!PBD::tokenize (tag_string, string(","), std::back_inserter (tags), true)) {
574 warning << _("SoundFileBrowser: Could not tokenize string: ") << tag_string << endmsg;
578 vector<string> results;
579 Library->search_members_and (results, tags);
582 for (vector<string>::iterator i = results.begin(); i != results.end(); ++i) {
583 TreeModel::iterator new_row = found_list->append();
584 TreeModel::Row row = *new_row;
585 string path = Glib::filename_from_uri (string ("file:") + *i);
586 row[found_list_columns.pathname] = path;
590 vector<Glib::ustring>
591 SoundFileBrowser::get_paths ()
593 vector<Glib::ustring> results;
595 int n = notebook.get_current_page ();
598 vector<Glib::ustring> filenames = chooser.get_filenames();
599 vector<Glib::ustring>::iterator i;
600 for (i = filenames.begin(); i != filenames.end(); ++i) {
602 if ((!stat((*i).c_str(), &buf)) && S_ISREG(buf.st_mode)) {
603 results.push_back (*i);
610 typedef TreeView::Selection::ListHandle_Path ListPath;
612 ListPath rows = found_list_view.get_selection()->get_selected_rows ();
613 for (ListPath::iterator i = rows.begin() ; i != rows.end(); ++i) {
614 TreeIter iter = found_list->get_iter(*i);
615 Glib::ustring str = (*iter)[found_list_columns.pathname];
617 results.push_back (str);
624 SoundFileBrowser::reset_options_noret ()
626 (void) reset_options ();
630 SoundFileBrowser::reset_options ()
632 vector<Glib::ustring> paths = get_paths ();
636 channel_combo.set_sensitive (false);
637 action_combo.set_sensitive (false);
638 where_combo.set_sensitive (false);
639 import.set_sensitive (false);
640 embed.set_sensitive (false);
646 channel_combo.set_sensitive (true);
647 action_combo.set_sensitive (true);
648 where_combo.set_sensitive (true);
649 import.set_sensitive (true);
650 embed.set_sensitive (true);
656 bool selection_includes_multichannel = check_multichannel_status (paths, same_size, err);
657 bool selection_can_be_embedded_with_links = check_link_status (*session, paths);
661 Glib::signal_idle().connect (mem_fun (*this, &SoundFileBrowser::bad_file_message));
665 if ((mode = get_mode()) == ImportAsRegion) {
666 where_combo.set_sensitive (false);
668 where_combo.set_sensitive (true);
671 vector<string> channel_strings;
673 if (mode == ImportAsTrack || mode == ImportAsTapeTrack || mode == ImportToTrack) {
674 channel_strings.push_back (_("one track per file"));
676 if (selection_includes_multichannel) {
677 channel_strings.push_back (_("one track per channel"));
680 if (paths.size() > 1) {
681 /* tape tracks are a single region per track, so we cannot
682 sequence multiple files.
684 if (mode != ImportAsTapeTrack) {
685 channel_strings.push_back (_("sequence files"));
688 channel_strings.push_back (_("all files in one region"));
694 channel_strings.push_back (_("one region per file"));
696 if (selection_includes_multichannel) {
697 channel_strings.push_back (_("one region per channel"));
700 if (paths.size() > 1) {
702 channel_strings.push_back (_("all files in one region"));
707 set_popdown_strings (channel_combo, channel_strings);
708 channel_combo.set_active_text (channel_strings.front());
710 if (Profile->get_sae()) {
711 if (selection_can_be_embedded_with_links) {
712 embed.set_sensitive (true);
714 embed.set_sensitive (false);
717 embed.set_sensitive (true);
725 SoundFileBrowser::bad_file_message()
727 MessageDialog msg (*this,
728 _("One or more of the selected files\ncannot be used by Ardour"),
733 resetting_ourselves = true;
734 chooser.unselect_uri (chooser.get_preview_uri());
735 resetting_ourselves = false;
739 SoundFileBrowser::check_multichannel_status (const vector<Glib::ustring>& paths, bool& same_size, bool& err)
744 bool some_mult = false;
749 for (vector<Glib::ustring>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
751 info.format = 0; // libsndfile says to clear this before sf_open().
753 if ((sf = sf_open ((char*) (*i).c_str(), SFM_READ, &info)) != 0) {
756 if (info.channels > 1) {
763 if (sz != info.frames) {
776 SoundFileBrowser::check_link_status (const Session& s, const vector<Glib::ustring>& paths)
778 string tmpdir = s.sound_dir();
781 tmpdir += "/linktest";
783 if (mkdir (tmpdir.c_str(), 0744)) {
784 if (errno != EEXIST) {
789 for (vector<Glib::ustring>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
791 char tmpc[MAXPATHLEN+1];
793 snprintf (tmpc, sizeof(tmpc), "%s/%s", tmpdir.c_str(), Glib::path_get_basename (*i).c_str());
797 if (link ((*i).c_str(), tmpc)) {
807 rmdir (tmpdir.c_str());
811 SoundFileChooser::SoundFileChooser (Gtk::Window& parent, string title, ARDOUR::Session* s)
812 : SoundFileBrowser (parent, title, s, 0)
814 set_default_size (700, 300);
816 // get_vbox()->pack_start (browser, false, false);
818 // add_button (Stock::OPEN, RESPONSE_OK);
819 // add_button (Stock::CANCEL, RESPONSE_CANCEL);
821 // chooser.set_select_multiple (false);
822 // browser.found_list_view.get_selection()->set_mode (SELECTION_SINGLE);
828 SoundFileChooser::get_filename ()
830 vector<Glib::ustring> paths;
832 paths = browser.get_paths ();
835 return Glib::ustring ();
838 if (!Glib::file_test (paths.front(), Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
839 return Glib::ustring();
842 return paths.front();
846 SoundFileBrowser::get_mode () const
848 Glib::ustring str = action_combo.get_active_text();
850 if (str == _("as new tracks")) {
851 return ImportAsTrack;
852 } else if (str == _("to the region list")) {
853 return ImportAsRegion;
854 } else if (str == _("to selected tracks")) {
855 return ImportToTrack;
857 return ImportAsTapeTrack;
862 SoundFileBrowser::get_position() const
864 Glib::ustring str = where_combo.get_active_text();
866 if (str == _("use file timestamp")) {
867 return ImportAtTimestamp;
868 } else if (str == _("at edit cursor")) {
869 return ImportAtEditCursor;
870 } else if (str == _("at playhead")) {
871 return ImportAtPlayhead;
873 return ImportAtStart;
878 SoundFileBrowser::get_channel_disposition () const
880 /* we use a map here because the channel combo can contain different strings
881 depending on the state of the other combos. the map contains all possible strings
882 and the ImportDisposition enum that corresponds to it.
885 Glib::ustring str = channel_combo.get_active_text();
886 DispositionMap::const_iterator x = disposition_map.find (str);
888 if (x == disposition_map.end()) {
889 fatal << string_compose (_("programming error: %1"), "unknown string for import disposition") << endmsg;