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 "TrackView.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 {
71 float minFreq, maxFreq;
72 wt->GetSpectrumBounds(&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
97 float minFreq, maxFreq;
98 wt->GetSpectrumBounds(&minFreq, &maxFreq);
99 const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
100 const double p = double(mouseYCoordinate - trackTopEdge) / trackHeight;
101 return numberScale.PositionToValue(1.0 - p);
102 }
103
104 long long PositionToLongSample(const WaveTrack *wt,
105 const ViewInfo &viewInfo,
106 int trackTopEdgeX,
107 int mousePosX)
108 {
109 wxInt64 posTime = viewInfo.PositionToTime(mousePosX, trackTopEdgeX);
110 sampleCount sc = wt->TimeToLongSamples(posTime);
111 return sc.as_long_long();
112 }
113
114 template<typename T>
115 inline void SetIfNotNull(T * pValue, const T Value)
116 {
117 if (pValue == NULL)
118 return;
119 *pValue = Value;
120 }
121
122 // This returns true if we're a spectral editing track.
123 inline bool isSpectralSelectionView(const TrackView *pTrackView) {
124 return
125 pTrackView &&
126 pTrackView->IsSpectral() &&
127 pTrackView->FindTrack() &&
128 pTrackView->FindTrack()->TypeSwitch< bool >(
129 [&](const WaveTrack *wt) {
130 const SpectrogramSettings &settings = wt->GetSpectrogramSettings();
131 return settings.SpectralSelectionEnabled();
132 });
133 }
134 wxCursor *CrosshairCursor()
135 {
136 static auto crosshairCursor =
137 ::MakeCursor(wxCURSOR_IBEAM, CrosshairCursorXpm, 16, 16);
138 return &*crosshairCursor;
139 }
140}
141
143 ( std::shared_ptr<StateSaver> pStateSaver,
144 const std::shared_ptr<TrackView> &pTrackView,
145 const TrackList &trackList,
146 const TrackPanelMouseState &st, const ViewInfo &viewInfo,
147 const std::shared_ptr<SpectralData> &pSpectralData,
148 const ProjectSettings &pSettings)
149 : mpStateSaver{ move(pStateSaver) }
150 , mpSpectralData(pSpectralData)
151 , mpView{ pTrackView }
152{
153 const wxMouseState &state = st.state;
154 auto pTrack = pTrackView->FindTrack().get();
155 auto wt = dynamic_cast<WaveTrack *>(pTrack);
156 double rate = mpSpectralData->GetSR();
157
158 mRect = st.rect;
159 mBrushRadius = pSettings.GetBrushRadius();
160 mFreqUpperBound = wt->GetSpectrogramSettings().maxFreq - 1;
161 mFreqLowerBound = wt->GetSpectrogramSettings().minFreq + 1;
163 mIsOvertones = pSettings.IsOvertones();
164 // Borrowed from TimeToLongSample
165 mSampleCountLowerBound = floor( pTrack->GetStartTime() * rate + 0.5);
166 mSampleCountUpperBound = floor( pTrack->GetEndTime() * rate + 0.5);
167}
168
170{
171}
172
173namespace {
174 // Is the distance between A and B less than D?
175 template < class A, class B, class DIST > bool within(A a, B b, DIST d)
176 {
177 return (a > b - d) && (a < b + d);
178 }
179
180 inline double findMaxRatio(double center, double rate)
181 {
182 const double minFrequency = 1.0;
183 const double maxFrequency = (rate / 2.0);
184 const double frequency =
185 std::min(maxFrequency,
186 std::max(minFrequency, center));
187 return
188 std::min(frequency / minFrequency, maxFrequency / frequency);
189 }
190}
191
193{
194
195}
196
198{
199 return false;
200}
201
202// Add or remove data according to the ctrl key press
203void BrushHandle::HandleHopBinData(int hopNum, int freqBinNum) {
204 // Ignore the mouse dragging outside valid area
205 // We should also check for the available freq. range that is visible to user
206 long long sampleCount = hopNum * mpSpectralData->GetHopSize();
207 wxInt64 freq = freqBinNum * mpSpectralData->GetSR() / mpSpectralData->GetWindowSize();
208
210 freq < mFreqLowerBound || freq > mFreqUpperBound)
211 return;
212
213 if(mbCtrlDown)
214 mpSpectralData->removeHopBinData(hopNum, freqBinNum);
215 else
216 mpSpectralData->addHopBinData(hopNum, freqBinNum);
217}
218
220 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
221{
222 if (mpStateSaver)
223 // Clear all unless there is a modifier key down
224 mpStateSaver->Init( *pProject, !evt.event.HasAnyModifiers() );
225
226 using namespace RefreshCode;
227
228 const auto pView = mpView.lock();
229 if ( !pView )
230 return Cancelled;
231
232 wxMouseEvent &event = evt.event;
233 const auto sTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
234 const auto pTrack = sTrack.get();
235 const WaveTrack *const wt =
236 static_cast<const WaveTrack*>(pTrack);
237 auto &trackPanel = TrackPanel::Get( *pProject );
238 auto &viewInfo = ViewInfo::Get( *pProject );
239
240 return RefreshAll;
241}
242
244 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
245{
246 using namespace RefreshCode;
247
248 const auto pView = mpView.lock();
249 if ( !pView )
250 return Cancelled;
251
252 wxMouseEvent &event = evt.event;
253 const auto sTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
254 const auto pTrack = sTrack.get();
255 WaveTrack *const wt =
256 static_cast<WaveTrack*>(pTrack);
257 auto &trackPanel = TrackPanel::Get( *pProject );
258 auto &viewInfo = ViewInfo::Get( *pProject );
259
260 auto posXToHopNum = [&](int x0){
261 double posTime = viewInfo.PositionToTime(x0, mRect.x);
262 sampleCount sc = wt->TimeToLongSamples(posTime);
263 auto hopSize = mpSpectralData->GetHopSize();
264 return (sc.as_long_long() + hopSize / 2) / hopSize;
265 };
266
267 auto posYToFreqBin = [&](int y0){
268 int resFreq = PositionToFrequency(wt, 0, y0, mRect.y, mRect.height);
269 double resFreqBin = resFreq / (mpSpectralData->GetSR() / mpSpectralData->GetWindowSize());
270 return static_cast<int>(std::round(resFreqBin));
271 };
272
273 auto drawCircle = [&](int h0, int bm){
274 // For each (h0, b0), draw circle
275 int h2 = mBrushRadius;
276 int b2 = 0;
277 int hChange = 1 - (mBrushRadius << 1);
278 int bChange = 0;
279 int radiusError = 0;
280 while (h2 >= b2) {
281 for (int i = h0 - h2; i <= h0 + h2; i++)
282 {
283 HandleHopBinData(i, bm + b2);
284 HandleHopBinData(i, bm - b2);
285 }
286 for (int i = h0 - b2; i <= h0 + b2; i++)
287 {
288 HandleHopBinData(i, bm + h2);
289 HandleHopBinData(i, bm - h2);
290 }
291
292 b2++;
293 radiusError += bChange;
294 bChange += 2;
295 if (((radiusError << 1) + hChange) > 0)
296 {
297 h2--;
298 radiusError += hChange;
299 hChange += 2;
300 }
301 } // End of full circle drawing
302 };
303
304 // Clip the coordinates
305 // TODO: Find ways to access the ClipParameters (for the mid)
306 int dest_xcoord = std::clamp(event.m_x, mRect.x + 10, mRect.x + mRect.width);
307 int dest_ycoord = std::clamp(event.m_y, mRect.y + 10, mRect.y + mRect.height);
308
309 int h1 = posXToHopNum(dest_xcoord);
310 int b1 = posYToFreqBin(dest_ycoord);
311
312 mbCtrlDown = event.ControlDown() || event.AltDown();
313
314 // Use the hop and bin number to calculate the brush stroke, instead of the mouse coordinates
315 // For mouse coordinate:
316 // (src_xcoord, src_ycoord) -> (dest_xcoord, dest_ycoord)
317
318 const auto &hopSize = mpSpectralData->GetHopSize();
319 if(!mpSpectralData->coordHistory.empty()){
320 int src_xcoord = mpSpectralData->coordHistory.back().first;
321 int src_ycoord = mpSpectralData->coordHistory.back().second;
322
323 int h0 = posXToHopNum(src_xcoord);
324 int b0 = posYToFreqBin(src_ycoord);
325 int wd = mBrushRadius * 2;
326
327 int dh = abs(h1-h0), sh = h0<h1 ? 1 : -1;
328 int db = -abs(b1-b0), sb = b0<b1 ? 1 : -1;
329 int err = dh+db, err2;
330
331 // Line drawing (draw points until the start coordinate reaches the end)
332 while(true){
333 if (h0 == h1 && b0 == b1)
334 break;
335 err2 = err * 2;
336 if (err2 * 2 >= db) { err += db; h0 += sh; }
337 if (err2 * 2 <= dh) { err += dh; b0 += sb; }
338
339
340 int bm = b0;
342 // Correct the y coord (snap to highest energy freq. bin)
343 if(auto *sView = dynamic_cast<SpectrumView*>(pView.get())){
345 h0 * hopSize, hopSize, mFreqSnappingRatio, bm);
346 if(resFreqBin != - 1)
347 bm = resFreqBin;
348 }
349 }
350
351 if(mIsOvertones){
352 // take bm and calculate the highest energy
353 std::vector<int> binsToWork = SpectralDataManager::FindHighestFrequencyBins(wt,
354 h0 * hopSize, hopSize, mOvertonesThreshold, bm);
355 for(auto & bins: binsToWork)
356 drawCircle(h0, bins);
357 }
358
359 drawCircle(h0, bm);
360 } // End of line connecting
361 }
362 mpSpectralData->coordHistory.push_back(std::make_pair(dest_xcoord, dest_ycoord));
363 return RefreshAll;
364}
365
367 (const TrackPanelMouseState &st, AudacityProject *pProject)
368{
370 wxCursor *pCursor = CrosshairCursor();
371 return { tip, pCursor };
372}
373
375 (const TrackPanelMouseEvent &evt, AudacityProject *pProject,
376 wxWindow *)
377{
378 using namespace RefreshCode;
379 mpSpectralData->saveAndClearBuffer();
380 if (mpStateSaver) {
381 mpStateSaver->Commit();
382 mpStateSaver.reset();
383 }
384 if(mbCtrlDown){
385 ProjectHistory::Get( *pProject ).PushState(
386 XO( "Erased selected area" ),
387 XO( "Erased selected area" ) );
388 ProjectHistory::Get( *pProject ).ModifyState(true);
389 }
390 else{
391 ProjectHistory::Get( *pProject ).PushState(
392 XO( "Selected area using Brush Tool" ),
393 XO( "Brush tool selection" ) );
394 ProjectHistory::Get( *pProject ).ModifyState(true);
395 }
396
397 return RefreshNone;
398}
399
401{
402 mpStateSaver.reset();
404}
405
408 const wxRect &rect, unsigned iPass )
409{
410 if ( iPass == TrackArtist::PassTracks ) {
411 auto& dc = context.dc;
412 wxPoint coord;
413 coord.x = mMostRecentX;
414 coord.y = mMostRecentY;
415 dc.SetBrush( *wxTRANSPARENT_BRUSH );
416 dc.SetPen( *wxYELLOW_PEN );
417 dc.DrawCircle(coord, mBrushRadius);
418 }
419}
420
421std::weak_ptr<Track> BrushHandle::FindTrack()
422{
423 auto pView = mpView.lock();
424 if (!pView)
425 return {};
426 else
427 return pView->FindTrack();
428}
429
@ FREQ_SNAP_DISTANCE
Definition: BrushHandle.cpp:52
@ SELECTION_RESIZE_REGION
Definition: BrushHandle.cpp:49
int min(int a, int b)
#define XO(s)
Definition: Internat.h:31
#define A(N)
Definition: ToChars.cpp:62
static Settings & settings()
Definition: TrackInfo.cpp:87
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:185
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:166
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
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
std::weak_ptr< TrackView > mpView
Definition: BrushHandle.h:98
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)
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
std::shared_ptr< Track > FindTrack()
float PositionToValue(float pp) const
Definition: NumberScale.h:154
float ValueToPosition(float val) const
Definition: NumberScale.h:255
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
sampleCount TimeToLongSamples(double t0) const
Convert correctly between an (absolute) time in seconds and a number of samples.
Definition: SampleTrack.cpp:42
static int FindFrequencySnappingBin(WaveTrack *wt, long long startSC, int hopSize, double threshold, int targetFreqBin)
static std::vector< int > FindHighestFrequencyBins(WaveTrack *wt, long long int startSC, int hopSize, double threshold, int targetFreqBin)
Spectrogram settings, either for one track or as defaults.
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:1336
std::shared_ptr< Subclass > Lock(const std::weak_ptr< Subclass > &wTrack)
Definition: Track.h:1601
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:486
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:230
virtual bool IsSpectral() const
Definition: TrackView.cpp:136
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:57
const SpectrogramSettings & GetSpectrogramSettings() const
Definition: WaveTrack.cpp:799
double GetRate() const override
Definition: WaveTrack.cpp:479
void GetSpectrumBounds(float *min, float *max) const
Definition: WaveTrack.cpp:352
double PositionToTime(wxInt64 position, wxInt64 origin=0, bool ignoreFisheye=false) const
Definition: ZoomInfo.cpp:41
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
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)
bool isSpectralSelectionView(const TrackView *pTrackView)
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