/* LongSoundEditor.c */
/* Paul Boersma, December 14, 1999 */

#include "LongSoundEditor.h"
#include "Resources.h"
#include "EditorM.h"

#define LongSoundEditor_members FunctionEditor_members \
	Widget publishButton, publishPreserveButton; \
	Widget copyButton; \
	Widget writeAiffButton, writeAifcButton, writeWavButton, writeNextSunButton, writeNistButton; \
	int autoscaling; \
	double minimum, maximum; \
	struct { int windowType; double relativeWidth; int preserveTimes; } publish; \
	double maxBuffer;
#define LongSoundEditor_methods FunctionEditor_methods
class_create_opaque (LongSoundEditor, FunctionEditor)

static struct {
	int autoscaling;
	struct {
		int windowType;
		double relativeWidth;
		int preserveTimes;
	}
		publish;
}
	preferences = {
		FALSE,   /* autoscaling */
		enumi (Sound_WINDOW, Hanning), 1.0, TRUE   /* publish */
	};

void LongSoundEditor_prefs (void) {
	Resources_addInt ("LongSoundEditor.autoscaling", & preferences.autoscaling);
	Resources_addInt ("LongSoundEditor.publish.windowType", & preferences.publish.windowType);
	Resources_addDouble ("LongSoundEditor.publish.relativeWidth", & preferences.publish.relativeWidth);
	Resources_addInt ("LongSoundEditor.publish.preserveTimes", & preferences.publish.preserveTimes);
}

/********** DESTRUCTION **********/

static void destroy (I) {
	iam (LongSoundEditor);
	inherited (LongSoundEditor) destroy (me);
}

/***** FILE MENU *****/

DIRECT (LongSoundEditor, cb_publish)
	LongSound longSound = my data;
	Sound publish = LongSound_extractPart (longSound, my startSelection, my endSelection, FALSE);
	iferror return 0;
	if (my publishCallback)
		my publishCallback (me, my publishClosure, publish);
END

DIRECT (LongSoundEditor, cb_publishPreserve)
	LongSound longSound = my data;
	Sound publish = LongSound_extractPart (longSound, my startSelection, my endSelection, TRUE);
	iferror return 0;
	if (my publishCallback)
		my publishCallback (me, my publishClosure, publish);
END

#if 0
FORM (LongSoundEditor, cb_publishWindow, "Extract windowed selection", 0)
	ENUM ("Window", Sound_WINDOW, enumi (Sound_WINDOW, Hanning))
	POSITIVE ("Relative width", "1.0")
	BOOLEAN ("Preserve times", 1)
	OK
SET_INTEGER ("Window", my publish.windowType)
SET_REAL ("Relative width", my publish.relativeWidth)
SET_INTEGER ("Preserve times", my publish.preserveTimes)
DO
	LongSound sound = my data;
	Sound publish;
	preferences.publish.windowType = my publish.windowType = GET_INTEGER ("Window");
	preferences.publish.relativeWidth = my publish.relativeWidth = GET_REAL ("Relative width");
	preferences.publish.preserveTimes = my publish.preserveTimes = GET_INTEGER ("Preserve times");
	publish = LongSound_extractWindowedPart (longSound, my startSelection, my endSelection, my publish.windowType,
		my publish.relativeWidth, my publish.preserveTimes);
	if (! publish) return 0;
	if (my publishCallback)
		my publishCallback (me, my publishClosure, publish);
END
#endif

FORM_WRITE (LongSoundEditor, cb_writeAiff, "Write selection to AIFF file", 0)
	sprintf (defaultName, "%s.aiff", ((LongSound) my data) -> name);
DO_WRITE
	LongSound sound = my data;
	if (my startSelection >= my endSelection)
		return Melder_error ("No samples selected.");
	if (! LongSound_writePartToAudioFile16 (sound, Melder_AIFF, my startSelection, my endSelection, file)) return 0;
END

FORM_WRITE (LongSoundEditor, cb_writeAifc, "Write selection to AIFC file", 0)
	sprintf (defaultName, "%s.aifc", ((LongSound) my data) -> name);
DO_WRITE
	LongSound sound = my data;
	if (my startSelection >= my endSelection)
		return Melder_error ("No samples selected.");
	if (! LongSound_writePartToAudioFile16 (sound, Melder_AIFC, my startSelection, my endSelection, file)) return 0;
END

FORM_WRITE (LongSoundEditor, cb_writeWav, "Write selection to WAV file", 0)
	sprintf (defaultName, "%s.wav", ((LongSound) my data) -> name);
DO_WRITE
	LongSound sound = my data;
	if (my startSelection >= my endSelection)
		return Melder_error ("No samples selected.");
	if (! LongSound_writePartToAudioFile16 (sound, Melder_WAV, my startSelection, my endSelection, file)) return 0;
END

FORM_WRITE (LongSoundEditor, cb_writeNextSun, "Write selection to NeXT/Sun file", 0)
	sprintf (defaultName, "%s.au", ((LongSound) my data) -> name);
DO_WRITE
	LongSound sound = my data;
	if (my startSelection >= my endSelection)
		return Melder_error ("No samples selected.");
	if (! LongSound_writePartToAudioFile16 (sound, Melder_NEXT_SUN, my startSelection, my endSelection, file)) return 0;
END

FORM_WRITE (LongSoundEditor, cb_writeNist, "Write selection to NIST file", 0)
	sprintf (defaultName, "%s.nist", ((LongSound) my data) -> name);
DO_WRITE
	LongSound sound = my data;
	if (my startSelection >= my endSelection)
		return Melder_error ("No samples selected.");
	if (! LongSound_writePartToAudioFile16 (sound, Melder_NIST, my startSelection, my endSelection, file)) return 0;
END

/***** EDIT MENU *****/

DIRECT (LongSoundEditor, cb_copy)
	Sound publish = LongSound_extractPart (my data, my startSelection, my endSelection, FALSE);
	iferror return 0;
	forget (Sound_clipboard);
	Sound_clipboard = publish;
END

/***** VIEW MENU *****/

DIRECT (LongSoundEditor, cb_autoscaling)
	preferences.autoscaling = my autoscaling = ! my autoscaling;
	FunctionEditor_redraw (me);
END

/***** MARKER MENUS *****/

/*
DIRECT (LongSoundEditor, cb_moveCursorToZero)
	double zero = LongSound_getNearestZeroCrossing (my data, my cursor);
	if (NUMdefined (zero)) {
		my cursor = zero;
		FunctionEditor_marksChanged (me);
	}
END
DIRECT (LongSoundEditor, cb_moveBtoZero)
	double zero = LongSound_getNearestZeroCrossing (my data, my startSelection);
	if (NUMdefined (zero)) {
		my startSelection = zero;
		FunctionEditor_marksChanged (me);
	}
END
DIRECT (LongSoundEditor, cb_moveEtoZero)
	double zero = LongSound_getNearestZeroCrossing (my data, my endSelection);
	if (NUMdefined (zero)) {
		my endSelection = zero;
		FunctionEditor_marksChanged (me);
	}
END*/

/***** HELP MENU *****/

DIRECT (LongSoundEditor, cb_LongSoundEditorHelp) Melder_help ("LongSoundEditor"); END

static void createMenus (I) {
	iam (LongSoundEditor);
	inherited (LongSoundEditor) createMenus (me);

	my publishButton = Editor_addCommand (me, "File", "Extract sound selection (time from 0)", 0, cb_publish);
	my publishPreserveButton = Editor_addCommand (me, "File", "Extract sound selection (preserve times)", 0, cb_publishPreserve);
	/*my publishWindowButton = Editor_addCommand (me, "File", "Extraxt sound selection (windowed)...", 0, cb_publishWindow);*/
	my writeAiffButton = Editor_addCommand (me, "File", "Write selection to AIFC file...", 0, cb_writeAifc);
	my writeAifcButton = Editor_addCommand (me, "File", "Write selection to AIFF file...", 0, cb_writeAiff);
	my writeWavButton = Editor_addCommand (me, "File", "Write selection to WAV file...", 0, cb_writeWav);
	my writeNextSunButton = Editor_addCommand (me, "File", "Write selection to Next/Sun file...", 0, cb_writeNextSun);
	my writeNistButton = Editor_addCommand (me, "File", "Write selection to NIST file...", 0, cb_writeNist);
	Editor_addCommand (me, "File", "-- close --", 0, NULL);

	Editor_addCommand (me, "Edit", "-- cut copy paste --", 0, NULL);
	my copyButton = Editor_addCommand (me, "Edit", "Copy selection to Sound clipboard", 'C', cb_copy);

	Editor_addCommand (me, "View", "-- view/sound --", 0, 0);
	Editor_addCommand (me, "View", "Autoscaling", motif_CHECKABLE | (preferences.autoscaling ? motif_CHECKED : 0), cb_autoscaling);

	/*Editor_addCommand (me, "C", "Move cursor to nearest zero crossing", '0', cb_moveCursorToZero);
	Editor_addCommand (me, "B", "Move B to nearest zero crossing", 0, cb_moveBtoZero);
	Editor_addCommand (me, "E", "Move E to nearest zero crossing", 0, cb_moveEtoZero);*/

	Editor_addCommand (me, "Help", "LongSoundEditor help", '?', cb_LongSoundEditorHelp);
}

/********** UPDATE **********/

static void prepareDraw (I) {
	iam (LongSoundEditor);
	LongSound longSound = my data;
	LongSound_haveWindow (longSound, my startWindow, my endWindow);
	Melder_clearError ();
}

void FunctionEditor_drawLongSound (I, LongSound longSound, int autoscaling) {
	iam (FunctionEditor);
	long first, last;
	int fits, nchan = longSound -> numberOfChannels, ichan;
	double tfirst, tlast;
	int cursorVisible = my cursor >= my startWindow && my cursor <= my endWindow;
	double cursorFunctionValue = 0.0;
	/*
	 * Make sure that the window fits the LongSound buffer (has been done already in prepareDraw (), but we forgot).
	 */
	fits = LongSound_haveWindow (longSound, my startWindow, my endWindow);
	iferror {
		int outOfMemory = strstr (Melder_getError (), "memory") != NULL;
		Melder_clearError ();
		Graphics_setWindow (my graphics, 0, 1, 0, 1);
		Graphics_setTextAlignment (my graphics, Graphics_CENTRE, Graphics_HALF);
		Graphics_printf (my graphics, 0.5, 0.5, outOfMemory ? "(out of memory)" : "(cannot read sound file)");
	} else if (! fits) {
		Graphics_setWindow (my graphics, 0, 1, 0, 1);
		Graphics_setTextAlignment (my graphics, Graphics_CENTRE, Graphics_HALF);
		Graphics_printf (my graphics, 0.5, 0.5, "(window too large; zoom in to see the data)");
	} else if (Sampled_getWindowSamples (longSound, my startWindow, my endWindow, & first, & last) > 1) {
		/*
		 * Draw the function value of the cursor position.
		 */
		/*
		if (cursorVisible) cursorFunctionValue = Vector_getValueAtX (my sound, my cursor, 70);*/
		/*if (cursorVisible)
			FunctionEditor_drawCursorFunctionValue (me, "%.4g", cursorFunctionValue);*/
		/*
		 * Draw the function(s).
		 */
		Graphics_setColour (my graphics, Graphics_BLACK);
		tfirst = Sampled_indexToX (longSound, first), tlast = Sampled_indexToX (longSound, last);
		for (ichan = 1; ichan <= nchan; ichan ++) {
			/*
			 * BUG: this will only work for mono or stereo, until Graphics_function16 handles quadro.
			 */
			double ymin = (double) (nchan - ichan) / nchan;
			double ymax = (double) (nchan + 1 - ichan) / nchan;
			Graphics_Viewport vp = Graphics_insetViewport (my graphics, 0, 1, ymin, ymax);
			long horizontal = 0;
			double minimum = -1.0, maximum = 1.0;
			if (autoscaling)
				LongSound_getWindowExtrema (longSound, my startWindow, my endWindow, ichan, & minimum, & maximum);
			Graphics_setColour (my graphics, Graphics_BLUE);
			if (minimum >= maximum) {
				double value = 0.5 * (minimum + maximum);
				horizontal = 1;
				minimum -= 1; maximum += 1;
				Graphics_setTextAlignment (my graphics, Graphics_RIGHT, Graphics_HALF);
				Graphics_setWindow (my graphics, 0, 1, minimum, maximum);
				Graphics_printf (my graphics, 0, value, "%.4g", value);
			} else if (autoscaling) {
				Graphics_setWindow (my graphics, 0, 1, -1.0, 1.0);
				Graphics_fillRectangle (my graphics, 1, 2, minimum, maximum);
				Graphics_setTextAlignment (my graphics, Graphics_LEFT, Graphics_TOP);
				Graphics_printf (my graphics, 1, minimum, "%.4g", minimum);
				Graphics_setTextAlignment (my graphics, Graphics_LEFT, Graphics_BOTTOM);
				Graphics_printf (my graphics, 1, maximum, "%.4g", maximum);
			} else {
				Graphics_setWindow (my graphics, my startWindow, my endWindow, -1.0, 1.0);
				FunctionEditor_drawRangeMark (me, "%.4g", minimum, Graphics_BOTTOM);
				FunctionEditor_drawRangeMark (me, "%.4g", maximum, Graphics_TOP);
			}
			if (minimum < 0 && maximum > 0 && ! horizontal) {
				Graphics_setWindow (my graphics, 0, 1, minimum, maximum);
				if (! cursorVisible || fabs (Graphics_dyWCtoMM (my graphics, cursorFunctionValue - 0.0)) > 3.0) {
					Graphics_setColour (my graphics, Graphics_BLACK);
					Graphics_setTextAlignment (my graphics, Graphics_RIGHT, Graphics_HALF);
					Graphics_text (my graphics, 0, 0, "0");
				}
				Graphics_setColour (my graphics, Graphics_CYAN);
				Graphics_setLineType (my graphics, Graphics_DOTTED);
				Graphics_line (my graphics, 0, 0, 1, 0);
				Graphics_setLineType (my graphics, Graphics_DRAWN);
			}
			/*
			 * Garnish the drawing area of each channel.
			 */
			Graphics_setWindow (my graphics, 0, 1, 0, 1);
			Graphics_setColour (my graphics, Graphics_CYAN);
			Graphics_innerRectangle (my graphics, 0, 1, 0, 1);
			/*
			 * Draw a very thin separator line underneath.
			 */
			if (ichan < nchan) {
				/*Graphics_setColour (my graphics, Graphics_BLACK);*/
				Graphics_line (my graphics, 0, 0, 1, 0);
			}
			/*
			 * Draw the samples.
			 */
			Graphics_setWindow (my graphics, my startWindow, my endWindow, minimum * 32768, maximum * 32768);
			Graphics_setColour (my graphics, Graphics_BLACK);
			Graphics_function16 (my graphics,
				longSound -> buffer - longSound -> imin * nchan + (ichan - 1), nchan - 1, first, last, tfirst, tlast);
			Graphics_resetViewport (my graphics, vp);
		}
	} else {
		Graphics_setWindow (my graphics, 0, 1, 0, 1);
		Graphics_setTextAlignment (my graphics, Graphics_CENTRE, Graphics_HALF);
		Graphics_printf (my graphics, 0.5, 0.5, "(zoom out to see the data)");
	}

}

static void draw (I) {
	iam (LongSoundEditor);
	LongSound longSound = my data;
	long first, last, selectedSamples;

	/*
	 * We check beforehand whether the window fits the LongSound buffer.
	 */
	if (my endWindow - my startWindow > longSound -> bufferLength) {
		Graphics_setWindow (my graphics, 0, 1, 0, 1);
		Graphics_setTextAlignment (my graphics, Graphics_CENTRE, Graphics_BOTTOM);
		Graphics_printf (my graphics, 0.5, 0.5, "(window longer than %.7g seconds)", longSound -> bufferLength);
		Graphics_setTextAlignment (my graphics, Graphics_CENTRE, Graphics_TOP);
		Graphics_printf (my graphics, 0.5, 0.5, "(zoom in to see the samples)");
		return;
	}

	FunctionEditor_drawLongSound (me, longSound, my autoscaling);

	/* Update buttons. */

	selectedSamples = Sampled_getWindowSamples (longSound,
		my startSelection, my endSelection, & first, & last);
	XtSetSensitive (my publishButton, selectedSamples != 0);
	XtSetSensitive (my publishPreserveButton, selectedSamples != 0);
	/*XtSetSensitive (my publishWindowButton, selectedSamples != 0);*/
	XtSetSensitive (my writeAiffButton, selectedSamples != 0);
	XtSetSensitive (my writeAifcButton, selectedSamples != 0);
	XtSetSensitive (my writeWavButton, selectedSamples != 0);
	XtSetSensitive (my writeNextSunButton, selectedSamples != 0);
	XtSetSensitive (my writeNistButton, selectedSamples != 0);
	XtSetSensitive (my copyButton, selectedSamples != 0);
}

static void play (I, double tmin, double tmax) {
	iam (LongSoundEditor);
	LongSound_playPart (my data, tmin, tmax, our playCallback, me);
}

class_methods (LongSoundEditor, FunctionEditor)
	class_method (destroy)
	class_method (createMenus)
	class_method (prepareDraw)
	class_method (draw)
	class_method (play)
class_methods_end

LongSoundEditor LongSoundEditor_create (Widget parent, const char *title, Any data) {
	LongSoundEditor me = new (LongSoundEditor);
	Melder_assert (Thing_member (data, classLongSound));
	if (! me || ! FunctionEditor_init (me, parent, title, data))
		return NULL;
	my minimum = -1;
	my maximum = 1;
	my autoscaling = preferences.autoscaling;
	my publish.windowType = preferences.publish.windowType;
	my publish.relativeWidth = preferences.publish.relativeWidth;
	my publish.preserveTimes = preferences.publish.preserveTimes;
	if (my endWindow - my startWindow > 30.0) {
		my endWindow = my startWindow + 30.0;
		FunctionEditor_marksChanged (me);
	}
	return me;
}

/* End of file LongSoundEditor.c */
