1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   ExportPCM.cpp
6 
7   Dominic Mazzoni
8 
9 **********************************************************************/
10 
11 
12 
13 #include <wx/defs.h>
14 
15 #include <wx/app.h>
16 #include <wx/choice.h>
17 #include <wx/dynlib.h>
18 #include <wx/filename.h>
19 #include <wx/intl.h>
20 #include <wx/timer.h>
21 #include <wx/string.h>
22 #include <wx/textctrl.h>
23 #include <wx/window.h>
24 
25 #include "sndfile.h"
26 
27 #include "Dither.h"
28 #include "../FileFormats.h"
29 #include "../Mix.h"
30 #include "Prefs.h"
31 #include "ProjectRate.h"
32 #include "../ShuttleGui.h"
33 #include "../Tags.h"
34 #include "../Track.h"
35 #include "../widgets/AudacityMessageBox.h"
36 #include "../widgets/ProgressDialog.h"
37 #include "../widgets/wxWidgetsWindowPlacement.h"
38 #include "wxFileNameWrapper.h"
39 
40 #include "Export.h"
41 
42 #ifdef USE_LIBID3TAG
43    #include <id3tag.h>
44    // DM: the following functions were supposed to have been
45    // included in id3tag.h - should be fixed in the next release
46    // of mad.
47    extern "C" {
48       struct id3_frame *id3_frame_new(char const *);
49       id3_length_t id3_latin1_length(id3_latin1_t const *);
50       void id3_latin1_decode(id3_latin1_t const *, id3_ucs4_t *);
51    }
52 #endif
53 
54 struct
55 {
56    int format;
57    const wxChar *name;
58    const TranslatableString desc;
59 }
60 static const kFormats[] =
61 {
62 #if defined(__WXMAC__)
63    {SF_FORMAT_AIFF | SF_FORMAT_PCM_16, wxT("AIFF"),   XO("AIFF (Apple/SGI)")},
64 #endif
65    {SF_FORMAT_WAV | SF_FORMAT_PCM_16,  wxT("WAV"),    XO("WAV (Microsoft)")},
66 };
67 
68 enum
69 {
70 #if defined(__WXMAC__)
71    FMT_AIFF,
72 #endif
73    FMT_WAV,
74    FMT_OTHER
75 };
76 
77 //----------------------------------------------------------------------------
78 // Statics
79 //----------------------------------------------------------------------------
80 
LoadOtherFormat(int def=0)81 static int LoadOtherFormat(int def = 0)
82 {
83    return gPrefs->Read(wxT("/FileFormats/ExportFormat_SF1"),
84                        kFormats[0].format & SF_FORMAT_TYPEMASK);
85 }
86 
SaveOtherFormat(int val)87 static void SaveOtherFormat(int val)
88 {
89    gPrefs->Write(wxT("/FileFormats/ExportFormat_SF1"), val);
90    gPrefs->Flush();
91 }
92 
LoadEncoding(int type)93 static int LoadEncoding(int type)
94 {
95    return gPrefs->Read(wxString::Format(wxT("/FileFormats/ExportFormat_SF1_Type/%s_%x"),
96                                         sf_header_shortname(type), type), (long int) 0);
97 }
98 
SaveEncoding(int type,int val)99 static void SaveEncoding(int type, int val)
100 {
101    gPrefs->Write(wxString::Format(wxT("/FileFormats/ExportFormat_SF1_Type/%s_%x"),
102                                   sf_header_shortname(type), type), val);
103    gPrefs->Flush();
104 }
105 
106 //----------------------------------------------------------------------------
107 // ExportPCMOptions Class
108 //----------------------------------------------------------------------------
109 
110 #define ID_HEADER_CHOICE           7102
111 #define ID_ENCODING_CHOICE         7103
112 
113 class ExportPCMOptions final : public wxPanelWrapper
114 {
115 public:
116 
117    ExportPCMOptions(wxWindow *parent, int format);
118    virtual ~ExportPCMOptions();
119 
120    void PopulateOrExchange(ShuttleGui & S);
121 
122    void OnShow(wxShowEvent & evt);
123    void OnHeaderChoice(wxCommandEvent & evt);
124    void OnEncodingChoice(wxCommandEvent & evt);
125 
126 private:
127 
128    void GetTypes();
129    void GetEncodings(int enc = 0);
130    void SendSuffixEvent();
131 
132 private:
133 
134    std::vector<int> mHeaderIndexes;
135    TranslatableStrings mHeaderNames;
136    wxChoice *mHeaderChoice;
137    int mHeaderFromChoice;
138 
139    std::vector<int> mEncodingIndexes;
140    TranslatableStrings mEncodingNames;
141    wxChoice *mEncodingChoice;
142    int mEncodingFromChoice;
143 
144    int mSelFormat;
145    int mType;
146 
147    DECLARE_EVENT_TABLE()
148 };
149 
BEGIN_EVENT_TABLE(ExportPCMOptions,wxPanelWrapper)150 BEGIN_EVENT_TABLE(ExportPCMOptions, wxPanelWrapper)
151    EVT_CHOICE(ID_HEADER_CHOICE, ExportPCMOptions::OnHeaderChoice)
152    EVT_CHOICE(ID_ENCODING_CHOICE, ExportPCMOptions::OnEncodingChoice)
153 END_EVENT_TABLE()
154 
155 ExportPCMOptions::ExportPCMOptions(wxWindow *parent, int selformat)
156 :  wxPanelWrapper(parent, wxID_ANY)
157 {
158    // Remember the selection format
159    mSelFormat = selformat;
160 
161    // Init choices
162    mHeaderFromChoice = 0;
163    mEncodingFromChoice = 0;
164 
165    if (mSelFormat < FMT_OTHER)
166    {
167       mType = kFormats[selformat].format & SF_FORMAT_TYPEMASK;
168       GetEncodings(mType & SF_FORMAT_SUBMASK);
169    }
170    else
171    {
172       GetTypes();
173       GetEncodings();
174    }
175 
176    ShuttleGui S(this, eIsCreatingFromPrefs);
177    PopulateOrExchange(S);
178 
179    parent->Bind(wxEVT_SHOW, &ExportPCMOptions::OnShow, this);
180 }
181 
~ExportPCMOptions()182 ExportPCMOptions::~ExportPCMOptions()
183 {
184    // Save the encoding
185    SaveOtherFormat(mType);
186    SaveEncoding(mType, sf_encoding_index_to_subtype(mEncodingIndexes[mEncodingFromChoice]));
187 }
188 
PopulateOrExchange(ShuttleGui & S)189 void ExportPCMOptions::PopulateOrExchange(ShuttleGui & S)
190 {
191    S.StartVerticalLay();
192    {
193       S.StartHorizontalLay(wxCENTER);
194       {
195          S.StartMultiColumn(2, wxCENTER);
196          {
197             S.SetStretchyCol(1);
198             if (mSelFormat == FMT_OTHER)
199             {
200                mHeaderChoice = S.Id(ID_HEADER_CHOICE)
201                   .AddChoice(XXO("Header:"),
202                              mHeaderNames,
203                              mHeaderFromChoice);
204             }
205             mEncodingChoice = S.Id(ID_ENCODING_CHOICE)
206                .AddChoice(XXO("Encoding:"),
207                           mEncodingNames,
208                           mEncodingFromChoice);
209          }
210          S.EndMultiColumn();
211       }
212       S.EndHorizontalLay();
213    }
214    S.EndVerticalLay();
215 
216    return;
217 }
218 
OnShow(wxShowEvent & evt)219 void ExportPCMOptions::OnShow(wxShowEvent & evt)
220 {
221    evt.Skip();
222 
223    // Since the initial file name may not have the "correct" extension,
224    // send off an event to have it changed.  Note that this will be
225    // done every time a user changes filters in the dialog.
226    if (evt.IsShown())
227    {
228       SendSuffixEvent();
229    }
230 }
231 
OnHeaderChoice(wxCommandEvent & evt)232 void ExportPCMOptions::OnHeaderChoice(wxCommandEvent & evt)
233 {
234    evt.Skip();
235 
236    // Remember new selection
237    mHeaderFromChoice = evt.GetInt();
238 
239    // Get the type for this selection
240    mType = sf_header_index_to_type(mHeaderIndexes[mHeaderFromChoice]);
241 
242    // Save the newly selected type
243    SaveOtherFormat(mType);
244 
245    // Reload the encodings valid for this new type
246    GetEncodings();
247 
248    // Repopulate the encoding choices
249    mEncodingChoice->Clear();
250    for (int i = 0, num = mEncodingNames.size(); i < num; ++i)
251    {
252       mEncodingChoice->AppendString(mEncodingNames[i].StrippedTranslation());
253    }
254 
255    // Select the desired encoding
256    mEncodingChoice->SetSelection(mEncodingFromChoice);
257 
258    // Send the event indicating a file suffix change.
259    SendSuffixEvent();
260 }
261 
OnEncodingChoice(wxCommandEvent & evt)262 void ExportPCMOptions::OnEncodingChoice(wxCommandEvent & evt)
263 {
264    evt.Skip();
265 
266    // Remember new selection
267    mEncodingFromChoice = evt.GetInt();
268 
269    // And save it
270    SaveEncoding(mType, sf_encoding_index_to_subtype(mEncodingIndexes[mEncodingFromChoice]));
271 }
272 
GetTypes()273 void ExportPCMOptions::GetTypes()
274 {
275    // Reset arrays
276    mHeaderIndexes.clear();
277    mHeaderNames.clear();
278 
279    // Get the previously saved type. Note that this is ONLY used for
280    // the FMT_OTHER ("Other uncompressed files") types.
281    int typ = LoadOtherFormat() & SF_FORMAT_TYPEMASK;
282 
283    // Rebuild the arrays
284    mHeaderFromChoice = 0;
285    for (int i = 0, num = sf_num_headers(); i < num; ++i)
286    {
287       int type = sf_header_index_to_type(i);
288 
289       switch (type)
290       {
291          // On the Mac, do not include in header list
292 #if defined(__WXMAC__)
293          case SF_FORMAT_AIFF:
294          break;
295 #endif
296 
297          // Do not include in header list
298          case SF_FORMAT_WAV:
299          break;
300 
301          default:
302             // Remember the index if this is the desired type
303             if (type == typ)
304             {
305                mHeaderFromChoice = mHeaderIndexes.size();
306             }
307 
308             // Store index and name
309             mHeaderIndexes.push_back(i);
310             mHeaderNames.push_back(Verbatim(sf_header_index_name(i)));
311          break;
312       }
313    }
314 
315    // Refresh the current type
316    mType = sf_header_index_to_type(mHeaderIndexes[mHeaderFromChoice]);
317 }
318 
GetEncodings(int enc)319 void ExportPCMOptions::GetEncodings(int enc)
320 {
321    // Setup for queries
322    SF_INFO info = {};
323    info.samplerate = 44100;
324    info.channels = 1;
325    info.sections = 1;
326 
327    // Reset arrays
328    mEncodingIndexes.clear();
329    mEncodingNames.clear();
330 
331    // If the encoding wasn't supplied, look it up
332    if (!(enc & SF_FORMAT_SUBMASK))
333    {
334       enc = LoadEncoding(mType);
335    }
336    enc &= SF_FORMAT_SUBMASK;
337 
338    // Fix for Bug 1218 - AIFF with no encoding should default to 16 bit.
339    if (mType == SF_FORMAT_AIFF && enc == 0)
340    {
341       enc = SF_FORMAT_PCM_16;
342    }
343 
344    // Rebuild the arrays
345    mEncodingFromChoice = 0;
346    for (int i = 0, num = sf_num_encodings(); i < num; ++i)
347    {
348       int sub = sf_encoding_index_to_subtype(i);
349 
350       // Since we're traversing the subtypes linearly, we have to
351       // make sure it can be paired with our current type.
352       info.format = mType | sub;
353       if (sf_format_check(&info))
354       {
355          // If this subtype matches our last saved encoding, remember
356          // its index so we can set it in the dialog.
357          if (sub == enc)
358          {
359             mEncodingFromChoice = mEncodingIndexes.size();
360          }
361 
362          // Store index and name
363          mEncodingIndexes.push_back(i);
364          mEncodingNames.push_back(Verbatim(sf_encoding_index_name(i)));
365       }
366    }
367 }
368 
SendSuffixEvent()369 void ExportPCMOptions::SendSuffixEvent()
370 {
371    // Synchronously process a change in suffix.
372    wxCommandEvent evt(AUDACITY_FILE_SUFFIX_EVENT, GetId());
373    evt.SetEventObject(this);
374    evt.SetString(sf_header_extension(mType));
375    ProcessWindowEvent(evt);
376 }
377 
378 //----------------------------------------------------------------------------
379 // ExportPCM Class
380 //----------------------------------------------------------------------------
381 
382 class ExportPCM final : public ExportPlugin
383 {
384 public:
385 
386    ExportPCM();
387 
388    // Required
389 
390    void OptionsCreate(ShuttleGui &S, int format) override;
391    ProgressResult Export(AudacityProject *project,
392                          std::unique_ptr<ProgressDialog> &pDialog,
393                          unsigned channels,
394                          const wxFileNameWrapper &fName,
395                          bool selectedOnly,
396                          double t0,
397                          double t1,
398                          MixerSpec *mixerSpec = NULL,
399                          const Tags *metadata = NULL,
400                          int subformat = 0) override;
401    // optional
402    wxString GetFormat(int index) override;
403    FileExtension GetExtension(int index) override;
404    unsigned GetMaxChannels(int index) override;
405 
406 private:
407    void ReportTooBigError(wxWindow * pParent);
408    ArrayOf<char> AdjustString(const wxString & wxStr, int sf_format);
409    bool AddStrings(AudacityProject *project, SNDFILE *sf, const Tags *tags, int sf_format);
410    bool AddID3Chunk(
411       const wxFileNameWrapper &fName, const Tags *tags, int sf_format);
412 
413 };
414 
ExportPCM()415 ExportPCM::ExportPCM()
416    : ExportPlugin()
417 {
418    int selformat; // the index of the format we are setting up at the moment
419 
420    // Add the "special" formats first
421    for (size_t i = 0; i < WXSIZEOF(kFormats); ++i)
422    {
423       selformat = AddFormat() - 1;
424       AddExtension(sf_header_extension(kFormats[i].format), selformat);
425       SetFormat(kFormats[i].name, selformat);
426       SetDescription(kFormats[i].desc, selformat);
427       SetCanMetaData(true, selformat);
428       SetMaxChannels(255, selformat);
429    }
430 
431    // Then add the generic libsndfile "format"
432    selformat = AddFormat() - 1;     // Matches FMT_OTHER
433    SetExtensions(sf_get_all_extensions(), selformat);
434    SetFormat(wxT("LIBSNDFILE"), selformat);
435    SetDescription(XO("Other uncompressed files"), selformat);
436    SetCanMetaData(true, selformat);
437    SetMaxChannels(255, selformat);
438 }
439 
ReportTooBigError(wxWindow * pParent)440 void ExportPCM::ReportTooBigError(wxWindow * pParent)
441 {
442    //Temporary translation hack, to say 'WAV or AIFF' rather than 'WAV'
443    auto message =
444       XO("You have attempted to Export a WAV or AIFF file which would be greater than 4GB.\n"
445       "Audacity cannot do this, the Export was abandoned.");
446 
447    BasicUI::ShowErrorDialog( wxWidgetsWindowPlacement{ pParent },
448       XO("Error Exporting"), message,
449       wxT("Size_limits_for_WAV_and_AIFF_files"));
450 
451 // This alternative error dialog was to cover the possibility we could not
452 // compute the size in advance.
453 #if 0
454    BasicUI::ShowErrorDialog( wxWidgetsWindowPlacement{ pParent },
455                   XO("Error Exporting"),
456                   XO("Your exported WAV file has been truncated as Audacity cannot export WAV\n"
457                     "files bigger than 4GB."),
458                   wxT("Size_limits_for_WAV_files"));
459 #endif
460 }
461 
462 /**
463  *
464  * @param subformat Control whether we are doing a "preset" export to a popular
465  * file type, or giving the user full control over libsndfile.
466  */
Export(AudacityProject * project,std::unique_ptr<ProgressDialog> & pDialog,unsigned numChannels,const wxFileNameWrapper & fName,bool selectionOnly,double t0,double t1,MixerSpec * mixerSpec,const Tags * metadata,int subformat)467 ProgressResult ExportPCM::Export(AudacityProject *project,
468                                  std::unique_ptr<ProgressDialog> &pDialog,
469                                  unsigned numChannels,
470                                  const wxFileNameWrapper &fName,
471                                  bool selectionOnly,
472                                  double t0,
473                                  double t1,
474                                  MixerSpec *mixerSpec,
475                                  const Tags *metadata,
476                                  int subformat)
477 {
478    double rate = ProjectRate::Get( *project ).GetRate();
479    const auto &tracks = TrackList::Get( *project );
480 
481    // Set a default in case the settings aren't found
482    int sf_format;
483 
484    switch (subformat)
485    {
486 #if defined(__WXMAC__)
487       case FMT_AIFF:
488          sf_format = SF_FORMAT_AIFF;
489       break;
490 #endif
491 
492       case FMT_WAV:
493          sf_format = SF_FORMAT_WAV;
494       break;
495 
496       default:
497          // Retrieve the current format.
498          sf_format = LoadOtherFormat();
499       break;
500    }
501 
502    // Prior to v2.4.0, sf_format will include the subtype. If not present,
503    // check for the format specific preference.
504    if (!(sf_format & SF_FORMAT_SUBMASK))
505    {
506       sf_format |= LoadEncoding(sf_format);
507    }
508 
509    // If subtype is still not specified, supply a default.
510    if (!(sf_format & SF_FORMAT_SUBMASK))
511    {
512       sf_format |= SF_FORMAT_PCM_16;
513    }
514 
515    int fileFormat = sf_format & SF_FORMAT_TYPEMASK;
516 
517    auto updateResult = ProgressResult::Success;
518    {
519       wxFile f;   // will be closed when it goes out of scope
520       SFFile       sf; // wraps f
521 
522       wxString     formatStr;
523       SF_INFO      info;
524       //int          err;
525 
526       //This whole operation should not occur while a file is being loaded on OD,
527       //(we are worried about reading from a file being written to,) so we block.
528       //Furthermore, we need to do this because libsndfile is not threadsafe.
529       formatStr = SFCall<wxString>(sf_header_name, fileFormat);
530 
531       // Use libsndfile to export file
532 
533       info.samplerate = (unsigned int)(rate + 0.5);
534       info.frames = (unsigned int)((t1 - t0)*rate + 0.5);
535       info.channels = numChannels;
536       info.format = sf_format;
537       info.sections = 1;
538       info.seekable = 0;
539 
540       // Bug 46.  Trap here, as sndfile.c does not trap it properly.
541       if( (numChannels != 1) && ((sf_format & SF_FORMAT_SUBMASK) == SF_FORMAT_GSM610) )
542       {
543          AudacityMessageBox( XO("GSM 6.10 requires mono") );
544          return ProgressResult::Cancelled;
545       }
546 
547       if (sf_format == SF_FORMAT_WAVEX + SF_FORMAT_GSM610) {
548          AudacityMessageBox(
549             XO("WAVEX and GSM 6.10 formats are not compatible") );
550          return ProgressResult::Cancelled;
551       }
552 
553       // If we can't export exactly the format they requested,
554       // try the default format for that header type...
555       //
556       // LLL: I don't think this is valid since libsndfile checks
557       // for all allowed subtypes explicitly and doesn't provide
558       // for an unspecified subtype.
559       if (!sf_format_check(&info))
560          info.format = (info.format & SF_FORMAT_TYPEMASK);
561       if (!sf_format_check(&info)) {
562          AudacityMessageBox( XO("Cannot export audio in this format.") );
563          return ProgressResult::Cancelled;
564       }
565       const auto path = fName.GetFullPath();
566       if (f.Open(path, wxFile::write)) {
567          // Even though there is an sf_open() that takes a filename, use the one that
568          // takes a file descriptor since wxWidgets can open a file with a Unicode name and
569          // libsndfile can't (under Windows).
570          sf.reset(SFCall<SNDFILE*>(sf_open_fd, f.fd(), SFM_WRITE, &info, FALSE));
571          //add clipping for integer formats.  We allow floats to clip.
572          sf_command(sf.get(), SFC_SET_CLIPPING, NULL, sf_subtype_is_integer(sf_format)?SF_TRUE:SF_FALSE) ;
573       }
574 
575       if (!sf) {
576          AudacityMessageBox( XO("Cannot export audio to %s").Format( path ) );
577          return ProgressResult::Cancelled;
578       }
579       // Retrieve tags if not given a set
580       if (metadata == NULL)
581          metadata = &Tags::Get( *project );
582 
583       // Install the meta data at the beginning of the file (except for
584       // WAV and WAVEX formats)
585       if (fileFormat != SF_FORMAT_WAV &&
586           fileFormat != SF_FORMAT_WAVEX) {
587          if (!AddStrings(project, sf.get(), metadata, sf_format)) {
588             return ProgressResult::Cancelled;
589          }
590       }
591 
592       sampleFormat format;
593       if (sf_subtype_more_than_16_bits(info.format))
594          format = floatSample;
595       else
596          format = int16Sample;
597 
598       // Bug 2200
599       // Only trap size limit for file types we know have an upper size limit.
600       // The error message mentions aiff and wav.
601       if( (fileFormat == SF_FORMAT_WAV) ||
602           (fileFormat == SF_FORMAT_WAVEX) ||
603           (fileFormat == SF_FORMAT_AIFF ))
604       {
605          float sampleCount = (float)(t1-t0)*rate*info.channels;
606          float byteCount = sampleCount * sf_subtype_bytes_per_sample( info.format);
607          // Test for 4 Gibibytes, rather than 4 Gigabytes
608          if( byteCount > 4.295e9)
609          {
610             ReportTooBigError( wxTheApp->GetTopWindow() );
611             return ProgressResult::Failed;
612          }
613       }
614       size_t maxBlockLen = 44100 * 5;
615 
616       {
617          std::vector<char> dither;
618          if ((info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_24) {
619             dither.reserve(maxBlockLen * info.channels * SAMPLE_SIZE(int24Sample));
620          }
621 
622          wxASSERT(info.channels >= 0);
623          auto mixer = CreateMixer(tracks, selectionOnly,
624                                   t0, t1,
625                                   info.channels, maxBlockLen, true,
626                                   rate, format, mixerSpec);
627 
628          InitProgress( pDialog, fName,
629             (selectionOnly
630                ? XO("Exporting the selected audio as %s")
631                : XO("Exporting the audio as %s"))
632                .Format( formatStr ) );
633          auto &progress = *pDialog;
634 
635          while (updateResult == ProgressResult::Success) {
636             sf_count_t samplesWritten;
637             size_t numSamples = mixer->Process(maxBlockLen);
638 
639             if (numSamples == 0)
640                break;
641 
642             auto mixed = mixer->GetBuffer();
643 
644             // Bug 1572: Not ideal, but it does add the desired dither
645             if ((info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_24) {
646                for (int c = 0; c < info.channels; ++c) {
647                   CopySamples(
648                      mixed + (c * SAMPLE_SIZE(format)), format,
649                      dither.data() + (c * SAMPLE_SIZE(int24Sample)), int24Sample,
650                      numSamples, gHighQualityDither, info.channels, info.channels
651                   );
652                   // Copy back without dither
653                   CopySamples(
654                      dither.data() + (c * SAMPLE_SIZE(int24Sample)), int24Sample,
655                      const_cast<samplePtr>(mixed) // PRL fix this!
656                         + (c * SAMPLE_SIZE(format)), format,
657                      numSamples, DitherType::none, info.channels, info.channels);
658                }
659             }
660 
661             if (format == int16Sample)
662                samplesWritten = SFCall<sf_count_t>(sf_writef_short, sf.get(), (const short *)mixed, numSamples);
663             else
664                samplesWritten = SFCall<sf_count_t>(sf_writef_float, sf.get(), (const float *)mixed, numSamples);
665 
666             if (static_cast<size_t>(samplesWritten) != numSamples) {
667                char buffer2[1000];
668                sf_error_str(sf.get(), buffer2, 1000);
669                //Used to give this error message
670 #if 0
671                AudacityMessageBox(
672                   XO(
673                   /* i18n-hint: %s will be the error message from libsndfile, which
674                    * is usually something unhelpful (and untranslated) like "system
675                    * error" */
676 "Error while writing %s file (disk full?).\nLibsndfile says \"%s\"")
677                      .Format( formatStr, wxString::FromAscii(buffer2) ));
678 #else
679                // But better to give the same error message as for
680                // other cases of disk exhaustion.
681                // The thrown exception doesn't escape but GuardedCall
682                // will enqueue a message.
683                GuardedCall([&fName]{
684                   throw FileException{
685                      FileException::Cause::Write, fName }; });
686 #endif
687                updateResult = ProgressResult::Cancelled;
688                break;
689             }
690 
691             updateResult = progress.Update(mixer->MixGetCurrentTime() - t0, t1 - t0);
692          }
693       }
694 
695       // Install the WAV metata in a "LIST" chunk at the end of the file
696       if (updateResult == ProgressResult::Success ||
697           updateResult == ProgressResult::Stopped) {
698          if (fileFormat == SF_FORMAT_WAV ||
699              fileFormat == SF_FORMAT_WAVEX) {
700             if (!AddStrings(project, sf.get(), metadata, sf_format)) {
701                // TODO: more precise message
702                ShowExportErrorDialog("PCM:675");
703                return ProgressResult::Cancelled;
704             }
705          }
706          if (0 != sf.close()) {
707             // TODO: more precise message
708             ShowExportErrorDialog("PCM:681");
709             return ProgressResult::Cancelled;
710          }
711       }
712    }
713 
714    if (updateResult == ProgressResult::Success ||
715        updateResult == ProgressResult::Stopped)
716       if ((fileFormat == SF_FORMAT_AIFF) ||
717           (fileFormat == SF_FORMAT_WAV))
718          // Note: file has closed, and gets reopened and closed again here:
719          if (!AddID3Chunk(fName, metadata, sf_format) ) {
720             // TODO: more precise message
721             ShowExportErrorDialog("PCM:694");
722             return ProgressResult::Cancelled;
723          }
724 
725    return updateResult;
726 }
727 
AdjustString(const wxString & wxStr,int sf_format)728 ArrayOf<char> ExportPCM::AdjustString(const wxString & wxStr, int sf_format)
729 {
730    bool b_aiff = false;
731    if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
732          b_aiff = true;    // Apple AIFF file
733 
734    // We must convert the string to 7 bit ASCII
735    size_t  sz = wxStr.length();
736    if(sz == 0)
737       return {};
738    // Size for secure allocation in case of local wide char usage
739    size_t  sr = (sz+4) * 2;
740 
741    ArrayOf<char> pDest{ sr, true };
742    if (!pDest)
743       return {};
744    ArrayOf<char> pSrc{ sr, true };
745    if (!pSrc)
746       return {};
747 
748    if(wxStr.mb_str(wxConvISO8859_1))
749       strncpy(pSrc.get(), wxStr.mb_str(wxConvISO8859_1), sz);
750    else if(wxStr.mb_str())
751       strncpy(pSrc.get(), wxStr.mb_str(), sz);
752    else
753       return {};
754 
755    char *pD = pDest.get();
756    char *pS = pSrc.get();
757    unsigned char c;
758 
759    // ISO Latin to 7 bit ascii conversion table (best approximation)
760    static char aASCII7Table[256] = {
761       0x00, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
762       0x5f, 0x09, 0x0a, 0x5f, 0x0d, 0x5f, 0x5f, 0x5f,
763       0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
764       0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
765       0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
766       0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
767       0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
768       0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
769       0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
770       0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
771       0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
772       0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
773       0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
774       0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
775       0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
776       0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
777       0x45, 0x20, 0x2c, 0x53, 0x22, 0x2e, 0x2b, 0x2b,
778       0x5e, 0x25, 0x53, 0x28, 0x4f, 0x20, 0x5a, 0x20,
779       0x20, 0x27, 0x27, 0x22, 0x22, 0x2e, 0x2d, 0x5f,
780       0x22, 0x54, 0x73, 0x29, 0x6f, 0x20, 0x7a, 0x59,
781       0x20, 0x21, 0x63, 0x4c, 0x6f, 0x59, 0x7c, 0x53,
782       0x22, 0x43, 0x61, 0x22, 0x5f, 0x2d, 0x43, 0x2d,
783       0x6f, 0x7e, 0x32, 0x33, 0x27, 0x75, 0x50, 0x27,
784       0x2c, 0x31, 0x6f, 0x22, 0x5f, 0x5f, 0x5f, 0x3f,
785       0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x43,
786       0x45, 0x45, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
787       0x44, 0x4e, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x78,
788       0x4f, 0x55, 0x55, 0x55, 0x55, 0x59, 0x70, 0x53,
789       0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x63,
790       0x65, 0x65, 0x65, 0x65, 0x69, 0x69, 0x69, 0x69,
791       0x64, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x2f,
792       0x6f, 0x75, 0x75, 0x75, 0x75, 0x79, 0x70, 0x79
793    };
794 
795    size_t i;
796    for(i = 0; i < sr; i++) {
797       c = (unsigned char) *pS++;
798       *pD++ = aASCII7Table[c];
799       if(c == 0)
800          break;
801    }
802    *pD = '\0';
803 
804    if(b_aiff) {
805       int len = (int)strlen(pDest.get());
806       if((len % 2) != 0) {
807          // In case of an odd length string, add a space char
808          strcat(pDest.get(), " ");
809       }
810    }
811 
812    return pDest;
813 }
814 
AddStrings(AudacityProject * WXUNUSED (project),SNDFILE * sf,const Tags * tags,int sf_format)815 bool ExportPCM::AddStrings(AudacityProject * WXUNUSED(project), SNDFILE *sf, const Tags *tags, int sf_format)
816 {
817    if (tags->HasTag(TAG_TITLE)) {
818       auto ascii7Str = AdjustString(tags->GetTag(TAG_TITLE), sf_format);
819       if (ascii7Str) {
820          sf_set_string(sf, SF_STR_TITLE, ascii7Str.get());
821       }
822    }
823 
824    if (tags->HasTag(TAG_ALBUM)) {
825       auto ascii7Str = AdjustString(tags->GetTag(TAG_ALBUM), sf_format);
826       if (ascii7Str) {
827          sf_set_string(sf, SF_STR_ALBUM, ascii7Str.get());
828       }
829    }
830 
831    if (tags->HasTag(TAG_ARTIST)) {
832       auto ascii7Str = AdjustString(tags->GetTag(TAG_ARTIST), sf_format);
833       if (ascii7Str) {
834          sf_set_string(sf, SF_STR_ARTIST, ascii7Str.get());
835       }
836    }
837 
838    if (tags->HasTag(TAG_COMMENTS)) {
839       auto ascii7Str = AdjustString(tags->GetTag(TAG_COMMENTS), sf_format);
840       if (ascii7Str) {
841          sf_set_string(sf, SF_STR_COMMENT, ascii7Str.get());
842       }
843    }
844 
845    if (tags->HasTag(TAG_YEAR)) {
846       auto ascii7Str = AdjustString(tags->GetTag(TAG_YEAR), sf_format);
847       if (ascii7Str) {
848          sf_set_string(sf, SF_STR_DATE, ascii7Str.get());
849       }
850    }
851 
852    if (tags->HasTag(TAG_GENRE)) {
853       auto ascii7Str = AdjustString(tags->GetTag(TAG_GENRE), sf_format);
854       if (ascii7Str) {
855          sf_set_string(sf, SF_STR_GENRE, ascii7Str.get());
856       }
857    }
858 
859    if (tags->HasTag(TAG_COPYRIGHT)) {
860       auto ascii7Str = AdjustString(tags->GetTag(TAG_COPYRIGHT), sf_format);
861       if (ascii7Str) {
862          sf_set_string(sf, SF_STR_COPYRIGHT, ascii7Str.get());
863       }
864    }
865 
866    if (tags->HasTag(TAG_SOFTWARE)) {
867       auto ascii7Str = AdjustString(tags->GetTag(TAG_SOFTWARE), sf_format);
868       if (ascii7Str) {
869          sf_set_string(sf, SF_STR_SOFTWARE, ascii7Str.get());
870       }
871    }
872 
873    if (tags->HasTag(TAG_TRACK)) {
874       auto ascii7Str = AdjustString(tags->GetTag(TAG_TRACK), sf_format);
875       if (ascii7Str) {
876          sf_set_string(sf, SF_STR_TRACKNUMBER, ascii7Str.get());
877       }
878    }
879 
880    return true;
881 }
882 
883 #ifdef USE_LIBID3TAG
884 struct id3_tag_deleter {
operator ()id3_tag_deleter885    void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
886 };
887 using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
888 #endif
889 
AddID3Chunk(const wxFileNameWrapper & fName,const Tags * tags,int sf_format)890 bool ExportPCM::AddID3Chunk(
891    const wxFileNameWrapper &fName, const Tags *tags, int sf_format)
892 {
893 #ifdef USE_LIBID3TAG
894    id3_tag_holder tp { id3_tag_new() };
895 
896    for (const auto &pair : tags->GetRange()) {
897       const auto &n = pair.first;
898       const auto &v = pair.second;
899       const char *name = "TXXX";
900 
901       if (n.CmpNoCase(TAG_TITLE) == 0) {
902          name = ID3_FRAME_TITLE;
903       }
904       else if (n.CmpNoCase(TAG_ARTIST) == 0) {
905          name = ID3_FRAME_ARTIST;
906       }
907       else if (n.CmpNoCase(TAG_ALBUM) == 0) {
908          name = ID3_FRAME_ALBUM;
909       }
910       else if (n.CmpNoCase(TAG_YEAR) == 0) {
911          name = ID3_FRAME_YEAR;
912       }
913       else if (n.CmpNoCase(TAG_GENRE) == 0) {
914          name = ID3_FRAME_GENRE;
915       }
916       else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
917          name = ID3_FRAME_COMMENT;
918       }
919       else if (n.CmpNoCase(TAG_TRACK) == 0) {
920          name = ID3_FRAME_TRACK;
921       }
922       else if (n.CmpNoCase(wxT("composer")) == 0) {
923          name = "TCOM";
924       }
925 
926       struct id3_frame *frame = id3_frame_new(name);
927 
928       if (!n.IsAscii() || !v.IsAscii()) {
929          id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
930       }
931       else {
932          id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
933       }
934 
935       MallocString<id3_ucs4_t> ucs4{
936          id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
937 
938       if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
939          // A hack to get around iTunes not recognizing the comment.  The
940          // language defaults to XXX and, since it's not a valid language,
941          // iTunes just ignores the tag.  So, either set it to a valid language
942          // (which one???) or just clear it.  Unfortunately, there's no supported
943          // way of clearing the field, so do it directly.
944          id3_field *f = id3_frame_field(frame, 1);
945          memset(f->immediate.value, 0, sizeof(f->immediate.value));
946          id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
947       }
948       else if (strcmp(name, "TXXX") == 0) {
949          id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
950 
951          ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
952 
953          id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
954       }
955       else {
956          auto addr = ucs4.get();
957          id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
958       }
959 
960       id3_tag_attachframe(tp.get(), frame);
961    }
962 
963    tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
964 
965    // If this version of libid3tag supports it, use v2.3 ID3
966    // tags instead of the newer, but less well supported, v2.4
967    // that libid3tag uses by default.
968 #ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
969    tp->options |= ID3_TAG_OPTION_ID3V2_3;
970 #endif
971 
972    id3_length_t len;
973 
974    len = id3_tag_render(tp.get(), 0);
975    if (len == 0)
976       return true;
977 
978    if ((len % 2) != 0) len++;   // Length must be even.
979    ArrayOf<id3_byte_t> buffer { len, true };
980    if (buffer == NULL)
981       return false;
982 
983    // Zero all locations, for ending odd UTF16 content
984    // correctly, i.e., two '\0's at the end.
985 
986    id3_tag_render(tp.get(), buffer.get());
987 
988    wxFFile f(fName.GetFullPath(), wxT("r+b"));
989    if (f.IsOpened()) {
990       wxUint32 sz;
991 
992       sz = (wxUint32) len;
993       if (!f.SeekEnd(0))
994          return false;
995       if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV)
996          {
997             if (4 != f.Write("id3 ", 4))// Must be lower case for foobar2000.
998                return false ;
999          }
1000       else {
1001          if (4 != f.Write("ID3 ", 4))
1002             return false;
1003          sz = wxUINT32_SWAP_ON_LE(sz);
1004       }
1005       if (4 != f.Write(&sz, 4))
1006          return false;
1007 
1008       if (len != f.Write(buffer.get(), len))
1009          return false;
1010 
1011       sz = (wxUint32) f.Tell() - 8;
1012       if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
1013          sz = wxUINT32_SWAP_ON_LE(sz);
1014 
1015       if (!f.Seek(4))
1016          return false;
1017       if (4 != f.Write(&sz, 4))
1018          return false;
1019 
1020       if (!f.Flush())
1021          return false;
1022 
1023       if (!f.Close())
1024          return false;
1025    }
1026    else
1027       return false;
1028 #endif
1029    return true;
1030 }
1031 
OptionsCreate(ShuttleGui & S,int format)1032 void ExportPCM::OptionsCreate(ShuttleGui &S, int format)
1033 {
1034    switch (format)
1035    {
1036 #if defined(__WXMAC__)
1037       case FMT_AIFF:
1038 #endif
1039       case FMT_WAV:
1040       case FMT_OTHER:
1041          S.AddWindow(safenew ExportPCMOptions{ S.GetParent(), format });
1042       break;
1043 
1044       default:
1045          ExportPlugin::OptionsCreate(S, format);
1046       break;
1047    }
1048 }
1049 
GetFormat(int index)1050 wxString ExportPCM::GetFormat(int index)
1051 {
1052    if (index != FMT_OTHER)
1053    {
1054       return ExportPlugin::GetFormat(index);
1055    }
1056 
1057    // Get the saved type
1058    int typ = LoadOtherFormat() & SF_FORMAT_TYPEMASK;
1059 
1060    // Return the format name for that type
1061    return sf_header_shortname(typ);
1062 }
1063 
GetExtension(int index)1064 FileExtension ExportPCM::GetExtension(int index)
1065 {
1066    if (index != FMT_OTHER)
1067    {
1068       return ExportPlugin::GetExtension(index);
1069    }
1070 
1071    // Get the saved type
1072    int typ = LoadOtherFormat() & SF_FORMAT_TYPEMASK;
1073 
1074    // Return the extension for that type
1075    return sf_header_extension(typ);
1076 }
1077 
GetMaxChannels(int index)1078 unsigned ExportPCM::GetMaxChannels(int index)
1079 {
1080    SF_INFO si = {};
1081 
1082    if (index < FMT_OTHER)
1083    {
1084       si.format = kFormats[index].format;
1085    }
1086    else
1087    {
1088       // Get the saved type
1089       si.format = LoadOtherFormat() & SF_FORMAT_TYPEMASK;
1090       si.format |= LoadEncoding(si.format);
1091    }
1092 
1093    for (si.channels = 1; sf_format_check(&si); si.channels++)
1094    {
1095       // just counting
1096    }
1097 
1098    // Return the max number of channels
1099    return si.channels - 1;
1100 }
1101 
1102 static Exporter::RegisteredExportPlugin sRegisteredPlugin{ "PCM",
__anone2fcd5cd0402null1103    []{ return std::make_unique< ExportPCM >(); }
1104 };
1105