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