From a76ad22c91374d1b488ce6a6b1b48af1bf2ed544 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Tue, 28 Mar 2017 11:29:40 -0700 Subject: [PATCH] Fix midi channel toggling This reintroduces the buttons to toggle display of individual midi channels, and cleans up the code behind that feature. This functionality has actually been present in production versions of audacity for a while, at least for clicking. However, the buttons themselves were not drawn, making it exteremly painful (but possible) to use. As requested by PRL, this is always enabled if USE_MIDI is defined. --- src/AudioIO.cpp | 2 +- src/NoteTrack.cpp | 87 ++++++++++++++++++--------------------------- src/NoteTrack.h | 22 +++++++++--- src/TrackArtist.cpp | 3 +- src/TrackPanel.cpp | 47 +++++++++++------------- src/TrackPanel.h | 11 +++++- 6 files changed, 83 insertions(+), 89 deletions(-) diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 87c10ce4c..02aabc12c 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -3797,7 +3797,7 @@ void AudioIO::OutputEvent() // 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)) && + if (((mNextEventTrack->IsVisibleChan(channel)) && // only play if note is not muted: !((mHasSolo || mNextEventTrack->GetMute()) && !mNextEventTrack->GetSolo())) || diff --git a/src/NoteTrack.cpp b/src/NoteTrack.cpp index fdd7c833b..64463c54b 100644 --- a/src/NoteTrack.cpp +++ b/src/NoteTrack.cpp @@ -213,13 +213,11 @@ void NoteTrack::WarpAndTransposeNotes(double t0, double t1, iter.begin(); Alg_event_ptr event; while (0 != (event = iter.next()) && event->time < t1) { - if (event->is_note() && event->time >= t0 && - // Allegro data structure does not restrict channels to 16. - // Since there is not way to select more than 16 channels, - // map all channel numbers mod 16. This will have no effect - // on MIDI files, but it will allow users to at least select - // all channels on non-MIDI event sequence data. - IsVisibleChan(event->chan % 16)) { + if (event->is_note() && event->time >= t0 +#ifdef EXPERIMENTAL_MIDI_CONTROLS + && IsVisibleChan(event->chan) +#endif + ) { event->set_pitch(event->get_pitch() + semitones); } } @@ -238,19 +236,15 @@ void NoteTrack::WarpAndTransposeNotes(double t0, double t1, mSeq->convert_to_seconds(); } - - -int NoteTrack::DrawLabelControls(wxDC & dc, const wxRect &r) +// Draws the midi channel toggle buttons within the given rect. +// The rect should be evenly divisible by 4 on both axis. +void NoteTrack::DrawLabelControls(wxDC & dc, const wxRect &rect) { - int wid = 23; - int ht = 16; + wxASSERT_MSG(rect.width % 4 == 0, "Midi channel control rect width must be divisible by 4"); + wxASSERT_MSG(rect.height % 4 == 0, "Midi channel control rect height must be divisible by 4"); - if (r.height < ht * 4) { - return r.y + 5 + ht * 4; - } - - int x = r.x + (r.width / 2 - wid * 2) + 2; - int y = r.y + 5; + auto cellWidth = rect.width / 4; + auto cellHeight = rect.height / 4; wxRect box; for (int row = 0; row < 4; row++) { @@ -259,10 +253,10 @@ int NoteTrack::DrawLabelControls(wxDC & dc, const wxRect &r) // used by AColor and button labels int chanName = row * 4 + col + 1; - box.x = x + col * wid; - box.y = y + row * ht; - box.width = wid; - box.height = ht; + box.x = rect.x + col * cellWidth; + box.y = rect.y + row * cellHeight; + box.width = cellWidth; + box.height = cellHeight; if (IsVisibleChan(chanName - 1)) { AColor::MIDIChannel(&dc, chanName); @@ -315,47 +309,39 @@ int NoteTrack::DrawLabelControls(wxDC & dc, const wxRect &r) } - wxString t; + wxString text; wxCoord w; wxCoord h; - t.Printf(wxT("%d"), chanName); - dc.GetTextExtent(t, &w, &h); + text.Printf(wxT("%d"), chanName); + dc.GetTextExtent(text, &w, &h); - dc.DrawText(t, box.x + (box.width - w) / 2, box.y + (box.height - h) / 2); + dc.DrawText(text, 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(); } -bool NoteTrack::LabelClick(const wxRect &r, int mx, int my, bool right) +// Handles clicking within the midi controls rect (same as DrawLabelControls). +// This is somewhat oddly written, as these aren't real buttons - they act +// when the mouse goes down; you can't hold it pressed and move off of it. +// Left-clicking toggles a single channel; right-clicking turns off all other channels. +bool NoteTrack::LabelClick(const wxRect &rect, int mx, int my, bool right) { - int wid = 23; - int ht = 16; + wxASSERT_MSG(rect.width % 4 == 0, "Midi channel control rect width must be divisible by 4"); + wxASSERT_MSG(rect.height % 4 == 0, "Midi channel control rect height must be divisible by 4"); - if (r.height < ht * 4) - return false; + auto cellWidth = rect.width / 4; + auto cellHeight = rect.height / 4; - int x = r.x + (r.width / 2 - wid * 2); - int y = r.y + 1; - // after adding Mute and Solo buttons, mapping is broken, so hack in the offset - y += 12; - - int col = (mx - x) / wid; - int row = (my - y) / ht; - - if (row < 0 || row >= 4 || col < 0 || col >= 4) - return false; + int col = (mx - rect.x) / cellWidth; + int row = (my - rect.y) / cellHeight; int channel = row * 4 + col; - if (right) { - if (mVisibleChannels == CHANNEL_BIT(channel)) - mVisibleChannels = ALL_CHANNELS; - else - mVisibleChannels = CHANNEL_BIT(channel); - } else + if (right) + SoloVisibleChan(channel); + else ToggleVisibleChan(channel); return true; @@ -428,11 +414,6 @@ void NoteTrack::PrintSequence() fclose(debugOutput); } -int NoteTrack::GetVisibleChannels() -{ - return mVisibleChannels; -} - Track::Holder NoteTrack::Cut(double t0, double t1) { if (t1 < t0) diff --git a/src/NoteTrack.h b/src/NoteTrack.h index c0db1bb94..6097c4b5d 100644 --- a/src/NoteTrack.h +++ b/src/NoteTrack.h @@ -79,15 +79,13 @@ class AUDACITY_DLL_API NoteTrack final void WarpAndTransposeNotes(double t0, double t1, const TimeWarper &warper, double semitones); - int DrawLabelControls(wxDC & dc, const wxRect &r); + void DrawLabelControls(wxDC & dc, const wxRect &rect); bool LabelClick(const wxRect &rect, int x, int y, bool right); void SetSequence(std::unique_ptr &&seq); Alg_seq* GetSequence(); void PrintSequence(); - int GetVisibleChannels(); - Alg_seq *MakeExportableSeq(std::unique_ptr &cleanup) const; bool ExportMIDI(const wxString &f) const; bool ExportAllegro(const wxString &f) const; @@ -184,14 +182,28 @@ class AUDACITY_DLL_API NoteTrack final // channels are numbered as integers 0-15, visible channels // (mVisibleChannels) is a bit set. Channels are displayed as // integers 1-16. -#define CHANNEL_BIT(c) (1 << (c)) + + // Allegro's data structure does not restrict channels to 16. + // Since there is not way to select more than 16 channels, + // map all channel numbers mod 16. This will have no effect + // on MIDI files, but it will allow users to at least select + // all channels on non-MIDI event sequence data. #define ALL_CHANNELS 0xFFFF - bool IsVisibleChan(int c) { +#define CHANNEL_BIT(c) (1 << (c & ALL_CHANNELS)) + bool IsVisibleChan(int c) const { return (mVisibleChannels & CHANNEL_BIT(c)) != 0; } void SetVisibleChan(int c) { mVisibleChannels |= CHANNEL_BIT(c); } void ClearVisibleChan(int c) { mVisibleChannels &= ~CHANNEL_BIT(c); } void ToggleVisibleChan(int c) { mVisibleChannels ^= CHANNEL_BIT(c); } + // Solos the given channel. If it's the only channel visible, all channels + // are enabled; otherwise, it is set to the only visible channel. + void SoloVisibleChan(int c) { + if (mVisibleChannels == CHANNEL_BIT(c)) + mVisibleChannels = ALL_CHANNELS; + else + mVisibleChannels = CHANNEL_BIT(c); + } private: std::unique_ptr mSeq; // NULL means no sequence // when Duplicate() is called, assume that it is to put a copy diff --git a/src/TrackArtist.cpp b/src/TrackArtist.cpp index 849254746..440990552 100644 --- a/src/TrackArtist.cpp +++ b/src/TrackArtist.cpp @@ -2837,7 +2837,6 @@ void TrackArtist::DrawNoteTrack(const NoteTrack *track, track->mSerializationBuffer.reset(); } wxASSERT(seq); - int visibleChannels = track->mVisibleChannels; if (!track->GetSelected()) sel0 = sel1 = 0.0; @@ -2939,7 +2938,7 @@ void TrackArtist::DrawNoteTrack(const NoteTrack *track, if (evt->get_type() == 'n') { // 'n' means a note Alg_note_ptr note = (Alg_note_ptr) evt; // if the note's channel is visible - if (visibleChannels & (1 << (evt->chan & 15))) { + if (track->IsVisibleChan(evt->chan)) { double xx = note->time + track->GetOffset(); double x1 = xx + note->dur; if (xx < h1 && x1 > h) { // omit if outside box diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index 0c4eea7aa..d24a4da4b 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -5179,7 +5179,6 @@ void TrackPanel::HandleLabelClick(wxMouseEvent & event) // DM: If it's a NoteTrack, it has special controls else if (t->GetKind() == Track::Note) { - wxRect midiRect; #ifdef EXPERIMENTAL_MIDI_OUT if (isleft && (MuteSoloFunc(t, rect, event.m_x, event.m_y, false) || MuteSoloFunc(t, rect, event.m_x, event.m_y, true))) @@ -5188,10 +5187,13 @@ void TrackPanel::HandleLabelClick(wxMouseEvent & event) if (isleft && VelocityFunc(t, rect, event, event.m_x, event.m_y)) return; #endif - mTrackInfo.GetTrackControlsRect(rect, midiRect); - if (midiRect.Contains(event.m_x, event.m_y) && - ((NoteTrack *)t)->LabelClick(midiRect, event.m_x, event.m_y, - event.Button(wxMOUSE_BTN_RIGHT))) { + wxRect midiRect; + mTrackInfo.GetMidiControlsRect(rect, midiRect); + + bool isright = event.Button(wxMOUSE_BTN_RIGHT); + + if ((isleft || isright) && midiRect.Contains(event.m_x, event.m_y) && + static_cast(t)->LabelClick(midiRect, event.m_x, event.m_y, isright)) { Refresh(false); return; } @@ -7314,13 +7316,10 @@ void TrackPanel::DrawOutside(Track * t, wxDC * dc, const wxRect & rec, #ifdef EXPERIMENTAL_MIDI_OUT else if (bIsNote) { wxRect midiRect; - mTrackInfo.GetTrackControlsRect(trackRect, midiRect); - // Offset by height of Solo/Mute buttons: - midiRect.y += 15; - midiRect.height -= 21; // allow room for minimize button at bottom - - ((NoteTrack *)t)->DrawLabelControls(*dc, midiRect); + mTrackInfo.GetMidiControlsRect(rect, midiRect); + if (midiRect.y + midiRect.height < rect.y + rect.height - 19) + static_cast(t)->DrawLabelControls(*dc, midiRect); // Draw some lines for MuteSolo buttons (normally handled by DrawBordersWithin but not done for note tracks) if (rect.height > 48) { // Note: offset up by 34 units @@ -9283,6 +9282,15 @@ void TrackInfo::GetSyncLockIconRect(const wxRect & rect, wxRect &dest) const dest.height = kTrackInfoBtnSize; } +#ifdef USE_MIDI +void TrackInfo::GetMidiControlsRect(const wxRect & rect, wxRect & dest) const +{ + dest.x = rect.x + 2; // To center slightly + dest.width = kMidiCellWidth * 4; + dest.y = rect.y + 34; + dest.height = kMidiCellHeight * 4; +} +#endif /// \todo Probably should move to 'Utils.cpp'. void TrackInfo::SetTrackInfoFont(wxDC * dc) const @@ -9354,22 +9362,7 @@ void TrackInfo::DrawBackground(wxDC * dc, const wxRect & rect, bool bSelected, #endif } -void TrackInfo::GetTrackControlsRect(const wxRect & rect, wxRect & dest) const -{ - wxRect top; - wxRect bot; - - GetTitleBarRect(rect, top); - GetMinimizeRect(rect, bot); - - dest.x = rect.x; - dest.width = kTrackInfoWidth - dest.x; - dest.y = top.GetBottom() + 2; // BUG - dest.height = bot.GetTop() - top.GetBottom() - 2; -} - - -void TrackInfo::DrawCloseBox(wxDC * dc, const wxRect & rect, Track * t, bool down) const +void TrackInfo::DrawCloseBox(wxDC * dc, const wxRect & rect, Track * t, bool down) const { wxRect bev; GetCloseBoxRect(rect, bev); diff --git a/src/TrackPanel.h b/src/TrackPanel.h index 1cc0aa790..6c765f9a6 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -114,7 +114,6 @@ private: // Draw the minimize button *and* the sync-lock track icon, if necessary. void DrawMinimize(wxDC * dc, const wxRect & rect, Track * t, bool down) const; - void GetTrackControlsRect(const wxRect & rect, wxRect &dest) const; void GetCloseBoxRect(const wxRect & rect, wxRect &dest) const; void GetTitleBarRect(const wxRect & rect, wxRect &dest) const; void GetMuteSoloRect(const wxRect & rect, wxRect &dest, bool solo, bool bHasSoloButton, @@ -126,6 +125,9 @@ private: #endif void GetMinimizeRect(const wxRect & rect, wxRect &dest) const; void GetSyncLockIconRect(const wxRect & rect, wxRect &dest) const; +#ifdef USE_MIDI + void GetMidiControlsRect(const wxRect & rect, wxRect &dest) const; +#endif public: LWSlider * GainSlider(WaveTrack *t, bool captured = false) const; @@ -881,6 +883,13 @@ enum : int { kTrackInfoBtnSize = 18 // widely used dimension, usually height }; +#ifdef USE_MIDI +enum : int { + kMidiCellWidth = (kTrackInfoWidth / 4) - 2, + kMidiCellHeight = kTrackInfoBtnSize +}; +#endif + #ifdef _MSC_VER #pragma warning( pop ) #endif