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#include "../../ProjectWindow.h"
24#include "../../RefreshCode.h"
25#include "../../SelectUtilities.h"
26#include "SelectionState.h"
27#include "../../SpectralDataManager.h"
28#include "../../TrackArtist.h"
29#include "../../TrackPanelAx.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
175namespace {
176 // Is the distance between A and B less than D?
177 template < class A, class B, class DIST > bool within(A a, B b, DIST d)
178 {
179 return (a > b - d) && (a < b + d);
180 }
181
182 inline double findMaxRatio(double center, double rate)
183 {
184 const double minFrequency = 1.0;
185 const double maxFrequency = (rate / 2.0);
186 const double frequency =
187 std::min(maxFrequency,
188 std::max(minFrequency, center));
189 return
190 std::min(frequency / minFrequency, maxFrequency / frequency);
191 }
192}
193
195{
196
197}
198
200{
201 return false;
202}
203
204// Add or remove data according to the ctrl key press
205void BrushHandle::HandleHopBinData(int hopNum, int freqBinNum) {
206 // Ignore the mouse dragging outside valid area
207 // We should also check for the available freq. range that is visible to user
208 long long sampleCount = hopNum * mpSpectralData->GetHopSize();
209 wxInt64 freq = freqBinNum * mpSpectralData->GetSR() / mpSpectralData->GetWindowSize();
210
212 freq < mFreqLowerBound || freq > mFreqUpperBound)
213 return;
214
215 if(mbCtrlDown)
216 mpSpectralData->removeHopBinData(hopNum, freqBinNum);
217 else
218 mpSpectralData->addHopBinData(hopNum, freqBinNum);
219}
220
222 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
223{
224 if (mpStateSaver)
225 // Clear all unless there is a modifier key down
226 mpStateSaver->Init( *pProject, !evt.event.HasAnyModifiers() );
227
228 using namespace RefreshCode;
229
230 const auto pView = mpView.lock();
231 if ( !pView )
232 return Cancelled;
233
234 wxMouseEvent &event = evt.event;
235 const auto sTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
236 const auto pTrack = sTrack.get();
237 const WaveTrack *const wt =
238 static_cast<const WaveTrack*>(pTrack);
239 auto &trackPanel = TrackPanel::Get( *pProject );
240 auto &viewInfo = ViewInfo::Get( *pProject );
241
242 return RefreshAll;
243}
244
246 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
247{
248 using namespace RefreshCode;
249
250 const auto pView = mpView.lock();
251 if ( !pView )
252 return Cancelled;
253
254 wxMouseEvent &event = evt.event;
255 const auto sTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
256 const auto pTrack = sTrack.get();
257 WaveTrack *const wt =
258 static_cast<WaveTrack*>(pTrack);
259 auto &trackPanel = TrackPanel::Get( *pProject );
260 auto &viewInfo = ViewInfo::Get( *pProject );
261
262 auto posXToHopNum = [&](int x0){
263 double posTime = viewInfo.PositionToTime(x0, mRect.x);
264 sampleCount sc = wt->TimeToLongSamples(posTime);
265 auto hopSize = mpSpectralData->GetHopSize();
266 return (sc.as_long_long() + hopSize / 2) / hopSize;
267 };
268
269 auto posYToFreqBin = [&](int y0){
270 int resFreq = PositionToFrequency(wt, 0, y0, mRect.y, mRect.height);
271 double resFreqBin = resFreq / (mpSpectralData->GetSR() / mpSpectralData->GetWindowSize());
272 return static_cast<int>(std::round(resFreqBin));
273 };
274
275 auto drawCircle = [&](int h0, int bm){
276 // For each (h0, b0), draw circle
277 int h2 = mBrushRadius;
278 int b2 = 0;
279 int hChange = 1 - (mBrushRadius << 1);
280 int bChange = 0;
281 int radiusError = 0;
282 while (h2 >= b2) {
283 for (int i = h0 - h2; i <= h0 + h2; i++)
284 {
285 HandleHopBinData(i, bm + b2);
286 HandleHopBinData(i, bm - b2);
287 }
288 for (int i = h0 - b2; i <= h0 + b2; i++)
289 {
290 HandleHopBinData(i, bm + h2);
291 HandleHopBinData(i, bm - h2);
292 }
293
294 b2++;
295 radiusError += bChange;
296 bChange += 2;
297 if (((radiusError << 1) + hChange) > 0)
298 {
299 h2--;
300 radiusError += hChange;
301 hChange += 2;
302 }
303 } // End of full circle drawing
304 };
305
306 // Clip the coordinates
307 // TODO: Find ways to access the ClipParameters (for the mid)
308 int dest_xcoord = std::clamp(event.m_x, mRect.x + 10, mRect.x + mRect.width);
309 int dest_ycoord = std::clamp(event.m_y, mRect.y + 10, mRect.y + mRect.height);
310
311 int h1 = posXToHopNum(dest_xcoord);
312 int b1 = posYToFreqBin(dest_ycoord);
313
314 mbCtrlDown = event.ControlDown() || event.AltDown();
315
316 // Use the hop and bin number to calculate the brush stroke, instead of the mouse coordinates
317 // For mouse coordinate:
318 // (src_xcoord, src_ycoord) -> (dest_xcoord, dest_ycoord)
319
320 const auto &hopSize = mpSpectralData->GetHopSize();
321 if(!mpSpectralData->coordHistory.empty()){
322 int src_xcoord = mpSpectralData->coordHistory.back().first;
323 int src_ycoord = mpSpectralData->coordHistory.back().second;
324
325 int h0 = posXToHopNum(src_xcoord);
326 int b0 = posYToFreqBin(src_ycoord);
327 int wd = mBrushRadius * 2;
328
329 int dh = abs(h1-h0), sh = h0<h1 ? 1 : -1;
330 int db = -abs(b1-b0), sb = b0<b1 ? 1 : -1;
331 int err = dh+db, err2;
332
333 // Line drawing (draw points until the start coordinate reaches the end)
334 while(true){
335 if (h0 == h1 && b0 == b1)
336 break;
337 err2 = err * 2;
338 if (err2 * 2 >= db) { err += db; h0 += sh; }
339 if (err2 * 2 <= dh) { err += dh; b0 += sb; }
340
341
342 int bm = b0;
344 // Correct the y coord (snap to highest energy freq. bin)
345 if(auto *sView = dynamic_cast<SpectrumView*>(pView.get())){
346 int resFreqBin =
348 h0 * hopSize, hopSize, mFreqSnappingRatio, bm);
349 if(resFreqBin != - 1)
350 bm = resFreqBin;
351 }
352 }
353
354 if(mIsOvertones){
355 // take bm and calculate the highest energy
356 std::vector<int> binsToWork = SpectralDataManager::FindHighestFrequencyBins(wt,
357 h0 * hopSize, hopSize, mOvertonesThreshold, bm);
358 for(auto & bins: binsToWork)
359 drawCircle(h0, bins);
360 }
361
362 drawCircle(h0, bm);
363 } // End of line connecting
364 }
365 mpSpectralData->coordHistory.push_back(std::make_pair(dest_xcoord, dest_ycoord));
366 return RefreshAll;
367}
368
370 (const TrackPanelMouseState &st, AudacityProject *pProject)
371{
373 wxCursor *pCursor = CrosshairCursor();
374 return { tip, pCursor };
375}
376
378 (const TrackPanelMouseEvent &evt, AudacityProject *pProject,
379 wxWindow *)
380{
381 using namespace RefreshCode;
382 mpSpectralData->saveAndClearBuffer();
383 if (mpStateSaver) {
384 mpStateSaver->Commit();
385 mpStateSaver.reset();
386 }
387 if(mbCtrlDown){
388 ProjectHistory::Get( *pProject ).PushState(
389 XO( "Erased selected area" ),
390 XO( "Erased selected area" ) );
391 ProjectHistory::Get( *pProject ).ModifyState(true);
392 }
393 else{
394 ProjectHistory::Get( *pProject ).PushState(
395 XO( "Selected area using Brush Tool" ),
396 XO( "Brush tool selection" ) );
397 ProjectHistory::Get( *pProject ).ModifyState(true);
398 }
399
400 return RefreshNone;
401}
402
404{
405 mpStateSaver.reset();
407}
408
411 const wxRect &rect, unsigned iPass )
412{
413 if ( iPass == TrackArtist::PassTracks ) {
414 auto& dc = context.dc;
415 wxPoint coord;
416 coord.x = mMostRecentX;
417 coord.y = mMostRecentY;
418 dc.SetBrush( *wxTRANSPARENT_BRUSH );
419 dc.SetPen( *wxYELLOW_PEN );
420 dc.DrawCircle(coord, mBrushRadius);
421 }
422}
423
424std::weak_ptr<Track> BrushHandle::FindTrack()
425{
426 auto pView = mpView.lock();
427 if (!pView)
428 return {};
429 else
430 return pView->FindTrack();
431}
432
@ 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:83
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:187
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:168
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:115
std::shared_ptr< StateSaver > mpStateSaver
Definition: BrushHandle.h:88
wxInt64 mFreqUpperBound
Definition: BrushHandle.h:110
long long mSampleCountLowerBound
Definition: BrushHandle.h:109
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 mIsSmartSelection
Definition: BrushHandle.h:108
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
BrushHandle(const BrushHandle &)
double mFreqSnappingRatio
Definition: BrushHandle.h:103
wxInt64 mFreqLowerBound
Definition: BrushHandle.h:110
bool mbCtrlDown
Definition: BrushHandle.h:113
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
void HandleHopBinData(int hopNum, int freqBinNum)
std::weak_ptr< ChannelView > mpView
Definition: BrushHandle.h:98
double mOvertonesThreshold
Definition: BrushHandle.h:106
Result Cancel(AudacityProject *) override
int mBrushRadius
Definition: BrushHandle.h:112
bool Escape(AudacityProject *pProject) override
int mMostRecentX
Definition: BrushHandle.h:111
bool mIsOvertones
Definition: BrushHandle.h:108
std::weak_ptr< Track > FindTrack()
bool IsClicked() const
Definition: BrushHandle.cpp:57
int mMostRecentY
Definition: BrushHandle.h:111
wxRect mRect
Definition: BrushHandle.h:99
long long mSampleCountUpperBound
Definition: BrushHandle.h:109
std::shared_ptr< SpectralData > mpSpectralData
Definition: BrushHandle.h:89
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:987
std::shared_ptr< Subclass > Lock(const std::weak_ptr< Subclass > &wTrack)
Definition: Track.h:1237
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:354
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:232
Holds a msgid for the translation catalog; may also bind format arguments.
unsigned Result
Definition: UIHandle.h:38
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
A Track that contains audio waveform data.
Definition: WaveTrack.h:221
double GetRate() const override
Definition: WaveTrack.cpp:792
sampleCount TimeToLongSamples(double t0) const
double PositionToTime(int64 position, int64 origin=0, bool ignoreFisheye=false) const
Definition: ZoomInfo.cpp:35
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