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:
parent
c07caade9f
commit
a76ad22c91
@ -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())) ||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user