Beispiel #1
0
void FilterBank_paint (FilterBank me, Graphics g, double xmin, double xmax, double ymin, double ymax, double minimum, double maximum, int garnish) {
	if (xmax <= xmin) {
		xmin = my xmin; xmax = my xmax; 
	}
	if (ymax <= ymin) {
		ymin = my ymin; ymax = my ymax;
	}
	long ixmin, ixmax, iymin, iymax;
	(void) Matrix_getWindowSamplesX (me, xmin - 0.49999 * my dx, xmax + 0.49999 * my dx, &ixmin, &ixmax);
	(void) Matrix_getWindowSamplesY (me, ymin - 0.49999 * my dy, ymax + 0.49999 * my dy, &iymin, &iymax);
	if (maximum <= minimum) {
		(void) Matrix_getWindowExtrema (me, ixmin, ixmax, iymin, iymax, &minimum, &maximum);
	}
	if (maximum <= minimum) { 
		minimum -= 1.0; maximum += 1.0;
	}
	if (xmin >= xmax || ymin >= ymax) {
		return;
	}
	Graphics_setInner (g);
	Graphics_setWindow (g, xmin, xmax, ymin, ymax);
	Graphics_image (g, my z,
			ixmin, ixmax, Sampled_indexToX   (me, ixmin - 0.5), Sampled_indexToX   (me, ixmax + 0.5),
			iymin, iymax, SampledXY_indexToY (me, iymin - 0.5), SampledXY_indexToY (me, iymax + 0.5),
			minimum, maximum);

	Graphics_unsetInner (g);
	if (garnish) {
		Graphics_drawInnerBox (g);
		Graphics_marksLeft (g, 2, 1, 1, 0);
		Graphics_textLeft (g, 1, GetFreqScaleText (my v_getFrequencyScale ()));
		Graphics_marksBottom (g, 2, 1, 1, 0);
		Graphics_textBottom (g, 1, U"Time (s)");
	}
}
Beispiel #2
0
static void cellArrayOrImage (Matrix me, Graphics g, double xmin, double xmax, double ymin, double ymax,
	double minimum, double maximum, bool interpolate)
{
	if (xmax <= xmin) { xmin = my xmin; xmax = my xmax; }
	if (ymax <= ymin) { ymin = my ymin; ymax = my ymax; }
	long ixmin, ixmax, iymin, iymax;
	(void) Matrix_getWindowSamplesX (me, xmin - 0.49999 * my dx, xmax + 0.49999 * my dx,
		& ixmin, & ixmax);
	(void) Matrix_getWindowSamplesY (me, ymin - 0.49999 * my dy, ymax + 0.49999 * my dy,
		& iymin, & iymax);
	if (maximum <= minimum)
		(void) Matrix_getWindowExtrema (me, ixmin, ixmax, iymin, iymax, & minimum, & maximum);
	if (maximum <= minimum) { minimum -= 1.0; maximum += 1.0; }
	if (xmin >= xmax || ymin >= ymax) return;
	Graphics_setInner (g);
	Graphics_setWindow (g, xmin, xmax, ymin, ymax);
	if (interpolate)
		Graphics_image (g, my z,
			ixmin, ixmax, Sampled_indexToX   (me, ixmin - 0.5), Sampled_indexToX   (me, ixmax + 0.5),
			iymin, iymax, SampledXY_indexToY (me, iymin - 0.5), SampledXY_indexToY (me, iymax + 0.5),
			minimum, maximum);
	else
		Graphics_cellArray (g, my z,
			ixmin, ixmax, Sampled_indexToX   (me, ixmin - 0.5), Sampled_indexToX   (me, ixmax + 0.5),
			iymin, iymax, SampledXY_indexToY (me, iymin - 0.5), SampledXY_indexToY (me, iymax + 0.5),
			minimum, maximum);
	Graphics_rectangle (g, xmin, xmax, ymin, ymax);
	Graphics_unsetInner (g);
}
Beispiel #3
0
void PowerCepstrogram_paint (PowerCepstrogram me, Graphics g, double tmin, double tmax, double qmin, double qmax, double dBmaximum, int autoscaling, double dynamicRangedB, double dynamicCompression, int garnish) {
	if (tmax <= tmin) { tmin = my xmin; tmax = my xmax; }
	if (qmax <= qmin) { qmin = my ymin; qmax = my ymax; }
	long itmin, itmax, ifmin, ifmax;
	if (! Matrix_getWindowSamplesX (me, tmin - 0.49999 * my dx, tmax + 0.49999 * my dx, & itmin, & itmax) ||
		 ! Matrix_getWindowSamplesY (me, qmin - 0.49999 * my dy, qmax + 0.49999 * my dy, & ifmin, & ifmax)) {
		return;
	}
	autoMatrix thee = Data_copy (me);
	double min = 1e308, max = -min;
	for (long i = 1; i <= my ny; i++) {
		for (long j = 1; j <= my nx; j++) {
			double val = TO10LOG (my z[i][j]);
			min = val < min ? val : min;
			max = val > max ? val : max;
			thy z[i][j] = val;
		}
	}
	double dBminimum = dBmaximum - dynamicRangedB;
	if (autoscaling) {
		dBminimum = min; dBmaximum = max;
	}

	for (long j = 1; j <= my nx; j++) {
		double lmax = thy z[1][j];
		for (long i = 2; i <= my ny; i++) {
			if (thy z[i][j] > lmax) {
				lmax = thy z[i][j];
			}
		}
		double factor = dynamicCompression * (max - lmax);
		for (long i = 1; i <= my ny; i++) {
			thy z[i][j] += factor;
		}
	}
	
	Graphics_setInner (g);
	Graphics_setWindow (g, tmin, tmax, qmin, qmax);
	Graphics_image (g, thy z,
		itmin, itmax,
		Matrix_columnToX (thee.get(), itmin - 0.5),
		Matrix_columnToX (thee.get(), itmax + 0.5),
		ifmin, ifmax,
		Matrix_rowToY (thee.get(), ifmin - 0.5),
		Matrix_rowToY (thee.get(), ifmax + 0.5),
		dBminimum, dBmaximum);

	Graphics_unsetInner (g);
	if (garnish) {
		Graphics_drawInnerBox (g);
		Graphics_textBottom (g, true, U"Time (s)");
		Graphics_marksBottom (g, 2, true, true, false);
		Graphics_marksLeft (g, 2, true, true, false);
		Graphics_textLeft (g, true, U"Quefrency (s)");
	}
}
Beispiel #4
0
void Spectrogram_paintInside (Spectrogram me, Graphics g, double tmin, double tmax, double fmin, double fmax,
	double maximum, int autoscaling, double dynamic, double preemphasis, double dynamicCompression)
{
	if (tmax <= tmin) { tmin = my xmin; tmax = my xmax; }
	if (fmax <= fmin) { fmin = my ymin; fmax = my ymax; }
	long itmin, itmax, ifmin, ifmax;
	if (! Matrix_getWindowSamplesX (me, tmin - 0.49999 * my dx, tmax + 0.49999 * my dx, & itmin, & itmax) ||
		 ! Matrix_getWindowSamplesY (me, fmin - 0.49999 * my dy, fmax + 0.49999 * my dy, & ifmin, & ifmax))
		return;
	Graphics_setWindow (g, tmin, tmax, fmin, fmax);
	autoNUMvector <double> preemphasisFactor (ifmin, ifmax);
	autoNUMvector <double> dynamicFactor (itmin, itmax);
	/* Pre-emphasis in place; also compute maximum after pre-emphasis. */
	for (long ifreq = ifmin; ifreq <= ifmax; ifreq ++) {
		preemphasisFactor [ifreq] = (preemphasis / NUMln2) * log (ifreq * my dy / 1000.0);
		for (long itime = itmin; itime <= itmax; itime ++) {
			double value = my z [ifreq] [itime];   /* Power. */
			value = (10.0/NUMln10) * log ((value + 1e-30) / 4.0e-10) + preemphasisFactor [ifreq];   /* dB */
			if (value > dynamicFactor [itime]) dynamicFactor [itime] = value;   /* Local maximum. */
			my z [ifreq] [itime] = value;
		}
	}
	/* Compute global maximum. */
	if (autoscaling) {
		maximum = 0.0;
		for (long itime = itmin; itime <= itmax; itime ++)
			if (dynamicFactor [itime] > maximum) maximum = dynamicFactor [itime];
	}
	/* Dynamic compression in place. */
	for (long itime = itmin; itime <= itmax; itime ++) {
		dynamicFactor [itime] = dynamicCompression * (maximum - dynamicFactor [itime]);
		for (long ifreq = ifmin; ifreq <= ifmax; ifreq ++)
			my z [ifreq] [itime] += dynamicFactor [itime];
	}
	Graphics_image (g, my z,
		itmin, itmax,
		Matrix_columnToX (me, itmin - 0.5),
		Matrix_columnToX (me, itmax + 0.5),
		ifmin, ifmax,
		Matrix_rowToY (me, ifmin - 0.5),
		Matrix_rowToY (me, ifmax + 0.5),
		maximum - dynamic, maximum);
	for (long ifreq = ifmin; ifreq <= ifmax; ifreq ++)
		for (long itime = itmin; itime <= itmax; itime ++) {
			double value = 4.0e-10 * exp ((my z [ifreq] [itime] - dynamicFactor [itime]
				- preemphasisFactor [ifreq]) * (NUMln10 / 10.0)) - 1e-30;
			my z [ifreq] [itime] = value > 0.0 ? value : 0.0;
		}
}
void PowerCepstrogram_paint (PowerCepstrogram me, Graphics g, double tmin, double tmax, double qmin, double qmax, double dBminimum, double dBmaximum, int garnish) {
	if (tmax <= tmin) { tmin = my xmin; tmax = my xmax; }
	if (qmax <= qmin) { qmin = my ymin; qmax = my ymax; }
	long itmin, itmax, ifmin, ifmax;
	if (! Matrix_getWindowSamplesX (me, tmin - 0.49999 * my dx, tmax + 0.49999 * my dx, & itmin, & itmax) ||
		 ! Matrix_getWindowSamplesY (me, qmin - 0.49999 * my dy, qmax + 0.49999 * my dy, & ifmin, & ifmax)) {
		return;
	}
	autoMatrix thee = (Matrix) Data_copy (me);
	double min = 1e38, max = -min;
	for (long i = 1; i <= my ny; i++) {
		for (long j = 1; j <= my nx; j++) {
			double val = TO10LOG (my z[i][j]);
			min = val < min ? val : min;
			max = val > max ? val : max;
			thy z[i][j] = val;
		}
	}
	if (dBmaximum <= dBminimum) {
		dBminimum = min; dBmaximum = max;
	}
	Graphics_setInner (g);
	Graphics_setWindow (g, tmin, tmax, qmin, qmax);
	Graphics_image (g, thy z,
		itmin, itmax,
		Matrix_columnToX (thee.peek(), itmin - 0.5),
		Matrix_columnToX (thee.peek(), itmax + 0.5),
		ifmin, ifmax,
		Matrix_rowToY (thee.peek(), ifmin - 0.5),
		Matrix_rowToY (thee.peek(), ifmax + 0.5),
		dBminimum, dBmaximum);

	Graphics_unsetInner (g);
	if (garnish) {
		Graphics_drawInnerBox (g);
		Graphics_textBottom (g, 1, L"Time (s)");
		Graphics_marksBottom (g, 2, 1, 1, 0);
		Graphics_marksLeft (g, 2, 1, 1, 0);
		Graphics_textLeft (g, 1, L"Quefrency (s)");
	}
}
void BandFilterSpectrogram_paintImage (BandFilterSpectrogram me, Graphics g, double xmin, double xmax, double ymin, double ymax, double minimum, double maximum, int garnish) {
	if (xmax <= xmin) {
		xmin = my xmin; xmax = my xmax; 
	}
	if (ymax <= ymin) {
		ymin = my ymin; ymax = my ymax;
	}
	long ixmin, ixmax, iymin, iymax;
	(void) Matrix_getWindowSamplesX (me, xmin - 0.49999 * my dx, xmax + 0.49999 * my dx, &ixmin, &ixmax);
	(void) Matrix_getWindowSamplesY (me, ymin - 0.49999 * my dy, ymax + 0.49999 * my dy, &iymin, &iymax);
	autoMatrix thee = Spectrogram_to_Matrix_dB ((Spectrogram) me, 4e-10, 10, -100);
	if (maximum <= minimum) {
		(void) Matrix_getWindowExtrema (thee.peek(), ixmin, ixmax, iymin, iymax, &minimum, &maximum);
	}
	if (maximum <= minimum) { 
		minimum -= 1.0; maximum += 1.0;
	}
	if (xmin >= xmax || ymin >= ymax) {
		return;
	}
	Graphics_setInner (g);
	Graphics_setWindow (g, xmin, xmax, ymin, ymax);
	Graphics_image (g, thy z,
			ixmin, ixmax, Sampled_indexToX   (thee.peek(), ixmin - 0.5), Sampled_indexToX   (thee.peek(), ixmax + 0.5),
			iymin, iymax, SampledXY_indexToY (thee.peek(), iymin - 0.5), SampledXY_indexToY (thee.peek(), iymax + 0.5),
			minimum, maximum);

	Graphics_unsetInner (g);
	if (garnish) {
		Graphics_drawInnerBox (g);
		Graphics_marksLeft (g, 2, 1, 1, 0);
		Graphics_textLeft (g, 1, Melder_cat (U"Frequency (", my v_getFrequencyUnit (), U")"));
		Graphics_marksBottom (g, 2, 1, 1, 0);
		Graphics_textBottom (g, 1, U"Time (s)");
	}
}
Beispiel #7
0
void structERPWindow :: v_drawSelectionViewer () {
	ERP erp = (ERP) data;
	Graphics_setWindow (d_graphics, -1.1, 1.1, -1.01, 1.19);
	Graphics_setGrey (d_graphics, 0.85);
	Graphics_fillRectangle (d_graphics, -1.1, 1.1, -1.01, 1.19);
	Graphics_setColour (d_graphics, Graphics_BLACK);
	long numberOfDrawableChannels =
			erp -> ny >= 64 && Melder_wcsequ (erp -> d_channelNames [64], L"O2") ? 64 :
			erp -> ny >= 32 && Melder_wcsequ (erp -> d_channelNames [32], L"Cz") ? 32 :
			0;
	BiosemiLocationData *biosemiLocationData = numberOfDrawableChannels == 64 ? biosemiCapCoordinates64 : numberOfDrawableChannels == 32 ? biosemiCapCoordinates32 : 0;
	for (long ichan = 1; ichan <= numberOfDrawableChannels; ichan ++) {
		double inclination = (double) biosemiLocationData [ichan]. inclination;
		double azimuth = (double) biosemiLocationData [ichan]. azimuth;
		bool rightHemisphere = inclination >= 0.0;
		double r = fabs (inclination / 115.0);
		double theta = rightHemisphere ? azimuth * (NUMpi / 180.0) : (azimuth + 180.0) * (NUMpi / 180.0);
		biosemiLocationData [ichan]. topX = r * cos (theta);
		biosemiLocationData [ichan]. topY = r * sin (theta);
	}
	long n = 201;
	double d = 2.0 / (n - 1);
	autoNUMvector <double> mean (1, numberOfDrawableChannels);
	for (long ichan = 1; ichan <= numberOfDrawableChannels; ichan ++) {
		mean [ichan] =
			d_startSelection == d_endSelection ?
				Sampled_getValueAtX (erp, d_startSelection, ichan, 0, true) :
				Vector_getMean (erp, d_startSelection, d_endSelection, ichan);
	}
	autoNUMmatrix <double> image (1, n, 1, n);
	for (long irow = 1; irow <= n; irow ++) {
		double y = -1.0 + (irow - 1) * d;
		for (long icol = 1; icol <= n; icol ++) {
			double x = -1.0 + (icol - 1) * d;
			if (x * x + y * y <= 1.0) {
				double value = NUMundefined, sum = 0.0, weight = 0.0;
				for (long ichan = 1; ichan <= numberOfDrawableChannels; ichan ++) {
					double dx = x - biosemiLocationData [ichan]. topX;
					double dy = y - biosemiLocationData [ichan]. topY;
					double distance = sqrt (dx * dx + dy * dy);
					if (distance < 1e-12) {
						value = mean [ichan];
						break;
					}
					distance = distance * distance * distance * distance * distance * distance;
					sum += mean [ichan] / distance;
					weight += 1.0 / distance;
				}
				if (value == NUMundefined)
					value = ( sum == 0.0 ? 0.0 : sum / weight );
				image [irow] [icol] = value;
			}
		}
	}
	double minimum = 0.0, maximum = 0.0;
	for (long irow = 1; irow <= n; irow ++) {
		for (long icol = 1; icol <= n; icol ++) {
			double value = image [irow] [icol];
			if (value < minimum) minimum = value;
			else if (value > maximum) maximum = value;
		}
	}
	double absoluteExtremum = - minimum > maximum ? - minimum : maximum;
	if (d_sound_scalingStrategy == kTimeSoundEditor_scalingStrategy_FIXED_RANGE) {
		minimum = d_sound_scaling_minimum;
		maximum = d_sound_scaling_maximum;
	} else if (d_sound_scalingStrategy == kTimeSoundEditor_scalingStrategy_FIXED_HEIGHT) {
		double mean = 0.5 * (minimum + maximum);
		minimum = mean - 0.5 * d_sound_scaling_height;
		maximum = mean + 0.5 * d_sound_scaling_height;
	} else {
		minimum = - absoluteExtremum;
		maximum = absoluteExtremum;
	}
	for (long irow = 1; irow <= n; irow ++) {
		double y = -1.0 + (irow - 1) * d;
		for (long icol = 1; icol <= n; icol ++) {
			double x = -1.0 + (icol - 1) * d;
			if (x * x + y * y > 1.0) {
				image [irow] [icol] = minimum + 0.1875 * (maximum - minimum);   // -0.625 * absoluteExtremum;
			}
		}
	}
	Graphics_image (d_graphics, image.peek(), 1, n, -1.0-0.5/n, 1.0+0.5/n, 1, n, -1.0-0.5/n, 1.0+0.5/n, minimum, maximum);
	Graphics_setLineWidth (d_graphics, 2.0);
	/*
	 * Nose.
	 */
	Graphics_setGrey (d_graphics, 0.5);
	{// scope
		double x [3] = { -0.08, 0.0, 0.08 }, y [3] = { 0.99, 1.18, 0.99 };
		Graphics_fillArea (d_graphics, 3, x, y);
	}
	Graphics_setColour (d_graphics, Graphics_BLACK);
	Graphics_line (d_graphics, -0.08, 0.99, 0.0, 1.18);
	Graphics_line (d_graphics, 0.08, 0.99, 0.0, 1.18);
	/*
	 * Ears.
	 */
	Graphics_setGrey (d_graphics, 0.5);
	Graphics_fillRectangle (d_graphics, -1.09, -1.00, -0.08, 0.08);
	Graphics_fillRectangle (d_graphics, 1.09, 1.00, -0.08, 0.08);
	Graphics_setColour (d_graphics, Graphics_BLACK);
	Graphics_line (d_graphics, -0.99, 0.08, -1.09, 0.08);
	Graphics_line (d_graphics, -1.09, 0.08, -1.09, -0.08);
	Graphics_line (d_graphics, -1.09, -0.08, -0.99, -0.08);
	Graphics_line (d_graphics, 0.99, 0.08, 1.09, 0.08);
	Graphics_line (d_graphics, 1.09, 0.08, 1.09, -0.08);
	Graphics_line (d_graphics, 1.09, -0.08, 0.99, -0.08);
	/*
	 * Scalp.
	 */
	Graphics_ellipse (d_graphics, -1.0, 1.0, -1.0, 1.0);
	Graphics_setLineWidth (d_graphics, 1.0);
}
Beispiel #8
0
void structERP :: f_drawScalp (Graphics graphics, double tmin, double tmax, double vmin, double vmax, bool garnish) {
	Graphics_setInner (graphics);
	Graphics_setWindow (graphics, -1.0, 1.0, -1.0, 1.0);
	//Graphics_setGrey (graphics, 1.0);
	//Graphics_fillRectangle (graphics, -1.1, 1.1, -1.01, 1.19);
	//Graphics_setColour (graphics, Graphics_BLACK);
	long numberOfDrawableChannels =
			this -> ny >= 64 && Melder_wcsequ (this -> d_channelNames [64], L"O2") ? 64 :
			this -> ny >= 32 && Melder_wcsequ (this -> d_channelNames [32], L"Cz") ? 32 :
			0;
	BiosemiLocationData *biosemiLocationData = numberOfDrawableChannels == 64 ? biosemiCapCoordinates64 : numberOfDrawableChannels == 32 ? biosemiCapCoordinates32 : 0;
	for (long ichan = 1; ichan <= numberOfDrawableChannels; ichan ++) {
		double inclination = (double) biosemiLocationData [ichan]. inclination;
		double azimuth = (double) biosemiLocationData [ichan]. azimuth;
		bool rightHemisphere = inclination >= 0.0;
		double r = fabs (inclination / 115.0);
		double theta = rightHemisphere ? azimuth * (NUMpi / 180.0) : (azimuth + 180.0) * (NUMpi / 180.0);
		biosemiLocationData [ichan]. topX = r * cos (theta);
		biosemiLocationData [ichan]. topY = r * sin (theta);
	}
	long n = 201;
	double d = 2.0 / (n - 1);
	autoNUMvector <double> mean (1, numberOfDrawableChannels);
	for (long ichan = 1; ichan <= numberOfDrawableChannels; ichan ++) {
		mean [ichan] = tmin == tmax ?
				Sampled_getValueAtX (this, tmin, ichan, 0, true) :
				Vector_getMean (this, tmin, tmax, ichan);
	}
	autoNUMmatrix <double> image (1, n, 1, n);
	for (long irow = 1; irow <= n; irow ++) {
		double y = -1.0 + (irow - 1) * d;
		for (long icol = 1; icol <= n; icol ++) {
			double x = -1.0 + (icol - 1) * d;
			if (x * x + y * y <= 1.0) {
				double value = NUMundefined, sum = 0.0, weight = 0.0;
				for (long ichan = 1; ichan <= numberOfDrawableChannels; ichan ++) {
					double dx = x - biosemiLocationData [ichan]. topX;
					double dy = y - biosemiLocationData [ichan]. topY;
					double distance = sqrt (dx * dx + dy * dy);
					if (distance < 1e-12) {
						value = mean [ichan];
						break;
					}
					distance = distance * distance * distance * distance * distance * distance;
					sum += mean [ichan] / distance;
					weight += 1.0 / distance;
				}
				if (value == NUMundefined)
					value = ( sum == 0.0 ? 0.0 : sum / weight );
				image [irow] [icol] = value;
			}
		}
	}
	for (long irow = 1; irow <= n; irow ++) {
		double y = -1.0 + (irow - 1) * d;
		for (long icol = 1; icol <= n; icol ++) {
			double x = -1.0 + (icol - 1) * d;
			if (x * x + y * y > 1.0) {
				image [irow] [icol] = vmin;
			}
		}
	}
	Graphics_image (graphics, image.peek(), 1, n, -1.0-0.5/n, 1.0+0.5/n, 1, n, -1.0-0.5/n, 1.0+0.5/n, vmin, vmax);
	Graphics_setLineWidth (graphics, 2.0);
	/*
	 * Nose.
	 */
	Graphics_setGrey (graphics, 0.5);
	{// scope
		double x [3] = { -0.08, 0.0, 0.08 }, y [3] = { 0.99, 1.18, 0.99 };
		Graphics_fillArea (graphics, 3, x, y);
	}
	Graphics_setColour (graphics, Graphics_BLACK);
	Graphics_line (graphics, -0.08, 0.99, 0.0, 1.18);
	Graphics_line (graphics, 0.08, 0.99, 0.0, 1.18);
	/*
	 * Ears.
	 */
	Graphics_setGrey (graphics, 0.5);
	Graphics_fillRectangle (graphics, -1.09, -1.00, -0.08, 0.08);
	Graphics_fillRectangle (graphics, 1.09, 1.00, -0.08, 0.08);
	Graphics_setColour (graphics, Graphics_BLACK);
	Graphics_line (graphics, -0.99, 0.08, -1.09, 0.08);
	Graphics_line (graphics, -1.09, 0.08, -1.09, -0.08);
	Graphics_line (graphics, -1.09, -0.08, -0.99, -0.08);
	Graphics_line (graphics, 0.99, 0.08, 1.09, 0.08);
	Graphics_line (graphics, 1.09, 0.08, 1.09, -0.08);
	Graphics_line (graphics, 1.09, -0.08, 0.99, -0.08);
	/*
	 * Scalp.
	 */
	Graphics_ellipse (graphics, -1.0, 1.0, -1.0, 1.0);
	Graphics_setLineWidth (graphics, 1.0);
	Graphics_unsetInner (graphics);
	if (garnish) {
		autoNUMmatrix <double> legend (1, n, 1, 2);
		for (long irow = 1; irow <= n; irow ++) {
			for (long icol = 1; icol <= 2; icol ++) {
				legend [irow] [icol] = (irow - 1) / (n - 1.0);
			}
		}
		Graphics_image (graphics, legend.peek(), 1, 2, 0.78, 0.98, 1, n, -0.8, +0.8, 0.0, 1.0);
		Graphics_rectangle (graphics, 0.78, 0.98, -0.8, +0.8);
		Graphics_setTextAlignment (graphics, Graphics_RIGHT, Graphics_TOP);
		Graphics_text2 (graphics, 1.0, -0.8, Melder_double (vmin * 1e6), L" \\muV");
		Graphics_setTextAlignment (graphics, Graphics_RIGHT, Graphics_BOTTOM);
		Graphics_text2 (graphics, 1.0, +0.8, Melder_double (vmax * 1e6), L" \\muV");
	}
}