Audacity 3.2.0
NoteTrack.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 NoteTrack.cpp
6
7 Dominic Mazzoni
8
9*******************************************************************//*******************************************************************/
15
16
17
18#include "NoteTrack.h"
19
20
21
22#include <wx/wxcrtvararg.h>
23#include <wx/dc.h>
24#include <wx/brush.h>
25#include <wx/pen.h>
26#include <wx/intl.h>
27
28#if defined(USE_MIDI)
29#include "../lib-src/header-substitutes/allegro.h"
30
31#include <sstream>
32
33#define ROUND(x) ((int) ((x) + 0.5))
34
35#include "AColor.h"
36#include "Prefs.h"
37#include "Project.h"
39
41
42#include "TimeWarper.h"
43
44#include "AllThemeResources.h"
45#include "Theme.h"
46
47#ifdef SONIFY
48#include <portmidi.h>
49
50#define SON_PROGRAM 0
51#define SON_AutoSave 67
52#define SON_ModifyState 60
53#define SON_NoteBackground 72
54#define SON_NoteForeground 74
55#define SON_Measures 76 /* "bar line" */
56#define SON_Serialize 77
57#define SON_Unserialize 79
58#define SON_VEL 100
59
60
61PmStream *sonMidiStream;
62bool sonificationStarted = false;
63
65{
66 PmError err = Pm_OpenOutput(&sonMidiStream, Pm_GetDefaultOutputDeviceID(),
67 NULL, 0, NULL, NULL, 0);
68 if (err) sonMidiStream = NULL;
69 if (sonMidiStream)
70 Pm_WriteShort(sonMidiStream, 0, Pm_Message(0xC0, SON_PROGRAM, 0));
71 sonificationStarted = true;
72}
73
74
76{
77 if (sonMidiStream) Pm_Close(sonMidiStream);
78 sonificationStarted = false;
79}
80
81
82
83
84void SonifyNoteOnOff(int p, int v)
85{
86 if (!sonificationStarted)
88 if (sonMidiStream)
89 Pm_WriteShort(sonMidiStream, 0, Pm_Message(0x90, p, v));
90}
91
92#define SONFNS(name) \
93 void SonifyBegin ## name() { SonifyNoteOnOff(SON_ ## name, SON_VEL); } \
94 void SonifyEnd ## name() { SonifyNoteOnOff(SON_ ## name, 0); }
95
96SONFNS(NoteBackground)
97SONFNS(NoteForeground)
98SONFNS(Measures)
99SONFNS(Serialize)
100SONFNS(Unserialize)
101SONFNS(ModifyState)
102SONFNS(AutoSave)
103
104#undef SONFNS
105
106#endif
107
108
109
111 "notetrack",
113};
114
116{
117 auto &tracks = TrackList::Get( project );
118 auto result = tracks.Add( std::make_shared<NoteTrack>());
119 result->AttachedTrackObjects::BuildAll();
120 return result;
121}
122
124 : NoteTrackBase()
125{
126 SetName(_("Note Track"));
127
128 mSeq = NULL;
130
133}
134
136{
137}
138
139Alg_seq &NoteTrack::GetSeq() const
140{
141 if (!mSeq) {
143 mSeq = std::make_unique<Alg_seq>();
144 else {
145 std::unique_ptr<Alg_track> alg_track
146 { Alg_seq::unserialize
148 wxASSERT(alg_track->get_type() == 's');
149 mSeq.reset( static_cast<Alg_seq*>(alg_track.release()) );
150
151 // Preserve the invariant that at most one of the representations is
152 // valid
153 mSerializationBuffer.reset();
155 }
156 }
157 wxASSERT(mSeq);
158 return *mSeq;
159}
160
162{
163 auto duplicate = std::make_shared<NoteTrack>();
164 duplicate->Init(*this);
165 // The duplicate begins life in serialized state. Often the duplicate is
166 // pushed on the Undo stack. Then we want to un-serialize it (or a further
167 // copy) only on demand after an Undo.
168 if (mSeq) {
170 wxASSERT(!mSerializationBuffer);
171 // serialize from this to duplicate's mSerializationBuffer
172 void *buffer;
173 mSeq->serialize(&buffer,
174 &duplicate->mSerializationLength);
175 duplicate->mSerializationBuffer.reset( (char*)buffer );
177 }
178 else if (mSerializationBuffer) {
179 // Copy already serialized data.
180 wxASSERT(!mSeq);
181 duplicate->mSerializationLength = this->mSerializationLength;
182 duplicate->mSerializationBuffer.reset
183 ( safenew char[ this->mSerializationLength ] );
184 memcpy( duplicate->mSerializationBuffer.get(),
185 this->mSerializationBuffer.get(), this->mSerializationLength );
186 }
187 else {
188 // We are duplicating a default-constructed NoteTrack, and that's okay
189 }
190 // copy some other fields here
191 duplicate->SetBottomNote(mBottomNote);
192 duplicate->SetTopNote(mTopNote);
193 duplicate->SetVisibleChannels(GetVisibleChannels());
194 duplicate->SetOffset(GetOffset());
195#ifdef EXPERIMENTAL_MIDI_OUT
196 duplicate->SetVelocity(GetVelocity());
197#endif
198 return duplicate;
199}
200
201
203{
204 return mOffset;
205}
206
208{
209 return GetOffset();
210}
211
213{
214 return GetStartTime() + GetSeq().get_real_dur();
215}
216
217void NoteTrack::WarpAndTransposeNotes(double t0, double t1,
218 const TimeWarper &warper,
219 double semitones)
220{
221 double offset = this->GetOffset(); // track is shifted this amount
222 auto &seq = GetSeq();
223 seq.convert_to_seconds(); // make sure time units are right
224 t1 -= offset; // adjust time range to compensate for track offset
225 t0 -= offset;
226 if (t1 > seq.get_dur()) { // make sure t0, t1 are within sequence
227 t1 = seq.get_dur();
228 if (t0 >= t1) return;
229 }
230 Alg_iterator iter(mSeq.get(), false);
231 iter.begin();
232 Alg_event_ptr event;
233 while (0 != (event = iter.next()) && event->time < t1) {
234 if (event->is_note() && event->time >= t0) {
235 event->set_pitch(event->get_pitch() + semitones);
236 }
237 }
238 iter.end();
239 // now, use warper to warp the tempo map
240 seq.convert_to_beats(); // beats remain the same
241 Alg_time_map_ptr map = seq.get_time_map();
242 map->insert_beat(t0, map->time_to_beat(t0));
243 map->insert_beat(t1, map->time_to_beat(t1));
244 int i, len = map->length();
245 for (i = 0; i < len; i++) {
246 Alg_beat &beat = map->beats[i];
247 beat.time = warper.Warp(beat.time + offset) - offset;
248 }
249 // about to redisplay, so might as well convert back to time now
250 seq.convert_to_seconds();
251}
252
253// Draws the midi channel toggle buttons within the given rect.
254// The rect should be evenly divisible by 4 on both axis.
256( const NoteTrack *pTrack, wxDC & dc, const wxRect &rect, int highlightedChannel )
257{
258 dc.SetTextForeground(theTheme.Colour(clrLabelTrackText));
259 wxASSERT_MSG(rect.width % 4 == 0, "Midi channel control rect width must be divisible by 4");
260 wxASSERT_MSG(rect.height % 4 == 0, "Midi channel control rect height must be divisible by 4");
261
262 auto cellWidth = rect.width / 4;
263 auto cellHeight = rect.height / 4;
264
265 wxRect box;
266 for (int row = 0; row < 4; row++) {
267 for (int col = 0; col < 4; col++) {
268 // chanName is the "external" channel number (1-16)
269 // used by AColor and button labels
270 int chanName = row * 4 + col + 1;
271
272 box.x = rect.x + col * cellWidth;
273 box.y = rect.y + row * cellHeight;
274 box.width = cellWidth;
275 box.height = cellHeight;
276
277 bool visible = pTrack ? pTrack->IsVisibleChan(chanName - 1) : true;
278 if (visible) {
279 // highlightedChannel counts 0 based
280 if ( chanName == highlightedChannel + 1 )
281 AColor::LightMIDIChannel(&dc, chanName);
282 else
283 AColor::MIDIChannel(&dc, chanName);
284 dc.DrawRectangle(box);
285// two choices: channel is enabled (to see and play) when button is in
286// "up" position (original Audacity style) or in "down" position
287//
288#define CHANNEL_ON_IS_DOWN 1
289#if CHANNEL_ON_IS_DOWN
290 AColor::DarkMIDIChannel(&dc, chanName);
291#else
292 AColor::LightMIDIChannel(&dc, chanName);
293#endif
294 AColor::Line(dc, box.x, box.y, box.x + box.width - 1, box.y);
295 AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1);
296
297#if CHANNEL_ON_IS_DOWN
298 AColor::LightMIDIChannel(&dc, chanName);
299#else
300 AColor::DarkMIDIChannel(&dc, chanName);
301#endif
302 AColor::Line(dc,
303 box.x + box.width - 1, box.y,
304 box.x + box.width - 1, box.y + box.height - 1);
305 AColor::Line(dc,
306 box.x, box.y + box.height - 1,
307 box.x + box.width - 1, box.y + box.height - 1);
308 } else {
309 if ( chanName == highlightedChannel + 1 )
310 AColor::LightMIDIChannel(&dc, chanName);
311 else
312 AColor::MIDIChannel(&dc, 0);
313 dc.DrawRectangle(box);
314#if CHANNEL_ON_IS_DOWN
316#else
318#endif
319 AColor::Line(dc, box.x, box.y, box.x + box.width - 1, box.y);
320 AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1);
321
322#if CHANNEL_ON_IS_DOWN
324#else
326#endif
327 AColor::Line(dc,
328 box.x + box.width - 1, box.y,
329 box.x + box.width - 1, box.y + box.height - 1);
330 AColor::Line(dc,
331 box.x, box.y + box.height - 1,
332 box.x + box.width - 1, box.y + box.height - 1);
333
334 }
335
336 wxString text;
337 wxCoord w;
338 wxCoord h;
339
340 text.Printf(wxT("%d"), chanName);
341 dc.GetTextExtent(text, &w, &h);
342
343 dc.DrawText(text, box.x + (box.width - w) / 2, box.y + (box.height - h) / 2);
344 }
345 }
346 dc.SetTextForeground(theTheme.Colour(clrTrackPanelText));
347 AColor::MIDIChannel(&dc, 0); // always return with gray color selected
348}
349
350int NoteTrack::FindChannel(const wxRect &rect, int mx, int my)
351{
352 wxASSERT_MSG(rect.width % 4 == 0, "Midi channel control rect width must be divisible by 4");
353 wxASSERT_MSG(rect.height % 4 == 0, "Midi channel control rect height must be divisible by 4");
354
355 auto cellWidth = rect.width / 4;
356 auto cellHeight = rect.height / 4;
357
358 int col = (mx - rect.x) / cellWidth;
359 int row = (my - rect.y) / cellHeight;
360
361 return row * 4 + col;
362}
363
364
365// Handles clicking within the midi controls rect (same as DrawLabelControls).
366// This is somewhat oddly written, as these aren't real buttons - they act
367// when the mouse goes down; you can't hold it pressed and move off of it.
368// Left-clicking toggles a single channel; right-clicking turns off all other channels.
369bool NoteTrack::LabelClick(const wxRect &rect, int mx, int my, bool right)
370{
371 auto channel = FindChannel(rect, mx, my);
372 if (right)
373 SoloVisibleChan(channel);
374 else
375 ToggleVisibleChan(channel);
376
377 return true;
378}
379
380void NoteTrack::SetSequence(std::unique_ptr<Alg_seq> &&seq)
381{
382 mSeq = std::move(seq);
383}
384
386{
387 FILE *debugOutput;
388
389 debugOutput = fopen("debugOutput.txt", "wt");
390 wxFprintf(debugOutput, "Importing MIDI...\n");
391
392 // This is called for debugging purposes. Do not compute mSeq on demand
393 // with GetSeq()
394 if (mSeq) {
395 int i = 0;
396
397 while(i < mSeq->length()) {
398 wxFprintf(debugOutput, "--\n");
399 wxFprintf(debugOutput, "type: %c\n",
400 ((Alg_event_ptr)mSeq->track_list.tracks[i])->get_type());
401 wxFprintf(debugOutput, "time: %f\n",
402 ((Alg_event_ptr)mSeq->track_list.tracks[i])->time);
403 wxFprintf(debugOutput, "channel: %li\n",
404 ((Alg_event_ptr)mSeq->track_list.tracks[i])->chan);
405
406 if(((Alg_event_ptr)mSeq->track_list.tracks[i])->get_type() == wxT('n'))
407 {
408 wxFprintf(debugOutput, "pitch: %f\n",
409 ((Alg_note_ptr)mSeq->track_list.tracks[i])->pitch);
410 wxFprintf(debugOutput, "duration: %f\n",
411 ((Alg_note_ptr)mSeq->track_list.tracks[i])->dur);
412 wxFprintf(debugOutput, "velocity: %f\n",
413 ((Alg_note_ptr)mSeq->track_list.tracks[i])->loud);
414 }
415 else if(((Alg_event_ptr)mSeq->track_list.tracks[i])->get_type() == wxT('n'))
416 {
417 wxFprintf(debugOutput, "key: %li\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->get_identifier());
418 wxFprintf(debugOutput, "attribute type: %c\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_type());
419 wxFprintf(debugOutput, "attribute: %s\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_name());
420
421 if(((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_type() == wxT('r'))
422 {
423 wxFprintf(debugOutput, "value: %f\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.r);
424 }
425 else if(((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_type() == wxT('i')) {
426 wxFprintf(debugOutput, "value: %li\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.i);
427 }
428 else if(((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_type() == wxT('s')) {
429 wxFprintf(debugOutput, "value: %s\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.s);
430 }
431 else {}
432 }
433
434 i++;
435 }
436 }
437 else {
438 wxFprintf(debugOutput, "No sequence defined!\n");
439 }
440
441 fclose(debugOutput);
442}
443
444Track::Holder NoteTrack::Cut(double t0, double t1)
445{
446 if (t1 < t0)
448
449 double len = t1-t0;
450 //auto delta = -(
451 //( std::min( t1, GetEndTime() ) ) - ( std::max( t0, GetStartTime() ) )
452 //);
453
454 auto newTrack = std::make_shared<NoteTrack>();
455
456 newTrack->Init(*this);
457
458 auto &seq = GetSeq();
459 seq.convert_to_seconds();
460 newTrack->mSeq.reset(seq.cut(t0 - GetOffset(), len, false));
461 newTrack->SetOffset(0);
462
463 // Not needed
464 // Alg_seq::cut seems to handle this
465 //AddToDuration( delta );
466
467 // What should be done with the rest of newTrack's members?
468 //(mBottomNote,
469 // mSerializationBuffer, mSerializationLength, mVisibleChannels)
470
471 return newTrack;
472}
473
474Track::Holder NoteTrack::Copy(double t0, double t1, bool) const
475{
476 if (t1 < t0)
478
479 double len = t1-t0;
480
481 auto newTrack = std::make_shared<NoteTrack>();
482
483 newTrack->Init(*this);
484
485 auto &seq = GetSeq();
486 seq.convert_to_seconds();
487 newTrack->mSeq.reset(seq.copy(t0 - GetOffset(), len, false));
488 newTrack->SetOffset(0);
489
490 // What should be done with the rest of newTrack's members?
491 // (mBottomNote, mSerializationBuffer,
492 // mSerializationLength, mVisibleChannels)
493
494 return newTrack;
495}
496
497bool NoteTrack::Trim(double t0, double t1)
498{
499 if (t1 < t0)
500 return false;
501 auto &seq = GetSeq();
502 //auto delta = -(
503 //( GetEndTime() - std::min( GetEndTime(), t1 ) ) +
504 //( std::max(t0, GetStartTime()) - GetStartTime() )
505 //);
506 seq.convert_to_seconds();
507 // DELETE way beyond duration just in case something is out there:
508 seq.clear(t1 - GetOffset(), seq.get_dur() + 10000.0, false);
509 // Now that stuff beyond selection is cleared, clear before selection:
510 seq.clear(0.0, t0 - GetOffset(), false);
511 // want starting time to be t0
512 SetOffset(t0);
513
514 // Not needed
515 // Alg_seq::clear seems to handle this
516 //AddToDuration( delta );
517
518 return true;
519}
520
521void NoteTrack::Clear(double t0, double t1)
522{
523 if (t1 < t0)
525
526 double len = t1-t0;
527
528 auto &seq = GetSeq();
529
530 auto offset = GetOffset();
531 auto start = t0 - offset;
532 if (start < 0.0) {
533 // AlgSeq::clear will shift the cleared interval, not changing len, if
534 // start is negative. That's not what we want to happen.
535 if (len > -start) {
536 seq.clear(0, len + start, false);
537 SetOffset(t0);
538 }
539 else
540 SetOffset(offset - len);
541 }
542 else {
543 //auto delta = -(
544 //( std::min( t1, GetEndTime() ) ) - ( std::max( t0, GetStartTime() ) )
545 //);
546 seq.clear(start, len, false);
547
548 // Not needed
549 // Alg_seq::clear seems to handle this
550 // AddToDuration( delta );
551 }
552}
553
554void NoteTrack::Paste(double t, const Track *src)
555{
556 // Paste inserts src at time t. If src has a positive offset,
557 // the offset is treated as silence which is also inserted. If
558 // the offset is negative, the offset is ignored and the ENTIRE
559 // src is inserted (otherwise, we would either lose data from
560 // src by not inserting things at negative times, or inserting
561 // things at negative times could overlap things already in
562 // the destination track).
563
564 //Check that src is a non-NULL NoteTrack
565 bool bOk = src && src->TypeSwitch< bool >( [&](const NoteTrack *other) {
566
567 auto myOffset = this->GetOffset();
568 if (t < myOffset) {
569 // workaround strange behavior described at
570 // http://bugzilla.audacityteam.org/show_bug.cgi?id=1735#c3
571 SetOffset(t);
572 InsertSilence(t, myOffset - t);
573 }
574
575 double delta = 0.0;
576 auto &seq = GetSeq();
577 auto offset = other->GetOffset();
578 if ( offset > 0 ) {
579 seq.convert_to_seconds();
580 seq.insert_silence( t - GetOffset(), offset );
581 t += offset;
582 // Is this needed or does Alg_seq::insert_silence take care of it?
583 //delta += offset;
584 }
585
586 // This seems to be needed:
587 delta += std::max( 0.0, t - GetEndTime() );
588
589 // This, not:
590 //delta += other->GetSeq().get_real_dur();
591
592 seq.paste(t - GetOffset(), &other->GetSeq());
593
594 AddToDuration( delta );
595
596 return true;
597 });
598
599 if ( !bOk )
600 // THROW_INCONSISTENCY_EXCEPTION; // ?
601 (void)0;// intentionally do nothing
602}
603
604void NoteTrack::Silence(double t0, double t1)
605{
606 if (t1 < t0)
608
609 auto len = t1 - t0;
610
611 auto &seq = GetSeq();
612 seq.convert_to_seconds();
613 // XXX: do we want to set the all param?
614 // If it's set, then it seems like notes are silenced if they start or end in the range,
615 // otherwise only if they start in the range. --Poke
616 seq.silence(t0 - GetOffset(), len, false);
617}
618
619void NoteTrack::InsertSilence(double t, double len)
620{
621 if (len < 0)
623
624 auto &seq = GetSeq();
625 seq.convert_to_seconds();
626 seq.insert_silence(t - GetOffset(), len);
627
628 // is this needed?
629 // AddToDuration( len );
630}
631
632#ifdef EXPERIMENTAL_MIDI_OUT
633void NoteTrack::SetVelocity(float velocity)
634{
635 if (GetVelocity() != velocity) {
636 DoSetVelocity(velocity);
637 Notify();
638 }
639}
640
641void NoteTrack::DoSetVelocity(float velocity)
642{
643 mVelocity.store(velocity, std::memory_order_relaxed);
644}
645#endif
646
647// Call this function to manipulate the underlying sequence data. This is
648// NOT the function that handles horizontal dragging.
649bool NoteTrack::Shift(double t) // t is always seconds
650{
651 if (t > 0) {
652 auto &seq = GetSeq();
653 // insert an even number of measures
654 seq.convert_to_beats();
655 // get initial tempo
656 double tempo = seq.get_tempo(0.0);
657 double beats_per_measure = seq.get_bar_len(0.0);
658 int m = ROUND(t * tempo / beats_per_measure);
659 // need at least 1 measure, so if we rounded down to zero, fix it
660 if (m == 0) m = 1;
661 // compute NEW tempo so that m measures at NEW tempo take t seconds
662 tempo = beats_per_measure * m / t; // in beats per second
663 seq.insert_silence(0.0, beats_per_measure * m);
664 seq.set_tempo(tempo * 60.0 /* bpm */, 0.0, beats_per_measure * m);
665 seq.write("afterShift.gro");
666 } else if (t < 0) {
667 auto &seq = GetSeq();
668 seq.convert_to_seconds();
669 seq.clear(0, t, true);
670 } else { // offset is zero, no modifications
671 return false;
672 }
673 return true;
674}
675
677{
678 // Alg_seq knows nothing about offset, so remove offset time
679 double seq_time = time - GetOffset();
680 double beat;
681 auto &seq = GetSeq();
682 seq_time = seq.nearest_beat_time(seq_time, &beat);
683 // add the offset back in to get "actual" audacity track time
684 return { seq_time + GetOffset(), beat };
685}
686
688{
689 static const Track::TypeInfo info{
690 { "note", "midi", XO("Note Track") }, true,
692 return info;
693}
694
695auto NoteTrack::GetTypeInfo() const -> const TypeInfo &
696{
697 return typeInfo();
698}
699
701{
702 return typeInfo();
703}
704
706{
707 auto pNewTrack = std::make_shared<NoteTrack>();
708 pNewTrack->Init(*this);
709 pNewTrack->Paste(0.0, this);
710 return pNewTrack;
711}
712
714{
715 ConstIntervals results;
716 results.emplace_back( GetStartTime(), GetEndTime() );
717 return results;
718}
719
721{
722 Intervals results;
723 results.emplace_back( GetStartTime(), GetEndTime() );
724 return results;
725}
726
727void NoteTrack::AddToDuration( double delta )
728{
729 auto &seq = GetSeq();
730#if 0
731 // PRL: Would this be better ?
732 seq.set_real_dur( seq.get_real_dur() + delta );
733#else
734 seq.convert_to_seconds();
735 seq.set_dur( seq.get_dur() + delta );
736#endif
737}
738
740 ( QuantizedTimeAndBeat t0, QuantizedTimeAndBeat t1, double newDur )
741{
742 auto &seq = GetSeq();
743 bool result = seq.stretch_region( t0.second, t1.second, newDur );
744 if (result) {
745 const auto oldDur = t1.first - t0.first;
746 AddToDuration( newDur - oldDur );
747 }
748 return result;
749}
750
751namespace
752{
753 void swap(std::unique_ptr<Alg_seq> &a, std::unique_ptr<Alg_seq> &b)
754 {
755 std::unique_ptr<Alg_seq> tmp = std::move(a);
756 a = std::move(b);
757 b = std::move(tmp);
758 }
759}
760
761Alg_seq *NoteTrack::MakeExportableSeq(std::unique_ptr<Alg_seq> &cleanup) const
762{
763 cleanup.reset();
764 double offset = GetOffset();
765 if (offset == 0)
766 return &GetSeq();
767 // make a copy, deleting events that are shifted before time 0
768 double start = -offset;
769 if (start < 0) start = 0;
770 // notes that begin before "start" are not included even if they
771 // extend past "start" (because "all" parameter is set to false)
772 cleanup.reset( GetSeq().copy(start, GetSeq().get_dur() - start, false) );
773 auto seq = cleanup.get();
774 if (offset > 0) {
775 {
776 // swap cleanup and mSeq so that Shift operates on the NEW copy
777 swap( this->mSeq, cleanup );
778 auto cleanup2 = finally( [&] { swap( this->mSeq, cleanup ); } );
779
780 const_cast< NoteTrack *>( this )->Shift(offset);
781 }
782#ifdef OLD_CODE
783 // now shift events by offset. This must be done with an integer
784 // number of measures, so first, find the beats-per-measure
785 double beats_per_measure = 4.0;
786 Alg_time_sig_ptr tsp = NULL;
787 if (seq->time_sig.length() > 0 && seq->time_sig[0].beat < ALG_EPS) {
788 // there is an initial time signature
789 tsp = &(seq->time_sig[0]);
790 beats_per_measure = (tsp->num * 4) / tsp->den;
791 }
792 // also need the initial tempo
793 double bps = ALG_DEFAULT_BPM / 60;
794 Alg_time_map_ptr map = seq->get_time_map();
795 Alg_beat_ptr bp = &(map->beats[0]);
796 if (bp->time < ALG_EPS) { // tempo change at time 0
797 if (map->beats.len > 1) { // compute slope to get tempo
798 bps = (map->beats[1].beat - map->beats[0].beat) /
799 (map->beats[1].time - map->beats[0].time);
800 } else if (seq->get_time_map()->last_tempo_flag) {
801 bps = seq->get_time_map()->last_tempo;
802 }
803 }
804 // find closest number of measures to fit in the gap
805 // number of measures is offset / measure_time
806 double measure_time = beats_per_measure / bps; // seconds per measure
807 int n = ROUND(offset / measure_time);
808 if (n == 0) n = 1;
809 // we will insert n measures. Compute the desired duration of each.
810 measure_time = offset / n;
811 bps = beats_per_measure / measure_time;
812 // insert integer multiple of measures at beginning
813 seq->convert_to_beats();
814 seq->insert_silence(0, beats_per_measure * n);
815 // make sure time signature at 0 is correct
816 if (tsp) {
817 seq->set_time_sig(0, tsp->num, tsp->den);
818 }
819 // adjust tempo to match offset
820 seq->set_tempo(bps * 60.0, 0, beats_per_measure * n);
821#endif
822 } else {
823 auto &mySeq = GetSeq();
824 // if offset is negative, it might not be a multiple of beats, but
825 // we want to preserve the relative positions of measures. I.e. we
826 // should shift barlines and time signatures as well as notes.
827 // Insert a time signature at the first bar-line if necessary.
828
829 // Translate start from seconds to beats and call it beat:
830 double beat = mySeq.get_time_map()->time_to_beat(start);
831 // Find the time signature in mySeq in effect at start (beat):
832 int i = mySeq.time_sig.find_beat(beat);
833 // i is where you would insert a NEW time sig at beat,
834 // Case 1: beat coincides with a time sig at i. Time signature
835 // at beat means that there is a barline at beat, so when beat
836 // is shifted to 0, the relative barline positions are preserved
837 if (mySeq.time_sig.length() > 0 &&
838 within(beat, mySeq.time_sig[i].beat, ALG_EPS)) {
839 // beat coincides with time signature change, so offset must
840 // be a multiple of beats
841 /* do nothing */ ;
842 // Case 2: there is no time signature before beat.
843 } else if (i == 0 && (mySeq.time_sig.length() == 0 ||
844 mySeq.time_sig[i].beat > beat)) {
845 // If beat does not fall on an implied barline, we need to
846 // insert a time signature.
847 double measures = beat / 4.0;
848 double imeasures = ROUND(measures);
849 if (!within(measures, imeasures, ALG_EPS)) {
850 double bar_offset = ((int)(measures) + 1) * 4.0 - beat;
851 seq->set_time_sig(bar_offset, 4, 4);
852 }
853 // This case should never be true because if i == 0, either there
854 // are no time signatures before beat (Case 2),
855 // or there is one time signature at beat (Case 1)
856 } else if (i == 0) {
857 /* do nothing (might be good to assert(false)) */ ;
858 // Case 3: i-1 must be the effective time sig position
859 } else {
860 i -= 1; // index the time signature in effect at beat
861 Alg_time_sig_ptr tsp = &(mySeq.time_sig[i]);
862 double beats_per_measure = (tsp->num * 4) / tsp->den;
863 double measures = (beat - tsp->beat) / beats_per_measure;
864 int imeasures = ROUND(measures);
865 if (!within(measures, imeasures, ALG_EPS)) {
866 // beat is not on a measure, so we need to insert a time sig
867 // to force a bar line at the first measure location after
868 // beat
869 double bar = tsp->beat + beats_per_measure * ((int)(measures) + 1);
870 double bar_offset = bar - beat;
871 // insert NEW time signature at bar_offset in NEW sequence
872 // It will have the same time signature, but the position will
873 // force a barline to match the barlines in mSeq
874 seq->set_time_sig(bar_offset, tsp->num, tsp->den);
875 }
876 // else beat coincides with a barline, so no need for an extra
877 // time signature to force barline alignment
878 }
879 }
880 return seq;
881}
882
883
884bool NoteTrack::ExportMIDI(const wxString &f) const
885{
886 std::unique_ptr<Alg_seq> cleanup;
887 auto seq = MakeExportableSeq(cleanup);
888 bool rslt = seq->smf_write(f.mb_str());
889 return rslt;
890}
891
892bool NoteTrack::ExportAllegro(const wxString &f) const
893{
894 double offset = GetOffset();
896 auto &seq = GetSeq();
897 if (in_seconds) {
898 seq.convert_to_seconds();
899 } else {
900 seq.convert_to_beats();
901 }
902 return seq.write(f.mb_str(), offset);
903}
904
905
906namespace {
907bool IsValidVisibleChannels(const int nValue)
908{
909 return (nValue >= 0 && nValue < (1 << 16));
910}
911}
912
913bool NoteTrack::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
914{
915 if (tag == "notetrack") {
916 for (auto pair : attrs)
917 {
918 auto attr = pair.first;
919 auto value = pair.second;
920
921 long nValue;
922 double dblValue;
923 if (this->Track::HandleCommonXMLAttribute(attr, value))
924 ;
925 else if (this->NoteTrackBase::HandleXMLAttribute(attr, value))
926 {}
927 else if (attr == "offset" && value.TryGet(dblValue))
928 SetOffset(dblValue);
929 else if (attr == "visiblechannels") {
930 if (!value.TryGet(nValue) ||
931 !IsValidVisibleChannels(nValue))
932 return false;
933 SetVisibleChannels(nValue);
934 }
935#ifdef EXPERIMENTAL_MIDI_OUT
936 else if (attr == "velocity" && value.TryGet(dblValue))
937 DoSetVelocity(static_cast<float>(dblValue));
938#endif
939 else if (attr == "bottomnote" && value.TryGet(nValue))
940 SetBottomNote(nValue);
941 else if (attr == "topnote" && value.TryGet(nValue))
942 SetTopNote(nValue);
943 else if (attr == "data") {
944 std::string s(value.ToWString());
945 std::istringstream data(s);
946 mSeq = std::make_unique<Alg_seq>(data, false);
947 }
948 } // while
949 return true;
950 }
951 return false;
952}
953
954XMLTagHandler *NoteTrack::HandleXMLChild(const std::string_view& WXUNUSED(tag))
955{
956 return NULL;
957}
958
959void NoteTrack::WriteXML(XMLWriter &xmlFile) const
960// may throw
961{
962 std::ostringstream data;
963 Track::Holder holder;
964 const NoteTrack *saveme = this;
965 if (!mSeq) {
966 // replace saveme with an (unserialized) duplicate, which is
967 // destroyed at end of function.
968 holder = Clone();
969 saveme = static_cast<NoteTrack*>(holder.get());
970 }
971 saveme->GetSeq().write(data, true);
972 xmlFile.StartTag(wxT("notetrack"));
973 saveme->Track::WriteCommonXMLAttributes( xmlFile );
975 xmlFile.WriteAttr(wxT("offset"), saveme->GetOffset());
976 xmlFile.WriteAttr(wxT("visiblechannels"),
977 static_cast<int>(saveme->GetVisibleChannels()));
978
979#ifdef EXPERIMENTAL_MIDI_OUT
980 xmlFile.WriteAttr(wxT("velocity"),
981 static_cast<double>(saveme->GetVelocity()));
982#endif
983 xmlFile.WriteAttr(wxT("bottomnote"), saveme->mBottomNote);
984 xmlFile.WriteAttr(wxT("topnote"), saveme->mTopNote);
985 xmlFile.WriteAttr(wxT("data"), wxString(data.str().c_str(), wxConvUTF8));
986 xmlFile.EndTag(wxT("notetrack"));
987}
988
990{
991 if (note < MinPitch)
992 note = MinPitch;
993 else if (note > 96)
994 note = 96;
995
996 wxCHECK(note <= mTopNote, );
997
998 mBottomNote = note;
999}
1000
1002{
1003 if (note > MaxPitch)
1004 note = MaxPitch;
1005
1006 wxCHECK(note >= mBottomNote, );
1007
1008 mTopNote = note;
1009}
1010
1011void NoteTrack::SetNoteRange(int note1, int note2)
1012{
1013 // Bounds check
1014 if (note1 > MaxPitch)
1015 note1 = MaxPitch;
1016 else if (note1 < MinPitch)
1017 note1 = MinPitch;
1018 if (note2 > MaxPitch)
1019 note2 = MaxPitch;
1020 else if (note2 < MinPitch)
1021 note2 = MinPitch;
1022 // Swap to ensure ordering
1023 if (note2 < note1) { auto tmp = note1; note1 = note2; note2 = tmp; }
1024
1025 mBottomNote = note1;
1026 mTopNote = note2;
1027}
1028
1030{
1031 // Ensure everything stays in bounds
1032 if (mBottomNote + offset < MinPitch || mTopNote + offset > MaxPitch)
1033 return;
1034
1035 mBottomNote += offset;
1036 mTopNote += offset;
1037}
1038
1039#if 0
1040void NoteTrack::StartVScroll()
1041{
1042 mStartBottomNote = mBottomNote;
1043}
1044
1045void NoteTrack::VScroll(int start, int end)
1046{
1047 int ph = GetPitchHeight();
1048 int delta = ((end - start) + ph / 2) / ph;
1049 ShiftNoteRange(delta);
1050}
1051#endif
1052
1053void NoteTrack::Zoom(const wxRect &rect, int y, float multiplier, bool center)
1054{
1055 NoteTrackDisplayData data = NoteTrackDisplayData(this, rect);
1056 int clickedPitch = data.YToIPitch(y);
1057 int extent = mTopNote - mBottomNote + 1;
1058 int newExtent = (int) (extent / multiplier);
1059 float position;
1060 if (center) {
1061 // center the pitch that the user clicked on
1062 position = .5;
1063 } else {
1064 // align to keep the pitch that the user clicked on in the same place
1065 position = extent / (clickedPitch - mBottomNote);
1066 }
1067 int newBottomNote = clickedPitch - (newExtent * position);
1068 int newTopNote = clickedPitch + (newExtent * (1 - position));
1069 SetNoteRange(newBottomNote, newTopNote);
1070}
1071
1072
1073void NoteTrack::ZoomTo(const wxRect &rect, int start, int end)
1074{
1075 wxRect trackRect(0, rect.GetY(), 1, rect.GetHeight());
1076 NoteTrackDisplayData data = NoteTrackDisplayData(this, trackRect);
1077 int pitch1 = data.YToIPitch(start);
1078 int pitch2 = data.YToIPitch(end);
1079 if (pitch1 == pitch2) {
1080 // Just zoom in instead of zooming to show only one note
1081 Zoom(rect, start, 1, true);
1082 return;
1083 }
1084 // It's fine for this to be in either order
1085 SetNoteRange(pitch1, pitch2);
1086}
1087
1089{
1090 Alg_iterator iterator( &GetSeq(), false );
1091 iterator.begin();
1092 Alg_event_ptr evt;
1093
1094 // Go through all of the notes, finding the minimum and maximum value pitches.
1095 bool hasNotes = false;
1096 int minPitch = MaxPitch;
1097 int maxPitch = MinPitch;
1098
1099 while (NULL != (evt = iterator.next())) {
1100 if (evt->is_note()) {
1101 int pitch = (int) evt->get_pitch();
1102 hasNotes = true;
1103 if (pitch < minPitch)
1104 minPitch = pitch;
1105 if (pitch > maxPitch)
1106 maxPitch = pitch;
1107 }
1108 }
1109
1110 if (!hasNotes) {
1111 // Semi-arbitrary default values:
1112 minPitch = 48;
1113 maxPitch = 72;
1114 }
1115
1116 SetNoteRange(minPitch, maxPitch);
1117}
1118
1120{
1121 auto span = track->GetTopNote() - track->GetBottomNote() + 1; // + 1 to make sure it includes both
1122
1123 mMargin = std::min((int) (r.height / (float)(span)) / 2, r.height / 4);
1124
1125 // Count the number of dividers between B/C and E/F
1126 int numC = 0, numF = 0;
1127 auto botOctave = track->GetBottomNote() / 12, botNote = track->GetBottomNote() % 12;
1128 auto topOctave = track->GetTopNote() / 12, topNote = track->GetTopNote() % 12;
1129 if (topOctave == botOctave)
1130 {
1131 if (botNote == 0) numC = 1;
1132 if (topNote <= 5) numF = 1;
1133 }
1134 else
1135 {
1136 numC = topOctave - botOctave;
1137 numF = topOctave - botOctave - 1;
1138 if (botNote == 0) numC++;
1139 if (botNote <= 5) numF++;
1140 if (topOctave <= 5) numF++;
1141 }
1142 // Effective space, excluding the margins and the lines between some notes
1143 auto effectiveHeight = r.height - (2 * (mMargin + 1)) - numC - numF;
1144 // Guaranteed that both the bottom and top notes will be visible
1145 // (assuming that the clamping below does not happen)
1146 mPitchHeight = effectiveHeight / ((float) span);
1147
1152
1153 mBottom = r.y + r.height - GetNoteMargin() - 1 - GetPitchHeight(1) +
1154 botOctave * GetOctaveHeight() + GetNotePos(botNote);
1155}
1156
1158{ return mBottom - (p / 12) * GetOctaveHeight() - GetNotePos(p % 12); }
1159
1161{
1162 y = mBottom - y; // pixels above pitch 0
1163 int octave = (y / GetOctaveHeight());
1164 y -= octave * GetOctaveHeight();
1165 // result is approximate because C and G are one pixel taller than
1166 // mPitchHeight.
1167 // Poke 1-13-18: However in practice this seems not to be an issue,
1168 // as long as we use mPitchHeight and not the rounded version
1169 return (y / mPitchHeight) + octave * 12;
1170}
1171
1172const float NoteTrack::ZoomStep = powf( 2.0f, 0.25f );
1173
1174#include <wx/log.h>
1175#include <wx/sstream.h>
1176#include <wx/txtstrm.h>
1177#include "AudioIOBase.h"
1178#include "portmidi.h"
1179
1180// FIXME: When EXPERIMENTAL_MIDI_IN is added (eventually) this should also be enabled -- Poke
1182{
1183 wxStringOutputStream o;
1184 wxTextOutputStream s(o, wxEOL_UNIX);
1185
1186 if (AudioIOBase::Get()->IsStreamActive()) {
1187 return XO("Stream is active ... unable to gather information.\n")
1188 .Translation();
1189 }
1190
1191
1192 // XXX: May need to trap errors as with the normal device info
1193 int recDeviceNum = Pm_GetDefaultInputDeviceID();
1194 int playDeviceNum = Pm_GetDefaultOutputDeviceID();
1195 int cnt = Pm_CountDevices();
1196
1197 // PRL: why only into the log?
1198 wxLogDebug(wxT("PortMidi reports %d MIDI devices"), cnt);
1199
1200 s << wxT("==============================\n");
1201 s << XO("Default recording device number: %d\n").Format( recDeviceNum );
1202 s << XO("Default playback device number: %d\n").Format( playDeviceNum );
1203
1204 auto recDevice = MIDIRecordingDevice.Read();
1205 auto playDevice = MIDIPlaybackDevice.Read();
1206
1207 // This gets info on all available audio devices (input and output)
1208 if (cnt <= 0) {
1209 s << XO("No devices found\n");
1210 return o.GetString();
1211 }
1212
1213 for (int i = 0; i < cnt; i++) {
1214 s << wxT("==============================\n");
1215
1216 const PmDeviceInfo* info = Pm_GetDeviceInfo(i);
1217 if (!info) {
1218 s << XO("Device info unavailable for: %d\n").Format( i );
1219 continue;
1220 }
1221
1222 wxString name = wxSafeConvertMB2WX(info->name);
1223 wxString hostName = wxSafeConvertMB2WX(info->interf);
1224
1225 s << XO("Device ID: %d\n").Format( i );
1226 s << XO("Device name: %s\n").Format( name );
1227 s << XO("Host name: %s\n").Format( hostName );
1228 /* i18n-hint: Supported, meaning made available by the system */
1229 s << XO("Supports output: %d\n").Format( info->output );
1230 /* i18n-hint: Supported, meaning made available by the system */
1231 s << XO("Supports input: %d\n").Format( info->input );
1232 s << XO("Opened: %d\n").Format( info->opened );
1233
1234 if (name == playDevice && info->output)
1235 playDeviceNum = i;
1236
1237 if (name == recDevice && info->input)
1238 recDeviceNum = i;
1239
1240 // XXX: This is only done because the same was applied with PortAudio
1241 // If PortMidi returns -1 for the default device, use the first one
1242 if (recDeviceNum < 0 && info->input){
1243 recDeviceNum = i;
1244 }
1245 if (playDeviceNum < 0 && info->output){
1246 playDeviceNum = i;
1247 }
1248 }
1249
1250 bool haveRecDevice = (recDeviceNum >= 0);
1251 bool havePlayDevice = (playDeviceNum >= 0);
1252
1253 s << wxT("==============================\n");
1254 if (haveRecDevice)
1255 s << XO("Selected MIDI recording device: %d - %s\n").Format( recDeviceNum, recDevice );
1256 else
1257 s << XO("No MIDI recording device found for '%s'.\n").Format( recDevice );
1258
1259 if (havePlayDevice)
1260 s << XO("Selected MIDI playback device: %d - %s\n").Format( playDeviceNum, playDevice );
1261 else
1262 s << XO("No MIDI playback device found for '%s'.\n").Format( playDevice );
1263
1264 // Mention our conditional compilation flags for Alpha only
1265#ifdef IS_ALPHA
1266
1267 // Not internationalizing these alpha-only messages
1268 s << wxT("==============================\n");
1269#ifdef EXPERIMENTAL_MIDI_OUT
1270 s << wxT("EXPERIMENTAL_MIDI_OUT is enabled\n");
1271#else
1272 s << wxT("EXPERIMENTAL_MIDI_OUT is NOT enabled\n");
1273#endif
1274#ifdef EXPERIMENTAL_MIDI_IN
1275 s << wxT("EXPERIMENTAL_MIDI_IN is enabled\n");
1276#else
1277 s << wxT("EXPERIMENTAL_MIDI_IN is NOT enabled\n");
1278#endif
1279
1280#endif
1281
1282 return o.GetString();
1283}
1284
1285StringSetting MIDIPlaybackDevice{ L"/MidiIO/PlaybackDevice", L"" };
1286StringSetting MIDIRecordingDevice{ L"/MidiIO/RecordingDevice", L"" };
1287IntSetting MIDISynthLatency_ms{ L"/MidiIO/SynthLatency", 5 };
1288
1289#endif // USE_MIDI
int min(int a, int b)
const TranslatableString name
Definition: Distortion.cpp:82
MessageBoxException for violation of preconditions or assertions.
#define THROW_INCONSISTENCY_EXCEPTION
Throw InconsistencyException, using C++ preprocessor to identify the source code location.
#define XO(s)
Definition: Internat.h:31
#define _(s)
Definition: Internat.h:75
void PmStream
Definition: MIDIPlay.h:19
#define safenew
Definition: MemoryX.h:10
IntSetting MIDISynthLatency_ms
Definition: NoteTrack.cpp:1287
StringSetting MIDIRecordingDevice
Definition: NoteTrack.cpp:1286
static ProjectFileIORegistry::ObjectReaderEntry readerEntry
Definition: NoteTrack.cpp:110
wxString GetMIDIDeviceInfo()
Definition: NoteTrack.cpp:1181
StringSetting MIDIPlaybackDevice
Definition: NoteTrack.cpp:1285
#define ROUND(x)
Definition: NoteTrack.cpp:33
static const Track::TypeInfo & typeInfo()
Definition: NoteTrack.cpp:687
#define SonifyEndSerialize()
Definition: NoteTrack.h:312
#define SonifyBeginSerialize()
Definition: NoteTrack.h:311
#define SonifyEndSonification()
Definition: NoteTrack.h:304
#define SonifyBeginSonification()
Definition: NoteTrack.h:303
std::pair< double, double > QuantizedTimeAndBeat
Definition: NoteTrack.h:56
THEME_API Theme theTheme
Definition: Theme.cpp:82
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:166
std::vector< Attribute > AttributesList
Definition: XMLTagHandler.h:40
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:658
static void MIDIChannel(wxDC *dc, int channel)
Definition: AColor.cpp:642
static void DarkMIDIChannel(wxDC *dc, int channel)
Definition: AColor.cpp:676
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
static AudioIOBase * Get()
Definition: AudioIOBase.cpp:91
Track subclass holding data representing sound (as notes, or samples, or ...)
Definition: Track.h:897
bool HandleXMLAttribute(const std::string_view &, const XMLAttributeValueView &)
Definition: Track.h:908
void WriteXMLAttributes(XMLWriter &WXUNUSED(xmlFile)) const
Definition: Track.h:905
Enum ReadEnum() const
Definition: Prefs.h:524
static EnumSetting< bool > AllegroStyleSetting
Specialization of Setting for int.
Definition: Prefs.h:349
Data used to display a note track.
Definition: NoteTrack.h:250
int GetPitchHeight(int factor) const
Definition: NoteTrack.h:263
int GetNotePos(int p) const
Definition: NoteTrack.h:283
int GetOctaveHeight() const
Definition: NoteTrack.h:266
NoteTrackDisplayData(const NoteTrack *track, const wxRect &r)
Definition: NoteTrack.cpp:1119
int GetNoteMargin() const
Definition: NoteTrack.h:265
int YToIPitch(int y) const
Definition: NoteTrack.cpp:1160
int IPitchToY(int p) const
Definition: NoteTrack.cpp:1157
A Track that is used for Midi notes. (Somewhat old code).
Definition: NoteTrack.h:63
void SetSequence(std::unique_ptr< Alg_seq > &&seq)
Definition: NoteTrack.cpp:380
const TypeInfo & GetTypeInfo() const override
Definition: NoteTrack.cpp:695
static const TypeInfo & ClassTypeInfo()
Definition: NoteTrack.cpp:700
ConstIntervals GetIntervals() const override
Report times on the track where important intervals begin and end, for UI to snap to.
Definition: NoteTrack.cpp:713
bool ExportAllegro(const wxString &f) const
Definition: NoteTrack.cpp:892
Track::Holder Cut(double t0, double t1) override
Definition: NoteTrack.cpp:444
virtual ~NoteTrack()
Definition: NoteTrack.cpp:135
void SetNoteRange(int note1, int note2)
Sets the top and bottom note (both pitches) automatically, swapping them if needed.
Definition: NoteTrack.cpp:1011
void AddToDuration(double delta)
Definition: NoteTrack.cpp:727
std::unique_ptr< char[]> mSerializationBuffer
Definition: NoteTrack.h:222
int mTopNote
Definition: NoteTrack.h:230
void Paste(double t, const Track *src) override
Definition: NoteTrack.cpp:554
double GetOffset() const override
Definition: NoteTrack.cpp:202
bool ExportMIDI(const wxString &f) const
Definition: NoteTrack.cpp:884
bool IsVisibleChan(int c) const
Definition: NoteTrack.h:182
void SetVisibleChannels(unsigned value)
Definition: NoteTrack.h:179
void WriteXML(XMLWriter &xmlFile) const override
Definition: NoteTrack.cpp:959
void PrintSequence()
Definition: NoteTrack.cpp:385
double GetEndTime() const override
Definition: NoteTrack.cpp:212
int FindChannel(const wxRect &rect, int mx, int my)
Definition: NoteTrack.cpp:350
static void DrawLabelControls(const NoteTrack *pTrack, wxDC &dc, const wxRect &rect, int highlightedChannel=-1)
Definition: NoteTrack.cpp:256
void ShiftNoteRange(int offset)
Shifts all notes vertically by the given pitch.
Definition: NoteTrack.cpp:1029
std::unique_ptr< Alg_seq > mSeq
Definition: NoteTrack.h:221
void InsertSilence(double t, double len) override
Definition: NoteTrack.cpp:619
XMLTagHandler * HandleXMLChild(const std::string_view &tag) override
Definition: NoteTrack.cpp:954
int mBottomNote
Definition: NoteTrack.h:230
bool StretchRegion(QuantizedTimeAndBeat t0, QuantizedTimeAndBeat t1, double newDur)
Definition: NoteTrack.cpp:740
Alg_seq * MakeExportableSeq(std::unique_ptr< Alg_seq > &cleanup) const
Definition: NoteTrack.cpp:761
void ZoomAllNotes()
Zooms so that all notes are visible.
Definition: NoteTrack.cpp:1088
static NoteTrack * New(AudacityProject &project)
Definition: NoteTrack.cpp:115
bool Shift(double t)
Definition: NoteTrack.cpp:649
Alg_seq & GetSeq() const
Definition: NoteTrack.cpp:139
void SetTopNote(int note)
Sets the top note (a pitch), making sure that it is never less than the bottom note.
Definition: NoteTrack.cpp:1001
unsigned GetVisibleChannels() const
Definition: NoteTrack.h:176
QuantizedTimeAndBeat NearestBeatTime(double time) const
Definition: NoteTrack.cpp:676
void Zoom(const wxRect &rect, int y, float multiplier, bool center)
Definition: NoteTrack.cpp:1053
void WarpAndTransposeNotes(double t0, double t1, const TimeWarper &warper, double semitones)
Definition: NoteTrack.cpp:217
void ZoomTo(const wxRect &rect, int start, int end)
Definition: NoteTrack.cpp:1073
void ToggleVisibleChan(int c)
Definition: NoteTrack.h:189
bool Trim(double t0, double t1)
Definition: NoteTrack.cpp:497
void SoloVisibleChan(int c)
Definition: NoteTrack.h:193
Track::Holder Clone() const override
Definition: NoteTrack.cpp:161
Track::Holder PasteInto(AudacityProject &) const override
Find or create the destination track for a paste, maybe in a different project.
Definition: NoteTrack.cpp:705
double GetStartTime() const override
Definition: NoteTrack.cpp:207
bool HandleXMLTag(const std::string_view &tag, const AttributesList &attrs) override
Definition: NoteTrack.cpp:913
void SetBottomNote(int note)
Sets the bottom note (a pitch), making sure that it is never greater than the top note.
Definition: NoteTrack.cpp:989
int GetBottomNote() const
Gets the current bottom note (a pitch)
Definition: NoteTrack.h:122
static const float ZoomStep
Definition: NoteTrack.h:241
int GetTopNote() const
Gets the current top note (a pitch)
Definition: NoteTrack.h:124
void Clear(double t0, double t1) override
Definition: NoteTrack.cpp:521
long mSerializationLength
Definition: NoteTrack.h:223
bool LabelClick(const wxRect &rect, int x, int y, bool right)
Definition: NoteTrack.cpp:369
void Silence(double t0, double t1) override
Definition: NoteTrack.cpp:604
Track::Holder Copy(double t0, double t1, bool forClipboard=true) const override
Definition: NoteTrack.cpp:474
static const TypeInfo & ClassTypeInfo()
Definition: Track.cpp:1322
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:200
Specialization of Setting for strings.
Definition: Prefs.h:363
wxColour & Colour(int iIndex)
Transforms one point in time to another point. For example, a time stretching effect might use one to...
Definition: TimeWarper.h:62
virtual double Warp(double originalTime) const =0
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:225
virtual void SetOffset(double o)
Definition: Track.h:483
double mOffset
Definition: Track.h:447
R TypeSwitch(const Functions &...functions)
Use this function rather than testing track type explicitly and making down-casts.
Definition: Track.h:832
std::shared_ptr< Track > Holder
Definition: Track.h:368
void Notify(int code=-1)
Definition: Track.cpp:278
bool HandleCommonXMLAttribute(const std::string_view &attr, const XMLAttributeValueView &valueView)
Definition: Track.cpp:1283
std::vector< Interval > Intervals
Definition: Track.h:335
std::vector< ConstInterval > ConstIntervals
Definition: Track.h:337
void SetName(const wxString &n)
Definition: Track.cpp:79
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:486
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:42
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:26
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
bool IsValidVisibleChannels(const int nValue)
Definition: NoteTrack.cpp:907
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:753