Audacity 3.2.0
NoteTrackView.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5NoteTrackView.cpp
6
7Paul Licameli split from TrackPanel.cpp
8
9**********************************************************************/
10
11
12#include "NoteTrackView.h"
13
14#ifdef USE_MIDI
15#include "../lib-src/header-substitutes/allegro.h"
16
18#include "../../../../NoteTrack.h"
19
20#include "AColor.h"
21#include "AllThemeResources.h"
22#include "../../../../HitTestResult.h"
23#include "Theme.h"
24#include "../../../../TrackArt.h"
25#include "../../../../TrackArtist.h"
26#include "../../../../TrackPanelDrawingContext.h"
27#include "../../../../TrackPanelMouseEvent.h"
28#include "ViewInfo.h"
29#include "../../../ui/SelectHandle.h"
30#include "StretchHandle.h"
32
33#include <wx/dc.h>
34
35NoteTrackView::NoteTrackView( const std::shared_ptr<Track> &pTrack )
36 : CommonTrackView{ pTrack }
37{
38}
39
41{
42}
43
44std::vector<UIHandlePtr> NoteTrackView::DetailedHitTest
45(const TrackPanelMouseState &WXUNUSED(state),
46 const AudacityProject *WXUNUSED(pProject), int, bool )
47{
48 // Eligible for stretch?
49 UIHandlePtr result;
50 std::vector<UIHandlePtr> results;
51#ifdef USE_MIDI
52#ifdef EXPERIMENTAL_MIDI_STRETCHING
54 mStretchHandle, state, pProject, Pointer<NoteTrack>(this) );
55 if (result)
56 results.push_back(result);
57#endif
58#endif
59
60 return results;
61}
62
65 return [](NoteTrack &track) {
66 return std::make_shared<NoteTrackView>( track.SharedPointer() );
67 };
68}
69
70std::shared_ptr<TrackVRulerControls> NoteTrackView::DoGetVRulerControls()
71{
72 return
73 std::make_shared<NoteTrackVRulerControls>( shared_from_this() );
74}
75
76#define TIME_TO_X(t) (zoomInfo.TimeToPosition((t), rect.x))
77#define X_TO_TIME(xx) (zoomInfo.PositionToTime((xx), rect.x))
78
79std::shared_ptr<CommonTrackCell> NoteTrackView::GetAffordanceControls()
80{
81 if (mpAffordanceCellControl == nullptr)
82 {
83 mpAffordanceCellControl = std::make_shared<NoteTrackAffordanceControls>(DoFindTrack());
84 }
86}
87
88namespace {
89
90/*
91Note: recall that Allegro attributes end in a type identifying letter.
92
93In addition to standard notes, an Allegro_Note can denote a graphic.
94A graphic is a note with a loud of zero (for quick testing) and an
95attribute named "shapea" set to one of the following atoms:
96 line
97 from (time, pitch) to (time+dur, y1r), where y1r is an
98 attribute
99 rectangle
100 from (time, pitch) to (time+dur, y1r), where y1r is an
101 attribute
102 triangle
103 coordinates are (time, pitch), (x1r, y1r), (x2r, y2r)
104 dur must be the max of x1r-time, x2r-time
105 polygon
106 coordinates are (time, pitch), (x1r, y1r), (x2r, y2r),
107 (x3r, y3r), ... are coordinates (since we cannot represent
108 arrays as attribute values, we just generate as many
109 attribute names as we need)
110 dur must be the max of xNr-time for all N
111 oval
112 similar to rectangle
113 Note: this oval has horizontal and vertical axes only
114 text
115 drawn at (time, pitch)
116 duration should be zero (text is clipped based on time and duration,
117 NOT based on actual coordinates)
118
119and optional attributes as follows:
120 linecolori is 0x00rrggbb format color for line or text foreground
121 fillcolori is 0x00rrggbb format color for fill or text background
122 linethicki is line thickness in pixels, 0 for no line
123 filll is true to fill rectangle or draw text background (default is false)
124 fonta is one of ['roman', 'swiss', 'modern'] (font, otherwise use default)
125 weighta may be 'bold' (font) (default is normal)
126 sizei is font size (default is 8)
127 justifys is a string containing two letters, a horizontal code and a
128 vertical code. The horizontal code is as follows:
129 l: the coordinate is to the left of the string (default)
130 c: the coordinate is at the center of the string
131 r: the coordinate is at the right of the string
132 The vertical code is as follows:
133 t: the coordinate is at the top of the string
134 c: the coordinate is at the center of the string
135 b: the coordinate is at the bottom of the string
136 d: the coordinate is at the baseline of the string (default)
137 Thus, -justifys:"lt" places the left top of the string at the point
138 given by (pitch, time). The default value is "ld".
139*/
140
141// returns NULL if note is not a shape,
142// returns atom (string) value of note if note is a shape
143const char *IsShape(Alg_note_ptr note)
144{
145 Alg_parameters_ptr parameters = note->parameters;
146 while (parameters) {
147 if (strcmp(parameters->parm.attr_name(), "shapea") == 0) {
148 return parameters->parm.a;
149 }
150 parameters = parameters->next;
151 }
152 return NULL;
153}
154
155// returns value of attr, or default if not found
156double LookupRealAttribute(Alg_note_ptr note, Alg_attribute attr, double def)
157{
158 Alg_parameters_ptr parameters = note->parameters;
159 while (parameters) {
160 if (parameters->parm.attr_name() == attr + 1 &&
161 parameters->parm.attr_type() == 'r') {
162 return parameters->parm.r;
163 }
164 parameters = parameters->next;
165 }
166 return def;
167}
168
169// returns value of attr, or default if not found
170long LookupIntAttribute(Alg_note_ptr note, Alg_attribute attr, long def)
171{
172 Alg_parameters_ptr parameters = note->parameters;
173 while (parameters) {
174 if (parameters->parm.attr_name() == attr + 1 &&
175 parameters->parm.attr_type() == 'i') {
176 return parameters->parm.i;
177 }
178 parameters = parameters->next;
179 }
180 return def;
181}
182
183// returns value of attr, or default if not found
184bool LookupLogicalAttribute(Alg_note_ptr note, Alg_attribute attr, bool def)
185{
186 Alg_parameters_ptr parameters = note->parameters;
187 while (parameters) {
188 if (parameters->parm.attr_name() == attr + 1 &&
189 parameters->parm.attr_type() == 'l') {
190 return parameters->parm.l;
191 }
192 parameters = parameters->next;
193 }
194 return def;
195}
196
197// returns value of attr, or default if not found
198const char *LookupStringAttribute(Alg_note_ptr note, Alg_attribute attr, const char *def)
199{
200 Alg_parameters_ptr parameters = note->parameters;
201 while (parameters) {
202 if (parameters->parm.attr_name() == attr + 1 &&
203 parameters->parm.attr_type() == 's') {
204 return parameters->parm.s;
205 }
206 parameters = parameters->next;
207 }
208 return def;
209}
210
211// returns value of attr, or default if not found
212const char *LookupAtomAttribute(Alg_note_ptr note, Alg_attribute attr, char *def)
213{
214 Alg_parameters_ptr parameters = note->parameters;
215 while (parameters) {
216 if (parameters->parm.attr_name() == attr + 1 &&
217 parameters->parm.attr_type() == 'a') {
218 return parameters->parm.s;
219 }
220 parameters = parameters->next;
221 }
222 return def;
223}
224
225// CLIP(x) changes x to lie between +/- CLIP_MAX due to graphics display problems
226// with very large coordinate values (this happens when you zoom in very far)
227// This will cause incorrect things to be displayed, but at these levels of zoom
228// you will only see a small fraction of the overall shape. Note that rectangles
229// and lines are clipped in a way that preserves correct graphics, so in
230// particular, line plots will be correct at any zoom (limited by floating point
231// precision).
232#define CLIP_MAX 16000
233#define CLIP(xx) { long c = (xx); if (c < -CLIP_MAX) c = -CLIP_MAX; \
234 if (c > CLIP_MAX) c = CLIP_MAX; (xx) = c; }
235
236#define RED(i) ( unsigned char )( (((i) >> 16) & 0xff) )
237#define GREEN(i) ( unsigned char )( (((i) >> 8) & 0xff) )
238#define BLUE(i) ( unsigned char )( ((i) & 0xff) )
239
240//#define PITCH_TO_Y(p) (rect.y + rect.height - (int)(pitchht * ((p) + 0.5 - pitch0) + 0.5))
241
242/*
243int PitchToY(double p, int bottom)
244{
245 int octave = (((int) (p + 0.5)) / 12);
246 int n = ((int) (p + 0.5)) % 12;
247
248 return IPITCH_TO_Y((int) (p + 0.5));
249 // was: bottom - octave * octaveHeight - notePos[n] - 4;
250}
251*/
252
253/* DrawNoteBackground is called by DrawNoteTrack twice: once to draw
254 the unselected background, and once to draw the selected background.
255 The selected background is the same except for the horizontal range
256 and the colors. The background rectangle region is given by rect; the
257 selected region is given by sel. The first time this is called,
258 sel is equal to rect, and the entire region is drawn with unselected
259 background colors.
260 */
262 const NoteTrack *track,
263 const wxRect &rect, const wxRect &sel,
264 const wxBrush &wb, const wxPen &wp,
265 const wxBrush &bb, const wxPen &bp,
266 const wxPen &mp)
267{
268 auto &dc = context.dc;
269 const auto artist = TrackArtist::Get( context );
270 const auto &zoomInfo = *artist->pZoomInfo;
271
272 dc.SetBrush(wb);
273 dc.SetPen(wp);
274#ifndef EXPERIMENTAL_NOTETRACK_OVERLAY
275 dc.DrawRectangle(sel); // fill rectangle with white keys background
276#endif
277
278 int left = TIME_TO_X(track->GetOffset());
279 if (left < sel.x) left = sel.x; // clip on left
280
281 int right = TIME_TO_X(track->GetOffset() + track->GetSeq().get_real_dur());
282 if (right > sel.x + sel.width) right = sel.x + sel.width; // clip on right
283
284 // need overlap between MIDI data and the background region
285 if (left >= right) return;
286
287 NoteTrackDisplayData data{ track, rect };
288 dc.SetBrush(bb);
289 int octave = 0;
290 // obottom is the window coordinate of octave divider line
291 int obottom = data.GetOctaveBottom(octave);
292 // eOffset is for the line between E and F; there's another line
293 // between B and C, hence the offset of 2 for two line thicknesses
294 int eOffset = data.GetPitchHeight(5) + 2;
295 while (obottom > rect.y + data.GetNoteMargin() + 3) {
296 // draw a black line separating octaves if this octave bottom is visible
297 if (obottom < rect.y + rect.height - data.GetNoteMargin()) {
298 dc.SetPen(*wxBLACK_PEN);
299 // obottom - 1 because obottom is at the bottom of the line
300 AColor::Line(dc, left, obottom - 1, right, obottom - 1);
301 }
302 dc.SetPen(bp);
303 // draw a black-key stripe colored line separating E and F if visible
304 if (obottom - eOffset > rect.y && obottom - eOffset < rect.y + rect.height) {
305 AColor::Line(dc, left, obottom - eOffset,
306 right, obottom - eOffset);
307 }
308
309 // draw visible black key lines
310 wxRect br;
311 br.x = left;
312 br.width = right - left;
313 br.height = data.GetPitchHeight(1);
314 for (int black = 0; black < 5; black++) {
315 br.y = obottom - data.GetBlackPos(black);
316 if (br.y > rect.y && br.y + br.height < rect.y + rect.height) {
317 dc.DrawRectangle(br); // draw each black key background stripe
318 }
319 }
320 obottom = data.GetOctaveBottom(++octave);
321 }
322
323 // draw bar lines
324 Alg_seq_ptr seq = &track->GetSeq();
325 // We assume that sliding a NoteTrack around slides the barlines
326 // along with the notes. This means that when we write out a track
327 // as Allegro or MIDI without the offset, we'll need to insert an
328 // integer number of measures of silence, using tempo change to
329 // match the duration to the offset.
330 // Iterate over all time signatures to generate beat positions of
331 // bar lines, map the beats to times, map the times to position,
332 // and draw the bar lines that fall within the region of interest (sel)
333 // seq->convert_to_beats();
334 dc.SetPen(mp);
335 Alg_time_sigs &sigs = seq->time_sig;
336 int i = 0; // index into ts[]
337 double next_bar_beat = 0.0;
338 double beats_per_measure = 4.0;
339 while (true) {
340 if (i < sigs.length() && sigs[i].beat < next_bar_beat + ALG_EPS) {
341 // NEW time signature takes effect
342 Alg_time_sig &sig = sigs[i++];
343 next_bar_beat = sig.beat;
344 beats_per_measure = (sig.num * 4.0) / sig.den;
345 }
346 // map beat to time
347 double t = seq->get_time_map()->beat_to_time(next_bar_beat);
348 // map time to position
349 int xx = TIME_TO_X(t + track->GetOffset());
350 if (xx > right) break;
351 AColor::Line(dc, xx, sel.y, xx, sel.y + sel.height);
352 next_bar_beat += beats_per_measure;
353 }
354}
355
356/* DrawNoteTrack:
357Draws a piano-roll style display of sequence data with added
358graphics. Since there may be notes outside of the display region,
359reserve a half-note-height margin at the top and bottom of the
360window and draw out-of-bounds notes here instead.
361*/
363 const NoteTrack *track,
364 const wxRect & rect,
365 bool muted,
366 bool selected)
367{
368 auto &dc = context.dc;
369 const auto artist = TrackArtist::Get( context );
370 const auto &selectedRegion = *artist->pSelectedRegion;
371 const auto &zoomInfo = *artist->pZoomInfo;
372
374 double sel0 = selectedRegion.t0();
375 double sel1 = selectedRegion.t1();
376
377 const double h = X_TO_TIME(rect.x);
378 const double h1 = X_TO_TIME(rect.x + rect.width);
379
380 Alg_seq_ptr seq = &track->GetSeq();
381
382 if (!track->GetSelected())
383 sel0 = sel1 = 0.0;
384
385 NoteTrackDisplayData data{ track, rect };
386
387 // reserve 1/2 note height at top and bottom of track for
388 // out-of-bounds notes
389 int numPitches = (rect.height) / data.GetPitchHeight(1);
390 if (numPitches < 0) numPitches = 0; // cannot be negative
391
392 // Background comes in 4 colors, that are now themed.
393 // 214, 214,214 -- unselected white keys
394 // 192,192,192 -- black keys
395 // 170,170,170 -- bar lines
396 // 165,165,190 -- selected white keys
397
398 wxPen blackStripePen;
399 blackStripePen.SetColour(theTheme.Colour( clrMidiZebra));
400 wxBrush blackStripeBrush;
401 blackStripeBrush.SetColour(theTheme.Colour( clrMidiZebra));
402 wxPen barLinePen;
403 barLinePen.SetColour(theTheme.Colour( clrMidiLines));
404
405 const auto &blankBrush = artist->blankBrush;
406 const auto &blankPen = artist->blankPen;
407 DrawNoteBackground(context, track, rect, rect, blankBrush, blankPen,
408 blackStripeBrush, blackStripePen, barLinePen);
409
410 dc.SetClippingRegion(rect);
411
412 // Draw the selection background
413 // First, the white keys, as a single rectangle
414 // In other words fill the selection area with selectedWhiteKeyPen
415 wxRect selBG;
416 selBG.y = rect.y;
417 selBG.height = rect.height;
418 selBG.x = TIME_TO_X(sel0);
419 selBG.width = TIME_TO_X(sel1) - TIME_TO_X(sel0);
420
421 wxPen selectedWhiteKeyPen;
422 selectedWhiteKeyPen.SetColour(165, 165, 190);
423 dc.SetPen(selectedWhiteKeyPen);
424
425 wxBrush selectedWhiteKeyBrush;
426 selectedWhiteKeyBrush.SetColour(theTheme.Colour( clrSelected ));
427 // Then, the black keys and octave stripes, as smaller rectangles
428 wxPen selectedBlackKeyPen;
429 selectedBlackKeyPen.SetColour(theTheme.Colour( clrMidiZebra));
430 wxBrush selectedBlackKeyBrush;
431 selectedBlackKeyBrush.SetColour(theTheme.Colour( clrMidiZebra));
432 wxPen selectedBarLinePen;
433 selectedBarLinePen.SetColour(theTheme.Colour( clrMidiLines));
434
435 DrawNoteBackground(context, track, rect, selBG,
436 selectedWhiteKeyBrush, selectedWhiteKeyPen,
437 selectedBlackKeyBrush, selectedBlackKeyPen,
438 selectedBarLinePen);
441 int marg = data.GetNoteMargin();
442
443 // NOTE: it would be better to put this in some global initialization
444 // function rather than do lookups every time.
445 Alg_attribute line = symbol_table.insert_string("line");
446 Alg_attribute rectangle = symbol_table.insert_string("rectangle");
447 Alg_attribute triangle = symbol_table.insert_string("triangle");
448 Alg_attribute polygon = symbol_table.insert_string("polygon");
449 Alg_attribute oval = symbol_table.insert_string("oval");
450 Alg_attribute text = symbol_table.insert_string("text");
451 Alg_attribute texts = symbol_table.insert_string("texts");
452 Alg_attribute x1r = symbol_table.insert_string("x1r");
453 Alg_attribute x2r = symbol_table.insert_string("x2r");
454 Alg_attribute y1r = symbol_table.insert_string("y1r");
455 Alg_attribute y2r = symbol_table.insert_string("y2r");
456 Alg_attribute linecolori = symbol_table.insert_string("linecolori");
457 Alg_attribute fillcolori = symbol_table.insert_string("fillcolori");
458 Alg_attribute linethicki = symbol_table.insert_string("linethicki");
459 Alg_attribute filll = symbol_table.insert_string("filll");
460 Alg_attribute fonta = symbol_table.insert_string("fonta");
461 Alg_attribute roman = symbol_table.insert_string("roman");
462 Alg_attribute swiss = symbol_table.insert_string("swiss");
463 Alg_attribute modern = symbol_table.insert_string("modern");
464 Alg_attribute weighta = symbol_table.insert_string("weighta");
465 Alg_attribute bold = symbol_table.insert_string("bold");
466 Alg_attribute sizei = symbol_table.insert_string("sizei");
467 Alg_attribute justifys = symbol_table.insert_string("justifys");
468
469 // We want to draw in seconds, so we need to convert to seconds
470 seq->convert_to_seconds();
471
472 Alg_iterator iterator(seq, false);
473 iterator.begin();
474 //for every event
475 Alg_event_ptr evt;
476 while (0 != (evt = iterator.next())) {
477 if (evt->get_type() == 'n') { // 'n' means a note
478 Alg_note_ptr note = (Alg_note_ptr) evt;
479 // if the note's channel is visible
480 if (track->IsVisibleChan(evt->chan)) {
481 double xx = note->time + track->GetOffset();
482 double x1 = xx + note->dur;
483 if (xx < h1 && x1 > h) { // omit if outside box
484 const char *shape = NULL;
485 if (note->loud > 0.0 || 0 == (shape = IsShape(note))) {
486 wxRect nr; // "note rectangle"
487 nr.y = data.PitchToY(note->pitch);
488 nr.height = data.GetPitchHeight(1);
489
490 nr.x = TIME_TO_X(xx);
491 nr.width = TIME_TO_X(x1) - nr.x;
492
493 if (nr.x + nr.width >= rect.x && nr.x < rect.x + rect.width) {
494 if (nr.x < rect.x) {
495 nr.width -= (rect.x - nr.x);
496 nr.x = rect.x;
497 }
498 if (nr.x + nr.width > rect.x + rect.width) // clip on right
499 nr.width = rect.x + rect.width - nr.x;
500
501 if (nr.y + nr.height < rect.y + marg + 3) {
502 // too high for window
503 nr.y = rect.y;
504 nr.height = marg;
505 dc.SetBrush(*wxBLACK_BRUSH);
506 dc.SetPen(*wxBLACK_PEN);
507 dc.DrawRectangle(nr);
508 } else if (nr.y >= rect.y + rect.height - marg - 1) {
509 // too low for window
510 nr.y = rect.y + rect.height - marg;
511 nr.height = marg;
512 dc.SetBrush(*wxBLACK_BRUSH);
513 dc.SetPen(*wxBLACK_PEN);
514 dc.DrawRectangle(nr);
515 } else {
516 if (nr.y + nr.height > rect.y + rect.height - marg)
517 nr.height = rect.y + rect.height - nr.y;
518 if (nr.y < rect.y + marg) {
519 int offset = rect.y + marg - nr.y;
520 nr.height -= offset;
521 nr.y += offset;
522 }
523 // nr.y += rect.y;
524 if (muted)
525 AColor::LightMIDIChannel(&dc, note->chan + 1);
526 else
527 AColor::MIDIChannel(&dc, note->chan + 1);
528 dc.DrawRectangle(nr);
529 if (data.GetPitchHeight(1) > 2) {
530 AColor::LightMIDIChannel(&dc, note->chan + 1);
531 AColor::Line(dc, nr.x, nr.y, nr.x + nr.width-2, nr.y);
532 AColor::Line(dc, nr.x, nr.y, nr.x, nr.y + nr.height-2);
533 AColor::DarkMIDIChannel(&dc, note->chan + 1);
534 AColor::Line(dc, nr.x+nr.width-1, nr.y,
535 nr.x+nr.width-1, nr.y+nr.height-1);
536 AColor::Line(dc, nr.x, nr.y+nr.height-1,
537 nr.x+nr.width-1, nr.y+nr.height-1);
538 }
539// }
540 }
541 }
542 } else if (shape) {
543 // draw a shape according to attributes
544 // add 0.5 to pitch because pitches are plotted with
545 // height = PITCH_HEIGHT; thus, the center is raised
546 // by PITCH_HEIGHT * 0.5
547 int yy = data.PitchToY(note->pitch);
548 long linecolor = LookupIntAttribute(note, linecolori, -1);
549 long linethick = LookupIntAttribute(note, linethicki, 1);
550 long fillcolor = -1;
551 long fillflag = 0;
552
553 // set default color to be that of channel
554 AColor::MIDIChannel(&dc, note->chan+1);
555 if (shape != text) {
556 if (linecolor != -1)
557 dc.SetPen(wxPen(wxColour(RED(linecolor),
558 GREEN(linecolor),
559 BLUE(linecolor)),
560 linethick, wxPENSTYLE_SOLID));
561 }
562 if (shape != line) {
563 fillcolor = LookupIntAttribute(note, fillcolori, -1);
564 fillflag = LookupLogicalAttribute(note, filll, false);
565
566 if (fillcolor != -1)
567 dc.SetBrush(wxBrush(wxColour(RED(fillcolor),
568 GREEN(fillcolor),
569 BLUE(fillcolor)),
570 wxBRUSHSTYLE_SOLID));
571 if (!fillflag) dc.SetBrush(*wxTRANSPARENT_BRUSH);
572 }
573 int y1 = data.PitchToY(LookupRealAttribute(note, y1r, note->pitch));
574 if (shape == line) {
575 // extreme zooms caues problems under windows, so we have to do some
576 // clipping before calling display routine
577 if (xx < h) { // clip line on left
578 yy = (int)((yy + (y1 - yy) * (h - xx) / (x1 - xx)) + 0.5);
579 xx = h;
580 }
581 if (x1 > h1) { // clip line on right
582 y1 = (int)((yy + (y1 - yy) * (h1 - xx) / (x1 - xx)) + 0.5);
583 x1 = h1;
584 }
585 AColor::Line(dc, TIME_TO_X(xx), yy, TIME_TO_X(x1), y1);
586 } else if (shape == rectangle) {
587 if (xx < h) { // clip on left, leave 10 pixels to spare
588 xx = X_TO_TIME(rect.x - (linethick + 10));
589 }
590 if (x1 > h1) { // clip on right, leave 10 pixels to spare
591 xx = X_TO_TIME(rect.x + rect.width + linethick + 10);
592 }
593 dc.DrawRectangle(TIME_TO_X(xx), yy, TIME_TO_X(x1) - TIME_TO_X(xx), y1 - yy + 1);
594 } else if (shape == triangle) {
595 wxPoint points[3];
596 points[0].x = TIME_TO_X(xx);
597 CLIP(points[0].x);
598 points[0].y = yy;
599 points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, note->pitch));
600 CLIP(points[1].x);
601 points[1].y = y1;
602 points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, xx));
603 CLIP(points[2].x);
604 points[2].y = data.PitchToY(LookupRealAttribute(note, y2r, note->pitch));
605 dc.DrawPolygon(3, points);
606 } else if (shape == polygon) {
607 wxPoint points[20]; // upper bound of 20 sides
608 points[0].x = TIME_TO_X(xx);
609 CLIP(points[0].x);
610 points[0].y = yy;
611 points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, xx));
612 CLIP(points[1].x);
613 points[1].y = y1;
614 points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, xx));
615 CLIP(points[2].x);
616 points[2].y = data.PitchToY(LookupRealAttribute(note, y2r, note->pitch));
617 int n = 3;
618 while (n < 20) {
619 char name[8];
620 sprintf(name, "x%dr", n);
621 Alg_attribute attr = symbol_table.insert_string(name);
622 double xn = LookupRealAttribute(note, attr, -1000000.0);
623 if (xn == -1000000.0) break;
624 points[n].x = TIME_TO_X(xn);
625 CLIP(points[n].x);
626 sprintf(name, "y%dr", n - 1);
627 attr = symbol_table.insert_string(name);
628 double yn = LookupRealAttribute(note, attr, -1000000.0);
629 if (yn == -1000000.0) break;
630 points[n].y = data.PitchToY(yn);
631 n++;
632 }
633 dc.DrawPolygon(n, points);
634 } else if (shape == oval) {
635 int ix = TIME_TO_X(xx);
636 CLIP(ix);
637 int ix1 = TIME_TO_X(x1) - TIME_TO_X(xx);
638 if (ix1 > CLIP_MAX * 2) ix1 = CLIP_MAX * 2; // CLIP a width
639 dc.DrawEllipse(ix, yy, ix1, y1 - yy + 1);
640 } else if (shape == text) {
641 if (linecolor != -1)
642 dc.SetTextForeground(wxColour(RED(linecolor),
643 GREEN(linecolor),
644 BLUE(linecolor)));
645 // if no color specified, copy color from brush
646 else dc.SetTextForeground(dc.GetBrush().GetColour());
647
648 // This seems to have no effect, so I commented it out. -RBD
649 //if (fillcolor != -1)
650 // dc.SetTextBackground(wxColour(RED(fillcolor),
651 // GREEN(fillcolor),
652 // BLUE(fillcolor)));
654 //else dc.SetTextBackground(dc.GetPen().GetColour());
655
656 const char *font = LookupAtomAttribute(note, fonta, NULL);
657 const char *weight = LookupAtomAttribute(note, weighta, NULL);
658 int size = LookupIntAttribute(note, sizei, 8);
659 const char *justify = LookupStringAttribute(note, justifys, "ld");
660 wxFont wxfont;
661 wxfont.SetFamily(font == roman ? wxFONTFAMILY_ROMAN :
662 (font == swiss ? wxFONTFAMILY_SWISS :
663 (font == modern ? wxFONTFAMILY_MODERN : wxFONTFAMILY_DEFAULT)));
664 wxfont.SetStyle(wxFONTSTYLE_NORMAL);
665 wxfont.SetWeight(weight == bold ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL);
666 wxfont.SetPointSize(size);
667 dc.SetFont(wxfont);
668
669 // now do justification
670 const char *s = LookupStringAttribute(note, texts, "");
671 wxCoord textWidth, textHeight;
672 dc.GetTextExtent(wxString::FromUTF8(s), &textWidth, &textHeight);
673 long hoffset = 0;
674 long voffset = -textHeight; // default should be baseline of text
675
676 if (strlen(justify) != 2) justify = "ld";
677
678 if (justify[0] == 'c') hoffset = -(textWidth/2);
679 else if (justify[0] == 'r') hoffset = -textWidth;
680
681 if (justify[1] == 't') voffset = 0;
682 else if (justify[1] == 'c') voffset = -(textHeight/2);
683 else if (justify[1] == 'b') voffset = -textHeight;
684 if (fillflag) {
685 // It should be possible to do this with background color,
686 // but maybe because of the transfer mode, no background is
687 // drawn. To fix this, just draw a rectangle:
688 dc.SetPen(wxPen(wxColour(RED(fillcolor),
689 GREEN(fillcolor),
690 BLUE(fillcolor)),
691 1, wxPENSTYLE_SOLID));
692 dc.DrawRectangle(TIME_TO_X(xx) + hoffset, yy + voffset,
693 textWidth, textHeight);
694 }
695 dc.DrawText(LAT1CTOWX(s), TIME_TO_X(xx) + hoffset, yy + voffset);
696 }
697 }
698 }
699 }
700 }
701 }
702 iterator.end();
703 // draw black line between top/bottom margins and the track
704 dc.SetPen(*wxBLACK_PEN);
705 AColor::Line(dc, rect.x, rect.y + marg, rect.x + rect.width, rect.y + marg);
706 AColor::Line(dc, rect.x, rect.y + rect.height - marg - 1, // subtract 1 to get
707 rect.x + rect.width, rect.y + rect.height - marg - 1); // top of line
708
709 if (h == 0.0 && track->GetOffset() < 0.0) {
711 }
712
713 //draw clip edges
714 {
715 int left = TIME_TO_X(track->GetOffset());
716 int right = TIME_TO_X(track->GetOffset() + track->GetSeq().get_real_dur());
717
718 TrackArt::DrawClipEdges(dc, wxRect(left, rect.GetTop(), right - left + 1, rect.GetHeight()), selected);
719 }
720
721 dc.DestroyClippingRegion();
723}
724
725}
726
729 const wxRect &rect, unsigned iPass )
730{
731 if ( iPass == TrackArtist::PassTracks ) {
732 const auto nt = std::static_pointer_cast<const NoteTrack>(
733 FindTrack()->SubstitutePendingChangedTrack());
734 bool muted = false;
735#ifdef EXPERIMENTAL_MIDI_OUT
736 const auto artist = TrackArtist::Get( context );
737 const auto hasSolo = artist->hasSolo;
738 muted = (hasSolo || nt->GetMute()) && !nt->GetSolo();
739#endif
740
741#ifdef EXPERIMENTAL_NOTETRACK_OVERLAY
743#endif
744 bool selected{ false };
745 if (auto affordance = std::dynamic_pointer_cast<NoteTrackAffordanceControls>(GetAffordanceControls()))
746 {
747 selected = affordance->IsSelected();
748 }
749
750 DrawNoteTrack(context, nt.get(), rect, muted, selected);
751 }
752 CommonTrackView::Draw( context, rect, iPass );
753}
754
755#include "SyncLock.h"
756
760 return [](auto &) { return SyncLockPolicy::Grouped; };
761}
762#endif
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
const TranslatableString name
Definition: Distortion.cpp:74
@ rectangle
Definition: Dither.h:20
@ triangle
Definition: Dither.h:20
#define LAT1CTOWX(X)
Definition: Internat.h:160
#define SonifyBeginNoteBackground()
Definition: NoteTrack.h:305
#define SonifyEndNoteBackground()
Definition: NoteTrack.h:306
#define SonifyBeginNoteForeground()
Definition: NoteTrack.h:307
#define SonifyEndNoteForeground()
Definition: NoteTrack.h:308
DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetNoteTrackView)
#define RED(i)
#define X_TO_TIME(xx)
#define CLIP_MAX
#define GREEN(i)
#define CLIP(xx)
#define BLUE(i)
#define TIME_TO_X(t)
@ Grouped
Can be part of a group.
THEME_API Theme theTheme
Definition: Theme.cpp:82
static wxBrush labelSelectedBrush
Definition: AColor.h:122
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:187
static void LightMIDIChannel(wxDC *dc, int channel)
Definition: AColor.cpp:681
static void MIDIChannel(wxDC *dc, int channel)
Definition: AColor.cpp:665
static wxBrush labelUnselectedBrush
Definition: AColor.h:121
static void DarkMIDIChannel(wxDC *dc, int channel)
Definition: AColor.cpp:699
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
std::shared_ptr< Track > DoFindTrack() override
std::shared_ptr< Track > FindTrack()
Data used to display a note track.
Definition: NoteTrack.h:250
A Track that is used for Midi notes. (Somewhat old code).
Definition: NoteTrack.h:63
double GetOffset() const override
Definition: NoteTrack.cpp:201
bool IsVisibleChan(int c) const
Definition: NoteTrack.h:182
Alg_seq & GetSeq() const
Definition: NoteTrack.cpp:138
std::shared_ptr< CommonTrackCell > mpAffordanceCellControl
Definition: NoteTrackView.h:40
std::vector< UIHandlePtr > DetailedHitTest(const TrackPanelMouseState &state, const AudacityProject *pProject, int currentTool, bool bMultiTool) override
std::shared_ptr< CommonTrackCell > GetAffordanceControls() override
NoteTrackView(const NoteTrackView &)=delete
~NoteTrackView() override
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
std::shared_ptr< TrackVRulerControls > DoGetVRulerControls() override
static UIHandlePtr HitTest(std::weak_ptr< StretchHandle > &holder, const TrackPanelMouseState &state, const AudacityProject *pProject, const std::shared_ptr< NoteTrack > &pTrack)
wxColour & Colour(int iIndex)
static TrackArtist * Get(TrackPanelDrawingContext &)
Definition: TrackArtist.cpp:69
bool GetSelected() const
Definition: Track.h:469
virtual void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass)
AUDACITY_DLL_API void DrawClipEdges(wxDC &dc, const wxRect &clipRect, bool selected=false)
Definition: TrackArt.cpp:259
AUDACITY_DLL_API void DrawBackgroundWithSelection(TrackPanelDrawingContext &context, const wxRect &rect, const Track *track, const wxBrush &selBrush, const wxBrush &unselBrush, bool useSelection=true)
Definition: TrackArt.cpp:425
AUDACITY_DLL_API void DrawNegativeOffsetTrackArrows(TrackPanelDrawingContext &context, const wxRect &rect)
Definition: TrackArt.cpp:126
bool LookupLogicalAttribute(Alg_note_ptr note, Alg_attribute attr, bool def)
const char * IsShape(Alg_note_ptr note)
const char * LookupStringAttribute(Alg_note_ptr note, Alg_attribute attr, const char *def)
const char * LookupAtomAttribute(Alg_note_ptr note, Alg_attribute attr, char *def)
void DrawNoteTrack(TrackPanelDrawingContext &context, const NoteTrack *track, const wxRect &rect, bool muted, bool selected)
double LookupRealAttribute(Alg_note_ptr note, Alg_attribute attr, double def)
void DrawNoteBackground(TrackPanelDrawingContext &context, const NoteTrack *track, const wxRect &rect, const wxRect &sel, const wxBrush &wb, const wxPen &wp, const wxBrush &bb, const wxPen &bp, const wxPen &mp)
long LookupIntAttribute(Alg_note_ptr note, Alg_attribute attr, long def)
For defining overrides of the method.