1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-08 16:11:14 +02:00

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.
This commit is contained in:
Pokechu22 2017-03-28 11:29:40 -07:00 committed by Paul Licameli
parent c07caade9f
commit a76ad22c91
6 changed files with 83 additions and 89 deletions

View File

@ -3797,7 +3797,7 @@ void AudioIO::OutputEvent()
// and if playback resumes, the pending note-off events WILL also // 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 // be sent (but if that is a problem, there would also be a problem
// in the non-pause case. // in the non-pause case.
if (((mNextEventTrack->GetVisibleChannels() & (1 << channel)) && if (((mNextEventTrack->IsVisibleChan(channel)) &&
// only play if note is not muted: // only play if note is not muted:
!((mHasSolo || mNextEventTrack->GetMute()) && !((mHasSolo || mNextEventTrack->GetMute()) &&
!mNextEventTrack->GetSolo())) || !mNextEventTrack->GetSolo())) ||

View File

@ -213,13 +213,11 @@ void NoteTrack::WarpAndTransposeNotes(double t0, double t1,
iter.begin(); iter.begin();
Alg_event_ptr event; Alg_event_ptr event;
while (0 != (event = iter.next()) && event->time < t1) { while (0 != (event = iter.next()) && event->time < t1) {
if (event->is_note() && event->time >= t0 && if (event->is_note() && event->time >= t0
// Allegro data structure does not restrict channels to 16. #ifdef EXPERIMENTAL_MIDI_CONTROLS
// Since there is not way to select more than 16 channels, && IsVisibleChan(event->chan)
// map all channel numbers mod 16. This will have no effect #endif
// on MIDI files, but it will allow users to at least select ) {
// all channels on non-MIDI event sequence data.
IsVisibleChan(event->chan % 16)) {
event->set_pitch(event->get_pitch() + semitones); event->set_pitch(event->get_pitch() + semitones);
} }
} }
@ -238,19 +236,15 @@ void NoteTrack::WarpAndTransposeNotes(double t0, double t1,
mSeq->convert_to_seconds(); mSeq->convert_to_seconds();
} }
// Draws the midi channel toggle buttons within the given rect.
// The rect should be evenly divisible by 4 on both axis.
int NoteTrack::DrawLabelControls(wxDC & dc, const wxRect &r) void NoteTrack::DrawLabelControls(wxDC & dc, const wxRect &rect)
{ {
int wid = 23; wxASSERT_MSG(rect.width % 4 == 0, "Midi channel control rect width must be divisible by 4");
int ht = 16; wxASSERT_MSG(rect.height % 4 == 0, "Midi channel control rect height must be divisible by 4");
if (r.height < ht * 4) { auto cellWidth = rect.width / 4;
return r.y + 5 + ht * 4; auto cellHeight = rect.height / 4;
}
int x = r.x + (r.width / 2 - wid * 2) + 2;
int y = r.y + 5;
wxRect box; wxRect box;
for (int row = 0; row < 4; row++) { 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 // used by AColor and button labels
int chanName = row * 4 + col + 1; int chanName = row * 4 + col + 1;
box.x = x + col * wid; box.x = rect.x + col * cellWidth;
box.y = y + row * ht; box.y = rect.y + row * cellHeight;
box.width = wid; box.width = cellWidth;
box.height = ht; box.height = cellHeight;
if (IsVisibleChan(chanName - 1)) { if (IsVisibleChan(chanName - 1)) {
AColor::MIDIChannel(&dc, chanName); AColor::MIDIChannel(&dc, chanName);
@ -315,47 +309,39 @@ int NoteTrack::DrawLabelControls(wxDC & dc, const wxRect &r)
} }
wxString t; wxString text;
wxCoord w; wxCoord w;
wxCoord h; wxCoord h;
t.Printf(wxT("%d"), chanName); text.Printf(wxT("%d"), chanName);
dc.GetTextExtent(t, &w, &h); 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 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; wxASSERT_MSG(rect.width % 4 == 0, "Midi channel control rect width must be divisible by 4");
int ht = 16; wxASSERT_MSG(rect.height % 4 == 0, "Midi channel control rect height must be divisible by 4");
if (r.height < ht * 4) auto cellWidth = rect.width / 4;
return false; auto cellHeight = rect.height / 4;
int x = r.x + (r.width / 2 - wid * 2); int col = (mx - rect.x) / cellWidth;
int y = r.y + 1; int row = (my - rect.y) / cellHeight;
// 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 channel = row * 4 + col; int channel = row * 4 + col;
if (right) { if (right)
if (mVisibleChannels == CHANNEL_BIT(channel)) SoloVisibleChan(channel);
mVisibleChannels = ALL_CHANNELS; else
else
mVisibleChannels = CHANNEL_BIT(channel);
} else
ToggleVisibleChan(channel); ToggleVisibleChan(channel);
return true; return true;
@ -428,11 +414,6 @@ void NoteTrack::PrintSequence()
fclose(debugOutput); fclose(debugOutput);
} }
int NoteTrack::GetVisibleChannels()
{
return mVisibleChannels;
}
Track::Holder NoteTrack::Cut(double t0, double t1) Track::Holder NoteTrack::Cut(double t0, double t1)
{ {
if (t1 < t0) if (t1 < t0)

View File

@ -79,15 +79,13 @@ class AUDACITY_DLL_API NoteTrack final
void WarpAndTransposeNotes(double t0, double t1, void WarpAndTransposeNotes(double t0, double t1,
const TimeWarper &warper, double semitones); 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); bool LabelClick(const wxRect &rect, int x, int y, bool right);
void SetSequence(std::unique_ptr<Alg_seq> &&seq); void SetSequence(std::unique_ptr<Alg_seq> &&seq);
Alg_seq* GetSequence(); Alg_seq* GetSequence();
void PrintSequence(); void PrintSequence();
int GetVisibleChannels();
Alg_seq *MakeExportableSeq(std::unique_ptr<Alg_seq> &cleanup) const; Alg_seq *MakeExportableSeq(std::unique_ptr<Alg_seq> &cleanup) const;
bool ExportMIDI(const wxString &f) const; bool ExportMIDI(const wxString &f) const;
bool ExportAllegro(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 // channels are numbered as integers 0-15, visible channels
// (mVisibleChannels) is a bit set. Channels are displayed as // (mVisibleChannels) is a bit set. Channels are displayed as
// integers 1-16. // 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 #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; return (mVisibleChannels & CHANNEL_BIT(c)) != 0;
} }
void SetVisibleChan(int c) { mVisibleChannels |= CHANNEL_BIT(c); } void SetVisibleChan(int c) { mVisibleChannels |= CHANNEL_BIT(c); }
void ClearVisibleChan(int c) { mVisibleChannels &= ~CHANNEL_BIT(c); } void ClearVisibleChan(int c) { mVisibleChannels &= ~CHANNEL_BIT(c); }
void ToggleVisibleChan(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: private:
std::unique_ptr<Alg_seq> mSeq; // NULL means no sequence std::unique_ptr<Alg_seq> mSeq; // NULL means no sequence
// when Duplicate() is called, assume that it is to put a copy // when Duplicate() is called, assume that it is to put a copy

View File

@ -2837,7 +2837,6 @@ void TrackArtist::DrawNoteTrack(const NoteTrack *track,
track->mSerializationBuffer.reset(); track->mSerializationBuffer.reset();
} }
wxASSERT(seq); wxASSERT(seq);
int visibleChannels = track->mVisibleChannels;
if (!track->GetSelected()) if (!track->GetSelected())
sel0 = sel1 = 0.0; sel0 = sel1 = 0.0;
@ -2939,7 +2938,7 @@ void TrackArtist::DrawNoteTrack(const NoteTrack *track,
if (evt->get_type() == 'n') { // 'n' means a note if (evt->get_type() == 'n') { // 'n' means a note
Alg_note_ptr note = (Alg_note_ptr) evt; Alg_note_ptr note = (Alg_note_ptr) evt;
// if the note's channel is visible // 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 xx = note->time + track->GetOffset();
double x1 = xx + note->dur; double x1 = xx + note->dur;
if (xx < h1 && x1 > h) { // omit if outside box if (xx < h1 && x1 > h) { // omit if outside box

View File

@ -5179,7 +5179,6 @@ void TrackPanel::HandleLabelClick(wxMouseEvent & event)
// DM: If it's a NoteTrack, it has special controls // DM: If it's a NoteTrack, it has special controls
else if (t->GetKind() == Track::Note) else if (t->GetKind() == Track::Note)
{ {
wxRect midiRect;
#ifdef EXPERIMENTAL_MIDI_OUT #ifdef EXPERIMENTAL_MIDI_OUT
if (isleft && (MuteSoloFunc(t, rect, event.m_x, event.m_y, false) || if (isleft && (MuteSoloFunc(t, rect, event.m_x, event.m_y, false) ||
MuteSoloFunc(t, rect, event.m_x, event.m_y, true))) 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)) if (isleft && VelocityFunc(t, rect, event, event.m_x, event.m_y))
return; return;
#endif #endif
mTrackInfo.GetTrackControlsRect(rect, midiRect); wxRect midiRect;
if (midiRect.Contains(event.m_x, event.m_y) && mTrackInfo.GetMidiControlsRect(rect, midiRect);
((NoteTrack *)t)->LabelClick(midiRect, event.m_x, event.m_y,
event.Button(wxMOUSE_BTN_RIGHT))) { bool isright = event.Button(wxMOUSE_BTN_RIGHT);
if ((isleft || isright) && midiRect.Contains(event.m_x, event.m_y) &&
static_cast<NoteTrack *>(t)->LabelClick(midiRect, event.m_x, event.m_y, isright)) {
Refresh(false); Refresh(false);
return; return;
} }
@ -7314,13 +7316,10 @@ void TrackPanel::DrawOutside(Track * t, wxDC * dc, const wxRect & rec,
#ifdef EXPERIMENTAL_MIDI_OUT #ifdef EXPERIMENTAL_MIDI_OUT
else if (bIsNote) { else if (bIsNote) {
wxRect midiRect; wxRect midiRect;
mTrackInfo.GetTrackControlsRect(trackRect, midiRect); mTrackInfo.GetMidiControlsRect(rect, 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);
if (midiRect.y + midiRect.height < rect.y + rect.height - 19)
static_cast<NoteTrack *>(t)->DrawLabelControls(*dc, midiRect);
// Draw some lines for MuteSolo buttons (normally handled by DrawBordersWithin but not done for note tracks) // Draw some lines for MuteSolo buttons (normally handled by DrawBordersWithin but not done for note tracks)
if (rect.height > 48) { if (rect.height > 48) {
// Note: offset up by 34 units // Note: offset up by 34 units
@ -9283,6 +9282,15 @@ void TrackInfo::GetSyncLockIconRect(const wxRect & rect, wxRect &dest) const
dest.height = kTrackInfoBtnSize; 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'. /// \todo Probably should move to 'Utils.cpp'.
void TrackInfo::SetTrackInfoFont(wxDC * dc) const void TrackInfo::SetTrackInfoFont(wxDC * dc) const
@ -9354,22 +9362,7 @@ void TrackInfo::DrawBackground(wxDC * dc, const wxRect & rect, bool bSelected,
#endif #endif
} }
void TrackInfo::GetTrackControlsRect(const wxRect & rect, wxRect & dest) const void TrackInfo::DrawCloseBox(wxDC * dc, const wxRect & rect, Track * t, bool down) 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
{ {
wxRect bev; wxRect bev;
GetCloseBoxRect(rect, bev); GetCloseBoxRect(rect, bev);

View File

@ -114,7 +114,6 @@ private:
// Draw the minimize button *and* the sync-lock track icon, if necessary. // 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 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 GetCloseBoxRect(const wxRect & rect, wxRect &dest) const;
void GetTitleBarRect(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, void GetMuteSoloRect(const wxRect & rect, wxRect &dest, bool solo, bool bHasSoloButton,
@ -126,6 +125,9 @@ private:
#endif #endif
void GetMinimizeRect(const wxRect & rect, wxRect &dest) const; void GetMinimizeRect(const wxRect & rect, wxRect &dest) const;
void GetSyncLockIconRect(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: public:
LWSlider * GainSlider(WaveTrack *t, bool captured = false) const; LWSlider * GainSlider(WaveTrack *t, bool captured = false) const;
@ -881,6 +883,13 @@ enum : int {
kTrackInfoBtnSize = 18 // widely used dimension, usually height kTrackInfoBtnSize = 18 // widely used dimension, usually height
}; };
#ifdef USE_MIDI
enum : int {
kMidiCellWidth = (kTrackInfoWidth / 4) - 2,
kMidiCellHeight = kTrackInfoBtnSize
};
#endif
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma warning( pop ) #pragma warning( pop )
#endif #endif