From fbbab2ce1b31369a944b44e866fbe5236f41a156 Mon Sep 17 00:00:00 2001 From: "james.k.crook@gmail.com" Date: Fri, 24 Oct 2014 16:42:46 +0000 Subject: [PATCH] Now have three varieties of the Meter Toolbar. We have separate record and play meters. The original kind of meter is now called a combined meter. I've kept it because it can be useful when undocked if you do want both meters. I've also fixed it so that if made very narrow the meters stack vertically just as they already did horizontally. --- src/Menus.cpp | 35 +++++- src/Menus.h | 2 + src/toolbars/ControlToolBar.cpp | 13 +-- src/toolbars/MeterToolBar.cpp | 195 +++++++++++++++++++------------- src/toolbars/MeterToolBar.h | 25 +++- src/toolbars/ToolBar.cpp | 7 +- src/toolbars/ToolBar.h | 15 ++- src/toolbars/ToolManager.cpp | 46 ++++++-- src/toolbars/ToolManager.h | 2 +- src/widgets/Meter.cpp | 10 +- 10 files changed, 236 insertions(+), 114 deletions(-) diff --git a/src/Menus.cpp b/src/Menus.cpp index 1fe3cbc59..4ee4b8ab2 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -612,7 +612,11 @@ void AudacityProject::CreateMenusAndCommands() /* i18n-hint: Clicking this menu item shows the toolbar for editing*/ c->AddCheck(wxT("ShowEditTB"), _("&Edit Toolbar"), FN(OnShowEditToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag); /* i18n-hint: Clicking this menu item shows the toolbar which has sound level meters*/ - c->AddCheck(wxT("ShowMeterTB"), _("&Meter Toolbar"), FN(OnShowMeterToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag); + c->AddCheck(wxT("ShowMeterTB"), _("&Combined Meter Toolbar"), FN(OnShowMeterToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag); + /* i18n-hint: Clicking this menu item shows the toolbar with the mixer*/ + c->AddCheck(wxT("ShowRecordMeterTB"), _("&Record Meter Toolbar"), FN(OnShowRecordMeterToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag); + /* i18n-hint: Clicking this menu item shows the toolbar with the mixer*/ + c->AddCheck(wxT("ShowPlayMeterTB"), _("&Play Meter Toolbar"), FN(OnShowPlayMeterToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag); /* i18n-hint: Clicking this menu item shows the toolbar with the mixer*/ c->AddCheck(wxT("ShowMixerTB"), _("Mi&xer Toolbar"), FN(OnShowMixerToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag); /* i18n-hint: Clicking this menu item shows the toolbar for selecting audio*/ @@ -1556,6 +1560,10 @@ void AudacityProject::ModifyToolbarMenus() mToolManager->IsVisible(EditBarID)); mCommandManager.Check(wxT("ShowMeterTB"), mToolManager->IsVisible(MeterBarID)); + mCommandManager.Check(wxT("ShowRecordMeterTB"), + mToolManager->IsVisible(RecordMeterBarID)); + mCommandManager.Check(wxT("ShowPlayMeterTB"), + mToolManager->IsVisible(PlayMeterBarID)); mCommandManager.Check(wxT("ShowMixerTB"), mToolManager->IsVisible(MixerBarID)); mCommandManager.Check(wxT("ShowSelectionTB"), @@ -4577,10 +4585,35 @@ void AudacityProject::OnShowEditToolBar() void AudacityProject::OnShowMeterToolBar() { + if( !mToolManager->IsVisible( MeterBarID ) ) + { + mToolManager->Hide( PlayMeterBarID ); + mToolManager->Hide( RecordMeterBarID ); + } mToolManager->ShowHide( MeterBarID ); ModifyToolbarMenus(); } +void AudacityProject::OnShowRecordMeterToolBar() +{ + if( !mToolManager->IsVisible( RecordMeterBarID ) ) + { + mToolManager->Hide( MeterBarID ); + } + mToolManager->ShowHide( RecordMeterBarID ); + ModifyToolbarMenus(); +} + +void AudacityProject::OnShowPlayMeterToolBar() +{ + if( !mToolManager->IsVisible( PlayMeterBarID ) ) + { + mToolManager->Hide( MeterBarID ); + } + mToolManager->ShowHide( PlayMeterBarID ); + ModifyToolbarMenus(); +} + void AudacityProject::OnShowMixerToolBar() { mToolManager->ShowHide( MixerBarID ); diff --git a/src/Menus.h b/src/Menus.h index d29354631..e263fe320 100644 --- a/src/Menus.h +++ b/src/Menus.h @@ -276,6 +276,8 @@ void OnShowTransportToolBar(); void OnShowDeviceToolBar(); void OnShowEditToolBar(); void OnShowMeterToolBar(); +void OnShowRecordMeterToolBar(); +void OnShowPlayMeterToolBar(); void OnShowMixerToolBar(); void OnShowSelectionToolBar(); void OnShowToolsToolBar(); diff --git a/src/toolbars/ControlToolBar.cpp b/src/toolbars/ControlToolBar.cpp index 6694a1656..3337dd0a1 100644 --- a/src/toolbars/ControlToolBar.cpp +++ b/src/toolbars/ControlToolBar.cpp @@ -628,13 +628,9 @@ void ControlToolBar::PlayPlayRegion(double t0, double t1, void ControlToolBar::SetVUMeters(AudacityProject *p) { - MeterToolBar *bar; - bar = p->GetMeterToolBar(); - if (bar) { - Meter *play, *record; - bar->GetMeters(&play, &record); - gAudioIO->SetMeters(record, play); - } + Meter *play, *record; + MeterToolBars::GetMeters( &play, &record); + gAudioIO->SetMeters(record, play); } void ControlToolBar::PlayCurrentRegion(bool looped /* = false */, @@ -770,13 +766,12 @@ void ControlToolBar::StopPlaying(bool stopStream /* = true*/) ClearCutPreviewTracks(); mBusyProject = NULL; - // So that we continue monitoring after playing or recording. // also clean the MeterQueues AudacityProject *project = GetActiveProject(); if( project ) { project->MayStartMonitoring(); - project->GetMeterToolBar()->Clear(); + MeterToolBars::Clear(); } } diff --git a/src/toolbars/MeterToolBar.cpp b/src/toolbars/MeterToolBar.cpp index d3dd9d2d6..79008b430 100644 --- a/src/toolbars/MeterToolBar.cpp +++ b/src/toolbars/MeterToolBar.cpp @@ -47,9 +47,20 @@ BEGIN_EVENT_TABLE( MeterToolBar, ToolBar ) END_EVENT_TABLE() //Standard contructor -MeterToolBar::MeterToolBar() +MeterToolBar::MeterToolBar(int WhichMeters) : ToolBar(MeterBarID, _("Meter"), wxT("Meter"), true) { + mWhichMeters = WhichMeters; + if( mWhichMeters == kWithRecordMeter ){ + mType = RecordMeterBarID; + mLabel = _("Record Meter"); + mSection = wxT("RecordMeter"); + } + if( mWhichMeters == kWithPlayMeter ){ + mType = PlayMeterBarID; + mLabel = _("Play Meter"); + mSection = wxT("PlayMeter"); + } mSizer = NULL; mPlayMeter = NULL; mRecordMeter = NULL; @@ -59,11 +70,6 @@ MeterToolBar::~MeterToolBar() { } -void MeterToolBar::Clear() -{ - if (mPlayMeter) mPlayMeter->Clear(); - if (mRecordMeter) mRecordMeter->Clear(); -} void MeterToolBar::Create(wxWindow * parent) { @@ -79,42 +85,48 @@ void MeterToolBar::Populate() mSizer = new wxGridBagSizer(); Add( mSizer, 1, wxEXPAND ); - //JKC: Record on left, playback on right. Left to right flow - //(maybe we should do it differently for Arabic language :-) ) - mRecordMeter = new Meter( this, - wxID_ANY, - true, - wxDefaultPosition, - wxSize( 130, 55 ) ); - /* i18n-hint: (noun) The meter that shows the loudness of the audio being recorded.*/ - mRecordMeter->SetName( _("Record Meter")); - /* i18n-hint: (noun) The meter that shows the loudness of the audio being recorded. - This is the name used in screen reader software, where having 'Meter' first - apparently is helpful to partially sighted people. */ - mRecordMeter->SetLabel( _("Meter-Record") ); - mSizer->Add( mRecordMeter, wxGBPosition( 0, 0 ), wxDefaultSpan, wxEXPAND ); - - - mPlayMeter = new Meter( this, - wxID_ANY, - false, - wxDefaultPosition, - wxSize( 130, 55 ) ); - /* i18n-hint: (noun) The meter that shows the loudness of the audio playing.*/ - mPlayMeter->SetName( _("Play Meter")); - /* i18n-hint: (noun) The meter that shows the loudness of the audio playing. - This is the name used in screen reader software, where having 'Meter' first - apparently is helpful to partially sighted people. */ - mPlayMeter->SetLabel( _("Meter-Play")); - mSizer->Add( mPlayMeter, wxGBPosition( 0, 1 ), wxDefaultSpan, wxEXPAND ); + if( mWhichMeters & kWithRecordMeter ){ + //JKC: Record on left, playback on right. Left to right flow + //(maybe we should do it differently for Arabic language :-) ) + mRecordMeter = new Meter( this, + wxID_ANY, + true, + wxDefaultPosition, + wxSize( 130, 55 ) ); + /* i18n-hint: (noun) The meter that shows the loudness of the audio being recorded.*/ + mRecordMeter->SetName( _("Record Meter")); + /* i18n-hint: (noun) The meter that shows the loudness of the audio being recorded. + This is the name used in screen reader software, where having 'Meter' first + apparently is helpful to partially sighted people. */ + mRecordMeter->SetLabel( _("Meter-Record") ); + mSizer->Add( mRecordMeter, wxGBPosition( 0, 0 ), wxDefaultSpan, wxEXPAND ); + } + if( mWhichMeters & kWithPlayMeter ){ + mPlayMeter = new Meter( this, + wxID_ANY, + false, + wxDefaultPosition, + wxSize( 130, 55 ) ); + /* i18n-hint: (noun) The meter that shows the loudness of the audio playing.*/ + mPlayMeter->SetName( _("Play Meter")); + /* i18n-hint: (noun) The meter that shows the loudness of the audio playing. + This is the name used in screen reader software, where having 'Meter' first + apparently is helpful to partially sighted people. */ + mPlayMeter->SetLabel( _("Meter-Play")); + mSizer->Add( mPlayMeter, wxGBPosition( 0, 1 ), wxDefaultSpan, wxEXPAND ); + } + if( IsVisible() ) + MeterToolBars::AddMeters( mPlayMeter, mRecordMeter ); RegenerateTooltips(); } void MeterToolBar::UpdatePrefs() { - mPlayMeter->UpdatePrefs(); - mRecordMeter->UpdatePrefs(); + if( mPlayMeter ) + mPlayMeter->UpdatePrefs(); + if( mRecordMeter ) + mRecordMeter->UpdatePrefs(); RegenerateTooltips(); @@ -128,13 +140,16 @@ void MeterToolBar::UpdatePrefs() void MeterToolBar::RegenerateTooltips() { #if wxUSE_TOOLTIPS - mPlayMeter->SetToolTip( _("Playback Level") ); - mRecordMeter->SetToolTip( _("Recording Level (Click to monitor.)") ); + if( mPlayMeter ) + mPlayMeter->SetToolTip( _("Playback Level") ); + if( mRecordMeter ) + mRecordMeter->SetToolTip( _("Recording Level (Click to monitor.)") ); #endif } bool MeterToolBar::DestroyChildren() { + MeterToolBars::RemoveMeters( mPlayMeter, mRecordMeter ); mPlayMeter = NULL; mRecordMeter = NULL; @@ -151,8 +166,7 @@ void MeterToolBar::OnSize( wxSizeEvent & WXUNUSED(event) ) int width, height; // We can be resized before populating...protect against it - if( !mSizer ) - { + if( !mSizer ) { return; } @@ -160,62 +174,91 @@ void MeterToolBar::OnSize( wxSizeEvent & WXUNUSED(event) ) Layout(); // Get the usable area -// GetClientSize( &width, &height ); -// width -= mSizer->GetPosition().x; wxSize sz = GetSizer()->GetSize(); width = sz.x; height = sz.y; - // Default location for play meter - wxGBPosition pos( 0, 1 ); + int nMeters = + ((mRecordMeter ==NULL) ? 0:1) + + ((mPlayMeter ==NULL) ? 0:1); - // Two horizontal - if( width > height ) - { - if( height > 120 ) - { - // Stacked - mRecordMeter->SetMinSize( wxSize( width, ( height / 2 ) ) ); - mPlayMeter->SetMinSize( wxSize( width, ( height / 2 ) ) ); - pos.SetCol( 0 ); - pos.SetRow( 1 ); - } - else - { - // Side-by-side - mRecordMeter->SetMinSize( wxSize( ( width / 2 ), height ) ); - mPlayMeter->SetMinSize( wxSize( ( width / 2 ), height ) ); - } + bool bHorizontal = ( width > height ); + bool bEndToEnd = ( nMeters > 1 ) && wxMin( width, height ) < (60 * nMeters); - mRecordMeter->SetStyle(Meter::HorizontalStereo); - mPlayMeter->SetStyle(Meter::HorizontalStereo); - } - else - { - // Two vertical, side-by-side - mRecordMeter->SetMinSize( wxSize( ( width / 2 ), height ) ); - mPlayMeter->SetMinSize( wxSize( ( width / 2 ), height ) ); - mRecordMeter->SetStyle(Meter::VerticalStereo); - mPlayMeter->SetStyle(Meter::VerticalStereo); + // Default location for second meter + wxGBPosition pos( 0, 0 ); + // If 2 meters, share the height or width. + if( nMeters > 1 ){ + if( bHorizontal ^ bEndToEnd ){ + height /= nMeters; + pos = wxGBPosition( 1, 0 ); + } else { + width /= nMeters; + pos = wxGBPosition( 0, 1 ); + } } - // Position the play meter - mSizer->SetItemPosition( mPlayMeter, pos ); + if( mRecordMeter ) { + mRecordMeter->SetStyle(bHorizontal ? Meter::HorizontalStereo : Meter::VerticalStereo); + mRecordMeter->SetMinSize( wxSize( width, height )); + } + if( mPlayMeter ) { + mPlayMeter->SetStyle(bHorizontal ? Meter::HorizontalStereo : Meter::VerticalStereo); + mPlayMeter->SetMinSize( wxSize( width, height )); + mSizer->SetItemPosition( mPlayMeter, pos ); + } // And make it happen Layout(); } -void MeterToolBar::GetMeters(Meter **playMeter, Meter **recordMeter) +bool MeterToolBar::Expose( bool show ) +{ + if( show ) + MeterToolBars::AddMeters( mPlayMeter, mRecordMeter ); + else + MeterToolBars::RemoveMeters( mPlayMeter, mRecordMeter ); + return ToolBar::Expose( show ); +} + + + +// Locally defined - we can change implementation easily later. +namespace MeterToolBars { + Meter * mPlayMeter=NULL; + Meter * mRecordMeter=NULL; +} + +void MeterToolBars::AddMeters(Meter *playMeter, Meter *recordMeter) +{ + if( playMeter != NULL ) mPlayMeter = playMeter; + if( recordMeter != NULL ) mRecordMeter = recordMeter; +} + +void MeterToolBars::RemoveMeters(Meter *playMeter, Meter *recordMeter) +{ + if( mPlayMeter == playMeter ) mPlayMeter = NULL; + if( mRecordMeter == recordMeter ) mRecordMeter = NULL; +} + +void MeterToolBars::GetMeters(Meter **playMeter, Meter **recordMeter) { *playMeter = mPlayMeter; *recordMeter = mRecordMeter; } -void MeterToolBar::StartMonitoring() +void MeterToolBars::StartMonitoring() { + if( mRecordMeter == NULL ) + return; wxASSERT( mRecordMeter ); mRecordMeter->StartMonitoring(); //wxASSERT( mPlayMeter ); //mPlayMeter->StartMonitoring(); - } + +void MeterToolBars::Clear() +{ + if (mPlayMeter) mPlayMeter->Clear(); + if (mRecordMeter) mRecordMeter->Clear(); +} + diff --git a/src/toolbars/MeterToolBar.h b/src/toolbars/MeterToolBar.h index 0ac35f28a..ddd8db008 100644 --- a/src/toolbars/MeterToolBar.h +++ b/src/toolbars/MeterToolBar.h @@ -23,11 +23,16 @@ class wxWindow; class Meter; + +// Constants used as bit pattern +const int kWithRecordMeter = 1; +const int kWithPlayMeter = 2; + class MeterToolBar:public ToolBar { public: - MeterToolBar(); + MeterToolBar(int WhichMeters); virtual ~MeterToolBar(); void Create(wxWindow *parent); @@ -38,19 +43,17 @@ class MeterToolBar:public ToolBar { virtual void EnableDisableButtons() {}; virtual void UpdatePrefs(); - void GetMeters(Meter **playMeter, Meter **recordMeter); - void StartMonitoring(); - void Clear(); - virtual void OnSize(wxSizeEvent & event); + virtual bool Expose( bool show ); int GetInitialWidth() {return 338;} - int GetMinToolbarWidth() { return 255; } + int GetMinToolbarWidth() { return 100; } private: void OnMeterPrefsUpdated(wxCommandEvent & evt); void RegenerateTooltips(); + int mWhichMeters; wxGridBagSizer *mSizer; Meter *mPlayMeter; Meter *mRecordMeter; @@ -62,5 +65,15 @@ class MeterToolBar:public ToolBar { }; +namespace MeterToolBars { + void AddMeters(Meter *playMeter, Meter *recordMeter); + void RemoveMeters(Meter *playMeter, Meter *recordMeter); + void GetMeters(Meter **playMeter, Meter **recordMeter); + void StartMonitoring(); + void Clear(); + //Meter *mPlayMeter; + //Meter *mRecordMeter; +}; + #endif diff --git a/src/toolbars/ToolBar.cpp b/src/toolbars/ToolBar.cpp index 601ba8b13..6fc097d03 100644 --- a/src/toolbars/ToolBar.cpp +++ b/src/toolbars/ToolBar.cpp @@ -177,6 +177,11 @@ bool ToolBar::IsVisible() return mVisible; } +void ToolBar::SetVisible( bool bVisible ) +{ + mVisible = bVisible; +} + // // Show or hide the toolbar // @@ -185,7 +190,7 @@ bool ToolBar::Expose( bool show ) bool was = mVisible; mVisible = show; - + if( IsDocked() ) { Show( show ); diff --git a/src/toolbars/ToolBar.h b/src/toolbars/ToolBar.h index 9df1c0dde..40063ce96 100644 --- a/src/toolbars/ToolBar.h +++ b/src/toolbars/ToolBar.h @@ -62,6 +62,8 @@ enum TransportBarID, ToolsBarID, MeterBarID, + RecordMeterBarID, + PlayMeterBarID, MixerBarID, EditBarID, TranscriptionBarID, @@ -94,11 +96,13 @@ class ToolBar:public wxPanel void SetDocked(ToolDock *dock, bool pushed); - bool Expose(bool show = true); + virtual bool Expose(bool show = true); bool IsResizable(); bool IsVisible(); bool IsDocked(); + void SetVisible( bool bVisible ); + /// Resizable toolbars should implement this. virtual int GetInitialWidth() {return -1;} @@ -165,6 +169,10 @@ class ToolBar:public wxPanel void OnMotion(wxMouseEvent & event); void OnCaptureLost(wxMouseCaptureLostEvent & event); + protected: + wxString mLabel; + wxString mSection; + int mType; private: bool IsResizeGrabberHit( wxPoint & pos ); void Init(wxWindow *parent, int type, const wxString & title, const wxString & label); @@ -176,11 +184,6 @@ class ToolBar:public wxPanel wxSizerItem *mSpacer; wxPoint mResizeStart; - - wxString mLabel; - wxString mSection; - int mType; - ToolDock *mDock; bool mVisible; diff --git a/src/toolbars/ToolManager.cpp b/src/toolbars/ToolManager.cpp index d08c0a09c..c5107b59d 100644 --- a/src/toolbars/ToolManager.cpp +++ b/src/toolbars/ToolManager.cpp @@ -404,7 +404,9 @@ ToolManager::ToolManager( AudacityProject *parent ) // Create all of the toolbars mBars[ ToolsBarID ] = new ToolsToolBar(); mBars[ TransportBarID ] = new ControlToolBar(); - mBars[ MeterBarID ] = new MeterToolBar(); + mBars[ RecordMeterBarID ] = new MeterToolBar( kWithRecordMeter ); + mBars[ PlayMeterBarID ] = new MeterToolBar( kWithPlayMeter ); + mBars[ MeterBarID ] = new MeterToolBar( kWithPlayMeter | kWithRecordMeter ); mBars[ EditBarID ] = new EditToolBar(); mBars[ MixerBarID ] = new MixerToolBar(); mBars[ TranscriptionBarID ] = new TranscriptionToolBar(); @@ -467,7 +469,7 @@ void ToolManager::Reset() gAudioIO->SetMeters( NULL, NULL ); // Disconnect all docked bars - for( ndx = 0; ndx < ToolBarCount; ndx++ ) + for( ndx = 0; ndx < ToolBarCount; ndx++ ) { wxWindow *parent; ToolDock *dock; @@ -491,6 +493,10 @@ void ToolManager::Reset() wxCommandEvent e; bar->GetEventHandler()->ProcessEvent(e); } + else if( ndx == MeterBarID ) + { + dock = NULL; + } else { dock = mTopDock; @@ -504,14 +510,32 @@ void ToolManager::Reset() bar->SetSize(bar->GetBestFittingSize()); } #endif - dock->Dock( bar ); - Expose( ndx, true ); - - if( parent ) + if( dock != NULL ) { - parent->Destroy(); + dock->Dock( bar ); + Expose( ndx, true ); + if( parent ) + parent->Destroy(); } + else + { + // Maybe construct a new floater + // this happens if we are bouncing it out of a dock. + if( parent == NULL ) { + wxPoint pt = wxPoint( 10, 10 ); + wxWindow * pWnd = new ToolFrame( mParent, this, bar, pt ); + bar->Reparent( pWnd ); + // Put it near center of screen/window. + // The adjustments help prevent it straddling two screens, + // and if there multiple toobars the ndx means they won't overlap too much. + pWnd->Centre( wxCENTER_ON_SCREEN ); + pWnd->Move( pWnd->GetPosition() + wxSize( ndx * 10 - 200, ndx * 10 )); + } + bar->SetDocked( NULL, false ); + bar->Expose( false ); + } + } // TODO:?? // If audio was playing, we stopped the VU meters, @@ -571,6 +595,7 @@ void ToolManager::ReadConfig() gPrefs->Read( wxT("W"), &width[ ndx ], -1 ); gPrefs->Read( wxT("H"), &height[ ndx ], -1 ); + bar->SetVisible( show[ ndx ] ); // Docked or floating? if( dock ) { @@ -851,6 +876,13 @@ void ToolManager::ShowHide( int type ) } } +void ToolManager::Hide( int type ) +{ + if( !IsVisible( type ) ) + return; + ShowHide( type ); +} + // // Set the visible/hidden state of a toolbar // diff --git a/src/toolbars/ToolManager.h b/src/toolbars/ToolManager.h index 8ea472d16..770a739d0 100644 --- a/src/toolbars/ToolManager.h +++ b/src/toolbars/ToolManager.h @@ -57,7 +57,7 @@ class ToolManager:public wxEvtHandler bool IsVisible( int type ); void ShowHide( int type ); - + void Hide( int type ); void Expose( int type, bool show ); ToolBar *GetToolBar( int type ) const; diff --git a/src/widgets/Meter.cpp b/src/widgets/Meter.cpp index ff83978d3..2a54ffb76 100644 --- a/src/widgets/Meter.cpp +++ b/src/widgets/Meter.cpp @@ -1334,13 +1334,9 @@ void Meter::StartMonitoring() AudacityProject *p = GetActiveProject(); if (p) { gAudioIO->StartMonitoring(p->GetRate()); - - MeterToolBar *bar = p->GetMeterToolBar(); - if (bar) { - Meter *play, *record; - bar->GetMeters(&play, &record); - gAudioIO->SetMeters(record, play); - } + Meter *play, *record; + MeterToolBars::GetMeters( &play, &record ); + gAudioIO->SetMeters(record, play); } }