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
18#include "Export.h"
19#include "wxFileNameWrapper.h"
20#include "Prefs.h"
21#include "Mix.h"
22
23#include <wavpack/wavpack.h>
24#include <wx/log.h>
25#include <wx/checkbox.h>
26#include <wx/choice.h>
27#include <wx/stream.h>
28
29#include "ShuttleGui.h"
30#include "../ProjectSettings.h"
31#include "AudacityMessageBox.h"
32#include "ProgressDialog.h"
33#include "Track.h"
34#include "ProjectRate.h"
35#include "Tags.h"
36
37//---------------------------------------------------------------------------
38// ExportWavPackOptions
39//---------------------------------------------------------------------------
40
41#define ID_HYBRID_MODE 9000
42#define ID_CREATE_WVC 9001
43
45{
46public:
47
48 ExportWavPackOptions(wxWindow *parent, int format);
49 virtual ~ExportWavPackOptions();
50
52 bool TransferDataToWindow() override;
53 bool TransferDataFromWindow() override;
54
55 void OnHybridMode(wxCommandEvent& evt);
56 void OnCreateCorrection(wxCommandEvent& evt);
57
58private:
59 wxCheckBox *mCreateCorrectionFile { nullptr };
60 wxChoice *mBitRate;
61
62 DECLARE_EVENT_TABLE()
63};
64
65BEGIN_EVENT_TABLE(ExportWavPackOptions, wxPanelWrapper)
69
70ExportWavPackOptions::ExportWavPackOptions(wxWindow *parent, int WXUNUSED(format))
71: wxPanelWrapper(parent, wxID_ANY)
72{
74 PopulateOrExchange(S);
75
76 TransferDataToWindow();
77}
78
80{
82}
83
85 XO("Low Quality (Fast)") ,
86 XO("Normal Quality") ,
87 XO("High Quality (Slow)") ,
88 XO("Very High Quality (Slowest)") ,
89};
90
91const std::vector< int > ExportQualityValues{
92 0,
93 1,
94 2,
95 3,
96};
97
98namespace
99{
101 XO("16 bit"),
102 XO("24 bit"),
103 XO("32 bit float "),
104};
105
106const std::vector<int> ExportBitDepthValues {
107 16,
108 24,
109 32,
110};
111
112IntSetting QualitySetting{ L"/FileFormats/WavPackEncodeQuality", 1 };
113IntSetting BitrateSetting{ L"/FileFormats/WavPackBitrate", 40 };
114IntSetting BitDepthSetting{ L"/FileFormats/WavPackBitDepth", 16 };
115
116BoolSetting HybridModeSetting{ L"/FileFormats/WavPackHybridMode", false };
117BoolSetting CreateCorrectionFileSetting{ L"/FileFormats/WavPackCreateCorrectionFile", false };
118
119/*
120Copied from ExportMP2.cpp by
121 Joshua Haberman
122 Markus Meyer
123*/
124
125// i18n-hint bps abbreviates "bits per sample"
126inline TranslatableString n_bps( int n ) { return XO("%.1f bps").Format( n / 10.0 ); }
127
129 n_bps(22),
130 n_bps(25),
131 n_bps(30),
132 n_bps(35),
133 n_bps(40),
134 n_bps(45),
135 n_bps(50),
136 n_bps(60),
137 n_bps(70),
138 n_bps(80),
139};
140
141const std::vector< int > BitRateValues {
142 22,
143 25,
144 30,
145 35,
146 40,
147 45,
148 50,
149 60,
150 70,
151 80,
152};
153
154}
155
157{
158 bool hybridMode = HybridModeSetting.Read();
159
160 S.StartVerticalLay();
161 {
162 S.StartHorizontalLay(wxEXPAND);
163 {
164 S.SetSizerProportion(1);
165 S.StartMultiColumn(2, wxCENTER);
166 {
167 S.TieNumberAsChoice(
168 XXO("Quality"),
172 );
173
174 S.TieNumberAsChoice(
175 XXO("Bit Depth"),
179 );
180
181 S.Id(ID_HYBRID_MODE).TieCheckBox( XXO("Hybrid Mode"), HybridModeSetting);
182
183 mCreateCorrectionFile = S.Id(ID_CREATE_WVC).Disable(!hybridMode).TieCheckBox(
184 XXO("Create Correction(.wvc) File"),
186 );
187
188 mBitRate = S.Disable(!hybridMode).TieNumberAsChoice(
189 XXO("Bit Rate:"),
193 );
194 }
195 S.EndMultiColumn();
196 }
197 S.EndHorizontalLay();
198 }
199 S.EndVerticalLay();
200}
201
203{
204 return true;
205}
206
208{
211
212 gPrefs->Flush();
213
214 return true;
215}
216
218{
219 const auto hybridMode = HybridModeSetting.Toggle();
220 mCreateCorrectionFile->Enable(hybridMode);
221 mBitRate->Enable(hybridMode);
222};
223
225{
227};
228
229//---------------------------------------------------------------------------
230// ExportWavPack
231//---------------------------------------------------------------------------
232
233struct WriteId final
234{
235 uint32_t bytesWritten {};
236 uint32_t firstBlockSize {};
237 std::unique_ptr<wxFile> file;
238};
239
240class ExportWavPack final : public ExportPlugin
241{
242public:
243
245
246 void OptionsCreate(ShuttleGui &S, int format) override;
247
249 std::unique_ptr<BasicUI::ProgressDialog>& pDialog,
250 unsigned channels,
251 const wxFileNameWrapper &fName,
252 bool selectedOnly,
253 double t0,
254 double t1,
255 MixerSpec *mixerSpec = NULL,
256 const Tags *metadata = NULL,
257 int subformat = 0) override;
258
259 static int WriteBlock(void *id, void *data, int32_t length);
260};
261
263: ExportPlugin()
264{
265 AddFormat();
266 SetFormat(wxT("WavPack"),0);
267 AddExtension(wxT("wv"),0);
268 SetMaxChannels(255,0);
269 SetCanMetaData(true,0);
270 SetDescription(XO("WavPack Files"),0);
271}
272
274 std::unique_ptr<BasicUI::ProgressDialog> &pDialog,
275 unsigned numChannels,
276 const wxFileNameWrapper &fName,
277 bool selectionOnly,
278 double t0,
279 double t1,
280 MixerSpec *mixerSpec,
281 const Tags *metadata,
282 int WXUNUSED(subformat))
283{
284 WavpackConfig config = {};
285 WriteId outWvFile, outWvcFile;
286 outWvFile.file = std::make_unique< wxFile >();
287
288 if (!outWvFile.file->Create(fName.GetFullPath(), true) || !outWvFile.file.get()->IsOpened()) {
289 AudacityMessageBox( XO("Unable to open target file for writing") );
290 return ProgressResult::Failed;
291 }
292
293 double rate = ProjectRate::Get( *project ).GetRate();
294 const auto &tracks = TrackList::Get( *project );
295
296 int quality = QualitySetting.Read();
297 bool hybridMode = HybridModeSetting.Read();
298 bool createCorrectionFile = CreateCorrectionFileSetting.Read();
299 int bitRate = BitrateSetting.Read();
300 int bitDepth = BitDepthSetting.Read();
301
303 if (bitDepth == 24) {
305 } else if (bitDepth == 32) {
307 }
308
309 config.num_channels = numChannels;
310 config.sample_rate = rate;
311 config.bits_per_sample = bitDepth;
312 config.bytes_per_sample = bitDepth/8;
313 config.float_norm_exp = format == floatSample ? 127 : 0;
314
315 if (config.num_channels <= 2)
316 config.channel_mask = 0x5 - config.num_channels;
317 else if (config.num_channels <= 18)
318 config.channel_mask = (1U << config.num_channels) - 1;
319 else
320 config.channel_mask = 0x3FFFF;
321
322 if (quality == 0) {
323 config.flags |= CONFIG_FAST_FLAG;
324 } else if (quality == 2) {
325 config.flags |= CONFIG_HIGH_FLAG;
326 } else if (quality == 3) {
327 config.flags |= CONFIG_HIGH_FLAG | CONFIG_VERY_HIGH_FLAG;
328 }
329
330 if (hybridMode) {
331 config.flags |= CONFIG_HYBRID_FLAG;
332 config.bitrate = bitRate / 10.0;
333
334 if (createCorrectionFile) {
335 config.flags |= CONFIG_CREATE_WVC;
336
337 outWvcFile.file = std::make_unique< wxFile >();
338 if (!outWvcFile.file->Create(fName.GetFullPath().Append("c"), true)) {
339 AudacityMessageBox( XO("Unable to create target file for writing") );
340 return ProgressResult::Failed;
341 }
342 }
343 }
344
345 // If we're not creating a correction file now, any one that currently exists with this name
346 // will become obsolete now, so delete it if it happens to exist (although it usually won't)
347
348 if (!hybridMode || !createCorrectionFile)
349 wxRemoveFile(fName.GetFullPath().Append("c"));
350
351 WavpackContext *wpc = WavpackOpenFileOutput(WriteBlock, &outWvFile, createCorrectionFile ? &outWvcFile : nullptr);
352 auto closeWavPackContext = finally([wpc]() { WavpackCloseFile(wpc); });
353
354 if (!WavpackSetConfiguration64(wpc, &config, -1, nullptr) || !WavpackPackInit(wpc)) {
355 ShowExportErrorDialog( WavpackGetErrorMessage(wpc) );
356 return ProgressResult::Failed;
357 }
358
359 // Samples to write per run
360 constexpr size_t SAMPLES_PER_RUN = 8192u;
361
362 const size_t bufferSize = SAMPLES_PER_RUN * numChannels;
363 ArrayOf<int32_t> wavpackBuffer{ bufferSize };
364 auto updateResult = ProgressResult::Success;
365 {
366 auto mixer = CreateMixer(tracks, selectionOnly,
367 t0, t1,
368 numChannels, SAMPLES_PER_RUN, true,
369 rate, format, mixerSpec);
370
371 InitProgress( pDialog, fName,
372 selectionOnly
373 ? XO("Exporting selected audio as WavPack")
374 : XO("Exporting the audio as WavPack") );
375 auto &progress = *pDialog;
376
377 while (updateResult == ProgressResult::Success) {
378 auto samplesThisRun = mixer->Process();
379
380 if (samplesThisRun == 0)
381 break;
382
383 if (format == int16Sample) {
384 const int16_t *mixed = reinterpret_cast<const int16_t*>(mixer->GetBuffer());
385 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
386 for (size_t i = 0; i < numChannels; i++) {
387 wavpackBuffer[j*numChannels + i] = (static_cast<int32_t>(*mixed++) * 65536) >> 16;
388 }
389 }
390 } else {
391 const int *mixed = reinterpret_cast<const int*>(mixer->GetBuffer());
392 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
393 for (size_t i = 0; i < numChannels; i++) {
394 wavpackBuffer[j*numChannels + i] = *mixed++;
395 }
396 }
397 }
398
399 if (!WavpackPackSamples(wpc, wavpackBuffer.get(), samplesThisRun)) {
400 ShowExportErrorDialog( WavpackGetErrorMessage(wpc) );
401 return ProgressResult::Failed;
402 }
403
404 if (updateResult == ProgressResult::Success)
405 updateResult =
406 progress.Poll(mixer->MixGetCurrentTime() - t0, t1 - t0);
407 }
408 }
409
410 if (!WavpackFlushSamples(wpc)) {
411 ShowExportErrorDialog( WavpackGetErrorMessage(wpc) );
412 return ProgressResult::Failed;
413 } else {
414 if (metadata == NULL)
415 metadata = &Tags::Get( *project );
416
417 wxString n;
418 for (const auto &pair : metadata->GetRange()) {
419 n = pair.first;
420 const auto &v = pair.second;
421
422 WavpackAppendTagItem(wpc,
423 n.mb_str(wxConvUTF8),
424 v.mb_str(wxConvUTF8),
425 static_cast<int>( strlen(v.mb_str(wxConvUTF8)) ));
426 }
427
428 if (!WavpackWriteTag(wpc)) {
429 ShowExportErrorDialog( WavpackGetErrorMessage(wpc) );
430 return ProgressResult::Failed;
431 }
432 }
433
434 if ( !outWvFile.file.get()->Close()
435 || ( outWvcFile.file && outWvcFile.file.get() && !outWvcFile.file.get()->Close())) {
436 return ProgressResult::Failed;
437 }
438
439 // wxFile::Create opens the file with only write access
440 // So, need to open the file again with both read and write access
441 if (!outWvFile.file->Open(fName.GetFullPath(), wxFile::read_write)) {
442 ShowExportErrorDialog( "Unable to update the actual length of the file" );
443 return ProgressResult::Failed;
444 }
445
446 ArrayOf<int32_t> firstBlockBuffer { outWvFile.firstBlockSize };
447 size_t bytesRead = outWvFile.file->Read(firstBlockBuffer.get(), outWvFile.firstBlockSize);
448
449 // Update the first block written with the actual number of samples written
450 WavpackUpdateNumSamples(wpc, firstBlockBuffer.get());
451 outWvFile.file->Seek(0);
452 size_t bytesWritten = outWvFile.file->Write(firstBlockBuffer.get(), outWvFile.firstBlockSize);
453
454 if ( !outWvFile.file.get()->Close() ) {
455 return ProgressResult::Failed;
456 }
457
458 return updateResult;
459}
460
461// Based on the implementation of write_block in dbry/WavPack
462// src: https://github.com/dbry/WavPack/blob/master/cli/wavpack.c
463int ExportWavPack::WriteBlock(void *id, void *data, int32_t length)
464{
465 if (id == nullptr || data == nullptr || length == 0)
466 return true; // This is considered to be success in wavpack.c reference code
467
468 WriteId *outId = static_cast<WriteId*>(id);
469
470 if (!outId->file)
471 // This does not match the wavpack.c but in our case if file is nullptr -
472 // the stream error has occured
473 return false;
474
475 // if (!outId->file->Write(data, length).IsOk()) {
476 if (outId->file->Write(data, length) != length) {
477 outId->file.reset();
478 return false;
479 }
480
481 outId->bytesWritten += length;
482
483 if (outId->firstBlockSize == 0)
484 outId->firstBlockSize = length;
485
486 return true;
487}
488
490{
491 S.AddWindow( safenew ExportWavPackOptions{ S.GetParent(), format } );
492}
493
495 []{ return std::make_unique< ExportWavPack >(); }
496};
497
498#ifdef HAS_CLOUD_UPLOAD
499#include "CloudExporterPlugin.h"
501
502class WavPackCloudHelper : public cloud::CloudExporterPlugin
503{
504public:
505 wxString GetExporterID() const override
506 {
507 return "WavPack";
508 }
509
510 FileExtension GetFileExtension() const override
511 {
512 return "wv";
513 }
514
515 void OnBeforeExport() override
516 {
521 }
522
523}; // WavPackCloudHelper
524
525static bool cloudExporterRegisterd = cloud::RegisterCloudExporter(
526 "audio/x-wavpack",
527 [](const AudacityProject&) { return std::make_unique<WavPackCloudHelper>(); });
528#endif
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
END_EVENT_TABLE()
void ShowExportErrorDialog(wxString ErrorCode, TranslatableString message, const TranslatableString &caption, bool allowReporting)
Definition: Export.cpp:1503
#define SAMPLES_PER_RUN
Definition: ExportFLAC.cpp:160
int format
Definition: ExportPCM.cpp:53
#define ID_HYBRID_MODE
const std::vector< int > ExportQualityValues
static Exporter::RegisteredExportPlugin sRegisteredPlugin
const TranslatableStrings ExportQualityNames
#define ID_CREATE_WVC
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
wxString FileExtension
File extension, not including any leading dot.
Definition: Identifier.h:224
#define safenew
Definition: MemoryX.h:10
FileConfig * gPrefs
Definition: Prefs.cpp:70
an object holding per-project preferred sample rate
sampleFormat
The ordering of these values with operator < agrees with the order of increasing bit width.
Definition: SampleFormat.h:30
@ eIsCreatingFromPrefs
Definition: ShuttleGui.h:46
@ eIsSavingToPrefs
Definition: ShuttleGui.h:47
#define S(N)
Definition: ToChars.cpp:64
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
This specialization of Setting for bool adds a Toggle method to negate the saved value.
Definition: Prefs.h:339
bool Toggle()
Write the negation of the previous value, and then return the current value.
Definition: Prefs.cpp:514
void AddExtension(const FileExtension &extension, int index)
Definition: Export.cpp:126
int AddFormat()
Add a NEW entry to the list of formats this plug-in can export.
Definition: Export.cpp:100
static void InitProgress(std::unique_ptr< BasicUI::ProgressDialog > &pDialog, const TranslatableString &title, const TranslatableString &message)
Definition: Export.cpp:250
void SetFormat(const wxString &format, int index)
Definition: Export.cpp:116
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, MixerSpec *mixerSpec)
Definition: Export.cpp:222
void SetDescription(const TranslatableString &description, int index)
Definition: Export.cpp:121
void SetCanMetaData(bool canmetadata, int index)
Definition: Export.cpp:146
void SetMaxChannels(unsigned maxchannels, unsigned index)
Definition: Export.cpp:141
void OptionsCreate(ShuttleGui &S, int format) override
ProgressResult Export(AudacityProject *project, std::unique_ptr< BasicUI::ProgressDialog > &pDialog, unsigned channels, const wxFileNameWrapper &fName, bool selectedOnly, double t0, double t1, MixerSpec *mixerSpec=NULL, const Tags *metadata=NULL, int subformat=0) override
called to export audio into a file.
static int WriteBlock(void *id, void *data, int32_t length)
ExportWavPackOptions(wxWindow *parent, int format)
wxCheckBox * mCreateCorrectionFile
void OnHybridMode(wxCommandEvent &evt)
void OnCreateCorrection(wxCommandEvent &evt)
virtual ~ExportWavPackOptions()
bool TransferDataToWindow() override
void PopulateOrExchange(ShuttleGui &S)
bool TransferDataFromWindow() override
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:143
Specialization of Setting for int.
Definition: Prefs.h:349
A matrix of booleans, one row per input channel, column per output.
Definition: MixerOptions.h:32
static ProjectRate & Get(AudacityProject &project)
Definition: ProjectRate.cpp:28
double GetRate() const
Definition: ProjectRate.cpp:53
bool Write(const T &value)
Write value to config and return true if successful.
Definition: Prefs.h:252
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:200
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:625
ID3 Tags (for MP3)
Definition: Tags.h:73
Iterators GetRange() const
Definition: Tags.cpp:436
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:487
Holds a msgid for the translation catalog; may also bind format arguments.
Helper interface, that allows to setup the desired export format on the ExportPlugin.
virtual wxString GetExporterID() const =0
Identifier of the ExportPlugin to be used.
virtual FileExtension GetFileExtension() const =0
File extension that is expected with this plugin.
virtual void OnBeforeExport()=0
Setup the preferred format for the export.
ProgressResult
Definition: BasicUI.h:147
const TranslatableStrings ExportBitDepthNames
const std::vector< int > ExportBitDepthValues
const std::vector< int > BitRateValues
const TranslatableStrings BitRateNames
bool RegisterCloudExporter(MimeType mimeType, CloudExporterPluginFactory factory)
Registers a factory for a specific mime type.
uint32_t bytesWritten
uint32_t firstBlockSize
std::unique_ptr< wxFile > file