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