diff --git a/include/audacity/Types.h b/include/audacity/Types.h index 63f028964..1bf2ead26 100644 --- a/include/audacity/Types.h +++ b/include/audacity/Types.h @@ -184,12 +184,12 @@ inline size_t limitSampleBufferSize( size_t bufferSize, sampleCount limit ) // ---------------------------------------------------------------------------- // Supported sample formats // ---------------------------------------------------------------------------- -typedef enum +enum sampleFormat : unsigned { int16Sample = 0x00020001, int24Sample = 0x00040001, floatSample = 0x0004000F -} sampleFormat; +}; // ---------------------------------------------------------------------------- // Provide the number of bytes a specific sample will take diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 876e7ca6a..a01302cf7 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -447,6 +447,7 @@ TimeTrack and AudioIOListener and whether the playback is looped. #include "WaveTrack.h" #include "AutoRecovery.h" +#include "prefs/QualityPrefs.h" #include "toolbars/ControlToolBar.h" #include "widgets/Meter.h" #include "widgets/ErrorDialog.h" @@ -1825,8 +1826,7 @@ void AudioIO::StartMonitoring(double sampleRate) bool success; long captureChannels; - sampleFormat captureFormat = (sampleFormat) - gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample); + auto captureFormat = QualityPrefs::SampleFormatChoice(); gPrefs->Read(wxT("/AudioIO/RecordChannels"), &captureChannels, 2L); gPrefs->Read(wxT("/AudioIO/SWPlaythrough"), &mSoftwarePlaythrough, false); int playbackChannels = 0; diff --git a/src/Dither.cpp b/src/Dither.cpp index b178f70a0..4f9569061 100644 --- a/src/Dither.cpp +++ b/src/Dither.cpp @@ -325,17 +325,17 @@ void Dither::Apply(enum DitherType ditherType, // We must do dithering switch (ditherType) { - case none: + case DitherType::none: DITHER(NoDither, dest, destFormat, destStride, source, sourceFormat, sourceStride, len); break; - case rectangle: + case DitherType::rectangle: DITHER(RectangleDither, dest, destFormat, destStride, source, sourceFormat, sourceStride, len); break; - case triangle: + case DitherType::triangle: Reset(); // reset dither filter for this NEW conversion DITHER(TriangleDither, dest, destFormat, destStride, source, sourceFormat, sourceStride, len); break; - case shaped: + case DitherType::shaped: Reset(); // reset dither filter for this NEW conversion DITHER(ShapedDither, dest, destFormat, destStride, source, sourceFormat, sourceStride, len); break; diff --git a/src/Dither.h b/src/Dither.h index 3069e3140..a7814b5b6 100644 --- a/src/Dither.h +++ b/src/Dither.h @@ -13,15 +13,16 @@ #include "SampleFormat.h" +/// These ditherers are currently available: +enum class DitherType : unsigned { + none = 0, rectangle = 1, triangle = 2, shaped = 3 }; + class Dither { public: /// Default constructor Dither(); - /// These ditherers are currently available: - enum DitherType { none = 0, rectangle = 1, triangle = 2, shaped = 3}; - /// Reset state of the dither. void Reset(); diff --git a/src/Menus.cpp b/src/Menus.cpp index 354ad39c4..170edd5bc 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -6817,10 +6817,8 @@ void AudacityProject::OnZoomToggle(const CommandContext &WXUNUSED(context) ) // const double origWidth = GetScreenEndTime() - origLeft; // Choose the zoom that is most different to the current zoom. - double Zoom1 = GetZoomOfPref( - wxT("/GUI/ZoomPreset1"), WaveTrack::kZoomDefault ); - double Zoom2 = GetZoomOfPref( - wxT("/GUI/ZoomPreset2"), WaveTrack::kZoom4To1 ); + double Zoom1 = GetZoomOfPreset( TracksPrefs::Zoom1Choice() ); + double Zoom2 = GetZoomOfPreset( TracksPrefs::Zoom2Choice() ); double Z = mViewInfo.GetZoom();// Current Zoom. double ChosenZoom = abs(log(Zoom1 / Z)) > abs(log( Z / Zoom2)) ? Zoom1:Zoom2; diff --git a/src/Prefs.cpp b/src/Prefs.cpp index bf2cdd7f2..3c20859bd 100755 --- a/src/Prefs.cpp +++ b/src/Prefs.cpp @@ -341,3 +341,90 @@ void FinishPreferences() gPrefs = NULL; } } + +////////// +wxString EnumSetting::Read() const +{ + const auto &defaultValue = Default().Internal(); + wxString value; + if ( !gPrefs->Read(mKey, &value, defaultValue) ) + if (!mMigrated) { + const_cast(this)->Migrate( value ); + mMigrated = true; + } + + // Remap to default if the string is not known -- this avoids surprises + // in case we try to interpret config files from future versions + auto index = Find( value ); + if ( index >= mnSymbols ) + value = defaultValue; + return value; +} + +size_t EnumSetting::Find( const wxString &value ) const +{ + return size_t( + std::find( begin(), end(), IdentInterfaceSymbol{ value, {} } ) + - mSymbols ); +} + +void EnumSetting::Migrate( wxString &value ) +{ +} + +bool EnumSetting::Write( const wxString &value ) +{ + auto index = Find( value ); + if (index >= mnSymbols) + return false; + + auto result = gPrefs->Write( mKey, value ); + mMigrated = true; + return result; +} + +int EncodedEnumSetting::ReadInt() const +{ + if (!mIntValues) + return 0; + + auto index = Find( Read() ); + wxASSERT( index < mnSymbols ); + return mIntValues[ index ]; +} + +size_t EncodedEnumSetting::FindInt( int code ) const +{ + if (!mIntValues) + return mnSymbols; + + return size_t( + std::find( mIntValues, mIntValues + mnSymbols, code ) + - mIntValues ); +} + +void EncodedEnumSetting::Migrate( wxString &value ) +{ + int intValue = 0; + if ( !mOldKey.empty() && + gPrefs->Read(mOldKey, &intValue, 0) ) { + // Make the migration, only once and persistently. + // Do not DELETE the old key -- let that be read if user downgrades + // Audacity. But further changes will be stored only to the NEW key + // and won't be seen then. + auto index = FindInt( intValue ); + if ( index >= mnSymbols ) + index = mDefaultSymbol; + value = mSymbols[index].Internal(); + Write(value); + gPrefs->Flush(); + } +} + +bool EncodedEnumSetting::WriteInt( int code ) // you flush gPrefs afterward +{ + auto index = FindInt( code ); + if ( index >= mnSymbols ) + return false; + return Write( mSymbols[index].Internal() ); +} diff --git a/src/Prefs.h b/src/Prefs.h index 7708a5150..97e6c5b6d 100644 --- a/src/Prefs.h +++ b/src/Prefs.h @@ -30,6 +30,7 @@ #define __AUDACITY_PREFS__ #include "Audacity.h" +#include "../include/audacity/IdentInterface.h" #include #include @@ -40,4 +41,84 @@ void FinishPreferences(); extern AUDACITY_DLL_API wxFileConfig *gPrefs; extern int gMenusDirty; +// Packages a table of user-visible choices each with an internal code string, +// a preference key path, +// and a default choice +class EnumSetting +{ +public: + EnumSetting( + const wxString &key, + const IdentInterfaceSymbol symbols[], size_t nSymbols, + size_t defaultSymbol + ) + : mKey{ key } + + , mSymbols{ symbols } + , mnSymbols{ nSymbols } + + , mDefaultSymbol{ defaultSymbol } + { + wxASSERT( defaultSymbol < nSymbols ); + } + + const wxString &Key() const { return mKey; } + const IdentInterfaceSymbol &Default() const + { return mSymbols[mDefaultSymbol]; } + const IdentInterfaceSymbol *begin() const { return mSymbols; } + const IdentInterfaceSymbol *end() const { return mSymbols + mnSymbols; } + + wxString Read() const; + bool Write( const wxString &value ); // you flush gPrefs afterward + +protected: + size_t Find( const wxString &value ) const; + virtual void Migrate( wxString& ); + + const wxString mKey; + + const IdentInterfaceSymbol *mSymbols; + const size_t mnSymbols; + + // stores an internal value + mutable bool mMigrated { false }; + + const size_t mDefaultSymbol; +}; + +// Extends EnumSetting with a corresponding table of integer codes +// (generally not equal to their table positions), +// and optionally an old preference key path that stored integer codes, to be +// migrated into one that stores internal string values instead +class EncodedEnumSetting : public EnumSetting +{ +public: + EncodedEnumSetting( + const wxString &key, + const IdentInterfaceSymbol symbols[], size_t nSymbols, + size_t defaultSymbol, + + const int intValues[] = nullptr, // must have same size as symbols + const wxString &oldKey = {} + ) + : EnumSetting{ key, symbols, nSymbols, defaultSymbol } + , mIntValues{ intValues } + , mOldKey{ oldKey } + { + wxASSERT( mIntValues ); + } + + // Read and write the encoded values + virtual int ReadInt() const; + bool WriteInt( int code ); // you flush gPrefs afterward + +protected: + size_t FindInt( int code ) const; + void Migrate( wxString& ) override; + +private: + const int *mIntValues; + const wxString mOldKey; +}; + #endif diff --git a/src/Project.cpp b/src/Project.cpp index 3b9fb9207..a6c6b73fb 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -166,6 +166,8 @@ scroll information. It also has some status flags. #include "commands/CommandType.h" #include "commands/CommandContext.h" +#include "prefs/QualityPrefs.h" + #include "../images/AudacityLogoAlpha.xpm" std::shared_ptr AudacityProject::msClipboard{ TrackList::Create() }; @@ -917,8 +919,7 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id, mViewInfo(0.0, 1.0, ZoomInfo::GetDefaultZoom()), mbLoadedFromAup( false ), mRate((double) gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleRate"), AudioIO::GetOptimalSupportedSampleRate())), - mDefaultFormat((sampleFormat) gPrefs-> - Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample)), + mDefaultFormat(QualityPrefs::SampleFormatChoice()), mSnapTo(gPrefs->Read(wxT("/SnapTo"), SNAP_OFF)), mSelectionFormat(gPrefs->Read(wxT("/SelectionFormat"), wxT(""))), mFrequencySelectionFormatName(gPrefs->Read(wxT("/FrequencySelectionFormatName"), wxT(""))), @@ -1305,7 +1306,7 @@ void AudacityProject::UpdatePrefsVariables() // gPrefs->Read(wxT("/GUI/UpdateSpectrogram"), &mViewInfo.bUpdateSpectrogram, true); gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleRate"), &mRate, AudioIO::GetOptimalSupportedSampleRate()); - mDefaultFormat = (sampleFormat) gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample); + mDefaultFormat = QualityPrefs::SampleFormatChoice(); gPrefs->Read(wxT("/AudioIO/SeekShortPeriod"), &mSeekShort, 1.0); gPrefs->Read(wxT("/AudioIO/SeekLongPeriod"), &mSeekLong, 15.0); @@ -5887,8 +5888,7 @@ wxString AudacityProject::GetHoursMinsString(int iMinutes) int AudacityProject::GetEstimatedRecordingMinsLeftOnDisk(long lCaptureChannels) { // Obtain the current settings - sampleFormat oCaptureFormat = (sampleFormat) - gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample); + auto oCaptureFormat = QualityPrefs::SampleFormatChoice(); if (lCaptureChannels == 0) { gPrefs->Read(wxT("/AudioIO/RecordChannels"), &lCaptureChannels, 2L); } @@ -6014,12 +6014,6 @@ double AudacityProject::GetZoomOfPreset( int preset ){ return result; } -double AudacityProject::GetZoomOfPref( const wxString & PresetPrefName, int defaultPreset ){ - int preset=defaultPreset; - gPrefs->Read( PresetPrefName, &preset, defaultPreset ); - return GetZoomOfPreset( preset ); -} - AudacityProject::PlaybackScroller::PlaybackScroller(AudacityProject *project) : mProject(project) { diff --git a/src/Project.h b/src/Project.h index 0056c1a0b..85474b5a0 100644 --- a/src/Project.h +++ b/src/Project.h @@ -600,7 +600,6 @@ public: double GetZoomOfToFit(); double GetZoomOfSelection(); double GetZoomOfPreset(int preset ); - double GetZoomOfPref( const wxString & PresetPrefName, int defaultPreset ); public: bool IsSoloSimple() const { return mSoloPref == wxT("Simple"); } diff --git a/src/Resample.cpp b/src/Resample.cpp index c7317c379..24f2865ae 100644 --- a/src/Resample.cpp +++ b/src/Resample.cpp @@ -26,6 +26,7 @@ #include "Prefs.h" #include "TranslatableStringArray.h" #include "Internat.h" +#include "../include/audacity/IdentInterface.h" #include @@ -50,47 +51,57 @@ Resample::~Resample() { } -int Resample::GetNumMethods() { return 4; } +////////// +static const IdentInterfaceSymbol methodNames[] = { + { wxT("LowQuality"), XO("Low Quality (Fastest)") }, + { wxT("MediumQuality"), XO("Medium Quality") }, + { wxT("HighQuality"), XO("High Quality") }, + { wxT("BestQuality"), XO("Best Quality (Slowest)") } +}; -wxString Resample::GetMethodName(int index) +static const size_t numMethods = WXSIZEOF(methodNames); + +static const wxString fastMethodKey = + wxT("/Quality/LibsoxrSampleRateConverterChoice"); + +static const wxString bestMethodKey = + wxT("/Quality/LibsoxrHQSampleRateConverterChoice"); + +static const wxString oldFastMethodKey = + wxT("/Quality/LibsoxrSampleRateConverter"); + +static const wxString oldBestMethodKey = + wxT("/Quality/LibsoxrHQSampleRateConverter"); + +static const size_t fastMethodDefault = 1; // Medium Quality +static const size_t bestMethodDefault = 3; // Best Quality + +static const int intChoicesMethod[] = { + 0, 1, 2, 3 +}; + +static_assert( WXSIZEOF(intChoicesMethod) == numMethods, "size mismatch" ); + +EncodedEnumSetting Resample::FastMethodSetting{ + fastMethodKey, + methodNames, numMethods, + fastMethodDefault, + + intChoicesMethod, + oldFastMethodKey +}; + +EncodedEnumSetting Resample::BestMethodSetting { - static const wxString soxr_method_names[] = { - XO("Low Quality (Fastest)"), - XO("Medium Quality"), - XO("High Quality"), - XO("Best Quality (Slowest)") - }; + bestMethodKey, + methodNames, numMethods, + bestMethodDefault, - wxASSERT( GetNumMethods() == - sizeof(soxr_method_names) / sizeof(*soxr_method_names) ); - - class MethodNamesArray final : public TranslatableStringArray - { - void Populate() override - { - for (auto &name : soxr_method_names) - mContents.push_back( wxGetTranslation( name ) ); - } - }; - - static MethodNamesArray theArray; - - return theArray.Get()[ index ]; -} - -const wxString Resample::GetFastMethodKey() -{ - return wxT("/Quality/LibsoxrSampleRateConverter"); -} - -const wxString Resample::GetBestMethodKey() -{ - return wxT("/Quality/LibsoxrHQSampleRateConverter"); -} - -int Resample::GetFastMethodDefault() {return 1;} -int Resample::GetBestMethodDefault() {return 3;} + intChoicesMethod, + oldBestMethodKey +}; +////////// std::pair Resample::Process(double factor, float *inBuffer, @@ -121,7 +132,7 @@ std::pair void Resample::SetMethod(const bool useBestMethod) { if (useBestMethod) - mMethod = gPrefs->Read(GetBestMethodKey(), GetBestMethodDefault()); + mMethod = BestMethodSetting.ReadInt(); else - mMethod = gPrefs->Read(GetFastMethodKey(), GetFastMethodDefault()); + mMethod = FastMethodSetting.ReadInt(); } diff --git a/src/Resample.h b/src/Resample.h index aa14ea842..023d6f291 100644 --- a/src/Resample.h +++ b/src/Resample.h @@ -20,6 +20,8 @@ #include "SampleFormat.h" +class EncodedEnumSetting; + struct soxr; extern "C" void soxr_delete(soxr*); struct soxr_deleter { @@ -43,13 +45,8 @@ class Resample final Resample(const bool useBestMethod, const double dMinFactor, const double dMaxFactor); ~Resample(); - static int GetNumMethods(); - static wxString GetMethodName(int index); - - static const wxString GetFastMethodKey(); - static const wxString GetBestMethodKey(); - static int GetFastMethodDefault(); - static int GetBestMethodDefault(); + static EncodedEnumSetting FastMethodSetting; + static EncodedEnumSetting BestMethodSetting; /** @brief Main processing function. Resamples from the input buffer to the * output buffer. diff --git a/src/SampleFormat.cpp b/src/SampleFormat.cpp index 84611bafb..951f5df39 100644 --- a/src/SampleFormat.cpp +++ b/src/SampleFormat.cpp @@ -44,19 +44,17 @@ #include "Prefs.h" #include "Dither.h" #include "Internat.h" +#include "prefs/QualityPrefs.h" -static Dither::DitherType gLowQualityDither = Dither::none; -static Dither::DitherType gHighQualityDither = Dither::none; +static DitherType gLowQualityDither = DitherType::none; +static DitherType gHighQualityDither = DitherType::none; static Dither gDitherAlgorithm; void InitDitherers() { // Read dither preferences - gLowQualityDither = (Dither::DitherType) - gPrefs->Read(wxT("/Quality/DitherAlgorithm"), (long)Dither::none); - - gHighQualityDither = (Dither::DitherType) - gPrefs->Read(wxT("/Quality/HQDitherAlgorithm"), (long)Dither::shaped); + gLowQualityDither = QualityPrefs::FastDitherChoice(); + gHighQualityDither = QualityPrefs::BestDitherChoice(); } const wxChar *GetSampleFormatStr(sampleFormat format) @@ -120,6 +118,6 @@ void CopySamplesNoDither(samplePtr src, sampleFormat srcFormat, unsigned int dstStride /* = 1 */) { gDitherAlgorithm.Apply( - Dither::none, + DitherType::none, src, srcFormat, dst, dstFormat, len, srcStride, dstStride); } diff --git a/src/Sequence.cpp b/src/Sequence.cpp index 586cedc1e..66d2f28cc 100644 --- a/src/Sequence.cpp +++ b/src/Sequence.cpp @@ -1040,7 +1040,7 @@ void Sequence::WriteXML(XMLWriter &xmlFile) const xmlFile.StartTag(wxT("sequence")); xmlFile.WriteAttr(wxT("maxsamples"), mMaxSamples); - xmlFile.WriteAttr(wxT("sampleformat"), mSampleFormat); + xmlFile.WriteAttr(wxT("sampleformat"), (size_t)mSampleFormat); xmlFile.WriteAttr(wxT("numsamples"), mNumSamples.as_long_long() ); for (b = 0; b < mBlock.size(); b++) { diff --git a/src/ShuttleGui.cpp b/src/ShuttleGui.cpp index a6dcbf773..da515ac2c 100644 --- a/src/ShuttleGui.cpp +++ b/src/ShuttleGui.cpp @@ -95,6 +95,7 @@ for registering for changes. #include "Audacity.h" #include "Experimental.h" +#include "Prefs.h" #include "Shuttle.h" #include "ShuttleGui.h" @@ -106,6 +107,7 @@ for registering for changes. #include #include #include +#include "../include/audacity/IdentInterface.h" #include "Internat.h" #include "WrappedType.h" #include "widgets/wxPanelWrapper.h" @@ -1847,32 +1849,55 @@ wxTextCtrl * ShuttleGuiBase::TieNumericTextBox( return pText; } +/// Variant of the standard TieChoice which does the two step exchange +/// between gui and stack variable and stack variable and shuttle. +/// @param Prompt The prompt shown beside the control. +/// @param Setting Encapsulates setting name, internal and visible +/// choice strings, and a designation of one of +/// those as default. +wxChoice *ShuttleGuiBase::TieChoice( + const wxString &Prompt, + EnumSetting &enumSetting ) +{ + // Do this to force any needed migrations first + enumSetting.Read(); + + wxArrayString visibleChoices, internalChoices; + for (const auto &ident : enumSetting) { + visibleChoices.push_back( ident.Translation() ); + internalChoices.push_back( ident.Internal() ); + } + return TieChoice( + Prompt, enumSetting.Key(), enumSetting.Default().Translation(), + visibleChoices, internalChoices ); +} + /// Variant of the standard TieChoice which does the two step exchange /// between gui and stack variable and stack variable and shuttle. /// @param Prompt The prompt shown beside the control. /// @param SettingName The setting name as stored in gPrefs /// @param Default The default value for this control (translated) /// @param Choices An array of choices that appear on screen. -/// @param TranslatedChoices The corresponding values (as a string array) +/// @param InternalChoices The corresponding values (as a string array) wxChoice * ShuttleGuiBase::TieChoice( const wxString &Prompt, const wxString &SettingName, const wxString &Default, const wxArrayString & Choices, - const wxArrayString & TranslatedChoices) + const wxArrayString & InternalChoices) { wxChoice * pChoice=(wxChoice*)NULL; int TempIndex=0; -// int TempIndex = TranslateToIndex( Default, TranslatedChoices ); +// int TempIndex = TranslateToIndex( Default, InternalChoices ); wxString TempStr = Default; WrappedType WrappedRef( TempStr ); // Get from prefs does 1 and 2. // Put to prefs does 2 and 3. if( DoStep(1) ) DoDataShuttle( SettingName, WrappedRef ); // Get Index from Prefs. - if( DoStep(1) ) TempIndex = TranslateToIndex( TempStr, TranslatedChoices ); // To an index + if( DoStep(1) ) TempIndex = TranslateToIndex( TempStr, InternalChoices ); // To an index if( DoStep(2) ) pChoice = TieChoice( Prompt, TempIndex, &Choices ); // Get/Put index from GUI. - if( DoStep(3) ) TempStr = TranslateFromIndex( TempIndex, TranslatedChoices ); // To a string + if( DoStep(3) ) TempStr = TranslateFromIndex( TempIndex, InternalChoices ); // To a string if( DoStep(3) ) DoDataShuttle( SettingName, WrappedRef ); // Put into Prefs. return pChoice; } @@ -1885,13 +1910,13 @@ wxChoice * ShuttleGuiBase::TieChoice( /// @param SettingName The setting name as stored in gPrefs /// @param Default The default value for this control (translated) /// @param Choices An array of choices that appear on screen. -/// @param TranslatedChoices The correcponding values (as an integer array) +/// @param InternalChoices The corresponding values (as an integer array) wxChoice * ShuttleGuiBase::TieChoice( const wxString &Prompt, const wxString &SettingName, const int Default, const wxArrayString & Choices, - const std::vector & TranslatedChoices) + const std::vector & InternalChoices) { wxChoice * pChoice=(wxChoice*)NULL; @@ -1901,13 +1926,34 @@ wxChoice * ShuttleGuiBase::TieChoice( // Get from prefs does 1 and 2. // Put to prefs does 2 and 3. if( DoStep(1) ) DoDataShuttle( SettingName, WrappedRef ); // Get Int from Prefs. - if( DoStep(1) ) TempIndex = TranslateToIndex( TranslatedInt, TranslatedChoices ); // Int to an index. + if( DoStep(1) ) TempIndex = TranslateToIndex( TranslatedInt, InternalChoices ); // Int to an index. if( DoStep(2) ) pChoice = TieChoice( Prompt, TempIndex, &Choices ); // Get/Put index from GUI. - if( DoStep(3) ) TranslatedInt = TranslateFromIndex( TempIndex, TranslatedChoices ); // Index to int + if( DoStep(3) ) TranslatedInt = TranslateFromIndex( TempIndex, InternalChoices ); // Index to int if( DoStep(3) ) DoDataShuttle( SettingName, WrappedRef ); // Put into Prefs. return pChoice; } +/// Variant of the standard TieChoice which does the two step exchange +/// between gui and stack variable and stack variable and shuttle. +/// The Translated choices and default are integers, not Strings. +/// Behaves identically to the previous, but is meant for use when the choices +/// are non-exhaustive and there is a companion control for abitrary entry. +/// @param Prompt The prompt shown beside the control. +/// @param SettingName The setting name as stored in gPrefs +/// @param Default The default value for this control (translated) +/// @param Choices An array of choices that appear on screen. +/// @param InternalChoices The corresponding values (as an integer array) +wxChoice * ShuttleGuiBase::TieNumberAsChoice( + const wxString &Prompt, + const wxString &SettingName, + const int Default, + const wxArrayString & Choices, + const std::vector & InternalChoices) +{ + return ShuttleGuiBase::TieChoice( + Prompt, SettingName, Default, Choices, InternalChoices ); +} + /// Integer specific version of StartRadioButtonGroup. /// All 'TieRadioButton()' enclosed must be ints. void ShuttleGuiBase::StartRadioButtonGroup( const wxString & SettingName, const int iDefaultValue ) @@ -2391,7 +2437,7 @@ wxChoice * ShuttleGuiGetDefinition::TieChoice( const wxString &SettingName, const wxString &Default, const wxArrayString &Choices, - const wxArrayString & TranslatedChoices ) + const wxArrayString & InternalChoices ) { StartStruct(); AddItem( SettingName, "id" ); @@ -2405,15 +2451,22 @@ wxChoice * ShuttleGuiGetDefinition::TieChoice( EndArray(); EndField(); EndStruct(); - return ShuttleGui::TieChoice( Prompt, SettingName, Default, Choices, TranslatedChoices ); + return ShuttleGui::TieChoice( Prompt, SettingName, Default, Choices, InternalChoices ); } wxChoice * ShuttleGuiGetDefinition::TieChoice( const wxString &Prompt, const wxString &SettingName, const int Default, const wxArrayString & Choices, - const std::vector & TranslatedChoices) + const std::vector & InternalChoices) { + // Should no longer come here! + // Choice controls in Preferences that really are exhaustive choices among + // non-numerical options must now encode the internal choices as strings, + // not numbers. + wxASSERT(false); + + // But if we do get here anyway, proceed sub-optimally as before. StartStruct(); AddItem( SettingName, "id" ); AddItem( Prompt, "prompt" ); @@ -2426,7 +2479,26 @@ wxChoice * ShuttleGuiGetDefinition::TieChoice( EndArray(); EndField(); EndStruct(); - return ShuttleGui::TieChoice( Prompt, SettingName, Default, Choices, TranslatedChoices ); + return ShuttleGui::TieChoice( Prompt, SettingName, Default, Choices, InternalChoices ); +} +wxChoice * ShuttleGuiGetDefinition::TieNumberAsChoice( + const wxString &Prompt, + const wxString &SettingName, + const int Default, + const wxArrayString & Choices, + const std::vector & InternalChoices) +{ + // Come here for controls that present non-exhaustive choices among some + // numbers, with an associated control that allows arbitrary entry of an + // "Other..." + StartStruct(); + AddItem( SettingName, "id" ); + AddItem( Prompt, "prompt" ); + AddItem( "number", "type" ); // not "enum" ! + AddItem( Default, "default" ); + EndStruct(); + return ShuttleGui::TieNumberAsChoice( + Prompt, SettingName, Default, Choices, InternalChoices ); } wxTextCtrl * ShuttleGuiGetDefinition::TieTextBox( const wxString &Prompt, diff --git a/src/ShuttleGui.h b/src/ShuttleGui.h index bdd96c7e0..a123480da 100644 --- a/src/ShuttleGui.h +++ b/src/ShuttleGui.h @@ -27,6 +27,9 @@ // For ShuttleGuiGetDefinitions. #include "commands/CommandTargets.h" +class EnumSetting; + + const int nMaxNestedSizers = 20; enum teShuttleMode @@ -209,18 +212,42 @@ public: const wxString &Prompt, const wxString &SettingName, const bool bDefault); + + // This one is defined in terms of the next and not virtual + virtual wxChoice *TieChoice( + const wxString &Prompt, + EnumSetting &enumSetting ); + virtual wxChoice * TieChoice( const wxString &Prompt, const wxString &SettingName, const wxString &Default, const wxArrayString &Choices, - const wxArrayString & TranslatedChoices ); + const wxArrayString & InternalChoices ); + + // This overload of TieChoice should no longer be used in Preferences! + // Some uses do remain in export settings dialogs. virtual wxChoice * TieChoice( const wxString &Prompt, const wxString &SettingName, const int Default, const wxArrayString & Choices, - const std::vector & TranslatedChoices); + const std::vector & InternalChoices ); + + // This overload presents what is really a numerical setting as a choice among + // commonly used values, but the choice is not exhaustive because there is + // also an associated control that allows entry of a user-specified value + // that is arbitrary (within some bounds). + // This behaves just like the previous for building dialogs, but the + // behavior is different when the call is intercepted for purposes of + // emitting scripting information about Preferences. + virtual wxChoice * TieNumberAsChoice( + const wxString &Prompt, + const wxString &SettingName, + const int Default, + const wxArrayString & Choices, + const std::vector & InternalChoices ); + virtual wxTextCtrl * TieTextBox( const wxString &Prompt, const wxString &SettingName, @@ -421,13 +448,23 @@ public: const wxString &SettingName, const wxString &Default, const wxArrayString &Choices, - const wxArrayString & TranslatedChoices ) override; + const wxArrayString & InternalChoices ) override; + + // An assertion will be violated if this override is reached! wxChoice * TieChoice( const wxString &Prompt, const wxString &SettingName, const int Default, const wxArrayString & Choices, - const std::vector & TranslatedChoices) override; + const std::vector & InternalChoices) override; + + wxChoice * TieNumberAsChoice( + const wxString &Prompt, + const wxString &SettingName, + const int Default, + const wxArrayString & Choices, + const std::vector & InternalChoices) override; + wxTextCtrl * TieTextBox( const wxString &Prompt, const wxString &SettingName, diff --git a/src/TrackArtist.cpp b/src/TrackArtist.cpp index cab96d37f..72198d1b4 100644 --- a/src/TrackArtist.cpp +++ b/src/TrackArtist.cpp @@ -176,6 +176,7 @@ audio tracks. #include "Prefs.h" #include "prefs/GUISettings.h" #include "prefs/SpectrogramSettings.h" +#include "prefs/TracksPrefs.h" #include "prefs/WaveformSettings.h" #include "Spectrum.h" #include "ViewInfo.h" @@ -3311,7 +3312,7 @@ void TrackArtist::UpdatePrefs() { mdBrange = gPrefs->Read(ENV_DB_KEY, mdBrange); mShowClipping = gPrefs->Read(wxT("/GUI/ShowClipping"), mShowClipping); - gPrefs->Read(wxT("/GUI/SampleView"), &mSampleDisplay, 1); + mSampleDisplay = TracksPrefs::SampleViewChoice(); SetColours(0); } diff --git a/src/TrackArtist.h b/src/TrackArtist.h index d3072090d..04c509a23 100644 --- a/src/TrackArtist.h +++ b/src/TrackArtist.h @@ -179,7 +179,7 @@ class AUDACITY_DLL_API TrackArtist { // Preference values float mdBrange; // "/GUI/EnvdBRange" long mShowClipping; // "/GUI/ShowClipping" - int mSampleDisplay; // "/GUI/SampleView" + int mSampleDisplay; bool mbShowTrackNameInWaveform; // "/GUI/ShowTrackNameInWaveform" int mMarginLeft; diff --git a/src/WaveTrack.cpp b/src/WaveTrack.cpp index 8fe26772f..e6baff06e 100644 --- a/src/WaveTrack.cpp +++ b/src/WaveTrack.cpp @@ -96,7 +96,7 @@ WaveTrack::WaveTrack(const std::shared_ptr &projDirManager, sampleFo // Force creation always: WaveformSettings &settings = GetIndependentWaveformSettings(); - mDisplay = FindDefaultViewMode(); + mDisplay = TracksPrefs::ViewModeChoice(); if (mDisplay == obsoleteWaveformDBDisplay) { mDisplay = Waveform; settings.scaleType = WaveformSettings::stLogarithmic; @@ -254,32 +254,6 @@ void WaveTrack::SetPanFromChannelType() }; -//static -WaveTrack::WaveTrackDisplay WaveTrack::FindDefaultViewMode() -{ - // PRL: Bugs 1043, 1044 - // 2.1.1 writes a NEW key for this preference, which got NEW values, - // to avoid confusing version 2.1.0 if it reads the preference file afterwards. - // Prefer the NEW preference key if it is present - - WaveTrack::WaveTrackDisplay viewMode; - gPrefs->Read(wxT("/GUI/DefaultViewModeNew"), &viewMode, -1); - - // Default to the old key only if not, default the value if it's not there either - wxASSERT(WaveTrack::MinDisplay >= 0); - if (viewMode < 0) { - int oldMode; - gPrefs->Read(wxT("/GUI/DefaultViewMode"), &oldMode, - (int)(WaveTrack::Waveform)); - viewMode = WaveTrack::ConvertLegacyDisplayValue(oldMode); - } - - // Now future-proof 2.1.1 against a recurrence of this sort of bug! - viewMode = WaveTrack::ValidateWaveTrackDisplay(viewMode); - - return viewMode; -} - // static WaveTrack::WaveTrackDisplay WaveTrack::ConvertLegacyDisplayValue(int oldValue) diff --git a/src/WaveTrack.h b/src/WaveTrack.h index a2c93c2d4..63bf2bedb 100644 --- a/src/WaveTrack.h +++ b/src/WaveTrack.h @@ -578,9 +578,6 @@ private: kMaxZoom, }; - // Read appropriate value from preferences - static WaveTrackDisplay FindDefaultViewMode(); - // Handle remapping of enum values from 2.1.0 and earlier static WaveTrackDisplay ConvertLegacyDisplayValue(int oldValue); diff --git a/src/import/ImportFLAC.cpp b/src/import/ImportFLAC.cpp index a65f3af58..20e1f2ffa 100644 --- a/src/import/ImportFLAC.cpp +++ b/src/import/ImportFLAC.cpp @@ -42,6 +42,7 @@ #include "ImportPlugin.h" #include "../Tags.h" +#include "../prefs/QualityPrefs.h" #include "../Experimental.h" @@ -350,8 +351,7 @@ FLACImportFileHandle::FLACImportFileHandle(const wxString & name) mStreamInfoDone(false), mUpdateResult(ProgressResult::Success) { - mFormat = (sampleFormat) - gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample); + mFormat = QualityPrefs::SampleFormatChoice(); mFile = std::make_unique(this); } diff --git a/src/import/ImportMP3.cpp b/src/import/ImportMP3.cpp index 7eb2ae402..42bca3f08 100644 --- a/src/import/ImportMP3.cpp +++ b/src/import/ImportMP3.cpp @@ -45,6 +45,7 @@ #include "ImportPlugin.h" #include "../Internat.h" #include "../Tags.h" +#include "../prefs/QualityPrefs.h" #define DESC _("MP3 files") @@ -498,8 +499,7 @@ enum mad_flow output_cb(void *_data, if(data->channels.empty()) { data->channels.resize(channels); - sampleFormat format = (sampleFormat) gPrefs-> - Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample); + auto format = QualityPrefs::SampleFormatChoice(); for(auto &channel: data->channels) { channel = data->trackFactory->NewWaveTrack(format, samplerate); diff --git a/src/import/ImportOGG.cpp b/src/import/ImportOGG.cpp index a9bc28130..587b1c92f 100644 --- a/src/import/ImportOGG.cpp +++ b/src/import/ImportOGG.cpp @@ -42,6 +42,7 @@ #include "../Prefs.h" #include "../Internat.h" #include "../Tags.h" +#include "../prefs/QualityPrefs.h" #define DESC _("Ogg Vorbis files") @@ -108,8 +109,7 @@ public: mVorbisFile(std::move(vorbisFile)) , mStreamUsage{ static_cast(mVorbisFile->links) } { - mFormat = (sampleFormat) - gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample); + mFormat = QualityPrefs::SampleFormatChoice(); for (int i = 0; i < mVorbisFile->links; i++) { diff --git a/src/import/ImportPCM.cpp b/src/import/ImportPCM.cpp index 5af23a99f..bacec2f36 100644 --- a/src/import/ImportPCM.cpp +++ b/src/import/ImportPCM.cpp @@ -38,6 +38,7 @@ #include "../ondemand/ODManager.h" #include "../ondemand/ODComputeSummaryTask.h" +#include "../prefs/QualityPrefs.h" //If OD is enabled, he minimum number of samples a file has to use it. //Otherwise, we use the older PCMAliasBlockFile method since it should be fast enough. @@ -204,8 +205,7 @@ PCMImportFileHandle::PCMImportFileHandle(wxString name, // the quality of the original file. // - mFormat = (sampleFormat) - gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample); + mFormat = QualityPrefs::SampleFormatChoice(); if (mFormat != floatSample && sf_subtype_more_than_16_bits(mInfo.format)) diff --git a/src/import/ImportRaw.cpp b/src/import/ImportRaw.cpp index bad27c063..0981d10d9 100644 --- a/src/import/ImportRaw.cpp +++ b/src/import/ImportRaw.cpp @@ -34,6 +34,7 @@ and sample size to help you importing data of an unknown format. #include "../ShuttleGui.h" #include "../UserException.h" #include "../WaveTrack.h" +#include "../prefs/QualityPrefs.h" #include #include @@ -193,8 +194,7 @@ void ImportRaw(wxWindow *parent, const wxString &fileName, // the quality of the original file. // - format = (sampleFormat) - gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample); + format = QualityPrefs::SampleFormatChoice(); if (format != floatSample && sf_subtype_more_than_16_bits(encoding)) diff --git a/src/ondemand/ODDecodeFlacTask.cpp b/src/ondemand/ODDecodeFlacTask.cpp index a6cade170..d3362d60e 100644 --- a/src/ondemand/ODDecodeFlacTask.cpp +++ b/src/ondemand/ODDecodeFlacTask.cpp @@ -225,8 +225,7 @@ bool ODFlacDecoder::ReadHeader() { mFormat = int16Sample;//start with the smallest and move up in the metadata_callback. //we want to use the native flac type for quick conversion. - /* (sampleFormat) - gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample);*/ + /* QualityPrefs::SampleFormatChoice(); */ mFile = std::make_unique(this); diff --git a/src/prefs/QualityPrefs.cpp b/src/prefs/QualityPrefs.cpp index 9b1ebd927..97622523f 100644 --- a/src/prefs/QualityPrefs.cpp +++ b/src/prefs/QualityPrefs.cpp @@ -30,6 +30,70 @@ #define ID_SAMPLE_RATE_CHOICE 7001 +////////// + +static const IdentInterfaceSymbol choicesFormat[] = { + { wxT("Format16Bit"), XO("16-bit") }, + { wxT("Format24Bit"), XO("24-bit") }, + { wxT("Format32BitFloat"), XO("32-bit float") } +}; +static const size_t nChoicesFormat = WXSIZEOF( choicesFormat ); +static const int intChoicesFormat[] = { + int16Sample, + int24Sample, + floatSample +}; +static_assert( nChoicesFormat == WXSIZEOF(intChoicesFormat), "size mismatch" ); + +static const size_t defaultChoiceFormat = 2; // floatSample + +static EncodedEnumSetting formatSetting{ + wxT("/SamplingRate/DefaultProjectSampleFormatChoice"), + choicesFormat, nChoicesFormat, defaultChoiceFormat, + + intChoicesFormat, + wxT("/SamplingRate/DefaultProjectSampleFormat"), +}; + +////////// +static const IdentInterfaceSymbol choicesDither[] = { + { XO("None") }, + { XO("Rectangle") }, + { XO("Triangle") }, + { XO("Shaped") }, +}; +static const size_t nChoicesDither = WXSIZEOF( choicesDither ); +static const int intChoicesDither[] = { + (int) DitherType::none, + (int) DitherType::rectangle, + (int) DitherType::triangle, + (int) DitherType::shaped, +}; +static_assert( + nChoicesDither == WXSIZEOF( intChoicesDither ), + "size mismatch" +); + +static const int defaultFastDither = 0; // none + +static EncodedEnumSetting fastDitherSetting{ + wxT("Quality/DitherAlgorithmChoice"), + choicesDither, nChoicesDither, defaultFastDither, + intChoicesDither, + wxT("Quality/DitherAlgorithm") +}; + +static const int defaultBestDither = 3; // shaped + +static EncodedEnumSetting bestDitherSetting{ + wxT("Quality/HQDitherAlgorithmChoice"), + choicesDither, nChoicesDither, defaultBestDither, + + intChoicesDither, + wxT("Quality/HQDitherAlgorithm") +}; + +////////// BEGIN_EVENT_TABLE(QualityPrefs, PrefsPanel) EVT_CHOICE(ID_SAMPLE_RATE_CHOICE, QualityPrefs::OnSampleRateChoice) END_EVENT_TABLE() @@ -71,12 +135,6 @@ void QualityPrefs::Populate() /// The corresponding labels are what gets stored. void QualityPrefs::GetNamesAndLabels() { - //------------ Dither Names - mDitherNames.Add(_("None")); mDitherLabels.push_back(Dither::none); - mDitherNames.Add(_("Rectangle")); mDitherLabels.push_back(Dither::rectangle); - mDitherNames.Add(_("Triangle")); mDitherLabels.push_back(Dither::triangle); - mDitherNames.Add(_("Shaped")); mDitherLabels.push_back(Dither::shaped); - //------------ Sample Rate Names // JKC: I don't understand the following comment. // Can someone please explain or correct it? @@ -101,18 +159,6 @@ void QualityPrefs::GetNamesAndLabels() // The label for the 'Other...' case can be any value at all. mSampleRateLabels.push_back(44100); // If chosen, this value will be overwritten - - //------------- Sample Format Names - mSampleFormatNames.Add(wxT("16-bit")); mSampleFormatLabels.push_back(int16Sample); - mSampleFormatNames.Add(wxT("24-bit")); mSampleFormatLabels.push_back(int24Sample); - mSampleFormatNames.Add(wxT("32-bit float")); mSampleFormatLabels.push_back(floatSample); - - //------------- Converter Names - int numConverters = Resample::GetNumMethods(); - for (int i = 0; i < numConverters; i++) { - mConverterNames.Add(Resample::GetMethodName(i)); - mConverterLabels.push_back(i); - } } void QualityPrefs::PopulateOrExchange(ShuttleGui & S) @@ -135,7 +181,7 @@ void QualityPrefs::PopulateOrExchange(ShuttleGui & S) // We make sure it uses the ID we want, so that we get changes S.Id(ID_SAMPLE_RATE_CHOICE); // We make sure we have a pointer to it, so that we can drive it. - mSampleRates = S.TieChoice( {}, + mSampleRates = S.TieNumberAsChoice( {}, wxT("/SamplingRate/DefaultProjectSampleRate"), AudioIO::GetOptimalSupportedSampleRate(), mSampleRateNames, @@ -149,10 +195,7 @@ void QualityPrefs::PopulateOrExchange(ShuttleGui & S) S.EndHorizontalLay(); S.TieChoice(_("Default Sample &Format:"), - wxT("/SamplingRate/DefaultProjectSampleFormat"), - floatSample, - mSampleFormatNames, - mSampleFormatLabels); + formatSetting); } S.EndMultiColumn(); } @@ -163,17 +206,11 @@ void QualityPrefs::PopulateOrExchange(ShuttleGui & S) S.StartMultiColumn(2, wxEXPAND); { S.TieChoice(_("Sample Rate Con&verter:"), - Resample::GetFastMethodKey(), - Resample::GetFastMethodDefault(), - mConverterNames, - mConverterLabels); + Resample::FastMethodSetting); /* i18n-hint: technical term for randomization to reduce undesirable resampling artifacts */ S.TieChoice(_("&Dither:"), - wxT("/Quality/DitherAlgorithm"), - Dither::none, - mDitherNames, - mDitherLabels); + fastDitherSetting); } S.EndMultiColumn(); } @@ -184,17 +221,11 @@ void QualityPrefs::PopulateOrExchange(ShuttleGui & S) S.StartMultiColumn(2); { S.TieChoice(_("Sample Rate Conver&ter:"), - Resample::GetBestMethodKey(), - Resample::GetBestMethodDefault(), - mConverterNames, - mConverterLabels); + Resample::BestMethodSetting); /* i18n-hint: technical term for randomization to reduce undesirable resampling artifacts */ S.TieChoice(_("Dit&her:"), - wxT("/Quality/HQDitherAlgorithm"), - Dither::shaped, - mDitherNames, - mDitherLabels); + bestDitherSetting); } S.EndMultiColumn(); } @@ -239,3 +270,19 @@ PrefsPanel *QualityPrefsFactory::operator () (wxWindow *parent, wxWindowID winid wxASSERT(parent); // to justify safenew return safenew QualityPrefs(parent, winid); } + +sampleFormat QualityPrefs::SampleFormatChoice() +{ + return (sampleFormat)formatSetting.ReadInt(); +} + +DitherType QualityPrefs::FastDitherChoice() +{ + return (DitherType) fastDitherSetting.ReadInt(); +} + +DitherType QualityPrefs::BestDitherChoice() +{ + return (DitherType) bestDitherSetting.ReadInt(); +} + diff --git a/src/prefs/QualityPrefs.h b/src/prefs/QualityPrefs.h index ecbfed018..4b8a3eff0 100644 --- a/src/prefs/QualityPrefs.h +++ b/src/prefs/QualityPrefs.h @@ -22,6 +22,8 @@ #include "PrefsPanel.h" class ShuttleGui; +enum sampleFormat : unsigned; +enum class DitherType : unsigned; class QualityPrefs final : public PrefsPanel { @@ -33,19 +35,18 @@ class QualityPrefs final : public PrefsPanel wxString HelpPageName() override; void PopulateOrExchange(ShuttleGui & S) override; + static sampleFormat SampleFormatChoice(); + + static DitherType FastDitherChoice(); + static DitherType BestDitherChoice(); + private: void Populate(); void GetNamesAndLabels(); void OnSampleRateChoice(wxCommandEvent & e); - wxArrayString mDitherNames; - std::vector mDitherLabels; wxArrayString mSampleRateNames; std::vector mSampleRateLabels; - wxArrayString mSampleFormatNames; - std::vector mSampleFormatLabels; - wxArrayString mConverterNames; - std::vector mConverterLabels; wxChoice *mSampleRates; wxTextCtrl *mOtherSampleRate; diff --git a/src/prefs/TracksPrefs.cpp b/src/prefs/TracksPrefs.cpp index f8e799517..1be8e4c45 100644 --- a/src/prefs/TracksPrefs.cpp +++ b/src/prefs/TracksPrefs.cpp @@ -45,17 +45,182 @@ namespace { } +////////// +static const IdentInterfaceSymbol choicesView[] = { + { XO("Waveform") }, + { wxT("WaveformDB"), XO("Waveform (dB)") }, + { XO("Spectrogram") } +}; +static const int intChoicesView[] = { + (int)(WaveTrack::Waveform), + (int)(WaveTrack::obsoleteWaveformDBDisplay), + (int)(WaveTrack::Spectrum) +}; +static const size_t nChoicesView = WXSIZEOF(choicesView); +static_assert( nChoicesView == WXSIZEOF(intChoicesView), "size mismatch" ); + +static const size_t defaultChoiceView = 0; + +class TracksViewModeSetting : public EncodedEnumSetting { +public: + TracksViewModeSetting( + const wxString &key, + const IdentInterfaceSymbol symbols[], size_t nSymbols, + size_t defaultSymbol, + + const int intValues[], + const wxString &oldKey + ) + : EncodedEnumSetting{ + key, symbols, nSymbols, defaultSymbol, intValues, oldKey } + {} + + void Migrate( wxString &value ) override + { + // Special logic for this preference which was twice migrated! + + // First test for the older but not oldest key: + EncodedEnumSetting::Migrate(value); + if (!value.empty()) + return; + + // PRL: Bugs 1043, 1044 + // 2.1.1 writes a NEW key for this preference, which got NEW values, + // to avoid confusing version 2.1.0 if it reads the preference file afterwards. + // Prefer the NEW preference key if it is present + + int oldMode; + gPrefs->Read(wxT("/GUI/DefaultViewMode"), // The very old key + &oldMode, + (int)(WaveTrack::Waveform)); + auto viewMode = WaveTrack::ConvertLegacyDisplayValue(oldMode); + + // Now future-proof 2.1.1 against a recurrence of this sort of bug! + viewMode = WaveTrack::ValidateWaveTrackDisplay(viewMode); + + const_cast(this)->WriteInt( viewMode ); + gPrefs->Flush(); + + value = mSymbols[ FindInt(viewMode) ].Internal(); + } +}; + +static TracksViewModeSetting viewModeSetting{ + wxT("/GUI/DefaultViewModeChoice"), + choicesView, nChoicesView, defaultChoiceView, + + intChoicesView, + wxT("/GUI/DefaultViewModeNew") +}; + +WaveTrack::WaveTrackDisplay TracksPrefs::ViewModeChoice() +{ + return (WaveTrack::WaveTrackDisplay) viewModeSetting.ReadInt(); +} + +////////// +static const IdentInterfaceSymbol choicesSampleDisplay[] = { + { wxT("ConnectDots"), XO("Connect dots") }, + { wxT("StemPlot"), XO("Stem plot") } +}; +static const size_t nChoicesSampleDisplay = WXSIZEOF( choicesSampleDisplay ); +static const int intChoicesSampleDisplay[] = { + (int) WaveTrack::LinearInterpolate, + (int) WaveTrack::StemPlot +}; +static_assert( + nChoicesSampleDisplay == WXSIZEOF(intChoicesSampleDisplay), "size mismatch" ); + +static const size_t defaultChoiceSampleDisplay = 1; + +static EncodedEnumSetting sampleDisplaySetting{ + wxT("/GUI/SampleViewChoice"), + choicesSampleDisplay, nChoicesSampleDisplay, defaultChoiceSampleDisplay, + + intChoicesSampleDisplay, + wxT("/GUI/SampleView") +}; + +WaveTrack::SampleDisplay TracksPrefs::SampleViewChoice() +{ + return (WaveTrack::SampleDisplay) sampleDisplaySetting.ReadInt(); +} + +////////// +static const IdentInterfaceSymbol choicesZoom[] = { + { wxT("FitToWidth"), XO("Fit to Width") }, + { wxT("ZoomToSelection"), XO("Zoom to Selection") }, + { wxT("ZoomDefault"), XO("Zoom Default") }, + { XO("Minutes") }, + { XO("Seconds") }, + { wxT("FifthsOfSeconds"), XO("5ths of Seconds") }, + { wxT("TenthsOfSeconds"), XO("10ths of Seconds") }, + { wxT("TwentiethsOfSeconds"), XO("20ths of Seconds") }, + { wxT("FiftiethsOfSeconds"), XO("50ths of Seconds") }, + { wxT("HundredthsOfSeconds"), XO("100ths of Seconds") }, + { wxT("FiveHundredthsOfSeconds"), XO("500ths of Seconds") }, + { XO("MilliSeconds") }, + { XO("Samples") }, + { wxT("FourPixelsPerSample"), XO("4 Pixels per Sample") }, + { wxT("MaxZoom"), XO("Max Zoom") }, +}; +static const size_t nChoicesZoom = WXSIZEOF( choicesZoom ); +static const int intChoicesZoom[] = { + WaveTrack::kZoomToFit, + WaveTrack::kZoomToSelection, + WaveTrack::kZoomDefault, + WaveTrack::kZoomMinutes, + WaveTrack::kZoomSeconds, + WaveTrack::kZoom5ths, + WaveTrack::kZoom10ths, + WaveTrack::kZoom20ths, + WaveTrack::kZoom50ths, + WaveTrack::kZoom100ths, + WaveTrack::kZoom500ths, + WaveTrack::kZoomMilliSeconds, + WaveTrack::kZoomSamples, + WaveTrack::kZoom4To1, + WaveTrack::kMaxZoom, +}; +static_assert( nChoicesZoom == WXSIZEOF(intChoicesZoom), "size mismatch" ); + +static const size_t defaultChoiceZoom1 = 2; // kZoomDefault + +static EncodedEnumSetting zoom1Setting{ + wxT("/GUI/ZoomPreset1Choice"), + choicesZoom, nChoicesZoom, defaultChoiceZoom1, + + intChoicesZoom, + wxT("/GUI/ZoomPreset1") +}; + +static const size_t defaultChoiceZoom2 = 13; // kZoom4To1 + +static EncodedEnumSetting zoom2Setting{ + wxT("/GUI/ZoomPreset2Choice"), + choicesZoom, nChoicesZoom, defaultChoiceZoom2, + + intChoicesZoom, + wxT("/GUI/ZoomPreset2") +}; + +WaveTrack::ZoomPresets TracksPrefs::Zoom1Choice() +{ + return (WaveTrack::ZoomPresets) zoom1Setting.ReadInt(); +} + +WaveTrack::ZoomPresets TracksPrefs::Zoom2Choice() +{ + return (WaveTrack::ZoomPresets) zoom2Setting.ReadInt(); +} + +////////// TracksPrefs::TracksPrefs(wxWindow * parent, wxWindowID winid) /* i18n-hint: "Tracks" include audio recordings but also other collections of * data associated with a time line, such as sequences of labels, and musical * notes */ : PrefsPanel(parent, winid, _("Tracks")) { - // Bugs 1043, 1044 - // First rewrite legacy preferences - gPrefs->Write(wxT("/GUI/DefaultViewModeNew"), - (int) WaveTrack::FindDefaultViewMode()); - Populate(); } @@ -68,56 +233,9 @@ void TracksPrefs::Populate() // Keep view choices and codes in proper correspondence -- // we don't display them by increasing integer values. - mViewChoices.Add(_("Waveform")); - mViewCodes.push_back((int)(WaveTrack::Waveform)); - - mViewChoices.Add(_("Waveform (dB)")); - mViewCodes.push_back((int)(WaveTrack::obsoleteWaveformDBDisplay)); - - mViewChoices.Add(_("Spectrogram")); - mViewCodes.push_back(WaveTrack::Spectrum); - // How samples are displayed when zoomed in: - mSampleDisplayChoices.Add(_("Connect dots")); - mSampleDisplayCodes.push_back((int) WaveTrack::LinearInterpolate); - - mSampleDisplayChoices.Add(_("Stem plot")); - mSampleDisplayCodes.push_back((int) WaveTrack::StemPlot); - - mZoomChoices.Add( _("Fit to Width") ); - mZoomCodes.push_back( WaveTrack::kZoomToFit ); - mZoomChoices.Add( _("Zoom to Selection") ); - mZoomCodes.push_back( WaveTrack::kZoomToSelection ); - mZoomChoices.Add( _("Zoom Default") ); - mZoomCodes.push_back( WaveTrack::kZoomDefault ); - mZoomChoices.Add( _("Minutes") ); - mZoomCodes.push_back( WaveTrack::kZoomMinutes ); - mZoomChoices.Add( _("Seconds") ); - mZoomCodes.push_back( WaveTrack::kZoomSeconds ); - mZoomChoices.Add( _("5ths of Seconds") ); - mZoomCodes.push_back( WaveTrack::kZoom5ths ); - mZoomChoices.Add( _("10ths of Seconds") ); - mZoomCodes.push_back( WaveTrack::kZoom10ths ); - mZoomChoices.Add( _("20ths of Seconds") ); - mZoomCodes.push_back( WaveTrack::kZoom20ths ); - mZoomChoices.Add( _("50ths of Seconds") ); - mZoomCodes.push_back( WaveTrack::kZoom50ths ); - mZoomChoices.Add( _("100ths of Seconds") ); - mZoomCodes.push_back( WaveTrack::kZoom100ths ); - mZoomChoices.Add( _("500ths of Seconds") ); - mZoomCodes.push_back( WaveTrack::kZoom500ths ); - mZoomChoices.Add( _("MilliSeconds") ); - mZoomCodes.push_back( WaveTrack::kZoomMilliSeconds ); - mZoomChoices.Add( _("Samples") ); - mZoomCodes.push_back( WaveTrack::kZoomSamples ); - mZoomChoices.Add( _("4 Pixels per Sample") ); - mZoomCodes.push_back( WaveTrack::kZoom4To1 ); - mZoomChoices.Add( _("Max Zoom") ); - mZoomCodes.push_back( WaveTrack::kMaxZoom ); - - //------------------------- Main section -------------------- // Now construct the GUI itself. @@ -153,16 +271,10 @@ void TracksPrefs::PopulateOrExchange(ShuttleGui & S) S.StartMultiColumn(2); { S.TieChoice(_("Default &view mode:"), - wxT("/GUI/DefaultViewModeNew"), - 0, - mViewChoices, - mViewCodes); + viewModeSetting ); S.TieChoice(_("Display &samples:"), - wxT("/GUI/SampleView"), - 1, - mSampleDisplayChoices, - mSampleDisplayCodes); + sampleDisplaySetting ); S.TieTextBox(_("Default audio track &name:"), wxT("/GUI/TrackNames/DefaultTrackName"), @@ -178,16 +290,10 @@ void TracksPrefs::PopulateOrExchange(ShuttleGui & S) S.StartMultiColumn(4); { S.TieChoice(_("Preset 1:"), - wxT("/GUI/ZoomPreset1"), - WaveTrack::kZoomDefault, - mZoomChoices, - mZoomCodes); + zoom1Setting ); S.TieChoice(_("Preset 2:"), - wxT("/GUI/ZoomPreset2"), - WaveTrack::kZoom4To1, - mZoomChoices, - mZoomCodes); + zoom2Setting ); } } S.EndStatic(); diff --git a/src/prefs/TracksPrefs.h b/src/prefs/TracksPrefs.h index 9282558aa..026da949c 100644 --- a/src/prefs/TracksPrefs.h +++ b/src/prefs/TracksPrefs.h @@ -20,6 +20,7 @@ #include #include "PrefsPanel.h" +#include "../WaveTrack.h" class ShuttleGui; @@ -36,18 +37,16 @@ class TracksPrefs final : public PrefsPanel static wxString GetDefaultAudioTrackNamePreference(); + static WaveTrack::WaveTrackDisplay ViewModeChoice(); + static WaveTrack::SampleDisplay SampleViewChoice(); + static WaveTrack::ZoomPresets Zoom1Choice(); + static WaveTrack::ZoomPresets Zoom2Choice(); + private: void Populate(); void PopulateOrExchange(ShuttleGui & S) override; static int iPreferencePinned; - - std::vector mViewCodes; - wxArrayString mViewChoices; - std::vector mSampleDisplayCodes; - wxArrayString mSampleDisplayChoices; - std::vector mZoomCodes; - wxArrayString mZoomChoices; }; class TracksPrefsFactory final : public PrefsPanelFactory