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