1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-23 15:50:05 +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
// 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())) ||

View File

@ -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)

View File

@ -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<Alg_seq> &&seq);
Alg_seq* GetSequence();
void PrintSequence();
int GetVisibleChannels();
Alg_seq *MakeExportableSeq(std::unique_ptr<Alg_seq> &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<Alg_seq> mSeq; // NULL means no sequence
// 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();
}
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

View File

@ -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<NoteTrack *>(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<NoteTrack *>(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);

View File

@ -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