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