Audacity 3.2.0
ClipPitchAndSpeedButtonHandle.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/*!********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 ClipPitchAndSpeedButtonHandle.cpp
7
8 Matthieu Hodgkinson
9
10**********************************************************************/
12#include "AllThemeResources.h"
13#include "BasicUI.h"
14#include "HitTestResult.h"
15#include "LowlitClipButton.h"
16#include "PitchAndSpeedDialog.h"
17#include "Project.h"
18#include "ProjectHistory.h"
19#include "RefreshCode.h"
20#include "Theme.h"
21#include "TimeStretching.h"
24#include "WaveClip.h"
25#include "WaveClipUIUtilities.h"
26#include "WaveTrackUtilities.h"
28#include <wx/dc.h>
29
30namespace
31{
32wxString GetPitchShiftText(int clipCentShift)
33{
34 wxString pitchShiftText;
35 if (clipCentShift != 0)
36 {
37 pitchShiftText = wxString::Format("%.2f", std::abs(clipCentShift) / 100.);
38 while (pitchShiftText.EndsWith("0"))
39 pitchShiftText.RemoveLast();
40 if (pitchShiftText.EndsWith(".") || pitchShiftText.EndsWith(","))
41 pitchShiftText.RemoveLast();
42 }
43 return pitchShiftText;
44}
45
46wxString GetPlaybackSpeedText(double clipStretchRatio)
47{
48 if (TimeAndPitchInterface::IsPassThroughMode(clipStretchRatio))
49 return {};
50
51 // clang-format off
52 // We reckon that most of the time, a rounded percentage value is sufficient.
53 // There are two exceptions:
54 // - The clip is only slightly stretched such that the rounded value is 100.
55 // There should be an indicator if and only if the clip is stretched, this
56 // must be reliable. Yet showing "100%" would be confusing. Hence in that
57 // case we constrain the values to [99.1, ..., 99.9, 100.1,
58 // ..., 100.9], i.e., omitting 100.0.
59 // - The clip is stretched so much that the playback speed is less than 1%.
60 // Make sure in that case that we never show 0%, but always at least 0.1%.
61 // clang-format on
62
63 const auto playbackSpeed = 100 / clipStretchRatio;
64 wxString fullText;
65
66 // We compare with .95 rather than 1, since playback speeds within [100.95,
67 // 101) get rounded to 101.0 and (99, 99.05] to 99.0 by `wxString::Format`.
68 // Let these be processed by the integer-display branch of this if statement
69 // instead.
70 if (fabs(playbackSpeed - 100.) < .95)
71 // Never show 100.0%
72 fullText += wxString::Format(
73 "%.1f%%", playbackSpeed > 100 ? std::max(playbackSpeed, 100.1) :
74 std::min(playbackSpeed, 99.9));
75 else if (playbackSpeed < 1)
76 // Never show 0.0%
77 fullText += wxString::Format("%.1f%%", std::max(playbackSpeed, 0.1));
78 else
79 {
80 const auto roundedPlaybackSpeed =
81 static_cast<int>(std::round(playbackSpeed));
82 fullText += wxString::Format("%d%%", roundedPlaybackSpeed);
83 }
84 return fullText;
85}
86
88 wxDC& dc, const wxRect& rect, const wxBitmap& icon, const wxString& text)
89{
90 const auto textExtent = dc.GetTextExtent(text);
91 const auto textWidth = textExtent.GetWidth();
92 const auto textHeight = textExtent.GetHeight();
93
94 const auto iconHeight = icon.GetHeight();
95 const auto iconWidth = textWidth == 0 ? 0 : icon.GetWidth();
96 const auto contentWidth = iconWidth + textWidth;
97 if (contentWidth == 0 || contentWidth > rect.width)
98 return;
99 const auto height = rect.GetHeight();
100 const auto iconTop = rect.GetTop() + (height - iconHeight) / 2;
101 const auto x = rect.x + (rect.width - contentWidth) / 2;
102 dc.DrawBitmap(icon, x, iconTop);
103 const auto y = rect.GetTop() + (height - textHeight) / 2;
104 dc.DrawText(text, x + iconWidth, y);
105}
106} // namespace
107
109 Type type, const std::shared_ptr<WaveTrack>& track,
110 const std::shared_ptr<WaveTrack::Interval>& clip)
113 track, clip }
114 , mType { type }
115{
116}
117
119 const TrackPanelMouseEvent& event, AudacityProject* pProject,
120 wxWindow* pParent)
121{
122 if (event.event.CmdDown())
123 {
124 if (mType == Type::Pitch)
125 {
126 mClip->SetCentShift(0);
128 XO("Reset Clip Pitch"), XO("Reset Clip Pitch"));
129 }
131 {
133 wxWidgetsWindowPlacement { pParent }, XO("Not enough space"),
134 XO("There is not enough space to expand the clip to its original speed."),
135 {});
137 }
138 else
139 {
142 XO("Reset Clip Speed"), XO("Reset Clip Speed"));
143 }
144 }
145 else
146 {
147 const auto focusedGroup = mType == Type::Pitch ?
150 BasicUI::CallAfter([project = pProject->weak_from_this(), track = mTrack,
151 clip = mClip, focusedGroup] {
152 if (auto pProject = project.lock())
153 PitchAndSpeedDialog::Get(*pProject)
154 .Retarget(track, clip)
155 .SetFocus(focusedGroup);
156 });
157 }
159}
160
162 const TrackPanelMouseState& state, AudacityProject* pProject)
163{
164 const auto ctrlDown = state.state.CmdDown();
165 const bool macOs = wxPlatformInfo::Get().GetOperatingSystemId() & wxOS_MAC;
166 if (mType == Type::Pitch)
167 return { ctrlDown ? XO("Click to reset clip pitch.") :
168 macOs ? XO("Click to change clip pitch, Cmd + click to reset.") :
169 XO("Click to change clip pitch, Ctrl + click to reset."),
170 nullptr };
171 else
172 return { ctrlDown ? XO("Click to reset clip speed.") :
173 macOs ? XO("Click to change clip speed, Cmd + click to reset.") :
174 XO("Click to change clip speed, Ctrl + click to reset."),
175 nullptr };
176}
177
178void ClipPitchAndSpeedButtonHandle::DoDraw(const wxRect& rect, wxDC& dc)
179{
180 const ClipInterface& clip = *mClip;
181 ClipButtonDrawingArgs args { rect, clip, dc };
182 if (mType == Type::Pitch)
184 else
186}
187
189 const ClipInterface& clip)
190{
191 // If we are to show some decimals, reserve a bit more space.
192 return clip.GetCentShift() % 100 == 0 ? 32 : 55;
193}
194
196 const ClipInterface&)
197{
198 return 60;
199}
200
202 const ClipInterface& clip)
203{
204 return clip.GetCentShift() != 0;
205}
206
208 const ClipInterface& clip)
209{
211}
212
215{
216 const auto& clip = args.clip;
217 const auto& rect = args.rect;
218 auto& dc = args.dc;
219 const auto clipCentShift = clip.GetCentShift();
220 if (clipCentShift == 0)
221 return;
222 const auto pitchText = GetPitchShiftText(clipCentShift);
224 dc, rect,
226 clipCentShift > 0 ? pitchUpIndicator : pitchDownIndicator),
227 pitchText);
228}
229
232{
233 const auto& clip = args.clip;
234 const auto& rect = args.rect;
235 auto& dc = args.dc;
236 const auto clipStretchRatio = clip.GetStretchRatio();
237 if (TimeAndPitchInterface::IsPassThroughMode(clipStretchRatio))
238 return;
239 const auto speedText = GetPlaybackSpeedText(clipStretchRatio);
241 dc, rect, theTheme.Bitmap(speedIndicator), speedText);
242}
Toolkit-neutral facade for basic user interface services.
ClipButtonId
Definition: ClipButtonId.h:14
int min(int a, int b)
XO("Cut/Copy/Paste")
const auto project
THEME_API Theme theTheme
Definition: Theme.cpp:82
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
virtual int GetCentShift() const =0
Result DoRelease(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
void DoDraw(const wxRect &rect, wxDC &dc) override
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *pProject) override
ClipPitchAndSpeedButtonHandle(Type type, const std::shared_ptr< WaveTrack > &track, const std::shared_ptr< WaveTrack::Interval > &clip)
virtual double GetStretchRatio() const =0
std::shared_ptr< WaveTrack > mTrack
std::shared_ptr< WaveTrack::Interval > mClip
void PushState(const TranslatableString &desc, const TranslatableString &shortDesc)
static ProjectHistory & Get(AudacityProject &project)
wxBitmap & Bitmap(int iIndex)
static bool IsPassThroughMode(double stretchRatio)
unsigned Result
Definition: UIHandle.h:40
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:214
void ShowErrorDialog(const WindowPlacement &placement, const TranslatableString &dlogTitle, const TranslatableString &message, const ManualPageID &helpPage, const ErrorDialogOptions &options={})
Show an error dialog with a link to the manual for further help.
Definition: BasicUI.h:272
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:202
WAVE_TRACK_API bool SetClipStretchRatio(const WaveTrack &track, WaveTrack::Interval &interval, double stretchRatio)
void SelectClip(AudacityProject &project, const WaveTrack::Interval &clip)
void DrawPitchOrSpeedIconIfItFits(wxDC &dc, const wxRect &rect, const wxBitmap &icon, const wxString &text)
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512
const ClipInterface & clip
static int GetWidth(const ClipInterface &clip)
static void DrawOnClip(ClipButtonDrawingArgs &)
static bool NeedsDrawing(const ClipInterface &)
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.