mirror of
https://github.com/cookiengineer/audacity
synced 2025-07-05 06:59:07 +02:00
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:
parent
932ca88255
commit
f3b91514d2
@ -209,36 +209,49 @@ int NoteTrack::DrawLabelControls(wxDC & dc, wxRect & r)
|
|||||||
// two choices: channel is enabled (to see and play) when button is in
|
// two choices: channel is enabled (to see and play) when button is in
|
||||||
// "up" position (original Audacity style) or in "down" position
|
// "up" position (original Audacity style) or in "down" position
|
||||||
//
|
//
|
||||||
#define CHANNEL_ON_IS_DOWN 0
|
#define CHANNEL_ON_IS_DOWN 1
|
||||||
#if !CHANNEL_ON_IS_DOWN
|
#if CHANNEL_ON_IS_DOWN
|
||||||
|
AColor::DarkMIDIChannel(&dc, channel);
|
||||||
|
#else
|
||||||
AColor::LightMIDIChannel(&dc, channel);
|
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.width - 1, box.y);
|
||||||
AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1);
|
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);
|
AColor::DarkMIDIChannel(&dc, channel);
|
||||||
|
#endif
|
||||||
AColor::Line(dc,
|
AColor::Line(dc,
|
||||||
box.x + box.width - 1, box.y,
|
box.x + box.width - 1, box.y,
|
||||||
box.x + box.width - 1, box.y + box.height - 1);
|
box.x + box.width - 1, box.y + box.height - 1);
|
||||||
AColor::Line(dc,
|
AColor::Line(dc,
|
||||||
box.x, box.y + box.height - 1,
|
box.x, box.y + box.height - 1,
|
||||||
box.x + box.width - 1, box.y + box.height - 1);
|
box.x + box.width - 1, box.y + box.height - 1);
|
||||||
#endif
|
|
||||||
} else {
|
} else {
|
||||||
AColor::MIDIChannel(&dc, 0);
|
AColor::MIDIChannel(&dc, 0);
|
||||||
dc.DrawRectangle(box);
|
dc.DrawRectangle(box);
|
||||||
#if CHANNEL_ON_IS_DOWN
|
#if CHANNEL_ON_IS_DOWN
|
||||||
AColor::LightMIDIChannel(&dc, 0);
|
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.width - 1, box.y);
|
||||||
AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1);
|
AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1);
|
||||||
|
|
||||||
|
#if CHANNEL_ON_IS_DOWN
|
||||||
AColor::DarkMIDIChannel(&dc, 0);
|
AColor::DarkMIDIChannel(&dc, 0);
|
||||||
|
#else
|
||||||
|
AColor::LightMIDIChannel(&dc, 0);
|
||||||
|
#endif
|
||||||
AColor::Line(dc,
|
AColor::Line(dc,
|
||||||
box.x + box.width - 1, box.y,
|
box.x + box.width - 1, box.y,
|
||||||
box.x + box.width - 1, box.y + box.height - 1);
|
box.x + box.width - 1, box.y + box.height - 1);
|
||||||
AColor::Line(dc,
|
AColor::Line(dc,
|
||||||
box.x, box.y + box.height - 1,
|
box.x, box.y + box.height - 1,
|
||||||
box.x + box.width - 1, box.y + box.height - 1);
|
box.x + box.width - 1, box.y + box.height - 1);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wxString t;
|
wxString t;
|
||||||
|
@ -17,6 +17,128 @@
|
|||||||
waveforms at least it needs to cache the samples that are
|
waveforms at least it needs to cache the samples that are
|
||||||
currently on-screen.
|
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.
|
||||||
|
|
||||||
*//*******************************************************************/
|
*//*******************************************************************/
|
||||||
|
|
||||||
|
@ -1146,7 +1146,7 @@ void TrackPanel::DoDrawIndicator(wxDC & dc)
|
|||||||
/// This method draws the cursor things, both in the
|
/// This method draws the cursor things, both in the
|
||||||
/// ruler as seen at the top of the screen, but also in each of the
|
/// ruler as seen at the top of the screen, but also in each of the
|
||||||
/// selected tracks.
|
/// selected tracks.
|
||||||
/// These are the 'vertical lines' through waves and ruler.
|
/// These are the 'vertical lines' through waves, notes, and ruler.
|
||||||
void TrackPanel::DrawCursor()
|
void TrackPanel::DrawCursor()
|
||||||
{
|
{
|
||||||
wxClientDC dc( this );
|
wxClientDC dc( this );
|
||||||
@ -3973,7 +3973,7 @@ void TrackPanel::HandleSliders(wxMouseEvent &event, bool pan)
|
|||||||
|
|
||||||
float newValue = slider->Get();
|
float newValue = slider->Get();
|
||||||
MixerBoard* pMixerBoard = this->GetMixerBoard(); // Update mixer board, too.
|
MixerBoard* pMixerBoard = this->GetMixerBoard(); // Update mixer board, too.
|
||||||
#ifdef USE_MIDI
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
if (mCapturedTrack->GetKind() == Track::Wave) {
|
if (mCapturedTrack->GetKind() == Track::Wave) {
|
||||||
#endif
|
#endif
|
||||||
WaveTrack *link = (WaveTrack *)mTracks->GetLink(mCapturedTrack);
|
WaveTrack *link = (WaveTrack *)mTracks->GetLink(mCapturedTrack);
|
||||||
@ -3994,7 +3994,7 @@ void TrackPanel::HandleSliders(wxMouseEvent &event, bool pan)
|
|||||||
if (pMixerBoard)
|
if (pMixerBoard)
|
||||||
pMixerBoard->UpdateGain((WaveTrack*)mCapturedTrack);
|
pMixerBoard->UpdateGain((WaveTrack*)mCapturedTrack);
|
||||||
}
|
}
|
||||||
#ifdef USE_MIDI
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
} else {
|
} else {
|
||||||
if (!pan) {
|
if (!pan) {
|
||||||
((NoteTrack *) mCapturedTrack)->SetGain(newValue);
|
((NoteTrack *) mCapturedTrack)->SetGain(newValue);
|
||||||
@ -4015,9 +4015,17 @@ void TrackPanel::HandleSliders(wxMouseEvent &event, bool pan)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.ButtonUp()) {
|
if (event.ButtonUp()) {
|
||||||
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
|
if (mCapturedTrack->GetKind() == Track::Wave) {
|
||||||
|
#endif
|
||||||
MakeParentPushState(pan ? _("Moved pan slider") : _("Moved gain slider"),
|
MakeParentPushState(pan ? _("Moved pan slider") : _("Moved gain slider"),
|
||||||
pan ? _("Pan") : _("Gain"),
|
pan ? _("Pan") : _("Gain"),
|
||||||
true /* consolidate */);
|
true /* consolidate */);
|
||||||
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
|
} else {
|
||||||
|
MakeParentPushState(_("Moved velocity slider"), _("Velocity"), true);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
SetCapturedTrack( NULL );
|
SetCapturedTrack( NULL );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5507,7 +5515,10 @@ void TrackPanel::DrawEverythingElse(wxDC * dc,
|
|||||||
clip.height - trackRect.y);
|
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);
|
HighlightFocusedTrack(dc, focusRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7997,7 +8008,7 @@ void TrackInfo::DrawSliders(wxDC *dc, WaveTrack *t, wxRect r)
|
|||||||
GetPanRect(r, panRect);
|
GetPanRect(r, panRect);
|
||||||
|
|
||||||
if (gainRect.y + gainRect.height < r.y + r.height - 19) {
|
if (gainRect.y + gainRect.height < r.y + r.height - 19) {
|
||||||
#ifdef USE_MIDI
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
GainSlider(index)->SetStyle(DB_SLIDER);
|
GainSlider(index)->SetStyle(DB_SLIDER);
|
||||||
#endif
|
#endif
|
||||||
GainSlider(index)->Move(wxPoint(gainRect.x, gainRect.y));
|
GainSlider(index)->Move(wxPoint(gainRect.x, gainRect.y));
|
||||||
|
@ -76,8 +76,8 @@ of an LWSlider or ASlider.
|
|||||||
#include <wx/sysopt.h>
|
#include <wx/sysopt.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "ASlider.h"
|
|
||||||
#include "../Experimental.h"
|
#include "../Experimental.h"
|
||||||
|
#include "ASlider.h"
|
||||||
|
|
||||||
#include "../AColor.h"
|
#include "../AColor.h"
|
||||||
#include "../ImageManipulation.h"
|
#include "../ImageManipulation.h"
|
||||||
@ -395,7 +395,7 @@ void LWSlider::SetStyle(int style)
|
|||||||
mMaxValue = SPEED_MAX;
|
mMaxValue = SPEED_MAX;
|
||||||
mStepValue = STEP_CONTINUOUS;
|
mStepValue = STEP_CONTINUOUS;
|
||||||
break;
|
break;
|
||||||
#ifdef USE_MIDI
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
case VEL_SLIDER:
|
case VEL_SLIDER:
|
||||||
mMinValue = VEL_MIN;
|
mMinValue = VEL_MIN;
|
||||||
mMaxValue = VEL_MAX;
|
mMaxValue = VEL_MAX;
|
||||||
@ -541,7 +541,7 @@ void LWSlider::CreatePopWin()
|
|||||||
wxString maxStr = mName + wxT(": 000000");
|
wxString maxStr = mName + wxT(": 000000");
|
||||||
|
|
||||||
if (mStyle == PAN_SLIDER || mStyle == DB_SLIDER || mStyle == SPEED_SLIDER
|
if (mStyle == PAN_SLIDER || mStyle == DB_SLIDER || mStyle == SPEED_SLIDER
|
||||||
#ifdef USE_MIDI
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
|| mStyle == VEL_SLIDER
|
|| mStyle == VEL_SLIDER
|
||||||
#endif
|
#endif
|
||||||
)
|
)
|
||||||
@ -917,7 +917,7 @@ void LWSlider::FormatPopWin()
|
|||||||
case SPEED_SLIDER:
|
case SPEED_SLIDER:
|
||||||
label.Printf(wxT("%s: %.2fx"), mName.c_str(), mCurrentValue);
|
label.Printf(wxT("%s: %.2fx"), mName.c_str(), mCurrentValue);
|
||||||
break;
|
break;
|
||||||
#ifdef USE_MIDI
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
case VEL_SLIDER:
|
case VEL_SLIDER:
|
||||||
label.Printf(wxT("%s: %s%d"), mName.c_str(),
|
label.Printf(wxT("%s: %s%d"), mName.c_str(),
|
||||||
(mCurrentValue > 0.0f ? _("+") : _("")),
|
(mCurrentValue > 0.0f ? _("+") : _("")),
|
||||||
@ -1543,7 +1543,7 @@ void ASlider::Set(float value)
|
|||||||
mLWSlider->Set(value);
|
mLWSlider->Set(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_MIDI
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
void ASlider::SetStyle(int style)
|
void ASlider::SetStyle(int style)
|
||||||
{
|
{
|
||||||
mStyle = style;
|
mStyle = style;
|
||||||
@ -1785,7 +1785,7 @@ wxAccStatus ASliderAx::GetValue(int childId, wxString* strValue)
|
|||||||
case SPEED_SLIDER:
|
case SPEED_SLIDER:
|
||||||
strValue->Printf( wxT("%.0f"), as->mLWSlider->mCurrentValue * 100 );
|
strValue->Printf( wxT("%.0f"), as->mLWSlider->mCurrentValue * 100 );
|
||||||
break;
|
break;
|
||||||
#ifdef USE_MIDI
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
case VEL_SLIDER:
|
case VEL_SLIDER:
|
||||||
strValue->Printf( wxT("%.0f"), as->mLWSlider->mCurrentValue);
|
strValue->Printf( wxT("%.0f"), as->mLWSlider->mCurrentValue);
|
||||||
break;
|
break;
|
||||||
|
@ -39,7 +39,7 @@ class Ruler;
|
|||||||
#define DB_SLIDER 2 // -36...36 dB
|
#define DB_SLIDER 2 // -36...36 dB
|
||||||
#define PAN_SLIDER 3 // -1.0...1.0
|
#define PAN_SLIDER 3 // -1.0...1.0
|
||||||
#define SPEED_SLIDER 4 // 0.01 ..3.0
|
#define SPEED_SLIDER 4 // 0.01 ..3.0
|
||||||
#ifdef USE_MIDI
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
#define VEL_SLIDER 5 // -50..50
|
#define VEL_SLIDER 5 // -50..50
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -257,7 +257,7 @@ class ASlider :public wxPanel
|
|||||||
|
|
||||||
float Get( bool convert = true );
|
float Get( bool convert = true );
|
||||||
void Set(float value);
|
void Set(float value);
|
||||||
#ifdef USE_MIDI
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
void SetStyle(int style);
|
void SetStyle(int style);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user