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
24#if defined(USE_MIDI)
25#include "WrapAllegro.h"
26
27#include <sstream>
28
29#define ROUND(x) ((int) ((x) + 0.5))
30
31#include "Prefs.h"
32#include "Project.h"
33
35
36#include "TimeWarper.h"
37
38#ifdef SONIFY
39#include <portmidi.h>
40
41#define SON_PROGRAM 0
42#define SON_AutoSave 67
43#define SON_ModifyState 60
44#define SON_NoteBackground 72
45#define SON_NoteForeground 74
46#define SON_Measures 76 /* "bar line" */
47#define SON_Serialize 77
48#define SON_Unserialize 79
49#define SON_VEL 100
50
51
52PmStream *sonMidiStream;
53bool sonificationStarted = false;
54
56{
57 PmError err = Pm_OpenOutput(&sonMidiStream, Pm_GetDefaultOutputDeviceID(),
58 NULL, 0, NULL, NULL, 0);
59 if (err) sonMidiStream = NULL;
60 if (sonMidiStream)
61 Pm_WriteShort(sonMidiStream, 0, Pm_Message(0xC0, SON_PROGRAM, 0));
62 sonificationStarted = true;
63}
64
65
67{
68 if (sonMidiStream) Pm_Close(sonMidiStream);
69 sonificationStarted = false;
70}
71
72
73
74
75void SonifyNoteOnOff(int p, int v)
76{
77 if (!sonificationStarted)
79 if (sonMidiStream)
80 Pm_WriteShort(sonMidiStream, 0, Pm_Message(0x90, p, v));
81}
82
83#define SONFNS(name) \
84 void SonifyBegin ## name() { SonifyNoteOnOff(SON_ ## name, SON_VEL); } \
85 void SonifyEnd ## name() { SonifyNoteOnOff(SON_ ## name, 0); }
86
87SONFNS(NoteBackground)
88SONFNS(NoteForeground)
89SONFNS(Measures)
90SONFNS(Serialize)
91SONFNS(Unserialize)
92SONFNS(ModifyState)
93SONFNS(AutoSave)
94
95#undef SONFNS
96
97#endif
98
99
101
102std::shared_ptr<ChannelInterval>
104{
105 if (iChannel == 0)
106 return std::make_shared<ChannelInterval>();
107 return {};
108}
109
111
113{}
114
116{
117 return false;
118}
119
121 "notetrack",
123};
124
126{
127 auto &tracks = TrackList::Get( project );
128 auto result = tracks.Add( std::make_shared<NoteTrack>());
129 result->AttachedTrackObjects::BuildAll();
130 return result;
131}
132
135{
136 SetName(_("Note Track"));
137
138 mSeq = NULL;
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
198 // copy some other fields here
199 Attachments &attachments = *duplicate;
200 attachments = *this;
201
202 duplicate->SetVisibleChannels(GetVisibleChannels());
203 duplicate->MoveTo(mOrigin);
204#ifdef EXPERIMENTAL_MIDI_OUT
205 duplicate->SetVelocity(GetVelocity());
206#endif
207 return TrackList::Temporary(nullptr, duplicate, nullptr);
208}
209
210
212 const std::optional<double>& oldTempo, double newTempo)
213{
214 assert(IsLeader());
215 if (!oldTempo.has_value())
216 return;
217 const auto ratio = *oldTempo / newTempo;
218 auto& seq = GetSeq();
219 seq.convert_to_beats();
220 const auto b1 = seq.get_dur();
221 seq.convert_to_seconds();
222 const auto newDuration = seq.get_dur() * ratio;
223 seq.stretch_region(0, b1, newDuration);
224 seq.set_real_dur(newDuration);
225}
226
227void NoteTrack::WarpAndTransposeNotes(double t0, double t1,
228 const TimeWarper &warper,
229 double semitones)
230{
231 double offset = this->mOrigin; // track is shifted this amount
232 auto &seq = GetSeq();
233 seq.convert_to_seconds(); // make sure time units are right
234 t1 -= offset; // adjust time range to compensate for track offset
235 t0 -= offset;
236 if (t1 > seq.get_dur()) { // make sure t0, t1 are within sequence
237 t1 = seq.get_dur();
238 if (t0 >= t1) return;
239 }
240 Alg_iterator iter(mSeq.get(), false);
241 iter.begin();
242 Alg_event_ptr event;
243 while (0 != (event = iter.next()) && event->time < t1) {
244 if (event->is_note() && event->time >= t0) {
245 event->set_pitch(event->get_pitch() + semitones);
246 }
247 }
248 iter.end();
249 // now, use warper to warp the tempo map
250 seq.convert_to_beats(); // beats remain the same
251 Alg_time_map_ptr map = seq.get_time_map();
252 map->insert_beat(t0, map->time_to_beat(t0));
253 map->insert_beat(t1, map->time_to_beat(t1));
254 int i, len = map->length();
255 for (i = 0; i < len; i++) {
256 Alg_beat &beat = map->beats[i];
257 beat.time = warper.Warp(beat.time + offset) - offset;
258 }
259 // about to redisplay, so might as well convert back to time now
260 seq.convert_to_seconds();
261}
262
263void NoteTrack::SetSequence(std::unique_ptr<Alg_seq> &&seq)
264{
265 mSeq = std::move(seq);
266}
267
269{
270 FILE *debugOutput;
271
272 debugOutput = fopen("debugOutput.txt", "wt");
273 wxFprintf(debugOutput, "Importing MIDI...\n");
274
275 // This is called for debugging purposes. Do not compute mSeq on demand
276 // with GetSeq()
277 if (mSeq) {
278 int i = 0;
279
280 while(i < mSeq->length()) {
281 wxFprintf(debugOutput, "--\n");
282 wxFprintf(debugOutput, "type: %c\n",
283 ((Alg_event_ptr)mSeq->track_list.tracks[i])->get_type());
284 wxFprintf(debugOutput, "time: %f\n",
285 ((Alg_event_ptr)mSeq->track_list.tracks[i])->time);
286 wxFprintf(debugOutput, "channel: %li\n",
287 ((Alg_event_ptr)mSeq->track_list.tracks[i])->chan);
288
289 if(((Alg_event_ptr)mSeq->track_list.tracks[i])->get_type() == wxT('n'))
290 {
291 wxFprintf(debugOutput, "pitch: %f\n",
292 ((Alg_note_ptr)mSeq->track_list.tracks[i])->pitch);
293 wxFprintf(debugOutput, "duration: %f\n",
294 ((Alg_note_ptr)mSeq->track_list.tracks[i])->dur);
295 wxFprintf(debugOutput, "velocity: %f\n",
296 ((Alg_note_ptr)mSeq->track_list.tracks[i])->loud);
297 }
298 else if(((Alg_event_ptr)mSeq->track_list.tracks[i])->get_type() == wxT('n'))
299 {
300 wxFprintf(debugOutput, "key: %li\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->get_identifier());
301 wxFprintf(debugOutput, "attribute type: %c\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_type());
302 wxFprintf(debugOutput, "attribute: %s\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_name());
303
304 if(((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_type() == wxT('r'))
305 {
306 wxFprintf(debugOutput, "value: %f\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.r);
307 }
308 else if(((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_type() == wxT('i')) {
309 wxFprintf(debugOutput, "value: %li\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.i);
310 }
311 else if(((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_type() == wxT('s')) {
312 wxFprintf(debugOutput, "value: %s\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.s);
313 }
314 else {}
315 }
316
317 i++;
318 }
319 }
320 else {
321 wxFprintf(debugOutput, "No sequence defined!\n");
322 }
323
324 fclose(debugOutput);
325}
326
327TrackListHolder NoteTrack::Cut(double t0, double t1)
328{
329 assert(IsLeader());
330 if (t1 < t0)
332
333 double len = t1-t0;
334 //auto delta = -(
335 //( std::min( t1, GetEndTime() ) ) - ( std::max( t0, GetStartTime() ) )
336 //);
337
338 auto newTrack = std::make_shared<NoteTrack>();
339
340 newTrack->Init(*this);
341
342 auto &seq = GetSeq();
343 seq.convert_to_seconds();
344 newTrack->mSeq.reset(seq.cut(t0 - mOrigin, len, false));
345 newTrack->MoveTo(0);
346
347 // Not needed
348 // Alg_seq::cut seems to handle this
349 //AddToDuration( delta );
350
351 // What should be done with the rest of newTrack's members?
352 //(mBottomNote,
353 // mSerializationBuffer, mSerializationLength, mVisibleChannels)
354
355 return TrackList::Temporary(nullptr, newTrack, nullptr);
356}
357
358TrackListHolder NoteTrack::Copy(double t0, double t1, bool) const
359{
360 if (t1 < t0)
362
363 double len = t1-t0;
364
365 auto newTrack = std::make_shared<NoteTrack>();
366
367 newTrack->Init(*this);
368
369 auto &seq = GetSeq();
370 seq.convert_to_seconds();
371 newTrack->mSeq.reset(seq.copy(t0 - mOrigin, len, false));
372 newTrack->MoveTo(0);
373
374 // What should be done with the rest of newTrack's members?
375 // (mBottomNote, mSerializationBuffer,
376 // mSerializationLength, mVisibleChannels)
377
378 return TrackList::Temporary(nullptr, newTrack, nullptr);
379}
380
381bool NoteTrack::Trim(double t0, double t1)
382{
383 if (t1 < t0)
384 return false;
385 auto &seq = GetSeq();
386 //auto delta = -(
387 //( GetEndTime() - std::min( GetEndTime(), t1 ) ) +
388 //( std::max(t0, GetStartTime()) - GetStartTime() )
389 //);
390 seq.convert_to_seconds();
391 // DELETE way beyond duration just in case something is out there:
392 seq.clear(t1 - mOrigin, seq.get_dur() + 10000.0, false);
393 // Now that stuff beyond selection is cleared, clear before selection:
394 seq.clear(0.0, t0 - mOrigin, false);
395 // want starting time to be t0
396 MoveTo(t0);
397
398 // Not needed
399 // Alg_seq::clear seems to handle this
400 //AddToDuration( delta );
401
402 return true;
403}
404
405void NoteTrack::Clear(double t0, double t1)
406{
407 assert(IsLeader());
408 if (t1 < t0)
410
411 double len = t1-t0;
412
413 auto &seq = GetSeq();
414
415 auto offset = mOrigin;
416 auto start = t0 - offset;
417 if (start < 0.0) {
418 // AlgSeq::clear will shift the cleared interval, not changing len, if
419 // start is negative. That's not what we want to happen.
420 if (len > -start) {
421 seq.clear(0, len + start, false);
422 MoveTo(t0);
423 }
424 else
425 MoveTo(offset - len);
426 }
427 else {
428 //auto delta = -(
429 //( std::min( t1, GetEndTime() ) ) - ( std::max( t0, GetStartTime() ) )
430 //);
431 seq.clear(start, len, false);
432
433 // Not needed
434 // Alg_seq::clear seems to handle this
435 // AddToDuration( delta );
436 }
437}
438
439void NoteTrack::Paste(double t, const Track &src)
440{
441 // Paste inserts src at time t. If src has a positive offset,
442 // the offset is treated as silence which is also inserted. If
443 // the offset is negative, the offset is ignored and the ENTIRE
444 // src is inserted (otherwise, we would either lose data from
445 // src by not inserting things at negative times, or inserting
446 // things at negative times could overlap things already in
447 // the destination track).
448
449 //Check that src is a non-NULL NoteTrack
450 bool bOk = src.TypeSwitch<bool>( [&](const NoteTrack &other) {
451
452 auto myOffset = this->mOrigin;
453 if (t < myOffset) {
454 // workaround strange behavior described at
455 // http://bugzilla.audacityteam.org/show_bug.cgi?id=1735#c3
456 MoveTo(t);
457 InsertSilence(t, myOffset - t);
458 }
459
460 double delta = 0.0;
461 auto &seq = GetSeq();
462 auto offset = other.mOrigin;
463 if (offset > 0) {
464 seq.convert_to_seconds();
465 seq.insert_silence(t - mOrigin, offset);
466 t += offset;
467 // Is this needed or does Alg_seq::insert_silence take care of it?
468 //delta += offset;
469 }
470
471 // This seems to be needed:
472 delta += std::max(0.0, t - GetEndTime());
473
474 // This, not:
475 //delta += other.GetSeq().get_real_dur();
476
477 seq.paste(t - mOrigin, &other.GetSeq());
478
479 AddToDuration(delta);
480
481 return true;
482 });
483
484 if (!bOk)
485 // THROW_INCONSISTENCY_EXCEPTION; // ?
486 (void)0;// intentionally do nothing
487}
488
489void NoteTrack::Silence(double t0, double t1, ProgressReporter)
490{
491 assert(IsLeader());
492 if (t1 < t0)
494
495 auto len = t1 - t0;
496
497 auto &seq = GetSeq();
498 seq.convert_to_seconds();
499 // XXX: do we want to set the all param?
500 // If it's set, then it seems like notes are silenced if they start or end in the range,
501 // otherwise only if they start in the range. --Poke
502 seq.silence(t0 - mOrigin, len, false);
503}
504
505void NoteTrack::InsertSilence(double t, double len)
506{
507 assert(IsLeader());
508 if (len < 0)
510
511 auto &seq = GetSeq();
512 seq.convert_to_seconds();
513 seq.insert_silence(t - mOrigin, len);
514
515 // is this needed?
516 // AddToDuration( len );
517}
518
519#ifdef EXPERIMENTAL_MIDI_OUT
520void NoteTrack::SetVelocity(float velocity)
521{
522 if (GetVelocity() != velocity) {
523 DoSetVelocity(velocity);
524 Notify(false);
525 }
526}
527
528void NoteTrack::DoSetVelocity(float velocity)
529{
530 mVelocity.store(velocity, std::memory_order_relaxed);
531}
532#endif
533
534// Call this function to manipulate the underlying sequence data. This is
535// NOT the function that handles horizontal dragging.
536bool NoteTrack::Shift(double t) // t is always seconds
537{
538 if (t > 0) {
539 auto &seq = GetSeq();
540 // insert an even number of measures
541 seq.convert_to_beats();
542 // get initial tempo
543 double tempo = seq.get_tempo(0.0);
544 double beats_per_measure = seq.get_bar_len(0.0);
545 int m = ROUND(t * tempo / beats_per_measure);
546 // need at least 1 measure, so if we rounded down to zero, fix it
547 if (m == 0) m = 1;
548 // compute NEW tempo so that m measures at NEW tempo take t seconds
549 tempo = beats_per_measure * m / t; // in beats per second
550 seq.insert_silence(0.0, beats_per_measure * m);
551 seq.set_tempo(tempo * 60.0 /* bpm */, 0.0, beats_per_measure * m);
552 seq.write("afterShift.gro");
553 } else if (t < 0) {
554 auto &seq = GetSeq();
555 seq.convert_to_seconds();
556 seq.clear(0, t, true);
557 } else { // offset is zero, no modifications
558 return false;
559 }
560 return true;
561}
562
564{
565 // Alg_seq knows nothing about offset, so remove offset time
566 double seq_time = time - mOrigin;
567 double beat;
568 auto &seq = GetSeq();
569 seq_time = seq.nearest_beat_time(seq_time, &beat);
570 // add the offset back in to get "actual" audacity track time
571 return { seq_time + mOrigin, beat };
572}
573
575{
576 static const Track::TypeInfo info{
577 { "note", "midi", XO("Note Track") }, true,
579 return info;
580}
581
582auto NoteTrack::GetTypeInfo() const -> const TypeInfo &
583{
584 return typeInfo();
585}
586
588{
589 return typeInfo();
590}
591
593{
594 assert(IsLeader());
595 auto pNewTrack = std::make_shared<NoteTrack>();
596 pNewTrack->Init(*this);
597 pNewTrack->Paste(0.0, *this);
598 list.Add(pNewTrack);
599 return pNewTrack;
600}
601
603{
604 return 1;
605}
606
607std::shared_ptr<WideChannelGroupInterval>
609{
610 if (iInterval == 0) {
611 // Just one, and no extra info in it!
612 const auto start = mOrigin;
613 const auto end = start + GetSeq().get_real_dur();
614 return std::make_shared<Interval>(*this, start, end);
615 }
616 return {};
617}
618
619void NoteTrack::AddToDuration( double delta )
620{
621 auto &seq = GetSeq();
622#if 0
623 // PRL: Would this be better ?
624 seq.set_real_dur( seq.get_real_dur() + delta );
625#else
626 seq.convert_to_seconds();
627 seq.set_dur( seq.get_dur() + delta );
628#endif
629}
630
632 ( QuantizedTimeAndBeat t0, QuantizedTimeAndBeat t1, double newDur )
633{
634 auto &seq = GetSeq();
635 bool result = seq.stretch_region( t0.second, t1.second, newDur );
636 if (result) {
637 const auto oldDur = t1.first - t0.first;
638 AddToDuration( newDur - oldDur );
639 }
640 return result;
641}
642
643namespace
644{
645 void swap(std::unique_ptr<Alg_seq> &a, std::unique_ptr<Alg_seq> &b)
646 {
647 std::unique_ptr<Alg_seq> tmp = std::move(a);
648 a = std::move(b);
649 b = std::move(tmp);
650 }
651}
652
653Alg_seq *NoteTrack::MakeExportableSeq(std::unique_ptr<Alg_seq> &cleanup) const
654{
655 cleanup.reset();
656 double offset = mOrigin;
657 if (offset == 0)
658 return &GetSeq();
659 // make a copy, deleting events that are shifted before time 0
660 double start = -offset;
661 if (start < 0) start = 0;
662 // notes that begin before "start" are not included even if they
663 // extend past "start" (because "all" parameter is set to false)
664 cleanup.reset( GetSeq().copy(start, GetSeq().get_dur() - start, false) );
665 auto seq = cleanup.get();
666 if (offset > 0) {
667 {
668 // swap cleanup and mSeq so that Shift operates on the NEW copy
669 swap( this->mSeq, cleanup );
670 auto cleanup2 = finally( [&] { swap( this->mSeq, cleanup ); } );
671
672 const_cast< NoteTrack *>( this )->Shift(offset);
673 }
674#ifdef OLD_CODE
675 // now shift events by offset. This must be done with an integer
676 // number of measures, so first, find the beats-per-measure
677 double beats_per_measure = 4.0;
678 Alg_time_sig_ptr tsp = NULL;
679 if (seq->time_sig.length() > 0 && seq->time_sig[0].beat < ALG_EPS) {
680 // there is an initial time signature
681 tsp = &(seq->time_sig[0]);
682 beats_per_measure = (tsp->num * 4) / tsp->den;
683 }
684 // also need the initial tempo
685 double bps = ALG_DEFAULT_BPM / 60;
686 Alg_time_map_ptr map = seq->get_time_map();
687 Alg_beat_ptr bp = &(map->beats[0]);
688 if (bp->time < ALG_EPS) { // tempo change at time 0
689 if (map->beats.len > 1) { // compute slope to get tempo
690 bps = (map->beats[1].beat - map->beats[0].beat) /
691 (map->beats[1].time - map->beats[0].time);
692 } else if (seq->get_time_map()->last_tempo_flag) {
693 bps = seq->get_time_map()->last_tempo;
694 }
695 }
696 // find closest number of measures to fit in the gap
697 // number of measures is offset / measure_time
698 double measure_time = beats_per_measure / bps; // seconds per measure
699 int n = ROUND(offset / measure_time);
700 if (n == 0) n = 1;
701 // we will insert n measures. Compute the desired duration of each.
702 measure_time = offset / n;
703 bps = beats_per_measure / measure_time;
704 // insert integer multiple of measures at beginning
705 seq->convert_to_beats();
706 seq->insert_silence(0, beats_per_measure * n);
707 // make sure time signature at 0 is correct
708 if (tsp) {
709 seq->set_time_sig(0, tsp->num, tsp->den);
710 }
711 // adjust tempo to match offset
712 seq->set_tempo(bps * 60.0, 0, beats_per_measure * n);
713#endif
714 } else {
715 auto &mySeq = GetSeq();
716 // if offset is negative, it might not be a multiple of beats, but
717 // we want to preserve the relative positions of measures. I.e. we
718 // should shift barlines and time signatures as well as notes.
719 // Insert a time signature at the first bar-line if necessary.
720
721 // Translate start from seconds to beats and call it beat:
722 double beat = mySeq.get_time_map()->time_to_beat(start);
723 // Find the time signature in mySeq in effect at start (beat):
724 int i = mySeq.time_sig.find_beat(beat);
725 // i is where you would insert a NEW time sig at beat,
726 // Case 1: beat coincides with a time sig at i. Time signature
727 // at beat means that there is a barline at beat, so when beat
728 // is shifted to 0, the relative barline positions are preserved
729 if (mySeq.time_sig.length() > 0 &&
730 within(beat, mySeq.time_sig[i].beat, ALG_EPS)) {
731 // beat coincides with time signature change, so offset must
732 // be a multiple of beats
733 /* do nothing */ ;
734 // Case 2: there is no time signature before beat.
735 } else if (i == 0 && (mySeq.time_sig.length() == 0 ||
736 mySeq.time_sig[i].beat > beat)) {
737 // If beat does not fall on an implied barline, we need to
738 // insert a time signature.
739 double measures = beat / 4.0;
740 double imeasures = ROUND(measures);
741 if (!within(measures, imeasures, ALG_EPS)) {
742 double bar_offset = ((int)(measures) + 1) * 4.0 - beat;
743 seq->set_time_sig(bar_offset, 4, 4);
744 }
745 // This case should never be true because if i == 0, either there
746 // are no time signatures before beat (Case 2),
747 // or there is one time signature at beat (Case 1)
748 } else if (i == 0) {
749 /* do nothing (might be good to assert(false)) */ ;
750 // Case 3: i-1 must be the effective time sig position
751 } else {
752 i -= 1; // index the time signature in effect at beat
753 Alg_time_sig_ptr tsp = &(mySeq.time_sig[i]);
754 double beats_per_measure = (tsp->num * 4) / tsp->den;
755 double measures = (beat - tsp->beat) / beats_per_measure;
756 int imeasures = ROUND(measures);
757 if (!within(measures, imeasures, ALG_EPS)) {
758 // beat is not on a measure, so we need to insert a time sig
759 // to force a bar line at the first measure location after
760 // beat
761 double bar = tsp->beat + beats_per_measure * ((int)(measures) + 1);
762 double bar_offset = bar - beat;
763 // insert NEW time signature at bar_offset in NEW sequence
764 // It will have the same time signature, but the position will
765 // force a barline to match the barlines in mSeq
766 seq->set_time_sig(bar_offset, tsp->num, tsp->den);
767 }
768 // else beat coincides with a barline, so no need for an extra
769 // time signature to force barline alignment
770 }
771 }
772 return seq;
773}
774
775
776bool NoteTrack::ExportMIDI(const wxString &f) const
777{
778 std::unique_ptr<Alg_seq> cleanup;
779 auto seq = MakeExportableSeq(cleanup);
780 bool rslt = seq->smf_write(f.mb_str());
781 return rslt;
782}
783
785 wxT("/FileFormats/AllegroStyleChoice"),
786 {
787 EnumValueSymbol{ wxT("Seconds"), XXO("&Seconds") },
788 /* i18n-hint: The music theory "beat" */
789 EnumValueSymbol{ wxT("Beats"), XXO("&Beats") },
790 },
791 0, // true
792
793 // for migrating old preferences:
794 {
795 true, false,
796 },
797 wxT("/FileFormats/AllegroStyle"),
798};
799
800bool NoteTrack::ExportAllegro(const wxString &f) const
801{
802 double offset = mOrigin;
803 auto in_seconds = AllegroStyleSetting.ReadEnum();
804 auto &seq = GetSeq();
805 if (in_seconds) {
806 seq.convert_to_seconds();
807 } else {
808 seq.convert_to_beats();
809 }
810 return seq.write(f.mb_str(), offset);
811}
812
813
814namespace {
815bool IsValidVisibleChannels(const int nValue)
816{
817 return (nValue >= 0 && nValue < (1 << 16));
818}
819}
820
821bool NoteTrack::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
822{
823 if (tag == "notetrack") {
824 for (auto pair : attrs)
825 {
826 auto attr = pair.first;
827 auto value = pair.second;
828
829 long nValue;
830 double dblValue;
831 if (this->Track::HandleCommonXMLAttribute(attr, value))
832 ;
833 else if (this->Attachments::FindIf([&](auto &attachment){
834 return attachment.HandleAttribute(pair);
835 }))
836 ;
837 else if (this->NoteTrackBase::HandleXMLAttribute(attr, value))
838 {}
839 else if (attr == "offset" && value.TryGet(dblValue))
840 MoveTo(dblValue);
841 else if (attr == "visiblechannels") {
842 if (!value.TryGet(nValue) ||
843 !IsValidVisibleChannels(nValue))
844 return false;
845 SetVisibleChannels(nValue);
846 }
847#ifdef EXPERIMENTAL_MIDI_OUT
848 else if (attr == "velocity" && value.TryGet(dblValue))
849 DoSetVelocity(static_cast<float>(dblValue));
850#endif
851 else if (attr == "data") {
852 std::string s(value.ToWString());
853 std::istringstream data(s);
854 mSeq = std::make_unique<Alg_seq>(data, false);
855 }
856 } // while
857 return true;
858 }
859 return false;
860}
861
862XMLTagHandler *NoteTrack::HandleXMLChild(const std::string_view& WXUNUSED(tag))
863{
864 return NULL;
865}
866
867void NoteTrack::WriteXML(XMLWriter &xmlFile) const
868// may throw
869{
870 assert(IsLeader());
871 std::ostringstream data;
872 Track::Holder holder;
873 const NoteTrack *saveme = this;
874 if (!mSeq) {
875 // replace saveme with an (unserialized) duplicate, which is
876 // destroyed at end of function.
877 holder = (*Clone(false)->begin())->SharedPointer();
878 saveme = static_cast<NoteTrack*>(holder.get());
879 }
880 saveme->GetSeq().write(data, true);
881 xmlFile.StartTag(wxT("notetrack"));
882 saveme->Track::WriteCommonXMLAttributes( xmlFile );
884 xmlFile.WriteAttr(wxT("offset"), saveme->mOrigin);
885 xmlFile.WriteAttr(wxT("visiblechannels"),
886 static_cast<int>(saveme->GetVisibleChannels()));
887
888#ifdef EXPERIMENTAL_MIDI_OUT
889 xmlFile.WriteAttr(wxT("velocity"),
890 static_cast<double>(saveme->GetVelocity()));
891#endif
892 saveme->Attachments::ForEach([&](auto &attachment){
893 attachment.WriteXML(xmlFile);
894 });
895 xmlFile.WriteAttr(wxT("data"), wxString(data.str().c_str(), wxConvUTF8));
896 xmlFile.EndTag(wxT("notetrack"));
897}
898
899#include <wx/log.h>
900#include <wx/sstream.h>
901#include <wx/txtstrm.h>
902#include "AudioIOBase.h"
903#include "portmidi.h"
904
905// FIXME: When EXPERIMENTAL_MIDI_IN is added (eventually) this should also be enabled -- Poke
907{
908 wxStringOutputStream o;
909 wxTextOutputStream s(o, wxEOL_UNIX);
910
911 if (AudioIOBase::Get()->IsStreamActive()) {
912 return XO("Stream is active ... unable to gather information.\n")
913 .Translation();
914 }
915
916
917 // XXX: May need to trap errors as with the normal device info
918 int recDeviceNum = Pm_GetDefaultInputDeviceID();
919 int playDeviceNum = Pm_GetDefaultOutputDeviceID();
920 int cnt = Pm_CountDevices();
921
922 // PRL: why only into the log?
923 wxLogDebug(wxT("PortMidi reports %d MIDI devices"), cnt);
924
925 s << wxT("==============================\n");
926 s << XO("Default recording device number: %d\n").Format( recDeviceNum );
927 s << XO("Default playback device number: %d\n").Format( playDeviceNum );
928
929 auto recDevice = MIDIRecordingDevice.Read();
930 auto playDevice = MIDIPlaybackDevice.Read();
931
932 // This gets info on all available audio devices (input and output)
933 if (cnt <= 0) {
934 s << XO("No devices found\n");
935 return o.GetString();
936 }
937
938 for (int i = 0; i < cnt; i++) {
939 s << wxT("==============================\n");
940
941 const PmDeviceInfo* info = Pm_GetDeviceInfo(i);
942 if (!info) {
943 s << XO("Device info unavailable for: %d\n").Format( i );
944 continue;
945 }
946
947 wxString name = wxSafeConvertMB2WX(info->name);
948 wxString hostName = wxSafeConvertMB2WX(info->interf);
949
950 s << XO("Device ID: %d\n").Format( i );
951 s << XO("Device name: %s\n").Format( name );
952 s << XO("Host name: %s\n").Format( hostName );
953 /* i18n-hint: Supported, meaning made available by the system */
954 s << XO("Supports output: %d\n").Format( info->output );
955 /* i18n-hint: Supported, meaning made available by the system */
956 s << XO("Supports input: %d\n").Format( info->input );
957 s << XO("Opened: %d\n").Format( info->opened );
958
959 if (name == playDevice && info->output)
960 playDeviceNum = i;
961
962 if (name == recDevice && info->input)
963 recDeviceNum = i;
964
965 // XXX: This is only done because the same was applied with PortAudio
966 // If PortMidi returns -1 for the default device, use the first one
967 if (recDeviceNum < 0 && info->input){
968 recDeviceNum = i;
969 }
970 if (playDeviceNum < 0 && info->output){
971 playDeviceNum = i;
972 }
973 }
974
975 bool haveRecDevice = (recDeviceNum >= 0);
976 bool havePlayDevice = (playDeviceNum >= 0);
977
978 s << wxT("==============================\n");
979 if (haveRecDevice)
980 s << XO("Selected MIDI recording device: %d - %s\n").Format( recDeviceNum, recDevice );
981 else
982 s << XO("No MIDI recording device found for '%s'.\n").Format( recDevice );
983
984 if (havePlayDevice)
985 s << XO("Selected MIDI playback device: %d - %s\n").Format( playDeviceNum, playDevice );
986 else
987 s << XO("No MIDI playback device found for '%s'.\n").Format( playDevice );
988
989 // Mention our conditional compilation flags for Alpha only
990#ifdef IS_ALPHA
991
992 // Not internationalizing these alpha-only messages
993 s << wxT("==============================\n");
994#ifdef EXPERIMENTAL_MIDI_OUT
995 s << wxT("EXPERIMENTAL_MIDI_OUT is enabled\n");
996#else
997 s << wxT("EXPERIMENTAL_MIDI_OUT is NOT enabled\n");
998#endif
999#ifdef EXPERIMENTAL_MIDI_IN
1000 s << wxT("EXPERIMENTAL_MIDI_IN is enabled\n");
1001#else
1002 s << wxT("EXPERIMENTAL_MIDI_IN is NOT enabled\n");
1003#endif
1004
1005#endif
1006
1007 return o.GetString();
1008}
1009
1010StringSetting MIDIPlaybackDevice{ L"/MidiIO/PlaybackDevice", L"" };
1011StringSetting MIDIRecordingDevice{ L"/MidiIO/RecordingDevice", L"" };
1012IntSetting MIDISynthLatency_ms{ L"/MidiIO/SynthLatency", 5 };
1013
1014#endif // USE_MIDI
wxT("CloseDown"))
const TranslatableString name
Definition: Distortion.cpp:76
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
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:9
IntSetting MIDISynthLatency_ms
Definition: NoteTrack.cpp:1012
StringSetting MIDIRecordingDevice
Definition: NoteTrack.cpp:1011
static ProjectFileIORegistry::ObjectReaderEntry readerEntry
Definition: NoteTrack.cpp:120
wxString GetMIDIDeviceInfo()
Definition: NoteTrack.cpp:906
StringSetting MIDIPlaybackDevice
Definition: NoteTrack.cpp:1010
#define ROUND(x)
Definition: NoteTrack.cpp:29
static const Track::TypeInfo & typeInfo()
Definition: NoteTrack.cpp:574
#define SonifyEndSerialize()
Definition: NoteTrack.h:248
#define SonifyBeginSerialize()
Definition: NoteTrack.h:247
#define SonifyEndSonification()
Definition: NoteTrack.h:240
#define SonifyBeginSonification()
Definition: NoteTrack.h:239
std::pair< double, double > QuantizedTimeAndBeat
Definition: NoteTrack.h:58
const auto tracks
const auto project
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
std::function< void(double)> ProgressReporter
Definition: Track.h:53
std::shared_ptr< TrackList > TrackListHolder
Definition: Track.h:42
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:169
std::pair< std::string_view, XMLAttributeValueView > Attribute
Definition: XMLTagHandler.h:39
std::vector< Attribute > AttributesList
Definition: XMLTagHandler.h:40
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:94
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
Utility to register hooks into a host class that attach client data.
Definition: ClientData.h:228
ClientData * FindIf(const Function &function)
Return pointer to first attachment in this that is not null and satisfies a predicate,...
Definition: ClientData.h:420
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
Enum ReadEnum() const
Definition: Prefs.h:534
Specialization of Setting for int.
Definition: Prefs.h:356
A Track that is used for Midi notes. (Somewhat old code).
Definition: NoteTrack.h:86
void SetSequence(std::unique_ptr< Alg_seq > &&seq)
Definition: NoteTrack.cpp:263
const TypeInfo & GetTypeInfo() const override
Definition: NoteTrack.cpp:582
static const TypeInfo & ClassTypeInfo()
Definition: NoteTrack.cpp:587
bool ExportAllegro(const wxString &f) const
Definition: NoteTrack.cpp:800
virtual ~NoteTrack()
Definition: NoteTrack.cpp:142
void AddToDuration(double delta)
Definition: NoteTrack.cpp:619
TrackListHolder Cut(double t0, double t1) override
Create tracks and modify this track.
Definition: NoteTrack.cpp:327
size_t NIntervals() const override
Report the number of intervals.
Definition: NoteTrack.cpp:602
TrackListHolder Clone(bool backup) const override
Definition: NoteTrack.cpp:168
std::shared_ptr< WideChannelGroupInterval > DoGetInterval(size_t iInterval) override
Retrieve an interval.
Definition: NoteTrack.cpp:608
std::unique_ptr< char[]> mSerializationBuffer
Definition: NoteTrack.h:216
Track::Holder PasteInto(AudacityProject &project, TrackList &list) const override
Definition: NoteTrack.cpp:592
bool ExportMIDI(const wxString &f) const
Definition: NoteTrack.cpp:776
void MoveTo(double origin) override
Change start time to given time point.
Definition: NoteTrack.h:106
void SetVisibleChannels(unsigned value)
Definition: NoteTrack.h:162
void WriteXML(XMLWriter &xmlFile) const override
Definition: NoteTrack.cpp:867
void Paste(double t, const Track &src) override
Weak precondition allows overrides to replicate one channel into many.
Definition: NoteTrack.cpp:439
void PrintSequence()
Definition: NoteTrack.cpp:268
TrackListHolder Copy(double t0, double t1, bool forClipboard=true) const override
Create new tracks and don't modify this track.
Definition: NoteTrack.cpp:358
void Silence(double t0, double t1, ProgressReporter reportProgress={}) override
Definition: NoteTrack.cpp:489
double mOrigin
Definition: NoteTrack.h:226
std::unique_ptr< Alg_seq > mSeq
Definition: NoteTrack.h:215
void InsertSilence(double t, double len) override
Definition: NoteTrack.cpp:505
XMLTagHandler * HandleXMLChild(const std::string_view &tag) override
Definition: NoteTrack.cpp:862
bool StretchRegion(QuantizedTimeAndBeat t0, QuantizedTimeAndBeat t1, double newDur)
Definition: NoteTrack.cpp:632
Alg_seq * MakeExportableSeq(std::unique_ptr< Alg_seq > &cleanup) const
Definition: NoteTrack.cpp:653
static EnumSetting< bool > AllegroStyleSetting
Definition: NoteTrack.h:89
static NoteTrack * New(AudacityProject &project)
Definition: NoteTrack.cpp:125
bool Shift(double t)
Definition: NoteTrack.cpp:536
void DoOnProjectTempoChange(const std::optional< double > &oldTempo, double newTempo) override
Definition: NoteTrack.cpp:211
Alg_seq & GetSeq() const
Definition: NoteTrack.cpp:146
unsigned GetVisibleChannels() const
Definition: NoteTrack.h:159
QuantizedTimeAndBeat NearestBeatTime(double time) const
Definition: NoteTrack.cpp:563
void WarpAndTransposeNotes(double t0, double t1, const TimeWarper &warper, double semitones)
Definition: NoteTrack.cpp:227
bool Trim(double t0, double t1)
Definition: NoteTrack.cpp:381
bool HandleXMLTag(const std::string_view &tag, const AttributesList &attrs) override
Definition: NoteTrack.cpp:821
void Clear(double t0, double t1) override
Definition: NoteTrack.cpp:405
long mSerializationLength
Definition: NoteTrack.h:217
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:207
Specialization of Setting for strings.
Definition: Prefs.h:370
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:122
void Notify(bool allChannels, int code=-1)
Definition: Track.cpp:257
R TypeSwitch(const Functions &...functions)
Definition: Track.h:436
bool IsLeader() const override
Definition: Track.cpp:291
std::shared_ptr< Track > Holder
Definition: Track.h:225
bool HandleCommonXMLAttribute(const std::string_view &attr, const XMLAttributeValueView &valueView)
Definition: Track.cpp:1235
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:993
TrackKind * Add(const std::shared_ptr< TrackKind > &t)
Definition: Track.h:1201
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:347
static TrackListHolder Temporary(AudacityProject *pProject, const Track::Holder &left={}, const Track::Holder &right={})
Definition: Track.cpp:1418
Generates overrides of channel-related functions.
Definition: Track.h:508
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:815
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:645
void copy(const T *src, T *dst, int32_t n)
Definition: VectorOps.h:40
std::shared_ptr< ChannelInterval > DoGetChannel(size_t iChannel) override
Retrieve a channel.
Definition: NoteTrack.cpp:103
~Interval() override
virtual void WriteXML(XMLWriter &xmlFile) const
Default implementation does nothing.
Definition: NoteTrack.cpp:112
virtual bool HandleAttribute(const Attribute &attribute)
Return whether the attribute was used; default returns false.
Definition: NoteTrack.cpp:115
~NoteTrackAttachment() override