NoteTrack channel buttons have prettier graphics in down position which now means ON; VEL_SLIDER depends on EXPERIMENTAL_MIDI_OUT (not USE_MIDI); velocity slider changes are labeled as such to Undo manager; fixed an OS X bug where backing bitmap does not get yellow highlight and then highlight on screen gets clobbered when user moves gain sliders (!); Added a high-level overview of redraw to TrackArtist.cpp.

This commit is contained in:
rbdannenberg 2010-10-28 17:34:35 +00:00
parent 932ca88255
commit f3b91514d2
5 changed files with 164 additions and 18 deletions

View File

@ -209,36 +209,49 @@ int NoteTrack::DrawLabelControls(wxDC & dc, wxRect & r)
// two choices: channel is enabled (to see and play) when button is in
// "up" position (original Audacity style) or in "down" position
//
#define CHANNEL_ON_IS_DOWN 0
#if !CHANNEL_ON_IS_DOWN
#define CHANNEL_ON_IS_DOWN 1
#if CHANNEL_ON_IS_DOWN
AColor::DarkMIDIChannel(&dc, channel);
#else
AColor::LightMIDIChannel(&dc, channel);
#endif
AColor::Line(dc, box.x, box.y, box.x + box.width - 1, box.y);
AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1);
#if CHANNEL_ON_IS_DOWN
AColor::LightMIDIChannel(&dc, channel);
#else
AColor::DarkMIDIChannel(&dc, channel);
#endif
AColor::Line(dc,
box.x + box.width - 1, box.y,
box.x + box.width - 1, box.y + box.height - 1);
AColor::Line(dc,
box.x, box.y + box.height - 1,
box.x + box.width - 1, box.y + box.height - 1);
#endif
} else {
AColor::MIDIChannel(&dc, 0);
dc.DrawRectangle(box);
#if CHANNEL_ON_IS_DOWN
AColor::LightMIDIChannel(&dc, 0);
#else
AColor::DarkMIDIChannel(&dc, 0);
#endif
AColor::Line(dc, box.x, box.y, box.x + box.width - 1, box.y);
AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1);
#if CHANNEL_ON_IS_DOWN
AColor::DarkMIDIChannel(&dc, 0);
#else
AColor::LightMIDIChannel(&dc, 0);
#endif
AColor::Line(dc,
box.x + box.width - 1, box.y,
box.x + box.width - 1, box.y + box.height - 1);
AColor::Line(dc,
box.x, box.y + box.height - 1,
box.x + box.width - 1, box.y + box.height - 1);
#endif
}
wxString t;

View File

@ -17,6 +17,128 @@
waveforms at least it needs to cache the samples that are
currently on-screen.
<b>How Audacity Redisplay Works \n
Roger Dannenberg</b> \n
Oct 2010 \n
This is a brief guide to Audacity redisplay -- it may not be complete. It
is my attempt to understand the complicated graphics strategy.
One basic idea is that redrawing waveforms is rather slow, so Audacity
saves waveform images in bitmaps to make redrawing faster. In particular,
during audio playback (and recording), the vertical time indicator is
drawn over the waveform about 20 times per second. To avoid unnecessary
computation, the indicator is erased by copying a column of pixels from
a bitmap image of the waveform. Notice that this implies a two-stage
process: first, waveforms are drawn to the bitmp; then, the bitmap
(or pieces of it) are copied to the screen, perhaps along with other
graphics.
The bitmap is for the entire track panel, i.e. multiple tracks, and
includes things like the Gain and Pan slders to the left of the
waveform images.
The screen update uses a mixture of direct drawing and indirect paint
events. The "normal" way to update a graphical display is to call
the Refresh() method when something invalidates the screen. Later, the
system calls OnPaint(), which the application overrides to (re)draw the
screen. In wxWidgets, you can also draw directly to the screen without
calling Refresh() and without waiting for OnPaint() to be called.
I would expect there to be a 2-level invalidation scheme: Some changes
invalidate the bitmap, forcing a bitmap redraw *and* a screen redraw.
Other changes merely update the screen using pre-existing bitmaps. In
Audacity, the "2-level" invalidation works like this: Anything
that invalidates the bitmap calls TrackPanel::Refresh(), which
has an eraseBackground parameter. This flag says to redraw the
bitmap when OnPaint() is called. If eraseBackground is false, the
existing bitmap can be used for waveform imges. Audacity also
draws directly to the screen to update the time indicator during
playback. To move the indicator, one column of pixels is drawn to
the screen to remove the indicator. Then the indicator is drawn at
a new time location.
The track panel consists of many components. The tree of calls that
update the bitmap looks like this:
\code
TrackPanel::DrawTracks(), calls
TrackArtist::DrawTracks();
TrackPanel::DrawEverythingElse();
for each track,
TrackPanel::DrawOutside();
TrackPanel::DrawOutsideOfTrack();
TrackPanel::DrawBordersAroundTrack();
TrackPanel::DrawShadow();
TrackInfo::DrawCloseBox();
TrackInfo::DrawTitleBar();
TrackInfo::DrawMinimize();
TrackInfo::DrawBordersWithin();
various TrackInfo sliders and buttons
TrackArtist::DrawVRuler();
TrackPanel::DrawZooming();
draws horizontal dashed lines during zoom-drag
TrackPanel::HighlightFocusedTrack();
draws yellow highlight on selected track
draw snap guidelines if any
\endcode
After drawing the bitmap and blitting the bitmap to the screen,
the following calls are (sometimes) made. To keep track of what has
been drawn on screen over the bitmap images,
\li \c mLastCursor is the position of the vertical line representing sel0,
the selected time position
\li \c mLastIndicator is the position of the moving vertical line during
playback
\code
TrackPanel::DoDrawIndicator();
copy pixel column from bitmap to screen to erase indicator line
TrackPanel::DoDrawCursor(); [if mLastCursor == mLastIndicator]
TrackPanel::DisplaySelection();
AdornedRulerPanel::DrawIndicator(); [not part of TrackPanel graphics]
draw indicator on each track
TrackPanel::DoDrawCursor();
draw cursor on each track [at mViewInfo->sel0]
AdornedRulerPanel::DrawCursor(); [not part of TrackPanel graphics]
TrackPanel::DisplaySelection();
\endcode
To move the indicator, TrackPanel::OnTimer() calls the following, using
a drawing context (DC) for the screen. (Refresh is not called to create
an OnPaint event. Instead, drawing is direct to the screen.)
\code
TrackPanel::DrawIndicator();
TrackPanel::DoDrawIndicator();
\endcode
Notice that TrackPanel::DrawZooming(), TrackPanel::HighlightFocusedTrack(),
and snap guidelines could be drawn directly to the screen rather than to
the bitmap, generally eliminating redraw work.
One problem is slider udpates. Sliders are in the left area of the track
panel. They are not wxWindows like wxSliders, but instead are just drawn
on the TrackPanel. When slider state changes, *all* tracks do a full
refresh, including recomputing the backing store. It would make more sense
to just invalidate the region containing the slider. However, doing that
would require either incrementally updating the bitmap (not currently done),
or maintaining the sliders and other track info on the screen and not in
the bitmap.
In my opinion, the bitmap should contain only the waveform, note, and
label images along with gray selection highlights. The track info
(sliders, buttons, title, etc.), track selection highlight, cursor, and
indicator should be drawn in the normal way, and clipping regions should
be used to avoid excessive copying of bitmaps (say, when sliders move),
or excessive redrawing of track info widgets (say, when scrolling occurs).
This is a fairly tricky code change since it requires careful specification
of what and where redraw should take place when any state changes. One
surprising finding is that NoteTrack display is slow compared to WaveTrack
display. Each note takes some time to gather attributes and select colors,
and while audio draws two amplitudes per horizontal pixels, large MIDI
scores can have more notes than horizontal pixels. This can make slider
changes very sluggish, but this can also be a problem with many
audio tracks.
*//*******************************************************************/

View File

@ -1052,7 +1052,7 @@ void TrackPanel::DoDrawIndicator(wxDC & dc)
if (x >= w) {
x = w - 1;
}
dc.Blit( x, 0, 1, mBacking->GetHeight(), &mBackingDC, x, 0 );
}
@ -1146,7 +1146,7 @@ void TrackPanel::DoDrawIndicator(wxDC & dc)
/// This method draws the cursor things, both in the
/// ruler as seen at the top of the screen, but also in each of the
/// selected tracks.
/// These are the 'vertical lines' through waves and ruler.
/// These are the 'vertical lines' through waves, notes, and ruler.
void TrackPanel::DrawCursor()
{
wxClientDC dc( this );
@ -3973,7 +3973,7 @@ void TrackPanel::HandleSliders(wxMouseEvent &event, bool pan)
float newValue = slider->Get();
MixerBoard* pMixerBoard = this->GetMixerBoard(); // Update mixer board, too.
#ifdef USE_MIDI
#ifdef EXPERIMENTAL_MIDI_OUT
if (mCapturedTrack->GetKind() == Track::Wave) {
#endif
WaveTrack *link = (WaveTrack *)mTracks->GetLink(mCapturedTrack);
@ -3994,7 +3994,7 @@ void TrackPanel::HandleSliders(wxMouseEvent &event, bool pan)
if (pMixerBoard)
pMixerBoard->UpdateGain((WaveTrack*)mCapturedTrack);
}
#ifdef USE_MIDI
#ifdef EXPERIMENTAL_MIDI_OUT
} else {
if (!pan) {
((NoteTrack *) mCapturedTrack)->SetGain(newValue);
@ -4015,9 +4015,17 @@ void TrackPanel::HandleSliders(wxMouseEvent &event, bool pan)
}
if (event.ButtonUp()) {
#ifdef EXPERIMENTAL_MIDI_OUT
if (mCapturedTrack->GetKind() == Track::Wave) {
#endif
MakeParentPushState(pan ? _("Moved pan slider") : _("Moved gain slider"),
pan ? _("Pan") : _("Gain"),
true /* consolidate */);
#ifdef EXPERIMENTAL_MIDI_OUT
} else {
MakeParentPushState(_("Moved velocity slider"), _("Velocity"), true);
}
#endif
SetCapturedTrack( NULL );
}
}
@ -5507,7 +5515,10 @@ void TrackPanel::DrawEverythingElse(wxDC * dc,
clip.height - trackRect.y);
}
if (GetFocusedTrack() != NULL && wxWindow::FindFocus() == this) {
// Previous code that caused highlight NOT to be drawn on backing
// bitmap due to wxWindow::FindFocus() not returning "this" on Mac:
// if (GetFocusedTrack() != NULL) && wxWindow::FindFocus() == this) {
if (GetFocusedTrack() != NULL) {
HighlightFocusedTrack(dc, focusRect);
}
@ -7997,7 +8008,7 @@ void TrackInfo::DrawSliders(wxDC *dc, WaveTrack *t, wxRect r)
GetPanRect(r, panRect);
if (gainRect.y + gainRect.height < r.y + r.height - 19) {
#ifdef USE_MIDI
#ifdef EXPERIMENTAL_MIDI_OUT
GainSlider(index)->SetStyle(DB_SLIDER);
#endif
GainSlider(index)->Move(wxPoint(gainRect.x, gainRect.y));

View File

@ -76,8 +76,8 @@ of an LWSlider or ASlider.
#include <wx/sysopt.h>
#endif
#include "ASlider.h"
#include "../Experimental.h"
#include "ASlider.h"
#include "../AColor.h"
#include "../ImageManipulation.h"
@ -395,7 +395,7 @@ void LWSlider::SetStyle(int style)
mMaxValue = SPEED_MAX;
mStepValue = STEP_CONTINUOUS;
break;
#ifdef USE_MIDI
#ifdef EXPERIMENTAL_MIDI_OUT
case VEL_SLIDER:
mMinValue = VEL_MIN;
mMaxValue = VEL_MAX;
@ -541,7 +541,7 @@ void LWSlider::CreatePopWin()
wxString maxStr = mName + wxT(": 000000");
if (mStyle == PAN_SLIDER || mStyle == DB_SLIDER || mStyle == SPEED_SLIDER
#ifdef USE_MIDI
#ifdef EXPERIMENTAL_MIDI_OUT
|| mStyle == VEL_SLIDER
#endif
)
@ -917,7 +917,7 @@ void LWSlider::FormatPopWin()
case SPEED_SLIDER:
label.Printf(wxT("%s: %.2fx"), mName.c_str(), mCurrentValue);
break;
#ifdef USE_MIDI
#ifdef EXPERIMENTAL_MIDI_OUT
case VEL_SLIDER:
label.Printf(wxT("%s: %s%d"), mName.c_str(),
(mCurrentValue > 0.0f ? _("+") : _("")),
@ -1543,7 +1543,7 @@ void ASlider::Set(float value)
mLWSlider->Set(value);
}
#ifdef USE_MIDI
#ifdef EXPERIMENTAL_MIDI_OUT
void ASlider::SetStyle(int style)
{
mStyle = style;
@ -1785,7 +1785,7 @@ wxAccStatus ASliderAx::GetValue(int childId, wxString* strValue)
case SPEED_SLIDER:
strValue->Printf( wxT("%.0f"), as->mLWSlider->mCurrentValue * 100 );
break;
#ifdef USE_MIDI
#ifdef EXPERIMENTAL_MIDI_OUT
case VEL_SLIDER:
strValue->Printf( wxT("%.0f"), as->mLWSlider->mCurrentValue);
break;

View File

@ -39,7 +39,7 @@ class Ruler;
#define DB_SLIDER 2 // -36...36 dB
#define PAN_SLIDER 3 // -1.0...1.0
#define SPEED_SLIDER 4 // 0.01 ..3.0
#ifdef USE_MIDI
#ifdef EXPERIMENTAL_MIDI_OUT
#define VEL_SLIDER 5 // -50..50
#endif
@ -257,7 +257,7 @@ class ASlider :public wxPanel
float Get( bool convert = true );
void Set(float value);
#ifdef USE_MIDI
#ifdef EXPERIMENTAL_MIDI_OUT
void SetStyle(int style);
#endif