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 &tracks = TrackList::Get( project );
402
403 const auto quality = ExportPluginHelpers::GetParameterValue<int>(
404 parameters,
406 1);
407 const auto hybridMode = ExportPluginHelpers::GetParameterValue<bool>(
408 parameters,
410 false);
411 const auto createCorrectionFile = ExportPluginHelpers::GetParameterValue<bool>(
412 parameters,
414 false);
415 const auto bitRate = ExportPluginHelpers::GetParameterValue<int>(
416 parameters,
418 40);
419 const auto bitDepth = ExportPluginHelpers::GetParameterValue<int>(
420 parameters,
422 16);
423
424
425 context.format = int16Sample;
426 if (bitDepth == 24) {
427 context.format = int24Sample;
428 } else if (bitDepth == 32) {
429 context.format = floatSample;
430 }
431
432 config.num_channels = numChannels;
433 config.sample_rate = sampleRate;
434 config.bits_per_sample = bitDepth;
435 config.bytes_per_sample = bitDepth/8;
436 config.float_norm_exp = context.format == floatSample ? 127 : 0;
437
438 if (config.num_channels <= 2)
439 config.channel_mask = 0x5 - config.num_channels;
440 else if (config.num_channels <= 18)
441 config.channel_mask = (1U << config.num_channels) - 1;
442 else
443 config.channel_mask = 0x3FFFF;
444
445 if (quality == 0) {
446 config.flags |= CONFIG_FAST_FLAG;
447 } else if (quality == 2) {
448 config.flags |= CONFIG_HIGH_FLAG;
449 } else if (quality == 3) {
450 config.flags |= CONFIG_HIGH_FLAG | CONFIG_VERY_HIGH_FLAG;
451 }
452
453 if (hybridMode) {
454 config.flags |= CONFIG_HYBRID_FLAG;
455 config.bitrate = bitRate / 10.0;
456
457 if (createCorrectionFile) {
458 config.flags |= CONFIG_CREATE_WVC;
459
460 outWvcFile.file = std::make_unique< wxFile >();
461 if (!outWvcFile.file->Create(fName.GetFullPath().Append("c"), true)) {
462 throw ExportException(_("Unable to create target file for writing"));
463 }
464 }
465 }
466
467 // If we're not creating a correction file now, any one that currently exists with this name
468 // will become obsolete now, so delete it if it happens to exist (although it usually won't)
469
470 if (!hybridMode || !createCorrectionFile)
471 wxRemoveFile(fName.GetFullPath().Append("c"));
472
473 context.wpc = WavpackOpenFileOutput(WriteBlock, &outWvFile, createCorrectionFile ? &outWvcFile : nullptr);
474 if (!WavpackSetConfiguration64(context.wpc, &config, -1, nullptr) || !WavpackPackInit(context.wpc)) {
475 throw ExportErrorException( WavpackGetErrorMessage(context.wpc) );
476 }
477
478 context.status = selectionOnly
479 ? XO("Exporting selected audio as WavPack")
480 : XO("Exporting the audio as WavPack");
481
482 context.metadata = std::make_unique<Tags>(
483 metadata == nullptr
484 ? Tags::Get( project )
485 : *metadata
486 );
487
488 context.mixer = ExportPluginHelpers::CreateMixer(tracks, selectionOnly,
489 t0, t1,
491 sampleRate, context.format, mixerSpec);
492
493 return true;
494}
495
497{
498 delegate.SetStatusString(context.status);
499
500 const size_t bufferSize = SAMPLES_PER_RUN * context.numChannels;
501
502 ArrayOf<int32_t> wavpackBuffer{ bufferSize };
503
504 auto exportResult = ExportResult::Success;
505 {
506
507 while (exportResult == ExportResult::Success) {
508 auto samplesThisRun = context.mixer->Process();
509
510 if (samplesThisRun == 0)
511 break;
512
513 if (context.format == int16Sample) {
514 const int16_t *mixed = reinterpret_cast<const int16_t*>(context.mixer->GetBuffer());
515 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
516 for (size_t i = 0; i < context.numChannels; i++) {
517 wavpackBuffer[j*context.numChannels + i] = (static_cast<int32_t>(*mixed++) * 65536) >> 16;
518 }
519 }
520 } else {
521 const int *mixed = reinterpret_cast<const int*>(context.mixer->GetBuffer());
522 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
523 for (size_t i = 0; i < context.numChannels; i++) {
524 wavpackBuffer[j*context.numChannels + i] = *mixed++;
525 }
526 }
527 }
528
529 if (!WavpackPackSamples(context.wpc, wavpackBuffer.get(), samplesThisRun)) {
530 throw ExportErrorException(WavpackGetErrorMessage(context.wpc));
531 }
533 delegate, *context.mixer, context.t0, context.t1);
534 }
535 }
536
537 if (!WavpackFlushSamples(context.wpc)) {
538 throw ExportErrorException( WavpackGetErrorMessage(context.wpc) );
539 } else {
540
541
542 wxString n;
543 for (const auto &pair : context.metadata->GetRange()) {
544 n = pair.first;
545 const auto &v = pair.second;
546
547 WavpackAppendTagItem(context.wpc,
548 n.mb_str(wxConvUTF8),
549 v.mb_str(wxConvUTF8),
550 static_cast<int>( strlen(v.mb_str(wxConvUTF8)) ));
551 }
552
553 if (!WavpackWriteTag(context.wpc)) {
554 throw ExportErrorException( WavpackGetErrorMessage(context.wpc) );
555 }
556 }
557
558 if ( !context.outWvFile.file.get()->Close()
559 || ( context.outWvcFile.file && context.outWvcFile.file.get() && !context.outWvcFile.file.get()->Close())) {
560 return ExportResult::Error;
561 }
562
563 // wxFile::Create opens the file with only write access
564 // So, need to open the file again with both read and write access
565 if (!context.outWvFile.file->Open(context.fName.GetFullPath(), wxFile::read_write)) {
566 throw ExportErrorException("Unable to update the actual length of the file");
567 }
568
569 ArrayOf<int32_t> firstBlockBuffer { context.outWvFile.firstBlockSize };
570 context.outWvFile.file->Read(firstBlockBuffer.get(), context.outWvFile.firstBlockSize);
571
572 // Update the first block written with the actual number of samples written
573 WavpackUpdateNumSamples(context.wpc, firstBlockBuffer.get());
574 context.outWvFile.file->Seek(0);
575 context.outWvFile.file->Write(firstBlockBuffer.get(), context.outWvFile.firstBlockSize);
576
577 if ( !context.outWvFile.file.get()->Close() ) {
578 return ExportResult::Error;
579 }
580 return exportResult;
581}
582
583
584// Based on the implementation of write_block in dbry/WavPack
585// src: https://github.com/dbry/WavPack/blob/master/cli/wavpack.c
586int WavPackExportProcessor::WriteBlock(void *id, void *data, int32_t length)
587{
588 if (id == nullptr || data == nullptr || length == 0)
589 return true; // This is considered to be success in wavpack.c reference code
590
591 WriteId *outId = static_cast<WriteId*>(id);
592
593 if (!outId->file)
594 // This does not match the wavpack.c but in our case if file is nullptr -
595 // the stream error has occured
596 return false;
597
598 // if (!outId->file->Write(data, length).IsOk()) {
599 if (outId->file->Write(data, length) != length) {
600 outId->file.reset();
601 return false;
602 }
603
604 outId->bytesWritten += length;
605
606 if (outId->firstBlockSize == 0)
607 outId->firstBlockSize = length;
608
609 return true;
610}
611
613 []{ return std::make_unique< ExportWavPack >(); }
614};
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:28
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:314
Holds a msgid for the translation catalog; may also bind format arguments.
std::unique_ptr< Mixer > mixer
TranslatableString status
struct WavPackExportProcessor::@182 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: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