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#include <wx/defs.h>
12
13#include <wx/app.h>
14#include <wx/dynlib.h>
15#include <wx/filename.h>
16
17#include <sndfile.h>
18
19#include "Dither.h"
20#include "FileFormats.h"
21#include "Mix.h"
22#include "Prefs.h"
23#include "Tags.h"
24#include "Track.h"
25#include "wxFileNameWrapper.h"
26
27#include "Export.h"
28#include "ExportOptionsEditor.h"
29
30#include "ExportPluginHelpers.h"
32
33#ifdef USE_LIBID3TAG
34 #include <id3tag.h>
35 // DM: the following functions were supposed to have been
36 // included in id3tag.h - should be fixed in the next release
37 // of mad.
38 extern "C" {
39 struct id3_frame *id3_frame_new(char const *);
40 id3_length_t id3_latin1_length(id3_latin1_t const *);
41 void id3_latin1_decode(id3_latin1_t const *, id3_ucs4_t *);
42 }
43#endif
44
45namespace {
46
47struct
48{
49 int format;
50 const wxChar *name;
52}
53const kFormats[] =
54{
55#if defined(__WXMAC__)
56 {SF_FORMAT_AIFF | SF_FORMAT_PCM_16, wxT("AIFF"), XO("AIFF (Apple/SGI)")},
57#endif
58 {SF_FORMAT_WAV | SF_FORMAT_PCM_16, wxT("WAV"), XO("WAV (Microsoft)")},
59};
60
61enum
62{
63#if defined(__WXMAC__)
65#endif
68};
69
70int LoadOtherFormat(const audacity::BasicSettings& config, int def)
71{
72 return config.Read(wxString("/FileFormats/ExportFormat_SF1"), def);
73}
74
76{
77 config.Write(wxT("/FileFormats/ExportFormat_SF1"), val);
78}
79
80int LoadEncoding(const audacity::BasicSettings& config, int type, int def)
81{
82 return config.Read(wxString::Format(wxT("/FileFormats/ExportFormat_SF1_Type/%s_%x"),
83 sf_header_shortname(type), type), def);
84}
85
86void SaveEncoding(audacity::BasicSettings& config, int type, int val)
87{
88 config.Write(wxString::Format(wxT("/FileFormats/ExportFormat_SF1_Type/%s_%x"),
89 sf_header_shortname(type), type), val);
90}
91
92void GetEncodings(int type, std::vector<ExportValue>& values, TranslatableStrings& names)
93{
94 // Setup for queries
95 SF_INFO info = {};
96 info.samplerate = 44100;
97 info.channels = 1;
98 info.sections = 1;
99
100 for (int i = 0, num = sf_num_encodings(); i < num; ++i)
101 {
102 int sub = sf_encoding_index_to_subtype(i);
103
104 // Since we're traversing the subtypes linearly, we have to
105 // make sure it can be paired with our current type.
106 info.format = type | sub;
107 if (sf_format_check(&info))
108 {
109 // Store subtype and name
110 values.emplace_back(sub);
112 }
113 }
114}
115
116enum : int
117{
120
122{
123 const int mType;
126public:
127
128 explicit ExportOptionsSFTypedEditor(int type)
129 : mType(type)
130 {
131 GetEncodings(type, mEncodingOption.values, mEncodingOption.names);
132
133 mEncodingOption.id = type;
134 mEncodingOption.title = XO("Encoding");
135 mEncodingOption.flags = ExportOption::TypeEnum;
136 mEncodingOption.defaultValue = mEncodingOption.values[0];
137
138 mEncoding = *std::get_if<int>(&mEncodingOption.defaultValue);
139 }
140
141 int GetOptionsCount() const override
142 {
143 return 1;
144 }
145
146 bool GetOption(int, ExportOption& option) const override
147 {
148 option = mEncodingOption;
149 return true;
150 }
151
152 bool GetValue(ExportOptionID, ExportValue& value) const override
153 {
154 value = mEncoding;
155 return true;
156 }
157
158 bool SetValue(ExportOptionID, const ExportValue& value) override
159 {
160 if(std::find(mEncodingOption.values.begin(),
161 mEncodingOption.values.end(), value) != mEncodingOption.values.end())
162 {
163 mEncoding = *std::get_if<int>(&value);
164 return true;
165 }
166 return false;
167 }
168
170 {
171 return {};
172 }
173
174 void Load(const audacity::BasicSettings& config) override
175 {
176 mEncoding = LoadEncoding(config, mType, mEncoding);
177 }
178
179 void Store(audacity::BasicSettings& config) const override
180 {
181 SaveEncoding(config, mType, mEncoding);
182 }
183};
184
186{
188 int mType;
189 std::unordered_map<int, int> mEncodings;
190
191 std::vector<ExportOption> mOptions;
192
193 bool IsValidType(const ExportValue& typeValue) const
194 {
195 if(std::holds_alternative<int>(typeValue))
196 {
197 const auto& typeOption = mOptions.front();
198 return std::find(typeOption.values.begin(),
199 typeOption.values.end(),
200 typeValue) != typeOption.values.end();
201 }
202 return false;
203 }
204
205
206public:
207
208 explicit ExportOptionsSFEditor(Listener* listener)
209 : mListener(listener)
210 {
211 ExportOption typeOption {
212 OptionIDSFType, XO("Header"),
213 0,
215 };
216
217 auto hasDefaultType = false;
218 for (int i = 0, num = sf_num_headers(); i < num; ++i)
219 {
220 const auto type = static_cast<int>(sf_header_index_to_type(i));
221 switch (type)
222 {
223 // On the Mac, do not include in header list
224#if defined(__WXMAC__)
225 case SF_FORMAT_AIFF: break;
226#endif
227 // Do not include in header list
228 case SF_FORMAT_WAV: break;
229 default:
230 {
231 typeOption.values.emplace_back(type);
232 typeOption.names.push_back(Verbatim(sf_header_index_name(i)));
233 ExportOption encodingOption {
234 type,
235 XO("Encoding"),
236 0,
238 };
239 GetEncodings(type, encodingOption.values, encodingOption.names);
240 encodingOption.defaultValue = encodingOption.values[0];
241 if(!hasDefaultType)
242 {
243 mType = type;
244 typeOption.defaultValue = type;
245 hasDefaultType = true;
246 }
247 else
248 encodingOption.flags |= ExportOption::Hidden;
249 mOptions.push_back(std::move(encodingOption));
250 mEncodings[type] = *std::get_if<int>(&encodingOption.defaultValue);
251 } break;
252 }
253 }
254 typeOption.defaultValue = typeOption.values[0];
255 mOptions.insert(mOptions.begin(), std::move(typeOption));
256 }
257
258 int GetOptionsCount() const override
259 {
260 return static_cast<int>(mOptions.size());
261 }
262
263 bool GetOption(int index, ExportOption& option) const override
264 {
265 if (index >= 0 && index < static_cast<int>(mOptions.size()))
266 {
267 option = mOptions[index];
268 return true;
269 }
270 return false;
271 }
272
273 bool GetValue(ExportOptionID id, ExportValue& value) const override
274 {
275 if(id == OptionIDSFType)
276 {
277 value = mType;
278 return true;
279 }
280 auto it = mEncodings.find(id);
281 if(it != mEncodings.end())
282 {
283 value = it->second;
284 return true;
285 }
286 return false;
287 }
288
289 bool SetValue(ExportOptionID id, const ExportValue& value) override
290 {
291 if(id == OptionIDSFType && IsValidType(value))
292 {
293 const auto newType = *std::get_if<int>(&value);
294 if(newType == mType)
295 return true;
296
297 if(mListener)
298 mListener->OnExportOptionChangeBegin();
299
300 for(auto& option : mOptions)
301 {
302 if(option.id == mType)
303 {
304 option.flags |= ExportOption::Hidden;
305 if(mListener)
306 mListener->OnExportOptionChange(option);
307 }
308 else if(option.id == newType)
309 {
310 option.flags &= ~ExportOption::Hidden;
311 if(mListener)
312 mListener->OnExportOptionChange(option);
313 }
314 }
315 mType = newType;
316 Store(*gPrefs);
317 if(mListener)
318 {
319 mListener->OnExportOptionChangeEnd();
320 mListener->OnFormatInfoChange();
321 }
322 return true;
323 }
324
325 auto it = mEncodings.find(id);
326 if(it != mEncodings.end() && std::holds_alternative<int>(value))
327 {
328 it->second = *std::get_if<int>(&value);
329 Store(*gPrefs);
330 return true;
331 }
332 return false;
333 }
334
336 {
337 return {};
338 }
339
340 void Load(const audacity::BasicSettings& config) override
341 {
342 mType = LoadOtherFormat(config, mType);
343 for(auto& p : mEncodings)
344 p.second = LoadEncoding(config, p.first, p.second);
345
346 // Prior to v2.4.0, sf_format will include the subtype.
347 if (mType & SF_FORMAT_SUBMASK)
348 {
349 const auto type = mType & SF_FORMAT_TYPEMASK;
350 const auto enc = mType & SF_FORMAT_SUBMASK;
351 mEncodings[type] = enc;
352 mType = type;
353 }
354
355 for(auto& option : mOptions)
356 {
357 const auto it = mEncodings.find(option.id);
358 if(it == mEncodings.end())
359 continue;
360
361 if(mType == it->first)
362 option.flags &= ~ExportOption::Hidden;
363 else
364 option.flags |= ExportOption::Hidden;
365 }
366 }
367
368 void Store(audacity::BasicSettings& config) const override
369 {
370 SaveOtherFormat(config, mType);
371 for(auto& [type, encoding] : mEncodings)
372 SaveEncoding(config, type, encoding);
373 }
374};
375
376}
377
379{
380 constexpr static size_t maxBlockLen = 44100 * 5;
381
382 struct
383 {
385 double t0;
386 double t1;
387 std::unique_ptr<Mixer> mixer;
389 SF_INFO info;
391 wxFile f;
392 SNDFILE* sf;
396 std::unique_ptr<Tags> metadata;
398
399public:
400
402 {
403 context.sf = nullptr;
404 context.subformat = subformat;
405 }
406
408 {
409 if(context.f.IsOpened())
410 {
411 if(context.sf != nullptr)
412 sf_close(context.sf);
413 context.f.Close();
414 }
415 }
416
418 const Parameters& parameters,
419 const wxFileNameWrapper& filename,
420 double t0, double t1, bool selectedOnly,
421 double sampleRate, unsigned channels,
422 MixerOptions::Downmix* mixerSpec,
423 const Tags* tags) override;
424
425 ExportResult Process(ExportProcessorDelegate& delegate) override;
426
427private:
428
429 static ArrayOf<char> AdjustString(const wxString & wxStr, int sf_format);
430 static void AddStrings(SNDFILE *sf, const Tags *tags, int sf_format);
431 static bool AddID3Chunk(
432 const wxFileNameWrapper &fName, const Tags *tags, int sf_format);
433};
434
435class ExportPCM final : public ExportPlugin
436{
437public:
438
440
441 int GetFormatCount() const override;
442 FormatInfo GetFormatInfo(int index) const override;
443
444 std::vector<std::string> GetMimeTypes(int formatIndex) const override;
445
446 bool ParseConfig(int formatIndex, const rapidjson::Value&, ExportProcessor::Parameters& parameters) const override;
447
448 std::unique_ptr<ExportOptionsEditor>
450
456 std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
457};
458
459ExportPCM::ExportPCM() = default;
460
462{
463 return WXSIZEOF(kFormats) + 1;// + FMT_OTHER
464}
465
467{
468 if(index == FMT_OTHER)
469 {
470 SF_INFO si = {};
471 //VS: returned format info depends on the format that was used last time.
472 //That could be a source of unexpected behavior
473 si.format = LoadOtherFormat(*gPrefs, kFormats[0].format & SF_FORMAT_TYPEMASK);
474 si.format |= LoadEncoding(*gPrefs, si.format, kFormats[0].format);
475
476 for (si.channels = 1; sf_format_check(&si); si.channels++)
477 {
478 // just counting
479 }
480 --si.channels;
481
482 return {
483 sf_header_shortname(si.format),
484 XO("Other uncompressed files"),
485 { sf_header_extension(si.format) },
486 static_cast<unsigned>(si.channels),
487 true
488 };
489 }
490
491 if(!(index >= 0 && index < FMT_OTHER))
492 index = 0;
493
494 return {
495 kFormats[index].name,
496 kFormats[index].desc,
498 255,
499 true
500 };
501}
502
503std::vector<std::string> ExportPCM::GetMimeTypes(int formatIndex) const
504{
505 if(formatIndex == FMT_WAV)
506 return { "audio/x-wav" };
507 return {};
508}
509
510bool ExportPCM::ParseConfig(int formatIndex, const rapidjson::Value&, ExportProcessor::Parameters& parameters) const
511{
512 if(formatIndex == FMT_WAV)
513 {
514 //no parameters available...
515 parameters = {};
516 return true;
517 }
518 return false;
519}
520
521std::unique_ptr<ExportOptionsEditor>
523{
524 if(format < FMT_OTHER)
525 return std::make_unique<ExportOptionsSFTypedEditor>(
526 kFormats[format].format & SF_FORMAT_TYPEMASK);
527 return std::make_unique<ExportOptionsSFEditor>(listener);
528}
529
530std::unique_ptr<ExportProcessor> ExportPCM::CreateProcessor(int format) const
531{
532 return std::make_unique<PCMExportProcessor>(format);
533}
534
535
537 const Parameters& parameters,
538 const wxFileNameWrapper& fName,
539 double t0, double t1, bool selectionOnly,
540 double sampleRate, unsigned numChannels,
541 MixerOptions::Downmix* mixerSpec,
542 const Tags* metadata)
543{
544 context.t0 = t0;
545 context.t1 = t1;
546 context.fName = fName;
547
548 // Set a default in case the settings aren't found
549 int& sf_format = context.sf_format;
550
551 switch (context.subformat)
552 {
553#if defined(__WXMAC__)
554 case FMT_AIFF:
555 sf_format = SF_FORMAT_AIFF;
556 break;
557#endif
558
559 case FMT_WAV:
560 sf_format = SF_FORMAT_WAV;
561 break;
562
563 default:
564 // Retrieve the current format.
566 break;
567 }
568
570
571 // If subtype is still not specified, supply a default.
572 if (!(sf_format & SF_FORMAT_SUBMASK))
573 {
574 sf_format |= SF_FORMAT_PCM_16;
575 }
576
577 int& fileFormat = context.fileFormat;
578 fileFormat = sf_format & SF_FORMAT_TYPEMASK;
579
580 {
581 wxFile &f = context.f;
582 SNDFILE* &sf = context.sf;
583
584 wxString formatStr;
585 SF_INFO &info = context.info;
586 //int err;
587
588 //This whole operation should not occur while a file is being loaded on OD,
589 //(we are worried about reading from a file being written to,) so we block.
590 //Furthermore, we need to do this because libsndfile is not threadsafe.
591 formatStr = SFCall<wxString>(sf_header_name, fileFormat);
592
593 // Use libsndfile to export file
594
595 info.samplerate = (unsigned int)(sampleRate + 0.5);
596 info.frames = (unsigned int)((t1 - t0)*sampleRate + 0.5);
597 info.channels = numChannels;
598 info.format = sf_format;
599 info.sections = 1;
600 info.seekable = 0;
601
602 // Bug 46. Trap here, as sndfile.c does not trap it properly.
603 if( (numChannels != 1) && ((sf_format & SF_FORMAT_SUBMASK) == SF_FORMAT_GSM610) )
604 {
605 throw ExportException(_("GSM 6.10 requires mono"));
606 }
607
608 if (sf_format == SF_FORMAT_WAVEX + SF_FORMAT_GSM610) {
609 throw ExportException(_("WAVEX and GSM 6.10 formats are not compatible"));
610 }
611
612 // If we can't export exactly the format they requested,
613 // try the default format for that header type...
614 //
615 // LLL: I don't think this is valid since libsndfile checks
616 // for all allowed subtypes explicitly and doesn't provide
617 // for an unspecified subtype.
618 if (!sf_format_check(&info))
619 info.format = (info.format & SF_FORMAT_TYPEMASK);
620 if (!sf_format_check(&info)) {
621 throw ExportException(_("Cannot export audio in this format."));
622 }
623 const auto path = fName.GetFullPath();
624 if (f.Open(path, wxFile::write)) {
625 // Even though there is an sf_open() that takes a filename, use the one that
626 // takes a file descriptor since wxWidgets can open a file with a Unicode name and
627 // libsndfile can't (under Windows).
628 sf = sf_open_fd(f.fd(), SFM_WRITE, &info, FALSE);
629 //add clipping for integer formats. We allow floats to clip.
630 sf_command(sf, SFC_SET_CLIPPING, NULL, sf_subtype_is_integer(sf_format)?SF_TRUE:SF_FALSE) ;
631 }
632
633 if (!sf) {
634 throw ExportException(_("Cannot export audio to %s").Format( path ));
635 }
636 // Retrieve tags if not given a set
637 if (metadata == NULL)
639
640 // Install the meta data at the beginning of the file (except for
641 // WAV and WAVEX formats)
642 if (fileFormat != SF_FORMAT_WAV &&
643 fileFormat != SF_FORMAT_WAVEX) {
645 }
646 context.metadata = std::make_unique<Tags>(*metadata);
647
649 context.format = floatSample;
650 else
651 context.format = int16Sample;
652
653 // Bug 2200
654 // Only trap size limit for file types we know have an upper size limit.
655 // The error message mentions aiff and wav.
656 if( (fileFormat == SF_FORMAT_WAV) ||
657 (fileFormat == SF_FORMAT_WAVEX) ||
658 (fileFormat == SF_FORMAT_AIFF ))
659 {
660 float sampleCount = (float)(t1-t0)*sampleRate*info.channels;
661 float byteCount = sampleCount * sf_subtype_bytes_per_sample( info.format);
662 // Test for 4 Gibibytes, rather than 4 Gigabytes
663 if( byteCount > 4.295e9)
664 {
665 //Temporary translation hack, to say 'WAV or AIFF' rather than 'WAV'
666 const auto message =
667 XO("You have attempted to Export a WAV or AIFF file which would be greater than 4GB.\n"
668 "Audacity cannot do this, the Export was abandoned.");
669 throw ExportErrorException(message,
670 wxT("Size_limits_for_WAV_and_AIFF_files"));
671 }
672 }
673
674
675 context.status = (selectionOnly
676 ? XO("Exporting the selected audio as %s")
677 : XO("Exporting the audio as %s")).Format( formatStr );
678
679
680 wxASSERT(info.channels >= 0);
682 project, selectionOnly, t0, t1, info.channels, maxBlockLen, true,
683 sampleRate, context.format, mixerSpec);
684 }
685
686 return true;
687}
688
690{
691 delegate.SetStatusString(context.status);
692
693 auto exportResult = ExportResult::Success;
694
695 {
696 std::vector<char> dither;
697 if ((context.info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_24) {
698 dither.reserve(maxBlockLen * context.info.channels * SAMPLE_SIZE(int24Sample));
699 }
700
701 while (exportResult == ExportResult::Success) {
702 sf_count_t samplesWritten;
703 size_t numSamples = context.mixer->Process();
704 if (numSamples == 0)
705 break;
706
707 auto mixed = context.mixer->GetBuffer();
708
709 // Bug 1572: Not ideal, but it does add the desired dither
710 if ((context.info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_24) {
711 for (int c = 0; c < context.info.channels; ++c) {
713 mixed + (c * SAMPLE_SIZE(context.format)), context.format,
714 dither.data() + (c * SAMPLE_SIZE(int24Sample)), int24Sample,
715 numSamples, gHighQualityDither, context.info.channels, context.info.channels
716 );
717 // Copy back without dither
719 dither.data() + (c * SAMPLE_SIZE(int24Sample)), int24Sample,
720 const_cast<samplePtr>(mixed) // PRL fix this!
721 + (c * SAMPLE_SIZE(context.format)), context.format,
722 numSamples, DitherType::none, context.info.channels, context.info.channels);
723 }
724 }
725
726 if (context.format == int16Sample)
727 samplesWritten = SFCall<sf_count_t>(sf_writef_short, context.sf, (const short *)mixed, numSamples);
728 else
729 samplesWritten = SFCall<sf_count_t>(sf_writef_float, context.sf, (const float *)mixed, numSamples);
730
731 if (static_cast<size_t>(samplesWritten) != numSamples) {
732 char buffer2[1000];
733 sf_error_str(context.sf, buffer2, 1000);
734 //Used to give this error message
735#if 0
737 XO(
738 /* i18n-hint: %s will be the error message from libsndfile, which
739 * is usually something unhelpful (and untranslated) like "system
740 * error" */
741"Error while writing %s file (disk full?).\nLibsndfile says \"%s\"")
742 .Format( formatStr, wxString::FromAscii(buffer2) ));
743#else
744 // But better to give the same error message as for
745 // other cases of disk exhaustion.
746 // The thrown exception doesn't escape but GuardedCall
747 // will enqueue a message.
748 GuardedCall([&]{
749 throw FileException{
751#endif
752 exportResult = ExportResult::Error;
753 break;
754 }
755 if(exportResult == ExportResult::Success)
757 delegate, *context.mixer, context.t0, context.t1);
758 }
759 }
760
761 // Install the WAV metata in a "LIST" chunk at the end of the file
762 if (exportResult != ExportResult::Cancelled && exportResult != ExportResult::Error) {
763 if (context.fileFormat == SF_FORMAT_WAV ||
764 context.fileFormat == SF_FORMAT_WAVEX) {
765 AddStrings(context.sf, context.metadata.get(), context.sf_format);
766 }
767 }
768
769 if (0 != sf_close(context.sf)) {
770 // TODO: more precise message
771 throw ExportErrorException("PCM:681");
772 }
773
774 context.sf = nullptr;
775 context.f.Close();
776
777 if (exportResult != ExportResult::Cancelled && exportResult != ExportResult::Error)
778 {
779 if ((context.fileFormat == SF_FORMAT_AIFF) ||
780 (context.fileFormat == SF_FORMAT_WAV))
781 // Note: file has closed, and gets reopened and closed again here:
782 if (!AddID3Chunk(context.fName, context.metadata.get(), context.sf_format) ) {
783 // TODO: more precise message
784 throw ExportErrorException("PCM:694");
785 }
786 }
787 return exportResult;
788}
789
790ArrayOf<char> PCMExportProcessor::AdjustString(const wxString & wxStr, int sf_format)
791{
792 bool b_aiff = false;
793 if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
794 b_aiff = true; // Apple AIFF file
795
796 // We must convert the string to 7 bit ASCII
797 size_t sz = wxStr.length();
798 if(sz == 0)
799 return {};
800 // Size for secure allocation in case of local wide char usage
801 size_t sr = (sz+4) * 2;
802
803 ArrayOf<char> pDest{ sr, true };
804 if (!pDest)
805 return {};
806 ArrayOf<char> pSrc{ sr, true };
807 if (!pSrc)
808 return {};
809
810 if(wxStr.mb_str(wxConvISO8859_1))
811 strncpy(pSrc.get(), wxStr.mb_str(wxConvISO8859_1), sz);
812 else if(wxStr.mb_str())
813 strncpy(pSrc.get(), wxStr.mb_str(), sz);
814 else
815 return {};
816
817 char *pD = pDest.get();
818 char *pS = pSrc.get();
819 unsigned char c;
820
821 // ISO Latin to 7 bit ascii conversion table (best approximation)
822 static char aASCII7Table[256] = {
823 0x00, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
824 0x5f, 0x09, 0x0a, 0x5f, 0x0d, 0x5f, 0x5f, 0x5f,
825 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
826 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
827 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
828 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
829 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
830 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
831 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
832 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
833 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
834 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
835 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
836 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
837 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
838 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
839 0x45, 0x20, 0x2c, 0x53, 0x22, 0x2e, 0x2b, 0x2b,
840 0x5e, 0x25, 0x53, 0x28, 0x4f, 0x20, 0x5a, 0x20,
841 0x20, 0x27, 0x27, 0x22, 0x22, 0x2e, 0x2d, 0x5f,
842 0x22, 0x54, 0x73, 0x29, 0x6f, 0x20, 0x7a, 0x59,
843 0x20, 0x21, 0x63, 0x4c, 0x6f, 0x59, 0x7c, 0x53,
844 0x22, 0x43, 0x61, 0x22, 0x5f, 0x2d, 0x43, 0x2d,
845 0x6f, 0x7e, 0x32, 0x33, 0x27, 0x75, 0x50, 0x27,
846 0x2c, 0x31, 0x6f, 0x22, 0x5f, 0x5f, 0x5f, 0x3f,
847 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x43,
848 0x45, 0x45, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
849 0x44, 0x4e, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x78,
850 0x4f, 0x55, 0x55, 0x55, 0x55, 0x59, 0x70, 0x53,
851 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x63,
852 0x65, 0x65, 0x65, 0x65, 0x69, 0x69, 0x69, 0x69,
853 0x64, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x2f,
854 0x6f, 0x75, 0x75, 0x75, 0x75, 0x79, 0x70, 0x79
855 };
856
857 size_t i;
858 for(i = 0; i < sr; i++) {
859 c = (unsigned char) *pS++;
860 *pD++ = aASCII7Table[c];
861 if(c == 0)
862 break;
863 }
864 *pD = '\0';
865
866 if(b_aiff) {
867 int len = (int)strlen(pDest.get());
868 if((len % 2) != 0) {
869 // In case of an odd length string, add a space char
870 strcat(pDest.get(), " ");
871 }
872 }
873
874 return pDest;
875}
876
877void PCMExportProcessor::AddStrings(SNDFILE *sf, const Tags *tags, int sf_format)
878{
879 if (tags->HasTag(TAG_TITLE)) {
880 auto ascii7Str = AdjustString(tags->GetTag(TAG_TITLE), sf_format);
881 if (ascii7Str) {
882 sf_set_string(sf, SF_STR_TITLE, ascii7Str.get());
883 }
884 }
885
886 if (tags->HasTag(TAG_ALBUM)) {
887 auto ascii7Str = AdjustString(tags->GetTag(TAG_ALBUM), sf_format);
888 if (ascii7Str) {
889 sf_set_string(sf, SF_STR_ALBUM, ascii7Str.get());
890 }
891 }
892
893 if (tags->HasTag(TAG_ARTIST)) {
894 auto ascii7Str = AdjustString(tags->GetTag(TAG_ARTIST), sf_format);
895 if (ascii7Str) {
896 sf_set_string(sf, SF_STR_ARTIST, ascii7Str.get());
897 }
898 }
899
900 if (tags->HasTag(TAG_COMMENTS)) {
901 auto ascii7Str = AdjustString(tags->GetTag(TAG_COMMENTS), sf_format);
902 if (ascii7Str) {
903 sf_set_string(sf, SF_STR_COMMENT, ascii7Str.get());
904 }
905 }
906
907 if (tags->HasTag(TAG_YEAR)) {
908 auto ascii7Str = AdjustString(tags->GetTag(TAG_YEAR), sf_format);
909 if (ascii7Str) {
910 sf_set_string(sf, SF_STR_DATE, ascii7Str.get());
911 }
912 }
913
914 if (tags->HasTag(TAG_GENRE)) {
915 auto ascii7Str = AdjustString(tags->GetTag(TAG_GENRE), sf_format);
916 if (ascii7Str) {
917 sf_set_string(sf, SF_STR_GENRE, ascii7Str.get());
918 }
919 }
920
921 if (tags->HasTag(TAG_COPYRIGHT)) {
922 auto ascii7Str = AdjustString(tags->GetTag(TAG_COPYRIGHT), sf_format);
923 if (ascii7Str) {
924 sf_set_string(sf, SF_STR_COPYRIGHT, ascii7Str.get());
925 }
926 }
927
928 if (tags->HasTag(TAG_SOFTWARE)) {
929 auto ascii7Str = AdjustString(tags->GetTag(TAG_SOFTWARE), sf_format);
930 if (ascii7Str) {
931 sf_set_string(sf, SF_STR_SOFTWARE, ascii7Str.get());
932 }
933 }
934
935 if (tags->HasTag(TAG_TRACK)) {
936 auto ascii7Str = AdjustString(tags->GetTag(TAG_TRACK), sf_format);
937 if (ascii7Str) {
938 sf_set_string(sf, SF_STR_TRACKNUMBER, ascii7Str.get());
939 }
940 }
941}
942
943#ifdef USE_LIBID3TAG
944struct id3_tag_deleter {
945 void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
946};
947using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
948#endif
949
951 const wxFileNameWrapper &fName, const Tags *tags, int sf_format)
952{
953#ifdef USE_LIBID3TAG
954 id3_tag_holder tp { id3_tag_new() };
955
956 for (const auto &pair : tags->GetRange()) {
957 const auto &n = pair.first;
958 const auto &v = pair.second;
959 const char *name = "TXXX";
960
961 if (n.CmpNoCase(TAG_TITLE) == 0) {
962 name = ID3_FRAME_TITLE;
963 }
964 else if (n.CmpNoCase(TAG_ARTIST) == 0) {
965 name = ID3_FRAME_ARTIST;
966 }
967 else if (n.CmpNoCase(TAG_ALBUM) == 0) {
968 name = ID3_FRAME_ALBUM;
969 }
970 else if (n.CmpNoCase(TAG_YEAR) == 0) {
971 name = ID3_FRAME_YEAR;
972 }
973 else if (n.CmpNoCase(TAG_GENRE) == 0) {
974 name = ID3_FRAME_GENRE;
975 }
976 else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
977 name = ID3_FRAME_COMMENT;
978 }
979 else if (n.CmpNoCase(TAG_TRACK) == 0) {
980 name = ID3_FRAME_TRACK;
981 }
982 else if (n.CmpNoCase(wxT("composer")) == 0) {
983 name = "TCOM";
984 }
985
986 struct id3_frame *frame = id3_frame_new(name);
987
988 if (!n.IsAscii() || !v.IsAscii()) {
989 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
990 }
991 else {
992 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
993 }
994
996 id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
997
998 if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
999 // A hack to get around iTunes not recognizing the comment. The
1000 // language defaults to XXX and, since it's not a valid language,
1001 // iTunes just ignores the tag. So, either set it to a valid language
1002 // (which one???) or just clear it. Unfortunately, there's no supported
1003 // way of clearing the field, so do it directly.
1004 id3_field *f = id3_frame_field(frame, 1);
1005 memset(f->immediate.value, 0, sizeof(f->immediate.value));
1006 id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
1007 }
1008 else if (strcmp(name, "TXXX") == 0) {
1009 id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
1010
1011 ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
1012
1013 id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
1014 }
1015 else {
1016 auto addr = ucs4.get();
1017 id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
1018 }
1019
1020 id3_tag_attachframe(tp.get(), frame);
1021 }
1022
1023 tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
1024
1025 // If this version of libid3tag supports it, use v2.3 ID3
1026 // tags instead of the newer, but less well supported, v2.4
1027 // that libid3tag uses by default.
1028#ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
1029 tp->options |= ID3_TAG_OPTION_ID3V2_3;
1030#endif
1031
1032 id3_length_t len;
1033
1034 len = id3_tag_render(tp.get(), 0);
1035 if (len == 0)
1036 return true;
1037
1038 if ((len % 2) != 0) len++; // Length must be even.
1039 ArrayOf<id3_byte_t> buffer { len, true };
1040 if (buffer == NULL)
1041 return false;
1042
1043 // Zero all locations, for ending odd UTF16 content
1044 // correctly, i.e., two '\0's at the end.
1045
1046 id3_tag_render(tp.get(), buffer.get());
1047
1048 wxFFile f(fName.GetFullPath(), wxT("r+b"));
1049 if (f.IsOpened()) {
1050 wxUint32 sz;
1051
1052 sz = (wxUint32) len;
1053 if (!f.SeekEnd(0))
1054 return false;
1055 if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV)
1056 {
1057 if (4 != f.Write("id3 ", 4))// Must be lower case for foobar2000.
1058 return false ;
1059 }
1060 else {
1061 if (4 != f.Write("ID3 ", 4))
1062 return false;
1063 sz = wxUINT32_SWAP_ON_LE(sz);
1064 }
1065 if (4 != f.Write(&sz, 4))
1066 return false;
1067
1068 if (len != f.Write(buffer.get(), len))
1069 return false;
1070
1071 sz = (wxUint32) f.Tell() - 8;
1072 if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
1073 sz = wxUINT32_SWAP_ON_LE(sz);
1074
1075 if (!f.Seek(4))
1076 return false;
1077 if (4 != f.Write(&sz, 4))
1078 return false;
1079
1080 if (!f.Flush())
1081 return false;
1082
1083 if (!f.Close())
1084 return false;
1085 }
1086 else
1087 return false;
1088#endif
1089 return true;
1090}
1091
1093 []{ return std::make_unique< ExportPCM >(); }
1094};
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)
@ none
Definition: Dither.h:20
const wxChar * values
@ FMT_OTHER
static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin
Definition: ExportPCM.cpp:1092
int ExportOptionID
Definition: ExportTypes.h:21
std::variant< bool, int, double, std::string > ExportValue
A type of option values (parameters) used by exporting plugins.
Definition: ExportTypes.h:38
ExportResult
Definition: ExportTypes.h:24
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:42
unsigned int sf_header_index_to_type(int i)
Definition: FileFormats.cpp:54
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:90
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:79
int sf_num_headers()
Get the number of container formats supported by libsndfile.
Definition: FileFormats.cpp:32
int sf_num_encodings()
Get the number of data encodings libsndfile supports (in any container or none.
Definition: FileFormats.cpp:70
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
std::unique_ptr< Character[], freer > MallocString
Definition: MemoryX.h:148
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
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:57
#define SAMPLE_SIZE(SampleFormat)
Definition: SampleFormat.h:52
#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
static TranslatableStrings names
Definition: TagsEditor.cpp:153
const auto project
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
Listener object that is used to report on option changes.
virtual void OnExportOptionChangeEnd()=0
Called after OnExportOptionChange
virtual void OnFormatInfoChange()=0
Called when format extension change (usually in response parameter change)
virtual void OnExportOptionChangeBegin()=0
Called before OnExportOptionChange
virtual void OnExportOptionChange(const ExportOption &option)=0
Called when option change.
Editor objects are used to retrieve a set of export options, and configure exporting parameters accor...
std::vector< int > SampleRateList
FormatInfo GetFormatInfo(int index) const override
Returns FormatInfo structure for given index if it's valid, or a default one. FormatInfo::format isn'...
Definition: ExportPCM.cpp:466
int GetFormatCount() const override
Definition: ExportPCM.cpp:461
std::unique_ptr< ExportProcessor > CreateProcessor(int format) const override
Definition: ExportPCM.cpp:530
std::vector< std::string > GetMimeTypes(int formatIndex) const override
Definition: ExportPCM.cpp:503
std::unique_ptr< ExportOptionsEditor > CreateOptionsEditor(int, ExportOptionsEditor::Listener *) const override
Creates format-dependent options editor, that is used to create a valid set of parameters to be used ...
Definition: ExportPCM.cpp:522
bool ParseConfig(int formatIndex, const rapidjson::Value &, ExportProcessor::Parameters &parameters) const override
Attempt to parse configuration JSON object and produce a suitable set of parameters....
Definition: ExportPCM.cpp:510
static T GetParameterValue(const ExportProcessor::Parameters &parameters, int id, T defaultValue=T())
static ExportResult UpdateProgress(ExportProcessorDelegate &delegate, Mixer &mixer, double t0, double t1)
Sends progress update to delegate and retrieves state update from it. Typically used inside each expo...
static std::unique_ptr< Mixer > CreateMixer(const AudacityProject &project, bool selectionOnly, double startTime, double stopTime, unsigned numOutChannels, size_t outBufferSize, bool outInterleaved, double outRate, sampleFormat outFormat, MixerOptions::Downmix *mixerSpec)
virtual void SetStatusString(const TranslatableString &str)=0
std::vector< std::tuple< ExportOptionID, ExportValue > > Parameters
Definition: ExportPlugin.h:93
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
~PCMExportProcessor() override
Definition: ExportPCM.cpp:407
static ArrayOf< char > AdjustString(const wxString &wxStr, int sf_format)
Definition: ExportPCM.cpp:790
static void AddStrings(SNDFILE *sf, const Tags *tags, int sf_format)
Definition: ExportPCM.cpp:877
PCMExportProcessor(int subformat)
Definition: ExportPCM.cpp:401
TranslatableString status
Definition: ExportPCM.cpp:388
static bool AddID3Chunk(const wxFileNameWrapper &fName, const Tags *tags, int sf_format)
Definition: ExportPCM.cpp:950
std::unique_ptr< Tags > metadata
Definition: ExportPCM.cpp:396
bool Initialize(AudacityProject &project, const Parameters &parameters, const wxFileNameWrapper &filename, double t0, double t1, bool selectedOnly, double sampleRate, unsigned channels, MixerOptions::Downmix *mixerSpec, const Tags *tags) override
Called before start processing.
Definition: ExportPCM.cpp:536
ExportResult Process(ExportProcessorDelegate &delegate) override
Definition: ExportPCM.cpp:689
struct PCMExportProcessor::@184 context
sampleFormat format
Definition: ExportPCM.cpp:390
wxFileNameWrapper fName
Definition: ExportPCM.cpp:394
static constexpr size_t maxBlockLen
Definition: ExportPCM.cpp:380
std::unique_ptr< Mixer > mixer
Definition: ExportPCM.cpp:387
ID3 Tags (for MP3)
Definition: Tags.h:73
Iterators GetRange() const
Definition: Tags.cpp:426
bool HasTag(const wxString &name) const
Definition: Tags.cpp:397
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
wxString GetTag(const wxString &name) const
Definition: Tags.cpp:406
Holds a msgid for the translation catalog; may also bind format arguments.
void Load(const audacity::BasicSettings &config) override
Definition: ExportPCM.cpp:340
bool SetValue(ExportOptionID id, const ExportValue &value) override
Definition: ExportPCM.cpp:289
void Store(audacity::BasicSettings &config) const override
Definition: ExportPCM.cpp:368
bool GetValue(ExportOptionID id, ExportValue &value) const override
Definition: ExportPCM.cpp:273
bool IsValidType(const ExportValue &typeValue) const
Definition: ExportPCM.cpp:193
bool GetOption(int index, ExportOption &option) const override
Definition: ExportPCM.cpp:263
void Store(audacity::BasicSettings &config) const override
Definition: ExportPCM.cpp:179
bool GetValue(ExportOptionID, ExportValue &value) const override
Definition: ExportPCM.cpp:152
bool GetOption(int, ExportOption &option) const override
Definition: ExportPCM.cpp:146
void Load(const audacity::BasicSettings &config) override
Definition: ExportPCM.cpp:174
bool SetValue(ExportOptionID, const ExportValue &value) override
Definition: ExportPCM.cpp:158
Base class for objects that provide facility to store data persistently, and access it with string ke...
Definition: BasicSettings.h:31
virtual bool Write(const wxString &key, bool value)=0
virtual bool Read(const wxString &key, bool *value) const =0
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
void GetEncodings(int type, std::vector< ExportValue > &values, TranslatableStrings &names)
Definition: ExportPCM.cpp:92
void SaveEncoding(audacity::BasicSettings &config, int type, int val)
Definition: ExportPCM.cpp:86
int LoadEncoding(const audacity::BasicSettings &config, int type, int def)
Definition: ExportPCM.cpp:80
void SaveOtherFormat(audacity::BasicSettings &config, int val)
Definition: ExportPCM.cpp:75
const TranslatableString desc
Definition: ExportPCM.cpp:51
int LoadOtherFormat(const audacity::BasicSettings &config, int def)
Definition: ExportPCM.cpp:70
struct anonymous_namespace{ExportPCM.cpp}::@181 kFormats[]
A type that provides a description of an exporting option. Isn't allowed to change except non-type re...
Definition: ExportTypes.h:43
ExportOptionID id
Internal option id.
Definition: ExportTypes.h:56
@ TypeEnum
List/enum option. values holds items, and names text to be displayed.
Definition: ExportTypes.h:48
@ Hidden
Option is not used and may be hidden from the user.
Definition: ExportTypes.h:51
ExportValue defaultValue
Default valid value for the parameter.
Definition: ExportTypes.h:58
int flags
A set of flag that desc.
Definition: ExportTypes.h:59
std::vector< ExportValue > values
Interpretation depends on type.
Definition: ExportTypes.h:60
TranslatableString title
Name of an option in a human-readable form.
Definition: ExportTypes.h:57
TranslatableStrings names
Interpretation depends on type.
Definition: ExportTypes.h:61
wxString format
Definition: ExportPlugin.h:35