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<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<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(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) {
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
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
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");
704 }
705 }
706 if (0 != sf.close()) {
707 // TODO: more precise message
708 ShowExportErrorDialog("PCM:681");
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");
723 }
724
725 return updateResult;
726}
727
728ArrayOf<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
815bool 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
884struct id3_tag_deleter {
885 void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
886};
887using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
888#endif
889
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
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
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:
1046 break;
1047 }
1048}
1049
1050wxString 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
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
1078unsigned 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
1103 []{ return std::make_unique< ExportPCM >(); }
1104};
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:1520
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:1102
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
bool AddID3Chunk(const wxFileNameWrapper &fName, const Tags *tags, int sf_format)
Definition: ExportPCM.cpp:890
unsigned GetMaxChannels(int index) override
Definition: ExportPCM.cpp:1078
wxString GetFormat(int index) override
Definition: ExportPCM.cpp:1050
void OptionsCreate(ShuttleGui &S, int format) override
Definition: ExportPCM.cpp:1032
bool AddStrings(AudacityProject *project, SNDFILE *sf, const Tags *tags, int sf_format)
Definition: ExportPCM.cpp:815
void ReportTooBigError(wxWindow *pParent)
Definition: ExportPCM.cpp:440
ArrayOf< char > AdjustString(const wxString &wxStr, int sf_format)
Definition: ExportPCM.cpp:728
ProgressResult Export(AudacityProject *project, std::unique_ptr< 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
FileExtension GetExtension(int index) override
Return the (first) file name extension for the sub-format.
Definition: ExportPCM.cpp:1064
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:209
virtual FileExtension GetExtension(int index=0)
Return the (first) file name extension for the sub-format.
Definition: Export.cpp:162
void AddExtension(const FileExtension &extension, int index)
Definition: Export.cpp:127
int AddFormat()
Add a NEW entry to the list of formats this plug-in can export.
Definition: Export.cpp:101
virtual wxString GetFormat(int index)
Definition: Export.cpp:152
void SetFormat(const wxString &format, int index)
Definition: Export.cpp:117
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:223
void SetDescription(const TranslatableString &description, int index)
Definition: Export.cpp:122
void SetCanMetaData(bool canmetadata, int index)
Definition: Export.cpp:147
static void InitProgress(std::unique_ptr< ProgressDialog > &pDialog, const TranslatableString &title, const TranslatableString &message)
Definition: Export.cpp:251
void SetMaxChannels(unsigned maxchannels, unsigned index)
Definition: Export.cpp:142
void SetExtensions(FileExtensions extensions, int index)
Definition: Export.cpp:132
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.
Class used with Mixer.
Definition: Mix.h:37
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.
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:18
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:241
int close()
Definition: FileFormats.h:150
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.