Audacity 3.2.0
BrushHandle.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5BrushHandle.cpp
6
7Edward Hui
8
9**********************************************************************/
10
11
12#include "BrushHandle.h"
13#include "Scrubbing.h"
14#include "ChannelView.h"
15
16#include "AColor.h"
17#include "SpectrumAnalyst.h"
18#include "NumberScale.h"
19#include "Project.h"
20#include "ProjectAudioIO.h"
21#include "ProjectHistory.h"
22#include "../../ProjectSettings.h"
23
24#include "../../RefreshCode.h"
25#include "../../SelectUtilities.h"
26#include "SelectionState.h"
27#include "../../SpectralDataManager.h"
28#include "../../TrackArtist.h"
29#include "TrackFocus.h"
30#include "../../TrackPanel.h"
31#include "../../TrackPanelDrawingContext.h"
32#include "../../TrackPanelMouseEvent.h"
33#include "ViewInfo.h"
34#include "WaveClip.h"
35#include "WaveTrack.h"
36#include "SpectrogramSettings.h"
37#include "../../../images/Cursors.h"
38#include "../playabletrack/wavetrack/ui/SpectrumView.h"
39
40#include <cmath>
41#include <wx/event.h>
42#include <iostream>
43
44enum {
45 //This constant determines the size of the horizontal region (in pixels) around
46 //the right and left selection bounds that can be used for horizontal selection adjusting
47 //(or, vertical distance around top and bottom bounds in spectrograms,
48 // for vertical selection adjusting)
50
51 // Seems 4 is too small to work at the top. Why?
53};
54
55// #define SPECTRAL_EDITING_ESC_KEY
56
58{
59 return mSelectionStateChanger.get() != NULL;
60}
61
62namespace
63{
66 double frequency,
67 wxInt64 trackTopEdge,
68 int trackHeight)
69 {
70 const auto &settings = SpectrogramSettings::Get(wc);
71 float minFreq, maxFreq;
72 SpectrogramBounds::Get(wc).GetBounds(wc, minFreq, maxFreq);
73 const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
74 const float p = numberScale.ValueToPosition(frequency);
75 return trackTopEdge + wxInt64((1.0 - p) * trackHeight);
76 }
77
81 bool maySnap,
82 wxInt64 mouseYCoordinate,
83 wxInt64 trackTopEdge,
84 int trackHeight)
85 {
86 const double rate = wc.GetRate();
87
88 // Handle snapping
89 if (maySnap &&
90 mouseYCoordinate - trackTopEdge < FREQ_SNAP_DISTANCE)
91 return rate;
92 if (maySnap &&
93 trackTopEdge + trackHeight - mouseYCoordinate < FREQ_SNAP_DISTANCE)
94 return -1;
95
96 const auto &settings = SpectrogramSettings::Get(wc);
97 const auto &cache = SpectrogramBounds::Get(wc);
98 float minFreq, maxFreq;
99 cache.GetBounds(wc, minFreq, maxFreq);
100 const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
101 const double p = double(mouseYCoordinate - trackTopEdge) / trackHeight;
102 return numberScale.PositionToValue(1.0 - p);
103 }
104
105 long long PositionToLongSample(const WaveTrack *wt,
106 const ViewInfo &viewInfo,
107 int trackTopEdgeX,
108 int mousePosX)
109 {
110 wxInt64 posTime = viewInfo.PositionToTime(mousePosX, trackTopEdgeX);
111 sampleCount sc = wt->TimeToLongSamples(posTime);
112 return sc.as_long_long();
113 }
114
115 template<typename T>
116 inline void SetIfNotNull(T * pValue, const T Value)
117 {
118 if (pValue == NULL)
119 return;
120 *pValue = Value;
121 }
122
123 // This returns true if we're a spectral editing track.
124 inline bool isSpectralSelectionView(const ChannelView *pChannelView) {
125 const auto pChannel = pChannelView && pChannelView->IsSpectral()
126 ? pChannelView->FindChannel<const WaveChannel>() : nullptr;
127 return pChannel &&
129 }
130 wxCursor *CrosshairCursor()
131 {
132 static auto crosshairCursor =
133 ::MakeCursor(wxCURSOR_IBEAM, CrosshairCursorXpm, 16, 16);
134 return &*crosshairCursor;
135 }
136}
137
139 std::shared_ptr<StateSaver> pStateSaver,
140 const std::shared_ptr<ChannelView> &pChannelView,
141 const TrackList &trackList,
142 const TrackPanelMouseState &st, const ViewInfo &viewInfo,
143 const std::shared_ptr<SpectralData> &pSpectralData,
144 const ProjectSettings &pSettings
145) : mpStateSaver{ move(pStateSaver) }
146 , mpSpectralData(pSpectralData)
147 , mpView{ pChannelView }
148{
149 const wxMouseState &state = st.state;
150 auto wc = pChannelView->FindChannel<WaveChannel>();
151 double rate = mpSpectralData->GetSR();
152
153 mRect = st.rect;
154 mBrushRadius = pSettings.GetBrushRadius();
155 const auto &settings = SpectrogramSettings::Get(*wc);
156 mFreqUpperBound = settings.maxFreq - 1;
157 mFreqLowerBound = settings.minFreq + 1;
159 mIsOvertones = pSettings.IsOvertones();
160 // Borrowed from TimeToLongSample
161 mSampleCountLowerBound = floor( wc->GetStartTime() * rate + 0.5);
162 mSampleCountUpperBound = floor( wc->GetEndTime() * rate + 0.5);
163}
164
166{
167}
168
169std::shared_ptr<const Track> BrushHandle::FindTrack() const
170{
171 return TrackFromChannel(const_cast<BrushHandle&>(*this).FindChannel());
172}
173
174namespace {
175 // Is the distance between A and B less than D?
176 template < class A, class B, class DIST > bool within(A a, B b, DIST d)
177 {
178 return (a > b - d) && (a < b + d);
179 }
180
181 inline double findMaxRatio(double center, double rate)
182 {
183 const double minFrequency = 1.0;
184 const double maxFrequency = (rate / 2.0);
185 const double frequency =
186 std::min(maxFrequency,
187 std::max(minFrequency, center));
188 return
189 std::min(frequency / minFrequency, maxFrequency / frequency);
190 }
191}
192
194{
195
196}
197
199{
200 return false;
201}
202
203// Add or remove data according to the ctrl key press
204void BrushHandle::HandleHopBinData(int hopNum, int freqBinNum) {
205 // Ignore the mouse dragging outside valid area
206 // We should also check for the available freq. range that is visible to user
207 long long sampleCount = hopNum * mpSpectralData->GetHopSize();
208 wxInt64 freq = freqBinNum * mpSpectralData->GetSR() / mpSpectralData->GetWindowSize();
209
211 freq < mFreqLowerBound || freq > mFreqUpperBound)
212 return;
213
214 if(mbCtrlDown)
215 mpSpectralData->removeHopBinData(hopNum, freqBinNum);
216 else
217 mpSpectralData->addHopBinData(hopNum, freqBinNum);
218}
219
221 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
222{
223 if (mpStateSaver)
224 // Clear all unless there is a modifier key down
225 mpStateSaver->Init( *pProject, !evt.event.HasAnyModifiers() );
226
227 using namespace RefreshCode;
228
229 const auto pView = mpView.lock();
230 if ( !pView )
231 return Cancelled;
232
233 wxMouseEvent &event = evt.event;
234 auto &trackPanel = TrackPanel::Get( *pProject );
235 auto &viewInfo = ViewInfo::Get( *pProject );
236
237 return RefreshAll;
238}
239
241 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
242{
243 using namespace RefreshCode;
244
245 const auto pView = mpView.lock();
246 if ( !pView )
247 return Cancelled;
248
249 wxMouseEvent &event = evt.event;
250 const auto wc = FindChannel();
251 auto &trackPanel = TrackPanel::Get( *pProject );
252 auto &viewInfo = ViewInfo::Get( *pProject );
253
254 auto posXToHopNum = [&](int x0){
255 double posTime = viewInfo.PositionToTime(x0, mRect.x);
256 sampleCount sc = wc->TimeToLongSamples(posTime);
257 auto hopSize = mpSpectralData->GetHopSize();
258 return (sc.as_long_long() + hopSize / 2) / hopSize;
259 };
260
261 auto posYToFreqBin = [&](int y0){
262 int resFreq = PositionToFrequency(*wc, 0, y0, mRect.y, mRect.height);
263 double resFreqBin = resFreq / (mpSpectralData->GetSR() / mpSpectralData->GetWindowSize());
264 return static_cast<int>(std::round(resFreqBin));
265 };
266
267 auto drawCircle = [&](int h0, int bm){
268 // For each (h0, b0), draw circle
269 int h2 = mBrushRadius;
270 int b2 = 0;
271 int hChange = 1 - (mBrushRadius << 1);
272 int bChange = 0;
273 int radiusError = 0;
274 while (h2 >= b2) {
275 for (int i = h0 - h2; i <= h0 + h2; i++)
276 {
277 HandleHopBinData(i, bm + b2);
278 HandleHopBinData(i, bm - b2);
279 }
280 for (int i = h0 - b2; i <= h0 + b2; i++)
281 {
282 HandleHopBinData(i, bm + h2);
283 HandleHopBinData(i, bm - h2);
284 }
285
286 b2++;
287 radiusError += bChange;
288 bChange += 2;
289 if (((radiusError << 1) + hChange) > 0)
290 {
291 h2--;
292 radiusError += hChange;
293 hChange += 2;
294 }
295 } // End of full circle drawing
296 };
297
298 // Clip the coordinates
299 // TODO: Find ways to access the ClipParameters (for the mid)
300 int dest_xcoord = std::clamp(event.m_x, mRect.x + 10, mRect.x + mRect.width);
301 int dest_ycoord = std::clamp(event.m_y, mRect.y + 10, mRect.y + mRect.height);
302
303 int h1 = posXToHopNum(dest_xcoord);
304 int b1 = posYToFreqBin(dest_ycoord);
305
306 mbCtrlDown = event.ControlDown() || event.AltDown();
307
308 // Use the hop and bin number to calculate the brush stroke, instead of the mouse coordinates
309 // For mouse coordinate:
310 // (src_xcoord, src_ycoord) -> (dest_xcoord, dest_ycoord)
311
312 const auto &hopSize = mpSpectralData->GetHopSize();
313 if(!mpSpectralData->coordHistory.empty()){
314 int src_xcoord = mpSpectralData->coordHistory.back().first;
315 int src_ycoord = mpSpectralData->coordHistory.back().second;
316
317 int h0 = posXToHopNum(src_xcoord);
318 int b0 = posYToFreqBin(src_ycoord);
319 int wd = mBrushRadius * 2;
320
321 int dh = abs(h1-h0), sh = h0<h1 ? 1 : -1;
322 int db = -abs(b1-b0), sb = b0<b1 ? 1 : -1;
323 int err = dh+db, err2;
324
325 // Line drawing (draw points until the start coordinate reaches the end)
326 while(true){
327 if (h0 == h1 && b0 == b1)
328 break;
329 err2 = err * 2;
330 if (err2 * 2 >= db) { err += db; h0 += sh; }
331 if (err2 * 2 <= dh) { err += dh; b0 += sb; }
332
333
334 int bm = b0;
336 // Correct the y coord (snap to highest energy freq. bin)
337 if(auto *sView = dynamic_cast<SpectrumView*>(pView.get())){
338 int resFreqBin =
340 h0 * hopSize, hopSize, mFreqSnappingRatio, bm);
341 if(resFreqBin != - 1)
342 bm = resFreqBin;
343 }
344 }
345
346 if(mIsOvertones){
347 // take bm and calculate the highest energy
348 std::vector<int> binsToWork = SpectralDataManager::FindHighestFrequencyBins(*wc,
349 h0 * hopSize, hopSize, mOvertonesThreshold, bm);
350 for(auto & bins: binsToWork)
351 drawCircle(h0, bins);
352 }
353
354 drawCircle(h0, bm);
355 } // End of line connecting
356 }
357 mpSpectralData->coordHistory.push_back(std::make_pair(dest_xcoord, dest_ycoord));
358 return RefreshAll;
359}
360
362 (const TrackPanelMouseState &st, AudacityProject *pProject)
363{
365 wxCursor *pCursor = CrosshairCursor();
366 return { tip, pCursor };
367}
368
370 (const TrackPanelMouseEvent &evt, AudacityProject *pProject,
371 wxWindow *)
372{
373 using namespace RefreshCode;
374 mpSpectralData->saveAndClearBuffer();
375 if (mpStateSaver) {
376 mpStateSaver->Commit();
377 mpStateSaver.reset();
378 }
379 if(mbCtrlDown){
380 ProjectHistory::Get( *pProject ).PushState(
381 XO( "Erased selected area" ),
382 XO( "Erased selected area" ) );
383 ProjectHistory::Get( *pProject ).ModifyState(true);
384 }
385 else{
386 ProjectHistory::Get( *pProject ).PushState(
387 XO( "Selected area using Brush Tool" ),
388 XO( "Brush tool selection" ) );
389 ProjectHistory::Get( *pProject ).ModifyState(true);
390 }
391
392 return RefreshNone;
393}
394
396{
397 mpStateSaver.reset();
399}
400
403 const wxRect &rect, unsigned iPass )
404{
405 if ( iPass == TrackArtist::PassTracks ) {
406 auto& dc = context.dc;
407 wxPoint coord;
408 coord.x = mMostRecentX;
409 coord.y = mMostRecentY;
410 dc.SetBrush( *wxTRANSPARENT_BRUSH );
411 dc.SetPen( *wxYELLOW_PEN );
412 dc.DrawCircle(coord, mBrushRadius);
413 }
414}
415
416std::shared_ptr<WaveChannel> BrushHandle::FindChannel()
417{
418 auto pView = mpView.lock();
419 if (!pView)
420 return {};
421 else
422 return pView->FindChannel<WaveChannel>();
423}
424
@ FREQ_SNAP_DISTANCE
Definition: BrushHandle.cpp:52
@ SELECTION_RESIZE_REGION
Definition: BrushHandle.cpp:49
int min(int a, int b)
XO("Cut/Copy/Paste")
const auto project
#define A(N)
Definition: ToChars.cpp:62
static Settings & settings()
Definition: TrackInfo.cpp:51
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:189
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:170
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
std::shared_ptr< SelectionStateChanger > mSelectionStateChanger
Definition: BrushHandle.h:117
std::shared_ptr< StateSaver > mpStateSaver
Definition: BrushHandle.h:90
wxInt64 mFreqUpperBound
Definition: BrushHandle.h:112
long long mSampleCountLowerBound
Definition: BrushHandle.h:111
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *pProject) override
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
virtual ~BrushHandle()
void Enter(bool forward, AudacityProject *pProject) override
bool IsDragging() const override
Definition: BrushHandle.cpp:57
bool mIsSmartSelection
Definition: BrushHandle.h:110
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
BrushHandle(const BrushHandle &)
double mFreqSnappingRatio
Definition: BrushHandle.h:105
wxInt64 mFreqLowerBound
Definition: BrushHandle.h:112
bool mbCtrlDown
Definition: BrushHandle.h:115
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
void HandleHopBinData(int hopNum, int freqBinNum)
std::weak_ptr< ChannelView > mpView
Definition: BrushHandle.h:100
double mOvertonesThreshold
Definition: BrushHandle.h:108
Result Cancel(AudacityProject *) override
int mBrushRadius
Definition: BrushHandle.h:114
bool Escape(AudacityProject *pProject) override
int mMostRecentX
Definition: BrushHandle.h:113
std::shared_ptr< WaveChannel > FindChannel()
bool mIsOvertones
Definition: BrushHandle.h:110
int mMostRecentY
Definition: BrushHandle.h:113
wxRect mRect
Definition: BrushHandle.h:101
long long mSampleCountUpperBound
Definition: BrushHandle.h:111
std::shared_ptr< SpectralData > mpSpectralData
Definition: BrushHandle.h:91
std::shared_ptr< const Track > FindTrack() const override
virtual bool IsSpectral() const
auto FindChannel() -> std::shared_ptr< Subtype >
May return null.
float PositionToValue(float pp) const
Definition: NumberScale.h:155
float ValueToPosition(float val) const
Definition: NumberScale.h:256
void PushState(const TranslatableString &desc, const TranslatableString &shortDesc)
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
Holds various per-project settings values, and sends events to the project when certain values change...
bool IsOvertones() const
int GetBrushRadius() const
bool IsSmartSelection() const
static std::vector< int > FindHighestFrequencyBins(WaveChannel &wc, long long int startSC, int hopSize, double threshold, int targetFreqBin)
static int FindFrequencySnappingBin(const WaveChannel &channel, long long startSC, int hopSize, double threshold, int targetFreqBin)
void GetBounds(const WaveChannel &wc, float &min, float &max) const
static SpectrogramBounds & Get(WaveTrack &track)
Get either the global default settings, or the track's own if previously created.
bool SpectralSelectionEnabled() const
static SpectrogramSettings & Get(const WaveTrack &track)
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:850
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:234
Holds a msgid for the translation catalog; may also bind format arguments.
static std::shared_ptr< const Track > TrackFromChannel(const std::shared_ptr< const Channel > &pChannel)
A frequent convenience in the definition of UIHandles.
Definition: UIHandle.cpp:63
unsigned Result
Definition: UIHandle.h:40
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
double GetRate() const override
Definition: WaveTrack.cpp:816
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
sampleCount TimeToLongSamples(double t0) const
double PositionToTime(int64 position, int64 origin=0, bool ignoreFisheye=false) const
Definition: ZoomInfo.cpp:34
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
long long as_long_long() const
Definition: SampleCount.h:48
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
bool isSpectralSelectionView(const ChannelView *pChannelView)
double PositionToFrequency(const WaveChannel &wc, bool maySnap, wxInt64 mouseYCoordinate, wxInt64 trackTopEdge, int trackHeight)
Definition: BrushHandle.cpp:80
void SetIfNotNull(T *pValue, const T Value)
long long PositionToLongSample(const WaveTrack *wt, const ViewInfo &viewInfo, int trackTopEdgeX, int mousePosX)
wxInt64 FrequencyToPosition(const WaveChannel &wc, double frequency, wxInt64 trackTopEdge, int trackHeight)
Converts a frequency to screen y position.
Definition: BrushHandle.cpp:65
double findMaxRatio(double center, double rate)
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512