/********************************************************************** Audacity: A Digital Audio Editor MixerBoard.cpp Vaughan Johnson, January 2007 Dominic Mazzoni **********************************************************************/ #include "Audacity.h" #include "Experimental.h" #include "MixerBoard.h" #include #include #include #include #include // for wxSystemSettings::GetColour and wxSystemSettings::GetMetric #include "AColor.h" #include "AudioIO.h" #ifdef EXPERIMENTAL_MIDI_OUT #include "NoteTrack.h" #endif #include "Project.h" #include "TrackPanel.h" // for EVT_TRACK_PANEL_TIMER #include "UndoManager.h" #include "WaveTrack.h" #include "widgets/Meter.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, const 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) { if (event.GetEventType() == wxEVT_KILL_FOCUS) { AudacityProject::ReleaseKeyboard(this); } else { AudacityProject::CaptureKeyboard(this); } 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, 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) //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*/) : wxPanelWrapper(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 safenew wxStaticText(this, -1, mTrack->GetName(), ctrlPos, ctrlSize, wxALIGN_CENTRE | wxST_NO_AUTORESIZE | wxSUNKEN_BORDER); #else safenew 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 = safenew 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 = safenew 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 = 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, 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 = safenew 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( 1, *(mMixerBoard->mImageMuteUp), *(mMixerBoard->mImageMuteOver), *(mMixerBoard->mImageMuteDown), *(mMixerBoard->mImageMuteDisabled)); this->UpdateMute(); ctrlPos.y += MUTE_SOLO_HEIGHT; mToggleButton_Solo = safenew 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 = safenew Meter(GetActiveProject(), // AudacityProject* project, 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 event(GetSize(), GetId()); event.SetEventObject(this); GetEventHandler()->ProcessEvent(event); UpdateGain(); #endif } void MixerTrackCluster::UpdatePrefs() { mMeter->UpdatePrefs(); // in case meter range has changed HandleResize(); // in case prefs "/GUI/Solo" changed } 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"), UndoPush::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"), UndoPush::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->SetAlternateIdx(mTrack->GetSolo() ? 1 : 0); if (mTrack->GetMute()) #else mToggleButton_Mute->SetAlternateIdx(mLeftTrack->GetSolo() ? 1 : 0); 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->SetAlternateIdx(bIsSolo ? 1 : 0); } 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() { return wxColour(102, 255, 102); // same as Meter playback color } // 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::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->SetAlternateIdx(mTrack->GetSolo() ? 1 : 0); #else mProject->HandleTrackMute(mLeftTrack, mToggleButton_Mute->WasShiftDown()); mToggleButton_Mute->SetAlternateIdx(mLeftTrack->GetSolo() ? 1 : 0); #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->SetAlternateIdx(bIsSolo ? 1 : 0); // 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 = 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->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(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(); // Events from the project don't propagate directly to this other frame, so... mProject->Connect(EVT_TRACK_PANEL_TIMER, wxCommandEventHandler(MixerBoard::OnTimer), NULL, this); } 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(); mProject->Disconnect(EVT_TRACK_PANEL_TIMER, wxCommandEventHandler(MixerBoard::OnTimer), NULL, this); } void MixerBoard::UpdatePrefs() { for (unsigned int nClusterIndex = 0; nClusterIndex < mMixerTrackClusters.GetCount(); nClusterIndex++) mMixerTrackClusters[nClusterIndex]->UpdatePrefs(); } // 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 = safenew 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(const 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(wxString{ 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); } 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 (mProject->IsAudioActive()) { UpdateMeters(gAudioIO->GetStreamTime(), (mProject->mLastPlayMode == PlayMode::loopedPlay)); } // Let other listeners get the notification event.Skip(); } // 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(parent, -1, wxString::Format(_("Audacity Mixer Board%s"), ((parent->GetName() == wxEmptyString) ? wxT("") : wxString::Format(wxT(" - %s"), parent->GetName()))), 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 } 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(); project->GetCommandManager()->FilterKeyEvent(project, event, true); }