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