/***************************************************************************
*       NAME:  IWNOTE.C $Revision: 1.16 $
**      COPYRIGHT:
**      "Copyright (c) 1994,1995 by e-Tek Labs"
**
**       "This software is furnished under a license and may be used,
**       copied, or disclosed only in accordance with the terms of such
**       license and with the inclusion of the above copyright notice.
**       This software or any other copies thereof may not be provided or
**       otherwise made available to any other person. No title to and
**       ownership of the software is hereby transfered."
****************************************************************************
* $Log: iwnote.c $
* Revision 1.16  1995/11/22 04:17:01  mleibow
* Fixed tremolo and restarting of effects.
* Revision 1.15  1995/11/21 16:58:43  mleibow
* One more attempt at gettings effects right (chorus)
* Revision 1.14  1995/11/21 17:00:28  sdsmith
* Added deinit function.
* Revision 1.13  1995/10/26 14:59:31  mleibow
* Added mutes for software mixer controls.
* Revision 1.12  1995/10/13 17:28:52  mleibow
* Initial attempt at getting effects (reverb and chorus) into midi engine.
* Revision 1.11  1995/06/22 08:30:47  sdsmith
* Added master volume query functions
* Revision 1.10  1995/05/25 18:17:08  mleibow
* Revision 1.9  1995/05/25 15:14:14  mleibow
* Added far ptr list handling
* Revision 1.8  1995/05/03 08:43:50  mleibow
* Added tremelo to mod wheel
* Revision 1.7  1995/04/20 06:00:51  mleibow
* Volume and Expression curves now match Sound Canvas
* Revision 1.6  1995/04/17 12:55:33  mleibow
* Added iw_midi_master_volume() & volume query code
* Revision 1.5  1995/04/03 12:21:57  mleibow
* added prototype for new synth timer routines (avoid timers generating NMI's)
* Revision 1.4  1995/03/30 16:30:30  teckert
* Bug fix line 1223, added check for null pep pointer
* Revision 1.3  1995/03/24 09:44:18  mleibow
* Added initialization for MPU401 and other emulations
* Revision 1.2  1995/02/24 14:02:58  mleibow
* added short volume ramp code and fixed LFO problem
* Revision 1.1  1995/02/23 11:07:15  unknown
* Initial revision
***************************************************************************/

#include <dos.h>

#include "iw.h"
#include "iwl.h"
#include "globals.h"
#include "iwscale.h"
#include "iwatten.h"
#include "iwnote.h"

//extern unsigned short vibrato_scale[256];

/* Test to see if shorter ramps sound better */
#define TEST_MIN_VOLUME 1800
#define TEST_MIN_OFFSET (1800>>4)

#define VOLUME(a) (iw_atten_tab[a] << 1)

/* local data structures */
short iwl_m_volume[IW_MAX_SYNTHS]; /* 12 bit log attenuation */
short iwl_midi_master_volume;
short iwl_midi_mute_atten;
struct channel_status iwl_channel_status[IW_MAX_CHANNELS];
static struct voice_status voice_status[IW_MAX_VOICES];
static struct note_status note_status[IW_MAX_VOICES];
struct iwl_list iwl_active_voices;
struct iwl_list iwl_active_notes;

/* local functions */
static int volume_handler(int voice);
static int voice_handler(int voice);
static void layer_complete(struct voice_status RFAR *vs, int wait);
static void do_volume_envelope(int voice);
static short trigger_layer(struct voice_status RFAR *vs, struct note_status RFAR *ns, int layer_event);
static USHORT venv_timer_handler(USHORT voice);
static void set_voice_tremolo(struct voice_status RFAR *vs);
#ifdef IW_MODULE_EFFECTS
static void effects_volume_handler(void);
extern short iwl_effects_master_volume;
extern short iwl_effects_mute_atten;
#endif

int iwl_note_init(void)
{
    struct voice_status RFAR *vs;
    struct channel_status *cs;
    struct note_status RFAR *ns;
    short i;

    iwl_midi_master_volume = IWL_TO_FMM(iw_atten_tab[100]);
    iwl_midi_mute_atten = 0;
    for (i=0; i < IW_MAX_SYNTHS; i++) {
	iwl_m_volume[i] = iw_atten_tab[127];
    }
    iwl_InitList(&iwl_active_voices);
    iwl_InitList(&iwl_active_notes);
    for (i=0, vs = voice_status; i < IW_MAX_VOICES; i++, vs++) {
	vs->voice = i;
	vs->status = 0;
    }
    for (i=0, cs = iwl_channel_status; i < IW_MAX_CHANNELS; i++, cs++) {
	cs->status = 0;
	cs->patch = 0;
	cs->volume = VOLUME(100); //iw_atten_tab[100];
	cs->expression = VOLUME(127); //iw_atten_tab[127];
	cs->effect1_level = VOLUME(40);
	cs->effect3_level = VOLUME(0);
	cs->pitch_bend = 1024;
	cs->pan = 64;
	cs->vib_depth = 0;
	cs->trem_depth = 0;
    }
    for (i=0, ns = note_status; i < IW_MAX_VOICES; i++, ns++) {
	ns->status = 0;
    }
    if (iw_add_handler(IW_IRQ_HANDLER_VOLUME, (int (RFAR *)(void))volume_handler)) {
	return(IW_NO_MORE_HANDLERS);
    }
    if (iw_add_handler(IW_IRQ_HANDLER_VOICE, (int (RFAR *)(void))voice_handler)) {
	return(IW_NO_MORE_HANDLERS);
    }
#ifdef IW_MODULE_EFFECTS
    if (iw_effects_add_volume_handler(effects_volume_handler)) {
	return(IW_NO_MORE_HANDLERS);
    }
#endif
    return(IW_OK);
}

void iwl_note_deinit(void)
{
    int i;

    for (i=0; i<IW_MAX_CHANNELS; i++) iw_midi_all_sounds_off(i);
    iw_remove_handler(IW_IRQ_HANDLER_VOLUME, (int (RFAR *)(void))volume_handler);
    iw_remove_handler(IW_IRQ_HANDLER_VOICE, (int (RFAR *)(void))voice_handler);
    /* We may need to add and effects volume handler remove  - SDS */
}

static void set_volume(short left, short right, int both)
{
    if (left > IW_MAX_VOLUME) left = IW_MAX_VOLUME;
    left <<= 4;
    if (both) IWL_OUT_W(SET_LEFT_OFFSET, left);
    IWL_OUT_W(SET_LEFT_FINAL_OFFSET, left);
    if (right > IW_MAX_VOLUME) right = IW_MAX_VOLUME;
    right <<= 4;
    if (both) IWL_OUT_W(SET_RIGHT_OFFSET, right);
    IWL_OUT_W(SET_RIGHT_FINAL_OFFSET, right);
}

#ifdef IW_MODULE_EFFECTS
static void set_effects_volume(short one, short three, int both)
{
    unsigned char emask;
    unsigned short evol;

    // The InterWave chip can only set one effect level, not two, so
    // if the difference of the two levels are within a certain threshold
    // then the larger will be used.  Otherwise only one will be
    // set.
    one += VOLUME(iwl_effects_master_volume) + iwl_effects_mute_atten;
    three += VOLUME(iwl_effects_master_volume) + iwl_effects_mute_atten;
    if (one > IW_MAX_VOLUME) one = IW_MAX_VOLUME;
    if (three > IW_MAX_VOLUME) three = IW_MAX_VOLUME;
    one = IW_MAX_VOLUME - one;	// math is easier this way
    three = IW_MAX_VOLUME - three;
    if (one && three) {
	if (one-three > 128) { // one is much louder than three
	    three = 0;
	} else if (three-one > 128) { // three is much louder than one
	    one = 0; // max attenuation
	}
	if (one && three) {
	    one = (three > one) ? three : one;
	    three = one;
	}
    }
    emask = 0;
    evol = IW_MAX_VOLUME<<4;
    if (one) {
	emask |= iw_get_effects_mask(0);
	evol = (IW_MAX_VOLUME-one)<<4;
    }
    if (three) {
	emask |= iw_get_effects_mask(1);
	evol = (IW_MAX_VOLUME-three)<<4;
    }
    IWL_OUT_B(SET_EFFECT_ACC_SEL, emask); // enable effects
    if (both) IWL_OUT_W(SET_EFFECT_VOLUME, evol);
    IWL_OUT_W(SET_EFFECT_FINAL_VOLUME, evol);
}
#endif

void iwl_change_voice_volume(int voice)
{
    struct voice_status RFAR *vs;
    struct channel_status *cs;
    struct note_status *ns;
    int atten;
    short vlo, vro, pan, rev_depth, chorus_depth;

    vs = &voice_status[voice];
    cs = &iwl_channel_status[vs->channel];
    atten = cs->volume + cs->expression + iwl_m_volume[vs->synth] + iwl_midi_master_volume + vs->layer_atten + vs->wave_atten + iwl_midi_mute_atten;
    pan = vs->pan_offset + (short)cs->pan;
    if (pan < 0) pan = 0;
    if (pan > 127) pan = 127;
    vlo = iw_atten_tab[(IW_ATTENTABSIZE-1)-pan];
    vro = iw_atten_tab[pan];
    if (pan != (IW_ATTENTABSIZE-1) && pan != 0) {
	vlo >>= 1;
	vro >>= 1;
    }
    IWL_SET_PAGE(voice);
    if (vs->layer->velocity_mode == IW_LAYER_TIME) atten += vs->vel_atten;
    set_volume(atten+vlo, atten+vro, 0);
#ifdef IW_MODULE_EFFECTS
    rev_depth = 0;
    chorus_depth = 0;
    ns = vs->note;
    if (vs->channel == 9) {
	/* percussion's effects are multipled against channel levels */
	switch (ns->patch->effect1) {
	    case IW_PATCH_EFFECT_REVERB:
		rev_depth = ns->patch->effect1_depth;
		break;
	    case IW_PATCH_EFFECT_CHORUS:
		chorus_depth = ns->patch->effect1_depth;
		break;
	}
	switch (ns->patch->effect2) {
	    case IW_PATCH_EFFECT_REVERB:
		rev_depth = ns->patch->effect2_depth;
		break;
	    case IW_PATCH_EFFECT_CHORUS:
		chorus_depth = ns->patch->effect2_depth;
		break;
	}
	rev_depth = VOLUME(rev_depth);
	chorus_depth = VOLUME(chorus_depth);
    }
    set_effects_volume(atten+cs->effect1_level+rev_depth,atten+cs->effect3_level+chorus_depth, 0);
#endif
}

void iw_midi_change_volume(int channel, int volume)
{
    struct channel_status *cs = &iwl_channel_status[channel];
    struct voice_status RFAR *vs;
    
    ENTER;
    cs->volume = VOLUME(volume); //iw_atten_tab[volume];
    for (vs = VS_START; vs != 0; vs = VS_NEXT(vs)) {
	if (vs->channel == (UCHAR)channel) {
	    iwl_change_voice_volume(vs->voice);
	}
    }
    LEAVE;
}

void iw_midi_change_expression(int channel, int volume)
{
    struct channel_status *cs = &iwl_channel_status[channel];
    struct voice_status RFAR *vs;

    ENTER;
    cs->expression = VOLUME(volume); //iw_atten_tab[volume];
    for (vs = VS_START; vs != 0; vs = VS_NEXT(vs)) {
	if (vs->channel == (UCHAR)channel) {
	    iwl_change_voice_volume(vs->voice);
	}
    }
    LEAVE;
}

void iw_midi_master_volume(int volume)
{
    struct voice_status RFAR *vs;

    ENTER;
    iwl_midi_master_volume = IWL_TO_FMM(iw_atten_tab[volume]);
    for (vs = VS_START; vs != 0; vs = VS_NEXT(vs)) {
	iwl_change_voice_volume(vs->voice);
    }
    LEAVE;
}

int iw_midi_get_master_volume(void)
{
    int i, rc;

	for (i=0; i<IW_ATTENTABSIZE; i++) {
		if (IWL_FROM_FMM(iwl_midi_master_volume) > iw_atten_tab[i]) {
			break;
		}
	}
	if (i)
		rc = i - 1;
	else
		rc = 0;
	if (rc < 0)
		rc = 0;
	if (rc > 127)
		rc = 127;
    return(rc);
}

void iw_midi_mute(short mute)
{
    struct voice_status RFAR *vs;

    ENTER;
    if (mute) {
	iwl_midi_mute_atten = IW_MAX_VOLUME;
    } else {
	iwl_midi_mute_atten = 0;
    }
    for (vs = VS_START; vs != 0; vs = VS_NEXT(vs)) {
	iwl_change_voice_volume(vs->voice);
    }
    LEAVE;
}

void iw_midi_synth_volume(USHORT synth, int master_volume)
{
    struct voice_status RFAR *vs;

    ENTER;
    iwl_m_volume[synth] = IWL_TO_FMM(iw_atten_tab[master_volume]);
    for (vs = VS_START; vs != 0; vs = VS_NEXT(vs)) {
	if (vs->synth == synth) {
	    iwl_change_voice_volume(vs->voice);
	}
    }
    LEAVE;
}

static int voice_handler(int voice)
{
    struct voice_status RFAR *vs = &voice_status[voice];

    if (vs->status & STAT_VOICE_RUNNING) {
	layer_complete(vs, DONT_WAIT);
	return(1);
    }
    return(0);
}

static int volume_handler(int voice)
{
    struct voice_status RFAR *vs = &voice_status[voice];

    if (vs->status & STAT_VOICE_RUNNING) {
	do_volume_envelope(voice);
	return(1);
    }
    return(0);
}

#ifdef IW_MODULE_EFFECTS
static void effects_volume_handler(void)
{
    struct voice_status RFAR *vs;

    ENTER;
    for (vs = VS_START; vs != 0; vs = VS_NEXT(vs)) {
	iwl_change_voice_volume(vs->voice);
    }
    LEAVE;
}
#endif

static short vibrato_table[] =
{
    0, 0,       31, 592,        61, 1175,       93, 1808,
    124, 2433,  152, 3007,      182, 3632,      213, 4290,
    241, 4894,  255, 5200,
};
static short calculate_vibrato(struct voice_status RFAR *vs, short cents)
{
    long depth;
    short *vi1, *vi2, pcents, v1;

    if (cents < 0) {
	pcents = -cents;
    } else {
	pcents = cents;
    }
    for (vi1=vibrato_table, vi2=vi1+2; pcents > *vi2; vi1 = vi2, vi2 += 2) ;
    v1 = *(vi1+1);
    // The FC table above is a list of pairs.  The first number in the pair
    // is the cents index from 0 - 255 cents, and the second number in the
    // pair is the FC adjustment needed to change the pitch by the indexed
    // number of cents.  The table was created for an FC of 32768.
    // The following expression does a linear interpolation against the
    // approximated log curve in the table above, and then scales the number
    // by the FC before the LFO.  This calculation also adjusts the output
    // value to produce the appropriate depth for the hardware.  The depth
    // is 2 * desired FC + 1.
    depth = (((long)(*(vi2+1)-v1) * (pcents-*vi1) / (*vi2 - *vi1)) + v1) * vs->fc_register >> 14;
    if (depth) depth++;
    if (depth > 255) depth = 255;
    if (cents < 0) {
	return(-(short)depth);
    } else {
	return((short)depth);
    }
}

static void set_voice_vibrato(struct voice_status RFAR *vs)
{
    struct channel_status *cs = &iwl_channel_status[vs->channel];
    short depth, bdepth, sweep=0, current_depth;
    short freq;

    bdepth = vs->layer->vibrato.depth;
    if (cs->vib_depth) {
	if (bdepth < 0) {
	    depth = calculate_vibrato(vs, -(((cs->vib_depth>>1) * (127+bdepth) >> 7)-bdepth));
	} else {
	    depth = calculate_vibrato(vs, bdepth + ((cs->vib_depth>>1) * (127-bdepth) >> 7));
	}
	if (bdepth) {
	    sweep = vs->layer->vibrato.sweep * (127-cs->vib_depth) >> 7;
	}
    } else {
	depth = calculate_vibrato(vs, bdepth);
	if (depth) {
	    sweep = vs->layer->vibrato.sweep;
	}
    }
    if (sweep == 0) {
	current_depth = depth<<5;
    } else {
	current_depth = 0;
    }
    if (vs->status & STAT_VOICE_VIBRATO_RUNNING) {
	if (depth) {
	    iwl_lfo_change_depth(vs->voice, IW_LFO_VIBRATO, depth);
	} else {
	    iwl_lfo_shutdown(vs->voice, IW_LFO_VIBRATO);
	    vs->status &= ~STAT_VOICE_VIBRATO_RUNNING;
	}
    } else if (depth) {
	freq = vs->layer->vibrato.freq;
	if (freq == 0) freq = 523;
	if (cs->vib_depth == 0) {
	    iwl_lfo_setup(vs->voice, IW_LFO_VIBRATO,
			  freq, current_depth,
			  depth, sweep,
			  vs->layer->vibrato.shape);
	} else { /* use the default parameters */
	    iwl_lfo_setup(vs->voice, IW_LFO_VIBRATO,
			  freq, current_depth,
			  depth, sweep,
			  IW_LFO_TRIANGLE);
	}
	vs->status |= STAT_VOICE_VIBRATO_RUNNING;
    }
}

void iw_midi_set_vibrato(int channel, int value)
{
    struct voice_status RFAR *vs;
    struct channel_status *cs;
    
    ENTER;
    cs = &iwl_channel_status[channel];
    cs->vib_depth = value;
    for (vs = VS_START; vs != 0; vs = VS_NEXT(vs)) {
	if (vs->channel == (UCHAR)channel) {
	    set_voice_vibrato(vs);
	}
    }
    LEAVE;
}

void iw_channel_pitch_bend(int channel, unsigned int bend)
{
    struct channel_status *cs = &iwl_channel_status[channel];
    struct voice_status RFAR *vs;
    ULONG newfc;

    ENTER;
    cs->pitch_bend = bend;
    for (vs = VS_START; vs != 0; vs = VS_NEXT(vs)) {
	if (vs->channel == (UCHAR)channel) {
	    if (cs->pitch_bend == 1024) {
		vs->fc_register = vs->ofc_reg;
	    } else {
		newfc = ((ULONG)vs->ofc_reg * cs->pitch_bend) >> 10;
		if (newfc > 65535L) newfc = 65535;
		vs->fc_register = (USHORT)newfc;
	    }
//	    if (vs->status & STAT_VOICE_VIBRATO_RUNNING) {
//		set_voice_vibrato(vs);
//	    }
	    IWL_SET_PAGE(vs->voice);
	    if (vs->pep) {
		IWL_OUT_W(SET_FREQUENCY, (USHORT)(((ULONG)vs->fc_register * (vs->penv_pitch>>16))>>10));
	    } else {
		IWL_OUT_W(SET_FREQUENCY, vs->fc_register);
	    }
	}
    }
    LEAVE;
}

static USHORT iwl_calc_fc(struct iw_wave RFAR *w, long freq)
{
	ULONG fc;
	int scale=0;

	while (freq >= 4194304L) { scale++; freq>>=2; } /* floating point */
	fc = (ULONG)freq << 10;
	fc = fc / w->sample_ratio;
	while (scale--) fc <<= 2; /* floating point */
	return((USHORT)fc);
}

static void stop_voice(int voice, int use_int)
{
    struct voice_status RFAR *vs = &voice_status[voice];
    unsigned short old_volume;

    if (vs->status & STAT_VOICE_RUNNING) {
	iwl_stop_cs_timer(voice);
	IWL_SET_PAGE(voice);
	vs->voice_control &= ~(IWL_IRQE|IWL_IRQP);
	vs->voice_control |= IWL_STOP;
	IWL_OUT_CONTROL(SET_CONTROL, vs->voice_control);
	IWL_OUT_CONTROL(SET_VOLUME_CONTROL, IWL_STOP);
	IWL_INP_W(GET_VOLUME, old_volume);
	old_volume = (USHORT)old_volume >> 8;
	/* Test to see if shorter ramps sound better */
	if (old_volume > TEST_MIN_OFFSET) {
	    if (use_int) {
		vs->volume_control = IWL_DIR_DEC|IWL_IRQE;
		/* make interrupt look like end of volume envelope */
		vs->venv_state = DONE;
	    } else {
		vs->volume_control = IWL_DIR_DEC;
	    }
	    /* Test to see if shorter ramps sound better */
	    IWL_OUT_B(SET_VOLUME_START, TEST_MIN_OFFSET);
	    IWL_OUT_B(SET_VOLUME_RATE, 63);
	    IWL_OUT_CONTROL(SET_VOLUME_CONTROL, vs->volume_control);
	} else if (use_int) {
	    /* make interrupt look like end of volume envelope */
	    vs->venv_state = DONE;
	    vs->status |= STAT_VOICE_RELEASING;
	    do_volume_envelope(vs->voice);
	}
    }
}

static void wait_voice(int voice)
{
    struct voice_status RFAR *vs = &voice_status[voice];
    UCHAR vc;

    if (vs->status & STAT_VOICE_RUNNING) {
	IWL_SET_PAGE(voice);
	/* wait for volume to stop */
	do {
	    IWL_INP_B(GET_VOLUME_CONTROL, vc);
	} while ((vc & (IWL_STOP|IWL_STOPPED)) == 0);
	IWL_OUT_W(SET_VOLUME, 0);
	IWL_OUT_B(SET_EFFECT_ACC_SEL, 0); // disable effects
	IWL_OUT_W(SET_EFFECT_VOLUME, 0);
	IWL_OUT_W(SET_EFFECT_FINAL_VOLUME, 0);
	iwl_lfo_shutdown(vs->voice, IW_LFO_VIBRATO); // disable vibrato
	iwl_lfo_shutdown(vs->voice, IW_LFO_TREMOLO); // disable tremolo
	vs->status = 0;
	iwl_DeleteNode(&iwl_active_voices, (struct iwl_node RFAR *)vs);
	iw_free_voice(voice);
    }
}

static void stop_note(struct note_status RFAR *ns, short wait)
{
    struct voice_status RFAR *vs, RFAR *nvs;
    /* voice stealing routine, turn all voices off which belong to note */

    ENTER;
    if (ns->status & NOTE_ACTIVE) {
	for (vs = VS_START; vs != 0; vs = VS_NEXT(vs)) {
	    if (vs->note == ns) {
		/* stop voice will also stop envelopes */
		stop_voice(vs->voice, !wait);
	    }
	}
	if (wait) {
	    for (vs = VS_START; vs != 0; vs = nvs) {
		nvs = VS_NEXT(vs);
		if (vs->note == ns) {
		    /* waits for volumes to become inaudible */
		    wait_voice(vs->voice);
		}
	    }
	    ns->status = 0;
	    iwl_DeleteNode(&iwl_active_notes, (struct iwl_node RFAR *)ns);
	}
    }
    LEAVE;
}

static void RFAR stop_note_voice(int note_voice)
{
    struct voice_status RFAR *vs = &voice_status[note_voice];

    ENTER;
    stop_note(vs->note, 1);
    LEAVE;
}

static void find_envelope(
    struct iw_envp_header RFAR *ep,
    struct iw_envp_record RFAR * RFAR *per,
    int note,
    int velocity)
{
    int i, j;
    struct iw_envp_record RFAR *er;

    if (ep == 0L) { *per = 0L; return; }
    er = (struct iw_envp_record RFAR *)(ep+1);
    if (ep->index_type == IW_ENVELOPE_IDX_VELOCITY) {
	i=velocity;
    } else if (ep->index_type == IW_ENVELOPE_IDX_FREQUENCY) {
	i=note;
    } else {
	ep->num_envelopes = 1;
    }
    for (j=1; j < ep->num_envelopes; j++) {
	if (i <= er->hirange) break;
	er = (struct iw_envp_record RFAR *)
	    ((char RFAR *)(er+1) + (er->nattack+er->nrelease)*2*sizeof(short));
    }
    *per = er;
}

static void layer_complete(struct voice_status RFAR *vs, int wait)
{
    struct note_status RFAR *ns;
    struct voice_status RFAR *vs1;

    ns = vs->note;
    if (wait) {
	stop_voice(vs->voice, POLLED);
	wait_voice(vs->voice);
	for (vs1 = VS_START; vs1 != 0; vs1 = VS_NEXT(vs1)) {
	    if (vs1 != vs && vs1->note == ns) return;
	}
	ns->status = 0;
	iwl_DeleteNode(&iwl_active_notes, (struct iwl_node RFAR *)ns);
    } else {
	stop_voice(vs->voice, USE_INTERRUPT);
    }
}

static void do_volume_envelope(int voice)
{
    struct voice_status RFAR *vs = &voice_status[voice];
    short old_volume;
    USHORT rate;
    short next;
    char program_next_ramp;
    static char retriggering = 0;

    IWL_SET_PAGE(voice);
    while (1) {
	program_next_ramp = 0;
	switch (vs->venv_state) {
	    case BEFORE:
		vs->venv_index = 0; /* initialize envelope pointer */
		vs->venv_data = (short RFAR *)(vs->ver+1); /* point to variable length data */
		vs->venv_state++;
		/* Test to see if shorter ramps sound better */
		IWL_OUT_W(SET_VOLUME, TEST_MIN_VOLUME);
		break;
	    case ATTACK:
		if (vs->venv_index < vs->ver->nattack) {
		    program_next_ramp = 1;
		    next = *vs->venv_data++;
		    rate = *vs->venv_data++;
		    vs->venv_index++;
		} else {
		    vs->venv_state++;
		}
		break;
	    case SUSTAIN:
		program_next_ramp = 1;
		next = vs->ver->sustain_offset;
		rate = vs->ver->sustain_rate;
		vs->venv_state++;
		break;
	    case VAR_RELEASE:
		if (vs->venv_index <= vs->ver->nattack &&
		    vs->vep->mode == IW_ENVELOPE_SUSTAIN) {
		    if (!(vs->status & STAT_VOICE_RELEASING)) {
			vs->status |= STAT_VOICE_SUSTAINING;
			vs->volume_control &= ~(IWL_IRQP|IWL_IRQE|IWL_STOP|IWL_STOPPED);
			IWL_OUT_CONTROL(SET_VOLUME_CONTROL, vs->volume_control|IWL_STOP);
			return; /* get outta here */
		    }
		}
		if (vs->venv_index <= vs->ver->nattack &&
		    vs->vep->mode != IW_ENVELOPE_ONE_SHOT) {
		    vs->venv_index = vs->ver->nattack;
		}
		if (vs->venv_index < (vs->ver->nattack+vs->ver->nrelease)) {
		    if (vs->venv_index == vs->ver->nattack) {
			IWL_OUT_CONTROL(SET_VOLUME_CONTROL, IWL_STOP);
			IWL_INP_W(GET_VOLUME, old_volume);
			old_volume = (USHORT)old_volume >> 8;
			if (vs->layer->velocity_mode == IW_LAYER_RATE) {
			    old_volume += vs->vel_atten>>4;
			}
			vs->rel_offset = old_volume - vs->ver->sustain_offset;
			if (vs->rel_offset > 0) vs->rel_offset = 0;
		    }
		    program_next_ramp = 1;
		    next = *vs->venv_data++;
		    rate = *vs->venv_data++;
		    vs->venv_index++;
		} else {
		    vs->venv_state++;
		}
		break;
	    case FINAL_RELEASE:
		program_next_ramp = 1;
		next = 0;
		rate = vs->ver->release_rate;
		vs->venv_state++;
		/* rate of 0 shuts off enveloping, but leaves note */
		/* playing.  This is useful for sampled percussion */
		if (rate == 0) {
		    vs->volume_control &= ~(IWL_IRQP|IWL_IRQE|IWL_STOP|IWL_STOPPED);
		    IWL_OUT_CONTROL(SET_VOLUME_CONTROL, vs->volume_control|IWL_STOP);
		    return;
		}
		break;
	    case DONE:
		if (vs->vep->flags & IW_ENV_FLAGS_RETRIGGER && !retriggering) {
		    if (!(vs->status & STAT_VOICE_RELEASING)) {
			retriggering = 1;
			if (vs->layer->flags & IW_LAYER_FLAGS_RETRIGGER) {
			    IWL_SET_PAGE(vs->voice);
			    IWL_OUT_CONTROL(SET_CONTROL, vs->voice_control|IWL_STOP);
			    if (trigger_layer(vs, vs->note, IW_LAYER_KEY_RETRIGGER)) {
				IWL_OUT_CONTROL(SET_CONTROL, vs->voice_control);
				retriggering = 0;
				return;
			    }
			}
			vs->venv_state = BEFORE;
			retriggering = 0;
			continue;
		    }
		}
		layer_complete(vs, WAIT);
		return;
	}
	if (!program_next_ramp) continue;
	IWL_OUT_CONTROL(SET_VOLUME_CONTROL, IWL_STOP);
	IWL_INP_W(GET_VOLUME, old_volume);
	old_volume = (USHORT)old_volume >> 8;
	if (rate == 0) {
	    program_next_ramp = 0;
	    continue; /* ignore point */
	}
	/* set up volume ramp */
	if (vs->layer->velocity_mode == IW_LAYER_RATE) {
	    /* the volume ramp registers are 8 bits, and the */
	    /* attenuation registers are 12 bits... compensate */
	    next -= vs->vel_atten>>4;
	}
	if (vs->venv_state == VAR_RELEASE && vs->venv_index > vs->ver->nattack) {
	    next += vs->rel_offset;
	}
	/* Test to see if shorter ramps sound better */
	if (next < TEST_MIN_OFFSET) next = TEST_MIN_OFFSET;
//      if (next < IW_MIN_OFFSET) next = IW_MIN_OFFSET;
	if (next > IW_MAX_OFFSET) next = IW_MAX_OFFSET;
	if (old_volume == next) {
	    continue; /* already there */
	}
	if (old_volume > next) {
	    IWL_OUT_B(SET_VOLUME_START, next);
	    IWL_OUT_B(SET_VOLUME_END, old_volume);
	    vs->volume_control |= IWL_DIR_DEC;
	} else {
	    IWL_OUT_B(SET_VOLUME_START, old_volume);
	    IWL_OUT_B(SET_VOLUME_END, next);
	    vs->volume_control &= ~IWL_DIR_DEC;
	}
	IWL_OUT_B(SET_VOLUME_RATE, rate);
	vs->volume_control &= ~(IWL_IRQP|IWL_STOP|IWL_STOPPED);
	vs->volume_control |= IWL_IRQE;
	IWL_OUT_CONTROL(SET_VOLUME_CONTROL, vs->volume_control);
	return;
    }
}

static void do_pitch_envelope(USHORT voice)
{
    struct voice_status RFAR *vs = &voice_status[voice];
    char program_next_ramp;
    short next, time;
    int use_timer;
    static char retriggering = 0;

    IWL_SET_PAGE(voice);
    if (vs->penv_state != BEFORE && vs->penv_in_progress) {
	if (--vs->penv_timer <= 0) {
	    vs->penv_pitch = vs->penv_next;
	} else {
	    vs->penv_pitch += vs->penv_increment;
	    use_timer=1;
	    goto pitch_return;
	}
    }
    while (1) {
	program_next_ramp = 0;
	vs->penv_in_progress = 0;
	switch (vs->penv_state) {
	    case BEFORE:
		vs->penv_index = 0; /* initialize envelope pointer */
		vs->penv_data = (short RFAR *)(vs->per+1); /* point to variable length data */
		vs->penv_state++;
		vs->penv_pitch = 1024L << 16;
		break;
	    case ATTACK:
		if (vs->penv_index < vs->per->nattack) {
		    program_next_ramp = 1;
		    next = *vs->penv_data++;
		    time = *vs->penv_data++;
		    vs->penv_index++;
		} else {
		    vs->penv_state++;
		}
		break;
	    case SUSTAIN:
		program_next_ramp = 1;
		next = vs->per->sustain_offset;
		time = vs->per->sustain_rate;
		vs->penv_state++;
		break;
	    case VAR_RELEASE:
		if (vs->penv_index <= vs->per->nattack &&
		    vs->pep->mode == IW_ENVELOPE_SUSTAIN) {
		    if (!(vs->status & STAT_VOICE_RELEASING)) {
			use_timer=0;
			goto pitch_return;
		    }
		}
		if (vs->penv_index <= vs->per->nattack &&
		    vs->pep->mode != IW_ENVELOPE_ONE_SHOT) {
		    vs->penv_index = vs->per->nattack;
		}
		if (vs->penv_index < (vs->per->nattack+vs->per->nrelease)) {
		    program_next_ramp = 1;
		    next = *vs->penv_data++;
		    time = *vs->penv_data++;
		    vs->penv_index++;
		} else {
		    vs->penv_state++;
		}
		break;
	    case FINAL_RELEASE:
		program_next_ramp = 1;
		next = 1024;
		time = vs->per->release_rate;
		vs->penv_state++;
		/* time of 0 shuts off enveloping, and leaves pitch */
		/* at current offset.  This is useful for indexed detuning */
		if (time == 0) {
		    use_timer=0;
		    goto pitch_return;
		}
		break;
	    case DONE:
		if (vs->pep->flags & IW_ENV_FLAGS_RETRIGGER && !retriggering) {
		    if (!(vs->status & STAT_VOICE_RELEASING)) {
			retriggering = 1;
			if (vs->layer->flags & IW_LAYER_FLAGS_RETRIGGER) {
			    IWL_SET_PAGE(vs->voice);
			    IWL_OUT_CONTROL(SET_CONTROL, vs->voice_control|IWL_STOP);
			    if (trigger_layer(vs, vs->note, IW_LAYER_KEY_RETRIGGER)) {
				retriggering = 0;
				return;
			    }
			}
			retriggering = 0;
			vs->penv_state = BEFORE;
			continue;
		    }
		}
		use_timer=0;
		goto pitch_return;
	}
	if (!program_next_ramp) continue;
	vs->penv_next = ((long)next)<<16;
	if (time == 0) {
	    vs->penv_pitch = vs->penv_next;
	    continue;
	}
	vs->penv_in_progress = 1;
	vs->penv_timer = time;
	vs->penv_increment = (vs->penv_next - vs->penv_pitch) / time;
	use_timer=1;
	break;
    }
    pitch_return:
    IWL_OUT_W(SET_FREQUENCY, (USHORT)(((ULONG)vs->fc_register * (vs->penv_pitch>>16))>>10));
    if (use_timer) {
	iwl_set_cs_timer(vs->voice, 1, do_pitch_envelope, vs->voice);
    }
}

static short trigger_layer(struct voice_status RFAR *vs, struct note_status RFAR *ns, int layer_event)
{
    struct channel_status *cs = &iwl_channel_status[vs->channel];
    struct iw_layer RFAR *l = vs->layer;
    struct iw_envp_header RFAR *vep, RFAR *pep;
    struct iw_envp_record RFAR *ver, RFAR *per;
    struct iw_envp RFAR *ep;
    struct iw_wave RFAR *w;
    ULONG l1, addrs, addr;
    USHORT sn, sn_interp;
    short pan_offset;
    short atten;
    short vlo, vro;
    short wave, pan, rev_depth, chorus_depth;
    UCHAR mode;

    /* layer calculations */
    if (layer_event <= IW_LAYER_KEY_DOWN || layer_event == IW_LAYER_KEY_LEGATO) {
	/* do kbd_freq scale and find waveform to play */
	if (l->freq_scale != 1024) {
	    /* translate, scale, translate */
	    l1 = ((ULONG)ns->note - l->freq_center) * l->freq_scale;
	    /* store the remainder */
	    sn_interp = (USHORT)(l1 & ((1L << 10)-1L));
	    sn = (USHORT)((l1 >> 10) + l->freq_center);
	} else {
	    sn = ns->note;
	}
	/* search for correct wave in wave list */
	w=l->waves;
	for (wave=0; wave < l->nwaves; wave++, w=w->id.p) {
	    if ((USHORT)w->high_note >= sn && (USHORT)w->low_note <= sn) break;
	}
	if (wave == l->nwaves) {
	    iw_free_voice(vs->voice);
	    return(0); /* ignore this layer */
	}
	vs->wave = w;
	vs->ofc_reg = iwl_calc_fc(w, iwl_scale_table[sn]);
	if (l->freq_scale != 1024) {
	    /**********************************************************************/
	    /* interp value is percentage between this semitone and next semitone */
	    /* This equation is the linear interpolation between a frequency and  */
	    /* another frequency one semitone higher in pitch.                    */
	    /* or (1.059F - F) * interp + F or                                    */
	    /* ((((K * log2(1/12) * interp) / K) + K) * original) / K             */
	    /* where K is a 2^10.                                                 */
	    /**********************************************************************/
	    vs->ofc_reg = (USHORT)(((((61L*sn_interp)>>10)+1024L) * vs->ofc_reg) >> 10);
	}
    }
    if (layer_event <= IW_LAYER_KEY_DOWN) {
	/* search for correct envelope in volume envelope list */
	ep = (struct iw_envp RFAR *)l->venv.p;
	if (ep) {
	    vep = &ep->h;
	} else {
	    vep = 0;
	}
	find_envelope(vep, &ver, ns->note, ns->velocity);
	/* make sure envelope is valid or cancel layer. */
	/* if no volume envelope record, then can't hear layer. */
	/* also, key_up layers must be ONE_SHOT */
	if (ver == 0L || (layer_event == IW_LAYER_KEY_UP &&
			  vep->mode != IW_ENVELOPE_ONE_SHOT)) {
	    iw_free_voice(vs->voice);
	    return(0); /* ignore this layer */
	}
	/* search for correct envelope in pitch envelope list */
	ep = (struct iw_envp RFAR *)l->penv.p;
	if (ep) {
	    pep = &ep->h;
	} else {
	    pep = 0;
	}
	find_envelope(pep, &per, ns->note, ns->velocity);
	/* sum all attenuations together */
	/* velocity attenuation is based on x^2... since hardware has */
	/* exponential volume table, we have to use log(x^2) which */
	/* turns out to be 2log(x) or 2 * the linear volume table! */
	vs->vel_atten = iw_atten_tab[ns->velocity] << 1;
	vs->layer_atten = l->attenuation<<5;
	vs->wave_atten = vs->wave->attenuation<<5;
	vs->synth = vs->channel >> 4;
	vs->vep = vep;
	vs->ver = ver;
	vs->pep = pep;
	vs->per = per;
	atten = iwl_m_volume[vs->synth] + iwl_midi_master_volume + cs->volume + cs->expression + vs->layer_atten + vs->wave_atten + iwl_midi_mute_atten;
	/* pan */
	if (l->pan_freq_scale) {
	    /* translate-scale-translate */
	    pan_offset = (((short)(ns->note - 64) * l->pan_freq_scale) >> 7) + (l->pan-64);
	} else {
	    pan_offset = l->pan - 64;
	}
	vs->pan_offset = pan_offset;
	pan = pan_offset + (short)cs->pan;
	if (pan < 0) pan = 0;
	if (pan > 127) pan = 127;
	vs->pan = (UCHAR)pan;
    }
    /* pan offset registers */
    vlo = iw_atten_tab[(IW_ATTENTABSIZE-1)-vs->pan];
    vro = iw_atten_tab[vs->pan];
    if (vs->pan != (IW_ATTENTABSIZE-1) && vs->pan != 0) {
	vlo >>= 1;
	vro >>= 1;
    }
    if (cs->pitch_bend == 1024) {
	vs->fc_register = vs->ofc_reg;
    } else {
	vs->fc_register = (USHORT)(((ULONG)vs->ofc_reg * cs->pitch_bend) >> 10);
    }
    /* Set page register for all I-O's. */
    IWL_SET_PAGE(vs->voice);
    if (layer_event != IW_LAYER_KEY_RETRIGGER) {
	/* if there is a pitch envelope, the fc will be set later */
	if (!vs->pep) IWL_OUT_W(SET_FREQUENCY, vs->fc_register);
	if (layer_event == IW_LAYER_KEY_LEGATO) {
	    if (vs->pep) {
		IWL_OUT_W(SET_FREQUENCY, (USHORT)(((ULONG)vs->fc_register * (vs->penv_pitch>>16))>>10));
	    }
	    return(1);
	}
	/* activate voice */
	mode = IWL_SMSI_OFFEN;
#ifdef IW_MODULE_EFFECTS
//	mode |= IWL_SMSI_AEP;
#endif
	if (vs->wave->m_format & IW_WAVE_FORMAT_ROM) mode |= IWL_SMSI_ROM;
	if (vs->wave->m_format & IW_WAVE_FORMAT_ULAW) mode |= IWL_SMSI_ULAW;
	IWL_OUT_B(SET_VOICE_MODE, mode); /* activate voice */
	if (l->velocity_mode == IW_LAYER_TIME) {
	    atten += vs->vel_atten;
	}
	set_volume(atten+vlo, atten+vro, 1);
#ifdef IW_MODULE_EFFECTS
	rev_depth = 0;
	chorus_depth = 0;
	if (vs->channel == 9) {
	    /* percussion's effects are multipled against channel levels */
	    switch (ns->patch->effect1) {
		case IW_PATCH_EFFECT_REVERB:
		    rev_depth = ns->patch->effect1_depth;
		    break;
		case IW_PATCH_EFFECT_CHORUS:
		    chorus_depth = ns->patch->effect1_depth;
		    break;
	    }
	    switch (ns->patch->effect2) {
		case IW_PATCH_EFFECT_REVERB:
		    rev_depth = ns->patch->effect2_depth;
		    break;
		case IW_PATCH_EFFECT_CHORUS:
		    chorus_depth = ns->patch->effect2_depth;
		    break;
	    }
	    rev_depth = VOLUME(rev_depth);
	    chorus_depth = VOLUME(chorus_depth);
	}
	set_effects_volume(atten+cs->effect1_level+rev_depth,atten+cs->effect3_level+chorus_depth, 1);
#endif
    }
    vs->voice_control = 0;
    vs->volume_control = 0;
    w = vs->wave;
    if (w->m_format & IW_WAVE_FORMAT_LOOP) {
	vs->voice_control |= IWL_LPE;
	if (w->m_format & IW_WAVE_FORMAT_BIDIR) {
	    vs->voice_control |= IWL_BLE;
	}
    } else {
	/* non looped sound should generate interrupt when done */
	vs->voice_control |= IWL_IRQE;
    }
    addrs = w->m_start;
    if (!(w->m_format & IW_WAVE_FORMAT_8BIT)) {
	vs->voice_control |= IWL_WT16;
	addrs = iwl_convert_to_16bit(addrs);
    }
    if (w->m_format & IW_WAVE_FORMAT_FORWARD) {
	iwl_set_addr_regs(vs->voice, addrs, SET_ACC_LOW, SET_ACC_HIGH);
    } else {
	vs->voice_control |= IWL_DIR_DEC;
	addr = (addrs<<4) + w->loopend;
	iwl_set_addr_f_regs(vs->voice, addr, SET_ACC_LOW, SET_ACC_HIGH);
    }
    addrs <<= 4;
    addr = addrs + w->loopstart;
    iwl_set_addr_f_regs(vs->voice, addr, SET_START_LOW, SET_START_HIGH);
    addr = addrs + w->loopend;
    iwl_set_addr_f_regs(vs->voice, addr, SET_END_LOW, SET_END_HIGH);

    vs->status = STAT_VOICE_RUNNING;
    if (layer_event != IW_LAYER_KEY_RETRIGGER) {
	/* put new voices at beginning of list so we don't have to walk */
	/* entire list to start each voice */
	iwl_AddNode(&iwl_active_voices, (struct iwl_node RFAR *)vs, 0L, IWL_PREPEND);
    }
    vs->venv_state = BEFORE; /* note hasn't started yet */
    vs->penv_state = BEFORE;
    do_volume_envelope(vs->voice);
    if (vs->status == 0) return(0); /* volume envelope was flat at 0 volume */
    if (vs->pep) do_pitch_envelope(vs->voice);
    /* if channel vibrato or built-in vibrato then start the LFO hardware */
    if (vs->layer->vibrato.depth || cs->vib_depth) {
	set_voice_vibrato(vs);
    }
    if (vs->layer->tremolo.depth) {
	set_voice_tremolo(vs);
    }
    return(1);
}

static int note_legato(int channel, int note)
{
    struct note_status RFAR *ns;
    struct voice_status RFAR *vs;

    for (ns=NS_START; ns != 0; ns = NS_NEXT(ns)) {
	if (ns->channel == channel) {
	    if (ns->status & NOTE_RELEASING) continue;
	    ns->note = note;
	    for (vs = VS_START; vs != 0; vs = VS_NEXT(vs)) {
		if (vs->note != ns) continue;
		trigger_layer(vs, ns, IW_LAYER_KEY_LEGATO);
	    }
	    return(1);
	}
    }
    return(0);
}

static void trigger(
    struct note_status RFAR *ns,
    int channel,
    int note,
    int velocity,
    struct iw_patch RFAR *patch,
    int priority,
    int layer_event)
{
    void (RFAR *callback_addr)(int);
    struct channel_status *cs;
    struct voice_status RFAR *vs;
    struct iw_layer RFAR *l;
    short nvoices_used, voice;
    short layer, i;

    if (layer_event == IW_LAYER_KEY_UP) {
	channel = ns->channel;
	note = ns->note;
	velocity = ns->velocity;
	patch = ns->patch;
	priority = ns->priority;
    } else if (layer_event == IW_LAYER_KEY_DOWN) {
	cs = &iwl_channel_status[channel];
	if (cs->status & CHANNEL_LEGATO) {
	    if (note_legato(channel, note)) return;
	}
    }
    /* loop through all layers, and trigger active layers */
    nvoices_used = 0;
    l = patch->layers;
    for (layer=0; layer < patch->nlayers; layer++, l=l->id.p) {
	/* ignore layers which aren't supposed to play */
	if (l->layer_event != layer_event) continue;
	if (patch->layer_mode == IW_PATCH_LAYER_NONE && layer > 0) break;
	if (patch->layer_mode == IW_PATCH_VELOCITY_SPLIT &&
	    (velocity < l->low_range || velocity > l->high_range)) continue;
	if (patch->layer_mode == IW_PATCH_FREQUENCY_SPLIT &&
	    (note < l->low_range || note > l->high_range)) continue;
#if defined(__BORLANDC__) && NEARCODE == 1
	asm mov word ptr callback_addr, offset stop_note_voice
	asm mov word ptr callback_addr+2, cs
#else
	callback_addr = stop_note_voice;
#endif
	voice = iw_allocate_voice(priority, callback_addr);
	if (voice == IW_NO_MORE_VOICES) continue;
	vs = &voice_status[voice];
	if (nvoices_used == 0 && layer_event == IW_LAYER_KEY_DOWN) {
	    /* find an empty note struct */
	    for (i=0, ns=note_status; i < IW_MAX_VOICES && ns->status; i++, ns++) ;
	    ns->note = (UCHAR)note;
	    ns->channel = (UCHAR)channel;
	    ns->velocity = (UCHAR)velocity;
	    ns->priority = (USHORT)priority;
	    ns->patch = patch; /* each note can be a different patch (drums) */
	    ns->status = NOTE_ACTIVE;
	    iwl_AddNode(&iwl_active_notes, (struct iwl_node RFAR *)ns, 0L, IWL_PREPEND);
	}
	vs->note = ns; /* identify voices which comprise a note */
	vs->channel = (unsigned char)channel;
	vs->layer = l;
	if (trigger_layer(vs, ns, layer_event)) {
	    nvoices_used++;
	}
    }
    if (nvoices_used == 0 && ns && layer_event == IW_LAYER_KEY_DOWN) {
	ns->status = 0;
	iwl_DeleteNode(&iwl_active_notes, (struct iwl_node RFAR *)ns);
    }
    /* voices completely programmed, now trigger via control registers */
    for (vs = VS_START; nvoices_used--; vs = VS_NEXT(vs)) {
	IWL_SET_PAGE(vs->voice);
	IWL_OUT_CONTROL(SET_CONTROL, vs->voice_control);
    }
}

void iw_midi_note_on(int channel, int note, int velocity, struct iw_patch RFAR *patch, int priority)
{
    struct channel_status *cs = &iwl_channel_status[channel];
    struct note_status RFAR *ns, RFAR *nns;

    if (velocity == 0) {
	iw_midi_note_off(channel, note);
	return;
    }
    ENTER;
    if (patch == 0L) {
	patch = cs->patch;
    }
    if (patch == 0L) {
	LEAVE;
	return;
    }
    /* look for mutually exclusive notes, and turn off others in group */
    if (patch->exclusion_mode != IW_PATCH_EXCL_NONE) {
	for (ns=NS_START; ns != 0; ns = nns) {
	    nns = NS_NEXT(ns); /* look ahead in case current node is deleted */
	    if (ns->channel == channel &&
		ns->patch->exclusion_group == patch->exclusion_group) {
		if (patch->exclusion_mode == IW_PATCH_EXCL_SINGLE) {
		    stop_note(ns, 0);
		} else {
		    if (ns->note == note) stop_note(ns, 0);
		}
	    }
	}
    }
    trigger(0, channel, note, velocity, patch, priority, IW_LAYER_KEY_DOWN);
    LEAVE;
}

static void note_off(struct note_status RFAR *ns)
{
    struct voice_status RFAR *vs, RFAR *nvs;

    if (ns->patch->layer_mode != IW_PATCH_LAYER_NONE) {
	trigger(ns, 0, 0, 0, 0, 0, IW_LAYER_KEY_UP);
    }
    for (vs = VS_START; vs != 0; vs = nvs) {
	nvs = VS_NEXT(vs);
	if (vs->note != ns) continue;
	if (vs->status & STAT_VOICE_RELEASING) continue;
	/* mark voice as releasing - even one-shots need to know when to
	   stop envelope and layer retriggering */
	vs->status |= STAT_VOICE_RELEASING;
	if (vs->vep->mode == IW_ENVELOPE_ONE_SHOT) continue;
	iw_adjust_priority(vs->voice, 32);
	ns->status &= ~NOTE_SUSTAIN;
	ns->status |= NOTE_RELEASING;
	/* force envelope to advance to release part */
	vs->status &= ~STAT_VOICE_SUSTAINING;
	if (vs->venv_state <= VAR_RELEASE && vs->venv_index <= vs->ver->nattack) {
	    vs->venv_state = VAR_RELEASE;
	    do_volume_envelope(vs->voice);
	}
	if (vs->pep && vs->pep->mode == IW_ENVELOPE_ONE_SHOT) continue;
	if (vs->pep && vs->penv_state <= VAR_RELEASE && vs->penv_index <= vs->per->nattack) {
	    /* stop any current pitch envelope */
	    iwl_stop_cs_timer(vs->voice);
	    /* restart pitch envelope at release section */
	    vs->penv_state = VAR_RELEASE;
	    vs->penv_in_progress = 0;
	    vs->penv_pitch = vs->penv_next;
	    do_pitch_envelope(vs->voice);
	}
    }
}

void iw_midi_note_off(int channel, int note)
{
    struct note_status RFAR *ns;
    struct channel_status *cs = &iwl_channel_status[channel];

    ENTER;
    if (cs->status & CHANNEL_SUSTAIN) {
	for (ns=NS_START; ns != 0; ns = NS_NEXT(ns)) {
	    if (ns->note == note && ns->channel == channel && !(ns->status & NOTE_RELEASING)) {
		ns->status |= NOTE_SUSTAIN;
	    }
	}
	LEAVE;
	return;
    }
    for (ns=NS_START; ns != 0; ns = NS_NEXT(ns)) {
	if (ns->note == note && ns->channel == channel && !(ns->status & NOTE_RELEASING)) break;
    }
    if (ns) note_off(ns);
    LEAVE;
}

void iw_midi_channel_sustain(int channel, int sustain)
{
    struct channel_status *cs = &iwl_channel_status[channel];
    struct note_status RFAR *ns, RFAR *nns;

    ENTER;
    if (sustain) {
	cs->status |= CHANNEL_SUSTAIN;
    } else {
	cs->status &= ~CHANNEL_SUSTAIN;
	for (ns=NS_START; ns != 0; ns = nns) {
	    nns = NS_NEXT(ns); /* look ahead in case current node is deleted */
	    if (ns->channel == channel && ns->status & NOTE_SUSTAIN) {
		note_off(ns);
	    }
	}
    }
    LEAVE;
}
void iw_midi_channel_legato(int channel, int legato)
{
    struct channel_status *cs = &iwl_channel_status[channel];

    ENTER;
    if (legato) {
	cs->status |= CHANNEL_LEGATO;
    } else {
	cs->status &= ~CHANNEL_LEGATO;
    }
    LEAVE;
}

void iw_midi_silence_patch_notes(struct iw_patch RFAR *patch)
{
    struct note_status RFAR *ns, RFAR *nns;

    ENTER;
    for (ns=NS_START; ns != 0; ns = nns) {
	nns = NS_NEXT(ns); /* look ahead in case current node is deleted */
	if (ns->patch == patch) {
	    stop_note(ns, 1);
	}
    }
    LEAVE;
}

void iw_midi_patch_removed(struct iw_patch RFAR *patch)
{
    struct channel_status *cs;
    int i;

    ENTER
    iw_midi_silence_patch_notes(patch);
    for (i=0, cs = iwl_channel_status; i < IW_MAX_CHANNELS; i++, cs++) {
	if (cs->patch == patch) cs->patch = 0;
    }
    LEAVE;
}

void iw_midi_change_program(int channel, struct iw_patch RFAR *patch)
{
    struct channel_status *cs = &iwl_channel_status[channel];

    ENTER;
    cs->patch = patch;
    LEAVE;
}

void iw_midi_set_balance(int channel, int pan)
{
    struct channel_status *cs;
    struct voice_status RFAR *vs;

    cs = &iwl_channel_status[channel];
    ENTER;
    cs->pan = pan;
    for (vs = VS_START; vs != 0; vs = VS_NEXT(vs)) {
	if (vs->channel == (UCHAR)channel) {
	    iwl_change_voice_volume(vs->voice);
	}
    }
    LEAVE;
}

void iw_midi_effect_level(int channel, int echan, int level)
{
    struct channel_status *cs;
    struct voice_status RFAR *vs;

    cs = &iwl_channel_status[channel];
    if (level < 0) level = 0;
    if (level > 127) level = 127;
    ENTER;
    if (echan == 1) {
	cs->effect1_level = VOLUME(level);
    } else if (echan == 3) {
	cs->effect3_level = VOLUME(level);
    } else {
	LEAVE;
	return;
    }
    for (vs = VS_START; vs != 0; vs = VS_NEXT(vs)) {
	if (vs->channel == (UCHAR)channel) {
	    iwl_change_voice_volume(vs->voice);
	}
    }
    LEAVE;
}

void iw_midi_all_notes_off(int channel)
{
    struct channel_status *cs;
    struct note_status RFAR *ns, RFAR *nns;

    ENTER;
    cs = &iwl_channel_status[channel];
    if (cs->status & CHANNEL_SUSTAIN) {
	for (ns=NS_START; ns != 0; ns = NS_NEXT(ns)) {
	    if (ns->channel == channel && !(ns->status & NOTE_RELEASING)) {
		ns->status |= NOTE_SUSTAIN;
	    }
	}
	LEAVE;
	return;
    }
    for (ns=NS_START; ns != 0; ns = nns) {
	nns = NS_NEXT(ns); /* look ahead in case current node is deleted */
	if (ns->channel == channel) {
	    note_off(ns);
	}
    }
    LEAVE;
}

void iw_midi_all_sounds_off(int channel)
{
    struct note_status RFAR *ns, RFAR *nns;

    ENTER;
    for (ns=NS_START; ns != 0; ns = nns) {
	nns = NS_NEXT(ns); /* look ahead in case current node is deleted */
	if (ns->channel == channel) {
	    stop_note(ns, 0);
	}
    }
    LEAVE;
}

static void set_voice_tremolo(struct voice_status RFAR *vs)
{
    struct channel_status *cs = &iwl_channel_status[vs->channel];
    short depth, bdepth, sweep=0, current_depth;
    short freq;
#define MAX_TDEPTH 90

    bdepth = vs->layer->tremolo.depth;
    if (cs->trem_depth) {
	if (bdepth < 0) {
	    depth = -((cs->trem_depth * (MAX_TDEPTH+bdepth)/MAX_TDEPTH)-bdepth);
	} else {
	    depth = bdepth + (cs->trem_depth * (MAX_TDEPTH-bdepth)/MAX_TDEPTH);
	}
	if (bdepth) {
	    sweep = vs->layer->tremolo.sweep * (127-cs->trem_depth) >> 7;
	}
    } else {
	depth = bdepth;
	if (depth) {
	    sweep = vs->layer->tremolo.sweep;
	}
    }
    if (sweep == 0) {
	current_depth = depth<<5;
    } else {
	current_depth = 0;
    }
    if (vs->status & STAT_VOICE_TREMOLO_RUNNING) {
	if (depth) {
	    iwl_lfo_change_depth(vs->voice, IW_LFO_TREMOLO, depth);
	} else {
	    iwl_lfo_shutdown(vs->voice, IW_LFO_TREMOLO);
	    vs->status &= ~STAT_VOICE_TREMOLO_RUNNING;
	}
    } else if (depth) {
	freq = vs->layer->tremolo.freq;
	if (freq == 0) freq = 523;
	iwl_lfo_setup(vs->voice, IW_LFO_TREMOLO,
		      freq, current_depth,
		      depth, sweep,
		      vs->layer->tremolo.shape);
	vs->status |= STAT_VOICE_TREMOLO_RUNNING;
    }
}

void iw_midi_set_tremolo(int channel, int value)
{
    struct voice_status RFAR *vs;
    struct channel_status *cs;
    
    ENTER;
    cs = &iwl_channel_status[channel];
    cs->trem_depth = value;
    for (vs = VS_START; vs != 0; vs = VS_NEXT(vs)) {
	if (vs->channel == (UCHAR)channel) {
	    if (vs->layer->tremolo.depth) {
		set_voice_tremolo(vs);
	    }
	}
    }
    LEAVE;
}
