Refactor note track rendering/position height code into own class

This will make later refactoring easier, and also fixes some const-correctness issues
This commit is contained in:
Pokechu22 2018-01-14 12:08:13 -08:00 committed by James Crook
parent 3b312f9d1b
commit 8e8d838a08
4 changed files with 101 additions and 114 deletions

View File

@ -214,9 +214,11 @@ double NoteTrack::GetEndTime() const
void NoteTrack::DoSetHeight(int h)
{
auto oldHeight = GetHeight();
auto oldMargin = GetNoteMargin(oldHeight);
NoteTrackDisplayData oldData = NoteTrackDisplayData(this, wxRect(0, 0, 1, oldHeight));
auto oldMargin = oldData.GetNoteMargin();
PlayableTrack::DoSetHeight(h);
auto margin = GetNoteMargin(h);
NoteTrackDisplayData newData = NoteTrackDisplayData(this, wxRect(0, 0, 1, h));
auto margin = newData.GetNoteMargin();
Zoom(
wxRect{ 0, 0, 1, h }, // only height matters
h - margin - 1, // preserve bottom note
@ -979,17 +981,17 @@ void NoteTrack::Zoom(const wxRect &rect, int y, float multiplier, bool center)
// Construct track rectangle to map pitch to screen coordinates
// Only y and height are needed:
wxRect trackRect(0, rect.GetY(), 1, rect.GetHeight());
PrepareIPitchToY(trackRect);
int clickedPitch = YToIPitch(y);
NoteTrackDisplayData data = NoteTrackDisplayData(this, trackRect);
int clickedPitch = data.YToIPitch(y);
// zoom by changing the pitch height
SetPitchHeight(rect.height, mPitchHeight * multiplier);
PrepareIPitchToY(trackRect); // update because mPitchHeight changed
NoteTrackDisplayData newData = NoteTrackDisplayData(this, trackRect); // update because mPitchHeight changed
if (center) {
int newCenterPitch = YToIPitch(rect.GetY() + rect.GetHeight() / 2);
int newCenterPitch = newData.YToIPitch(rect.GetY() + rect.GetHeight() / 2);
// center the pitch that the user clicked on
SetBottomNote(mBottomNote + (clickedPitch - newCenterPitch));
} else {
int newClickedPitch = YToIPitch(y);
int newClickedPitch = newData.YToIPitch(y);
// align to keep the pitch that the user clicked on in the same place
SetBottomNote(mBottomNote + (clickedPitch - newClickedPitch));
}
@ -999,9 +1001,9 @@ void NoteTrack::Zoom(const wxRect &rect, int y, float multiplier, bool center)
void NoteTrack::ZoomTo(const wxRect &rect, int start, int end)
{
wxRect trackRect(0, rect.GetY(), 1, rect.GetHeight());
PrepareIPitchToY(trackRect);
int topPitch = YToIPitch(start);
int botPitch = YToIPitch(end);
NoteTrackDisplayData data = NoteTrackDisplayData(this, trackRect);
int topPitch = data.YToIPitch(start);
int botPitch = data.YToIPitch(end);
if (topPitch < botPitch) { // swap
int temp = topPitch; topPitch = botPitch; botPitch = temp;
}
@ -1013,7 +1015,20 @@ void NoteTrack::ZoomTo(const wxRect &rect, int start, int end)
Zoom(rect, (start + end) / 2, trialPitchHeight / mPitchHeight, true);
}
int NoteTrack::YToIPitch(int y)
NoteTrackDisplayData::NoteTrackDisplayData(const NoteTrack* track, const wxRect &r)
{
mPitchHeight = track->mPitchHeight;
mMargin = std::min(r.height / 4, (GetPitchHeight(1) + 1) / 2);
mBottom = r.y + r.height - GetNoteMargin() - 1 - GetPitchHeight(1) +
(track->GetBottomNote() / 12) * GetOctaveHeight() +
GetNotePos(track->GetBottomNote() % 12);
}
int NoteTrackDisplayData::IPitchToY(int p) const
{ return mBottom - (p / 12) * GetOctaveHeight() - GetNotePos(p % 12); }
int NoteTrackDisplayData::YToIPitch(int y) const
{
y = mBottom - y; // pixels above pitch 0
int octave = (y / GetOctaveHeight());

View File

@ -64,9 +64,12 @@ using QuantizedTimeAndBeat = std::pair< double, double >;
class StretchHandle;
class NoteTrackDisplayData;
class AUDACITY_DLL_API NoteTrack final
: public NoteTrackBase
{
friend class NoteTrackDisplayData;
public:
NoteTrack(const std::shared_ptr<DirManager> &projDirManager);
virtual ~NoteTrack();
@ -145,44 +148,6 @@ class AUDACITY_DLL_API NoteTrack final
/// If center is true, the result will be centered at y.
void Zoom(const wxRect &rect, int y, float multiplier, bool center);
void ZoomTo(const wxRect &rect, int start, int end);
int GetNoteMargin(int height) const
{ return std::min(height / 4, (GetPitchHeight(1) + 1) / 2); }
int GetOctaveHeight() const { return GetPitchHeight(12) + 2; }
// call this once before a series of calls to IPitchToY(). It
// sets mBottom to offset of octave 0 so that mBottomNote
// is located at r.y + r.height - (GetNoteMargin() + 1 + GetPitchHeight())
void PrepareIPitchToY(const wxRect &r) const {
mBottom =
r.y + r.height - GetNoteMargin(r.height) - 1 - GetPitchHeight(1) +
(mBottomNote / 12) * GetOctaveHeight() +
GetNotePos(mBottomNote % 12);
}
// IPitchToY returns Y coordinate of top of pitch p
int IPitchToY(int p) const {
return mBottom - (p / 12) * GetOctaveHeight() - GetNotePos(p % 12);
}
// compute the window coordinate of the bottom of an octave: This is
// the bottom of the line separating B and C.
int GetOctaveBottom(int oct) const {
return IPitchToY(oct * 12) + GetPitchHeight(1) + 1;
}
// Y coordinate for given floating point pitch (rounded to int)
int PitchToY(double p) const {
return IPitchToY((int) (p + 0.5));
}
// Integer pitch corresponding to a Y coordinate
int YToIPitch(int y);
// map pitch class number (0-11) to pixel offset from bottom of octave
// (the bottom of the black line between B and C) to the top of the
// note. Note extra pixel separates B(11)/C(0) and E(4)/F(5).
int GetNotePos(int p) const
{ return 1 + GetPitchHeight(p + 1) + (p > 4); }
// get pixel offset to top of ith black key note
int GetBlackPos(int i) const { return GetNotePos(i * 2 + 1 + (i > 1)); }
// GetWhitePos tells where to draw lines between keys as an offset from
// GetOctaveBottom. GetWhitePos(0) returns 1, which matches the location
// of the line separating B and C
int GetWhitePos(int i) const { return 1 + (i * GetOctaveHeight()) / 7; }
void SetBottomNote(int note)
{
if (note < 0)
@ -254,10 +219,11 @@ class AUDACITY_DLL_API NoteTrack final
float mVelocity; // velocity offset
#endif
// mBottom is the Y offset of pitch 0 (normally off screen)
mutable int mBottom;
int mBottomNote;
#if 0
// Also unused from vertical scrolling
int mStartBottomNote;
#endif
// Remember continuous variation for zooming,
// but it is rounded off whenever drawing:
@ -275,6 +241,47 @@ protected:
std::shared_ptr<TrackVRulerControls> DoGetVRulerControls() override;
};
class NoteTrackDisplayData {
private:
float mPitchHeight;
// mBottom is the Y offset of pitch 0 (normally off screen)
// Used so that mBottomNote is located at
// mY + mHeight - (GetNoteMargin() + 1 + GetPitchHeight())
int mBottom;
int mMargin;
public:
NoteTrackDisplayData(const NoteTrack* track, const wxRect &r);
int GetPitchHeight(int factor) const
{ return std::max(1, (int)(factor * mPitchHeight)); }
int GetNoteMargin() const { return mMargin; };
int GetOctaveHeight() const { return GetPitchHeight(12) + 2; }
// IPitchToY returns Y coordinate of top of pitch p
int IPitchToY(int p) const;
// compute the window coordinate of the bottom of an octave: This is
// the bottom of the line separating B and C.
int GetOctaveBottom(int oct) const {
return IPitchToY(oct * 12) + GetPitchHeight(1) + 1;
}
// Y coordinate for given floating point pitch (rounded to int)
int PitchToY(double p) const {
return IPitchToY((int) (p + 0.5));
}
// Integer pitch corresponding to a Y coordinate
int YToIPitch(int y) const;
// map pitch class number (0-11) to pixel offset from bottom of octave
// (the bottom of the black line between B and C) to the top of the
// note. Note extra pixel separates B(11)/C(0) and E(4)/F(5).
int GetNotePos(int p) const
{ return 1 + GetPitchHeight(p + 1) + (p > 4); }
// get pixel offset to top of ith black key note
int GetBlackPos(int i) const { return GetNotePos(i * 2 + 1 + (i > 1)); }
// GetWhitePos tells where to draw lines between keys as an offset from
// GetOctaveBottom. GetWhitePos(0) returns 1, which matches the location
// of the line separating B and C
int GetWhitePos(int i) const { return 1 + (i * GetOctaveHeight()) / 7; }
};
#endif // USE_MIDI
#ifndef SONIFY

View File

@ -138,36 +138,6 @@ namespace {
}
#endif
#ifdef USE_MIDI
/*
const int octaveHeight = 62;
const int blackPos[5] = { 6, 16, 32, 42, 52 };
const int whitePos[7] = { 0, 9, 17, 26, 35, 44, 53 };
const int notePos[12] = { 1, 6, 11, 16, 21, 27,
32, 37, 42, 47, 52, 57 };
// map pitch number to window coordinate of the *top* of the note
// Note the "free" variable bottom, which is assumed to be a local
// variable set to the offset of pitch 0 relative to the window
#define IPITCH_TO_Y(t, p) (bottom - ((p) / 12) * octaveHeight - \
notePos[(p) % 12] - (t)->GetPitchHeight())
// GetBottom is called from a couple of places to compute the hypothetical
// coordinate of the bottom of pitch 0 in window coordinates. See
// IPITCH_TO_Y above, which computes coordinates relative to GetBottom()
// Note the -NOTE_MARGIN, which leaves a little margin to draw notes that
// are out of bounds. I'm not sure why the -2 is necessary.
int TrackArt::GetBottom(NoteTrack *t, const wxRect &rect)
{
int bottomNote = t->GetBottomNote();
int bottom = rect.y + rect.height - 2 - t->GetNoteMargin() +
((bottomNote / 12) * octaveHeight + notePos[bottomNote % 12]);
return bottom;
}
*/
#endif // USE_MIDI
TrackArtist::TrackArtist( TrackPanel *parent_ )
: parent( parent_ )
{
@ -549,8 +519,7 @@ void TrackArt::DrawVRuler
rect.y += 1;
rect.height -= 1;
//int bottom = GetBottom(track, rect);
track->PrepareIPitchToY(rect);
NoteTrackDisplayData data = NoteTrackDisplayData(track, rect);
wxPen hilitePen;
hilitePen.SetColour(120, 120, 120);
@ -568,13 +537,13 @@ void TrackArt::DrawVRuler
dc->SetFont(labelFont);
int octave = 0;
int obottom = track->GetOctaveBottom(octave);
int marg = track->GetNoteMargin(rect.height);
//IPITCH_TO_Y(octave * 12) + PITCH_HEIGHT + 1;
int obottom = data.GetOctaveBottom(octave);
int marg = data.GetNoteMargin();
while (obottom >= rect.y) {
dc->SetPen(*wxBLACK_PEN);
for (int white = 0; white < 7; white++) {
int pos = track->GetWhitePos(white);
int pos = data.GetWhitePos(white);
if (obottom - pos > rect.y + marg + 1 &&
// don't draw too close to margin line -- it's annoying
obottom - pos < rect.y + rect.height - marg - 3)
@ -582,11 +551,11 @@ void TrackArt::DrawVRuler
rect.x + rect.width, obottom - pos);
}
wxRect br = rect;
br.height = track->GetPitchHeight(1);
br.height = data.GetPitchHeight(1);
br.x++;
br.width = 17;
for (int black = 0; black < 5; black++) {
br.y = obottom - track->GetBlackPos(black);
br.y = obottom - data.GetBlackPos(black);
if (br.y > rect.y + marg - 2 && br.y + br.height < rect.y + rect.height - marg) {
dc->SetPen(hilitePen);
dc->DrawRectangle(br);
@ -614,7 +583,7 @@ void TrackArt::DrawVRuler
obottom - height + 2);
}
}
obottom = track->GetOctaveBottom(++octave);
obottom = data.GetOctaveBottom(++octave);
}
// draw lines delineating the out-of-bounds margins
dc->SetPen(*wxBLACK_PEN);
@ -2844,16 +2813,17 @@ void TrackArt::DrawNoteBackground(TrackPanelDrawingContext &context,
// need overlap between MIDI data and the background region
if (left >= right) return;
NoteTrackDisplayData data = NoteTrackDisplayData(track, rect);
dc.SetBrush(bb);
int octave = 0;
// obottom is the window coordinate of octave divider line
int obottom = track->GetOctaveBottom(octave);
int obottom = data.GetOctaveBottom(octave);
// eOffset is for the line between E and F; there's another line
// between B and C, hence the offset of 2 for two line thicknesses
int eOffset = track->GetPitchHeight(5) + 2;
while (obottom > rect.y + track->GetNoteMargin(rect.height) + 3) {
int eOffset = data.GetPitchHeight(5) + 2;
while (obottom > rect.y + data.GetNoteMargin() + 3) {
// draw a black line separating octaves if this octave botton is visible
if (obottom < rect.y + rect.height - track->GetNoteMargin(rect.height)) {
if (obottom < rect.y + rect.height - data.GetNoteMargin()) {
dc.SetPen(*wxBLACK_PEN);
// obottom - 1 because obottom is at the bottom of the line
AColor::Line(dc, left, obottom - 1, right, obottom - 1);
@ -2869,14 +2839,14 @@ void TrackArt::DrawNoteBackground(TrackPanelDrawingContext &context,
wxRect br;
br.x = left;
br.width = right - left;
br.height = track->GetPitchHeight(1);
br.height = data.GetPitchHeight(1);
for (int black = 0; black < 5; black++) {
br.y = obottom - track->GetBlackPos(black);
br.y = obottom - data.GetBlackPos(black);
if (br.y > rect.y && br.y + br.height < rect.y + rect.height) {
dc.DrawRectangle(br); // draw each black key background stripe
}
}
obottom = track->GetOctaveBottom(++octave);
obottom = data.GetOctaveBottom(++octave);
}
// draw bar lines
@ -2940,17 +2910,13 @@ void TrackArt::DrawNoteTrack(TrackPanelDrawingContext &context,
if (!track->GetSelected())
sel0 = sel1 = 0.0;
NoteTrackDisplayData data = NoteTrackDisplayData(track, rect);
// reserve 1/2 note height at top and bottom of track for
// out-of-bounds notes
int numPitches = (rect.height) / track->GetPitchHeight(1);
int numPitches = (rect.height) / data.GetPitchHeight(1);
if (numPitches < 0) numPitches = 0; // cannot be negative
// bottom is the hypothetical location of the bottom of pitch 0 relative to
// the top of the clipping region rect: rect.height - PITCH_HEIGHT/2 is where the
// bottomNote is displayed, and to that
// we add the height of bottomNote from the position of pitch 0
track->PrepareIPitchToY(rect);
#ifdef EXPERIMENTAL_NOTETRACK_OVERLAY
DrawBackgroundWithSelection(context, rect, track,
AColor::labelSelectedBrush, AColor::labelUnselectedBrush);
@ -3005,7 +2971,7 @@ void TrackArt::DrawNoteTrack(TrackPanelDrawingContext &context,
selectedBarLinePen);
SonifyEndNoteBackground();
SonifyBeginNoteForeground();
int marg = track->GetNoteMargin(rect.height);
int marg = data.GetNoteMargin();
// NOTE: it would be better to put this in some global initialization
// function rather than do lookups every time.
@ -3051,8 +3017,8 @@ void TrackArt::DrawNoteTrack(TrackPanelDrawingContext &context,
const char *shape = NULL;
if (note->loud > 0.0 || 0 == (shape = IsShape(note))) {
wxRect nr; // "note rectangle"
nr.y = track->PitchToY(note->pitch);
nr.height = track->GetPitchHeight(1);
nr.y = data.PitchToY(note->pitch);
nr.height = data.GetPitchHeight(1);
nr.x = TIME_TO_X(xx);
nr.width = TIME_TO_X(x1) - nr.x;
@ -3093,7 +3059,7 @@ void TrackArt::DrawNoteTrack(TrackPanelDrawingContext &context,
else
AColor::MIDIChannel(&dc, note->chan + 1);
dc.DrawRectangle(nr);
if (track->GetPitchHeight(1) > 2) {
if (data.GetPitchHeight(1) > 2) {
AColor::LightMIDIChannel(&dc, note->chan + 1);
AColor::Line(dc, nr.x, nr.y, nr.x + nr.width-2, nr.y);
AColor::Line(dc, nr.x, nr.y, nr.x, nr.y + nr.height-2);
@ -3111,7 +3077,7 @@ void TrackArt::DrawNoteTrack(TrackPanelDrawingContext &context,
// add 0.5 to pitch because pitches are plotted with
// height = PITCH_HEIGHT; thus, the center is raised
// by PITCH_HEIGHT * 0.5
int yy = track->PitchToY(note->pitch);
int yy = data.PitchToY(note->pitch);
long linecolor = LookupIntAttribute(note, linecolori, -1);
long linethick = LookupIntAttribute(note, linethicki, 1);
long fillcolor = -1;
@ -3137,7 +3103,7 @@ void TrackArt::DrawNoteTrack(TrackPanelDrawingContext &context,
wxBRUSHSTYLE_SOLID));
if (!fillflag) dc.SetBrush(*wxTRANSPARENT_BRUSH);
}
int y1 = track->PitchToY(LookupRealAttribute(note, y1r, note->pitch));
int y1 = data.PitchToY(LookupRealAttribute(note, y1r, note->pitch));
if (shape == line) {
// extreme zooms caues problems under windows, so we have to do some
// clipping before calling display routine
@ -3168,7 +3134,7 @@ void TrackArt::DrawNoteTrack(TrackPanelDrawingContext &context,
points[1].y = y1;
points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, xx));
CLIP(points[2].x);
points[2].y = track->PitchToY(LookupRealAttribute(note, y2r, note->pitch));
points[2].y = data.PitchToY(LookupRealAttribute(note, y2r, note->pitch));
dc.DrawPolygon(3, points);
} else if (shape == polygon) {
wxPoint points[20]; // upper bound of 20 sides
@ -3180,7 +3146,7 @@ void TrackArt::DrawNoteTrack(TrackPanelDrawingContext &context,
points[1].y = y1;
points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, xx));
CLIP(points[2].x);
points[2].y = track->PitchToY(LookupRealAttribute(note, y2r, note->pitch));
points[2].y = data.PitchToY(LookupRealAttribute(note, y2r, note->pitch));
int n = 3;
while (n < 20) {
char name[8];
@ -3194,7 +3160,7 @@ void TrackArt::DrawNoteTrack(TrackPanelDrawingContext &context,
attr = symbol_table.insert_string(name);
double yn = LookupRealAttribute(note, attr, -1000000.0);
if (yn == -1000000.0) break;
points[n].y = track->PitchToY(yn);
points[n].y = data.PitchToY(yn);
n++;
}
dc.DrawPolygon(n, points);

View File

@ -92,7 +92,6 @@ namespace TrackArt {
const WaveTrack *track,
const wxRect & rect);
#ifdef USE_MIDI
int GetBottom(NoteTrack *t, const wxRect &rect);
void DrawNoteBackground(TrackPanelDrawingContext &context,
const NoteTrack *track,
const wxRect &rect, const wxRect &sel,