1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-16 08:09:32 +02:00

Changes to seeking and scrubbing from Paul Licameli.

These are mostly under an EXPERIMENTAL_ #ifdef.  Also has a change for the prompt string for preferences so the displayed keybinding is adjusted when in multitool mode.
This commit is contained in:
james.k.crook@gmail.com 2014-11-29 22:09:57 +00:00
parent 94c243cb2e
commit c71397beae
13 changed files with 1783 additions and 1644 deletions

View File

@ -605,6 +605,8 @@ AudioIO::AudioIO()
mMixerOutputVol = 1.0; mMixerOutputVol = 1.0;
mInputMixerWorks = false; mInputMixerWorks = false;
#endif #endif
mLastPlaybackTimeMillis = 0;
} }
AudioIO::~AudioIO() AudioIO::~AudioIO()
@ -1127,7 +1129,8 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks,
AudioIOListener* listener, AudioIOListener* listener,
bool playLooped /* = false */, bool playLooped /* = false */,
double cutPreviewGapStart /* = 0.0 */, double cutPreviewGapStart /* = 0.0 */,
double cutPreviewGapLen /* = 0.0 */) double cutPreviewGapLen, /* = 0.0 */
const double *pStartTime /* = 0 */)
{ {
if( IsBusy() ) if( IsBusy() )
return 0; return 0;
@ -1369,6 +1372,20 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks,
AILASetStartTime(); AILASetStartTime();
#endif #endif
if (pStartTime)
{
// Calculate the new time position
mTime = std::max(mT0, std::min(mT1, *pStartTime));
// Reset mixer positions for all playback tracks
unsigned numMixers = mPlaybackTracks.GetCount();
for (unsigned ii = 0; ii < numMixers; ++ii)
mPlaybackMixers[ii]->Reposition(mTime);
if(mTimeTrack)
mWarpedTime = mTimeTrack->ComputeWarpedLength(mT0, mTime);
else
mWarpedTime = mTime - mT0;
}
// We signal the audio thread to call FillBuffers, to prime the RingBuffers // We signal the audio thread to call FillBuffers, to prime the RingBuffers
// so that they will have data in them when the stream starts. Having the // so that they will have data in them when the stream starts. Having the
// audio thread call FillBuffers here makes the code more predictable, since // audio thread call FillBuffers here makes the code more predictable, since
@ -3648,6 +3665,8 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
chanCnt = 0; chanCnt = 0;
} }
gAudioIO->mLastPlaybackTimeMillis = ::wxGetLocalTimeMillis();
// //
// Clip output to [-1.0,+1.0] range (msmeyer) // Clip output to [-1.0,+1.0] range (msmeyer)
// //

View File

@ -53,16 +53,7 @@ wxString DeviceName(const PaDeviceInfo* info);
wxString HostName(const PaDeviceInfo* info); wxString HostName(const PaDeviceInfo* info);
bool ValidateDeviceNames(); bool ValidateDeviceNames();
class AUDACITY_DLL_API AudioIOListener { class AudioIOListener;
public:
AudioIOListener() {}
virtual ~AudioIOListener() {}
virtual void OnAudioIORate(int rate) = 0;
virtual void OnAudioIOStartRecording() = 0;
virtual void OnAudioIOStopRecording() = 0;
virtual void OnAudioIONewBlockFiles(const wxString& blockFileLog) = 0;
};
#define BAD_STREAM_TIME -1000000000.0 #define BAD_STREAM_TIME -1000000000.0
@ -113,7 +104,10 @@ class AUDACITY_DLL_API AudioIO {
AudioIOListener* listener, AudioIOListener* listener,
bool playLooped = false, bool playLooped = false,
double cutPreviewGapStart = 0.0, double cutPreviewGapStart = 0.0,
double cutPreviewGapLen = 0.0); double cutPreviewGapLen = 0.0,
// May be other than t0,
// but will be constrained between t0 and t1
const double *pStartTime = 0);
/** \brief Stop recording, playback or input monitoring. /** \brief Stop recording, playback or input monitoring.
* *
@ -123,7 +117,7 @@ class AUDACITY_DLL_API AudioIO {
void StopStream(); void StopStream();
/** \brief Move the playback / recording position of the current stream /** \brief Move the playback / recording position of the current stream
* by the specified amount from where it is now */ * by the specified amount from where it is now */
void SeekStream(double seconds) { mSeek = seconds; }; void SeekStream(double seconds) { mSeek = seconds; }
/** \brief Returns true if audio i/o is busy starting, stopping, playing, /** \brief Returns true if audio i/o is busy starting, stopping, playing,
* or recording. * or recording.
@ -140,6 +134,8 @@ class AUDACITY_DLL_API AudioIO {
bool IsStreamActive(); bool IsStreamActive();
bool IsStreamActive(int token); bool IsStreamActive(int token);
wxLongLong GetLastPlaybackTime() const { return mLastPlaybackTimeMillis; }
#ifdef EXPERIMENTAL_MIDI_OUT #ifdef EXPERIMENTAL_MIDI_OUT
/** \brief Compute the current PortMidi timestamp time. /** \brief Compute the current PortMidi timestamp time.
* *
@ -517,6 +513,8 @@ private:
volatile bool mAudioThreadFillBuffersLoopRunning; volatile bool mAudioThreadFillBuffersLoopRunning;
volatile bool mAudioThreadFillBuffersLoopActive; volatile bool mAudioThreadFillBuffersLoopActive;
wxLongLong mLastPlaybackTimeMillis;
#ifdef EXPERIMENTAL_MIDI_OUT #ifdef EXPERIMENTAL_MIDI_OUT
volatile bool mMidiThreadFillBuffersLoopRunning; volatile bool mMidiThreadFillBuffersLoopRunning;
volatile bool mMidiThreadFillBuffersLoopActive; volatile bool mMidiThreadFillBuffersLoopActive;

View File

@ -105,6 +105,12 @@
// Paul Licameli (PRL) 5 Oct 2014 // Paul Licameli (PRL) 5 Oct 2014
#define EXPERIMENTAL_SPECTRAL_EDITING #define EXPERIMENTAL_SPECTRAL_EDITING
// Paul Licameli (PRL) 29 Nov 2014
#define EXPERIMENTAL_SCRUBBING
// Paul Licameli (PRL) 29 Nov 2014
#define EXPERIMENTAL_IMPROVED_SEEKING
// Philip Van Baren 01 July 2009 // Philip Van Baren 01 July 2009
// Replace RealFFT() and PowerSpectrum function to use (faster) RealFFTf function // Replace RealFFT() and PowerSpectrum function to use (faster) RealFFTf function
#define EXPERIMENTAL_USE_REALFFTF #define EXPERIMENTAL_USE_REALFFTF

View File

@ -47,6 +47,7 @@ for drawing different aspects of the label and its text box.
#include <wx/textfile.h> #include <wx/textfile.h>
#include <wx/utils.h> #include <wx/utils.h>
#include "AudioIO.h"
#include "LabelTrack.h" #include "LabelTrack.h"
#include "DirManager.h" #include "DirManager.h"
#include "Internat.h" #include "Internat.h"

View File

@ -21,6 +21,7 @@
#include <wx/settings.h> // for wxSystemSettings::GetColour and wxSystemSettings::GetMetric #include <wx/settings.h> // for wxSystemSettings::GetColour and wxSystemSettings::GetMetric
#include "AColor.h" #include "AColor.h"
#include "AudioIO.h"
#include "MixerBoard.h" #include "MixerBoard.h"
#ifdef EXPERIMENTAL_MIDI_OUT #ifdef EXPERIMENTAL_MIDI_OUT
#include "NoteTrack.h" #include "NoteTrack.h"

View File

@ -25,7 +25,7 @@
#include "UndoManager.h" #include "UndoManager.h"
#include "ViewInfo.h" #include "ViewInfo.h"
#include "TrackPanelListener.h" #include "TrackPanelListener.h"
#include "AudioIO.h" #include "AudioIOListener.h"
#include "commands/CommandManager.h" #include "commands/CommandManager.h"
#include "effects/EffectManager.h" #include "effects/EffectManager.h"
#include "xml/XMLTagHandler.h" #include "xml/XMLTagHandler.h"

View File

@ -584,6 +584,12 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id,
mSelStartValid = false; mSelStartValid = false;
mSelStart = 0; mSelStart = 0;
#ifdef EXPERIMENTAL_SCRUBBING
mScrubbing = false;
mLastScrubTime = 0;
mLastScrubPosition = 0;
#endif
} }
TrackPanel::~TrackPanel() TrackPanel::~TrackPanel()
@ -994,6 +1000,32 @@ void TrackPanel::OnTimer()
(p->mLastPlayMode == loopedPlay)); (p->mLastPlayMode == loopedPlay));
} }
#ifdef EXPERIMENTAL_SCRUBBING
if (mScrubbing
&&
gAudioIO->IsStreamActive(GetProject()->GetAudioIOToken()))
{
if (gAudioIO->GetLastPlaybackTime() < mLastScrubTime) {
// Allow some audio catch up
}
else {
wxMouseState state(::wxGetMouseState());
wxCoord xx = state.GetX();
ScreenToClient(&xx, NULL);
double leadPosition = PositionToTime(xx, GetLeftOffset());
if (mLastScrubPosition != leadPosition) {
wxLongLong clockTime = ::wxGetLocalTimeMillis();
double lagPosition = gAudioIO->GetStreamTime();
gAudioIO->SeekStream(leadPosition - lagPosition);
mLastScrubPosition = leadPosition;
mLastScrubTime = clockTime;
}
}
}
#endif
// Check whether we were playing or recording, but the stream has stopped. // Check whether we were playing or recording, but the stream has stopped.
if (p->GetAudioIOToken()>0 && if (p->GetAudioIOToken()>0 &&
!gAudioIO->IsStreamActive(p->GetAudioIOToken())) !gAudioIO->IsStreamActive(p->GetAudioIOToken()))
@ -1724,8 +1756,12 @@ void TrackPanel::SetCursorAndTipWhenSelectTool( Track * t,
wxString keyStr wxString keyStr
(GetProject()->GetCommandManager()->GetKeyFromName(wxT("Preferences"))); (GetProject()->GetCommandManager()->GetKeyFromName(wxT("Preferences")));
// Must compose a string that survives the function call, hence static. // Must compose a string that survives the function call, hence static.
if (keyStr.IsEmpty())
// No keyboard preference defined for opening Preferences dialog
/* i18n-hint: These are the names of a menu and a command in that menu */
keyStr = _("Edit, Preferences...");
static wxString result; static wxString result;
/* i18n-hint: %s is usually replaced by "Ctrl+P" */ /* i18n-hint: %s is usually replaced by "Ctrl+P" for Windows/Linux, "Command+," for Mac */
result = wxString::Format( result = wxString::Format(
_("Multi-Tool Mode: %s for Mouse and Keyboard Preferences."), _("Multi-Tool Mode: %s for Mouse and Keyboard Preferences."),
keyStr.c_str()); keyStr.c_str());
@ -1977,6 +2013,52 @@ void TrackPanel::HandleSelect(wxMouseEvent & event)
} }
} else if (event.LeftUp() || event.RightUp()) { } else if (event.LeftUp() || event.RightUp()) {
#ifdef EXPERIMENTAL_SCRUBBING
if(mScrubbing) {
if (gAudioIO->IsBusy()) {
AudacityProject *p = GetActiveProject();
if (p) {
ControlToolBar * ctb = p->GetControlToolBar();
ctb->StopPlaying();
}
}
if (mAdjustSelectionEdges) {
if (event.ShiftDown()) {
// Adjust time selection as if shift-left click at end
const double selend = PositionToTime(event.m_x, GetLeftOffset());
SelectionBoundary boundary = ChooseTimeBoundary(selend, false);
switch (boundary)
{
case SBLeft:
mViewInfo->selectedRegion.setT0(selend);
break;
case SBRight:
mViewInfo->selectedRegion.setT1(selend);
break;
default:
wxASSERT(false);
}
UpdateSelectionDisplay();
}
else {
// Adjust time selection as if left click
StartSelection(event.m_x, r.x);
DisplaySelection();
}
}
mScrubbing = false;
}
else if (event.CmdDown()) {
// A control-click will set just the indicator to the clicked spot,
// and turn playback on -- but delayed until button up,
// and only if no intervening drag
StartOrJumpPlayback(event);
}
// Don't return yet
#endif
if (mSnapManager) { if (mSnapManager) {
delete mSnapManager; delete mSnapManager;
mSnapManager = NULL; mSnapManager = NULL;
@ -2055,6 +2137,67 @@ void TrackPanel::HandleSelect(wxMouseEvent & event)
} }
void TrackPanel::StartOrJumpPlayback(wxMouseEvent &event)
{
AudacityProject *p = GetActiveProject();
if (p) {
double clicktime = PositionToTime(event.m_x, GetLeftOffset());
const double t1 = mViewInfo->selectedRegion.t1();
// Play to end of selection, or if that is not right of the pick, end of track
double endtime = clicktime < t1 ? t1 : mViewInfo->total;
//Behavior should differ depending upon whether we are
//currently in playback mode or not.
bool busy = gAudioIO->IsBusy();
if (!busy)
{
//If we aren't currently playing back, start playing back at
//the clicked point
ControlToolBar * ctb = p->GetControlToolBar();
//ctb->SetPlay(true);// Not needed as done in PlayPlayRegion
ctb->PlayPlayRegion(clicktime, endtime,false) ;
}
else
{
//If we are playing back, stop and move playback
//to the clicked point.
//This unpauses paused audio as well. The right thing to do might be to
//leave it paused but move the point. This would probably
//require a new method in ControlToolBar: SetPause();
ControlToolBar * ctb = p->GetControlToolBar();
ctb->StopPlaying();
ctb->PlayPlayRegion(clicktime,endtime,false) ;
}
}
}
#ifdef EXPERIMENTAL_SCRUBBING
void TrackPanel::StartScrubbing(double position)
{
AudacityProject *p = GetActiveProject();
if (p &&
// Should I make a bigger tolerance than zero?
mLastScrubPosition != position) {
ControlToolBar * ctb = p->GetControlToolBar();
bool busy = gAudioIO->IsBusy();
double maxTime = p->GetTracks()->GetEndTime();
if (busy)
ctb->StopPlaying();
ctb->PlayPlayRegion(0, maxTime, false, false,
0,
&position);
mScrubbing = true;
mLastScrubPosition = position;
mLastScrubTime = ::wxGetLocalTimeMillis();
}
}
#endif
/// This method gets called when we're handling selection /// This method gets called when we're handling selection
/// and the mouse was just clicked. /// and the mouse was just clicked.
void TrackPanel::SelectionHandleClick(wxMouseEvent & event, void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
@ -2087,6 +2230,12 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
#endif #endif
if (event.ShiftDown() if (event.ShiftDown()
#ifdef EXPERIMENTAL_SCRUBBING
// Ctrl prevails over Shift with scrubbing enabled
&& !event.CmdDown()
#endif
#ifdef USE_MIDI #ifdef USE_MIDI
&& !stretch && !stretch
#endif #endif
@ -2171,40 +2320,14 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
&& !stretch && !stretch
#endif #endif
) { ) {
AudacityProject *p = GetActiveProject(); #ifdef EXPERIMENTAL_SCRUBBING
if (p) { // With scrubbing enabled, playback happens on button up, not down,
// and only if we do not start a scrub in the interim.
double clicktime = PositionToTime(event.m_x, GetLeftOffset()); mScrubbing = false;
const double t1 = mViewInfo->selectedRegion.t1(); mLastScrubPosition = PositionToTime(event.m_x, GetLeftOffset());
double endtime = clicktime < t1 ? t1 : mViewInfo->total; #else
StartOrJumpPlayback(event);
//Behavior should differ depending upon whether we are #endif
//currently in playback mode or not.
bool busy = gAudioIO->IsBusy();
if(!busy)
{
//If we aren't currently playing back, start playing back at
//the clicked point
ControlToolBar * ctb = p->GetControlToolBar();
//ctb->SetPlay(true);// Not needed as done in PlayPlayRegion
ctb->PlayPlayRegion(clicktime, endtime,false) ;
}
else
{
//If we are playing back, stop and move playback
//to the clicked point.
//This unpauses paused audio as well. The right thing to do might be to
//leave it paused but move the point. This would probably
//require a new method in ControlToolBar: SetPause();
ControlToolBar * ctb = p->GetControlToolBar();
ctb->StopPlaying();
ctb->PlayPlayRegion(clicktime,endtime,false) ;
}
}
return; return;
} }
//Make sure you are within the selected track //Make sure you are within the selected track
@ -2907,9 +3030,22 @@ void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack)
return; return;
// Also fuhggeddaboudit if we're not dragging and not autoscrolling. // Also fuhggeddaboudit if we're not dragging and not autoscrolling.
if ((!event.Dragging() && !mAutoScrolling) || event.CmdDown()) if (!event.Dragging() && !mAutoScrolling)
return; return;
if (event.CmdDown()) {
#ifdef EXPERIMENTAL_SCRUBBING
if (!mScrubbing) {
double position = PositionToTime(event.m_x, GetLeftOffset());
StartScrubbing(position);
}
else
#else
// Ctrl-drag has no meaning, fuhggeddaboudit
#endif
return;
}
wxRect r = mCapturedRect; wxRect r = mCapturedRect;
Track *pTrack = mCapturedTrack; Track *pTrack = mCapturedTrack;
@ -3091,7 +3227,8 @@ wxInt64 TrackPanel::FrequencyToPosition(double frequency,
} }
#endif #endif
void SetIfNotNull( double * pValue, const double Value ) template<typename T>
inline void SetIfNotNull( T * pValue, const T Value )
{ {
if( pValue == NULL ) if( pValue == NULL )
return; return;
@ -3099,6 +3236,43 @@ void SetIfNotNull( double * pValue, const double Value )
} }
TrackPanel::SelectionBoundary TrackPanel::ChooseTimeBoundary
(double selend, bool onlyWithinSnapDistance,
wxInt64 *pPixelDist, double *pPinValue) const
{
const double t0 = mViewInfo->selectedRegion.t0();
const double t1 = mViewInfo->selectedRegion.t1();
wxInt64 pixelDist = mViewInfo->zoom * fabs(selend - t0);
bool chooseLeft = true;
if (mViewInfo->selectedRegion.isPoint())
// Special case when selection is a point, and thus left
// and right distances are the same
chooseLeft = (selend < t0);
else {
const wxInt64 rightDist = mViewInfo->zoom * fabs(selend - t1);
if (rightDist < pixelDist)
chooseLeft = false, pixelDist = rightDist;
}
SetIfNotNull(pPixelDist, pixelDist);
if (onlyWithinSnapDistance &&
pixelDist >= SELECTION_RESIZE_REGION) {
SetIfNotNull( pPinValue, -1.0);
return SBNone;
}
else if (chooseLeft) {
SetIfNotNull( pPinValue, t1);
return SBLeft;
}
else {
SetIfNotNull( pPinValue, t0);
return SBRight;
}
}
TrackPanel::SelectionBoundary TrackPanel::ChooseBoundary TrackPanel::SelectionBoundary TrackPanel::ChooseBoundary
(wxMouseEvent & event, const Track *pTrack, const wxRect &rect, (wxMouseEvent & event, const Track *pTrack, const wxRect &rect,
bool mayDragWidth, bool onlyWithinSnapDistance, bool mayDragWidth, bool onlyWithinSnapDistance,
@ -3110,28 +3284,19 @@ bool mayDragWidth, bool onlyWithinSnapDistance,
// May choose no boundary if onlyWithinSnapDistance is true. // May choose no boundary if onlyWithinSnapDistance is true.
// Otherwise choose the eligible boundary nearest the mouse click. // Otherwise choose the eligible boundary nearest the mouse click.
const double selend = PositionToTime(event.m_x, rect.x); const double selend = PositionToTime(event.m_x, rect.x);
wxInt64 pixelDist = 0;
SelectionBoundary boundary =
ChooseTimeBoundary(selend, onlyWithinSnapDistance,
&pixelDist, pPinValue);
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
const double t0 = mViewInfo->selectedRegion.t0(); const double t0 = mViewInfo->selectedRegion.t0();
const double t1 = mViewInfo->selectedRegion.t1(); const double t1 = mViewInfo->selectedRegion.t1();
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
const double f0 = mViewInfo->selectedRegion.f0(); const double f0 = mViewInfo->selectedRegion.f0();
const double f1 = mViewInfo->selectedRegion.f1(); const double f1 = mViewInfo->selectedRegion.f1();
const double fc = mViewInfo->selectedRegion.fc(); const double fc = mViewInfo->selectedRegion.fc();
double ratio = 0; double ratio = 0;
#endif
wxInt64 pixelDist = mViewInfo->zoom * fabs(selend - t0);
bool chooseLeft = true;
if (mViewInfo->selectedRegion.isPoint())
// Special case when selection is a point, and thus left
// and right distances are the same
chooseLeft = (selend < t0);
else {
const wxInt64 rightDist = mViewInfo->zoom * fabs(selend - t1);
if (rightDist < pixelDist)
chooseLeft = false, pixelDist = rightDist;
}
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
bool chooseTime = true; bool chooseTime = true;
bool chooseBottom = true; bool chooseBottom = true;
bool chooseCenter = false; bool chooseCenter = false;
@ -3146,14 +3311,20 @@ bool mayDragWidth, bool onlyWithinSnapDistance,
? FrequencyToPosition(f0, rect.y, rect.height, ? FrequencyToPosition(f0, rect.y, rect.height,
wt->GetRate(), logF) wt->GetRate(), logF)
: rect.y + rect.height; : rect.y + rect.height;
wxInt64 verticalDist = abs(int(event.m_y - bottomSel));
const wxInt64 topSel = (f1 >= 0) const wxInt64 topSel = (f1 >= 0)
? FrequencyToPosition(f1, rect.y, rect.height, ? FrequencyToPosition(f1, rect.y, rect.height,
wt->GetRate(), logF) wt->GetRate(), logF)
: rect.y; : rect.y;
wxInt64 signedBottomDist = int(event.m_y - bottomSel);
wxInt64 verticalDist = abs(signedBottomDist);
if (bottomSel == topSel)
// Top and bottom are too close to resolve on screen
chooseBottom = (signedBottomDist >= 0);
else {
const wxInt64 topDist = abs(int(event.m_y - topSel)); const wxInt64 topDist = abs(int(event.m_y - topSel));
if (topDist < verticalDist) if (topDist < verticalDist)
chooseBottom = false, verticalDist = topDist; chooseBottom = false, verticalDist = topDist;
}
if (fc > 0 if (fc > 0
#ifdef SPECTRAL_EDITING_ESC_KEY #ifdef SPECTRAL_EDITING_ESC_KEY
&& mayDragWidth && mayDragWidth
@ -3202,19 +3373,7 @@ bool mayDragWidth, bool onlyWithinSnapDistance,
else else
#endif #endif
{ {
if (onlyWithinSnapDistance && return boundary;
pixelDist >= SELECTION_RESIZE_REGION) {
SetIfNotNull( pPinValue, -1.0);
return SBNone;
}
else if (chooseLeft) {
SetIfNotNull( pPinValue, t1);
return SBLeft;
}
else {
SetIfNotNull( pPinValue, t0);
return SBRight;
}
} }
} }
@ -5805,8 +5964,8 @@ void TrackPanel::OnKeyDown(wxKeyEvent & event)
else { else {
// Handle ESC during frequency drag // Handle ESC during frequency drag
wxMouseState state(::wxGetMouseState()); wxMouseState state(::wxGetMouseState());
wxCoord xx = state.GetX(), yy = state.GetY(); wxCoord yy = state.GetY();
ScreenToClient(&xx, &yy); ScreenToClient(NULL, &yy);
wxRect r; wxRect r;
if (wt == FindTrack(state.GetX(), yy, false, false, &r)) { if (wt == FindTrack(state.GetX(), yy, false, false, &r)) {
eFreqSelMode saveMode = mFreqSelMode; eFreqSelMode saveMode = mFreqSelMode;
@ -7333,10 +7492,42 @@ void TrackPanel::ScrollIntoView(int x)
void TrackPanel::OnCursorLeft( bool shift, bool ctrl, bool keyup ) void TrackPanel::OnCursorLeft( bool shift, bool ctrl, bool keyup )
{ {
if( keyup ) // PRL: What I found and preserved, strange though it be:
// During playback: jump depends on preferences and is independent of the zoom
// and does not vary if the key is held
// Else: jump depends on the zoom and gets bigger if the key is held
int snapToTime = GetActiveProject()->GetSnapTo();
double quietSeekStepPositive = 1.0 / mViewInfo->zoom;
double audioSeekStepPositive = shift ? mSeekLong : mSeekShort;
SeekLeftOrRight
(true, shift, ctrl, keyup, snapToTime, true, false,
quietSeekStepPositive, audioSeekStepPositive);
}
void TrackPanel::OnCursorRight(bool shift, bool ctrl, bool keyup)
{
// PRL: What I found and preserved, strange though it be:
// During playback: jump depends on preferences and is independent of the zoom
// and does not vary if the key is held
// Else: jump depends on the zoom and gets bigger if the key is held
int snapToTime = GetActiveProject()->GetSnapTo();
double quietSeekStepPositive = 1.0 / mViewInfo->zoom;
double audioSeekStepPositive = shift ? mSeekLong : mSeekShort;
SeekLeftOrRight
(false, shift, ctrl, keyup, snapToTime, true, false,
quietSeekStepPositive, audioSeekStepPositive);
}
// Handle small cursor and play head movements
void TrackPanel::SeekLeftOrRight
(bool leftward, bool shift, bool ctrl, bool keyup,
int snapToTime, bool mayAccelerateQuiet, bool mayAccelerateAudio,
double quietSeekStepPositive, double audioSeekStepPositive)
{
if (keyup)
{ {
int token = GetProject()->GetAudioIOToken(); int token = GetProject()->GetAudioIOToken();
if( token > 0 && gAudioIO->IsStreamActive( token ) ) if (token > 0 && gAudioIO->IsStreamActive(token))
{ {
return; return;
} }
@ -7347,181 +7538,119 @@ void TrackPanel::OnCursorLeft( bool shift, bool ctrl, bool keyup )
// If the last adjustment was very recent, we are // If the last adjustment was very recent, we are
// holding the key down and should move faster. // holding the key down and should move faster.
wxLongLong curtime = ::wxGetLocalTimeMillis(); const wxLongLong curtime = ::wxGetLocalTimeMillis();
int multiplier = 1; enum { MIN_INTERVAL = 50 };
if( curtime - mLastSelectionAdjustment < 50 ) const bool fast = (curtime - mLastSelectionAdjustment < MIN_INTERVAL);
// How much faster should the cursor move if shift is down?
enum { LARGER_MULTIPLIER = 4 };
int multiplier = (fast && mayAccelerateQuiet) ? LARGER_MULTIPLIER : 1;
if (leftward)
multiplier = -multiplier;
int token = GetProject()->GetAudioIOToken();
if (shift && ctrl)
{ {
multiplier = 4;
}
mLastSelectionAdjustment = curtime; mLastSelectionAdjustment = curtime;
int snapToTime = GetActiveProject()->GetSnapTo(); // Contract selection
// Contract selection from the right to the left
if( shift && ctrl )
{
// Reduce and constrain (counter-intuitive) // Reduce and constrain (counter-intuitive)
if (leftward) {
mViewInfo->selectedRegion.setT1( mViewInfo->selectedRegion.setT1(
std::max(mViewInfo->selectedRegion.t0(), std::max(mViewInfo->selectedRegion.t0(),
snapToTime snapToTime
? GridMove(mViewInfo->selectedRegion.t1(), -multiplier) ? GridMove(mViewInfo->selectedRegion.t1(), multiplier)
: mViewInfo->selectedRegion.t1() - : mViewInfo->selectedRegion.t1() +
multiplier / mViewInfo->zoom)); multiplier * quietSeekStepPositive));
// Make sure it's visible. // Make sure it's visible.
ScrollIntoView( mViewInfo->selectedRegion.t1() ); ScrollIntoView(mViewInfo->selectedRegion.t1());
Refresh( false );
} }
// Extend selection toward the left else {
else if( shift )
{
// If playing, reposition a long amount of time
int token = GetProject()->GetAudioIOToken();
if( token > 0 && gAudioIO->IsStreamActive( token ) )
{
gAudioIO->SeekStream(-mSeekLong);
return;
}
// Expand and constrain
mViewInfo->selectedRegion.setT0(
std::max(0.0,
snapToTime
? GridMove(mViewInfo->selectedRegion.t0(), -multiplier)
: mViewInfo->selectedRegion.t0() -
multiplier / mViewInfo->zoom));
// Make sure it's visible.
ScrollIntoView( mViewInfo->selectedRegion.t0() );
Refresh( false );
}
// Move the cursor toward the left
else
{
// If playing, reposition a short amount of time
int token = GetProject()->GetAudioIOToken();
if( token > 0 && gAudioIO->IsStreamActive( token ) )
{
gAudioIO->SeekStream(-mSeekShort);
return;
}
// Already in cursor mode?
if( mViewInfo->selectedRegion.isPoint() )
{
// Move and constrain
mViewInfo->selectedRegion.setT0(
std::max(0.0,
snapToTime
? GridMove(mViewInfo->selectedRegion.t0(), -multiplier)
: mViewInfo->selectedRegion.t0() -
multiplier / mViewInfo->zoom),
false);
mViewInfo->selectedRegion.collapseToT0();
// Move the visual cursor
DrawCursor();
}
else
{
// Transition to cursor mode.
mViewInfo->selectedRegion.collapseToT0();
Refresh( false );
}
// Make sure it's visible
ScrollIntoView( mViewInfo->selectedRegion.t0() );
}
}
void TrackPanel::OnCursorRight( bool shift, bool ctrl, bool keyup )
{
if( keyup )
{
int token = GetProject()->GetAudioIOToken();
if( token > 0 && gAudioIO->IsStreamActive( token ) )
{
return;
}
MakeParentModifyState(false);
return;
}
// If the last adjustment was very recent, we are
// holding the key down and should move faster.
wxLongLong curtime = ::wxGetLocalTimeMillis();
int multiplier = 1;
if( curtime - mLastSelectionAdjustment < 50 )
{
multiplier = 4;
}
mLastSelectionAdjustment = curtime;
int snapToTime = GetActiveProject()->GetSnapTo();
// Contract selection from the left to the right
if( shift && ctrl )
{
// Reduce and constrain (counter-intuitive)
mViewInfo->selectedRegion.setT0( mViewInfo->selectedRegion.setT0(
std::min(mViewInfo->selectedRegion.t1(), std::min(mViewInfo->selectedRegion.t1(),
snapToTime snapToTime
? GridMove(mViewInfo->selectedRegion.t0(), multiplier) ? GridMove(mViewInfo->selectedRegion.t0(), multiplier)
: mViewInfo->selectedRegion.t0() + : mViewInfo->selectedRegion.t0() +
multiplier / mViewInfo->zoom)); multiplier * quietSeekStepPositive));
// Make sure new position is in view. // Make sure new position is in view.
ScrollIntoView( mViewInfo->selectedRegion.t0() ); ScrollIntoView(mViewInfo->selectedRegion.t0());
Refresh( false );
} }
// Extend selection toward the right Refresh(false);
else if( shift ) }
{ else if (token > 0 && gAudioIO->IsStreamActive(token)) {
// If playing, reposition a long amount of time #ifdef EXPERIMENTAL_IMPROVED_SEEKING
int token = GetProject()->GetAudioIOToken(); if (gAudioIO->GetLastPlaybackTime() < mLastSelectionAdjustment) {
if( token > 0 && gAudioIO->IsStreamActive( token ) ) // Allow time for the last seek to output a buffer before
{ // discarding samples again
gAudioIO->SeekStream(mSeekLong); // Do not advance mLastSelectionAdjustment
return; return;
} }
#endif
mLastSelectionAdjustment = curtime;
// Ignore the multiplier for the quiet case
multiplier = (fast && mayAccelerateAudio) ? LARGER_MULTIPLIER : 1;
if (leftward)
multiplier = -multiplier;
// If playing, reposition
gAudioIO->SeekStream(multiplier * audioSeekStepPositive);
return;
}
else if (shift)
{
mLastSelectionAdjustment = curtime;
// Extend selection
// Expand and constrain // Expand and constrain
double end = mTracks->GetEndTime(); if (leftward) {
mViewInfo->selectedRegion.setT1( mViewInfo->selectedRegion.setT0(
std::min(end, std::max(0.0,
snapToTime snapToTime
? GridMove(mViewInfo->selectedRegion.t1(), multiplier) ? GridMove(mViewInfo->selectedRegion.t0(), multiplier)
: mViewInfo->selectedRegion.t1() + multiplier/mViewInfo->zoom)); : mViewInfo->selectedRegion.t0() +
multiplier * quietSeekStepPositive));
// Make sure new position is in view. // Make sure it's visible.
ScrollIntoView( mViewInfo->selectedRegion.t1() ); ScrollIntoView(mViewInfo->selectedRegion.t0());
Refresh( false );
} }
// Move the cursor toward the right else {
else
{
// If playing, reposition a short amount of time
int token = GetProject()->GetAudioIOToken();
if( token > 0 && gAudioIO->IsStreamActive( token ) )
{
gAudioIO->SeekStream(mSeekShort);
return;
}
// Already in cursor mode?
if (mViewInfo->selectedRegion.isPoint())
{
// Move and constrain
double end = mTracks->GetEndTime(); double end = mTracks->GetEndTime();
mViewInfo->selectedRegion.setT1( mViewInfo->selectedRegion.setT1(
std::min(end, std::min(end,
snapToTime snapToTime
? GridMove(mViewInfo->selectedRegion.t1(), multiplier) ? GridMove(mViewInfo->selectedRegion.t1(), multiplier)
: mViewInfo->selectedRegion.t1() + : mViewInfo->selectedRegion.t1() +
multiplier / mViewInfo->zoom), multiplier * quietSeekStepPositive));
// Make sure new position is in view.
ScrollIntoView(mViewInfo->selectedRegion.t1());
}
Refresh(false);
}
else
{
mLastSelectionAdjustment = curtime;
// Move the cursor
// Already in cursor mode?
if (mViewInfo->selectedRegion.isPoint())
{
// Move and constrain
double end = mTracks->GetEndTime();
mViewInfo->selectedRegion.setT0(
std::max(0.0,
std::min(end,
snapToTime
? GridMove(mViewInfo->selectedRegion.t0(), multiplier)
: mViewInfo->selectedRegion.t0() + multiplier * quietSeekStepPositive
)
),
false); false);
mViewInfo->selectedRegion.collapseToT1(); mViewInfo->selectedRegion.collapseToT0();
// Move the visual cursor // Move the visual cursor
DrawCursor(); DrawCursor();
@ -7529,12 +7658,15 @@ void TrackPanel::OnCursorRight( bool shift, bool ctrl, bool keyup )
else else
{ {
// Transition to cursor mode. // Transition to cursor mode.
if (leftward)
mViewInfo->selectedRegion.collapseToT0();
else
mViewInfo->selectedRegion.collapseToT1(); mViewInfo->selectedRegion.collapseToT1();
Refresh( false ); Refresh(false);
} }
// Make sure new position is in view // Make sure new position is in view
ScrollIntoView( mViewInfo->selectedRegion.t1() ); ScrollIntoView(mViewInfo->selectedRegion.t1());
} }
} }
@ -7656,74 +7788,25 @@ void TrackPanel::OnBoundaryMove(bool left, bool boundaryContract)
// longjump=false: Use mSeekShort; longjump=true: Use mSeekLong // longjump=false: Use mSeekShort; longjump=true: Use mSeekLong
void TrackPanel::OnCursorMove(bool forward, bool jump, bool longjump ) void TrackPanel::OnCursorMove(bool forward, bool jump, bool longjump )
{ {
// If the last adjustment was very recent, we are // PRL: nobody calls this yet with !jump
// holding the key down and should move faster.
wxLongLong curtime = ::wxGetLocalTimeMillis();
int multiplier = 1;
if( curtime - mLastSelectionAdjustment < 50 )
{
multiplier = 4;
}
mLastSelectionAdjustment = curtime;
float direction = -1; double positiveSeekStep;
if (forward) {
direction = 1;
}
float mSeek;
if (jump) { if (jump) {
if (!longjump) { if (!longjump) {
mSeek = mSeekShort; positiveSeekStep = mSeekShort;
} else { } else {
mSeek = mSeekLong; positiveSeekStep = mSeekLong;
} }
} else { } else {
mSeek = multiplier / mViewInfo->zoom; positiveSeekStep = 1.0 / mViewInfo->zoom;
} }
mSeek *= direction; bool mayAccelerate = !jump;
SeekLeftOrRight
// If playing, reposition a short amount of time (!forward, false, false, false,
int token = GetProject()->GetAudioIOToken(); 0, mayAccelerate, mayAccelerate,
if( token > 0 && gAudioIO->IsStreamActive( token ) ) positiveSeekStep, positiveSeekStep);
{
gAudioIO->SeekStream(mSeek);
}
else
{
// Already in cursor mode?
if( mViewInfo->selectedRegion.isPoint() )
{
// Move and constrain
double t0 = mViewInfo->selectedRegion.t0() + mSeek;
if( !forward && t0 < 0.0 )
{
t0 = 0.0;
}
double end = mTracks->GetEndTime();
if( forward && t0 > end)
{
t0 = end;
}
mViewInfo->selectedRegion.setT0(t0, false);
mViewInfo->selectedRegion.collapseToT0();
// Move the visual cursor
DrawCursor();
}
else
{
// Transition to cursor mode.
mViewInfo->selectedRegion.collapseToT0();
Refresh( false );
}
// Make sure it's visible
ScrollIntoView( mViewInfo->selectedRegion.t0() );
MakeParentModifyState(false); MakeParentModifyState(false);
}
} }
//The following methods operate controls on specified tracks, //The following methods operate controls on specified tracks,

View File

@ -310,6 +310,10 @@ class AUDACITY_DLL_API TrackPanel:public wxPanel {
// AS: Selection handling // AS: Selection handling
virtual void HandleSelect(wxMouseEvent & event); virtual void HandleSelect(wxMouseEvent & event);
virtual void SelectionHandleDrag(wxMouseEvent &event, Track *pTrack); virtual void SelectionHandleDrag(wxMouseEvent &event, Track *pTrack);
void StartOrJumpPlayback(wxMouseEvent &event);
#ifdef EXPERIMENTAL_SCRUBBING
void StartScrubbing(double position);
#endif
virtual void SelectionHandleClick(wxMouseEvent &event, virtual void SelectionHandleClick(wxMouseEvent &event,
Track* pTrack, wxRect r); Track* pTrack, wxRect r);
virtual void StartSelection (int mouseXCoordinate, int trackLeftEdge); virtual void StartSelection (int mouseXCoordinate, int trackLeftEdge);
@ -317,6 +321,12 @@ class AUDACITY_DLL_API TrackPanel:public wxPanel {
Track *pTrack); Track *pTrack);
virtual void UpdateSelectionDisplay(); virtual void UpdateSelectionDisplay();
// Handle small cursor and play head movements
void SeekLeftOrRight
(bool left, bool shift, bool ctrl, bool keyup,
int snapToTime, bool mayAccelerateQuiet, bool mayAccelerateAudio,
double quietSeekStepPositive, double audioSeekStepPositive);
#ifdef EXPERIMENTAL_SPECTRAL_EDITING #ifdef EXPERIMENTAL_SPECTRAL_EDITING
void StartSnappingFreqSelection (WaveTrack *pTrack); void StartSnappingFreqSelection (WaveTrack *pTrack);
void MoveSnappingFreqSelection (int mouseYCoordinate, void MoveSnappingFreqSelection (int mouseYCoordinate,
@ -688,6 +698,9 @@ protected:
SBBottom, SBTop, SBCenter, SBWidth, SBBottom, SBTop, SBCenter, SBWidth,
#endif #endif
}; };
SelectionBoundary ChooseTimeBoundary
(double selend, bool onlyWithinSnapDistance,
wxInt64 *pPixelDist = NULL, double *pPinValue = NULL) const;
SelectionBoundary ChooseBoundary SelectionBoundary ChooseBoundary
(wxMouseEvent & event, const Track *pTrack, (wxMouseEvent & event, const Track *pTrack,
const wxRect &rect, const wxRect &rect,
@ -739,6 +752,12 @@ protected:
int mMoveUpThreshold; int mMoveUpThreshold;
int mMoveDownThreshold; int mMoveDownThreshold;
#ifdef EXPERIMENTAL_SCRUBBING
bool mScrubbing;
wxLongLong mLastScrubTime; // milliseconds
double mLastScrubPosition;
#endif
wxCursor *mArrowCursor; wxCursor *mArrowCursor;
wxCursor *mPencilCursor; wxCursor *mPencilCursor;
wxCursor *mSelectCursor; wxCursor *mSelectCursor;

View File

@ -35,6 +35,7 @@
#include "../widgets/treebook.h" #include "../widgets/treebook.h"
#endif #endif
#include "../AudioIO.h"
#include "../Experimental.h" #include "../Experimental.h"
#include "../Project.h" #include "../Project.h"
#include "../Prefs.h" #include "../Prefs.h"

View File

@ -32,6 +32,8 @@
*//*******************************************************************/ *//*******************************************************************/
#include <algorithm>
#include "../Audacity.h" #include "../Audacity.h"
#include "../Experimental.h" #include "../Experimental.h"
@ -438,7 +440,8 @@ bool ControlToolBar::IsRecordDown()
void ControlToolBar::PlayPlayRegion(double t0, double t1, void ControlToolBar::PlayPlayRegion(double t0, double t1,
bool looped /* = false */, bool looped /* = false */,
bool cutpreview /* = false */, bool cutpreview /* = false */,
TimeTrack *timetrack /* = NULL */) TimeTrack *timetrack /* = NULL */,
const double *pStartTime /* = NULL */)
{ {
SetPlay(true, looped, cutpreview); SetPlay(true, looped, cutpreview);
@ -564,7 +567,8 @@ void ControlToolBar::PlayPlayRegion(double t0, double t1,
NoteTrackArray(), NoteTrackArray(),
#endif #endif
timetrack, p->GetRate(), tcp0, tcp1, p, false, timetrack, p->GetRate(), tcp0, tcp1, p, false,
t0, t1-t0); t0, t1-t0,
pStartTime);
} else } else
{ {
// Cannot create cut preview tracks, clean up and exit // Cannot create cut preview tracks, clean up and exit
@ -583,7 +587,9 @@ void ControlToolBar::PlayPlayRegion(double t0, double t1,
t->GetNoteTrackArray(false), t->GetNoteTrackArray(false),
#endif #endif
timetrack, timetrack,
p->GetRate(), t0, t1, p, looped); p->GetRate(), t0, t1, p, looped,
0, 0,
pStartTime);
} }
if (token != 0) { if (token != 0) {
success = true; success = true;

View File

@ -67,7 +67,10 @@ class ControlToolBar:public ToolBar {
void PlayPlayRegion(double t0, double t1, void PlayPlayRegion(double t0, double t1,
bool looped = false, bool looped = false,
bool cutpreview = false, bool cutpreview = false,
TimeTrack *timetrack = NULL); TimeTrack *timetrack = NULL,
// May be other than t0,
// but will be constrained between t0 and t1
const double *pStartTime = NULL);
void PlayDefault(); void PlayDefault();
// Stop playing // Stop playing

View File

@ -60,6 +60,7 @@
#include "../AColor.h" #include "../AColor.h"
#include "../AllThemeResources.h" #include "../AllThemeResources.h"
#include "../AudioIO.h"
#include "../ImageManipulation.h" #include "../ImageManipulation.h"
#include "../Prefs.h" #include "../Prefs.h"
#include "../Project.h" #include "../Project.h"

View File

@ -510,6 +510,7 @@
<ClInclude Include="..\..\..\src\AudacityApp.h" /> <ClInclude Include="..\..\..\src\AudacityApp.h" />
<ClInclude Include="..\..\..\src\AudacityLogger.h" /> <ClInclude Include="..\..\..\src\AudacityLogger.h" />
<ClInclude Include="..\..\..\src\AudioIO.h" /> <ClInclude Include="..\..\..\src\AudioIO.h" />
<ClInclude Include="..\..\..\src\AudioIOListener.h" />
<ClInclude Include="..\..\..\src\AutoRecovery.h" /> <ClInclude Include="..\..\..\src\AutoRecovery.h" />
<ClInclude Include="..\..\..\src\BatchCommandDialog.h" /> <ClInclude Include="..\..\..\src\BatchCommandDialog.h" />
<ClInclude Include="..\..\..\src\BatchCommands.h" /> <ClInclude Include="..\..\..\src\BatchCommands.h" />