Audacity 3.2.0
ExportFilePanel.cpp
Go to the documentation of this file.
1#include "ExportFilePanel.h"
2
3#include <numeric>
4#include <wx/stattext.h>
5#include <wx/button.h>
6#include <wx/choice.h>
7#include <wx/textctrl.h>
8#include <wx/radiobut.h>
9#include <wx/wupdlock.h>
10
11#include "Export.h"
12#include "ExportMixerDialog.h"
13#include "ProjectRate.h"
14#include "Mix.h"
15#include "WaveTrack.h"
16
17#include "ShuttleGui.h"
20#include "ExportUtils.h"
21#include "WindowAccessible.h"
22
23#if wxUSE_ACCESSIBILITY
24#include "WindowAccessible.h"
25#endif
26
27wxDEFINE_EVENT(AUDACITY_EXPORT_FORMAT_CHANGE_EVENT, wxCommandEvent);
28
29namespace
30{
31
34 8000,
35 11025,
36 16000,
37 22050,
38 32000,
39 44100,
40 48000,
41 88200,
42 96000,
43 176400,
44 192000,
45 352800,
46 384000
47};
48
49enum
50{
51 FolderBrowseID = wxID_HIGHEST,
52
54
58
60
62};
63
65{
66 enum {
67 CustomSampleRateID = wxID_HIGHEST
68 };
69public:
70 CustomSampleRateDialog(wxWindow* parent, int defaultSampleRate = 44100)
71 : wxDialogWrapper(parent, wxID_ANY, XO("Custom Sample Rate"), wxDefaultPosition, {-1, 160})
72 , mSampleRate(defaultSampleRate)
73 {
75 S.SetBorder(5);
76 S.StartHorizontalLay(wxEXPAND);
77 {
78 S.StartMultiColumn(2, wxALIGN_CENTER_VERTICAL);
79 {
80 S.Id(CustomSampleRateID).AddNumericTextBox(XO("New sample rate (Hz):"), wxString::Format("%d", mSampleRate), 0);
81 }
82 S.EndMultiColumn();
83 }
84 S.EndHorizontalLay();
85
86 S.AddStandardButtons();
87 }
88
89 int GetSampleRate() const noexcept
90 {
91 return mSampleRate;
92 }
93
94private:
95
96 void OnSampleRateChange(wxCommandEvent& event)
97 {
98 long rate;
99 if(event.GetString().ToLong(&rate))
100 mSampleRate = static_cast<int>(rate);
101 }
102
104
106};
107
108
109
110}
111
112BEGIN_EVENT_TABLE(CustomSampleRateDialog, wxDialogWrapper)
113 EVT_TEXT(CustomSampleRateID, CustomSampleRateDialog::OnSampleRateChange)
115
116BEGIN_EVENT_TABLE(ExportFilePanel, wxPanelWrapper)
118
119 EVT_CHOICE(FormatID, ExportFilePanel::OnFormatChange)
120
121 EVT_RADIOBUTTON(AudioMixModeMonoID, ExportFilePanel::OnChannelsChange)
122 EVT_RADIOBUTTON(AudioMixModeStereoID, ExportFilePanel::OnChannelsChange)
123 EVT_RADIOBUTTON(AudioMixModeCustomID, ExportFilePanel::OnChannelsChange)
124
126
127 EVT_CHOICE(SampleRateID, ExportFilePanel::OnSampleRateChange)
129
130
132 bool monoStereoMode,
133 wxWindow* parent,
134 wxWindowID winid)
135 : wxPanelWrapper(parent, winid)
136 , mMonoStereoMode(monoStereoMode)
137 , mProject(project)
138{
139 ShuttleGui S(this, eIsCreating);
140 PopulateOrExchange(S);
141}
142
144
146{
147 TranslatableStrings formats;
148 if(S.GetMode() == eIsCreating)
149 {
150 for(auto [plugin, formatIndex] : ExportPluginRegistry::Get())
151 {
152 auto formatInfo = plugin->GetFormatInfo(formatIndex);
153 formats.push_back(formatInfo.description);
154 }
155 }
156
157 S.SetBorder(5);
158 S.StartMultiColumn(3, wxEXPAND);
159 {
160 S.SetStretchyCol(1);
161
162 mFullName = S.AddTextBox(XO("File &Name:"), {}, 0);
163 mFullName->Bind(wxEVT_KILL_FOCUS, &ExportFilePanel::OnFullNameFocusKill, this);
164 S.AddSpace(1);
165
166 mFolder = S.AddTextBox(XO("Fo&lder:"), {}, 0);
167 S.Id(FolderBrowseID).AddButton(XO("&Browse..."));
168
169 mFormat = S.Id(FormatID).AddChoice(XO("&Format:"), formats);
170 S.AddSpace(1);
171 }
172 S.EndMultiColumn();
173
174 S.SetBorder(5);
175 S.StartStatic(XO("Audio options"));
176 {
177 S.StartTwoColumn();
178 {
179 if(auto prompt = S.AddPrompt(XO("Channels")))
180 prompt->SetMinSize({140, -1});
181
182 S.StartHorizontalLay(wxALIGN_LEFT);
183 {
184 S.SetBorder(2);
185
186 const int channels = 2;
187
188 mMono = S.Id(AudioMixModeMonoID).AddRadioButton(XO("M&ono"), 1, channels);
189 mStereo = S.Id(AudioMixModeStereoID).AddRadioButtonToGroup(XO("&Stereo"), 2, channels);
190 if(!mMonoStereoMode)
191 {
192 //i18n-hint refers to custom channel mapping configuration
193 mCustomMapping = S.Id(AudioMixModeCustomID).AddRadioButtonToGroup(XO("Custom mappin&g"), 0, true);
195 //i18n-hint accessibility hint, refers to export channel configuration
196 .Name(XO("Configure custom mapping"))
197 .AddButton(XO("Configure"));
198#if wxUSE_ACCESSIBILITY
200#endif
201 }
202 }
203 S.EndHorizontalLay();
204
205 S.SetBorder(5);
206
207 if(auto prompt = S.AddPrompt(XO("Sample &Rate")))
208 prompt->SetMinSize({140, -1});
209
210 S.StartHorizontalLay(wxALIGN_LEFT);
211 {
212 mRates = S.Id(SampleRateID).AddChoice({}, {});
213 }
214 S.EndHorizontalLay();
215 }
216 S.EndTwoColumn();
217
218 mAudioOptionsPanel = S.StartPanel();
219 {
220
221 }
222 S.EndPanel();
223 }
224 S.EndStatic();
225}
226
227void ExportFilePanel::Init(const wxFileName& filename,
228 int sampleRate,
229 const wxString& format,
230 int channels,
231 const ExportProcessor::Parameters& parameters,
232 const MixerOptions::Downmix* mixerSpec)
233{
234 mFolder->SetValue(filename.GetPath());
235 mFullName->SetValue(filename.GetFullName());
237
238 auto selectedFormatIndex = 0;
239 if(!format.empty())
240 {
241 auto counter = 0;
242 for(auto [plugin, formatIndex] : ExportPluginRegistry::Get())
243 {
244 if(plugin->GetFormatInfo(formatIndex).format.IsSameAs(format))
245 {
246 selectedFormatIndex = counter;
247 break;
248 }
249 ++counter;
250 }
251 }
252
253 if(mixerSpec != nullptr)
254 {
255 assert(!mMonoStereoMode);
256 *mMixerSpec = *mixerSpec;
257 mCustomMapping->SetValue(true);
258 }
259 else
260 {
261 int numChannels = channels;
262 if(numChannels == 0)
263 {
264 numChannels = 1;
265 const auto waveTracks =
268 false);
269 for(const auto track : waveTracks)
270 {
271 if(TrackList::NChannels(*track) >= 2 || track->GetPan() != .0f)
272 {
273 numChannels = 2;
274 break;
275 }
276 }
277 }
278 if(numChannels == 1)
279 mMono->SetValue(true);
280 else
281 mStereo->SetValue(true);
282 }
283
284 mFormat->SetSelection(selectedFormatIndex);
285
286 ChangeFormat(selectedFormatIndex);
287
288 if(!parameters.empty())
289 mOptionsHandler->SetParameters(parameters);
290
291 if(mCustomizeChannels != nullptr)
292 mCustomizeChannels->Enable(mCustomMapping->GetValue());
293}
294
295// Used as part of fix for issue #4960
297{
298 mFullName->SetFocus();
299 mFullName->SelectAll();
300}
301
303{
305 return;
306
307 if(!enabled && mCustomMapping->GetValue())
308 {
309 if(mStereo->IsEnabled())
310 mStereo->SetValue(true);
311 else
312 mMono->SetValue(true);
313 }
314 mCustomMapping->Enable(enabled);
315 mCustomizeChannels->Enable(enabled);
316}
317
319{
320 return mFolder->GetValue();
321}
322
324{
326 return mFullName->GetValue();
327}
328
330{
331 return mSelectedPlugin;
332}
333
335{
337}
338
340{
341 return mSampleRate;
342}
343
345{
346 mOptionsHandler->TransferDataFromEditor();
347 return mOptionsHandler->GetParameters();
348}
349
351{
352 if(mCustomMapping != nullptr && mCustomMapping->GetValue())
353 return 0;
354 return mMono->GetValue() ? 1 : 2;
355}
356
358{
359 return mMixerSpec.get();
360}
361
363{
364 if(mSelectedPlugin == nullptr)
365 return;
366
367 const auto formatInfo = mSelectedPlugin->GetFormatInfo(mSelectedFormatIndex);
368 if(formatInfo.extensions.empty())
369 return;
370
371 wxFileName filename;
372 filename.SetFullName(mFullName->GetValue());
373
374 //Remove extra whitespaces
375 auto desiredExt = filename.GetExt().Trim().Trim(false);
376
377 auto it = std::find_if(
378 formatInfo.extensions.begin(),
379 formatInfo.extensions.end(),
380 // if typed extension uses different case (e.g. MP3 instead of mp3)
381 // we'll reset the file extension to one provided by FormatInfo
382 [&](const auto& ext) { return desiredExt.IsSameAs(ext, false); });
383
384 if(it == formatInfo.extensions.end())
385 it = formatInfo.extensions.begin();
386
387 if(!it->empty() && !it->IsSameAs(filename.GetExt()))
388 {
389 filename.SetExt(*it);
390 mFullName->SetValue(filename.GetFullName());
391 }
392}
393
395{
396 //When user has finished typing make sure that file extension
397 //is one of extensions supplied by FormatInfo
398
399 event.Skip();
400
402}
403
404void ExportFilePanel::OnFormatChange(wxCommandEvent &event)
405{
406 ChangeFormat(event.GetInt());
407 event.Skip();
408}
409
410void ExportFilePanel::OnSampleRateChange(wxCommandEvent &event)
411{
412 const auto clientData = event.GetClientData();
413 if(clientData == nullptr)
414 {
415 CustomSampleRateDialog dialog(this, mSampleRate);
416 if(dialog.ShowModal() == wxID_OK &&
417 dialog.GetSampleRate() > 0)
418 {
419 mSampleRate = dialog.GetSampleRate();
420 }
422 }
423 else
424 mSampleRate = *reinterpret_cast<const int*>(&clientData);
425}
426
427void ExportFilePanel::OnFolderBrowse(wxCommandEvent &event)
428{
429 FileNames::FileTypes fileTypes;
430
431 for(auto [plugin, formatIndex] : ExportPluginRegistry::Get())
432 {
433 const auto formatInfo = plugin->GetFormatInfo(formatIndex);
434 fileTypes.emplace_back(formatInfo.description, formatInfo.extensions);
435 }
436 wxFileDialog fd(this, _("Choose a location to save the exported files"),
437 mFolder->GetValue(),
438 mFullName->GetValue(),
439 FileNames::FormatWildcard(fileTypes),
440 wxFD_SAVE);
441 fd.SetFilterIndex(mFormat->GetSelection());
442
443 if(fd.ShowModal() == wxID_OK)
444 {
445 wxFileName filepath (fd.GetPath());
446 mFolder->SetValue(filepath.GetPath());
447 mFullName->SetValue(filepath.GetFullName());
448 const auto selectedFormat = fd.GetFilterIndex();
449 if(selectedFormat != mFormat->GetSelection())
450 {
451 mFormat->SetSelection(selectedFormat);
452 ChangeFormat(selectedFormat);
453 }
454 }
455}
456
457void ExportFilePanel::OnChannelsChange(wxCommandEvent& event)
458{
459 if(mCustomizeChannels != nullptr)
460 mCustomizeChannels->Enable(event.GetId() == AudioMixModeCustomID);
461}
462
463void ExportFilePanel::OnChannelsConfigure(wxCommandEvent &event)
464{
465 //Configure for all tracks, but some channels may turn out to be silent
466 //if exported region does not contain audio samples
467 auto waveTracks = TrackList::Get(mProject).Any<const WaveTrack>();
468
469 auto mixerSpec = std::make_unique<MixerOptions::Downmix>(*mMixerSpec);
470
471 ExportMixerDialog md(waveTracks,
472 mixerSpec.get(),
473 nullptr,
474 1,
475 XO("Advanced Mixing Options"));
476 if(md.ShowModal() == wxID_OK)
477 mMixerSpec.swap(mixerSpec);
478}
479
480
482{
483 mSelectedPlugin = nullptr;
484
485 wxWindowUpdateLocker wndupdlck(mAudioOptionsPanel);
486
487 auto formatCounter = 0;
488
489 for(auto [plugin, formatIndex] : ExportPluginRegistry::Get())
490 {
491 if(formatCounter != index)
492 {
493 ++formatCounter;
494 continue;
495 }
496
498
499 mSelectedPlugin = plugin;
500 mSelectedFormatIndex = formatIndex;
501
502 auto formatInfo = plugin->GetFormatInfo(formatIndex);
503 UpdateFileNameExt(formatInfo.extensions[0]);
504
505 mAudioOptionsPanel->SetSizer(nullptr);
506 mAudioOptionsPanel->DestroyChildren();
507
509 mOptionsHandler = std::make_unique<ExportOptionsHandler>(S, *plugin, formatIndex);
511
512 UpdateMaxChannels(formatInfo.maxChannels);
513
515
516 mAudioOptionsPanel->Layout();
517
518 wxPostEvent(GetParent(), wxCommandEvent { AUDACITY_EXPORT_FORMAT_CHANGE_EVENT, GetId() });
519
520 return;
521 }
522}
523
525{
526 switch(e.type)
527 {
530 break;
532 {
534 UpdateFileNameExt(formatInfo.extensions[0]);
535 UpdateMaxChannels(formatInfo.maxChannels);
536 } break;
537 }
538
539}
540
541void ExportFilePanel::UpdateFileNameExt(const wxString& ext)
542{
543 if(!ext.empty())
544 {
545 wxFileName filename;
546 filename.SetFullName(mFullName->GetValue());
547 filename.SetExt(ext.BeforeFirst(' ').Lower());
548 mFullName->SetValue(filename.GetFullName());
549 }
550}
551
552void ExportFilePanel::UpdateMaxChannels(unsigned maxChannels)
553{
554 if(maxChannels < 2 && mStereo->GetValue())
555 mMono->SetValue(true);
556 mStereo->Enable(maxChannels > 1);
557 if(!mMonoStereoMode)
558 {
559 const auto mixerMaxChannels = std::clamp(
560 maxChannels,
561 // JKC: This is an attempt to fix a 'watching brief' issue, where the slider is
562 // sometimes not slidable. My suspicion is that a mixer may incorrectly
563 // state the number of channels - so we assume there are always at least two.
564 // The downside is that if someone is exporting to a mono device, the dialog
565 // will allow them to output to two channels. Hmm. We may need to revisit this.
566 // STF (April 2016): AMR (narrowband) and MP3 may export 1 channel.
567 1u,
569 if(!mMixerSpec || mMixerSpec->GetMaxNumChannels() != mixerMaxChannels)
570 {
571 auto waveTracks = TrackList::Get(mProject).Any<const WaveTrack>();
572 mMixerSpec = std::make_unique<MixerOptions::Downmix>(
573 waveTracks.sum([](const auto track) { return track->NChannels(); }),
574 mixerMaxChannels);
575 }
576 }
577}
578
580{
581 auto availableRates = mOptionsHandler->GetSampleRateList();
582 std::sort(availableRates.begin(), availableRates.end());
583
584 const auto* rates = availableRates.empty() ? &DefaultRates : &availableRates;
585
586 mRates->Clear();
587
588 void* clientData;
589 int customRate = mSampleRate;
590 int selectedItemIndex = 0;
591 //Prefer lowest possible sample rate that is not less than mSampleRate.
592 //Initialize with highest value, so that if all available rates are less
593 //than mSampleRate then we will choose highest rate
594 int preferredRate = rates->back();
595 int preferredItemIndex = rates->size() - 1;
596 for(auto rate : *rates)
597 {
598 *reinterpret_cast<int*>(&clientData) = rate;
599 const auto itemIndex =
600 mRates->Append(
601 XO("%d Hz").Format(rate).Translation(),
602 clientData);
603 if(rate == mSampleRate)
604 {
605 customRate = 0;
606 selectedItemIndex = itemIndex;
607 }
608 if(rate >= mSampleRate && rate < preferredRate)
609 {
610 preferredItemIndex = itemIndex;
611 preferredRate = rate;
612 }
613 }
614
615 if(rates == &DefaultRates)
616 {
617 if(customRate != 0)
618 {
619 *reinterpret_cast<int*>(&clientData) = customRate;
620 selectedItemIndex =
621 mRates->Append(
622 XO("%d Hz (custom)").Format(customRate).Translation(),
623 clientData);
624 }
625 mRates->Append(_("Other..."));
626 }
627 else if(customRate != 0)//sample rate not in the list
628 {
629 auto selectedRate = (*rates)[preferredItemIndex];
630 mSampleRate = selectedRate;
631 selectedItemIndex = preferredItemIndex;
632 }
633 mRates->SetSelection(selectedItemIndex);
634}
END_EVENT_TABLE()
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
wxDEFINE_EVENT(AUDACITY_EXPORT_FORMAT_CHANGE_EVENT, wxCommandEvent)
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
#define safenew
Definition: MemoryX.h:10
an object holding per-project preferred sample rate
@ eIsCreating
Definition: ShuttleGui.h:37
const auto project
#define S(N)
Definition: ToChars.cpp:64
std::vector< TranslatableString > TranslatableStrings
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
const ExportPlugin * GetPlugin() const
wxChoice * mFormat
std::unique_ptr< MixerOptions::Downmix > mMixerSpec
void OnFullNameFocusKill(wxFocusEvent &event)
std::unique_ptr< ExportOptionsHandler > mOptionsHandler
void OnSampleRateChange(wxCommandEvent &event)
const ExportPlugin * mSelectedPlugin
int GetSampleRate() const
wxString GetPath() const
~ExportFilePanel() override
wxTextCtrl * mFolder
AudacityProject & mProject
void SetCustomMappingEnabled(bool enabled)
static constexpr auto MaxExportChannels
wxString GetFullName()
MixerOptions::Downmix * GetMixerSpec() const
void ChangeFormat(int index)
wxTextCtrl * mFullName
void OnFormatChange(wxCommandEvent &event)
wxRadioButton * mStereo
int GetFormat() const
wxWindow * mAudioOptionsPanel
int GetChannels() const
wxRadioButton * mCustomMapping
void OnFolderBrowse(wxCommandEvent &event)
wxRadioButton * mMono
void OnChannelsConfigure(wxCommandEvent &event)
ExportProcessor::Parameters GetParameters() const
void OnOptionsHandlerEvent(const ExportOptionsHandlerEvent &e)
wxChoice * mRates
Observer::Subscription mOptionsChangeSubscription
void UpdateMaxChannels(unsigned maxChannels)
void Init(const wxFileName &filename, int sampleRate, const wxString &format=wxEmptyString, int channels=0, const ExportProcessor::Parameters &parameters={}, const MixerOptions::Downmix *mixerSpec=nullptr)
Initializes panel with export settings provided as arguments. Call is required.
void PopulateOrExchange(ShuttleGui &S)
void UpdateFileNameExt(const wxString &ext)
void OnChannelsChange(wxCommandEvent &event)
wxButton * mCustomizeChannels
Dialog for advanced mixing.
std::vector< int > SampleRateList
virtual FormatInfo GetFormatInfo(int index) const =0
Returns FormatInfo structure for given index if it's valid, or a default one. FormatInfo::format isn'...
static ExportPluginRegistry & Get()
std::vector< std::tuple< ExportOptionID, ExportValue > > Parameters
Definition: ExportPlugin.h:93
static TrackIterRange< const WaveTrack > FindExportWaveTracks(const TrackList &tracks, bool selectedOnly)
Definition: ExportUtils.cpp:18
std::vector< FileType > FileTypes
Definition: FileNames.h:75
A matrix of booleans, one row per input channel, column per output.
Definition: MixerOptions.h:32
void Reset() noexcept
Breaks the connection (constant time)
Definition: Observer.cpp:101
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:630
size_t NChannels() const
Definition: Track.cpp:960
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:1079
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:347
A Track that contains audio waveform data.
Definition: WaveTrack.h:222
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
CustomSampleRateDialog(wxWindow *parent, int defaultSampleRate=44100)
FILES_API wxString FormatWildcard(const FileTypes &fileTypes)
const ExportOptionsEditor::SampleRateList DefaultRates
enum ExportOptionsHandlerEvent::@43 type