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