/* 

    TiMidity -- Experimental MIDI to WAVE converter
    Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    ncurs_c.c
   
    */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include "gtim.h"

#if defined(USE_NCURSES) && !defined(RENAMED_NCURSES)
#include <ncurses.h>
#else
#include <curses.h>
#endif

#ifdef HAVE_STRING_H
#    include <string.h>
#else
#    include <strings.h>
#endif

#include "common.h"
#include "instrum.h"
#include "playmidi.h"
#include "output.h"
#include "controls.h"

#define FILTER_INTERPOLATION

static void ctl_refresh (void);
static void ctl_help_mode (void);
static void ctl_total_time (uint32 tt);
static void ctl_master_volume (int mv);
static void ctl_file_name (char *name);
static void ctl_current_time (uint32 ct);
static void ctl_note (int v);
static void ctl_note_display (void);
static void ctl_volume (int ch, int val);
static void ctl_program (int ch, int val, const char *name);
static void ctl_bank (int ch, int val, int var, const char *name);
static void ctl_expression (int ch, int val);
static void ctl_panning (int ch, int val);
static void ctl_sustain (int ch, int val, int letter);
static void ctl_pitch_bend (int ch, int val, uint32 mod);
static void ctl_reset (void);
static void ctl_redo (void);
static int ctl_open (int using_stdin, int using_stdout);
static void ctl_close (void);
static int ctl_read (int32 *valp);
static int cmsg (int type, int verbosity_level, const char *fmt, ...);
static void ctl_pass_playing_list (int number_of_files, const char *list_of_files[]);
static void ctl_tempo(int t, int tr);
static void ctl_keysig(int k, int ko);
static void ctl_timesig(int n, int d, int c, int b);
static void ctl_misc_controller(int ch, int val, int col, int letter, int use_color);
static void ctl_song_title (char *name);
static void ctl_author (char *name);

/**********************************************/
/* export the interface functions */

#undef ctl
#define ctl ncurses_control_mode

ControlMode ctl = {
    "ncurses interface", 'n',
    0, 1, 0,
    ctl_open, ctl_pass_playing_list, ctl_close, ctl_read, cmsg,
    ctl_refresh, ctl_reset, ctl_redo, ctl_file_name, ctl_total_time, ctl_current_time,
    ctl_note, ctl_master_volume, ctl_program, ctl_bank, ctl_volume,
    ctl_expression, ctl_panning, ctl_sustain, ctl_pitch_bend,
    ctl_tempo, ctl_keysig, ctl_timesig, ctl_misc_controller, ctl_song_title, ctl_author
};


/***********************************************************************/
/* foreground/background checks disabled since switching to curses */
/* static int in_foreground=1; */
static int ctl_help_window = 0;
static int ctl_mix_window = 0;
static int maxy, maxx;
static int labels_start_x, labels_width = 20;
static int reverb_x, chorus_x, celeste_x;
static int opt_volume_x, opt_expression_x;
static int bar_h_x = 14, bar_h_width;
static int lyric_start, lyric_end;
static int notes_x, notes_width;
static int low_clip, high_clip;
static int patches_x, patches_width;
static int program_x, bank_x;
static int volume_x;
static int expression_x;
static int panning_x;
static int sustain_x;
static int pitch_bend_x;
static int misc_controller_x;

static int master_volume_x;
static int master_volume_width;
static int clips_x;
static int clips_width;

static int file_name_width, file_name_x;
static int tempo_width, tempo_x, timesig_width, timesig_x, keysig_width, keysig_x;
static int song_title_width, song_title_x, author_width, author_x;

#define NAME_MAX 512
static int save_master_volume;
static uint32 save_total_time;
static char save_file_name[NAME_MAX];
static char save_song_title[NAME_MAX];
static char save_author[NAME_MAX];
static int screen_bugfix = 0;
static uint32 current_centiseconds = 0;
static uint32 current_offset = 0;
static int songoffset = 0, current_voices = 0, current_real_voices = 0, current_reverb_voices = 0 ;
static int current_tempo = CTL_STATUS_UPDATE;
static int loading_progress = 0;

extern MidiEvent *current_event;

static const char **play_list;
static int play_list_count = 0, play_list_current;
static int play_list_jump = -1, play_list_increment = 0;
static int play_list_mouse_x = -1, play_list_mouse_y = -1;
static int play_list_y;

typedef struct {
    uint8   status, channel, note, velocity, nvoices, real_voices, clone_type, reverb_voices, legato;
    uint32  tempo, time;
} OldVoice;
#define MAX_OLD_NOTES 1024
static OldVoice old_note[MAX_OLD_NOTES];
static int leading_pointer = 0, trailing_pointer = 0;

#define MAX_PNW 12
static char patch_name[16][MAX_PNW];

static char short_cfg_names[4][6];
static char  saved_message[200];

static int lastkeysig = CTL_STATUS_UPDATE;
static int lastoffset = CTL_STATUS_UPDATE;
static int lasttempo = CTL_STATUS_UPDATE;
static int cooked_tempo = CTL_STATUS_UPDATE;
static int lastratio = CTL_STATUS_UPDATE;

#define LAST_TRACE 60
#define MAX_TRACE_LEN 32
static char trace_message[LAST_TRACE][MAX_TRACE_LEN];
static int current_trace = 0;

#ifdef HAVE_USE_DEFAULT_COLORS
#ifdef REALLY_USE_DEFAULT
static int my_bg = COLOR_BLACK;
#endif
#endif

static int bright;

#define BRIGHT_RED (bright + COLOR_RED)
#define BRIGHT_GREEN (bright + COLOR_GREEN)
#define BRIGHT_YELLOW (bright + COLOR_YELLOW)
#define BRIGHT_BLUE (bright + COLOR_BLUE)
#define BRIGHT_MAGENTA (bright + COLOR_MAGENTA)
#define BRIGHT_CYAN (bright + COLOR_CYAN)
#define BRIGHT_WHITE (bright + COLOR_WHITE)

static void
make_pairs (void) {
    if (COLORS >= 16 && COLOR_PAIRS >= 16) bright = 8;
    else bright = 0;

    init_pair (COLOR_RED, COLOR_BLACK, COLOR_RED);
    init_pair (COLOR_GREEN, COLOR_BLACK, COLOR_GREEN);
    init_pair (COLOR_YELLOW, COLOR_BLACK, COLOR_YELLOW);
    init_pair (COLOR_BLUE, COLOR_BLACK, COLOR_BLUE);
    init_pair (COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA);
    init_pair (COLOR_CYAN, COLOR_BLACK, COLOR_CYAN);
    init_pair (COLOR_WHITE, COLOR_BLACK, COLOR_WHITE);

    if (bright) {
        init_pair (bright + COLOR_RED, COLOR_BLACK, bright + COLOR_RED);
        init_pair (bright + COLOR_GREEN, COLOR_BLACK, bright + COLOR_GREEN);
        init_pair (bright + COLOR_YELLOW, COLOR_BLACK, COLOR_YELLOW + bright);
        init_pair (bright + COLOR_BLUE, COLOR_BLACK, bright + COLOR_BLUE);
        init_pair (bright + COLOR_MAGENTA, COLOR_BLACK, bright + COLOR_MAGENTA);
        init_pair (bright + COLOR_CYAN, COLOR_BLACK, bright + COLOR_CYAN);
        init_pair (bright + COLOR_WHITE, COLOR_BLACK, bright + COLOR_WHITE);
    }
}

static void
set_color (WINDOW * win, chtype color)
{
    if (has_colors ()) {
	wattron (win, COLOR_PAIR (color));
    }
    else wattron (win, A_BOLD);
}

static void
unset_color (WINDOW * win)
{
    if (has_colors ())
	wattrset (win, COLOR_PAIR (0));
    else wattroff (win, A_BOLD);
}


static WINDOW *mw = 0, *msgwin = 0;

static void
_ctl_refresh (void)
{
    wmove (mw, 0, 0);
    wrefresh (mw);
}

static void
clear_voices_bar (void)
{
	int bar_max;
	bar_max = voices;
	while (bar_max > bar_h_width) {
		bar_max = bar_max * 7 / 8;
	}
	if (bar_max > 8) {
		int i;
		for (i = bar_h_x; i <= bar_max; i++) {
			wmove (mw, 6, i);
			waddch (mw, ' ');
		}
	}
}


/* 6:Ch | Voices:  < 14 ... maxx-20 > room for bar is maxx - 20 - 2 - margins */
static void
make_voices_bar (void)
{
	int bar_x, bar_real_x, bar_max, bar_buf, bar_ceil, bar_nonreverb;
	bar_max = voices;
	bar_x = current_voices;
	bar_real_x = current_real_voices;
	bar_nonreverb = bar_x - current_reverb_voices;
	bar_ceil = voices_ceiling;
	//bar_buf = output_buffer_full / 4;
	bar_buf = danger / 100;
	if (bar_buf < 0) bar_buf = 0;
	else if (bar_buf > bar_max) bar_buf = bar_max;
	while (bar_max > bar_h_width) {
		bar_max = bar_max * 7 / 8;
		bar_x = bar_x * 7 / 8;
		bar_real_x = bar_real_x * 7 / 8;
		bar_buf = bar_buf * 7 / 8;
		bar_ceil = bar_ceil * 7 / 8;
		bar_nonreverb = bar_nonreverb * 7 / 8;
	}
	if (bar_ceil >= bar_max) bar_ceil = bar_max - 1;
	if (bar_max > 8) {
		int i;
		//set_color (mw, COLOR_YELLOW);
		set_color (mw, COLOR_GREEN);
		for (i = 0; i < bar_x; i++) {
			wmove (mw, 6, i + bar_h_x);
			if (has_colors()/* && i > bar_buf*/) {
				if (i == bar_buf + 1) {
				       if (i < bar_real_x) set_color (mw, COLOR_GREEN);
				       //else if (i < bar_nonreverb) set_color (mw, COLOR_RED);
				       else if (i < bar_nonreverb) set_color (mw, COLOR_YELLOW);
				       else set_color (mw, COLOR_BLUE);
				}
				if (i == bar_real_x) {
				    //if (i < bar_nonreverb) set_color (mw, COLOR_RED);
				    if (i < bar_nonreverb) set_color (mw, COLOR_YELLOW);
				    else set_color (mw, COLOR_BLUE);
				}
				else if (i == bar_nonreverb) {
				    set_color (mw, COLOR_BLUE);
				}
			}
			if (i <= bar_buf) waddch (mw, '-');
			else waddch (mw, '|');
		}
		unset_color (mw);
		for (i = bar_x + 1; i < bar_max; i++) {
			wmove (mw, 6, i + bar_h_x);
			if (i == bar_ceil) waddch (mw, '<');
			else waddch (mw, ' ');
		}
	}
}


static int
show_playlist (void)
{
    int i, ret = 0, show_count, show_start, show_end, show_apparent, show_max;
    int first_trace, end_trace, trace_x;

    if (!ctl.trace_playing) return ret;
    if (play_list_count < 1) return ret;

    show_count = play_list_count;
    show_max = maxy - play_list_y;

    end_trace = current_trace;
    if (end_trace > show_max) first_trace = end_trace - show_max;
    else first_trace = 0;
    trace_x = maxx - MAX_TRACE_LEN - 3;
    if (trace_x < 26) trace_x = 0;

    if (show_count > show_max) show_count = show_max;
    //if (show_count < 2) return ret;

    show_start = play_list_current;
    if (show_count < play_list_count) show_start -= show_count/4;

    if (show_start < 0) show_start = 0;
    show_end = show_start + show_count;
    if (show_end > play_list_count) {
	    show_end = play_list_count;
	    show_start = show_end - show_count;
	    if (show_start < 0) show_start = 0;
    }


    if (play_list_current + play_list_increment >= play_list_count)
	    return ret;
    if (play_list_current + play_list_increment < 0)
	    return ret;
    show_apparent = play_list_current + play_list_increment;

    if (play_list_mouse_x >= 6 &&
	 play_list_mouse_y >= play_list_y &&
	 play_list_mouse_y < play_list_y + show_count) {
	    play_list_jump = play_list_mouse_y - play_list_y + show_start;
	    show_apparent = play_list_jump;
	    ret = 1;
    }

    for (i = 0; i < show_max; i++) {
    	wmove (mw, play_list_y + i, 6);
        wclrtoeol (mw);
	if (i >= show_count) continue;
	if (show_start + i == show_apparent) {
		set_color (mw, BRIGHT_GREEN);
	}
        if (show_start + i < play_list_count) waddstr (mw, play_list[ show_start + i]);
	if (show_start + i == show_apparent) {
		unset_color (mw);
	}
    }
    if (trace_x && end_trace - first_trace > 0) {
	set_color (mw, COLOR_CYAN);
	for (i = 0; i < end_trace - first_trace; i++) {
    	    wmove (mw, play_list_y + i, trace_x);
	    waddstr (mw, trace_message[first_trace+i]);
	}
	unset_color (mw);
    }
    return ret;
}

static void
show_reverb_opts (void)
{
    	wmove (mw, 6, opt_volume_x);
	if (has_colors ()) {
    	    if (opt_volume_curve == 0)
	        set_color (mw, COLOR_GREEN);
	    else set_color (mw, COLOR_WHITE);
	}
	else {
    	    if (opt_volume_curve == 0)
		wattron (mw, A_BOLD);
	    else wattroff (mw, A_BOLD);
	}
    	if (opt_volume_curve == 0) waddch (mw, 'l');
	else waddch (mw, (unsigned)('0' + opt_volume_curve));

    	wmove (mw, 6, opt_expression_x);
	if (has_colors ()) {
    	    if (opt_expression_curve == 0)
	        set_color (mw, COLOR_GREEN);
	    else set_color (mw, COLOR_WHITE);
	}
	else {
    	    if (opt_expression_curve == 0)
		wattron (mw, A_BOLD);
	    else wattroff (mw, A_BOLD);
	}
    	if (opt_expression_curve == 0) waddch (mw, 'p');
	else waddch (mw, (unsigned)('0' + opt_expression_curve));

    	wmove (mw, 6, reverb_x);
	if (has_colors ()) {
    	    if (reverb_options & OPT_REVERB_VOICE)
	        set_color (mw, COLOR_GREEN);
	    else set_color (mw, COLOR_WHITE);
	}
	else {
    	    if (reverb_options & OPT_REVERB_VOICE)
		wattron (mw, A_BOLD);
	    else wattroff (mw, A_BOLD);
	}
	waddch (mw, 'R');

    	wmove (mw, 6, chorus_x);
	if (has_colors ()) {
    	    if (reverb_options & OPT_CHORUS_VOICE)
	        set_color (mw, COLOR_GREEN);
	    else set_color (mw, COLOR_WHITE);
	}
	else {
    	    if (reverb_options & OPT_CHORUS_VOICE)
		wattron (mw, A_BOLD);
	    else wattroff (mw, A_BOLD);
	}
	waddch (mw, 'C');

    	wmove (mw, 6, celeste_x);
	if (has_colors ()) {
    	    if (reverb_options & OPT_CELESTE_VOICE)
	        set_color (mw, COLOR_GREEN);
	    else set_color (mw, COLOR_WHITE);
	}
	else {
    	    if (reverb_options & OPT_CELESTE_VOICE)
		wattron (mw, A_BOLD);
	    else wattroff (mw, A_BOLD);
	}
	if (XG_System_On) waddch (mw, 'V');
	else waddch (mw, 'c');

	unset_color (mw);
}

static void
ctl_refresh (void)
{
    if (ctl.trace_playing) {
	ctl_current_time (0);
	ctl_note_display ();
	if (!ctl_help_window) {
	    ctl_keysig (CTL_STATUS_UPDATE, CTL_STATUS_UPDATE);
	    if (current_tempo != CTL_STATUS_UPDATE && tempo_width >= 10) {
	    		wmove (mw, 4, tempo_x);
    			wprintw (mw, (char *) "Tempo: %3d", current_tempo);
	    }
	}
	make_voices_bar ();
	show_playlist ();
	show_reverb_opts ();
    }
    _ctl_refresh ();
}

static void
re_init_screen (void)
{
    if (screen_bugfix)
	return;

    screen_bugfix = 1;
    wmove (mw, 1, 3);

    wprintw (mw, (char *) "patchset: %-20s interpolation: %-8s%s   loading: %-4s",
	     cfg_names[cfg_select] ? cfg_names[cfg_select] : "?",
	     (current_interpolation&INTERPOLATION_CSPLINE) ? "C-spline" :
	         (current_interpolation&INTERPOLATION_LAGRANGE) ? "Lagrange" : "linear",
	     (current_interpolation&INTERPOLATION_BUTTERWORTH) ? "[F]" : "   ",
	     fast_load? "fast" : "full");

    touchwin (mw);
    _ctl_refresh ();

    if (msgwin) {
	touchwin (msgwin);
	wrefresh (msgwin);
    }
}
/*
 "V/Up   Louder   b/Left  Skip back"
 "v/Down Softer   f/Right Fast forward"
 "n/Next Next     r/Home  Restart song"
 "p/Prev Prev.    q/End   Exit"

  000000000011111111112222222222333333
  012345678901234567890123456789012345
*/
#define H_WIN_TEXT_WIDTH 36
#define H_WIN_TEXT_HEIGHT 2
#define H_WIN_TOTAL_WIDTH H_WIN_TEXT_WIDTH + 4
#define H_WIN_TOTAL_HEIGHT H_WIN_TEXT_HEIGHT + 3
#define H_WIN_START_X 14
#define H_WIN_START_Y 1

/*
 ACS_ULCORNER	 upper left corner
 ACS_LLCORNER	 lower left corner
 ACS_URCORNER	 upper right corner
 ACS_LRCORNER	 lower right corner
 ACS_LTEE	 tee pointing right
 ACS_RTEE	 tee pointing left
 ACS_BTEE	 tee pointing up
 ACS_TTEE	 tee pointing down
*/
static double mix_x_factor;
static int mix_center_y, mix_center_x;
static int mix_left_y, mix_left_x;
static int mix_right_y, mix_right_x;
static int mix_rear_left_y, mix_rear_left_x;
static int mix_rear_right_y, mix_rear_right_x;
static int mix_separation_x, mix_separation_y;

static WINDOW *mixwin = NULL;

static void
show_speaker_positions (void) {
	int i, j;

	for (j = 2; j < 16-1; j++)
	    for (i = 1; i < notes_width-1; i++)
		    mvwaddch (mixwin, j, i, ' ');

	mix_x_factor = (double)(notes_width-4) / 127.0;


	mix_center_y = 8 + 2;
	mix_center_x = notes_x + 2 + opt_center_position * mix_x_factor;
	wmove (mixwin, mix_center_y-8, mix_center_x-notes_x);
	waddch (mixwin, 'C');

	mix_left_y = 8 + 3;
	mix_left_x = notes_x + 2 + opt_left_position * mix_x_factor;
	wmove (mixwin, mix_left_y-8, mix_left_x-notes_x);
	waddch (mixwin, 'L');

	mix_right_y = 8 + 3;
	mix_right_x = notes_x + 2 + opt_right_position * mix_x_factor;
	wmove (mixwin, mix_right_y-8, mix_right_x-notes_x);
	waddch (mixwin, 'R');

	mix_rear_left_y = 8 + 4;
	mix_rear_left_x = notes_x + 2 + opt_rear_left_position * mix_x_factor;
	wmove (mixwin, mix_rear_left_y-8, mix_rear_left_x-notes_x);
	waddch (mixwin, 'l');

	mix_rear_right_y = 8 + 4;
	mix_rear_right_x = notes_x + 2 + opt_rear_right_position * mix_x_factor;
	wmove (mixwin, mix_rear_right_y-8, mix_rear_right_x-notes_x);
	waddch (mixwin, 'r');

	mix_separation_y = mix_rear_left_y + 2;
	wmove (mixwin, mix_separation_y-8, 2);
	waddstr (mixwin, "Surround separation: [down]   [up]");
	mix_separation_x = notes_x + 2 + sizeof("Surround separation: [down]") - 1;
	wmove (mixwin, mix_separation_y-8, mix_separation_x - notes_x);
	wprintw (mixwin, (char *) "%3d", wide_panning);
}

static void
show_mixer(void)
{
    if (has_colors ()) set_color (mixwin, COLOR_WHITE);
    else wattron (mixwin, A_REVERSE);
    show_speaker_positions ();
    if (has_colors ()) unset_color (mixwin);
    else wattroff (mixwin, A_REVERSE);
    wrefresh (mixwin);
}

static void
handle_mixer_mouse (void) {
    int mix_mouse_x;

    if (play_list_mouse_y == mix_separation_y) {
	if (play_list_mouse_x < mix_separation_x && play_list_mouse_x > mix_separation_x - 6)
		wide_panning--;
	else if (play_list_mouse_x > mix_separation_x + 3 && play_list_mouse_x < mix_separation_x + 3 + 4)
		wide_panning++;
	else beep();
	show_mixer();
	return;
    }

    mix_mouse_x = (double)(play_list_mouse_x - notes_x - 2) / mix_x_factor;

    if (mix_mouse_x < 0 || mix_mouse_x > 127) {
	    beep ();
	    return;
    }
    if (play_list_mouse_y == mix_center_y) {
	    opt_center_position = mix_mouse_x;
    }
    else if (play_list_mouse_y == mix_left_y) {
	if (mix_mouse_x < 64)
	    opt_left_position = mix_mouse_x;
	else opt_right_position = mix_mouse_x;
    }
    else if (play_list_mouse_y == mix_rear_left_y) {
	if (mix_mouse_x < 64)
	    opt_rear_left_position = mix_mouse_x;
	else opt_rear_right_position = mix_mouse_x;
    }
    else beep ();

    show_mixer();
}

static void
mixer_mode (void)
{
    if (ctl_mix_window) {
	ctl_mix_window = 0;
	touchwin (mw);
	_ctl_refresh ();
    }
    else {
	int i;

	ctl_mix_window = 1;

	if (!mixwin) mixwin = newwin (16, notes_width, 8, notes_x);

	if (has_colors ()) set_color (mixwin, COLOR_WHITE);
	else wattron (mixwin, A_REVERSE);

	werase (mixwin);

	for (i = 1; i < notes_width-1; i++)
		    mvwaddch (mixwin, 1, i, ' ');

	wmove (mixwin, 1, (int)(notes_width - sizeof ("Speaker Positions"))/2);
	waddstr (mixwin, "Speaker Positions");

	show_speaker_positions ();

	if (has_colors ()) set_color (mixwin, BRIGHT_YELLOW);
	else wattron (mixwin, A_REVERSE);

	wborder (mixwin, 0, 0, 0, 0, 0, 0, 0, 0);

	if (has_colors ()) unset_color (mixwin);
	else wattroff (mixwin, A_REVERSE);

	wrefresh (mixwin);

    }
}

static void
ctl_help_mode (void)
{
    static WINDOW *helpwin = NULL;
    if (ctl_help_window) {
	ctl_help_window = 0;
	touchwin (mw);
	_ctl_refresh ();
    }
    else {
	ctl_help_window = 1;
	if (!helpwin)
	helpwin = newwin (H_WIN_TOTAL_HEIGHT, H_WIN_TOTAL_WIDTH, H_WIN_START_Y, H_WIN_START_X);

	if (has_colors ()) set_color (helpwin, COLOR_WHITE);
	else wattron (helpwin, A_REVERSE);

	werase (helpwin);

	wmove (helpwin, 1, 1);
	waddstr (helpwin,
		 " V/Up   Louder   b/Left  Skip back    ");
	wmove (helpwin, 2, 1);
	waddstr (helpwin,
		 " v/Down Softer   f/Right Fast forward ");
	wmove (helpwin, 3, 1);
	waddstr (helpwin,
		 " n/Next Next     r/Home  Restart song ");
	wmove (helpwin, 4, 1);
	waddstr (helpwin,
		 " p/Prev Prev.    q/End   Exit         ");

	if (has_colors ()) set_color (helpwin, COLOR_CYAN);

	wborder (helpwin, 0, 0, 0, 0, 0, 0, 0, 0);

	wrefresh (helpwin);

    }
}

static void
ctl_total_time (uint32 tt)
{
    int     mins, secs = (int) tt / play_mode->rate;
    save_total_time = tt;
    mins = secs / 60;
    secs -= mins * 60;

    if (!ctl_help_window) {
    wmove (mw, 4, 6 + 6 + 3);
    wattron (mw, A_BOLD);
    wprintw (mw, (char *) "%3d:%02d", mins, secs);
    wattroff (mw, A_BOLD);
    _ctl_refresh ();
    }

    songoffset = 0;
    current_centiseconds = 0;
    current_offset = 0;
    for (secs = 0; secs < 16; secs++)
	patch_name[secs][0] = '\0';
}

static void
ctl_master_volume (int mv)
{
    save_master_volume = mv;
    wmove (mw, 4, master_volume_x);
    waddstr (mw, "Volume:");
    wmove (mw, 4, master_volume_x + 8);
    wattron (mw, A_BOLD);
    wprintw (mw, (char *) "%3d%%", mv);
    wattroff (mw, A_BOLD);
    _ctl_refresh ();
}

static void
ctl_file_name (char *name)
{
    unsigned int nwidth;
    const char *subname;

    if (name) {
        nwidth = file_name_width - 6;
        subname = strrchr(name, '/');

        if (!subname) subname = name;
        else subname++;
        if (!strcmp (subname + strlen(subname) - 4, ".mid")) {
            if (nwidth > strlen(subname) - 4)
	        nwidth = strlen(subname) - 4;
        }
        strncpy (save_file_name, subname, nwidth);
        save_file_name[nwidth] = '\0';
    }

    if (save_file_name[0] && (int)strlen (save_file_name) + 6 <= file_name_width) {
        wmove (mw, 3, file_name_x);
        wclrtoeol (mw);
        wmove (mw, 3, file_name_x);
        waddstr (mw, "File:");
        wmove (mw, 3, file_name_x + 6);
        wattron (mw, A_BOLD);
        waddstr (mw, save_file_name);

	nwidth = strlen (save_file_name) + 6 + 2;
    	wmove (mw, 3, (int)(file_name_x + nwidth));

	nwidth += 6;
	if (XG_System_On) waddstr (mw, "[XG]");
	else if (GS_System_On) waddstr (mw, "[GS]");
	else if (GM_System_On) waddstr (mw, "[GM]");
	else nwidth -= 6;

        wattroff (mw, A_BOLD);

        song_title_x = file_name_x + nwidth;
	if (file_name_width > song_title_x)
	    song_title_width = file_name_width - song_title_x;

        _ctl_refresh ();
    }
}

static void ctl_song_title (char *name) {

    if (name && !save_song_title[0]) {
        strncpy (save_song_title, name, NAME_MAX);
	save_song_title[NAME_MAX-1] = '\0';
    }

    if (save_song_title[0] && (int)strlen (save_song_title) + 7 <= song_title_width) {
        wmove (mw, 3, song_title_x);
        waddstr (mw, "Title:");
        wmove (mw, 3, song_title_x + 7);
        wattron (mw, A_BOLD);
        waddstr (mw, save_song_title);
        wattroff (mw, A_BOLD);

        author_x = song_title_x + strlen (save_song_title) + 7 + 2;
	if (song_title_width > author_x)
	    author_width = song_title_width - author_x;

        _ctl_refresh ();
    }
}

static void ctl_author (char *name) {

    if (name && !save_author[0]) {
        strncpy (save_author, name, NAME_MAX);
    }

    if (save_author[0] && (int)strlen (save_author) <= author_width) {
        wmove (mw, 3, author_x);
        wattron (mw, A_BOLD);
        waddstr (mw, save_author);
        wattroff (mw, A_BOLD);
        _ctl_refresh ();
    }
}


static void
ctl_current_time (uint32 ct)
{
    int     centisecs, realct;
    int     mins, secs;

    if (!ctl.trace_playing || ctl_help_window)
	return;

    realct = play_mode->output_count (ct);
    if (realct < 0)
	realct = 0;
    else
	realct += songoffset;
    current_offset = realct;
    centisecs = realct / (play_mode->rate / 100);
    current_centiseconds = (uint32) centisecs;

    secs = centisecs / 100;
    mins = secs / 60;
    secs -= mins * 60;
    wmove (mw, 4, 6);
    set_color (mw, BRIGHT_GREEN);
    wprintw (mw, (char *) "%3d:%02d", mins, secs);
    unset_color (mw);

    if (realct > 100 && clips_width >= 11 + 2) {
        wmove (mw, 4, clips_x);
        waddstr (mw, "Clips:");
        wmove (mw, 4, clips_x + 7);
        wattron (mw, A_BOLD);
        wprintw (mw, (char *) "%3d%%", output_clips * 100 / realct);
        wattroff (mw, A_BOLD);
    }

    _ctl_refresh ();
}

static void
ctl_note (int v)
{
    int     i, n, rn, rv;
    if (!ctl.trace_playing || (voice[v].clone_type & IS_CLONE) )
	return;

    old_note[leading_pointer].status = voice[v].status;
    old_note[leading_pointer].clone_type = voice[v].clone_type;
    old_note[leading_pointer].channel = voice[v].channel;
    old_note[leading_pointer].note = voice[v].note;
    old_note[leading_pointer].velocity = voice[v].velocity;
    old_note[leading_pointer].legato = voice[v].initial_offset;
    old_note[leading_pointer].tempo = cooked_tempo;
    old_note[leading_pointer].time =
	current_event->time / (play_mode->rate / 100);
    rv = rn = n = 0;
    i = voices;
    while (i--)
	if (voice[i].status != VOICE_FREE) {
	    n++;
	    if ((voice[i].clone_type & IS_CLONE)) {
		if ((voice[i].clone_type & REVERB_CLONE)) rv++;
	    }
	    else rn++;
	}
    old_note[leading_pointer].nvoices = n;
    old_note[leading_pointer].real_voices = rn;
    old_note[leading_pointer].reverb_voices = rv;
    leading_pointer++;
    if (leading_pointer == MAX_OLD_NOTES)
	leading_pointer = 0;

}

static void
ctl_note_display (void)
{
    int     v = trailing_pointer;
    uint32  then;

    if (notes_width < 10)
	return;

    then = old_note[v].time;

    while (then <= current_centiseconds && v != leading_pointer) {
      int     xl, new_note, loud;

      if (!ctl_mix_window) {
	new_note = old_note[v].note - low_clip;
	if (new_note < 0)
	    new_note = 0;
	if (new_note > high_clip)
	    new_note = high_clip;
	xl = (new_note * notes_width) / (high_clip + 1);
	wmove (mw, 8 + (old_note[v].channel & 0x0f), xl + notes_x);
	//loud = (old_note[v].velocity > 64);
	loud = !old_note[v].legato;

	switch (old_note[v].status) {
	case VOICE_DIE:
	case VOICE_FREE:
	    waddch (mw, ACS_BULLET);
	    break;
	case VOICE_ON:
	    if (has_colors ()) {
		if (old_note[v].channel > 15)
		    set_color (mw, COLOR_CYAN);
		else if (old_note[v].clone_type)
		    set_color (mw, loud? BRIGHT_MAGENTA : COLOR_MAGENTA);
		else if (channel[old_note[v].channel].kit)
		    set_color (mw, loud? BRIGHT_YELLOW : COLOR_YELLOW);
		else
		    set_color (mw, loud? BRIGHT_RED : COLOR_RED);
	    }
	    else
		wattron (mw, A_BOLD);
	    waddch (mw, (unsigned char)('0' + (10 * old_note[v].velocity) / 128));
	    if (has_colors ())
		unset_color (mw);
	    else
		wattroff (mw, A_BOLD);
	    break;
	case VOICE_OFF:
	    if (has_colors ())
		set_color (mw, COLOR_GREEN);
	    waddch (mw, (unsigned char)('0' + (10 * old_note[v].velocity) / 128));
	    if (has_colors ())
		unset_color (mw);
	    break;
	case VOICE_SUSTAINED:
	default:
	    if (has_colors ())
		set_color (mw, COLOR_BLUE);
	    waddch (mw, (unsigned char)('0' + (10 * old_note[v].velocity) / 128));
	    if (has_colors ())
		unset_color (mw);
	    break;
	}
      }
	current_voices = old_note[v].nvoices;
	current_real_voices = old_note[v].real_voices;
	current_reverb_voices = old_note[v].reverb_voices;
	current_tempo = old_note[v].tempo;
	v++;
	if (v == MAX_OLD_NOTES)
	    v = 0;
	then = old_note[v].time;
    }
    trailing_pointer = v;
}

static void
ctl_program (int ch, int val, const char *name)
{
    int     realch = ch;
    if (!ctl.trace_playing)
	return;
    ch &= 0x0f;

    if (name && realch < 16) {
	const char *subname = strrchr(name, '/');
	if (!subname || strlen(name) < MAX_PNW) subname = name;
	else subname++;
	strncpy (patch_name[ch], subname, MAX_PNW);
	patch_name[ch][MAX_PNW - 1] = '\0';
    }
    if (patches_width >= 12) {
	wmove (mw, 8 + ch, patches_x);
	if (patch_name[ch][0])
	    wprintw (mw, (char *) "%-12s", patch_name[ch]);
	else
	    waddstr (mw, "            ");
    }

    wmove (mw, 8 + ch, program_x);
    if (!channel[ch].kit)
	wprintw (mw, (char *) "%3d", val);
}

static void
ctl_bank (int ch, int val, int var, const char *name)
{
    int     realch = ch;
    ch &= 0x0f;
    if (!ctl.trace_playing || ch != realch) return;
    if (!val && !var && !channel[ch].kit && !channel[ch].xg) return;

    if (name && channel[ch].kit) {
	strncpy (patch_name[ch], name, MAX_PNW);
	patch_name[ch][MAX_PNW - 1] = '\0';
    }
    if (patches_width >= 12 && channel[ch].kit) {
	wmove (mw, 8 + ch, patches_x);
	if (patch_name[ch][0]) {
	    if (channel[ch].kit == 126) set_color (mw, BRIGHT_MAGENTA);
	    else set_color (mw, BRIGHT_YELLOW);
	    wprintw (mw, (char *) "%-12s", patch_name[ch]);
	    unset_color (mw);
	}
	else
	    waddstr (mw, "            ");
    }

    wmove (mw, 8 + ch, bank_x);
    if (var) {
	    set_color (mw, COLOR_CYAN);
	wprintw (mw, (char *) "%3d", var);
	    unset_color (mw);
    }
    else if (channel[ch].kit) {
	    set_color (mw, BRIGHT_YELLOW);
	if (val >= MAXPROG) val -= MAXPROG;
	wprintw (mw, (char *) "%3d", val);
	    unset_color (mw);
        wmove (mw, 8 + ch, program_x);
	waddstr (mw, "   ");
    }
    else if (channel[ch].sfx) {
	    set_color (mw, COLOR_MAGENTA);
	waddstr (mw, "sfx");
	    unset_color (mw);
    }
    else if (channel[ch].xg) {
	if (channel[ch].xg == 48)
	    set_color (mw, BRIGHT_BLUE);
	else  set_color (mw, BRIGHT_GREEN);
	wprintw (mw, (char *) "%3d", val);
	    unset_color (mw);
    }
    else
	wprintw (mw, (char *) "%3d", val);
}

static void
ctl_volume (int ch, int val)
{
    if (!ctl.trace_playing)
	return;
    ch &= 0x0f;
    wmove (mw, 8 + ch, volume_x);
    wprintw (mw, (char *) "%3d", val);
}

static void
ctl_expression (int ch, int val)
{
    if (!ctl.trace_playing)
	return;
    ch &= 0x0f;
    wmove (mw, 8 + ch, expression_x);
    wprintw (mw, (char *) "%3d", val);
}

static void
ctl_panning (int ch, int val)
{
    if (!ctl.trace_playing)
	return;
    ch &= 0x0f;
    wmove (mw, 8 + ch, panning_x);
    if (val != NO_PANNING && has_colors ()) {
	if (val <= 60)
	    set_color (mw, COLOR_CYAN);
	if (val >= 68)
	    set_color (mw, BRIGHT_YELLOW);
    }
    if (val == NO_PANNING)
	waddstr (mw, "   ");
    else if (val < 5)
	waddstr (mw, " L ");
    else if (val > 123)
	waddstr (mw, " R ");
    else if (val > 60 && val < 68)
	waddstr (mw, " C ");
    else {
	val = (100 * (val - 64)) / 64;
	if (val < 0) {
	    waddch (mw, '-');
	    val = -val;
	}
	else
	    waddch (mw, '+');
	wprintw (mw, (char *) "%02d", val);
    }
    if (val != NO_PANNING && has_colors ()) {
	unset_color (mw);
    }
}

static void
ctl_sustain (int ch, int val, int letter)
{
    if (!ctl.trace_playing)
	return;
    ch &= 0x0f;
    //wmove (mw, 8 + ch, maxx - 4);
    wmove (mw, 8 + ch, sustain_x);
    if (val) {
	if (has_colors ()) {
		if (letter == 'S') set_color (mw, BRIGHT_BLUE);
		else if (letter == 'P') set_color (mw, BRIGHT_YELLOW);
		else if (letter == 'O') set_color (mw, COLOR_CYAN);
		else if (letter == 's') set_color (mw, BRIGHT_GREEN);
	}
	waddch (mw, (unsigned char)letter);
        if (has_colors ()) unset_color (mw);
    }
    else
	waddch (mw, ' ');
}

static void
ctl_pitch_bend (int ch, int val, uint32 mod)
{
    if (!ctl.trace_playing)
	return;
    ch &= 0x0f;
    //wmove (mw, 8 + ch, maxx - 2);
    wmove (mw, 8 + ch, pitch_bend_x);
    if (mod) {
	if (has_colors ()) set_color (mw, COLOR_MAGENTA);
	waddch (mw, '*');
    }
    else if (val > 0x2000) {
	if (has_colors ()) set_color (mw, BRIGHT_YELLOW);
	waddch (mw, '+');
    }
    else if (val < 0x2000) {
	if (has_colors ()) set_color (mw, BRIGHT_CYAN);
	waddch (mw, '-');
    }
    else {
	waddch (mw, ' ');
    }
    if (has_colors ()) unset_color (mw);
}

static void
ctl_reset (void)
{
    int     i, j;

    loading_progress = 0;
    lastkeysig = CTL_STATUS_UPDATE;
    lastoffset = CTL_STATUS_UPDATE;
    lasttempo = CTL_STATUS_UPDATE;
    lastratio = CTL_STATUS_UPDATE;

    current_tempo = cooked_tempo = CTL_STATUS_UPDATE;
    songoffset = 0;
    current_offset = 0;
    song_title_width = 0;
    author_width = 0;
    save_file_name[0] = save_song_title[0] = save_author[0] = '\0';
    current_trace = 0;

    if (!ctl.trace_playing) {
	    return;
    }

    if (ctl_help_window) ctl_help_mode ();
    if (ctl_mix_window) mixer_mode ();
    wmove (mw, 2, 0);
    wclrtoeol (mw);
    wmove (mw, 4, timesig_x);
    wclrtoeol (mw);
    if (save_master_volume)
	ctl_master_volume (save_master_volume);
    for (i = 0; i < 16; i++) {
	patch_name[i][0] = '\0';
	wmove (mw, 8 + i, patches_x);
        wclrtoeol (mw);
	if (notes_width >= 10)
	    for (j = 0; j < notes_width; j++)
		mvwaddch (mw, 8 + i, notes_x + j, ACS_BULLET);
    }
    for (i = 0; i < MAX_OLD_NOTES; i++) {
	old_note[i].time = 0;
    }
    clear_voices_bar ();
    leading_pointer = trailing_pointer = current_voices = current_real_voices = current_reverb_voices = 0;
    _ctl_refresh ();
}

static void
ctl_redo (void)
{
    int     i;
    if (!ctl.trace_playing) {
#if 0
        if (save_master_volume)
	    ctl_master_volume (save_master_volume);
        ctl_file_name (NULL);
        ctl_song_title (NULL);
        ctl_author (NULL);
#endif
	return;
    }
    loading_progress = 0;
    wmove (mw, 2, 0);
    wclrtoeol (mw);
    wmove (mw, 4, timesig_x);
    wclrtoeol (mw);

    if (save_master_volume)
	ctl_master_volume (save_master_volume);
    ctl_file_name (NULL);
    ctl_song_title (NULL);
    ctl_author (NULL);

    for (i = 0; i < 16; i++) {
	int j;
	//wmove (mw, 8 + i, patches_x);
	if (notes_width >= 10)
	    for (j = 0; j < notes_width; j++)
		mvwaddch (mw, 8 + i, notes_x + j, ACS_BULLET);
	//ctl_program (i, channel[i].program, 0);
	//ctl_volume (i, channel[i].volume);
	//ctl_expression (i, channel[i].expression);
	//ctl_panning (i, channel[i].panning);
	//ctl_sustain (i, channel[i].sustain, 'S');
	//ctl_pitch_bend (i, channel[i].pitchbend, channel[i].modulation_wheel);
    }

    for (i = 0; i < MAX_OLD_NOTES; i++) {
	old_note[i].time = 0;
    }
    clear_voices_bar ();
    leading_pointer = trailing_pointer = current_voices = current_real_voices = current_reverb_voices = 0;
    _ctl_refresh ();
}

static void
set_dimensions (void)
{

/* line 3 */
    file_name_x = 0;
    file_name_width = maxx - 1;
    song_title_width = 0;
    song_title_x = file_name_x + file_name_width;
    author_width = 0;
    author_x = song_title_x + song_title_width;

/* line 4 */
    timesig_x = 23;
    timesig_width = 14 + 2;
    tempo_x = timesig_x + timesig_width;
    tempo_width = 10 + 2;
    keysig_x = tempo_x + tempo_width;
    keysig_width = 13 + 1;

    labels_width = 28;
    labels_start_x = maxx - labels_width;
    opt_volume_x = labels_start_x + 4 + 4 + 2;
    opt_expression_x = labels_start_x + 4 + 4 + 4 + 2;
    reverb_x = labels_start_x + 4 + 4 + 4 + 4 + 4 + 2 + 2;
    chorus_x = reverb_x + 1;
    celeste_x = chorus_x + 1;

    master_volume_x = labels_start_x - (12+2+11+2-labels_width);
    master_volume_width = 12 + 2;
    clips_x = master_volume_x + master_volume_width;
    clips_width = 11 + 2;

    patches_x = 3;
    patches_width = MAX_PNW + 1;
    notes_x = patches_x + patches_width;
    notes_width = maxx - patches_x - patches_width - labels_width - 1;
    // = 100 - 3 - 13 - 28 - 1 = 100 - 45 = 55
	//xl = (new_note * notes_width) / (high_clip + 1);
    low_clip = 32;
    high_clip = 59;  // display notes 32-91
    if (high_clip < notes_width - 1) high_clip = notes_width - 1;

    lyric_start = maxx / 16;
    lyric_end = maxx - maxx / 16;

    bar_h_x = 14;
    bar_h_width = maxx - labels_width - bar_h_x - 2;
    bank_x = labels_start_x;
    program_x = labels_start_x + 4;
    volume_x = labels_start_x + 4 + 4;
    expression_x = labels_start_x + 8 + 4;
    panning_x = labels_start_x + 12 + 4;
    sustain_x = labels_start_x + 16 + 4;
    pitch_bend_x = labels_start_x + 17 + 4;
    misc_controller_x = labels_start_x + 20 + 4;

    play_list_y = 25;
}

static void
draw_windows (void)
{
    int     i;

    set_dimensions ();

    werase (mw);
    wmove (mw, 0, 0);
    waddstr (mw, PACKAGE_STRING "/TiMidity v0.2i");
    wmove (mw, 0, maxx - 52);
    waddstr (mw, "(C) 1995 Tuukka Toivonen <toivonen@clinet.fi>");

    wmove (mw, 1, 3);
    wprintw (mw, (char *) "patchset: %-20s interpolation: %-8s   loading: %-4s",
	     cfg_names[cfg_select] ? cfg_names[cfg_select] : "?",
	     (current_interpolation&INTERPOLATION_CSPLINE) ? "C-spline" :
	         (current_interpolation&INTERPOLATION_LAGRANGE) ? "Lagrange" : "linear",
	     fast_load? "fast" : "full");

    if (saved_message[0]) {
    	wmove (mw, 2, lyric_start);
	waddstr (mw, saved_message);
	saved_message[0] = '\0';
    }
    wmove (mw, 4, 0);
    if (ctl.trace_playing) {
	waddstr (mw, "Time:");
	wmove (mw, 4, 6 + 6 + 1);
	waddch (mw, '/');
    }
    else {
	waddstr (mw, "Playing time:");
    }
    if (save_master_volume)
	ctl_master_volume (save_master_volume);
    if (save_total_time)
	ctl_total_time (save_total_time);
    ctl_file_name (NULL);
    ctl_song_title (NULL);
    ctl_author (NULL);
    wmove (mw, 5, 0);
    for (i = 0; i < maxx; i++)
	waddch (mw, ACS_HLINE);

    if (ctl.trace_playing) {
	wmove (mw, 6, 0);
	waddstr (mw, "Ch");
	wmove (mw, 6, 3);
	waddch (mw, ACS_VLINE);
	wmove (mw, 5, 3);
	waddch (mw, ACS_TTEE);
	wmove (mw, 6, 5);
	waddstr (mw, "Voices:");
	wmove (mw, 6, labels_start_x);
	waddstr (mw, "Bnk Prg Vol Exp Pan SBbhRCcX");
	wmove (mw, 6, labels_start_x - 2);
	waddch (mw, ACS_VLINE);
	wmove (mw, 5, labels_start_x - 2);
	waddch (mw, ACS_TTEE);
	wmove (mw, 7, 0);
	for (i = 0; i < maxx; i++)
	    waddch (mw, ACS_HLINE);
	wmove (mw, 7, 3);
	waddch (mw, ACS_BTEE);
	wmove (mw, 7, labels_start_x - 2);
	waddch (mw, ACS_BTEE);
	for (i = 0; i < 16; i++) {
	    wmove (mw, 8 + i, 0);
	    wprintw (mw, (char *) "%02d", i + 1);
	}
	if (maxy > 24) {
	    wmove (mw, 24, 0);
	    for (i = 0; i < maxx; i++)
	        waddch (mw, ACS_HLINE);
	}
    }
    else {
	if (msgwin)
	    wresize (msgwin, maxy - 6, maxx);
	else {
	    msgwin = newwin (maxy - 6, maxx, 6, 0);
	    scrollok (msgwin, TRUE);
	    werase (msgwin);
	}
	touchwin (msgwin);
	wrefresh (msgwin);
    }
    touchwin (mw);
    wrefresh (mw);
    screen_bugfix = 0;
}

static void
slk_setup (void)
{
    int i;

    slk_set (1, "Help", 1);
    if ((current_interpolation & INTERPOLATION_LINEAR))
    	slk_set (2, "Lnear", 1);
    else slk_set (2, "nonln", 1);
    if ((current_interpolation & INTERPOLATION_CSPLINE))
    	slk_set (3, "Cspln", 1);
    else if ((current_interpolation & INTERPOLATION_LAGRANGE))
    	slk_set (3, "Lgrng", 1);
    else slk_set (3, "     ", 1);
    if ((current_interpolation & INTERPOLATION_BUTTERWORTH))
    	slk_set (4, "Filtr", 1);
    else slk_set (4, "noflt", 1);

    for (i = 0; i < 4; i++) {
	if (cfg_names[i])
	    strncpy (short_cfg_names[i], cfg_names[i], 5);
	else
	    strcpy (short_cfg_names[i], "(nne)");
	short_cfg_names[i][5] = '\0';
	slk_set (i + 5, short_cfg_names[i], 1);
    }

    slk_set (9, "Next", 1);
    slk_set (10, "Quit", 1);
    if (opt_dry)
	slk_set (11, "Dry", 1);
    else
	slk_set (11, "Wet", 1);
    slk_set (12, "Mixer", 1);
    slk_noutrefresh ();
}

static int
ctl_open (int using_stdin, int using_stdout)
{
    if (using_stdin || using_stdout) {
	    fprintf(stderr, "Ncurses interface doesn't use stdin or stdout\n");
	    return -1;
    }
    slk_init (3);
    initscr ();

    if (has_colors ()) {
	start_color ();
#ifdef HAVE_USE_DEFAULT_COLORS
#ifdef REALLY_USE_DEFAULT
	if (use_default_colors () == OK)
	    my_bg = -1;
#endif
#endif
	make_pairs ();
    }


    cbreak ();
    noecho ();
    nonl ();
    nodelay (stdscr, 1);
    scrollok (stdscr, 0);
    idlok (stdscr, 1);
    keypad (stdscr, TRUE);
    curs_set (0);
    getmaxyx (stdscr, maxy, maxx);
    ctl.opened = 1;

#ifdef NCURSES_MOUSE_VERSION
    (void) mousemask(BUTTON1_CLICKED, (mmask_t *) NULL);
#endif /* NCURSES_MOUSE_VERSION */

    if (ctl.trace_playing)
	mw = stdscr;
    else
	mw = newwin (6, maxx, 0, 0);

    slk_setup();

    draw_windows ();
    ctl_reset ();
    return 0;
}

static void
ctl_close (void)
{
    if (ctl.opened) {
	refresh ();
	endwin ();
	curs_set (1);
	ctl.opened = 0;
    }
}

static int ncurses_song_paused = 0;

static int
ctl_read (int32 *valp)
{
    int     c;
    if (last_rc_command) {
	*valp = last_rc_arg;
	c = last_rc_command;
	last_rc_command = 0;
	return c;
    }
    while ((c = getch ()) != ERR) {
	re_init_screen ();

	switch (c) {
	case 'h':
	case '?':
	case KEY_F (1):
	    ctl_help_mode ();
	    return RC_NONE;

	case 'V':
	case KEY_UP:
	    *valp = 10;
	    return RC_CHANGE_VOLUME;
	case 'v':
	case KEY_DOWN:
	    *valp = -10;
	    return RC_CHANGE_VOLUME;
	case 27:
	case 'q':
	case KEY_END:
	case KEY_F (10):
	    return RC_QUIT;
	case KEY_F (9):
	case 'n':
	case KEY_NPAGE:
	    play_list_increment = 1;
	    show_playlist();
	    return RC_NEXT;
	case KEY_F (12):
	    mixer_mode ();
	    return RC_NONE;
	case 'p':
	case KEY_PPAGE:
	    play_list_increment = -1;
	    show_playlist();
	    return RC_REALLY_PREVIOUS;
	case 'r':
	case KEY_HOME:
	    return RC_RESTART;

	case 'F':
	    fast_load = !fast_load;
	    screen_bugfix = 0;
	    re_init_screen ();
	    return RC_NONE;

	case 'f':
	case KEY_RIGHT:
	    songoffset += *valp = play_mode->rate;
	    return RC_FORWARD;
	case 'b':
	case KEY_LEFT:
	    songoffset -= *valp = play_mode->rate;
	    if (songoffset < 0)
		songoffset = 0;
	    return RC_BACK;

	case KEY_SLEFT:
	    if (voices_ceiling - 10 > 16) voices_ceiling -= 10;
	    else voices_ceiling = 16;
	    return RC_NONE;
	case KEY_SRIGHT:
	    if (voices_ceiling + 10 < voices) voices_ceiling += 10;
	    else voices_ceiling = voices;
	    return RC_NONE;
#ifdef NCURSES_MOUSE_VERSION
	case KEY_MOUSE:
	    {
		MEVENT myevent;

		if ( getmouse(&myevent) == OK ) {
			play_list_mouse_x = myevent.x;
			play_list_mouse_y = myevent.y;
		}
	    }
	    if (ctl_mix_window && play_list_mouse_y > 8 && play_list_mouse_y < 8 + 16) {
		    handle_mixer_mouse ();
		    return RC_NONE;
	    }
	    if (play_list_mouse_y == 6) {
		if (play_list_mouse_x == reverb_x) {
		    reverb_options ^= OPT_REVERB_VOICE;
		}
		else if (play_list_mouse_x == chorus_x) {
		    reverb_options ^= OPT_CHORUS_VOICE;
		}
		else if (play_list_mouse_x == celeste_x) {
		    reverb_options ^= OPT_CELESTE_VOICE;
		}
		else if (play_list_mouse_x == opt_volume_x) {
		        opt_volume_curve++;
		        if (opt_volume_curve > 5) opt_volume_curve = 0;
		}
		else if (play_list_mouse_x == opt_expression_x) {
		        opt_expression_curve++;
		        if (opt_expression_curve > 5) opt_expression_curve = 0;
		}
		else beep ();
		show_reverb_opts ();
		return RC_NONE;
	    }
	    if (show_playlist()) return RC_NEXT;
	    beep ();
    	    play_list_mouse_x = play_list_mouse_y = -1;
	    return RC_NONE;
#endif /* NCURSES_MOUSE_VERSION */

#ifdef KEY_RESIZE
	case KEY_RESIZE:
	    getmaxyx (stdscr, maxy, maxx);
	    slk_touch();
    	    slk_noutrefresh ();
	    draw_windows ();
	    ctl_redo ();
	    return RC_NONE;
#endif
/*
2 - shows linear vs cspline interpolation (vs lagrange?)
3 - shows ??
4 - shows filter = butterworth vs vsf vs moog vs no filter
*/
	case KEY_F (2):
    	    if ((current_interpolation & INTERPOLATION_LINEAR)) {
		current_interpolation = (INTERPOLATION_CSPLINE | (current_interpolation & INTERPOLATION_BUTTERWORTH));
    		slk_set (2, "nonln", 1);
    		slk_set (3, "Cspln", 1);
	    }
	    else {
		current_interpolation = (INTERPOLATION_LINEAR | (current_interpolation & INTERPOLATION_BUTTERWORTH));
    		slk_set (2, "Lnear", 1);
    		slk_set (3, "     ", 1);
	    }
	    slk_refresh ();
	    screen_bugfix = 0;
	    re_init_screen ();
	    return RC_NONE;
	case KEY_F (3):
    	    if ((current_interpolation & INTERPOLATION_CSPLINE)) {
		current_interpolation = INTERPOLATION_LAGRANGE;
    		slk_set (2, "nonln", 1);
    		slk_set (3, "Lgrng", 1);
    		slk_set (4, "noflt", 1);
	    }
	    else {
		current_interpolation = (INTERPOLATION_CSPLINE | (current_interpolation & INTERPOLATION_BUTTERWORTH));
    		slk_set (2, "nonln", 1);
    		slk_set (3, "Cspln", 1);
	    }
	    slk_refresh ();
	    screen_bugfix = 0;
	    re_init_screen ();
	    return RC_NONE;
	case KEY_F (4):
	    if (!(current_interpolation & INTERPOLATION_BUTTERWORTH)) {
		current_interpolation = INTERPOLATION_BUTTERWORTH | INTERPOLATION_CSPLINE;
    		slk_set (2, "nonln", 1);
    		slk_set (3, "Cspln", 1);
    		slk_set (4, "Filtr", 1);
	    }
	    else {
		current_interpolation &= ~INTERPOLATION_BUTTERWORTH;
    		slk_set (4, "noflt", 1);
	    }
	    slk_refresh ();
	    screen_bugfix = 0;
	    re_init_screen ();
	    return RC_NONE;
	case KEY_F (5):
	    cfg_select = 0;
	    screen_bugfix = 0;
	    re_init_screen ();
	    return RC_PATCHCHANGE;
	case KEY_F (6):
	    cfg_select = 1;
	    screen_bugfix = 0;
	    re_init_screen ();
	    return RC_PATCHCHANGE;
	case KEY_F (7):
	    cfg_select = 2;
	    screen_bugfix = 0;
	    re_init_screen ();
	    return RC_PATCHCHANGE;
	case KEY_F (8):
	    cfg_select = 3;
	    screen_bugfix = 0;
	    re_init_screen ();
	    return RC_PATCHCHANGE;
	case KEY_F (11):
	    if (!opt_dry) {
		opt_dry = 1;
		slk_set (11, "Dry", 1);
	    }
	    else {
		opt_dry = 0;
		slk_set (11, "Wet", 1);
	    }
	    slk_refresh ();
	    return RC_NONE;
	default:
	    return RC_NONE;
	case ' ':
	    if (ncurses_song_paused)
	    songoffset = current_offset;
	    ncurses_song_paused = !ncurses_song_paused;
	    return RC_TOGGLE_PAUSE;
	}
    }
    re_init_screen ();

    return RC_NONE;
}

static int
cmsg (int type, int verbosity_level, const char *fmt, ...)
{
    va_list ap;
    char    local_string[1024];
    int     flagnl = 1, msg_len;
    static int	    lyric_x_pos = 0;

    if ((type == CMSG_TEXT || type == CMSG_INFO || type == CMSG_WARNING) &&
	ctl.verbosity < verbosity_level)
	return 0;
    if (type == CMSG_LOAD && ctl.verbosity < verbosity_level && !ctl.trace_playing)
	return 0;
    if (type == CMSG_TRACE && ctl.verbosity < verbosity_level)
	return 0;
    if (*fmt == '~') {
	flagnl = 0;
	fmt++;
    }

    va_start (ap, fmt);
    vsprintf(local_string, fmt, ap);
    msg_len = strlen (local_string);

    if (!ctl.opened) {
	strncpy(saved_message, local_string, 200);
    }
    else if (ctl.trace_playing) {

	if (type == CMSG_TRACE) {
	    if (current_trace < LAST_TRACE) {
		if (msg_len > MAX_TRACE_LEN-1) msg_len = MAX_TRACE_LEN-1;
		local_string[MAX_TRACE_LEN-1] = '\0';
		strcpy (trace_message[current_trace++], local_string);
	    }
    	    va_end (ap);
    	    return 0;
	}

	if (type == CMSG_LOAD) {
		msg_len = 0;
		flagnl = 1;
	}
    	if (!lyric_x_pos) lyric_x_pos = lyric_start;

	if (flagnl) lyric_x_pos = lyric_start;
	if (lyric_x_pos + msg_len > lyric_end) lyric_x_pos = lyric_start;
	if (lyric_x_pos + msg_len > lyric_end) {
	        wmove (mw, 2, 0);
	        wclrtoeol (mw);
    		va_end (ap);
    		return 0;
	}

	switch (type) {
	case CMSG_LOAD:
	case CMSG_LYRIC:
	/*
	case CMSG_WARNING:
	case CMSG_ERROR:
	*/
	case CMSG_FATAL:
	    wmove (mw, 2, lyric_x_pos);
	    if (!ctl_help_window) wclrtoeol (mw);
	    if (has_colors ()) {
		if (type == CMSG_LYRIC)
			set_color (mw, BRIGHT_YELLOW);
		else if (type == CMSG_LOAD)
			set_color (mw, COLOR_CYAN);
		else set_color (mw, COLOR_RED);
	    }
	    else
		wattron (mw, A_REVERSE);
	    if (type == CMSG_LOAD) {
		    int i;
		    int load_bar_width = maxx - 6 - lyric_x_pos - 7;

		    if (max_patch_memory) {
			    float used_mem = (float)current_patch_memory / (float)max_patch_memory;
			    if (used_mem > 1.0) used_mem = 1.0;
		    	    waddstr(mw, "Memory");
			    loading_progress = (int)(used_mem * load_bar_width);
		    }
		    else waddstr(mw, "Loading");

		    if (!ctl_help_window)
		    for (i = 0; i < loading_progress; i++) {
		    	wmove (mw, 2, lyric_x_pos + 7 + i);
		        waddch (mw, '.');
		    }
		    if (!max_patch_memory && loading_progress < load_bar_width) loading_progress++;
	    }
	    else if (!ctl_help_window) vwprintw (mw, (char *) fmt, ap);

	    if (has_colors ()) unset_color (mw);
	    else wattroff (mw, A_REVERSE);
	    _ctl_refresh ();
	    if (type != CMSG_LYRIC && type != CMSG_LOAD) {
		sleep (2);
		lyric_x_pos = lyric_start;
	        wmove (mw, 2, 0);
	        wclrtoeol (mw);
	    }
	    else {
		lyric_x_pos += msg_len;
	    }
	    _ctl_refresh ();
	    break;
	}
    }
    else {
	switch (type) {
	default:
	    vwprintw (msgwin, (char *) fmt, ap);
	    if (flagnl) waddch (msgwin, '\n');
	    break;

	case CMSG_WARNING:
	    if (has_colors ()) set_color (msgwin, BRIGHT_YELLOW);
	    else wattron (msgwin, A_BOLD);
	    vwprintw (msgwin, (char *) fmt, ap);
	    if (has_colors ()) unset_color (msgwin);
	    else wattroff (msgwin, A_BOLD);
	    waddch (msgwin, '\n');
	    break;

	case CMSG_TIME:
	case CMSG_TOTAL:
	case CMSG_TEXT:
	case CMSG_FILE:
	case CMSG_LYRIC:
	case CMSG_LOAD:
	case CMSG_TRACE:
	    if (has_colors ()) switch(type) {
		    case CMSG_TRACE: set_color (msgwin, COLOR_CYAN); break;
		    case CMSG_LYRIC:
		    case CMSG_FILE: set_color (msgwin, COLOR_WHITE); break;
		    case CMSG_TEXT:
		    case CMSG_TIME: set_color (msgwin, BRIGHT_GREEN); break;
		    case CMSG_LOAD: set_color (msgwin, COLOR_BLUE); break;
		    case CMSG_TOTAL: set_color (msgwin, COLOR_MAGENTA); break;
	    }
	    vwprintw (msgwin, (char *) fmt, ap);
	    if (has_colors ()) unset_color (msgwin);
	    if (flagnl) waddch (msgwin, '\n');
	    break;

	case CMSG_ERROR:
	case CMSG_FATAL:
	    if (has_colors ())
		set_color (msgwin, COLOR_RED);
	    else
		wattron (msgwin, A_REVERSE);
	    vwprintw (msgwin, (char *) fmt, ap);
	    if (has_colors ())
		unset_color (msgwin);
	    else
		wattroff (msgwin, A_REVERSE);
	    waddch (msgwin, '\n');
	    if (type == CMSG_FATAL) {
		wrefresh (msgwin);
		sleep (2);
	    }
	    break;
	}
	wrefresh (msgwin);
    }

    va_end (ap);
    return 0;
}

static void
ctl_pass_playing_list (int number_of_files, const char *list_of_files[])
{
    int     i = 0;

    if (number_of_files == 0)
	return;

    play_list = list_of_files;
    play_list_count = number_of_files;
    play_list_jump = -1;
    play_list_current = 0;
    play_list_increment = 0;

    show_playlist();

    for (;;) {

    	play_list_current = i;
        play_list_increment = 0;

	switch (play_midi_file (list_of_files[i])) {

	case RC_REALLY_PREVIOUS:
	    if (i > 0)
		i--;
	    break;

	case RC_PATCHCHANGE:
	    free_instruments ();
	    end_soundfont ();
	    clear_config ();
	    /* what if read_config fails?? */
	    if (read_config_file (current_config_file, 0))
		cmsg (CMSG_ERROR, VERB_NORMAL,
			   "Could not read patchset %d", cfg_select);
	    break;

	default:		/* An error or something */
	case RC_NEXT:

	    if (play_list_jump >= 0) {
		if (play_list_jump < play_list_count) {
			i = play_list_jump - 1;
		}
	    }
	    play_list_jump = -1;
    	    play_list_mouse_x = play_list_mouse_y = -1;

	    if (i < number_of_files - 1) {
		i++;
		ctl_reset ();
		break;
	    }
	    /* else fall through */

	case RC_QUIT:
	    play_mode->close_output ();
	    ctl_close ();
	    return;
	}
    }
}

static void
ctl_keysig(int k, int ko)
{
#ifdef tplus
	static const char *keysig_name[] = {
		"Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F", "C",
		"G", "D", "A", "E", "B", "F#", "C#", "G#",
		"D#", "A#"
	};
	int i, j;
	
	if (k == CTL_STATUS_UPDATE)
		k = lastkeysig;
	else
		lastkeysig = k;
	if (ko == CTL_STATUS_UPDATE)
		ko = lastoffset;
	else
		lastoffset = ko;
	if (k == CTL_STATUS_UPDATE) return;
	i = k + ((k < 8) ? 7 : -6);
	if (ko > 0)
		for (j = 0; j < ko; j++)
			i += (i > 10) ? -5 : 7;
	else
		for (j = 0; j < abs(ko); j++)
			i += (i < 7) ? 5 : -7;

	if (ctl.trace_playing && !ctl_help_window && keysig_width >= 13) {
	    wmove (mw, 4, keysig_x);
	    wattron(mw, A_BOLD);
	    wprintw(mw, "Key of %s %s",
			keysig_name[i], (k < 8) ? "Maj" : "Min");
	    wattroff(mw, A_BOLD);
	    _ctl_refresh();
	}
	if (!ctl.trace_playing) {
	    cmsg (CMSG_INFO, VERB_VERBOSE, "Key of %s %s (%+03d) ",
			keysig_name[i], (k < 8) ? "Maj" : "Min", ko);
	}
#endif
}

static void
ctl_tempo(int t, int tr)
{
#ifdef tplus
	
	if (t == CTL_STATUS_UPDATE)
		t = lasttempo;
	else
		lasttempo = t;
	if (tr == CTL_STATUS_UPDATE)
		tr = lastratio;
	else
		lastratio = tr;
	if (t != CTL_STATUS_UPDATE) {
		cooked_tempo =  (int) (500000 / (double) t * 120 * (double) tr / 100 + 0.5);
		if (!ctl.trace_playing) cmsg (CMSG_INFO, VERB_VERBOSE,
			"Tempo: %3d (%03d %%) ", cooked_tempo, tr);
	}
#endif
}


static void
ctl_timesig(int n, int d, int c, int b)
{
    if (ctl.trace_playing && !ctl_help_window && timesig_width >= 9) {
	wmove (mw, 4, timesig_x);
	waddstr(mw, "Signature:");
	wmove (mw, 4, timesig_x + 12);
	wattron(mw, A_BOLD);
	wprintw(mw, "%d/%d", n, 1<<d);
	wattroff(mw, A_BOLD);
	_ctl_refresh();
    }
    if (!ctl.trace_playing)  cmsg (CMSG_INFO, VERB_VERBOSE,
	      "Time signature: %d/%d %d clock %d q.n.", n, 1<<d, c, b);
}

static void
ctl_misc_controller(int ch, int val, int col, int letter, int use_color)
{
    if (!ctl.trace_playing || misc_controller_x + col >= maxx)
	return;
    ch &= 0x0f;
    use_color = 1;
    wmove (mw, 8 + ch, misc_controller_x + col);
    if (has_colors () && val != 0) {
	if (val > 120)      set_color (mw, BRIGHT_YELLOW);
	else if (val > 100) set_color (mw, BRIGHT_RED);
	else if (val >  80) set_color (mw, BRIGHT_MAGENTA);
	else if (val >  60) set_color (mw, BRIGHT_BLUE);
	else if (val >  40) set_color (mw, COLOR_CYAN);
	else if (val >  0) set_color (mw, BRIGHT_GREEN);
	else if (val <  -80) set_color (mw, COLOR_BLUE);
	else if (val <  -60) set_color (mw, COLOR_MAGENTA);
	else if (val <  -40) set_color (mw, COLOR_YELLOW);
	else if (val <  0) set_color (mw, COLOR_GREEN);
    }
    if (val != 0) waddch (mw, (unsigned char)letter);
    else waddch (mw, ' ');
    if (has_colors () && val != 0) unset_color (mw);
}
