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