From f1f254f974c27d8cc5c295bf3621892f0d92d6c2 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Wed, 8 Jul 2015 02:48:21 -0400 Subject: [PATCH] TrackPanel no longer implements the draw tool... ... also implement ESC key for it --- mac/Audacity.xcodeproj/project.pbxproj | 16 +- src/Makefile.am | 2 + src/TrackPanel.cpp | 402 +-------------- src/TrackPanel.h | 25 +- src/toolbars/ToolsToolBar.cpp | 3 + .../wavetrack/ui/SampleHandle.cpp | 483 ++++++++++++++++++ .../playabletrack/wavetrack/ui/SampleHandle.h | 74 +++ .../wavetrack/ui/WaveTrackUI.cpp | 18 +- src/tracks/ui/TrackUI.cpp | 4 +- win/Projects/Audacity/Audacity.vcxproj | 6 +- .../Audacity/Audacity.vcxproj.filters | 10 +- 11 files changed, 608 insertions(+), 435 deletions(-) create mode 100644 src/tracks/playabletrack/wavetrack/ui/SampleHandle.cpp create mode 100644 src/tracks/playabletrack/wavetrack/ui/SampleHandle.h diff --git a/mac/Audacity.xcodeproj/project.pbxproj b/mac/Audacity.xcodeproj/project.pbxproj index da3032fd2..62d132ff2 100644 --- a/mac/Audacity.xcodeproj/project.pbxproj +++ b/mac/Audacity.xcodeproj/project.pbxproj @@ -1204,6 +1204,7 @@ 28FC1AFB0A47762C00A188AE /* WrappedType.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28FC1AF90A47762C00A188AE /* WrappedType.cpp */; }; 28FE4A080ABF4E960056F5C4 /* mmx_optimized.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28FE4A060ABF4E960056F5C4 /* mmx_optimized.cpp */; }; 28FE4A090ABF4E960056F5C4 /* sse_optimized.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28FE4A070ABF4E960056F5C4 /* sse_optimized.cpp */; }; + 5E000A211EC7B5D500E8FD93 /* SampleHandle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E000A1F1EC7B5D500E8FD93 /* SampleHandle.cpp */; }; 5E02BFF21D1164DF00EB7578 /* Distortion.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E02BFF01D1164DF00EB7578 /* Distortion.cpp */; }; 5E07842E1DEE6B8600CA76EA /* FileException.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E07842C1DEE6B8600CA76EA /* FileException.cpp */; }; 5E0784311DF1E4F400CA76EA /* UserException.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E07842F1DF1E4F400CA76EA /* UserException.cpp */; }; @@ -1220,6 +1221,8 @@ 5E15126E1DB0010C00702E29 /* TrackControls.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E1512681DB0010C00702E29 /* TrackControls.cpp */; }; 5E15126F1DB0010C00702E29 /* TrackUI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E15126A1DB0010C00702E29 /* TrackUI.cpp */; }; 5E1512701DB0010C00702E29 /* TrackVRulerControls.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E15126B1DB0010C00702E29 /* TrackVRulerControls.cpp */; }; + 5E73963B1DAFD82D00BA0A4D /* PopupMenuTable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E7396391DAFD82D00BA0A4D /* PopupMenuTable.cpp */; }; + 5E73963E1DAFD86000BA0A4D /* ZoomHandle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E73963C1DAFD86000BA0A4D /* ZoomHandle.cpp */; }; 5E74D2E31CC4429700D88B0B /* EditCursorOverlay.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E74D2DD1CC4429700D88B0B /* EditCursorOverlay.cpp */; }; 5E74D2E41CC4429700D88B0B /* PlayIndicatorOverlay.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E74D2DF1CC4429700D88B0B /* PlayIndicatorOverlay.cpp */; }; 5E74D2E51CC4429700D88B0B /* Scrubbing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E74D2E11CC4429700D88B0B /* Scrubbing.cpp */; }; @@ -1238,8 +1241,6 @@ 5ED1D0B11CDE560C00471E3C /* BackedPanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5ED1D0AF1CDE560C00471E3C /* BackedPanel.cpp */; }; 5EF17C231D1F0A690090A642 /* ScrubbingToolBar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5EF17C211D1F0A690090A642 /* ScrubbingToolBar.cpp */; }; 5EF958851DEB121800191280 /* InconsistencyException.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5EF958831DEB121800191280 /* InconsistencyException.cpp */; }; - 5E73963B1DAFD82D00BA0A4D /* PopupMenuTable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E7396391DAFD82D00BA0A4D /* PopupMenuTable.cpp */; }; - 5E73963E1DAFD86000BA0A4D /* ZoomHandle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E73963C1DAFD86000BA0A4D /* ZoomHandle.cpp */; }; 8406A93812D0F2510011EA01 /* EQDefaultCurves.xml in Resources */ = {isa = PBXBuildFile; fileRef = 8406A93712D0F2510011EA01 /* EQDefaultCurves.xml */; }; 8484F31413086237002DF7F0 /* DeviceManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8484F31213086237002DF7F0 /* DeviceManager.cpp */; }; AA0084191EA8C6E70070CCE3 /* TracksBehaviorsPrefs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AA0084181EA8C6E70070CCE3 /* TracksBehaviorsPrefs.cpp */; }; @@ -3001,6 +3002,8 @@ 28FE4A060ABF4E960056F5C4 /* mmx_optimized.cpp */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = mmx_optimized.cpp; sourceTree = ""; tabWidth = 3; }; 28FE4A070ABF4E960056F5C4 /* sse_optimized.cpp */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = sse_optimized.cpp; sourceTree = ""; tabWidth = 3; }; 28FEC1B21A12B6FB00FACE48 /* EffectAutomationParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EffectAutomationParameters.h; path = ../include/audacity/EffectAutomationParameters.h; sourceTree = SOURCE_ROOT; }; + 5E000A1F1EC7B5D500E8FD93 /* SampleHandle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SampleHandle.cpp; sourceTree = ""; }; + 5E000A201EC7B5D500E8FD93 /* SampleHandle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SampleHandle.h; sourceTree = ""; }; 5E02BFF01D1164DF00EB7578 /* Distortion.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Distortion.cpp; sourceTree = ""; }; 5E02BFF11D1164DF00EB7578 /* Distortion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Distortion.h; sourceTree = ""; }; 5E07842C1DEE6B8600CA76EA /* FileException.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileException.cpp; sourceTree = ""; }; @@ -3034,6 +3037,9 @@ 5E15126C1DB0010C00702E29 /* TrackVRulerControls.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrackVRulerControls.h; sourceTree = ""; }; 5E4685F81CCA9D84008741F2 /* CommandFunctors.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CommandFunctors.h; sourceTree = ""; }; 5E61EE0C1CBAA6BB0009FCF1 /* MemoryX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MemoryX.h; sourceTree = ""; }; + 5E7396391DAFD82D00BA0A4D /* PopupMenuTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PopupMenuTable.cpp; sourceTree = ""; }; + 5E73963C1DAFD86000BA0A4D /* ZoomHandle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZoomHandle.cpp; sourceTree = ""; }; + 5E73963D1DAFD86000BA0A4D /* ZoomHandle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZoomHandle.h; sourceTree = ""; }; 5E74D2D91CC4427B00D88B0B /* TrackPanelCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrackPanelCell.h; sourceTree = ""; }; 5E74D2DA1CC4427B00D88B0B /* TrackPanelCellIterator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrackPanelCellIterator.h; sourceTree = ""; }; 5E74D2DD1CC4429700D88B0B /* EditCursorOverlay.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EditCursorOverlay.cpp; sourceTree = ""; }; @@ -3074,10 +3080,7 @@ 5EF17C221D1F0A690090A642 /* ScrubbingToolBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrubbingToolBar.h; sourceTree = ""; }; 5EF958831DEB121800191280 /* InconsistencyException.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InconsistencyException.cpp; sourceTree = ""; }; 5EF958841DEB121800191280 /* InconsistencyException.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InconsistencyException.h; sourceTree = ""; }; - 5E7396391DAFD82D00BA0A4D /* PopupMenuTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PopupMenuTable.cpp; sourceTree = ""; }; 5E73963A1DAFD82D00BA0A4D /* PopupMenuTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopupMenuTable.h; sourceTree = ""; }; - 5E73963C1DAFD86000BA0A4D /* ZoomHandle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZoomHandle.cpp; sourceTree = ""; }; - 5E73963D1DAFD86000BA0A4D /* ZoomHandle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZoomHandle.h; sourceTree = ""; }; 82FF184D13CF01A600C1B664 /* dBTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dBTable.cpp; path = sbsms/src/dBTable.cpp; sourceTree = ""; }; 82FF184E13CF01A600C1B664 /* dBTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dBTable.h; path = sbsms/src/dBTable.h; sourceTree = ""; }; 82FF184F13CF01A600C1B664 /* slide.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = slide.cpp; path = sbsms/src/slide.cpp; sourceTree = ""; }; @@ -5784,6 +5787,8 @@ 5EA018221EC7B226001F2996 /* ui */ = { isa = PBXGroup; children = ( + 5E000A1F1EC7B5D500E8FD93 /* SampleHandle.cpp */, + 5E000A201EC7B5D500E8FD93 /* SampleHandle.h */, 5EA018231EC7B226001F2996 /* WaveTrackControls.cpp */, 5EA018241EC7B226001F2996 /* WaveTrackControls.h */, 5EA018251EC7B226001F2996 /* WaveTrackUI.cpp */, @@ -7550,6 +7555,7 @@ 1790B13C09883BFD008A330A /* Fade.cpp in Sources */, 1790B13E09883BFD008A330A /* Invert.cpp in Sources */, 1790B13F09883BFD008A330A /* LadspaEffect.cpp in Sources */, + 5E000A211EC7B5D500E8FD93 /* SampleHandle.cpp in Sources */, 1790B14109883BFD008A330A /* Leveller.cpp in Sources */, 1790B14209883BFD008A330A /* LoadEffects.cpp in Sources */, 5E0784311DF1E4F400CA76EA /* UserException.cpp in Sources */, diff --git a/src/Makefile.am b/src/Makefile.am index 90e78e823..65a86d4f2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -559,6 +559,8 @@ audacity_SOURCES = \ tracks/playabletrack/notetrack/ui/NoteTrackUI.cpp \ tracks/playabletrack/notetrack/ui/NoteTrackVRulerControls.cpp \ tracks/playabletrack/notetrack/ui/NoteTrackVRulerControls.h \ + tracks/playabletrack/wavetrack/ui/SampleHandle.cpp \ + tracks/playabletrack/wavetrack/ui/SampleHandle.h \ tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp \ tracks/playabletrack/wavetrack/ui/WaveTrackControls.h \ tracks/playabletrack/wavetrack/ui/WaveTrackVRulerControls.cpp \ diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index ca8321412..67d9e330e 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -467,7 +467,6 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id, mRedrawAfterStop = false; - mPencilCursor = MakeCursor( wxCURSOR_PENCIL, DrawCursorXpm, 12, 22); mSelectCursor = MakeCursor( wxCURSOR_IBEAM, IBeamCursorXpm, 17, 16); mEnvelopeCursor= MakeCursor( wxCURSOR_ARROW, EnvCursorXpm, 16, 16); mDisabledCursor= MakeCursor( wxCURSOR_NO_ENTRY, DisabledCursorXpm,16, 16); @@ -492,7 +491,6 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id, #endif mArrowCursor = std::make_unique(wxCURSOR_ARROW); - mSmoothCursor = std::make_unique(wxCURSOR_SPRAYCAN); mResizeCursor = std::make_unique(wxCURSOR_SIZENS); mRearrangeCursor = std::make_unique(wxCURSOR_HAND); mAdjustLeftSelectionCursor = std::make_unique(wxCURSOR_POINT_LEFT); @@ -520,10 +518,6 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id, // Timer is started after the window is visible GetProject()->Bind(wxEVT_IDLE, &TrackPanel::OnIdle, this); - //Initialize a member variable pointing to the current - //drawing track. - mDrawingTrack =NULL; - mZoomStart = -1; mZoomEnd = -1; @@ -1157,7 +1151,6 @@ void TrackPanel::HandleInterruptedDrag() IsClosing, IsAdjustingLabel, - IsAdjustingSample, IsRearranging, IsSliding, IsEnveloping, @@ -1298,7 +1291,6 @@ bool TrackPanel::HandleEscapeKey(bool down) } break; case IsVZooming: - //case IsAdjustingSample: break; case IsResizing: mCapturedTrack->SetHeight(mInitialActualHeight); @@ -1762,7 +1754,7 @@ void TrackPanel::SetCursorAndTipWhenSelectTool( Track * t, /// In this method we know what tool we are using, /// so set the cursor accordingly. void TrackPanel::SetCursorAndTipByTool( int tool, - const wxMouseEvent & event, wxString& ) + const wxMouseEvent &, wxString& ) { bool unsafe = IsUnsafe(); @@ -1777,12 +1769,6 @@ void TrackPanel::SetCursorAndTipByTool( int tool, case slideTool: SetCursor(unsafe ? *mDisabledCursor : *mSlideCursor); break; - case drawTool: - if (unsafe) - SetCursor(*mDisabledCursor); - else - SetCursor(event.AltDown()? *mSmoothCursor : *mPencilCursor); - break; } // doesn't actually change the tip itself, but it could (should?) do at some // future date. @@ -4407,330 +4393,6 @@ void TrackPanel::HandleWaveTrackVZoom } } -namespace { -// Is the sample horizontally nearest to the cursor sufficiently separated from -// its neighbors that the pencil tool should be allowed to drag it? -bool SampleResolutionTest(const ViewInfo &viewInfo, const WaveTrack *wt, double time, double rate, int width) -{ - // Require more than 3 pixels per sample - // Round to an exact sample time - const double adjustedTime = wt->LongSamplesToTime(wt->TimeToLongSamples(time)); - const wxInt64 xx = std::max(wxInt64(0), viewInfo.TimeToPosition(adjustedTime)); - ZoomInfo::Intervals intervals; - viewInfo.FindIntervals(rate, intervals, width); - ZoomInfo::Intervals::const_iterator it = intervals.begin(), end = intervals.end(), prev; - wxASSERT(it != end && it->position == 0); - do - prev = it++; - while (it != end && it->position <= xx); - const double threshold = 3 * rate; // three times as many pixels per second, as samples - return prev->averageZoom > threshold; -} -} - -/// Determines if we can edit samples in a wave track. -/// Also pops up warning messages in certain cases where we can't. -/// @return true if we can edit the samples, false otherwise. -bool TrackPanel::IsSampleEditingPossible( wxMouseEvent &event, const WaveTrack * wt ) -{ - //Exit if it's not waveform display - //Get out of here if we shouldn't be drawing right now: - //If we aren't displaying the waveform, Display a message dialog - const int display = wt->GetDisplay(); - - if (WaveTrack::Waveform != display) - { - wxMessageBox(_("To use Draw, choose 'Waveform' or 'Waveform (dB)' in the Track Dropdown Menu."), _("Draw Tool")); - return false; - } - - bool showPoints; - { - const auto foundCell = FindCell(event.m_x, event.m_y); - if ( foundCell.type != CellType::Track ) - return false; - const double rate = wt->GetRate(); - const double time = mViewInfo->PositionToTime(event.m_x, foundCell.rect.x); - int width; - GetTracksUsableArea(&width, NULL); - showPoints = SampleResolutionTest(*mViewInfo, wt, time, rate, width); - } - - //If we aren't zoomed in far enough, show a message dialog. - if(!showPoints) - { - wxMessageBox(_("To use Draw, zoom in further until you can see the individual samples."), _("Draw Tool")); - return false; - } - return true; -} - -float TrackPanel::FindSampleEditingLevel(wxMouseEvent &event, double dBRange, double t0) -{ - // Calculate where the mouse is located vertically (between +/- 1) - float zoomMin, zoomMax; - mDrawingTrack->GetDisplayBounds(&zoomMin, &zoomMax); - - const int y = event.m_y - mDrawingTrackTop; - const int height = mDrawingTrack->GetHeight() - (kTopMargin + kBottomMargin); - const bool dB = !mDrawingTrack->GetWaveformSettings().isLinear(); - float newLevel = - ::ValueOfPixel(y, height, false, dB, dBRange, zoomMin, zoomMax); - - //Take the envelope into account - Envelope *const env = mDrawingTrack->GetEnvelopeAtX(event.m_x); - if (env) - { - // Calculate sample as it would be rendered, so quantize time - double envValue = env->GetValue( t0, 1.0 / mDrawingTrack->GetRate() ); - if (envValue > 0) - newLevel /= envValue; - else - newLevel = 0; - - //Make sure the NEW level is between +/-1 - newLevel = std::max(-1.0f, std::min(1.0f, newLevel)); - } - - return newLevel; -} - -/// We're in a track view and zoomed enough to see the samples. -/// Someone has just clicked the mouse. What do we do? -void TrackPanel::HandleSampleEditingClick( wxMouseEvent & event ) -{ - //Get the track the mouse is over, and save it away for future events - mDrawingTrack = NULL; - const auto foundCell = FindCell(event.m_x, event.m_y); - auto &t = foundCell.pTrack; - auto &rect = foundCell.rect; - - if (!t || (t->GetKind() != Track::Wave) - || foundCell.type != CellType::Track) - return; - const auto wt = static_cast(t); - if( !IsSampleEditingPossible( event, wt ) ) - return; - - /// \todo Should mCapturedTrack take the place of mDrawingTrack?? - mDrawingTrack = wt; - mDrawingTrackTop = rect.y + kTopMargin; - - //If we are still around, we are drawing in earnest. Set some member data structures up: - //First, calculate the starting sample. To get this, we need the time - double t0 = mViewInfo->PositionToTime(event.m_x, GetLeftOffset()); - - //convert t0 to samples - mDrawingStartSample = mDrawingTrack->TimeToLongSamples(t0); - // quantize - t0 = mDrawingTrack->LongSamplesToTime(mDrawingStartSample); - - //Determine how drawing should occur. If alt is down, - //do a smoothing, instead of redrawing. - if( event.m_altDown ) - { - //************************************************* - //*** ALT-DOWN-CLICK (SAMPLE SMOOTHING) *** - //************************************************* - // - // Smoothing works like this: There is a smoothing kernel radius constant that - // determines how wide the averaging window is. Plus, there is a smoothing brush radius, - // which determines how many pixels wide around the selected pixel this smoothing is applied. - // - // Samples will be replaced by a mixture of the original points and the smoothed points, - // with a triangular mixing probability whose value at the center point is - // SMOOTHING_PROPORTION_MAX and at the far bounds is SMOOTHING_PROPORTION_MIN - - //Get the region of samples around the selected point - size_t sampleRegionSize = 1 + 2 * (SMOOTHING_KERNEL_RADIUS + SMOOTHING_BRUSH_RADIUS); - Floats sampleRegion{ sampleRegionSize }; - Floats newSampleRegion{ 1 + 2 * (size_t)SMOOTHING_BRUSH_RADIUS }; - - //Get a sample from the track to do some tricks on. - mDrawingTrack->Get((samplePtr)sampleRegion.get(), floatSample, - mDrawingStartSample - SMOOTHING_KERNEL_RADIUS - SMOOTHING_BRUSH_RADIUS, - sampleRegionSize); - //Go through each point of the smoothing brush and apply a smoothing operation. - for(auto j = -SMOOTHING_BRUSH_RADIUS; j <= SMOOTHING_BRUSH_RADIUS; j++){ - float sumOfSamples = 0; - for (auto i = -SMOOTHING_KERNEL_RADIUS; i <= SMOOTHING_KERNEL_RADIUS; i++){ - //Go through each point of the smoothing kernel and find the average - - //The average is a weighted average, scaled by a weighting kernel that is simply triangular - // A triangular kernel across N items, with a radius of R ( 2 R + 1 points), if the farthest: - // points have a probability of a, the entire triangle has total probability of (R + 1)^2. - // For sample number i and middle brush sample M, (R + 1 - abs(M-i))/ ((R+1)^2) gives a - // legal distribution whose total probability is 1. - // - // - // weighting factor value - sumOfSamples += (SMOOTHING_KERNEL_RADIUS + 1 - abs(i)) * sampleRegion[i + j + SMOOTHING_KERNEL_RADIUS + SMOOTHING_BRUSH_RADIUS]; - - } - newSampleRegion[j + SMOOTHING_BRUSH_RADIUS] = sumOfSamples/((SMOOTHING_KERNEL_RADIUS + 1) *(SMOOTHING_KERNEL_RADIUS + 1) ); - } - - - // Now that the NEW sample levels are determined, go through each and mix it appropriately - // with the original point, according to a 2-part linear function whose center has probability - // SMOOTHING_PROPORTION_MAX and extends out SMOOTHING_BRUSH_RADIUS, at which the probability is - // SMOOTHING_PROPORTION_MIN. _MIN and _MAX specify how much of the smoothed curve make it through. - - float prob; - - for(auto j = -SMOOTHING_BRUSH_RADIUS; j <= SMOOTHING_BRUSH_RADIUS; j++){ - - prob = SMOOTHING_PROPORTION_MAX - (float)abs(j)/SMOOTHING_BRUSH_RADIUS * (SMOOTHING_PROPORTION_MAX - SMOOTHING_PROPORTION_MIN); - - newSampleRegion[j+SMOOTHING_BRUSH_RADIUS] = - newSampleRegion[j + SMOOTHING_BRUSH_RADIUS] * prob + - sampleRegion[SMOOTHING_BRUSH_RADIUS + SMOOTHING_KERNEL_RADIUS + j] * (1 - prob); - } - //Set the sample to the point of the mouse event - mDrawingTrack->Set((samplePtr)newSampleRegion.get(), floatSample, mDrawingStartSample - SMOOTHING_BRUSH_RADIUS, 1 + 2 * SMOOTHING_BRUSH_RADIUS); - - mDrawingLastDragSampleValue = 0; - } - else - { - //************************************************* - //*** PLAIN DOWN-CLICK (NORMAL DRAWING) *** - //************************************************* - - SetCapturedTrack(t, IsAdjustingSample); - - //Otherwise (e.g., the alt button is not down) do normal redrawing, based on the mouse position. - const float newLevel = FindSampleEditingLevel - (event, mDrawingTrack->GetWaveformSettings().dBRange, t0); - - //Set the sample to the point of the mouse event - mDrawingTrack->Set((samplePtr)&newLevel, floatSample, mDrawingStartSample, 1); - - mDrawingLastDragSampleValue = newLevel; - } - - //Set the member data structures for drawing - mDrawingLastDragSample=mDrawingStartSample; - - //Redraw the region of the selected track - RefreshTrack(mDrawingTrack); -} - -void TrackPanel::HandleSampleEditingDrag( wxMouseEvent & event ) -{ - //************************************************* - //*** DRAG-DRAWING *** - //************************************************* - - //The following will happen on a drag or a down-click. - // The point should get re-drawn at the location of the mouse. - //Exit if the mDrawingTrack is null. - if( mDrawingTrack == NULL) - return; - - //Exit dragging if the alt key is down--Don't allow left-right dragging for smoothing operation - if (mMouseCapture != IsAdjustingSample) - return; - - sampleCount s0; //declare this for use below. It designates the sample number which to draw. - - // Figure out what time the click was at - //Find the point that we want to redraw at. If the control button is down, - //adjust only the originally clicked-on sample - - if( event.m_controlDown) { - //************************************************* - //*** CTRL-DOWN (Hold Initial Sample Constant *** - //************************************************* - - s0 = mDrawingStartSample; - } - else { - //************************************************* - //*** Normal CLICK-drag (Normal drawing) *** - //************************************************* - - //Otherwise, adjust the sample you are dragging over right now. - //convert this to samples - const double t = mViewInfo->PositionToTime(event.m_x, GetLeftOffset()); - s0 = mDrawingTrack->TimeToLongSamples(t); - } - - const double t0 = mDrawingTrack->LongSamplesToTime(s0); - - //Otherwise, do normal redrawing, based on the mouse position. - // Calculate where the mouse is located vertically (between +/- 1) - - const float newLevel = FindSampleEditingLevel - (event, mDrawingTrack->GetWaveformSettings().dBRange, t0); - - //Now, redraw all samples between current and last redrawn sample, inclusive - //Go from the smaller to larger sample. - const auto start = std::min( s0, mDrawingLastDragSample); - const auto end = std::max( s0, mDrawingLastDragSample); - // Few enough samples to be drawn individually on screen will not - // overflow size_t: - const auto size = ( end - start + 1 ).as_size_t(); - if (size == 1) { - mDrawingTrack->Set((samplePtr)&newLevel, floatSample, start, size); - } - else { - std::vector values(size); - for (auto i = start; i <= end; ++i) { - //This interpolates each sample linearly: - // i - start will not overflow size_t either: - values[( i - start ).as_size_t()] = - mDrawingLastDragSampleValue + (newLevel - mDrawingLastDragSampleValue) * - (i - mDrawingLastDragSample).as_float() / - (s0 - mDrawingLastDragSample).as_float(); - } - mDrawingTrack->Set((samplePtr)&values[0], floatSample, start, size); - } - - //Update the member data structures. - mDrawingLastDragSample=s0; - mDrawingLastDragSampleValue = newLevel; - - //Redraw the region of the selected track - RefreshTrack(mDrawingTrack); -} - -void TrackPanel::HandleSampleEditingButtonUp( wxMouseEvent & WXUNUSED(event)) -{ - //************************************************* - //*** UP-CLICK (Finish drawing) *** - //************************************************* - SetCapturedTrack( NULL ); - //On up-click, send the state to the undo stack - mDrawingTrack=NULL; //Set this to NULL so it will catch improper drag events. - MakeParentPushState(_("Moved Samples"), - _("Sample Edit"), - UndoPush::CONSOLIDATE | UndoPush::AUTOSAVE); -} - - -/// This handles adjusting individual samples by hand using the draw tool(s) -/// -/// There are several member data structure for handling drawing: -/// - mDrawingTrack: keeps track of which track you clicked down on, so drawing doesn't -/// jump to a NEW track -/// - mDrawingTrackTop: The top position of the drawing track--makes drawing easier. -/// - mDrawingStartSample: The sample you clicked down on, so that you can hold it steady -/// - mDrawingLastDragSample: When drag-drawing, this keeps track of the last sample you dragged over, -/// so it can smoothly redraw samples that got skipped over -/// - mDrawingLastDragSampleValue: The value of the last -void TrackPanel::HandleSampleEditing(wxMouseEvent & event) -{ - if (event.LeftDown() ) { - HandleSampleEditingClick( event); - } else if (mDrawingTrack && event.Dragging()) { - HandleSampleEditingDrag( event ); - } else if(mDrawingTrack && event.ButtonUp()) { - HandleSampleEditingButtonUp( event ); - } -} - - // This is for when a given track gets the x. void TrackPanel::HandleClosing(wxMouseEvent & event) { @@ -6885,10 +6547,6 @@ void TrackPanel::HandleTrackSpecificMouseEvent(wxMouseEvent & event) if (!unsafe) HandleSlide(event); break; - case drawTool: - if (!unsafe) - HandleSampleEditing(event); - break; } } } @@ -6958,8 +6616,6 @@ int TrackPanel::DetermineToolToUse( ToolsToolBar * pTtb, const wxMouseEvent & ev currentTool = envelopeTool; } else if( HitTestSlide( pTrack, rect, event )){ currentTool = slideTool; - } else if( HitTestSamples( pTrack, rect, event )){ - currentTool = drawTool; } //Use the false argument since in multimode we don't @@ -7069,62 +6725,6 @@ bool TrackPanel::HitTestEnvelope(Track *track, const wxRect &rect, const wxMouse return( distance < yTolerance ); } -/// method that tells us if the mouse event landed on an -/// editable sample -bool TrackPanel::HitTestSamples(Track *track, const wxRect &rect, const wxMouseEvent & event) -{ - wxASSERT(track); - if( track->GetKind() != Track::Wave ) - return false; - - WaveTrack *wavetrack = (WaveTrack *)track; - //Get rate in order to calculate the critical zoom threshold - double rate = wavetrack->GetRate(); - const double dBRange = wavetrack->GetWaveformSettings().dBRange; - - const int displayType = wavetrack->GetDisplay(); - if (WaveTrack::Waveform != displayType) - return false; // Not a wave, so return. - const bool dB = !wavetrack->GetWaveformSettings().isLinear(); - - const double tt = mViewInfo->PositionToTime(event.m_x, rect.x); - int width; - GetTracksUsableArea(&width, NULL); - if (!SampleResolutionTest(*mViewInfo, wavetrack, tt, rate, width)) - return false; - - // Just get one sample. - float oneSample; - auto s0 = (sampleCount)(tt * rate + 0.5); - if ( !wavetrack->Get( - (samplePtr)&oneSample, floatSample, s0, 1, fillZero, - // Do not propagate exception but return a failure value - false)) - return false; - - // Get y distance of envelope point from center line (in pixels). - float zoomMin, zoomMax; - - wavetrack->GetDisplayBounds(&zoomMin, &zoomMax); - - double envValue = 1.0; - Envelope* env = wavetrack->GetEnvelopeAtX(event.GetX()); - if (env) - // Calculate sample as it would be rendered, so quantize time - envValue = env->GetValue( tt, 1.0 / wavetrack->GetRate() ); - - int yValue = GetWaveYPos( oneSample * envValue, - zoomMin, zoomMax, - rect.height, dB, true, dBRange, false) + rect.y; - - // Get y position of mouse (in pixels) - int yMouse = event.m_y; - - // Perhaps yTolerance should be put into preferences? - const int yTolerance = 10; // More tolerance on samples than on envelope. - return( abs( yValue - yMouse ) < yTolerance ); -} - /// method that tells us if the mouse event landed on a /// time-slider that allows us to time shift the sequence. bool TrackPanel::HitTestSlide(Track * WXUNUSED(track), const wxRect &rect, const wxMouseEvent & event) diff --git a/src/TrackPanel.h b/src/TrackPanel.h index 9c7544c0c..89747e0f8 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -398,7 +398,6 @@ class AUDACITY_DLL_API TrackPanel final : public OverlayPanel { // Working out where to dispatch the event to. virtual int DetermineToolToUse( ToolsToolBar * pTtb, const wxMouseEvent & event); virtual bool HitTestEnvelope(Track *track, const wxRect &rect, const wxMouseEvent & event); - virtual bool HitTestSamples(Track *track, const wxRect &rect, const wxMouseEvent & event); virtual bool HitTestSlide(Track *track, const wxRect &rect, const wxMouseEvent & event); #ifdef USE_MIDI // data for NoteTrack interactive stretch operations: @@ -529,14 +528,6 @@ protected: WaveTrack *track, bool shiftDown, bool rightUp, bool fixedMousePoint); - // Handle sample editing using the 'draw' tool. - virtual bool IsSampleEditingPossible( wxMouseEvent & event, const WaveTrack * t ); - virtual void HandleSampleEditing(wxMouseEvent & event); - float FindSampleEditingLevel(wxMouseEvent &event, double dBRange, double t0); - virtual void HandleSampleEditingClick( wxMouseEvent & event ); - virtual void HandleSampleEditingDrag( wxMouseEvent & event ); - virtual void HandleSampleEditingButtonUp( wxMouseEvent & event ); - // MM: Handle mouse wheel rotation virtual void HandleWheelRotation(wxMouseEvent & event); virtual void HandleWheelRotationInVRuler @@ -823,12 +814,6 @@ public: protected: - WaveTrack * mDrawingTrack; // Keeps track of which track you are drawing on between events cf. HandleDraw() - int mDrawingTrackTop; // Keeps track of the top position of the drawing track. - sampleCount mDrawingStartSample; // sample of last click-down - sampleCount mDrawingLastDragSample; // sample of last drag-over - float mDrawingLastDragSampleValue; // value of last drag-over - #ifdef EXPERIMENTAL_SPECTRAL_EDITING void HandleCenterFrequencyCursor (bool shiftDown, wxString &tip, const wxCursor ** ppCursor); @@ -880,7 +865,6 @@ public: IsSelecting, IsAdjustingLabel, IsSelectingLabelText, - IsAdjustingSample, IsResizing, IsResizingBetweenLinkedTracks, IsResizingBelowLinkedTracks, @@ -913,10 +897,10 @@ protected: int mRearrangeCount; std::unique_ptr - mArrowCursor, mPencilCursor, mSelectCursor, + mArrowCursor, mSelectCursor, mResizeCursor, mSlideCursor, mEnvelopeCursor, // doubles as the center frequency cursor // for spectral selection - mSmoothCursor, mZoomInCursor, mZoomOutCursor, + mZoomInCursor, mZoomOutCursor, mRearrangeCursor, mDisabledCursor, mAdjustLeftSelectionCursor, mAdjustRightSelectionCursor; #ifdef EXPERIMENTAL_SPECTRAL_EDITING @@ -1026,10 +1010,5 @@ enum : int { // for vertical selection adjusting) #define SELECTION_RESIZE_REGION 3 -#define SMOOTHING_KERNEL_RADIUS 3 -#define SMOOTHING_BRUSH_RADIUS 5 -#define SMOOTHING_PROPORTION_MAX 0.7 -#define SMOOTHING_PROPORTION_MIN 0.0 - #endif diff --git a/src/toolbars/ToolsToolBar.cpp b/src/toolbars/ToolsToolBar.cpp index 660168dcc..fc91cee8e 100644 --- a/src/toolbars/ToolsToolBar.cpp +++ b/src/toolbars/ToolsToolBar.cpp @@ -88,6 +88,9 @@ ToolsToolBar::ToolsToolBar() mMessageOfTool[selectTool] = _("Click and drag to select audio"); mMessageOfTool[envelopeTool] = _("Click and drag to edit the amplitude envelope"); + + // TODO: message should also mention the brush. Describing the modifier key + // (alt, or other) varies with operating system. mMessageOfTool[drawTool] = _("Click and drag to edit the samples"); // TODO: Why not mention middle click to zoom normal on Windows too? diff --git a/src/tracks/playabletrack/wavetrack/ui/SampleHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/SampleHandle.cpp new file mode 100644 index 000000000..f364ae635 --- /dev/null +++ b/src/tracks/playabletrack/wavetrack/ui/SampleHandle.cpp @@ -0,0 +1,483 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +SampleHandle.cpp + +Paul Licameli split from TrackPanel.cpp + +**********************************************************************/ + +#include "../../../../Audacity.h" +#include "SampleHandle.h" + +#include +#include "../../../../MemoryX.h" +#include + +#include "../../../../Envelope.h" +#include "../../../../HitTestResult.h" +#include "../../../../prefs/WaveformSettings.h" +#include "../../../../Project.h" +#include "../../../../RefreshCode.h" +#include "../../../../toolbars/ToolsToolBar.h" +#include "../../../../TrackArtist.h" +#include "../../../../TrackPanelMouseEvent.h" +#include "../../../../UndoManager.h" +#include "../../../../ViewInfo.h" +#include "../../../../WaveTrack.h" +#include "../../../../../images/Cursors.h" + + +static const int SMOOTHING_KERNEL_RADIUS = 3; +static const int SMOOTHING_BRUSH_RADIUS = 5; +static const double SMOOTHING_PROPORTION_MAX = 0.7; +static const double SMOOTHING_PROPORTION_MIN = 0.0; + +SampleHandle::SampleHandle() +{ +} + +SampleHandle &SampleHandle::Instance() +{ + static SampleHandle instance; + return instance; +} + +HitTestPreview SampleHandle::HitPreview +(const wxMouseEvent &event, const AudacityProject *pProject, bool unsafe) +{ + static auto disabledCursor = + ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16); + static wxCursor smoothCursor{ wxCURSOR_SPRAYCAN }; + static auto pencilCursor = + ::MakeCursor(wxCURSOR_PENCIL, DrawCursorXpm, 12, 22); + const ToolsToolBar *const ttb = pProject->GetToolsToolBar(); + return { + ttb->GetMessageForTool(drawTool), + (unsafe + ? &*disabledCursor + : (event.AltDown() + ? &smoothCursor + : &*pencilCursor)) + }; +} + +HitTestResult SampleHandle::HitAnywhere +(const wxMouseEvent &event, const AudacityProject *pProject) +{ + const bool unsafe = pProject->IsAudioActive(); + return { + HitPreview(event, pProject, unsafe), + (unsafe + ? NULL + : &Instance()) + }; +} + +namespace { + inline double adjustTime(const WaveTrack *wt, double time) + { + // Round to an exact sample time + return wt->LongSamplesToTime(wt->TimeToLongSamples(time)); + } + + // Is the sample horizontally nearest to the cursor sufficiently separated + // from its neighbors that the pencil tool should be allowed to drag it? + bool SampleResolutionTest + ( const ViewInfo &viewInfo, const WaveTrack *wt, double time, int width ) + { + // Require more than 3 pixels per sample + const wxInt64 xx = std::max(wxInt64(0), viewInfo.TimeToPosition(time)); + ZoomInfo::Intervals intervals; + const double rate = wt->GetRate(); + viewInfo.FindIntervals(rate, intervals, width); + ZoomInfo::Intervals::const_iterator it = intervals.begin(), + end = intervals.end(), prev; + wxASSERT(it != end && it->position == 0); + do + prev = it++; + while (it != end && it->position <= xx); + const double threshold = 3 * rate; + // three times as many pixels per second, as samples + return prev->averageZoom > threshold; + } +} + +HitTestResult SampleHandle::HitTest +(const wxMouseEvent &event, const wxRect &rect, + const AudacityProject *pProject, Track *pTrack) +{ + const ViewInfo &viewInfo = pProject->GetViewInfo(); + + /// method that tells us if the mouse event landed on an + /// editable sample + if (pTrack->GetKind() != Track::Wave) + return {}; + + WaveTrack *wavetrack = static_cast(pTrack); + + const int displayType = wavetrack->GetDisplay(); + if (WaveTrack::Waveform != displayType) + return {}; // Not a wave, so return. + + const double tt = + adjustTime(wavetrack, viewInfo.PositionToTime(event.m_x, rect.x)); + if (!SampleResolutionTest(viewInfo, wavetrack, tt, rect.width)) + return {}; + + // Just get one sample. + float oneSample; + const double rate = wavetrack->GetRate(); + const auto s0 = (sampleCount)(tt * rate + 0.5); + if (! wavetrack->Get((samplePtr)&oneSample, floatSample, s0, 1, fillZero, + // Do not propagate exception but return a failure value + false) ) + return {}; + + // Get y distance of envelope point from center line (in pixels). + float zoomMin, zoomMax; + + wavetrack->GetDisplayBounds(&zoomMin, &zoomMax); + + double envValue = 1.0; + Envelope* env = wavetrack->GetEnvelopeAtX(event.GetX()); + if (env) + // Calculate sample as it would be rendered, so quantize time + envValue = env->GetValue( tt, 1.0 / wavetrack->GetRate() ); + + const bool dB = !wavetrack->GetWaveformSettings().isLinear(); + int yValue = GetWaveYPos(oneSample * envValue, + zoomMin, zoomMax, + rect.height, dB, true, + wavetrack->GetWaveformSettings().dBRange, false) + rect.y; + + // Get y position of mouse (in pixels) + int yMouse = event.m_y; + + // Perhaps yTolerance should be put into preferences? + const int yTolerance = 10; // More tolerance on samples than on envelope. + if (abs(yValue - yMouse) >= yTolerance) + return {}; + + return HitAnywhere(event, pProject); +} + +SampleHandle::~SampleHandle() +{ +} + +namespace { + /// Determines if we can edit samples in a wave track. + /// Also pops up warning messages in certain cases where we can't. + /// @return true if we can edit the samples, false otherwise. + bool IsSampleEditingPossible + (const wxMouseEvent &event, + const wxRect &rect, const ViewInfo &viewInfo, Track *pTrack, int width) + { + if (pTrack->GetKind() != Track::Wave) + return false; + WaveTrack *wt = static_cast(pTrack); + + //Get out of here if we shouldn't be drawing right now: + //If we aren't displaying the waveform, Display a message dialog + const int display = wt->GetDisplay(); + if (WaveTrack::Waveform != display) + { + wxMessageBox(_( +"To use Draw, choose 'Waveform' or 'Waveform (dB)' in the Track Dropdown Menu."), + _("Draw Tool")); + return false; + } + + //If we aren't zoomed in far enough, show a message dialog. + const double time = adjustTime(wt, viewInfo.PositionToTime(event.m_x, rect.x)); + if (!SampleResolutionTest(viewInfo, wt, time, width)) + { + wxMessageBox(_( +"To use Draw, zoom in further until you can see the individual samples."), + _("Draw Tool")); + return false; + } + return true; + } +} + +UIHandle::Result SampleHandle::Click +(const TrackPanelMouseEvent &evt, AudacityProject *pProject) +{ + const wxMouseEvent &event = evt.event; + const wxRect &rect = evt.rect; + const ViewInfo &viewInfo = pProject->GetViewInfo(); + Track *const pTrack = static_cast(evt.pCell); + + using namespace RefreshCode; + + /// Someone has just clicked the mouse. What do we do? + const bool unsafe = pProject->IsAudioActive(); + if (unsafe) + return Cancelled; + if (!IsSampleEditingPossible + (event, rect, viewInfo, pTrack, rect.width)) + return Cancelled; + + /// We're in a track view and zoomed enough to see the samples. + mClickedTrack = static_cast(pTrack); + mRect = rect; + + //If we are still around, we are drawing in earnest. Set some member data structures up: + //First, calculate the starting sample. To get this, we need the time + const double t0 = + adjustTime(mClickedTrack, viewInfo.PositionToTime(event.m_x, rect.x)); + + //convert t0 to samples + mClickedStartSample = mClickedTrack->TimeToLongSamples(t0); + + //Determine how drawing should occur. If alt is down, + //do a smoothing, instead of redrawing. + if (event.m_altDown) + { + mAltKey = true; + //************************************************* + //*** ALT-DOWN-CLICK (SAMPLE SMOOTHING) *** + //************************************************* + // + // Smoothing works like this: There is a smoothing kernel radius constant that + // determines how wide the averaging window is. Plus, there is a smoothing brush radius, + // which determines how many pixels wide around the selected pixel this smoothing is applied. + // + // Samples will be replaced by a mixture of the original points and the smoothed points, + // with a triangular mixing probability whose value at the center point is + // SMOOTHING_PROPORTION_MAX and at the far bounds is SMOOTHING_PROPORTION_MIN + + //Get the region of samples around the selected point + size_t sampleRegionSize = 1 + 2 * (SMOOTHING_KERNEL_RADIUS + SMOOTHING_BRUSH_RADIUS); + Floats sampleRegion{ sampleRegionSize }; + Floats newSampleRegion{ 1 + 2 * (size_t)SMOOTHING_BRUSH_RADIUS }; + + //Get a sample from the track to do some tricks on. + mClickedTrack->Get((samplePtr)sampleRegion.get(), floatSample, + mClickedStartSample - SMOOTHING_KERNEL_RADIUS - SMOOTHING_BRUSH_RADIUS, + sampleRegionSize); + + //Go through each point of the smoothing brush and apply a smoothing operation. + for (auto jj = -SMOOTHING_BRUSH_RADIUS; jj <= SMOOTHING_BRUSH_RADIUS; ++jj) { + float sumOfSamples = 0; + for (auto ii = -SMOOTHING_KERNEL_RADIUS; ii <= SMOOTHING_KERNEL_RADIUS; ++ii) { + //Go through each point of the smoothing kernel and find the average + + //The average is a weighted average, scaled by a weighting kernel that is simply triangular + // A triangular kernel across N items, with a radius of R ( 2 R + 1 points), if the farthest: + // points have a probability of a, the entire triangle has total probability of (R + 1)^2. + // For sample number ii and middle brush sample M, (R + 1 - abs(M-ii))/ ((R+1)^2) gives a + // legal distribution whose total probability is 1. + // + // + // weighting factor value + sumOfSamples += + (SMOOTHING_KERNEL_RADIUS + 1 - abs(ii)) * + sampleRegion[ii + jj + SMOOTHING_KERNEL_RADIUS + SMOOTHING_BRUSH_RADIUS]; + + } + newSampleRegion[jj + SMOOTHING_BRUSH_RADIUS] = + sumOfSamples / + ((SMOOTHING_KERNEL_RADIUS + 1) *(SMOOTHING_KERNEL_RADIUS + 1)); + } + + + // Now that the NEW sample levels are determined, go through each and mix it appropriately + // with the original point, according to a 2-part linear function whose center has probability + // SMOOTHING_PROPORTION_MAX and extends out SMOOTHING_BRUSH_RADIUS, at which the probability is + // SMOOTHING_PROPORTION_MIN. _MIN and _MAX specify how much of the smoothed curve make it through. + + float prob; + + for (auto jj = -SMOOTHING_BRUSH_RADIUS; jj <= SMOOTHING_BRUSH_RADIUS; ++jj) { + + prob = + SMOOTHING_PROPORTION_MAX - + (float)abs(jj) / SMOOTHING_BRUSH_RADIUS * + (SMOOTHING_PROPORTION_MAX - SMOOTHING_PROPORTION_MIN); + + newSampleRegion[jj + SMOOTHING_BRUSH_RADIUS] = + newSampleRegion[jj + SMOOTHING_BRUSH_RADIUS] * prob + + sampleRegion[SMOOTHING_BRUSH_RADIUS + SMOOTHING_KERNEL_RADIUS + jj] * + (1 - prob); + } + //Set the sample to the point of the mouse event + mClickedTrack->Set((samplePtr)newSampleRegion.get(), floatSample, + mClickedStartSample - SMOOTHING_BRUSH_RADIUS, 1 + 2 * SMOOTHING_BRUSH_RADIUS); + + // mLastDragSampleValue will not be used + } + else + { + mAltKey = false; + //************************************************* + //*** PLAIN DOWN-CLICK (NORMAL DRAWING) *** + //************************************************* + + //Otherwise (e.g., the alt button is not down) do normal redrawing, based on the mouse position. + const float newLevel = FindSampleEditingLevel(event, viewInfo, t0); + + //Set the sample to the point of the mouse event + mClickedTrack->Set((samplePtr)&newLevel, floatSample, mClickedStartSample, 1); + + mLastDragSampleValue = newLevel; + } + + //Set the member data structures for drawing + mLastDragSample = mClickedStartSample; + + // Sample data changed on either branch, so refresh the track display. + return RefreshCell; +} + +UIHandle::Result SampleHandle::Drag +(const TrackPanelMouseEvent &evt, AudacityProject *pProject) +{ + using namespace RefreshCode; + const wxMouseEvent &event = evt.event; + const ViewInfo &viewInfo = pProject->GetViewInfo(); + + const bool unsafe = pProject->IsAudioActive(); + if (unsafe) { + this->Cancel(pProject); + return RefreshCell | Cancelled; + } + + //************************************************* + //*** DRAG-DRAWING *** + //************************************************* + + //No dragging effects if the alt key is down-- + //Don't allow left-right dragging for smoothing operation + if (mAltKey) + return RefreshNone; + + sampleCount s0; //declare this for use below. It designates which sample number to draw. + + // Figure out what time the click was at + //Find the point that we want to redraw at. If the control button is down, + //adjust only the originally clicked-on sample + + if (event.m_controlDown) { + //************************************************* + //*** CTRL-DOWN (Hold Initial Sample Constant *** + //************************************************* + + s0 = mClickedStartSample; + } + else { + //************************************************* + //*** Normal CLICK-drag (Normal drawing) *** + //************************************************* + + //Otherwise, adjust the sample you are dragging over right now. + //convert this to samples + const double tt = viewInfo.PositionToTime(event.m_x, mRect.x); + s0 = mClickedTrack->TimeToLongSamples(tt); + } + + const double t0 = mClickedTrack->LongSamplesToTime(s0); + + // Do redrawing, based on the mouse position. + // Calculate where the mouse is located vertically (between +/- 1) + + const float newLevel = FindSampleEditingLevel(event, viewInfo, t0); + + //Now, redraw all samples between current and last redrawn sample, inclusive + //Go from the smaller to larger sample. + const auto start = std::min(s0, mLastDragSample); + const auto end = std::max(s0, mLastDragSample); + // Few enough samples to be drawn individually on screen will not + // overflow size_t: + const auto size = ( end - start + 1 ).as_size_t(); + if (size == 1) { + mClickedTrack->Set((samplePtr)&newLevel, floatSample, start, size); + } + else { + std::vector values(size); + for (auto ii = start; ii <= end; ++ii) { + //This interpolates each sample linearly: + // i - start will not overflow size_t either: + values[( ii - start ).as_size_t()] = + mLastDragSampleValue + (newLevel - mLastDragSampleValue) * + (ii - mLastDragSample).as_float() / + (s0 - mLastDragSample).as_float(); + } + mClickedTrack->Set((samplePtr)&values[0], floatSample, start, size); + } + + //Update the member data structures. + mLastDragSample = s0; + mLastDragSampleValue = newLevel; + + return RefreshCell; +} + +HitTestPreview SampleHandle::Preview +(const TrackPanelMouseEvent &evt, const AudacityProject *pProject) +{ + return HitPreview(evt.event, pProject, false); +} + +UIHandle::Result SampleHandle::Release +(const TrackPanelMouseEvent &, AudacityProject *pProject, + wxWindow *) +{ + const bool unsafe = pProject->IsAudioActive(); + if (unsafe) + return this->Cancel(pProject); + + //************************************************* + //*** UP-CLICK (Finish drawing) *** + //************************************************* + //On up-click, send the state to the undo stack + mClickedTrack = nullptr; //Set this to NULL so it will catch improper drag events. + pProject->PushState(_("Moved Samples"), + _("Sample Edit"), + UndoPush::CONSOLIDATE | UndoPush::AUTOSAVE); + + // No change to draw since last drag + return RefreshCode::RefreshNone; +} + +UIHandle::Result SampleHandle::Cancel(AudacityProject *pProject) +{ + pProject->RollbackState(); + mClickedTrack = nullptr; + return RefreshCode::RefreshCell; +} + +float SampleHandle::FindSampleEditingLevel + (const wxMouseEvent &event, const ViewInfo &, double t0) +{ + // Calculate where the mouse is located vertically (between +/- 1) + float zoomMin, zoomMax; + mClickedTrack->GetDisplayBounds(&zoomMin, &zoomMax); + + const int yy = event.m_y - mRect.y; + const int height = mRect.GetHeight(); + const bool dB = !mClickedTrack->GetWaveformSettings().isLinear(); + float newLevel = + ::ValueOfPixel(yy, height, false, dB, + mClickedTrack->GetWaveformSettings().dBRange, zoomMin, zoomMax); + + //Take the envelope into account + Envelope *const env = mClickedTrack->GetEnvelopeAtX(event.m_x); + if (env) + { + // Calculate sample as it would be rendered, so quantize time + double envValue = env->GetValue( t0, 1.0 / mClickedTrack->GetRate()); + if (envValue > 0) + newLevel /= envValue; + else + newLevel = 0; + + //Make sure the NEW level is between +/-1 + newLevel = std::max(-1.0f, std::min(1.0f, newLevel)); + } + + return newLevel; +} diff --git a/src/tracks/playabletrack/wavetrack/ui/SampleHandle.h b/src/tracks/playabletrack/wavetrack/ui/SampleHandle.h new file mode 100644 index 000000000..77a53e607 --- /dev/null +++ b/src/tracks/playabletrack/wavetrack/ui/SampleHandle.h @@ -0,0 +1,74 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +SampleHandle.h + +Paul Licameli + +**********************************************************************/ + +#ifndef __AUDACITY_SAMPLE_HANDLE__ +#define __AUDACITY_SAMPLE_HANDLE__ + +#include "../../../../UIHandle.h" +#include "audacity/Types.h" + +class wxMouseEvent; +#include + +struct HitTestResult; +class Track; +class ViewInfo; +class WaveTrack; + +class SampleHandle final : public UIHandle +{ + SampleHandle(); + SampleHandle(const SampleHandle&) = delete; + SampleHandle &operator=(const SampleHandle&) = delete; + static SampleHandle& Instance(); + static HitTestPreview HitPreview + (const wxMouseEvent &event, const AudacityProject *pProject, bool unsafe); + +public: + static HitTestResult HitAnywhere + (const wxMouseEvent &event, const AudacityProject *pProject); + static HitTestResult HitTest + (const wxMouseEvent &event, const wxRect &rect, + const AudacityProject *pProject, Track *pTrack); + + virtual ~SampleHandle(); + + Result Click + (const TrackPanelMouseEvent &event, AudacityProject *pProject) override; + + Result Drag + (const TrackPanelMouseEvent &event, AudacityProject *pProject) override; + + HitTestPreview Preview + (const TrackPanelMouseEvent &event, const AudacityProject *pProject) + override; + + Result Release + (const TrackPanelMouseEvent &event, AudacityProject *pProject, + wxWindow *pParent) override; + + Result Cancel(AudacityProject *pProject) override; + + bool StopsOnKeystroke() override { return true; } + +private: + float FindSampleEditingLevel + (const wxMouseEvent &event, const ViewInfo &viewInfo, double t0); + + WaveTrack *mClickedTrack{}; + wxRect mRect{}; + + sampleCount mClickedStartSample{}; + sampleCount mLastDragSample{}; + float mLastDragSampleValue{}; + bool mAltKey{}; +}; + +#endif diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackUI.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackUI.cpp index 9f6e14b40..be86b4de9 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackUI.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackUI.cpp @@ -13,12 +13,28 @@ Paul Licameli split from TrackPanel.cpp #include "WaveTrackVRulerControls.h" #include "../../../../HitTestResult.h" +#include "../../../../Project.h" +#include "../../../../TrackPanelMouseEvent.h" +#include "../../../../toolbars/ToolsToolBar.h" + +#include "SampleHandle.h" HitTestResult WaveTrack::HitTest (const TrackPanelMouseEvent &event, const AudacityProject *pProject) { - return Track::HitTest(event, pProject); + HitTestResult result = Track::HitTest(event, pProject); + if (result.preview.cursor) + return result; + + const ToolsToolBar *const pTtb = pProject->GetToolsToolBar(); + if (pTtb->IsDown(multiTool)) { + if (NULL != (result = + SampleHandle::HitTest(event.event, event.rect, pProject, this)).preview.cursor) + ; + } + + return result; } TrackControls *WaveTrack::GetControls() diff --git a/src/tracks/ui/TrackUI.cpp b/src/tracks/ui/TrackUI.cpp index d8901beb1..2b2d03f15 100644 --- a/src/tracks/ui/TrackUI.cpp +++ b/src/tracks/ui/TrackUI.cpp @@ -17,6 +17,7 @@ Paul Licameli split from TrackPanel.cpp #include "../../Project.h" #include "../../toolbars/ToolsToolBar.h" +#include "../playabletrack/wavetrack/ui/SampleHandle.h" #include "ZoomHandle.h" HitTestResult Track::HitTest @@ -28,12 +29,13 @@ HitTestResult Track::HitTest const bool isMultiTool = pTtb->IsDown(multiTool); if (!isMultiTool) { switch (pTtb->GetCurrentTool()) { + case drawTool: + return SampleHandle::HitAnywhere(event.event, pProject); case zoomTool: return ZoomHandle::HitAnywhere(event.event, pProject); case selectTool: case envelopeTool: - case drawTool: case slideTool: default: // cases not yet implemented diff --git a/win/Projects/Audacity/Audacity.vcxproj b/win/Projects/Audacity/Audacity.vcxproj index 313e1c202..f2c1a3a3a 100755 --- a/win/Projects/Audacity/Audacity.vcxproj +++ b/win/Projects/Audacity/Audacity.vcxproj @@ -1,4 +1,4 @@ - + @@ -230,6 +230,7 @@ + @@ -490,6 +491,7 @@ + @@ -1173,4 +1175,4 @@ - + \ No newline at end of file diff --git a/win/Projects/Audacity/Audacity.vcxproj.filters b/win/Projects/Audacity/Audacity.vcxproj.filters index e05b888c6..b75bb4906 100755 --- a/win/Projects/Audacity/Audacity.vcxproj.filters +++ b/win/Projects/Audacity/Audacity.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -992,6 +992,9 @@ src\widgets + + src\tracks\playabletrack\wavetrack\ui + @@ -1978,6 +1981,9 @@ src\widgets + + src\tracks\playabletrack\wavetrack\ui + @@ -2201,4 +2207,4 @@ plug-ins - + \ No newline at end of file