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/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
54struct
55{
56 int format;
57 const wxChar *name;
59}
60static 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
68enum
69{
70#if defined(__WXMAC__)
72#endif
75};
76
77//----------------------------------------------------------------------------
78// Statics
79//----------------------------------------------------------------------------
80
81static int LoadOtherFormat(int def = 0)
82{
83 return gPrefs->Read(wxT("/FileFormats/ExportFormat_SF1"),
84 kFormats[0].format & SF_FORMAT_TYPEMASK);
85}
86
87static void SaveOtherFormat(int val)
88{
89 gPrefs->Write(wxT("/FileFormats/ExportFormat_SF1"), val);
90 gPrefs->Flush();
91}
92
93static 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
99static 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
114{
115public:
116
117 ExportPCMOptions(wxWindow *parent, int format);
118 virtual ~ExportPCMOptions();
119
121
122 void OnShow(wxShowEvent & evt);
123 void OnHeaderChoice(wxCommandEvent & evt);
124 void OnEncodingChoice(wxCommandEvent & evt);
125
126private:
127
128 void GetTypes();
129 void GetEncodings(int enc = 0);
130 void SendSuffixEvent();
131
132private:
133
134 std::vector<int> mHeaderIndexes;
136 wxChoice *mHeaderChoice;
138
139 std::vector<int> mEncodingIndexes;
143
145 int mType;
146
147 DECLARE_EVENT_TABLE()
148};
149
150BEGIN_EVENT_TABLE(ExportPCMOptions, wxPanelWrapper)
154
155ExportPCMOptions::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
177 PopulateOrExchange(S);
178
179 parent->Bind(wxEVT_SHOW, &ExportPCMOptions::OnShow, this);
180}
181
183{
184 // Save the encoding
187}
188
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 {
201 .AddChoice(XXO("Header:"),
204 }
206 .AddChoice(XXO("Encoding:"),
209 }
210 S.EndMultiColumn();
211 }
212 S.EndHorizontalLay();
213 }
214 S.EndVerticalLay();
215
216 return;
217}
218
219void 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 {
229 }
230}
231
232void 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
241
242 // Save the newly selected type
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
257
258 // Send the event indicating a file suffix change.
260}
261
262void ExportPCMOptions::OnEncodingChoice(wxCommandEvent & evt)
263{
264 evt.Skip();
265
266 // Remember new selection
267 mEncodingFromChoice = evt.GetInt();
268
269 // And save it
271}
272
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
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 {
306 }
307
308 // Store index and name
309 mHeaderIndexes.push_back(i);
311 break;
312 }
313 }
314
315 // Refresh the current type
317}
318
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
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 {
360 }
361
362 // Store index and name
363 mEncodingIndexes.push_back(i);
365 }
366 }
367}
368
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
382class ExportPCM final : public ExportPlugin
383{
384public:
385
386 ExportPCM();
387
388 // Required
389
390 void OptionsCreate(ShuttleGui &S, int format) override;
392 std::unique_ptr<BasicUI::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
406private:
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
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;
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
434 SetFormat(wxT("LIBSNDFILE"), selformat);
435 SetDescription(XO("Other uncompressed files"), selformat);
436 SetCanMetaData(true, selformat);
437 SetMaxChannels(255, selformat);
438}
439
440void 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
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
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
468 std::unique_ptr<BasicUI::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") );
545 }
546
547 if (sf_format == SF_FORMAT_WAVEX + SF_FORMAT_GSM610) {
549 XO("WAVEX and GSM 6.10 formats are not compatible") );
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.") );
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 ) );
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)) {
589 }
590 }
591
593 if (sf_subtype_more_than_16_bits(info.format))
595 else
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();
638 if (numSamples == 0)
639 break;
640
641 auto mixed = mixer->GetBuffer();
642
643 // Bug 1572: Not ideal, but it does add the desired dither
644 if ((info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_24) {
645 for (int c = 0; c < info.channels; ++c) {
647 mixed + (c * SAMPLE_SIZE(format)), format,
648 dither.data() + (c * SAMPLE_SIZE(int24Sample)), int24Sample,
649 numSamples, gHighQualityDither, info.channels, info.channels
650 );
651 // Copy back without dither
653 dither.data() + (c * SAMPLE_SIZE(int24Sample)), int24Sample,
654 const_cast<samplePtr>(mixed) // PRL fix this!
655 + (c * SAMPLE_SIZE(format)), format,
656 numSamples, DitherType::none, info.channels, info.channels);
657 }
658 }
659
660 if (format == int16Sample)
661 samplesWritten = SFCall<sf_count_t>(sf_writef_short, sf.get(), (const short *)mixed, numSamples);
662 else
663 samplesWritten = SFCall<sf_count_t>(sf_writef_float, sf.get(), (const float *)mixed, numSamples);
664
665 if (static_cast<size_t>(samplesWritten) != numSamples) {
666 char buffer2[1000];
667 sf_error_str(sf.get(), buffer2, 1000);
668 //Used to give this error message
669#if 0
671 XO(
672 /* i18n-hint: %s will be the error message from libsndfile, which
673 * is usually something unhelpful (and untranslated) like "system
674 * error" */
675"Error while writing %s file (disk full?).\nLibsndfile says \"%s\"")
676 .Format( formatStr, wxString::FromAscii(buffer2) ));
677#else
678 // But better to give the same error message as for
679 // other cases of disk exhaustion.
680 // The thrown exception doesn't escape but GuardedCall
681 // will enqueue a message.
682 GuardedCall([&fName]{
683 throw FileException{
684 FileException::Cause::Write, fName }; });
685#endif
686 updateResult = ProgressResult::Cancelled;
687 break;
688 }
689
690 updateResult = progress.Poll(mixer->MixGetCurrentTime() - t0, t1 - t0);
691 }
692 }
693
694 // Install the WAV metata in a "LIST" chunk at the end of the file
695 if (updateResult == ProgressResult::Success ||
696 updateResult == ProgressResult::Stopped) {
697 if (fileFormat == SF_FORMAT_WAV ||
698 fileFormat == SF_FORMAT_WAVEX) {
699 if (!AddStrings(project, sf.get(), metadata, sf_format)) {
700 // TODO: more precise message
701 ShowExportErrorDialog("PCM:675");
703 }
704 }
705 if (0 != sf.close()) {
706 // TODO: more precise message
707 ShowExportErrorDialog("PCM:681");
709 }
710 }
711 }
712
713 if (updateResult == ProgressResult::Success ||
714 updateResult == ProgressResult::Stopped)
715 if ((fileFormat == SF_FORMAT_AIFF) ||
716 (fileFormat == SF_FORMAT_WAV))
717 // Note: file has closed, and gets reopened and closed again here:
718 if (!AddID3Chunk(fName, metadata, sf_format) ) {
719 // TODO: more precise message
720 ShowExportErrorDialog("PCM:694");
722 }
723
724 return updateResult;
725}
726
727ArrayOf<char> ExportPCM::AdjustString(const wxString & wxStr, int sf_format)
728{
729 bool b_aiff = false;
730 if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
731 b_aiff = true; // Apple AIFF file
732
733 // We must convert the string to 7 bit ASCII
734 size_t sz = wxStr.length();
735 if(sz == 0)
736 return {};
737 // Size for secure allocation in case of local wide char usage
738 size_t sr = (sz+4) * 2;
739
740 ArrayOf<char> pDest{ sr, true };
741 if (!pDest)
742 return {};
743 ArrayOf<char> pSrc{ sr, true };
744 if (!pSrc)
745 return {};
746
747 if(wxStr.mb_str(wxConvISO8859_1))
748 strncpy(pSrc.get(), wxStr.mb_str(wxConvISO8859_1), sz);
749 else if(wxStr.mb_str())
750 strncpy(pSrc.get(), wxStr.mb_str(), sz);
751 else
752 return {};
753
754 char *pD = pDest.get();
755 char *pS = pSrc.get();
756 unsigned char c;
757
758 // ISO Latin to 7 bit ascii conversion table (best approximation)
759 static char aASCII7Table[256] = {
760 0x00, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
761 0x5f, 0x09, 0x0a, 0x5f, 0x0d, 0x5f, 0x5f, 0x5f,
762 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
763 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
764 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
765 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
766 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
767 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
768 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
769 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
770 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
771 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
772 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
773 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
774 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
775 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
776 0x45, 0x20, 0x2c, 0x53, 0x22, 0x2e, 0x2b, 0x2b,
777 0x5e, 0x25, 0x53, 0x28, 0x4f, 0x20, 0x5a, 0x20,
778 0x20, 0x27, 0x27, 0x22, 0x22, 0x2e, 0x2d, 0x5f,
779 0x22, 0x54, 0x73, 0x29, 0x6f, 0x20, 0x7a, 0x59,
780 0x20, 0x21, 0x63, 0x4c, 0x6f, 0x59, 0x7c, 0x53,
781 0x22, 0x43, 0x61, 0x22, 0x5f, 0x2d, 0x43, 0x2d,
782 0x6f, 0x7e, 0x32, 0x33, 0x27, 0x75, 0x50, 0x27,
783 0x2c, 0x31, 0x6f, 0x22, 0x5f, 0x5f, 0x5f, 0x3f,
784 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x43,
785 0x45, 0x45, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
786 0x44, 0x4e, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x78,
787 0x4f, 0x55, 0x55, 0x55, 0x55, 0x59, 0x70, 0x53,
788 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x63,
789 0x65, 0x65, 0x65, 0x65, 0x69, 0x69, 0x69, 0x69,
790 0x64, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x2f,
791 0x6f, 0x75, 0x75, 0x75, 0x75, 0x79, 0x70, 0x79
792 };
793
794 size_t i;
795 for(i = 0; i < sr; i++) {
796 c = (unsigned char) *pS++;
797 *pD++ = aASCII7Table[c];
798 if(c == 0)
799 break;
800 }
801 *pD = '\0';
802
803 if(b_aiff) {
804 int len = (int)strlen(pDest.get());
805 if((len % 2) != 0) {
806 // In case of an odd length string, add a space char
807 strcat(pDest.get(), " ");
808 }
809 }
810
811 return pDest;
812}
813
814bool ExportPCM::AddStrings(AudacityProject * WXUNUSED(project), SNDFILE *sf, const Tags *tags, int sf_format)
815{
816 if (tags->HasTag(TAG_TITLE)) {
817 auto ascii7Str = AdjustString(tags->GetTag(TAG_TITLE), sf_format);
818 if (ascii7Str) {
819 sf_set_string(sf, SF_STR_TITLE, ascii7Str.get());
820 }
821 }
822
823 if (tags->HasTag(TAG_ALBUM)) {
824 auto ascii7Str = AdjustString(tags->GetTag(TAG_ALBUM), sf_format);
825 if (ascii7Str) {
826 sf_set_string(sf, SF_STR_ALBUM, ascii7Str.get());
827 }
828 }
829
830 if (tags->HasTag(TAG_ARTIST)) {
831 auto ascii7Str = AdjustString(tags->GetTag(TAG_ARTIST), sf_format);
832 if (ascii7Str) {
833 sf_set_string(sf, SF_STR_ARTIST, ascii7Str.get());
834 }
835 }
836
837 if (tags->HasTag(TAG_COMMENTS)) {
838 auto ascii7Str = AdjustString(tags->GetTag(TAG_COMMENTS), sf_format);
839 if (ascii7Str) {
840 sf_set_string(sf, SF_STR_COMMENT, ascii7Str.get());
841 }
842 }
843
844 if (tags->HasTag(TAG_YEAR)) {
845 auto ascii7Str = AdjustString(tags->GetTag(TAG_YEAR), sf_format);
846 if (ascii7Str) {
847 sf_set_string(sf, SF_STR_DATE, ascii7Str.get());
848 }
849 }
850
851 if (tags->HasTag(TAG_GENRE)) {
852 auto ascii7Str = AdjustString(tags->GetTag(TAG_GENRE), sf_format);
853 if (ascii7Str) {
854 sf_set_string(sf, SF_STR_GENRE, ascii7Str.get());
855 }
856 }
857
858 if (tags->HasTag(TAG_COPYRIGHT)) {
859 auto ascii7Str = AdjustString(tags->GetTag(TAG_COPYRIGHT), sf_format);
860 if (ascii7Str) {
861 sf_set_string(sf, SF_STR_COPYRIGHT, ascii7Str.get());
862 }
863 }
864
865 if (tags->HasTag(TAG_SOFTWARE)) {
866 auto ascii7Str = AdjustString(tags->GetTag(TAG_SOFTWARE), sf_format);
867 if (ascii7Str) {
868 sf_set_string(sf, SF_STR_SOFTWARE, ascii7Str.get());
869 }
870 }
871
872 if (tags->HasTag(TAG_TRACK)) {
873 auto ascii7Str = AdjustString(tags->GetTag(TAG_TRACK), sf_format);
874 if (ascii7Str) {
875 sf_set_string(sf, SF_STR_TRACKNUMBER, ascii7Str.get());
876 }
877 }
878
879 return true;
880}
881
882#ifdef USE_LIBID3TAG
883struct id3_tag_deleter {
884 void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
885};
886using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
887#endif
888
890 const wxFileNameWrapper &fName, const Tags *tags, int sf_format)
891{
892#ifdef USE_LIBID3TAG
893 id3_tag_holder tp { id3_tag_new() };
894
895 for (const auto &pair : tags->GetRange()) {
896 const auto &n = pair.first;
897 const auto &v = pair.second;
898 const char *name = "TXXX";
899
900 if (n.CmpNoCase(TAG_TITLE) == 0) {
901 name = ID3_FRAME_TITLE;
902 }
903 else if (n.CmpNoCase(TAG_ARTIST) == 0) {
904 name = ID3_FRAME_ARTIST;
905 }
906 else if (n.CmpNoCase(TAG_ALBUM) == 0) {
907 name = ID3_FRAME_ALBUM;
908 }
909 else if (n.CmpNoCase(TAG_YEAR) == 0) {
910 name = ID3_FRAME_YEAR;
911 }
912 else if (n.CmpNoCase(TAG_GENRE) == 0) {
913 name = ID3_FRAME_GENRE;
914 }
915 else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
916 name = ID3_FRAME_COMMENT;
917 }
918 else if (n.CmpNoCase(TAG_TRACK) == 0) {
919 name = ID3_FRAME_TRACK;
920 }
921 else if (n.CmpNoCase(wxT("composer")) == 0) {
922 name = "TCOM";
923 }
924
925 struct id3_frame *frame = id3_frame_new(name);
926
927 if (!n.IsAscii() || !v.IsAscii()) {
928 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
929 }
930 else {
931 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
932 }
933
935 id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
936
937 if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
938 // A hack to get around iTunes not recognizing the comment. The
939 // language defaults to XXX and, since it's not a valid language,
940 // iTunes just ignores the tag. So, either set it to a valid language
941 // (which one???) or just clear it. Unfortunately, there's no supported
942 // way of clearing the field, so do it directly.
943 id3_field *f = id3_frame_field(frame, 1);
944 memset(f->immediate.value, 0, sizeof(f->immediate.value));
945 id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
946 }
947 else if (strcmp(name, "TXXX") == 0) {
948 id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
949
950 ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
951
952 id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
953 }
954 else {
955 auto addr = ucs4.get();
956 id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
957 }
958
959 id3_tag_attachframe(tp.get(), frame);
960 }
961
962 tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
963
964 // If this version of libid3tag supports it, use v2.3 ID3
965 // tags instead of the newer, but less well supported, v2.4
966 // that libid3tag uses by default.
967#ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
968 tp->options |= ID3_TAG_OPTION_ID3V2_3;
969#endif
970
971 id3_length_t len;
972
973 len = id3_tag_render(tp.get(), 0);
974 if (len == 0)
975 return true;
976
977 if ((len % 2) != 0) len++; // Length must be even.
978 ArrayOf<id3_byte_t> buffer { len, true };
979 if (buffer == NULL)
980 return false;
981
982 // Zero all locations, for ending odd UTF16 content
983 // correctly, i.e., two '\0's at the end.
984
985 id3_tag_render(tp.get(), buffer.get());
986
987 wxFFile f(fName.GetFullPath(), wxT("r+b"));
988 if (f.IsOpened()) {
989 wxUint32 sz;
990
991 sz = (wxUint32) len;
992 if (!f.SeekEnd(0))
993 return false;
994 if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV)
995 {
996 if (4 != f.Write("id3 ", 4))// Must be lower case for foobar2000.
997 return false ;
998 }
999 else {
1000 if (4 != f.Write("ID3 ", 4))
1001 return false;
1002 sz = wxUINT32_SWAP_ON_LE(sz);
1003 }
1004 if (4 != f.Write(&sz, 4))
1005 return false;
1006
1007 if (len != f.Write(buffer.get(), len))
1008 return false;
1009
1010 sz = (wxUint32) f.Tell() - 8;
1011 if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
1012 sz = wxUINT32_SWAP_ON_LE(sz);
1013
1014 if (!f.Seek(4))
1015 return false;
1016 if (4 != f.Write(&sz, 4))
1017 return false;
1018
1019 if (!f.Flush())
1020 return false;
1021
1022 if (!f.Close())
1023 return false;
1024 }
1025 else
1026 return false;
1027#endif
1028 return true;
1029}
1030
1032{
1033 switch (format)
1034 {
1035#if defined(__WXMAC__)
1036 case FMT_AIFF:
1037#endif
1038 case FMT_WAV:
1039 case FMT_OTHER:
1040 S.AddWindow(safenew ExportPCMOptions{ S.GetParent(), format });
1041 break;
1042
1043 default:
1045 break;
1046 }
1047}
1048
1049wxString ExportPCM::GetFormat(int index)
1050{
1051 if (index != FMT_OTHER)
1052 {
1053 return ExportPlugin::GetFormat(index);
1054 }
1055
1056 // Get the saved type
1057 int typ = LoadOtherFormat() & SF_FORMAT_TYPEMASK;
1058
1059 // Return the format name for that type
1060 return sf_header_shortname(typ);
1061}
1062
1064{
1065 if (index != FMT_OTHER)
1066 {
1067 return ExportPlugin::GetExtension(index);
1068 }
1069
1070 // Get the saved type
1071 int typ = LoadOtherFormat() & SF_FORMAT_TYPEMASK;
1072
1073 // Return the extension for that type
1074 return sf_header_extension(typ);
1075}
1076
1077unsigned ExportPCM::GetMaxChannels(int index)
1078{
1079 SF_INFO si = {};
1080
1081 if (index < FMT_OTHER)
1082 {
1083 si.format = kFormats[index].format;
1084 }
1085 else
1086 {
1087 // Get the saved type
1088 si.format = LoadOtherFormat() & SF_FORMAT_TYPEMASK;
1089 si.format |= LoadEncoding(si.format);
1090 }
1091
1092 for (si.channels = 1; sf_format_check(&si); si.channels++)
1093 {
1094 // just counting
1095 }
1096
1097 // Return the max number of channels
1098 return si.channels - 1;
1099}
1100
1102 []{ return std::make_unique< ExportPCM >(); }
1103};
1104
1105#ifdef HAS_CLOUD_UPLOAD
1106# include "CloudExporterPlugin.h"
1107# include "CloudExportersRegistry.h"
1108
1109class PCMCloudHelper : public cloud::CloudExporterPlugin
1110{
1111public:
1112 wxString GetExporterID() const override
1113 {
1114 return "WAV";
1115 }
1116
1117 FileExtension GetFileExtension() const override
1118 {
1119 return "wav";
1120 }
1121
1122 void OnBeforeExport() override
1123 {
1124 }
1125
1126}; // WavPackCloudHelper
1127
1128static bool cloudExporterRegisterd = cloud::RegisterCloudExporter(
1129 "audio/x-wav",
1130 [](const AudacityProject&) { return std::make_unique<PCMCloudHelper>(); });
1131#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:1538
static int LoadEncoding(int type)
Definition: ExportPCM.cpp:93
const TranslatableString desc
Definition: ExportPCM.cpp:58
int format
Definition: ExportPCM.cpp:56
static void SaveEncoding(int type, int val)
Definition: ExportPCM.cpp:99
static void SaveOtherFormat(int val)
Definition: ExportPCM.cpp:87
static Exporter::RegisteredExportPlugin sRegisteredPlugin
Definition: ExportPCM.cpp:1101
struct @46 kFormats[]
#define ID_ENCODING_CHOICE
Definition: ExportPCM.cpp:111
#define ID_HEADER_CHOICE
Definition: ExportPCM.cpp:110
static int LoadOtherFormat(int def=0)
Definition: ExportPCM.cpp:81
@ FMT_AIFF
Definition: ExportPCM.cpp:71
@ FMT_WAV
Definition: ExportPCM.cpp:73
@ FMT_OTHER
Definition: ExportPCM.cpp:74
const wxChar * name
Definition: ExportPCM.cpp:57
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:47
unsigned int sf_header_index_to_type(int i)
Definition: FileFormats.cpp:59
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:95
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:84
int sf_num_headers()
Get the number of container formats supported by libsndfile.
Definition: FileFormats.cpp:37
int sf_num_encodings()
Get the number of data encodings libsndfile supports (in any container or none.
Definition: FileFormats.cpp:75
wxString FileExtension
File extension, not including any leading dot.
Definition: Identifier.h:224
#define XXO(s)
Definition: Internat.h:44
#define XO(s)
Definition: Internat.h:31
std::unique_ptr< Character[], freer > MallocString
Definition: MemoryX.h:146
#define safenew
Definition: MemoryX.h:10
FileConfig * gPrefs
Definition: Prefs.cpp:71
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
Definition: SampleFormat.h:29
@ floatSample
Definition: SampleFormat.h:34
@ int16Sample
Definition: SampleFormat.h:32
@ int24Sample
Definition: SampleFormat.h:33
char * samplePtr
Definition: SampleFormat.h:49
#define SAMPLE_SIZE(SampleFormat)
Definition: SampleFormat.h:44
@ eIsCreatingFromPrefs
Definition: ShuttleGui.h:48
#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:89
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:467
bool AddID3Chunk(const wxFileNameWrapper &fName, const Tags *tags, int sf_format)
Definition: ExportPCM.cpp:889
unsigned GetMaxChannels(int index) override
Definition: ExportPCM.cpp:1077
wxString GetFormat(int index) override
Definition: ExportPCM.cpp:1049
void OptionsCreate(ShuttleGui &S, int format) override
Definition: ExportPCM.cpp:1031
bool AddStrings(AudacityProject *project, SNDFILE *sf, const Tags *tags, int sf_format)
Definition: ExportPCM.cpp:814
void ReportTooBigError(wxWindow *pParent)
Definition: ExportPCM.cpp:440
ArrayOf< char > AdjustString(const wxString &wxStr, int sf_format)
Definition: ExportPCM.cpp:727
FileExtension GetExtension(int index) override
Return the (first) file name extension for the sub-format.
Definition: ExportPCM.cpp:1063
void SendSuffixEvent()
Definition: ExportPCM.cpp:369
void OnShow(wxShowEvent &evt)
Definition: ExportPCM.cpp:219
ExportPCMOptions(wxWindow *parent, int format)
Definition: ExportPCM.cpp:155
virtual ~ExportPCMOptions()
Definition: ExportPCM.cpp:182
TranslatableStrings mEncodingNames
Definition: ExportPCM.cpp:140
std::vector< int > mHeaderIndexes
Definition: ExportPCM.cpp:134
std::vector< int > mEncodingIndexes
Definition: ExportPCM.cpp:139
void OnEncodingChoice(wxCommandEvent &evt)
Definition: ExportPCM.cpp:262
void PopulateOrExchange(ShuttleGui &S)
Definition: ExportPCM.cpp:189
TranslatableStrings mHeaderNames
Definition: ExportPCM.cpp:135
wxChoice * mHeaderChoice
Definition: ExportPCM.cpp:136
void GetEncodings(int enc=0)
Definition: ExportPCM.cpp:319
void OnHeaderChoice(wxCommandEvent &evt)
Definition: ExportPCM.cpp:232
wxChoice * mEncodingChoice
Definition: ExportPCM.cpp:141
virtual void OptionsCreate(ShuttleGui &S, int format)=0
Definition: Export.cpp:210
virtual FileExtension GetExtension(int index=0)
Return the (first) file name extension for the sub-format.
Definition: Export.cpp:163
void AddExtension(const FileExtension &extension, int index)
Definition: Export.cpp:128
int AddFormat()
Add a NEW entry to the list of formats this plug-in can export.
Definition: Export.cpp:102
virtual wxString GetFormat(int index)
Definition: Export.cpp:153
static void InitProgress(std::unique_ptr< BasicUI::ProgressDialog > &pDialog, const TranslatableString &title, const TranslatableString &message)
Definition: Export.cpp:252
void SetFormat(const wxString &format, int index)
Definition: Export.cpp:118
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:224
void SetDescription(const TranslatableString &description, int index)
Definition: Export.cpp:123
void SetCanMetaData(bool canmetadata, int index)
Definition: Export.cpp:148
void SetMaxChannels(unsigned maxchannels, unsigned index)
Definition: Export.cpp:143
void SetExtensions(FileExtensions extensions, int index)
Definition: Export.cpp:133
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:628
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:486
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:145
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:254
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.