1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-15 23:59:37 +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;
mInputMixerWorks = false;
#endif
mLastPlaybackTimeMillis = 0;
}
AudioIO::~AudioIO()
@ -1127,7 +1129,8 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks,
AudioIOListener* listener,
bool playLooped /* = false */,
double cutPreviewGapStart /* = 0.0 */,
double cutPreviewGapLen /* = 0.0 */)
double cutPreviewGapLen, /* = 0.0 */
const double *pStartTime /* = 0 */)
{
if( IsBusy() )
return 0;
@ -1369,6 +1372,20 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks,
AILASetStartTime();
#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
// 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
@ -3648,6 +3665,8 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
chanCnt = 0;
}
gAudioIO->mLastPlaybackTimeMillis = ::wxGetLocalTimeMillis();
//
// 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);
bool ValidateDeviceNames();
class AUDACITY_DLL_API 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;
};
class AudioIOListener;
#define BAD_STREAM_TIME -1000000000.0
@ -113,7 +104,10 @@ class AUDACITY_DLL_API AudioIO {
AudioIOListener* listener,
bool playLooped = false,
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.
*
@ -123,7 +117,7 @@ class AUDACITY_DLL_API AudioIO {
void StopStream();
/** \brief Move the playback / recording position of the current stream
* 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,
* or recording.
@ -140,6 +134,8 @@ class AUDACITY_DLL_API AudioIO {
bool IsStreamActive();
bool IsStreamActive(int token);
wxLongLong GetLastPlaybackTime() const { return mLastPlaybackTimeMillis; }
#ifdef EXPERIMENTAL_MIDI_OUT
/** \brief Compute the current PortMidi timestamp time.
*
@ -517,6 +513,8 @@ private:
volatile bool mAudioThreadFillBuffersLoopRunning;
volatile bool mAudioThreadFillBuffersLoopActive;
wxLongLong mLastPlaybackTimeMillis;
#ifdef EXPERIMENTAL_MIDI_OUT
volatile bool mMidiThreadFillBuffersLoopRunning;
volatile bool mMidiThreadFillBuffersLoopActive;

View File

@ -105,6 +105,12 @@
// Paul Licameli (PRL) 5 Oct 2014
#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
// Replace RealFFT() and PowerSpectrum function to use (faster) RealFFTf function
#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/utils.h>
#include "AudioIO.h"
#include "LabelTrack.h"
#include "DirManager.h"
#include "Internat.h"

View File

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

View File

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

View File

@ -584,6 +584,12 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id,
mSelStartValid = false;
mSelStart = 0;
#ifdef EXPERIMENTAL_SCRUBBING
mScrubbing = false;
mLastScrubTime = 0;
mLastScrubPosition = 0;
#endif
}
TrackPanel::~TrackPanel()
@ -994,6 +1000,32 @@ void TrackPanel::OnTimer()
(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.
if (p->GetAudioIOToken()>0 &&
!gAudioIO->IsStreamActive(p->GetAudioIOToken()))
@ -1724,8 +1756,12 @@ void TrackPanel::SetCursorAndTipWhenSelectTool( Track * t,
wxString keyStr
(GetProject()->GetCommandManager()->GetKeyFromName(wxT("Preferences")));
// 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;
/* 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(
_("Multi-Tool Mode: %s for Mouse and Keyboard Preferences."),
keyStr.c_str());
@ -1977,6 +2013,52 @@ void TrackPanel::HandleSelect(wxMouseEvent & event)
}
} 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) {
delete mSnapManager;
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
/// and the mouse was just clicked.
void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
@ -2087,10 +2230,16 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
#endif
if (event.ShiftDown()
#ifdef USE_MIDI
&& !stretch
#ifdef EXPERIMENTAL_SCRUBBING
// Ctrl prevails over Shift with scrubbing enabled
&& !event.CmdDown()
#endif
) {
#ifdef USE_MIDI
&& !stretch
#endif
) {
// If the shift button is down and no track is selected yet,
// at least select the track we clicked into.
bool isAtLeastOneTrackSelected = false;
@ -2168,43 +2317,17 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
// and turn playback on.
else if(event.CmdDown()
#ifdef USE_MIDI
&& !stretch
&& !stretch
#endif
) {
#ifdef EXPERIMENTAL_SCRUBBING
// With scrubbing enabled, playback happens on button up, not down,
// and only if we do not start a scrub in the interim.
mScrubbing = false;
mLastScrubPosition = PositionToTime(event.m_x, GetLeftOffset());
#else
StartOrJumpPlayback(event);
#endif
) {
AudacityProject *p = GetActiveProject();
if (p) {
double clicktime = PositionToTime(event.m_x, GetLeftOffset());
const double t1 = mViewInfo->selectedRegion.t1();
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) ;
}
}
return;
}
//Make sure you are within the selected track
@ -2907,9 +3030,22 @@ void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack)
return;
// Also fuhggeddaboudit if we're not dragging and not autoscrolling.
if ((!event.Dragging() && !mAutoScrolling) || event.CmdDown())
if (!event.Dragging() && !mAutoScrolling)
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;
Track *pTrack = mCapturedTrack;
@ -3091,7 +3227,8 @@ wxInt64 TrackPanel::FrequencyToPosition(double frequency,
}
#endif
void SetIfNotNull( double * pValue, const double Value )
template<typename T>
inline void SetIfNotNull( T * pValue, const T Value )
{
if( pValue == NULL )
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
(wxMouseEvent & event, const Track *pTrack, const wxRect &rect,
bool mayDragWidth, bool onlyWithinSnapDistance,
@ -3110,28 +3284,19 @@ bool mayDragWidth, bool onlyWithinSnapDistance,
// May choose no boundary if onlyWithinSnapDistance is true.
// Otherwise choose the eligible boundary nearest the mouse click.
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 t1 = mViewInfo->selectedRegion.t1();
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
const double f0 = mViewInfo->selectedRegion.f0();
const double f1 = mViewInfo->selectedRegion.f1();
const double fc = mViewInfo->selectedRegion.fc();
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 chooseBottom = true;
bool chooseCenter = false;
@ -3146,14 +3311,20 @@ bool mayDragWidth, bool onlyWithinSnapDistance,
? FrequencyToPosition(f0, rect.y, rect.height,
wt->GetRate(), logF)
: rect.y + rect.height;
wxInt64 verticalDist = abs(int(event.m_y - bottomSel));
const wxInt64 topSel = (f1 >= 0)
? FrequencyToPosition(f1, rect.y, rect.height,
wt->GetRate(), logF)
wt->GetRate(), logF)
: rect.y;
const wxInt64 topDist = abs(int(event.m_y - topSel));
if (topDist < verticalDist)
chooseBottom = false, verticalDist = topDist;
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));
if (topDist < verticalDist)
chooseBottom = false, verticalDist = topDist;
}
if (fc > 0
#ifdef SPECTRAL_EDITING_ESC_KEY
&& mayDragWidth
@ -3202,19 +3373,7 @@ bool mayDragWidth, bool onlyWithinSnapDistance,
else
#endif
{
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;
}
return boundary;
}
}
@ -5805,8 +5964,8 @@ void TrackPanel::OnKeyDown(wxKeyEvent & event)
else {
// Handle ESC during frequency drag
wxMouseState state(::wxGetMouseState());
wxCoord xx = state.GetX(), yy = state.GetY();
ScreenToClient(&xx, &yy);
wxCoord yy = state.GetY();
ScreenToClient(NULL, &yy);
wxRect r;
if (wt == FindTrack(state.GetX(), yy, false, false, &r)) {
eFreqSelMode saveMode = mFreqSelMode;
@ -7333,113 +7492,42 @@ void TrackPanel::ScrollIntoView(int x)
void TrackPanel::OnCursorLeft( 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;
// 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();
// Contract selection from the right to the left
if( shift && ctrl )
{
// Reduce and constrain (counter-intuitive)
mViewInfo->selectedRegion.setT1(
std::max(mViewInfo->selectedRegion.t0(),
snapToTime
? GridMove(mViewInfo->selectedRegion.t1(), -multiplier)
: mViewInfo->selectedRegion.t1() -
multiplier / mViewInfo->zoom));
// Make sure it's visible.
ScrollIntoView( mViewInfo->selectedRegion.t1() );
Refresh( false );
}
// Extend selection toward the left
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() );
}
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 )
void TrackPanel::OnCursorRight(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
(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();
if( token > 0 && gAudioIO->IsStreamActive( token ) )
if (token > 0 && gAudioIO->IsStreamActive(token))
{
return;
}
@ -7450,78 +7538,119 @@ void TrackPanel::OnCursorRight( bool shift, bool ctrl, bool keyup )
// 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;
const wxLongLong curtime = ::wxGetLocalTimeMillis();
enum { MIN_INTERVAL = 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 snapToTime = GetActiveProject()->GetSnapTo();
int token = GetProject()->GetAudioIOToken();
// Contract selection from the left to the right
if( shift && ctrl )
if (shift && ctrl)
{
mLastSelectionAdjustment = curtime;
// Contract selection
// Reduce and constrain (counter-intuitive)
mViewInfo->selectedRegion.setT0(
std::min(mViewInfo->selectedRegion.t1(),
snapToTime
if (leftward) {
mViewInfo->selectedRegion.setT1(
std::max(mViewInfo->selectedRegion.t0(),
snapToTime
? GridMove(mViewInfo->selectedRegion.t1(), multiplier)
: mViewInfo->selectedRegion.t1() +
multiplier * quietSeekStepPositive));
// Make sure it's visible.
ScrollIntoView(mViewInfo->selectedRegion.t1());
}
else {
mViewInfo->selectedRegion.setT0(
std::min(mViewInfo->selectedRegion.t1(),
snapToTime
? GridMove(mViewInfo->selectedRegion.t0(), multiplier)
: mViewInfo->selectedRegion.t0() +
multiplier / mViewInfo->zoom));
multiplier * quietSeekStepPositive));
// Make sure new position is in view.
ScrollIntoView( mViewInfo->selectedRegion.t0() );
Refresh( false );
// Make sure new position is in view.
ScrollIntoView(mViewInfo->selectedRegion.t0());
}
Refresh(false);
}
// Extend selection toward the right
else if( shift )
{
// If playing, reposition a long amount of time
int token = GetProject()->GetAudioIOToken();
if( token > 0 && gAudioIO->IsStreamActive( token ) )
{
gAudioIO->SeekStream(mSeekLong);
else if (token > 0 && gAudioIO->IsStreamActive(token)) {
#ifdef EXPERIMENTAL_IMPROVED_SEEKING
if (gAudioIO->GetLastPlaybackTime() < mLastSelectionAdjustment) {
// Allow time for the last seek to output a buffer before
// discarding samples again
// Do not advance mLastSelectionAdjustment
return;
}
#endif
mLastSelectionAdjustment = curtime;
// Expand and constrain
double end = mTracks->GetEndTime();
mViewInfo->selectedRegion.setT1(
std::min(end,
snapToTime
? GridMove(mViewInfo->selectedRegion.t1(), multiplier)
: mViewInfo->selectedRegion.t1() + multiplier/mViewInfo->zoom));
// Ignore the multiplier for the quiet case
multiplier = (fast && mayAccelerateAudio) ? LARGER_MULTIPLIER : 1;
if (leftward)
multiplier = -multiplier;
// Make sure new position is in view.
ScrollIntoView( mViewInfo->selectedRegion.t1() );
Refresh( false );
// If playing, reposition
gAudioIO->SeekStream(multiplier * audioSeekStepPositive);
return;
}
else if (shift)
{
mLastSelectionAdjustment = curtime;
// Extend selection
// Expand and constrain
if (leftward) {
mViewInfo->selectedRegion.setT0(
std::max(0.0,
snapToTime
? GridMove(mViewInfo->selectedRegion.t0(), multiplier)
: mViewInfo->selectedRegion.t0() +
multiplier * quietSeekStepPositive));
// Make sure it's visible.
ScrollIntoView(mViewInfo->selectedRegion.t0());
}
else {
double end = mTracks->GetEndTime();
mViewInfo->selectedRegion.setT1(
std::min(end,
snapToTime
? GridMove(mViewInfo->selectedRegion.t1(), multiplier)
: mViewInfo->selectedRegion.t1() +
multiplier * quietSeekStepPositive));
// Make sure new position is in view.
ScrollIntoView(mViewInfo->selectedRegion.t1());
}
Refresh(false);
}
// Move the cursor toward the right
else
{
// If playing, reposition a short amount of time
int token = GetProject()->GetAudioIOToken();
if( token > 0 && gAudioIO->IsStreamActive( token ) )
{
gAudioIO->SeekStream(mSeekShort);
return;
}
mLastSelectionAdjustment = curtime;
// Move the cursor
// Already in cursor mode?
if (mViewInfo->selectedRegion.isPoint())
{
// Move and constrain
double end = mTracks->GetEndTime();
mViewInfo->selectedRegion.setT1(
std::min(end,
snapToTime
? GridMove(mViewInfo->selectedRegion.t1(), multiplier)
: mViewInfo->selectedRegion.t1() +
multiplier / mViewInfo->zoom),
mViewInfo->selectedRegion.setT0(
std::max(0.0,
std::min(end,
snapToTime
? GridMove(mViewInfo->selectedRegion.t0(), multiplier)
: mViewInfo->selectedRegion.t0() + multiplier * quietSeekStepPositive
)
),
false);
mViewInfo->selectedRegion.collapseToT1();
mViewInfo->selectedRegion.collapseToT0();
// Move the visual cursor
DrawCursor();
@ -7529,12 +7658,15 @@ void TrackPanel::OnCursorRight( bool shift, bool ctrl, bool keyup )
else
{
// Transition to cursor mode.
mViewInfo->selectedRegion.collapseToT1();
Refresh( false );
if (leftward)
mViewInfo->selectedRegion.collapseToT0();
else
mViewInfo->selectedRegion.collapseToT1();
Refresh(false);
}
// 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
void TrackPanel::OnCursorMove(bool forward, bool jump, bool longjump )
{
// 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;
// PRL: nobody calls this yet with !jump
float direction = -1;
if (forward) {
direction = 1;
}
float mSeek;
double positiveSeekStep;
if (jump) {
if (!longjump) {
mSeek = mSeekShort;
positiveSeekStep = mSeekShort;
} else {
mSeek = mSeekLong;
positiveSeekStep = mSeekLong;
}
} else {
mSeek = multiplier / mViewInfo->zoom;
positiveSeekStep = 1.0 / mViewInfo->zoom;
}
mSeek *= direction;
bool mayAccelerate = !jump;
SeekLeftOrRight
(!forward, false, false, false,
0, mayAccelerate, mayAccelerate,
positiveSeekStep, positiveSeekStep);
// If playing, reposition a short amount of time
int token = GetProject()->GetAudioIOToken();
if( token > 0 && gAudioIO->IsStreamActive( token ) )
{
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,

View File

@ -310,6 +310,10 @@ class AUDACITY_DLL_API TrackPanel:public wxPanel {
// AS: Selection handling
virtual void HandleSelect(wxMouseEvent & event);
virtual void SelectionHandleDrag(wxMouseEvent &event, Track *pTrack);
void StartOrJumpPlayback(wxMouseEvent &event);
#ifdef EXPERIMENTAL_SCRUBBING
void StartScrubbing(double position);
#endif
virtual void SelectionHandleClick(wxMouseEvent &event,
Track* pTrack, wxRect r);
virtual void StartSelection (int mouseXCoordinate, int trackLeftEdge);
@ -317,6 +321,12 @@ class AUDACITY_DLL_API TrackPanel:public wxPanel {
Track *pTrack);
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
void StartSnappingFreqSelection (WaveTrack *pTrack);
void MoveSnappingFreqSelection (int mouseYCoordinate,
@ -688,6 +698,9 @@ protected:
SBBottom, SBTop, SBCenter, SBWidth,
#endif
};
SelectionBoundary ChooseTimeBoundary
(double selend, bool onlyWithinSnapDistance,
wxInt64 *pPixelDist = NULL, double *pPinValue = NULL) const;
SelectionBoundary ChooseBoundary
(wxMouseEvent & event, const Track *pTrack,
const wxRect &rect,
@ -739,6 +752,12 @@ protected:
int mMoveUpThreshold;
int mMoveDownThreshold;
#ifdef EXPERIMENTAL_SCRUBBING
bool mScrubbing;
wxLongLong mLastScrubTime; // milliseconds
double mLastScrubPosition;
#endif
wxCursor *mArrowCursor;
wxCursor *mPencilCursor;
wxCursor *mSelectCursor;

View File

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

View File

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

View File

@ -67,7 +67,10 @@ class ControlToolBar:public ToolBar {
void PlayPlayRegion(double t0, double t1,
bool looped = 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();
// Stop playing

View File

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

File diff suppressed because it is too large Load Diff