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