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