1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-20 14:47:49 +02:00
dofuuz 474f05d414 New colormap for Spectrogram
- Add color scheme preference

Add 'Spectro. Color scheme' to 'Set Track Visuals' macro command
2021-06-15 12:41:44 -04:00

812 lines
27 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
SpectrumView.cpp
Paul Licameli split from WaveTrackView.cpp
**********************************************************************/
#include "SpectrumView.h"
#include "SpectrumVRulerControls.h"
#include "WaveTrackView.h"
#include "WaveTrackViewConstants.h"
#include "../../../../AColor.h"
#include "../../../../Prefs.h"
#include "../../../../NumberScale.h"
#include "../../../../TrackArtist.h"
#include "../../../../TrackPanelDrawingContext.h"
#include "../../../../ViewInfo.h"
#include "../../../../WaveClip.h"
#include "../../../../WaveTrack.h"
#include "../../../../prefs/SpectrogramSettings.h"
#include <wx/dcmemory.h>
#include <wx/graphics.h>
static WaveTrackSubView::Type sType{
WaveTrackViewConstants::Spectrum,
{ wxT("Spectrogram"), XXO("&Spectrogram") }
};
static WaveTrackSubViewType::RegisteredType reg{ sType };
SpectrumView::~SpectrumView() = default;
bool SpectrumView::IsSpectral() const
{
return true;
}
std::vector<UIHandlePtr> SpectrumView::DetailedHitTest(
const TrackPanelMouseState &state,
const AudacityProject *pProject, int currentTool, bool bMultiTool )
{
const auto wt = std::static_pointer_cast< WaveTrack >( FindTrack() );
return WaveTrackSubView::DoDetailedHitTest(
state, pProject, currentTool, bMultiTool, wt
).second;
}
void SpectrumView::DoSetMinimized( bool minimized )
{
auto wt = static_cast<WaveTrack*>( FindTrack().get() );
#ifdef EXPERIMENTAL_HALF_WAVE
bool bHalfWave;
gPrefs->Read(wxT("/GUI/CollapseToHalfWave"), &bHalfWave, false);
if( bHalfWave && minimized)
{
// It is all right to set the top of scale to a huge number,
// not knowing the track rate here -- because when retrieving the
// value, then we pass in a sample rate and clamp it above to the
// Nyquist frequency.
constexpr auto max = std::numeric_limits<float>::max();
const bool spectrumLinear =
(wt->GetSpectrogramSettings().scaleType ==
SpectrogramSettings::stLinear);
// Zoom out full
wt->SetSpectrumBounds( spectrumLinear ? 0.0f : 1.0f, max );
}
#endif
TrackView::DoSetMinimized( minimized );
}
auto SpectrumView::SubViewType() const -> const Type &
{
return sType;
}
std::shared_ptr<TrackVRulerControls> SpectrumView::DoGetVRulerControls()
{
return std::make_shared<SpectrumVRulerControls>( shared_from_this() );
}
namespace
{
static inline float findValue
(const float *spectrum, float bin0, float bin1, unsigned nBins,
bool autocorrelation, int gain, int range)
{
float value;
#if 0
// Averaging method
if ((int)(bin1) == (int)(bin0)) {
value = spectrum[(int)(bin0)];
} else {
float binwidth= bin1 - bin0;
value = spectrum[(int)(bin0)] * (1.f - bin0 + (int)bin0);
bin0 = 1 + (int)(bin0);
while (bin0 < (int)(bin1)) {
value += spectrum[(int)(bin0)];
bin0 += 1.0;
}
// Do not reference past end of freq array.
if ((int)(bin1) >= (int)nBins) {
bin1 -= 1.0;
}
value += spectrum[(int)(bin1)] * (bin1 - (int)(bin1));
value /= binwidth;
}
#else
// Maximum method, and no apportionment of any single bins over multiple pixel rows
// See Bug971
int index, limitIndex;
if (autocorrelation) {
// bin = 2 * nBins / (nBins - 1 - array_index);
// Solve for index
index = std::max(0.0f, std::min(float(nBins - 1),
(nBins - 1) - (2 * nBins) / (std::max(1.0f, bin0))
));
limitIndex = std::max(0.0f, std::min(float(nBins - 1),
(nBins - 1) - (2 * nBins) / (std::max(1.0f, bin1))
));
}
else {
index = std::min<int>(nBins - 1, (int)(floor(0.5 + bin0)));
limitIndex = std::min<int>(nBins, (int)(floor(0.5 + bin1)));
}
value = spectrum[index];
while (++index < limitIndex)
value = std::max(value, spectrum[index]);
#endif
if (!autocorrelation) {
// Last step converts dB to a 0.0-1.0 range
value = (value + range + gain) / (double)range;
}
value = std::min(1.0f, std::max(0.0f, value));
return value;
}
// dashCount counts both dashes and the spaces between them.
inline AColor::ColorGradientChoice
ChooseColorSet( float bin0, float bin1, float selBinLo,
float selBinCenter, float selBinHi, int dashCount, bool isSpectral )
{
if (!isSpectral)
return AColor::ColorGradientTimeSelected;
if ((selBinCenter >= 0) && (bin0 <= selBinCenter) &&
(selBinCenter < bin1))
return AColor::ColorGradientEdge;
if ((0 == dashCount % 2) &&
(((selBinLo >= 0) && (bin0 <= selBinLo) && ( selBinLo < bin1)) ||
((selBinHi >= 0) && (bin0 <= selBinHi) && ( selBinHi < bin1))))
return AColor::ColorGradientEdge;
if ((selBinLo < 0 || selBinLo < bin1) && (selBinHi < 0 || selBinHi > bin0))
return AColor::ColorGradientTimeAndFrequencySelected;
return AColor::ColorGradientTimeSelected;
}
void DrawClipSpectrum(TrackPanelDrawingContext &context,
WaveTrackCache &waveTrackCache,
const WaveClip *clip,
const wxRect & rect)
{
auto &dc = context.dc;
const auto artist = TrackArtist::Get( context );
const auto &selectedRegion = *artist->pSelectedRegion;
const auto &zoomInfo = *artist->pZoomInfo;
#ifdef PROFILE_WAVEFORM
Profiler profiler;
#endif
const WaveTrack *const track = waveTrackCache.GetTrack().get();
const SpectrogramSettings &settings = track->GetSpectrogramSettings();
const bool autocorrelation = (settings.algorithm == SpectrogramSettings::algPitchEAC);
enum { DASH_LENGTH = 10 /* pixels */ };
const ClipParameters params{
true, track, clip, rect, selectedRegion, zoomInfo };
const wxRect &hiddenMid = params.hiddenMid;
// The "hiddenMid" rect contains the part of the display actually
// containing the waveform, as it appears without the fisheye. If it's empty, we're done.
if (hiddenMid.width <= 0) {
return;
}
const double &t0 = params.t0;
const double &tOffset = params.tOffset;
const auto &ssel0 = params.ssel0;
const auto &ssel1 = params.ssel1;
const double &averagePixelsPerSample = params.averagePixelsPerSample;
const double &rate = params.rate;
const double &hiddenLeftOffset = params.hiddenLeftOffset;
const double &leftOffset = params.leftOffset;
const wxRect &mid = params.mid;
double freqLo = SelectedRegion::UndefinedFrequency;
double freqHi = SelectedRegion::UndefinedFrequency;
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
freqLo = selectedRegion.f0();
freqHi = selectedRegion.f1();
#endif
const int &colorScheme = settings.colorScheme;
const int &range = settings.range;
const int &gain = settings.gain;
#ifdef EXPERIMENTAL_FIND_NOTES
const bool &fftFindNotes = settings.fftFindNotes;
const double &findNotesMinA = settings.findNotesMinA;
const int &numberOfMaxima = settings.numberOfMaxima;
const bool &findNotesQuantize = settings.findNotesQuantize;
#endif
#ifdef EXPERIMENTAL_FFT_Y_GRID
const bool &fftYGrid = settings.fftYGrid;
#endif
dc.SetPen(*wxTRANSPARENT_PEN);
// We draw directly to a bit image in memory,
// and then paint this directly to our offscreen
// bitmap. Note that this could be optimized even
// more, but for now this is not bad. -dmazzoni
wxImage image((int)mid.width, (int)mid.height);
if (!image.IsOk())
return;
#ifdef EXPERIMENTAL_SPECTROGRAM_OVERLAY
image.SetAlpha();
unsigned char *alpha = image.GetAlpha();
#endif
unsigned char *data = image.GetData();
const auto half = settings.GetFFTLength() / 2;
const double binUnit = rate / (2 * half);
const float *freq = 0;
const sampleCount *where = 0;
bool updated;
{
const double pps = averagePixelsPerSample * rate;
updated = clip->GetSpectrogram(waveTrackCache, freq, where,
(size_t)hiddenMid.width,
t0, pps);
}
auto nBins = settings.NBins();
float minFreq, maxFreq;
track->GetSpectrumBounds(&minFreq, &maxFreq);
const SpectrogramSettings::ScaleType scaleType = settings.scaleType;
// nearest frequency to each pixel row from number scale, for selecting
// the desired fft bin(s) for display on that row
float *bins = (float*)alloca(sizeof(*bins)*(hiddenMid.height + 1));
{
const NumberScale numberScale( settings.GetScale( minFreq, maxFreq ) );
NumberScale::Iterator it = numberScale.begin(mid.height);
float nextBin = std::max( 0.0f, std::min( float(nBins - 1),
settings.findBin( *it, binUnit ) ) );
int yy;
for (yy = 0; yy < hiddenMid.height; ++yy) {
bins[yy] = nextBin;
nextBin = std::max( 0.0f, std::min( float(nBins - 1),
settings.findBin( *++it, binUnit ) ) );
}
bins[yy] = nextBin;
}
#ifdef EXPERIMENTAL_FFT_Y_GRID
const float
log2 = logf(2.0f),
scale2 = (lmax - lmin) / log2,
lmin2 = lmin / log2;
ArrayOf<bool> yGrid{size_t(mid.height)};
for (int yy = 0; yy < mid.height; ++yy) {
float n = (float(yy) / mid.height*scale2 - lmin2) * 12;
float n2 = (float(yy + 1) / mid.height*scale2 - lmin2) * 12;
float f = float(minFreq) / (fftSkipPoints + 1)*powf(2.0f, n / 12.0f + lmin2);
float f2 = float(minFreq) / (fftSkipPoints + 1)*powf(2.0f, n2 / 12.0f + lmin2);
n = logf(f / 440) / log2 * 12;
n2 = logf(f2 / 440) / log2 * 12;
if (floor(n) < floor(n2))
yGrid[yy] = true;
else
yGrid[yy] = false;
}
#endif //EXPERIMENTAL_FFT_Y_GRID
if (!updated && clip->mSpecPxCache->valid &&
((int)clip->mSpecPxCache->len == hiddenMid.height * hiddenMid.width)
&& scaleType == clip->mSpecPxCache->scaleType
&& gain == clip->mSpecPxCache->gain
&& range == clip->mSpecPxCache->range
&& minFreq == clip->mSpecPxCache->minFreq
&& maxFreq == clip->mSpecPxCache->maxFreq
#ifdef EXPERIMENTAL_FFT_Y_GRID
&& fftYGrid==fftYGridOld
#endif //EXPERIMENTAL_FFT_Y_GRID
#ifdef EXPERIMENTAL_FIND_NOTES
&& fftFindNotes == artist->fftFindNotesOld
&& findNotesMinA == artist->findNotesMinAOld
&& numberOfMaxima == artist->findNotesNOld
&& findNotesQuantize == artist->findNotesQuantizeOld
#endif
) {
// Wave clip's spectrum cache is up to date,
// and so is the spectrum pixel cache
}
else {
// Update the spectrum pixel cache
clip->mSpecPxCache = std::make_unique<SpecPxCache>(hiddenMid.width * hiddenMid.height);
clip->mSpecPxCache->valid = true;
clip->mSpecPxCache->scaleType = scaleType;
clip->mSpecPxCache->gain = gain;
clip->mSpecPxCache->range = range;
clip->mSpecPxCache->minFreq = minFreq;
clip->mSpecPxCache->maxFreq = maxFreq;
#ifdef EXPERIMENTAL_FIND_NOTES
artist->fftFindNotesOld = fftFindNotes;
artist->findNotesMinAOld = findNotesMinA;
artist->findNotesNOld = numberOfMaxima;
artist->findNotesQuantizeOld = findNotesQuantize;
#endif
#ifdef EXPERIMENTAL_FIND_NOTES
float log2 = logf( 2.0f ),
lmin = logf( minFreq ), lmax = logf( maxFreq ), scale = lmax - lmin,
lmins = lmin,
lmaxs = lmax
;
#endif //EXPERIMENTAL_FIND_NOTES
#ifdef EXPERIMENTAL_FIND_NOTES
int maxima[128];
float maxima0[128], maxima1[128];
const float
f2bin = half / (rate / 2.0f),
bin2f = 1.0f / f2bin,
minDistance = powf(2.0f, 2.0f / 12.0f),
i0 = expf(lmin) / binUnit,
i1 = expf(scale + lmin) / binUnit,
minColor = 0.0f;
const size_t maxTableSize = 1024;
ArrayOf<int> indexes{ maxTableSize };
#endif //EXPERIMENTAL_FIND_NOTES
#ifdef _OPENMP
#pragma omp parallel for
#endif
for (int xx = 0; xx < hiddenMid.width; ++xx) {
#ifdef EXPERIMENTAL_FIND_NOTES
int maximas = 0;
const int x0 = nBins * xx;
if (fftFindNotes) {
for (int i = maxTableSize - 1; i >= 0; i--)
indexes[i] = -1;
// Build a table of (most) values, put the index in it.
for (int i = (int)(i0); i < (int)(i1); i++) {
float freqi = freq[x0 + (int)(i)];
int value = (int)((freqi + gain + range) / range*(maxTableSize - 1));
if (value < 0)
value = 0;
if (value >= maxTableSize)
value = maxTableSize - 1;
indexes[value] = i;
}
// Build from the indices an array of maxima.
for (int i = maxTableSize - 1; i >= 0; i--) {
int index = indexes[i];
if (index >= 0) {
float freqi = freq[x0 + index];
if (freqi < findNotesMinA)
break;
bool ok = true;
for (int m = 0; m < maximas; m++) {
// Avoid to store very close maxima.
float maxm = maxima[m];
if (maxm / index < minDistance && index / maxm < minDistance) {
ok = false;
break;
}
}
if (ok) {
maxima[maximas++] = index;
if (maximas >= numberOfMaxima)
break;
}
}
}
// The f2pix helper macro converts a frequency into a pixel coordinate.
#define f2pix(f) (logf(f)-lmins)/(lmaxs-lmins)*hiddenMid.height
// Possibly quantize the maxima frequencies and create the pixel block limits.
for (int i = 0; i < maximas; i++) {
int index = maxima[i];
float f = float(index)*bin2f;
if (findNotesQuantize)
{
f = expf((int)(log(f / 440) / log2 * 12 - 0.5) / 12.0f*log2) * 440;
maxima[i] = f*f2bin;
}
float f0 = expf((log(f / 440) / log2 * 24 - 1) / 24.0f*log2) * 440;
maxima0[i] = f2pix(f0);
float f1 = expf((log(f / 440) / log2 * 24 + 1) / 24.0f*log2) * 440;
maxima1[i] = f2pix(f1);
}
}
int it = 0;
bool inMaximum = false;
#endif //EXPERIMENTAL_FIND_NOTES
for (int yy = 0; yy < hiddenMid.height; ++yy) {
const float bin = bins[yy];
const float nextBin = bins[yy+1];
if (settings.scaleType != SpectrogramSettings::stLogarithmic) {
const float value = findValue
(freq + nBins * xx, bin, nextBin, nBins, autocorrelation, gain, range);
clip->mSpecPxCache->values[xx * hiddenMid.height + yy] = value;
}
else {
float value;
#ifdef EXPERIMENTAL_FIND_NOTES
if (fftFindNotes) {
if (it < maximas) {
float i0 = maxima0[it];
if (yy >= i0)
inMaximum = true;
if (inMaximum) {
float i1 = maxima1[it];
if (yy + 1 <= i1) {
value = findValue(freq + x0, bin, nextBin, nBins, autocorrelation, gain, range);
if (value < findNotesMinA)
value = minColor;
}
else {
it++;
inMaximum = false;
value = minColor;
}
}
else {
value = minColor;
}
}
else
value = minColor;
}
else
#endif //EXPERIMENTAL_FIND_NOTES
{
value = findValue
(freq + nBins * xx, bin, nextBin, nBins, autocorrelation, gain, range);
}
clip->mSpecPxCache->values[xx * hiddenMid.height + yy] = value;
} // logF
} // each yy
} // each xx
} // updating cache
float selBinLo = settings.findBin( freqLo, binUnit);
float selBinHi = settings.findBin( freqHi, binUnit);
float selBinCenter = (freqLo < 0 || freqHi < 0)
? -1
: settings.findBin( sqrt(freqLo * freqHi), binUnit );
const bool isSpectral = settings.SpectralSelectionEnabled();
const bool hidden = (ZoomInfo::HIDDEN == zoomInfo.GetFisheyeState());
const int begin = hidden
? 0
: std::max(0, (int)(zoomInfo.GetFisheyeLeftBoundary(-leftOffset)));
const int end = hidden
? 0
: std::min(mid.width, (int)(zoomInfo.GetFisheyeRightBoundary(-leftOffset)));
const size_t numPixels = std::max(0, end - begin);
SpecCache specCache;
// need explicit resize since specCache.where[] accessed before Populate()
specCache.Grow(numPixels, settings, -1, t0);
if (numPixels > 0) {
for (int ii = begin; ii < end; ++ii) {
const double time = zoomInfo.PositionToTime(ii, -leftOffset) - tOffset;
specCache.where[ii - begin] = sampleCount(0.5 + rate * time);
}
specCache.Populate
(settings, waveTrackCache,
0, 0, numPixels,
clip->GetNumSamples(),
tOffset, rate,
0 // FIXME: PRL -- make reassignment work with fisheye
);
}
// build color gradient tables (not thread safe)
if (!AColor::gradient_inited)
AColor::PreComputeGradient();
// left pixel column of the fisheye
int fisheyeLeft = zoomInfo.GetFisheyeLeftBoundary(-leftOffset);
// Bug 2389 - always draw at least one pixel of selection.
int selectedX = zoomInfo.TimeToPosition(selectedRegion.t0(), -leftOffset);
#ifdef _OPENMP
#pragma omp parallel for
#endif
for (int xx = 0; xx < mid.width; ++xx) {
int correctedX = xx + leftOffset - hiddenLeftOffset;
// in fisheye mode the time scale has changed, so the row values aren't cached
// in the loop above, and must be fetched from fft cache
float* uncached;
if (!zoomInfo.InFisheye(xx, -leftOffset)) {
uncached = 0;
}
else {
int specIndex = (xx - fisheyeLeft) * nBins;
wxASSERT(specIndex >= 0 && specIndex < (int)specCache.freq.size());
uncached = &specCache.freq[specIndex];
}
// zoomInfo must be queried for each column since with fisheye enabled
// time between columns is variable
auto w0 = sampleCount(0.5 + rate *
(zoomInfo.PositionToTime(xx, -leftOffset) - tOffset));
auto w1 = sampleCount(0.5 + rate *
(zoomInfo.PositionToTime(xx+1, -leftOffset) - tOffset));
bool maybeSelected = ssel0 <= w0 && w1 < ssel1;
maybeSelected = maybeSelected || (xx == selectedX);
for (int yy = 0; yy < hiddenMid.height; ++yy) {
const float bin = bins[yy];
const float nextBin = bins[yy+1];
// For spectral selection, determine what colour
// set to use. We use a darker selection if
// in both spectral range and time range.
AColor::ColorGradientChoice selected = AColor::ColorGradientUnselected;
// If we are in the time selected range, then we may use a different color set.
if (maybeSelected)
selected =
ChooseColorSet(bin, nextBin, selBinLo, selBinCenter, selBinHi,
(xx + leftOffset - hiddenLeftOffset) / DASH_LENGTH, isSpectral);
const float value = uncached
? findValue(uncached, bin, nextBin, nBins, autocorrelation, gain, range)
: clip->mSpecPxCache->values[correctedX * hiddenMid.height + yy];
unsigned char rv, gv, bv;
GetColorGradient(value, selected, colorScheme, &rv, &gv, &bv);
#ifdef EXPERIMENTAL_FFT_Y_GRID
if (fftYGrid && yGrid[yy]) {
rv /= 1.1f;
gv /= 1.1f;
bv /= 1.1f;
}
#endif //EXPERIMENTAL_FFT_Y_GRID
int px = ((mid.height - 1 - yy) * mid.width + xx);
#ifdef EXPERIMENTAL_SPECTROGRAM_OVERLAY
// More transparent the closer to zero intensity.
alpha[px]= wxMin( 200, (value+0.3) * 500) ;
#endif
px *=3;
data[px++] = rv;
data[px++] = gv;
data[px] = bv;
} // each yy
} // each xx
wxBitmap converted = wxBitmap(image);
wxMemoryDC memDC;
memDC.SelectObject(converted);
dc.Blit(mid.x, mid.y, mid.width, mid.height, &memDC, 0, 0, wxCOPY, FALSE);
// Draw clip edges, as also in waveform view, which improves the appearance
// of split views
params.DrawClipEdges( dc, rect );
}
}
void SpectrumView::DoDraw( TrackPanelDrawingContext &context,
const WaveTrack *track,
const wxRect & rect )
{
const auto artist = TrackArtist::Get( context );
const auto &blankSelectedBrush = artist->blankSelectedBrush;
const auto &blankBrush = artist->blankBrush;
TrackArt::DrawBackgroundWithSelection(
context, rect, track, blankSelectedBrush, blankBrush );
WaveTrackCache cache(track->SharedPointer<const WaveTrack>());
for (const auto &clip: track->GetClips())
DrawClipSpectrum( context, cache, clip.get(), rect );
DrawBoldBoundaries( context, track, rect );
}
void SpectrumView::Draw(
TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass )
{
if ( iPass == TrackArtist::PassTracks ) {
auto &dc = context.dc;
// Update cache for locations, e.g. cutlines and merge points
// Bug2588: do this for both channels, even if one is not drawn, so that
// cut-line editing (which depends on the locations cache) works properly.
// If both channels are visible, we will duplicate this effort, but that
// matters little.
for( auto channel:
TrackList::Channels(static_cast<WaveTrack*>(FindTrack().get())) )
channel->UpdateLocationsCache();
const auto wt = std::static_pointer_cast<const WaveTrack>(
FindTrack()->SubstitutePendingChangedTrack());
const auto artist = TrackArtist::Get( context );
#if defined(__WXMAC__)
wxAntialiasMode aamode = dc.GetGraphicsContext()->GetAntialiasMode();
dc.GetGraphicsContext()->SetAntialiasMode(wxANTIALIAS_NONE);
#endif
DoDraw( context, wt.get(), rect );
#if defined(__WXMAC__)
dc.GetGraphicsContext()->SetAntialiasMode(aamode);
#endif
}
CommonTrackView::Draw( context, rect, iPass );
}
static const WaveTrackSubViews::RegisteredFactory key{
[]( WaveTrackView &view ){
return std::make_shared< SpectrumView >( view );
}
};
// The following attaches the spectrogram settings item to the wave track popup
// menu. It is appropriate only to spectrum view and so is kept in this
// source file with the rest of the spectrum view implementation.
#include "WaveTrackControls.h"
#include "../../../../AudioIOBase.h"
#include "../../../../Menus.h"
#include "../../../../ProjectHistory.h"
#include "../../../../RefreshCode.h"
#include "../../../../prefs/PrefsDialog.h"
#include "../../../../prefs/SpectrumPrefs.h"
#include "../../../../widgets/AudacityMessageBox.h"
#include "../../../../widgets/PopupMenuTable.h"
namespace {
struct SpectrogramSettingsHandler : PopupMenuHandler {
PlayableTrackControls::InitMenuData *mpData{};
static SpectrogramSettingsHandler &Instance()
{
static SpectrogramSettingsHandler instance;
return instance;
}
void OnSpectrogramSettings(wxCommandEvent &);
void InitUserData(void *pUserData) override
{
mpData = static_cast< PlayableTrackControls::InitMenuData* >(pUserData);
}
void DestroyMenu() override
{
mpData = nullptr;
}
};
void SpectrogramSettingsHandler::OnSpectrogramSettings(wxCommandEvent &)
{
class ViewSettingsDialog final : public PrefsDialog
{
public:
ViewSettingsDialog(wxWindow *parent, AudacityProject &project,
const TranslatableString &title, PrefsPanel::Factories &factories,
int page)
: PrefsDialog(parent, &project, title, factories)
, mPage(page)
{
}
long GetPreferredPage() override
{
return mPage;
}
void SavePreferredPage() override
{
}
private:
const int mPage;
};
auto gAudioIO = AudioIOBase::Get();
if (gAudioIO->IsBusy()){
AudacityMessageBox(
XO(
"To change Spectrogram Settings, stop any\n playing or recording first."),
XO("Stop the Audio First"),
wxOK | wxICON_EXCLAMATION | wxCENTRE);
return;
}
WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack);
PrefsPanel::Factories factories;
// factories.push_back(WaveformPrefsFactory( pTrack ));
factories.push_back(SpectrumPrefsFactory( pTrack ));
const int page =
// (pTrack->GetDisplay() == WaveTrackViewConstants::Spectrum) ? 1 :
0;
auto title = XO("%s:").Format( pTrack->GetName() );
ViewSettingsDialog dialog(
mpData->pParent, mpData->project, title, factories, page);
if (0 != dialog.ShowModal()) {
// Redraw
AudacityProject *const project = &mpData->project;
ProjectHistory::Get( *project ).ModifyState(true);
//Bug 1725 Toolbar was left greyed out.
//This solution is overkill, but does fix the problem and is what the
//prefs dialog normally does.
MenuCreator::RebuildAllMenuBars();
mpData->result = RefreshCode::RefreshAll;
}
}
PopupMenuTable::AttachedItem sAttachment{
GetWaveTrackMenuTable(),
{ "SubViews/Extra" },
std::make_unique<PopupMenuSection>( "SpectrogramSettings",
// Conditionally add menu item for settings, if showing spectrum
PopupMenuTable::Computed< WaveTrackPopupMenuTable >(
[]( WaveTrackPopupMenuTable &table ) -> Registry::BaseItemPtr {
using Entry = PopupMenuTable::Entry;
static const int OnSpectrogramSettingsID =
GetWaveTrackMenuTable().ReserveId();
const auto pTrack = &table.FindWaveTrack();
const auto &view = WaveTrackView::Get( *pTrack );
const auto displays = view.GetDisplays();
bool hasSpectrum = (displays.end() != std::find(
displays.begin(), displays.end(),
WaveTrackSubView::Type{ WaveTrackViewConstants::Spectrum, {} }
) );
if( hasSpectrum )
// In future, we might move this to the context menu of the
// Spectrum vertical ruler.
// (But the latter won't be satisfactory without a means to
// open that other context menu with keystrokes only, and that
// would require some notion of a focused sub-view.)
return std::make_unique<Entry>( "SpectrogramSettings",
Entry::Item,
OnSpectrogramSettingsID,
XXO("S&pectrogram Settings..."),
(wxCommandEventFunction)
(&SpectrogramSettingsHandler::OnSpectrogramSettings),
SpectrogramSettingsHandler::Instance(),
[]( PopupMenuHandler &handler, wxMenu &menu, int id ){
// Bug 1253. Shouldn't open preferences if audio is busy.
// We can't change them on the fly yet anyway.
auto gAudioIO = AudioIOBase::Get();
menu.Enable(id, !gAudioIO->IsBusy());
} );
else
return nullptr;
} ) )
};
}