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->OnExportOptionChangeEnd();
169 }
170 }
171 return true;
172 }
173
175 {
176 return {};
177 }
178
179 void Load(const audacity::BasicSettings& config) override
180 {
181 auto quality = std::get_if<int>(&mValues[OptionIDQuality]);
182 auto bitDepth = std::get_if<int>(&mValues[OptionIDBitDepth]);
183 auto hybridMode = std::get_if<bool>(&mValues[OptionIDHybridMode]);
184 auto createCorrection = std::get_if<bool>(&mValues[OptionIDCreateCorrection]);
185 auto bitRate = std::get_if<int>(&mValues[OptionIDBitRate]);
186
187 config.Read(L"/FileFormats/WavPackEncodeQuality", quality);
188 config.Read(L"/FileFormats/WavPackBitDepth", bitDepth);
189 config.Read(L"/FileFormats/WavPackHybridMode", hybridMode);
190 config.Read(L"/FileFormats/WavPackCreateCorrectionFile", createCorrection);
191 config.Read(L"/FileFormats/WavPackBitrate", bitRate);
192
193 OnHybridModeChange(*hybridMode);
194 }
195
196 void Store(audacity::BasicSettings& config) const override
197 {
198 auto it = mValues.find(OptionIDQuality);
199 if(it != mValues.end())
200 config.Write(L"/FileFormats/WavPackEncodeQuality", *std::get_if<int>(&it->second));
201
202 it = mValues.find(OptionIDBitDepth);
203 if(it != mValues.end())
204 config.Write(L"/FileFormats/WavPackBitDepth", *std::get_if<int>(&it->second));
205
206 it = mValues.find(OptionIDHybridMode);
207 if(it != mValues.end())
208 config.Write(L"/FileFormats/WavPackHybridMode", *std::get_if<bool>(&it->second));
209
210 it = mValues.find(OptionIDCreateCorrection);
211 if(it != mValues.end())
212 config.Write(L"/FileFormats/WavPackCreateCorrectionFile", *std::get_if<bool>(&it->second));
213
214 it = mValues.find(OptionIDBitRate);
215 if(it != mValues.end())
216 config.Write(L"/FileFormats/WavPackBitrate", *std::get_if<int>(&it->second));
217 }
218
219private:
220 void OnHybridModeChange(bool hybridMode)
221 {
222 if(hybridMode)
223 mOptions[OptionIDCreateCorrection].flags &= ~ExportOption::Flags::ReadOnly;
224 else
225 mOptions[OptionIDCreateCorrection].flags |= ExportOption::Flags::ReadOnly;
226 }
227};
228
229}
230
231struct WriteId final
232{
233 uint32_t bytesWritten {};
234 uint32_t firstBlockSize {};
235 std::unique_ptr<wxFile> file;
236};
237
239{
240 // Samples to write per run
241 static constexpr size_t SAMPLES_PER_RUN = 8192u;
242
243 struct
244 {
246 double t0;
247 double t1;
248 unsigned numChannels;
252 WavpackContext *wpc{};
253 std::unique_ptr<Mixer> mixer;
254 std::unique_ptr<Tags> metadata;
256public:
257
259
261 const Parameters& parameters,
262 const wxFileNameWrapper& filename,
263 double t0, double t1, bool selectedOnly,
264 double sampleRate, unsigned channels,
265 MixerOptions::Downmix* mixerSpec,
266 const Tags* tags) override;
267
268 ExportResult Process(ExportProcessorDelegate& delegate) override;
269
270private:
271 static int WriteBlock(void *id, void *data, int32_t length);
272};
273
274class ExportWavPack final : public ExportPlugin
275{
276public:
277
279
280 int GetFormatCount() const override;
281 FormatInfo GetFormatInfo(int) const override;
282
283 std::vector<std::string> GetMimeTypes(int) const override;
284
285 bool ParseConfig(int formatIndex, const rapidjson::Value& document, ExportProcessor::Parameters& parameters) const override;
286
287 std::unique_ptr<ExportOptionsEditor>
289
290 std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
291};
292
294
296{
297 return 1;
298}
299
301{
302 return {
303 wxT("WavPack"), XO("WavPack Files"), { wxT("wv") }, 255, true
304 };
305}
306
307std::vector<std::string> ExportWavPack::GetMimeTypes(int) const
308{
309 return { "audio/x-wavpack" };
310}
311
312bool ExportWavPack::ParseConfig(int formatIndex, const rapidjson::Value& config, ExportProcessor::Parameters& parameters) const
313{
314 if(!config.IsObject() ||
315 !config.HasMember("quality") || !config["quality"].IsNumber() ||
316 !config.HasMember("bit_rate") || !config["bit_rate"].IsNumber() ||
317 !config.HasMember("bit_depth") || !config["bit_depth"].IsNumber() ||
318 !config.HasMember("hybrid_mode") || !config["hybrid_mode"].IsBool())
319 return false;
320
321 const auto quality = ExportValue(config["quality"].GetInt());
322 const auto bitRate = ExportValue(config["bit_rate"].GetInt());
323 const auto bitDepth = ExportValue(config["bit_depth"].GetInt());
324 const auto hybridMode = ExportValue(config["hybrid_mode"].GetBool());
325
326 for(const auto& option : ExportWavPackOptions)
327 {
328 if((option.id == OptionIDQuality &&
329 std::find(option.values.begin(),
330 option.values.end(),
331 quality) == option.values.end())
332 ||
333 (option.id == OptionIDBitRate &&
334 std::find(option.values.begin(),
335 option.values.end(),
336 bitRate) == option.values.end())
337 ||
338 (option.id == OptionIDBitDepth &&
339 std::find(option.values.begin(),
340 option.values.end(),
341 bitDepth) == option.values.end()))
342 return false;
343 }
345 { OptionIDQuality, quality },
346 { OptionIDBitRate, bitRate },
347 { OptionIDBitDepth, bitDepth },
348 { OptionIDHybridMode, hybridMode },
349 { OptionIDCreateCorrection, false }
350 };
351 std::swap(parameters, result);
352 return true;
353}
354
355std::unique_ptr<ExportOptionsEditor>
357{
358 return std::make_unique<ExportOptionsWavPackEditor>(listener);
359}
360
361std::unique_ptr<ExportProcessor> ExportWavPack::CreateProcessor(int) const
362{
363 return std::make_unique<WavPackExportProcessor>();
364}
365
366
368{
369 if(context.wpc)
370 WavpackCloseFile(context.wpc);
371}
372
374 const Parameters& parameters,
375 const wxFileNameWrapper& fName,
376 double t0, double t1, bool selectionOnly,
377 double sampleRate, unsigned numChannels,
378 MixerOptions::Downmix* mixerSpec,
379 const Tags* metadata)
380{
381 context.t0 = t0;
382 context.t1 = t1;
383 context.numChannels = numChannels;
384 context.fName = fName;
385
386 WavpackConfig config = {};
387 auto& outWvFile = context.outWvFile;
388 auto& outWvcFile = context.outWvcFile;
389 outWvFile.file = std::make_unique< wxFile >();
390
391 if (!outWvFile.file->Create(fName.GetFullPath(), true) || !outWvFile.file->IsOpened()) {
392 throw ExportException(_("Unable to open target file for writing"));
393 }
394
395 const auto &tracks = TrackList::Get( project );
396
397 const auto quality = ExportPluginHelpers::GetParameterValue<int>(
398 parameters,
400 1);
401 const auto hybridMode = ExportPluginHelpers::GetParameterValue<bool>(
402 parameters,
404 false);
405 const auto createCorrectionFile = ExportPluginHelpers::GetParameterValue<bool>(
406 parameters,
408 false);
409 const auto bitRate = ExportPluginHelpers::GetParameterValue<int>(
410 parameters,
412 40);
413 const auto bitDepth = ExportPluginHelpers::GetParameterValue<int>(
414 parameters,
416 16);
417
418
419 context.format = int16Sample;
420 if (bitDepth == 24) {
421 context.format = int24Sample;
422 } else if (bitDepth == 32) {
423 context.format = floatSample;
424 }
425
426 config.num_channels = numChannels;
427 config.sample_rate = sampleRate;
428 config.bits_per_sample = bitDepth;
429 config.bytes_per_sample = bitDepth/8;
430 config.float_norm_exp = context.format == floatSample ? 127 : 0;
431
432 if (config.num_channels <= 2)
433 config.channel_mask = 0x5 - config.num_channels;
434 else if (config.num_channels <= 18)
435 config.channel_mask = (1U << config.num_channels) - 1;
436 else
437 config.channel_mask = 0x3FFFF;
438
439 if (quality == 0) {
440 config.flags |= CONFIG_FAST_FLAG;
441 } else if (quality == 2) {
442 config.flags |= CONFIG_HIGH_FLAG;
443 } else if (quality == 3) {
444 config.flags |= CONFIG_HIGH_FLAG | CONFIG_VERY_HIGH_FLAG;
445 }
446
447 if (hybridMode) {
448 config.flags |= CONFIG_HYBRID_FLAG;
449 config.bitrate = bitRate / 10.0;
450
451 if (createCorrectionFile) {
452 config.flags |= CONFIG_CREATE_WVC;
453
454 outWvcFile.file = std::make_unique< wxFile >();
455 if (!outWvcFile.file->Create(fName.GetFullPath().Append("c"), true)) {
456 throw ExportException(_("Unable to create target file for writing"));
457 }
458 }
459 }
460
461 // If we're not creating a correction file now, any one that currently exists with this name
462 // will become obsolete now, so delete it if it happens to exist (although it usually won't)
463
464 if (!hybridMode || !createCorrectionFile)
465 wxRemoveFile(fName.GetFullPath().Append("c"));
466
467 context.wpc = WavpackOpenFileOutput(WriteBlock, &outWvFile, createCorrectionFile ? &outWvcFile : nullptr);
468 if (!WavpackSetConfiguration64(context.wpc, &config, -1, nullptr) || !WavpackPackInit(context.wpc)) {
469 throw ExportErrorException( WavpackGetErrorMessage(context.wpc) );
470 }
471
472 context.status = selectionOnly
473 ? XO("Exporting selected audio as WavPack")
474 : XO("Exporting the audio as WavPack");
475
476 context.metadata = std::make_unique<Tags>(
477 metadata == nullptr
478 ? Tags::Get( project )
479 : *metadata
480 );
481
482 context.mixer = ExportPluginHelpers::CreateMixer(tracks, selectionOnly,
483 t0, t1,
485 sampleRate, context.format, mixerSpec);
486
487 return true;
488}
489
491{
492 delegate.SetStatusString(context.status);
493
494 const size_t bufferSize = SAMPLES_PER_RUN * context.numChannels;
495
496 ArrayOf<int32_t> wavpackBuffer{ bufferSize };
497
498 auto exportResult = ExportResult::Success;
499 {
500
501 while (exportResult == ExportResult::Success) {
502 auto samplesThisRun = context.mixer->Process();
503
504 if (samplesThisRun == 0)
505 break;
506
507 if (context.format == int16Sample) {
508 const int16_t *mixed = reinterpret_cast<const int16_t*>(context.mixer->GetBuffer());
509 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
510 for (size_t i = 0; i < context.numChannels; i++) {
511 wavpackBuffer[j*context.numChannels + i] = (static_cast<int32_t>(*mixed++) * 65536) >> 16;
512 }
513 }
514 } else {
515 const int *mixed = reinterpret_cast<const int*>(context.mixer->GetBuffer());
516 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
517 for (size_t i = 0; i < context.numChannels; i++) {
518 wavpackBuffer[j*context.numChannels + i] = *mixed++;
519 }
520 }
521 }
522
523 if (!WavpackPackSamples(context.wpc, wavpackBuffer.get(), samplesThisRun)) {
524 throw ExportErrorException(WavpackGetErrorMessage(context.wpc));
525 }
527 delegate, *context.mixer, context.t0, context.t1);
528 }
529 }
530
531 if (!WavpackFlushSamples(context.wpc)) {
532 throw ExportErrorException( WavpackGetErrorMessage(context.wpc) );
533 } else {
534
535
536 wxString n;
537 for (const auto &pair : context.metadata->GetRange()) {
538 n = pair.first;
539 const auto &v = pair.second;
540
541 WavpackAppendTagItem(context.wpc,
542 n.mb_str(wxConvUTF8),
543 v.mb_str(wxConvUTF8),
544 static_cast<int>( strlen(v.mb_str(wxConvUTF8)) ));
545 }
546
547 if (!WavpackWriteTag(context.wpc)) {
548 throw ExportErrorException( WavpackGetErrorMessage(context.wpc) );
549 }
550 }
551
552 if ( !context.outWvFile.file.get()->Close()
553 || ( context.outWvcFile.file && context.outWvcFile.file.get() && !context.outWvcFile.file.get()->Close())) {
554 return ExportResult::Error;
555 }
556
557 // wxFile::Create opens the file with only write access
558 // So, need to open the file again with both read and write access
559 if (!context.outWvFile.file->Open(context.fName.GetFullPath(), wxFile::read_write)) {
560 throw ExportErrorException("Unable to update the actual length of the file");
561 }
562
563 ArrayOf<int32_t> firstBlockBuffer { context.outWvFile.firstBlockSize };
564 context.outWvFile.file->Read(firstBlockBuffer.get(), context.outWvFile.firstBlockSize);
565
566 // Update the first block written with the actual number of samples written
567 WavpackUpdateNumSamples(context.wpc, firstBlockBuffer.get());
568 context.outWvFile.file->Seek(0);
569 context.outWvFile.file->Write(firstBlockBuffer.get(), context.outWvFile.firstBlockSize);
570
571 if ( !context.outWvFile.file.get()->Close() ) {
572 return ExportResult::Error;
573 }
574 return exportResult;
575}
576
577
578// Based on the implementation of write_block in dbry/WavPack
579// src: https://github.com/dbry/WavPack/blob/master/cli/wavpack.c
580int WavPackExportProcessor::WriteBlock(void *id, void *data, int32_t length)
581{
582 if (id == nullptr || data == nullptr || length == 0)
583 return true; // This is considered to be success in wavpack.c reference code
584
585 WriteId *outId = static_cast<WriteId*>(id);
586
587 if (!outId->file)
588 // This does not match the wavpack.c but in our case if file is nullptr -
589 // the stream error has occured
590 return false;
591
592 // if (!outId->file->Write(data, length).IsOk()) {
593 if (outId->file->Write(data, length) != length) {
594 outId->file.reset();
595 return false;
596 }
597
598 outId->bytesWritten += length;
599
600 if (outId->firstBlockSize == 0)
601 outId->firstBlockSize = length;
602
603 return true;
604}
605
607 []{ return std::make_unique< ExportWavPack >(); }
608};
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 tracks
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:27
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 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
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
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:354
Holds a msgid for the translation catalog; may also bind format arguments.
std::unique_ptr< Mixer > mixer
TranslatableString status
struct WavPackExportProcessor::@179 context
static constexpr size_t SAMPLES_PER_RUN
ExportResult Process(ExportProcessorDelegate &delegate) override
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:770
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