Audacity 3.2.0
EqualizationCurves.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 EqualizationCurves.cpp
6
7 Mitch Golden
8 Vaughan Johnson (Preview)
9 Martyn Shaw (FIR filters, response curve, graphic EQ)
10
11 Paul Licameli split from Equalization.cpp
12
13**********************************************************************/
14
15#include "EqualizationCurves.h"
17
18#include <wx/log.h>
19#include "AudacityMessageBox.h"
20#include "FileNames.h"
21#include "XMLFileReader.h"
22
23// Increment whenever EQCurves.xml is updated
24#define EQCURVES_VERSION 1
25#define EQCURVES_REVISION 0
26#define UPDATE_ALL 0 // 0 = merge NEW presets only, 1 = Update all factory presets.
27
29{
30 wxString base = wxT("/Effects/Equalization/");
32 base = wxT("/Effects/GraphicEq/");
33 else if( mOptions == kEqOptionCurve )
34 base = wxT("/Effects/FilterCurve/");
35 return base;
36}
37
40 const TranslatableString &msg,
41 const TranslatableString &titleStr,
42 long style)
43{
44 // Compare with EffectUIServices::DoMessageBox
45 auto title = titleStr.empty()
46 ? name
47 : XO("%s: %s").Format( name, titleStr );
48 return AudacityMessageBox( msg, title, style, nullptr );
49}
50
51//
52// Load external curves with fallback to default, then message
53//
54void EQCurveReader::LoadCurves(const wxString &fileName, bool append)
55{
56// We've disabled the XML management of curves.
57// Just going via .cfg files now.
58#ifndef LEGACY_EQ
59 (void)fileName;
60 (void)append;
61 mCurves.clear();
62 mCurves.push_back( wxT("unnamed") ); // we still need a default curve to use
63#else
64 // Construct normal curve filename
65 //
66 // LLL: Wouldn't you know that as of WX 2.6.2, there is a conflict
67 // between wxStandardPaths and wxConfig under Linux. The latter
68 // creates a normal file as "$HOME/.audacity", while the former
69 // expects the ".audacity" portion to be a directory.
70 // MJS: I don't know what the above means, or if I have broken it.
71 wxFileName fn;
72
73 if(fileName.empty()) {
74 // Check if presets are up to date.
75 wxString eqCurvesCurrentVersion = wxString::Format(wxT("%d.%d"), EQCURVES_VERSION, EQCURVES_REVISION);
76 wxString eqCurvesInstalledVersion;
77 gPrefs->Read(GetPrefsPrefix() + "PresetVersion", &eqCurvesInstalledVersion, wxT(""));
78
79 bool needUpdate = (eqCurvesCurrentVersion != eqCurvesInstalledVersion);
80
81 // UpdateDefaultCurves allows us to import NEW factory presets only,
82 // or update all factory preset curves.
83 if (needUpdate)
85 fn = wxFileName( FileNames::DataDir(), wxT("EQCurves.xml") );
86 }
87 else
88 fn = fileName; // user is loading a specific set of curves
89
90 // If requested file doesn't exist...
91 if( !fn.FileExists() && !GetDefaultFileName(fn) ) {
92 mCurves.clear();
93 /* i18n-hint: name of the 'unnamed' custom curve */
94 mCurves.push_back( _("unnamed") ); // we still need a default curve to use
95 return;
96 }
97
98 EQCurve tempCustom(wxT("temp"));
99 if( append == false ) // Start from scratch
100 mCurves.clear();
101 else // appending so copy and remove 'unnamed', to replace later
102 {
103 tempCustom.points = mCurves.back().points;
104 mCurves.pop_back();
105 }
106
107 // Load the curves
108 XMLFileReader reader;
109 const wxString fullPath{ fn.GetFullPath() };
110 if( !reader.Parse( this, fullPath ) )
111 {
112 /* i18n-hint: EQ stands for 'Equalization'.*/
113 auto msg = XO("Error Loading EQ Curves from file:\n%s\nError message says:\n%s")
114 .Format( fullPath, reader.GetErrorStr() );
115 // Inform user of load failure
116 EQUtils::DoMessageBox(mName, msg, XO("Error Loading EQ Curves"));
117 mCurves.push_back( _("unnamed") ); // we always need a default curve to use
118 return;
119 }
120
121 // Move "unnamed" to end, if it exists in current language.
122 int numCurves = mCurves.size();
123 int curve;
124 EQCurve tempUnnamed(wxT("tempUnnamed"));
125 for( curve = 0; curve < numCurves-1; curve++ )
126 {
127 if( mCurves[curve].Name == _("unnamed") )
128 {
129 tempUnnamed.points = mCurves[curve].points;
130 mCurves.erase(mCurves.begin() + curve);
131 mCurves.push_back( _("unnamed") ); // add 'unnamed' back at the end
132 mCurves.back().points = tempUnnamed.points;
133 }
134 }
135
136 if( mCurves.back().Name != _("unnamed") )
137 mCurves.push_back( _("unnamed") ); // we always need a default curve to use
138 if( append == true )
139 {
140 mCurves.back().points = tempCustom.points;
141 }
142#endif
143 return;
144}
145
146//
147// Update presets to match Audacity version.
148//
149void EQCurveReader::UpdateDefaultCurves(bool updateAll /* false */)
150{
151 if (mCurves.size() == 0)
152 return;
153
154 wxString unnamed = wxT("unnamed");
155
156 // Save the "unnamed" curve and remove it so we can add it back as the final curve.
157 EQCurve userUnnamed(wxT("temp"));
158 userUnnamed = mCurves.back();
159 mCurves.pop_back();
160
161 EQCurveArray userCurves = mCurves;
162 mCurves.clear();
163 // We only wamt to look for the shipped EQDefaultCurves.xml
164 wxFileName fn = wxFileName(FileNames::ResourcesDir(), wxT("EQDefaultCurves.xml"));
165 wxLogDebug(wxT("Attempting to load EQDefaultCurves.xml from %s"),fn.GetFullPath());
166 XMLFileReader reader;
167
168 if(!reader.Parse(this, fn.GetFullPath())) {
169 wxLogError(wxT("EQDefaultCurves.xml could not be read."));
170 return;
171 }
172 else {
173 wxLogDebug(wxT("Loading EQDefaultCurves.xml successful."));
174 }
175
176 EQCurveArray defaultCurves = mCurves;
177 mCurves.clear(); // clear now so that we can sort then add back.
178
179 // Remove "unnamed" if it exists.
180 if (defaultCurves.back().Name == unnamed) {
181 defaultCurves.pop_back();
182 }
183 else {
184 wxLogError(wxT("Error in EQDefaultCurves.xml"));
185 }
186
187 int numUserCurves = userCurves.size();
188 int numDefaultCurves = defaultCurves.size();
189 EQCurve tempCurve(wxT("test"));
190
191 if (updateAll) {
192 // Update all factory preset curves.
193 // Sort and add factory defaults first;
194 mCurves = defaultCurves;
195 std::sort(mCurves.begin(), mCurves.end());
196 // then add remaining user curves:
197 for (int curveCount = 0; curveCount < numUserCurves; curveCount++) {
198 bool isCustom = true;
199 tempCurve = userCurves[curveCount];
200 // is the name in the default set?
201 for (int defCurveCount = 0; defCurveCount < numDefaultCurves; defCurveCount++) {
202 if (tempCurve.Name == mCurves[defCurveCount].Name) {
203 isCustom = false;
204 break;
205 }
206 }
207 // if tempCurve is not in the default set, add it to mCurves.
208 if (isCustom) {
209 mCurves.push_back(tempCurve);
210 }
211 }
212 }
213 else {
214 // Import NEW factory defaults but retain all user modified curves.
215 for (int defCurveCount = 0; defCurveCount < numDefaultCurves; defCurveCount++) {
216 bool isUserCurve = false;
217 // Add if the curve is in the user's set (preserve user's copy)
218 for (int userCurveCount = 0; userCurveCount < numUserCurves; userCurveCount++) {
219 if (userCurves[userCurveCount].Name == defaultCurves[defCurveCount].Name) {
220 isUserCurve = true;
221 mCurves.push_back(userCurves[userCurveCount]);
222 break;
223 }
224 }
225 if (!isUserCurve) {
226 mCurves.push_back(defaultCurves[defCurveCount]);
227 }
228 }
229 std::sort(mCurves.begin(), mCurves.end());
230 // now add the rest of the user's curves.
231 for (int userCurveCount = 0; userCurveCount < numUserCurves; userCurveCount++) {
232 bool isDefaultCurve = false;
233 tempCurve = userCurves[userCurveCount];
234 for (int defCurveCount = 0; defCurveCount < numDefaultCurves; defCurveCount++) {
235 if (tempCurve.Name == defaultCurves[defCurveCount].Name) {
236 isDefaultCurve = true;
237 break;
238 }
239 }
240 if (!isDefaultCurve) {
241 mCurves.push_back(tempCurve);
242 }
243 }
244 }
245 defaultCurves.clear();
246 userCurves.clear();
247
248 // Add back old "unnamed"
249 if(userUnnamed.Name == unnamed) {
250 mCurves.push_back( userUnnamed ); // we always need a default curve to use
251 }
252
253 EQCurveWriter{ mCurves }.SaveCurves();
254
255 // Write current EqCurve version number
256 // TODO: Probably better if we used pluginregistry.cfg
257 wxString eqCurvesCurrentVersion = wxString::Format(wxT("%d.%d"), EQCURVES_VERSION, EQCURVES_REVISION);
258 gPrefs->Write(GetPrefsPrefix()+"PresetVersion", eqCurvesCurrentVersion);
259 gPrefs->Flush();
260
261 return;
262}
263
264//
265// Get fully qualified filename of EQDefaultCurves.xml
266//
267bool EQCurveReader::GetDefaultFileName(wxFileName &fileName)
268{
269 // look in data dir first, in case the user has their own defaults (maybe downloaded ones)
270 fileName = wxFileName( FileNames::DataDir(), wxT("EQDefaultCurves.xml") );
271 if( !fileName.FileExists() )
272 { // Default file not found in the data dir. Fall back to Resources dir.
273 // See http://docs.wxwidgets.org/trunk/classwx_standard_paths.html#5514bf6288ee9f5a0acaf065762ad95d
274 fileName = wxFileName( FileNames::ResourcesDir(), wxT("EQDefaultCurves.xml") );
275 }
276 if( !fileName.FileExists() )
277 {
278 // LLL: Is there really a need for an error message at all???
279 //auto errorMessage = XO("EQCurves.xml and EQDefaultCurves.xml were not found on your system.\nPlease press 'help' to visit the download page.\n\nSave the curves at %s")
280 // .Format( FileNames::DataDir() );
281 //BasicUI::ShowErrorDialog( wxWidgetsWindowPlacement{ mUIParent },
282 // XO("EQCurves.xml and EQDefaultCurves.xml missing"),
283 // errorMessage, wxT("http://wiki.audacityteam.org/wiki/EQCurvesDownload"), false);
284
285 // Have another go at finding EQCurves.xml in the data dir, in case 'help' helped
286 fileName = wxFileName( FileNames::DataDir(), wxT("EQDefaultCurves.xml") );
287 }
288 return (fileName.FileExists());
289}
290
291
292//
293// Save curves to external file
294//
295void EQCurveWriter::SaveCurves(const wxString &fileName)
296{
297 wxFileName fn;
298 if( fileName.empty() )
299 {
300 // Construct default curve filename
301 //
302 // LLL: Wouldn't you know that as of WX 2.6.2, there is a conflict
303 // between wxStandardPaths and wxConfig under Linux. The latter
304 // creates a normal file as "$HOME/.audacity", while the former
305 // expects the ".audacity" portion to be a directory.
306 fn = wxFileName( FileNames::DataDir(), wxT("EQCurves.xml") );
307
308 // If the directory doesn't exist...
309 if( !fn.DirExists() )
310 {
311 // Attempt to create it
312 if( !fn.Mkdir( fn.GetPath(), 511, wxPATH_MKDIR_FULL ) )
313 {
314 // MkDir() will emit message
315 return;
316 }
317 }
318 }
319 else
320 fn = fileName;
321
322 GuardedCall( [&] {
323 // Create/Open the file
324 const wxString fullPath{ fn.GetFullPath() };
325 XMLFileWriter eqFile{ fullPath, XO("Error Saving Equalization Curves") };
326
327 // Write the curves
328 WriteXML( eqFile );
329
330 eqFile.Commit();
331 } );
332}
333
334//
335// Process XML tags and handle the ones we recognize
336//
338 const std::string_view& tag, const AttributesList &attrs)
339{
340 // May want to add a version strings...
341 if (tag == "equalizationeffect")
342 {
343 return true;
344 }
345
346 // Located a NEW curve
347 if (tag == "curve")
348 {
349 // Process the attributes
350 for (auto pair : attrs)
351 {
352 auto attr = pair.first;
353 auto value = pair.second;
354
355 // Create a NEW curve and name it
356 if( attr == "name" )
357 {
358 const wxString strValue = value.ToWString();
359 // check for a duplicate name and add (n) if there is one
360 int n = 0;
361 wxString strValueTemp = strValue;
362 bool exists;
363 do
364 {
365 exists = false;
366 for(size_t i = 0; i < mCurves.size(); i++)
367 {
368 if(n>0)
369 strValueTemp.Printf(wxT("%s (%d)"),strValue,n);
370 if(mCurves[i].Name == strValueTemp)
371 {
372 exists = true;
373 break;
374 }
375 }
376 n++;
377 }
378 while(exists == true);
379
380 mCurves.push_back( EQCurve( strValueTemp ) );
381 }
382 }
383
384 // Tell caller it was processed
385 return true;
386 }
387
388 // Located a NEW point
389 if(tag == "point")
390 {
391 // Set defaults in case attributes are missing
392 double f = 0.0;
393 double d = 0.0;
394
395 // Process the attributes
396 double dblValue;
397 for (auto pair : attrs)
398 {
399 auto attr = pair.first;
400 auto value = pair.second;
401
402 // Get the frequency
403 if( attr == "f" )
404 {
405 if (!value.TryGet(dblValue))
406 return false;
407 f = dblValue;
408 }
409 // Get the dB
410 else if( attr == "d" )
411 {
412 if (!value.TryGet(dblValue))
413 return false;
414 d = dblValue;
415 }
416 }
417
418 // Create a NEW point
419 mCurves[ mCurves.size() - 1 ].points.push_back( EQPoint( f, d ) );
420
421 // Tell caller it was processed
422 return true;
423 }
424
425 // Tell caller we didn't understand the tag
426 return false;
427}
428
429//
430// Return handler for recognized tags
431//
432XMLTagHandler *EQCurveReader::HandleXMLChild(const std::string_view& tag)
433{
434 if (tag == "equalizationeffect")
435 {
436 return this;
437 }
438
439 if (tag == "curve")
440 {
441 return this;
442 }
443
444 if (tag == "point")
445 {
446 return this;
447 }
448
449 return NULL;
450}
451
452//
453// Write all of the curves to the XML file
454//
456// may throw
457{
458 // Start our hierarchy
459 xmlFile.StartTag( wxT( "equalizationeffect" ) );
460
461 // Write all curves
462 int numCurves = mCurves.size();
463 int curve;
464 for( curve = 0; curve < numCurves; curve++ )
465 {
466 // Start a NEW curve
467 xmlFile.StartTag( wxT( "curve" ) );
468 xmlFile.WriteAttr( wxT( "name" ), mCurves[ curve ].Name );
469
470 // Write all points
471 int numPoints = mCurves[ curve ].points.size();
472 int point;
473 for( point = 0; point < numPoints; point++ )
474 {
475 // Write NEW point
476 xmlFile.StartTag( wxT( "point" ) );
477 xmlFile.WriteAttr( wxT( "f" ), mCurves[ curve ].points[ point ].Freq, 12 );
478 xmlFile.WriteAttr( wxT( "d" ), mCurves[ curve ].points[ point ].dB, 12 );
479 xmlFile.EndTag( wxT( "point" ) );
480 }
481
482 // Terminate curve
483 xmlFile.EndTag( wxT( "curve" ) );
484 }
485
486 // Terminate our hierarchy
487 xmlFile.EndTag( wxT( "equalizationeffect" ) );
488}
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)
const TranslatableString name
Definition: Distortion.cpp:76
#define EQCURVES_REVISION
#define UPDATE_ALL
#define EQCURVES_VERSION
std::vector< EQCurve > EQCurveArray
const int kEqOptionCurve
const int kEqOptionGraphic
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
static const auto title
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
static const auto fn
std::vector< Attribute > AttributesList
Definition: XMLTagHandler.h:40
One curve in a list.
std::vector< EQPoint > points
wxString Name
wxString GetPrefsPrefix()
const TranslatableString mName
bool GetDefaultFileName(wxFileName &fileName)
EQCurveArray & mCurves
void UpdateDefaultCurves(bool updateAll=false)
bool HandleXMLTag(const std::string_view &tag, const AttributesList &attrs) override
XMLTagHandler * HandleXMLChild(const std::string_view &tag) override
void LoadCurves(const wxString &fileName={}, bool append=false)
Serializer of curves into XML files.
void SaveCurves(const wxString &fileName={})
void WriteXML(XMLWriter &xmlFile) const
One point in a curve.
Holds a msgid for the translation catalog; may also bind format arguments.
Reads a file and passes the results through an XMLTagHandler.
Definition: XMLFileReader.h:19
const TranslatableString & GetErrorStr() const
bool Parse(XMLTagHandler *baseHandler, const FilePath &fname)
Wrapper to output XML data to files.
Definition: XMLWriter.h:84
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:42
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:25
virtual bool Flush() noexcept=0
virtual bool Write(const wxString &key, bool value)=0
virtual bool Read(const wxString &key, bool *value) const =0
int DoMessageBox(const TranslatableString &name, const TranslatableString &msg, const TranslatableString &titleStr, long style=wxOK|wxCENTRE)
FILES_API FilePath ResourcesDir()
FILES_API FilePath DataDir()
Audacity user data directory.