521f72747fcb32ee687b2b552b4ed2ab0bf2dc9c
[ardour.git] / libs / ardour / export_profile_manager.cc
1 /*
2     Copyright (C) 2008 Paul Davis
3     Author: Sakari Bergen
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <cassert>
22 #include <stdexcept>
23
24 #include <glibmm/fileutils.h>
25
26 #include "pbd/enumwriter.h"
27 #include "pbd/xml++.h"
28 #include "pbd/convert.h"
29
30 #include "ardour/export_profile_manager.h"
31 #include "ardour/export_format_specification.h"
32 #include "ardour/export_timespan.h"
33 #include "ardour/export_channel_configuration.h"
34 #include "ardour/export_filename.h"
35 #include "ardour/export_preset.h"
36 #include "ardour/export_handler.h"
37 #include "ardour/export_failed.h"
38 #include "ardour/filename_extensions.h"
39 #include "ardour/route.h"
40 #include "ardour/session.h"
41
42 #include "i18n.h"
43
44 using namespace std;
45 using namespace Glib;
46 using namespace PBD;
47
48 namespace ARDOUR
49 {
50
51 ExportProfileManager::ExportProfileManager (Session & s) :
52   handler (s.get_export_handler()),
53   session (s),
54
55   session_range (new Location (s)),
56   ranges (new LocationList ()),
57   single_range_mode (false),
58
59   format_list (new FormatList ())
60 {
61
62         /* Initialize path variables */
63
64         export_config_dir = user_config_directory();
65         export_config_dir /= "export";
66         search_path += export_config_dir;
67
68         search_path += ardour_search_path().add_subdirectory_to_paths("export");
69         search_path += system_config_search_path().add_subdirectory_to_paths("export");;
70
71         /* create export config directory if necessary */
72
73         if (!sys::exists (export_config_dir)) {
74                 sys::create_directory (export_config_dir);
75         }
76
77         load_presets ();
78         load_formats ();
79
80         /* Initialize all lists with an empty config */
81
82         XMLNodeList dummy;
83         init_timespans (dummy);
84         init_channel_configs (dummy);
85         init_formats (dummy);
86         init_filenames (dummy);
87 }
88
89 ExportProfileManager::~ExportProfileManager ()
90 {
91         if (single_range_mode) { return; }
92
93         XMLNode * instant_xml (new XMLNode ("ExportProfile"));
94         serialize_profile (*instant_xml);
95         session.add_instant_xml (*instant_xml, false);
96 }
97
98 void
99 ExportProfileManager::load_profile ()
100 {
101         XMLNode * instant_node = session.instant_xml ("ExportProfile");
102         if (instant_node) {
103                 set_state (*instant_node);
104         } else {
105                 XMLNode empty_node ("ExportProfile");
106                 set_state (empty_node);
107         }
108 }
109
110 void
111 ExportProfileManager::prepare_for_export ()
112 {
113         ChannelConfigPtr channel_config = channel_configs.front()->config;
114         TimespanListPtr ts_list = timespans.front()->timespans;
115
116         FormatStateList::const_iterator format_it;
117         FilenameStateList::const_iterator filename_it;
118
119         for (TimespanList::iterator ts_it = ts_list->begin(); ts_it != ts_list->end(); ++ts_it) {
120                 for (format_it = formats.begin(), filename_it = filenames.begin();
121                      format_it != formats.end() && filename_it != filenames.end();
122                      ++format_it, ++filename_it) {
123
124 //                      filename->include_timespan = (ts_list->size() > 1); Disabled for now...
125                         handler->add_export_config (*ts_it, channel_config, (*format_it)->format, (*filename_it)->filename);
126                 }
127         }
128 }
129
130 bool
131 ExportProfileManager::load_preset (PresetPtr preset)
132 {
133         bool ok = true;
134
135         current_preset = preset;
136         if (!preset) { return false; }
137
138         XMLNode const * state;
139         if ((state = preset->get_local_state())) {
140                 set_local_state (*state);
141         } else { ok = false; }
142
143         if ((state = preset->get_global_state())) {
144                 if (!set_global_state (*state)) {
145                         ok = false;
146                 }
147         } else { ok = false; }
148
149         return ok;
150 }
151
152 void
153 ExportProfileManager::load_presets ()
154 {
155         vector<sys::path> found = find_file (string_compose (X_("*%1"),export_preset_suffix));
156
157         for (vector<sys::path>::iterator it = found.begin(); it != found.end(); ++it) {
158                 load_preset_from_disk (*it);
159         }
160 }
161
162 ExportProfileManager::PresetPtr
163 ExportProfileManager::save_preset (string const & name)
164 {
165         string filename = export_config_dir.to_string() + "/" + name + export_preset_suffix;
166
167         if (!current_preset) {
168                 current_preset.reset (new ExportPreset (filename, session));
169                 preset_list.push_back (current_preset);
170         }
171
172         XMLNode * global_preset = new XMLNode ("ExportPreset");
173         XMLNode * local_preset = new XMLNode ("ExportPreset");
174
175         serialize_global_profile (*global_preset);
176         serialize_local_profile (*local_preset);
177
178         current_preset->set_name (name);
179         current_preset->set_global_state (*global_preset);
180         current_preset->set_local_state (*local_preset);
181
182         current_preset->save (filename);
183
184         return current_preset;
185 }
186
187 void
188 ExportProfileManager::remove_preset ()
189 {
190         if (!current_preset) { return; }
191
192         for (PresetList::iterator it = preset_list.begin(); it != preset_list.end(); ++it) {
193                 if (*it == current_preset) {
194                         preset_list.erase (it);
195                         break;
196                 }
197         }
198
199         FileMap::iterator it = preset_file_map.find (current_preset->id());
200         if (it != preset_file_map.end()) {
201                 sys::remove (it->second);
202                 preset_file_map.erase (it);
203         }
204
205         current_preset->remove_local();
206         current_preset.reset();
207 }
208
209 void
210 ExportProfileManager::load_preset_from_disk (PBD::sys::path const & path)
211 {
212         PresetPtr preset (new ExportPreset (path.to_string(), session));
213
214         /* Handle id to filename mapping and don't add duplicates to list */
215
216         FilePair pair (preset->id(), path);
217         if (preset_file_map.insert (pair).second) {
218                 preset_list.push_back (preset);
219         }
220 }
221
222 bool
223 ExportProfileManager::set_state (XMLNode const & root)
224 {
225         return set_global_state (root) & set_local_state (root);
226 }
227
228 bool
229 ExportProfileManager::set_global_state (XMLNode const & root)
230 {
231         return init_filenames (root.children ("ExportFilename")) &
232                init_formats (root.children ("ExportFormat"));
233 }
234
235 bool
236 ExportProfileManager::set_local_state (XMLNode const & root)
237 {
238         return init_timespans (root.children ("ExportTimespan")) &
239                init_channel_configs (root.children ("ExportChannelConfiguration"));
240 }
241
242 void
243 ExportProfileManager::serialize_profile (XMLNode & root)
244 {
245         serialize_local_profile (root);
246         serialize_global_profile (root);
247 }
248
249 void
250 ExportProfileManager::serialize_global_profile (XMLNode & root)
251 {
252         for (FormatStateList::iterator it = formats.begin(); it != formats.end(); ++it) {
253                 root.add_child_nocopy (serialize_format (*it));
254         }
255
256         for (FilenameStateList::iterator it = filenames.begin(); it != filenames.end(); ++it) {
257                 root.add_child_nocopy ((*it)->filename->get_state());
258         }
259 }
260
261 void
262 ExportProfileManager::serialize_local_profile (XMLNode & root)
263 {
264         for (TimespanStateList::iterator it = timespans.begin(); it != timespans.end(); ++it) {
265                 root.add_child_nocopy (serialize_timespan (*it));
266         }
267
268         for (ChannelConfigStateList::iterator it = channel_configs.begin(); it != channel_configs.end(); ++it) {
269                 root.add_child_nocopy ((*it)->config->get_state());
270         }
271 }
272
273 std::vector<sys::path>
274 ExportProfileManager::find_file (std::string const & pattern)
275 {
276         vector<sys::path> found;
277
278         Glib::PatternSpec pattern_spec (pattern);
279         find_matching_files_in_search_path (search_path, pattern_spec, found);
280
281         return found;
282 }
283
284 void
285 ExportProfileManager::set_selection_range (nframes_t start, nframes_t end)
286 {
287
288         if (start || end) {
289                 selection_range.reset (new Location (session));
290                 selection_range->set_name (_("Selection"));
291                 selection_range->set (start, end);
292         } else {
293                 selection_range.reset();
294         }
295
296         for (TimespanStateList::iterator it = timespans.begin(); it != timespans.end(); ++it) {
297                 (*it)->selection_range = selection_range;
298         }
299 }
300
301 std::string
302 ExportProfileManager::set_single_range (nframes_t start, nframes_t end, Glib::ustring name)
303 {
304         single_range_mode = true;
305
306         single_range.reset (new Location (session));
307         single_range->set_name (name);
308         single_range->set (start, end);
309
310         update_ranges ();
311
312         return single_range->id().to_s();
313 }
314
315 bool
316 ExportProfileManager::init_timespans (XMLNodeList nodes)
317 {
318         timespans.clear ();
319         update_ranges ();
320
321         bool ok = true;
322         for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
323                 TimespanStatePtr span = deserialize_timespan (**it);
324                 if (span) {
325                         timespans.push_back (span);
326                 } else { ok = false; }
327         }
328
329         if (timespans.empty()) {
330                 TimespanStatePtr state (new TimespanState (session_range, selection_range, ranges));
331                 timespans.push_back (state);
332                 
333                 // Add session as default selection
334                 TimespanPtr timespan = handler->add_timespan();
335                 timespan->set_name (session_range->name());
336                 timespan->set_range_id ("session");
337                 timespan->set_range (session_range->start(), session_range->end());
338                 state->timespans->push_back (timespan);
339                 return false;
340         }
341
342         return ok;
343 }
344
345 ExportProfileManager::TimespanStatePtr
346 ExportProfileManager::deserialize_timespan (XMLNode & root)
347 {
348         TimespanStatePtr state (new TimespanState (session_range, selection_range, ranges));
349         XMLProperty const * prop;
350
351         XMLNodeList spans = root.children ("Range");
352         for (XMLNodeList::iterator node_it = spans.begin(); node_it != spans.end(); ++node_it) {
353
354                 prop = (*node_it)->property ("id");
355                 if (!prop) { continue; }
356                 ustring id = prop->value();
357
358                 for (LocationList::iterator it = ranges->begin(); it != ranges->end(); ++it) {
359                         if ((!id.compare ("session") && *it == session_range.get()) ||
360                             (!id.compare ("selection") && *it == selection_range.get()) ||
361                             (!id.compare ((*it)->id().to_s()))) {
362                                 TimespanPtr timespan = handler->add_timespan();
363                                 timespan->set_name ((*it)->name());
364                                 timespan->set_range_id (id);
365                                 timespan->set_range ((*it)->start(), (*it)->end());
366                                 state->timespans->push_back (timespan);
367                         }
368                 }
369         }
370
371         if ((prop = root.property ("format"))) {
372                 state->time_format = (TimeFormat) string_2_enum (prop->value(), TimeFormat);
373         }
374
375         return state;
376 }
377
378 XMLNode &
379 ExportProfileManager::serialize_timespan (TimespanStatePtr state)
380 {
381         XMLNode & root = *(new XMLNode ("ExportTimespan"));
382         XMLNode * span;
383
384         update_ranges ();
385
386         for (TimespanList::iterator it = state->timespans->begin(); it != state->timespans->end(); ++it) {
387                 if ((span = root.add_child ("Range"))) {
388                         span->add_property ("id", (*it)->range_id());
389                 }
390         }
391
392         root.add_property ("format", enum_2_string (state->time_format));
393
394         return root;
395 }
396
397 void
398 ExportProfileManager::update_ranges () {
399         ranges->clear();
400
401         if (single_range_mode) {
402                 ranges->push_back (single_range.get());
403                 return;
404         }
405
406         /* Session */
407
408         session_range->set_name (_("Session"));
409         session_range->set (session.current_start_frame(), session.current_end_frame());
410         ranges->push_back (session_range.get());
411
412         /* Selection */
413
414         if (selection_range) {
415                 ranges->push_back (selection_range.get());
416         }
417
418         /* ranges */
419
420         LocationList const & list (session.locations()->list());
421         for (LocationList::const_iterator it = list.begin(); it != list.end(); ++it) {
422                 if ((*it)->is_range_marker()) {
423                         ranges->push_back (*it);
424                 }
425         }
426 }
427
428 bool
429 ExportProfileManager::init_channel_configs (XMLNodeList nodes)
430 {
431         channel_configs.clear();
432
433         if (nodes.empty()) {
434                 ChannelConfigStatePtr config (new ChannelConfigState (handler->add_channel_config()));
435                 channel_configs.push_back (config);
436                 
437                 // Add master outs as default
438                 IO* master_out = session.master_out()->output().get();
439                 if (!master_out) { return false; }
440                 
441                 for (uint32_t n = 0; n < master_out->n_ports().n_audio(); ++n) {
442                         PortExportChannel * channel = new PortExportChannel ();
443                         channel->add_port (master_out->audio (n));
444                         
445                         ExportChannelPtr chan_ptr (channel);
446                         config->config->register_channel (chan_ptr);
447                 }
448                 return false;
449         }
450
451         for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
452                 ChannelConfigStatePtr config (new ChannelConfigState (handler->add_channel_config()));
453                 config->config->set_state (**it);
454                 channel_configs.push_back (config);
455         }
456
457         return true;
458 }
459
460 ExportProfileManager::FormatStatePtr
461 ExportProfileManager::duplicate_format_state (FormatStatePtr state)
462 {
463         /* Note: The pointer in the new FormatState should point to the same format spec
464                  as the original state's pointer. The spec itself should not be copied!   */
465
466         FormatStatePtr format (new FormatState (format_list, state->format));
467         formats.push_back (format);
468         return format;
469 }
470
471 void
472 ExportProfileManager::remove_format_state (FormatStatePtr state)
473 {
474         for (FormatStateList::iterator it = formats.begin(); it != formats.end(); ++it) {
475                 if (*it == state) {
476                         formats.erase (it);
477                         return;
478                 }
479         }
480 }
481
482 sys::path
483 ExportProfileManager::save_format_to_disk (FormatPtr format)
484 {
485         // TODO filename character stripping
486
487         /* Get filename for file */
488
489         Glib::ustring new_name = format->name();
490         new_name += export_format_suffix;
491
492         sys::path new_path (export_config_dir);
493         new_path /= new_name;
494
495         /* Check if format is on disk already */
496         FileMap::iterator it;
497         if ((it = format_file_map.find (format->id())) != format_file_map.end()) {
498
499                 /* Check if config is not in user config dir */
500                 if (it->second.branch_path().to_string().compare (export_config_dir.to_string())) {
501
502                         /* Write new file */
503
504                         XMLTree tree (new_path.to_string());
505                         tree.set_root (&format->get_state());
506                         tree.write();
507
508                 } else {
509
510                         /* Update file and rename if necessary */
511
512                         XMLTree tree (it->second.to_string());
513                         tree.set_root (&format->get_state());
514                         tree.write();
515
516                         if (new_name.compare (it->second.leaf())) {
517                                 sys::rename (it->second, new_path);
518                         }
519                 }
520
521                 it->second = new_path;
522
523         } else {
524                 /* Write new file */
525
526                 XMLTree tree (new_path.to_string());
527                 tree.set_root (&format->get_state());
528                 tree.write();
529         }
530
531         FormatListChanged ();
532         return new_path;
533 }
534
535 void
536 ExportProfileManager::remove_format_profile (FormatPtr format)
537 {
538         for (FormatList::iterator it = format_list->begin(); it != format_list->end(); ++it) {
539                 if (*it == format) {
540                         format_list->erase (it);
541                         break;
542                 }
543         }
544
545         FileMap::iterator it = format_file_map.find (format->id());
546         if (it != format_file_map.end()) {
547                 sys::remove (it->second);
548                 format_file_map.erase (it);
549         }
550
551         FormatListChanged ();
552 }
553
554 ExportProfileManager::FormatPtr
555 ExportProfileManager::get_new_format (FormatPtr original)
556 {
557         FormatPtr format;
558         if (original) {
559                 format.reset (new ExportFormatSpecification (*original));
560         } else {
561                 format = handler->add_format();
562                 format->set_name ("empty format");
563         }
564
565         sys::path path = save_format_to_disk (format);
566         FilePair pair (format->id(), path);
567         format_file_map.insert (pair);
568
569         format_list->push_back (format);
570         FormatListChanged ();
571
572         return format;
573 }
574
575 bool
576 ExportProfileManager::init_formats (XMLNodeList nodes)
577 {
578         formats.clear();
579
580         bool ok = true;
581         for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
582                 FormatStatePtr format = deserialize_format (**it);
583                 if (format) {
584                         formats.push_back (format);
585                 } else { ok = false; }
586         }
587
588         if (formats.empty ()) {
589                 FormatStatePtr format (new FormatState (format_list, FormatPtr ()));
590                 formats.push_back (format);
591                 return false;
592         }
593
594         return ok;
595 }
596
597 ExportProfileManager::FormatStatePtr
598 ExportProfileManager::deserialize_format (XMLNode & root)
599 {
600         XMLProperty * prop;
601         UUID id;
602
603         if ((prop = root.property ("id"))) {
604                 id = prop->value();
605         }
606
607         for (FormatList::iterator it = format_list->begin(); it != format_list->end(); ++it) {
608                 if ((*it)->id() == id) {
609                         return FormatStatePtr (new FormatState (format_list, *it));
610                 }
611         }
612
613         return FormatStatePtr ();
614 }
615
616 XMLNode &
617 ExportProfileManager::serialize_format (FormatStatePtr state)
618 {
619         XMLNode * root = new XMLNode ("ExportFormat");
620
621         string id = state->format ? state->format->id().to_s() : "";
622         root->add_property ("id", id);
623
624         return *root;
625 }
626
627 void
628 ExportProfileManager::load_formats ()
629 {
630         vector<sys::path> found = find_file (string_compose ("*%1", export_format_suffix));
631
632         for (vector<sys::path>::iterator it = found.begin(); it != found.end(); ++it) {
633                 load_format_from_disk (*it);
634         }
635 }
636
637 void
638 ExportProfileManager::load_format_from_disk (PBD::sys::path const & path)
639 {
640         XMLTree const tree (path.to_string());
641         FormatPtr format = handler->add_format (*tree.root());
642
643         /* Handle id to filename mapping and don't add duplicates to list */
644
645         FilePair pair (format->id(), path);
646         if (format_file_map.insert (pair).second) {
647                 format_list->push_back (format);
648         }
649
650         FormatListChanged ();
651 }
652
653 ExportProfileManager::FilenameStatePtr
654 ExportProfileManager::duplicate_filename_state (FilenameStatePtr state)
655 {
656         FilenameStatePtr filename (new FilenameState (handler->add_filename_copy (state->filename)));
657         filenames.push_back (filename);
658         return filename;
659 }
660
661 void
662 ExportProfileManager::remove_filename_state (FilenameStatePtr state)
663 {
664         for (FilenameStateList::iterator it = filenames.begin(); it != filenames.end(); ++it) {
665                 if (*it == state) {
666                         filenames.erase (it);
667                         return;
668                 }
669         }
670 }
671
672 bool
673 ExportProfileManager::init_filenames (XMLNodeList nodes)
674 {
675         filenames.clear ();
676
677         for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
678                 FilenamePtr filename = handler->add_filename();
679                 filename->set_state (**it);
680                 filenames.push_back (FilenameStatePtr (new FilenameState (filename)));
681         }
682
683         if (filenames.empty()) {
684                 FilenameStatePtr filename (new FilenameState (handler->add_filename()));
685                 filenames.push_back (filename);
686                 return false;
687         }
688
689         return true;
690 }
691
692 boost::shared_ptr<ExportProfileManager::Warnings>
693 ExportProfileManager::get_warnings ()
694 {
695         boost::shared_ptr<Warnings> warnings (new Warnings ());
696
697         ChannelConfigStatePtr channel_config_state = channel_configs.front();
698         TimespanStatePtr timespan_state = timespans.front();
699
700         /*** Check "global" config ***/
701
702         TimespanListPtr timespans = timespan_state->timespans;
703         ChannelConfigPtr channel_config = channel_config_state->config;
704
705         /* Check Timespans are not empty */
706
707         if (timespans->empty()) {
708                 warnings->errors.push_back (_("No timespan has been selected!"));
709         }
710
711         /* Check channel config ports */
712
713         if (!channel_config->all_channels_have_ports ()) {
714                 warnings->warnings.push_back (_("Some channels are empty"));
715         }
716
717         /*** Check files ***/
718
719         FormatStateList::const_iterator format_it;
720         FilenameStateList::const_iterator filename_it;
721         for (format_it = formats.begin(), filename_it = filenames.begin();
722              format_it != formats.end() && filename_it != filenames.end();
723              ++format_it, ++filename_it) {
724                         check_config (warnings, timespan_state, channel_config_state, *format_it, *filename_it);
725         }
726
727         return warnings;
728 }
729
730 void
731 ExportProfileManager::check_config (boost::shared_ptr<Warnings> warnings,
732                                     TimespanStatePtr timespan_state,
733                                     ChannelConfigStatePtr channel_config_state,
734                                     FormatStatePtr format_state,
735                                     FilenameStatePtr filename_state)
736 {
737         TimespanListPtr timespans = timespan_state->timespans;
738         ChannelConfigPtr channel_config = channel_config_state->config;
739         FormatPtr format = format_state->format;
740         FilenamePtr filename = filename_state->filename;
741
742         /* Check format and maximum channel count */
743         if (!format || !format->type()) {
744                 warnings->errors.push_back (_("No format selected!"));
745         } else if (!channel_config->get_n_chans()) {
746                 warnings->errors.push_back (_("All channels are empty!"));
747         } else if (!check_format (format, channel_config->get_n_chans())) {
748                 warnings->errors.push_back (_("One or more of the selected formats is not compatible with this system!"));
749         } else if (format->channel_limit() < channel_config->get_n_chans()) {
750                 warnings->errors.push_back
751                   (string_compose (_("%1 supports only %2 channels, but you have %3 channels in your channel configuration"),
752                                      format->format_name(),
753                                      format->channel_limit(),
754                                      channel_config->get_n_chans()));
755         }
756
757         if (!warnings->errors.empty()) { return; }
758
759         /* Check filenames */
760
761 //      filename->include_timespan = (timespans->size() > 1); Disabled for now...
762
763         for (std::list<TimespanPtr>::iterator timespan_it = timespans->begin(); timespan_it != timespans->end(); ++timespan_it) {
764                 filename->set_timespan (*timespan_it);
765
766                 if (channel_config->get_split()) {
767                         filename->include_channel = true;
768
769                         for (uint32_t chan = 1; chan <= channel_config->get_n_chans(); ++chan) {
770                                 filename->set_channel (chan);
771
772                                 Glib::ustring path = filename->get_path (format);
773
774                                 if (sys::exists (sys::path (path))) {
775                                         warnings->conflicting_filenames.push_back (path);
776                                 }
777                         }
778
779                 } else {
780                         Glib::ustring path = filename->get_path (format);
781
782                         if (sys::exists (sys::path (path))) {
783                                 warnings->conflicting_filenames.push_back (path);
784                         }
785                 }
786         }
787 }
788
789 bool
790 ExportProfileManager::check_format (FormatPtr format, uint32_t channels)
791 {
792         switch (format->type()) {
793           case ExportFormatBase::T_Sndfile:
794                 return check_sndfile_format (format, channels);
795
796           default:
797                 throw ExportFailed (X_("Invalid format given for ExportFileFactory::check!"));
798         }
799 }
800
801 bool
802 ExportProfileManager::check_sndfile_format (FormatPtr format, unsigned int channels)
803 {
804         SF_INFO sf_info;
805         sf_info.channels = channels;
806         sf_info.samplerate = format->sample_rate ();
807         sf_info.format = format->format_id () | format->sample_format ();
808
809         return (sf_format_check (&sf_info) == SF_TRUE ? true : false);
810 }
811
812 }; // namespace ARDOUR