1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-09-18 09:00:52 +02:00

Fix the harder parts of drawing and interaction, for possibly nonuniform zoom

This commit is contained in:
Paul Licameli 2015-06-11 13:49:44 -04:00
commit 924b5e3496
7 changed files with 466 additions and 147 deletions

View File

@ -1456,8 +1456,10 @@ wxInt64 AudacityProject::PixelWidthBeforeTime(double scrollto) const
const double lowerBound = ScrollingLowerBoundTime();
return
mViewInfo.TimeToPosition(scrollto, 0
, true
) -
mViewInfo.TimeToPosition(lowerBound, 0
, true
);
}

View File

@ -1475,13 +1475,15 @@ struct ClipParameters
(bool spectrum, const WaveTrack *track, const WaveClip *clip, const wxRect &rect,
const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo)
{
selectedRegion;
tOffset = clip->GetOffset();
rate = clip->GetRate();
h = zoomInfo.h; //The horizontal position in seconds
pps = zoomInfo.zoom; //points-per-second--the zoom level
h = zoomInfo.PositionToTime(0, 0
, true
);
h1 = zoomInfo.PositionToTime(rect.width, 0
, true
);
double sel0 = selectedRegion.t0(); //left selection bound
double sel1 = selectedRegion.t1(); //right selection bound
@ -1494,18 +1496,17 @@ struct ClipParameters
const double trackLen = clip->GetEndTime() - clip->GetStartTime();
tstep = 1.0 / pps; // Seconds per point
tpre = h - tOffset; // offset corrected time of
// left edge of display
tpost = tpre + (rect.width * tstep); // offset corrected time of
tpost = h1 - tOffset; // offset corrected time of
// right edge of display
const double sps = 1. / rate; //seconds-per-sample
// Determine whether we should show individual samples
// or draw circular points as well
showIndividualSamples = (pps / rate > 0.5); //zoomed in a lot
showPoints = (pps / rate > 3.0); //zoomed in even more
averagePixelsPerSample = rect.width / (rate * (h1 - h));
showIndividualSamples = averagePixelsPerSample > 0.5;
// Calculate actual selection bounds so that t0 > 0 and t1 < the
// end of the track
@ -1514,7 +1515,7 @@ struct ClipParameters
if (showIndividualSamples) {
// adjustment so that the last circular point doesn't appear
// to be hanging off the end
t1 += 2. / pps;
t1 += 2. / (averagePixelsPerSample * rate);
}
// Make sure t1 (the right bound) is greater than 0
@ -1542,50 +1543,76 @@ struct ClipParameters
ssel1 = (sampleCount)(0.5 + trackLen * rate);
}
// The variable "mid" will be the rectangle containing the
// The variable "hiddenMid" will be the rectangle containing the
// actual waveform, as opposed to any blank area before
// or after the track.
mid = rect;
// or after the track, as it would appear without the fisheye.
hiddenMid = rect;
// If the left edge of the track is to the right of the left
// edge of the display, then there's some blank area to the
// left of the track. Reduce the "mid"
// rect by size of the blank area.
// left of the track. Reduce the "hiddenMid"
hiddenLeftOffset = 0;
if (tpre < 0) {
// Fill in the area to the left of the track
double delta = rect.width;
if (t0 < tpost) {
delta = (int)((t0 - tpre) * pps);
}
// Offset the rectangle containing the waveform by the width
// of the area we just erased.
mid.x += (int)delta;
mid.width -= (int)delta;
hiddenLeftOffset = std::min(rect.width, int(
zoomInfo.TimeToPosition(tOffset, 0
, true
)
));
hiddenMid.x += hiddenLeftOffset;
hiddenMid.width -= hiddenLeftOffset;
}
// If the right edge of the track is to the left of the the right
// edge of the display, then there's some blank area to the right
// of the track. Reduce the "mid" rect by the
// of the track. Reduce the "hiddenMid" rect by the
// size of the blank area.
if (tpost > t1) {
wxRect post = rect;
if (t1 > tpre) {
post.x += (int)((t1 - tpre) * pps);
const int hiddenRightOffset = std::min(rect.width, int(
zoomInfo.TimeToPosition(tOffset + t1, 0
, true
)
));
hiddenMid.width = std::max(0, hiddenRightOffset - hiddenLeftOffset);
}
post.width = rect.width - (post.x - rect.x);
// Reduce the rectangle containing the waveform by the width
// of the area we just erased.
mid.width -= post.width;
// The variable "mid" will be the rectangle containing the
// actual waveform, as distorted by the fisheye,
// as opposed to any blank area before or after the track.
mid = rect;
// If the left edge of the track is to the right of the left
// edge of the display, then there's some blank area to the
// left of the track. Reduce the "hiddenMid"
leftOffset = 0;
if (tpre < 0) {
leftOffset = std::min(rect.width, int(
zoomInfo.TimeToPosition(tOffset, 0
, false
)
));
mid.x += leftOffset;
mid.width -= leftOffset;
}
// If the right edge of the track is to the left of the the right
// edge of the display, then there's some blank area to the right
// of the track. Reduce the "hiddenMid" rect by the
// size of the blank area.
if (tpost > t1) {
const int distortedRightOffset = std::min(rect.width, int(
zoomInfo.TimeToPosition(tOffset + t1, 0
, false
)
));
mid.width = std::max(0, distortedRightOffset - leftOffset);
}
}
double tOffset;
double rate;
double h; // absolute time of left edge of display
double tstep;
double tpre; // offset corrected time of left edge of display
// double h1;
double h1;
double tpost; // offset corrected time of right edge of display
// Calculate actual selection bounds so that t0 > 0 and t1 < the
@ -1593,16 +1620,61 @@ struct ClipParameters
double t0;
double t1;
double pps;
bool showIndividualSamples, showPoints;
double averagePixelsPerSample;
bool showIndividualSamples;
sampleCount ssel0;
sampleCount ssel1;
wxRect hiddenMid;
int hiddenLeftOffset;
wxRect mid;
int leftOffset;
};
}
namespace {
struct WavePortion {
wxRect rect;
const double averageZoom;
const bool inFisheye;
WavePortion(int x, int y, int w, int h, double zoom, bool i)
: rect(x, y, w, h), averageZoom(zoom), inFisheye(i)
{}
};
void FindWavePortions
(std::vector<WavePortion> &portions, const wxRect &rect, const ZoomInfo &zoomInfo,
const ClipParameters &params)
{
// If there is no fisheye, then only one rectangle has nonzero width.
// If there is a fisheye, make rectangles for before and after
// (except when they are squeezed to zero width), and at least one for inside
// the fisheye.
ZoomInfo::Intervals intervals;
zoomInfo.FindIntervals(params.rate, intervals, rect.x);
ZoomInfo::Intervals::const_iterator it = intervals.begin(), end = intervals.end(), prev;
wxASSERT(it != end && it->position == rect.x);
const int rightmost = rect.x + rect.width;
for (int left = rect.x; left < rightmost;) {
while (it != end && it->position <= left)
prev = it++;
const int right = std::max(left, int(
it != end ? it->position : rightmost
));
const int width = right - left;
if (width > 0)
portions.push_back(
WavePortion(left, rect.y, width, rect.height,
prev->averageZoom, prev->inFisheye)
);
left = right;
}
}
}
void TrackArtist::DrawClipWaveform(WaveTrack *track,
WaveClip *clip,
wxDC & dc,
@ -1619,88 +1691,171 @@ void TrackArtist::DrawClipWaveform(WaveTrack *track,
#endif
const ClipParameters params(false, track, clip, rect, selectedRegion, zoomInfo);
const wxRect &mid = params.mid;
// The "mid" rect contains the part of the display actually
// containing the waveform. If it's empty, we're done.
if (mid.width <= 0) {
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 &pps = params.pps;
const double &tOffset = params.tOffset;
const double &tstep = params.tstep;
const bool &showIndividualSamples = params.showIndividualSamples;
const bool &showPoints = params.showPoints;
const double &h = params.h;
const double &tpre = params.tpre;
const double &tpost = params.tpost;
const double &t1 = params.t1;
const double &averagePixelsPerSample = params.averagePixelsPerSample;
const double &rate = params.rate;
double leftOffset = params.leftOffset;
const wxRect &mid = params.mid;
// Calculate sample-based offset-corrected selection
dc.SetPen(*wxTRANSPARENT_PEN);
// If we get to this point, the clip is actually visible on the
// screen, so remember the display rectangle.
clip->SetDisplayRect(mid);
clip->SetDisplayRect(hiddenMid);
// The bounds (controlled by vertical zooming; -1.0...1.0
// by default)
float zoomMin, zoomMax;
track->GetDisplayBounds(&zoomMin, &zoomMax);
// Get the values of the envelope corresponding to each pixel
// in the display, and use these to compute the height of the
// track at each pixel
double *envValues = new double[mid.width];
clip->GetEnvelope()->GetValues(envValues, mid.width, t0 + tOffset, tstep);
std::vector<double> vEnv(mid.width);
double *const env = &vEnv[0];
clip->GetEnvelope()->GetValues(env, mid.width, leftOffset, zoomInfo);
// Draw the background of the track, outlining the shape of
// the envelope and using a colored pen for the selected
// part of the waveform
DrawWaveformBackground(dc, mid.x - rect.x, mid,
envValues,
DrawWaveformBackground(dc, leftOffset, mid,
env,
zoomMin, zoomMax, dB,
selectedRegion, zoomInfo, drawEnvelope,
!track->GetSelected());
if (!showIndividualSamples) {
WaveDisplay display(mid.width);
WaveDisplay display(hiddenMid.width);
bool isLoadingOD = false;//true if loading on demand block in sequence.
const double pps =
averagePixelsPerSample * rate;
if (!params.showIndividualSamples) {
// The WaveClip class handles the details of computing the shape
// of the waveform. The only way GetWaveDisplay will fail is if
// there's a serious error, like some of the waveform data can't
// be loaded. So if the function returns false, we can just exit.
// Note that we compute the full width display even if there is a
// fisheye hiding part of it, because of the caching. If the
// fisheye moves over the background, there is then less to do when
// redrawing.
if (!clip->GetWaveDisplay(display,
t0, pps, isLoadingOD)) {
t0, pps, isLoadingOD))
return;
}
DrawMinMaxRMS(dc, mid, envValues,
// For each portion separately, we will decide to draw
// it as min/max/rms or as individual samples.
std::vector<WavePortion> portions;
FindWavePortions(portions, rect, zoomInfo, params);
const unsigned nPortions = portions.size();
// Require at least 1/2 pixel per sample for drawing individual samples.
const double threshold1 = 0.5 * rate;
// Require at least 3 pixels per sample for drawing the draggable points.
const double threshold2 = 3 * rate;
for (unsigned ii = 0; ii < nPortions; ++ii) {
WavePortion &portion = portions[ii];
const bool showIndividualSamples = portion.averageZoom > threshold1;
const bool showPoints = portion.averageZoom > threshold2;
wxRect& rect = portion.rect;
rect.Intersect(mid);
wxASSERT(rect.width >= 0);
float *useMin = 0, *useMax = 0, *useRms = 0;
int *useBl = 0;
WaveDisplay fisheyeDisplay(rect.width);
int skipped = 0, skippedLeft = 0, skippedRight = 0;
if (portion.inFisheye) {
if (!showIndividualSamples) {
fisheyeDisplay.Allocate();
const sampleCount numSamples = clip->GetNumSamples();
// Get wave display data for different magnification
int jj = 0;
for (; jj < rect.width; ++jj) {
const double time =
zoomInfo.PositionToTime(jj, -leftOffset) - tOffset;
const sampleCount sample = (sampleCount)floor(time * rate + 0.5);
if (sample < 0) {
++rect.x;
++skippedLeft;
continue;
}
if (sample >= numSamples)
break;
fisheyeDisplay.where[jj - skippedLeft] = sample;
}
skippedRight = rect.width - jj;
skipped = skippedRight + skippedLeft;
rect.width -= skipped;
// where needs a sentinel
if (jj > 0)
fisheyeDisplay.where[jj - skippedLeft] =
1 + fisheyeDisplay.where[jj - skippedLeft - 1];
fisheyeDisplay.width -= skipped;
// Get a wave display for the fisheye, uncached.
if (rect.width > 0)
if (!clip->GetWaveDisplay(
fisheyeDisplay, t0, -1.0, // ignored
isLoadingOD))
continue; // serious error. just don't draw??
useMin = fisheyeDisplay.min;
useMax = fisheyeDisplay.max;
useRms = fisheyeDisplay.rms;
useBl = fisheyeDisplay.bl;
}
}
else {
const int pos = leftOffset - params.hiddenLeftOffset;
useMin = display.min + pos;
useMax = display.max + pos;
useRms = display.rms + pos;
useBl = display.bl + pos;
}
leftOffset += skippedLeft;
if (rect.width > 0) {
if (!showIndividualSamples) {
std::vector<double> vEnv2(rect.width);
double *const env2 = &vEnv2[0];
clip->GetEnvelope()->GetValues(env2, rect.width, leftOffset, zoomInfo);
DrawMinMaxRMS(dc, rect, env2,
zoomMin, zoomMax, dB,
display.min, display.max, display.rms, display.bl,
useMin, useMax, useRms, useBl,
isLoadingOD, muted
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
, track->GetChannelGain(track->GetChannel())
#endif
);
}
else {
DrawIndividualSamples(dc, mid.x - rect.x, mid,
zoomMin, zoomMax, dB,
clip, zoomInfo, bigPoints, showPoints, muted);
else
DrawIndividualSamples(dc, leftOffset, rect, zoomMin, zoomMax, dB,
clip, zoomInfo,
bigPoints, showPoints, muted);
}
leftOffset += rect.width + skippedRight;
}
if (drawEnvelope) {
DrawEnvelope(dc, mid, envValues, zoomMin, zoomMax, dB);
DrawEnvelope(dc, mid, env, zoomMin, zoomMax, dB);
clip->GetEnvelope()->DrawPoints(dc, rect, zoomInfo, dB, zoomMin, zoomMax);
}
delete[] envValues;
// Draw arrows on the left side if the track extends to the left of the
// beginning of time. :)
if (h == 0.0 && tOffset < 0.0) {
@ -1880,19 +2035,26 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
enum { DASH_LENGTH = 10 /* pixels */ };
const ClipParameters params(true, track, clip, rect, selectedRegion, zoomInfo);
const wxRect &mid = params.mid;
// The "mid" rect contains the part of the display actually
// containing the waveform. If it's empty, we're done.
if (mid.width <= 0) {
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 &pps = params.pps;
const double &tstep = params.tstep;
const double &tOffset = params.tOffset;
const double &ssel0 = params.ssel0;
const double &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;
// If we get to this point, the clip is actually visible on the
// screen, so remember the display rectangle.
clip->SetDisplayRect(hiddenMid);
double freqLo = SelectedRegion::UndefinedFrequency;
double freqHi = SelectedRegion::UndefinedFrequency;
@ -1924,16 +2086,20 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
// bitmap. Note that this could be optimized even
// more, but for now this is not bad. -dmazzoni
wxImage *image = new wxImage((int)mid.width, (int)mid.height);
if (!image)return;
if (!image)
return;
unsigned char *data = image->GetData();
const int half = GetSpectrumWindowSize(!autocorrelation) / 2;
const double binUnit = rate / (2 * half);
const float *freq = 0;
const sampleCount *where = 0;
bool updated = clip->GetSpectrogram(waveTrackCache, freq, where, mid.width,
bool updated;
{
const double pps = averagePixelsPerSample * rate;
updated = clip->GetSpectrogram(waveTrackCache, freq, where, hiddenMid.width,
t0, pps, autocorrelation);
}
int ifreq = lrint(rate / 2);
@ -1990,7 +2156,8 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
}
#endif //EXPERIMENTAL_FFT_Y_GRID
if (!updated && clip->mSpecPxCache->valid && (clip->mSpecPxCache->len == mid.height * mid.width)
if (!updated && clip->mSpecPxCache->valid &&
(clip->mSpecPxCache->len == hiddenMid.height * hiddenMid.width)
&& gain == clip->mSpecPxCache->gain
&& range == clip->mSpecPxCache->range
&& minFreq == clip->mSpecPxCache->minFreq
@ -2011,7 +2178,7 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
else {
// Update the spectrum pixel cache
delete clip->mSpecPxCache;
clip->mSpecPxCache = new SpecPxCache(mid.width * mid.height);
clip->mSpecPxCache = new SpecPxCache(hiddenMid.width * hiddenMid.height);
clip->mSpecPxCache->valid = true;
clip->mSpecPxCache->gain = gain;
clip->mSpecPxCache->range = range;
@ -2044,15 +2211,15 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
int *indexes = new int[maxTableSize];
#endif //EXPERIMENTAL_FIND_NOTES
for (int xx = 0; xx < mid.width; ++xx)
for (int xx = 0; xx < hiddenMid.width; ++xx)
{
if (!logF) {
for (int yy = 0; yy < mid.height; ++yy) {
for (int yy = 0; yy < hiddenMid.height; ++yy) {
float bin0 = float(yy) * binPerPx + minBin;
float bin1 = float(yy + 1) * binPerPx + minBin;
const float value = findValue
(freq + half * xx, bin0, bin1, half, autocorrelation, gain, range);
clip->mSpecPxCache->values[xx * mid.height + yy] = value;
clip->mSpecPxCache->values[xx * hiddenMid.height + yy] = value;
}
}
else {
@ -2099,7 +2266,7 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
}
// The f2pix helper macro converts a frequency into a pixel coordinate.
#define f2pix(f) (logf(f)-lmins)/(lmaxs-lmins)*mid.height
#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++) {
@ -2122,8 +2289,8 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
double yy2_base = exp(lmin) / binUnit;
float yy2 = yy2_base;
double exp_scale_per_height = exp(scale / mid.height);
for (int yy = 0; yy < mid.height; ++yy) {
double exp_scale_per_height = exp(scale / hiddenMid.height);
for (int yy = 0; yy < hiddenMid.height; ++yy) {
if (int(yy2) >= half)
yy2=half-1;
if (yy2<0)
@ -2167,13 +2334,12 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
value = findValue
(freq + half * xx, bin0, bin1, half, autocorrelation, gain, range);
}
clip->mSpecPxCache->values[xx * mid.height + yy] = value;
clip->mSpecPxCache->values[xx * hiddenMid.height + yy] = value;
yy2 = yy2_base;
} // each yy
} // is logF
} // each xx
} // updating cache
float selBinLo = freqLo / binUnit;
@ -2181,22 +2347,54 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
float selBinCenter =
((freqLo < 0 || freqHi < 0) ? -1 : sqrt(freqLo * freqHi)) / binUnit;
sampleCount w1 = sampleCount(0.5 + rate *
t0
sampleCount w1(0.5 + rate *
(zoomInfo.PositionToTime(0, -leftOffset) - tOffset)
);
for (int xx = 0; xx < mid.width; ++xx)
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 int numPixels = std::max(0, end - begin);
const int zeroPaddingFactor = autocorrelation ? 1 : settings.zeroPaddingFactor;
SpecCache specCache
(numPixels, autocorrelation, -1,
t0, settings.windowType,
settings.windowSize, zeroPaddingFactor, settings.frequencyGain);
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,
autocorrelation);
}
int correctedX = leftOffset - hiddenLeftOffset;
int fisheyeColumn = 0;
for (int xx = 0; xx < mid.width; ++xx, ++correctedX)
{
const bool inFisheye = zoomInfo.InFisheye(xx, -leftOffset);
float *const uncached =
inFisheye ? &specCache.freq[(fisheyeColumn++) * half] : 0;
sampleCount w0 = w1;
w1 = sampleCount(0.5 + rate *
(t0 + (xx+1) * tstep)
(zoomInfo.PositionToTime(xx + 1, -leftOffset) - tOffset)
);
// TODO: The logF and non-logF case are very similar.
// They should be merged and simplified.
if (!logF)
{
for (int yy = 0; yy < mid.height; ++yy) {
for (int yy = 0; yy < hiddenMid.height; ++yy) {
float bin0 = float(yy) * binPerPx + minBin;
float bin1 = float(yy + 1) * binPerPx + minBin;
@ -2211,12 +2409,14 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
{
bool isSpectral = ((track->GetDisplay() == WaveTrack::SpectralSelectionDisplay) ||
(track->GetDisplay() == WaveTrack::SpectralSelectionLogDisplay));
selected = ChooseColorSet( bin0, bin1, selBinLo, selBinCenter, selBinHi, xx/DASH_LENGTH, isSpectral );
selected = ChooseColorSet(bin0, bin1, selBinLo, selBinCenter, selBinHi,
(xx + leftOffset - hiddenLeftOffset) / DASH_LENGTH, isSpectral);
}
unsigned char rv, gv, bv;
const float value =
clip->mSpecPxCache->values[xx * mid.height + yy];
const float value = uncached
? findValue(uncached, bin0, bin1, half, autocorrelation, gain, range)
: clip->mSpecPxCache->values[correctedX * hiddenMid.height + yy];
GetColorGradient(value, selected, isGrayscale, &rv, &gv, &bv);
int px = ((mid.height - 1 - yy) * mid.width + xx) * 3;
data[px++] = rv;
@ -2228,8 +2428,8 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
{
double yy2_base=exp(lmin)/binUnit;
float yy2 = yy2_base;
double exp_scale_per_height = exp(scale/mid.height);
for (int yy = 0; yy < mid.height; ++yy) {
double exp_scale_per_height = exp(scale / hiddenMid.height);
for (int yy = 0; yy < hiddenMid.height; ++yy) {
if (int(yy2)>=half)
yy2=half-1;
if (yy2<0)
@ -2243,20 +2443,21 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
yy3=0;
float bin1 = float(yy3);
AColor::ColorGradientChoice selected =
AColor::ColorGradientUnselected;
AColor::ColorGradientChoice selected = AColor::ColorGradientUnselected;
// If we are in the time selected range, then we may use a different color set.
if (ssel0 <= w0 && w1 < ssel1)
{
bool isSpectral = ((track->GetDisplay() == WaveTrack::SpectralSelectionDisplay) ||
(track->GetDisplay() == WaveTrack::SpectralSelectionLogDisplay));
selected = ChooseColorSet( bin0, bin1, selBinLo, selBinCenter, selBinHi, xx/DASH_LENGTH, isSpectral );
selected = ChooseColorSet(
bin0, bin1, selBinLo, selBinCenter, selBinHi,
(xx + leftOffset - hiddenLeftOffset) / DASH_LENGTH, isSpectral);
}
const float value = clip->mSpecPxCache->values[xx * mid.height + yy];
yy2 = yy2_base;
unsigned char rv, gv, bv;
const float value = uncached
? findValue(uncached, bin0, bin1, half, autocorrelation, gain, range)
: clip->mSpecPxCache->values[correctedX * hiddenMid.height + yy];
GetColorGradient(value, selected, isGrayscale, &rv, &gv, &bv);
#ifdef EXPERIMENTAL_FFT_Y_GRID
@ -2271,13 +2472,11 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &waveTrackCache,
data[px++] = rv;
data[px++] = gv;
data[px] = bv;
} // each yy
} // logF
} // each xx
// If we get to this point, the clip is actually visible on the
// screen, so remember the display rectangle.
clip->SetDisplayRect(mid);
yy2 = yy2_base;
}
}
}
wxBitmap converted = wxBitmap(*image);

View File

@ -4927,10 +4927,31 @@ void TrackPanel::HandleVZoomButtonUp( wxMouseEvent & event )
MakeParentModifyState(true);
}
namespace {
// Is the sample horizontally nearest to the cursor sufficiently separated from
// its neighbors that the pencil tool should be allowed to drag it?
bool SampleResolutionTest(const ViewInfo &viewInfo, const WaveTrack *wt, double time, double rate)
{
// Require more than 3 pixels per sample
// Round to an exact sample time
const double adjustedTime = wt->LongSamplesToTime(wt->TimeToLongSamples(time));
const wxInt64 xx = std::max(wxInt64(0), viewInfo.TimeToPosition(adjustedTime));
ZoomInfo::Intervals intervals;
viewInfo.FindIntervals(rate, intervals);
ZoomInfo::Intervals::const_iterator it = intervals.begin(), end = intervals.end(), prev;
wxASSERT(it != end && it->position == 0);
do
prev = it++;
while (it != end && it->position <= xx);
const double threshold = 3 * rate; // three times as many pixels per second, as samples
return prev->averageZoom > threshold;
}
}
/// Determines if we can edit samples in a wave track.
/// Also pops up warning messages in certain cases where we can't.
/// @return true if we can edit the samples, false otherwise.
bool TrackPanel::IsSampleEditingPossible( wxMouseEvent & WXUNUSED(event), Track * t )
bool TrackPanel::IsSampleEditingPossible( wxMouseEvent &event, Track * t )
{
//Exit if we don't have a track
if(!t)
@ -4959,10 +4980,15 @@ bool TrackPanel::IsSampleEditingPossible( wxMouseEvent & WXUNUSED(event), Track
}
#endif
//Get rate in order to calculate the critical zoom threshold
//Find out the zoom level
bool showPoints;
{
wxRect r;
FindTrack(event.m_x, event.m_y, false, false, &r);
WaveTrack *const wt = static_cast<WaveTrack*>(t);
const double rate = wt->GetRate();
const bool showPoints = (mViewInfo->zoom / rate > 3.0);
const double time = mViewInfo->PositionToTime(event.m_x, r.x);
showPoints = SampleResolutionTest(*mViewInfo, wt, time, rate);
}
//If we aren't zoomed in far enough, show a message dialog.
if(!showPoints)
@ -7065,21 +7091,18 @@ bool TrackPanel::HitTestSamples(Track *track, wxRect &r, wxMouseEvent & event)
//Get rate in order to calculate the critical zoom threshold
double rate = wavetrack->GetRate();
//Find out the zoom level
bool showPoints = (mViewInfo->zoom / rate > 3.0);
if( !showPoints )
return false;
int displayType = wavetrack->GetDisplay();
bool dB = (WaveTrack::WaveformDBDisplay == displayType);
if (!(WaveTrack::WaveformDisplay == displayType || dB))
return false; // Not a wave, so return.
float oneSample;
const double tt = mViewInfo->PositionToTime(event.m_x, r.x);
sampleCount s0 = (sampleCount)(tt * rate + 0.5);
if (!SampleResolutionTest(*mViewInfo, wavetrack, tt, rate))
return false;
// Just get one sample.
float oneSample;
sampleCount s0 = (sampleCount)(tt * rate + 0.5);
wavetrack->Get((samplePtr)&oneSample, floatSample, s0, 1);
// Get y distance of envelope point from center line (in pixels).

View File

@ -37,6 +37,7 @@ ZoomInfo::~ZoomInfo()
/// the track as an additional parameter.
double ZoomInfo::PositionToTime(wxInt64 position,
wxInt64 origin
, bool // ignoreFisheye
) const
{
return h + (position - origin) / zoom;
@ -46,6 +47,7 @@ double ZoomInfo::PositionToTime(wxInt64 position,
/// STM: Converts a project time to screen x position.
wxInt64 ZoomInfo::TimeToPosition(double projectTime,
wxInt64 origin
, bool // ignoreFisheye
) const
{
return floor(0.5 +
@ -73,6 +75,23 @@ void ZoomInfo::ZoomBy(double multiplier)
SetZoom(zoom * multiplier);
}
void ZoomInfo::FindIntervals
(double /*rate*/, Intervals &results, wxInt64 origin) const
{
results.clear();
results.reserve(2);
const wxInt64 rightmost(origin + (0.5 + screen * zoom));
wxASSERT(origin <= rightmost);
{
results.push_back(Interval(origin, zoom, false));
}
if (origin < rightmost)
results.push_back(Interval(rightmost, 0, false));
wxASSERT(!results.empty() && results[0].position == origin);
}
ViewInfo::ViewInfo(double start, double screenDuration, double pixelsPerSecond)
: ZoomInfo(start, screenDuration, pixelsPerSecond)
, selectedRegion()

View File

@ -11,6 +11,7 @@
#ifndef __AUDACITY_VIEWINFO__
#define __AUDACITY_VIEWINFO__
#include <vector>
#include "SelectedRegion.h"
@ -31,6 +32,8 @@ public:
double h; // h pos in secs
double screen; // screen width in secs
protected:
double zoom; // pixels per second
public:
@ -41,6 +44,7 @@ public:
// origin specifies the pixel corresponding to time h
double PositionToTime(wxInt64 position,
wxInt64 origin = 0
, bool ignoreFisheye = false
) const;
// do NOT use this once to convert a duration to a pixel width!
@ -49,6 +53,7 @@ public:
// origin specifies the pixel corresponding to time h
wxInt64 TimeToPosition(double time,
wxInt64 origin = 0
, bool ignoreFisheye = false
) const;
double OffsetTimeByPixels(double time, wxInt64 offset) const
@ -78,6 +83,46 @@ public:
// Limits zoom to certain bounds
// multipliers above 1.0 zoom in, below out
void ZoomBy(double multiplier);
struct Interval {
const wxInt64 position; const double averageZoom; const bool inFisheye;
Interval(wxInt64 p, double z, bool i)
: position(p), averageZoom(z), inFisheye(i) {}
};
typedef std::vector<Interval> Intervals;
// Find an increasing sequence of pixel positions. Each is the start
// of an interval, or is the end position.
// Each of the disjoint intervals should be drawn
// separately.
// It is guaranteed that there is at least one entry and the position of the
// first entry equals origin.
// @param origin specifies the pixel position corresponding to time ViewInfo::h.
void FindIntervals
(double rate, Intervals &results, wxInt64 origin = 0) const;
enum FisheyeState {
HIDDEN,
PINNED,
NUM_STATES,
};
FisheyeState GetFisheyeState() const
{ return HIDDEN; } // stub
// Return true if the mouse position is anywhere in the fisheye
// origin specifies the pixel corresponding to time h
bool InFisheye(wxInt64 position, wxInt64 origin = 0) const
{origin; return false;} // stub
// These accessors ignore the fisheye hiding state.
// Inclusive:
wxInt64 GetFisheyeLeftBoundary(wxInt64 origin = 0) const
{origin; return 0;} // stub
// Exclusive:
wxInt64 GetFisheyeRightBoundary(wxInt64 origin = 0) const
{origin; return 0;} // stub
};
class AUDACITY_DLL_API ViewInfo

View File

@ -98,8 +98,8 @@ using std::max;
Ruler::Ruler()
{
mMin = 0.0;
mMax = 100.0;
mMin = mHiddenMin = 0.0;
mMax = mHiddenMax = 100.0;
mOrientation = wxHORIZONTAL;
mSpacing = 6;
mHasSetSpacing = false;
@ -233,14 +233,27 @@ void Ruler::SetOrientation(int orient)
}
void Ruler::SetRange(double min, double max)
{
SetRange(min, max, min, max);
}
void Ruler::SetRange
(double min, double max, double hiddenMin, double hiddenMax)
{
// For a horizontal ruler,
// min is the value in the center of pixel "left",
// max is the value in the center of pixel "right".
if (mMin != min || mMax != max) {
// In the special case of a time ruler,
// hiddenMin and hiddenMax are values that would be shown with the fisheye
// turned off. In other cases they equal min and max respectively.
if (mMin != min || mMax != max ||
mHiddenMin != hiddenMin || mHiddenMax != hiddenMax) {
mMin = min;
mMax = max;
mHiddenMin = hiddenMin;
mHiddenMax = hiddenMax;
Invalidate();
}
@ -1070,7 +1083,11 @@ void Ruler::Update(TimeTrack* timetrack)// Envelope *speedEnv, long minSpeed, lo
} else if(mLog==false) {
double UPP = (mMax-mMin)/mLength; // Units per pixel
// Use the "hidden" min and max to determine the tick size.
// That may make a difference with fisheye.
// Otherwise you may see the tick size for the whole ruler change
// when the fisheye approaches start or end.
double UPP = (mHiddenMax-mHiddenMin)/mLength; // Units per pixel
FindLinearTickSizes(UPP);
// Left and Right Edges
@ -1843,14 +1860,18 @@ void AdornedRulerPanel::OnSize(wxSizeEvent & WXUNUSED(evt))
Refresh( false );
}
double AdornedRulerPanel::Pos2Time(int p)
double AdornedRulerPanel::Pos2Time(int p, bool ignoreFisheye)
{
return mViewInfo->PositionToTime(p, mLeftOffset);
return mViewInfo->PositionToTime(p, mLeftOffset
, ignoreFisheye
);
}
int AdornedRulerPanel::Time2Pos(double t)
int AdornedRulerPanel::Time2Pos(double t, bool ignoreFisheye)
{
return mViewInfo->TimeToPosition(t, mLeftOffset);
return mViewInfo->TimeToPosition(t, mLeftOffset
, ignoreFisheye
);
}
@ -2360,10 +2381,12 @@ void AdornedRulerPanel::DoDrawBorder(wxDC * dc)
void AdornedRulerPanel::DoDrawMarks(wxDC * dc, bool /*text */ )
{
const double min = Pos2Time(0);
const double hiddenMin = Pos2Time(0, true);
const double max = Pos2Time(mInner.width);
const double hiddenMax = Pos2Time(mInner.width, true);
ruler.SetTickColour( theTheme.Colour( clrTrackPanelText ) );
ruler.SetRange( min, max );
ruler.SetRange( min, max, hiddenMin, hiddenMax );
ruler.Draw( *dc );
}

View File

@ -56,6 +56,13 @@ class AUDACITY_DLL_API Ruler {
// (at the center of the pixel, in both cases)
void SetRange(double min, double max);
// An overload needed for the special case of fisheye
// min is the value at (x, y)
// max is the value at (x+width, y+height)
// hiddenMin, hiddenMax are the values that would be shown without the fisheye.
// (at the center of the pixel, in both cases)
void SetRange(double min, double max, double hiddenMin, double hiddenMax);
//
// Optional Ruler Parameters
//
@ -167,6 +174,7 @@ private:
bool mUserFonts;
double mMin, mMax;
double mHiddenMin, mHiddenMax;
double mMajor;
double mMinor;
@ -305,8 +313,8 @@ private:
void DrawQuickPlayIndicator(wxDC * dc, bool clear /*delete old only*/);
void DoDrawPlayRegion(wxDC * dc);
double Pos2Time(int p);
int Time2Pos(double t);
double Pos2Time(int p, bool ignoreFisheye = false);
int Time2Pos(double t, bool ignoreFisheye = false);
bool IsWithinMarker(int mousePosX, double markerTime);