audacia/src/MixerBoard.cpp
Paul Licameli 02e620d35f Move functions into new file SelectUtilities.cpp ...
... so that other files do not have link dependency on SelectMenus.cpp

The new file enlarges the big s.c.c. to 24
2019-06-24 23:06:44 -04:00

1492 lines
48 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
MixerBoard.cpp
Vaughan Johnson, January 2007
Dominic Mazzoni
**********************************************************************/
#include "Audacity.h" // for USE_* macros
#include "MixerBoard.h"
#include "Experimental.h"
#include <cfloat>
#include <math.h>
#include <wx/setup.h> // for wxUSE_* macros
#include <wx/bmpbuttn.h>
#include <wx/dcclient.h>
#include <wx/dcmemory.h>
#include <wx/icon.h>
#include <wx/settings.h> // for wxSystemSettings::GetColour and wxSystemSettings::GetMetric
#include "AColor.h"
#include "AllThemeResources.h"
#include "AudioIO.h"
#include "Menus.h"
#ifdef USE_MIDI
#include "NoteTrack.h"
#endif
#include "KeyboardCapture.h"
#include "Prefs.h" // for RTL_WORKAROUND
#include "Project.h"
#include "ProjectAudioIO.h"
#include "ProjectHistory.h"
#include "ProjectSettings.h"
#include "ProjectWindow.h"
#include "SelectUtilities.h"
#include "TrackPanel.h" // for EVT_TRACK_PANEL_TIMER
#include "UndoManager.h"
#include "WaveTrack.h"
#include "widgets/AButton.h"
#include "widgets/Meter.h"
#include "../images/MusicalInstruments.h"
#ifdef __WXMSW__
#include "../images/AudacityLogo.xpm"
#else
#include "../images/AudacityLogo48x48.xpm"
#endif
#include "commands/CommandManager.h"
#include "toolbars/ControlToolBar.h"
// 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,
const wxString &name,
const wxPoint & pos,
const wxSize & size,
const ASlider::Options &options)
: ASlider(parent, id, name, pos, size, options)
{
}
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)
{
KeyboardCapture::OnFocus( *this, event );
}
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
const int kInset = 4;
const int kDoubleInset = (2 * kInset);
const int kTripleInset = (3 * kInset);
const int kQuadrupleInset = (4 * kInset);
const int TRACK_NAME_HEIGHT = 18;
const int MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH = 48;
const int MUTE_SOLO_HEIGHT = 19;
const int PAN_HEIGHT = 24;
const int kLeftSideStackWidth = MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH - kDoubleInset; //vvv Change when numbers shown on slider scale.
const int kRightSideStackWidth = MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH + kDoubleInset;
const int kMixerTrackClusterWidth = kLeftSideStackWidth + kRightSideStackWidth + kQuadrupleInset; // kDoubleInset margin on both sides
enum {
ID_BITMAPBUTTON_MUSICAL_INSTRUMENT = 13000,
ID_SLIDER_PAN,
ID_SLIDER_GAIN,
#ifdef EXPERIMENTAL_MIDI_OUT
ID_SLIDER_VELOCITY,
#endif
ID_TOGGLEBUTTON_MUTE,
ID_TOGGLEBUTTON_SOLO,
};
BEGIN_EVENT_TABLE(MixerTrackCluster, wxPanelWrapper)
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)
#ifdef EXPERIMENTAL_MIDI_OUT
EVT_SLIDER(ID_SLIDER_VELOCITY, MixerTrackCluster::OnSlider_Velocity)
#endif
//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,
const std::shared_ptr<PlayableTrack> &pTrack,
const wxPoint& pos /*= wxDefaultPosition*/,
const wxSize& size /*= wxDefaultSize*/)
: wxPanelWrapper(parent, -1, pos, size)
, mTrack{ pTrack }
{
mMixerBoard = grandParent;
mProject = project;
wxASSERT( pTrack );
SetName(mTrack->GetName());
//this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
this->SetBackgroundColour( theTheme.Colour( clrMedium ) );
// 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 =
safenew auStaticText(this, mTrack->GetName());
//v Useful when different tracks are different colors, but not now.
// mStaticText_TrackName->SetBackgroundColour(this->GetTrackColor());
mStaticText_TrackName->SetForegroundColour(theTheme.Colour(clrMedium));
mStaticText_TrackName->SetForegroundColour(theTheme.Colour(clrTrackPanelText));
mStaticText_TrackName->SetSize( ctrlSize );
mStaticText_TrackName->SetPosition( ctrlPos );
// gain and velocity sliders at left (both in same place)
ctrlPos.x = kDoubleInset;
ctrlPos.y += TRACK_NAME_HEIGHT + kDoubleInset;
const int nGainSliderHeight =
size.GetHeight() - ctrlPos.y - kQuadrupleInset;
ctrlSize.Set(kLeftSideStackWidth - kQuadrupleInset, nGainSliderHeight);
mSlider_Gain =
safenew MixerTrackSlider(
this, ID_SLIDER_GAIN,
/* i18n-hint: title of the Gain slider, used to adjust the volume */
_("Gain"),
ctrlPos, ctrlSize,
ASlider::Options{}
.Style( DB_SLIDER )
.Orientation( wxVERTICAL ));
mSlider_Gain->SetName(_("Gain"));
#ifdef EXPERIMENTAL_MIDI_OUT
mSlider_Velocity =
safenew MixerTrackSlider(
this, ID_SLIDER_VELOCITY,
/* i18n-hint: title of the MIDI Velocity slider */
_("Velocity"),
ctrlPos, ctrlSize,
ASlider::Options{}
.Style( VEL_SLIDER )
.Orientation( wxVERTICAL ));
mSlider_Velocity->SetName(_("Velocity"));
#endif
// 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);
wxBitmap* bitmap = mMixerBoard->GetMusicalInstrumentBitmap(mTrack.get());
wxASSERT(bitmap);
mBitmapButton_MusicalInstrument =
safenew 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 =
safenew MixerTrackSlider(
this, ID_SLIDER_PAN,
/* i18n-hint: Title of the Pan slider, used to move the sound left or right */
_("Pan"),
ctrlPos, ctrlSize,
ASlider::Options{}.Style( PAN_SLIDER ));
mSlider_Pan->SetName(_("Pan"));
// mute/solo buttons stacked below Pan slider
ctrlPos.y += PAN_HEIGHT + kDoubleInset;
ctrlSize.Set(mMixerBoard->mMuteSoloWidth, MUTE_SOLO_HEIGHT);
mToggleButton_Mute =
safenew AButton(this, ID_TOGGLEBUTTON_MUTE,
ctrlPos, ctrlSize,
*(mMixerBoard->mImageMuteUp), *(mMixerBoard->mImageMuteOver),
*(mMixerBoard->mImageMuteDown), *(mMixerBoard->mImageMuteDown),
*(mMixerBoard->mImageMuteDisabled),
true); // toggle button
mToggleButton_Mute->SetName(_("Mute"));
mToggleButton_Mute->SetAlternateImages(
1,
*(mMixerBoard->mImageMuteUp), *(mMixerBoard->mImageMuteOver),
*(mMixerBoard->mImageMuteDown), *(mMixerBoard->mImageMuteDown),
*(mMixerBoard->mImageMuteDisabled));
ctrlPos.y += MUTE_SOLO_HEIGHT;
mToggleButton_Solo =
safenew AButton(this, ID_TOGGLEBUTTON_SOLO,
ctrlPos, ctrlSize,
*(mMixerBoard->mImageSoloUp), *(mMixerBoard->mImageSoloOver),
*(mMixerBoard->mImageSoloDown), *(mMixerBoard->mImageSoloDown),
*(mMixerBoard->mImageSoloDisabled),
true); // toggle button
mToggleButton_Solo->SetName(_("Solo"));
bool bSoloNone = ProjectSettings::Get( *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);
mMeter.Release();
if (GetWave()) {
mMeter =
safenew MeterPanel(GetActiveProject(), // AudacityProject* project,
this, -1, // wxWindow* parent, wxWindowID id,
false, // bool isInput
ctrlPos, ctrlSize, // const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize,
MeterPanel::MixerTrackCluster); // Style style = HorizontalStereo,
mMeter->SetName(_("Signal Level Meter"));
}
#if wxUSE_TOOLTIPS
mStaticText_TrackName->SetToolTip(mTrack->GetName());
mToggleButton_Mute->SetToolTip(_("Mute"));
mToggleButton_Solo->SetToolTip(_("Solo"));
if (GetWave())
mMeter->SetToolTip(_("Signal Level Meter"));
#endif // wxUSE_TOOLTIPS
UpdateForStateChange();
#ifdef __WXMAC__
wxSizeEvent event(GetSize(), GetId());
event.SetEventObject(this);
GetEventHandler()->ProcessEvent(event);
#endif
}
WaveTrack *MixerTrackCluster::GetWave() const
{
return dynamic_cast< WaveTrack * >( mTrack.get() );
}
WaveTrack *MixerTrackCluster::GetRight() const
{
// TODO: more-than-two-channels
auto left = GetWave();
if (left) {
auto channels = TrackList::Channels(left);
if ( channels.size() > 1 )
return * ++ channels.first;
}
return nullptr;
}
#ifdef EXPERIMENTAL_MIDI_OUT
NoteTrack *MixerTrackCluster::GetNote() const
{
return dynamic_cast< NoteTrack * >( mTrack.get() );
}
#endif
// Old approach modified things in situ.
// However with a theme change there is so much to modify, it is easier
// to recreate.
#if 0
void MixerTrackCluster::UpdatePrefs()
{
this->SetBackgroundColour( theTheme.Colour( clrMedium ) );
mStaticText_TrackName->SetForegroundColour(theTheme.Colour(clrTrackPanelText));
HandleResize(); // in case prefs "/GUI/Solo" changed
}
#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);
#ifdef EXPERIMENTAL_MIDI_OUT
mSlider_Velocity->SetSize(-1, nGainSliderHeight);
#endif
bool bSoloNone = ProjectSettings::Get( *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;
if (mMeter)
mMeter->SetSize(-1, nMeterY, -1, nMeterHeight);
}
void MixerTrackCluster::HandleSliderGain(const bool bWantPushState /*= false*/)
{
float fValue = mSlider_Gain->Get();
if (GetWave())
GetWave()->SetGain(fValue);
if (GetRight())
GetRight()->SetGain(fValue);
// Update the TrackPanel correspondingly.
TrackPanel::Get( *mProject ).RefreshTrack(mTrack.get());
if (bWantPushState)
ProjectHistory::Get( *mProject )
.PushState(_("Moved gain slider"), _("Gain"), UndoPush::CONSOLIDATE );
}
#ifdef EXPERIMENTAL_MIDI_OUT
void MixerTrackCluster::HandleSliderVelocity(const bool bWantPushState /*= false*/)
{
float fValue = mSlider_Velocity->Get();
if (GetNote())
GetNote()->SetVelocity(fValue);
// Update the TrackPanel correspondingly.
TrackPanel::Get( *mProject ).RefreshTrack(mTrack.get());
if (bWantPushState)
ProjectHistory::Get( *mProject )
.PushState(_("Moved velocity slider"), _("Velocity"),
UndoPush::CONSOLIDATE);
}
#endif
void MixerTrackCluster::HandleSliderPan(const bool bWantPushState /*= false*/)
{
float fValue = mSlider_Pan->Get();
if (GetWave()) // test in case track is a NoteTrack
GetWave()->SetPan(fValue);
if (GetRight())
GetRight()->SetPan(fValue);
// Update the TrackPanel correspondingly.
TrackPanel::Get( *mProject ).RefreshTrack(mTrack.get());
if (bWantPushState)
ProjectHistory::Get( *mProject )
.PushState(_("Moved pan slider"), _("Pan"),
UndoPush::CONSOLIDATE );
}
void MixerTrackCluster::ResetMeter(const bool bResetClipping)
{
if (mMeter)
mMeter->Reset(GetWave()->GetRate(), bResetClipping);
}
// Update appearance to match the state of the track
void MixerTrackCluster::UpdateForStateChange()
{
const wxString newName = mTrack->GetName();
if (newName != GetName()) {
SetName(newName);
mStaticText_TrackName->SetLabel(newName);
mStaticText_TrackName->SetName(newName);
#if wxUSE_TOOLTIPS
mStaticText_TrackName->SetToolTip(newName);
#endif
mBitmapButton_MusicalInstrument->SetBitmapLabel(
*(mMixerBoard->GetMusicalInstrumentBitmap(mTrack.get())));
}
mToggleButton_Mute->SetAlternateIdx(mTrack->GetSolo() ? 1 : 0);
if (mTrack->GetMute())
mToggleButton_Mute->PushDown();
else
mToggleButton_Mute->PopUp();
bool bIsSolo = mTrack->GetSolo();
if (bIsSolo)
mToggleButton_Solo->PushDown();
else
mToggleButton_Solo->PopUp();
mToggleButton_Mute->SetAlternateIdx(bIsSolo ? 1 : 0);
if (!GetWave())
mSlider_Pan->Hide();
else
mSlider_Pan->Set(GetWave()->GetPan());
if (!GetWave())
mSlider_Gain->Hide();
else
mSlider_Gain->Set(GetWave()->GetGain());
#ifdef EXPERIMENTAL_MIDI_OUT
if (!GetNote())
mSlider_Velocity->Hide();
else
mSlider_Velocity->Set(GetNote()->GetVelocity());
#endif
}
void MixerTrackCluster::UpdateMeter(const double t0, const double t1)
{
// 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 (!GetWave())
return;
if ((t0 < 0.0) || (t1 < 0.0) || (t0 >= t1) || // bad time value or nothing to show
((mMixerBoard->HasSolo() || mTrack->GetMute()) && !mTrack->GetSolo())
)
{
//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 MeterPanel::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 MeterPanel::UpdateDisplay(). New code is below the previous (now commented out).
//
//const size_t kFramesPerBuffer = 4;
//float min; // dummy, since it's not shown in meters
//Floats maxLeft{kFramesPerBuffer};
//Floats rmsLeft{kFramesPerBuffer};
//Floats maxRight{kFramesPerBuffer};
//Floats rmsRight{kFramesPerBuffer};
//
//#ifdef EXPERIMENTAL_MIDI_OUT
// bool bSuccess = (GetWave() != nullptr);
//#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 &=
// mTrack->GetMinMax(&min, &(maxLeft[i]), dFrameT0, dFrameT1) &&
// mTrack->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 = mTrack->GetChannelGain(0);
// maxLeft[i] *= gain;
// rmsLeft[i] *= gain;
// if (mRightTrack)
// gain = mRightTrack->GetChannelGain(1);
// maxRight[i] *= gain;
// rmsRight[i] *= gain;
// }
// if ( mMeter ) mMeter->UpdateDisplay(
// 2, // If mono, show left track values in both meters, as in MeterToolBar, rather than nChannels.
// kFramesPerBuffer,
// maxLeft, rmsLeft,
// maxRight, rmsRight,
// mTrack->TimeToLongSamples(t1 - t0));
//}
//
const auto pTrack = GetWave();
auto startSample = (sampleCount)((pTrack->GetRate() * t0) + 0.5);
auto scnFrames = (sampleCount)((pTrack->GetRate() * (t1 - t0)) + 0.5);
// Expect that the difference of t1 and t0 is the part of a track played
// in about 1/20 second (ticks of TrackPanel timer), so this won't overflow
auto nFrames = scnFrames.as_size_t();
Floats tempFloatsArray{ nFrames };
decltype(tempFloatsArray) meterFloatsArray;
// Don't throw on read error in this drawing update routine
bool bSuccess = pTrack->Get((samplePtr)tempFloatsArray.get(),
floatSample, startSample, nFrames, fillZero, false);
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 = Floats{ 2 * nFrames };
// Interleave for stereo. Left/mono first.
for (unsigned int index = 0; index < nFrames; index++)
meterFloatsArray[2 * index] = tempFloatsArray[index];
if (GetRight())
// Again, don't throw
bSuccess = GetRight()->Get((samplePtr)tempFloatsArray.get(),
floatSample, startSample, nFrames, fillZero, false);
if (bSuccess)
// Interleave right channel, or duplicate same signal for "right" channel in mono case.
for (unsigned 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 = pTrack->GetChannelGain(0);
if (gain < 1.0)
for (unsigned int index = 0; index < nFrames; index++)
meterFloatsArray[2 * index] *= gain;
if (GetRight())
gain = GetRight()->GetChannelGain(1);
else
gain = pTrack->GetChannelGain(1);
if (gain < 1.0)
for (unsigned int index = 0; index < nFrames; index++)
meterFloatsArray[(2 * index) + 1] *= gain;
// Clip to [-1.0, 1.0] range.
for (unsigned 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;
if (mMeter)
mMeter->UpdateDisplay(2, nFrames, meterFloatsArray.get());
}
else
this->ResetMeter(false);
}
// private
wxColour MixerTrackCluster::GetTrackColor()
{
return wxColour(102, 255, 102); // same as Meter playback color
}
// event handlers
void MixerTrackCluster::HandleSelect(bool bShiftDown, bool bControlDown)
{
SelectUtilities::DoListSelection(*mProject,
mTrack.get(), bShiftDown, bControlDown, true);
}
void MixerTrackCluster::OnMouseEvent(wxMouseEvent& event)
{
if (event.ButtonUp())
this->HandleSelect(event.ShiftDown(), event.ControlDown());
else
event.Skip();
}
void MixerTrackCluster::OnPaint(wxPaintEvent & WXUNUSED(event))
{
UpdateForStateChange();
auto selected = mTrack->GetSelected();
wxColour col = theTheme.Colour(selected ? clrTrackInfoSelected : clrTrackInfo) ;
SetBackgroundColour( col );
if (mMeter)
mMeter->SetBackgroundColour( col );
mStaticText_TrackName->SetBackgroundColour( col );
mSlider_Gain->SetBackgroundColour( col );
mSlider_Pan->SetBackgroundColour( col );
wxPaintDC dc(this);
AColor::MediumTrackInfo(&dc, selected);
dc.DrawRectangle(this->GetClientRect());
wxSize clusterSize = this->GetSize();
wxRect bev(0, 0, clusterSize.GetWidth() - 1, clusterSize.GetHeight() - 1);
//bev.Inflate(-1, -1);
AColor::Bevel(dc, true, bev);// same bevel whether selected or not.
}
void MixerTrackCluster::OnButton_MusicalInstrument(wxCommandEvent& WXUNUSED(event))
{
const auto &state = ::wxGetMouseState();
this->HandleSelect(state.ShiftDown(), state.ControlDown());
}
void MixerTrackCluster::OnSlider_Gain(wxCommandEvent& WXUNUSED(event))
{
this->HandleSliderGain();
}
#ifdef EXPERIMENTAL_MIDI_OUT
void MixerTrackCluster::OnSlider_Velocity(wxCommandEvent& WXUNUSED(event))
{
this->HandleSliderVelocity();
}
#endif
//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))
{
TrackActions::DoTrackMute(
*mProject, mTrack.get(), mToggleButton_Mute->WasShiftDown());
mToggleButton_Mute->SetAlternateIdx(mTrack->GetSolo() ? 1 : 0);
// Update the TrackPanel correspondingly.
if (ProjectSettings::Get(*mProject).IsSoloSimple())
ProjectWindow::Get( *mProject ).RedrawProject();
else
// Update only the changed track.
TrackPanel::Get( *mProject ).RefreshTrack(mTrack.get());
}
void MixerTrackCluster::OnButton_Solo(wxCommandEvent& WXUNUSED(event))
{
TrackActions::DoTrackSolo(
*mProject, mTrack.get(), mToggleButton_Solo->WasShiftDown());
bool bIsSolo = mTrack->GetSolo();
mToggleButton_Mute->SetAlternateIdx(bIsSolo ? 1 : 0);
// Update the TrackPanel correspondingly.
// Bug 509: Must repaint all, as many tracks can change with one Solo change.
ProjectWindow::Get( *mProject ).RedrawProject();
}
// class MusicalInstrument
MusicalInstrument::MusicalInstrument(std::unique_ptr<wxBitmap> &&pBitmap, const wxString & strXPMfilename)
{
mBitmap = std::move(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.push_back(strKeyword);
strFilename = strFilename.Mid(nUnderscoreIndex + 1);
}
if (!strFilename.empty()) // Skip trailing underscores.
mKeywords.push_back(strFilename); // Add the last one.
}
MusicalInstrument::~MusicalInstrument()
{
mKeywords.clear();
}
// 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.
SelectUtilities::SelectNone( *mProject );
}
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*2 + kTripleInset
BEGIN_EVENT_TABLE(MixerBoard, wxWindow)
EVT_PAINT(MixerBoard::OnPaint)
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;
wxASSERT(pProject); // to justify safenew
mScrolledWindow =
safenew 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->SetBackgroundColour( theTheme.Colour( clrMedium ) );
RTL_WORKAROUND(mScrolledWindow);
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.
auto pBoxSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
pBoxSizer->push_back(mScrolledWindow, 0, wxExpand, 0);
this->SetAutoLayout(true);
this->SetSizer(pBoxSizer);
pBoxSizer->Fit(this);
pBoxSizer->SetSizeHints(this);
*/
mPrevT1 = 0.0;
mTracks = &TrackList::Get( *mProject );
// Events from the project don't propagate directly to this other frame, so...
mProject->Bind(EVT_TRACK_PANEL_TIMER,
&MixerBoard::OnTimer,
this);
mTracks->Bind(EVT_TRACKLIST_SELECTION_CHANGE,
&MixerBoard::OnTrackChanged,
this);
mTracks->Bind(EVT_TRACKLIST_PERMUTED,
&MixerBoard::OnTrackSetChanged,
this);
mTracks->Bind(EVT_TRACKLIST_ADDITION,
&MixerBoard::OnTrackSetChanged,
this);
mTracks->Bind(EVT_TRACKLIST_DELETION,
&MixerBoard::OnTrackSetChanged,
this);
mTracks->Bind(EVT_TRACKLIST_TRACK_DATA_CHANGE,
&MixerBoard::OnTrackChanged,
this);
wxTheApp->Connect(EVT_AUDIOIO_PLAYBACK,
wxCommandEventHandler(MixerBoard::OnStartStop),
NULL,
this);
}
void MixerBoard::UpdatePrefs()
{
// Destroys this:
static_cast<MixerBoardFrame*>(GetParent())->Recreate( mProject );
// Old approach modified things in situ.
// However with a theme change there is so much to modify, it is easier
// to recreate.
#if 0
mScrolledWindow->SetBackgroundColour( theTheme.Colour( clrMedium ) );
if( mImageMuteUp ){
mImageMuteUp.reset();
mImageMuteOver.reset();
mImageMuteDown.reset();
mImageMuteDownWhileSolo.reset();
mImageMuteDisabled.reset();
mImageSoloUp.reset();
mImageSoloOver.reset();
mImageSoloDown.reset();
mImageSoloDisabled.reset();
}
for (unsigned int nClusterIndex = 0; nClusterIndex < mMixerTrackClusters.size(); nClusterIndex++)
mMixerTrackClusters[nClusterIndex]->UpdatePrefs();
Refresh();
#endif
}
// 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;
size_t nClusterCount = mMixerTrackClusters.size();
unsigned int nClusterIndex = 0;
MixerTrackCluster* pMixerTrackCluster = NULL;
for (auto pPlayableTrack: mTracks->Leaders<PlayableTrack>()) {
// TODO: more-than-two-channels
auto spTrack = pPlayableTrack->SharedPointer<PlayableTrack>();
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.
mMixerTrackClusters[nClusterIndex]->mTrack = spTrack;
// Assume linked track is wave or null
mMixerTrackClusters[nClusterIndex]->UpdateForStateChange();
}
else
{
// Not already showing it. Add a NEW MixerTrackCluster.
wxPoint clusterPos(
kInset + nClusterIndex * kMixerTrackClusterWidth,
kInset);
wxSize clusterSize(kMixerTrackClusterWidth, nClusterHeight);
pMixerTrackCluster =
safenew MixerTrackCluster(mScrolledWindow, this, mProject,
spTrack,
clusterPos, clusterSize);
if (pMixerTrackCluster)
mMixerTrackClusters.push_back(pMixerTrackCluster);
}
nClusterIndex++;
}
if (pMixerTrackCluster)
{
// Added at least one MixerTrackCluster.
this->UpdateWidth();
this->ResizeTrackClusters();
}
else while (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.
// Successively DELETE from right to left.
RemoveTrackCluster(--nClusterCount);
}
}
int MixerBoard::GetTrackClustersWidth()
{
return
kInset + // extra margin at left for first one
(mMixerTrackClusters.size() * // number of tracks times
(kInset + kMixerTrackClusterWidth)) + // left margin and width for each
kDoubleInset; // plus final right margin
}
void MixerBoard::RemoveTrackCluster(size_t nIndex)
{
auto pMixerTrackCluster = mMixerTrackClusters[nIndex];
mMixerTrackClusters.erase(mMixerTrackClusters.begin() + nIndex);
pMixerTrackCluster->Destroy(); // DELETE is unsafe on wxWindow.
// Close the gap, if any.
wxPoint pos;
int targetX;
for (unsigned int i = nIndex; i < mMixerTrackClusters.size(); 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();
}
wxBitmap* MixerBoard::GetMusicalInstrumentBitmap(const Track* pTrack)
{
if (mMusicalInstruments.empty())
return NULL;
// random choice: return mMusicalInstruments[(int)pTrack % mMusicalInstruments.size()].mBitmap;
const wxString strTrackName(pTrack->GetName().MakeLower());
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.size(); nInstrIndex++)
{
nScore = 0;
nNumKeywords = mMusicalInstruments[nInstrIndex]->mKeywords.size();
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.get();
}
bool MixerBoard::HasSolo()
{
return !(( mTracks->Any<PlayableTrack>() + &PlayableTrack::GetSolo ).empty());
}
void MixerBoard::RefreshTrackClusters(bool bEraseBackground /*= true*/)
{
for (unsigned int i = 0; i < mMixerTrackClusters.size(); i++)
mMixerTrackClusters[i]->Refresh(bEraseBackground);
}
void MixerBoard::ResizeTrackClusters()
{
for (unsigned int nClusterIndex = 0; nClusterIndex < mMixerTrackClusters.size(); 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.size(); i++)
mMixerTrackClusters[i]->ResetMeter(bResetClipping);
}
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.size(); 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::MakeButtonBitmap( wxMemoryDC & dc, wxBitmap & WXUNUSED(bitmap), wxRect & bev, const wxString & str, bool up )
{
int textWidth, textHeight;
int fontSize = 10;
#ifdef __WXMSW__
fontSize = 8;
#endif
wxFont font(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
GetTextExtent(str, &textWidth, &textHeight, NULL, NULL, &font);
AColor::UseThemeColour( &dc, clrMedium );
dc.DrawRectangle(bev);
AColor::Bevel2( dc, up, bev, false );
wxCoord x = bev.x + (bev.width - textWidth) / 2;
wxCoord y = bev.y + (bev.height - textHeight) / 2;
dc.SetFont(font);
dc.SetTextForeground(theTheme.Colour(clrTrackPanelText));
dc.SetBackgroundMode(wxTRANSPARENT);
dc.DrawText(str, x, y);
// dc.DrawText(str, 0, 0);
}
void MixerBoard::CreateMuteSoloImages()
{
// Much of this is similar to TrackInfo::MuteOrSoloDrawFunction.
wxMemoryDC dc;
wxString str = _("Mute");
//mMuteSoloWidth = textWidth + kQuadrupleInset;
//if (mMuteSoloWidth < kRightSideStackWidth - kInset)
mMuteSoloWidth = kRightSideStackWidth - kInset;
wxBitmap bitmap(mMuteSoloWidth, MUTE_SOLO_HEIGHT,24);
dc.SelectObject(bitmap);
wxRect bev(0, 0, mMuteSoloWidth, MUTE_SOLO_HEIGHT);
const bool up=true;
const bool down=false;
MakeButtonBitmap( dc, bitmap, bev, str, up );
mImageMuteUp = std::make_unique<wxImage>(bitmap.ConvertToImage());
mImageMuteOver = std::make_unique<wxImage>(bitmap.ConvertToImage()); // Same as up, for now.
MakeButtonBitmap( dc, bitmap, bev, str, down );
//AColor::Bevel(dc, false, bev);
mImageMuteDown = std::make_unique<wxImage>(bitmap.ConvertToImage());
MakeButtonBitmap( dc, bitmap, bev, str, down );
mImageMuteDownWhileSolo = std::make_unique<wxImage>(bitmap.ConvertToImage());
mImageMuteDisabled = std::make_unique<wxImage>(mMuteSoloWidth, MUTE_SOLO_HEIGHT); // Leave empty because unused.
str = _("Solo");
MakeButtonBitmap( dc, bitmap, bev, str, up );
mImageSoloUp = std::make_unique<wxImage>(bitmap.ConvertToImage());
mImageSoloOver = std::make_unique<wxImage>(bitmap.ConvertToImage()); // Same as up, for now.
MakeButtonBitmap( dc, bitmap, bev, str, down );
mImageSoloDown = std::make_unique<wxImage>(bitmap.ConvertToImage());
mImageSoloDisabled = std::make_unique<wxImage>(mMuteSoloWidth, MUTE_SOLO_HEIGHT); // Leave empty because unused.
}
int MixerBoard::FindMixerTrackCluster(const PlayableTrack* pTrack,
MixerTrackCluster** hMixerTrackCluster) const
{
*hMixerTrackCluster = NULL;
for (unsigned int i = 0; i < mMixerTrackClusters.size(); i++)
{
if (mMixerTrackClusters[i]->mTrack.get() == pTrack)
{
*hMixerTrackCluster = mMixerTrackClusters[i];
return i;
}
}
return -1;
}
void MixerBoard::LoadMusicalInstruments()
{
const struct Data { const char * const *bitmap; wxString name; } table[] = {
{acoustic_guitar_gtr_xpm, wxT("acoustic_guitar_gtr")},
{acoustic_piano_pno_xpm, wxT("acoustic_piano_pno")},
{back_vocal_bg_vox_xpm, wxT("back_vocal_bg_vox")},
{clap_xpm, wxT("clap")},
{drums_dr_xpm, wxT("drums_dr")},
{electric_bass_guitar_bs_gtr_xpm, wxT("electric_bass_guitar_bs_gtr")},
{electric_guitar_gtr_xpm, wxT("electric_guitar_gtr")},
{electric_piano_pno_key_xpm, wxT("electric_piano_pno_key")},
{kick_xpm, wxT("kick")},
{loop_xpm, wxT("loop")},
{organ_org_xpm, wxT("organ_org")},
{perc_xpm, wxT("perc")},
{sax_xpm, wxT("sax")},
{snare_xpm, wxT("snare")},
{string_violin_cello_xpm, wxT("string_violin_cello")},
{synth_xpm, wxT("synth")},
{tambo_xpm, wxT("tambo")},
{trumpet_horn_xpm, wxT("trumpet_horn")},
{turntable_xpm, wxT("turntable")},
{vibraphone_vibes_xpm, wxT("vibraphone_vibes")},
{vocal_vox_xpm, wxT("vocal_vox")},
// This one must be last, so it wins when best score is 0.
{_default_instrument_xpm, wxEmptyString},
};
wxRect bev(1, 1, MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH - 2, MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH - 2);
wxMemoryDC dc;
for (const auto &data : table) {
auto bmp = std::make_unique<wxBitmap>(data.bitmap,24);
dc.SelectObject(*bmp);
AColor::Bevel(dc, false, bev);
mMusicalInstruments.push_back(std::make_unique<MusicalInstrument>(
std::move(bmp), data.name
));
};
}
// event handlers
void MixerBoard::OnPaint(wxPaintEvent& evt)
{
if (!mUpToDate) {
mUpToDate = true;
UpdateTrackClusters();
Refresh();
}
// Does the base class do anything for repainting?
evt.Skip();
}
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);
}
void MixerBoard::OnTimer(wxCommandEvent &event)
{
// PRL 12 Jul 2015: Moved the below (with comments) out of TrackPanel::OnTimer.
// Vaughan, 2011-01-28: No longer doing this on timer.
// Now it's in AudioIO::SetMeters() and AudioIO::StopStream(), as with Meter Toolbar meters.
//if (pMixerBoard)
// pMixerBoard->ResetMeters(false);
//v Vaughan, 2011-02-25: Moved this update back here from audacityAudioCallback.
// See note there.
// Vaughan, 2010-01-30:
// Since all we're doing here is updating the meters, I moved it to
// audacityAudioCallback where it calls gAudioIO->mOutputMeter->UpdateDisplay().
if (ProjectAudioIO::Get( *mProject ).IsAudioActive())
{
auto gAudioIO = AudioIOBase::Get();
UpdateMeters(
gAudioIO->GetStreamTime(),
(ControlToolBar::Get( *mProject ).GetLastPlayMode()
== PlayMode::loopedPlay)
);
}
// Let other listeners get the notification
event.Skip();
}
void MixerBoard::OnTrackChanged(TrackListEvent &evt)
{
evt.Skip();
auto pTrack = evt.mpTrack.lock();
auto pPlayable = dynamic_cast<PlayableTrack*>( pTrack.get() );
if ( pPlayable ) {
MixerTrackCluster *pMixerTrackCluster;
FindMixerTrackCluster( pPlayable, &pMixerTrackCluster );
if ( pMixerTrackCluster )
pMixerTrackCluster->Refresh();
}
}
void MixerBoard::OnTrackSetChanged(wxEvent &evt)
{
evt.Skip();
mUpToDate = false;
UpdateTrackClusters();
Refresh();
}
void MixerBoard::OnStartStop(wxCommandEvent &evt)
{
evt.Skip();
bool start = evt.GetInt();
ResetMeters( start );
}
// class MixerBoardFrame
BEGIN_EVENT_TABLE(MixerBoardFrame, wxFrame)
EVT_KEY_DOWN(MixerBoardFrame::OnKeyEvent)
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( &GetProjectFrame( *parent ), -1,
wxString::Format(_("Audacity Mixer Board%s"),
((parent->GetProjectName().empty()) ?
wxT("") :
wxString::Format(wxT(" - %s"),
parent->GetProjectName()))),
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 = safenew 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
Center();
}
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());
}
void MixerBoardFrame::OnKeyEvent(wxKeyEvent & event)
{
AudacityProject *project = GetActiveProject();
auto &commandManager = CommandManager::Get( *project );
commandManager.FilterKeyEvent(project, event, true);
}
void MixerBoardFrame::Recreate( AudacityProject *pProject )
{
wxPoint pos = mMixerBoard->GetPosition();
wxSize siz = mMixerBoard->GetSize();
wxSize siz2 = this->GetSize();
//wxLogDebug("Got rid of board %p", mMixerBoard );
mMixerBoard->Destroy();
mMixerBoard = NULL;
mMixerBoard = safenew MixerBoard(pProject, this, pos, siz);
//wxLogDebug("Created NEW board %p", mMixerBoard );
mMixerBoard->UpdateTrackClusters();
mMixerBoard->SetSize( siz );
this->SetSize( siz2 );
}