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 "../../prefs/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{
65 wxInt64 FrequencyToPosition(const WaveTrack *wt,
66 double frequency,
67 wxInt64 trackTopEdge,
68 int trackHeight)
69 {
70 const auto &settings = SpectrogramSettings::Get(*wt);
71 float minFreq, maxFreq;
72 SpectrogramBounds::Get(*wt).GetBounds(*wt, 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 = wt->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(*wt);
97 const auto &cache = SpectrogramBounds::Get(*wt);
98 float minFreq, maxFreq;
99 cache.GetBounds(*wt, 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 return
126 pChannelView &&
127 pChannelView->IsSpectral() &&
128 pChannelView->FindTrack() &&
129 pChannelView->FindTrack()->TypeSwitch<bool>(
130 [&](const WaveTrack &wt) {
131 const auto &settings = SpectrogramSettings::Get(wt);
132 return settings.SpectralSelectionEnabled();
133 });
134 }
135 wxCursor *CrosshairCursor()
136 {
137 static auto crosshairCursor =
138 ::MakeCursor(wxCURSOR_IBEAM, CrosshairCursorXpm, 16, 16);
139 return &*crosshairCursor;
140 }
141}
142
144 std::shared_ptr<StateSaver> pStateSaver,
145 const std::shared_ptr<ChannelView> &pChannelView,
146 const TrackList &trackList,
147 const TrackPanelMouseState &st, const ViewInfo &viewInfo,
148 const std::shared_ptr<SpectralData> &pSpectralData,
149 const ProjectSettings &pSettings
150) : mpStateSaver{ move(pStateSaver) }
151 , mpSpectralData(pSpectralData)
152 , mpView{ pChannelView }
153{
154 const wxMouseState &state = st.state;
155 auto pTrack = pChannelView->FindTrack().get();
156 auto wt = dynamic_cast<WaveTrack *>(pTrack);
157 double rate = mpSpectralData->GetSR();
158
159 mRect = st.rect;
160 mBrushRadius = pSettings.GetBrushRadius();
161 const auto &settings = SpectrogramSettings::Get(*wt);
162 mFreqUpperBound = settings.maxFreq - 1;
163 mFreqLowerBound = settings.minFreq + 1;
165 mIsOvertones = pSettings.IsOvertones();
166 // Borrowed from TimeToLongSample
167 mSampleCountLowerBound = floor( pTrack->GetStartTime() * rate + 0.5);
168 mSampleCountUpperBound = floor( pTrack->GetEndTime() * rate + 0.5);
169}
170
172{
173}
174
175std::shared_ptr<const Channel> BrushHandle::FindChannel() const
176{
177 return std::dynamic_pointer_cast<const Channel>(FindTrack().lock());
178}
179
180namespace {
181 // Is the distance between A and B less than D?
182 template < class A, class B, class DIST > bool within(A a, B b, DIST d)
183 {
184 return (a > b - d) && (a < b + d);
185 }
186
187 inline double findMaxRatio(double center, double rate)
188 {
189 const double minFrequency = 1.0;
190 const double maxFrequency = (rate / 2.0);
191 const double frequency =
192 std::min(maxFrequency,
193 std::max(minFrequency, center));
194 return
195 std::min(frequency / minFrequency, maxFrequency / frequency);
196 }
197}
198
200{
201
202}
203
205{
206 return false;
207}
208
209// Add or remove data according to the ctrl key press
210void BrushHandle::HandleHopBinData(int hopNum, int freqBinNum) {
211 // Ignore the mouse dragging outside valid area
212 // We should also check for the available freq. range that is visible to user
213 long long sampleCount = hopNum * mpSpectralData->GetHopSize();
214 wxInt64 freq = freqBinNum * mpSpectralData->GetSR() / mpSpectralData->GetWindowSize();
215
217 freq < mFreqLowerBound || freq > mFreqUpperBound)
218 return;
219
220 if(mbCtrlDown)
221 mpSpectralData->removeHopBinData(hopNum, freqBinNum);
222 else
223 mpSpectralData->addHopBinData(hopNum, freqBinNum);
224}
225
227 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
228{
229 if (mpStateSaver)
230 // Clear all unless there is a modifier key down
231 mpStateSaver->Init( *pProject, !evt.event.HasAnyModifiers() );
232
233 using namespace RefreshCode;
234
235 const auto pView = mpView.lock();
236 if ( !pView )
237 return Cancelled;
238
239 wxMouseEvent &event = evt.event;
240 const auto sTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
241 const auto pTrack = sTrack.get();
242 const WaveTrack *const wt =
243 static_cast<const WaveTrack*>(pTrack);
244 auto &trackPanel = TrackPanel::Get( *pProject );
245 auto &viewInfo = ViewInfo::Get( *pProject );
246
247 return RefreshAll;
248}
249
251 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
252{
253 using namespace RefreshCode;
254
255 const auto pView = mpView.lock();
256 if ( !pView )
257 return Cancelled;
258
259 wxMouseEvent &event = evt.event;
260 const auto sTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
261 const auto pTrack = sTrack.get();
262 WaveTrack *const wt =
263 static_cast<WaveTrack*>(pTrack);
264 auto &trackPanel = TrackPanel::Get( *pProject );
265 auto &viewInfo = ViewInfo::Get( *pProject );
266
267 auto posXToHopNum = [&](int x0){
268 double posTime = viewInfo.PositionToTime(x0, mRect.x);
269 sampleCount sc = wt->TimeToLongSamples(posTime);
270 auto hopSize = mpSpectralData->GetHopSize();
271 return (sc.as_long_long() + hopSize / 2) / hopSize;
272 };
273
274 auto posYToFreqBin = [&](int y0){
275 int resFreq = PositionToFrequency(wt, 0, y0, mRect.y, mRect.height);
276 double resFreqBin = resFreq / (mpSpectralData->GetSR() / mpSpectralData->GetWindowSize());
277 return static_cast<int>(std::round(resFreqBin));
278 };
279
280 auto drawCircle = [&](int h0, int bm){
281 // For each (h0, b0), draw circle
282 int h2 = mBrushRadius;
283 int b2 = 0;
284 int hChange = 1 - (mBrushRadius << 1);
285 int bChange = 0;
286 int radiusError = 0;
287 while (h2 >= b2) {
288 for (int i = h0 - h2; i <= h0 + h2; i++)
289 {
290 HandleHopBinData(i, bm + b2);
291 HandleHopBinData(i, bm - b2);
292 }
293 for (int i = h0 - b2; i <= h0 + b2; i++)
294 {
295 HandleHopBinData(i, bm + h2);
296 HandleHopBinData(i, bm - h2);
297 }
298
299 b2++;
300 radiusError += bChange;
301 bChange += 2;
302 if (((radiusError << 1) + hChange) > 0)
303 {
304 h2--;
305 radiusError += hChange;
306 hChange += 2;
307 }
308 } // End of full circle drawing
309 };
310
311 // Clip the coordinates
312 // TODO: Find ways to access the ClipParameters (for the mid)
313 int dest_xcoord = std::clamp(event.m_x, mRect.x + 10, mRect.x + mRect.width);
314 int dest_ycoord = std::clamp(event.m_y, mRect.y + 10, mRect.y + mRect.height);
315
316 int h1 = posXToHopNum(dest_xcoord);
317 int b1 = posYToFreqBin(dest_ycoord);
318
319 mbCtrlDown = event.ControlDown() || event.AltDown();
320
321 // Use the hop and bin number to calculate the brush stroke, instead of the mouse coordinates
322 // For mouse coordinate:
323 // (src_xcoord, src_ycoord) -> (dest_xcoord, dest_ycoord)
324
325 const auto &hopSize = mpSpectralData->GetHopSize();
326 if(!mpSpectralData->coordHistory.empty()){
327 int src_xcoord = mpSpectralData->coordHistory.back().first;
328 int src_ycoord = mpSpectralData->coordHistory.back().second;
329
330 int h0 = posXToHopNum(src_xcoord);
331 int b0 = posYToFreqBin(src_ycoord);
332 int wd = mBrushRadius * 2;
333
334 int dh = abs(h1-h0), sh = h0<h1 ? 1 : -1;
335 int db = -abs(b1-b0), sb = b0<b1 ? 1 : -1;
336 int err = dh+db, err2;
337
338 // Line drawing (draw points until the start coordinate reaches the end)
339 while(true){
340 if (h0 == h1 && b0 == b1)
341 break;
342 err2 = err * 2;
343 if (err2 * 2 >= db) { err += db; h0 += sh; }
344 if (err2 * 2 <= dh) { err += dh; b0 += sb; }
345
346
347 int bm = b0;
349 // Correct the y coord (snap to highest energy freq. bin)
350 if(auto *sView = dynamic_cast<SpectrumView*>(pView.get())){
351 int resFreqBin =
353 h0 * hopSize, hopSize, mFreqSnappingRatio, bm);
354 if(resFreqBin != - 1)
355 bm = resFreqBin;
356 }
357 }
358
359 if(mIsOvertones){
360 // take bm and calculate the highest energy
361 std::vector<int> binsToWork = SpectralDataManager::FindHighestFrequencyBins(wt,
362 h0 * hopSize, hopSize, mOvertonesThreshold, bm);
363 for(auto & bins: binsToWork)
364 drawCircle(h0, bins);
365 }
366
367 drawCircle(h0, bm);
368 } // End of line connecting
369 }
370 mpSpectralData->coordHistory.push_back(std::make_pair(dest_xcoord, dest_ycoord));
371 return RefreshAll;
372}
373
375 (const TrackPanelMouseState &st, AudacityProject *pProject)
376{
378 wxCursor *pCursor = CrosshairCursor();
379 return { tip, pCursor };
380}
381
383 (const TrackPanelMouseEvent &evt, AudacityProject *pProject,
384 wxWindow *)
385{
386 using namespace RefreshCode;
387 mpSpectralData->saveAndClearBuffer();
388 if (mpStateSaver) {
389 mpStateSaver->Commit();
390 mpStateSaver.reset();
391 }
392 if(mbCtrlDown){
393 ProjectHistory::Get( *pProject ).PushState(
394 XO( "Erased selected area" ),
395 XO( "Erased selected area" ) );
396 ProjectHistory::Get( *pProject ).ModifyState(true);
397 }
398 else{
399 ProjectHistory::Get( *pProject ).PushState(
400 XO( "Selected area using Brush Tool" ),
401 XO( "Brush tool selection" ) );
402 ProjectHistory::Get( *pProject ).ModifyState(true);
403 }
404
405 return RefreshNone;
406}
407
409{
410 mpStateSaver.reset();
412}
413
416 const wxRect &rect, unsigned iPass )
417{
418 if ( iPass == TrackArtist::PassTracks ) {
419 auto& dc = context.dc;
420 wxPoint coord;
421 coord.x = mMostRecentX;
422 coord.y = mMostRecentY;
423 dc.SetBrush( *wxTRANSPARENT_BRUSH );
424 dc.SetPen( *wxYELLOW_PEN );
425 dc.DrawCircle(coord, mBrushRadius);
426 }
427}
428
429std::weak_ptr<Track> BrushHandle::FindTrack()
430{
431 auto pView = mpView.lock();
432 if (!pView)
433 return {};
434 else
435 return pView->FindTrack();
436}
437
438std::weak_ptr<const Track> BrushHandle::FindTrack() const
439{
440 return const_cast<BrushHandle&>(*this).FindTrack();
441}
442
@ 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:69
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:188
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:169
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:118
std::shared_ptr< StateSaver > mpStateSaver
Definition: BrushHandle.h:90
wxInt64 mFreqUpperBound
Definition: BrushHandle.h:113
long long mSampleCountLowerBound
Definition: BrushHandle.h:112
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:111
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
BrushHandle(const BrushHandle &)
double mFreqSnappingRatio
Definition: BrushHandle.h:106
wxInt64 mFreqLowerBound
Definition: BrushHandle.h:113
bool mbCtrlDown
Definition: BrushHandle.h:116
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
void HandleHopBinData(int hopNum, int freqBinNum)
std::weak_ptr< ChannelView > mpView
Definition: BrushHandle.h:101
double mOvertonesThreshold
Definition: BrushHandle.h:109
Result Cancel(AudacityProject *) override
std::shared_ptr< const Channel > FindChannel() const override
int mBrushRadius
Definition: BrushHandle.h:115
bool Escape(AudacityProject *pProject) override
int mMostRecentX
Definition: BrushHandle.h:114
bool mIsOvertones
Definition: BrushHandle.h:111
std::weak_ptr< Track > FindTrack()
int mMostRecentY
Definition: BrushHandle.h:114
wxRect mRect
Definition: BrushHandle.h:102
long long mSampleCountUpperBound
Definition: BrushHandle.h:112
std::shared_ptr< SpectralData > mpSpectralData
Definition: BrushHandle.h:91
virtual bool IsSpectral() const
std::shared_ptr< Track > FindTrack()
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(WaveTrack *wt, 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 WaveTrack &wt, float &min, float &max) const
static SpectrogramBounds & Get(WaveTrack &track)
Get either the global default settings, or the track's own if previously created.
static SpectrogramSettings & Get(const WaveTrack &track)
Mutative access to attachment even if the track argument is const.
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:993
std::shared_ptr< Subclass > Lock(const std::weak_ptr< Subclass > &wTrack)
Definition: Track.h:1246
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:347
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:233
Holds a msgid for the translation catalog; may also bind format arguments.
unsigned Result
Definition: UIHandle.h:39
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
A Track that contains audio waveform data.
Definition: WaveTrack.h:227
double GetRate() const override
Definition: WaveTrack.cpp:1128
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 WaveTrack *wt, 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)
double findMaxRatio(double center, double rate)
wxInt64 FrequencyToPosition(const WaveTrack *wt, double frequency, wxInt64 trackTopEdge, int trackHeight)
Converts a frequency to screen y position.
Definition: BrushHandle.cpp:65
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512