mirror of
https://github.com/cookiengineer/audacity
synced 2025-12-17 06:01:12 +01:00
This change is believed to be a direct refactoring that does not change functionality. It paves the way for more complex kinds of selection, such as selections involving frequency as well as time. It also reduces risk of left and right edges being swapped in future code using SelectedRegion, as the default is to swap on assignment if needed.
1757 lines
57 KiB
C++
1757 lines
57 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
MixerBoard.cpp
|
|
|
|
Vaughan Johnson, January 2007
|
|
Dominic Mazzoni
|
|
|
|
**********************************************************************/
|
|
|
|
#include "Audacity.h"
|
|
|
|
#include "Experimental.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include <wx/dcmemory.h>
|
|
#include <wx/arrimpl.cpp>
|
|
#include <wx/settings.h> // for wxSystemSettings::GetColour and wxSystemSettings::GetMetric
|
|
|
|
#include "AColor.h"
|
|
#include "MixerBoard.h"
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
#include "NoteTrack.h"
|
|
#endif
|
|
#include "Project.h"
|
|
#include "Track.h"
|
|
|
|
|
|
#include "../images/MusicalInstruments.h"
|
|
#ifdef __WXMSW__
|
|
#include "../images/AudacityLogo.xpm"
|
|
#else
|
|
#include "../images/AudacityLogo48x48.xpm"
|
|
#endif
|
|
|
|
|
|
// class MixerTrackSlider
|
|
|
|
BEGIN_EVENT_TABLE(MixerTrackSlider, ASlider)
|
|
EVT_MOUSE_EVENTS(MixerTrackSlider::OnMouseEvent)
|
|
|
|
EVT_SET_FOCUS(MixerTrackSlider::OnFocus)
|
|
EVT_KILL_FOCUS(MixerTrackSlider::OnFocus)
|
|
EVT_COMMAND(wxID_ANY, EVT_CAPTURE_KEY, MixerTrackSlider::OnCaptureKey)
|
|
|
|
END_EVENT_TABLE()
|
|
|
|
MixerTrackSlider::MixerTrackSlider(wxWindow * parent,
|
|
wxWindowID id,
|
|
wxString name,
|
|
const wxPoint & pos,
|
|
const wxSize & size,
|
|
int style /*= FRAC_SLIDER*/,
|
|
bool popup /*= true*/,
|
|
bool canUseShift /*= true*/,
|
|
float stepValue /*= STEP_CONTINUOUS*/,
|
|
int orientation /*= wxHORIZONTAL*/)
|
|
: ASlider(parent, id, name, pos, size,
|
|
style, popup, canUseShift, stepValue, orientation)
|
|
{
|
|
}
|
|
|
|
void MixerTrackSlider::OnMouseEvent(wxMouseEvent &event)
|
|
{
|
|
ASlider::OnMouseEvent(event);
|
|
|
|
if (event.ButtonUp())
|
|
{
|
|
MixerTrackCluster* pMixerTrackCluster = (MixerTrackCluster*)(this->GetParent());
|
|
switch (mStyle)
|
|
{
|
|
case DB_SLIDER: pMixerTrackCluster->HandleSliderGain(true); break;
|
|
case PAN_SLIDER: pMixerTrackCluster->HandleSliderPan(true); break;
|
|
default: break; // no-op
|
|
}
|
|
}
|
|
}
|
|
|
|
void MixerTrackSlider::OnFocus(wxFocusEvent &event)
|
|
{
|
|
wxCommandEvent e(EVT_CAPTURE_KEYBOARD);
|
|
|
|
if (event.GetEventType() == wxEVT_KILL_FOCUS) {
|
|
e.SetEventType(EVT_RELEASE_KEYBOARD);
|
|
}
|
|
e.SetEventObject(this);
|
|
GetParent()->GetEventHandler()->ProcessEvent(e);
|
|
|
|
Refresh(false);
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void MixerTrackSlider::OnCaptureKey(wxCommandEvent &event)
|
|
{
|
|
wxKeyEvent *kevent = (wxKeyEvent *)event.GetEventObject();
|
|
int keyCode = kevent->GetKeyCode();
|
|
|
|
// Pass LEFT/RIGHT/UP/DOWN/PAGEUP/PAGEDOWN through for input/output sliders
|
|
if (keyCode == WXK_LEFT || keyCode == WXK_RIGHT ||
|
|
keyCode == WXK_UP || keyCode == WXK_DOWN ||
|
|
keyCode == WXK_PAGEUP || keyCode == WXK_PAGEDOWN) {
|
|
return;
|
|
}
|
|
|
|
event.Skip();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
// class MixerTrackCluster
|
|
|
|
#define kInset 4
|
|
#define kDoubleInset (2 * kInset)
|
|
#define kTripleInset (3 * kInset)
|
|
#define kQuadrupleInset (4 * kInset)
|
|
|
|
#define TRACK_NAME_HEIGHT 18
|
|
#define MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH 48
|
|
#define MUTE_SOLO_HEIGHT 16
|
|
#define PAN_HEIGHT 24
|
|
|
|
#define kLeftSideStackWidth MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH - kDoubleInset //vvv Change when numbers shown on slider scale.
|
|
#define kRightSideStackWidth MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH + kDoubleInset
|
|
#define kMixerTrackClusterWidth kLeftSideStackWidth + kRightSideStackWidth + kQuadrupleInset // kDoubleInset margin on both sides
|
|
|
|
enum {
|
|
ID_BITMAPBUTTON_MUSICAL_INSTRUMENT = 13000,
|
|
ID_SLIDER_PAN,
|
|
ID_SLIDER_GAIN,
|
|
ID_TOGGLEBUTTON_MUTE,
|
|
ID_TOGGLEBUTTON_SOLO,
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE(MixerTrackCluster, wxPanel)
|
|
EVT_CHAR(MixerTrackCluster::OnKeyEvent)
|
|
EVT_MOUSE_EVENTS(MixerTrackCluster::OnMouseEvent)
|
|
EVT_PAINT(MixerTrackCluster::OnPaint)
|
|
|
|
EVT_BUTTON(ID_BITMAPBUTTON_MUSICAL_INSTRUMENT, MixerTrackCluster::OnButton_MusicalInstrument)
|
|
EVT_SLIDER(ID_SLIDER_PAN, MixerTrackCluster::OnSlider_Pan)
|
|
EVT_SLIDER(ID_SLIDER_GAIN, MixerTrackCluster::OnSlider_Gain)
|
|
//v EVT_COMMAND_SCROLL(ID_SLIDER_GAIN, MixerTrackCluster::OnSliderScroll_Gain)
|
|
EVT_COMMAND(ID_TOGGLEBUTTON_MUTE, wxEVT_COMMAND_BUTTON_CLICKED, MixerTrackCluster::OnButton_Mute)
|
|
EVT_COMMAND(ID_TOGGLEBUTTON_SOLO, wxEVT_COMMAND_BUTTON_CLICKED, MixerTrackCluster::OnButton_Solo)
|
|
END_EVENT_TABLE()
|
|
|
|
MixerTrackCluster::MixerTrackCluster(wxWindow* parent,
|
|
MixerBoard* grandParent, AudacityProject* project,
|
|
WaveTrack* pLeftTrack, WaveTrack* pRightTrack /*= NULL*/,
|
|
const wxPoint& pos /*= wxDefaultPosition*/,
|
|
const wxSize& size /*= wxDefaultSize*/)
|
|
: wxPanel(parent, -1, pos, size)
|
|
{
|
|
mMixerBoard = grandParent;
|
|
mProject = project;
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
if (pLeftTrack->GetKind() == Track::Note) {
|
|
mLeftTrack = NULL;
|
|
mNoteTrack = (NoteTrack*) pLeftTrack;
|
|
mTrack = pLeftTrack;
|
|
} else {
|
|
wxASSERT(pLeftTrack->GetKind() == Track::Wave);
|
|
mTrack = mLeftTrack = pLeftTrack;
|
|
mNoteTrack = NULL;
|
|
}
|
|
#else
|
|
wxASSERT(pLeftTrack->GetKind() == Track::Wave);
|
|
mLeftTrack = pLeftTrack;
|
|
#endif
|
|
mRightTrack = pRightTrack;
|
|
|
|
SetName(mLeftTrack->GetName());
|
|
|
|
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
|
|
|
|
// Not sure why, but sizers weren't getting offset vertically,
|
|
// probably because not using wxDefaultPosition,
|
|
// so positions are calculated explicitly below, and sizers code was removed.
|
|
// (Still available in Audacity_UmixIt branch off 1.2.6.)
|
|
|
|
// track name
|
|
wxPoint ctrlPos(kDoubleInset, kDoubleInset);
|
|
wxSize ctrlSize(size.GetWidth() - kQuadrupleInset, TRACK_NAME_HEIGHT);
|
|
mStaticText_TrackName =
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
new wxStaticText(this, -1, mTrack->GetName(), ctrlPos, ctrlSize,
|
|
wxALIGN_CENTRE | wxST_NO_AUTORESIZE | wxSUNKEN_BORDER);
|
|
#else
|
|
new wxStaticText(this, -1, mLeftTrack->GetName(), ctrlPos, ctrlSize,
|
|
wxALIGN_CENTRE | 0x0001 | wxBORDER_SUNKEN);
|
|
#endif
|
|
//v Useful when different tracks are different colors, but not now.
|
|
// mStaticText_TrackName->SetBackgroundColour(this->GetTrackColor());
|
|
|
|
|
|
// gain slider at left
|
|
ctrlPos.x = kDoubleInset;
|
|
ctrlPos.y += TRACK_NAME_HEIGHT + kDoubleInset;
|
|
const int nGainSliderHeight =
|
|
size.GetHeight() - ctrlPos.y - kQuadrupleInset;
|
|
ctrlSize.Set(kLeftSideStackWidth - kQuadrupleInset, nGainSliderHeight);
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
if (mNoteTrack) {
|
|
mSlider_Gain =
|
|
new MixerTrackSlider(
|
|
this, ID_SLIDER_GAIN,
|
|
/* i18n-hint: title of the MIDI Velocity slider */
|
|
_("Velocity"),
|
|
ctrlPos, ctrlSize, VEL_SLIDER, true,
|
|
true, 0.0, wxVERTICAL);
|
|
} else
|
|
#endif
|
|
mSlider_Gain =
|
|
new MixerTrackSlider(
|
|
this, ID_SLIDER_GAIN,
|
|
/* i18n-hint: title of the Gain slider, used to adjust the volume */
|
|
_("Gain"),
|
|
ctrlPos, ctrlSize, DB_SLIDER, true,
|
|
true, 0.0, wxVERTICAL);
|
|
mSlider_Gain->SetName(_("Gain"));
|
|
|
|
this->UpdateGain();
|
|
|
|
|
|
// other controls and meter at right
|
|
|
|
// musical instrument image
|
|
ctrlPos.x += kLeftSideStackWidth + kInset; // + kInset to center it in right side stack
|
|
ctrlSize.Set(MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH, MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH);
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
wxBitmap* bitmap = mMixerBoard->GetMusicalInstrumentBitmap(mTrack->GetName());
|
|
#else
|
|
wxBitmap* bitmap = mMixerBoard->GetMusicalInstrumentBitmap(mLeftTrack);
|
|
#endif
|
|
wxASSERT(bitmap);
|
|
mBitmapButton_MusicalInstrument =
|
|
new wxBitmapButton(this, ID_BITMAPBUTTON_MUSICAL_INSTRUMENT, *bitmap,
|
|
ctrlPos, ctrlSize,
|
|
wxBU_AUTODRAW, wxDefaultValidator,
|
|
_("Musical Instrument"));
|
|
mBitmapButton_MusicalInstrument->SetName(_("Musical Instrument"));
|
|
|
|
|
|
// pan slider
|
|
ctrlPos.x -= kInset; // Remove inset for instrument, so Pan is at leftmost of left side stack.
|
|
ctrlPos.y += MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH + kDoubleInset;
|
|
ctrlSize.Set(kRightSideStackWidth, PAN_HEIGHT);
|
|
|
|
// The width of the pan slider must be odd (don't ask).
|
|
if (!(ctrlSize.x & 1))
|
|
ctrlSize.x--;
|
|
|
|
mSlider_Pan =
|
|
new MixerTrackSlider(
|
|
this, ID_SLIDER_PAN,
|
|
/* i18n-hint: Title of the Pan slider, used to move the sound left or right */
|
|
_("Pan"),
|
|
ctrlPos, ctrlSize, PAN_SLIDER, true);
|
|
mSlider_Pan->SetName(_("Pan"));
|
|
|
|
this->UpdatePan();
|
|
|
|
// mute/solo buttons stacked below Pan slider
|
|
ctrlPos.y += PAN_HEIGHT + kDoubleInset;
|
|
ctrlSize.Set(mMixerBoard->mMuteSoloWidth, MUTE_SOLO_HEIGHT);
|
|
mToggleButton_Mute =
|
|
new AButton(this, ID_TOGGLEBUTTON_MUTE,
|
|
ctrlPos, ctrlSize,
|
|
*(mMixerBoard->mImageMuteUp), *(mMixerBoard->mImageMuteOver),
|
|
*(mMixerBoard->mImageMuteDown), *(mMixerBoard->mImageMuteDisabled),
|
|
true); // toggle button
|
|
mToggleButton_Mute->SetName(_("Mute"));
|
|
mToggleButton_Mute->SetAlternateImages(
|
|
*(mMixerBoard->mImageMuteUp), *(mMixerBoard->mImageMuteOver),
|
|
*(mMixerBoard->mImageMuteDown), *(mMixerBoard->mImageMuteDisabled));
|
|
this->UpdateMute();
|
|
|
|
ctrlPos.y += MUTE_SOLO_HEIGHT;
|
|
mToggleButton_Solo =
|
|
new AButton(this, ID_TOGGLEBUTTON_SOLO,
|
|
ctrlPos, ctrlSize,
|
|
*(mMixerBoard->mImageSoloUp), *(mMixerBoard->mImageSoloOver),
|
|
*(mMixerBoard->mImageSoloDown), *(mMixerBoard->mImageSoloDisabled),
|
|
true); // toggle button
|
|
mToggleButton_Solo->SetName(_("Solo"));
|
|
this->UpdateSolo();
|
|
bool bSoloNone = mProject->IsSoloNone();
|
|
mToggleButton_Solo->Show(!bSoloNone);
|
|
|
|
|
|
// meter
|
|
ctrlPos.y += (bSoloNone ? 0 : MUTE_SOLO_HEIGHT) + kDoubleInset;
|
|
const int nMeterHeight =
|
|
nGainSliderHeight -
|
|
(MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH + kDoubleInset) -
|
|
(PAN_HEIGHT + kDoubleInset) -
|
|
(MUTE_SOLO_HEIGHT + (bSoloNone ? 0 : MUTE_SOLO_HEIGHT) + kDoubleInset);
|
|
ctrlSize.Set(kRightSideStackWidth, nMeterHeight);
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
if (mLeftTrack) {
|
|
#endif
|
|
mMeter =
|
|
new Meter(this, -1, // wxWindow* parent, wxWindowID id,
|
|
false, // bool isInput
|
|
ctrlPos, ctrlSize, // const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize,
|
|
Meter::MixerTrackCluster); // Style style = HorizontalStereo,
|
|
mMeter->SetName(_("Signal Level Meter"));
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
} else {
|
|
mMeter = NULL;
|
|
}
|
|
#endif
|
|
|
|
#if wxUSE_TOOLTIPS
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
mStaticText_TrackName->SetToolTip(mTrack->GetName());
|
|
#else
|
|
mStaticText_TrackName->SetToolTip(mLeftTrack->GetName());
|
|
#endif
|
|
mToggleButton_Mute->SetToolTip(_("Mute"));
|
|
mToggleButton_Solo->SetToolTip(_("Solo"));
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
if (mLeftTrack)
|
|
#endif
|
|
mMeter->SetToolTip(_("Signal Level Meter"));
|
|
#endif // wxUSE_TOOLTIPS
|
|
|
|
#ifdef __WXMAC__
|
|
wxSizeEvent dummyEvent;
|
|
this->OnSize(dummyEvent);
|
|
UpdateGain();
|
|
#endif
|
|
}
|
|
|
|
void MixerTrackCluster::HandleResize() // For wxSizeEvents, update gain slider and meter.
|
|
{
|
|
wxSize scrolledWindowClientSize = this->GetParent()->GetClientSize();
|
|
const int newClusterHeight =
|
|
scrolledWindowClientSize.GetHeight() - kDoubleInset - // nClusterHeight from MixerBoard::UpdateTrackClusters
|
|
wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y) + // wxScrolledWindow::GetClientSize doesn't account for its scrollbar size.
|
|
kDoubleInset;
|
|
|
|
this->SetSize(-1, newClusterHeight);
|
|
|
|
// Change only the heights of mSlider_Gain and mMeter.
|
|
// But update shown status of mToggleButton_Solo, which affects top of mMeter.
|
|
const int nGainSliderHeight =
|
|
newClusterHeight -
|
|
(kInset + // margin above mStaticText_TrackName
|
|
TRACK_NAME_HEIGHT + kDoubleInset) - // mStaticText_TrackName + margin
|
|
kQuadrupleInset; // margin below gain slider
|
|
mSlider_Gain->SetSize(-1, nGainSliderHeight);
|
|
|
|
bool bSoloNone = mProject->IsSoloNone();
|
|
|
|
mToggleButton_Solo->Show(!bSoloNone);
|
|
|
|
const int nRequiredHeightAboveMeter =
|
|
MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH + kDoubleInset +
|
|
PAN_HEIGHT + kDoubleInset +
|
|
MUTE_SOLO_HEIGHT + (bSoloNone ? 0 : MUTE_SOLO_HEIGHT) + kDoubleInset;
|
|
const int nMeterY =
|
|
kDoubleInset + // margin at top
|
|
TRACK_NAME_HEIGHT + kDoubleInset +
|
|
nRequiredHeightAboveMeter;
|
|
const int nMeterHeight = nGainSliderHeight - nRequiredHeightAboveMeter;
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
if (mMeter)
|
|
#endif
|
|
mMeter->SetSize(-1, nMeterY, -1, nMeterHeight);
|
|
}
|
|
|
|
void MixerTrackCluster::HandleSliderGain(const bool bWantPushState /*= false*/)
|
|
{
|
|
float fValue = mSlider_Gain->Get();
|
|
if (mLeftTrack)
|
|
mLeftTrack->SetGain(fValue);
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
else
|
|
mNoteTrack->SetGain(fValue);
|
|
#endif
|
|
if (mRightTrack)
|
|
mRightTrack->SetGain(fValue);
|
|
|
|
// Update the TrackPanel correspondingly.
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
mProject->RefreshTPTrack(mTrack);
|
|
#else
|
|
mProject->RefreshTPTrack(mLeftTrack);
|
|
#endif
|
|
if (bWantPushState)
|
|
mProject->TP_PushState(_("Moved gain slider"), _("Gain"), PUSH_CONSOLIDATE );
|
|
}
|
|
|
|
void MixerTrackCluster::HandleSliderPan(const bool bWantPushState /*= false*/)
|
|
{
|
|
float fValue = mSlider_Pan->Get();
|
|
if (mLeftTrack) // test in case track is a NoteTrack
|
|
mLeftTrack->SetPan(fValue);
|
|
if (mRightTrack)
|
|
mRightTrack->SetPan(fValue);
|
|
|
|
// Update the TrackPanel correspondingly.
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
mProject->RefreshTPTrack(mTrack);
|
|
#else
|
|
mProject->RefreshTPTrack(mLeftTrack);
|
|
#endif
|
|
|
|
if (bWantPushState)
|
|
mProject->TP_PushState(_("Moved pan slider"), _("Pan"), PUSH_CONSOLIDATE );
|
|
}
|
|
|
|
void MixerTrackCluster::ResetMeter(const bool bResetClipping)
|
|
{
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
if (mMeter)
|
|
#endif
|
|
mMeter->Reset(mLeftTrack->GetRate(), bResetClipping);
|
|
}
|
|
|
|
|
|
// These are used by TrackPanel for synchronizing control states, etc.
|
|
|
|
// Update the controls that can be affected by state change.
|
|
void MixerTrackCluster::UpdateForStateChange()
|
|
{
|
|
this->UpdateName();
|
|
this->UpdatePan();
|
|
this->UpdateGain();
|
|
}
|
|
|
|
void MixerTrackCluster::UpdateName()
|
|
{
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
const wxString newName = mTrack->GetName();
|
|
#else
|
|
const wxString newName = mLeftTrack->GetName();
|
|
#endif
|
|
SetName(newName);
|
|
mStaticText_TrackName->SetLabel(newName);
|
|
#if wxUSE_TOOLTIPS
|
|
mStaticText_TrackName->SetToolTip(newName);
|
|
#endif
|
|
mBitmapButton_MusicalInstrument->SetBitmapLabel(
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
*(mMixerBoard->GetMusicalInstrumentBitmap(newName)));
|
|
#else
|
|
*(mMixerBoard->GetMusicalInstrumentBitmap(mLeftTrack)));
|
|
#endif
|
|
}
|
|
|
|
void MixerTrackCluster::UpdateMute()
|
|
{
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
mToggleButton_Mute->SetAlternate(mTrack->GetSolo());
|
|
if (mTrack->GetMute())
|
|
#else
|
|
mToggleButton_Mute->SetAlternate(mLeftTrack->GetSolo());
|
|
if (mLeftTrack->GetMute())
|
|
#endif
|
|
mToggleButton_Mute->PushDown();
|
|
else
|
|
mToggleButton_Mute->PopUp();
|
|
}
|
|
|
|
void MixerTrackCluster::UpdateSolo()
|
|
{
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
bool bIsSolo = mTrack->GetSolo();
|
|
#else
|
|
bool bIsSolo = mLeftTrack->GetSolo();
|
|
#endif
|
|
if (bIsSolo)
|
|
mToggleButton_Solo->PushDown();
|
|
else
|
|
mToggleButton_Solo->PopUp();
|
|
mToggleButton_Mute->SetAlternate(bIsSolo);
|
|
}
|
|
|
|
void MixerTrackCluster::UpdatePan()
|
|
{
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
if (mNoteTrack) {
|
|
mSlider_Pan->Hide();
|
|
return;
|
|
}
|
|
#endif
|
|
mSlider_Pan->Set(mLeftTrack->GetPan());
|
|
}
|
|
|
|
void MixerTrackCluster::UpdateGain()
|
|
{
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
if (mNoteTrack) {
|
|
mSlider_Gain->SetStyle(VEL_SLIDER);
|
|
mSlider_Gain->Set(mNoteTrack->GetGain());
|
|
return;
|
|
}
|
|
mSlider_Gain->SetStyle(DB_SLIDER);
|
|
#endif
|
|
mSlider_Gain->Set(mLeftTrack->GetGain());
|
|
}
|
|
|
|
void MixerTrackCluster::UpdateMeter(const double t0, const double t1)
|
|
{
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
// NoteTracks do not (currently) register on meters. It would probably be
|
|
// a good idea to display 16 channel "active" lights rather than a meter
|
|
if (!mLeftTrack)
|
|
return;
|
|
#else
|
|
wxASSERT(mLeftTrack && (mLeftTrack->GetKind() == Track::Wave));
|
|
#endif
|
|
|
|
//vvv Vaughan, 2010-11-27:
|
|
// NOTE TO ROGER DANNENBERG:
|
|
// I undid the mTrack hack in this conditional, as the rest of the method still assumed it's a wavetrack
|
|
// so dereferencing mLeftTrack would have gotten a NULL pointer fault.
|
|
// I really think MixerTrackCluster should be factored for NoteTracks.
|
|
// REPLY: I think bSuccess prevents dereferencing mLeftTrack, but I will
|
|
// check. We should talk about whether it's better to factor
|
|
// MixerTrackCluster or more fully hide track types from MixerTrackCluster.
|
|
// For now, out change plan produced the following:
|
|
// Vaughan, 2011=10-15: There's no bSuccess here, so I don't know what you mean.
|
|
// But this change is consistent with the others for EXPERIMENTAL_MIDI_OUT, so I accept it.
|
|
if ((t0 < 0.0) || (t1 < 0.0) || (t0 >= t1) || // bad time value or nothing to show
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
((mMixerBoard->HasSolo() || mTrack->GetMute()) && !mTrack->GetSolo()))
|
|
#else
|
|
((mMixerBoard->HasSolo() || mLeftTrack->GetMute()) && !mLeftTrack->GetSolo()))
|
|
#endif
|
|
{
|
|
//v Vaughan, 2011-02-25: Moved the update back to TrackPanel::OnTimer() as it helps with
|
|
// playback issues reported by Bill and noted on Bug 258, so no assert.
|
|
// Vaughan, 2011-02-04: Now that we're updating all meters from audacityAudioCallback,
|
|
// this causes an assert if you click Mute while playing, because ResetMeter() resets
|
|
// the timer, and wxTimerbase says that can only be done from main thread --
|
|
// but it seems to work fine.
|
|
this->ResetMeter(false);
|
|
return;
|
|
}
|
|
|
|
// Vaughan, 2010-11-27:
|
|
// This commented out code is flawed. Mistaken understanding of "frame" vs "window".
|
|
// Caused me to override Meter::UpdateDisplay().
|
|
// But I think it's got a good idea, of calling WaveTracks' GetMinMax and GetRMS
|
|
// instead of passing in all the data and asking the meter to derive peak and rms.
|
|
// May be worth revisiting as I think it should perform better, because it uses the min/max/rms
|
|
// stored in blockfiles, rather than calculating them, but for now, changing it to use the
|
|
// original Meter::UpdateDisplay(). New code is below the previous (now commented out).
|
|
//
|
|
//const int kFramesPerBuffer = 4;
|
|
//float min; // dummy, since it's not shown in meters
|
|
//float* maxLeft = new float[kFramesPerBuffer];
|
|
//float* rmsLeft = new float[kFramesPerBuffer];
|
|
//float* maxRight = new float[kFramesPerBuffer];
|
|
//float* rmsRight = new float[kFramesPerBuffer];
|
|
//
|
|
//#ifdef EXPERIMENTAL_MIDI_OUT
|
|
// bool bSuccess = (mLeftTrack != NULL);
|
|
//#else
|
|
// bool bSuccess = true;
|
|
//#endif
|
|
|
|
//const double dFrameInterval = (t1 - t0) / (double)kFramesPerBuffer;
|
|
//double dFrameT0 = t0;
|
|
//double dFrameT1 = t0 + dFrameInterval;
|
|
//int i = 0;
|
|
//while (bSuccess && (i < kFramesPerBuffer))
|
|
//{
|
|
// bSuccess &=
|
|
// mLeftTrack->GetMinMax(&min, &(maxLeft[i]), dFrameT0, dFrameT1) &&
|
|
// mLeftTrack->GetRMS(&(rmsLeft[i]), dFrameT0, dFrameT1);
|
|
// if (bSuccess && mRightTrack)
|
|
// bSuccess &=
|
|
// mRightTrack->GetMinMax(&min, &(maxRight[i]), dFrameT0, dFrameT1) &&
|
|
// mRightTrack->GetRMS(&(rmsRight[i]), dFrameT0, dFrameT1);
|
|
// else
|
|
// {
|
|
// // Mono: Start with raw values same as left.
|
|
// // To be modified by bWantPostFadeValues and channel pan/gain.
|
|
// maxRight[i] = maxLeft[i];
|
|
// rmsRight[i] = rmsLeft[i];
|
|
// }
|
|
// dFrameT0 += dFrameInterval;
|
|
// dFrameT1 += dFrameInterval;
|
|
// i++;
|
|
//}
|
|
//
|
|
//const bool bWantPostFadeValues = true; //v Turn this into a checkbox on MixerBoard? For now, always true.
|
|
//if (bSuccess && bWantPostFadeValues)
|
|
//if (bSuccess)
|
|
//{
|
|
// for (i = 0; i < kFramesPerBuffer; i++)
|
|
// {
|
|
// float gain = mLeftTrack->GetChannelGain(0);
|
|
// maxLeft[i] *= gain;
|
|
// rmsLeft[i] *= gain;
|
|
// if (mRightTrack)
|
|
// gain = mRightTrack->GetChannelGain(1);
|
|
// maxRight[i] *= gain;
|
|
// rmsRight[i] *= gain;
|
|
// }
|
|
// mMeter->UpdateDisplay(
|
|
// 2, // If mono, show left track values in both meters, as in MeterToolBar, rather than kNumChannels.
|
|
// kFramesPerBuffer,
|
|
// maxLeft, rmsLeft,
|
|
// maxRight, rmsRight,
|
|
// mLeftTrack->TimeToLongSamples(t1 - t0));
|
|
//}
|
|
//
|
|
//delete[] maxLeft;
|
|
//delete[] rmsLeft;
|
|
//delete[] maxRight;
|
|
//delete[] rmsRight;
|
|
|
|
sampleCount startSample = (sampleCount)((mLeftTrack->GetRate() * t0) + 0.5);
|
|
sampleCount nFrames = (sampleCount)((mLeftTrack->GetRate() * (t1 - t0)) + 0.5);
|
|
float* meterFloatsArray = NULL;
|
|
float* tempFloatsArray = new float[nFrames];
|
|
bool bSuccess = mLeftTrack->Get((samplePtr)tempFloatsArray, floatSample, startSample, nFrames);
|
|
if (bSuccess)
|
|
{
|
|
// We always pass a stereo sample array to the meter, as it shows 2 channels.
|
|
// Mono shows same in both meters.
|
|
// Since we're not mixing, need to duplicate same signal for "right" channel in mono case.
|
|
meterFloatsArray = new float[2 * nFrames];
|
|
|
|
// Interleave for stereo. Left/mono first.
|
|
for (int index = 0; index < nFrames; index++)
|
|
meterFloatsArray[2 * index] = tempFloatsArray[index];
|
|
|
|
if (mRightTrack)
|
|
bSuccess = mRightTrack->Get((samplePtr)tempFloatsArray, floatSample, startSample, nFrames);
|
|
|
|
if (bSuccess)
|
|
// Interleave right channel, or duplicate same signal for "right" channel in mono case.
|
|
for (int index = 0; index < nFrames; index++)
|
|
meterFloatsArray[(2 * index) + 1] = tempFloatsArray[index];
|
|
}
|
|
|
|
//const bool bWantPostFadeValues = true; //v Turn this into a checkbox on MixerBoard? For now, always true.
|
|
//if (bSuccess && bWantPostFadeValues)
|
|
if (bSuccess)
|
|
{
|
|
//vvv Need to apply envelope, too? See Mixer::MixSameRate.
|
|
float gain = mLeftTrack->GetChannelGain(0);
|
|
if (gain < 1.0)
|
|
for (int index = 0; index < nFrames; index++)
|
|
meterFloatsArray[2 * index] *= gain;
|
|
if (mRightTrack)
|
|
gain = mRightTrack->GetChannelGain(1);
|
|
else
|
|
gain = mLeftTrack->GetChannelGain(1);
|
|
if (gain < 1.0)
|
|
for (int index = 0; index < nFrames; index++)
|
|
meterFloatsArray[(2 * index) + 1] *= gain;
|
|
// Clip to [-1.0, 1.0] range.
|
|
for (int index = 0; index < 2 * nFrames; index++)
|
|
if (meterFloatsArray[index] < -1.0)
|
|
meterFloatsArray[index] = -1.0;
|
|
else if (meterFloatsArray[index] > 1.0)
|
|
meterFloatsArray[index] = 1.0;
|
|
|
|
mMeter->UpdateDisplay(2, nFrames, meterFloatsArray);
|
|
}
|
|
else
|
|
this->ResetMeter(false);
|
|
|
|
delete[] meterFloatsArray;
|
|
delete[] tempFloatsArray;
|
|
}
|
|
|
|
// private
|
|
|
|
wxColour MixerTrackCluster::GetTrackColor()
|
|
{
|
|
//#if (AUDACITY_BRANDING == BRAND_UMIXIT)
|
|
// return AColor::GetTrackColor((void*)mLeftTrack);
|
|
//#else
|
|
return wxColour(102, 255, 102); // same as Meter playback color
|
|
//#endif
|
|
}
|
|
|
|
|
|
// event handlers
|
|
|
|
void MixerTrackCluster::HandleSelect(const bool bShiftDown)
|
|
{
|
|
if (bShiftDown)
|
|
{
|
|
// ShiftDown => Just toggle selection on this track.
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
bool bSelect = !mTrack->GetSelected();
|
|
mTrack->SetSelected(bSelect);
|
|
#else
|
|
bool bSelect = !mLeftTrack->GetSelected();
|
|
mLeftTrack->SetSelected(bSelect);
|
|
#endif
|
|
if (mRightTrack)
|
|
mRightTrack->SetSelected(bSelect);
|
|
|
|
// Refresh only this MixerTrackCluster and WaveTrack in TrackPanel.
|
|
this->Refresh(true);
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
mProject->RefreshTPTrack(mTrack);
|
|
#else
|
|
mProject->RefreshTPTrack(mLeftTrack);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// exclusive select
|
|
mProject->SelectNone();
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
mTrack->SetSelected(true);
|
|
#else
|
|
mLeftTrack->SetSelected(true);
|
|
#endif
|
|
if (mRightTrack)
|
|
mRightTrack->SetSelected(true);
|
|
|
|
if (mProject->GetSel0() >= mProject->GetSel1())
|
|
{
|
|
// No range previously selected, so use the range of this track.
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
mProject->mViewInfo.selectedRegion.setTimes(
|
|
mTrack->GetOffset(), mTrack->GetEndTime());
|
|
#else
|
|
mProject->mViewInfo.selectedRegion.setTimes(
|
|
mLeftTrack->GetOffset(), mLeftTrack->GetEndTime());
|
|
#endif
|
|
}
|
|
|
|
// Exclusive select, so refresh all MixerTrackClusters.
|
|
// This could just be a call to wxWindow::Refresh, but this is
|
|
// more efficient and when ProjectLogo is shown as background,
|
|
// it's necessary to prevent blinking.
|
|
mMixerBoard->RefreshTrackClusters(false);
|
|
}
|
|
}
|
|
|
|
void MixerTrackCluster::OnKeyEvent(wxKeyEvent & event)
|
|
{
|
|
mProject->HandleKeyDown(event);
|
|
}
|
|
|
|
void MixerTrackCluster::OnMouseEvent(wxMouseEvent& event)
|
|
{
|
|
if (event.ButtonUp())
|
|
this->HandleSelect(event.ShiftDown());
|
|
else
|
|
event.Skip();
|
|
}
|
|
|
|
void MixerTrackCluster::OnPaint(wxPaintEvent & WXUNUSED(event))
|
|
{
|
|
wxPaintDC dc(this);
|
|
|
|
#ifdef __WXMAC__
|
|
// Fill with correct color, not scroller background. Done automatically on Windows.
|
|
AColor::Medium(&dc, false);
|
|
dc.DrawRectangle(this->GetClientRect());
|
|
#endif
|
|
|
|
wxSize clusterSize = this->GetSize();
|
|
wxRect bev(0, 0, clusterSize.GetWidth() - 1, clusterSize.GetHeight() - 1);
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
if (mTrack->GetSelected())
|
|
#else
|
|
if (mLeftTrack->GetSelected())
|
|
#endif
|
|
{
|
|
for (unsigned int i = 0; i < 4; i++) // 4 gives a big bevel, but there were complaints about visibility otherwise.
|
|
{
|
|
bev.Inflate(-1, -1);
|
|
AColor::Bevel(dc, false, bev);
|
|
}
|
|
}
|
|
else
|
|
AColor::Bevel(dc, true, bev);
|
|
}
|
|
|
|
|
|
void MixerTrackCluster::OnButton_MusicalInstrument(wxCommandEvent& WXUNUSED(event))
|
|
{
|
|
bool bShiftDown = ::wxGetMouseState().ShiftDown();
|
|
this->HandleSelect(bShiftDown);
|
|
}
|
|
|
|
void MixerTrackCluster::OnSlider_Gain(wxCommandEvent& WXUNUSED(event))
|
|
{
|
|
this->HandleSliderGain();
|
|
}
|
|
|
|
//v void MixerTrackCluster::OnSliderScroll_Gain(wxScrollEvent& WXUNUSED(event))
|
|
//{
|
|
//int sliderValue = (int)(mSlider_Gain->Get()); //v mSlider_Gain->GetValue();
|
|
//#ifdef __WXMSW__
|
|
// // Negate because wxSlider on Windows has min at top, max at bottom.
|
|
// // mSlider_Gain->GetValue() is in [-6,36]. wxSlider has min at top, so this is [-36dB,6dB].
|
|
// sliderValue = -sliderValue;
|
|
//#endif
|
|
//wxString str = _("Gain: ");
|
|
//if (sliderValue > 0)
|
|
// str += "+";
|
|
//str += wxString::Format("%d dB", sliderValue);
|
|
//mSlider_Gain->SetToolTip(str);
|
|
//}
|
|
|
|
void MixerTrackCluster::OnSlider_Pan(wxCommandEvent& WXUNUSED(event))
|
|
{
|
|
this->HandleSliderPan();
|
|
}
|
|
|
|
void MixerTrackCluster::OnButton_Mute(wxCommandEvent& WXUNUSED(event))
|
|
{
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
mProject->HandleTrackMute(mTrack, mToggleButton_Mute->WasShiftDown());
|
|
mToggleButton_Mute->SetAlternate(mTrack->GetSolo());
|
|
#else
|
|
mProject->HandleTrackMute(mLeftTrack, mToggleButton_Mute->WasShiftDown());
|
|
mToggleButton_Mute->SetAlternate(mLeftTrack->GetSolo());
|
|
#endif
|
|
|
|
// Update the TrackPanel correspondingly.
|
|
if (mProject->IsSoloSimple())
|
|
{
|
|
// Have to refresh all tracks.
|
|
mMixerBoard->UpdateSolo();
|
|
mProject->RedrawProject();
|
|
}
|
|
else
|
|
// Update only the changed track.
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
mProject->RefreshTPTrack(mTrack);
|
|
#else
|
|
mProject->RefreshTPTrack(mLeftTrack);
|
|
#endif
|
|
}
|
|
|
|
void MixerTrackCluster::OnButton_Solo(wxCommandEvent& WXUNUSED(event))
|
|
{
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
mProject->HandleTrackSolo(mTrack, mToggleButton_Solo->WasShiftDown());
|
|
bool bIsSolo = mTrack->GetSolo();
|
|
#else
|
|
mProject->HandleTrackSolo(mLeftTrack, mToggleButton_Solo->WasShiftDown());
|
|
bool bIsSolo = mLeftTrack->GetSolo();
|
|
#endif
|
|
mToggleButton_Mute->SetAlternate(bIsSolo);
|
|
|
|
// Update the TrackPanel correspondingly.
|
|
if (mProject->IsSoloSimple())
|
|
{
|
|
// Have to refresh all tracks.
|
|
mMixerBoard->UpdateMute();
|
|
mMixerBoard->UpdateSolo();
|
|
mProject->RedrawProject();
|
|
}
|
|
else
|
|
// Update only the changed track.
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
mProject->RefreshTPTrack(mTrack);
|
|
#else
|
|
mProject->RefreshTPTrack(mLeftTrack);
|
|
#endif
|
|
}
|
|
|
|
|
|
// class MusicalInstrument
|
|
|
|
MusicalInstrument::MusicalInstrument(wxBitmap* pBitmap, const wxString strXPMfilename)
|
|
{
|
|
mBitmap = pBitmap;
|
|
|
|
int nUnderscoreIndex;
|
|
wxString strFilename = strXPMfilename;
|
|
strFilename.MakeLower(); // Make sure, so we don't have to do case insensitive comparison.
|
|
wxString strKeyword;
|
|
while ((nUnderscoreIndex = strFilename.Find(wxT('_'))) != -1)
|
|
{
|
|
strKeyword = strFilename.Left(nUnderscoreIndex);
|
|
mKeywords.Add(strKeyword);
|
|
strFilename = strFilename.Mid(nUnderscoreIndex + 1);
|
|
}
|
|
if (!strFilename.IsEmpty()) // Skip trailing underscores.
|
|
mKeywords.Add(strFilename); // Add the last one.
|
|
}
|
|
|
|
MusicalInstrument::~MusicalInstrument()
|
|
{
|
|
delete mBitmap;
|
|
mKeywords.Clear();
|
|
}
|
|
|
|
WX_DEFINE_OBJARRAY(MusicalInstrumentArray);
|
|
|
|
|
|
// class MixerBoardScrolledWindow
|
|
|
|
// wxScrolledWindow ignores mouse clicks in client area,
|
|
// but they don't get passed to Mixerboard.
|
|
// We need to catch them to deselect all track clusters.
|
|
|
|
BEGIN_EVENT_TABLE(MixerBoardScrolledWindow, wxScrolledWindow)
|
|
EVT_MOUSE_EVENTS(MixerBoardScrolledWindow::OnMouseEvent)
|
|
END_EVENT_TABLE()
|
|
|
|
MixerBoardScrolledWindow::MixerBoardScrolledWindow(AudacityProject* project,
|
|
MixerBoard* parent, wxWindowID id /*= -1*/,
|
|
const wxPoint& pos /*= wxDefaultPosition*/,
|
|
const wxSize& size /*= wxDefaultSize*/,
|
|
long style /*= wxHSCROLL | wxVSCROLL*/)
|
|
: wxScrolledWindow(parent, id, pos, size, style)
|
|
{
|
|
mMixerBoard = parent;
|
|
mProject = project;
|
|
}
|
|
|
|
MixerBoardScrolledWindow::~MixerBoardScrolledWindow()
|
|
{
|
|
}
|
|
|
|
void MixerBoardScrolledWindow::OnMouseEvent(wxMouseEvent& event)
|
|
{
|
|
if (event.ButtonUp())
|
|
{
|
|
//v Even when I implement MixerBoard::OnMouseEvent and call event.Skip()
|
|
// here, MixerBoard::OnMouseEvent never gets called.
|
|
// So, added mProject to MixerBoardScrolledWindow and just directly do what's needed here.
|
|
mProject->SelectNone();
|
|
}
|
|
else
|
|
event.Skip();
|
|
}
|
|
|
|
|
|
// class MixerBoard
|
|
|
|
#define MIXER_BOARD_MIN_HEIGHT 460
|
|
|
|
// Min width is one cluster wide, plus margins.
|
|
#define MIXER_BOARD_MIN_WIDTH kTripleInset + kMixerTrackClusterWidth + kTripleInset
|
|
|
|
|
|
BEGIN_EVENT_TABLE(MixerBoard, wxWindow)
|
|
EVT_SIZE(MixerBoard::OnSize)
|
|
END_EVENT_TABLE()
|
|
|
|
MixerBoard::MixerBoard(AudacityProject* pProject,
|
|
wxFrame* parent,
|
|
const wxPoint& pos /*= wxDefaultPosition*/,
|
|
const wxSize& size /*= wxDefaultSize*/)
|
|
: wxWindow(parent, -1, pos, size)
|
|
{
|
|
// public data members
|
|
|
|
// mute & solo button images
|
|
// Create once and store on MixerBoard for use in all MixerTrackClusters.
|
|
mImageMuteUp = NULL;
|
|
mImageMuteOver = NULL;
|
|
mImageMuteDown = NULL;
|
|
mImageMuteDownWhileSolo = NULL;
|
|
mImageMuteDisabled = NULL;
|
|
mImageSoloUp = NULL;
|
|
mImageSoloOver = NULL;
|
|
mImageSoloDown = NULL;
|
|
mImageSoloDisabled = NULL;
|
|
|
|
mMuteSoloWidth = kRightSideStackWidth - kInset; // correct for max width, but really set in MixerBoard::CreateMuteSoloImages
|
|
|
|
// private data members
|
|
this->LoadMusicalInstruments(); // Set up mMusicalInstruments.
|
|
mProject = pProject;
|
|
|
|
mScrolledWindow =
|
|
new MixerBoardScrolledWindow(
|
|
pProject, // AudacityProject* project,
|
|
this, -1, // wxWindow* parent, wxWindowID id = -1,
|
|
this->GetClientAreaOrigin(), // const wxPoint& pos = wxDefaultPosition,
|
|
size, // const wxSize& size = wxDefaultSize,
|
|
wxHSCROLL); // long style = wxHSCROLL | wxVSCROLL, const wxString& name = "scrolledWindow")
|
|
|
|
// Set background color to same as TrackPanel background.
|
|
#ifdef EXPERIMENTAL_THEMING
|
|
mScrolledWindow->SetBackgroundColour(this->GetParent()->GetBackgroundColour());
|
|
#else
|
|
mScrolledWindow->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
|
|
#endif
|
|
|
|
mScrolledWindow->SetScrollRate(10, 0); // no vertical scroll
|
|
mScrolledWindow->SetVirtualSize(size);
|
|
|
|
/* This doesn't work to make the mScrolledWindow automatically resize, so do it explicitly in OnSize.
|
|
wxBoxSizer* pBoxSizer = new wxBoxSizer(wxVERTICAL);
|
|
pBoxSizer->Add(mScrolledWindow, 0, wxExpand, 0);
|
|
this->SetAutoLayout(true);
|
|
this->SetSizer(pBoxSizer);
|
|
pBoxSizer->Fit(this);
|
|
pBoxSizer->SetSizeHints(this);
|
|
*/
|
|
|
|
mPrevT1 = 0.0;
|
|
mTracks = mProject->GetTracks();
|
|
}
|
|
|
|
MixerBoard::~MixerBoard()
|
|
{
|
|
// public data members
|
|
delete mImageMuteUp;
|
|
delete mImageMuteOver;
|
|
delete mImageMuteDown;
|
|
delete mImageMuteDownWhileSolo;
|
|
delete mImageMuteDisabled;
|
|
delete mImageSoloUp;
|
|
delete mImageSoloOver;
|
|
delete mImageSoloDown;
|
|
delete mImageSoloDisabled;
|
|
|
|
// private data members
|
|
mMusicalInstruments.Clear();
|
|
}
|
|
|
|
// Reassign mixer input strips (MixerTrackClusters) to Track Clusters
|
|
// both have the same order.
|
|
// If EXPERIMENTAL_MIDI_OUT, then Note Tracks appear in the
|
|
// mixer, and we must be able to convert and reuse a MixerTrackCluster
|
|
// from audio to midi or midi to audio. This task is handled by
|
|
// UpdateForStateChange().
|
|
//
|
|
void MixerBoard::UpdateTrackClusters()
|
|
{
|
|
if (!mImageMuteUp)
|
|
this->CreateMuteSoloImages();
|
|
|
|
const int nClusterHeight = mScrolledWindow->GetClientSize().GetHeight() - kDoubleInset;
|
|
const size_t nClusterCount = mMixerTrackClusters.GetCount();
|
|
unsigned int nClusterIndex = 0;
|
|
TrackListIterator iterTracks(mTracks);
|
|
MixerTrackCluster* pMixerTrackCluster = NULL;
|
|
Track* pLeftTrack;
|
|
Track* pRightTrack;
|
|
|
|
pLeftTrack = iterTracks.First();
|
|
while (pLeftTrack) {
|
|
pRightTrack = pLeftTrack->GetLinked() ? iterTracks.Next() : NULL;
|
|
|
|
if (pLeftTrack->GetKind() == Track::Wave
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
|| pLeftTrack->GetKind() == Track::Note
|
|
#endif
|
|
)
|
|
{
|
|
if (nClusterIndex < nClusterCount)
|
|
{
|
|
// Already showing it.
|
|
// Track clusters are maintained in the same order as the WaveTracks.
|
|
// Track pointers can change for the "same" track for different states
|
|
// on the undo stack, so update the pointers and display name.
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
if (pLeftTrack->GetKind() == Track::Note) {
|
|
mMixerTrackClusters[nClusterIndex]->mNoteTrack = (NoteTrack*)pLeftTrack;
|
|
mMixerTrackClusters[nClusterIndex]->mLeftTrack = NULL;
|
|
} else {
|
|
mMixerTrackClusters[nClusterIndex]->mNoteTrack = NULL;
|
|
mMixerTrackClusters[nClusterIndex]->mLeftTrack = (WaveTrack*)pLeftTrack;
|
|
}
|
|
#else
|
|
mMixerTrackClusters[nClusterIndex]->mLeftTrack = (WaveTrack*)pLeftTrack;
|
|
#endif
|
|
mMixerTrackClusters[nClusterIndex]->mRightTrack = (WaveTrack*)pRightTrack;
|
|
mMixerTrackClusters[nClusterIndex]->UpdateForStateChange();
|
|
}
|
|
else
|
|
{
|
|
// Not already showing it. Add a new MixerTrackCluster.
|
|
wxPoint clusterPos(
|
|
(kInset + // extra inset to left for first one, so it's double
|
|
(nClusterIndex *
|
|
(kInset + kMixerTrackClusterWidth)) + // left margin and width for each to its left
|
|
kInset), // plus left margin for new cluster
|
|
kInset);
|
|
wxSize clusterSize(kMixerTrackClusterWidth, nClusterHeight);
|
|
pMixerTrackCluster =
|
|
new MixerTrackCluster(mScrolledWindow, this, mProject,
|
|
(WaveTrack*)pLeftTrack, (WaveTrack*)pRightTrack,
|
|
clusterPos, clusterSize);
|
|
if (pMixerTrackCluster)
|
|
mMixerTrackClusters.Add(pMixerTrackCluster);
|
|
}
|
|
nClusterIndex++;
|
|
}
|
|
pLeftTrack = iterTracks.Next();
|
|
}
|
|
|
|
if (pMixerTrackCluster)
|
|
{
|
|
// Added at least one MixerTrackCluster.
|
|
this->UpdateWidth();
|
|
this->ResizeTrackClusters();
|
|
}
|
|
else if (nClusterIndex < nClusterCount)
|
|
{
|
|
// We've got too many clusters.
|
|
// This can happen only on things like Undo New Audio Track or Undo Import
|
|
// that don't call RemoveTrackCluster explicitly.
|
|
// We've already updated the track pointers for the clusters to the left, so just remove all the rest.
|
|
// Keep nClusterIndex constant and successively delete from left to right.
|
|
for (unsigned int nCounter = nClusterIndex; nCounter < nClusterCount; nCounter++)
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
this->RemoveTrackCluster(mMixerTrackClusters[nClusterIndex]->mTrack);
|
|
#else
|
|
this->RemoveTrackCluster(mMixerTrackClusters[nClusterIndex]->mLeftTrack);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
int MixerBoard::GetTrackClustersWidth()
|
|
{
|
|
return
|
|
kInset + // extra margin at left for first one
|
|
(mMixerTrackClusters.GetCount() * // number of tracks times
|
|
(kInset + kMixerTrackClusterWidth)) + // left margin and width for each
|
|
kDoubleInset; // plus final right margin
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
void MixerBoard::MoveTrackCluster(const Track* pTrack,
|
|
bool bUp) // Up in TrackPanel is left in MixerBoard.
|
|
#else
|
|
void MixerBoard::MoveTrackCluster(const WaveTrack* pTrack,
|
|
bool bUp) // Up in TrackPanel is left in MixerBoard.
|
|
#endif
|
|
{
|
|
MixerTrackCluster* pMixerTrackCluster;
|
|
int nIndex = FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
|
|
if (pMixerTrackCluster == NULL)
|
|
return; // Couldn't find it.
|
|
|
|
wxPoint pos;
|
|
if (bUp)
|
|
{ // Move it up (left).
|
|
if (nIndex <= 0)
|
|
return; // It's already first (0), or not found (-1).
|
|
|
|
pos = pMixerTrackCluster->GetPosition();
|
|
mMixerTrackClusters[nIndex] = mMixerTrackClusters[nIndex - 1];
|
|
mMixerTrackClusters[nIndex]->Move(pos);
|
|
|
|
mMixerTrackClusters[nIndex - 1] = pMixerTrackCluster;
|
|
pMixerTrackCluster->Move(pos.x - (kInset + kMixerTrackClusterWidth), pos.y);
|
|
}
|
|
else
|
|
{ // Move it down (right).
|
|
if (((unsigned int)nIndex + 1) >= mMixerTrackClusters.GetCount())
|
|
return; // It's already last.
|
|
|
|
pos = pMixerTrackCluster->GetPosition();
|
|
mMixerTrackClusters[nIndex] = mMixerTrackClusters[nIndex + 1];
|
|
mMixerTrackClusters[nIndex]->Move(pos);
|
|
|
|
mMixerTrackClusters[nIndex + 1] = pMixerTrackCluster;
|
|
pMixerTrackCluster->Move(pos.x + (kInset + kMixerTrackClusterWidth), pos.y);
|
|
}
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
void MixerBoard::RemoveTrackCluster(const Track* pTrack)
|
|
#else
|
|
void MixerBoard::RemoveTrackCluster(const WaveTrack* pTrack)
|
|
#endif
|
|
{
|
|
// Find and destroy.
|
|
MixerTrackCluster* pMixerTrackCluster;
|
|
int nIndex = this->FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
|
|
if (pMixerTrackCluster == NULL)
|
|
return; // Couldn't find it.
|
|
|
|
mMixerTrackClusters.RemoveAt(nIndex);
|
|
pMixerTrackCluster->Destroy(); // delete is unsafe on wxWindow.
|
|
|
|
// Close the gap, if any.
|
|
wxPoint pos;
|
|
int targetX;
|
|
for (unsigned int i = nIndex; i < mMixerTrackClusters.GetCount(); i++)
|
|
{
|
|
pos = mMixerTrackClusters[i]->GetPosition();
|
|
targetX =
|
|
kInset + // extra inset to left for first one, so it's double
|
|
(i * (kInset + kMixerTrackClusterWidth)) + // left margin and width for each
|
|
kInset; // plus left margin for this cluster
|
|
if (pos.x != targetX)
|
|
mMixerTrackClusters[i]->Move(targetX, pos.y);
|
|
}
|
|
|
|
this->UpdateWidth();
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
// Sanity check: if there is still a MixerTrackCluster with pTrack, then
|
|
// we deleted the first but should have deleted the last:
|
|
FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
|
|
assert(pMixerTrackCluster == NULL);
|
|
#endif
|
|
}
|
|
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
wxBitmap* MixerBoard::GetMusicalInstrumentBitmap(wxString name)
|
|
#else
|
|
wxBitmap* MixerBoard::GetMusicalInstrumentBitmap(const WaveTrack* pLeftTrack)
|
|
#endif
|
|
{
|
|
if (mMusicalInstruments.IsEmpty())
|
|
return NULL;
|
|
|
|
// random choice: return mMusicalInstruments[(int)pLeftTrack % mMusicalInstruments.GetCount()].mBitmap;
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
const wxString strTrackName(name.MakeLower());
|
|
#else
|
|
const wxString strTrackName(pLeftTrack->GetName().MakeLower());
|
|
#endif
|
|
size_t nBestItemIndex = 0;
|
|
unsigned int nBestScore = 0;
|
|
unsigned int nInstrIndex = 0;
|
|
unsigned int nKeywordIndex;
|
|
unsigned int nNumKeywords;
|
|
unsigned int nPointsPerMatch;
|
|
unsigned int nScore;
|
|
for (nInstrIndex = 0; nInstrIndex < mMusicalInstruments.GetCount(); nInstrIndex++)
|
|
{
|
|
nScore = 0;
|
|
|
|
nNumKeywords = mMusicalInstruments[nInstrIndex].mKeywords.GetCount();
|
|
if (nNumKeywords > 0)
|
|
{
|
|
nPointsPerMatch = 10 / nNumKeywords;
|
|
for (nKeywordIndex = 0; nKeywordIndex < nNumKeywords; nKeywordIndex++)
|
|
if (strTrackName.Contains(mMusicalInstruments[nInstrIndex].mKeywords[nKeywordIndex]))
|
|
{
|
|
nScore +=
|
|
nPointsPerMatch +
|
|
// Longer keywords get more points.
|
|
(2 * mMusicalInstruments[nInstrIndex].mKeywords[nKeywordIndex].Length());
|
|
}
|
|
}
|
|
|
|
// Choose later one if just matching nBestScore, for better variety,
|
|
// and so default works as last element.
|
|
if (nScore >= nBestScore)
|
|
{
|
|
nBestScore = nScore;
|
|
nBestItemIndex = nInstrIndex;
|
|
}
|
|
}
|
|
return mMusicalInstruments[nBestItemIndex].mBitmap;
|
|
}
|
|
|
|
bool MixerBoard::HasSolo()
|
|
{
|
|
TrackListIterator iterTracks(mTracks);
|
|
Track* pTrack;
|
|
for (pTrack = iterTracks.First(); pTrack; pTrack = iterTracks.Next())
|
|
if (pTrack->GetSolo())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
void MixerBoard::RefreshTrackCluster(const Track* pTrack, bool bEraseBackground /*= true*/)
|
|
#else
|
|
void MixerBoard::RefreshTrackCluster(const WaveTrack* pTrack, bool bEraseBackground )
|
|
#endif
|
|
{
|
|
MixerTrackCluster* pMixerTrackCluster;
|
|
this->FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
|
|
if (pMixerTrackCluster)
|
|
pMixerTrackCluster->Refresh(bEraseBackground);
|
|
}
|
|
|
|
void MixerBoard::RefreshTrackClusters(bool bEraseBackground /*= true*/)
|
|
{
|
|
for (unsigned int i = 0; i < mMixerTrackClusters.GetCount(); i++)
|
|
mMixerTrackClusters[i]->Refresh(bEraseBackground);
|
|
}
|
|
|
|
void MixerBoard::ResizeTrackClusters()
|
|
{
|
|
for (unsigned int nClusterIndex = 0; nClusterIndex < mMixerTrackClusters.GetCount(); nClusterIndex++)
|
|
mMixerTrackClusters[nClusterIndex]->HandleResize();
|
|
}
|
|
|
|
void MixerBoard::ResetMeters(const bool bResetClipping)
|
|
{
|
|
mPrevT1 = BAD_STREAM_TIME;
|
|
|
|
if (!this->IsShown())
|
|
return;
|
|
|
|
for (unsigned int i = 0; i < mMixerTrackClusters.GetCount(); i++)
|
|
mMixerTrackClusters[i]->ResetMeter(bResetClipping);
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
void MixerBoard::UpdateName(const Track* pTrack)
|
|
#else
|
|
void MixerBoard::UpdateName(const WaveTrack* pTrack)
|
|
#endif
|
|
{
|
|
MixerTrackCluster* pMixerTrackCluster;
|
|
this->FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
|
|
if (pMixerTrackCluster)
|
|
pMixerTrackCluster->UpdateName();
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
void MixerBoard::UpdateMute(const Track* pTrack /*= NULL*/) // NULL means update for all tracks.
|
|
#else
|
|
void MixerBoard::UpdateMute(const WaveTrack* pTrack /*= NULL*/) // NULL means update for all tracks.
|
|
#endif
|
|
{
|
|
if (pTrack == NULL)
|
|
{
|
|
for (unsigned int i = 0; i < mMixerTrackClusters.GetCount(); i++)
|
|
mMixerTrackClusters[i]->UpdateMute();
|
|
}
|
|
else
|
|
{
|
|
MixerTrackCluster* pMixerTrackCluster;
|
|
FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
|
|
if (pMixerTrackCluster)
|
|
pMixerTrackCluster->UpdateMute();
|
|
}
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
void MixerBoard::UpdateSolo(const Track* pTrack /*= NULL*/) // NULL means update for all tracks.
|
|
#else
|
|
void MixerBoard::UpdateSolo(const WaveTrack* pTrack /*= NULL*/) // NULL means update for all tracks.
|
|
#endif
|
|
{
|
|
if (pTrack == NULL)
|
|
{
|
|
for (unsigned int i = 0; i < mMixerTrackClusters.GetCount(); i++)
|
|
mMixerTrackClusters[i]->UpdateSolo();
|
|
}
|
|
else
|
|
{
|
|
MixerTrackCluster* pMixerTrackCluster;
|
|
FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
|
|
if (pMixerTrackCluster)
|
|
pMixerTrackCluster->UpdateSolo();
|
|
}
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
void MixerBoard::UpdatePan(const Track* pTrack)
|
|
#else
|
|
void MixerBoard::UpdatePan(const WaveTrack* pTrack)
|
|
#endif
|
|
{
|
|
MixerTrackCluster* pMixerTrackCluster;
|
|
FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
|
|
if (pMixerTrackCluster)
|
|
pMixerTrackCluster->UpdatePan();
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
void MixerBoard::UpdateGain(const Track* pTrack)
|
|
#else
|
|
void MixerBoard::UpdateGain(const WaveTrack* pTrack)
|
|
#endif
|
|
{
|
|
MixerTrackCluster* pMixerTrackCluster;
|
|
FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
|
|
if (pMixerTrackCluster)
|
|
pMixerTrackCluster->UpdateGain();
|
|
}
|
|
|
|
void MixerBoard::UpdateMeters(const double t1, const bool bLoopedPlay)
|
|
{
|
|
if (!this->IsShown() || (t1 == BAD_STREAM_TIME))
|
|
return;
|
|
|
|
if (mPrevT1 == BAD_STREAM_TIME)
|
|
{
|
|
mPrevT1 = t1;
|
|
return;
|
|
}
|
|
|
|
// In loopedPlay mode, at the end of the loop, mPrevT1 is set to
|
|
// selection end, so the next t1 will be less, but we do want to
|
|
// keep updating the meters.
|
|
if (t1 <= mPrevT1)
|
|
{
|
|
if (bLoopedPlay)
|
|
mPrevT1 = t1;
|
|
return;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < mMixerTrackClusters.GetCount(); i++)
|
|
mMixerTrackClusters[i]->UpdateMeter(mPrevT1, t1);
|
|
|
|
mPrevT1 = t1;
|
|
}
|
|
|
|
|
|
void MixerBoard::UpdateWidth()
|
|
{
|
|
int newWidth = this->GetTrackClustersWidth();
|
|
|
|
// Min width is one cluster wide, plus margins.
|
|
if (newWidth < MIXER_BOARD_MIN_WIDTH)
|
|
newWidth = MIXER_BOARD_MIN_WIDTH;
|
|
|
|
mScrolledWindow->SetVirtualSize(newWidth, -1);
|
|
this->GetParent()->SetSize(newWidth + kDoubleInset, -1);
|
|
}
|
|
|
|
//
|
|
// private methods
|
|
//
|
|
|
|
void MixerBoard::CreateMuteSoloImages()
|
|
{
|
|
// Much of this is taken from TrackLabel::DrawMuteSolo.
|
|
wxMemoryDC dc;
|
|
wxString str = _("Mute");
|
|
int textWidth, textHeight;
|
|
|
|
int fontSize = 10;
|
|
#ifdef __WXMSW__
|
|
fontSize = 8;
|
|
#endif
|
|
wxFont font(fontSize, wxSWISS, wxNORMAL, wxNORMAL);
|
|
this->GetTextExtent(str, &textWidth, &textHeight, NULL, NULL, &font);
|
|
mMuteSoloWidth = textWidth + kQuadrupleInset;
|
|
if (mMuteSoloWidth < kRightSideStackWidth - kInset)
|
|
mMuteSoloWidth = kRightSideStackWidth - kInset;
|
|
|
|
wxBitmap bitmap(mMuteSoloWidth, MUTE_SOLO_HEIGHT);
|
|
dc.SelectObject(bitmap);
|
|
wxRect bev(0, 0, mMuteSoloWidth - 1, MUTE_SOLO_HEIGHT - 1);
|
|
|
|
// mute button images
|
|
AColor::Mute(&dc, false, false, false);
|
|
dc.DrawRectangle(bev);
|
|
|
|
wxCoord x = bev.x + (bev.width - textWidth) / 2;
|
|
wxCoord y = bev.y + (bev.height - textHeight) / 2;
|
|
dc.SetFont(font);
|
|
dc.DrawText(str, x, y);
|
|
|
|
AColor::Bevel(dc, true, bev);
|
|
|
|
mImageMuteUp = new wxImage(bitmap.ConvertToImage());
|
|
mImageMuteOver = new wxImage(bitmap.ConvertToImage()); // Same as up, for now.
|
|
|
|
AColor::Mute(&dc, true, true, false);
|
|
dc.DrawRectangle(bev);
|
|
dc.DrawText(str, x, y);
|
|
AColor::Bevel(dc, false, bev);
|
|
mImageMuteDown = new wxImage(bitmap.ConvertToImage());
|
|
|
|
AColor::Mute(&dc, true, true, true);
|
|
dc.DrawRectangle(bev);
|
|
dc.DrawText(str, x, y);
|
|
AColor::Bevel(dc, false, bev);
|
|
mImageMuteDownWhileSolo = new wxImage(bitmap.ConvertToImage());
|
|
|
|
mImageMuteDisabled = new wxImage(mMuteSoloWidth, MUTE_SOLO_HEIGHT); // Leave empty because unused.
|
|
|
|
|
|
// solo button images
|
|
AColor::Solo(&dc, false, false);
|
|
dc.DrawRectangle(bev);
|
|
|
|
str = _("Solo");
|
|
dc.GetTextExtent(str, &textWidth, &textHeight);
|
|
x = bev.x + (bev.width - textWidth) / 2;
|
|
y = bev.y + (bev.height - textHeight) / 2;
|
|
dc.DrawText(str, x, y);
|
|
|
|
AColor::Bevel(dc, true, bev);
|
|
|
|
mImageSoloUp = new wxImage(bitmap.ConvertToImage());
|
|
mImageSoloOver = new wxImage(bitmap.ConvertToImage()); // Same as up, for now.
|
|
|
|
AColor::Solo(&dc, true, true);
|
|
dc.DrawRectangle(bev);
|
|
dc.DrawText(str, x, y);
|
|
AColor::Bevel(dc, false, bev);
|
|
mImageSoloDown = new wxImage(bitmap.ConvertToImage());
|
|
|
|
mImageSoloDisabled = new wxImage(mMuteSoloWidth, MUTE_SOLO_HEIGHT); // Leave empty because unused.
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
int MixerBoard::FindMixerTrackCluster(const Track* pTrack,
|
|
MixerTrackCluster** hMixerTrackCluster) const
|
|
#else
|
|
int MixerBoard::FindMixerTrackCluster(const WaveTrack* pLeftTrack,
|
|
MixerTrackCluster** hMixerTrackCluster) const
|
|
#endif
|
|
{
|
|
*hMixerTrackCluster = NULL;
|
|
for (unsigned int i = 0; i < mMixerTrackClusters.GetCount(); i++)
|
|
{
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
if (mMixerTrackClusters[i]->mTrack == pTrack)
|
|
#else
|
|
if (mMixerTrackClusters[i]->mLeftTrack == pLeftTrack)
|
|
#endif
|
|
{
|
|
*hMixerTrackCluster = mMixerTrackClusters[i];
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void MixerBoard::LoadMusicalInstruments()
|
|
{
|
|
wxRect bev(1, 1, MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH - 2, MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH - 2);
|
|
wxBitmap* bitmap;
|
|
wxMemoryDC dc;
|
|
MusicalInstrument* pMusicalInstrument;
|
|
|
|
|
|
bitmap = new wxBitmap((const char**)acoustic_guitar_gtr_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("acoustic_guitar_gtr"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)acoustic_piano_pno_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("acoustic_piano_pno"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)back_vocal_bg_vox_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("back_vocal_bg_vox"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)clap_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("clap"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
|
|
bitmap = new wxBitmap((const char**)drums_dr_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("drums_dr"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)electric_bass_guitar_bs_gtr_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("electric_bass_guitar_bs_gtr"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)electric_guitar_gtr_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("electric_guitar_gtr"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)electric_piano_pno_key_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("electric_piano_pno_key"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
|
|
bitmap = new wxBitmap((const char**)kick_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("kick"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)loop_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("loop"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)organ_org_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("organ_org"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)perc_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("perc"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
|
|
bitmap = new wxBitmap((const char**)sax_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("sax"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)snare_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("snare"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)string_violin_cello_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("string_violin_cello"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)synth_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("synth"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
|
|
bitmap = new wxBitmap((const char**)tambo_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("tambo"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)trumpet_horn_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("trumpet_horn"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)turntable_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("turntable"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
bitmap = new wxBitmap((const char**)vibraphone_vibes_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("vibraphone_vibes"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
|
|
bitmap = new wxBitmap((const char**)vocal_vox_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxT("vocal_vox"));
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
|
|
|
|
// This one must be last, so it wins when best score is 0.
|
|
bitmap = new wxBitmap((const char**)_default_instrument_xpm);
|
|
dc.SelectObject(*bitmap);
|
|
AColor::Bevel(dc, false, bev);
|
|
pMusicalInstrument = new MusicalInstrument(bitmap, wxEmptyString);
|
|
mMusicalInstruments.Add(pMusicalInstrument);
|
|
}
|
|
|
|
// event handlers
|
|
|
|
void MixerBoard::OnSize(wxSizeEvent &evt)
|
|
{
|
|
// this->FitInside() doesn't work, and it doesn't happen automatically. Is wxScrolledWindow wrong?
|
|
mScrolledWindow->SetSize(evt.GetSize());
|
|
|
|
this->ResizeTrackClusters();
|
|
this->RefreshTrackClusters(true);
|
|
}
|
|
|
|
|
|
// class MixerBoardFrame
|
|
|
|
BEGIN_EVENT_TABLE(MixerBoardFrame, wxFrame)
|
|
EVT_CLOSE(MixerBoardFrame::OnCloseWindow)
|
|
EVT_MAXIMIZE(MixerBoardFrame::OnMaximize)
|
|
EVT_SIZE(MixerBoardFrame::OnSize)
|
|
END_EVENT_TABLE()
|
|
|
|
// Default to fitting one track.
|
|
const wxSize kDefaultSize =
|
|
wxSize(MIXER_BOARD_MIN_WIDTH, MIXER_BOARD_MIN_HEIGHT);
|
|
|
|
MixerBoardFrame::MixerBoardFrame(AudacityProject* parent)
|
|
: wxFrame(parent, -1,
|
|
wxString::Format(_("Audacity Mixer Board%s"),
|
|
((parent->GetName() == wxEmptyString) ?
|
|
wxT("") :
|
|
wxString::Format(wxT(" - %s"),
|
|
parent->GetName().c_str()).c_str())),
|
|
wxDefaultPosition, kDefaultSize,
|
|
//vvv Bug in wxFRAME_FLOAT_ON_PARENT:
|
|
// If both the project frame and MixerBoardFrame are minimized and you restore MixerBoardFrame,
|
|
// you can't restore project frame until you close MixerBoardFrame, but then project frame and
|
|
// MixerBoardFrame are restored but MixerBoardFrame is unresponsive because it thinks it's not shown.
|
|
// wxDEFAULT_FRAME_STYLE | wxFRAME_FLOAT_ON_PARENT)
|
|
wxDEFAULT_FRAME_STYLE)
|
|
{
|
|
mMixerBoard = new MixerBoard(parent, this, wxDefaultPosition, kDefaultSize);
|
|
|
|
this->SetSizeHints(MIXER_BOARD_MIN_WIDTH, MIXER_BOARD_MIN_HEIGHT);
|
|
|
|
mMixerBoard->UpdateTrackClusters();
|
|
|
|
// loads either the XPM or the windows resource, depending on the platform
|
|
#if !defined(__WXMAC__) && !defined(__WXX11__)
|
|
#ifdef __WXMSW__
|
|
wxIcon ic(wxICON(AudacityLogo));
|
|
#else
|
|
wxIcon ic(wxICON(AudacityLogo48x48));
|
|
#endif
|
|
SetIcon(ic);
|
|
#endif
|
|
}
|
|
|
|
MixerBoardFrame::~MixerBoardFrame()
|
|
{
|
|
}
|
|
|
|
// event handlers
|
|
void MixerBoardFrame::OnCloseWindow(wxCloseEvent &WXUNUSED(event))
|
|
{
|
|
this->Hide();
|
|
}
|
|
|
|
void MixerBoardFrame::OnMaximize(wxMaximizeEvent &event)
|
|
{
|
|
// Update the size hints to show all tracks before skipping to let default handling happen.
|
|
mMixerBoard->UpdateWidth();
|
|
event.Skip();
|
|
}
|
|
|
|
void MixerBoardFrame::OnSize(wxSizeEvent & WXUNUSED(event))
|
|
{
|
|
mMixerBoard->SetSize(this->GetClientSize());
|
|
}
|
|
|
|
|