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 const auto &tracks = TrackList::Get( project );
549
550 // Set a default in case the settings aren't found
551 int& sf_format = context.sf_format;
552
553 switch (context.subformat)
554 {
555#if defined(__WXMAC__)
556 case FMT_AIFF:
557 sf_format = SF_FORMAT_AIFF;
558 break;
559#endif
560
561 case FMT_WAV:
562 sf_format = SF_FORMAT_WAV;
563 break;
564
565 default:
566 // Retrieve the current format.
568 break;
569 }
570
572
573 // If subtype is still not specified, supply a default.
574 if (!(sf_format & SF_FORMAT_SUBMASK))
575 {
576 sf_format |= SF_FORMAT_PCM_16;
577 }
578
579 int& fileFormat = context.fileFormat;
580 fileFormat = sf_format & SF_FORMAT_TYPEMASK;
581
582 {
583 wxFile &f = context.f;
584 SNDFILE* &sf = context.sf;
585
586 wxString formatStr;
587 SF_INFO &info = context.info;
588 //int err;
589
590 //This whole operation should not occur while a file is being loaded on OD,
591 //(we are worried about reading from a file being written to,) so we block.
592 //Furthermore, we need to do this because libsndfile is not threadsafe.
593 formatStr = SFCall<wxString>(sf_header_name, fileFormat);
594
595 // Use libsndfile to export file
596
597 info.samplerate = (unsigned int)(sampleRate + 0.5);
598 info.frames = (unsigned int)((t1 - t0)*sampleRate + 0.5);
599 info.channels = numChannels;
600 info.format = sf_format;
601 info.sections = 1;
602 info.seekable = 0;
603
604 // Bug 46. Trap here, as sndfile.c does not trap it properly.
605 if( (numChannels != 1) && ((sf_format & SF_FORMAT_SUBMASK) == SF_FORMAT_GSM610) )
606 {
607 throw ExportException(_("GSM 6.10 requires mono"));
608 }
609
610 if (sf_format == SF_FORMAT_WAVEX + SF_FORMAT_GSM610) {
611 throw ExportException(_("WAVEX and GSM 6.10 formats are not compatible"));
612 }
613
614 // If we can't export exactly the format they requested,
615 // try the default format for that header type...
616 //
617 // LLL: I don't think this is valid since libsndfile checks
618 // for all allowed subtypes explicitly and doesn't provide
619 // for an unspecified subtype.
620 if (!sf_format_check(&info))
621 info.format = (info.format & SF_FORMAT_TYPEMASK);
622 if (!sf_format_check(&info)) {
623 throw ExportException(_("Cannot export audio in this format."));
624 }
625 const auto path = fName.GetFullPath();
626 if (f.Open(path, wxFile::write)) {
627 // Even though there is an sf_open() that takes a filename, use the one that
628 // takes a file descriptor since wxWidgets can open a file with a Unicode name and
629 // libsndfile can't (under Windows).
630 sf = sf_open_fd(f.fd(), SFM_WRITE, &info, FALSE);
631 //add clipping for integer formats. We allow floats to clip.
632 sf_command(sf, SFC_SET_CLIPPING, NULL, sf_subtype_is_integer(sf_format)?SF_TRUE:SF_FALSE) ;
633 }
634
635 if (!sf) {
636 throw ExportException(_("Cannot export audio to %s").Format( path ));
637 }
638 // Retrieve tags if not given a set
639 if (metadata == NULL)
641
642 // Install the meta data at the beginning of the file (except for
643 // WAV and WAVEX formats)
644 if (fileFormat != SF_FORMAT_WAV &&
645 fileFormat != SF_FORMAT_WAVEX) {
647 }
648 context.metadata = std::make_unique<Tags>(*metadata);
649
651 context.format = floatSample;
652 else
653 context.format = int16Sample;
654
655 // Bug 2200
656 // Only trap size limit for file types we know have an upper size limit.
657 // The error message mentions aiff and wav.
658 if( (fileFormat == SF_FORMAT_WAV) ||
659 (fileFormat == SF_FORMAT_WAVEX) ||
660 (fileFormat == SF_FORMAT_AIFF ))
661 {
662 float sampleCount = (float)(t1-t0)*sampleRate*info.channels;
663 float byteCount = sampleCount * sf_subtype_bytes_per_sample( info.format);
664 // Test for 4 Gibibytes, rather than 4 Gigabytes
665 if( byteCount > 4.295e9)
666 {
667 //Temporary translation hack, to say 'WAV or AIFF' rather than 'WAV'
668 const auto message =
669 XO("You have attempted to Export a WAV or AIFF file which would be greater than 4GB.\n"
670 "Audacity cannot do this, the Export was abandoned.");
671 throw ExportErrorException(message,
672 wxT("Size_limits_for_WAV_and_AIFF_files"));
673 }
674 }
675
676
677 context.status = (selectionOnly
678 ? XO("Exporting the selected audio as %s")
679 : XO("Exporting the audio as %s")).Format( formatStr );
680
681
682 wxASSERT(info.channels >= 0);
683 context.mixer = ExportPluginHelpers::CreateMixer(tracks, selectionOnly,
684 t0, t1,
685 info.channels, maxBlockLen, true,
686 sampleRate, context.format, mixerSpec);
687 }
688
689 return true;
690}
691
693{
694 delegate.SetStatusString(context.status);
695
696 auto exportResult = ExportResult::Success;
697
698 {
699 std::vector<char> dither;
700 if ((context.info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_24) {
701 dither.reserve(maxBlockLen * context.info.channels * SAMPLE_SIZE(int24Sample));
702 }
703
704 while (exportResult == ExportResult::Success) {
705 sf_count_t samplesWritten;
706 size_t numSamples = context.mixer->Process();
707 if (numSamples == 0)
708 break;
709
710 auto mixed = context.mixer->GetBuffer();
711
712 // Bug 1572: Not ideal, but it does add the desired dither
713 if ((context.info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_24) {
714 for (int c = 0; c < context.info.channels; ++c) {
716 mixed + (c * SAMPLE_SIZE(context.format)), context.format,
717 dither.data() + (c * SAMPLE_SIZE(int24Sample)), int24Sample,
718 numSamples, gHighQualityDither, context.info.channels, context.info.channels
719 );
720 // Copy back without dither
722 dither.data() + (c * SAMPLE_SIZE(int24Sample)), int24Sample,
723 const_cast<samplePtr>(mixed) // PRL fix this!
724 + (c * SAMPLE_SIZE(context.format)), context.format,
725 numSamples, DitherType::none, context.info.channels, context.info.channels);
726 }
727 }
728
729 if (context.format == int16Sample)
730 samplesWritten = SFCall<sf_count_t>(sf_writef_short, context.sf, (const short *)mixed, numSamples);
731 else
732 samplesWritten = SFCall<sf_count_t>(sf_writef_float, context.sf, (const float *)mixed, numSamples);
733
734 if (static_cast<size_t>(samplesWritten) != numSamples) {
735 char buffer2[1000];
736 sf_error_str(context.sf, buffer2, 1000);
737 //Used to give this error message
738#if 0
740 XO(
741 /* i18n-hint: %s will be the error message from libsndfile, which
742 * is usually something unhelpful (and untranslated) like "system
743 * error" */
744"Error while writing %s file (disk full?).\nLibsndfile says \"%s\"")
745 .Format( formatStr, wxString::FromAscii(buffer2) ));
746#else
747 // But better to give the same error message as for
748 // other cases of disk exhaustion.
749 // The thrown exception doesn't escape but GuardedCall
750 // will enqueue a message.
751 GuardedCall([&]{
752 throw FileException{
754#endif
755 exportResult = ExportResult::Error;
756 break;
757 }
758 if(exportResult == ExportResult::Success)
760 delegate, *context.mixer, context.t0, context.t1);
761 }
762 }
763
764 // Install the WAV metata in a "LIST" chunk at the end of the file
765 if (exportResult != ExportResult::Cancelled && exportResult != ExportResult::Error) {
766 if (context.fileFormat == SF_FORMAT_WAV ||
767 context.fileFormat == SF_FORMAT_WAVEX) {
768 AddStrings(context.sf, context.metadata.get(), context.sf_format);
769 }
770 }
771
772 if (0 != sf_close(context.sf)) {
773 // TODO: more precise message
774 throw ExportErrorException("PCM:681");
775 }
776
777 context.sf = nullptr;
778 context.f.Close();
779
780 if (exportResult != ExportResult::Cancelled && exportResult != ExportResult::Error)
781 {
782 if ((context.fileFormat == SF_FORMAT_AIFF) ||
783 (context.fileFormat == SF_FORMAT_WAV))
784 // Note: file has closed, and gets reopened and closed again here:
785 if (!AddID3Chunk(context.fName, context.metadata.get(), context.sf_format) ) {
786 // TODO: more precise message
787 throw ExportErrorException("PCM:694");
788 }
789 }
790 return exportResult;
791}
792
793ArrayOf<char> PCMExportProcessor::AdjustString(const wxString & wxStr, int sf_format)
794{
795 bool b_aiff = false;
796 if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
797 b_aiff = true; // Apple AIFF file
798
799 // We must convert the string to 7 bit ASCII
800 size_t sz = wxStr.length();
801 if(sz == 0)
802 return {};
803 // Size for secure allocation in case of local wide char usage
804 size_t sr = (sz+4) * 2;
805
806 ArrayOf<char> pDest{ sr, true };
807 if (!pDest)
808 return {};
809 ArrayOf<char> pSrc{ sr, true };
810 if (!pSrc)
811 return {};
812
813 if(wxStr.mb_str(wxConvISO8859_1))
814 strncpy(pSrc.get(), wxStr.mb_str(wxConvISO8859_1), sz);
815 else if(wxStr.mb_str())
816 strncpy(pSrc.get(), wxStr.mb_str(), sz);
817 else
818 return {};
819
820 char *pD = pDest.get();
821 char *pS = pSrc.get();
822 unsigned char c;
823
824 // ISO Latin to 7 bit ascii conversion table (best approximation)
825 static char aASCII7Table[256] = {
826 0x00, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
827 0x5f, 0x09, 0x0a, 0x5f, 0x0d, 0x5f, 0x5f, 0x5f,
828 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
829 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
830 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
831 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
832 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
833 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
834 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
835 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
836 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
837 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
838 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
839 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
840 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
841 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
842 0x45, 0x20, 0x2c, 0x53, 0x22, 0x2e, 0x2b, 0x2b,
843 0x5e, 0x25, 0x53, 0x28, 0x4f, 0x20, 0x5a, 0x20,
844 0x20, 0x27, 0x27, 0x22, 0x22, 0x2e, 0x2d, 0x5f,
845 0x22, 0x54, 0x73, 0x29, 0x6f, 0x20, 0x7a, 0x59,
846 0x20, 0x21, 0x63, 0x4c, 0x6f, 0x59, 0x7c, 0x53,
847 0x22, 0x43, 0x61, 0x22, 0x5f, 0x2d, 0x43, 0x2d,
848 0x6f, 0x7e, 0x32, 0x33, 0x27, 0x75, 0x50, 0x27,
849 0x2c, 0x31, 0x6f, 0x22, 0x5f, 0x5f, 0x5f, 0x3f,
850 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x43,
851 0x45, 0x45, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
852 0x44, 0x4e, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x78,
853 0x4f, 0x55, 0x55, 0x55, 0x55, 0x59, 0x70, 0x53,
854 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x63,
855 0x65, 0x65, 0x65, 0x65, 0x69, 0x69, 0x69, 0x69,
856 0x64, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x2f,
857 0x6f, 0x75, 0x75, 0x75, 0x75, 0x79, 0x70, 0x79
858 };
859
860 size_t i;
861 for(i = 0; i < sr; i++) {
862 c = (unsigned char) *pS++;
863 *pD++ = aASCII7Table[c];
864 if(c == 0)
865 break;
866 }
867 *pD = '\0';
868
869 if(b_aiff) {
870 int len = (int)strlen(pDest.get());
871 if((len % 2) != 0) {
872 // In case of an odd length string, add a space char
873 strcat(pDest.get(), " ");
874 }
875 }
876
877 return pDest;
878}
879
880void PCMExportProcessor::AddStrings(SNDFILE *sf, const Tags *tags, int sf_format)
881{
882 if (tags->HasTag(TAG_TITLE)) {
883 auto ascii7Str = AdjustString(tags->GetTag(TAG_TITLE), sf_format);
884 if (ascii7Str) {
885 sf_set_string(sf, SF_STR_TITLE, ascii7Str.get());
886 }
887 }
888
889 if (tags->HasTag(TAG_ALBUM)) {
890 auto ascii7Str = AdjustString(tags->GetTag(TAG_ALBUM), sf_format);
891 if (ascii7Str) {
892 sf_set_string(sf, SF_STR_ALBUM, ascii7Str.get());
893 }
894 }
895
896 if (tags->HasTag(TAG_ARTIST)) {
897 auto ascii7Str = AdjustString(tags->GetTag(TAG_ARTIST), sf_format);
898 if (ascii7Str) {
899 sf_set_string(sf, SF_STR_ARTIST, ascii7Str.get());
900 }
901 }
902
903 if (tags->HasTag(TAG_COMMENTS)) {
904 auto ascii7Str = AdjustString(tags->GetTag(TAG_COMMENTS), sf_format);
905 if (ascii7Str) {
906 sf_set_string(sf, SF_STR_COMMENT, ascii7Str.get());
907 }
908 }
909
910 if (tags->HasTag(TAG_YEAR)) {
911 auto ascii7Str = AdjustString(tags->GetTag(TAG_YEAR), sf_format);
912 if (ascii7Str) {
913 sf_set_string(sf, SF_STR_DATE, ascii7Str.get());
914 }
915 }
916
917 if (tags->HasTag(TAG_GENRE)) {
918 auto ascii7Str = AdjustString(tags->GetTag(TAG_GENRE), sf_format);
919 if (ascii7Str) {
920 sf_set_string(sf, SF_STR_GENRE, ascii7Str.get());
921 }
922 }
923
924 if (tags->HasTag(TAG_COPYRIGHT)) {
925 auto ascii7Str = AdjustString(tags->GetTag(TAG_COPYRIGHT), sf_format);
926 if (ascii7Str) {
927 sf_set_string(sf, SF_STR_COPYRIGHT, ascii7Str.get());
928 }
929 }
930
931 if (tags->HasTag(TAG_SOFTWARE)) {
932 auto ascii7Str = AdjustString(tags->GetTag(TAG_SOFTWARE), sf_format);
933 if (ascii7Str) {
934 sf_set_string(sf, SF_STR_SOFTWARE, ascii7Str.get());
935 }
936 }
937
938 if (tags->HasTag(TAG_TRACK)) {
939 auto ascii7Str = AdjustString(tags->GetTag(TAG_TRACK), sf_format);
940 if (ascii7Str) {
941 sf_set_string(sf, SF_STR_TRACKNUMBER, ascii7Str.get());
942 }
943 }
944}
945
946#ifdef USE_LIBID3TAG
947struct id3_tag_deleter {
948 void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
949};
950using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
951#endif
952
954 const wxFileNameWrapper &fName, const Tags *tags, int sf_format)
955{
956#ifdef USE_LIBID3TAG
957 id3_tag_holder tp { id3_tag_new() };
958
959 for (const auto &pair : tags->GetRange()) {
960 const auto &n = pair.first;
961 const auto &v = pair.second;
962 const char *name = "TXXX";
963
964 if (n.CmpNoCase(TAG_TITLE) == 0) {
965 name = ID3_FRAME_TITLE;
966 }
967 else if (n.CmpNoCase(TAG_ARTIST) == 0) {
968 name = ID3_FRAME_ARTIST;
969 }
970 else if (n.CmpNoCase(TAG_ALBUM) == 0) {
971 name = ID3_FRAME_ALBUM;
972 }
973 else if (n.CmpNoCase(TAG_YEAR) == 0) {
974 name = ID3_FRAME_YEAR;
975 }
976 else if (n.CmpNoCase(TAG_GENRE) == 0) {
977 name = ID3_FRAME_GENRE;
978 }
979 else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
980 name = ID3_FRAME_COMMENT;
981 }
982 else if (n.CmpNoCase(TAG_TRACK) == 0) {
983 name = ID3_FRAME_TRACK;
984 }
985 else if (n.CmpNoCase(wxT("composer")) == 0) {
986 name = "TCOM";
987 }
988
989 struct id3_frame *frame = id3_frame_new(name);
990
991 if (!n.IsAscii() || !v.IsAscii()) {
992 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
993 }
994 else {
995 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
996 }
997
999 id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
1000
1001 if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
1002 // A hack to get around iTunes not recognizing the comment. The
1003 // language defaults to XXX and, since it's not a valid language,
1004 // iTunes just ignores the tag. So, either set it to a valid language
1005 // (which one???) or just clear it. Unfortunately, there's no supported
1006 // way of clearing the field, so do it directly.
1007 id3_field *f = id3_frame_field(frame, 1);
1008 memset(f->immediate.value, 0, sizeof(f->immediate.value));
1009 id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
1010 }
1011 else if (strcmp(name, "TXXX") == 0) {
1012 id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
1013
1014 ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
1015
1016 id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
1017 }
1018 else {
1019 auto addr = ucs4.get();
1020 id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
1021 }
1022
1023 id3_tag_attachframe(tp.get(), frame);
1024 }
1025
1026 tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
1027
1028 // If this version of libid3tag supports it, use v2.3 ID3
1029 // tags instead of the newer, but less well supported, v2.4
1030 // that libid3tag uses by default.
1031#ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
1032 tp->options |= ID3_TAG_OPTION_ID3V2_3;
1033#endif
1034
1035 id3_length_t len;
1036
1037 len = id3_tag_render(tp.get(), 0);
1038 if (len == 0)
1039 return true;
1040
1041 if ((len % 2) != 0) len++; // Length must be even.
1042 ArrayOf<id3_byte_t> buffer { len, true };
1043 if (buffer == NULL)
1044 return false;
1045
1046 // Zero all locations, for ending odd UTF16 content
1047 // correctly, i.e., two '\0's at the end.
1048
1049 id3_tag_render(tp.get(), buffer.get());
1050
1051 wxFFile f(fName.GetFullPath(), wxT("r+b"));
1052 if (f.IsOpened()) {
1053 wxUint32 sz;
1054
1055 sz = (wxUint32) len;
1056 if (!f.SeekEnd(0))
1057 return false;
1058 if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV)
1059 {
1060 if (4 != f.Write("id3 ", 4))// Must be lower case for foobar2000.
1061 return false ;
1062 }
1063 else {
1064 if (4 != f.Write("ID3 ", 4))
1065 return false;
1066 sz = wxUINT32_SWAP_ON_LE(sz);
1067 }
1068 if (4 != f.Write(&sz, 4))
1069 return false;
1070
1071 if (len != f.Write(buffer.get(), len))
1072 return false;
1073
1074 sz = (wxUint32) f.Tell() - 8;
1075 if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
1076 sz = wxUINT32_SWAP_ON_LE(sz);
1077
1078 if (!f.Seek(4))
1079 return false;
1080 if (4 != f.Write(&sz, 4))
1081 return false;
1082
1083 if (!f.Flush())
1084 return false;
1085
1086 if (!f.Close())
1087 return false;
1088 }
1089 else
1090 return false;
1091#endif
1092 return true;
1093}
1094
1096 []{ return std::make_unique< ExportPCM >(); }
1097};
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:1095
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:147
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 tracks
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 TrackList &tracks, 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:793
static void AddStrings(SNDFILE *sf, const Tags *tags, int sf_format)
Definition: ExportPCM.cpp:880
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:953
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
struct PCMExportProcessor::@174 context
ExportResult Process(ExportProcessorDelegate &delegate) override
Definition: ExportPCM.cpp:692
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
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
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
struct anonymous_namespace{ExportPCM.cpp}::@171 kFormats[]
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
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