/*
 * Schism Tracker - a cross-platform Impulse Tracker clone
 * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
 * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
 * copyright (c) 2009 Storlek & Mrs. Brisby
 * copyright (c) 2010-2012 Storlek
 * URL: http://schismtracker.org/
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/* This is getting almost as disturbing as the pattern editor. */

#include "headers.h"
#include "it.h"
#include "page.h"
#include "song.h"
#include "dmoz.h"
#include "video.h"

#include <sys/stat.h>

/* --------------------------------------------------------------------- */
/* just one global variable... */

int instrument_list_subpage = PAGE_INSTRUMENT_LIST_GENERAL;

/* --------------------------------------------------------------------- */
/* ... but tons o' ugly statics */

static struct widget widgets_general[18];
static struct widget widgets_volume[17];
static struct widget widgets_panning[19];
static struct widget widgets_pitch[20];

/* rastops for envelope */
static struct vgamem_overlay env_overlay = {
	32, 18, 65, 25,
	NULL, 0, 0, 0
};

/* toggled when pressing "," on the note table's sample field
 * more of a boolean than a bit mask  -delt.
 */
static int note_sample_mask = 1;

static struct widget *get_page_widgets(void)
{
	switch (instrument_list_subpage) {
	case PAGE_INSTRUMENT_LIST_GENERAL:
		return widgets_general;
	case PAGE_INSTRUMENT_LIST_VOLUME:
		return widgets_volume;
	case PAGE_INSTRUMENT_LIST_PANNING:
		return widgets_panning;
	case PAGE_INSTRUMENT_LIST_PITCH:
		return widgets_pitch;
	};
	return widgets_general;
}

static const int subpage_switches_group[5] = { 1, 2, 3, 4, -1 };
static const int nna_group[5] = { 6, 7, 8, 9, -1 };
static const int dct_group[5] = { 10, 11, 12, 13, -1 };
static const int dca_group[4] = { 14, 15, 16, -1 };

static const char *const pitch_envelope_states[] = { "Off", "On Pitch", "On Filter", NULL };

static int top_instrument = 1;
static int current_instrument = 1;
static int _altswap_lastvis = 99; // for alt-down instrument-swapping
static int instrument_cursor_pos = 25;  /* "play" mode */

static int note_trans_top_line = 0;
static int note_trans_sel_line = 0;

static int note_trans_cursor_pos = 0;

/* shared by all the numentries on a page
 * (0 = volume, 1 = panning, 2 = pitch) */
static int numentry_cursor_pos[3] = { 0 };

static int current_node_vol = 0;
static int current_node_pan = 0;
static int current_node_pitch = 0;

static int envelope_edit_mode = 0;
static int envelope_mouse_edit = 0;
static int envelope_tick_limit = 0;

static void set_subpage(int page);

/* playback */
static int last_note = 61;              /* C-5 */

/* strange saved envelopes */
static song_envelope_t saved_env[10];
static unsigned int flags[10] = {0};

/* --------------------------------------------------------------------------------------------------------- */

static void save_envelope(int slot, song_envelope_t *e, unsigned int sec)
{
	song_instrument_t *ins;

	slot = ((unsigned)slot)%10;

	ins = song_get_instrument(current_instrument);
	memcpy(&saved_env[slot], e, sizeof(song_envelope_t));

	switch (sec) {
	case ENV_VOLUME:
		flags[slot] = ins->flags & (ENV_VOLUME|ENV_VOLSUSTAIN|ENV_VOLLOOP|ENV_VOLCARRY);
		break;
	case ENV_PANNING:
		flags[slot] =
			((ins->flags & ENV_PANNING) ? ENV_VOLUME : 0)
		|       ((ins->flags & ENV_PANSUSTAIN) ? ENV_VOLSUSTAIN : 0)
		|       ((ins->flags & ENV_PANLOOP) ? ENV_VOLLOOP : 0)
		|       ((ins->flags & ENV_PANCARRY) ? ENV_VOLCARRY : 0)
		|       (ins->flags & ENV_SETPANNING);
		break;
	case ENV_PITCH:
		flags[slot] =
			((ins->flags & ENV_PITCH) ? ENV_VOLUME : 0)
		|       ((ins->flags & ENV_PITCHSUSTAIN) ? ENV_VOLSUSTAIN : 0)
		|       ((ins->flags & ENV_PITCHLOOP) ? ENV_VOLLOOP : 0)
		|       ((ins->flags & ENV_PITCHCARRY) ? ENV_VOLCARRY : 0)
		|       (ins->flags & ENV_FILTER);
		break;
	};
}
static void restore_envelope(int slot, song_envelope_t *e, unsigned int sec)
{
	song_instrument_t *ins;

	song_lock_audio();

	slot = ((unsigned)slot)%10;

	ins = song_get_instrument(current_instrument);
	memcpy(e, &saved_env[slot], sizeof(song_envelope_t));

	switch (sec) {
	case ENV_VOLUME:
		ins->flags &= ~(ENV_VOLUME|ENV_VOLSUSTAIN|ENV_VOLLOOP|ENV_VOLCARRY);
		ins->flags |= (flags[slot] & (ENV_VOLUME|ENV_VOLSUSTAIN|ENV_VOLLOOP|ENV_VOLCARRY));
		break;

	case ENV_PANNING:
		ins->flags &= ~(ENV_PANNING|ENV_PANSUSTAIN|ENV_PANLOOP|ENV_PANCARRY|ENV_SETPANNING);
		if (flags[slot] & ENV_VOLUME) ins->flags |= ENV_PANNING;
		if (flags[slot] & ENV_VOLSUSTAIN) ins->flags |= ENV_PANSUSTAIN;
		if (flags[slot] & ENV_VOLLOOP) ins->flags |= ENV_PANLOOP;
		if (flags[slot] & ENV_VOLCARRY) ins->flags |= ENV_PANCARRY;
		ins->flags |= (flags[slot] & ENV_SETPANNING);
		break;

	case ENV_PITCH:
		ins->flags &= ~(ENV_PITCH|ENV_PITCHSUSTAIN|ENV_PITCHLOOP|ENV_PITCHCARRY|ENV_FILTER);
		if (flags[slot] & ENV_VOLUME) ins->flags |= ENV_PITCH;
		if (flags[slot] & ENV_VOLSUSTAIN) ins->flags |= ENV_PITCHSUSTAIN;
		if (flags[slot] & ENV_VOLLOOP) ins->flags |= ENV_PITCHLOOP;
		if (flags[slot] & ENV_VOLCARRY) ins->flags |= ENV_PITCHCARRY;
		ins->flags |= (flags[slot] & ENV_FILTER);
		break;

	};

	song_unlock_audio();

	status.flags |= SONG_NEEDS_SAVE;
}



/* --------------------------------------------------------------------------------------------------------- */

static void instrument_list_draw_list(void);

/* --------------------------------------------------------------------------------------------------------- */
static int _last_vis_inst(void)
{
	int i, j, n;

	n = 99;
	j = 0;
	/* 65 is first visible sample on last page */
	for (i = 65; i < MAX_INSTRUMENTS; i++) {
		if (!csf_instrument_is_empty(current_song->instruments[i])) {
			j = i;
		}
	}
	while ((j + 34) > n)
		n += 34;
	return MIN(n, MAX_INSTRUMENTS - 1);
}
/* the actual list */

static void instrument_list_reposition(void)
{
	if (current_instrument < top_instrument) {
		top_instrument = current_instrument;
		if (top_instrument < 1) {
			top_instrument = 1;
		}
	} else if (current_instrument > top_instrument + 34) {
		top_instrument = current_instrument - 34;
	}
}

int instrument_get_current(void)
{
	return current_instrument;
}

void instrument_set(int n)
{
	int new_ins = n;
	song_instrument_t *ins;

	if (page_is_instrument_list(status.current_page)) {
		new_ins = CLAMP(n, 1, _last_vis_inst());
	} else {
		new_ins = CLAMP(n, 0, _last_vis_inst());
	}

	if (current_instrument == new_ins)
		return;

	envelope_edit_mode = 0;
	current_instrument = new_ins;
	instrument_list_reposition();

	ins = song_get_instrument(current_instrument);

	current_node_vol = ins->vol_env.nodes ? CLAMP(current_node_vol, 0, ins->vol_env.nodes - 1) : 0;
	current_node_pan = ins->pan_env.nodes ? CLAMP(current_node_vol, 0, ins->pan_env.nodes - 1) : 0;
	current_node_pitch = ins->pitch_env.nodes ? CLAMP(current_node_vol, 0, ins->pan_env.nodes - 1) : 0;

	status.flags |= NEED_UPDATE;
}

void instrument_synchronize_to_sample(void)
{
	song_instrument_t *ins;
	int sample = sample_get_current();
	int n, pos;

	/* 1. if the instrument with the same number as the current sample
	 * has the sample in its sample_map, change to that instrument. */
	ins = song_get_instrument(sample);
	for (pos = 0; pos < 120; pos++) {
		if ((int)(ins->sample_map[pos]) == sample) {
			instrument_set(sample);
			return;
		}
	}

	/* 2. look through the instrument list for the first instrument
	 * that uses the selected sample. */
	for (n = 1; n < 100; n++) {
		if (n == sample)
			continue;
		ins = song_get_instrument(n);
		for (pos = 0; pos < 120; pos++) {
			if ((int)(ins->sample_map[pos]) == sample) {
				instrument_set(n);
				return;
			}
		}
	}

	/* 3. if no instruments are using the sample, just change to the
	 * same-numbered instrument. */
	instrument_set(sample);
}

/* --------------------------------------------------------------------- */

static int instrument_list_add_char(int c)
{
	song_instrument_t *ins;

	if (c < 32)
		return 0;
	ins = song_get_instrument(current_instrument);
	text_add_char(ins->name, c, &instrument_cursor_pos, 25);
	if (instrument_cursor_pos == 25)
		instrument_cursor_pos--;

	get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1);
	status.flags |= NEED_UPDATE;
	status.flags |= SONG_NEEDS_SAVE;
	return 1;
}

static void instrument_list_delete_char(void)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);
	text_delete_char(ins->name, &instrument_cursor_pos, 25);

	get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1);
	status.flags |= NEED_UPDATE;
	status.flags |= SONG_NEEDS_SAVE;
}

static void instrument_list_delete_next_char(void)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);
	text_delete_next_char(ins->name, &instrument_cursor_pos, 25);

	get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1);
	status.flags |= NEED_UPDATE;
	status.flags |= SONG_NEEDS_SAVE;
}

static void clear_instrument_text(void)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);

	memset(ins->filename, 0, 14);
	memset(ins->name, 0, 26);
	if (instrument_cursor_pos != 25)
		instrument_cursor_pos = 0;

	get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1);
	status.flags |= NEED_UPDATE;
	status.flags |= SONG_NEEDS_SAVE;
}

/* --------------------------------------------------------------------- */

static void do_swap_instrument(int n)
{
	if (n >= 1 && n <= _last_vis_inst()) {
		song_swap_instruments(current_instrument, n);
	}
}

static void do_exchange_instrument(int n)
{
	if (n >= 1 && n <= _last_vis_inst()) {
		song_exchange_instruments(current_instrument, n);
	}
}

static void do_copy_instrument(int n)
{
	if (n >= 1 && n <= _last_vis_inst()) {
		song_copy_instrument(current_instrument, n);
	}
}

static void do_replace_instrument(int n)
{
	if (n >= 1 && n <= _last_vis_inst()) {
		song_replace_instrument(current_instrument, n);
	}
}

/* --------------------------------------------------------------------- */

static void instrument_list_draw_list(void)
{
	int pos, n;
	song_instrument_t *ins;
	int selected = (ACTIVE_PAGE.selected_widget == 0);
	int is_current;
	int ss, cl = 0, cr = 0;
	int is_playing[MAX_INSTRUMENTS];
	char buf[4];

	ss = -1;

	song_get_playing_instruments(is_playing);

	for (pos = 0, n = top_instrument; pos < 35; pos++, n++) {
		ins = song_get_instrument(n);
		is_current = (n == current_instrument);

		if (ins->played)
			draw_char(is_playing[n] > 1 ? 183 : 173, 1, 13 + pos, is_playing[n] ? 3 : 1, 2);

		draw_text(num99tostr(n, buf), 2, 13 + pos, 0, 2);
		if (instrument_cursor_pos < 25) {
			/* it's in edit mode */
			if (is_current) {
				draw_text_len(ins->name, 25, 5, 13 + pos, 6, 14);
				if (selected) {
					draw_char(ins->name[instrument_cursor_pos],
						  5 + instrument_cursor_pos,
						  13 + pos, 0, 3);
				}
			} else {
				draw_text_len(ins->name, 25, 5, 13 + pos, 6, 0);
			}
		} else {
			draw_text_len(ins->name, 25, 5, 13 + pos,
				      ((is_current && selected) ? 0 : 6),
				      (is_current ? (selected ? 3 : 14) : 0));
		}
		if (ss == n) {
			draw_text_len(ins->name + cl, (cr-cl)+1,
					5 + cl, 13 + pos,
					(is_current ? 3 : 11), 8);
		}
	}
}

static int instrument_list_handle_key_on_list(struct key_event * k)
{
	int new_ins = current_instrument;

	if (k->state == KEY_PRESS && k->mouse != MOUSE_NONE && k->y >= 13 && k->y <= 47 && k->x >= 5 && k->x <= 30) {
		if (k->mouse == MOUSE_CLICK) {
			new_ins = (k->y - 13) + top_instrument;
			if (instrument_cursor_pos < 25)
				instrument_cursor_pos = MIN(k->x - 5, 24);
			status.flags |= NEED_UPDATE;
		} else if (k->mouse == MOUSE_DBLCLICK) {
			/* this doesn't seem to work, but I think it'd be
			more useful if double click switched to edit mode */
			if (instrument_cursor_pos < 25) {
				instrument_cursor_pos = 25;
				get_page_widgets()->accept_text = 0;
			} else {
				set_page(PAGE_LOAD_INSTRUMENT);
			}
			status.flags |= NEED_UPDATE;
			return 1;

		} else if (k->mouse == MOUSE_SCROLL_UP) {
			top_instrument -= MOUSE_SCROLL_LINES;
			if (top_instrument < 1) top_instrument = 1;
			status.flags |= NEED_UPDATE;
			return 1;
		} else if (k->mouse == MOUSE_SCROLL_DOWN) {
			top_instrument += MOUSE_SCROLL_LINES;
			if (top_instrument > (_last_vis_inst()-34)) top_instrument = _last_vis_inst()-34;
			status.flags |= NEED_UPDATE;
			return 1;
		}
	} else {
		switch (k->sym.sym) {
		case SDLK_UP:
			if (k->state == KEY_RELEASE)
				return 0;
			if (k->mod & KMOD_ALT) {
				if (current_instrument > 1) {
					new_ins = current_instrument - 1;
					song_swap_instruments(current_instrument, new_ins);
				}
			} else if (!NO_MODIFIER(k->mod)) {
				return 0;
			} else {
				new_ins--;
			}
			break;
		case SDLK_DOWN:
			if (k->state == KEY_RELEASE)
				return 0;
			if (k->mod & KMOD_ALT) {
				// restrict position to the "old" value of _last_vis_inst()
				// (this is entirely for aesthetic reasons)
				if (status.last_keysym.sym != SDLK_DOWN && !k->is_repeat)
					_altswap_lastvis = _last_vis_inst();
				if (current_instrument < _altswap_lastvis) {
					new_ins = current_instrument + 1;
					song_swap_instruments(current_instrument, new_ins);
				}
			} else if (!NO_MODIFIER(k->mod)) {
				return 0;
			} else {
				new_ins++;
			}
			break;
		case SDLK_PAGEUP:
			if (k->state == KEY_RELEASE)
				return 0;
			if (k->mod & KMOD_CTRL)
				new_ins = 1;
			else
				new_ins -= 16;
			break;
		case SDLK_PAGEDOWN:
			if (k->state == KEY_RELEASE)
				return 0;
			if (k->mod & KMOD_CTRL)
				new_ins = _last_vis_inst();
			else
				new_ins += 16;
			break;
		case SDLK_HOME:
			if (k->state == KEY_RELEASE)
				return 0;
			if (!NO_MODIFIER(k->mod))
				return 0;
			if (instrument_cursor_pos < 25) {
				instrument_cursor_pos = 0;
				get_page_widgets()->accept_text = 1;
				status.flags |= NEED_UPDATE;
			}
			return 1;
		case SDLK_END:
			if (k->state == KEY_RELEASE)
				return 0;
			if (!NO_MODIFIER(k->mod))
				return 0;
			if (instrument_cursor_pos < 24) {
				instrument_cursor_pos = 24;
				get_page_widgets()->accept_text = 1;
				status.flags |= NEED_UPDATE;
			}
			return 1;
		case SDLK_LEFT:
			if (k->state == KEY_RELEASE)
				return 0;
			if (!NO_MODIFIER(k->mod))
				return 0;
			if (instrument_cursor_pos < 25 && instrument_cursor_pos > 0) {
				instrument_cursor_pos--;
				get_page_widgets()->accept_text = 1;
				status.flags |= NEED_UPDATE;
			}
			return 1;
		case SDLK_RIGHT:
			if (k->state == KEY_RELEASE)
				return 0;
			if (!NO_MODIFIER(k->mod))
				return 0;
			if (instrument_cursor_pos == 25) {
				get_page_widgets()->accept_text = 0;
				change_focus_to(1);
			} else if (instrument_cursor_pos < 24) {
				get_page_widgets()->accept_text = 1;
				instrument_cursor_pos++;
				status.flags |= NEED_UPDATE;
			}
			return 1;
		case SDLK_RETURN:
			if (k->state == KEY_PRESS)
				return 0;
			if (instrument_cursor_pos < 25) {
				instrument_cursor_pos = 25;
				get_page_widgets()->accept_text = 0;
				status.flags |= NEED_UPDATE;
			} else {
				get_page_widgets()->accept_text = 1;
				set_page(PAGE_LOAD_INSTRUMENT);
			}
			return 1;
		case SDLK_ESCAPE:
			if ((k->mod & KMOD_SHIFT) || instrument_cursor_pos < 25) {
				if (k->state == KEY_RELEASE)
					return 1;
				instrument_cursor_pos = 25;
				get_page_widgets()->accept_text = 0;
				status.flags |= NEED_UPDATE;
				return 1;
			}
			return 0;
		case SDLK_BACKSPACE:
			if (k->state == KEY_RELEASE)
				return 0;
			if (instrument_cursor_pos == 25)
				return 0;
			if ((k->mod & (KMOD_CTRL | KMOD_ALT)) == 0)
				instrument_list_delete_char();
			else if (k->mod & KMOD_CTRL)
				instrument_list_add_char(127);
			return 1;
		case SDLK_INSERT:
			if (k->state == KEY_RELEASE)
				return 0;
			if (k->mod & KMOD_ALT) {
				song_insert_instrument_slot(current_instrument);
				status.flags |= NEED_UPDATE;
				return 1;
			}
			return 0;
		case SDLK_DELETE:
			if (k->state == KEY_RELEASE)
				return 0;
			if (k->mod & KMOD_ALT) {
				song_remove_instrument_slot(current_instrument);
				status.flags |= NEED_UPDATE;
				return 1;
			} else if ((k->mod & KMOD_CTRL) == 0) {
				if (instrument_cursor_pos == 25)
					return 0;
				instrument_list_delete_next_char();
				return 1;
			}
			return 0;
		default:
			if (k->state == KEY_RELEASE)
				return 0;
			if (k->mod & KMOD_ALT) {
				if (k->sym.sym == SDLK_c) {
					clear_instrument_text();
					return 1;
				}
			} else if ((k->mod & KMOD_CTRL) == 0) {
				if (instrument_cursor_pos < 25) {
					if (!k->is_textinput) return 1;
					if (!k->unicode) return 0;
					return instrument_list_add_char(k->unicode);
				} else if (k->sym.sym == SDLK_SPACE || k->unicode == ' ') {
					instrument_cursor_pos = 0;
					get_page_widgets()->accept_text = 0;
					status.flags |= NEED_UPDATE;
					memused_songchanged();
					return 1;
				}
			}
			return 0;
		};
	}

	new_ins = CLAMP(new_ins, 1, _last_vis_inst());
	if (new_ins != current_instrument) {
		instrument_set(new_ins);
		status.flags |= NEED_UPDATE;
		memused_songchanged();
	}

	return 1;
}

/* --------------------------------------------------------------------- */
/* note translation table */

static void note_trans_reposition(void)
{
	if (note_trans_sel_line < note_trans_top_line) {
		note_trans_top_line = note_trans_sel_line;
	} else if (note_trans_sel_line > note_trans_top_line + 31) {
		note_trans_top_line = note_trans_sel_line - 31;
	}
}

static void note_trans_draw(void)
{
	int pos, n;
	int is_selected = (ACTIVE_PAGE.selected_widget == 5);
	int bg, sel_bg = (is_selected ? 14 : 0);
	song_instrument_t *ins = song_get_instrument(current_instrument);
	char buf[4];

	for (pos = 0, n = note_trans_top_line; pos < 32; pos++, n++) {
		bg = ((n == note_trans_sel_line) ? sel_bg : 0);

		/* invalid notes are translated to themselves (and yes, this edits the actual instrument) */
		if (ins->note_map[n] < 1 || ins->note_map[n] > 120)
			ins->note_map[n] = n + 1;

		draw_text(get_note_string(n + 1, buf), 32, 16 + pos, 2, bg);
		draw_char(168, 35, 16 + pos, 2, bg);
		draw_text(get_note_string(ins->note_map[n], buf), 36, 16 + pos, 2, bg);
		if (is_selected && n == note_trans_sel_line) {
			if (note_trans_cursor_pos == 0)
				draw_char(buf[0], 36, 16 + pos, 0, 3);
			else if (note_trans_cursor_pos == 1)
				draw_char(buf[2], 38, 16 + pos, 0, 3);
		}
		draw_char(0, 39, 16 + pos, 2, bg);
		if (ins->sample_map[n]) {
			num99tostr(ins->sample_map[n], buf);
		} else {
			buf[0] = buf[1] = 173;
			buf[2] = 0;
		}
		draw_text(buf, 40, 16 + pos, 2, bg);
		if (is_selected && n == note_trans_sel_line) {
			if (note_trans_cursor_pos == 2)
				draw_char(buf[0], 40, 16 + pos, 0, 3);
			else if (note_trans_cursor_pos == 3)
				draw_char(buf[1], 41, 16 + pos, 0, 3);
		}
	}

	/* draw the little mask thingy at the bottom. Could optimize this....  -delt.
	   Sure can! This could share the same track-view functions that the
	   pattern editor ought to be using. -Storlek */
	if (is_selected && !(status.flags & CLASSIC_MODE)) {
		switch (note_trans_cursor_pos) {
		case 0:
			draw_char(171, 36, 48, 3, 2);
			draw_char(171, 37, 48, 3, 2);
			draw_char(169, 38, 48, 3, 2);
			if (note_sample_mask) {
				draw_char(169, 40, 48, 3, 2);
				draw_char(169, 41, 48, 3, 2);
			}
			break;
		case 1:
			draw_char(169, 38, 48, 3, 2);
			if (note_sample_mask) {
				draw_char(170, 40, 48, 3, 2);
				draw_char(170, 41, 48, 3, 2);
			}
			break;
		case 2:
		case 3:
			draw_char(note_sample_mask ? 171 : 169, 40, 48, 3, 2);
			draw_char(note_sample_mask ? 171 : 169, 41, 48, 3, 2);
			break;
		};
	}
}

static void instrument_note_trans_transpose(song_instrument_t *ins, int dir)
{
	int i;
	for (i = 0; i < 120; i++) {
		ins->note_map[i] = CLAMP(ins->note_map[i]+dir, 1, 120);
	}
}

static void instrument_note_trans_insert(song_instrument_t *ins, int pos)
{
	int i;
	for (i = 119; i > pos; i--) {
		ins->note_map[i] = ins->note_map[i-1];
		ins->sample_map[i] = ins->sample_map[i-1];
	}
	if (pos) {
		ins->note_map[pos] = ins->note_map[pos-1]+1;
	} else {
		ins->note_map[0] = 1;
	}
}

static void instrument_note_trans_delete(song_instrument_t *ins, int pos)
{
	int i;
	for (i = pos; i < 120; i++) {
		ins->note_map[i] = ins->note_map[i+1];
		ins->sample_map[i] = ins->sample_map[i+1];
	}
	ins->note_map[119] = ins->note_map[118]+1;
}

static int note_trans_handle_key(struct key_event * k)
{
	int prev_line = note_trans_sel_line;
	int new_line = prev_line;
	int prev_pos = note_trans_cursor_pos;
	int new_pos = prev_pos;
	song_instrument_t *ins = song_get_instrument(current_instrument);
	int c, n;

	if (k->mouse == MOUSE_CLICK && k->mouse_button == MOUSE_BUTTON_MIDDLE) {
		if (k->state == KEY_RELEASE)
			status.flags |= CLIPPY_PASTE_SELECTION;
		return 1;
	} else if (k->mouse == MOUSE_SCROLL_UP || k->mouse == MOUSE_SCROLL_DOWN) {
		if (k->state == KEY_PRESS) {
			note_trans_top_line += (k->mouse == MOUSE_SCROLL_UP) ? -3 : 3;
			note_trans_top_line = CLAMP(note_trans_top_line, 0, 119 - 31);
			status.flags |= NEED_UPDATE;
		}
		return 1;
	} else if (k->mouse != MOUSE_NONE) {
		if (k->x >= 32 && k->x <= 41 && k->y >= 16 && k->y <= 47) {
			new_line = note_trans_top_line + k->y - 16;
			if (new_line == prev_line) {
				switch (k->x - 36) {
				case 2:
					new_pos = 1;
					break;
				case 4:
					new_pos = 2;
					break;
				case 5:
					new_pos = 3;
					break;
				default:
					new_pos = 0;
					break;
				};
			}
		}
	} else if (k->mod & KMOD_ALT) {
		if (k->state == KEY_RELEASE)
			return 0;
		switch (k->sym.sym) {
		case SDLK_UP:
			instrument_note_trans_transpose(ins, 1);
			break;
		case SDLK_DOWN:
			instrument_note_trans_transpose(ins, -1);
			break;
		case SDLK_INSERT:
			instrument_note_trans_insert(ins, note_trans_sel_line);
			break;
		case SDLK_DELETE:
			instrument_note_trans_delete(ins, note_trans_sel_line);
			break;
		case SDLK_n:
			n = note_trans_sel_line - 1; // the line to copy *from*
			if (n < 0 || ins->note_map[n] == NOTE_LAST)
				break;
			ins->note_map[note_trans_sel_line] = ins->note_map[n] + 1;
			ins->sample_map[note_trans_sel_line] = ins->sample_map[n];
			new_line++;
			break;
		case SDLK_p:
			n = note_trans_sel_line + 1; // the line to copy *from*
			if (n > (NOTE_LAST - NOTE_FIRST) || ins->note_map[n] == NOTE_FIRST)
				break;
			ins->note_map[note_trans_sel_line] = ins->note_map[n] - 1;
			ins->sample_map[note_trans_sel_line] = ins->sample_map[n];
			new_line--;
			break;
		case SDLK_a:
			c = sample_get_current();
			for (n = 0; n < (NOTE_LAST - NOTE_FIRST + 1); n++)
				ins->sample_map[n] = c;
			if (k->mod & KMOD_SHIFT) {
				// Copy the name too.
				memcpy(ins->name, current_song->samples[c].name, 32);
			}
			break;
		default:
			return 0;
		}
	} else {
		switch (k->sym.sym) {
		case SDLK_UP:
			if (k->state == KEY_RELEASE)
				return 0;
			if (k->mod & KMOD_CTRL)
				sample_set(sample_get_current () - 1);
			if (!NO_MODIFIER(k->mod))
				return 0;
			if (--new_line < 0) {
				change_focus_to(1);
				return 1;
			}
			break;
		case SDLK_DOWN:
			if (k->state == KEY_RELEASE)
				return 0;
			if (k->mod & KMOD_CTRL)
				sample_set(sample_get_current () + 1);
			if (!NO_MODIFIER(k->mod))
				return 0;
			new_line++;
			break;
		case SDLK_PAGEUP:
			if (k->state == KEY_RELEASE)
				return 0;
			if (k->mod & KMOD_CTRL) {
				instrument_set(current_instrument - 1);
				return 1;
			}
			new_line -= 16;
			break;
		case SDLK_PAGEDOWN:
			if (k->state == KEY_RELEASE)
				return 0;
			if (k->mod & KMOD_CTRL) {
				instrument_set(current_instrument + 1);
				return 1;
			}
			new_line += 16;
			break;
		case SDLK_HOME:
			if (k->state == KEY_RELEASE)
				return 0;
			if (!NO_MODIFIER(k->mod))
				return 0;
			new_line = 0;
			break;
		case SDLK_END:
			if (k->state == KEY_RELEASE)
				return 0;
			if (!NO_MODIFIER(k->mod))
				return 0;
			new_line = 119;
			break;
		case SDLK_LEFT:
			if (k->state == KEY_RELEASE)
				return 0;
			if (!NO_MODIFIER(k->mod))
				return 0;
			new_pos--;
			break;
		case SDLK_RIGHT:
			if (k->state == KEY_RELEASE)
				return 0;
			if (!NO_MODIFIER(k->mod))
				return 0;
			new_pos++;
			break;
		case SDLK_RETURN:
			if (k->state == KEY_PRESS)
				return 0;
			if (!NO_MODIFIER(k->mod))
				return 0;
			sample_set(ins->sample_map[note_trans_sel_line]);
			get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1);
			return 1;
		case SDLK_LESS:
		case SDLK_SEMICOLON:
		case SDLK_COLON:
			if (k->state == KEY_RELEASE)
				return 0;
			sample_set(sample_get_current() - 1);
			return 1;
		case SDLK_GREATER:
		case SDLK_QUOTE:
		case SDLK_QUOTEDBL:
			if (k->state == KEY_RELEASE)
				return 0;
			sample_set(sample_get_current() + 1);
			return 1;

		default:
			if (k->state == KEY_RELEASE)
				return 0;
			switch (note_trans_cursor_pos) {
			case 0:        /* note */
				n = kbd_get_note(k);
				if (!NOTE_IS_NOTE(n))
					return 0;
				ins->note_map[note_trans_sel_line] = n;
				if (note_sample_mask || (status.flags & CLASSIC_MODE))
					ins->sample_map[note_trans_sel_line] = sample_get_current();
				new_line++;
				break;
			case 1:        /* octave */
				c = kbd_char_to_hex(k);
				if (c < 0 || c > 9) return 0;
				n = ins->note_map[note_trans_sel_line];
				n = ((n - 1) % 12) + (12 * c) + 1;
				ins->note_map[note_trans_sel_line] = n;
				new_line++;
				break;

				/* Made it possible to enter H to R letters
				on 1st digit for expanded sample slots.  -delt. */

			case 2:        /* instrument, first digit */
			case 3:        /* instrument, second digit */
				if (!k->is_synthetic)
					break;
				if (k->sym.sym == SDLK_SPACE) {
					ins->sample_map[note_trans_sel_line] =
						sample_get_current();
					new_line++;
					break;
				}

				if ((k->sym.sym == SDLK_PERIOD && NO_MODIFIER(k->mod)) || k->sym.sym == SDLK_DELETE) {
					ins->sample_map[note_trans_sel_line] = 0;
					new_line += (k->sym.sym == SDLK_PERIOD) ? 1 : 0;
					break;
				}
				if (k->sym.sym == SDLK_COMMA && NO_MODIFIER(k->mod)) {
					note_sample_mask = note_sample_mask ? 0 : 1;
					break;
				}

				n = ins->sample_map[note_trans_sel_line];
				if (note_trans_cursor_pos == 2) {
					c = kbd_char_to_99(k);
					if (c < 0) return 0;
					n = (c * 10) + (n % 10);
					new_pos++;
				} else {
					c = kbd_char_to_hex(k);
					if (c < 0 || c > 9) return 0;
					n = ((n / 10) * 10) + c;
					new_pos--;
					new_line++;
				}
				n = MIN(n, MAX_SAMPLES - 1);
				ins->sample_map[note_trans_sel_line] = n;
				sample_set(n);
				break;
			}
			break;
		}
	}

	new_line = CLAMP(new_line, 0, 119);
	note_trans_cursor_pos = CLAMP(new_pos, 0, 3);
	if (new_line != prev_line) {
		note_trans_sel_line = new_line;
		note_trans_reposition();
	}

	/* this causes unneeded redraws in some cases... oh well :P */
	status.flags |= NEED_UPDATE;
	return 1;
}

/* --------------------------------------------------------------------------------------------------------- */
/* envelope helper functions */

static void _env_draw_axes(int middle)
{
	int n, y = middle ? 31 : 62;
	for (n = 0; n < 64; n += 2)
		vgamem_ovl_drawpixel(&env_overlay, 3, n, 12);
	for (n = 0; n < 256; n += 2)
		vgamem_ovl_drawpixel(&env_overlay, 1 + n, y, 12);
}

static void _env_draw_node(int x, int y, int on)
{
	int c = (status.flags & CLASSIC_MODE) ? 12 : 5;

	vgamem_ovl_drawpixel(&env_overlay, x - 1, y - 1, c);
	vgamem_ovl_drawpixel(&env_overlay, x - 1, y, c);
	vgamem_ovl_drawpixel(&env_overlay, x - 1, y + 1, c);

	vgamem_ovl_drawpixel(&env_overlay, x, y - 1, c);
	vgamem_ovl_drawpixel(&env_overlay, x, y, c);
	vgamem_ovl_drawpixel(&env_overlay, x, y + 1, c);

	vgamem_ovl_drawpixel(&env_overlay, x + 1, y - 1,c);
	vgamem_ovl_drawpixel(&env_overlay, x + 1, y,c);
	vgamem_ovl_drawpixel(&env_overlay, x + 1, y + 1,c);

	if (on) {
		vgamem_ovl_drawpixel(&env_overlay, x - 3, y - 1,c);
		vgamem_ovl_drawpixel(&env_overlay, x - 3, y,c);
		vgamem_ovl_drawpixel(&env_overlay, x - 3, y + 1,c);

		vgamem_ovl_drawpixel(&env_overlay, x + 3, y - 1,c);
		vgamem_ovl_drawpixel(&env_overlay, x + 3, y,c);
		vgamem_ovl_drawpixel(&env_overlay, x + 3, y + 1,c);
	}
}

static void _env_draw_loop(int xs, int xe, int sustain)
{
	int y = 0;
	int c = (status.flags & CLASSIC_MODE) ? 12 : 3;

	if (sustain) {
		while (y < 62) {
			/* unrolled once */
			vgamem_ovl_drawpixel(&env_overlay, xs, y, c);
			vgamem_ovl_drawpixel(&env_overlay, xe, y, c); y++;
			vgamem_ovl_drawpixel(&env_overlay, xs, y, 0);
			vgamem_ovl_drawpixel(&env_overlay, xe, y, 0); y++;
			vgamem_ovl_drawpixel(&env_overlay, xs, y, c);
			vgamem_ovl_drawpixel(&env_overlay, xe, y, c); y++;
			vgamem_ovl_drawpixel(&env_overlay, xs, y, 0);
			vgamem_ovl_drawpixel(&env_overlay, xe, y, 0); y++;
		}
	} else {
		while (y < 62) {
			vgamem_ovl_drawpixel(&env_overlay, xs, y, 0);
			vgamem_ovl_drawpixel(&env_overlay, xe, y, 0); y++;
			vgamem_ovl_drawpixel(&env_overlay, xs, y, c);
			vgamem_ovl_drawpixel(&env_overlay, xe, y, c); y++;
			vgamem_ovl_drawpixel(&env_overlay, xs, y, c);
			vgamem_ovl_drawpixel(&env_overlay, xe, y, c); y++;
			vgamem_ovl_drawpixel(&env_overlay, xs, y, 0);
			vgamem_ovl_drawpixel(&env_overlay, xe, y, 0); y++;
		}
	}
}

static void _env_draw(const song_envelope_t *env, int middle, int current_node,
			int env_on, int loop_on, int sustain_on, int env_num)
{
	song_voice_t *channel;
	unsigned int *channel_list;
	char buf[16];
	unsigned int envpos[3];
	int x, y, n, m, c;
	int last_x = 0, last_y = 0;
	int max_ticks = 50;

	while (env->ticks[env->nodes - 1] >= max_ticks)
		max_ticks *= 2;

	vgamem_ovl_clear(&env_overlay, 0);

	/* draw the axis lines */
	_env_draw_axes(middle);

	for (n = 0; n < env->nodes; n++) {
		x = 4 + env->ticks[n] * 256 / max_ticks;

		/* 65 values are being crammed into 62 pixels => have to lose three pixels somewhere.
		 * This is where IT compromises -- I don't quite get how the lines are drawn, though,
		 * because it changes for each value... (apart from drawing 63 and 64 the same way) */
		y = env->values[n];
		if (y > 63) y--;
		if (y > 42) y--;
		if (y > 21) y--;
		y = 62 - y;

		_env_draw_node(x, y, n == current_node);

		if (last_x)
			vgamem_ovl_drawline(&env_overlay,
				last_x, last_y, x, y, 12);

		last_x = x;
		last_y = y;
	}

	if (sustain_on)
		_env_draw_loop(4 + env->ticks[env->sustain_start] * 256 / max_ticks,
			       4 + env->ticks[env->sustain_end] * 256 / max_ticks, 1);
	if (loop_on)
		_env_draw_loop(4 + env->ticks[env->loop_start] * 256 / max_ticks,
			       4 + env->ticks[env->loop_end] * 256 / max_ticks, 0);

	if (env_on) {
		max_ticks = env->ticks[env->nodes-1];
		m = max_ticks ? song_get_mix_state(&channel_list) : 0;
		while (m--) {
			channel = song_get_mix_channel(channel_list[m]);
			if (channel->ptr_instrument != song_get_instrument(current_instrument))
				continue;

			envpos[0] = channel->vol_env_position;
			envpos[1] = channel->pan_env_position;
			envpos[2] = channel->pitch_env_position;

			x = 4 + (envpos[env_num] * (last_x-4) / max_ticks);
			if (x > last_x)
				x = last_x;
			c =  (status.flags & CLASSIC_MODE)
				? 12
				: ((channel->flags & (CHN_KEYOFF | CHN_NOTEFADE)) ? 8 : 6);
			for (y = 0; y < 62; y++)
				vgamem_ovl_drawpixel(&env_overlay, x, y, c);
		}
	}

	draw_fill_chars(65, 18, 76, 25, 0);
	vgamem_ovl_apply(&env_overlay);

	sprintf(buf, "Node %d/%d", current_node, env->nodes);
	draw_text(buf, 66, 19, 2, 0);
	sprintf(buf, "Tick %d", env->ticks[current_node]);
	draw_text(buf, 66, 21, 2, 0);
	sprintf(buf, "Value %d", (int)(env->values[current_node] - (middle ? 32 : 0)));
	draw_text(buf, 66, 23, 2, 0);
}

/* return: the new current node */
static int _env_node_add(song_envelope_t *env, int current_node, int override_tick, int override_value)
{
	int newtick, newvalue;

	status.flags |= SONG_NEEDS_SAVE;

	if (env->nodes > 24 || current_node == env->nodes - 1)
		return current_node;

	newtick = (env->ticks[current_node] + env->ticks[current_node + 1]) / 2;
	newvalue = (env->values[current_node] + env->values[current_node + 1]) / 2;
	if (override_tick > -1 && override_value > -1) {
		newtick = override_tick;
		newvalue = override_value;
	} else if (newtick == env->ticks[current_node] || newtick == env->ticks[current_node + 1]) {
		printf("Not enough room!\n");
		return current_node;
	}

	env->nodes++;
	memmove(env->ticks + current_node + 1, env->ticks + current_node,
		(env->nodes - current_node - 1) * sizeof(env->ticks[0]));
	memmove(env->values + current_node + 1, env->values + current_node,
		(env->nodes - current_node - 1) * sizeof(env->values[0]));
	env->ticks[current_node + 1] = newtick;
	env->values[current_node + 1] = newvalue;
	if (env->loop_end > current_node) env->loop_end++;
	if (env->loop_start > current_node) env->loop_start++;
	if (env->sustain_end > current_node) env->sustain_end++;
	if (env->sustain_start > current_node) env->sustain_start++;

	return current_node;
}

/* return: the new current node */
static int _env_node_remove(song_envelope_t *env, int current_node)
{
	status.flags |= SONG_NEEDS_SAVE;

	if (current_node == 0 || env->nodes < 3)
		return current_node;

	memmove(env->ticks + current_node, env->ticks + current_node + 1,
		(env->nodes - current_node - 1) * sizeof(env->ticks[0]));
	memmove(env->values + current_node, env->values + current_node + 1,
		(env->nodes - current_node - 1) * sizeof(env->values[0]));
	env->nodes--;

	if (env->loop_start >= env->nodes)
		env->loop_start = env->nodes - 1;
	else if (env->loop_start > current_node)
		env->loop_start--;
	if (env->loop_end >= env->nodes)
		env->loop_end = env->nodes - 1;
	else if (env->loop_end > current_node)
		env->loop_end--;
	if (env->sustain_start >= env->nodes)
		env->sustain_start = env->nodes - 1;
	else if (env->sustain_start > current_node)
		env->sustain_start--;
	if (env->sustain_end >= env->nodes)
		env->sustain_end = env->nodes - 1;
	else if (env->sustain_end > current_node)
		env->sustain_end--;
	if (current_node >= env->nodes)
		current_node = env->nodes - 1;

	return current_node;
}

static void do_pre_loop_cut(void *ign)
{
	song_envelope_t *env = (song_envelope_t *)ign;
	unsigned int bt;
	int i;
	bt = env->ticks[env->loop_start];
	for (i = env->loop_start; i < 32; i++) {
		env->ticks[i - env->loop_start] = env->ticks[i] - bt;
		env->values[i - env->loop_start] = env->values[i];
	}
	env->nodes -= env->loop_start;
	if (env->sustain_start > env->loop_start) {
		env->sustain_start -= env->loop_start;
	} else {
		env->sustain_start = 0;
	}
	if (env->sustain_end > env->loop_start) {
		env->sustain_end -= env->loop_start;
	} else {
		env->sustain_end = 0;
	}
	if (env->loop_end > env->loop_start) {
		env->loop_end -= env->loop_start;
	} else {
		env->loop_end = 0;
	}
	env->loop_start = 0;
	if (env->loop_start > env->loop_end)
		env->loop_end = env->loop_start;
	if (env->sustain_start > env->sustain_end)
		env->sustain_end = env->sustain_start;
	status.flags |= NEED_UPDATE;
}

static void do_post_loop_cut(void *ign)
{
	song_envelope_t *env = (song_envelope_t *)ign;
	env->nodes = env->loop_end+1;
}


static void env_resize(song_envelope_t *env, int ticks)
{
	int old = env->ticks[env->nodes - 1];
	int n, t;

	if (ticks > 9999)
		ticks = 9999;
	for (n = 1; n < env->nodes; n++) {
		t = env->ticks[n] * ticks / old;
		env->ticks[n] = MAX(t, env->ticks[n - 1] + 1);
	}
	status.flags |= NEED_UPDATE;
}


static struct widget env_resize_widgets[2];
static int env_resize_cursor;

static void do_env_resize(void *data)
{
	env_resize((song_envelope_t *) data, env_resize_widgets[0].d.numentry.value);
}

static void env_resize_draw_const(void)
{
	draw_text("Resize Envelope", 34, 24, 3, 2);
	draw_text("New Length", 31, 27, 0, 2);
	draw_box(41, 26, 49, 28, BOX_THICK | BOX_INNER | BOX_INSET);
}

static void env_resize_dialog(song_envelope_t *env)
{
	struct dialog *dialog;

	env_resize_cursor = 0;
	create_numentry(env_resize_widgets + 0, 42, 27, 7, 0, 1, 1, NULL, 0, 9999, &env_resize_cursor);
	env_resize_widgets[0].d.numentry.value = env->ticks[env->nodes - 1];
	create_button(env_resize_widgets + 1, 36, 30, 6, 0, 1, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1);
	dialog = dialog_create_custom(26, 22, 29, 11, env_resize_widgets, 2, 0, env_resize_draw_const, env);
	dialog->action_yes = do_env_resize;
}



static struct widget env_adsr_widgets[4];
static int env_adsr_cursor = 0;

static void do_env_adsr(void *data)
{
	// FIXME | move env flags into the envelope itself, where they should be in the first place.
	// FIXME | then this nonsense can go away.
	song_instrument_t *ins = (song_instrument_t *) data;
	song_envelope_t *env = &ins->vol_env;
	int a = env_adsr_widgets[0].d.thumbbar.value;
	int d = env_adsr_widgets[1].d.thumbbar.value;
	int s = env_adsr_widgets[2].d.thumbbar.value;
	int r = env_adsr_widgets[3].d.thumbbar.value;
	int v1 = MAX(a, a * a / 16);
	int v2 = MAX(v1 + d * d / 16, v1 + d);
	int v3 = MAX(v2 + r * r / 4, v2 + r);
	int n = 0;

	if (a) {
		env->ticks[n] = 0;
		env->values[n++] = 0;
	}
	if (d) {
		env->ticks[n] = v1;
		env->values[n++] = 64;
	}
	env->sustain_start = env->sustain_end = n;
	env->ticks[n] = v2;
	env->values[n++] = s / 2;
	env->ticks[n] = v3;
	env->values[n++] = 0;
	env->nodes = n;
	for (n = 0; n < env->nodes - 1; n++)
		if (env->ticks[n] >= env->ticks[n + 1])
			env->ticks[n + 1] = env->ticks[n] + 1;
	ins->flags |= ENV_VOLSUSTAIN | ENV_VOLUME; // arghhhhh
}

static void env_adsr_draw_const(void)
{
	draw_text("Envelope Generator", 32, 22, 0, 2);
	draw_text("Attack", 27, 24, 0, 2);
	draw_text("Decay", 28, 25, 0, 2);
	draw_text("Sustain", 26, 26, 0, 2);
	draw_text("Release", 26, 27, 0, 2);

	draw_box(33, 23, 51, 28, BOX_THICK | BOX_INNER | BOX_INSET);
}

static void env_adsr_dialog(UNUSED song_envelope_t *env)
{
	struct dialog *dialog;
	song_instrument_t *ins = song_get_instrument(current_instrument); // ARGHHH

	env_adsr_cursor = 0;
	create_thumbbar(env_adsr_widgets + 0, 34, 24, 17, 4, 1, 4, NULL, 0, 128);
	create_thumbbar(env_adsr_widgets + 1, 34, 25, 17, 0, 2, 4, NULL, 0, 128);
	create_thumbbar(env_adsr_widgets + 2, 34, 26, 17, 1, 3, 4, NULL, 0, 128);
	create_thumbbar(env_adsr_widgets + 3, 34, 27, 17, 2, 4, 4, NULL, 0, 128);
	create_button(env_adsr_widgets + 4, 36, 30, 6, 3, 0, 4, 4, 0, dialog_cancel_NULL, "Cancel", 1);

	dialog = dialog_create_custom(25, 21, 31, 12, env_adsr_widgets, 5, 0, env_adsr_draw_const, ins);
	dialog->action_yes = do_env_adsr;
}




/* the return value here is actually a bitmask:
r & 1 => the key was handled
r & 2 => the envelope changed (i.e., it should be enabled) */
static int _env_handle_key_viewmode(struct key_event *k, song_envelope_t *env, int *current_node, unsigned int sec)
{
	int new_node = *current_node;
	int n;

	switch (k->sym.sym) {
	case SDLK_UP:
		if (k->state == KEY_RELEASE)
			return 0;
		change_focus_to(1);
		return 1;
	case SDLK_DOWN:
		if (k->state == KEY_RELEASE)
			return 0;
		change_focus_to(6);
		return 1;
	case SDLK_LEFT:
		if (k->state == KEY_RELEASE)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		new_node--;
		break;
	case SDLK_RIGHT:
		if (k->state == KEY_RELEASE)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		new_node++;
		break;
	case SDLK_INSERT:
		if (k->state == KEY_RELEASE)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		*current_node = _env_node_add(env, *current_node, -1, -1);
		status.flags |= NEED_UPDATE;
		return 1 | 2;
	case SDLK_DELETE:
		if (k->state == KEY_RELEASE)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		*current_node = _env_node_remove(env, *current_node);
		status.flags |= NEED_UPDATE;
		return 1 | 2;
	case SDLK_SPACE:
		if (k->state == KEY_RELEASE)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		song_keyup(KEYJAZZ_NOINST, current_instrument, last_note);
		song_keydown(KEYJAZZ_NOINST, current_instrument, last_note, 64, KEYJAZZ_CHAN_CURRENT);
		return 1;
	case SDLK_RETURN:
		if (k->state == KEY_PRESS)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		envelope_edit_mode = 1;
		status.flags |= NEED_UPDATE;
		return 1 | 2;
	case SDLK_l:
		if (k->state == KEY_PRESS)
			return 0;
		if (!(k->mod & KMOD_ALT)) return 0;
		if (env->loop_end < (env->nodes-1))  {
			dialog_create(DIALOG_OK_CANCEL, "Cut envelope?", do_post_loop_cut, NULL, 1, env);
			return 1;
		}
		return 0;
	case SDLK_b:
		if (k->state == KEY_PRESS)
			return 0;
		if (!(k->mod & KMOD_ALT)) return 0;
		if (env->loop_start > 0) {
			dialog_create(DIALOG_OK_CANCEL, "Cut envelope?", do_pre_loop_cut, NULL, 1, env);
			return 1;
		}
		return 0;

	// F/G for key symmetry with pattern double/halve block
	// E for symmetry with sample resize
	case SDLK_f:
		if (k->state == KEY_PRESS)
			return 0;
		if (!(k->mod & KMOD_ALT)) return 0;
		env_resize(env, env->ticks[env->nodes - 1] * 2);
		return 1;
	case SDLK_g:
		if (k->state == KEY_PRESS)
			return 0;
		if (!(k->mod & KMOD_ALT)) return 0;
		env_resize(env, env->ticks[env->nodes - 1] / 2);
		return 1;
	case SDLK_e:
		if (k->state == KEY_PRESS)
			return 0;
		if (!(k->mod & KMOD_ALT)) return 0;
		env_resize_dialog(env);
		return 1;

	case SDLK_z:
		if (k->state == KEY_PRESS)
			return 0;
		if (!(k->mod & KMOD_ALT)) return 0;
		env_adsr_dialog(env);
		return 1;

	default:
		if (k->state == KEY_PRESS)
			return 0;

		n = numeric_key_event(k, 0);
		if (n > -1) {
			if (k->mod & (KMOD_ALT | KMOD_CTRL)) {
				save_envelope(n, env, sec);
				status_text_flash("Envelope copied into slot %d", n);
			} else if (k->mod & KMOD_SHIFT) {
				restore_envelope(n, env, sec);
				if (!(status.flags & CLASSIC_MODE))
					status_text_flash("Pasted envelope from slot %d", n);
			}
			return 1;
		}
		return 0;
	}

	new_node = CLAMP(new_node, 0, env->nodes - 1);
	if (*current_node != new_node) {
		*current_node = new_node;
		status.flags |= NEED_UPDATE;
	}

	return 1;
}

/* mouse handling routines for envelope */
static int _env_handle_mouse(struct key_event *k, song_envelope_t *env, int *current_node)
{
	int x, y, i;
	int max_ticks = 50;

	if (k->mouse != MOUSE_CLICK) return 0;

	if (k->state == KEY_RELEASE) {
		/* mouse release */
		if (envelope_mouse_edit) {
			if (current_node && *current_node) {
				for (i = 0; i < env->nodes-1; i++) {
					if (*current_node == i) continue;
					if (env->ticks[ *current_node ] == env->ticks[i]
					&& env->values[ *current_node ] == env->values[i]) {
						status_text_flash("Removed node %d", (int)(*current_node));
						status.flags |= SONG_NEEDS_SAVE;

						*current_node = _env_node_remove(env, *current_node);
						break;
					}
				}

			}
			status.flags |= NEED_UPDATE;
		}
		memused_songchanged();
		envelope_mouse_edit = 0;
		return 1;
	}

	while (env->ticks[env->nodes - 1] >= max_ticks)
		max_ticks *= 2;

	if (envelope_mouse_edit) {
		if (k->fx < 259)
			x = 0;
		else
			x = (k->fx - 259) * max_ticks / 256;
		y = 64 - (k->fy - 144);
		if (y > 63) y++;
		if (y > 42) y++;
		if (y > 21) y++;
		if (y > 64) y = 64;
		if (y < 0) y = 0;

		if (*current_node && env->ticks[ (*current_node)-1 ] >= x) {
			x = env->ticks[ (*current_node)-1 ]+1;
		}
		if (*current_node < (env->nodes-1)) {
			if (env->ticks[ (*current_node)+1 ] <= x) {
				x = env->ticks[ (*current_node)+1 ]-1;
			}
		}
		if (env->ticks[*current_node] == x && env->ticks[*current_node] == y) {
			return 1;
		}
		if (x < 0) x = 0;
		if (x > envelope_tick_limit) x = envelope_tick_limit;
		if (x > 9999) x = 9999;
		if (*current_node) env->ticks[ *current_node ] = x;
		env->values[ *current_node ] = y;
		status.flags |= SONG_NEEDS_SAVE;
		status.flags |= NEED_UPDATE;
	} else {
		int n;
		int dist, dx, dy;
		int best_dist = 0;
		int best_dist_node;

		best_dist_node = -1;

		if (k->x < 32 || k->y < 18 || k->x > 32+45 || k->y > 18+8)
			return 0;

		for (n = 0; n < env->nodes; n++) {
			x = 259 + env->ticks[n] * 256 / max_ticks;
			y = env->values[n];
			if (y > 63) y--;
			if (y > 42) y--;
			if (y > 21) y--;
			y = 206 - y;

			dx = abs(x - (int) k->fx);
			dy = abs(y - (int) k->fy);
			dist = i_sqrt((dx*dx)+(dy*dy));
			if (best_dist_node == -1 || dist < best_dist) {
				if (dist <= 5) {
					best_dist = dist;
					best_dist_node = n;
				}
			}
		}
		if (best_dist_node == -1) {
			x = (k->fx - 259) * max_ticks / 256;
			y = 64 - (k->fy - 144);
			if (y > 63) y++;
			if (y > 42) y++;
			if (y > 21) y++;
			if (y > 64) y = 64;
			if (y < 0) y = 0;
			if (x > 0 && x < max_ticks) {
				*current_node = 0;
				for (i = 1; i < env->nodes; i++) {
					/* something too close */
					if (env->ticks[i] <= x) *current_node = i;
					if (abs(env->ticks[i] - x) < 2) return 0;
				}
				best_dist_node = (_env_node_add(env, *current_node, x, y))+1;
				status_text_flash("Created node %d", best_dist_node);
			}
			if (best_dist_node == -1) return 0;
		}

		envelope_tick_limit = env->ticks[env->nodes - 1] * 2;
		envelope_mouse_edit = 1;
		*current_node = best_dist_node;
		status.flags |= SONG_NEEDS_SAVE;
		status.flags |= NEED_UPDATE;
		return 1;
	}
	return 0;
}



/* - this function is only ever called when the envelope is in edit mode
   - envelope_edit_mode is only ever assigned a true value once, in _env_handle_key_viewmode.
   - when _env_handle_key_viewmode enables envelope_edit_mode, it indicates in its return value
     that the envelope should be enabled.
   - therefore, the envelope will always be enabled when this function is called, so there is
     no reason to indicate a change in the envelope here. */
static int _env_handle_key_editmode(struct key_event *k, song_envelope_t *env, int *current_node)
{
	int new_node = *current_node, new_tick = env->ticks[*current_node],
		new_value = env->values[*current_node];

	/* TODO: when does adding/removing a node alter loop points? */

	switch (k->sym.sym) {
	case SDLK_UP:
		if (k->state == KEY_RELEASE)
			return 0;
		if (k->mod & KMOD_ALT)
			new_value += 16;
		else
			new_value++;
		break;
	case SDLK_DOWN:
		if (k->state == KEY_RELEASE)
			return 0;
		if (k->mod & KMOD_ALT)
			new_value -= 16;
		else
			new_value--;
		break;
	case SDLK_PAGEUP:
		if (k->state == KEY_RELEASE)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		new_value += 16;
		break;
	case SDLK_PAGEDOWN:
		if (k->state == KEY_RELEASE)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		new_value -= 16;
		break;
	case SDLK_LEFT:
		if (k->state == KEY_RELEASE)
			return 1;
		if (k->mod & KMOD_CTRL)
			new_node--;
		else if (k->mod & KMOD_ALT)
			new_tick -= 16;
		else
			new_tick--;
		break;
	case SDLK_RIGHT:
		if (k->state == KEY_RELEASE)
			return 1;
		if (k->mod & KMOD_CTRL)
			new_node++;
		else if (k->mod & KMOD_ALT)
			new_tick += 16;
		else
			new_tick++;
		break;
	case SDLK_TAB:
		if (k->state == KEY_RELEASE)
			return 0;
		if (k->mod & KMOD_SHIFT)
			new_tick -= 16;
		else
			new_tick += 16;
		break;
	case SDLK_HOME:
		if (k->state == KEY_RELEASE)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		new_tick = 0;
		break;
	case SDLK_END:
		if (k->state == KEY_RELEASE)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		new_tick = 10000;
		break;
	case SDLK_INSERT:
		if (k->state == KEY_RELEASE)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		*current_node = _env_node_add(env, *current_node, -1, -1);
		status.flags |= NEED_UPDATE;
		return 1;
	case SDLK_DELETE:
		if (k->state == KEY_RELEASE)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		*current_node = _env_node_remove(env, *current_node);
		status.flags |= NEED_UPDATE;
		return 1;
	case SDLK_SPACE:
		if (k->state == KEY_RELEASE)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		song_keyup(KEYJAZZ_NOINST, current_instrument, last_note);
		song_keydown(KEYJAZZ_NOINST, current_instrument, last_note, 64, KEYJAZZ_CHAN_CURRENT);
		return 1;
	case SDLK_RETURN:
		if (k->state == KEY_PRESS)
			return 0;
		if (!NO_MODIFIER(k->mod))
			return 0;
		envelope_edit_mode = 0;
		memused_songchanged();
		status.flags |= NEED_UPDATE;
		break;
	default:
		return 0;
	}

	new_node = CLAMP(new_node, 0, env->nodes - 1);
	if (new_node != *current_node) {
		status.flags |= NEED_UPDATE;
		*current_node = new_node;
		return 1;
	}

	new_tick = (new_node == 0) ? 0 : CLAMP(new_tick,
					env->ticks[new_node - 1] + 1,
				       ((new_node == env->nodes - 1)
					? 10000 : env->ticks[new_node + 1]) - 1);
	if (new_tick != env->ticks[new_node]) {
		env->ticks[*current_node] = new_tick;
		status.flags |= SONG_NEEDS_SAVE;
		status.flags |= NEED_UPDATE;
		return 1;
	}
	new_value = CLAMP(new_value, 0, 64);

	if (new_value != (int)env->values[new_node]) {
		env->values[*current_node] = (unsigned int)new_value;
		status.flags |= SONG_NEEDS_SAVE;
		status.flags |= NEED_UPDATE;
		return 1;
	}

	return 1;
}

/* --------------------------------------------------------------------------------------------------------- */
/* envelope stuff (draw()'s and handle_key()'s) */

static void _draw_env_label(const char *env_name, int is_selected)
{
	int pos = 33;

	pos += draw_text(env_name, pos, 16, is_selected ? 3 : 0, 2);
	pos += draw_text(" Envelope", pos, 16, is_selected ? 3 : 0, 2);
	if (envelope_edit_mode || envelope_mouse_edit)
		draw_text(" (Edit)", pos, 16, is_selected ? 3 : 0, 2);
}

static void volume_envelope_draw(void)
{
	int is_selected = (ACTIVE_PAGE.selected_widget == 5);
	song_instrument_t *ins = song_get_instrument(current_instrument);

	_draw_env_label("Volume", is_selected);
	_env_draw(&ins->vol_env, 0, current_node_vol,
		ins->flags & ENV_VOLUME,
		  ins->flags & ENV_VOLLOOP, ins->flags & ENV_VOLSUSTAIN, 0);
}

static void panning_envelope_draw(void)
{
	int is_selected = (ACTIVE_PAGE.selected_widget == 5);
	song_instrument_t *ins = song_get_instrument(current_instrument);

	_draw_env_label("Panning", is_selected);
	_env_draw(&ins->pan_env, 1, current_node_pan,
		ins->flags & ENV_PANNING,
		  ins->flags & ENV_PANLOOP, ins->flags & ENV_PANSUSTAIN, 1);
}

static void pitch_envelope_draw(void)
{
	int is_selected = (ACTIVE_PAGE.selected_widget == 5);
	song_instrument_t *ins = song_get_instrument(current_instrument);

	_draw_env_label("Frequency", is_selected);
	_env_draw(&ins->pitch_env, (ins->flags & ENV_FILTER) ? 0 : 1, current_node_pitch,
		ins->flags & (ENV_PITCH|ENV_FILTER),
		  ins->flags & ENV_PITCHLOOP, ins->flags & ENV_PITCHSUSTAIN, 2);
}

static int volume_envelope_handle_key(struct key_event * k)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);
	int r;

	if (_env_handle_mouse(k, &ins->vol_env, &current_node_vol)) {
		ins->flags |= ENV_VOLUME;
		return 1;
	}
	if (envelope_edit_mode)
		r = _env_handle_key_editmode(k, &ins->vol_env, &current_node_vol);
	else
		r = _env_handle_key_viewmode(k, &ins->vol_env, &current_node_vol, ENV_VOLUME);
	if (r & 2) {
		r ^= 2;
		ins->flags |= ENV_VOLUME;
	}
	return r;
}

static int panning_envelope_handle_key(struct key_event * k)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);
	int r;

	if (_env_handle_mouse(k, &ins->pan_env, &current_node_pan)) {
		ins->flags |= ENV_PANNING;
		return 1;
	}

	if (envelope_edit_mode)
		r = _env_handle_key_editmode(k, &ins->pan_env, &current_node_pan);
	else
		r = _env_handle_key_viewmode(k, &ins->pan_env, &current_node_pan, ENV_PANNING);
	if (r & 2) {
		r ^= 2;
		ins->flags |= ENV_PANNING;
	}
	return r;
}

static int pitch_envelope_handle_key(struct key_event * k)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);
	int r;

	if (_env_handle_mouse(k, &ins->pitch_env, &current_node_pitch)) {
		ins->flags |= ENV_PITCH;
		return 1;
	}
	if (envelope_edit_mode)
		r = _env_handle_key_editmode(k, &ins->pitch_env, &current_node_pitch);
	else
		r = _env_handle_key_viewmode(k, &ins->pitch_env, &current_node_pitch, ENV_PITCH);
	if (r & 2) {
		r ^= 2;
		ins->flags |= ENV_PITCH;
	}
	return r;
}

/* --------------------------------------------------------------------------------------------------------- */
/* pitch-pan center */

static int pitch_pan_center_handle_key(struct key_event *k)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);
	int ppc = ins->pitch_pan_center;

	if (k->state == KEY_RELEASE)
		return 0;
	switch (k->sym.sym) {
	case SDLK_LEFT:
		if (!NO_MODIFIER(k->mod))
			return 0;
		ppc--;
		break;
	case SDLK_RIGHT:
		if (!NO_MODIFIER(k->mod))
			return 0;
		ppc++;
		break;
	default:
		if ((k->mod & (KMOD_CTRL | KMOD_ALT)) == 0) {
			ppc = kbd_get_note(k);
			if (ppc < 1 || ppc > 120)
				return 0;
			ppc--;
			break;
		}
		return 0;
	}
	if ((unsigned int)ppc != ins->pitch_pan_center
	&& ppc >= 0 && ppc < 120) {
		ins->pitch_pan_center = (unsigned int)ppc;
		status.flags |= NEED_UPDATE;
	}
	return 1;
}

static void pitch_pan_center_draw(void)
{
	char buf[4];
	int selected = (ACTIVE_PAGE.selected_widget == 16);
	song_instrument_t *ins = song_get_instrument(current_instrument);

	draw_text(get_note_string(ins->pitch_pan_center + 1, buf), 54, 45, selected ? 3 : 2, 0);
}

/* --------------------------------------------------------------------------------------------------------- */
/* default key handler (for instrument changing on pgup/pgdn) */

static void do_ins_save(void *p)
{
	char *ptr = (char *)p;
	if (song_save_instrument(current_instrument, ptr))
		status_text_flash("Instrument saved (instrument %d)", current_instrument);
	else
		status_text_flash("Error: Instrument %d NOT saved! (No Filename?)", current_instrument);
	free(ptr);
}

static void instrument_save(void)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);
	char *ptr = (char *) dmoz_path_concat(cfg_dir_instruments, ins->filename);
	struct stat buf;

	if (stat(ptr, &buf) == 0) {
		if (S_ISDIR(buf.st_mode)) {
			status_text_flash("%s is a directory", ins->filename);
			return;
		} else if (S_ISREG(buf.st_mode)) {
			dialog_create(DIALOG_OK_CANCEL,
				"Overwrite file?", do_ins_save,
				free, 1, ptr);
			return;
		} else {
			status_text_flash("%s is not a regular file", ins->filename);
			return;
		}
	}
	if (song_save_instrument(current_instrument, ptr))
		status_text_flash("Instrument saved (instrument %d)", current_instrument);
	else
		status_text_flash("Error: Instrument %d NOT saved! (No Filename?)", current_instrument);
	free(ptr);
}

static void do_delete_inst(UNUSED void *ign)
{
	song_delete_instrument(current_instrument, 0);
}

static void do_delete_inst_preserve(UNUSED void *ign)
{
	song_delete_instrument(current_instrument, 1);
}

static void instrument_list_handle_alt_key(struct key_event *k)
{
	/* song_instrument_t *ins = song_get_instrument(current_instrument); */

	if (k->state == KEY_RELEASE)
		return;
	switch (k->sym.sym) {
	case SDLK_n:
		song_toggle_multichannel_mode();
		return;
	case SDLK_o:
		instrument_save();
		return;
	case SDLK_r:
		smpprompt_create("Replace instrument with:", "Instrument", do_replace_instrument);
		return;
	case SDLK_s:
		// extra space to align the text like IT
		smpprompt_create("Swap instrument with: ", "Instrument", do_swap_instrument);
		return;
	case SDLK_x:
		smpprompt_create("Exchange instrument with:", "Instrument", do_exchange_instrument);
		return;
	case SDLK_p:
		smpprompt_create("Copy instrument:", "Instrument", do_copy_instrument);
		return;
	case SDLK_w:
		song_wipe_instrument(current_instrument);
		break;
	case SDLK_d:
        if (k->mod & KMOD_SHIFT) {
            dialog_create(DIALOG_OK_CANCEL,
                "Delete Instrument? (preserve shared samples)",
                do_delete_inst_preserve, NULL, 1, NULL);
        } else {
            dialog_create(DIALOG_OK_CANCEL,
                "Delete Instrument?",
                do_delete_inst, NULL, 1, NULL);
        }
		return;
	default:
		return;
	}

	status.flags |= NEED_UPDATE;
}

static int instrument_list_pre_handle_key(struct key_event * k)
{
	// Only handle plain F4 key when no dialog is active.
	if (status.dialog_type != DIALOG_NONE || k->sym.sym != SDLK_F4 || (k->mod & (KMOD_CTRL | KMOD_ALT)))
		return 0;
	if (k->state == KEY_RELEASE)
		return 1;

	if (song_is_instrument_mode()) {
		int csamp = sample_get_current();
		sample_synchronize_to_instrument();
		if (csamp != sample_get_current())
			return 0;
	}

	if (k->mod & KMOD_SHIFT) {
		switch (status.current_page) {
			default:
			case PAGE_INSTRUMENT_LIST_VOLUME:  set_subpage(PAGE_INSTRUMENT_LIST_GENERAL); break;
			case PAGE_INSTRUMENT_LIST_PANNING: set_subpage(PAGE_INSTRUMENT_LIST_VOLUME);  break;
			case PAGE_INSTRUMENT_LIST_PITCH:   set_subpage(PAGE_INSTRUMENT_LIST_PANNING); break;
			case PAGE_INSTRUMENT_LIST_GENERAL: set_subpage(PAGE_INSTRUMENT_LIST_PITCH);   break;
		}
	} else {
		switch (status.current_page) {
			default:
			case PAGE_INSTRUMENT_LIST_PITCH:   set_subpage(PAGE_INSTRUMENT_LIST_GENERAL); break;
			case PAGE_INSTRUMENT_LIST_GENERAL: set_subpage(PAGE_INSTRUMENT_LIST_VOLUME);  break;
			case PAGE_INSTRUMENT_LIST_VOLUME:  set_subpage(PAGE_INSTRUMENT_LIST_PANNING); break;
			case PAGE_INSTRUMENT_LIST_PANNING: set_subpage(PAGE_INSTRUMENT_LIST_PITCH);   break;
		}
	}
	return 1;
}
static void instrument_list_handle_key(struct key_event * k)
{
	if (k->is_textinput) return;
	switch (k->sym.sym) {
	case SDLK_COMMA:
		if (NO_MODIFIER(k->mod)) {
			if (!(status.flags & CLASSIC_MODE)
			&& ACTIVE_PAGE.selected_widget == 5) return;
		}
	case SDLK_LESS:
		if (k->state == KEY_RELEASE)
			return;
		song_change_current_play_channel(-1, 0);
		return;
	case SDLK_PERIOD:
		if (NO_MODIFIER(k->mod)) {
			if (!(status.flags & CLASSIC_MODE)
			&& ACTIVE_PAGE.selected_widget == 5) return;
		}
	case SDLK_GREATER:
		if (k->state == KEY_RELEASE)
			return;
		song_change_current_play_channel(1, 0);
		return;

	case SDLK_PAGEUP:
		if (k->state == KEY_RELEASE)
			return;
		instrument_set(current_instrument - 1);
		break;
	case SDLK_PAGEDOWN:
		if (k->state == KEY_RELEASE)
			return;
		instrument_set(current_instrument + 1);
		break;
	case SDLK_ESCAPE:
		if ((k->mod & KMOD_SHIFT) || instrument_cursor_pos < 25) {
			if (k->state == KEY_RELEASE)
				return;
			instrument_cursor_pos = 25;
			get_page_widgets()->accept_text = 0;
			change_focus_to(0);
			status.flags |= NEED_UPDATE;
			return;
		}
		return;
	default:
		if (k->mod & (KMOD_ALT)) {
			instrument_list_handle_alt_key(k);
		} else {
			int n, v;

			if (k->midi_note > -1) {
				n = k->midi_note;
				if (k->midi_volume > -1) {
					v = k->midi_volume / 2;
				} else {
					v = 64;
				}
			} else {
				v = 64;
				n = kbd_get_note(k);
				if (n <= 0 || n > 120)
					return;
			}

			if (k->state == KEY_RELEASE) {
				song_keyup(0, current_instrument, n);
				status.last_keysym.sym = 0;
			} else if (!k->is_repeat) {
				song_keydown(KEYJAZZ_NOINST, current_instrument, n, v, KEYJAZZ_CHAN_CURRENT);
			}
			last_note = n;
		}
		return;
	}
}

/* --------------------------------------------------------------------- */

static void set_subpage(int page)
{
	int widget = ACTIVE_PAGE.selected_widget;
	int b = 1;
	switch (page) {
	case PAGE_INSTRUMENT_LIST_GENERAL: b = 1; break;
	case PAGE_INSTRUMENT_LIST_VOLUME:  b = 2; break;
	case PAGE_INSTRUMENT_LIST_PANNING: b = 3; break;
	case PAGE_INSTRUMENT_LIST_PITCH:   b = 4; break;
	default: return;
	};
	togglebutton_set(pages[page].widgets, b, 0);
	set_page(page);
	if (widget >= ACTIVE_PAGE.total_widgets)
		widget = ACTIVE_PAGE.total_widgets - 1;
	ACTIVE_PAGE.selected_widget = widget;
	instrument_list_subpage = page;
	status.flags |= NEED_UPDATE;
}

static void change_subpage(void)
{
	int widget = ACTIVE_PAGE.selected_widget;
	int p[] = {
		PAGE_INSTRUMENT_LIST_GENERAL,
		PAGE_INSTRUMENT_LIST_VOLUME,
		PAGE_INSTRUMENT_LIST_PANNING,
		PAGE_INSTRUMENT_LIST_PITCH,
	};
	set_subpage(p[CLAMP(widget - 1, 0, 3)]);
}

/* --------------------------------------------------------------------- */
/* predraw hooks... */

static void instrument_list_general_predraw_hook(void)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);

	togglebutton_set(widgets_general, 6 + (ins->nna % 4), 0);
	togglebutton_set(widgets_general, 10 + (ins->dct % 4), 0);
	togglebutton_set(widgets_general, 14 + (ins->dca % 3), 0);

	widgets_general[17].d.textentry.text = ins->filename;
}

static void instrument_list_volume_predraw_hook(void)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);

	widgets_volume[6].d.toggle.state = !!(ins->flags & ENV_VOLUME);
	widgets_volume[7].d.toggle.state = !!(ins->flags & ENV_VOLCARRY);
	widgets_volume[8].d.toggle.state = !!(ins->flags & ENV_VOLLOOP);
	widgets_volume[11].d.toggle.state = !!(ins->flags & ENV_VOLSUSTAIN);

	/* FIXME: this is the wrong place for this.
	... and it's probably not even right -- how does Impulse Tracker handle loop constraints?
	See below for panning/pitch envelopes; same deal there. */
	if (ins->vol_env.loop_start > ins->vol_env.loop_end)
		ins->vol_env.loop_end = ins->vol_env.loop_start;
	if (ins->vol_env.sustain_start > ins->vol_env.sustain_end)
		ins->vol_env.sustain_end = ins->vol_env.sustain_start;

	widgets_volume[9].d.numentry.max = ins->vol_env.nodes - 1;
	widgets_volume[10].d.numentry.max = ins->vol_env.nodes - 1;
	widgets_volume[12].d.numentry.max = ins->vol_env.nodes - 1;
	widgets_volume[13].d.numentry.max = ins->vol_env.nodes - 1;

	widgets_volume[9].d.numentry.value = ins->vol_env.loop_start;
	widgets_volume[10].d.numentry.value = ins->vol_env.loop_end;
	widgets_volume[12].d.numentry.value = ins->vol_env.sustain_start;
	widgets_volume[13].d.numentry.value = ins->vol_env.sustain_end;

	/* current_song hack: shifting values all over the place here, ugh */
	widgets_volume[14].d.thumbbar.value = ins->global_volume;
	widgets_volume[15].d.thumbbar.value = ins->fadeout >> 5;
	widgets_volume[16].d.thumbbar.value = ins->vol_swing;
}

static void instrument_list_panning_predraw_hook(void)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);

	widgets_panning[6].d.toggle.state = !!(ins->flags & ENV_PANNING);
	widgets_panning[7].d.toggle.state = !!(ins->flags & ENV_PANCARRY);
	widgets_panning[8].d.toggle.state = !!(ins->flags & ENV_PANLOOP);
	widgets_panning[11].d.toggle.state = !!(ins->flags & ENV_PANSUSTAIN);

	if (ins->pan_env.loop_start > ins->pan_env.loop_end)
		ins->pan_env.loop_end = ins->pan_env.loop_start;
	if (ins->pan_env.sustain_start > ins->pan_env.sustain_end)
		ins->pan_env.sustain_end = ins->pan_env.sustain_start;

	widgets_panning[9].d.numentry.max = ins->pan_env.nodes - 1;
	widgets_panning[10].d.numentry.max = ins->pan_env.nodes - 1;
	widgets_panning[12].d.numentry.max = ins->pan_env.nodes - 1;
	widgets_panning[13].d.numentry.max = ins->pan_env.nodes - 1;

	widgets_panning[9].d.numentry.value = ins->pan_env.loop_start;
	widgets_panning[10].d.numentry.value = ins->pan_env.loop_end;
	widgets_panning[12].d.numentry.value = ins->pan_env.sustain_start;
	widgets_panning[13].d.numentry.value = ins->pan_env.sustain_end;

	widgets_panning[14].d.toggle.state = !!(ins->flags & ENV_SETPANNING);
	widgets_panning[15].d.thumbbar.value = ins->panning >> 2;
	/* (widgets_panning[16] is the pitch-pan center) */
	widgets_panning[17].d.thumbbar.value = ins->pitch_pan_separation;
	widgets_panning[18].d.thumbbar.value = ins->pan_swing;
}

static void instrument_list_pitch_predraw_hook(void)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);

	widgets_pitch[6].d.menutoggle.state = ((ins->flags & ENV_PITCH)
					     ? ((ins->flags & ENV_FILTER)
						? 2 : 1) : 0);
	widgets_pitch[7].d.toggle.state = !!(ins->flags & ENV_PITCHCARRY);
	widgets_pitch[8].d.toggle.state = !!(ins->flags & ENV_PITCHLOOP);
	widgets_pitch[11].d.toggle.state = !!(ins->flags & ENV_PITCHSUSTAIN);

	if (ins->pitch_env.loop_start > ins->pitch_env.loop_end)
		ins->pitch_env.loop_end = ins->pitch_env.loop_start;
	if (ins->pitch_env.sustain_start > ins->pitch_env.sustain_end)
		ins->pitch_env.sustain_end = ins->pitch_env.sustain_start;

	widgets_pitch[9].d.numentry.max = ins->pitch_env.nodes - 1;
	widgets_pitch[10].d.numentry.max = ins->pitch_env.nodes - 1;
	widgets_pitch[12].d.numentry.max = ins->pitch_env.nodes - 1;
	widgets_pitch[13].d.numentry.max = ins->pitch_env.nodes - 1;

	widgets_pitch[9].d.numentry.value = ins->pitch_env.loop_start;
	widgets_pitch[10].d.numentry.value = ins->pitch_env.loop_end;
	widgets_pitch[12].d.numentry.value = ins->pitch_env.sustain_start;
	widgets_pitch[13].d.numentry.value = ins->pitch_env.sustain_end;

	if (ins->ifc & 0x80)
		widgets_pitch[14].d.thumbbar.value = ins->ifc & 0x7f;
	else
		widgets_pitch[14].d.thumbbar.value = -1;
	if (ins->ifr & 0x80)
		widgets_pitch[15].d.thumbbar.value = ins->ifr & 0x7f;
	else
		widgets_pitch[15].d.thumbbar.value = -1;

	/* printf("ins%02d: ch%04d pgm%04d bank%06d drum%04d\n", current_instrument,
		ins->midi_channel, ins->midi_program, ins->midi_bank, ins->midi_drum_key); */
	widgets_pitch[16].d.bitset.value           = ins->midi_channel_mask;
	widgets_pitch[17].d.thumbbar.value = (signed char) ins->midi_program;
	widgets_pitch[18].d.thumbbar.value = (signed char) (ins->midi_bank & 0xff);
	widgets_pitch[19].d.thumbbar.value = (signed char) (ins->midi_bank >> 8);
	/* what is midi_drum_key for? */
}

/* --------------------------------------------------------------------- */
/* update values in song */

static void instrument_list_general_update_values(void)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);

	status.flags |= SONG_NEEDS_SAVE;
	for (ins->nna = 4; ins->nna--;)
		if (widgets_general[ins->nna + 6].d.togglebutton.state)
			break;
	for (ins->dct = 4; ins->dct--;)
		if (widgets_general[ins->dct + 10].d.togglebutton.state)
			break;
	for (ins->dca = 3; ins->dca--;)
		if (widgets_general[ins->dca + 14].d.togglebutton.state)
			break;
}

static void update_filename(void)
{
	status.flags |= SONG_NEEDS_SAVE;
}

#define CHECK_SET(a,b,c) if (a != b) { a = b; c; }

static void instrument_list_volume_update_values(void)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);

	status.flags |= SONG_NEEDS_SAVE;
	ins->flags &= ~(ENV_VOLUME | ENV_VOLCARRY | ENV_VOLLOOP | ENV_VOLSUSTAIN);
	if (widgets_volume[6].d.toggle.state)
		ins->flags |= ENV_VOLUME;
	if (widgets_volume[7].d.toggle.state)
		ins->flags |= ENV_VOLCARRY;
	if (widgets_volume[8].d.toggle.state)
		ins->flags |= ENV_VOLLOOP;
	if (widgets_volume[11].d.toggle.state)
		ins->flags |= ENV_VOLSUSTAIN;

	CHECK_SET(ins->vol_env.loop_start, widgets_volume[9].d.numentry.value,
		  ins->flags |= ENV_VOLLOOP);
	CHECK_SET(ins->vol_env.loop_end, widgets_volume[10].d.numentry.value,
		  ins->flags |= ENV_VOLLOOP);
	CHECK_SET(ins->vol_env.sustain_start, widgets_volume[12].d.numentry.value,
		  ins->flags |= ENV_VOLSUSTAIN);
	CHECK_SET(ins->vol_env.sustain_end, widgets_volume[13].d.numentry.value,
		  ins->flags |= ENV_VOLSUSTAIN);

	/* more ugly shifts */
	ins->global_volume = widgets_volume[14].d.thumbbar.value;
	ins->fadeout = widgets_volume[15].d.thumbbar.value << 5;
	ins->vol_swing = widgets_volume[16].d.thumbbar.value;

	song_update_playing_instrument(current_instrument);
}

static void instrument_list_panning_update_values(void)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);
	int n;

	status.flags |= SONG_NEEDS_SAVE;
	ins->flags &= ~(ENV_PANNING | ENV_PANCARRY | ENV_PANLOOP | ENV_PANSUSTAIN | ENV_SETPANNING);
	if (widgets_panning[6].d.toggle.state)
		ins->flags |= ENV_PANNING;
	if (widgets_panning[7].d.toggle.state)
		ins->flags |= ENV_PANCARRY;
	if (widgets_panning[8].d.toggle.state)
		ins->flags |= ENV_PANLOOP;
	if (widgets_panning[11].d.toggle.state)
		ins->flags |= ENV_PANSUSTAIN;
	if (widgets_panning[14].d.toggle.state)
		ins->flags |= ENV_SETPANNING;

	CHECK_SET(ins->pan_env.loop_start, widgets_panning[9].d.numentry.value,
		  ins->flags |= ENV_PANLOOP);
	CHECK_SET(ins->pan_env.loop_end, widgets_panning[10].d.numentry.value,
		  ins->flags |= ENV_PANLOOP);
	CHECK_SET(ins->pan_env.sustain_start, widgets_panning[12].d.numentry.value,
		  ins->flags |= ENV_PANSUSTAIN);
	CHECK_SET(ins->pan_env.sustain_end, widgets_panning[13].d.numentry.value,
		  ins->flags |= ENV_PANSUSTAIN);

	n = widgets_panning[15].d.thumbbar.value << 2;
	if (ins->panning != (unsigned int)n) {
		ins->panning = (unsigned int)n;
		ins->flags |= ENV_SETPANNING;
	}
	/* (widgets_panning[16] is the pitch-pan center) */
	ins->pitch_pan_separation = widgets_panning[17].d.thumbbar.value;
	ins->pan_swing = widgets_panning[18].d.thumbbar.value;

	song_update_playing_instrument(current_instrument);
}

static void instrument_list_pitch_update_values(void)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);

	status.flags |= SONG_NEEDS_SAVE;
	ins->flags &= ~(ENV_PITCH | ENV_PITCHCARRY | ENV_PITCHLOOP | ENV_PITCHSUSTAIN | ENV_FILTER);

	switch (widgets_pitch[6].d.menutoggle.state) {
	case 2: ins->flags |= ENV_FILTER;
	case 1: ins->flags |= ENV_PITCH;
	}

	if (widgets_pitch[6].d.menutoggle.state)
		ins->flags |= ENV_PITCH;
	if (widgets_pitch[7].d.toggle.state)
		ins->flags |= ENV_PITCHCARRY;
	if (widgets_pitch[8].d.toggle.state)
		ins->flags |= ENV_PITCHLOOP;
	if (widgets_pitch[11].d.toggle.state)
		ins->flags |= ENV_PITCHSUSTAIN;

	CHECK_SET(ins->pitch_env.loop_start, widgets_pitch[9].d.numentry.value,
		  ins->flags |= ENV_PITCHLOOP);
	CHECK_SET(ins->pitch_env.loop_end, widgets_pitch[10].d.numentry.value,
		  ins->flags |= ENV_PITCHLOOP);
	CHECK_SET(ins->pitch_env.sustain_start, widgets_pitch[12].d.numentry.value,
		  ins->flags |= ENV_PITCHSUSTAIN);
	CHECK_SET(ins->pitch_env.sustain_end, widgets_pitch[13].d.numentry.value,
		  ins->flags |= ENV_PITCHSUSTAIN);
	if (widgets_pitch[14].d.thumbbar.value > -1) {
		ins->ifc = widgets_pitch[14].d.thumbbar.value | 0x80;
	} else {
		ins->ifc = 0x7f;
	}
	if (widgets_pitch[15].d.thumbbar.value > -1) {
		ins->ifr = widgets_pitch[15].d.thumbbar.value | 0x80;
	} else {
		ins->ifr = 0x7f;
	}
	ins->midi_channel_mask = widgets_pitch[16].d.bitset.value;
	ins->midi_program = widgets_pitch[17].d.thumbbar.value;
	ins->midi_bank = ((widgets_pitch[19].d.thumbbar.value << 8)
			  | (widgets_pitch[18].d.thumbbar.value & 0xff));

	song_update_playing_instrument(current_instrument);
}

/* --------------------------------------------------------------------- */
/* draw_const functions */

static void instrument_list_draw_const(void)
{
	draw_box(4, 12, 30, 48, BOX_THICK | BOX_INNER | BOX_INSET);
}

static void instrument_list_general_draw_const(void)
{
	int n;

	instrument_list_draw_const();

	draw_box(31, 15, 42, 48, BOX_THICK | BOX_INNER | BOX_INSET);

	/* Kind of a hack, and not really useful, but... :) */
	if (status.flags & CLASSIC_MODE) {
		draw_box(55, 46, 73, 48, BOX_THICK | BOX_INNER | BOX_INSET);
		draw_text("    ", 69, 47, 1, 0);
	} else {
		draw_box(55, 46, 69, 48, BOX_THICK | BOX_INNER | BOX_INSET);
	}

	draw_text("New Note Action", 54, 17, 0, 2);
	draw_text("Duplicate Check Type & Action", 47, 32, 0, 2);
	draw_text("Filename", 47, 47, 0, 2);

	for (n = 0; n < 35; n++) {
		draw_char(134, 44 + n, 15, 0, 2);
		draw_char(134, 44 + n, 30, 0, 2);
		draw_char(154, 44 + n, 45, 0, 2);
	}
}

static void instrument_list_volume_draw_const(void)
{
	instrument_list_draw_const();

	draw_fill_chars(57, 28, 62, 29, 0);
	draw_fill_chars(57, 32, 62, 34, 0);
	draw_fill_chars(57, 37, 62, 39, 0);

	draw_box(31, 17, 77, 26, BOX_THICK | BOX_INNER | BOX_INSET);
	draw_box(53, 27, 63, 30, BOX_THICK | BOX_INNER | BOX_INSET);
	draw_box(53, 31, 63, 35, BOX_THICK | BOX_INNER | BOX_INSET);
	draw_box(53, 36, 63, 40, BOX_THICK | BOX_INNER | BOX_INSET);
	draw_box(53, 41, 71, 44, BOX_THICK | BOX_INNER | BOX_INSET);
	draw_box(53, 45, 71, 47, BOX_THICK | BOX_INNER | BOX_INSET);

	draw_text("Volume Envelope", 38, 28, 0, 2);
	draw_text("Carry", 48, 29, 0, 2);
	draw_text("Envelope Loop", 40, 32, 0, 2);
	draw_text("Loop Begin", 43, 33, 0, 2);
	draw_text("Loop End", 45, 34, 0, 2);
	draw_text("Sustain Loop", 41, 37, 0, 2);
	draw_text("SusLoop Begin", 40, 38, 0, 2);
	draw_text("SusLoop End", 42, 39, 0, 2);
	draw_text("Global Volume", 40, 42, 0, 2);
	draw_text("Fadeout", 46, 43, 0, 2);
	draw_text("Volume Swing %", 39, 46, 0, 2);
}

static void instrument_list_panning_draw_const(void)
{
	instrument_list_draw_const();

	draw_fill_chars(57, 28, 62, 29, 0);
	draw_fill_chars(57, 32, 62, 34, 0);
	draw_fill_chars(57, 37, 62, 39, 0);
	draw_fill_chars(57, 42, 62, 45, 0);

	draw_box(31, 17, 77, 26, BOX_THICK | BOX_INNER | BOX_INSET);
	draw_box(53, 27, 63, 30, BOX_THICK | BOX_INNER | BOX_INSET);
	draw_box(53, 31, 63, 35, BOX_THICK | BOX_INNER | BOX_INSET);
	draw_box(53, 36, 63, 40, BOX_THICK | BOX_INNER | BOX_INSET);
	draw_box(53, 41, 63, 48, BOX_THICK | BOX_INNER | BOX_INSET);

	draw_text("Panning Envelope", 37, 28, 0, 2);
	draw_text("Carry", 48, 29, 0, 2);
	draw_text("Envelope Loop", 40, 32, 0, 2);
	draw_text("Loop Begin", 43, 33, 0, 2);
	draw_text("Loop End", 45, 34, 0, 2);
	draw_text("Sustain Loop", 41, 37, 0, 2);
	draw_text("SusLoop Begin", 40, 38, 0, 2);
	draw_text("SusLoop End", 42, 39, 0, 2);
	draw_text("Default Pan", 42, 42, 0, 2);
	draw_text("Pan Value", 44, 43, 0, 2);
	draw_text("Pitch-Pan Center", 37, 45, 0, 2);
	draw_text("Pitch-Pan Separation", 33, 46, 0, 2);
	if (status.flags & CLASSIC_MODE) {
		/* Hmm. The 's' in swing isn't capitalised. ;) */
		draw_text("Pan swing", 44, 47, 0, 2);
	} else {
		draw_text("Pan Swing", 44, 47, 0, 2);
	}

	draw_text("\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a", 54, 44, 2, 0);
}

static void instrument_list_pitch_draw_const(void)
{
	instrument_list_draw_const();

	draw_fill_chars(57, 28, 62, 29, 0);
	draw_fill_chars(57, 32, 62, 34, 0);
	draw_fill_chars(57, 37, 62, 39, 0);

	draw_box(31, 17, 77, 26, BOX_THICK | BOX_INNER | BOX_INSET);
	draw_box(53, 27, 63, 30, BOX_THICK | BOX_INNER | BOX_INSET);
	draw_box(53, 31, 63, 35, BOX_THICK | BOX_INNER | BOX_INSET);
	draw_box(53, 36, 63, 40, BOX_THICK | BOX_INNER | BOX_INSET);
	draw_box(53, 41, 71, 48, BOX_THICK | BOX_INNER | BOX_INSET);

	draw_text("Frequency Envelope", 35, 28, 0, 2);
	draw_text("Carry", 48, 29, 0, 2);
	draw_text("Envelope Loop", 40, 32, 0, 2);
	draw_text("Loop Begin", 43, 33, 0, 2);
	draw_text("Loop End", 45, 34, 0, 2);
	draw_text("Sustain Loop", 41, 37, 0, 2);
	draw_text("SusLoop Begin", 40, 38, 0, 2);
	draw_text("SusLoop End", 42, 39, 0, 2);
	draw_text("Default Cutoff", 36, 42, 0, 2);
	draw_text("Default Resonance", 36, 43, 0, 2);
	draw_text("MIDI Channels", 36, 44, 0, 2);
	draw_text("MIDI Program", 36, 45, 0, 2);
	draw_text("MIDI Bank Low", 36, 46, 0, 2);
	draw_text("MIDI Bank High", 36, 47, 0, 2);
}

/* --------------------------------------------------------------------- */
/* load_page functions */

static void _load_page_common(struct page *page, struct widget *page_widgets)
{
	int i;

	memset(saved_env, 0, sizeof(saved_env));
	for (i = 0; i < 10; i++) {
		saved_env[i].nodes = 2;
		saved_env[i].ticks[0] = 0;
		saved_env[i].ticks[1] = 100;
		saved_env[i].values[0] = 32;
		saved_env[i].values[1] = 32;
	}
	vgamem_ovl_alloc(&env_overlay);

	page->title = "Instrument List (F4)";
	page->pre_handle_key = instrument_list_pre_handle_key;
	page->handle_key = instrument_list_handle_key;
	page->widgets = page_widgets;
	page->help_index = HELP_INSTRUMENT_LIST;
	page->set_page = instrument_list_reposition;

	/* the first five widgets are the same for all four pages. */

	/* 0 = instrument list */
	create_other(page_widgets + 0, 1, instrument_list_handle_key_on_list, instrument_list_draw_list);
	page_widgets[0].accept_text = (instrument_cursor_pos == 25 ? 0 : 1);
	page_widgets[0].x = 5;
	page_widgets[0].y = 13;
	page_widgets[0].width = 24;
	page_widgets[0].height = 34;

	/* 1-4 = subpage switches */
	create_togglebutton(page_widgets + 1, 32, 13, 7, 1, 5, 0, 2, 2, change_subpage, "General",
			    1, subpage_switches_group);
	create_togglebutton(page_widgets + 2, 44, 13, 7, 2, 5, 1, 3, 3, change_subpage, "Volume",
			    1, subpage_switches_group);
	create_togglebutton(page_widgets + 3, 56, 13, 7, 3, 5, 2, 4, 4, change_subpage, "Panning",
			    1, subpage_switches_group);
	create_togglebutton(page_widgets + 4, 68, 13, 7, 4, 5, 3, 0, 0, change_subpage, "Pitch",
			    2, subpage_switches_group);
}

void instrument_list_general_load_page(struct page *page)
{
	_load_page_common(page, widgets_general);

	page->draw_const = instrument_list_general_draw_const;
	page->predraw_hook = instrument_list_general_predraw_hook;
	page->total_widgets = 18;

	/* special case stuff */
	widgets_general[1].d.togglebutton.state = 1;
	widgets_general[2].next.down = widgets_general[3].next.down = widgets_general[4].next.down = 6;

	/* 5 = note trans table */
	create_other(widgets_general + 5, 6, note_trans_handle_key, note_trans_draw);
	widgets_general[5].x = 32;
	widgets_general[5].y = 16;
	widgets_general[5].width = 9;
	widgets_general[5].height = 31;

	/* 6-9 = nna toggles */
	create_togglebutton(widgets_general + 6, 46, 19, 29, 2, 7, 5, 0, 0,
			    instrument_list_general_update_values,
			    "Note Cut", 2, nna_group);
	create_togglebutton(widgets_general + 7, 46, 22, 29, 6, 8, 5, 0, 0,
			    instrument_list_general_update_values,
			    "Continue", 2, nna_group);
	create_togglebutton(widgets_general + 8, 46, 25, 29, 7, 9, 5, 0, 0,
			    instrument_list_general_update_values,
			    "Note Off", 2, nna_group);
	create_togglebutton(widgets_general + 9, 46, 28, 29, 8, 10, 5, 0, 0,
			    instrument_list_general_update_values,
			    "Note Fade", 2, nna_group);

	/* 10-13 = dct toggles */
	create_togglebutton(widgets_general + 10, 46, 34, 12, 9, 11, 5, 14,
			    14, instrument_list_general_update_values,
			    "Disabled", 2, dct_group);
	create_togglebutton(widgets_general + 11, 46, 37, 12, 10, 12, 5, 15,
			    15, instrument_list_general_update_values,
			    "Note", 2, dct_group);
	create_togglebutton(widgets_general + 12, 46, 40, 12, 11, 13, 5, 16,
			    16, instrument_list_general_update_values,
			    "Sample", 2, dct_group);
	create_togglebutton(widgets_general + 13, 46, 43, 12, 12, 17, 5, 13,
			    13, instrument_list_general_update_values,
			    "Instrument", 2, dct_group);
	/* 14-16 = dca toggles */
	create_togglebutton(widgets_general + 14, 62, 34, 13, 9, 15, 10, 0,
			    0, instrument_list_general_update_values,
			    "Note Cut", 2, dca_group);
	create_togglebutton(widgets_general + 15, 62, 37, 13, 14, 16, 11, 0,
			    0, instrument_list_general_update_values,
			    "Note Off", 2, dca_group);
	create_togglebutton(widgets_general + 16, 62, 40, 13, 15, 17, 12, 0,
			    0, instrument_list_general_update_values,
			    "Note Fade", 2, dca_group);
	/* 17 = filename */
	/* impulse tracker has a 17-char-wide box for the filename for
	 * some reason, though it still limits the actual text to 12
	 * characters. go figure... */
	create_textentry(widgets_general + 17, 56, 47, 13, 13, 17, 0, update_filename,
			 NULL, 12);
}

static int _fixup_mouse_instpage_volume(struct key_event *k)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);
	if (envelope_mouse_edit && ins) {
		if (_env_handle_mouse(k, &ins->vol_env, &current_node_vol)) {
			ins->flags |= ENV_VOLUME;
			return 1;
		}
	}
	if ((k->sym.sym == SDLK_l || k->sym.sym == SDLK_b) && (k->mod & KMOD_ALT)) {
		return _env_handle_key_viewmode(k, &ins->vol_env, &current_node_vol, ENV_VOLUME);
	}
	return instrument_list_pre_handle_key(k);
}

void instrument_list_volume_load_page(struct page *page)
{
	_load_page_common(page, widgets_volume);

	page->pre_handle_key = _fixup_mouse_instpage_volume;
	page->draw_const = instrument_list_volume_draw_const;
	page->predraw_hook = instrument_list_volume_predraw_hook;
	page->total_widgets = 17;

	/* 5 = volume envelope */
	create_other(widgets_volume + 5, 0, volume_envelope_handle_key, volume_envelope_draw);
	widgets_volume[5].x = 32;
	widgets_volume[5].y = 18;
	widgets_volume[5].width = 45;
	widgets_volume[5].height = 8;

	/* 6-7 = envelope switches */
	create_toggle(widgets_volume + 6, 54, 28, 5, 7, 0, 0, 0,
		      instrument_list_volume_update_values);
	create_toggle(widgets_volume + 7, 54, 29, 6, 8, 0, 0, 0,
		      instrument_list_volume_update_values);

	/* 8-10 envelope loop settings */
	create_toggle(widgets_volume + 8, 54, 32, 7, 9, 0, 0, 0,
		      instrument_list_volume_update_values);
	create_numentry(widgets_volume + 9, 54, 33, 3, 8, 10, 0,
			instrument_list_volume_update_values, 0, 1,
			numentry_cursor_pos + 0);
	create_numentry(widgets_volume + 10, 54, 34, 3, 9, 11, 0,
			instrument_list_volume_update_values, 0, 1,
			numentry_cursor_pos + 0);

	/* 11-13 = susloop settings */
	create_toggle(widgets_volume + 11, 54, 37, 10, 12, 0, 0, 0,
		      instrument_list_volume_update_values);
	create_numentry(widgets_volume + 12, 54, 38, 3, 11, 13, 0,
			instrument_list_volume_update_values, 0, 1,
			numentry_cursor_pos + 0);
	create_numentry(widgets_volume + 13, 54, 39, 3, 12, 14, 0,
			instrument_list_volume_update_values, 0, 1,
			numentry_cursor_pos + 0);

	/* 14-16 = volume thumbbars */
	create_thumbbar(widgets_volume + 14, 54, 42, 17, 13, 15, 0,
			instrument_list_volume_update_values, 0, 128);
	create_thumbbar(widgets_volume + 15, 54, 43, 17, 14, 16, 0,
			instrument_list_volume_update_values, 0, 256);
	create_thumbbar(widgets_volume + 16, 54, 46, 17, 15, 16, 0,
			instrument_list_volume_update_values, 0, 100);
}

static int _fixup_mouse_instpage_panning(struct key_event *k)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);
	if (envelope_mouse_edit && ins) {
		if (_env_handle_mouse(k, &ins->pan_env, &current_node_pan)) {
			ins->flags |= ENV_PANNING;
			return 1;
		}
	}
	if ((k->sym.sym == SDLK_l || k->sym.sym == SDLK_b) && (k->mod & KMOD_ALT)) {
		return _env_handle_key_viewmode(k, &ins->pan_env, &current_node_pan, ENV_PANNING);
	}
	return instrument_list_pre_handle_key(k);
}
void instrument_list_panning_load_page(struct page *page)
{
	_load_page_common(page, widgets_panning);

	page->pre_handle_key = _fixup_mouse_instpage_panning;
	page->draw_const = instrument_list_panning_draw_const;
	page->predraw_hook = instrument_list_panning_predraw_hook;
	page->total_widgets = 19;

	/* 5 = panning envelope */
	create_other(widgets_panning + 5, 0, panning_envelope_handle_key, panning_envelope_draw);
	widgets_panning[5].x = 32;
	widgets_panning[5].y = 18;
	widgets_panning[5].width = 45;
	widgets_panning[5].height = 8;

	/* 6-7 = envelope switches */
	create_toggle(widgets_panning + 6, 54, 28, 5, 7, 0, 0, 0,
		      instrument_list_panning_update_values);
	create_toggle(widgets_panning + 7, 54, 29, 6, 8, 0, 0, 0,
		      instrument_list_panning_update_values);

	/* 8-10 envelope loop settings */
	create_toggle(widgets_panning + 8, 54, 32, 7, 9, 0, 0, 0,
		      instrument_list_panning_update_values);
	create_numentry(widgets_panning + 9, 54, 33, 3, 8, 10, 0,
			instrument_list_panning_update_values, 0, 1,
			numentry_cursor_pos + 1);
	create_numentry(widgets_panning + 10, 54, 34, 3, 9, 11, 0,
			instrument_list_panning_update_values, 0, 1,
			numentry_cursor_pos + 1);

	/* 11-13 = susloop settings */
	create_toggle(widgets_panning + 11, 54, 37, 10, 12, 0, 0, 0,
		      instrument_list_panning_update_values);
	create_numentry(widgets_panning + 12, 54, 38, 3, 11, 13, 0,
			instrument_list_panning_update_values, 0, 1,
			numentry_cursor_pos + 1);
	create_numentry(widgets_panning + 13, 54, 39, 3, 12, 14, 0,
			instrument_list_panning_update_values, 0, 1,
			numentry_cursor_pos + 1);

	/* 14-15 = default panning */
	create_toggle(widgets_panning + 14, 54, 42, 13, 15, 0, 0, 0,
		      instrument_list_panning_update_values);
	create_thumbbar(widgets_panning + 15, 54, 43, 9, 14, 16, 0,
			instrument_list_panning_update_values, 0, 64);

	/* 16 = pitch-pan center */
	create_other(widgets_panning + 16, 0, pitch_pan_center_handle_key, pitch_pan_center_draw);
	widgets_panning[16].next.up = 15;
	widgets_panning[16].next.down = 17;

	/* 17-18 = other panning stuff */
	create_thumbbar(widgets_panning + 17, 54, 46, 9, 16, 18, 0,
			instrument_list_panning_update_values, -32, 32);
	create_thumbbar(widgets_panning + 18, 54, 47, 9, 17, 18, 0,
			instrument_list_panning_update_values, 0, 64);
}

static int _fixup_mouse_instpage_pitch(struct key_event *k)
{
	song_instrument_t *ins = song_get_instrument(current_instrument);
	if (envelope_mouse_edit && ins) {
		if (_env_handle_mouse(k, &ins->pitch_env, &current_node_pitch)) {
			ins->flags |= ENV_PITCH;
			return 1;
		}
	}
	if ((k->sym.sym == SDLK_l || k->sym.sym == SDLK_b) && (k->mod & KMOD_ALT)) {
		return _env_handle_key_viewmode(k, &ins->pitch_env, &current_node_pitch, ENV_PITCH);
	}
	return instrument_list_pre_handle_key(k);
}
void instrument_list_pitch_load_page(struct page *page)
{
	static int midi_channel_selection_cursor_position = 0;

	_load_page_common(page, widgets_pitch);

	page->pre_handle_key = _fixup_mouse_instpage_pitch;
	page->draw_const = instrument_list_pitch_draw_const;
	page->predraw_hook = instrument_list_pitch_predraw_hook;
	page->total_widgets = 20;

	/* 5 = pitch envelope */
	create_other(widgets_pitch + 5, 0, pitch_envelope_handle_key, pitch_envelope_draw);
	widgets_pitch[5].x = 32;
	widgets_pitch[5].y = 18;
	widgets_pitch[5].width = 45;
	widgets_pitch[5].height = 8;

	/* 6-7 = envelope switches */
	create_menutoggle(widgets_pitch + 6, 54, 28, 5, 7, 0, 0, 0,
		      instrument_list_pitch_update_values, pitch_envelope_states);
	create_toggle(widgets_pitch + 7, 54, 29, 6, 8, 0, 0, 0,
		      instrument_list_pitch_update_values);

	/* 8-10 envelope loop settings */
	create_toggle(widgets_pitch + 8, 54, 32, 7, 9, 0, 0, 0,
		      instrument_list_pitch_update_values);
	create_numentry(widgets_pitch + 9, 54, 33, 3, 8, 10, 0,
			instrument_list_pitch_update_values, 0, 1,
			numentry_cursor_pos + 2);
	create_numentry(widgets_pitch + 10, 54, 34, 3, 9, 11, 0,
			instrument_list_pitch_update_values, 0, 1,
			numentry_cursor_pos + 2);

	/* 11-13 = susloop settings */
	create_toggle(widgets_pitch + 11, 54, 37, 10, 12, 0, 0, 0,
		      instrument_list_pitch_update_values);
	create_numentry(widgets_pitch + 12, 54, 38, 3, 11, 13, 0,
			instrument_list_pitch_update_values, 0, 1,
			numentry_cursor_pos + 2);
	create_numentry(widgets_pitch + 13, 54, 39, 3, 12, 14, 0,
			instrument_list_pitch_update_values, 0, 1,
			numentry_cursor_pos + 2);

	/* 14-15 = filter cutoff/resonance */
	create_thumbbar(widgets_pitch + 14, 54, 42, 17, 13, 15, 0,
			instrument_list_pitch_update_values, -1, 127);
	create_thumbbar(widgets_pitch + 15, 54, 43, 17, 14, 16, 0,
			instrument_list_pitch_update_values, -1, 127);
	widgets_pitch[14].d.thumbbar.text_at_min = "Off";
	widgets_pitch[15].d.thumbbar.text_at_min = "Off";

	/* 16-19 = midi crap */
	create_bitset(widgets_pitch + 16, 54, 44, 17, 15, 17, 0,
			instrument_list_pitch_update_values,
			17,
			" 1 2 3 4 5 6 7 8 9P\0""111213141516M\0",
			".\0.\0.\0.\0.\0.\0.\0.\0.\0p\0.\0.\0.\0.\0.\0.\0m\0",
			&midi_channel_selection_cursor_position
			);
	widgets_pitch[16].d.bitset.activation_keys =
		"123456789pabcdefm";

	create_thumbbar(widgets_pitch + 17, 54, 45, 17, 16, 18, 0,
			instrument_list_pitch_update_values, -1, 127);
	create_thumbbar(widgets_pitch + 18, 54, 46, 17, 17, 19, 0,
			instrument_list_pitch_update_values, -1, 127);
	create_thumbbar(widgets_pitch + 19, 54, 47, 17, 18, 19, 0,
			instrument_list_pitch_update_values, -1, 127);
	widgets_pitch[17].d.thumbbar.text_at_min = "Off";
	widgets_pitch[18].d.thumbbar.text_at_min = "Off";
	widgets_pitch[19].d.thumbbar.text_at_min = "Off";
}

