From 2fd555537875a25efcf933eb68a2a02302b55712 Mon Sep 17 00:00:00 2001 From: rbdannenberg Date: Thu, 7 Oct 2010 21:36:39 +0000 Subject: [PATCH] Fixed playback/redraw interactions (redraw was converting from seconds to beats while player was trying to read seconds in another thread). Added compile-time option make channel select be the down position. Fixed problem with MIDI track mute to eliminate hung notes. --- lib-src/portsmf/allegro.cpp | 48 +++++++++++++++++++++-------- lib-src/portsmf/allegro.h | 10 ++++++- src/AudioIO.cpp | 60 ++++++++++++++++++++++++------------- src/NoteTrack.cpp | 21 ++++++++++++- src/TrackArtist.cpp | 3 +- src/TrackPanel.cpp | 4 +-- 6 files changed, 106 insertions(+), 40 deletions(-) diff --git a/lib-src/portsmf/allegro.cpp b/lib-src/portsmf/allegro.cpp index 3965dff97..19352c21f 100644 --- a/lib-src/portsmf/allegro.cpp +++ b/lib-src/portsmf/allegro.cpp @@ -698,6 +698,7 @@ void Alg_events::append(Alg_event_ptr event) Alg_events::~Alg_events() { + assert(!in_use); // individual events are not deleted, only the array if (events) { delete[] events; @@ -2622,7 +2623,15 @@ void Alg_tracks::reset() if (tracks) delete [] tracks; tracks = NULL; len = 0; - maxlen = 0; // Modified by Ning Hu Nov.19 2002 + maxlen = 0; +} + + +void Alg_tracks::set_in_use(bool flag) +{ + for (int i = 0; i < len; i++) { + tracks[i]->in_use = flag; + } } @@ -2680,15 +2689,8 @@ bool Alg_iterator::earlier(int i, int j) // followed immediately with the same timestamp by a note-on (common // in MIDI files), the note-off will be scheduled first - Alg_pending_event_ptr p_i = &(pending_events[i]); - Alg_event_ptr e_i = (*(p_i->events))[p_i->index]; - double t_i = (p_i->note_on ? e_i->time : - e_i->get_end_time() - ALG_EPS) + p_i->offset; - - Alg_pending_event_ptr p_j = &(pending_events[j]); - Alg_event_ptr e_j = (*(p_j->events))[p_j->index]; - double t_j = (p_j->note_on ? e_j->time : - e_j->get_end_time() - ALG_EPS) + p_j->offset; + double t_i = pending_events[i].time; + double t_j = pending_events[j].time; if (t_i < t_j) return true; // not sure if this case really exists or this is the best rule, but @@ -2707,6 +2709,17 @@ void Alg_iterator::insert(Alg_events_ptr events, long index, pending_events[len].note_on = note_on; pending_events[len].cookie = cookie; pending_events[len].offset = offset; + Alg_event_ptr event = (*events)[index]; + pending_events[len].time = (note_on ? event->time : + event->get_end_time() - ALG_EPS) + offset; + /* BEGIN DEBUG * + printf("insert %p=%p[%d] @ %g\n", event, events, index, + pending_events[len].time); + printf(" is_note %d note_on %d time %g dur %g end_time %g offset %g\n", + event->is_note(), note_on, event->time, event->get_duration(), + event->get_end_time(), offset); + } + * END DEBUG */ int loc = len; int loc_parent = HEAP_PARENT(loc); len++; @@ -2720,12 +2733,12 @@ void Alg_iterator::insert(Alg_events_ptr events, long index, loc = loc_parent; loc_parent = HEAP_PARENT(loc); } - // printf("After insert:"); show(); } + bool Alg_iterator::remove_next(Alg_events_ptr &events, long &index, bool ¬e_on, void *&cookie, - double &offset) + double &offset, double &time) { if (len == 0) return false; // empty! events = pending_events[0].events; @@ -2734,6 +2747,7 @@ bool Alg_iterator::remove_next(Alg_events_ptr &events, long &index, offset = pending_events[0].offset; cookie = pending_events[0].cookie; offset = pending_events[0].offset; + time = pending_events[0].time; len--; pending_events[0] = pending_events[len]; // sift down @@ -3421,7 +3435,8 @@ Alg_event_ptr Alg_iterator::next(bool *note_on, void **cookie_ptr, // return the next event in time from any track { bool on; - if (!remove_next(events_ptr, index, on, cookie, offset)) { + double when; + if (!remove_next(events_ptr, index, on, cookie, offset, when)) { return NULL; } if (note_on) *note_on = on; @@ -3484,6 +3499,13 @@ void Alg_seq::merge_tracks() } +void Alg_seq::set_in_use(bool flag) +{ + Alg_track::set_in_use(flag); + track_list.set_in_use(flag); +} + + // sr_letter_to_type = {"i": 'Integer', "r": 'Real', "s": 'String', // "l": 'Logical', "a": 'Symbol'} diff --git a/lib-src/portsmf/allegro.h b/lib-src/portsmf/allegro.h index 9a7da871b..9173b131e 100644 --- a/lib-src/portsmf/allegro.h +++ b/lib-src/portsmf/allegro.h @@ -324,6 +324,9 @@ public: // creating a new track and adding notes to it. It is *not* // updated after uninsert(), so use it with care. double last_note_off; + // initially false, in_use can be used to mark "do not delete". If an + // Alg_events instance is deleted while "in_use", an assertion will fail. + bool in_use; virtual int length() { return len; } Alg_event_ptr &operator[](int i) { assert(i >= 0 && i < len); @@ -333,6 +336,7 @@ public: maxlen = len = 0; events = NULL; last_note_off = 0; + in_use = false; } // destructor deletes the events array, but not the // events themselves @@ -797,6 +801,7 @@ public: virtual Alg_event_list *find(double t, double len, bool all, long channel_mask, long event_type_mask); + virtual void set_in_use(bool flag) { in_use = flag; } // // MIDI playback // @@ -891,6 +896,7 @@ public: void append(Alg_track_ptr track); void add_track(int track_num, Alg_time_map_ptr time_map, bool seconds); void reset(); + void set_in_use(bool flag); // handy to set in_use flag on all tracks } *Alg_tracks_ptr; @@ -912,6 +918,7 @@ typedef struct Alg_pending_event { long index; // offset of this event bool note_on; // is this a note-on or a note-off (if applicable)? double offset; // time offset for events + double time; // time for this event } *Alg_pending_event_ptr; @@ -934,7 +941,7 @@ private: void *cookie, double offset); // returns the info on the next pending event in the priority queue bool remove_next(Alg_events_ptr &events, long &index, bool ¬e_on, - void *&cookie, double &offset); + void *&cookie, double &offset, double &time); public: bool note_off_flag; // remembers if we are iterating over note-off // events as well as note-on and update events @@ -1096,6 +1103,7 @@ public: double *num, double *den); // void set_events(Alg_event_ptr *events, long len, long max); void merge_tracks(); // move all track data into one track + void set_in_use(bool flag); // set in_use flag on all tracks } *Alg_seq_ptr, &Alg_seq_ref; diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 47cf5da82..8724dfb89 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -1322,13 +1322,18 @@ void AudioIO::PrepareMidiIterator(bool send, double offset) { int i; int nTracks = mMidiPlaybackTracks.GetCount(); - // Set up to play only one track: - mSeq = mMidiPlaybackTracks[0]->GetSequence(); - mIterator = new Alg_iterator(mSeq, false); + // instead of initializing with an Alg_seq, we use begin_seq() + // below to add ALL Alg_seq's. + mIterator = new Alg_iterator(NULL, false); // Iterator not yet intialized, must add each track... for (i = 0; i < nTracks; i++) { NoteTrack *t = mMidiPlaybackTracks[i]; - mIterator->begin_seq(t->GetSequence(), t, t->GetOffset() + offset); + Alg_seq_ptr seq = t->GetSequence(); + // mark sequence tracks as "in use" since we're handing this + // off to another thread and want to make sure nothing happens + // to the data until playback finishes. This is just a sanity check. + seq->set_in_use(true); + mIterator->begin_seq(seq, t, t->GetOffset() + offset); } GetNextEvent(); // prime the pump for FillMidiBuffers @@ -1382,13 +1387,6 @@ bool AudioIO::StartPortMidiStream() &::MidiTime, NULL, mMidiLatency); - // DEBUGGING - //const PmDeviceInfo *info = Pm_GetDeviceInfo(playbackDevice); - //printf("Pm_OpenOutput on %s, return code %d\n", - // info->name, mLastPmError); - - //fprintf(stderr, "mT0: %f\n", mT0); - mMidiStreamActive = true; mPauseTime = 0; mMidiPaused = false; @@ -1528,6 +1526,15 @@ void AudioIO::StopStream() Pm_Close(mMidiStream); mMidiStream = NULL; mIterator->end(); + + // set in_use flags to false + int nTracks = mMidiPlaybackTracks.GetCount(); + for (int i = 0; i < nTracks; i++) { + NoteTrack *t = mMidiPlaybackTracks[i]; + Alg_seq_ptr seq = t->GetSequence(); + seq->set_in_use(false); + } + delete mIterator; mIterator = NULL; // just in case someone tries to reference it mMidiPlaySpeed = 1.0; @@ -2612,11 +2619,11 @@ void AudioIO::OutputEvent() int command = -1; int data1 = -1; int data2 = -1; - // 0.0005 is for rounding double eventTime = (mNextEventTime - mT0) / mMidiPlaySpeed + mT0; double time = eventTime + PauseTime() + 0.0005 - ((mMidiLatency + mSynthLatency) * 0.001); + if (mNumPlaybackChannels > 0) { // is there audio playback? time += 1; // MidiTime() has a 1s offset } else { @@ -2645,15 +2652,25 @@ void AudioIO::OutputEvent() // if mNextEvent's channel is visible, play it, visibility can // be updated while playing. Be careful: if we have a note-off, // then we must not pay attention to the channel selection - // because we must turn the note off even if the user changed - // the channel selection after the note began + // or mute/solo buttons because we must turn the note off + // even if the user changed something after the note began + // Note that because multiple tracks can output to the same + // MIDI channels, it is not a good idea to send "All Notes Off" + // when the user presses the mute button. We have no easy way + // to know what notes are sounding on any given muted track, so + // we'll just wait for the note-off events to happen. + // Also note that note-offs are only sent when we call + // mIterator->request_note_off(), so notes that are not played + // will note generate random note-offs. There is the interesting + // case that if the playback is paused, all-notes-off WILL be sent + // and if playback resumes, the pending note-off events WILL also + // be sent (but if that is a problem, there would also be a problem + // in the non-pause case. if (((mNextEventTrack->GetVisibleChannels() & (1 << channel)) && // only play if note is not muted: !((mHasSolo || mNextEventTrack->GetMute()) && - !mNextEventTrack->GetSolo()))) { - // || - // the following allows note-offs even when muted or not selected - // (mNextEvent->is_note() && !mNextIsNoteOn)) { + !mNextEventTrack->GetSolo())) || + (mNextEvent->is_note() && !mNextIsNoteOn)) { // Note event if (mNextEvent->is_note() && !mSendMidiState) { // Pitch and velocity @@ -2718,9 +2735,10 @@ void AudioIO::OutputEvent() Pm_WriteShort(mMidiStream, timestamp, Pm_Message((int) (command + channel), (long) data1, (long) data2)); - //printf("Pm_WriteShort %lx @ %d\n", - // Pm_Message((int) (command + channel), - // (long) data1, (long) data2), timestamp); + /* printf("Pm_WriteShort %lx (%p) @ %d, advance %d\n", + Pm_Message((int) (command + channel), + (long) data1, (long) data2), + mNextEvent, timestamp, timestamp - Pt_Time()); */ } } } diff --git a/src/NoteTrack.cpp b/src/NoteTrack.cpp index 6ae565081..13d201363 100644 --- a/src/NoteTrack.cpp +++ b/src/NoteTrack.cpp @@ -206,7 +206,11 @@ int NoteTrack::DrawLabelControls(wxDC & dc, wxRect & r) if (mVisibleChannels & (1 << (channel - 1))) { AColor::MIDIChannel(&dc, channel); dc.DrawRectangle(box); - +// two choices: channel is enabled (to see and play) when button is in +// "up" position (original Audacity style) or in "down" position +// +#define CHANNEL_ON_IS_DOWN 0 +#if !CHANNEL_ON_IS_DOWN AColor::LightMIDIChannel(&dc, channel); AColor::Line(dc, box.x, box.y, box.x + box.width - 1, box.y); AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1); @@ -218,9 +222,23 @@ int NoteTrack::DrawLabelControls(wxDC & dc, wxRect & r) AColor::Line(dc, box.x, box.y + box.height - 1, box.x + box.width - 1, box.y + box.height - 1); +#endif } else { AColor::MIDIChannel(&dc, 0); dc.DrawRectangle(box); +#if CHANNEL_ON_IS_DOWN + AColor::LightMIDIChannel(&dc, 0); + AColor::Line(dc, box.x, box.y, box.x + box.width - 1, box.y); + AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1); + + AColor::DarkMIDIChannel(&dc, 0); + AColor::Line(dc, + box.x + box.width - 1, box.y, + box.x + box.width - 1, box.y + box.height - 1); + AColor::Line(dc, + box.x, box.y + box.height - 1, + box.x + box.width - 1, box.y + box.height - 1); +#endif } wxString t; @@ -233,6 +251,7 @@ int NoteTrack::DrawLabelControls(wxDC & dc, wxRect & r) dc.DrawText(t, box.x + (box.width - w) / 2, box.y + (box.height - h) / 2); } } + AColor::MIDIChannel(&dc, 0); // always return with gray color selected return box.GetBottom(); } diff --git a/src/TrackArtist.cpp b/src/TrackArtist.cpp index 96fb3897f..ec057588e 100644 --- a/src/TrackArtist.cpp +++ b/src/TrackArtist.cpp @@ -2252,7 +2252,7 @@ void TrackArtist::DrawNoteBackground(NoteTrack *track, wxDC &dc, // Iterate over all time signatures to generate beat positions of // bar lines, map the beats to times, map the times to position, // and draw the bar lines that fall within the region of interest (sel) - seq->convert_to_beats(); + // seq->convert_to_beats(); dc.SetPen(mp); Alg_time_sigs &sigs = seq->time_sig; int i = 0; // index into ts[] @@ -2408,7 +2408,6 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, iterator.begin(); //for every event Alg_event_ptr evt; - printf ("go time\n"); while (evt = iterator.next()) { if (evt->get_type() == 'n') { // 'n' means a note Alg_note_ptr note = (Alg_note_ptr) evt; diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index 58ab5b8d1..60e8451d4 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -3996,7 +3996,7 @@ void TrackPanel::HandleSliders(wxMouseEvent &event, bool pan) pMixerBoard->UpdateGain((WaveTrack*)mCapturedTrack); } #ifdef USE_MIDI - } else { + } else { if (!pan) { ((NoteTrack *) mCapturedTrack)->SetGain(newValue); #ifdef EXPERIMENTAL_MIXER_BOARD @@ -4006,7 +4006,7 @@ void TrackPanel::HandleSliders(wxMouseEvent &event, bool pan) pMixerBoard->UpdateGain((WaveTrack*)mCapturedTrack); #endif } - } + } #endif VisibleTrackIterator iter(GetProject());