Audacity 3.2.0
ExportWavPack.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 SPDX-License-Identifier: GPL-2.0-or-later
4
5 Audacity: A Digital Audio Editor
6
7 ExportWavPack.cpp
8
9 Subhradeep Chakraborty
10
11 Based on ExportOGG.cpp, ExportMP2.cpp by:
12 Joshua Haberman
13 Markus Meyer
14
15**********************************************************************/
16
17#include "Export.h"
18#include "wxFileNameWrapper.h"
19#include "Mix.h"
20
21#include <wavpack/wavpack.h>
22
23#include <rapidjson/document.h>
24
25#include "Track.h"
26#include "Tags.h"
27
28#include "ExportPluginHelpers.h"
29#include "ExportOptionsEditor.h"
31
32namespace
33{
34
35enum : int {
41};
42
44 XO("Low Quality (Fast)") ,
45 XO("Normal Quality") ,
46 XO("High Quality (Slow)") ,
47 XO("Very High Quality (Slowest)") ,
48};
49
51 XO("16 bit"),
52 XO("24 bit"),
53 XO("32 bit float"),
54};
55
56/*
57Copied from ExportMP2.cpp by
58 Joshua Haberman
59 Markus Meyer
60*/
61
62// i18n-hint bps abbreviates "bits per sample"
63inline TranslatableString n_bps( int n ) { return XO("%.1f bps").Format( n / 10.0 ); }
64
66 n_bps(22),
67 n_bps(25),
68 n_bps(30),
69 n_bps(35),
70 n_bps(40),
71 n_bps(45),
72 n_bps(50),
73 n_bps(60),
74 n_bps(70),
75 n_bps(80),
76};
77
78
79const std::initializer_list<ExportOption> ExportWavPackOptions {
80 {
81
82 OptionIDQuality, XO("Quality"),
83 1,
85 { 0, 1, 2, 3 },
87 },
88 {
89 OptionIDBitDepth, XO("Bit Depth"),
90 16,
92 { 16, 24, 32 },
94 },
95 {
96 OptionIDHybridMode, XO("Hybrid Mode"),
97 false
98 },
99 {
100 OptionIDCreateCorrection, XO("Create Correction(.wvc) File"),
101 false,
103 },
104 {
105 OptionIDBitRate, XO("Bit Rate"),
106 40,
108 { 22, 25, 30, 35, 40, 45, 50, 60, 70, 80 },
110 }
111};
112
114{
115 Listener* mListener{nullptr};
116 std::vector<ExportOption> mOptions = ExportWavPackOptions;
117 std::unordered_map<ExportOptionID, ExportValue> mValues;
118public:
119
121 : mListener(listener)
122 {
123 for(const auto& option : mOptions)
124 mValues[option.id] = option.defaultValue;
125 }
126
127 int GetOptionsCount() const override
128 {
129 return static_cast<int>(mOptions.size());
130 }
131
132 bool GetOption(int index, ExportOption& option) const override
133 {
134 if(index >= 0 && index < mOptions.size())
135 {
136 option = mOptions[index];
137 return true;
138 }
139 return false;
140 }
141
142 bool GetValue(ExportOptionID id, ExportValue& value) const override
143 {
144 const auto it = mValues.find(id);
145 if(it != mValues.end())
146 {
147 value = it->second;
148 return true;
149 }
150 return false;
151 }
152
153 bool SetValue(ExportOptionID id, const ExportValue& value) override
154 {
155 auto it = mValues.find(id);
156 if(it == mValues.end() || value.index() != it->second.index())
157 return false;
158
159 it->second = value;
160 if(id == OptionIDHybridMode)
161 {
162 OnHybridModeChange(*std::get_if<bool>(&value));
163
164 if(mListener)
165 {
166 mListener->OnExportOptionChangeBegin();
167 mListener->OnExportOptionChange(mOptions[OptionIDCreateCorrection]);
168 mListener->OnExportOptionChange(mOptions[OptionIDBitRate]);
169 mListener->OnExportOptionChangeEnd();
170 }
171 }
172 return true;
173 }
174
176 {
177 return {};
178 }
179
180 void Load(const audacity::BasicSettings& config) override
181 {
182 auto quality = std::get_if<int>(&mValues[OptionIDQuality]);
183 auto bitDepth = std::get_if<int>(&mValues[OptionIDBitDepth]);
184 auto hybridMode = std::get_if<bool>(&mValues[OptionIDHybridMode]);
185 auto createCorrection = std::get_if<bool>(&mValues[OptionIDCreateCorrection]);
186 auto bitRate = std::get_if<int>(&mValues[OptionIDBitRate]);
187
188 config.Read(L"/FileFormats/WavPackEncodeQuality", quality);
189 config.Read(L"/FileFormats/WavPackBitDepth", bitDepth);
190 config.Read(L"/FileFormats/WavPackHybridMode", hybridMode);
191 config.Read(L"/FileFormats/WavPackCreateCorrectionFile", createCorrection);
192 config.Read(L"/FileFormats/WavPackBitrate", bitRate);
193
194 OnHybridModeChange(*hybridMode);
195 }
196
197 void Store(audacity::BasicSettings& config) const override
198 {
199 auto it = mValues.find(OptionIDQuality);
200 if(it != mValues.end())
201 config.Write(L"/FileFormats/WavPackEncodeQuality", *std::get_if<int>(&it->second));
202
203 it = mValues.find(OptionIDBitDepth);
204 if(it != mValues.end())
205 config.Write(L"/FileFormats/WavPackBitDepth", *std::get_if<int>(&it->second));
206
207 it = mValues.find(OptionIDHybridMode);
208 if(it != mValues.end())
209 config.Write(L"/FileFormats/WavPackHybridMode", *std::get_if<bool>(&it->second));
210
211 it = mValues.find(OptionIDCreateCorrection);
212 if(it != mValues.end())
213 config.Write(L"/FileFormats/WavPackCreateCorrectionFile", *std::get_if<bool>(&it->second));
214
215 it = mValues.find(OptionIDBitRate);
216 if(it != mValues.end())
217 config.Write(L"/FileFormats/WavPackBitrate", *std::get_if<int>(&it->second));
218 }
219
220private:
221 void OnHybridModeChange(bool hybridMode)
222 {
223 if(hybridMode)
224 {
225 mOptions[OptionIDCreateCorrection].flags &= ~ExportOption::Flags::ReadOnly;
226 mOptions[OptionIDBitRate].flags &= ~ExportOption::Flags::ReadOnly;
227
228 } else {
229 mOptions[OptionIDCreateCorrection].flags |= ExportOption::Flags::ReadOnly;
230 mOptions[OptionIDBitRate].flags |= ExportOption::Flags::ReadOnly;
231 }
232 }
233};
234
235}
236
237struct WriteId final
238{
239 uint32_t bytesWritten {};
240 uint32_t firstBlockSize {};
241 std::unique_ptr<wxFile> file;
242};
243
245{
246 // Samples to write per run
247 static constexpr size_t SAMPLES_PER_RUN = 8192u;
248
249 struct
250 {
252 double t0;
253 double t1;
254 unsigned numChannels;
258 WavpackContext *wpc{};
259 std::unique_ptr<Mixer> mixer;
260 std::unique_ptr<Tags> metadata;
262public:
263
265
267 const Parameters& parameters,
268 const wxFileNameWrapper& filename,
269 double t0, double t1, bool selectedOnly,
270 double sampleRate, unsigned channels,
271 MixerOptions::Downmix* mixerSpec,
272 const Tags* tags) override;
273
274 ExportResult Process(ExportProcessorDelegate& delegate) override;
275
276private:
277 static int WriteBlock(void *id, void *data, int32_t length);
278};
279
280class ExportWavPack final : public ExportPlugin
281{
282public:
283
285
286 int GetFormatCount() const override;
287 FormatInfo GetFormatInfo(int) const override;
288
289 std::vector<std::string> GetMimeTypes(int) const override;
290
291 bool ParseConfig(int formatIndex, const rapidjson::Value& document, ExportProcessor::Parameters& parameters) const override;
292
293 std::unique_ptr<ExportOptionsEditor>
295
296 std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
297};
298
300
302{
303 return 1;
304}
305
307{
308 return {
309 wxT("WavPack"), XO("WavPack Files"), { wxT("wv") }, 255, true
310 };
311}
312
313std::vector<std::string> ExportWavPack::GetMimeTypes(int) const
314{
315 return { "audio/x-wavpack" };
316}
317
318bool ExportWavPack::ParseConfig(int formatIndex, const rapidjson::Value& config, ExportProcessor::Parameters& parameters) const
319{
320 if(!config.IsObject() ||
321 !config.HasMember("quality") || !config["quality"].IsNumber() ||
322 !config.HasMember("bit_rate") || !config["bit_rate"].IsNumber() ||
323 !config.HasMember("bit_depth") || !config["bit_depth"].IsNumber() ||
324 !config.HasMember("hybrid_mode") || !config["hybrid_mode"].IsBool())
325 return false;
326
327 const auto quality = ExportValue(config["quality"].GetInt());
328 const auto bitRate = ExportValue(config["bit_rate"].GetInt());
329 const auto bitDepth = ExportValue(config["bit_depth"].GetInt());
330 const auto hybridMode = ExportValue(config["hybrid_mode"].GetBool());
331
332 for(const auto& option : ExportWavPackOptions)
333 {
334 if((option.id == OptionIDQuality &&
335 std::find(option.values.begin(),
336 option.values.end(),
337 quality) == option.values.end())
338 ||
339 (option.id == OptionIDBitRate &&
340 std::find(option.values.begin(),
341 option.values.end(),
342 bitRate) == option.values.end())
343 ||
344 (option.id == OptionIDBitDepth &&
345 std::find(option.values.begin(),
346 option.values.end(),
347 bitDepth) == option.values.end()))
348 return false;
349 }
351 { OptionIDQuality, quality },
352 { OptionIDBitRate, bitRate },
353 { OptionIDBitDepth, bitDepth },
354 { OptionIDHybridMode, hybridMode },
355 { OptionIDCreateCorrection, false }
356 };
357 std::swap(parameters, result);
358 return true;
359}
360
361std::unique_ptr<ExportOptionsEditor>
363{
364 return std::make_unique<ExportOptionsWavPackEditor>(listener);
365}
366
367std::unique_ptr<ExportProcessor> ExportWavPack::CreateProcessor(int) const
368{
369 return std::make_unique<WavPackExportProcessor>();
370}
371
372
374{
375 if(context.wpc)
376 WavpackCloseFile(context.wpc);
377}
378
380 const Parameters& parameters,
381 const wxFileNameWrapper& fName,
382 double t0, double t1, bool selectionOnly,
383 double sampleRate, unsigned numChannels,
384 MixerOptions::Downmix* mixerSpec,
385 const Tags* metadata)
386{
387 context.t0 = t0;
388 context.t1 = t1;
389 context.numChannels = numChannels;
390 context.fName = fName;
391
392 WavpackConfig config = {};
393 auto& outWvFile = context.outWvFile;
394 auto& outWvcFile = context.outWvcFile;
395 outWvFile.file = std::make_unique< wxFile >();
396
397 if (!outWvFile.file->Create(fName.GetFullPath(), true) || !outWvFile.file->IsOpened()) {
398 throw ExportException(_("Unable to open target file for writing"));
399 }
400
401 const auto quality = ExportPluginHelpers::GetParameterValue<int>(
402 parameters,
404 1);
405 const auto hybridMode = ExportPluginHelpers::GetParameterValue<bool>(
406 parameters,
408 false);
409 const auto createCorrectionFile = ExportPluginHelpers::GetParameterValue<bool>(
410 parameters,
412 false);
413 const auto bitRate = ExportPluginHelpers::GetParameterValue<int>(
414 parameters,
416 40);
417 const auto bitDepth = ExportPluginHelpers::GetParameterValue<int>(
418 parameters,
420 16);
421
422
423 context.format = int16Sample;
424 if (bitDepth == 24) {
425 context.format = int24Sample;
426 } else if (bitDepth == 32) {
427 context.format = floatSample;
428 }
429
430 config.num_channels = numChannels;
431 config.sample_rate = sampleRate;
432 config.bits_per_sample = bitDepth;
433 config.bytes_per_sample = bitDepth/8;
434 config.float_norm_exp = context.format == floatSample ? 127 : 0;
435
436 if (config.num_channels <= 2)
437 config.channel_mask = 0x5 - config.num_channels;
438 else if (config.num_channels <= 18)
439 config.channel_mask = (1U << config.num_channels) - 1;
440 else
441 config.channel_mask = 0x3FFFF;
442
443 if (quality == 0) {
444 config.flags |= CONFIG_FAST_FLAG;
445 } else if (quality == 2) {
446 config.flags |= CONFIG_HIGH_FLAG;
447 } else if (quality == 3) {
448 config.flags |= CONFIG_HIGH_FLAG | CONFIG_VERY_HIGH_FLAG;
449 }
450
451 if (hybridMode) {
452 config.flags |= CONFIG_HYBRID_FLAG;
453 config.bitrate = bitRate / 10.0;
454
455 if (createCorrectionFile) {
456 config.flags |= CONFIG_CREATE_WVC;
457
458 outWvcFile.file = std::make_unique< wxFile >();
459 if (!outWvcFile.file->Create(fName.GetFullPath().Append("c"), true)) {
460 throw ExportException(_("Unable to create target file for writing"));
461 }
462 }
463 }
464
465 // If we're not creating a correction file now, any one that currently exists with this name
466 // will become obsolete now, so delete it if it happens to exist (although it usually won't)
467
468 if (!hybridMode || !createCorrectionFile)
469 wxRemoveFile(fName.GetFullPath().Append("c"));
470
471 context.wpc = WavpackOpenFileOutput(WriteBlock, &outWvFile, createCorrectionFile ? &outWvcFile : nullptr);
472 if (!WavpackSetConfiguration64(context.wpc, &config, -1, nullptr) || !WavpackPackInit(context.wpc)) {
473 throw ExportErrorException( WavpackGetErrorMessage(context.wpc) );
474 }
475
476 context.status = selectionOnly
477 ? XO("Exporting selected audio as WavPack")
478 : XO("Exporting the audio as WavPack");
479
480 context.metadata = std::make_unique<Tags>(
481 metadata == nullptr
482 ? Tags::Get( project )
483 : *metadata
484 );
485
487 project, selectionOnly, t0, t1, numChannels, SAMPLES_PER_RUN, true,
488 sampleRate, context.format, mixerSpec);
489
490 return true;
491}
492
494{
495 delegate.SetStatusString(context.status);
496
497 const size_t bufferSize = SAMPLES_PER_RUN * context.numChannels;
498
499 ArrayOf<int32_t> wavpackBuffer{ bufferSize };
500
501 auto exportResult = ExportResult::Success;
502 {
503
504 while (exportResult == ExportResult::Success) {
505 auto samplesThisRun = context.mixer->Process();
506
507 if (samplesThisRun == 0)
508 break;
509
510 if (context.format == int16Sample) {
511 const int16_t *mixed = reinterpret_cast<const int16_t*>(context.mixer->GetBuffer());
512 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
513 for (size_t i = 0; i < context.numChannels; i++) {
514 wavpackBuffer[j*context.numChannels + i] = (static_cast<int32_t>(*mixed++) * 65536) >> 16;
515 }
516 }
517 } else {
518 const int *mixed = reinterpret_cast<const int*>(context.mixer->GetBuffer());
519 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
520 for (size_t i = 0; i < context.numChannels; i++) {
521 wavpackBuffer[j*context.numChannels + i] = *mixed++;
522 }
523 }
524 }
525
526 if (!WavpackPackSamples(context.wpc, wavpackBuffer.get(), samplesThisRun)) {
527 throw ExportErrorException(WavpackGetErrorMessage(context.wpc));
528 }
530 delegate, *context.mixer, context.t0, context.t1);
531 }
532 }
533
534 if (!WavpackFlushSamples(context.wpc)) {
535 throw ExportErrorException( WavpackGetErrorMessage(context.wpc) );
536 } else {
537
538
539 wxString n;
540 for (const auto &pair : context.metadata->GetRange()) {
541 n = pair.first;
542 const auto &v = pair.second;
543
544 WavpackAppendTagItem(context.wpc,
545 n.mb_str(wxConvUTF8),
546 v.mb_str(wxConvUTF8),
547 static_cast<int>( strlen(v.mb_str(wxConvUTF8)) ));
548 }
549
550 if (!WavpackWriteTag(context.wpc)) {
551 throw ExportErrorException( WavpackGetErrorMessage(context.wpc) );
552 }
553 }
554
555 if ( !context.outWvFile.file.get()->Close()
556 || ( context.outWvcFile.file && context.outWvcFile.file.get() && !context.outWvcFile.file.get()->Close())) {
557 return ExportResult::Error;
558 }
559
560 // wxFile::Create opens the file with only write access
561 // So, need to open the file again with both read and write access
562 if (!context.outWvFile.file->Open(context.fName.GetFullPath(), wxFile::read_write)) {
563 throw ExportErrorException("Unable to update the actual length of the file");
564 }
565
566 ArrayOf<int32_t> firstBlockBuffer { context.outWvFile.firstBlockSize };
567 context.outWvFile.file->Read(firstBlockBuffer.get(), context.outWvFile.firstBlockSize);
568
569 // Update the first block written with the actual number of samples written
570 WavpackUpdateNumSamples(context.wpc, firstBlockBuffer.get());
571 context.outWvFile.file->Seek(0);
572 context.outWvFile.file->Write(firstBlockBuffer.get(), context.outWvFile.firstBlockSize);
573
574 if ( !context.outWvFile.file.get()->Close() ) {
575 return ExportResult::Error;
576 }
577 return exportResult;
578}
579
580
581// Based on the implementation of write_block in dbry/WavPack
582// src: https://github.com/dbry/WavPack/blob/master/cli/wavpack.c
583int WavPackExportProcessor::WriteBlock(void *id, void *data, int32_t length)
584{
585 if (id == nullptr || data == nullptr || length == 0)
586 return true; // This is considered to be success in wavpack.c reference code
587
588 WriteId *outId = static_cast<WriteId*>(id);
589
590 if (!outId->file)
591 // This does not match the wavpack.c but in our case if file is nullptr -
592 // the stream error has occured
593 return false;
594
595 // if (!outId->file->Write(data, length).IsOk()) {
596 if (outId->file->Write(data, length) != length) {
597 outId->file.reset();
598 return false;
599 }
600
601 outId->bytesWritten += length;
602
603 if (outId->firstBlockSize == 0)
604 outId->firstBlockSize = length;
605
606 return true;
607}
608
610 []{ return std::make_unique< ExportWavPack >(); }
611};
wxT("CloseDown"))
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
static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
sampleFormat
The ordering of these values with operator < agrees with the order of increasing bit width.
Definition: SampleFormat.h:30
const auto project
declares abstract base class Track, TrackList, and iterators over TrackList
std::vector< TranslatableString > TranslatableStrings
int id
This simplifies arrays of arrays, each array separately allocated with NEW[] But it might be better t...
Definition: MemoryX.h:29
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.
Editor objects are used to retrieve a set of export options, and configure exporting parameters accor...
std::vector< int > SampleRateList
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
FormatInfo GetFormatInfo(int) const override
Returns FormatInfo structure for given index if it's valid, or a default one. FormatInfo::format isn'...
int GetFormatCount() const override
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 ...
std::unique_ptr< ExportProcessor > CreateProcessor(int format) const override
bool ParseConfig(int formatIndex, const rapidjson::Value &document, ExportProcessor::Parameters &parameters) const override
Attempt to parse configuration JSON object and produce a suitable set of parameters....
std::vector< std::string > GetMimeTypes(int) const override
A matrix of booleans, one row per input channel, column per output.
Definition: MixerOptions.h:32
ID3 Tags (for MP3)
Definition: Tags.h:73
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
Holds a msgid for the translation catalog; may also bind format arguments.
std::unique_ptr< Mixer > mixer
TranslatableString status
static constexpr size_t SAMPLES_PER_RUN
ExportResult Process(ExportProcessorDelegate &delegate) override
struct WavPackExportProcessor::@185 context
static int WriteBlock(void *id, void *data, int32_t length)
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.
WavpackContext * wpc
wxFileNameWrapper fName
std::unique_ptr< Tags > metadata
bool SetValue(ExportOptionID id, const ExportValue &value) override
bool GetValue(ExportOptionID id, ExportValue &value) const override
std::unordered_map< ExportOptionID, ExportValue > mValues
void Store(audacity::BasicSettings &config) const override
bool GetOption(int index, ExportOption &option) const override
void Load(const audacity::BasicSettings &config) override
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
const TranslatableStrings ExportBitDepthNames
const std::initializer_list< ExportOption > ExportWavPackOptions
const TranslatableStrings ExportQualityNames
const TranslatableStrings BitRateNames
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:628
A type that provides a description of an exporting option. Isn't allowed to change except non-type re...
Definition: ExportTypes.h:43
@ TypeEnum
List/enum option. values holds items, and names text to be displayed.
Definition: ExportTypes.h:48
@ ReadOnly
Parameter is read-only, client should not attempt to change it's value.
Definition: ExportTypes.h:50
int flags
A set of flag that desc.
Definition: ExportTypes.h:59
uint32_t bytesWritten
uint32_t firstBlockSize
std::unique_ptr< wxFile > file