705 lines
22 KiB
C++
705 lines
22 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
Contrast.cpp
|
|
|
|
\class ContrastDialog
|
|
\brief Dialog used for Contrast menu item
|
|
|
|
*//*******************************************************************/
|
|
|
|
#include "../Audacity.h"
|
|
#include "Contrast.h"
|
|
|
|
#include "../CommonCommandFlags.h"
|
|
#include "../WaveTrack.h"
|
|
#include "../Prefs.h"
|
|
#include "../Project.h"
|
|
#include "../ProjectSettings.h"
|
|
#include "../ProjectWindow.h"
|
|
#include "../ShuttleGui.h"
|
|
#include "../FileNames.h"
|
|
#include "../ViewInfo.h"
|
|
#include "../widgets/HelpSystem.h"
|
|
#include "../widgets/NumericTextCtrl.h"
|
|
#include "../widgets/AudacityMessageBox.h"
|
|
#include "../widgets/ErrorDialog.h"
|
|
|
|
#include <cmath>
|
|
#include <limits>
|
|
|
|
#if defined(__WXMSW__) && !defined(__CYGWIN__)
|
|
#include <float.h>
|
|
#define finite(x) _finite(x)
|
|
#endif
|
|
|
|
#include <wx/button.h>
|
|
#include <wx/filedlg.h>
|
|
#include <wx/valtext.h>
|
|
#include <wx/log.h>
|
|
#include <wx/wfstream.h>
|
|
#include <wx/txtstrm.h>
|
|
|
|
#include "../PlatformCompatibility.h"
|
|
|
|
#define DB_MAX_LIMIT 0.0 // Audio is massively distorted.
|
|
#define WCAG2_PASS 20.0 // dB difference required to pass WCAG2 test.
|
|
|
|
|
|
bool ContrastDialog::GetDB(float &dB)
|
|
{
|
|
float rms = float(0.0);
|
|
|
|
// For stereo tracks: sqrt((mean(L)+mean(R))/2)
|
|
double meanSq = 0.0;
|
|
|
|
auto p = FindProjectFromWindow( this );
|
|
auto range =
|
|
TrackList::Get( *p ).SelectedLeaders< const WaveTrack >();
|
|
auto numberSelectedTracks = range.size();
|
|
if (numberSelectedTracks > 1) {
|
|
AudacityMessageDialog m(
|
|
nullptr,
|
|
XO("You can only measure one track at a time."),
|
|
XO("Error"),
|
|
wxOK);
|
|
m.ShowModal();
|
|
return false;
|
|
}
|
|
if(numberSelectedTracks == 0) {
|
|
AudacityMessageDialog m(
|
|
nullptr,
|
|
XO("Please select an audio track."),
|
|
XO("Error"),
|
|
wxOK);
|
|
m.ShowModal();
|
|
return false;
|
|
}
|
|
|
|
const auto channels = TrackList::Channels( *range.begin() );
|
|
for ( auto t : channels ) {
|
|
wxASSERT(mT0 <= mT1);
|
|
|
|
// Ignore whitespace beyond ends of track.
|
|
if(mT0 < t->GetStartTime())
|
|
mT0 = t->GetStartTime();
|
|
if(mT1 > t->GetEndTime())
|
|
mT1 = t->GetEndTime();
|
|
|
|
auto SelT0 = t->TimeToLongSamples(mT0);
|
|
auto SelT1 = t->TimeToLongSamples(mT1);
|
|
|
|
if(SelT0 > SelT1)
|
|
{
|
|
AudacityMessageDialog m(
|
|
nullptr,
|
|
XO("Invalid audio selection.\nPlease ensure that audio is selected."),
|
|
XO("Error"),
|
|
wxOK);
|
|
m.ShowModal();
|
|
return false;
|
|
}
|
|
|
|
if(SelT0 == SelT1)
|
|
{
|
|
AudacityMessageDialog m(
|
|
nullptr,
|
|
XO("Nothing to measure.\nPlease select a section of a track."),
|
|
XO("Error"),
|
|
wxOK);
|
|
m.ShowModal();
|
|
return false;
|
|
}
|
|
|
|
// Don't throw in this analysis dialog
|
|
rms = t->GetRMS(mT0, mT1, false);
|
|
meanSq += rms * rms;
|
|
}
|
|
// TODO: This works for stereo, provided the audio clips are in both channels.
|
|
// We should really count gaps between clips as silence.
|
|
rms = (meanSq > 0.0)
|
|
? sqrt( meanSq/static_cast<double>( channels.size() ) )
|
|
: 0.0;
|
|
|
|
// Gives warning C4056, Overflow in floating-point constant arithmetic
|
|
// -INFINITY is intentional here.
|
|
// Looks like we are stuck with this warning, as
|
|
// #pragma warning( disable : 4056)
|
|
// even around the whole function does not disable it successfully.
|
|
|
|
dB = (rms == 0.0)? -INFINITY : LINEAR_TO_DB(rms);
|
|
return true;
|
|
}
|
|
|
|
void ContrastDialog::SetStartAndEndTime()
|
|
{
|
|
auto p = FindProjectFromWindow( this );
|
|
auto &selectedRegion = ViewInfo::Get( *p ).selectedRegion;
|
|
mT0 = selectedRegion.t0();
|
|
mT1 = selectedRegion.t1();
|
|
}
|
|
|
|
|
|
// WDR: class implementations
|
|
|
|
//----------------------------------------------------------------------------
|
|
// ContrastDialog
|
|
//----------------------------------------------------------------------------
|
|
|
|
// WDR: event table for ContrastDialog
|
|
|
|
enum {
|
|
ID_BUTTON_USECURRENTF = 10001,
|
|
ID_BUTTON_USECURRENTB,
|
|
//ID_BUTTON_GETURL,
|
|
ID_BUTTON_EXPORT,
|
|
ID_BUTTON_RESET,
|
|
//ID_BUTTON_CLOSE,
|
|
ID_FOREGROUNDSTART_T,
|
|
ID_FOREGROUNDEND_T,
|
|
ID_BACKGROUNDSTART_T,
|
|
ID_BACKGROUNDEND_T,
|
|
ID_FOREGROUNDDB_TEXT,
|
|
ID_BACKGROUNDDB_TEXT,
|
|
ID_RESULTS_TEXT,
|
|
ID_RESULTSDB_TEXT
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE(ContrastDialog,wxDialogWrapper)
|
|
EVT_BUTTON(ID_BUTTON_USECURRENTF, ContrastDialog::OnGetForeground)
|
|
EVT_BUTTON(ID_BUTTON_USECURRENTB, ContrastDialog::OnGetBackground)
|
|
EVT_BUTTON(wxID_HELP, ContrastDialog::OnGetURL)
|
|
EVT_BUTTON(ID_BUTTON_EXPORT, ContrastDialog::OnExport)
|
|
EVT_BUTTON(ID_BUTTON_RESET, ContrastDialog::OnReset)
|
|
EVT_BUTTON(wxID_CANCEL, ContrastDialog::OnClose)
|
|
END_EVENT_TABLE()
|
|
|
|
void ContrastDialog::OnChar(wxKeyEvent &event)
|
|
{
|
|
// Is this still required?
|
|
if (event.GetKeyCode() == WXK_TAB) {
|
|
// pass to next handler
|
|
event.Skip();
|
|
return;
|
|
}
|
|
|
|
// ignore any other key
|
|
event.Skip(false);
|
|
return;
|
|
}
|
|
|
|
/* i18n-hint: WCAG2 is the 'Web Content Accessibility Guidelines (WCAG) 2.0', see http://www.w3.org/TR/WCAG20/ */
|
|
ContrastDialog::ContrastDialog(wxWindow * parent, wxWindowID id,
|
|
const TranslatableString & title,
|
|
const wxPoint & pos):
|
|
wxDialogWrapper(parent, id, title, pos, wxDefaultSize,
|
|
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX )
|
|
{
|
|
SetName();
|
|
|
|
mT0 = 0.0;
|
|
mT1 = 0.0;
|
|
foregrounddB = 0.0;
|
|
backgrounddB = 0.0;
|
|
mForegroundIsDefined = false;
|
|
mBackgroundIsDefined = false;
|
|
|
|
// NULL out the control members until the controls are created.
|
|
mForegroundStartT = NULL;
|
|
mForegroundEndT = NULL;
|
|
mBackgroundStartT = NULL;
|
|
mBackgroundEndT = NULL;
|
|
wxTextValidator vld(wxFILTER_NUMERIC);
|
|
wxString number;
|
|
|
|
auto p = FindProjectFromWindow( this );
|
|
const auto &settings = ProjectSettings::Get( *p );
|
|
mProjectRate = settings.GetRate();
|
|
|
|
const auto options = NumericTextCtrl::Options{}
|
|
.AutoPos(true)
|
|
.MenuEnabled(false)
|
|
.ReadOnly(true);
|
|
|
|
ShuttleGui S(this, eIsCreating);
|
|
|
|
S.SetBorder(5);
|
|
S.StartHorizontalLay(wxCENTER, false);
|
|
{
|
|
/* i18n-hint: RMS abbreviates root mean square, a certain averaging method */
|
|
S.AddTitle(XO(
|
|
"Contrast Analyzer, for measuring RMS volume differences between two selections of audio."));
|
|
}
|
|
S.EndHorizontalLay();
|
|
S.StartStatic( XO("Parameters") );
|
|
{
|
|
S.StartMultiColumn(5, wxEXPAND);
|
|
{
|
|
|
|
// Headings
|
|
S.AddFixedText( {} ); // spacer
|
|
S.AddFixedText(XO("Start"));
|
|
S.AddFixedText(XO("End"));
|
|
S.AddFixedText( {} ); // spacer
|
|
S.AddFixedText(XO("Volume "));
|
|
|
|
//Foreground
|
|
S.AddFixedText(XO("&Foreground:"), false);
|
|
if (S.GetMode() == eIsCreating)
|
|
{
|
|
mForegroundStartT = safenew
|
|
NumericTextCtrl(S.GetParent(), ID_FOREGROUNDSTART_T,
|
|
NumericConverter::TIME,
|
|
NumericConverter::HundredthsFormat(),
|
|
0.0,
|
|
mProjectRate,
|
|
options);
|
|
}
|
|
S.Name(XO("Foreground start time"))
|
|
.AddWindow(mForegroundStartT);
|
|
|
|
if (S.GetMode() == eIsCreating)
|
|
{
|
|
mForegroundEndT = safenew
|
|
NumericTextCtrl(S.GetParent(), ID_FOREGROUNDEND_T,
|
|
NumericConverter::TIME,
|
|
NumericConverter::HundredthsFormat(),
|
|
0.0,
|
|
mProjectRate,
|
|
options);
|
|
}
|
|
S.Name(XO("Foreground end time"))
|
|
.AddWindow(mForegroundEndT);
|
|
|
|
m_pButton_UseCurrentF = S.Id(ID_BUTTON_USECURRENTF).AddButton(XO("&Measure selection"));
|
|
mForegroundRMSText = S.Id(ID_FOREGROUNDDB_TEXT)
|
|
.ConnectRoot(wxEVT_KEY_DOWN,
|
|
&ContrastDialog::OnChar)
|
|
.AddTextBox( {}, wxT(""), 17);
|
|
|
|
//Background
|
|
S.AddFixedText(XO("&Background:"));
|
|
if (S.GetMode() == eIsCreating)
|
|
{
|
|
mBackgroundStartT = safenew
|
|
NumericTextCtrl(S.GetParent(), ID_BACKGROUNDSTART_T,
|
|
NumericConverter::TIME,
|
|
NumericConverter::HundredthsFormat(),
|
|
0.0,
|
|
mProjectRate,
|
|
options);
|
|
}
|
|
S.Name(XO("Background start time"))
|
|
.AddWindow(mBackgroundStartT);
|
|
|
|
if (S.GetMode() == eIsCreating)
|
|
{
|
|
mBackgroundEndT = safenew
|
|
NumericTextCtrl(S.GetParent(), ID_BACKGROUNDEND_T,
|
|
NumericConverter::TIME,
|
|
NumericConverter::HundredthsFormat(),
|
|
0.0,
|
|
mProjectRate,
|
|
options);
|
|
}
|
|
S.Name(XO("Background end time"))
|
|
.AddWindow(mBackgroundEndT);
|
|
|
|
m_pButton_UseCurrentB = S.Id(ID_BUTTON_USECURRENTB).AddButton(XO("Mea&sure selection"));
|
|
mBackgroundRMSText = S.Id(ID_BACKGROUNDDB_TEXT)
|
|
.ConnectRoot(wxEVT_KEY_DOWN,
|
|
&ContrastDialog::OnChar)
|
|
.AddTextBox( {}, wxT(""), 17);
|
|
}
|
|
S.EndMultiColumn();
|
|
}
|
|
S.EndStatic();
|
|
|
|
//Result
|
|
S.StartStatic( XO("Result") );
|
|
{
|
|
S.StartMultiColumn(3, wxCENTER);
|
|
{
|
|
auto label = XO("Co&ntrast Result:");
|
|
S.AddFixedText(label);
|
|
mPassFailText = S.Id(ID_RESULTS_TEXT)
|
|
.Name(label)
|
|
.ConnectRoot(wxEVT_KEY_DOWN,
|
|
&ContrastDialog::OnChar)
|
|
.AddTextBox( {}, wxT(""), 50);
|
|
m_pButton_Reset = S.Id(ID_BUTTON_RESET).AddButton(XO("R&eset"));
|
|
|
|
label = XO("&Difference:");
|
|
S.AddFixedText(label);
|
|
mDiffText = S.Id(ID_RESULTSDB_TEXT)
|
|
.Name(label)
|
|
.ConnectRoot(wxEVT_KEY_DOWN,
|
|
&ContrastDialog::OnChar)
|
|
.AddTextBox( {}, wxT(""), 50);
|
|
m_pButton_Export = S.Id(ID_BUTTON_EXPORT).AddButton(XO("E&xport..."));
|
|
}
|
|
S.EndMultiColumn();
|
|
}
|
|
S.EndStatic();
|
|
S.AddStandardButtons(eCloseButton |eHelpButton);
|
|
#if 0
|
|
S.StartMultiColumn(3, wxEXPAND);
|
|
{
|
|
S.SetStretchyCol(1);
|
|
m_pButton_GetURL = S.Id(ID_BUTTON_GETURL).AddButton(XO("&Help"));
|
|
S.AddFixedText({}); // spacer
|
|
m_pButton_Close = S.Id(ID_BUTTON_CLOSE).AddButton(XO("&Close"));
|
|
}
|
|
S.EndMultiColumn();
|
|
#endif
|
|
Layout();
|
|
Fit();
|
|
SetMinSize(GetSize());
|
|
Center();
|
|
}
|
|
|
|
void ContrastDialog::OnGetURL(wxCommandEvent & WXUNUSED(event))
|
|
{
|
|
// Original help page is back on-line (March 2016), but the manual should be more reliable.
|
|
// http://www.eramp.com/WCAG_2_audio_contrast_tool_help.htm
|
|
HelpSystem::ShowHelp(this, wxT("Contrast"));
|
|
}
|
|
|
|
void ContrastDialog::OnClose(wxCommandEvent & WXUNUSED(event))
|
|
{
|
|
wxCommandEvent dummyEvent;
|
|
OnReset(dummyEvent);
|
|
|
|
Show(false);
|
|
}
|
|
|
|
void ContrastDialog::OnGetForeground(wxCommandEvent & /*event*/)
|
|
{
|
|
auto p = FindProjectFromWindow( this );
|
|
auto &selectedRegion = ViewInfo::Get( *p ).selectedRegion;
|
|
|
|
if( TrackList::Get( *p ).Selected< const WaveTrack >() ) {
|
|
mForegroundStartT->SetValue(selectedRegion.t0());
|
|
mForegroundEndT->SetValue(selectedRegion.t1());
|
|
}
|
|
|
|
SetStartAndEndTime();
|
|
mForegroundIsDefined = GetDB(foregrounddB);
|
|
m_pButton_UseCurrentF->SetFocus();
|
|
results();
|
|
}
|
|
|
|
void ContrastDialog::OnGetBackground(wxCommandEvent & /*event*/)
|
|
{
|
|
auto p = FindProjectFromWindow( this );
|
|
auto &selectedRegion = ViewInfo::Get( *p ).selectedRegion;
|
|
|
|
if( TrackList::Get( *p ).Selected< const WaveTrack >() ) {
|
|
mBackgroundStartT->SetValue(selectedRegion.t0());
|
|
mBackgroundEndT->SetValue(selectedRegion.t1());
|
|
}
|
|
|
|
SetStartAndEndTime();
|
|
mBackgroundIsDefined = GetDB(backgrounddB);
|
|
m_pButton_UseCurrentB->SetFocus();
|
|
results();
|
|
}
|
|
|
|
namespace {
|
|
// PRL: I gathered formatting into these functions, and eliminated some
|
|
// repetitions, and removed the redundant word "Average" as applied to RMS.
|
|
// Should these variations in formats be collapsed further?
|
|
|
|
// Pass nullptr when value is not yet defined
|
|
TranslatableString FormatRMSMessage( float *pValue )
|
|
{
|
|
|
|
/* i18n-hint: RMS abbreviates root mean square, a certain averaging method */
|
|
auto format0 = XO("RMS = %s.");
|
|
|
|
/* i18n-hint: dB abbreviates decibels */
|
|
auto format1 = XO("%s dB");
|
|
|
|
TranslatableString value;
|
|
|
|
if ( pValue ) {
|
|
if( fabs( *pValue ) != std::numeric_limits<float>::infinity() ) {
|
|
auto number = wxString::Format( wxT("%.2f"), *pValue );
|
|
value = format1.Format( number );
|
|
}
|
|
else
|
|
value = XO("zero");
|
|
}
|
|
else
|
|
value = format1.Format( "" );
|
|
|
|
return format0.Format( value );
|
|
}
|
|
|
|
wxString FormatDifference( float diffdB )
|
|
{
|
|
if( diffdB != diffdB ) // test for NaN, reliant on IEEE implementation
|
|
return _("indeterminate");
|
|
else {
|
|
if( diffdB != std::numeric_limits<float>::infinity() )
|
|
/* i18n-hint: dB abbreviates decibels */
|
|
/* i18n-hint: RMS abbreviates root mean square, a certain averaging method */
|
|
return wxString::Format(_("%.2f dB RMS"), diffdB);
|
|
else
|
|
/* i18n-hint: dB abbreviates decibels */
|
|
return _("Infinite dB difference");
|
|
}
|
|
}
|
|
|
|
TranslatableString FormatDifferenceForExport( float diffdB )
|
|
{
|
|
if( diffdB != diffdB ) //test for NaN, reliant on IEEE implementation
|
|
return XO("Difference is indeterminate.");
|
|
else
|
|
if( fabs(diffdB) != std::numeric_limits<float>::infinity() )
|
|
/* i18n-hint: dB abbreviates decibels */
|
|
/* i18n-hint: RMS abbreviates root mean square, a certain averaging method */
|
|
return XO("Difference = %.2f RMS dB.").Format( diffdB );
|
|
else
|
|
/* i18n-hint: dB abbreviates decibels */
|
|
/* i18n-hint: RMS abbreviates root mean square, a certain averaging method */
|
|
return XO("Difference = infinite RMS dB.");
|
|
}
|
|
}
|
|
|
|
void ContrastDialog::results()
|
|
{
|
|
mPassFailText->SetName(wxT(""));
|
|
mPassFailText->ChangeValue(wxT(""));
|
|
mDiffText->ChangeValue(wxT(""));
|
|
|
|
// foreground and background defined.
|
|
if(mForegroundIsDefined && mBackgroundIsDefined) {
|
|
float diffdB = std::fabs(foregrounddB - backgrounddB);
|
|
if(foregrounddB > DB_MAX_LIMIT) {
|
|
mPassFailText->ChangeValue(_("Foreground level too high"));
|
|
}
|
|
else if (backgrounddB > DB_MAX_LIMIT) {
|
|
mPassFailText->ChangeValue(_("Background level too high"));
|
|
}
|
|
else if (backgrounddB > foregrounddB) {
|
|
mPassFailText->ChangeValue(_("Background higher than foreground"));
|
|
}
|
|
else if(diffdB > WCAG2_PASS) {
|
|
/* i18n-hint: WCAG abbreviates Web Content Accessibility Guidelines */
|
|
mPassFailText->ChangeValue(_("WCAG2 Pass"));
|
|
}
|
|
else {
|
|
/* i18n-hint: WCAG abbreviates Web Content Accessibility Guidelines */
|
|
mPassFailText->ChangeValue(_("WCAG2 Fail"));
|
|
}
|
|
|
|
/* i18n-hint: i.e. difference in loudness at the moment. */
|
|
mDiffText->SetName(_("Current difference"));
|
|
mDiffText->ChangeValue( FormatDifference( diffdB ) );
|
|
}
|
|
|
|
if (mForegroundIsDefined) {
|
|
mForegroundRMSText->SetName(_("Measured foreground level")); // Read by screen-readers
|
|
if(std::isinf(- foregrounddB))
|
|
mForegroundRMSText->ChangeValue(_("zero"));
|
|
else
|
|
mForegroundRMSText->ChangeValue(wxString::Format(_("%.2f dB"), foregrounddB)); // i18n-hint: short form of 'decibels'
|
|
}
|
|
else {
|
|
mForegroundRMSText->SetName(_("No foreground measured")); // Read by screen-readers
|
|
mForegroundRMSText->ChangeValue(wxT(""));
|
|
mPassFailText->ChangeValue(_("Foreground not yet measured"));
|
|
}
|
|
|
|
if (mBackgroundIsDefined) {
|
|
mBackgroundRMSText->SetName(_("Measured background level"));
|
|
if(std::isinf(- backgrounddB))
|
|
mBackgroundRMSText->ChangeValue(_("zero"));
|
|
else
|
|
mBackgroundRMSText->ChangeValue(wxString::Format(_("%.2f dB"), backgrounddB));
|
|
}
|
|
else {
|
|
mBackgroundRMSText->SetName(_("No background measured"));
|
|
mBackgroundRMSText->ChangeValue(wxT(""));
|
|
mPassFailText->ChangeValue(_("Background not yet measured"));
|
|
}
|
|
}
|
|
|
|
void ContrastDialog::OnExport(wxCommandEvent & WXUNUSED(event))
|
|
{
|
|
// TODO: Handle silence checks better (-infinity dB)
|
|
auto project = FindProjectFromWindow( this );
|
|
wxString fName = wxT("contrast.txt");
|
|
|
|
fName = FileNames::SelectFile(FileNames::Operation::Export,
|
|
XO("Export Contrast Result As:"),
|
|
wxEmptyString,
|
|
fName,
|
|
wxT("txt"),
|
|
{ FileNames::TextFiles, FileNames::AllFiles },
|
|
wxFD_SAVE | wxRESIZE_BORDER,
|
|
this);
|
|
|
|
if (fName.empty())
|
|
return;
|
|
|
|
wxFFileOutputStream ffStream{ fName };
|
|
|
|
if (!ffStream.IsOk()) {
|
|
AudacityMessageBox( XO("Couldn't write to file: %s").Format( fName ) );
|
|
return;
|
|
}
|
|
|
|
wxTextOutputStream ss(ffStream);
|
|
|
|
ss
|
|
<< wxT("===================================") << '\n'
|
|
/* i18n-hint: WCAG abbreviates Web Content Accessibility Guidelines */
|
|
<< XO("WCAG 2.0 Success Criteria 1.4.7 Contrast Results") << '\n'
|
|
<< '\n'
|
|
<< XO("Filename = %s.").Format( project->GetFileName() ) << '\n'
|
|
<< '\n'
|
|
<< XO("Foreground") << '\n';
|
|
|
|
float t = (float)mForegroundStartT->GetValue();
|
|
int h = (int)(t/3600); // there must be a standard function for this!
|
|
int m = (int)((t - h*3600)/60);
|
|
float s = t - h*3600.0 - m*60.0;
|
|
|
|
ss
|
|
<< XO("Time started = %2d hour(s), %2d minute(s), %.2f seconds.")
|
|
.Format( h, m, s ) << '\n';
|
|
|
|
t = (float)mForegroundEndT->GetValue();
|
|
h = (int)(t/3600);
|
|
m = (int)((t - h*3600)/60);
|
|
s = t - h*3600.0 - m*60.0;
|
|
|
|
ss
|
|
<< XO("Time ended = %2d hour(s), %2d minute(s), %.2f seconds.")
|
|
.Format( h, m, s ) << '\n'
|
|
<< FormatRMSMessage( mForegroundIsDefined ? &foregrounddB : nullptr ) << '\n'
|
|
<< '\n'
|
|
<< XO("Background") << '\n';
|
|
|
|
t = (float)mBackgroundStartT->GetValue();
|
|
h = (int)(t/3600);
|
|
m = (int)((t - h*3600)/60);
|
|
s = t - h*3600.0 - m*60.0;
|
|
|
|
ss
|
|
<< XO("Time started = %2d hour(s), %2d minute(s), %.2f seconds.")
|
|
.Format( h, m, s ) << '\n';
|
|
|
|
t = (float)mBackgroundEndT->GetValue();
|
|
h = (int)(t/3600);
|
|
m = (int)((t - h*3600)/60);
|
|
s = t - h*3600.0 - m*60.0;
|
|
|
|
ss
|
|
<< XO("Time ended = %2d hour(s), %2d minute(s), %.2f seconds.")
|
|
.Format( h, m, s ) << '\n'
|
|
<< FormatRMSMessage( mBackgroundIsDefined ? &backgrounddB : nullptr ) << '\n'
|
|
<< '\n'
|
|
<< XO("Results") << '\n';
|
|
|
|
float diffdB = foregrounddB - backgrounddB;
|
|
|
|
ss
|
|
<< FormatDifferenceForExport( diffdB ) << '\n'
|
|
<< (( diffdB > 20. )
|
|
? XO("Success Criteria 1.4.7 of WCAG 2.0: Pass")
|
|
: XO("Success Criteria 1.4.7 of WCAG 2.0: Fail")) << '\n'
|
|
<< '\n'
|
|
<< XO("Data gathered") << '\n';
|
|
|
|
wxDateTime now = wxDateTime::Now();
|
|
int year = now.GetYear();
|
|
wxDateTime::Month month = now.GetMonth();
|
|
wxString monthName = now.GetMonthName(month);
|
|
int dom = now.GetDay();
|
|
int hour = now.GetHour();
|
|
int minute = now.GetMinute();
|
|
int second = now.GetSecond();
|
|
/* i18n-hint: day of month, month, year, hour, minute, second */
|
|
auto sNow = XO("%d %s %02d %02dh %02dm %02ds")
|
|
.Format( dom, monthName, year, hour, minute, second );
|
|
|
|
ss <<
|
|
sNow << '\n'
|
|
<< wxT("===================================") << '\n'
|
|
<< '\n';
|
|
}
|
|
|
|
void ContrastDialog::OnReset(wxCommandEvent & /*event*/)
|
|
{
|
|
mForegroundStartT->SetValue(0.0);
|
|
mForegroundEndT->SetValue(0.0);
|
|
mBackgroundStartT->SetValue(0.0);
|
|
mBackgroundEndT->SetValue(0.0);
|
|
mForegroundIsDefined = false;
|
|
mBackgroundIsDefined = false;
|
|
|
|
mForegroundRMSText->SetName(_("No foreground measured")); // Read by screen-readers
|
|
mBackgroundRMSText->SetName(_("No background measured"));
|
|
mForegroundRMSText->ChangeValue(wxT("")); // Displayed value
|
|
mBackgroundRMSText->ChangeValue(wxT(""));
|
|
mPassFailText->ChangeValue(wxT(""));
|
|
mDiffText->ChangeValue(wxT(""));
|
|
}
|
|
|
|
// Remaining code hooks this add-on into the application
|
|
#include "commands/CommandContext.h"
|
|
#include "commands/CommandManager.h"
|
|
#include "../commands/ScreenshotCommand.h"
|
|
|
|
namespace {
|
|
|
|
// Contrast window attached to each project is built on demand by:
|
|
AudacityProject::AttachedWindows::RegisteredFactory sContrastDialogKey{
|
|
[]( AudacityProject &parent ) -> wxWeakRef< wxWindow > {
|
|
auto &window = ProjectWindow::Get( parent );
|
|
return safenew ContrastDialog(
|
|
&window, -1, XO("Contrast Analysis (WCAG 2 compliance)"),
|
|
wxPoint{ 150, 150 }
|
|
);
|
|
}
|
|
};
|
|
|
|
// Define our extra menu item that invokes that factory
|
|
struct Handler : CommandHandlerObject {
|
|
void OnContrast(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto contrastDialog =
|
|
&project.AttachedWindows::Get< ContrastDialog >( sContrastDialogKey );
|
|
|
|
contrastDialog->CentreOnParent();
|
|
if( ScreenshotCommand::MayCapture( contrastDialog ) )
|
|
return;
|
|
contrastDialog->Show();
|
|
}
|
|
};
|
|
|
|
CommandHandlerObject &findCommandHandler(AudacityProject &) {
|
|
// Handler is not stateful. Doesn't need a factory registered with
|
|
// AudacityProject.
|
|
static Handler instance;
|
|
return instance;
|
|
}
|
|
|
|
// Register that menu item
|
|
|
|
using namespace MenuTable;
|
|
AttachedItem sAttachment{ wxT("Analyze/Analyzers/Windows"),
|
|
( FinderScope{ findCommandHandler },
|
|
Command( wxT("ContrastAnalyser"), XXO("Contrast..."),
|
|
&Handler::OnContrast,
|
|
AudioIONotBusyFlag() | WaveTracksSelectedFlag() | TimeSelectedFlag(),
|
|
wxT("Ctrl+Shift+T") ) )
|
|
};
|
|
|
|
}
|