From 8fbfa460c44b4e56c54852bf321b10810f9af296 Mon Sep 17 00:00:00 2001 From: Leland Lucius Date: Thu, 16 Apr 2015 22:53:42 -0500 Subject: [PATCH] Migrating the remaining effects This brings the builtin, LV2, and VAMP effects inline with the Audio Units, LADSPA, and VST effects. All effects now share a common UI. This gives all effects (though not implemented for all): User and factory preset capability Preset import/export capability Shared or private configuration options Builtin effects can now be migrated to RTP, depending on algorithm. LV2 effects now support graphical interfaces if the plugin supplies one. Nyquist prompt enhanced to provide some features of the Nyquist Workbench. It may not look like it, but this was a LOT of work, so trust me, there WILL be problems and everything effect related should be suspect. Keep a sharp eye (or two) open. --- images/Effect.h | 309 -- include/audacity/EffectAutomationParameters.h | 117 +- include/audacity/EffectInterface.h | 40 +- include/audacity/PluginInterface.h | 6 +- include/audacity/Types.h | 2 +- lib-src/mod-nyq-bench/NyqBench.cpp | 6 +- lib-src/mod-nyq-bench/NyqBench.h | 2 +- locale/POTFILES.in | 7 - mac/Audacity.xcodeproj/project.pbxproj | 26 +- src/Audacity.h | 16 - src/AudacityApp.cpp | 5 - src/AudioIO.cpp | 15 +- src/BatchCommands.cpp | 66 +- src/BatchCommands.h | 1 + src/Experimental.h | 9 +- src/Makefile.am | 7 - src/Makefile.in | 60 +- src/Menus.cpp | 455 +-- src/Menus.h | 33 +- src/ModuleManager.cpp | 6 +- src/PluginManager.cpp | 62 +- src/PluginManager.h | 22 +- src/Project.cpp | 5 +- src/Project.h | 13 +- src/ShuttleGui.cpp | 12 +- src/ShuttleGui.h | 10 +- src/SseMathFuncs.h | 56 +- src/audacity.xml | 16 +- src/commands/CommandManager.cpp | 2 +- src/effects/Amplify.cpp | 454 ++- src/effects/Amplify.h | 107 +- src/effects/AutoDuck.cpp | 595 ++- src/effects/AutoDuck.h | 177 +- src/effects/BassTreble.cpp | 678 ++-- src/effects/BassTreble.h | 122 +- src/effects/ChangePitch.cpp | 1248 +++--- src/effects/ChangePitch.h | 141 +- src/effects/ChangeSpeed.cpp | 710 ++-- src/effects/ChangeSpeed.h | 128 +- src/effects/ChangeTempo.cpp | 581 ++- src/effects/ChangeTempo.h | 124 +- src/effects/ClickRemoval.cpp | 306 +- src/effects/ClickRemoval.h | 103 +- src/effects/Compressor.cpp | 639 +-- src/effects/Compressor.h | 166 +- src/effects/Contrast.cpp | 8 +- src/effects/DtmfGen.cpp | 789 ++-- src/effects/DtmfGen.h | 148 +- src/effects/Echo.cpp | 278 +- src/effects/Echo.h | 104 +- src/effects/Effect.cpp | 1618 +++++--- src/effects/Effect.h | 449 ++- src/effects/EffectCategory.cpp | 100 - src/effects/EffectCategory.h | 139 - src/effects/EffectManager.cpp | 299 +- src/effects/EffectManager.h | 103 +- src/effects/Equalization.cpp | 3467 ++++++++--------- src/effects/Equalization.h | 423 +- src/effects/Equalization48x.cpp | 4 +- src/effects/Fade.cpp | 111 +- src/effects/Fade.h | 87 +- src/effects/FindClipping.cpp | 132 +- src/effects/FindClipping.h | 70 +- src/effects/Generator.h | 2 +- src/effects/Invert.cpp | 63 +- src/effects/Invert.h | 41 +- src/effects/Leveller.cpp | 371 +- src/effects/Leveller.h | 96 +- src/effects/LoadEffects.cpp | 459 ++- src/effects/LoadEffects.h | 55 +- src/effects/Noise.cpp | 362 +- src/effects/Noise.h | 121 +- src/effects/NoiseReduction.cpp | 57 +- src/effects/NoiseReduction.h | 21 +- src/effects/NoiseRemoval.cpp | 42 +- src/effects/NoiseRemoval.h | 40 +- src/effects/Normalize.cpp | 420 +- src/effects/Normalize.h | 96 +- src/effects/Paulstretch.cpp | 347 +- src/effects/Paulstretch.h | 106 +- src/effects/Phaser.cpp | 779 ++-- src/effects/Phaser.h | 236 +- src/effects/Repair.cpp | 33 +- src/effects/Repair.h | 35 +- src/effects/Repeat.cpp | 219 +- src/effects/Repeat.h | 87 +- src/effects/Reverb.cpp | 946 +++-- src/effects/Reverb.h | 162 +- src/effects/Reverse.cpp | 38 +- src/effects/Reverse.h | 43 +- src/effects/SBSMSEffect.h | 7 +- src/effects/ScienFilter.cpp | 2468 ++++++------ src/effects/ScienFilter.h | 315 +- src/effects/ScoreAlignDialog.cpp | 10 +- src/effects/Silence.cpp | 101 +- src/effects/Silence.h | 60 +- src/effects/SimpleMono.h | 14 +- src/effects/SoundTouchEffect.h | 14 +- src/effects/StereoToMono.cpp | 179 +- src/effects/StereoToMono.h | 51 +- src/effects/TimeScale.cpp | 813 ++-- src/effects/TimeScale.h | 130 +- src/effects/ToneGen.cpp | 617 +-- src/effects/ToneGen.h | 141 +- src/effects/TruncSilence.cpp | 565 +-- src/effects/TruncSilence.h | 97 +- src/effects/TwoPassSimpleMono.h | 18 +- src/effects/VST/VSTEffect.cpp | 304 +- src/effects/VST/VSTEffect.h | 71 +- src/effects/Wahwah.cpp | 635 ++- src/effects/Wahwah.h | 209 +- src/effects/audiounits/AudioUnitEffect.cpp | 310 +- src/effects/audiounits/AudioUnitEffect.h | 40 +- src/effects/ladspa/LadspaEffect.cpp | 352 +- src/effects/ladspa/LadspaEffect.h | 62 +- src/effects/lv2/LV2Effect.cpp | 2855 ++++++++------ src/effects/lv2/LV2Effect.h | 342 +- src/effects/lv2/LV2PortGroup.cpp | 61 - src/effects/lv2/LV2PortGroup.h | 51 - src/effects/lv2/LoadLV2.cpp | 303 +- src/effects/lv2/LoadLV2.h | 69 +- src/effects/lv2/lv2_event.h | 273 -- src/effects/lv2/lv2_event_helpers.h | 261 -- src/effects/lv2/lv2_uri_map.h | 88 - src/effects/nyquist/LoadNyquist.cpp | 100 +- src/effects/nyquist/Nyquist.cpp | 1535 ++++---- src/effects/nyquist/Nyquist.h | 202 +- src/effects/vamp/LoadVamp.cpp | 271 +- src/effects/vamp/LoadVamp.h | 11 + src/effects/vamp/VampEffect.cpp | 1157 +++--- src/effects/vamp/VampEffect.h | 136 +- src/prefs/DevicePrefs.cpp | 8 +- src/prefs/MidiIOPrefs.cpp | 8 +- src/prefs/SpectrumPrefs.cpp | 6 +- src/widgets/Meter.cpp | 7 +- src/widgets/numformatter.cpp | 9 + src/widgets/valnum.cpp | 984 ++--- src/widgets/valnum.h | 40 +- win/Projects/Audacity/Audacity.vcxproj | 12 +- .../Audacity/Audacity.vcxproj.filters | 12 - 140 files changed, 17288 insertions(+), 20367 deletions(-) delete mode 100644 src/effects/EffectCategory.cpp delete mode 100644 src/effects/EffectCategory.h delete mode 100644 src/effects/lv2/LV2PortGroup.cpp delete mode 100644 src/effects/lv2/LV2PortGroup.h delete mode 100644 src/effects/lv2/lv2_event.h delete mode 100644 src/effects/lv2/lv2_event_helpers.h delete mode 100644 src/effects/lv2/lv2_uri_map.h diff --git a/images/Effect.h b/images/Effect.h index 790cc33f1..5080f10b5 100644 --- a/images/Effect.h +++ b/images/Effect.h @@ -1,165 +1,3 @@ -/* XPM */ -static const char * effect_disable_xpm[] = { -"16 16 51 1", -" c None", -". c #F3D3D3", -"+ c #E19393", -"@ c #D25C5C", -"# c #C73636", -"$ c #E7A8A8", -"% c #CD4B4B", -"& c #C32727", -"* c #C63333", -"= c #CF5353", -"- c #F0CACA", -"; c #EFC6C6", -"> c #DA7A7A", -", c #C83939", -"' c #DB7E7E", -") c #F5DBDB", -"! c #F2CFCF", -"~ c #CF5454", -"{ c #C42B2B", -"] c #C42A2A", -"^ c #F9EBEB", -"/ c #D56767", -"( c #C83A3A", -"_ c #CB4343", -": c #FCF6F6", -"< c #DF8D8D", -"[ c #C52E2E", -"} c #C52F2F", -"| c #E08F8F", -"1 c #FEFBFB", -"2 c #E8AEAE", -"3 c #D76E6E", -"4 c #F9E9E9", -"5 c #F1CBCB", -"6 c #CB4545", -"7 c #C32828", -"8 c #CE5050", -"9 c #F3D5D5", -"0 c #E19292", -"a c #F0C9C9", -"b c #C32929", -"c c #C93D3D", -"d c #ECBCBC", -"e c #FEFCFC", -"f c #CC4646", -"g c #C63232", -"h c #E49E9E", -"i c #FEFAFA", -"j c #FBEFEF", -"k c #D36161", -"l c #EDBFBF", -" .+@####@+. ", -" $%&&&&&&&&%$ ", -" $*&&&&&&&&&&*$ ", -".%&&&=+-;>,&&&%.", -"+&&&') !~{]&&&+", -"@&&=) ^/]&(_&&@", -"#&&+ :<[&}|<&&#", -"#&&- 12#&]34-&&#", -"#&&- 567789 -&&#", -"#&&0a=b&cde +&&#", -"@&&f6b&ghi )=&&@", -"+&&&77* c #37A13C", -", c #61B564", -"' c #96CD99", -") c #6BBA6F", -"! c #3CA341", -"~ c #3AA23F", -"{ c #6EBB72", -"] c #D3EAD5", -"^ c #FAFCFA", -"/ c #C4E3C5", -"( c #4EAC52", -"_ c #38A13D", -": c #3DA442", -"< c #67B76A", -"[ c #D5EBD5", -"} c #FDFEFD", -"| c #FAFDFB", -"1 c #ADD8AE", -"2 c #42A647", -"3 c #3EA442", -"4 c #70BC74", -"5 c #A3D4A6", -"6 c #84C486", -"7 c #49A94D", -"8 c #53AE56", -"9 c #C1E2C2", -"0 c #FAFDFA", -"a c #FBFDFB", -"b c #CAE6CC", -"c c #5BB260", -"d c #4DAB51", -"e c #BCE0BE", -"f c #F9FCFA", -"g c #EFF7EF", -"h c #B1DAB2", -"i c #A9D7AB", -"j c #F5FAF6", -"k c #FCFEFC", -"l c #D5EBD6", -"m c #63B566", -"n c #85C688", -"o c #E9F5EA", -"p c #FFFFFF", -"q c #F8FCF8", -"r c #F4FAF4", -"s c #FEFFFE", -"t c #E9F4E9", -"u c #7DC280", -"v c #8CC98F", -"w c #E8F4E9", -"x c #F6FBF6", -"y c #A1D3A3", -"z c #81C384", -"A c #D6ECD7", -"B c #E2F2E3", -"C c #A4D4A6", -"D c #4AAA4F", -"E c #54AE58", -"F c #5EB463", -" .+@##@+. ", -" $%&******&%$ ", -" $=**********=$ ", -" %********-;-*% ", -".&*******>,')!&.", -"+*******~{]^/(*+", -"@**_:_*><[}|12*@", -"#*34567890abc-*#", -"#*defghijklm-**#", -"@*&nopqrstu_***@", -"+**2vwssxy:****+", -".&**3zABCD****&.", -" %***!EF2*****% ", -" $=****-*****=$ ", -" $%&******&%$ ", -" .+@##@+. "}; - /* XPM */ static const char * effect_menu_xpm[] = { "16 16 2 1", @@ -523,153 +361,6 @@ static const char * effect_ffwd_xpm[] = { " MN MN ", " "}; -/* XPM */ -static const char * effect_disable_disabled_xpm[] = { -"16 16 46 1", -" c None", -". c #E3E3E3", -"+ c #BABABA", -"@ c #979797", -"# c #7E7E7E", -"$ c #C7C7C7", -"% c #8C8C8C", -"& c #757575", -"* c #7C7C7C", -"= c #919191", -"- c #DDDDDD", -"; c #DADADA", -"> c #AAAAAA", -", c #808080", -"' c #ACACAC", -") c #E8E8E8", -"! c #E0E0E0", -"~ c #777777", -"{ c #F2F2F2", -"] c #9E9E9E", -"^ c #818181", -"/ c #878787", -"( c #F9F9F9", -"_ c #B6B6B6", -": c #797979", -"< c #7A7A7A", -"[ c #B7B7B7", -"} c #FCFCFC", -"| c #CBCBCB", -"1 c #A2A2A2", -"2 c #F1F1F1", -"3 c #DEDEDE", -"4 c #888888", -"5 c #8F8F8F", -"6 c #E4E4E4", -"7 c #B9B9B9", -"8 c #DCDCDC", -"9 c #767676", -"0 c #838383", -"a c #D4D4D4", -"b c #FDFDFD", -"c c #898989", -"d c #C1C1C1", -"e c #F5F5F5", -"f c #9A9A9A", -"g c #D6D6D6", -" .+@####@+. ", -" $%&&&&&&&&%$ ", -" $*&&&&&&&&&&*$ ", -".%&&&=+-;>,&&&%.", -"+&&&') !=~~&&&+", -"@&&=) {]~&^/&&@", -"#&&+ (_:&<[_&&#", -"#&&- }|#&~12-&&#", -"#&&- 34&&56 -&&#", -"#&&78=9&0ab +&&#", -"@&&c49&*d} )=&&@", -"+&&&&&*_e )'&&&+", -".%&&&:fg-+=&&&%.", -" $*&&&&&&&&&&*$ ", -" $%&&&&&&&&%$ ", -" .+@####@+. "}; - -/* XPM */ -static const char * effect_enable_disabled_xpm[] = { -"16 16 61 1", -" c None", -". c #D6D6D6", -"+ c #ACACAC", -"@ c #8B8B8B", -"# c #737373", -"$ c #ECECEC", -"% c #ADADAD", -"& c #717171", -"* c #6A6A6A", -"= c #A0A0A0", -"- c #6B6B6B", -"; c #6D6D6D", -"> c #6C6C6C", -", c #B1B1B1", -"' c #929292", -") c #6F6F6F", -"! c #6E6E6E", -"~ c #949494", -"{ c #DEDEDE", -"] c #FBFBFB", -"^ c #D3D3D3", -"/ c #7D7D7D", -"( c #707070", -"_ c #8F8F8F", -": c #E0E0E0", -"< c #FDFDFD", -"[ c #C2C2C2", -"} c #747474", -"| c #969696", -"1 c #BBBBBB", -"2 c #A4A4A4", -"3 c #797979", -"4 c #808080", -"5 c #D1D1D1", -"6 c #FCFCFC", -"7 c #D8D8D8", -"8 c #868686", -"9 c #7C7C7C", -"0 c #CECECE", -"a c #FAFAFA", -"b c #F3F3F3", -"c c #C5C5C5", -"d c #C0C0C0", -"e c #F7F7F7", -"f c #8C8C8C", -"g c #A5A5A5", -"h c #EFEFEF", -"i c #FFFFFF", -"j c #FEFEFE", -"k c #EEEEEE", -"l c #9F9F9F", -"m c #AAAAAA", -"n c #F8F8F8", -"o c #BABABA", -"p c #A2A2A2", -"q c #E1E1E1", -"r c #EAEAEA", -"s c #BCBCBC", -"t c #7A7A7A", -"u c #818181", -"v c #898989", -" .+@##@+. ", -" $%&******&%$ ", -" $=**********=$ ", -" %********-;-*% ", -".&*******>@,')&.", -"+*******!~{]^/*+", -"@**>(>*>_:<][}*@", -"#*&|12345]678-*#", -"#*90abcde<:f-**#", -"@*&ghiaejkl>***@", -"+**}mkjjno(****+", -".&**&pqrst****&.", -" %***)uv}*****% ", -" $=****-*****=$ ", -" $%&******&%$ ", -" .+@##@+. "}; - /* XPM */ static const char * effect_play_disabled_xpm[] = { "16 16 48 1", diff --git a/include/audacity/EffectAutomationParameters.h b/include/audacity/EffectAutomationParameters.h index 5f3c9bad7..bf6fbdd79 100644 --- a/include/audacity/EffectAutomationParameters.h +++ b/include/audacity/EffectAutomationParameters.h @@ -48,13 +48,14 @@ class EffectAutomationParameters : public wxFileConfig { public: - EffectAutomationParameters() + EffectAutomationParameters(const wxString & parms = wxEmptyString) : wxFileConfig(wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, 0) { + SetParameters(parms); } virtual ~EffectAutomationParameters() @@ -81,19 +82,40 @@ public: return wxFileConfig::DoWriteLong(NormalizeName(key), lValue); } + bool ReadFloat(const wxString & key, float *pf) const + { + double d = *pf; + bool success = Read(key, &d); + if (success) + { + *pf = (float) d; + } + return success; + } + + bool ReadFloat(const wxString & key, float *pf, float defVal) const + { + if (!ReadFloat(key, pf)) + { + *pf = defVal; + } + return true; + } + + bool WriteFloat(const wxString & key, float f) + { + return Write(key, f); + } + bool ReadEnum(const wxString & key, int *pi, const wxArrayString & choices) const { wxString s; - if (wxFileConfig::Read(key, &s)) + if (!wxFileConfig::Read(key, &s)) { - int i = choices.Index(s); - if (i != wxNOT_FOUND) - { - *pi = i; - return true; - } + return false; } - return false; + *pi = choices.Index(s); + return true; } bool ReadEnum(const wxString & key, int *pi, int defVal, const wxArrayString & choices) const @@ -105,6 +127,15 @@ public: return true; } + bool ReadEnum(const wxString & key, int *pi, const wxString & defVal, const wxArrayString & choices) const + { + if (!ReadEnum(key, pi, choices)) + { + *pi = choices.Index(defVal); + } + return true; + } + bool WriteEnum(const wxString & key, int value, const wxArrayString & choices) { if (value < 0 || value >= (int) choices.GetCount()) @@ -115,6 +146,54 @@ public: return wxFileConfig::Write(key, choices[value]); } + bool ReadAndVerify(const wxString & key, float *val, float defVal, float min, float max) const + { + ReadFloat(key, val, defVal); + return (*val >= min && *val <= max); + } + + bool ReadAndVerify(const wxString & key, double *val, double defVal, double min, double max) const + { + Read(key, val, defVal); + return (*val >= min && *val <= max); + } + + bool ReadAndVerify(const wxString & key, int *val, int defVal, int min, int max) const + { + Read(key, val, defVal); + return (*val >= min && *val <= max); + } + + bool ReadAndVerify(const wxString & key, long *val, long defVal, long min, long max) const + { + Read(key, val, defVal); + return (*val >= min && *val <= max); + } + + bool ReadAndVerify(const wxString & key, bool *val, bool defVal) const + { + Read(key, val, defVal); + return true; + } + + bool ReadAndVerify(const wxString & key, wxString *val, const wxString & defVal) const + { + Read(key, val, defVal); + return true; + } + + bool ReadAndVerify(const wxString & key, int *val, int defVal, const wxArrayString & choices) const + { + ReadEnum(key, val, defVal, choices); + return (*val != wxNOT_FOUND); + } + + bool ReadAndVerify(const wxString & key, int *val, const wxString & defVal, const wxArrayString & choices) const + { + ReadEnum(key, val, defVal, choices); + return (*val != wxNOT_FOUND); + } + wxString NormalizeName(const wxString & name) const { wxString cleaned = name; @@ -145,7 +224,7 @@ public: return false; } - str += key + wxT("=\"") + val + wxT("\" "); + str += key + wxT("=\"") + Escape(val) + wxT("\" "); res = wxFileConfig::GetNextEntry(key, ndx); } @@ -167,7 +246,7 @@ public: wxString key = parsed[i].BeforeFirst(wxT('=')).Trim(false).Trim(true); wxString val = parsed[i].AfterFirst(wxT('=')).Trim(false).Trim(true); - if (!wxFileConfig::Write(key, val)) + if (!wxFileConfig::Write(key, Unescape(val))) { return false; } @@ -175,6 +254,22 @@ public: return true; } + + wxString Escape(wxString val) + { + val.Replace(wxT("\\"), wxT("\\\\"), true); + val.Replace(wxT("\""), wxT("\\\""), true); + + return val; + } + + wxString Unescape(wxString val) + { + val.Replace(wxT("\\\""), wxT("\""), true); + val.Replace(wxT("\\\\"), wxT("\\"), true); + + return val; + } }; #endif diff --git a/include/audacity/EffectInterface.h b/include/audacity/EffectInterface.h index ea0db74f2..b859a966b 100644 --- a/include/audacity/EffectInterface.h +++ b/include/audacity/EffectInterface.h @@ -87,12 +87,12 @@ public: class EffectUIHostInterface; class EffectUIClientInterface; -class AUDACITY_DLL_API EffectHostInterface : public EffectIdentInterface, - public ConfigClientInterface +class AUDACITY_DLL_API EffectHostInterface : public ConfigClientInterface { public: virtual ~EffectHostInterface() {}; + virtual double GetDefaultDuration() = 0; virtual double GetDuration() = 0; virtual bool SetDuration(double seconds) = 0; @@ -108,7 +108,7 @@ public: virtual wxString GetFactoryDefaultsGroup() = 0; }; -class EffectClientInterface : public EffectIdentInterface +class AUDACITY_DLL_API EffectClientInterface : public EffectIdentInterface { public: virtual ~EffectClientInterface() {}; @@ -122,15 +122,15 @@ public: virtual int GetMidiOutCount() = 0; virtual void SetSampleRate(sampleCount rate) = 0; - virtual sampleCount GetBlockSize(sampleCount maxBlockSize) = 0; + virtual sampleCount SetBlockSize(sampleCount maxBlockSize) = 0; virtual sampleCount GetLatency() = 0; virtual sampleCount GetTailSize() = 0; virtual bool IsReady() = 0; - virtual bool ProcessInitialize() = 0; + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL) = 0; virtual bool ProcessFinalize() = 0; - virtual sampleCount ProcessBlock(float **inbuf, float **outbuf, sampleCount size) = 0; + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) = 0; virtual bool RealtimeInitialize() = 0; virtual bool RealtimeAddProcessor(int numChannels, float sampleRate) = 0; @@ -138,41 +138,41 @@ public: virtual bool RealtimeSuspend() = 0; virtual bool RealtimeResume() = 0; virtual bool RealtimeProcessStart() = 0; - virtual sampleCount RealtimeProcess(int group, float **inbuf, float **outbuf, sampleCount numSamples) = 0; + virtual sampleCount RealtimeProcess(int group, float **inBuf, float **outBuf, sampleCount numSamples) = 0; virtual bool RealtimeProcessEnd() = 0; virtual bool ShowInterface(wxWindow *parent, bool forceModal = false) = 0; virtual bool GetAutomationParameters(EffectAutomationParameters & parms) = 0; virtual bool SetAutomationParameters(EffectAutomationParameters & parms) = 0; + + virtual bool LoadUserPreset(const wxString & name) = 0; + virtual bool SaveUserPreset(const wxString & name) = 0; + + virtual wxArrayString GetFactoryPresets() = 0; + virtual bool LoadFactoryPreset(int id) = 0; + virtual bool LoadFactoryDefaults() = 0; }; -class EffectUIHostInterface +class AUDACITY_DLL_API EffectUIHostInterface { public: virtual ~EffectUIHostInterface() {}; }; -class EffectUIClientInterface +class AUDACITY_DLL_API EffectUIClientInterface { public: virtual ~EffectUIClientInterface() {}; - virtual void SetUIHost(EffectUIHostInterface *host) = 0; - virtual bool PopulateUI(wxWindow *parent) = 0; + virtual void SetHostUI(EffectUIHostInterface *host) = 0; virtual bool IsGraphicalUI() = 0; + virtual bool PopulateUI(wxWindow *parent) = 0; virtual bool ValidateUI() = 0; virtual bool HideUI() = 0; virtual bool CloseUI() = 0; - virtual void LoadUserPreset(const wxString & name) = 0; - virtual void SaveUserPreset(const wxString & name) = 0; - - virtual wxArrayString GetFactoryPresets() = 0; - virtual void LoadFactoryPreset(int id) = 0; - virtual void LoadFactoryDefaults() = 0; - - virtual bool CanExport() = 0; + virtual bool CanExportPresets() = 0; virtual void ExportPresets() = 0; virtual void ImportPresets() = 0; @@ -180,7 +180,7 @@ public: virtual void ShowOptions() = 0; }; -class EffectManagerInterface +class AUDACITY_DLL_API EffectManagerInterface { public: virtual ~EffectManagerInterface() {}; diff --git a/include/audacity/PluginInterface.h b/include/audacity/PluginInterface.h index 2101024f4..1636379c0 100644 --- a/include/audacity/PluginInterface.h +++ b/include/audacity/PluginInterface.h @@ -56,9 +56,9 @@ class PluginManagerInterface public: virtual ~PluginManagerInterface() {}; - virtual const PluginID & RegisterModulePlugin(ModuleInterface *module) = 0; - virtual const PluginID & RegisterEffectPlugin(ModuleInterface *provider, EffectIdentInterface *effect) = 0; - virtual const PluginID & RegisterImporterPlugin(ModuleInterface *provider, ImporterInterface *importer) = 0; + virtual const PluginID & RegisterPlugin(ModuleInterface *module) = 0; + virtual const PluginID & RegisterPlugin(ModuleInterface *provider, EffectIdentInterface *effect) = 0; + virtual const PluginID & RegisterPlugin(ModuleInterface *provider, ImporterInterface *importer) = 0; virtual void FindFilesInPathList(const wxString & pattern, const wxArrayString & pathList, diff --git a/include/audacity/Types.h b/include/audacity/Types.h index 48fe68c3e..32f82b120 100644 --- a/include/audacity/Types.h +++ b/include/audacity/Types.h @@ -120,7 +120,7 @@ typedef enum ChannelNameBottomFrontCenter, ChannelNameBottomFrontLeft, ChannelNameBottomFrontRight, -} ChannelName; +} ChannelName, *ChannelNames; // LLL FIXME: Until a complete API is devised, we have to use // AUDACITY_DLL_API when defining API classes. This diff --git a/lib-src/mod-nyq-bench/NyqBench.cpp b/lib-src/mod-nyq-bench/NyqBench.cpp index 60bb50fe0..4ddb3f466 100644 --- a/lib-src/mod-nyq-bench/NyqBench.cpp +++ b/lib-src/mod-nyq-bench/NyqBench.cpp @@ -690,8 +690,8 @@ NyqBench::NyqBench(wxWindow * parent) mOutput = NULL; // No need to delete...EffectManager will do it - mEffect = new EffectNyquist(wxT("===nyquistworker===")); - EffectManager::Get().RegisterEffect(mEffect, HIDDEN_EFFECT); + mEffect = new NyquistEffect(wxT("===nyquistworker===")); + EffectManager::Get().RegisterEffect(mEffect); mPath = gPrefs->Read(wxT("NyqBench/Path"), wxEmptyString); mAutoLoad = (gPrefs->Read(wxT("NyqBench/AutoLoad"), 0L) != 0); @@ -1366,7 +1366,7 @@ void NyqBench::OnGo(wxCommandEvent & e) UpdateWindowUI(); const PluginID & id = EffectManager::Get().GetEffectByIdentifier(mEffect->GetSymbol()); - p->OnEffect(ALL_EFFECTS, id); + p->OnEffect(id); mRunning = false; UpdateWindowUI(); diff --git a/lib-src/mod-nyq-bench/NyqBench.h b/lib-src/mod-nyq-bench/NyqBench.h index 2533991c9..003732993 100644 --- a/lib-src/mod-nyq-bench/NyqBench.h +++ b/lib-src/mod-nyq-bench/NyqBench.h @@ -191,7 +191,7 @@ class NyqBench:public wxFrame wxFindReplaceData mFindData; NyqTextCtrl *mFindText; - EffectNyquist *mEffect; + NyquistEffect *mEffect; wxFont mScriptFont; wxFont mOutputFont; diff --git a/locale/POTFILES.in b/locale/POTFILES.in index babcc02df..8f241e849 100644 --- a/locale/POTFILES.in +++ b/locale/POTFILES.in @@ -273,8 +273,6 @@ src/effects/Echo.cpp src/effects/Echo.h src/effects/Effect.cpp src/effects/Effect.h -src/effects/EffectCategory.cpp -src/effects/EffectCategory.h src/effects/EffectManager.cpp src/effects/EffectManager.h src/effects/EffectRack.cpp @@ -352,13 +350,8 @@ src/effects/ladspa/LadspaEffect.h src/effects/ladspa/ladspa.h src/effects/lv2/LV2Effect.cpp src/effects/lv2/LV2Effect.h -src/effects/lv2/LV2PortGroup.cpp -src/effects/lv2/LV2PortGroup.h src/effects/lv2/LoadLV2.cpp src/effects/lv2/LoadLV2.h -src/effects/lv2/lv2_event.h -src/effects/lv2/lv2_event_helpers.h -src/effects/lv2/lv2_uri_map.h src/effects/nyquist/LoadNyquist.cpp src/effects/nyquist/LoadNyquist.h src/effects/nyquist/Nyquist.cpp diff --git a/mac/Audacity.xcodeproj/project.pbxproj b/mac/Audacity.xcodeproj/project.pbxproj index fc6a3414d..47a126f82 100644 --- a/mac/Audacity.xcodeproj/project.pbxproj +++ b/mac/Audacity.xcodeproj/project.pbxproj @@ -1182,7 +1182,6 @@ 28ED7B7D1A1C77BF008A01D9 /* SpectralEditParametricEQ.ny in Resources */ = {isa = PBXBuildFile; fileRef = 28ED7B771A1C77BF008A01D9 /* SpectralEditParametricEQ.ny */; }; 28ED7B7E1A1C77BF008A01D9 /* SpectralEditShelves.ny in Resources */ = {isa = PBXBuildFile; fileRef = 28ED7B781A1C77BF008A01D9 /* SpectralEditShelves.ny */; }; 28ED7B7F1A1C77BF008A01D9 /* StudioFadeOut.ny in Resources */ = {isa = PBXBuildFile; fileRef = 28ED7B791A1C77BF008A01D9 /* StudioFadeOut.ny */; }; - 28EDC8DF0E5550750034BBE7 /* LV2PortGroup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28EDC8DD0E5550750034BBE7 /* LV2PortGroup.cpp */; }; 28F00A930A3E2FF100A3E5F5 /* FileNames.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28F00A900A3E2FF100A3E5F5 /* FileNames.cpp */; }; 28F1D81D0A2D0019005506A7 /* AttachableScrollBar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28F1D8170A2D0018005506A7 /* AttachableScrollBar.cpp */; }; 28F1D81E0A2D0019005506A7 /* ExpandingToolBar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28F1D8190A2D0018005506A7 /* ExpandingToolBar.cpp */; }; @@ -1258,7 +1257,6 @@ ED2707460EF9C550007D4FFD /* libsbsms.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ED2706F40EF9C3C6007D4FFD /* libsbsms.a */; }; ED2707500EF9C64F007D4FFD /* SBSMSEffect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED27074B0EF9C64F007D4FFD /* SBSMSEffect.cpp */; }; ED2707510EF9C64F007D4FFD /* TimeScale.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED27074D0EF9C64F007D4FFD /* TimeScale.cpp */; }; - ED3D7FF00DF73889000F43E3 /* EffectCategory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED3D7FEC0DF73889000F43E3 /* EffectCategory.cpp */; }; ED3D7FF10DF73889000F43E3 /* EffectManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED3D7FEE0DF73889000F43E3 /* EffectManager.cpp */; }; ED64C823124567ED007CF2FC /* ScoreAlignDialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED64C821124567ED007CF2FC /* ScoreAlignDialog.cpp */; }; ED85B3DA16A46FC9006DA21D /* hr.po in Sources */ = {isa = PBXBuildFile; fileRef = ED85B3CF16A46DDA006DA21D /* hr.po */; }; @@ -2931,11 +2929,6 @@ 28ED7B771A1C77BF008A01D9 /* SpectralEditParametricEQ.ny */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = SpectralEditParametricEQ.ny; path = "../plug-ins/SpectralEditParametricEQ.ny"; sourceTree = SOURCE_ROOT; }; 28ED7B781A1C77BF008A01D9 /* SpectralEditShelves.ny */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = SpectralEditShelves.ny; path = "../plug-ins/SpectralEditShelves.ny"; sourceTree = SOURCE_ROOT; }; 28ED7B791A1C77BF008A01D9 /* StudioFadeOut.ny */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = StudioFadeOut.ny; path = "../plug-ins/StudioFadeOut.ny"; sourceTree = SOURCE_ROOT; }; - 28EDC8DA0E5550750034BBE7 /* lv2_event.h */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.c.h; name = lv2_event.h; path = lv2/lv2_event.h; sourceTree = ""; tabWidth = 3; }; - 28EDC8DB0E5550750034BBE7 /* lv2_event_helpers.h */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.c.h; name = lv2_event_helpers.h; path = lv2/lv2_event_helpers.h; sourceTree = ""; tabWidth = 3; }; - 28EDC8DC0E5550750034BBE7 /* lv2_uri_map.h */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.c.h; name = lv2_uri_map.h; path = lv2/lv2_uri_map.h; sourceTree = ""; tabWidth = 3; }; - 28EDC8DD0E5550750034BBE7 /* LV2PortGroup.cpp */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; name = LV2PortGroup.cpp; path = lv2/LV2PortGroup.cpp; sourceTree = ""; tabWidth = 3; }; - 28EDC8DE0E5550750034BBE7 /* LV2PortGroup.h */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.c.h; name = LV2PortGroup.h; path = lv2/LV2PortGroup.h; sourceTree = ""; tabWidth = 3; }; 28F00A900A3E2FF100A3E5F5 /* FileNames.cpp */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = FileNames.cpp; sourceTree = ""; tabWidth = 3; }; 28F00A910A3E2FF100A3E5F5 /* FileNames.h */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = FileNames.h; sourceTree = ""; tabWidth = 3; }; 28F00A920A3E2FF100A3E5F5 /* ThemeAsCeeCode.h */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = ThemeAsCeeCode.h; sourceTree = ""; tabWidth = 3; }; @@ -3065,8 +3058,6 @@ ED27074C0EF9C64F007D4FFD /* SBSMSEffect.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = SBSMSEffect.h; sourceTree = ""; tabWidth = 3; }; ED27074D0EF9C64F007D4FFD /* TimeScale.cpp */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = TimeScale.cpp; sourceTree = ""; tabWidth = 3; }; ED27074E0EF9C64F007D4FFD /* TimeScale.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = TimeScale.h; sourceTree = ""; tabWidth = 3; }; - ED3D7FEC0DF73889000F43E3 /* EffectCategory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = EffectCategory.cpp; sourceTree = ""; tabWidth = 3; }; - ED3D7FED0DF73889000F43E3 /* EffectCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = EffectCategory.h; sourceTree = ""; tabWidth = 3; }; ED3D7FEE0DF73889000F43E3 /* EffectManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = EffectManager.cpp; sourceTree = ""; tabWidth = 3; }; ED3D7FEF0DF73889000F43E3 /* EffectManager.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = EffectManager.h; sourceTree = ""; tabWidth = 3; }; ED64C821124567ED007CF2FC /* ScoreAlignDialog.cpp */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = ScoreAlignDialog.cpp; sourceTree = ""; tabWidth = 3; }; @@ -4076,8 +4067,6 @@ 1790B01809883BFD008A330A /* Echo.h */, 1790B01909883BFD008A330A /* Effect.cpp */, 1790B01A09883BFD008A330A /* Effect.h */, - ED3D7FEC0DF73889000F43E3 /* EffectCategory.cpp */, - ED3D7FED0DF73889000F43E3 /* EffectCategory.h */, ED3D7FEE0DF73889000F43E3 /* EffectManager.cpp */, ED3D7FEF0DF73889000F43E3 /* EffectManager.h */, 280A8B4819F440880091DE70 /* EffectRack.cpp */, @@ -5357,11 +5346,6 @@ 28D587C80E264CF3009C7DEA /* LoadLV2.h */, 28D587C90E264CF4009C7DEA /* LV2Effect.cpp */, 28D587CA0E264CF4009C7DEA /* LV2Effect.h */, - 28EDC8DD0E5550750034BBE7 /* LV2PortGroup.cpp */, - 28EDC8DE0E5550750034BBE7 /* LV2PortGroup.h */, - 28EDC8DA0E5550750034BBE7 /* lv2_event.h */, - 28EDC8DB0E5550750034BBE7 /* lv2_event_helpers.h */, - 28EDC8DC0E5550750034BBE7 /* lv2_uri_map.h */, ); name = lv2; sourceTree = ""; @@ -7512,7 +7496,6 @@ 288052C20DEA73F500671EA4 /* NonGuiThread.cpp in Sources */, 28530C4C0DF2105200555C94 /* HtmlWindow.cpp in Sources */, 28530C4D0DF2105200555C94 /* ProgressDialog.cpp in Sources */, - ED3D7FF00DF73889000F43E3 /* EffectCategory.cpp in Sources */, ED3D7FF10DF73889000F43E3 /* EffectManager.cpp in Sources */, 283135EC0DFB9D110076D551 /* ImportFFmpeg.cpp in Sources */, 283135FF0DFBA2E80076D551 /* FFmpeg.cpp in Sources */, @@ -7532,7 +7515,6 @@ 186CCE720E51F48500659159 /* ODDecodeFlacTask.cpp in Sources */, 186CCE730E51F48500659159 /* ODDecodeTask.cpp in Sources */, 186CCEA40E523C8E00659159 /* Profiler.cpp in Sources */, - 28EDC8DF0E5550750034BBE7 /* LV2PortGroup.cpp in Sources */, 18D8314E0ED0F56300FD870D /* Contrast.cpp in Sources */, ED2707500EF9C64F007D4FFD /* SBSMSEffect.cpp in Sources */, ED2707510EF9C64F007D4FFD /* TimeScale.cpp in Sources */, @@ -8163,7 +8145,7 @@ ); PRODUCT_NAME = Audacity; SKIP_INSTALL = NO; - USER_HEADER_SEARCH_PATHS = "$(TOPLEVEL)/include $(TOPLEVEL)/mac $(TOPLEVEL)/src/** $(TOPLEVEL)/lib-src/FileDialog $(TOPLEVEL)/lib-src/libflac/include $(TOPLEVEL)/lib-src/libid3tag $(TOPLEVEL)/lib-src/lame $(TOPLEVEL)/lib-src/libmad $(TOPLEVEL)/lib-src/libogg/include $(TOPLEVEL)/lib-src/libnyquist/nyq $(TOPLEVEL)/lib-src/libresample/include $(TOPLEVEL)/lib-src/libsndfile/src $(TOPLEVEL)/lib-src/libsoxr/src $(TOPLEVEL)/lib-src/libvamp $(TOPLEVEL)/lib-src/libvorbis/include $(TOPLEVEL)/lib-src/portaudio-v19/include $(TOPLEVEL)/lib-src/portmixer/px_common $(TOPLEVEL)/lib-src/portsmf $(TOPLEVEL)/lib-src/soundtouch/include $(TOPLEVEL)/lib-src/lv2/include $(TOPLEVEL)/lib-src/twolame/libtwolame"; + USER_HEADER_SEARCH_PATHS = "$(TOPLEVEL)/include $(TOPLEVEL)/mac $(TOPLEVEL)/src/** $(TOPLEVEL)/lib-src/FileDialog $(TOPLEVEL)/lib-src/libflac/include $(TOPLEVEL)/lib-src/libid3tag $(TOPLEVEL)/lib-src/lame $(TOPLEVEL)/lib-src/libmad $(TOPLEVEL)/lib-src/libogg/include $(TOPLEVEL)/lib-src/libnyquist/nyq $(TOPLEVEL)/lib-src/libsndfile/src $(TOPLEVEL)/lib-src/libsoxr/src $(TOPLEVEL)/lib-src/libvamp $(TOPLEVEL)/lib-src/libvorbis/include $(TOPLEVEL)/lib-src/portaudio-v19/include $(TOPLEVEL)/lib-src/portmixer/px_common $(TOPLEVEL)/lib-src/portsmf $(TOPLEVEL)/lib-src/soundtouch/include $(TOPLEVEL)/lib-src/lv2/include $(TOPLEVEL)/lib-src/twolame/libtwolame"; }; name = "Debug Static"; }; @@ -8524,7 +8506,7 @@ ); PRODUCT_NAME = Audacity; SKIP_INSTALL = NO; - USER_HEADER_SEARCH_PATHS = "$(TOPLEVEL)/include $(TOPLEVEL)/mac $(TOPLEVEL)/src/** $(TOPLEVEL)/lib-src/FileDialog $(TOPLEVEL)/lib-src/libflac/include $(TOPLEVEL)/lib-src/libid3tag $(TOPLEVEL)/lib-src/lame $(TOPLEVEL)/lib-src/libmad $(TOPLEVEL)/lib-src/libogg/include $(TOPLEVEL)/lib-src/libnyquist/nyq $(TOPLEVEL)/lib-src/libresample/include $(TOPLEVEL)/lib-src/libsndfile/src $(TOPLEVEL)/lib-src/libsoxr/src $(TOPLEVEL)/lib-src/libvamp $(TOPLEVEL)/lib-src/libvorbis/include $(TOPLEVEL)/lib-src/portaudio-v19/include $(TOPLEVEL)/lib-src/portmixer/px_common $(TOPLEVEL)/lib-src/portsmf $(TOPLEVEL)/lib-src/soundtouch/include $(TOPLEVEL)/lib-src/lv2/include $(TOPLEVEL)/lib-src/twolame/libtwolame"; + USER_HEADER_SEARCH_PATHS = "$(TOPLEVEL)/include $(TOPLEVEL)/mac $(TOPLEVEL)/src/** $(TOPLEVEL)/lib-src/FileDialog $(TOPLEVEL)/lib-src/libflac/include $(TOPLEVEL)/lib-src/libid3tag $(TOPLEVEL)/lib-src/lame $(TOPLEVEL)/lib-src/libmad $(TOPLEVEL)/lib-src/libogg/include $(TOPLEVEL)/lib-src/libnyquist/nyq $(TOPLEVEL)/lib-src/libsndfile/src $(TOPLEVEL)/lib-src/libsoxr/src $(TOPLEVEL)/lib-src/libvamp $(TOPLEVEL)/lib-src/libvorbis/include $(TOPLEVEL)/lib-src/portaudio-v19/include $(TOPLEVEL)/lib-src/portmixer/px_common $(TOPLEVEL)/lib-src/portsmf $(TOPLEVEL)/lib-src/soundtouch/include $(TOPLEVEL)/lib-src/lv2/include $(TOPLEVEL)/lib-src/twolame/libtwolame"; }; name = "Debug Shared"; }; @@ -8835,7 +8817,7 @@ ); PRODUCT_NAME = Audacity; SKIP_INSTALL = NO; - USER_HEADER_SEARCH_PATHS = "$(TOPLEVEL)/include $(TOPLEVEL)/mac $(TOPLEVEL)/src/** $(TOPLEVEL)/lib-src/FileDialog $(TOPLEVEL)/lib-src/libflac/include $(TOPLEVEL)/lib-src/libid3tag $(TOPLEVEL)/lib-src/lame $(TOPLEVEL)/lib-src/libmad $(TOPLEVEL)/lib-src/libogg/include $(TOPLEVEL)/lib-src/libnyquist/nyq $(TOPLEVEL)/lib-src/libresample/include $(TOPLEVEL)/lib-src/libsndfile/src $(TOPLEVEL)/lib-src/libsoxr/src $(TOPLEVEL)/lib-src/libvamp $(TOPLEVEL)/lib-src/libvorbis/include $(TOPLEVEL)/lib-src/portaudio-v19/include $(TOPLEVEL)/lib-src/portmixer/px_common $(TOPLEVEL)/lib-src/portsmf $(TOPLEVEL)/lib-src/soundtouch/include $(TOPLEVEL)/lib-src/lv2/include $(TOPLEVEL)/lib-src/twolame/libtwolame"; + USER_HEADER_SEARCH_PATHS = "$(TOPLEVEL)/include $(TOPLEVEL)/mac $(TOPLEVEL)/src/** $(TOPLEVEL)/lib-src/FileDialog $(TOPLEVEL)/lib-src/libflac/include $(TOPLEVEL)/lib-src/libid3tag $(TOPLEVEL)/lib-src/lame $(TOPLEVEL)/lib-src/libmad $(TOPLEVEL)/lib-src/libogg/include $(TOPLEVEL)/lib-src/libnyquist/nyq $(TOPLEVEL)/lib-src/libsndfile/src $(TOPLEVEL)/lib-src/libsoxr/src $(TOPLEVEL)/lib-src/libvamp $(TOPLEVEL)/lib-src/libvorbis/include $(TOPLEVEL)/lib-src/portaudio-v19/include $(TOPLEVEL)/lib-src/portmixer/px_common $(TOPLEVEL)/lib-src/portsmf $(TOPLEVEL)/lib-src/soundtouch/include $(TOPLEVEL)/lib-src/lv2/include $(TOPLEVEL)/lib-src/twolame/libtwolame"; }; name = "Release Shared"; }; @@ -9329,7 +9311,7 @@ ); PRODUCT_NAME = Audacity; SKIP_INSTALL = NO; - USER_HEADER_SEARCH_PATHS = "$(TOPLEVEL)/include $(TOPLEVEL)/mac $(TOPLEVEL)/src/** $(TOPLEVEL)/lib-src/FileDialog $(TOPLEVEL)/lib-src/libflac/include $(TOPLEVEL)/lib-src/libid3tag $(TOPLEVEL)/lib-src/lame $(TOPLEVEL)/lib-src/libmad $(TOPLEVEL)/lib-src/libogg/include $(TOPLEVEL)/lib-src/libnyquist/nyq $(TOPLEVEL)/lib-src/libresample/include $(TOPLEVEL)/lib-src/libsndfile/src $(TOPLEVEL)/lib-src/libsoxr/src $(TOPLEVEL)/lib-src/libvamp $(TOPLEVEL)/lib-src/libvorbis/include $(TOPLEVEL)/lib-src/portaudio-v19/include $(TOPLEVEL)/lib-src/portmixer/px_common $(TOPLEVEL)/lib-src/portsmf $(TOPLEVEL)/lib-src/soundtouch/include $(TOPLEVEL)/lib-src/lv2/include $(TOPLEVEL)/lib-src/twolame/libtwolame"; + USER_HEADER_SEARCH_PATHS = "$(TOPLEVEL)/include $(TOPLEVEL)/mac $(TOPLEVEL)/src/** $(TOPLEVEL)/lib-src/FileDialog $(TOPLEVEL)/lib-src/libflac/include $(TOPLEVEL)/lib-src/libid3tag $(TOPLEVEL)/lib-src/lame $(TOPLEVEL)/lib-src/libmad $(TOPLEVEL)/lib-src/libogg/include $(TOPLEVEL)/lib-src/libnyquist/nyq $(TOPLEVEL)/lib-src/libsndfile/src $(TOPLEVEL)/lib-src/libsoxr/src $(TOPLEVEL)/lib-src/libvamp $(TOPLEVEL)/lib-src/libvorbis/include $(TOPLEVEL)/lib-src/portaudio-v19/include $(TOPLEVEL)/lib-src/portmixer/px_common $(TOPLEVEL)/lib-src/portsmf $(TOPLEVEL)/lib-src/soundtouch/include $(TOPLEVEL)/lib-src/lv2/include $(TOPLEVEL)/lib-src/twolame/libtwolame"; }; name = "Release Static"; }; diff --git a/src/Audacity.h b/src/Audacity.h index 1e7010746..c0924ea3b 100644 --- a/src/Audacity.h +++ b/src/Audacity.h @@ -152,22 +152,6 @@ void QuitAudacity(); #endif #endif -// For compilers that support precompilation, includes "wx/wx.h". -// Mainly for MSVC developers. -// -// This precompilation is only done for non-unicode debug builds. -// The rationale is that this is where there is the big time saving -// because that's what you build whilst debugging. -// Whilst disabling precompilation for other builds will ensure -// that missing headers that would affect other platforms do get -// seen by MSVC developers too. - -#ifndef UNICODE -#ifdef __WXDEBUG__ -//#include -#endif -#endif - // This macro is used widely, so declared here. #define QUANTIZED_TIME(time, rate) ((double)((sampleCount)floor(((double)(time) * (rate)) + 0.5))) / (rate) diff --git a/src/AudacityApp.cpp b/src/AudacityApp.cpp index 52f63e27b..fe88e468c 100644 --- a/src/AudacityApp.cpp +++ b/src/AudacityApp.cpp @@ -67,7 +67,6 @@ It handles initialization and termination by subclassing wxApp. #include "DirManager.h" #include "commands/CommandHandler.h" #include "commands/AppCommandEvent.h" -#include "effects/LoadEffects.h" #include "effects/Contrast.h" #include "widgets/ASlider.h" #include "FFmpeg.h" @@ -1320,8 +1319,6 @@ Click the 'Help' button for known issue."), InitDitherers(); InitAudioIO(); - LoadEffects(); - #ifdef __WXMAC__ // On the Mac, users don't expect a program to quit when you close the last window. @@ -1926,8 +1923,6 @@ int AudacityApp::OnExit() DropFFmpegLibs(); #endif - UnloadEffects(); - DeinitFFT(); BlockFile::Deinit(); diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index e9ed18079..2f37eb14a 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -1383,7 +1383,6 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, } } while(!bDone); -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) if (mNumPlaybackChannels > 0) { EffectManager & em = EffectManager::Get(); @@ -1407,7 +1406,6 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, em.RealtimeAddProcessor(group++, chanCnt, vt->GetRate()); } } -#endif #ifdef AUTOMATED_INPUT_LEVEL_ADJUSTMENT AILASetStartTime(); @@ -1500,12 +1498,10 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, void AudioIO::StartStreamCleanup(bool bOnlyBuffers) { -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) if (mNumPlaybackChannels > 0) { EffectManager::Get().RealtimeFinalize(); } -#endif if(mPlaybackBuffers) { @@ -1709,13 +1705,11 @@ void AudioIO::StopStream() wxMutexLocker locker(mSuspendAudioThread); -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) // No longer need effects processing if (mNumPlaybackChannels > 0) { EffectManager::Get().RealtimeFinalize(); } -#endif // // We got here in one of two ways: @@ -1964,7 +1958,6 @@ void AudioIO::StopStream() void AudioIO::SetPaused(bool state) { -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) if (state != mPaused) { if (state) @@ -1976,7 +1969,6 @@ void AudioIO::SetPaused(bool state) EffectManager::Get().RealtimeResume(); } } -#endif mPaused = state; } @@ -3632,10 +3624,8 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, tempBufs[c] = (float *) alloca(framesPerBuffer * sizeof(float)); } -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) EffectManager & em = EffectManager::Get(); em.RealtimeProcessStart(); -#endif bool selected = false; int group = 0; @@ -3718,13 +3708,12 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, } #endif -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) if( !cut && selected ) { len = em.RealtimeProcess(group, chanCnt, tempBufs, len); } group++; -#endif + // If our buffer is empty and the time indicator is past // the end, then we've actually finished playing the entire // selection. @@ -3783,9 +3772,7 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, chanCnt = 0; } -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) em.RealtimeProcessEnd(); -#endif gAudioIO->mLastPlaybackTimeMillis = ::wxGetLocalTimeMillis(); diff --git a/src/BatchCommands.cpp b/src/BatchCommands.cpp index cef8af383..bc95f6f73 100644 --- a/src/BatchCommands.cpp +++ b/src/BatchCommands.cpp @@ -57,7 +57,6 @@ static wxString SpecialCommands[] = { // wxT("Import"), // non-functioning wxT("ExportMP3_56k_before"), wxT("ExportMP3_56k_after"), - wxT("StereoToMono"), wxT("ExportFLAC"), wxT("ExportMP3"), wxT("ExportOgg"), @@ -381,6 +380,53 @@ bool BatchCommands::IsMono() return mono; } +wxString BatchCommands::BuildCleanFileName(wxString fileName, wxString extension) +{ + wxFileName newFileName(fileName); + wxString justName = newFileName.GetName(); + wxString pathName = newFileName.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR); + + if (justName == wxT("")) { + wxDateTime now = wxDateTime::Now(); + int year = now.GetYear(); + wxDateTime::Month month = now.GetMonth(); + wxString monthName = now.GetMonthName(month); + int dom = now.GetDay(); + int hour = now.GetHour(); + int minute = now.GetMinute(); + int second = now.GetSecond(); + justName = wxString::Format(wxT("%d-%s-%02d-%02d-%02d-%02d"), + year, monthName.c_str(), dom, hour, minute, second); + +// SetName(cleanedFileName); +// bool isStereo; +// double endTime = project->mTracks->GetEndTime(); +// double startTime = 0.0; + //OnSelectAll(); + pathName = gPrefs->Read(wxT("/DefaultOpenPath"), ::wxGetCwd()); + ::wxMessageBox(wxString::Format(wxT("Export recording to %s\n/cleaned/%s%s"), + pathName.c_str(), justName.c_str(), extension.c_str()), + wxT("Export recording"), + wxOK | wxCENTRE); + pathName += wxT("/"); + } + wxString cleanedName = pathName; + cleanedName += wxT("cleaned"); + bool flag = ::wxFileName::FileExists(cleanedName); + if (flag == true) { + ::wxMessageBox(wxT("Cannot create directory 'cleaned'. \nFile already exists that is not a directory")); + return wxT(""); + } + ::wxFileName::Mkdir(cleanedName, 0777, wxPATH_MKDIR_FULL); // make sure it exists + + cleanedName += wxT("/"); + cleanedName += justName; + cleanedName += extension; + wxGetApp().AddFileToHistory(cleanedName); + + return cleanedName; +} + bool BatchCommands::WriteMp3File( const wxString Name, int bitrate ) { //check if current project is mono or stereo int numChannels = 2; @@ -450,10 +496,10 @@ bool BatchCommands::ApplySpecialCommand(int WXUNUSED(iCommand), const wxString c else extension = wxT(".mp3"); if (mFileName.IsEmpty()) { - filename = project->BuildCleanFileName(project->GetFileName(), extension); + filename = BuildCleanFileName(project->GetFileName(), extension); } else { - filename = project->BuildCleanFileName(mFileName, extension); + filename = BuildCleanFileName(mFileName, extension); } // We have a command index, but we don't use it! @@ -470,14 +516,6 @@ bool BatchCommands::ApplySpecialCommand(int WXUNUSED(iCommand), const wxString c } else if (command == wxT("ExportMP3_56k_after")) { filename.Replace(wxT("cleaned/"), wxT("cleaned/MasterAfter_"), false); return WriteMp3File(filename, 56); - } else if (command == wxT("StereoToMono")) { - // StereoToMono is an effect masquerading as a menu item. - const PluginID & ID = EffectManager::Get().GetEffectByIdentifier(wxT("StereoToMono")); - if (!ID.empty()) { - return ApplyEffectCommand(ID, command, params); - } - wxMessageBox(_("Stereo to Mono Effect not found")); - return false; } else if (command == wxT("ExportMP3")) { return WriteMp3File(filename, 0); // 0 bitrate means use default/current } else if (command == wxT("ExportWAV")) { @@ -551,8 +589,12 @@ bool BatchCommands::ApplyEffectCommand(const PluginID & ID, const wxString comma // (most effects require that you have something selected). project->SelectAllIfNone(); + if (!SetCurrentParametersFor(command, params)) + return false; + // NOW actually apply the effect. - return project->OnEffect(ALL_EFFECTS | CONFIGURED_EFFECT , ID, params, false); + return project->OnEffect(ID, AudacityProject::OnEffectFlags::kConfigured | + AudacityProject::OnEffectFlags::kSkipState); } bool BatchCommands::ApplyCommand(const wxString command, const wxString params) diff --git a/src/BatchCommands.h b/src/BatchCommands.h index 6e696d987..fe31d7292 100644 --- a/src/BatchCommands.h +++ b/src/BatchCommands.h @@ -33,6 +33,7 @@ class BatchCommands { void AbortBatch(); // Utility functions for the special commands. + wxString BuildCleanFileName(wxString fileName, wxString extension); bool WriteMp3File( const wxString Name, int bitrate ); double GetEndTime(); bool IsMono(); diff --git a/src/Experimental.h b/src/Experimental.h index 74b57d10e..b86b7682d 100644 --- a/src/Experimental.h +++ b/src/Experimental.h @@ -39,7 +39,7 @@ #define EXPERIMENTAL_FULL_WASAPI // JKC (effect by Norm C, 02 Oct 2013) -//#define EXPERIMENTAL_SCIENCE_FILTERS +#define EXPERIMENTAL_SCIENCE_FILTERS // LLL, 01 Oct 2013: // new key assignment view for preferences @@ -73,6 +73,8 @@ // As a minimum, if this is turned on for a release, // it should have an easy mechanism to disable it at run-time, such as a menu item or a pref, // preferrably disabled until other work is done. Martyn 22/12/2008. +// +// All code removed after 2.1.0 release since it was unmaintained. LLL //#define EFFECT_CATEGORIES // Andreas Micheler, 20.Nov 2007: @@ -150,10 +152,7 @@ // Module prefs provides a panel in prefs where users can choose which modules // to enable. -// #define EXPERIMENTAL_MODULE_PREFS - -// Define to include realtime effects processing. -#define EXPERIMENTAL_REALTIME_EFFECTS +#define EXPERIMENTAL_MODULE_PREFS // Define to include the effects rack (such as it is). //#define EXPERIMENTAL_EFFECTS_RACK diff --git a/src/Makefile.am b/src/Makefile.am index 5d0a731d1..baac6eeb9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -321,8 +321,6 @@ audacity_SOURCES = \ effects/Echo.h \ effects/Effect.cpp \ effects/Effect.h \ - effects/EffectCategory.cpp \ - effects/EffectCategory.h \ effects/EffectManager.cpp \ effects/EffectManager.h \ effects/EffectRack.cpp \ @@ -656,11 +654,6 @@ audacity_SOURCES += \ effects/lv2/LoadLV2.h \ effects/lv2/LV2Effect.cpp \ effects/lv2/LV2Effect.h \ - effects/lv2/lv2_event.h \ - effects/lv2/lv2_event_helpers.h \ - effects/lv2/LV2PortGroup.cpp \ - effects/lv2/LV2PortGroup.h \ - effects/lv2/lv2_uri_map.h \ $(NULL) endif diff --git a/src/Makefile.in b/src/Makefile.in index 813802022..48fae31d7 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -148,11 +148,6 @@ bin_PROGRAMS = audacity$(EXEEXT) @USE_LV2_TRUE@ effects/lv2/LoadLV2.h \ @USE_LV2_TRUE@ effects/lv2/LV2Effect.cpp \ @USE_LV2_TRUE@ effects/lv2/LV2Effect.h \ -@USE_LV2_TRUE@ effects/lv2/lv2_event.h \ -@USE_LV2_TRUE@ effects/lv2/lv2_event_helpers.h \ -@USE_LV2_TRUE@ effects/lv2/LV2PortGroup.cpp \ -@USE_LV2_TRUE@ effects/lv2/LV2PortGroup.h \ -@USE_LV2_TRUE@ effects/lv2/lv2_uri_map.h \ @USE_LV2_TRUE@ $(NULL) @USE_PORTSMF_TRUE@am__append_34 = $(PORTSMF_CFLAGS) @@ -354,7 +349,6 @@ am__audacity_SOURCES_DIST = BlockFile.cpp BlockFile.h DirManager.cpp \ effects/Compressor.h effects/Contrast.cpp effects/Contrast.h \ effects/DtmfGen.cpp effects/DtmfGen.h effects/Echo.cpp \ effects/Echo.h effects/Effect.cpp effects/Effect.h \ - effects/EffectCategory.cpp effects/EffectCategory.h \ effects/EffectManager.cpp effects/EffectManager.h \ effects/EffectRack.cpp effects/EffectRack.h \ effects/Equalization.cpp effects/Equalization.h \ @@ -469,14 +463,12 @@ am__audacity_SOURCES_DIST = BlockFile.cpp BlockFile.h DirManager.cpp \ effects/nyquist/Nyquist.cpp effects/nyquist/Nyquist.h \ effects/lv2/LoadLV2.cpp effects/lv2/LoadLV2.h \ effects/lv2/LV2Effect.cpp effects/lv2/LV2Effect.h \ - effects/lv2/lv2_event.h effects/lv2/lv2_event_helpers.h \ - effects/lv2/LV2PortGroup.cpp effects/lv2/LV2PortGroup.h \ - effects/lv2/lv2_uri_map.h NoteTrack.cpp NoteTrack.h \ - import/ImportMIDI.cpp import/ImportMIDI.h import/ImportQT.cpp \ - import/ImportQT.h effects/vamp/LoadVamp.cpp \ - effects/vamp/LoadVamp.h effects/vamp/VampEffect.cpp \ - effects/vamp/VampEffect.h effects/VST/aeffectx.h \ - effects/VST/VSTEffect.cpp effects/VST/VSTEffect.h + NoteTrack.cpp NoteTrack.h import/ImportMIDI.cpp \ + import/ImportMIDI.h import/ImportQT.cpp import/ImportQT.h \ + effects/vamp/LoadVamp.cpp effects/vamp/LoadVamp.h \ + effects/vamp/VampEffect.cpp effects/vamp/VampEffect.h \ + effects/VST/aeffectx.h effects/VST/VSTEffect.cpp \ + effects/VST/VSTEffect.h am__objects_1 = audacity-BlockFile.$(OBJEXT) \ audacity-DirManager.$(OBJEXT) audacity-Dither.$(OBJEXT) \ audacity-FileFormats.$(OBJEXT) audacity-Internat.$(OBJEXT) \ @@ -502,8 +494,7 @@ am__objects_1 = audacity-BlockFile.$(OBJEXT) \ @USE_LIBNYQUIST_TRUE@am__objects_7 = effects/nyquist/audacity-LoadNyquist.$(OBJEXT) \ @USE_LIBNYQUIST_TRUE@ effects/nyquist/audacity-Nyquist.$(OBJEXT) @USE_LV2_TRUE@am__objects_8 = effects/lv2/audacity-LoadLV2.$(OBJEXT) \ -@USE_LV2_TRUE@ effects/lv2/audacity-LV2Effect.$(OBJEXT) \ -@USE_LV2_TRUE@ effects/lv2/audacity-LV2PortGroup.$(OBJEXT) +@USE_LV2_TRUE@ effects/lv2/audacity-LV2Effect.$(OBJEXT) @USE_PORTSMF_TRUE@am__objects_9 = audacity-NoteTrack.$(OBJEXT) \ @USE_PORTSMF_TRUE@ import/audacity-ImportMIDI.$(OBJEXT) @USE_QUICKTIME_TRUE@am__objects_10 = \ @@ -595,7 +586,6 @@ am_audacity_OBJECTS = $(am__objects_1) audacity-AboutDialog.$(OBJEXT) \ effects/audacity-DtmfGen.$(OBJEXT) \ effects/audacity-Echo.$(OBJEXT) \ effects/audacity-Effect.$(OBJEXT) \ - effects/audacity-EffectCategory.$(OBJEXT) \ effects/audacity-EffectManager.$(OBJEXT) \ effects/audacity-EffectRack.$(OBJEXT) \ effects/audacity-Equalization.$(OBJEXT) \ @@ -1217,7 +1207,6 @@ audacity_SOURCES = $(libaudacity_la_SOURCES) AboutDialog.cpp \ effects/Compressor.h effects/Contrast.cpp effects/Contrast.h \ effects/DtmfGen.cpp effects/DtmfGen.h effects/Echo.cpp \ effects/Echo.h effects/Effect.cpp effects/Effect.h \ - effects/EffectCategory.cpp effects/EffectCategory.h \ effects/EffectManager.cpp effects/EffectManager.h \ effects/EffectRack.cpp effects/EffectRack.h \ effects/Equalization.cpp effects/Equalization.h \ @@ -1592,8 +1581,6 @@ effects/audacity-Echo.$(OBJEXT): effects/$(am__dirstamp) \ effects/$(DEPDIR)/$(am__dirstamp) effects/audacity-Effect.$(OBJEXT): effects/$(am__dirstamp) \ effects/$(DEPDIR)/$(am__dirstamp) -effects/audacity-EffectCategory.$(OBJEXT): effects/$(am__dirstamp) \ - effects/$(DEPDIR)/$(am__dirstamp) effects/audacity-EffectManager.$(OBJEXT): effects/$(am__dirstamp) \ effects/$(DEPDIR)/$(am__dirstamp) effects/audacity-EffectRack.$(OBJEXT): effects/$(am__dirstamp) \ @@ -1908,9 +1895,6 @@ effects/lv2/audacity-LoadLV2.$(OBJEXT): effects/lv2/$(am__dirstamp) \ effects/lv2/$(DEPDIR)/$(am__dirstamp) effects/lv2/audacity-LV2Effect.$(OBJEXT): effects/lv2/$(am__dirstamp) \ effects/lv2/$(DEPDIR)/$(am__dirstamp) -effects/lv2/audacity-LV2PortGroup.$(OBJEXT): \ - effects/lv2/$(am__dirstamp) \ - effects/lv2/$(DEPDIR)/$(am__dirstamp) import/audacity-ImportMIDI.$(OBJEXT): import/$(am__dirstamp) \ import/$(DEPDIR)/$(am__dirstamp) import/audacity-ImportQT.$(OBJEXT): import/$(am__dirstamp) \ @@ -2104,7 +2088,6 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-DtmfGen.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Echo.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Effect.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-EffectCategory.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-EffectManager.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-EffectRack.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Equalization.Po@am__quote@ @@ -2141,7 +2124,6 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@effects/audiounits/$(DEPDIR)/audacity-AudioUnitEffect.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/ladspa/$(DEPDIR)/audacity-LadspaEffect.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/lv2/$(DEPDIR)/audacity-LV2Effect.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@effects/lv2/$(DEPDIR)/audacity-LV2PortGroup.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/lv2/$(DEPDIR)/audacity-LoadLV2.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/nyquist/$(DEPDIR)/audacity-LoadNyquist.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/nyquist/$(DEPDIR)/audacity-Nyquist.Po@am__quote@ @@ -4125,20 +4107,6 @@ effects/audacity-Effect.obj: effects/Effect.cpp @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/audacity-Effect.obj `if test -f 'effects/Effect.cpp'; then $(CYGPATH_W) 'effects/Effect.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/Effect.cpp'; fi` -effects/audacity-EffectCategory.o: effects/EffectCategory.cpp -@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/audacity-EffectCategory.o -MD -MP -MF effects/$(DEPDIR)/audacity-EffectCategory.Tpo -c -o effects/audacity-EffectCategory.o `test -f 'effects/EffectCategory.cpp' || echo '$(srcdir)/'`effects/EffectCategory.cpp -@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/$(DEPDIR)/audacity-EffectCategory.Tpo effects/$(DEPDIR)/audacity-EffectCategory.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='effects/EffectCategory.cpp' object='effects/audacity-EffectCategory.o' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/audacity-EffectCategory.o `test -f 'effects/EffectCategory.cpp' || echo '$(srcdir)/'`effects/EffectCategory.cpp - -effects/audacity-EffectCategory.obj: effects/EffectCategory.cpp -@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/audacity-EffectCategory.obj -MD -MP -MF effects/$(DEPDIR)/audacity-EffectCategory.Tpo -c -o effects/audacity-EffectCategory.obj `if test -f 'effects/EffectCategory.cpp'; then $(CYGPATH_W) 'effects/EffectCategory.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/EffectCategory.cpp'; fi` -@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/$(DEPDIR)/audacity-EffectCategory.Tpo effects/$(DEPDIR)/audacity-EffectCategory.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='effects/EffectCategory.cpp' object='effects/audacity-EffectCategory.obj' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/audacity-EffectCategory.obj `if test -f 'effects/EffectCategory.cpp'; then $(CYGPATH_W) 'effects/EffectCategory.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/EffectCategory.cpp'; fi` - effects/audacity-EffectManager.o: effects/EffectManager.cpp @am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/audacity-EffectManager.o -MD -MP -MF effects/$(DEPDIR)/audacity-EffectManager.Tpo -c -o effects/audacity-EffectManager.o `test -f 'effects/EffectManager.cpp' || echo '$(srcdir)/'`effects/EffectManager.cpp @am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/$(DEPDIR)/audacity-EffectManager.Tpo effects/$(DEPDIR)/audacity-EffectManager.Po @@ -5889,20 +5857,6 @@ effects/lv2/audacity-LV2Effect.obj: effects/lv2/LV2Effect.cpp @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/lv2/audacity-LV2Effect.obj `if test -f 'effects/lv2/LV2Effect.cpp'; then $(CYGPATH_W) 'effects/lv2/LV2Effect.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/lv2/LV2Effect.cpp'; fi` -effects/lv2/audacity-LV2PortGroup.o: effects/lv2/LV2PortGroup.cpp -@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/lv2/audacity-LV2PortGroup.o -MD -MP -MF effects/lv2/$(DEPDIR)/audacity-LV2PortGroup.Tpo -c -o effects/lv2/audacity-LV2PortGroup.o `test -f 'effects/lv2/LV2PortGroup.cpp' || echo '$(srcdir)/'`effects/lv2/LV2PortGroup.cpp -@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/lv2/$(DEPDIR)/audacity-LV2PortGroup.Tpo effects/lv2/$(DEPDIR)/audacity-LV2PortGroup.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='effects/lv2/LV2PortGroup.cpp' object='effects/lv2/audacity-LV2PortGroup.o' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/lv2/audacity-LV2PortGroup.o `test -f 'effects/lv2/LV2PortGroup.cpp' || echo '$(srcdir)/'`effects/lv2/LV2PortGroup.cpp - -effects/lv2/audacity-LV2PortGroup.obj: effects/lv2/LV2PortGroup.cpp -@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/lv2/audacity-LV2PortGroup.obj -MD -MP -MF effects/lv2/$(DEPDIR)/audacity-LV2PortGroup.Tpo -c -o effects/lv2/audacity-LV2PortGroup.obj `if test -f 'effects/lv2/LV2PortGroup.cpp'; then $(CYGPATH_W) 'effects/lv2/LV2PortGroup.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/lv2/LV2PortGroup.cpp'; fi` -@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/lv2/$(DEPDIR)/audacity-LV2PortGroup.Tpo effects/lv2/$(DEPDIR)/audacity-LV2PortGroup.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='effects/lv2/LV2PortGroup.cpp' object='effects/lv2/audacity-LV2PortGroup.obj' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/lv2/audacity-LV2PortGroup.obj `if test -f 'effects/lv2/LV2PortGroup.cpp'; then $(CYGPATH_W) 'effects/lv2/LV2PortGroup.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/lv2/LV2PortGroup.cpp'; fi` - audacity-NoteTrack.o: NoteTrack.cpp @am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT audacity-NoteTrack.o -MD -MP -MF $(DEPDIR)/audacity-NoteTrack.Tpo -c -o audacity-NoteTrack.o `test -f 'NoteTrack.cpp' || echo '$(srcdir)/'`NoteTrack.cpp @am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/audacity-NoteTrack.Tpo $(DEPDIR)/audacity-NoteTrack.Po diff --git a/src/Menus.cpp b/src/Menus.cpp index eecc3dad4..e0e34119a 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -178,28 +178,10 @@ AudacityProjectCommandFunctor::AudacityProjectCommandFunctor(AudacityProject *pr mPluginID = pluginID; } -#if defined(EFFECT_CATEGORIES) -AudacityProjectCommandFunctor::AudacityProjectCommandFunctor(AudacityProject *project, - audCommandListFunction commandFunction, - wxArrayInt explicitIndices) -{ - mProject = project; - mCommandFunction = NULL; - mCommandKeyFunction = NULL; - mCommandListFunction = commandFunction; - mCommandPluginFunction = NULL; - mExplicitIndices = explicitIndices; -} -#endif - void AudacityProjectCommandFunctor::operator()(int index, const wxEvent * evt) { if (mCommandPluginFunction) - (mProject->*(mCommandPluginFunction)) (mPluginID); -#if defined(EFFECT_CATEGORIES) - else if (mCommandListFunction && mExplicitIndices.GetCount() > 0) - (mProject->*(mCommandListFunction)) (mExplicitIndices[index]); -#endif + (mProject->*(mCommandPluginFunction)) (mPluginID, AudacityProject::OnEffectFlags::kNone); else if (mCommandListFunction) (mProject->*(mCommandListFunction)) (index); else if (mCommandKeyFunction) @@ -992,44 +974,10 @@ void AudacityProject::CreateMenusAndCommands() c->BeginMenu(_("&Generate")); c->SetDefaultFlags(AudioIONotBusyFlag, AudioIONotBusyFlag); -#ifndef EFFECT_CATEGORIES PopulateEffectsMenu(c, EffectTypeGenerate, AudioIONotBusyFlag, AudioIONotBusyFlag); -#else - - int flags; - - flags = INSERT_EFFECT | BUILTIN_EFFECT | PLUGIN_EFFECT; - EffectCategory* ac = - em.LookupCategory(wxT("http://lv2plug.in/ns/lv2core#GeneratorPlugin")); - CategorySet roots = ac->GetSubCategories(); - EffectSet generators = ac->GetEffects(); - EffectSet topLevel = CreateEffectSubmenus(c, roots, flags, 0); - std::copy(generators.begin(), generators.end(), - std::insert_iterator(topLevel, topLevel.begin())); - AddEffectsToMenu(c, topLevel); - - // Add all uncategorised effects in a special submenu - EffectSet unsorted = - em.GetUnsortedEffects(flags); - if (unsorted.size() > 0) { - c->AddSeparator(); - c->BeginSubMenu(_("Unsorted")); - names.Clear(); - indices.Clear(); - EffectSet::const_iterator iter; - for (iter = unsorted.begin(); iter != unsorted.end(); ++iter) { - names.Add((*iter)->GetEffectName()); - indices.Add((*iter)->GetEffectID()); - } - c->AddItemList(wxT("Generate"), names, - FNI(OnProcessAny, indices), true); - c->EndSubMenu(); - } - -#endif // EFFECT_CATEGORIES c->EndMenu(); @@ -1040,7 +988,7 @@ void AudacityProject::CreateMenusAndCommands() c->BeginMenu(_("Effe&ct")); wxString buildMenuLabel; - if (mLastEffectType != 0) { + if (!mLastEffect.IsEmpty()) { buildMenuLabel.Printf(_("Repeat %s"), EffectManager::Get().GetEffectName(mLastEffect).c_str()); } @@ -1053,40 +1001,10 @@ void AudacityProject::CreateMenusAndCommands() c->AddSeparator(); - // this is really ugly but we need to keep all the old code to get any - // effects at all in the menu when EFFECT_CATEGORIES is undefined - -#ifndef EFFECT_CATEGORIES PopulateEffectsMenu(c, EffectTypeProcess, AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag, TracksExistFlag | IsRealtimeNotActiveFlag); -#else - int flags = PROCESS_EFFECT | BUILTIN_EFFECT | PLUGIN_EFFECT | ADVANCED_EFFECT; - // The categories form a DAG, so we start at the roots (the categories - // without incoming links) - CategorySet roots = em.GetRootCategories(); - EffectSet topLevel = CreateEffectSubmenus(c, roots, flags, 0); - AddEffectsToMenu(c, topLevel); - - // Add all uncategorised effects in a special submenu - EffectSet unsorted = - em.GetUnsortedEffects(flags); - if (unsorted.size() > 0) { - c->AddSeparator(); - c->BeginSubMenu(_("Unsorted")); - names.Clear(); - indices.Clear(); - EffectSet::const_iterator iter; - for (iter = unsorted.begin(); iter != unsorted.end(); ++iter) { - names.Add((*iter)->GetEffectName()); - indices.Add((*iter)->GetEffectID()); - } - c->AddItemList(wxT("Effect"), names, FNI(OnProcessAny, indices), true); - c->EndSubMenu(); - } - -#endif c->EndMenu(); @@ -1103,42 +1021,10 @@ void AudacityProject::CreateMenusAndCommands() AudioIONotBusyFlag | WaveTracksSelectedFlag | TimeSelectedFlag, AudioIONotBusyFlag | WaveTracksSelectedFlag | TimeSelectedFlag); -#ifndef EFFECT_CATEGORIES PopulateEffectsMenu(c, EffectTypeAnalyze, AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag, TracksExistFlag | IsRealtimeNotActiveFlag); -#else - - flags = ANALYZE_EFFECT | BUILTIN_EFFECT | PLUGIN_EFFECT; - EffectCategory* ac = - em.LookupCategory(wxT("http://lv2plug.in/ns/lv2core#AnalyserPlugin")); - CategorySet roots = ac->GetSubCategories(); - EffectSet analyzers = ac->GetEffects(); - EffectSet topLevel = CreateEffectSubmenus(c, roots, flags, 0); - std::copy(analyzers.begin(), analyzers.end(), - std::insert_iterator(topLevel, topLevel.begin())); - AddEffectsToMenu(c, topLevel); - - // Add all uncategorised effects in a special submenu - EffectSet unsorted = - em.GetUnsortedEffects(flags); - if (unsorted.size() > 0) { - c->AddSeparator(); - c->BeginSubMenu(_("Unsorted")); - names.Clear(); - indices.Clear(); - EffectSet::const_iterator iter; - for (iter = unsorted.begin(); iter != unsorted.end(); ++iter) { - names.Add((*iter)->GetEffectName()); - indices.Add((*iter)->GetEffectID()); - } - c->AddItemList(wxT("Analyze"), names, - FNI(OnProcessAny, indices), true); - c->EndSubMenu(); - } - -#endif // EFFECT_CATEGORIES c->EndMenu(); @@ -1381,7 +1267,7 @@ void AudacityProject::PopulateEffectsMenu(CommandManager* c, AddEffectMenuItems(c, defplugs, batchflags, realflags, true); - if (optplugs.GetCount()) + if (defplugs.GetCount() && optplugs.GetCount()) { c->AddSeparator(); } @@ -1421,13 +1307,6 @@ void AudacityProject::AddEffectMenuItems(CommandManager *c, wxString name = plug->GetName(); - // This goes away once everything has been converted to new format - wxString stripped; - if (name.EndsWith(wxT("..."), &stripped)) - { - name = stripped; - } - if (plug->IsEffectInteractive()) { name += wxT("..."); @@ -1492,13 +1371,6 @@ void AudacityProject::AddEffectMenuItems(CommandManager *c, wxString name = plug->GetName(); - // This goes away once everything has been converted to new format - wxString stripped; - if (name.EndsWith(wxT("..."), &stripped)) - { - name = stripped; - } - if (plug->IsEffectInteractive()) { name += wxT("..."); @@ -1635,73 +1507,6 @@ void AudacityProject::AddEffectMenuItemGroup(CommandManager *c, return; } -#ifdef EFFECT_CATEGORIES - -EffectSet AudacityProject::CreateEffectSubmenus(CommandManager* c, - const CategorySet& categories, - int flags, - unsigned submenuThreshold) { - EffectSet topLevel; - - CategorySet::const_iterator iter; - for (iter = categories.begin(); iter != categories.end(); ++iter) { - - EffectSet effects = (*iter)->GetAllEffects(flags); - - // If the subgraph for this category only contains a single effect, - // add it directly in this menu - if (effects.size() <= submenuThreshold) - topLevel.insert(effects.begin(), effects.end()); - - // If there are more than one effect, add a submenu for the category - else if (effects.size() > 0) { - - EffectSet directEffects = (*iter)->GetEffects(flags); - CategorySet subCategories = (*iter)->GetSubCategories(); - CategorySet nonEmptySubCategories; - - CategorySet::const_iterator sci; - for (sci = subCategories.begin(); sci != subCategories.end(); ++sci) { - if ((*sci)->GetAllEffects(flags).size() > 0) - nonEmptySubCategories.insert(*sci); - } - - // If there are no direct effects and only one subcategory, - // add the contents of that subcategory directly in this menu. - if (directEffects.size() == 0 && nonEmptySubCategories.size() == 1) { - EffectSet a = CreateEffectSubmenus(c, nonEmptySubCategories, - flags); - topLevel.insert(a.begin(), a.end()); - } - - // Else, add submenus for the subcategories - else { - c->BeginSubMenu((*iter)->GetName()); - EffectSet a = CreateEffectSubmenus(c, subCategories, flags); - a.insert(directEffects.begin(), directEffects.end()); - AddEffectsToMenu(c, a); - c->EndSubMenu(); - } - } - } - - return topLevel; -} - -void AudacityProject::AddEffectsToMenu(CommandManager* c, - const EffectSet& effects) { - wxArrayString names; - wxArrayInt indices; - EffectSet::const_iterator iter; - for (iter = effects.begin(); iter != effects.end(); ++iter) { - names.Add((*iter)->GetEffectName()); - indices.Add((*iter)->GetEffectID()); - } - c->AddItemList(wxT("Effects"), names, FNI(OnProcessAny, indices), true); -} - -#endif - void AudacityProject::CreateRecentFilesMenu(CommandManager *c) { // Recent Files and Recent Projects menus @@ -1907,7 +1712,7 @@ wxUint32 AudacityProject::GetUpdateFlags() if (mUndoManager.UnsavedChanges()) flags |= UnsavedChangesFlag; - if (!mLastEffect.empty()) + if (!mLastEffect.IsEmpty()) flags |= HasLastEffectFlag; if (mUndoManager.UndoAvailable()) @@ -1948,10 +1753,8 @@ wxUint32 AudacityProject::GetUpdateFlags() else flags |= IsNotSyncLockedFlag; -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) if (!EffectManager::Get().RealtimeIsActive()) flags |= IsRealtimeNotActiveFlag; -#endif return flags; } @@ -3258,23 +3061,23 @@ void AudacityProject::OnZeroCrossing() /// OnEffect() takes a PluginID and has the EffectManager execute the assocated effect. /// -/// At the moment flags are used only to indicate -/// whether to prompt for parameters or whether to -/// use the most recently stored values. -/// -/// At some point we should change to specifying a -/// parameter source - one of: -/// + Prompt -/// + Use previous values -/// + Parse from a string that is passed in -/// -/// DanH: I've added the third option as a temporary measure. I think this -/// should eventually be done by having effects as Command objects. -bool AudacityProject::OnEffect(int type, - const PluginID & ID, - wxString params, - bool saveState) +/// At the moment flags are used only to indicate whether to prompt for parameters and +/// whether to save the state to history. +bool AudacityProject::OnEffect(const PluginID & ID, int flags) { + const PluginDescriptor *plug = PluginManager::Get().GetPlugin(ID); + wxASSERT(plug != NULL); + EffectType type = plug->GetEffectType(); + + // Make sure there's no activity since the effect is about to be applied + // to the project's tracks. Mainly for Apply during RTP, but also used + // for batch commands + if (flags & OnEffectFlags::kConfigured) + { + OnStop(); + SelectAllIfNone(); + } + wxGetApp().SetMissingAliasedFileWarningShouldShow(true); TrackListIterator iter(mTracks); @@ -3295,7 +3098,7 @@ bool AudacityProject::OnEffect(int type, if (count == 0) { // No tracks were selected... - if (type & INSERT_EFFECT) { + if (type == EffectTypeGenerate) { // Create a new track for the generated audio... newTrack = mTrackFactory->NewWaveTrack(); mTracks->Add(newTrack); @@ -3309,154 +3112,77 @@ bool AudacityProject::OnEffect(int type, EffectManager & em = EffectManager::Get(); - if (em.DoEffect(ID, this, type, mRate, mTracks, mTrackFactory, - &mViewInfo.selectedRegion, params)) - { - if (saveState) - { - wxString longDesc = em.GetEffectDescription(ID); - wxString shortDesc = em.GetEffectName(ID); + bool success = em.DoEffect(ID, this, mRate, + mTracks, mTrackFactory, + &mViewInfo.selectedRegion, + !(flags & OnEffectFlags::kConfigured)); - if (shortDesc.Length() > 3 && shortDesc.Right(3)==wxT("...")) - shortDesc = shortDesc.Left(shortDesc.Length()-3); - - PushState(longDesc, shortDesc); - - // Only remember a successful effect, don't rmemeber insert, - // or analyze effects. - if ((type & (INSERT_EFFECT | ANALYZE_EFFECT))==0) { - mLastEffect = ID; - mLastEffectType = type; - wxString lastEffectDesc; - /* i18n-hint: %s will be the name of the effect which will be - * repeated if this menu item is chosen */ - lastEffectDesc.Printf(_("Repeat %s"), shortDesc.c_str()); - mCommandManager.Modify(wxT("RepeatLastEffect"), lastEffectDesc); - } - } - //STM: - //The following automatically re-zooms after sound was generated. - // IMO, it was disorienting, removing to try out without re-fitting - //mchinen:12/14/08 reapplying for generate effects - if ( type & INSERT_EFFECT) - { - if (count == 0 || (clean && mViewInfo.selectedRegion.t0() == 0.0)) - OnZoomFit(); - // mTrackPanel->Refresh(false); - } - RedrawProject(); - if (focus != NULL) { - focus->SetFocus(); - } - mTrackPanel->EnsureVisible(mTrackPanel->GetFirstSelectedTrack()); - - mTrackPanel->Refresh(false); - } else { + if (!success) { if (newTrack) { mTracks->Remove(newTrack); delete newTrack; mTrackPanel->Refresh(false); } -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) // For now, we're limiting realtime preview to a single effect, so // make sure the menus reflect that fact that one may have just been // opened. UpdateMenus(false); -#endif return false; } + if (!(flags & OnEffectFlags::kSkipState)) + { + wxString shortDesc = em.GetEffectName(ID); + wxString longDesc = em.GetEffectDescription(ID); + PushState(longDesc, shortDesc); + + // Only remember a successful effect, don't rmemeber insert, + // or analyze effects. + if (type == EffectTypeProcess) { + mLastEffect = ID; + wxString lastEffectDesc; + /* i18n-hint: %s will be the name of the effect which will be + * repeated if this menu item is chosen */ + lastEffectDesc.Printf(_("Repeat %s"), shortDesc.c_str()); + mCommandManager.Modify(wxT("RepeatLastEffect"), lastEffectDesc); + } + } + + //STM: + //The following automatically re-zooms after sound was generated. + // IMO, it was disorienting, removing to try out without re-fitting + //mchinen:12/14/08 reapplying for generate effects + if (type == EffectTypeGenerate) + { + if (count == 0 || (clean && mViewInfo.selectedRegion.t0() == 0.0)) + OnZoomFit(); + // mTrackPanel->Refresh(false); + } + RedrawProject(); + if (focus != NULL) { + focus->SetFocus(); + } + mTrackPanel->EnsureVisible(mTrackPanel->GetFirstSelectedTrack()); + + mTrackPanel->Refresh(false); + return true; } -void AudacityProject::OnEffect(const PluginID & pluginID) -{ - PluginManager & pm = PluginManager::Get(); - const PluginDescriptor *plug = pm.GetPlugin(pluginID); - - int type; - switch (plug->GetEffectType()) - { - case EffectTypeGenerate: - type = INSERT_EFFECT; - break; - - case EffectTypeProcess: - type = PROCESS_EFFECT; - break; - - case EffectTypeAnalyze: - type = ANALYZE_EFFECT; - break; - - case EffectTypeNone: - type = 0; - break; - } - - type |= plug->IsEffectDefault() ? BUILTIN_EFFECT : PLUGIN_EFFECT; - - OnEffect(type, pluginID); -} - -// Warning...complete hackage ahead -void AudacityProject::OnEffect(const PluginID & pluginID, bool configured) -{ - PluginManager & pm = PluginManager::Get(); - const PluginDescriptor *plug = pm.GetPlugin(pluginID); - - int type; - switch (plug->GetEffectType()) - { - case EffectTypeGenerate: - type = INSERT_EFFECT; - break; - - case EffectTypeProcess: - type = PROCESS_EFFECT; - break; - - case EffectTypeAnalyze: - type = ANALYZE_EFFECT; - break; - - case EffectTypeNone: - type = 0; - break; - } - - type |= plug->IsEffectDefault() ? BUILTIN_EFFECT : PLUGIN_EFFECT; - type |= configured ? CONFIGURED_EFFECT : 0; - - OnStop(); - SelectAllIfNone(); - - OnEffect(type, pluginID); -} - void AudacityProject::OnRepeatLastEffect(int WXUNUSED(index)) { - if (!mLastEffect.empty()) { - // Setting the CONFIGURED_EFFECT bit prevents - // prompting for parameters. - OnEffect(mLastEffectType | CONFIGURED_EFFECT, mLastEffect); + if (!mLastEffect.IsEmpty()) + { + OnEffect(mLastEffect, OnEffectFlags::kConfigured); } } -#ifdef EFFECT_CATEGORIES -void AudacityProject::OnProcessAny(int index) -{ - Effect* e = EffectManager::Get().GetEffect(index); - OnEffect(ALL_EFFECTS, e); -} -#endif - void AudacityProject::OnStereoToMono(int WXUNUSED(index)) { - OnEffect(ALL_EFFECTS, - EffectManager::Get().GetEffectByIdentifier(wxT("StereoToMono"))); + OnEffect(EffectManager::Get().GetEffectByIdentifier(wxT("StereoToMono")), + OnEffectFlags::kConfigured); } // @@ -6359,53 +6085,6 @@ void AudacityProject::OnEditChains() dlg.ShowModal(); } -wxString AudacityProject::BuildCleanFileName(wxString fileName, wxString extension) -{ - wxFileName newFileName(fileName); - wxString justName = newFileName.GetName(); - wxString pathName = newFileName.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR); - - if (justName == wxT("")) { - wxDateTime now = wxDateTime::Now(); - int year = now.GetYear(); - wxDateTime::Month month = now.GetMonth(); - wxString monthName = now.GetMonthName(month); - int dom = now.GetDay(); - int hour = now.GetHour(); - int minute = now.GetMinute(); - int second = now.GetSecond(); - justName = wxString::Format(wxT("%d-%s-%02d-%02d-%02d-%02d"), - year, monthName.c_str(), dom, hour, minute, second); - -// SetName(cleanedFileName); -// bool isStereo; -// double endTime = project->mTracks->GetEndTime(); -// double startTime = 0.0; - //OnSelectAll(); - pathName = gPrefs->Read(wxT("/DefaultOpenPath"), ::wxGetCwd()); - ::wxMessageBox(wxString::Format(wxT("Export recording to %s\n/cleaned/%s%s"), - pathName.c_str(), justName.c_str(), extension.c_str()), - wxT("Export recording"), - wxOK | wxCENTRE); - pathName += wxT("/"); - } - wxString cleanedName = pathName; - cleanedName += wxT("cleaned"); - bool flag = ::wxFileName::FileExists(cleanedName); - if (flag == true) { - ::wxMessageBox(wxT("Cannot create directory 'cleaned'. \nFile already exists that is not a directory")); - return wxT(""); - } - ::wxFileName::Mkdir(cleanedName, 0777, wxPATH_MKDIR_FULL); // make sure it exists - - cleanedName += wxT("/"); - cleanedName += justName; - cleanedName += extension; - wxGetApp().AddFileToHistory(cleanedName); - - return cleanedName; -} - void AudacityProject::OnRemoveTracks() { TrackListIterator iter(mTracks); diff --git a/src/Menus.h b/src/Menus.h index c84ea9e9a..874011ce9 100644 --- a/src/Menus.h +++ b/src/Menus.h @@ -24,20 +24,6 @@ private: void CreateMenusAndCommands(); -#ifdef EFFECT_CATEGORIES - -/** Generate submenus for the categories that contain more than one effect - and return the effects from the categories that do not contain more than - submenuThreshold effects so the caller can add them to the current menu. */ -EffectSet CreateEffectSubmenus(CommandManager* c, - const CategorySet& categories, int flags, - unsigned submenuThreshold = 1); - -/** Add the set of effects to the current menu. */ -void AddEffectsToMenu(CommandManager* c, const EffectSet& effects); - -#endif - void PopulateEffectsMenu(CommandManager *c, EffectType type, int batchflags, int realflags); void AddEffectMenuItems(CommandManager *c, EffectPlugs & plugs, int batchflags, int realflags, bool isDefault); void AddEffectMenuItemGroup(CommandManager *c, const wxArrayString & names, const PluginIDList & plugs, const wxArrayInt & flags, bool isDefault); @@ -361,17 +347,22 @@ void OnEditLabels(); // Effect Menu -bool OnEffect(int type, const PluginID & ID, wxString params = wxEmptyString, bool saveState = true); -void OnEffect(const PluginID & pluginID); -void OnEffect(const PluginID & pluginID, bool configured = false); +class OnEffectFlags +{ +public: + // No flags specified + static const int kNone = 0x00; + // Flag used to disable prompting for configuration parameteres. + static const int kConfigured = 0x01; + // Flag used to disable saving the state after processing. + static const int kSkipState = 0x02; +}; + +bool OnEffect(const PluginID & ID, int flags = OnEffectFlags::kNone); void OnRepeatLastEffect(int index); -#ifdef EFFECT_CATEGORIES -void OnProcessAny(int index); -#endif void OnApplyChain(); void OnEditChains(); void OnStereoToMono(int index); -wxString BuildCleanFileName(wxString fileName, wxString extension); // Help Menu diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 16b238d43..95d559471 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -413,7 +413,7 @@ bool ModuleManager::DiscoverProviders() if (module) { // Register the provider - pm.RegisterModulePlugin(module); + pm.RegisterPlugin(module); // Now, allow the module to auto-register children module->AutoRegisterPlugins(pm); @@ -434,7 +434,7 @@ void ModuleManager::InitializeBuiltins() if (module->Initialize()) { // Register the provider - const PluginID & id = pm.RegisterModulePlugin(module); + const PluginID & id = pm.RegisterPlugin(module); // Need to remember it mDynModules[id] = module; @@ -508,7 +508,7 @@ void ModuleManager::RegisterModule(ModuleInterface *module) mDynModules[id] = module; - PluginManager::Get().RegisterModulePlugin(module); + PluginManager::Get().RegisterPlugin(module); } void ModuleManager::FindAllPlugins(PluginIDList & providers, wxArrayString & paths) diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index b8c2b6323..9c2d51781 100644 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -608,7 +608,11 @@ void PluginRegistrationDialog::PopulateOrExchange(ShuttleGui &S) } Layout(); Fit(); - SetSizeHints(GetSize()); + wxRect r = wxGetClientDisplayRect(); + wxSize sz = GetSize(); + sz.SetWidth(wxMin(sz.GetWidth(), r.GetWidth())); + sz.SetHeight(wxMin(sz.GetHeight(), r.GetHeight())); + SetMinSize(sz); // Parent window is usually not there yet, so centre on screen rather than on parent. CenterOnScreen(); @@ -790,7 +794,7 @@ PluginDescriptor::~PluginDescriptor() return; } -bool PluginDescriptor::IsInstantiated() +bool PluginDescriptor::IsInstantiated() const { return mInstance != NULL; } @@ -1083,7 +1087,7 @@ void PluginDescriptor::SetImporterExtensions(const wxArrayString & extensions) // // ============================================================================ -const PluginID & PluginManager::RegisterModulePlugin(ModuleInterface *module) +const PluginID & PluginManager::RegisterPlugin(ModuleInterface *module) { PluginDescriptor & plug = CreatePlugin(GetID(module), module, PluginTypeModule); @@ -1093,7 +1097,7 @@ const PluginID & PluginManager::RegisterModulePlugin(ModuleInterface *module) return plug.GetID(); } -const PluginID & PluginManager::RegisterEffectPlugin(ModuleInterface *provider, EffectIdentInterface *effect) +const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, EffectIdentInterface *effect) { PluginDescriptor & plug = CreatePlugin(GetID(effect), effect, PluginTypeEffect); @@ -1112,7 +1116,7 @@ const PluginID & PluginManager::RegisterEffectPlugin(ModuleInterface *provider, return plug.GetID(); } -const PluginID & PluginManager::RegisterImporterPlugin(ModuleInterface *provider, ImporterInterface *importer) +const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, ImporterInterface *importer) { PluginDescriptor & plug = CreatePlugin(GetID(importer), importer, PluginTypeImporter); @@ -1963,6 +1967,11 @@ const PluginDescriptor *PluginManager::GetFirstPluginForEffectType(EffectType ty gPrefs->Read(plug.GetEffectFamily() + wxT("/Enable"), &familyEnabled, true); if (plug.IsValid() && plug.IsEnabled() && plug.GetEffectType() == type && familyEnabled) { + if (plug.IsInstantiated() && ((Effect *)plug.GetInstance())->IsHidden()) + { + continue; + } + return &plug; } } @@ -1979,6 +1988,11 @@ const PluginDescriptor *PluginManager::GetNextPluginForEffectType(EffectType typ gPrefs->Read(plug.GetEffectFamily() + wxT("/Enable"), &familyEnabled, true); if (plug.IsValid() && plug.IsEnabled() && plug.GetEffectType() == type && familyEnabled) { + if (plug.IsInstantiated() && ((Effect *)plug.GetInstance())->IsHidden()) + { + continue; + } + return &plug; } } @@ -1996,7 +2010,7 @@ bool PluginManager::IsRegistered(const PluginID & ID) return true; } -const PluginID & PluginManager::RegisterLegacyEffectPlugin(EffectIdentInterface *effect) +const PluginID & PluginManager::RegisterPlugin(EffectIdentInterface *effect) { PluginDescriptor & plug = CreatePlugin(GetID(effect), effect, PluginTypeEffect); @@ -2404,7 +2418,7 @@ bool PluginManager::SetConfig(const wxString & key, const sampleCount & value) return result; } -wxString PluginManager::SettingsID(const PluginID & ID) +wxString PluginManager::SettingsPath(const PluginID & ID, bool shared) { if (mPlugins.find(ID) == mPlugins.end()) { @@ -2412,23 +2426,25 @@ wxString PluginManager::SettingsID(const PluginID & ID) } const PluginDescriptor & plug = mPlugins[ID]; + + wxString id = GetPluginTypeString(plug.GetPluginType()) + + wxT("_") + + plug.GetEffectFamily() + // is empty for non-Effects + wxT("_") + + plug.GetVendor() + + wxT("_") + + (shared ? wxT("") : plug.GetName()); - return wxString::Format(wxT("%s_%s_%s_%s"), - GetPluginTypeString(plug.GetPluginType()).c_str(), - plug.GetEffectFamily().c_str(), // is empty for non-Effects - plug.GetVendor().c_str(), - plug.GetName().c_str()); + return SETROOT + + ConvertID(id) + + wxCONFIG_PATH_SEPARATOR + + (shared ? wxT("shared") : wxT("private")) + + wxCONFIG_PATH_SEPARATOR; } wxString PluginManager::SharedGroup(const PluginID & ID, const wxString & group) { - wxString settingsID = SettingsID(ID); - - wxString path = SETROOT + - ConvertID(settingsID) + - wxCONFIG_PATH_SEPARATOR + - wxT("shared") + - wxCONFIG_PATH_SEPARATOR; + wxString path = SettingsPath(ID, true); wxFileName f(group); if (!f.GetName().IsEmpty()) @@ -2452,13 +2468,7 @@ wxString PluginManager::SharedKey(const PluginID & ID, const wxString & group, c wxString PluginManager::PrivateGroup(const PluginID & ID, const wxString & group) { - wxString settingsID = SettingsID(ID); - - wxString path = SETROOT + - ConvertID(settingsID) + - wxCONFIG_PATH_SEPARATOR + - wxT("private") + - wxCONFIG_PATH_SEPARATOR; + wxString path = SettingsPath(ID, false); wxFileName f(group); if (!f.GetName().IsEmpty()) diff --git a/src/PluginManager.h b/src/PluginManager.h index a12f6b824..bf069231a 100644 --- a/src/PluginManager.h +++ b/src/PluginManager.h @@ -45,7 +45,7 @@ public: PluginDescriptor(); virtual ~PluginDescriptor(); - bool IsInstantiated(); + bool IsInstantiated() const; IdentInterface *GetInstance(); void SetInstance(IdentInterface *instance); @@ -173,14 +173,14 @@ public: // PluginManagerInterface implementation - const PluginID & RegisterModulePlugin(ModuleInterface *module); - const PluginID & RegisterEffectPlugin(ModuleInterface *provider, EffectIdentInterface *effect); - const PluginID & RegisterImporterPlugin(ModuleInterface *provider, ImporterInterface *importer); + virtual const PluginID & RegisterPlugin(ModuleInterface *module); + virtual const PluginID & RegisterPlugin(ModuleInterface *provider, EffectIdentInterface *effect); + virtual const PluginID & RegisterPlugin(ModuleInterface *provider, ImporterInterface *importer); - void FindFilesInPathList(const wxString & pattern, - const wxArrayString & pathList, - wxArrayString & files, - bool directories = false); + virtual void FindFilesInPathList(const wxString & pattern, + const wxArrayString & pathList, + wxArrayString & files, + bool directories = false); virtual bool GetSharedConfigSubgroups(const PluginID & ID, const wxString & group, wxArrayString & subgroups); @@ -256,8 +256,8 @@ public: IdentInterface *GetInstance(const PluginID & ID); void SetInstance(const PluginID & ID, IdentInterface *instance); // TODO: Remove after conversion - // - const PluginID & RegisterLegacyEffectPlugin(EffectIdentInterface *effect); + // For builtin effects + const PluginID & RegisterPlugin(EffectIdentInterface *effect); private: void Load(); @@ -289,7 +289,7 @@ private: bool SetConfig(const wxString & key, const double & value); bool SetConfig(const wxString & key, const sampleCount & value); - wxString SettingsID(const PluginID & ID); + wxString SettingsPath(const PluginID & ID, bool shared); wxString SharedGroup(const PluginID & ID, const wxString & group); wxString SharedKey(const PluginID & ID, const wxString & group, const wxString & key); wxString PrivateGroup(const PluginID & ID, const wxString & group); diff --git a/src/Project.cpp b/src/Project.cpp index 798996859..0218678a2 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -772,7 +772,6 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id, mImportedDependencies(false), mWantSaveCompressed(false), mLastEffect(wxEmptyString), - mLastEffectType(0), mTimerRecordCanceled(false), mMenuClose(false) , mbInitializingScrollbar(false) @@ -3656,8 +3655,8 @@ bool AudacityProject::Import(wxString fileName, WaveTrackArray* pTrackArray /*= //TODO: All we want is a SelectAll() SelectNone(); SelectAllIfNone(); - OnEffect(ALL_EFFECTS | CONFIGURED_EFFECT, - EffectManager::Get().GetEffectByIdentifier(wxT("Normalize"))); + OnEffect(EffectManager::Get().GetEffectByIdentifier(wxT("Normalize")), + OnEffectFlags::kConfigured); } GetDirManager()->FillBlockfilesCache(); diff --git a/src/Project.h b/src/Project.h index 8602644a7..587564f01 100644 --- a/src/Project.h +++ b/src/Project.h @@ -605,8 +605,7 @@ class AUDACITY_DLL_API AudacityProject: public wxFrame, // Last effect applied to this project PluginID mLastEffect; - int mLastEffectType; - + // The screenshot class needs to access internals friend class ScreenshotCommand; @@ -626,7 +625,7 @@ class AUDACITY_DLL_API AudacityProject: public wxFrame, typedef void (AudacityProject::*audCommandFunction)(); typedef void (AudacityProject::*audCommandKeyFunction)(const wxEvent *); typedef void (AudacityProject::*audCommandListFunction)(int); -typedef void (AudacityProject::*audCommandPluginFunction)(const PluginID &); +typedef bool (AudacityProject::*audCommandPluginFunction)(const PluginID &, int); // Previously this was in menus.cpp, and the declaration of the // command functor was not visible anywhere else. @@ -642,11 +641,6 @@ public: AudacityProjectCommandFunctor(AudacityProject *project, audCommandPluginFunction commandFunction, const PluginID & pluginID); -#if defined(EFFECT_CATEGORIES) - AudacityProjectCommandFunctor(AudacityProject *project, - audCommandListFunction commandFunction, - wxArrayInt explicitIndices); -#endif virtual void operator()(int index = 0, const wxEvent *evt = NULL); @@ -657,9 +651,6 @@ private: audCommandListFunction mCommandListFunction; audCommandPluginFunction mCommandPluginFunction; PluginID mPluginID; -#if defined(EFFECT_CATEGORIES) - wxArrayInt mExplicitIndices; -#endif }; #endif diff --git a/src/ShuttleGui.cpp b/src/ShuttleGui.cpp index 9e5cc912c..bb8976842 100644 --- a/src/ShuttleGui.cpp +++ b/src/ShuttleGui.cpp @@ -1456,6 +1456,12 @@ wxSlider * ShuttleGuiBase::TieSlider( const wxString &Prompt, int &pos, const in return TieSlider( Prompt, WrappedRef, max, min ); } +wxSlider * ShuttleGuiBase::TieSlider( const wxString &Prompt, double &pos, const double max, const double min ) +{ + WrappedType WrappedRef( pos ); + return TieSlider( Prompt, WrappedRef, max, min ); +} + wxSlider * ShuttleGuiBase::TieSlider( const wxString &Prompt, float &pos, const float fMin, const float fMax) { const float RoundFix=0.0000001f; @@ -2241,12 +2247,12 @@ void ShuttleGui::AddStandardButtons(long buttons, wxButton *extra) EndVerticalLay(); } -void ShuttleGui::AddSpace( int width, int height ) +wxSizerItem * ShuttleGui::AddSpace( int width, int height ) { if( mShuttleMode != eIsCreating ) - return; + return NULL; - mpSizer->Add( width, height, 0); + return mpSizer->Add( width, height, 0); } void ShuttleGui::SetSizeHints( wxWindow *window, const wxArrayString & items ) diff --git a/src/ShuttleGui.h b/src/ShuttleGui.h index 60f77944c..7b5add4cf 100644 --- a/src/ShuttleGui.h +++ b/src/ShuttleGui.h @@ -17,6 +17,7 @@ #include "Audacity.h" #include +#include #include #include "WrappedType.h" @@ -176,6 +177,7 @@ public: wxSlider * TieSlider( const wxString &Prompt, WrappedType & WrappedRef, const int max, const int min = 0 ); wxSlider * TieSlider( const wxString &Prompt, int &pos, const int max, const int min = 0); + wxSlider * TieSlider( const wxString &Prompt, double &pos, const double max, const double min = 0.0); wxSlider * TieSlider( const wxString &Prompt, float &pos, const float fMin, const float fMax); wxSlider * TieVSlider( const wxString &Prompt, float &pos, const float fMin, const float fMax); @@ -259,6 +261,8 @@ public: int GetId() {return miIdNext;}; void UseUpId(); + wxSizer * GetSizer() {return mpSizer;}; + protected: void SetProportions( int Default ); void PushSizer(); @@ -368,8 +372,8 @@ public: RulerPanel * AddRulerVertical( float low, float hi, const wxString & Units ); AttachableScrollBar * AddAttachableScrollBar( long style = wxSB_HORIZONTAL ); void AddStandardButtons( long buttons = eOkButton | eCancelButton, wxButton *extra = NULL ); - void AddSpace( int width, int height ); - void AddSpace( int size ) { AddSpace( size, size ); }; + wxSizerItem * AddSpace( int width, int height ); + wxSizerItem * AddSpace( int size ) { return AddSpace( size, size ); }; int GetBorder() { return miBorder; }; void SetSizeHints( int minX = -1, int minY = -1 ); @@ -377,5 +381,7 @@ public: void SetSizeHints( const wxArrayInt & items ); static void SetSizeHints( wxWindow *window, const wxArrayString & items ); static void SetSizeHints( wxWindow *window, const wxArrayInt & items ); + + teShuttleMode GetMode() { return mShuttleMode; }; }; #endif diff --git a/src/SseMathFuncs.h b/src/SseMathFuncs.h index 3625d88a2..d5cb91817 100644 --- a/src/SseMathFuncs.h +++ b/src/SseMathFuncs.h @@ -1,31 +1,31 @@ -/********************************************************************** - - Audacity: A Digital Audio Editor - - SseMathFuncs.h - - Stephen Moshier (wrote original C version, The Cephes Library) - Julien Pommier (converted to use SSE) - Andrew Hallendorff (adapted for Audacity) - -*******************************************************************//** - - \file SseMathfuncs.h - \brief SSE maths functions (for FFTs) - -*//****************************************************************/ - - -/* JKC: The trig functions use Taylor's series, on the range 0 to Pi/4 - * computing both Sin and Cos, and using one or the other (in the - * solo functions), or both in the more useful for us for FFTs sincos - * function. - * The constants minus_cephes_DP1 to minus_cephes_DP3 are used in the - * angle reduction modulo function. - * 4 sincos are done at a time. - * If we wanted to do just sin or just cos, we could speed things up - * by queuing up the Sines and Cosines into batches of 4 separately.*/ - +/********************************************************************** + + Audacity: A Digital Audio Editor + + SseMathFuncs.h + + Stephen Moshier (wrote original C version, The Cephes Library) + Julien Pommier (converted to use SSE) + Andrew Hallendorff (adapted for Audacity) + +*******************************************************************//** + + \file SseMathfuncs.h + \brief SSE maths functions (for FFTs) + +*//****************************************************************/ + + +/* JKC: The trig functions use Taylor's series, on the range 0 to Pi/4 + * computing both Sin and Cos, and using one or the other (in the + * solo functions), or both in the more useful for us for FFTs sincos + * function. + * The constants minus_cephes_DP1 to minus_cephes_DP3 are used in the + * angle reduction modulo function. + * 4 sincos are done at a time. + * If we wanted to do just sin or just cos, we could speed things up + * by queuing up the Sines and Cosines into batches of 4 separately.*/ + diff --git a/src/audacity.xml b/src/audacity.xml index aeb10787b..9af55f539 100644 --- a/src/audacity.xml +++ b/src/audacity.xml @@ -1,8 +1,8 @@ - - - - - Audacity project - - - + + + + + Audacity project + + + diff --git a/src/commands/CommandManager.cpp b/src/commands/CommandManager.cpp index 8628e73f2..6a7c5f766 100644 --- a/src/commands/CommandManager.cpp +++ b/src/commands/CommandManager.cpp @@ -1126,7 +1126,7 @@ bool CommandManager::HandleTextualCommand(wxString & Str, wxUint32 flags, wxUint { if (em.GetEffectByIdentifier(plug->GetID()).IsSameAs(Str)) { - return proj->OnEffect( ALL_EFFECTS | CONFIGURED_EFFECT, plug->GetID()); + return proj->OnEffect(plug->GetID(), AudacityProject::OnEffectFlags::kConfigured); } plug = pm.GetNextPlugin(PluginTypeEffect); } diff --git a/src/effects/Amplify.cpp b/src/effects/Amplify.cpp index 3a4d1a5e4..4e04a4995 100644 --- a/src/effects/Amplify.cpp +++ b/src/effects/Amplify.cpp @@ -10,329 +10,323 @@ *******************************************************************//** \class EffectAmplify -\brief An EffectSimpleMono +\brief An Effect This rewritten class supports a smart Amplify effect - it calculates the maximum amount of gain that can be applied to all tracks without causing clipping and selects this as the default parameter. -*//****************************************************************//** - -\class AmplifyDialog -\brief Dilaog used with EffectAmplify. - *//*******************************************************************/ #include "../Audacity.h" -#include "Amplify.h" - -#include "../ShuttleGui.h" -#include "../WaveTrack.h" - #include +#include + #include #include +#include #include #include #include #include +#include "../WaveTrack.h" +#include "../widgets/valnum.h" + +#include "Amplify.h" + +enum +{ + ID_Amp = 10000, + ID_Peak, + ID_Clip +}; + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( Ratio, float, wxTRANSLATE("Ratio"), 0.9f, -FLT_MAX, FLT_MAX, 1.0f ); +Param( NoClip, bool, wxTRANSLATE("Allow Clipping"), false, false, true, 1.0f ); +Param( Amp, float, wxT(""), -0.91515f, -50.0, 50.0, 10.0f ); + // // EffectAmplify // +BEGIN_EVENT_TABLE(EffectAmplify, wxEvtHandler) + EVT_SLIDER(ID_Amp, EffectAmplify::OnAmpSlider) + EVT_TEXT(ID_Amp, EffectAmplify::OnAmpText) + EVT_TEXT(ID_Peak, EffectAmplify::OnPeakText) + EVT_CHECKBOX(ID_Clip, EffectAmplify::OnClipCheckBox) +END_EVENT_TABLE() + EffectAmplify::EffectAmplify() { - ratio = float(1.0); - peak = float(0.0); + mAmp = DEF_Amp; + mNoClip = DEF_NoClip; + mRatio = pow(10.0, mAmp * 20.0); + mPeak = 0.0; } -wxString EffectAmplify::GetEffectDescription() { - // Note: This is useful only after ratio has been set. - return wxString::Format(_("Applied effect: %s %.1f dB"), - this->GetEffectName().c_str(), 20*log10(ratio)); +EffectAmplify::~EffectAmplify() +{ } +// IdentInterface implementation + +wxString EffectAmplify::GetSymbol() +{ + return AMPLIFY_PLUGIN_SYMBOL; +} + +wxString EffectAmplify::GetDescription() +{ + // Note: This is useful only after ratio has been set. + return wxTRANSLATE("Increases or decreases the volume of the audio you have selected"); +} + +// EffectIdentInterface implementation + +EffectType EffectAmplify::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +int EffectAmplify::GetAudioInCount() +{ + return 1; +} + +int EffectAmplify::GetAudioOutCount() +{ + return 1; +} + +sampleCount EffectAmplify::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) +{ + for (sampleCount i = 0; i < blockLen; i++) + { + outBlock[0][i] = inBlock[0][i] * mRatio; + } + + return blockLen; +} + +bool EffectAmplify::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.WriteFloat(KEY_Ratio, mRatio); + parms.Write(KEY_NoClip, !mNoClip); + + return true; +} + +bool EffectAmplify::SetAutomationParameters(EffectAutomationParameters & parms) +{ + float Ratio; + float def = pow(10.0, DEF_Amp / 20.0); + float min = pow(10.0, MIN_Amp / 20.0); + float max = pow(10.0, MAX_Amp / 20.0); + + if (!parms.ReadAndVerify(KEY_Ratio, &Ratio, def, min, max)) + { + return false; + } + ReadAndVerifyBool(NoClip); + + mRatio = Ratio; + mNoClip = !NoClip; + + return true; +} + +// Effect implementation + bool EffectAmplify::Init() { - peak = float(0.0); + mPeak = 0.0; SelectedTrackListOfKindIterator iter(Track::Wave, mTracks); - for (Track *t = iter.First(); t; t = iter.Next()) { + for (Track *t = iter.First(); t; t = iter.Next()) + { float min, max; ((WaveTrack *)t)->GetMinMax(&min, &max, mT0, mT1); float newpeak = (fabs(min) > fabs(max) ? fabs(min) : fabs(max)); - if (newpeak > peak) { - peak = newpeak; + if (newpeak > mPeak) + { + mPeak = newpeak; } } return true; } -bool EffectAmplify::TransferParameters( Shuttle & shuttle ) +void EffectAmplify::PopulateOrExchange(ShuttleGui & S) { - shuttle.TransferFloat( wxT("Ratio"), ratio, 0.9f ); - return true; -} - -bool EffectAmplify::PromptUser() -{ - AmplifyDialog dlog(this, mParent); - dlog.peak = peak; - if (peak > 0.0) - dlog.ratio = 1.0 / peak; - else - dlog.ratio = 1.0; - dlog.TransferDataToWindow(); - dlog.CenterOnParent(); - dlog.ShowModal(); - - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; - - ratio = dlog.ratio; - if (dlog.noclip && ratio*peak > 1.0) - ratio = 1.0 / peak; - - return true; -} - -bool EffectAmplify::ProcessSimpleMono(float *buffer, sampleCount len) -{ - sampleCount i; - for (i = 0; i < len; i++) - { - buffer[i] = (buffer[i] * ratio); - } - return true; -} - -//---------------------------------------------------------------------------- -// AmplifyDialog -//---------------------------------------------------------------------------- - -#define AMP_MIN -500 -#define AMP_MAX 500 - -#define ID_AMP_TEXT 10001 -#define ID_PEAK_TEXT 10002 -#define ID_AMP_SLIDER 10003 -#define ID_CLIP_CHECKBOX 10004 - -BEGIN_EVENT_TABLE(AmplifyDialog, EffectDialog) - EVT_SLIDER(ID_AMP_SLIDER, AmplifyDialog::OnAmpSlider) - EVT_TEXT(ID_AMP_TEXT, AmplifyDialog::OnAmpText) - EVT_TEXT(ID_PEAK_TEXT, AmplifyDialog::OnPeakText) - EVT_CHECKBOX(ID_CLIP_CHECKBOX, AmplifyDialog::OnClipCheckBox) - EVT_BUTTON(ID_EFFECT_PREVIEW, AmplifyDialog::OnPreview) -END_EVENT_TABLE() - -AmplifyDialog::AmplifyDialog(EffectAmplify *effect, - wxWindow * parent) -: EffectDialog(parent, _("Amplify"), PROCESS_EFFECT), - mEffect(effect) -{ - - ratio = float(1.0); - peak = float(0.0); - - Init(); -} - -void AmplifyDialog::PopulateOrExchange(ShuttleGui & S) -{ - wxTextValidator vld(wxFILTER_NUMERIC); - S.AddSpace(0, 5); - // Amplitude - S.StartMultiColumn(2, wxCENTER); + S.StartVerticalLay(0); { - mAmpT = S.Id(ID_AMP_TEXT).AddTextBox(_("Amplification (dB):"), - wxT(""), - 12); - mAmpT->SetValidator(vld); - } - S.EndMultiColumn(); + // Amplitude + S.StartMultiColumn(2, wxCENTER); + { + FloatingPointValidator vldAmp(1, &mAmp); + vldAmp.SetRange(MIN_Amp, MAX_Amp); + mAmpT = S.Id(ID_Amp).AddTextBox(_("Amplification (dB):"), wxT(""), 12); + mAmpT->SetValidator(vldAmp); + } + S.EndMultiColumn(); - // Amplitude - S.StartHorizontalLay(wxEXPAND); - { - S.SetStyle(wxSL_HORIZONTAL); - mAmpS = S.Id(ID_AMP_SLIDER).AddSlider(wxT(""), - 0, - AMP_MAX, - AMP_MIN); - mAmpS->SetName(_("Amplification dB")); - } - S.EndHorizontalLay(); + // Amplitude + S.StartHorizontalLay(wxEXPAND); + { + S.SetStyle(wxSL_HORIZONTAL); + mAmpS = S.Id(ID_Amp).AddSlider(wxT(""), 0, MAX_Amp * SCL_Amp, MIN_Amp * SCL_Amp); + mAmpS->SetName(_("Amplification dB")); + } + S.EndHorizontalLay(); - // Peek - S.StartMultiColumn(2, wxCENTER); - { - mPeakT = S.Id(ID_PEAK_TEXT).AddTextBox(_("New Peak Amplitude (dB):"), - wxT(""), - 12); - mPeakT->SetValidator(vld); - } - S.EndMultiColumn(); + // Peek + S.StartMultiColumn(2, wxCENTER); + { + FloatingPointValidator vldNewPeak(1, &mNewPeak); + vldNewPeak.SetRange(20.0 * log10(pow(10.0, MIN_Amp / 20.0) * mPeak), + 20.0 * log10(pow(10.0, MAX_Amp / 20.0) * mPeak)); + mNewPeakT = S.Id(ID_Peak).AddTextBox(_("New Peak Amplitude (dB):"), wxT(""), 12); + mNewPeakT->SetValidator(vldNewPeak); + } + S.EndMultiColumn(); + + // Clipping + S.StartHorizontalLay(wxCENTER); + { + mClip = S.Id(ID_Clip).AddCheckBox(_("Allow clipping"), wxT("false")); + } + S.EndHorizontalLay(); - // Clipping - S.StartHorizontalLay(wxCENTER); - { - mClip = S.Id(ID_CLIP_CHECKBOX).AddCheckBox(_("Allow clipping"), - wxT("false")); } - S.EndHorizontalLay(); + S.EndVerticalLay(); return; } -bool AmplifyDialog::TransferDataToWindow() +bool EffectAmplify::TransferDataToWindow() { + if (mPeak > 0.0) + mRatio = 1.0 / mPeak; + else + mRatio = 1.0; + // limit range of gain - double dB = TrapDouble(200*log10(ratio), AMP_MIN, AMP_MAX)/10.0; - ratio = pow(10.0, dB/20.0); + double dB = TrapDouble(20.0 * log10(mRatio), MIN_Amp, MAX_Amp); - mAmpS->SetValue((int)(200*log10(ratio)+0.5)); + mRatio = pow(10.0, dB / 20.0); - mAmpT->ChangeValue(wxString::Format(wxT("%.1f"), 20*log10(ratio))); + mAmp = 20.0 * log10(mRatio); + mAmpT->GetValidator()->TransferToWindow(); - wxString str; - if( ratio*peak > 0.0 ) - str.Printf(wxT("%.1f"), 20*log10(ratio*peak)); - else - str = _("-Infinity"); // the case when the waveform is all zero - mPeakT->ChangeValue(str); + mAmpS->SetValue((int) (mAmp * SCL_Amp + 0.5)); + + mNewPeak = 20.0 * log10(mRatio * mPeak); + mNewPeakT->GetValidator()->TransferToWindow(); + + CheckClip(); return true; } -bool AmplifyDialog::TransferDataFromWindow() +bool EffectAmplify::TransferDataFromWindow() { - wxString val = mAmpT->GetValue(); - double r; + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } - val.ToDouble(&r); - ratio = pow(10.0,TrapDouble(r*10, AMP_MIN, AMP_MAX)/200.0); + mRatio = pow(10.0, TrapDouble(mAmp, MIN_Amp, MAX_Amp) / 20.0); - noclip = !mClip->GetValue(); + mNoClip = !mClip->GetValue(); - return true; -} - -bool AmplifyDialog::Validate() -{ - TransferDataFromWindow(); - - if (mClip->GetValue() == false) { - if (ratio * peak > 1.0) - ratio = 1.0 / peak; + if (mNoClip && mRatio * mPeak > 1.0) + { + mRatio = 1.0 / mPeak; } return true; } -// handler implementations for AmplifyDialog -void AmplifyDialog::CheckClip() +// EffectAmplify implementation + +void EffectAmplify::CheckClip() { - wxButton *ok = (wxButton *) FindWindow(wxID_OK); + double peak = mRatio * mPeak; + EnableApply(mClip->GetValue() || (peak > 0.0 && peak <= 1.0)); +} - if (!mClip->GetValue() == true) { - ok->Enable(ratio * peak <= 1.0); +void EffectAmplify::OnAmpText(wxCommandEvent & WXUNUSED(evt)) +{ + if (!mAmpT->GetValidator()->TransferFromWindow()) + { + EnableApply(false); + return; } - else { - ok->Enable(true); + + mRatio = pow(10.0, TrapDouble(mAmp, MIN_Amp, MAX_Amp) / 20.0); + + mAmpS->SetValue((int) (mAmp * SCL_Amp + 0.5)); + + mNewPeak = 20.0 * log10(mRatio * mPeak); + mNewPeakT->GetValidator()->TransferToWindow(); + + CheckClip(); +} + +void EffectAmplify::OnPeakText(wxCommandEvent & WXUNUSED(evt)) +{ + if (!mNewPeakT->GetValidator()->TransferFromWindow()) + { + EnableApply(false); + return; } -} -void AmplifyDialog::OnAmpText(wxCommandEvent & WXUNUSED(event)) -{ - wxString val = mAmpT->GetValue(); - double r; + double r = pow(10.0, mNewPeak / 20.0) / mPeak; - val.ToDouble(&r); - ratio = pow(10.0,TrapDouble(r*10, AMP_MIN, AMP_MAX)/200.0); + mAmp = TrapDouble(20.0 * log10(r), MIN_Amp, MAX_Amp); + mRatio = pow(10.0, mAmp / 20.0); - mAmpS->SetValue((int)(200*log10(ratio)+0.5)); + mAmpT->GetValidator()->TransferToWindow(); - if( ratio*peak > 0.0 ) - val.Printf(wxT("%.1f"), 20*log10(ratio*peak)); - else - val = _("-Infinity"); // the case when the waveform is all zero - mPeakT->ChangeValue(val); + mAmpS->SetValue((int) (mAmp * SCL_Amp + 0.5)); CheckClip(); } -void AmplifyDialog::OnPeakText(wxCommandEvent & WXUNUSED(event)) +void EffectAmplify::OnAmpSlider(wxCommandEvent & evt) { - wxString val = mPeakT->GetValue(); - double r; + double dB = evt.GetInt() / SCL_Amp; + mRatio = pow(10.0, TrapDouble(dB, MIN_Amp, MAX_Amp) / 20.0); - val.ToDouble(&r); - ratio = pow(10.0, r/20.0) / peak; + double dB2 = (evt.GetInt() - 1) / SCL_Amp; + double ratio2 = pow(10.0, TrapDouble(dB2, MIN_Amp, MAX_Amp) / 20.0); - double dB = TrapDouble(200*log10(ratio), AMP_MIN, AMP_MAX)/10.0; - ratio = pow(10.0, dB/20.0); + if (!mClip->GetValue() && mRatio * mPeak > 1.0 && ratio2 * mPeak < 1.0) + { + mRatio = 1.0 / mPeak; + } - mAmpS->SetValue((int)(10*dB+0.5)); + mAmp = 20.0 * log10(mRatio); + mAmpT->GetValidator()->TransferToWindow(); - val.Printf(wxT("%.1f"), dB); - mAmpT->ChangeValue(val); + mNewPeak = 20.0 * log10(mRatio * mPeak); + mNewPeakT->GetValidator()->TransferToWindow(); CheckClip(); } -void AmplifyDialog::OnAmpSlider(wxCommandEvent & WXUNUSED(event)) -{ - wxString str; - - double dB = mAmpS->GetValue() / 10.0; - ratio = pow(10.0,TrapDouble(dB, AMP_MIN, AMP_MAX)/20.0); - - double dB2 = (mAmpS->GetValue()-1) / 10.0; - double ratio2 = pow(10.0,TrapDouble(dB2, AMP_MIN, AMP_MAX)/20.0); - - if (!mClip->GetValue() && ratio * peak > 1.0 && ratio2 * peak < 1.0) - ratio = 1.0 / peak; - - str.Printf(wxT("%.1f"), 20*log10(ratio)); - mAmpT->ChangeValue(str); - - if (ratio*peak > 0.0) - str.Printf(wxT("%.1f"), 20*log10(ratio*peak)); - else - str = _("-Infinity"); // the case when the waveform is all zero - mPeakT->ChangeValue(str); - - CheckClip(); -} - -void AmplifyDialog::OnClipCheckBox(wxCommandEvent & WXUNUSED(event)) +void EffectAmplify::OnClipCheckBox(wxCommandEvent & WXUNUSED(evt)) { CheckClip(); } - -void AmplifyDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - - // Save & restore parameters around Preview, because we didn't do OK. - float oldRatio = mEffect->ratio; - float oldPeak = mEffect->peak; - - mEffect->ratio = ratio; - if (noclip && ratio*peak > 1.0) - mEffect->ratio = 1.0 / peak; - mEffect->peak = peak; - - mEffect->Preview(); - - mEffect->ratio = oldRatio; - mEffect->peak = oldPeak; -} diff --git a/src/effects/Amplify.h b/src/effects/Amplify.h index fe488bf9b..cd1401efc 100644 --- a/src/effects/Amplify.h +++ b/src/effects/Amplify.h @@ -15,98 +15,71 @@ #ifndef __AUDACITY_EFFECT_AMPLIFY__ #define __AUDACITY_EFFECT_AMPLIFY__ -#include #include -#include -#include +#include #include +#include #include -#include "SimpleMono.h" +#include "../ShuttleGui.h" -class WaveTrack; +#include "Effect.h" -class EffectAmplify:public EffectSimpleMono +#define AMPLIFY_PLUGIN_SYMBOL wxTRANSLATE("Amplify") + +class EffectAmplify : public Effect { - friend class AmplifyDialog; - - public: +public: EffectAmplify(); + virtual ~EffectAmplify(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Amplify...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#AmplifierPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Amplify")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Amplifying")); - } + virtual EffectType GetType(); - // Useful only after PromptUser values have been set. - virtual wxString GetEffectDescription(); + // EffectClientInterface implementation + + virtual int GetAudioInCount(); + virtual int GetAudioOutCount(); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + + // Effect implementation virtual bool Init(); - - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); - - protected: - virtual bool ProcessSimpleMono(float *buffer, sampleCount len); - - private: - float ratio; - float peak; -}; - -//---------------------------------------------------------------------------- -// AmplifyDialog -//---------------------------------------------------------------------------- - -class AmplifyDialog:public EffectDialog -{ - public: - // constructors and destructors - AmplifyDialog(EffectAmplify *effect, wxWindow * parent); - - // method declarations - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - bool Validate(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); private: - // handlers - void OnAmpText(wxCommandEvent & event); - void OnPeakText(wxCommandEvent & event); - void OnAmpSlider(wxCommandEvent & event); - void OnClipCheckBox(wxCommandEvent & event); - void OnPreview( wxCommandEvent &event ); + // EffectAmplify implementation + void OnAmpText(wxCommandEvent & evt); + void OnPeakText(wxCommandEvent & evt); + void OnAmpSlider(wxCommandEvent & evt); + void OnClipCheckBox(wxCommandEvent & evt); void CheckClip(); - private: +private: + float mPeak; + + float mRatio; + float mAmp; + float mNewPeak; + bool mNoClip; + wxSlider *mAmpS; wxTextCtrl *mAmpT; - wxTextCtrl *mPeakT; + wxTextCtrl *mNewPeakT; wxCheckBox *mClip; DECLARE_EVENT_TABLE(); - - public: - EffectAmplify *mEffect; - - float ratio; - float peak; - bool noclip; }; #endif // __AUDACITY_EFFECT_AMPLIFY__ diff --git a/src/effects/AutoDuck.cpp b/src/effects/AutoDuck.cpp index dd6079a8a..acbd3e9f3 100644 --- a/src/effects/AutoDuck.cpp +++ b/src/effects/AutoDuck.cpp @@ -13,41 +13,43 @@ *******************************************************************/ +#include "../Audacity.h" + #include -#include -#include +#include + #include #include -#include +#include +#include +#include -#include "../Audacity.h" -#include "../Prefs.h" -#include "../Internat.h" -#include "../Theme.h" -#include "../AllThemeResources.h" #include "../AColor.h" +#include "../AllThemeResources.h" +#include "../Internat.h" +#include "../Prefs.h" +#include "../Theme.h" +#include "../widgets/valnum.h" #include "AutoDuck.h" -#include - -/* - * Default values for effect params - */ -#define PARAM_DEFAULT_DUCK_AMOUNT_DB -12.0 -#define PARAM_DEFAULT_OUTER_FADE_DOWN_LEN 0.5 -#define PARAM_DEFAULT_INNER_FADE_DOWN_LEN 0 -#define PARAM_DEFAULT_OUTER_FADE_UP_LEN 0.5 -#define PARAM_DEFAULT_INNER_FADE_UP_LEN 0 -#define PARAM_DEFAULT_THRESHOLD_DB -30.0 -#define PARAM_DEFAULT_MAXIMUM_PAUSE 1.0 +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( DuckAmountDb, double, wxTRANSLATE("DuckAmountDb"), -12.0, -24.0, 0.0, 1 ); +Param( InnerFadeDownLen, double, wxTRANSLATE("InnerFadeDownLen"), 0.0, 0.0, 3.0, 1 ); +Param( InnerFadeUpLen, double, wxTRANSLATE("InnerFadeUpLen"), 0.0, 0.0, 3.0, 1 ); +Param( OuterFadeDownLen, double, wxTRANSLATE("OuterFadeDownLen"), 0.5, 0.0, 3.0, 1 ); +Param( OuterFadeUpLen, double, wxTRANSLATE("OuterFadeUpLen"), 0.5, 0.0, 3.0, 1 ); +Param( ThresholdDb, double, wxTRANSLATE("ThresholdDb"), -30.0, -100.0, 0.0, 1 ); +Param( MaximumPause, double, wxTRANSLATE("MaximumPause"), 1.0, 0.0, DBL_MAX, 1 ); /* * Common constants */ -#define BUF_SIZE 131072 // number of samples to process at once -#define RMS_WINDOW_SIZE 100 // samples in circular RMS window buffer +static const int kBufSize = 131072; // number of samples to process at once +static const int kRMSWindowSize = 100; // samples in circular RMS window buffer /* * A auto duck region and an array of auto duck regions @@ -65,6 +67,8 @@ struct AutoDuckRegion double t1; }; +#include + WX_DECLARE_OBJARRAY(AutoDuckRegion, AutoDuckRegionArray); WX_DEFINE_OBJARRAY(AutoDuckRegionArray); @@ -72,45 +76,121 @@ WX_DEFINE_OBJARRAY(AutoDuckRegionArray); * Effect implementation */ +BEGIN_EVENT_TABLE(EffectAutoDuck, wxEvtHandler) + EVT_TEXT(wxID_ANY, EffectAutoDuck::OnValueChanged) +END_EVENT_TABLE() + EffectAutoDuck::EffectAutoDuck() { - SetEffectFlags(BUILTIN_EFFECT | PROCESS_EFFECT | ADVANCED_EFFECT); - - gPrefs->Read(wxT("/Effects/AutoDuck/DuckAmountDb"), - &mDuckAmountDb, PARAM_DEFAULT_DUCK_AMOUNT_DB); - gPrefs->Read(wxT("/Effects/AutoDuck/InnerFadeDownLen"), - &mInnerFadeDownLen, PARAM_DEFAULT_INNER_FADE_DOWN_LEN); - gPrefs->Read(wxT("/Effects/AutoDuck/InnerFadeUpLen"), - &mInnerFadeUpLen, PARAM_DEFAULT_INNER_FADE_UP_LEN); - gPrefs->Read(wxT("/Effects/AutoDuck/OuterFadeDownLen"), - &mOuterFadeDownLen, PARAM_DEFAULT_OUTER_FADE_DOWN_LEN); - gPrefs->Read(wxT("/Effects/AutoDuck/OuterFadeUpLen"), - &mOuterFadeUpLen, PARAM_DEFAULT_OUTER_FADE_UP_LEN); - gPrefs->Read(wxT("/Effects/AutoDuck/ThresholdDb"), - &mThresholdDb, PARAM_DEFAULT_THRESHOLD_DB); - gPrefs->Read(wxT("/Effects/AutoDuck/MaximumPause"), - &mMaximumPause, PARAM_DEFAULT_MAXIMUM_PAUSE); + mDuckAmountDb = DEF_DuckAmountDb; + mInnerFadeDownLen = DEF_InnerFadeDownLen; + mInnerFadeUpLen = DEF_InnerFadeUpLen; + mOuterFadeDownLen = DEF_OuterFadeDownLen; + mOuterFadeUpLen = DEF_OuterFadeUpLen; + mThresholdDb = DEF_ThresholdDb; + mMaximumPause = DEF_MaximumPause; mControlTrack = NULL; + + mPanel = NULL; +} + +EffectAutoDuck::~EffectAutoDuck() +{ +} + +// IdentInterface implementation + +wxString EffectAutoDuck::GetSymbol() +{ + return AUTODUCK_PLUGIN_SYMBOL; +} + +wxString EffectAutoDuck::GetDescription() +{ + return wxTRANSLATE("Reduces (ducks) the volume of one or more tracks whenever the volume of a specified \"control\" track reaches a particular level"); +} + +// EffectIdentInterface implementation + +EffectType EffectAutoDuck::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +bool EffectAutoDuck::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_DuckAmountDb, mDuckAmountDb); + parms.Write(KEY_InnerFadeDownLen, mInnerFadeDownLen); + parms.Write(KEY_InnerFadeUpLen, mInnerFadeUpLen); + parms.Write(KEY_OuterFadeDownLen, mOuterFadeDownLen); + parms.Write(KEY_OuterFadeUpLen, mOuterFadeUpLen); + parms.Write(KEY_ThresholdDb, mThresholdDb); + parms.Write(KEY_MaximumPause, mMaximumPause); + + return true; +} + +bool EffectAutoDuck::SetAutomationParameters(EffectAutomationParameters & parms) +{ + ReadAndVerifyDouble(DuckAmountDb); + ReadAndVerifyDouble(InnerFadeDownLen); + ReadAndVerifyDouble(InnerFadeUpLen); + ReadAndVerifyDouble(OuterFadeDownLen); + ReadAndVerifyDouble(OuterFadeUpLen); + ReadAndVerifyDouble(ThresholdDb); + ReadAndVerifyDouble(MaximumPause); + + mDuckAmountDb = DuckAmountDb; + mInnerFadeDownLen = InnerFadeDownLen; + mInnerFadeUpLen = InnerFadeUpLen; + mOuterFadeDownLen = OuterFadeDownLen; + mOuterFadeUpLen = OuterFadeUpLen; + mThresholdDb = ThresholdDb; + mMaximumPause = MaximumPause; + + return true; +} + +// Effect implementation + +bool EffectAutoDuck::Startup() +{ + wxString base = wxT("/Effects/AutoDuck/"); + + // Migrate settings from 2.1.0 or before + + // Already migrated, so bail + if (gPrefs->Exists(base + wxT("Migrated"))) + { + return true; + } + + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + gPrefs->Read(base + wxT("DuckAmountDb"), &mDuckAmountDb, DEF_DuckAmountDb); + gPrefs->Read(base + wxT("InnerFadeDownLen"), &mInnerFadeDownLen, DEF_InnerFadeDownLen); + gPrefs->Read(base + wxT("InnerFadeUpLen"), &mInnerFadeUpLen, DEF_InnerFadeUpLen); + gPrefs->Read(base + wxT("OuterFadeDownLen"), &mOuterFadeDownLen, DEF_OuterFadeDownLen); + gPrefs->Read(base + wxT("OuterFadeUpLen"), &mOuterFadeUpLen, DEF_OuterFadeUpLen); + gPrefs->Read(base + wxT("ThresholdDb"), &mThresholdDb, DEF_ThresholdDb); + gPrefs->Read(base + wxT("MaximumPause"), &mMaximumPause, DEF_MaximumPause); + + SaveUserPreset(GetCurrentSettingsGroup()); + + // Do not migrate again + gPrefs->Write(base + wxT("Migrated"), true); + gPrefs->Flush(); + } + + return true; } bool EffectAutoDuck::Init() { - gPrefs->Read(wxT("/Effects/AutoDuck/DuckAmountDb"), - &mDuckAmountDb, PARAM_DEFAULT_DUCK_AMOUNT_DB); - gPrefs->Read(wxT("/Effects/AutoDuck/InnerFadeDownLen"), - &mInnerFadeDownLen, PARAM_DEFAULT_INNER_FADE_DOWN_LEN); - gPrefs->Read(wxT("/Effects/AutoDuck/InnerFadeUpLen"), - &mInnerFadeUpLen, PARAM_DEFAULT_INNER_FADE_UP_LEN); - gPrefs->Read(wxT("/Effects/AutoDuck/OuterFadeDownLen"), - &mOuterFadeDownLen, PARAM_DEFAULT_OUTER_FADE_DOWN_LEN); - gPrefs->Read(wxT("/Effects/AutoDuck/OuterFadeUpLen"), - &mOuterFadeUpLen, PARAM_DEFAULT_OUTER_FADE_UP_LEN); - gPrefs->Read(wxT("/Effects/AutoDuck/ThresholdDb"), - &mThresholdDb, PARAM_DEFAULT_THRESHOLD_DB); - gPrefs->Read(wxT("/Effects/AutoDuck/MaximumPause"), - &mMaximumPause, PARAM_DEFAULT_MAXIMUM_PAUSE); - mControlTrack = NULL; TrackListIterator iter(mTracks); @@ -142,7 +222,7 @@ bool EffectAutoDuck::Init() /* i18n-hint: Auto duck is the name of an effect that 'ducks' (reduces the volume) * of the audio automatically when there is sound on another track. Not as * in 'Donald-Duck'!*/ - _("Auto Duck"), wxICON_ERROR, mParent); + _("Auto Duck"), wxICON_ERROR, mUIParent); return false; } } @@ -154,7 +234,7 @@ bool EffectAutoDuck::Init() { wxMessageBox( _("Auto Duck needs a control track which must be placed below the selected track(s)."), - _("Auto Duck"), wxICON_ERROR, mParent); + _("Auto Duck"), wxICON_ERROR, mUIParent); return false; } @@ -163,55 +243,11 @@ bool EffectAutoDuck::Init() return true; } -bool EffectAutoDuck::TransferParameters( Shuttle & shuttle ) -{ - shuttle.TransferDouble(wxT("DuckAmountDb"), mDuckAmountDb, - PARAM_DEFAULT_DUCK_AMOUNT_DB); - shuttle.TransferDouble(wxT("InnerFadeDownLen"), mInnerFadeDownLen, - PARAM_DEFAULT_INNER_FADE_DOWN_LEN); - shuttle.TransferDouble(wxT("InnerFadeUpLen"), mInnerFadeUpLen, - PARAM_DEFAULT_INNER_FADE_UP_LEN); - shuttle.TransferDouble(wxT("OuterFadeDownLen"), mOuterFadeDownLen, - PARAM_DEFAULT_OUTER_FADE_DOWN_LEN); - shuttle.TransferDouble(wxT("OuterFadeUpLen"), mOuterFadeUpLen, - PARAM_DEFAULT_OUTER_FADE_UP_LEN); - shuttle.TransferDouble(wxT("ThresholdDb"), mThresholdDb, - PARAM_DEFAULT_THRESHOLD_DB); - shuttle.TransferDouble(wxT("MaximumPause"), mMaximumPause, - PARAM_DEFAULT_MAXIMUM_PAUSE); - - return true; -} - -bool EffectAutoDuck::CheckWhetherSkipEffect() -{ - return false; -} - void EffectAutoDuck::End() { mControlTrack = NULL; } -bool EffectAutoDuck::PromptUser() -{ - EffectAutoDuckDialog dlog(this, mParent); - - if (dlog.ShowModal() != wxID_OK) - return false; // user cancelled dialog - - gPrefs->Write(wxT("/Effects/AutoDuck/DuckAmountDb"), mDuckAmountDb); - gPrefs->Write(wxT("/Effects/AutoDuck/OuterFadeDownLen"), mOuterFadeDownLen); - gPrefs->Write(wxT("/Effects/AutoDuck/OuterFadeUpLen"), mOuterFadeUpLen); - gPrefs->Write(wxT("/Effects/AutoDuck/InnerFadeDownLen"), mInnerFadeDownLen); - gPrefs->Write(wxT("/Effects/AutoDuck/InnerFadeUpLen"), mInnerFadeUpLen); - gPrefs->Write(wxT("/Effects/AutoDuck/ThresholdDb"), mThresholdDb); - gPrefs->Write(wxT("/Effects/AutoDuck/MaximumPause"), mMaximumPause); - gPrefs->Flush(); - - return true; -} - bool EffectAutoDuck::Process() { sampleCount i; @@ -243,15 +279,15 @@ bool EffectAutoDuck::Process() double threshold = pow(10.0, mThresholdDb/20); // adjust the threshold so we can compare it to the rmsSum value - threshold = threshold * threshold * RMS_WINDOW_SIZE; + threshold = threshold * threshold * kRMSWindowSize; int rmsPos = 0; float rmsSum = 0; - float *rmsWindow = new float[RMS_WINDOW_SIZE]; - for (i = 0; i < RMS_WINDOW_SIZE; i++) + float *rmsWindow = new float[kRMSWindowSize]; + for (i = 0; i < kRMSWindowSize; i++) rmsWindow[i] = 0; - float *buf = new float[BUF_SIZE]; + float *buf = new float[kBufSize]; bool inDuckRegion = false; @@ -267,8 +303,8 @@ bool EffectAutoDuck::Process() while (pos < end) { sampleCount len = end - pos; - if (len > BUF_SIZE) - len = BUF_SIZE; + if (len > kBufSize) + len = kBufSize; mControlTrack->Get((samplePtr)buf, floatSample, pos, (sampleCount)len); @@ -277,7 +313,7 @@ bool EffectAutoDuck::Process() rmsSum -= rmsWindow[rmsPos]; rmsWindow[rmsPos] = buf[i - pos] * buf[i - pos]; rmsSum += rmsWindow[rmsPos]; - rmsPos = (rmsPos + 1) % RMS_WINDOW_SIZE; + rmsPos = (rmsPos + 1) % kRMSWindowSize; bool thresholdExceeded = rmsSum > threshold; @@ -343,7 +379,7 @@ bool EffectAutoDuck::Process() if (!cancel) { - this->CopyInputTracks(); // Set up mOutputTracks. + CopyInputTracks(); // Set up mOutputTracks. SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks); Track *iterTrack = iter.First(); @@ -373,10 +409,100 @@ bool EffectAutoDuck::Process() } } - this->ReplaceProcessedTracks(!cancel); + ReplaceProcessedTracks(!cancel); return !cancel; } +void EffectAutoDuck::PopulateOrExchange(ShuttleGui & S) +{ + S.SetBorder(5); + S.StartVerticalLay(true); + { + S.AddSpace(0, 5); + + mPanel = new EffectAutoDuckPanel(S.GetParent(), this); + S.AddWindow(mPanel); + + S.AddSpace(0, 5); + + S.StartMultiColumn(6, wxCENTER); + { + FloatingPointValidator vldDuckAmountDb(1, &mDuckAmountDb, NUM_VAL_NO_TRAILING_ZEROES); + vldDuckAmountDb.SetRange(MIN_DuckAmountDb, MAX_DuckAmountDb); + mDuckAmountDbBox = S.AddTextBox(_("Duck amount:"), wxT(""), 10); + mDuckAmountDbBox->SetValidator(vldDuckAmountDb); + S.AddUnits(_("dB")); + + FloatingPointValidator vldMaximumPause(2, &mMaximumPause, NUM_VAL_NO_TRAILING_ZEROES); + vldMaximumPause.SetRange(MIN_MaximumPause, MAX_MaximumPause); + mMaximumPauseBox = S.AddTextBox(_("Maximum pause:"), wxT(""), 10); + mMaximumPauseBox->SetValidator(vldMaximumPause); + S.AddUnits(_("seconds")); + + FloatingPointValidator vldOuterFadeDownLen(2, &mOuterFadeDownLen, NUM_VAL_NO_TRAILING_ZEROES); + vldOuterFadeDownLen.SetRange(MIN_OuterFadeDownLen, MAX_OuterFadeDownLen); + mOuterFadeDownLenBox = S.AddTextBox(_("Outer fade down length:"), wxT(""), 10); + mOuterFadeDownLenBox->SetValidator(vldOuterFadeDownLen); + S.AddUnits(_("seconds")); + + FloatingPointValidator vldOuterFadeUpLen(2, &mOuterFadeUpLen, NUM_VAL_NO_TRAILING_ZEROES); + vldOuterFadeUpLen.SetRange(MIN_OuterFadeUpLen, MAX_OuterFadeUpLen); + mOuterFadeUpLenBox = S.AddTextBox(_("Outer fade up length:"), wxT(""), 10); + mOuterFadeUpLenBox->SetValidator(vldOuterFadeUpLen); + S.AddUnits(_("seconds")); + + FloatingPointValidator vldInnerFadeDownLen(2, &mInnerFadeDownLen, NUM_VAL_NO_TRAILING_ZEROES); + vldInnerFadeDownLen.SetRange(MIN_InnerFadeDownLen, MAX_InnerFadeDownLen); + mInnerFadeDownLenBox = S.AddTextBox(_("Inner fade down length:"), wxT(""), 10); + mInnerFadeDownLenBox->SetValidator(vldInnerFadeDownLen); + S.AddUnits(_("seconds")); + + FloatingPointValidator vldInnerFadeUpLen(2, &mInnerFadeUpLen, NUM_VAL_NO_TRAILING_ZEROES); + vldInnerFadeUpLen.SetRange(MIN_InnerFadeUpLen, MAX_InnerFadeUpLen); + mInnerFadeUpLenBox = S.AddTextBox(_("Inner fade up length:"), wxT(""), 10); + mInnerFadeUpLenBox->SetValidator(vldInnerFadeUpLen); + S.AddUnits(_("seconds")); + } + S.EndMultiColumn(); + + S.StartMultiColumn(3, wxCENTER); + { + FloatingPointValidator vldThresholdDb(2, &mThresholdDb, NUM_VAL_NO_TRAILING_ZEROES); + vldThresholdDb.SetRange(MIN_ThresholdDb, MAX_ThresholdDb); + mThresholdDbBox = S.AddTextBox(_("Threshold:"), wxT(""), 10); + mThresholdDbBox->SetValidator(vldThresholdDb); + S.AddUnits(_("dB")); + } + S.EndMultiColumn(); + + } + S.EndVerticalLay(); + + return; +} + +bool EffectAutoDuck::TransferDataToWindow() +{ + if (!mUIParent->TransferDataToWindow()) + { + return false; + } + + return true; +} + +bool EffectAutoDuck::TransferDataFromWindow() +{ + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } + + return true; +} + +// EffectAutoDuck implementation + // this currently does an exponential fade bool EffectAutoDuck::ApplyDuckFade(int trackNumber, WaveTrack* t, double t0, double t1) @@ -386,7 +512,7 @@ bool EffectAutoDuck::ApplyDuckFade(int trackNumber, WaveTrack* t, sampleCount start = t->TimeToLongSamples(t0); sampleCount end = t->TimeToLongSamples(t1); - float *buf = new float[BUF_SIZE]; + float *buf = new float[kBufSize]; sampleCount pos = start; int fadeDownSamples = t->TimeToLongSamples( @@ -405,8 +531,8 @@ bool EffectAutoDuck::ApplyDuckFade(int trackNumber, WaveTrack* t, while (pos < end) { sampleCount len = end - pos; - if (len > BUF_SIZE) - len = BUF_SIZE; + if (len > kBufSize) + len = kBufSize; t->Get((samplePtr)buf, floatSample, pos, len); @@ -444,171 +570,13 @@ bool EffectAutoDuck::ApplyDuckFade(int trackNumber, WaveTrack* t, return cancel; } -/* - * Effect dialog implementation - */ - -#define ID_DUCK_AMOUNT_DB 10001 -#define ID_THRESHOLD_DB 10002 -#define ID_INNER_FADE_DOWN_LEN 10003 -#define ID_INNER_FADE_UP_LEN 10004 -#define ID_OUTER_FADE_DOWN_LEN 10005 -#define ID_OUTER_FADE_UP_LEN 10006 -#define ID_MAXIMUM_PAUSE 10007 -#define ID_PANEL 10008 - -BEGIN_EVENT_TABLE(EffectAutoDuckDialog, wxDialog) - EVT_BUTTON(wxID_OK, EffectAutoDuckDialog::OnOk) - EVT_BUTTON(wxID_CANCEL, EffectAutoDuckDialog::OnCancel) - EVT_TEXT(ID_DUCK_AMOUNT_DB, EffectAutoDuckDialog::OnValueChanged) - EVT_TEXT(ID_THRESHOLD_DB, EffectAutoDuckDialog::OnValueChanged) - EVT_TEXT(ID_INNER_FADE_DOWN_LEN, EffectAutoDuckDialog::OnValueChanged) - EVT_TEXT(ID_INNER_FADE_UP_LEN, EffectAutoDuckDialog::OnValueChanged) - EVT_TEXT(ID_OUTER_FADE_DOWN_LEN, EffectAutoDuckDialog::OnValueChanged) - EVT_TEXT(ID_OUTER_FADE_UP_LEN, EffectAutoDuckDialog::OnValueChanged) - EVT_TEXT(ID_MAXIMUM_PAUSE, EffectAutoDuckDialog::OnValueChanged) -END_EVENT_TABLE() - -EffectAutoDuckDialog::EffectAutoDuckDialog(EffectAutoDuck* effect, - wxWindow *parent) : wxDialog(parent, -1, _("Auto Duck"), - wxDefaultPosition, wxDefaultSize) -{ - mEffect = effect; - wxTextValidator vld(wxFILTER_NUMERIC); - - ShuttleGui S(this, eIsCreating); - - S.SetBorder(5); - S.StartVerticalLay(true); - { - S.AddSpace(0, 5); - - mPanel = (EffectAutoDuckPanel*) - S.AddWindow(new EffectAutoDuckPanel(this, ID_PANEL)); - - S.AddSpace(0, 5); - - S.StartMultiColumn(6, wxCENTER); - { - mDuckAmountDbBox = S.Id(ID_DUCK_AMOUNT_DB).AddTextBox( - _("Duck amount:"), - Internat::ToDisplayString(mEffect->mDuckAmountDb), 10); - S.AddUnits(_("dB")); - mDuckAmountDbBox->SetValidator(vld); - - mMaximumPauseBox = S.Id(ID_MAXIMUM_PAUSE).AddTextBox( - _("Maximum pause:"), - Internat::ToDisplayString(mEffect->mMaximumPause), 10); - S.AddUnits(_("seconds")); - mMaximumPauseBox->SetValidator(vld); - - mOuterFadeDownLenBox = S.Id(ID_OUTER_FADE_DOWN_LEN).AddTextBox( - _("Outer fade down length:"), - Internat::ToDisplayString(mEffect->mOuterFadeDownLen), 10); - S.AddUnits(_("seconds")); - mOuterFadeDownLenBox->SetValidator(vld); - - mOuterFadeUpLenBox = S.Id(ID_OUTER_FADE_UP_LEN).AddTextBox( - _("Outer fade up length:"), - Internat::ToDisplayString(mEffect->mOuterFadeUpLen), 10); - S.AddUnits(_("seconds")); - mOuterFadeUpLenBox->SetValidator(vld); - - mInnerFadeDownLenBox = S.Id(ID_INNER_FADE_DOWN_LEN).AddTextBox( - _("Inner fade down length:"), - Internat::ToDisplayString(mEffect->mInnerFadeDownLen), 10); - S.AddUnits(_("seconds")); - mInnerFadeDownLenBox->SetValidator(vld); - - mInnerFadeUpLenBox = S.Id(ID_INNER_FADE_UP_LEN).AddTextBox( - _("Inner fade up length:"), - Internat::ToDisplayString(mEffect->mInnerFadeUpLen), 10); - S.AddUnits(_("seconds")); - mInnerFadeUpLenBox->SetValidator(vld); - } - S.EndMultiColumn(); - - S.StartMultiColumn(3, wxCENTER); - { - mThresholdDbBox = S.Id(ID_THRESHOLD_DB).AddTextBox( - _("Threshold:"), - Internat::ToDisplayString(mEffect->mThresholdDb), 10); - S.AddUnits(_("dB")); - mThresholdDbBox->SetValidator(vld); - } - S.EndMultiColumn(); - - } - S.EndVerticalLay(); - - S.AddStandardButtons(); - - Layout(); - Fit(); - SetMinSize(GetSize()); - Center(); -} - -void EffectAutoDuckDialog::OnOk(wxCommandEvent& WXUNUSED(event)) -{ - double duckAmountDb = 0, thresholdDb = 0; - double innerFadeDownLen = 0, innerFadeUpLen = 0; - double outerFadeDownLen = 0, outerFadeUpLen = 0; - double maximumPause = 0; - - bool success = - mDuckAmountDbBox->GetValue().ToDouble(&duckAmountDb) && - duckAmountDb > -100 && - duckAmountDb < 0 && - mThresholdDbBox->GetValue().ToDouble(&thresholdDb) && - thresholdDb > -100 && - thresholdDb < 0 && - mInnerFadeDownLenBox->GetValue().ToDouble(&innerFadeDownLen) && - innerFadeDownLen >= 0 && - innerFadeDownLen < 1000 && - mInnerFadeUpLenBox->GetValue().ToDouble(&innerFadeUpLen) && - innerFadeUpLen >= 0 && - innerFadeUpLen < 1000 && - mOuterFadeDownLenBox->GetValue().ToDouble(&outerFadeDownLen) && - outerFadeDownLen >= 0 && - outerFadeDownLen < 1000 && - mOuterFadeUpLenBox->GetValue().ToDouble(&outerFadeUpLen) && - outerFadeUpLen >= 0 && - outerFadeUpLen < 1000 && - mMaximumPauseBox->GetValue().ToDouble(&maximumPause) && - maximumPause >= 0 && - maximumPause < 1000; - - if (!success) - { - wxMessageBox(_("Please enter valid values."), _("Auto Duck"), - wxICON_ERROR, this); - return; - } - - mEffect->mDuckAmountDb = duckAmountDb; - mEffect->mThresholdDb = thresholdDb; - mEffect->mInnerFadeDownLen = innerFadeDownLen; - mEffect->mInnerFadeUpLen = innerFadeUpLen; - mEffect->mOuterFadeDownLen = outerFadeDownLen; - mEffect->mOuterFadeUpLen = outerFadeUpLen; - mEffect->mMaximumPause = maximumPause; - - EndModal(wxID_OK); -} - -void EffectAutoDuckDialog::OnCancel(wxCommandEvent& WXUNUSED(event)) -{ - EndModal(wxID_CANCEL); -} - -void EffectAutoDuckDialog::OnValueChanged(wxCommandEvent& WXUNUSED(event)) +void EffectAutoDuck::OnValueChanged(wxCommandEvent & WXUNUSED(evt)) { mPanel->Refresh(false); } /* - * Effect dialog panel implementation + * EffectAutoDuckPanel implementation */ #define CONTROL_POINT_REGION 10 // pixel distance to click on a control point @@ -620,12 +588,6 @@ void EffectAutoDuckDialog::OnValueChanged(wxCommandEvent& WXUNUSED(event)) #define FADE_UP_START 450 // x coordinate #define DUCK_AMOUNT_START 50 // y coordinate -#define MAX_DUCK_AMOUNT 0 // db -#define MIN_DUCK_AMOUNT -24 // db - -#define MIN_FADE 0 // seconds -#define MAX_FADE 3 // seconds - #define FADE_SCALE 40 // scale factor for second -> pixel conversion #define DUCK_AMOUNT_SCALE 8 // scale factor for db -> pixel conversion @@ -648,10 +610,11 @@ BEGIN_EVENT_TABLE(EffectAutoDuckPanel, wxPanel) EVT_MOTION(EffectAutoDuckPanel::OnMotion) END_EVENT_TABLE() -EffectAutoDuckPanel::EffectAutoDuckPanel(EffectAutoDuckDialog* parent, - wxWindowID id) : wxPanel(parent, id, wxDefaultPosition, wxSize(600, 300)) +EffectAutoDuckPanel::EffectAutoDuckPanel(wxWindow *parent, EffectAutoDuck *effect) +: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(600, 300)) { mParent = parent; + mEffect = effect; mCurrentControlPoint = none; mBackgroundBitmap = NULL; @@ -673,7 +636,7 @@ void EffectAutoDuckPanel::ResetControlPoints() mControlPoints[duckAmount] = wxPoint(-100,-100); } -void EffectAutoDuckPanel::OnPaint(wxPaintEvent& WXUNUSED(event)) +void EffectAutoDuckPanel::OnPaint(wxPaintEvent & WXUNUSED(evt)) { int clientWidth, clientHeight; GetSize(&clientWidth, &clientHeight); @@ -703,17 +666,17 @@ void EffectAutoDuckPanel::OnPaint(wxPaintEvent& WXUNUSED(event)) double innerFadeUpLen = 0; double outerFadeDownLen = 0; double outerFadeUpLen = 0; - mParent->mDuckAmountDbBox->GetValue().ToDouble(&duckAmountDb); - mParent->mInnerFadeDownLenBox->GetValue().ToDouble(&innerFadeDownLen); - mParent->mInnerFadeUpLenBox->GetValue().ToDouble(&innerFadeUpLen); - mParent->mOuterFadeDownLenBox->GetValue().ToDouble(&outerFadeDownLen); - mParent->mOuterFadeUpLenBox->GetValue().ToDouble(&outerFadeUpLen); + mEffect->mDuckAmountDbBox->GetValue().ToDouble(&duckAmountDb); + mEffect->mInnerFadeDownLenBox->GetValue().ToDouble(&innerFadeDownLen); + mEffect->mInnerFadeUpLenBox->GetValue().ToDouble(&innerFadeUpLen); + mEffect->mOuterFadeDownLenBox->GetValue().ToDouble(&outerFadeDownLen); + mEffect->mOuterFadeUpLenBox->GetValue().ToDouble(&outerFadeUpLen); - if (innerFadeDownLen < MIN_FADE || innerFadeDownLen > MAX_FADE || - innerFadeUpLen < MIN_FADE || innerFadeUpLen > MAX_FADE || - outerFadeDownLen < MIN_FADE || outerFadeDownLen > MAX_FADE || - outerFadeUpLen < MIN_FADE || outerFadeUpLen > MAX_FADE || - duckAmountDb < MIN_DUCK_AMOUNT || duckAmountDb > MAX_DUCK_AMOUNT) + if (innerFadeDownLen < MIN_InnerFadeDownLen || innerFadeDownLen > MAX_InnerFadeDownLen || + innerFadeUpLen < MIN_InnerFadeUpLen || innerFadeUpLen > MAX_InnerFadeUpLen || + outerFadeDownLen < MIN_OuterFadeDownLen || outerFadeDownLen > MAX_OuterFadeDownLen || + outerFadeUpLen < MIN_OuterFadeUpLen || outerFadeUpLen > MAX_OuterFadeUpLen || + duckAmountDb < MIN_DuckAmountDb || duckAmountDb > MAX_DuckAmountDb) { // values are out of range, no preview available wxString message = wxString::Format(_("Preview not available")); @@ -839,14 +802,14 @@ void EffectAutoDuckPanel::OnPaint(wxPaintEvent& WXUNUSED(event)) } void EffectAutoDuckPanel::OnMouseCaptureChanged( - wxMouseCaptureChangedEvent& WXUNUSED(event)) + wxMouseCaptureChangedEvent & WXUNUSED(evt)) { SetCursor(wxNullCursor); mCurrentControlPoint = none; } void EffectAutoDuckPanel::OnMouseCaptureLost( - wxMouseCaptureLostEvent& WXUNUSED(event)) + wxMouseCaptureLostEvent & WXUNUSED(evt)) { mCurrentControlPoint = none; @@ -857,7 +820,7 @@ void EffectAutoDuckPanel::OnMouseCaptureLost( } EffectAutoDuckPanel::EControlPoint - EffectAutoDuckPanel::GetNearestControlPoint(const wxPoint& pt) + EffectAutoDuckPanel::GetNearestControlPoint(const wxPoint & pt) { int dist[AUTO_DUCK_PANEL_NUM_CONTROL_POINTS]; int i; @@ -876,7 +839,7 @@ EffectAutoDuckPanel::EControlPoint return none; } -void EffectAutoDuckPanel::OnLeftDown(wxMouseEvent &evt) +void EffectAutoDuckPanel::OnLeftDown(wxMouseEvent & evt) { EControlPoint nearest = GetNearestControlPoint(evt.GetPosition()); @@ -895,7 +858,7 @@ void EffectAutoDuckPanel::OnLeftDown(wxMouseEvent &evt) } } -void EffectAutoDuckPanel::OnLeftUp(wxMouseEvent& WXUNUSED(event)) +void EffectAutoDuckPanel::OnLeftUp(wxMouseEvent & WXUNUSED(evt)) { if (mCurrentControlPoint != none) { @@ -904,7 +867,7 @@ void EffectAutoDuckPanel::OnLeftUp(wxMouseEvent& WXUNUSED(event)) } } -void EffectAutoDuckPanel::OnMotion(wxMouseEvent &evt) +void EffectAutoDuckPanel::OnMotion(wxMouseEvent & evt) { switch (GetNearestControlPoint(evt.GetPosition())) { @@ -945,54 +908,28 @@ void EffectAutoDuckPanel::OnMotion(wxMouseEvent &evt) { case outerFadeDown: newValue = ((double)(FADE_DOWN_START - evt.GetX())) / FADE_SCALE; - if (newValue < MIN_FADE) - newValue = MIN_FADE; - if (newValue > MAX_FADE) - newValue = MAX_FADE; - mParent->mOuterFadeDownLenBox->SetValue( - Internat::ToDisplayString(newValue)); + mEffect->mOuterFadeDownLen = TrapDouble(newValue, MIN_OuterFadeDownLen, MAX_OuterFadeDownLen); break; case outerFadeUp: newValue = ((double)(evt.GetX() - FADE_UP_START)) / FADE_SCALE; - if (newValue < MIN_FADE) - newValue = MIN_FADE; - if (newValue > MAX_FADE) - newValue = MAX_FADE; - mParent->mOuterFadeUpLenBox->SetValue( - Internat::ToDisplayString(newValue)); + mEffect->mOuterFadeUpLen = TrapDouble(newValue, MIN_OuterFadeUpLen, MAX_OuterFadeUpLen); break; case innerFadeDown: newValue = ((double)(evt.GetX() - FADE_DOWN_START)) / FADE_SCALE; - if (newValue < MIN_FADE) - newValue = MIN_FADE; - if (newValue > MAX_FADE) - newValue = MAX_FADE; - mParent->mInnerFadeDownLenBox->SetValue( - Internat::ToDisplayString(newValue)); + mEffect->mInnerFadeDownLen = TrapDouble(newValue, MIN_InnerFadeDownLen, MAX_InnerFadeDownLen); break; case innerFadeUp: newValue = ((double)(FADE_UP_START - evt.GetX())) / FADE_SCALE; - if (newValue < MIN_FADE) - newValue = MIN_FADE; - if (newValue > MAX_FADE) - newValue = MAX_FADE; - mParent->mInnerFadeUpLenBox->SetValue( - Internat::ToDisplayString(newValue)); + mEffect->mInnerFadeUpLen = TrapDouble(newValue, MIN_InnerFadeUpLen, MAX_InnerFadeUpLen); break; case duckAmount: - newValue = ((double)(DUCK_AMOUNT_START - evt.GetY())) / - DUCK_AMOUNT_SCALE; - if (newValue < MIN_DUCK_AMOUNT) - newValue = MIN_DUCK_AMOUNT; - if (newValue > MAX_DUCK_AMOUNT) - newValue = MAX_DUCK_AMOUNT; - mParent->mDuckAmountDbBox->SetValue( - Internat::ToDisplayString(newValue)); + newValue = ((double)(DUCK_AMOUNT_START - evt.GetY())) / DUCK_AMOUNT_SCALE; + mEffect->mDuckAmountDb = TrapDouble(newValue, MIN_DuckAmountDb, MAX_DuckAmountDb); break; case none: wxASSERT(false); // should not happen } - + mEffect->TransferDataToWindow(); Refresh(false); } } diff --git a/src/effects/AutoDuck.h b/src/effects/AutoDuck.h index 66faf0a54..bc01117f2 100644 --- a/src/effects/AutoDuck.h +++ b/src/effects/AutoDuck.h @@ -11,47 +11,91 @@ #ifndef __AUDACITY_EFFECT_AUTODUCK__ #define __AUDACITY_EFFECT_AUTODUCK__ -#include +#include +#include +#include #include +#include #include +#include + +#include "../ShuttleGui.h" +#include "../WaveTrack.h" #include "Effect.h" -class EffectAutoDuck; class EffectAutoDuckPanel; -class EffectAutoDuckDialog: public wxDialog -{ -public: - EffectAutoDuckDialog(EffectAutoDuck* effect, wxWindow* parent); - -private: - friend class EffectAutoDuckPanel; - - void OnOk(wxCommandEvent& evt); - void OnCancel(wxCommandEvent& evt); - void OnValueChanged(wxCommandEvent& evt); - - EffectAutoDuck* mEffect; - - wxTextCtrl* mDuckAmountDbBox; - wxTextCtrl* mInnerFadeDownLenBox; - wxTextCtrl* mInnerFadeUpLenBox; - wxTextCtrl* mOuterFadeDownLenBox; - wxTextCtrl* mOuterFadeUpLenBox; - wxTextCtrl* mThresholdDbBox; - wxTextCtrl* mMaximumPauseBox; - EffectAutoDuckPanel* mPanel; - - DECLARE_EVENT_TABLE() -}; - #define AUTO_DUCK_PANEL_NUM_CONTROL_POINTS 5 -class EffectAutoDuckPanel: public wxPanel +#define AUTODUCK_PLUGIN_SYMBOL wxTRANSLATE("Auto Duck") + +class EffectAutoDuck : public Effect { public: - EffectAutoDuckPanel(EffectAutoDuckDialog* parent, wxWindowID id); + EffectAutoDuck(); + virtual ~EffectAutoDuck(); + + // IdentInterface implementation + + virtual wxString GetSymbol(); + virtual wxString GetDescription(); + + // EffectIdentInterface implementation + + virtual EffectType GetType(); + + // EffectClientInterface implementation + + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + + // Effect implementation + + virtual bool Startup(); + virtual bool Init(); + virtual void End(); + virtual bool Process(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); + +private: + // EffectAutoDuck implementation + + bool ApplyDuckFade(int trackNumber, WaveTrack *t, double t0, double t1); + + void OnValueChanged(wxCommandEvent & evt); + +private: + double mDuckAmountDb; + double mInnerFadeDownLen; + double mInnerFadeUpLen; + double mOuterFadeDownLen; + double mOuterFadeUpLen; + double mThresholdDb; + double mMaximumPause; + + WaveTrack *mControlTrack; + + wxTextCtrl *mDuckAmountDbBox; + wxTextCtrl *mInnerFadeDownLenBox; + wxTextCtrl *mInnerFadeUpLenBox; + wxTextCtrl *mOuterFadeDownLenBox; + wxTextCtrl *mOuterFadeUpLenBox; + wxTextCtrl *mThresholdDbBox; + wxTextCtrl *mMaximumPauseBox; + EffectAutoDuckPanel *mPanel; + + DECLARE_EVENT_TABLE(); + + friend class EffectAutoDuckPanel; +}; + +class EffectAutoDuckPanel : public wxPanel +{ +public: + EffectAutoDuckPanel(wxWindow *parent, EffectAutoDuck *effect); virtual ~EffectAutoDuckPanel(); private: @@ -69,76 +113,27 @@ private: virtual bool AcceptsFocus() const {return false;} #endif - void OnPaint(wxPaintEvent& evt); - void OnMouseCaptureChanged(wxMouseCaptureChangedEvent &evt); - void OnMouseCaptureLost(wxMouseCaptureLostEvent &evt); - void OnLeftDown(wxMouseEvent &evt); - void OnLeftUp(wxMouseEvent &evt); - void OnMotion(wxMouseEvent &evt); + void OnPaint(wxPaintEvent & evt); + void OnMouseCaptureChanged(wxMouseCaptureChangedEvent & evt); + void OnMouseCaptureLost(wxMouseCaptureLostEvent & evt); + void OnLeftDown(wxMouseEvent & evt); + void OnLeftUp(wxMouseEvent & evt); + void OnMotion(wxMouseEvent & evt); void ResetControlPoints(); - EControlPoint GetNearestControlPoint(const wxPoint& pt); + EControlPoint GetNearestControlPoint(const wxPoint & pt); - EffectAutoDuckDialog* mParent; - wxBitmap* mBackgroundBitmap; +private: + wxWindow *mParent; + EffectAutoDuck *mEffect; + wxBitmap *mBackgroundBitmap; EControlPoint mCurrentControlPoint; wxPoint mControlPoints[AUTO_DUCK_PANEL_NUM_CONTROL_POINTS]; wxPoint mMoveStartControlPoints[AUTO_DUCK_PANEL_NUM_CONTROL_POINTS]; wxPoint mMouseDownPoint; bool mControlPointMoveActivated; - DECLARE_EVENT_TABLE() -}; - -class EffectAutoDuck: public Effect -{ - friend class EffectAutoDuckDialog; - -public: - EffectAutoDuck(); - - virtual wxString GetEffectName() - { - return wxString(wxTRANSLATE("Auto Duck...")); - } - - virtual std::set GetEffectCategories() - { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#DynamicsPlugin")); - return result; - } - - virtual wxString GetEffectIdentifier() - { - return wxString(wxT("AutoDuck")); - } - - virtual wxString GetEffectAction() - { - return wxString(_("Processing Auto Duck...")); - } - - virtual bool PromptUser(); - virtual bool TransferParameters(Shuttle & shuttle); - - virtual bool Init(); - virtual void End(); - virtual bool CheckWhetherSkipEffect(); - virtual bool Process(); - -private: - bool ApplyDuckFade(int trackNumber, WaveTrack* t, double t0, double t1); - - double mDuckAmountDb; - double mInnerFadeDownLen; - double mInnerFadeUpLen; - double mOuterFadeDownLen; - double mOuterFadeUpLen; - double mThresholdDb; - double mMaximumPause; - - WaveTrack* mControlTrack; + DECLARE_EVENT_TABLE(); }; #endif diff --git a/src/effects/BassTreble.cpp b/src/effects/BassTreble.cpp index 46eb2968d..5efa13097 100644 --- a/src/effects/BassTreble.cpp +++ b/src/effects/BassTreble.cpp @@ -10,113 +10,225 @@ ******************************************************************//** \class EffectBassTreble -\brief A TwoPassSimpleMono, high shelf and low shelf filters. +\brief A high shelf and low shelf filter. The first pass applies the equalization and calculates the peak value. The second pass, if enabled, normalizes to the level set by the level control. -*//****************************************************************//** - -\class BassTrebleDialog -\brief Dialog for EffectBassTreble - *//*******************************************************************/ #include "../Audacity.h" + #include -#include "BassTreble.h" -#include "../WaveTrack.h" -#include "../Prefs.h" - #include -#include -#include -#include +#include +#include #include -#include -#include + +#include "../Prefs.h" +#include "../WaveTrack.h" +#include "../widgets/valnum.h" + +#include "BassTreble.h" + +enum +{ + ID_Bass = 10000, + ID_Treble, + ID_Level, + ID_Normalize, +}; + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( Bass, double, wxTRANSLATE("Bass"), 0.0, -15.0, 15.0, 1 ); +Param( Treble, double, wxTRANSLATE("Treble"), 0.0, -15.0, 15.0, 1 ); +Param( Level, double, wxTRANSLATE("Level"), -1.0, -30.0, 0.0, 1 ); +Param( Normalize, bool, wxTRANSLATE("Normalize"), true, false, true, 1 ); + +// Sliders are integer, so range is x 10 +// to allow 1 decimal place resolution +static const int kSliderScale = 10; // Used to communicate the type of the filter. -static const int bassType = 0; //Low Shelf -static const int trebleType = 1; // High Shelf +enum kShelfType +{ + kBass, + kTreble +}; +BEGIN_EVENT_TABLE(EffectBassTreble, wxEvtHandler) + EVT_SLIDER(ID_Bass, EffectBassTreble::OnBassSlider) + EVT_SLIDER(ID_Treble, EffectBassTreble::OnTrebleSlider) + EVT_SLIDER(ID_Level, EffectBassTreble::OnLevelSlider) + EVT_TEXT(ID_Bass, EffectBassTreble::OnBassText) + EVT_TEXT(ID_Treble, EffectBassTreble::OnTrebleText) + EVT_TEXT(ID_Level, EffectBassTreble::OnLevelText) + EVT_CHECKBOX(ID_Normalize, EffectBassTreble::OnNormalize) +END_EVENT_TABLE() EffectBassTreble::EffectBassTreble() { + dB_bass = DEF_Bass; + dB_treble = DEF_Treble; + dB_level = DEF_Level; + mbNormalize = DEF_Normalize; } -bool EffectBassTreble::Init() +EffectBassTreble::~EffectBassTreble() { - // restore saved preferences - int readBool; - gPrefs->Read(wxT("/Effects/BassTreble/Bass"), &dB_bass, 0.0); - gPrefs->Read(wxT("/Effects/BassTreble/Treble"), &dB_treble, 0.0); - gPrefs->Read(wxT("/Effects/BassTreble/Level"), &dB_level, -1.0); - gPrefs->Read(wxT("/Effects/BassTreble/Normalize"), &readBool, 1 ); +} - // Validate data - dB_level = (dB_level > 0)? 0 : dB_level; - mbNormalize = (readBool != 0); +// IdentInterface implementation + +wxString EffectBassTreble::GetSymbol() +{ + return BASSTREBLE_PLUGIN_SYMBOL; +} + +wxString EffectBassTreble::GetDescription() +{ + return wxTRANSLATE("Increases or decreases the lower frequencies and higher frequencies of your audio independently"); +} + +// EffectIdentInterface implementation + +EffectType EffectBassTreble::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +int EffectBassTreble::GetAudioInCount() +{ + return 1; +} + +int EffectBassTreble::GetAudioOutCount() +{ + return 1; +} + +bool EffectBassTreble::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) +{ + if (GetPass() == 1) + { + const float slope = 0.4f; // same slope for both filters + const double hzBass = 250.0f; + const double hzTreble = 4000.0f; + + //(re)initialise filter parameters + xn1Bass=xn2Bass=yn1Bass=yn2Bass=0; + xn1Treble=xn2Treble=yn1Treble=yn2Treble=0; + + // Compute coefficents of the low shelf biquand IIR filter + Coefficents(hzBass, slope, dB_bass, kBass, + a0Bass, a1Bass, a2Bass, + b0Bass, b1Bass, b2Bass); + + // Compute coefficents of the high shelf biquand IIR filter + Coefficents(hzTreble, slope, dB_treble, kTreble, + a0Treble, a1Treble, a2Treble, + b0Treble, b1Treble, b2Treble); + } return true; } -wxString EffectBassTreble::GetEffectDescription() { - // Note: This is useful only after values have been set. - wxString strResult = - wxString::Format(_("Applied effect: %s bass = %.1f dB, treble = %.1f dB"), - this->GetEffectName().c_str(), - dB_bass, dB_treble); - (mbNormalize) ? - strResult += wxString::Format(_(", level enabled at = %.1f dB"), dB_level) : - strResult += wxString::Format(_(", level disabled")); +sampleCount EffectBassTreble::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) +{ + float *ibuf = inBlock[0]; + float *obuf = outBlock[0]; - return strResult; + if (GetPass() == 1) + { + for (sampleCount i = 0; i < blockLen; i++) + { + obuf[i] = DoFilter(ibuf[i]) / mPreGain; + } + } + else + { + float gain = (pow(10.0, dB_level / 20.0f)) / mMax; + for (sampleCount i = 0; i < blockLen; i++) + { + // Normalize to specified level + obuf[i] = ibuf[i] * (mPreGain * gain); + } + } + + return blockLen; } -bool EffectBassTreble::PromptUser() +bool EffectBassTreble::GetAutomationParameters(EffectAutomationParameters & parms) { - BassTrebleDialog dlog(this, mParent); - dlog.mBass = dB_bass; - dlog.mTreble = dB_treble; - dlog.mLevel = dB_level; - dlog.mbNormalize = mbNormalize; - dlog.TransferDataToWindow(); - dlog.CentreOnParent(); - dlog.ShowModal(); - - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; - - dB_bass = dlog.mBass; - dB_treble = dlog.mTreble; - dB_level = dlog.mLevel; - mbNormalize = dlog.mbNormalize; - - gPrefs->Write(wxT("/Effects/BassTreble/Bass"), dB_bass); - gPrefs->Write(wxT("/Effects/BassTreble/Treble"), dB_treble); - gPrefs->Write(wxT("/Effects/BassTreble/Level"), dB_level); - gPrefs->Write(wxT("/Effects/BassTreble/Normalize"), mbNormalize); - gPrefs->Flush(); + parms.Write(KEY_Bass, dB_bass); + parms.Write(KEY_Treble, dB_treble); + parms.Write(KEY_Level, dB_level); + parms.Write(KEY_Normalize, mbNormalize); return true; } -bool EffectBassTreble::TransferParameters(Shuttle & shuttle) +bool EffectBassTreble::SetAutomationParameters(EffectAutomationParameters & parms) { - shuttle.TransferDouble(wxT("Bass"),dB_bass,0.0); - shuttle.TransferDouble(wxT("Treble"),dB_treble,0.0); - shuttle.TransferDouble(wxT("Level"),dB_level,0.0); - shuttle.TransferBool( wxT("Normalize"), mbNormalize, true ); + ReadAndVerifyDouble(Bass); + ReadAndVerifyDouble(Treble); + ReadAndVerifyDouble(Level); + ReadAndVerifyBool(Normalize); + + dB_bass = Bass; + dB_treble = Treble; + dB_level = Level; + mbNormalize = Normalize; + + return true; +} + +// Effect implementation + +bool EffectBassTreble::Startup() +{ + wxString base = wxT("/Effects/BassTreble/"); + + // Migrate settings from 2.1.0 or before + + // Already migrated, so bail + if (gPrefs->Exists(base + wxT("Migrated"))) + { + return true; + } + + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + int readBool; + gPrefs->Read(base + wxT("Bass"), &dB_bass, 0.0); + gPrefs->Read(base + wxT("Treble"), &dB_treble, 0.0); + gPrefs->Read(base + wxT("Level"), &dB_level, -1.0); + gPrefs->Read(base + wxT("Normalize"), &readBool, 1 ); + + // Validate data + dB_level = (dB_level > 0) ? 0 : dB_level; + mbNormalize = (readBool != 0); + + SaveUserPreset(GetCurrentSettingsGroup()); + + // Do not migrate again + gPrefs->Write(base + wxT("Migrated"), true); + gPrefs->Flush(); + } return true; } bool EffectBassTreble::InitPass1() { - mMax=0.0; + mMax = 0.0; // Integer format tracks require headroom to avoid clipping // when saved between passes (bug 619) @@ -124,57 +236,139 @@ bool EffectBassTreble::InitPass1() if (mbNormalize) // don't need to calculate this if only doing one pass. { // Up to (gain + 6dB) headroom required for treble boost (experimental). - mPreGain = (dB_treble > 0)? (dB_treble + 6.0) : 0.0; + mPreGain = (dB_treble > 0) ? (dB_treble + 6.0) : 0.0; if (dB_bass >= 0) { - mPreGain = (mPreGain > dB_bass)? mPreGain : dB_bass; - } else { - // Up to 6 dB headroom reaquired for bass cut (experimental) - mPreGain = (mPreGain > 6.0)? mPreGain : 6.0; + mPreGain = (mPreGain > dB_bass) ? mPreGain : dB_bass; } - mPreGain = (exp (log(10.0) * mPreGain / 20)); // to linear - } else { + else + { + // Up to 6 dB headroom reaquired for bass cut (experimental) + mPreGain = (mPreGain > 6.0) ? mPreGain : 6.0; + } + mPreGain = (exp(log(10.0) * mPreGain / 20)); // to linear + } + else + { mPreGain = 1.0; // Unity gain } - if (!mbNormalize) - DisableSecondPass(); - return true; } -bool EffectBassTreble::NewTrackPass1() +bool EffectBassTreble::InitPass2() { - const float slope = 0.4f; // same slope for both filters - const double hzBass = 250.0f; - const double hzTreble = 4000.0f; + return mbNormalize && mMax != 0; +} - //(re)initialise filter parameters - xn1Bass=xn2Bass=yn1Bass=yn2Bass=0; - xn1Treble=xn2Treble=yn1Treble=yn2Treble=0; +void EffectBassTreble::PopulateOrExchange(ShuttleGui & S) +{ + S.StartVerticalLay(0); + { + S.StartStatic(wxT("")); + { + S.StartMultiColumn(3, wxEXPAND); + S.SetStretchyCol(2); + { + #ifdef __WXGTK__ + // BoxSizer is to make first mnemonic work, on Linux. + wxPanel* cPanel = new wxPanel(S.GetParent(), wxID_ANY); + wxBoxSizer* cSizer = new wxBoxSizer(wxVERTICAL); + cPanel->SetSizer(cSizer); + #endif - // Compute coefficents of the low shelf biquand IIR filter - Coefficents(hzBass, slope, dB_bass, bassType, - a0Bass, a1Bass, a2Bass, - b0Bass, b1Bass, b2Bass); + // Bass control + FloatingPointValidator vldBass(1, &dB_bass); + vldBass.SetRange(MIN_Bass, MAX_Bass); + mBassT = S.Id(ID_Bass).AddTextBox(_("&Bass (dB):"), wxT(""), 10); + mBassT->SetName(_("Bass (dB):")); + mBassT->SetValidator(vldBass); - // Compute coefficents of the high shelf biquand IIR filter - Coefficents(hzTreble, slope, dB_treble, trebleType, - a0Treble, a1Treble, a2Treble, - b0Treble, b1Treble, b2Treble); + S.SetStyle(wxSL_HORIZONTAL); + mBassS = S.Id(ID_Bass).AddSlider(wxT(""), 0, MAX_Bass * kSliderScale, MIN_Bass * kSliderScale); + mBassS->SetName(_("Bass")); + mBassS->SetPageSize(30); + + // Treble control + FloatingPointValidator vldTreble(1, &dB_treble); + vldTreble.SetRange(MIN_Treble, MAX_Treble); + mTrebleT = S.Id(ID_Treble).AddTextBox(_("&Treble (dB):"), wxT(""), 10); + mTrebleT->SetValidator(vldTreble); + + S.SetStyle(wxSL_HORIZONTAL); + mTrebleS = S.Id(ID_Treble).AddSlider(wxT(""), 0, MAX_Treble * kSliderScale, MIN_Treble * kSliderScale); + mTrebleS->SetName(_("Treble")); + mTrebleS->SetPageSize(30); + + // Level control + FloatingPointValidator vldLevel(1, &dB_level); + vldLevel.SetRange(MIN_Level, MAX_Level); + mLevelT = S.Id(ID_Level).AddTextBox(_("&Level (dB):"), wxT(""), 10); + mLevelT->SetValidator(vldLevel); + + S.SetStyle(wxSL_HORIZONTAL); + mLevelS = S.Id(ID_Level).AddSlider(wxT(""), 0, MAX_Level * kSliderScale, MIN_Level * kSliderScale); + mLevelS->SetName(_("Level")); + mLevelS->SetPageSize(30); + } + S.EndMultiColumn(); + } + S.EndStatic(); + + // Normalize checkbox + S.StartHorizontalLay(wxLEFT, true); + { + mNormalizeCheckBox = S.Id(ID_Normalize).AddCheckBox(_("&Enable level control"), + DEF_Normalize ? wxT("true") : wxT("false")); + mWarning = S.AddVariableText(wxT(""), false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + } + S.EndHorizontalLay(); + } + S.EndVerticalLay(); + + return; +} + +bool EffectBassTreble::TransferDataToWindow() +{ + if (!mUIParent->TransferDataToWindow()) + { + return false; + } + + mBassS->SetValue((int) dB_bass * kSliderScale + 0.5); + mTrebleS->SetValue((int) dB_treble * kSliderScale + 0.5); + mLevelS->SetValue((int) dB_level * kSliderScale + 0.5); + mNormalizeCheckBox->SetValue(mbNormalize); + + UpdateUI(); return true; } +bool EffectBassTreble::TransferDataFromWindow() +{ + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } + + mbNormalize = mNormalizeCheckBox->GetValue(); + + return true; +} + +// EffectBassTreble implementation + void EffectBassTreble::Coefficents(double hz, float slope, double gain, int type, float& a0, float& a1, float& a2, float& b0, float& b1, float& b2) { - double w = 2 * M_PI * hz / mCurRate; + double w = 2 * M_PI * hz / mSampleRate; double a = exp(log(10.0) * gain / 40); double b = sqrt((a * a + 1) / slope - (pow((a - 1), 2))); - if (type == bassType) + if (type == kBass) { b0 = a * ((a + 1) - (a - 1) * cos(w) + b * sin(w)); b1 = 2 * a * ((a - 1) - (a + 1) * cos(w)); @@ -183,7 +377,7 @@ void EffectBassTreble::Coefficents(double hz, float slope, double gain, int type a1 = -2 * ((a - 1) + (a + 1) * cos(w)); a2 = (a + 1) + (a - 1) * cos(w) - b * sin(w); } - else //assumed trebleType + else //assumed kTreble { b0 = a * ((a + 1) + (a - 1) * cos(w) + b * sin(w)); b1 = -2 * a * ((a - 1) + (a + 1) * cos(w)); @@ -194,32 +388,6 @@ void EffectBassTreble::Coefficents(double hz, float slope, double gain, int type } } -bool EffectBassTreble::InitPass2() -{ - return mbNormalize; -} - -// Process the input -bool EffectBassTreble::ProcessPass1(float *buffer, sampleCount len) -{ - for (sampleCount i = 0; i < len; i++) - buffer[i] = (DoFilter(buffer[i]) / mPreGain); - - return true; -} - -bool EffectBassTreble::ProcessPass2(float *buffer, sampleCount len) -{ - if (mMax != 0) - { - float gain = (pow(10.0, dB_level/20.0f))/mMax; - for (int i = 0; i < len; i++) - // Normalize to specified level - buffer[i] *= (mPreGain * gain); - } - return true; -} - float EffectBassTreble::DoFilter(float in) { // Bass filter @@ -245,266 +413,84 @@ float EffectBassTreble::DoFilter(float in) return out; } -//---------------------------------------------------------------------------- -// BassTrebleDialog -//---------------------------------------------------------------------------- - -// Declare window functions - -#define ID_BASS_TEXT 10001 -#define ID_BASS_SLIDER 10002 -#define ID_TREBLE_TEXT 10003 -#define ID_TREBLE_SLIDER 10004 -#define ID_LEVEL_TEXT 10005 -#define ID_LEVEL_SLIDER 10006 -#define ID_NORMALIZE 10007 - -// Declare ranges -// Sliders are integer, so range is x 10 -// to allow 1 decimal place resolution - -#define BASS_MIN -150 // Corresponds to -15 db -#define BASS_MAX 150 // Corresponds to +15 dB -#define TREBLE_MIN -150 // Corresponds to -15 dB -#define TREBLE_MAX 150 // Corresponds to +15 dB -#define LEVEL_MIN -300 // Corresponds to -30 dN -#define LEVEL_MAX 0 // Corresponds to 0 dB -#define DB_MAX 100 // Maximum allowed dB boost - -BEGIN_EVENT_TABLE(BassTrebleDialog, EffectDialog) - EVT_SLIDER(ID_BASS_SLIDER, BassTrebleDialog::OnBassSlider) - EVT_SLIDER(ID_TREBLE_SLIDER, BassTrebleDialog::OnTrebleSlider) - EVT_SLIDER(ID_LEVEL_SLIDER, BassTrebleDialog::OnLevelSlider) - EVT_TEXT(ID_BASS_TEXT, BassTrebleDialog::OnBassText) - EVT_TEXT(ID_TREBLE_TEXT, BassTrebleDialog::OnTrebleText) - EVT_TEXT(ID_LEVEL_TEXT, BassTrebleDialog::OnLevelText) - EVT_CHECKBOX(ID_NORMALIZE, BassTrebleDialog::OnNormalize) - EVT_BUTTON(ID_EFFECT_PREVIEW, BassTrebleDialog::OnPreview) -END_EVENT_TABLE() - -BassTrebleDialog::BassTrebleDialog(EffectBassTreble *effect, - wxWindow * parent): - EffectDialog(parent, _("Bass and Treble"), PROCESS_EFFECT), - mEffect(effect) -{ - Init(); -} - - -void BassTrebleDialog::PopulateOrExchange(ShuttleGui & S) -{ - S.StartStatic(wxT("")); - { - S.StartMultiColumn(3, wxEXPAND); - S.SetStretchyCol(2); - { - #ifdef __WXGTK__ - // BoxSizer is to make first mnemonic work, on Linux. - wxPanel* cPanel = new wxPanel( this, wxID_ANY ); - wxBoxSizer* cSizer = new wxBoxSizer(wxVERTICAL); - cPanel->SetSizer(cSizer); - #endif - - wxTextValidator vld(wxFILTER_NUMERIC); - - // Bass control - mBassT = S.Id(ID_BASS_TEXT).AddTextBox(_("&Bass (dB):"), wxT(""), 10); - mBassT->SetName(_("Bass (dB):")); - mBassT->SetValidator(vld); - - S.SetStyle(wxSL_HORIZONTAL); - mBassS = S.Id(ID_BASS_SLIDER).AddSlider(wxT(""), 0, BASS_MAX, BASS_MIN); - mBassS->SetName(_("Bass")); - mBassS->SetRange(BASS_MIN, BASS_MAX); - mBassS->SetPageSize(30); - - // Treble control - mTrebleT = S.Id(ID_TREBLE_TEXT).AddTextBox(_("&Treble (dB):"), wxT(""), 10); - mTrebleT->SetValidator(vld); - - S.SetStyle(wxSL_HORIZONTAL); - mTrebleS = S.Id(ID_TREBLE_SLIDER).AddSlider(wxT(""), 0, TREBLE_MAX, TREBLE_MIN); - mTrebleS->SetName(_("Treble")); - mTrebleS->SetRange(TREBLE_MIN, TREBLE_MAX); - mTrebleS->SetPageSize(30); - - // Level control - mLevelT = S.Id(ID_LEVEL_TEXT).AddTextBox(_("&Level (dB):"), wxT(""), 10); - mLevelT->SetValidator(vld); - - S.SetStyle(wxSL_HORIZONTAL); - mLevelS = S.Id(ID_LEVEL_SLIDER).AddSlider(wxT(""), 0, LEVEL_MAX, LEVEL_MIN); - mLevelS->SetName(_("Level")); - mLevelS->SetRange(LEVEL_MIN, LEVEL_MAX); - mLevelS->SetPageSize(30); - } - S.EndMultiColumn(); - } - S.EndStatic(); - - // Normalize checkbox - S.StartTwoColumn(); - { - S.AddSpace(5,0); - mNormalizeCheckBox = S.Id(ID_NORMALIZE).AddCheckBox(_("&Enable level control"), - mbNormalize ? wxT("true") : wxT("false")); - - S.AddSpace(5, 0); - mWarning = S.AddVariableText( wxT(""), false); - } - S.EndTwoColumn(); -} - -bool BassTrebleDialog::TransferDataToWindow() -{ - mBassS->SetValue((double)mBass); - mTrebleS->SetValue((double)mTreble); - mLevelS->SetValue((double)mLevel); - - mBassT->SetValue(wxString::Format(wxT("%.1f"), (float)mBass)); - mTrebleT->SetValue(wxString::Format(wxT("%.1f"), (float)mTreble)); - mLevelT->SetValue(wxString::Format(wxT("%.1f"), (float)mLevel)); - - mNormalizeCheckBox->SetValue(mbNormalize); - UpdateUI(); - TransferDataFromWindow(); - - return true; -} - -bool BassTrebleDialog::TransferDataFromWindow() -{ - mBassT->GetValue().ToDouble(&mBass); - mTrebleT->GetValue().ToDouble(&mTreble); - mLevelT->GetValue().ToDouble(&mLevel); - mbNormalize = mNormalizeCheckBox->GetValue(); - - // Ensure that max values can never exceed design limits - // See bug 683. - mBass = wxMin(DB_MAX, mBass); - mTreble = wxMin(DB_MAX, mTreble); - mLevel = wxMin(0, mLevel); - - return true; -} - -void BassTrebleDialog::OnNormalize(wxCommandEvent& WXUNUSED(evt)) -{ - UpdateUI(); -} - -void BassTrebleDialog::UpdateUI() +void EffectBassTreble::UpdateUI() { + double bass, treble, level; + mBassT->GetValue().ToDouble(&bass); + mTrebleT->GetValue().ToDouble(&treble); + mLevelT->GetValue().ToDouble(&level); bool enable = mNormalizeCheckBox->GetValue(); - bool okEnabled = true; - bool preveiwEnabled = true; - wxString warning = wxT(""); - double v0, v1, v2; - wxString val0 = mBassT->GetValue(); - val0.ToDouble(&v0); - wxString val1 = mTrebleT->GetValue(); - val1.ToDouble(&v1); - wxString val2 = mLevelT->GetValue(); - val2.ToDouble(&v2); // Disallow level control if disabled mLevelT->Enable(enable); mLevelS->Enable(enable); - wxButton *ok = (wxButton *) FindWindow(wxID_OK); - wxButton *preview = (wxButton *) FindWindow(ID_EFFECT_PREVIEW); - - if (v0==0 && v1==0 && !enable) { - // Disallow OK if nothing to do (but allow preview) - okEnabled = false; - warning = (_("Warning: No change to apply.")); + if (bass == 0 && treble == 0 && !enable) + { + // Disallow Apply if nothing to do + EnableApply(false); + mWarning->SetLabel(_(" No change to apply.")); } - if (v0 > DB_MAX) { - okEnabled = false; - preveiwEnabled = false; - warning = (_("Error: Maximum Bass = 100 dB.")); + else + { + if (level > 0 && enable) + { + // Disallow Apply if level enabled and > 0 + EnableApply(false); + mWarning->SetLabel(_(": Maximum 0 dB.")); + } + else + { + // Apply enabled + EnableApply(true); + mWarning->SetLabel(wxT("")); + } } - if (v1 > DB_MAX) { - okEnabled = false; - preveiwEnabled = false; - warning = (_("Error: Maximum Treble = 100 dB.")); - } - if ((v2 > 0) && enable) { - // Disallow OK and Preview if level enabled and > 0 - okEnabled = false; - preveiwEnabled = false; - warning = (_("Error: Maximum Level = 0 dB.")); - } - - mWarning->SetLabel(warning); - ok->Enable(okEnabled); - preview->Enable(preveiwEnabled); } -// handler implementations for BassTrebleDialog -void BassTrebleDialog::OnBassText(wxCommandEvent & WXUNUSED(event)) +void EffectBassTreble::OnBassText(wxCommandEvent & WXUNUSED(evt)) { - double val; - mBassT->GetValue().ToDouble(&val); - int newval = floor(val / 0.1 + 0.5); - mBassS->SetValue(TrapDouble(newval, BASS_MIN, BASS_MAX)); + mBassT->GetValidator()->TransferFromWindow(); + mBassS->SetValue((int) floor(dB_bass * kSliderScale + 0.5)); UpdateUI(); } -void BassTrebleDialog::OnTrebleText(wxCommandEvent & WXUNUSED(event)) +void EffectBassTreble::OnTrebleText(wxCommandEvent & WXUNUSED(evt)) { - double val; - mTrebleT->GetValue().ToDouble(&val); - int newval = floor(val / 0.1 + 0.5); - mTrebleS->SetValue(TrapDouble(newval, TREBLE_MIN, TREBLE_MAX)); + mTrebleT->GetValidator()->TransferFromWindow(); + mTrebleS->SetValue((int) floor(dB_treble * kSliderScale + 0.5)); UpdateUI(); } -void BassTrebleDialog::OnLevelText(wxCommandEvent & WXUNUSED(event)) +void EffectBassTreble::OnLevelText(wxCommandEvent & WXUNUSED(evt)) { - double val; - mLevelT->GetValue().ToDouble(&val); - int newval = floor(val / 0.1 + 0.5); - mLevelS->SetValue(TrapDouble(newval, LEVEL_MIN, LEVEL_MAX)); + mLevelT->GetValidator()->TransferFromWindow(); + mLevelS->SetValue((int) floor(dB_level * kSliderScale + 0.5)); UpdateUI(); } -void BassTrebleDialog::OnBassSlider(wxCommandEvent & WXUNUSED(event)) +void EffectBassTreble::OnBassSlider(wxCommandEvent & evt) { - mBassT->SetValue(wxString::Format(wxT("%.1f"), mBassS->GetValue() * 0.1)); + dB_bass = (double) evt.GetInt() / kSliderScale; + mBassT->GetValidator()->TransferToWindow(); UpdateUI(); } -void BassTrebleDialog::OnTrebleSlider(wxCommandEvent & WXUNUSED(event)) +void EffectBassTreble::OnTrebleSlider(wxCommandEvent & evt) { - mTrebleT->SetValue(wxString::Format(wxT("%.1f"), mTrebleS->GetValue() * 0.1)); + dB_treble = (double) evt.GetInt() / kSliderScale; + mTrebleT->GetValidator()->TransferToWindow(); UpdateUI(); } -void BassTrebleDialog::OnLevelSlider(wxCommandEvent & WXUNUSED(event)) +void EffectBassTreble::OnLevelSlider(wxCommandEvent & evt) { - mLevelT->SetValue(wxString::Format(wxT("%.1f"), mLevelS->GetValue() * 0.1)); + dB_level = (double) evt.GetInt() / kSliderScale; + mLevelT->GetValidator()->TransferToWindow(); UpdateUI(); } -void BassTrebleDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) +void EffectBassTreble::OnNormalize(wxCommandEvent& WXUNUSED(evt)) { - TransferDataFromWindow(); - - // Save & restore parameters around Preview, because we didn't do OK. - double oldBass = mEffect->dB_bass; - double oldTreble = mEffect->dB_treble; - double oldLevel = mEffect->dB_level; - bool oldUseGain = mEffect->mbNormalize; - - mEffect->dB_bass = mBass; - mEffect->dB_treble = mTreble; - mEffect->dB_level = mLevel; - mEffect->mbNormalize = mbNormalize; - mEffect->Preview(); - - mEffect->dB_bass = oldBass; - mEffect->dB_treble = oldTreble; - mEffect->dB_level = oldLevel; - mEffect->mbNormalize = oldUseGain; + UpdateUI(); } diff --git a/src/effects/BassTreble.h b/src/effects/BassTreble.h index ec29d62da..d98746993 100644 --- a/src/effects/BassTreble.h +++ b/src/effects/BassTreble.h @@ -12,56 +12,70 @@ #ifndef __AUDACITY_EFFECT_BASS_TREBLE__ #define __AUDACITY_EFFECT_BASS_TREBLE__ -#include "TwoPassSimpleMono.h" +#include +#include +#include +#include +#include +#include -class wxSizer; -class wxTextCtrl; -class WaveTrack; +#include "../ShuttleGui.h" -class EffectBassTreble: public EffectTwoPassSimpleMono { +#include "Effect.h" +#define BASSTREBLE_PLUGIN_SYMBOL wxTRANSLATE("Bass and Treble") + +class EffectBassTreble : public Effect +{ public: EffectBassTreble(); - virtual ~EffectBassTreble() {}; + virtual ~EffectBassTreble(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Bass and Treble...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#EQPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Bass and Treble")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Adjusting Bass and Treble")); - } + virtual EffectType GetType(); - // Useful only after PromptUser values have been set. - virtual wxString GetEffectDescription(); + // EffectClientInterface implementation -protected: - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); - virtual bool Init(); + virtual int GetAudioInCount(); + virtual int GetAudioOutCount(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); - virtual bool ProcessPass1(float *buffer, sampleCount len); - virtual bool ProcessPass2(float *buffer, sampleCount len); + // Effect Implementation + + virtual bool Startup(); + virtual bool InitPass1(); + virtual bool InitPass2(); + + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); + +private: + // EffectBassTreble implementation void Coefficents(double hz, float slope, double gain, int type, float& a0, float& a1, float& a2, float& b0, float& b1, float& b2); + float DoFilter(float in); + void UpdateUI(); + + void OnBassText(wxCommandEvent & evt); + void OnTrebleText(wxCommandEvent & evt); + void OnLevelText(wxCommandEvent & evt); + void OnBassSlider(wxCommandEvent & evt); + void OnTrebleSlider(wxCommandEvent & evt); + void OnLevelSlider(wxCommandEvent & evt); + void OnNormalize(wxCommandEvent & evt); private: - virtual bool NewTrackPass1(); - virtual bool InitPass1(); - virtual bool InitPass2(); - float DoFilter(float in); - float xn1Bass, xn2Bass, yn1Bass, yn2Bass, wBass, swBass, cwBass, aBass, bBass, a0Bass, a1Bass, a2Bass, b0Bass, b1Bass, b2Bass; @@ -75,36 +89,6 @@ private: bool mbNormalize; double mPreGain; - friend class BassTrebleDialog; -}; - -//---------------------------------------------------------------------------- -// BassTrebleDialog -//---------------------------------------------------------------------------- -class BassTrebleDialog:public EffectDialog { -public: - BassTrebleDialog(EffectBassTreble *effect, wxWindow * parent); - virtual ~BassTrebleDialog() {}; - - // method declarations for BassTrebleDialog - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - -private: - // handler declarations for BassTrebleDialog - void OnBassText(wxCommandEvent & event); - void OnTrebleText(wxCommandEvent & event); - void OnLevelText(wxCommandEvent & event); - void OnBassSlider(wxCommandEvent & event); - void OnTrebleSlider(wxCommandEvent & event); - void OnLevelSlider(wxCommandEvent & event); - void OnNormalize(wxCommandEvent& evt); - void UpdateUI(); - void OnPreview(wxCommandEvent & event); - void set_properties(); - -private: wxSlider *mBassS; wxSlider *mTrebleS; wxSlider *mLevelS; @@ -112,17 +96,9 @@ private: wxTextCtrl *mTrebleT; wxTextCtrl *mLevelT; wxCheckBox *mNormalizeCheckBox; - wxStaticText* mWarning; + wxStaticText *mWarning; -public: - EffectBassTreble *mEffect; - - double mBass; - double mTreble; - double mLevel; - bool mbNormalize; - - DECLARE_EVENT_TABLE() + DECLARE_EVENT_TABLE(); }; #endif diff --git a/src/effects/ChangePitch.cpp b/src/effects/ChangePitch.cpp index a08d44569..236754010 100644 --- a/src/effects/ChangePitch.cpp +++ b/src/effects/ChangePitch.cpp @@ -19,49 +19,329 @@ the pitch without changing the tempo. #if USE_SOUNDTOUCH -#include "ChangePitch.h" - #include #include -#include -#include -#include -#include -#include -#include +#include #include -#include "../ShuttleGui.h" #include "../PitchName.h" #include "../Spectrum.h" #include "../WaveTrack.h" +#include "../widgets/valnum.h" #include "TimeWarper.h" +#include "ChangePitch.h" + +enum { + ID_PercentChange = 10000, + ID_FromPitch, + ID_FromOctave, + ID_ToPitch, + ID_ToOctave, + ID_SemitonesChange, + ID_FromFrequency, + ID_ToFrequency +}; + +// Soundtouch is not reasonable below -99% or above 3000%. + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( Percentage, double, wxTRANSLATE("Percentage"), 0.0, -99.0, 3000.0, 1 ); + +// We warp the slider to go up to 400%, but user can enter up to 3000% +static const double kSliderMax = 100.0; // warped above zero to actually go up to 400% +static const double kSliderWarp = 1.30105; // warp power takes max from 100 to 400. // EffectChangePitch +BEGIN_EVENT_TABLE(EffectChangePitch, wxEvtHandler) + EVT_CHOICE(ID_FromPitch, EffectChangePitch::OnChoice_FromPitch) + EVT_TEXT(ID_FromOctave, EffectChangePitch::OnSpin_FromOctave) + EVT_CHOICE(ID_ToPitch, EffectChangePitch::OnChoice_ToPitch) + EVT_TEXT(ID_ToOctave, EffectChangePitch::OnSpin_ToOctave) + + EVT_TEXT(ID_SemitonesChange, EffectChangePitch::OnText_SemitonesChange) + + EVT_TEXT(ID_FromFrequency, EffectChangePitch::OnText_FromFrequency) + EVT_TEXT(ID_ToFrequency, EffectChangePitch::OnText_ToFrequency) + + EVT_TEXT(ID_PercentChange, EffectChangePitch::OnText_PercentChange) + EVT_SLIDER(ID_PercentChange, EffectChangePitch::OnSlider_PercentChange) +END_EVENT_TABLE() + EffectChangePitch::EffectChangePitch() { + m_dPercentChange = DEF_Percentage; m_dSemitonesChange = 0.0; m_dStartFrequency = 0.0; // 0.0 => uninitialized - m_dPercentChange = 0.0; + m_bLoopDetect = false; + + // NULL out these control members because there are some cases where the + // event table handlers get called during this method, and those handlers that + // can cause trouble check for NULL. + m_pChoice_FromPitch = NULL; + m_pSpin_FromOctave = NULL; + m_pChoice_ToPitch = NULL; + m_pSpin_ToOctave = NULL; + + m_pTextCtrl_SemitonesChange = NULL; + + m_pTextCtrl_FromFrequency = NULL; + m_pTextCtrl_ToFrequency = NULL; + + m_pTextCtrl_PercentChange = NULL; + m_pSlider_PercentChange = NULL; } -wxString EffectChangePitch::GetEffectDescription() +EffectChangePitch::~EffectChangePitch() { - // This is useful only after m_dSemitonesChange has been set. - return wxString::Format(_("Applied effect: %s %.2f semitones"), - this->GetEffectName().c_str(), - m_dSemitonesChange); } +// IdentInterface implementation + +wxString EffectChangePitch::GetSymbol() +{ + return CHANGEPITCH_PLUGIN_SYMBOL; +} + +wxString EffectChangePitch::GetDescription() +{ + return wxTRANSLATE("Change the pitch of a track without changing its tempo"); +} + +// EffectIdentInterface implementation + +EffectType EffectChangePitch::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +bool EffectChangePitch::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_Percentage, m_dPercentChange); + + return true; +} + +bool EffectChangePitch::SetAutomationParameters(EffectAutomationParameters & parms) +{ + // Vaughan, 2013-06: Long lost to history, I don't see why m_dPercentChange was chosen to be shuttled. + // Only m_dSemitonesChange is used in Process(). + ReadAndVerifyDouble(Percentage); + + m_dPercentChange = Percentage; + + m_dSemitonesChange = (12.0 * log((100.0 + m_dPercentChange) / 100.0)) / log(2.0); + + return true; +} + +// Effect implementation + bool EffectChangePitch::Init() { mSoundTouch = NULL; return true; } +bool EffectChangePitch::Process() +{ + mSoundTouch = new SoundTouch(); + SetTimeWarper(new IdentityTimeWarper()); + mSoundTouch->setPitchSemiTones((float)(m_dSemitonesChange)); +#ifdef USE_MIDI + // Note: m_dSemitonesChange is private to ChangePitch because it only + // needs to pass it along to mSoundTouch (above). I added mSemitones + // to SoundTouchEffect (the super class) to convey this value + // to process Note tracks. This approach minimizes changes to existing + // code, but it would be cleaner to change all m_dSemitonesChange to + // mSemitones, make mSemitones exist with or without USE_MIDI, and + // eliminate the next line: + mSemitones = m_dSemitonesChange; +#endif + return EffectSoundTouch::Process(); +} + +bool EffectChangePitch::CheckWhetherSkipEffect() +{ + return (m_dPercentChange == 0.0); +} + +void EffectChangePitch::PopulateOrExchange(ShuttleGui & S) +{ + DeduceFrequencies(); // Set frequency-related control values based on sample. + + // effect parameters + double dFromMIDInote = FreqToMIDInote(m_dStartFrequency); + double dToMIDInote = dFromMIDInote + m_dSemitonesChange; + m_nFromPitch = PitchIndex(dFromMIDInote); + m_nFromOctave = PitchOctave(dFromMIDInote); + m_nToPitch = PitchIndex(dToMIDInote); + m_nToOctave = PitchOctave(dToMIDInote); + + m_dSemitonesChange = m_dSemitonesChange; + + m_FromFrequency = m_dStartFrequency; + Calc_PercentChange(); + Calc_ToFrequency(); + + wxArrayString pitch; + pitch.Add(wxT("C")); + pitch.Add(wxT("C#/Db")); + pitch.Add(wxT("D")); + pitch.Add(wxT("D#/Eb")); + pitch.Add(wxT("E")); + pitch.Add(wxT("F")); + pitch.Add(wxT("F#/Gb")); + pitch.Add(wxT("G")); + pitch.Add(wxT("G#/Ab")); + pitch.Add(wxT("A")); + pitch.Add(wxT("A#/Bb")); + pitch.Add(wxT("B")); + + S.SetBorder(5); + + S.StartVerticalLay(0); + { + S.StartVerticalLay(); + { + S.AddTitle(_("Change Pitch without Changing Tempo")); + S.AddTitle( + wxString::Format(_("Estimated Start Pitch: %s%d (%.3f Hz)"), + pitch[m_nFromPitch].c_str(), m_nFromOctave, m_FromFrequency)); + } + S.EndVerticalLay(); + + /* i18n-hint: (noun) Musical pitch.*/ + S.StartStatic(_("Pitch")); + { + S.StartMultiColumn(6, wxALIGN_CENTER); // 6 controls, because each AddChoice adds a wxStaticText and a wxChoice. + { + m_pChoice_FromPitch = S.Id(ID_FromPitch).AddChoice(_("from"), wxT(""), &pitch); + m_pChoice_FromPitch->SetName(_("from")); + m_pChoice_FromPitch->SetSizeHints(80, -1); + + m_pSpin_FromOctave = S.Id(ID_FromOctave).AddSpinCtrl(wxT(""), m_nFromOctave, INT_MAX, INT_MIN); + m_pSpin_FromOctave->SetName(_("from Octave")); + m_pSpin_FromOctave->SetSizeHints(50, -1); + + m_pChoice_ToPitch = S.Id(ID_ToPitch).AddChoice(_("to"), wxT(""), &pitch); + m_pChoice_ToPitch->SetName(_("to")); + m_pChoice_ToPitch->SetSizeHints(80, -1); + + m_pSpin_ToOctave = + S.Id(ID_ToOctave).AddSpinCtrl(wxT(""), m_nToOctave, INT_MAX, INT_MIN); + m_pSpin_ToOctave->SetName(_("to Octave")); + m_pSpin_ToOctave->SetSizeHints(50, -1); + } + S.EndMultiColumn(); + + S.StartHorizontalLay(wxALIGN_CENTER); + { + FloatingPointValidator vldSemitones(3, &m_dSemitonesChange, NUM_VAL_NO_TRAILING_ZEROES | NUM_VAL_ZERO_AS_BLANK); + m_pTextCtrl_SemitonesChange = + S.Id(ID_SemitonesChange).AddTextBox(_("Semitones (half-steps):"), wxT(""), 12); + m_pTextCtrl_SemitonesChange->SetName(_("Semitones (half-steps)")); + m_pTextCtrl_SemitonesChange->SetValidator(vldSemitones); + } + S.EndHorizontalLay(); + } + S.EndStatic(); + + S.StartStatic(_("Frequency")); + { + S.StartMultiColumn(5, wxALIGN_CENTER); // 5, because AddTextBox adds a wxStaticText and a wxTextCtrl. + { + FloatingPointValidator vldFromFrequency(3, &m_FromFrequency, NUM_VAL_NO_TRAILING_ZEROES); + vldFromFrequency.SetMin(0.0); + m_pTextCtrl_FromFrequency = S.Id(ID_FromFrequency).AddTextBox(_("from"), wxT(""), 12); + m_pTextCtrl_FromFrequency->SetName(_("from (Hz)")); + m_pTextCtrl_FromFrequency->SetValidator(vldFromFrequency); + + FloatingPointValidator vldToFrequency(3, &m_ToFrequency, NUM_VAL_NO_TRAILING_ZEROES); + vldToFrequency.SetMin(0.0); + m_pTextCtrl_ToFrequency = S.Id(ID_ToFrequency).AddTextBox(_("to"), wxT(""), 12); + m_pTextCtrl_ToFrequency->SetName(_("to (Hz)")); + m_pTextCtrl_ToFrequency->SetValidator(vldToFrequency); + + S.AddUnits(_("Hz")); + } + S.EndMultiColumn(); + + S.StartHorizontalLay(wxALIGN_CENTER); + { + FloatingPointValidator vldPercentage(3, &m_dPercentChange, NUM_VAL_NO_TRAILING_ZEROES); + vldPercentage.SetRange(MIN_Percentage, MAX_Percentage); + m_pTextCtrl_PercentChange = S.Id(ID_PercentChange).AddTextBox(_("Percent Change:"), wxT(""), 12); + m_pTextCtrl_PercentChange->SetValidator(vldPercentage); + } + S.EndHorizontalLay(); + + S.StartHorizontalLay(wxEXPAND); + { + S.SetStyle(wxSL_HORIZONTAL); + m_pSlider_PercentChange = S.Id(ID_PercentChange) + .AddSlider(wxT(""), 0, (int)kSliderMax, (int)MIN_Percentage); + m_pSlider_PercentChange->SetName(_("Percent Change")); + } + S.EndHorizontalLay(); + } + S.EndStatic(); + } + S.EndVerticalLay(); + + return; +} + +bool EffectChangePitch::TransferDataToWindow() +{ + m_bLoopDetect = true; + + if (!mUIParent->TransferDataToWindow()) + { + return false; + } + + // from/to pitch controls + m_pChoice_FromPitch->SetSelection(m_nFromPitch); + m_pSpin_FromOctave->SetValue(m_nFromOctave); + Update_Choice_ToPitch(); + Update_Spin_ToOctave(); + + // percent change controls + Update_Slider_PercentChange(); + + m_bLoopDetect = false; + + return true; +} + +bool EffectChangePitch::TransferDataFromWindow() +{ + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } + + // from/to pitch controls + m_nFromPitch = m_pChoice_FromPitch->GetSelection(); + m_nFromOctave = m_pSpin_FromOctave->GetValue(); + + m_nToPitch = m_pChoice_ToPitch->GetSelection(); + + // No need to update Slider_PercentChange here because TextCtrl_PercentChange + // always tracks it & is more precise (decimal points). + + return true; +} + +// EffectChangePitch implementation + // Deduce m_FromFrequency from the samples at the beginning of // the selection. Then set some other params accordingly. void EffectChangePitch::DeduceFrequencies() @@ -127,339 +407,9 @@ void EffectChangePitch::DeduceFrequencies() } } -bool EffectChangePitch::PromptUser() -{ - this->DeduceFrequencies(); // Set frequency-related control values based on sample. - - ChangePitchDialog dlog(this, mParent, m_dSemitonesChange, m_dStartFrequency); - dlog.CentreOnParent(); - dlog.ShowModal(); - - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; - - m_dSemitonesChange = dlog.m_dSemitonesChange; - m_dStartFrequency = dlog.m_FromFrequency; - m_dPercentChange = dlog.m_dPercentChange; - return true; -} - -bool EffectChangePitch::TransferParameters( Shuttle & shuttle ) -{ - // Vaughan, 2013-06: Long lost to history, I don't see why m_dPercentChange was chosen to be shuttled. - // Only m_dSemitonesChange is used in Process(). - shuttle.TransferDouble(wxT("Percentage"),m_dPercentChange,0.0); - m_dSemitonesChange = (12.0 * log((100.0 + m_dPercentChange) / 100.0)) / log(2.0); - return true; -} - -bool EffectChangePitch::Process() -{ - mSoundTouch = new SoundTouch(); - SetTimeWarper(new IdentityTimeWarper()); - mSoundTouch->setPitchSemiTones((float)(m_dSemitonesChange)); -#ifdef USE_MIDI - // Note: m_dSemitonesChange is private to ChangePitch because it only - // needs to pass it along to mSoundTouch (above). I added mSemitones - // to SoundTouchEffect (the super class) to convey this value - // to process Note tracks. This approach minimizes changes to existing - // code, but it would be cleaner to change all m_dSemitonesChange to - // mSemitones, make mSemitones exist with or without USE_MIDI, and - // eliminate the next line: - mSemitones = m_dSemitonesChange; -#endif - return this->EffectSoundTouch::Process(); -} - -//---------------------------------------------------------------------------- -// ChangePitchDialog -//---------------------------------------------------------------------------- - -// Soundtouch is not reasonable below -99% or above 3000%. -// We warp the slider to go up to 400%, but user can enter up to 3000% -#define PERCENTCHANGE_MIN -99.0 -#define PERCENTCHANGE_MAX_SLIDER 100.0 // warped above zero to actually go up to 400% -#define PERCENTCHANGE_MAX_TEXT 3000.0 -#define PERCENTCHANGE_SLIDER_WARP 1.30105 // warp power takes max from 100 to 400. - -enum { - ID_TEXT_PERCENTCHANGE = 10001, - ID_SLIDER_PERCENTCHANGE, - ID_CHOICE_FROMPITCH, - ID_SPIN_FROMOCTAVE, - ID_CHOICE_TOPITCH, - ID_SPIN_TOOCTAVE, - ID_TEXT_SEMITONESCHANGE, - ID_TEXT_FROMFREQUENCY, - ID_TEXT_TOFREQUENCY -}; - -// event table for ChangePitchDialog - -BEGIN_EVENT_TABLE(ChangePitchDialog, EffectDialog) - EVT_CHOICE(ID_CHOICE_FROMPITCH, ChangePitchDialog::OnChoice_FromPitch) - EVT_TEXT(ID_SPIN_FROMOCTAVE, ChangePitchDialog::OnSpin_FromOctave) - EVT_CHOICE(ID_CHOICE_TOPITCH, ChangePitchDialog::OnChoice_ToPitch) - EVT_TEXT(ID_SPIN_TOOCTAVE, ChangePitchDialog::OnSpin_ToOctave) - - EVT_TEXT(ID_TEXT_SEMITONESCHANGE, ChangePitchDialog::OnText_SemitonesChange) - - EVT_TEXT(ID_TEXT_FROMFREQUENCY, ChangePitchDialog::OnText_FromFrequency) - EVT_TEXT(ID_TEXT_TOFREQUENCY, ChangePitchDialog::OnText_ToFrequency) - - EVT_TEXT(ID_TEXT_PERCENTCHANGE, ChangePitchDialog::OnText_PercentChange) - EVT_SLIDER(ID_SLIDER_PERCENTCHANGE, ChangePitchDialog::OnSlider_PercentChange) - - EVT_BUTTON(ID_EFFECT_PREVIEW, ChangePitchDialog::OnPreview) -END_EVENT_TABLE() - -ChangePitchDialog::ChangePitchDialog(EffectChangePitch *effect, wxWindow *parent, - double dSemitonesChange, double dStartFrequency) -: EffectDialog(parent, _("Change Pitch"), PROCESS_EFFECT), - mEffect(effect) -{ - m_bLoopDetect = false; - - // NULL out these control members because there are some cases where the - // event table handlers get called during this method, and those handlers that - // can cause trouble check for NULL. - m_pChoice_FromPitch = NULL; - m_pSpin_FromOctave = NULL; - m_pChoice_ToPitch = NULL; - m_pSpin_ToOctave = NULL; - - m_pTextCtrl_SemitonesChange = NULL; - - m_pTextCtrl_FromFrequency = NULL; - m_pTextCtrl_ToFrequency = NULL; - - m_pTextCtrl_PercentChange = NULL; - m_pSlider_PercentChange = NULL; - - // effect parameters - double dFromMIDInote = FreqToMIDInote(dStartFrequency); - double dToMIDInote = dFromMIDInote + dSemitonesChange; - m_nFromPitch = PitchIndex(dFromMIDInote); - m_nFromOctave = PitchOctave(dFromMIDInote); - m_nToPitch = PitchIndex(dToMIDInote); - m_nToOctave = PitchOctave(dToMIDInote); - - m_dSemitonesChange = dSemitonesChange; - - m_FromFrequency = dStartFrequency; - this->Calc_PercentChange(); - this->Calc_ToFrequency(); - - Init(); -} - -void ChangePitchDialog::PopulateOrExchange(ShuttleGui & S) -{ - wxTextValidator nullvld(wxFILTER_INCLUDE_CHAR_LIST); - wxTextValidator numvld(wxFILTER_NUMERIC); - - wxTextValidator nonNegNumValidator(wxFILTER_INCLUDE_CHAR_LIST); // like wxFILTER_NUMERIC, but disallow negative numbers. - wxArrayString aIncludes; - aIncludes.Add(wxT("0")); - aIncludes.Add(wxT("1")); - aIncludes.Add(wxT("2")); - aIncludes.Add(wxT("3")); - aIncludes.Add(wxT("4")); - aIncludes.Add(wxT("5")); - aIncludes.Add(wxT("6")); - aIncludes.Add(wxT("7")); - aIncludes.Add(wxT("8")); - aIncludes.Add(wxT("9")); - aIncludes.Add(wxT(".")); - nonNegNumValidator.SetIncludes(aIncludes); - - wxArrayString pitch; - pitch.Add(wxT("C")); - pitch.Add(wxT("C#/Db")); - pitch.Add(wxT("D")); - pitch.Add(wxT("D#/Eb")); - pitch.Add(wxT("E")); - pitch.Add(wxT("F")); - pitch.Add(wxT("F#/Gb")); - pitch.Add(wxT("G")); - pitch.Add(wxT("G#/Ab")); - pitch.Add(wxT("A")); - pitch.Add(wxT("A#/Bb")); - pitch.Add(wxT("B")); - - S.SetBorder(5); - - S.StartVerticalLay(); - { - S.AddTitle(_("Change Pitch without Changing Tempo")); - S.AddTitle( - wxString::Format(_("Estimated Start Pitch: %s%d (%.3f Hz)"), - pitch[m_nFromPitch].c_str(), m_nFromOctave, m_FromFrequency)); - } - S.EndVerticalLay(); - - /* i18n-hint: (noun) Musical pitch.*/ - S.StartStatic(_("Pitch")); - { - S.StartMultiColumn(6, wxALIGN_CENTER); // 6 controls, because each AddChoice adds a wxStaticText and a wxChoice. - { - m_pChoice_FromPitch = S.Id(ID_CHOICE_FROMPITCH).AddChoice(_("from"), wxT(""), &pitch); - m_pChoice_FromPitch->SetName(_("from")); - m_pChoice_FromPitch->SetSizeHints(80, -1); - - m_pSpin_FromOctave = S.Id(ID_SPIN_FROMOCTAVE).AddSpinCtrl(wxT(""), m_nFromOctave, INT_MAX, INT_MIN); - m_pSpin_FromOctave->SetName(_("from Octave")); - m_pSpin_FromOctave->SetSizeHints(50, -1); - - m_pChoice_ToPitch = S.Id(ID_CHOICE_TOPITCH).AddChoice(_("to"), wxT(""), &pitch); - m_pChoice_ToPitch->SetName(_("to")); - m_pChoice_ToPitch->SetSizeHints(80, -1); - - m_pSpin_ToOctave = - S.Id(ID_SPIN_TOOCTAVE).AddSpinCtrl(wxT(""), m_nToOctave, INT_MAX, INT_MIN); - m_pSpin_ToOctave->SetName(_("to Octave")); - m_pSpin_ToOctave->SetSizeHints(50, -1); - } - S.EndMultiColumn(); - - S.StartHorizontalLay(wxALIGN_CENTER); - { - m_pTextCtrl_SemitonesChange = - S.Id(ID_TEXT_SEMITONESCHANGE).AddTextBox(_("Semitones (half-steps):"), wxT(""), 12); - m_pTextCtrl_SemitonesChange->SetName(_("Semitones (half-steps)")); - m_pTextCtrl_SemitonesChange->SetValidator(numvld); - } - S.EndHorizontalLay(); - } - S.EndStatic(); - - S.StartStatic(_("Frequency")); - { - S.StartMultiColumn(5, wxALIGN_CENTER); // 5, because AddTextBox adds a wxStaticText and a wxTextCtrl. - { - m_pTextCtrl_FromFrequency = S.Id(ID_TEXT_FROMFREQUENCY).AddTextBox(_("from"), wxT(""), 12); - m_pTextCtrl_FromFrequency->SetName(_("from (Hz)")); - m_pTextCtrl_FromFrequency->SetValidator(nonNegNumValidator); - - m_pTextCtrl_ToFrequency = S.Id(ID_TEXT_TOFREQUENCY).AddTextBox(_("to"), wxT(""), 12); - m_pTextCtrl_ToFrequency->SetName(_("to (Hz)")); - m_pTextCtrl_ToFrequency->SetValidator(nonNegNumValidator); - - S.AddUnits(_("Hz")); - } - S.EndMultiColumn(); - - S.StartHorizontalLay(wxALIGN_CENTER); - { - m_pTextCtrl_PercentChange = S.Id(ID_TEXT_PERCENTCHANGE).AddTextBox(_("Percent Change:"), wxT(""), 12); - m_pTextCtrl_PercentChange->SetValidator(numvld); - } - S.EndHorizontalLay(); - - S.StartHorizontalLay(wxEXPAND); - { - S.SetStyle(wxSL_HORIZONTAL); - m_pSlider_PercentChange = S.Id(ID_SLIDER_PERCENTCHANGE) - .AddSlider(wxT(""), 0, (int)PERCENTCHANGE_MAX_SLIDER, (int)PERCENTCHANGE_MIN); - m_pSlider_PercentChange->SetName(_("Percent Change")); - } - S.EndHorizontalLay(); - } - S.EndStatic(); -} - -bool ChangePitchDialog::TransferDataToWindow() -{ - m_bLoopDetect = true; - - // from/to pitch controls - if (m_pChoice_FromPitch) - m_pChoice_FromPitch->SetSelection(m_nFromPitch); - if (m_pSpin_FromOctave) - m_pSpin_FromOctave->SetValue(m_nFromOctave); - this->Update_Choice_ToPitch(); - this->Update_Spin_ToOctave(); - - // semitones change control - this->Update_Text_SemitonesChange(); - - // from/to frequency controls - if (m_pTextCtrl_FromFrequency) { - wxString str; - if ((m_ToFrequency > 0.0) && (m_ToFrequency <= DBL_MAX)) - str.Printf(wxT("%.3f"), m_FromFrequency); - else - str = wxT(""); - m_pTextCtrl_FromFrequency->SetValue(str); - } - - this->Update_Text_ToFrequency(); - - // percent change controls - this->Update_Text_PercentChange(); - this->Update_Slider_PercentChange(); - - m_bLoopDetect = false; - - return true; -} - -bool ChangePitchDialog::TransferDataFromWindow() -{ - double newDouble; - wxString str; - - - // from/to pitch controls - if (m_pChoice_FromPitch) - m_nFromPitch = m_pChoice_FromPitch->GetSelection(); - if (m_pSpin_FromOctave) - m_nFromOctave = m_pSpin_FromOctave->GetValue(); - - if (m_pChoice_ToPitch) - m_nToPitch = m_pChoice_ToPitch->GetSelection(); - - - // semitones change control - if (m_pTextCtrl_SemitonesChange) { - str = m_pTextCtrl_SemitonesChange->GetValue(); - str.ToDouble(&newDouble); - m_dSemitonesChange = newDouble; - } - - - // from/to frequency controls - if (m_pTextCtrl_FromFrequency) { - str = m_pTextCtrl_FromFrequency->GetValue(); - str.ToDouble(&newDouble); - m_FromFrequency = newDouble; - } - - if (m_pTextCtrl_ToFrequency) { - str = m_pTextCtrl_ToFrequency->GetValue(); - str.ToDouble(&newDouble); - m_ToFrequency = newDouble; - } - - - // percent change controls - if (m_pTextCtrl_PercentChange) { - str = m_pTextCtrl_PercentChange->GetValue(); - str.ToDouble(&newDouble); - m_dPercentChange = newDouble; - } - - // No need to update Slider_PercentChange here because TextCtrl_PercentChange - // always tracks it & is more precise (decimal points). - - - return true; -} - - // calculations -void ChangePitchDialog::Calc_ToPitch() +void EffectChangePitch::Calc_ToPitch() { int nSemitonesChange = (int)(m_dSemitonesChange + ((m_dSemitonesChange < 0.0) ? -0.5 : 0.5)); @@ -468,30 +418,30 @@ void ChangePitchDialog::Calc_ToPitch() m_nToPitch += 12; } -void ChangePitchDialog::Calc_ToOctave() +void EffectChangePitch::Calc_ToOctave() { m_nToOctave = PitchOctave(FreqToMIDInote(m_ToFrequency)); } -void ChangePitchDialog::Calc_SemitonesChange_fromPitches() +void EffectChangePitch::Calc_SemitonesChange_fromPitches() { m_dSemitonesChange = PitchToMIDInote(m_nToPitch, m_nToOctave) - PitchToMIDInote(m_nFromPitch, m_nFromOctave); } -void ChangePitchDialog::Calc_SemitonesChange_fromPercentChange() +void EffectChangePitch::Calc_SemitonesChange_fromPercentChange() { // Use m_dPercentChange rather than m_FromFrequency & m_ToFrequency, because // they start out uninitialized, but m_dPercentChange is always valid. m_dSemitonesChange = (12.0 * log((100.0 + m_dPercentChange) / 100.0)) / log(2.0); } -void ChangePitchDialog::Calc_ToFrequency() +void EffectChangePitch::Calc_ToFrequency() { m_ToFrequency = (m_FromFrequency * (100.0 + m_dPercentChange)) / 100.0; } -void ChangePitchDialog::Calc_PercentChange() +void EffectChangePitch::Calc_PercentChange() { m_dPercentChange = 100.0 * (pow(2.0, (m_dSemitonesChange / 12.0)) - 1.0); } @@ -499,420 +449,316 @@ void ChangePitchDialog::Calc_PercentChange() // handlers -void ChangePitchDialog::OnChoice_FromPitch(wxCommandEvent & WXUNUSED(event)) +void EffectChangePitch::OnChoice_FromPitch(wxCommandEvent & WXUNUSED(evt)) { if (m_bLoopDetect) return; - if (m_pChoice_FromPitch) { - m_nFromPitch = m_pChoice_FromPitch->GetSelection(); - m_FromFrequency = PitchToFreq(m_nFromPitch, m_nFromOctave); + m_nFromPitch = m_pChoice_FromPitch->GetSelection(); + m_FromFrequency = PitchToFreq(m_nFromPitch, m_nFromOctave); - this->Calc_ToPitch(); - this->Calc_ToFrequency(); - this->Calc_ToOctave(); // Call after Calc_ToFrequency(). + Calc_ToPitch(); + Calc_ToFrequency(); + Calc_ToOctave(); // Call after Calc_ToFrequency(). - m_bLoopDetect = true; - { - this->Update_Choice_ToPitch(); - this->Update_Spin_ToOctave(); - this->Update_Text_FromFrequency(); - this->Update_Text_ToFrequency(); - } - m_bLoopDetect = false; - } -} - -void ChangePitchDialog::OnSpin_FromOctave(wxCommandEvent & WXUNUSED(event)) -{ - if (m_bLoopDetect) - return; - - if (m_pSpin_FromOctave) + m_bLoopDetect = true; { - m_nFromOctave = m_pSpin_FromOctave->GetValue(); - //vvv If I change this code to not keep semitones and percent constant, - // will need validation code as in OnSpin_ToOctave. - m_FromFrequency = PitchToFreq(m_nFromPitch, m_nFromOctave); - - this->Calc_ToFrequency(); - this->Calc_ToOctave(); // Call after Calc_ToFrequency(). - - m_bLoopDetect = true; - { - this->Update_Spin_ToOctave(); - this->Update_Text_FromFrequency(); - this->Update_Text_ToFrequency(); - } - m_bLoopDetect = false; + Update_Choice_ToPitch(); + Update_Spin_ToOctave(); + Update_Text_FromFrequency(); + Update_Text_ToFrequency(); } + m_bLoopDetect = false; } -void ChangePitchDialog::OnChoice_ToPitch(wxCommandEvent & WXUNUSED(event)) +void EffectChangePitch::OnSpin_FromOctave(wxCommandEvent & WXUNUSED(evt)) { if (m_bLoopDetect) return; - if (m_pChoice_ToPitch) + m_nFromOctave = m_pSpin_FromOctave->GetValue(); + //vvv If I change this code to not keep semitones and percent constant, + // will need validation code as in OnSpin_ToOctave. + m_FromFrequency = PitchToFreq(m_nFromPitch, m_nFromOctave); + + Calc_ToFrequency(); + Calc_ToOctave(); // Call after Calc_ToFrequency(). + + m_bLoopDetect = true; { - m_nToPitch = m_pChoice_ToPitch->GetSelection(); - - this->Calc_SemitonesChange_fromPitches(); - this->Calc_PercentChange(); // Call *after* m_dSemitonesChange is updated. - this->Calc_ToFrequency(); // Call *after* m_dPercentChange is updated. - - m_bLoopDetect = true; - { - this->Update_Text_SemitonesChange(); - this->Update_Text_ToFrequency(); - this->Update_Text_PercentChange(); - this->Update_Slider_PercentChange(); - } - m_bLoopDetect = false; + Update_Spin_ToOctave(); + Update_Text_FromFrequency(); + Update_Text_ToFrequency(); } + m_bLoopDetect = false; } -void ChangePitchDialog::OnSpin_ToOctave(wxCommandEvent & WXUNUSED(event)) +void EffectChangePitch::OnChoice_ToPitch(wxCommandEvent & WXUNUSED(evt)) { if (m_bLoopDetect) return; - if (m_pSpin_ToOctave) + m_nToPitch = m_pChoice_ToPitch->GetSelection(); + + Calc_SemitonesChange_fromPitches(); + Calc_PercentChange(); // Call *after* m_dSemitonesChange is updated. + Calc_ToFrequency(); // Call *after* m_dPercentChange is updated. + + m_bLoopDetect = true; { - int nNewValue = m_pSpin_ToOctave->GetValue(); - // Validation: Rather than set a range for octave numbers, enforce a range that - // keeps m_dPercentChange above -99%, per Soundtouch constraints. - if ((nNewValue + 3) < m_nFromOctave) - { - ::wxBell(); - m_pSpin_ToOctave->SetValue(m_nFromOctave - 3); - return; - } - m_nToOctave = nNewValue; - - m_ToFrequency = PitchToFreq(m_nToPitch, m_nToOctave); - - this->Calc_SemitonesChange_fromPitches(); - this->Calc_PercentChange(); // Call *after* m_dSemitonesChange is updated. - - m_bLoopDetect = true; - { - this->Update_Text_SemitonesChange(); - this->Update_Text_ToFrequency(); - this->Update_Text_PercentChange(); - this->Update_Slider_PercentChange(); - } - m_bLoopDetect = false; + Update_Text_SemitonesChange(); + Update_Text_ToFrequency(); + Update_Text_PercentChange(); + Update_Slider_PercentChange(); } + m_bLoopDetect = false; } -void ChangePitchDialog::OnText_SemitonesChange(wxCommandEvent & WXUNUSED(event)) +void EffectChangePitch::OnSpin_ToOctave(wxCommandEvent & WXUNUSED(evt)) { if (m_bLoopDetect) return; - if (m_pTextCtrl_SemitonesChange) { - wxString str = m_pTextCtrl_SemitonesChange->GetValue(); - if (str.IsEmpty()) - m_dSemitonesChange = 0.0; - else - { - double newValue = 0; - str.ToDouble(&newValue); - m_dSemitonesChange = newValue; - } - - this->Calc_PercentChange(); - this->Calc_ToFrequency(); // Call *after* m_dPercentChange is updated. - this->Calc_ToPitch(); - this->Calc_ToOctave(); // Call after Calc_ToFrequency(). - - m_bLoopDetect = true; - { - this->Update_Choice_ToPitch(); - this->Update_Spin_ToOctave(); - this->Update_Text_ToFrequency(); - this->Update_Text_PercentChange(); - this->Update_Slider_PercentChange(); - } - m_bLoopDetect = false; - - // If m_dSemitonesChange is a big enough negative, we can go to or below 0 freq. - // If m_dSemitonesChange is a big enough positive, we can go to 1.#INF (Windows) or inf (Linux). - // But practically, these are best limits for Soundtouch. - bool bIsGoodValue = (m_dSemitonesChange > -80.0) && (m_dSemitonesChange <= 60.0); - this->FindWindow(wxID_OK)->Enable(bIsGoodValue); - this->FindWindow(ID_EFFECT_PREVIEW)->Enable(bIsGoodValue); - } -} - -void ChangePitchDialog::OnText_FromFrequency(wxCommandEvent & WXUNUSED(event)) -{ - if (m_bLoopDetect) - return; - - if (m_pTextCtrl_FromFrequency) { - wxString str = m_pTextCtrl_FromFrequency->GetValue(); - double newDouble; - str.ToDouble(&newDouble); - // Empty string causes unpredictable results with ToDouble() and later calculations. - // Non-positive frequency makes no sense, but user might still be editing, - // so it's not an error, but we do not want to update the values/controls. - if (str.IsEmpty() || (newDouble <= 0.0) || (newDouble > DBL_MAX)) - { - this->FindWindow(wxID_OK)->Disable(); - this->FindWindow(ID_EFFECT_PREVIEW)->Disable(); - return; - } - m_FromFrequency = newDouble; - - double newFromMIDInote = FreqToMIDInote(m_FromFrequency); - m_nFromPitch = PitchIndex(newFromMIDInote); - m_nFromOctave = PitchOctave(newFromMIDInote); - this->Calc_ToPitch(); - this->Calc_ToFrequency(); - this->Calc_ToOctave(); // Call after Calc_ToFrequency(). - - m_bLoopDetect = true; - { - this->Update_Choice_FromPitch(); - this->Update_Spin_FromOctave(); - this->Update_Choice_ToPitch(); - this->Update_Spin_ToOctave(); - this->Update_Text_ToFrequency(); - } - m_bLoopDetect = false; - - // Success. Make sure OK and Preview are enabled, in case we disabled above during editing. - this->FindWindow(wxID_OK)->Enable(); - this->FindWindow(ID_EFFECT_PREVIEW)->Enable(); -} -} - -void ChangePitchDialog::OnText_ToFrequency(wxCommandEvent & WXUNUSED(event)) -{ - if (m_bLoopDetect) - return; - - if (m_pTextCtrl_ToFrequency) { - wxString str = m_pTextCtrl_ToFrequency->GetValue(); - double newDouble; - str.ToDouble(&newDouble); - // Empty string causes unpredictable results with ToDouble() and later calculations. - // Non-positive frequency makes no sense, but user might still be editing, - // so it's not an error, but we do not want to update the values/controls. - if (str.IsEmpty() || (newDouble <= 0.0) ) - { - this->FindWindow(wxID_OK)->Disable(); - this->FindWindow(ID_EFFECT_PREVIEW)->Disable(); - return; - } - m_ToFrequency = newDouble; - - m_dPercentChange = (((double)(m_ToFrequency) * 100.0) / - (double)(m_FromFrequency)) - 100.0; - - this->Calc_ToOctave(); // Call after Calc_ToFrequency(). - this->Calc_SemitonesChange_fromPercentChange(); - this->Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated. - - m_bLoopDetect = true; - { - this->Update_Choice_ToPitch(); - this->Update_Spin_ToOctave(); - this->Update_Text_SemitonesChange(); - this->Update_Text_PercentChange(); - this->Update_Slider_PercentChange(); - } - m_bLoopDetect = false; - - // Success. Make sure OK and Preview are disabled if percent change is out of bounds. - // Can happen while editing. - // If the value is good, might also need to re-enable because of above clause. - bool bIsGoodValue = (m_dPercentChange > PERCENTCHANGE_MIN) && (m_dPercentChange <= PERCENTCHANGE_MAX_TEXT); - this->FindWindow(wxID_OK)->Enable(bIsGoodValue); - this->FindWindow(ID_EFFECT_PREVIEW)->Enable(bIsGoodValue); - } -} - -void ChangePitchDialog::OnText_PercentChange(wxCommandEvent & WXUNUSED(event)) -{ - if (m_bLoopDetect) - return; - - if (m_pTextCtrl_PercentChange) { - wxString str = m_pTextCtrl_PercentChange->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - // User might still be editing, so out of bounds is not an error, - // but we do not want to update the values/controls. - if (str.IsEmpty() || (newValue < PERCENTCHANGE_MIN) || (newValue > PERCENTCHANGE_MAX_TEXT)) - { - this->FindWindow(wxID_OK)->Disable(); - this->FindWindow(ID_EFFECT_PREVIEW)->Disable(); - return; - } - m_dPercentChange = newValue; - - this->Calc_SemitonesChange_fromPercentChange(); - this->Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated. - this->Calc_ToFrequency(); - this->Calc_ToOctave(); // Call after Calc_ToFrequency(). - - m_bLoopDetect = true; - { - this->Update_Choice_ToPitch(); - this->Update_Spin_ToOctave(); - this->Update_Text_SemitonesChange(); - this->Update_Text_ToFrequency(); - this->Update_Slider_PercentChange(); - } - m_bLoopDetect = false; - - // Success. Make sure OK and Preview are enabled, in case we disabled above during editing. - this->FindWindow(wxID_OK)->Enable(); - this->FindWindow(ID_EFFECT_PREVIEW)->Enable(); - } -} - -void ChangePitchDialog::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(event)) -{ - if (m_bLoopDetect) - return; - - if (m_pSlider_PercentChange) { - m_dPercentChange = (double)(m_pSlider_PercentChange->GetValue()); - // Warp positive values to actually go up faster & further than negatives. - if (m_dPercentChange > 0.0) - m_dPercentChange = pow(m_dPercentChange, PERCENTCHANGE_SLIDER_WARP); - - this->Calc_SemitonesChange_fromPercentChange(); - this->Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated. - this->Calc_ToFrequency(); - this->Calc_ToOctave(); // Call after Calc_ToFrequency(). - - m_bLoopDetect = true; - { - this->Update_Choice_ToPitch(); - this->Update_Spin_ToOctave(); - this->Update_Text_SemitonesChange(); - this->Update_Text_ToFrequency(); - this->Update_Text_PercentChange(); - } - m_bLoopDetect = false; - } -} - -void ChangePitchDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - - // Save & restore parameters around Preview, because we didn't do OK. - double oldSemitonesChange = m_dSemitonesChange; - if( m_dPercentChange < PERCENTCHANGE_MIN) + int nNewValue = m_pSpin_ToOctave->GetValue(); + // Validation: Rather than set a range for octave numbers, enforce a range that + // keeps m_dPercentChange above -99%, per Soundtouch constraints. + if ((nNewValue + 3) < m_nFromOctave) { - m_dPercentChange = PERCENTCHANGE_MIN; - this->Update_Text_PercentChange(); + ::wxBell(); + m_pSpin_ToOctave->SetValue(m_nFromOctave - 3); + return; } - mEffect->m_dSemitonesChange = m_dSemitonesChange; - mEffect->Preview(); - mEffect->m_dSemitonesChange = oldSemitonesChange; + m_nToOctave = nNewValue; + + m_ToFrequency = PitchToFreq(m_nToPitch, m_nToOctave); + + Calc_SemitonesChange_fromPitches(); + Calc_PercentChange(); // Call *after* m_dSemitonesChange is updated. + + m_bLoopDetect = true; + { + Update_Text_SemitonesChange(); + Update_Text_ToFrequency(); + Update_Text_PercentChange(); + Update_Slider_PercentChange(); + } + m_bLoopDetect = false; +} + +void EffectChangePitch::OnText_SemitonesChange(wxCommandEvent & WXUNUSED(evt)) +{ + if (m_bLoopDetect) + return; + + if (!m_pTextCtrl_SemitonesChange->GetValidator()->TransferFromWindow()) + { + EnableApply(false); + return; + } + + Calc_PercentChange(); + Calc_ToFrequency(); // Call *after* m_dPercentChange is updated. + Calc_ToPitch(); + Calc_ToOctave(); // Call after Calc_ToFrequency(). + + m_bLoopDetect = true; + { + Update_Choice_ToPitch(); + Update_Spin_ToOctave(); + Update_Text_ToFrequency(); + Update_Text_PercentChange(); + Update_Slider_PercentChange(); + } + m_bLoopDetect = false; + + // If m_dSemitonesChange is a big enough negative, we can go to or below 0 freq. + // If m_dSemitonesChange is a big enough positive, we can go to 1.#INF (Windows) or inf (Linux). + // But practically, these are best limits for Soundtouch. + bool bIsGoodValue = (m_dSemitonesChange > -80.0) && (m_dSemitonesChange <= 60.0); + EnableApply(bIsGoodValue); +} + +void EffectChangePitch::OnText_FromFrequency(wxCommandEvent & WXUNUSED(evt)) +{ + if (m_bLoopDetect) + return; + + // Empty string causes unpredictable results with ToDouble() and later calculations. + // Non-positive frequency makes no sense, but user might still be editing, + // so it's not an error, but we do not want to update the values/controls. + if (!m_pTextCtrl_FromFrequency->GetValidator()->TransferFromWindow()) + { + EnableApply(false); + return; + } + + double newFromMIDInote = FreqToMIDInote(m_FromFrequency); + m_nFromPitch = PitchIndex(newFromMIDInote); + m_nFromOctave = PitchOctave(newFromMIDInote); + Calc_ToPitch(); + Calc_ToFrequency(); + Calc_ToOctave(); // Call after Calc_ToFrequency(). + + m_bLoopDetect = true; + { + Update_Choice_FromPitch(); + Update_Spin_FromOctave(); + Update_Choice_ToPitch(); + Update_Spin_ToOctave(); + Update_Text_ToFrequency(); + } + m_bLoopDetect = false; + + // Success. Make sure OK and Preview are enabled, in case we disabled above during editing. + EnableApply(true); +} + +void EffectChangePitch::OnText_ToFrequency(wxCommandEvent & WXUNUSED(evt)) +{ + if (m_bLoopDetect) + return; + + // Empty string causes unpredictable results with ToDouble() and later calculations. + // Non-positive frequency makes no sense, but user might still be editing, + // so it's not an error, but we do not want to update the values/controls. + if (!m_pTextCtrl_ToFrequency->GetValidator()->TransferFromWindow()) + { + EnableApply(false); + return; + } + + m_dPercentChange = ((m_ToFrequency * 100.0) / m_FromFrequency) - 100.0; + + Calc_ToOctave(); // Call after Calc_ToFrequency(). + Calc_SemitonesChange_fromPercentChange(); + Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated. + + m_bLoopDetect = true; + { + Update_Choice_ToPitch(); + Update_Spin_ToOctave(); + Update_Text_SemitonesChange(); + Update_Text_PercentChange(); + Update_Slider_PercentChange(); + } + m_bLoopDetect = false; + + // Success. Make sure OK and Preview are disabled if percent change is out of bounds. + // Can happen while editing. + // If the value is good, might also need to re-enable because of above clause. + bool bIsGoodValue = (m_dPercentChange > MIN_Percentage) && (m_dPercentChange <= MAX_Percentage); + EnableApply(bIsGoodValue); +} + +void EffectChangePitch::OnText_PercentChange(wxCommandEvent & WXUNUSED(evt)) +{ + if (m_bLoopDetect) + return; + + if (!m_pTextCtrl_PercentChange->GetValidator()->TransferFromWindow()) + { + EnableApply(false); + return; + } + + Calc_SemitonesChange_fromPercentChange(); + Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated. + Calc_ToFrequency(); + Calc_ToOctave(); // Call after Calc_ToFrequency(). + + m_bLoopDetect = true; + { + Update_Choice_ToPitch(); + Update_Spin_ToOctave(); + Update_Text_SemitonesChange(); + Update_Text_ToFrequency(); + Update_Slider_PercentChange(); + } + m_bLoopDetect = false; + + // Success. Make sure OK and Preview are enabled, in case we disabled above during editing. + EnableApply(true); +} + +void EffectChangePitch::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(evt)) +{ + if (m_bLoopDetect) + return; + + m_dPercentChange = (double)(m_pSlider_PercentChange->GetValue()); + // Warp positive values to actually go up faster & further than negatives. + if (m_dPercentChange > 0.0) + m_dPercentChange = pow(m_dPercentChange, kSliderWarp); + + Calc_SemitonesChange_fromPercentChange(); + Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated. + Calc_ToFrequency(); + Calc_ToOctave(); // Call after Calc_ToFrequency(). + + m_bLoopDetect = true; + { + Update_Choice_ToPitch(); + Update_Spin_ToOctave(); + Update_Text_SemitonesChange(); + Update_Text_ToFrequency(); + Update_Text_PercentChange(); + } + m_bLoopDetect = false; } // helper fns for controls -void ChangePitchDialog::Update_Choice_FromPitch() +void EffectChangePitch::Update_Choice_FromPitch() { - if (m_pChoice_FromPitch) - m_pChoice_FromPitch->SetSelection(m_nFromPitch); + m_pChoice_FromPitch->SetSelection(m_nFromPitch); } -void ChangePitchDialog::Update_Spin_FromOctave() +void EffectChangePitch::Update_Spin_FromOctave() { - if (m_pSpin_FromOctave) - m_pSpin_FromOctave->SetValue(m_nFromOctave); + m_pSpin_FromOctave->SetValue(m_nFromOctave); } -void ChangePitchDialog::Update_Choice_ToPitch() +void EffectChangePitch::Update_Choice_ToPitch() { - if (m_pChoice_ToPitch) - m_pChoice_ToPitch->SetSelection(m_nToPitch); + m_pChoice_ToPitch->SetSelection(m_nToPitch); } -void ChangePitchDialog::Update_Spin_ToOctave() +void EffectChangePitch::Update_Spin_ToOctave() { - if (m_pSpin_ToOctave) - m_pSpin_ToOctave->SetValue(m_nToOctave); + m_pSpin_ToOctave->SetValue(m_nToOctave); } -void ChangePitchDialog::Update_Text_SemitonesChange() +void EffectChangePitch::Update_Text_SemitonesChange() { - if (m_pTextCtrl_SemitonesChange) { - wxString str; - str.Printf(wxT("%.2f"), m_dSemitonesChange); - m_pTextCtrl_SemitonesChange->SetValue(str); - } + m_pTextCtrl_SemitonesChange->GetValidator()->TransferToWindow(); } -void ChangePitchDialog::Update_Text_FromFrequency() +void EffectChangePitch::Update_Text_FromFrequency() { - if (m_pTextCtrl_FromFrequency) { - wxString str; - if ((m_FromFrequency > 0.0) && (m_FromFrequency <= DBL_MAX)) - str.Printf(wxT("%.3f"), m_FromFrequency); - else - str = wxT(""); - m_pTextCtrl_FromFrequency->SetValue(str); - } + m_pTextCtrl_FromFrequency->GetValidator()->TransferToWindow(); } -void ChangePitchDialog::Update_Text_ToFrequency() +void EffectChangePitch::Update_Text_ToFrequency() { - if (m_pTextCtrl_ToFrequency) { - wxString str; - if ((m_ToFrequency > 0.0) && (m_ToFrequency <= DBL_MAX)) - str.Printf(wxT("%.3f"), m_ToFrequency); - else - str = wxT(""); - m_pTextCtrl_ToFrequency->SetValue(str); - } + m_pTextCtrl_ToFrequency->GetValidator()->TransferToWindow(); } -void ChangePitchDialog::Update_Text_PercentChange() +void EffectChangePitch::Update_Text_PercentChange() { - if (m_pTextCtrl_PercentChange) { - wxString str; - if ((m_ToFrequency > 0.0) && (m_ToFrequency <= DBL_MAX)) - str.Printf(wxT("%.3f"), m_dPercentChange); - else - str = wxT(""); - m_pTextCtrl_PercentChange->SetValue(str); - - bool bIsGoodValue = (m_dPercentChange >= PERCENTCHANGE_MIN) && (m_dPercentChange <= PERCENTCHANGE_MAX_TEXT); - this->FindWindow(wxID_OK)->Enable(bIsGoodValue); - this->FindWindow(ID_EFFECT_PREVIEW)->Enable(bIsGoodValue); - } + m_pTextCtrl_PercentChange->GetValidator()->TransferToWindow(); } -void ChangePitchDialog::Update_Slider_PercentChange() +void EffectChangePitch::Update_Slider_PercentChange() { - if (m_pSlider_PercentChange) { - double unwarped = m_dPercentChange; - if (unwarped > 0.0) - // Un-warp values above zero to actually go up to PERCENTCHANGE_MAX_SLIDER. - unwarped = pow(m_dPercentChange, (1.0 / PERCENTCHANGE_SLIDER_WARP)); + double unwarped = m_dPercentChange; + if (unwarped > 0.0) + // Un-warp values above zero to actually go up to kSliderMax. + unwarped = pow(m_dPercentChange, (1.0 / kSliderWarp)); - // Add 0.5 to unwarped so trunc -> round. - int newSetting = (int)(unwarped + 0.5); - if (newSetting < PERCENTCHANGE_MIN) - newSetting = (int)PERCENTCHANGE_MIN; - if (newSetting > PERCENTCHANGE_MAX_SLIDER) - newSetting = (int)PERCENTCHANGE_MAX_SLIDER; - m_pSlider_PercentChange->SetValue(newSetting); - } + // Add 0.5 to unwarped so trunc -> round. + m_pSlider_PercentChange->SetValue((int)(unwarped + 0.5)); } - #endif // USE_SOUNDTOUCH diff --git a/src/effects/ChangePitch.h b/src/effects/ChangePitch.h index 7ba03249f..0668f677a 100644 --- a/src/effects/ChangePitch.h +++ b/src/effects/ChangePitch.h @@ -20,78 +20,55 @@ the pitch without changing the tempo. #ifndef __AUDACITY_EFFECT_CHANGEPITCH__ #define __AUDACITY_EFFECT_CHANGEPITCH__ -#include "SoundTouchEffect.h" - -#include -#include +#include +#include #include #include +#include +#include + +#include "../ShuttleGui.h" + +#include "SoundTouchEffect.h" + +#define CHANGEPITCH_PLUGIN_SYMBOL wxTRANSLATE("Change Pitch") class EffectChangePitch : public EffectSoundTouch { - public: +public: EffectChangePitch(); + virtual ~EffectChangePitch(); - virtual wxString GetEffectName() { return wxString(wxTRANSLATE("Change Pitch...")); } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#PitchPlugin")); - result.insert(wxT("http://audacityteam.org/namespace#PitchAndTempo")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Change Pitch")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Changing Pitch")); - } + virtual EffectType GetType(); - // Useful only after PromptUser values have been set. - virtual wxString GetEffectDescription(); + // EffectClientInterface implementation + + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + + // Effect implementation virtual bool Init(); + virtual bool Process(); + virtual bool CheckWhetherSkipEffect(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); + +private: + // EffectChangePitch implementation // Deduce m_FromFrequency from the samples at the beginning of // the selection. Then set some other params accordingly. virtual void DeduceFrequencies(); - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); - - virtual bool CheckWhetherSkipEffect() { return (m_dPercentChange == 0.0); } - virtual bool Process(); - -private: - double m_dSemitonesChange; // how many semitones to change pitch - double m_dStartFrequency; // starting frequency of first 0.2s of selection - double m_dPercentChange; // percent change to apply to frequency - -friend class ChangePitchDialog; -}; - -//---------------------------------------------------------------------------- -// ChangePitchDialog -//---------------------------------------------------------------------------- - -class wxChoice; -class wxRadioButton; -class wxString; -class wxTextCtrl; - -class ChangePitchDialog : public EffectDialog -{ - public: - ChangePitchDialog(EffectChangePitch * effect, wxWindow * parent, - double dSemitonesChange, double dStartFrequency); - - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - - private: // calculations void Calc_ToPitch(); // Update m_nToPitch from new m_dSemitonesChange. void Calc_ToOctave(); @@ -102,20 +79,18 @@ class ChangePitchDialog : public EffectDialog void Calc_PercentChange(); // Update m_dPercentChange based on new m_dSemitonesChange. // handlers - void OnChoice_FromPitch(wxCommandEvent & event); - void OnSpin_FromOctave(wxCommandEvent & event); - void OnChoice_ToPitch(wxCommandEvent & event); - void OnSpin_ToOctave(wxCommandEvent & event); + void OnChoice_FromPitch(wxCommandEvent & evt); + void OnSpin_FromOctave(wxCommandEvent & evt); + void OnChoice_ToPitch(wxCommandEvent & evt); + void OnSpin_ToOctave(wxCommandEvent & evt); - void OnText_SemitonesChange(wxCommandEvent & event); + void OnText_SemitonesChange(wxCommandEvent & evt); - void OnText_FromFrequency(wxCommandEvent & event); - void OnText_ToFrequency(wxCommandEvent & event); + void OnText_FromFrequency(wxCommandEvent & evt); + void OnText_ToFrequency(wxCommandEvent & evt); - void OnText_PercentChange(wxCommandEvent & event); - void OnSlider_PercentChange(wxCommandEvent & event); - - void OnPreview( wxCommandEvent &event ); + void OnText_PercentChange(wxCommandEvent & evt); + void OnSlider_PercentChange(wxCommandEvent & evt); // helper fns for controls void Update_Choice_FromPitch(); @@ -131,8 +106,21 @@ class ChangePitchDialog : public EffectDialog void Update_Text_PercentChange(); // Update control per current m_dPercentChange. void Update_Slider_PercentChange(); // Update control per current m_dPercentChange. - private: - EffectChangePitch * mEffect; +private: + // effect parameters + int m_nFromPitch; // per PitchIndex() + int m_nFromOctave; // per PitchOctave() + int m_nToPitch; // per PitchIndex() + int m_nToOctave; // per PitchOctave() + + double m_FromFrequency; // starting frequency of selection + double m_ToFrequency; // target frequency of selection + + double m_dSemitonesChange; // how many semitones to change pitch + double m_dStartFrequency; // starting frequency of first 0.2s of selection + double m_dPercentChange; // percent change to apply to pitch + // Slider is (-100, 200], but textCtrls can set higher. + bool m_bLoopDetect; // Used to avoid loops in initialization and in event handling. // controls @@ -147,26 +135,9 @@ class ChangePitchDialog : public EffectDialog wxTextCtrl * m_pTextCtrl_PercentChange; wxSlider * m_pSlider_PercentChange; - public: - // effect parameters - int m_nFromPitch; // per PitchIndex() - int m_nFromOctave; // per PitchOctave() - int m_nToPitch; // per PitchIndex() - int m_nToOctave; // per PitchOctave() - - double m_dSemitonesChange; // how many semitones to change pitch - - double m_FromFrequency; // starting frequency of selection - double m_ToFrequency; // target frequency of selection - - double m_dPercentChange; // percent change to apply to pitch - // Slider is (-100, 200], but textCtrls can set higher. - - private: - DECLARE_EVENT_TABLE() + DECLARE_EVENT_TABLE(); }; - #endif // __AUDACITY_EFFECT_CHANGEPITCH__ #endif // USE_SOUNDTOUCH diff --git a/src/effects/ChangeSpeed.cpp b/src/effects/ChangeSpeed.cpp index 553122296..c15d85ce7 100644 --- a/src/effects/ChangeSpeed.cpp +++ b/src/effects/ChangeSpeed.cpp @@ -11,64 +11,139 @@ \class EffectChangeSpeed \brief An Effect that affects both pitch & speed. -*//****************************************************************//** - -\class ChangeSpeedDialog -\brief Dialog used with EffectChangeSpeed - *//*******************************************************************/ +#include "../Audacity.h" + #include -#include -#include -#include -#include +#include -#include "../Audacity.h" -#include "../LabelTrack.h" #include "../Envelope.h" #include "../LabelTrack.h" #include "../Prefs.h" #include "../Project.h" +#include "../Resample.h" #include "../ShuttleGui.h" +#include "../widgets/valnum.h" #include "ChangeSpeed.h" + #include "TimeWarper.h" +enum +{ + ID_PercentChange = 10000, + ID_Multiplier, + ID_FromVinyl, + ID_ToVinyl, + ID_ToLength +}; // the standard vinyl rpm choices // If the percent change is not one of these ratios, the choice control gets "n/a". -enum { +enum kVinyl +{ kVinyl_33AndAThird = 0, kVinyl_45, kVinyl_78, - kVinyl_NA + kVinyl_NA, + kNumVinyl }; +static const wxChar *kVinylStrings[kNumVinyl] = +{ + wxT("33 1/3"), + wxT("45"), + wxT("78"), + /* i18n-hint: n/a is an English abbreviation meaning "not applicable". */ + wxTRANSLATE("n/a"), +}; + +// Soundtouch is not reasonable below -99% or above 3000%. + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( Percentage, double, wxTRANSLATE("Percentage"), 0.0, -99.0, 4900.0, 1 ); + +// We warp the slider to go up to 400%, but user can enter higher values +static const double kSliderMax = 100.0; // warped above zero to actually go up to 400% +static const double kSliderWarp = 1.30105; // warp power takes max from 100 to 400. // // EffectChangeSpeed // +BEGIN_EVENT_TABLE(EffectChangeSpeed, wxEvtHandler) + EVT_TEXT(ID_PercentChange, EffectChangeSpeed::OnText_PercentChange) + EVT_TEXT(ID_Multiplier, EffectChangeSpeed::OnText_Multiplier) + EVT_SLIDER(ID_PercentChange, EffectChangeSpeed::OnSlider_PercentChange) + EVT_CHOICE(ID_FromVinyl, EffectChangeSpeed::OnChoice_Vinyl) + EVT_CHOICE(ID_ToVinyl, EffectChangeSpeed::OnChoice_Vinyl) + EVT_TEXT(ID_ToLength, EffectChangeSpeed::OnTimeCtrl_ToLength) + EVT_COMMAND(ID_ToLength, EVT_TIMETEXTCTRL_UPDATED, EffectChangeSpeed::OnTimeCtrlUpdate) +END_EVENT_TABLE() + EffectChangeSpeed::EffectChangeSpeed() { - // Retrieve last used control values - gPrefs->Read(wxT("/Effects/ChangeSpeed/PercentChange"), &m_PercentChange, 0); - // default format "4" is the same as the Selection toolbar: "hh:mm:ss + milliseconds"; - gPrefs->Read(wxT("/Effects/ChangeSpeed/TimeFormat"), &mTimeCtrlFormat, 4); + // effect parameters + m_PercentChange = DEF_Percentage; - gPrefs->Read(wxT("/Effects/ChangeSpeed/VinylChoice"), &mFromVinyl, 0); - if (mFromVinyl == kVinyl_NA) { - mFromVinyl = kVinyl_33AndAThird; - } + mFromVinyl = kVinyl_33AndAThird; + mToVinyl = kVinyl_33AndAThird; + mFromLength = 0.0; + mToLength = 0.0; + mFormat = _("hh:mm:ss + milliseconds"); + mbLoopDetect = false; } -wxString EffectChangeSpeed::GetEffectDescription() { - // Note: This is useful only after change amount has been set. - return wxString::Format(_("Applied effect: %s %.3f%%"), - this->GetEffectName().c_str(), - m_PercentChange); +EffectChangeSpeed::~EffectChangeSpeed() +{ +} + +// IdentInterface implementation + +wxString EffectChangeSpeed::GetSymbol() +{ + return CHANGESPEED_PLUGIN_SYMBOL; +} + +wxString EffectChangeSpeed::GetDescription() +{ + return wxTRANSLATE("Change the speed of a track, also changing its pitch"); +} + +// EffectIdentInterface implementation + +EffectType EffectChangeSpeed::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +bool EffectChangeSpeed::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_Percentage, m_PercentChange); + + return true; +} + +bool EffectChangeSpeed::SetAutomationParameters(EffectAutomationParameters & parms) +{ + ReadAndVerifyDouble(Percentage); + + m_PercentChange = Percentage; + + return true; +} + +// Effect implementation + +bool EffectChangeSpeed::CheckWhetherSkipEffect() +{ + return (m_PercentChange == 0.0); } double EffectChangeSpeed::CalcPreviewInputLength(double previewLength) @@ -76,30 +151,43 @@ double EffectChangeSpeed::CalcPreviewInputLength(double previewLength) return previewLength * (100.0 + m_PercentChange) / 100.0; } -bool EffectChangeSpeed::PromptUser() +bool EffectChangeSpeed::Startup() { - ChangeSpeedDialog dlog(this, mParent); - dlog.m_PercentChange = m_PercentChange; - dlog.mFromVinyl = mFromVinyl; - dlog.mFromLength = mFromLength; - dlog.mTimeCtrlFormat = mTimeCtrlFormat; - // Don't need to call TransferDataToWindow, although other - // Audacity dialogs (from which I derived this one) do it, because - // ShowModal calls stuff that eventually calls wxWindowBase::OnInitDialog, - // which calls dlog.TransferDataToWindow(); - dlog.CentreOnParent(); - dlog.ShowModal(); + wxString base = wxT("/Effects/ChangeSpeed/"); - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; + // Migrate settings from 2.1.0 or before - m_PercentChange = dlog.m_PercentChange; - mFromVinyl = dlog.mFromVinyl; - mTimeCtrlFormat = dlog.mTimeCtrlFormat; + // Already migrated, so bail + if (gPrefs->Exists(base + wxT("Migrated"))) + { + return true; + } + + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + // Retrieve last used control values + gPrefs->Read(base + wxT("PercentChange"), &m_PercentChange, 0); + + // default format "4" is the same as the Selection toolbar: "hh:mm:ss + milliseconds"; + gPrefs->Read(base + wxT("TimeFormat"), &mFormat, _("hh:mm:ss + milliseconds")); + + gPrefs->Read(base + wxT("VinylChoice"), &mFromVinyl, 0); + if (mFromVinyl == kVinyl_NA) + { + mFromVinyl = kVinyl_33AndAThird; + } + + SetPrivateConfig(GetCurrentSettingsGroup(), wxT("TimeFormat"), mFormat); + SetPrivateConfig(GetCurrentSettingsGroup(), wxT("VinylChoice"), mFromVinyl); + + SaveUserPreset(GetCurrentSettingsGroup()); + + // Do not migrate again + gPrefs->Write(base + wxT("Migrated"), true); + gPrefs->Flush(); + } - gPrefs->Write(wxT("/Effects/ChangeSpeed/PercentChange"), m_PercentChange); - gPrefs->Write(wxT("/Effects/ChangeSpeed/TimeFormat"), mTimeCtrlFormat); - gPrefs->Flush(); return true; } @@ -111,25 +199,6 @@ bool EffectChangeSpeed::Init() return true; } -// Labels are time-scaled linearly inside the affected region, and labels after -// the region are shifted along according to how the region size changed. -bool EffectChangeSpeed::ProcessLabelTrack(Track *t) -{ - SetTimeWarper(new RegionTimeWarper(mT0, mT1, - new LinearTimeWarper(mT0, mT0, - mT1, mT0 + (mT1-mT0)*mFactor))); - LabelTrack *lt = (LabelTrack*)t; - if (lt == NULL) return false; - lt->WarpLabels(*GetTimeWarper()); - return true; -} - -bool EffectChangeSpeed::TransferParameters( Shuttle & shuttle ) -{ - shuttle.TransferDouble(wxT("Percentage"),m_PercentChange,0.0); - return true; -} - bool EffectChangeSpeed::Process() { // Similar to EffectSoundTouch::Process() @@ -137,7 +206,7 @@ bool EffectChangeSpeed::Process() // Iterate over each track. // Track::All is needed because this effect needs to introduce // silence in the sync-lock group tracks to keep sync - this->CopyInputTracks(Track::All); // Set up mOutputTracks. + CopyInputTracks(Track::All); // Set up mOutputTracks. bool bGoodResult = true; TrackListIterator iter(mOutputTracks); @@ -204,6 +273,185 @@ bool EffectChangeSpeed::Process() return bGoodResult; } +void EffectChangeSpeed::PopulateOrExchange(ShuttleGui & S) +{ + S.SetBorder(5); + + S.StartVerticalLay(0); + { + S.AddSpace(0, 5); + S.AddTitle(_("Change Speed, affecting both Tempo and Pitch")); + S.AddSpace(0, 10); + + // Speed multiplier and percent change controls. + S.StartMultiColumn(4, wxCENTER); + { + FloatingPointValidator vldMultiplier(3, &mMultiplier, NUM_VAL_NO_TRAILING_ZEROES); + vldMultiplier.SetRange(MIN_Percentage / 100.0, MAX_Percentage / 100.0); + mpTextCtrl_Multiplier = + S.Id(ID_Multiplier).AddTextBox(_("Speed Multiplier:"), wxT(""), 12); + mpTextCtrl_Multiplier->SetValidator(vldMultiplier); + + FloatingPointValidator vldPercentage(3, &m_PercentChange, NUM_VAL_NO_TRAILING_ZEROES); + vldPercentage.SetRange(MIN_Percentage, MAX_Percentage); + mpTextCtrl_PercentChange = + S.Id(ID_PercentChange).AddTextBox(_("Percent Change:"), wxT(""), 12); + mpTextCtrl_PercentChange->SetValidator(vldPercentage); + } + S.EndMultiColumn(); + + // Percent change slider. + S.StartHorizontalLay(wxEXPAND); + { + S.SetStyle(wxSL_HORIZONTAL); + mpSlider_PercentChange = + S.Id(ID_PercentChange).AddSlider(wxT(""), 0, (int)kSliderMax, (int)MIN_Percentage); + mpSlider_PercentChange->SetName(_("Percent Change")); + } + S.EndHorizontalLay(); + + // Vinyl rpm controls. + S.StartMultiColumn(5, wxCENTER); + { + /* i18n-hint: "rpm" is an English abbreviation meaning "revolutions per minute". */ + S.AddUnits(_("Standard Vinyl rpm:")); + + wxASSERT(kNumVinyl == WXSIZEOF(kVinylStrings)); + + wxArrayString vinylChoices; + for (int i = 0; i < kNumVinyl; i++) + { + if (i == kVinyl_NA) + { + vinylChoices.Add(wxGetTranslation(kVinylStrings[i])); + } + else + { + vinylChoices.Add(kVinylStrings[i]); + } + } + + mpChoice_FromVinyl = + S.Id(ID_FromVinyl).AddChoice(_("from"), wxT(""), &vinylChoices); + mpChoice_FromVinyl->SetName(_("From rpm")); + mpChoice_FromVinyl->SetSizeHints(100, -1); + + mpChoice_ToVinyl = + S.Id(ID_ToVinyl).AddChoice(_("to"), wxT(""), &vinylChoices); + mpChoice_ToVinyl->SetName(_("To rpm")); + mpChoice_ToVinyl->SetSizeHints(100, -1); + } + S.EndMultiColumn(); + + // From/To time controls. + S.StartStatic(_("Selection Length"), 0); + { + S.StartMultiColumn(2, wxCENTER); + { + S.AddPrompt(_("Current Length:")); + + mpFromLengthCtrl = new + NumericTextCtrl(NumericConverter::TIME, + S.GetParent(), + wxID_ANY, + mFormat, + mFromLength, + mProjectRate); + + mpFromLengthCtrl->SetName(_("from")); + mpFromLengthCtrl->SetToolTip(_("Current length of selection.")); + mpFromLengthCtrl->SetReadOnly(true); + mpFromLengthCtrl->EnableMenu(false); + S.AddWindow(mpFromLengthCtrl, wxALIGN_LEFT); + + S.AddPrompt(_("New Length:")); + + mpToLengthCtrl = new + NumericTextCtrl(NumericConverter::TIME, + S.GetParent(), + ID_ToLength, + mFormat, + mToLength, + mProjectRate); + + mpToLengthCtrl->SetName(_("to")); + mpToLengthCtrl->EnableMenu(); + S.AddWindow(mpToLengthCtrl, wxALIGN_LEFT); + } + S.EndMultiColumn(); + } + S.EndStatic(); + } + S.EndVerticalLay(); +} + +bool EffectChangeSpeed::TransferDataToWindow() +{ + mbLoopDetect = true; + + if (!mUIParent->TransferDataToWindow()) + { + return false; + } + + GetPrivateConfig(GetCurrentSettingsGroup(), wxT("TimeFormat"), mFormat, mFormat); + GetPrivateConfig(GetCurrentSettingsGroup(), wxT("VinylChoice"), mFromVinyl, mFromVinyl); + + if (mFromVinyl == kVinyl_NA) + { + mFromVinyl = kVinyl_33AndAThird; + } + + Update_Text_PercentChange(); + Update_Text_Multiplier(); + Update_Slider_PercentChange(); + Update_TimeCtrl_ToLength(); + + // Set from/to Vinyl controls - mFromVinyl must be set first. + mpChoice_FromVinyl->SetSelection(mFromVinyl); + // Then update to get correct mToVinyl. + Update_Vinyl(); + // Then update ToVinyl control. + mpChoice_ToVinyl->SetSelection(mToVinyl); + + // Set From Length control. + // Set the format first so we can get sample accuracy. + mpFromLengthCtrl->SetFormatName(mFormat); + mpFromLengthCtrl->SetValue(mFromLength); + + mbLoopDetect = false; + + return true; +} + +bool EffectChangeSpeed::TransferDataFromWindow() +{ + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } + + SetPrivateConfig(GetCurrentSettingsGroup(), wxT("TimeFormat"), mFormat); + SetPrivateConfig(GetCurrentSettingsGroup(), wxT("VinylChoice"), mFromVinyl); + + return true; +} + +// EffectChangeSpeed implementation + +// Labels are time-scaled linearly inside the affected region, and labels after +// the region are shifted along according to how the region size changed. +bool EffectChangeSpeed::ProcessLabelTrack(Track *t) +{ + SetTimeWarper(new RegionTimeWarper(mT0, mT1, + new LinearTimeWarper(mT0, mT0, + mT1, mT0 + (mT1-mT0)*mFactor))); + LabelTrack *lt = (LabelTrack*)t; + if (lt == NULL) return false; + lt->WarpLabels(*GetTimeWarper()); + return true; +} + // ProcessOne() takes a track, transforms it to bunch of buffer-blocks, // and calls libsamplerate code on these blocks. bool EffectChangeSpeed::ProcessOne(WaveTrack * track, @@ -304,237 +552,42 @@ bool EffectChangeSpeed::ProcessOne(WaveTrack * track, return bResult; } +// handler implementations for EffectChangeSpeed -// -// ChangeSpeedDialog -// - -// -99 for PERCENTCHANGE_MIN because -100% is nonsensical. -#define PERCENTCHANGE_MIN -99 - -#define PERCENTCHANGE_MAX 100 // warped above zero to actually go up to 400% -#define PERCENTCHANGE_MAX_TEXT 4900 // maximum allowed by text entry (= 50 times) -#define PERCENTCHANGE_SLIDER_WARP 1.30105 // warp power takes max from 100 to 400. - -enum { - ID_TEXT_PERCENTCHANGE = 10001, - ID_TEXT_MULTIPLIER, - ID_SLIDER_PERCENTCHANGE, - ID_CHOICE_FROMVINYL, - ID_CHOICE_TOVINYL, - ID_TIMECTRL_TOLENGTH -}; - - -// event table for ChangeSpeedDialog - -BEGIN_EVENT_TABLE(ChangeSpeedDialog, EffectDialog) - EVT_TEXT(ID_TEXT_PERCENTCHANGE, ChangeSpeedDialog::OnText_PercentChange) - EVT_TEXT(ID_TEXT_MULTIPLIER, ChangeSpeedDialog::OnText_Multiplier) - EVT_SLIDER(ID_SLIDER_PERCENTCHANGE, ChangeSpeedDialog::OnSlider_PercentChange) - EVT_CHOICE(ID_CHOICE_FROMVINYL, ChangeSpeedDialog::OnChoice_Vinyl) - EVT_CHOICE(ID_CHOICE_TOVINYL, ChangeSpeedDialog::OnChoice_Vinyl) - EVT_TEXT(ID_TIMECTRL_TOLENGTH, ChangeSpeedDialog::OnTimeCtrl_ToLength) - EVT_COMMAND(ID_TIMECTRL_TOLENGTH, EVT_TIMETEXTCTRL_UPDATED, ChangeSpeedDialog::OnTimeCtrlUpdate) - - EVT_BUTTON(ID_EFFECT_PREVIEW, ChangeSpeedDialog::OnPreview) -END_EVENT_TABLE() - -ChangeSpeedDialog::ChangeSpeedDialog(EffectChangeSpeed *effect, wxWindow *parent) -: EffectDialog(parent, - /* i18n-hint: Audacity's change speed effect changes the speed and pitch.*/ - _("Change Speed"), - PROCESS_EFFECT), - mEffect(effect) -{ - mbLoopDetect = false; - - // effect parameters - m_PercentChange = 0.0; - mFromVinyl = kVinyl_33AndAThird; - mToVinyl = kVinyl_33AndAThird; - mFromLength = 0.0; - mToLength = 0.0; - mFormat = wxT(""); - mTimeCtrlFormat = 0; - - Init(); -} - -void ChangeSpeedDialog::PopulateOrExchange(ShuttleGui & S) -{ - S.SetBorder(5); - - S.AddSpace(0, 5); - S.AddTitle(_("Change Speed, affecting both Tempo and Pitch")); - S.AddSpace(0, 10); - - // Speed multiplier and percent change controls. - S.StartMultiColumn(4, wxLEFT); - { - wxTextValidator validator(wxFILTER_NUMERIC); - mpTextCtrl_Multiplier = - S.Id(ID_TEXT_MULTIPLIER).AddTextBox(_("Speed Multiplier:"), wxT(""), 12); - mpTextCtrl_Multiplier->SetValidator(validator); - - mpTextCtrl_PercentChange = - S.Id(ID_TEXT_PERCENTCHANGE).AddTextBox(_("Percent Change:"), wxT(""), 12); - mpTextCtrl_PercentChange->SetValidator(validator); - } - S.EndMultiColumn(); - - // Percent change slider. - S.StartHorizontalLay(wxEXPAND); - { - S.SetStyle(wxSL_HORIZONTAL); - mpSlider_PercentChange = - S.Id(ID_SLIDER_PERCENTCHANGE).AddSlider(wxT(""), 0, (int)PERCENTCHANGE_MAX, (int)PERCENTCHANGE_MIN); - mpSlider_PercentChange->SetName(_("Percent Change")); - } - S.EndHorizontalLay(); - - // Vinyl rpm controls. - S.StartMultiColumn(5, wxCENTER); - { - /* i18n-hint: "rpm" is an English abbreviation meaning "revolutions per minute". */ - S.AddUnits(_("Standard Vinyl rpm:")); - - wxArrayString rpmStrings; - rpmStrings.Add(wxT("33 1/3")); - rpmStrings.Add(wxT("45")); - rpmStrings.Add(wxT("78")); - /* i18n-hint: n/a is an English abbreviation meaning "not applicable". */ - rpmStrings.Add(_("n/a")); - - mpChoice_FromVinyl = - S.Id(ID_CHOICE_FROMVINYL).AddChoice(_("from"), wxT(""), &rpmStrings); - mpChoice_FromVinyl->SetName(_("From rpm")); - mpChoice_FromVinyl->SetSizeHints(100, -1); - - mpChoice_ToVinyl = - S.Id(ID_CHOICE_TOVINYL).AddChoice(_("to"), wxT(""), &rpmStrings); - mpChoice_ToVinyl->SetName(_("To rpm")); - mpChoice_ToVinyl->SetSizeHints(100, -1); - } - S.EndMultiColumn(); - - // From/To time controls. - S.StartStatic(_("Selection Length"), 0); - { - S.StartMultiColumn(2, wxLEFT); - { - S.AddPrompt(_("Current Length") + wxString(wxT(":"))); - - mpFromLengthCtrl = new - NumericTextCtrl(NumericConverter::TIME, this, - wxID_ANY, - mFormat, - mFromLength, - mEffect->mProjectRate); - - mpFromLengthCtrl->SetName(_("from")); - S.AddWindow(mpFromLengthCtrl, wxALIGN_LEFT); -#if wxUSE_TOOLTIPS - wxString tip(_("Current length of selection.")); - mpFromLengthCtrl->SetToolTip(tip); -#endif - mpFromLengthCtrl->SetReadOnly(true); - mpFromLengthCtrl->EnableMenu(false); - - - S.AddPrompt(_("New Length") + wxString(wxT(":"))); - - mpToLengthCtrl = new - NumericTextCtrl(NumericConverter::TIME, this, - ID_TIMECTRL_TOLENGTH, - mFormat, - mToLength, - mEffect->mProjectRate); - - mpToLengthCtrl->SetName(_("to")); - S.AddWindow(mpToLengthCtrl, wxALIGN_LEFT); - mpToLengthCtrl->EnableMenu(); - } - S.EndMultiColumn(); - } - S.EndStatic(); -} - -bool ChangeSpeedDialog::TransferDataToWindow() -{ - mbLoopDetect = true; - - this->Update_Text_PercentChange(); - this->Update_Text_Multiplier(); - this->Update_Slider_PercentChange(); - this->Update_TimeCtrl_ToLength(); - - // Set from/to Vinyl controls - mFromVinyl must be set first. - mpChoice_FromVinyl->SetSelection(mFromVinyl); - // Then update to get correct mToVinyl. - this->Update_Vinyl(); - // Then update ToVinyl control. - mpChoice_ToVinyl->SetSelection(mToVinyl); - - // Set From Length control. - // Set the format first so we can get sample accuracy. - mpFromLengthCtrl->SetFormatName(mFormat); - mpFromLengthCtrl->SetValue(mFromLength); - - mbLoopDetect = false; - - return true; -} - -bool ChangeSpeedDialog::Validate() -{ - TransferDataFromWindow(); - m_PercentChange = TrapDouble(m_PercentChange, PERCENTCHANGE_MIN, PERCENTCHANGE_MAX_TEXT); - return true; -} - - -// handler implementations for ChangeSpeedDialog - -void ChangeSpeedDialog::OnText_PercentChange(wxCommandEvent & WXUNUSED(event)) +void EffectChangeSpeed::OnText_PercentChange(wxCommandEvent & WXUNUSED(evt)) { if (mbLoopDetect) return; - double newValue = 0; - wxString str = mpTextCtrl_PercentChange->GetValue(); - str.ToDouble(&newValue); - m_PercentChange = newValue; - this->UpdateUI(); + mpTextCtrl_PercentChange->GetValidator()->TransferFromWindow(); + UpdateUI(); mbLoopDetect = true; - this->Update_Text_Multiplier(); - this->Update_Slider_PercentChange(); - this->Update_Vinyl(); - this->Update_TimeCtrl_ToLength(); + Update_Text_Multiplier(); + Update_Slider_PercentChange(); + Update_Vinyl(); + Update_TimeCtrl_ToLength(); mbLoopDetect = false; } -void ChangeSpeedDialog::OnText_Multiplier(wxCommandEvent & WXUNUSED(event)) +void EffectChangeSpeed::OnText_Multiplier(wxCommandEvent & WXUNUSED(evt)) { if (mbLoopDetect) return; - double newValue = 0; - wxString str = mpTextCtrl_Multiplier->GetValue(); - str.ToDouble(&newValue); - m_PercentChange = 100 * (newValue - 1); - this->UpdateUI(); + mpTextCtrl_Multiplier->GetValidator()->TransferFromWindow(); + m_PercentChange = 100 * (mMultiplier - 1); + UpdateUI(); mbLoopDetect = true; - this->Update_Text_PercentChange(); - this->Update_Slider_PercentChange(); - this->Update_Vinyl(); - this->Update_TimeCtrl_ToLength(); + Update_Text_PercentChange(); + Update_Slider_PercentChange(); + Update_Vinyl(); + Update_TimeCtrl_ToLength(); mbLoopDetect = false; } -void ChangeSpeedDialog::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(event)) +void EffectChangeSpeed::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(evt)) { if (mbLoopDetect) return; @@ -542,18 +595,18 @@ void ChangeSpeedDialog::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(event)) m_PercentChange = (double)(mpSlider_PercentChange->GetValue()); // Warp positive values to actually go up faster & further than negatives. if (m_PercentChange > 0.0) - m_PercentChange = pow(m_PercentChange, PERCENTCHANGE_SLIDER_WARP); - this->UpdateUI(); + m_PercentChange = pow(m_PercentChange, kSliderWarp); + UpdateUI(); mbLoopDetect = true; - this->Update_Text_PercentChange(); - this->Update_Text_Multiplier(); - this->Update_Vinyl(); - this->Update_TimeCtrl_ToLength(); + Update_Text_PercentChange(); + Update_Text_Multiplier(); + Update_Vinyl(); + Update_TimeCtrl_ToLength(); mbLoopDetect = false; } -void ChangeSpeedDialog::OnChoice_Vinyl(wxCommandEvent & WXUNUSED(event)) +void EffectChangeSpeed::OnChoice_Vinyl(wxCommandEvent & WXUNUSED(evt)) { // Treat mpChoice_FromVinyl and mpChoice_ToVinyl as one control since we need // both to calculate Percent Change. @@ -561,8 +614,7 @@ void ChangeSpeedDialog::OnChoice_Vinyl(wxCommandEvent & WXUNUSED(event)) mToVinyl = mpChoice_ToVinyl->GetSelection(); // Use this as the 'preferred' choice. if (mFromVinyl != kVinyl_NA) { - gPrefs->Write(wxT("/Effects/ChangeSpeed/VinylChoice"), mFromVinyl); - gPrefs->Flush(); + SetPrivateConfig(GetCurrentSettingsGroup(), wxT("VinylChoice"), mFromVinyl); } // If mFromVinyl & mToVinyl are set, then there's a new percent change. @@ -583,94 +635,74 @@ void ChangeSpeedDialog::OnChoice_Vinyl(wxCommandEvent & WXUNUSED(event)) case kVinyl_78: toRPM = 78; break; } m_PercentChange = ((toRPM * 100.0) / fromRPM) - 100.0; - this->UpdateUI(); + UpdateUI(); mbLoopDetect = true; - this->Update_Text_PercentChange(); - this->Update_Text_Multiplier(); - this->Update_Slider_PercentChange(); - this->Update_TimeCtrl_ToLength(); + Update_Text_PercentChange(); + Update_Text_Multiplier(); + Update_Slider_PercentChange(); + Update_TimeCtrl_ToLength(); } mbLoopDetect = false; } -void ChangeSpeedDialog::OnTimeCtrl_ToLength(wxCommandEvent & WXUNUSED(event)) +void EffectChangeSpeed::OnTimeCtrl_ToLength(wxCommandEvent & WXUNUSED(evt)) { if (mbLoopDetect) return; - mToLength = mpToLengthCtrl->GetValue(); - m_PercentChange = ((mFromLength * 100.0) / mToLength) - 100.0; - this->UpdateUI(); + mToLength = mpToLengthCtrl->GetValue(); + m_PercentChange = ((mFromLength * 100.0) / mToLength) - 100.0; + UpdateUI(); - mbLoopDetect = true; + mbLoopDetect = true; - this->Update_Text_PercentChange(); - this->Update_Text_Multiplier(); - this->Update_Slider_PercentChange(); - this->Update_Vinyl(); + Update_Text_PercentChange(); + Update_Text_Multiplier(); + Update_Slider_PercentChange(); + Update_Vinyl(); - mbLoopDetect = false; + mbLoopDetect = false; } -void ChangeSpeedDialog::OnTimeCtrlUpdate(wxCommandEvent &evt) +void EffectChangeSpeed::OnTimeCtrlUpdate(wxCommandEvent & evt) { - mTimeCtrlFormat = evt.GetInt(); + mFormat = evt.GetString(); - mFormat = mpToLengthCtrl->GetBuiltinName(mTimeCtrlFormat); mpFromLengthCtrl->SetFormatName(mFormat); // Update From/To Length controls (precision has changed). mpToLengthCtrl->SetValue(mToLength); mpFromLengthCtrl->SetValue(mFromLength); } -void ChangeSpeedDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - - // Save & restore parameters around Preview, because we didn't do OK. - double oldPercentChange = mEffect->m_PercentChange; - - // bug 773 allows effects to bypass greyed out OK button, - // but validate() should now catch that. - wxASSERT((m_PercentChange >= PERCENTCHANGE_MIN) && (m_PercentChange <= PERCENTCHANGE_MAX_TEXT)); - - mEffect->m_PercentChange = m_PercentChange; - mEffect->Preview(); - mEffect->m_PercentChange = oldPercentChange; -} - // helper functions -void ChangeSpeedDialog::Update_Text_PercentChange() +void EffectChangeSpeed::Update_Text_PercentChange() // Update Text Percent control from percent change. { - wxString str; - str.Printf(wxT("%.3f"), m_PercentChange); - mpTextCtrl_PercentChange->SetValue(str); + mpTextCtrl_PercentChange->GetValidator()->TransferToWindow(); } -void ChangeSpeedDialog::Update_Text_Multiplier() +void EffectChangeSpeed::Update_Text_Multiplier() // Update Multiplier control from percent change. { - wxString str; - str.Printf(wxT("%.3f"), 1 + (m_PercentChange) / 100.0); - mpTextCtrl_Multiplier->SetValue(str); + mMultiplier = 1 + (m_PercentChange) / 100.0; + mpTextCtrl_Multiplier->GetValidator()->TransferToWindow(); } -void ChangeSpeedDialog::Update_Slider_PercentChange() +void EffectChangeSpeed::Update_Slider_PercentChange() // Update Slider Percent control from percent change. { double unwarped = m_PercentChange; if (unwarped > 0.0) - // Un-warp values above zero to actually go up to PERCENTCHANGE_MAX. - unwarped = pow(m_PercentChange, (1.0 / PERCENTCHANGE_SLIDER_WARP)); + // Un-warp values above zero to actually go up to kSliderMax. + unwarped = pow(m_PercentChange, (1.0 / kSliderWarp)); // Add 0.5 to unwarped so trunc -> round. mpSlider_PercentChange->SetValue((int)(unwarped + 0.5)); } -void ChangeSpeedDialog::Update_Vinyl() +void EffectChangeSpeed::Update_Vinyl() // Update Vinyl controls from percent change. { // Match Vinyl rpm when within 0.01% of a standard ratio. @@ -684,7 +716,7 @@ void ChangeSpeedDialog::Update_Vinyl() mpChoice_ToVinyl->SetSelection(mpChoice_FromVinyl->GetSelection()); } else { // Use the last saved option. - gPrefs->Read(wxT("/Effects/ChangeSpeed/VinylChoice"), &mFromVinyl, 0); + GetPrivateConfig(GetCurrentSettingsGroup(), wxT("VinylChoice"), mFromVinyl, 0); mpChoice_FromVinyl->SetSelection(mFromVinyl); mpChoice_ToVinyl->SetSelection(mFromVinyl); } @@ -721,13 +753,12 @@ void ChangeSpeedDialog::Update_Vinyl() mToVinyl = mpChoice_ToVinyl->GetSelection(); } -void ChangeSpeedDialog::Update_TimeCtrl_ToLength() +void EffectChangeSpeed::Update_TimeCtrl_ToLength() // Update ToLength control from percent change. { mToLength = (mFromLength * 100.0) / (100.0 + m_PercentChange); // Set the format first so we can get sample accuracy. - mFormat = mpToLengthCtrl->GetBuiltinName(mTimeCtrlFormat); mpToLengthCtrl->SetFormatName(mFormat); // Negative times do not make sense. // 359999 = 99h:59m:59s which is a little less disturbing than overflow characters @@ -736,11 +767,8 @@ void ChangeSpeedDialog::Update_TimeCtrl_ToLength() mpToLengthCtrl->SetValue(mToLength); } -void ChangeSpeedDialog::UpdateUI() +void EffectChangeSpeed::UpdateUI() // Disable OK and Preview if not in sensible range. { - bool enabled = (m_PercentChange < PERCENTCHANGE_MIN || - m_PercentChange > PERCENTCHANGE_MAX_TEXT)? false : true; - FindWindow(wxID_OK)->Enable(enabled); - FindWindow(ID_EFFECT_PREVIEW)->Enable(enabled); + EnableApply(m_PercentChange >= MIN_Percentage && m_PercentChange <= MAX_Percentage); } diff --git a/src/effects/ChangeSpeed.h b/src/effects/ChangeSpeed.h index ed49aa74f..e93c75ea0 100644 --- a/src/effects/ChangeSpeed.h +++ b/src/effects/ChangeSpeed.h @@ -13,58 +13,75 @@ #ifndef __AUDACITY_EFFECT_CHANGESPEED__ #define __AUDACITY_EFFECT_CHANGESPEED__ -#include "Effect.h" -#include "../Resample.h" - #include -#include -#include +#include #include #include #include + +#include "../ShuttleGui.h" +#include "../Track.h" +#include "../WaveTrack.h" #include "../widgets/NumericTextCtrl.h" +#include "Effect.h" + +#define CHANGESPEED_PLUGIN_SYMBOL wxTRANSLATE("Change Speed") + class EffectChangeSpeed : public Effect { - public: +public: EffectChangeSpeed(); + virtual ~EffectChangeSpeed(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Change Speed...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://audacityteam.org/namespace#PitchAndTempo")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Change Speed")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Changing Speed")); - } + virtual EffectType GetType(); - // Useful only after PromptUser values have been set. - virtual wxString GetEffectDescription(); + // EffectClientInterface implementation - double CalcPreviewInputLength(double previewLength); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); - protected: + // Effect implementation + + virtual bool CheckWhetherSkipEffect(); + virtual double CalcPreviewInputLength(double previewLength); + virtual bool Startup(); virtual bool Init(); - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); - - virtual bool CheckWhetherSkipEffect() { return (m_PercentChange == 0.0); } virtual bool Process(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataFromWindow(); + virtual bool TransferDataToWindow(); - private: - bool ProcessOne(WaveTrack * t, sampleCount start, sampleCount end); +private: + // EffectChangeSpeed implementation + + bool ProcessOne(WaveTrack *t, sampleCount start, sampleCount end); bool ProcessLabelTrack(Track *t); - private: + // handlers + void OnText_PercentChange(wxCommandEvent & evt); + void OnText_Multiplier(wxCommandEvent & evt); + void OnSlider_PercentChange(wxCommandEvent & evt); + void OnChoice_Vinyl(wxCommandEvent & evt); + void OnTimeCtrl_ToLength(wxCommandEvent & evt); + void OnTimeCtrlUpdate(wxCommandEvent & evt); + + // helper functions + void Update_Text_PercentChange(); // Update control per current m_PercentChange. + void Update_Text_Multiplier(); // Update control per current m_PercentChange. + void Update_Slider_PercentChange(); // Update control per current m_PercentChange. + void Update_Vinyl(); // Update Vinyl controls for new percent change. + void Update_TimeCtrl_ToLength(); // Update target length controls for new percent change. + void UpdateUI(); // Enable / disable OK / preview. + +private: // track related int mCurTrackNum; double mMaxNewLength; @@ -79,42 +96,8 @@ class EffectChangeSpeed : public Effect double mFactor; // scale factor calculated from percent change double mFromLength; // current selection length int mTimeCtrlFormat; // time control format index number + double mMultiplier; -friend class ChangeSpeedDialog; -}; - - -class ChangeSpeedDialog : public EffectDialog -{ - public: - ChangeSpeedDialog(EffectChangeSpeed * effect, - wxWindow * parent); - - void PopulateOrExchange(ShuttleGui& S); - bool TransferDataToWindow(); - bool Validate(); - - private: - // handlers - void OnText_PercentChange(wxCommandEvent & event); - void OnText_Multiplier(wxCommandEvent & event); - void OnSlider_PercentChange(wxCommandEvent & event); - void OnChoice_Vinyl(wxCommandEvent & event); - void OnTimeCtrl_ToLength(wxCommandEvent & event); - void OnTimeCtrlUpdate(wxCommandEvent & event); - - void OnPreview(wxCommandEvent &event); - - // helper functions - void Update_Text_PercentChange(); // Update control per current m_PercentChange. - void Update_Text_Multiplier(); // Update control per current m_PercentChange. - void Update_Slider_PercentChange(); // Update control per current m_PercentChange. - void Update_Vinyl(); // Update Vinyl controls for new percent change. - void Update_TimeCtrl_ToLength(); // Update target length controls for new percent change. - void UpdateUI(); // Enable / disable OK / preview. - - private: - EffectChangeSpeed * mEffect; bool mbLoopDetect; // controls @@ -132,18 +115,7 @@ class ChangeSpeedDialog : public EffectDialog double mToLength; // target length of selection wxString mFormat; // time control format - public: - // effect parameters - double m_PercentChange; // percent change to apply to tempo - // -100% is meaningless, but sky's the upper limit. - // Slider is (-100, 200], but textCtrls can set higher. - int mFromVinyl; // from standard vinyl speed (rpm) - double mFromLength; // current selection length - int mTimeCtrlFormat; // time control format index - - private: - DECLARE_EVENT_TABLE() + DECLARE_EVENT_TABLE(); }; - #endif // __AUDACITY_EFFECT_CHANGESPEED__ diff --git a/src/effects/ChangeTempo.cpp b/src/effects/ChangeTempo.cpp index dcd944b74..0997ba18d 100644 --- a/src/effects/ChangeTempo.cpp +++ b/src/effects/ChangeTempo.cpp @@ -13,54 +13,121 @@ \brief An EffectSoundTouch provides speeding up or slowing down tempo without changing pitch. -*//****************************************************************//** - -\class ChangeTempoDialog -\brief Dialog used with EffectChangeTempo - *//*******************************************************************/ #include "../Audacity.h" // for USE_SOUNDTOUCH #if USE_SOUNDTOUCH -#include "ChangeTempo.h" - -#include "../ShuttleGui.h" -#include "TimeWarper.h" - #include -#include -#include -#include -#include -#include +#include +#include "../ShuttleGui.h" +#include "../widgets/valnum.h" +#include "TimeWarper.h" + +#include "ChangeTempo.h" + +enum +{ + ID_PercentChange = 10000, + ID_FromBPM, + ID_ToBPM, + ID_FromLength, + ID_ToLength +}; + +// Soundtouch is not reasonable below -99% or above 3000%. + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( Percentage, double, wxTRANSLATE("Percentage"), 0.0, -99.0, 3000.0, 1 ); + +// We warp the slider to go up to 400%, but user can enter higher values. +static const double kSliderMax = 100.0; // warped above zero to actually go up to 400% +static const double kSliderWarp = 1.30105; // warp power takes max from 100 to 400. // // EffectChangeTempo // +BEGIN_EVENT_TABLE(EffectChangeTempo, wxEvtHandler) + EVT_TEXT(ID_PercentChange, EffectChangeTempo::OnText_PercentChange) + EVT_SLIDER(ID_PercentChange, EffectChangeTempo::OnSlider_PercentChange) + EVT_TEXT(ID_FromBPM, EffectChangeTempo::OnText_FromBPM) + EVT_TEXT(ID_ToBPM, EffectChangeTempo::OnText_ToBPM) + EVT_TEXT(ID_ToLength, EffectChangeTempo::OnText_ToLength) +END_EVENT_TABLE() + EffectChangeTempo::EffectChangeTempo() { - m_PercentChange = 0.0; + m_PercentChange = DEF_Percentage; m_FromBPM = 0.0; // indicates not yet set m_ToBPM = 0.0; // indicates not yet set m_FromLength = 0.0; m_ToLength = 0.0; + + m_bLoopDetect = false; } +EffectChangeTempo::~EffectChangeTempo() +{ +} + +// IdentInterface implementation + +wxString EffectChangeTempo::GetSymbol() +{ + return CHANGETEMPO_PLUGIN_SYMBOL; +} + +wxString EffectChangeTempo::GetDescription() +{ + return wxTRANSLATE("Change the tempo of a selection without changing its pitch"); +} + +// EffectIdentInterface implementation + +EffectType EffectChangeTempo::GetType() +{ + return EffectTypeProcess; +} + +bool EffectChangeTempo::SupportsAutomation() +{ + return true; +} + +// EffectClientInterface implementation + +bool EffectChangeTempo::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_Percentage, m_PercentChange); + + return true; +} + +bool EffectChangeTempo::SetAutomationParameters(EffectAutomationParameters & parms) +{ + ReadAndVerifyDouble(Percentage); + + m_PercentChange = Percentage; + + return true; +} + +// Effect implementation + double EffectChangeTempo::CalcPreviewInputLength(double previewLength) { return previewLength * (100.0 + m_PercentChange) / 100.0; } -wxString EffectChangeTempo::GetEffectDescription() { - // Note: This is useful only after change amount has been set. - return wxString::Format(_("Applied effect: %s %.1f%%"), - this->GetEffectName().c_str(), - m_PercentChange); +bool EffectChangeTempo::CheckWhetherSkipEffect() +{ + return (m_PercentChange == 0.0); } bool EffectChangeTempo::Init() @@ -75,37 +142,6 @@ bool EffectChangeTempo::Init() return true; } -bool EffectChangeTempo::PromptUser() -{ - ChangeTempoDialog dlog(this, mParent); - dlog.m_PercentChange = m_PercentChange; - dlog.m_FromBPM = m_FromBPM; - dlog.m_ToBPM = m_ToBPM; - dlog.m_FromLength = m_FromLength; - dlog.m_ToLength = m_ToLength; - // Don't need to call TransferDataToWindow, although other - // Audacity dialogs (from which I derived this one) do it, because - // ShowModal calls stuff that eventually calls wxWindowBase::OnInitDialog, - // which calls dlog.TransferDataToWindow(); - dlog.CentreOnParent(); - dlog.ShowModal(); - - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; - - m_PercentChange = dlog.m_PercentChange; - m_FromBPM = dlog.m_FromBPM; - m_ToBPM = dlog.m_ToBPM; - m_ToLength = dlog.m_ToLength; - return true; -} - -bool EffectChangeTempo::TransferParameters( Shuttle & shuttle ) -{ - shuttle.TransferDouble(wxT("Percentage"),m_PercentChange,0.0); - return true; -} - bool EffectChangeTempo::Process() { mSoundTouch = new SoundTouch(); @@ -113,384 +149,233 @@ bool EffectChangeTempo::Process() double mT1Dashed = mT0 + (mT1 - mT0)/(m_PercentChange/100.0 + 1.0); SetTimeWarper(new RegionTimeWarper(mT0, mT1, new LinearTimeWarper(mT0, mT0, mT1, mT1Dashed ))); - bool success = this->EffectSoundTouch::Process(); + bool success = EffectSoundTouch::Process(); if( success ) mT1 = mT0 + (mT1 - mT0)/(m_PercentChange/100 + 1.); return success; } -//---------------------------------------------------------------------------- -// ChangeTempoDialog -//---------------------------------------------------------------------------- - -#define PERCENTCHANGE_MIN -99 -#define PERCENTCHANGE_MAX 100 // warped above zero to actually go up to 400% -#define PERCENTCHANGE_SLIDER_WARP 1.30105 // warp power takes max from 100 to 400. - -#define ID_TEXT_PERCENTCHANGE 10001 -#define ID_SLIDER_PERCENTCHANGE 10002 -#define ID_TEXT_FROMBPM 10003 -#define ID_TEXT_TOBPM 10004 -#define ID_TEXT_FROMLENGTH 10005 -#define ID_TEXT_TOLENGTH 10006 - -// event table for ChangeTempoDialog - -BEGIN_EVENT_TABLE(ChangeTempoDialog, EffectDialog) - EVT_TEXT(ID_TEXT_PERCENTCHANGE, ChangeTempoDialog::OnText_PercentChange) - EVT_SLIDER(ID_SLIDER_PERCENTCHANGE, ChangeTempoDialog::OnSlider_PercentChange) - EVT_TEXT(ID_TEXT_FROMBPM, ChangeTempoDialog::OnText_FromBPM) - EVT_TEXT(ID_TEXT_TOBPM, ChangeTempoDialog::OnText_ToBPM) - EVT_TEXT(ID_TEXT_TOLENGTH, ChangeTempoDialog::OnText_ToLength) - - EVT_BUTTON(ID_EFFECT_PREVIEW, ChangeTempoDialog::OnPreview) -END_EVENT_TABLE() - -ChangeTempoDialog::ChangeTempoDialog(EffectChangeTempo *effect, wxWindow *parent) -: EffectDialog(parent, _("Change Tempo"), PROCESS_EFFECT), - mEffect(effect) +void EffectChangeTempo::PopulateOrExchange(ShuttleGui & S) { - m_bLoopDetect = false; - - // NULL out these control members because there are some cases where the - // event table handlers get called during this method, and those handlers that - // can cause trouble check for NULL. - m_pTextCtrl_PercentChange = NULL; - m_pSlider_PercentChange = NULL; - m_pTextCtrl_FromBPM = NULL; - m_pTextCtrl_ToBPM = NULL; - m_pTextCtrl_FromLength = NULL; - m_pTextCtrl_ToLength = NULL; - - // effect parameters - m_PercentChange = 0.0; - m_FromBPM = 0.0; // indicates not yet set - m_ToBPM = 0.0; // indicates not yet set - m_FromLength = 0.0; - m_ToLength = 0.0; - - Init(); -} - -void ChangeTempoDialog::PopulateOrExchange(ShuttleGui & S) -{ - wxTextValidator nullvld(wxFILTER_INCLUDE_CHAR_LIST); - wxTextValidator numvld(wxFILTER_NUMERIC); - - S.AddSpace(0, 5); - S.AddTitle(_("Change Tempo without Changing Pitch")); - S.SetBorder(5); - - // - S.StartMultiColumn(2, wxCENTER); + S.StartVerticalLay(0); { - m_pTextCtrl_PercentChange = S.Id(ID_TEXT_PERCENTCHANGE) - .AddTextBox(_("Percent Change:"), wxT(""), 12); - m_pTextCtrl_PercentChange->SetValidator(numvld); - } - S.EndMultiColumn(); - - // - S.StartHorizontalLay(wxEXPAND); - { - S.SetStyle(wxSL_HORIZONTAL); - m_pSlider_PercentChange = S.Id(ID_SLIDER_PERCENTCHANGE) - .AddSlider(wxT(""), 0, (int)PERCENTCHANGE_MAX, (int)PERCENTCHANGE_MIN); - m_pSlider_PercentChange->SetName(_("Percent Change")); - } - S.EndHorizontalLay(); - - // - S.StartMultiColumn(5, wxCENTER); - { - // - S.AddUnits(_("Beats per minute:")); - - m_pTextCtrl_FromBPM = S.Id(ID_TEXT_FROMBPM) - .AddTextBox(_("from"), wxT(""), 12); - m_pTextCtrl_FromBPM->SetName(_("From beats per minute")); - m_pTextCtrl_FromBPM->SetValidator(numvld); - - m_pTextCtrl_ToBPM = S.Id(ID_TEXT_TOBPM) - .AddTextBox(_("to"), wxT(""), 12); - m_pTextCtrl_ToBPM->SetName(_("To beats per minute")); - m_pTextCtrl_ToBPM->SetValidator(numvld); + S.AddSpace(0, 5); + S.AddTitle(_("Change Tempo without Changing Pitch")); + S.SetBorder(5); // - S.AddUnits(_("Length (seconds):")); + S.StartMultiColumn(2, wxCENTER); + { + FloatingPointValidator vldPercentage(3, &m_PercentChange, NUM_VAL_NO_TRAILING_ZEROES); + vldPercentage.SetRange(MIN_Percentage, MAX_Percentage); + m_pTextCtrl_PercentChange = S.Id(ID_PercentChange) + .AddTextBox(_("Percent Change:"), wxT(""), 12); + m_pTextCtrl_PercentChange->SetValidator(vldPercentage); + } + S.EndMultiColumn(); - m_pTextCtrl_FromLength = S.Id(ID_TEXT_FROMLENGTH) - .AddTextBox(_("from"), wxT(""), 12); - m_pTextCtrl_FromLength->SetName(_("From length in seconds")); - m_pTextCtrl_FromLength->SetValidator(nullvld); + // + S.StartHorizontalLay(wxEXPAND); + { + S.SetStyle(wxSL_HORIZONTAL); + m_pSlider_PercentChange = S.Id(ID_PercentChange) + .AddSlider(wxT(""), 0, (int)kSliderMax, (int)MIN_Percentage); + m_pSlider_PercentChange->SetName(_("Percent Change")); + } + S.EndHorizontalLay(); - m_pTextCtrl_ToLength = S.Id(ID_TEXT_TOLENGTH) - .AddTextBox(_("to"), wxT(""), 12); - m_pTextCtrl_ToLength->SetName(_("To length in seconds")); - m_pTextCtrl_ToLength->SetValidator(numvld); + // + S.StartMultiColumn(5, wxCENTER); + { + // + S.AddUnits(_("Beats per minute:")); + + FloatingPointValidator vldFromBPM(3, &m_FromBPM, NUM_VAL_NO_TRAILING_ZEROES | NUM_VAL_ZERO_AS_BLANK); + m_pTextCtrl_FromBPM = S.Id(ID_FromBPM) + .AddTextBox(_("from"), wxT(""), 12); + m_pTextCtrl_FromBPM->SetName(_("From beats per minute")); + m_pTextCtrl_FromBPM->SetValidator(vldFromBPM); + + FloatingPointValidator vldToBPM(3, &m_ToBPM, NUM_VAL_NO_TRAILING_ZEROES | NUM_VAL_ZERO_AS_BLANK); + m_pTextCtrl_ToBPM = S.Id(ID_ToBPM) + .AddTextBox(_("to"), wxT(""), 12); + m_pTextCtrl_ToBPM->SetName(_("To beats per minute")); + m_pTextCtrl_ToBPM->SetValidator(vldToBPM); + + // + S.AddUnits(_("Length (seconds):")); + + FloatingPointValidator vldFromLength(3, &m_FromLength, NUM_VAL_NO_TRAILING_ZEROES); + m_pTextCtrl_FromLength = S.Id(ID_FromLength) + .AddTextBox(_("from"), wxT(""), 12); + m_pTextCtrl_FromLength->SetName(_("From length in seconds")); + m_pTextCtrl_FromLength->SetValidator(vldFromLength); + m_pTextCtrl_FromLength->Enable(false); // Disable because the value comes from the user selection. + + FloatingPointValidator vldToLength(3, &m_ToLength, NUM_VAL_NO_TRAILING_ZEROES); + vldToLength.SetRange((m_FromLength * 100.0) / (100.0 + MAX_Percentage), + (m_FromLength * 100.0) / (100.0 + MIN_Percentage)); + m_pTextCtrl_ToLength = S.Id(ID_ToLength) + .AddTextBox(_("to"), wxT(""), 12); + m_pTextCtrl_ToLength->SetName(_("To length in seconds")); + m_pTextCtrl_ToLength->SetValidator(vldToLength); + } + S.EndMultiColumn(); } - S.EndMultiColumn(); + S.EndVerticalLay(); return; } -bool ChangeTempoDialog::TransferDataToWindow() +bool EffectChangeTempo::TransferDataToWindow() { m_bLoopDetect = true; + if (!mUIParent->TransferDataToWindow()) + { + return false; + } + // percent change controls - this->Update_Text_PercentChange(); - this->Update_Slider_PercentChange(); - - // from/to BPM controls - wxString str; - if (m_pTextCtrl_FromBPM) { - if (m_FromBPM != 0.0) - str.Printf(wxT("%.3f"), m_FromBPM); - else - str = wxT(""); - m_pTextCtrl_FromBPM->SetValue(str); - } - if (m_pTextCtrl_ToBPM) { - if (m_ToBPM != 0.0) - str.Printf(wxT("%.3f"), m_ToBPM); - else - str = wxT(""); - m_pTextCtrl_ToBPM->SetValue(str); - } - - // from/to Length controls - if (m_pTextCtrl_FromLength) { - str.Printf(wxT("%.2f"), m_FromLength); - m_pTextCtrl_FromLength->SetValue(str); - m_pTextCtrl_FromLength->Enable(false); // Disable because the value comes from the user selection. - } - if (m_pTextCtrl_ToLength) { - str.Printf(wxT("%.2f"), m_ToLength); - m_pTextCtrl_ToLength->SetValue(str); - } + Update_Slider_PercentChange(); m_bLoopDetect = false; return true; } -bool ChangeTempoDialog::TransferDataFromWindow() +bool EffectChangeTempo::TransferDataFromWindow() { - wxString str; - - // percent change controls - if (m_pTextCtrl_PercentChange) { - str = m_pTextCtrl_PercentChange->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_PercentChange = newValue; - } - - // Ignore Slider_PercentChange because TextCtrl_PercentChange - // always tracks it & is more precise (decimal points). - - // from/to BPM controls - double newValue; - if (m_pTextCtrl_FromBPM) { - str = m_pTextCtrl_FromBPM->GetValue(); - str.ToDouble(&newValue); - m_FromBPM = newValue; - } - if (m_pTextCtrl_ToBPM) { - str = m_pTextCtrl_ToBPM->GetValue(); - str.ToDouble(&newValue); - m_ToBPM = newValue; - } - - // from/to Length controls - // Don't do m_pTextCtrl_ToLength. It's disabled. - if (m_pTextCtrl_ToLength) { - str = m_pTextCtrl_ToLength->GetValue(); - str.ToDouble(&newValue); - m_ToLength = newValue; + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; } return true; } -// handler implementations for ChangeTempoDialog +// handler implementations for EffectChangeTempo -void ChangeTempoDialog::OnText_PercentChange(wxCommandEvent & WXUNUSED(event)) +void EffectChangeTempo::OnText_PercentChange(wxCommandEvent & WXUNUSED(evt)) { if (m_bLoopDetect) return; - if (m_pTextCtrl_PercentChange) { - wxString str = m_pTextCtrl_PercentChange->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_PercentChange = newValue; + m_pTextCtrl_PercentChange->GetValidator()->TransferFromWindow(); - m_bLoopDetect = true; - this->Update_Slider_PercentChange(); - this->Update_Text_ToBPM(); - this->Update_Text_ToLength(); - m_bLoopDetect = false; - - FindWindow(wxID_OK)->Enable(m_PercentChange > -100.0); - } + m_bLoopDetect = true; + Update_Slider_PercentChange(); + Update_Text_ToBPM(); + Update_Text_ToLength(); + m_bLoopDetect = false; } -void ChangeTempoDialog::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(event)) +void EffectChangeTempo::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(evt)) { if (m_bLoopDetect) return; - if (m_pSlider_PercentChange) { - m_PercentChange = (double)(m_pSlider_PercentChange->GetValue()); - // Warp positive values to actually go up faster & further than negatives. - if (m_PercentChange > 0.0) - m_PercentChange = pow(m_PercentChange, PERCENTCHANGE_SLIDER_WARP); + m_PercentChange = (double)(m_pSlider_PercentChange->GetValue()); + // Warp positive values to actually go up faster & further than negatives. + if (m_PercentChange > 0.0) + m_PercentChange = pow(m_PercentChange, kSliderWarp); - m_bLoopDetect = true; - this->Update_Text_PercentChange(); - this->Update_Text_ToBPM(); - this->Update_Text_ToLength(); - m_bLoopDetect = false; - } + m_bLoopDetect = true; + Update_Text_PercentChange(); + Update_Text_ToBPM(); + Update_Text_ToLength(); + m_bLoopDetect = false; } -void ChangeTempoDialog::OnText_FromBPM(wxCommandEvent & WXUNUSED(event)) +void EffectChangeTempo::OnText_FromBPM(wxCommandEvent & WXUNUSED(evt)) { if (m_bLoopDetect) return; - if (m_pTextCtrl_FromBPM) { - wxString str = m_pTextCtrl_FromBPM->GetValue(); - double newValue; - str.ToDouble(&newValue); - m_FromBPM = newValue; + m_pTextCtrl_FromBPM->GetValidator()->TransferFromWindow(); - m_bLoopDetect = true; + m_bLoopDetect = true; - this->Update_Text_ToBPM(); + Update_Text_ToBPM(); - m_bLoopDetect = false; - } + m_bLoopDetect = false; } -void ChangeTempoDialog::OnText_ToBPM(wxCommandEvent & WXUNUSED(event)) +void EffectChangeTempo::OnText_ToBPM(wxCommandEvent & WXUNUSED(evt)) { if (m_bLoopDetect) return; - if (m_pTextCtrl_ToBPM) { - wxString str = m_pTextCtrl_ToBPM->GetValue(); - double newValue; - str.ToDouble(&newValue); - m_ToBPM = newValue; + m_pTextCtrl_ToBPM->GetValidator()->TransferFromWindow(); - m_bLoopDetect = true; + m_bLoopDetect = true; - // If FromBPM has already been set, then there's a new percent change. - if (m_FromBPM != 0.0) { - m_PercentChange = ((m_ToBPM * 100.0) / m_FromBPM) - 100.0; - - this->Update_Text_PercentChange(); - this->Update_Slider_PercentChange(); - - this->Update_Text_ToLength(); - } - - m_bLoopDetect = false; - } -} - -void ChangeTempoDialog::OnText_ToLength(wxCommandEvent & WXUNUSED(event)) -{ - if (m_bLoopDetect) - return; - - if (m_pTextCtrl_ToLength) { - wxString str = m_pTextCtrl_ToLength->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_ToLength = newValue; - - m_PercentChange = ((m_FromLength * 100.0) / m_ToLength) - 100.0; - - m_bLoopDetect = true; - - this->Update_Text_PercentChange(); - this->Update_Slider_PercentChange(); - - this->Update_Text_ToBPM(); - - m_bLoopDetect = false; - } -} - -void ChangeTempoDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - - // Save & restore parameters around Preview, because we didn't do OK. - double oldPercentChange = mEffect->m_PercentChange; - if( m_PercentChange < -99.0) + // If FromBPM has already been set, then there's a new percent change. + if (m_FromBPM != 0.0 && m_ToBPM != 0.0) { - m_PercentChange = -99.0; - this->Update_Text_PercentChange(); + m_PercentChange = ((m_ToBPM * 100.0) / m_FromBPM) - 100.0; + + Update_Text_PercentChange(); + Update_Slider_PercentChange(); + + Update_Text_ToLength(); } - mEffect->m_PercentChange = m_PercentChange; - mEffect->Preview(); - mEffect->m_PercentChange = oldPercentChange; + + m_bLoopDetect = false; +} + +void EffectChangeTempo::OnText_ToLength(wxCommandEvent & WXUNUSED(evt)) +{ + if (m_bLoopDetect) + return; + + m_pTextCtrl_ToLength->GetValidator()->TransferFromWindow(); + + if (m_ToLength != 0.0) + { + m_PercentChange = ((m_FromLength * 100.0) / m_ToLength) - 100.0; + } + + m_bLoopDetect = true; + + Update_Text_PercentChange(); + Update_Slider_PercentChange(); + + Update_Text_ToBPM(); + + m_bLoopDetect = false; } // helper fns -void ChangeTempoDialog::Update_Text_PercentChange() +void EffectChangeTempo::Update_Text_PercentChange() { - if (m_pTextCtrl_PercentChange) { - wxString str; - str.Printf(wxT("%.3f"), m_PercentChange); - m_pTextCtrl_PercentChange->SetValue(str); - FindWindow(wxID_OK)->Enable(m_PercentChange > -100.0); - } + m_pTextCtrl_PercentChange->GetValidator()->TransferToWindow(); } -void ChangeTempoDialog::Update_Slider_PercentChange() +void EffectChangeTempo::Update_Slider_PercentChange() { - if (m_pSlider_PercentChange) { - double unwarped = m_PercentChange; - if (unwarped > 0.0) - // Un-warp values above zero to actually go up to PERCENTCHANGE_MAX. - unwarped = pow(m_PercentChange, (1.0 / PERCENTCHANGE_SLIDER_WARP)); + double unwarped = m_PercentChange; + if (unwarped > 0.0) + // Un-warp values above zero to actually go up to kSliderMax. + unwarped = pow(m_PercentChange, (1.0 / kSliderWarp)); - // Add 0.5 to unwarped so trunc -> round. - m_pSlider_PercentChange->SetValue((int)(unwarped + 0.5)); - } + // Add 0.5 to unwarped so trunc -> round. + m_pSlider_PercentChange->SetValue((int)(unwarped + 0.5)); } -void ChangeTempoDialog::Update_Text_ToBPM() +void EffectChangeTempo::Update_Text_ToBPM() // Use m_FromBPM & m_PercentChange to set new m_ToBPM & control. { - // Update ToBPM iff FromBPM has been set. - if (m_FromBPM == 0.0) - return; - m_ToBPM = (((m_FromBPM * (100.0 + m_PercentChange)) / 100.0)); - if (m_pTextCtrl_ToBPM) { - wxString str; - str.Printf(wxT("%.3f"), m_ToBPM); - m_pTextCtrl_ToBPM->SetValue(str); - } + m_pTextCtrl_ToBPM->GetValidator()->TransferToWindow(); } -void ChangeTempoDialog::Update_Text_ToLength() +void EffectChangeTempo::Update_Text_ToLength() // Use m_FromLength & m_PercentChange to set new m_ToLength & control. { m_ToLength = (m_FromLength * 100.0) / (100.0 + m_PercentChange); - if (m_pTextCtrl_ToLength) { - wxString str; - str.Printf(wxT("%.2f"), m_ToLength); - m_pTextCtrl_ToLength->SetValue(str); - } + m_pTextCtrl_ToLength->GetValidator()->TransferToWindow(); } -#endif // USE_SOUNDTOUCH +#endif // USE_SOUNDTOUCH \ No newline at end of file diff --git a/src/effects/ChangeTempo.h b/src/effects/ChangeTempo.h index 7aec4975a..79ca7d861 100644 --- a/src/effects/ChangeTempo.h +++ b/src/effects/ChangeTempo.h @@ -16,85 +16,57 @@ #ifndef __AUDACITY_EFFECT_CHANGETEMPO__ #define __AUDACITY_EFFECT_CHANGETEMPO__ +#include +#include +#include +#include + +#include "../ShuttleGui.h" + #include "SoundTouchEffect.h" -#include -#include -#include +#define CHANGETEMPO_PLUGIN_SYMBOL wxTRANSLATE("Change Tempo") -class wxString; -class wxTextCtrl; - - -class EffectChangeTempo : public EffectSoundTouch { - - public: +class EffectChangeTempo : public EffectSoundTouch +{ +public: EffectChangeTempo(); + virtual ~EffectChangeTempo(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Change Tempo...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://audacityteam.org/namespace#PitchAndTempo")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Change Tempo")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Changing Tempo")); - } + virtual EffectType GetType(); + virtual bool SupportsAutomation(); - // Useful only after PromptUser values have been set. - virtual wxString GetEffectDescription(); + // EffectClientInterface implementation + + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + + // Effect implementation virtual bool Init(); - - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); - - virtual bool CheckWhetherSkipEffect() { return (m_PercentChange == 0.0); } + virtual bool CheckWhetherSkipEffect(); virtual bool Process(); + virtual double CalcPreviewInputLength(double previewLength); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); - double CalcPreviewInputLength(double previewLength); +private: + // EffectChangeTempo implementation - private: - double m_PercentChange; // percent change to apply to tempo - // -100% is meaningless, but sky's the upper limit - double m_FromBPM; // user-set beats-per-minute. Zero means not yet set. - double m_ToBPM; // Zero value means not yet set. - double m_FromLength; // starting length of selection - double m_ToLength; // target length of selection - -friend class ChangeTempoDialog; -}; - -//---------------------------------------------------------------------------- -// ChangeTempoDialog -//---------------------------------------------------------------------------- - -class ChangeTempoDialog:public EffectDialog { - public: - ChangeTempoDialog(EffectChangeTempo * effect, - wxWindow * parent); - - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - - private: // handlers - void OnText_PercentChange(wxCommandEvent & event); - void OnSlider_PercentChange(wxCommandEvent & event); - void OnText_FromBPM(wxCommandEvent & event); - void OnText_ToBPM(wxCommandEvent & event); - void OnText_ToLength(wxCommandEvent & event); - - void OnPreview( wxCommandEvent &event ); + void OnText_PercentChange(wxCommandEvent & evt); + void OnSlider_PercentChange(wxCommandEvent & evt); + void OnText_FromBPM(wxCommandEvent & evt); + void OnText_ToBPM(wxCommandEvent & evt); + void OnText_ToLength(wxCommandEvent & evt); // helper fns void Update_Text_PercentChange(); // Update control per current m_PercentChange. @@ -102,8 +74,14 @@ class ChangeTempoDialog:public EffectDialog { void Update_Text_ToBPM(); // Use m_FromBPM & m_PercentChange to set new m_ToBPM & control. void Update_Text_ToLength(); // Use m_FromLength & m_PercentChange to set new m_ToLength & control. - private: - EffectChangeTempo * mEffect; +private: + double m_PercentChange; // percent change to apply to tempo + // -100% is meaningless, but sky's the upper limit + double m_FromBPM; // user-set beats-per-minute. Zero means not yet set. + double m_ToBPM; // Zero value means not yet set. + double m_FromLength; // starting length of selection + double m_ToLength; // target length of selection + bool m_bLoopDetect; // controls @@ -114,21 +92,9 @@ class ChangeTempoDialog:public EffectDialog { wxTextCtrl * m_pTextCtrl_FromLength; wxTextCtrl * m_pTextCtrl_ToLength; - public: - // effect parameters - double m_PercentChange; // percent change to apply to tempo - // -100% is meaningless, but sky's the upper limit. - // Slider is (-100, 200], but textCtrls can set higher. - double m_FromBPM; // user-set beats-per-minute. Zero means not yet set. - double m_ToBPM; // Zero value means not yet set. - double m_FromLength; // starting length of selection - double m_ToLength; // target length of selection - - private: - DECLARE_EVENT_TABLE() + DECLARE_EVENT_TABLE(); }; - #endif // __AUDACITY_EFFECT_CHANGETEMPO__ #endif // USE_SOUNDTOUCH diff --git a/src/effects/ClickRemoval.cpp b/src/effects/ClickRemoval.cpp index dd61fabd3..27a477a2b 100644 --- a/src/effects/ClickRemoval.cpp +++ b/src/effects/ClickRemoval.cpp @@ -22,101 +22,133 @@ This file is intended to become part of Audacity. You may modify and/or distribute it under the same terms as Audacity itself. -*//****************************************************************//** - -\class ClickRemovalDialog -\brief Dialog used with EffectClickRemoval - *//*******************************************************************/ - #include "../Audacity.h" #include -#if defined(__WXMSW__) && !defined(__CYGWIN__) -#include -#define finite(x) _finite(x) -#endif - -#include -#include -#include -#include -#include -#include #include +#include +#include + +#include "../Prefs.h" +#include "../widgets/valnum.h" #include "ClickRemoval.h" -#include "../ShuttleGui.h" -#include "../Envelope.h" -// #include "../FFT.h" -#include "../WaveTrack.h" -#include "../Prefs.h" -#define MIN_THRESHOLD 0 -#define MAX_THRESHOLD 900 -#define MIN_CLICK_WIDTH 0 -#define MAX_CLICK_WIDTH 40 +enum +{ + ID_Thresh = 10000, + ID_Width +}; + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( Threshold, int, wxTRANSLATE("Threshold"), 200, 0, 900, 1 ); +Param( Width, int, wxTRANSLATE("Width"), 20, 0, 40, 1 ); + +BEGIN_EVENT_TABLE(EffectClickRemoval, wxEvtHandler) + EVT_SLIDER(ID_Thresh, EffectClickRemoval::OnThreshSlider) + EVT_SLIDER(ID_Width, EffectClickRemoval::OnWidthSlider) + EVT_TEXT(ID_Thresh, EffectClickRemoval::OnThreshText) + EVT_TEXT(ID_Width, EffectClickRemoval::OnWidthText) +END_EVENT_TABLE() EffectClickRemoval::EffectClickRemoval() { + mThresholdLevel = DEF_Threshold; + mClickWidth = DEF_Width; + windowSize = 8192; sep = 2049; - - Init(); } EffectClickRemoval::~EffectClickRemoval() { } -bool EffectClickRemoval::Init() +// IdentInterface implementation + +wxString EffectClickRemoval::GetSymbol() { - mThresholdLevel = gPrefs->Read(wxT("/Effects/ClickRemoval/ClickThresholdLevel"), 200); - if ((mThresholdLevel < MIN_THRESHOLD) || (mThresholdLevel > MAX_THRESHOLD)) { // corrupted Prefs? - mThresholdLevel = 0; //Off-skip - gPrefs->Write(wxT("/Effects/ClickRemoval/ClickThresholdLevel"), mThresholdLevel); - } - mClickWidth = gPrefs->Read(wxT("/Effects/ClickRemoval/ClickWidth"), 20); - if ((mClickWidth < MIN_CLICK_WIDTH) || (mClickWidth > MAX_CLICK_WIDTH)) { // corrupted Prefs? - mClickWidth = 0; //Off-skip - gPrefs->Write(wxT("/Effects/ClickRemoval/ClickWidth"), mClickWidth); - } - return gPrefs->Flush(); + return CLICKREMOVAL_PLUGIN_SYMBOL; } +wxString EffectClickRemoval::GetDescription() +{ + return wxTRANSLATE("Click Removal is designed to remove clicks on audio tracks"); +} + +// EffectIdentInterface implementation + +EffectType EffectClickRemoval::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +bool EffectClickRemoval::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_Threshold, mThresholdLevel); + parms.Write(KEY_Width, mClickWidth); + + return true; +} + +bool EffectClickRemoval::SetAutomationParameters(EffectAutomationParameters & parms) +{ + ReadAndVerifyInt(Threshold); + ReadAndVerifyInt(Width); + + mThresholdLevel = Threshold; + mClickWidth = Width; + + return true; +} + +// Effect implementation + bool EffectClickRemoval::CheckWhetherSkipEffect() { return ((mClickWidth == 0) || (mThresholdLevel == 0)); } -bool EffectClickRemoval::PromptUser() +bool EffectClickRemoval::Startup() { - ClickRemovalDialog dlog(this, mParent); - dlog.mThresh = mThresholdLevel; - dlog.mWidth = mClickWidth; + wxString base = wxT("/Effects/ClickRemoval/"); - dlog.TransferDataToWindow(); - dlog.CentreOnParent(); - dlog.ShowModal(); + // Migrate settings from 2.1.0 or before - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; + // Already migrated, so bail + if (gPrefs->Exists(base + wxT("Migrated"))) + { + return true; + } - mThresholdLevel = dlog.mThresh; - mClickWidth = dlog.mWidth; + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + mThresholdLevel = gPrefs->Read(base + wxT("ClickThresholdLevel"), 200); + if ((mThresholdLevel < MIN_Threshold) || (mThresholdLevel > MAX_Threshold)) + { // corrupted Prefs? + mThresholdLevel = 0; //Off-skip + } + mClickWidth = gPrefs->Read(base + wxT("ClickWidth"), 20); + if ((mClickWidth < MIN_Width) || (mClickWidth > MAX_Width)) + { // corrupted Prefs? + mClickWidth = 0; //Off-skip + } - gPrefs->Write(wxT("/Effects/ClickRemoval/ClickThresholdLevel"), mThresholdLevel); - gPrefs->Write(wxT("/Effects/ClickRemoval/ClickWidth"), mClickWidth); + SaveUserPreset(GetCurrentSettingsGroup()); - return gPrefs->Flush(); -} + // Do not migrate again + gPrefs->Write(base + wxT("Migrated"), true); + gPrefs->Flush(); + } -bool EffectClickRemoval::TransferParameters( Shuttle & shuttle ) -{ - shuttle.TransferInt(wxT("Threshold"),mThresholdLevel,0); - shuttle.TransferInt(wxT("Width"),mClickWidth,0); return true; } @@ -153,7 +185,7 @@ bool EffectClickRemoval::Process() if (bGoodResult && !mbDidSomething) // Processing successful, but ineffective. wxMessageBox( wxString::Format(_("Algorithm not effective on this audio. Nothing changed.")), - this->GetEffectName(), + GetName(), wxOK | wxICON_ERROR); this->ReplaceProcessedTracks(bGoodResult && mbDidSomething); @@ -166,7 +198,7 @@ bool EffectClickRemoval::ProcessOne(int count, WaveTrack * track, sampleCount st { wxMessageBox( wxString::Format(_("Selection must be larger than %d samples."), windowSize/2), - this->GetEffectName(), + GetName(), wxOK | wxICON_ERROR); return false; } @@ -294,49 +326,8 @@ bool EffectClickRemoval::RemoveClicks(sampleCount len, float *buffer) return bResult; } - -// WDR: class implementations - -//---------------------------------------------------------------------------- -// ClickRemovalDialog -//---------------------------------------------------------------------------- - -const static wxChar *numbers[] = +void EffectClickRemoval::PopulateOrExchange(ShuttleGui & S) { - wxT("0"), wxT("1"), wxT("2"), wxT("3"), wxT("4"), - wxT("5"), wxT("6"), wxT("7"), wxT("8"), wxT("9") -}; - -// Declare window functions - -#define ID_THRESH_TEXT 10001 -#define ID_THRESH_SLIDER 10002 -#define ID_WIDTH_TEXT 10003 -#define ID_WIDTH_SLIDER 10004 - -// Declare ranges - -BEGIN_EVENT_TABLE(ClickRemovalDialog, EffectDialog) - EVT_SLIDER(ID_THRESH_SLIDER, ClickRemovalDialog::OnThreshSlider) - EVT_SLIDER(ID_WIDTH_SLIDER, ClickRemovalDialog::OnWidthSlider) - EVT_TEXT(ID_THRESH_TEXT, ClickRemovalDialog::OnThreshText) - EVT_TEXT(ID_WIDTH_TEXT, ClickRemovalDialog::OnWidthText) - EVT_BUTTON(ID_EFFECT_PREVIEW, ClickRemovalDialog::OnPreview) -END_EVENT_TABLE() - -ClickRemovalDialog::ClickRemovalDialog(EffectClickRemoval *effect, - wxWindow *parent) -: EffectDialog(parent, _("Click Removal"), PROCESS_EFFECT), - mEffect(effect) -{ - Init(); -} - -void ClickRemovalDialog::PopulateOrExchange(ShuttleGui & S) -{ - wxTextValidator vld(wxFILTER_INCLUDE_CHAR_LIST); - vld.SetIncludes(wxArrayString(10, numbers)); - S.AddSpace(0, 5); S.SetBorder(10); @@ -344,115 +335,84 @@ void ClickRemovalDialog::PopulateOrExchange(ShuttleGui & S) S.SetStretchyCol(2); { // Threshold - mThreshT = S.Id(ID_THRESH_TEXT).AddTextBox(_("Threshold (lower is more sensitive):"), - wxT(""), - 10); - mThreshT->SetValidator(vld); + IntegerValidator vldThresh(&mThresholdLevel); + vldThresh.SetRange(MIN_Threshold, MAX_Threshold); + mThreshT = S.Id(ID_Thresh).AddTextBox(_("Threshold (lower is more sensitive):"), + wxT(""), + 10); + mThreshT->SetValidator(vldThresh); S.SetStyle(wxSL_HORIZONTAL); - mThreshS = S.Id(ID_THRESH_SLIDER).AddSlider(wxT(""), - 0, - MAX_THRESHOLD); + mThreshS = S.Id(ID_Thresh).AddSlider(wxT(""), mThresholdLevel, MAX_Threshold, MIN_Threshold); mThreshS->SetName(_("Threshold")); - mThreshS->SetRange(MIN_THRESHOLD, MAX_THRESHOLD); + mThreshS->SetValidator(wxGenericValidator(&mThresholdLevel)); #if defined(__WXGTK__) // Force a minimum size since wxGTK allows it to go to zero mThreshS->SetMinSize(wxSize(100, -1)); #endif // Click width - mWidthT = S.Id(ID_WIDTH_TEXT).AddTextBox(_("Max Spike Width (higher is more sensitive):"), - wxT(""), - 10); - mWidthT->SetValidator(vld); + IntegerValidator vldWidth(&mClickWidth); + vldWidth.SetRange(MIN_Width, MAX_Width); + mWidthT = S.Id(ID_Width).AddTextBox(_("Max Spike Width (higher is more sensitive):"), + wxT(""), + 10); + mWidthT->SetValidator(vldWidth); S.SetStyle(wxSL_HORIZONTAL); - mWidthS = S.Id(ID_WIDTH_SLIDER).AddSlider(wxT(""), - 0, - MAX_CLICK_WIDTH); + mWidthS = S.Id(ID_Width).AddSlider(wxT(""), mClickWidth, MAX_Width, MIN_Width); mWidthS->SetName(_("Max Spike Width")); - mWidthS->SetRange(MIN_CLICK_WIDTH, MAX_CLICK_WIDTH); + mWidthS->SetValidator(wxGenericValidator(&mClickWidth)); #if defined(__WXGTK__) // Force a minimum size since wxGTK allows it to go to zero mWidthS->SetMinSize(wxSize(100, -1)); #endif } S.EndMultiColumn(); + return; } -bool ClickRemovalDialog::TransferDataToWindow() +bool EffectClickRemoval::TransferDataToWindow() { - mWidthS->SetValue(mWidth); - mThreshS->SetValue(mThresh); - - mWidthT->SetValue(wxString::Format(wxT("%d"), mWidth)); - mThreshT->SetValue(wxString::Format(wxT("%d"), mThresh)); + if (!mUIParent->TransferDataToWindow()) + { + return false; + } return true; } -bool ClickRemovalDialog::TransferDataFromWindow() +bool EffectClickRemoval::TransferDataFromWindow() { - mWidth = TrapLong(mWidthS->GetValue(), MIN_CLICK_WIDTH, MAX_CLICK_WIDTH); - mThresh = TrapLong(mThreshS->GetValue(), MIN_THRESHOLD, MAX_THRESHOLD); + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } return true; } -// WDR: handler implementations for ClickRemovalDialog - -void ClickRemovalDialog::OnWidthText(wxCommandEvent & WXUNUSED(event)) +void EffectClickRemoval::OnWidthText(wxCommandEvent & WXUNUSED(evt)) { - long val; - - mWidthT->GetValue().ToLong(&val); - mWidthS->SetValue(TrapLong(val, MIN_CLICK_WIDTH, MAX_CLICK_WIDTH)); + mWidthT->GetValidator()->TransferFromWindow(); + mWidthS->GetValidator()->TransferToWindow(); } -void ClickRemovalDialog::OnThreshText(wxCommandEvent & WXUNUSED(event)) +void EffectClickRemoval::OnThreshText(wxCommandEvent & WXUNUSED(evt)) { - long val; - - mThreshT->GetValue().ToLong(&val); - mThreshS->SetValue(TrapLong(val, MIN_THRESHOLD, MAX_THRESHOLD)); + mThreshT->GetValidator()->TransferFromWindow(); + mThreshS->GetValidator()->TransferToWindow(); } -void ClickRemovalDialog::OnWidthSlider(wxCommandEvent & WXUNUSED(event)) +void EffectClickRemoval::OnWidthSlider(wxCommandEvent & WXUNUSED(evt)) { - mWidthT->SetValue(wxString::Format(wxT("%d"), mWidthS->GetValue())); + mWidthS->GetValidator()->TransferFromWindow(); + mWidthT->GetValidator()->TransferToWindow(); } -void ClickRemovalDialog::OnThreshSlider(wxCommandEvent & WXUNUSED(event)) +void EffectClickRemoval::OnThreshSlider(wxCommandEvent & WXUNUSED(evt)) { - mThreshT->SetValue(wxString::Format(wxT("%d"), mThreshS->GetValue())); + mThreshS->GetValidator()->TransferFromWindow(); + mThreshT->GetValidator()->TransferToWindow(); } - -void ClickRemovalDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - mEffect->mThresholdLevel = mThresh; - mEffect->mClickWidth = mWidth; - mEffect->Preview(); -} - -// WDR: handler implementations for NoiseRemovalDialog -/* -void ClickRemovalDialog::OnPreview(wxCommandEvent &event) -{ - // Save & restore parameters around Preview, because we didn't do OK. - int oldLevel = mEffect->mThresholdLevel; - int oldWidth = mEffect->mClickWidth; - int oldSep = mEffect->sep; - - mEffect->mThresholdLevel = m_pSlider->GetValue(); - mEffect->mClickWidth = m_pSlider_width->GetValue(); - // mEffect->sep = m_pSlider_sep->GetValue(); - - mEffect->Preview(); - - mEffect->sep = oldSep; - mEffect->mClickWidth = oldWidth; - mEffect->mThresholdLevel = oldLevel; -} -*/ diff --git a/src/effects/ClickRemoval.h b/src/effects/ClickRemoval.h index a382a6d4d..b8fa17f06 100644 --- a/src/effects/ClickRemoval.h +++ b/src/effects/ClickRemoval.h @@ -16,54 +16,47 @@ #ifndef __AUDACITY_EFFECT_CLICK_REMOVAL__ #define __AUDACITY_EFFECT_CLICK_REMOVAL__ -#include -#include -#include -#include +#include #include -#include -#include +#include +#include -class wxString; +#include "../Envelope.h" +#include "../ShuttleGui.h" +#include "../WaveTrack.h" #include "Effect.h" -class Envelope; -class WaveTrack; - -class EffectClickRemoval: public Effect { +#define CLICKREMOVAL_PLUGIN_SYMBOL wxTRANSLATE("Click Removal") +class EffectClickRemoval : public Effect +{ public: - EffectClickRemoval(); virtual ~EffectClickRemoval(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Click Removal...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://audacityteam.org/namespace#NoiseRemoval")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Click Removal")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Removing clicks and pops...")); - } + virtual EffectType GetType(); - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); + // EffectClientInterface implementation - virtual bool Init(); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + + // Effect implementation virtual bool CheckWhetherSkipEffect(); - + virtual bool Startup(); virtual bool Process(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); private: bool ProcessOne(int count, WaveTrack * track, @@ -71,54 +64,26 @@ private: bool RemoveClicks(sampleCount len, float *buffer); + void OnWidthText(wxCommandEvent & evt); + void OnThreshText(wxCommandEvent & evt); + void OnWidthSlider(wxCommandEvent & evt); + void OnThreshSlider(wxCommandEvent & evt); + +private: Envelope *mEnvelope; bool mbDidSomething; // This effect usually does nothing on real-world data. - int windowSize; - int mThresholdLevel; - int mClickWidth; - int sep; + int windowSize; + int mThresholdLevel; + int mClickWidth; + int sep; -friend class ClickRemovalDialog; -}; - -// WDR: class declarations - -//---------------------------------------------------------------------------- -// BassWidthDialog -//---------------------------------------------------------------------------- -class ClickRemovalDialog:public EffectDialog { - public: - // constructors and destructors - ClickRemovalDialog(EffectClickRemoval *effect, wxWindow *parent); - - // WDR: method declarations for BassWidthDialog - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - - private: - // WDR: handler declarations for BassWidthDialog - void OnWidthText(wxCommandEvent & event); - void OnThreshText(wxCommandEvent & event); - void OnWidthSlider(wxCommandEvent & event); - void OnThreshSlider(wxCommandEvent & event); - void OnPreview(wxCommandEvent & event); - - private: wxSlider *mWidthS; wxSlider *mThreshS; wxTextCtrl *mWidthT; wxTextCtrl *mThreshT; - DECLARE_EVENT_TABLE() - - public: - EffectClickRemoval *mEffect; - - int mThresh; - int mWidth; - + DECLARE_EVENT_TABLE(); }; #endif diff --git a/src/effects/Compressor.cpp b/src/effects/Compressor.cpp index 813b260b8..d9402ca4a 100644 --- a/src/effects/Compressor.cpp +++ b/src/effects/Compressor.cpp @@ -19,67 +19,74 @@ *//****************************************************************//** -\class CompressorDialog -\brief Dialog used with EffectCompressor. - -*//****************************************************************//** - \class CompressorPanel -\brief Panel used within the CompressorDialog for EffectCompressor. +\brief Panel used within the EffectCompressor for EffectCompressor. *//*******************************************************************/ - -#include "../Audacity.h" // for rint from configwin.h +#include "../Audacity.h" #include -#include -#include #include -#include #include +#include +#include + +#include "../AColor.h" +#include "../Prefs.h" +#include "../float_cast.h" +#include "../widgets/Ruler.h" #include "Compressor.h" -#include "../ShuttleGui.h" -#include "../WaveTrack.h" -#include "../widgets/Ruler.h" -#include "../AColor.h" -#include "../Shuttle.h" -#include "../Prefs.h" + +enum +{ + ID_Threshold = 10000, + ID_NoiseFloor, + ID_Ratio, + ID_Attack, + ID_Decay +}; + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( Threshold, double, wxTRANSLATE("Threshold"), -12.0, -60.0, -1.0, 1 ); +Param( NoiseFloor, double, wxTRANSLATE("NoiseFloor"), -40.0, -80.0, -20.0, 5 ); +Param( Ratio, double, wxTRANSLATE("Ratio"), 2.0, 1.5, 10.0, 2 ); +Param( AttackTime, double, wxTRANSLATE("AttackTime"), 0.2, 0.1, 5.0, 100 ); +Param( ReleaseTime, double, wxTRANSLATE("ReleaseTime"), 1.0, 1.0, 30.0, 10 ); +Param( Normalize, bool, wxTRANSLATE("Normalize"), true, false, true, 1 ); +Param( UsePeak, bool, wxTRANSLATE("UsePeak"), false, false, true, 1 ); + +//---------------------------------------------------------------------------- +// EffectCompressor +//---------------------------------------------------------------------------- + +BEGIN_EVENT_TABLE(EffectCompressor, wxEvtHandler) + EVT_SLIDER(wxID_ANY, EffectCompressor::OnSlider) +END_EVENT_TABLE() EffectCompressor::EffectCompressor() { - mNormalize = true; - mUsePeak = false; + mThresholdDB = DEF_Threshold; + mNoiseFloorDB = DEF_NoiseFloor; + mAttackTime = DEF_AttackTime; // seconds + mDecayTime = DEF_ReleaseTime; // seconds + mRatio = DEF_Ratio; // positive number > 1.0 + mNormalize = DEF_Normalize; + mUsePeak = DEF_UsePeak; + mThreshold = 0.25; - mAttackTime = 0.2; // seconds - mDecayTime = 1.0; // seconds - mRatio = 2.0; // positive number > 1.0 - mCompression = 0.5; - mThresholdDB = -12.0; - mNoiseFloorDB = -40.0; mNoiseFloor = 0.01; + mCompression = 0.5; mCircle = NULL; mFollow1 = NULL; mFollow2 = NULL; mFollowLen = 0; } -bool EffectCompressor::Init() -{ - // Restore the saved preferences - gPrefs->Read(wxT("/Effects/Compressor/ThresholdDB"), &mThresholdDB, -12.0f ); - gPrefs->Read(wxT("/Effects/Compressor/NoiseFloorDB"), &mNoiseFloorDB, -40.0f ); - gPrefs->Read(wxT("/Effects/Compressor/Ratio"), &mRatio, 2.0f ); - gPrefs->Read(wxT("/Effects/Compressor/AttackTime"), &mAttackTime, 0.2f ); - gPrefs->Read(wxT("/Effects/Compressor/DecayTime"), &mDecayTime, 1.0f ); - gPrefs->Read(wxT("/Effects/Compressor/Normalize"), &mNormalize, true ); - gPrefs->Read(wxT("/Effects/Compressor/UsePeak"), &mUsePeak, false ); - - return true; -} - EffectCompressor::~EffectCompressor() { if (mCircle) { @@ -96,56 +103,226 @@ EffectCompressor::~EffectCompressor() } } -bool EffectCompressor::TransferParameters( Shuttle & shuttle ) +// IdentInterface implementation + +wxString EffectCompressor::GetSymbol() { - shuttle.TransferDouble( wxT("Threshold"), mThresholdDB, -12.0f ); - shuttle.TransferDouble( wxT("NoiseFloor"), mNoiseFloorDB, -40.0f ); - shuttle.TransferDouble( wxT("Ratio"), mRatio, 2.0f ); - shuttle.TransferDouble( wxT("AttackTime"), mAttackTime, 0.2f ); - shuttle.TransferDouble( wxT("ReleaseTime"), mDecayTime, 1.0f ); - shuttle.TransferBool( wxT("Normalize"), mNormalize, true ); - shuttle.TransferBool( wxT("UsePeak"), mUsePeak, false ); + return COMPRESSOR_PLUGIN_SYMBOL; +} + +wxString EffectCompressor::GetDescription() +{ + return wxTRANSLATE("Compresses the dynamic range of audio"); +} + +// EffectIdentInterface implementation + +EffectType EffectCompressor::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +bool EffectCompressor::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_Threshold, mThresholdDB); + parms.Write(KEY_NoiseFloor, mNoiseFloorDB); + parms.Write(KEY_Ratio, mRatio); + parms.Write(KEY_AttackTime, mAttackTime); + parms.Write(KEY_ReleaseTime, mDecayTime); + parms.Write(KEY_Normalize, mNormalize); + parms.Write(KEY_UsePeak, mUsePeak); + return true; } -bool EffectCompressor::PromptUser() +bool EffectCompressor::SetAutomationParameters(EffectAutomationParameters & parms) { - CompressorDialog dlog(this, mParent); - dlog.threshold = mThresholdDB; - dlog.noisefloor = mNoiseFloorDB; - dlog.ratio = mRatio; - dlog.attack = mAttackTime; - dlog.decay = mDecayTime; - dlog.useGain = mNormalize; - dlog.usePeak = mUsePeak; - dlog.TransferDataToWindow(); + ReadAndVerifyDouble(Threshold); + ReadAndVerifyDouble(NoiseFloor); + ReadAndVerifyDouble(Ratio); + ReadAndVerifyDouble(AttackTime); + ReadAndVerifyDouble(ReleaseTime); + ReadAndVerifyBool(Normalize); + ReadAndVerifyBool(UsePeak); - dlog.CentreOnParent(); - dlog.ShowModal(); + mThresholdDB = Threshold; + mNoiseFloorDB = NoiseFloor; + mRatio = Ratio; + mAttackTime = AttackTime; + mDecayTime = ReleaseTime; + mNormalize = Normalize; + mUsePeak = UsePeak; - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; - - mThresholdDB = dlog.threshold; - mNoiseFloorDB = dlog.noisefloor; - mRatio = dlog.ratio; - mAttackTime = dlog.attack; - mDecayTime = dlog.decay; - mNormalize = dlog.useGain; - mUsePeak = dlog.usePeak; - - // Retain the settings - gPrefs->Write(wxT("/Effects/Compressor/ThresholdDB"), mThresholdDB); - gPrefs->Write(wxT("/Effects/Compressor/NoiseFloorDB"), mNoiseFloorDB); - gPrefs->Write(wxT("/Effects/Compressor/Ratio"), mRatio); - gPrefs->Write(wxT("/Effects/Compressor/AttackTime"), mAttackTime); - gPrefs->Write(wxT("/Effects/Compressor/DecayTime"), mDecayTime); - gPrefs->Write(wxT("/Effects/Compressor/Normalize"), mNormalize); - gPrefs->Write(wxT("/Effects/Compressor/UsePeak"), mUsePeak); - - return gPrefs->Flush(); + return true; } +// Effect Implemenration + +bool EffectCompressor::Startup() +{ + wxString base = wxT("/Effects/Compressor/"); + + // Migrate settings from 2.1.0 or before + + // Already migrated, so bail + if (gPrefs->Exists(base + wxT("Migrated"))) + { + return true; + } + + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + gPrefs->Read(base + wxT("ThresholdDB"), &mThresholdDB, -12.0f ); + gPrefs->Read(base + wxT("NoiseFloorDB"), &mNoiseFloorDB, -40.0f ); + gPrefs->Read(base + wxT("Ratio"), &mRatio, 2.0f ); + gPrefs->Read(base + wxT("AttackTime"), &mAttackTime, 0.2f ); + gPrefs->Read(base + wxT("DecayTime"), &mDecayTime, 1.0f ); + gPrefs->Read(base + wxT("Normalize"), &mNormalize, true ); + gPrefs->Read(base + wxT("UsePeak"), &mUsePeak, false ); + + SaveUserPreset(GetCurrentSettingsGroup()); + + // Do not migrate again + gPrefs->Write(base + wxT("Migrated"), true); + gPrefs->Flush(); + } + + return true; +} + +void EffectCompressor::PopulateOrExchange(ShuttleGui & S) +{ + S.SetBorder(5); + + S.StartHorizontalLay(wxEXPAND, true); + { + S.SetBorder(10); + mPanel = new EffectCompressorPanel(S.GetParent(), + mThresholdDB, + mNoiseFloorDB, + mRatio); + mPanel->SetMinSize(wxSize(400, 200)); + S.Prop(true).AddWindow(mPanel, wxEXPAND | wxALL); + S.SetBorder(5); + } + S.EndHorizontalLay(); + + S.StartStatic(wxT("")); + { + S.StartMultiColumn(3, wxEXPAND | wxALIGN_CENTER_VERTICAL); + { + S.SetStretchyCol(1); + mThresholdLabel = S.AddVariableText(_("Threshold:"), true, + wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + S.SetStyle(wxSL_HORIZONTAL); + mThresholdSlider = S.Id(ID_Threshold).AddSlider(wxT(""), + DEF_Threshold * SCL_Threshold, + MAX_Threshold * SCL_Threshold, + MIN_Threshold * SCL_Threshold); + mThresholdSlider->SetName(_("Threshold")); + mThresholdText = S.AddVariableText(wxT("XXX dB"), true, + wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + + mNoiseFloorLabel = S.AddVariableText(_("Noise Floor:"), true, + wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + S.SetStyle(wxSL_HORIZONTAL); + mNoiseFloorSlider = S.Id(ID_NoiseFloor).AddSlider(wxT(""), + DEF_NoiseFloor / SCL_NoiseFloor, + MAX_NoiseFloor / SCL_NoiseFloor, + MIN_NoiseFloor / SCL_NoiseFloor); + mNoiseFloorSlider->SetName(_("Noise Floor")); + mNoiseFloorText = S.AddVariableText(wxT("XXX dB"), true, + wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + + mRatioLabel = S.AddVariableText(_("Ratio:"), true, + wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + S.SetStyle(wxSL_HORIZONTAL); + mRatioSlider = S.Id(ID_Ratio).AddSlider(wxT(""), + DEF_Ratio * SCL_Ratio, + MAX_Ratio * SCL_Ratio, + MIN_Ratio * SCL_Ratio); + mRatioSlider->SetName(_("Ratio")); + mRatioText = S.AddVariableText(wxT("XXXX:1"), true, + wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + + /* i18n-hint: Particularly in percussion, sounds can be regarded as having + * an 'attack' phase where the sound builds up and a 'decay' where the + * sound dies away. So this means 'onset duration'. */ + mAttackLabel = S.AddVariableText(_("Attack Time:"), true, + wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + S.SetStyle(wxSL_HORIZONTAL); + mAttackSlider = S.Id(ID_Attack).AddSlider(wxT(""), + DEF_AttackTime * SCL_AttackTime, + MAX_AttackTime * SCL_AttackTime, + MIN_AttackTime * SCL_AttackTime); + mAttackSlider->SetName(_("Attack Time")); + mAttackText = S.AddVariableText(wxT("XXXX secs"), true, + wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + + mDecayLabel = S.AddVariableText(_("Release Time:"), true, + wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + S.SetStyle(wxSL_HORIZONTAL); + mDecaySlider = S.Id(ID_Decay).AddSlider(wxT(""), + DEF_ReleaseTime * SCL_ReleaseTime, + MAX_ReleaseTime * SCL_ReleaseTime, + MIN_ReleaseTime * SCL_ReleaseTime); + mDecaySlider->SetName(_("Release Time")); + mDecayText = S.AddVariableText(wxT("XXXX secs"), true, + wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + } + S.EndMultiColumn(); + } + S.EndStatic(); + + S.StartHorizontalLay(wxCENTER, false); + { + /* i18n-hint: Make-up, i.e. correct for any reduction, rather than fabricate it.*/ + mGainCheckBox = S.AddCheckBox(_("Make-up gain for 0 dB after compressing"), + DEF_Normalize ? wxT("true") : wxT("false")); + mPeakCheckBox = S.AddCheckBox(_("Compress based on Peaks"), + DEF_UsePeak ? wxT("true") : wxT("false")); + } + S.EndHorizontalLay(); +} + +bool EffectCompressor::TransferDataToWindow() +{ + mThresholdSlider->SetValue(lrint(mThresholdDB)); + mNoiseFloorSlider->SetValue(lrint(mNoiseFloorDB / SCL_NoiseFloor)); + mRatioSlider->SetValue(lrint(mRatio * SCL_Ratio)); + mAttackSlider->SetValue(lrint(mAttackTime * SCL_AttackTime)); + mDecaySlider->SetValue(lrint(mDecayTime * SCL_ReleaseTime)); + mGainCheckBox->SetValue(mNormalize); + mPeakCheckBox->SetValue(mUsePeak); + + UpdateUI(); + + return true; +} + +bool EffectCompressor::TransferDataFromWindow() +{ + if (!mUIParent->Validate()) + { + return false; + } + + mThresholdDB = (double) mThresholdSlider->GetValue(); + mNoiseFloorDB = (double) mNoiseFloorSlider->GetValue() * SCL_NoiseFloor; + mRatio = (double) mRatioSlider->GetValue() / SCL_Ratio; + mAttackTime = (double) mAttackSlider->GetValue() / 100.0; //SCL_AttackTime; + mDecayTime = (double) mDecaySlider->GetValue() / SCL_ReleaseTime; + mNormalize = mGainCheckBox->GetValue(); + mUsePeak = mPeakCheckBox->GetValue(); + + return true; +} + +// EffectTwoPassSimpleMono implementation + bool EffectCompressor::NewTrackPass1() { mThreshold = pow(10.0, mThresholdDB/20); // factor of 20 because it's power @@ -175,6 +352,7 @@ bool EffectCompressor::NewTrackPass1() return true; } + bool EffectCompressor::InitPass1() { mMax=0.0; @@ -209,11 +387,12 @@ bool EffectCompressor::InitPass1() return true; } + bool EffectCompressor::InitPass2() { - // Actually, this should not even be called, because we call - // DisableSecondPass() before, if mNormalize is false. - return mNormalize; + // Actually, this should not even be called, because we call + // DisableSecondPass() before, if mNormalize is false. + return mNormalize; } // Process the input with 2 buffers available at a time @@ -432,25 +611,74 @@ float EffectCompressor::DoCompression(float value, double env) return out; } +void EffectCompressor::OnSlider(wxCommandEvent & WXUNUSED(evt)) +{ + TransferDataFromWindow(); + UpdateUI(); +} + +void EffectCompressor::UpdateUI() +{ + mThresholdLabel->SetName(wxString::Format(_("Threshold %d dB"), (int) mThresholdDB)); + /* i18n-hint: usually leave this as is as dB doesn't get translated*/ + mThresholdText->SetLabel(wxString::Format(_("%3d dB"), (int) mThresholdDB)); + mThresholdText->SetName(mThresholdText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) + + mNoiseFloorLabel->SetName(wxString::Format(_("Noise Floor %d dB"), (int) mNoiseFloorDB)); + mNoiseFloorText->SetLabel(wxString::Format(_("%3d dB"), (int) mNoiseFloorDB)); + mNoiseFloorText->SetName(mNoiseFloorText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) + + if (mRatioSlider->GetValue() % 2 == 0) { + mRatioLabel->SetName(wxString::Format(_("Ratio %.0f to 1"), mRatio)); + /* i18n-hint: Unless your language has a different convention for ratios, + * like 8:1, leave as is.*/ + mRatioText->SetLabel(wxString::Format(_("%.0f:1"), mRatio)); + } + else { + mRatioLabel->SetName(wxString::Format(_("Ratio %.1f to 1"), mRatio)); + /* i18n-hint: Unless your language has a different convention for ratios, + * like 8:1, leave as is.*/ + mRatioText->SetLabel(wxString::Format(_("%.1f:1"), mRatio)); + } + mRatioText->SetName(mRatioText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) + + mAttackLabel->SetName(wxString::Format(_("Attack Time %.2f secs"), mAttackTime)); + mAttackText->SetLabel(wxString::Format(_("%.2f secs"), mAttackTime)); + mAttackText->SetName(mAttackText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) + + mDecayLabel->SetName(wxString::Format(_("Release Time %.1f secs"), mDecayTime)); + mDecayText->SetLabel(wxString::Format(_("%.1f secs"), mDecayTime)); + mDecayText->SetName(mDecayText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) + + mPanel->Refresh(false); + + return; +} + //---------------------------------------------------------------------------- -// CompressorPanel +// EffectCompressorPanel //---------------------------------------------------------------------------- -BEGIN_EVENT_TABLE(CompressorPanel, wxPanel) - EVT_PAINT(CompressorPanel::OnPaint) +BEGIN_EVENT_TABLE(EffectCompressorPanel, wxPanel) + EVT_PAINT(EffectCompressorPanel::OnPaint) + EVT_SIZE(EffectCompressorPanel::OnSize) END_EVENT_TABLE() -CompressorPanel::CompressorPanel( wxWindow *parent, wxWindowID id, - const wxPoint& pos, - const wxSize& size): - wxPanel(parent, id, pos, size) +EffectCompressorPanel::EffectCompressorPanel(wxWindow *parent, + double & threshold, + double & noiseFloor, + double & ratio) +: wxPanel(parent), + threshold(threshold), + noiseFloor(noiseFloor), + ratio(ratio) { mBitmap = NULL; mWidth = 0; mHeight = 0; } -void CompressorPanel::OnPaint(wxPaintEvent & WXUNUSED(event)) +void EffectCompressorPanel::OnPaint(wxPaintEvent & WXUNUSED(evt)) { wxPaintDC dc(this); @@ -525,11 +753,11 @@ void CompressorPanel::OnPaint(wxPaintEvent & WXUNUSED(event)) mEnvRect = border; mEnvRect.Deflate( 2, 2 ); - int kneeX = (int)rint((rangeDB+threshold)*mEnvRect.width/rangeDB); - int kneeY = (int)rint((rangeDB+threshold/ratio)*mEnvRect.height/rangeDB); + int kneeX = lrint((rangeDB+threshold)*mEnvRect.width/rangeDB); + int kneeY = lrint((rangeDB+threshold/ratio)*mEnvRect.height/rangeDB); int finalY = mEnvRect.height; - int startY = (int)rint((threshold*(1.0/ratio-1.0))*mEnvRect.height/rangeDB); + int startY = lrint((threshold*(1.0/ratio-1.0))*mEnvRect.height/rangeDB); // Yellow line for threshold /* memDC.SetPen(wxPen(wxColour(220, 220, 0), 1, wxSOLID)); @@ -569,226 +797,7 @@ void CompressorPanel::OnPaint(wxPaintEvent & WXUNUSED(event)) &memDC, 0, 0, wxCOPY, FALSE); } -//---------------------------------------------------------------------------- -// CompressorDialog -//---------------------------------------------------------------------------- - -enum { - ThresholdID = 7100, - NoiseFloorID, - RatioID, - AttackID, - DecayID -}; - -BEGIN_EVENT_TABLE(CompressorDialog, EffectDialog) - EVT_SIZE( CompressorDialog::OnSize ) - EVT_BUTTON( ID_EFFECT_PREVIEW, CompressorDialog::OnPreview ) - EVT_SLIDER( ThresholdID, CompressorDialog::OnSlider ) - EVT_SLIDER( NoiseFloorID, CompressorDialog::OnSlider ) - EVT_SLIDER( RatioID, CompressorDialog::OnSlider ) - EVT_SLIDER( AttackID, CompressorDialog::OnSlider ) - EVT_SLIDER( DecayID, CompressorDialog::OnSlider ) -END_EVENT_TABLE() - -CompressorDialog::CompressorDialog(EffectCompressor *effect, wxWindow *parent) -: EffectDialog(parent, _("Dynamic Range Compressor"), PROCESS_EFFECT, - wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ), - mEffect(effect) +void EffectCompressorPanel::OnSize(wxSizeEvent & WXUNUSED(evt)) { - Init(); - - SetSizeHints(500, 400); - SetSize(500, 500); -} - -void CompressorDialog::PopulateOrExchange(ShuttleGui & S) -{ - S.SetBorder(5); - - S.StartHorizontalLay(wxEXPAND, true); - { - S.SetBorder(10); - mPanel = new CompressorPanel(S.GetParent(), wxID_ANY); - mPanel->threshold = threshold; - mPanel->noisefloor = noisefloor; - mPanel->ratio = ratio; - S.Prop(true).AddWindow(mPanel, wxEXPAND | wxALL); - S.SetBorder(5); - } - S.EndHorizontalLay(); - - S.StartStatic(wxT("")); - { - S.StartMultiColumn(3, wxEXPAND | wxALIGN_CENTER_VERTICAL); - { - S.SetStretchyCol(1); - mThresholdLabel = S.AddVariableText(_("Threshold:"), true, - wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); - S.SetStyle(wxSL_HORIZONTAL); - mThresholdSlider = S.Id(ThresholdID).AddSlider(wxT(""), -12, -1, -60); - mThresholdSlider->SetName(_("Threshold")); - mThresholdText = S.AddVariableText(wxT("XXX dB"), true, - wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - - mNoiseFloorLabel = S.AddVariableText(_("Noise Floor:"), true, - wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); - S.SetStyle(wxSL_HORIZONTAL); - mNoiseFloorSlider = S.Id(NoiseFloorID).AddSlider(wxT(""), -8, -4, -16); - mNoiseFloorSlider->SetName(_("Noise Floor")); - mNoiseFloorText = S.AddVariableText(wxT("XXX dB"), true, - wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - - mRatioLabel = S.AddVariableText(_("Ratio:"), true, - wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); - S.SetStyle(wxSL_HORIZONTAL); - mRatioSlider = S.Id(RatioID).AddSlider(wxT(""), 4, 20, 3); - mRatioSlider->SetName(_("Ratio")); - mRatioText = S.AddVariableText(wxT("XXXX:1"), true, - wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - - /* i18n-hint: Particularly in percussion, sounds can be regarded as having - * an 'attack' phase where the sound builds up and a 'decay' where the - * sound dies away. So this means 'onset duration'. */ - mAttackLabel = S.AddVariableText(_("Attack Time:"), true, - wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); - S.SetStyle(wxSL_HORIZONTAL); - mAttackSlider = S.Id(AttackID).AddSlider(wxT(""), 2, 500, 1); - mAttackSlider->SetName(_("Attack Time")); - mAttackText = S.AddVariableText(wxT("XXXX secs"), true, - wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - - mDecayLabel = S.AddVariableText(_("Release Time:"), true, - wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); - S.SetStyle(wxSL_HORIZONTAL); - mDecaySlider = S.Id(DecayID).AddSlider(wxT(""), 10, 300, 1); - mDecaySlider->SetName(_("Release Time")); - mDecayText = S.AddVariableText(wxT("XXXX secs"), true, - wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - } - S.EndMultiColumn(); - } - S.EndStatic(); - - S.StartHorizontalLay(wxCENTER, false); - { - /* i18n-hint: Make-up, i.e. correct for any reduction, rather than fabricate it.*/ - mGainCheckBox = S.AddCheckBox(_("Make-up gain for 0 dB after compressing"), - wxT("true")); - mPeakCheckBox = S.AddCheckBox(_("Compress based on Peaks"), - wxT("false")); - } - S.EndHorizontalLay(); -} - -bool CompressorDialog::TransferDataToWindow() -{ - mPanel->threshold = threshold; - mPanel->noisefloor = noisefloor; - mPanel->ratio = ratio; - - mThresholdSlider->SetValue((int)rint(threshold)); - mNoiseFloorSlider->SetValue((int)rint(noisefloor/5)); - mRatioSlider->SetValue((int)rint(ratio*2)); - mAttackSlider->SetValue((int)rint(attack*100)); - mDecaySlider->SetValue((int)rint(decay*10)); - mGainCheckBox->SetValue(useGain); - mPeakCheckBox->SetValue(usePeak); - - TransferDataFromWindow(); - - return true; -} - -bool CompressorDialog::TransferDataFromWindow() -{ - threshold = (double)mThresholdSlider->GetValue(); - noisefloor = (double)mNoiseFloorSlider->GetValue() * 5.0; - ratio = (double)(mRatioSlider->GetValue() / 2.0); - attack = (double)(mAttackSlider->GetValue() / 100.0); - decay = (double)(mDecaySlider->GetValue() / 10.0); - useGain = mGainCheckBox->GetValue(); - usePeak = mPeakCheckBox->GetValue(); - - mPanel->threshold = threshold; - mPanel->noisefloor = noisefloor; - mPanel->ratio = ratio; - - mThresholdLabel->SetName(wxString::Format(_("Threshold %d dB"), (int) threshold)); - /* i18n-hint: usually leave this as is as dB doesn't get translated*/ - mThresholdText->SetLabel(wxString::Format(_("%3d dB"), (int) threshold)); - mThresholdText->SetName(mThresholdText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - - mNoiseFloorLabel->SetName(wxString::Format(_("Noise Floor %d dB"), (int) noisefloor)); - mNoiseFloorText->SetLabel(wxString::Format(_("%3d dB"), (int) noisefloor)); - mNoiseFloorText->SetName(mNoiseFloorText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - - if (mRatioSlider->GetValue()%2 == 0) { - mRatioLabel->SetName(wxString::Format(_("Ratio %.0f to 1"), ratio)); - /* i18n-hint: Unless your language has a different convention for ratios, - * like 8:1, leave as is.*/ - mRatioText->SetLabel(wxString::Format(_("%.0f:1"), ratio)); - } - else { - mRatioLabel->SetName(wxString::Format(_("Ratio %.1f to 1"), ratio)); - /* i18n-hint: Unless your language has a different convention for ratios, - * like 8:1, leave as is.*/ - mRatioText->SetLabel(wxString::Format(_("%.1f:1"), ratio)); - } - mRatioText->SetName(mRatioText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - - mAttackLabel->SetName(wxString::Format(_("Attack Time %.2f secs"), attack)); - mAttackText->SetLabel(wxString::Format(_("%.2f secs"), attack)); - mAttackText->SetName(mAttackText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - - mDecayLabel->SetName(wxString::Format(_("Release Time %.1f secs"), decay)); - mDecayText->SetLabel(wxString::Format(_("%.1f secs"), decay)); - mDecayText->SetName(mDecayText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - - mPanel->Refresh(false); - - return true; -} - -void CompressorDialog::OnSize(wxSizeEvent &event) -{ - mPanel->Refresh( false ); - event.Skip(); -} - -void CompressorDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - - // Save & restore parameters around Preview, because we didn't do OK. - double oldAttackTime = mEffect->mAttackTime; - double oldDecayTime = mEffect->mDecayTime; - double oldThresholdDB = mEffect->mThresholdDB; - double oldNoiseFloorDB = mEffect->mNoiseFloorDB; - double oldRatio = mEffect->mRatio; - bool oldUseGain = mEffect->mNormalize; - bool oldUsePeak = mEffect->mUsePeak; - - mEffect->mAttackTime = attack; - mEffect->mDecayTime = decay; - mEffect->mThresholdDB = threshold; - mEffect->mNoiseFloorDB = noisefloor; - mEffect->mRatio = ratio; - mEffect->mNormalize = useGain; - mEffect->mUsePeak = usePeak; - - mEffect->Preview(); - - mEffect->mAttackTime = oldAttackTime; - mEffect->mDecayTime = oldDecayTime; - mEffect->mThresholdDB = oldThresholdDB; - mEffect->mNoiseFloorDB = oldNoiseFloorDB; - mEffect->mRatio = oldRatio; - mEffect->mNormalize = oldUseGain; - mEffect->mUsePeak = oldUsePeak; -} - -void CompressorDialog::OnSlider(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); + Refresh(false); } diff --git a/src/effects/Compressor.h b/src/effects/Compressor.h index 5fb9fc850..a6e81b267 100644 --- a/src/effects/Compressor.h +++ b/src/effects/Compressor.h @@ -11,72 +11,78 @@ #ifndef __AUDACITY_EFFECT_COMPRESSOR__ #define __AUDACITY_EFFECT_COMPRESSOR__ -class wxString; - -#include -#include -#include #include -#include #include -#include +#include +#include #include -#include +#include +#include #include -#include +#include + +#include "../ShuttleGui.h" + #include "TwoPassSimpleMono.h" -class WaveTrack; +class EffectCompressorPanel; -class EffectCompressor: public EffectTwoPassSimpleMono { +#define COMPRESSOR_PLUGIN_SYMBOL wxTRANSLATE("Compressor") +class EffectCompressor : public EffectTwoPassSimpleMono +{ public: EffectCompressor(); virtual ~EffectCompressor(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Compressor...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#CompressorPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Compressor")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Applying Dynamic Range Compression...")); - } + virtual EffectType GetType(); - virtual bool Init(); - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); + // EffectClientInterface implementation - protected: - virtual bool TwoBufferProcessPass1(float *buffer1, sampleCount len1, float *buffer2, sampleCount len2); - virtual bool ProcessPass2(float *buffer, sampleCount len); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); - private: + // Effect implementation + + virtual bool Startup(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); + +protected: + // EffectTwoPassSimpleMono implementation - virtual bool NewTrackPass1(); virtual bool InitPass1(); virtual bool InitPass2(); + virtual bool NewTrackPass1(); + virtual bool ProcessPass2(float *buffer, sampleCount len); + virtual bool TwoBufferProcessPass1(float *buffer1, sampleCount len1, float *buffer2, sampleCount len2); + +private: + // EffectCompressor implementation void FreshenCircle(); float AvgCircle(float x); + void Follow(float *buffer, float *env, int len, float *previous, int previous_len); + float DoCompression(float x, double env); + + void OnSlider(wxCommandEvent & evt); + void UpdateUI(); + +private: double mRMSSum; int mCircleSize; int mCirclePos; double *mCircle; - void Follow(float *buffer, float *env, int len, float *previous, int previous_len); - float DoCompression(float x, double env); - double mAttackTime; double mThresholdDB; double mNoiseFloorDB; @@ -94,69 +100,13 @@ public: int mNoiseCounter; double mGain; double mLastLevel; - float *mFollow1; - float *mFollow2; + float *mFollow1; + float *mFollow2; sampleCount mFollowLen; double mMax; //MJS - friend class CompressorDialog; -}; - -class CompressorPanel: public wxPanel -{ -public: - CompressorPanel( wxWindow *parent, wxWindowID id, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize); - - void OnPaint(wxPaintEvent & event); - - double threshold; - double noisefloor; - double ratio; - -private: - - wxBitmap *mBitmap; - wxRect mEnvRect; - int mWidth; - int mHeight; - - DECLARE_EVENT_TABLE() -}; - -// WDR: class declarations - -//---------------------------------------------------------------------------- -// CompressorDialog -//---------------------------------------------------------------------------- - -class CompressorDialog: public EffectDialog -{ -public: - // constructors and destructors - CompressorDialog( EffectCompressor *effect, wxWindow *parent); - - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - - double threshold; - double noisefloor; - double ratio; - double attack; - double decay; // "release" - bool useGain; - bool usePeak; - -private: - void OnSize( wxSizeEvent &event ); - void OnSlider( wxCommandEvent &event ); - void OnPreview( wxCommandEvent &event ); - - EffectCompressor *mEffect; - CompressorPanel *mPanel; + EffectCompressorPanel *mPanel; wxStaticText *mThresholdLabel; wxSlider *mThresholdSlider; @@ -181,8 +131,32 @@ private: wxCheckBox *mGainCheckBox; wxCheckBox *mPeakCheckBox; + DECLARE_EVENT_TABLE(); +}; + +class EffectCompressorPanel: public wxPanel +{ +public: + EffectCompressorPanel(wxWindow *parent, + double & threshold, + double & noiseFloor, + double & ratio); + private: - DECLARE_EVENT_TABLE() + void OnPaint(wxPaintEvent & evt); + void OnSize(wxSizeEvent & evt); + +private: + double & threshold; + double & noiseFloor; + double & ratio; + + wxBitmap *mBitmap; + wxRect mEnvRect; + int mWidth; + int mHeight; + + DECLARE_EVENT_TABLE(); }; #endif diff --git a/src/effects/Contrast.cpp b/src/effects/Contrast.cpp index 5d045b6a3..d38e942c2 100644 --- a/src/effects/Contrast.cpp +++ b/src/effects/Contrast.cpp @@ -257,7 +257,7 @@ ContrastDialog::ContrastDialog(wxWindow * parent, wxWindowID id, //Foreground S.AddFixedText(_("&Foreground:"), false); - if (mForegroundStartT == NULL) + if (S.GetMode() == eIsCreating) { mForegroundStartT = new NumericTextCtrl(NumericConverter::TIME, this, @@ -273,7 +273,7 @@ ContrastDialog::ContrastDialog(wxWindow * parent, wxWindowID id, } S.AddWindow(mForegroundStartT); - if (mForegroundEndT == NULL) + if (S.GetMode() == eIsCreating) { mForegroundEndT = new NumericTextCtrl(NumericConverter::TIME, this, @@ -295,7 +295,7 @@ ContrastDialog::ContrastDialog(wxWindow * parent, wxWindowID id, //Background S.AddFixedText(_("&Background:")); - if (mBackgroundStartT == NULL) + if (S.GetMode() == eIsCreating) { mBackgroundStartT = new NumericTextCtrl(NumericConverter::TIME, this, @@ -311,7 +311,7 @@ ContrastDialog::ContrastDialog(wxWindow * parent, wxWindowID id, } S.AddWindow(mBackgroundStartT); - if (mBackgroundEndT == NULL) + if (S.GetMode() == eIsCreating) { mBackgroundEndT = new NumericTextCtrl(NumericConverter::TIME, this, diff --git a/src/effects/DtmfGen.cpp b/src/effects/DtmfGen.cpp index 271faef05..528999761 100644 --- a/src/effects/DtmfGen.cpp +++ b/src/effects/DtmfGen.cpp @@ -9,50 +9,269 @@ *******************************************************************//** \class EffectDtmf -\brief An effect for the "Generator" menu to generate DTMF tones +\brief An effect that generates DTMF tones *//*******************************************************************/ -// For compilers that support precompilation, includes "wx/wx.h". -#include - -#ifndef WX_PRECOMP -// Include your minimal set of headers here, or wx.h -#include -#endif - -#include "DtmfGen.h" #include "../Audacity.h" -#include "../Project.h" -#include "../Prefs.h" -#include "../ShuttleGui.h" -#include "../WaveTrack.h" -#include "../widgets/NumericTextCtrl.h" -#include -#include -#include -#include -#include -#include -#include +#include +#include #include -#ifndef M_PI -#define M_PI 3.14159265358979323846 /* pi */ -#endif -#define DUTY_MIN 0 -#define DUTY_MAX 1000 -#define DUTY_SCALE (DUTY_MAX/100.0) // ensure float division -#define FADEINOUT 250.0 // used for fadein/out needed to remove clicking noise -#define AMP_MIN 0 -#define AMP_MAX 1 +#include "../Prefs.h" +#include "../widgets/valnum.h" + +#include "DtmfGen.h" + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( Sequence, wxString, wxTRANSLATE("Sequence"), wxT("audacity"), wxT(""), wxT(""), wxT("")); +Param( DutyCycle, double, wxTRANSLATE("Duty Cycle"), 55.0, 0.0, 100.0, 10.0 ); +Param( Amplitude, double, wxTRANSLATE("Amplitude"), 0.0, 0.0, 1.0, 1 ); + +static const double kFadeInOut = 250.0; // used for fadein/out needed to remove clicking noise + +const static wxChar *kSymbols[] = +{ + wxT("0"), wxT("1"), wxT("2"), wxT("3"), + wxT("4"), wxT("5"), wxT("6"), wxT("7"), + wxT("8"), wxT("9"), wxT("*"), wxT("#"), + wxT("A"), wxT("B"), wxT("C"), wxT("D"), + wxT("a"), wxT("b"), wxT("c"), wxT("d"), + wxT("e"), wxT("f"), wxT("g"), wxT("h"), + wxT("i"), wxT("j"), wxT("k"), wxT("l"), + wxT("m"), wxT("n"), wxT("o"), wxT("p"), + wxT("q"), wxT("r"), wxT("s"), wxT("t"), + wxT("u"), wxT("v"), wxT("w"), wxT("x"), + wxT("y"), wxT("z") +}; // // EffectDtmf // -bool EffectDtmf::Init() +BEGIN_EVENT_TABLE(EffectDtmf, wxEvtHandler) + EVT_TEXT(wxID_ANY, EffectDtmf::OnText) + EVT_SLIDER(wxID_ANY, EffectDtmf::OnSlider) +END_EVENT_TABLE() + +EffectDtmf::EffectDtmf() +{ + dtmfDutyCycle = DEF_DutyCycle; + dtmfAmplitude = DEF_Amplitude; + dtmfString = DEF_Sequence; + mDuration = GetDefaultDuration(); + dtmfTone = 0.0; + dtmfSilence = 0.0; +} + +EffectDtmf::~EffectDtmf() +{ +} + +// IdentInterface implementation + +wxString EffectDtmf::GetSymbol() +{ + return DTMFTONES_PLUGIN_SYMBOL; +} + +wxString EffectDtmf::GetDescription() +{ + return wxTRANSLATE("Generates dual-tone multi-frequency (DTMF) tones like those produced by the keypad on telephones"); +} + +// EffectIdentInterface implementation + +EffectType EffectDtmf::GetType() +{ + return EffectTypeGenerate; +} + +// EffectClientInterface implementation + +int EffectDtmf::GetAudioOutCount() +{ + return 1; +} + +bool EffectDtmf::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) +{ + // all dtmf sequence durations in samples from seconds + // MJS: Note that mDuration is in seconds but will have been quantised to the units of the TTC. + // If this was 'samples' and the project rate was lower than the track rate, + // extra samples may get created as mDuration may now be > mT1 - mT0; + // However we are making our best efforts at creating what was asked for. + + sampleCount nT0 = (sampleCount)floor(mT0 * mSampleRate + 0.5); + sampleCount nT1 = (sampleCount)floor((mT0 + mDuration) * mSampleRate + 0.5); + numSamplesSequence = nT1 - nT0; // needs to be exact number of samples selected + + //make under-estimates if anything, and then redistribute the few remaining samples + numSamplesTone = (sampleCount)floor(dtmfTone * mSampleRate); + numSamplesSilence = (sampleCount)floor(dtmfSilence * mSampleRate); + + // recalculate the sum, and spread the difference - due to approximations. + // Since diff should be in the order of "some" samples, a division (resulting in zero) + // is not sufficient, so we add the additional remaining samples in each tone/silence block, + // at least until available. + diff = numSamplesSequence - (dtmfNTones*numSamplesTone) - (dtmfNTones-1)*numSamplesSilence; + while (diff > 2*dtmfNTones - 1) { // more than one per thingToBeGenerated + // in this case, both numSamplesTone and numSamplesSilence would change, so it makes sense + // to recalculate diff here, otherwise just keep the value we already have + + // should always be the case that dtmfNTones>1, as if 0, we don't even start processing, + // and with 1 there is no difference to spread (no silence slot)... + wxASSERT(dtmfNTones > 1); + numSamplesTone += (diff/(dtmfNTones)); + numSamplesSilence += (diff/(dtmfNTones-1)); + diff = numSamplesSequence - (dtmfNTones*numSamplesTone) - (dtmfNTones-1)*numSamplesSilence; + } + wxASSERT(diff >= 0); // should never be negative + + curSeqPos = -1; // pointer to string in dtmfString + isTone = false; + numRemaining = 0; + + return true; +} + +sampleCount EffectDtmf::ProcessBlock(float **WXUNUSED(inbuf), float **outbuf, sampleCount size) +{ + float *buffer = outbuf[0]; + sampleCount processed = 0; + + // for the whole dtmf sequence, we will be generating either tone or silence + // according to a bool value, and this might be done in small chunks of size + // 'block', as a single tone might sometimes be larger than the block + // tone and silence generally have different duration, thus two generation blocks + // + // Note: to overcome a 'clicking' noise introduced by the abrupt transition from/to + // silence, I added a fade in/out of 1/250th of a second (4ms). This can still be + // tweaked but gives excellent results at 44.1kHz: I haven't tried other freqs. + // A problem might be if the tone duration is very short (<10ms)... (?) + // + // One more problem is to deal with the approximations done when calculating the duration + // of both tone and silence: in some cases the final sum might not be same as the initial + // duration. So, to overcome this, we had a redistribution block up, and now we will spread + // the remaining samples in every bin in order to achieve the full duration: test case was + // to generate an 11 tone DTMF sequence, in 4 seconds, and with DutyCycle=75%: after generation + // you ended up with 3.999s or in other units: 3 seconds and 44097 samples. + // + while (size) + { + if (numRemaining == 0) + { + isTone = !isTone; + + if (isTone) + { + curSeqPos++; + numRemaining = numSamplesTone; + curTonePos = 0; + } + else + { + numRemaining = numSamplesSilence; + } + + // the statement takes care of extracting one sample from the diff bin and + // adding it into the current block until depletion + numRemaining += (diff-- > 0 ? 1 : 0); + } + + sampleCount len = wxMin(numRemaining, size); + + if (isTone) + { + // generate the tone and append + MakeDtmfTone(buffer, len, mSampleRate, dtmfString[curSeqPos], curTonePos, numSamplesTone, dtmfAmplitude); + curTonePos += len; + } + else + { + memset(buffer, 0, sizeof(float) * len); + } + + numRemaining -= len; + + buffer += len; + size -= len; + processed += len; + } + + return processed; +} + +bool EffectDtmf::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_Sequence, dtmfString); + parms.Write(KEY_DutyCycle, dtmfDutyCycle); + parms.Write(KEY_Amplitude, dtmfAmplitude); + + return true; +} + +bool EffectDtmf::SetAutomationParameters(EffectAutomationParameters & parms) +{ + ReadAndVerifyDouble(DutyCycle); + ReadAndVerifyDouble(Amplitude); + ReadAndVerifyString(Sequence); + + wxString symbols; + for (unsigned int i = 0; i < WXSIZEOF(kSymbols); i++) + { + symbols += kSymbols[i]; + } + + if (Sequence.find_first_not_of(symbols) != wxString::npos) + { + return false; + } + + dtmfDutyCycle = DutyCycle; + dtmfAmplitude = Amplitude; + dtmfString = Sequence; + + Recalculate(); + + return true; +} + +// Effect implementation + +bool EffectDtmf::Startup() +{ + wxString base = wxT("/Effects/DtmfGen/"); + + // Migrate settings from 2.1.0 or before + + // Already migrated, so bail + if (gPrefs->Exists(base + wxT("Migrated"))) + { + return true; + } + + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + gPrefs->Read(base + wxT("String"), &dtmfString, wxT("audacity")); + gPrefs->Read(base + wxT("DutyCycle"), &dtmfDutyCycle, 550L); + gPrefs->Read(base + wxT("Amplitude"), &dtmfAmplitude, 0.8f); + + SaveUserPreset(GetCurrentSettingsGroup()); + + // Do not migrate again + gPrefs->Write(base + wxT("Migrated"), true); + gPrefs->Flush(); + } + + return true; +} + +void EffectDtmf::PopulateOrExchange(ShuttleGui & S) { // dialog will be passed values from effect // Effect retrieves values from saved config @@ -61,75 +280,151 @@ bool EffectDtmf::Init() // value from saved config: this is useful is user wants to // replace selection with dtmf sequence + bool isSelection = false; if (mT1 > mT0) { // there is a selection: let's fit in there... // MJS: note that this is just for the TTC and is independent of the track rate // but we do need to make sure we have the right number of samples at the project rate - AudacityProject *p = GetActiveProject(); - double projRate = p->GetRate(); - double quantMT0 = QUANTIZED_TIME(mT0, projRate); - double quantMT1 = QUANTIZED_TIME(mT1, projRate); + double quantMT0 = QUANTIZED_TIME(mT0, mProjectRate); + double quantMT1 = QUANTIZED_TIME(mT1, mProjectRate); mDuration = quantMT1 - quantMT0; - mIsSelection = true; - } else { - // retrieve last used values - gPrefs->Read(wxT("/Effects/DtmfGen/SequenceDuration"), &mDuration, 1L); - mIsSelection = false; + isSelection = true; } - gPrefs->Read(wxT("/Effects/DtmfGen/String"), &dtmfString, wxT("audacity")); - gPrefs->Read(wxT("/Effects/DtmfGen/DutyCycle"), &dtmfDutyCycle, 550L); - gPrefs->Read(wxT("/Effects/DtmfGen/Amplitude"), &dtmfAmplitude, 0.8f); - dtmfNTones = wxStrlen(dtmfString); + S.AddSpace(0, 5); + S.StartMultiColumn(2, wxCENTER); + { + wxTextValidator vldDtmf(wxFILTER_INCLUDE_CHAR_LIST, &dtmfString); + vldDtmf.SetIncludes(wxArrayString(WXSIZEOF(kSymbols), kSymbols)); + S.AddTextBox(_("DTMF sequence:"), wxT(""), 10)->SetValidator(vldDtmf); - return true; + FloatingPointValidator vldAmp(1, &dtmfAmplitude); + vldAmp.SetRange(MIN_Amplitude, MAX_Amplitude); + S.AddTextBox(_("Amplitude (0-1):"), wxT(""), 10)->SetValidator(vldAmp); + + S.AddPrompt(_("Duration:")); + mDtmfDurationT = new + NumericTextCtrl(NumericConverter::TIME, + S.GetParent(), + wxID_ANY, + /* use this instead of "seconds" because if a selection is passed to the + * effect, I want it (mDuration) to be used as the duration, and with + * "seconds" this does not always work properly. For example, it rounds + * down to zero... */ + isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), + mDuration, + mProjectRate, + wxDefaultPosition, + wxDefaultSize, + true); + mDtmfDurationT->SetName(_("Duration")); + mDtmfDurationT->EnableMenu(); + S.AddWindow(mDtmfDurationT); + + S.AddFixedText(_("Tone/silence ratio:"), false); + S.SetStyle(wxSL_HORIZONTAL | wxEXPAND); + mDtmfDutyS = S.AddSlider(wxT(""), + dtmfDutyCycle * SCL_DutyCycle, + MAX_DutyCycle * SCL_DutyCycle, + MIN_DutyCycle * SCL_DutyCycle); + S.SetSizeHints(-1,-1); + } + S.EndMultiColumn(); + + S.StartMultiColumn(2, wxCENTER); + { + S.AddFixedText(_("Duty cycle:"), false); + mDtmfDutyT = S.AddVariableText(wxString::Format(wxT("%.1f %%"), dtmfDutyCycle), false); + + S.AddFixedText(_("Tone duration:"), false); + mDtmfSilenceT = S.AddVariableText(wxString::Format(wxString(wxT("%.0f ")) + _("ms"), dtmfTone * 1000.0), false); + + S.AddFixedText(_("Silence duration:"), false); + mDtmfToneT = S.AddVariableText(wxString::Format(wxString(wxT("%0.f ")) + _("ms"), dtmfSilence * 1000.0), false); + } + S.EndMultiColumn(); } -bool EffectDtmf::PromptUser() +bool EffectDtmf::TransferDataToWindow() { - DtmfDialog dlog(this, mParent, - /* i18n-hint: DTMF stands for 'Dial Tone Modulation Format'. Leave as is.*/ - _("DTMF Tone Generator")); + Recalculate(); - Init(); - - // Initialize dialog locals - dlog.dIsSelection = mIsSelection; - dlog.dString = dtmfString; - dlog.dDutyCycle = dtmfDutyCycle; - dlog.dDuration = mDuration; - dlog.dAmplitude = dtmfAmplitude; - - // start dialog - dlog.Init(); - dlog.ShowModal(); - - if (dlog.GetReturnCode() == wxID_CANCEL) + if (!mUIParent->TransferDataToWindow()) + { return false; + } - // if there was an OK, retrieve values - dtmfString = dlog.dString; - dtmfDutyCycle = dlog.dDutyCycle; - mDuration = dlog.dDuration; - dtmfAmplitude = dlog.dAmplitude; + mDtmfDutyS->SetValue(dtmfDutyCycle * SCL_DutyCycle); + mDtmfDurationT->SetValue(mDuration); - dtmfNTones = dlog.dNTones; - dtmfTone = dlog.dTone; - dtmfSilence = dlog.dSilence; + UpdateUI(); return true; } - -bool EffectDtmf::TransferParameters( Shuttle & WXUNUSED(shuttle) ) +bool EffectDtmf::TransferDataFromWindow() { + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } + + dtmfDutyCycle = (double) mDtmfDutyS->GetValue() / SCL_DutyCycle; + mDuration = mDtmfDurationT->GetValue(); + + // recalculate to make sure all values are up-to-date. This is especially + // important if the user did not change any values in the dialog + Recalculate(); + return true; } +// EffectDtmf implementation + +void EffectDtmf::Recalculate() +{ + // remember that dtmfDutyCycle is in range (0.0-100.0) + + dtmfNTones = (int) dtmfString.Length(); + + if (dtmfNTones==0) { + // no tones, all zero: don't do anything + // this should take care of the case where user got an empty + // dtmf sequence into the generator: track won't be generated + mDuration = 0; + dtmfTone = 0; + dtmfSilence = mDuration; + } else { + if (dtmfNTones==1) { + // single tone, as long as the sequence + dtmfTone = mDuration; + dtmfSilence = 0; + } else { + // Don't be fooled by the fact that you divide the sequence into dtmfNTones: + // the last slot will only contain a tone, not ending with silence. + // Given this, the right thing to do is to divide the sequence duration + // by dtmfNTones tones and (dtmfNTones-1) silences each sized according to the duty + // cycle: original division was: + // slot=mDuration / (dtmfNTones*(dtmfDutyCycle/MAX_DutyCycle)+(dtmfNTones-1)*(1.0-dtmfDutyCycle/MAX_DutyCycle)) + // which can be simplified in the one below. + // Then just take the part that belongs to tone or silence. + // + double slot = mDuration / ((double)dtmfNTones + (dtmfDutyCycle / 100.0) - 1); + dtmfTone = slot * (dtmfDutyCycle / 100.0); // seconds + dtmfSilence = slot * (1.0 - (dtmfDutyCycle / 100.0)); // seconds + + // Note that in the extremes we have: + // - dutyCycle=100%, this means no silence, so each tone will measure mDuration/dtmfNTones + // - dutyCycle=0%, this means no tones, so each silence slot will measure mDuration/(NTones-1) + // But we always count: + // - dtmfNTones tones + // - dtmfNTones-1 silences + } + } +} bool EffectDtmf::MakeDtmfTone(float *buffer, sampleCount len, float fs, wxChar tone, sampleCount last, sampleCount total, float amplitude) { - /* -------------------------------------------- 1209 Hz 1336 Hz 1477 Hz 1633 Hz @@ -232,7 +527,7 @@ bool EffectDtmf::MakeDtmfTone(float *buffer, sampleCount len, float fs, wxChar t // generate a fade-in of duration 1/250th of second if (last==0) { - A=(fs/FADEINOUT); + A=(fs/kFadeInOut); for(sampleCount i=0; i=0) { @@ -255,336 +550,30 @@ bool EffectDtmf::MakeDtmfTone(float *buffer, sampleCount len, float fs, wxChar t return true; } -bool EffectDtmf::GenerateTrack(WaveTrack *tmp, - const WaveTrack &track, - int ntrack) +void EffectDtmf::UpdateUI(void) { - bool bGoodResult = true; - - // all dtmf sequence durations in samples from seconds - // MJS: Note that mDuration is in seconds but will have been quantised to the units of the TTC. - // If this was 'samples' and the project rate was lower than the track rate, - // extra samples may get created as mDuration may now be > mT1 - mT0; - // However we are making our best efforts at creating what was asked for. - sampleCount nT0 = tmp->TimeToLongSamples(mT0); - sampleCount nT1 = tmp->TimeToLongSamples(mT0 + mDuration); - numSamplesSequence = nT1 - nT0; // needs to be exact number of samples selected - - //make under-estimates if anything, and then redistribute the few remaining samples - numSamplesTone = (sampleCount)floor(dtmfTone * track.GetRate()); - numSamplesSilence = (sampleCount)floor(dtmfSilence * track.GetRate()); - - // recalculate the sum, and spread the difference - due to approximations. - // Since diff should be in the order of "some" samples, a division (resulting in zero) - // is not sufficient, so we add the additional remaining samples in each tone/silence block, - // at least until available. - int diff = numSamplesSequence - (dtmfNTones*numSamplesTone) - (dtmfNTones-1)*numSamplesSilence; - while (diff > 2*dtmfNTones - 1) { // more than one per thingToBeGenerated - // in this case, both numSamplesTone and numSamplesSilence would change, so it makes sense - // to recalculate diff here, otherwise just keep the value we already have - - // should always be the case that dtmfNTones>1, as if 0, we don't even start processing, - // and with 1 there is no difference to spread (no silence slot)... - wxASSERT(dtmfNTones > 1); - numSamplesTone += (diff/(dtmfNTones)); - numSamplesSilence += (diff/(dtmfNTones-1)); - diff = numSamplesSequence - (dtmfNTones*numSamplesTone) - (dtmfNTones-1)*numSamplesSilence; - } - wxASSERT(diff >= 0); // should never be negative - - // this var will be used as extra samples distributor - int extra=0; - - sampleCount i = 0; - sampleCount j = 0; - int n=0; // pointer to string in dtmfString - sampleCount block; - bool isTone = true; - float *data = new float[tmp->GetMaxBlockSize()]; - - // for the whole dtmf sequence, we will be generating either tone or silence - // according to a bool value, and this might be done in small chunks of size - // 'block', as a single tone might sometimes be larger than the block - // tone and silence generally have different duration, thus two generation blocks - // - // Note: to overcome a 'clicking' noise introduced by the abrupt transition from/to - // silence, I added a fade in/out of 1/250th of a second (4ms). This can still be - // tweaked but gives excellent results at 44.1kHz: I haven't tried other freqs. - // A problem might be if the tone duration is very short (<10ms)... (?) - // - // One more problem is to deal with the approximations done when calculating the duration - // of both tone and silence: in some cases the final sum might not be same as the initial - // duration. So, to overcome this, we had a redistribution block up, and now we will spread - // the remaining samples in every bin in order to achieve the full duration: test case was - // to generate an 11 tone DTMF sequence, in 4 seconds, and with DutyCycle=75%: after generation - // you ended up with 3.999s or in other units: 3 seconds and 44097 samples. - // - while ((i < numSamplesSequence) && bGoodResult) { - if (isTone) - { // generate tone - // the statement takes care of extracting one sample from the diff bin and - // adding it into the tone block until depletion - extra=(diff-- > 0?1:0); - for(j=0; j < numSamplesTone+extra && bGoodResult; j+=block) { - block = tmp->GetBestBlockSize(j); - if (block > (numSamplesTone+extra - j)) - block = numSamplesTone+extra - j; - - // generate the tone and append - MakeDtmfTone(data, block, track.GetRate(), dtmfString[n], j, numSamplesTone, dtmfAmplitude); - tmp->Append((samplePtr)data, floatSample, block); - //Update the Progress meter - if (TrackProgress(ntrack, (double)(i+j) / numSamplesSequence)) - bGoodResult = false; - } - i += numSamplesTone; - n++; - if(n>=dtmfNTones)break; - } - else - { // generate silence - // the statement takes care of extracting one sample from the diff bin and - // adding it into the silence block until depletion - extra=(diff-- > 0?1:0); - for(j=0; j < numSamplesSilence+extra && bGoodResult; j+=block) { - block = tmp->GetBestBlockSize(j); - if (block > (numSamplesSilence+extra - j)) - block = numSamplesSilence+extra - j; - - // generate silence and append - memset(data, 0, sizeof(float)*block); - tmp->Append((samplePtr)data, floatSample, block); - //Update the Progress meter - if (TrackProgress(ntrack, (double)(i+j) / numSamplesSequence)) - bGoodResult = false; - } - i += numSamplesSilence; - } - // flip flag - isTone=!isTone; - - } // finished the whole dtmf sequence - wxLogDebug(wxT("Extra %d diff: %d"), extra, diff); - delete[] data; - return bGoodResult; -} - -void EffectDtmf::Success() -{ - /* save last used values - save duration unless value was got from selection, so we save only - when user explicitely setup a value - */ - if (mT1 == mT0) - gPrefs->Write(wxT("/Effects/DtmfGen/SequenceDuration"), mDuration); - - gPrefs->Write(wxT("/Effects/DtmfGen/String"), dtmfString); - gPrefs->Write(wxT("/Effects/DtmfGen/DutyCycle"), dtmfDutyCycle); - gPrefs->Write(wxT("/Effects/DtmfGen/Amplitude"), dtmfAmplitude); - gPrefs->Flush(); -} - -//---------------------------------------------------------------------------- -// DtmfDialog -//---------------------------------------------------------------------------- - -const static wxChar *dtmfSymbols[] = -{ - wxT("0"), wxT("1"), wxT("2"), wxT("3"), - wxT("4"), wxT("5"), wxT("6"), wxT("7"), - wxT("8"), wxT("9"), wxT("*"), wxT("#"), - wxT("A"), wxT("B"), wxT("C"), wxT("D"), - wxT("a"), wxT("b"), wxT("c"), wxT("d"), - wxT("e"), wxT("f"), wxT("g"), wxT("h"), - wxT("i"), wxT("j"), wxT("k"), wxT("l"), - wxT("m"), wxT("n"), wxT("o"), wxT("p"), - wxT("q"), wxT("r"), wxT("s"), wxT("t"), - wxT("u"), wxT("v"), wxT("w"), wxT("x"), - wxT("y"), wxT("z") -}; - -#define ID_DTMF_DUTYCYCLE_SLIDER 10001 -#define ID_DTMF_STRING_TEXT 10002 -#define ID_DTMF_DURATION_TEXT 10003 -#define ID_DTMF_DUTYCYCLE_TEXT 10004 -#define ID_DTMF_TONELEN_TEXT 10005 -#define ID_DTMF_SILENCE_TEXT 10006 - - -BEGIN_EVENT_TABLE(DtmfDialog, EffectDialog) - EVT_TEXT(ID_DTMF_STRING_TEXT, DtmfDialog::OnDtmfStringText) - EVT_TEXT(ID_DTMF_DURATION_TEXT, DtmfDialog::OnDtmfDurationText) - EVT_COMMAND(wxID_ANY, EVT_TIMETEXTCTRL_UPDATED, DtmfDialog::OnTimeCtrlUpdate) - EVT_SLIDER(ID_DTMF_DUTYCYCLE_SLIDER, DtmfDialog::OnDutyCycleSlider) -END_EVENT_TABLE() - - -DtmfDialog::DtmfDialog(EffectDtmf * effect, wxWindow * parent, const wxString & title) -: EffectDialog(parent, title, INSERT_EFFECT), - mEffect(effect) -{ - /* - wxString dString; // dtmf tone string - int dNTones; // total number of tones to generate - double dTone; // duration of a single tone - double dSilence; // duration of silence between tones - double dDuration; // duration of the whole dtmf tone sequence - */ - dTone = 0; - dSilence = 0; - dDuration = 0; - - mDtmfDurationT = NULL; -} - -void DtmfDialog::PopulateOrExchange( ShuttleGui & S ) -{ - wxTextValidator vldDtmf(wxFILTER_INCLUDE_CHAR_LIST); - vldDtmf.SetIncludes(wxArrayString(42, dtmfSymbols)); - - S.AddSpace(0, 5); - S.StartMultiColumn(2, wxEXPAND); - { - mDtmfStringT = S.Id(ID_DTMF_STRING_TEXT).AddTextBox(_("DTMF sequence:"), wxT(""), 10); - mDtmfStringT->SetValidator(vldDtmf); - - // The added colon to improve visual consistency was placed outside - // the translatable strings to avoid breaking translations close to 2.0. - // TODO: Make colon part of the translatable string after 2.0. - S.TieNumericTextBox(_("Amplitude (0-1)") + wxString(wxT(":")), dAmplitude, 10); - - S.AddPrompt(_("Duration:")); - if (mDtmfDurationT == NULL) - { - mDtmfDurationT = new - NumericTextCtrl(NumericConverter::TIME, this, - ID_DTMF_DURATION_TEXT, - /* use this instead of "seconds" because if a selection is passed to the - * effect, I want it (dDuration) to be used as the duration, and with - * "seconds" this does not always work properly. For example, it rounds - * down to zero... */ - dIsSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), - dDuration, - mEffect->mProjectRate, - wxDefaultPosition, - wxDefaultSize, - true); - mDtmfDurationT->SetName(_("Duration")); - mDtmfDurationT->EnableMenu(); - } - S.AddWindow(mDtmfDurationT); - - S.AddFixedText(_("Tone/silence ratio:"), false); - S.SetStyle(wxSL_HORIZONTAL | wxEXPAND); - mDtmfDutyS = S.Id(ID_DTMF_DUTYCYCLE_SLIDER).AddSlider(wxT(""), (int)dDutyCycle, DUTY_MAX, DUTY_MIN); - - S.SetSizeHints(-1,-1); - } - S.EndMultiColumn(); - - S.StartMultiColumn(2, wxCENTER); - { - S.AddFixedText(_("Duty cycle:"), false); - mDtmfDutyT = S.Id(ID_DTMF_DUTYCYCLE_TEXT).AddVariableText(wxString::Format(wxT("%.1f %%"), (float) dDutyCycle/DUTY_SCALE), false); - S.AddFixedText(_("Tone duration:"), false); - mDtmfSilenceT = S.Id(ID_DTMF_TONELEN_TEXT).AddVariableText(wxString::Format(wxString(wxT("%d ")) + _("ms"), (int) dTone * 1000), false); - S.AddFixedText(_("Silence duration:"), false); - mDtmfToneT = S.Id(ID_DTMF_SILENCE_TEXT).AddVariableText(wxString::Format(wxString(wxT("%d ")) + _("ms"), (int) dSilence * 1000), false); - } - S.EndMultiColumn(); -} - -bool DtmfDialog::TransferDataToWindow() - { - mDtmfDutyS->SetValue((int)dDutyCycle); - mDtmfDurationT->SetValue(dDuration); - mDtmfStringT->SetValue(dString); - - return true; -} - -bool DtmfDialog::TransferDataFromWindow() -{ - EffectDialog::TransferDataFromWindow(); - dAmplitude = TrapDouble(dAmplitude, AMP_MIN, AMP_MAX); - // recalculate to make sure all values are up-to-date. This is especially - // important if the user did not change any values in the dialog - Recalculate(); - - return true; -} - -/* - * - */ - -void DtmfDialog::Recalculate(void) { - - // remember that dDutyCycle is in range (0-1000) - double slot; - - dString = mDtmfStringT->GetValue(); - dDuration = mDtmfDurationT->GetValue(); - - dNTones = wxStrlen(dString); - dDutyCycle = TrapLong(mDtmfDutyS->GetValue(), DUTY_MIN, DUTY_MAX); - - if (dNTones==0) { - // no tones, all zero: don't do anything - // this should take care of the case where user got an empty - // dtmf sequence into the generator: track won't be generated - dTone = 0; - dDuration = 0; - dSilence = dDuration; - } else - if (dNTones==1) { - // single tone, as long as the sequence - dSilence = 0; - dTone = dDuration; - - } else { - // Don't be fooled by the fact that you divide the sequence into dNTones: - // the last slot will only contain a tone, not ending with silence. - // Given this, the right thing to do is to divide the sequence duration - // by dNTones tones and (dNTones-1) silences each sized according to the duty - // cycle: original division was: - // slot=dDuration / (dNTones*(dDutyCycle/DUTY_MAX)+(dNTones-1)*(1.0-dDutyCycle/DUTY_MAX)) - // which can be simplified in the one below. - // Then just take the part that belongs to tone or silence. - // - slot=dDuration/((double)dNTones+(dDutyCycle/DUTY_MAX)-1); - dTone = slot * (dDutyCycle/DUTY_MAX); // seconds - dSilence = slot * (1.0 - (dDutyCycle/DUTY_MAX)); // seconds - - // Note that in the extremes we have: - // - dutyCycle=100%, this means no silence, so each tone will measure dDuration/dNTones - // - dutyCycle=0%, this means no tones, so each silence slot will measure dDuration/(NTones-1) - // But we always count: - // - dNTones tones - // - dNTones-1 silences - } - - mDtmfDutyT->SetLabel(wxString::Format(wxT("%.1f %%"), (float)dDutyCycle/DUTY_SCALE)); + mDtmfDutyT->SetLabel(wxString::Format(wxT("%.1f %%"), dtmfDutyCycle)); mDtmfDutyT->SetName(mDtmfDutyT->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - mDtmfSilenceT->SetLabel(wxString::Format(wxString(wxT("%d ")) + _("ms"), (int) (dTone * 1000))); + + mDtmfSilenceT->SetLabel(wxString::Format(wxString(wxT("%.0f ")) + _("ms"), dtmfTone * 1000.0)); mDtmfSilenceT->SetName(mDtmfSilenceT->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - mDtmfToneT->SetLabel(wxString::Format(wxString(wxT("%d ")) + _("ms"), (int) (dSilence * 1000))); + + mDtmfToneT->SetLabel(wxString::Format(wxString(wxT("%0.f ")) + _("ms"), dtmfSilence * 1000.0)); mDtmfToneT->SetName(mDtmfToneT->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) } -void DtmfDialog::OnDutyCycleSlider(wxCommandEvent & WXUNUSED(event)) { +void EffectDtmf::OnSlider(wxCommandEvent & evt) +{ + dtmfDutyCycle = (double) evt.GetInt() / SCL_DutyCycle; + mUIParent->TransferDataFromWindow(); Recalculate(); + UpdateUI(); } - -void DtmfDialog::OnDtmfStringText(wxCommandEvent & WXUNUSED(event)) { +void EffectDtmf::OnText(wxCommandEvent & WXUNUSED(evt)) +{ + mDuration = mDtmfDurationT->GetValue(); + mUIParent->TransferDataFromWindow(); Recalculate(); -} - -void DtmfDialog::OnDtmfDurationText(wxCommandEvent & WXUNUSED(event)) { - Recalculate(); -} - -void DtmfDialog::OnTimeCtrlUpdate(wxCommandEvent & WXUNUSED(event)) { - this->Fit(); + UpdateUI(); } diff --git a/src/effects/DtmfGen.h b/src/effects/DtmfGen.h index cd99082d4..4436ea5e0 100644 --- a/src/effects/DtmfGen.h +++ b/src/effects/DtmfGen.h @@ -7,134 +7,92 @@ Salvo Ventura Dec 2006 - An effect for the "Generator" menu to generate DTMF tones + An effect that generates DTMF tones **********************************************************************/ #ifndef __AUDACITY_EFFECT_DTMF__ #define __AUDACITY_EFFECT_DTMF__ -#include -#include -#include -#include +#include +#include +#include #include -#include #include "../ShuttleGui.h" -#include "../WaveTrack.h" +#include "../widgets/NumericTextCtrl.h" -#include "Generator.h" +#include "Effect.h" -class NumericTextCtrl; +#define DTMFTONES_PLUGIN_SYMBOL wxTRANSLATE("DTMF Tones") -#define __UNINITIALIZED__ (-1) +class EffectDtmf : public Effect +{ +public: + EffectDtmf(); + virtual ~EffectDtmf(); -class EffectDtmf : public Generator { + // IdentInterface implementation - public: - EffectDtmf() : mIsSelection(false) { - SetEffectFlags(BUILTIN_EFFECT | INSERT_EFFECT); - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("DTMF Tones...")); - } + // EffectIdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#GeneratorPlugin")); - return result; - } + virtual EffectType GetType(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("DTMF Tones")); - } + // EffectClientInterface implementation - // Return true if the effect supports processing via batch chains. - virtual bool SupportsChains() { - return false; - } + virtual int GetAudioOutCount(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); - virtual wxString GetEffectDescription() { - return wxString::Format(_("Applied effect: Generate DTMF tones, %.6lf seconds"), mDuration); - } + // Effect implementation - virtual wxString GetEffectAction() { - return wxString(_("Generating DTMF tones")); - } + virtual bool Startup(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataFromWindow(); + virtual bool TransferDataToWindow(); - virtual bool Init(); - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); +private: + // EffectDtmf implementation - private: - sampleCount numSamplesSequence, numSamplesTone, numSamplesSilence; + bool MakeDtmfTone(float *buffer, sampleCount len, float fs, + wxChar tone, sampleCount last, + sampleCount total, float amplitude); + void Recalculate(); - wxString dtmfString; // dtmf tone string - int dtmfNTones; // total number of tones to generate - double dtmfTone; // duration of a single tone in ms - double dtmfSilence; // duration of silence between tones in ms - double dtmfDutyCycle; // ratio of dtmfTone/(dtmfTone+dtmfSilence) - double dtmfAmplitude; // amplitude of dtmf tone sequence, restricted to (0-1) - bool mIsSelection; + void UpdateUI(); - protected: - virtual bool MakeDtmfTone(float *buffer, sampleCount len, float fs, - wxChar tone, sampleCount last, - sampleCount total, float amplitude); - bool GenerateTrack(WaveTrack *tmp, const WaveTrack &track, int ntrack); - void Success(); + void OnText(wxCommandEvent & evt); + void OnSlider(wxCommandEvent & evt); - // friendship ... - friend class DtmfDialog; +private: + sampleCount numSamplesSequence; // total number of samples to generate + sampleCount numSamplesTone; // number of samples in a tone block + sampleCount numSamplesSilence; // number of samples in a silence block + sampleCount diff; // number of extra samples to redistribute + sampleCount numRemaining; // number of samples left to produce in the current block + sampleCount curTonePos; // position in tone to start the wave + bool isTone; // true if block is tone, otherwise silence + int curSeqPos; // index into dtmf tone string -}; + wxString dtmfString; // dtmf tone string + int dtmfNTones; // total number of tones to generate + double dtmfTone; // duration of a single tone in ms + double dtmfSilence; // duration of silence between tones in ms + double dtmfDutyCycle; // ratio of dtmfTone/(dtmfTone+dtmfSilence) + double dtmfAmplitude; // amplitude of dtmf tone sequence, restricted to (0-1) -//---------------------------------------------------------------------------- -// DtmfDialog -//---------------------------------------------------------------------------- - -// Declare window functions - -class DtmfDialog:public EffectDialog { - public: - // constructors and destructors - DtmfDialog(EffectDtmf * effect, wxWindow * parent, const wxString & title); - - // method declarations - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - - private: - void OnDtmfStringText(wxCommandEvent & event); - void OnDtmfDurationText(wxCommandEvent & event); - void OnDutyCycleSlider(wxCommandEvent & event); - void OnTimeCtrlUpdate(wxCommandEvent & event); - void Recalculate(void); - - private: - EffectDtmf *mEffect; wxSlider *mDtmfDutyS; - wxTextCtrl *mDtmfStringT; NumericTextCtrl *mDtmfDurationT; wxStaticText *mDtmfToneT; wxStaticText *mDtmfSilenceT; wxStaticText *mDtmfDutyT; - DECLARE_EVENT_TABLE() - - public: - wxString dString; // dtmf tone string - int dNTones; // total number of tones to generate - double dTone; // duration of a single tone - double dSilence; // duration of silence between tones - double dDuration; // duration of the whole dtmf tone sequence - double dDutyCycle; // ratio of dTone/(dTone+dSilence) - double dAmplitude; // amplitude of dtmf tone sequence, restricted to (0-1) - bool dIsSelection; // true if duration comes from selection - + DECLARE_EVENT_TABLE(); }; #endif diff --git a/src/effects/Echo.cpp b/src/effects/Echo.cpp index 77c7a7c77..c6a2eb9b1 100644 --- a/src/effects/Echo.cpp +++ b/src/effects/Echo.cpp @@ -19,246 +19,150 @@ *//*******************************************************************/ - #include "../Audacity.h" -#include -#include -#include -#include -#include -#include -#include +#include -#include #include -#include + +#include "../widgets/valnum.h" #include "Echo.h" -#include "../WaveTrack.h" + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( Delay, float, wxTRANSLATE("Delay"), 1.0, 0.0, FLT_MAX, 1 ); +Param( Decay, float, wxTRANSLATE("Decay"), 0.5, 1.0, 1.0, 1 ); EffectEcho::EffectEcho() { - delay = float(1.0); - decay = float(0.5); + delay = DEF_Delay; + decay = DEF_Decay; } -wxString EffectEcho::GetEffectDescription() { - // Note: This is useful only after values have been set. - return wxString::Format(_("Applied effect: %s delay = %f seconds, decay factor = %f"), - this->GetEffectName().c_str(), delay, decay); -} - -bool EffectEcho::PromptUser() +EffectEcho::~EffectEcho() { - EchoDialog dlog(this, mParent); - dlog.delay = delay; - dlog.decay = decay; - dlog.CentreOnParent(); - dlog.ShowModal(); +} - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; +// IdentInterface implementation - delay = dlog.delay; - decay = dlog.decay; +wxString EffectEcho::GetSymbol() +{ + return ECHO_PLUGIN_SYMBOL; +} + +wxString EffectEcho::GetDescription() +{ + return wxTRANSLATE("Repeats the selected audio again and again"); +} + +// EffectIdentInterface implementation + +EffectType EffectEcho::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +int EffectEcho::GetAudioInCount() +{ + return 1; +} + +int EffectEcho::GetAudioOutCount() +{ + return 1; +} + +bool EffectEcho::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) +{ + histPos = 0; + histLen = (sampleCount) (mSampleRate * delay); + history = new float[histLen]; + memset(history, 0, sizeof(float) * histLen); + + return history != NULL; +} + +bool EffectEcho::ProcessFinalize() +{ + delete [] history; return true; } -bool EffectEcho::TransferParameters( Shuttle & shuttle ) +sampleCount EffectEcho::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) { - shuttle.TransferFloat(wxT("Delay"),delay,1.0); - shuttle.TransferFloat(wxT("Decay"),decay,0.5); - return true; -} + float *ibuf = inBlock[0]; + float *obuf = outBlock[0]; -bool EffectEcho::Process() -{ - this->CopyInputTracks(); // Set up mOutputTracks. - bool bGoodResult = true; - - SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks); - WaveTrack *track = (WaveTrack *) iter.First(); - int count = 0; - while (track) { - double trackStart = track->GetStartTime(); - double trackEnd = track->GetEndTime(); - double t0 = mT0 < trackStart? trackStart: mT0; - double t1 = mT1 > trackEnd? trackEnd: mT1; - - if (t1 > t0) { - sampleCount start = track->TimeToLongSamples(t0); - sampleCount end = track->TimeToLongSamples(t1); - sampleCount len = (sampleCount)(end - start); - - if (!ProcessOne(count, track, start, len)) - { - bGoodResult = false; - break; - } + for (sampleCount i = 0; i < blockLen; i++, histPos++) + { + if (histPos == histLen) + { + histPos = 0; } - - track = (WaveTrack *) iter.Next(); - count++; + history[histPos] = obuf[i] = ibuf[i] + history[histPos] * decay; } - this->ReplaceProcessedTracks(bGoodResult); - return bGoodResult; + return blockLen; } -bool EffectEcho::ProcessOne(int count, WaveTrack * track, - sampleCount start, sampleCount len) +bool EffectEcho::GetAutomationParameters(EffectAutomationParameters & parms) { - sampleCount s = 0; - sampleCount blockSize = (sampleCount) (track->GetRate() * delay); - - //do nothing if the delay is less than 1 sample or greater than - //the length of the selection - if (blockSize < 1 || blockSize > len) - return true; - - float *buffer0 = new float[blockSize]; - float *buffer1 = new float[blockSize]; - - float *ptr0 = buffer0; - float *ptr1 = buffer1; - - bool first = true; - - while (s < len) { - sampleCount block = blockSize; - if (s + block > len) - block = len - s; - - track->Get((samplePtr)ptr0, floatSample, start + s, block); - if (!first) { - for (sampleCount i = 0; i < block; i++) - ptr0[i] += ptr1[i] * decay; - track->Set((samplePtr)ptr0, floatSample, start + s, block); - } - - float *ptrtemp = ptr0; - ptr0 = ptr1; - ptr1 = ptrtemp; - - first = false; - - s += block; - - if (TrackProgress(count, s / (double) len)) { - delete[]buffer0; - delete[]buffer1; - - return false; - } - } - - delete[]buffer0; - delete[]buffer1; + parms.WriteFloat(KEY_Delay, delay); + parms.WriteFloat(KEY_Decay, decay); return true; } -//---------------------------------------------------------------------------- -// EchoDialog -//---------------------------------------------------------------------------- - -// event table for EchoDialog - -BEGIN_EVENT_TABLE(EchoDialog, EffectDialog) - EVT_BUTTON(ID_EFFECT_PREVIEW, EchoDialog::OnPreview) -END_EVENT_TABLE() - -EchoDialog::EchoDialog(EffectEcho * effect, wxWindow * parent) -: EffectDialog(parent, _("Echo")) +bool EffectEcho::SetAutomationParameters(EffectAutomationParameters & parms) { - m_bLoopDetect = false; - m_pEffect = effect; + ReadAndVerifyFloat(Delay); + ReadAndVerifyFloat(Decay); - // NULL out these control members because there are some cases where the - // event table handlers get called during this method, and those handlers that - // can cause trouble check for NULL. - m_pTextCtrl_Delay = NULL; - m_pTextCtrl_Decay = NULL; + delay = Delay; + decay = Decay; - // effect parameters - delay = float(1.0); - decay = float(0.5); - - // Initialize dialog - Init(); + return true; } -void EchoDialog::PopulateOrExchange(ShuttleGui & S) +void EffectEcho::PopulateOrExchange(ShuttleGui & S) { S.AddSpace(0, 5); S.StartMultiColumn(2, wxALIGN_CENTER); { - m_pTextCtrl_Delay = S.AddTextBox(_("Delay time (seconds):"), - wxT("1.0"), - 10); - m_pTextCtrl_Delay->SetValidator(wxTextValidator(wxFILTER_NUMERIC)); + FloatingPointValidator vldDelay(3, &delay, NUM_VAL_NO_TRAILING_ZEROES); + vldDelay.SetRange(MIN_Delay, MAX_Delay); + S.AddTextBox(_("Delay time (seconds):"), wxT(""), 10)->SetValidator(vldDelay); - m_pTextCtrl_Decay = S.AddTextBox(_("Decay factor:"), - wxT("0.5"), - 10); - m_pTextCtrl_Decay->SetValidator(wxTextValidator(wxFILTER_NUMERIC)); + FloatingPointValidator vldDecay(3, &decay, NUM_VAL_NO_TRAILING_ZEROES); + vldDecay.SetRange(MIN_Decay, MAX_Decay); + S.AddTextBox(_("Decay factor:"), wxT(""), 10)->SetValidator(vldDecay); } S.EndMultiColumn(); } -bool EchoDialog::TransferDataToWindow() +bool EffectEcho::TransferDataToWindow() { - m_bLoopDetect = true; - - wxString str; - if (m_pTextCtrl_Delay) { - str.Printf(wxT("%g"), delay); - m_pTextCtrl_Delay->SetValue(str); - } - if (m_pTextCtrl_Decay) { - str.Printf(wxT("%g"), decay); - m_pTextCtrl_Decay->SetValue(str); + if (!mUIParent->TransferDataToWindow()) + { + return false; } - m_bLoopDetect = false; return true; } -bool EchoDialog::TransferDataFromWindow() +bool EffectEcho::TransferDataFromWindow() { - double newValue; - wxString str; - if (m_pTextCtrl_Delay) { - str = m_pTextCtrl_Delay->GetValue(); - str.ToDouble(&newValue); - delay = (float)(newValue); - } - if (m_pTextCtrl_Decay) { - str = m_pTextCtrl_Decay->GetValue(); - str.ToDouble(&newValue); - decay = (float)(newValue); + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; } + return true; } -// handler implementations for EchoDialog - -void EchoDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - - // Save & restore parameters around Preview, because we didn't do OK. - float oldDelay = m_pEffect->delay; - float oldDecay = m_pEffect->decay; - - m_pEffect->delay = delay; - m_pEffect->decay = decay; - - m_pEffect->Preview(); - - m_pEffect->delay = oldDelay; - m_pEffect->decay = oldDecay; -} diff --git a/src/effects/Echo.h b/src/effects/Echo.h index 48d2dd922..267c58a61 100644 --- a/src/effects/Echo.h +++ b/src/effects/Echo.h @@ -12,91 +12,55 @@ #ifndef __AUDACITY_EFFECT_ECHO__ #define __AUDACITY_EFFECT_ECHO__ -class wxString; +#include +#include +#include -#include - -#include +#include "../ShuttleGui.h" #include "Effect.h" -class wxStaticText; - -class WaveTrack; - -class EffectEcho:public Effect { - - public: +#define ECHO_PLUGIN_SYMBOL wxTRANSLATE("Echo") +class EffectEcho : public Effect +{ +public: EffectEcho(); + virtual ~EffectEcho(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Echo...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#DelayPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Echo")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Performing Echo")); - } + virtual EffectType GetType(); - // Useful only after PromptUser values have been set. - virtual wxString GetEffectDescription(); + // EffectClientInterface implementation - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); + virtual int GetAudioInCount(); + virtual int GetAudioOutCount(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); + virtual bool ProcessFinalize(); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); - virtual bool Process(); + // Effect implementation + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); - private: - bool ProcessOne(int count, WaveTrack * t, - sampleCount start, sampleCount len); +private: + // EffectEcho implementation - float delay; - float decay; - -friend class EchoDialog; -}; - -//---------------------------------------------------------------------------- -// EchoDialog -//---------------------------------------------------------------------------- - -class EchoDialog:public EffectDialog { - public: - EchoDialog(EffectEcho * effect, wxWindow * parent); - - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - - private: - // handlers - void OnPreview( wxCommandEvent &event ); - - private: - bool m_bLoopDetect; - EffectEcho * m_pEffect; - - // controls - wxTextCtrl * m_pTextCtrl_Delay; - wxTextCtrl * m_pTextCtrl_Decay; - - public: - // effect parameters - float delay; - float decay; - - private: - DECLARE_EVENT_TABLE() +private: + double delay; + double decay; + float *history; + sampleCount histPos; + sampleCount histLen; }; #endif // __AUDACITY_EFFECT_ECHO__ diff --git a/src/effects/Effect.cpp b/src/effects/Effect.cpp index a692b75dc..804455dfa 100644 --- a/src/effects/Effect.cpp +++ b/src/effects/Effect.cpp @@ -46,34 +46,31 @@ greater use in future. #include "../ondemand/ODManager.h" #include "TimeWarper.h" -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) && defined(__WXMAC__) +#if defined(__WXMAC__) #include #endif +static const int kDummyID = 30000; +static const int kSaveAsID = 30001; +static const int kImportID = 30002; +static const int kExportID = 30003; +static const int kDefaultsID = 30004; +static const int kOptionsID = 30005; +static const int kUserPresetsDummyID = 30006; +static const int kDeletePresetDummyID = 30007; +static const int kMenuID = 30100; +static const int kEnableID = 30101; +static const int kPlayID = 30102; +static const int kRewindID = 30103; +static const int kFFwdID = 30104; +static const int kPlaybackID = 30105; +static const int kCaptureID = 30106; +static const int kUserPresetsID = 31000; +static const int kDeletePresetID = 32000; +static const int kFactoryPresetsID = 33000; + WX_DECLARE_VOIDPTR_HASH_MAP( bool, t2bHash ); -// -// public static methods -// - -double Effect::sDefaultGenerateLen = 30.0; - - -wxString Effect::StripAmpersand(const wxString& str) -{ - wxString strippedStr = str; - strippedStr.Replace(wxT("&"), wxT("")); - // ! is used for hiding effects, and should not affect sort order. - strippedStr.Replace(wxT("!"), wxT("")); - return strippedStr; -} - - -// -// public methods -// - -// Legacy (or full blown effect) Effect::Effect() { mParent = NULL; @@ -94,9 +91,8 @@ Effect::Effect() mRealtimeSuspendCount = 1; // Effects are initially suspended mRealtimeSuspendLock.Leave(); - // Can change effect flags later (this is the new way) - // OR using the old way, over-ride GetEffectFlags(). - mFlags = BUILTIN_EFFECT | PROCESS_EFFECT | ADVANCED_EFFECT; + mUIParent = NULL; + mUIDialog = NULL; mNumAudioIn = 0; mNumAudioOut = 0; @@ -109,6 +105,9 @@ Effect::Effect() mBufferSize = 0; mBlockSize = 0; mNumChannels = 0; + + AudacityProject *p = GetActiveProject(); + mProjectRate = p ? p->GetRate() : 44100; } Effect::~Effect() @@ -129,28 +128,6 @@ EffectType Effect::GetType() return mClient->GetType(); } - if (mFlags & HIDDEN_EFFECT) - { - return EffectTypeNone; - } - - if (mFlags & INSERT_EFFECT) - { - return EffectTypeGenerate; - } - - if (mFlags & PROCESS_EFFECT) - { - return EffectTypeProcess; - } - - if (mFlags & ANALYZE_EFFECT) - { - return EffectTypeAnalyze; - } - - wxASSERT( true ); - return EffectTypeNone; } @@ -161,7 +138,7 @@ wxString Effect::GetPath() return mClient->GetPath(); } - return wxEmptyString; + return BUILTIN_EFFECT_PREFIX + GetSymbol(); } wxString Effect::GetSymbol() @@ -171,7 +148,7 @@ wxString Effect::GetSymbol() return mClient->GetSymbol(); } - return GetEffectIdentifier(); + return wxEmptyString; } wxString Effect::GetName() @@ -181,7 +158,7 @@ wxString Effect::GetName() return mClient->GetName(); } - return GetEffectName(); + return GetSymbol(); } wxString Effect::GetVendor() @@ -201,7 +178,7 @@ wxString Effect::GetVersion() return mClient->GetVersion(); } - return wxT("Various"); + return AUDACITY_VERSION_STRING; } wxString Effect::GetDescription() @@ -211,7 +188,7 @@ wxString Effect::GetDescription() return mClient->GetDescription(); } - return GetEffectIdentifier(); + return wxEmptyString; } wxString Effect::GetFamily() @@ -231,7 +208,7 @@ bool Effect::IsInteractive() return mClient->IsInteractive(); } - return GetEffectName().EndsWith(wxT("...")); + return true; } bool Effect::IsDefault() @@ -241,7 +218,7 @@ bool Effect::IsDefault() return mClient->IsDefault(); } - return (mFlags & BUILTIN_EFFECT) != 0; + return true; } bool Effect::IsLegacy() @@ -254,6 +231,16 @@ bool Effect::IsLegacy() return true; } +bool Effect::SupportsRealtime() +{ + if (mClient) + { + return mClient->SupportsRealtime(); + } + + return false; +} + bool Effect::SupportsAutomation() { if (mClient) @@ -261,11 +248,404 @@ bool Effect::SupportsAutomation() return mClient->SupportsAutomation(); } - return SupportsChains(); + return true; +} + +// EffectClientInterface implementation + +bool Effect::SetHost(EffectHostInterface *host) +{ + if (mClient) + { + return mClient->SetHost(host); + } + + return true; +} + +int Effect::GetAudioInCount() +{ + if (mClient) + { + return mClient->GetAudioInCount(); + } + + return 0; +} + +int Effect::GetAudioOutCount() +{ + if (mClient) + { + return mClient->GetAudioOutCount(); + } + + return 0; +} + +int Effect::GetMidiInCount() +{ + if (mClient) + { + return mClient->GetMidiInCount(); + } + + return 0; +} + +int Effect::GetMidiOutCount() +{ + if (mClient) + { + return mClient->GetMidiOutCount(); + } + + return 0; +} + +void Effect::SetSampleRate(sampleCount rate) +{ + if (mClient) + { + mClient->SetSampleRate(rate); + } + + mSampleRate = rate; +} + +sampleCount Effect::SetBlockSize(sampleCount maxBlockSize) +{ + if (mClient) + { + return mClient->SetBlockSize(maxBlockSize); + } + + mBlockSize = maxBlockSize; + + return mBlockSize; +} + +sampleCount Effect::GetLatency() +{ + if (mClient) + { + return mClient->GetLatency(); + } + + return 0; +} + +sampleCount Effect::GetTailSize() +{ + if (mClient) + { + return mClient->GetTailSize(); + } + + return 0; +} + +bool Effect::IsReady() +{ + if (mClient) + { + return mClient->IsReady(); + } + + return true; +} + +bool Effect::ProcessInitialize(sampleCount totalLen, ChannelNames chanMap) +{ + if (mClient) + { + return mClient->ProcessInitialize(totalLen, chanMap); + } + + return true; +} + +bool Effect::ProcessFinalize() +{ + if (mClient) + { + return mClient->ProcessFinalize(); + } + + return true; +} + +sampleCount Effect::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) +{ + if (mClient) + { + return mClient->ProcessBlock(inBlock, outBlock, blockLen); + } + + return 0; +} + +bool Effect::RealtimeInitialize() +{ + if (mClient) + { + mBlockSize = mClient->SetBlockSize(512); + return mClient->RealtimeInitialize(); + } + + return false; +} + +bool Effect::RealtimeAddProcessor(int numChannels, float sampleRate) +{ + if (mClient) + { + return mClient->RealtimeAddProcessor(numChannels, sampleRate); + } + + return true; +} + +bool Effect::RealtimeFinalize() +{ + if (mClient) + { + return mClient->RealtimeFinalize(); + } + + return false; +} + +bool Effect::RealtimeSuspend() +{ + if (mClient) + { + if (mClient->RealtimeSuspend()) + { + mRealtimeSuspendLock.Enter(); + mRealtimeSuspendCount++; + mRealtimeSuspendLock.Leave(); + return true; + } + } + + return false; +} + +bool Effect::RealtimeResume() +{ + if (mClient) + { + if (mClient->RealtimeResume()) + { + mRealtimeSuspendLock.Enter(); + mRealtimeSuspendCount--; + mRealtimeSuspendLock.Leave(); + return true; + } + } + + return false; +} + +bool Effect::RealtimeProcessStart() +{ + if (mClient) + { + return mClient->RealtimeProcessStart(); + } + + return true; +} + +sampleCount Effect::RealtimeProcess(int group, + float **inbuf, + float **outbuf, + sampleCount numSamples) +{ + if (mClient) + { + return mClient->RealtimeProcess(group, inbuf, outbuf, numSamples); + } + + return 0; +} + +bool Effect::RealtimeProcessEnd() +{ + if (mClient) + { + return mClient->RealtimeProcessEnd(); + } + + return true; +} + +bool Effect::ShowInterface(wxWindow *parent, bool forceModal) +{ + if (mClient) + { + return mClient->ShowInterface(parent, forceModal); + } + + mUIDialog = CreateUI(parent, this); + if (!mUIDialog) + { + return false; + } + + mUIDialog->Layout(); + mUIDialog->Fit(); + mUIDialog->SetMinSize(mUIDialog->GetSize()); + + bool res = mUIDialog->ShowModal() != 0; + + mUIDialog = NULL; + + return res; +} + +bool Effect::GetAutomationParameters(EffectAutomationParameters & parms) +{ + if (mClient) + { + return mClient->GetAutomationParameters(parms); + } + + return true; +} + +bool Effect::SetAutomationParameters(EffectAutomationParameters & parms) +{ + if (mClient) + { + return mClient->SetAutomationParameters(parms); + } + + return true; +} + +bool Effect::LoadUserPreset(const wxString & name) +{ + if (!SupportsAutomation()) + { + return true; + } + + wxString parms; + if (!GetPrivateConfig(name, wxT("Parameters"), parms)) + { + return false; + } + + return SetAutomationParameters(parms); +} + +bool Effect::SaveUserPreset(const wxString & name) +{ + if (!SupportsAutomation()) + { + return true; + } + + wxString parms; + if (!GetAutomationParameters(parms)) + { + return false; + } + + return SetPrivateConfig(name, wxT("Parameters"), parms); +} + +wxArrayString Effect::GetFactoryPresets() +{ + return wxArrayString(); +} + +bool Effect::LoadFactoryPreset(int WXUNUSED(id)) +{ + return true; +} + +bool Effect::LoadFactoryDefaults() +{ + return LoadUserPreset(GetFactoryDefaultsGroup()); +} + +// EffectUIClientInterface implementation + +void Effect::SetHostUI(EffectUIHostInterface *WXUNUSED(host)) +{ +} + +bool Effect::PopulateUI(wxWindow *parent) +{ + mUIParent = parent; + mUIParent->PushEventHandler(this); + + LoadUserPreset(GetCurrentSettingsGroup()); + + ShuttleGui S(mUIParent, eIsCreating); + PopulateOrExchange(S); + + mUIParent->SetMinSize(mUIParent->GetSizer()->GetMinSize()); + + return true; +} + +bool Effect::IsGraphicalUI() +{ + return false; +} + +bool Effect::ValidateUI() +{ + return mUIParent->Validate(); +} + +bool Effect::HideUI() +{ + return true; +} + +bool Effect::CloseUI() +{ + mUIParent->RemoveEventHandler(this); + + mUIParent = NULL; + + return true; +} + +bool Effect::CanExportPresets() +{ + return false; +} + +void Effect::ExportPresets() +{ +} + +void Effect::ImportPresets() +{ +} + +bool Effect::HasOptions() +{ + return false; +} + +void Effect::ShowOptions() +{ } // EffectHostInterface implementation +double Effect::GetDefaultDuration() +{ + return 30.0; +} + double Effect::GetDuration() { if (mT1 > mT0) @@ -273,9 +653,9 @@ double Effect::GetDuration() return mT1 - mT0; } - if (mClient->GetType() == EffectTypeGenerate) + if (GetType() == EffectTypeGenerate) { - return sDefaultGenerateLen; + return GetDefaultDuration(); } return 0; @@ -290,12 +670,10 @@ bool Effect::SetDuration(double seconds) bool Effect::Apply() { - // This is absolute hackage...but easy + // This is absolute hackage...but easy and I can't think of another way just now. // // It should callback to the EffectManager to kick off the processing - GetActiveProject()->OnEffect(GetID(), true); - - return true; + return GetActiveProject()->OnEffect(GetID(), AudacityProject::OnEffectFlags::kConfigured); } void Effect::Preview() @@ -307,18 +685,6 @@ wxDialog *Effect::CreateUI(wxWindow *parent, EffectUIClientInterface *client) { EffectUIHost *dlg = new EffectUIHost(parent, this, client); -#if defined(__WXMAC__) - // We want the effects windows on the Mac to float above the project window - // but still have normal modal dialogs appear above the effects windows and - // not let the effect windows fall behind the project window. - // - // This seems to accomplish that, but time will be the real judge. - WindowRef windowRef = (WindowRef) dlg->MacGetWindowRef(); - WindowGroupRef parentGroup = GetWindowGroup((WindowRef) ((wxFrame *)wxGetTopLevelParent(parent))->MacGetWindowRef()); - ChangeWindowGroupAttributes(parentGroup, kWindowGroupAttrSharedActivation, kWindowGroupAttrMoveTogether); - SetWindowGroup(windowRef, parentGroup); -#endif - if (dlg->Initialize()) { return dlg; @@ -511,7 +877,7 @@ PluginID Effect::GetID() return PluginManager::GetID(mClient); } - return wxString::Format(wxT("LEGACY_EFFECT_ID_%d"), GetEffectID()); + return PluginManager::GetID(this); } bool Effect::Startup(EffectClientInterface *client) @@ -520,119 +886,80 @@ bool Effect::Startup(EffectClientInterface *client) mClient = client; // Set host so client startup can use our services - if (!mClient->SetHost(this)) + if (!SetHost(this)) { // Bail if the client startup fails mClient = NULL; return false; } - mNumAudioIn = mClient->GetAudioInCount(); - mNumAudioOut = mClient->GetAudioOutCount(); + mNumAudioIn = GetAudioInCount(); + mNumAudioOut = GetAudioOutCount(); - int flags = PLUGIN_EFFECT; - switch (mClient->GetType()) + bool haveDefaults; + GetPrivateConfig(GetFactoryDefaultsGroup(), wxT("Initialized"), haveDefaults, false); + if (!haveDefaults) { - case EffectTypeGenerate: - flags |= INSERT_EFFECT; - break; - - case EffectTypeProcess: - flags |= PROCESS_EFFECT; - break; - - case EffectTypeAnalyze: - flags |= INSERT_EFFECT; - break; - - case EffectTypeNone: - // Nothing to set - break; + SaveUserPreset(GetFactoryDefaultsGroup()); + SetPrivateConfig(GetFactoryDefaultsGroup(), wxT("Initialized"), true); } + LoadUserPreset(GetCurrentSettingsGroup()); - SetEffectFlags(flags); + return Startup(); +} +bool Effect::Startup() +{ return true; } bool Effect::GetAutomationParameters(wxString & parms) { - if (mClient) - { - EffectAutomationParameters eap; - if (!mClient->GetAutomationParameters(eap)) - { - return false; - } + EffectAutomationParameters eap; - return eap.GetParameters(parms); - } - - ShuttleCli shuttle; - shuttle.mbStoreInClient = false; - if (!TransferParameters(shuttle)) + if (mUIDialog && !TransferDataFromWindow()) { return false; } - parms = shuttle.mParams; + if (!GetAutomationParameters(eap)) + { + return false; + } - return true; + return eap.GetParameters(parms); } bool Effect::SetAutomationParameters(const wxString & parms) { - if (mClient) + EffectAutomationParameters eap(parms); + + if (!SetAutomationParameters(eap)) { - EffectAutomationParameters eap; - eap.SetParameters(parms); - return mClient->SetAutomationParameters(eap); + wxMessageBox( + wxString::Format( + _("Could not set parameters of effect %s\n to %s."), + GetName().c_str(), + parms.c_str() + ) + ); + return false; } - ShuttleCli shuttle; - shuttle.mParams = parms; - shuttle.mbStoreInClient = true; - return TransferParameters(shuttle); -} - -// All legacy effects should have this overridden -wxString Effect::GetEffectName() -{ - if (mClient) + if (!mUIDialog) { - return mClient->GetName(); + return true; } - return wxT("DummyIdentifier"); + return TransferDataToWindow(); } -// All legacy effects should have this overridden -wxString Effect::GetEffectIdentifier() -{ - if (mClient) - { - return mClient->GetName(); - } - - return wxT("DummyIdentifier"); -} - -// All legacy effects should have this overridden -wxString Effect::GetEffectAction() -{ - if (mClient) - { - return _("Applying ") + mClient->GetName(); - } - - return wxT("DummyName"); -} - -bool Effect::DoEffect(wxWindow *parent, int flags, +bool Effect::DoEffect(wxWindow *parent, double projectRate, TrackList *list, TrackFactory *factory, - SelectedRegion *selectedRegion, wxString params) + SelectedRegion *selectedRegion, + bool shouldPrompt /* = true */) { double t0 = selectedRegion->t0(); double t1 = selectedRegion->t1(); @@ -669,43 +996,19 @@ bool Effect::DoEffect(wxWindow *parent, int flags, return false; } - // If a parameter string was provided, it overrides any remembered settings - // (but if the user is to be prompted, that takes priority) - if (!params.IsEmpty()) + // Prompting will be bypassed when applying an effect that has already + // been configured, e.g. repeating the last effect on a different selection. + if (shouldPrompt && !PromptUser(parent)) { - ShuttleCli shuttle; - shuttle.mParams = params; - shuttle.mbStoreInClient=true; - if( !TransferParameters( shuttle )) - { - wxMessageBox( - wxString::Format( - _("Could not set parameters of effect %s\n to %s."), - GetEffectName().c_str(), - params.c_str() - ) - ); - return false; - } - } - - // Don't prompt user if we are dealing with a - // effect that is already configured, e.g. repeating - // the last effect on a different selection. - if ((flags & CONFIGURED_EFFECT) == 0) - { - if (!PromptUser()) - { - return false; - } + return false; } bool returnVal = true; bool skipFlag = CheckWhetherSkipEffect(); if (skipFlag == false) { - mProgress = new ProgressDialog(StripAmpersand(GetEffectName()), - GetEffectAction(), + mProgress = new ProgressDialog(GetName(), + wxString::Format(_("Applying %s..."), GetName().c_str()), pdlgHideStopButton); returnVal = Process(); delete mProgress; @@ -734,52 +1037,76 @@ bool Effect::Init() return true; } -bool Effect::PromptUser() +bool Effect::PromptUser(wxWindow *parent, bool isBatch) { - return PromptUser(mParent); -} - -bool Effect::PromptUser(wxWindow *parent, bool forceModal) -{ - mParent = parent; - - if (mClient) + if (IsInteractive()) { - bool res = mClient->ShowInterface(parent, forceModal); + mParent = parent; + + bool res = ShowInterface(parent, isBatch); // Really need to clean this up...should get easier when // all effects get converted. - if (!res || (SupportsRealtime() && !forceModal)) + if (!res || (SupportsRealtime() && isBatch)) { // Return false to force DoEffect() to skip processing since // this UI has either been shown modeless or there was an error. return false; } } - else - { - PromptUser(); - } return true; } -// All legacy effects should have this overridden + +int Effect::GetPass() +{ + return mPass; +} + +bool Effect::InitPass1() +{ + return true; +} + +bool Effect::InitPass2() +{ + return false; +} + bool Effect::Process() { - if (!mClient) - { - return false; - } - - bool isGenerator = mClient->GetType() == EffectTypeGenerate; - CopyInputTracks(Track::All); bool bGoodResult = true; + mPass = 1; + if (InitPass1()) + { + bGoodResult = ProcessPass(); + mPass = 2; + if (bGoodResult && InitPass2()) + { + bGoodResult = ProcessPass(); + } + } + + ReplaceProcessedTracks(bGoodResult); + + return bGoodResult; +} + +bool Effect::ProcessPass() +{ + bool bGoodResult = true; + bool isGenerator = GetType() == EffectTypeGenerate; + bool editClipCanMove; + gPrefs->Read(wxT("/GUI/EditClipCanMove"), &editClipCanMove, true); + mInBuffer = NULL; mOutBuffer = NULL; + ChannelName map[3]; + sampleCount prevBufferSize = 0; mBufferSize = 0; mBlockSize = 0; @@ -809,15 +1136,31 @@ bool Effect::Process() if (!isGenerator) { GetSamples(left, &leftStart, &len); + mSampleCnt = len; } else { len = 0; leftStart = 0; + mSampleCnt = left->TimeToLongSamples(mDuration); } mNumChannels = 1; + if (left->GetChannel() == Track::LeftChannel) + { + map[0] = ChannelNameFrontLeft; + } + else if (left->GetChannel() == Track::RightChannel) + { + map[0] = ChannelNameFrontRight; + } + else + { + map[0] = ChannelNameMono; + } + map[1] = ChannelNameEOL; + right = NULL; rightStart = 0; if (left->GetLinked() && mNumAudioIn > 1) @@ -829,14 +1172,28 @@ bool Effect::Process() } clear = false; mNumChannels = 2; + + if (right->GetChannel() == Track::LeftChannel) + { + map[1] = ChannelNameFrontLeft; + } + else if (right->GetChannel() == Track::RightChannel) + { + map[1] = ChannelNameFrontRight; + } + else + { + map[1] = ChannelNameMono; + } + map[2] = ChannelNameEOL; } // Let the client know the sample rate - mClient->SetSampleRate(left->GetRate()); + SetSampleRate(left->GetRate()); // Get the block size the client wants to use sampleCount max = left->GetMaxBlockSize() * 2; - mBlockSize = mClient->GetBlockSize(max); + mBlockSize = SetBlockSize(max); // Calculate the buffer size to be at least the max rounded up to the clients // selected block size. @@ -927,7 +1284,7 @@ bool Effect::Process() } // Go process the track(s) - bGoodResult = ProcessTrack(count, left, right, leftStart, rightStart, len); + bGoodResult = ProcessTrack(count, map, left, right, leftStart, rightStart, len); if (!bGoodResult) { break; @@ -960,12 +1317,11 @@ bool Effect::Process() mInBufPos = NULL; } - ReplaceProcessedTracks(bGoodResult); - return bGoodResult; } bool Effect::ProcessTrack(int count, + ChannelNames map, WaveTrack *left, WaveTrack *right, sampleCount leftStart, @@ -975,7 +1331,10 @@ bool Effect::ProcessTrack(int count, bool rc = true; // Give the plugin a chance to initialize - mClient->ProcessInitialize(); + if (!ProcessInitialize(len, map)) + { + return false; + } // For each input block of samples, we pass it to the effect along with a // variable output location. This output location is simply a pointer into a @@ -1009,8 +1368,8 @@ bool Effect::ProcessTrack(int count, WaveTrack *genLeft = NULL; WaveTrack *genRight = NULL; sampleCount genLength = 0; - bool isGenerator = mClient->GetType() == EffectTypeGenerate; - bool isProcessor = mClient->GetType() == EffectTypeProcess; + bool isGenerator = GetType() == EffectTypeGenerate; + bool isProcessor = GetType() == EffectTypeProcess; if (isGenerator) { genLength = left->GetRate() * mDuration; @@ -1100,7 +1459,7 @@ bool Effect::ProcessTrack(int count, // From this point on, we only want to feed zeros to the plugin if (!cleared) - { + { // Reset the input buffer positions for (int i = 0; i < mNumChannels; i++) { @@ -1117,14 +1476,26 @@ bool Effect::ProcessTrack(int count, } // Finally call the plugin to process the block + sampleCount processed; try { - mClient->ProcessBlock(mInBufPos, mOutBufPos, curBlockSize); + processed = ProcessBlock(mInBufPos, mOutBufPos, curBlockSize); } catch(...) { + if (genLeft) + { + delete genLeft; + } + + if (genRight) + { + delete genRight; + } + return false; } + wxASSERT(processed == curBlockSize); // Bump to next input buffer position if (inputRemaining) @@ -1147,7 +1518,7 @@ bool Effect::ProcessTrack(int count, // Get the current number of delayed samples and accumulate if (isProcessor) { - sampleCount delay = mClient->GetLatency(); + sampleCount delay = GetLatency(); curDelay += delay; delayRemaining += delay; @@ -1219,7 +1590,7 @@ bool Effect::ProcessTrack(int count, if (mNumChannels > 1) { - if (TrackGroupProgress(count, (inLeftPos - leftStart) / (double) len)) + if (TrackGroupProgress(count, (inLeftPos - leftStart) / (double) (isGenerator ? genLength : len))) { rc = false; break; @@ -1227,7 +1598,7 @@ bool Effect::ProcessTrack(int count, } else { - if (TrackProgress(count, (inLeftPos - leftStart) / (double) len)) + if (TrackProgress(count, (inLeftPos - leftStart) / (double) (isGenerator ? genLength : len))) { rc = false; break; @@ -1258,22 +1629,28 @@ bool Effect::ProcessTrack(int count, if (isGenerator) { + StepTimeWarper *warper = new StepTimeWarper(mT0 + genLength, genLength - (mT1 - mT0)); + // Transfer the data from the temporary tracks to the actual ones genLeft->Flush(); - SetTimeWarper(new StepTimeWarper(mT0 + genLength, genLength - (mT1 - mT0))); - left->ClearAndPaste(mT0, mT1, genLeft, true, true, GetTimeWarper()); + left->ClearAndPaste(mT0, mT1, genLeft, true, true, warper); delete genLeft; if (genRight) { genRight->Flush(); - right->ClearAndPaste(mT0, mT1, genRight, true, true, GetTimeWarper()); + right->ClearAndPaste(mT0, mT1, genRight, true, true, warper); delete genRight; } + + delete warper; } // Allow the plugin to cleanup - mClient->ProcessFinalize(); + if (!ProcessFinalize()) + { + return false; + } return rc; } @@ -1282,6 +1659,91 @@ void Effect::End() { } +void Effect::PopulateOrExchange(ShuttleGui & WXUNUSED(S)) +{ + return; +} + +bool Effect::TransferDataToWindow() +{ + return true; +} + +bool Effect::TransferDataFromWindow() +{ + return true; +} + +bool Effect::EnableApply(bool enable) +{ + // May be called during initialization, so try to find the dialog + wxWindow *dlg = mUIDialog; + if (!dlg && mUIParent) + { + dlg = wxGetTopLevelParent(mUIParent); + } + + if (dlg) + { + wxWindow *apply = dlg->FindWindow(wxID_APPLY); + + // Don't allow focus to get trapped + if (!enable) + { + wxWindow *focus = dlg->FindFocus(); + if (focus == apply) + { + dlg->FindWindow(wxID_CLOSE)->SetFocus(); + } + } + + apply->Enable(enable); + } + + EnablePreview(enable); + + return enable; +} + +bool Effect::EnablePreview(bool enable) +{ + // May be called during initialization, so try to find the dialog + wxWindow *dlg = mUIDialog; + if (!dlg && mUIParent) + { + dlg = wxGetTopLevelParent(mUIParent); + } + + if (dlg) + { + wxWindow *play = dlg->FindWindow(kPlayID); + if (play) + { + wxWindow *rewind = dlg->FindWindow(kRewindID); + wxWindow *ffwd = dlg->FindWindow(kFFwdID); + + // Don't allow focus to get trapped + if (!enable) + { + wxWindow *focus = dlg->FindFocus(); + if (focus && (focus == play || focus == rewind || focus == ffwd)) + { + dlg->FindWindow(wxID_CLOSE)->SetFocus(); + } + } + + play->Enable(enable); + if (SupportsRealtime()) + { + rewind->Enable(enable); + ffwd->Enable(enable); + } + } + } + + return enable; +} + bool Effect::TotalProgress(double frac) { int updateResult = (mProgress ? @@ -1503,119 +1965,11 @@ void Effect::CountWaveTracks() } } -float TrapFloat(float x, float min, float max) -{ - if (x <= min) - return min; - else if (x >= max) - return max; - else - return x; -} - -double TrapDouble(double x, double min, double max) -{ - if (x <= min) - return min; - else if (x >= max) - return max; - else - return x; -} - -long TrapLong(long x, long min, long max) -{ - if (x <= min) - return min; - else if (x >= max) - return max; - else - return x; -} - double Effect::CalcPreviewInputLength(double previewLength) { return previewLength; } -wxString Effect::GetPreviewName() -{ - return _("Pre&view"); -} - -bool Effect::SupportsRealtime() -{ -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) - if (mClient) - { - return mClient->SupportsRealtime(); - } -#endif - - return false; -} - -bool Effect::RealtimeInitialize() -{ -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) - if (mClient) - { - mBlockSize = mClient->GetBlockSize(512); - return mClient->RealtimeInitialize(); - } -#endif - - return false; -} - -bool Effect::RealtimeFinalize() -{ -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) - if (mClient) - { - return mClient->RealtimeFinalize(); - } -#endif - - return false; -} - -bool Effect::RealtimeSuspend() -{ -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) - if (mClient) - { - if (mClient->RealtimeSuspend()) - { - mRealtimeSuspendLock.Enter(); - mRealtimeSuspendCount++; - mRealtimeSuspendLock.Leave(); - return true; - } - } -#endif - - return false; -} - -bool Effect::RealtimeResume() -{ -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) - if (mClient) - { - if (mClient->RealtimeResume()) - { - mRealtimeSuspendLock.Enter(); - mRealtimeSuspendCount--; - mRealtimeSuspendLock.Leave(); - return true; - } - } -#endif - - return false; -} - // RealtimeAddProcessor and RealtimeProcess use the same method of // determining the current processor index, so updates to one should // be reflected in the other. @@ -1681,11 +2035,6 @@ bool Effect::RealtimeAddProcessor(int group, int chans, float rate) return true; } -bool Effect::RealtimeProcessStart() -{ - return mClient->RealtimeProcessStart(); -} - // RealtimeAddProcessor and RealtimeProcess use the same method of // determining the current processor group, so updates to one should // be reflected in the other. @@ -1695,7 +2044,6 @@ sampleCount Effect::RealtimeProcess(int group, float **outbuf, sampleCount numSamples) { -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) // // The caller passes the number of channels to process and specifies // the number of input and output buffers. There will always be the @@ -1803,14 +2151,6 @@ sampleCount Effect::RealtimeProcess(int group, } return len; -#else - return 0; -#endif -} - -bool Effect::RealtimeProcessEnd() -{ - return mClient->RealtimeProcessEnd(); } bool Effect::IsRealtimeActive() @@ -1818,6 +2158,11 @@ bool Effect::IsRealtimeActive() return mRealtimeSuspendCount == 0; } +bool Effect::IsHidden() +{ + return false; +} + void Effect::Preview(bool dryOnly) { if (mNumTracks==0) // nothing to preview @@ -1841,8 +2186,8 @@ void Effect::Preview(bool dryOnly) t1 = mT1; // Generators can run without a selection. - if (!GeneratorPreview() && (t1 <= t0)) - return; +// if (!GeneratorPreview() && (t1 <= t0)) +// return; bool success = ::MixAndRender(mTracks, mFactory, rate, floatSample, t0, t1, &mixLeft, &mixRight); @@ -1883,7 +2228,7 @@ void Effect::Preview(bool dryOnly) // Effect is already inited; we call Process, End, and then Init // again, so the state is exactly the way it was before Preview // was called. - mProgress = new ProgressDialog(StripAmpersand(GetEffectName()), + mProgress = new ProgressDialog(GetName(), _("Preparing preview"), pdlgHideCancelButton); // Have only "Stop" button. bSuccess = Process(); @@ -1925,7 +2270,7 @@ void Effect::Preview(bool dryOnly) if (token) { int previewing = eProgressSuccess; - mProgress = new ProgressDialog(StripAmpersand(GetEffectName()), + mProgress = new ProgressDialog(GetName(), _("Previewing"), pdlgHideCancelButton); while (gAudioIO->IsStreamActive(token) && previewing == eProgressSuccess) { @@ -1960,26 +2305,6 @@ void Effect::Preview(bool dryOnly) mTracks = saveTracks; } -int Effect::GetAudioInCount() -{ - if (mClient) - { - return mClient->GetAudioInCount(); - } - - return 0; -} - -int Effect::GetAudioOutCount() -{ - if (mClient) - { - return mClient->GetAudioInCount(); - } - - return 0; -} - BEGIN_EVENT_TABLE(EffectDialog, wxDialog) EVT_BUTTON(wxID_OK, EffectDialog::OnOk) END_EVENT_TABLE() @@ -2005,11 +2330,10 @@ void EffectDialog::Init() PopulateOrExchange(S); long buttons = eOkButton; - - if (mType == PROCESS_EFFECT || mType == INSERT_EFFECT) + if (mType != EffectTypeAnalyze) { buttons |= eCancelButton; - if (mType == PROCESS_EFFECT) + if (mType == EffectTypeProcess) { buttons |= ePreviewButton; } @@ -2053,12 +2377,12 @@ bool EffectDialog::Validate() return true; } -void EffectDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) +void EffectDialog::OnPreview(wxCommandEvent & WXUNUSED(evt)) { return; } -void EffectDialog::OnOk(wxCommandEvent & WXUNUSED(event)) +void EffectDialog::OnOk(wxCommandEvent & WXUNUSED(evt)) { // On wxGTK (wx2.8.12), the default action is still executed even if // the button is disabled. This appears to affect all wxDialogs, not @@ -2079,16 +2403,13 @@ void EffectDialog::OnOk(wxCommandEvent & WXUNUSED(event)) // /////////////////////////////////////////////////////////////////////////////// -class EffectPanel : public wxScrolledWindow +class EffectPanel : public wxPanel { public: EffectPanel(wxWindow *parent) - : wxScrolledWindow(parent, - wxID_ANY, - wxDefaultPosition, - wxDefaultSize, - wxVSCROLL | wxTAB_TRAVERSAL) + : wxPanel(parent) { + mAcceptsFocus = true; } virtual ~EffectPanel() @@ -2099,18 +2420,21 @@ public: // wxWindow implementation // ============================================================================ - virtual bool AcceptsFocus() const + bool AcceptsFocus() const { - // Only accept focus if we're not a GUI host. - // - // This assumes that any effect will have more than one control in its - // interface unless it is a GUI interface. It's a fairly safe assumption. -#if defined(__WXMAC__) - return GetChildren().GetCount() > 2; -#else - return GetChildren().GetCount() > 1; -#endif + return mAcceptsFocus; } + + // ============================================================================ + // EffectPanel implementation + // ============================================================================ + void SetAccept(bool accept) + { + mAcceptsFocus = accept; + } + +private: + bool mAcceptsFocus; }; /////////////////////////////////////////////////////////////////////////////// @@ -2119,34 +2443,11 @@ public: // /////////////////////////////////////////////////////////////////////////////// -#include -WX_DEFINE_OBJARRAY(AccelArray); - #include "../../images/Effect.h" -enum -{ - kDummyID = 30000, - kSaveAsID = 30001, - kImportID = 30002, - kExportID = 30003, - kDefaultsID = 30004, - kOptionsID = 30005, - kUserPresetsDummyID = 30006, - kDeletePresetDummyID = 30007, - kMenuID = 30100, - kEnableID = 30101, - kPlayID = 30102, - kRewindID = 30103, - kFFwdID = 30104, - kPlaybackID = 30105, - kCaptureID = 30106, - kUserPresetsID = 31000, - kDeletePresetID = 32000, - kFactoryPresetsID = 33000, -}; - BEGIN_EVENT_TABLE(EffectUIHost, wxDialog) + EVT_ERASE_BACKGROUND(EffectUIHost::OnErase) + EVT_PAINT(EffectUIHost::OnPaint) EVT_CLOSE(EffectUIHost::OnClose) EVT_BUTTON(wxID_APPLY, EffectUIHost::OnApply) EVT_BUTTON(wxID_CANCEL, EffectUIHost::OnCancel) @@ -2169,7 +2470,8 @@ EffectUIHost::EffectUIHost(wxWindow *parent, Effect *effect, EffectUIClientInterface *client) : wxDialog(parent, wxID_ANY, effect->GetName(), - wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) + wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX) { SetName(effect->GetName()); SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY); @@ -2181,6 +2483,7 @@ EffectUIHost::EffectUIHost(wxWindow *parent, mProject = GetActiveProject(); mInitialized = false; + mSupportsRealtime = false; mDisableTransport = false; @@ -2188,27 +2491,12 @@ EffectUIHost::EffectUIHost(wxWindow *parent, mPlayPos = 0.0; - mClient->SetUIHost(this); + mClient->SetHostUI(this); } EffectUIHost::~EffectUIHost() { - if (mInitialized) - { - mInitialized = false; - - wxTheApp->Disconnect(EVT_AUDIOIO_PLAYBACK, - wxCommandEventHandler(EffectUIHost::OnPlayback), - NULL, - this); - - wxTheApp->Disconnect(EVT_AUDIOIO_CAPTURE, - wxCommandEventHandler(EffectUIHost::OnCapture), - NULL, - this); - - EffectManager::Get().RealtimeRemoveEffect(mEffect); - } + CleanupRealtime(); if (mClient) { @@ -2217,6 +2505,65 @@ EffectUIHost::~EffectUIHost() } } +// ============================================================================ +// wxWindow implementation +// ============================================================================ + +#if defined(__WXMAC__) + +// As mentioned below, we want to manipulate the window attributes, but doing +// so causes extra events to fire and those events lead to the rebuilding of +// the menus. Unfortunately, if this happens when a modal dialog is displayed +// the menus become disabled until the menubar is completely rebuilt, like when +// leaving preferecnes. +// +// So, we only do this when NOT displaying a modal dialog since that's really +// only when it is needed. + +bool EffectUIHost::Show(bool show) +{ + if (!mIsModal) + { + // We want the effects windows on the Mac to float above the project window + // but still have normal modal dialogs appear above the effects windows and + // not let the effect windows fall behind the project window. + // + // This seems to accomplish that, but time will be the real judge. + WindowRef windowRef = (WindowRef) MacGetWindowRef(); + WindowGroupRef parentGroup = GetWindowGroup((WindowRef) ((wxFrame *)wxGetTopLevelParent(mParent))->MacGetWindowRef()); + ChangeWindowGroupAttributes(parentGroup, kWindowGroupAttrSharedActivation, kWindowGroupAttrMoveTogether); + SetWindowGroup(windowRef, parentGroup); + } + mIsModal = false; + + return wxDialog::Show(show); +} +#endif + +bool EffectUIHost::TransferDataToWindow() +{ + return mEffect->TransferDataToWindow(); +} + +bool EffectUIHost::TransferDataFromWindow() +{ + return mEffect->TransferDataFromWindow(); +} + +// ============================================================================ +// wxDialog implementation +// ============================================================================ + +#if defined(__WXMAC__) +// See explanation in EffectUIHost::Show() +int EffectUIHost::ShowModal() +{ + mIsModal = true; + + return wxDialog::ShowModal(); +} +#endif + // ============================================================================ // EffectUIHost implementation // ============================================================================ @@ -2229,35 +2576,27 @@ bool EffectUIHost::Initialize() EffectPanel *w = new EffectPanel(this); // Try to give the window a sensible default/minimum size - w->SetMinSize(wxSize(wxMax(600, mParent->GetSize().GetWidth() * 2/3), + w->SetMinSize(wxSize(wxMax(600, mParent->GetSize().GetWidth() * 2 / 3), mParent->GetSize().GetHeight() / 2)); - w->SetScrollRate(0, 20); - mDisableTransport = !gAudioIO->IsAvailable(mProject); mPlaying = gAudioIO->IsStreamActive(); // not exactly right, but will suffice mCapturing = gAudioIO->IsStreamActive() && gAudioIO->GetNumCaptureChannels() > 0; - wxPanel *bar = new wxPanel(this, wxID_ANY); - wxSizer *s = CreateStdButtonSizer(this, eApplyButton | eCloseButton, bar); - - mApplyBtn = (wxButton *) FindWindowById(wxID_APPLY); - mCloseBtn = (wxButton *) FindWindowById(wxID_CANCEL); - - hs->Add(w, 1, wxEXPAND); - vs->Add(hs, 1, wxEXPAND); - vs->Add(s, 0, wxEXPAND | wxALIGN_CENTER_VERTICAL); - SetSizer(vs); - if (!mClient->PopulateUI(w)) { return false; } - mIsGUI = mClient->IsGraphicalUI(); + hs->Add(w, 1, wxEXPAND); + vs->Add(hs, 1, wxEXPAND); + + wxPanel *bar = new wxPanel(this, wxID_ANY); wxBoxSizer *bs = new wxBoxSizer(wxHORIZONTAL); - bar->SetSizer(bs); + + mSupportsRealtime = mEffect->SupportsRealtime(); + mIsGUI = mClient->IsGraphicalUI(); wxBitmapButton *bb; @@ -2288,9 +2627,18 @@ bool EffectUIHost::Initialize() if (!mIsGUI) { - mPlayToggleBtn = new wxButton(bar, kPlayID, _("Start &Playback")); - mPlayToggleBtn->SetToolTip(_("Start and stop playback")); - bs->Add(mPlayToggleBtn, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin); + if (mSupportsRealtime) + { + mPlayToggleBtn = new wxButton(bar, kPlayID, _("Start &Playback")); + mPlayToggleBtn->SetToolTip(_("Start and stop playback")); + bs->Add(mPlayToggleBtn, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin); + } + else if (mEffect->GetType() != EffectTypeAnalyze) + { + mPlayToggleBtn = new wxButton(bar, kPlayID, _("&Preview")); + mPlayToggleBtn->SetToolTip(_("Preview effect")); + bs->Add(mPlayToggleBtn, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin); + } } else { @@ -2302,117 +2650,131 @@ bool EffectUIHost::Initialize() bb->SetBitmapDisabled(mPlayDisabledBM); mPlayBtn = bb; bs->Add(mPlayBtn); - } - - if (!mIsGUI) - { - mRewindBtn = new wxButton(bar, kRewindID, _("Skip &Backward")); - bs->Add(mRewindBtn, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin); - } - else - { - bb = new wxBitmapButton(bar, kRewindID, CreateBitmap(effect_rewind_xpm, true, true)); - bb->SetBitmapDisabled(CreateBitmap(effect_rewind_disabled_xpm, true, true)); - mRewindBtn = bb; + if (!mSupportsRealtime) + { + mPlayBtn->SetToolTip(_("Preview effect")); #if defined(__WXMAC__) - mRewindBtn->SetName(_("Skip &Backward")); + mPlayBtn->SetName(_("Preview effect")); #else - mRewindBtn->SetLabel(_("Skip &Backward")); + mPlayBtn->SetLabel(_("&Preview effect")); #endif - bs->Add(mRewindBtn); + } } - mRewindBtn->SetToolTip(_("Skip backward")); - if (!mIsGUI) + if (mSupportsRealtime) { - mFFwdBtn = new wxButton(bar, kFFwdID, _("Skip &Forward")); - bs->Add(mFFwdBtn, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin); - } - else - { - bb = new wxBitmapButton(bar, kFFwdID, CreateBitmap(effect_ffwd_xpm, true, true)); - bb->SetBitmapDisabled(CreateBitmap(effect_ffwd_disabled_xpm, true, true)); - mFFwdBtn = bb; + if (!mIsGUI) + { + mRewindBtn = new wxButton(bar, kRewindID, _("Skip &Backward")); + bs->Add(mRewindBtn, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin); + } + else + { + bb = new wxBitmapButton(bar, kRewindID, CreateBitmap(effect_rewind_xpm, true, true)); + bb->SetBitmapDisabled(CreateBitmap(effect_rewind_disabled_xpm, true, true)); + mRewindBtn = bb; #if defined(__WXMAC__) - mFFwdBtn->SetName(_("Skip &Foreward")); + mRewindBtn->SetName(_("Skip &Backward")); #else - mFFwdBtn->SetLabel(_("Skip &Foreward")); + mRewindBtn->SetLabel(_("Skip &Backward")); #endif - bs->Add(mFFwdBtn); + bs->Add(mRewindBtn); + } + mRewindBtn->SetToolTip(_("Skip backward")); + + if (!mIsGUI) + { + mFFwdBtn = new wxButton(bar, kFFwdID, _("Skip &Forward")); + bs->Add(mFFwdBtn, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin); + } + else + { + bb = new wxBitmapButton(bar, kFFwdID, CreateBitmap(effect_ffwd_xpm, true, true)); + bb->SetBitmapDisabled(CreateBitmap(effect_ffwd_disabled_xpm, true, true)); + mFFwdBtn = bb; +#if defined(__WXMAC__) + mFFwdBtn->SetName(_("Skip &Foreward")); +#else + mFFwdBtn->SetLabel(_("Skip &Foreward")); +#endif + bs->Add(mFFwdBtn); + } + mFFwdBtn->SetToolTip(_("Skip forward")); + + bs->Add(5, 5); + + mEnableCb = new wxCheckBox(bar, kEnableID, _("&Enable")); + mEnableCb->SetValue(mEnabled); + mEnableCb->SetName(_("Enable")); + bs->Add(mEnableCb, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin); } - mFFwdBtn->SetToolTip(_("Skip forward")); - - bs->Add(5, 5); - - mEnableCb = new wxCheckBox(bar, kEnableID, _("&Enable")); - mEnableCb->SetValue(mEnabled); - mEnableCb->SetName(_("Enable")); - bs->Add(mEnableCb, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin); - - UpdateControls(); bar->SetSizerAndFit(bs); + + wxSizer *s = CreateStdButtonSizer(this, eApplyButton | eCloseButton, bar); + vs->Add(s, 0, wxEXPAND | wxALIGN_CENTER_VERTICAL); + + SetSizer(vs); Layout(); Fit(); Center(); -#if defined(__WXMAC__) - (w->GetChildren().GetCount() > 2 ? w : FindWindowById(wxID_APPLY))->SetFocus(); -#else - (w->GetChildren().GetCount() > 1 ? w : FindWindowById(wxID_APPLY))->SetFocus(); -#endif + mApplyBtn = (wxButton *) FindWindowById(wxID_APPLY); + mCloseBtn = (wxButton *) FindWindowById(wxID_CANCEL); + + UpdateControls(); + + w->SetAccept(!mIsGUI); + (!mIsGUI ? w : FindWindowById(wxID_APPLY))->SetFocus(); LoadUserPresets(); - mClient->LoadUserPreset(mEffect->GetCurrentSettingsGroup()); - - EffectManager::Get().RealtimeAddEffect(mEffect); - - wxTheApp->Connect(EVT_AUDIOIO_PLAYBACK, - wxCommandEventHandler(EffectUIHost::OnPlayback), - NULL, - this); - - wxTheApp->Connect(EVT_AUDIOIO_CAPTURE, - wxCommandEventHandler(EffectUIHost::OnCapture), - NULL, - this); - - mInitialized = true; + InitializeRealtime(); return true; } +void EffectUIHost::OnErase(wxEraseEvent & WXUNUSED(evt)) +{ + // Ignore it +} + +void EffectUIHost::OnPaint(wxPaintEvent & WXUNUSED(evt)) +{ + wxPaintDC dc(this); + +#if defined(__WXGTK__) + dc.SetBackground(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE))); +#endif + + dc.Clear(); +} + void EffectUIHost::OnClose(wxCloseEvent & WXUNUSED(evt)) { - if (mInitialized) - { - mInitialized = false; - - wxTheApp->Disconnect(EVT_AUDIOIO_PLAYBACK, - wxCommandEventHandler(EffectUIHost::OnPlayback), - NULL, - this); - - wxTheApp->Disconnect(EVT_AUDIOIO_CAPTURE, - wxCommandEventHandler(EffectUIHost::OnCapture), - NULL, - this); - - EffectManager::Get().RealtimeRemoveEffect(mEffect); - } + CleanupRealtime(); Hide(); mClient->CloseUI(); mClient = NULL; - + Destroy(); } void EffectUIHost::OnApply(wxCommandEvent & WXUNUSED(evt)) { - if (mProject->mViewInfo.selectedRegion.isPoint()) + // On wxGTK (wx2.8.12), the default action is still executed even if + // the button is disabled. This appears to affect all wxDialogs, not + // just our Effects dialogs. So, this is a only temporary workaround + // for legacy effects that disable the OK button. Hopefully this has + // been corrected in wx3. + if (!FindWindowById(wxID_APPLY)->IsEnabled()) + { + return; + } + + if (mEffect->GetType() != EffectTypeGenerate && mProject->mViewInfo.selectedRegion.isPoint()) { wxMessageBox(_("You must select audio in the project window.")); return; @@ -2423,34 +2785,18 @@ void EffectUIHost::OnApply(wxCommandEvent & WXUNUSED(evt)) return; } - mClient->SaveUserPreset(mEffect->GetCurrentSettingsGroup()); + // This will take care of calling TransferDataFromWindow() + if (!mEffect->SaveUserPreset(mEffect->GetCurrentSettingsGroup())) + { + return; + } if (IsModal()) { - if (mInitialized) - { - mInitialized = false; - - wxTheApp->Disconnect(EVT_AUDIOIO_PLAYBACK, - wxCommandEventHandler(EffectUIHost::OnPlayback), - NULL, - this); - - wxTheApp->Disconnect(EVT_AUDIOIO_CAPTURE, - wxCommandEventHandler(EffectUIHost::OnCapture), - NULL, - this); - - EffectManager::Get().RealtimeRemoveEffect(mEffect); - } - - SetReturnCode(true); + EndModal(true); Close(); -#if !defined(__WXGTK__) - EndModal(true); -#endif return; } @@ -2461,33 +2807,12 @@ void EffectUIHost::OnApply(wxCommandEvent & WXUNUSED(evt)) void EffectUIHost::OnCancel(wxCommandEvent & WXUNUSED(evt)) { - if (mInitialized) - { - mInitialized = false; - - wxTheApp->Disconnect(EVT_AUDIOIO_PLAYBACK, - wxCommandEventHandler(EffectUIHost::OnPlayback), - NULL, - this); - - wxTheApp->Disconnect(EVT_AUDIOIO_CAPTURE, - wxCommandEventHandler(EffectUIHost::OnCapture), - NULL, - this); - - EffectManager::Get().RealtimeRemoveEffect(mEffect); - } - if (IsModal()) { - SetReturnCode(false); + EndModal(false); Close(); -#if !defined(__WXGTK__) - EndModal(false); -#endif - return; } @@ -2519,7 +2844,7 @@ void EffectUIHost::OnMenu(wxCommandEvent & WXUNUSED(evt)) menu->Append(0, _("User Presets"), sub); } - wxArrayString factory = mClient->GetFactoryPresets(); + wxArrayString factory = mEffect->GetFactoryPresets(); sub = new wxMenu(); sub->Append(kDefaultsID, _("Defaults")); @@ -2556,8 +2881,8 @@ void EffectUIHost::OnMenu(wxCommandEvent & WXUNUSED(evt)) menu->AppendSeparator(); menu->Append(kSaveAsID, _("Save As...")); menu->AppendSeparator(); - menu->Append(kImportID, _("Import..."))->Enable(mClient->CanExport()); - menu->Append(kExportID, _("Export..."))->Enable(mClient->CanExport()); + menu->Append(kImportID, _("Import..."))->Enable(mClient->CanExportPresets()); + menu->Append(kExportID, _("Export..."))->Enable(mClient->CanExportPresets()); menu->AppendSeparator(); menu->Append(kOptionsID, _("Options..."))->Enable(mClient->HasOptions()); menu->AppendSeparator(); @@ -2569,8 +2894,6 @@ void EffectUIHost::OnMenu(wxCommandEvent & WXUNUSED(evt)) sub->Append(kDummyID, wxString::Format(_("Version: %s"), mEffect->GetVersion().c_str())); sub->Append(kDummyID, wxString::Format(_("Vendor: %s"), mEffect->GetVendor().c_str())); sub->Append(kDummyID, wxString::Format(_("Description: %s"), mEffect->GetDescription().c_str())); -// sub->Append(kDummyID, wxString::Format(_("Audio In: %d"), mEffect->GetAudioInCount())); -// sub->Append(kDummyID, wxString::Format(_("Audio Out: %d"), mEffect->GetAudioOutCount())); menu->Append(0, _("About"), sub); @@ -2598,6 +2921,18 @@ void EffectUIHost::OnEnable(wxCommandEvent & WXUNUSED(evt)) void EffectUIHost::OnPlay(wxCommandEvent & WXUNUSED(evt)) { + if (!mSupportsRealtime) + { + if (!mClient->ValidateUI() || !mEffect->TransferDataFromWindow()) + { + return; + } + + mEffect->Preview(false); + + return; + } + if (mPlaying) { mPlayPos = gAudioIO->GetStreamTime(); @@ -2729,14 +3064,14 @@ void EffectUIHost::OnUserPreset(wxCommandEvent & evt) { int preset = evt.GetId() - kUserPresetsID; - mClient->LoadUserPreset(mEffect->GetUserPresetsGroup(mUserPresets[preset])); + mEffect->LoadUserPreset(mEffect->GetUserPresetsGroup(mUserPresets[preset])); return; } void EffectUIHost::OnFactoryPreset(wxCommandEvent & evt) { - mClient->LoadFactoryPreset(evt.GetId() - kFactoryPresetsID); + mEffect->LoadFactoryPreset(evt.GetId() - kFactoryPresetsID); return; } @@ -2795,7 +3130,7 @@ void EffectUIHost::OnSaveAs(wxCommandEvent & WXUNUSED(evt)) name = text->GetValue(); if (mUserPresets.Index(name) == wxNOT_FOUND) { - mClient->SaveUserPreset(mEffect->GetUserPresetsGroup(name)); + mEffect->SaveUserPreset(mEffect->GetUserPresetsGroup(name)); LoadUserPresets(); break; } @@ -2833,7 +3168,7 @@ void EffectUIHost::OnOptions(wxCommandEvent & WXUNUSED(evt)) void EffectUIHost::OnDefaults(wxCommandEvent & WXUNUSED(evt)) { - mClient->LoadFactoryDefaults(); + mEffect->LoadFactoryDefaults(); return; } @@ -2887,55 +3222,62 @@ void EffectUIHost::UpdateControls() } mApplyBtn->Enable(!mCapturing); - (!mIsGUI ? mPlayToggleBtn : mPlayBtn)->Enable(!(mCapturing || mDisableTransport)); - mRewindBtn->Enable(!(mCapturing || mDisableTransport)); - mFFwdBtn->Enable(!(mCapturing || mDisableTransport)); - mEnableCb->Enable(!(mCapturing || mDisableTransport)); - - wxBitmapButton *bb; - - if (mPlaying) + if (mEffect->GetType() != EffectTypeAnalyze) { - if (!mIsGUI) - { - /* i18n-hint: The access key "&P" should be the same in - "Stop &Playback" and "Start &Playback" */ - mPlayToggleBtn->SetLabel(_("Stop &Playback")); - mPlayToggleBtn->Refresh(); - } - else - { - bb = (wxBitmapButton *) mPlayBtn; - bb->SetBitmapLabel(mStopBM); - bb->SetBitmapDisabled(mStopDisabledBM); - bb->SetToolTip(_("Stop")); -#if defined(__WXMAC__) - bb->SetName(_("Stop &Playback")); -#else - bb->SetLabel(_("Stop &Playback")); -#endif - } + (!mIsGUI ? mPlayToggleBtn : mPlayBtn)->Enable(!(mCapturing || mDisableTransport)); } - else + + if (mSupportsRealtime) { - if (!mIsGUI) + mRewindBtn->Enable(!(mCapturing || mDisableTransport)); + mFFwdBtn->Enable(!(mCapturing || mDisableTransport)); + mEnableCb->Enable(!(mCapturing || mDisableTransport)); + + wxBitmapButton *bb; + + if (mPlaying) { - /* i18n-hint: The access key "&P" should be the same in - "Stop &Playback" and "Start &Playback" */ - mPlayToggleBtn->SetLabel(_("Start &Playback")); - mPlayToggleBtn->Refresh(); + if (!mIsGUI) + { + /* i18n-hint: The access key "&P" should be the same in + "Stop &Playback" and "Start &Playback" */ + mPlayToggleBtn->SetLabel(_("Stop &Playback")); + mPlayToggleBtn->Refresh(); + } + else + { + bb = (wxBitmapButton *) mPlayBtn; + bb->SetBitmapLabel(mStopBM); + bb->SetBitmapDisabled(mStopDisabledBM); + bb->SetToolTip(_("Stop")); +#if defined(__WXMAC__) + bb->SetName(_("Stop &Playback")); +#else + bb->SetLabel(_("Stop &Playback")); +#endif + } } else { - bb = (wxBitmapButton *) mPlayBtn; - bb->SetBitmapLabel(mPlayBM); - bb->SetBitmapDisabled(mPlayDisabledBM); - bb->SetToolTip(_("Play")); + if (!mIsGUI) + { + /* i18n-hint: The access key "&P" should be the same in + "Stop &Playback" and "Start &Playback" */ + mPlayToggleBtn->SetLabel(_("Start &Playback")); + mPlayToggleBtn->Refresh(); + } + else + { + bb = (wxBitmapButton *) mPlayBtn; + bb->SetBitmapLabel(mPlayBM); + bb->SetBitmapDisabled(mPlayDisabledBM); + bb->SetToolTip(_("Play")); #if defined(__WXMAC__) - bb->SetName(_("Start &Playback")); + bb->SetName(_("Start &Playback")); #else - bb->SetLabel(_("Start &Playback")); + bb->SetLabel(_("Start &Playback")); #endif + } } } } @@ -2950,3 +3292,43 @@ void EffectUIHost::LoadUserPresets() return; } + +void EffectUIHost::InitializeRealtime() +{ + if (mSupportsRealtime && !mInitialized) + { + EffectManager::Get().RealtimeAddEffect(mEffect); + + wxTheApp->Connect(EVT_AUDIOIO_PLAYBACK, + wxCommandEventHandler(EffectUIHost::OnPlayback), + NULL, + this); + + wxTheApp->Connect(EVT_AUDIOIO_CAPTURE, + wxCommandEventHandler(EffectUIHost::OnCapture), + NULL, + this); + + mInitialized = true; + } +} + +void EffectUIHost::CleanupRealtime() +{ + if (mSupportsRealtime && mInitialized) + { + wxTheApp->Disconnect(EVT_AUDIOIO_PLAYBACK, + wxCommandEventHandler(EffectUIHost::OnPlayback), + NULL, + this); + + wxTheApp->Disconnect(EVT_AUDIOIO_CAPTURE, + wxCommandEventHandler(EffectUIHost::OnCapture), + NULL, + this); + + EffectManager::Get().RealtimeRemoveEffect(mEffect); + + mInitialized = false; + } +} \ No newline at end of file diff --git a/src/effects/Effect.h b/src/effects/Effect.h index 5013583fb..7cc7bbd07 100644 --- a/src/effects/Effect.h +++ b/src/effects/Effect.h @@ -34,39 +34,20 @@ class wxWindow; #include "../Internat.h" #include "../widgets/ProgressDialog.h" +#define BUILTIN_EFFECT_PREFIX wxT("Builtin Effect: ") + class SelectedRegion; class TimeWarper; class EffectUIHost; -#define PLUGIN_EFFECT 0x0001 -#define BUILTIN_EFFECT 0x0002 -// ADVANCED_EFFECT was introduced for Lynn Allan's 'CleanSpeech' -// it allows the list of effects to be filtered to exclude -// the advanced effects. -// Left in when CLEANSPEECH was removed, as it may be useful at some point. -#define ADVANCED_EFFECT 0x0004 -// HIDDEN_EFFECT allows an item to be excluded from the effects -// menu -#define HIDDEN_EFFECT 0x0008 +// TODO: Apr-06-2015 +// TODO: Much more cleanup of old methods and variables is needed, but +// TODO: can't be done until after all effects are using the new API. -#define INSERT_EFFECT 0x0010 -#define PROCESS_EFFECT 0x0020 -#define ANALYZE_EFFECT 0x0040 -#define ALL_EFFECTS 0x00FF - -// Flag used to disable prompting for configuration -// parameteres. -#define CONFIGURED_EFFECT 0x8000 - -//CLEAN-ME: Rogue value to skip unwanted effects in a chain. -//lda: SKIP_EFFECT_MILLISECOND is a rogue value, used where a millisecond -//time is required to indicate "Don't do this effect". -//It is used in SpikeCleaner and TruncSilence. -//MERGE: Maybe we can remove this now that we have configurable batch -//and so can just drop the steps we don't want? -#define SKIP_EFFECT_MILLISECOND 99999 - -class AUDACITY_DLL_API Effect : public EffectHostInterface +class AUDACITY_DLL_API Effect : public wxEvtHandler, + public EffectClientInterface, + public EffectUIClientInterface, + public EffectHostInterface { // // public methods @@ -99,8 +80,70 @@ class AUDACITY_DLL_API Effect : public EffectHostInterface virtual bool SupportsRealtime(); virtual bool SupportsAutomation(); + // EffectClientInterface implementation + + virtual bool SetHost(EffectHostInterface *host); + + virtual int GetAudioInCount(); + virtual int GetAudioOutCount(); + + virtual int GetMidiInCount(); + virtual int GetMidiOutCount(); + + virtual sampleCount GetLatency(); + virtual sampleCount GetTailSize(); + + virtual void SetSampleRate(sampleCount rate); + virtual sampleCount SetBlockSize(sampleCount maxBlockSize); + + virtual bool IsReady(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); + virtual bool ProcessFinalize(); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + + virtual bool RealtimeInitialize(); + virtual bool RealtimeAddProcessor(int numChannels, float sampleRate); + virtual bool RealtimeFinalize(); + virtual bool RealtimeSuspend(); + virtual bool RealtimeResume(); + virtual bool RealtimeProcessStart(); + virtual sampleCount RealtimeProcess(int group, + float **inbuf, + float **outbuf, + sampleCount numSamples); + virtual bool RealtimeProcessEnd(); + + virtual bool ShowInterface(wxWindow *parent, bool forceModal = false); + + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + + virtual bool LoadUserPreset(const wxString & name); + virtual bool SaveUserPreset(const wxString & name); + + virtual wxArrayString GetFactoryPresets(); + virtual bool LoadFactoryPreset(int id); + virtual bool LoadFactoryDefaults(); + + // EffectUIClientInterface implementation + + virtual void SetHostUI(EffectUIHostInterface *host); + virtual bool PopulateUI(wxWindow *parent); + virtual bool IsGraphicalUI(); + virtual bool ValidateUI(); + virtual bool HideUI(); + virtual bool CloseUI(); + + virtual bool CanExportPresets(); + virtual void ExportPresets(); + virtual void ImportPresets(); + + virtual bool HasOptions(); + virtual void ShowOptions(); + // EffectHostInterface implementation + virtual double GetDefaultDuration(); virtual double GetDuration(); virtual bool SetDuration(double duration); @@ -156,142 +199,43 @@ class AUDACITY_DLL_API Effect : public EffectHostInterface // Effect implementation virtual PluginID GetID(); + virtual bool Startup(EffectClientInterface *client); + virtual bool Startup(); + virtual bool GetAutomationParameters(wxString & parms); virtual bool SetAutomationParameters(const wxString & parms); - // Each subclass of Effect should override this method. - // This name will go in the menu bar; - // append "..." if your effect pops up a dialog - virtual wxString GetEffectName(); - - // Each subclass of Effect should override this method. - // This should return the category of this effect, which - // will determine where in the menu it's placed. -#ifdef EFFECT_CATEGORIES - virtual std::set GetEffectCategories() = 0; -#endif - - // Previously optional. Now required to identify effects for Chain support. - // Each subclass of Effect should override this method. - // This should be human-readable, but should NOT be translated. Use wxT(""), not _(""). - virtual wxString GetEffectIdentifier(); - - // Each subclass of Effect should override this method. - // This name will go in the progress dialog, but can be used - // elsewhere, and it should describe what is being done. - // For example, if the effect is "Filter", the action is - // "Filtering", or if the effect is "Bass Boost", the action - // is "Boosting Bass Frequencies". - virtual wxString GetEffectAction(); - - // Each subclass of Effect should override this method. - // This description will go in the History state. - // Override to include effect parameters, so typically useful only after PromptUser. - virtual wxString GetEffectDescription() { - // Default provides effect name. - return wxString::Format(_("Applied effect: %s"), - this->GetEffectName().c_str()); - } - - // Return flags which tell you what kind of effect this is. - // It will be either a built-in or a plug-in effect, and it - // will be one of Insert, Process, or Analyze. - virtual int GetEffectFlags() { - // Default of BUILTIN_EFFECT | PROCESS_EFFECT | ADVANCED_EFFECT (set in constructor) - - // covers most built-in effects. - return mFlags; - } - - // Return true if the effect supports processing via batch chains. - virtual bool SupportsChains() { - // All builtin effect support chains (???) - return (mFlags & BUILTIN_EFFECT) != 0; - } - - // Preview normally requires a selection, but INSERT_EFFECTs do not. - // Return true to override the need for a selection. - virtual bool GeneratorPreview(){ - return false; - } - - // Called to set or retrieve parameter values. Return true if successful. - virtual bool TransferParameters( Shuttle & WXUNUSED(shuttle) ) { - return true; - } - void SetPresetParameters( const wxArrayString * Names, const wxArrayString * Values ){ if( Names ) mPresetNames = *Names; if( Values ) mPresetValues = *Values; } - void SetEffectFlags( int NewFlags ) - { - mFlags = NewFlags; - } - - // The Effect class fully implements the Preview method for you. - // Only override it if you need to do preprocessing or cleanup. - virtual void Preview(bool dryOnly); - - // Most effects just use the previewLength, but time-stretching/compressing - // effects need to use a different input length, so override this method. - virtual double CalcPreviewInputLength(double previewLength); - - // Get an unique ID assigned to each registered effect. - // The first effect will have ID zero. - int GetEffectID() { - return mEffectID; - } - - // Set an unique ID assigned to each registered effect. - void SetEffectID(int id) { - mEffectID = id; - } - // Returns true on success. Will only operate on tracks that // have the "selected" flag set to true, which is consistent with // Audacity's standard UI. - bool DoEffect(wxWindow *parent, int flags, double projectRate, TrackList *list, + bool DoEffect(wxWindow *parent, double projectRate, TrackList *list, TrackFactory *factory, SelectedRegion *selectedRegion, - wxString params); - - wxString GetPreviewName(); - - //ANSWER-ME: Isn't this pointless? - // None of the built-in functions has an ampersand in the result of - // GetEffectName(), the only strings on which this method is used. - // In fact, the example 'E&qualizer' does not exist in the code! - // Strip ampersand ('&' char) from string. This effectively removes the - // shortcut from the string ('E&qualizer' becomes 'Equalizer'). This is - // important for sorting. - static wxString StripAmpersand(const wxString& str); - - int GetAudioInCount(); - int GetAudioOutCount(); + bool shouldPrompt = true); // Realtime Effect Processing - bool RealtimeInitialize(); bool RealtimeAddProcessor(int group, int chans, float rate); - bool RealtimeFinalize(); - bool RealtimeSuspend(); - bool RealtimeResume(); - bool RealtimeProcessStart(); sampleCount RealtimeProcess(int group, int chans, float **inbuf, float **outbuf, sampleCount numSamples); - bool RealtimeProcessEnd(); bool IsRealtimeActive(); - // - // protected virtual methods - // - // Each subclass of Effect overrides one or more of these methods to - // do its processing. - // - protected: + virtual bool IsHidden(); + +// +// protected virtual methods +// +// Each subclass of Effect overrides one or more of these methods to +// do its processing. +// +protected: // Called once each time an effect is called. Perform any initialization; // make sure that the effect can be performed on the selected tracks and // return false otherwise @@ -301,8 +245,7 @@ class AUDACITY_DLL_API Effect : public EffectHostInterface // This method will not always be called (for example if a user // repeats an effect) but if it is called, it will be called // after Init. - virtual bool PromptUser(); - virtual bool PromptUser(wxWindow *parent, bool forceModal = false); + virtual bool PromptUser(wxWindow *parent = NULL, bool isBatch = false); // Check whether effect should be skipped // Typically this is only useful in automation, for example @@ -313,42 +256,28 @@ class AUDACITY_DLL_API Effect : public EffectHostInterface // Actually do the effect here. virtual bool Process(); + virtual bool ProcessPass(); + virtual bool InitPass1(); + virtual bool InitPass2(); + virtual int GetPass(); // clean up any temporary memory virtual void End(); - // - // protected data - // - // The Effect base class will set these variables, some or all of which - // may be needed by any particular subclass of Effect. - // - protected: - wxWindow *mParent; - ProgressDialog *mProgress; - double mProjectRate; // Sample rate of the project - new tracks should - // be created with this rate... - TrackFactory *mFactory; - TrackList *mTracks; // the complete list of all tracks - TrackList *mOutputTracks; // used only if CopyInputTracks() is called. - double mT0; - double mT1; -#ifdef EXPERIMENTAL_SPECTRAL_EDITING - double mF0; - double mF1; -#endif - TimeWarper *mWarper; - wxArrayString mPresetNames; - wxArrayString mPresetValues; + // Most effects just use the previewLength, but time-stretching/compressing + // effects need to use a different input length, so override this method. + virtual double CalcPreviewInputLength(double previewLength); + // The Effect class fully implements the Preview method for you. + // Only override it if you need to do preprocessing or cleanup. + virtual void Preview(bool dryOnly); + + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); + virtual bool EnableApply(bool enable = true); + virtual bool EnablePreview(bool enable = true); - // - // protected methods - // - // These methods can be used by subclasses of Effect in order to display a - // progress dialog or perform common calculations - // - protected: // The Progress methods all return true if the user has cancelled; // you should exit immediately if this happens (cleaning up memory // is okay, but don't try to undo). @@ -374,22 +303,6 @@ class AUDACITY_DLL_API Effect : public EffectHostInterface void SetTimeWarper(TimeWarper *warper); TimeWarper *GetTimeWarper(); - // - // protected static data - // - // Preferences shared by all effects - // - protected: - static double sDefaultGenerateLen; - int mFlags; - double mDuration; - - // type of the tracks on mOutputTracks - int mOutputTracksType; - - // - // private methods - // // Use these two methods to copy the input tracks to mOutputTracks, if // doing the processing on them, and replacing the originals only on success (and not cancel). void CopyInputTracks(int trackType = Track::Wave); @@ -402,6 +315,41 @@ class AUDACITY_DLL_API Effect : public EffectHostInterface // Use this to append a new output track. void AddToOutputTracks(Track *t); +// +// protected data +// +// The Effect base class will set these variables, some or all of which +// may be needed by any particular subclass of Effect. +// +protected: + ProgressDialog *mProgress; + double mProjectRate; // Sample rate of the project - new tracks should + // be created with this rate... + double mSampleRate; + TrackFactory *mFactory; + TrackList *mTracks; // the complete list of all tracks + TrackList *mOutputTracks; // used only if CopyInputTracks() is called. + double mT0; + double mT1; +#ifdef EXPERIMENTAL_SPECTRAL_EDITING + double mF0; + double mF1; +#endif + TimeWarper *mWarper; + wxArrayString mPresetNames; + wxArrayString mPresetValues; + int mPass; + + // UI + wxDialog *mUIDialog; + wxWindow *mUIParent; + + double mDuration; + sampleCount mSampleCnt; + + // type of the tracks on mOutputTracks + int mOutputTracksType; + // Used only by the base Effect class // private: @@ -410,6 +358,7 @@ class AUDACITY_DLL_API Effect : public EffectHostInterface // Driver for client effects bool ProcessTrack(int count, + ChannelNames map, WaveTrack *left, WaveTrack *right, sampleCount leftStart, @@ -421,15 +370,15 @@ class AUDACITY_DLL_API Effect : public EffectHostInterface // // Used only by the base Effect class // - private: +private: + wxWindow *mParent; + wxArrayPtrVoid mIMap; wxArrayPtrVoid mOMap; int mNumTracks; //v This is really mNumWaveTracks, per CountWaveTracks() and GetNumWaveTracks(). int mNumGroups; - int mEffectID; - // For client driver EffectClientInterface *mClient; int mNumAudioIn; @@ -452,9 +401,13 @@ class AUDACITY_DLL_API Effect : public EffectHostInterface friend class EffectManager;// so it can call PromptUser in support of batch commands. friend class EffectRack; + friend class EffectUIHost; }; -// Base dialog for generate effect + +// FIXME: +// FIXME: Remove this once all effects are using the new dialog +// FIXME: #define ID_EFFECT_PREVIEW ePreviewID @@ -465,7 +418,7 @@ public: // constructors and destructors EffectDialog(wxWindow * parent, const wxString & title, - int type = PROCESS_EFFECT, + int type = 0, int flags = wxDEFAULT_DIALOG_STYLE, int additionalButtons = 0); @@ -475,8 +428,8 @@ public: virtual bool TransferDataToWindow(); virtual bool TransferDataFromWindow(); virtual bool Validate(); - virtual void OnPreview(wxCommandEvent & event); - virtual void OnOk(wxCommandEvent & event); + virtual void OnPreview(wxCommandEvent & evt); + virtual void OnOk(wxCommandEvent & evt); private: int mType; @@ -485,8 +438,6 @@ private: DECLARE_EVENT_TABLE(); }; -WX_DECLARE_OBJARRAY(wxAcceleratorEntry, AccelArray); - // class EffectUIHost : public wxDialog, public EffectUIHostInterface @@ -498,9 +449,22 @@ public: EffectUIClientInterface *client); virtual ~EffectUIHost(); +#if defined(__WXMAC__) + virtual bool Show(bool show = true); +#endif + + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); + +#if defined(__WXMAC__) + virtual int ShowModal(); +#endif + bool Initialize(); private: + void OnErase(wxEraseEvent & evt); + void OnPaint(wxPaintEvent & evt); void OnClose(wxCloseEvent & evt); void OnApply(wxCommandEvent & evt); void OnCancel(wxCommandEvent & evt); @@ -524,6 +488,9 @@ private: wxBitmap CreateBitmap(const char *xpm[], bool up, bool pusher); void LoadUserPresets(); + void InitializeRealtime(); + void CleanupRealtime(); + private: AudacityProject *mProject; wxWindow *mParent; @@ -532,9 +499,13 @@ private: wxArrayString mUserPresets; bool mInitialized; - + bool mSupportsRealtime; bool mIsGUI; +#if defined(__WXMAC__) + bool mIsModal; +#endif + wxButton *mApplyBtn; wxButton *mCloseBtn; wxButton *mMenuBtn; @@ -560,15 +531,85 @@ private: SelectedRegion mRegion; double mPlayPos; - AccelArray mAccels; - DECLARE_EVENT_TABLE(); }; // Utility functions -float TrapFloat(float x, float min, float max); -double TrapDouble(double x, double min, double max); -long TrapLong(long x, long min, long max); +inline float TrapFloat(float x, float min, float max) +{ + if (x <= min) + return min; + + if (x >= max) + return max; + + return x; +} + +inline double TrapDouble(double x, double min, double max) +{ + if (x <= min) + return min; + + if (x >= max) + return max; + + return x; +} + +inline long TrapLong(long x, long min, long max) +{ + if (x <= min) + return min; + + if (x >= max) + return max; + + return x; +} + +// Helper macros for defining, reading and verifying effect parameters + +#define Param(name, type, key, def, min, max, scale) \ + static const wxChar * KEY_ ## name = (key); \ + static const type DEF_ ## name = (def); \ + static const type MIN_ ## name = (min); \ + static const type MAX_ ## name = (max); \ + static const type SCL_ ## name = (scale); + +#define PBasic(name, type, key, def) \ + static const wxChar * KEY_ ## name = (key); \ + static const type DEF_ ## name = (def); + +#define PRange(name, type, key, def, min, max) \ + PBasic(name, type, key, def); \ + static const type MIN_ ## name = (min); \ + static const type MAX_ ## name = (max); + +#define PScale(name, type, key, def, min, max, scale) \ + PRange(name, type, key, def, min, max); \ + static const type SCL_ ## name = (scale); + +#define ReadParam(type, name) \ + type name; \ + if (!parms.ReadAndVerify(KEY_ ## name, &name, DEF_ ## name, MIN_ ## name, MAX_ ## name)) \ + return false; + +#define ReadBasic(type, name) \ + type name; \ + if (!parms.ReadAndVerify(KEY_ ## name, &name, DEF_ ## name)) \ + return false; + +#define ReadAndVerifyEnum(name, list) \ + int name; \ + if (!parms.ReadAndVerify(KEY_ ## name, &name, DEF_ ## name, list)) \ + return false; + +#define ReadAndVerifyInt(name) ReadParam(int, name) +#define ReadAndVerifyDouble(name) ReadParam(double, name) +#define ReadAndVerifyFloat(name) ReadParam(float, name) +#define ReadAndVerifyBool(name) ReadBasic(bool, name) +#define ReadAndVerifyString(name) ReadBasic(wxString, name) #endif diff --git a/src/effects/EffectCategory.cpp b/src/effects/EffectCategory.cpp deleted file mode 100644 index d9de6a5ca..000000000 --- a/src/effects/EffectCategory.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/********************************************************************** - - Audacity: A Digital Audio Editor - - EffectCategory.cpp - - Audacity(R) is copyright (c) 1999-2008 Audacity Team. - License: GPL v2. See License.txt. - -**********************************************************************/ - -#include "../Audacity.h" - -#include "Effect.h" -#include "EffectCategory.h" - - -EffectCategory::EffectCategory(const wxString& uri, const wxString& name) - : mUri(uri), - mName(name), - mParentsFrozen(false) { - -} - - -const wxString& EffectCategory::GetUri() const { - return mUri; -} - -const wxString& EffectCategory::GetName() const { - return mName; -} - -const CategorySet& EffectCategory::GetParents() const { - return mParents; -} - -const CategorySet& EffectCategory::GetSubCategories() const { - return mSubCategories; -} - -EffectSet EffectCategory::GetEffects(int type) const { - EffectSet result; - EffectSet::const_iterator iter; - for (iter = mEffects.begin(); iter != mEffects.end(); ++iter) { - int g = (*iter)->GetEffectFlags(); - if ((g & type) == g) - result.insert(*iter); - } - return result; -} - - // Return all the effects that belong to this immediate category or any - // of its subcategories), filtered by effect type. -EffectSet EffectCategory::GetAllEffects(int type) const { - EffectSet result = GetEffects(type); - CategorySet::const_iterator iter; - for (iter = mSubCategories.begin(); iter != mSubCategories.end(); ++iter) { - EffectSet tmp = (*iter)->GetAllEffects(type); - EffectSet::const_iterator itr2; - for (itr2 = tmp.begin(); itr2 != tmp.end(); ++itr2) - result.insert(*itr2); - } - return result; -} - - -bool EffectCategory::AddParent(EffectCategory* parent) { - if (mParentsFrozen) - return false; - if (parent->IsDescendantOf(this)) - return false; - mParents.insert(parent); - parent->mSubCategories.insert(this); - return true; -} - -bool EffectCategory::AddEffect(Effect* effect) { - mEffects.insert(effect); - return true; -} - -void EffectCategory::FreezeParents() { - mParentsFrozen = true; -} - -void EffectCategory::UnfreezeParents() { - mParentsFrozen = false; -} - -bool EffectCategory::IsDescendantOf(EffectCategory* category) { - if (category == this) - return true; - CategorySet::const_iterator iter; - for (iter = mParents.begin(); iter != mParents.end(); ++iter) { - if ((*iter)->IsDescendantOf(category)) - return true; - } - return false; -} diff --git a/src/effects/EffectCategory.h b/src/effects/EffectCategory.h deleted file mode 100644 index 95ce547ce..000000000 --- a/src/effects/EffectCategory.h +++ /dev/null @@ -1,139 +0,0 @@ -/********************************************************************** - - Audacity: A Digital Audio Editor - - EffectCategory.h - - Audacity(R) is copyright (c) 1999-2008 Audacity Team. - License: GPL v2. See License.txt. - -******************************************************************//** - -\class EffectCategory -\brief EffectManager contains all the information about an effect category. - -That includes links to parent categories and subcategories, name and -URI, and of course the effects that belong to this category. - -*//*******************************************************************/ - - -#ifndef __AUDACITY_EFFECTCATEGORY__ -#define __AUDACITY_EFFECTCATEGORY__ - -#include - -#include - -#include "Effect.h" - - -class EffectCategory { - - public: - - /** This is used in the EffectSet typedef. It compares effect pointers, - first by their names and then by pointer values. This makes EffectSets - automatically sorted by name while allowing for different effects with - the same name. */ - struct CompareEffects { - bool operator()(Effect* a, Effect* b) const { - return (a->GetEffectName() < b->GetEffectName()) || - ((a->GetEffectName() == b->GetEffectName()) && (a < b)); - } - }; - - typedef std::set EffectSet; - - /** This is used in the CategorySet typedef. It compares category pointers, - first by their names and then by pointer values. This makes CategorySets - automatically sorted by name while allowing for different categories - with the same name. */ - struct CompareCategories { - bool operator()(const EffectCategory* a, const EffectCategory* b) const { - return (a->GetName() < b->GetName()) || - ((a->GetName() == b->GetName()) && (a < b)); - } - }; - - typedef std::set CategorySet; - - - /** Return the URI for this category, which is used as a global, persistent - string identifier for the category. */ - const wxString& GetUri() const; - - /** Return the (possibly i18n'd) name for the category. */ - const wxString& GetName() const; - - /** Return all the parent categories of this category. A category may - have 0, 1 or more parents - the structure of all categories forms - a DAG, not necessarily a tree, since LV2 allows for categories to - have multiple parents (e.g. Reverb is a subcategory of both Simulator - and Delay). */ - const CategorySet& GetParents() const; - - /** Return all the subcategories of this category. A category may have 0, 1 - or more subcategories. */ - const CategorySet& GetSubCategories() const; - - /** Return all the effects that belong to this immediate category (not any - of its subcategories), filtered by effect type. */ - EffectSet GetEffects(int type = ALL_EFFECTS) const; - - /** Return all the effects that belong to this immediate category or any - of its subcategories), filtered by effect type. */ - EffectSet GetAllEffects(int type = ALL_EFFECTS) const; - - protected: - - /** All constructors and destructors are non-public, allowing only - EffectManager (which is our friend) to create new categories. */ - EffectCategory(const wxString& uri, const wxString& name); - - /** Add a new parent category to this category, making this a subcategory of - the parent. Returns true if the parent was added (or already had been - added), false if the parent could not be added because FreezeParents() - has been called. This is protected so only our friend EffectManager - can change the category graph (it needs to keep track of root nodes). */ - bool AddParent(EffectCategory* parent); - - /** Add an effect to this category. This is protected so only our friend - EffectManager can call it. */ - bool AddEffect(Effect* effect); - - /** This function sets a flag that makes AddParent() fail when called for - this category object, preventing anyone from adding new parents to - this category. This is useful if you are mapping a different category - tree onto the already existing one (e.g. LRDF onto LV2) and don't - want to add more internal parent/subcategory relations between already - existing categories, but still want to allow adding of completely - new, unmapped subcategories. */ - void FreezeParents(); - - /** Reset the flag set by FreezeParents(), allowing to add parent categories - again. */ - void UnfreezeParents(); - - /** Returns true if category is an ancestor of this category, false - if not. */ - bool IsDescendantOf(EffectCategory* category); - - friend class EffectManager; - - wxString mUri; - wxString mName; - CategorySet mParents; - CategorySet mSubCategories; - EffectSet mEffects; - bool mParentsFrozen; - -}; - - -// Add these in the global namespace to reduce typing -typedef EffectCategory::EffectSet EffectSet; -typedef EffectCategory::CategorySet CategorySet; - - -#endif diff --git a/src/effects/EffectManager.cpp b/src/effects/EffectManager.cpp index 2d0c23941..2ddb3a005 100644 --- a/src/effects/EffectManager.cpp +++ b/src/effects/EffectManager.cpp @@ -37,153 +37,11 @@ EffectManager & EffectManager::Get() EffectManager::EffectManager() { -#ifdef EFFECT_CATEGORIES - mCategories = new CategoryMap(); - mRootCategories = new CategorySet(); - mUnsorted = new EffectSet(); - - // Create effect category graph. These categories and relationships - // are taken from revision 2 of lv2.ttl, loaders for other plugin systems - // (such as LADSPA/LRDF) should map their categories to these ones when - // applicable. Individual LADSPA/LRDF and LV2 plugins can add new - // categories and make them subcategories of the existing ones, but not - // add subcategory relationships between these categories. - // - // We need some persistent, global identifiers for categories - LRDF - // and LV2 uses URI strings so we do that too. The URIs here are the - // same ones as in lv2.ttl. Category identifiers in other plugin systems - // must be mapped to URIs by their loaders. - -#define LV2PREFIX "http://lv2plug.in/ns/lv2core#" - - typedef EffectCategory* CatPtr; - - CatPtr gen = AddCategory(wxT(LV2PREFIX) wxT("GeneratorPlugin"), - _("Generator")); - CatPtr inst = AddCategory(wxT(LV2PREFIX) wxT("InstrumentPlugin"), - /* i18n-hint: (noun).*/ - _("Instrument")); - CatPtr osc = AddCategory(wxT(LV2PREFIX) wxT("OscillatorPlugin"), - _("Oscillator")); - CatPtr util = AddCategory(wxT(LV2PREFIX) wxT("UtilityPlugin"), - _("Utility")); - CatPtr conv = AddCategory(wxT(LV2PREFIX) wxT("ConverterPlugin"), - _("Converter")); - CatPtr anal = AddCategory(wxT(LV2PREFIX) wxT("AnalyserPlugin"), - _("Analyser")); - CatPtr mix = AddCategory(wxT(LV2PREFIX) wxT("MixerPlugin"), - _("Mixer")); - CatPtr sim = AddCategory(wxT(LV2PREFIX) wxT("SimulatorPlugin"), - _("Simulator")); - CatPtr del = AddCategory(wxT(LV2PREFIX) wxT("DelayPlugin"), - _("Delay")); - CatPtr mod = AddCategory(wxT(LV2PREFIX) wxT("ModulatorPlugin"), - _("Modulator")); - CatPtr rev = AddCategory(wxT(LV2PREFIX) wxT("ReverbPlugin"), - _("Reverb")); - CatPtr phas = AddCategory(wxT(LV2PREFIX) wxT("PhaserPlugin"), - _("Phaser")); - CatPtr flng = AddCategory(wxT(LV2PREFIX) wxT("FlangerPlugin"), - _("Flanger")); - CatPtr chor = AddCategory(wxT(LV2PREFIX) wxT("ChorusPlugin"), - _("Chorus")); - CatPtr flt = AddCategory(wxT(LV2PREFIX) wxT("FilterPlugin"), - _("Filter")); - CatPtr lp = AddCategory(wxT(LV2PREFIX) wxT("LowpassPlugin"), - _("Lowpass")); - CatPtr bp = AddCategory(wxT(LV2PREFIX) wxT("BandpassPlugin"), - _("Bandpass")); - CatPtr hp = AddCategory(wxT(LV2PREFIX) wxT("HighpassPlugin"), - _("Highpass")); - CatPtr comb = AddCategory(wxT(LV2PREFIX) wxT("CombPlugin"), - _("Comb")); - CatPtr alp = AddCategory(wxT(LV2PREFIX) wxT("AllpassPlugin"), - _("Allpass")); - CatPtr eq = AddCategory(wxT(LV2PREFIX) wxT("EQPlugin"), - _("Equaliser")); - CatPtr peq = AddCategory(wxT(LV2PREFIX) wxT("ParaEQPlugin"), - _("Parametric")); - CatPtr meq = AddCategory(wxT(LV2PREFIX) wxT("MultiEQPlugin"), - _("Multiband")); - CatPtr spec = AddCategory(wxT(LV2PREFIX) wxT("SpectralPlugin"), - _("Spectral Processor")); - CatPtr ptch = AddCategory(wxT(LV2PREFIX) wxT("PitchPlugin"), - _("Pitch Shifter")); - CatPtr amp = AddCategory(wxT(LV2PREFIX) wxT("AmplifierPlugin"), - _("Amplifier")); - CatPtr dist = AddCategory(wxT(LV2PREFIX) wxT("DistortionPlugin"), - _("Distortion")); - CatPtr shp = AddCategory(wxT(LV2PREFIX) wxT("WaveshaperPlugin"), - _("Waveshaper")); - CatPtr dyn = AddCategory(wxT(LV2PREFIX) wxT("DynamicsPlugin"), - _("Dynamics Processor")); - CatPtr cmp = AddCategory(wxT(LV2PREFIX) wxT("CompressorPlugin"), - _("Compressor")); - CatPtr exp = AddCategory(wxT(LV2PREFIX) wxT("ExpanderPlugin"), - _("Expander")); - CatPtr lim = AddCategory(wxT(LV2PREFIX) wxT("LimiterPlugin"), - _("Limiter")); - CatPtr gate = AddCategory(wxT(LV2PREFIX) wxT("GatePlugin"), - _("Gate")); - - AddCategoryParent(inst, gen); - AddCategoryParent(osc, gen); - AddCategoryParent(conv, util); - AddCategoryParent(anal, util); - AddCategoryParent(mix, util); - AddCategoryParent(rev, sim); - AddCategoryParent(rev, del); - AddCategoryParent(phas, mod); - AddCategoryParent(flng, mod); - AddCategoryParent(chor, mod); - AddCategoryParent(lp, flt); - AddCategoryParent(bp, flt); - AddCategoryParent(hp, flt); - AddCategoryParent(comb, flt); - AddCategoryParent(alp, flt); - AddCategoryParent(eq, flt); - AddCategoryParent(peq, eq); - AddCategoryParent(meq, eq); - AddCategoryParent(ptch, spec); - AddCategoryParent(shp, dist); - AddCategoryParent(cmp, dyn); - AddCategoryParent(exp, dyn); - AddCategoryParent(lim, dyn); - AddCategoryParent(gate, dyn); - // We also add a couple of categories for internal use. These are not - // in lv2.ttl. - -#define ATEAM "http://audacityteam.org/namespace#" - - CatPtr nrm = AddCategory(wxT(ATEAM) wxT("NoiseRemoval"), - _("Noise Removal")); - CatPtr pnt = AddCategory(wxT(ATEAM) wxT("PitchAndTempo"), - _("Pitch and Tempo")); - CatPtr tim = AddCategory(wxT(ATEAM) wxT("TimelineChanger"), - _("Timeline Changer")); - CatPtr aTim = AddCategory(wxT(ATEAM) wxT("TimeAnalyser"), - _("Time")); - CatPtr onst = AddCategory(wxT(ATEAM) wxT("OnsetDetector"), - _("Onsets")); - AddCategoryParent(nrm, util); - AddCategoryParent(tim, util); - AddCategoryParent(aTim, anal); - AddCategoryParent(onst, aTim); - - // We freeze the internal subcategory relations between the categories - // added so far so LADSPA/LRDF or other category systems don't ruin - // our hierarchy. - FreezeCategories(); - -#endif - -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) mRealtimeLock.Enter(); mRealtimeActive = false; mRealtimeSuspended = true; mRealtimeLatency = 0; mRealtimeLock.Leave(); -#endif #if defined(EXPERIMENTAL_EFFECTS_RACK) mRack = NULL; @@ -192,90 +50,35 @@ EffectManager::EffectManager() EffectManager::~EffectManager() { -#ifdef EFFECT_CATEGORIES - CategoryMap::iterator i; - for (i = mCategories->begin(); i != mCategories->end(); ++i) - delete i->second; - - delete mUnsorted; - delete mRootCategories; - delete mCategories; -#endif - #if defined(EXPERIMENTAL_EFFECTS_RACK) // wxWidgets has already destroyed the rack since it was derived from wxFrame. So // no need to delete it here. #endif - EffectMap::iterator iter = mEffects.begin(); - while (iter != mEffects.end()) + EffectMap::iterator iter = mHostEffects.begin(); + while (iter != mHostEffects.end()) { delete iter->second; iter++; } } -void EffectManager::RegisterEffect(ModuleInterface *p, Effect *f, int NewFlags) +// Here solely for the purpose of Nyquist Workbench until +// a better solution is devised. +void EffectManager::RegisterEffect(Effect *f) { - f->SetEffectID(mNumEffects++); - - if( NewFlags != 0) - { - f->SetEffectFlags( NewFlags ); - } - - mEffects[PluginManager::Get().RegisterEffectPlugin(p, f)] = f; -} - -void EffectManager::RegisterEffect(Effect *f, int NewFlags) -{ - f->SetEffectID(mNumEffects++); - - if( NewFlags != 0) - { - f->SetEffectFlags( NewFlags ); - } - // This will go away after all effects have been converted - mEffects[PluginManager::Get().RegisterLegacyEffectPlugin(f)] = f; - -#ifdef EFFECT_CATEGORIES - // Add the effect in the right categories - std::set catUris = f->GetEffectCategories(); - bool oneValid = false; - std::set::const_iterator iter; - for (iter = catUris.begin(); iter != catUris.end(); ++iter) { - EffectCategory* cat = LookupCategory(*iter); - if (cat != 0) { - cat->AddEffect(f); - oneValid = true; - } - } - if (!oneValid) - mUnsorted->insert(f); - -#endif -} - -void EffectManager::UnregisterEffects() -{ -#ifdef EFFECT_CATEGORIES - mUnsorted->clear(); - - CategoryMap::iterator iter; - for (iter = mCategories->begin(); iter != mCategories->end(); ++iter) - iter->second->mEffects.clear(); -#endif + mEffects[PluginManager::Get().RegisterPlugin(f)] = f; } bool EffectManager::DoEffect(const PluginID & ID, wxWindow *parent, - int flags, double projectRate, TrackList *list, TrackFactory *factory, SelectedRegion *selectedRegion, - wxString params) + bool shouldPrompt /* = true */) + { Effect *effect = GetEffect(ID); @@ -284,7 +87,7 @@ bool EffectManager::DoEffect(const PluginID & ID, return false; } -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) && defined(EXPERIMENTAL_EFFECTS_RACK) +#if defined(EXPERIMENTAL_EFFECTS_RACK) if (effect->SupportsRealtime()) { GetRack()->Add(effect); @@ -292,12 +95,11 @@ bool EffectManager::DoEffect(const PluginID & ID, #endif return effect->DoEffect(parent, - flags, projectRate, list, factory, selectedRegion, - params); + shouldPrompt); } wxString EffectManager::GetEffectName(const PluginID & ID) @@ -337,7 +139,7 @@ wxString EffectManager::GetEffectDescription(const PluginID & ID) if (effect) { - return effect->GetEffectDescription(); + return wxString::Format(_("Applied effect: %s"), effect->GetName().c_str()); } return wxEmptyString; @@ -484,7 +286,6 @@ void EffectManager::RealtimeSetEffects(const EffectArray & effects) } #endif -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) bool EffectManager::RealtimeIsActive() { return mRealtimeEffects.GetCount() != 0; @@ -538,8 +339,6 @@ void EffectManager::RealtimeRemoveEffect(Effect *effect) RealtimeResume(); } -#endif - void EffectManager::RealtimeInitialize() { // The audio thread should not be running yet, but protect anyway @@ -772,29 +571,31 @@ int EffectManager::GetRealtimeLatency() Effect *EffectManager::GetEffect(const PluginID & ID) { - Effect *effect; - // TODO: This is temporary and should be redone when all effects are converted if (mEffects.find(ID) == mEffects.end()) { + Effect *effect; + + // This will instantiate the effect client if it hasn't already been done EffectIdentInterface *ident = dynamic_cast(PluginManager::Get().GetInstance(ID)); if (ident && ident->IsLegacy()) { effect = dynamic_cast(ident); - effect->SetEffectID(mNumEffects++); - mEffects[ID] = effect; - return effect; + if (effect && effect->Startup(NULL)) + { + mEffects[ID] = effect; + return effect; + } } effect = new Effect(); if (effect) { - // This will instantiate the effect client if it hasn't already been done EffectClientInterface *client = dynamic_cast(ident); if (client && effect->Startup(client)) { - effect->SetEffectID(mNumEffects++); mEffects[ID] = effect; + mHostEffects[ID] = effect; return effect; } @@ -832,63 +633,3 @@ const PluginID & EffectManager::GetEffectByIdentifier(const wxString & strTarget return empty;; } - -#ifdef EFFECT_CATEGORIES - -EffectCategory* EffectManager::AddCategory(const wxString& URI, - const wxString& name) { - - CategoryMap::const_iterator iter = mCategories->find(URI); - if (iter != mCategories->end()) - return iter->second; - EffectCategory* cat = new EffectCategory(URI, name); - mCategories->insert(std::make_pair(URI, cat)); - mRootCategories->insert(cat); - return cat; -} - -EffectCategory* EffectManager::LookupCategory(const wxString& URI) { - CategoryMap::const_iterator iter = mCategories->find(URI); - if (iter != mCategories->end()) - return iter->second; - return 0; -} - -bool EffectManager::AddCategoryParent(EffectCategory* child, - EffectCategory* parent) { - bool result = child->AddParent(parent); - if (!result) - return false; - CategorySet::iterator iter = mRootCategories->find(child); - if (iter != mRootCategories->end()) - mRootCategories->erase(iter); - return true; -} - -void EffectManager::FreezeCategories() { - CategoryMap::iterator iter; - for (iter = mCategories->begin(); iter != mCategories->end(); ++iter) - iter->second->FreezeParents(); -} - -const CategorySet& EffectManager::GetRootCategories() const { - return *mRootCategories; -} - -EffectSet EffectManager::GetUnsortedEffects(int flags) const { - - if (flags == ALL_EFFECTS) - return *mUnsorted; - - EffectSet result; - EffectSet::const_iterator iter; - for (iter = mUnsorted->begin(); iter != mUnsorted->end(); ++iter) { - int g = (*iter)->GetEffectFlags(); - if ((flags & g) == g) - result.insert(*iter); - } - - return result; -} - -#endif diff --git a/src/effects/EffectManager.h b/src/effects/EffectManager.h index 4c2813889..501af1bb0 100644 --- a/src/effects/EffectManager.h +++ b/src/effects/EffectManager.h @@ -21,18 +21,10 @@ effects. #ifndef __AUDACITY_EFFECTMANAGER__ #define __AUDACITY_EFFECTMANAGER__ -#include -#include -#include - #include "audacity/EffectInterface.h" #include "../PluginManager.h" #include "Effect.h" -#ifdef EFFECT_CATEGORIES -#include "EffectCategory.h" -#endif - WX_DEFINE_USER_EXPORTED_ARRAY(Effect *, EffectArray, class AUDACITY_DLL_API); WX_DECLARE_STRING_HASH_MAP_WITH_DECL(Effect *, EffectMap, class AUDACITY_DLL_API); @@ -42,35 +34,26 @@ class EffectRack; class AUDACITY_DLL_API EffectManager { -#if defined(EXPERIMENTAL_EFFECTS_RACK) - friend class EffectRack; -#endif - - public: +public: /** Get the singleton instance of the EffectManager. Probably not safe for multi-thread use. */ - static EffectManager& Get(); - - // - // public methods - // - // Used by the outside program to register the list of effects and retrieve - // them by index number, usually when the user selects one from a menu. - // - public: + static EffectManager & Get(); +// +// public methods +// +// Used by the outside program to register the list of effects and retrieve +// them by index number, usually when the user selects one from a menu. +// +public: EffectManager(); + virtual ~EffectManager(); - /** A destructor is needed so we can delete all categories. */ - ~EffectManager(); - - /** Register an effect so it will appear in the menu. */ - void RegisterEffect(Effect *f, int AdditionalFlags=0); - void RegisterEffect(ModuleInterface *p, Effect *f, int AdditionalFlags=0); - - /** Unregister all effects. */ - void UnregisterEffects(); + /** Register an effect so it can be executed. */ + // Here solely for the purpose of Nyquist Workbench until + // a better solution is devised. + void RegisterEffect(Effect *f); /** Run an effect given the plugin ID */ // Returns true on success. Will only operate on tracks that @@ -78,12 +61,11 @@ class AUDACITY_DLL_API EffectManager // Audacity's standard UI. bool DoEffect(const PluginID & ID, wxWindow *parent, - int flags, double projectRate, TrackList *list, TrackFactory *factory, SelectedRegion *selectedRegion, - wxString params); + bool shouldPrompt = true); wxString GetEffectName(const PluginID & ID); wxString GetEffectIdentifier(const PluginID & ID); @@ -95,7 +77,6 @@ class AUDACITY_DLL_API EffectManager bool SetEffectParameters(const PluginID & ID, const wxString & params); bool PromptUser(const PluginID & ID, wxWindow *parent); -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) // Realtime effect processing bool RealtimeIsActive(); bool RealtimeIsSuspended(); @@ -111,7 +92,6 @@ class AUDACITY_DLL_API EffectManager sampleCount RealtimeProcess(int group, int chans, float **buffers, sampleCount numSamples); void RealtimeProcessEnd(); int GetRealtimeLatency(); -#endif #if defined(EXPERIMENTAL_EFFECTS_RACK) void ShowRack(); @@ -119,37 +99,7 @@ class AUDACITY_DLL_API EffectManager const PluginID & GetEffectByIdentifier(const wxString & strTarget); -#ifdef EFFECT_CATEGORIES - - /** Add a new effect category with the given URI and name and - return a pointer to it. If a category with this URI already - exists, return that instead. */ - EffectCategory* AddCategory(const wxString& URI, - const wxString& name); - - /** Return a pointer to the effect category with the given URI - or 0 if no such category has been added. */ - EffectCategory* LookupCategory(const wxString& URI); - - /** Make one category the parent of another category. Both categories - must have been returned from AddCategory() or LookupCategory(). - If the new parent-child relationship would create any loops - in the graph of categories false will be returned and the graph - will not be modified, otherwise the function will return true. */ - bool AddCategoryParent(EffectCategory* child, EffectCategory* parent); - - /** Freeze the subcategory relations between all categories added so far. */ - void FreezeCategories(); - - /** Return the set of all root categories, i.e. the ones without parents. */ - const CategorySet& GetRootCategories() const; - - /** Return the set of all uncategorised effects. */ - EffectSet GetUnsortedEffects(int flags = ALL_EFFECTS) const; - -#endif - - private: +private: /** Return an effect by its ID. */ Effect *GetEffect(const PluginID & ID); @@ -159,14 +109,10 @@ class AUDACITY_DLL_API EffectManager private: EffectMap mEffects; + EffectMap mHostEffects; int mNumEffects; -#if defined(EXPERIMENTAL_EFFECTS_RACK) - EffectRack *mRack; -#endif - -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) wxCriticalSection mRealtimeLock; EffectArray mRealtimeEffects; int mRealtimeLatency; @@ -174,20 +120,11 @@ private: bool mRealtimeActive; wxArrayInt mRealtimeChans; wxArrayDouble mRealtimeRates; -#endif -#ifdef EFFECT_CATEGORIES - // This maps URIs to EffectCategory pointers for all added categories. - // It is needed for fast lookup and easy deletion. - typedef std::map CategoryMap; - CategoryMap *mCategories; +#if defined(EXPERIMENTAL_EFFECTS_RACK) + EffectRack *mRack; - // These are the root categories, i.e. the ones without parents. - CategorySet *mRootCategories; - - // Special category that all effects with unknown category URIs - // are placed in. - EffectSet *mUnsorted; + friend class EffectRack; #endif }; diff --git a/src/effects/Equalization.cpp b/src/effects/Equalization.cpp index 3504557d8..ef04ae6dd 100644 --- a/src/effects/Equalization.cpp +++ b/src/effects/Equalization.cpp @@ -32,11 +32,6 @@ Clone of the FFT Filter effect, no longer part of Audacity. -*//****************************************************************//** - - \class EqualizationDialog - \brief Dialog used with EffectEqualization - *//****************************************************************//** \class EqualizationPanel @@ -58,25 +53,8 @@ #include "../Audacity.h" -#include "../Experimental.h" -#include "Equalization.h" -#include "../AColor.h" -#include "../ShuttleGui.h" -#include "../PlatformCompatibility.h" -#include "../FileNames.h" -#include "../Envelope.h" -#include "../widgets/LinkingHtmlWindow.h" -#include "../widgets/ErrorDialog.h" -#include "../FFT.h" -#include "../Prefs.h" -#include "../Project.h" -#include "../WaveTrack.h" -#include "../widgets/Ruler.h" -#include "../xml/XMLFileReader.h" -#include "../Theme.h" -#include "../AllThemeResources.h" -#include "../WaveTrack.h" -#include "../float_cast.h" + +#include #include #include @@ -99,144 +77,137 @@ #include #include #include +#include +#include + +#include "../Experimental.h" +#include "../AColor.h" +#include "../ShuttleGui.h" +#include "../PlatformCompatibility.h" +#include "../FileNames.h" +#include "../Envelope.h" +#include "../widgets/LinkingHtmlWindow.h" +#include "../widgets/ErrorDialog.h" +#include "../FFT.h" +#include "../Prefs.h" +#include "../Project.h" +#include "../WaveTrack.h" +#include "../widgets/Ruler.h" +#include "../xml/XMLFileReader.h" +#include "../Theme.h" +#include "../AllThemeResources.h" +#include "../WaveTrack.h" +#include "../float_cast.h" + +#include "Equalization.h" #ifdef EXPERIMENTAL_EQ_SSE_THREADED #include "Equalization48x.h" #endif -#if wxUSE_TOOLTIPS -#include + +enum +{ + ID_Length = 10000, + ID_dBMax, + ID_dBMin, + ID_Clear, + ID_Invert, + ID_Draw, + ID_Graphic, + ID_Interp, + ID_Linear, + ID_Grid, + ID_Curve, + ID_Manage, + ID_Delete, +#ifdef EXPERIMENTAL_EQ_SSE_THREADED + ID_DefaultMath, + ID_SSE, + ID_SSEThreaded, + ID_AVX, + ID_AVXThreaded, + ID_Bench, #endif -#include + ID_Slider, // needs to come last +}; -#include +enum kInterpolations +{ + kBspline, + kCosine, + kCubic, + kNumInterpolations +}; -#include +static const wxString kInterpStrings[kNumInterpolations] = +{ + /* i18n-hint: Technical term for a kind of curve.*/ + wxTRANSLATE("B-spline"), + wxTRANSLATE("Cosine"), + wxTRANSLATE("Cubic") +}; -WX_DEFINE_OBJARRAY( EQPointArray ); -WX_DEFINE_OBJARRAY( EQCurveArray ); - -const double EqualizationDialog::thirdOct[] = +static const double kThirdOct[] = { 20., 25., 31., 40., 50., 63., 80., 100., 125., 160., 200., 250., 315., 400., 500., 630., 800., 1000., 1250., 1600., 2000., 2500., 3150., 4000., 5000., 6300., 8000., 10000., 12500., 16000., 20000., }; -// LLL: This seem to have a strange interaction with wxWidgets 2.8.9. It's -// possible that the wxSlider was fixed as it seems to work ok for me -// under Ubuntu 8.10 with self build wxWidgets 2.8.9. +// Define keys, defaults, minimums, and maximums for the effect parameters // -// Leaving here for now...just disabling -#ifdef __WXGTK__disabled -/* -* wxSlider exhibits strange behaviour on wxGTK when wxSL_INVERSE and/or -* negative scale values are used. This affects at least SUSE 9.3 with -* self-compiled wxWidgets 2.6.2 and Kubuntu 6.06. This class is used -* to transparently work around those problems. Use wxSliderBugfix wherever -* you would have used a slider with wxSL_INVERSE and/or negative scale -* values. -* -* The equalization class uses wxSliderBugfix everywhere even when not -* strictly needed. This is important because we override some wxSlider -* functions which are not virtual, and so we need to cast to wxSliderBugfix -* instead of to wxSlider to allow the right function to be called statically. -*/ -class wxSliderBugfix : public wxSlider -{ -public: - wxSliderBugfix(wxWindow* parent, wxWindowID id, int value, - int minValue, int maxValue, - const wxPoint& point = wxDefaultPosition, - const wxSize& size = wxDefaultSize, - long style = wxSL_HORIZONTAL, - const wxValidator& validator = wxDefaultValidator, - const wxString& name = wxT("slider")) : - wxSlider(parent, id, 0, 0, maxValue - minValue, point, - size, style & ~wxSL_INVERSE, validator, name) { - isInverse = (style & wxSL_INVERSE) != 0; - originalMinValue = minValue; - SetValue(value); - } +// Name Type Key Def Min Max Scale +Param( FilterLength, int, wxTRANSLATE("FilterLength"), 4001, 21, 8191, 0 ); +Param( CurveName, wxChar*, wxTRANSLATE("CurveName"), wxT("unnamed"), wxT(""), wxT(""), wxT("")); +Param( InterpLin, bool, wxTRANSLATE("InterpolateLin"), false, false, true, false ); +Param( InterpMeth, int, wxTRANSLATE("InterpolationMethod"), 0, 0, 0, 0 ); +Param( DrawMode, bool, wxT(""), true, false, true, false ); +Param( DrawGrid, bool, wxT(""), true, false, true, false ); +Param( dBMin, float, wxT(""), -30.0, -120.0, -10.0, 0 ); +Param( dBMax, float, wxT(""), 30.0, 0.0, 60.0, 0 ); - int GetMin() const { - return originalMinValue; - } +#include +WX_DEFINE_OBJARRAY( EQPointArray ); +WX_DEFINE_OBJARRAY( EQCurveArray ); - int GetMax() const { - return wxSlider::GetMax() + originalMinValue; - } +///---------------------------------------------------------------------------- +// EffectEqualization +//---------------------------------------------------------------------------- - int GetValue() const { - if (isInverse) - return wxSlider::GetMax() - wxSlider::GetValue() + originalMinValue; - else - return wxSlider::GetValue() + originalMinValue; - } +BEGIN_EVENT_TABLE(EffectEqualization, wxEvtHandler) + EVT_SIZE( EffectEqualization::OnSize ) + EVT_PAINT( EffectEqualization::OnPaint ) + EVT_ERASE_BACKGROUND( EffectEqualization::OnErase ) - void SetValue(int value) { - if (isInverse) - wxSlider::SetValue(wxSlider::GetMax() - value + originalMinValue); - else - wxSlider::SetValue(value - originalMinValue); - } + EVT_SLIDER( ID_Length, EffectEqualization::OnSliderM ) + EVT_SLIDER( ID_dBMax, EffectEqualization::OnSliderDBMAX ) + EVT_SLIDER( ID_dBMin, EffectEqualization::OnSliderDBMIN ) + EVT_COMMAND_RANGE(ID_Slider, + ID_Slider + NUMBER_OF_BANDS - 1, + wxEVT_COMMAND_SLIDER_UPDATED, + EffectEqualization::OnSlider) + EVT_CHOICE( ID_Interp, EffectEqualization::OnInterp ) -private: - bool isInverse; - int originalMinValue; -}; + EVT_CHOICE( ID_Curve, EffectEqualization::OnCurve ) + EVT_BUTTON( ID_Manage, EffectEqualization::OnManage ) + EVT_BUTTON( ID_Clear, EffectEqualization::OnClear ) + EVT_BUTTON( ID_Invert, EffectEqualization::OnInvert ) -#else - -/* -* On platforms other than wxGTK, the workaround is not needed -*/ -#define wxSliderBugfix wxSlider - -#endif // ifdef __WXGTK__ - -#define NUM_INTERP_CHOICES 3 -static wxString interpChoiceStrings[NUM_INTERP_CHOICES]; - -void EffectEqualization::ReadPrefs() -{ - - gPrefs->Read(wxT("/Effects/Equalization/FilterLength"), - &mM, 4001); - if ((mM < 21) || (mM > 8191)) { // corrupted Prefs? - mM = 4001; //default - gPrefs->Write(wxT("/Effects/Equalization/FilterLength"), mM); - } - gPrefs->Read(wxT("/Effects/Equalization/CurveName"), &mCurveName, wxT("unnamed")); - gPrefs->Read(wxT("/Effects/Equalization/Lin"), &mLin, false); - gPrefs->Read(wxT("/Effects/Equalization/dBMin"), &mdBMin, -30.0); - if ((mdBMin < -120) || (mdBMin > -10)) { // corrupted Prefs? - mdBMin = -30; //default - gPrefs->Write(wxT("/Effects/Equalization/FilterLength"), mdBMin); - } - gPrefs->Read(wxT("/Effects/Equalization/dBMax"), &mdBMax, 30.); - if ((mdBMax < 0) || (mdBMax > 60)) { // corrupted Prefs? - mdBMax = 30; //default - gPrefs->Write(wxT("/Effects/Equalization/FilterLength"), mdBMax); - } - gPrefs->Read(wxT("/Effects/Equalization/DrawMode"), &mDrawMode, true); - gPrefs->Read(wxT("/Effects/Equalization/Interp"), &mInterp, 0); - gPrefs->Read(wxT("/Effects/Equalization/DrawGrid"), &mDrawGrid, true); + EVT_RADIOBUTTON(ID_Draw, EffectEqualization::OnDrawMode) + EVT_RADIOBUTTON(ID_Graphic, EffectEqualization::OnGraphicMode) + EVT_CHECKBOX(ID_Linear, EffectEqualization::OnLinFreq) + EVT_CHECKBOX(ID_Grid, EffectEqualization::OnGridOnOff) #ifdef EXPERIMENTAL_EQ_SSE_THREADED - bool useSSE; - gPrefs->Read(wxT("SSE/GUI"), &useSSE); - if(useSSE && !mEffectEqualization48x) - mEffectEqualization48x=new EffectEqualization48x; - else - if(!useSSE && mEffectEqualization48x) { - delete mEffectEqualization48x; - mEffectEqualization48x=NULL; - } - mBench=false; + EVT_RADIOBUTTON(ID_DefaultMath, EffectEqualization::OnProcessingRadio) + EVT_RADIOBUTTON(ID_SSE, EffectEqualization::OnProcessingRadio) + EVT_RADIOBUTTON(ID_SSEThreaded, EffectEqualization::OnProcessingRadio) + EVT_RADIOBUTTON(ID_AVX, EffectEqualization::OnProcessingRadio) + EVT_RADIOBUTTON(ID_AVXThreaded, EffectEqualization::OnProcessingRadio) + EVT_BUTTON(ID_Bench, EffectEqualization::OnBench) #endif - gPrefs->Flush(); -} +END_EVENT_TABLE() EffectEqualization::EffectEqualization() { @@ -249,19 +220,82 @@ EffectEqualization::EffectEqualization() mEffectEqualization48x=NULL; #endif - ReadPrefs(); + mM = DEF_FilterLength; + mLin = DEF_InterpLin; + mDrawMode = DEF_DrawMode; + mDrawGrid = DEF_DrawGrid; + mInterp = DEF_InterpMeth; + mdBMin = DEF_dBMin; + mdBMax = DEF_dBMax; + mCurveName = DEF_CurveName; - mPrompting = false; + for (int i = 0; i < kNumInterpolations; i++) + { + mInterpolations.Add(wxGetTranslation(kInterpStrings[i])); + } - /* i18n-hint: Technical term for a kind of curve.*/ - interpChoiceStrings[0] = _("B-spline"); - interpChoiceStrings[1] = _("Cosine"); - interpChoiceStrings[2] = _("Cubic"); + mLogEnvelope = new Envelope(); + mLogEnvelope->SetInterpolateDB(false); + mLogEnvelope->Mirror(false); + mLogEnvelope->SetRange(MIN_dBMin, MAX_dBMax); // MB: this is the highest possible range + + mLinEnvelope = new Envelope(); + mLinEnvelope->SetInterpolateDB(false); + mLinEnvelope->Mirror(false); + mLinEnvelope->SetRange(MIN_dBMin, MAX_dBMax); // MB: this is the highest possible range + + mEnvelope = (mLin ? mLinEnvelope : mLogEnvelope); + + mWindowSize = windowSize; + + mCurve = NULL; + mDirty = false; + + // Load the EQ curves + LoadCurves(); + if (mDisallowCustom) + { + mCustomBackup.Name = wxT("unnamed"); + EQCurve &realCustom = mCurves[mCurves.GetCount()-1]; + wxASSERT(realCustom.Name.IsSameAs(wxT("unnamed"))); + mCustomBackup.points = realCustom.points; + } + + // Note: initial curve is set in TransferDataToWindow + + mBandsInUse = NUMBER_OF_BANDS; + //double loLog = log10(mLoFreq); + //double stepLog = (log10(mHiFreq) - loLog)/((double)NUM_PTS-1.); + for(int i=0; i= kNumInterpolations) + { + InterpMeth -= kNumInterpolations; + } + + mEnvelope = (mLin ? mLinEnvelope : mLogEnvelope); + + return true; +} + +// EffectUIClientInterface implementation + +bool EffectEqualization::ValidateUI() +{ + // If editing a batch chain, we don't want to be using the unnamed curve so + // we offer to save it. + while (mDisallowCustom && mCurveName.IsSameAs(wxT("unnamed"))) + { + wxMessageBox(_("To use this EQ curve in a batch chain, please choose a new name for it.\nChoose the 'Save/Manage Curves...' button and rename the 'unnamed' curve, then use that one."), + _("EQ Curve needs a different name"), + wxOK | wxCENTRE, + mUIParent); + return false; + } + + SetPrivateConfig(GetCurrentSettingsGroup(), wxT("dBMin"), mdBMin); + SetPrivateConfig(GetCurrentSettingsGroup(), wxT("dBMax"), mdBMax); + SetPrivateConfig(GetCurrentSettingsGroup(), wxT("DrawMode"), mDrawMode); + SetPrivateConfig(GetCurrentSettingsGroup(), wxT("DrawGrid"), mDrawGrid); + + return true; +} + +// Effect implementation + +bool EffectEqualization::Startup() +{ + wxString base = wxT("/Effects/Equalization/"); + + // Migrate settings from 2.1.0 or before + + // Already migrated, so bail + if (gPrefs->Exists(base + wxT("Migrated"))) + { + return true; + } + + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + // These get saved to the current preset + gPrefs->Read(base + wxT("FilterLength"), &mM, 4001); + if ((mM < 21) || (mM > 8191)) { // corrupted Prefs? + mM = 4001; //default + } + gPrefs->Read(base + wxT("CurveName"), &mCurveName, wxT("unnamed")); + gPrefs->Read(base + wxT("Lin"), &mLin, false); + gPrefs->Read(base + wxT("Interp"), &mInterp, 0); + + SaveUserPreset(GetCurrentSettingsGroup()); + + // These persist across preset changes + double dBMin; + gPrefs->Read(base + wxT("dBMin"), &dBMin, -30.0); + if ((dBMin < -120) || (dBMin > -10)) { // corrupted Prefs? + dBMin = -30; //default + } + mdBMin = dBMin; + SetPrivateConfig(GetCurrentSettingsGroup(), wxT("dBMin"), mdBMin); + + double dBMax; + gPrefs->Read(base + wxT("dBMax"), &dBMax, 30.); + if ((dBMax < 0) || (dBMax > 60)) { // corrupted Prefs? + dBMax = 30; //default + } + mdBMax = dBMax; + SetPrivateConfig(GetCurrentSettingsGroup(), wxT("dBMax"), mdBMax); + + gPrefs->Read(base + wxT("DrawMode"), &mDrawMode, true); + SetPrivateConfig(GetCurrentSettingsGroup(), wxT("DrawMode"), mDrawMode); + + gPrefs->Read(base + wxT("DrawGrid"), &mDrawGrid, true); + SetPrivateConfig(GetCurrentSettingsGroup(), wxT("DrawGrid"), mDrawGrid); + + // Do not migrate again + gPrefs->Write(base + wxT("Migrated"), true); + gPrefs->Flush(); + } + + return true; +} + bool EffectEqualization::Init() { int selcount = 0; @@ -302,108 +479,24 @@ bool EffectEqualization::Init() } t = iter.Next(); } + + mHiFreq = rate / 2.0; + mLoFreq = loFreqI; + + mBandsInUse = 0; + while (kThirdOct[mBandsInUse] <= mHiFreq) { + mBandsInUse++; + if (mBandsInUse == NUMBER_OF_BANDS) + break; + } + + mEnvelope = (mLin ? mLinEnvelope : mLogEnvelope); + + CalcFilter(); + return(true); } -bool EffectEqualization::PromptUser() -{ - // Detect whether we are editing a batch chain by checking the parent window - mEditingBatchParams = (mParent != GetActiveProject()); - if (!mEditingBatchParams) - { - ReadPrefs(); - } - - TrackListOfKindIterator iter(Track::Wave, mTracks); - WaveTrack *t = (WaveTrack *) iter.First(); - float hiFreq; - if (t) - hiFreq = ((float)(t->GetRate())/2.); - else - hiFreq = ((float)(GetActiveProject()->GetRate())/2.); - - - EqualizationDialog dlog(this, ((double)loFreqI), hiFreq, mFilterFuncR, mFilterFuncI, - windowSize, mCurveName, mEditingBatchParams, mParent, -1, _("Equalization")); - - dlog.M = mM; - dlog.curveName = mCurveName; - dlog.linCheck = mLin; - dlog.dBMin = mdBMin; - dlog.dBMax = mdBMax; - dlog.drawMode = mDrawMode; - dlog.interp = mInterp; - dlog.drawGrid = mDrawGrid; - dlog.CentreOnParent(); - - mPrompting = true; // true when previewing, false in batch - dlog.ShowModal(); - mPrompting = false; - - if (!dlog.GetReturnCode()) - return false; - - mM = dlog.M; - mCurveName = dlog.curveName; - mLin = dlog.linCheck; - mdBMin = dlog.dBMin; - mdBMax = dlog.dBMax; - mDrawMode = dlog.drawMode; - mInterp = dlog.interp; - mDrawGrid = dlog.drawGrid; - - if (!mEditingBatchParams) - { - gPrefs->Write(wxT("/Effects/Equalization/FilterLength"),mM); - gPrefs->Write(wxT("/Effects/Equalization/CurveName"),mCurveName); - gPrefs->Write(wxT("/Effects/Equalization/Lin"),mLin); - gPrefs->Write(wxT("/Effects/Equalization/dBMin"),mdBMin); - gPrefs->Write(wxT("/Effects/Equalization/dBMax"),mdBMax); - gPrefs->Write(wxT("/Effects/Equalization/DrawMode"),mDrawMode); - gPrefs->Write(wxT("/Effects/Equalization/Interp"), mInterp); - gPrefs->Write(wxT("/Effects/Equalization/DrawGrid"), mDrawGrid); - gPrefs->Flush(); - } - - return true; -} - -bool EffectEqualization::DontPromptUser() -{ - TrackListOfKindIterator iter(Track::Wave, mTracks); - WaveTrack *t = (WaveTrack *) iter.First(); - float hiFreq; - if (t) - hiFreq = ((float)(t->GetRate())/2.); - else - hiFreq = ((float)(GetActiveProject()->GetRate())/2.); - - EqualizationDialog dlog(this, ((double)loFreqI), hiFreq, mFilterFuncR, mFilterFuncI, - windowSize, mCurveName, false, NULL, -1, _("Equalization")); - - dlog.M = mM; - dlog.curveName = mCurveName; - dlog.linCheck = false; - dlog.interp = mInterp; - // IS required here - no call to ShowModal! - dlog.TransferDataToWindow(); - dlog.CalcFilter(); // is needed here - the one done due to TransferDataToWindow->OnLinFreq isn't reliable - - return true; -} - -bool EffectEqualization::TransferParameters( Shuttle & shuttle ) -{ - shuttle.TransferInt(wxT("FilterLength"),mM,4001); - shuttle.TransferString(wxT("CurveName"),mCurveName,wxT("eric")); - shuttle.TransferBool(wxT("InterpolateLin"),mLin,false); - shuttle.TransferEnum(wxT("interpolationMethod"),mInterp, NUM_INTERP_CHOICES,interpChoiceStrings); - - if(!mPrompting) - DontPromptUser(); // not previewing, ie batch mode or initial setup - return true; -} - bool EffectEqualization::Process() { #ifdef EXPERIMENTAL_EQ_SSE_THREADED @@ -446,6 +539,437 @@ bool EffectEqualization::Process() return bGoodResult; } +bool EffectEqualization::PopulateUI(wxWindow *parent) +{ + mUIParent = parent; + mUIParent->PushEventHandler(this); + + LoadUserPreset(GetCurrentSettingsGroup()); + + ShuttleGui S(mUIParent, eIsCreating); + PopulateOrExchange(S); + + return true; +} + +bool EffectEqualization::CloseUI() +{ + mUIParent->RemoveEventHandler(this); + + mUIParent = NULL; + + return true; +} + +void EffectEqualization::PopulateOrExchange(ShuttleGui & S) +{ + wxWindow *parent = S.GetParent(); + + wxStaticText *txt; + wxButton *btn; + + // Create the base sizer + szrV = new wxBoxSizer( wxVERTICAL ); + szrV->AddSpacer(10); + + // ------------------------------------------------------------------- + // EQ panel and sliders for vertical scale + // ------------------------------------------------------------------- + szr1 = new wxFlexGridSizer( 4, 0, 0 ); + szr1->AddGrowableCol( 2, 0 ); + szr1->AddGrowableRow( 0, 0 ); + szr1->SetFlexibleDirection( wxBOTH ); + + szr2 = new wxBoxSizer( wxVERTICAL ); + mdBMaxSlider = new wxSlider(parent, ID_dBMax, DEF_dBMax, MIN_dBMax, MAX_dBMax, + wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE); + szr2->Add( mdBMaxSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 ); + mdBMinSlider = new wxSlider(parent, ID_dBMin, DEF_dBMin, MIN_dBMin, MAX_dBMin, + wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE); + szr2->Add( mdBMinSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 ); + szr1->Add( szr2, 0, wxEXPAND|wxALIGN_CENTRE|wxALL, 4 ); + +#if wxUSE_ACCESSIBILITY + mdBMaxSlider->SetName(_("Max dB")); + mdBMaxSlider->SetAccessible(new SliderAx(mdBMaxSlider, wxString(wxT("%d ")) + _("dB"))); + mdBMinSlider->SetName(_("Min dB")); + mdBMinSlider->SetAccessible(new SliderAx(mdBMinSlider, wxString(wxT("%d ")) + _("dB"))); +#endif + + mdBRuler = new RulerPanel(parent, wxID_ANY); + mdBRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes + mdBRuler->ruler.SetOrientation(wxVERTICAL); + mdBRuler->ruler.SetRange(60.0, -120.0); + mdBRuler->ruler.SetFormat(Ruler::LinearDBFormat); + mdBRuler->ruler.SetUnits(_("dB")); + mdBRuler->ruler.SetLabelEdges(true); + mdBRuler->ruler.mbTicksAtExtremes = true; + int w, h; + mdBRuler->ruler.GetMaxSize(&w, NULL); + mdBRuler->SetSize(wxSize(w, 150)); // height needed for wxGTK + + szr4 = new wxBoxSizer( wxVERTICAL ); + szr4->AddSpacer(PANELBORDER); // vertical space for panel border + szr4->Add( mdBRuler, 1, wxEXPAND|wxALIGN_LEFT|wxALL ); + szr4->AddSpacer(PANELBORDER); // vertical space for panel border + szr1->Add( szr4, 0, wxEXPAND|wxALIGN_LEFT|wxALL ); + + mPanel = new EqualizationPanel(this, parent); + szr1->Add( mPanel, 1, wxEXPAND|wxALIGN_CENTRE); + szr3 = new wxBoxSizer( wxVERTICAL ); + szr1->Add( szr3, 0, wxALIGN_CENTRE|wxRIGHT, 0); //spacer for last EQ + + /// Next row of wxFlexGridSizer + szr1->Add(1, 1); // horizontal spacer + szr1->Add(1, 1); // horizontal spacer + + mFreqRuler = new RulerPanel(parent, wxID_ANY); + mFreqRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes + mFreqRuler->ruler.SetOrientation(wxHORIZONTAL); + mFreqRuler->ruler.SetLog(true); + mFreqRuler->ruler.SetRange(mLoFreq, mHiFreq); + mFreqRuler->ruler.SetFormat(Ruler::IntFormat); + mFreqRuler->ruler.SetUnits(_("Hz")); + mFreqRuler->ruler.SetFlip(true); + mFreqRuler->ruler.SetLabelEdges(true); + mFreqRuler->ruler.mbTicksAtExtremes = true; + mFreqRuler->ruler.GetMaxSize(NULL, &h); + mFreqRuler->SetMinSize(wxSize(-1, h)); + szr5 = new wxBoxSizer( wxHORIZONTAL ); + szr5->AddSpacer(PANELBORDER); // horizontal space for panel border + szr5->Add( mFreqRuler, 1, wxEXPAND|wxALIGN_LEFT); + szr5->AddSpacer(PANELBORDER); // horizontal space for panel border + szr1->Add( szr5, 0, wxEXPAND|wxALIGN_LEFT|wxALL ); + szr1->Layout(); + + szrV->Add( szr1, 1, wxEXPAND|wxALIGN_CENTER|wxALL, 0 ); + + // ------------------------------------------------------------------- + // Graphic EQ - parent gets laid out horizontally in onSize + // ------------------------------------------------------------------- + + szrG = new wxBoxSizer( wxHORIZONTAL ); + szrG->Add(0, 0, 0); // horizontal spacer, will be used to position LH EQ slider + for (int i = 0; (i < NUMBER_OF_BANDS) && (kThirdOct[i] <= mHiFreq); ++i) + { + mSliders[i] = new wxSlider(parent, ID_Slider + i, 0, -20, +20, + wxDefaultPosition, wxSize(20, 124), wxSL_VERTICAL| + wxSL_INVERSE); + szrG->Add( mSliders[i], 0, wxEXPAND|wxALIGN_CENTER ); + szrG->Add(0, 0, 0); // horizontal spacer - used to put EQ sliders in correct position + mSliders[i]->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(EffectEqualization::OnErase)); + mEQVals[i] = 0.; + +#if wxUSE_ACCESSIBILITY + wxString name; + if( kThirdOct[i] < 1000.) + name.Printf(wxString(wxT("%d ")) + _("Hz"), (int) kThirdOct[i]); + else + name.Printf(wxString(wxT("%g ")) + _("kHz"), kThirdOct[i]/1000.); + mSliders[i]->SetName(name); + mSliders[i]->SetAccessible(new SliderAx(mSliders[i], wxString(wxT("%d ")) + _("dB"))); +#endif + } + szrV->Add( szrG, 0, wxEXPAND|wxALIGN_LEFT|wxALL, 0 ); + + wxSizerItem *EQslider = szrG->GetItem((size_t)1); + wxSize EQsliderSize = EQslider->GetSize(); //size of the sliders + szr3->SetMinSize(EQsliderSize.x/2, -1); //extra gap for last slider + + // ------------------------------------------------------------------- + // Graphic or curve drawing? + // ------------------------------------------------------------------- + szrH = new wxBoxSizer( wxHORIZONTAL ); + + mDraw = new wxRadioButton( + parent, ID_Draw, _("&Draw Curves"), + wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); + mDraw->SetName(_("Draw Curves")); + szrH->Add( mDraw, 0, wxRIGHT, 10 ); + + mGraphic = new wxRadioButton( + parent, ID_Graphic, _("&Graphic EQ"), + wxDefaultPosition, wxDefaultSize, 0 ); + mGraphic->SetName(_("Graphic EQ")); + szrH->Add( mGraphic, 0, wxRIGHT, 4 ); + + mInterpChoice = new wxChoice(parent, ID_Interp, + wxDefaultPosition, wxDefaultSize, mInterpolations); + + mInterpChoice->SetSelection(0); + szrI = new wxBoxSizer( wxHORIZONTAL ); + szrI->Add( mInterpChoice, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, 40 ); + szrH->Add( szrI ); + + szrL = new wxBoxSizer( wxHORIZONTAL ); + mLinFreq = new wxCheckBox(parent, ID_Linear, _("Li&near Frequency Scale")); + mLinFreq->SetName(_("Linear Frequency Scale")); + szrL->Add( mLinFreq, 0 ); + szrH->Add(szrL); // either szrI or szrL are visible, not both. + + // ------------------------------------------------------------------- + // Filter length grouping + // ------------------------------------------------------------------- + + // length of filter (M) label + txt = new wxStaticText(parent, wxID_ANY, _("Length of &Filter:")); + szrH->Add( txt, 0 ); + + // length of filter (M) slider + mMSlider = new wxSlider(parent, ID_Length, (mM -1)/2, 10, 4095, + wxDefaultPosition, wxSize(200, -1), wxSL_HORIZONTAL); + mMSlider->SetName(_("Length of Filter")); + szrH->Add( mMSlider, 0, wxEXPAND ); + + wxString label; + label.Printf( wxT("%d"), mM ); + mMText = new wxStaticText(parent, wxID_ANY, label); + mMText->SetName(label); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) + szrH->Add( mMText, 0 ); + + // Add the length / graphic / draw grouping + szrV->Add( szrH, 0, wxALIGN_CENTER | wxALL, 4 ); + + // ------------------------------------------------------------------- + // Curve management grouping + // ------------------------------------------------------------------- + szrC = new wxBoxSizer( wxHORIZONTAL ); //szrC is for the curves bits + + txt = new wxStaticText( parent, wxID_ANY, _("&Select Curve:") ); + szrC->Add( txt, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 4 ); + + mCurve = new wxChoice( parent, ID_Curve ); + szrC->Add( mCurve, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 4 ); + + mManage = new wxButton( parent, ID_Manage, _("S&ave/Manage Curves...") ); + mManage->SetName(_("Save and Manage Curves")); + szrC->Add( mManage, 0, wxALIGN_CENTRE|wxLEFT, 4 ); + + btn = new wxButton( parent, ID_Clear, _("Fla&tten")); + szrC->Add( btn, 0, wxALIGN_CENTRE | wxALL, 4 ); + btn = new wxButton( parent, ID_Invert, _("&Invert")); + szrC->Add( btn, 0, wxALIGN_CENTRE | wxALL, 4 ); + mGridOnOff = new wxCheckBox(parent, ID_Grid, _("G&rids"), + wxDefaultPosition, wxDefaultSize, +#if defined(__WXGTK__) + // Fixes bug #662 + wxALIGN_LEFT); +#else + wxALIGN_RIGHT); +#endif + mGridOnOff->SetName(_("Grids")); + szrC->Add( mGridOnOff, 0, wxALIGN_CENTRE | wxALL, 4 ); + + szrV->Add( szrC, 0, wxALIGN_CENTER | wxALL, 0 ); + +#ifdef EXPERIMENTAL_EQ_SSE_THREADED + // ------------------------------------------------------------------- + // Processing routine selection + // ------------------------------------------------------------------- + if(mEffectEqualization48x) { + szrM = new wxBoxSizer( wxHORIZONTAL ); + txt = new wxStaticText( parent, wxID_ANY, _("&Processing: ") ); + szrM->Add( txt, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 4 ); + + mMathProcessingType[0] = new wxRadioButton( + parent, ID_DefaultMath, _("D&efault"), + wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); + mMathProcessingType[0]->SetName(_("Default")); + szrM->Add( mMathProcessingType[0], 0, wxRIGHT, 10 ); + + mMathProcessingType[1] = new wxRadioButton( + parent, ID_SSE, _("&SSE"), + wxDefaultPosition, wxDefaultSize, 0 ); + mMathProcessingType[1]->SetName(_("SSE")); + szrM->Add( mMathProcessingType[1], 0, wxRIGHT, 4 ); + + mMathProcessingType[2] = new wxRadioButton( + parent, ID_SSE_Threaded, _("SSE &Threaded"), + wxDefaultPosition, wxDefaultSize, 0 ); + mMathProcessingType[2]->SetName(_("SSE")); + szrM->Add( mMathProcessingType[2], 0, wxRIGHT, 4 ); + + mMathProcessingType[3] = new wxRadioButton( + parent, ID_AVX, _("A&VX"), + wxDefaultPosition, wxDefaultSize, 0 ); + mMathProcessingType[3]->SetName(_("AVX")); + szrM->Add( mMathProcessingType[3], 0, wxRIGHT, 4 ); + + mMathProcessingType[4] = new wxRadioButton( + parent, ID_AVX_Threaded, _("AV&X Threaded"), + wxDefaultPosition, wxDefaultSize, 0 ); + mMathProcessingType[4]->SetName(_("AVX Threaded")); + szrM->Add( mMathProcessingType[4], 0, wxRIGHT, 4 ); + + if(!EffectEqualization48x::GetMathCaps()->SSE) { + mMathProcessingType[1]->Disable(); + mMathProcessingType[2]->Disable(); + } + if(true) { //!EffectEqualization48x::GetMathCaps()->AVX) { not implemented + mMathProcessingType[3]->Disable(); + mMathProcessingType[4]->Disable(); + } + // update the control state + mMathProcessingType[0]->SetValue(true); + int mathPath=EffectEqualization48x::GetMathPath(); + if(mathPath&MATH_FUNCTION_SSE) { + mMathProcessingType[1]->SetValue(true); + if(mathPath&MATH_FUNCTION_THREADED) + mMathProcessingType[2]->SetValue(true); + } + if(false) { //mathPath&MATH_FUNCTION_AVX) { not implemented + mMathProcessingType[3]->SetValue(true); + if(mathPath&MATH_FUNCTION_THREADED) + mMathProcessingType[4]->SetValue(true); + } + btn = new wxButton( parent, ID_Bench, _("&Bench")); + szrM->Add( btn, 0, wxRIGHT, 4 ); + + szrV->Add( szrM, 0, wxALIGN_CENTER | wxALL, 0 ); + } +#endif + + wxGetTopLevelParent(parent)->SetAutoLayout(true); + + szrV->Show(szrG, true); + szrH->Show(szrI, true); + szrH->Show(szrL, false); + + parent->SetSizer(szrV); + szrV->SetSizeHints(parent); + + szrL->SetMinSize(szrI->GetSize()); + + szrV->Show(szrC, true); + szrV->Show(szrG, false); + szrH->Show(szrI, false); + szrH->Show(szrL, true); + + return; +} + +// +// Populate the window with relevant variables +// +bool EffectEqualization::TransferDataToWindow() +{ + GetPrivateConfig(GetCurrentSettingsGroup(), wxT("dBMin"), mdBMin, DEF_dBMin); + GetPrivateConfig(GetCurrentSettingsGroup(), wxT("dBMax"), mdBMax, DEF_dBMax); + GetPrivateConfig(GetCurrentSettingsGroup(), wxT("DrawMode"), mDrawMode, DEF_DrawMode); + GetPrivateConfig(GetCurrentSettingsGroup(), wxT("DrawGrid"), mDrawGrid, DEF_DrawGrid); + + // Set log or lin freq scale (affects interpolation as well) + mLinFreq->SetValue( mLin ); + wxCommandEvent dummyEvent; + OnLinFreq(dummyEvent); // causes a CalcFilter + + mGridOnOff->SetValue( mDrawGrid ); // checks/unchecks the box on the interface + + mMSlider->SetValue((mM-1)/2); + mM = 0; // force refresh in TransferDataFromWindow() + + mdBMinSlider->SetValue((int)mdBMin); + mdBMin = 0; // force refresh in TransferDataFromWindow() + + mdBMaxSlider->SetValue((int)mdBMax); + mdBMax = 0; // force refresh in TransferDataFromWindow() + + // Reload the curve names + mCurve->Clear(); + for (size_t i = 0, cnt = mCurves.GetCount(); i < cnt; i++) + { + mCurve->Append(mCurves[ i ].Name); + } + mCurve->SetStringSelection(mCurveName); + + // Allow the control to resize + mCurve->SetSizeHints(-1, -1); + + // Set initial curve + setCurve( mCurveName ); + + // Set graphic interpolation mode + mInterpChoice->SetSelection(mInterp); + + // Set Graphic (Fader) or Draw mode + // Set Graphic (Fader) or Draw mode + if (mDrawMode) + { + mDraw->SetValue(true); + } + else + { + mGraphic->SetValue(true); + UpdateGraphic(); + } + + TransferDataFromWindow(); + + mUIParent->Layout(); + wxGetTopLevelParent(mUIParent)->Layout(); + + return true; +} + +// +// Retrieve data from the window +// +bool EffectEqualization::TransferDataFromWindow() +{ + wxString tip; + + bool rr = false; + float dB = (float) mdBMinSlider->GetValue(); + if (dB != mdBMin) { + rr = true; + mdBMin = dB; + tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)mdBMin); + mdBMinSlider->SetToolTip(tip); + } + + dB = (float) mdBMaxSlider->GetValue(); + if (dB != mdBMax) { + rr = true; + mdBMax = dB; + tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)mdBMax); + mdBMaxSlider->SetToolTip(tip); + } + + // Refresh ruler if values have changed + if (rr) { + int w1, w2, h; + mdBRuler->ruler.GetMaxSize(&w1, &h); + mdBRuler->ruler.SetRange(mdBMax, mdBMin); + mdBRuler->ruler.GetMaxSize(&w2, &h); + if( w1 != w2 ) // Reduces flicker + { + mdBRuler->SetSize(wxSize(w2,h)); + szr1->Layout(); + LayoutEQSliders(); + szrG->Layout(); + mFreqRuler->Refresh(false); + } + mdBRuler->Refresh(false); + + mPanel->Refresh(false); + } + + int m = 2 * mMSlider->GetValue() + 1; // odd numbers only + if (m != mM) { + rr = true; + mM = m; + mPanel->ForceRecalc(); + + tip.Printf(wxT("%d"), mM); + mMText->SetLabel(tip); + mMText->SetName(mMText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) + mMSlider->SetToolTip(tip); + } + + return true; +} + +// EffectEqualization implementation bool EffectEqualization::ProcessOne(int count, WaveTrack * t, sampleCount start, sampleCount len) @@ -613,8 +1137,118 @@ bool EffectEqualization::ProcessOne(int count, WaveTrack * t, return bLoopSuccess; } -void EffectEqualization::Filter(sampleCount len, - float *buffer) +bool EffectEqualization::CalcFilter() +{ + double loLog = log10(mLoFreq); + double hiLog = log10(mHiFreq); + double denom = hiLog - loLog; + + double delta = mHiFreq / ((double)(mWindowSize/2.)); + double val0; + double val1; + + bool lin = mDrawMode && mLin; + + if( lin ) + { + val0 = mLinEnvelope->GetValue(0.0); //no scaling required - saved as dB + val1 = mLinEnvelope->GetValue(1.0); + } + else + { + val0 = mLogEnvelope->GetValue(0.0); //no scaling required - saved as dB + val1 = mLogEnvelope->GetValue(1.0); + } + mFilterFuncR[0] = val0; + double freq = delta; + + int i; + for(i=1; i<=mWindowSize/2; i++) + { + double when; + if( lin ) + when = freq/mHiFreq; + else + when = (log10(freq) - loLog)/denom; + if(when < 0.) + { + mFilterFuncR[i] = val0; + } + else if(when > 1.0) + { + mFilterFuncR[i] = val1; + } + else + { + if( lin ) + mFilterFuncR[i] = mLinEnvelope->GetValue(when); + else + mFilterFuncR[i] = mLogEnvelope->GetValue(when); + } + freq += delta; + } + mFilterFuncR[mWindowSize/2] = val1; + + mFilterFuncR[0] = (float)(pow(10., mFilterFuncR[0]/20.)); + for(i=1;iFlatten(0.); - mEnvelope->Mirror(false); - mEnvelope->SetTrackLen(1.0); - - RecalcRequired = true; -} - -EqualizationPanel::~EqualizationPanel() -{ - if (mBitmap) - delete mBitmap; - if (mOuti) - delete [] mOuti; - if (mOutr) - delete [] mOutr; -} - -void EqualizationPanel::Recalc() -{ - if (mOutr) - delete [] mOutr; - mOutr = new float[mWindowSize]; - - if (mOuti) - delete [] mOuti; - mOuti = new float[mWindowSize]; - - mParent->CalcFilter(); //to calculate the actual response -#ifdef EXPERIMENTAL_USE_REALFFTF - InverseRealFFT(mWindowSize, mFilterFuncR, mFilterFuncI, mOutr); -#else - FFT(mWindowSize,true,mFilterFuncR,mFilterFuncI,mOutr,mOuti); //work out FIR response - note mOuti will be all zeros -#endif // EXPERIMENTAL_USE_REALFFTF -} - -void EqualizationPanel::OnSize(wxSizeEvent & WXUNUSED(event)) -{ - Refresh( false ); -} - -void EqualizationPanel::OnPaint(wxPaintEvent & WXUNUSED(event)) -{ - wxPaintDC dc(this); - if(RecalcRequired) { - Recalc(); - RecalcRequired = false; - } - int width, height; - GetSize(&width, &height); - - if (!mBitmap || mWidth!=width || mHeight!=height) - { - if (mBitmap) - delete mBitmap; - - mWidth = width; - mHeight = height; - mBitmap = new wxBitmap(mWidth, mHeight); - } - - wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)); - - wxMemoryDC memDC; - memDC.SelectObject(*mBitmap); - - wxRect bkgndRect; - bkgndRect.x = 0; - bkgndRect.y = 0; - bkgndRect.width = mWidth; - bkgndRect.height = mHeight; - memDC.SetBrush(bkgndBrush); - memDC.SetPen(*wxTRANSPARENT_PEN); - memDC.DrawRectangle(bkgndRect); - - bkgndRect.y = mHeight; - memDC.DrawRectangle(bkgndRect); - - wxRect border; - border.x = 0; - border.y = 0; - border.width = mWidth; - border.height = mHeight; - - memDC.SetBrush(*wxWHITE_BRUSH); - memDC.SetPen(*wxBLACK_PEN); - memDC.DrawRectangle(border); - - mEnvRect = border; - mEnvRect.Deflate(PANELBORDER, PANELBORDER); - - // Pure blue x-axis line - memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 1, wxSOLID)); - int center = (int) (mEnvRect.height * dBMax/(dBMax-dBMin) + .5); - AColor::Line(memDC, - mEnvRect.GetLeft(), mEnvRect.y + center, - mEnvRect.GetRight(), mEnvRect.y + center); - - // Draw the grid, if asked for. Do it now so it's underneath the main plots. - if( mParent->drawGrid ) - { - mParent->freqRuler->ruler.DrawGrid(memDC, mEnvRect.height, true, true, PANELBORDER, PANELBORDER); - mParent->dBRuler->ruler.DrawGrid(memDC, mEnvRect.width, true, true, PANELBORDER, PANELBORDER); - } - - // Med-blue envelope line - memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 3, wxSOLID)); - - // Draw envelope - double *values = new double[mEnvRect.width]; - mEnvelope->GetValues(values, mEnvRect.width, 0.0, 1.0/mEnvRect.width); - int x, y, xlast = 0, ylast = 0; - bool off = false, off1 = false; - for(int i=0; i= mEnvRect.height) - { - y = mEnvRect.height - 1; - off = true; - } - else - { - off = false; - off1 = false; - } - if ( (i != 0) & (!off1) ) - { - AColor::Line(memDC, xlast, ylast, - x, mEnvRect.y + y); - } - off1 = off; - xlast = x; - ylast = mEnvRect.y + y; - } - delete[] values; - - //Now draw the actual response that you will get. - //mFilterFunc has a linear scale, window has a log one so we have to fiddle about - memDC.SetPen(wxPen(theTheme.Colour( clrResponseLines ), 1, wxSOLID)); - double scale = (double)mEnvRect.height/(dBMax-dBMin); //pixels per dB - double yF; //gain at this freq - double delta = mHiFreq/(((double)mWindowSize/2.)); //size of each freq bin - - bool lin = mParent->mFaderOrDraw[0]->GetValue() & mParent->mLinFreq->IsChecked(); // log or lin scale? - - double loLog = log10(mLoFreq); - double step = lin ? mHiFreq : (log10(mHiFreq) - loLog); - step /= ((double)mEnvRect.width-1.); - double freq; //actual freq corresponding to x position - int halfM = (M-1)/2; - int n; //index to mFreqFunc - for(int i=0; imEnvRect.height) - yF = mEnvRect.height - 1; - if(yF<0.) - yF=0.; - y = (int)(yF+.5); - - if (i != 0) - { - AColor::Line(memDC, xlast, ylast, x, mEnvRect.y + y); - } - xlast = x; - ylast = mEnvRect.y + y; - } - - memDC.SetPen(*wxBLACK_PEN); - if( mParent->mFaderOrDraw[0]->GetValue() ) - mEnvelope->DrawPoints(memDC, mEnvRect, 0.0, mEnvRect.width-1, false, dBMin, dBMax); - - dc.Blit(0, 0, mWidth, mHeight, - &memDC, 0, 0, wxCOPY, FALSE); -} - -void EqualizationPanel::OnMouseEvent(wxMouseEvent & event) -{ - if (event.ButtonDown() && !HasCapture()) - { - CaptureMouse(); - } - - if (mEnvelope->MouseEvent(event, mEnvRect, 0.0, mEnvRect.width, false, - dBMin, dBMax)) - { - mParent->EnvelopeUpdated(); - RecalcRequired = true; - Refresh(false); - } - - if (event.ButtonUp() && HasCapture()) - { - ReleaseMouse(); - } -} - -void EqualizationPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event)) -{ - if (HasCapture()) - { - ReleaseMouse(); - } -} - - -// WDR: class implementations - -//---------------------------------------------------------------------------- -// EqualizationDialog -//---------------------------------------------------------------------------- - -// WDR: event table for EqualizationDialog - -BEGIN_EVENT_TABLE(EqualizationDialog,wxDialog) - EVT_SIZE( EqualizationDialog::OnSize ) - EVT_PAINT( EqualizationDialog::OnPaint ) - EVT_ERASE_BACKGROUND( EqualizationDialog::OnErase ) - - EVT_SLIDER( ID_LENGTH, EqualizationDialog::OnSliderM ) - EVT_SLIDER( ID_DBMAX, EqualizationDialog::OnSliderDBMAX ) - EVT_SLIDER( ID_DBMIN, EqualizationDialog::OnSliderDBMIN ) - EVT_COMMAND_RANGE( ID_SLIDER, - ID_SLIDER + NUMBER_OF_BANDS - 1, - wxEVT_COMMAND_SLIDER_UPDATED, - EqualizationDialog::OnSlider) - EVT_CHOICE( ID_INTERP, EqualizationDialog::OnInterp ) - - EVT_CHOICE( ID_CURVE, EqualizationDialog::OnCurve ) - EVT_BUTTON( ID_MANAGE, EqualizationDialog::OnManage ) - EVT_BUTTON( ID_CLEAR, EqualizationDialog::OnClear ) - EVT_BUTTON( ID_INVERT, EqualizationDialog::OnInvert ) - - EVT_BUTTON( ID_EFFECT_PREVIEW, EqualizationDialog::OnPreview ) - EVT_BUTTON( wxID_OK, EqualizationDialog::OnOk ) - EVT_BUTTON( wxID_CANCEL, EqualizationDialog::OnCancel ) - EVT_RADIOBUTTON(drawRadioID, EqualizationDialog::OnDrawRadio) - EVT_RADIOBUTTON(sliderRadioID, EqualizationDialog::OnSliderRadio) - -#ifdef EXPERIMENTAL_EQ_SSE_THREADED - EVT_RADIOBUTTON(defaultMathRadioID, EqualizationDialog::OnProcessingRadio) - EVT_RADIOBUTTON(sSERadioID, EqualizationDialog::OnProcessingRadio) - EVT_RADIOBUTTON(sSEThreadedRadioID, EqualizationDialog::OnProcessingRadio) - EVT_RADIOBUTTON(aVXRadioID, EqualizationDialog::OnProcessingRadio) - EVT_RADIOBUTTON(aVXThreadedRadioID, EqualizationDialog::OnProcessingRadio) - EVT_BUTTON( ID_BENCH, EqualizationDialog::OnBench ) -#endif - - EVT_CHECKBOX(ID_LIN_FREQ, EqualizationDialog::OnLinFreq) - EVT_CHECKBOX(GridOnOffID, EqualizationDialog::OnGridOnOff) -END_EVENT_TABLE() - -EqualizationDialog::EqualizationDialog(EffectEqualization * effect, - double loFreq, double hiFreq, - float *filterFuncR, - float *filterFuncI, - long windowSize, - wxString curveName, - bool disallowCustom, - wxWindow *parent, wxWindowID id, - const wxString &title, - const wxPoint &position, - const wxSize& size, - long style): - wxDialog( parent, id, title, position, size, style | wxRESIZE_BORDER | wxMAXIMIZE_BOX ), - mDisallowCustom(disallowCustom), - mCustomBackup(wxT("unnamed")) -{ - m_pEffect = effect; - - M = 4001; - linCheck = false; - dBMin = -30.; - dBMax = 30; - -#if wxUSE_TOOLTIPS - wxToolTip::Enable(true); -#endif - - mLogEnvelope = new Envelope(); - mLogEnvelope->SetInterpolateDB(false); - mLogEnvelope->Mirror(false); - mLogEnvelope->SetRange(-120.0, 60.0); // MB: this is the highest possible range - - mLinEnvelope = new Envelope(); - mLinEnvelope->SetInterpolateDB(false); - mLinEnvelope->Mirror(false); - mLinEnvelope->SetRange(-120.0, 60.0); // MB: this is the highest possible range - - mLoFreq = loFreq; - mHiFreq = hiFreq; - - mFilterFuncR = filterFuncR; - mFilterFuncI = filterFuncI; - mWindowSize = windowSize; - - mCurve = NULL; - mDirty = false; - - // Load the EQ curves - LoadCurves(); - if (mDisallowCustom) - { - mCustomBackup.Name = wxT("unnamed"); - EQCurve &realCustom = mCurves[mCurves.GetCount()-1]; - wxASSERT(realCustom.Name.IsSameAs(wxT("unnamed"))); - mCustomBackup.points = realCustom.points; - } - - // Create the dialog - MakeEqualizationDialog(); - - // Note: initial curve is set in TransferDataToWindow - - bandsInUse = NUMBER_OF_BANDS; - //double loLog = log10(mLoFreq); - //double stepLog = (log10(mHiFreq) - loLog)/((double)NUM_PTS-1.); - for(int i=0; iGetMessage().c_str()), - _("Error Saving Equalization Curves"), wxICON_ERROR, this); + _("Error Saving Equalization Curves"), wxICON_ERROR, mUIParent); delete pException; } } -// -// Create the Equalization dialog -// -void EqualizationDialog::MakeEqualizationDialog() -{ - wxStaticText *txt; - wxButton *btn; - - // Create the base sizer - szrV = new wxBoxSizer( wxVERTICAL ); - szrV->AddSpacer(10); - - // ------------------------------------------------------------------- - // EQ panel and sliders for vertical scale - // ------------------------------------------------------------------- - szr1 = new wxFlexGridSizer( 4, 0, 0 ); - szr1->AddGrowableCol( 2, 1 ); - szr1->AddGrowableRow( 0, 1 ); - szr1->SetFlexibleDirection( wxBOTH ); - - szr2 = new wxBoxSizer( wxVERTICAL ); - dBMaxSlider = new wxSliderBugfix(this, ID_DBMAX, 30, 0, 60, - wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE); - szr2->Add( dBMaxSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 ); - dBMinSlider = new wxSliderBugfix(this, ID_DBMIN, -30, -120, -10, - wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE); - szr2->Add( dBMinSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 ); - szr1->Add( szr2, 0, wxEXPAND|wxALIGN_CENTRE|wxALL, 4 ); - -#if wxUSE_ACCESSIBILITY - dBMaxSlider->SetName(_("Max dB")); - dBMaxSlider->SetAccessible(new SliderAx(dBMaxSlider, wxString(wxT("%d ")) + _("dB"))); - dBMinSlider->SetName(_("Min dB")); - dBMinSlider->SetAccessible(new SliderAx(dBMinSlider, wxString(wxT("%d ")) + _("dB"))); -#endif - - dBRuler = new RulerPanel(this, wxID_ANY); - dBRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes - dBRuler->ruler.SetOrientation(wxVERTICAL); - dBRuler->ruler.SetRange(60.0, -120.0); - dBRuler->ruler.SetFormat(Ruler::LinearDBFormat); - dBRuler->ruler.SetUnits(_("dB")); - dBRuler->ruler.SetLabelEdges(true); - dBRuler->ruler.mbTicksAtExtremes = true; - int w, h; - dBRuler->ruler.GetMaxSize(&w, NULL); - dBRuler->SetSize(wxSize(w, 150)); // height needed for wxGTK - - szr4 = new wxBoxSizer( wxVERTICAL ); - szr4->AddSpacer(PANELBORDER); // vertical space for panel border - szr4->Add( dBRuler, 1, wxEXPAND|wxALIGN_LEFT|wxALL ); - szr4->AddSpacer(PANELBORDER); // vertical space for panel border - szr1->Add( szr4, 0, wxEXPAND|wxALIGN_LEFT|wxALL ); - - mPanel = new EqualizationPanel( mLoFreq, mHiFreq, - mLogEnvelope, - this, - mFilterFuncR, mFilterFuncI, mWindowSize, - ID_FILTERPANEL); - szr1->Add( mPanel, 1, wxEXPAND|wxALIGN_CENTRE); - szr3 = new wxBoxSizer( wxVERTICAL ); - szr1->Add( szr3, 0, wxALIGN_CENTRE|wxRIGHT, 0); //spacer for last EQ - - /// Next row of wxFlexGridSizer - szr1->Add(1, 1); // horizontal spacer - szr1->Add(1, 1); // horizontal spacer - - freqRuler = new RulerPanel(this, wxID_ANY); - freqRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes - freqRuler->ruler.SetOrientation(wxHORIZONTAL); - freqRuler->ruler.SetLog(true); - freqRuler->ruler.SetRange(mLoFreq, mHiFreq); - freqRuler->ruler.SetFormat(Ruler::IntFormat); - freqRuler->ruler.SetUnits(_("Hz")); - freqRuler->ruler.SetFlip(true); - freqRuler->ruler.SetLabelEdges(true); - freqRuler->ruler.mbTicksAtExtremes = true; - freqRuler->ruler.GetMaxSize(NULL, &h); - freqRuler->SetMinSize(wxSize(-1, h)); - szr5 = new wxBoxSizer( wxHORIZONTAL ); - szr5->AddSpacer(PANELBORDER); // horizontal space for panel border - szr5->Add( freqRuler, 1, wxEXPAND|wxALIGN_LEFT); - szr5->AddSpacer(PANELBORDER); // horizontal space for panel border - szr1->Add( szr5, 0, wxEXPAND|wxALIGN_LEFT|wxALL ); - - szrV->Add( szr1, 1, wxEXPAND|wxALIGN_CENTER|wxALL, 0 ); - - // ------------------------------------------------------------------- - // Graphic EQ - this gets laid out horizontally in onSize - // ------------------------------------------------------------------- - - szrG = new wxBoxSizer( wxHORIZONTAL ); - szrG->Add(0, 0, 0); // horizontal spacer, will be used to position LH EQ slider - for (int i = 0; (i < NUMBER_OF_BANDS) && (thirdOct[i] <= mHiFreq); ++i) - { - m_sliders[i] = new wxSliderBugfix(this, ID_SLIDER + i, 0, -20, +20, - wxDefaultPosition, wxSize(20, 124), wxSL_VERTICAL| - wxSL_INVERSE); - szrG->Add( m_sliders[i], 0, wxEXPAND|wxALIGN_CENTER ); - szrG->Add(0, 0, 0); // horizontal spacer - used to put EQ sliders in correct position - m_sliders[i]->Connect(wxEVT_ERASE_BACKGROUND,wxEraseEventHandler(EqualizationDialog::OnErase)); - m_sliders_old[i] = 0; - m_EQVals[i] = 0.; - -#if wxUSE_ACCESSIBILITY - wxString name; - if( thirdOct[i] < 1000.) - name.Printf(wxString(wxT("%d ")) + _("Hz"), (int)thirdOct[i]); - else - name.Printf(wxString(wxT("%g ")) + _("kHz"), thirdOct[i]/1000.); - m_sliders[i]->SetName(name); - m_sliders[i]->SetAccessible(new SliderAx(m_sliders[i], wxString(wxT("%d ")) + _("dB"))); -#endif - } - szrV->Add( szrG, 0, wxEXPAND|wxALIGN_LEFT|wxALL, 0 ); - - wxSizerItem *EQslider = szrG->GetItem((size_t)1); - wxSize EQsliderSize = EQslider->GetSize(); //size of the sliders - szr3->SetMinSize(EQsliderSize.x/2, -1); //extra gap for last slider - - // ------------------------------------------------------------------- - // Graphic or curve drawing? - // ------------------------------------------------------------------- - szrH = new wxBoxSizer( wxHORIZONTAL ); - - mFaderOrDraw[0] = new wxRadioButton( - this, drawRadioID, _("&Draw Curves"), - wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); - mFaderOrDraw[0]->SetName(_("Draw Curves")); - szrH->Add( mFaderOrDraw[0], 0, wxRIGHT, 10 ); - - mFaderOrDraw[1] = new wxRadioButton( - this, sliderRadioID, _("&Graphic EQ"), - wxDefaultPosition, wxDefaultSize, 0 ); - mFaderOrDraw[1]->SetName(_("Graphic EQ")); - szrH->Add( mFaderOrDraw[1], 0, wxRIGHT, 4 ); - - mInterpChoice = new wxChoice(this, ID_INTERP, - wxDefaultPosition, wxDefaultSize, - NUM_INTERP_CHOICES, interpChoiceStrings); - - mInterpChoice->SetSelection(0); - szrI = new wxBoxSizer( wxHORIZONTAL ); - szrI->Add( mInterpChoice, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, 40 ); - szrH->Add( szrI ); - - szrL = new wxBoxSizer( wxHORIZONTAL ); - mLinFreq = new wxCheckBox(this, ID_LIN_FREQ, _("Li&near Frequency Scale")); - mLinFreq->SetName(_("Linear Frequency Scale")); - szrL->Add( mLinFreq, 0 ); - szrH->Add(szrL); // either szrI or szrL are visible, not both. - - // ------------------------------------------------------------------- - // Filter length grouping - // ------------------------------------------------------------------- - - // length of filter (M) label - txt = new wxStaticText(this, wxID_ANY, _("Length of &Filter:")); - szrH->Add( txt, 0 ); - - // length of filter (M) slider - MSlider = new wxSliderBugfix(this, ID_LENGTH, (M -1)/2, 10, 4095, - wxDefaultPosition, wxSize(200, -1), wxSL_HORIZONTAL); - MSlider->SetName(_("Length of Filter")); - szrH->Add( MSlider, 0, wxEXPAND ); - - wxString label; - label.Printf( wxT("%d"), M ); - mMText = new wxStaticText(this, wxID_ANY, label); - mMText->SetName(label); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - szrH->Add( mMText, 0 ); - - // Add the length / graphic / draw grouping - szrV->Add( szrH, 0, wxALIGN_CENTER | wxALL, 4 ); - - // ------------------------------------------------------------------- - // Curve management grouping - // ------------------------------------------------------------------- - szrC = new wxBoxSizer( wxHORIZONTAL ); //szrC is for the curves bits - - txt = new wxStaticText( this, wxID_ANY, _("&Select Curve:") ); - szrC->Add( txt, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 4 ); - - // Create the choice sizer (helps in recreating choice control) - mCurveSizer = new wxBoxSizer( wxHORIZONTAL ); - szrC->Add( mCurveSizer, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 4 ); - - // Create the choice control - CreateChoice(); - - mManage = new wxButton( this, ID_MANAGE, _("S&ave/Manage Curves...") ); - mManage->SetName(_("Save and Manage Curves")); - szrC->Add( mManage, 0, wxALIGN_CENTRE|wxLEFT, 4 ); - - btn = new wxButton( this, ID_CLEAR, _("Fla&tten")); - szrC->Add( btn, 0, wxALIGN_CENTRE | wxALL, 4 ); - btn = new wxButton( this, ID_INVERT, _("&Invert")); - szrC->Add( btn, 0, wxALIGN_CENTRE | wxALL, 4 ); - mGridOnOff = new wxCheckBox(this, GridOnOffID, _("G&rids"), - wxDefaultPosition, wxDefaultSize, -#if defined(__WXGTK__) - // Fixes bug #662 - wxALIGN_LEFT); -#else - wxALIGN_RIGHT); -#endif - mGridOnOff->SetName(_("Grids")); - szrC->Add( mGridOnOff, 0, wxALIGN_CENTRE | wxALL, 4 ); - - szrV->Add( szrC, 0, wxALIGN_CENTER | wxALL, 0 ); - -#ifdef EXPERIMENTAL_EQ_SSE_THREADED - // ------------------------------------------------------------------- - // Processing routine selection - // ------------------------------------------------------------------- - if(m_pEffect->mEffectEqualization48x) { - szrM = new wxBoxSizer( wxHORIZONTAL ); - txt = new wxStaticText( this, wxID_ANY, _("&Processing: ") ); - szrM->Add( txt, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 4 ); - - mMathProcessingType[0] = new wxRadioButton( - this, defaultMathRadioID, _("D&efault"), - wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); - mMathProcessingType[0]->SetName(_("Default")); - szrM->Add( mMathProcessingType[0], 0, wxRIGHT, 10 ); - - mMathProcessingType[1] = new wxRadioButton( - this, sSERadioID, _("&SSE"), - wxDefaultPosition, wxDefaultSize, 0 ); - mMathProcessingType[1]->SetName(_("SSE")); - szrM->Add( mMathProcessingType[1], 0, wxRIGHT, 4 ); - - mMathProcessingType[2] = new wxRadioButton( - this, sSEThreadedRadioID, _("SSE &Threaded"), - wxDefaultPosition, wxDefaultSize, 0 ); - mMathProcessingType[2]->SetName(_("SSE")); - szrM->Add( mMathProcessingType[2], 0, wxRIGHT, 4 ); - - mMathProcessingType[3] = new wxRadioButton( - this, aVXRadioID, _("A&VX"), - wxDefaultPosition, wxDefaultSize, 0 ); - mMathProcessingType[3]->SetName(_("AVX")); - szrM->Add( mMathProcessingType[3], 0, wxRIGHT, 4 ); - - mMathProcessingType[4] = new wxRadioButton( - this, aVXThreadedRadioID, _("AV&X Threaded"), - wxDefaultPosition, wxDefaultSize, 0 ); - mMathProcessingType[4]->SetName(_("AVX Threaded")); - szrM->Add( mMathProcessingType[4], 0, wxRIGHT, 4 ); - - if(!EffectEqualization48x::GetMathCaps()->SSE) { - mMathProcessingType[1]->Disable(); - mMathProcessingType[2]->Disable(); - } - if(true) { //!EffectEqualization48x::GetMathCaps()->AVX) { not implemented - mMathProcessingType[3]->Disable(); - mMathProcessingType[4]->Disable(); - } - // update the control state - mMathProcessingType[0]->SetValue(true); - int mathPath=EffectEqualization48x::GetMathPath(); - if(mathPath&MATH_FUNCTION_SSE) { - mMathProcessingType[1]->SetValue(true); - if(mathPath&MATH_FUNCTION_THREADED) - mMathProcessingType[2]->SetValue(true); - } - if(false) { //mathPath&MATH_FUNCTION_AVX) { not implemented - mMathProcessingType[3]->SetValue(true); - if(mathPath&MATH_FUNCTION_THREADED) - mMathProcessingType[4]->SetValue(true); - } - btn = new wxButton( this, ID_BENCH, _("&Bench")); - szrM->Add( btn, 0, wxRIGHT, 4 ); - - szrV->Add( szrM, 0, wxALIGN_CENTER | wxALL, 0 ); - } -#endif - - // ------------------------------------------------------------------- - // Preview, OK, & Cancel buttons - // ------------------------------------------------------------------- - szrV->Add(CreateStdButtonSizer(this, ePreviewButton|eCancelButton|eOkButton), 0, wxEXPAND); - - SetAutoLayout(false); - - szrV->Show(szrG,true); - szrH->Show(szrI,true); - szrH->Show(szrL,false); - - SetSizerAndFit( szrV ); - SetSizeHints(GetSize()); - szrL->SetMinSize( szrI->GetSize() ); - - szrV->Show(szrC,true); - szrV->Show(szrG,false); - szrH->Show(szrI,false); - szrH->Show(szrL,true); - - return; -} - -// -// (Re)Create the choice control -// -void EqualizationDialog::CreateChoice() -{ - wxChoice *choice; - int i; - int numCurves = mCurves.GetCount(); - wxString *curveNames = new wxString[ numCurves ]; - - // Create an array of names - for( i = 0; i < numCurves; i++ ) - { - curveNames[ i ] = mCurves[ i ].Name; - } - - // Create the control - choice = new wxChoice( this, ID_CURVE, - wxDefaultPosition, wxDefaultSize, - numCurves, curveNames ); - - // Have an existing control? - if( mCurve ) - { - // Then detach it from its sizer and delete it - mCurveSizer->Detach( mCurve ); - delete mCurve; - } - - // Save control ptr and add to its sizer - mCurve = choice; - mCurve->SetName(_("Select Curve")); - mCurveSizer->Add( mCurve, 0 ); - - // Delete the array of names - delete [] curveNames; -} - -// -// Validate data -// -bool EqualizationDialog::Validate() -{ - // If editing a batch chain, we don't want to be using the unnamed curve so - // we offer to save it. - while (mDisallowCustom && curveName.IsSameAs(wxT("unnamed"))) - { - wxMessageBox(_("To use this EQ curve in a batch chain, please choose a new name for it.\nChoose the 'Save/Manage Curves...' button and rename the 'unnamed' curve, then use that one."), - _("EQ Curve needs a different name"), - wxOK | wxCENTRE, - this); - return false; - } - return true; -} - -// -// Populate the window with relevant variables -// -bool EqualizationDialog::TransferDataToWindow() -{ - // Set log or lin freq scale (affects interpolation as well) - mLinFreq->SetValue( linCheck ); - wxCommandEvent dummyEvent; - OnLinFreq(dummyEvent); // causes a CalcFilter - - mGridOnOff->SetValue( drawGrid ); // checks/unchecks the box on the interface - - MSlider->SetValue((M-1)/2); - M = 0; // force refresh in TransferDataFromWindow() - - dBMinSlider->SetValue((int)dBMin); - dBMin = 0; // force refresh in TransferDataFromWindow() - - dBMaxSlider->SetValue((int)dBMax); - dBMax = 0; // force refresh in TransferDataFromWindow() - - // Set initial curve - setCurve( curveName ); - - // Set graphic interpolation mode - mInterpChoice->SetSelection(interp); - - // Set Graphic (Fader) or Draw mode - if(drawMode) - { - mFaderOrDraw[0]->SetValue(true); - } - else - { - mFaderOrDraw[1]->SetValue(true); - UpdateGraphic(); - } - - return TransferDataFromWindow(); -} - -// -// Retrieve data from the window -// -bool EqualizationDialog::TransferDataFromWindow() -{ // Read the sliders and send to the panel - wxString tip; - - bool rr = false; - int dB = dBMinSlider->GetValue(); - if (dB != dBMin) { - rr = true; - dBMin = dB; - mPanel->dBMin = dBMin; -#if wxUSE_TOOLTIPS - tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)dBMin); - dBMinSlider->SetToolTip(tip); -#endif - } - - dB = dBMaxSlider->GetValue(); - if (dB != dBMax) { - rr = true; - dBMax = dB; - mPanel->dBMax = dBMax; -#if wxUSE_TOOLTIPS - tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)dBMax); - dBMaxSlider->SetToolTip(tip); -#endif - } - - // Refresh ruler if values have changed - if (rr) { - int w1, w2, h; - dBRuler->ruler.GetMaxSize(&w1, &h); - dBRuler->ruler.SetRange(dBMax, dBMin); - dBRuler->ruler.GetMaxSize(&w2, &h); - if( w1 != w2 ) // Reduces flicker - { - dBRuler->SetSize(wxSize(w2,h)); - szr1->Layout(); - LayoutEQSliders(); - szrG->Layout(); - freqRuler->Refresh(false); - } - dBRuler->Refresh(false); - } - - int m = 2 * MSlider->GetValue() + 1; // odd numbers only - if (m != M) { - M = m; - mPanel->M = M; - mMText->SetLabel(wxString::Format(wxT("%d"), M)); - mMText->SetName(mMText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) -#if wxUSE_TOOLTIPS - tip.Printf(wxT("%d"), M); - MSlider->SetToolTip(tip); -#endif - } - - mPanel->Refresh(false); - - return true; -} - -bool EqualizationDialog::CalcFilter() -{ - double loLog = log10(mLoFreq); - double hiLog = log10(mHiFreq); - double denom = hiLog - loLog; - - double delta = mHiFreq / ((double)(mWindowSize/2.)); - double val0; - double val1; - - bool lin = mFaderOrDraw[0]->GetValue() & mLinFreq->IsChecked(); - - if( lin ) - { - val0 = mLinEnvelope->GetValue(0.0); //no scaling required - saved as dB - val1 = mLinEnvelope->GetValue(1.0); - } - else - { - val0 = mLogEnvelope->GetValue(0.0); //no scaling required - saved as dB - val1 = mLogEnvelope->GetValue(1.0); - } - mFilterFuncR[0] = val0; - double freq = delta; - - int i; - for(i=1; i<=mWindowSize/2; i++) - { - double when; - if( lin ) - when = freq/mHiFreq; - else - when = (log10(freq) - loLog)/denom; - if(when < 0.) - { - mFilterFuncR[i] = val0; - } - else if(when > 1.0) - { - mFilterFuncR[i] = val1; - } - else - { - if( lin ) - mFilterFuncR[i] = mLinEnvelope->GetValue(when); - else - mFilterFuncR[i] = mLogEnvelope->GetValue(when); - } - freq += delta; - } - mFilterFuncR[mWindowSize/2] = val1; - - mFilterFuncR[0] = (float)(pow(10., mFilterFuncR[0]/20.)); - for(i=1;iIsChecked() ) // linear freq mode? + if( mLin ) // linear freq mode? { Envelope *env = mLinEnvelope; env->Flatten(0.); @@ -1850,15 +1506,15 @@ void EqualizationDialog::setCurve(int currentCurve) } if(changed) // not all points were loaded so switch to unnamed EnvelopeUpdated(); - mPanel->RecalcRequired = true; - mPanel->Refresh( false ); + mPanel->ForceRecalc(); } -void EqualizationDialog::setCurve() + +void EffectEqualization::setCurve() { setCurve((int) mCurves.GetCount()-1); } -void EqualizationDialog::setCurve(wxString curveName) +void EffectEqualization::setCurve(wxString curveName) { unsigned i = 0; for( i = 0; i < mCurves.GetCount(); i++ ) @@ -1876,35 +1532,29 @@ void EqualizationDialog::setCurve(wxString curveName) // // Set new curve selection and manage state of delete button // -void EqualizationDialog::Select( int curve ) +void EffectEqualization::Select( int curve ) { // Set current choice mCurve->SetSelection( curve ); - curveName = mCurves[ curve ].Name; - - // If the "unnamed" curve became active - if( (unsigned int)curve + 1U == mCurve->GetCount() ) - { - // Prevent focus from being lost - if( mDelete->FindFocus() == mDelete ) - { - mCurve->SetFocus(); - } - } + mCurveName = mCurves[ curve ].Name; } // // Capture updated envelope // -void EqualizationDialog::EnvelopeUpdated() +void EffectEqualization::EnvelopeUpdated() { - if( mFaderOrDraw[0]->GetValue() & mLinFreq->IsChecked() ) + if (mDraw && mLin) + { EnvelopeUpdated(mLinEnvelope, true); + } else + { EnvelopeUpdated(mLogEnvelope, false); + } } -void EqualizationDialog::EnvelopeUpdated(Envelope *env, bool lin) +void EffectEqualization::EnvelopeUpdated(Envelope *env, bool lin) { // Allocate and populate point arrays int numPoints = env->GetNumberOfPoints(); @@ -1960,7 +1610,7 @@ void EqualizationDialog::EnvelopeUpdated(Envelope *env, bool lin) // // Process XML tags and handle the ones we recognize // -bool EqualizationDialog::HandleXMLTag(const wxChar *tag, const wxChar **attrs) +bool EffectEqualization::HandleXMLTag(const wxChar *tag, const wxChar **attrs) { // May want to add a version strings... if( !wxStrcmp( tag, wxT("equalizationeffect") ) ) @@ -2061,7 +1711,7 @@ bool EqualizationDialog::HandleXMLTag(const wxChar *tag, const wxChar **attrs) // // Return handler for recognized tags // -XMLTagHandler *EqualizationDialog::HandleXMLChild(const wxChar *tag) +XMLTagHandler *EffectEqualization::HandleXMLChild(const wxChar *tag) { if( !wxStrcmp( tag, wxT("equalizationeffect") ) ) { @@ -2084,7 +1734,7 @@ XMLTagHandler *EqualizationDialog::HandleXMLChild(const wxChar *tag) // // Write all of the curves to the XML file // -void EqualizationDialog::WriteXML(XMLWriter &xmlFile) +void EffectEqualization::WriteXML(XMLWriter &xmlFile) { // Start our heirarchy xmlFile.StartTag( wxT( "equalizationeffect" ) ); @@ -2118,63 +1768,14 @@ void EqualizationDialog::WriteXML(XMLWriter &xmlFile) xmlFile.EndTag( wxT( "equalizationeffect" ) ); } -// WDR: handler implementations for EqualizationDialog - +/////////////////////////////////////////////////////////////////////////////// // -// Graphic EQ slider was adjusted +// All EffectEqualization methods beyond this point interact with the UI, so +// can't be called while the UI is not displayed. // +/////////////////////////////////////////////////////////////////////////////// -void EqualizationDialog::OnSlider(wxCommandEvent & event) -{ - wxSliderBugfix *s = (wxSliderBugfix *)event.GetEventObject(); - for (int i = 0; (i < NUMBER_OF_BANDS) && (thirdOct[i] <= mHiFreq); ++i) - { - if( s == m_sliders[i]) - { - int posn = m_sliders[i]->GetValue(); - if( wxGetKeyState(WXK_SHIFT) ) - { - if( posn > m_sliders_old[i] ) - m_EQVals[i] += (float).1; - else - if( posn < m_sliders_old[i] ) - m_EQVals[i] -= .1f; - } - else - m_EQVals[i] += (posn - m_sliders_old[i]); - if( m_EQVals[i] > 20. ) - m_EQVals[i] = 20.; - if( m_EQVals[i] < -20. ) - m_EQVals[i] = -20.; - int newPosn = (int)m_EQVals[i]; - m_sliders[i]->SetValue( newPosn ); - m_sliders_old[i] = newPosn; -#if wxUSE_TOOLTIPS - wxString tip; - if( thirdOct[i] < 1000.) - tip.Printf( wxT("%dHz\n%.1fdB"), (int)thirdOct[i], m_EQVals[i] ); - else - tip.Printf( wxT("%gkHz\n%.1fdB"), thirdOct[i]/1000., m_EQVals[i] ); - s->SetToolTip(tip); -#endif - break; - } - } - GraphicEQ(mLogEnvelope); - EnvelopeUpdated(); -} - -void EqualizationDialog::OnInterp(wxCommandEvent & WXUNUSED(event)) -{ - if(mFaderOrDraw[1]->GetValue()) - { - GraphicEQ(mLogEnvelope); - EnvelopeUpdated(); - } - interp = mInterpChoice->GetSelection(); -} - -void EqualizationDialog::LayoutEQSliders() +void EffectEqualization::LayoutEQSliders() { //layout the Graphic EQ sizer here wxSize szrGSize = szrG->GetSize(); //total size we have to play with @@ -2201,265 +1802,25 @@ void EqualizationDialog::LayoutEQSliders() double loLog = log10(mLoFreq); double hiLog = log10(mHiFreq); double denom = hiLog - loLog; - for (int i = 1; (i < NUMBER_OF_BANDS) && (thirdOct[i] <= mHiFreq); ++i) //go along the spacers + for (int i = 1; (i < NUMBER_OF_BANDS) && (kThirdOct[i] <= mHiFreq); ++i) //go along the spacers { - float posn = range*(log10(thirdOct[i])-loLog)/denom; //centre of this slider, from start + float posn = range*(log10(kThirdOct[i])-loLog)/denom; //centre of this slider, from start w = start + ((int)(posn+.5)) - EQsliderSize.x/2; //LH edge of slider, from 0 w = w - so_far; //gap needed to put it here szrG->SetItemMinSize((size_t)(i*2), w, -1); //set spacers so that sliders aligned with ruler so_far += (w + EQsliderSize.x); } - RefreshRect(wxRect(szrG->GetPosition(),szrGSize)); + mUIParent->RefreshRect(wxRect(szrG->GetPosition(), szrGSize)); } -void EqualizationDialog::GraphicEQ(Envelope *env) -{ - // JKC: 'value' is for height of curve. - // The 0.0 initial value would only get used if NUM_PTS were 0. - double value = 0.0; - double dist, span, s; - - env->Flatten(0.); - env->SetTrackLen(1.0); - - int n = mInterpChoice->GetSelection(); - switch( n ) - { - case 0: // B-spline - { - int minF = 0; - for(int i=0; i whenSliders[bandsInUse-1] ) //after last fader - { - dist = whens[i] - whenSliders[bandsInUse-1]; - span = whenSliders[bandsInUse-1] - whenSliders[bandsInUse-2]; - s = dist/span; - if( s > 1.5 ) - value = 0.; - else if( s > .5 ) - value = m_EQVals[bandsInUse-1]*(s - 1.5)*(s - 1.5)/2.; - else - value = m_EQVals[bandsInUse-1]*(.75 - s*s) + - m_EQVals[bandsInUse-2]*(s - .5)*(s - .5)/2.; - } - else //normal case - { - dist = whens[i] - whenSliders[minF]; - span = whenSliders[minF+1] - whenSliders[minF]; - s = dist/span; - if(s < .5 ) - { - value = m_EQVals[minF]*(0.75 - s*s); - if( minF+1 < bandsInUse ) - value += m_EQVals[minF+1]*(s+.5)*(s+.5)/2.; - if( minF-1 >= 0 ) - value += m_EQVals[minF-1]*(s-.5)*(s-.5)/2.; - } - else - { - value = m_EQVals[minF]*(s-1.5)*(s-1.5)/2.; - if( minF+1 < bandsInUse ) - value += m_EQVals[minF+1]*(.75-(1.-s)*(1.-s)); - if( minF+2 < bandsInUse ) - value += m_EQVals[minF+2]*(s-.5)*(s-.5)/2.; - } - } - } - if(whens[i]<=0.) - env->Move( 0., value ); - env->Insert( whens[i], value ); - } - env->Move( 1., value ); - break; - } - - case 1: // Cosine squared - { - int minF = 0; - for(int i=0; i whenSliders[bandsInUse-1] ) //after last fader - { - span = whenSliders[bandsInUse-1] - whenSliders[bandsInUse-2]; - dist = whens[i] - whenSliders[bandsInUse-1]; - if( dist < span ) - value = m_EQVals[bandsInUse-1]*(1. + cos(M_PI*dist/span))/2.; - else - value = 0.; - } - else //normal case - { - span = whenSliders[minF+1] - whenSliders[minF]; - dist = whenSliders[minF+1] - whens[i]; - value = m_EQVals[minF]*(1. + cos(M_PI*(span-dist)/span))/2. + - m_EQVals[minF+1]*(1. + cos(M_PI*dist/span))/2.; - } - } - if(whens[i]<=0.) - env->Move( 0., value ); - env->Insert( whens[i], value ); - } - env->Move( 1., value ); - break; - } - - case 2: // Cubic Spline - { - double y2[NUMBER_OF_BANDS+1]; - m_EQVals[bandsInUse] = m_EQVals[bandsInUse-1]; - spline(whenSliders, m_EQVals, bandsInUse+1, y2); - for(double xf=0; xf<1.; xf+=1./NUM_PTS) - { - env->Insert(xf, splint(whenSliders, m_EQVals, bandsInUse+1, y2, xf)); - } - break; - } - } - - mPanel->RecalcRequired = true; - mPanel->Refresh( false ); -} - -void EqualizationDialog::spline(double x[], double y[], int n, double y2[]) -{ - int i; - double p, sig, *u = new double[n]; - - y2[0] = 0.; // - u[0] = 0.; //'natural' boundary conditions - for(i=1;i=0;i--) - y2[i] = y2[i]*y2[i+1] + u[i]; - - delete [] u; -} - -double EqualizationDialog::splint(double x[], double y[], int n, double y2[], double xr) -{ - double a, b, h; - static double xlast = 0.; // remember last x value requested - static int k = 0; // and which interval we were in - - if( xr < xlast ) - k = 0; // gone back to start, (or somewhere to the left) - xlast = xr; - while( (x[k] <= xr) && (k < n-1) ) - k++; - k--; - h = x[k+1] - x[k]; - a = ( x[k+1] - xr )/h; - b = (xr - x[k])/h; - return( a*y[k]+b*y[k+1]+((a*a*a-a)*y2[k]+(b*b*b-b)*y2[k+1])*h*h/6.); -} - -void EqualizationDialog::OnDrawRadio(wxCommandEvent & WXUNUSED(event)) -{ - int numPoints = mLogEnvelope->GetNumberOfPoints(); - double *when = new double[ numPoints ]; - double *value = new double[ numPoints ]; - double deltadB = 0.1; - double dx, dy, dx1, dy1, err; - - mLogEnvelope->GetPoints( when, value, numPoints ); - - // set 'unnamed' as the selected curve - EnvelopeUpdated(); - - bool flag = true; - while (flag) - { - flag = false; - int numDeleted = 0; - mLogEnvelope->GetPoints( when, value, numPoints ); - for(int j=0;jDelete(j+1); - numPoints--; - numDeleted++; - flag = true; - } - } - } - delete [] when; - delete [] value; - - if(mLinFreq->IsChecked()) - { - EnvLogToLin(); - mPanel->mEnvelope = mLinEnvelope; - freqRuler->ruler.SetLog(false); - freqRuler->ruler.SetRange(0, mHiFreq); - } - - szrV->Show(szrG,false); - szrH->Show(szrI,false); - szrH->Show(szrL,true); - Layout(); - mPanel->RecalcRequired = true; // it may have changed slightly due to the deletion of points - mPanel->Refresh( false ); - drawMode = true; -} - -void EqualizationDialog::OnSliderRadio(wxCommandEvent & WXUNUSED(event)) -{ - UpdateGraphic(); -} - -void EqualizationDialog::UpdateGraphic() +void EffectEqualization::UpdateGraphic() { double loLog = log10(mLoFreq); double hiLog = log10(mHiFreq); double denom = hiLog - loLog; - if(mLinFreq->IsChecked()) //going from lin to log freq scale + if(mLin) //going from lin to log freq scale { // add some extra points to the linear envelope for the graphic to follow double step = pow(2., 1./12.); // twelve steps per octave double when,value; @@ -2471,85 +1832,59 @@ void EqualizationDialog::UpdateGraphic() } EnvLinToLog(); - mPanel->mEnvelope = mLogEnvelope; - freqRuler->ruler.SetLog(true); - freqRuler->ruler.SetRange(mLoFreq, mHiFreq); + mEnvelope = mLogEnvelope; + mFreqRuler->ruler.SetLog(true); + mFreqRuler->ruler.SetRange(mLoFreq, mHiFreq); } - bandsInUse = 0; - while(thirdOct[bandsInUse] <= mHiFreq) { - bandsInUse++; - if(bandsInUse == NUMBER_OF_BANDS) - break; - } - - for (int i = 0; iGetValue(whenSliders[i]); //set initial values of sliders - if( m_EQVals[i] > 20.) - m_EQVals[i] = 20.; - if( m_EQVals[i] < -20.) - m_EQVals[i] = -20.; + mWhenSliders[i] = (log10(kThirdOct[i])-loLog)/denom; + mEQVals[i] = mLogEnvelope->GetValue(mWhenSliders[i]); //set initial values of sliders + if( mEQVals[i] > 20.) + mEQVals[i] = 20.; + if( mEQVals[i] < -20.) + mEQVals[i] = -20.; } ErrMin(); //move sliders to minimise error - for (int i = 0; iSetValue(lrint(m_EQVals[i])); //actually set slider positions - m_sliders_old[i] = m_sliders[i]->GetValue(); -#if wxUSE_TOOLTIPS + mSliders[i]->SetValue(lrint(mEQVals[i])); //actually set slider positions + mSlidersOld[i] = mSliders[i]->GetValue(); wxString tip; - if( thirdOct[i] < 1000.) - tip.Printf( wxT("%dHz\n%.1fdB"), (int)thirdOct[i], m_EQVals[i] ); + if( kThirdOct[i] < 1000.) + tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] ); else - tip.Printf( wxT("%gkHz\n%.1fdB"), thirdOct[i]/1000., m_EQVals[i] ); - m_sliders[i]->SetToolTip(tip); -#endif + tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] ); + mSliders[i]->SetToolTip(tip); } + szrV->Show(szrG,true); // eq sliders szrH->Show(szrI,true); // interpolation choice szrH->Show(szrL,false); // linear freq checkbox - Layout(); // Make all sizers get resized first - LayoutEQSliders(); // Then layout sliders - Layout(); // And layout again to resize dialog + mUIParent->Layout(); + wxGetTopLevelParent(mUIParent)->Layout(); +// mUIParent->Layout(); // Make all sizers get resized first + LayoutEQSliders(); // Then layout sliders + mUIParent->Layout(); + wxGetTopLevelParent(mUIParent)->Layout(); +// mUIParent->Layout(); // And layout again to resize dialog - wxSize wsz = GetSize(); + wxSize wsz = mUIParent->GetSize(); wxSize ssz = szrV->GetSize(); if (ssz.x > wsz.x || ssz.y > wsz.y) { - Fit(); + mUIParent->Fit(); } + GraphicEQ(mLogEnvelope); - drawMode = false; + mDrawMode = false; } -void EqualizationDialog::OnLinFreq(wxCommandEvent & WXUNUSED(event)) -{ - if(mLinFreq->IsChecked()) //going from log to lin freq scale - { - freqRuler->ruler.SetLog(false); - freqRuler->ruler.SetRange(0, mHiFreq); - EnvLogToLin(); - mPanel->mEnvelope = mLinEnvelope; - linCheck = true; - } - else //going from lin to log freq scale - { - freqRuler->ruler.SetLog(true); - freqRuler->ruler.SetRange(mLoFreq, mHiFreq); - EnvLinToLog(); - mPanel->mEnvelope = mLogEnvelope; - linCheck = false; - } - mPanel->Recalc(); - freqRuler->Refresh(false); - mPanel->Refresh(false); -} - -void EqualizationDialog::EnvLogToLin(void) +void EffectEqualization::EnvLogToLin(void) { int numPoints = mLogEnvelope->GetNumberOfPoints(); if( numPoints == 0 ) @@ -2576,7 +1911,7 @@ void EqualizationDialog::EnvLogToLin(void) delete [] value; } -void EqualizationDialog::EnvLinToLog(void) +void EffectEqualization::EnvLinToLog(void) { int numPoints = mLinEnvelope->GetNumberOfPoints(); if( numPoints == 0 ) @@ -2618,13 +1953,13 @@ void EqualizationDialog::EnvLinToLog(void) EnvelopeUpdated(mLogEnvelope, false); } -void EqualizationDialog::ErrMin(void) +void EffectEqualization::ErrMin(void) { double vals[NUM_PTS]; int i; double error = 0.0; double oldError = 0.0; - double m_EQValsOld = 0.0; + double mEQValsOld = 0.0; double correction = 1.6; bool flag; int j=0; @@ -2638,20 +1973,20 @@ void EqualizationDialog::ErrMin(void) testEnvelope->CopyFrom(mLogEnvelope, 0.0, 1.0); for(i=0; i < NUM_PTS; i++) - vals[i] = testEnvelope->GetValue(whens[i]); + vals[i] = testEnvelope->GetValue(mWhens[i]); // Do error minimisation error = 0.; GraphicEQ(testEnvelope); for(i=0; i < NUM_PTS; i++) //calc initial error { - double err = vals[i] - testEnvelope->GetValue(whens[i]); + double err = vals[i] - testEnvelope->GetValue(mWhens[i]); error += err*err; } oldError = error; - while( j < bandsInUse*12 ) //loop over the sliders a number of times + while( j < mBandsInUse*12 ) //loop over the sliders a number of times { - i = j%bandsInUse; //use this slider + i = j%mBandsInUse; //use this slider if( (j > 0) & (i == 0) ) // if we've come back to the first slider again... { if( correction > 0 ) @@ -2663,59 +1998,376 @@ void EqualizationDialog::ErrMin(void) do { oldError = error; - m_EQValsOld = m_EQVals[i]; - m_EQVals[i] += correction; //move fader value - if( m_EQVals[i] > 20. ) + mEQValsOld = mEQVals[i]; + mEQVals[i] += correction; //move fader value + if( mEQVals[i] > 20. ) { - m_EQVals[i] = 20.; + mEQVals[i] = 20.; flag = false; } - if( m_EQVals[i] < -20. ) + if( mEQVals[i] < -20. ) { - m_EQVals[i] = -20.; + mEQVals[i] = -20.; flag = false; } GraphicEQ(testEnvelope); //calculate envelope error = 0.; for(int k=0; k < NUM_PTS; k++) //calculate error { - double err = vals[k] - testEnvelope->GetValue(whens[k]); + double err = vals[k] - testEnvelope->GetValue(mWhens[k]); error += err*err; } } while( (error < oldError) & flag ); if( error > oldError ) { - m_EQVals[i] = m_EQValsOld; //last one didn't work + mEQVals[i] = mEQValsOld; //last one didn't work error = oldError; } else oldError = error; - if( error < .0025 * bandsInUse) + if( error < .0025 * mBandsInUse) break; // close enuff j++; //try next slider } - if( error > .0025 * bandsInUse ) // not within 0.05dB on each slider, on average + if( error > .0025 * mBandsInUse ) // not within 0.05dB on each slider, on average { Select( (int) mCurves.GetCount()-1 ); EnvelopeUpdated(testEnvelope, false); } delete testEnvelope; - testEnvelope = NULL; } -void EqualizationDialog::OnSliderM(wxCommandEvent & WXUNUSED(event)) +void EffectEqualization::GraphicEQ(Envelope *env) +{ + // JKC: 'value' is for height of curve. + // The 0.0 initial value would only get used if NUM_PTS were 0. + double value = 0.0; + double dist, span, s; + + env->Flatten(0.); + env->SetTrackLen(1.0); + + switch( mInterp ) + { + case kBspline: // B-spline + { + int minF = 0; + for(int i=0; i mWhenSliders[mBandsInUse-1] ) //after last fader + { + dist = mWhens[i] - mWhenSliders[mBandsInUse-1]; + span = mWhenSliders[mBandsInUse-1] - mWhenSliders[mBandsInUse-2]; + s = dist/span; + if( s > 1.5 ) + value = 0.; + else if( s > .5 ) + value = mEQVals[mBandsInUse-1]*(s - 1.5)*(s - 1.5)/2.; + else + value = mEQVals[mBandsInUse-1]*(.75 - s*s) + + mEQVals[mBandsInUse-2]*(s - .5)*(s - .5)/2.; + } + else //normal case + { + dist = mWhens[i] - mWhenSliders[minF]; + span = mWhenSliders[minF+1] - mWhenSliders[minF]; + s = dist/span; + if(s < .5 ) + { + value = mEQVals[minF]*(0.75 - s*s); + if( minF+1 < mBandsInUse ) + value += mEQVals[minF+1]*(s+.5)*(s+.5)/2.; + if( minF-1 >= 0 ) + value += mEQVals[minF-1]*(s-.5)*(s-.5)/2.; + } + else + { + value = mEQVals[minF]*(s-1.5)*(s-1.5)/2.; + if( minF+1 < mBandsInUse ) + value += mEQVals[minF+1]*(.75-(1.-s)*(1.-s)); + if( minF+2 < mBandsInUse ) + value += mEQVals[minF+2]*(s-.5)*(s-.5)/2.; + } + } + } + if(mWhens[i]<=0.) + env->Move( 0., value ); + env->Insert( mWhens[i], value ); + } + env->Move( 1., value ); + break; + } + + case kCosine: // Cosine squared + { + int minF = 0; + for(int i=0; i mWhenSliders[mBandsInUse-1] ) //after last fader + { + span = mWhenSliders[mBandsInUse-1] - mWhenSliders[mBandsInUse-2]; + dist = mWhens[i] - mWhenSliders[mBandsInUse-1]; + if( dist < span ) + value = mEQVals[mBandsInUse-1]*(1. + cos(M_PI*dist/span))/2.; + else + value = 0.; + } + else //normal case + { + span = mWhenSliders[minF+1] - mWhenSliders[minF]; + dist = mWhenSliders[minF+1] - mWhens[i]; + value = mEQVals[minF]*(1. + cos(M_PI*(span-dist)/span))/2. + + mEQVals[minF+1]*(1. + cos(M_PI*dist/span))/2.; + } + } + if(mWhens[i]<=0.) + env->Move( 0., value ); + env->Insert( mWhens[i], value ); + } + env->Move( 1., value ); + break; + } + + case kCubic: // Cubic Spline + { + double y2[NUMBER_OF_BANDS+1]; + mEQVals[mBandsInUse] = mEQVals[mBandsInUse-1]; + spline(mWhenSliders, mEQVals, mBandsInUse+1, y2); + for(double xf=0; xf<1.; xf+=1./NUM_PTS) + { + env->Insert(xf, splint(mWhenSliders, mEQVals, mBandsInUse+1, y2, xf)); + } + break; + } + } + + mPanel->ForceRecalc(); +} + +void EffectEqualization::spline(double x[], double y[], int n, double y2[]) +{ + int i; + double p, sig, *u = new double[n]; + + y2[0] = 0.; // + u[0] = 0.; //'natural' boundary conditions + for(i=1;i=0;i--) + y2[i] = y2[i]*y2[i+1] + u[i]; + + delete [] u; +} + +double EffectEqualization::splint(double x[], double y[], int n, double y2[], double xr) +{ + double a, b, h; + static double xlast = 0.; // remember last x value requested + static int k = 0; // and which interval we were in + + if( xr < xlast ) + k = 0; // gone back to start, (or somewhere to the left) + xlast = xr; + while( (x[k] <= xr) && (k < n-1) ) + k++; + k--; + h = x[k+1] - x[k]; + a = ( x[k+1] - xr )/h; + b = (xr - x[k])/h; + return( a*y[k]+b*y[k+1]+((a*a*a-a)*y2[k]+(b*b*b-b)*y2[k+1])*h*h/6.); +} + +void EffectEqualization::OnSize(wxSizeEvent & event) +{ + mUIParent->Layout(); + + if (!mDrawMode) + { + LayoutEQSliders(); + } + + event.Skip(); +} + +void EffectEqualization::OnErase(wxEraseEvent & WXUNUSED(event)) +{ + // Ignore it +} + +void EffectEqualization::OnPaint(wxPaintEvent & event) +{ + wxPaintDC dc(mUIParent); + +#if defined(__WXGTK__) + dc.SetBackground(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE))); +#endif + + dc.Clear(); + + event.Skip(); +} + +void EffectEqualization::OnSlider(wxCommandEvent & event) +{ + wxSlider *s = (wxSlider *)event.GetEventObject(); + for (int i = 0; i < mBandsInUse; i++) + { + if( s == mSliders[i]) + { + int posn = mSliders[i]->GetValue(); + if( wxGetKeyState(WXK_SHIFT) ) + { + if( posn > mSlidersOld[i] ) + mEQVals[i] += (float).1; + else + if( posn < mSlidersOld[i] ) + mEQVals[i] -= .1f; + } + else + mEQVals[i] += (posn - mSlidersOld[i]); + if( mEQVals[i] > 20. ) + mEQVals[i] = 20.; + if( mEQVals[i] < -20. ) + mEQVals[i] = -20.; + int newPosn = (int)mEQVals[i]; + mSliders[i]->SetValue( newPosn ); + mSlidersOld[i] = newPosn; + wxString tip; + if( kThirdOct[i] < 1000.) + tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] ); + else + tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] ); + s->SetToolTip(tip); + break; + } + } + GraphicEQ(mLogEnvelope); + EnvelopeUpdated(); +} + +void EffectEqualization::OnInterp(wxCommandEvent & WXUNUSED(event)) +{ + if(mGraphic->GetValue()) + { + GraphicEQ(mLogEnvelope); + EnvelopeUpdated(); + } + mInterp = mInterpChoice->GetSelection(); +} + +void EffectEqualization::OnDrawMode(wxCommandEvent & WXUNUSED(event)) +{ + int numPoints = mLogEnvelope->GetNumberOfPoints(); + double *when = new double[ numPoints ]; + double *value = new double[ numPoints ]; + double deltadB = 0.1; + double dx, dy, dx1, dy1, err; + + mLogEnvelope->GetPoints( when, value, numPoints ); + + // set 'unnamed' as the selected curve + EnvelopeUpdated(); + + bool flag = true; + while (flag) + { + flag = false; + int numDeleted = 0; + mLogEnvelope->GetPoints( when, value, numPoints ); + for(int j=0;jDelete(j+1); + numPoints--; + numDeleted++; + flag = true; + } + } + } + delete [] when; + delete [] value; + + if(mLin) + { + EnvLogToLin(); + mEnvelope = mLinEnvelope; + mFreqRuler->ruler.SetLog(false); + mFreqRuler->ruler.SetRange(0, mHiFreq); + } + + szrV->Show(szrG,false); + szrH->Show(szrI,false); + szrH->Show(szrL,true); + mUIParent->Layout(); + wxGetTopLevelParent(mUIParent)->Layout(); + mPanel->ForceRecalc(); // it may have changed slightly due to the deletion of points + mDrawMode = true; +} + +void EffectEqualization::OnGraphicMode(wxCommandEvent & WXUNUSED(event)) +{ + UpdateGraphic(); + + mDrawMode = false; +} + +void EffectEqualization::OnSliderM(wxCommandEvent & WXUNUSED(event)) { TransferDataFromWindow(); - mPanel->RecalcRequired = true; + mPanel->ForceRecalc(); } -void EqualizationDialog::OnSliderDBMIN(wxCommandEvent & WXUNUSED(event)) +void EffectEqualization::OnSliderDBMIN(wxCommandEvent & WXUNUSED(event)) { TransferDataFromWindow(); } -void EqualizationDialog::OnSliderDBMAX(wxCommandEvent & WXUNUSED(event)) +void EffectEqualization::OnSliderDBMAX(wxCommandEvent & WXUNUSED(event)) { TransferDataFromWindow(); } @@ -2723,73 +2375,66 @@ void EqualizationDialog::OnSliderDBMAX(wxCommandEvent & WXUNUSED(event)) // // New curve was selected // -void EqualizationDialog::OnCurve(wxCommandEvent & WXUNUSED(event)) +void EffectEqualization::OnCurve(wxCommandEvent & WXUNUSED(event)) { // Select new curve -#if wxCHECK_VERSION(2, 6, 2) && !defined(__WXX11__) setCurve( mCurve->GetCurrentSelection() ); -#else - setCurve( mCurve->GetSelection() ); -#endif - if( !drawMode ) + if( !mDrawMode ) UpdateGraphic(); } // // User wants to modify the list in some way // -void EqualizationDialog::OnManage(wxCommandEvent & WXUNUSED(event)) +void EffectEqualization::OnManage(wxCommandEvent & WXUNUSED(event)) { - EditCurvesDialog d(this, mCurve->GetSelection()); + EditCurvesDialog d(mUIParent, this, mCurve->GetSelection()); d.ShowModal(); } -void EqualizationDialog::OnClear(wxCommandEvent & WXUNUSED(event)) +void EffectEqualization::OnClear(wxCommandEvent & WXUNUSED(event)) { mLogEnvelope->Flatten(0.); mLogEnvelope->SetTrackLen(1.0); mLinEnvelope->Flatten(0.); mLinEnvelope->SetTrackLen(1.0); - mPanel->RecalcRequired = true; - mPanel->Refresh(false); - if( !drawMode ) + mPanel->ForceRecalc(); + if( !mDrawMode ) { - for( int i=0; i< bandsInUse; i++) + for( int i=0; i< mBandsInUse; i++) { - m_sliders[i]->SetValue(0); - m_sliders_old[i] = 0; - m_EQVals[i] = 0.; -#if wxUSE_TOOLTIPS + mSliders[i]->SetValue(0); + mSlidersOld[i] = 0; + mEQVals[i] = 0.; + wxString tip; - if( thirdOct[i] < 1000.) - tip.Printf( wxT("%dHz\n%.1fdB"), (int)thirdOct[i], 0. ); + if( kThirdOct[i] < 1000.) + tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], 0. ); else - tip.Printf( wxT("%gkHz\n%.1fdB"), thirdOct[i]/1000., 0. ); - m_sliders[i]->SetToolTip(tip); -#endif + tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., 0. ); + mSliders[i]->SetToolTip(tip); } } EnvelopeUpdated(); } -void EqualizationDialog::OnInvert(wxCommandEvent & WXUNUSED(event)) // Inverts any curve +void EffectEqualization::OnInvert(wxCommandEvent & WXUNUSED(event)) // Inverts any curve { - if(!drawMode) // Graphic (Slider) mode. Invert the sliders. + if(!mDrawMode) // Graphic (Slider) mode. Invert the sliders. { - for (int i = 0; (i < NUMBER_OF_BANDS) && (thirdOct[i] <= mHiFreq); ++i) + for (int i = 0; i < mBandsInUse; i++) { - m_EQVals[i] = -m_EQVals[i]; - int newPosn = (int)m_EQVals[i]; - m_sliders[i]->SetValue( newPosn ); - m_sliders_old[i] = newPosn; -#if wxUSE_TOOLTIPS + mEQVals[i] = -mEQVals[i]; + int newPosn = (int)mEQVals[i]; + mSliders[i]->SetValue( newPosn ); + mSlidersOld[i] = newPosn; + wxString tip; - if( thirdOct[i] < 1000.) - tip.Printf( wxT("%dHz\n%.1fdB"), (int)thirdOct[i], m_EQVals[i] ); + if( kThirdOct[i] < 1000.) + tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] ); else - tip.Printf( wxT("%gkHz\n%.1fdB"), thirdOct[i]/1000., m_EQVals[i] ); - m_sliders[i]->SetToolTip(tip); -#endif + tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] ); + mSliders[i]->SetToolTip(tip); } GraphicEQ(mLogEnvelope); } @@ -2800,7 +2445,7 @@ void EqualizationDialog::OnInvert(wxCommandEvent & WXUNUSED(event)) // Inverts a // determine if log or lin curve is the current one // and find out how many points are in the curve - if(mLinFreq->IsChecked()) // lin freq scale and so envelope + if(mLin) // lin freq scale and so envelope { lin = true; numPoints = mLinEnvelope->GetNumberOfPoints(); @@ -2842,125 +2487,337 @@ void EqualizationDialog::OnInvert(wxCommandEvent & WXUNUSED(event)) // Inverts a } // and update the display etc - mPanel->Recalc(); - mPanel->Refresh(false); + mPanel->ForceRecalc(); EnvelopeUpdated(); } -void EqualizationDialog::OnErase(wxEraseEvent & WXUNUSED(event)) +void EffectEqualization::OnGridOnOff(wxCommandEvent & WXUNUSED(event)) { - // Ignore it -} - -void EqualizationDialog::OnPaint(wxPaintEvent & WXUNUSED(event)) -{ - wxPaintDC dc(this); - -#if defined(__WXGTK__) - dc.SetBackground(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE))); -#endif - - dc.Clear(); -} - -void EqualizationDialog::OnSize(wxSizeEvent & event) -{ - Layout(); - - if (mFaderOrDraw[1]->GetValue()) - { - LayoutEQSliders(); - } - - event.Skip(); -} - -void EqualizationDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - m_pEffect->Preview(); - mPanel->RecalcRequired = true; - //redraw the window. - Refresh(); - Update(); - //v Restore previous values? -} - -void EqualizationDialog::OnGridOnOff(wxCommandEvent & WXUNUSED(event)) -{ - if( mGridOnOff->IsChecked() ) - drawGrid = true; - else - drawGrid = false; + mDrawGrid = mGridOnOff->IsChecked(); mPanel->Refresh(false); } -void EqualizationDialog::RevertCustom() +void EffectEqualization::OnLinFreq(wxCommandEvent & WXUNUSED(event)) { - EQCurve &realCustom = mCurves[mCurves.GetCount()-1]; - wxASSERT(realCustom.Name.IsSameAs(wxT("unnamed"))); - realCustom.points = mCustomBackup.points; -} - -void EqualizationDialog::Finish(bool ok) -{ - if(mLogEnvelope) - delete mLogEnvelope; - mLogEnvelope = NULL; - if(mLinEnvelope) - delete mLinEnvelope; - mLinEnvelope = NULL; - mPanel = NULL; - - EndModal(ok); -} - -void EqualizationDialog::OnCancel(wxCommandEvent & WXUNUSED(event)) -{ - if (mDisallowCustom) - RevertCustom(); - - Finish(false); -} - -void EqualizationDialog::OnOk(wxCommandEvent & event) -{ - TransferDataFromWindow(); - - if( Validate() ) + mLin = mLinFreq->IsChecked(); + if(mLin) //going from log to lin freq scale { - // Update unnamed curve (so it's there for next time) - //(done in a hurry, may not be the neatest -MJS) - if( !mDisallowCustom) - { - if ( mDirty && drawMode ) - { - int i, j; - int numPoints = mLogEnvelope->GetNumberOfPoints(); - double *when = new double[ numPoints ]; - double *value = new double[ numPoints ]; - mLogEnvelope->GetPoints( when, value, numPoints ); - for(i=0,j=0;jvalue[i+1]-.05) && - (value[i+1]value[i+2]-.05) ) - { // within < 0.05 dB? - mLogEnvelope->Delete(j+1); - numPoints--; - j--; - } - } - delete [] when; - delete [] value; - Select( (int) mCurves.GetCount()-1 ); - } - SaveCurves(); - } - Finish(true); + mFreqRuler->ruler.SetLog(false); + mFreqRuler->ruler.SetRange(0, mHiFreq); + EnvLogToLin(); + mEnvelope = mLinEnvelope; + mLin = true; } - else + else //going from lin to log freq scale { - event.Skip(false); + mFreqRuler->ruler.SetLog(true); + mFreqRuler->ruler.SetRange(mLoFreq, mHiFreq); + EnvLinToLog(); + mEnvelope = mLogEnvelope; + mLin = false; + } + mFreqRuler->Refresh(false); + mPanel->ForceRecalc(); +} + +#ifdef EXPERIMENTAL_EQ_SSE_THREADED + +void EffectEqualization::OnProcessingRadio(wxCommandEvent & event) +{ + int testEvent=event.GetId(); + switch(testEvent) + { + case defaultMathRadioID: EffectEqualization48x::SetMathPath(MATH_FUNCTION_ORIGINAL); + break; + case sSERadioID: EffectEqualization48x::SetMathPath(MATH_FUNCTION_SSE); + break; + case sSEThreadedRadioID: EffectEqualization48x::SetMathPath(MATH_FUNCTION_THREADED | MATH_FUNCTION_SSE); + break; + case aVXRadioID: testEvent=2; + break; + case aVXThreadedRadioID: testEvent=2; + break; + } + +}; + +void EffectEqualization::OnBench( wxCommandEvent & event) +{ + m_pEffect->mBench=true; + OnOk(event); +} + +#endif + +//---------------------------------------------------------------------------- +// EqualizationPanel +//---------------------------------------------------------------------------- + +BEGIN_EVENT_TABLE(EqualizationPanel, wxPanel) + EVT_PAINT(EqualizationPanel::OnPaint) + EVT_MOUSE_EVENTS(EqualizationPanel::OnMouseEvent) + EVT_MOUSE_CAPTURE_LOST(EqualizationPanel::OnCaptureLost) + EVT_SIZE(EqualizationPanel::OnSize) +END_EVENT_TABLE() + +EqualizationPanel::EqualizationPanel(EffectEqualization *effect, wxWindow *parent) +: wxPanel(parent) +{ + mParent = parent; + mEffect = effect; + + mOutr = NULL; + mOuti = NULL; + + mBitmap = NULL; + mWidth = 0; + mHeight = 0; + + mEffect->mEnvelope->Flatten(0.); + mEffect->mEnvelope->Mirror(false); + mEffect->mEnvelope->SetTrackLen(1.0); + + ForceRecalc(); +} + +EqualizationPanel::~EqualizationPanel() +{ + if (mBitmap) + delete mBitmap; + if (mOuti) + delete [] mOuti; + if (mOutr) + delete [] mOutr; +} + +void EqualizationPanel::ForceRecalc() +{ + mRecalcRequired = true; + Refresh(false); +} + +void EqualizationPanel::Recalc() +{ + if (mOutr) + delete [] mOutr; + mOutr = new float[mEffect->mWindowSize]; + + if (mOuti) + delete [] mOuti; + mOuti = new float[mEffect->mWindowSize]; + + mEffect->CalcFilter(); //to calculate the actual response +#ifdef EXPERIMENTAL_USE_REALFFTF + InverseRealFFT(mEffect->mWindowSize, mEffect->mFilterFuncR, mEffect->mFilterFuncI, mOutr); +#else + FFT(mWindowSize,true,mFilterFuncR,mFilterFuncI,mOutr,mOuti); //work out FIR response - note mOuti will be all zeros +#endif // EXPERIMENTAL_USE_REALFFTF +} + +void EqualizationPanel::OnSize(wxSizeEvent & WXUNUSED(event)) +{ + Refresh( false ); +} + +void EqualizationPanel::OnPaint(wxPaintEvent & WXUNUSED(event)) +{ + wxPaintDC dc(this); + if(mRecalcRequired) { + Recalc(); + mRecalcRequired = false; + } + int width, height; + GetSize(&width, &height); + + if (!mBitmap || mWidth!=width || mHeight!=height) + { + if (mBitmap) + delete mBitmap; + + mWidth = width; + mHeight = height; + mBitmap = new wxBitmap(mWidth, mHeight); + } + + wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)); + + wxMemoryDC memDC; + memDC.SelectObject(*mBitmap); + + wxRect bkgndRect; + bkgndRect.x = 0; + bkgndRect.y = 0; + bkgndRect.width = mWidth; + bkgndRect.height = mHeight; + memDC.SetBrush(bkgndBrush); + memDC.SetPen(*wxTRANSPARENT_PEN); + memDC.DrawRectangle(bkgndRect); + + bkgndRect.y = mHeight; + memDC.DrawRectangle(bkgndRect); + + wxRect border; + border.x = 0; + border.y = 0; + border.width = mWidth; + border.height = mHeight; + + memDC.SetBrush(*wxWHITE_BRUSH); + memDC.SetPen(*wxBLACK_PEN); + memDC.DrawRectangle(border); + + mEnvRect = border; + mEnvRect.Deflate(PANELBORDER, PANELBORDER); + + // Pure blue x-axis line + memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 1, wxSOLID)); + int center = (int) (mEnvRect.height * mEffect->mdBMax/(mEffect->mdBMax-mEffect->mdBMin) + .5); + AColor::Line(memDC, + mEnvRect.GetLeft(), mEnvRect.y + center, + mEnvRect.GetRight(), mEnvRect.y + center); + + // Draw the grid, if asked for. Do it now so it's underneath the main plots. + if( mEffect->mDrawGrid ) + { + mEffect->mFreqRuler->ruler.DrawGrid(memDC, mEnvRect.height, true, true, PANELBORDER, PANELBORDER); + mEffect->mdBRuler->ruler.DrawGrid(memDC, mEnvRect.width, true, true, PANELBORDER, PANELBORDER); + } + + // Med-blue envelope line + memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 3, wxSOLID)); + + // Draw envelope + double *values = new double[mEnvRect.width]; + mEffect->mEnvelope->GetValues(values, mEnvRect.width, 0.0, 1.0/mEnvRect.width); + int x, y, xlast = 0, ylast = 0; + bool off = false, off1 = false; + for(int i=0; imdBMax-values[i])/(mEffect->mdBMax-mEffect->mdBMin)) + .25 ); //needs more optimising, along with'what you get'? + if( y >= mEnvRect.height) + { + y = mEnvRect.height - 1; + off = true; + } + else + { + off = false; + off1 = false; + } + if ( (i != 0) & (!off1) ) + { + AColor::Line(memDC, xlast, ylast, + x, mEnvRect.y + y); + } + off1 = off; + xlast = x; + ylast = mEnvRect.y + y; + } + delete[] values; + + //Now draw the actual response that you will get. + //mFilterFunc has a linear scale, window has a log one so we have to fiddle about + memDC.SetPen(wxPen(theTheme.Colour( clrResponseLines ), 1, wxSOLID)); + double scale = (double)mEnvRect.height/(mEffect->mdBMax-mEffect->mdBMin); //pixels per dB + double yF; //gain at this freq + double delta = mEffect->mHiFreq/(((double)mEffect->mWindowSize/2.)); //size of each freq bin + + bool lin = mEffect->mDraw && mEffect->mLin; // log or lin scale? + + double loLog = log10(mEffect->mLoFreq); + double step = lin ? mEffect->mHiFreq : (log10(mEffect->mHiFreq) - loLog); + step /= ((double)mEnvRect.width-1.); + double freq; //actual freq corresponding to x position + int halfM = (mEffect->mM-1)/2; + int n; //index to mFreqFunc + for(int i=0; imHiFreq; //radians, normalized + double wtemp = sin(0.5 * theta); + double wpr = -2.0 * wtemp * wtemp; + double wpi = -1.0 * sin(theta); + double wr = cos(theta*halfM); + double wi = sin(theta*halfM); + + yF = 0.; + for(int j=0;jmdBMin; + } + else + { //use FFT, it has enough resolution + n = (int)(freq/delta + .5); + if(pow(mEffect->mFilterFuncR[n],2)+pow(mEffect->mFilterFuncI[n],2)!=0.) + yF = 10.0*log10(pow(mEffect->mFilterFuncR[n],2)+pow(mEffect->mFilterFuncI[n],2)); //10 here, a power + else + yF = mEffect->mdBMin; + } + if(yF < mEffect->mdBMin) + yF = mEffect->mdBMin; + yF = center-scale*yF; + if(yF>mEnvRect.height) + yF = mEnvRect.height - 1; + if(yF<0.) + yF=0.; + y = (int)(yF+.5); + + if (i != 0) + { + AColor::Line(memDC, xlast, ylast, x, mEnvRect.y + y); + } + xlast = x; + ylast = mEnvRect.y + y; + } + + memDC.SetPen(*wxBLACK_PEN); + if( mEffect->mDraw->GetValue() ) + mEffect->mEnvelope->DrawPoints(memDC, mEnvRect, 0.0, mEnvRect.width-1, false, mEffect->mdBMin, mEffect->mdBMax); + + dc.Blit(0, 0, mWidth, mHeight, + &memDC, 0, 0, wxCOPY, FALSE); +} + +void EqualizationPanel::OnMouseEvent(wxMouseEvent & event) +{ + if (event.ButtonDown() && !HasCapture()) + { + CaptureMouse(); + } + + if (mEffect->mEnvelope->MouseEvent(event, mEnvRect, 0.0, mEnvRect.width, false, + mEffect->mdBMin, mEffect->mdBMax)) + { + mEffect->EnvelopeUpdated(); + ForceRecalc(); + } + + if (event.ButtonUp() && HasCapture()) + { + ReleaseMouse(); + } +} + +void EqualizationPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event)) +{ + if (HasCapture()) + { + ReleaseMouse(); } } @@ -2983,7 +2840,7 @@ BEGIN_EVENT_TABLE(EditCurvesDialog, wxDialog) EVT_BUTTON(wxID_OK, EditCurvesDialog::OnOK) END_EVENT_TABLE() -EditCurvesDialog::EditCurvesDialog(EqualizationDialog * parent, int position): +EditCurvesDialog::EditCurvesDialog(wxWindow * parent, EffectEqualization * effect, int position): wxDialog(parent, wxID_ANY, _("Manage Curves List"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) @@ -2991,13 +2848,14 @@ wxDialog(parent, wxID_ANY, _("Manage Curves List"), SetLabel(_("Manage Curves")); // Provide visual label SetName(_("Manage Curves List")); // Provide audible label mParent = parent; + mEffect = effect; mPosition = position; - // make a copy of mParent->mCurves here to muck about with. + // make a copy of mEffect->mCurves here to muck about with. mEditCurves.Clear(); - for (unsigned int i = 0; i < mParent->mCurves.GetCount(); i++) + for (unsigned int i = 0; i < mEffect->mCurves.GetCount(); i++) { - mEditCurves.Add(mParent->mCurves[i].Name); - mEditCurves[i].points = mParent->mCurves[i].points; + mEditCurves.Add(mEffect->mCurves[i].Name); + mEditCurves[i].points = mEffect->mCurves[i].points; } Populate(); @@ -3347,11 +3205,11 @@ void EditCurvesDialog::OnImport( wxCommandEvent & WXUNUSED(event)) // Use EqualizationDialog::LoadCurves to read into (temporary) mEditCurves // This may not be the best OOP way of doing it, but I don't know better (MJS) EQCurveArray temp; - temp = mParent->mCurves; // temp copy of the main dialog curves - mParent->mCurves = mEditCurves; // copy EditCurvesDialog to main interface - mParent->LoadCurves(fileName, true); // use main interface to load imported curves - mEditCurves = mParent->mCurves; // copy back to this interface - mParent->mCurves = temp; // and reset the main interface how it was + temp = mEffect->mCurves; // temp copy of the main dialog curves + mEffect->mCurves = mEditCurves; // copy EditCurvesDialog to main interface + mEffect->LoadCurves(fileName, true); // use main interface to load imported curves + mEditCurves = mEffect->mCurves; // copy back to this interface + mEffect->mCurves = temp; // and reset the main interface how it was PopulateList(0); // update the EditCurvesDialog dialog return; } @@ -3366,7 +3224,7 @@ void EditCurvesDialog::OnExport( wxCommandEvent & WXUNUSED(event)) fileName = filePicker.GetPath(); EQCurveArray temp; - temp = mParent->mCurves; // backup the parent's curves + temp = mEffect->mCurves; // backup the parent's curves EQCurveArray exportCurves; // Copy selected curves to export exportCurves.Clear(); long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); @@ -3386,9 +3244,9 @@ void EditCurvesDialog::OnExport( wxCommandEvent & WXUNUSED(event)) } if(i>0) { - mParent->mCurves = exportCurves; - mParent->SaveCurves(fileName); - mParent->mCurves = temp; + mEffect->mCurves = exportCurves; + mEffect->SaveCurves(fileName); + mEffect->mCurves = temp; wxString message; message.Printf(_("%d curves exported to %s"), i, fileName.c_str()); wxMessageBox(message, _("Curves exported")); @@ -3405,11 +3263,11 @@ void EditCurvesDialog::OnLibrary( wxCommandEvent & WXUNUSED(event)) void EditCurvesDialog::OnDefaults( wxCommandEvent & WXUNUSED(event)) { EQCurveArray temp; - temp = mParent->mCurves; + temp = mEffect->mCurves; // we expect this to fail in LoadCurves (due to a lack of path) and handle that there - mParent->LoadCurves( wxT("EQDefaultCurves.xml") ); - mEditCurves = mParent->mCurves; - mParent->mCurves = temp; + mEffect->LoadCurves( wxT("EQDefaultCurves.xml") ); + mEditCurves = mEffect->mCurves; + mEffect->mCurves = temp; PopulateList(0); // update the EditCurvesDialog dialog } @@ -3446,18 +3304,19 @@ void EditCurvesDialog::OnOK(wxCommandEvent & WXUNUSED(event)) { // Make a backup of the current curves wxString backupPlace = wxFileName( FileNames::DataDir(), wxT("EQBackup.xml") ).GetFullPath(); - mParent->SaveCurves(backupPlace); + mEffect->SaveCurves(backupPlace); // Load back into the main dialog - mParent->mCurves.Clear(); + mEffect->mCurves.Clear(); for (unsigned int i = 0; i < mEditCurves.GetCount(); i++) { - mParent->mCurves.Add(mEditCurves[i].Name); - mParent->mCurves[i].points = mEditCurves[i].points; + mEffect->mCurves.Add(mEditCurves[i].Name); + mEffect->mCurves[i].points = mEditCurves[i].points; } - mParent->SaveCurves(); - mParent->LoadCurves(); - mParent->CreateChoice(); - mParent->Layout(); + mEffect->SaveCurves(); + mEffect->LoadCurves(); +// mEffect->CreateChoice(); + wxGetTopLevelParent(mEffect->mUIParent)->Layout(); +// mEffect->mUIParent->Layout(); // Select something sensible long item = mList->GetNextItem(-1, @@ -3465,7 +3324,7 @@ void EditCurvesDialog::OnOK(wxCommandEvent & WXUNUSED(event)) wxLIST_STATE_SELECTED); if (item == -1) item = mList->GetItemCount()-1; // nothing selected, default to 'unnamed' - mParent->setCurve(item); + mEffect->setCurve(item); EndModal(true); } @@ -3560,7 +3419,7 @@ wxAccStatus SliderAx::GetKeyboardShortcut( int WXUNUSED(childId), wxString *shor // rect is in screen coordinates. wxAccStatus SliderAx::GetLocation( wxRect& rect, int WXUNUSED(elementId) ) { - wxSliderBugfix *s = wxDynamicCast( GetWindow(), wxSliderBugfix ); + wxSlider *s = wxDynamicCast( GetWindow(), wxSlider ); rect = s->GetRect(); rect.SetPosition( s->GetParent()->ClientToScreen( rect.GetPosition() ) ); @@ -3571,7 +3430,7 @@ wxAccStatus SliderAx::GetLocation( wxRect& rect, int WXUNUSED(elementId) ) // Gets the name of the specified object. wxAccStatus SliderAx::GetName(int WXUNUSED(childId), wxString* name) { - wxSliderBugfix *s = wxDynamicCast( GetWindow(), wxSliderBugfix ); + wxSlider *s = wxDynamicCast( GetWindow(), wxSlider ); *name = s->GetName(); @@ -3616,7 +3475,7 @@ wxAccStatus SliderAx::GetSelections( wxVariant * WXUNUSED(selections) ) // Returns a state constant. wxAccStatus SliderAx::GetState(int childId, long* state) { - wxSliderBugfix *s = wxDynamicCast( GetWindow(), wxSliderBugfix ); + wxSlider *s = wxDynamicCast( GetWindow(), wxSlider ); switch( childId ) { @@ -3650,7 +3509,7 @@ wxAccStatus SliderAx::GetState(int childId, long* state) // or child. wxAccStatus SliderAx::GetValue(int childId, wxString* strValue) { - wxSliderBugfix *s = wxDynamicCast( GetWindow(), wxSliderBugfix ); + wxSlider *s = wxDynamicCast( GetWindow(), wxSlider ); if( childId == 0 ) { diff --git a/src/effects/Equalization.h b/src/effects/Equalization.h index 64f72c919..5beeff20b 100644 --- a/src/effects/Equalization.h +++ b/src/effects/Equalization.h @@ -42,7 +42,10 @@ #include "../widgets/Ruler.h" #include "../RealFFTf.h" -class EqualizationDialog; +#define EQUALIZATION_PLUGIN_SYMBOL wxTRANSLATE("Equalization") + + +class EqualizationPanel; // // One point in a curve @@ -67,7 +70,7 @@ WX_DECLARE_OBJARRAY( EQPoint, EQPointArray); class EQCurve { public: - EQCurve( const wxString & name ) { Name = name; } + EQCurve( const wxString & name = wxEmptyString ) { Name = name; } EQCurve( const wxChar * name ) { Name = name; } wxString Name; EQPointArray points; @@ -78,38 +81,46 @@ WX_DECLARE_OBJARRAY( EQCurve, EQCurveArray ); class EffectEqualization48x; #endif -class EffectEqualization: public Effect { - +class EffectEqualization : public Effect, + public XMLTagHandler +{ public: - EffectEqualization(); virtual ~EffectEqualization(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Equalization...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#EQPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Equalization")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Performing Equalization")); - } + virtual EffectType GetType(); + // EffectClientInterface implementation + + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + + // EffectUIClientInterface implementation + + virtual bool ValidateUI(); + + // Effect implementation + + virtual bool Startup(); virtual bool Init(); - virtual bool PromptUser(); - virtual bool DontPromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); - virtual bool Process(); + virtual bool PopulateUI(wxWindow *parent); + virtual bool CloseUI(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); + +private: + // EffectEqualization implementation + // Number of samples in an FFT window enum {windowSize=16384}; //MJS - work out the optimum for this at run time? Have a dialog box for it? @@ -117,17 +128,57 @@ public: // low range of human hearing enum {loFreqI=20}; - - -private: bool ProcessOne(int count, WaveTrack * t, sampleCount start, sampleCount len); + virtual bool CalcFilter(); + void Filter(sampleCount len, float *buffer); + + void EnvelopeUpdated(); + void EnvelopeUpdated(Envelope *env, bool lin); - void Filter(sampleCount len, - float *buffer); + void LoadCurves(wxString fileName = wxT(""), bool append = false); + void SaveCurves(wxString fileName = wxT("")); + void Select(int sel); + void setCurve(int currentCurve); + void setCurve(wxString curveName); + void setCurve(void); + + // XMLTagHandler callback methods for loading and saving + bool HandleXMLTag(const wxChar *tag, const wxChar **attrs); + XMLTagHandler *HandleXMLChild(const wxChar *tag); + void WriteXML(XMLWriter &xmlFile); - void ReadPrefs(); + void LayoutEQSliders(); + void UpdateGraphic(void); + void EnvLogToLin(void); + void EnvLinToLog(void); + void ErrMin(void); + void GraphicEQ(Envelope *env); + void spline(double x[], double y[], int n, double y2[]); + double splint(double x[], double y[], int n, double y2[], double xr); + void OnSize( wxSizeEvent & event ); + void OnErase( wxEraseEvent & event ); + void OnPaint( wxPaintEvent & event ); + void OnSlider( wxCommandEvent & event ); + void OnInterp( wxCommandEvent & event ); + void OnSliderM( wxCommandEvent & event ); + void OnSliderDBMAX( wxCommandEvent & event ); + void OnSliderDBMIN( wxCommandEvent & event ); + void OnDrawMode( wxCommandEvent &event ); + void OnGraphicMode( wxCommandEvent &event ); + void OnCurve( wxCommandEvent & event ); + void OnManage( wxCommandEvent & event ); + void OnClear( wxCommandEvent & event ); + void OnInvert( wxCommandEvent & event ); + void OnGridOnOff( wxCommandEvent & event ); + void OnLinFreq( wxCommandEvent & event ); +#ifdef EXPERIMENTAL_EQ_SSE_THREADED + void OnProcessingRadio( wxCommandEvent & event ); + void OnBench( wxCommandEvent & event ); +#endif + +private: HFFT hFFT; float *mFFTBuffer; float *mFilterFuncR; @@ -135,231 +186,40 @@ private: int mM; wxString mCurveName; bool mLin; - double mdBMax; - double mdBMin; + float mdBMax; + float mdBMin; bool mDrawMode; int mInterp; - bool mPrompting; bool mDrawGrid; - bool mEditingBatchParams; -#ifdef EXPERIMENTAL_EQ_SSE_THREADED - bool mBench; - EffectEqualization48x *mEffectEqualization48x; -friend class EffectEqualization48x; -#endif -public: - -friend class EqualizationDialog; -friend class EqualizationPanel; -}; - - -class EqualizationPanel: public wxPanel -{ -public: - EqualizationPanel( double loFreq, double hiFreq, - Envelope *env, - EqualizationDialog *parent, - float *filterFuncR, float *filterFuncI, long windowSize, - wxWindowID id, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize); - ~EqualizationPanel(); - - void OnMouseEvent(wxMouseEvent & event); - void OnCaptureLost(wxMouseCaptureLostEvent & event); - void OnPaint(wxPaintEvent & event); - void OnSize (wxSizeEvent & event); - - // We don't need or want to accept focus. - bool AcceptsFocus() const { return false; } - - void Recalc(); - - int M; - float dBMax; - float dBMin; - bool RecalcRequired; - - Envelope *mEnvelope; - -private: - - wxBitmap *mBitmap; - wxRect mEnvRect; - EqualizationDialog *mParent; - int mWidth; - int mHeight; - long mWindowSize; - float *mFilterFuncR; - float *mFilterFuncI; - float *mOutr; - float *mOuti; - - double mLoFreq; - double mHiFreq; - - DECLARE_EVENT_TABLE() -}; - - -// WDR: class declarations - -//---------------------------------------------------------------------------- -// EqualizationDialog -//---------------------------------------------------------------------------- - -class EqualizationDialog: public wxDialog, public XMLTagHandler -{ -public: - // constructors and destructors - EqualizationDialog(EffectEqualization * effect, - double loFreq, double hiFreq, - float *filterFuncR, float *filterFuncI, long windowSize, wxString CurveName, bool disallowCustom, - wxWindow *parent, wxWindowID id, - const wxString &title, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize, - long style = wxDEFAULT_DIALOG_STYLE ); - ~EqualizationDialog(); - - // WDR: method declarations for EqualizationDialog - virtual bool Validate(); - virtual bool TransferDataToWindow(); - virtual bool TransferDataFromWindow(); - virtual bool CalcFilter(); - - void EnvelopeUpdated(); - void EnvelopeUpdated(Envelope *env, bool lin); - static const double thirdOct[]; - wxRadioButton *mFaderOrDraw[2]; -#ifdef EXPERIMENTAL_EQ_SSE_THREADED - wxRadioButton *mMathProcessingType[5]; // default, sse, sse threaded, AVX, AVX threaded (note AVX is not implemented yet -#endif - wxChoice *mInterpChoice; - wxCheckBox *mLinFreq; - int M; - wxString curveName; - bool linCheck; - float dBMin; - float dBMax; - double whens[NUM_PTS]; - double whenSliders[NUMBER_OF_BANDS+1]; - int bandsInUse; - bool drawMode; - int interp; - bool drawGrid; - RulerPanel *dBRuler; - RulerPanel *freqRuler; - friend class EditCurvesDialog; - -private: - void MakeEqualizationDialog(); - void CreateChoice(); - void LoadCurves(wxString fileName = wxT(""), bool append = false); - void SaveCurves(wxString fileName = wxT("")); - void Select(int sel); - void setCurve(int currentCurve); - void setCurve(wxString curveName); - void setCurve(void); - void GraphicEQ(Envelope *env); - void spline(double x[], double y[], int n, double y2[]); - double splint(double x[], double y[], int n, double y2[], double xr); - void LayoutEQSliders(); - void RevertCustom(); - void Finish(bool ok); - - // XMLTagHandler callback methods for loading and saving - bool HandleXMLTag(const wxChar *tag, const wxChar **attrs); - XMLTagHandler *HandleXMLChild(const wxChar *tag); - void WriteXML(XMLWriter &xmlFile); - -private: - // WDR: member variable declarations for EqualizationDialog - - enum - { - ID_FILTERPANEL = 10000, - ID_LENGTH, - ID_DBMAX, - ID_DBMIN, - ID_CURVE, - ID_MANAGE, - ID_DELETE, - ID_CLEAR, - ID_INVERT, - drawRadioID, - sliderRadioID, -#ifdef EXPERIMENTAL_EQ_SSE_THREADED - defaultMathRadioID, - sSERadioID, - sSEThreadedRadioID, - aVXRadioID, - aVXThreadedRadioID, - ID_BENCH, -#endif - ID_INTERP, - ID_LIN_FREQ, - GridOnOffID, - ID_SLIDER // needs to come last - }; - -private: - // WDR: handler declarations for EqualizationDialog - void OnPaint( wxPaintEvent &event ); - void OnSize( wxSizeEvent &event ); - void OnErase( wxEraseEvent &event ); - void OnSlider( wxCommandEvent &event ); - void OnInterp( wxCommandEvent &event ); - void OnSliderM( wxCommandEvent &event ); - void OnSliderDBMAX( wxCommandEvent &event ); - void OnSliderDBMIN( wxCommandEvent &event ); - void OnDrawRadio(wxCommandEvent &event ); - void OnSliderRadio(wxCommandEvent &event ); -#ifdef EXPERIMENTAL_EQ_SSE_THREADED - void OnProcessingRadio(wxCommandEvent &event ); - void OnBench( wxCommandEvent & event); -#endif - void OnLinFreq(wxCommandEvent &event ); - void UpdateGraphic(void); - void EnvLogToLin(void); - void EnvLinToLog(void); - void ErrMin(void); - void OnCurve( wxCommandEvent &event ); - void OnManage( wxCommandEvent &event ); - void OnClear( wxCommandEvent &event ); - void OnInvert( wxCommandEvent &event ); - void OnPreview(wxCommandEvent &event); - void OnOk( wxCommandEvent &event ); - void OnCancel( wxCommandEvent &event ); - void OnGridOnOff( wxCommandEvent &event ); -private: - EffectEqualization * m_pEffect; + double mWhens[NUM_PTS]; + double mWhenSliders[NUMBER_OF_BANDS+1]; + int mBandsInUse; + RulerPanel *mdBRuler; + RulerPanel *mFreqRuler; + wxArrayString mInterpolations; bool mDisallowCustom; double mLoFreq; double mHiFreq; - float *mFilterFuncR; - float *mFilterFuncI; long mWindowSize; bool mDirty; - wxSlider * m_sliders[NUMBER_OF_BANDS]; - int m_sliders_old[NUMBER_OF_BANDS]; - double m_EQVals[NUMBER_OF_BANDS+1]; + int mSlidersOld[NUMBER_OF_BANDS]; + double mEQVals[NUMBER_OF_BANDS+1]; + + EQCurveArray mCurves; + EQCurve mCustomBackup; - EqualizationPanel *mPanel; Envelope *mLogEnvelope; Envelope *mLinEnvelope; - wxBoxSizer *mCurveSizer; - wxChoice *mCurve; - wxButton *mDelete; - wxButton *mManage; - wxStaticText *mMText; - wxStaticText *octText; - wxSlider *MSlider; - wxSlider *dBMinSlider; - wxSlider *dBMaxSlider; + Envelope *mEnvelope; + +#ifdef EXPERIMENTAL_EQ_SSE_THREADED + bool mBench; + EffectEqualization48x *mEffectEqualization48x; + friend class EffectEqualization48x; +#endif + wxBoxSizer *szrC; wxBoxSizer *szrG; wxBoxSizer *szrV; @@ -374,22 +234,87 @@ private: wxBoxSizer *szr3; wxBoxSizer *szr4; wxBoxSizer *szr5; - wxSize size; + + wxSizerItem *mLeftSpacer; + + EqualizationPanel *mPanel; + wxRadioButton *mDraw; + wxRadioButton *mGraphic; + wxCheckBox *mLinFreq; wxCheckBox *mGridOnOff; - EQCurveArray mCurves; - EQCurve mCustomBackup; + wxChoice *mInterpChoice; + wxChoice *mCurve; + wxButton *mManage; + wxStaticText *mMText; + wxSlider *mMSlider; + wxSlider *mdBMinSlider; + wxSlider *mdBMaxSlider; + wxSlider *mSliders[NUMBER_OF_BANDS]; + +#ifdef EXPERIMENTAL_EQ_SSE_THREADED + wxRadioButton *mMathProcessingType[5]; // default, sse, sse threaded, AVX, AVX threaded (note AVX is not implemented yet + wxBoxSizer *szrM; +#endif + + DECLARE_EVENT_TABLE(); + + friend class EqualizationPanel; + friend class EditCurvesDialog; +}; + +class EqualizationPanel: public wxPanel +{ +public: + EqualizationPanel(EffectEqualization *effect, wxWindow *parent); + ~EqualizationPanel(); + + // We don't need or want to accept focus. + bool AcceptsFocus() const { return false; } + + void ForceRecalc(); private: - DECLARE_EVENT_TABLE() + void Recalc(); + void OnMouseEvent(wxMouseEvent & event); + void OnCaptureLost(wxMouseCaptureLostEvent & event); + void OnPaint(wxPaintEvent & event); + void OnSize (wxSizeEvent & event); + +public: +// int & mM; +// float & mdBMax; +// float & mdBMin; +// Envelope & mEnvelope; + +private: + wxWindow *mParent; + EffectEqualization *mEffect; + + bool mRecalcRequired; + + wxBitmap *mBitmap; + wxRect mEnvRect; + int mWidth; + int mHeight; +// long mWindowSize; +// float *mFilterFuncR; +// float *mFilterFuncI; + float *mOutr; + float *mOuti; + +// double mLoFreq; +// double mHiFreq; + + DECLARE_EVENT_TABLE(); }; // EditCurvesDialog. Note that the 'modified' curve used to be called 'custom' but is now called 'unnamed' // Some things that deal with 'unnamed' curves still use, for example, 'mCustomBackup' as variable names. -class EditCurvesDialog:public wxDialog +class EditCurvesDialog : public wxDialog { public: - EditCurvesDialog(EqualizationDialog * parent, int position); + EditCurvesDialog(wxWindow * parent, EffectEqualization * effect, int position); ~EditCurvesDialog(); private: @@ -409,7 +334,8 @@ private: wxListCtrl *mList; // List of curves. EQCurveArray mEditCurves; // Copy of curves to muck about with - EqualizationDialog *mParent; // the parent EQ Dialog + wxWindow *mParent; // the parent EQ Dialog + EffectEqualization *mEffect; // the parent EQ effect int mPosition; // position of current curve in list void Populate(); void PopulateOrExchange(ShuttleGui &S); @@ -427,7 +353,6 @@ private: DECLARE_EVENT_TABLE() }; - #if wxUSE_ACCESSIBILITY class SliderAx: public wxWindowAccessible diff --git a/src/effects/Equalization48x.cpp b/src/effects/Equalization48x.cpp index 65d5f4160..cb2366cf0 100644 --- a/src/effects/Equalization48x.cpp +++ b/src/effects/Equalization48x.cpp @@ -251,8 +251,8 @@ bool EffectEqualization48x::FreeBuffersWorkers() } -#pragma warning(push) -// Disable the unreachable code warning in MSVC, for this function. +#pragma warning(push) +// Disable the unreachable code warning in MSVC, for this function. #pragma warning(disable: 4702) bool EffectEqualization48x::RunFunctionSelect(int flags, int count, WaveTrack * track, sampleCount start, sampleCount len) { diff --git a/src/effects/Fade.cpp b/src/effects/Fade.cpp index fc787fc03..f9b936269 100644 --- a/src/effects/Fade.cpp +++ b/src/effects/Fade.cpp @@ -8,57 +8,92 @@ *******************************************************************//** -\class EffectFadeIn -\brief An EffectSimpleMono - -*//****************************************************************//** - -\class EffectFadeOut -\brief An EffectSimpleMono +\class EffectFade +\brief An Effect *//*******************************************************************/ #include "../Audacity.h" +#include + #include "Fade.h" -#include "../WaveTrack.h" -#include -#include - -bool EffectFadeIn::NewTrackSimpleMono() +EffectFade::EffectFade(bool fadeIn) +{ + mFadeIn = fadeIn; +} + +EffectFade::~EffectFade() +{ +} + +// IdentInterface implementation + +wxString EffectFade::GetSymbol() +{ + return mFadeIn + ? FADEIN_PLUGIN_SYMBOL + : FADEOUT_PLUGIN_SYMBOL; +} + +wxString EffectFade::GetDescription() +{ + return mFadeIn + ? wxTRANSLATE("Applies a linear fade-in to the selected audio") + : wxTRANSLATE("Applies a linear fade-out to the selected audio"); +} + +// EffectIdentInterface implementation + +EffectType EffectFade::GetType() +{ + return EffectTypeProcess; +} + +bool EffectFade::IsInteractive() +{ + return false; +} + +// EffectClientInterface implementation + +int EffectFade::GetAudioInCount() +{ + return 1; +} + +int EffectFade::GetAudioOutCount() +{ + return 1; +} + +bool EffectFade::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) { - mLen = (int)((mCurT1 - mCurT0) * mCurRate + 0.5); mSample = 0; return true; } -bool EffectFadeIn::ProcessSimpleMono(float *buffer, sampleCount len) +sampleCount EffectFade::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) { - for (sampleCount i = 0; i < len; i++) - buffer[i] = (float) (buffer[i] * (float) (mSample + i) - / (float) (mLen)); - mSample += len; + float *ibuf = inBlock[0]; + float *obuf = outBlock[0]; - return true; -} - -bool EffectFadeOut::NewTrackSimpleMono() -{ - mLen = (int)((mCurT1 - mCurT0) * mCurRate + 0.5); - mSample = 0; - - return true; -} - -bool EffectFadeOut::ProcessSimpleMono(float *buffer, sampleCount len) -{ - for (sampleCount i = 0; i < len; i++) - buffer[i] = (float) (buffer[i] - * (float) (mLen - 1 - (mSample + i)) - / (float) (mLen)); - mSample += len; - - return true; + if (mFadeIn) + { + for (sampleCount i = 0; i < blockLen; i++) + { + obuf[i] = (ibuf[i] * ((float) mSample++)) / mSampleCnt; + } + } + else + { + for (sampleCount i = 0; i < blockLen; i++) + { + obuf[i] = (ibuf[i] * ((float) mSampleCnt - 1 - mSample++)) / mSampleCnt; + } + } + + return blockLen; } diff --git a/src/effects/Fade.h b/src/effects/Fade.h index 273228c25..88154e84b 100644 --- a/src/effects/Fade.h +++ b/src/effects/Fade.h @@ -11,78 +11,41 @@ #ifndef __AUDACITY_EFFECT_FADE__ #define __AUDACITY_EFFECT_FADE__ -#include "SimpleMono.h" +#include -#include +#include "Effect.h" -class wxString; +#define FADEIN_PLUGIN_SYMBOL wxTRANSLATE("Fade In") +#define FADEOUT_PLUGIN_SYMBOL wxTRANSLATE("Fade Out") -class EffectFadeIn: public EffectSimpleMono { +class EffectFade : public Effect +{ +public: + EffectFade(bool fadeIn = false); + virtual ~EffectFade(); - public: - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Fade In")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#UtilityPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Fade In")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Fading In")); - } + virtual EffectType GetType(); + virtual bool IsInteractive(); - virtual bool PromptUser() { - return true; - } + // EffectClientInterface implementation - protected: + virtual int GetAudioInCount(); + virtual int GetAudioOutCount(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + +private: + // EffectFadeIn implementation + + bool mFadeIn; sampleCount mSample; - sampleCount mLen; - - virtual bool NewTrackSimpleMono(); - - virtual bool ProcessSimpleMono(float *buffer, sampleCount len); -}; - -class EffectFadeOut:public EffectSimpleMono { - - public: - virtual wxString GetEffectName() { - return wxString(_("Fade Out")); - } - - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#UtilityPlugin")); - return result; - } - - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Fade Out")); - } - - virtual wxString GetEffectAction() { - return wxString(_("Fading Out")); - } - - virtual bool PromptUser() { - return true; - } - - protected: - sampleCount mSample; - sampleCount mLen; - - virtual bool NewTrackSimpleMono(); - - virtual bool ProcessSimpleMono(float *buffer, sampleCount len); }; #endif diff --git a/src/effects/FindClipping.cpp b/src/effects/FindClipping.cpp index ae76f6ec6..9cc6a452b 100644 --- a/src/effects/FindClipping.cpp +++ b/src/effects/FindClipping.cpp @@ -20,55 +20,78 @@ #include "../Audacity.h" -#include "../AudacityApp.h" -#include -#include -#include -#include -#include -#include -#include - -#include -#include #include +#include + +#include "../AudacityApp.h" +#include "../widgets/valnum.h" + #include "FindClipping.h" -#include "../LabelTrack.h" -#include "../WaveTrack.h" + +#define DEF_Start 3 +#define MIN_Start 1 + +#define DEF_Stop 3 +#define MIN_Stop 1 EffectFindClipping::EffectFindClipping() { - SetEffectFlags(BUILTIN_EFFECT | ANALYZE_EFFECT); - mStart = 3; - mStop = 3; + mStart = DEF_Start; + mStop = DEF_Stop; } -wxString EffectFindClipping::GetEffectDescription() +EffectFindClipping::~EffectFindClipping() { - return wxString::Format(_("Detect clipping")); } -bool EffectFindClipping::PromptUser() -{ - FindClippingDialog dlg(this, mParent); - dlg.CentreOnParent(); +// IdentInterface implementation - if (dlg.ShowModal() == wxID_CANCEL) { +wxString EffectFindClipping::GetSymbol() +{ + return FINDCLIPPING_PLUGIN_SYMBOL; +} + +wxString EffectFindClipping::GetDescription() +{ + return wxTRANSLATE("This displays runs of clipped samples in a Label Track"); +} + +// EffectIdentInterface implementation + +EffectType EffectFindClipping::GetType() +{ + return EffectTypeAnalyze; +} + +// EffectClientInterface implementation + +bool EffectFindClipping::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(wxT("Start"), mStart); + parms.Write(wxT("Stop"), mStop); + + return true; +} + +bool EffectFindClipping::SetAutomationParameters(EffectAutomationParameters & parms) +{ + int start; + int stop; + + parms.Read(wxT("Start"), &start, DEF_Start); + parms.Read(wxT("Stop"), &stop, DEF_Stop); + + if (start < MIN_Start || stop < MIN_Stop) + { return false; } return true; } -bool EffectFindClipping::TransferParameters(Shuttle & shuttle) -{ - shuttle.TransferInt(wxT("Start"), mStart, 3); - shuttle.TransferInt(wxT("Stop"), mStop, 3); - - return true; -} +// Effect implementation bool EffectFindClipping::Process() { @@ -203,41 +226,42 @@ bool EffectFindClipping::ProcessOne(LabelTrack * l, return bGoodResult; } -//---------------------------------------------------------------------------- -// FindClippingDialog -//---------------------------------------------------------------------------- - -FindClippingDialog::FindClippingDialog(EffectFindClipping * effect, wxWindow * parent) -: EffectDialog(parent, _("Find Clipping"), INSERT_EFFECT) -{ - mEffect = effect; - - Init(); -} - -void FindClippingDialog::PopulateOrExchange(ShuttleGui & S) +void EffectFindClipping::PopulateOrExchange(ShuttleGui & S) { S.StartMultiColumn(2, wxALIGN_CENTER); { + IntegerValidator vldStart(&mStart); + vldStart.SetMin(MIN_Start); S.TieTextBox(_("Start threshold (samples):"), - mEffect->mStart, - 10)->SetValidator(wxTextValidator(wxFILTER_NUMERIC)); + mStart, + 10)->SetValidator(vldStart); + IntegerValidator vldStop(&mStop); + vldStop.SetMin(MIN_Stop); S.TieTextBox(_("Stop threshold (samples):"), - mEffect->mStop, - 10)->SetValidator(wxTextValidator(wxFILTER_NUMERIC)); + mStop, + 10)->SetValidator(vldStop); } S.EndMultiColumn(); } -bool FindClippingDialog::TransferDataFromWindow() +bool EffectFindClipping::TransferDataToWindow() { - EffectDialog::TransferDataFromWindow(); - - if (mEffect->mStart <= 0 || mEffect->mStop <= 0) { - wxMessageBox(_("Start and stop must be greater than 0.")); - return false; - } + ShuttleGui S(mUIParent, eIsSettingToDialog); + PopulateOrExchange(S); + + return true; +} + +bool EffectFindClipping::TransferDataFromWindow() +{ + if (!mUIParent->Validate()) + { + return false; + } + + ShuttleGui S(mUIParent, eIsGettingFromDialog); + PopulateOrExchange(S); return true; } diff --git a/src/effects/FindClipping.h b/src/effects/FindClipping.h index ddd80d07c..965d194c9 100644 --- a/src/effects/FindClipping.h +++ b/src/effects/FindClipping.h @@ -14,73 +14,51 @@ class wxString; -#include +#include -#include +#include "../LabelTrack.h" +#include "../WaveTrack.h" #include "Effect.h" -class wxStaticText; +#define FINDCLIPPING_PLUGIN_SYMBOL wxTRANSLATE("Find Clipping") -class WaveTrack; - -class EffectFindClipping:public Effect +class EffectFindClipping : public Effect { - friend class FindClippingDialog; - - public: - +public: EffectFindClipping(); + virtual ~EffectFindClipping(); - virtual wxString GetEffectName() - { - return wxString(wxTRANSLATE("Find Clipping...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() - { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#AnalyserPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() - { - return wxString(wxT("Find Clipping")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() - { - return wxString(_("Detecting clipping")); - } + virtual EffectType GetType(); - virtual wxString GetEffectDescription(); + // EffectClientInterface implementation - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + + // Effect implementation virtual bool Process(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); + +private: + // EffectFindCliping implementation - private: bool ProcessOne(LabelTrack *l, int count, WaveTrack * t, sampleCount start, sampleCount len); +private: int mStart; ///< Using int rather than sampleCount because values are only ever small numbers int mStop; ///< Using int rather than sampleCount because values are only ever small numbers }; -//---------------------------------------------------------------------------- -// FindClippingDialog -//---------------------------------------------------------------------------- -class FindClippingDialog:public EffectDialog { - public: - FindClippingDialog(EffectFindClipping * effect, wxWindow * parent); - - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataFromWindow(); - - private: - EffectFindClipping *mEffect; -}; - #endif // __AUDACITY_EFFECT_FINDCLIPPING__ diff --git a/src/effects/Generator.h b/src/effects/Generator.h index ff01c77f1..c18e27267 100644 --- a/src/effects/Generator.h +++ b/src/effects/Generator.h @@ -24,7 +24,7 @@ class Generator : public Effect { public: - Generator() : mDuration(sDefaultGenerateLen) { } + Generator() : mDuration(GetDefaultDuration()) { } protected: // Amount of time to generate, in seconds diff --git a/src/effects/Invert.cpp b/src/effects/Invert.cpp index 59f18e9db..17d2b61eb 100644 --- a/src/effects/Invert.cpp +++ b/src/effects/Invert.cpp @@ -10,20 +10,69 @@ *******************************************************************//** \class EffectInvert -\brief An EffectSimpleMono that inverts the selected audio. +\brief An Effect that inverts the selected audio. *//*******************************************************************/ - #include "../Audacity.h" +#include + #include "Invert.h" -bool EffectInvert::ProcessSimpleMono(float *buffer, sampleCount len) +EffectInvert::EffectInvert() { - sampleCount i; - for (i = 0; i < len; i++) - buffer[i] = -buffer[i]; - return true; } +EffectInvert::~EffectInvert() +{ +} + +// IdentInterface implementation + +wxString EffectInvert::GetSymbol() +{ + return INVERT_PLUGIN_SYMBOL; +} + +wxString EffectInvert::GetDescription() +{ + return wxTRANSLATE("Flips the audio samples upside-down, reversing their polarity"); +} + +// EffectIdentInterface implementation + +EffectType EffectInvert::GetType() +{ + return EffectTypeProcess; +} + +bool EffectInvert::IsInteractive() +{ + return false; +} + +// EffectClientInterface implementation + +int EffectInvert::GetAudioInCount() +{ + return 1; +} + +int EffectInvert::GetAudioOutCount() +{ + return 1; +} + +sampleCount EffectInvert::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) +{ + float *ibuf = inBlock[0]; + float *obuf = outBlock[0]; + + for (sampleCount i = 0; i < blockLen; i++) + { + obuf[i] = -ibuf[i]; + } + + return blockLen; +} diff --git a/src/effects/Invert.h b/src/effects/Invert.h index f49cf9813..e088563f1 100644 --- a/src/effects/Invert.h +++ b/src/effects/Invert.h @@ -13,40 +13,33 @@ #ifndef __AUDACITY_EFFECT_INVERT__ #define __AUDACITY_EFFECT_INVERT__ -#include #include -#include "SimpleMono.h" +#include "Effect.h" -class WaveTrack; +#define INVERT_PLUGIN_SYMBOL wxTRANSLATE("Invert") -class EffectInvert:public EffectSimpleMono { +class EffectInvert : public Effect +{ +public: + EffectInvert(); + virtual ~EffectInvert(); - public: - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Invert")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#UtilityPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Invert")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Inverting")); - } + virtual EffectType GetType(); + virtual bool IsInteractive(); - virtual bool PromptUser() { - return true; - } + // EffectClientInterface implementation - protected: - virtual bool ProcessSimpleMono(float *buffer, sampleCount len); + virtual int GetAudioInCount(); + virtual int GetAudioOutCount(); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); }; #endif diff --git a/src/effects/Leveller.cpp b/src/effects/Leveller.cpp index e942cec1b..a747b766b 100644 --- a/src/effects/Leveller.cpp +++ b/src/effects/Leveller.cpp @@ -9,75 +9,253 @@ ******************************************************************//** \class EffectLeveller -\brief An EffectSimpleMono - -*//***************************************************************//** - -\class LevellerDialog -\brief Dialog for EffectLeveller +\brief An Effect *//*******************************************************************/ - - #include "../Audacity.h" -// For compilers that support precompilation, includes "wx.h". -#include - -#ifdef __BORLANDC__ -#pragma hdrstop -#endif - -#ifndef WX_PRECOMP -// Include your minimal set of headers here, or wx.h -#include -#endif - #include + +#include +#include +#include + #include "../Prefs.h" + #include "Leveller.h" +enum kPasses +{ + kLight, + kModerate, + kHeavy, + kHeavier, + kHeaviest, + kNumPasses +}; + +static const wxString kPassStrings[kNumPasses] = +{ + /* i18n-hint: Of strength of an effect. Not strongly.*/ + wxTRANSLATE("Light"), + wxTRANSLATE("Moderate"), + /* i18n-hint: Of strength of an effect. Strongly.*/ + wxTRANSLATE("Heavy"), + wxTRANSLATE("Heavier"), + wxTRANSLATE("Heaviest"), +}; + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( Level, int, wxTRANSLATE("dB"), 10, 0, Enums::NumDbChoices - 1, 1 ); +Param( Passes, int, wxTRANSLATE("Passes"), kModerate, 0, kNumPasses - 1, 1 ); + +// +// EffectLeveller +// + EffectLeveller::EffectLeveller() { - Init(); + mPassIndex = DEF_Passes; + mDbIndex = DEF_Level; + + mNumPasses = mPassIndex + 1; + mDbSilenceThreshold = Enums::Db2Signal[mDbIndex]; + + CalcLevellerFactors(); } -#define NUM_PASSES_CHOICES 5 - -bool EffectLeveller::Init() +EffectLeveller::~EffectLeveller() { - mLevellerNumPasses = gPrefs->Read(wxT("/Effects/Leveller/LevellerNumPasses"), 2L) ; - if ((mLevellerNumPasses <= 0) || (mLevellerNumPasses > NUM_PASSES_CHOICES)) { // corrupted Prefs? - mLevellerNumPasses = 1; - gPrefs->Write(wxT("/Effects/Leveller/LevellerNumPasses"), 1); - } - mLevellerDbChoiceIndex = gPrefs->Read(wxT("/Effects/Leveller/LevellerDbChoiceIndex"), 10L); - if ((mLevellerDbChoiceIndex < 0) || (mLevellerDbChoiceIndex >= Enums::NumDbChoices)) { // corrupted Prefs? - mLevellerDbChoiceIndex = 0; //Least dB - gPrefs->Write(wxT("/Effects/Leveller/LevellerDbChoiceIndex"), mLevellerDbChoiceIndex); - } - gPrefs->Flush(); +} - mLevellerDbSilenceThreshold = Enums::Db2Signal[mLevellerDbChoiceIndex]; +// IdentInterface implementation + +wxString EffectLeveller::GetSymbol() +{ + return LEVELLER_PLUGIN_SYMBOL; +} + +wxString EffectLeveller::GetDescription() +{ + return wxTRANSLATE("Leveler is a simple, combined compressor and limiter effect for reducing the dynamic range of audio"); +} + +// EffectIdentInterface implementation + +EffectType EffectLeveller::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +int EffectLeveller::GetAudioInCount() +{ + return 1; +} + +int EffectLeveller::GetAudioOutCount() +{ + return 1; +} + +sampleCount EffectLeveller::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) +{ + float *ibuf = inBlock[0]; + float *obuf = outBlock[0]; + + for (sampleCount i = 0; i < blockLen; i++) + { + float frame = ibuf[i]; + for (int pass = 0; pass < mNumPasses; pass++) + { + frame = LevelOneFrame(frame); + } + obuf[i] = frame; + } + + return blockLen; +} + +bool EffectLeveller::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_Level, Enums::DbChoices[mDbIndex]); + parms.Write(KEY_Passes, kPassStrings[mPassIndex]); + + return true; +} + +bool EffectLeveller::SetAutomationParameters(EffectAutomationParameters & parms) +{ + // Allow for 2.1.0 and before + wxArrayString passChoices(kNumPasses, kPassStrings); + passChoices.Insert(wxT("1"), 0); + passChoices.Insert(wxT("2"), 1); + passChoices.Insert(wxT("3"), 2); + passChoices.Insert(wxT("4"), 3); + passChoices.Insert(wxT("5"), 4); + + ReadAndVerifyEnum(Level, wxArrayString(Enums::NumDbChoices,Enums::GetDbChoices())); + ReadAndVerifyEnum(Passes, passChoices); + + mDbIndex = Level; + mPassIndex = Passes; + + // Readjust for 2.1.0 or before + if (mPassIndex >= kNumPasses) + { + mPassIndex -= kNumPasses; + } + + mNumPasses = mPassIndex + 1; + mDbSilenceThreshold = Enums::Db2Signal[mDbIndex]; CalcLevellerFactors(); return true; } -bool EffectLeveller::CheckWhetherSkipEffect() +// Effect implementation + +bool EffectLeveller::Startup() { - return mLevellerNumPasses == 0; + wxString base = wxT("/Effects/Leveller/"); + + // Migrate settings from 2.1.0 or before + + // Already migrated, so bail + if (gPrefs->Exists(base + wxT("Migrated"))) + { + return true; + } + + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + mNumPasses = gPrefs->Read(base + wxT("LevellerNumPasses"), 2L); + if ((mNumPasses <= 0) || (mNumPasses > kNumPasses)) + { // corrupted Pr + mNumPasses = 1; + } + mDbIndex = gPrefs->Read(base + wxT("LevellerDbChoiceIndex"), 10L); + if ((mDbIndex < 0) || (mDbIndex >= Enums::NumDbChoices)) + { // cor + mDbIndex = 0; //Least dB + } + + SaveUserPreset(GetCurrentSettingsGroup()); + + // Do not migrate again + gPrefs->Write(base + wxT("Migrated"), true); + gPrefs->Flush(); + } + + return true; } -void EffectLeveller::End() +void EffectLeveller::PopulateOrExchange(ShuttleGui & S) { - int frameSum = (int)mFrameSum; - gPrefs->Write(wxT("/Validate/LevellerFrameSum"), frameSum); - gPrefs->Flush(); + wxASSERT(kNumPasses == WXSIZEOF(kPassStrings)); + + wxArrayString passChoices; + for (int i = 0; i < kNumPasses; i++) + { + passChoices.Add(wxGetTranslation(kPassStrings[i])); + } + + wxArrayString dBChoices(Enums::NumDbChoices,Enums::GetDbChoices()); + + S.SetBorder(5); + + S.StartVerticalLay(); + { + S.AddSpace(5); + S.StartMultiColumn(2, wxALIGN_CENTER); + { + S.AddChoice(_("Degree of Leveling:"), + wxT(""), + &passChoices)->SetValidator(wxGenericValidator(&mPassIndex)); + S.AddChoice(_("Noise Threshold:"), + wxT(""), + &dBChoices)->SetValidator(wxGenericValidator(&mDbIndex)); + } + S.EndMultiColumn(); + } + S.EndVerticalLay(); + + return; } +bool EffectLeveller::TransferDataToWindow() +{ + if (!mUIParent->TransferDataToWindow()) + { + return false; + } + + return true; +} + +bool EffectLeveller::TransferDataFromWindow() +{ + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } + + mNumPasses = mPassIndex + 1; + mDbSilenceThreshold = Enums::Db2Signal[mDbIndex]; + + CalcLevellerFactors(); + + return true; +} + +// EffectLeveller implementation + #define LEVELER_FACTORS 6 static double gLimit[LEVELER_FACTORS] = { 0.0001, 0.0, 0.1, 0.3, 0.5, 1.0 }; static double gAdjLimit[LEVELER_FACTORS]; @@ -87,8 +265,7 @@ static double gAdjFactor[LEVELER_FACTORS] = { 0.80, 1.00, 1.20, 1.20, 1.00, 0.80 void EffectLeveller::CalcLevellerFactors() { - mFrameSum = 0.0; - gLimit[1] = mLevellerDbSilenceThreshold; + gLimit[1] = mDbSilenceThreshold; int prev = 0; double addOnValue = 0.0; double prevLimit = 0.0; @@ -113,40 +290,6 @@ void EffectLeveller::CalcLevellerFactors() } } -bool EffectLeveller::PromptUser() -{ - LevellerDialog dlog(this, mParent); - dlog.mLevellerDbChoiceIndex = mLevellerDbChoiceIndex; - dlog.mLevellerNumPassesChoiceIndex = mLevellerNumPasses-1; - dlog.TransferDataToWindow(); - - dlog.CentreOnParent(); - dlog.ShowModal(); - - if (dlog.GetReturnCode() == wxID_CANCEL) { - return false; - } - - mLevellerNumPasses = dlog.mLevellerNumPassesChoiceIndex+1; - mLevellerDbChoiceIndex = dlog.mLevellerDbChoiceIndex; - mLevellerDbSilenceThreshold = Enums::Db2Signal[mLevellerDbChoiceIndex]; - - gPrefs->Write(wxT("/Effects/Leveller/LevellerDbChoiceIndex"), mLevellerDbChoiceIndex); - gPrefs->Write(wxT("/Effects/Leveller/LevellerNumPasses"), mLevellerNumPasses); - gPrefs->Flush(); - - CalcLevellerFactors(); - - return true; -} - -bool EffectLeveller::TransferParameters( Shuttle & shuttle ) -{ - shuttle.TransferEnum(wxT("dB"),mLevellerDbChoiceIndex,Enums::NumDbChoices,Enums::GetDbChoices()); - shuttle.TransferInt(wxT("Passes"),mLevellerNumPasses,1); - return true; -} - float EffectLeveller::LevelOneFrame(float frameInBuffer) { float curFrame; @@ -161,7 +304,6 @@ float EffectLeveller::LevelOneFrame(float frameInBuffer) curSign = 1.0; } fabsCurFrame = (float)fabs(curFrame); - mFrameSum += fabsCurFrame; for (int f = 0; f < LEVELER_FACTORS; ++f) { if (fabsCurFrame <= gLimit[f]) { @@ -173,74 +315,3 @@ float EffectLeveller::LevelOneFrame(float frameInBuffer) return (float)0.99; } -bool EffectLeveller::ProcessSimpleMono(float *buffer, sampleCount len) -{ - for (int pass = 0; pass < mLevellerNumPasses; ++pass) { - for (int i = 0; i < len; ++i) { - buffer[i] = LevelOneFrame(buffer[i]); - } - } - return true; -} - -//---------------------------------------------------------------------------- -// LevellerDialog -//---------------------------------------------------------------------------- - -BEGIN_EVENT_TABLE(LevellerDialog, EffectDialog) - EVT_BUTTON(ID_EFFECT_PREVIEW, LevellerDialog::OnPreview) -END_EVENT_TABLE() - -LevellerDialog::LevellerDialog(EffectLeveller *effect, wxWindow *parent) -: EffectDialog(parent, _("Leveler"), PROCESS_EFFECT), // Lynn called it "Leveller", but preferred spelling is "Leveler". - mEffect(effect) -{ - mLevellerNumPassesChoiceIndex = 0;// - mLevellerDbChoiceIndex = 0; - Init(); -} - -void LevellerDialog::PopulateOrExchange(ShuttleGui & S) -{ - wxArrayString db(Enums::NumDbChoices, Enums::GetDbChoices()); - wxArrayString numPasses; - - /* i18n-hint: Of strength of an effect. Not strongly.*/ - numPasses.Add(_("Light")); - numPasses.Add(_("Moderate")); - /* i18n-hint: Of strength of an effect. Strongly.*/ - numPasses.Add(_("Heavy")); - numPasses.Add(_("Heavier")); - numPasses.Add(_("Heaviest")); - - S.SetBorder(5); - S.AddSpace(5); - - S.StartMultiColumn(2); - { - S.TieChoice(_("Degree of Leveling:"), - mLevellerNumPassesChoiceIndex, - &numPasses); - S.TieChoice(_("Noise Threshold:"), - mLevellerDbChoiceIndex, - &db); - } - S.EndMultiColumn(); -} - -void LevellerDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - - // Save & restore parameters around Preview. - int oldLevellerDbChoiceIndex = mEffect->mLevellerDbChoiceIndex; - int oldLevellerNumPasses = mEffect->mLevellerNumPasses; - - mEffect->mLevellerDbChoiceIndex = mLevellerDbChoiceIndex; - mEffect->mLevellerNumPasses = mLevellerNumPassesChoiceIndex+1; - - mEffect->Preview(); - - mEffect->mLevellerDbChoiceIndex = oldLevellerDbChoiceIndex; - mEffect->mLevellerNumPasses = oldLevellerNumPasses; -} diff --git a/src/effects/Leveller.h b/src/effects/Leveller.h index fc5a50116..1e1fbadb5 100644 --- a/src/effects/Leveller.h +++ b/src/effects/Leveller.h @@ -11,83 +11,57 @@ #ifndef __AUDACITY_EFFECT_LEVELER__ #define __AUDACITY_EFFECT_LEVELER__ -#include "SimpleMono.h" - -#include +#include #include -#include -class EffectLeveller: public EffectSimpleMono +#include "../ShuttleGui.h" + +#include "Effect.h" + +#define LEVELLER_PLUGIN_SYMBOL wxTRANSLATE("Leveller") + +class EffectLeveller : public Effect { - friend class LevellerDialog; - - public: +public: EffectLeveller(); + virtual ~EffectLeveller(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Leveler...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#CompressorPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Leveller")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Applying Leveler...")); - } - virtual bool Init(); - virtual void End(); - virtual bool CheckWhetherSkipEffect(); - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); + virtual EffectType GetType(); - protected: - virtual bool ProcessSimpleMono(float *buffer, sampleCount len); + // EffectClientInterface implementation - private: - void CalcLevellerFactors(); - int mLevellerDbChoiceIndex; - int mLevellerNumPasses; - double mFrameSum; - double mLevellerDbSilenceThreshold; - float LevelOneFrame(float frame); -}; + virtual int GetAudioInCount(); + virtual int GetAudioOutCount(); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); -//---------------------------------------------------------------------------- -// LevellerDialog -//---------------------------------------------------------------------------- + // Effect implementation -class LevellerDialog: public EffectDialog -{ - public: - // constructors and destructors - LevellerDialog(EffectLeveller *effect, wxWindow * parent); + virtual bool Startup(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); - // method declarations - void PopulateOrExchange(ShuttleGui & S); -// bool TransferDataToWindow(); -// bool TransferDataFromWindow(); +private: + // EffectLeveller implementation - private: - // handlers - void OnPreview( wxCommandEvent &event ); + void CalcLevellerFactors(); + float LevelOneFrame(float frame); - private: - EffectLeveller *mEffect; - wxChoice *mLevellerDbSilenceThresholdChoice; - wxChoice *mLevellerNumPassesChoice; +private: + int mNumPasses; + double mDbSilenceThreshold; - DECLARE_EVENT_TABLE() - - public: - int mLevellerDbChoiceIndex; - int mLevellerNumPassesChoiceIndex; + int mDbIndex; + int mPassIndex; }; #endif diff --git a/src/effects/LoadEffects.cpp b/src/effects/LoadEffects.cpp index 4469637dc..892cb4d89 100644 --- a/src/effects/LoadEffects.cpp +++ b/src/effects/LoadEffects.cpp @@ -57,216 +57,269 @@ #include "ChangeTempo.h" #endif -void LoadEffects() +// +// Include the SoundTouch effects, if requested +// +#if defined(USE_SOUNDTOUCH) +#define SOUNDTOUCH_EFFECTS \ + EFFECT( CHANGEPITCH, EffectChangePitch() ) \ + EFFECT( CHANGETEMPO, EffectChangeTempo() ) +#else +#define SOUNDTOUCH_EFFECTS +#endif + +// +// Select the desired Noise Reduction/Removal effect +// +#if defined(EXPERIMENTAL_NOISE_REDUCTION) +#define NOISEREDUCTION_EFFECT \ + EFFECT( NOISEREDUCTION, EffectNoiseReduction() ) +#else +#define NOISEREDUCTION_EFFECT \ + EFFECT( NOISEREMOVAL, EffectNoiseRemoval() ) +#endif + +// +// Include the Classic Filters effect, if requested +// +#if defined(EXPERIMENTAL_SCIENCE_FILTERS) +#define CLASSICFILTER_EFFECT \ + EFFECT( CLASSICFILTERS, EffectScienFilter() ) +#else +#define CLASSICFILTER_EFFECT +#endif + +// +// Include the SBSMS effect, if requested +// +#if defined(USE_SBSMS) +#define SBSMS_EFFECTS \ + EFFECT( TIMESCALE, EffectTimeScale() ) +#else +#define SBSMS_EFFECTS +#endif + +// +// Define the complete list of effects and how to instantiate each +// +#define EFFECT_LIST \ + EFFECT( CHIRP, EffectToneGen(true) ) \ + EFFECT( DTMFTONES, EffectDtmf() ) \ + EFFECT( NOISE, EffectNoise() ) \ + EFFECT( SILENCE, EffectSilence() ) \ + EFFECT( TONE, EffectToneGen(false) ) \ + EFFECT( AMPLIFY, EffectAmplify() ) \ + EFFECT( AUTODUCK, EffectAutoDuck() ) \ + EFFECT( BASSTREBLE, EffectBassTreble() ) \ + EFFECT( CHANGESPEED, EffectChangeSpeed() ) \ + EFFECT( CLICKREMOVAL, EffectClickRemoval() ) \ + EFFECT( COMPRESSOR, EffectCompressor() ) \ + EFFECT( ECHO, EffectEcho() ) \ + EFFECT( PAULSTRETCH, EffectPaulstretch() ) \ + EFFECT( EQUALIZATION, EffectEqualization() ) \ + EFFECT( FADEIN, EffectFade(true) ) \ + EFFECT( FADEOUT, EffectFade(false) ) \ + EFFECT( INVERT, EffectInvert() ) \ + EFFECT( LEVELLER, EffectLeveller() ) \ + EFFECT( NORMALIZE, EffectNormalize() ) \ + EFFECT( PHASER, EffectPhaser() ) \ + EFFECT( REPEAT, EffectRepeat() ) \ + EFFECT( REVERB, EffectReverb() ) \ + EFFECT( REVERSE, EffectReverse() ) \ + EFFECT( STEREOTOMONO, EffectStereoToMono() ) \ + EFFECT( TRUNCATESILENCE, EffectTruncSilence() ) \ + EFFECT( WAHWAH, EffectWahwah() ) \ + EFFECT( FINDCLIPPING, EffectFindClipping() ) \ + NOISEREDUCTION_EFFECT \ + CLASSICFILTER_EFFECT \ + SOUNDTOUCH_EFFECTS \ + SBSMS_EFFECTS + +// +// Define the EFFECT() macro to generate enum names +// +#define EFFECT(n, i) ENUM_ ## n, + +// +// Create the enum for the list of effects (will be used in a switch statement) +// +enum { + EFFECT_LIST +}; - EffectManager& em = EffectManager::Get(); +// +// Redefine EFFECT() to add the effect's name to an array +// +#undef EFFECT +#define EFFECT(n, i) n ## _PLUGIN_SYMBOL, -#ifdef EFFECT_CATEGORIES +// +// Create the effect name array +// +static const wxChar *kEffectNames[] = +{ + EFFECT_LIST +}; - // Create effect category graph. These categories and relationships - // are taken from revision 2 of lv2.ttl, loaders for other plugin systems - // (such as LADSPA/LRDF) should map their categories to these ones when - // applicable. Individual LADSPA/LRDF and LV2 plugins can add new - // categories and make them subcategories of the existing ones, but not - // add subcategory relationships between these categories. - // - // We need some persistent, global identifiers for categories - LRDF - // and LV2 uses URI strings so we do that too. The URIs here are the - // same ones as in lv2.ttl. Category identifiers in other plugin systems - // must be mapped to URIs by their loaders. +// +// Redefine EFFECT() to generate a case statement for the lookup switch +// +#undef EFFECT +#define EFFECT(n, i) case ENUM_ ## n: return new i; -#define LV2PREFIX "http://lv2plug.in/ns/lv2core#" - - typedef EffectCategory* CatPtr; - - CatPtr gen = em.AddCategory(wxT(LV2PREFIX) wxT("GeneratorPlugin"), - _("Generator")); - CatPtr inst = em.AddCategory(wxT(LV2PREFIX) wxT("InstrumentPlugin"), - /* i18n-hint: (noun).*/ - _("Instrument")); - CatPtr osc = em.AddCategory(wxT(LV2PREFIX) wxT("OscillatorPlugin"), - _("Oscillator")); - CatPtr util = em.AddCategory(wxT(LV2PREFIX) wxT("UtilityPlugin"), - _("Utility")); - CatPtr conv = em.AddCategory(wxT(LV2PREFIX) wxT("ConverterPlugin"), - _("Converter")); - CatPtr anal = em.AddCategory(wxT(LV2PREFIX) wxT("AnalyserPlugin"), - _("Analyser")); - CatPtr mix = em.AddCategory(wxT(LV2PREFIX) wxT("MixerPlugin"), - _("Mixer")); - CatPtr sim = em.AddCategory(wxT(LV2PREFIX) wxT("SimulatorPlugin"), - _("Simulator")); - CatPtr del = em.AddCategory(wxT(LV2PREFIX) wxT("DelayPlugin"), - _("Delay")); - CatPtr mod = em.AddCategory(wxT(LV2PREFIX) wxT("ModulatorPlugin"), - _("Modulator")); - CatPtr rev = em.AddCategory(wxT(LV2PREFIX) wxT("ReverbPlugin"), - _("Reverb")); - CatPtr phas = em.AddCategory(wxT(LV2PREFIX) wxT("PhaserPlugin"), - _("Phaser")); - CatPtr flng = em.AddCategory(wxT(LV2PREFIX) wxT("FlangerPlugin"), - _("Flanger")); - CatPtr chor = em.AddCategory(wxT(LV2PREFIX) wxT("ChorusPlugin"), - _("Chorus")); - CatPtr flt = em.AddCategory(wxT(LV2PREFIX) wxT("FilterPlugin"), - _("Filter")); - CatPtr lp = em.AddCategory(wxT(LV2PREFIX) wxT("LowpassPlugin"), - _("Lowpass")); - CatPtr bp = em.AddCategory(wxT(LV2PREFIX) wxT("BandpassPlugin"), - _("Bandpass")); - CatPtr hp = em.AddCategory(wxT(LV2PREFIX) wxT("HighpassPlugin"), - _("Highpass")); - CatPtr comb = em.AddCategory(wxT(LV2PREFIX) wxT("CombPlugin"), - _("Comb")); - CatPtr alp = em.AddCategory(wxT(LV2PREFIX) wxT("AllpassPlugin"), - _("Allpass")); - CatPtr eq = em.AddCategory(wxT(LV2PREFIX) wxT("EQPlugin"), - _("Equaliser")); - CatPtr peq = em.AddCategory(wxT(LV2PREFIX) wxT("ParaEQPlugin"), - _("Parametric")); - CatPtr meq = em.AddCategory(wxT(LV2PREFIX) wxT("MultiEQPlugin"), - _("Multiband")); - CatPtr spec = em.AddCategory(wxT(LV2PREFIX) wxT("SpectralPlugin"), - _("Spectral Processor")); - CatPtr ptch = em.AddCategory(wxT(LV2PREFIX) wxT("PitchPlugin"), - _("Pitch Shifter")); - CatPtr amp = em.AddCategory(wxT(LV2PREFIX) wxT("AmplifierPlugin"), - _("Amplifier")); - CatPtr dist = em.AddCategory(wxT(LV2PREFIX) wxT("DistortionPlugin"), - _("Distortion")); - CatPtr shp = em.AddCategory(wxT(LV2PREFIX) wxT("WaveshaperPlugin"), - _("Waveshaper")); - CatPtr dyn = em.AddCategory(wxT(LV2PREFIX) wxT("DynamicsPlugin"), - _("Dynamics Processor")); - CatPtr cmp = em.AddCategory(wxT(LV2PREFIX) wxT("CompressorPlugin"), - _("Compressor")); - CatPtr exp = em.AddCategory(wxT(LV2PREFIX) wxT("ExpanderPlugin"), - _("Expander")); - CatPtr lim = em.AddCategory(wxT(LV2PREFIX) wxT("LimiterPlugin"), - _("Limiter")); - CatPtr gate = em.AddCategory(wxT(LV2PREFIX) wxT("GatePlugin"), - _("Gate")); - - em.AddCategoryParent(inst, gen); - em.AddCategoryParent(osc, gen); - em.AddCategoryParent(conv, util); - em.AddCategoryParent(anal, util); - em.AddCategoryParent(mix, util); - em.AddCategoryParent(rev, sim); - em.AddCategoryParent(rev, del); - em.AddCategoryParent(phas, mod); - em.AddCategoryParent(flng, mod); - em.AddCategoryParent(chor, mod); - em.AddCategoryParent(lp, flt); - em.AddCategoryParent(bp, flt); - em.AddCategoryParent(hp, flt); - em.AddCategoryParent(comb, flt); - em.AddCategoryParent(alp, flt); - em.AddCategoryParent(eq, flt); - em.AddCategoryParent(peq, eq); - em.AddCategoryParent(meq, eq); - em.AddCategoryParent(ptch, spec); - em.AddCategoryParent(shp, dist); - em.AddCategoryParent(cmp, dyn); - em.AddCategoryParent(exp, dyn); - em.AddCategoryParent(lim, dyn); - em.AddCategoryParent(gate, dyn); - - // We also add a couple of categories for internal use. These are not - // in lv2.ttl. - -#define ATEAM "http://audacityteam.org/namespace#" - -#ifdef EXPERIMENTAL_NOISE_REDUCTION - CatPtr nrm = em.AddCategory(wxT(ATEAM) wxT("NoiseReduction"), - _("Noise Reduction")); -#else - CatPtr nrm = em.AddCategory(wxT(ATEAM) wxT("NoiseRemoval"), - _("Noise Removal")); -#endif - CatPtr pnt = em.AddCategory(wxT(ATEAM) wxT("PitchAndTempo"), - _("Pitch and Tempo")); - CatPtr tim = em.AddCategory(wxT(ATEAM) wxT("TimelineChanger"), - _("Timeline Changer")); - CatPtr aTim = em.AddCategory(wxT(ATEAM) wxT("TimeAnalyser"), - _("Time")); - CatPtr onst = em.AddCategory(wxT(ATEAM) wxT("OnsetDetector"), - _("Onsets")); - em.AddCategoryParent(nrm, util); - em.AddCategoryParent(tim, util); - em.AddCategoryParent(aTim, anal); - em.AddCategoryParent(onst, aTim); - - // We freeze the internal subcategory relations between the categories - // added so far so LADSPA/LRDF or other category systems don't ruin - // our hierarchy. - em.FreezeCategories(); - -#endif - - // Generate menu - em.RegisterEffect(new EffectNoise()); - em.RegisterEffect(new EffectSilence()); - em.RegisterEffect(new EffectToneGen()); - em.RegisterEffect(new EffectDtmf()); - // A little magic to convert 'Tone' to chirps. - em.RegisterEffect(&((new EffectToneGen())->EnableForChirps())); - - // Effect menu - - em.RegisterEffect(new EffectAmplify()); - - //Commented out now that the Compressor effect works better - //em.RegisterEffect(new EffectAvcCompressor()); - - const int SIMPLE_EFFECT = BUILTIN_EFFECT | PROCESS_EFFECT; - // In this list, designating an effect as 'SIMPLE_EFFECT' just means - // that it should be included in even the most basic of menus. - - em.RegisterEffect(new EffectAutoDuck()); - em.RegisterEffect(new EffectBassTreble()); - em.RegisterEffect(new EffectChangeSpeed()); - #ifdef USE_SOUNDTOUCH - em.RegisterEffect(new EffectChangePitch()); - em.RegisterEffect(new EffectChangeTempo()); - #endif - em.RegisterEffect(new EffectClickRemoval()); - em.RegisterEffect(new EffectCompressor()); - em.RegisterEffect(new EffectEcho()); - em.RegisterEffect(new EffectPaulstretch()); - em.RegisterEffect(new EffectEqualization()); - em.RegisterEffect(new EffectFadeIn(), SIMPLE_EFFECT); - em.RegisterEffect(new EffectFadeOut(), SIMPLE_EFFECT); - em.RegisterEffect(new EffectInvert()); - em.RegisterEffect(new EffectLeveller(), SIMPLE_EFFECT); -#ifdef EXPERIMENTAL_NOISE_REDUCTION - em.RegisterEffect(new EffectNoiseReduction(), SIMPLE_EFFECT); -#else - em.RegisterEffect(new EffectNoiseRemoval(), SIMPLE_EFFECT); -#endif - em.RegisterEffect(new EffectNormalize(), SIMPLE_EFFECT); - em.RegisterEffect(new EffectPhaser()); - em.RegisterEffect(new EffectRepair()); - em.RegisterEffect(new EffectRepeat()); - em.RegisterEffect(new EffectReverb()); - em.RegisterEffect(new EffectReverse()); -#ifdef EXPERIMENTAL_SCIENCE_FILTERS - em.RegisterEffect(new EffectScienFilter()); -#endif - em.RegisterEffect(new EffectStereoToMono(), HIDDEN_EFFECT);// NOT in normal effects list. - em.RegisterEffect(new EffectTruncSilence(), SIMPLE_EFFECT); -#ifdef USE_SBSMS - em.RegisterEffect(new EffectTimeScale()); -#endif - em.RegisterEffect(new EffectWahwah()); - - // Analyze menu - em.RegisterEffect(new EffectFindClipping()); +// ============================================================================ +// Module registration entry point +// +// This is the symbol that Audacity looks for when the module is built as a +// dynamic library. +// +// When the module is builtin to Audacity, we use the same function, but it is +// declared static so as not to clash with other builtin modules. +// ============================================================================ +DECLARE_MODULE_ENTRY(AudacityModule) +{ + // Create and register the importer + return new BuiltinEffectsModule(moduleManager, path); } -void UnloadEffects() +// ============================================================================ +// Register this as a builtin module +// ============================================================================ +DECLARE_BUILTIN_MODULE(BuiltinsEffectBuiltin); + +/////////////////////////////////////////////////////////////////////////////// +// +// BuiltinEffectsModule +// +/////////////////////////////////////////////////////////////////////////////// + +BuiltinEffectsModule::BuiltinEffectsModule(ModuleManagerInterface *moduleManager, + const wxString *path) { - EffectManager::Get().UnregisterEffects(); + mModMan = moduleManager; + if (path) + { + mPath = *path; + } } +BuiltinEffectsModule::~BuiltinEffectsModule() +{ + mPath.Clear(); +} + +// ============================================================================ +// IdentInterface implementation +// ============================================================================ + +wxString BuiltinEffectsModule::GetPath() +{ + return mPath; +} + +wxString BuiltinEffectsModule::GetSymbol() +{ + return wxTRANSLATE("Builtin Effects"); +} + +wxString BuiltinEffectsModule::GetName() +{ + return wxTRANSLATE("Builtin Effects"); +} + +wxString BuiltinEffectsModule::GetVendor() +{ + return wxTRANSLATE("The Audacity Team"); +} + +wxString BuiltinEffectsModule::GetVersion() +{ + // This "may" be different if this were to be maintained as a separate DLL + return AUDACITY_VERSION_STRING; +} + +wxString BuiltinEffectsModule::GetDescription() +{ + return wxTRANSLATE("Provides builtin effects to Audacity"); +} + +// ============================================================================ +// ModuleInterface implementation +// ============================================================================ + +bool BuiltinEffectsModule::Initialize() +{ + for (size_t i = 0; i < WXSIZEOF(kEffectNames); i++) + { + mNames.Add(wxString(BUILTIN_EFFECT_PREFIX) + kEffectNames[i]); + } + + return true; +} + +void BuiltinEffectsModule::Terminate() +{ + // Nothing to do here + return; +} + +bool BuiltinEffectsModule::AutoRegisterPlugins(PluginManagerInterface & WXUNUSED(pm)) +{ + // Nothing to do here + return false; +} + +wxArrayString BuiltinEffectsModule::FindPlugins(PluginManagerInterface & WXUNUSED(pm)) +{ + return mNames; +} + +bool BuiltinEffectsModule::RegisterPlugin(PluginManagerInterface & pm, const wxString & path) +{ + Effect *effect = Instantiate(path); + if (effect) + { + pm.RegisterPlugin(this, effect); + delete effect; + return true; + } + + return false; +} + +bool BuiltinEffectsModule::IsPluginValid(const wxString & path) +{ + return mNames.Index(path) != wxNOT_FOUND; +} + +IdentInterface *BuiltinEffectsModule::CreateInstance(const wxString & path) +{ + return Instantiate(path); +} + +void BuiltinEffectsModule::DeleteInstance(IdentInterface *instance) +{ + Effect *effect = dynamic_cast(instance); + if (effect) + { + delete effect; + } +} + +// ============================================================================ +// BuiltinEffectsModule implementation +// ============================================================================ + +Effect *BuiltinEffectsModule::Instantiate(const wxString & path) +{ + wxASSERT(path.StartsWith(BUILTIN_EFFECT_PREFIX)); + wxASSERT(mNames.Index(path) != wxNOT_FOUND); + + switch (mNames.Index(path)) + { + EFFECT_LIST; + } + + return NULL; +} diff --git a/src/effects/LoadEffects.h b/src/effects/LoadEffects.h index e1bb1478e..3e3908e77 100644 --- a/src/effects/LoadEffects.h +++ b/src/effects/LoadEffects.h @@ -8,12 +8,55 @@ **********************************************************************/ -#ifndef __AUDACITY_LOAD_EFFECTS__ -#define __AUDACITY_LOAD_EFFECTS__ +#include "audacity/ModuleInterface.h" +#include "audacity/EffectInterface.h" +#include "audacity/PluginInterface.h" -#include +#include "Effect.h" -void LoadEffects(); -void UnloadEffects(); +/////////////////////////////////////////////////////////////////////////////// +// +// BuiltinEffectsModule +// +/////////////////////////////////////////////////////////////////////////////// -#endif // define __AUDACITY_LOAD_EFFECTS__ +class BuiltinEffectsModule : public ModuleInterface +{ +public: + BuiltinEffectsModule(ModuleManagerInterface *moduleManager, const wxString *path); + virtual ~BuiltinEffectsModule(); + + // IdentInterface implementatino + + virtual wxString GetPath(); + virtual wxString GetSymbol(); + virtual wxString GetName(); + virtual wxString GetVendor(); + virtual wxString GetVersion(); + virtual wxString GetDescription(); + + // ModuleInterface implementation + + virtual bool Initialize(); + virtual void Terminate(); + + virtual bool AutoRegisterPlugins(PluginManagerInterface & pm); + virtual wxArrayString FindPlugins(PluginManagerInterface & pm); + virtual bool RegisterPlugin(PluginManagerInterface & pm, const wxString & path); + + virtual bool IsPluginValid(const wxString & path); + + virtual IdentInterface *CreateInstance(const wxString & path); + virtual void DeleteInstance(IdentInterface *instance); + +private: + // BuiltinEffectModule implementation + + Effect *Instantiate(const wxString & path); + +private: + ModuleManagerInterface *mModMan; + wxString mPath; + + wxArrayString mNames; +}; diff --git a/src/effects/Noise.cpp b/src/effects/Noise.cpp index 9a132beb7..14de00289 100644 --- a/src/effects/Noise.cpp +++ b/src/effects/Noise.cpp @@ -9,238 +9,260 @@ *******************************************************************//** \class EffectNoise -\brief An Effect for the "Generator" menu to add white noise. +\brief An effect to add white noise. *//*******************************************************************/ #include "../Audacity.h" -#include "../Project.h" -#include "../Prefs.h" -#include "../ShuttleGui.h" -#include "../WaveTrack.h" -#include "../widgets/NumericTextCtrl.h" -#include "Noise.h" #include -#ifndef M_PI -#define M_PI 3.14159265358979323846 /* pi */ -#endif -#define AMP_MIN 0 -#define AMP_MAX 1 +#include +#include +#include +#include + +#include "../Prefs.h" +#include "../widgets/valnum.h" + +#include "Noise.h" + +enum kTypes +{ + kWhite, + kPink, + kBrownian, + kNumTypes +}; + +static const wxChar *kTypeStrings[kNumTypes] = +{ + wxTRANSLATE("White"), + wxTRANSLATE("Pink"), + wxTRANSLATE("Brownian") +}; + +// Name Type Key Def Min Max Scale +Param( Type, int, wxTRANSLATE("Type"), kWhite, 0, kNumTypes - 1, 1 ); +Param( Amp, double, wxTRANSLATE("Amplitude"), 0.8, 0.0, 1.0, 1 ); // // EffectNoise // -bool EffectNoise::Init() +EffectNoise::EffectNoise() { - gPrefs->Read(wxT("/Effects/Noise/Duration"), &mDuration, 1L); - return true; + mType = DEF_Type; + mAmp = DEF_Amp; + mDuration = GetDefaultDuration(); + + y = z = buf0 = buf1 = buf2 = buf3 = buf4 = buf5 = buf6 = 0; } -bool EffectNoise::PromptUser() +EffectNoise::~EffectNoise() { - wxArrayString noiseTypeList; - - noiseTypeList.Add(_("White")); - noiseTypeList.Add(_("Pink")); - noiseTypeList.Add(_("Brownian")); - - NoiseDialog dlog(this, mParent, _("Noise Generator")); - - // dialog will be passed values from effect - // Effect retrieves values from saved config - // Dialog will take care of using them to initialize controls - // If there is a selection, use that duration, otherwise use - // value from saved config: this is useful is user wants to - // replace selection with noise - // - if (mT1 > mT0) { - mDuration = mT1 - mT0; - dlog.nIsSelection = true; - } else { - gPrefs->Read(wxT("/Effects/Noise/Duration"), &mDuration, 30L); - dlog.nIsSelection = false; - } - - gPrefs->Read(wxT("/Effects/Noise/Type"), &noiseType, 0L); - gPrefs->Read(wxT("/Effects/Noise/Amplitude"), &noiseAmplitude, 0.8f); - - // Initialize dialog locals - dlog.nDuration = mDuration; - dlog.nAmplitude = noiseAmplitude; - dlog.nType = noiseType; - dlog.nTypeList = &noiseTypeList; - - // start dialog - dlog.Init(); - dlog.TransferDataToWindow(); - dlog.Fit(); - dlog.ShowModal(); - - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; - - // if there was an OK, retrieve values - noiseType = dlog.nType; - mDuration = dlog.nDuration; - noiseAmplitude = dlog.nAmplitude; - - return true; } -bool EffectNoise::TransferParameters( Shuttle & WXUNUSED(shuttle) ) +// IdentInterface implementation + +wxString EffectNoise::GetSymbol() { - return true; + return NOISE_PLUGIN_SYMBOL; } -bool EffectNoise::MakeNoise(float *buffer, sampleCount len, float fs, float amplitude) +wxString EffectNoise::GetDescription() { + return wxTRANSLATE("Generates one of three different types of noise"); +} + +// EffectIdentInterface implementation + +EffectType EffectNoise::GetType() +{ + return EffectTypeGenerate; +} + +// EffectClientInterface implementation + +int EffectNoise::GetAudioOutCount() +{ + return 1; +} + +sampleCount EffectNoise::ProcessBlock(float **WXUNUSED(inbuf), float **outbuf, sampleCount size) +{ + float *buffer = outbuf[0]; + float white; - sampleCount i; - float div = ((float)RAND_MAX) / 2.0f; + float amplitude; + float div = ((float) RAND_MAX) / 2.0f; - switch (noiseType) { + switch (mType) + { default: - case 0: // white - for(i=0; i 0.01)? 9.0/sqrt(fs) : 0.01; + case kBrownian: // Brownian + //float leakage=0.997; // experimental value at 44.1kHz + //double scaling = 0.05; // experimental value at 44.1kHz + // min and max protect against instability at extreme sample rates. + float leakage = ((mSampleRate - 144.0) / mSampleRate < 0.9999) + ? (mSampleRate - 144.0) / mSampleRate + : 0.9999f; - for(i=0; i 0.01) + ? 9.0 / sqrt(mSampleRate) + : 0.01f; + + for (sampleCount i = 0; i < size; i++) + { + white = (rand() / div) - 1.0f; z = leakage * y + white * scaling; - y = (fabs(z) > 1.0) ? (leakage * y - white * scaling) : z; - buffer[i] = amplitude * y; - } - break; + y = fabs(z) > 1.0 + ? leakage * y - white * scaling + : z; + buffer[i] = mAmp * y; + } + break; } + + return size; +} + +bool EffectNoise::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_Type, kTypeStrings[mType]); + parms.Write(KEY_Amp, mAmp); + return true; } -void EffectNoise::GenerateBlock(float *data, - const WaveTrack &track, - sampleCount block) +bool EffectNoise::SetAutomationParameters(EffectAutomationParameters & parms) { - MakeNoise(data, block, track.GetRate(), noiseAmplitude); + ReadAndVerifyEnum(Type, wxArrayString(kNumTypes, kTypeStrings)); + ReadAndVerifyDouble(Amp); + + mType = Type; + mAmp = Amp; + + return true; } -void EffectNoise::Success() -{ - /* save last used values - save duration unless value was got from selection, so we save only - when user explicitely setup a value - */ - if (mT1 == mT0) - gPrefs->Write(wxT("/Effects/Noise/Duration"), mDuration); +// Effect implementation - gPrefs->Write(wxT("/Effects/Noise/Type"), noiseType); - gPrefs->Write(wxT("/Effects/Noise/Amplitude"), noiseAmplitude); - gPrefs->Flush(); +bool EffectNoise::Startup() +{ + wxString base = wxT("/Effects/Noise/"); + + // Migrate settings from 2.1.0 or before + + // Already migrated, so bail + if (gPrefs->Exists(base + wxT("Migrated"))) + { + return true; + } + + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + gPrefs->Read(base + wxT("Type"), &mType, 0L); + gPrefs->Read(base + wxT("Amplitude"), &mAmp, 0.8f); + + SaveUserPreset(GetCurrentSettingsGroup()); + + // Do not migrate again + gPrefs->Write(base + wxT("Migrated"), true); + gPrefs->Flush(); + } + + return true; } -//---------------------------------------------------------------------------- -// NoiseDialog -//---------------------------------------------------------------------------- - -BEGIN_EVENT_TABLE(NoiseDialog, EffectDialog) - EVT_COMMAND(wxID_ANY, EVT_TIMETEXTCTRL_UPDATED, NoiseDialog::OnTimeCtrlUpdate) -END_EVENT_TABLE() - -NoiseDialog::NoiseDialog(EffectNoise * effect, wxWindow * parent, const wxString & title) -: EffectDialog(parent, title, INSERT_EFFECT), - mEffect(effect) +void EffectNoise::PopulateOrExchange(ShuttleGui & S) { - mNoiseDurationT = NULL; - /* // already initialized in EffectNoise::PromptUser - nDuration = mDuration; - nAmplitude = noiseAmplitude; - nType = noiseType; - */ -} + wxASSERT(kNumTypes == WXSIZEOF(kTypeStrings)); + + wxArrayString typeChoices; + for (int i = 0; i < kNumTypes; i++) + { + typeChoices.Add(wxGetTranslation(kTypeStrings[i])); + } -void NoiseDialog::PopulateOrExchange( ShuttleGui & S ) -{ S.StartMultiColumn(2, wxCENTER); { - S.TieChoice(_("Noise type:"), nType, nTypeList); - S.TieNumericTextBox(_("Amplitude (0-1)") + wxString(wxT(":")), nAmplitude, 10); - S.AddPrompt(_("Duration") + wxString(wxT(":"))); - if (mNoiseDurationT == NULL) - { - mNoiseDurationT = new - NumericTextCtrl(NumericConverter::TIME, this, - wxID_ANY, - wxT(""), - nDuration, - mEffect->mProjectRate, - wxDefaultPosition, - wxDefaultSize, - true); - /* use this instead of "seconds" because if a selection is passed to - * the effect, I want it (nDuration) to be used as the duration, and - * with "seconds" this does not always work properly. For example, - * it rounds down to zero... */ - mNoiseDurationT->SetName(_("Duration")); - mNoiseDurationT->SetFormatString(mNoiseDurationT->GetBuiltinFormat(nIsSelection==true?(_("hh:mm:ss + samples")):(_("hh:mm:ss + milliseconds")))); - mNoiseDurationT->EnableMenu(); - } + S.AddChoice(_("Noise type:"), wxT(""), &typeChoices)->SetValidator(wxGenericValidator(&mType)); + + FloatingPointValidator vldAmp(1, &mAmp, NUM_VAL_NO_TRAILING_ZEROES); + vldAmp.SetRange(MIN_Amp, MAX_Amp); + S.AddTextBox(_("Amplitude (0-1):"), wxT(""), 12)->SetValidator(vldAmp); + S.AddPrompt(_("Duration:")); + + mNoiseDurationT = new + NumericTextCtrl(NumericConverter::TIME, + S.GetParent(), + wxID_ANY, + /* use this instead of "seconds" because if a selection is passed to + * the effect, I want it (nDuration) to be used as the duration, and + * with "seconds" this does not always work properly. For example, + * it rounds down to zero... */ + (mT1 > mT0) ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), + mDuration, + mProjectRate, + wxDefaultPosition, + wxDefaultSize, + true); + mNoiseDurationT->SetName(_("Duration")); + mNoiseDurationT->EnableMenu(); S.AddWindow(mNoiseDurationT, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL); } S.EndMultiColumn(); } -bool NoiseDialog::TransferDataToWindow() +bool EffectNoise::TransferDataToWindow() { - EffectDialog::TransferDataToWindow(); + if (!mUIParent->TransferDataToWindow()) + { + return false; + } - // Must handle this ourselves since ShuttleGui doesn't know about it - mNoiseDurationT->SetValue(nDuration); + mNoiseDurationT->SetValue(mDuration); return true; } -bool NoiseDialog::TransferDataFromWindow() +bool EffectNoise::TransferDataFromWindow() { - EffectDialog::TransferDataFromWindow(); + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } - nAmplitude = TrapDouble(nAmplitude, AMP_MIN, AMP_MAX); - - // Must handle this ourselves since ShuttleGui doesn't know about it - nDuration = mNoiseDurationT->GetValue(); + mDuration = mNoiseDurationT->GetValue(); return true; } - -void NoiseDialog::OnTimeCtrlUpdate(wxCommandEvent & WXUNUSED(event)) { - Fit(); -} diff --git a/src/effects/Noise.h b/src/effects/Noise.h index e6c67712b..4eda9115d 100644 --- a/src/effects/Noise.h +++ b/src/effects/Noise.h @@ -6,119 +6,60 @@ Dominic Mazzoni - An effect for the "Generator" menu to add white noise. + An effect to add white noise. **********************************************************************/ #ifndef __AUDACITY_EFFECT_NOISE__ #define __AUDACITY_EFFECT_NOISE__ -#include -#include +#include -#include "Generator.h" +#include "../ShuttleGui.h" +#include "../widgets/NumericTextCtrl.h" -class wxString; -class wxChoice; -class wxTextCtrl; -class NumericTextCtrl; -class ShuttleGui; +#include "Effect.h" -#define __UNINITIALIZED__ (-1) +#define NOISE_PLUGIN_SYMBOL wxTRANSLATE("Noise") -class WaveTrack; +class EffectNoise : public Effect +{ +public: + EffectNoise(); + virtual ~EffectNoise(); + // IdentInterface implementation -class EffectNoise : public BlockGenerator { + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - public: - EffectNoise() { - SetEffectFlags(BUILTIN_EFFECT | INSERT_EFFECT); - noiseType = 0; - noiseAmplitude = 1.0; - y = z = buf0 = buf1 = buf2 = buf3 = buf4 = buf5 = buf6 = 0; - } - virtual bool Init(); + // EffectIdentInterface implementation - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Noise...")); - } + virtual EffectType GetType(); - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#GeneratorPlugin")); - return result; - } + // EffectClientInterface implementation - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Noise")); - } + virtual int GetAudioOutCount(); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); - // Return true if the effect supports processing via batch chains. - virtual bool SupportsChains() { - return false; - } + // Effect implementation - virtual wxString GetEffectDescription() { - return wxString::Format(_("Applied effect: Generate Noise, %.6lf seconds"), mDuration); - } + virtual bool Startup(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); - virtual wxString GetEffectAction() { - return wxString(_("Generating Noise")); - } +private: + // EffectNoise implementation - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); +private: + int mType; + double mAmp; - void GenerateBlock(float *data, const WaveTrack &track, sampleCount block); - void Success(); - - private: - sampleCount numSamples; - int noiseType; - double noiseAmplitude; float y, z, buf0, buf1, buf2, buf3, buf4, buf5, buf6; - protected: - virtual bool MakeNoise(float *buffer, sampleCount len, float fs, float amplitude); - //double mCurRate; - - // friendship ... - friend class NoiseDialog; - -}; - -//---------------------------------------------------------------------------- -// NoiseDialog -//---------------------------------------------------------------------------- - -// Declare window functions - -class NoiseDialog:public EffectDialog { - public: - // constructors and destructors - NoiseDialog(EffectNoise * effect, wxWindow * parent, const wxString & title); - - // method declarations - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - - private: - void OnTimeCtrlUpdate(wxCommandEvent & event); - DECLARE_EVENT_TABLE() - - public: - double nRate; - double nTime; - wxArrayString *nTypeList; - double nDuration; - int nType; - double nAmplitude; - bool nIsSelection; - - private: - EffectNoise *mEffect; NumericTextCtrl *mNoiseDurationT; }; diff --git a/src/effects/NoiseReduction.cpp b/src/effects/NoiseReduction.cpp index 9642332f2..401341b0b 100644 --- a/src/effects/NoiseReduction.cpp +++ b/src/effects/NoiseReduction.cpp @@ -417,6 +417,25 @@ EffectNoiseReduction::~EffectNoiseReduction() { } +// IdentInterface implementation + +wxString EffectNoiseReduction::GetSymbol() +{ + return NOISEREDUCTION_PLUGIN_SYMBOL; +} + +wxString EffectNoiseReduction::GetDescription() +{ + return wxTRANSLATE("Removes background noise such as fans, tape noise, or hums"); +} + +// EffectIdentInterface implementation + +EffectType EffectNoiseReduction::GetType() +{ + return EffectTypeProcess; +} + bool EffectNoiseReduction::Init() { return true; @@ -427,37 +446,12 @@ bool EffectNoiseReduction::CheckWhetherSkipEffect() return false; } -wxString EffectNoiseReduction::GetEffectName() -{ - return wxString(wxTRANSLATE("Noise Reduction...")); -} - -std::set EffectNoiseReduction::GetEffectCategories() -{ - std::set result; - result.insert(wxT("http://audacityteam.org/namespace#NoiseReduction")); - return result; -} - -wxString EffectNoiseReduction::GetEffectIdentifier() -{ - return wxString(wxT("Noise Reduction")); -} - -wxString EffectNoiseReduction::GetEffectAction() -{ - if (mSettings->mDoProfile) - return wxString(_("Creating Noise Profile")); - else - return wxString(_("Reducing Noise")); -} - -bool EffectNoiseReduction::PromptUser() +bool EffectNoiseReduction::PromptUser(wxWindow *parent, bool isBatch) { // We may want to twiddle the levels if we are setting // from an automation dialog, the only case in which we can // get here without any wavetracks. - return mSettings->PromptUser(this, mParent, + return mSettings->PromptUser(this, parent, (mStatistics.get() != 0), (GetNumWaveTracks() == 0)); } @@ -596,13 +590,6 @@ bool EffectNoiseReduction::Settings::Validate() const return true; } -bool EffectNoiseReduction::TransferParameters( Shuttle & WXUNUSED(shuttle) ) -{ - //shuttle.TransferDouble(wxT("Gain"), mNoiseGain, 0.0); - //shuttle.TransferDouble(wxT("Freq"), mFreqSmoothingHz, 0.0); - return true; -} - bool EffectNoiseReduction::Process() { // This same code will either reduce noise or profile it @@ -1540,7 +1527,7 @@ EffectNoiseReduction::Dialog::Dialog (EffectNoiseReduction *effect, EffectNoiseReduction::Settings *settings, wxWindow *parent, bool bHasProfile, bool bAllowTwiddleSettings) - : EffectDialog( parent, _("Noise Reduction"), PROCESS_EFFECT) + : EffectDialog( parent, _("Noise Reduction"), EffectTypeProcess) , m_pEffect(effect) , m_pSettings(settings) // point to , mTempSettings(*settings) // copy diff --git a/src/effects/NoiseReduction.h b/src/effects/NoiseReduction.h index f7ea158f1..ccb7a451a 100644 --- a/src/effects/NoiseReduction.h +++ b/src/effects/NoiseReduction.h @@ -17,6 +17,8 @@ #include +#define NOISEREDUCTION_PLUGIN_SYMBOL wxTRANSLATE("Noise Reduction") + class EffectNoiseReduction: public Effect { public: @@ -25,13 +27,20 @@ public: using Effect::TrackProgress; - virtual wxString GetEffectName(); - virtual std::set GetEffectCategories(); - virtual wxString GetEffectIdentifier(); - virtual wxString GetEffectAction(); + // IdentInterface implementation - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); + virtual wxString GetSymbol(); + virtual wxString GetDescription(); + + // EffectIdentInterface implementation + + virtual EffectType GetType(); + + // Effect implementation + +// using Effect::TrackProgress; + + virtual bool PromptUser(wxWindow *parent = NULL, bool isBatch = false); virtual bool Init(); virtual bool CheckWhetherSkipEffect(); diff --git a/src/effects/NoiseRemoval.cpp b/src/effects/NoiseRemoval.cpp index 852f39f8c..611c4a112 100644 --- a/src/effects/NoiseRemoval.cpp +++ b/src/effects/NoiseRemoval.cpp @@ -39,6 +39,9 @@ *//*******************************************************************/ #include "../Audacity.h" +#include "../Experimental.h" + +#if !defined(EXPERIMENTAL_NOISE_REDUCTION) #include "NoiseRemoval.h" @@ -108,6 +111,32 @@ EffectNoiseRemoval::~EffectNoiseRemoval() delete [] mNoiseThreshold; } +// IdentInterface implementation + +wxString EffectNoiseRemoval::GetSymbol() +{ + return wxTRANSLATE("Noise Removal"); +} + +wxString EffectNoiseRemoval::GetDescription() +{ + return wxTRANSLATE("Removes constant background noise such as fans, tape noise, or hums"); +} + +// EffectIdentInterface implementation + +EffectType EffectNoiseRemoval::GetType() +{ + return EffectTypeProcess; +} + +bool EffectNoiseRemoval::SupportsAutomation() +{ + return false; +} + +// Effect implementation + #define MAX_NOISE_LEVEL 30 bool EffectNoiseRemoval::Init() { @@ -121,8 +150,7 @@ bool EffectNoiseRemoval::Init() bool EffectNoiseRemoval::CheckWhetherSkipEffect() { - bool rc = (mLevel == 0); - return rc; + return (mLevel == 0); } bool EffectNoiseRemoval::PromptUser() @@ -174,14 +202,6 @@ bool EffectNoiseRemoval::PromptUser() return gPrefs->Flush(); } -bool EffectNoiseRemoval::TransferParameters( Shuttle & WXUNUSED(shuttle) ) -{ - //shuttle.TransferDouble(wxT("Gain"), mNoiseGain, 0.0); - //shuttle.TransferDouble(wxT("Freq"), mFreqSmoothingHz, 0.0); - //shuttle.TransferDouble(wxT("Time"), mAttackDecayTime, 0.0); - return true; -} - bool EffectNoiseRemoval::Process() { Initialize(); @@ -866,3 +886,5 @@ void NoiseRemovalDialog::OnTimeSlider(wxCommandEvent & WXUNUSED(event)) mTime = mTimeS->GetValue() / (TIME_MAX*1.0); mTimeT->SetValue(wxString::Format(wxT("%.2f"), mTime)); } + +#endif \ No newline at end of file diff --git a/src/effects/NoiseRemoval.h b/src/effects/NoiseRemoval.h index 4a75195ca..193265477 100644 --- a/src/effects/NoiseRemoval.h +++ b/src/effects/NoiseRemoval.h @@ -12,6 +12,10 @@ #ifndef __AUDACITY_EFFECT_NOISE_REMOVAL__ #define __AUDACITY_EFFECT_NOISE_REMOVAL__ +#include "../Audacity.h" + +#if !defined(EXPERIMENTAL_NOISE_REDUCTION) + #include "Effect.h" #include @@ -26,37 +30,25 @@ class WaveTrack; #include "../RealFFTf.h" -class EffectNoiseRemoval: public Effect { - +class EffectNoiseRemoval : public Effect +{ public: - EffectNoiseRemoval(); virtual ~EffectNoiseRemoval(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Noise Removal...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://audacityteam.org/namespace#NoiseRemoval")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Noise Removal")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - if (mDoProfile) - return wxString(_("Creating Noise Profile")); - else - return wxString(_("Removing Noise")); - } + virtual EffectType GetType(); + virtual bool SupportsAutomation(); + + // Effect implementation virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); - virtual bool Init(); virtual bool CheckWhetherSkipEffect(); virtual bool Process(); @@ -123,7 +115,7 @@ private: float **mRealFFTs; // mHistoryLen x mWindowSize float **mImagFFTs; // mHistoryLen x mWindowSize -friend class NoiseRemovalDialog; + friend class NoiseRemovalDialog; }; // WDR: class declarations @@ -199,3 +191,5 @@ private: }; #endif + +#endif diff --git a/src/effects/Normalize.cpp b/src/effects/Normalize.cpp index ef46ec9e0..ccb2a3ba2 100644 --- a/src/effects/Normalize.cpp +++ b/src/effects/Normalize.cpp @@ -12,11 +12,6 @@ \class EffectNormalize \brief An Effect. -*//****************************************************************//** - -\class NormalizeDialog -\brief Dialog used with EffectNormalize - *//*******************************************************************/ @@ -24,122 +19,128 @@ #include -#include "Normalize.h" -#include "../ShuttleGui.h" -#include "../Internat.h" -#include "../WaveTrack.h" -#include "../Prefs.h" -#include "../Project.h" -#include "../Shuttle.h" - -#include -#include -#include #include -#include -#include -#include -#include +#include -#define NORMALIZE_DB_MIN -145 -#define NORMALIZE_DB_MAX 60 +#include "../Internat.h" +#include "../Prefs.h" +#include "../ShuttleGui.h" +#include "../WaveTrack.h" +#include "../widgets/valnum.h" + +#include "Normalize.h" + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( Level, double, wxTRANSLATE("Level"), 0.0, -145.0, 0.0, 1 ); +Param( RemoveDC, bool, wxTRANSLATE("RemoveDcOffset"), true, false, true, 1 ); +Param( ApplyGain, bool, wxTRANSLATE("ApplyGain"), true, false, true, 1 ); +Param( StereoInd, bool, wxTRANSLATE("StereoIndependent"), false, false, true, 1 ); + +BEGIN_EVENT_TABLE(EffectNormalize, wxEvtHandler) + EVT_CHECKBOX(wxID_ANY, EffectNormalize::OnUpdateUI) + EVT_TEXT(wxID_ANY, EffectNormalize::OnUpdateUI) +END_EVENT_TABLE() EffectNormalize::EffectNormalize() { - Init(); + mLevel = MIN_Level; + mDC = DEF_RemoveDC; + mGain = DEF_ApplyGain; + mStereoInd = DEF_StereoInd; } -static double gFrameSum; //lda odd ... having this as member var crashed on exit - -bool EffectNormalize::Init() +EffectNormalize::~EffectNormalize() { - int boolProxy = gPrefs->Read(wxT("/Effects/Normalize/RemoveDcOffset"), 1); - mDC = (boolProxy == 1); - boolProxy = gPrefs->Read(wxT("/Effects/Normalize/Normalize"), 1); - mGain = (boolProxy == 1); - gPrefs->Read(wxT("/Effects/Normalize/Level"), &mLevel, -1.0); - if(mLevel > 0.0) // this should never happen - mLevel = -mLevel; - boolProxy = gPrefs->Read(wxT("/Effects/Normalize/StereoIndependent"), 0L); - mStereoInd = (boolProxy == 1); +} + +// IdentInterface implementation + +wxString EffectNormalize::GetSymbol() +{ + return NORMALIZE_PLUGIN_SYMBOL; +} + +wxString EffectNormalize::GetDescription() +{ + return wxTRANSLATE("Sets the peak amplitude of a one or more tracks"); +} + +// EffectIdentInterface implementation + +EffectType EffectNormalize::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +bool EffectNormalize::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_Level, mLevel); + parms.Write(KEY_ApplyGain, mGain); + parms.Write(KEY_RemoveDC, mDC); + parms.Write(KEY_StereoInd, mStereoInd); + return true; } -wxString EffectNormalize::GetEffectDescription() // useful only after parameter values have been set +bool EffectNormalize::SetAutomationParameters(EffectAutomationParameters & parms) { - // Note: This is useful only after ratio has been set. - wxString strResult = - /* i18n-hint: First %s is the effect name, 2nd and 3rd are either true or - * false (translated below) if those options were selected */ - wxString::Format(_("Applied effect: %s remove dc offset = %s, normalize amplitude = %s, stereo independent %s"), - this->GetEffectName().c_str(), - /* i18n-hint: true here means that the option was - * selected. Opposite false if not selected */ - mDC ? _("true") : _("false"), - mGain ? _("true") : _("false"), - mStereoInd ? _("true") : _("false")); - if (mGain) - strResult += wxString::Format(_(", maximum amplitude = %.1f dB"), mLevel); + ReadAndVerifyDouble(Level); + ReadAndVerifyBool(ApplyGain); + ReadAndVerifyBool(RemoveDC); + ReadAndVerifyBool(StereoInd); - return strResult; -} + mLevel = Level; + mGain = ApplyGain; + mDC = RemoveDC; + mStereoInd = StereoInd; -bool EffectNormalize::TransferParameters( Shuttle & shuttle ) -{ - shuttle.TransferBool( wxT("ApplyGain"), mGain, true ); - shuttle.TransferBool( wxT("RemoveDcOffset"), mDC, true ); - shuttle.TransferDouble( wxT("Level"), mLevel, 0.0); - shuttle.TransferBool( wxT("StereoIndependent"), mStereoInd, false ); return true; } +// Effect implementation + bool EffectNormalize::CheckWhetherSkipEffect() { - bool rc = ((mGain == false) && (mDC == false)); - return rc; + return ((mGain == false) && (mDC == false)); } -void EffectNormalize::End() +bool EffectNormalize::Startup() { - bool bValidate; - gPrefs->Read(wxT("/Validate/Enabled"), &bValidate, false ); // this never get written! Why is this here? MJS - if( bValidate ) + wxString base = wxT("/Effects/Normalize/"); + + // Migrate settings from 2.1.0 or before + + // Already migrated, so bail + if (gPrefs->Exists(base + wxT("Migrated"))) { - int checkOffset = abs((int)(mOffset * 1000.0)); - gPrefs->Write(wxT("/Validate/Norm_Offset"), checkOffset); - int checkMultiplier = abs((int)(mMult * 1000.0)); - gPrefs->Write(wxT("/Validate/Norm_Multiplier"), checkMultiplier); - int checkFrameSum = (int)gFrameSum; - gPrefs->Write(wxT("/Validate/Norm_FrameSum"), checkFrameSum); + return true; + } + + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + int boolProxy = gPrefs->Read(base + wxT("RemoveDcOffset"), 1); + mDC = (boolProxy == 1); + boolProxy = gPrefs->Read(base + wxT("Normalize"), 1); + mGain = (boolProxy == 1); + gPrefs->Read(base + wxT("Level"), &mLevel, -1.0); + if(mLevel > 0.0) // this should never happen + mLevel = -mLevel; + boolProxy = gPrefs->Read(base + wxT("StereoIndependent"), 0L); + mStereoInd = (boolProxy == 1); + + SaveUserPreset(GetCurrentSettingsGroup()); + + // Do not migrate again + gPrefs->Write(base + wxT("Migrated"), true); gPrefs->Flush(); } -} -bool EffectNormalize::PromptUser() -{ - NormalizeDialog dlog(this, mParent); - dlog.mGain = mGain; - dlog.mDC = mDC; - dlog.mLevel = mLevel; - dlog.mStereoInd = mStereoInd; - dlog.TransferDataToWindow(); - - dlog.CentreOnParent(); - dlog.ShowModal(); - - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; - - mGain = dlog.mGain; - mDC = dlog.mDC; - mLevel = dlog.mLevel; - mStereoInd = dlog.mStereoInd; - gPrefs->Write(wxT("/Effects/Normalize/RemoveDcOffset"), mDC); - gPrefs->Write(wxT("/Effects/Normalize/Normalize"), mGain); - gPrefs->Write(wxT("/Effects/Normalize/Level"), mLevel); - gPrefs->Write(wxT("/Effects/Normalize/StereoIndependent"), mStereoInd); - - return gPrefs->Flush(); + return true; } bool EffectNormalize::Process() @@ -150,8 +151,8 @@ bool EffectNormalize::Process() float ratio; if( mGain ) ratio = pow(10.0,TrapDouble(mLevel, // same value used for all tracks - NORMALIZE_DB_MIN, - NORMALIZE_DB_MAX)/20.0); + MIN_Level, + MAX_Level)/20.0); else ratio = 1.0; @@ -262,6 +263,76 @@ bool EffectNormalize::Process() return bGoodResult; } +void EffectNormalize::PopulateOrExchange(ShuttleGui & S) +{ + mCreating = true; + + S.StartVerticalLay(0); + { + S.StartMultiColumn(2, wxALIGN_CENTER); + { + S.StartVerticalLay(false); + { + mDCCheckBox = S.AddCheckBox(_("Remove DC offset (center on 0.0 vertically)"), + mDC ? wxT("true") : wxT("false")); + mDCCheckBox->SetValidator(wxGenericValidator(&mDC)); + + S.StartHorizontalLay(wxALIGN_CENTER, false); + { + mGainCheckBox = S.AddCheckBox(_("Normalize maximum amplitude to"), + mGain ? wxT("true") : wxT("false")); + mGainCheckBox->SetValidator(wxGenericValidator(&mGain)); + + FloatingPointValidator vldLevel(1, &mLevel); + vldLevel.SetRange(MIN_Level, MAX_Level); + mLevelTextCtrl = S.AddTextBox(wxT(""), wxT(""), 10); + mLevelTextCtrl->SetName(_("Maximum amplitude dB")); + mLevelTextCtrl->SetValidator(vldLevel); + mLeveldB = S.AddVariableText(_("dB"), false, + wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + mWarning = S.AddVariableText( wxT(""), false, + wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + } + S.EndHorizontalLay(); + + + mStereoIndCheckBox = S.AddCheckBox(_("Normalize stereo channels independently"), + mStereoInd ? wxT("true") : wxT("false")); + mStereoIndCheckBox->SetValidator(wxGenericValidator(&mStereoInd)); + } + S.EndVerticalLay(); + } + S.EndMultiColumn(); + } + S.EndVerticalLay(); + + mCreating = false; +} + +bool EffectNormalize::TransferDataToWindow() +{ + if (!mUIParent->TransferDataToWindow()) + { + return false; + } + + UpdateUI(); + + return true; +} + +bool EffectNormalize::TransferDataFromWindow() +{ + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } + + return true; +} + +// EffectNormalize implementation + void EffectNormalize::AnalyseTrack(WaveTrack * track, wxString msg) { if(mGain) { @@ -427,160 +498,29 @@ void EffectNormalize::ProcessData(float *buffer, sampleCount len) for(i=0; iTransferDataFromWindow()) { - S.StartVerticalLay(false); - { - mDCCheckBox = - S.Id(ID_DC_REMOVE). - AddCheckBox(_("Remove DC offset (center on 0.0 vertically)"), - mDC ? wxT("true") : wxT("false")); - - S.StartHorizontalLay(wxALIGN_CENTER, false); - { - mGainCheckBox = - S.Id(ID_NORMALIZE_AMPLITUDE). - AddCheckBox(_("Normalize maximum amplitude to"), - mGain ? wxT("true") : wxT("false")); - - mLevelTextCtrl = S.Id(ID_LEVEL_TEXT).AddTextBox(wxT(""), wxT(""), 10); - mLevelTextCtrl->SetValidator(vld); - mLevelTextCtrl->SetName(_("Maximum amplitude dB")); - mLeveldB = S.AddVariableText(_("dB"), false, - wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); - mWarning = S.AddVariableText( wxT(""), false, - wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); - } - S.EndHorizontalLay(); - mStereoIndCheckBox = S.AddCheckBox(_("Normalize stereo channels independently"), - mStereoInd ? wxT("true") : wxT("false")); - } - S.EndVerticalLay(); + mWarning->SetLabel(_(". Maximum 0dB.")); + EnableApply(false); + return; } - S.EndTwoColumn(); -} + mWarning->SetLabel(wxT("")); -bool NormalizeDialog::TransferDataToWindow() -{ - mGainCheckBox->SetValue(mGain); - mDCCheckBox->SetValue(mDC); - mLevelTextCtrl->SetValue(Internat::ToDisplayString(mLevel, 1)); - mStereoIndCheckBox->SetValue(mStereoInd); - - UpdateUI(); - - TransferDataFromWindow(); - - return true; -} - -bool NormalizeDialog::TransferDataFromWindow() -{ - mGain = mGainCheckBox->GetValue(); - mDC = mDCCheckBox->GetValue(); - mLevel = Internat::CompatibleToDouble(mLevelTextCtrl->GetValue()); - mStereoInd = mStereoIndCheckBox->GetValue(); - - return true; -} - -void NormalizeDialog::OnUpdateUI(wxCommandEvent& WXUNUSED(event)) -{ - UpdateUI(); -} - -void NormalizeDialog::UpdateUI() -{ // Disallow level stuff if not normalizing - bool enable = mGainCheckBox->GetValue(); - mLevelTextCtrl->Enable(enable); - mLeveldB->Enable(enable); - mStereoIndCheckBox->Enable(enable); + mLevelTextCtrl->Enable(mGain); + mLeveldB->Enable(mGain); + mStereoIndCheckBox->Enable(mGain); // Disallow OK/Preview if doing nothing - wxButton *ok = (wxButton *) FindWindow(wxID_OK); - wxButton *preview = (wxButton *) FindWindow(ID_EFFECT_PREVIEW); - bool dc = mDCCheckBox->GetValue(); - if( !enable && !dc ) - { - ok->Enable(false); - preview->Enable(false); - } - else - { - ok->Enable(true); - preview->Enable(true); - } - - // Disallow OK/Preview if requested level is > 0 - wxString val = mLevelTextCtrl->GetValue(); - double r; - val.ToDouble(&r); - if(r > 0.0) - { - ok->Enable(false); - preview->Enable(false); - mWarning->SetLabel(_(". Maximum 0dB.")); - } - else - mWarning->SetLabel(wxT("")); -} - -void NormalizeDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - - // Save & restore parameters around Preview, because we didn't do OK. - bool oldGain = mEffect->mGain; - bool oldDC = mEffect->mDC; - double oldLevel = mEffect->mLevel; - bool oldStereoInd = mEffect->mStereoInd; - - mEffect->mGain = mGain; - mEffect->mDC = mDC; - mEffect->mLevel = mLevel; - mEffect->mStereoInd = mStereoInd; - - mEffect->Preview(); - - mEffect->mGain = oldGain; - mEffect->mDC = oldDC; - mEffect->mLevel = oldLevel; - mEffect->mStereoInd = oldStereoInd; + EnableApply(mGain || mDC); } diff --git a/src/effects/Normalize.h b/src/effects/Normalize.h index 540586409..aa2700340 100644 --- a/src/effects/Normalize.h +++ b/src/effects/Normalize.h @@ -12,63 +12,64 @@ #ifndef __AUDACITY_EFFECT_NORMALIZE__ #define __AUDACITY_EFFECT_NORMALIZE__ -#include "Effect.h" - #include +#include #include +#include #include -class wxString; +#include "../ShuttleGui.h" +#include "../WaveTrack.h" -class WaveTrack; +#include "Effect.h" -class EffectNormalize: public Effect +#define NORMALIZE_PLUGIN_SYMBOL wxTRANSLATE("Normalize") + +class EffectNormalize : public Effect { - friend class NormalizeDialog; - - public: +public: EffectNormalize(); + virtual ~EffectNormalize(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Normalize...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#UtilityPlugin")); - result.insert(wxT("http://lv2plug.in/ns/lv2core#AmplifierPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - // This is just used internally, users should not see it. Do not translate. - virtual wxString GetEffectIdentifier() { - return wxT("Normalize"); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Normalizing...")); - } + virtual EffectType GetType(); - virtual wxString GetEffectDescription(); // useful only after parameter values have been set + // EffectClientInterface implementation - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + + // Effect implementation - virtual bool Init(); - virtual void End(); virtual bool CheckWhetherSkipEffect(); + virtual bool Startup(); virtual bool Process(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); + +private: + // EffectNormalize implementation - private: bool ProcessOne(WaveTrack * t, wxString msg); virtual void AnalyseTrack(WaveTrack * track, wxString msg); virtual void AnalyzeData(float *buffer, sampleCount len); bool AnalyseDC(WaveTrack * track, wxString msg); virtual void ProcessData(float *buffer, sampleCount len); + void OnUpdateUI(wxCommandEvent & evt); + void UpdateUI(); + +private: + double mLevel; bool mGain; bool mDC; - double mLevel; bool mStereoInd; int mCurTrackNum; @@ -80,32 +81,7 @@ class EffectNormalize: public Effect float mMax; double mSum; sampleCount mCount; -}; -//---------------------------------------------------------------------------- -// NormalizeDialog -//---------------------------------------------------------------------------- - -class NormalizeDialog: public EffectDialog -{ - public: - // constructors and destructors - NormalizeDialog(EffectNormalize *effect, wxWindow * parent); - - // method declarations - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - - private: - // handlers - void OnUpdateUI(wxCommandEvent& evt); - void OnPreview(wxCommandEvent &event); - - void UpdateUI(); - - private: - EffectNormalize *mEffect; wxCheckBox *mGainCheckBox; wxCheckBox *mDCCheckBox; wxTextCtrl *mLevelTextCtrl; @@ -113,13 +89,9 @@ class NormalizeDialog: public EffectDialog wxStaticText *mWarning; wxCheckBox *mStereoIndCheckBox; - DECLARE_EVENT_TABLE() + bool mCreating; - public: - bool mGain; - bool mDC; - double mLevel; - bool mStereoInd; + DECLARE_EVENT_TABLE(); }; #endif diff --git a/src/effects/Paulstretch.cpp b/src/effects/Paulstretch.cpp index 29ff1c4aa..3cf16f79d 100644 --- a/src/effects/Paulstretch.cpp +++ b/src/effects/Paulstretch.cpp @@ -1,87 +1,135 @@ /********************************************************************** -Audacity: A Digital Audio Editor + Audacity: A Digital Audio Editor -Paulstretch.cpp + Paulstretch.cpp -Nasca Octavian Paul (Paul Nasca) -Some GUI code was taken from the Echo effect + Nasca Octavian Paul (Paul Nasca) + Some GUI code was taken from the Echo effect - *******************************************************************/ +*******************************************************************//** -/** +\class EffectPaulstretch +\brief An Extreme Time Stretch and Time Smear effect - \class EffectPaulstretch - \brief An Extreme Time Stretch and Time Smear effect - -*//****************************************************************/ - -/** - -\class PaulstretchDialog -\brief PaulstretchDialog used with EffectPaulstretch - -*/ - -/*******************************************************************/ +*//*******************************************************************/ #include "../Audacity.h" -#include -#include -#include -#include -#include -#include -#include - -#include -#include #include -#include +#include + +#include +#include + +#include "../FFT.h" +#include "../widgets/valnum.h" #include "Paulstretch.h" -#include "../WaveTrack.h" -#include "../FFT.h" +// Define keys, defaults, minimums, and maximums for the effect parameters -EffectPaulstretch::EffectPaulstretch(){ - amount=10.0; - time_resolution=0.25; +// +// Name Type Key Def Min Max Scale +Param( Amount, float, wxTRANSLATE("Stretch Factor"), 10.0, 1.0, FLT_MAX, 1 ); +Param( Time, float, wxTRANSLATE("Time Resolution"), 0.25f, 0.001f, FLT_MAX, 1 ); + +class PaulStretch +{ +public: + PaulStretch(float rap_,int in_bufsize_,float samplerate_); + //in_bufsize is also a half of a FFT buffer (in samples) + virtual ~PaulStretch(); + + void process(float *smps,int nsmps); + + int in_bufsize; + int poolsize;//how many samples are inside the input_pool size (need to know how many samples to fill when seeking) + + int out_bufsize; + float *out_buf; + + int get_nsamples();//how many samples are required to be added in the pool next time + int get_nsamples_for_fill();//how many samples are required to be added for a complete buffer refill (at start of the song or after seek) + + void set_rap(float newrap);//set the current stretch value + +protected: + virtual void process_spectrum(float *WXUNUSED(freq)){}; + float samplerate; + +private: + float *in_pool;//de marimea in_bufsize + float rap; + float *old_out_smp_buf; + + float *fft_smps,*fft_c,*fft_s,*fft_freq,*fft_tmp; + + double remained_samples;//how many fraction of samples has remained (0..1) }; -wxString EffectPaulstretch::GetEffectDescription(){ - // Note: This is useful only after values have been set. - return wxString::Format(_("Applied effect: %s stretch factor = %f times, time resolution = %f seconds"), - this->GetEffectName().c_str(), amount,time_resolution); +// +// EffectPaulstretch +// -}; +BEGIN_EVENT_TABLE(EffectPaulstretch, wxEvtHandler) + EVT_TEXT(wxID_ANY, EffectPaulstretch::OnText) +END_EVENT_TABLE() -bool EffectPaulstretch::PromptUser(){ - PaulstretchDialog dlog(this, mParent); - dlog.amount = amount; - dlog.time_resolution = time_resolution; - dlog.CentreOnParent(); - dlog.ShowModal(); +EffectPaulstretch::EffectPaulstretch() +{ + amount = DEF_Amount; + time_resolution = DEF_Time; +} - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; +EffectPaulstretch::~EffectPaulstretch() +{ +} - amount = dlog.amount; - time_resolution = dlog.time_resolution; +// IdentInterface implementation + +wxString EffectPaulstretch::GetSymbol() +{ + return PAULSTRETCH_PLUGIN_SYMBOL; +} + +wxString EffectPaulstretch::GetDescription() +{ + return wxTRANSLATE("Use Paulstretch only for an extreme time-stretch or \"stasis\" effect"); +} + +// EffectIdentInterface implementation + +EffectType EffectPaulstretch::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +bool EffectPaulstretch::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.WriteFloat(KEY_Amount, amount); + parms.WriteFloat(KEY_Time, time_resolution); return true; -}; +} -bool EffectPaulstretch::TransferParameters(Shuttle &shuttle){ - shuttle.TransferFloat(wxT("Stretch Factor"),amount,10.0); - shuttle.TransferFloat(wxT("Time Resolution"),time_resolution,0.25); +bool EffectPaulstretch::SetAutomationParameters(EffectAutomationParameters & parms) +{ + ReadAndVerifyFloat(Amount); + ReadAndVerifyFloat(Time); + + amount = Amount; + time_resolution = Time; return true; -}; +} +// Effect implementation -bool EffectPaulstretch::Process(){ +bool EffectPaulstretch::Process() +{ CopyInputTracks(); SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks); WaveTrack *track = (WaveTrack *) iter.First(); @@ -108,44 +156,57 @@ bool EffectPaulstretch::Process(){ return true; } -class PaulStretch{ - public: - PaulStretch(float rap_,int in_bufsize_,float samplerate_); - //in_bufsize is also a half of a FFT buffer (in samples) - virtual ~PaulStretch(); +void EffectPaulstretch::PopulateOrExchange(ShuttleGui & S) +{ + S.StartMultiColumn(2, wxALIGN_CENTER); + { + FloatingPointValidator vldAmount(1, &amount); + vldAmount.SetMin(MIN_Amount); - void process(float *smps,int nsmps); + /* i18n-hint: This is how many times longer the sound will be, e.g. applying + * the effect to a 1-second sample, with the default Stretch Factor of 10.0 + * will give an (approximately) 10 second sound + */ + S.AddTextBox(_("Stretch Factor:"), wxT(""), 10)->SetValidator(vldAmount); - int in_bufsize; - int poolsize;//how many samples are inside the input_pool size (need to know how many samples to fill when seeking) - - int out_bufsize; - float *out_buf; - - int get_nsamples();//how many samples are required to be added in the pool next time - int get_nsamples_for_fill();//how many samples are required to be added for a complete buffer refill (at start of the song or after seek) - - void set_rap(float newrap);//set the current stretch value - - - protected: - virtual void process_spectrum(float *WXUNUSED(freq)){}; - float samplerate; - private: - float *in_pool;//de marimea in_bufsize - float rap; - float *old_out_smp_buf; - - float *fft_smps,*fft_c,*fft_s,*fft_freq,*fft_tmp; - - double remained_samples;//how many fraction of samples has remained (0..1) + FloatingPointValidator vldTime(1, &time_resolution); + vldTime.SetMin(MIN_Time); + S.AddTextBox(_("Time Resolution (seconds):"), wxT(""), 10)->SetValidator(vldTime); + } + S.EndMultiColumn(); }; +bool EffectPaulstretch::TransferDataToWindow() +{ + if (!mUIParent->TransferDataToWindow()) + { + return false; + } -bool EffectPaulstretch::ProcessOne(WaveTrack *track,double t0,double t1,int count){ + return true; +} +bool EffectPaulstretch::TransferDataFromWindow() +{ + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } + + return true; +} + +// EffectPaulstretch implementation + +void EffectPaulstretch::OnText(wxCommandEvent & WXUNUSED(evt)) +{ + EnableApply(mUIParent->TransferDataFromWindow()); +} + +bool EffectPaulstretch::ProcessOne(WaveTrack *track,double t0,double t1,int count) +{ int stretch_buf_size;//must be power of 2 (because Audacity's fft requires it) - if (time_resolution<0.001) time_resolution=0.001f; + if (time_resolutionGetRate()*time_resolution*0.5; tmp=log(tmp)/log(2.0); @@ -154,7 +215,7 @@ bool EffectPaulstretch::ProcessOne(WaveTrack *track,double t0,double t1,int coun }; if (stretch_buf_size<128) stretch_buf_size=128; double amount=this->amount; - if (amount<1.0) amount=1.0; + if (amountTimeToLongSamples(t0); sampleCount end = track->TimeToLongSamples(t1); @@ -249,14 +310,11 @@ bool EffectPaulstretch::ProcessOne(WaveTrack *track,double t0,double t1,int coun return !cancelled; }; - - /*************************************************************/ - - -PaulStretch::PaulStretch(float rap_,int in_bufsize_,float samplerate_){ +PaulStretch::PaulStretch(float rap_,int in_bufsize_,float samplerate_) +{ samplerate=samplerate_; rap=rap_; in_bufsize=in_bufsize_; @@ -286,7 +344,8 @@ PaulStretch::PaulStretch(float rap_,int in_bufsize_,float samplerate_){ }; -PaulStretch::~PaulStretch(){ +PaulStretch::~PaulStretch() +{ delete [] out_buf; delete [] old_out_smp_buf; delete [] in_pool; @@ -297,12 +356,14 @@ PaulStretch::~PaulStretch(){ delete [] fft_tmp; }; -void PaulStretch::set_rap(float newrap){ +void PaulStretch::set_rap(float newrap) +{ if (rap>=1.0) rap=newrap; else rap=1.0; }; -void PaulStretch::process(float *smps,int nsmps){ +void PaulStretch::process(float *smps,int nsmps) +{ //add new samples to the pool if ((smps!=NULL)&&(nsmps!=0)){ if (nsmps>poolsize){ @@ -373,8 +434,8 @@ void PaulStretch::process(float *smps,int nsmps){ }; - -int PaulStretch::get_nsamples(){ +int PaulStretch::get_nsamples() +{ double r=out_bufsize/rap; int ri=(int)floor(r); double rf=r-floor(r); @@ -392,93 +453,7 @@ int PaulStretch::get_nsamples(){ return ri; }; -int PaulStretch::get_nsamples_for_fill(){ +int PaulStretch::get_nsamples_for_fill() +{ return poolsize; }; - - BEGIN_EVENT_TABLE(PaulstretchDialog, EffectDialog) - EVT_BUTTON(ID_EFFECT_PREVIEW, PaulstretchDialog::OnPreview) -END_EVENT_TABLE() - - PaulstretchDialog::PaulstretchDialog(EffectPaulstretch *effect, wxWindow *parent):EffectDialog(parent,wxT("Paulstretch")){ - m_bLoopDetect=false; - m_pEffect=effect; - - m_pTextCtrl_Amount=NULL; - m_pTextCtrl_TimeResolution=NULL; - - amount=10.0; - time_resolution=0.25; - - Init(); - }; - - -void PaulstretchDialog::PopulateOrExchange(ShuttleGui & S) -{ - S.StartMultiColumn(2, wxALIGN_CENTER); - { - /* i18n-hint: This is how many times longer the sound will be, e.g. applying - * the effect to a 1-second sample, with the default Stretch Factor of 10.0 - * will give an (approximately) 10 second sound - */ - m_pTextCtrl_Amount = S.AddTextBox(_("Stretch Factor:"),wxT("10.0"),10); - m_pTextCtrl_Amount->SetValidator(wxTextValidator(wxFILTER_NUMERIC)); - - m_pTextCtrl_TimeResolution= S.AddTextBox(_("Time Resolution (seconds):"), wxT("0.25"),10); - m_pTextCtrl_TimeResolution->SetValidator(wxTextValidator(wxFILTER_NUMERIC)); - } - S.EndMultiColumn(); - -}; - -bool PaulstretchDialog::TransferDataToWindow(){ - m_bLoopDetect = true; - - wxString str; - if (m_pTextCtrl_Amount) { - str.Printf(wxT("%g"), amount); - m_pTextCtrl_Amount->SetValue(str); - } - if (m_pTextCtrl_TimeResolution) { - str.Printf(wxT("%g"), time_resolution); - m_pTextCtrl_TimeResolution->SetValue(str); - } - - m_bLoopDetect = false; - return true; -} -bool PaulstretchDialog::TransferDataFromWindow(){ - double newValue; - wxString str; - if (m_pTextCtrl_Amount) { - str = m_pTextCtrl_Amount->GetValue(); - str.ToDouble(&newValue); - amount = (float)(newValue); - } - if (m_pTextCtrl_TimeResolution) { - str = m_pTextCtrl_TimeResolution->GetValue(); - str.ToDouble(&newValue); - time_resolution = (float)(newValue); - } - return true; -} - -void PaulstretchDialog::OnPreview(wxCommandEvent & WXUNUSED(event)){ - TransferDataFromWindow(); - - // Save & restore parameters around Preview, because we didn't do OK. - float oldAmount = m_pEffect->amount; - float oldTimeResolution = m_pEffect->time_resolution; - - m_pEffect->amount = amount; - m_pEffect->time_resolution = time_resolution; - - m_pEffect->Preview(); - - m_pEffect->amount = oldAmount; - m_pEffect->time_resolution = oldTimeResolution; -} - - - diff --git a/src/effects/Paulstretch.h b/src/effects/Paulstretch.h index 2e7942895..91367ade1 100644 --- a/src/effects/Paulstretch.h +++ b/src/effects/Paulstretch.h @@ -10,88 +10,56 @@ #ifndef __AUDACITY_EFFECT_PAULSTRETCH__ #define __AUDACITY_EFFECT_PAULSTRETCH__ -#include "SimpleMono.h" -#include -#include +#include +#include "../ShuttleGui.h" +#include "../WaveTrack.h" -class WaveTrack; +#include "Effect.h" -class EffectPaulstretch:public Effect{ +#define PAULSTRETCH_PLUGIN_SYMBOL wxTRANSLATE("Paulstretch") - public: - EffectPaulstretch(); +class EffectPaulstretch : public Effect +{ +public: + EffectPaulstretch(); + virtual ~EffectPaulstretch(); - virtual wxString GetEffectName() { - /* i18n-hint: This is the name of the effect, i.e. a proper noun, which - * wouldn't normally get translated. It's the combination of the author's - * name (Paul) with what it does (stretch sound) - */ - return wxString(wxTRANSLATE("Paulstretch...")); - } + // IdentInterface implementation - virtual wxString GetEffectAction() { - /* i18n-hint: This is the text that is shown whilst the effect is being - * processed. The effect stretches the input in time, making the sound - * much longer and spread out whilst at the same pitch. Paulstretch is the - * name of the effect (it's also translated on it's own). - */ - return wxString(_("Stretching with Paulstretch")); - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() {return wxT("Paulstretch");} + // EffectIdentInterface implementation - // Useful only after PromptUser values have been set. - virtual wxString GetEffectDescription(); + virtual EffectType GetType(); - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); - virtual bool Process(); + // EffectClientInterface implementation - protected: + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); - bool ProcessOne(WaveTrack *track,double t0,double t1,int count); - float amount; - float time_resolution;//seconds + // Effect implementation - friend class PaulstretchDialog; - private: - double m_t1; + virtual bool Process(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); + +private: + // EffectPaulstretch implementation + + void OnText(wxCommandEvent & evt); + + bool ProcessOne(WaveTrack *track, double t0, double t1, int count); + +private: + float amount; + float time_resolution; //seconds + double m_t1; + + DECLARE_EVENT_TABLE(); }; - -//-------------------------------------------------------------------------- -// PaulstretchDialog -//-------------------------------------------------------------------------- - -class PaulstretchDialog:public EffectDialog { - public: - PaulstretchDialog(EffectPaulstretch * effect, wxWindow * parent); - - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - - private: - // handlers - void OnPreview( wxCommandEvent &event ); - - private: - bool m_bLoopDetect; - EffectPaulstretch * m_pEffect; - - // controls - wxTextCtrl * m_pTextCtrl_Amount; - wxTextCtrl * m_pTextCtrl_TimeResolution; - - public: - // effect parameters - float amount; - float time_resolution;//seconds - private: - DECLARE_EVENT_TABLE() -}; - - #endif diff --git a/src/effects/Phaser.cpp b/src/effects/Phaser.cpp index c3261a679..6c52274a3 100644 --- a/src/effects/Phaser.cpp +++ b/src/effects/Phaser.cpp @@ -14,560 +14,427 @@ *******************************************************************//** \class EffectPhaser -\brief An EffectSimpleMono - -*//****************************************************************//** - -\class PhaserDialog -\brief Dialog for EffectPhaser +\brief An Effect *//*******************************************************************/ #include "../Audacity.h" +#include + +#include + +#include "../widgets/valnum.h" + #include "Phaser.h" -#include "../ShuttleGui.h" -#include "../WaveTrack.h" -#include "../FFT.h" +enum +{ + ID_Stages = 10000, + ID_DryWet, + ID_Freq, + ID_Phase, + ID_Depth, + ID_Feedback +}; -#include -#include -#include -#include -#include -#include +// Name Type Key Def Min Max Scale +Param( Stages, int, wxTRANSLATE("Stages"), 2, 2, NUM_STAGES, 1 ); +Param( DryWet, int, wxTRANSLATE("DryWet"), 128, 0, 255, 1 ); +Param( Freq, double, wxTRANSLATE("Freq"), 0.4, 0.1, 4.0, 10 ); +Param( Phase, double, wxTRANSLATE("Phase"), 0.0, 0.0, 359.0, 1 ); +Param( Depth, int, wxTRANSLATE("Depth"), 100, 0, 255, 1 ); +Param( Feedback, int, wxTRANSLATE("Feedback"), 0, -100, 100, 1 ); -#include +// +#define phaserlfoshape 4.0 + +// How many samples are processed before recomputing the lfo value again +#define lfoskipsamples 20 // // EffectPhaser // - -#define phaserlfoshape 4.0 - -// How many samples are processed before compute the lfo value again -#define lfoskipsamples 20 +BEGIN_EVENT_TABLE(EffectPhaser, wxEvtHandler) + EVT_SLIDER(ID_Stages, EffectPhaser::OnStagesSlider) + EVT_SLIDER(ID_DryWet, EffectPhaser::OnDryWetSlider) + EVT_SLIDER(ID_Freq, EffectPhaser::OnFreqSlider) + EVT_SLIDER(ID_Phase, EffectPhaser::OnPhaseSlider) + EVT_SLIDER(ID_Depth, EffectPhaser::OnDepthSlider) + EVT_SLIDER(ID_Feedback, EffectPhaser::OnFeedbackSlider) + EVT_TEXT(ID_Stages, EffectPhaser::OnStagesText) + EVT_TEXT(ID_DryWet, EffectPhaser::OnDryWetText) + EVT_TEXT(ID_Freq, EffectPhaser::OnFreqText) + EVT_TEXT(ID_Phase, EffectPhaser::OnPhaseText) + EVT_TEXT(ID_Depth, EffectPhaser::OnDepthText) + EVT_TEXT(ID_Feedback, EffectPhaser::OnFeedbackText) +END_EVENT_TABLE() EffectPhaser::EffectPhaser() { - freq = (float)0.4; - depth = 100; - startphase = float(0.0); - stages = 2; - drywet = 128; - fb = float(0.0); + mStages = DEF_Stages; + mDryWet = DEF_DryWet; + mFreq = DEF_Freq; + mPhase = DEF_Phase; + mDepth = DEF_Depth; + mFeedback = DEF_Feedback; } -wxString EffectPhaser::GetEffectDescription() { - // Note: This is useful only after values have been set. - return wxString::Format(_("Applied effect: %s %d stages, %.0f%% wet, frequency = %.1f Hz, start phase = %.0f deg, depth = %d, feedback = %.0f%%"), - this->GetEffectName().c_str(), - stages, - float(drywet*100/255), - freq, - (startphase * 180 / M_PI), - depth, - fb); -} - -bool EffectPhaser::PromptUser() +EffectPhaser::~EffectPhaser() { - PhaserDialog dlog(this, mParent); - - dlog.freq = freq; - dlog.startphase = startphase * 180 / M_PI; - dlog.fb = fb; - dlog.depth = depth; - dlog.stages = stages; - dlog.drywet = drywet; - - dlog.TransferDataToWindow(); - dlog.CentreOnParent(); - dlog.ShowModal(); - - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; - - freq = dlog.freq; - startphase = dlog.startphase * M_PI / 180; - fb = dlog.fb; - depth = dlog.depth; - stages = dlog.stages; - drywet = dlog.drywet; - - return true; } -bool EffectPhaser::TransferParameters( Shuttle & shuttle ) +// IdentInterface implementation + +wxString EffectPhaser::GetSymbol() { - shuttle.TransferInt(wxT("Stages"),stages,2); - shuttle.TransferInt(wxT("Wet"),drywet,128); - shuttle.TransferFloat(wxT("Freq"),freq,0.4f); - shuttle.TransferInt(wxT("Depth"),depth,100); - shuttle.TransferFloat(wxT("Feedback"),fb,0.0f); - return true; + return PHASER_PLUGIN_SYMBOL; } -bool EffectPhaser::NewTrackSimpleMono() +wxString EffectPhaser::GetDescription() { - for (int j = 0; j < stages; j++) + return wxTRANSLATE("Combines phase-shifted signals with the original signal"); +} + +// EffectIdentInterface implementation + +EffectType EffectPhaser::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +int EffectPhaser::GetAudioInCount() +{ + return 1; +} + +int EffectPhaser::GetAudioOutCount() +{ + return 1; +} + +bool EffectPhaser::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames chanMap) +{ + for (int j = 0; j < mStages; j++) + { old[j] = 0; + } skipcount = 0; gain = 0; fbout = 0; - lfoskip = freq * 2 * M_PI / mCurRate; + lfoskip = mFreq * 2 * M_PI / mSampleRate; - phase = startphase; - if (mCurChannel == Track::RightChannel) - phase += (float)M_PI; - - return true; -} - -bool EffectPhaser::ProcessSimpleMono(float *buffer, sampleCount len) -{ - float m, tmp, in, out; - int i, j; - - for (i = 0; i < len; i++) { - in = buffer[i]; - - m = in + fbout * fb / 100; - if (((skipcount++) % lfoskipsamples) == 0) { - //compute sine between 0 and 1 - gain = (1 + cos(skipcount * lfoskip + phase)) / 2; - - // change lfo shape - gain = - (exp(gain * phaserlfoshape) - 1) / (exp(phaserlfoshape)-1); - - gain = 1 - gain / 255 * depth; // attenuate the lfo - } - // phasing routine - for (j = 0; j < stages; j++) { - tmp = old[j]; - old[j] = gain * tmp + m; - m = tmp - gain * old[j]; - } - fbout = m; - out = (m * drywet + in * (255 - drywet)) / 255; - - buffer[i] = out; + phase = mPhase * M_PI / 180; + if (chanMap[0] == ChannelNameFrontRight) + { + phase += M_PI; } return true; } -// WDR: class implementations - -//---------------------------------------------------------------------------- -// PhaserDialog -//---------------------------------------------------------------------------- - -#define FREQ_MIN 1 -#define FREQ_MAX 40 -#define PHASE_MIN 0 -#define PHASE_MAX 359 -#define DEPTH_MIN 0 -#define DEPTH_MAX 255 -#define STAGES_MIN 2 -#define STAGES_MAX 24 -#define DRYWET_MIN 0 -#define DRYWET_MAX 255 -#define FB_MIN -100 -#define FB_MAX 100 - -// WDR: event table for PhaserDialog - -BEGIN_EVENT_TABLE(PhaserDialog, EffectDialog) - EVT_TEXT(ID_PHASER_STAGESTEXT, PhaserDialog::OnStagesText) - EVT_TEXT(ID_PHASER_DRYWETTEXT, PhaserDialog::OnDryWetText) - EVT_TEXT(ID_PHASER_FREQTEXT, PhaserDialog::OnFreqText) - EVT_TEXT(ID_PHASER_PHASETEXT, PhaserDialog::OnPhaseText) - EVT_TEXT(ID_PHASER_DEPTHTEXT, PhaserDialog::OnDepthText) - EVT_TEXT(ID_PHASER_FEEDBACKTEXT, PhaserDialog::OnFeedbackText) - EVT_SLIDER(ID_PHASER_STAGESSLIDER, PhaserDialog::OnStagesSlider) - EVT_SLIDER(ID_PHASER_DRYWETSLIDER, PhaserDialog::OnDryWetSlider) - EVT_SLIDER(ID_PHASER_FREQSLIDER, PhaserDialog::OnFreqSlider) - EVT_SLIDER(ID_PHASER_PHASESLIDER, PhaserDialog::OnPhaseSlider) - EVT_SLIDER(ID_PHASER_DEPTHSLIDER, PhaserDialog::OnDepthSlider) - EVT_SLIDER(ID_PHASER_FEEDBACKSLIDER, PhaserDialog::OnFeedbackSlider) - EVT_BUTTON(ID_EFFECT_PREVIEW, PhaserDialog::OnPreview) -END_EVENT_TABLE() - -PhaserDialog::PhaserDialog(EffectPhaser * effect, wxWindow * parent) -: EffectDialog(parent, _("Phaser"), PROCESS_EFFECT), - mEffect(effect) +sampleCount EffectPhaser::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) { - Init(); + float *ibuf = inBlock[0]; + float *obuf = outBlock[0]; + + for (sampleCount i = 0; i < blockLen; i++) + { + double in = ibuf[i]; + + double m = in + fbout * mFeedback / 100; + + if (((skipcount++) % lfoskipsamples) == 0) + { + //compute sine between 0 and 1 + gain = (1.0 + cos(skipcount * lfoskip + phase)) / 2.0; + + // change lfo shape + gain = (exp(gain * phaserlfoshape) - 1.0) / (exp(phaserlfoshape) - 1.0); + + // attenuate the lfo + gain = 1.0 - gain / 255.0 * mDepth; + } + + // phasing routine + for (int j = 0; j < mStages; j++) + { + double tmp = old[j]; + old[j] = gain * tmp + m; + m = tmp - gain * old[j]; + } + fbout = m; + + obuf[i] = (float) ((m * mDryWet + in * (255 - mDryWet)) / 255); + } + + return blockLen; } -void PhaserDialog::PopulateOrExchange(ShuttleGui & S) +bool EffectPhaser::GetAutomationParameters(EffectAutomationParameters & parms) { - wxTextValidator vld(wxFILTER_NUMERIC); + parms.Write(KEY_Stages, mStages); + parms.Write(KEY_DryWet, mDryWet); + parms.Write(KEY_Freq, mFreq); + parms.Write(KEY_Phase, mPhase); + parms.Write(KEY_Depth, mDepth); + parms.Write(KEY_Feedback, mFeedback); + return true; +} + +bool EffectPhaser::SetAutomationParameters(EffectAutomationParameters & parms) +{ + ReadAndVerifyInt(Stages); + ReadAndVerifyInt(DryWet); + ReadAndVerifyDouble(Freq); + ReadAndVerifyDouble(Phase); + ReadAndVerifyInt(Depth); + ReadAndVerifyInt(Feedback); + + if (Stages & 1) // must be even, but don't complain about it + { + Stages &= ~1; + } + + mFreq = Freq; + mFeedback = Feedback; + mStages = Stages; + mDryWet = DryWet; + mDepth = Depth; + mPhase = Phase; + + return true; +} + +// Effect implementation + +void EffectPhaser::PopulateOrExchange(ShuttleGui & S) +{ S.SetBorder(5); S.StartMultiColumn(3, wxEXPAND); { - wxSlider *s; - wxTextCtrl * tempTC; - S.SetStretchyCol(1); - tempTC = S.Id(ID_PHASER_STAGESTEXT).AddTextBox(_("Stages:"), wxT(""), 12); + S.SetStretchyCol(2); + + IntegerValidator vldStages(&mStages); + vldStages.SetRange(MIN_Stages, MAX_Stages); + mStagesT = S.Id(ID_Stages).AddTextBox(_("Stages:"), wxT(""), 12); + mStagesT->SetValidator(vldStages); + S.SetStyle(wxSL_HORIZONTAL); - tempTC->SetValidator(vld); - s = S.Id(ID_PHASER_STAGESSLIDER).AddSlider(wxT(""), 2, STAGES_MAX, STAGES_MIN); - s->SetName(_("Stages")); + mStagesS = S.Id(ID_Stages).AddSlider(wxT(""), DEF_Stages * SCL_Stages, MAX_Stages * SCL_Stages, MIN_Stages * SCL_Stages); + mStagesS->SetName(_("Stages")); + mStagesS->SetLineSize(2); #if defined(__WXGTK__) - s->SetMinSize(wxSize(100, -1)); + mStagesS->SetMinSize(wxSize(100, -1)); #endif - tempTC = S.Id(ID_PHASER_DRYWETTEXT).AddTextBox(_("Dry/Wet:"), wxT(""), 12); + IntegerValidator vldDryWet(&mDryWet); + vldDryWet.SetRange(MIN_DryWet, MAX_DryWet); + mDryWetT = S.Id(ID_DryWet).AddTextBox(_("Dry/Wet:"), wxT(""), 12); + mDryWetT->SetValidator(vldDryWet); + S.SetStyle(wxSL_HORIZONTAL); - tempTC->SetValidator(vld); - s = S.Id(ID_PHASER_DRYWETSLIDER).AddSlider(wxT(""), 0, DRYWET_MAX, DRYWET_MIN); - s->SetName(_("Dry Wet")); + mDryWetS = S.Id(ID_DryWet).AddSlider(wxT(""), DEF_DryWet * SCL_DryWet, MAX_DryWet * SCL_DryWet, MIN_DryWet * SCL_DryWet); + mDryWetS->SetName(_("Dry Wet")); #if defined(__WXGTK__) - s->SetMinSize(wxSize(100, -1)); + mDryWetS->SetMinSize(wxSize(100, -1)); #endif - tempTC = S.Id(ID_PHASER_FREQTEXT).AddTextBox(_("LFO Frequency (Hz):"), wxT(""), 12); + FloatingPointValidator vldFreq(1, &mFreq); + vldFreq.SetRange(MIN_Freq, MAX_Freq); + mFreqT = S.Id(ID_Freq).AddTextBox(_("LFO Frequency (Hz):"), wxT(""), 12); + mFreqT->SetValidator(vldFreq); + S.SetStyle(wxSL_HORIZONTAL); - tempTC->SetValidator(vld); - s = S.Id(ID_PHASER_FREQSLIDER).AddSlider(wxT(""), 100, FREQ_MAX, FREQ_MIN); - s->SetName(_("LFO frequency in hertz")); + mFreqS = S.Id(ID_Freq).AddSlider(wxT(""), DEF_Freq * SCL_Freq, MAX_Freq * SCL_Freq, MIN_Freq * SCL_Freq); + mFreqS ->SetName(_("LFO frequency in hertz")); #if defined(__WXGTK__) - s->SetMinSize(wxSize(100, -1)); + mFreqS ->SetMinSize(wxSize(100, -1)); #endif - tempTC = S.Id(ID_PHASER_PHASETEXT).AddTextBox(_("LFO Start Phase (deg.):"), wxT(""), 12); + FloatingPointValidator vldPhase(1, &mPhase); + vldPhase.SetRange(MIN_Phase, MAX_Phase); + mPhaseT = S.Id(ID_Phase).AddTextBox(_("LFO Start Phase (deg.):"), wxT(""), 12); + mPhaseT->SetValidator(vldPhase); + S.SetStyle(wxSL_HORIZONTAL); - tempTC->SetValidator(vld); - s = S.Id(ID_PHASER_PHASESLIDER).AddSlider(wxT(""), 0, PHASE_MAX, PHASE_MIN); - s->SetName(_("LFO start phase in degrees")); - s->SetLineSize(10); + mPhaseS = S.Id(ID_Phase).AddSlider(wxT(""), DEF_Phase * SCL_Phase, MAX_Phase * SCL_Phase, MIN_Phase * SCL_Phase); + mPhaseS->SetName(_("LFO start phase in degrees")); + mPhaseS->SetLineSize(10); #if defined(__WXGTK__) - s->SetMinSize(wxSize(100, -1)); + mPhaseS->SetMinSize(wxSize(100, -1)); #endif - tempTC = S.Id(ID_PHASER_DEPTHTEXT).AddTextBox(_("Depth:"), wxT(""), 12); + IntegerValidator vldDepth(&mDepth); + vldDepth.SetRange(MIN_Depth, MAX_Depth); + mDepthT = S.Id(ID_Depth).AddTextBox(_("Depth:"), wxT(""), 12); + mDepthT->SetValidator(vldDepth); + S.SetStyle(wxSL_HORIZONTAL); - tempTC->SetValidator(vld); - s = S.Id(ID_PHASER_DEPTHSLIDER).AddSlider(wxT(""), 0, DEPTH_MAX, DEPTH_MIN); - s->SetName(_("Depth in percent")); + mDepthS = S.Id(ID_Depth).AddSlider(wxT(""), DEF_Depth * SCL_Depth, MAX_Depth * SCL_Depth, MIN_Depth * SCL_Depth); + mDepthS->SetName(_("Depth in percent")); #if defined(__WXGTK__) - s->SetMinSize(wxSize(100, -1)); + mDepthS->SetMinSize(wxSize(100, -1)); #endif - tempTC = S.Id(ID_PHASER_FEEDBACKTEXT).AddTextBox(_("Feedback (%):"), wxT(""), 12); + IntegerValidator vldFeedback(&mFeedback); + vldFeedback.SetRange(MIN_Feedback, MAX_Feedback); + mFeedbackT = S.Id(ID_Feedback).AddTextBox(_("Feedback (%):"), wxT(""), 12); + mFeedbackT->SetValidator(vldFeedback); + S.SetStyle(wxSL_HORIZONTAL); - tempTC->SetValidator(vld); - s = S.Id(ID_PHASER_FEEDBACKSLIDER).AddSlider(wxT(""), 0, FB_MAX, FB_MIN); - s->SetName(_("Feedback in percent")); - s->SetLineSize(10); + mFeedbackS = S.Id(ID_Feedback).AddSlider(wxT(""), DEF_Feedback * SCL_Feedback, MAX_Feedback * SCL_Feedback, MIN_Feedback * SCL_Feedback); + mFeedbackS->SetName(_("Feedback in percent")); + mFeedbackS->SetLineSize(10); #if defined(__WXGTK__) - s->SetMinSize(wxSize(100, -1)); + mFeedbackS->SetMinSize(wxSize(100, -1)); #endif } S.EndMultiColumn(); } -bool PhaserDialog::TransferDataToWindow() +bool EffectPhaser::TransferDataToWindow() { - wxSlider *slider; - - slider = GetFreqSlider(); - if (slider) - slider->SetValue((int)(freq * 10)); - - slider = GetPhaseSlider(); - if (slider) - slider->SetValue((int)startphase); - - slider = GetDepthSlider(); - if (slider) - slider->SetValue((int)depth); - - slider = GetFeedbackSlider(); - if (slider) - slider->SetValue((int)fb); - - slider = GetDryWetSlider(); - if (slider) - slider->SetValue((int)drywet); - - slider = GetStagesSlider(); - if (slider) - slider->SetValue((int)stages); - - wxTextCtrl *text; - - text = GetStagesText(); - if (text) { - wxString str; - str.Printf(wxT("%d"), stages); - text->SetValue(str); + if (!mUIParent->TransferDataToWindow()) + { + return false; } - text = GetDryWetText(); - if (text) { - wxString str; - str.Printf(wxT("%d"), drywet); - text->SetValue(str); - } - - text = GetFreqText(); - if (text) { - wxString str; - str.Printf(wxT("%.1f"), freq); - text->SetValue(str); - } - - text = GetPhaseText(); - if (text) { - wxString str; - str.Printf(wxT("%d"), (int) startphase); - text->SetValue(str); - } - - text = GetDepthText(); - if (text) { - wxString str; - str.Printf(wxT("%d"), (int) depth); - text->SetValue(str); - } - - text = GetFeedbackText(); - if (text) { - wxString str; - str.Printf(wxT("%d"), (int) fb); - text->SetValue(str); - } - - return TRUE; + return true; } -bool PhaserDialog::TransferDataFromWindow() +bool EffectPhaser::TransferDataFromWindow() { - wxTextCtrl *c; - long x; - - c = GetFreqText(); - if (c) { - double d; - c->GetValue().ToDouble(&d); - freq = TrapDouble(d * 10, FREQ_MIN, FREQ_MAX) / 10; + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; } - c = GetPhaseText(); - if (c) { - c->GetValue().ToLong(&x); - startphase = TrapLong(x, PHASE_MIN, PHASE_MAX); + if (mStages & 1) // must be even + { + mStages &= ~1; + mUIParent->TransferDataToWindow(); } - c = GetDepthText(); - if (c) { - c->GetValue().ToLong(&x); - depth = TrapLong(x, DEPTH_MIN, DEPTH_MAX); + return true; +} + +// EffectPhaser implementation + +void EffectPhaser::OnStagesSlider(wxCommandEvent & evt) +{ + int val = evt.GetInt() & ~1; // must be even; + mPhaseS->SetValue(val); + mStages /= SCL_Stages; + mStagesT->GetValidator()->TransferToWindow(); + EnableApply(mUIParent->Validate()); +} + +void EffectPhaser::OnDryWetSlider(wxCommandEvent & evt) +{ + mDryWet = evt.GetInt() / SCL_DryWet; + mDryWetT->GetValidator()->TransferToWindow(); + EnableApply(mUIParent->Validate()); +} + +void EffectPhaser::OnFreqSlider(wxCommandEvent & evt) +{ + mFreq = (double) evt.GetInt() / SCL_Freq; + mFreqT->GetValidator()->TransferToWindow(); + EnableApply(mUIParent->Validate()); +} + +void EffectPhaser::OnPhaseSlider(wxCommandEvent & evt) +{ + int val = ((evt.GetInt() + 5) / 10) * 10; // round to nearest multiple of 10 + val = val > MAX_Phase * SCL_Phase ? MAX_Phase * SCL_Phase : val; + mPhaseS->SetValue(val); + mPhase = (double) val / SCL_Phase; + mPhaseT->GetValidator()->TransferToWindow(); + EnableApply(mUIParent->Validate()); +} + +void EffectPhaser::OnDepthSlider(wxCommandEvent & evt) +{ + mDepth = evt.GetInt() / SCL_Depth; + mDepthT->GetValidator()->TransferToWindow(); + EnableApply(mUIParent->Validate()); +} + +void EffectPhaser::OnFeedbackSlider(wxCommandEvent & evt) +{ + int val = evt.GetInt(); + val = ((val + (val > 0 ? 5 : -5)) / 10) * 10; // round to nearest multiple of 10 + val = val > MAX_Feedback * SCL_Feedback ? MAX_Feedback * SCL_Feedback : val; + mFeedbackS->SetValue(val); + mFeedback = val / SCL_Feedback; + mFeedbackT->GetValidator()->TransferToWindow(); + EnableApply(mUIParent->Validate()); +} + +void EffectPhaser::OnStagesText(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; } - c = GetFeedbackText(); - if (c) { - c->GetValue().ToLong(&x); - fb = TrapLong(x, FB_MIN, FB_MAX); + mStagesS->SetValue((int) (mStages * SCL_Stages)); +} + +void EffectPhaser::OnDryWetText(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; } - c = GetStagesText(); - if (c) { - c->GetValue().ToLong(&x); - stages = TrapLong(x, STAGES_MIN, STAGES_MAX); - if ((stages % 2) == 1) // must be even - stages = TrapLong(stages - 1, STAGES_MIN, STAGES_MAX); + mDryWetS->SetValue((int) (mDryWet * SCL_DryWet)); +} + +void EffectPhaser::OnFreqText(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; } - c = GetDryWetText(); - if (c) { - c->GetValue().ToLong(&x); - drywet = TrapLong(x, DRYWET_MIN, DRYWET_MAX); + mFreqS->SetValue((int) (mFreq * SCL_Freq)); +} + +void EffectPhaser::OnPhaseText(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; } - return TRUE; + mPhaseS->SetValue((int) (mPhase * SCL_Phase)); } -// WDR: handler implementations for PhaserDialog - -void PhaserDialog::OnStagesSlider(wxCommandEvent & WXUNUSED(event)) +void EffectPhaser::OnDepthText(wxCommandEvent & WXUNUSED(evt)) { - wxString str; - long stage = GetStagesSlider()->GetValue(); - str.Printf(wxT("%ld"), stage); - GetStagesText()->SetValue(str); -} - -void PhaserDialog::OnDryWetSlider(wxCommandEvent & WXUNUSED(event)) -{ - wxString str; - long drywet = GetDryWetSlider()->GetValue(); - str.Printf(wxT("%ld"), drywet); - GetDryWetText()->SetValue(str); -} - -void PhaserDialog::OnFeedbackSlider(wxCommandEvent & WXUNUSED(event)) -{ - wxString str; - long fb = GetFeedbackSlider()->GetValue(); - if (fb > 0) // round to nearest multiple of 10 - fb = ((fb + 5) / 10) * 10; - else - fb = ((fb - 5) / 10) * 10; - str.Printf(wxT("%ld"), fb); - GetFeedbackText()->SetValue(str); -} - -void PhaserDialog::OnDepthSlider(wxCommandEvent & WXUNUSED(event)) -{ - wxString str; - long depth = GetDepthSlider()->GetValue(); - str.Printf(wxT("%ld"), depth); - GetDepthText()->SetValue(str); -} - -void PhaserDialog::OnPhaseSlider(wxCommandEvent & WXUNUSED(event)) -{ - wxString str; - long phase = GetPhaseSlider()->GetValue(); - phase = ((phase + 5) / 10) * 10; // round to nearest multiple of 10 - str.Printf(wxT("%ld"), phase); - GetPhaseText()->SetValue(str); -} - -void PhaserDialog::OnFreqSlider(wxCommandEvent & WXUNUSED(event)) -{ - wxString str; - long freq = GetFreqSlider()->GetValue(); - str.Printf(wxT("%.1f"), freq / 10.0); - GetFreqText()->SetValue(str); -} - -void PhaserDialog::OnStagesText(wxCommandEvent & WXUNUSED(event)) -{ - wxTextCtrl *c = GetStagesText(); - if (c) { - long stage; - - c->GetValue().ToLong(&stage); - stage = TrapLong(stage, STAGES_MIN, STAGES_MAX); - - wxSlider *slider = GetStagesSlider(); - if (slider) - slider->SetValue(stage); + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; } + + mDepthS->SetValue((int) (mDepth * SCL_Depth)); } -void PhaserDialog::OnDryWetText(wxCommandEvent & WXUNUSED(event)) +void EffectPhaser::OnFeedbackText(wxCommandEvent & WXUNUSED(evt)) { - wxTextCtrl *c = GetDryWetText(); - if (c) { - long drywet; - - c->GetValue().ToLong(&drywet); - drywet = TrapLong(drywet, DRYWET_MIN, DRYWET_MAX); - - wxSlider *slider = GetDryWetSlider(); - if (slider) - slider->SetValue(drywet); + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; } -} - -void PhaserDialog::OnFeedbackText(wxCommandEvent & WXUNUSED(event)) -{ - wxTextCtrl *c = GetFeedbackText(); - if (c) { - long fb; - - c->GetValue().ToLong(&fb); - fb = TrapLong(fb, FB_MIN, FB_MAX); - - wxSlider *slider = GetFeedbackSlider(); - if (slider) - slider->SetValue(fb); - } -} - -void PhaserDialog::OnDepthText(wxCommandEvent & WXUNUSED(event)) -{ - wxTextCtrl *c = GetDepthText(); - if (c) { - long depth; - - c->GetValue().ToLong(&depth); - depth = TrapLong(depth, DEPTH_MIN, DEPTH_MAX); - - wxSlider *slider = GetDepthSlider(); - if (slider) - slider->SetValue(depth); - } -} - -void PhaserDialog::OnPhaseText(wxCommandEvent & WXUNUSED(event)) -{ - wxTextCtrl *c = GetPhaseText(); - if (c) { - long phase; - - c->GetValue().ToLong(&phase); - phase = TrapLong(phase, PHASE_MIN, PHASE_MAX); - - wxSlider *slider = GetPhaseSlider(); - if (slider) - slider->SetValue(phase); - } -} - -void PhaserDialog::OnFreqText(wxCommandEvent & WXUNUSED(event)) -{ - wxTextCtrl *c = GetFreqText(); - if (c) { - double freq; - - c->GetValue().ToDouble(&freq); - freq = TrapDouble(freq * 10, FREQ_MIN, FREQ_MAX); - - wxSlider *slider = GetFreqSlider(); - if (slider) - slider->SetValue((int)freq); - } -} - -void PhaserDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - - // Save & restore parameters around Preview, because we didn't do OK. - float old_freq = mEffect->freq; - float old_startphase = mEffect->startphase; - float old_fb = mEffect->fb; - int old_depth = mEffect->depth; - int old_stages = mEffect->stages; - int old_drywet = mEffect->drywet; - - mEffect->freq = freq; - mEffect->startphase = startphase * M_PI / 180; - mEffect->fb = fb; - mEffect->depth = depth; - mEffect->stages = stages; - mEffect->drywet = drywet; - - mEffect->Preview(); - - mEffect->freq = old_freq; - mEffect->startphase = old_startphase; - mEffect->fb = old_fb; - mEffect->depth = old_depth; - mEffect->stages = old_stages; - mEffect->drywet = old_drywet; + + mFeedbackS->SetValue((int) (mFeedback * SCL_Feedback)); } diff --git a/src/effects/Phaser.h b/src/effects/Phaser.h index 53a844c5b..608a85609 100644 --- a/src/effects/Phaser.h +++ b/src/effects/Phaser.h @@ -16,188 +16,108 @@ #ifndef __AUDACITY_EFFECT_PHASER__ #define __AUDACITY_EFFECT_PHASER__ -// For compilers that support precompilation, includes "wx/wx.h". -#include - -#ifndef WX_PRECOMP -#include -#endif - -#include -#include +#include #include +#include +#include -class wxString; -class wxTextCtrl; -class wxSizer; -class wxSpinCtrl; +#include "../ShuttleGui.h" -#include "SimpleMono.h" +#include "Effect.h" -class WaveTrack; +#define NUM_STAGES 24 -class EffectPhaser:public EffectSimpleMono { +#define PHASER_PLUGIN_SYMBOL wxTRANSLATE("Phaser") - public: +class EffectPhaser : public Effect +{ +public: EffectPhaser(); + virtual ~EffectPhaser(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Phaser...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#PhaserPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Phaser")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Applying Phaser")); - } + virtual EffectType GetType(); - // Useful only after PromptUser values have been set. - virtual wxString GetEffectDescription(); + // EffectClientInterface implementation - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); + virtual int GetAudioInCount(); + virtual int GetAudioOutCount(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); - protected: - virtual bool NewTrackSimpleMono(); - - virtual bool ProcessSimpleMono(float *buffer, sampleCount len); - -/* - Phaser Parameters - - freq - Phaser's LFO frequency - startphase - Phaser's LFO startphase (radians), needed for stereo Phasers - depth - Phaser depth (0 - no depth, 255 - max depth) - stages - Phaser stages (recomanded from 2 to 16-24, and EVEN NUMBER) - drywet - Dry/wet mix, (0 - dry, 128 - dry=wet, 255 - wet) - fb - Phaser FeedBack (0 - no feedback, 100 = 100% Feedback, - -100 = -100% FeedBack) -*/ - - private: - - // parameters - float freq; - float startphase; - float fb; - int depth; - int stages; - int drywet; - - // state variables - unsigned long skipcount; - float old[24]; // must be as large as MAX_STAGES - float gain; - float fbout; - float lfoskip; - float phase; - -friend class PhaserDialog; -}; - -// Declare window functions - -#define ID_PHASER_STAGESTEXT 12001 -#define ID_PHASER_STAGESSLIDER 12002 -#define ID_PHASER_DRYWETTEXT 12003 -#define ID_PHASER_DRYWETSLIDER 12004 -#define ID_PHASER_FREQTEXT 12005 -#define ID_PHASER_FREQSLIDER 12006 -#define ID_PHASER_PHASETEXT 12007 -#define ID_PHASER_PHASESLIDER 12008 -#define ID_PHASER_DEPTHTEXT 12009 -#define ID_PHASER_DEPTHSLIDER 12010 -#define ID_PHASER_FEEDBACKTEXT 12011 -#define ID_PHASER_FEEDBACKSLIDER 12012 - -//---------------------------------------------------------------------------- -// PhaserDialog -//---------------------------------------------------------------------------- - -class PhaserDialog:public EffectDialog { - public: - // constructors and destructors - PhaserDialog(EffectPhaser * effect, wxWindow * parent); + // Effect implementation void PopulateOrExchange(ShuttleGui & S); bool TransferDataToWindow(); bool TransferDataFromWindow(); - wxSlider *GetStagesSlider() { - return (wxSlider *) FindWindow(ID_PHASER_STAGESSLIDER); - } - wxSlider *GetDryWetSlider() { - return (wxSlider *) FindWindow(ID_PHASER_DRYWETSLIDER); - } - wxSlider *GetFeedbackSlider() { - return (wxSlider *) FindWindow(ID_PHASER_FEEDBACKSLIDER); - } - wxSlider *GetDepthSlider() { - return (wxSlider *) FindWindow(ID_PHASER_DEPTHSLIDER); - } - wxSlider *GetPhaseSlider() { - return (wxSlider *) FindWindow(ID_PHASER_PHASESLIDER); - } - wxSlider *GetFreqSlider() { - return (wxSlider *) FindWindow(ID_PHASER_FREQSLIDER); - } - wxTextCtrl *GetStagesText() { - return (wxTextCtrl *) FindWindow(ID_PHASER_STAGESTEXT); - } - wxTextCtrl *GetDryWetText() { - return (wxTextCtrl *) FindWindow(ID_PHASER_DRYWETTEXT); - } - wxTextCtrl *GetFeedbackText() { - return (wxTextCtrl *) FindWindow(ID_PHASER_FEEDBACKTEXT); - } - wxTextCtrl *GetDepthText() { - return (wxTextCtrl *) FindWindow(ID_PHASER_DEPTHTEXT); - } - wxTextCtrl *GetPhaseText() { - return (wxTextCtrl *) FindWindow(ID_PHASER_PHASETEXT); - } - wxTextCtrl *GetFreqText() { - return (wxTextCtrl *) FindWindow(ID_PHASER_FREQTEXT); - } +protected: + // EffectPhaser implementation - private: - // WDR: handler declarations for PhaserDialog - void OnStagesSlider(wxCommandEvent & event); - void OnDryWetSlider(wxCommandEvent & event); - void OnFeedbackSlider(wxCommandEvent & event); - void OnDepthSlider(wxCommandEvent & event); - void OnPhaseSlider(wxCommandEvent & event); - void OnFreqSlider(wxCommandEvent & event); - void OnStagesText(wxCommandEvent & event); - void OnDryWetText(wxCommandEvent & event); - void OnFeedbackText(wxCommandEvent & event); - void OnDepthText(wxCommandEvent & event); - void OnPhaseText(wxCommandEvent & event); - void OnFreqText(wxCommandEvent & event); - void OnPreview(wxCommandEvent &event); + void OnStagesSlider(wxCommandEvent & evt); + void OnDryWetSlider(wxCommandEvent & evt); + void OnFeedbackSlider(wxCommandEvent & evt); + void OnDepthSlider(wxCommandEvent & evt); + void OnPhaseSlider(wxCommandEvent & evt); + void OnFreqSlider(wxCommandEvent & evt); + void OnStagesText(wxCommandEvent & evt); + void OnDryWetText(wxCommandEvent & evt); + void OnFeedbackText(wxCommandEvent & evt); + void OnDepthText(wxCommandEvent & evt); + void OnPhaseText(wxCommandEvent & evt); + void OnFreqText(wxCommandEvent & evt); +/* + Phaser Parameters - private: - EffectPhaser * mEffect; + mFreq - Phaser's LFO frequency + mPhase - Phaser's LFO startphase (radians), needed for stereo Phasers + mDepth - Phaser depth (0 - no depth, 255 - max depth) + mStages - Phaser stages (recomanded from 2 to 16-24, and EVEN NUMBER) + mDryWet - Dry/wet mix, (0 - dry, 128 - dry=wet, 255 - wet) + mFeedback - Phaser FeedBack (0 - no feedback, 100 = 100% Feedback, + -100 = -100% FeedBack) +*/ - public: - float freq; - float startphase; - float fb; +private: + // state variables + sampleCount skipcount; + double old[NUM_STAGES]; // must be as large as MAX_STAGES + double gain; + double fbout; + double lfoskip; + double phase; - int depth; - int stages; - int drywet; + // parameters + int mStages; + int mDryWet; + double mFreq; + double mPhase; + int mDepth; + int mFeedback; - private: - DECLARE_EVENT_TABLE() + wxTextCtrl *mStagesT; + wxTextCtrl *mDryWetT; + wxTextCtrl *mFreqT; + wxTextCtrl *mPhaseT; + wxTextCtrl *mDepthT; + wxTextCtrl *mFeedbackT; + + wxSlider *mStagesS; + wxSlider *mDryWetS; + wxSlider *mFreqS; + wxSlider *mPhaseS; + wxSlider *mDepthS; + wxSlider *mFeedbackS; + + DECLARE_EVENT_TABLE(); }; #endif diff --git a/src/effects/Repair.cpp b/src/effects/Repair.cpp index 0d5493ea1..ef5e8b641 100644 --- a/src/effects/Repair.cpp +++ b/src/effects/Repair.cpp @@ -25,12 +25,13 @@ the audio, rather than actually finding the clicks. #include -#include #include +#include + +#include "../InterpolateAudio.h" +#include "../WaveTrack.h" #include "Repair.h" -#include "../WaveTrack.h" -#include "../InterpolateAudio.h" EffectRepair::EffectRepair() { @@ -40,18 +41,32 @@ EffectRepair::~EffectRepair() { } -bool EffectRepair::PromptUser() +// IdentInterface implementation + +wxString EffectRepair::GetSymbol() { - return true; + return REPAIR_PLUGIN_SYMBOL; } -bool EffectRepair::TransferParameters( Shuttle & WXUNUSED(shuttle) ) +wxString EffectRepair::GetDescription() { - //TODO: pop-click values. -// shuttle.TransferInt("",,0); - return true; + return wxTRANSLATE("Sets the peak amplitude of a one or more tracks"); } +// EffectIdentInterface implementation + +EffectType EffectRepair::GetType() +{ + return EffectTypeProcess; +} + +bool EffectRepair::IsInteractive() +{ + return false; +} + +// Effect implementation + bool EffectRepair::Process() { //v This may be too much copying for EffectRepair. To support Cancel, may be able to copy much less. diff --git a/src/effects/Repair.h b/src/effects/Repair.h index a0f337819..2b07bda72 100644 --- a/src/effects/Repair.h +++ b/src/effects/Repair.h @@ -11,44 +11,37 @@ #ifndef __AUDACITY_EFFECT_REPAIR__ #define __AUDACITY_EFFECT_REPAIR__ -#include #include -#include "SimpleMono.h" +#include "Effect.h" + +#define REPAIR_PLUGIN_SYMBOL wxTRANSLATE("Repair") class WaveTrack; -class EffectRepair: public Effect { - +class EffectRepair : public Effect +{ public: - EffectRepair(); virtual ~EffectRepair(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Repair")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://audacityteam.org/namespace#NoiseRemoval")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Repair")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Repairing damaged audio")); - } + virtual EffectType GetType(); + virtual bool IsInteractive(); - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); + // Effect implementation virtual bool Process(); private: + // EffectRepair implementaion + bool ProcessOne(int count, WaveTrack * track, sampleCount start, sampleCount len, diff --git a/src/effects/Repeat.cpp b/src/effects/Repeat.cpp index c448d4457..c93acabdd 100644 --- a/src/effects/Repeat.cpp +++ b/src/effects/Repeat.cpp @@ -22,104 +22,83 @@ #include "../Audacity.h" -#include "Repeat.h" -#include "../ShuttleGui.h" -#include "../WaveTrack.h" -#include "../LabelTrack.h" -#include "../widgets/NumericTextCtrl.h" -#include "../Project.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include + +#include "../LabelTrack.h" +#include "../ShuttleGui.h" +#include "../WaveTrack.h" +#include "../widgets/NumericTextCtrl.h" +#include "../widgets/valnum.h" + +#include "Repeat.h" + +BEGIN_EVENT_TABLE(EffectRepeat, wxEvtHandler) + EVT_TEXT(wxID_ANY, EffectRepeat::OnRepeatTextChange) +END_EVENT_TABLE() EffectRepeat::EffectRepeat() { repeatCount = 10; } -wxString EffectRepeat::GetEffectDescription() { - // Note: This is useful only after values have been set. - return wxString::Format(_("Repeated %d times"), repeatCount); +EffectRepeat::~EffectRepeat() +{ } -bool EffectRepeat::PromptUser() +// IdentInterface implementation + +wxString EffectRepeat::GetSymbol() { - // - // Figure out the maximum number of times the selection - // could be repeated without overflowing any track - // - int maxCount = -1; - TrackListOfKindIterator iter(Track::Wave, mTracks); - WaveTrack *track = (WaveTrack *) iter.First(); - while (track) { - sampleCount trackLen = - (sampleCount)((track->GetEndTime() - track->GetStartTime()) * - track->GetRate()); - sampleCount selectionLen = (sampleCount)((mT1 - mT0) * track->GetRate()); - if (selectionLen == 0) { - wxMessageBox(_("Selection is too short to repeat."), - _("Repeat"), wxOK | wxCENTRE, mParent); - return false; - } - int availSamples = 2147483647 - trackLen; - int count = availSamples / selectionLen; - if (maxCount == -1 || count < maxCount) - maxCount = count; + return REPEAT_PLUGIN_SYMBOL; +} - track = (WaveTrack *) iter.Next(); - } +wxString EffectRepeat::GetDescription() +{ + return wxTRANSLATE("Repeats the selection the specified number of times"); +} - if (maxCount <= 1) { - // TO DO: Not really true now that SampleCount is 64-bit int, but while bug 416 - // is open, do we want to encourage repeating hugely long tracks? - wxMessageBox(_("Tracks are too long to repeat the selection."), - _("Repeat"), wxOK | wxCENTRE, mParent); - return false; - } +// EffectIdentInterface implementation - RepeatDialog dlog(this, mParent); - dlog.repeatCount = repeatCount; - dlog.selectionTimeSecs = mT1 - mT0; - dlog.maxCount = maxCount; - dlog.TransferDataToWindow(); +EffectType EffectRepeat::GetType() +{ + return EffectTypeProcess; +} - dlog.CentreOnParent(); +// EffectClientInterface implementation - dlog.ShowModal(); - - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; - - repeatCount = dlog.repeatCount; - if (repeatCount > maxCount) - repeatCount = maxCount; - if (repeatCount < 1) - repeatCount = 1; +bool EffectRepeat::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(wxT("Count"), repeatCount); return true; } -bool EffectRepeat::TransferParameters( Shuttle & shuttle ) +bool EffectRepeat::SetAutomationParameters(EffectAutomationParameters & parms) { - shuttle.TransferInt(wxT("Count"),repeatCount,1); + int count; + + parms.Read(wxT("Count"), &count, 10); + + if (count < 0 || count > 2147483647 / mProjectRate) + { + return false; + } + + repeatCount = count; + return true; } +// Effect implementation + bool EffectRepeat::Process() { // Set up mOutputTracks. // This effect needs Track::All for sync-lock grouping. - this->CopyInputTracks(Track::All); + CopyInputTracks(Track::All); int nTrack = 0; bool bGoodResult = true; @@ -127,7 +106,8 @@ bool EffectRepeat::Process() TrackListIterator iter(mOutputTracks); - for (Track *t = iter.First(); t && bGoodResult; t = iter.Next()) { + for (Track *t = iter.First(); t && bGoodResult; t = iter.Next()) + { if (t->GetKind() == Track::Label) { if (t->GetSelected() || t->IsSyncLockSelected()) @@ -152,7 +132,9 @@ bool EffectRepeat::Process() double tc = mT0 + tLen; if (len <= 0) + { continue; + } Track *dest; track->Copy(mT0, mT1, &dest); @@ -183,57 +165,29 @@ bool EffectRepeat::Process() mT1 = maxDestLen; } - this->ReplaceProcessedTracks(bGoodResult); + ReplaceProcessedTracks(bGoodResult); return bGoodResult; } -//---------------------------------------------------------------------------- -// RepeatDialog -//---------------------------------------------------------------------------- - -const static wxChar *numbers[] = +void EffectRepeat::PopulateOrExchange(ShuttleGui & S) { - wxT("0"), wxT("1"), wxT("2"), wxT("3"), wxT("4"), - wxT("5"), wxT("6"), wxT("7"), wxT("8"), wxT("9") -}; - -#define ID_REPEAT_TEXT 7000 - -BEGIN_EVENT_TABLE(RepeatDialog, EffectDialog) - EVT_TEXT(ID_REPEAT_TEXT, RepeatDialog::OnRepeatTextChange) - EVT_BUTTON(ID_EFFECT_PREVIEW, RepeatDialog::OnPreview) -END_EVENT_TABLE() - -RepeatDialog::RepeatDialog(EffectRepeat *effect, - wxWindow * parent) -: EffectDialog(parent, _("Repeat"), PROCESS_EFFECT), - mEffect(effect) -{ - Init(); -} - -void RepeatDialog::PopulateOrExchange(ShuttleGui & S) -{ - wxTextValidator vld(wxFILTER_INCLUDE_CHAR_LIST); - vld.SetIncludes(wxArrayString(10, numbers)); - S.StartHorizontalLay(wxCENTER, false); { - mRepeatCount = S.Id(ID_REPEAT_TEXT).AddTextBox(_("Number of times to repeat:"), - wxT(""), - 12); - mRepeatCount->SetValidator(vld); + IntegerValidator vldRepeatCount(&repeatCount); + vldRepeatCount.SetRange(1, 2147483647 / mProjectRate); + mRepeatCount = S.AddTextBox(_("Number of times to repeat:"), wxT(""), 12); + mRepeatCount->SetValidator(vldRepeatCount); } S.EndHorizontalLay(); S.StartHorizontalLay(wxCENTER, true); { - mTotalTime = S.AddVariableText(_("New selection length: hh:mm:ss")); + mTotalTime = S.AddVariableText(_("New selection length: dd:hh:mm:ss")); } S.EndHorizontalLay(); } -bool RepeatDialog::TransferDataToWindow() +bool EffectRepeat::TransferDataToWindow() { mRepeatCount->ChangeValue(wxString::Format(wxT("%d"), repeatCount)); @@ -242,57 +196,38 @@ bool RepeatDialog::TransferDataToWindow() return true; } -bool RepeatDialog::TransferDataFromWindow() +bool EffectRepeat::TransferDataFromWindow() { + if (!mUIParent->Validate()) + { + return false; + } + long l; + mRepeatCount->GetValue().ToLong(&l); - repeatCount = l; - if (repeatCount < 1) - repeatCount = 1; - if (repeatCount > maxCount) - repeatCount = maxCount; + repeatCount = (int) l; return true; } -void RepeatDialog::DisplayNewTime() +void EffectRepeat::DisplayNewTime() { - wxString str; + TransferDataFromWindow(); - str = _("New selection length: "); - NumericTextCtrl tt(NumericTextCtrl::TIME, this, - wxID_ANY, - wxT(""), - selectionTimeSecs * (repeatCount + 1), - mEffect->mProjectRate, - wxPoint(10000, 10000), // create offscreen - wxDefaultSize, - true); - tt.SetFormatString(tt.GetBuiltinFormat(_("hh:mm:ss"))); - str += tt.GetString(); + NumericConverter nc(NumericConverter::TIME, + _("hh:mm:ss"), + (mT1 - mT0) * (repeatCount + 1), + mProjectRate); + + wxString str = _("New selection length: ") + nc.GetString(); mTotalTime->SetLabel(str); mTotalTime->SetName(str); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) } -void RepeatDialog::OnRepeatTextChange(wxCommandEvent & WXUNUSED(event)) +void EffectRepeat::OnRepeatTextChange(wxCommandEvent & WXUNUSED(evt)) { - TransferDataFromWindow(); - DisplayNewTime(); } - -void RepeatDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - - int oldRepeatCount = mEffect->repeatCount; - - mEffect->repeatCount = repeatCount; - - // LL: Preview doesn't work...Effect::Preview needs to allow new length - mEffect->Preview(); - - mEffect->repeatCount = oldRepeatCount; -} diff --git a/src/effects/Repeat.h b/src/effects/Repeat.h index a3dd5b231..9a2aff974 100644 --- a/src/effects/Repeat.h +++ b/src/effects/Repeat.h @@ -11,82 +11,57 @@ #ifndef __AUDACITY_EFFECT_REPEAT__ #define __AUDACITY_EFFECT_REPEAT__ +#include +#include +#include +#include + +#include "../ShuttleGui.h" + #include "Effect.h" -#include -#include +#define REPEAT_PLUGIN_SYMBOL wxTRANSLATE("Repeat") -class wxString; -class wxStaticText; -class wxTextCtrl; - -class WaveTrack; - -class EffectRepeat:public Effect +class EffectRepeat : public Effect { - friend class RepeatDialog; - - public: +public: EffectRepeat(); + virtual ~EffectRepeat(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Repeat...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://audacityteam.org/namespace#TimelineChanger")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Repeat")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Performing Repeat")); - } + virtual EffectType GetType(); - // Useful only after PromptUser values have been set. - virtual wxString GetEffectDescription(); + // EffectClientInterface implementation - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + + // Effect implementation virtual bool Process(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); - private: - int repeatCount; -}; - -class RepeatDialog:public EffectDialog { - public: - // constructors and destructors - RepeatDialog(EffectRepeat *effect, wxWindow * parent); - - // method declarations - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - - private: - // handlers - void OnRepeatTextChange(wxCommandEvent & event); - void OnPreview( wxCommandEvent &event ); +private: + // EffectRepeat implementation + void OnRepeatTextChange(wxCommandEvent & evt); void DisplayNewTime(); - private: - EffectRepeat *mEffect; +private: + int repeatCount; + wxTextCtrl *mRepeatCount; wxStaticText *mTotalTime; - DECLARE_EVENT_TABLE() - - public: - int repeatCount; - int maxCount; - double selectionTimeSecs; + DECLARE_EVENT_TABLE(); }; #endif diff --git a/src/effects/Reverb.cpp b/src/effects/Reverb.cpp index c007fddd8..a11c5b595 100644 --- a/src/effects/Reverb.cpp +++ b/src/effects/Reverb.cpp @@ -12,574 +12,518 @@ \class EffectReverb \brief A reverberation effect -*//****************************************************************//** - -\class ReverbDialogue -\brief A configuration class used by effect, EffectReverb. - *//*******************************************************************/ #include "../Audacity.h" -#include "Reverb.h" -#include "Reverb_libSoX.h" + +#include +#include + +#include "../Audacity.h" #include "../Prefs.h" +#include "../widgets/valnum.h" + +#include "Reverb_libSoX.h" +#include "Reverb.h" + +enum +{ + ID_RoomSize = 10000, + ID_PreDelay, + ID_Reverberance, + ID_HfDamping, + ID_ToneLow, + ID_ToneHigh, + ID_WetGain, + ID_DryGain, + ID_StereoWidth, + ID_WetOnly +}; + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( RoomSize, double, wxTRANSLATE("RoomSize"), 75, 0, 100, 1 ); +Param( PreDelay, double, wxTRANSLATE("Delay"), 10, 0, 200, 1 ); +Param( Reverberance, double, wxTRANSLATE("Reverberance"), 50, 0, 100, 1 ); +Param( HfDamping, double, wxTRANSLATE("HfDamping"), 50, 0, 100, 1 ); +Param( ToneLow, double, wxTRANSLATE("ToneLow"), 100, 0, 100, 1 ); +Param( ToneHigh, double, wxTRANSLATE("ToneHigh"), 100, 0, 100, 1 ); +Param( WetGain, double, wxTRANSLATE("WetGain"), -1, -20, 10, 1 ); +Param( DryGain, double, wxTRANSLATE("DryGain"), -1, -20, 10, 1 ); +Param( StereoWidth, double, wxTRANSLATE("StereoWidth"), 100, 0, 100, 1 ); +Param( WetOnly, bool, wxTRANSLATE("WetOnly"), false, false, true, 1 ); + +static const struct +{ + const wxChar *name; + EffectReverb::Params params; +} +FactoryPresets[] = +{ + // Room Pre Hf Tone Tone Wet Dry Stereo Wet + // Name Size, Delay, Reverb, Damping, Low, High, Gain, Gain, Width, Only + wxTRANSLATE("Vocal I" ), { 70, 20, 40, 99, 100, 50, -12, 0, 70, false }, + wxTRANSLATE("Vocal II"), { 50, 0, 50, 99, 50, 100, -1, -1, 70, false }, + wxTRANSLATE("Bathroom"), { 16, 8, 80, 0, 0, 100, -6, 0, 100, false }, + wxTRANSLATE("Small Room Bright"), { 30, 10, 50, 50, 50, 100, -1, -1, 100, false }, + wxTRANSLATE("Small Room Dark"), { 30, 10, 50, 50, 100, 0, -1, -1, 100, false }, + wxTRANSLATE("Medium Room"), { 75, 10, 40, 50, 100, 70, -1, -1, 70, false }, + wxTRANSLATE("Large Room"), { 85, 10, 40, 50, 100, 80, 0, -6, 90, false }, + wxTRANSLATE("Church Hall"), { 90, 32, 60, 50, 100, 50, 0, -12, 100, false }, + wxTRANSLATE("Cathedral"), { 90, 16, 90, 50, 100, 0, 0, -20, 100, false }, +}; + +struct Reverb_priv_t +{ + reverb_t reverb; + float *dry; + float *wet[2]; +}; // // EffectReverb // -struct Reverb_priv_t { - size_t ichannels, ochannels; - struct { - reverb_t reverb; - float * dry, * wet[2]; - } chan[2]; -}; +BEGIN_EVENT_TABLE(EffectReverb, wxEvtHandler) -void EffectReverb::Create(double rate, bool isStereo) -{ - mP = (Reverb_priv_t *)calloc(sizeof *mP, 1); - size_t i; +#define SpinSliderEvent(n) \ + EVT_SLIDER(ID_ ## n, EffectReverb::On ## n ## Slider) \ + EVT_TEXT(ID_ ## n, EffectReverb::On ## n ## Text) -#define BLOCK 0x4000u - mP->ichannels = mP->ochannels = 1 + isStereo; - for (i = 0; i < mP->ichannels; ++i) - reverb_create( - &mP->chan[i].reverb, rate, mParams.mWetGain, mParams.mRoomSize, - mParams.mReverberance, mParams.mHfDamping, mParams.mDelay, mParams.mStereoWidth*isStereo, - mParams.mToneLow, mParams.mToneHigh, BLOCK, mP->chan[i].wet); -} + SpinSliderEvent(RoomSize) + SpinSliderEvent(PreDelay) + SpinSliderEvent(Reverberance) + SpinSliderEvent(HfDamping) + SpinSliderEvent(ToneLow) + SpinSliderEvent(ToneHigh) + SpinSliderEvent(WetGain) + SpinSliderEvent(DryGain) + SpinSliderEvent(StereoWidth) -bool EffectReverb::ProcessOneBlock(sampleCount len0, float * const * chans0) -{ - float * chans[2]; - chans[0] = chans0[0], chans[1] = chans0[1]; - size_t c, i, w, len = len0; - float const dryMult(mParams.mWetOnly? 0 : dB_to_linear(mParams.mDryGain)); - while (len) { - size_t len1 = min(len, (size_t)BLOCK); - for (c = 0; c < mP->ichannels; ++c) { - mP->chan[c].dry = (float *)fifo_write(&mP->chan[c].reverb.input_fifo, len1, chans[c]); - reverb_process(&mP->chan[c].reverb, len1); - } - if (mP->ichannels == 2) - for (i = 0; i < len1; ++i) - for (w = 0; w < 2; ++w) - chans[w][i] = dryMult * mP->chan[w].dry[i] + - .5 * (mP->chan[0].wet[w][i] + mP->chan[1].wet[w][i]); - else for (i = 0; i < len1; ++i) - for (w = 0; w < mP->ochannels; ++w) - chans[0][i] = dryMult * mP->chan[0].dry[i] + mP->chan[0].wet[w][i]; - len -= len1; - for (c = 0; c < mP->ichannels; chans[c++] += len1); - } - return true; -} +#undef SpinSliderEvent -void EffectReverb::Delete() -{ - for (size_t i = 0; i < mP->ichannels; reverb_delete(&mP->chan[i++].reverb)); - free(mP); -} - -// Most of what follows should really be provided by Audacity framework classes: -//vvv Not sure what you mean by that, Rob. A la EffectSimpleMono, e.g.? - -bool EffectReverb::ProcessOneTrack(size_t n, WaveTrack * track, WaveTrack * track2, wxString const & msg) -{ - sampleCount begin = track->TimeToLongSamples(mCurT0), pos = begin; - sampleCount end = track->TimeToLongSamples(mCurT1); - float * buffers[2]; - buffers[0] = new float[track->GetMaxBlockSize()]; - buffers[1] = track2? new float[track->GetMaxBlockSize()] : 0; - bool cancelled = false; - - Create(track->GetRate(), track2 != 0); - while (!cancelled && pos < end) { - sampleCount block = track->GetBestBlockSize(pos); - block = min(block, end - pos); - track->Get((samplePtr) buffers[0], floatSample, pos, block); - if (track2) - track2->Get((samplePtr) buffers[1], floatSample, pos, block); - ProcessOneBlock(block, buffers); - track->Set((samplePtr) buffers[0], floatSample, pos, block); - if (track2) - track2->Set((samplePtr) buffers[1], floatSample, pos, block); - pos += block; - cancelled = TrackProgress(n, (1. * pos - begin) / (1. * end - begin) * .5 + .5, msg); - } - Delete(); - delete[] buffers[0]; - delete[] buffers[1]; - return !cancelled; -} - -bool EffectReverb::Process() -{ - CopyInputTracks(); // Set up mOutputTracks. - SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks); - WaveTrack * track = (WaveTrack *)iter.First(); - bool success = true; - for (int n = 0; success && track; track = (WaveTrack *)iter.Next(), ++n) { - mCurT0 = max(mT0, track->GetStartTime()); - mCurT1 = min(mT1, track->GetEndTime()); - if (mCurT1 > mCurT0) { - wxString msg(_("Processing: ") + track->GetName()); - if (track->GetLinked() && mParams.mStereoWidth) // do stereo? - success = ProcessOneTrack(++n, track, (WaveTrack *)iter.Next(), msg); - else success = ProcessOneTrack(n, track, 0, msg); - } - } - ReplaceProcessedTracks(success); - return success; -} - -wxString EffectReverb::SettingsPath(int settingsNumber) const -{ - wxString x(wxString::FromAscii("/Effects/Reverb/")); - if (settingsNumber >= 0) - x += wxString::Format(wxString::FromAscii("%i/"), settingsNumber); - return x; -} - -wxString EffectReverb::SettingsName(int settingsNumber) const -{ - wxString x(SettingsPath(settingsNumber)); - gPrefs->Read(x + wxT("name"), &x, wxString::Format(wxString::FromAscii("Settings%i"), settingsNumber)); - return x; -} - -void EffectReverb::LoadSettings(int settingsNumber, EffectReverb::Params & params) -{ - wxString sSettingsPath(SettingsPath(settingsNumber)); - gPrefs->Read(sSettingsPath + wxT("RoomSize"), ¶ms.mRoomSize, 75); - gPrefs->Read(sSettingsPath + wxT("Delay"), ¶ms.mDelay, 10); - gPrefs->Read(sSettingsPath + wxT("Reverberance"), ¶ms.mReverberance, 50); - gPrefs->Read(sSettingsPath + wxT("HfDamping"), ¶ms.mHfDamping, 50); - gPrefs->Read(sSettingsPath + wxT("ToneLow"), ¶ms.mToneLow, 100); - gPrefs->Read(sSettingsPath + wxT("ToneHigh"), ¶ms.mToneHigh, 100); - gPrefs->Read(sSettingsPath + wxT("WetGain"), ¶ms.mWetGain, -1); - gPrefs->Read(sSettingsPath + wxT("DryGain"), ¶ms.mDryGain, -1); - gPrefs->Read(sSettingsPath + wxT("StereoWidth"), ¶ms.mStereoWidth, 100); - gPrefs->Read(sSettingsPath + wxT("WetOnly"), ¶ms.mWetOnly, 0); -} - -void EffectReverb::SaveSettings(int settingsNumber, EffectReverb::Params const * params, wxString const * name) const -{ - wxString sSettingsPath(SettingsPath(settingsNumber)); - if (name) - gPrefs->Write(sSettingsPath + wxT("name"), *name); - if (params) - { - gPrefs->Write(sSettingsPath + wxT("RoomSize"), params->mRoomSize); - gPrefs->Write(sSettingsPath + wxT("Delay"), params->mDelay); - gPrefs->Write(sSettingsPath + wxT("Reverberance"), params->mReverberance); - gPrefs->Write(sSettingsPath + wxT("HfDamping"), params->mHfDamping); - gPrefs->Write(sSettingsPath + wxT("ToneLow"), params->mToneLow); - gPrefs->Write(sSettingsPath + wxT("ToneHigh"), params->mToneHigh); - gPrefs->Write(sSettingsPath + wxT("WetGain"), params->mWetGain); - gPrefs->Write(sSettingsPath + wxT("DryGain"), params->mDryGain); - gPrefs->Write(sSettingsPath + wxT("StereoWidth"), params->mStereoWidth); - gPrefs->Write(sSettingsPath + wxT("WetOnly"), params->mWetOnly); - } - gPrefs->Flush(); -} +END_EVENT_TABLE() EffectReverb::EffectReverb() { - LoadSettings(-1, mParams); + mParams.mRoomSize = DEF_RoomSize; + mParams.mPreDelay = DEF_PreDelay; + mParams.mReverberance = DEF_Reverberance; + mParams.mHfDamping = DEF_HfDamping; + mParams.mToneLow = DEF_ToneLow; + mParams.mToneHigh = DEF_ToneHigh; + mParams.mWetGain = DEF_WetGain; + mParams.mDryGain = DEF_DryGain; + mParams.mStereoWidth = DEF_StereoWidth; + mParams.mWetOnly = DEF_WetOnly; + + mProcessingEvent = false; } -wxString EffectReverb::GetEffectDescription() +EffectReverb::~EffectReverb() { - wxString strResult = - wxString::Format(_("Applied effect: %s"), GetEffectName().c_str()); - strResult += wxString::Format(_(", Room Size = %.0f"), mParams.mRoomSize); - strResult += wxString::Format(_(", Delay = %.0fms"), mParams.mDelay); - strResult += wxString::Format(_(", Reverberance = %.0f%%"), mParams.mReverberance); - strResult += wxString::Format(_(", Damping = %.0f%%"), mParams.mHfDamping); - strResult += wxString::Format(_(", Tone Low = %.0f%%"), mParams.mToneLow); - strResult += wxString::Format(_(", Tone High = %.0f%%"), mParams.mToneHigh); - strResult += wxString::Format(_(", Wet Gain = %.0fdB"), mParams.mWetGain); - strResult += wxString::Format(_(", Dry Gain Size = %.0fdB"), mParams.mDryGain); - strResult += wxString::Format(_(", Stereo Width = %.0f%%"), mParams.mStereoWidth); - strResult += wxString::Format(_(", Wet Only = %s"), mParams.mWetOnly ? _("true") : _("false")); - return strResult; } -bool EffectReverb::PromptUser() +// IdentInterface implementation + +wxString EffectReverb::GetSymbol() { - ReverbDialogue d(this, mParent); - d.CentreOnParent(); - d.ShowModal(); - if (d.GetReturnCode() == wxID_CANCEL) + return REVERB_PLUGIN_SYMBOL; +} + +wxString EffectReverb::GetDescription() +{ + return wxTRANSLATE("Adds ambience or a \"hall effect\""); +} + +// EffectIdentInterface implementation + +EffectType EffectReverb::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +int EffectReverb::GetAudioInCount() +{ + return 2; +} + +int EffectReverb::GetAudioOutCount() +{ + return 2; +} + +#define BLOCK 16384 + +bool EffectReverb::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames chanMap) +{ + bool isStereo = false; + mNumChans = 1; + if (chanMap && chanMap[0] != ChannelNameEOL && chanMap[1] == ChannelNameFrontRight) + { + isStereo = true; + mNumChans = 2; + } + + mP = (Reverb_priv_t *) calloc(sizeof(*mP), mNumChans); + + for (int i = 0; i < mNumChans; i++) + { + reverb_create(&mP[i].reverb, + mSampleRate, + mParams.mWetGain, + mParams.mRoomSize, + mParams.mReverberance, + mParams.mHfDamping, + mParams.mPreDelay, + mParams.mStereoWidth * (isStereo ? 1 : 0), + mParams.mToneLow, + mParams.mToneHigh, + BLOCK, + mP[i].wet); + } + + return true; +} + +bool EffectReverb::ProcessFinalize() +{ + for (int i = 0; i < mNumChans; i++) + { + reverb_delete(&mP[i].reverb); + } + + free(mP); + + return true; +} + +sampleCount EffectReverb::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) +{ + float *ichans[2] = {NULL, NULL}; + float *ochans[2] = {NULL, NULL}; + + for (int c = 0; c < mNumChans; c++) + { + ichans[c] = inBlock[c]; + ochans[c] = outBlock[c]; + } + + float const dryMult = mParams.mWetOnly ? 0 : dB_to_linear(mParams.mDryGain); + + sampleCount remaining = blockLen; + + while (remaining) + { + size_t len = min((size_t) remaining, (size_t) BLOCK); + for (int c = 0; c < mNumChans; c++) + { + // Write the input samples to the reverb fifo. Returned value is the address of the + // fifo buffer which contains a copy of the input samples. + mP[c].dry = (float *) fifo_write(&mP[c].reverb.input_fifo, len, ichans[c]); + reverb_process(&mP[c].reverb, len); + } + + if (mNumChans == 2) + { + for (sampleCount i = 0; i < len; i++) + { + for (int w = 0; w < 2; w++) + { + ochans[w][i] = dryMult * + mP[w].dry[i] + + 0.5 * + (mP[0].wet[w][i] + mP[1].wet[w][i]); + } + } + } + else + { + for (sampleCount i = 0; i < len; i++) + { + ochans[0][i] = dryMult * + mP[0].dry[i] + + mP[0].wet[0][i]; + } + } + + remaining -= len; + + for (int c = 0; c < mNumChans; c++) + { + ichans[c] += len; + ochans[c] += len; + } + } + + return blockLen; +} + +bool EffectReverb::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_RoomSize, mParams.mRoomSize); + parms.Write(KEY_PreDelay, mParams.mPreDelay); + parms.Write(KEY_Reverberance, mParams.mReverberance); + parms.Write(KEY_HfDamping, mParams.mHfDamping); + parms.Write(KEY_ToneLow, mParams.mToneLow); + parms.Write(KEY_ToneHigh, mParams.mToneHigh); + parms.Write(KEY_WetGain, mParams.mWetGain); + parms.Write(KEY_DryGain, mParams.mDryGain); + parms.Write(KEY_StereoWidth, mParams.mStereoWidth); + parms.Write(KEY_WetOnly, mParams.mWetOnly); + + return true; +} + +bool EffectReverb::SetAutomationParameters(EffectAutomationParameters & parms) +{ + ReadAndVerifyDouble(RoomSize); + ReadAndVerifyDouble(PreDelay); + ReadAndVerifyDouble(Reverberance); + ReadAndVerifyDouble(HfDamping); + ReadAndVerifyDouble(ToneLow); + ReadAndVerifyDouble(ToneHigh); + ReadAndVerifyDouble(WetGain); + ReadAndVerifyDouble(DryGain); + ReadAndVerifyDouble(StereoWidth); + ReadAndVerifyBool(WetOnly); + + mParams.mRoomSize = RoomSize; + mParams.mPreDelay = PreDelay; + mParams.mReverberance = Reverberance; + mParams.mHfDamping = HfDamping; + mParams.mToneLow = ToneLow; + mParams.mToneHigh = ToneHigh; + mParams.mWetGain = WetGain; + mParams.mDryGain = DryGain; + mParams.mStereoWidth = StereoWidth; + mParams.mWetOnly = WetOnly; + + return true; +} + +wxArrayString EffectReverb::GetFactoryPresets() +{ + wxArrayString names; + + for (int i = 0; i < WXSIZEOF(FactoryPresets); i++) + { + names.Add(wxGetTranslation(FactoryPresets[i].name)); + } + + return names; +} + +bool EffectReverb::LoadFactoryPreset(int id) +{ + if (id < 0 || id >= (int) WXSIZEOF(FactoryPresets)) + { return false; - SaveSettings(-1, &mParams); - return true; -} + } -bool EffectReverb::TransferParameters(Shuttle & shuttle) -{ - shuttle.TransferDouble(wxT("RoomSize"), mParams.mRoomSize, 75); - shuttle.TransferDouble(wxT("Delay"), mParams.mDelay, 10); - shuttle.TransferDouble(wxT("Reverberance"), mParams.mReverberance, 50); - shuttle.TransferDouble(wxT("HfDamping"), mParams.mHfDamping, 50); - shuttle.TransferDouble(wxT("ToneLow"), mParams.mToneLow, 100); - shuttle.TransferDouble(wxT("ToneHigh"), mParams.mToneHigh, 100); - shuttle.TransferDouble(wxT("WetGain"), mParams.mWetGain, -1); - shuttle.TransferDouble(wxT("DryGain"), mParams.mDryGain, -1); - shuttle.TransferDouble(wxT("StereoWidth"), mParams.mStereoWidth, 100); + mParams = FactoryPresets[id].params; - shuttle.TransferBool(wxT("WetOnly"), mParams.mWetOnly, 0); + if (mUIDialog) + { + TransferDataToWindow(); + } return true; } -//---------------------------------------------------------------------------- -// ReverbDialogue -//---------------------------------------------------------------------------- +// Effect implementation -#include -#include -#include -#include -#include -#include - -enum {ID_START = 10000, - ID_PRESETS, - ID_LOAD_SETTINGS, - ID_SAVE_SETTINGS, - ID_RENAME_SETTINGS, - - ID_RoomSize_TEXT, ID_RoomSize_WIDGET, - ID_Delay_TEXT, ID_Delay_WIDGET, - ID_Reverberance_TEXT, ID_Reverberance_WIDGET, - ID_HfDamping_TEXT, ID_HfDamping_WIDGET, - ID_ToneLow_TEXT, ID_ToneLow_WIDGET, - ID_ToneHigh_TEXT, ID_ToneHigh_WIDGET, - ID_WetGain_TEXT, ID_WetGain_WIDGET, - ID_DryGain_TEXT, ID_DryGain_WIDGET, - ID_StereoWidth_TEXT, ID_StereoWidth_WIDGET, - - ID_WetOnly_WIDGET, - - ID_END}; - -BEGIN_EVENT_TABLE(ReverbDialogue, EffectDialog) - EVT_SLIDER(ID_RoomSize_WIDGET, ReverbDialogue::OnRoomSizeWidget) - EVT_TEXT(ID_RoomSize_TEXT, ReverbDialogue::OnRoomSizeText) - - EVT_SLIDER(ID_Delay_WIDGET, ReverbDialogue::OnDelayWidget) - EVT_TEXT(ID_Delay_TEXT, ReverbDialogue::OnDelayText) - - EVT_SLIDER(ID_Reverberance_WIDGET, ReverbDialogue::OnReverberanceWidget) - EVT_TEXT(ID_Reverberance_TEXT, ReverbDialogue::OnReverberanceText) - - EVT_SLIDER(ID_HfDamping_WIDGET, ReverbDialogue::OnHfDampingWidget) - EVT_TEXT(ID_HfDamping_TEXT, ReverbDialogue::OnHfDampingText) - - EVT_SLIDER(ID_ToneLow_WIDGET, ReverbDialogue::OnToneLowWidget) - EVT_TEXT(ID_ToneLow_TEXT, ReverbDialogue::OnToneLowText) - - EVT_SLIDER(ID_ToneHigh_WIDGET, ReverbDialogue::OnToneHighWidget) - EVT_TEXT(ID_ToneHigh_TEXT, ReverbDialogue::OnToneHighText) - - EVT_SLIDER(ID_WetGain_WIDGET, ReverbDialogue::OnWetGainWidget) - EVT_TEXT(ID_WetGain_TEXT, ReverbDialogue::OnWetGainText) - - EVT_SLIDER(ID_DryGain_WIDGET, ReverbDialogue::OnDryGainWidget) - EVT_TEXT(ID_DryGain_TEXT, ReverbDialogue::OnDryGainText) - - EVT_SLIDER(ID_StereoWidth_WIDGET, ReverbDialogue::OnStereoWidthWidget) - EVT_TEXT(ID_StereoWidth_TEXT, ReverbDialogue::OnStereoWidthText) - - EVT_BUTTON(ePreviewID, ReverbDialogue::OnPreview) - EVT_BUTTON(ePreviewDryID, ReverbDialogue::OnPreview) - EVT_BUTTON(ID_PRESETS, ReverbDialogue::LoadPreset) - EVT_BUTTON(ID_SAVE_SETTINGS, ReverbDialogue::SaveSettings) - EVT_BUTTON(ID_LOAD_SETTINGS, ReverbDialogue::LoadSettings) - EVT_BUTTON(ID_RENAME_SETTINGS, ReverbDialogue::RenameSettings) -END_EVENT_TABLE() - -ReverbDialogue::ReverbDialogue(EffectReverb * effect, wxWindow * parent): - EffectDialog(parent, _("Reverb"), PROCESS_EFFECT, wxDEFAULT_DIALOG_STYLE, ePreviewDryButton), - mEffect(*effect), mParams(effect->mParams) +bool EffectReverb::Startup() { - Init(); + wxString base = wxT("/Effects/Reverb/"); + + // Migrate settings from 2.1.0 or before + + // Already migrated, so bail + if (gPrefs->Exists(base + wxT("Migrated"))) + { + return true; + } + + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + gPrefs->Read(base + wxT("RoomSize"), &mParams.mRoomSize, DEF_RoomSize); + gPrefs->Read(base + wxT("Delay"), &mParams.mPreDelay, DEF_PreDelay); + gPrefs->Read(base + wxT("Reverberance"), &mParams.mReverberance, DEF_Reverberance); + gPrefs->Read(base + wxT("HfDamping"), &mParams.mHfDamping, DEF_HfDamping); + gPrefs->Read(base + wxT("ToneLow"), &mParams.mToneLow, DEF_ToneLow); + gPrefs->Read(base + wxT("ToneHigh"), &mParams.mToneHigh, DEF_ToneHigh); + gPrefs->Read(base + wxT("WetGain"), &mParams.mWetGain, DEF_WetGain); + gPrefs->Read(base + wxT("DryGain"), &mParams.mDryGain, DEF_DryGain); + gPrefs->Read(base + wxT("StereoWidth"), &mParams.mStereoWidth, DEF_StereoWidth); + gPrefs->Read(base + wxT("WetOnly"), &mParams.mWetOnly, DEF_WetOnly); + + SaveUserPreset(GetCurrentSettingsGroup()); + + // Do not migrate again + gPrefs->Write(base + wxT("Migrated"), true); + } + + // Load the previous user presets + for (int i = 0; i < 10; i++) + { + wxString path = base + wxString::Format(wxT("%d/"), i); + if (gPrefs->Exists(path)) + { + Params save = mParams; + wxString name; + + gPrefs->Read(path + wxT("RoomSize"), &mParams.mRoomSize, DEF_RoomSize); + gPrefs->Read(path + wxT("Delay"), &mParams.mPreDelay, DEF_PreDelay); + gPrefs->Read(path + wxT("Reverberance"), &mParams.mReverberance, DEF_Reverberance); + gPrefs->Read(path + wxT("HfDamping"), &mParams.mHfDamping, DEF_HfDamping); + gPrefs->Read(path + wxT("ToneLow"), &mParams.mToneLow, DEF_ToneLow); + gPrefs->Read(path + wxT("ToneHigh"), &mParams.mToneHigh, DEF_ToneHigh); + gPrefs->Read(path + wxT("WetGain"), &mParams.mWetGain, DEF_WetGain); + gPrefs->Read(path + wxT("DryGain"), &mParams.mDryGain, DEF_DryGain); + gPrefs->Read(path + wxT("StereoWidth"), &mParams.mStereoWidth, DEF_StereoWidth); + gPrefs->Read(path + wxT("WetOnly"), &mParams.mWetOnly, DEF_WetOnly); + gPrefs->Read(path + wxT("name"), &name, wxEmptyString); + + if (!name.IsEmpty()) + { + name.Prepend(wxT(" - ")); + } + name.Prepend(wxString::Format(wxT("Settings%d"), i)); + + SaveUserPreset(GetUserPresetsGroup(name)); + + mParams = save; + } + } + + return true; } -void ReverbDialogue::PopulateOrExchange(ShuttleGui & s) +void EffectReverb::PopulateOrExchange(ShuttleGui & S) { - s.AddSpace(0, 5); + S.AddSpace(0, 5); - s.StartMultiColumn(3, wxEXPAND); + S.StartMultiColumn(3, wxEXPAND); { - s.SetStretchyCol(2); + S.SetStretchyCol(2); - mRoomSizeText = s.Id(ID_RoomSize_TEXT).AddSpinCtrl(_("&Room Size (%):"), 0, 100, 0); - s.SetStyle(wxSL_HORIZONTAL); - mRoomSizeWidget = s.Id(ID_RoomSize_WIDGET).AddSlider(wxT(""), 0, 100, 0); +#define SpinSlider(n, p) \ + m ## n ## T = S.Id(ID_ ## n). \ + AddSpinCtrl( p, DEF_ ## n, MAX_ ## n, MIN_ ## n); \ + S.SetStyle(wxSL_HORIZONTAL); \ + m ## n ## S = S.Id(ID_ ## n). \ + AddSlider(wxT(""), DEF_ ## n, MAX_ ## n, MIN_ ## n); - // Rob's original code referred to this param as "Delay". - // Then, May 11, 2013, in a thread on [Audacity-quality], subject "Reverb effect", - // Steve suggested and Gale seconded renaming it "Pre-delay". - // I've changed it only here, in the GUI, and left the rest of the code as *Delay*. - mDelayText = s.Id(ID_Delay_TEXT).AddSpinCtrl(_("&Pre-delay (ms):"), 0, 200, 0); - s.SetStyle(wxSL_HORIZONTAL); - mDelayWidget = s.Id(ID_Delay_WIDGET).AddSlider(wxT(""), 0, 200, 0); + SpinSlider(RoomSize, _("&Room Size (%):")); + SpinSlider(PreDelay, _("&Pre-delay (ms):")); + SpinSlider(Reverberance, _("Rever&berance (%):")); + SpinSlider(HfDamping, _("Da&mping (%):")); + SpinSlider(ToneLow, _("Tone &Low (%):")); + SpinSlider(ToneHigh, _("Tone &High (%):")); + SpinSlider(WetGain, _("Wet &Gain (dB):")); + SpinSlider(DryGain, _("Dr&y Gain (dB):")); + SpinSlider(StereoWidth, _("Stereo Wid&th (%):")); - mReverberanceText = s.Id(ID_Reverberance_TEXT).AddSpinCtrl(_("Rever&berance (%):"), 0, 100, 0); - s.SetStyle(wxSL_HORIZONTAL); - mReverberanceWidget = s.Id(ID_Reverberance_WIDGET).AddSlider(wxT(""), 0, 100, 0); +#undef SpinSlider - mHfDampingText = s.Id(ID_HfDamping_TEXT).AddSpinCtrl(_("Da&mping (%):"), 0, 100, 0); - s.SetStyle(wxSL_HORIZONTAL); - mHfDampingWidget = s.Id(ID_HfDamping_WIDGET).AddSlider(wxT(""), 0, 100, 0); - - mToneLowText = s.Id(ID_ToneLow_TEXT).AddSpinCtrl(_("Tone &Low (%):"), 0, 100, 0); - s.SetStyle(wxSL_HORIZONTAL); - mToneLowWidget = s.Id(ID_ToneLow_WIDGET).AddSlider(wxT(""), 0, 100, 0); - - mToneHighText = s.Id(ID_ToneHigh_TEXT).AddSpinCtrl(_("Tone &High (%):"), 0, 100, 0); - s.SetStyle(wxSL_HORIZONTAL); - mToneHighWidget = s.Id(ID_ToneHigh_WIDGET).AddSlider(wxT(""), 0, 100, 0); - - mWetGainText = s.Id(ID_WetGain_TEXT).AddSpinCtrl(_("Wet &Gain (dB):"), 0, 10, -20); - s.SetStyle(wxSL_HORIZONTAL); - mWetGainWidget = s.Id(ID_WetGain_WIDGET).AddSlider(wxT(""), 0, 10, -20); - - mDryGainText = s.Id(ID_DryGain_TEXT).AddSpinCtrl(_("Dr&y Gain (dB):"), 0, 10, -20); - s.SetStyle(wxSL_HORIZONTAL); - mDryGainWidget = s.Id(ID_DryGain_WIDGET).AddSlider(wxT(""), 0, 10, -20); - - mStereoWidthText = s.Id(ID_StereoWidth_TEXT).AddSpinCtrl(_("Stereo Wid&th (%):"), 0, 100, 0); - s.SetStyle(wxSL_HORIZONTAL); - mStereoWidthWidget = s.Id(ID_StereoWidth_WIDGET).AddSlider(wxT(""), 0, 100, 0); } - s.EndMultiColumn(); + S.EndMultiColumn(); - s.StartHorizontalLay(wxCENTER, false); + S.StartHorizontalLay(wxCENTER, false); { - mWetOnlyWidget = s.Id(ID_WetOnly_WIDGET).AddCheckBox(_("Wet O&nly"), wxT("false")); + mWetOnlyC = S.Id(ID_WetOnly). + AddCheckBox(_("Wet O&nly"), DEF_WetOnly ? wxT("true") : wxT("false")); } - s.EndHorizontalLay(); + S.EndHorizontalLay(); - s.StartHorizontalLay(wxCENTER); { - s.StartStatic(_("Presets:")); { - s.Id(ID_PRESETS), s.AddButton(_("Lo&ad")); - } s.EndStatic(); - s.StartStatic(_("User settings:")); { - s.StartHorizontalLay(wxCENTER); { - s.Id(ID_LOAD_SETTINGS), s.AddButton(_("Loa&d")); - s.Id(ID_SAVE_SETTINGS), s.AddButton(_("&Save")); - s.Id(ID_RENAME_SETTINGS), s.AddButton(_("R&ename")); - } s.EndHorizontalLay(); - } s.EndStatic(); - } s.EndHorizontalLay(); return; } -bool ReverbDialogue::TransferDataToWindow() +bool EffectReverb::TransferDataToWindow() { - mRoomSizeWidget->SetValue(int(mParams.mRoomSize)); - mRoomSizeText->SetValue(wxString::Format(wxT("%d"), int(mParams.mRoomSize))); +#define SetSpinSlider(n) \ + m ## n ## S->SetValue((int) mParams.m ## n); \ + m ## n ## T->SetValue(wxString::Format(wxT("%d"), (int) mParams.m ## n)); - mDelayWidget->SetValue(int(mParams.mDelay)); - mDelayText->SetValue(wxString::Format(wxT("%d"), int(mParams.mDelay))); + SetSpinSlider(RoomSize); + SetSpinSlider(PreDelay); + SetSpinSlider(Reverberance); + SetSpinSlider(HfDamping); + SetSpinSlider(ToneLow); + SetSpinSlider(ToneHigh); + SetSpinSlider(WetGain); + SetSpinSlider(DryGain); + SetSpinSlider(StereoWidth); - mReverberanceWidget->SetValue(int(mParams.mReverberance)); - mReverberanceText->SetValue(wxString::Format(wxT("%d"), int(mParams.mReverberance))); +#undef SetSpinSlider - mHfDampingWidget->SetValue(int(mParams.mHfDamping)); - mHfDampingText->SetValue(wxString::Format(wxT("%d"), int(mParams.mHfDamping))); - - mToneLowWidget->SetValue(int(mParams.mToneLow)); - mToneLowText->SetValue(wxString::Format(wxT("%d"), int(mParams.mToneLow))); - - mToneHighWidget->SetValue(int(mParams.mToneHigh)); - mToneHighText->SetValue(wxString::Format(wxT("%d"), int(mParams.mToneHigh))); - - mWetGainWidget->SetValue(int(mParams.mWetGain)); - mWetGainText->SetValue(wxString::Format(wxT("%d"), int(mParams.mWetGain))); - - mDryGainWidget->SetValue(int(mParams.mDryGain)); - mDryGainText->SetValue(wxString::Format(wxT("%d"), int(mParams.mDryGain))); - - mStereoWidthWidget->SetValue(int(mParams.mStereoWidth)); - mStereoWidthText->SetValue(wxString::Format(wxT("%d"), int(mParams.mStereoWidth))); - - mWetOnlyWidget->SetValue(int(mParams.mWetOnly)); + mWetOnlyC->SetValue((int) mParams.mWetOnly); return true; } -bool ReverbDialogue::TransferDataFromWindow() +bool EffectReverb::TransferDataFromWindow() { - mParams.mRoomSize = mRoomSizeWidget->GetValue(); - mParams.mDelay = mDelayWidget->GetValue(); - mParams.mReverberance = mReverberanceWidget->GetValue(); - mParams.mHfDamping = mHfDampingWidget->GetValue(); - mParams.mToneLow = mToneLowWidget->GetValue(); - mParams.mToneHigh = mToneHighWidget->GetValue(); - mParams.mWetGain = mWetGainWidget->GetValue(); - mParams.mDryGain = mDryGainWidget->GetValue(); - mParams.mStereoWidth = mStereoWidthWidget->GetValue(); - mParams.mWetOnly = mWetOnlyWidget->GetValue(); + if (!mUIParent->Validate()) + { + return false; + } + + mParams.mRoomSize = mRoomSizeS->GetValue(); + mParams.mPreDelay = mPreDelayS->GetValue(); + mParams.mReverberance = mReverberanceS->GetValue(); + mParams.mHfDamping = mHfDampingS->GetValue(); + mParams.mToneLow = mToneLowS->GetValue(); + mParams.mToneHigh = mToneHighS->GetValue(); + mParams.mWetGain = mWetGainS->GetValue(); + mParams.mDryGain = mDryGainS->GetValue(); + mParams.mStereoWidth = mStereoWidthS->GetValue(); + mParams.mWetOnly = mWetOnlyC->GetValue(); + return true; } -void ReverbDialogue::OnRoomSizeText(wxCommandEvent & WXUNUSED(event)) -{ int val = mRoomSizeText->GetValue(); mRoomSizeWidget->SetValue(TrapLong(val, 0, 100)); } -void ReverbDialogue::OnRoomSizeWidget(wxCommandEvent & WXUNUSED(event)) -{ mRoomSizeText->SetValue(wxString::Format(wxT("%d"), mRoomSizeWidget->GetValue())); } - -void ReverbDialogue::OnDelayText(wxCommandEvent & WXUNUSED(event)) -{ int val = mDelayText->GetValue(); mDelayWidget->SetValue(TrapLong(val, 0, 200)); } -void ReverbDialogue::OnDelayWidget(wxCommandEvent & WXUNUSED(event)) -{ mDelayText->SetValue(wxString::Format(wxT("%d"), mDelayWidget->GetValue())); } - -void ReverbDialogue::OnReverberanceText(wxCommandEvent & WXUNUSED(event)) -{ int val = mReverberanceText->GetValue(); mReverberanceWidget->SetValue(TrapLong(val, 0, 100)); } -void ReverbDialogue::OnReverberanceWidget(wxCommandEvent & WXUNUSED(event)) -{ mReverberanceText->SetValue(wxString::Format(wxT("%d"), mReverberanceWidget->GetValue())); } - -void ReverbDialogue::OnHfDampingText(wxCommandEvent & WXUNUSED(event)) -{ int val = mHfDampingText->GetValue(); mHfDampingWidget->SetValue(TrapLong(val, 0, 100)); } -void ReverbDialogue::OnHfDampingWidget(wxCommandEvent & WXUNUSED(event)) -{ mHfDampingText->SetValue(wxString::Format(wxT("%d"), mHfDampingWidget->GetValue())); } - -void ReverbDialogue::OnToneLowText(wxCommandEvent & WXUNUSED(event)) -{ int val = mToneLowText->GetValue(); mToneLowWidget->SetValue(TrapLong(val, 0, 100)); } -void ReverbDialogue::OnToneLowWidget(wxCommandEvent & WXUNUSED(event)) -{ mToneLowText->SetValue(wxString::Format(wxT("%d"), mToneLowWidget->GetValue())); } - -void ReverbDialogue::OnToneHighText(wxCommandEvent & WXUNUSED(event)) -{ int val = mToneHighText->GetValue(); mToneHighWidget->SetValue(TrapLong(val, 0, 100)); } -void ReverbDialogue::OnToneHighWidget(wxCommandEvent & WXUNUSED(event)) -{ mToneHighText->SetValue(wxString::Format(wxT("%d"), mToneHighWidget->GetValue())); } - -void ReverbDialogue::OnWetGainText(wxCommandEvent & WXUNUSED(event)) -{ int val = mWetGainText->GetValue(); mWetGainWidget->SetValue(TrapLong(val, -20, 10)); } -void ReverbDialogue::OnWetGainWidget(wxCommandEvent & WXUNUSED(event)) -{ mWetGainText->SetValue(wxString::Format(wxT("%d"), mWetGainWidget->GetValue())); } - -void ReverbDialogue::OnDryGainText(wxCommandEvent & WXUNUSED(event)) -{ int val = mDryGainText->GetValue(); mDryGainWidget->SetValue(TrapLong(val, -20, 10)); } -void ReverbDialogue::OnDryGainWidget(wxCommandEvent & WXUNUSED(event)) -{ mDryGainText->SetValue(wxString::Format(wxT("%d"), mDryGainWidget->GetValue())); } - -void ReverbDialogue::OnStereoWidthText(wxCommandEvent & WXUNUSED(event)) -{ int val = mStereoWidthText->GetValue(); mStereoWidthWidget->SetValue(TrapLong(val, 0, 100)); } -void ReverbDialogue::OnStereoWidthWidget(wxCommandEvent & WXUNUSED(event)) -{ mStereoWidthText->SetValue(wxString::Format(wxT("%d"), mStereoWidthWidget->GetValue())); } - - -static int wxGetChoiceFromUser(wxWindow * parent, wxString const & message, - wxString const & caption, wxArrayString const & choices, -#if wxCHECK_VERSION(3,0,0) - void * * clientData = 0, -#else - char * * clientData = 0, -#endif - long style = wxCHOICEDLG_STYLE, - wxPoint const & pos = wxDefaultPosition) // Home-grown function -{ - wxSingleChoiceDialog d(parent, message, caption, choices, clientData, style, pos); - d.ShowModal(); - return d.GetReturnCode() == wxID_CANCEL? -1 : d.GetSelection(); -} - -void ReverbDialogue::LoadPreset(wxCommandEvent & WXUNUSED(event)) -{ - EffectReverb::Params & p = mParams; - wxString caption(_("Reverb settings")); - wxString message(_("Load preset:")); - wxArrayString choices; - choices.Add(_("Vocal I")); - choices.Add(_("Vocal II")); - choices.Add(_("Bathroom")); - choices.Add(_("Small Room Bright")); - choices.Add(_("Small Room Dark")); - choices.Add(_("Medium Room")); - choices.Add(_("Large Room")); - choices.Add(_("Church Hall")); - choices.Add(_("Cathedral")); - int i(wxGetChoiceFromUser(this, message, caption, choices)); - switch (i) { - case 0: p.mRoomSize=70; p.mHfDamping=99; p.mDelay=20; p.mReverberance=40; p.mToneLow=100; p.mToneHigh=50 ; p.mWetGain=-12; p.mDryGain=0 ; p.mStereoWidth=70 ; break; - case 1: p.mRoomSize=50; p.mHfDamping=99; p.mDelay=0 ; p.mReverberance=50; p.mToneLow=50 ; p.mToneHigh=100; p.mWetGain=-1 ; p.mDryGain=-1 ; p.mStereoWidth=70 ; break; - case 2: p.mRoomSize=16; p.mHfDamping=0 ; p.mDelay=8 ; p.mReverberance=80; p.mToneLow=0 ; p.mToneHigh=100; p.mWetGain=-6 ; p.mDryGain=0 ; p.mStereoWidth=100; break; - case 3: p.mRoomSize=30; p.mHfDamping=50; p.mDelay=10; p.mReverberance=50; p.mToneLow=50 ; p.mToneHigh=100; p.mWetGain=-1 ; p.mDryGain=-1 ; p.mStereoWidth=100; break; - case 4: p.mRoomSize=30; p.mHfDamping=50; p.mDelay=10; p.mReverberance=50; p.mToneLow=100; p.mToneHigh=0 ; p.mWetGain=-1 ; p.mDryGain=-1 ; p.mStereoWidth=100; break; - case 5: p.mRoomSize=75; p.mHfDamping=50; p.mDelay=10; p.mReverberance=40; p.mToneLow=100; p.mToneHigh=70 ; p.mWetGain=-1 ; p.mDryGain=-1 ; p.mStereoWidth=70 ; break; - case 6: p.mRoomSize=85; p.mHfDamping=50; p.mDelay=10; p.mReverberance=40; p.mToneLow=100; p.mToneHigh=80 ; p.mWetGain=0 ; p.mDryGain=-6 ; p.mStereoWidth=90 ; break; - case 7: p.mRoomSize=90; p.mHfDamping=50; p.mDelay=32; p.mReverberance=60; p.mToneLow=100; p.mToneHigh=50 ; p.mWetGain=0 ; p.mDryGain=-12; p.mStereoWidth=100; break; - case 8: p.mRoomSize=90; p.mHfDamping=50; p.mDelay=16; p.mReverberance=90; p.mToneLow=100; p.mToneHigh=0 ; p.mWetGain=0 ; p.mDryGain=-20; p.mStereoWidth=100; break; - default: return; +#define SpinSliderHandlers(n) \ + void EffectReverb::On ## n ## Slider(wxCommandEvent & evt) \ + { \ + if (mProcessingEvent) return; \ + mProcessingEvent = true; \ + m ## n ## T->SetValue(wxString::Format(wxT("%d"), evt.GetInt())); \ + mProcessingEvent = false; \ + } \ + void EffectReverb::On ## n ## Text(wxCommandEvent & evt) \ + { \ + if (mProcessingEvent) return; \ + mProcessingEvent = true; \ + m ## n ## S->SetValue(TrapLong(evt.GetInt(), MIN_ ## n, MAX_ ## n)); \ + mProcessingEvent = false; \ } - p.mWetOnly=0; - TransferDataToWindow(); - SetTitle(choices[i]); -} -int ReverbDialogue::ChooseSettings(wxString const & message) -{ - wxString caption(_("Reverb settings")); - wxArrayString choices; - for (int i = 0; i < 10; choices.Add(mEffect.SettingsName(i++))); - return wxGetChoiceFromUser(this, message, caption, choices); -} +SpinSliderHandlers(RoomSize); +SpinSliderHandlers(PreDelay); +SpinSliderHandlers(Reverberance); +SpinSliderHandlers(HfDamping); +SpinSliderHandlers(ToneLow); +SpinSliderHandlers(ToneHigh); +SpinSliderHandlers(WetGain); +SpinSliderHandlers(DryGain); +SpinSliderHandlers(StereoWidth); -void ReverbDialogue::SaveSettings(wxCommandEvent & WXUNUSED(event)) -{ - int i(ChooseSettings(_("Save current settings as:"))); - if (i >= 0) { - EffectReverb::Params savedParams(mParams); - TransferDataFromWindow(); - mEffect.SaveSettings(i, &mParams); - mParams = savedParams; - SetTitle(mEffect.SettingsName(i)); - } -} +#undef SpinSliderHandlers -void ReverbDialogue::SetTitle(wxString const & name) +void EffectReverb::SetTitle(const wxString & name) { wxString title(_("Reverb")); - if (name != wxT("")) + + if (!name.IsEmpty()) + { title += wxT(": ") + name; - wxTopLevelWindow::SetTitle(title); -} - -void ReverbDialogue::LoadSettings(wxCommandEvent & WXUNUSED(event)) -{ - int i(ChooseSettings(_("Load settings:"))); - if (i >= 0) { - mEffect.LoadSettings(i, mParams); - TransferDataToWindow(); - SetTitle(mEffect.SettingsName(i)); } -} -void ReverbDialogue::RenameSettings(wxCommandEvent & WXUNUSED(event)) -{ - int i(ChooseSettings(_("Rename settings:"))); - if (i >= 0) { - wxString oldName = mEffect.SettingsName(i); - wxString newName = wxGetTextFromUser(_("Change name to:"), oldName, oldName, this); - if (newName != wxT("")) - mEffect.SaveSettings(i, 0, &newName); - } + mUIDialog->SetTitle(title); } - -void ReverbDialogue::OnPreview(wxCommandEvent & event) -{ - if (event.GetId() == ePreviewID) { - EffectReverb::Params savedParams(mParams); - TransferDataFromWindow(); - mEffect.Preview(); - mParams = savedParams; - } - else mEffect.Preview(true); -} - diff --git a/src/effects/Reverb.h b/src/effects/Reverb.h index d20586252..4444ddafc 100644 --- a/src/effects/Reverb.h +++ b/src/effects/Reverb.h @@ -13,14 +13,16 @@ #define __AUDACITY_EFFECT_REVERB__ #include -#include -#include +#include #include +#include +#include + +#include "../ShuttleGui.h" #include "Effect.h" -class wxSpinCtrl; -class WaveTrack; +#define REVERB_PLUGIN_SYMBOL wxTRANSLATE("Reverb") struct Reverb_priv_t; @@ -28,34 +30,12 @@ class EffectReverb : public Effect { public: EffectReverb(); - virtual ~EffectReverb() {}; + virtual ~EffectReverb(); - // Implemented from the base class 'Effect': - virtual wxString GetEffectName() {return wxTRANSLATE("Reverb...");} - virtual wxString GetEffectAction() {return _("Applying Reverb");} - virtual wxString GetEffectIdentifier() {return wxT("Reverb");} - virtual wxString GetEffectDescription(); // Useful only after PromptUser values have been set. - virtual bool TransferParameters(Shuttle & shuttle); - - protected: - bool PromptUser(); - bool Process(); - - // Processing: - void Create(double rate, bool isStereo); - bool ProcessOneBlock(sampleCount len, float * const * chans); - bool ProcessOneTrack(size_t n, WaveTrack * track, WaveTrack * track2, wxString const & msg); - void Delete(); - double mCurT0, mCurT1; - Reverb_priv_t * mP; - - // Settings: - wxString SettingsPath(int settingsNumber) const; - wxString SettingsName(int settingsNumber) const; - - struct Params { + struct Params + { double mRoomSize; - double mDelay; + double mPreDelay; double mReverberance; double mHfDamping; double mToneLow; @@ -65,89 +45,83 @@ public: double mStereoWidth; bool mWetOnly; }; - void LoadSettings(int settingsNumber, Params & params); - void SaveSettings(int settingsNumber, Params const * params, wxString const * name = 0) const; - Params mParams; + // IdentInterface implementation - friend class ReverbDialogue; -}; + virtual wxString GetSymbol(); + virtual wxString GetDescription(); -//---------------------------------------------------------------------------- -// ReverbDialogue -//---------------------------------------------------------------------------- -class ReverbDialogue : public EffectDialog -{ -public: - ReverbDialogue(EffectReverb * effect, wxWindow * parent); - virtual ~ReverbDialogue() {}; + // EffectIdentInterface implementation -private: - void SetTitle(wxString const & name = wxT("")); - void PopulateOrExchange(ShuttleGui &); + virtual EffectType GetType(); + + // EffectClientInterface implementation + + virtual int GetAudioInCount(); + virtual int GetAudioOutCount(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); + virtual bool ProcessFinalize(); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + virtual wxArrayString GetFactoryPresets(); + virtual bool LoadFactoryPreset(int id); + + // Effect implementation + + bool Startup(); + void PopulateOrExchange(ShuttleGui & S); bool TransferDataToWindow(); bool TransferDataFromWindow(); - void LoadPreset(wxCommandEvent & WXUNUSED(event)); - int ChooseSettings(wxString const & message); - void LoadSettings(wxCommandEvent & WXUNUSED(event)); - void RenameSettings(wxCommandEvent & WXUNUSED(event)); - void SaveSettings(wxCommandEvent & WXUNUSED(event)); - void OnPreview(wxCommandEvent & event); +private: + // EffectReverb implementation - // event handlers and member vars - void OnRoomSizeWidget(wxCommandEvent & event); - void OnRoomSizeText(wxCommandEvent & event); - wxSlider * mRoomSizeWidget; - wxSpinCtrl * mRoomSizeText; + void SetTitle(const wxString & name = wxT("")); - void OnDelayWidget(wxCommandEvent & event); - void OnDelayText(wxCommandEvent & event); - wxSlider * mDelayWidget; - wxSpinCtrl * mDelayText; +#define SpinSliderHandlers(n) \ + void On ## n ## Slider(wxCommandEvent & evt); \ + void On ## n ## Text(wxCommandEvent & evt); - void OnReverberanceWidget(wxCommandEvent & event); - void OnReverberanceText(wxCommandEvent & event); - wxSlider * mReverberanceWidget; - wxSpinCtrl * mReverberanceText; + SpinSliderHandlers(RoomSize); + SpinSliderHandlers(PreDelay); + SpinSliderHandlers(Reverberance); + SpinSliderHandlers(HfDamping); + SpinSliderHandlers(ToneLow); + SpinSliderHandlers(ToneHigh); + SpinSliderHandlers(WetGain); + SpinSliderHandlers(DryGain); + SpinSliderHandlers(StereoWidth); - void OnHfDampingWidget(wxCommandEvent & event); - void OnHfDampingText(wxCommandEvent & event); - wxSlider * mHfDampingWidget; - wxSpinCtrl * mHfDampingText; +#undef SpinSliderHandlers - void OnToneLowWidget(wxCommandEvent & event); - void OnToneLowText(wxCommandEvent & event); - wxSlider * mToneLowWidget; - wxSpinCtrl * mToneLowText; +private: + int mNumChans; + Reverb_priv_t *mP; - void OnToneHighWidget(wxCommandEvent & event); - void OnToneHighText(wxCommandEvent & event); - wxSlider * mToneHighWidget; - wxSpinCtrl * mToneHighText; + Params mParams; - void OnWetGainWidget(wxCommandEvent & event); - void OnWetGainText(wxCommandEvent & event); - wxSlider * mWetGainWidget; - wxSpinCtrl * mWetGainText; + bool mProcessingEvent; - void OnDryGainWidget(wxCommandEvent & event); - void OnDryGainText(wxCommandEvent & event); - wxSlider * mDryGainWidget; - wxSpinCtrl * mDryGainText; +#define SpinSlider(n) \ + wxSpinCtrl *m ## n ## T; \ + wxSlider *m ## n ## S; - void OnStereoWidthWidget(wxCommandEvent & event); - void OnStereoWidthText(wxCommandEvent & event); - wxSlider * mStereoWidthWidget; - wxSpinCtrl * mStereoWidthText; + SpinSlider(RoomSize); + SpinSlider(PreDelay); + SpinSlider(Reverberance); + SpinSlider(HfDamping); + SpinSlider(ToneLow); + SpinSlider(ToneHigh); + SpinSlider(WetGain); + SpinSlider(DryGain); + SpinSlider(StereoWidth); - wxCheckBox * mWetOnlyWidget; +#undef SpinSlider + wxCheckBox *mWetOnlyC; - EffectReverb & mEffect; - EffectReverb::Params & mParams; - - DECLARE_EVENT_TABLE() + DECLARE_EVENT_TABLE(); }; #endif diff --git a/src/effects/Reverse.cpp b/src/effects/Reverse.cpp index d84345a8d..df5c74b1e 100644 --- a/src/effects/Reverse.cpp +++ b/src/effects/Reverse.cpp @@ -18,11 +18,12 @@ #include -#include "Reverse.h" -#include "../Project.h" -#include "../WaveTrack.h" +#include + #include "../LabelTrack.h" +#include "Reverse.h" + // // EffectReverse // @@ -31,6 +32,36 @@ EffectReverse::EffectReverse() { } +EffectReverse::~EffectReverse() +{ +} + +// IdentInterface implementation + +wxString EffectReverse::GetSymbol() +{ + return REVERSE_PLUGIN_SYMBOL; +} + +wxString EffectReverse::GetDescription() +{ + return wxTRANSLATE("Reverses the selected audio"); +} + +// EffectIdentInterface implementation + +EffectType EffectReverse::GetType() +{ + return EffectTypeProcess; +} + +bool EffectReverse::IsInteractive() +{ + return false; +} + +// Effect implementation + bool EffectReverse::Process() { //Track::All is needed because Reverse should move the labels too @@ -234,4 +265,3 @@ bool EffectReverse::ProcessOneClip(int count, WaveTrack *track, return rc; } - diff --git a/src/effects/Reverse.h b/src/effects/Reverse.h index 973d381cd..df078a809 100644 --- a/src/effects/Reverse.h +++ b/src/effects/Reverse.h @@ -13,44 +13,37 @@ #ifndef __AUDACITY_EFFECT_REVERSE__ #define __AUDACITY_EFFECT_REVERSE__ -#include +#include + +#include "../WaveTrack.h" #include "Effect.h" -#define __UNINITIALIZED__ (-1) +#define REVERSE_PLUGIN_SYMBOL wxTRANSLATE("Reverse") -class WaveTrack; - -class EffectReverse:public Effect { - - public: +class EffectReverse : public Effect +{ +public: EffectReverse(); + virtual ~EffectReverse(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Reverse")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://audacityteam.org/namespace#TimelineChanger")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Reverse")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Reversing")); - } + virtual EffectType GetType(); + virtual bool IsInteractive(); - virtual bool PromptUser() { - return true; - } + // Effect implementation virtual bool Process(); - private: +private: + // EffectReverse implementation + bool ProcessOneClip(int count, WaveTrack* track, sampleCount start, sampleCount len, sampleCount originalStart, sampleCount originalEnd); bool ProcessOneWave(int count, WaveTrack* track, sampleCount start, sampleCount len); diff --git a/src/effects/SBSMSEffect.h b/src/effects/SBSMSEffect.h index 336ac358d..205ece823 100644 --- a/src/effects/SBSMSEffect.h +++ b/src/effects/SBSMSEffect.h @@ -21,14 +21,15 @@ #include "sbsms.h" using namespace _sbsms_; -class EffectSBSMS : public Effect { - public: +class EffectSBSMS : public Effect +{ +public: virtual bool Process(); void setParameters(double rateStart, double rateEnd, double pitchStart, double pitchEnd, SlideType rateSlideType, SlideType pitchSlideType, bool bLinkRatePitch, bool bRateReferenceInput, bool bPitchReferenceInput); - private: +private: bool ProcessLabelTrack(Track *track); double rateStart, rateEnd, pitchStart, pitchEnd; bool bLinkRatePitch, bRateReferenceInput, bPitchReferenceInput; diff --git a/src/effects/ScienFilter.cpp b/src/effects/ScienFilter.cpp index 7626fa130..24fc252b7 100644 --- a/src/effects/ScienFilter.cpp +++ b/src/effects/ScienFilter.cpp @@ -1,1303 +1,1165 @@ -/********************************************************************** - - Audacity: A Digital Audio Editor - - Effect/ScienFilter.cpp - - Norm C - Mitch Golden - Vaughan Johnson (Preview) - -*******************************************************************//** - -\file ScienFilter.cpp -\brief Implements EffectScienFilter, ScienFilterDialog, -ScienFilterPanel. - -*//****************************************************************//** - -\class EffectScienFilter -\brief An Effect. - - Performs IIR filtering that emulates analog filters, specifically - Butterworth, Chebyshev Type I and Type II. Highpass and lowpass filters - are supported, as are filter orders from 1 to 10. - - The filter is applied using biquads - -*//****************************************************************//** - -\class ScienFilterDialog -\brief Dialog used with EffectScienFilter - -*//****************************************************************//** - -\class ScienFilterPanel -\brief ScienFilterPanel is used with ScienFilterDialog and controls -a graph for EffectScienFilter. - -*//*******************************************************************/ - -#include "../Audacity.h" -#include "ScienFilter.h" -#include "Equalization.h" // For SliderAx -#include "../AColor.h" -#include "../ShuttleGui.h" -#include "../PlatformCompatibility.h" -#include "../Prefs.h" -#include "../Project.h" -#include "../WaveTrack.h" -#include "../widgets/Ruler.h" -#include "../Theme.h" -#include "../AllThemeResources.h" -#include "../WaveTrack.h" -#include "../float_cast.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if wxUSE_TOOLTIPS -#include -#endif -#include - -#include - -#include - -#define PI 3.1415926535 -#define square(a) ((a)*(a)) - -#ifndef __min - #define __min(a,b) ((a) < (b) ? (a) : (b)) -#endif -#ifndef __max - #define __max(a,b) ((a) > (b) ? (a) : (b)) -#endif - -// Local functions - -void EffectScienFilter::ReadPrefs() -{ - double dTemp; - gPrefs->Read(wxT("/SciFilter/Order"), &mOrder, 1); - mOrder = __max (1, mOrder); - mOrder = __min (MAX_FILTER_ORDER, mOrder); - gPrefs->Read(wxT("/SciFilter/FilterType"), &mFilterType, 0); - mFilterType = __max (0, mFilterType); - mFilterType = __min (2, mFilterType); - gPrefs->Read(wxT("/SciFilter/FilterSubtype"), &mFilterSubtype, 0); - mFilterSubtype = __max (0, mFilterSubtype); - mFilterSubtype = __min (1, mFilterSubtype); - gPrefs->Read(wxT("/SciFilter/Cutoff"), &dTemp, 1000.0); - mCutoff = (float)dTemp; - mCutoff = __max (1, mCutoff); - mCutoff = __min (100000, mCutoff); - gPrefs->Read(wxT("/SciFilter/Ripple"), &dTemp, 1.0); - mRipple = dTemp; - mRipple = __max (0, mRipple); - mRipple = __min (100, mRipple); - gPrefs->Read(wxT("/SciFilter/StopbandRipple"), &dTemp, 30.0); - mStopbandRipple = dTemp; - mStopbandRipple = __max (0, mStopbandRipple); - mStopbandRipple = __min (100, mStopbandRipple); -} - -EffectScienFilter::EffectScienFilter() -{ - ReadPrefs(); - mPrompting = false; -} - - -EffectScienFilter::~EffectScienFilter() -{ -} - -bool EffectScienFilter::Init() -{ - int selcount = 0; - double rate = 0.0; - TrackListIterator iter(GetActiveProject()->GetTracks()); - Track *t = iter.First(); - while (t) { - if (t->GetSelected() && t->GetKind() == Track::Wave) { - WaveTrack *track = (WaveTrack *)t; - if (selcount==0) { - rate = track->GetRate(); - } - else { - if (track->GetRate() != rate) { - wxMessageBox(_("To apply a filter, all selected tracks must have the same sample rate.")); - return(false); - } - } - selcount++; - } - t = iter.Next(); - } - return(true);} - -bool EffectScienFilter::PromptUser() -{ - // Detect whether we are editing a batch chain by checking the parent window - mEditingBatchParams = (mParent != GetActiveProject()); - if (!mEditingBatchParams) - { - ReadPrefs(); - } - - TrackListOfKindIterator iter(Track::Wave, mTracks); - WaveTrack *t = (WaveTrack *) iter.First(); - float hiFreq; - if (t) - hiFreq = ((float)(t->GetRate())/2.); - else - hiFreq = ((float)(GetActiveProject()->GetRate())/2.); - - ScienFilterDialog dlog(this, ((double)loFreqI), hiFreq, mParent, -1, _("Classic Filters")); - - dlog.dBMin = mdBMin; - dlog.dBMax = mdBMax; - dlog.Order = mOrder; - dlog.Cutoff = mCutoff; - dlog.FilterType = mFilterType; - dlog.FilterSubtype = mFilterSubtype; - dlog.Ripple = mRipple; - dlog.StopbandRipple = mStopbandRipple; - - dlog.CentreOnParent(); - - mPrompting = true; // true when previewing, false in batch - dlog.ShowModal(); - mPrompting = false; - - if (!dlog.GetReturnCode()) - return false; - - mdBMin = dlog.dBMin; - mdBMax = dlog.dBMax; - mOrder = dlog.Order; - mCutoff = dlog.Cutoff; - mFilterType = dlog.FilterType; - mFilterSubtype = dlog.FilterSubtype; - mRipple = dlog.Ripple; - mStopbandRipple = dlog.StopbandRipple; - - if (!mEditingBatchParams) - { - // Save preferences - gPrefs->Write(wxT("/SciFilter/Order"), mOrder); - gPrefs->Write(wxT("/SciFilter/FilterType"), mFilterType); - gPrefs->Write(wxT("/SciFilter/FilterSubtype"), mFilterSubtype); - gPrefs->Write(wxT("/SciFilter/Cutoff"), mCutoff); - gPrefs->Write(wxT("/SciFilter/Ripple"), mRipple); - gPrefs->Write(wxT("/SciFilter/StopbandRipple"), mStopbandRipple); - gPrefs->Flush(); - } - - return true; -} - -bool EffectScienFilter::DontPromptUser() -{ - TrackListOfKindIterator iter(Track::Wave, mTracks); - WaveTrack *t = (WaveTrack *) iter.First(); - float hiFreq; - if (t) - hiFreq = ((float)(t->GetRate())/2.); - else - hiFreq = ((float)(GetActiveProject()->GetRate())/2.); - /*i18n-hint: 'Classic Filters' is an audio effect. It's a low-pass or high-pass - filter with specfic characteristics. */ - ScienFilterDialog dlog(this, ((double)loFreqI), hiFreq, NULL, -1, _("Classic Filters")); - dlog.dBMin = mdBMin; - dlog.dBMax = mdBMax; - dlog.Order = mOrder; - dlog.Cutoff = mCutoff; - dlog.FilterType = mFilterType; - dlog.FilterSubtype = mFilterSubtype; - dlog.Ripple = mRipple; - - dlog.CalcFilter(this); - - return true; -} - -bool EffectScienFilter::TransferParameters( Shuttle & shuttle ) -{ - // if shuttle.mbStoreInClient is true, read prefs ScienFilter/FilterType (etc.) string and put into mFilterType (etc.) - // else put mFilterType (etc.) into string form and write prefs - shuttle.TransferInt(wxT("FilterType"),mFilterType,0); - shuttle.TransferInt(wxT("FilterSubtype"),mFilterSubtype,0); // etc. - shuttle.TransferInt(wxT("Order"),mOrder,2); - shuttle.TransferFloat(wxT("Cutoff"),mCutoff,1000); - shuttle.TransferFloat(wxT("PassbandRipple"),mRipple,1); - shuttle.TransferFloat(wxT("StopbandRipple"),mStopbandRipple,30); - - if(!mPrompting) - DontPromptUser(); // not previewing, ie batch mode or initial setup - return true; -} - -bool EffectScienFilter::Process() -{ - this->CopyInputTracks(); // Set up mOutputTracks. - bool bGoodResult = true; - - SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks); - WaveTrack *track = (WaveTrack *) iter.First(); - int count = 0; - while (track) - { - double trackStart = track->GetStartTime(); - double trackEnd = track->GetEndTime(); - double t0 = mT0 < trackStart? trackStart: mT0; - double t1 = mT1 > trackEnd? trackEnd: mT1; - - if (t1 > t0) { - sampleCount start = track->TimeToLongSamples(t0); - sampleCount end = track->TimeToLongSamples(t1); - sampleCount len = (sampleCount)(end - start); - - if (!ProcessOne(count, track, start, len)) - { - bGoodResult = false; - break; - } - } - - track = (WaveTrack *) iter.Next(); - count++; - } - - this->ReplaceProcessedTracks(bGoodResult); - return bGoodResult; -} - - -bool EffectScienFilter::ProcessOne(int count, WaveTrack * t, - sampleCount start, sampleCount len) -{ - // Create a new WaveTrack to hold all of the output - AudacityProject *p = GetActiveProject(); - WaveTrack *output = p->GetTrackFactory()->NewWaveTrack(floatSample, t->GetRate()); - - sampleCount s = start; - sampleCount idealBlockLen = t->GetMaxBlockSize(); - float *buffer = new float[idealBlockLen]; - sampleCount originalLen = len; - - TrackProgress(count, 0.0); - bool bLoopSuccess = true; - - for (int iPair = 0; iPair < (mOrder+1)/2; iPair++) - mpBiquad [iPair]->fPrevIn = mpBiquad [iPair]->fPrevPrevIn = mpBiquad [iPair]->fPrevOut = mpBiquad [iPair]->fPrevPrevOut = 0; - - while(len) - { - sampleCount block = idealBlockLen; - if (block > len) - block = len; - - t->Get((samplePtr)buffer, floatSample, s, block); - - for (int iPair = 0; iPair < (mOrder+1)/2; iPair++) - { - mpBiquad[iPair]->pfIn = buffer; - mpBiquad[iPair]->pfOut = buffer; - Biquad_Process (mpBiquad[iPair], block); - } - output->Append ((samplePtr)buffer, floatSample, block); - len -= block; - s += block; - - if (TrackProgress (count, (s-start)/(double)originalLen)) - { - bLoopSuccess = false; - break; - } - } - if (bLoopSuccess) - { - output->Flush(); - // Now move the appropriate bit of the output back to the track - float *bigBuffer = new float[originalLen]; - output->Get((samplePtr)bigBuffer, floatSample, 0, originalLen); - t->Set((samplePtr)bigBuffer, floatSample, start, originalLen); - delete[] bigBuffer; - } - - delete[] buffer; - delete output; - - return bLoopSuccess; -} - -void EffectScienFilter::Filter(sampleCount WXUNUSED(len), - float *WXUNUSED(buffer)) -{ -} - - -//---------------------------------------------------------------------------- -// ScienFilterPanel -//---------------------------------------------------------------------------- - -BEGIN_EVENT_TABLE(ScienFilterPanel, wxPanel) - EVT_PAINT(ScienFilterPanel::OnPaint) - EVT_SIZE(ScienFilterPanel::OnSize) -END_EVENT_TABLE() - -ScienFilterPanel::ScienFilterPanel( double loFreq, double hiFreq, - ScienFilterDialog *parent, - wxWindowID id, const wxPoint& pos, const wxSize& size): - wxPanel(parent, id, pos, size) -{ - mBitmap = NULL; - mWidth = 0; - mHeight = 0; - mLoFreq = loFreq; - mHiFreq = hiFreq; - mParent = parent; -} - -ScienFilterPanel::~ScienFilterPanel() -{ - if (mBitmap) - delete mBitmap; -} - -void ScienFilterPanel::OnSize(wxSizeEvent & WXUNUSED(evt)) -{ - Refresh( false ); -} - -void ScienFilterPanel::OnPaint(wxPaintEvent & WXUNUSED(evt)) -{ - wxPaintDC dc(this); - int width, height; - GetSize(&width, &height); - - if (!mBitmap || mWidth!=width || mHeight!=height) - { - if (mBitmap) - delete mBitmap; - - mWidth = width; - mHeight = height; - mBitmap = new wxBitmap(mWidth, mHeight); - } - - wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)); - - wxMemoryDC memDC; - memDC.SelectObject(*mBitmap); - - wxRect bkgndRect; - bkgndRect.x = 0; - bkgndRect.y = 0; - bkgndRect.width = mWidth; - bkgndRect.height = mHeight; - memDC.SetBrush(bkgndBrush); - memDC.SetPen(*wxTRANSPARENT_PEN); - memDC.DrawRectangle(bkgndRect); - - bkgndRect.y = mHeight; - memDC.DrawRectangle(bkgndRect); - - wxRect border; - border.x = 0; - border.y = 0; - border.width = mWidth; - border.height = mHeight; - - memDC.SetBrush(*wxWHITE_BRUSH); - memDC.SetPen(*wxBLACK_PEN); - memDC.DrawRectangle(border); - - mEnvRect = border; - mEnvRect.Deflate(2, 2); - - // Pure blue x-axis line - memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 1, wxSOLID)); - int center = (int) (mEnvRect.height * dBMax/(dBMax-dBMin) + .5); - AColor::Line(memDC, - mEnvRect.GetLeft(), mEnvRect.y + center, - mEnvRect.GetRight(), mEnvRect.y + center); - - //Now draw the actual response that you will get. - //mFilterFunc has a linear scale, window has a log one so we have to fiddle about - memDC.SetPen(wxPen(theTheme.Colour( clrResponseLines ), 3, wxSOLID)); - double scale = (double)mEnvRect.height/(dBMax-dBMin); // pixels per dB - double yF; // gain at this freq - - double loLog = log10(mLoFreq); - double step = log10(mHiFreq) - loLog; - step /= ((double)mEnvRect.width-1.); - double freq; // actual freq corresponding to x position - int x, y, xlast = 0, ylast = 0; - for(int i=0; iFilterMagnAtFreq (freq); - yF = 20*log10(yF); - - if(yF < dBMin) - yF = dBMin; - yF = center-scale*yF; - if(yF>mEnvRect.height) - yF = mEnvRect.height - 1; - if(yF<0.) - yF=0.; - y = (int)(yF+.5); - - if (i != 0 && (y < mEnvRect.height-1 || ylast < mEnvRect.y + mEnvRect.height-1)) - { - AColor::Line (memDC, xlast, ylast, x, mEnvRect.y + y); - } - xlast = x; - ylast = mEnvRect.y + y; - } - - memDC.SetPen(*wxBLACK_PEN); - mParent->freqRuler->ruler.DrawGrid(memDC, mEnvRect.height+2, true, true, 0, 1); - mParent->dBRuler->ruler.DrawGrid(memDC, mEnvRect.width+2, true, true, 1, 2); - - dc.Blit(0, 0, mWidth, mHeight, - &memDC, 0, 0, wxCOPY, FALSE); -} - - -// WDR: class implementations - -//---------------------------------------------------------------------------- -// ScienFilterDialog -//---------------------------------------------------------------------------- - -// WDR: event table for ScienFilterDialog - -BEGIN_EVENT_TABLE(ScienFilterDialog,wxDialog) - EVT_SIZE( ScienFilterDialog::OnSize ) - EVT_PAINT( ScienFilterDialog::OnPaint ) - EVT_ERASE_BACKGROUND( ScienFilterDialog::OnErase ) - - EVT_SLIDER( ID_DBMAX, ScienFilterDialog::OnSliderDBMAX ) - EVT_SLIDER( ID_DBMIN, ScienFilterDialog::OnSliderDBMIN ) - EVT_CHOICE( ID_FILTER_ORDER, ScienFilterDialog::OnOrder) - EVT_CHOICE( ID_FILTER_TYPE, ScienFilterDialog::OnFilterType) - EVT_CHOICE( ID_FILTER_SUBTYPE, ScienFilterDialog::OnFilterSubtype) - EVT_TEXT( ID_CUTOFF, ScienFilterDialog::OnCutoff) - EVT_TEXT( ID_RIPPLE, ScienFilterDialog::OnRipple) - EVT_TEXT( ID_STOPBAND_RIPPLE, ScienFilterDialog::OnStopbandRipple) - - EVT_BUTTON( ID_EFFECT_PREVIEW, ScienFilterDialog::OnPreview ) - EVT_BUTTON( wxID_OK, ScienFilterDialog::OnOk ) - EVT_BUTTON( wxID_CANCEL, ScienFilterDialog::OnCancel ) -END_EVENT_TABLE() - -ScienFilterDialog::ScienFilterDialog(EffectScienFilter * effect, - double loFreq, double hiFreq, - wxWindow *parent, wxWindowID id, - const wxString &title, - const wxPoint &position, - const wxSize& size, - long style): - wxDialog( parent, id, title, position, size, style | wxRESIZE_BORDER | wxMAXIMIZE_BOX ) -{ - m_pEffect = effect; - - dBMin = -30.; - dBMax = 30; - -#if wxUSE_TOOLTIPS - wxToolTip::Enable(true); -#endif - - mLoFreq = loFreq; - mNyquist = hiFreq; - - memset (effect->mpBiquad, 0, sizeof(effect->mpBiquad)); - for (int i = 0; i < MAX_FILTER_ORDER/2; i++) - { - effect->mpBiquad[i] = (BiquadStruct*)calloc (sizeof (BiquadStruct), 1); - effect->mpBiquad[i]->fNumerCoeffs [0] = 1.0; // straight-through - } - - // Create the dialog - MakeScienFilterDialog(); -} - -ScienFilterDialog::~ScienFilterDialog() -{ -} - -// -// Create the ScienFilter dialog -// -void ScienFilterDialog::MakeScienFilterDialog() -{ - wxStaticText *st; - wxSizerFlags flagslabel; - wxSizerFlags flagsunits; - - mCutoffCtl = NULL; - mRippleCtl = NULL; - mStopbandRippleCtl = NULL; - - // TODO: This code would be more readable if using ShuttleGUI. - // Some updates to ShuttleGui would help this. - - // Create the base sizer - szrV = new wxBoxSizer( wxVERTICAL ); - - // ------------------------------------------------------------------- - // ROW 1: Freq response panel and sliders for vertical scale - // ------------------------------------------------------------------- - szr1 = new wxFlexGridSizer( 3, 0, 0 ); - szr1->AddGrowableCol( 2, 1 ); - szr1->AddGrowableRow( 0, 1 ); - szr1->SetFlexibleDirection( wxBOTH ); - - szr2 = new wxBoxSizer( wxVERTICAL ); - dBMaxSlider = new wxSlider(this, ID_DBMAX, 10, 0, 20, - wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE); - szr2->Add( dBMaxSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 ); - dBMinSlider = new wxSlider(this, ID_DBMIN, -10, -120, -10, - wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE); - szr2->Add( dBMinSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 ); - szr1->Add( szr2, 0, wxEXPAND|wxALIGN_CENTRE|wxALL, 4 ); - -#if wxUSE_ACCESSIBILITY - dBMaxSlider->SetName(_("Max dB")); - dBMaxSlider->SetAccessible(new SliderAx(dBMaxSlider, wxString(wxT("%d ")) + _("dB"))); - dBMinSlider->SetName(_("Min dB")); - dBMinSlider->SetAccessible(new SliderAx(dBMinSlider, wxString(wxT("%d ")) + _("dB"))); -#endif - - dBRuler = new RulerPanel(this, wxID_ANY); - dBRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes - dBRuler->ruler.SetOrientation(wxVERTICAL); - dBRuler->ruler.SetRange(30.0, -120.0); - dBRuler->ruler.SetFormat(Ruler::LinearDBFormat); - dBRuler->ruler.SetUnits(_("dB")); - dBRuler->ruler.SetLabelEdges(true); - int w, h; - dBRuler->ruler.GetMaxSize(&w, NULL); - dBRuler->SetSize(wxSize(w, 150)); // height needed for wxGTK - - szr4 = new wxBoxSizer( wxVERTICAL ); - szr4->AddSpacer(2); // vertical space for panel border and thickness of line - szr4->Add( dBRuler, 1, wxEXPAND|wxALIGN_LEFT|wxALL ); - szr4->AddSpacer(1); // vertical space for thickness of line - szr1->Add( szr4, 0, wxEXPAND|wxALIGN_LEFT|wxALL ); - - wxSize size; - size.Set (400, 200); - mPanel = new ScienFilterPanel( mLoFreq, mNyquist, - this, - ID_FILTERPANEL, wxDefaultPosition, size); - szr1->Add( mPanel, 1, wxEXPAND|wxALIGN_CENTRE|wxRIGHT, 4); - - /// Next row of wxFlexGridSizer - szr1->Add(1, 1); // horizontal spacer - szr1->Add(1, 1); // horizontal spacer - - freqRuler = new RulerPanel(this, wxID_ANY); - freqRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes - freqRuler->ruler.SetOrientation(wxHORIZONTAL); - freqRuler->ruler.SetLog(true); - freqRuler->ruler.SetRange(mLoFreq, mNyquist); - freqRuler->ruler.SetFormat(Ruler::IntFormat); - freqRuler->ruler.SetUnits(wxT("")); - freqRuler->ruler.SetFlip(true); - freqRuler->ruler.SetLabelEdges(true); - freqRuler->ruler.GetMaxSize(NULL, &h); - freqRuler->SetMinSize(wxSize(-1, h)); - szr1->Add( freqRuler, 0, wxEXPAND|wxALIGN_LEFT|wxRIGHT, 4 ); - - szrV->Add( szr1, 1, wxEXPAND|wxALIGN_CENTER|wxALL, 0 ); - - // ------------------------------------------------------------------- - // ROW 2 and 3: Type, Order, Ripple, Subtype, Cutoff - // ------------------------------------------------------------------- - szr3 = new wxFlexGridSizer (6, 5, 2); // 6 columns, 5px Vertical gap, 2px Horizontal gap - flagslabel.Border(wxLEFT, 12).Align(wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL ); - flagsunits.Align( wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL ); - - st = new wxStaticText(this, wxID_ANY, _("&Filter Type:")); - st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - szr3->Add(st, flagslabel ); - mFilterTypeCtl = new wxChoice (this, ID_FILTER_TYPE); - mFilterTypeCtl->SetName(wxStripMenuCodes(st->GetLabel())); - /*i18n-hint: Butterworth is the name of the person after whom the filter type is named.*/ - mFilterTypeCtl->Append (_("Butterworth")); - /*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/ - mFilterTypeCtl->Append (_("Chebyshev Type I")); - /*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/ - mFilterTypeCtl->Append (_("Chebyshev Type II")); - szr3->Add(mFilterTypeCtl); - - /*i18n-hint: 'Order' means the complexity of the filter, and is a number between 1 and 10.*/ - st = new wxStaticText(this, wxID_ANY, _("O&rder:")); - st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - szr3->Add(st, flagslabel ); - mFilterOrderCtl = new wxChoice (this, ID_FILTER_ORDER); - mFilterOrderCtl->SetName(wxStripMenuCodes(st->GetLabel())); - mFilterOrderCtl->Append (wxT("1")); - mFilterOrderCtl->Append (wxT("2")); - mFilterOrderCtl->Append (wxT("3")); - mFilterOrderCtl->Append (wxT("4")); - mFilterOrderCtl->Append (wxT("5")); - mFilterOrderCtl->Append (wxT("6")); - mFilterOrderCtl->Append (wxT("7")); - mFilterOrderCtl->Append (wxT("8")); - mFilterOrderCtl->Append (wxT("9")); - mFilterOrderCtl->Append (wxT("10")); - szr3->Add(mFilterOrderCtl); - - st = new wxStaticText(this, wxID_ANY, wxT("")); - st->SetName(wxT("")); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - szr3->Add(st); // empty field in grid to balance Hz in next row - - szrPass = new wxBoxSizer( wxHORIZONTAL ); - st = new wxStaticText(this, wxID_ANY, _("&Passband Ripple:")); - st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - szrPass->Add(st, flagslabel); - wxSize Size(wxDefaultSize); - Size.SetWidth (40); - mRippleCtl = new wxTextCtrl (this, ID_RIPPLE, wxT("0.0"), wxDefaultPosition, Size); - mRippleCtl->SetName( _("Maximum passband attenuation (dB):")); - szrPass->Add(mRippleCtl, 0 ); - st = new wxStaticText(this, wxID_ANY, _("dB")); - st->SetName(st->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - szrPass->Add(st, flagsunits); - szr3->Add(szrPass); - - st = new wxStaticText(this, wxID_ANY, _("&Subtype:")); - szr3->Add(st, flagslabel); - st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - mFilterSubTypeCtl = new wxChoice (this, ID_FILTER_SUBTYPE); - mFilterSubTypeCtl->SetName(wxStripMenuCodes(st->GetLabel())); - mFilterSubTypeCtl->Append (_("Lowpass")); - mFilterSubTypeCtl->Append (_("Highpass")); - szr3->Add(mFilterSubTypeCtl); - - st = new wxStaticText(this, wxID_ANY, _("C&utoff:")); - st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - szr3->Add(st, flagslabel); - Size.SetWidth (50); - mCutoffCtl = new wxTextCtrl (this, ID_CUTOFF, wxT("0.0"), wxDefaultPosition, Size); - mCutoffCtl->SetName(_("Cutoff(Hz):")); - szr3->Add(mCutoffCtl, 0); - st = new wxStaticText(this, wxID_ANY, _("Hz")); - st->SetName(st->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - szr3->Add(st, flagsunits); - - szrStop = new wxBoxSizer( wxHORIZONTAL ); - st = new wxStaticText(this, wxID_ANY, _("Minimum S&topband Attenuation:") ); - st->SetName(wxStripMenuCodes(st->GetLabel())); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - szrStop->Add( st, flagslabel ); - Size.SetWidth (40); - mStopbandRippleCtl = new wxTextCtrl (this, ID_STOPBAND_RIPPLE, wxT("0.0"), wxDefaultPosition, Size); - mStopbandRippleCtl->SetName(_("Minimum stopband attenuation (dB):")); - szrStop->Add(mStopbandRippleCtl, 0 ); - st = new wxStaticText(this, wxID_ANY, _("dB")); - st->SetName(st->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - szrStop->Add(st, flagsunits); - szr3->Add(szrStop); - - // Calculate the min size with both pass and stop-band attenuations showing, to stop them jumping around - szrPass->Show(true); - szrStop->Show(true); - szr3->SetMinSize(szr3->CalcMin()); - - // ------------------------------------------------------------------- - // ROW 4: Subtype, Cutoff - // ------------------------------------------------------------------- - - szrV->Add( szr3, 0, wxALIGN_CENTER | wxALL, 4 ); - - // ------------------------------------------------------------------- - // ROW 5: Preview, OK, & Cancel buttons - // ------------------------------------------------------------------- - szrV->Add(CreateStdButtonSizer(this, ePreviewButton|eCancelButton|eOkButton), 0, wxEXPAND); - - // ------------------------------------------------------------------- - // Display now - // ------------------------------------------------------------------- - SetAutoLayout(false); - - SetSizerAndFit( szrV ); - SetSizeHints(GetSize()); - - return; -} - - -// -// Validate data -// -bool ScienFilterDialog::Validate() -{ - // In this case I don't think there's anything the user could have screwed up - return true; -} - -// -// Populate the window with relevant variables -// -bool ScienFilterDialog::TransferDataToWindow() -{ - dBMinSlider->SetValue((int)dBMin); - dBMin = 0; // force refresh in TransferGraphLimitsFromWindow() - - dBMaxSlider->SetValue((int)dBMax); - dBMax = 0; // force refresh in TransferGraphLimitsFromWindow() - - mFilterTypeCtl->SetSelection (FilterType); - mFilterOrderCtl->SetSelection (Order - 1); - mFilterSubTypeCtl->SetSelection (FilterSubtype); - mCutoffCtl->SetValue (Internat::ToDisplayString(Cutoff)); - mRippleCtl->SetValue (Internat::ToDisplayString(Ripple)); - mStopbandRippleCtl->SetValue (Internat::ToDisplayString(StopbandRipple)); - EnableDisableRippleCtl (FilterType); - - return TransferGraphLimitsFromWindow(); -} - -// -// Retrieve data from the window -// -bool ScienFilterDialog::TransferGraphLimitsFromWindow() -{ - // Read the sliders and send to the panel - wxString tip; - - bool rr = false; - int dB = dBMinSlider->GetValue(); - if (dB != dBMin) { - rr = true; - dBMin = dB; - mPanel->dBMin = dBMin; -#if wxUSE_TOOLTIPS - tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)dBMin); - dBMinSlider->SetToolTip(tip); -#endif - } - - dB = dBMaxSlider->GetValue(); - if (dB != dBMax) { - rr = true; - dBMax = dB; - mPanel->dBMax = dBMax; -#if wxUSE_TOOLTIPS - tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)dBMax); - dBMaxSlider->SetToolTip(tip); -#endif - } - - // Refresh ruler if values have changed - if (rr) { - int w1, w2, h; - dBRuler->ruler.GetMaxSize(&w1, &h); - dBRuler->ruler.SetRange(dBMax, dBMin); - dBRuler->ruler.GetMaxSize(&w2, &h); - if( w1 != w2 ) // Reduces flicker - { - dBRuler->SetSize(wxSize(w2,h)); - szr1->Layout(); - freqRuler->Refresh(false); - } - dBRuler->Refresh(false); - } - - mPanel->Refresh(false); - - return true; -} - -bool ScienFilterDialog::CalcFilter (EffectScienFilter* effect) -{ - TrackListOfKindIterator iter(Track::Wave, effect->mTracks); - WaveTrack *t = (WaveTrack *) iter.First(); - float hiFreq; - if (t) - hiFreq = ((float)(t->GetRate())/2.); - else - hiFreq = ((float)(GetActiveProject()->GetRate())/2.); - - // Set up the coefficients in all the biquads - float fNorm = Cutoff / hiFreq; - if (fNorm >= 0.9999) - fNorm = 0.9999F; - float fC = tan (PI * fNorm / 2); - float fDCPoleDistSqr = 1.0F; - float fZPoleX, fZPoleY; - float fZZeroX, fZZeroY; - float beta = cos (fNorm*PI); - switch (FilterType) - { - case 0: // Butterworth - if ((Order & 1) == 0) - { - // Even order - for (int iPair = 0; iPair < Order/2; iPair++) - { - float fSPoleX = fC * cos (PI - (iPair + 0.5) * PI / Order); - float fSPoleY = fC * sin (PI - (iPair + 0.5) * PI / Order); - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - effect->mpBiquad[iPair]->fNumerCoeffs [0] = 1; - if (FilterSubtype == 0) // LOWPASS - effect->mpBiquad[iPair]->fNumerCoeffs [1] = 2; - else - effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2; - effect->mpBiquad[iPair]->fNumerCoeffs [2] = 1; - effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX; - effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); - if (FilterSubtype == 0) // LOWPASS - fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); - else - fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist - } - } - else - { - // Odd order - first do the 1st-order section - float fSPoleX = -fC; - float fSPoleY = 0; - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - effect->mpBiquad[0]->fNumerCoeffs [0] = 1; - if (FilterSubtype == 0) // LOWPASS - effect->mpBiquad[0]->fNumerCoeffs [1] = 1; - else - effect->mpBiquad[0]->fNumerCoeffs [1] = -1; - effect->mpBiquad[0]->fNumerCoeffs [2] = 0; - effect->mpBiquad[0]->fDenomCoeffs [0] = -fZPoleX; - effect->mpBiquad[0]->fDenomCoeffs [1] = 0; - if (FilterSubtype == 0) // LOWPASS - fDCPoleDistSqr = 1 - fZPoleX; - else - fDCPoleDistSqr = fZPoleX + 1; // dist from Nyquist - for (int iPair = 1; iPair <= Order/2; iPair++) - { - float fSPoleX = fC * cos (PI - iPair * PI / Order); - float fSPoleY = fC * sin (PI - iPair * PI / Order); - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - effect->mpBiquad[iPair]->fNumerCoeffs [0] = 1; - if (FilterSubtype == 0) // LOWPASS - effect->mpBiquad[iPair]->fNumerCoeffs [1] = 2; - else - effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2; - effect->mpBiquad[iPair]->fNumerCoeffs [2] = 1; - effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX; - effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); - if (FilterSubtype == 0) // LOWPASS - fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); - else - fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist - } - } - effect->mpBiquad[0]->fNumerCoeffs [0] *= fDCPoleDistSqr / (1 << Order); // mult by DC dist from poles, divide by dist from zeroes - effect->mpBiquad[0]->fNumerCoeffs [1] *= fDCPoleDistSqr / (1 << Order); - effect->mpBiquad[0]->fNumerCoeffs [2] *= fDCPoleDistSqr / (1 << Order); - break; - - case 1: // Chebyshev Type 1 - double eps; eps = sqrt (pow (10.0, __max(0.001, Ripple) / 10.0) - 1); - double a; a = log (1 / eps + sqrt(1 / square(eps) + 1)) / Order; - // Assume even order to start - for (int iPair = 0; iPair < Order/2; iPair++) - { - float fSPoleX = -fC * sinh (a) * sin ((2*iPair + 1) * PI / (2 * Order)); - float fSPoleY = fC * cosh (a) * cos ((2*iPair + 1) * PI / (2 * Order)); - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - if (FilterSubtype == 0) // LOWPASS - { - fZZeroX = -1; - fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); - fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist - } - else - { - // Highpass - do the digital LP->HP transform on the poles and zeroes - ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); - fZZeroX = 1; - fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist - fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist - } - effect->mpBiquad[iPair]->fNumerCoeffs [0] = fDCPoleDistSqr; - effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2 * fZZeroX * fDCPoleDistSqr; - effect->mpBiquad[iPair]->fNumerCoeffs [2] = fDCPoleDistSqr; - effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX; - effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); - } - if ((Order & 1) == 0) - { - float fTemp = pow (10.0, -__max(0.001, Ripple) / 20.0); // at DC the response is down R dB (for even-order) - effect->mpBiquad[0]->fNumerCoeffs [0] *= fTemp; - effect->mpBiquad[0]->fNumerCoeffs [1] *= fTemp; - effect->mpBiquad[0]->fNumerCoeffs [2] *= fTemp; - } - else - { - // Odd order - now do the 1st-order section - float fSPoleX = -fC * sinh (a); - float fSPoleY = 0; - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - if (FilterSubtype == 0) // LOWPASS - { - fZZeroX = -1; - fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY)); - fDCPoleDistSqr /= 2; // dist from zero at Nyquist - } - else - { - // Highpass - do the digital LP->HP transform on the poles and zeroes - ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); - fZZeroX = 1; - fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist - fDCPoleDistSqr /= 2; // dist from zero at Nyquist - } - effect->mpBiquad[(Order-1)/2]->fNumerCoeffs [0] = fDCPoleDistSqr; - effect->mpBiquad[(Order-1)/2]->fNumerCoeffs [1] = -fZZeroX * fDCPoleDistSqr; - effect->mpBiquad[(Order-1)/2]->fNumerCoeffs [2] = 0; - effect->mpBiquad[(Order-1)/2]->fDenomCoeffs [0] = -fZPoleX; - effect->mpBiquad[(Order-1)/2]->fDenomCoeffs [1] = 0; - } - break; - - case 2: // Chebyshev Type 2 - float fSZeroX, fSZeroY; - float fSPoleX, fSPoleY; - eps = pow (10.0, -__max(0.001, StopbandRipple) / 20.0); - a = log (1 / eps + sqrt(1 / square(eps) + 1)) / Order; - - // Assume even order - for (int iPair = 0; iPair < Order/2; iPair++) - { - ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * Order)), - cosh (a) * cos ((2*iPair + 1) * PI / (2 * Order)), - &fSPoleX, &fSPoleY); - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - fSZeroX = 0; - fSZeroY = fC / cos (((2 * iPair) + 1) * PI / (2 * Order)); - BilinTransform (fSZeroX, fSZeroY, &fZZeroX, &fZZeroY); - - if (FilterSubtype == 0) // LOWPASS - { - fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); - fDCPoleDistSqr /= Calc2D_DistSqr (1, 0, fZZeroX, fZZeroY); - } - else - { - // Highpass - do the digital LP->HP transform on the poles and zeroes - ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); - ComplexDiv (beta - fZZeroX, -fZZeroY, 1 - beta * fZZeroX, -beta * fZZeroY, &fZZeroX, &fZZeroY); - fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist - fDCPoleDistSqr /= Calc2D_DistSqr (-1, 0, fZZeroX, fZZeroY); - } - effect->mpBiquad[iPair]->fNumerCoeffs [0] = fDCPoleDistSqr; - effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2 * fZZeroX * fDCPoleDistSqr; - effect->mpBiquad[iPair]->fNumerCoeffs [2] = (square(fZZeroX) + square(fZZeroY)) * fDCPoleDistSqr; - effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX; - effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); - } - // Now, if it's odd order, we have one more to do - if (Order & 1) - { - int iPair = (Order-1)/2; // we'll do it as a biquad, but it's just first-order - ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * Order)), - cosh (a) * cos ((2*iPair + 1) * PI / (2 * Order)), - &fSPoleX, &fSPoleY); - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - fZZeroX = -1; // in the s-plane, the zero is at infinity - fZZeroY = 0; - if (FilterSubtype == 0) // LOWPASS - { - fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY)); - fDCPoleDistSqr /= 2; - } - else - { - // Highpass - do the digital LP->HP transform on the poles and zeroes - ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -fZPoleY, &fZPoleX, &fZPoleY); - fZZeroX = 1; - fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist - fDCPoleDistSqr /= 2; - } - effect->mpBiquad[iPair]->fNumerCoeffs [0] = fDCPoleDistSqr; - effect->mpBiquad[iPair]->fNumerCoeffs [1] = -fZZeroX * fDCPoleDistSqr; - effect->mpBiquad[iPair]->fNumerCoeffs [2] = 0; - effect->mpBiquad[iPair]->fDenomCoeffs [0] = -fZPoleX; - effect->mpBiquad[iPair]->fDenomCoeffs [1] = 0; - } - break; - } - effect->mOrder = Order; // ?? needed for ProcessOne to work in Preview. This probably should be done a different way, but how? - return true; -} - -static double s_fChebyCoeffs [MAX_FILTER_ORDER][MAX_FILTER_ORDER+1] = { - // For Chebyshev polynomials of the first kind (see http://en.wikipedia.org/wiki/Chebyshev_polynomial) - // Coeffs are in the order 0, 1, 2...9 - {0, 1}, // order 1 - {-1, 0, 2}, // order 2 etc. - {0, -3, 0, 4}, - {1, 0, -8, 0, 8}, - {0, 5, 0, -20, 0, 16}, - {-1, 0, 18, 0, -48, 0, 32}, - {0, -7, 0, 56, 0, -112, 0, 64}, - {1, 0, -32, 0, 160, 0, -256, 0, 128}, - {0, 9, 0, -120, 0, 432, 0, -576, 0, 256}, - {-1, 0, 50, 0, -400, 0, 1120, 0, -1280, 0, 512} -}; - -static double ChebyPoly (int Order, double NormFreq) // NormFreq = 1 at the f0 point (where response is R dB down) -{ - // Calc cosh (Order * acosh (NormFreq)); - double x = 1; - double fSum = 0; - wxASSERT (Order > 0 && Order <= MAX_FILTER_ORDER); - for (int i = 0; i <= Order; i++) - { - fSum += s_fChebyCoeffs [Order-1][i] * x; - x *= NormFreq; - } - return fSum; -} - -float ScienFilterDialog::FilterMagnAtFreq (float Freq) -{ - float Magn; - if (Freq >= mNyquist) - Freq = mNyquist - 1; // prevent tan(PI/2) - float FreqWarped = tan (PI * Freq/(2*mNyquist)); - if (Cutoff >= mNyquist) - Cutoff = mNyquist - 1; - float CutoffWarped = tan (PI * Cutoff/(2*mNyquist)); - float fOverflowThresh = pow (10.0, 12.0 / (2*Order)); // once we exceed 10^12 there's not much to be gained and overflow could happen - - switch (FilterType) - { - case 0: // Butterworth - default: - switch (FilterSubtype) - { - case 0: // lowpass - default: - if (FreqWarped/CutoffWarped > fOverflowThresh) // prevent pow() overflow - Magn = 0; - else - Magn = sqrt (1 / (1 + pow (FreqWarped/CutoffWarped, 2*Order))); - break; - case 1: // highpass - if (FreqWarped/CutoffWarped > fOverflowThresh) - Magn = 1; - else - Magn = sqrt (pow (FreqWarped/CutoffWarped, 2*Order) / (1 + pow (FreqWarped/CutoffWarped, 2*Order))); - break; - } - break; - - case 1: // Chebyshev Type 1 - double eps; eps = sqrt(pow (10.0, __max(0.001, Ripple)/10.0) - 1); - switch (FilterSubtype) - { - case 0: // lowpass - default: - Magn = sqrt (1 / (1 + square(eps) * square(ChebyPoly(Order, FreqWarped/CutoffWarped)))); - break; - case 1: - Magn = sqrt (1 / (1 + square(eps) * square(ChebyPoly(Order, CutoffWarped/FreqWarped)))); - break; - } - break; - - case 2: // Chebyshev Type 2 - eps = 1 / sqrt(pow (10.0, __max(0.001, StopbandRipple)/10.0) - 1); - switch (FilterSubtype) - { - case 0: // lowpass - default: - Magn = sqrt (1 / (1 + 1 / (square(eps) * square(ChebyPoly(Order, CutoffWarped/FreqWarped))))); - break; - case 1: - Magn = sqrt (1 / (1 + 1 / (square(eps) * square(ChebyPoly(Order, FreqWarped/CutoffWarped))))); - break; - } - break; - } - - return Magn; -} - - - -// WDR: handler implementations for ScienFilterDialog - -void ScienFilterDialog::OnOrder(wxCommandEvent &WXUNUSED(event)) -{ - Order = mFilterOrderCtl->GetSelection() + 1; // 0..n-1 -> 1..n - mPanel->Refresh (false); -} - -void ScienFilterDialog::OnFilterType (wxCommandEvent &WXUNUSED(event)) -{ - FilterType = mFilterTypeCtl->GetSelection(); - EnableDisableRippleCtl (FilterType); - mPanel->Refresh (false); -} - -void ScienFilterDialog::OnFilterSubtype (wxCommandEvent &WXUNUSED(event)) -{ - FilterSubtype = mFilterSubTypeCtl->GetSelection(); - mPanel->Refresh (false); -} - -void ScienFilterDialog::OnCutoff (wxCommandEvent &WXUNUSED(event)) -{ - double CutoffTemp; - if (mCutoffCtl) - { - if (mCutoffCtl->GetValue().ToDouble(&CutoffTemp)) - { - Cutoff = CutoffTemp; - if (Cutoff >= mNyquist) - { - Cutoff = mNyquist - 1; // could handle Nyquist as a special case? eg. straight through if LPF - mCutoffCtl->SetValue(Internat::ToDisplayString(Cutoff)); - } - wxButton *ok = (wxButton *) FindWindow(wxID_OK); - if (Cutoff < 0.1) // 0.1 Hz min - { - // Disable OK button - ok->Enable(0); - } - else - ok->Enable(1); - } - mPanel->Refresh (false); - } -} - -void ScienFilterDialog::OnRipple (wxCommandEvent &WXUNUSED(event)) -{ - double RippleTemp; - if (mRippleCtl) - { - if (mRippleCtl->GetValue().ToDouble(&RippleTemp)) - Ripple = RippleTemp; - mPanel->Refresh (false); - } -} - -void ScienFilterDialog::OnStopbandRipple (wxCommandEvent &WXUNUSED(event)) -{ - double RippleTemp; - if (mStopbandRippleCtl) - { - if (mStopbandRippleCtl->GetValue().ToDouble(&RippleTemp)) - StopbandRipple = RippleTemp; - mPanel->Refresh (false); - } -} - -void ScienFilterDialog::OnSliderDBMIN(wxCommandEvent &WXUNUSED(event)) -{ - TransferGraphLimitsFromWindow(); -} - -void ScienFilterDialog::OnSliderDBMAX(wxCommandEvent &WXUNUSED(event)) -{ - TransferGraphLimitsFromWindow(); -} - - -void ScienFilterDialog::OnErase(wxEraseEvent &WXUNUSED(event)) -{ - // Ignore it -} - -void ScienFilterDialog::OnPaint(wxPaintEvent &WXUNUSED(event)) -{ - wxPaintDC dc(this); - -#if defined(__WXGTK__) - dc.SetBackground(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE))); -#endif - - dc.Clear(); -} - -void ScienFilterDialog::OnSize(wxSizeEvent &event) -{ - Layout(); - - event.Skip(); -} - -void ScienFilterDialog::OnPreview(wxCommandEvent &WXUNUSED(event)) -{ - CalcFilter (m_pEffect); - m_pEffect->Preview(); -} - - -void ScienFilterDialog::Finish(bool ok) -{ - mPanel = NULL; - EndModal(ok); -} - -void ScienFilterDialog::OnCancel(wxCommandEvent &WXUNUSED(event)) -{ - Finish(false); -} - -void ScienFilterDialog::OnOk(wxCommandEvent &event) -{ - CalcFilter (m_pEffect); - - if( Validate() ) - { - Finish(true); - } - else - { - event.Skip(false); - } -} - -void ScienFilterDialog::EnableDisableRippleCtl (int FilterType) -{ - if (FilterType == 0) // Butterworth - { - szrPass->Show(false); - szrStop->Show(false); - } - else if (FilterType == 1) // Chebyshev Type1 - { - szrPass->Show(true); - szrStop->Show(false); - } - else // Chebyshev Type2 - { - szrPass->Show(false); - szrStop->Show(true); - } - wxSizeEvent dummy; - OnSize(dummy); -} - +/********************************************************************** + + Audacity: A Digital Audio Editor + + Effect/ScienFilter.cpp + + Norm C + Mitch Golden + Vaughan Johnson (Preview) + +*******************************************************************//** + +\file ScienFilter.cpp +\brief Implements EffectScienFilter, EffectScienFilterPanel. + +*//****************************************************************//** + +\class EffectScienFilter +\brief An Effect. + + Performs IIR filtering that emulates analog filters, specifically + Butterworth, Chebyshev Type I and Type II. Highpass and lowpass filters + are supported, as are filter orders from 1 to 10. + + The filter is applied using biquads + +*//****************************************************************//** + +\class EffectScienFilterPanel +\brief EffectScienFilterPanel is used with EffectScienFilter and controls +a graph for EffectScienFilter. + +*//*******************************************************************/ + +#include "../Audacity.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../AColor.h" +#include "../AllThemeResources.h" +#include "../PlatformCompatibility.h" +#include "../Prefs.h" +#include "../Project.h" +#include "../Theme.h" +#include "../WaveTrack.h" +#include "../widgets/valnum.h" + +#include "Equalization.h" // For SliderAx +#include "ScienFilter.h" + +#if !defined(M_PI) +#define PI = 3.1415926535897932384626433832795 +#else +#define PI M_PI +#endif +#define square(a) ((a)*(a)) + +enum +{ + ID_FilterPanel = 10000, + ID_dBMax, + ID_dBMin, + ID_Type, + ID_SubType, + ID_Order, + ID_Ripple, + ID_Cutoff, + ID_StopbandRipple +}; + +enum kTypes +{ + kButterworth, + kChebyshevTypeI, + kChebyshevTypeII, + kNumTypes +}; + +static const wxChar *kTypeStrings[] = +{ + /*i18n-hint: Butterworth is the name of the person after whom the filter type is named.*/ + wxTRANSLATE("Butterworth"), + /*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/ + wxTRANSLATE("Chebyshev Type I"), + /*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/ + wxTRANSLATE("Chebyshev Type II") +}; + +enum kSubTypes +{ + kLowPass, + kHighPass, + kNumSubTypes +}; + +static const wxChar *kSubTypeStrings[] = +{ + wxTRANSLATE("Lowpass"), + wxTRANSLATE("Highpass") +}; + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( Type, int, wxTRANSLATE("FilterType"), kButterworth, 0, kNumTypes - 1, 1 ); +Param( Subtype, int, wxTRANSLATE("FilterSubtype"), kLowPass, 0, kNumSubTypes - 1, 1 ); +Param( Order, int, wxTRANSLATE("Order"), 1, 1, 10, 1 ); +Param( Cutoff, float, wxTRANSLATE("Cutoff"), 1000.0, 1.0, FLT_MAX, 1 ); +Param( Passband, float, wxTRANSLATE("PassbandRipple"), 1.0, 0.0, 100.0, 1 ); +Param( Stopband, float, wxTRANSLATE("StopbandRipple"), 30.0, 0.0, 100.0, 1 ); + +static const double s_fChebyCoeffs[MAX_Order][MAX_Order + 1] = +{ + // For Chebyshev polynomials of the first kind (see http://en.wikipedia.org/wiki/Chebyshev_polynomial) + // Coeffs are in the order 0, 1, 2...9 + { 0, 1}, // order 1 + {-1, 0, 2}, // order 2 etc. + { 0, -3, 0, 4}, + { 1, 0, -8, 0, 8}, + { 0, 5, 0, -20, 0, 16}, + {-1, 0, 18, 0, -48, 0, 32}, + { 0, -7, 0, 56, 0, -112, 0, 64}, + { 1, 0, -32, 0, 160, 0, -256, 0, 128}, + { 0, 9, 0, -120, 0, 432, 0, -576, 0, 256}, + {-1, 0, 50, 0, -400, 0, 1120, 0, -1280, 0, 512} +}; + +//---------------------------------------------------------------------------- +// EffectScienFilter +//---------------------------------------------------------------------------- + +BEGIN_EVENT_TABLE(EffectScienFilter, wxEvtHandler) + EVT_SIZE(EffectScienFilter::OnSize) + + EVT_SLIDER(ID_dBMax, EffectScienFilter::OnSliderDBMAX) + EVT_SLIDER(ID_dBMin, EffectScienFilter::OnSliderDBMIN) + EVT_CHOICE(ID_Order, EffectScienFilter::OnOrder) + EVT_CHOICE(ID_Type, EffectScienFilter::OnFilterType) + EVT_CHOICE(ID_SubType, EffectScienFilter::OnFilterSubtype) + EVT_TEXT(ID_Cutoff, EffectScienFilter::OnCutoff) + EVT_TEXT(ID_Ripple, EffectScienFilter::OnRipple) + EVT_TEXT(ID_StopbandRipple, EffectScienFilter::OnStopbandRipple) +END_EVENT_TABLE() + +EffectScienFilter::EffectScienFilter() +{ + mOrder = DEF_Order; + mFilterType = DEF_Type; + mFilterSubtype = DEF_Subtype; + mCutoff = DEF_Cutoff; + mRipple = DEF_Passband; + mStopbandRipple = DEF_Stopband; + + mOrderIndex = mOrder - 1; + + mdBMin = -30.0; + mdBMax = 30.0; + + mLoFreq = 20; // Lowest frequency to display in response graph + mNyquist = 44100.0 / 2.0; // only used during initialization, updated when effect is used + + mpBiquad = new BiquadStruct[MAX_Order / 2]; + memset(mpBiquad, 0, sizeof(BiquadStruct) * MAX_Order / 2); + for (int i = 0; i < MAX_Order / 2; i++) + { + mpBiquad[i].fNumerCoeffs[0] = 1.0; // straight-through + } +} + +EffectScienFilter::~EffectScienFilter() +{ + delete [] mpBiquad; +} + +// IdentInterface implementation + +wxString EffectScienFilter::GetSymbol() +{ + return CLASSICFILTERS_PLUGIN_SYMBOL; +} + +wxString EffectScienFilter::GetDescription() +{ + return wxTRANSLATE("Performs IIR filtering that emulates analog filters"); +} + +// EffectIdentInterface implementation + +EffectType EffectScienFilter::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +int EffectScienFilter::GetAudioInCount() +{ + return 1; +} + +int EffectScienFilter::GetAudioOutCount() +{ + return 1; +} + +bool EffectScienFilter::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) +{ + for (int iPair = 0; iPair < (mOrder + 1) / 2; iPair++) + { + mpBiquad[iPair].fPrevIn = 0; + mpBiquad[iPair].fPrevPrevIn = 0; + mpBiquad[iPair].fPrevOut = 0; + mpBiquad[iPair].fPrevPrevOut = 0; + } + + return true; +} + +sampleCount EffectScienFilter::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) +{ + float *ibuf = inBlock[0]; + for (int iPair = 0; iPair < (mOrder + 1) / 2; iPair++) + { + mpBiquad[iPair].pfIn = ibuf; + mpBiquad[iPair].pfOut = outBlock[0]; + Biquad_Process(&mpBiquad[iPair], blockLen); + ibuf = outBlock[0]; + } + + return blockLen; +} + +bool EffectScienFilter::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_Type, kTypeStrings[mFilterType]); + parms.Write(KEY_Subtype, kSubTypeStrings[mFilterSubtype]); + parms.Write(KEY_Order, mOrder); + parms.WriteFloat(KEY_Cutoff, mCutoff); + parms.WriteFloat(KEY_Passband, mRipple); + parms.WriteFloat(KEY_Stopband, mStopbandRipple); + + return true; +} + +bool EffectScienFilter::SetAutomationParameters(EffectAutomationParameters & parms) +{ + ReadAndVerifyEnum(Type, wxArrayString(kNumTypes, kTypeStrings)); + ReadAndVerifyEnum(Subtype, wxArrayString(kNumSubTypes, kSubTypeStrings)); + ReadAndVerifyInt(Order); + ReadAndVerifyFloat(Cutoff); + ReadAndVerifyFloat(Passband); + ReadAndVerifyFloat(Stopband); + + mFilterType = Type; + mFilterSubtype = Subtype; + mOrder = Order; + mCutoff = Cutoff; + mRipple = Passband; + mStopbandRipple = Stopband; + + mOrderIndex = mOrder - 1; + + CalcFilter(); + + return true; +} + +// Effect implementation + +bool EffectScienFilter::Startup() +{ + wxString base = wxT("/SciFilter/"); + + // Migrate settings from 2.1.0 or before + + // Already migrated, so bail + if (gPrefs->Exists(base + wxT("Migrated"))) + { + return true; + } + + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + double dTemp; + gPrefs->Read(base + wxT("Order"), &mOrder, 1); + mOrder = wxMax (1, mOrder); + mOrder = wxMin (MAX_Order, mOrder); + gPrefs->Read(base + wxT("FilterType"), &mFilterType, 0); + mFilterType = wxMax (0, mFilterType); + mFilterType = wxMin (2, mFilterType); + gPrefs->Read(base + wxT("FilterSubtype"), &mFilterSubtype, 0); + mFilterSubtype = wxMax (0, mFilterSubtype); + mFilterSubtype = wxMin (1, mFilterSubtype); + gPrefs->Read(base + wxT("Cutoff"), &dTemp, 1000.0); + mCutoff = (float)dTemp; + mCutoff = wxMax (1, mCutoff); + mCutoff = wxMin (100000, mCutoff); + gPrefs->Read(base + wxT("Ripple"), &dTemp, 1.0); + mRipple = dTemp; + mRipple = wxMax (0, mRipple); + mRipple = wxMin (100, mRipple); + gPrefs->Read(base + wxT("StopbandRipple"), &dTemp, 30.0); + mStopbandRipple = dTemp; + mStopbandRipple = wxMax (0, mStopbandRipple); + mStopbandRipple = wxMin (100, mStopbandRipple); + + SaveUserPreset(GetCurrentSettingsGroup()); + + // Do not migrate again + gPrefs->Write(base + wxT("Migrated"), true); + gPrefs->Flush(); + } + + return true; +} + +bool EffectScienFilter::Init() +{ + int selcount = 0; + double rate = 0.0; + + TrackListOfKindIterator iter(Track::Wave, mTracks); + WaveTrack *t = (WaveTrack *) iter.First(); + + mNyquist = (t ? t->GetRate() : GetActiveProject()->GetRate()) / 2.0; + + while (t) + { + if (t->GetSelected() && t->GetKind() == Track::Wave) + { + if (selcount == 0) + { + rate = t->GetRate(); + } + else + { + if (t->GetRate() != rate) + { + wxMessageBox(_("To apply a filter, all selected tracks must have the same sample rate.")); + return false; + } + } + selcount++; + } + t = (WaveTrack *) iter.Next(); + } + + return true; +} + +void EffectScienFilter::PopulateOrExchange(ShuttleGui & S) +{ + wxWindow *parent = S.GetParent(); + + S.AddSpace(5); + S.SetSizerProportion(1); + S.StartMultiColumn(3, wxEXPAND); + { + S.SetStretchyCol(1); + S.SetStretchyRow(0); + + // ------------------------------------------------------------------- + // ROW 1: Freq response panel and sliders for vertical scale + // ------------------------------------------------------------------- + + S.StartVerticalLay(); + { + mdBRuler = new RulerPanel(parent, wxID_ANY); + mdBRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes + mdBRuler->ruler.SetOrientation(wxVERTICAL); + mdBRuler->ruler.SetRange(30.0, -120.0); + mdBRuler->ruler.SetFormat(Ruler::LinearDBFormat); + mdBRuler->ruler.SetUnits(_("dB")); + mdBRuler->ruler.SetLabelEdges(true); + int w; + mdBRuler->ruler.GetMaxSize(&w, NULL); + mdBRuler->SetSize(wxSize(w, 150)); // height needed for wxGTK + + S.SetBorder(1); + S.AddSpace(1, 1); + S.Prop(1); + S.AddWindow(mdBRuler, wxEXPAND | wxALIGN_RIGHT | wxTOP); + S.AddSpace(1, 1); + } + S.EndVerticalLay(); + + mPanel = new EffectScienFilterPanel(this, parent); + mPanel->SetFreqRange(mLoFreq, mNyquist); + + S.SetBorder(5); + S.Prop(1); + S.AddWindow(mPanel, wxEXPAND | wxALIGN_CENTRE | wxRIGHT); + S.SetSizeHints(-1, -1); + + S.StartVerticalLay(); + { + S.AddVariableText(_("+ dB"), false, wxCENTER); + S.SetStyle(wxSL_VERTICAL | wxSL_INVERSE); + mdBMaxSlider = S.Id(ID_dBMax).AddSlider(wxT(""), 10, 20, 0); +#if wxUSE_ACCESSIBILITY + mdBMaxSlider->SetName(_("Max dB")); + mdBMaxSlider->SetAccessible(new SliderAx(mdBMaxSlider, wxString(wxT("%d ")) + _("dB"))); +#endif + + S.SetStyle(wxSL_VERTICAL | wxSL_INVERSE); + mdBMinSlider = S.Id(ID_dBMin).AddSlider(wxT(""), -10, -10, -120); + S.AddVariableText(_("- dB"), false, wxCENTER); +#if wxUSE_ACCESSIBILITY + mdBMinSlider->SetName(_("Min dB")); + mdBMinSlider->SetAccessible(new SliderAx(mdBMinSlider, wxString(wxT("%d ")) + _("dB"))); +#endif + } + S.EndVerticalLay(); + + // ------------------------------------------------------------------- + // ROW 2: Frequency ruler + // ------------------------------------------------------------------- + + S.AddSpace(1, 1); + + mfreqRuler = new RulerPanel(parent, wxID_ANY); + mfreqRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes + mfreqRuler->ruler.SetOrientation(wxHORIZONTAL); + mfreqRuler->ruler.SetLog(true); + mfreqRuler->ruler.SetRange(mLoFreq, mNyquist); + mfreqRuler->ruler.SetFormat(Ruler::IntFormat); + mfreqRuler->ruler.SetUnits(wxT("")); + mfreqRuler->ruler.SetFlip(true); + mfreqRuler->ruler.SetLabelEdges(true); + int h; + mfreqRuler->ruler.GetMaxSize(NULL, &h); + mfreqRuler->SetMinSize(wxSize(-1, h)); + + S.Prop(1); + S.AddWindow(mfreqRuler, wxEXPAND | wxALIGN_LEFT | wxRIGHT); + S.SetSizeHints(-1, -1); + + S.AddSpace(1, 1); + + // ------------------------------------------------------------------- + // ROW 3 and 4: Type, Order, Ripple, Subtype, Cutoff + // ------------------------------------------------------------------- + + S.AddSpace(1, 1); + S.SetSizerProportion(0); + S.StartMultiColumn(8, wxALIGN_CENTER); + { + wxASSERT(kNumTypes == WXSIZEOF(kTypeStrings)); + + wxArrayString typeChoices; + for (int i = 0; i < kNumTypes; i++) + { + typeChoices.Add(wxGetTranslation(kTypeStrings[i])); + } + + mFilterTypeCtl = S.Id(ID_Type).AddChoice(_("&Filter Type:"), wxT(""), &typeChoices); + mFilterTypeCtl->SetValidator(wxGenericValidator(&mFilterType)); + S.SetSizeHints(-1, -1); + + wxArrayString orders; + for (int i = 1; i <= 10; i++) + { + orders.Add(wxString::Format(wxT("%d"), i)); + } + /*i18n-hint: 'Order' means the complexity of the filter, and is a number between 1 and 10.*/ + mFilterOrderCtl = S.Id(ID_Order).AddChoice(_("O&rder:"), wxT(""), &orders); + mFilterOrderCtl->SetValidator(wxGenericValidator(&mOrderIndex)); + S.SetSizeHints(-1, -1); + S.AddSpace(1, 1); + + FloatingPointValidator vldRipple(1, &mRipple); + vldRipple.SetRange(MIN_Passband, MAX_Passband); + + mRippleCtlP = S.AddVariableText(_("&Passband Ripple:"), false, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + mRippleCtl = S.Id(ID_Ripple).AddTextBox(wxT(""), wxT(""), 10); + mRippleCtl->SetName(_("Passband Ripple (dB)")); + mRippleCtl->SetValidator(vldRipple); + mRippleCtlU = S.AddVariableText(_("dB"), false, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + + wxASSERT(kNumSubTypes == WXSIZEOF(kSubTypeStrings)); + + wxArrayString subTypeChoices; + for (int i = 0; i < kNumSubTypes; i++) + { + subTypeChoices.Add(wxGetTranslation(kSubTypeStrings[i])); + } + + mFilterSubTypeCtl = S.Id(ID_SubType).AddChoice(_("&Subtype:"), wxT(""), &subTypeChoices); + mFilterSubTypeCtl->SetValidator(wxGenericValidator(&mFilterSubtype)); + S.SetSizeHints(-1, -1); + + FloatingPointValidator vldCutoff(1, &mCutoff); + vldCutoff.SetRange(MIN_Cutoff, mNyquist - 1); + + mCutoffCtl = S.Id(ID_Cutoff).AddTextBox(_("C&utoff:"), wxT(""), 10); + mCutoffCtl->SetName(_("Cutoff (Hz)")); + mCutoffCtl->SetValidator(vldCutoff); + S.AddUnits(_("Hz")); + + FloatingPointValidator vldStopbandRipple(1, &mStopbandRipple); + vldStopbandRipple.SetRange(MIN_Stopband, MAX_Stopband); + + mStopbandRippleCtlP = S.AddVariableText(_("Minimum S&topband Attenuation:"), false, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + mStopbandRippleCtl = S.Id(ID_StopbandRipple).AddTextBox(wxT(""), wxT(""), 10); + mStopbandRippleCtl->SetName(_("Minimum S&topband Attenuation (dB)")); + mStopbandRippleCtl->SetValidator(vldStopbandRipple); + mStopbandRippleCtlU = S.AddVariableText(_("dB"), false, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + } + S.EndMultiColumn(); + S.AddSpace(1, 1); + } + S.EndMultiColumn(); + + mFilterTypeCtl->SetFocus(); + + return; +} + +// +// Populate the window with relevant variables +// +bool EffectScienFilter::TransferDataToWindow() +{ + mOrderIndex = mOrder - 1; + + if (!mUIParent->TransferDataToWindow()) + { + return false; + } + + mdBMinSlider->SetValue((int) mdBMin); + mdBMin = 0.0; // force refresh in TransferGraphLimitsFromWindow() + + mdBMaxSlider->SetValue((int) mdBMax); + mdBMax = 0.0; // force refresh in TransferGraphLimitsFromWindow() + + EnableDisableRippleCtl(mFilterType); + + return TransferGraphLimitsFromWindow(); +} + +bool EffectScienFilter::TransferDataFromWindow() +{ + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } + + mOrder = mOrderIndex + 1; + + CalcFilter(); + + return true; +} + +// EffectScienFilter implementation + +// +// Retrieve data from the window +// +bool EffectScienFilter::TransferGraphLimitsFromWindow() +{ + // Read the sliders and send to the panel + wxString tip; + + bool rr = false; + int dB = mdBMinSlider->GetValue(); + if (dB != mdBMin) { + rr = true; + mdBMin = dB; + tip.Printf(wxString(wxT("%d ")) + _("dB"), (int)mdBMin); + mdBMinSlider->SetToolTip(tip); + } + + dB = mdBMaxSlider->GetValue(); + if (dB != mdBMax) { + rr = true; + mdBMax = dB; + tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)mdBMax); + mdBMaxSlider->SetToolTip(tip); + } + + if (rr) { + mPanel->SetDbRange(mdBMin, mdBMax); + } + + // Refresh ruler if values have changed + if (rr) { + int w1, w2, h; + mdBRuler->ruler.GetMaxSize(&w1, &h); + mdBRuler->ruler.SetRange(mdBMax, mdBMin); + mdBRuler->ruler.GetMaxSize(&w2, &h); + if( w1 != w2 ) // Reduces flicker + { + mdBRuler->SetSize(wxSize(w2,h)); + mUIParent->Layout(); + mfreqRuler->Refresh(false); + } + mdBRuler->Refresh(false); + } + + mPanel->Refresh(false); + + return true; +} + +bool EffectScienFilter::CalcFilter() +{ + // Set up the coefficients in all the biquads + float fNorm = mCutoff / mNyquist; + if (fNorm >= 0.9999) + fNorm = 0.9999F; + float fC = tan (PI * fNorm / 2); + float fDCPoleDistSqr = 1.0F; + float fZPoleX, fZPoleY; + float fZZeroX, fZZeroY; + float beta = cos (fNorm*PI); + switch (mFilterType) + { + case kButterworth: // Butterworth + if ((mOrder & 1) == 0) + { + // Even order + for (int iPair = 0; iPair < mOrder/2; iPair++) + { + float fSPoleX = fC * cos (PI - (iPair + 0.5) * PI / mOrder); + float fSPoleY = fC * sin (PI - (iPair + 0.5) * PI / mOrder); + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + mpBiquad[iPair].fNumerCoeffs [0] = 1; + if (mFilterSubtype == kLowPass) // LOWPASS + mpBiquad[iPair].fNumerCoeffs [1] = 2; + else + mpBiquad[iPair].fNumerCoeffs [1] = -2; + mpBiquad[iPair].fNumerCoeffs [2] = 1; + mpBiquad[iPair].fDenomCoeffs [0] = -2 * fZPoleX; + mpBiquad[iPair].fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); + if (mFilterSubtype == kLowPass) // LOWPASS + fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); + else + fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist + } + } + else + { + // Odd order - first do the 1st-order section + float fSPoleX = -fC; + float fSPoleY = 0; + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + mpBiquad[0].fNumerCoeffs [0] = 1; + if (mFilterSubtype == kLowPass) // LOWPASS + mpBiquad[0].fNumerCoeffs [1] = 1; + else + mpBiquad[0].fNumerCoeffs [1] = -1; + mpBiquad[0].fNumerCoeffs [2] = 0; + mpBiquad[0].fDenomCoeffs [0] = -fZPoleX; + mpBiquad[0].fDenomCoeffs [1] = 0; + if (mFilterSubtype == kLowPass) // LOWPASS + fDCPoleDistSqr = 1 - fZPoleX; + else + fDCPoleDistSqr = fZPoleX + 1; // dist from Nyquist + for (int iPair = 1; iPair <= mOrder/2; iPair++) + { + float fSPoleX = fC * cos (PI - iPair * PI / mOrder); + float fSPoleY = fC * sin (PI - iPair * PI / mOrder); + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + mpBiquad[iPair].fNumerCoeffs [0] = 1; + if (mFilterSubtype == kLowPass) // LOWPASS + mpBiquad[iPair].fNumerCoeffs [1] = 2; + else + mpBiquad[iPair].fNumerCoeffs [1] = -2; + mpBiquad[iPair].fNumerCoeffs [2] = 1; + mpBiquad[iPair].fDenomCoeffs [0] = -2 * fZPoleX; + mpBiquad[iPair].fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); + if (mFilterSubtype == kLowPass) // LOWPASS + fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); + else + fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist + } + } + mpBiquad[0].fNumerCoeffs [0] *= fDCPoleDistSqr / (1 << mOrder); // mult by DC dist from poles, divide by dist from zeroes + mpBiquad[0].fNumerCoeffs [1] *= fDCPoleDistSqr / (1 << mOrder); + mpBiquad[0].fNumerCoeffs [2] *= fDCPoleDistSqr / (1 << mOrder); + break; + + case kChebyshevTypeI: // Chebyshev Type 1 + double eps; eps = sqrt (pow (10.0, wxMax(0.001, mRipple) / 10.0) - 1); + double a; a = log (1 / eps + sqrt(1 / square(eps) + 1)) / mOrder; + // Assume even order to start + for (int iPair = 0; iPair < mOrder/2; iPair++) + { + float fSPoleX = -fC * sinh (a) * sin ((2*iPair + 1) * PI / (2 * mOrder)); + float fSPoleY = fC * cosh (a) * cos ((2*iPair + 1) * PI / (2 * mOrder)); + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + if (mFilterSubtype == kLowPass) // LOWPASS + { + fZZeroX = -1; + fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); + fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist + } + else + { + // Highpass - do the digital LP->HP transform on the poles and zeroes + ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); + fZZeroX = 1; + fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist + fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist + } + mpBiquad[iPair].fNumerCoeffs [0] = fDCPoleDistSqr; + mpBiquad[iPair].fNumerCoeffs [1] = -2 * fZZeroX * fDCPoleDistSqr; + mpBiquad[iPair].fNumerCoeffs [2] = fDCPoleDistSqr; + mpBiquad[iPair].fDenomCoeffs [0] = -2 * fZPoleX; + mpBiquad[iPair].fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); + } + if ((mOrder & 1) == 0) + { + float fTemp = pow (10.0, -wxMax(0.001, mRipple) / 20.0); // at DC the response is down R dB (for even-order) + mpBiquad[0].fNumerCoeffs [0] *= fTemp; + mpBiquad[0].fNumerCoeffs [1] *= fTemp; + mpBiquad[0].fNumerCoeffs [2] *= fTemp; + } + else + { + // Odd order - now do the 1st-order section + float fSPoleX = -fC * sinh (a); + float fSPoleY = 0; + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + if (mFilterSubtype == kLowPass) // LOWPASS + { + fZZeroX = -1; + fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY)); + fDCPoleDistSqr /= 2; // dist from zero at Nyquist + } + else + { + // Highpass - do the digital LP->HP transform on the poles and zeroes + ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); + fZZeroX = 1; + fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist + fDCPoleDistSqr /= 2; // dist from zero at Nyquist + } + mpBiquad[(mOrder-1)/2].fNumerCoeffs [0] = fDCPoleDistSqr; + mpBiquad[(mOrder-1)/2].fNumerCoeffs [1] = -fZZeroX * fDCPoleDistSqr; + mpBiquad[(mOrder-1)/2].fNumerCoeffs [2] = 0; + mpBiquad[(mOrder-1)/2].fDenomCoeffs [0] = -fZPoleX; + mpBiquad[(mOrder-1)/2].fDenomCoeffs [1] = 0; + } + break; + + case kChebyshevTypeII: // Chebyshev Type 2 + float fSZeroX, fSZeroY; + float fSPoleX, fSPoleY; + eps = pow (10.0, -wxMax(0.001, mStopbandRipple) / 20.0); + a = log (1 / eps + sqrt(1 / square(eps) + 1)) / mOrder; + + // Assume even order + for (int iPair = 0; iPair < mOrder/2; iPair++) + { + ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * mOrder)), + cosh (a) * cos ((2*iPair + 1) * PI / (2 * mOrder)), + &fSPoleX, &fSPoleY); + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + fSZeroX = 0; + fSZeroY = fC / cos (((2 * iPair) + 1) * PI / (2 * mOrder)); + BilinTransform (fSZeroX, fSZeroY, &fZZeroX, &fZZeroY); + + if (mFilterSubtype == kLowPass) // LOWPASS + { + fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); + fDCPoleDistSqr /= Calc2D_DistSqr (1, 0, fZZeroX, fZZeroY); + } + else + { + // Highpass - do the digital LP->HP transform on the poles and zeroes + ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); + ComplexDiv (beta - fZZeroX, -fZZeroY, 1 - beta * fZZeroX, -beta * fZZeroY, &fZZeroX, &fZZeroY); + fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist + fDCPoleDistSqr /= Calc2D_DistSqr (-1, 0, fZZeroX, fZZeroY); + } + mpBiquad[iPair].fNumerCoeffs [0] = fDCPoleDistSqr; + mpBiquad[iPair].fNumerCoeffs [1] = -2 * fZZeroX * fDCPoleDistSqr; + mpBiquad[iPair].fNumerCoeffs [2] = (square(fZZeroX) + square(fZZeroY)) * fDCPoleDistSqr; + mpBiquad[iPair].fDenomCoeffs [0] = -2 * fZPoleX; + mpBiquad[iPair].fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); + } + // Now, if it's odd order, we have one more to do + if (mOrder & 1) + { + int iPair = (mOrder-1)/2; // we'll do it as a biquad, but it's just first-order + ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * mOrder)), + cosh (a) * cos ((2*iPair + 1) * PI / (2 * mOrder)), + &fSPoleX, &fSPoleY); + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + fZZeroX = -1; // in the s-plane, the zero is at infinity + fZZeroY = 0; + if (mFilterSubtype == kLowPass) // LOWPASS + { + fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY)); + fDCPoleDistSqr /= 2; + } + else + { + // Highpass - do the digital LP->HP transform on the poles and zeroes + ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -fZPoleY, &fZPoleX, &fZPoleY); + fZZeroX = 1; + fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist + fDCPoleDistSqr /= 2; + } + mpBiquad[iPair].fNumerCoeffs [0] = fDCPoleDistSqr; + mpBiquad[iPair].fNumerCoeffs [1] = -fZZeroX * fDCPoleDistSqr; + mpBiquad[iPair].fNumerCoeffs [2] = 0; + mpBiquad[iPair].fDenomCoeffs [0] = -fZPoleX; + mpBiquad[iPair].fDenomCoeffs [1] = 0; + } + break; + } + return true; +} + +double EffectScienFilter::ChebyPoly(int Order, double NormFreq) // NormFreq = 1 at the f0 point (where response is R dB down) +{ + // Calc cosh (Order * acosh (NormFreq)); + double x = 1; + double fSum = 0; + wxASSERT (Order >= MIN_Order && Order <= MAX_Order); + for (int i = 0; i <= Order; i++) + { + fSum += s_fChebyCoeffs [Order-1][i] * x; + x *= NormFreq; + } + return fSum; +} + +float EffectScienFilter::FilterMagnAtFreq(float Freq) +{ + float Magn; + if (Freq >= mNyquist) + Freq = mNyquist - 1; // prevent tan(PI/2) + float FreqWarped = tan (PI * Freq/(2*mNyquist)); + if (mCutoff >= mNyquist) + mCutoff = mNyquist - 1; + float CutoffWarped = tan (PI * mCutoff/(2*mNyquist)); + float fOverflowThresh = pow (10.0, 12.0 / (2*mOrder)); // once we exceed 10^12 there's not much to be gained and overflow could happen + + switch (mFilterType) + { + case kButterworth: // Butterworth + default: + switch (mFilterSubtype) + { + case kLowPass: // lowpass + default: + if (FreqWarped/CutoffWarped > fOverflowThresh) // prevent pow() overflow + Magn = 0; + else + Magn = sqrt (1 / (1 + pow (FreqWarped/CutoffWarped, 2*mOrder))); + break; + case kHighPass: // highpass + if (FreqWarped/CutoffWarped > fOverflowThresh) + Magn = 1; + else + Magn = sqrt (pow (FreqWarped/CutoffWarped, 2*mOrder) / (1 + pow (FreqWarped/CutoffWarped, 2*mOrder))); + break; + } + break; + + case kChebyshevTypeI: // Chebyshev Type 1 + double eps; eps = sqrt(pow (10.0, wxMax(0.001, mRipple)/10.0) - 1); + switch (mFilterSubtype) + { + case 0: // lowpass + default: + Magn = sqrt (1 / (1 + square(eps) * square(ChebyPoly(mOrder, FreqWarped/CutoffWarped)))); + break; + case 1: + Magn = sqrt (1 / (1 + square(eps) * square(ChebyPoly(mOrder, CutoffWarped/FreqWarped)))); + break; + } + break; + + case kChebyshevTypeII: // Chebyshev Type 2 + eps = 1 / sqrt(pow (10.0, wxMax(0.001, mStopbandRipple)/10.0) - 1); + switch (mFilterSubtype) + { + case kLowPass: // lowpass + default: + Magn = sqrt (1 / (1 + 1 / (square(eps) * square(ChebyPoly(mOrder, CutoffWarped/FreqWarped))))); + break; + case kHighPass: + Magn = sqrt (1 / (1 + 1 / (square(eps) * square(ChebyPoly(mOrder, FreqWarped/CutoffWarped))))); + break; + } + break; + } + + return Magn; +} + +void EffectScienFilter::OnOrder(wxCommandEvent & WXUNUSED(evt)) +{ + mOrderIndex = mFilterOrderCtl->GetSelection(); + mOrder = mOrderIndex + 1; // 0..n-1 -> 1..n + mPanel->Refresh(false); +} + +void EffectScienFilter::OnFilterType(wxCommandEvent & WXUNUSED(evt)) +{ + mFilterType = mFilterTypeCtl->GetSelection(); + EnableDisableRippleCtl(mFilterType); + mPanel->Refresh(false); +} + +void EffectScienFilter::OnFilterSubtype(wxCommandEvent & WXUNUSED(evt)) +{ + mFilterSubtype = mFilterSubTypeCtl->GetSelection(); + mPanel->Refresh(false); +} + +void EffectScienFilter::OnCutoff(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; + } + + mPanel->Refresh(false); +} + +void EffectScienFilter::OnRipple(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; + } + + mPanel->Refresh(false); +} + +void EffectScienFilter::OnStopbandRipple(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; + } + + mPanel->Refresh(false); +} + +void EffectScienFilter::OnSliderDBMIN(wxCommandEvent & WXUNUSED(evt)) +{ + TransferGraphLimitsFromWindow(); +} + +void EffectScienFilter::OnSliderDBMAX(wxCommandEvent & WXUNUSED(evt)) +{ + TransferGraphLimitsFromWindow(); +} + +void EffectScienFilter::OnSize(wxSizeEvent & evt) +{ + // On Windows the Passband and Stopband boxes do not refresh properly + // on a resize...no idea why. + mUIParent->Refresh(); + evt.Skip(); +} + +void EffectScienFilter::EnableDisableRippleCtl(int FilterType) +{ + bool ripple; + bool stop; + + if (FilterType == kButterworth) // Butterworth + { + ripple = false; + stop = false; + } + else if (FilterType == kChebyshevTypeI) // Chebyshev Type1 + { + ripple = true; + stop = false; + } + else // Chebyshev Type2 + { + ripple = false; + stop = true; + } + + mRippleCtlP->Enable(ripple); + mRippleCtl->Enable(ripple); + mRippleCtlU->Enable(ripple); + mStopbandRippleCtlP->Enable(stop); + mStopbandRippleCtl->Enable(stop); + mStopbandRippleCtlU->Enable(stop); +} + +//---------------------------------------------------------------------------- +// EffectScienFilterPanel +//---------------------------------------------------------------------------- + +BEGIN_EVENT_TABLE(EffectScienFilterPanel, wxPanel) + EVT_PAINT(EffectScienFilterPanel::OnPaint) + EVT_SIZE(EffectScienFilterPanel::OnSize) +END_EVENT_TABLE() + +EffectScienFilterPanel::EffectScienFilterPanel(EffectScienFilter *effect, wxWindow *parent) +: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(400, 200)) +{ + mEffect = effect; + mParent = parent; + + mBitmap = NULL; + mWidth = 0; + mHeight = 0; + mLoFreq = 0.0; + mHiFreq = 0.0; + mDbMin = 0.0; + mDbMax = 0.0; +} + +EffectScienFilterPanel::~EffectScienFilterPanel() +{ + if (mBitmap) + { + delete mBitmap; + } +} + +void EffectScienFilterPanel::SetFreqRange(double lo, double hi) +{ + mLoFreq = lo; + mHiFreq = hi; + Refresh(false); +} + +void EffectScienFilterPanel::SetDbRange(double min, double max) +{ + mDbMin = min; + mDbMax = max; + Refresh(false); +} + +bool EffectScienFilterPanel::AcceptsFocus() const +{ + return false; +} + +void EffectScienFilterPanel::OnSize(wxSizeEvent & WXUNUSED(evt)) +{ + Refresh(false); +} + +void EffectScienFilterPanel::OnPaint(wxPaintEvent & WXUNUSED(evt)) +{ + wxPaintDC dc(this); + int width, height; + GetSize(&width, &height); + + if (!mBitmap || mWidth != width || mHeight != height) + { + if (mBitmap) + { + delete mBitmap; + } + + mWidth = width; + mHeight = height; + mBitmap = new wxBitmap(mWidth, mHeight); + } + + wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)); + + wxMemoryDC memDC; + memDC.SelectObject(*mBitmap); + + wxRect bkgndRect; + bkgndRect.x = 0; + bkgndRect.y = 0; + bkgndRect.width = mWidth; + bkgndRect.height = mHeight; + memDC.SetBrush(bkgndBrush); + memDC.SetPen(*wxTRANSPARENT_PEN); + memDC.DrawRectangle(bkgndRect); + + bkgndRect.y = mHeight; + memDC.DrawRectangle(bkgndRect); + + wxRect border; + border.x = 0; + border.y = 0; + border.width = mWidth; + border.height = mHeight; + + memDC.SetBrush(*wxWHITE_BRUSH); + memDC.SetPen(*wxBLACK_PEN); + memDC.DrawRectangle(border); + + mEnvRect = border; + mEnvRect.Deflate(2, 2); + + // Pure blue x-axis line + memDC.SetPen(wxPen(theTheme.Colour(clrGraphLines), 1, wxSOLID)); + int center = (int) (mEnvRect.height * mDbMax / (mDbMax - mDbMin) + 0.5); + AColor::Line(memDC, + mEnvRect.GetLeft(), mEnvRect.y + center, + mEnvRect.GetRight(), mEnvRect.y + center); + + //Now draw the actual response that you will get. + //mFilterFunc has a linear scale, window has a log one so we have to fiddle about + memDC.SetPen(wxPen(theTheme.Colour(clrResponseLines), 3, wxSOLID)); + double scale = (double) mEnvRect.height / (mDbMax - mDbMin); // pixels per dB + double yF; // gain at this freq + + double loLog = log10(mLoFreq); + double step = log10(mHiFreq) - loLog; + step /= ((double) mEnvRect.width - 1.0); + double freq; // actual freq corresponding to x position + int x, y, xlast = 0, ylast = 0; + for (int i = 0; i < mEnvRect.width; i++) + { + x = mEnvRect.x + i; + freq = pow(10.0, loLog + i * step); //Hz + yF = mEffect->FilterMagnAtFreq (freq); + yF = 20.0 * log10(yF); + + if (yF < mDbMin) + { + yF = mDbMin; + } + + yF = center-scale * yF; + if (yF > mEnvRect.height) + { + yF = (double) mEnvRect.height - 1.0; + } + if (yF < 0.0) + { + yF = 0.0; + } + y = (int) (yF + 0.5); + + if (i != 0 && (y < mEnvRect.height - 1 || ylast < mEnvRect.y + mEnvRect.height - 1)) + { + AColor::Line(memDC, xlast, ylast, x, mEnvRect.y + y); + } + xlast = x; + ylast = mEnvRect.y + y; + } + + memDC.SetPen(*wxBLACK_PEN); + mEffect->mfreqRuler->ruler.DrawGrid(memDC, mEnvRect.height + 2, true, true, 0, 1); + mEffect->mdBRuler->ruler.DrawGrid(memDC, mEnvRect.width + 2, true, true, 1, 2); + + dc.Blit(0, 0, mWidth, mHeight, &memDC, 0, 0, wxCOPY, FALSE); + + memDC.SelectObject(wxNullBitmap); +} diff --git a/src/effects/ScienFilter.h b/src/effects/ScienFilter.h index ed8fddca3..26f76bdc1 100644 --- a/src/effects/ScienFilter.h +++ b/src/effects/ScienFilter.h @@ -13,247 +13,160 @@ Vaughan Johnson (Preview) #ifndef __AUDACITY_EFFECT_SCIENFILTER__ #define __AUDACITY_EFFECT_SCIENFILTER__ -#define MAX_FILTER_ORDER 10 - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include #include -#include +#include +#include +#include +#include +#include +#include -#if wxUSE_ACCESSIBILITY -#include -#endif - -#include "Effect.h" -#include "../WaveTrack.h" +#include "../ShuttleGui.h" #include "../widgets/Ruler.h" #include "Biquad.h" -class ScienFilterDialog; +#include "Effect.h" +#define CLASSICFILTERS_PLUGIN_SYMBOL wxTRANSLATE("Classic Filters") -class EffectScienFilter: public Effect { +class EffectScienFilterPanel; +class EffectScienFilter : public Effect +{ public: - EffectScienFilter(); virtual ~EffectScienFilter(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Classic Filters...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#EQPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Classic Filters")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Performing Classic Filtering")); - } + virtual EffectType GetType(); + // EffectClientInterface implementation + + virtual int GetAudioInCount(); + virtual int GetAudioOutCount(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + + // Effect implementation + + virtual bool Startup(); virtual bool Init(); - virtual bool PromptUser(); - virtual bool DontPromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); - bool CalcFilterCoeffs (void); - - virtual bool Process(); - - // Lowest frequency to display in response graph - enum {loFreqI=20}; + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); private: - bool ProcessOne(int count, WaveTrack * t, - sampleCount start, sampleCount len); + // EffectScienFilter implementation - void Filter(sampleCount len, - float *buffer); - void ReadPrefs(); + virtual bool TransferGraphLimitsFromWindow(); + virtual bool CalcFilter(); + double ChebyPoly (int Order, double NormFreq); + float FilterMagnAtFreq(float Freq); - //int mM; + bool CalcFilterCoeffs (void); + void EnableDisableRippleCtl (int FilterType); + + void OnSize( wxSizeEvent & evt ); + void OnSlider( wxCommandEvent & evt ); + + void OnOrder( wxCommandEvent & evt ); + void OnCutoff( wxCommandEvent & evt ); + void OnRipple( wxCommandEvent & evt ); + void OnStopbandRipple( wxCommandEvent & evt ); + void OnFilterType( wxCommandEvent & evt ); + void OnFilterSubtype( wxCommandEvent & evt ); + + void OnSliderDBMAX( wxCommandEvent & evt ); + void OnSliderDBMIN( wxCommandEvent & evt ); + +private: float mCutoff; - int mOrder; float mRipple; float mStopbandRipple; int mFilterType; // Butterworth etc. int mFilterSubtype; // lowpass, highpass - BiquadStruct* mpBiquad[5]; // MAX_ORDER/2 + int mOrder; + int mOrderIndex; + BiquadStruct *mpBiquad; double mdBMax; double mdBMin; - bool mPrompting; bool mEditingBatchParams; -public: - - friend class ScienFilterDialog; - friend class ScienFilterPanel; -}; - - -class ScienFilterPanel: public wxPanel -{ -public: - ScienFilterPanel( double loFreq, double hiFreq, - ScienFilterDialog *parent, - wxWindowID id, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize); - ~ScienFilterPanel(); - -#if 0 - Needed only if user can draw in the graph - void OnMouseEvent(wxMouseEvent & event); - void OnCaptureLost(wxMouseCaptureLostEvent & event); -#endif - void OnPaint(wxPaintEvent & event); - void OnSize (wxSizeEvent & event); - - // We don't need or want to accept focus. - bool AcceptsFocus() const { return false; } - - float dBMax; - float dBMin; - -private: - - wxBitmap *mBitmap; - wxRect mEnvRect; - ScienFilterDialog *mParent; - int mWidth; - int mHeight; - - double mLoFreq; - double mHiFreq; - - DECLARE_EVENT_TABLE() -}; - - -// WDR: class declarations - -//---------------------------------------------------------------------------- -// ScienFilterDialog -//---------------------------------------------------------------------------- - -class ScienFilterDialog: public wxDialog //, public XMLTagHandler -{ -public: - // constructors and destructors - ScienFilterDialog(EffectScienFilter * effect, - double loFreq, double hiFreq, - wxWindow *parent, wxWindowID id, - const wxString &title, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize, - long style = wxDEFAULT_DIALOG_STYLE ); - ~ScienFilterDialog(); - - // WDR: method declarations for ScienFilterDialog - virtual bool Validate(); - virtual bool TransferDataToWindow(); - virtual bool TransferGraphLimitsFromWindow(); - virtual bool CalcFilter(EffectScienFilter* effect); - float FilterMagnAtFreq (float Freq); - - wxChoice* mFilterTypeCtl; - wxChoice* mFilterSubTypeCtl; - wxChoice* mFilterOrderCtl; - - float Cutoff; - int Order; - float Ripple; - float StopbandRipple; - int FilterType; // Butterworth etc. - int FilterSubtype; // lowpass, highpass - - float dBMin; - float dBMax; - int interp; - RulerPanel *dBRuler; - RulerPanel *freqRuler; - -private: - void MakeScienFilterDialog(); - void Finish(bool ok); - -private: - // WDR: member variable declarations for ScienFilterDialog - - enum - { - ID_FILTERPANEL = 10000, - ID_DBMAX, - ID_DBMIN, - ID_FILTER_TYPE, - ID_FILTER_SUBTYPE, - ID_FILTER_ORDER, - ID_RIPPLE, - ID_CUTOFF, - ID_STOPBAND_RIPPLE - }; - -private: - // WDR: handler declarations for ScienFilterDialog - void OnPaint( wxPaintEvent &event ); - void OnSize( wxSizeEvent &event ); - void OnErase( wxEraseEvent &event ); - void OnSlider( wxCommandEvent &event ); - - void OnOrder( wxCommandEvent &event ); - void OnCutoff( wxCommandEvent &event ); - void OnRipple( wxCommandEvent &event ); - void OnStopbandRipple( wxCommandEvent &event ); - void OnFilterType( wxCommandEvent &event ); - void OnFilterSubtype( wxCommandEvent &event ); - - void OnSliderDBMAX( wxCommandEvent &event ); - void OnSliderDBMIN( wxCommandEvent &event ); - void OnPreview(wxCommandEvent &event); - void OnOk( wxCommandEvent &event ); - void OnCancel( wxCommandEvent &event ); - void EnableDisableRippleCtl (int FilterType); -private: - EffectScienFilter * m_pEffect; - double mLoFreq; double mNyquist; - ScienFilterPanel *mPanel; - wxSlider *dBMinSlider; - wxSlider *dBMaxSlider; - wxBoxSizer *szrV; - wxFlexGridSizer *szr3; - wxBoxSizer *szr4; - wxBoxSizer *szr2; - wxFlexGridSizer *szr1; - wxSize size; - wxTextCtrl* mRippleCtl; - wxTextCtrl* mStopbandRippleCtl; - wxTextCtrl* mCutoffCtl; + EffectScienFilterPanel *mPanel; + wxSlider *mdBMinSlider; + wxSlider *mdBMaxSlider; - // sizers for pass and stop-band attenuations - wxBoxSizer *szrPass; - wxBoxSizer *szrStop; + wxStaticText *mRippleCtlP; + wxTextCtrl *mRippleCtl; + wxStaticText *mRippleCtlU; + + wxTextCtrl *mCutoffCtl; + + wxStaticText *mStopbandRippleCtlP; + wxTextCtrl *mStopbandRippleCtl; + wxStaticText *mStopbandRippleCtlU; + + wxChoice *mFilterTypeCtl; + wxChoice *mFilterSubTypeCtl; + wxChoice *mFilterOrderCtl; + + RulerPanel *mdBRuler; + RulerPanel *mfreqRuler; + + DECLARE_EVENT_TABLE(); + + friend class EffectScienFilterPanel; +}; + +class EffectScienFilterPanel : public wxPanel +{ +public: + EffectScienFilterPanel(EffectScienFilter *effect, wxWindow *parent); + virtual ~EffectScienFilterPanel(); + + // We don't need or want to accept focus. + bool AcceptsFocus() const; + + void SetFreqRange(double lo, double hi); + void SetDbRange(double min, double max); private: - DECLARE_EVENT_TABLE() + void OnPaint(wxPaintEvent & evt); + void OnSize(wxSizeEvent & evt); +private: + EffectScienFilter *mEffect; + wxWindow *mParent; + + double mLoFreq; + double mHiFreq; + + double mDbMin; + double mDbMax; + + wxBitmap *mBitmap; + wxRect mEnvRect; + int mWidth; + int mHeight; + + friend class EffectScienFilter; + + DECLARE_EVENT_TABLE(); }; #if wxUSE_ACCESSIBILITY diff --git a/src/effects/ScoreAlignDialog.cpp b/src/effects/ScoreAlignDialog.cpp index 88cd8f23a..bdc8ee8f7 100644 --- a/src/effects/ScoreAlignDialog.cpp +++ b/src/effects/ScoreAlignDialog.cpp @@ -96,7 +96,7 @@ ScoreAlignDialog::ScoreAlignDialog(wxWindow *parent, ScoreAlignParams ¶ms) S.StartMultiColumn(3, wxEXPAND | wxALIGN_CENTER_VERTICAL); S.SetStretchyCol(1); - mFramePeriodLabel = S.AddVariableText(_("Frame Period")+wxString(wxT(":")), true, + mFramePeriodLabel = S.AddVariableText(_("Frame Period:"), true, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); S.SetStyle(wxSL_HORIZONTAL); mFramePeriodSlider = S.Id(ID_FRAMEPERIOD).AddSlider(wxT(""), @@ -106,7 +106,7 @@ ScoreAlignDialog::ScoreAlignDialog(wxWindow *parent, ScoreAlignParams ¶ms) mFramePeriodText = S.AddVariableText(SA_DFT_FRAME_PERIOD_TEXT, true, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - mWindowSizeLabel = S.AddVariableText(_("Window Size")+wxString(wxT(":")), true, + mWindowSizeLabel = S.AddVariableText(_("Window Size":), true, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); S.SetStyle(wxSL_HORIZONTAL); mWindowSizeSlider = S.Id(ID_WINDOWSIZE).AddSlider(wxT(""), @@ -140,7 +140,7 @@ ScoreAlignDialog::ScoreAlignDialog(wxWindow *parent, ScoreAlignParams ¶ms) This is a new experimental effect, and until we have it documented in the user manual we don't have a clear description of what this parameter does. It is OK to leave it in English. */ - mPresmoothLabel = S.AddVariableText(_("Presmooth Time")+wxString(wxT(":")), true, + mPresmoothLabel = S.AddVariableText(_("Presmooth Time:"), true, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); S.SetStyle(wxSL_HORIZONTAL); mPresmoothSlider = S.Id(ID_PRESMOOTH).AddSlider(wxT(""), @@ -153,7 +153,7 @@ ScoreAlignDialog::ScoreAlignDialog(wxWindow *parent, ScoreAlignParams ¶ms) This is a new experimental effect, and until we have it documented in the user manual we don't have a clear description of what this parameter does. It is OK to leave it in English. */ - mLineTimeLabel = S.AddVariableText(_("Line Time")+wxString(wxT(":")), true, + mLineTimeLabel = S.AddVariableText(_("Line Time:"), true, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); S.SetStyle(wxSL_HORIZONTAL); mLineTimeSlider = S.Id(ID_LINETIME).AddSlider(wxT(""), @@ -166,7 +166,7 @@ ScoreAlignDialog::ScoreAlignDialog(wxWindow *parent, ScoreAlignParams ¶ms) This is a new experimental effect, and until we have it documented in the user manual we don't have a clear description of what this parameter does. It is OK to leave it in English. */ - mSmoothTimeLabel = S.AddVariableText(_("Smooth Time")+wxString(wxT(":")), true, + mSmoothTimeLabel = S.AddVariableText(_("Smooth Time:"), true, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); S.SetStyle(wxSL_HORIZONTAL); mSmoothTimeSlider = S.Id(ID_SMOOTHTIME).AddSlider(wxT(""), diff --git a/src/effects/Silence.cpp b/src/effects/Silence.cpp index 1ce811d72..f9db05690 100644 --- a/src/effects/Silence.cpp +++ b/src/effects/Silence.cpp @@ -9,54 +9,87 @@ *******************************************************************//** \class EffectSilence -\brief An Effect for the "Generator" menu to add silence. +\brief An effect to add silence. *//*******************************************************************/ - #include "../Audacity.h" -#include - -#include -#include -#include -#include +#include #include "Silence.h" -#include "../WaveTrack.h" -#include "../TimeDialog.h" -#include "../Prefs.h" -bool EffectSilence::PromptUser() +EffectSilence::EffectSilence() { - wxString fmt; + mDuration = GetDuration(); +} - if (mT1 > mT0) { - // there is a selection: let's fit in there... - mDuration = mT1 - mT0; - fmt = _("hh:mm:ss + samples"); - } else { - // Retrieve last used values - gPrefs->Read(wxT("/Effects/SilenceGen/Duration"), &mDuration, 30L); - fmt = _("hh:mm:ss + milliseconds"); - } +EffectSilence::~EffectSilence() +{ +} - TimeDialog dlog(mParent, _("Silence Generator"), fmt, mProjectRate, - mDuration ); +// IdentInterface implementation - if (dlog.ShowModal() == wxID_CANCEL) - return false; +wxString EffectSilence::GetSymbol() +{ + return SILENCE_PLUGIN_SYMBOL; +} - mDuration = dlog.GetTimeValue(); - /* Save last used values. - Save duration unless value was got from selection, so we save only - when user explicitly set up a value */ - if (mT1 == mT0) +wxString EffectSilence::GetDescription() +{ + return wxTRANSLATE("Creates audio of zero amplitude"); +} + +// EffectIdentInterface implementation + +EffectType EffectSilence::GetType() +{ + return EffectTypeGenerate; +} + +// Effect implementation + +void EffectSilence::PopulateOrExchange(ShuttleGui & S) +{ + S.StartVerticalLay(); { - gPrefs->Write(wxT("/Effects/SilenceGen/Duration"), mDuration); - gPrefs->Flush(); + S.StartHorizontalLay(); + { + S.AddPrompt(_("Duration:")); + if (S.GetMode() == eIsCreating) + { + mDurationT = new + NumericTextCtrl(NumericConverter::TIME, + S.GetParent(), + wxID_ANY, + (mT1 > mT0) ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), + mDuration, + mProjectRate, + wxDefaultPosition, + wxDefaultSize, + true); + mDurationT->SetName(_("Duration")); + mDurationT->EnableMenu(); + } + S.AddWindow(mDurationT, wxALIGN_CENTER | wxALL); + } + S.EndHorizontalLay(); } + S.EndVerticalLay(); + + return; +} + +bool EffectSilence::TransferDataToWindow() +{ + mDurationT->SetValue(mDuration); + + return true; +} + +bool EffectSilence::TransferDataFromWindow() +{ + mDuration = mDurationT->GetValue(); return true; } @@ -65,7 +98,7 @@ bool EffectSilence::GenerateTrack(WaveTrack *tmp, const WaveTrack & WXUNUSED(track), int WXUNUSED(ntrack)) { - const bool bResult = tmp->InsertSilence(0.0, mDuration); + bool bResult = tmp->InsertSilence(0.0, mDuration); wxASSERT(bResult); return bResult; } diff --git a/src/effects/Silence.h b/src/effects/Silence.h index 77a550262..e3e4f74e9 100644 --- a/src/effects/Silence.h +++ b/src/effects/Silence.h @@ -6,60 +6,50 @@ Dominic Mazzoni - An effect for the "Generator" menu to add silence. + An effect to add silence. **********************************************************************/ #ifndef __AUDACITY_EFFECT_SILENCE__ #define __AUDACITY_EFFECT_SILENCE__ -#include -#include -#include +#include + +#include "../WaveTrack.h" +#include "../widgets/NumericTextCtrl.h" #include "Generator.h" -class wxSizer; -class wxTextCtrl; +#define SILENCE_PLUGIN_SYMBOL wxTRANSLATE("Silence") -class EffectSilence : public Generator { +class EffectSilence : public Generator +{ +public: + EffectSilence(); + virtual ~EffectSilence(); - public: - EffectSilence() { - SetEffectFlags(BUILTIN_EFFECT | INSERT_EFFECT); - } + // IdentInterface implementation - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Silence...")); - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#GeneratorPlugin")); - return result; - } + // EffectIdentInterface implementation - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Silence")); - } + virtual EffectType GetType(); - virtual wxString GetEffectAction() { - return wxString(_("Generating Silence")); - } + // Effect implementation - // Return true if the effect supports processing via batch chains. - virtual bool SupportsChains() { - return false; - } + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); - // Useful only after PromptUser values have been set. - virtual wxString GetEffectDescription() { - return wxString::Format(_("Applied effect: Generate Silence, %.6lf seconds"), mDuration); - } +protected: + // Generator implementation - virtual bool PromptUser(); - protected: bool GenerateTrack(WaveTrack *tmp, const WaveTrack &track, int ntrack); + +private: + NumericTextCtrl *mDurationT; }; #endif diff --git a/src/effects/SimpleMono.h b/src/effects/SimpleMono.h index a308bdf3e..fb017608e 100644 --- a/src/effects/SimpleMono.h +++ b/src/effects/SimpleMono.h @@ -21,15 +21,12 @@ class WaveTrack; -class EffectSimpleMono:public Effect { - - public: +class EffectSimpleMono : public Effect +{ +public: virtual bool Process(); - private: - bool ProcessOne(WaveTrack * t, sampleCount start, sampleCount end); - - protected: +protected: // Override this method if you need to do things // before every track (including the first one) @@ -38,6 +35,7 @@ class EffectSimpleMono:public Effect { // Override this method to actually process audio virtual bool ProcessSimpleMono(float *buffer, sampleCount len) = 0; +protected: // Other useful information int mCurTrackNum; double mCurRate; @@ -45,6 +43,8 @@ class EffectSimpleMono:public Effect { double mCurT1; int mCurChannel; +private: + bool ProcessOne(WaveTrack * t, sampleCount start, sampleCount end); }; #endif diff --git a/src/effects/SoundTouchEffect.h b/src/effects/SoundTouchEffect.h index 497a47971..e6d4c6a47 100644 --- a/src/effects/SoundTouchEffect.h +++ b/src/effects/SoundTouchEffect.h @@ -34,21 +34,27 @@ using namespace soundtouch; class WaveTrack; -class EffectSoundTouch:public Effect { +class EffectSoundTouch : public Effect +{ +public: + + // Effect implementation - public: virtual bool Process(); + + // EffectSoundTouch implementation + #ifdef USE_MIDI double mSemitones; // pitch change for NoteTracks EffectSoundTouch() { mSemitones = 0; } #endif - protected: +protected: SoundTouch *mSoundTouch; double mCurT0; double mCurT1; - private: +private: bool ProcessLabelTrack(Track *track); #ifdef USE_MIDI bool ProcessNoteTrack(Track *track); diff --git a/src/effects/StereoToMono.cpp b/src/effects/StereoToMono.cpp index c0d3cdc4e..7c974b4a8 100644 --- a/src/effects/StereoToMono.cpp +++ b/src/effects/StereoToMono.cpp @@ -13,160 +13,75 @@ *//*******************************************************************/ - #include "../Audacity.h" -// For compilers that support precompilation, includes "wx.h". -#include +#include -#ifdef __BORLANDC__ -#pragma hdrstop -#endif - -#ifndef WX_PRECOMP -// Include your minimal set of headers here, or wx.h -#include -#endif - -#include #include "StereoToMono.h" -#include "../Project.h" EffectStereoToMono::EffectStereoToMono() { - Init(); } -bool EffectStereoToMono::Init() -{ - return true; -} - -void EffectStereoToMono::End() +EffectStereoToMono::~EffectStereoToMono() { } -//TODO: There are a lot of places where a track is being checked -// to see if it is stereo. Consolidate these -bool EffectStereoToMono::CheckWhetherSkipEffect() -{ - TrackListOfKindIterator iter(Track::Wave, mTracks); - WaveTrack *t = (WaveTrack*)iter.First(); - while (t) { - if (t->GetLinked()) { - return false; - } - t = (WaveTrack *)iter.Next(); - } +// IdentInterface implementation - return true; +wxString EffectStereoToMono::GetSymbol() +{ + return STEREOTOMONO_PLUGIN_SYMBOL; } -bool EffectStereoToMono::ProcessOne(int count) +wxString EffectStereoToMono::GetDescription() { - float curLeftFrame; - float curRightFrame; - float curMonoFrame; - - sampleCount idealBlockLen = mLeftTrack->GetMaxBlockSize() * 2; - sampleCount index = mStart; - float *leftBuffer = new float[idealBlockLen]; - float *rightBuffer = new float[idealBlockLen]; - bool bResult = true; - - while (index < mEnd) { - bResult &= mLeftTrack->Get((samplePtr)leftBuffer, floatSample, index, idealBlockLen); - bResult &= mRightTrack->Get((samplePtr)rightBuffer, floatSample, index, idealBlockLen); - sampleCount limit = idealBlockLen; - if ((index + idealBlockLen) > mEnd) { - limit = mEnd - index; - } - for (sampleCount i = 0; i < limit; ++i) { - index++; - curLeftFrame = leftBuffer[i]; - curRightFrame = rightBuffer[i]; - curMonoFrame = (curLeftFrame + curRightFrame) / 2.0; - leftBuffer[i] = curMonoFrame; - } - bResult &= mOutTrack->Append((samplePtr)leftBuffer, floatSample, limit); - if (TrackProgress(count, 2.*((double)index / (double)(mEnd - mStart)))) - return false; - } - - double minStart = wxMin(mLeftTrack->GetStartTime(), mRightTrack->GetStartTime()); - bResult &= mLeftTrack->Clear(mLeftTrack->GetStartTime(), mLeftTrack->GetEndTime()); - bResult &= mOutTrack->Flush(); - bResult &= mLeftTrack->Paste(minStart, mOutTrack); - mLeftTrack->SetLinked(false); - mRightTrack->SetLinked(false); - mLeftTrack->SetChannel(Track::MonoChannel); - mOutputTracks->Remove(mRightTrack); - delete mRightTrack; - - delete [] leftBuffer; - delete [] rightBuffer; - - return bResult; + return wxTRANSLATE("Converts stereo tracks to mono"); } -bool EffectStereoToMono::Process() +// EffectIdentInterface implementation + +EffectType EffectStereoToMono::GetType() { - // Do not use mWaveTracks here. We will possibly delete tracks, - // so we must use the "real" tracklist. - this->CopyInputTracks(); // Set up mOutputTracks. - bool bGoodResult = true; + // Really EffectTypeProcess, but this prevents it from showing in the Effect Menu + return EffectTypeNone; +} - SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks); - mLeftTrack = (WaveTrack *)iter.First(); - bool refreshIter = false; +bool EffectStereoToMono::IsInteractive() +{ + return false; +} - if(mLeftTrack) +// EffectClientInterface implementation + +int EffectStereoToMono::GetAudioInCount() +{ + return 2; +} + +int EffectStereoToMono::GetAudioOutCount() +{ + return 1; +} + +sampleCount EffectStereoToMono::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) +{ + float *left = inBlock[0]; + float *right = inBlock[1]; + float *mono = outBlock[0]; + + for (sampleCount i = 0; i < blockLen; i++) { - // create a new WaveTrack to hold all of the output - AudacityProject *p = GetActiveProject(); - mOutTrack = p->GetTrackFactory()->NewWaveTrack(floatSample, mLeftTrack->GetRate()); + mono[i] = (left[i] + right[i]) / 2.0; } - int count = 0; - while (mLeftTrack) { - if (mLeftTrack->GetKind() == Track::Wave && - mLeftTrack->GetSelected() && - mLeftTrack->GetLinked()) { - - mRightTrack = (WaveTrack *)iter.Next(); - - if ((mLeftTrack->GetRate() == mRightTrack->GetRate())) { - sampleCount leftTrackStart = mLeftTrack->TimeToLongSamples(mLeftTrack->GetStartTime()); - sampleCount rightTrackStart = mRightTrack->TimeToLongSamples(mRightTrack->GetStartTime()); - mStart = wxMin(leftTrackStart, rightTrackStart); - - sampleCount leftTrackEnd = mLeftTrack->TimeToLongSamples(mLeftTrack->GetEndTime()); - sampleCount rightTrackEnd = mRightTrack->TimeToLongSamples(mRightTrack->GetEndTime()); - mEnd = wxMax(leftTrackEnd, rightTrackEnd); - - bGoodResult = ProcessOne(count); - if (!bGoodResult) - break; - - mOutTrack->Clear(mOutTrack->GetStartTime(), mOutTrack->GetEndTime()); - - // The right channel has been deleted, so we must restart from the beginning - refreshIter = true; - } - } - - if (refreshIter) { - mLeftTrack = (WaveTrack *)iter.First(); - refreshIter = false; - } - else { - mLeftTrack = (WaveTrack *)iter.Next(); - } - count++; - } - - if(mOutTrack) - delete mOutTrack; - this->ReplaceProcessedTracks(bGoodResult); - return bGoodResult; + return blockLen; } + +// Effect implementatino + +bool EffectStereoToMono::IsHidden() +{ + return true; +} + diff --git a/src/effects/StereoToMono.h b/src/effects/StereoToMono.h index 232e4af9b..0017586aa 100644 --- a/src/effects/StereoToMono.h +++ b/src/effects/StereoToMono.h @@ -11,52 +11,37 @@ #ifndef __AUDACITY_EFFECT_STEREO_TO_MONO__ #define __AUDACITY_EFFECT_STEREO_TO_MONO__ +#include + #include "Effect.h" -class EffectStereoToMono: public Effect { +#define STEREOTOMONO_PLUGIN_SYMBOL wxTRANSLATE("Stereo To Mono") +class EffectStereoToMono : public Effect +{ public: - EffectStereoToMono(); + virtual ~EffectStereoToMono(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Stereo to Mono")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#MixerPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - // Used internally, users will not see this. Do not translate. - virtual wxString GetEffectIdentifier() { - return wxT("Stereo To Mono"); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Applying Stereo to Mono")); - } - virtual bool Init(); - virtual void End(); - virtual bool CheckWhetherSkipEffect(); + virtual EffectType GetType(); + virtual bool IsInteractive(); - virtual bool PromptUser() { - return true; - } + // EffectClientInterface implementation - protected: - virtual bool Process(); + virtual int GetAudioInCount(); + virtual int GetAudioOutCount(); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); -private: - bool ProcessOne(int); - - sampleCount mStart; - sampleCount mEnd; - WaveTrack *mLeftTrack; - WaveTrack *mRightTrack; - WaveTrack *mOutTrack; + // Effect implementation + bool IsHidden(); }; #endif diff --git a/src/effects/TimeScale.cpp b/src/effects/TimeScale.cpp index 98be6767e..2bb2bddc4 100644 --- a/src/effects/TimeScale.cpp +++ b/src/effects/TimeScale.cpp @@ -11,604 +11,407 @@ \class EffectTimeScale \brief An EffectTimeScale does high quality sliding time scaling/pitch shifting -*//****************************************************************//** - -\class TimeScaleDialog -\brief Dialog used with EffectTimeScale - *//*******************************************************************/ #include "../Audacity.h" // for USE_SBSMS #if USE_SBSMS +#include + +#include + +#include "../widgets/valnum.h" + #include "TimeScale.h" #include "sbsms.h" -#include "../ShuttleGui.h" +enum +{ + ID_RatePercentChangeStart = 10000, + ID_RatePercentChangeEnd, + ID_PitchHalfStepsStart, + ID_PitchHalfStepsEnd, + ID_PitchPercentChangeStart, + ID_PitchPercentChangeEnd +}; -#include - -#include -#include -#include -#include -#include +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( RatePercentStart, double, wxTRANSLATE("RatePercentChangeStart"), 0.0, -90.0, 500, 1 ); +Param( RatePercentEnd, double, wxTRANSLATE("RatePercentChangeEnd"), 0.0, -90.0, 500, 1 ); +Param( HalfStepsStart, double, wxTRANSLATE("PitchHalfStepsStart"), 0.0, -12.0, 12.0, 1 ); +Param( HalfStepsEnd, double, wxTRANSLATE("PitchHalfStepsEnd"), 0.0, -12.0, 12.0, 1 ); +Param( PitchPercentStart, double, wxTRANSLATE("PitchPercentChangeStart"), 0.0, -50.0, 100.0, 1 ); +Param( PitchPercentEnd, double, wxTRANSLATE("PitchPercentChangeEnd"), 0.0, -50.0, 100.0, 1 ); // // EffectTimeScale // +BEGIN_EVENT_TABLE(EffectTimeScale, wxEvtHandler) + EVT_TEXT(ID_RatePercentChangeStart, EffectTimeScale::OnText_RatePercentChangeStart) + EVT_TEXT(ID_RatePercentChangeEnd, EffectTimeScale::OnText_RatePercentChangeEnd) + EVT_TEXT(ID_PitchHalfStepsStart, EffectTimeScale::OnText_PitchHalfStepsStart) + EVT_TEXT(ID_PitchHalfStepsEnd, EffectTimeScale::OnText_PitchHalfStepsEnd) + EVT_TEXT(ID_PitchPercentChangeStart, EffectTimeScale::OnText_PitchPercentChangeStart) + EVT_TEXT(ID_PitchPercentChangeEnd, EffectTimeScale::OnText_PitchPercentChangeEnd) + EVT_SLIDER(ID_RatePercentChangeStart, EffectTimeScale::OnSlider_RatePercentChangeStart) + EVT_SLIDER(ID_RatePercentChangeEnd, EffectTimeScale::OnSlider_RatePercentChangeEnd) +END_EVENT_TABLE() + EffectTimeScale::EffectTimeScale() { - m_RatePercentChangeStart = 0; - m_RatePercentChangeEnd = 0; - m_PitchHalfStepsStart = 0; - m_PitchHalfStepsEnd = 0; - m_PitchPercentChangeStart = 0; - m_PitchPercentChangeEnd = 0; - m_PreAnalyze = false; + m_RatePercentChangeStart = DEF_RatePercentStart; + m_RatePercentChangeEnd = DEF_RatePercentEnd; + m_PitchHalfStepsStart = DEF_HalfStepsStart; + m_PitchHalfStepsEnd = DEF_HalfStepsEnd; + m_PitchPercentChangeStart = DEF_PitchPercentStart; + m_PitchPercentChangeEnd = DEF_PitchPercentEnd; } -wxString EffectTimeScale::GetEffectDescription() { - // Note: This is useful only after change amount has been set. - return wxString::Format(_("Applied effect: %s"), this->GetEffectName().c_str()); +EffectTimeScale::~EffectTimeScale() +{ } +// IdentInterface implementation + +wxString EffectTimeScale::GetSymbol() +{ + return TIMESCALE_PLUGIN_SYMBOL; +} + +wxString EffectTimeScale::GetName() +{ + return wxTRANSLATE("Sliding Time Scale/Pitch Shift"); +} + +wxString EffectTimeScale::GetDescription() +{ + return wxTRANSLATE("Allows continuous changes to the tempo and/or pitch"); +} + +// EffectIdentInterface implementation + +EffectType EffectTimeScale::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +bool EffectTimeScale::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_RatePercentStart, m_RatePercentChangeStart); + parms.Write(KEY_RatePercentEnd, m_RatePercentChangeEnd); + parms.Write(KEY_HalfStepsStart, m_PitchHalfStepsStart); + parms.Write(KEY_HalfStepsEnd, m_PitchHalfStepsEnd); + parms.Write(KEY_PitchPercentStart, m_PitchPercentChangeStart); + parms.Write(KEY_PitchPercentEnd, m_PitchPercentChangeEnd); + + return true; +} + +bool EffectTimeScale::SetAutomationParameters(EffectAutomationParameters & parms) +{ + ReadAndVerifyDouble(RatePercentStart); + ReadAndVerifyDouble(RatePercentEnd); + ReadAndVerifyDouble(HalfStepsStart); + ReadAndVerifyDouble(HalfStepsEnd); + ReadAndVerifyDouble(PitchPercentStart); + ReadAndVerifyDouble(PitchPercentEnd); + + m_RatePercentChangeStart = RatePercentStart; + m_RatePercentChangeEnd = RatePercentEnd; + m_PitchHalfStepsStart = HalfStepsStart; + m_PitchHalfStepsEnd = HalfStepsEnd; + m_PitchPercentChangeStart = PitchPercentStart; + m_PitchPercentChangeEnd = PitchPercentEnd; + + return true; +} + +// Effect implementation + bool EffectTimeScale::Init() { return true; } -bool EffectTimeScale::PromptUser() -{ - TimeScaleDialog dlog(this, mParent); - dlog.m_RatePercentChangeStart = m_RatePercentChangeStart; - dlog.m_RatePercentChangeEnd = m_RatePercentChangeEnd; - dlog.m_PitchHalfStepsStart = m_PitchHalfStepsStart; - dlog.m_PitchHalfStepsEnd = m_PitchHalfStepsEnd; - dlog.m_PitchPercentChangeStart = m_PitchPercentChangeStart; - dlog.m_PitchPercentChangeEnd = m_PitchPercentChangeEnd; - dlog.m_PreAnalyze = m_PreAnalyze; - - // Don't need to call TransferDataToWindow, although other - // Audacity dialogs (from which I derived this one) do it, because - // ShowModal calls stuff that eventually calls wxWindowBase::OnInitDialog, - // which calls dlog.TransferDataToWindow(); - dlog.CentreOnParent(); - dlog.ShowModal(); - - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; - - m_RatePercentChangeStart = dlog.m_RatePercentChangeStart; - m_RatePercentChangeEnd = dlog.m_RatePercentChangeEnd; - m_PitchHalfStepsStart = dlog.m_PitchHalfStepsStart; - m_PitchHalfStepsEnd = dlog.m_PitchHalfStepsEnd; - m_PitchPercentChangeStart = dlog.m_PitchPercentChangeStart; - m_PitchPercentChangeEnd = dlog.m_PitchPercentChangeEnd; - m_PreAnalyze = dlog.m_PreAnalyze; - - return true; -} - -bool EffectTimeScale::TransferParameters( Shuttle & shuttle ) -{ - shuttle.TransferDouble(wxT("RatePercentChangeStart"),m_RatePercentChangeStart,0.0); - shuttle.TransferDouble(wxT("RatePercentChangeEnd"),m_RatePercentChangeEnd,0.0); - shuttle.TransferDouble(wxT("PitchHalfStepsStart"),m_PitchHalfStepsStart,0.0); - shuttle.TransferDouble(wxT("PitchHalfStepsEnd"),m_PitchHalfStepsEnd,0.0); - shuttle.TransferDouble(wxT("PitchPercentChangeStart"),m_PitchPercentChangeStart,0.0); - shuttle.TransferDouble(wxT("PitchPercentChangeEnd"),m_PitchPercentChangeEnd,0.0); - shuttle.TransferBool(wxT("PreAnalyze"),m_PreAnalyze,false); - return true; -} - -inline double PercentChangeToRatio(double percentChange) -{ - return 1.0 + percentChange / 100.0; -} - -inline double HalfStepsToPercentChange(double halfSteps) -{ - return 100.0 * (pow(2.0,halfSteps/12.0) - 1.0); -} - -inline double PercentChangeToHalfSteps(double percentChange) -{ - return 17.312340490667560888319096172023 * log(PercentChangeToRatio(percentChange)); -} - bool EffectTimeScale::Process() { double pitchStart = PercentChangeToRatio(m_PitchPercentChangeStart); double pitchEnd = PercentChangeToRatio(m_PitchPercentChangeEnd); double rateStart = PercentChangeToRatio(m_RatePercentChangeStart); double rateEnd = PercentChangeToRatio(m_RatePercentChangeEnd); - this->EffectSBSMS::setParameters(rateStart,rateEnd,pitchStart,pitchEnd,SlideLinearOutputRate,SlideLinearOutputRate,false,false,false); - return this->EffectSBSMS::Process(); + EffectSBSMS::setParameters(rateStart,rateEnd,pitchStart,pitchEnd,SlideLinearOutputRate,SlideLinearOutputRate,false,false,false); + return EffectSBSMS::Process(); } -//---------------------------------------------------------------------------- -// TimeScaleDialog -//---------------------------------------------------------------------------- - -#define RATE_PERCENTCHANGE_MAX_SLIDER 150 -#define RATE_PERCENTCHANGE_MIN_SLIDER -75 -#define RATE_PERCENTCHANGE_MAX_TEXT 500 -#define RATE_PERCENTCHANGE_MIN_TEXT -90 -#define RATE_PERCENTCHANGE_DEFAULT 0 -#define PITCH_HALFSTEPS_MIN_TEXT -12 -#define PITCH_HALFSTEPS_MAX_TEXT 12 -#define PITCH_PERCENTCHANGE_MIN_TEXT -50 -#define PITCH_PERCENTCHANGE_MAX_TEXT 100 - - -#define ID_TEXT_RATE_PERCENTCHANGE_START 10001 -#define ID_TEXT_RATE_PERCENTCHANGE_END 10002 -#define ID_TEXT_PITCH_HALFSTEPS_START 10003 -#define ID_TEXT_PITCH_HALFSTEPS_END 10004 -#define ID_TEXT_PITCH_PERCENTCHANGE_START 10005 -#define ID_TEXT_PITCH_PERCENTCHANGE_END 10006 -#define ID_SLIDER_RATE_PERCENTCHANGE_START 10007 -#define ID_SLIDER_RATE_PERCENTCHANGE_END 10008 -#define ID_CHECKBOX_PREANALYZE 10009 - -// event table for TimeScaleDialog - -BEGIN_EVENT_TABLE(TimeScaleDialog, EffectDialog) - EVT_TEXT(ID_TEXT_RATE_PERCENTCHANGE_START, TimeScaleDialog::OnText_RatePercentChangeStart) - EVT_TEXT(ID_TEXT_RATE_PERCENTCHANGE_END, TimeScaleDialog::OnText_RatePercentChangeEnd) - EVT_TEXT(ID_TEXT_PITCH_HALFSTEPS_START, TimeScaleDialog::OnText_PitchHalfStepsStart) - EVT_TEXT(ID_TEXT_PITCH_HALFSTEPS_END, TimeScaleDialog::OnText_PitchHalfStepsEnd) - EVT_TEXT(ID_TEXT_PITCH_PERCENTCHANGE_START, TimeScaleDialog::OnText_PitchPercentChangeStart) - EVT_TEXT(ID_TEXT_PITCH_PERCENTCHANGE_END, TimeScaleDialog::OnText_PitchPercentChangeEnd) - EVT_SLIDER(ID_SLIDER_RATE_PERCENTCHANGE_START, TimeScaleDialog::OnSlider_RatePercentChangeStart) - EVT_SLIDER(ID_SLIDER_RATE_PERCENTCHANGE_END, TimeScaleDialog::OnSlider_RatePercentChangeEnd) - EVT_CHECKBOX(ID_CHECKBOX_PREANALYZE, TimeScaleDialog::OnCheckBox_PreAnalyze) -END_EVENT_TABLE() - -TimeScaleDialog::TimeScaleDialog(EffectTimeScale *effect, wxWindow *parent) - : EffectDialog(parent, _("Sliding Time Scale/Pitch Shift"), INSERT_EFFECT), - mEffect(effect) +void EffectTimeScale::PopulateOrExchange(ShuttleGui & S) { - m_bLoopDetect = false; - - // NULL out these control members because there are some cases where the - // event table handlers get called during this method, and those handlers that - // can cause trouble check for NULL. - - m_pTextCtrl_RatePercentChangeStart = NULL; - m_pTextCtrl_RatePercentChangeEnd = NULL; - m_pSlider_RatePercentChangeStart = NULL; - m_pSlider_RatePercentChangeEnd = NULL; - m_pTextCtrl_PitchPercentChangeStart = NULL; - m_pTextCtrl_PitchPercentChangeEnd = NULL; - m_pTextCtrl_PitchHalfStepsStart = NULL; - m_pTextCtrl_PitchHalfStepsEnd = NULL; - m_pCheckBox_PreAnalyze = NULL; - - // effect parameters - m_RatePercentChangeStart = 0; - m_RatePercentChangeEnd = 0; - m_PitchPercentChangeStart = 0; - m_PitchPercentChangeEnd = 0; - m_PitchHalfStepsStart = 0; - m_PitchHalfStepsEnd = 0; - m_PreAnalyze = false; - - Init(); -} - -void TimeScaleDialog::PopulateOrExchange(ShuttleGui & S) -{ - - wxTextValidator nullvld(wxFILTER_INCLUDE_CHAR_LIST); - wxTextValidator numvld(wxFILTER_NUMERIC); - S.SetBorder(5); S.AddSpace(0, 5); - S.StartMultiColumn(2, 0); - // Rate Start - S.StartStatic(_("Initial Tempo Change (%)")); + S.StartMultiColumn(2, wxALIGN_CENTER); { - S.StartMultiColumn(1, wxCENTER); + // Rate Start + S.StartStatic(_("Initial Tempo Change (%)")); { - m_pTextCtrl_RatePercentChangeStart = S.Id(ID_TEXT_RATE_PERCENTCHANGE_START) - .AddTextBox(wxT(""), wxT(""), 12); - m_pTextCtrl_RatePercentChangeStart->SetValidator(numvld); + S.StartMultiColumn(1, wxCENTER); + { + FloatingPointValidator + vldRatePercentChangeStart(3, &m_RatePercentChangeStart, NUM_VAL_NO_TRAILING_ZEROES); + vldRatePercentChangeStart.SetRange(MIN_RatePercentStart, MAX_RatePercentStart); + + m_pTextCtrl_RatePercentChangeStart = S.Id(ID_RatePercentChangeStart) + .AddTextBox(wxT(""), wxT(""), 12); + m_pTextCtrl_RatePercentChangeStart->SetValidator(vldRatePercentChangeStart); + } + S.EndMultiColumn(); + S.StartHorizontalLay(wxEXPAND, 0); + { + S.SetStyle(wxSL_HORIZONTAL); + m_pSlider_RatePercentChangeStart = S.Id(ID_RatePercentChangeStart) + .AddSlider(wxT(""), DEF_RatePercentStart, MAX_RatePercentStart, MIN_RatePercentStart); + } + S.EndHorizontalLay(); } - S.EndMultiColumn(); - S.StartHorizontalLay(wxEXPAND,0); + S.EndStatic(); + + S.StartStatic(_("Final Tempo Change (%)")); { - S.SetStyle(wxSL_HORIZONTAL); - m_pSlider_RatePercentChangeStart = S.Id(ID_SLIDER_RATE_PERCENTCHANGE_START) - .AddSlider(wxT(""), (int)RATE_PERCENTCHANGE_DEFAULT, (int)RATE_PERCENTCHANGE_MAX_SLIDER, (int)RATE_PERCENTCHANGE_MIN_SLIDER); + S.StartMultiColumn(1, wxCENTER); + { + FloatingPointValidator + vldRatePercentChangeEnd(3, &m_RatePercentChangeEnd, NUM_VAL_NO_TRAILING_ZEROES); + vldRatePercentChangeEnd.SetRange(MIN_RatePercentEnd, MAX_RatePercentEnd); + + m_pTextCtrl_RatePercentChangeEnd = S.Id(ID_RatePercentChangeEnd) + .AddTextBox(wxT(""), wxT(""), 12); + m_pTextCtrl_RatePercentChangeEnd->SetValidator(vldRatePercentChangeEnd); + } + S.EndMultiColumn(); + S.StartHorizontalLay(wxEXPAND, 0); + { + S.SetStyle(wxSL_HORIZONTAL); + m_pSlider_RatePercentChangeEnd = S.Id(ID_RatePercentChangeEnd) + .AddSlider(wxT(""), DEF_RatePercentEnd, MAX_RatePercentEnd, MIN_RatePercentEnd); + } + S.EndHorizontalLay(); } - S.EndHorizontalLay(); + S.EndStatic(); + + // Pitch Start + S.StartStatic(_("Initial Pitch Shift")); + { + S.StartMultiColumn(2, wxCENTER); + { + FloatingPointValidator + vldPitchHalfStepsStart(3, &m_PitchHalfStepsStart, NUM_VAL_NO_TRAILING_ZEROES); + vldPitchHalfStepsStart.SetRange(MIN_HalfStepsStart, MAX_HalfStepsStart); + + m_pTextCtrl_PitchHalfStepsStart = S.Id(ID_PitchHalfStepsStart) + .AddTextBox(_("(semitones) [-12 to 12]:"), wxT(""), 12); + m_pTextCtrl_PitchHalfStepsStart->SetValidator(vldPitchHalfStepsStart); + + FloatingPointValidator + vldPitchPercentChangeStart(3, &m_PitchPercentChangeStart, NUM_VAL_NO_TRAILING_ZEROES); + vldPitchPercentChangeStart.SetRange(MIN_PitchPercentStart, MAX_PitchPercentStart); + + m_pTextCtrl_PitchPercentChangeStart = S.Id(ID_PitchPercentChangeStart) + .AddTextBox(_("(%) [-50 to 100]:"), wxT(""), 12); + m_pTextCtrl_PitchPercentChangeStart->SetValidator(vldPitchPercentChangeStart); + } + S.EndMultiColumn(); + } + S.EndStatic(); + + // Pitch End + S.StartStatic(_("Final Pitch Shift")); + { + S.StartMultiColumn(2, wxCENTER); + { + FloatingPointValidator + vldPitchHalfStepsEnd(3, &m_PitchHalfStepsEnd, NUM_VAL_NO_TRAILING_ZEROES); + vldPitchHalfStepsEnd.SetRange(MIN_HalfStepsEnd, MAX_HalfStepsEnd); + + m_pTextCtrl_PitchHalfStepsEnd = S.Id(ID_PitchHalfStepsEnd) + .AddTextBox(_("(semitones) [-12 to 12]:"), wxT(""), 12); + m_pTextCtrl_PitchHalfStepsEnd->SetValidator(vldPitchHalfStepsEnd); + + FloatingPointValidator + vldPitchPercentChangeEnd(3, &m_PitchPercentChangeEnd, NUM_VAL_NO_TRAILING_ZEROES); + vldPitchPercentChangeEnd.SetRange(MIN_PitchPercentStart, MAX_PitchPercentStart); + + m_pTextCtrl_PitchPercentChangeEnd = S.Id(ID_PitchPercentChangeEnd) + .AddTextBox(_("(%) [-50 to 100]:"), wxT(""), 12); + m_pTextCtrl_PitchPercentChangeEnd->SetValidator(vldPitchPercentChangeEnd); + } + S.EndMultiColumn(); + } + S.EndStatic(); } - S.EndStatic(); - - S.StartStatic(_("Final Tempo Change (%)")); - { - S.StartMultiColumn(1, wxCENTER); - { - m_pTextCtrl_RatePercentChangeEnd = S.Id(ID_TEXT_RATE_PERCENTCHANGE_END) - .AddTextBox(wxT(""), wxT(""), 12); - m_pTextCtrl_RatePercentChangeEnd->SetValidator(numvld); - } - S.EndMultiColumn(); - S.StartHorizontalLay(wxEXPAND,0); - { - S.SetStyle(wxSL_HORIZONTAL); - m_pSlider_RatePercentChangeEnd = S.Id(ID_SLIDER_RATE_PERCENTCHANGE_END) - .AddSlider(wxT(""), (int)RATE_PERCENTCHANGE_DEFAULT, (int)RATE_PERCENTCHANGE_MAX_SLIDER, (int)RATE_PERCENTCHANGE_MIN_SLIDER); - } - S.EndHorizontalLay(); - } - S.EndStatic(); - - // Pitch Start - S.StartStatic(_("Initial Pitch Shift")); - { - S.StartMultiColumn(2, wxCENTER); - { - m_pTextCtrl_PitchHalfStepsStart = S.Id(ID_TEXT_PITCH_HALFSTEPS_START) - .AddTextBox(_("(semitones) [-12 to 12]:"), wxT(""), 12); - m_pTextCtrl_PitchHalfStepsStart->SetValidator(numvld); - - m_pTextCtrl_PitchPercentChangeStart = S.Id(ID_TEXT_PITCH_PERCENTCHANGE_START) - .AddTextBox(_("(%) [-50 to 100]:"), wxT(""), 12); - m_pTextCtrl_PitchPercentChangeStart->SetValidator(numvld); - } - S.EndMultiColumn(); - } - S.EndStatic(); - - // Pitch End - S.StartStatic(_("Final Pitch Shift")); - { - S.StartMultiColumn(2, wxCENTER); - { - m_pTextCtrl_PitchHalfStepsEnd = S.Id(ID_TEXT_PITCH_HALFSTEPS_END) - .AddTextBox(_("(semitones) [-12 to 12]:"), wxT(""), 12); - m_pTextCtrl_PitchHalfStepsEnd->SetValidator(numvld); - - m_pTextCtrl_PitchPercentChangeEnd = S.Id(ID_TEXT_PITCH_PERCENTCHANGE_END) - .AddTextBox(_("(%) [-50 to 100]:"), wxT(""), 12); - m_pTextCtrl_PitchPercentChangeEnd->SetValidator(numvld); - } - S.EndMultiColumn(); - } - S.EndStatic(); S.EndMultiColumn(); return; } -bool TimeScaleDialog::TransferDataToWindow() +bool EffectTimeScale::TransferDataToWindow() { - m_bLoopDetect = true; + if (!mUIParent->TransferDataToWindow()) + { + return false; + } - this->Update_Text_RatePercentChangeStart(); - this->Update_Text_RatePercentChangeEnd(); - this->Update_Slider_RatePercentChangeStart(); - this->Update_Slider_RatePercentChangeEnd(); - this->Update_Text_PitchHalfStepsStart(); - this->Update_Text_PitchHalfStepsEnd(); - this->Update_Text_PitchPercentChangeStart(); - this->Update_Text_PitchPercentChangeEnd(); - this->Update_CheckBox_PreAnalyze(); - - m_bLoopDetect = false; + Update_Slider_RatePercentChangeStart(); + Update_Slider_RatePercentChangeEnd(); return true; } -bool TimeScaleDialog::TransferDataFromWindow() +bool EffectTimeScale::TransferDataFromWindow() { - wxString str; - - if (m_pTextCtrl_RatePercentChangeStart) { - str = m_pTextCtrl_RatePercentChangeStart->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_RatePercentChangeStart = newValue; - } - - if (m_pTextCtrl_RatePercentChangeEnd) { - str = m_pTextCtrl_RatePercentChangeEnd->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_RatePercentChangeEnd = newValue; - } - - if (m_pTextCtrl_PitchHalfStepsStart) { - str = m_pTextCtrl_PitchHalfStepsStart->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_PitchHalfStepsStart = newValue; - } - - if (m_pTextCtrl_PitchHalfStepsEnd) { - str = m_pTextCtrl_PitchHalfStepsEnd->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_PitchHalfStepsEnd = newValue; - } - - if (m_pTextCtrl_PitchPercentChangeStart) { - str = m_pTextCtrl_PitchPercentChangeStart->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_PitchPercentChangeStart = newValue; - } - - if (m_pTextCtrl_PitchPercentChangeEnd) { - str = m_pTextCtrl_PitchPercentChangeEnd->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_PitchPercentChangeEnd = newValue; - } - - if(m_pCheckBox_PreAnalyze) { - m_PreAnalyze = m_pCheckBox_PreAnalyze->GetValue(); + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; } return true; } -bool TimeScaleDialog::CheckParameters() +inline double EffectTimeScale::PercentChangeToRatio(double percentChange) { - return - (m_RatePercentChangeStart >= RATE_PERCENTCHANGE_MIN_TEXT && - m_RatePercentChangeStart <= RATE_PERCENTCHANGE_MAX_TEXT) - && - (m_RatePercentChangeEnd >= RATE_PERCENTCHANGE_MIN_TEXT && - m_RatePercentChangeEnd <= RATE_PERCENTCHANGE_MAX_TEXT) - && - (m_PitchHalfStepsStart >= PITCH_HALFSTEPS_MIN_TEXT && - m_PitchHalfStepsStart <= PITCH_HALFSTEPS_MAX_TEXT) - && - (m_PitchHalfStepsEnd >= PITCH_HALFSTEPS_MIN_TEXT && - m_PitchHalfStepsEnd <= PITCH_HALFSTEPS_MAX_TEXT) - && - (m_PitchPercentChangeStart >= PITCH_PERCENTCHANGE_MIN_TEXT && - m_PitchPercentChangeStart <= PITCH_PERCENTCHANGE_MAX_TEXT) - && - (m_PitchPercentChangeEnd >= PITCH_PERCENTCHANGE_MIN_TEXT && - m_PitchPercentChangeEnd <= PITCH_PERCENTCHANGE_MAX_TEXT); + return 1.0 + percentChange / 100.0; } -// handler implementations for TimeScaleDialog - -void TimeScaleDialog::OnText_RatePercentChangeStart(wxCommandEvent & WXUNUSED(event)) +inline double EffectTimeScale::HalfStepsToPercentChange(double halfSteps) { - if (m_bLoopDetect) + return 100.0 * (pow(2.0,halfSteps/12.0) - 1.0); +} + +inline double EffectTimeScale::PercentChangeToHalfSteps(double percentChange) +{ + return 12.0 * log2(PercentChangeToRatio(percentChange)); +} + +void EffectTimeScale::Update_Text_RatePercentChangeStart() +{ + m_pTextCtrl_RatePercentChangeStart->GetValidator()->TransferToWindow(); +} + +void EffectTimeScale::Update_Text_RatePercentChangeEnd() +{ + m_pTextCtrl_RatePercentChangeEnd->GetValidator()->TransferToWindow(); +} + +void EffectTimeScale::Update_Slider_RatePercentChangeStart() +{ + m_pSlider_RatePercentChangeStart->SetValue((int)(m_RatePercentChangeStart + 0.5)); +} + +void EffectTimeScale::Update_Slider_RatePercentChangeEnd() +{ + m_pSlider_RatePercentChangeEnd->SetValue((int)(m_RatePercentChangeEnd + 0.5)); +} + +void EffectTimeScale::Update_Text_PitchHalfStepsStart() +{ + m_pTextCtrl_PitchHalfStepsStart->GetValidator()->TransferToWindow(); +} + +void EffectTimeScale::Update_Text_PitchHalfStepsEnd() +{ + m_pTextCtrl_PitchHalfStepsEnd->GetValidator()->TransferToWindow(); +} + +void EffectTimeScale::Update_Text_PitchPercentChangeStart() +{ + m_pTextCtrl_PitchPercentChangeStart->GetValidator()->TransferToWindow(); +} + +void EffectTimeScale::Update_Text_PitchPercentChangeEnd() +{ + m_pTextCtrl_PitchPercentChangeEnd->GetValidator()->TransferToWindow(); +} + +void EffectTimeScale::OnText_RatePercentChangeStart(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { return; - - if (m_pTextCtrl_RatePercentChangeStart) { - wxString str = m_pTextCtrl_RatePercentChangeStart->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_RatePercentChangeStart = newValue; - - m_bLoopDetect = true; - this->Update_Slider_RatePercentChangeStart(); - m_bLoopDetect = false; - - FindWindow(wxID_OK)->Enable(CheckParameters()); } + + Update_Slider_RatePercentChangeStart(); } -void TimeScaleDialog::OnText_RatePercentChangeEnd(wxCommandEvent & WXUNUSED(event)) +void EffectTimeScale::OnText_RatePercentChangeEnd(wxCommandEvent & WXUNUSED(evt)) { - if (m_bLoopDetect) + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { return; - - if (m_pTextCtrl_RatePercentChangeEnd) { - wxString str = m_pTextCtrl_RatePercentChangeEnd->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_RatePercentChangeEnd = newValue; - - m_bLoopDetect = true; - this->Update_Slider_RatePercentChangeEnd(); - m_bLoopDetect = false; - - FindWindow(wxID_OK)->Enable(CheckParameters()); } + + Update_Slider_RatePercentChangeEnd(); } -void TimeScaleDialog::OnSlider_RatePercentChangeStart(wxCommandEvent & WXUNUSED(event)) +void EffectTimeScale::OnSlider_RatePercentChangeStart(wxCommandEvent & evt) { - if (m_bLoopDetect) + m_RatePercentChangeStart = (double) evt.GetInt(); + + Update_Text_RatePercentChangeStart(); +} + +void EffectTimeScale::OnSlider_RatePercentChangeEnd(wxCommandEvent & evt) +{ + m_RatePercentChangeEnd = (double) evt.GetInt(); + + Update_Text_RatePercentChangeEnd(); +} + +void EffectTimeScale::OnText_PitchHalfStepsStart(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { return; - - if (m_pSlider_RatePercentChangeStart) { - m_RatePercentChangeStart = (double)(m_pSlider_RatePercentChangeStart->GetValue()); - - m_bLoopDetect = true; - this->Update_Text_RatePercentChangeStart(); - m_bLoopDetect = false; } + + m_PitchPercentChangeStart = HalfStepsToPercentChange(m_PitchHalfStepsStart); + Update_Text_PitchPercentChangeStart(); } -void TimeScaleDialog::OnSlider_RatePercentChangeEnd(wxCommandEvent & WXUNUSED(event)) +void EffectTimeScale::OnText_PitchHalfStepsEnd(wxCommandEvent & WXUNUSED(evt)) { - if (m_bLoopDetect) + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { return; - - if (m_pSlider_RatePercentChangeEnd) { - m_RatePercentChangeEnd = (double)(m_pSlider_RatePercentChangeEnd->GetValue()); - - m_bLoopDetect = true; - this->Update_Text_RatePercentChangeEnd(); - m_bLoopDetect = false; } + + m_PitchPercentChangeEnd = HalfStepsToPercentChange(m_PitchHalfStepsEnd); + Update_Text_PitchPercentChangeEnd(); } -void TimeScaleDialog::OnText_PitchHalfStepsStart(wxCommandEvent & WXUNUSED(event)) +void EffectTimeScale::OnText_PitchPercentChangeStart(wxCommandEvent & WXUNUSED(evt)) { - if (m_bLoopDetect) + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { return; - - if (m_pTextCtrl_PitchHalfStepsStart) { - wxString str = m_pTextCtrl_PitchHalfStepsStart->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_PitchHalfStepsStart = newValue; - m_PitchPercentChangeStart = HalfStepsToPercentChange(newValue); - - m_bLoopDetect = true; - this->Update_Text_PitchPercentChangeStart(); - m_bLoopDetect = false; - - FindWindow(wxID_OK)->Enable(CheckParameters()); } + + m_PitchHalfStepsStart = PercentChangeToHalfSteps(m_PitchPercentChangeStart); + Update_Text_PitchHalfStepsStart(); } -void TimeScaleDialog::OnText_PitchHalfStepsEnd(wxCommandEvent & WXUNUSED(event)) +void EffectTimeScale::OnText_PitchPercentChangeEnd(wxCommandEvent & WXUNUSED(evt)) { - if (m_bLoopDetect) + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { return; - - if (m_pTextCtrl_PitchHalfStepsEnd) { - wxString str = m_pTextCtrl_PitchHalfStepsEnd->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_PitchHalfStepsEnd = newValue; - m_PitchPercentChangeEnd = HalfStepsToPercentChange(newValue); - - m_bLoopDetect = true; - this->Update_Text_PitchPercentChangeEnd(); - m_bLoopDetect = false; - - FindWindow(wxID_OK)->Enable(CheckParameters()); } -} -void TimeScaleDialog::OnText_PitchPercentChangeStart(wxCommandEvent & WXUNUSED(event)) -{ - if (m_bLoopDetect) - return; - - if (m_pTextCtrl_PitchPercentChangeStart) { - wxString str = m_pTextCtrl_PitchPercentChangeStart->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_PitchPercentChangeStart = newValue; - m_PitchHalfStepsStart = PercentChangeToHalfSteps(newValue); - - m_bLoopDetect = true; - this->Update_Text_PitchHalfStepsStart(); - m_bLoopDetect = false; - - FindWindow(wxID_OK)->Enable(CheckParameters()); - } -} - -void TimeScaleDialog::OnText_PitchPercentChangeEnd(wxCommandEvent & WXUNUSED(event)) -{ - if (m_bLoopDetect) - return; - - if (m_pTextCtrl_PitchPercentChangeEnd) { - wxString str = m_pTextCtrl_PitchPercentChangeEnd->GetValue(); - double newValue = 0; - str.ToDouble(&newValue); - m_PitchPercentChangeEnd = newValue; - m_PitchHalfStepsEnd = PercentChangeToHalfSteps(newValue); - - m_bLoopDetect = true; - this->Update_Text_PitchHalfStepsEnd(); - m_bLoopDetect = false; - - FindWindow(wxID_OK)->Enable(CheckParameters()); - } -} - -void TimeScaleDialog::OnCheckBox_PreAnalyze(wxCommandEvent & WXUNUSED(event)) -{ - if (m_pCheckBox_PreAnalyze) { - m_PreAnalyze = m_pCheckBox_PreAnalyze->GetValue(); - } -} - -void TimeScaleDialog::Update_Text_RatePercentChangeStart() -{ - if (m_pTextCtrl_RatePercentChangeStart) { - wxString str; - str.Printf(wxT("%.3f"), m_RatePercentChangeStart); - m_pTextCtrl_RatePercentChangeStart->SetValue(str); - } -} - -void TimeScaleDialog::Update_Text_RatePercentChangeEnd() -{ - if (m_pTextCtrl_RatePercentChangeEnd) { - wxString str; - str.Printf(wxT("%.3f"), m_RatePercentChangeEnd); - m_pTextCtrl_RatePercentChangeEnd->SetValue(str); - } -} - -void TimeScaleDialog::Update_Slider_RatePercentChangeStart() -{ - if (m_pSlider_RatePercentChangeStart) { - m_pSlider_RatePercentChangeStart->SetValue((int)(m_RatePercentChangeStart + 0.5)); - } -} - -void TimeScaleDialog::Update_Slider_RatePercentChangeEnd() -{ - if (m_pSlider_RatePercentChangeEnd) { - m_pSlider_RatePercentChangeEnd->SetValue((int)(m_RatePercentChangeEnd + 0.5)); - } -} - -void TimeScaleDialog::Update_Text_PitchHalfStepsStart() -{ - if (m_pTextCtrl_PitchHalfStepsStart) { - wxString str; - str.Printf(wxT("%.3f"), m_PitchHalfStepsStart); - m_pTextCtrl_PitchHalfStepsStart->SetValue(str); - } -} - -void TimeScaleDialog::Update_Text_PitchHalfStepsEnd() -{ - if (m_pTextCtrl_PitchHalfStepsEnd) { - wxString str; - str.Printf(wxT("%.3f"), m_PitchHalfStepsEnd); - m_pTextCtrl_PitchHalfStepsEnd->SetValue(str); - } -} - -void TimeScaleDialog::Update_Text_PitchPercentChangeStart() -{ - if (m_pTextCtrl_PitchPercentChangeStart) { - wxString str; - str.Printf(wxT("%.3f"), m_PitchPercentChangeStart); - m_pTextCtrl_PitchPercentChangeStart->SetValue(str); - } -} - -void TimeScaleDialog::Update_Text_PitchPercentChangeEnd() -{ - if (m_pTextCtrl_PitchPercentChangeEnd) { - wxString str; - str.Printf(wxT("%.3f"), m_PitchPercentChangeEnd); - m_pTextCtrl_PitchPercentChangeEnd->SetValue(str); - } -} - -void TimeScaleDialog::Update_CheckBox_PreAnalyze() -{ - if (m_pCheckBox_PreAnalyze) { - m_pCheckBox_PreAnalyze->SetValue(m_PreAnalyze); - } + m_PitchHalfStepsEnd = PercentChangeToHalfSteps(m_PitchPercentChangeEnd); + Update_Text_PitchHalfStepsEnd(); } #endif diff --git a/src/effects/TimeScale.h b/src/effects/TimeScale.h index 09bbf2b25..79fb9eb72 100644 --- a/src/effects/TimeScale.h +++ b/src/effects/TimeScale.h @@ -15,86 +15,63 @@ #ifndef __AUDACITY_EFFECT_TIMESCALE__ #define __AUDACITY_EFFECT_TIMESCALE__ +#include +#include +#include +#include + +#include "../ShuttleGui.h" + #include "SBSMSEffect.h" -#include -#include -#include +#define TIMESCALE_PLUGIN_SYMBOL wxTRANSLATE("Time Scale") -class wxString; -class wxTextCtrl; - -class EffectTimeScale : public EffectSBSMS { - - public: +class EffectTimeScale : public EffectSBSMS +{ +public: EffectTimeScale(); + virtual ~EffectTimeScale(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Sliding Time Scale/Pitch Shift...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://audacityteam.org/namespace#PitchAndTempo")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetName(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Time Scale")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Changing Tempo/Pitch")); - } + virtual EffectType GetType(); - // Useful only after PromptUser values have been set. - virtual wxString GetEffectDescription(); + // EffectClientInterface implementation + + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + + // Effect implementation virtual bool Init(); - - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); virtual bool Process(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); - private: - double m_RatePercentChangeStart; - double m_RatePercentChangeEnd; - double m_PitchHalfStepsStart; - double m_PitchHalfStepsEnd; - double m_PitchPercentChangeStart; - double m_PitchPercentChangeEnd; - bool m_PreAnalyze; +private: + // EffectTimeScale implementation - friend class TimeScaleDialog; -}; + inline double PercentChangeToRatio(double percentChange); + inline double HalfStepsToPercentChange(double halfSteps); + inline double PercentChangeToHalfSteps(double percentChange); -//---------------------------------------------------------------------------- -// TimeScaleDialog -//---------------------------------------------------------------------------- + void OnText_RatePercentChangeStart(wxCommandEvent & evt); + void OnText_RatePercentChangeEnd(wxCommandEvent & evt); + void OnText_PitchPercentChangeStart(wxCommandEvent & evt); + void OnText_PitchPercentChangeEnd(wxCommandEvent & evt); + void OnText_PitchHalfStepsStart(wxCommandEvent & evt); + void OnText_PitchHalfStepsEnd(wxCommandEvent & evt); + void OnSlider_RatePercentChangeStart(wxCommandEvent & evt); + void OnSlider_RatePercentChangeEnd(wxCommandEvent & evt); + void OnCheckBox_PreAnalyze(wxCommandEvent & evt); -class TimeScaleDialog:public EffectDialog { - public: - TimeScaleDialog(EffectTimeScale * effect, - wxWindow * parent); - - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - - private: - // handlers - void OnText_RatePercentChangeStart(wxCommandEvent & event); - void OnText_RatePercentChangeEnd(wxCommandEvent & event); - void OnText_PitchPercentChangeStart(wxCommandEvent & event); - void OnText_PitchPercentChangeEnd(wxCommandEvent & event); - void OnText_PitchHalfStepsStart(wxCommandEvent & event); - void OnText_PitchHalfStepsEnd(wxCommandEvent & event); - void OnSlider_RatePercentChangeStart(wxCommandEvent & event); - void OnSlider_RatePercentChangeEnd(wxCommandEvent & event); - void OnCheckBox_PreAnalyze(wxCommandEvent & event); - - // helper fns - bool CheckParameters(); void Update_Text_RatePercentChangeStart(); void Update_Text_RatePercentChangeEnd(); void Update_Text_PitchPercentChangeStart(); @@ -103,13 +80,15 @@ class TimeScaleDialog:public EffectDialog { void Update_Text_PitchHalfStepsEnd(); void Update_Slider_RatePercentChangeStart(); void Update_Slider_RatePercentChangeEnd(); - void Update_CheckBox_PreAnalyze(); - private: - EffectTimeScale *mEffect; - bool m_bLoopDetect; +private: + double m_RatePercentChangeStart; + double m_RatePercentChangeEnd; + double m_PitchHalfStepsStart; + double m_PitchHalfStepsEnd; + double m_PitchPercentChangeStart; + double m_PitchPercentChangeEnd; - // controls wxTextCtrl *m_pTextCtrl_RatePercentChangeStart; wxTextCtrl *m_pTextCtrl_RatePercentChangeEnd; wxSlider *m_pSlider_RatePercentChangeStart; @@ -118,19 +97,8 @@ class TimeScaleDialog:public EffectDialog { wxTextCtrl *m_pTextCtrl_PitchHalfStepsEnd; wxTextCtrl *m_pTextCtrl_PitchPercentChangeStart; wxTextCtrl *m_pTextCtrl_PitchPercentChangeEnd; - wxCheckBox *m_pCheckBox_PreAnalyze; - public: - double m_RatePercentChangeStart; - double m_RatePercentChangeEnd; - double m_PitchHalfStepsStart; - double m_PitchHalfStepsEnd; - double m_PitchPercentChangeStart; - double m_PitchPercentChangeEnd; - bool m_PreAnalyze; - - private: - DECLARE_EVENT_TABLE() + DECLARE_EVENT_TABLE(); }; #endif // __AUDACITY_EFFECT_TIMESCALE diff --git a/src/effects/ToneGen.cpp b/src/effects/ToneGen.cpp index 68ce8ba94..69254ba7f 100644 --- a/src/effects/ToneGen.cpp +++ b/src/effects/ToneGen.cpp @@ -16,360 +16,431 @@ An extended mode of EffectToneGen supports 'chirps' where the frequency changes smoothly during the tone. -*//****************************************************************//** - -\class ToneGenDialog -\brief Dialog used with EffectToneGen - *//*******************************************************************/ #include "../Audacity.h" -#include "ToneGen.h" -#include "../FFT.h" -#include "../Project.h" -#include "../ShuttleGui.h" -#include "../WaveTrack.h" -#include "../Prefs.h" -#include "../widgets/NumericTextCtrl.h" - -#include -#include -#include #include +#include + +#include + +#include "../Project.h" +#include "../widgets/NumericTextCtrl.h" +#include "../widgets/valnum.h" + +#include "ToneGen.h" + +enum kInterpolations +{ + kLinear, + kLogarithmic, + kNumInterpolations +}; + +static const wxString kInterStrings[kNumInterpolations] = +{ + wxTRANSLATE("Linear"), + wxTRANSLATE("Logarithmic") +}; + +enum kWaveforms +{ + kSine, + kSquare, + kSawtooth, + kSquareNoAlias, + kNumWaveforms +}; + +static const wxString kWaveStrings[kNumWaveforms] = +{ + wxTRANSLATE("Sine"), + wxTRANSLATE("Square"), + wxTRANSLATE("Sawtooth"), + wxTRANSLATE("Square, no alias") +}; + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( StartFreq, double, wxTRANSLATE("StartFreq"), 440.0, 1.0, DBL_MAX, 1 ); +Param( EndFreq, double, wxTRANSLATE("EndFreq"), 1320.0, 1.0, DBL_MAX, 1 ); +Param( StartAmp, double, wxTRANSLATE("StartAmp"), 0.8, 0.0, 1.0, 1 ); +Param( EndAmp, double, wxTRANSLATE("EndAmp"), 0.1, 0.0, 1.0, 1 ); +Param( Frequency, double, wxTRANSLATE("Frequency"), 440.0, 1.0, DBL_MAX, 1 ); +Param( Amplitude, double, wxTRANSLATE("Amplitude"), 0.8, 0.0, 1.0, 1 ); +Param( Waveform, int, wxTRANSLATE("Waveform"), 0, 0, kNumWaveforms - 1, 1 ); +Param( Interp, int, wxTRANSLATE("Interpolation"), 0, 0, kNumInterpolations - 1, 1 ); // // EffectToneGen // -EffectToneGen::EffectToneGen() +BEGIN_EVENT_TABLE(EffectToneGen, wxEvtHandler) + EVT_TEXT(wxID_ANY, EffectToneGen::OnControlUpdate) +END_EVENT_TABLE(); + +EffectToneGen::EffectToneGen(bool isChirp) { - SetEffectFlags(BUILTIN_EFFECT | INSERT_EFFECT); - mbChirp = false; - mbLogInterpolation = false; - waveform = 0; //sine - frequency[0] = float(440.0); //Hz - frequency[1] = float(1320.0); //Hz - amplitude[0] = float(0.8); - amplitude[1] = float(0.1); - interpolation = 0; + wxASSERT(kNumWaveforms == WXSIZEOF(kWaveStrings)); + wxASSERT(kNumInterpolations == WXSIZEOF(kInterStrings)); + + mChirp = isChirp; + + mWaveform = DEF_Waveform; + mFrequency[0] = DEF_StartFreq; + mFrequency[1] = DEF_EndFreq; + mAmplitude[0] = DEF_StartAmp; + mAmplitude[1] = DEF_EndAmp; + mInterpolation = DEF_Interp; + mDuration = GetDuration(); + + for (int i = 0; i < kNumWaveforms; i++) + { + mWaveforms.Add(wxGetTranslation(kWaveStrings[i])); + } + + for (int i = 0; i < kNumInterpolations; i++) + { + mInterpolations.Add(wxGetTranslation(kInterStrings[i])); + } } -wxString EffectToneGen::GetEffectDescription() { - // Note: This is useful only after values have been set. - /// \todo update to include *all* chirp parameters?? - const wxChar* waveformNames[] = {wxT("sine"), wxT("square"), wxT("sawtooth"), wxT("square, no alias")}; - //const wxChar* interpolationNames[] = {wxT("linear"), wxT("logarithmic")}; - return wxString::Format(_("Applied effect: Generate %s wave %s, frequency = %.2f Hz, amplitude = %.2f, %.6lf seconds"), - waveformNames[waveform], mbChirp ? wxT("chirp") : wxT("tone"), frequency[0], amplitude[0], mDuration); +EffectToneGen::~EffectToneGen() +{ } -bool EffectToneGen::PromptUser() +// IdentInterface implementation + +wxString EffectToneGen::GetSymbol() { - wxArrayString waveforms; - wxArrayString interpolations; - interpolations.Add(_("Linear")); - interpolations.Add(_("Logarithmic")); - ToneGenDialog dlog(this, mParent, mbChirp ? _("Chirp Generator") : _("Tone Generator")); - waveforms.Add(_("Sine")); - waveforms.Add(_("Square")); - waveforms.Add(_("Sawtooth")); - waveforms.Add(_("Square, no alias")); - dlog.isSelection= false; + return mChirp + ? CHIRP_PLUGIN_SYMBOL + : TONE_PLUGIN_SYMBOL; +} - if (mT1 > mT0) { - mDuration = mT1 - mT0; - dlog.isSelection= true; - } - else { - // Retrieve last used values - gPrefs->Read(wxT("/Effects/ToneGen/Duration"), &mDuration, 30L); - } +wxString EffectToneGen::GetDescription() +{ + return mChirp + ? wxTRANSLATE("Generates four different types of tone waveform while allowing starting and ending amplitude and frequency") + : wxTRANSLATE("Generates four different types of tone waveform"); +} - dlog.mbChirp = mbChirp; - dlog.waveform = waveform; - dlog.frequency[0] = frequency[0]; - dlog.frequency[1] = frequency[1]; - dlog.amplitude[0] = amplitude[0]; - dlog.amplitude[1] = amplitude[1]; - dlog.mDuration = mDuration; - dlog.waveforms = &waveforms; - dlog.interpolation = interpolation; - dlog.interpolations = &interpolations; - dlog.Init(); - dlog.TransferDataToWindow(); - dlog.Fit(); - dlog.ShowModal(); +// EffectIdentInterface implementation - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; +EffectType EffectToneGen::GetType() +{ + return EffectTypeGenerate; +} + +// EffectClientInterface implementation + +int EffectToneGen::GetAudioOutCount() +{ + return 1; +} + +bool EffectToneGen::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) +{ + mPositionInCycles = 0.0; + mSample = 0; - waveform = dlog.waveform; - frequency[0] = dlog.frequency[0]; - frequency[1] = dlog.frequency[1]; - amplitude[0] = dlog.amplitude[0]; - amplitude[1] = dlog.amplitude[1]; - interpolation = dlog.interpolation; - if (interpolation==0) - mbLogInterpolation = false; - if (interpolation==1) - mbLogInterpolation = true; - if( !mbChirp ) - { - frequency[1] = frequency[0]; - amplitude[1] = amplitude[0]; - } - mDuration = dlog.mDuration; - /* Save last used values. - Save duration unless value was got from selection, so we save only - when user explicitly set up a value */ - if (mT1 == mT0) // ANSWER ME: Only if end time equals start time? - { - return (gPrefs->Write(wxT("/Effects/ToneGen/Duration"), mDuration) && - gPrefs->Flush()); - } return true; } -bool EffectToneGen::TransferParameters( Shuttle & WXUNUSED(shuttle) ) -{ -/// \todo this should in time be using ShuttleGui too. -// shuttle.TransferInt("",,0); - return true; -} - -bool EffectToneGen::MakeTone(float *buffer, sampleCount len) +sampleCount EffectToneGen::ProcessBlock(float **WXUNUSED(inBlock), float **outBlock, sampleCount blockLen) { + float *buffer = outBlock[0]; double throwaway = 0; //passed to modf but never used sampleCount i; double f = 0.0; - double a,b; + double a, b; int k; double frequencyQuantum; double BlendedFrequency; double BlendedAmplitude; - double BlendedLogFrequency = 0.0f; + double BlendedLogFrequency = 0.0; // calculate delta, and reposition from where we left - double amplitudeQuantum = (amplitude[1]-amplitude[0]) / numSamples; - BlendedAmplitude = amplitude[0] + amplitudeQuantum * mSample; + double amplitudeQuantum = (mAmplitude[1] - mAmplitude[0]) / mSampleCnt; + BlendedAmplitude = mAmplitude[0] + amplitudeQuantum * mSample; // precalculations: - double pre2PI = 2 * M_PI; - double pre4divPI = 4. / M_PI; + double pre2PI = 2.0 * M_PI; + double pre4divPI = 4.0 / M_PI; // initial setup should calculate deltas - if( mbLogInterpolation ) + if (mInterpolation == kLogarithmic) { // this for log interpolation - logFrequency[0] = log10( frequency[0] ); - logFrequency[1] = log10( frequency[1] ); + mLogFrequency[0] = log10(mFrequency[0]); + mLogFrequency[1] = log10(mFrequency[1]); // calculate delta, and reposition from where we left - frequencyQuantum = (logFrequency[1]-logFrequency[0]) / numSamples; - BlendedLogFrequency = logFrequency[0] + frequencyQuantum * mSample; - BlendedFrequency = pow( 10.0, (double)BlendedLogFrequency ); - } else { + frequencyQuantum = (mLogFrequency[1] - mLogFrequency[0]) / mSampleCnt; + BlendedLogFrequency = mLogFrequency[0] + frequencyQuantum * mSample; + BlendedFrequency = pow(10.0, BlendedLogFrequency); + } + else + { // this for regular case, linear interpolation - frequencyQuantum = (frequency[1]-frequency[0]) / numSamples; - BlendedFrequency = frequency[0] + frequencyQuantum * mSample; + frequencyQuantum = (mFrequency[1] - mFrequency[0]) / mSampleCnt; + BlendedFrequency = mFrequency[0] + frequencyQuantum * mSample; } // synth loop - for (i = 0; i < len; i++) { - switch (waveform) { - case 0: //sine - f = (float) sin(pre2PI * mPositionInCycles/mCurRate); + for (i = 0; i < blockLen; i++) + { + switch (mWaveform) + { + case kSine: + f = sin(pre2PI * mPositionInCycles / mSampleRate); break; - case 1: //square - f = (modf(mPositionInCycles/mCurRate, &throwaway) < 0.5) ? 1.0f :-1.0f; + case kSquare: + f = (modf(mPositionInCycles / mSampleRate, &throwaway) < 0.5) ? 1.0 : -1.0; break; - case 2: //sawtooth - f = (2 * modf(mPositionInCycles/mCurRate+0.5f, &throwaway)) -1.0f; + case kSawtooth: + f = (2.0 * modf(mPositionInCycles / mSampleRate + 0.5, &throwaway)) - 1.0; break; - case 3: //square, no alias. Good down to 110Hz @ 44100Hz sampling. + case kSquareNoAlias: // Good down to 110Hz @ 44100Hz sampling. //do fundamental (k=1) outside loop - b = (1. + cos((pre2PI * BlendedFrequency)/mCurRate))/pre4divPI; //scaling - f = (float) pre4divPI * sin(pre2PI * mPositionInCycles/mCurRate); - for(k=3; (k<200) && (k * BlendedFrequency < mCurRate/2.); k+=2) - { - //Hanning Window in freq domain - a = 1. + cos((pre2PI * k * BlendedFrequency)/mCurRate); - //calc harmonic, apply window, scale to amplitude of fundamental - f += (float) a * sin(pre2PI * mPositionInCycles/mCurRate * k)/(b*k); - } + b = (1.0 + cos((pre2PI * BlendedFrequency) / mSampleRate)) / pre4divPI; //scaling + f = pre4divPI * sin(pre2PI * mPositionInCycles / mSampleRate); + for (k = 3; (k < 200) && (k * BlendedFrequency < mSampleRate / 2.0); k += 2) + { + //Hanning Window in freq domain + a = 1.0 + cos((pre2PI * k * BlendedFrequency) / mSampleRate); + //calc harmonic, apply window, scale to amplitude of fundamental + f += a * sin(pre2PI * mPositionInCycles / mSampleRate * k) / (b * k); + } } // insert value in buffer - buffer[i] = BlendedAmplitude * f; + buffer[i] = (float) (BlendedAmplitude * f); // update freq,amplitude mPositionInCycles += BlendedFrequency; BlendedAmplitude += amplitudeQuantum; - if (mbLogInterpolation) { + if (mInterpolation == kLogarithmic) + { BlendedLogFrequency += frequencyQuantum; - BlendedFrequency = pow( 10.0, (double)BlendedLogFrequency); - } else { + BlendedFrequency = pow(10.0, BlendedLogFrequency); + } + else + { BlendedFrequency += frequencyQuantum; } } // update external placeholder - mSample += len; + mSample += blockLen; + + return blockLen; +} + +bool EffectToneGen::GetAutomationParameters(EffectAutomationParameters & parms) +{ + if (mChirp) + { + parms.Write(KEY_StartFreq, mFrequency[0]); + parms.Write(KEY_EndFreq, mFrequency[1]); + parms.Write(KEY_StartAmp, mAmplitude[0]); + parms.Write(KEY_EndAmp, mAmplitude[1]); + } + else + { + parms.Write(KEY_Frequency, mFrequency[0]); + parms.Write(KEY_Amplitude, mAmplitude[0]); + } + + parms.Write(KEY_Waveform, mWaveforms[mWaveform]); + parms.Write(KEY_Interp, mInterpolations[mInterpolation]); + return true; } -void EffectToneGen::BeforeGenerate() +bool EffectToneGen::SetAutomationParameters(EffectAutomationParameters & parms) { - mPositionInCycles = 0.0; -} - -void EffectToneGen::GenerateBlock(float *data, - const WaveTrack & WXUNUSED(track), - sampleCount block) -{ - MakeTone(data, block); -} - -void EffectToneGen::BeforeTrack(const WaveTrack &track) -{ - mSample = 0; - mCurRate = track.GetRate(); -} - -// WDR: class implementations - -//---------------------------------------------------------------------------- -// ToneGenDialog -//---------------------------------------------------------------------------- - -#define FREQ_MIN 1 -#define AMP_MIN 0 -#define AMP_MAX 1 - -BEGIN_EVENT_TABLE(ToneGenDialog, EffectDialog) - EVT_COMMAND(wxID_ANY, EVT_TIMETEXTCTRL_UPDATED, ToneGenDialog::OnTimeCtrlUpdate) -END_EVENT_TABLE() - -ToneGenDialog::ToneGenDialog(EffectToneGen * effect, wxWindow * parent, const wxString & title) -: EffectDialog(parent, title, INSERT_EFFECT), - mEffect(effect) -{ - mToneDurationT = NULL; - mbChirp = false; -} - -/// Populates simple dialog that has a single tone. -void ToneGenDialog::PopulateOrExchangeStandard( ShuttleGui & S ) -{ - S.StartMultiColumn(2, wxCENTER); + ReadAndVerifyEnum(Waveform, mWaveforms); + ReadAndVerifyEnum(Interp, mInterpolations); + if (mChirp) { - S.TieChoice(_("Waveform") + wxString(wxT(":")), waveform, waveforms); - S.SetSizeHints(-1, -1); - - // The added colon to improve visual consistency was placed outside - // the translatable strings to avoid breaking translations close to 2.0. - // TODO: Make colon part of the translatable string after 2.0. - S.TieNumericTextBox(_("Frequency (Hz)") + wxString(wxT(":")), frequency[0], 5); - S.TieNumericTextBox(_("Amplitude (0-1)") + wxString(wxT(":")), amplitude[0], 5); - S.AddPrompt(_("Duration") + wxString(wxT(":"))); - if (mToneDurationT == NULL) - { - mToneDurationT = new - NumericTextCtrl(NumericConverter::TIME, this, - wxID_ANY, - isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), - mDuration, - mEffect->mProjectRate, - wxDefaultPosition, - wxDefaultSize, - true); - mToneDurationT->SetName(_("Duration")); - mToneDurationT->EnableMenu(); - } - S.AddWindow(mToneDurationT); + ReadAndVerifyDouble(StartFreq); + ReadAndVerifyDouble(EndFreq); + ReadAndVerifyDouble(StartAmp); + ReadAndVerifyDouble(EndAmp); + mFrequency[0] = StartFreq; + mFrequency[1] = EndFreq; + mAmplitude[0] = StartAmp; + mAmplitude[1] = EndAmp; } - S.EndMultiColumn(); -} - -/// Populates more complex dialog that has a chirp. -void ToneGenDialog::PopulateOrExchangeExtended( ShuttleGui & S ) -{ - S.StartMultiColumn(2, wxCENTER); - { - S.TieChoice(_("Waveform:"), waveform, waveforms); - S.SetSizeHints(-1, -1); - } - S.EndMultiColumn(); - S.StartMultiColumn(3, wxCENTER); - { - S.AddFixedText(wxT("")); - S.AddTitle(_("Start")); - S.AddTitle(_("End")); - - // The added colon to improve visual consistency was placed outside - // the translatable strings to avoid breaking translations close to 2.0. - // TODO: Make colon part of the translatable string after 2.0. - S.TieNumericTextBox(_("Frequency (Hz)") + wxString(wxT(":")), frequency[0], 10)->SetName(_("Frequency Hertz Start")); - S.TieNumericTextBox(wxT(""), frequency[1], 10)->SetName(_("Frequency Hertz End")); - S.TieNumericTextBox(_("Amplitude (0-1)") + wxString(wxT(":")), amplitude[0], 10)->SetName(_("Amplitude Start")); - S.TieNumericTextBox(wxT(""), amplitude[1], 10)->SetName(_("Amplitude End")); - } - S.EndMultiColumn(); - S.StartMultiColumn(2, wxCENTER); - { - S.TieChoice(_("Interpolation:"), interpolation, interpolations); - S.AddPrompt(_("Duration") + wxString(wxT(":"))); - if (mToneDurationT == NULL) - { - mToneDurationT = new - NumericTextCtrl(NumericConverter::TIME, this, - wxID_ANY, - isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), - mDuration, - mEffect->mProjectRate, - wxDefaultPosition, - wxDefaultSize, - true); - mToneDurationT->SetName(_("Duration")); - mToneDurationT->EnableMenu(); - } - S.AddWindow(mToneDurationT); - } - S.EndMultiColumn(); -} - -void ToneGenDialog::PopulateOrExchange(ShuttleGui & S) -{ - if( !mbChirp ) - PopulateOrExchangeStandard( S ); else - PopulateOrExchangeExtended( S ); + { + ReadAndVerifyDouble(Frequency); + ReadAndVerifyDouble(Amplitude); + mFrequency[0] = Frequency; + mFrequency[1] = Frequency; + mAmplitude[0] = Amplitude; + mAmplitude[1] = Amplitude; + } + + mWaveform = Waveform; + mInterpolation = Interp; + + double freqMax = (GetActiveProject() ? GetActiveProject()->GetRate() : 44100.0) / 2.0; + mFrequency[1] = TrapDouble(mFrequency[1], MIN_EndFreq, freqMax); + + return true; } -bool ToneGenDialog::TransferDataToWindow() -{ - EffectDialog::TransferDataToWindow(); +// Effect implementation + +void EffectToneGen::PopulateOrExchange(ShuttleGui & S) +{ + wxTextCtrl *t; + + S.StartMultiColumn(2, wxCENTER); + { + S.TieChoice(_("Waveform:"), mWaveform, &mWaveforms); + + if (mChirp) + { + S.AddFixedText(wxT("")); + S.StartHorizontalLay(wxEXPAND); + { + S.StartHorizontalLay(wxLEFT, 50); + { + S.AddTitle(_("Start")); + } + S.EndHorizontalLay(); + + S.StartHorizontalLay(wxLEFT, 50); + { + S.AddTitle(_("End")); + } + S.EndHorizontalLay(); + } + S.EndHorizontalLay(); + + S.AddPrompt(_("Frequency (Hz):")); + S.StartHorizontalLay(wxEXPAND); + { + S.StartHorizontalLay(wxLEFT, 50); + { + FloatingPointValidator vldStartFreq(6, &mFrequency[0], NUM_VAL_NO_TRAILING_ZEROES); + vldStartFreq.SetRange(MIN_StartFreq, GetActiveProject()->GetRate() / 2.0); + t = S.AddTextBox(wxT(""), wxT(""), 12); + t->SetName(_("Frequency Hertz Start")); + t->SetValidator(vldStartFreq); + } + S.EndHorizontalLay(); + + S.StartHorizontalLay(wxLEFT, 50); + { + FloatingPointValidator vldEndFreq(6, &mFrequency[1], NUM_VAL_NO_TRAILING_ZEROES); + vldEndFreq.SetRange(MIN_EndFreq, GetActiveProject()->GetRate() / 2.0); + t = S.AddTextBox(wxT(""), wxT(""), 12); + t->SetName(_("Frequency Hertz End")); + t->SetValidator(vldEndFreq); + } + S.EndHorizontalLay(); + } + S.EndHorizontalLay(); + + S.AddPrompt(_("Amplitude (Hz):")); + S.StartHorizontalLay(wxEXPAND); + { + S.StartHorizontalLay(wxLEFT, 50); + { + FloatingPointValidator vldStartAmp(6, &mAmplitude[0], NUM_VAL_NO_TRAILING_ZEROES); + vldStartAmp.SetRange(MIN_StartAmp, MAX_StartAmp); + t = S.AddTextBox(wxT(""), wxT(""), 12); + t->SetName(_("Amplitude Start")); + t->SetValidator(vldStartAmp); + } + S.EndHorizontalLay(); + + S.StartHorizontalLay(wxLEFT, 50); + { + FloatingPointValidator vldEndAmp(6, &mAmplitude[1], NUM_VAL_NO_TRAILING_ZEROES); + vldEndAmp.SetRange(MIN_EndAmp, MAX_EndAmp); + t = S.AddTextBox(wxT(""), wxT(""), 12); + t->SetName(_("Amplitude End")); + t->SetValidator(vldEndAmp); + } + S.EndHorizontalLay(); + } + S.EndHorizontalLay(); + } + else + { + FloatingPointValidator vldFrequency(6, &mFrequency[0], NUM_VAL_NO_TRAILING_ZEROES); + vldFrequency.SetRange(MIN_Frequency, GetActiveProject()->GetRate() / 2.0); + t = S.AddTextBox(_("Frequency (Hz):"), wxT(""), 12); + t->SetValidator(vldFrequency); + + FloatingPointValidator vldAmplitude(6, &mAmplitude[1], NUM_VAL_NO_TRAILING_ZEROES); + vldAmplitude.SetRange(MIN_Amplitude, MAX_Amplitude); + t = S.AddTextBox(_("Amplitude (0-1):"), wxT(""), 12); + t->SetValidator(vldAmplitude); + } + + S.TieChoice(_("Interpolation:"), mInterpolation, &mInterpolations); + S.AddPrompt(_("Duration:")); + mToneDurationT = new + NumericTextCtrl(NumericConverter::TIME, + S.GetParent(), + wxID_ANY, + (mT1 > mT0) ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), + mDuration, + mProjectRate, + wxDefaultPosition, + wxDefaultSize, + true); + mToneDurationT->SetName(_("Duration")); + mToneDurationT->EnableMenu(); + S.AddWindow(mToneDurationT, wxALIGN_LEFT | wxALL); + } + S.EndMultiColumn(); + + return; +} + +bool EffectToneGen::TransferDataToWindow() +{ + if (!mUIParent->TransferDataToWindow()) + { + return false; + } - // Must handle this ourselves since ShuttleGui doesn't know about it mToneDurationT->SetValue(mDuration); return true; } -bool ToneGenDialog::TransferDataFromWindow() +bool EffectToneGen::TransferDataFromWindow() { - EffectDialog::TransferDataFromWindow(); + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } - amplitude[0] = TrapDouble(amplitude[0], AMP_MIN, AMP_MAX); - frequency[0] = TrapDouble(frequency[0], FREQ_MIN, (float)(GetActiveProject()->GetRate())/2.); - amplitude[1] = TrapDouble(amplitude[1], AMP_MIN, AMP_MAX); - frequency[1] = TrapDouble(frequency[1], FREQ_MIN, (float)(GetActiveProject()->GetRate())/2.); + if (!mChirp) + { + mFrequency[1] = mFrequency[0]; + mAmplitude[1] = mAmplitude[0]; + } - // Must handle this ourselves since ShuttleGui doesn't know about it mDuration = mToneDurationT->GetValue(); return true; } -void ToneGenDialog::OnTimeCtrlUpdate(wxCommandEvent & WXUNUSED(event)) { - Fit(); +// EffectToneGen implementation + +void EffectToneGen::OnControlUpdate(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; + } } diff --git a/src/effects/ToneGen.h b/src/effects/ToneGen.h index 0aa7a965f..b0883b075 100644 --- a/src/effects/ToneGen.h +++ b/src/effects/ToneGen.h @@ -13,125 +13,72 @@ #ifndef __AUDACITY_EFFECT_TONEGEN__ #define __AUDACITY_EFFECT_TONEGEN__ -#include "Generator.h" -#include "../Experimental.h" +#include +#include -#include +#include "../ShuttleGui.h" +#include "../widgets/NumericTextCtrl.h" -class wxString; -class wxChoice; -class wxTextCtrl; -class NumericTextCtrl; -class ShuttleGui; +#include "Effect.h" -#define __UNINITIALIZED__ (-1) +#define CHIRP_PLUGIN_SYMBOL wxTRANSLATE("Chirp") +#define TONE_PLUGIN_SYMBOL wxTRANSLATE("Tone") -class WaveTrack; +class EffectToneGen : public Effect +{ +public: + EffectToneGen(bool isChirp); + virtual ~EffectToneGen(); + // IdentInterface implementation -class EffectToneGen : public BlockGenerator { + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - public: - EffectToneGen(); - // A 'Chirp' is a tone that changes in frequency. - EffectToneGen & EnableForChirps(){mbChirp=true;return *this;}; + // EffectIdentInterface implementation - virtual wxString GetEffectName() { - return wxString(mbChirp? wxTRANSLATE("Chirp..."):wxTRANSLATE("Tone...")); - } + virtual EffectType GetType(); - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#GeneratorPlugin")); - return result; - } + // EffectClientInterface implementation - virtual wxString GetEffectIdentifier() { - return wxString(mbChirp ? wxT("Chirp") : wxT("Tone")); - } + virtual int GetAudioOutCount(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); - virtual wxString GetEffectAction() { - return wxString(mbChirp? _("Generating Chirp") : _("Generating Tone")); - } + // Effect implementation - // Return true if the effect supports processing via batch chains. - virtual bool SupportsChains() { - return false; - } + void PopulateOrExchange(ShuttleGui & S); + bool TransferDataFromWindow(); + bool TransferDataToWindow(); - // Useful only after PromptUser values have been set. - virtual wxString GetEffectDescription(); +private: + // EffectToneGen implementation - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); + void OnControlUpdate(wxCommandEvent & evt); - void GenerateBlock(float *data, const WaveTrack &track, sampleCount block); - void BeforeGenerate(); - void BeforeTrack(const WaveTrack &track); - - protected: - virtual bool MakeTone(float *buffer, sampleCount len); - - private: - - bool mbChirp; - bool mbLogInterpolation; - - double mPositionInCycles; - - // If we made these static variables, - // Tone and Chirp would share the same parameters. - int waveform; - float frequency[2]; - float amplitude[2]; - float logFrequency[2]; - double mCurRate; - int interpolation; +private: + bool mChirp; // mSample is an external placeholder to remember the last "buffer" // position so we use it to reinitialize from where we left sampleCount mSample; - // friendship ... - friend class ToneGenDialog; + double mPositionInCycles; -}; + // If we made these static variables, + // Tone and Chirp would share the same parameters. + int mWaveform; + int mInterpolation; + double mFrequency[2]; + double mAmplitude[2]; + double mLogFrequency[2]; -// WDR: class declarations - -//---------------------------------------------------------------------------- -// ToneGenDialog -//---------------------------------------------------------------------------- - -class ToneGenDialog:public EffectDialog { - public: - // constructors and destructors - ToneGenDialog(EffectToneGen * effect, wxWindow * parent, const wxString & title); - - // WDR: method declarations - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - - private: - void PopulateOrExchangeStandard(ShuttleGui & S); - void PopulateOrExchangeExtended(ShuttleGui & S); - void OnTimeCtrlUpdate(wxCommandEvent & event); - DECLARE_EVENT_TABLE() - - public: - EffectToneGen *mEffect; - bool mbChirp; - wxArrayString *waveforms; - int waveform; - double frequency[2]; - double amplitude[2]; - double mDuration; - bool isSelection; - int interpolation; - wxArrayString *interpolations; - - private: + wxArrayString mWaveforms; + wxArrayString mInterpolations; NumericTextCtrl *mToneDurationT; + + DECLARE_EVENT_TABLE(); }; #endif diff --git a/src/effects/TruncSilence.cpp b/src/effects/TruncSilence.cpp index b6cf9539a..305dcee5d 100644 --- a/src/effects/TruncSilence.cpp +++ b/src/effects/TruncSilence.cpp @@ -13,105 +13,192 @@ \brief Truncate Silence automatically reduces the length of passages where the volume is below a set threshold level. - \todo mBlendFrameCount only retrieved from prefs ... not using dialog - Only way to change (for windows) is thru registry - The values should be figured dynamically ... too many frames could be invalid - -*//****************************************************************//** - -\class TruncSilenceDialog -\brief Dialog used with EffectTruncSilence - *//*******************************************************************/ #include "../Audacity.h" -#include -#include -#include #include #include -#include "../Experimental.h" +#include + #include "../Prefs.h" -#include "../Project.h" #include "../WaveTrack.h" +#include "../widgets/valnum.h" + #include "TruncSilence.h" +enum kActions +{ + kTruncate, + kCompress, + kNumActions +}; + +static const wxChar *kActionStrings[kNumActions] = +{ + wxTRANSLATE("Truncate Detected Silence"), + wxTRANSLATE("Compress Excess Silence") +}; + +// Define defaults, minimums, and maximums for each parameter +#define DefaultAndLimits(name, def, min, max) \ + static const double DEF_ ## name = (def); \ + static const double MIN_ ## name = (min); \ + static const double MAX_ ## name = (max); + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( DbIndex, int, wxTRANSLATE("Db"), 0, 0, Enums::NumDbChoices - 1, 1 ); +Param( ActIndex, int, wxTRANSLATE("Action"), kTruncate, 0, kNumActions - 1, 1 ); +Param( Minimum, double, wxTRANSLATE("Minimum"), 0.5, 0.001, 10000.0, 1 ); +Param( Truncate, double, wxTRANSLATE("Truncate"), 0.5, 0.0, 10000.0, 1 ); +Param( Compress, double, wxTRANSLATE("Compress"), 50.0, 0.0, 99.9, 1 ); + +static const sampleCount DEF_BlendFrameCount = 100; + +#include WX_DEFINE_LIST(RegionList); +BEGIN_EVENT_TABLE(EffectTruncSilence, wxEvtHandler) + EVT_CHOICE(wxID_ANY, EffectTruncSilence::OnControlChange) + EVT_TEXT(wxID_ANY, EffectTruncSilence::OnControlChange) +END_EVENT_TABLE() + EffectTruncSilence::EffectTruncSilence() { - Init(); + mDbChoices = wxArrayString(Enums::NumDbChoices, Enums::GetDbChoices()); + + mInitialAllowedSilence = DEF_Minimum; + mTruncLongestAllowedSilence = DEF_Truncate; + mSilenceCompressPercent = DEF_Compress; + mTruncDbChoiceIndex = DEF_DbIndex; + mActionIndex = DEF_ActIndex; + + // This used to be changeable via the audacity.cfg/registery. Doubtful that was + // ever done. + // + // Original comment: + // + // mBlendFrameCount only retrieved from prefs ... not using dialog + // Only way to change (for windows) is thru registry + // The values should be figured dynamically ... too many frames could be invalid + mBlendFrameCount = DEF_BlendFrameCount; } -bool EffectTruncSilence::Init() -{ - mTruncDbChoiceIndex = gPrefs->Read(wxT("/Effects/TruncateSilence/DbChoiceIndex"), 4L); - if ((mTruncDbChoiceIndex < 0) || (mTruncDbChoiceIndex >= Enums::NumDbChoices)) { // corrupted Prefs? - mTruncDbChoiceIndex = 4L; - gPrefs->Write(wxT("/Effects/TruncateSilence/LongestAllowedSilence"), 4L); - } - mProcessIndex = gPrefs->Read(wxT("/Effects/TruncateSilence/ProcessChoice"), 0L); - if ((mProcessIndex < 0) || (mProcessIndex > 1)) { // corrupted Prefs? - mProcessIndex = 0L; - gPrefs->Write(wxT("/Effects/TruncateSilence/ProcessChoice"), 0L); - } - gPrefs->Read(wxT("/Effects/TruncateSilence/InitialAllowedSilence"), &mInitialAllowedSilence, 0.5); - if ((mInitialAllowedSilence < 0.001) || (mInitialAllowedSilence > 10000.0)) { // corrupted Prefs? - mInitialAllowedSilence = 0.5; - gPrefs->Write(wxT("/Effects/TruncateSilence/InitialAllowedSilence"), 0.5); - } - - gPrefs->Read(wxT("/Effects/TruncateSilence/LongestAllowedSilence"), &mTruncLongestAllowedSilence, 0.5); - if ((mTruncLongestAllowedSilence < 0.0) || (mTruncLongestAllowedSilence > 10000.0)) { // corrupted Prefs? - mTruncLongestAllowedSilence = 0.5; - gPrefs->Write(wxT("/Effects/TruncateSilence/LongestAllowedSilence"), 0.5); - } - gPrefs->Read(wxT("/Effects/TruncateSilence/CompressPercent"), &mSilenceCompressPercent, 50.0); - if ((mSilenceCompressPercent < 0.0) || (mSilenceCompressPercent > 100.0)) { // corrupted Prefs? - mSilenceCompressPercent = 50.0; - gPrefs->Write(wxT("/Effects/TruncateSilence/CompressPercent"), 50.0); - } - // mBlendFrameCount is not currently used in dialog. - mBlendFrameCount = gPrefs->Read(wxT("/Effects/TruncateSilence/BlendFrameCount"), 100L); - if ((mBlendFrameCount < 0) || (mBlendFrameCount >= 5000)) { // corrupted Prefs? - mBlendFrameCount = 100; - gPrefs->Write(wxT("/Effects/TruncateSilence/BlendFrameCount"), 100); - } - return gPrefs->Flush(); -} - -void EffectTruncSilence::End() +EffectTruncSilence::~EffectTruncSilence() { } -bool EffectTruncSilence::PromptUser() +// IdentInterface implementation + +wxString EffectTruncSilence::GetSymbol() { - TruncSilenceDialog dlog(this, mParent); + return TRUNCATESILENCE_PLUGIN_SYMBOL; +} - dlog.CentreOnParent(); - dlog.ShowModal(); +wxString EffectTruncSilence::GetDescription() +{ + return wxTRANSLATE("Automatically reduces the length of passages where the volume is below a specified level"); +} - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; +// EffectIdentInterface implementation - gPrefs->Write(wxT("/Effects/TruncateSilence/DbChoiceIndex"), mTruncDbChoiceIndex); - gPrefs->Write(wxT("/Effects/TruncateSilence/ProcessChoice"), mProcessIndex); - gPrefs->Write(wxT("/Effects/TruncateSilence/InitialAllowedSilence"), mInitialAllowedSilence); - gPrefs->Write(wxT("/Effects/TruncateSilence/LongestAllowedSilence"), mTruncLongestAllowedSilence); - gPrefs->Write(wxT("/Effects/TruncateSilence/CompressPercent"), mSilenceCompressPercent); - gPrefs->Flush(); +EffectType EffectTruncSilence::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +bool EffectTruncSilence::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_DbIndex, Enums::DbChoices[mTruncDbChoiceIndex]); + parms.Write(KEY_ActIndex, kActionStrings[mActionIndex]); + parms.Write(KEY_Minimum, mInitialAllowedSilence); + parms.Write(KEY_Truncate, mTruncLongestAllowedSilence); + parms.Write(KEY_Compress, mSilenceCompressPercent); + return true; } -bool EffectTruncSilence::TransferParameters( Shuttle & shuttle ) +bool EffectTruncSilence::SetAutomationParameters(EffectAutomationParameters & parms) { - shuttle.TransferEnum(wxT("Db"), mTruncDbChoiceIndex, Enums::NumDbChoices, Enums::GetDbChoices()); - shuttle.TransferInt(wxT("Action"), mProcessIndex, 0); - shuttle.TransferDouble(wxT("Minimum"), mInitialAllowedSilence, 0.5); - shuttle.TransferDouble(wxT("Truncate"), mTruncLongestAllowedSilence, 0.5); - shuttle.TransferDouble(wxT("Compress"), mSilenceCompressPercent, 50.0); + wxArrayString actions(kNumActions, kActionStrings); + actions.Insert(wxT("0"), 0); // Compatible with 2.1.0 and before + actions.Insert(wxT("1"), 1); // Compatible with 2.1.0 and before + + ReadAndVerifyDouble(Minimum); + ReadAndVerifyDouble(Truncate); + ReadAndVerifyDouble(Compress); + ReadAndVerifyEnum(DbIndex, mDbChoices); + ReadAndVerifyEnum(ActIndex, actions); + + mInitialAllowedSilence = Minimum; + mTruncLongestAllowedSilence = Truncate; + mSilenceCompressPercent = Compress; + mTruncDbChoiceIndex = DbIndex; + mActionIndex = ActIndex; + + // Readjust for 2.1.0 or before + if (mActionIndex >= kNumActions) + { + mActionIndex -= kNumActions; + } + + return true; +} + +// Effect implementation + +bool EffectTruncSilence::Startup() +{ + wxString base = wxT("/Effects/TruncateSilence/"); + + // Migrate settings from 2.1.0 or before + + // Already migrated, so bail + if (gPrefs->Exists(base + wxT("Migrated"))) + { + return true; + } + + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + mTruncDbChoiceIndex = gPrefs->Read(base + wxT("DbChoiceIndex"), 4L); + if ((mTruncDbChoiceIndex < 0) || (mTruncDbChoiceIndex >= Enums::NumDbChoices)) + { // corrupted Prefs? + mTruncDbChoiceIndex = 4L; + } + mActionIndex = gPrefs->Read(base + wxT("ProcessChoice"), 0L); + if ((mActionIndex < 0) || (mActionIndex > 1)) + { // corrupted Prefs? + mActionIndex = 0L; + } + gPrefs->Read(base + wxT("InitialAllowedSilence"), &mInitialAllowedSilence, 0.5); + if ((mInitialAllowedSilence < 0.001) || (mInitialAllowedSilence > 10000.0)) + { // corrupted Prefs? + mInitialAllowedSilence = 0.5; + } + gPrefs->Read(base + wxT("LongestAllowedSilence"), &mTruncLongestAllowedSilence, 0.5); + if ((mTruncLongestAllowedSilence < 0.0) || (mTruncLongestAllowedSilence > 10000.0)) + { // corrupted Prefs? + mTruncLongestAllowedSilence = 0.5; + } + gPrefs->Read(base + wxT("CompressPercent"), &mSilenceCompressPercent, 50.0); + if ((mSilenceCompressPercent < 0.0) || (mSilenceCompressPercent > 100.0)) + { // corrupted Prefs? + mSilenceCompressPercent = 50.0; + } + + SaveUserPreset(GetCurrentSettingsGroup()); + } + + // Do not migrate again + gPrefs->Write(base + wxT("Migrated"), true); + return true; } @@ -121,7 +208,7 @@ bool EffectTruncSilence::Process() const double detectFrac = .4; // Copy tracks - this->CopyInputTracks(Track::All); + CopyInputTracks(Track::All); // Lower bound on the amount of silence to find at a time -- this avoids // detecting silence repeatedly in low-frequency sounds. @@ -170,13 +257,16 @@ bool EffectTruncSilence::Process() // Keep position in overall silences list for optimization RegionList::iterator rit(silences.begin()); - while (index < end) { + while (index < end) + { // Show progress dialog, test for cancellation cancelled = TotalProgress( detectFrac * (whichTrack + index / (double)end) / (double)GetNumWaveTracks()); if (cancelled) + { break; + } // // Optimization: if not in a silent region skip ahead to the next one @@ -186,16 +276,21 @@ bool EffectTruncSilence::Process() { // Find the first silent region ending after current time if ((*rit)->end >= curTime) + { break; + } } - if (rit == silences.end()) { + if (rit == silences.end()) + { // No more regions -- no need to process the rest of the track break; } - else if ((*rit)->start > curTime) { + else if ((*rit)->start > curTime) + { // End current silent region, skip ahead - if (silentFrames >= minSilenceFrames) { + if (silentFrames >= minSilenceFrames) + { Region *r = new Region; r->start = wt->LongSamplesToTime(index - silentFrames); r->end = wt->LongSamplesToTime(index); @@ -211,7 +306,8 @@ bool EffectTruncSilence::Process() // Limit size of current block if we've reached the end sampleCount count = blockLen; - if ((index + count) > end) { + if ((index + count) > end) + { count = end - index; } @@ -219,11 +315,14 @@ bool EffectTruncSilence::Process() wt->Get((samplePtr)(buffer), floatSample, index, count); // Look for silences in current block - for (sampleCount i = 0; i < count; ++i) { - if (fabs(buffer[i]) < truncDbSilenceThreshold) { + for (sampleCount i = 0; i < count; ++i) + { + if (fabs(buffer[i]) < truncDbSilenceThreshold) + { ++silentFrames; } - else { + else + { if (silentFrames >= minSilenceFrames) { // Record the silent region @@ -272,7 +371,8 @@ bool EffectTruncSilence::Process() int whichReg = 0; RegionList::reverse_iterator rit; double totalCutLen = 0.0; // For cutting selection at the end - for (rit = silences.rbegin(); rit != silences.rend(); ++rit) { + for (rit = silences.rbegin(); rit != silences.rend(); ++rit) + { Region *r = *rit; // Progress dialog and cancellation. Do additional cleanup before return. @@ -291,11 +391,12 @@ bool EffectTruncSilence::Process() double inLength = r->end - r->start; double outLength; - switch (mProcessIndex) { - case 0: + switch (mActionIndex) + { + case kTruncate: outLength = wxMin(mTruncLongestAllowedSilence, inLength); break; - case 1: + case kCompress: outLength = mInitialAllowedSilence + (inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0; break; @@ -313,7 +414,9 @@ bool EffectTruncSilence::Process() { // Don't waste time past the end of a track if (t->GetEndTime() < r->start) + { continue; + } if (t->GetKind() == Track::Wave && ( t->GetSelected() || t->IsSyncLockSelected())) @@ -328,7 +431,8 @@ bool EffectTruncSilence::Process() cutEnd = wt->LongSamplesToTime(wt->TimeToLongSamples(cutEnd)); // Make sure the cross-fade does not affect non-silent frames - if (wt->LongSamplesToTime(blendFrames) > inLength) { + if (wt->LongSamplesToTime(blendFrames) > inLength) + { blendFrames = wt->TimeToLongSamples(inLength); } @@ -341,7 +445,8 @@ bool EffectTruncSilence::Process() wt->Get((samplePtr)buf1, floatSample, t1, blendFrames); wt->Get((samplePtr)buf2, floatSample, t2, blendFrames); - for (sampleCount i = 0; i < blendFrames; ++i) { + for (sampleCount i = 0; i < blendFrames; ++i) + { buf1[i] = ((blendFrames-i) * buf1[i] + i * buf2[i]) / (double)blendFrames; } @@ -373,6 +478,94 @@ bool EffectTruncSilence::Process() return true; } +void EffectTruncSilence::PopulateOrExchange(ShuttleGui & S) +{ + wxASSERT(kNumActions == WXSIZEOF(kActionStrings)); + + wxArrayString actionChoices; + for (int i = 0; i < kNumActions; i++) + { + actionChoices.Add(wxGetTranslation(kActionStrings[i])); + } + + S.AddSpace(0, 5); + + S.StartStatic(_("Detect Silence")); + { + S.StartMultiColumn(3, wxALIGN_CENTER_HORIZONTAL); + { + // Threshold + mTruncDbChoice = S.AddChoice(_("Level:"), wxT(""), &mDbChoices); + mTruncDbChoice->SetValidator(wxGenericValidator(&mTruncDbChoiceIndex)); + S.SetSizeHints(-1, -1); + S.AddSpace(0); // 'choices' already includes units. + + // Ignored silence + FloatingPointValidator vldDur(3, &mInitialAllowedSilence, NUM_VAL_NO_TRAILING_ZEROES); + vldDur.SetRange(MIN_Minimum, MAX_Minimum); + mInitialAllowedSilenceT = S.AddTextBox(_("Duration:"), wxT(""), 12); + mInitialAllowedSilenceT->SetValidator(vldDur); + S.AddUnits(wxT("seconds")); + } + S.EndMultiColumn(); + } + S.EndStatic(); + + S.StartStatic(_("Action")); + { + S.StartHorizontalLay(); + { + // Action choices + mActionChoice = S.AddChoice(wxT(""), wxT(""), &actionChoices); + mActionChoice->SetValidator(wxGenericValidator(&mActionIndex)); + S.SetSizeHints(-1, -1); + } + S.EndHorizontalLay(); + S.StartMultiColumn(3, wxALIGN_CENTER_HORIZONTAL); + { + // Truncation / Compression factor + + FloatingPointValidator vldTrunc(3, &mTruncLongestAllowedSilence, NUM_VAL_NO_TRAILING_ZEROES); + vldTrunc.SetRange(MIN_Truncate, MAX_Truncate); + mTruncLongestAllowedSilenceT = S.AddTextBox(_("Truncate to:"), wxT(""), 12); + mTruncLongestAllowedSilenceT->SetValidator(vldTrunc); + S.AddUnits(wxT("seconds")); + + FloatingPointValidator vldComp(3, &mSilenceCompressPercent, NUM_VAL_NO_TRAILING_ZEROES); + vldComp.SetRange(MIN_Compress, MAX_Compress); + mSilenceCompressPercentT = S.AddTextBox(_("Compress to:"), wxT(""), 12); + mSilenceCompressPercentT->SetValidator(vldComp); + S.AddUnits(wxT("percent")); + } + S.EndMultiColumn(); + } + S.EndStatic(); + + UpdateUI(); +} + +bool EffectTruncSilence::TransferDataToWindow() +{ + if (!mUIParent->TransferDataToWindow()) + { + return false; + } + + return true; +} + +bool EffectTruncSilence::TransferDataFromWindow() +{ + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } + + return true; +} + +// EffectTruncSilence implementation + // Finds the intersection of the ordered region lists, stores in dest void EffectTruncSilence::Intersect(RegionList &dest, const RegionList &src) { @@ -392,7 +585,8 @@ void EffectTruncSilence::Intersect(RegionList &dest, const RegionList &src) // This logic, causing the loop to run once after end of src, must occur // each time srcIter is updated - if (srcIter == src.end()) { + if (srcIter == src.end()) + { lastRun = true; } @@ -416,15 +610,19 @@ void EffectTruncSilence::Intersect(RegionList &dest, const RegionList &src) if (nsEnd > nsStart) { // Increment through dest until we have a region that could be affected - while (curDest->end <= nsStart) { + while (curDest->end <= nsStart) + { ++destIter; if (destIter == dest.end()) + { return; + } curDest = *destIter; } // Check for splitting dest region in two - if (nsStart > curDest->start && nsEnd < curDest->end) { + if (nsStart > curDest->start && nsEnd < curDest->end) + { // The second region Region *r = new Region; r->start = nsEnd; @@ -441,10 +639,12 @@ void EffectTruncSilence::Intersect(RegionList &dest, const RegionList &src) // work around two two wxList::insert() bugs. First, in some // versions it returns the wrong value. Second, in some versions, // it crashes when you insert at list end. - if (nextIt == dest.end()) { + if (nextIt == dest.end()) + { dest.Append(r); } - else { + else + { dest.insert(nextIt, r); } ++destIter; // (now points at the newly-inserted region) @@ -460,15 +660,20 @@ void EffectTruncSilence::Intersect(RegionList &dest, const RegionList &src) ++destIter; if (destIter == dest.end()) + { return; + } curDest = *destIter; } // Check for all dest regions that need to be removed completely - while (nsStart <= curDest->start && nsEnd >= curDest->end) { + while (nsStart <= curDest->start && nsEnd >= curDest->end) + { destIter = dest.erase(destIter); if (destIter == dest.end()) + { return; + } curDest = *destIter; } @@ -480,15 +685,18 @@ void EffectTruncSilence::Intersect(RegionList &dest, const RegionList &src) } } - if (lastRun) { + if (lastRun) + { // done lastRun = false; } - else { + else + { // Next non-silent region starts at the end of this silent region nsStart = curSrc->end; ++srcIter; - if (srcIter == src.end()) { + if (srcIter == src.end()) + { lastRun = true; } } @@ -503,169 +711,36 @@ void EffectTruncSilence::BlendFrames(float* buffer, int blendFrameCount, int lef double beforeFactor = 1.0; double afterFactor = 0.0; double adjFactor = 1.0 / (double)blendFrameCount; - for (int j = 0; j < blendFrameCount; ++j) { + for (int j = 0; j < blendFrameCount; ++j) + { bufOutput[j] = (float)((bufBefore[j] * beforeFactor) + (bufAfter[j] * afterFactor)); beforeFactor -= adjFactor; afterFactor += adjFactor; } } -//---------------------------------------------------------------------------- -// TruncSilenceDialog -//---------------------------------------------------------------------------- - -enum { - ID_DETECT_SILENCE = 1001, - ID_TRUNCATION_DURATION, - ID_COMPRESS_FACTOR, - ID_PROCESS_CHOICE -}; - -BEGIN_EVENT_TABLE(TruncSilenceDialog, EffectDialog) - EVT_BUTTON(ID_EFFECT_PREVIEW, TruncSilenceDialog::OnPreview) - EVT_CHOICE(ID_PROCESS_CHOICE, TruncSilenceDialog::OnControlChange) - EVT_TEXT(ID_DETECT_SILENCE, TruncSilenceDialog::OnControlChange) - EVT_TEXT(ID_TRUNCATION_DURATION, TruncSilenceDialog::OnControlChange) - EVT_TEXT(ID_COMPRESS_FACTOR, TruncSilenceDialog::OnControlChange) -END_EVENT_TABLE() - -TruncSilenceDialog::TruncSilenceDialog(EffectTruncSilence * effect, - wxWindow * parent) -: EffectDialog(parent, _("Truncate Silence"), PROCESS_EFFECT), - mEffect(effect) +void EffectTruncSilence::UpdateUI() { - Init(); -} - -void TruncSilenceDialog::PopulateOrExchange(ShuttleGui & S) -{ - S.AddSpace(0, 5); - - S.StartHorizontalLay(); + switch (mActionIndex) { - // Action choices - wxArrayString processChoices; - processChoices.Add(_("Truncate Detected Silence")); - processChoices.Add(_("Compress Excess Silence")); - - S.Id(ID_PROCESS_CHOICE).TieChoice(wxT(""), - mEffect->mProcessIndex, - &processChoices); - S.SetSizeHints(-1, -1); - } - S.EndHorizontalLay(); - - - S.StartStatic(_("Detect Silence")); - { - S.StartMultiColumn(3, wxALIGN_CENTER_HORIZONTAL); - { - // Threshold - wxArrayString choices(Enums::NumDbChoices, Enums::GetDbChoices()); - S.TieChoice(_("Level:"), - mEffect->mTruncDbChoiceIndex, - &choices); - S.SetSizeHints(-1, -1); - S.AddSpace(0); // 'choices' aleady includes units. - - // Ignored silence - S.Id(ID_DETECT_SILENCE).TieNumericTextBox(_("Duration:"), - mEffect->mInitialAllowedSilence, - 12); - S.AddUnits(wxT("seconds")); - } - S.EndMultiColumn(); - } - S.EndStatic(); - - S.StartMultiColumn(3, wxALIGN_CENTER_HORIZONTAL); - { - // Truncation / Compression factor - S.Id( ID_TRUNCATION_DURATION ).TieNumericTextBox(_("Truncate to:"), - mEffect->mTruncLongestAllowedSilence, - 12); - S.AddUnits(wxT("seconds")); - - S.Id( ID_COMPRESS_FACTOR ).TieNumericTextBox(_("Compress to:"), - mEffect->mSilenceCompressPercent, - 12); - S.AddUnits(wxT("percent")); - } - S.EndMultiColumn(); - - // Warnings - pWarning = S.AddVariableText( wxT("") ); - UpdateUI(); -} - -void TruncSilenceDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - mEffect->Preview(); -} - -void TruncSilenceDialog::UpdateUI() -{ - wxWindow *pWnd; - - switch (mEffect->mProcessIndex) - { - case 0: - pWnd = FindWindowById(ID_TRUNCATION_DURATION, this); - pWnd->Enable(true); - pWnd = FindWindowById(ID_COMPRESS_FACTOR, this); - pWnd->Enable(false); + case kTruncate: + mTruncLongestAllowedSilenceT->Enable(true); + mSilenceCompressPercentT->Enable(false); break; - case 1: - pWnd = FindWindowById(ID_TRUNCATION_DURATION, this); - pWnd->Enable(false); - pWnd = FindWindowById(ID_COMPRESS_FACTOR, this); - pWnd->Enable(true); + case kCompress: + mTruncLongestAllowedSilenceT->Enable(false); + mSilenceCompressPercentT->Enable(true); } } -void TruncSilenceDialog::OnControlChange(wxCommandEvent & WXUNUSED(event)) +void EffectTruncSilence::OnControlChange(wxCommandEvent & WXUNUSED(evt)) { - // We may even get called during the constructor. - // This test saves us from calling unsafe functions. - if( !IsShown() ) - return; - TransferDataFromWindow(); - - bool bOk = true; - - wxString warningText; - if (mEffect->mInitialAllowedSilence < 0.001) { - bOk = false; - warningText = _("Minimum detection duration: 0.001 seconds."); - } else if (mEffect->mInitialAllowedSilence > 10000.0) { - bOk = false; - warningText = _("Maximum detection duration: 10000 seconds."); - } - - if ((mEffect->mTruncLongestAllowedSilence < 0.0f) && (mEffect->mProcessIndex != 1)) { - bOk = false; - warningText = _("Cannot truncate to less than 0 seconds."); - } else if ((mEffect->mTruncLongestAllowedSilence > 10000.0) && (mEffect->mProcessIndex != 1)) { - bOk = false; - warningText = _("Maximum truncation length is 10000 seconds."); - } - - if ((mEffect->mSilenceCompressPercent < 0.0) && (mEffect->mProcessIndex != 0)) { - bOk = false; - warningText = _("Compression cannot be less than 0 percent."); - } else if ((mEffect->mSilenceCompressPercent >= 100.0) && (mEffect->mProcessIndex != 0)) { - bOk = false; - warningText = _("Compression must be less than 100 percent"); - } - - pWarning->SetLabel( bOk ? wxT("") : warningText); - - wxWindow *pWnd; - pWnd = FindWindowById( wxID_OK, this ); - pWnd->Enable( bOk ); - pWnd = FindWindowById( ID_EFFECT_PREVIEW, this ); - pWnd->Enable( bOk ); + mActionChoice->GetValidator()->TransferFromWindow(); UpdateUI(); + + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; + } } diff --git a/src/effects/TruncSilence.h b/src/effects/TruncSilence.h index 5b2be6a4a..54446db5c 100644 --- a/src/effects/TruncSilence.h +++ b/src/effects/TruncSilence.h @@ -17,85 +17,80 @@ #ifndef __AUDACITY_EFFECT_TRUNC_SILENCE__ #define __AUDACITY_EFFECT_TRUNC_SILENCE__ -#include "Effect.h" -#include "../Experimental.h" - +#include +#include +#include #include +#include +#include + +#include "../ShuttleGui.h" + +#include "Effect.h" + +#define TRUNCATESILENCE_PLUGIN_SYMBOL wxTRANSLATE("Truncate Silence") // Declaration of RegionList struct REGION; typedef struct REGION Region; WX_DECLARE_LIST(Region, RegionList); -class EffectTruncSilence: public Effect { - +class EffectTruncSilence : public Effect +{ public: - EffectTruncSilence(); + virtual ~EffectTruncSilence(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Truncate Silence...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://audacityteam.org/namespace#TimelineChanger")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Truncate Silence")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Truncating Silence...")); - } - virtual bool Init(); - virtual void End(); - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); + virtual EffectType GetType(); + // EffectClientInterface implementation + + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + + // Effect implementation + + virtual bool Startup(); virtual bool Process(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); + +private: + // EffectTruncSilence implementation - private: //ToDo ... put BlendFrames in Effects, Project, or other class void BlendFrames(float* buffer, int leftIndex, int rightIndex, int blendFrameCount); void Intersect(RegionList &dest, const RegionList &src); - private: + void OnControlChange(wxCommandEvent & evt); + void UpdateUI(); + +private: int mTruncDbChoiceIndex; - int mProcessIndex; - sampleCount mBlendFrameCount; + int mActionIndex; double mInitialAllowedSilence; double mTruncLongestAllowedSilence; double mSilenceCompressPercent; -friend class TruncSilenceDialog; -}; + wxArrayString mDbChoices; -//---------------------------------------------------------------------------- -// TruncSilenceDialog -//---------------------------------------------------------------------------- + sampleCount mBlendFrameCount; -class TruncSilenceDialog: public EffectDialog -{ -public: - // constructors and destructors - TruncSilenceDialog(EffectTruncSilence * effect, - wxWindow * parent); - - void PopulateOrExchange(ShuttleGui & S); - void OnPreview(wxCommandEvent & event); - void OnControlChange(wxCommandEvent & event); - void UpdateUI(); - -private: - EffectTruncSilence *mEffect; - wxStaticText * pWarning; - -private: - DECLARE_EVENT_TABLE() + wxChoice *mTruncDbChoice; + wxChoice *mActionChoice; + wxTextCtrl *mInitialAllowedSilenceT; + wxTextCtrl *mTruncLongestAllowedSilenceT; + wxTextCtrl *mSilenceCompressPercentT; + DECLARE_EVENT_TABLE(); }; #endif diff --git a/src/effects/TwoPassSimpleMono.h b/src/effects/TwoPassSimpleMono.h index fbbbec7da..2400b88d1 100644 --- a/src/effects/TwoPassSimpleMono.h +++ b/src/effects/TwoPassSimpleMono.h @@ -17,17 +17,15 @@ class WaveTrack; -class EffectTwoPassSimpleMono:public Effect { +class EffectTwoPassSimpleMono : public Effect +{ +public: + // Effect implementation - public: virtual bool Process(); - private: - bool ProcessOne(WaveTrack * t, - sampleCount start, sampleCount end); - bool ProcessPass(); - - protected: +protected: + // EffectTwoPassSimpleMono implemetation // Override these methods if you need to initialize something // before each pass. Return None if processing should stop. @@ -66,6 +64,10 @@ class EffectTwoPassSimpleMono:public Effect { int mPass; bool mSecondPassDisabled; +private: + bool ProcessOne(WaveTrack * t, + sampleCount start, sampleCount end); + bool ProcessPass(); }; #endif diff --git a/src/effects/VST/VSTEffect.cpp b/src/effects/VST/VSTEffect.cpp index ec893a355..1df6c052b 100644 --- a/src/effects/VST/VSTEffect.cpp +++ b/src/effects/VST/VSTEffect.cpp @@ -83,7 +83,6 @@ #include "../../FileNames.h" #include "../../Internat.h" #include "../../PlatformCompatibility.h" -#include "../../Prefs.h" #include "../../ShuttleGui.h" #include "../../effects/Effect.h" #include "../../widgets/NumericTextCtrl.h" @@ -609,7 +608,7 @@ bool VSTEffectsModule::RegisterPlugin(PluginManagerInterface & pm, const wxStrin if (!skip && cont) { valid = true; - pm.RegisterEffectPlugin(this, proc); + pm.RegisterPlugin(this, proc); } } break; @@ -875,7 +874,7 @@ private: /////////////////////////////////////////////////////////////////////////////// // -// LadspaEffectEventHelper +// VSTEffect // /////////////////////////////////////////////////////////////////////////////// @@ -885,45 +884,16 @@ enum ID_SLIDERS = 21000, }; -BEGIN_EVENT_TABLE(VSTEffectEventHelper, wxEvtHandler) - EVT_COMMAND_RANGE(ID_SLIDERS, ID_SLIDERS + 999, wxEVT_COMMAND_SLIDER_UPDATED, VSTEffectEventHelper::OnSlider) - - // Events from the audioMaster callback - EVT_COMMAND(wxID_ANY, EVT_SIZEWINDOW, VSTEffectEventHelper::OnSizeWindow) -END_EVENT_TABLE() - -VSTEffectEventHelper::VSTEffectEventHelper(VSTEffect *effect) -{ - mEffect = effect; -} - -VSTEffectEventHelper::~VSTEffectEventHelper() -{ -} - -// ============================================================================ -// VSTEffectEventHelper implementation -// ============================================================================ - -void VSTEffectEventHelper::OnSlider(wxCommandEvent & evt) -{ - mEffect->OnSlider(evt); -} - -void VSTEffectEventHelper::OnSizeWindow(wxCommandEvent & evt) -{ - mEffect->OnSizeWindow(evt); -} - -/////////////////////////////////////////////////////////////////////////////// -// -// VSTEffect -// -/////////////////////////////////////////////////////////////////////////////// - DEFINE_LOCAL_EVENT_TYPE(EVT_SIZEWINDOW); DEFINE_LOCAL_EVENT_TYPE(EVT_UPDATEDISPLAY); +BEGIN_EVENT_TABLE(VSTEffect, wxEvtHandler) + EVT_COMMAND_RANGE(ID_SLIDERS, ID_SLIDERS + 999, wxEVT_COMMAND_SLIDER_UPDATED, VSTEffect::OnSlider) + + // Events from the audioMaster callback + EVT_COMMAND(wxID_ANY, EVT_SIZEWINDOW, VSTEffect::OnSizeWindow) +END_EVENT_TABLE() + #if defined(__WXMAC__) // To use, change the SDK in the project to at least 10.5 @@ -987,9 +957,6 @@ OSStatus VSTEffect::OnTrackingEvent(EventRef event) // Events to be captured in the overlay window static const EventTypeSpec OverlayEventList[] = { -#if !defined(EXPERIMENTAL_REALTIME_EFFECTS) - { kEventClassWindow, kEventWindowGetClickModality }, -#endif #if 0 { kEventClassMouse, kEventMouseDown }, { kEventClassMouse, kEventMouseUp }, @@ -1114,14 +1081,6 @@ OSStatus VSTEffect::OnOverlayEvent(EventHandlerCallRef handler, EventRef event) sizeof(res), &res); -#if !defined(EXPERIMENTAL_REALTIME_EFFECTS) - // If the front window is the overlay, then make our window - // the selected one so that the mouse click goes to it instead. - if (frontwin == mOverlayRef) - { - SelectWindow(mWindowRef); - } -#endif return noErr; } break; @@ -1287,12 +1246,6 @@ OSStatus VSTEffect::OnWindowEvent(EventHandlerCallRef handler, EventRef event) &mOverlayViewTrackingHandlerRef); HIViewNewTrackingArea(root, NULL, 0, NULL); HIViewNewTrackingArea(view, NULL, 0, NULL); - -//#if !defined(EXPERIMENTAL_REALTIME_EFFECTS) - // Since we set the activation scope to independent, - // we need to make sure the overlay gets activated. - ActivateWindow(mOverlayRef, TRUE); -//#endif } } break; @@ -1490,7 +1443,6 @@ intptr_t VSTEffect::AudioMaster(AEffect * effect, return 0; } -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) case audioMasterBeginEdit: case audioMasterEndEdit: return 0; @@ -1502,13 +1454,6 @@ intptr_t VSTEffect::AudioMaster(AEffect * effect, } return 0; -#else - // These are not needed since we don't need the parameter values until after the editor - // has already been closed. If we did realtime effects, then we'd need these. - case audioMasterBeginEdit: - case audioMasterEndEdit: - case audioMasterAutomate: -#endif // We're always connected (sort of) case audioMasterPinConnected: @@ -1541,7 +1486,6 @@ VSTEffect::VSTEffect(const wxString & path, VSTEffect *master) mModule = NULL; mAEffect = NULL; mDialog = NULL; - mEventHelper = NULL; mTimer = new VSTEffectTimer(this); mTimerGuard = 0; @@ -1816,7 +1760,7 @@ int VSTEffect::GetMidiOutCount() return mMidiOuts; } -sampleCount VSTEffect::GetBlockSize(sampleCount maxBlockSize) +sampleCount VSTEffect::SetBlockSize(sampleCount maxBlockSize) { if (mUserBlockSize > maxBlockSize) { @@ -1858,7 +1802,7 @@ bool VSTEffect::IsReady() return mReady; } -bool VSTEffect::ProcessInitialize() +bool VSTEffect::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) { // Initialize time info memset(&mTimeInfo, 0, sizeof(mTimeInfo)); @@ -1893,15 +1837,15 @@ bool VSTEffect::ProcessFinalize() return true; } -sampleCount VSTEffect::ProcessBlock(float **inbuf, float **outbuf, sampleCount size) +sampleCount VSTEffect::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) { // Go let the plugin moleste the samples - callProcessReplacing(inbuf, outbuf, size); + callProcessReplacing(inBlock, outBlock, blockLen); // And track the position - mTimeInfo.samplePos += ((double) size / mTimeInfo.sampleRate); + mTimeInfo.samplePos += ((double) blockLen / mTimeInfo.sampleRate); - return size; + return blockLen; } int VSTEffect::GetChannelCount() @@ -1929,7 +1873,7 @@ bool VSTEffect::RealtimeInitialize() mMasterOut[i] = new float[mBlockSize]; } - return ProcessInitialize(); + return ProcessInitialize(0, NULL); } bool VSTEffect::RealtimeAddProcessor(int numChannels, float sampleRate) @@ -1937,7 +1881,7 @@ bool VSTEffect::RealtimeAddProcessor(int numChannels, float sampleRate) VSTEffect *slave = new VSTEffect(mPath, this); mSlaves.Add(slave); - slave->GetBlockSize(mBlockSize); + slave->SetBlockSize(mBlockSize); slave->SetChannelCount(numChannels); slave->SetSampleRate(sampleRate); @@ -1965,7 +1909,7 @@ bool VSTEffect::RealtimeAddProcessor(int numChannels, float sampleRate) callDispatcher(effEndSetProgram, 0, 0, NULL, 0.0); } - return slave->ProcessInitialize(); + return slave->ProcessInitialize(0, NULL); } bool VSTEffect::RealtimeFinalize() @@ -2093,23 +2037,7 @@ bool VSTEffect::ShowInterface(wxWindow *parent, bool forceModal) { mSampleRate = 44100; mBlockSize = 8192; - ProcessInitialize(); - } - - // I can't believe we haven't run into this before, but a terrible assumption has - // been made all along...effects do NOT have to provide textual parameters. Examples - // of effects that do not support parameters are some from BBE Sound. These effects - // are NOT broken. They just weren't written to support textual parameters. - long gui = (gPrefs->Read(wxT("/VST/GUI"), (long) true) != 0); - if (!gui && mAEffect->numParams == 0) - { -#if defined(__WXGTK__) - wxMessageBox(_("This effect does not support a textual interface. At this time, you may not use this effect on Linux."), - _("VST Effect")); -#else - wxMessageBox(_("This effect does not support a textual interface. Falling back to graphical display."), - _("VST Effect")); -#endif + ProcessInitialize(0, NULL); } mDialog = mHost->CreateUI(parent, this); @@ -2176,11 +2104,65 @@ bool VSTEffect::SetAutomationParameters(EffectAutomationParameters & parms) return true; } + +bool VSTEffect::LoadUserPreset(const wxString & name) +{ + if (!LoadParameters(name)) + { + return false; + } + + RefreshParameters(); + + return true; +} + +bool VSTEffect::SaveUserPreset(const wxString & name) +{ + return SaveParameters(name); +} + +wxArrayString VSTEffect::GetFactoryPresets() +{ + wxArrayString progs; + + // Some plugins, like Guitar Rig 5, only report 128 programs while they have hundreds. While + // I was able to come up with a hack in the Guitar Rig case to gather all of the program names + // it would not let me set a program outside of the first 128. + for (int i = 0; i < mAEffect->numPrograms; i++) + { + progs.Add(GetString(effGetProgramNameIndexed, i)); + } + + return progs; +} + +bool VSTEffect::LoadFactoryPreset(int id) +{ + callSetProgram(id); + + RefreshParameters(); + + return true; +} + +bool VSTEffect::LoadFactoryDefaults() +{ + if (!LoadParameters(mHost->GetFactoryDefaultsGroup())) + { + return false; + } + + RefreshParameters(); + + return true; +} + // ============================================================================ // EffectUIClientInterface implementation // ============================================================================ -void VSTEffect::SetUIHost(EffectUIHostInterface *host) +void VSTEffect::SetHostUI(EffectUIHostInterface *host) { mUIHost = host; } @@ -2190,8 +2172,7 @@ bool VSTEffect::PopulateUI(wxWindow *parent) mDialog = (wxDialog *) wxGetTopLevelParent(parent); mParent = parent; - mEventHelper = new VSTEffectEventHelper(this); - mParent->PushEventHandler(mEventHelper); + mParent->PushEventHandler(this); // Determine if the VST editor is supposed to be used or not mHost->GetSharedConfig(wxT("Options"), @@ -2236,8 +2217,7 @@ bool VSTEffect::HideUI() bool VSTEffect::CloseUI() { - mParent->RemoveEventHandler(mEventHelper); - delete mEventHelper; + mParent->RemoveEventHandler(this); PowerOff(); @@ -2276,48 +2256,7 @@ bool VSTEffect::CloseUI() return true; } -void VSTEffect::LoadUserPreset(const wxString & name) -{ - LoadParameters(name); - - RefreshParameters(); -} - -void VSTEffect::SaveUserPreset(const wxString & name) -{ - SaveParameters(name); -} - -wxArrayString VSTEffect::GetFactoryPresets() -{ - wxArrayString progs; - - // Some plugins, like Guitar Rig 5, only report 128 programs while they have hundreds. While - // I was able to come up with a hack in the Guitar Rig case to gather all of the program names - // it would not let me set a program outside of the first 128. - for (int i = 0; i < mAEffect->numPrograms; i++) - { - progs.Add(GetString(effGetProgramNameIndexed, i)); - } - - return progs; -} - -void VSTEffect::LoadFactoryPreset(int id) -{ - callSetProgram(id); - - RefreshParameters(); -} - -void VSTEffect::LoadFactoryDefaults() -{ - LoadParameters(mHost->GetFactoryDefaultsGroup()); - - RefreshParameters(); -} - -bool VSTEffect::CanExport() +bool VSTEffect::CanExportPresets() { return true; } @@ -2810,7 +2749,7 @@ wxArrayInt VSTEffect::GetEffectIDs() return effectIDs; } -void VSTEffect::LoadParameters(const wxString & group) +bool VSTEffect::LoadParameters(const wxString & group) { wxString value; @@ -2823,7 +2762,7 @@ void VSTEffect::LoadParameters(const wxString & group) (info.pluginVersion != mAEffect->version) || (info.numElements != mAEffect->numParams)) { - return; + return false; } if (mHost->GetPrivateConfig(group, wxT("Chunk"), value, wxEmptyString)) @@ -2837,39 +2776,44 @@ void VSTEffect::LoadParameters(const wxString & group) } delete [] buf; - return; + return true; + } + + if (!mHost->GetPrivateConfig(group, wxT("Value"), value, wxEmptyString)) + { + return false; } if (callDispatcher(effBeginLoadProgram, 0, 0, &info, 0.0) == -1) { - return; + return false; } callDispatcher(effBeginSetProgram, 0, 0, NULL, 0.0); - if (mHost->GetPrivateConfig(group, wxT("Value"), value, wxEmptyString)) - { - size_t cnt = mSlaves.GetCount(); + size_t cnt = mSlaves.GetCount(); - wxStringTokenizer st(value, wxT(',')); - for (int i = 0; st.HasMoreTokens(); i++) - { - double val = 0.0; - st.GetNextToken().ToDouble(&val); + wxStringTokenizer st(value, wxT(',')); + for (int i = 0; st.HasMoreTokens(); i++) + { + double val = 0.0; + st.GetNextToken().ToDouble(&val); - if (val >= -1.0 && val <= 1.0) + if (val >= -1.0 && val <= 1.0) + { + callSetParameter(i, val); + for (size_t i = 0; i < cnt; i++) { - callSetParameter(i, val); - for (size_t i = 0; i < cnt; i++) - { - mSlaves[i]->callSetParameter(i, val); - } + mSlaves[i]->callSetParameter(i, val); } } } + callDispatcher(effEndSetProgram, 0, 0, NULL, 0.0); + + return true; } -void VSTEffect::SaveParameters(const wxString & group) +bool VSTEffect::SaveParameters(const wxString & group) { mHost->SetPrivateConfig(group, wxT("UniqueID"), mAEffect->uniqueID); mHost->SetPrivateConfig(group, wxT("Version"), mAEffect->version); @@ -2879,11 +2823,13 @@ void VSTEffect::SaveParameters(const wxString & group) { void *chunk = NULL; int clen = (int) callDispatcher(effGetChunk, 1, 0, &chunk, 0.0); - if (clen > 0) + if (clen <= 0) { - mHost->SetPrivateConfig(group, wxT("Chunk"), VSTEffect::b64encode(chunk, clen)); - return; + return false; } + + mHost->SetPrivateConfig(group, wxT("Chunk"), VSTEffect::b64encode(chunk, clen)); + return true; } wxString parms; @@ -2892,7 +2838,7 @@ void VSTEffect::SaveParameters(const wxString & group) parms += wxString::Format(wxT(",%f"), callGetParameter(i)); } - mHost->SetPrivateConfig(group, wxT("Value"), parms.Mid(1)); + return mHost->SetPrivateConfig(group, wxT("Value"), parms.Mid(1)); } void VSTEffect::OnTimer() @@ -3519,12 +3465,26 @@ void VSTEffect::BuildFancy() void VSTEffect::BuildPlain() { + wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL); + wxScrolledWindow *scroller = new wxScrolledWindow(mParent, + wxID_ANY, + wxDefaultPosition, + wxDefaultSize, + wxVSCROLL | wxTAB_TRAVERSAL); + + // Try to give the window a sensible default/minimum size + scroller->SetMinSize(wxSize(wxMax(600, mParent->GetSize().GetWidth() * 2 / 3), + mParent->GetSize().GetHeight() / 2)); + scroller->SetScrollRate(0, 20); + mainSizer->Add(scroller, 1, wxEXPAND | wxALL, 5); + mParent->SetSizer(mainSizer); + mNames = new wxStaticText *[mAEffect->numParams]; mSliders = new wxSlider *[mAEffect->numParams]; mDisplays = new wxStaticText *[mAEffect->numParams]; mLabels = new wxStaticText *[mAEffect->numParams]; - wxSizer *paramSizer = new wxStaticBoxSizer(wxVERTICAL, mParent, _("Effect Settings")); + wxSizer *paramSizer = new wxStaticBoxSizer(wxVERTICAL, scroller, _("Effect Settings")); wxFlexGridSizer *gridSizer = new wxFlexGridSizer(4, 0, 0); gridSizer->AddGrowableCol(1); @@ -3532,10 +3492,10 @@ void VSTEffect::BuildPlain() // Add the duration control for generators if (GetType() == EffectTypeGenerate) { - wxControl *item = new wxStaticText(mParent, 0, _("Duration:")); + wxControl *item = new wxStaticText(scroller, 0, _("Duration:")); gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); mDuration = new NumericTextCtrl(NumericConverter::TIME, - mParent, + scroller, ID_DURATION, _("hh:mm:ss + milliseconds"), mHost->GetDuration(), @@ -3563,18 +3523,18 @@ void VSTEffect::BuildPlain() text += wxT(':'); } - mParent->GetTextExtent(text, &w, &h); + scroller->GetTextExtent(text, &w, &h); if (w > namew) { namew = w; } } - mParent->GetTextExtent(wxT("HHHHHHHH"), &w, &h); + scroller->GetTextExtent(wxT("HHHHHHHH"), &w, &h); for (int i = 0; i < mAEffect->numParams; i++) { - mNames[i] = new wxStaticText(mParent, + mNames[i] = new wxStaticText(scroller, wxID_ANY, wxEmptyString, wxDefaultPosition, @@ -3582,7 +3542,7 @@ void VSTEffect::BuildPlain() wxALIGN_RIGHT | wxST_NO_AUTORESIZE); gridSizer->Add(mNames[i], 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); - mSliders[i] = new wxSlider(mParent, + mSliders[i] = new wxSlider(scroller, ID_SLIDERS + i, 0, 0, @@ -3591,7 +3551,7 @@ void VSTEffect::BuildPlain() wxSize(200, -1)); gridSizer->Add(mSliders[i], 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 5); - mDisplays[i] = new wxStaticText(mParent, + mDisplays[i] = new wxStaticText(scroller, wxID_ANY, wxEmptyString, wxDefaultPosition, @@ -3599,7 +3559,7 @@ void VSTEffect::BuildPlain() wxALIGN_RIGHT | wxST_NO_AUTORESIZE); gridSizer->Add(mDisplays[i], 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); - mLabels[i] = new wxStaticText(mParent, + mLabels[i] = new wxStaticText(scroller, wxID_ANY, wxEmptyString, wxDefaultPosition, @@ -3609,7 +3569,7 @@ void VSTEffect::BuildPlain() } paramSizer->Add(gridSizer, 1, wxEXPAND | wxALL, 5); - mParent->SetSizer(paramSizer); + scroller->SetSizer(paramSizer); RefreshParameters(); @@ -3674,7 +3634,7 @@ void VSTEffect::OnSizeWindow(wxCommandEvent & evt) return; } - // This really needs some work. We should know anything about the parent... + // This really needs some work. We shouldn't know anything about the parent... mContainer->SetMinSize(evt.GetInt(), (int) evt.GetExtraLong()); mParent->SetMinSize(mContainer->GetMinSize()); mDialog->Layout(); diff --git a/src/effects/VST/VSTEffect.h b/src/effects/VST/VSTEffect.h index 86df7805c..48596ca11 100644 --- a/src/effects/VST/VSTEffect.h +++ b/src/effects/VST/VSTEffect.h @@ -61,12 +61,10 @@ WX_DEFINE_ARRAY_PTR(VSTEffect *, VSTEffectArray); DECLARE_LOCAL_EVENT_TYPE(EVT_SIZEWINDOW, -1); DECLARE_LOCAL_EVENT_TYPE(EVT_UPDATEDISPLAY, -1); -class VSTEffectEventHelper; - -class VSTEffect : public EffectClientInterface, +class VSTEffect : public wxEvtHandler, + public EffectClientInterface, public EffectUIClientInterface, public XMLTagHandler - { public: VSTEffect(const wxString & path, VSTEffect *master = NULL); @@ -105,12 +103,12 @@ class VSTEffect : public EffectClientInterface, virtual sampleCount GetTailSize(); virtual void SetSampleRate(sampleCount rate); - virtual sampleCount GetBlockSize(sampleCount maxBlockSize); + virtual sampleCount SetBlockSize(sampleCount maxBlockSize); virtual bool IsReady(); - virtual bool ProcessInitialize(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); virtual bool ProcessFinalize(); - virtual sampleCount ProcessBlock(float **inbuf, float **outbuf, sampleCount size); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); virtual bool RealtimeInitialize(); virtual bool RealtimeAddProcessor(int numChannels, float sampleRate); @@ -129,23 +127,23 @@ class VSTEffect : public EffectClientInterface, virtual bool GetAutomationParameters(EffectAutomationParameters & parms); virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + virtual bool LoadUserPreset(const wxString & name); + virtual bool SaveUserPreset(const wxString & name); + + virtual wxArrayString GetFactoryPresets(); + virtual bool LoadFactoryPreset(int id); + virtual bool LoadFactoryDefaults(); + // EffectUIClientInterface implementation - virtual void SetUIHost(EffectUIHostInterface *host); + virtual void SetHostUI(EffectUIHostInterface *host); virtual bool PopulateUI(wxWindow *parent); virtual bool IsGraphicalUI(); virtual bool ValidateUI(); virtual bool HideUI(); virtual bool CloseUI(); - virtual void LoadUserPreset(const wxString & name); - virtual void SaveUserPreset(const wxString & name); - - virtual wxArrayString GetFactoryPresets(); - virtual void LoadFactoryPreset(int id); - virtual void LoadFactoryDefaults(); - - virtual bool CanExport(); + virtual bool CanExportPresets(); virtual void ExportPresets(); virtual void ImportPresets(); @@ -171,8 +169,8 @@ private: wxArrayInt GetEffectIDs(); // Parameter loading and saving - void LoadParameters(const wxString & group); - void SaveParameters(const wxString & group); + bool LoadParameters(const wxString & group); + bool SaveParameters(const wxString & group); // Base64 encoding and decoding static wxString b64encode(const void *in, int len); @@ -195,15 +193,6 @@ private: void OnSave(wxCommandEvent & evt); void OnSettings(wxCommandEvent & evt); - - void OnApply(wxCommandEvent & evt); - void OnOk(wxCommandEvent & evt); - void OnCancel(wxCommandEvent & evt); - void OnPreview(wxCommandEvent & evt); - void OnDefaults(wxCommandEvent & evt); - void OnClose(wxCloseEvent & evt); - - void BuildPlain(); void BuildFancy(); wxSizer *BuildProgramBar(); @@ -309,7 +298,6 @@ private: wxDialog *mDialog; wxWindow *mParent; EffectUIHostInterface *mUIHost; - VSTEffectEventHelper *mEventHelper; wxSizerItem *mContainer; bool mGui; @@ -360,32 +348,9 @@ private: #endif - friend class VSTEffectEventHelper; - friend class VSTEffectsModule; -}; - -/////////////////////////////////////////////////////////////////////////////// -// -// VSTEffectEventHelper -// -/////////////////////////////////////////////////////////////////////////////// - -class VSTEffectEventHelper : public wxEvtHandler -{ -public: - VSTEffectEventHelper(VSTEffect *effect); - virtual ~VSTEffectEventHelper(); - - // VSTEffectEventHelper implementation - - void OnSlider(wxCommandEvent & evt); - void ControlSetFocus(wxFocusEvent & evt); - void OnSizeWindow(wxCommandEvent & evt); - -private: - VSTEffect *mEffect; - DECLARE_EVENT_TABLE(); + + friend class VSTEffectsModule; }; /////////////////////////////////////////////////////////////////////////////// diff --git a/src/effects/Wahwah.cpp b/src/effects/Wahwah.cpp index 512b3a546..19013255d 100644 --- a/src/effects/Wahwah.cpp +++ b/src/effects/Wahwah.cpp @@ -14,99 +14,105 @@ *******************************************************************//** \class EffectWahwah -\brief An EffectSimpleMono - -*//****************************************************************//** - -\class WahwahDialog -\brief Dialog for EffectWahwah +\brief An Effect *//*******************************************************************/ - - #include "../Audacity.h" -#include "Wahwah.h" -#include "../ShuttleGui.h" -#include "../WaveTrack.h" -#include "../FFT.h" - #include #include -#include -#include -#include -#include -#include -#include + +#include "../widgets/valnum.h" + +#include "Wahwah.h" + +enum +{ + ID_Freq = 10000, + ID_Phase, + ID_Depth, + ID_Res, + ID_FreqOfs +}; + +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( Freq, double, wxTRANSLATE("Freq"), 1.5, 0.1, 4.0, 10 ); +Param( Phase, double, wxTRANSLATE("Phase"), 0.0, 0.0, 359.0, 1 ); +Param( Depth, int, wxTRANSLATE("Depth"), 70, 0, 100, 1 ); // scaled to 0-1 before processing +Param( Res, double, wxTRANSLATE("Resonance"), 2.5, 0.1, 10.0, 10 ); +Param( FreqOfs, int, wxTRANSLATE("Offset"), 30, 0, 100, 1 ); // scaled to 0-1 before processing + +// How many samples are processed before recomputing the lfo value again +#define lfoskipsamples 30 // // EffectWahwah // -#define lfoskipsamples 30 +BEGIN_EVENT_TABLE(EffectWahwah, wxEvtHandler) + EVT_SLIDER(ID_Freq, EffectWahwah::OnFreqSlider) + EVT_SLIDER(ID_Phase, EffectWahwah::OnPhaseSlider) + EVT_SLIDER(ID_Depth, EffectWahwah::OnDepthSlider) + EVT_SLIDER(ID_Res, EffectWahwah::OnResonanceSlider) + EVT_SLIDER(ID_FreqOfs, EffectWahwah::OnFreqOffSlider) + EVT_TEXT(ID_Freq, EffectWahwah::OnFreqText) + EVT_TEXT(ID_Phase, EffectWahwah::OnPhaseText) + EVT_TEXT(ID_Depth, EffectWahwah::OnDepthText) + EVT_TEXT(ID_Res, EffectWahwah::OnResonanceText) + EVT_TEXT(ID_FreqOfs, EffectWahwah::OnFreqOffText) +END_EVENT_TABLE(); EffectWahwah::EffectWahwah() { - freq = float(1.5); - startphase = 0; - depth = (float)0.7; - freqofs = (float)0.3; - res = float(2.5); + mFreq = DEF_Freq; + mPhase = DEF_Phase; + mDepth = DEF_Depth; + mRes = DEF_Res; + mFreqOfs = DEF_FreqOfs; } -wxString EffectWahwah::GetEffectDescription() { - // Note: This is useful only after values have been set. - return wxString::Format(_("Applied effect: %s frequency = %.1f Hz, start phase = %.0f deg, depth = %.0f%%, resonance = %.1f, frequency offset = %.0f%%"), - this->GetEffectName().c_str(), - freq, - (startphase * 180 / M_PI), - (depth * 100), - res, - (freqofs * 100)); -} - -bool EffectWahwah::PromptUser() +EffectWahwah::~EffectWahwah() { - WahwahDialog dlog(this, mParent); - - dlog.freq = freq; - dlog.freqoff = freqofs * 100; - dlog.startphase = startphase * 180 / M_PI; - dlog.res = res; - dlog.depth = depth * 100; - - dlog.TransferDataToWindow(); - dlog.CentreOnParent(); - dlog.ShowModal(); - - if (dlog.GetReturnCode() == wxID_CANCEL) - return false; - - freq = dlog.freq; - freqofs = dlog.freqoff / 100; - startphase = dlog.startphase * M_PI / 180; - res = dlog.res; - depth = dlog.depth / 100; - - return true; } -bool EffectWahwah::TransferParameters( Shuttle & shuttle ) +// IdentInterface implementation + +wxString EffectWahwah::GetSymbol() { - shuttle.TransferFloat(wxT("Freq"),freq,1.5f); - shuttle.TransferFloat(wxT("Phase"),startphase,0.0f); - shuttle.TransferFloat(wxT("Depth"),depth,0.7f); - shuttle.TransferFloat(wxT("Resonance"),res,2.5f); - shuttle.TransferFloat(wxT("Offset"),freqofs,0.3f); - return true; + return WAHWAH_PLUGIN_SYMBOL; } -bool EffectWahwah::NewTrackSimpleMono() +wxString EffectWahwah::GetDescription() { - lfoskip = freq * 2 * M_PI / mCurRate; + return wxTRANSLATE("Rapid tone quality variations, like that guitar sound so popular in the 1970's"); +} + +// EffectIdentInterface implementation + +EffectType EffectWahwah::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation + +int EffectWahwah::GetAudioInCount() +{ + return 1; +} + +int EffectWahwah::GetAudioOutCount() +{ + return 1; +} + +bool EffectWahwah::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames chanMap) +{ + lfoskip = mFreq * 2 * M_PI / mSampleRate; skipcount = 0; xn1 = 0; xn2 = 0; @@ -119,29 +125,38 @@ bool EffectWahwah::NewTrackSimpleMono() a1 = 0; a2 = 0; - phase = startphase; - if (mCurChannel == Track::RightChannel) - phase += (float)M_PI; + depth = mDepth / 100.0; + freqofs = mFreqOfs / 100.0; + + phase = mPhase * M_PI / 180.0; + if (chanMap[0] == ChannelNameFrontRight) + { + phase += M_PI; + } return true; } -bool EffectWahwah::ProcessSimpleMono(float *buffer, sampleCount len) +sampleCount EffectWahwah::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) { - float frequency, omega, sn, cs, alpha; - float in, out; + float *ibuf = inBlock[0]; + float *obuf = outBlock[0]; + double frequency, omega, sn, cs, alpha; + double in, out; - for (int i = 0; i < len; i++) { - in = buffer[i]; + for (int i = 0; i < blockLen; i++) + { + in = (double) ibuf[i]; - if ((skipcount++) % lfoskipsamples == 0) { + if ((skipcount++) % lfoskipsamples == 0) + { frequency = (1 + cos(skipcount * lfoskip + phase)) / 2; frequency = frequency * depth * (1 - freqofs) + freqofs; frequency = exp((frequency - 1) * 6); omega = M_PI * frequency; sn = sin(omega); cs = cos(omega); - alpha = sn / (2 * res); + alpha = sn / (2 * mRes); b0 = (1 - cs) / 2; b1 = 1 - cs; b2 = (1 - cs) / 2; @@ -155,359 +170,221 @@ bool EffectWahwah::ProcessSimpleMono(float *buffer, sampleCount len) yn2 = yn1; yn1 = out; - buffer[i] = (float) out; + obuf[i] = (float) out; } + return blockLen; +} + +bool EffectWahwah::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_Freq, mFreq); + parms.Write(KEY_Phase, mPhase); + parms.Write(KEY_Depth, mDepth); + parms.Write(KEY_Res, mRes); + parms.Write(KEY_FreqOfs, mFreqOfs); + + return true; +} + +bool EffectWahwah::SetAutomationParameters(EffectAutomationParameters & parms) +{ + ReadAndVerifyDouble(Freq); + ReadAndVerifyDouble(Phase); + ReadAndVerifyInt(Depth); + ReadAndVerifyDouble(Res); + ReadAndVerifyInt(FreqOfs); + + mFreq = Freq; + mPhase = Phase; + mDepth = Depth; + mRes = Res; + mFreqOfs = FreqOfs; + return true; } -// WDR: class implementations +// Effect implementation -//---------------------------------------------------------------------------- -// WahwahDialog -//---------------------------------------------------------------------------- - -#define FREQ_MIN 1 -#define FREQ_MAX 40 -#define FREQOFF_MIN 0 -#define FREQOFF_MAX 100 -#define PHASE_MIN 0 -#define PHASE_MAX 359 -#define DEPTH_MIN 0 -#define DEPTH_MAX 100 -#define RES_MIN 0 -#define RES_MAX 100 - -// WDR: event table for WahwahDialog - -BEGIN_EVENT_TABLE(WahwahDialog, EffectDialog) - EVT_BUTTON(ID_EFFECT_PREVIEW, WahwahDialog::OnPreview) - EVT_TEXT(ID_FREQTEXT, WahwahDialog::OnFreqText) - EVT_TEXT(ID_FREQOFFTEXT, WahwahDialog::OnFreqOffText) - EVT_TEXT(ID_PHASETEXT, WahwahDialog::OnPhaseText) - EVT_TEXT(ID_DEPTHTEXT, WahwahDialog::OnDepthText) - EVT_TEXT(ID_RESONANCETEXT, WahwahDialog::OnResonanceText) - EVT_SLIDER(ID_FREQSLIDER, WahwahDialog::OnFreqSlider) - EVT_SLIDER(ID_FREQOFFSLIDER, WahwahDialog::OnFreqOffSlider) - EVT_SLIDER(ID_PHASESLIDER, WahwahDialog::OnPhaseSlider) - EVT_SLIDER(ID_DEPTHSLIDER, WahwahDialog::OnDepthSlider) - EVT_SLIDER(ID_RESONANCESLIDER, WahwahDialog::OnResonanceSlider) -END_EVENT_TABLE() - -WahwahDialog::WahwahDialog(EffectWahwah * effect, wxWindow * parent) -: EffectDialog(parent, _("Wahwah"), PROCESS_EFFECT), - mEffect(effect) +void EffectWahwah::PopulateOrExchange(ShuttleGui & S) { - Init(); -} - -void WahwahDialog::PopulateOrExchange(ShuttleGui & S) -{ - wxTextValidator vld(wxFILTER_NUMERIC); - S.SetBorder(5); S.AddSpace(0, 5); - S.StartMultiColumn(3, wxCENTER); + S.StartMultiColumn(3, wxEXPAND); { - wxSlider *s; - wxTextCtrl * tempTC; - tempTC = S.Id(ID_FREQTEXT).AddTextBox(_("LFO Frequency (Hz):"), wxT(""), 12); + S.SetStretchyCol(2); + + FloatingPointValidator vldfreq(1, &mFreq); + vldfreq.SetRange(MIN_Freq, MAX_Freq); + mFreqT = S.Id(ID_Freq).AddTextBox(_("LFO Frequency (Hz):"), wxT(""), 12); + mFreqT->SetValidator(vldfreq); + S.SetStyle(wxSL_HORIZONTAL); - tempTC->SetValidator(vld); - s = S.Id(ID_FREQSLIDER).AddSlider(wxT(""), 100, FREQ_MAX, FREQ_MIN); - s->SetName(_("LFO frequency in hertz")); + mFreqS = S.Id(ID_Freq).AddSlider(wxT(""), DEF_Freq * SCL_Freq, MAX_Freq * SCL_Freq, MIN_Freq * SCL_Freq); + mFreqS->SetName(_("LFO frequency in hertz")); #if defined(__WXGTK__) - s->SetMinSize(wxSize(100, -1)); + mFreqS->SetMinSize(wxSize(100, -1)); #endif - tempTC = S.Id(ID_PHASETEXT).AddTextBox(_("LFO Start Phase (deg.):"), wxT(""), 12); + FloatingPointValidator vldphase(1, &mPhase); + vldphase.SetRange(MIN_Phase, MAX_Phase); + mPhaseT = S.Id(ID_Phase).AddTextBox(_("LFO Start Phase (deg.):"), wxT(""), 12); + mPhaseT->SetValidator(vldphase); + S.SetStyle(wxSL_HORIZONTAL); - tempTC->SetValidator(vld); - s = S.Id(ID_PHASESLIDER).AddSlider(wxT(""), 0, PHASE_MAX, PHASE_MIN); - s->SetName(_("LFO start phase in degrees")); - s->SetLineSize(10); + mPhaseS = S.Id(ID_Phase).AddSlider(wxT(""), DEF_Phase * SCL_Phase, MAX_Phase * SCL_Phase, MIN_Phase * SCL_Phase); + mPhaseS->SetName(_("LFO start phase in degrees")); + mPhaseS->SetLineSize(10); #if defined(__WXGTK__) - s->SetMinSize(wxSize(100, -1)); + mPhaseS->SetMinSize(wxSize(100, -1)); #endif - tempTC = S.Id(ID_DEPTHTEXT).AddTextBox(_("Depth (%):"), wxT(""), 12); + IntegerValidator vlddepth(&mDepth); + vlddepth.SetRange(MIN_Depth, MAX_Depth); + mDepthT = S.Id(ID_Depth).AddTextBox(_("Depth (%):"), wxT(""), 12); + mDepthT->SetValidator(vlddepth); + S.SetStyle(wxSL_HORIZONTAL); - tempTC->SetValidator(vld); - s = S.Id(ID_DEPTHSLIDER).AddSlider(wxT(""), 0, DEPTH_MAX, DEPTH_MIN); - s->SetName(_("Depth in percent")); + mDepthS = S.Id(ID_Depth).AddSlider(wxT(""), DEF_Depth * SCL_Depth, MAX_Depth * SCL_Depth, MIN_Depth * SCL_Depth); + mDepthS->SetName(_("Depth in percent")); #if defined(__WXGTK__) - s->SetMinSize(wxSize(100, -1)); + mDepthS->SetMinSize(wxSize(100, -1)); #endif - tempTC = S.Id(ID_RESONANCETEXT).AddTextBox(_("Resonance:"), wxT(""), 12); + FloatingPointValidator vldres(1, &mRes); + vldres.SetRange(MIN_Res, MAX_Res); + mResT = S.Id(ID_Res).AddTextBox(_("Resonance:"), wxT(""), 12); + mResT->SetValidator(vldres); + S.SetStyle(wxSL_HORIZONTAL); - tempTC->SetValidator(vld); - s = S.Id(ID_RESONANCESLIDER).AddSlider(wxT(""), 0, RES_MAX, RES_MIN); - s->SetName(_("Resonance")); + mResS = S.Id(ID_Res).AddSlider(wxT(""), DEF_Res * SCL_Res, MAX_Res * SCL_Res, MIN_Res * SCL_Res); + mResS->SetName(_("Resonance")); #if defined(__WXGTK__) - s->SetMinSize(wxSize(100, -1)); + mResS->SetMinSize(wxSize(100, -1)); #endif - tempTC = S.Id(ID_FREQOFFTEXT).AddTextBox(_("Wah Frequency Offset (%):"), wxT(""), 12); + IntegerValidator vldfreqoffset(&mFreqOfs); + vldfreqoffset.SetRange(MIN_FreqOfs, MAX_FreqOfs); + mFreqOfsT = S.Id(ID_FreqOfs).AddTextBox(_("Wah Frequency Offset (%):"), wxT(""), 12); + mFreqOfsT->SetValidator(vldfreqoffset); + S.SetStyle(wxSL_HORIZONTAL); - tempTC->SetValidator(vld); - s =S.Id(ID_FREQOFFSLIDER).AddSlider(wxT(""), 0, FREQOFF_MAX, FREQOFF_MIN); - s->SetName(_("Wah frequency offset in percent")); + mFreqOfsS = S.Id(ID_FreqOfs).AddSlider(wxT(""), DEF_FreqOfs * SCL_FreqOfs, MAX_FreqOfs * SCL_FreqOfs, MIN_FreqOfs * SCL_FreqOfs); + mFreqOfsT->SetName(_("Wah frequency offset in percent")); #if defined(__WXGTK__) - s->SetMinSize(wxSize(100, -1)); + mFreqOfsT->SetMinSize(wxSize(100, -1)); #endif } S.EndMultiColumn(); } -bool WahwahDialog::TransferDataToWindow() +bool EffectWahwah::TransferDataToWindow() { - wxSlider *slider; - - slider = GetFreqSlider(); - if (slider) - slider->SetValue((int)(freq * 10)); - - slider = GetFreqOffSlider(); - if (slider) - slider->SetValue((int)freqoff); - - slider = GetDepthSlider(); - if (slider) - slider->SetValue((int)depth); - - slider = GetPhaseSlider(); - if (slider) - slider->SetValue((int)startphase); - - slider = GetResonanceSlider(); - if (slider) - slider->SetValue((int)(res * 10)); - - wxTextCtrl *text = GetFreqText(); - if (text) { - wxString str; - str.Printf(wxT("%.1f"), freq); - text->SetValue(str); + if (!mUIParent->TransferDataToWindow()) + { + return false; } - text = GetFreqOffText(); - if (text) { - wxString str; - str.Printf(wxT("%d"), (int) freqoff); - text->SetValue(str); - } - - text = GetPhaseText(); - if (text) { - wxString str; - str.Printf(wxT("%d"), (int) startphase); - text->SetValue(str); - } - - text = GetDepthText(); - if (text) { - wxString str; - str.Printf(wxT("%d"), (int) depth); - text->SetValue(str); - } - - text = GetResonanceText(); - if (text) { - wxString str; - str.Printf(wxT("%.1f"), res); - text->SetValue(str); - } - - return TRUE; + return true; } -bool WahwahDialog::TransferDataFromWindow() +bool EffectWahwah::TransferDataFromWindow() { - wxTextCtrl *c; - long x; - - c = GetFreqText(); - if (c) { - double d; - c->GetValue().ToDouble(&d); - freq = TrapDouble(d * 10, FREQ_MIN, FREQ_MAX) / 10; + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; } - c = GetFreqOffText(); - if (c) { - double d; - c->GetValue().ToDouble(&d); - freqoff = TrapDouble(d, FREQOFF_MIN, FREQOFF_MAX); + return true; +} + +// EffectWahwah implementation + +void EffectWahwah::OnFreqSlider(wxCommandEvent & evt) +{ + mFreq = (double) evt.GetInt() / SCL_Freq; + mFreqT->GetValidator()->TransferToWindow(); + EnableApply(mUIParent->Validate()); +} + +void EffectWahwah::OnPhaseSlider(wxCommandEvent & evt) +{ + int val = ((evt.GetInt() + 5) / 10) * 10; // round to nearest multiple of 10 + val = val > MAX_Phase * SCL_Phase ? MAX_Phase * SCL_Phase : val; + mPhaseS->SetValue(val); + mPhase = (double) val / SCL_Phase; + mPhaseT->GetValidator()->TransferToWindow(); + EnableApply(mUIParent->Validate()); +} + +void EffectWahwah::OnDepthSlider(wxCommandEvent & evt) +{ + mDepth = evt.GetInt() / SCL_Depth; + mDepthT->GetValidator()->TransferToWindow(); + EnableApply(mUIParent->Validate()); +} + +void EffectWahwah::OnResonanceSlider(wxCommandEvent & evt) +{ + mRes = (double) evt.GetInt() / SCL_Res; + mResT->GetValidator()->TransferToWindow(); + EnableApply(mUIParent->Validate()); +} + +void EffectWahwah::OnFreqOffSlider(wxCommandEvent & evt) +{ + mFreqOfs = evt.GetInt() / SCL_FreqOfs; + mFreqOfsT->GetValidator()->TransferToWindow(); + EnableApply(mUIParent->Validate()); +} + +void EffectWahwah::OnFreqText(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; } - c = GetPhaseText(); - if (c) { - c->GetValue().ToLong(&x); - startphase = TrapLong(x, PHASE_MIN, PHASE_MAX); + mFreqS->SetValue((int) (mFreq * SCL_Freq)); +} + +void EffectWahwah::OnPhaseText(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; } - c = GetDepthText(); - if (c) { - c->GetValue().ToLong(&x); - depth = TrapLong(x, DEPTH_MIN, DEPTH_MAX); + mPhaseS->SetValue((int) (mPhase * SCL_Phase)); +} + +void EffectWahwah::OnDepthText(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; } - c = GetResonanceText(); - if (c) { - double d; - c->GetValue().ToDouble(&d); - res = TrapDouble(d * 10, RES_MIN, RES_MAX) / 10; + mDepthS->SetValue((int) (mDepth * SCL_Depth)); +} + +void EffectWahwah::OnResonanceText(wxCommandEvent & WXUNUSED(evt)) +{ + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; } - return TRUE; + mResS->SetValue((int) (mRes * SCL_Res)); } -// WDR: handler implementations for WahwahDialog - -void WahwahDialog::OnResonanceSlider(wxCommandEvent & WXUNUSED(event)) +void EffectWahwah::OnFreqOffText(wxCommandEvent & WXUNUSED(evt)) { - wxString str; - long res = GetResonanceSlider()->GetValue(); - str.Printf(wxT("%.1f"), res / 10.0); - GetResonanceText()->SetValue(str); -} - -void WahwahDialog::OnDepthSlider(wxCommandEvent & WXUNUSED(event)) -{ - wxString str; - long depth = GetDepthSlider()->GetValue(); - str.Printf(wxT("%ld"), depth); - GetDepthText()->SetValue(str); -} - -void WahwahDialog::OnPhaseSlider(wxCommandEvent & WXUNUSED(event)) -{ - wxString str; - long phase = GetPhaseSlider()->GetValue(); - phase = ((phase + 5) / 10) * 10; // round to nearest multiple of 10 - str.Printf(wxT("%ld"), phase); - GetPhaseText()->SetValue(str); -} - -void WahwahDialog::OnFreqSlider(wxCommandEvent & WXUNUSED(event)) -{ - wxString str; - long freql = GetFreqSlider()->GetValue(); - str.Printf(wxT("%.1f"), freql / 10.0); - GetFreqText()->SetValue(str); -} - -void WahwahDialog::OnFreqOffSlider(wxCommandEvent & WXUNUSED(event)) -{ - wxString str; - long freqoff = GetFreqOffSlider()->GetValue(); - str.Printf(wxT("%d"), (int) freqoff); - GetFreqOffText()->SetValue(str); -} - -void WahwahDialog::OnResonanceText(wxCommandEvent & WXUNUSED(event)) -{ - wxTextCtrl *c = GetResonanceText(); - if (c) { - double resd; - c->GetValue().ToDouble(&resd); - - res = resd; - res = TrapDouble(resd * 10, RES_MIN, RES_MAX) / 10.0; - - wxSlider *slider = GetResonanceSlider(); - if (slider) - slider->SetValue((int)floor(res * 10 + .5)); + if (!EnableApply(mUIParent->TransferDataFromWindow())) + { + return; } + + mFreqOfsS->SetValue((int) (mFreqOfs * SCL_FreqOfs)); } - -void WahwahDialog::OnDepthText(wxCommandEvent & WXUNUSED(event)) -{ - wxTextCtrl *c = GetDepthText(); - if (c) { - long depth; - - c->GetValue().ToLong(&depth); - depth = TrapLong(depth, DEPTH_MIN, DEPTH_MAX); - - wxSlider *slider = GetDepthSlider(); - if (slider) - slider->SetValue(depth); - } -} - -void WahwahDialog::OnPhaseText(wxCommandEvent & WXUNUSED(event)) -{ - wxTextCtrl *c = GetPhaseText(); - if (c) { - long phase; - - c->GetValue().ToLong(&phase); - phase = TrapLong(phase, PHASE_MIN, PHASE_MAX); - - wxSlider *slider = GetPhaseSlider(); - if (slider) - slider->SetValue(phase); - } -} - -void WahwahDialog::OnFreqText(wxCommandEvent & WXUNUSED(event)) -{ - wxTextCtrl *c = GetFreqText(); - if (c) { - long freql; - double freqd; - c->GetValue().ToDouble(&freqd); - - freq = freqd; - freql = TrapLong(((long)floor(freq * 10 + .5)), FREQ_MIN, FREQ_MAX); - - wxSlider *slider = GetFreqSlider(); - if (slider) - slider->SetValue(freql); - } -} - -void WahwahDialog::OnFreqOffText(wxCommandEvent & WXUNUSED(event)) -{ - wxTextCtrl *c = GetFreqOffText(); - if (c) { - double freqoff; - c->GetValue().ToDouble(&freqoff); - freqoff = TrapDouble(freqoff, FREQOFF_MIN, FREQOFF_MAX); - - wxSlider *slider = GetFreqOffSlider(); - if (slider) - slider->SetValue((int)freqoff); - } -} - -void WahwahDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) -{ - TransferDataFromWindow(); - - // Save & restore parameters around Preview, because we didn't do OK. - float old_freq = mEffect->freq; - float old_freqofs = mEffect->freqofs; - float old_startphase = mEffect->startphase; - float old_res = mEffect->res; - float old_depth = mEffect->depth; - - mEffect->freq = freq; - mEffect->freqofs = freqoff / 100; - mEffect->startphase = startphase * M_PI / 180; - mEffect->res = res; - mEffect->depth = depth / 100; - - mEffect->Preview(); - - mEffect->freq = old_freq; - mEffect->freqofs = old_freqofs; - mEffect->startphase = old_startphase; - mEffect->res = old_res; - mEffect->depth = old_depth; -} - - diff --git a/src/effects/Wahwah.h b/src/effects/Wahwah.h index bf27778ba..8c15a615f 100644 --- a/src/effects/Wahwah.h +++ b/src/effects/Wahwah.h @@ -16,168 +16,101 @@ #ifndef __AUDACITY_EFFECT_WAHWAH__ #define __AUDACITY_EFFECT_WAHWAH__ -// For compilers that support precompilation, includes "wx/wx.h". -#include - -#ifndef WX_PRECOMP -#include -#endif - -#include +#include #include +#include +#include -class wxString; -class wxSizer; -class wxTextCtrl; +#include "../ShuttleGui.h" -#include "SimpleMono.h" +#include "Effect.h" -class EffectWahwah:public EffectSimpleMono { +#define WAHWAH_PLUGIN_SYMBOL wxTRANSLATE("Wahwah") - public: +class EffectWahwah : public Effect +{ +public: EffectWahwah(); + virtual ~EffectWahwah(); - virtual wxString GetEffectName() { - return wxString(wxTRANSLATE("Wahwah...")); - } + // IdentInterface implementation - virtual std::set GetEffectCategories() { - std::set result; - result.insert(wxT("http://lv2plug.in/ns/lv2core#ModulatorPlugin")); - return result; - } + virtual wxString GetSymbol(); + virtual wxString GetDescription(); - virtual wxString GetEffectIdentifier() { - return wxString(wxT("Wahwah")); - } + // EffectIdentInterface implementation - virtual wxString GetEffectAction() { - return wxString(_("Applying Wahwah")); - } + virtual EffectType GetType(); - // Useful only after PromptUser values have been set. - virtual wxString GetEffectDescription(); + // EffectClientInterface implementation - virtual bool PromptUser(); - virtual bool TransferParameters( Shuttle & shuttle ); + virtual int GetAudioInCount(); + virtual int GetAudioOutCount(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); - protected: - virtual bool NewTrackSimpleMono(); + // Effect implementation - virtual bool ProcessSimpleMono(float *buffer, sampleCount len); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); - float phase; - float lfoskip; +private: + // EffectWahwah implementation + + void OnFreqSlider(wxCommandEvent & evt); + void OnPhaseSlider(wxCommandEvent & evt); + void OnDepthSlider(wxCommandEvent & evt); + void OnResonanceSlider(wxCommandEvent & evt); + void OnFreqOffSlider(wxCommandEvent & evt); + + void OnFreqText(wxCommandEvent & evt); + void OnPhaseText(wxCommandEvent & evt); + void OnDepthText(wxCommandEvent & evt); + void OnResonanceText(wxCommandEvent & evt); + void OnFreqOffText(wxCommandEvent & evt); + +private: + double depth; + double freqofs; + double phase; + double lfoskip; unsigned long skipcount; - float xn1, xn2, yn1, yn2; - float b0, b1, b2, a0, a1, a2; + double xn1, xn2, yn1, yn2; + double b0, b1, b2, a0, a1, a2; /* Parameters: - freq - LFO frequency - startphase - LFO startphase in RADIANS - usefull for stereo WahWah - depth - Wah depth - freqofs - Wah frequency offset - res - Resonance + mFreq - LFO frequency + mPhase - LFO startphase in RADIANS - useful for stereo WahWah + mDepth - Wah depth + mRes - Resonance + mFreqOfs - Wah frequency offset !!!!!!!!!!!!! IMPORTANT!!!!!!!!! : - depth and freqofs should be from 0(min) to 1(max) ! - res should be greater than 0 ! */ + mDepth and mFreqOfs should be from 0(min) to 1(max) ! + mRes should be greater than 0 ! */ - private: - float freq, startphase; - float depth, freqofs, res; + double mFreq; + double mPhase; + int mDepth; + double mRes; + int mFreqOfs; -friend class WahwahDialog; -}; + wxTextCtrl *mFreqT; + wxTextCtrl *mPhaseT; + wxTextCtrl *mDepthT; + wxTextCtrl *mResT; + wxTextCtrl *mFreqOfsT; -// Declare window functions + wxSlider *mFreqS; + wxSlider *mPhaseS; + wxSlider *mDepthS; + wxSlider *mResS; + wxSlider *mFreqOfsS; -#define ID_FREQTEXT 10003 -#define ID_FREQSLIDER 10004 -#define ID_PHASETEXT 10005 -#define ID_PHASESLIDER 10006 -#define ID_DEPTHTEXT 10007 -#define ID_DEPTHSLIDER 10008 -#define ID_RESONANCETEXT 10009 -#define ID_RESONANCESLIDER 10010 -#define ID_FREQOFFTEXT 10011 -#define ID_FREQOFFSLIDER 10012 - -// WDR: class declarations - -//---------------------------------------------------------------------------- -// WahwahDialog -//---------------------------------------------------------------------------- - -class WahwahDialog:public EffectDialog { - public: - // constructors and destructors - WahwahDialog(EffectWahwah * effect, wxWindow * parent); - - void PopulateOrExchange(ShuttleGui & S); - bool TransferDataToWindow(); - bool TransferDataFromWindow(); - - // WDR: method declarations for WahwahDialog - wxSlider *GetResonanceSlider() { - return (wxSlider *) FindWindow(ID_RESONANCESLIDER); - } wxSlider *GetDepthSlider() { - return (wxSlider *) FindWindow(ID_DEPTHSLIDER); - } - wxSlider *GetPhaseSlider() { - return (wxSlider *) FindWindow(ID_PHASESLIDER); - } - wxSlider *GetFreqSlider() { - return (wxSlider *) FindWindow(ID_FREQSLIDER); - } - wxSlider *GetFreqOffSlider() { - return (wxSlider *) FindWindow(ID_FREQOFFSLIDER); - } - wxTextCtrl *GetResonanceText() { - return (wxTextCtrl *) FindWindow(ID_RESONANCETEXT); - } - wxTextCtrl *GetDepthText() { - return (wxTextCtrl *) FindWindow(ID_DEPTHTEXT); - } - wxTextCtrl *GetPhaseText() { - return (wxTextCtrl *) FindWindow(ID_PHASETEXT); - } - wxTextCtrl *GetFreqText() { - return (wxTextCtrl *) FindWindow(ID_FREQTEXT); - } - wxTextCtrl *GetFreqOffText() { - return (wxTextCtrl *) FindWindow(ID_FREQOFFTEXT); - } - - private: - // WDR: member variable declarations for WahwahDialog - - private: - // WDR: handler declarations for WahwahDialog - void OnResonanceSlider(wxCommandEvent & event); - void OnDepthSlider(wxCommandEvent & event); - void OnPhaseSlider(wxCommandEvent & event); - void OnFreqSlider(wxCommandEvent & event); - void OnFreqOffSlider(wxCommandEvent & event); - void OnResonanceText(wxCommandEvent & event); - void OnDepthText(wxCommandEvent & event); - void OnPhaseText(wxCommandEvent & event); - void OnFreqText(wxCommandEvent & event); - void OnFreqOffText(wxCommandEvent & event); - void OnPreview(wxCommandEvent &event); - - private: - EffectWahwah * mEffect; - - public: - float freq; - float freqoff; - float startphase; - float res; - float depth; - - private: - DECLARE_EVENT_TABLE() + DECLARE_EVENT_TABLE(); }; #endif diff --git a/src/effects/audiounits/AudioUnitEffect.cpp b/src/effects/audiounits/AudioUnitEffect.cpp index 3526d2226..29e5f00d3 100644 --- a/src/effects/audiounits/AudioUnitEffect.cpp +++ b/src/effects/audiounits/AudioUnitEffect.cpp @@ -160,7 +160,7 @@ bool AudioUnitEffectsModule::RegisterPlugin(PluginManagerInterface & pm, const w return false; } - pm.RegisterEffectPlugin(this, &effect); + pm.RegisterPlugin(this, &effect); return true; } @@ -720,63 +720,6 @@ void AudioUnitEffectImportDialog::OnOk(wxCommandEvent & WXUNUSED(evt)) EndModal(wxID_OK); } -/////////////////////////////////////////////////////////////////////////////// -// -// AudioUnitEffectEventHelper -// -/////////////////////////////////////////////////////////////////////////////// - -class AudioUnitEffectEventHelper : public wxEvtHandler -{ -public: - AudioUnitEffectEventHelper(AudioUnitEffect *effect); - virtual ~AudioUnitEffectEventHelper(); - - // AudioUnitEffectEventHelper implementatino - - void OnSize(wxSizeEvent & evt); - -private: - AudioUnitEffect *mEffect; - - DECLARE_EVENT_TABLE(); -}; - -/////////////////////////////////////////////////////////////////////////////// -// -// AudioUnitEffectEventHelper -// -/////////////////////////////////////////////////////////////////////////////// - -BEGIN_EVENT_TABLE(AudioUnitEffectEventHelper, wxEvtHandler) - EVT_SIZE(AudioUnitEffectEventHelper::OnSize) -END_EVENT_TABLE() - -AudioUnitEffectEventHelper::AudioUnitEffectEventHelper(AudioUnitEffect *effect) -{ - mEffect = effect; -} - -AudioUnitEffectEventHelper::~AudioUnitEffectEventHelper() -{ -} - -// ============================================================================ -// AudioUnitEffectEventHelper implementation -// ============================================================================ - -void AudioUnitEffectEventHelper::OnSize(wxSizeEvent & evt) -{ - HIViewRef viewRef = mEffect->mAUView; - wxWindow *parent = mEffect->mParent; - - // The parent panel has been resized, so make the AU the - // same size. - HIRect rect; - HIViewGetFrame((HIViewRef) parent->GetHandle(), &rect); - HIViewSetFrame(viewRef, &rect); -} - /////////////////////////////////////////////////////////////////////////////// // // AudioUnitEffect @@ -910,6 +853,10 @@ OSStatus AudioUnitEffect::WindowEventHandler(EventRef eventRef) return result; } +BEGIN_EVENT_TABLE(AudioUnitEffect, wxEvtHandler) + EVT_SIZE(AudioUnitEffect::OnSize) +END_EVENT_TABLE() + AudioUnitEffect::AudioUnitEffect(const wxString & path, const wxString & name, Component component, @@ -937,7 +884,6 @@ AudioUnitEffect::AudioUnitEffect(const wxString & path, mAUTrackingHandlerRef = NULL; mTrackingHandlerUPP = NULL; - mEventHelper = NULL; mEventListenerRef = NULL; } @@ -1263,7 +1209,7 @@ void AudioUnitEffect::SetSampleRate(sampleCount rate) mSampleRate = rate; } -sampleCount AudioUnitEffect::GetBlockSize(sampleCount maxBlockSize) +sampleCount AudioUnitEffect::SetBlockSize(sampleCount maxBlockSize) { return mBlockSize; } @@ -1310,7 +1256,7 @@ bool AudioUnitEffect::IsReady() return mReady; } -bool AudioUnitEffect::ProcessInitialize() +bool AudioUnitEffect::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) { ComponentResult auResult; @@ -1389,20 +1335,20 @@ bool AudioUnitEffect::ProcessFinalize() return true; } -sampleCount AudioUnitEffect::ProcessBlock(float **inbuf, float **outbuf, sampleCount size) +sampleCount AudioUnitEffect::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) { for (int i = 0; i < mAudioIns; i++) { mInputList->mBuffers[i].mNumberChannels = 1; - mInputList->mBuffers[i].mData = inbuf[i]; - mInputList->mBuffers[i].mDataByteSize = sizeof(float) * size; + mInputList->mBuffers[i].mData = inBlock[i]; + mInputList->mBuffers[i].mDataByteSize = sizeof(float) * blockLen; } for (int i = 0; i < mAudioOuts; i++) { mOutputList->mBuffers[i].mNumberChannels = 1; - mOutputList->mBuffers[i].mData = outbuf[i]; - mOutputList->mBuffers[i].mDataByteSize = sizeof(float) * size; + mOutputList->mBuffers[i].mData = outBlock[i]; + mOutputList->mBuffers[i].mDataByteSize = sizeof(float) * blockLen; } AudioUnitRenderActionFlags flags = 0; @@ -1412,7 +1358,7 @@ sampleCount AudioUnitEffect::ProcessBlock(float **inbuf, float **outbuf, sampleC &flags, &mTimeStamp, 0, - size, + blockLen, mOutputList); if (auResult != 0) { @@ -1420,9 +1366,9 @@ sampleCount AudioUnitEffect::ProcessBlock(float **inbuf, float **outbuf, sampleC return 0; } - mTimeStamp.mSampleTime += size; + mTimeStamp.mSampleTime += blockLen; - return size; + return blockLen; } bool AudioUnitEffect::RealtimeInitialize() @@ -1441,7 +1387,7 @@ bool AudioUnitEffect::RealtimeInitialize() mMasterOut[i] = new float[mBlockSize]; } - return ProcessInitialize(); + return ProcessInitialize(0); } bool AudioUnitEffect::RealtimeAddProcessor(int numChannels, float sampleRate) @@ -1453,7 +1399,7 @@ bool AudioUnitEffect::RealtimeAddProcessor(int numChannels, float sampleRate) return false; } - slave->GetBlockSize(mBlockSize); + slave->SetBlockSize(mBlockSize); slave->SetChannelCount(numChannels); slave->SetSampleRate(sampleRate); @@ -1465,7 +1411,7 @@ bool AudioUnitEffect::RealtimeAddProcessor(int numChannels, float sampleRate) mSlaves.Add(slave); - return slave->ProcessInitialize(); + return slave->ProcessInitialize(0); } bool AudioUnitEffect::RealtimeFinalize() @@ -1749,11 +1695,100 @@ bool AudioUnitEffect::SetAutomationParameters(EffectAutomationParameters & parms return true; } +bool AudioUnitEffect::LoadUserPreset(const wxString & name) +{ + return LoadParameters(name); +} + +bool AudioUnitEffect::SaveUserPreset(const wxString & name) +{ + return SaveParameters(name); +} + +bool AudioUnitEffect::LoadFactoryPreset(int id) +{ + OSStatus result; + + // Retrieve the list of factory presets + CFArrayRef array; + UInt32 dataSize = sizeof(CFArrayRef); + result = AudioUnitGetProperty(mUnit, + kAudioUnitProperty_FactoryPresets, + kAudioUnitScope_Global, + 0, + &array, + &dataSize); + if (result != noErr) + { + return false; + } + + if (id < 0 || id >= CFArrayGetCount(array)) + { + return false; + } + + AUPreset *preset = (AUPreset *) CFArrayGetValueAtIndex(array, id); + + result = AudioUnitSetProperty(mUnit, + kAudioUnitProperty_PresentPreset, + kAudioUnitScope_Global, + 0, + preset, + sizeof(AUPreset)); + if (result == noErr) + { + AudioUnitParameter aup; + aup.mAudioUnit = mUnit; + aup.mParameterID = kAUParameterListener_AnyParameter; + aup.mScope = kAudioUnitScope_Global; + aup.mElement = 0; + AUParameterListenerNotify(NULL, NULL, &aup); + } + + CFRelease(array); + + return result == noErr; +} + +bool AudioUnitEffect::LoadFactoryDefaults() +{ + return LoadParameters(mHost->GetFactoryDefaultsGroup()); +} + +wxArrayString AudioUnitEffect::GetFactoryPresets() +{ + OSStatus result; + wxArrayString presets; + + // Retrieve the list of factory presets + CFArrayRef array; + UInt32 dataSize = sizeof(CFArrayRef); + result = AudioUnitGetProperty(mUnit, + kAudioUnitProperty_FactoryPresets, + kAudioUnitScope_Global, + 0, + &array, + &dataSize); + if (result == noErr) + { + for (CFIndex i = 0, cnt = CFArrayGetCount(array); i < cnt; i++) + { + AUPreset *preset = (AUPreset *) CFArrayGetValueAtIndex(array, i); + wxMacCFStringHolder holder(preset->presetName, false); + presets.Add(holder.AsString()); + } + CFRelease(array); + } + + return presets; +} + // ============================================================================ // EffectUIClientInterface Implementation // ============================================================================ -void AudioUnitEffect::SetUIHost(EffectUIHostInterface *host) +void AudioUnitEffect::SetHostUI(EffectUIHostInterface *host) { mUIHost = host; @@ -1873,8 +1908,7 @@ bool AudioUnitEffect::PopulateUI(wxWindow *parent) HIViewSetFrame(auView, &rect); } - mEventHelper = new AudioUnitEffectEventHelper(this); - mParent->PushEventHandler(mEventHelper); + mParent->PushEventHandler(this); mEventRef = GetControlEventTarget(auView); @@ -1964,11 +1998,7 @@ bool AudioUnitEffect::CloseUI() { RemoveHandler(); - if (mEventHelper) - { - mParent->RemoveEventHandler(mEventHelper); - delete mEventHelper; - } + mParent->RemoveEventHandler(this); if (mCarbonView) { @@ -1988,92 +2018,7 @@ bool AudioUnitEffect::CloseUI() return true; } -void AudioUnitEffect::LoadUserPreset(const wxString & name) -{ - LoadParameters(name); -} - -void AudioUnitEffect::SaveUserPreset(const wxString & name) -{ - SaveParameters(name); -} - -void AudioUnitEffect::LoadFactoryPreset(int id) -{ - OSStatus result; - - // Retrieve the list of factory presets - CFArrayRef array; - UInt32 dataSize = sizeof(CFArrayRef); - result = AudioUnitGetProperty(mUnit, - kAudioUnitProperty_FactoryPresets, - kAudioUnitScope_Global, - 0, - &array, - &dataSize); - if (result == noErr) - { - if (id < CFArrayGetCount(array)) - { - AUPreset *preset = (AUPreset *) CFArrayGetValueAtIndex(array, id); - - result = AudioUnitSetProperty(mUnit, - kAudioUnitProperty_PresentPreset, - kAudioUnitScope_Global, - 0, - preset, - sizeof(AUPreset)); - if (result == noErr) - { - AudioUnitParameter aup; - aup.mAudioUnit = mUnit; - aup.mParameterID = kAUParameterListener_AnyParameter; - aup.mScope = kAudioUnitScope_Global; - aup.mElement = 0; - AUParameterListenerNotify(NULL, NULL, &aup); - } - } - - CFRelease(array); - } - - return; -} - -void AudioUnitEffect::LoadFactoryDefaults() -{ - LoadParameters(mHost->GetFactoryDefaultsGroup()); -} - -wxArrayString AudioUnitEffect::GetFactoryPresets() -{ - OSStatus result; - wxArrayString presets; - - // Retrieve the list of factory presets - CFArrayRef array; - UInt32 dataSize = sizeof(CFArrayRef); - result = AudioUnitGetProperty(mUnit, - kAudioUnitProperty_FactoryPresets, - kAudioUnitScope_Global, - 0, - &array, - &dataSize); - if (result == noErr) - { - for (CFIndex i = 0, cnt = CFArrayGetCount(array); i < cnt; i++) - { - AUPreset *preset = (AUPreset *) CFArrayGetValueAtIndex(array, i); - wxMacCFStringHolder holder(preset->presetName, false); - presets.Add(holder.AsString()); - } - CFRelease(array); - } - - return presets; -} - -bool AudioUnitEffect::CanExport() +bool AudioUnitEffect::CanExportPresets() { return true; } @@ -2110,6 +2055,15 @@ void AudioUnitEffect::ShowOptions() // AudioUnitEffect Implementation // ============================================================================ +void AudioUnitEffect::OnSize(wxSizeEvent & evt) +{ + // The parent panel has been resized, so make the AU the + // same size. + HIRect rect; + HIViewGetFrame((HIViewRef) mParent->GetHandle(), &rect); + HIViewSetFrame(mAUView, &rect); +} + void AudioUnitEffect::RemoveHandler() { if (mAUTrackingHandlerRef) @@ -2161,7 +2115,7 @@ void AudioUnitEffect::RemoveHandler() } } -void AudioUnitEffect::LoadParameters(const wxString & group) +bool AudioUnitEffect::LoadParameters(const wxString & group) { OSStatus result; UInt32 dataSize; @@ -2170,7 +2124,7 @@ void AudioUnitEffect::LoadParameters(const wxString & group) wxString value; if (!mHost->GetPrivateConfig(group, wxT("Value"), value, wxEmptyString)) { - return; + return false; } wxStringTokenizer tokens(value, wxT(',')); @@ -2182,14 +2136,14 @@ void AudioUnitEffect::LoadParameters(const wxString & group) &isWritable); if (result != noErr) { - return; + return false; } UInt32 cnt = dataSize / sizeof(AudioUnitParameterID); if (cnt != tokens.CountTokens()) { - return; + return false; } AudioUnitParameterID *array = new AudioUnitParameterID[cnt]; @@ -2203,7 +2157,7 @@ void AudioUnitEffect::LoadParameters(const wxString & group) if (result != noErr) { delete [] array; - return; + return false; } for (int i = 0; i < cnt; i++) @@ -2221,7 +2175,7 @@ void AudioUnitEffect::LoadParameters(const wxString & group) if (result != noErr) { delete [] array; - return; + return false; } } @@ -2233,9 +2187,11 @@ void AudioUnitEffect::LoadParameters(const wxString & group) AUParameterListenerNotify(NULL, NULL, &aup); delete [] array; + + return true; } -void AudioUnitEffect::SaveParameters(const wxString & group) +bool AudioUnitEffect::SaveParameters(const wxString & group) { OSStatus result; UInt32 dataSize; @@ -2250,7 +2206,7 @@ void AudioUnitEffect::SaveParameters(const wxString & group) &isWritable); if (result != noErr) { - return; + return false; } UInt32 cnt = dataSize / sizeof(AudioUnitParameterID); @@ -2265,7 +2221,7 @@ void AudioUnitEffect::SaveParameters(const wxString & group) if (result != noErr) { delete [] array; - return; + return false; } for (int i = 0; i < cnt; i++) @@ -2279,7 +2235,7 @@ void AudioUnitEffect::SaveParameters(const wxString & group) if (result != noErr) { delete [] array; - return; + return false; } parms += wxString::Format(wxT(",%f"), value); @@ -2288,6 +2244,8 @@ void AudioUnitEffect::SaveParameters(const wxString & group) mHost->SetPrivateConfig(group, wxT("Value"), parms.Mid(1)); delete [] array; + + return true; } bool AudioUnitEffect::SetRateAndChannels() diff --git a/src/effects/audiounits/AudioUnitEffect.h b/src/effects/audiounits/AudioUnitEffect.h index aa134922e..0b70bda93 100644 --- a/src/effects/audiounits/AudioUnitEffect.h +++ b/src/effects/audiounits/AudioUnitEffect.h @@ -32,11 +32,11 @@ class AudioUnitEffect; WX_DEFINE_ARRAY_PTR(AudioUnitEffect *, AudioUnitEffectArray); -class AudioUnitEffectEventHelper; class AudioUnitEffectExportDialog; class AudioUnitEffectImportDialog; -class AudioUnitEffect : public EffectClientInterface, +class AudioUnitEffect : public wxEvtHandler, + public EffectClientInterface, public EffectUIClientInterface { public: @@ -76,15 +76,15 @@ public: virtual int GetMidiOutCount(); virtual void SetSampleRate(sampleCount rate); - virtual sampleCount GetBlockSize(sampleCount maxBlockSize); + virtual sampleCount SetBlockSize(sampleCount maxBlockSize); virtual sampleCount GetLatency(); virtual sampleCount GetTailSize(); virtual bool IsReady(); - virtual bool ProcessInitialize(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); virtual bool ProcessFinalize(); - virtual sampleCount ProcessBlock(float **inbuf, float **outbuf, sampleCount size); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); virtual bool RealtimeInitialize(); virtual bool RealtimeAddProcessor(int numChannels, float sampleRate); @@ -103,24 +103,23 @@ public: virtual bool GetAutomationParameters(EffectAutomationParameters & parms); virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + virtual bool LoadUserPreset(const wxString & name); + virtual bool SaveUserPreset(const wxString & name); + + virtual bool LoadFactoryPreset(int id); + virtual bool LoadFactoryDefaults(); + virtual wxArrayString GetFactoryPresets(); + // EffectUIClientInterface implementation - virtual void SetUIHost(EffectUIHostInterface *host); + virtual void SetHostUI(EffectUIHostInterface *host); virtual bool PopulateUI(wxWindow *parent); virtual bool IsGraphicalUI(); virtual bool ValidateUI(); virtual bool HideUI(); virtual bool CloseUI(); - virtual void LoadUserPreset(const wxString & name); - virtual void SaveUserPreset(const wxString & name); - - virtual void LoadFactoryPreset(int id); - virtual void LoadFactoryDefaults(); - - virtual wxArrayString GetFactoryPresets(); - - virtual bool CanExport(); + virtual bool CanExportPresets(); virtual void ExportPresets(); virtual void ImportPresets(); @@ -177,8 +176,12 @@ private: void GetChannelCounts(); - void LoadParameters(const wxString & group); - void SaveParameters(const wxString & group); + bool LoadParameters(const wxString & group); + bool SaveParameters(const wxString & group); + + void OnSize(wxSizeEvent & evt); + +private: wxString mPath; wxString mName; @@ -236,9 +239,8 @@ private: EventHandlerRef mContentTrackingHandlerRef; EventHandlerRef mAUTrackingHandlerRef; - AudioUnitEffectEventHelper *mEventHelper; + DECLARE_EVENT_TABLE(); - friend class AudioUnitEffectEventHelper; friend class AudioUnitEffectExportDialog; friend class AudioUnitEffectImportDialog; }; diff --git a/src/effects/ladspa/LadspaEffect.cpp b/src/effects/ladspa/LadspaEffect.cpp index 4dd75d448..f22d80e5b 100644 --- a/src/effects/ladspa/LadspaEffect.cpp +++ b/src/effects/ladspa/LadspaEffect.cpp @@ -149,105 +149,6 @@ bool LadspaEffectsModule::AutoRegisterPlugins(PluginManagerInterface & WXUNUSED( wxArrayString LadspaEffectsModule::FindPlugins(PluginManagerInterface & pm) { -#if defined(USE_LIBLRDF) && defined(EFFECT_CATEGORIES) - - EffectManager& em = EffectManager::Get(); - wxArrayString rdfPathList; - wxString rdfPathVar; - wxArrayString rdfFiles; - - InitCategoryMap(); - lrdf_init(); - - rdfPathVar = wxGetenv(wxT("LADSPA_RDF_PATH")); - if (rdfPathVar != wxT("")) - wxGetApp().AddMultiPathsToPathList(rdfPathVar, rdfPathList); - -#ifdef __WXGTK__ - wxGetApp().AddUniquePathToPathList(wxT("/usr/share/ladspa/rdf"), - rdfPathList); - wxGetApp().AddUniquePathToPathList(wxT("/usr/local/share/ladspa/rdf"), - rdfPathList); -#endif - -#ifdef __WXMAC__ - wxGetApp().AddUniquePathToPathList(wxT("/usr/share/ladspa/rdf"), - rdfPathList); - // XXX Maybe other Mac paths here? -#endif - -#ifdef __WXMSW__ - //wxGetApp().AddUniquePathToPathList(wxT("WINDOWS LRDF PATH"), - // rdfPathList); - // XXX Other Windows paths here. -#endif - - // Add the Audacity paths so we get ladspa.rdfs if we are using a local - // liblrdf - for(i=0; icount; ++i) { - char* label = lrdf_get_label(cats->items[i]); - if (!label) - continue; - wxString uri = MapCategoryUri(wxString::FromAscii(cats->items[i])); - em.AddCategory(uri, wxString::FromUTF8(label)); - std::free(label); - - lrdf_uris* plugs = lrdf_get_instances(cats->items[i]); - if (plugs) { - for (size_t j = 0; j < plugs->count; ++j) { - unsigned long uid = lrdf_get_uid(plugs->items[j]); - gPluginCategories.insert(std::make_pair(uid, uri)); - } - lrdf_free_uris(plugs); - } - } - - // And their relationships - for (size_t i = 0; i < cats->count; ++i) { - EffectCategory* p = - em.LookupCategory(MapCategoryUri(wxString::FromAscii(cats-> - items[i]))); - if (!p) - continue; - lrdf_uris* subs = lrdf_get_subclasses(cats->items[i]); - if (subs) { - for (size_t j = 0; j < subs->count; ++j) { - EffectCategory* c = - em.LookupCategory(MapCategoryUri(wxString::FromAscii(subs->items[j]))); - if (c) - em.AddCategoryParent(c, p); - } - lrdf_free_uris(subs); - } - } - - lrdf_free_uris(cats); - - } - -#endif - wxArrayString pathList; wxArrayString files; wxString pathVar; @@ -321,17 +222,9 @@ bool LadspaEffectsModule::RegisterPlugin(PluginManagerInterface & pm, const wxSt const LADSPA_Descriptor *data; for (data = mainFn(index); data; data = mainFn(++index)) { -#if defined(USE_LIBLRDF) && defined(EFFECT_CATEGORIES) - std::set categories; - std::multimap::const_iterator iter; - iter = gPluginCategories.lower_bound(data->UniqueID); - for ( ; (iter != gPluginCategories.end() && - iter->first == data->UniqueID); ++iter) - categories.insert(iter->second); -#endif LadspaEffect effect(path, index); if (effect.SetHost(NULL)) { - pm.RegisterEffectPlugin(this, &effect); + pm.RegisterPlugin(this, &effect); } } } @@ -466,12 +359,6 @@ void LadspaEffectOptionsDialog::OnOk(wxCommandEvent & WXUNUSED(evt)) EndModal(wxID_OK); } -/////////////////////////////////////////////////////////////////////////////// -// -// LadspaEffectEventHelper -// -/////////////////////////////////////////////////////////////////////////////// - enum { ID_DURATION = 20000, @@ -480,46 +367,18 @@ enum ID_TEXTS = 23000, }; -BEGIN_EVENT_TABLE(LadspaEffectEventHelper, wxEvtHandler) - EVT_COMMAND_RANGE(ID_TOGGLES, ID_TOGGLES + 999, wxEVT_COMMAND_CHECKBOX_CLICKED, LadspaEffectEventHelper::OnCheckBox) - EVT_COMMAND_RANGE(ID_SLIDERS, ID_SLIDERS + 999, wxEVT_COMMAND_SLIDER_UPDATED, LadspaEffectEventHelper::OnSlider) - EVT_COMMAND_RANGE(ID_TEXTS, ID_TEXTS + 999, wxEVT_COMMAND_TEXT_UPDATED, LadspaEffectEventHelper::OnTextCtrl) -END_EVENT_TABLE() - -LadspaEffectEventHelper::LadspaEffectEventHelper(LadspaEffect *effect) -{ - mEffect = effect; -} - -LadspaEffectEventHelper::~LadspaEffectEventHelper() -{ -} - -// ============================================================================ -// LadspaEffectEventHelper implementation -// ============================================================================ - -void LadspaEffectEventHelper::OnCheckBox(wxCommandEvent & evt) -{ - mEffect->OnCheckBox(evt); -} - -void LadspaEffectEventHelper::OnSlider(wxCommandEvent & evt) -{ - mEffect->OnSlider(evt); -} - -void LadspaEffectEventHelper::OnTextCtrl(wxCommandEvent & evt) -{ - mEffect->OnTextCtrl(evt); -} - /////////////////////////////////////////////////////////////////////////////// // // LadspaEffect // /////////////////////////////////////////////////////////////////////////////// +BEGIN_EVENT_TABLE(LadspaEffect, wxEvtHandler) + EVT_COMMAND_RANGE(ID_TOGGLES, ID_TOGGLES + 999, wxEVT_COMMAND_CHECKBOX_CLICKED, LadspaEffect::OnCheckBox) + EVT_COMMAND_RANGE(ID_SLIDERS, ID_SLIDERS + 999, wxEVT_COMMAND_SLIDER_UPDATED, LadspaEffect::OnSlider) + EVT_COMMAND_RANGE(ID_TEXTS, ID_TEXTS + 999, wxEVT_COMMAND_TEXT_UPDATED, LadspaEffect::OnTextCtrl) +END_EVENT_TABLE() + LadspaEffect::LadspaEffect(const wxString & path, int index) { mPath = path; @@ -546,7 +405,6 @@ LadspaEffect::LadspaEffect(const wxString & path, int index) mLatencyPort = -1; - mEventHelper = NULL; mDialog = NULL; mSliders = NULL; mFields = NULL; @@ -874,7 +732,7 @@ void LadspaEffect::SetSampleRate(sampleCount rate) mSampleRate = rate; } -sampleCount LadspaEffect::GetBlockSize(sampleCount maxBlockSize) +sampleCount LadspaEffect::SetBlockSize(sampleCount maxBlockSize) { mBlockSize = maxBlockSize; @@ -902,7 +760,7 @@ bool LadspaEffect::IsReady() return mReady; } -bool LadspaEffect::ProcessInitialize() +bool LadspaEffect::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) { /* Instantiate the plugin */ if (!mReady) @@ -933,23 +791,23 @@ bool LadspaEffect::ProcessFinalize() return true; } -sampleCount LadspaEffect::ProcessBlock(float **inbuf, float **outbuf, sampleCount size) +sampleCount LadspaEffect::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) { for (int i = 0; i < mAudioIns; i++) { - mData->connect_port(mMaster, mInputPorts[i], inbuf[i]); + mData->connect_port(mMaster, mInputPorts[i], inBlock[i]); } for (int i = 0; i < mAudioOuts; i++) { - mData->connect_port(mMaster, mOutputPorts[i], outbuf[i]); + mData->connect_port(mMaster, mOutputPorts[i], outBlock[i]); } - mData->run(mMaster, size); + mData->run(mMaster, blockLen); RefreshControls(true); - return size; + return blockLen; } bool LadspaEffect::RealtimeInitialize() @@ -1035,6 +893,10 @@ bool LadspaEffect::ShowInterface(wxWindow *parent, bool forceModal) return false; } + mDialog->Layout(); + mDialog->Fit(); + mDialog->SetMinSize(mDialog->GetSize()); + if ((SupportsRealtime() || GetType() == EffectTypeAnalyze) && !forceModal) { mDialog->Show(); @@ -1088,11 +950,50 @@ bool LadspaEffect::SetAutomationParameters(EffectAutomationParameters & parms) return true; } +bool LadspaEffect::LoadUserPreset(const wxString & name) +{ + if (!LoadParameters(name)) + { + return false; + } + + RefreshControls(); + + return true; +} + +bool LadspaEffect::SaveUserPreset(const wxString & name) +{ + return SaveParameters(name); +} + +wxArrayString LadspaEffect::GetFactoryPresets() +{ + return wxArrayString(); +} + +bool LadspaEffect::LoadFactoryPreset(int WXUNUSED(id)) +{ + return true; +} + +bool LadspaEffect::LoadFactoryDefaults() +{ + if (!LoadParameters(mHost->GetFactoryDefaultsGroup())) + { + return false; + } + + RefreshControls(); + + return true; +} + // ============================================================================ // EffectUIClientInterface Implementation // ============================================================================ -void LadspaEffect::SetUIHost(EffectUIHostInterface *host) +void LadspaEffect::SetHostUI(EffectUIHostInterface *host) { mUIHost = host; } @@ -1101,21 +1002,30 @@ bool LadspaEffect::PopulateUI(wxWindow *parent) { mParent = parent; - mEventHelper = new LadspaEffectEventHelper(this); - mParent->PushEventHandler(mEventHelper); + mParent->PushEventHandler(this); - mToggles = new wxCheckBox*[mData->PortCount]; - mSliders = new wxSlider*[mData->PortCount]; - mFields = new wxTextCtrl*[mData->PortCount]; - mLabels = new wxStaticText*[mData->PortCount]; + mToggles = new wxCheckBox *[mData->PortCount]; + mSliders = new wxSlider *[mData->PortCount]; + mFields = new wxTextCtrl *[mData->PortCount]; + mLabels = new wxStaticText *[mData->PortCount]; memset(mFields, 0, mData->PortCount * sizeof(wxTextCtrl *)); + wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL); + wxScrolledWindow *w = new wxScrolledWindow(mParent, + wxID_ANY, + wxDefaultPosition, + wxDefaultSize, + wxVSCROLL | wxTAB_TRAVERSAL); + w->SetScrollRate(0, 20); + mainSizer->Add(w, 0, wxEXPAND); + mParent->SetSizer(mainSizer); + wxSizer *marginSizer = new wxBoxSizer(wxVERTICAL); if (mNumInputControls) { - wxSizer *paramSizer = new wxStaticBoxSizer(wxVERTICAL, mParent, _("Effect Settings")); + wxSizer *paramSizer = new wxStaticBoxSizer(wxVERTICAL, w, _("Effect Settings")); wxFlexGridSizer *gridSizer = new wxFlexGridSizer(5, 0, 0); gridSizer->AddGrowableCol(3); @@ -1125,17 +1035,17 @@ bool LadspaEffect::PopulateUI(wxWindow *parent) // Add the duration control for generators if (GetType() == EffectTypeGenerate) { - item = new wxStaticText(mParent, 0, _("Duration:")); + item = new wxStaticText(w, 0, _("Duration:")); gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); mDuration = new NumericTextCtrl(NumericConverter::TIME, - mParent, - ID_DURATION, - _("hh:mm:ss + milliseconds"), - mHost->GetDuration(), - mSampleRate, - wxDefaultPosition, - wxDefaultSize, - true); + w, + ID_DURATION, + _("hh:mm:ss + milliseconds"), + mHost->GetDuration(), + mSampleRate, + wxDefaultPosition, + wxDefaultSize, + true); mDuration->SetName(_("Duration")); mDuration->EnableMenu(); gridSizer->Add(mDuration, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); @@ -1153,7 +1063,7 @@ bool LadspaEffect::PopulateUI(wxWindow *parent) } wxString labelText = LAT1CTOWX(mData->PortNames[p]); - item = new wxStaticText(mParent, 0, labelText + wxT(":")); + item = new wxStaticText(w, 0, labelText + wxT(":")); gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); wxString fieldText; @@ -1161,7 +1071,7 @@ bool LadspaEffect::PopulateUI(wxWindow *parent) if (LADSPA_IS_HINT_TOGGLED(hint.HintDescriptor)) { - mToggles[p] = new wxCheckBox(mParent, ID_TOGGLES + p, wxT("")); + mToggles[p] = new wxCheckBox(w, ID_TOGGLES + p, wxT("")); mToggles[p]->SetName(labelText); mToggles[p]->SetValue(mInputControls[p] > 0); gridSizer->Add(mToggles[p], 0, wxALL, 5); @@ -1216,7 +1126,7 @@ bool LadspaEffect::PopulateUI(wxWindow *parent) // Don't specify a value at creation time. This prevents unwanted events // being sent to the OnTextCtrl() handler before the associated slider // has been created. - mFields[p] = new wxTextCtrl(mParent, ID_TEXTS + p); + mFields[p] = new wxTextCtrl(w, ID_TEXTS + p); mFields[p]->SetName(labelText); gridSizer->Add(mFields[p], 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); @@ -1231,7 +1141,7 @@ bool LadspaEffect::PopulateUI(wxWindow *parent) { str = Internat::ToDisplayString(lower); } - item = new wxStaticText(mParent, 0, str); + item = new wxStaticText(w, 0, str); gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); } else @@ -1239,7 +1149,7 @@ bool LadspaEffect::PopulateUI(wxWindow *parent) gridSizer->Add(1, 1, 0); } - mSliders[p] = new wxSlider(mParent, ID_SLIDERS + p, + mSliders[p] = new wxSlider(w, ID_SLIDERS + p, 0, 0, 1000, wxDefaultPosition, wxSize(200, -1)); @@ -1256,7 +1166,7 @@ bool LadspaEffect::PopulateUI(wxWindow *parent) { str = Internat::ToDisplayString(upper); } - item = new wxStaticText(mParent, 0, str); + item = new wxStaticText(w, 0, str); gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, 5); } else @@ -1303,13 +1213,13 @@ bool LadspaEffect::PopulateUI(wxWindow *parent) mFields[p]->SetValue(fieldText); } - paramSizer->Add(gridSizer, 1, wxEXPAND | wxALL, 5); - marginSizer->Add(paramSizer, 1, wxEXPAND | wxALL, 5); + paramSizer->Add(gridSizer, 0, wxEXPAND | wxALL, 5); + marginSizer->Add(paramSizer, 0, wxEXPAND | wxALL, 5); } if (mNumOutputControls > 0 ) { - wxSizer *paramSizer = new wxStaticBoxSizer(wxVERTICAL, mParent, _("Effect Output")); + wxSizer *paramSizer = new wxStaticBoxSizer(wxVERTICAL, w, _("Effect Output")); wxFlexGridSizer *gridSizer = new wxFlexGridSizer(2, 0, 0); gridSizer->AddGrowableCol(3); @@ -1325,12 +1235,12 @@ bool LadspaEffect::PopulateUI(wxWindow *parent) } wxString labelText = LAT1CTOWX(mData->PortNames[p]); - item = new wxStaticText(mParent, 0, labelText + wxT(":")); + item = new wxStaticText(w, 0, labelText + wxT(":")); gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); wxString fieldText; - mFields[p] = new wxTextCtrl(mParent, wxID_ANY, + mFields[p] = new wxTextCtrl(w, wxID_ANY, fieldText, wxDefaultPosition, wxDefaultSize, @@ -1339,13 +1249,15 @@ bool LadspaEffect::PopulateUI(wxWindow *parent) gridSizer->Add(mFields[p], 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); } - paramSizer->Add(gridSizer, 1, wxEXPAND | wxALL, 5); - marginSizer->Add(paramSizer, 1, wxEXPAND | wxALL, 5); + paramSizer->Add(gridSizer, 0, wxEXPAND | wxALL, 5); + marginSizer->Add(paramSizer, 0, wxEXPAND | wxALL, 5); RefreshControls(true); } - mParent->SetSizer(marginSizer); + w->SetSizerAndFit(marginSizer); + + mParent->SetSizeHints(-1, -1); return true; } @@ -1377,8 +1289,7 @@ bool LadspaEffect::HideUI() bool LadspaEffect::CloseUI() { - mParent->RemoveEventHandler(mEventHelper); - delete mEventHelper; + mParent->RemoveEventHandler(this); if (mToggles) { @@ -1411,34 +1322,7 @@ bool LadspaEffect::CloseUI() return true; } -void LadspaEffect::LoadUserPreset(const wxString & name) -{ - LoadParameters(name); - RefreshControls(); -} - -void LadspaEffect::SaveUserPreset(const wxString & name) -{ - SaveParameters(name); -} - -wxArrayString LadspaEffect::GetFactoryPresets() -{ - return wxArrayString(); -} - -void LadspaEffect::LoadFactoryPreset(int WXUNUSED(id)) -{ - return; -} - -void LadspaEffect::LoadFactoryDefaults() -{ - LoadParameters(mHost->GetFactoryDefaultsGroup()); - RefreshControls(); -} - -bool LadspaEffect::CanExport() +bool LadspaEffect::CanExportPresets() { return false; } @@ -1507,26 +1391,32 @@ void LadspaEffect::Unload() } } -void LadspaEffect::LoadParameters(const wxString & group) +bool LadspaEffect::LoadParameters(const wxString & group) { wxString value; - if (mHost->GetPrivateConfig(group, wxT("Value"), value, wxEmptyString)) + if (!mHost->GetPrivateConfig(group, wxT("Value"), value, wxEmptyString)) { - wxStringTokenizer st(value, wxT(',')); - if (st.CountTokens() == mData->PortCount) - { - for (unsigned long p = 0; st.HasMoreTokens(); p++) - { - double val = 0.0; - st.GetNextToken().ToDouble(&val); - mInputControls[p] = val; - } - } + return false; } + + wxStringTokenizer st(value, wxT(',')); + if (st.CountTokens() != mData->PortCount) + { + return false; + } + + for (unsigned long p = 0; st.HasMoreTokens(); p++) + { + double val = 0.0; + st.GetNextToken().ToDouble(&val); + mInputControls[p] = val; + } + + return true; } -void LadspaEffect::SaveParameters(const wxString & group) +bool LadspaEffect::SaveParameters(const wxString & group) { wxString parms; for (unsigned long p = 0; p < mData->PortCount; p++) @@ -1534,7 +1424,7 @@ void LadspaEffect::SaveParameters(const wxString & group) parms += wxString::Format(wxT(",%f"), mInputControls[p]); } - mHost->SetPrivateConfig(group, wxT("Value"), parms.Mid(1)); + return mHost->SetPrivateConfig(group, wxT("Value"), parms.Mid(1)); } LADSPA_Handle LadspaEffect::InitInstance(float sampleRate) diff --git a/src/effects/ladspa/LadspaEffect.h b/src/effects/ladspa/LadspaEffect.h index f77010841..71c4c6e26 100644 --- a/src/effects/ladspa/LadspaEffect.h +++ b/src/effects/ladspa/LadspaEffect.h @@ -34,9 +34,8 @@ class wxCheckBox; WX_DEFINE_ARRAY_PTR(LADSPA_Handle, LadspaSlaveArray); -class LadspaEffectEventHelper; - -class LadspaEffect : public EffectClientInterface, +class LadspaEffect : public wxEvtHandler, + public EffectClientInterface, public EffectUIClientInterface { public: @@ -73,15 +72,15 @@ public: virtual int GetMidiOutCount(); virtual void SetSampleRate(sampleCount rate); - virtual sampleCount GetBlockSize(sampleCount maxBlockSize); + virtual sampleCount SetBlockSize(sampleCount maxBlockSize); virtual sampleCount GetLatency(); virtual sampleCount GetTailSize(); virtual bool IsReady(); - virtual bool ProcessInitialize(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); virtual bool ProcessFinalize(); - virtual sampleCount ProcessBlock(float **inbuf, float **outbuf, sampleCount size); + virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); virtual bool RealtimeInitialize(); virtual bool RealtimeAddProcessor(int numChannels, float sampleRate); @@ -100,23 +99,23 @@ public: virtual bool GetAutomationParameters(EffectAutomationParameters & parms); virtual bool SetAutomationParameters(EffectAutomationParameters & parms); + virtual bool LoadUserPreset(const wxString & name); + virtual bool SaveUserPreset(const wxString & name); + + virtual wxArrayString GetFactoryPresets(); + virtual bool LoadFactoryPreset(int id); + virtual bool LoadFactoryDefaults(); + // EffectUIClientInterface implementation - virtual void SetUIHost(EffectUIHostInterface *host); + virtual void SetHostUI(EffectUIHostInterface *host); virtual bool PopulateUI(wxWindow *parent); virtual bool IsGraphicalUI(); virtual bool ValidateUI(); virtual bool HideUI(); virtual bool CloseUI(); - virtual void LoadUserPreset(const wxString & name); - virtual void SaveUserPreset(const wxString & name); - - virtual wxArrayString GetFactoryPresets(); - virtual void LoadFactoryPreset(int id); - virtual void LoadFactoryDefaults(); - - virtual bool CanExport(); + virtual bool CanExportPresets(); virtual void ExportPresets(); virtual void ImportPresets(); @@ -129,8 +128,8 @@ private: bool Load(); void Unload(); - void LoadParameters(const wxString & group); - void SaveParameters(const wxString & group); + bool LoadParameters(const wxString & group); + bool SaveParameters(const wxString & group); LADSPA_Handle InitInstance(float sampleRate); void FreeInstance(LADSPA_Handle handle); @@ -180,7 +179,6 @@ private: LadspaSlaveArray mSlaves; EffectUIHostInterface *mUIHost; - LadspaEffectEventHelper *mEventHelper; NumericTextCtrl *mDuration; wxDialog *mDialog; @@ -190,33 +188,9 @@ private: wxStaticText **mLabels; wxCheckBox **mToggles; - friend class LadspaEffectEventHelper; - friend class LadspaEffectsModule; -}; - -/////////////////////////////////////////////////////////////////////////////// -// -// LadspaEffectEventHelper -// -/////////////////////////////////////////////////////////////////////////////// - -class LadspaEffectEventHelper : public wxEvtHandler -{ -public: - LadspaEffectEventHelper(LadspaEffect *effect); - virtual ~LadspaEffectEventHelper(); - - // LadspaEffectEventHelper implementatino - - void OnCheckBox(wxCommandEvent & evt); - void OnSlider(wxCommandEvent & evt); - void OnTextCtrl(wxCommandEvent & evt); - void ControlSetFocus(wxFocusEvent & evt); - -private: - LadspaEffect *mEffect; - DECLARE_EVENT_TABLE(); + + friend class LadspaEffectsModule; }; /////////////////////////////////////////////////////////////////////////////// diff --git a/src/effects/lv2/LV2Effect.cpp b/src/effects/lv2/LV2Effect.cpp index 8585eee28..0504f972b 100644 --- a/src/effects/lv2/LV2Effect.cpp +++ b/src/effects/lv2/LV2Effect.cpp @@ -1,8 +1,8 @@ - /********************************************************************** +/********************************************************************** Audacity: A Digital Audio Editor - LV2Effect.h + LV2Effect.cpp Audacity(R) is copyright (c) 1999-2008 Audacity Team. License: GPL v2. See License.txt. @@ -19,39 +19,245 @@ #include #define isfinite _finite #define isnan _isnan -#elif defined(__WXMAC__) -#else -//#define isfinite std::isfinite -//#define isnam std::isnan #endif -#include #include -#include #include +#include +#include #include #include #include -#include #include -#include -#include #include #include #include -#include -#include "../Effect.h" #include "LoadLV2.h" #include "LV2Effect.h" -#include "LV2PortGroup.h" #include "../../Internat.h" -#include "lv2_event_helpers.h" +#include "../../ShuttleGui.h" +#include "../../widgets/valnum.h" #include "lilv/lilv.h" +#include "suil/suil.h" +#include "lv2/lv2plug.in/ns/ext/instance-access/instance-access.h" +#include "lv2/lv2plug.in/ns/ext/port-groups/port-groups.h" +#include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h" +#include "lv2/lv2plug.in/ns/ext/parameters/parameters.h" +#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" -#include +#if defined(__WXGTK__) +#include + +#include +#endif + +// Define the static URI nodes +#undef URI +#define URI(n, u) LilvNode *LV2Effect::n = NULL; +URILIST + +/////////////////////////////////////////////////////////////////////////////// +// +// LV2EffectMeter +// +/////////////////////////////////////////////////////////////////////////////// + +class LV2EffectMeter : public wxWindow +{ +public: + LV2EffectMeter(wxWindow *parent, const LV2Port & ctrl); + virtual ~LV2EffectMeter(); + +private: + void OnErase(wxEraseEvent & evt); + void OnPaint(wxPaintEvent & evt); + void OnIdle(wxIdleEvent & evt); + void OnSize(wxSizeEvent & evt); + +private: + const LV2Port & mCtrl; + float mLastValue; + + DECLARE_EVENT_TABLE(); +}; + +BEGIN_EVENT_TABLE(LV2EffectMeter, wxWindow) + EVT_IDLE(LV2EffectMeter::OnIdle) + EVT_ERASE_BACKGROUND(LV2EffectMeter::OnErase) + EVT_PAINT(LV2EffectMeter::OnPaint) + EVT_SIZE(LV2EffectMeter::OnSize) +END_EVENT_TABLE() + +LV2EffectMeter::LV2EffectMeter(wxWindow *parent, const LV2Port & ctrl) +: wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDEFAULT_CONTROL_BORDER), + mCtrl(ctrl) +{ + mLastValue = -mCtrl.mVal; +} + +LV2EffectMeter::~LV2EffectMeter() +{ +} + +void LV2EffectMeter::OnIdle(wxIdleEvent & WXUNUSED(evt)) +{ + if (mLastValue != mCtrl.mVal) + { + Refresh(); + } +} + +void LV2EffectMeter::OnErase(wxEraseEvent & WXUNUSED(evt)) +{ + // Just ignore it to prevent flashing +} + +void LV2EffectMeter::OnPaint(wxPaintEvent & WXUNUSED(evt)) +{ + wxDC *dc = wxAutoBufferedPaintDCFactory(this); + + // Cache some metrics + wxRect r = GetClientRect(); + wxCoord x = r.GetLeft(); + wxCoord y = r.GetTop(); + wxCoord w = r.GetWidth(); + wxCoord h = r.GetHeight(); + + float val = mCtrl.mVal; + if (val > mCtrl.mMax) + { + val = mCtrl.mMax; + } + if (val < mCtrl.mMin) + { + val = mCtrl.mMin; + } + + // Setup for erasing the background + dc->SetPen(*wxTRANSPARENT_PEN); + dc->SetBrush(wxColour(100, 100, 220)); + + dc->DrawRectangle(x, y, (w * (val / (mCtrl.mMax - mCtrl.mMin))), h); + + mLastValue = mCtrl.mVal; + + delete dc; +} + +void LV2EffectMeter::OnSize(wxSizeEvent & WXUNUSED(evt)) +{ + Refresh(false); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// LV2EffectSettingsDialog +// +/////////////////////////////////////////////////////////////////////////////// + +class LV2EffectSettingsDialog : public wxDialog +{ +public: + LV2EffectSettingsDialog(wxWindow *parent, LV2Effect *effect); + virtual ~LV2EffectSettingsDialog(); + + void PopulateOrExchange(ShuttleGui & S); + + void OnOk(wxCommandEvent & evt); + +private: + LV2Effect *mEffect; + bool mUseLatency; + bool mUseGUI; + + DECLARE_EVENT_TABLE(); +}; + +BEGIN_EVENT_TABLE(LV2EffectSettingsDialog, wxDialog) + EVT_BUTTON(wxID_OK, LV2EffectSettingsDialog::OnOk) +END_EVENT_TABLE() + +LV2EffectSettingsDialog::LV2EffectSettingsDialog(wxWindow *parent, LV2Effect *effect) +: wxDialog(parent, wxID_ANY, wxString(_("LV2 Effect Settings"))) +{ + mEffect = effect; + + mEffect->mHost->GetSharedConfig(wxT("Settings"), wxT("UseLatency"), mUseLatency, true); + mEffect->mHost->GetSharedConfig(wxT("Settings"), wxT("UseGUI"), mUseGUI, true); +wxPrintf(wxT("#1 usegui %d\n"), mUseGUI); + ShuttleGui S(this, eIsCreating); + PopulateOrExchange(S); +} + +LV2EffectSettingsDialog::~LV2EffectSettingsDialog() +{ +} + +void LV2EffectSettingsDialog::PopulateOrExchange(ShuttleGui & S) +{ + S.SetBorder(5); + S.StartHorizontalLay(wxEXPAND, 1); + { + S.StartVerticalLay(false); + { + S.StartStatic(_("Latency Compensation")); + { + S.AddVariableText(wxString() + + _("As part of their processing, some LV2 effects must delay returning ") + + _("audio to Audacity. When not compensating for this delay, you will ") + + _("notice that small silences have been inserted into the audio. ") + + _("Enabling this setting will provide that compensation, but it may ") + + _("not work for all LV2 effects."))->Wrap(650); + + S.StartHorizontalLay(wxALIGN_LEFT); + { + S.TieCheckBox(_("Enable &compensation"), + mUseLatency); + } + S.EndHorizontalLay(); + } + S.EndStatic(); + + S.StartStatic(_("Graphical Mode")); + { + S.AddVariableText(wxString() + + _("LV2 effects can have a graphical interface for setting parameter values.") + + _(" A basic text-only method is also available. ") + + _(" Reopen the effect for this to take effect."))->Wrap(650); + S.TieCheckBox(_("Enable &graphical interface"), + mUseGUI); + } + S.EndStatic(); + } + S.EndVerticalLay(); + } + S.EndHorizontalLay(); + + S.AddStandardButtons(); + + Layout(); + Fit(); + Center(); +} + +void LV2EffectSettingsDialog::OnOk(wxCommandEvent & WXUNUSED(evt)) +{ + if (!Validate()) + { + return; + } + + ShuttleGui S(this, eIsGettingFromDialog); + PopulateOrExchange(S); + + mEffect->mHost->SetSharedConfig(wxT("Settings"), wxT("UseLatency"), mUseLatency); + mEffect->mHost->SetSharedConfig(wxT("Settings"), wxT("UseGUI"), mUseGUI); + + EndModal(wxID_OK); +} /////////////////////////////////////////////////////////////////////////////// // @@ -59,257 +265,98 @@ // /////////////////////////////////////////////////////////////////////////////// +enum +{ + ID_Duration = 10000, + ID_Triggers = 11000, + ID_Toggles = 12000, + ID_Sliders = 13000, + ID_Choices = 14000, + ID_Texts = 15000, +}; + +BEGIN_EVENT_TABLE(LV2Effect, wxEvtHandler) + EVT_COMMAND_RANGE(ID_Triggers, ID_Triggers + 999, wxEVT_COMMAND_BUTTON_CLICKED, LV2Effect::OnTrigger) + EVT_COMMAND_RANGE(ID_Toggles, ID_Toggles + 999, wxEVT_COMMAND_CHECKBOX_CLICKED, LV2Effect::OnToggle) + EVT_COMMAND_RANGE(ID_Sliders, ID_Sliders + 999, wxEVT_COMMAND_SLIDER_UPDATED, LV2Effect::OnSlider) + EVT_COMMAND_RANGE(ID_Choices, ID_Choices + 999, wxEVT_COMMAND_CHOICE_SELECTED, LV2Effect::OnChoice) + EVT_COMMAND_RANGE(ID_Texts, ID_Texts + 999, wxEVT_COMMAND_TEXT_UPDATED, LV2Effect::OnText) + + EVT_IDLE(LV2Effect::OnIdle) +END_EVENT_TABLE() + +#include + WX_DEFINE_OBJARRAY(LV2PortArray); -LV2Effect::LV2Effect(const LilvPlugin *data, - const std::set & categories) -: mValid(true), - mCategories(categories), - mMidiInput(0), - mLatencyPortIndex(-1) +LV2Effect::LV2Effect(const LilvPlugin *plug) { + mPlug = plug; - // We don't support any features at all, so if the plugin requires - // any we skip it. - LilvNodes *req = lilv_plugin_get_required_features(data); - size_t nFeatures = lilv_nodes_size(req); - lilv_nodes_free(req); - if (nFeatures > 0) - { - mValid = false; - return; - } + mHost = NULL; + mMaster = NULL; + mProcess = NULL; - mData = data; - pluginName = GetString(lilv_plugin_get_name(mData), true); + mSampleRate = 44100; + mBlockSize = 512; - fInBuffer = NULL; - fOutBuffer = NULL; + mLatencyPort = -1; + mLatencyDone = false; + mLatency = 0.0; - mDuration = 0; + mDialog = NULL; + mSliders = NULL; + mFields = NULL; - // Allocate buffers for the port indices and the default control values - int numPorts = lilv_plugin_get_num_ports(mData); - float *minimumValues = new float [numPorts]; - float *maximumValues = new float [numPorts]; - float *defaultValues = new float [numPorts]; + mURIMap = NULL; + mNumURIMap = 0; + mOptions = NULL; + mNumOptions = 0; + mFeatures = NULL; + mNumFeatures = 0; - // Retrieve the port ranges for all ports (some values may be NaN) - lilv_plugin_get_port_ranges_float(mData, minimumValues, - maximumValues, defaultValues); - - // Get info about all ports - for (int i = 0; i < numPorts; i++) - { - const LilvPort *port = lilv_plugin_get_port_by_index(mData, i); - LV2Port internalPort; - internalPort.mIndex = lilv_port_get_index(mData, port); - - // Get the port name - LilvNode *tmpName = lilv_port_get_name(mData, port); - internalPort.mName = GetString(tmpName); - lilv_node_free(tmpName); - - // Get the scale points - LilvScalePoints* points = lilv_port_get_scale_points(mData, port); - LILV_FOREACH(scale_points, j, points) - { - const LilvScalePoint *point = lilv_scale_points_get(points, j); - - internalPort.mScaleValues.Add(lilv_node_as_float(lilv_scale_point_get_value(point))); - internalPort.mScaleLabels.Add(GetString(lilv_scale_point_get_label(point))); - } - lilv_scale_points_free(points); - - // Get the groups - LilvNodes *groups = lilv_port_get_value(mData, port, gPortGroup); - if (groups) - { - LilvNode *group = lilv_nodes_get_first(groups); - wxString uri = GetString(group); - - wxString label; - const LilvNode *name = lilv_world_get(gWorld, group, gName, NULL); - if (name) - { - label = GetString(name); - } - else - { - // Shouldn't happen, but provide something - label = uri; - } - lilv_nodes_free(groups); - - // Check for new group - if (mPortGroups.find(uri) == mPortGroups.end()) - { - mPortGroups[uri] = LV2PortGroup(label); - } -#if 0 - // Get subgroup - // - // LLL: This isn't right...must find or construct a plugin with - // subgroups. - LilvNodes *subgroup = lilv_node_get_value(mData, port, gSubGroupOf); - if (subgroups) - { - LilvNode *subgroup = lilv_nodes_get_first(subgroups); - wxString uri = GetString(subgroup); - const LilvNode *subgroup = lilv_world_get(gWorld, group, gSubGroupOf, NULL); - wxString label = GetString(name); - lilv_nodes_free(subgroup); - } - else -#endif - { - mRootGroup.AddSubGroup(mPortGroups[uri]); - } - mPortGroups[uri].AddParameter(i); - - } - else - { - mRootGroup.AddParameter(i); - } - - // Get the port type - if (lilv_port_is_a(mData, port, gAudioPortClass)) - { - if (lilv_port_is_a(mData, port, gInputPortClass)) - { - mAudioInputs.Add(internalPort); - } - else if (lilv_port_is_a(mData, port, gOutputPortClass)) - { - mAudioOutputs.Add(internalPort); - } - } - else if (lilv_port_is_a(mData, port, gControlPortClass) && - lilv_port_is_a(mData, port, gInputPortClass)) - { - internalPort.mControlBuffer = float(1.0); - internalPort.mMin = minimumValues[i]; - internalPort.mMax = maximumValues[i]; - internalPort.mDefault = defaultValues[i]; - if (isfinite(defaultValues[i])) - { - internalPort.mControlBuffer = defaultValues[i]; - } - else if (isfinite(minimumValues[i])) - { - internalPort.mControlBuffer = minimumValues[i]; - } - else if (isfinite(maximumValues[i])) - { - internalPort.mControlBuffer = maximumValues[i]; - } - - if (lilv_port_has_property(mData, port, gPortToggled)) - { - internalPort.mToggle = true; - } - else if (lilv_port_has_property(mData, port, gPortIsInteger)) - { - internalPort.mInteger = true; - } - else if (lilv_port_has_property(mData, port, gPortIsSampleRate)) - { - internalPort.mSampleRate = true; - } - else if (lilv_port_has_property(mData, port, gPortIsEnumeration)) - { - internalPort.mEnumeration = true; - } - - mControlInputs.Add(internalPort); - } - else if (lilv_port_is_a(mData, port, gControlPortClass) && - lilv_port_is_a(mData, port, gOutputPortClass)) - { - // If there is more than one latency port, the plugin is invalid - if (lilv_port_has_property(mData, port, gPortIsLatency)) - { - if (mLatencyPortIndex >= 0) - { - mValid = false; - continue; - } - mLatencyPortIndex = i; - } - else if (!lilv_port_has_property(mData, port, gPortIsOptional)) - { - mControlOutputs.Add(internalPort); - } - } - else if (lilv_port_is_a(mData, port, gMidiPortClass) && - lilv_port_is_a(mData, port, gInputPortClass)) - { - // If there is more than one MIDI input port, the plugin is invalid - if (mMidiInput) - { - mValid = false; - continue; - } - mMidiInput = new LV2Port(internalPort); - } - else - { - // Unknown port type, we set the invalid flag - // mValid = false; - } - } - - delete [] minimumValues; - delete [] maximumValues; - delete [] defaultValues; - - // MIDI synths may not have any audio inputs. - if (mMidiInput && mAudioInputs.GetCount() > 0) - { - mValid = false; - } - - // Determine whether the plugin is a generator, effect or analyser - // depending on the number of ports of each type (not completely accurate, - // but works most of the time) - int flags = PLUGIN_EFFECT; - if (mAudioInputs.GetCount() == 0) - { - flags |= INSERT_EFFECT; - } - else if (mAudioOutputs.GetCount() == 0) - { - flags |= ANALYZE_EFFECT; - } - else - { - flags |= PROCESS_EFFECT; - } - - SetEffectFlags(flags); + mIdleFeature = NULL; + mOptionsInterface = NULL; } LV2Effect::~LV2Effect() { - if (mMidiInput) + if (mURIMap) { - delete mMidiInput; + for (int i = 0; i < mNumURIMap; i++) + { + free(mURIMap[i]); + } + free(mURIMap); + } + + if (mFeatures) + { + for (int i = 0; mFeatures[i] != NULL; i++) + { + delete mFeatures[i]; + } + free(mFeatures); + } + + if (mOptions) + { + free(mOptions); } } // ============================================================================ -// IdentInterface implementation +// IdentInterface Implementation // ============================================================================ wxString LV2Effect::GetPath() { - return GetString(lilv_plugin_get_uri(mData)); + return LilvString(lilv_plugin_get_uri(mPlug)); } wxString LV2Effect::GetSymbol() { - return pluginName; + return LilvString(lilv_plugin_get_name(mPlug), true); } wxString LV2Effect::GetName() @@ -319,11 +366,11 @@ wxString LV2Effect::GetName() wxString LV2Effect::GetVendor() { - wxString vendor = GetString(lilv_plugin_get_author_name(mData), true); + wxString vendor = LilvString(lilv_plugin_get_author_name(mPlug), true); if (vendor.IsEmpty()) { - vendor = wxT("N/A"); + vendor = _("N/A"); } return vendor; @@ -331,22 +378,36 @@ wxString LV2Effect::GetVendor() wxString LV2Effect::GetVersion() { - return wxT("N/A"); + return wxT("1.0"); } wxString LV2Effect::GetDescription() { - return wxT("N/A"); + return _("N/A"); } // ============================================================================ -// EffectIdentInterface implementation +// EffectIdentInterface Implementation // ============================================================================ EffectType LV2Effect::GetType() { - // For now, relegate to Effect() - return Effect::GetType(); + if (GetAudioInCount() == 0 && GetAudioOutCount() == 0) + { + return EffectTypeNone; + } + + if (GetAudioInCount() == 0) + { + return EffectTypeGenerate; + } + + if (GetAudioOutCount() == 0) + { + return EffectTypeAnalyze; + } + + return EffectTypeProcess; } wxString LV2Effect::GetFamily() @@ -356,8 +417,7 @@ wxString LV2Effect::GetFamily() bool LV2Effect::IsInteractive() { - // For now, relegate to Effect() - return Effect::IsInteractive(); + return mControls.GetCount() != 0; } bool LV2Effect::IsDefault() @@ -367,12 +427,12 @@ bool LV2Effect::IsDefault() bool LV2Effect::IsLegacy() { - return true; + return false; } bool LV2Effect::SupportsRealtime() { - return false; + return GetType() == EffectTypeProcess; } bool LV2Effect::SupportsAutomation() @@ -381,428 +441,745 @@ bool LV2Effect::SupportsAutomation() } // ============================================================================ -// Effect Implementation +// EffectClientInterface Implementation // ============================================================================ -wxString LV2Effect::GetEffectName() +bool LV2Effect::SetHost(EffectHostInterface *host) { - if (mControlInputs.GetCount() > 0) + mHost = host; + + // Allocate buffers for the port indices and the default control values + int numPorts = lilv_plugin_get_num_ports(mPlug); + float *minimumVals = new float [numPorts]; + float *maximumVals = new float [numPorts]; + float *defaultValues = new float [numPorts]; + + // Retrieve the port ranges for all ports (some values may be NaN) + lilv_plugin_get_port_ranges_float(mPlug, + minimumVals, + maximumVals, + defaultValues); + + // Get info about all ports + for (int i = 0; i < numPorts; i++) { - return pluginName + wxT("..."); - } - else - { - return pluginName; - } -} + const LilvPort *port = lilv_plugin_get_port_by_index(mPlug, i); + int index = lilv_port_get_index(mPlug, port); -std::set LV2Effect::GetEffectCategories() -{ - return mCategories; -} - -wxString LV2Effect::GetEffectIdentifier() -{ - wxStringTokenizer st(pluginName, wxT(" ")); - wxString id; - - // CamelCase the name - while (st.HasMoreTokens()) - { - wxString tok = st.GetNextToken(); - - id += tok.Left(1).MakeUpper() + tok.Mid(1); - } - - return id; -} - -wxString LV2Effect::GetEffectAction() -{ - return wxString::Format(_("Performing Effect: %s"), - pluginName.c_str()); -} - -bool LV2Effect::Init() -{ - mBlockSize = 0; - mainRate = 0; - - TrackListOfKindIterator iter(Track::Wave, mTracks); - Track *left = iter.First(); - while(left) - { - if (mainRate == 0) + // Quick check for audio ports + if (lilv_port_is_a(mPlug, port, gAudio)) { - mainRate = (int)(((WaveTrack *)left)->GetRate() + 0.5); + if (lilv_port_is_a(mPlug, port, gInput)) + { + mAudioInputs.Add(index); + } + else if (lilv_port_is_a(mPlug, port, gOutput)) + { + mAudioOutputs.Add(index); + } + continue; } - if (left->GetLinked()) + // Only control ports from this point + if (!lilv_port_is_a(mPlug, port, gControl)) { - Track *right = iter.Next(); + continue; + } - if (((WaveTrack *)left)->GetRate() != - ((WaveTrack *)right)->GetRate()) + LV2Port ctrl; + ctrl.mIndex = index; + + // Get the port name + LilvNode *tmpName = lilv_port_get_name(mPlug, port); + ctrl.mName = LilvString(tmpName); + lilv_node_free(tmpName); + + // Get any unit descriptor + LilvNode *unit = lilv_port_get(mPlug, port, gUnit); + if (unit) + { + ctrl.mUnits = LilvString(lilv_world_get(gWorld, unit, gUnitSymbol, NULL)); + } + + // Get the group to which this port belongs or default to the main group + ctrl.mGroup = wxEmptyString; + LilvNode *group = lilv_port_get(mPlug, port, gGroup); + if (group) + { + ctrl.mGroup = LilvString(lilv_world_get(gWorld, group, gLabel, NULL)); + if (ctrl.mGroup.IsEmpty()) { - wxMessageBox(_("Sorry, Plug-in Effects cannot be performed on stereo tracks where the individual channels of the track do not match.")); - return false; + ctrl.mGroup = LilvString(lilv_world_get(gWorld, group, gName, NULL)); + } + + if (ctrl.mGroup.IsEmpty()) + { + ctrl.mGroup = LilvString(group); } } - left = iter.Next(); - } - - if (mainRate <= 0) - { - mainRate = (int)(mProjectRate + 0.5); - } - - return true; -} - -bool LV2Effect::PromptUser() -{ - if (mControlInputs.GetCount() > 0) - { - double length = mT1 > mT0 ? mT1 - mT0 : sDefaultGenerateLen; - double noteLength = length / 2; - unsigned char noteVelocity = 64; - unsigned char noteKey = 64; - - LV2EffectDialog dlog(this, mParent, mData, mainRate, length, - noteLength, noteVelocity, noteKey); - dlog.CentreOnParent(); - dlog.ShowModal(); - - if (!dlog.GetReturnCode()) + // Add it if not previously done + if (mGroups.Index(ctrl.mGroup) == wxNOT_FOUND) { - return false; + mGroups.Add(ctrl.mGroup); } - mDuration = dlog.GetLength(); - mNoteLength = dlog.GetNoteLength(); - mNoteVelocity = dlog.GetNoteVelocity(); - mNoteKey = dlog.GetNoteKey(); - } - - return true; -} - -bool LV2Effect::Process() -{ - CopyInputTracks(); - bool bGoodResult = true; - - TrackListIterator iter(mOutputTracks); - int count = 0; - Track *left = iter.First(); - Track *right = NULL; - while (left) - { - sampleCount lstart = 0, rstart = 0; - sampleCount len; - GetSamples((WaveTrack *)left, &lstart, &len); - - right = NULL; - if (left->GetLinked() && mAudioInputs.GetCount() > 1) + // Get the scale points + LilvScalePoints *points = lilv_port_get_scale_points(mPlug, port); + LILV_FOREACH(scale_points, j, points) { - right = iter.Next(); - GetSamples((WaveTrack *)right, &rstart, &len); + const LilvScalePoint *point = lilv_scale_points_get(points, j); + + ctrl.mScaleValues.Add(lilv_node_as_float(lilv_scale_point_get_value(point))); + ctrl.mScaleLabels.Add(LilvString(lilv_scale_point_get_label(point))); } + lilv_scale_points_free(points); - if (mAudioInputs.GetCount() < 2 && right) + // Collect the value and range info + ctrl.mHasLo = !isnan(minimumVals[i]); + ctrl.mHasHi = !isnan(maximumVals[i]); + ctrl.mVal = 0.0; + ctrl.mMin = ctrl.mHasLo ? minimumVals[i] : 0.0; + ctrl.mMax = ctrl.mHasHi ? maximumVals[i] : 1.0; + ctrl.mLo = ctrl.mMin; + ctrl.mHi = ctrl.mMax; + ctrl.mDef = !isnan(defaultValues[i]) ? + defaultValues[i] : + ctrl.mHasLo ? + ctrl.mMin : + ctrl.mHasHi ? + ctrl.mMax : + 0.0; + + // Figure out the type of port we have + if (lilv_port_is_a(mPlug, port, gInput)) { - // If the effect is mono, apply to each channel separately + ctrl.mInput = true; + if (lilv_port_has_property(mPlug, port, gToggled)) + { + ctrl.mToggle = true; + } + else if (lilv_port_has_property(mPlug, port, gEnumeration)) + { + ctrl.mEnumeration = true; + } + else if (lilv_port_has_property(mPlug, port, gInteger)) + { + ctrl.mInteger = true; + } + else if (lilv_port_has_property(mPlug, port, gSampleRate)) + { + ctrl.mSampleRate = true; + } - bGoodResult = ProcessStereo(count, (WaveTrack *)left, NULL, - lstart, 0, len) && - ProcessStereo(count, (WaveTrack *)right, NULL, - rstart, 0, len); + // Trigger properties can be combined with other types, but it + // seems mostly to be combined with toggle. So, we turn the + // checkbox into a button. + if (lilv_port_has_property(mPlug, port, gTrigger)) + { + ctrl.mTrigger = true; + } + + // We'll make the slider logarithmic + if (lilv_port_has_property(mPlug, port, gLogarithmic)) + { + ctrl.mLogarithmic = true; + if (ctrl.mHasLo) + { + ctrl.mLo = logf(ctrl.mLo); + } + + if (ctrl.mHasHi) + { + ctrl.mHi = logf(ctrl.mHi); + } + } + + if (lilv_port_has_property(mPlug, port, gEnumeration)) + { + ctrl.mEnumeration = true; + } + + mControlsMap[ctrl.mIndex] = mControls.GetCount(); + mGroupMap[ctrl.mGroup].Add(mControls.GetCount()); + mControls.Add(ctrl); + } + else if (lilv_port_is_a(mPlug, port, gOutput)) + { + ctrl.mInput = false; + if (lilv_port_has_property(mPlug, port, gLatency)) + { + mLatencyPort = i; + } + else + { + mGroupMap[ctrl.mGroup].Add(mControls.GetCount()); + mControls.Add(ctrl); + } } else { - bGoodResult = ProcessStereo(count, - (WaveTrack *)left, (WaveTrack *)right, - lstart, rstart, len); + // Just ignore it for now } - - if (!bGoodResult) - { - break; - } - - left = iter.Next(); - count++; } - ReplaceProcessedTracks(bGoodResult); + delete [] minimumVals; + delete [] maximumVals; + delete [] defaultValues; - return bGoodResult; + // mHost will be null during registration + if (mHost) + { + mHost->GetSharedConfig(wxT("Settings"), wxT("UseLatency"), mUseLatency, true); + mHost->GetSharedConfig(wxT("Settings"), wxT("UseGUI"), mUseGUI, true); +wxPrintf(wxT("#1 usegui %d\n"), mUseGUI); + + bool haveDefaults; + mHost->GetPrivateConfig(mHost->GetFactoryDefaultsGroup(), wxT("Initialized"), haveDefaults, false); + if (!haveDefaults) + { + SaveParameters(mHost->GetFactoryDefaultsGroup()); + mHost->SetPrivateConfig(mHost->GetFactoryDefaultsGroup(), wxT("Initialized"), true); + } + + LoadParameters(mHost->GetCurrentSettingsGroup()); + } + + AddOption(LV2_BUF_SIZE__minBlockLength, + sizeof(mBlockSize), + LV2_ATOM__Int, + &mBlockSize); + mBlockSizeOption = AddOption(LV2_BUF_SIZE__maxBlockLength, + sizeof(mBlockSize), + LV2_ATOM__Int, + &mBlockSize); + mSampleRateOption = AddOption(LV2_CORE__sampleRate, + sizeof(mSampleRate), + LV2_ATOM__Double, + &mSampleRate); + AddOption(NULL, 0, NULL, NULL); + + mUriMapFeature.callback_data = this; + mUriMapFeature.uri_to_id = LV2Effect::uri_to_id; + + mURIDMapFeature.handle = this; + mURIDMapFeature.map = LV2Effect::urid_map; + + mURIDUnmapFeature.handle = this; + mURIDUnmapFeature.unmap = LV2Effect::urid_unmap; + + mUIResizeFeature.handle = this; + mUIResizeFeature.ui_resize = LV2Effect::ui_resize; + + AddFeature(LV2_UI_PREFIX "makeResident", NULL); + AddFeature(LV2_UI__noUserResize, NULL); + AddFeature(LV2_BUF_SIZE__boundedBlockLength, NULL); + AddFeature(LV2_OPTIONS__options, mOptions); + AddFeature(LV2_URI_MAP_URI, &mUriMapFeature); + AddFeature(LV2_URID__map, &mURIDMapFeature); + AddFeature(LV2_URID__unmap, &mURIDUnmapFeature); + AddFeature(LV2_UI__resize, &mUIResizeFeature); + AddFeature(LV2_DATA_ACCESS_URI, &mExtDataFeature); + mInstanceAccessFeature = AddFeature(LV2_INSTANCE_ACCESS_URI, NULL); + mParentFeature = AddFeature(LV2_UI__parent, NULL); + AddFeature(NULL, NULL); + + return true; } -bool LV2Effect::ProcessStereo(int count, - WaveTrack *left, - WaveTrack *right, - sampleCount lstart, - sampleCount rstart, - sampleCount len) +int LV2Effect::GetAudioInCount() { - /* Allocate buffers */ - if (mBlockSize == 0) - { - mBlockSize = left->GetMaxBlockSize() * 2; + return (int) mAudioInputs.GetCount(); +} - fInBuffer = new float *[mAudioInputs.GetCount()]; - for (size_t i = 0; i < mAudioInputs.GetCount(); i++) +int LV2Effect::GetAudioOutCount() +{ + return (int) mAudioOutputs.GetCount(); +} + +int LV2Effect::GetMidiInCount() +{ + return 0; +} + +int LV2Effect::GetMidiOutCount() +{ + return 0; +} + +void LV2Effect::SetSampleRate(sampleCount rate) +{ + mSampleRate = (double) rate; + + if (mOptionsInterface && mOptionsInterface->set) + { + LV2_Options_Option options[2]; // 2 for empty terminating option + memset(&options, 0, sizeof(options)); + memcpy(&options, mSampleRateOption, sizeof(*mSampleRateOption)); + + if (mMaster) { - fInBuffer[i] = new float[mBlockSize]; + mOptionsInterface->set(lilv_instance_get_handle(mMaster), options); } - fOutBuffer = new float *[mAudioOutputs.GetCount()]; - for (size_t i = 0; i < mAudioOutputs.GetCount(); i++) + for (size_t i = 0, cnt = mSlaves.GetCount(); i < cnt; i++) { - fOutBuffer[i] = new float[mBlockSize]; + mOptionsInterface->set(lilv_instance_get_handle(mSlaves[i]), options); + } + } +} + +sampleCount LV2Effect::SetBlockSize(sampleCount maxBlockSize) +{ + mBlockSize = (int) maxBlockSize; + + if (mOptionsInterface && mOptionsInterface->set) + { + LV2_Options_Option options[2]; // 2 for empty terminating option + memset(&options, 0, sizeof(options)); + memcpy(&options, mBlockSizeOption, sizeof(*mBlockSizeOption)); + + if (mMaster) + { + mOptionsInterface->set(lilv_instance_get_handle(mMaster), options); + } + + for (size_t i = 0, cnt = mSlaves.GetCount(); i < cnt; i++) + { + mOptionsInterface->set(lilv_instance_get_handle(mSlaves[i]), options); } } - /* Instantiate the plugin */ - LilvInstance *handle = lilv_plugin_instantiate(mData, - left->GetRate(), - gLV2Features); - if (!handle) + return mBlockSize; +} + +sampleCount LV2Effect::GetLatency() +{ + if (mUseLatency && mLatencyPort >= 0 && !mLatencyDone) + { + mLatencyDone = true; + return mLatency; + } + + return 0; +} + +sampleCount LV2Effect::GetTailSize() +{ + return 0; +} + +bool LV2Effect::IsReady() +{ + return mMaster != NULL; +} + +bool LV2Effect::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) +{ + mProcess = InitInstance(mSampleRate); + if (!mProcess) { - wxMessageBox(wxString::Format(_("Unable to load plug-in %s"), pluginName.c_str())); return false; } - /* Write the Note On to the MIDI event buffer and connect it */ - LV2_Event_Buffer *midiBuffer = NULL; - int noteOffTime; - if (mMidiInput) - { - midiBuffer = lv2_event_buffer_new(40, 2); - LV2_Event_Iterator iter; - lv2_event_begin(&iter, midiBuffer); - uint8_t noteOn[] = { 0x90, mNoteKey, mNoteVelocity }; - lv2_event_write(&iter, 0, 0, 1, 3, noteOn); - noteOffTime = mNoteLength * left->GetRate(); - if (noteOffTime < len && noteOffTime < mBlockSize) { - uint8_t noteOff[] = { 0x80, mNoteKey, 64 }; - lv2_event_write(&iter, noteOffTime, 0, 1, 3, noteOff); - } - lilv_instance_connect_port(handle, mMidiInput->mIndex, midiBuffer); - } + lilv_instance_activate(mProcess); - for (size_t p = 0; p < mAudioInputs.GetCount(); p++) - { - lilv_instance_connect_port(handle, mAudioInputs[p].mIndex, fInBuffer[p]); - } - - for (size_t p = 0; p < mAudioOutputs.GetCount(); p++) - { - lilv_instance_connect_port(handle, mAudioOutputs[p].mIndex, fOutBuffer[p]); - } - - for (size_t p = 0; p < mControlInputs.GetCount(); p++) - { - lilv_instance_connect_port(handle, mControlInputs[p].mIndex, - &mControlInputs[p].mControlBuffer); - } - - for (size_t p = 0; p < mControlOutputs.GetCount(); p++) - { - lilv_instance_connect_port(handle, mControlOutputs[p].mIndex, - &mControlOutputs[p].mControlBuffer); - } - - float latency = 0.0; - if (mLatencyPortIndex >= 0) - { - lilv_instance_connect_port(handle, mLatencyPortIndex, &latency); - } - - lilv_instance_activate(handle); - - // Actually perform the effect here - - sampleCount originalLen = len; - sampleCount ls = lstart; - sampleCount rs = rstart; - sampleCount ols = ls; - sampleCount ors = rs; - bool noteOver = false; - - sampleCount delayed = 0; - sampleCount delay = 0; - bool cleared = false; - - while (len || delayed) - { - int block = mBlockSize; - - if (len) - { - if (block > len) - { - block = len; - } - - if (left && mAudioInputs.GetCount() > 0) - { - left->Get((samplePtr)fInBuffer[0], floatSample, ls, block); - } - - if (right && mAudioInputs.GetCount() > 1) - { - right->Get((samplePtr)fInBuffer[1], floatSample, rs, block); - } - } - else if (delayed) - { - // At the end if we don't have enough left for a whole block - if (block > delayed) - { - block = delayed; - } - - // Clear the input buffer so that we only pass zeros to the effect. - if (!cleared) - { - for (int i = 0; i < mBlockSize; i++) - { - fInBuffer[0][i] = 0.0; - } - - if (right) - { - memcpy(fInBuffer[1], fOutBuffer[0], mBlockSize); - } - cleared = true; - } - } - - lilv_instance_run(handle, block); - - if (delayed == 0 && latency != 0) - { - delayed = delay = latency; - } - - if (delay >= block) - { - delay -= block; - } - else if (delay > 0) - { - sampleCount oblock = block - delay; - if (left && mAudioOutputs.GetCount() > 0) - { - left->Set((samplePtr)(fOutBuffer[0] + delay), floatSample, ols, oblock); - } - - if (right && mAudioOutputs.GetCount() > 1) - { - right->Set((samplePtr)(fOutBuffer[1] + delay), floatSample, ors, oblock); - } - ols += oblock; - ors += oblock; - delay = 0; - } - else - { - if (left && mAudioOutputs.GetCount() > 0) - { - left->Set((samplePtr)fOutBuffer[0], floatSample, ols, block); - } - - if (right && mAudioOutputs.GetCount() > 1) - { - right->Set((samplePtr)fOutBuffer[1], floatSample, ors, block); - } - ols += block; - ors += block; - } - - if (len) - { - len -= block; - noteOffTime -= block; - - // Clear the event buffer and add the note off event if needed - if (mMidiInput) - { - lv2_event_buffer_reset(midiBuffer, 1, - (uint8_t *)midiBuffer + - sizeof(LV2_Event_Buffer)); - - if (!noteOver && noteOffTime < len && noteOffTime < block) - { - LV2_Event_Iterator iter; - lv2_event_begin(&iter, midiBuffer); - uint8_t noteOff[] = { 0x80, mNoteKey, 64 }; - lv2_event_write(&iter, noteOffTime, 0, 1, 3, noteOff); - noteOver = true; - } - } - } - else if (delayed) - { - delayed -= block; - } - ls += block; - rs += block; - - if (mAudioInputs.GetCount() > 1) - { - if (TrackGroupProgress(count, (ls-lstart)/(double)originalLen)) - { - return false; - } - } - else - { - if (TrackProgress(count, (ls-lstart)/(double)originalLen)) - { - return false; - } - } - - } - - lilv_instance_deactivate(handle); - lilv_instance_free(handle); + mLatencyDone = false; return true; } -void LV2Effect::End() +bool LV2Effect::ProcessFinalize() { - unsigned long i; - - if (fInBuffer) + if (mProcess) { - for (i = 0; i < mAudioInputs.GetCount(); i++) - { - if (fInBuffer[i]) - { - delete [] fInBuffer[i]; - } - } - delete [] fInBuffer; - fInBuffer = NULL; + lilv_instance_deactivate(mProcess); + + FreeInstance(mProcess); + mProcess = NULL; } - if (fOutBuffer) + return true; +} + +sampleCount LV2Effect::ProcessBlock(float **inbuf, float **outbuf, sampleCount size) +{ + for (size_t p = 0, cnt = mAudioInputs.GetCount(); p < cnt; p++) { - for (i = 0; i < mAudioOutputs.GetCount(); i++) + lilv_instance_connect_port(mProcess, mAudioInputs[p], inbuf[p]); + } + + for (size_t p = 0, cnt = mAudioOutputs.GetCount(); p < cnt; p++) + { + lilv_instance_connect_port(mProcess, mAudioOutputs[p], outbuf[p]); + } + + lilv_instance_run(mProcess, size); + + return size; +} + +bool LV2Effect::RealtimeInitialize() +{ + mMasterIn = new float *[mAudioInputs.GetCount()]; + for (size_t p = 0, cnt = mAudioInputs.GetCount(); p < cnt; p++) + { + mMasterIn[p] = new float[mBlockSize]; + lilv_instance_connect_port(mMaster, mAudioInputs[p], mMasterIn[p]); + } + + mMasterOut = new float *[mAudioOutputs.GetCount()]; + for (size_t p = 0, cnt = mAudioOutputs.GetCount(); p < cnt; p++) + { + mMasterOut[p] = new float[mBlockSize]; + lilv_instance_connect_port(mMaster, mAudioOutputs[p], mMasterOut[p]); + } + + lilv_instance_activate(mMaster); + + return true; +} + +bool LV2Effect::RealtimeFinalize() +{ + for (size_t i = 0, cnt = mSlaves.GetCount(); i < cnt; i++) + { + lilv_instance_deactivate(mSlaves[i]); + + FreeInstance(mSlaves[i]); + } + mSlaves.Clear(); + + lilv_instance_deactivate(mMaster); + + for (size_t p = 0, cnt = mAudioInputs.GetCount(); p < cnt; p++) + { + delete [] mMasterIn[p]; + } + delete [] mMasterIn; + + for (size_t p = 0, cnt = mAudioOutputs.GetCount(); p < cnt; p++) + { + delete [] mMasterOut[p]; + } + delete [] mMasterOut; + + return true; +} + +bool LV2Effect::RealtimeSuspend() +{ + return true; +} + +bool LV2Effect::RealtimeResume() +{ + return true; +} + +bool LV2Effect::RealtimeProcessStart() +{ + for (size_t p = 0, cnt = mAudioInputs.GetCount(); p < cnt; p++) + { + memset(mMasterIn[p], 0, mBlockSize * sizeof(float)); + } + + mNumSamples = 0; + + return true; +} + +sampleCount LV2Effect::RealtimeProcess(int group, + float **inbuf, + float **outbuf, + sampleCount numSamples) +{ + wxASSERT(group >= 0 && group < (int) mSlaves.GetCount()); + wxASSERT(numSamples <= mBlockSize); + + if (group < 0 || group >= (int) mSlaves.GetCount()) + { + return 0; + } + + for (size_t p = 0, cnt = mAudioInputs.GetCount(); p < cnt; p++) + { + for (sampleCount s = 0; s < numSamples; s++) { - if (fOutBuffer[i]) + mMasterIn[p][s] += inbuf[p][s]; + } + } + mNumSamples = wxMax(numSamples, mNumSamples); + + LilvInstance *slave = mSlaves[group]; + + for (size_t p = 0, cnt = mAudioInputs.GetCount(); p < cnt; p++) + { + lilv_instance_connect_port(slave, mAudioInputs[p], inbuf[p]); + } + + for (size_t p = 0, cnt = mAudioOutputs.GetCount(); p < cnt; p++) + { + lilv_instance_connect_port(slave, mAudioOutputs[p], outbuf[p]); + } + + lilv_instance_run(slave, numSamples); + + return numSamples; +} + +bool LV2Effect::RealtimeAddProcessor(int WXUNUSED(numChannels), float sampleRate) +{ + LilvInstance *slave = InitInstance(sampleRate); + if (!slave) + { + return false; + } + + lilv_instance_activate(slave); + + mSlaves.Add(slave); + + return true; +} + +bool LV2Effect::RealtimeProcessEnd() +{ + lilv_instance_run(mMaster, mNumSamples); + + return true; +} + +bool LV2Effect::ShowInterface(wxWindow *parent, bool forceModal) +{ + if (mDialog) + { + mDialog->Close(true); + return false; + } + + mDialog = mHost->CreateUI(parent, this); + if (!mDialog) + { + return false; + } + + // Try to give the window a sensible default/minimum size + mDialog->Layout(); + mDialog->Fit(); + mDialog->SetMinSize(mDialog->GetSize()); + + if ((SupportsRealtime() || GetType() == EffectTypeAnalyze) && !forceModal) + { + mDialog->Show(); + + return false; + } + + bool res = mDialog->ShowModal() != 0; + mDialog = NULL; + + return res; +} + +bool LV2Effect::GetAutomationParameters(EffectAutomationParameters & parms) +{ + for (size_t p = 0, cnt = mControls.GetCount(); p < cnt; p++) + { + if (mControls[p].mInput) + { + if (!parms.Write(mControls[p].mName, mControls[p].mVal)) { - delete [] fOutBuffer[i]; + return false; } } - delete [] fOutBuffer; - fOutBuffer = NULL; + } + + return true; +} + +bool LV2Effect::SetAutomationParameters(EffectAutomationParameters & parms) +{ + for (size_t p = 0, cnt = mControls.GetCount(); p < cnt; p++) + { + if (mControls[p].mInput) + { + double d = 0.0; + if (!parms.Read(mControls[p].mName, &d)) + { + return false; + } + + mControls[p].mVal = d; + } + } + + return true; +} + +// ============================================================================ +// EffectUIClientInterface Implementation +// ============================================================================ + +void LV2Effect::SetHostUI(EffectUIHostInterface *host) +{ + mUIHost = host; +} + +bool LV2Effect::PopulateUI(wxWindow *parent) +{ + mParent = parent; + + mParent->PushEventHandler(this); + + mSliders = NULL; + mFields = NULL; + mSuilHost = NULL; + mSuilInstance = NULL; + + mMaster = InitInstance(mSampleRate); + if (mMaster == NULL) + { + wxMessageBox(wxT("Couldn't instantiate effect")); + return false; + } + + // Determine if the GUI editor is supposed to be used or not + mHost->GetSharedConfig(wxT("Settings"), + wxT("UseGUI"), + mUseGUI, + true); +wxPrintf(wxT("#2 usegui %d\n"), mUseGUI); + + if (mUseGUI) + { + mUseGUI = BuildFancy(); + } + + if (!mUseGUI) + { + return BuildPlain(); + } + + return true; +} + +bool LV2Effect::IsGraphicalUI() +{ + return mUseGUI; +} + +bool LV2Effect::ValidateUI() +{ + if (!mParent->Validate() || !mParent->TransferDataFromWindow()) + { + return false; + } + + if (GetType() == EffectTypeGenerate) + { + mHost->SetDuration(/* ... */ 0.0 /* ... */ ); + } + + return true; +} + +bool LV2Effect::HideUI() +{ + return true; +} + +bool LV2Effect::CloseUI() +{ + mParent->RemoveEventHandler(this); + + if (mSliders) + { + delete [] mSliders; + mSliders = NULL; + } + + if (mFields) + { + delete [] mFields; + mFields = NULL; + } + + if (mSuilInstance) + { + mIdleFeature = NULL; + suil_instance_free(mSuilInstance); + mSuilInstance = NULL; + } + + if (mSuilHost) + { + suil_host_free(mSuilHost); + mSuilHost = NULL; + } + + if (mMaster) + { + FreeInstance(mMaster); + mMaster = NULL; + } + + mUIHost = NULL; + mParent = NULL; + mDialog = NULL; + + return true; +} + +bool LV2Effect::LoadUserPreset(const wxString & name) +{ + return LoadParameters(name); +} + +bool LV2Effect::SaveUserPreset(const wxString & name) +{ + return SaveParameters(name); +} + +wxArrayString LV2Effect::GetFactoryPresets() +{ + return wxArrayString(); +} + +bool LV2Effect::LoadFactoryPreset(int WXUNUSED(id)) +{ + return true; +} + +bool LV2Effect::LoadFactoryDefaults() +{ + return LoadParameters(mHost->GetFactoryDefaultsGroup()); +} + +bool LV2Effect::CanExportPresets() +{ + return true; +} + +void LV2Effect::ExportPresets() +{ +} + +void LV2Effect::ImportPresets() +{ +} + +bool LV2Effect::HasOptions() +{ + return true; +} + +void LV2Effect::ShowOptions() +{ + LV2EffectSettingsDialog dlg(mParent, this); + if (dlg.ShowModal() == wxID_OK) + { + mHost->GetSharedConfig(wxT("Settings"), wxT("UseLatency"), mUseLatency, true); } } @@ -810,514 +1187,480 @@ void LV2Effect::End() // LV2Effect Implementation // ============================================================================ -bool LV2Effect::IsValid() +bool LV2Effect::LoadParameters(const wxString & group) { - return mValid; -} + wxString value; - -LV2PortArray & LV2Effect::GetControls() -{ - return mControlInputs; -} - - -bool LV2Effect::IsSynth() -{ - return (mMidiInput != 0); -} - - -bool LV2Effect::SetNote(sampleCount len, - unsigned char velocity, unsigned char key) -{ - if (velocity == 0 || velocity > 127 || key > 127) + if (!mHost->GetPrivateConfig(group, wxT("Value"), value, wxEmptyString)) { return false; } - mNoteLength = len; - mNoteVelocity = velocity; - mNoteKey = key; + wxStringTokenizer st(value, wxT(',')); + for (size_t p = 0; st.HasMoreTokens(); p++) + { + double val = 0.0; + st.GetNextToken().ToDouble(&val); + mControls[p].mVal = (float) val; + } return true; } -const LV2PortGroup & LV2Effect::GetRootGroup() +bool LV2Effect::SaveParameters(const wxString & group) { - return mRootGroup; -} + wxString parms; -wxString LV2Effect::GetString(const LilvNode *node) -{ - return wxString::FromUTF8(lilv_node_as_string(node)); -} - -wxString LV2Effect::GetString(LilvNode *node, bool free) -{ - wxString str = GetString(node); - if (free) + for (size_t i = 0, cnt = mControls.GetCount(); i < cnt; i++) { - lilv_node_free(node); + parms += wxString::Format(wxT(",%f"), mControls[i].mVal); } - return str; + return mHost->SetPrivateConfig(group, wxT("Value"), parms.Mid(1)); } -// This should be moved to its own source file, it's in LadspaEffect.cpp -// as well -class LV2Slider:public wxSlider +LV2_Options_Option *LV2Effect::AddOption(const char *key, uint32_t size, const char *type, void *value) { -public: - LV2Slider(wxWindow *parent, wxWindowID id, - int value, int minValue, int maxValue, - const wxPoint & pos = wxDefaultPosition, - const wxSize & size = wxDefaultSize, - long style = wxSL_HORIZONTAL, - const wxValidator & validator = wxDefaultValidator, - const wxString & name = wxSliderNameStr) - : wxSlider(parent, id, value, minValue, maxValue, - pos, size, style, validator, name) + int ndx = mNumOptions; + + mNumOptions += 1; + mOptions = (LV2_Options_Option *) realloc(mOptions, mNumOptions * sizeof(LV2_Options_Option)); + memset(&mOptions[ndx], 0, sizeof(mOptions[ndx])); + + if (key != NULL) { - }; + mOptions[ndx].context = LV2_OPTIONS_INSTANCE; + mOptions[ndx].subject = 0; + mOptions[ndx].key = URID_Map(key); + mOptions[ndx].size = size; + mOptions[ndx].type = URID_Map(type); + mOptions[ndx].value = value; + } - void OnSetFocus(wxFocusEvent & event) - { - wxScrolledWindow *p = (wxScrolledWindow *) GetParent(); - wxRect r = GetRect(); - wxRect rv = p->GetRect(); - rv.y = 0; + return &mOptions[ndx]; +} - event.Skip(); - - int y; - int yppu; - p->GetScrollPixelsPerUnit(NULL, &yppu); - - if (r.y >= rv.y && r.GetBottom() <= rv.GetBottom()) - { - return; - } - - if (r.y < rv.y) - { - p->CalcUnscrolledPosition(0, r.y, NULL, &r.y); - y = r.y / yppu; - } - else - { - p->CalcUnscrolledPosition(0, r.y, NULL, &r.y); - y = (r.GetBottom() - rv.GetBottom() + yppu) / yppu; - } - - p->Scroll(-1, y); - }; - -private: - DECLARE_EVENT_TABLE(); -}; - -BEGIN_EVENT_TABLE(LV2Slider, wxSlider) - EVT_SET_FOCUS(LV2Slider::OnSetFocus) -END_EVENT_TABLE() - -class LV2TextCtrl:public wxTextCtrl +LV2_Feature *LV2Effect::AddFeature(const char *uri, void *data) { -public: - LV2TextCtrl(wxWindow *parent, wxWindowID id, - const wxString & value = wxEmptyString, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize, - long style = 0, - const wxValidator & validator = wxDefaultValidator, - const wxString & name = wxTextCtrlNameStr) - : wxTextCtrl(parent, id, value, - pos, size, style, validator, name) + int ndx = mNumFeatures; + + mNumFeatures += 1; + mFeatures = (LV2_Feature **) realloc(mFeatures, mNumFeatures * sizeof(LV2_Feature *)); + mFeatures[ndx] = NULL; + + if (uri != NULL) { - }; + mFeatures[ndx] = new LV2_Feature; + mFeatures[ndx]->URI = uri; + mFeatures[ndx]->data = data; + } - void OnSetFocus(wxFocusEvent & event) - { - wxScrolledWindow *p = (wxScrolledWindow *) GetParent(); - wxRect r = GetRect(); - wxRect rv = p->GetRect(); - rv.y = 0; + return mFeatures[ndx]; +} - event.Skip(); - - int y; - int yppu; - p->GetScrollPixelsPerUnit(NULL, &yppu); - - if (r.y >= rv.y && r.GetBottom() <= rv.GetBottom()) - { - return; - } - - if (r.y < rv.y) - { - p->CalcUnscrolledPosition(0, r.y, NULL, &r.y); - y = r.y / yppu; - } - else - { - p->CalcUnscrolledPosition(0, r.y, NULL, &r.y); - y = (r.GetBottom() - rv.GetBottom() + yppu) / yppu; - } - - p->Scroll(-1, y); - }; - -private: - DECLARE_EVENT_TABLE(); -}; - -BEGIN_EVENT_TABLE(LV2TextCtrl, wxTextCtrl) - EVT_SET_FOCUS(LV2TextCtrl::OnSetFocus) -END_EVENT_TABLE() - -static const int LADSPA_SECONDS_ID = 13101; - -BEGIN_EVENT_TABLE(LV2EffectDialog, wxDialog) - EVT_BUTTON(wxID_OK, LV2EffectDialog::OnOK) - EVT_BUTTON(wxID_CANCEL, LV2EffectDialog::OnCancel) - EVT_BUTTON(ID_EFFECT_PREVIEW, LV2EffectDialog::OnPreview) - EVT_SLIDER(wxID_ANY, LV2EffectDialog::OnSlider) - EVT_TEXT(wxID_ANY, LV2EffectDialog::OnTextCtrl) - EVT_CHECKBOX(wxID_ANY, LV2EffectDialog::OnCheckBox) - EVT_CHOICE(wxID_ANY, LV2EffectDialog::OnChoiceCtrl) -END_EVENT_TABLE() - -IMPLEMENT_CLASS(LV2EffectDialog, wxDialog) - -LV2EffectDialog::LV2EffectDialog(LV2Effect *effect, - wxWindow *parent, - const LilvPlugin *data, - int sampleRate, - double length, - double WXUNUSED(noteLength), - unsigned char WXUNUSED(noteVelocity), - unsigned char WXUNUSED(noteKey)) -: wxDialog(parent, wxID_ANY, - mEffect->GetString(lilv_plugin_get_name(data)), - wxDefaultPosition, wxSize(500, -1), - wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), - mEffect(effect), - mData(data), - mControls(effect->GetControls()), - mSampleRate(sampleRate), - mLength(length) +LilvInstance *LV2Effect::InitInstance(float sampleRate) { + LilvInstance *handle = lilv_plugin_instantiate(mPlug, + sampleRate, + mFeatures); + if (!handle) + { + return NULL; + } -#if defined(__WXMSW__) || (defined(__WXGTK__) && wxCHECK_VERSION(3, 0, 0)) - // In some environments wxWindows calls OnTextCtrl during creation - // of the text control, and LV2EffectDialog::OnTextCtrl calls HandleText, - // which assumes all the fields have been initialized. - // This can give us a bad pointer crash, so manipulate inSlider to - // no-op HandleText during creation. - inSlider = true; -#else - inSlider = false; + mOptionsInterface = (LV2_Options_Interface *) + lilv_instance_get_extension_data(handle, LV2_OPTIONS__interface); + + SetBlockSize(mBlockSize); + SetSampleRate(sampleRate); + + for (size_t p = 0, cnt = mControls.GetCount(); p < cnt; p++) + { + lilv_instance_connect_port(handle, + mControls[p].mIndex, + &mControls[p].mVal); + } + + if (mLatencyPort >= 0) + { + lilv_instance_connect_port(handle, mLatencyPort, &mLatency); + } + + return handle; +} + +void LV2Effect::FreeInstance(LilvInstance *handle) +{ + lilv_instance_free(handle); +} + +bool LV2Effect::BuildFancy() +{ + // Set the native UI type + const char *nativeType = +#if defined(__WXGTK__) + LV2_UI__GtkUI; +#elif defined(__WXMSW__) + LV2_UI__WindowsUI; +#elif defined(__WXMAC__) + LV2_UI__CocoaUI; #endif - inText = false; - inText = true; + + // Determine if the plugin has a supported UI + const LilvUI *ui = NULL; + const LilvNode *uiType = NULL; + LilvUIs *uis = lilv_plugin_get_uis(mPlug); + if (uis) + { + LilvNode *containerType = lilv_new_uri(gWorld, nativeType); + if (containerType) + { + LILV_FOREACH(uis, iter, uis) + { + ui = lilv_uis_get(uis, iter); + if (lilv_ui_is_supported(ui, suil_ui_supported, containerType, &uiType)) + { + break; + } + ui = NULL; + } + + lilv_node_free(containerType); + } + } + + // No usable UI found + if (ui == NULL) + { + lilv_uis_free(uis); + return false; + } + + // Use a panel to host the plugins GUI + mContainer = new wxPanel(mParent, wxID_ANY); + if (!mContainer) + { + lilv_uis_free(uis); + return false; + } + + wxBoxSizer *vs = new wxBoxSizer(wxVERTICAL); + wxSizerItem *si = NULL; + if (vs) + { + wxBoxSizer *hs = new wxBoxSizer(wxHORIZONTAL); + if (hs) + { + si = hs->Add(mContainer, 1, wxCENTER | wxEXPAND); + vs->Add(hs, 0, wxCENTER); + } + } + + if (!si) + { + delete vs; + delete mContainer; + lilv_uis_free(uis); + return false; + } + +// wxBoxSizer *hs = new wxBoxSizer(wxVERTICAL); +// vs->Add(mContainer, 0, wxALIGN_CENTER); +// mParent->SetSizer(vs); +//wxWindow *mContainer = mParent; +#if defined(__WXGTK__) + // Make sure the parent has a window + if (!GTK_WIDGET(mContainer->m_wxwindow)->window) + { + gtk_widget_realize(GTK_WIDGET(mContainer->m_wxwindow)); + } + + mParentFeature->data = GTK_WIDGET(mContainer->GetHandle()); +#elif defined(__WXMSW__) + mParentFeature->data = mContainer->GetHandle(); +#elif defined(__WXMAC__) +#endif + + mInstanceAccessFeature->data = lilv_instance_get_handle(mMaster); + mExtDataFeature.data_access = lilv_instance_get_descriptor(mMaster)->extension_data; + + // Create the suil host + mSuilHost = suil_host_new(LV2Effect::suil_write_func, NULL, NULL, NULL); + if (!mSuilHost) + { + delete vs; + delete mContainer; + lilv_uis_free(uis); + return false; + } + + mSuilInstance = suil_instance_new(mSuilHost, + this, + nativeType, + lilv_node_as_uri(lilv_plugin_get_uri(mPlug)), + lilv_node_as_uri(lilv_ui_get_uri(ui)), + lilv_node_as_uri(uiType), + lilv_uri_to_path(lilv_node_as_uri(lilv_ui_get_bundle_uri(ui))), + lilv_uri_to_path(lilv_node_as_uri(lilv_ui_get_binary_uri(ui))), + mFeatures); + + lilv_uis_free(uis); + + // Bail if the instance (no compatible UI) couldn't be created + if (!mSuilInstance) + { + suil_host_free(mSuilHost); + mSuilHost = NULL; + + delete vs; + delete mContainer; + return false; + } + +#if defined(__WXGTK__) + GtkWidget* widget = GTK_WIDGET(suil_instance_get_widget(mSuilInstance)); + gtk_widget_show_all(widget); + + GtkRequisition sz; + gtk_widget_size_request(widget, &sz); + gtk_widget_set_size_request(widget, 1, 1); + gtk_widget_set_size_request(widget, sz.width, sz.height); + + GtkPizza *pizza = GTK_PIZZA(mContainer->m_wxwindow); + gtk_pizza_put(pizza, + widget, + 0, //gtk_pizza_get_xoffset(pizza), + 0, //gtk_pizza_get_yoffset(pizza), + sz.width, + sz.height); + gtk_widget_show_all(GTK_WIDGET(pizza)); + si->SetMinSize(wxSize(sz.width, sz.height)); +#elif defined(__WXMSW__) + HWND widget = (HWND) suil_instance_get_widget(mSuilInstance); + RECT rect; + GetWindowRect(widget, &rect); + si->SetMinSize(wxSize(rect.right - rect.left, rect.bottom - rect.top)); +#elif defined(__WXMAC__) +// si->SetMinSize(wxSize(sz.width, sz.height)); +#endif + + mParent->SetSizerAndFit(vs); + + mIdleFeature = (const LV2UI_Idle_Interface *) + suil_instance_extension_data(mSuilInstance, LV2_UI__idleInterface); + + UIRefresh(); + + return true; +} + +bool LV2Effect::BuildPlain() +{ + int numCols = 5; // Allocate memory for the user parameter controls int ctrlcnt = (int) mControls.GetCount(); - mToggles = new wxCheckBox*[ctrlcnt]; - mSliders = new wxSlider*[ctrlcnt]; - mFields = new wxTextCtrl*[ctrlcnt]; - mLabels = new wxStaticText*[ctrlcnt]; - mEnums = new wxChoice*[ctrlcnt]; + mSliders = new wxSlider *[ctrlcnt]; + mFields = new wxTextCtrl *[ctrlcnt]; - wxControl *item; - - wxBoxSizer *vSizer = new wxBoxSizer(wxVERTICAL); - - // Add information about the plugin - LilvNode *tmpValue = lilv_plugin_get_author_name(data); - if (tmpValue) - { - wxString author(_("Author: ") + mEffect->GetString(tmpValue)); - item = new wxStaticText(this, wxID_ANY, author); - vSizer->Add(item, 0, wxALL, 5); - lilv_node_free(tmpValue); - } - - wxScrolledWindow *w = new wxScrolledWindow(this, + wxSizer *outerSizer = new wxBoxSizer(wxVERTICAL); + wxScrolledWindow *w = new wxScrolledWindow(mParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxTAB_TRAVERSAL); - - // Try to give the window a sensible default/minimum size - w->SetMinSize(wxSize( - wxMax(600, parent->GetSize().GetWidth() * 2/3), - parent->GetSize().GetHeight() / 2)); - w->SetScrollRate(0, 20); - vSizer->Add(w, 1, wxEXPAND|wxALL, 5); + outerSizer->Add(w, 1, wxEXPAND); - // Preview, OK, & Cancel buttons - vSizer->Add(CreateStdButtonSizer(this, ePreviewButton|eCancelButton|eOkButton), 0, wxEXPAND); + wxSizer *innerSizer = new wxBoxSizer(wxVERTICAL); - SetSizer(vSizer); - - wxSizer *paramSizer = - new wxStaticBoxSizer(wxVERTICAL, w, _("Effect Settings")); - - wxFlexGridSizer *gridSizer = - new wxFlexGridSizer(5, 0, 0); - gridSizer->AddGrowableCol(3); - - // Now add the length control - if (mEffect->GetEffectFlags() & INSERT_EFFECT) + if (GetType() == EffectTypeGenerate) { - item = new wxStaticText(w, 0, _("Length (seconds)")); - gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - mSeconds = new wxTextCtrl(w, LADSPA_SECONDS_ID, Internat::ToDisplayString(length)); - mSeconds->SetName(_("Length (seconds)")); - gridSizer->Add(mSeconds, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - gridSizer->Add(1, 1, 0); - gridSizer->Add(1, 1, 0); - gridSizer->Add(1, 1, 0); - ConnectFocus(mSeconds); + // Add the length control + wxSizer *groupSizer = new wxStaticBoxSizer(wxVERTICAL, w, _("Generator")); + + wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); + + wxWindow *item = new wxStaticText(w, 0, _("&Duration:")); + sizer->Add(item, 0, wxALIGN_CENTER | wxALL, 5); + mDuration = new NumericTextCtrl(NumericConverter::TIME, + w, + ID_Duration, + _("hh:mm:ss + milliseconds"), + mHost->GetDuration(), + mSampleRate, + wxDefaultPosition, + wxDefaultSize, + true); + mDuration->SetName(_("Duration")); + mDuration->EnableMenu(); + sizer->Add(mDuration, 0, wxALIGN_CENTER | wxALL, 5); + + groupSizer->Add(sizer, 0, wxALIGN_CENTER | wxALL, 5); + innerSizer->Add(groupSizer, 0, wxEXPAND | wxALL, 5); } - // The note controls if the plugin is a synth - if (mEffect->IsSynth()) + mGroups.Sort(); + + for (size_t i = 0, cnt = mGroups.GetCount(); i < cnt; i++) { - // Note length control - item = new wxStaticText(w, wxID_ANY, _("Note length (seconds)")); - gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); - mNoteSeconds = new wxTextCtrl(w, LADSPA_SECONDS_ID, Internat::ToDisplayString(length / 2)); - mNoteSeconds->SetName(_("Note length (seconds)")); - gridSizer->Add(mNoteSeconds, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - gridSizer->Add(1, 1, 0); - gridSizer->Add(1, 1, 0); - gridSizer->Add(1, 1, 0); - ConnectFocus(mNoteSeconds); - - // Note velocity control - item = new wxStaticText(w, wxID_ANY, _("Note velocity")); - gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); - mNoteVelocity = new wxTextCtrl(w, LADSPA_SECONDS_ID, Internat::ToDisplayString(64)); - mNoteVelocity->SetName(_("Note velocity")); - gridSizer->Add(mNoteVelocity, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - gridSizer->Add(1, 1, 0); - gridSizer->Add(1, 1, 0); - gridSizer->Add(1, 1, 0); - ConnectFocus(mNoteVelocity); - - // Note key control - item = new wxStaticText(w, wxID_ANY, _("Note key")); - gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); - mNoteKey = new wxTextCtrl(w, LADSPA_SECONDS_ID, Internat::ToDisplayString(64)); - mNoteKey->SetName(_("Note key")); - gridSizer->Add(mNoteKey, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - gridSizer->Add(1, 1, 0); - gridSizer->Add(1, 1, 0); - gridSizer->Add(1, 1, 0); - ConnectFocus(mNoteKey); - } - - paramSizer->Add(gridSizer, 1, wxEXPAND | wxALL, 5); - - // Create user parameter controls - std::queue groups; - groups.push(&mEffect->GetRootGroup()); - - while (!groups.empty()) - { - const LV2PortGroup* pg = groups.front(); - groups.pop(); - - if (pg->GetName() != wxEmptyString) + wxString label = mGroups[i]; + if (label.IsEmpty()) { - wxSizer *groupSizer = - new wxStaticBoxSizer(wxVERTICAL, w, pg->GetName()); - paramSizer->Add(groupSizer, 0, wxEXPAND | wxALL, 5); - gridSizer = new wxFlexGridSizer(5, 0, 0); - gridSizer->AddGrowableCol(3); - groupSizer->Add(gridSizer, 1, wxEXPAND | wxALL, 5); + label = _("Effect Settings"); } + wxSizer *groupSizer = new wxStaticBoxSizer(wxVERTICAL, w, label); - const LV2PortGroupArray & subgroups = pg->GetSubGroups(); - LV2PortGroupArray::const_iterator si; - for (si = subgroups.begin(); si != subgroups.end(); si++) - { - if ((*si)->GetParameters().GetCount() != 0) - { - groups.push(*si); - } - } + wxFlexGridSizer *gridSizer = new wxFlexGridSizer(numCols, 5, 5); + gridSizer->AddGrowableCol(3); - const wxArrayInt & params = pg->GetParameters(); - for (size_t pi = 0; pi < params.GetCount(); pi++) + const wxArrayInt & params = mGroupMap[mGroups[i]]; + for (size_t pi = 0, cnt = params.GetCount(); pi < cnt; pi++) { int p = params[pi]; - if (p >= ctrlcnt) + LV2Port & ctrl = mControls[p]; + wxString labelText = ctrl.mName; + if (!ctrl.mUnits.IsEmpty()) { + labelText += wxT(" (") + ctrl.mUnits + wxT(")"); + } + + if (ctrl.mTrigger) + { + gridSizer->Add(1, 1, 0); + + wxButton *b = new wxButton(w, ID_Triggers + p, labelText); + gridSizer->Add(b, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + + gridSizer->Add(1, 1, 0); + gridSizer->Add(1, 1, 0); + gridSizer->Add(1, 1, 0); continue; } - LV2Port & port = mControls[p]; - wxString labelText = port.mName; - item = new wxStaticText(w, 0, labelText + wxT(":")); - gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); + wxWindow *item = new wxStaticText(w, wxID_ANY, labelText + wxT(":"), + wxDefaultPosition, wxDefaultSize, + wxALIGN_RIGHT); + gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT); - wxString fieldText; - - if (port.mToggle) + if (ctrl.mToggle) { - mToggles[p] = new wxCheckBox(w, p, wxT("")); - mToggles[p]->SetName(labelText); - mToggles[p]->SetValue(port.mControlBuffer > 0); - gridSizer->Add(mToggles[p], 0, wxALL, 5); - ConnectFocus(mToggles[p]); + wxCheckBox *c = new wxCheckBox(w, ID_Toggles + p, wxT("")); + c->SetName(labelText); + c->SetValue(ctrl.mVal > 0); + gridSizer->Add(c, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); gridSizer->Add(1, 1, 0); gridSizer->Add(1, 1, 0); gridSizer->Add(1, 1, 0); } - else if (port.mEnumeration) + else if (ctrl.mEnumeration) // Check before integer { - mEnums[p] = new wxChoice(w, p); - mEnums[p]->SetName(labelText); - mEnums[p]->Append(port.mScaleLabels); - int s; - for (s = (int) port.mScaleValues.GetCount() - 1; s >= 0; s--) + size_t s; + for (s = ctrl.mScaleValues.GetCount() - 1; s >= 0; s--) { - if (port.mControlBuffer >= port.mScaleValues[s]) + if (ctrl.mVal >= ctrl.mScaleValues[s]) { break; } } + if (s < 0) { s = 0; } - mEnums[p]->SetSelection(s); - gridSizer->Add(mEnums[p], 0, wxALL | wxEXPAND, 5); - ConnectFocus(mEnums[p]); + + wxChoice *c = new wxChoice(w, ID_Choices + p); + c->SetName(labelText); + c->Append(ctrl.mScaleLabels); + c->SetSelection(s); + gridSizer->Add(c, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); gridSizer->Add(1, 1, 0); gridSizer->Add(1, 1, 0); gridSizer->Add(1, 1, 0); } + else if (!ctrl.mInput) + { + gridSizer->Add(1, 1, 0); + gridSizer->Add(1, 1, 0); + LV2EffectMeter *m = new LV2EffectMeter(w, ctrl); + gridSizer->Add(m, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND); + gridSizer->Add(1, 1, 0); + } else { - if (port.mInteger) + mFields[p] = new wxTextCtrl(w, ID_Texts + p, wxT("")); + mFields[p]->SetName(labelText); + gridSizer->Add(mFields[p], 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + + float rate = ctrl.mSampleRate ? mSampleRate : 1.0; + float lower = ctrl.mMin * rate; + float upper = ctrl.mMax * rate; + + ctrl.mVal = ctrl.mDef; + ctrl.mTmp = ctrl.mDef * rate; + + if (ctrl.mInteger) { - fieldText.Printf(wxT("%d"), (int)(port.mControlBuffer + 0.5)); + IntegerValidator vld(&ctrl.mTmp); + vld.SetRange(lower, upper); + mFields[p]->SetValidator(vld); } else { - fieldText = Internat::ToDisplayString(port.mControlBuffer); + FloatingPointValidator vld(6, &ctrl.mTmp); + vld.SetRange(lower, upper); + + // Set number of decimal places + float range = ctrl.mMax - ctrl.mMin; + int style = range < 10 ? NUM_VAL_THREE_TRAILING_ZEROES : + range < 100 ? NUM_VAL_TWO_TRAILING_ZEROES : + NUM_VAL_ONE_TRAILING_ZERO; + vld.SetStyle(style); + + mFields[p]->SetValidator(vld); } - mFields[p] = new wxTextCtrl(w, p, fieldText); - mFields[p]->SetName(labelText); - gridSizer->Add(mFields[p], 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - ConnectFocus(mFields[p]); - - wxString bound; - double lower = 0.0; - double upper = 0.0; - bool haslo = false; - bool hashi = false; - bool forceint = false; - wxString loLabel; - wxString hiLabel; -#if 0 - ScalePointMap::const_iterator iter = - scalePoints.find(port.mIndex); - - if (!wxNaN(port.mMin)) + if (ctrl.mHasLo) { - lower = port.mMin; - haslo = true; - if (iter != scalePoints.end()) + wxString str; + if (ctrl.mInteger || ctrl.mSampleRate) { - std::map::const_iterator iter2 = - iter->second.find(lower); - if (iter2 != iter->second.end()) - { - loLabel = iter2->second; - } + str.Printf(wxT("%d"), (int)(ctrl.mMin * rate + 0.5)); } - } - - if (!isnan(port.mMax)) - { - upper = port.mMax; - hashi = true; - if (iter != scalePoints.end()) + else { - std::map::const_iterator iter2 = - iter->second.find(upper); - if (iter2 != iter->second.end()) - { - hiLabel = iter2->second; - } - } - } -#endif - if (port.mSampleRate) - { - lower *= mSampleRate * 1000; - upper *= mSampleRate; - forceint = true; - } - - wxString str; - if (haslo) - { - str = loLabel; - if (str.IsEmpty()) - { - if (port.mInteger || forceint) - { - str.Printf(wxT("%d"), (int)(lower + 0.5)); - } - else - { - str = Internat::ToDisplayString(lower); - } + str = Internat::ToDisplayString(ctrl.mMin * rate); } item = new wxStaticText(w, wxID_ANY, str); - gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); + gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT); } else { gridSizer->Add(1, 1, 0); } - mSliders[p] = - new wxSlider(w, p, - 0, 0, 1000, - wxDefaultPosition, - wxSize(200, -1)); + mSliders[p] = new wxSlider(w, ID_Sliders + p, + 0, 0, 1000, + wxDefaultPosition, + wxSize(150, -1)); mSliders[p]->SetName(labelText); - gridSizer->Add(mSliders[p], 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 5); - ConnectFocus(mSliders[p]); + gridSizer->Add(mSliders[p], 0, wxALIGN_CENTER_VERTICAL | wxEXPAND); - if (hashi) + if (ctrl.mHasHi) { - str = hiLabel; - if (str.IsEmpty()) + wxString str; + if (ctrl.mInteger || ctrl.mSampleRate) { - if (port.mInteger || forceint) - { - str.Printf(wxT("%d"), (int)(upper + 0.5)); - } - else - { - str = Internat::ToDisplayString(upper); - } + str.Printf(wxT("%d"), (int)(ctrl.mMax * rate + 0.5)); + } + else + { + str = Internat::ToDisplayString(ctrl.mMax * rate); } item = new wxStaticText(w, wxID_ANY, str); - gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, 5); + gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); } else { @@ -1325,295 +1668,307 @@ LV2EffectDialog::LV2EffectDialog(LV2Effect *effect, } } } + + groupSizer->Add(gridSizer, 1, wxEXPAND | wxALL, 5); + innerSizer->Add(groupSizer, 0, wxEXPAND | wxALL, 5); } - // Set all of the mSliders based on the value in the - // text mFields - inSlider = false; // Now we're ready for HandleText to actually do something. - HandleText(); + innerSizer->Layout(); - w->SetSizer(paramSizer); + // Calculate the maximum width of all columns (bypass Generator sizer) + wxArrayInt widths; + widths.Add(0, numCols); - Layout(); - Fit(); - SetSizeHints(GetSize()); -} - -LV2EffectDialog::~LV2EffectDialog() -{ - delete [] mToggles; - delete [] mSliders; - delete [] mFields; - delete [] mLabels; - delete [] mEnums; -} - -void LV2EffectDialog::OnCheckBox(wxCommandEvent &event) -{ - int p = event.GetId(); - - mControls[p].mControlBuffer = mToggles[p]->GetValue(); -} - -void LV2EffectDialog::OnSlider(wxCommandEvent &event) -{ - int p = event.GetId(); - - // if we don't add the following three lines, changing - // the value of the slider will change the text, which - // will change the slider, and so on. This gets rid of - // the implicit loop. - if (inText) - return; - inSlider = true; - - float val; - float lower = float(0.0); - float upper = float(10.0); - float range; - bool forceint = false; - - if (isfinite(mControls[p].mMin)) + size_t cnt = innerSizer->GetChildren().GetCount(); + for (size_t i = (GetType() == EffectTypeGenerate); i < cnt; i++) { - lower = mControls[p].mMin; + wxSizer *groupSizer = innerSizer->GetItem(i)->GetSizer(); + wxFlexGridSizer *gridSizer = (wxFlexGridSizer *)groupSizer->GetItem((size_t) 0)->GetSizer(); + + size_t items = gridSizer->GetChildren().GetCount(); + int cols = gridSizer->GetCols(); + + for (size_t j = 0; j < items; j++) + { + wxSizerItem *item = gridSizer->GetItem(j); + widths[j % cols] = wxMax(widths[j % cols], item->GetSize().GetWidth()); + } } - if (isfinite(mControls[p].mMax)) + // Set each column in all of the groups to the same width. + for (size_t i = (GetType() == EffectTypeGenerate); i < cnt; i++) { - upper = mControls[p].mMax; - } + wxSizer *groupSizer = innerSizer->GetItem(i)->GetSizer(); + wxFlexGridSizer *gridSizer = (wxFlexGridSizer *)groupSizer->GetItem((size_t) 0)->GetSizer(); - if (mControls[p].mSampleRate) + size_t items = gridSizer->GetChildren().GetCount(); + int cols = gridSizer->GetCols(); + + for (size_t j = 0; j < items; j++) + { + wxSizerItem *item = gridSizer->GetItem(j); + + int flags = item->GetFlag(); + if (flags & wxEXPAND) + { + continue; + } + + if (flags & wxALIGN_RIGHT) + { + flags = (flags & ~wxALL) | wxLEFT; + } + else + { + flags = (flags & ~wxALL) | wxRIGHT; + } + item->SetFlag(flags); + + item->SetBorder(widths[j % cols] - item->GetMinSize().GetWidth()); + } + } + + w->SetSizer(innerSizer); + mParent->SetSizer(outerSizer); + + TransferDataToWindow(); + + return true; +} + +bool LV2Effect::TransferDataToWindow() +{ + if (!mParent->TransferDataToWindow()) { - lower *= mSampleRate; - upper *= mSampleRate; - forceint = true; + return false; } - range = upper - lower; - - val = (mSliders[p]->GetValue() / 1000.0) * range + lower; - - // Force the value to an integer if requested - wxString str; - if (mControls[p].mInteger || forceint) + for (size_t i = 0, cnt = mGroups.GetCount(); i < cnt; i++) { - str.Printf(wxT("%d"), (int)(val + 0.5)); + const wxArrayInt & params = mGroupMap[mGroups[i]]; + for (size_t pi = 0, cnt = params.GetCount(); pi < cnt; pi++) + { + int p = params[pi]; + LV2Port & ctrl = mControls[p]; + + if (ctrl.mTrigger) + { + continue; + } + + if (ctrl.mToggle) + { + wxCheckBox *c = wxDynamicCast(mParent->FindWindow(ID_Toggles + p), wxCheckBox); + c->SetValue(ctrl.mVal > 0); + } + else if (ctrl.mEnumeration) // Check before integer + { + size_t s; + for (s = ctrl.mScaleValues.GetCount() - 1; s >= 0; s--) + { + if (ctrl.mVal >= ctrl.mScaleValues[s]) + { + break; + } + } + + if (s < 0) + { + s = 0; + } + + wxChoice *c = wxDynamicCast(mParent->FindWindow(ID_Choices + p), wxChoice); + c->SetSelection(s); + } + else if (ctrl.mInput) + { + SetSlider(mSliders[p], ctrl); + } + } } - else + + return true; +} + +bool LV2Effect::TransferDataFromWindow() +{ + if (!mParent->Validate() || !mParent->TransferDataFromWindow()) { - str = Internat::ToDisplayString(val); + return false; } - mFields[p]->SetValue(str); - - mControls[p].mControlBuffer = val; - - inSlider = false; + return true; } -void LV2EffectDialog::OnChoiceCtrl(wxCommandEvent & WXUNUSED(event)) +void LV2Effect::SetSlider(wxSlider *slider, const LV2Port & ctrl) { -// HandleText(); + float val = ctrl.mLogarithmic ? logf(ctrl.mVal) : ctrl.mVal; + + slider->SetValue((int) (val - ctrl.mLo) / (ctrl.mHi - ctrl.mLo) * 1000.0 + 0.5); } -void LV2EffectDialog::OnTextCtrl(wxCommandEvent & WXUNUSED(event)) +void LV2Effect::OnTrigger(wxCommandEvent & evt) { - HandleText(); + int p = evt.GetId() - ID_Triggers; + + mControls[p].mVal = mControls[p].mDef; } -void LV2EffectDialog::HandleText() +void LV2Effect::OnToggle(wxCommandEvent & evt) { - // if we don't add the following three lines, changing - // the value of the slider will change the text, which - // will change the slider, and so on. This gets rid of - // the implicit loop. + int p = evt.GetId() - ID_Toggles; - if (inSlider) + mControls[p].mVal = evt.GetInt() ? 1.0 : 0.0; +} + +void LV2Effect::OnChoice(wxCommandEvent & evt) +{ + int p = evt.GetId() - ID_Choices; + + mControls[p].mVal = mControls[p].mScaleValues[evt.GetInt()]; +} + +void LV2Effect::OnText(wxCommandEvent & evt) +{ + int p = evt.GetId() - ID_Texts; + LV2Port & ctrl = mControls[p]; + + if (mParent->FindWindow(ID_Texts + p)->GetValidator()->TransferFromWindow()) { - return; - } - inText = true; + ctrl.mVal = ctrl.mSampleRate ? ctrl.mTmp / mSampleRate : ctrl.mTmp; + SetSlider(mSliders[p], mControls[p]); + } +} - for (uint32_t p = 0; p < mControls.GetCount(); p++) +void LV2Effect::OnSlider(wxCommandEvent & evt) +{ + int p = evt.GetId() - ID_Sliders; + LV2Port & ctrl = mControls[p]; + + float val = ((float) evt.GetInt() / 1000.0) * (ctrl.mHi - ctrl.mLo) + ctrl.mLo; + + ctrl.mVal = ctrl.mLogarithmic ? expf(val) : val; + ctrl.mTmp = ctrl.mSampleRate ? ctrl.mVal * mSampleRate : ctrl.mVal; + + mParent->FindWindow(ID_Texts + p)->GetValidator()->TransferToWindow(); +} + +void LV2Effect::OnIdle(wxIdleEvent & WXUNUSED(evt)) +{ + if (mIdleFeature) { - double dval; - float val; - float lower = float(0.0); - float upper = float(10.0); - float range; + mIdleFeature->idle(suil_instance_get_handle(mSuilInstance)); + } +} - if (mControls[p].mToggle) +void LV2Effect::UIRefresh() +{ + if (mSuilInstance) + { + for (size_t p = 0, cnt = mControls.GetCount(); p < cnt; p++) { - continue; + if (mControls[p].mInput) + { + suil_instance_port_event(mSuilInstance, + p, + sizeof(float), + 0, + &mControls[p].mVal); + } } + } +} - if (mControls[p].mEnumeration) +// ============================================================================ +// Feature handlers +// ============================================================================ + +// static callback +uint32_t LV2Effect::uri_to_id(LV2_URI_Map_Callback_Data callback_data, + const char *WXUNUSED(map), + const char *uri) +{ + return ((LV2Effect *)callback_data)->URID_Map(uri); +} + +// static callback +LV2_URID LV2Effect::urid_map(LV2_URID_Map_Handle handle, const char *uri) +{ + return ((LV2Effect *)handle)->URID_Map(uri); +} + +LV2_URID LV2Effect::URID_Map(const char *uri) +{ + for (int i = 0; i < mNumURIMap; i++) + { + if (strcmp(mURIMap[i], uri) == 0) { - continue; + return i + 1; } - - dval = Internat::CompatibleToDouble(mFields[p]->GetValue()); - val = dval; - - if (!isnan(mControls[p].mMin)) - { - lower = mControls[p].mMin; - } - - if (!isnan(mControls[p].mMax)) - { - upper = mControls[p].mMax; - } - - if (mControls[p].mSampleRate) - { - lower *= mSampleRate; - upper *= mSampleRate; - } - range = upper - lower; - - if (val < lower) - { - val = lower; - } - - if (val > upper) - { - val = upper; - } - - mControls[p].mControlBuffer = val; - - mSliders[p]->SetValue((int)(((val-lower)/range) * 1000.0 + 0.5)); } - inText = false; + mNumURIMap += 1; + mURIMap = (char **) realloc(mURIMap, mNumURIMap * sizeof(char*)); + mURIMap[mNumURIMap - 1] = strdup(uri); + + return mNumURIMap; } -void LV2EffectDialog::OnOK(wxCommandEvent & WXUNUSED(event)) +// static callback +const char *LV2Effect::urid_unmap(LV2_URID_Unmap_Handle handle, LV2_URID urid) { - EndModal(TRUE); + return ((LV2Effect *)handle)->URID_Unmap(urid); } -void LV2EffectDialog::OnCancel(wxCommandEvent & WXUNUSED(event)) +const char *LV2Effect::URID_Unmap(LV2_URID urid) { - EndModal(FALSE); + if (urid > 0 && urid <= (LV2_URID) mNumURIMap) + { + return mURIMap[urid - 1]; + } + + return NULL; } -void LV2EffectDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) - +// static callback +int LV2Effect::ui_resize(LV2UI_Feature_Handle handle, int width, int height) { - mEffect->Preview(); + return ((LV2Effect *)handle)->UIResize(width, height); } -void LV2EffectDialog::ConnectFocus(wxControl *c) +int LV2Effect::UIResize(int WXUNUSED(width), int WXUNUSED(height)) { - c->GetEventHandler()->Connect(wxEVT_SET_FOCUS, - wxFocusEventHandler(LV2EffectDialog::ControlSetFocus)); +#if 0 + // Nothing to do yet +#endif + return 1; } -void LV2EffectDialog::DisconnectFocus(wxControl *c) +// static callback +void LV2Effect::suil_write_func(SuilController controller, + uint32_t port_index, + uint32_t buffer_size, + uint32_t protocol, + const void *buffer) { - c->GetEventHandler()->Disconnect(wxEVT_SET_FOCUS, - wxFocusEventHandler(LV2EffectDialog::ControlSetFocus)); + ((LV2Effect *)controller)->UIWrite(port_index, buffer_size, protocol, buffer); } -void LV2EffectDialog::ControlSetFocus(wxFocusEvent &event) +void LV2Effect::UIWrite(uint32_t port_index, + uint32_t buffer_size, + uint32_t protocol, + const void *buffer) { - wxControl *c = (wxControl *) event.GetEventObject(); - wxScrolledWindow *p = (wxScrolledWindow *) c->GetParent(); - wxRect r = c->GetRect(); - wxRect rv = p->GetRect(); - rv.y = 0; - - event.Skip(); - - int y; - int yppu; - p->GetScrollPixelsPerUnit(NULL, &yppu); - - if (r.y >= rv.y && r.GetBottom() <= rv.GetBottom()) + if (protocol != 0 || buffer_size != sizeof(float)) { return; } - if (r.y < rv.y) + wxLongToLongHashMap::iterator it = mControlsMap.find(port_index); + if (it != mControlsMap.end()) { - p->CalcUnscrolledPosition(0, r.y, NULL, &r.y); - y = r.y / yppu; + mControls[(it->second)].mVal = *((const float *)buffer); } - else - { - p->CalcUnscrolledPosition(0, r.y, NULL, &r.y); - y = (r.GetBottom() - rv.GetBottom() + yppu) / yppu; - } - - p->Scroll(-1, y); -}; - -double LV2EffectDialog::GetLength() -{ - if (mEffect->GetEffectFlags() & INSERT_EFFECT) - { - mLength = Internat::CompatibleToDouble(mSeconds->GetValue()); - } - - return mLength; -} - - -double LV2EffectDialog::GetNoteLength() -{ - if (mEffect->IsSynth()) - { - return Internat::CompatibleToDouble(mNoteSeconds->GetValue()); - } - return 0; -} - -unsigned char LV2EffectDialog::GetNoteVelocity() -{ - if (mEffect->IsSynth()) - { - double velocity = - Internat::CompatibleToDouble(mNoteVelocity->GetValue()); - - if (velocity < 1) - { - return 1; - } - - if (velocity > 127) - { - return 127; - } - - return (unsigned char)velocity; - } - return 64; -} - -unsigned char LV2EffectDialog::GetNoteKey() -{ - if (mEffect->IsSynth()) - { - double key = - Internat::CompatibleToDouble(mNoteKey->GetValue()); - - if (key < 1) - { - return 1; - } - - if (key > 127) - { - return 127; - } - - return (unsigned char)key; - } - return 64; } #endif diff --git a/src/effects/lv2/LV2Effect.h b/src/effects/lv2/LV2Effect.h index 0ed72bb00..03f4387f0 100644 --- a/src/effects/lv2/LV2Effect.h +++ b/src/effects/lv2/LV2Effect.h @@ -9,45 +9,74 @@ *********************************************************************/ -class wxSlider; -class wxStaticText; -class wxTextCtrl; -class wxCheckBox; +#include "../../Audacity.h" -#include -#include +#if USE_LV2 + +#include #include #include +#include +#include +#include +#include +#include + +#include "lv2/lv2plug.in/ns/ext/atom/forge.h" +#include "lv2/lv2plug.in/ns/ext/data-access/data-access.h" +#include "lv2/lv2plug.in/ns/ext/options/options.h" +#include "lv2/lv2plug.in/ns/ext/uri-map/uri-map.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" #include +#include -#include "../Effect.h" -#include "LV2PortGroup.h" +#include "../../widgets/NumericTextCtrl.h" + +#include "LoadLV2.h" #define LV2EFFECTS_VERSION wxT("1.0.0.0") #define LV2EFFECTS_FAMILY wxT("LV2") /** A structure that contains information about a single LV2 plugin port. */ -struct LV2Port +class LV2Port { +public: LV2Port() - : mToggle(false), - mInteger(false), - mSampleRate(false), - mEnumeration(false) { + mInput = false; + mToggle = false; + mTrigger = false; + mInteger = false; + mSampleRate = false; + mEnumeration = false; + mLogarithmic = false; + mHasLo = false; + mHasHi = false; } uint32_t mIndex; wxString mName; + wxString mGroup; + wxString mUnits; float mMin; float mMax; - float mDefault; - float mControlBuffer; + float mDef; + float mVal; + float mTmp; + float mLo; + float mHi; + bool mHasLo; + bool mHasHi; + bool mInput; bool mToggle; + bool mTrigger; bool mInteger; bool mSampleRate; bool mEnumeration; + bool mLogarithmic; + LilvPort *mPort; // ScalePoints @@ -56,16 +85,17 @@ struct LV2Port }; WX_DECLARE_OBJARRAY(LV2Port, LV2PortArray); +WX_DECLARE_STRING_HASH_MAP(wxArrayInt, LV2GroupMap); +WX_DEFINE_ARRAY_PTR(LilvInstance *, LV2SlaveArray); -/** The main LV2 plugin class. It handles loading and applying a - single plugin. */ -class LV2Effect:public Effect +class LV2EffectSettingsDialog; + +class LV2Effect : public wxEvtHandler, + public EffectClientInterface, + public EffectUIClientInterface { public: - - /** Create an LV2Effect from an LV2 data handle and a category set. */ - LV2Effect(const LilvPlugin *plug, - const std::set & categories = std::set()); + LV2Effect(const LilvPlugin *plug); virtual ~LV2Effect(); // IdentInterface implementation @@ -87,132 +117,218 @@ public: virtual bool SupportsRealtime(); virtual bool SupportsAutomation(); - // Effect implementation + // EffectClientInterface implementation - /** Get the name of the effect. */ - virtual wxString GetEffectName(); + virtual bool SetHost(EffectHostInterface *host); - /** Get the categories of the effect. */ - virtual std::set GetEffectCategories(); + virtual int GetAudioInCount(); + virtual int GetAudioOutCount(); - /** Get the effect identifier (for internal use). */ - virtual wxString GetEffectIdentifier(); + virtual int GetMidiInCount(); + virtual int GetMidiOutCount(); - /** Get the action string. */ - virtual wxString GetEffectAction(); + virtual void SetSampleRate(sampleCount rate); + virtual sampleCount SetBlockSize(sampleCount maxBlockSize); - virtual bool Init(); + virtual sampleCount GetLatency(); + virtual sampleCount GetTailSize(); - virtual bool PromptUser(); + virtual bool IsReady(); + virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); + virtual bool ProcessFinalize(); + virtual sampleCount ProcessBlock(float **inbuf, float **outbuf, sampleCount size); - virtual bool Process(); + virtual bool RealtimeInitialize(); + virtual bool RealtimeAddProcessor(int numChannels, float sampleRate); + virtual bool RealtimeFinalize(); + virtual bool RealtimeSuspend(); + virtual bool RealtimeResume(); + virtual bool RealtimeProcessStart(); + virtual sampleCount RealtimeProcess(int group, + float **inbuf, + float **outbuf, + sampleCount numSamples); + virtual bool RealtimeProcessEnd(); - virtual void End(); + virtual bool ShowInterface(wxWindow *parent, bool forceModal = false); - bool IsValid(); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); - /** Return a list of LV2Ports for the input parameters. */ - LV2PortArray & GetControls(); + // EffectUIClientInterface implementation - /** Return true if the plugin is a synth (MIDI input), false if not. */ - bool IsSynth(); + virtual void SetHostUI(EffectUIHostInterface *host); + virtual bool PopulateUI(wxWindow *parent); + virtual bool IsGraphicalUI(); + virtual bool ValidateUI(); + virtual bool HideUI(); + virtual bool CloseUI(); - /** Modify the note settings for the plugin (only for synths). */ - bool SetNote(sampleCount len, unsigned char velocity, unsigned char key); + virtual bool LoadUserPreset(const wxString & name); + virtual bool SaveUserPreset(const wxString & name); - /** Get the port group tree for the plugin. */ - const LV2PortGroup & GetRootGroup(); + virtual wxArrayString GetFactoryPresets(); + virtual bool LoadFactoryPreset(int id); + virtual bool LoadFactoryDefaults(); - wxString GetString(const LilvNode *node); - wxString GetString(LilvNode *node, bool free); + virtual bool CanExportPresets(); + virtual void ExportPresets(); + virtual void ImportPresets(); + + virtual bool HasOptions(); + virtual void ShowOptions(); + + // LV2Effect implementation private: - bool ProcessStereo(int count, WaveTrack *left, WaveTrack *right, - sampleCount lstart, sampleCount rstart, - sampleCount len); + bool Load(); + void Unload(); - bool mValid; - wxString pluginName; + bool LoadParameters(const wxString & group); + bool SaveParameters(const wxString & group); - const LilvPlugin *mData; - sampleCount mBlockSize; - float **fInBuffer; - float **fOutBuffer; - int mainRate; + LilvInstance *InitInstance(float sampleRate); + void FreeInstance(LilvInstance *handle); - std::set mCategories; + static uint32_t uri_to_id(LV2_URI_Map_Callback_Data callback_data, + const char *map, + const char *uri); - LV2PortArray mControlInputs; - LV2PortArray mControlOutputs; - LV2PortArray mAudioInputs; - LV2PortArray mAudioOutputs; - LV2Port *mMidiInput; - int mLatencyPortIndex; + static LV2_URID urid_map(LV2_URID_Map_Handle handle, const char *uri); + LV2_URID URID_Map(const char *uri); - sampleCount mNoteLength; - unsigned char mNoteVelocity; - unsigned char mNoteKey; + static const char *urid_unmap(LV2_URID_Unmap_Handle handle, LV2_URID urid); + const char *URID_Unmap(LV2_URID urid); - LV2PortGroup mRootGroup; - std::map mPortGroups; -}; + static int ui_resize(LV2UI_Feature_Handle handle, int width, int height); + int UIResize(int width, int height); + LV2_Options_Option *AddOption(const char *key, uint32_t size, const char *type, void *value); + LV2_Feature *AddFeature(const char *uri, void *data); -/** The control dialog for an LV2 plugin. */ -class LV2EffectDialog:public wxDialog -{ - DECLARE_DYNAMIC_CLASS(LV2EffectDialog) + bool BuildFancy(); + bool BuildPlain(); -public: - LV2EffectDialog(LV2Effect *effect, - wxWindow *parent, - const LilvPlugin *data, - int sampleRate, - double length, - double noteLength, - unsigned char noteVelocity, - unsigned char noteKey); + bool TransferDataToWindow(); + bool TransferDataFromWindow(); + void SetSlider(wxSlider *slider, const LV2Port & ctrl); - ~LV2EffectDialog(); + void OnTrigger(wxCommandEvent & evt); + void OnToggle(wxCommandEvent & evt); + void OnChoice(wxCommandEvent & evt); + void OnText(wxCommandEvent & evt); + void OnSlider(wxCommandEvent & evt); - void OnCheckBox(wxCommandEvent & event); - void OnSlider(wxCommandEvent & event); - void OnTextCtrl(wxCommandEvent & event); - void OnChoiceCtrl(wxCommandEvent & event); - void OnOK(wxCommandEvent & event); - void OnCancel(wxCommandEvent & event); - void OnPreview(wxCommandEvent & event); - void ControlSetFocus(wxFocusEvent & event); + void OnIdle(wxIdleEvent & evt); - double GetLength(); - double GetNoteLength(); - unsigned char GetNoteVelocity(); - unsigned char GetNoteKey(); + static void suil_write_func(SuilController controller, + uint32_t port_index, + uint32_t buffer_size, + uint32_t protocol, + const void *buffer); - DECLARE_EVENT_TABLE() + void UIWrite(uint32_t port_index, + uint32_t buffer_size, + uint32_t protocol, + const void *buffer); + + void UIRefresh(); private: - void HandleText(); - void ConnectFocus(wxControl *c); - void DisconnectFocus(wxControl *c); + // Declare the static URI nodes + #undef URI + #define URI(n, u) static LilvNode *n; + URILIST; + + const LilvPlugin *mPlug; + + EffectHostInterface *mHost; + + int mBlockSize; + double mSampleRate; + + wxLongToLongHashMap mControlsMap; + LV2PortArray mControls; + wxArrayInt mAudioInputs; + wxArrayInt mAudioOutputs; + + LV2GroupMap mGroupMap; + wxArrayString mGroups; + + bool mUseLatency; + int mLatencyPort; + bool mLatencyDone; + float mLatency; + + LilvInstance *mMaster; + LilvInstance *mProcess; + LV2SlaveArray mSlaves; + + float **mMasterIn; + float **mMasterOut; + sampleCount mNumSamples; -private: - LV2Effect *mEffect; - const LilvPlugin *mData; - LV2PortArray & mControls; - int mSampleRate; double mLength; - bool inSlider; - bool inText; + wxDialog *mDialog; + wxWindow *mParent; + EffectUIHostInterface *mUIHost; + bool mUseGUI; + wxWindow *mContainer; + + char **mURIMap; + int mNumURIMap; + + LV2_URI_Map_Feature mUriMapFeature; + LV2_URID_Map mURIDMapFeature; + LV2_URID_Unmap mURIDUnmapFeature; + LV2UI_Resize mUIResizeFeature; + LV2_Extension_Data_Feature mExtDataFeature; + + LV2_Options_Option *mBlockSizeOption; + LV2_Options_Option *mSampleRateOption; + + LV2_Options_Interface *mOptionsInterface; + LV2_Options_Option *mOptions; + int mNumOptions; + + LV2_Feature **mFeatures; + int mNumFeatures; + + LV2_Feature *mInstanceAccessFeature; + LV2_Feature *mParentFeature; + + const LV2UI_Idle_Interface *mIdleFeature; + + SuilHost *mSuilHost; + SuilInstance *mSuilInstance; + + NumericTextCtrl *mDuration; wxSlider **mSliders; wxTextCtrl **mFields; - wxStaticText **mLabels; - wxCheckBox **mToggles; - wxChoice **mEnums; - wxTextCtrl *mSeconds; - wxTextCtrl *mNoteSeconds; - wxTextCtrl *mNoteVelocity; - wxTextCtrl *mNoteKey; + + DECLARE_EVENT_TABLE(); + + friend class LV2EffectSettingsDialog; + friend class LV2EffectsModule; }; + +inline wxString LilvString(const LilvNode *node) +{ + return wxString::FromUTF8(lilv_node_as_string(node)); +}; + +inline wxString LilvString(LilvNode *node, bool free) +{ + wxString str = LilvString(node); + if (free) + { + lilv_node_free(node); + } + + return str; +}; + + +#endif diff --git a/src/effects/lv2/LV2PortGroup.cpp b/src/effects/lv2/LV2PortGroup.cpp deleted file mode 100644 index 3ee74b932..000000000 --- a/src/effects/lv2/LV2PortGroup.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/********************************************************************** - - Audacity: A Digital Audio Editor - - LV2PortGroup.cpp - - Audacity(R) is copyright (c) 1999-2008 Audacity Team. - License: GPL v2. See License.txt. - -*********************************************************************/ - -#include "../../Audacity.h" - -#if defined(USE_LV2) - -#include - -#include "LV2PortGroup.h" - -LV2PortGroup::LV2PortGroup(const wxString & name) -: mName(name) -{ -} - -void LV2PortGroup::AddSubGroup(const LV2PortGroup & subgroup) -{ - wxString name = subgroup.GetName(); - - LV2PortGroupArray::iterator i; - for (i = mSubGroups.begin(); i != mSubGroups.end(); i++) - { - if ((*i)->GetName() == name) - { - return; - } - } - - mSubGroups.push_back(&subgroup); -} - -const LV2PortGroupArray & LV2PortGroup::GetSubGroups() const -{ - return mSubGroups; -} - -void LV2PortGroup::AddParameter(int parameter) -{ - mParameters.Add(parameter); -} - -const wxArrayInt & LV2PortGroup::GetParameters() const -{ - return mParameters; -} - -const wxString & LV2PortGroup::GetName() const -{ - return mName; -} - -#endif diff --git a/src/effects/lv2/LV2PortGroup.h b/src/effects/lv2/LV2PortGroup.h deleted file mode 100644 index fa9b68184..000000000 --- a/src/effects/lv2/LV2PortGroup.h +++ /dev/null @@ -1,51 +0,0 @@ -/********************************************************************** - - Audacity: A Digital Audio Editor - - LV2PortGroup.h - - Audacity(R) is copyright (c) 1999-2008 Audacity Team. - License: GPL v2. See License.txt. - -*********************************************************************/ - -#ifndef LV2PORTGROUP_H -#define LV2PORTGROUP_H - - -#include - -#include - -class LV2PortGroup; -typedef std::vector LV2PortGroupArray; - -/** A class that contains information about a single LV2 plugin port group, - such as its children and its name. */ -class LV2PortGroup -{ -public: - LV2PortGroup(const wxString & name = wxEmptyString); - - /** Add a subgroup of this group. */ - void AddSubGroup(const LV2PortGroup & subgroup); - - /** Return a list of all subgroups. */ - const LV2PortGroupArray & GetSubGroups() const; - - /** Add a parameter number (not port number). */ - void AddParameter(int parameter); - - /** Return a list of all parameters in this group. */ - const wxArrayInt & GetParameters() const; - - const wxString & GetName() const; - - private: - - wxString mName; - LV2PortGroupArray mSubGroups; - wxArrayInt mParameters; -}; - -#endif diff --git a/src/effects/lv2/LoadLV2.cpp b/src/effects/lv2/LoadLV2.cpp index bade8c2e4..cf2633685 100644 --- a/src/effects/lv2/LoadLV2.cpp +++ b/src/effects/lv2/LoadLV2.cpp @@ -24,6 +24,7 @@ Functions that find and load all LV2 plugins on the system. #include #include +#include #include #include #include @@ -35,9 +36,11 @@ Functions that find and load all LV2 plugins on the system. #include "LV2Effect.h" #include "lv2/lv2plug.in/ns/ext/event/event.h" -#include "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "lv2/lv2plug.in/ns/ext/instance-access/instance-access.h" #include "lv2/lv2plug.in/ns/ext/port-groups/port-groups.h" +#include "lv2/lv2plug.in/ns/ext/port-props/port-props.h" #include "lv2/lv2plug.in/ns/ext/uri-map/uri-map.h" +#include "lv2/lv2plug.in/ns/ext/presets/presets.h" #include "LoadLV2.h" @@ -66,64 +69,12 @@ DECLARE_BUILTIN_MODULE(LV2sEffectBuiltin); // LV2EffectsModule // /////////////////////////////////////////////////////////////////////////////// +WX_DECLARE_STRING_HASH_MAP(LilvNode *, UriHash); LilvWorld *gWorld = NULL; -// This is the URI Map Feature object. It is required for loading synth -// plugins. -static uint32_t uri_to_id(LV2_URI_Map_Callback_Data WXUNUSED(cbd), - const char *map, const char *uri) -{ - if (!std::strcmp(map, LV2_EVENT_URI)) - { - if (!std::strcmp(uri, LV2_MIDI__MidiEvent)) - { - return 1; - } - else if (!std::strcmp(uri, LV2_EVENT__TimeStamp)) - { - return 2; - } - } - - return 0; -} -static LV2_URI_Map_Feature gURIMap = { 0, &uri_to_id }; -static LV2_Feature gURIMapFeature = { "http://lv2plug.in/ns/ext/uri-map", - &gURIMap }; - -// This is the event refcounter object. We don't actually implement it -// since we only ever send flat MIDI events to the plugins, but it is -// still required. -uint32_t event_ref(LV2_Event_Callback_Data WXUNUSED(callback_data), - LV2_Event *WXUNUSED(event)) -{ - return 0; -} -static LV2_Event_Feature gEventRef = { 0, &event_ref, &event_ref }; -static LV2_Feature gEventRefFeature = { "http://lv2plug.in/ns/ext/event", - &gEventRef }; - -// These are the LV2 Features we support. -LV2_Feature*const gLV2Features[] = { &gURIMapFeature, &gEventRefFeature, 0 }; - -LilvNode *gAudioPortClass; -LilvNode *gControlPortClass; -LilvNode *gMidiPortClass; -LilvNode *gInputPortClass; -LilvNode *gOutputPortClass; -LilvNode *gPortToggled; -LilvNode *gPortIsInteger; -LilvNode *gPortIsSampleRate; -LilvNode *gPortIsEnumeration; -LilvNode *gPortIsLatency; -LilvNode *gPortIsOptional; -LilvNode *gName; -LilvNode *gPortGroup; -LilvNode *gSubGroupOf; - LV2EffectsModule::LV2EffectsModule(ModuleManagerInterface *moduleManager, - const wxString *path) + const wxString *path) { mModMan = moduleManager; if (path) @@ -147,17 +98,17 @@ wxString LV2EffectsModule::GetPath() wxString LV2EffectsModule::GetSymbol() { - return wxT("LV2 Effects"); + return wxT("LV2 Effects Module"); } wxString LV2EffectsModule::GetName() { - return wxTRANSLATE("LV2 Effects"); + return _("LV2 Effects Module"); } wxString LV2EffectsModule::GetVendor() { - return wxTRANSLATE("The Audacity Team"); + return _("The Audacity Team"); } wxString LV2EffectsModule::GetVersion() @@ -168,7 +119,7 @@ wxString LV2EffectsModule::GetVersion() wxString LV2EffectsModule::GetDescription() { - return wxTRANSLATE("Provides LV2 Effects support to Audacity"); + return _("Provides LV2 Effects support to Audacity"); } // ============================================================================ @@ -184,20 +135,65 @@ bool LV2EffectsModule::Initialize() return false; } - gAudioPortClass = lilv_new_uri(gWorld, LV2_CORE__AudioPort); - gControlPortClass = lilv_new_uri(gWorld, LV2_CORE__ControlPort); - gMidiPortClass = lilv_new_uri(gWorld, LV2_EVENT__EventPort); - gInputPortClass = lilv_new_uri(gWorld, LV2_CORE__InputPort); - gOutputPortClass = lilv_new_uri(gWorld, LV2_CORE__OutputPort); - gPortToggled = lilv_new_uri(gWorld, LV2_CORE__toggled); - gPortIsInteger = lilv_new_uri(gWorld, LV2_CORE__integer); - gPortIsSampleRate = lilv_new_uri(gWorld, LV2_CORE__sampleRate); - gPortIsEnumeration = lilv_new_uri(gWorld, LV2_CORE__enumeration); - gPortIsLatency = lilv_new_uri(gWorld, LV2_CORE__reportsLatency); - gPortIsOptional = lilv_new_uri(gWorld, LV2_CORE__connectionOptional); - gName = lilv_new_uri(gWorld, LV2_CORE__name); - gPortGroup = lilv_new_uri(gWorld, LV2_PORT_GROUPS__group); - gSubGroupOf = lilv_new_uri(gWorld, LV2_PORT_GROUPS__subGroupOf); + // Create LilvNodes for each of the URIs we need + #undef URI + #define URI(n, u) LV2Effect::n = lilv_new_uri(gWorld, u); + URILIST + + wxString newVar; + +#if defined(__WXMAC__) +#define LV2PATH wxT("/Library/Audio/Plug-Ins/LV2") + + wxFileName libdir; +// libdir.AssignDir(wxT(LIBDIR)); + libdir.AppendDir(wxT("lv2")); + + newVar += wxT(":$HOME/.lv2"); + + // Look in ~/Library/Audio/Plug-Ins/lv2 and /Library/Audio/Plug-Ins/lv2 + newVar += wxT(":$HOME") LV2PATH; + newVar += wxT(":") LV2PATH; + + newVar += wxT(":/usr/local/lib/lv2"); + newVar += wxT(":/usr/lib/lv2"); + newVar += wxT(":") + libdir.GetPath(); + +#elif defined(__WXMSW__) + + newVar += wxT(";%APPDATA%\\LV2"); + newVar += wxT(";%COMMONPROGRAMFILES%\\LV2"); + newVar += wxT(";%COMMONPROGRAMFILES(x86)%\\LV2"); + +#else + + wxFileName libdir; + libdir.AssignDir(wxT(LIBDIR)); + libdir.AppendDir(wxT("lv2")); + + newVar += wxT(":$HOME/.lv2"); + newVar += wxT(":/usr/local/lib/lv2"); + newVar += wxT(":/usr/lib/lv2"); + newVar += wxT(":/usr/local/lib64/lv2"); + newVar += wxT(":/usr/lib64/lv2"); + newVar += wxT(":") + libdir.GetPath(); + +#endif + + // Start with the LV2_PATH environment variable (if any) + wxString pathVar; + wxGetEnv(wxT("LV2_PATH"), &pathVar); + + if (pathVar.IsEmpty()) + { + pathVar = newVar.Mid(1); + } + else + { + pathVar += newVar; + } + + wxSetEnv(wxT("LV2_PATH"), pathVar); lilv_world_load_all(gWorld); @@ -206,47 +202,10 @@ bool LV2EffectsModule::Initialize() void LV2EffectsModule::Terminate() { - lilv_node_free(gAudioPortClass); - gAudioPortClass = NULL; - - lilv_node_free(gControlPortClass); - gControlPortClass = NULL; - - lilv_node_free(gMidiPortClass); - gMidiPortClass = NULL; - - lilv_node_free(gInputPortClass); - gInputPortClass = NULL; - - lilv_node_free(gOutputPortClass); - gOutputPortClass = NULL; - - lilv_node_free(gPortToggled); - gPortToggled = NULL; - - lilv_node_free(gPortIsInteger); - gPortIsInteger = NULL; - - lilv_node_free(gPortIsSampleRate); - gPortIsSampleRate = NULL; - - lilv_node_free(gPortIsEnumeration); - gPortIsEnumeration = NULL; - - lilv_node_free(gPortIsLatency); - gPortIsLatency = NULL; - - lilv_node_free(gPortIsOptional); - gPortIsOptional = NULL; - - lilv_node_free(gName); - gName = NULL; - - lilv_node_free(gPortGroup); - gPortGroup = NULL; - - lilv_node_free(gSubGroupOf); - gSubGroupOf = NULL; + // Free the LilvNodes for each of the URIs we need + #undef URI + #define URI(n, u) lilv_node_free(LV2Effect::n); + URILIST lilv_world_free(gWorld); gWorld = NULL; @@ -256,89 +215,83 @@ void LV2EffectsModule::Terminate() bool LV2EffectsModule::AutoRegisterPlugins(PluginManagerInterface & WXUNUSED(pm)) { - EffectManager& em = EffectManager::Get(); + return false; +} -#ifdef EFFECT_CATEGORIES - - // Add all LV2 categories and their relationships - LilvPluginClasses classes = Lilv_world_get_plugin_classes(gWorld); - for (unsigned index = 0; index < Lilv_plugin_classes_size(classes);++index){ - LilvPluginClass c = Lilv_plugin_classes_get_at(classes, index); - em.AddCategory(wxString::FromUTF8(lilv_node_as_uri(Lilv_plugin_class_get_uri(c))), - wxString::FromUTF8(lilv_node_as_string(Lilv_plugin_class_get_label(c)))); - } - for (unsigned index = 0; index < Lilv_plugin_classes_size(classes);++index){ - LilvPluginClass c = Lilv_plugin_classes_get_at(classes, index); - LilvPluginClasses ch = Lilv_plugin_class_get_children(c); - EffectCategory* pCat = em.LookupCategory(wxString::FromUTF8(lilv_node_as_uri(Lilv_plugin_class_get_uri(c)))); - for (unsigned j = 0; j < Lilv_plugin_classes_size(ch); ++j) { - EffectCategory* chCat = em.LookupCategory(wxString::FromUTF8(lilv_node_as_uri(Lilv_plugin_class_get_uri(Lilv_plugin_classes_get_at(ch, j))))); - if (chCat && pCat) { - em.AddCategoryParent(chCat, pCat); - } - } - } - -#endif - - // Retrieve data about all plugins +wxArrayString LV2EffectsModule::FindPlugins(PluginManagerInterface & WXUNUSED(pm)) +{ + // Retrieve data about all LV2 plugins const LilvPlugins *plugs = lilv_world_get_all_plugins(gWorld); - // Iterate over all plugins and register them with the EffectManager + // Iterate over all plugins retrieve their URI + wxArrayString plugins; LILV_FOREACH(plugins, i, plugs) { - const LilvPlugin *plug = lilv_plugins_get(plugs, i); - std::set cats; - cats.insert(wxString::FromUTF8(lilv_node_as_uri(lilv_plugin_class_get_uri(lilv_plugin_get_class(plug))))); - LV2Effect *effect = new LV2Effect(plug, cats); - if (effect->IsValid()) - { - em.RegisterEffect(this, effect); - } - else - { - delete effect; - } + plugins.Add(LilvString(lilv_plugin_get_uri(lilv_plugins_get(plugs, i)))); + } + + return plugins; +} + +bool LV2EffectsModule::RegisterPlugin(PluginManagerInterface & pm, const wxString & path) +{ + const LilvPlugin *plug = GetPlugin(path); + if (!plug) + { + return false; + } + + LV2Effect effect(plug); + if (effect.SetHost(NULL)) + { + pm.RegisterPlugin(this, &effect); } return true; } -wxArrayString LV2EffectsModule::FindPlugins(PluginManagerInterface & WXUNUSED(pm)) -{ - // Nothing to do here yet - return wxArrayString(); -} - -bool LV2EffectsModule::RegisterPlugin(PluginManagerInterface & WXUNUSED(pm), const wxString & WXUNUSED(path)) -{ - // Nothing to do here yet - return false; -} - bool LV2EffectsModule::IsPluginValid(const wxString & path) { - LilvNode *uri = lilv_new_uri(gWorld, path.ToUTF8()); - const LilvPlugin *plugin = lilv_plugins_get_by_uri(lilv_world_get_all_plugins(gWorld), uri); - lilv_node_free(uri); - - return plugin != NULL; + return GetPlugin(path) != NULL; } -IdentInterface *LV2EffectsModule::CreateInstance(const wxString & WXUNUSED(path)) +IdentInterface *LV2EffectsModule::CreateInstance(const wxString & path) { - // Nothing to do here yet since we are autoregistering (and creating legacy - // effects anyway). - return NULL; + const LilvPlugin *plug = GetPlugin(path); + if (!plug) + { + return NULL; + } + + return new LV2Effect(plug); } -void LV2EffectsModule::DeleteInstance(IdentInterface *WXUNUSED(instance)) +void LV2EffectsModule::DeleteInstance(IdentInterface *instance) { - // Nothing to do here yet + LV2Effect *effect = dynamic_cast(instance); + if (effect) + { + delete effect; + } } // ============================================================================ // LV2EffectsModule implementation // ============================================================================ +const LilvPlugin *LV2EffectsModule::GetPlugin(const wxString & path) +{ + LilvNode *uri = lilv_new_uri(gWorld, path.ToUTF8()); + if (!uri) + { + return NULL; + } + + const LilvPlugin *plug = lilv_plugins_get_by_uri(lilv_world_get_all_plugins(gWorld), uri); + + lilv_node_free(uri); + + return plug; +} + #endif diff --git a/src/effects/lv2/LoadLV2.h b/src/effects/lv2/LoadLV2.h index c05d94ebb..507e533d1 100644 --- a/src/effects/lv2/LoadLV2.h +++ b/src/effects/lv2/LoadLV2.h @@ -9,12 +9,53 @@ *********************************************************************/ +#ifndef LV2EFFECTSMODULE_H +#define LV2EFFECTSMODULE_H + +#include +#include + #include +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" +#include "lv2/lv2plug.in/ns/ext/data-access/data-access.h" +#include "lv2/lv2plug.in/ns/ext/instance-access/instance-access.h" +#include "lv2/lv2plug.in/ns/ext/port-groups/port-groups.h" +#include "lv2/lv2plug.in/ns/ext/port-props/port-props.h" +#include "lv2/lv2plug.in/ns/ext/presets/presets.h" +#include "lv2/lv2plug.in/ns/ext/uri-map/uri-map.h" +#include "lv2/lv2plug.in/ns/extensions/units/units.h" + #include "audacity/ModuleInterface.h" #include "audacity/EffectInterface.h" #include "audacity/PluginInterface.h" +#undef URI +#define URI(n, u) + +// Define the list of URIs that we will use +#undef URILIST +#define URILIST \ + URI( gAudio, LV2_CORE__AudioPort ) \ + URI( gControl, LV2_CORE__ControlPort ) \ + URI( gInput, LV2_CORE__InputPort ) \ + URI( gOutput, LV2_CORE__OutputPort ) \ + URI( gOptional, LV2_CORE__connectionOptional ) \ + URI( gEnumeration, LV2_CORE__enumeration ) \ + URI( gInteger, LV2_CORE__integer ) \ + URI( gName, LV2_CORE__name ) \ + URI( gLatency, LV2_CORE__reportsLatency ) \ + URI( gSampleRate, LV2_CORE__sampleRate ) \ + URI( gToggled, LV2_CORE__toggled ) \ + URI( gGroup, LV2_PORT_GROUPS__group ) \ + URI( gSubGroupOf, LV2_PORT_GROUPS__subGroupOf ) \ + URI( gLogarithmic, LV2_PORT_PROPS__logarithmic ) \ + URI( gTrigger, LV2_PORT_PROPS__trigger ) \ + URI( gPreset, LV2_PRESETS__Preset ) \ + URI( gUnit, LV2_UNITS__unit ) \ + URI( gUnitSymbol, LV2_UNITS__symbol ) \ + URI( gLabel, LILV_NS_RDFS "label" ) + /////////////////////////////////////////////////////////////////////////////// // // LV2EffectsModule @@ -52,6 +93,9 @@ public: // LV2EffectModule implementation +private: + const LilvPlugin *GetPlugin(const wxString & path); + private: ModuleManagerInterface *mModMan; wxString mPath; @@ -59,27 +103,4 @@ private: extern LilvWorld *gWorld; -// This is the LV2 Feature array. It is passed to every LV2 plugin on -// instantiation. So far it only contains the URI Map Feature, which is -// needed to load synths. -extern LV2_Feature * const gLV2Features[]; - -// These are needed for comparisons -extern LilvNode *gAudioPortClass; -extern LilvNode *gControlPortClass; -extern LilvNode *gMidiPortClass; -extern LilvNode *gInputPortClass; -extern LilvNode *gOutputPortClass; -extern LilvNode *gPortToggled; -extern LilvNode *gPortIsInteger; -extern LilvNode *gPortIsSampleRate; -extern LilvNode *gPortIsEnumeration; -extern LilvNode *gPortIsLatency; -extern LilvNode *gPortIsOptional; -extern LilvNode *gName; -extern LilvNode *gPortGroup; -extern LilvNode *gSubGroupOf; - - -void LoadLV2Plugins(); -void UnloadLV2Plugins(); +#endif diff --git a/src/effects/lv2/lv2_event.h b/src/effects/lv2/lv2_event.h deleted file mode 100644 index fea1868a0..000000000 --- a/src/effects/lv2/lv2_event.h +++ /dev/null @@ -1,273 +0,0 @@ -/* lv2_event.h - C header file for the LV2 events extension. - * - * Copyright (C) 2006-2007 Lars Luthman - * Copyright (C) 2008 Dave Robillard - * - * This header is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This header is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this header; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA - */ - -#ifndef LV2_EVENT_H -#define LV2_EVENT_H - -#define LV2_EVENT_URI "http://lv2plug.in/ns/ext/event" -#define LV2_EVENT_AUDIO_STAMP 0 - -#include - -/** @file - * This header defines the code portion of the LV2 events extension with - * URI . - * - * Below, the URI prefix 'lv2ev' is assumed to expand to - * . - * - * This extension is a generic transport mechanism for time stamped events - * of any type (e.g. MIDI, OSC, ramps, etc). Each port can transport mixed - * events of any type; the type of events and timestamps are defined by a URI - * which is mapped to an integer by the host for performance reasons. - * - * This extension requires the host to support the LV2 URI Map extension. - * This requirement is implicit - a plugin does not have to list the URI Map - * feature as required or optional in its RDF data for the host to provide - * the URI Map LV2_Feature in the instantiation function. - * - * Any host which supports this extension MUST guarantee that any call to - * the LV2 URI Map uri_to_id function with the URI of this extension as the - * 'map' argument returns a value within the range of uint16_t. - */ - - -/** The best Pulses Per Quarter Note for tempo-based uint32_t timestmaps. - * Equal to 2^12 * 5 * 7 * 9 * 11 * 13 * 17, which is evenly divisble - * by all integers from 1 through 18 inclusive, and powers of 2 up to 2^12. - */ -static const uint32_t LV2_EVENT_PPQN = 3136573440U; - - -/** An LV2 event (header only). - * - * LV2 events are generic time-stamped containers for any type of event. - * The type field defines the format of a given event's contents. - * - * This struct defines the header of an LV2 event. An LV2 event, as specified - * in this extension, is a single chunk of POD (plain old data), usually - * contained in a flat buffer (see LV2_EventBuffer below), that can be safely - * copied using a simple: - * - * memcpy(ev_copy, ev, sizeof(LV2_Event) + ev->size); (or equivalent) - * - * However, events with event type 0 need to be handled specially (see below). - */ -typedef struct { - - /** The frames portion of timestamp. The units used here can optionally - * be set for a port (with lv2ev:supportsTimeStamp and related - * properties), otherwise this is audio frames, corresponding to the - * sample_count parameter of the LV2 run method (e.g. frame 0 is the - * first frame for that call to run). - */ - uint32_t frames; - - /** The sub-frames portion of timestamp. The units used here can - * optionally be set for a port (with lv2ev:supportsTimeStamp and - * related properties), otherwise this is 1/(2^32) of an audio frame. - */ - uint32_t subframes; - - /** The type of this event, as a number which represents some URI - * defining an event type. This value MUST be some value previously - * returned from a call to the uri_to_id function defined in the LV2 - * URI map extension (see lv2_uri_map.h). - * - * There are special rules which must be followed depending on the type - * of an event. If the plugin recognizes an event type, the definition - * of that event type will describe how to interpret the event, and - * any required behaviour. Otherwise, if the type is 0, this event is a - * non-POD event and lv2_event_unref MUST be called if the event is - * 'dropped' (see below). Even if the plugin does not understand an - * event, it may pass the event through to an output by simply copying - * (and NOT calling lv2_event_unref). These rules are designed to - * allow for generic event handling plugins and large non-POD events, - * but with minimal hassle on simple plugins that "don't care" about - * these more advanced features. - * - * Plugins should not interpret type 0 events in any way unless - * specified by another extension. - */ - uint16_t type; - - /** The size of the data portion of this event in bytes, which - * immediately follows. The header size (12 bytes) is not included in - * this value. - */ - uint16_t size; - - /* size bytes of data follow here */ - -} LV2_Event; - - - -/** A buffer of LV2 events (header only). - * - * This struct is used as the port buffer for LV2 plugin ports that have the - * port class lv2ev:EventPort. - * - * The data member points to a buffer that contains an event header (defined - * by struct* LV2_Event), followed by that event's contents (padded to 64 bits), - * followed by another header, etc: - * - * | | | | | | | - * | | | | | | | | | | | | | | | | | | | | | | | | | - * |FRAMES |SUBFRMS|TYP|LEN|DATA..DATA..PAD|FRAMES | ... - */ -typedef struct { - - /** The contents of the event buffer. This may or may not reside in the - * same block of memory as this header, plugins must not assume either. - * The host guarantees this points to at least capacity bytes of - * allocated memory (though only size bytes of that are valid events). - */ - uint8_t* data; - - /** The size of this event header in bytes (including everything). - * - * This is to allow for extending this header in the future without - * breaking binary compatibility. Whenever this header is copied, - * it MUST be done using this field (and NOT the sizeof this struct). - */ - uint16_t header_size; - - /** The type of the time stamps for events in this buffer. - * As a special exception, '0' always means audio frames and subframes - * (1/UINT32_MAX'th of a frame) in the sample rate passed to instantiate. - * INPUTS: The host must set this field to the numeric ID of some URI - * defining the meaning of the frames/subframes fields of contained - * events (obtained by the LV2 URI Map uri_to_id function with the URI - * of this extension as the 'map' argument, see lv2_uri_map.h). - * The host must never pass a plugin a buffer which uses a stamp type - * the plugin does not 'understand'. The value of this field must - * never change, except when connect_port is called on the input - * port, at which time the host MUST have set the stamp_type field to - * the value that will be used for all subsequent run calls. - * OUTPUTS: The plugin may set this to any value that has been returned - * from uri_to_id with the URI of this extension for a 'map' argument. - * When connected to a buffer with connect_port, output ports MUST set - * this field to the type of time stamp they will be writing. On any - * call to connect_port on an event input port, the plugin may change - * this field on any output port, it is the responsibility of the host - * to check if any of these values have changed and act accordingly. - */ - uint16_t stamp_type; - - /** The number of events in this buffer. - * INPUTS: The host must set this field to the number of events - * contained in the data buffer before calling run(). - * The plugin must not change this field. - * OUTPUTS: The plugin must set this field to the number of events it - * has written to the buffer before returning from run(). - * Any initial value should be ignored by the plugin. - */ - uint32_t event_count; - - /** The size of the data buffer in bytes. - * This is set by the host and must not be changed by the plugin. - * The host is allowed to change this between run() calls. - */ - uint32_t capacity; - - /** The size of the initial portion of the data buffer containing data. - * INPUTS: The host must set this field to the number of bytes used - * by all events it has written to the buffer (including headers) - * before calling the plugin's run(). - * The plugin must not change this field. - * OUTPUTS: The plugin must set this field to the number of bytes - * used by all events it has written to the buffer (including headers) - * before returning from run(). - * Any initial value should be ignored by the plugin. - */ - uint32_t size; - -} LV2_Event_Buffer; - - -/** Opaque pointer to host data. */ -typedef void* LV2_Event_Callback_Data; - - -/** The data field of the LV2_Feature for this extension. - * - * To support this extension the host must pass an LV2_Feature struct to the - * plugin's instantiate method with URI "http://lv2plug.in/ns/ext/event" - * and data pointed to an instance of this struct. The plugin does not have - * to list that URI as a required or optional feature in its RDF data - the - * host MUST pass this LV2_Feature if the plugin has an port of class - * lv2ev:EventPortthat the host connects to. - */ -typedef struct { - - /** Opaque pointer to host data. - * - * The plugin MUST pass this to any call to functions in this struct. - * Otherwise, it must not be interpreted in any way. - */ - LV2_Event_Callback_Data callback_data; - - /** Take a reference to a non-POD event. - * - * If a plugin receives an event with type 0, it means the event is a - * pointer to some object in memory and not a flat sequence of bytes - * in the buffer. When receiving a non-POD event, the plugin already - * has an implicit reference to the event. If the event is stored AND - * passed to an output, or passed to two outputs, lv2_event_ref MUST - * be called on that event. - * If the event is only stored OR passed through, this is not necessary - * (as the plugin already has 1 implicit reference). - * - * The host guarantees that this function is realtime safe if the - * plugin is. - * - * @param event An event received at an input that will be copied to - * more than one output or duplicated in some other way. - * - * PLUGINS THAT VIOLATE THESE RULES MAY CAUSE CRASHES AND MEMORY LEAKS. - */ - uint32_t (*lv2_event_ref)(LV2_Event_Callback_Data callback_data, - LV2_Event* event); - - /** Drop a reference to a non-POD event. - * - * If a plugin receives an event with type 0, it means the event is a - * pointer to some object in memory and not a flat sequence of bytes - * in the buffer. If the plugin does not pass the event through to - * an output or store it internally somehow, it MUST call this function - * on the event (more information on using non-POD events below). - * - * The host guarantees that this function is realtime safe if the - * plugin is. - * - * @param event An event received at an input that will not be copied to - * an output or stored in any way. - * - * PLUGINS THAT VIOLATE THESE RULES MAY CAUSE CRASHES AND MEMORY LEAKS. - */ - uint32_t (*lv2_event_unref)(LV2_Event_Callback_Data callback_data, - LV2_Event* event); - -} LV2_Event_Feature; - - -#endif // LV2_EVENT_H - diff --git a/src/effects/lv2/lv2_event_helpers.h b/src/effects/lv2/lv2_event_helpers.h deleted file mode 100644 index c1984f7ee..000000000 --- a/src/effects/lv2/lv2_event_helpers.h +++ /dev/null @@ -1,261 +0,0 @@ -/* lv2_event_helpers.h - Helper functions for the LV2 events extension. - * - * Copyright (C) 2008 Dave Robillard - * - * This header is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This header is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this header; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA - */ - -#ifndef LV2_EVENT_HELPERS_H -#define LV2_EVENT_HELPERS_H - -#include -//#include -#include -#include - -#include "lv2_event.h" - -/** @file - * This header defines some helper functions for the the LV2 events extension - * with URI ('lv2ev'). - * - * These functions are provided for convenience only, use of them is not - * required for supporting lv2ev (i.e. the events extension is defined by the - * raw buffer format described in lv2_event.h and NOT by this API). - * - * Note that these functions are all static inline which basically means: - * do not take the address of these functions. */ - - -/** Pad a size to 64 bits (for event sizes) */ -static inline uint16_t -lv2_event_pad_size(uint16_t size) -{ - return (size + 7) & (~7); -} - - -/** Initialize (empty, reset..) an existing event buffer. - * The contents of buf are ignored entirely and overwritten, except capacity - * which is unmodified. */ -static inline void -lv2_event_buffer_reset(LV2_Event_Buffer* buf, uint16_t stamp_type, uint8_t *data) -{ - buf->data = data; - buf->header_size = sizeof(LV2_Event_Buffer); - buf->stamp_type = stamp_type; - buf->event_count = 0; - buf->size = 0; -} - - -/** Allocate a new, empty event buffer. */ -static inline LV2_Event_Buffer* -lv2_event_buffer_new(uint32_t capacity, uint16_t stamp_type) -{ - LV2_Event_Buffer* buf = (LV2_Event_Buffer*)malloc(sizeof(LV2_Event_Buffer) + capacity); - if (buf != NULL) { - buf->capacity = capacity; - lv2_event_buffer_reset(buf, stamp_type, (uint8_t *)(buf + 1)); - return buf; - } else { - return NULL; - } -} - - -/** An iterator over an LV2_Event_Buffer. - * - * Multiple simultaneous read iterators over a single buffer is fine, - * but changing the buffer invalidates all iterators (e.g. RW Lock). */ -typedef struct { - LV2_Event_Buffer* buf; - uint32_t offset; -} LV2_Event_Iterator; - - -/** Reset an iterator to point to the start of @a buf. - * @return True if @a iter is valid, otherwise false (buffer is empty) */ -static inline bool -lv2_event_begin(LV2_Event_Iterator* iter, - LV2_Event_Buffer* buf) -{ - iter->buf = buf; - iter->offset = 0; - return (buf->size > 0); -} - - -/** Check if @a iter is valid.. - * @return True if @a iter is valid, otherwise false (past end of buffer) */ -static inline bool -lv2_event_is_valid(LV2_Event_Iterator* iter) -{ - return (iter->offset < iter->buf->size); -} - - -/** Advance @a iter forward one event. - * @a iter must be valid. - * @return True if @a iter is valid, otherwise false (reached end of buffer) */ -static inline bool -lv2_event_increment(LV2_Event_Iterator* iter) -{ - assert(lv2_event_is_valid(iter)); - - LV2_Event* const ev = (LV2_Event*)( - (uint8_t*)iter->buf->data + iter->offset); - - iter->offset += lv2_event_pad_size(sizeof(LV2_Event) + ev->size); - - return true; -} - - -/** Dereference an event iterator (get the event currently pointed at). - * @a iter must be valid. - * @a data if non-NULL, will be set to point to the contents of the event - * returned. - * @return A Pointer to the event @a iter is currently pointing at, or NULL - * if the end of the buffer is reached (in which case @a data is - * also set to NULL). */ -static inline LV2_Event* -lv2_event_get(LV2_Event_Iterator* iter, - uint8_t** data) -{ - assert(lv2_event_is_valid(iter)); - - LV2_Event* const ev = (LV2_Event*)( - (uint8_t*)iter->buf->data + iter->offset); - - if (data) - *data = (uint8_t*)ev + sizeof(LV2_Event); - - return ev; -} - - -/** Get the type of the non-POD event referenced by an event iterator. - * @a iter must be valid. - * @return The type of the non-POD event, or 0 if the event is not non-POD. */ -static inline uint16_t -lv2_event_get_nonpod_type(LV2_Event_Iterator* iter) -{ - assert(lv2_event_is_valid(iter)); - - LV2_Event* const ev = (LV2_Event*)( - (uint8_t*)iter->buf->data + iter->offset); - - if (ev->type != 0 || ev->size < 2) - return 0; - - return *(uint16_t*)((uint8_t*)ev + sizeof(LV2_Event)); -} - - -/** Write an event at @a iter. - * The event (if any) pointed to by @iter will be overwritten, and @a iter - * incremented to point to the following event (i.e. several calls to this - * function can be done in sequence without twiddling iter in-between). - * @return True if event was written, otherwise false (buffer is full). */ -static inline bool -lv2_event_write(LV2_Event_Iterator* iter, - uint32_t frames, - uint32_t subframes, - uint16_t type, - uint16_t size, - const uint8_t* data) -{ - if (iter->buf->capacity - iter->buf->size < sizeof(LV2_Event) + size) - return false; - - LV2_Event* const ev = (LV2_Event*)( - (uint8_t*)iter->buf->data + iter->offset); - - ev->frames = frames; - ev->subframes = subframes; - ev->type = type; - ev->size = size; - memcpy((uint8_t*)ev + sizeof(LV2_Event), data, size); - ++iter->buf->event_count; - - size = lv2_event_pad_size(sizeof(LV2_Event) + size); - iter->buf->size += size; - iter->offset += size; - - return true; -} - - -/** Reserve space for an event in the buffer and return a pointer to - * the memory where the caller can write the event data, or NULL if there - * is not enough room in the buffer. */ -static inline uint8_t* -lv2_event_reserve(LV2_Event_Iterator* iter, - uint32_t frames, - uint32_t subframes, - uint16_t type, - uint16_t size) -{ - size = lv2_event_pad_size(size); - if (iter->buf->capacity - iter->buf->size < sizeof(LV2_Event) + size) - return NULL; - - LV2_Event* const ev = (LV2_Event*)((uint8_t*)iter->buf->data + - iter->offset); - - ev->frames = frames; - ev->subframes = subframes; - ev->type = type; - ev->size = size; - ++iter->buf->event_count; - - size = lv2_event_pad_size(sizeof(LV2_Event) + size); - iter->buf->size += size; - iter->offset += size; - - return (uint8_t*)ev + sizeof(LV2_Event); -} - - -/** Write an event at @a iter. - * The event (if any) pointed to by @iter will be overwritten, and @a iter - * incremented to point to the following event (i.e. several calls to this - * function can be done in sequence without twiddling iter in-between). - * @return True if event was written, otherwise false (buffer is full). */ -static inline bool -lv2_event_write_event(LV2_Event_Iterator* iter, - const LV2_Event* ev, - const uint8_t* data) -{ - if (iter->buf->capacity - iter->buf->size < sizeof(LV2_Event) + ev->size) - return false; - - LV2_Event* const write_ev = (LV2_Event*)( - (uint8_t*)iter->buf->data + iter->offset); - - *write_ev = *ev; - memcpy((uint8_t*)write_ev + sizeof(LV2_Event), data, ev->size); - ++iter->buf->event_count; - - const uint16_t size = lv2_event_pad_size(sizeof(LV2_Event) + ev->size); - iter->buf->size += size; - iter->offset += size; - - return true; -} - -#endif // LV2_EVENT_HELPERS_H - diff --git a/src/effects/lv2/lv2_uri_map.h b/src/effects/lv2/lv2_uri_map.h deleted file mode 100644 index 8c281f3cb..000000000 --- a/src/effects/lv2/lv2_uri_map.h +++ /dev/null @@ -1,88 +0,0 @@ -/* lv2_uri_map.h - C header file for the LV2 URI Map extension. - * - * Copyright (C) 2008 Dave Robillard - * - * This header is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This header is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this header; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA - */ - -#ifndef LV2_URI_MAP_H -#define LV2_URI_MAP_H - -#define LV2_URI_MAP_URI "http://lv2plug.in/ns/ext/uri-map" - -#include - -/** @file - * This header defines the LV2 URI Map extension with the URI - * (preferred prefix 'lv2urimap'). - * - * This extension defines a simple mechanism for plugins to map URIs to - * integers, usually for performance reasons (e.g. processing events - * typed by URIs in real time). The expected use case is for plugins to - * map URIs to integers for things they 'understand' at instantiation time, - * and store those values for use in the audio thread without doing any string - * comparison. This allows the extensibility of RDF with the performance of - * integers (or centrally defined enumerations). - */ - - -/** Opaque pointer to host data. */ -typedef void* LV2_URI_Map_Callback_Data; - - -/** The data field of the LV2_Feature for this extension. - * - * To support this feature the host must pass an LV2_Feature struct to the - * plugin's instantiate method with URI "http://lv2plug.in/ns/ext/uri-map" - * and data pointed to an instance of this struct. - */ -typedef struct { - - /** Opaque pointer to host data. - * - * The plugin MUST pass this to any call to functions in this struct. - * Otherwise, it must not be interpreted in any way. - */ - LV2_URI_Map_Callback_Data callback_data; - - /** Get the numeric ID of a URI from the host. - * - * @param callback_data Must be the callback_data member of this struct. - * @param map The 'context' of this URI. Certain extensions may define a - * URI that must be passed here with certain restrictions on the - * return value (e.g. limited range). This value may be NULL if - * the plugin needs an ID for a URI in general. - * @param uri The URI to be mapped to an integer ID. - * - * This function is referentially transparent - any number of calls with - * the same arguments is guaranteed to return the same value over the life - * of a plugin instance (though the same URI may return different values - * with a different map parameter). However, this function is not - * necessarily very fast: plugins should cache any IDs they might need in - * performance critical situations. - * The return value 0 is reserved and means an ID for that URI could not - * be created for whatever reason. Extensions may define more precisely - * what this means, but in general plugins should gracefully handle 0 - * and consider whatever they wanted the URI for "unsupported". - */ - uint32_t (*uri_to_id)(LV2_URI_Map_Callback_Data callback_data, - const char* map, - const char* uri); - -} LV2_URI_Map_Feature; - - -#endif // LV2_URI_MAP_H - diff --git a/src/effects/nyquist/LoadNyquist.cpp b/src/effects/nyquist/LoadNyquist.cpp index a596f1f8e..042bcf6d8 100644 --- a/src/effects/nyquist/LoadNyquist.cpp +++ b/src/effects/nyquist/LoadNyquist.cpp @@ -8,8 +8,12 @@ **********************************************************************/ -#include "../EffectManager.h" +#include "../../AudacityApp.h" + +#include + #include "Nyquist.h" + #include "LoadNyquist.h" // ============================================================================ @@ -64,12 +68,12 @@ wxString NyquistEffectsModule::GetPath() wxString NyquistEffectsModule::GetSymbol() { - return wxT("Nyquist Effects"); + return wxTRANSLATE("Nyquist Effects"); } wxString NyquistEffectsModule::GetName() { - return wxTRANSLATE("Nyquist Effects"); + return GetSymbol(); } wxString NyquistEffectsModule::GetVendor() @@ -94,65 +98,71 @@ wxString NyquistEffectsModule::GetDescription() bool NyquistEffectsModule::Initialize() { - // Nothing to do here - return true; + wxArrayString audacityPathList = wxGetApp().audacityPathList; + + for (size_t i = 0, cnt = audacityPathList.GetCount(); i < cnt; i++) + { + wxFileName name(audacityPathList[i], wxT("")); + name.AppendDir(wxT("nyquist")); + name.SetFullName(wxT("nyquist.lsp")); + if (name.FileExists()) + { + // set_xlisp_path doesn't handle fn_Str() in Unicode build. May or may not actually work. + nyx_set_xlisp_path(name.GetPath().ToUTF8()); + return true; + } + } + + wxLogWarning(wxT("Critical Nyquist files could not be found. Nyquist effects will not work.")); + + return false; } void NyquistEffectsModule::Terminate() { - // Nothing to do here + nyx_set_xlisp_path(NULL); + return; } -bool NyquistEffectsModule::AutoRegisterPlugins(PluginManagerInterface & pm) +bool NyquistEffectsModule::AutoRegisterPlugins(PluginManagerInterface & WXUNUSED(pm)) { - // For Nyquist, we autoregister plugins at this time using the legacy - // interface. This will change eventually. - - wxArrayString pathList = EffectNyquist::GetNyquistSearchPath(); + // Nothing to do here + return false; +} + +wxArrayString NyquistEffectsModule::FindPlugins(PluginManagerInterface & pm) +{ + wxArrayString pathList = NyquistEffect::GetNyquistSearchPath(); wxArrayString files; - // Create one "interactive Nyquist" effect - EffectNyquist *effect = new EffectNyquist(wxT("")); - EffectManager::Get().RegisterEffect(this, effect); - + // Add the Nyquist prompt effect + files.Add(NYQUIST_PROMPT_ID); + // Load .ny plug-ins pm.FindFilesInPathList(wxT("*.ny"), pathList, files); #ifdef __WXGTK__ pm.FindFilesInPathList(wxT("*.NY"), pathList, files); // Ed's fix for bug 179 #endif - for (size_t i = 0; i < files.GetCount(); i++) + return files; +} + +bool NyquistEffectsModule::RegisterPlugin(PluginManagerInterface & pm, const wxString & path) +{ + NyquistEffect effect(path); + if (effect.IsOk()) { - EffectNyquist *effect = new EffectNyquist(files[i]); - if (effect->LoadedNyFile()) - { - EffectManager::Get().RegisterEffect(this, effect); - } - else - { - delete effect; - } + pm.RegisterPlugin(this, &effect); + return true; } - return true; -} - -wxArrayString NyquistEffectsModule::FindPlugins(PluginManagerInterface & WXUNUSED(pm)) -{ - // Nothing to do here yet - return wxArrayString(); -} - -bool NyquistEffectsModule::RegisterPlugin(PluginManagerInterface & WXUNUSED(pm), const wxString & WXUNUSED(path)) -{ - // Nothing to do here yet return false; } bool NyquistEffectsModule::IsPluginValid(const wxString & path) { - if (path == wxT("nyquist prompt")) + if (path == NYQUIST_PROMPT_ID) { return true; } @@ -162,11 +172,8 @@ bool NyquistEffectsModule::IsPluginValid(const wxString & path) IdentInterface *NyquistEffectsModule::CreateInstance(const wxString & path) { - // Normally, we wouldn't have anything to do here since we're autoregistering, but - // if we have Nyquist effects in directories we didn't scan. - - EffectNyquist *effect = new EffectNyquist(path); - if (effect->LoadedNyFile()) + NyquistEffect *effect = new NyquistEffect(path); + if (effect->IsOk()) { return effect; } @@ -178,10 +185,13 @@ IdentInterface *NyquistEffectsModule::CreateInstance(const wxString & path) void NyquistEffectsModule::DeleteInstance(IdentInterface *instance) { - // Nothing to do here + NyquistEffect *effect = dynamic_cast(instance); + if (effect) + { + delete effect; + } } // ============================================================================ // NyquistEffectsModule implementation // ============================================================================ - diff --git a/src/effects/nyquist/Nyquist.cpp b/src/effects/nyquist/Nyquist.cpp index 3d3f406d1..182380e64 100644 --- a/src/effects/nyquist/Nyquist.cpp +++ b/src/effects/nyquist/Nyquist.cpp @@ -8,24 +8,14 @@ ******************************************************************//** -\class EffectNyquist +\class NyquistEffect \brief An Effect that calls up a Nyquist (XLISP) plug in, i.e. many possible effects from this one class. *//****************************************************************//** -\class NyquistInputDialog -\brief Dialog used with EffectNyquist - -*//****************************************************************//** - \class NyquistOutputDialog -\brief Dialog used with EffectNyquist - -*//****************************************************************//** - -\class NyquistDialog -\brief Base class for NyquistInputDialog and NyquistOutputDialog. +\brief Dialog used with NyquistEffect *//****************************************************************//** @@ -34,21 +24,20 @@ effects from this one class. *//*******************************************************************/ - - #include "../../Audacity.h" #include #include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include "../../AudacityApp.h" @@ -62,6 +51,8 @@ effects from this one class. #include "../../widgets/valnum.h" #include "../../Prefs.h" +#include "FileDialog.h" + #include "Nyquist.h" #ifndef nyx_returns_start_and_end_time @@ -74,7 +65,24 @@ effects from this one class. #include #include -#include +enum +{ + ID_Editor = 10000, + ID_Version, + ID_Load, + ID_Save, + ID_Clear, + ID_Debug, + + ID_Slider = 11000, + ID_Text = 12000, + ID_Choice = 13000 +}; + +#define UNINITIALIZED_CONTROL ((double)99999999.99) + +static const wxChar *KEY_Version = wxTRANSLATE("Version"); +static const wxChar *KEY_Command = wxTRANSLATE("Command"); /////////////////////////////////////////////////////////////////////////////// // @@ -82,17 +90,29 @@ effects from this one class. // /////////////////////////////////////////////////////////////////////////////// -#define UNINITIALIZED_CONTROL ((double)99999999.99) - +#include WX_DEFINE_OBJARRAY(NyqControlArray); -EffectNyquist::EffectNyquist(wxString fName) +BEGIN_EVENT_TABLE(NyquistEffect, wxEvtHandler) + EVT_BUTTON(ID_Load, NyquistEffect::OnLoad) + EVT_BUTTON(ID_Save, NyquistEffect::OnSave) + EVT_BUTTON(ID_Clear, NyquistEffect::OnClear) + EVT_BUTTON(ID_Debug, NyquistEffect::OnDebug) + + EVT_COMMAND_RANGE(ID_Slider, ID_Slider+99, + wxEVT_COMMAND_SLIDER_UPDATED, NyquistEffect::OnSlider) + EVT_COMMAND_RANGE(ID_Text, ID_Text+99, + wxEVT_COMMAND_TEXT_UPDATED, NyquistEffect::OnText) + EVT_COMMAND_RANGE(ID_Choice, ID_Choice + 99, + wxEVT_COMMAND_CHOICE_SELECTED, NyquistEffect::OnChoice) +END_EVENT_TABLE() + +NyquistEffect::NyquistEffect(wxString fName) { mAction = _("Applying Nyquist Effect..."); mInputCmd = wxEmptyString; mCmd = wxEmptyString; - SetEffectFlags(HIDDEN_EFFECT); - mInteractive = false; + mIsPrompt = false; mExternal = false; mCompiler = false; mDebug = false; @@ -100,6 +120,7 @@ EffectNyquist::EffectNyquist(wxString fName) mOK = false; mAuthor = wxT("N/A"); mCopyright = wxT("N/A"); + // set clip/split handling when applying over clip boundary. mRestoreSplits = true; // Default: Restore split lines. mMergeClips = -1; // Default (auto): Merge if length remains unchanged. @@ -110,61 +131,57 @@ EffectNyquist::EffectNyquist(wxString fName) mBreak = false; mCont = false; - if (!SetXlispPath()) { - wxLogWarning(wxT("Critical Nyquist files could not be found. Nyquist effects will not work.")); - return; - } - - if (fName == wxT("")) { - // Interactive Nyquist - gPrefs->Read(wxT("/Effects/NyquistPrompt/Version"), &mVersion, 4); + // Interactive Nyquist + if (fName == NYQUIST_PROMPT_ID) + { + mName = wxTRANSLATE("Nyquist Prompt"); + mType = EffectTypeProcess; mOK = true; - mInteractive = true; - mName = wxTRANSLATE("Nyquist Prompt..."); - SetEffectFlags(PROCESS_EFFECT | BUILTIN_EFFECT | ADVANCED_EFFECT); + mIsPrompt = true; + return; } - // wxLogNull dontLog; // Vaughan, 2010-08-27: Why turn off logging? Logging is good! - mName = wxFileName(fName).GetName(); mFileName = wxFileName(fName); mFileModified = mFileName.GetModificationTime(); ParseFile(); } -EffectNyquist::~EffectNyquist() +NyquistEffect::~NyquistEffect() { - nyx_set_xlisp_path(NULL); } -// ============================================================================ // IdentInterface implementation -// ============================================================================ -wxString EffectNyquist::GetPath() +wxString NyquistEffect::GetPath() { - if (mFileName.GetFullPath().IsEmpty()) + if (mIsPrompt) { - return wxT("nyquist prompt"); + return NYQUIST_PROMPT_ID; } return mFileName.GetFullPath(); } -wxString EffectNyquist::GetSymbol() +wxString NyquistEffect::GetSymbol() { + if (mIsPrompt) + { + return wxTRANSLATE("Nyquist Prompt"); + } + return mName; } -wxString EffectNyquist::GetName() +wxString NyquistEffect::GetName() { return GetSymbol(); } -wxString EffectNyquist::GetVendor() +wxString NyquistEffect::GetVendor() { - if (GetPath() == wxT("nyquist prompt")) + if (mIsPrompt) { return _("Audacity"); } @@ -172,296 +189,184 @@ wxString EffectNyquist::GetVendor() return mAuthor; } -wxString EffectNyquist::GetVersion() +wxString NyquistEffect::GetVersion() { return wxT("N/A"); } -wxString EffectNyquist::GetDescription() +wxString NyquistEffect::GetDescription() { return mCopyright; } -// ============================================================================ // EffectIdentInterface implementation -// ============================================================================ -EffectType EffectNyquist::GetType() +EffectType NyquistEffect::GetType() { - // For now, relegate to Effect() - return Effect::GetType(); + return mType; } -wxString EffectNyquist::GetFamily() +wxString NyquistEffect::GetFamily() { return NYQUISTEFFECTS_FAMILY; } -bool EffectNyquist::IsInteractive() +bool NyquistEffect::IsInteractive() { - // For now, relegate to Effect() - return Effect::IsInteractive(); -} - -bool EffectNyquist::IsDefault() -{ - if (GetPath() == wxT("nyquist prompt")) + if (mIsPrompt) { return true; } - return false; + return mControls.GetCount() != 0; } -bool EffectNyquist::IsLegacy() +bool NyquistEffect::IsDefault() { - return true; + return mIsPrompt; } -bool EffectNyquist::SupportsRealtime() +// EffectClientInterface implementation + +bool NyquistEffect::GetAutomationParameters(EffectAutomationParameters & parms) { - return false; -} - -bool EffectNyquist::SupportsAutomation() -{ - return true; -} - -// ============================================================================ -// Effect Implementation -// ============================================================================ - -bool EffectNyquist::SupportsChains() -{ - return (GetEffectFlags() & PROCESS_EFFECT) != 0; -} - -bool EffectNyquist::TransferParameters( Shuttle & shuttle ) -{ - - for (size_t i = 0; i < mControls.GetCount(); i++) { - NyqControl *ctrl = &mControls[i]; - double d = ctrl->val; - bool good = false; - - if (d == UNINITIALIZED_CONTROL) { - if (ctrl->type != NYQ_CTRL_STRING) { - if (!shuttle.mbStoreInClient) { - d = GetCtrlValue(ctrl->valStr); - } - } - } - - if (ctrl->type == NYQ_CTRL_REAL) { - good = shuttle.TransferDouble(ctrl->var, d, 0.0); - } - else if (ctrl->type == NYQ_CTRL_INT) { - int val = (int) d; - good = shuttle.TransferInt(ctrl->var, val, 0); - d = (double) val; - } - else if (ctrl->type == NYQ_CTRL_CHOICE) { - //str is coma separated labels for each choice - wxString str = ctrl->label; - wxArrayString choices; - - while (1) { - int ci = str.Find( ',' ); //coma index - - if (ci == -1) { - choices.Add( str ); - break; - } - else { - choices.Add(str.Left(ci)); - } - - str = str.Right(str.length() - ci - 1); - } - - int cnt = choices.GetCount(); - if (choices.GetCount() > 0) { - wxString *array = NULL; - array = new wxString[cnt]; - for (int j = 0; j < cnt; j++ ) { - array[j] = choices[j]; - } - - int val = (int) d; - good = shuttle.TransferEnum(ctrl->var, val, cnt, array); - d = (double) val; - - delete [] array; - } - } - else if (ctrl->type == NYQ_CTRL_STRING) { - good = shuttle.TransferString(ctrl->var, ctrl->valStr, wxEmptyString); - } - - if (ctrl->type != NYQ_CTRL_STRING) { - if (shuttle.mbStoreInClient && good) { - ctrl->val = d; - } - } - } - - return true; -} - -bool EffectNyquist::GeneratorPreview() -{ - // Enable Nyquist generator plug-ins to create preview without a selection - return (mEnablePreview && (GetEffectFlags() & INSERT_EFFECT)); -} - -bool EffectNyquist::PromptUser() -{ - while (mInteractive) { - NyquistInputDialog dlog(wxGetTopLevelParent(NULL), -1, - _("Nyquist Prompt"), - _("Enter Nyquist Command: "), - mInputCmd); - dlog.mVersion = mVersion; - dlog.CentreOnParent(); - int result = dlog.ShowModal(); - - if (result == wxID_CANCEL) { - return false; - } - - mVersion = dlog.mVersion; - - /*if (result == eDebugID) { - mDebug = true; - }*/ - mDebug = (result == eDebugID); - - // remember exact input in mInputCmd which will appear in the next - // NyquistInputDialog. Copy to mCmd for possible embedding in - // "function main() begin ... end": - mCmd = mInputCmd = dlog.GetCommand(); - - // Is this LISP or SAL? Both allow comments. After comments, LISP - // must begin with "(". Technically, a LISP expression could be a - // symbol or number or string, etc., but these are not really - // useful expressions. If the input begins with a symbol, number, - // or string, etc., it is more likely an erroneous attempt to type - // a SAL expression (which should probably begin with "return"), - // so we will treat it as SAL. - - // this is a state machine to scan past LISP comments and white - // space to find the first real character of LISP or SAL. Note - // that #| ... |# style comments are not valid in SAL, so we do - // not skip these. Instead, "#|" indicates LISP if found. - // - unsigned int i = 0; - bool inComment = false; // handle "; ... \n" comments - while (i < mCmd.Len()) { - if (inComment) { - inComment = (mCmd[i] != wxT('\n')); - } else if (mCmd[i] == wxT(';')) { - inComment = true; - } else if (!wxIsspace(mCmd[i])) { - break; // found the first non-comment, non-space character - } - i++; - } - - // invariant: i == mCmd.Len() | - // mCmd[i] is first non-comment, non-space character - - mIsSal = false; - if (mCmd.Len() > i && mCmd[i] != wxT('(') && - (mCmd[i] != wxT('#') || mCmd.Len() <= i + 1 || - mCmd[i + 1] != wxT('|'))) { - mIsSal = true; - wxString cmdUp = mCmd.Upper(); - int returnLoc = cmdUp.Find(wxT("RETURN")); - if (returnLoc == wxNOT_FOUND) { - wxMessageBox(_("Your code looks like SAL syntax, but there is no return statement. Either use a return statement such as\n\treturn s * 0.1\nfor SAL, or begin with an open parenthesis such as\n\t(mult s 0.1)\n for LISP."), _("Error in Nyquist code"), wxOK | wxCENTRE); - return false; - } - } - - if (result != ePreviewID) - { - return true; - } - - Preview(); - } - - if (!mExternal) { - //TODO: If we want to auto-add parameters from spectral selection, - //we will need to modify this test. - //Note that removing it stops the caching of parameter values, - //(during this session). - if (mFileName.GetModificationTime().IsLaterThan(mFileModified)) - { - ParseFile(); - mFileModified = mFileName.GetModificationTime(); - } - } - - if (mControls.GetCount() == 0) { + if (mExternal) + { return true; } - for (unsigned int i = 0; i < mControls.GetCount(); i++) { - NyqControl *ctrl = &mControls[i]; + if (mIsPrompt) + { + parms.Write(KEY_Command, mInputCmd); + parms.Write(KEY_Version, mVersion); - if (ctrl->type == NYQ_CTRL_STRING) { - continue; - } - - if (ctrl->val == UNINITIALIZED_CONTROL) { - ctrl->val = GetCtrlValue(ctrl->valStr); - } - - if (ctrl->type == NYQ_CTRL_CHOICE) { - continue; - } - - ctrl->low = GetCtrlValue(ctrl->lowStr); - ctrl->high = GetCtrlValue(ctrl->highStr); - - if (ctrl->high < ctrl->low) { - ctrl->high = ctrl->low + 1; - } - - if (ctrl->val < ctrl->low) { - ctrl->val = ctrl->low; - } - - if (ctrl->val > ctrl->high) { - ctrl->val = ctrl->high; - } - - ctrl->ticks = 1000; - if (ctrl->type == NYQ_CTRL_INT && - (ctrl->high - ctrl->low < ctrl->ticks)) { - ctrl->ticks = (int)(ctrl->high - ctrl->low); - } + return true; } - NyquistDialog dlog(mParent, -1, mName, mInfo, mEnablePreview, this); - dlog.CentreOnParent(); - int result = dlog.ShowModal(); + for (size_t c = 0, cnt = mControls.GetCount(); c < cnt; c++) + { + NyqControl & ctrl = mControls[c]; + double d = ctrl.val; - if (result == wxID_CANCEL) { - return false; + if (d == UNINITIALIZED_CONTROL && ctrl.type != NYQ_CTRL_STRING) + { + d = GetCtrlValue(ctrl.valStr); + } + + if (ctrl.type == NYQ_CTRL_REAL) + { + parms.Write(ctrl.var, d); + } + else if (ctrl.type == NYQ_CTRL_INT) + { + parms.Write(ctrl.var, (int) d); + } + else if (ctrl.type == NYQ_CTRL_CHOICE) + { + parms.WriteEnum(ctrl.var, (int) d, wxStringTokenize(ctrl.label, wxT(","))); + } + else if (ctrl.type == NYQ_CTRL_STRING) + { + parms.Write(ctrl.var, ctrl.valStr); + } } - /* if (result == eDebugID) { - mDebug = true; - } */ - mDebug = (result == eDebugID); - return true; } -bool EffectNyquist::Process() +bool NyquistEffect::SetAutomationParameters(EffectAutomationParameters & parms) +{ + if (mExternal) + { + return true; + } + + if (mIsPrompt) + { + parms.Read(KEY_Command, &mInputCmd, wxEmptyString); + parms.Read(KEY_Version, &mVersion, mVersion); + + return true; + } + + // First pass verifies values + for (size_t c = 0, cnt = mControls.GetCount(); c < cnt; c++) + { + NyqControl & ctrl = mControls[c]; + bool good = false; + + if (ctrl.type == NYQ_CTRL_REAL) + { + double val; + good = parms.Read(ctrl.var, &val) && + val >= ctrl.low && + val <= ctrl.high; + } + else if (ctrl.type == NYQ_CTRL_INT) + { + int val; + good = parms.Read(ctrl.var, &val) && + val >= ctrl.low && + val <= ctrl.high; + } + else if (ctrl.type == NYQ_CTRL_CHOICE) + { + int val; + good = parms.ReadEnum(ctrl.var, &val, wxStringTokenize(ctrl.label, wxT(","))) && + val != wxNOT_FOUND; + } + else if (ctrl.type == NYQ_CTRL_STRING) + { + wxString val; + good = parms.Read(ctrl.var, &val); + } + + if (!good) + { + return false; + } + } + + // Second pass sets the variables + for (size_t c = 0, cnt = mControls.GetCount(); c < cnt; c++) + { + NyqControl & ctrl = mControls[c]; + + double d = ctrl.val; + if (d == UNINITIALIZED_CONTROL && ctrl.type != NYQ_CTRL_STRING) + { + d = GetCtrlValue(ctrl.valStr); + } + + if (ctrl.type == NYQ_CTRL_REAL) + { + parms.Read(ctrl.var, &ctrl.val); + } + else if (ctrl.type == NYQ_CTRL_INT) + { + int val; + parms.Read(ctrl.var, &val); + ctrl.val = (double) val; + } + else if (ctrl.type == NYQ_CTRL_CHOICE) + { + int val; + parms.ReadEnum(ctrl.var, &val, wxStringTokenize(ctrl.label, wxT(","))); + ctrl.val = (double) val; + } + else if (ctrl.type == NYQ_CTRL_STRING) + { + wxString val; + parms.Read(ctrl.var, &ctrl.valStr); + } + } + + return true; +} + +// Effect Implementation + +bool NyquistEffect::Process() { bool success = true; @@ -472,7 +377,7 @@ bool EffectNyquist::Process() // We must copy all the tracks, because Paste needs label tracks to ensure // correct sync-lock group behavior when the timeline is affected; then we just want // to operate on the selected wave tracks - this->CopyInputTracks(Track::All); + CopyInputTracks(Track::All); SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks); mCurTrack[0] = (WaveTrack *) iter.First(); mOutputTime = 0; @@ -480,7 +385,7 @@ bool EffectNyquist::Process() mProgressIn = 0; mProgressOut = 0; mProgressTot = 0; - mScale = (GetEffectFlags() & PROCESS_EFFECT ? 0.5 : 1.0) / GetNumWaveGroups(); + mScale = (GetType() == EffectTypeProcess ? 0.5 : 1.0) / GetNumWaveGroups(); mStop = false; mBreak = false; @@ -488,7 +393,8 @@ bool EffectNyquist::Process() mTrackIndex = 0; - mDebugOutput = ""; + mDebugOutput = new std::string(); + mOutput.Clear(); if (mVersion >= 4) { @@ -503,7 +409,7 @@ bool EffectNyquist::Process() mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'HELP)\n"), EscapeString(FileNames::HtmlHelpDir().RemoveLast()).c_str()); mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'TEMP)\n"), EscapeString(FileNames::TempDir()).c_str()); - wxArrayString paths = EffectNyquist::GetNyquistSearchPath(); + wxArrayString paths = NyquistEffect::GetNyquistSearchPath(); wxString list; for (size_t i = 0, cnt = paths.GetCount(); i < cnt; i++) { @@ -628,7 +534,6 @@ bool EffectNyquist::Process() mProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'CENTER-HZ)\n"), centerHz.c_str()); mProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'HIGH-HZ)\n"), highHz.c_str()); mProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'BANDWIDTH)\n"), bandwidth.c_str()); - } // Keep track of whether the current track is first selected in its sync-lock group @@ -647,7 +552,7 @@ bool EffectNyquist::Process() if (mCurTrack[1]->GetRate() != mCurTrack[0]->GetRate()) { wxMessageBox(_("Sorry, cannot apply effect on stereo tracks where the tracks don't match."), wxT("Nyquist"), - wxOK | wxCENTRE, mParent); + wxOK | wxCENTRE, mUIParent); success = false; goto finish; } @@ -708,23 +613,96 @@ bool EffectNyquist::Process() finish: - if (mDebug && !mExternal) { - NyquistOutputDialog dlog(mParent, -1, - _("Nyquist"), - _("Nyquist Output: "), - NyquistToWxString(mDebugOutput.c_str())); - dlog.CentreOnParent(); - dlog.ShowModal(); - } - - this->ReplaceProcessedTracks(success); + ReplaceProcessedTracks(success); mDebug = false; + delete mDebugOutput; + return success; } -bool EffectNyquist::ProcessOne() +bool NyquistEffect::ShowInterface(wxWindow *parent, bool forceModal) +{ + // Show the normal (prompt or effect) interface + bool res = Effect::ShowInterface(parent, forceModal); +printf("res = %d %d %d\n", res, mIsPrompt, (int)mControls.GetCount()); + // We're done if the user clicked "Close", we are not the Nyquist Prompt, + // or the program currently loaded into the prompt doesn't have a UI. + if (!res || !mIsPrompt || mControls.GetCount() == 0) + { + return res; + } + + NyquistEffect effect(NYQUIST_WORKER_ID); + + effect.SetCommand(mInputCmd); + effect.mDebug = false; + + SelectedRegion region(mT0, mT1); + effect.DoEffect(parent, + mProjectRate, + mTracks, + mFactory, + ®ion, + true); + + return false; +} + +void NyquistEffect::PopulateOrExchange(ShuttleGui & S) +{ + if (mIsPrompt) + { + BuildPromptWindow(S); + } + else + { + BuildEffectWindow(S); + } +} + +bool NyquistEffect::TransferDataToWindow() +{ + mUIParent->TransferDataToWindow(); + bool success; + + if (mIsPrompt) + { + success = TransferDataToPromptWindow(); + } + else + { + + success = TransferDataToEffectWindow(); + } + + if (success) + { + EnablePreview(mEnablePreview); + } + + return success; +} + +bool NyquistEffect::TransferDataFromWindow() +{ + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } + + if (mIsPrompt) + { + return TransferDataFromPromptWindow(); + } + + return TransferDataFromEffectWindow(); +} + +// NyquistEffect implementation + +bool NyquistEffect::ProcessOne() { nyx_rval rval; @@ -833,7 +811,7 @@ bool EffectNyquist::ProcessOne() Internat::ToString(maxPeak).c_str()); } - if (GetEffectFlags() & INSERT_EFFECT) { + if (GetType() == EffectTypeGenerate) { nyx_set_audio_params(mCurTrack[0]->GetRate(), 0); } else { @@ -846,7 +824,7 @@ bool EffectNyquist::ProcessOne() // Restore the Nyquist sixteenth note symbol for Generate plugins. // See http://bugzilla.audacityteam.org/show_bug.cgi?id=490. - if (GetEffectFlags() & INSERT_EFFECT) { + if (GetType() == EffectTypeGenerate) { cmd += wxT("(setf s 0.25)\n"); } @@ -932,6 +910,7 @@ bool EffectNyquist::ProcessOne() mCurBuffer[i] = NULL; } + wxLogMessage(wxT("%s"), cmd.c_str()); rval = nyx_eval_expression(cmd.mb_str(wxConvUTF8)); if (!rval) { @@ -942,13 +921,13 @@ bool EffectNyquist::ProcessOne() if (rval == nyx_string) { wxMessageBox(NyquistToWxString(nyx_get_string()), wxT("Nyquist"), - wxOK | wxCENTRE, mParent); + wxOK | wxCENTRE, mUIParent); // True if not process type. // If not returning audio from process effect, // return first reult then stop (disables preview) // but allow all output from Nyquist Prompt. - return (!(GetEffectFlags() & PROCESS_EFFECT)|| mInteractive); + return (GetType() != EffectTypeProcess || mIsPrompt); } if (rval == nyx_double) { @@ -956,8 +935,8 @@ bool EffectNyquist::ProcessOne() str.Printf(_("Nyquist returned the value:") + wxString(wxT(" %f")), nyx_get_double()); wxMessageBox(str, wxT("Nyquist"), - wxOK | wxCENTRE, mParent); - return (!(GetEffectFlags() & PROCESS_EFFECT)|| mInteractive); + wxOK | wxCENTRE, mUIParent); + return (GetType() != EffectTypeProcess || mIsPrompt); } if (rval == nyx_int) { @@ -965,8 +944,8 @@ bool EffectNyquist::ProcessOne() str.Printf(_("Nyquist returned the value:") + wxString(wxT(" %d")), nyx_get_int()); wxMessageBox(str, wxT("Nyquist"), - wxOK | wxCENTRE, mParent); - return (!(GetEffectFlags() & PROCESS_EFFECT)|| mInteractive); + wxOK | wxCENTRE, mUIParent); + return (GetType() != EffectTypeProcess || mIsPrompt); } if (rval == nyx_labels) { @@ -984,7 +963,7 @@ bool EffectNyquist::ProcessOne() if (!ltrack) { ltrack = mFactory->NewLabelTrack(); - this->AddToOutputTracks((Track *)ltrack); + AddToOutputTracks((Track *)ltrack); } for (l = 0; l < numLabels; l++) { @@ -997,13 +976,13 @@ bool EffectNyquist::ProcessOne() ltrack->AddLabel(SelectedRegion(t0 + mT0, t1 + mT0), UTF8CTOWX(str)); } - return (!(GetEffectFlags() & PROCESS_EFFECT)|| mInteractive); + return (GetType() != EffectTypeProcess || mIsPrompt); } if (rval != nyx_audio) { // This should not happen, but leaving in for now just in case (Dec 2014) wxMessageBox(_("Undefined return value.\n"), wxT("Nyquist"), - wxOK | wxCENTRE, mParent); + wxOK | wxCENTRE, mUIParent); return false; } @@ -1013,21 +992,21 @@ bool EffectNyquist::ProcessOne() if (outChannels > mCurNumChannels) { wxMessageBox(_("Nyquist returned too many audio channels.\n"), wxT("Nyquist"), - wxOK | wxCENTRE, mParent); + wxOK | wxCENTRE, mUIParent); return false; } if (outChannels == -1) { wxMessageBox(_("Nyquist returned one audio channel as an array.\n"), wxT("Nyquist"), - wxOK | wxCENTRE, mParent); + wxOK | wxCENTRE, mUIParent); return false; } if (outChannels == 0) { wxMessageBox(_("Nyquist returned an empty array.\n"), wxT("Nyquist"), - wxOK | wxCENTRE, mParent); + wxOK | wxCENTRE, mUIParent); return false; } @@ -1063,7 +1042,7 @@ bool EffectNyquist::ProcessOne() if (mOutputTime <= 0) { wxMessageBox(_("Nyquist did not return audio.\n"), wxT("Nyquist"), - wxOK | wxCENTRE, mParent); + wxOK | wxCENTRE, mUIParent); for (i = 0; i < outChannels; i++) { delete mOutputTrack[i]; mOutputTrack[i] = NULL; @@ -1089,7 +1068,7 @@ bool EffectNyquist::ProcessOne() mCurTrack[i]->ClearAndPaste(mT0, mT1, out, mRestoreSplits, bMergeClips); } else { - mCurTrack[i]->ClearAndPaste(mT0, mT1, out, mRestoreSplits, mMergeClips); + mCurTrack[i]->ClearAndPaste(mT0, mT1, out, mRestoreSplits, mMergeClips != 0); } // If we were first in the group adjust non-selected group tracks @@ -1117,10 +1096,10 @@ bool EffectNyquist::ProcessOne() } // ============================================================================ -// EffectNyquist Implementation +// NyquistEffect Implementation // ============================================================================ -wxString EffectNyquist::NyquistToWxString(const char *nyqString) +wxString NyquistEffect::NyquistToWxString(const char *nyqString) { wxString str(nyqString, wxConvUTF8); if (nyqString != NULL && nyqString[0] && str.IsEmpty()) { @@ -1131,7 +1110,7 @@ wxString EffectNyquist::NyquistToWxString(const char *nyqString) return str; } -wxString EffectNyquist::EscapeString(const wxString & inStr) +wxString NyquistEffect::EscapeString(const wxString & inStr) { wxString str = inStr; @@ -1141,22 +1120,29 @@ wxString EffectNyquist::EscapeString(const wxString & inStr) return str; } -void EffectNyquist::Break() +void NyquistEffect::SetCommand(wxString cmd) +{ + mExternal = true; + + ParseCommand(cmd); +} + +void NyquistEffect::Break() { mBreak = true; } -void EffectNyquist::Continue() +void NyquistEffect::Continue() { mCont = true; } -void EffectNyquist::Stop() +void NyquistEffect::Stop() { mStop = true; } -wxString EffectNyquist::UnQuote(wxString s) +wxString NyquistEffect::UnQuote(wxString s) { wxString out; int len = s.Length(); @@ -1168,7 +1154,7 @@ wxString EffectNyquist::UnQuote(wxString s) return s; } -double EffectNyquist::GetCtrlValue(wxString s) +double NyquistEffect::GetCtrlValue(wxString s) { if (s == wxT("rate")) { TrackListOfKindIterator iter(Track::Wave, mTracks); @@ -1178,7 +1164,7 @@ double EffectNyquist::GetCtrlValue(wxString s) return Internat::CompatibleToDouble(s); } -void EffectNyquist::Parse(wxString line) +void NyquistEffect::Parse(wxString line) { wxArrayString tokens; @@ -1232,23 +1218,26 @@ void EffectNyquist::Parse(wxString line) if (len >= 2 && tokens[0] == wxT("type")) { if (tokens[1] == wxT("process")) { - SetEffectFlags(PROCESS_EFFECT | PLUGIN_EFFECT); + mType = EffectTypeProcess; } else if (tokens[1] == wxT("generate")) { - SetEffectFlags(INSERT_EFFECT | PLUGIN_EFFECT); + mType = EffectTypeGenerate; } else if (tokens[1] == wxT("analyze")) { - SetEffectFlags(ANALYZE_EFFECT | PLUGIN_EFFECT); + mType = EffectTypeAnalyze; } return; } if (len == 2 && tokens[0] == wxT("codetype")) { + // This will stop ParseProgram() from doing a best guess as program type. if (tokens[1] == wxT("lisp")) { mIsSal = false; + mFoundType = true; } else if (tokens[1] == wxT("sal")) { mIsSal = true; + mFoundType = true; } return; } @@ -1290,6 +1279,10 @@ void EffectNyquist::Parse(wxString line) if (len >= 2 && tokens[0] == wxT("name")) { mName = UnQuote(tokens[1]); + if (mName.EndsWith(wxT("..."))) + { + mName = mName.RemoveLast(3); + } return; } @@ -1307,6 +1300,9 @@ void EffectNyquist::Parse(wxString line) if (tokens[1] == wxT("enabled") || tokens[1] == wxT("true")) { mEnablePreview = true; } + else if (tokens[1] == wxT("disabled") || tokens[1] == wxT("false")) { + mEnablePreview = false; + } return; } @@ -1345,11 +1341,12 @@ void EffectNyquist::Parse(wxString line) ctrl.name = tokens[2]; ctrl.label = tokens[4]; ctrl.valStr = tokens[5]; + ctrl.val = GetCtrlValue(ctrl.valStr); if (tokens[3] == wxT("string")) { ctrl.type = NYQ_CTRL_STRING; } - else if (tokens[ 3 ] == wxT("choice")) { + else if (tokens[3] == wxT("choice")) { ctrl.type = NYQ_CTRL_CHOICE; } else { @@ -1378,10 +1375,28 @@ void EffectNyquist::Parse(wxString line) } ctrl.lowStr = tokens[6]; + ctrl.low = GetCtrlValue(ctrl.lowStr); ctrl.highStr = tokens[7]; - } + ctrl.high = GetCtrlValue(ctrl.highStr); - ctrl.val = UNINITIALIZED_CONTROL; + if (ctrl.high < ctrl.low) { + ctrl.high = ctrl.low + 1; + } + + if (ctrl.val < ctrl.low) { + ctrl.val = ctrl.low; + } + + if (ctrl.val > ctrl.high) { + ctrl.val = ctrl.high; + } + + ctrl.ticks = 1000; + if (ctrl.type == NYQ_CTRL_INT && + (ctrl.high - ctrl.low < ctrl.ticks)) { + ctrl.ticks = (int)(ctrl.high - ctrl.low); + } + } if( mPresetNames.Index( ctrl.var ) == wxNOT_FOUND ) { @@ -1396,100 +1411,83 @@ void EffectNyquist::Parse(wxString line) } } -void EffectNyquist::ParseFile() +bool NyquistEffect::ParseProgram(wxInputStream & stream) { - wxTextFile f(mFileName.GetFullPath()); - if (!f.Open()) - return; + if (!stream.IsOk()) + { + return false; + } + + wxTextInputStream pgm(stream); mCmd = wxT(""); - SetEffectFlags(PROCESS_EFFECT | PLUGIN_EFFECT); - mOK = false; - mEnablePreview = false; mIsSal = false; mControls.Clear(); - mDebug = false; + mCategories.Clear(); - int i; - int len = f.GetLineCount(); - wxString line; - for (i = 0; i < len; i++) { - line = f[i]; - if (line.Length() > 1 && line[0] == wxT(';')) { + mFoundType = false; + while (!stream.Eof() && stream.IsOk()) + { + wxString line = pgm.ReadLine().Trim(false); + if (line.Length() > 1 && line[0] == wxT(';')) + { Parse(line); } + else if (!mFoundType && line.Length() > 0) + { + mFoundType = true; + + if (line[0] == wxT('(') || + (line[0] == wxT('#') && line.Length() > 1 && line[1] == wxT('|'))) + { + mIsSal = false; + } + else if (line.MakeUpper().Find(wxT("RETURN")) != wxNOT_FOUND) + { + mIsSal = true; + } + else if (mIsPrompt) + { + wxMessageBox(_("Your code looks like SAL syntax, but there is no return statement. Either use a return statement such as\n\treturn s * 0.1\nfor SAL, or begin with an open parenthesis such as\n\t(mult s 0.1)\n for LISP."), _("Error in Nyquist code"), wxOK | wxCENTRE); + return false; + } + // Just throw it at Nyquist to see what happens + } + // preserve comments so that SAL effects compile with proper line numbers mCmd += line + wxT("\n"); } + + return true; } -void EffectNyquist::SetCommand(wxString cmd) +void NyquistEffect::ParseFile() { - mExternal = true; - mInteractive = false; - mCmd = wxT(""); - SetEffectFlags(INSERT_EFFECT | HIDDEN_EFFECT); - mOK = false; - mIsSal = false; - mControls.Clear(); + mEnablePreview = true; - wxStringTokenizer lines(cmd, wxT("\n")); - while (lines.HasMoreTokens()) { - wxString line = lines.GetNextToken(); + wxFileInputStream stream(mFileName.GetFullPath()); - if (line.Length() > 1 && line[0] == wxT(';')) { - Parse(line); - } - else { - mCmd += line + wxT("\n"); - } - } + ParseProgram(stream); } -bool EffectNyquist::SetXlispPath() +bool NyquistEffect::ParseCommand(const wxString & cmd) { - wxString fname; + mEnablePreview = true; - fname = mXlispPath + wxFILE_SEP_PATH + wxT("nyinit.lsp"); - if (!(::wxFileExists(fname))) { - mXlispPath = wxT(""); - } + wxStringInputStream stream(cmd + wxT(" ")); - if (mXlispPath == wxT("")) { - wxArrayString audacityPathList = wxGetApp().audacityPathList; - wxArrayString pathList; - wxArrayString files; - unsigned int i; - - for (i = 0; i < audacityPathList.GetCount(); i++) { - wxString prefix = audacityPathList[i] + wxFILE_SEP_PATH; - wxGetApp().AddUniquePathToPathList(prefix + wxT("nyquist"), - pathList); - } - - wxGetApp().FindFilesInPathList(wxT("nyquist.lsp"), pathList, files); - - if (files.GetCount() > 0) { - mXlispPath = ::wxPathOnly(files[0]); - } - } - - /* set_xlisp_path doesn't handle fn_Str() in Unicode build. May or may not actually work. */ - nyx_set_xlisp_path(mXlispPath.mb_str()); - - fname = mXlispPath + wxFILE_SEP_PATH + wxT("nyinit.lsp"); - return ::wxFileExists(fname); + return ParseProgram(stream); } -int EffectNyquist::StaticGetCallback(float *buffer, int channel, +int NyquistEffect::StaticGetCallback(float *buffer, int channel, long start, long len, long totlen, void *userdata) { - EffectNyquist *This = (EffectNyquist *)userdata; + NyquistEffect *This = (NyquistEffect *)userdata; return This->GetCallback(buffer, channel, start, len, totlen); } -int EffectNyquist::GetCallback(float *buffer, int ch, +int NyquistEffect::GetCallback(float *buffer, int ch, long start, long len, long WXUNUSED(totlen)) { if (mCurBuffer[ch]) { @@ -1543,15 +1541,15 @@ int EffectNyquist::GetCallback(float *buffer, int ch, return 0; } -int EffectNyquist::StaticPutCallback(float *buffer, int channel, +int NyquistEffect::StaticPutCallback(float *buffer, int channel, long start, long len, long totlen, void *userdata) { - EffectNyquist *This = (EffectNyquist *)userdata; + NyquistEffect *This = (NyquistEffect *)userdata; return This->PutCallback(buffer, channel, start, len, totlen); } -int EffectNyquist::PutCallback(float *buffer, int channel, +int NyquistEffect::PutCallback(float *buffer, int channel, long start, long len, long totlen) { if (channel == 0) { @@ -1573,26 +1571,28 @@ int EffectNyquist::PutCallback(float *buffer, int channel, return -1; // failure } -void EffectNyquist::StaticOutputCallback(int c, void *This) +void NyquistEffect::StaticOutputCallback(int c, void *This) { - ((EffectNyquist *)This)->OutputCallback(c); + ((NyquistEffect *)This)->OutputCallback(c); } -void EffectNyquist::OutputCallback(int c) +void NyquistEffect::OutputCallback(int c) { if (mDebug && !mExternal) { - mDebugOutput += (char)c; + mOutput += c; return; } +// mOutput += wxString::FromUTF8((char *) &c, 1); + mOutput += c; std::cout << (char)c; } -void EffectNyquist::StaticOSCallback(void *This) +void NyquistEffect::StaticOSCallback(void *This) { - ((EffectNyquist *)This)->OSCallback(); + ((NyquistEffect *)This)->OSCallback(); } -void EffectNyquist::OSCallback() +void NyquistEffect::OSCallback() { if (mStop) { mStop = false; @@ -1622,7 +1622,7 @@ void EffectNyquist::OSCallback() #endif } -wxArrayString EffectNyquist::GetNyquistSearchPath() +wxArrayString NyquistEffect::GetNyquistSearchPath() { wxArrayString audacityPathList = wxGetApp().audacityPathList; wxArrayString pathList; @@ -1638,401 +1638,402 @@ wxArrayString EffectNyquist::GetNyquistSearchPath() return pathList; } -/////////////////////////////////////////////////////////////////////////////// -// -// NyquistDialog -// -/////////////////////////////////////////////////////////////////////////////// - -#define ID_NYQ_SLIDER 2000 -#define ID_NYQ_TEXT 3000 -#define ID_NYQ_CHOICE 4000 - -BEGIN_EVENT_TABLE(NyquistDialog, wxDialog) - EVT_BUTTON(wxID_OK, NyquistDialog::OnOk) - EVT_BUTTON(wxID_CANCEL, NyquistDialog::OnCancel) - EVT_BUTTON(eDebugID, NyquistDialog::OnDebug) - EVT_BUTTON(ePreviewID, NyquistDialog::OnPreview) - EVT_COMMAND_RANGE(ID_NYQ_SLIDER, ID_NYQ_SLIDER+99, - wxEVT_COMMAND_SLIDER_UPDATED, NyquistDialog::OnSlider) - EVT_COMMAND_RANGE(ID_NYQ_TEXT, ID_NYQ_TEXT+99, - wxEVT_COMMAND_TEXT_UPDATED, NyquistDialog::OnText) - EVT_COMMAND_RANGE( ID_NYQ_CHOICE, ID_NYQ_CHOICE + 99, - wxEVT_COMMAND_CHOICE_SELECTED, NyquistDialog::OnChoice ) -END_EVENT_TABLE() - -NyquistDialog::NyquistDialog(wxWindow * parent, wxWindowID id, - const wxString & title, - wxString info, - bool preview, - EffectNyquist *effect) -: wxDialog(parent, id, title) +bool NyquistEffect::TransferDataToPromptWindow() { - mEffect = effect; - mControls = &mEffect->mControls; - mInHandler = true; // prevents race condition on MSW + mCommandText->ChangeValue(mInputCmd); + mVersionCheckBox->SetValue(mVersion <= 3); - wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL); - - if (info.Length() > 0) { - wxControl *item; - item = new wxStaticText(this, -1, info); - item->SetName(info); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - mainSizer->Add(item, 0, wxALIGN_LEFT | wxALL, 5); - } - - wxFlexGridSizer *grid = new wxFlexGridSizer(4, 0, 0); - - for (size_t i = 0; i < mControls->GetCount(); i++) { - wxControl *item; - NyqControl *ctrl = &((*mControls)[i]); - - item = new wxStaticText(this, -1, ctrl->name + wxT(":")); - item->SetName(item->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - grid->Add(item, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxALL, 5); - - if (ctrl->type == NYQ_CTRL_STRING) { - grid->Add(10, 10); - - item = new wxTextCtrl(this, ID_NYQ_TEXT+i, ctrl->valStr, - wxDefaultPosition, wxSize(150, -1)); - item->SetName(ctrl->name); - grid->Add(item, 0, wxALIGN_CENTRE | wxALIGN_CENTER_VERTICAL | wxALL, 5); - } - else if (ctrl->type == NYQ_CTRL_CHOICE) { - //str is coma separated labels for each choice - wxString str = ctrl->label; - wxArrayString choices; - - while (1) { - int ci = str.Find( ',' ); //coma index - - if (ci == -1) { - choices.Add( str ); - break; - } - else { - choices.Add(str.Left(ci)); - } - - str = str.Right(str.length() - ci - 1); - } - - wxChoice *choice = new wxChoice(this, ID_NYQ_CHOICE + i, - wxDefaultPosition, wxSize(150, -1), choices); - choice->SetName(ctrl->name); - - int val = (int)ctrl->val; - if (val >= 0 && val < (int)choice->GetCount()) { - choice->SetSelection(val); - } - - grid->Add(10, 10); - grid->Add(choice, 0, wxALIGN_CENTRE | wxALIGN_CENTER_VERTICAL | wxALL, 5); - } - else { - // Integer or Real - int val = (int)(0.5 + ctrl->ticks * (ctrl->val - ctrl->low) / - (ctrl->high - ctrl->low)); - - item = new wxTextCtrl(this, ID_NYQ_TEXT+i, wxT(""), - wxDefaultPosition, wxSize(60, -1)); - item->SetName(ctrl->name); - if (ctrl->type == NYQ_CTRL_REAL) { - // > 12 decimal places can cause rounding errors in display. - FloatingPointValidator vld(12, &ctrl->val); - vld.SetRange(-FLT_MAX, FLT_MAX); - // Set number of decimal places - if (ctrl->high - ctrl->low < 10) { - vld.SetStyle(NUM_VAL_THREE_TRAILING_ZEROES); - } else if (ctrl->high - ctrl->low < 100) { - vld.SetStyle(NUM_VAL_TWO_TRAILING_ZEROES); - } else { - vld.SetStyle(NUM_VAL_ONE_TRAILING_ZERO); - } - item->SetValidator(vld); - } - else { - IntegerValidator vld(&ctrl->val); - vld.SetRange(INT_MIN, INT_MAX); - item->SetValidator(vld); - } - - grid->Add(item, 0, wxALIGN_CENTRE | wxALIGN_CENTER_VERTICAL | wxALL, 5); - - item = new wxSlider(this, ID_NYQ_SLIDER+i, val, 0, ctrl->ticks, - wxDefaultPosition, wxSize(150, -1)); - item->SetName(ctrl->name); - - grid->Add(item, 0, wxALIGN_CENTRE | wxALIGN_CENTER_VERTICAL | wxALL, 5); - } - - if (ctrl->type == NYQ_CTRL_CHOICE) { - grid->Add( 10, 10 ); - } - else { - item = new wxStaticText(this, -1, ctrl->label); - item->SetName(ctrl->label); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - grid->Add(item, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 5); - } - } - mainSizer->Add(grid, 0, wxALIGN_CENTRE | wxALL, 5); - - if (preview) { - mainSizer->Add(CreateStdButtonSizer(this, ePreviewButton | eDebugButton | eCancelButton | eOkButton), - 0, - wxEXPAND); - } else { - mainSizer->Add(CreateStdButtonSizer(this, eDebugButton | eCancelButton | eOkButton), - 0, - wxEXPAND); - } - - mInHandler = false; - - wxCommandEvent dummy; - OnSlider(dummy); - - SetAutoLayout(true); - SetSizer(mainSizer); - mainSizer->Fit(this); - mainSizer->SetSizeHints(this); + return true; } -// ============================================================================ -// NyquistDialog implementation -// ============================================================================ - -void NyquistDialog::OnSlider(wxCommandEvent & /* event */) +bool NyquistEffect::TransferDataToEffectWindow() { - if (mInHandler) { - return; // prevent recursing forever + for (size_t i = 0, cnt = mControls.GetCount(); i < cnt; i++) + { + NyqControl & ctrl = mControls[i]; + + if (ctrl.type == NYQ_CTRL_STRING) + { + wxTextCtrl *t = (wxTextCtrl *) mUIParent->FindWindow(ID_Text + i); + t->ChangeValue(ctrl.valStr); + } + else if (ctrl.type == NYQ_CTRL_CHOICE) + { + wxArrayString choices = wxStringTokenize(ctrl.label, wxT(",")); + + int val = (int)ctrl.val; + if (val < 0 || val >= (int)choices.GetCount()) + { + val = 0; + } + + wxChoice *c = (wxChoice *) mUIParent->FindWindow(ID_Choice + i); + c->SetSelection(val); + } + else + { + // wxTextCtrls are handled by the validators + double range = ctrl.high - ctrl.low; + int val = (int)(0.5 + ctrl.ticks * (ctrl.val - ctrl.low) / range); + wxSlider *s = (wxSlider *) mUIParent->FindWindow(ID_Slider + i); + s->SetValue(val); + } } - mInHandler = true; + return true; +} - for (size_t i = 0; i < mControls->GetCount(); i++) { - NyqControl *ctrl = &((*mControls)[i]); +bool NyquistEffect::TransferDataFromPromptWindow() +{ + mInputCmd = mCommandText->GetValue(); + mVersion = mVersionCheckBox->GetValue() ? 3 : 4; - if (ctrl->type == NYQ_CTRL_STRING || ctrl->type == NYQ_CTRL_CHOICE) { + return ParseCommand(mInputCmd); +} + +bool NyquistEffect::TransferDataFromEffectWindow() +{ + if (!mExternal) + { + //TODO: If we want to auto-add parameters from spectral selection, + //we will need to modify this test. + //Note that removing it stops the caching of parameter values, + //(during this session). + if (mFileName.GetModificationTime().IsLaterThan(mFileModified)) + { + ParseFile(); + mFileModified = mFileName.GetModificationTime(); + } + } + + if (mControls.GetCount() == 0) + { + return true; + } + + for (unsigned int i = 0; i < mControls.GetCount(); i++) + { + NyqControl *ctrl = &mControls[i]; + + if (ctrl->type == NYQ_CTRL_STRING) + { continue; } - wxSlider *slider = (wxSlider *)FindWindow(ID_NYQ_SLIDER + i); - wxTextCtrl *text = (wxTextCtrl *)FindWindow(ID_NYQ_TEXT + i); - wxASSERT(slider && text); - - int val = slider->GetValue(); - - double newVal = (val / (double)ctrl->ticks)* - (ctrl->high - ctrl->low) + ctrl->low; - - // Determine precision for displayed number - int precision = ctrl->high - ctrl->low < 1 ? 3 : - ctrl->high - ctrl->low < 10 ? 2 : - ctrl->high - ctrl->low < 100 ? 1 : - 0; - - // If the value is at least one tick different from the current value - // change it (this prevents changes from manually entered values unless - // the slider actually moved) - if (fabs(newVal - ctrl->val) >= (1 / (double)ctrl->ticks) * - (ctrl->high - ctrl->low) && - fabs(newVal - ctrl->val) >= pow(0.1, precision) / 2 ) + if (ctrl->val == UNINITIALIZED_CONTROL) { - // First round to the appropriate precision - newVal *= pow(10.0, precision); - newVal = floor(newVal + 0.5); - newVal /= pow(10.0, precision); + ctrl->val = GetCtrlValue(ctrl->valStr); + } - ctrl->val = newVal; + if (ctrl->type == NYQ_CTRL_CHOICE) + { + continue; + } - text->GetValidator()->TransferToWindow(); + ctrl->low = GetCtrlValue(ctrl->lowStr); + ctrl->high = GetCtrlValue(ctrl->highStr); + + if (ctrl->high < ctrl->low) + { + ctrl->high = ctrl->low + 1; + } + + if (ctrl->val < ctrl->low) + { + ctrl->val = ctrl->low; + } + + if (ctrl->val > ctrl->high) + { + ctrl->val = ctrl->high; + } + + ctrl->ticks = 1000; + if (ctrl->type == NYQ_CTRL_INT && + (ctrl->high - ctrl->low < ctrl->ticks)) + { + ctrl->ticks = (int)(ctrl->high - ctrl->low); } } - mInHandler = false; + return true; } -void NyquistDialog::OnChoice( wxCommandEvent &event ) +void NyquistEffect::BuildPromptWindow(ShuttleGui & S) { - if (mInHandler) { - return; // prevent recursing forever - } - mInHandler = true; - - unsigned int ctrlId = event.GetId() - ID_NYQ_CHOICE; - wxASSERT(ctrlId >= 0 && ctrlId < mControls->GetCount()); - - NyqControl *ctrl = &(mControls->Item(ctrlId)); - wxChoice *choice = (wxChoice *)FindWindow(ID_NYQ_CHOICE + ctrlId); - wxASSERT(choice); - - ctrl->val = choice->GetSelection(); - - mInHandler = false; -} - -void NyquistDialog::OnText(wxCommandEvent &event) -{ - if (mInHandler) { - return; // prevent recursing forever - } - - mInHandler = true; - - unsigned int ctrlId = event.GetId() - ID_NYQ_TEXT; - wxASSERT(ctrlId >= 0 && ctrlId < mControls->GetCount()); - - NyqControl *ctrl = &((*mControls)[ctrlId]); - wxTextCtrl *text = (wxTextCtrl *)FindWindow(ID_NYQ_TEXT + ctrlId); - wxASSERT(text); - - if (ctrl->type == NYQ_CTRL_STRING) { - ctrl->valStr = text->GetValue(); - } else { - text->GetValidator()->TransferFromWindow(); - wxSlider *slider = (wxSlider *)FindWindow(ID_NYQ_SLIDER + ctrlId); - wxASSERT(slider); - - int pos = (int)floor((ctrl->val - ctrl->low) / - (ctrl->high - ctrl->low) * ctrl->ticks + 0.5); - if (pos < 0) { - pos = 0; - } - else if (pos > ctrl->ticks) { - pos = ctrl->ticks; - } - - slider->SetValue(pos); - } - - mInHandler = false; -} - -void NyquistDialog::OnOk(wxCommandEvent & /* event */) -{ - // Transfer data - - EndModal(wxID_OK); -} - -void NyquistDialog::OnCancel(wxCommandEvent & /* event */) -{ - EndModal(wxID_CANCEL); -} - -void NyquistDialog::OnDebug(wxCommandEvent & /* event */) -{ - // Transfer data - - EndModal(eDebugID); -} - -void NyquistDialog::OnPreview(wxCommandEvent & /* event */) -{ - mEffect->Preview(); -} - -/////////////////////////////////////////////////////////////////////////////// -// -// NyquistInputDialog -// -/////////////////////////////////////////////////////////////////////////////// - -#define ID_VERSION 1001 - -BEGIN_EVENT_TABLE(NyquistInputDialog, wxDialog) - EVT_CHECKBOX(ID_VERSION, NyquistInputDialog::OnVersionCheck) - EVT_BUTTON(wxID_OK, NyquistInputDialog::OnOk) - EVT_BUTTON(wxID_CANCEL, NyquistInputDialog::OnCancel) - EVT_BUTTON(eDebugID, NyquistInputDialog::OnDebug) - EVT_BUTTON(ePreviewID, NyquistInputDialog::OnPreview) -END_EVENT_TABLE() - -NyquistInputDialog::NyquistInputDialog(wxWindow * parent, wxWindowID id, - const wxString & title, - const wxString & prompt, - wxString initialCommand) -: wxDialog(parent, id, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) -{ - // Recover last used setting. - gPrefs->Read(wxT("/Effects/NyquistPrompt/Version"), &mVersion, 4); - - ShuttleGui S(this, eIsCreating); - S.StartVerticalLay(); { - S.StartHorizontalLay(wxEXPAND, 0); + S.StartMultiColumn(3, wxEXPAND); { - S.AddVariableText(prompt); + S.SetStretchyCol(1); + + S.AddVariableText(_("Enter Nyquist Command: ")); + + S.AddSpace(1, 1); + + mVersionCheckBox = S.AddCheckBox(_("&Use legacy (version 3) syntax."), + (mVersion == 3) ? wxT("true") : wxT("false")); } - S.EndHorizontalLay(); + S.EndMultiColumn(); S.StartHorizontalLay(wxEXPAND, 1); { - mCommandText = S.AddTextWindow(initialCommand); + mCommandText = S.AddTextWindow(wxT("")); mCommandText->SetMinSize(wxSize(500, 200)); } S.EndHorizontalLay(); - S.StartHorizontalLay(wxEXPAND, 0); + S.StartHorizontalLay(wxALIGN_CENTER, 0); { - mVersionCheckBox = S.Id(ID_VERSION).AddCheckBox(_("&Use legacy (version 3) syntax."), - (mVersion == 3) ? wxT("true") : wxT("false")); - + S.Id(ID_Load).AddButton(_("&Load")); + S.Id(ID_Save).AddButton(_("&Save")); + S.Id(ID_Clear).AddButton(_("&Clear")); + S.AddSpace(10, 1); + S.Id(ID_Debug).AddButton(_("&Debug")); } S.EndHorizontalLay(); } S.EndVerticalLay(); - // Debug, OK, & Cancel buttons - S.AddStandardButtons(ePreviewButton|eDebugButton|eCancelButton|eOkButton); - - GetSizer()->SetSizeHints(this); - mCommandText->SetFocus(); } -// ============================================================================ -// NyquistInputDialog implementation -// ============================================================================ +void NyquistEffect::BuildEffectWindow(ShuttleGui & S) +{ + S.SetStyle(wxVSCROLL | wxTAB_TRAVERSAL); + wxScrolledWindow *scroller = S.StartScroller(2); + { + S.StartMultiColumn(4); + { + for (size_t i = 0; i < mControls.GetCount(); i++) + { + NyqControl & ctrl = mControls[i]; + + S.AddPrompt(ctrl.name + wxT(":")); + + if (ctrl.type == NYQ_CTRL_STRING) + { + S.AddSpace(10, 10); + + S.Id(ID_Text + i).AddTextBox(wxT(""), ctrl.valStr, 12); + } + else if (ctrl.type == NYQ_CTRL_CHOICE) + { + S.AddSpace(10, 10); + + wxArrayString choices = wxStringTokenize(ctrl.label, wxT(",")); + + int val = (int)ctrl.val; + if (val < 0 || val >= (int)choices.GetCount()) + { + val = 0; + } + + S.Id(ID_Choice + i).AddChoice(wxT(""), choices[val], &choices); + } + else + { + // Integer or Real + wxTextCtrl *item = S.Id(ID_Text+i).AddTextBox(wxT(""), wxT(""), 12); + + double range = ctrl.high - ctrl.low; + + if (ctrl.type == NYQ_CTRL_REAL) + { + // > 12 decimal places can cause rounding errors in display. + FloatingPointValidator vld(12, &ctrl.val); + vld.SetRange(ctrl.low, ctrl.high); + + // Set number of decimal places + int style = range < 10 ? NUM_VAL_THREE_TRAILING_ZEROES : + range < 100 ? NUM_VAL_TWO_TRAILING_ZEROES : + NUM_VAL_ONE_TRAILING_ZERO; + vld.SetStyle(style); + + item->SetValidator(vld); + } + else + { + IntegerValidator vld(&ctrl.val); + vld.SetRange((int) ctrl.low, (int) ctrl.high); + item->SetValidator(vld); + } + + int val = (int)(0.5 + ctrl.ticks * (ctrl.val - ctrl.low) / range); + S.SetStyle(wxSL_HORIZONTAL); + S.Id(ID_Slider + i).AddSlider(wxT(""), val, ctrl.ticks, 0); + S.SetSizeHints(150, -1); + } + + if (ctrl.type == NYQ_CTRL_CHOICE || ctrl.label.IsEmpty()) + { + S.AddSpace(10, 10); + } + else + { + S.AddUnits(ctrl.label); + } + } + } + S.EndMultiColumn(); + } + S.EndScroller(); + + scroller->SetScrollRate(0, 20); +} + +// NyquistEffect implementation + +bool NyquistEffect::IsOk() +{ + return mOK; +} + +void NyquistEffect::OnLoad(wxCommandEvent & WXUNUSED(evt)) +{ + if (mCommandText->IsModified()) + { + if (wxMessageBox(_("Current program has been modified.\nDiscard changes?"), + GetName(), + wxYES_NO) == wxNO) + { + return; + } + } + + FileDialog dlog(mUIParent, + _("Load Nyquist script"), + mFileName.GetPath(), + wxEmptyString, + _("Nyquist scripts (*.ny)|*.ny|Lisp scripts (*.lsp)|*.lsp|All files|*"), + wxFD_OPEN | wxRESIZE_BORDER); + + if (dlog.ShowModal() != wxID_OK) + { + return; + } + + mFileName = dlog.GetPath(); + + if (!mCommandText->LoadFile(mFileName.GetFullPath())) + { + wxMessageBox(_("File could not be loaded"), GetName()); + } +} + +void NyquistEffect::OnSave(wxCommandEvent & WXUNUSED(evt)) +{ + FileDialog dlog(mUIParent, + _("Save Nyquist script"), + mFileName.GetPath(), + mFileName.GetFullName(), + _("Nyquist scripts (*.ny)|*.ny|Lisp scripts (*.lsp)|*.lsp|All files|*"), + wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER); + + if (dlog.ShowModal() != wxID_OK) + { + return; + } + + mFileName = dlog.GetPath(); + + if (!mCommandText->SaveFile(mFileName.GetFullPath())) + { + wxMessageBox(_("File could not be saved"), GetName()); + } +} + +void NyquistEffect::OnClear(wxCommandEvent & WXUNUSED(evt)) +{ + mCommandText->Clear(); +} + +void NyquistEffect::OnDebug(wxCommandEvent & WXUNUSED(evt)) +{ + NyquistEffect effect(NYQUIST_WORKER_ID); -wxString NyquistInputDialog::GetCommand() -{ - return mCommandText->GetValue(); + effect.SetCommand(mCommandText->GetValue()); + effect.mDebug = true; + + SelectedRegion region(mT0, mT1); + effect.DoEffect(mUIParent, + mProjectRate, + mTracks, + mFactory, + ®ion, + true); + + NyquistOutputDialog dlog(mUIParent, + wxID_ANY, + _("Nyquist"), + _("Nyquist Output: "), + effect.mOutput); + dlog.CentreOnParent(); + dlog.ShowModal(); + + return; } -void NyquistInputDialog::OnVersionCheck(wxCommandEvent& WXUNUSED(evt)) +void NyquistEffect::OnSlider(wxCommandEvent & evt) { - mVersion = (mVersionCheckBox->GetValue()) ? 3 : 4; - gPrefs->Write(wxT("/Effects/NyquistPrompt/Version"), mVersion); - gPrefs->Flush(); + int i = evt.GetId() - ID_Slider; + NyqControl & ctrl = mControls[i]; + + int val = evt.GetInt(); + double range = ctrl.high - ctrl.low; + double newVal = (val / (double)ctrl.ticks) * range + ctrl.low; + + // Determine precision for displayed number + int precision = range < 1.0 ? 3 : + range < 10.0 ? 2 : + range < 100.0 ? 1 : + 0; + + // If the value is at least one tick different from the current value + // change it (this prevents changes from manually entered values unless + // the slider actually moved) + if (fabs(newVal - ctrl.val) >= (1 / (double)ctrl.ticks) * range && + fabs(newVal - ctrl.val) >= pow(0.1, precision) / 2) + { + // First round to the appropriate precision + newVal *= pow(10.0, precision); + newVal = floor(newVal + 0.5); + newVal /= pow(10.0, precision); + + ctrl.val = newVal; + + mUIParent->FindWindow(ID_Text + i)->GetValidator()->TransferToWindow(); + } } -void NyquistInputDialog::OnOk(wxCommandEvent & /* event */) +void NyquistEffect::OnChoice(wxCommandEvent & evt) { - EndModal(wxID_OK); + mControls[evt.GetId() - ID_Choice].val = (double) evt.GetInt(); } -void NyquistInputDialog::OnCancel(wxCommandEvent & /* event */) +void NyquistEffect::OnText(wxCommandEvent & evt) { - EndModal(wxID_CANCEL); + int i = evt.GetId() - ID_Text; + + NyqControl & ctrl = mControls[i]; + + if (ctrl.type == NYQ_CTRL_STRING) + { + ctrl.valStr = evt.GetString(); + } + else + { + if (wxDynamicCast(evt.GetEventObject(), wxWindow)->GetValidator()->TransferFromWindow()) + { + int pos = (int)floor((ctrl.val - ctrl.low) / + (ctrl.high - ctrl.low) * ctrl.ticks + 0.5); + + wxSlider *slider = (wxSlider *)mUIParent->FindWindow(ID_Slider + i); + slider->SetValue(pos); + } + } } -void NyquistInputDialog::OnDebug(wxCommandEvent & /* event */) -{ - // Transfer data - - EndModal(eDebugID); -} - -void NyquistInputDialog::OnPreview(wxCommandEvent & /* event */) -{ - EndModal(ePreviewID); -} - - /////////////////////////////////////////////////////////////////////////////// // // NyquistOutputDialog diff --git a/src/effects/nyquist/Nyquist.h b/src/effects/nyquist/Nyquist.h index 5235cdafc..4e0225143 100644 --- a/src/effects/nyquist/Nyquist.h +++ b/src/effects/nyquist/Nyquist.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -31,12 +32,23 @@ #define NYQUISTEFFECTS_VERSION wxT("1.0.0.0") #define NYQUISTEFFECTS_FAMILY wxT("Nyquist") +#define NYQUIST_PROMPT_ID wxT("=== Nyquist Prompt ===") +#define NYQUIST_WORKER_ID wxT("=== Nyquist Worker ===") + +enum NyqControlType +{ + NYQ_CTRL_INT, + NYQ_CTRL_REAL, + NYQ_CTRL_STRING, + NYQ_CTRL_CHOICE, +}; + class NyqControl { - public: +public: + int type; wxString var; wxString name; - int type; wxString label; wxString valStr; wxString lowStr; @@ -46,22 +58,18 @@ class NyqControl double high; int ticks; }; -#define NYQ_CTRL_INT 0 -#define NYQ_CTRL_REAL 1 -#define NYQ_CTRL_STRING 2 -#define NYQ_CTRL_CHOICE 3 WX_DECLARE_USER_EXPORTED_OBJARRAY(NyqControl, NyqControlArray, AUDACITY_DLL_API); -class AUDACITY_DLL_API EffectNyquist:public Effect +class AUDACITY_DLL_API NyquistEffect : public Effect { - public: +public: /** @param fName File name of the Nyquist script defining this effect. If * an empty string, then prompt the user for the Nyquist code to interpret. */ - EffectNyquist(wxString fName); - virtual ~EffectNyquist(); + NyquistEffect(wxString fName); + virtual ~NyquistEffect(); // IdentInterface implementation @@ -78,85 +86,49 @@ class AUDACITY_DLL_API EffectNyquist:public Effect virtual wxString GetFamily(); virtual bool IsInteractive(); virtual bool IsDefault(); - virtual bool IsLegacy(); - virtual bool SupportsRealtime(); - virtual bool SupportsAutomation(); + + // EffectClientInterface implementation + + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); // Effect implementation - /** Get the name of the effect (taken from the script that is loaded). Note - * that this name is currently not translated because the translations system - * doesn't see the lisp files containing the Nyquist effect scripts. - * @return The name of the effect */ - virtual wxString GetEffectName() { - return mName; - } - - virtual std::set GetEffectCategories() { - std::set cats; - for (size_t i = 0; i < mCategories.GetCount(); i++) { - cats.insert(mCategories[i]); - } - return cats; - } - - virtual wxString GetEffectIdentifier() { - if (mInteractive) { - // Disabled for now... - return wxT(""); - } - - wxStringTokenizer st(mName, wxT(" ")); - wxString id; - - // CamelCase the name - while (st.HasMoreTokens()) { - wxString tok = st.GetNextToken(); - - id += tok.Left(1).MakeUpper() + tok.Mid(1); - } - - return id; - } - - virtual wxString GetEffectAction() { - return mAction; - } - - virtual bool GeneratorPreview(); - virtual bool PromptUser(); - virtual bool Process(); + virtual bool ShowInterface(wxWindow *parent, bool forceModal = false); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); - // Batch chain support - virtual bool SupportsChains(); - virtual bool TransferParameters( Shuttle & shuttle ); - - // EffectNyquist implementation - - bool SetXlispPath(); - - bool LoadedNyFile() { - return mOK; - } + // NyquistEffect implementation + // For Nyquist Workbench support + void SetCommand(wxString cmd); void Continue(); void Break(); void Stop(); - void SetCommand(wxString cmd); - wxString GetOutput(); +private: + // NyquistEffect implementation + bool ProcessOne(); + + void BuildPromptWindow(ShuttleGui & S); + void BuildEffectWindow(ShuttleGui & S); + + bool TransferDataToPromptWindow(); + bool TransferDataToEffectWindow(); + + bool TransferDataFromPromptWindow(); + bool TransferDataFromEffectWindow(); + + bool IsOk(); static wxArrayString GetNyquistSearchPath(); - private: - static wxString NyquistToWxString(const char *nyqString); wxString EscapeString(const wxString & inStr); - bool ProcessOne(); - static int StaticGetCallback(float *buffer, int channel, long start, long len, long totlen, void *userdata); @@ -173,12 +145,24 @@ class AUDACITY_DLL_API EffectNyquist:public Effect void OutputCallback(int c); void OSCallback(); - void Parse(wxString line); void ParseFile(); + bool ParseCommand(const wxString & cmd); + bool ParseProgram(wxInputStream & stream); + void Parse(wxString line); + wxString UnQuote(wxString s); double GetCtrlValue(wxString s); - private: + void OnLoad(wxCommandEvent & evt); + void OnSave(wxCommandEvent & evt); + void OnClear(wxCommandEvent & evt); + void OnDebug(wxCommandEvent & evt); + + void OnText(wxCommandEvent & evt); + void OnSlider(wxCommandEvent & evt); + void OnChoice(wxCommandEvent & evt); + +private: wxString mXlispPath; @@ -189,6 +173,7 @@ class AUDACITY_DLL_API EffectNyquist:public Effect bool mBreak; bool mCont; + bool mFoundType; bool mCompiler; bool mIsSal; bool mExternal; @@ -196,7 +181,7 @@ class AUDACITY_DLL_API EffectNyquist:public Effect * the "Nyquist Prompt", false for all other effects (lisp code read from * files) */ - bool mInteractive; + bool mIsPrompt; bool mOK; wxString mInputCmd; // history: exactly what the user typed wxString mCmd; // the command to be processed @@ -205,9 +190,12 @@ class AUDACITY_DLL_API EffectNyquist:public Effect wxString mInfo; wxString mAuthor; wxString mCopyright; + EffectType mType; + bool mEnablePreview; bool mDebug; - std::string mDebugOutput; + std::string *mDebugOutput; + wxString mOutput; int mVersion; NyqControlArray mControls; @@ -238,75 +226,27 @@ class AUDACITY_DLL_API EffectNyquist:public Effect bool mRestoreSplits; int mMergeClips; - friend class NyquistDialog; -}; - -class NyquistDialog:public wxDialog -{ - public: - // constructors and destructors - NyquistDialog(wxWindow * parent, wxWindowID id, - const wxString & title, - wxString info, - bool preview, - EffectNyquist *effect); - - private: - EffectNyquist *mEffect; - NyqControlArray *mControls; - bool mInHandler; - - void OnText(wxCommandEvent & event); - void OnSlider(wxCommandEvent & event); - void OnChoice( wxCommandEvent &event ); - void OnPreview(wxCommandEvent & event); - void OnDebug(wxCommandEvent & event); - void OnOk(wxCommandEvent & event); - void OnCancel(wxCommandEvent & event); - - private: - DECLARE_EVENT_TABLE() - -}; - -class NyquistInputDialog:public wxDialog -{ - public: - NyquistInputDialog(wxWindow * parent, wxWindowID id, - const wxString & title, - const wxString & prompt, - wxString initialCommand); - - wxString GetCommand(); - void OnVersionCheck(wxCommandEvent& evt); - int mVersion; - - private: wxTextCtrl *mCommandText; wxCheckBox *mVersionCheckBox; - void OnOk(wxCommandEvent & event); - void OnCancel(wxCommandEvent & event); - void OnDebug(wxCommandEvent & event); - void OnPreview(wxCommandEvent & event); + DECLARE_EVENT_TABLE(); - private: - DECLARE_EVENT_TABLE() + friend class NyquistEffectsModule; }; -class NyquistOutputDialog:public wxDialog +class NyquistOutputDialog : public wxDialog { - public: +public: NyquistOutputDialog(wxWindow * parent, wxWindowID id, const wxString & title, const wxString & prompt, wxString message); - private: +private: void OnOk(wxCommandEvent & event); - private: - DECLARE_EVENT_TABLE() +private: + DECLARE_EVENT_TABLE(); }; diff --git a/src/effects/vamp/LoadVamp.cpp b/src/effects/vamp/LoadVamp.cpp index d9918beb0..feee025ba 100644 --- a/src/effects/vamp/LoadVamp.cpp +++ b/src/effects/vamp/LoadVamp.cpp @@ -8,9 +8,12 @@ **********************************************************************/ +#include "../../Audacity.h" + +#if defined(USE_VAMP) + #include -#include "../../Audacity.h" #include "../EffectManager.h" #include "VampEffect.h" #include "LoadVamp.h" @@ -20,6 +23,7 @@ using namespace Vamp; using namespace Vamp::HostExt; +using namespace Vamp::HostExt; // ============================================================================ // Module registration entry point @@ -114,29 +118,24 @@ void VampEffectsModule::Terminate() bool VampEffectsModule::AutoRegisterPlugins(PluginManagerInterface & WXUNUSED(pm)) { -#ifdef EFFECT_CATEGORIES - InitCategoryMap(); -#endif + return false; +} + +wxArrayString VampEffectsModule::FindPlugins(PluginManagerInterface & WXUNUSED(pm)) +{ + wxArrayString names; PluginLoader *loader = PluginLoader::getInstance(); - EffectManager& em = EffectManager::Get(); - PluginLoader::PluginKeyList keys = loader->listPlugins(); - for (PluginLoader::PluginKeyList::iterator i = keys.begin(); - i != keys.end(); ++i) { - - Plugin *vp = loader->loadPlugin(*i, 48000); // rate doesn't matter here - if (!vp) continue; - -#ifdef EFFECT_CATEGORIES - - PluginLoader::PluginCategoryHierarchy category = - loader->getPluginCategory(*i); - wxString vampCategory = VampHierarchyToUri(category); - -#endif + for (PluginLoader::PluginKeyList::iterator i = keys.begin(); i != keys.end(); ++i) + { + Plugin *vp = PluginLoader::getInstance()->loadPlugin(*i, 48000); // rate doesn't matter here + if (!vp) + { + continue; + } // We limit the listed plugin outputs to those whose results can // readily be displayed in an Audacity label track. @@ -159,143 +158,181 @@ bool VampEffectsModule::AutoRegisterPlugins(PluginManagerInterface & WXUNUSED(pm Plugin::OutputList outputs = vp->getOutputDescriptors(); - int n = 0; - - bool hasParameters = !vp->getParameterDescriptors().empty(); - - for (Plugin::OutputList::iterator j = outputs.begin(); - j != outputs.end(); ++j) { + int output = 0; + for (Plugin::OutputList::iterator j = outputs.begin(); j != outputs.end(); j++) + { if (j->sampleType == Plugin::OutputDescriptor::FixedSampleRate || - j->sampleType == Plugin::OutputDescriptor::OneSamplePerStep || - !j->hasFixedBinCount || - (j->hasFixedBinCount && j->binCount > 1)) { - + j->sampleType == Plugin::OutputDescriptor::OneSamplePerStep || + !j->hasFixedBinCount || + (j->hasFixedBinCount && j->binCount > 1)) + { // All of these qualities disqualify (see notes above) - ++n; + ++output; continue; } - wxString name = LAT1CTOWX(vp->getName().c_str()); + wxString name = wxString::FromUTF8(vp->getName().c_str()); - if (outputs.size() > 1) { + if (outputs.size() > 1) + { // This is not the plugin's only output. // Use "plugin name: output name" as the effect name, // unless the output name is the same as the plugin name - wxString outputName = LAT1CTOWX(j->name.c_str()); - if (outputName != name) { + wxString outputName = wxString::FromUTF8(j->name.c_str()); + if (outputName != name) + { name = wxString::Format(wxT("%s: %s"), name.c_str(), outputName.c_str()); } } -#ifdef EFFECT_CATEGORIES - VampEffect *effect = new VampEffect(*i, n, hasParameters, name, - vampCategory); -#else - VampEffect *effect = new VampEffect(*i, n, hasParameters, name); -#endif - em.RegisterEffect(this, effect); + wxString path = wxString::FromUTF8(i->c_str()) + wxT("/") + name; + names.Add(path); - ++n; + ++output; } delete vp; } - return true; + return names; } -wxArrayString VampEffectsModule::FindPlugins(PluginManagerInterface & WXUNUSED(pm)) +bool VampEffectsModule::RegisterPlugin(PluginManagerInterface & pm, const wxString & path) { - // Nothing to do here yet - return wxArrayString(); -} + int output; + bool hasParameters; + + Plugin *vp = FindPlugin(path, output, hasParameters); + if (vp) + { + VampEffect effect(vp, path, output, hasParameters); + pm.RegisterPlugin(this, &effect); + + return true; + } -bool VampEffectsModule::RegisterPlugin(PluginManagerInterface & WXUNUSED(pm), const wxString & WXUNUSED(path)) -{ - // Nothing to do here yet return false; } bool VampEffectsModule::IsPluginValid(const wxString & path) { - return wxFileName::FileExists(path); + int output; + bool hasParameters; + + Plugin *vp = FindPlugin(path, output, hasParameters); + if (vp) + { + delete vp; + return true; + } + + return false; } -IdentInterface *VampEffectsModule::CreateInstance(const wxString & WXUNUSED(path)) +IdentInterface *VampEffectsModule::CreateInstance(const wxString & path) { - // Nothing to do here yet since we are autoregistering (and creating legacy - // effects anyway). + int output; + bool hasParameters; + + Plugin *vp = FindPlugin(path, output, hasParameters); + if (vp) + { + return new VampEffect(vp, path, output, hasParameters); + } + return NULL; } -void VampEffectsModule::DeleteInstance(IdentInterface *WXUNUSED(instance)) +void VampEffectsModule::DeleteInstance(IdentInterface *instance) { - // Nothing to do here yet -} - -// ============================================================================ -// VampEffectsModule implementation -// ============================================================================ - -#ifdef EFFECT_CATEGORIES - -static std::map gCategoryMap; - -#define VAMP(S) wxT("http://audacityteam.org/namespace#VampCategories") wxT(S) -#define ATEAM(S) wxT("http://audacityteam.org/namespace#") wxT(S) - -/** Initialise the data structure that maps locally generated URI strings to - internal ones. */ -static void InitCategoryMap() -{ - gCategoryMap[VAMP("/Time")] = ATEAM("TimeAnalyser"); - gCategoryMap[VAMP("/Time/Onsets")] = ATEAM("OnsetDetector"); -} - -/** Map the generated VAMP category URI to the internal ones. */ -static wxString MapCategoryUri(const wxString& uri) -{ - std::map::const_iterator iter; - iter = gCategoryMap.find(uri); - if (iter != gCategoryMap.end()) - return iter->second; - return uri; -} - -/** Generate category URIs for all levels in a VAMP category hierarchy, - add them to the EffectManager and return the most detailed one. */ -static wxString VampHierarchyToUri(const PluginLoader::PluginCategoryHierarchy& h) -{ - // Else, generate URIs and add them to the EffectManager - EffectManager& em = EffectManager::Get(); - wxString vampCategory = - wxString::FromAscii("http://audacityteam.org/namespace#VampCategories"); - EffectCategory* parent = - em.LookupCategory(wxT("http://lv2plug.in/ns/lv2core#AnalyserPlugin")); - if (parent) { - for (size_t c = 0; c < h.size(); ++c) { - vampCategory += wxT("/"); - wxString catName = wxString::FromAscii(h[c].c_str()); - vampCategory += catName; - EffectCategory* ec = em.AddCategory(MapCategoryUri(vampCategory), - catName); - em.AddCategoryParent(ec, parent); - parent = ec; - } + VampEffect *effect = dynamic_cast(instance); + if (effect) + { + delete effect; } - return MapCategoryUri(vampCategory); +} + +// VampEffectsModule implementation + +Plugin *VampEffectsModule::FindPlugin(const wxString & path, + int & output, + bool & hasParameters) +{ + PluginLoader::PluginKey key = path.BeforeLast(wxT('/')).ToUTF8().data(); + + Plugin *vp = PluginLoader::getInstance()->loadPlugin(key, 48000); // rate doesn't matter here + if (!vp) + { + return false; + } + + // We limit the listed plugin outputs to those whose results can + // readily be displayed in an Audacity label track. + // + // - Any output whose features have no values (time instants only), + // with or without duration, is fine + // + // - Any output whose features have more than one value, or an + // unknown or variable number of values, is right out + // + // - Any output whose features have exactly one value, with + // variable sample rate or with duration, should be OK -- + // this implies a sparse feature, of which the time and/or + // duration are significant aspects worth displaying + // + // - An output whose features have exactly one value, with + // fixed sample rate and no duration, cannot be usefully + // displayed -- the value is the only significant piece of + // data there and we have no good value plot + + Plugin::OutputList outputs = vp->getOutputDescriptors(); + + output = 0; + + hasParameters = !vp->getParameterDescriptors().empty(); + + for (Plugin::OutputList::iterator j = outputs.begin(); j != outputs.end(); j++) + { + if (j->sampleType == Plugin::OutputDescriptor::FixedSampleRate || + j->sampleType == Plugin::OutputDescriptor::OneSamplePerStep || + !j->hasFixedBinCount || + (j->hasFixedBinCount && j->binCount > 1)) + { + // All of these qualities disqualify (see notes above) + + ++output; + continue; + } + + wxString name = wxString::FromUTF8(vp->getName().c_str()); + + if (outputs.size() > 1) + { + // This is not the plugin's only output. + // Use "plugin name: output name" as the effect name, + // unless the output name is the same as the plugin name + wxString outputName = wxString::FromUTF8(j->name.c_str()); + if (outputName != name) + { + name = wxString::Format(wxT("%s: %s"), + name.c_str(), outputName.c_str()); + } + } + + if (wxString::FromUTF8(key.c_str()) + wxT("/") + name == path) + { + return vp; + } + + ++output; + } + + delete vp; + + return NULL; } #endif - -void LoadVampPlugins() -{ - -} - -void UnloadVampPlugins() -{ -} diff --git a/src/effects/vamp/LoadVamp.h b/src/effects/vamp/LoadVamp.h index fe62b949f..20cdd1aae 100644 --- a/src/effects/vamp/LoadVamp.h +++ b/src/effects/vamp/LoadVamp.h @@ -8,6 +8,10 @@ **********************************************************************/ +#include "../../Audacity.h" + +#if defined(USE_VAMP) + #include "audacity/ModuleInterface.h" #include "audacity/EffectInterface.h" #include "audacity/PluginInterface.h" @@ -47,9 +51,16 @@ public: virtual IdentInterface *CreateInstance(const wxString & path); virtual void DeleteInstance(IdentInterface *instance); +private: // VampEffectModule implementation + Vamp::Plugin *FindPlugin(const wxString & wpath, + int & output, + bool & hasParameters); + private: ModuleManagerInterface *mModMan; wxString mPath; }; + +#endif \ No newline at end of file diff --git a/src/effects/vamp/VampEffect.cpp b/src/effects/vamp/VampEffect.cpp index 17a236116..c655b5f7e 100644 --- a/src/effects/vamp/VampEffect.cpp +++ b/src/effects/vamp/VampEffect.cpp @@ -12,6 +12,9 @@ **********************************************************************/ #include "../../Audacity.h" + +#if defined(USE_VAMP) + #include "VampEffect.h" #include @@ -33,35 +36,88 @@ #include #include + +#include "../../widgets/valnum.h" + +enum +{ + ID_Program = 10000, + ID_Sliders = 11000, + ID_Choices = 12000, + ID_Texts = 13000, + ID_Toggles = 14000, +}; + /////////////////////////////////////////////////////////////////////////////// // // VampEffect // /////////////////////////////////////////////////////////////////////////////// -VampEffect::VampEffect(Vamp::HostExt::PluginLoader::PluginKey key, +BEGIN_EVENT_TABLE(VampEffect, wxEvtHandler) + EVT_SLIDER(wxID_ANY, VampEffect::OnSlider) + EVT_TEXT(wxID_ANY, VampEffect::OnTextCtrl) + EVT_CHECKBOX(wxID_ANY, VampEffect::OnCheckBox) + EVT_CHOICE(wxID_ANY, VampEffect::OnChoice) +END_EVENT_TABLE() + +VampEffect::VampEffect(Vamp::Plugin *plugin, + const wxString & path, int output, - bool hasParameters, - wxString name, - wxString category) : - mKey(key), + bool hasParameters) +: mPlugin(plugin), + mPath(path), mOutput(output), mHasParameters(hasParameters), - mName(name), - mRate(0), - mCategory(category), - mPlugin(NULL) + mRate(0) { - Vamp::HostExt::PluginLoader *loader = Vamp::HostExt::PluginLoader::getInstance(); - mPlugin = loader->loadPlugin(mKey, 48000); // rate doesn't matter here + mKey = mPath.BeforeLast(wxT('/')).ToUTF8(); + mName = mPath.AfterLast(wxT('/')); - SetEffectFlags(PLUGIN_EFFECT | ANALYZE_EFFECT); + mSliders = NULL; + mFields = NULL; + mLabels = NULL; + mToggles = NULL; + mChoices = NULL; + mValues = NULL; } VampEffect::~VampEffect() { - delete mPlugin; - mPlugin = NULL; + if (mPlugin) + { + delete mPlugin; + } + + if (mValues) + { + delete [] mValues; + } + + if (mSliders) + { + delete [] mSliders; + } + + if (mFields) + { + delete [] mFields; + } + + if (mLabels) + { + delete [] mLabels; + } + + if (mToggles) + { + delete [] mToggles; + } + + if (mChoices) + { + delete [] mChoices; + } } // ============================================================================ @@ -70,8 +126,7 @@ VampEffect::~VampEffect() wxString VampEffect::GetPath() { - Vamp::HostExt::PluginLoader *loader = Vamp::HostExt::PluginLoader::getInstance(); - return LAT1CTOWX(loader->getLibraryPathForPlugin(mKey).c_str()); + return mPath; } wxString VampEffect::GetSymbol() @@ -86,7 +141,7 @@ wxString VampEffect::GetName() wxString VampEffect::GetVendor() { - return LAT1CTOWX(mPlugin->getMaker().c_str()); + return wxString::FromUTF8(mPlugin->getMaker().c_str()); } wxString VampEffect::GetVersion() @@ -96,7 +151,7 @@ wxString VampEffect::GetVersion() wxString VampEffect::GetDescription() { - return LAT1CTOWX(mPlugin->getCopyright().c_str()); + return wxString::FromUTF8(mPlugin->getCopyright().c_str()); } // ============================================================================ @@ -105,8 +160,7 @@ wxString VampEffect::GetDescription() EffectType VampEffect::GetType() { - // For now, relegate to Effect() - return Effect::GetType(); + return EffectTypeAnalyze; } wxString VampEffect::GetFamily() @@ -116,8 +170,7 @@ wxString VampEffect::GetFamily() bool VampEffect::IsInteractive() { - // For now, relegate to Effect() - return Effect::IsInteractive(); + return mHasParameters; } bool VampEffect::IsDefault() @@ -125,72 +178,185 @@ bool VampEffect::IsDefault() return false; } -bool VampEffect::IsLegacy() + +// EffectClientInterface implementation + +int VampEffect::GetAudioInCount() { - return true; + return mPlugin->getMaxChannelCount(); } -bool VampEffect::SupportsRealtime() +bool VampEffect::GetAutomationParameters(EffectAutomationParameters & parms) { - return false; -} + for (size_t p = 0, cnt = mParameters.size(); p < cnt; p++) + { + wxString key = wxString::FromUTF8(mParameters[p].identifier.c_str()); + float value = mPlugin->getParameter(mParameters[p].identifier); + float lower = mParameters[p].minValue; + float upper = mParameters[p].maxValue; -bool VampEffect::SupportsAutomation() -{ - return true; -} + if (mParameters[p].isQuantized && + mParameters[p].quantizeStep == 1.0 && + lower == 0.0 && + upper == 1.0) + { + bool val = value > 0.5; -wxString VampEffect::GetEffectName() -{ - if (mHasParameters) { - return mName + LAT1CTOWX("..."); - } else { - return mName; + parms.Write(key, val); + } + else if (mParameters[p].isQuantized && + mParameters[p].quantizeStep == 1.0 && + !mParameters[p].valueNames.empty()) + { + wxArrayString choices; + int val = 0; + + for (size_t i = 0, cnt = mParameters[p].valueNames.size(); i < cnt; i++) + { + wxString choice = wxString::FromUTF8(mParameters[p].valueNames[i].c_str()); + if (size_t(value - mParameters[p].minValue + 0.5) == i) + { + val = i; + } + choices.Add(choice); + } + + parms.WriteEnum(key, val, choices); + } + else + { + parms.Write(key, value); + } } + + return true; } -std::set VampEffect::GetEffectCategories() +bool VampEffect::SetAutomationParameters(EffectAutomationParameters & parms) { - std::set result; - if (mCategory != wxT("")) - result.insert(mCategory); - return result; -} + // First pass verifies values + for (size_t p = 0, cnt = mParameters.size(); p < cnt; p++) + { + wxString key = wxString::FromUTF8(mParameters[p].identifier.c_str()); + float lower = mParameters[p].minValue; + float upper = mParameters[p].maxValue; + bool good = false; + if (mParameters[p].isQuantized && + mParameters[p].quantizeStep == 1.0 && + lower == 0.0 && + upper == 1.0) + { + bool val; -wxString VampEffect::GetEffectIdentifier() -{ - return LAT1CTOWX(mKey.c_str()); -} + good = parms.Read(key, &val); + } + else if (mParameters[p].isQuantized && + mParameters[p].quantizeStep == 1.0 && + !mParameters[p].valueNames.empty()) + { + wxArrayString choices; + int val; -wxString VampEffect::GetEffectAction() -{ - return wxString::Format(_("Extracting features: %s"), - GetEffectName().c_str()); + for (size_t i = 0, cnt = mParameters[p].valueNames.size(); i < cnt; i++) + { + wxString choice = wxString::FromUTF8(mParameters[p].valueNames[i].c_str()); + choices.Add(choice); + } + + good = parms.ReadEnum(key, &val, choices) && val != wxNOT_FOUND; + } + else + { + double val; + + good = parms.Read(key, &val) && val >= lower && val <= upper; + } + + if (!good) + { + return false; + } + } + + // Second pass sets the variables + for (size_t p = 0, cnt = mParameters.size(); p < cnt; p++) + { + wxString key = wxString::FromUTF8(mParameters[p].identifier.c_str()); + float lower = mParameters[p].minValue; + float upper = mParameters[p].maxValue; + + if (mParameters[p].isQuantized && + mParameters[p].quantizeStep == 1.0 && + lower == 0.0 && + upper == 1.0) + { + bool val; + + parms.Read(key, &val); + + mPlugin->setParameter(mParameters[p].identifier, val ? upper : lower); + } + else if (mParameters[p].isQuantized && + mParameters[p].quantizeStep == 1.0 && + !mParameters[p].valueNames.empty()) + { + wxArrayString choices; + int val; + + for (size_t i = 0, cnt = mParameters[p].valueNames.size(); i < cnt; i++) + { + wxString choice = wxString::FromUTF8(mParameters[p].valueNames[i].c_str()); + choices.Add(choice); + } + + parms.ReadEnum(key, &val, choices); + + mPlugin->setParameter(mParameters[p].identifier, (float) val); + } + else + { + double val; + + parms.Read(key, &val); + + if (mParameters[p].isQuantized) + { + float qs = mParameters[p].quantizeStep; + + if (qs != 0.0) + { + val = int((val - lower) / qs + 0.5) * qs + lower; + } + } + + mPlugin->setParameter(mParameters[p].identifier, val); + } + } + + return true; } bool VampEffect::Init() { - Vamp::HostExt::PluginLoader *loader = - Vamp::HostExt::PluginLoader::getInstance(); - - delete mPlugin; - mPlugin = 0; - TrackListOfKindIterator iter(Track::Wave, mTracks); WaveTrack *left = (WaveTrack *)iter.First(); mRate = 0.0; - while (left) { - - if (mRate == 0.0) mRate = left->GetRate(); - - if (left->GetLinked()) { + while (left) + { + if (mRate == 0.0) + { + mRate = left->GetRate(); + } + if (left->GetLinked()) + { WaveTrack *right = (WaveTrack *)iter.Next(); - if (left->GetRate() != right->GetRate()) { + if (left->GetRate() != right->GetRate()) + { wxMessageBox(_("Sorry, Vamp Plug-ins cannot be run on stereo tracks where the individual channels of the track do not match.")); return false; } @@ -199,12 +365,24 @@ bool VampEffect::Init() left = (WaveTrack *)iter.Next(); } - if (mRate <= 0.0) mRate = mProjectRate; + if (mRate <= 0.0) + { + mRate = mProjectRate; + } - mPlugin = loader->loadPlugin - (mKey, mRate, Vamp::HostExt::PluginLoader::ADAPT_ALL); + // The plugin must be reloaded to allow changing parameters - if (!mPlugin) { + Vamp::HostExt::PluginLoader *loader = Vamp::HostExt::PluginLoader::getInstance(); + + if (mPlugin) + { + delete mPlugin; + mPlugin = NULL; + } + + mPlugin = loader->loadPlugin(mKey, mRate, Vamp::HostExt::PluginLoader::ADAPT_ALL); + if (!mPlugin) + { wxMessageBox(_("Sorry, failed to load Vamp Plug-in.")); return false; } @@ -212,23 +390,12 @@ bool VampEffect::Init() return true; } -bool VampEffect::PromptUser() -{ - if (!mPlugin) return false; - if (!mHasParameters) return true; - - VampEffectDialog dlog(this, mParent, mPlugin); - dlog.CentreOnParent(); - dlog.ShowModal(); - - if (!dlog.GetReturnCode()) return false; - - return true; -} - bool VampEffect::Process() { - if (!mPlugin) return false; + if (!mPlugin) + { + return false; + } TrackListOfKindIterator iter(Track::Wave, mTracks); @@ -239,7 +406,8 @@ bool VampEffect::Process() bool multiple = false; int prevTrackChannels = 0; - if (GetNumWaveGroups() > 1) { + if (GetNumWaveGroups() > 1) + { // if there is another track beyond this one and any linked one, // then we're processing more than one track. That means we // should use the originating track name in each new label @@ -247,8 +415,8 @@ bool VampEffect::Process() multiple = true; } - while (left) { - + while (left) + { sampleCount lstart, rstart = 0; sampleCount len; GetSamples(left, &lstart, &len); @@ -256,7 +424,8 @@ bool VampEffect::Process() WaveTrack *right = NULL; int channels = 1; - if (left->GetLinked()) { + if (left->GetLinked()) + { right = (WaveTrack *)iter.Next(); channels = 2; GetSamples(right, &rstart, &len); @@ -267,30 +436,45 @@ bool VampEffect::Process() bool initialiseRequired = true; - if (block == 0) { - if (step != 0) block = step; - else block = 1024; + if (block == 0) + { + if (step != 0) + { + block = step; + } + else + { + block = 1024; + } } - if (step == 0) { + + if (step == 0) + { step = block; } - if (prevTrackChannels > 0) { + if (prevTrackChannels > 0) + { // Plugin has already been initialised, so if the number of // channels remains the same, we only need to do a reset. // Otherwise we need to re-construct the whole plugin, // because a Vamp plugin can't be re-initialised. - if (prevTrackChannels == channels) { + if (prevTrackChannels == channels) + { mPlugin->reset(); initialiseRequired = false; - } else { + } + else + { //!!! todo: retain parameters previously set Init(); } } - if (initialiseRequired) { - if (!mPlugin->initialise(channels, step, block)) { + if (initialiseRequired) + { + if (!mPlugin->initialise(channels, step, block)) + { wxMessageBox(_("Sorry, Vamp Plug-in failed to initialize.")); return false; } @@ -298,57 +482,85 @@ bool VampEffect::Process() LabelTrack *ltrack = mFactory->NewLabelTrack(); - if (!multiple) { - ltrack->SetName(GetEffectName()); - } else { + if (!multiple) + { + ltrack->SetName(GetName()); + } + else + { ltrack->SetName(wxString::Format(wxT("%s: %s"), left->GetName().c_str(), - GetEffectName().c_str())); + GetName().c_str())); } mTracks->Add(ltrack); - float **data = new float*[channels]; // ANSWER-ME: Vigilant Sentry marks this as memory leak, var "data" not deleted. - for (int c = 0; c < channels; ++c) data[c] = new float[block]; + float **data = new float *[channels]; // ANSWER-ME: Vigilant Sentry marks this as memory leak, var "data" not deleted. + for (int c = 0; c < channels; ++c) + { + data[c] = new float[block]; + } sampleCount originalLen = len; sampleCount ls = lstart; sampleCount rs = rstart; - while (len) { - + while (len) + { int request = block; if (request > len) request = len; - if (left) left->Get((samplePtr)data[0], floatSample, ls, request); - if (right) right->Get((samplePtr)data[1], floatSample, rs, request); + if (left) + { + left->Get((samplePtr)data[0], floatSample, ls, request); + } - if (request < (int)block) { - for (int c = 0; c < channels; ++c) { - for (int i = request; i < (int)block; ++i) { + if (right) + { + right->Get((samplePtr)data[1], floatSample, rs, request); + } + + if (request < (int)block) + { + for (int c = 0; c < channels; ++c) + { + for (int i = request; i < (int)block; ++i) + { data[c][i] = 0.f; } } } - Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime - (ls, (int)(mRate + 0.5)); + Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime(ls, (int)(mRate + 0.5)); Vamp::Plugin::FeatureSet features = mPlugin->process(data, timestamp); AddFeatures(ltrack, features); - if (len > (int)step) len -= step; - else len = 0; + if (len > (int)step) + { + len -= step; + } + else + { + len = 0; + } ls += step; rs += step; - if (channels > 1) { + if (channels > 1) + { if (TrackGroupProgress(count, (ls - lstart) / double(originalLen))) + { return false; - } else { + } + } + else + { if (TrackProgress(count, (ls - lstart) / double(originalLen))) + { return false; + } } } @@ -363,12 +575,215 @@ bool VampEffect::Process() return true; } +void VampEffect::End() +{ + delete mPlugin; + mPlugin = 0; +} + +void VampEffect::PopulateOrExchange(ShuttleGui & S) +{ + Vamp::Plugin::ProgramList programs = mPlugin->getPrograms(); + + mParameters = mPlugin->getParameterDescriptors(); + + int count = mParameters.size(); + + mToggles = new wxCheckBox *[count]; + mSliders = new wxSlider *[count]; + mFields = new wxTextCtrl *[count]; + mLabels = new wxStaticText *[count]; + mChoices = new wxChoice *[count]; + mValues = new float[count]; + + S.SetStyle(wxVSCROLL | wxTAB_TRAVERSAL); + wxScrolledWindow *scroller = S.StartScroller(2); + { + S.StartStatic(_("Plugin Settings")); + { + S.StartMultiColumn(5, wxEXPAND); + { + S.SetStretchyCol(3); + + if (!programs.empty()) + { + wxString currentProgram = wxString::FromUTF8(mPlugin->getCurrentProgram().c_str()); + + wxArrayString choices; + for (size_t i = 0, cnt = programs.size(); i < cnt; i++) + { + choices.Add(wxString::FromUTF8(programs[i].c_str())); + } + + S.AddPrompt(_("Program")); + + S.Id(ID_Program); + mProgram = S.AddChoice(wxT(""), currentProgram, &choices); + mProgram->SetName(_("Program")); + mProgram->SetSizeHints(-1, -1); + wxSizer *s = mProgram->GetContainingSizer(); + s->GetItem(mProgram)->SetFlag(wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL); + + S.AddSpace(1, 1); + S.AddSpace(1, 1); + S.AddSpace(1, 1); + } + + for (int p = 0; p < count; p++) + { + wxString tip = wxString::FromUTF8(mParameters[p].description.c_str()); + wxString unit = wxString::FromUTF8(mParameters[p].unit.c_str()); + + float value = mPlugin->getParameter(mParameters[p].identifier); + + mToggles[p] = NULL; + mChoices[p] = NULL; + mSliders[p] = NULL; + mFields[p] = NULL; + mValues[p] = 0.0; + + wxString labelText = wxString::FromUTF8(mParameters[p].name.c_str()); + if (!unit.IsEmpty()) + { + labelText += wxT(" (") + unit + wxT(")"); + } + S.AddPrompt(labelText + wxT(":")); + + if (mParameters[p].isQuantized && + mParameters[p].quantizeStep == 1.0 && + mParameters[p].minValue == 0.0 && + mParameters[p].maxValue == 1.0) + { + S.Id(ID_Toggles + p); + mToggles[p] = S.AddCheckBox(wxT(""), + value > 0.5 ? wxT("true") : wxT("false")); + mToggles[p]->SetName(labelText); + if (!tip.IsEmpty()) + { + mToggles[p]->SetToolTip(tip); + } + wxSizer *s = mToggles[p]->GetContainingSizer(); + s->GetItem(mToggles[p])->SetFlag(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL); + + S.AddSpace(1, 1); + S.AddSpace(1, 1); + S.AddSpace(1, 1); + + } + else if (mParameters[p].isQuantized && + mParameters[p].quantizeStep == 1.0 && + !mParameters[p].valueNames.empty()) + { + wxArrayString choices; + wxString selected; + + for (size_t i = 0, cnt = mParameters[p].valueNames.size(); i < cnt; i++) + { + wxString choice = wxString::FromUTF8(mParameters[p].valueNames[i].c_str()); + if (size_t(value - mParameters[p].minValue + 0.5) == i) + { + selected = choice; + } + choices.Add(choice); + } + + S.Id(ID_Choices + p); + mChoices[p] = S.AddChoice(wxT(""), selected, &choices); + mChoices[p]->SetName(labelText); + mChoices[p]->SetSizeHints(-1, -1); + if (!tip.IsEmpty()) + { + mChoices[p]->SetToolTip(tip); + } + wxSizer *s = mChoices[p]->GetContainingSizer(); + s->GetItem(mChoices[p])->SetFlag(wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL); + + S.AddSpace(1, 1); + S.AddSpace(1, 1); + S.AddSpace(1, 1); + } + else + { + mValues[p] = value; + FloatingPointValidator vld(6, &mValues[p]); + vld.SetRange(mParameters[p].minValue, mParameters[p].maxValue); + + float range = mParameters[p].maxValue - mParameters[p].minValue; + int style = range < 10 ? NUM_VAL_THREE_TRAILING_ZEROES : + range < 100 ? NUM_VAL_TWO_TRAILING_ZEROES : + NUM_VAL_ONE_TRAILING_ZERO; + vld.SetStyle(style); + + S.Id(ID_Texts + p); + mFields[p] = S.AddTextBox(wxT(""), wxT(""), 12); + mFields[p]->SetName(labelText); + mFields[p]->SetValidator(vld); + if (!tip.IsEmpty()) + { + mFields[p]->SetToolTip(tip); + } + wxSizer *s = mFields[p]->GetContainingSizer(); + s->GetItem(mFields[p])->SetFlag(wxALIGN_CENTER_VERTICAL | wxALL); + + wxString str = Internat::ToDisplayString(mParameters[p].minValue); + S.AddPrompt(str); + + S.SetStyle(wxSL_HORIZONTAL); + S.Id(ID_Sliders + p); + mSliders[p] = S.AddSlider(wxT(""), 0, 1000, 0); + mSliders[p]->SetName(labelText); + mSliders[p]->SetSizeHints(150, -1); + if (!tip.IsEmpty()) + { + mSliders[p]->SetToolTip(tip); + } + + str = Internat::ToDisplayString(mParameters[p].maxValue); + S.AddUnits(str); + } + } + } + S.EndMultiColumn(); + } + S.EndStatic(); + } + S.EndScroller(); + + scroller->SetScrollRate(0, 20); + + return; +} + +bool VampEffect::TransferDataToWindow() +{ + if (!mUIParent->TransferDataToWindow()) + { + return false; + } + + UpdateFromPlugin(); + + return true; +} + +bool VampEffect::TransferDataFromWindow() +{ + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } + + return true; +} + +// VampEffect implementation + void VampEffect::AddFeatures(LabelTrack *ltrack, Vamp::Plugin::FeatureSet &features) { for (Vamp::Plugin::FeatureList::iterator fli = features[mOutput].begin(); - fli != features[mOutput].end(); ++fli) { - + fli != features[mOutput].end(); ++fli) + { Vamp::RealTime ftime0 = fli->timestamp; double ltime0 = ftime0.sec + (double(ftime0.nsec) / 1000000000.0); @@ -377,10 +792,14 @@ void VampEffect::AddFeatures(LabelTrack *ltrack, double ltime1 = ftime1.sec + (double(ftime1.nsec) / 1000000000.0); wxString label = LAT1CTOWX(fli->label.c_str()); - if (label == wxString()) { - if (fli->values.empty()) { + if (label == wxString()) + { + if (fli->values.empty()) + { label = wxString::Format(LAT1CTOWX("%.3f"), ltime0); - } else { + } + else + { label = wxString::Format(LAT1CTOWX("%.3f"), *fli->values.begin()); } } @@ -389,477 +808,123 @@ void VampEffect::AddFeatures(LabelTrack *ltrack, } } -void VampEffect::End() +void VampEffect::UpdateFromPlugin() { - delete mPlugin; - mPlugin = 0; -} - - -BEGIN_EVENT_TABLE(VampEffectDialog, wxDialog) - EVT_BUTTON(wxID_OK, VampEffectDialog::OnOK) - EVT_BUTTON(wxID_CANCEL, VampEffectDialog::OnCancel) - EVT_SLIDER(wxID_ANY, VampEffectDialog::OnSlider) - EVT_TEXT(wxID_ANY, VampEffectDialog::OnTextCtrl) - EVT_CHECKBOX(wxID_ANY, VampEffectDialog::OnCheckBox) - EVT_COMBOBOX(wxID_ANY, VampEffectDialog::OnComboBox) -END_EVENT_TABLE() - -IMPLEMENT_CLASS(VampEffectDialog, wxDialog) - -VampEffectDialog::VampEffectDialog(VampEffect *effect, - wxWindow *parent, - Vamp::Plugin *plugin) : - wxDialog(parent, -1, effect->GetEffectName(), - wxDefaultPosition, wxDefaultSize, - wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), - mEffect(effect), - mPlugin(plugin) -{ - Vamp::Plugin::ProgramList programs = plugin->getPrograms(); - - mParameters = plugin->getParameterDescriptors(); - -#if defined(__WXMSW__) || (defined(__WXGTK__) && wxCHECK_VERSION(3, 0, 0)) - // In some environments wxWidgets calls OnTextCtrl during creation - // of the text control, and VampEffectDialog::OnTextCtrl calls HandleText, - // which assumes all the fields have been initialized. - // This can give us a bad pointer crash, so manipulate inSlider to - // no-op HandleText during creation. - inSlider = true; -#else - inSlider = false; -#endif - - inText = false; - - int count = mParameters.size(); - - toggles = new wxCheckBox*[count]; - sliders = new wxSlider*[count]; - fields = new wxTextCtrl*[count]; - labels = new wxStaticText*[count]; - combos = new wxComboBox*[count]; - - wxControl *item; - - wxBoxSizer *vSizer = new wxBoxSizer(wxVERTICAL); - - item = new wxStaticText(this, 0, - LAT1CTOWX(plugin->getName().c_str()) + - wxString(_(" - Vamp audio analysis plugin"))); - vSizer->Add(item, 0, wxALL, 5); - - item = new wxStaticText(this, 0, - LAT1CTOWX(plugin->getDescription().c_str())); - vSizer->Add(item, 0, wxALL, 5); - - item = new wxStaticText(this, 0, - wxString(_("Author: ")) - + LAT1CTOWX(plugin->getMaker().c_str())); - - vSizer->Add(item, 0, wxALL, 5); - - item = new wxStaticText(this, 0, - LAT1CTOWX(plugin->getCopyright().c_str())); - vSizer->Add(item, 0, wxALL, 5); - - wxScrolledWindow *w = new wxScrolledWindow(this, - wxID_ANY, - wxDefaultPosition, - wxDefaultSize, - wxVSCROLL | wxTAB_TRAVERSAL); - - // Try to give the window a sensible default/minimum size - w->SetMinSize(wxSize( - wxMax(400, parent->GetSize().GetWidth() / 2), - parent->GetSize().GetHeight() / 2)); - - w->SetScrollRate(0, 20); - vSizer->Add(w, 1, wxEXPAND|wxALL, 5); - - vSizer->Add(CreateStdButtonSizer(this, eCancelButton|eOkButton), 0, wxEXPAND); - - SetSizer(vSizer); - - wxSizer *paramSizer = - new wxStaticBoxSizer(wxVERTICAL, w, _("Plugin Settings")); - - wxFlexGridSizer *gridSizer = new wxFlexGridSizer(5, 0, 0); - gridSizer->AddGrowableCol(3); - - programCombo = 0; - - if (!programs.empty()) { - - wxArrayString choices; - wxString currentProgram = - wxString(mPlugin->getCurrentProgram().c_str(), wxConvISO8859_1); - - for (size_t i = 0; i < programs.size(); ++i) { - - wxString choice = wxString(programs[i].c_str(), wxConvISO8859_1); - choices.Add(choice); - } - - gridSizer->Add(new wxStaticText(w, 0, _("Program")), - 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - - programCombo = new wxComboBox(w, 9999, currentProgram, - wxDefaultPosition, wxDefaultSize, - choices, wxCB_READONLY); - programCombo->SetName(_("Program")); - - gridSizer->Add(1, 1, 0); - gridSizer->Add(1, 1, 0); - - gridSizer->Add(programCombo, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 5); - ConnectFocus(programCombo); - - gridSizer->Add(1, 1, 0); - } - - for (int p = 0; p < count; p++) { - - wxString labelText = LAT1CTOWX(mParameters[p].name.c_str()); - item = new wxStaticText(w, 0, labelText + wxT(":")); - item->SetName(labelText); - gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - - wxString fieldText; - + for (size_t p = 0, cnt = mParameters.size(); p < cnt; p++) + { float value = mPlugin->getParameter(mParameters[p].identifier); - toggles[p] = 0; - combos[p] = 0; - sliders[p] = 0; - fields[p] = 0; - if (mParameters[p].isQuantized && mParameters[p].quantizeStep == 1.0 && mParameters[p].minValue == 0.0 && - mParameters[p].maxValue == 1.0) { + mParameters[p].maxValue == 1.0) + { + mToggles[p]->SetValue(value > 0.5); + } + else if (mParameters[p].isQuantized && + mParameters[p].quantizeStep == 1.0 && + !mParameters[p].valueNames.empty()) + { + mChoices[p]->SetSelection(size_t(value - mParameters[p].minValue + 0.5)); + } + else + { + mValues[p] = value; + mFields[p]->GetValidator()->TransferToWindow(); - toggles[p] = new wxCheckBox(w, p, wxT("")); - toggles[p]->SetName(labelText); - toggles[p]->SetValue(value > 0.5); - gridSizer->Add(toggles[p], 0, wxALL, 5); - ConnectFocus(toggles[p]); + float lower = mParameters[p].minValue; + float upper = mParameters[p].maxValue; + float range = upper - lower; - gridSizer->Add(1, 1, 0); - gridSizer->Add(1, 1, 0); - gridSizer->Add(1, 1, 0); + if (mParameters[p].isQuantized) + { + float qs = mParameters[p].quantizeStep; - } else if (mParameters[p].isQuantized && - mParameters[p].quantizeStep == 1.0 && - !mParameters[p].valueNames.empty()) { - - wxArrayString choices; - wxString selected; - - for (size_t i = 0; i < mParameters[p].valueNames.size(); ++i) { - wxString choice = wxString - (mParameters[p].valueNames[i].c_str(), wxConvISO8859_1); - if (size_t(value - mParameters[p].minValue + 0.5) == i) { - selected = choice; + if (qs != 0.0) + { + value = int((value - lower) / qs + 0.5) * qs + lower; } - choices.Add(choice); } - combos[p] = new wxComboBox(w, p, selected, - wxDefaultPosition, wxDefaultSize, - choices, wxCB_READONLY); - combos[p]->SetName(labelText); - - gridSizer->Add(1, 1, 0); - gridSizer->Add(1, 1, 0); - - gridSizer->Add(combos[p], 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 5); - ConnectFocus(combos[p]); - - gridSizer->Add(1, 1, 0); - - } else { - - fieldText = Internat::ToDisplayString(value); - - fields[p] = new wxTextCtrl(w, p, fieldText); - fields[p]->SetName(labelText); - gridSizer->Add(fields[p], 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); - ConnectFocus(fields[p]); - - wxString str = Internat::ToDisplayString(mParameters[p].minValue); - item = new wxStaticText(w, 0, str); - gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); - - sliders[p] = - new wxSlider(w, p, - 0, 0, 1000, - wxDefaultPosition, - wxSize(100, -1)); - sliders[p]->SetName(labelText); - gridSizer->Add(sliders[p], 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 5); - ConnectFocus(sliders[p]); - - str = Internat::ToDisplayString(mParameters[p].maxValue); - item = new wxStaticText(w, 0, str); - gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, 5); + mSliders[p]->SetValue((int)(((value - lower) / range) * 1000.0 + 0.5)); } } - - // Set all of the sliders based on the value in the - // text fields - inSlider = false; // Now we're ready for HandleText to actually do something. - HandleText(); - - paramSizer->Add(gridSizer, 1, wxEXPAND | wxALL, 5); - w->SetSizer(paramSizer); - - Layout(); - Fit(); - SetSizeHints(GetSize()); } -VampEffectDialog::~VampEffectDialog() +void VampEffect::OnCheckBox(wxCommandEvent &event) { - delete[] sliders; - delete[] fields; - delete[] labels; - delete[] toggles; - delete[] combos; + int p = event.GetId() - ID_Toggles; + + mPlugin->setParameter(mParameters[p].identifier, mToggles[p]->GetValue()); } -void VampEffectDialog::OnCheckBox(wxCommandEvent &event) +void VampEffect::OnChoice(wxCommandEvent & evt) { - int p = event.GetId(); + int p = evt.GetId(); - mPlugin->setParameter(mParameters[p].identifier, toggles[p]->GetValue()); -} - -void VampEffectDialog::OnComboBox(wxCommandEvent &event) -{ - int p = event.GetId(); - - if (p == 9999) { - // special value for programs + // special value for programs + if (p == ID_Program) + { Vamp::Plugin::ProgramList programs = mPlugin->getPrograms(); - for (size_t i = 0; i < programs.size(); ++i) { - if (wxString(programs[i].c_str(), wxConvISO8859_1) == - programCombo->GetValue()) { - mPlugin->selectProgram(programs[i]); - break; - } - } + mPlugin->selectProgram(programs[evt.GetInt()]); UpdateFromPlugin(); return; } - int value = -1; - for (size_t i = 0; i < mParameters[p].valueNames.size(); ++i) { - if (wxString(mParameters[p].valueNames[i].c_str(), wxConvISO8859_1) == - combos[p]->GetValue()) { - value = i; - break; - } - } - if (value >= 0) { - mPlugin->setParameter(mParameters[p].identifier, value); - } + mPlugin->setParameter(mParameters[p - ID_Choices].identifier, evt.GetInt()); } -void VampEffectDialog::OnSlider(wxCommandEvent &event) +void VampEffect::OnSlider(wxCommandEvent & evt) { - int p = event.GetId(); + int p = evt.GetId() - ID_Sliders; - // if we don't add the following three lines, changing - // the value of the slider will change the text, which - // will change the slider, and so on. This gets rid of - // the implicit loop. - if (inText) return; - inSlider = true; - - float val; float lower = mParameters[p].minValue; float upper = mParameters[p].maxValue; - float range; - - range = upper - lower; - - val = (sliders[p]->GetValue() / 1000.0) * range; - - if (mParameters[p].isQuantized) { + float range = upper - lower; + float val = (evt.GetInt() / 1000.0) * range; + if (mParameters[p].isQuantized) + { float qs = mParameters[p].quantizeStep; - if (qs != 0.0) { + if (qs != 0.0) + { val = int(val / qs + 0.5) * qs; } } val += lower; - wxString str = Internat::ToDisplayString(val); + mValues[p] = val; + mFields[p]->GetValidator()->TransferToWindow(); - fields[p]->SetValue(str); + mPlugin->setParameter(mParameters[p].identifier, val); +} + +void VampEffect::OnTextCtrl(wxCommandEvent & evt) +{ + int p = evt.GetId() - ID_Texts; + + mFields[p]->GetValidator()->TransferToWindow(); + + float lower = mParameters[p].minValue; + float upper = mParameters[p].maxValue; + float range = upper - lower; + float val = mValues[p]; + + if (mParameters[p].isQuantized) + { + float qs = mParameters[p].quantizeStep; + + if (qs != 0.0) + { + val = int((val - lower) / qs + 0.5) * qs + lower; + } + } mPlugin->setParameter(mParameters[p].identifier, val); - inSlider = false; + mSliders[p]->SetValue((int)(((val - lower) / range) * 1000.0 + 0.5)); } -void VampEffectDialog::OnTextCtrl(wxCommandEvent & WXUNUSED(event)) -{ - HandleText(); -} - -void VampEffectDialog::HandleText() -{ - // if we don't add the following three lines, changing - // the value of the slider will change the text, which - // will change the slider, and so on. This gets rid of - // the implicit loop. - - if (inSlider) return; - inText = true; - - for (unsigned long p = 0; p < mParameters.size(); p++) { - - if (mParameters[p].isQuantized && - mParameters[p].quantizeStep == 1.0) { - - if (mParameters[p].minValue == 0.0 && - mParameters[p].maxValue == 1.0) { - // toggle, not slider - continue; - } - - if (!mParameters[p].valueNames.empty()) { - // combo, not slider - continue; - } - } - - double dval; - float val; - float lower = mParameters[p].minValue; - float upper = mParameters[p].maxValue; - float range; - - dval = Internat::CompatibleToDouble(fields[p]->GetValue()); - val = dval; - - range = upper - lower; - - if (val < lower) val = lower; - if (val > upper) val = upper; - - if (mParameters[p].isQuantized) { - - float qs = mParameters[p].quantizeStep; - - if (qs != 0.0) { - val = int((val - lower) / qs + 0.5) * qs + lower; - } - } - - mPlugin->setParameter(mParameters[p].identifier, val); - - sliders[p]->SetValue((int)(((val-lower)/range) * 1000.0 + 0.5)); - } - - inText = false; -} - -void VampEffectDialog::UpdateFromPlugin() -{ - inSlider = true; - - for (unsigned long p = 0; p < mParameters.size(); p++) { - - float value = mPlugin->getParameter(mParameters[p].identifier); - - if (mParameters[p].isQuantized && - mParameters[p].quantizeStep == 1.0 && - mParameters[p].minValue == 0.0 && - mParameters[p].maxValue == 1.0) { - - toggles[p]->SetValue(value > 0.5); - - } else if (mParameters[p].isQuantized && - mParameters[p].quantizeStep == 1.0 && - !mParameters[p].valueNames.empty()) { - - wxString selected; - - for (size_t i = 0; i < mParameters[p].valueNames.size(); ++i) { - wxString choice = wxString - (mParameters[p].valueNames[i].c_str(), wxConvISO8859_1); - if (size_t(value - mParameters[p].minValue + 0.5) == i) { - selected = choice; - break; - } - } - - combos[p]->SetValue(selected); - - } else { - - fields[p]->SetValue(Internat::ToDisplayString(value)); - } - } - - inSlider = false; - HandleText(); -} - -void VampEffectDialog::OnOK(wxCommandEvent & WXUNUSED(event)) -{ - EndModal(TRUE); -} - -void VampEffectDialog::OnCancel(wxCommandEvent & WXUNUSED(event)) -{ - EndModal(FALSE); -} - -void VampEffectDialog::ConnectFocus(wxControl *c) -{ - c->GetEventHandler()->Connect(wxEVT_SET_FOCUS, - wxFocusEventHandler(VampEffectDialog::ControlSetFocus)); -} - -void VampEffectDialog::DisconnectFocus(wxControl *c) -{ - c->GetEventHandler()->Disconnect(wxEVT_SET_FOCUS, - wxFocusEventHandler(VampEffectDialog::ControlSetFocus)); -} - -void VampEffectDialog::ControlSetFocus(wxFocusEvent &event) -{ - wxControl *c = (wxControl *) event.GetEventObject(); - wxScrolledWindow *p = (wxScrolledWindow *) c->GetParent(); - wxRect r = c->GetRect(); - wxRect rv = p->GetRect(); - rv.y = 0; - - event.Skip(); - - int y; - int yppu; - p->GetScrollPixelsPerUnit(NULL, &yppu); - - if (r.y >= rv.y && r.GetBottom() <= rv.GetBottom()) { - return; - } - - if (r.y < rv.y) { - p->CalcUnscrolledPosition(0, r.y, NULL, &r.y); - y = r.y / yppu; - } - else { - p->CalcUnscrolledPosition(0, r.y, NULL, &r.y); - y = (r.GetBottom() - rv.GetBottom() + yppu) / yppu; - } - - p->Scroll(-1, y); -}; +#endif \ No newline at end of file diff --git a/src/effects/vamp/VampEffect.h b/src/effects/vamp/VampEffect.h index 7a041d626..7e4d08d59 100644 --- a/src/effects/vamp/VampEffect.h +++ b/src/effects/vamp/VampEffect.h @@ -11,31 +11,34 @@ **********************************************************************/ -#include "../Effect.h" -#include "../../LabelTrack.h" +#include "../../Audacity.h" -class wxSlider; -class wxStaticText; -class wxTextCtrl; -class wxCheckBox; -class wxComboBox; +#if defined(USE_VAMP) -#include +#include +#include +#include +#include +#include +#include +#include #include +#include "../../LabelTrack.h" + +#include "../Effect.h" + #define VAMPEFFECTS_VERSION wxT("1.0.0.0") #define VAMPEFFECTS_FAMILY wxT("Vamp") -class VampEffect : public Effect { - - public: - - VampEffect(Vamp::HostExt::PluginLoader::PluginKey key, +class VampEffect : public Effect +{ +public: + VampEffect(Vamp::Plugin *plugin, + const wxString & path, int output, - bool hasParameters, - wxString name, - wxString category = wxString()); + bool hasParameters); virtual ~VampEffect(); // IdentInterface implementation @@ -53,84 +56,59 @@ class VampEffect : public Effect { virtual wxString GetFamily(); virtual bool IsInteractive(); virtual bool IsDefault(); - virtual bool IsLegacy(); - virtual bool SupportsRealtime(); - virtual bool SupportsAutomation(); + + // EffectClientInterface implementation + + virtual int GetAudioInCount(); + virtual bool GetAutomationParameters(EffectAutomationParameters & parms); + virtual bool SetAutomationParameters(EffectAutomationParameters & parms); // Effect implementation - virtual wxString GetEffectName(); - - virtual std::set GetEffectCategories(); - - virtual wxString GetEffectIdentifier(); - - virtual wxString GetEffectAction(); - virtual bool Init(); - - virtual bool PromptUser(); - virtual bool Process(); - virtual void End(); + virtual void PopulateOrExchange(ShuttleGui & S); + virtual bool TransferDataToWindow(); + virtual bool TransferDataFromWindow(); - private: +private: + // VampEffect implemetation - VampEffect(const VampEffect &); - VampEffect &operator=(const VampEffect &); + void AddFeatures(LabelTrack *track, Vamp::Plugin::FeatureSet & features); - Vamp::HostExt::PluginLoader::PluginKey mKey; + void UpdateFromPlugin(); + + void OnCheckBox(wxCommandEvent & evt); + void OnChoice(wxCommandEvent & evt); + void OnSlider(wxCommandEvent & evt); + void OnTextCtrl(wxCommandEvent & evt); + +private: + Vamp::Plugin *mPlugin; + wxString mPath; int mOutput; bool mHasParameters; + + Vamp::HostExt::PluginLoader::PluginKey mKey; wxString mName; double mRate; - wxString mCategory; - Vamp::Plugin *mPlugin; + bool mInSlider; + bool mInText; - void AddFeatures(LabelTrack *track, - Vamp::Plugin::FeatureSet &features); -}; - - -class VampEffectDialog : public wxDialog { - - DECLARE_DYNAMIC_CLASS(VampEffectDialog) - - public: - VampEffectDialog(VampEffect *effect, - wxWindow *parent, - Vamp::Plugin *plugin); - ~VampEffectDialog(); - - void OnCheckBox(wxCommandEvent & event); - void OnComboBox(wxCommandEvent & event); - void OnSlider(wxCommandEvent & event); - void OnTextCtrl(wxCommandEvent & event); - void OnOK(wxCommandEvent & event); - void OnCancel(wxCommandEvent & event); - void OnPreview(wxCommandEvent & event); - void ControlSetFocus(wxFocusEvent & event); - - DECLARE_EVENT_TABLE() - - private: - void HandleText(); - void UpdateFromPlugin(); - void ConnectFocus(wxControl *c); - void DisconnectFocus(wxControl *c); - bool inSlider; - bool inText; - - VampEffect *mEffect; - Vamp::Plugin *mPlugin; Vamp::Plugin::ParameterList mParameters; - wxSlider **sliders; - wxTextCtrl **fields; - wxStaticText **labels; - wxCheckBox **toggles; - wxComboBox **combos; - wxComboBox *programCombo; + float *mValues; + + wxSlider **mSliders; + wxTextCtrl **mFields; + wxStaticText **mLabels; + wxCheckBox **mToggles; + wxChoice **mChoices; + wxChoice *mProgram; + + DECLARE_EVENT_TABLE(); }; + +#endif diff --git a/src/prefs/DevicePrefs.cpp b/src/prefs/DevicePrefs.cpp index be203b1b2..24e5fa502 100644 --- a/src/prefs/DevicePrefs.cpp +++ b/src/prefs/DevicePrefs.cpp @@ -112,7 +112,7 @@ void DevicePrefs::PopulateOrExchange(ShuttleGui & S) S.StartMultiColumn(2); { S.Id(HostID); - mHost = S.TieChoice(_("&Host") + wxString(wxT(":")), + mHost = S.TieChoice(_("&Host:"), wxT("/AudioIO/Host"), wxT(""), mHostNames, @@ -131,7 +131,7 @@ void DevicePrefs::PopulateOrExchange(ShuttleGui & S) S.StartMultiColumn(2); { S.Id(PlayID); - mPlay = S.AddChoice(_("&Device") + wxString(wxT(":")), + mPlay = S.AddChoice(_("&Device:"), wxEmptyString, &empty); } @@ -144,12 +144,12 @@ void DevicePrefs::PopulateOrExchange(ShuttleGui & S) S.StartMultiColumn(2); { S.Id(RecordID); - mRecord = S.AddChoice(_("De&vice") + wxString(wxT(":")), + mRecord = S.AddChoice(_("De&vice:"), wxEmptyString, &empty); S.Id(ChannelsID); - mChannels = S.AddChoice(_("Cha&nnels") + wxString(wxT(":")), + mChannels = S.AddChoice(_("Cha&nnels:"), wxEmptyString, &empty); } diff --git a/src/prefs/MidiIOPrefs.cpp b/src/prefs/MidiIOPrefs.cpp index 06cbdccde..d541e6ec3 100644 --- a/src/prefs/MidiIOPrefs.cpp +++ b/src/prefs/MidiIOPrefs.cpp @@ -120,7 +120,7 @@ void MidiIOPrefs::PopulateOrExchange( ShuttleGui & S ) { { S.Id(HostID); /* i18n-hint: (noun) */ - mHost = S.TieChoice(_("Host") + wxString(wxT(":")), + mHost = S.TieChoice(_("Host":), wxT("/MidiIO/Host"), wxT(""), mHostNames, @@ -138,7 +138,7 @@ void MidiIOPrefs::PopulateOrExchange( ShuttleGui & S ) { S.StartMultiColumn(2); { S.Id(PlayID); - mPlay = S.AddChoice(_("Device") + wxString(wxT(":")), + mPlay = S.AddChoice(_("Device:"), wxEmptyString, &empty); int latency = gPrefs->Read(wxT("/MidiIO/OutputLatency"), @@ -156,13 +156,13 @@ void MidiIOPrefs::PopulateOrExchange( ShuttleGui & S ) { S.StartMultiColumn(2); { S.Id(RecordID); - mRecord = S.AddChoice(_("Device") + wxString(wxT(":")), + mRecord = S.AddChoice(_("Device:"), wxEmptyString, &empty); S.Id(ChannelsID); /* - mChannels = S.AddChoice(_("Channels") + wxString(wxT(":")), + mChannels = S.AddChoice(_("Channels:"), wxEmptyString, &empty); */ diff --git a/src/prefs/SpectrumPrefs.cpp b/src/prefs/SpectrumPrefs.cpp index aa9d13577..8ea330737 100644 --- a/src/prefs/SpectrumPrefs.cpp +++ b/src/prefs/SpectrumPrefs.cpp @@ -79,14 +79,14 @@ void SpectrumPrefs::PopulateOrExchange(ShuttleGui & S) { S.StartMultiColumn(2); { - S.TieChoice(_("Window &size") + wxString(wxT(":")), + S.TieChoice(_("Window &size:"), wxT("/Spectrum/FFTSize"), 256, mSizeChoices, mSizeCodes); S.SetSizeHints(mSizeChoices); - S.TieChoice(_("Window &type") + wxString(wxT(":")), + S.TieChoice(_("Window &type:"), wxT("/Spectrum/WindowType"), 3, mTypeChoices, @@ -114,7 +114,7 @@ void SpectrumPrefs::PopulateOrExchange(ShuttleGui & S) S.StartMultiColumn(2); { /* /////i18n-hint: (noun) here the user chooses points to skip.*/ - S.TieChoice(TRANSLATABLE("Skip Points") + wxString(wxT(":")), + S.TieChoice(TRANSLATABLE("Skip Points:"), wxT("/Spectrum/FFTSkipPoints"), 0, wskipn, diff --git a/src/widgets/Meter.cpp b/src/widgets/Meter.cpp index d3a5f1ae5..7f89ec8c5 100644 --- a/src/widgets/Meter.cpp +++ b/src/widgets/Meter.cpp @@ -1884,15 +1884,16 @@ void *Meter::SaveState() void Meter::RestoreState(void *state) { - mMonitoring = ((bool *)state)[0]; - mActive = ((bool *)state)[1]; + bool *s = (bool *)state; + mMonitoring = s[0]; + mActive = s[1]; if (mActive) { mTimer.Start(1000 / mMeterRefreshRate); } - delete [] state; + delete [] s; } // diff --git a/src/widgets/numformatter.cpp b/src/widgets/numformatter.cpp index 33123a353..d6aa040cf 100644 --- a/src/widgets/numformatter.cpp +++ b/src/widgets/numformatter.cpp @@ -32,6 +32,7 @@ #include #include // for setlocale and LC_ALL +#include #include // ---------------------------------------------------------------------------- @@ -265,6 +266,14 @@ wxString NumberFormatter::ToString(double val, int precision, int style) format.Printf(wxT("%%.%df"), precision); } + if (isnan(val)) + { + return _("NaN"); + } + if (isinf(val)) + { + return _("-Infinity"); + } wxString s = wxString::Format(format, val); if ( style & Style_WithThousandsSep ) diff --git a/src/widgets/valnum.cpp b/src/widgets/valnum.cpp index 76a1a5436..e8eaa6e84 100644 --- a/src/widgets/valnum.cpp +++ b/src/widgets/valnum.cpp @@ -1,452 +1,532 @@ -///////////////////////////////////////////////////////////////////////////// -// -// Backport from wxWidgets-3.0-rc1 -// -///////////////////////////////////////////////////////////////////////////// -// Name: src/common/valnum.cpp -// Purpose: Numeric validator classes implementation -// Author: Vadim Zeitlin based on the submission of Fulvio Senore -// Created: 2010-11-06 -// Copyright: (c) 2010 wxWidgets team -// Licence: wxWindows licence -///////////////////////////////////////////////////////////////////////////// - -// ============================================================================ -// Declarations -// ============================================================================ - -// ---------------------------------------------------------------------------- -// headers -// ---------------------------------------------------------------------------- - -// For compilers that support precompilation, includes "wx.h". -#include - -#ifdef __BORLANDC__ - #pragma hdrstop -#endif - -#if wxUSE_VALIDATORS && wxUSE_TEXTCTRL - -#ifndef WX_PRECOMP - #include - #include -#endif - -#include "valnum.h" -#include "numformatter.h" - -// ============================================================================ -// NumValidatorBase implementation -// ============================================================================ - -BEGIN_EVENT_TABLE(NumValidatorBase, wxValidator) - EVT_CHAR(NumValidatorBase::OnChar) - EVT_KILL_FOCUS(NumValidatorBase::OnKillFocus) -END_EVENT_TABLE() - -int NumValidatorBase::GetFormatFlags() const -{ - int flags = NumberFormatter::Style_None; - if ( m_style & NUM_VAL_THOUSANDS_SEPARATOR ) - flags |= NumberFormatter::Style_WithThousandsSep; - if ( m_style & NUM_VAL_NO_TRAILING_ZEROES ) - flags |= NumberFormatter::Style_NoTrailingZeroes; - if ( m_style & NUM_VAL_ONE_TRAILING_ZERO ) - flags |= NumberFormatter::Style_OneTrailingZero; - if ( m_style & NUM_VAL_TWO_TRAILING_ZEROES ) - flags |= NumberFormatter::Style_TwoTrailingZeroes; - if ( m_style & NUM_VAL_THREE_TRAILING_ZEROES ) - flags |= NumberFormatter::Style_ThreeTrailingZeroes; - - return flags; -} - -wxTextEntry *NumValidatorBase::GetTextEntry() const -{ -#if wxUSE_TEXTCTRL - if ( wxTextCtrl *text = wxDynamicCast(m_validatorWindow, wxTextCtrl) ) - return text; -#endif // wxUSE_TEXTCTRL - - wxFAIL_MSG(wxT("Can only be used with wxTextCtrl or wxComboBox")); - - return NULL; -} - -bool NumValidatorBase::Validate(wxWindow *parent) -{ - // If window is disabled, simply return - if ( !m_validatorWindow->IsEnabled() ) - return true; - - wxString errmsg; - bool res = DoValidateNumber(&errmsg); - - if ( !res ) - { - wxMessageBox(errmsg, _("Validation error"), - wxOK | wxICON_ERROR, parent); - m_validatorWindow->SetFocus(); - return false; - } - - return true; -} - -void -NumValidatorBase::GetCurrentValueAndInsertionPoint(wxString& val, - int& pos) const -{ - wxTextEntry * const control = GetTextEntry(); - if ( !control ) - return; - - val = control->GetValue(); - pos = control->GetInsertionPoint(); - - long selFrom, selTo; - control->GetSelection(&selFrom, &selTo); - - const long selLen = selTo - selFrom; - if ( selLen ) - { - // Remove selected text because pressing a key would make it disappear. - val.erase(selFrom, selLen); - - // And adjust the insertion point to have correct position in the new - // string. - if ( pos > selFrom ) - { - if ( pos >= selTo ) - pos -= selLen; - else - pos = selFrom; - } - } -} - -bool NumValidatorBase::IsMinusOk(const wxString& val, int pos) const -{ - // Minus is only ever accepted in the beginning of the string. - if ( pos != 0 ) - return false; - - // And then only if there is no existing minus sign there. - if ( !val.empty() && val[0] == '-' ) - return false; - - return true; -} - -void NumValidatorBase::OnChar(wxKeyEvent& event) -{ - // By default we just validate this key so don't prevent the normal - // handling from taking place. - event.Skip(); - - if ( !m_validatorWindow ) - return; - -#if wxUSE_UNICODE - const int ch = event.GetUnicodeKey(); - const int c = event.GetKeyCode(); - if ( c > WXK_START ) - { - // It's a character without any Unicode equivalent at all, e.g. cursor - // arrow or function key, we never filter those. - return; - } -#else // !wxUSE_UNICODE - const int ch = event.GetKeyCode(); - const int c = ch; - if ( ch > WXK_DELETE ) - { - // Not a character neither. - return; - } -#endif // wxUSE_UNICODE/!wxUSE_UNICODE - - // Space is an allowed thousands separator. But we don't allow user to type - // it. We will add it at formatting time in OnKillFocus(). - if ( c < WXK_SPACE || c == WXK_DELETE ) - { - // Allow ASCII control characters and Delete. - return; - } - - // Check if this character is allowed in the current state. - wxString val; - int pos; - GetCurrentValueAndInsertionPoint(val, pos); - - if ( !IsCharOk(val, pos, ch) ) - { - if ( !wxValidator::IsSilent() ) - wxBell(); - - // Do not skip the event in this case, stop handling it here. - event.Skip(false); - } -} - -void NumValidatorBase::OnKillFocus(wxFocusEvent& event) -{ - wxTextEntry * const control = GetTextEntry(); - if ( !control ) - return; - - // When we change the control value below, its "modified" status is reset - // so we need to explicitly keep it marked as modified if it was so in the - // first place. - // - // Notice that only wxTextCtrl (and not wxTextEntry) has - // IsModified()/MarkDirty() methods hence the need for dynamic cast. - wxTextCtrl * const text = wxDynamicCast(m_validatorWindow, wxTextCtrl); - const bool wasModified = text ? text->IsModified() : false; - - control->ChangeValue(NormalizeString(control->GetValue())); - - if ( wasModified ) - text->MarkDirty(); - - event.Skip(); - -// Validate(text); -} - -// ============================================================================ -// IntegerValidatorBase implementation -// ============================================================================ - -wxString IntegerValidatorBase::ToString(LongestValueType value) const -{ - return NumberFormatter::ToString(value, GetFormatFlags()); -} - -bool -IntegerValidatorBase::FromString(const wxString& s, LongestValueType *value) -{ - return NumberFormatter::FromString(s, value); -} - -bool -IntegerValidatorBase::IsCharOk(const wxString& val, int pos, wxChar ch) const -{ - // We may accept minus sign if we can represent negative numbers at all. - if ( ch == '-' ) - { - // Notice that entering '-' can make our value invalid, for example if - // we're limited to -5..15 range and the current value is 12, then the - // new value would be (invalid) -12. We consider it better to let the - // user do this because perhaps he is going to press Delete key next to - // make it -2 and forcing him to delete 1 first would be unnatural. - // - // TODO: It would be nice to indicate that the current control contents - // is invalid (if it's indeed going to be the case) once - // wxValidator supports doing this non-intrusively. - return m_min < 0 && IsMinusOk(val, pos); - } - - // A separator is accepted if the locale allow it, the other chars must be digits - if ( ch < '0' || ch > '9' ) - { - wxChar thousands; - if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousands) ) - { - if (ch != thousands) - return false; - } - else - { - return false; - } - } - - return true; -} - -bool IntegerValidatorBase::DoValidateNumber(wxString * errMsg) const -{ - wxTextEntry * const control = GetTextEntry(); - if ( !control ) - return false; - - wxString s(control->GetValue()); - wxChar thousandsSep; - if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousandsSep) ) - s.Replace(wxString(thousandsSep), wxString()); - - if ( s.empty() ) - { - // Is blank, but allowed. Stop here - if ( HasFlag(NUM_VAL_ZERO_AS_BLANK) ) - { - return true; - } - // We can't do any check with an empty string - else - { - *errMsg = _("Empty value"); - return false; - } - } - - // Can it be converted to a value? - LongestValueType value; - bool res = FromString(s, &value); - if ( !res ) - *errMsg = _("Malformed number"); - else - { - res = IsInRange(value); - if ( !res ) - *errMsg = _("Not in range"); - } - - return res; -} - -// ============================================================================ -// FloatingPointValidatorBase implementation -// ============================================================================ - -wxString FloatingPointValidatorBase::ToString(LongestValueType value) const -{ - return NumberFormatter::ToString(value, m_precision, GetFormatFlags()); -} - -bool -FloatingPointValidatorBase::FromString(const wxString& s, - LongestValueType *value) -{ - return NumberFormatter::FromString(s, value); -} - -bool -FloatingPointValidatorBase::IsCharOk(const wxString& val, - int pos, - wxChar ch) const -{ - if ( ch == '-' ) - { - // We may accept minus sign if we can represent negative numbers at all. - if ( pos == 0 ) - return m_min < 0 && IsMinusOk(val, pos); - // or for the exponent definition - else if ( val[pos-1] != 'e' && val[pos-1] != 'E' ) - return false; - - return true; - } - else if ( ch == '+' ) - { - if ( pos == 0 ) - return m_max >= 0; - else if ( val[pos-1] != 'e' && val[pos-1] != 'E' ) - return false; - - return true; - } - - const wxChar separator = NumberFormatter::GetDecimalSeparator(); - if ( ch == separator ) - { - if ( val.find(separator) != wxString::npos ) - { - // There is already a decimal separator, can't insert another one. - return false; - } - - // Prepending a separator before the sign isn't allowed. - if ( pos == 0 && !val.empty() && ( val[0] == '-' || val[0] == '+' ) ) - return false; - - // Otherwise always accept it, adding a decimal separator doesn't - // change the number value and, in particular, can't make it invalid. - // OTOH the checks below might not pass because strings like "." or - // "-." are not valid numbers so parsing them would fail, hence we need - // to treat it specially here. - return true; - } - - // Must be a digit, an exponent or a thousands separator. - if( ( ch < '0' || ch > '9' ) && ch != 'E' && ch != 'e' ) - { - wxChar thousands; - if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousands) ) - { - if (ch != thousands) - return false; - } - else - { - return false; - } - } - - // Check the number of decimal digits in the final string - wxString str(val); - str.insert(pos, ch); - return ValidatePrecision(str); -} - -bool FloatingPointValidatorBase::DoValidateNumber(wxString * errMsg) const -{ - wxTextEntry * const control = GetTextEntry(); - if ( !control ) - return false; - - wxString s(control->GetValue()); - wxChar thousandsSep; - if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousandsSep) ) - s.Replace(wxString(thousandsSep), wxString()); - - if ( s.empty() ) - { - if ( HasFlag(NUM_VAL_ZERO_AS_BLANK) ) - return true; //Is blank, but allowed. Stop here - else - { - *errMsg = _("Empty value"); - return false; //We can't do any checks with an empty string - } - } - - LongestValueType value; - bool res = FromString(s, &value); // Can it be converted to a value? - if ( !res ) - *errMsg = _("Value overflow"); - else - { - res = ValidatePrecision(s); - if ( !res ) - *errMsg = _("Too many decimal digits"); - else - { - res = IsInRange(value); - if ( !res ) - *errMsg = _("Not in range"); - } - } - - return res; -} - -bool FloatingPointValidatorBase::ValidatePrecision(const wxString& s) const -{ - size_t posSep = s.find(NumberFormatter::GetDecimalSeparator()); - if ( posSep == wxString::npos ) - posSep = s.length(); - - // If user typed exponent 'e' the number of decimal digits is not - // important at all. But we must know that 'e' position. - size_t posExp = s.Lower().Find(_("e")); - if ( posExp == wxString::npos ) - posExp = s.length(); - - // Return true if number has no more decimal digits than allowed - return ( (int)(posExp - posSep) - 1 <= (int)m_precision ); -} - -#endif // wxUSE_VALIDATORS && wxUSE_TEXTCTRL +///////////////////////////////////////////////////////////////////////////// +// +// Backport from wxWidgets-3.0-rc1 +// +///////////////////////////////////////////////////////////////////////////// +// Name: src/common/valnum.cpp +// Purpose: Numeric validator classes implementation +// Author: Vadim Zeitlin based on the submission of Fulvio Senore +// Created: 2010-11-06 +// Copyright: (c) 2010 wxWidgets team +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +// ============================================================================ +// Declarations +// ============================================================================ + +// ---------------------------------------------------------------------------- +// headers +// ---------------------------------------------------------------------------- + +// For compilers that support precompilation, includes "wx.h". +#include + +#ifdef __BORLANDC__ + #pragma hdrstop +#endif + +#if wxUSE_VALIDATORS && wxUSE_TEXTCTRL + +#ifndef WX_PRECOMP + #include + #include +#endif + +#include +#include + +#include "valnum.h" +#include "numformatter.h" + +// ============================================================================ +// NumValidatorBase implementation +// ============================================================================ + +BEGIN_EVENT_TABLE(NumValidatorBase, wxValidator) + EVT_CHAR(NumValidatorBase::OnChar) + EVT_TEXT_PASTE(wxID_ANY, NumValidatorBase::OnPaste) + EVT_KILL_FOCUS(NumValidatorBase::OnKillFocus) +END_EVENT_TABLE() + +int NumValidatorBase::GetFormatFlags() const +{ + int flags = NumberFormatter::Style_None; + if ( m_style & NUM_VAL_THOUSANDS_SEPARATOR ) + flags |= NumberFormatter::Style_WithThousandsSep; + if ( m_style & NUM_VAL_NO_TRAILING_ZEROES ) + flags |= NumberFormatter::Style_NoTrailingZeroes; + if ( m_style & NUM_VAL_ONE_TRAILING_ZERO ) + flags |= NumberFormatter::Style_OneTrailingZero; + if ( m_style & NUM_VAL_TWO_TRAILING_ZEROES ) + flags |= NumberFormatter::Style_TwoTrailingZeroes; + if ( m_style & NUM_VAL_THREE_TRAILING_ZEROES ) + flags |= NumberFormatter::Style_ThreeTrailingZeroes; + + return flags; +} + +wxTextEntry *NumValidatorBase::GetTextEntry() const +{ +#if wxUSE_TEXTCTRL + if ( wxTextCtrl *text = wxDynamicCast(m_validatorWindow, wxTextCtrl) ) + return text; +#endif // wxUSE_TEXTCTRL + + wxFAIL_MSG(wxT("Can only be used with wxTextCtrl or wxComboBox")); + + return NULL; +} + +bool NumValidatorBase::Validate(wxWindow *parent) +{ + // If window is disabled, simply return + if ( !m_validatorWindow->IsEnabled() ) + return true; + + wxString errmsg; + bool res = DoValidateNumber(&errmsg); + + if ( !res ) + { + wxMessageBox(errmsg, _("Validation error"), + wxOK | wxICON_ERROR, parent); + m_validatorWindow->SetFocus(); + return false; + } + + return true; +} + +void +NumValidatorBase::GetCurrentValueAndInsertionPoint(wxString& val, + int& pos) const +{ + wxTextEntry * const control = GetTextEntry(); + if ( !control ) + return; + + val = control->GetValue(); + pos = control->GetInsertionPoint(); + + long selFrom, selTo; + control->GetSelection(&selFrom, &selTo); + + const long selLen = selTo - selFrom; + if ( selLen ) + { + // Remove selected text because pressing a key would make it disappear. + val.erase(selFrom, selLen); + + // And adjust the insertion point to have correct position in the new + // string. + if ( pos > selFrom ) + { + if ( pos >= selTo ) + pos -= selLen; + else + pos = selFrom; + } + } +} + +bool NumValidatorBase::IsMinusOk(const wxString& val, int pos) const +{ + // Minus is only ever accepted in the beginning of the string. + if ( pos != 0 ) + return false; + + // And then only if there is no existing minus sign there. + if ( !val.empty() && val[0] == '-' ) + return false; + + return true; +} + +void NumValidatorBase::OnChar(wxKeyEvent& event) +{ + // By default we just validate this key so don't prevent the normal + // handling from taking place. + event.Skip(); + + if ( !m_validatorWindow ) + return; + +#if wxUSE_UNICODE + const int ch = event.GetUnicodeKey(); + const int c = event.GetKeyCode(); + if ( c > WXK_START ) + { + // It's a character without any Unicode equivalent at all, e.g. cursor + // arrow or function key, we never filter those. + return; + } +#else // !wxUSE_UNICODE + const int ch = event.GetKeyCode(); + const int c = ch; + if ( ch > WXK_DELETE ) + { + // Not a character neither. + return; + } +#endif // wxUSE_UNICODE/!wxUSE_UNICODE + + // Space is an allowed thousands separator. But we don't allow user to type + // it. We will add it at formatting time in OnKillFocus(). + if ( c < WXK_SPACE || c == WXK_DELETE ) + { + // Allow ASCII control characters and Delete. + return; + } + + // Check if this character is allowed in the current state. + wxString val; + int pos; + GetCurrentValueAndInsertionPoint(val, pos); + + if ( !IsCharOk(val, pos, ch) ) + { + if ( !wxValidator::IsSilent() ) + wxBell(); + + // Do not skip the event in this case, stop handling it here. + event.Skip(false); + } +} + +void NumValidatorBase::OnPaste(wxClipboardTextEvent& event) +{ + event.Skip(false); + + wxTextEntry * const control = GetTextEntry(); + if ( !control ) + { + return; + } + + wxClipboardLocker cb; +// if (!wxClipboard::Get()->IsSupported(wxDataFormat(wxDF_TEXT))) + if (!wxClipboard::Get()->IsSupported(wxDF_TEXT)) + { + return; + } + + wxTextDataObject data; + if (!wxClipboard::Get()->GetData( data )) + { + return; + } + + wxString toPaste = data.GetText(); + wxString val; + int pos; + GetCurrentValueAndInsertionPoint(val, pos); + + for (size_t i = 0, cnt = toPaste.Length(); i < cnt; i++) + { + const wxChar ch = toPaste[i]; + + // Check if this character is allowed in the current state. + if ( IsCharOk(val, pos, ch) ) + { + val = GetValueAfterInsertingChar(val, pos++, ch); + } + else if ( !wxValidator::IsSilent() ) + { + wxBell(); + } + } + + // When we change the control value below, its "modified" status is reset + // so we need to explicitly keep it marked as modified if it was so in the + // first place. + // + // Notice that only wxTextCtrl (and not wxTextEntry) has + // IsModified()/MarkDirty() methods hence the need for dynamic cast. + wxTextCtrl * const text = wxDynamicCast(m_validatorWindow, wxTextCtrl); + const bool wasModified = text ? text->IsModified() : false; + + control->ChangeValue(NormalizeString(val)); + + if ( wasModified ) + { + text->MarkDirty(); + } +} + +void NumValidatorBase::OnKillFocus(wxFocusEvent& event) +{ + wxTextEntry * const control = GetTextEntry(); + if ( !control ) + return; + + // When we change the control value below, its "modified" status is reset + // so we need to explicitly keep it marked as modified if it was so in the + // first place. + // + // Notice that only wxTextCtrl (and not wxTextEntry) has + // IsModified()/MarkDirty() methods hence the need for dynamic cast. + wxTextCtrl * const text = wxDynamicCast(m_validatorWindow, wxTextCtrl); + const bool wasModified = text ? text->IsModified() : false; + + control->ChangeValue(NormalizeString(control->GetValue())); + + if ( wasModified ) + text->MarkDirty(); + + event.Skip(); + +// Validate(text); +} + +// ============================================================================ +// IntegerValidatorBase implementation +// ============================================================================ + +wxString IntegerValidatorBase::ToString(LongestValueType value) const +{ + return NumberFormatter::ToString(value, GetFormatFlags()); +} + +bool +IntegerValidatorBase::FromString(const wxString& s, LongestValueType *value) +{ + return NumberFormatter::FromString(s, value); +} + +bool +IntegerValidatorBase::IsCharOk(const wxString& val, int pos, wxChar ch) const +{ + // We may accept minus sign if we can represent negative numbers at all. + if ( ch == '-' ) + { + // Notice that entering '-' can make our value invalid, for example if + // we're limited to -5..15 range and the current value is 12, then the + // new value would be (invalid) -12. We consider it better to let the + // user do this because perhaps he is going to press Delete key next to + // make it -2 and forcing him to delete 1 first would be unnatural. + // + // TODO: It would be nice to indicate that the current control contents + // is invalid (if it's indeed going to be the case) once + // wxValidator supports doing this non-intrusively. + return m_min < 0 && IsMinusOk(val, pos); + } + + // A separator is accepted if the locale allow it, the other chars must be digits + if ( ch < '0' || ch > '9' ) + { + wxChar thousands; + if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousands) ) + { +// if (ch != thousands) + return false; + } + else + { + return false; + } + } + + return true; +} + +bool IntegerValidatorBase::DoValidateNumber(wxString * errMsg) const +{ + wxTextEntry * const control = GetTextEntry(); + if ( !control ) + return false; + + wxString s(control->GetValue()); + wxChar thousandsSep; + if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousandsSep) ) + s.Replace(wxString(thousandsSep), wxString()); + + if ( s.empty() ) + { + // Is blank, but allowed. Stop here + if ( HasFlag(NUM_VAL_ZERO_AS_BLANK) ) + { + return true; + } + // We can't do any check with an empty string + else + { + *errMsg = _("Empty value"); + return false; + } + } + + // Can it be converted to a value? + LongestValueType value; + bool res = FromString(s, &value); + if ( !res ) + *errMsg = _("Malformed number"); + else + { + res = IsInRange(value); + if ( !res ) + *errMsg = _("Not in range"); + } + + return res; +} + +// ============================================================================ +// FloatingPointValidatorBase implementation +// ============================================================================ + +wxString FloatingPointValidatorBase::ToString(LongestValueType value) const +{ + return NumberFormatter::ToString(value, m_precision, GetFormatFlags()); +} + +bool +FloatingPointValidatorBase::FromString(const wxString& s, + LongestValueType *value) +{ + return NumberFormatter::FromString(s, value); +} + +bool +FloatingPointValidatorBase::IsCharOk(const wxString& val, + int pos, + wxChar ch) const +{ + if ( ch == '-' ) + { + // We may accept minus sign if we can represent negative numbers at all. + if ( pos == 0 ) + return m_min < 0 && IsMinusOk(val, pos); + // or for the exponent definition + else if ( val[pos-1] != 'e' && val[pos-1] != 'E' ) + return false; + + return true; + } + else if ( ch == '+' ) + { + if ( pos == 0 ) + return m_max >= 0; + else if ( val[pos-1] != 'e' && val[pos-1] != 'E' ) + return false; + + return true; + } + + const wxChar separator = NumberFormatter::GetDecimalSeparator(); + if ( ch == separator ) + { + if ( val.find(separator) != wxString::npos ) + { + // There is already a decimal separator, can't insert another one. + return false; + } + + // Prepending a separator before the sign isn't allowed. + if ( pos == 0 && !val.empty() && ( val[0] == '-' || val[0] == '+' ) ) + return false; + + // Otherwise always accept it, adding a decimal separator doesn't + // change the number value and, in particular, can't make it invalid. + // OTOH the checks below might not pass because strings like "." or + // "-." are not valid numbers so parsing them would fail, hence we need + // to treat it specially here. + return true; + } + + // Must be a digit, an exponent or a thousands separator. + if( ( ch < '0' || ch > '9' ) && ch != 'E' && ch != 'e' ) + { + wxChar thousands; + if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousands) ) + { +// if (ch != thousands) + return false; + } + else + { + return false; + } + } + + // Check the number of decimal digits in the final string + wxString str(val); + str.insert(pos, ch); + return ValidatePrecision(str); +} + +bool FloatingPointValidatorBase::DoValidateNumber(wxString * errMsg) const +{ + wxTextEntry * const control = GetTextEntry(); + if ( !control ) + return false; + + wxString s(control->GetValue()); + wxChar thousandsSep; + if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousandsSep) ) + s.Replace(wxString(thousandsSep), wxString()); + + if ( s.empty() ) + { + if ( HasFlag(NUM_VAL_ZERO_AS_BLANK) ) + return true; //Is blank, but allowed. Stop here + else + { + *errMsg = _("Empty value"); + return false; //We can't do any checks with an empty string + } + } + + LongestValueType value; + bool res = FromString(s, &value); // Can it be converted to a value? + if ( !res ) + *errMsg = _("Value overflow"); + else + { + res = ValidatePrecision(s); + if ( !res ) + *errMsg = _("Too many decimal digits"); + else + { + res = IsInRange(value); + if ( !res ) + { + if (m_minSet && m_maxSet) + { + errMsg->Printf(_("Value not in range: %.*f to %.*f"), + m_precision, m_min, m_precision, m_max); + } + else if (m_minSet) + { + errMsg->Printf(_("Value must not be less than %.*f"), + m_precision, m_min); + } + else if (m_maxSet) + { + errMsg->Printf(_("Value must not be greather than %.*f"), + m_precision, m_max); + } + } + } + } + + return res; +} + +bool FloatingPointValidatorBase::ValidatePrecision(const wxString& s) const +{ + size_t posSep = s.find(NumberFormatter::GetDecimalSeparator()); + if ( posSep == wxString::npos ) + posSep = s.length(); + + // If user typed exponent 'e' the number of decimal digits is not + // important at all. But we must know that 'e' position. + size_t posExp = s.Lower().Find(_("e")); + if ( posExp == wxString::npos ) + posExp = s.length(); + + // Return true if number has no more decimal digits than allowed + return ( (int)(posExp - posSep) - 1 <= (int)m_precision ); +} + +#endif // wxUSE_VALIDATORS && wxUSE_TEXTCTRL diff --git a/src/widgets/valnum.h b/src/widgets/valnum.h index e6268b61d..0e72d52bf 100644 --- a/src/widgets/valnum.h +++ b/src/widgets/valnum.h @@ -15,6 +15,7 @@ #if wxUSE_VALIDATORS +#include #include #include @@ -43,19 +44,23 @@ public: // Change the validator style. Usually it's specified during construction. void SetStyle(int style) { m_style = style; } - // Called when the value in the window must be validated. - // This function can pop up an error message. - virtual bool Validate(wxWindow * parent); + // Called when the value in the window must be validated. + // This function can pop up an error message. + virtual bool Validate(wxWindow * parent); protected: NumValidatorBase(int style) { m_style = style; + m_minSet = false; + m_maxSet = false; } NumValidatorBase(const NumValidatorBase& other) : wxValidator() { m_style = other.m_style; + m_minSet = other.m_minSet; + m_maxSet = other.m_maxSet; } bool HasFlag(NumValidatorStyle style) const @@ -85,6 +90,9 @@ protected: return val; } + bool m_minSet; + bool m_maxSet; + private: // Check whether the specified character can be inserted in the control at // the given position in the string representing the current controls @@ -97,14 +105,15 @@ private: // NormalizeString the contents of the string if it's a valid number, return // empty string otherwise. virtual wxString NormalizeString(const wxString& s) const = 0; - - // Do all checks to ensure this is a valid value. - // Returns 'true' if the control has valid value. - // Otherwise the cause is indicated in 'errMsg'. - virtual bool DoValidateNumber(wxString * errMsg) const = 0; + + // Do all checks to ensure this is a valid value. + // Returns 'true' if the control has valid value. + // Otherwise the cause is indicated in 'errMsg'. + virtual bool DoValidateNumber(wxString * errMsg) const = 0; // Event handlers. void OnChar(wxKeyEvent& event); + void OnPaste(wxClipboardTextEvent& event); void OnKillFocus(wxFocusEvent& event); @@ -115,7 +124,6 @@ private: // Combination of wxVAL_NUM_XXX values. int m_style; - DECLARE_EVENT_TABLE(); DECLARE_NO_ASSIGN_CLASS(NumValidatorBase); @@ -158,11 +166,13 @@ public: void SetMin(ValueType min) { this->DoSetMin(min); + BaseValidator::m_minSet = true; } void SetMax(ValueType max) { this->DoSetMax(max); + BaseValidator::m_maxSet = true; } void SetRange(ValueType min, ValueType max) @@ -179,7 +189,7 @@ public: if ( !control ) return false; - control->SetValue(NormalizeValue(*m_value)); + control->ChangeValue(NormalizeValue(*m_value)); } return true; @@ -193,6 +203,10 @@ public: if ( !control ) return false; + // If window is disabled, simply return + if ( !control->IsEnabled() ) + return true; + const wxString s(control->GetValue()); LongestValueType value; if ( s.empty() && BaseValidator::HasFlag(NUM_VAL_ZERO_AS_BLANK) ) @@ -397,9 +411,9 @@ protected: // Implement NumValidatorBase pure virtual method. virtual bool IsCharOk(const wxString& val, int pos, wxChar ch) const; virtual bool DoValidateNumber(wxString * errMsg) const; - - //Checks that it doesn't have too many decimal digits. - bool ValidatePrecision(const wxString& s) const; + + //Checks that it doesn't have too many decimal digits. + bool ValidatePrecision(const wxString& s) const; private: // Maximum number of decimals digits after the decimal separator. diff --git a/win/Projects/Audacity/Audacity.vcxproj b/win/Projects/Audacity/Audacity.vcxproj index 85f282d2a..78a0a8180 100755 --- a/win/Projects/Audacity/Audacity.vcxproj +++ b/win/Projects/Audacity/Audacity.vcxproj @@ -92,7 +92,7 @@ MaxSpeed - $(WXWIN)\lib\vc_dll\mswu;$(WXWIN)\include;..\..;..\..\..\include;..\..\..\lib-src\expat\lib;..\..\..\lib-src\FileDialog;..\..\..\lib-src\FileDialog\win;..\..\..\lib-src\ffmpeg\win32;..\..\..\lib-src\ffmpeg;..\..\..\lib-src\lib-widget-extra;..\..\..\lib-src\libflac\include;..\..\..\lib-src\libid3tag;..\..\..\lib-src\libmad\msvc++;..\..\..\lib-src\libmad;..\..\..\lib-src\libnyquist;..\..\..\lib-src\libogg\include;..\..\..\lib-src\libscorealign;..\libsndfile;..\..\..\lib-src\libsoxr\src;..\..\..\lib-src\libvamp;..\..\..\lib-src\libvorbis\include;..\..\..\lib-src\portaudio-v19\include;..\..\..\lib-src\portmixer\include;..\..\..\lib-src\portsmf;..\..\..\lib-src\sbsms\include;..\..\..\lib-src\soundtouch\include;..\..\..\lib-src\twolame\libtwolame;..\..\..\lib-src\portmidi\pm_common;..\..\..\lib-src\portmidi\pm_win;..\..\..\lib-src\portmidi\porttime;..\..\..\lib-src\lv2\lilv;..\..\..\lib-src\lv2\lv2;..\..\..\lib-src\lame;$(GSTREAMER_SDK)\include\gstreamer-1.0;$(GSTREAMER_SDK)\include\glib-2.0;$(GSTREAMER_SDK)\lib\glib-2.0\include;%(AdditionalIncludeDirectories) + $(WXWIN)\lib\vc_dll\mswu;$(WXWIN)\include;..\..;..\..\..\include;..\..\..\lib-src\expat\lib;..\..\..\lib-src\FileDialog;..\..\..\lib-src\FileDialog\win;..\..\..\lib-src\ffmpeg\win32;..\..\..\lib-src\ffmpeg;..\..\..\lib-src\lib-widget-extra;..\..\..\lib-src\libflac\include;..\..\..\lib-src\libid3tag;..\..\..\lib-src\libmad\msvc++;..\..\..\lib-src\libmad;..\..\..\lib-src\libnyquist;..\..\..\lib-src\libogg\include;..\..\..\lib-src\libscorealign;..\libsndfile;..\..\..\lib-src\libsoxr\src;..\..\..\lib-src\libvamp;..\..\..\lib-src\libvorbis\include;..\..\..\lib-src\portaudio-v19\include;..\..\..\lib-src\portmixer\include;..\..\..\lib-src\portsmf;..\..\..\lib-src\sbsms\include;..\..\..\lib-src\soundtouch\include;..\..\..\lib-src\twolame\libtwolame;..\..\..\lib-src\portmidi\pm_common;..\..\..\lib-src\portmidi\pm_win;..\..\..\lib-src\portmidi\porttime;..\..\..\lib-src\lv2\lilv;..\..\..\lib-src\lv2\lv2;..\..\..\lib-src\lv2\suil;..\..\..\lib-src\lame;$(GSTREAMER_SDK)\include\gstreamer-1.0;$(GSTREAMER_SDK)\include\glib-2.0;$(GSTREAMER_SDK)\lib\glib-2.0\include;%(AdditionalIncludeDirectories) BUILDING_AUDACITY;FLAC__NO_DLL;XML_STATIC;__STDC_CONSTANT_MACROS;WXUSINGDLL;__WXMSW__;NDEBUG;WIN32;STRICT;%(PreprocessorDefinitions) true MultiThreadedDLL @@ -123,7 +123,7 @@ MaxSpeed - $(WXWIN3)\lib\vc_dll\mswu;$(WXWIN3)\include;..\..;..\..\..\include;..\..\..\lib-src\expat\lib;..\..\..\lib-src\FileDialog;..\..\..\lib-src\FileDialog\win;..\..\..\lib-src\ffmpeg\win32;..\..\..\lib-src\ffmpeg;..\..\..\lib-src\lib-widget-extra;..\..\..\lib-src\libflac\include;..\..\..\lib-src\libid3tag;..\..\..\lib-src\libmad\msvc++;..\..\..\lib-src\libmad;..\..\..\lib-src\libnyquist;..\..\..\lib-src\libogg\include;..\..\..\lib-src\libscorealign;..\libsndfile;..\..\..\lib-src\libsoxr\src;..\..\..\lib-src\libvamp;..\..\..\lib-src\libvorbis\include;..\..\..\lib-src\portaudio-v19\include;..\..\..\lib-src\portmixer\include;..\..\..\lib-src\portsmf;..\..\..\lib-src\sbsms\include;..\..\..\lib-src\soundtouch\include;..\..\..\lib-src\twolame\libtwolame;..\..\..\lib-src\portmidi\pm_common;..\..\..\lib-src\portmidi\pm_win;..\..\..\lib-src\portmidi\porttime;..\..\..\lib-src\lv2\lilv;..\..\..\lib-src\lv2\lv2;..\..\..\lib-src\lame;$(GSTREAMER_SDK)\include\gstreamer-1.0;$(GSTREAMER_SDK)\include\glib-2.0;$(GSTREAMER_SDK)\lib\glib-2.0\include;%(AdditionalIncludeDirectories) + $(WXWIN3)\lib\vc_dll\mswu;$(WXWIN3)\include;..\..;..\..\..\include;..\..\..\lib-src\expat\lib;..\..\..\lib-src\FileDialog;..\..\..\lib-src\FileDialog\win;..\..\..\lib-src\ffmpeg\win32;..\..\..\lib-src\ffmpeg;..\..\..\lib-src\lib-widget-extra;..\..\..\lib-src\libflac\include;..\..\..\lib-src\libid3tag;..\..\..\lib-src\libmad\msvc++;..\..\..\lib-src\libmad;..\..\..\lib-src\libnyquist;..\..\..\lib-src\libogg\include;..\..\..\lib-src\libscorealign;..\libsndfile;..\..\..\lib-src\libsoxr\src;..\..\..\lib-src\libvamp;..\..\..\lib-src\libvorbis\include;..\..\..\lib-src\portaudio-v19\include;..\..\..\lib-src\portmixer\include;..\..\..\lib-src\portsmf;..\..\..\lib-src\sbsms\include;..\..\..\lib-src\soundtouch\include;..\..\..\lib-src\twolame\libtwolame;..\..\..\lib-src\portmidi\pm_common;..\..\..\lib-src\portmidi\pm_win;..\..\..\lib-src\portmidi\porttime;..\..\..\lib-src\lv2\lilv;..\..\..\lib-src\lv2\lv2;..\..\..\lib-src\lv2\suil;..\..\..\lib-src\lame;$(GSTREAMER_SDK)\include\gstreamer-1.0;$(GSTREAMER_SDK)\include\glib-2.0;$(GSTREAMER_SDK)\lib\glib-2.0\include;%(AdditionalIncludeDirectories) BUILDING_AUDACITY;FLAC__NO_DLL;XML_STATIC;__STDC_CONSTANT_MACROS;WXUSINGDLL;__WXMSW__;NDEBUG;WIN32;STRICT;%(PreprocessorDefinitions) true MultiThreadedDLL @@ -155,7 +155,7 @@ Disabled - $(WXWIN)\lib\vc_dll\mswud;$(WXWIN)\include;..\..;..\..\..\include;..\..\..\lib-src\expat\lib;..\..\..\lib-src\FileDialog;..\..\..\lib-src\FileDialog\win;..\..\..\lib-src\lib-widget-extra;..\..\..\lib-src\libflac\include;..\..\..\lib-src\libid3tag;..\..\..\lib-src\libmad\msvc++;..\..\..\lib-src\libmad;..\..\..\lib-src\libnyquist;..\..\..\lib-src\libogg\include;..\..\..\lib-src\libscorealign;..\libsndfile;..\..\..\lib-src\libsoxr\src;..\..\..\lib-src\libvamp;..\..\..\lib-src\libvorbis\include;..\..\..\lib-src\portaudio-v19\include;..\..\..\lib-src\portmixer\include;..\..\..\lib-src\portsmf;..\..\..\lib-src\sbsms\include;..\..\..\lib-src\soundtouch\include;..\..\..\lib-src\twolame\libtwolame;..\..\..\lib-src\portmidi\pm_common;..\..\..\lib-src\portmidi\pm_win;..\..\..\lib-src\portmidi\porttime;..\..\..\lib-src\ffmpeg\win32;..\..\..\lib-src\ffmpeg;..\..\..\lib-src\lv2\lilv;..\..\..\lib-src\lv2\lv2;..\..\..\lib-src\lame;&quot;$(GSTREAMER_SDK)\include\gstreamer-1.0;$(GSTREAMER_SDK)\include\glib-2.0;$(GSTREAMER_SDK)\lib\glib-2.0\include;%(AdditionalIncludeDirectories) + $(WXWIN)\lib\vc_dll\mswud;$(WXWIN)\include;..\..;..\..\..\include;..\..\..\lib-src\expat\lib;..\..\..\lib-src\FileDialog;..\..\..\lib-src\FileDialog\win;..\..\..\lib-src\lib-widget-extra;..\..\..\lib-src\libflac\include;..\..\..\lib-src\libid3tag;..\..\..\lib-src\libmad\msvc++;..\..\..\lib-src\libmad;..\..\..\lib-src\libnyquist;..\..\..\lib-src\libogg\include;..\..\..\lib-src\libscorealign;..\libsndfile;..\..\..\lib-src\libsoxr\src;..\..\..\lib-src\libvamp;..\..\..\lib-src\libvorbis\include;..\..\..\lib-src\portaudio-v19\include;..\..\..\lib-src\portmixer\include;..\..\..\lib-src\portsmf;..\..\..\lib-src\sbsms\include;..\..\..\lib-src\soundtouch\include;..\..\..\lib-src\twolame\libtwolame;..\..\..\lib-src\portmidi\pm_common;..\..\..\lib-src\portmidi\pm_win;..\..\..\lib-src\portmidi\porttime;..\..\..\lib-src\ffmpeg\win32;..\..\..\lib-src\ffmpeg;..\..\..\lib-src\lv2\lilv;..\..\..\lib-src\lv2\lv2;..\..\..\lib-src\lv2\suil;..\..\..\lib-src\lame;&quot;$(GSTREAMER_SDK)\include\gstreamer-1.0;$(GSTREAMER_SDK)\include\glib-2.0;$(GSTREAMER_SDK)\lib\glib-2.0\include;%(AdditionalIncludeDirectories) BUILDING_AUDACITY;FLAC__NO_DLL;XML_STATIC;__STDC_CONSTANT_MACROS;WXUSINGDLL;__WXMSW__;__WXDEBUG__;_DEBUG;WIN32;STRICT;%(PreprocessorDefinitions) true EnableFastChecks @@ -191,7 +191,7 @@ Disabled - $(WXWIN3)\lib\vc_dll\mswud;$(WXWIN3)\include;..\..;..\..\..\include;..\..\..\lib-src\expat\lib;..\..\..\lib-src\FileDialog;..\..\..\lib-src\FileDialog\win;..\..\..\lib-src\lib-widget-extra;..\..\..\lib-src\libflac\include;..\..\..\lib-src\libid3tag;..\..\..\lib-src\libmad\msvc++;..\..\..\lib-src\libmad;..\..\..\lib-src\libnyquist;..\..\..\lib-src\libogg\include;..\..\..\lib-src\libscorealign;..\libsndfile;..\..\..\lib-src\libsoxr\src;..\..\..\lib-src\libvamp;..\..\..\lib-src\libvorbis\include;..\..\..\lib-src\portaudio-v19\include;..\..\..\lib-src\portmixer\include;..\..\..\lib-src\portsmf;..\..\..\lib-src\sbsms\include;..\..\..\lib-src\soundtouch\include;..\..\..\lib-src\twolame\libtwolame;..\..\..\lib-src\portmidi\pm_common;..\..\..\lib-src\portmidi\pm_win;..\..\..\lib-src\portmidi\porttime;..\..\..\lib-src\ffmpeg\win32;..\..\..\lib-src\ffmpeg;..\..\..\lib-src\lv2\lilv;..\..\..\lib-src\lv2\lv2;..\..\..\lib-src\lame;&quot;$(GSTREAMER_SDK)\include\gstreamer-1.0;$(GSTREAMER_SDK)\include\glib-2.0;$(GSTREAMER_SDK)\lib\glib-2.0\include;%(AdditionalIncludeDirectories) + $(WXWIN3)\lib\vc_dll\mswud;$(WXWIN3)\include;..\..;..\..\..\include;..\..\..\lib-src\expat\lib;..\..\..\lib-src\FileDialog;..\..\..\lib-src\FileDialog\win;..\..\..\lib-src\lib-widget-extra;..\..\..\lib-src\libflac\include;..\..\..\lib-src\libid3tag;..\..\..\lib-src\libmad\msvc++;..\..\..\lib-src\libmad;..\..\..\lib-src\libnyquist;..\..\..\lib-src\libogg\include;..\..\..\lib-src\libscorealign;..\libsndfile;..\..\..\lib-src\libsoxr\src;..\..\..\lib-src\libvamp;..\..\..\lib-src\libvorbis\include;..\..\..\lib-src\portaudio-v19\include;..\..\..\lib-src\portmixer\include;..\..\..\lib-src\portsmf;..\..\..\lib-src\sbsms\include;..\..\..\lib-src\soundtouch\include;..\..\..\lib-src\twolame\libtwolame;..\..\..\lib-src\portmidi\pm_common;..\..\..\lib-src\portmidi\pm_win;..\..\..\lib-src\portmidi\porttime;..\..\..\lib-src\ffmpeg\win32;..\..\..\lib-src\ffmpeg;..\..\..\lib-src\lv2\lilv;..\..\..\lib-src\lv2\lv2;..\..\..\lib-src\lv2\suil;..\..\..\lib-src\lame;&quot;$(GSTREAMER_SDK)\include\gstreamer-1.0;$(GSTREAMER_SDK)\include\glib-2.0;$(GSTREAMER_SDK)\lib\glib-2.0\include;%(AdditionalIncludeDirectories) BUILDING_AUDACITY;FLAC__NO_DLL;XML_STATIC;__STDC_CONSTANT_MACROS;WXUSINGDLL;__WXMSW__;__WXDEBUG__;_DEBUG;WIN32;STRICT;%(PreprocessorDefinitions) true EnableFastChecks @@ -342,7 +342,6 @@ - @@ -494,7 +493,6 @@ - @@ -617,7 +615,6 @@ - @@ -775,7 +772,6 @@ - diff --git a/win/Projects/Audacity/Audacity.vcxproj.filters b/win/Projects/Audacity/Audacity.vcxproj.filters index 5fc0f8aae..9a31c3d9c 100755 --- a/win/Projects/Audacity/Audacity.vcxproj.filters +++ b/win/Projects/Audacity/Audacity.vcxproj.filters @@ -336,9 +336,6 @@ src/effects - - src/effects - src/effects @@ -792,9 +789,6 @@ src/effects/lv2 - - src/effects/lv2 - src @@ -1124,9 +1118,6 @@ src/effects - - src/effects - src/effects @@ -1598,9 +1589,6 @@ src/effects/lv2 - - src/effects/lv2 - src