Audacity 3.2.0
SpectralDataDialog.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 SpectralDataDialog.cpp
6
7 Edward Hui
8
9*******************************************************************//*******************************************************************/
15
16#ifdef EXPERIMENTAL_BRUSH_TOOL
17
18#include <type_traits>
19
20#include <wx/app.h>
21#include <wx/defs.h>
22#include <wx/button.h>
23#include <wx/checkbox.h>
24#include <wx/dialog.h>
25#include <wx/event.h>
26#include <wx/frame.h>
27#include <wx/imaglist.h>
28#include <wx/intl.h>
29#include <wx/listctrl.h>
30#include <wx/settings.h>
31#include <wx/spinctrl.h>
32#include <wx/statline.h>
33#include <wx/stattext.h>
34#include <wx/textctrl.h>
35#include <wx/tglbtn.h>
36
37#include "../images/Arrow.xpm"
38#include "../images/Empty9x16.xpm"
39
40#include "AllThemeResources.h"
41#include "AudioIO.h"
42#include "ClientData.h"
43#include "Clipboard.h"
46#include "Menus.h"
47#include "UndoManager.h"
48#include "Prefs.h"
49#include "Project.h"
50#include "ProjectFileIO.h"
51#include "ProjectHistory.h"
52#include "ProjectSettings.h"
53#include "ProjectWindows.h"
54#include "ShuttleGui.h"
55#include "SpectralDataManager.h"
57#include "Theme.h"
58#include "TrackPanel.h"
59#include "WaveTrack.h"
60
63#include "widgets/wxPanelWrapper.h" // to inherit
64
65// If defined, make a checkbox for smart selection of the fundamental
66// independently of overtones
67#undef SMART_CHECKBOX
68
69enum {
70 ID_BRUSH_BUTTON = 10000,
71#ifdef SMART_CHECKBOX
72 ID_CHECKBOX_SMART,
73#endif
74 ID_CHECKBOX_OVERTONES,
75 ID_SLIDER_BRUSH_SIZE
76};
77
78class wxButton;
79class wxCheckBox;
80class wxListCtrl;
81class wxListEvent;
82class wxSpinCtrl;
83class wxTextCtrl;
84class AudacityProject;
85class ShuttleGui;
86class UndoManager;
87
88class SpectralDataDialog final : public wxDialogWrapper,
89 public PrefsListener,
90 public ClientData::Base,
92{
93
94 public:
95 static SpectralDataDialog &Get(AudacityProject &project);
96 static SpectralDataDialog *Find(AudacityProject *pProject);
97
98 explicit SpectralDataDialog(AudacityProject &parent);
99
100 void UpdateDisplayForClipboard(ClipboardChangeMessage);
101 void UpdateDisplay(UndoRedoMessage);
102 void DoUpdateDisplay();
103 void UpdateControls( bool active );
104
105 bool Show( bool show = true ) override;
106
107 private:
108 void Populate(ShuttleGui & S);
109
110 void OnAudioIO(AudioIOEvent ev);
111 void DoUpdate();
112
113 void OnCloseWindow(wxCloseEvent &event);
114 void OnApply(wxCommandEvent &event);
115 void OnBrushButton(wxCommandEvent &event);
116 void OnBrushSizeSlider(wxCommandEvent &event);
117 void OnCheckSmartSelection(wxCommandEvent &event);
118 void OnCheckOvertones(wxCommandEvent &event);
119
120 // PrefsListener implementation
121 void UpdatePrefs() override;
122
123 Observer::Subscription mAudioIOSubscription
124 , mUndoSubscription
125 , mClipboardSubscription
126 ;
127 AudacityProject &mProject;
128 wxToggleButton *mBrushButton = nullptr;
129 bool mAudioIOBusy { false };
130
131 public:
132 void DoToolChanged();
133
134 DECLARE_EVENT_TABLE()
135};
136
137class AUDACITY_DLL_API SpectralDataDialogWorker final
138 : public ClientData::Base{
139public:
140 explicit SpectralDataDialogWorker( AudacityProject &project );
141 ~SpectralDataDialogWorker();
142
143 void OnToolChanged(wxCommandEvent &evt);
144 void OnIdle(wxIdleEvent &evt);
145private:
146 AudacityProject &mProject;
147 unsigned mPrevNViews = 0;
148};
149
150BEGIN_EVENT_TABLE(SpectralDataDialog, wxDialogWrapper)
151 EVT_TOGGLEBUTTON(ID_BRUSH_BUTTON, SpectralDataDialog::OnBrushButton)
152 #ifdef SMART_CHECKBOX
153 EVT_CHECKBOX(ID_CHECKBOX_SMART, SpectralDataDialog::OnCheckSmartSelection)
154#endif
155 EVT_CHECKBOX(ID_CHECKBOX_OVERTONES, SpectralDataDialog::OnCheckOvertones)
157 EVT_SLIDER(ID_SLIDER_BRUSH_SIZE, SpectralDataDialog::OnBrushSizeSlider)
159
160#define Title XO("Spectral Data Control Panel")
161
162SpectralDataDialog::SpectralDataDialog(AudacityProject &parent)
163 : mProject(parent)
164 , wxDialogWrapper(FindProjectFrame( &parent ), wxID_ANY, Title,
165 wxDefaultPosition, wxDefaultSize,
166 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER )
167
168{
169 SetName();
170 //------------------------- Main section --------------------
171 // Construct the GUI.
172 ShuttleGui S(this, eIsCreating);
173 Populate(S);
174 CentreOnParent();
175
176 mAudioIOSubscription = AudioIO::Get()
177 ->Subscribe(*this, &SpectralDataDialog::OnAudioIO);
178
179 mClipboardSubscription = Clipboard::Get()
180 .Subscribe(*this, &::SpectralDataDialog::UpdateDisplayForClipboard);
181
182 mUndoSubscription = UndoManager::Get(parent)
183 .Subscribe(*this, &SpectralDataDialog::UpdateDisplay);
184
185 DoToolChanged();
186}
187
188static const AttachedWindows::RegisteredFactory key{
189 []( AudacityProject &project ){
190 return safenew SpectralDataDialog( project );
191 }
192};
193
194static wxToggleButton *MakeButton(wxWindow *pParent)
195{
196 auto button = safenew wxBitmapToggleButton{
197 pParent, ID_BRUSH_BUTTON, theTheme.Bitmap(bmpSpectralBrush) };
198 // Name isn't shown but may be pronounced by a screen reader
199 button->SetName(XO("Brush Tool").Translation());
200 return button;
201}
202
203void SpectralDataDialog::Populate(ShuttleGui & S)
204{
205 mBrushButton = MakeButton(this);
206
207 S.StartVerticalLay(true);
208 S.SetBorder(10);
209 {
210 S.AddVariableText(XO("Spectral Brush"));
211 S.AddWindow(mBrushButton, wxALIGN_LEFT);
212
213 S.AddVariableText(XO("Brush radius"));
214 S.Id(ID_SLIDER_BRUSH_SIZE)
215 .Style(wxSL_HORIZONTAL)
216 .Name(XO("Custom brush size"))
217 .AddSlider( {}, 5, 10, 1);
218
219 S.AddWindow(safenew wxStaticLine{ S.GetParent() });
220
221 S.Id(ID_CHECKBOX_OVERTONES)
222 .AddCheckBox(
223 XXO("Auto-select overtones (beta)"),
224 false);
225
226#ifdef SMART_CHECKBOX
227 S.Id(ID_CHECKBOX_SMART)
228 .AddCheckBox(XXO("Enable smart selection"), false);
229#endif
230
231 S.AddVariableText(
232 XO("Select the fundamental frequency\n"
233 "and release the mouse"));
234 }
235 S.EndVerticalLay();
236 // ----------------------- End of main section --------------
237
238 Layout();
239 Fit();
240 SetMinSize(GetSize());
241}
242
243void SpectralDataDialog::OnAudioIO(AudioIOEvent ev)
244{
245 if (ev.type != AudioIOEvent::MONITOR)
246 mAudioIOBusy = ev.on;
247}
248
249void SpectralDataDialog::UpdateDisplayForClipboard(ClipboardChangeMessage)
250{
251 DoUpdateDisplay();
252}
253
254void SpectralDataDialog::UpdateDisplay(UndoRedoMessage message)
255{
256 switch (message.type) {
262 break;
263 default:
264 return;
265 }
266 DoUpdateDisplay();
267}
268
269void SpectralDataDialog::DoUpdateDisplay()
270{
271 if(IsShown())
272 DoUpdate();
273}
274
275void SpectralDataDialog::UpdateControls( bool active )
276{
277 if (mBrushButton)
278 mBrushButton->SetValue(active);
279}
280
281static bool IsBrushToolActive(AudacityProject &project)
282{
283 return ProjectSettings::Get(project).GetTool() == ToolCodes::brushTool;
284}
285
286bool SpectralDataDialog::Show( bool show )
287{
288 if ( show && !IsShown())
289 DoUpdate();
290 if ( IsShown() && !show && IsBrushToolActive(mProject) )
292 auto result = wxDialogWrapper::Show( show );
293 CommandManager::Get( mProject ).UpdateCheckmarks( mProject );
294 return result;
295}
296
297void SpectralDataDialog::DoUpdate()
298{
299}
300
301void SpectralDataDialog::OnCloseWindow(wxCloseEvent & WXUNUSED(event))
302{
303 this->Show(false);
304}
305
306// PrefsListener implementation
308{
309 bool shown = IsShown();
310 if (shown) {
311 Show(false);
312 }
313
314 SetSizer(nullptr);
315 DestroyChildren();
316
317 SetTitle(Title);
318 ShuttleGui S(this, eIsCreating);
319 Populate(S);
320 DoToolChanged();
321
322 if (shown) {
323 Show(true);
324 }
325}
326
327void SpectralDataDialog::OnBrushButton(wxCommandEvent &event) {
328 if (mBrushButton->GetValue())
329 ProjectSettings::Get(mProject).SetTool(ToolCodes::brushTool);
330 else
331 // Don't stay up
332 mBrushButton->SetValue(true);
333}
334
335static const AudacityProject::AttachedObjects::RegisteredFactory sSpectralWorkerKey{
336 []( AudacityProject &project ){
337 return std::make_shared< SpectralDataDialogWorker >( project );
338 }
339};
340
341AttachedWindows::RegisteredFactory sSpectralDataDialogKey{
342 []( AudacityProject &parent ) -> wxWeakRef< wxWindow > {
343 return safenew SpectralDataDialog( parent );
344 }
345};
346
348{
349 // Ensure existence of the dialog
350 return static_cast<SpectralDataDialog&>(
351 GetAttachedWindows(project).Get( sSpectralDataDialogKey ) );
352}
353
355{
356 // Return a pointer to the dialog only if it exists
357 return pProject
358 ? static_cast<SpectralDataDialog*>(
359 GetAttachedWindows(*pProject).Find( sSpectralDataDialogKey ) )
360 : nullptr;
361}
362
363// Current workflow for spectral editing dialog:
364// 1. ProjectSettings change event listened by Worker::OnToolChanged()
365// 2. In Worker::OnToolChanged(), get Dialog from AttachedWindows and invoke Show()
366// 3. Dialog::OnApply() listens to the apply button in Dialog, which calculates and applies the effect
367SpectralDataDialogWorker::SpectralDataDialogWorker(AudacityProject &project)
368 : mProject{ project }
369{
370 project.Bind(EVT_PROJECT_SETTINGS_CHANGE, &SpectralDataDialogWorker::OnToolChanged, this);
371 wxTheApp->Bind(wxEVT_IDLE, &SpectralDataDialogWorker::OnIdle, this);
372}
373
374SpectralDataDialogWorker::~SpectralDataDialogWorker()
375{
376 wxTheApp->Unbind(wxEVT_IDLE, &SpectralDataDialogWorker::OnIdle, this);
377}
378
379void SpectralDataDialogWorker::OnToolChanged(wxCommandEvent &evt)
380{
381 evt.Skip();
382 if (evt.GetInt() == ProjectSettings::ChangedTool) {
383 // Find not Get to avoid creating the dialog if not yet done
384 if (auto pDialog = SpectralDataDialog::Find(&mProject);
385 pDialog && pDialog->IsShown())
386 pDialog->DoToolChanged();
387 else {
388 // Dialog is hidden, or not yet constructed
389 using Type = std::underlying_type_t<decltype(ToolCodes::brushTool)>;
390 constexpr auto value = static_cast<Type>(ToolCodes::brushTool);
391
392 auto &projectSettings = ProjectSettings::Get( mProject );
393 if (projectSettings.GetTool() == value) {
394 auto oldValue = static_cast<Type>( evt.GetExtraLong() );
395 if (oldValue + 1 == value)
396 // continue tool rotation
397 wxTheApp->CallAfter([&]{ projectSettings.SetTool(
398 (value + 1) % ToolCodes::numTools); });
399 else if ((oldValue + ToolCodes::numTools - 1 ) % ToolCodes::numTools
400 == value)
401 // continue backwards tool rotation
402 wxTheApp->CallAfter([&]{ projectSettings.SetTool(
403 (value + ToolCodes::numTools - 1 ) % ToolCodes::numTools); });
404 else
405 // restore old tool value
406 wxTheApp->CallAfter([&]{ projectSettings.SetTool(oldValue); });
407 }
408 }
409 }
410}
411
412static bool HasVisibleSpectralView(WaveTrack *wt)
413{
414 auto &trackView = TrackView::Get(*wt);
415 if ( auto waveTrackViewPtr = dynamic_cast<WaveTrackView*>(&trackView) ) {
416 const auto range = waveTrackViewPtr->GetSubViews();
417 return std::any_of( range.begin(), range.end(),
418 [](const auto &pair){
419 return dynamic_cast<SpectrumView*>(pair.second.get()); } );
420 }
421 return false;
422}
423
424static unsigned CountVisibleSpectralViews( AudacityProject &project )
425{
426 const auto range = TrackList::Get(project).Any< WaveTrack >();
427 return std::count_if( range.begin(), range.end(), HasVisibleSpectralView );
428}
429
430void SpectralDataDialogWorker::OnIdle(wxIdleEvent &evt)
431{
432 evt.Skip();
433 auto nViews = CountVisibleSpectralViews(mProject);
434 if (nViews > mPrevNViews) {
435 // Some track transitioned its view to spectral or multi.
436 // Show the dialog.
437 auto &dialog = SpectralDataDialog::Get(mProject);
438 dialog.Show();
439 dialog.Raise();
440 }
441 else if (nViews == 0 && mPrevNViews > 0) {
442 // The last spectrum view was closed.
443 // Hide the dialog.
444 if (auto pDialog = SpectralDataDialog::Find(&mProject))
445 pDialog->Hide();
446 }
447 mPrevNViews = nViews;
448}
449
450void SpectralDataDialog::DoToolChanged()
451{
452 UpdateControls( IsBrushToolActive(mProject) );
453}
454
455void SpectralDataDialog::OnBrushSizeSlider(wxCommandEvent &event) {
456 auto &projectSettings = ProjectSettings::Get( mProject );
457 projectSettings.SetBrushRadius(event.GetInt());
458}
459
460void SpectralDataDialog::OnCheckSmartSelection(wxCommandEvent &event){
461 int isSelected = event.GetInt();
462 wxASSERT(isSelected == 0 || isSelected == 1);
463 auto &projectSettings = ProjectSettings::Get( mProject );
464 projectSettings.SetSmartSelection(isSelected);
465}
466
467void SpectralDataDialog::OnCheckOvertones(wxCommandEvent &event){
468 int isSelected = event.GetInt();
469 wxASSERT(isSelected == 0 || isSelected == 1);
470 auto &projectSettings = ProjectSettings::Get( mProject );
471 projectSettings.SetOvertones(isSelected);
472#ifndef SMART_CHECKBOX
473 // One checkbox controls both things
474 OnCheckSmartSelection(event);
475#endif
476}
477
478namespace {
479void OnSpectralEditingPanel(const CommandContext &context)
480{
481 auto &project = context.project;
482 auto &dialog = SpectralDataDialog::Get(project);
483 dialog.Show( !dialog.IsShown() );
484}
485
486using namespace MenuTable;
488 wxT("View/Other/Toolbars/Toolbars/Other"),
489 Command( wxT("ShowSpectralSelectionPanel"),
490 XXO("Spectra&l Selection Panel"),
491 OnSpectralEditingPanel,
494 .CheckTest( [](AudacityProject &project) {
495 // Find not Get to avoid creating the dialog if not yet done
496 auto pDialog = SpectralDataDialog::Find(&project);
497 return pDialog && pDialog->IsShown();
498 } ) )
499};
500
501}
502
503#endif
wxT("CloseDown"))
END_EVENT_TABLE()
Utility ClientData::Site to register hooks into a host class that attach client data.
constexpr CommandFlag AlwaysEnabledFlag
Definition: CommandFlag.h:34
static const AudacityProject::AttachedObjects::RegisteredFactory key
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define safenew
Definition: MemoryX.h:10
wxFrame * FindProjectFrame(AudacityProject *project)
Get a pointer to the window associated with a project, or null if the given pointer is null,...
AUDACITY_DLL_API AttachedWindows & GetAttachedWindows(AudacityProject &project)
accessors for certain important windows associated with each project
@ eIsCreating
Definition: ShuttleGui.h:39
THEME_API Theme theTheme
Definition: Theme.cpp:82
#define S(N)
Definition: ToChars.cpp:64
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
static AudioIO * Get()
Definition: AudioIO.cpp:147
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:266
Subclass * Find(const RegisteredFactory &key)
Get a (bare) pointer to an attachment, or null, down-cast it to Subclass *; will not create on demand...
Definition: ClientData.h:333
Subclass & Get(const RegisteredFactory &key)
Get reference to an attachment, creating on demand if not present, down-cast it to Subclass.
Definition: ClientData.h:309
static Clipboard & Get()
Definition: Clipboard.cpp:28
CommandContext provides additional information to an 'Apply()' command. It provides the project,...
AudacityProject & project
void UpdateCheckmarks(AudacityProject &project)
static CommandManager & Get(AudacityProject &project)
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
A move-only handle representing a connection to a Publisher.
Definition: Observer.h:70
A listener notified of changes in preferences.
Definition: Prefs.h:556
void SetTool(int tool)
static ProjectSettings & Get(AudacityProject &project)
int GetTool() const
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:628
Provides UI for spectral editing and parameters adjustments.
wxBitmap & Bitmap(int iIndex)
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:1437
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:486
static TrackView & Get(Track &)
Definition: TrackView.cpp:69
Maintain a non-persistent list of states of the project, to support undo and redo commands.
Definition: UndoManager.h:167
static UndoManager & Get(AudacityProject &project)
Definition: UndoManager.cpp:67
A Track that contains audio waveform data.
Definition: WaveTrack.h:57
wxString Find(const FilePath &path)
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:194
std::unique_ptr< CommandItem > Command(const CommandID &name, const TranslatableString &label_in, void(Handler::*pmf)(const CommandContext &), CommandFlag flags, const CommandManager::Options &options={}, CommandHandlerFinder finder=FinderScope::DefaultFinder())
AUDACITY_DLL_API void UpdatePrefs(wxWindow *pParent)
void OnCloseWindow(wxCloseEvent &e)
bool on
Definition: AudioIO.h:77
enum AudioIOEvent::Type type
A convenient default parameter for class template Site.
Definition: ClientData.h:28
Message is sent during idle time by the global clipboard.
Definition: Clipboard.h:24
Options && CheckTest(const CheckFn &fn) &&
Type of message published by UndoManager.
Definition: UndoManager.h:61
@ Purge
Undo or redo states eliminated.
Definition: UndoManager.h:75
enum UndoRedoMessage::Type type