Audacity  2.2.2
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 #include "Audacity.h"
18 #include "NoteTrack.h"
19 #include "Experimental.h"
20 
21 #include <wx/dc.h>
22 #include <wx/brush.h>
23 #include <wx/pen.h>
24 #include <wx/intl.h>
25 
26 #if defined(USE_MIDI)
27 #include <sstream>
28 
29 #define ROUND(x) ((int) ((x) + 0.5))
30 
31 #include "AColor.h"
32 #include "DirManager.h"
33 #include "Internat.h"
34 #include "Prefs.h"
35 #include "effects/TimeWarper.h"
36 
37 #include "InconsistencyException.h"
38 
39 #include "TrackPanel.h" // For TrackInfo
40 #include "AllThemeResources.h"
41 
42 #ifdef SONIFY
43 #include "../lib-src/portmidi/pm_common/portmidi.h"
44 
45 #define SON_PROGRAM 0
46 #define SON_AutoSave 67
47 #define SON_ModifyState 60
48 #define SON_NoteBackground 72
49 #define SON_NoteForeground 74
50 #define SON_Measures 76 /* "bar line" */
51 #define SON_Serialize 77
52 #define SON_Unserialize 79
53 #define SON_VEL 100
54 
55 
56 PmStream *sonMidiStream;
57 bool sonificationStarted = false;
58 
60 {
61  PmError err = Pm_OpenOutput(&sonMidiStream, Pm_GetDefaultOutputDeviceID(),
62  NULL, 0, NULL, NULL, 0);
63  if (err) sonMidiStream = NULL;
64  if (sonMidiStream)
65  Pm_WriteShort(sonMidiStream, 0, Pm_Message(0xC0, SON_PROGRAM, 0));
66  sonificationStarted = true;
67 }
68 
69 
71 {
72  if (sonMidiStream) Pm_Close(sonMidiStream);
73  sonificationStarted = false;
74 }
75 
76 
77 
78 
79 void SonifyNoteOnOff(int p, int v)
80 {
81  if (!sonificationStarted)
83  if (sonMidiStream)
84  Pm_WriteShort(sonMidiStream, 0, Pm_Message(0x90, p, v));
85 }
86 
87 #define SONFNS(name) \
88  void SonifyBegin ## name() { SonifyNoteOnOff(SON_ ## name, SON_VEL); } \
89  void SonifyEnd ## name() { SonifyNoteOnOff(SON_ ## name, 0); }
90 
91 SONFNS(NoteBackground)
92 SONFNS(NoteForeground)
93 SONFNS(Measures)
94 SONFNS(Serialize)
95 SONFNS(Unserialize)
96 SONFNS(ModifyState)
97 SONFNS(AutoSave)
98 
99 #undef SONFNS
100 
101 #endif
102 
103 
104 
105 NoteTrack::Holder TrackFactory::NewNoteTrack()
106 {
107  return std::make_unique<NoteTrack>(mDirManager);
108 }
109 
110 NoteTrack::NoteTrack(const std::shared_ptr<DirManager> &projDirManager)
111  : NoteTrackBase(projDirManager)
112 {
113  SetDefaultName(_("Note Track"));
114  SetName(GetDefaultName());
115 
116  SetHeight( TrackInfo::DefaultNoteTrackHeight() );
117 
118  mSeq = NULL;
119  mSerializationLength = 0;
120 
121 #ifdef EXPERIMENTAL_MIDI_OUT
122  mVelocity = 0;
123 #endif
124  mBottomNote = 24;
125  mPitchHeight = 5.0f;
126 
127  mVisibleChannels = ALL_CHANNELS;
128 }
129 
130 NoteTrack::~NoteTrack()
131 {
132 }
133 
134 Alg_seq &NoteTrack::GetSeq() const
135 {
136  if (!mSeq) {
137  if (!mSerializationBuffer)
138  mSeq = std::make_unique<Alg_seq>();
139  else {
140  std::unique_ptr<Alg_track> alg_track
141  { Alg_seq::unserialize
142  ( mSerializationBuffer.get(), mSerializationLength ) };
143  wxASSERT(alg_track->get_type() == 's');
144  mSeq.reset( static_cast<Alg_seq*>(alg_track.release()) );
145 
146  // Preserve the invariant that at most one of the representations is
147  // valid
148  mSerializationBuffer.reset();
149  mSerializationLength = 0;
150  }
151  }
152  wxASSERT(mSeq);
153  return *mSeq;
154 }
155 
156 Track::Holder NoteTrack::Duplicate() const
157 {
158  auto duplicate = std::make_unique<NoteTrack>(mDirManager);
159  duplicate->Init(*this);
160  // The duplicate begins life in serialized state. Often the duplicate is
161  // pushed on the Undo stack. Then we want to un-serialize it (or a further
162  // copy) only on demand after an Undo.
163  if (mSeq) {
165  wxASSERT(!mSerializationBuffer);
166  // serialize from this to duplicate's mSerializationBuffer
167  void *buffer;
168  mSeq->serialize(&buffer,
169  &duplicate->mSerializationLength);
170  duplicate->mSerializationBuffer.reset( (char*)buffer );
172  }
173  else if (mSerializationBuffer) {
174  // Copy already serialized data.
175  wxASSERT(!mSeq);
176  duplicate->mSerializationLength = this->mSerializationLength;
177  duplicate->mSerializationBuffer.reset
178  ( safenew char[ this->mSerializationLength ] );
179  memcpy( duplicate->mSerializationBuffer.get(),
180  this->mSerializationBuffer.get(), this->mSerializationLength );
181  }
182  else {
183  // We are duplicating a default-constructed NoteTrack, and that's okay
184  }
185  // copy some other fields here
186  duplicate->SetBottomNote(mBottomNote);
187  duplicate->mPitchHeight = mPitchHeight;
188  duplicate->mVisibleChannels = mVisibleChannels;
189  duplicate->SetOffset(GetOffset());
190 #ifdef EXPERIMENTAL_MIDI_OUT
191  duplicate->SetVelocity(GetVelocity());
192 #endif
193  // This std::move is needed to "upcast" the pointer type
194  return std::move(duplicate);
195 }
196 
197 
198 double NoteTrack::GetOffset() const
199 {
200  return mOffset;
201 }
202 
203 double NoteTrack::GetStartTime() const
204 {
205  return GetOffset();
206 }
207 
208 double NoteTrack::GetEndTime() const
209 {
210  return GetStartTime() + GetSeq().get_real_dur();
211 }
212 
213 void NoteTrack::DoSetHeight(int h)
214 {
215  auto oldHeight = GetHeight();
216  auto oldMargin = GetNoteMargin(oldHeight);
218  auto margin = GetNoteMargin(h);
219  Zoom(
220  wxRect{ 0, 0, 1, h }, // only height matters
221  h - margin - 1, // preserve bottom note
222  (float)(h - 2 * margin) /
223  std::max(1, oldHeight - 2 * oldMargin),
224  false
225  );
226 }
227 
228 
229 void NoteTrack::WarpAndTransposeNotes(double t0, double t1,
230  const TimeWarper &warper,
231  double semitones)
232 {
233  double offset = this->GetOffset(); // track is shifted this amount
234  auto &seq = GetSeq();
235  seq.convert_to_seconds(); // make sure time units are right
236  t1 -= offset; // adjust time range to compensate for track offset
237  t0 -= offset;
238  if (t1 > seq.get_dur()) { // make sure t0, t1 are within sequence
239  t1 = seq.get_dur();
240  if (t0 >= t1) return;
241  }
242  Alg_iterator iter(mSeq.get(), false);
243  iter.begin();
244  Alg_event_ptr event;
245  while (0 != (event = iter.next()) && event->time < t1) {
246  if (event->is_note() && event->time >= t0) {
247  event->set_pitch(event->get_pitch() + semitones);
248  }
249  }
250  iter.end();
251  // now, use warper to warp the tempo map
252  seq.convert_to_beats(); // beats remain the same
253  Alg_time_map_ptr map = seq.get_time_map();
254  map->insert_beat(t0, map->time_to_beat(t0));
255  map->insert_beat(t1, map->time_to_beat(t1));
256  int i, len = map->length();
257  for (i = 0; i < len; i++) {
258  Alg_beat &beat = map->beats[i];
259  beat.time = warper.Warp(beat.time + offset) - offset;
260  }
261  // about to redisplay, so might as well convert back to time now
262  seq.convert_to_seconds();
263 }
264 
265 // Draws the midi channel toggle buttons within the given rect.
266 // The rect should be evenly divisible by 4 on both axis.
267 void NoteTrack::DrawLabelControls
268 ( const NoteTrack *pTrack, wxDC & dc, const wxRect &rect, int highlightedChannel )
269 {
270  dc.SetTextForeground(theTheme.Colour(clrLabelTrackText));
271  wxASSERT_MSG(rect.width % 4 == 0, "Midi channel control rect width must be divisible by 4");
272  wxASSERT_MSG(rect.height % 4 == 0, "Midi channel control rect height must be divisible by 4");
273 
274  auto cellWidth = rect.width / 4;
275  auto cellHeight = rect.height / 4;
276 
277  wxRect box;
278  for (int row = 0; row < 4; row++) {
279  for (int col = 0; col < 4; col++) {
280  // chanName is the "external" channel number (1-16)
281  // used by AColor and button labels
282  int chanName = row * 4 + col + 1;
283 
284  box.x = rect.x + col * cellWidth;
285  box.y = rect.y + row * cellHeight;
286  box.width = cellWidth;
287  box.height = cellHeight;
288 
289  bool visible = pTrack ? pTrack->IsVisibleChan(chanName - 1) : true;
290  if (visible) {
291  // highlightedChannel counts 0 based
292  if ( chanName == highlightedChannel + 1 )
293  AColor::LightMIDIChannel(&dc, chanName);
294  else
295  AColor::MIDIChannel(&dc, chanName);
296  dc.DrawRectangle(box);
297 // two choices: channel is enabled (to see and play) when button is in
298 // "up" position (original Audacity style) or in "down" position
299 //
300 #define CHANNEL_ON_IS_DOWN 1
301 #if CHANNEL_ON_IS_DOWN
302  AColor::DarkMIDIChannel(&dc, chanName);
303 #else
304  AColor::LightMIDIChannel(&dc, chanName);
305 #endif
306  AColor::Line(dc, box.x, box.y, box.x + box.width - 1, box.y);
307  AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1);
308 
309 #if CHANNEL_ON_IS_DOWN
310  AColor::LightMIDIChannel(&dc, chanName);
311 #else
312  AColor::DarkMIDIChannel(&dc, chanName);
313 #endif
314  AColor::Line(dc,
315  box.x + box.width - 1, box.y,
316  box.x + box.width - 1, box.y + box.height - 1);
317  AColor::Line(dc,
318  box.x, box.y + box.height - 1,
319  box.x + box.width - 1, box.y + box.height - 1);
320  } else {
321  if ( chanName == highlightedChannel + 1 )
322  AColor::LightMIDIChannel(&dc, chanName);
323  else
324  AColor::MIDIChannel(&dc, 0);
325  dc.DrawRectangle(box);
326 #if CHANNEL_ON_IS_DOWN
327  AColor::LightMIDIChannel(&dc, 0);
328 #else
329  AColor::DarkMIDIChannel(&dc, 0);
330 #endif
331  AColor::Line(dc, box.x, box.y, box.x + box.width - 1, box.y);
332  AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1);
333 
334 #if CHANNEL_ON_IS_DOWN
335  AColor::DarkMIDIChannel(&dc, 0);
336 #else
337  AColor::LightMIDIChannel(&dc, 0);
338 #endif
339  AColor::Line(dc,
340  box.x + box.width - 1, box.y,
341  box.x + box.width - 1, box.y + box.height - 1);
342  AColor::Line(dc,
343  box.x, box.y + box.height - 1,
344  box.x + box.width - 1, box.y + box.height - 1);
345 
346  }
347 
348  wxString text;
349  wxCoord w;
350  wxCoord h;
351 
352  text.Printf(wxT("%d"), chanName);
353  dc.GetTextExtent(text, &w, &h);
354 
355  dc.DrawText(text, box.x + (box.width - w) / 2, box.y + (box.height - h) / 2);
356  }
357  }
358  dc.SetTextForeground(theTheme.Colour(clrTrackPanelText));
359  AColor::MIDIChannel(&dc, 0); // always return with gray color selected
360 }
361 
362 int NoteTrack::FindChannel(const wxRect &rect, int mx, int my)
363 {
364  wxASSERT_MSG(rect.width % 4 == 0, "Midi channel control rect width must be divisible by 4");
365  wxASSERT_MSG(rect.height % 4 == 0, "Midi channel control rect height must be divisible by 4");
366 
367  auto cellWidth = rect.width / 4;
368  auto cellHeight = rect.height / 4;
369 
370  int col = (mx - rect.x) / cellWidth;
371  int row = (my - rect.y) / cellHeight;
372 
373  return row * 4 + col;
374 }
375 
376 
377 // Handles clicking within the midi controls rect (same as DrawLabelControls).
378 // This is somewhat oddly written, as these aren't real buttons - they act
379 // when the mouse goes down; you can't hold it pressed and move off of it.
380 // Left-clicking toggles a single channel; right-clicking turns off all other channels.
381 bool NoteTrack::LabelClick(const wxRect &rect, int mx, int my, bool right)
382 {
383  auto channel = FindChannel(rect, mx, my);
384  if (right)
385  SoloVisibleChan(channel);
386  else
387  ToggleVisibleChan(channel);
388 
389  return true;
390 }
391 
392 void NoteTrack::SetSequence(std::unique_ptr<Alg_seq> &&seq)
393 {
394  mSeq = std::move(seq);
395 }
396 
397 void NoteTrack::PrintSequence()
398 {
399  FILE *debugOutput;
400 
401  debugOutput = fopen("debugOutput.txt", "wt");
402  wxFprintf(debugOutput, "Importing MIDI...\n");
403 
404  // This is called for debugging purposes. Do not compute mSeq on demand
405  // with GetSeq()
406  if (mSeq) {
407  int i = 0;
408 
409  while(i < mSeq->length()) {
410  wxFprintf(debugOutput, "--\n");
411  wxFprintf(debugOutput, "type: %c\n",
412  ((Alg_event_ptr)mSeq->track_list.tracks[i])->get_type());
413  wxFprintf(debugOutput, "time: %f\n",
414  ((Alg_event_ptr)mSeq->track_list.tracks[i])->time);
415  wxFprintf(debugOutput, "channel: %li\n",
416  ((Alg_event_ptr)mSeq->track_list.tracks[i])->chan);
417 
418  if(((Alg_event_ptr)mSeq->track_list.tracks[i])->get_type() == wxT('n'))
419  {
420  wxFprintf(debugOutput, "pitch: %f\n",
421  ((Alg_note_ptr)mSeq->track_list.tracks[i])->pitch);
422  wxFprintf(debugOutput, "duration: %f\n",
423  ((Alg_note_ptr)mSeq->track_list.tracks[i])->dur);
424  wxFprintf(debugOutput, "velocity: %f\n",
425  ((Alg_note_ptr)mSeq->track_list.tracks[i])->loud);
426  }
427  else if(((Alg_event_ptr)mSeq->track_list.tracks[i])->get_type() == wxT('n'))
428  {
429  wxFprintf(debugOutput, "key: %li\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->get_identifier());
430  wxFprintf(debugOutput, "attribute type: %c\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_type());
431  wxFprintf(debugOutput, "attribute: %s\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_name());
432 
433  if(((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_type() == wxT('r'))
434  {
435  wxFprintf(debugOutput, "value: %f\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.r);
436  }
437  else if(((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_type() == wxT('i')) {
438  wxFprintf(debugOutput, "value: %li\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.i);
439  }
440  else if(((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.attr_type() == wxT('s')) {
441  wxFprintf(debugOutput, "value: %s\n", ((Alg_update_ptr)mSeq->track_list.tracks[i])->parameter.s);
442  }
443  else {}
444  }
445 
446  i++;
447  }
448  }
449  else {
450  wxFprintf(debugOutput, "No sequence defined!\n");
451  }
452 
453  fclose(debugOutput);
454 }
455 
456 Track::Holder NoteTrack::Cut(double t0, double t1)
457 {
458  if (t1 < t0)
460 
461  double len = t1-t0;
462  //auto delta = -(
463  //( std::min( t1, GetEndTime() ) ) - ( std::max( t0, GetStartTime() ) )
464  //);
465 
466  auto newTrack = std::make_unique<NoteTrack>(mDirManager);
467 
468  newTrack->Init(*this);
469 
470  auto &seq = GetSeq();
471  seq.convert_to_seconds();
472  newTrack->mSeq.reset(seq.cut(t0 - GetOffset(), len, false));
473  newTrack->SetOffset(0);
474 
475  // Not needed
476  // Alg_seq::cut seems to handle this
477  //AddToDuration( delta );
478 
479  // What should be done with the rest of newTrack's members?
480  //(mBottomNote, mDirManager,
481  // mSerializationBuffer, mSerializationLength, mVisibleChannels)
482 
483  // This std::move is needed to "upcast" the pointer type
484  return std::move(newTrack);
485 }
486 
487 Track::Holder NoteTrack::Copy(double t0, double t1, bool) const
488 {
489  if (t1 < t0)
491 
492  double len = t1-t0;
493 
494  auto newTrack = std::make_unique<NoteTrack>(mDirManager);
495 
496  newTrack->Init(*this);
497 
498  auto &seq = GetSeq();
499  seq.convert_to_seconds();
500  newTrack->mSeq.reset(seq.copy(t0 - GetOffset(), len, false));
501  newTrack->SetOffset(0);
502 
503  // What should be done with the rest of newTrack's members?
504  // (mBottomNote, mDirManager, mSerializationBuffer,
505  // mSerializationLength, mVisibleChannels)
506 
507  // This std::move is needed to "upcast" the pointer type
508  return std::move(newTrack);
509 }
510 
511 bool NoteTrack::Trim(double t0, double t1)
512 {
513  if (t1 < t0)
514  return false;
515  auto &seq = GetSeq();
516  //auto delta = -(
517  //( GetEndTime() - std::min( GetEndTime(), t1 ) ) +
518  //( std::max(t0, GetStartTime()) - GetStartTime() )
519  //);
520  seq.convert_to_seconds();
521  // DELETE way beyond duration just in case something is out there:
522  seq.clear(t1 - GetOffset(), seq.get_dur() + 10000.0, false);
523  // Now that stuff beyond selection is cleared, clear before selection:
524  seq.clear(0.0, t0 - GetOffset(), false);
525  // want starting time to be t0
526  SetOffset(t0);
527 
528  // Not needed
529  // Alg_seq::clear seems to handle this
530  //AddToDuration( delta );
531 
532  return true;
533 }
534 
535 void NoteTrack::Clear(double t0, double t1)
536 {
537  if (t1 < t0)
539 
540  double len = t1-t0;
541 
542  auto &seq = GetSeq();
543 
544  auto offset = GetOffset();
545  auto start = t0 - offset;
546  if (start < 0.0) {
547  // AlgSeq::clear will shift the cleared interval, not changing len, if
548  // start is negative. That's not what we want to happen.
549  if (len > -start) {
550  seq.clear(0, len + start, false);
551  SetOffset(t0);
552  }
553  else
554  SetOffset(offset - len);
555  }
556  else {
557  //auto delta = -(
558  //( std::min( t1, GetEndTime() ) ) - ( std::max( t0, GetStartTime() ) )
559  //);
560  seq.clear(start, len, false);
561 
562  // Not needed
563  // Alg_seq::clear seems to handle this
564  // AddToDuration( delta );
565  }
566 }
567 
568 void NoteTrack::Paste(double t, const Track *src)
569 {
570  // Paste inserts src at time t. If src has a positive offset,
571  // the offset is treated as silence which is also inserted. If
572  // the offset is negative, the offset is ignored and the ENTIRE
573  // src is inserted (otherwise, we would either lose data from
574  // src by not inserting things at negative times, or inserting
575  // things at negative times could overlap things already in
576  // the destination track).
577 
578  //Check that src is a non-NULL NoteTrack
579  if (src == NULL || src->GetKind() != Track::Note)
580  // THROW_INCONSISTENCY_EXCEPTION; // ?
581  return;
582 
583  auto myOffset = this->GetOffset();
584  if (t < myOffset) {
585  // workaround strange behavior described at
586  // http://bugzilla.audacityteam.org/show_bug.cgi?id=1735#c3
587  SetOffset(t);
588  InsertSilence(t, myOffset - t);
589  }
590 
591  NoteTrack* other = (NoteTrack*)src;
592 
593  double delta = 0.0;
594  auto &seq = GetSeq();
595  auto offset = other->GetOffset();
596  if ( offset > 0 ) {
597  seq.convert_to_seconds();
598  seq.insert_silence( t - GetOffset(), offset );
599  t += offset;
600  // Is this needed or does Alg_seq::insert_silence take care of it?
601  //delta += offset;
602  }
603 
604  // This seems to be needed:
605  delta += std::max( 0.0, t - GetEndTime() );
606 
607  // This, not:
608  //delta += other->GetSeq().get_real_dur();
609 
610  seq.paste(t - GetOffset(), &other->GetSeq());
611 
612  AddToDuration( delta );
613 }
614 
615 void NoteTrack::Silence(double t0, double t1)
616 {
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 - GetOffset(), len, false);
628 }
629 
630 void NoteTrack::InsertSilence(double t, double len)
631 {
632  if (len < 0)
634 
635  auto &seq = GetSeq();
636  seq.convert_to_seconds();
637  seq.insert_silence(t - GetOffset(), len);
638 
639  // is this needed?
640  // AddToDuration( len );
641 }
642 
643 // Call this function to manipulate the underlying sequence data. This is
644 // NOT the function that handles horizontal dragging.
645 bool NoteTrack::Shift(double t) // t is always seconds
646 {
647  if (t > 0) {
648  auto &seq = GetSeq();
649  // insert an even number of measures
650  seq.convert_to_beats();
651  // get initial tempo
652  double tempo = seq.get_tempo(0.0);
653  double beats_per_measure = seq.get_bar_len(0.0);
654  int m = ROUND(t * tempo / beats_per_measure);
655  // need at least 1 measure, so if we rounded down to zero, fix it
656  if (m == 0) m = 1;
657  // compute NEW tempo so that m measures at NEW tempo take t seconds
658  tempo = beats_per_measure * m / t; // in beats per second
659  seq.insert_silence(0.0, beats_per_measure * m);
660  seq.set_tempo(tempo * 60.0 /* bpm */, 0.0, beats_per_measure * m);
661  seq.write("afterShift.gro");
662  } else if (t < 0) {
663  auto &seq = GetSeq();
664  seq.convert_to_seconds();
665  seq.clear(0, t, true);
666  } else { // offset is zero, no modifications
667  return false;
668  }
669  return true;
670 }
671 
672 QuantizedTimeAndBeat NoteTrack::NearestBeatTime( double time ) const
673 {
674  // Alg_seq knows nothing about offset, so remove offset time
675  double seq_time = time - GetOffset();
676  double beat;
677  auto &seq = GetSeq();
678  seq_time = seq.nearest_beat_time(seq_time, &beat);
679  // add the offset back in to get "actual" audacity track time
680  return { seq_time + GetOffset(), beat };
681 }
682 
683 void NoteTrack::AddToDuration( double delta )
684 {
685  auto &seq = GetSeq();
686 #if 0
687  // PRL: Would this be better ?
688  seq.set_real_dur( seq.get_real_dur() + delta );
689 #else
690  seq.convert_to_seconds();
691  seq.set_dur( seq.get_dur() + delta );
692 #endif
693 }
694 
695 bool NoteTrack::StretchRegion
696  ( QuantizedTimeAndBeat t0, QuantizedTimeAndBeat t1, double newDur )
697 {
698  auto &seq = GetSeq();
699  bool result = seq.stretch_region( t0.second, t1.second, newDur );
700  if (result) {
701  const auto oldDur = t1.first - t0.first;
702  AddToDuration( newDur - oldDur );
703  }
704  return result;
705 }
706 
707 namespace
708 {
709  void swap(std::unique_ptr<Alg_seq> &a, std::unique_ptr<Alg_seq> &b)
710  {
711  std::unique_ptr<Alg_seq> tmp = std::move(a);
712  a = std::move(b);
713  b = std::move(tmp);
714  }
715 }
716 
717 Alg_seq *NoteTrack::MakeExportableSeq(std::unique_ptr<Alg_seq> &cleanup) const
718 {
719  cleanup.reset();
720  double offset = GetOffset();
721  if (offset == 0)
722  return &GetSeq();
723  // make a copy, deleting events that are shifted before time 0
724  double start = -offset;
725  if (start < 0) start = 0;
726  // notes that begin before "start" are not included even if they
727  // extend past "start" (because "all" parameter is set to false)
728  cleanup.reset( GetSeq().copy(start, GetSeq().get_dur() - start, false) );
729  auto seq = cleanup.get();
730  if (offset > 0) {
731  {
732  // swap cleanup and mSeq so that Shift operates on the NEW copy
733  swap( this->mSeq, cleanup );
734  auto cleanup2 = finally( [&] { swap( this->mSeq, cleanup ); } );
735 
736  const_cast< NoteTrack *>( this )->Shift(offset);
737  }
738 #ifdef OLD_CODE
739  // now shift events by offset. This must be done with an integer
740  // number of measures, so first, find the beats-per-measure
741  double beats_per_measure = 4.0;
742  Alg_time_sig_ptr tsp = NULL;
743  if (seq->time_sig.length() > 0 && seq->time_sig[0].beat < ALG_EPS) {
744  // there is an initial time signature
745  tsp = &(seq->time_sig[0]);
746  beats_per_measure = (tsp->num * 4) / tsp->den;
747  }
748  // also need the initial tempo
749  double bps = ALG_DEFAULT_BPM / 60;
750  Alg_time_map_ptr map = seq->get_time_map();
751  Alg_beat_ptr bp = &(map->beats[0]);
752  if (bp->time < ALG_EPS) { // tempo change at time 0
753  if (map->beats.len > 1) { // compute slope to get tempo
754  bps = (map->beats[1].beat - map->beats[0].beat) /
755  (map->beats[1].time - map->beats[0].time);
756  } else if (seq->get_time_map()->last_tempo_flag) {
757  bps = seq->get_time_map()->last_tempo;
758  }
759  }
760  // find closest number of measures to fit in the gap
761  // number of measures is offset / measure_time
762  double measure_time = beats_per_measure / bps; // seconds per measure
763  int n = ROUND(offset / measure_time);
764  if (n == 0) n = 1;
765  // we will insert n measures. Compute the desired duration of each.
766  measure_time = offset / n;
767  bps = beats_per_measure / measure_time;
768  // insert integer multiple of measures at beginning
769  seq->convert_to_beats();
770  seq->insert_silence(0, beats_per_measure * n);
771  // make sure time signature at 0 is correct
772  if (tsp) {
773  seq->set_time_sig(0, tsp->num, tsp->den);
774  }
775  // adjust tempo to match offset
776  seq->set_tempo(bps * 60.0, 0, beats_per_measure * n);
777 #endif
778  } else {
779  auto &mySeq = GetSeq();
780  // if offset is negative, it might not be a multiple of beats, but
781  // we want to preserve the relative positions of measures. I.e. we
782  // should shift barlines and time signatures as well as notes.
783  // Insert a time signature at the first bar-line if necessary.
784 
785  // Translate start from seconds to beats and call it beat:
786  double beat = mySeq.get_time_map()->time_to_beat(start);
787  // Find the time signature in mySeq in effect at start (beat):
788  int i = mySeq.time_sig.find_beat(beat);
789  // i is where you would insert a NEW time sig at beat,
790  // Case 1: beat coincides with a time sig at i. Time signature
791  // at beat means that there is a barline at beat, so when beat
792  // is shifted to 0, the relative barline positions are preserved
793  if (mySeq.time_sig.length() > 0 &&
794  within(beat, mySeq.time_sig[i].beat, ALG_EPS)) {
795  // beat coincides with time signature change, so offset must
796  // be a multiple of beats
797  /* do nothing */ ;
798  // Case 2: there is no time signature before beat.
799  } else if (i == 0 && (mySeq.time_sig.length() == 0 ||
800  mySeq.time_sig[i].beat > beat)) {
801  // If beat does not fall on an implied barline, we need to
802  // insert a time signature.
803  double measures = beat / 4.0;
804  double imeasures = ROUND(measures);
805  if (!within(measures, imeasures, ALG_EPS)) {
806  double bar_offset = ((int)(measures) + 1) * 4.0 - beat;
807  seq->set_time_sig(bar_offset, 4, 4);
808  }
809  // This case should never be true because if i == 0, either there
810  // are no time signatures before beat (Case 2),
811  // or there is one time signature at beat (Case 1)
812  } else if (i == 0) {
813  /* do nothing (might be good to assert(false)) */ ;
814  // Case 3: i-1 must be the effective time sig position
815  } else {
816  i -= 1; // index the time signature in effect at beat
817  Alg_time_sig_ptr tsp = &(mySeq.time_sig[i]);
818  double beats_per_measure = (tsp->num * 4) / tsp->den;
819  double measures = (beat - tsp->beat) / beats_per_measure;
820  int imeasures = ROUND(measures);
821  if (!within(measures, imeasures, ALG_EPS)) {
822  // beat is not on a measure, so we need to insert a time sig
823  // to force a bar line at the first measure location after
824  // beat
825  double bar = tsp->beat + beats_per_measure * ((int)(measures) + 1);
826  double bar_offset = bar - beat;
827  // insert NEW time signature at bar_offset in NEW sequence
828  // It will have the same time signature, but the position will
829  // force a barline to match the barlines in mSeq
830  seq->set_time_sig(bar_offset, tsp->num, tsp->den);
831  }
832  // else beat coincides with a barline, so no need for an extra
833  // time signature to force barline alignment
834  }
835  }
836  return seq;
837 }
838 
839 
840 bool NoteTrack::ExportMIDI(const wxString &f) const
841 {
842  std::unique_ptr<Alg_seq> cleanup;
843  auto seq = MakeExportableSeq(cleanup);
844  bool rslt = seq->smf_write(f.mb_str());
845  return rslt;
846 }
847 
848 bool NoteTrack::ExportAllegro(const wxString &f) const
849 {
850  double offset = GetOffset();
851  bool in_seconds;
852  gPrefs->Read(wxT("/FileFormats/AllegroStyle"), &in_seconds, true);
853  auto &seq = GetSeq();
854  if (in_seconds) {
855  seq.convert_to_seconds();
856  } else {
857  seq.convert_to_beats();
858  }
859  return seq.write(f.mb_str(), offset);
860 }
861 
862 
863 bool NoteTrack::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
864 {
865  if (!wxStrcmp(tag, wxT("notetrack"))) {
866  while (*attrs) {
867  const wxChar *attr = *attrs++;
868  const wxChar *value = *attrs++;
869  if (!value)
870  break;
871  const wxString strValue = value;
872  long nValue;
873  double dblValue;
874  if (!wxStrcmp(attr, wxT("name")) && XMLValueChecker::IsGoodString(strValue))
875  mName = strValue;
876  else if (this->NoteTrackBase::HandleXMLAttribute(attr, value))
877  {}
878  else if (!wxStrcmp(attr, wxT("offset")) &&
879  XMLValueChecker::IsGoodString(strValue) &&
880  Internat::CompatibleToDouble(strValue, &dblValue))
881  SetOffset(dblValue);
882  else if (!wxStrcmp(attr, wxT("visiblechannels"))) {
883  if (!XMLValueChecker::IsGoodInt(strValue) ||
884  !strValue.ToLong(&nValue) ||
885  !XMLValueChecker::IsValidVisibleChannels(nValue))
886  return false;
887  mVisibleChannels = nValue;
888  }
889  else if (!wxStrcmp(attr, wxT("height")) &&
890  XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
891  mHeight = nValue;
892  else if (!wxStrcmp(attr, wxT("minimized")) &&
893  XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
894  mMinimized = (nValue != 0);
895  else if (!wxStrcmp(attr, wxT("isSelected")) &&
896  XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
897  this->SetSelected(nValue != 0);
898 #ifdef EXPERIMENTAL_MIDI_OUT
899  else if (!wxStrcmp(attr, wxT("velocity")) &&
900  XMLValueChecker::IsGoodString(strValue) &&
901  Internat::CompatibleToDouble(strValue, &dblValue))
902  mVelocity = (float) dblValue;
903 #endif
904  else if (!wxStrcmp(attr, wxT("bottomnote")) &&
905  XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
906  SetBottomNote(nValue);
907  else if (!wxStrcmp(attr, wxT("data"))) {
908  std::string s(strValue.mb_str(wxConvUTF8));
909  std::istringstream data(s);
910  mSeq = std::make_unique<Alg_seq>(data, false);
911  }
912  } // while
913  return true;
914  }
915  return false;
916 }
917 
918 XMLTagHandler *NoteTrack::HandleXMLChild(const wxChar * WXUNUSED(tag))
919 {
920  return NULL;
921 }
922 
923 void NoteTrack::WriteXML(XMLWriter &xmlFile) const
924 // may throw
925 {
926  std::ostringstream data;
927  Track::Holder holder;
928  const NoteTrack *saveme = this;
929  if (!mSeq) {
930  // replace saveme with an (unserialized) duplicate, which is
931  // destroyed at end of function.
932  holder = Duplicate();
933  saveme = static_cast<NoteTrack*>(holder.get());
934  }
935  saveme->GetSeq().write(data, true);
936  xmlFile.StartTag(wxT("notetrack"));
937  xmlFile.WriteAttr(wxT("name"), saveme->mName);
938  this->NoteTrackBase::WriteXMLAttributes(xmlFile);
939  xmlFile.WriteAttr(wxT("offset"), saveme->GetOffset());
940  xmlFile.WriteAttr(wxT("visiblechannels"), saveme->mVisibleChannels);
941  xmlFile.WriteAttr(wxT("height"), saveme->GetActualHeight());
942  xmlFile.WriteAttr(wxT("minimized"), saveme->GetMinimized());
943  xmlFile.WriteAttr(wxT("isSelected"), this->GetSelected());
944 
945 #ifdef EXPERIMENTAL_MIDI_OUT
946  xmlFile.WriteAttr(wxT("velocity"), (double) saveme->mVelocity);
947 #endif
948  xmlFile.WriteAttr(wxT("bottomnote"), saveme->mBottomNote);
949  xmlFile.WriteAttr(wxT("data"), wxString(data.str().c_str(), wxConvUTF8));
950  xmlFile.EndTag(wxT("notetrack"));
951 }
952 
953 #if 0
954 void NoteTrack::StartVScroll()
955 {
956  mStartBottomNote = mBottomNote;
957 }
958 
959 void NoteTrack::VScroll(int start, int end)
960 {
961  int ph = GetPitchHeight();
962  int delta = ((end - start) + ph / 2) / ph;
963  SetBottomNote(mStartBottomNote + delta);
964 }
965 #endif
966 
967 void NoteTrack::Zoom(const wxRect &rect, int y, float multiplier, bool center)
968 {
969  // Construct track rectangle to map pitch to screen coordinates
970  // Only y and height are needed:
971  wxRect trackRect(0, rect.GetY(), 1, rect.GetHeight());
972  PrepareIPitchToY(trackRect);
973  int clickedPitch = YToIPitch(y);
974  // zoom by changing the pitch height
975  SetPitchHeight(rect.height, mPitchHeight * multiplier);
976  PrepareIPitchToY(trackRect); // update because mPitchHeight changed
977  if (center) {
978  int newCenterPitch = YToIPitch(rect.GetY() + rect.GetHeight() / 2);
979  // center the pitch that the user clicked on
980  SetBottomNote(mBottomNote + (clickedPitch - newCenterPitch));
981  } else {
982  int newClickedPitch = YToIPitch(y);
983  // align to keep the pitch that the user clicked on in the same place
984  SetBottomNote(mBottomNote + (clickedPitch - newClickedPitch));
985  }
986 }
987 
988 
989 void NoteTrack::ZoomTo(const wxRect &rect, int start, int end)
990 {
991  wxRect trackRect(0, rect.GetY(), 1, rect.GetHeight());
992  PrepareIPitchToY(trackRect);
993  int topPitch = YToIPitch(start);
994  int botPitch = YToIPitch(end);
995  if (topPitch < botPitch) { // swap
996  int temp = topPitch; topPitch = botPitch; botPitch = temp;
997  }
998  if (topPitch == botPitch) { // can't divide by zero, do something else
999  Zoom(rect, start, 1, true);
1000  return;
1001  }
1002  auto trialPitchHeight = (float)trackRect.height / (topPitch - botPitch);
1003  Zoom(rect, (start + end) / 2, trialPitchHeight / mPitchHeight, true);
1004 }
1005 
1006 int NoteTrack::YToIPitch(int y)
1007 {
1008  y = mBottom - y; // pixels above pitch 0
1009  int octave = (y / GetOctaveHeight());
1010  y -= octave * GetOctaveHeight();
1011  // result is approximate because C and G are one pixel taller than
1012  // mPitchHeight.
1013  return (y / GetPitchHeight(1)) + octave * 12;
1014 }
1015 
1016 const float NoteTrack::ZoomStep = powf( 2.0f, 0.25f );
1017 
1018 #endif // USE_MIDI
AUDACITY_DLL_API Theme theTheme
Definition: Theme.cpp:209
static void DarkMIDIChannel(wxDC *dc, int channel)
Definition: AColor.cpp:602
#define SonifyBeginSonification()
Definition: NoteTrack.h:280
const std::shared_ptr< DirManager > mDirManager
Definition: Track.h:859
#define THROW_INCONSISTENCY_EXCEPTION
#define safenew
Definition: Audacity.h:223
static bool IsGoodInt(const wxString &strInt)
Check that the supplied string can be converted to a long (32bit) integer.
virtual int GetKind() const
Definition: Track.h:322
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper, LinearInputRateSlideTimeWarper, LinearOutputRateSlideTimeWarper, LinearInputInverseRateTimeWarper, GeometricInputRateTimeWarper, GeometricOutputRateTimeWarper classes.
wxFileConfig * gPrefs
Definition: Prefs.cpp:72
static void LightMIDIChannel(wxDC *dc, int channel)
Definition: AColor.cpp:584
static unsigned DefaultNoteTrackHeight()
static bool IsGoodString(const wxString &str)
static bool CompatibleToDouble(const wxString &stringToConvert, double *result)
Convert a string to a number.
Definition: Internat.cpp:121
static void MIDIChannel(wxDC *dc, int channel)
Definition: AColor.cpp:568
Fundamental data object of Audacity, placed in the TrackPanel. Classes derived form it include the Wa...
Definition: Track.h:94
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:70
#define SonifyEndSonification()
Definition: NoteTrack.h:281
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:252
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:122
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom"))), OnMoveTrack) void TrackMenuTable::OnSetName(wxCommandEvent &)
virtual double Warp(double originalTime) const =0
Transforms one point in time to another point. For example, a time stretching effect might use one to...
Definition: TimeWarper.h:61
wxColour & Colour(int iIndex)
Definition: Theme.cpp:1208
std::unique_ptr< Track > Holder
Definition: Track.h:256
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:22
virtual void DoSetHeight(int h)
Definition: Track.cpp:207
#define SonifyBeginSerialize()
Definition: NoteTrack.h:288
#define SonifyEndSerialize()
Definition: NoteTrack.h:289
A Track that is used for Midi notes. (Somewhat old code).