/***************************************************************************
*	NAME:  EFFECTS.C $Revision: 1.4 $
**	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: effects.c $
* Revision 1.4  1995/11/22 04:16:21  mleibow
* Fixed tremolo and restarting of effects.
* Revision 1.3  1995/10/26 14:59:26  mleibow
* Added mutes for software mixer controls.
* Revision 1.2  1995/10/13 17:17:22  mleibow
* Implemented base level GS effects for reverb and chorus.
* Revision 1.1  1995/02/23 11:06:59  unknown
* Initial revision
***************************************************************************/

#include <dos.h>

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

#ifdef IW_MODULE_EFFECTS

// voice's effects processor enabled (use own buffer)
#define EV_EPE	(1 << 0)
// voice's alternate effects path enabled (feedback)
#define EV_AEP  (1 << 1)
// effect voice in use
#define EV_IN_USE  (1 << 2)
// effect voice stopping
#define EV_STOPPING (1 << 3)

// max voices per channel
#define NUM_CHAN_MACROS 8
#define NUM_EFFECTS_CHANNELS 2

struct iwl_effect_voice {
    unsigned short flags;
    unsigned short voice;
    unsigned short read_buffer;
    unsigned long buffer_size;
    unsigned long buffer_addr;
    unsigned long read_pos;
    unsigned long write_pos;
    unsigned short lpan;
    unsigned short rpan;
    unsigned short evol;
    unsigned short vctl;
    unsigned short tctl;
    unsigned short vdepth;
    unsigned short tdepth;
    unsigned short volume;
    unsigned char write_mask;
    unsigned char m_write_mask;
};

#define EFFECT_IN_USE   (1 << 0)
#define EFFECT_STOPPING (1 << 1)
static struct current_effect {
    unsigned long dram_base; // memory allocated for channel
    int chan_effect; // current effect playing on channel
    int next_effect;
    unsigned short flags;
    unsigned char emask; // current voice-mask for effect playing on channel
} current_effect[NUM_EFFECTS_CHANNELS];

struct iwl_effect {
    int nvoices;
    struct iwl_effect_voice ev[6];
} iwl_chan1_effects[NUM_CHAN_MACROS] =
{
  { 6, // room 1
    {
      { EV_EPE|EV_AEP, 0, 0, 0x0834, 0, 0, 0x0832, 0x0de0, 0x0410, 0xfc30, 0xc05f, 0x4000, 0x0000, 0x0000, 0xf000, 0x01, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0x0a38, 0, 0, 0x0a36, 0x0410, 0x0de0, 0xfb40, 0xc0be, 0x4000, 0x0000, 0x0000, 0xf000, 0x02, 0x0 },
      { EV_EPE|EV_AEP, 2, 2, 0x0ccc, 0, 0, 0x0cca, 0x0de0, 0x0410, 0xfa10, 0xc11d, 0x4000, 0x0000, 0x0000, 0xf000, 0x04, 0x0 },
      { EV_EPE|EV_AEP, 3, 3, 0x0e28, 0, 0, 0x0e26, 0x0410, 0x0de0, 0xf970, 0xc17c, 0x4000, 0x0000, 0x0000, 0xf000, 0x08, 0x0 },
      { EV_EPE|EV_AEP, 4, 4, 0x0ef4, 0, 0, 0x0ef2, 0x0de0, 0x0410, 0xf900, 0xc1db, 0x4000, 0x0000, 0x0000, 0xf000, 0x10, 0x0 },
      { EV_EPE|EV_AEP, 5, 5, 0x1138, 0, 0, 0x1136, 0x0410, 0x0de0, 0xf800, 0xc23a, 0x4000, 0x0000, 0x0000, 0xf000, 0x20, 0x0 },
    }
  },
  { 6, // room 2
    {
      { EV_EPE|EV_AEP, 0, 0, 0x0834, 0, 0, 0x0832, 0x0de0, 0x0410, 0xfc30, 0xc05f, 0x4000, 0x0000, 0x0000, 0xf000, 0x01, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0x0a38, 0, 0, 0x0a36, 0x0410, 0x0de0, 0xfb40, 0xc0be, 0x4000, 0x0000, 0x0000, 0xf000, 0x02, 0x0 },
      { EV_EPE|EV_AEP, 2, 2, 0x0ccc, 0, 0, 0x0cca, 0x0de0, 0x0410, 0xfa10, 0xc11d, 0x4000, 0x0000, 0x0000, 0xf000, 0x04, 0x0 },
      { EV_EPE|EV_AEP, 3, 3, 0x0e28, 0, 0, 0x0e26, 0x0410, 0x0de0, 0xf970, 0xc17c, 0x4000, 0x0000, 0x0000, 0xf000, 0x08, 0x0 },
      { EV_EPE|EV_AEP, 4, 4, 0x0ef4, 0, 0, 0x0ef2, 0x0de0, 0x0410, 0xf900, 0xc1db, 0x4000, 0x0000, 0x0000, 0xf000, 0x10, 0x0 },
      { EV_EPE|EV_AEP, 5, 5, 0x1138, 0, 0, 0x1136, 0x0410, 0x0de0, 0xf800, 0xc23a, 0x4000, 0x0000, 0x0000, 0xf000, 0x20, 0x0 },
    }
  },
  { 6, // room 3
    {
      { EV_EPE|EV_AEP, 0, 0, 0x0834, 0, 0, 0x0832, 0x0de0, 0x0410, 0xfc30, 0xc05f, 0x4000, 0x0000, 0x0000, 0xf000, 0x01, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0x0a38, 0, 0, 0x0a36, 0x0410, 0x0de0, 0xfb40, 0xc0be, 0x4000, 0x0000, 0x0000, 0xf000, 0x02, 0x0 },
      { EV_EPE|EV_AEP, 2, 2, 0x0ccc, 0, 0, 0x0cca, 0x0de0, 0x0410, 0xfa10, 0xc11d, 0x4000, 0x0000, 0x0000, 0xf000, 0x04, 0x0 },
      { EV_EPE|EV_AEP, 3, 3, 0x0e28, 0, 0, 0x0e26, 0x0410, 0x0de0, 0xf970, 0xc17c, 0x4000, 0x0000, 0x0000, 0xf000, 0x08, 0x0 },
      { EV_EPE|EV_AEP, 4, 4, 0x0ef4, 0, 0, 0x0ef2, 0x0de0, 0x0410, 0xf900, 0xc1db, 0x4000, 0x0000, 0x0000, 0xf000, 0x10, 0x0 },
      { EV_EPE|EV_AEP, 5, 5, 0x1138, 0, 0, 0x1136, 0x0410, 0x0de0, 0xf800, 0xc23a, 0x4000, 0x0000, 0x0000, 0xf000, 0x20, 0x0 },
    }
  },
  { 6, // hall 1
    {
      { EV_EPE|EV_AEP, 0, 0, 0x0834, 0, 0, 0x0832, 0x0de0, 0x0410, 0xfc30, 0xc05f, 0x4000, 0x0000, 0x0000, 0xf000, 0x01, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0x0a38, 0, 0, 0x0a36, 0x0410, 0x0de0, 0xfb40, 0xc0be, 0x4000, 0x0000, 0x0000, 0xf000, 0x02, 0x0 },
      { EV_EPE|EV_AEP, 2, 2, 0x0ccc, 0, 0, 0x0cca, 0x0de0, 0x0410, 0xfa10, 0xc11d, 0x4000, 0x0000, 0x0000, 0xf000, 0x04, 0x0 },
      { EV_EPE|EV_AEP, 3, 3, 0x0e28, 0, 0, 0x0e26, 0x0410, 0x0de0, 0xf970, 0xc17c, 0x4000, 0x0000, 0x0000, 0xf000, 0x08, 0x0 },
      { EV_EPE|EV_AEP, 4, 4, 0x0ef4, 0, 0, 0x0ef2, 0x0de0, 0x0410, 0xf900, 0xc1db, 0x4000, 0x0000, 0x0000, 0xf000, 0x10, 0x0 },
      { EV_EPE|EV_AEP, 5, 5, 0x1138, 0, 0, 0x1136, 0x0410, 0x0de0, 0xf800, 0xc23a, 0x4000, 0x0000, 0x0000, 0xf000, 0x20, 0x0 },
    }
  },
  { 6, // hall 2
    {
      { EV_EPE|EV_AEP, 0, 0, 0x0834, 0, 0, 0x0832, 0x0de0, 0x0410, 0xfc30, 0xc05f, 0x4000, 0x0000, 0x0000, 0xf000, 0x01, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0x0a38, 0, 0, 0x0a36, 0x0410, 0x0de0, 0xfb40, 0xc0be, 0x4000, 0x0000, 0x0000, 0xf000, 0x02, 0x0 },
      { EV_EPE|EV_AEP, 2, 2, 0x0ccc, 0, 0, 0x0cca, 0x0de0, 0x0410, 0xfa10, 0xc11d, 0x4000, 0x0000, 0x0000, 0xf000, 0x04, 0x0 },
      { EV_EPE|EV_AEP, 3, 3, 0x0e28, 0, 0, 0x0e26, 0x0410, 0x0de0, 0xf970, 0xc17c, 0x4000, 0x0000, 0x0000, 0xf000, 0x08, 0x0 },
      { EV_EPE|EV_AEP, 4, 4, 0x0ef4, 0, 0, 0x0ef2, 0x0de0, 0x0410, 0xf900, 0xc1db, 0x4000, 0x0000, 0x0000, 0xf000, 0x10, 0x0 },
      { EV_EPE|EV_AEP, 5, 5, 0x1138, 0, 0, 0x1136, 0x0410, 0x0de0, 0xf800, 0xc23a, 0x4000, 0x0000, 0x0000, 0xf000, 0x20, 0x0 },
    }
  },
  { 6, // plate
    {
      { EV_EPE|EV_AEP, 0, 0, 0x0834, 0, 0, 0x0832, 0x0de0, 0x0410, 0xfc30, 0xc05f, 0x4000, 0x0000, 0x0000, 0xf000, 0x01, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0x0a38, 0, 0, 0x0a36, 0x0410, 0x0de0, 0xfb40, 0xc0be, 0x4000, 0x0000, 0x0000, 0xf000, 0x02, 0x0 },
      { EV_EPE|EV_AEP, 2, 2, 0x0ccc, 0, 0, 0x0cca, 0x0de0, 0x0410, 0xfa10, 0xc11d, 0x4000, 0x0000, 0x0000, 0xf000, 0x04, 0x0 },
      { EV_EPE|EV_AEP, 3, 3, 0x0e28, 0, 0, 0x0e26, 0x0410, 0x0de0, 0xf970, 0xc17c, 0x4000, 0x0000, 0x0000, 0xf000, 0x08, 0x0 },
      { EV_EPE|EV_AEP, 4, 4, 0x0ef4, 0, 0, 0x0ef2, 0x0de0, 0x0410, 0xf900, 0xc1db, 0x4000, 0x0000, 0x0000, 0xf000, 0x10, 0x0 },
      { EV_EPE|EV_AEP, 5, 5, 0x1138, 0, 0, 0x1136, 0x0410, 0x0de0, 0xf800, 0xc23a, 0x4000, 0x0000, 0x0000, 0xf000, 0x20, 0x0 },
    }
  },
  { 2, // delay
    {
      { EV_EPE|EV_AEP, 0, 0, 0x25c0, 0, 0, 0x25be, 0x0800, 0x0800, 0xe7c0, 0xc05f, 0x4000, 0x0000, 0x0000, 0xf000, 0x01, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0x0508, 0, 0, 0x0506, 0x0410, 0x0de0, 0xfda0, 0xc0be, 0x4000, 0x0000, 0x0000, 0x0000, 0x02, 0x0 },
    }
  },
  { 2, // panning delay
    {
      { EV_EPE|EV_AEP, 0, 0, 0x24c0, 0, 0, 0x24be, 0xfff0, 0x0000, 0xf000, 0xc05f, 0x4000, 0x0000, 0x0000, 0xfff0, 0x02, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0x24c0, 0, 0, 0x24be, 0x0000, 0xfff0, 0xf000, 0xc0be, 0x4000, 0x0000, 0x0000, 0xfff0, 0x01, 0x0 },
    }
  },
};

struct iwl_effect iwl_chan2_effects[NUM_CHAN_MACROS] =
{
  { 2, // chorus 1
    {
      { EV_EPE, 0, 0, 0x52c, 0, 0, 0x52a, 0x0de0, 0x0410, 0, 0xc02f, 0xc1db, 0x0050, 0x0333, 0xfb00, 0x0, 0x0 },
      { EV_EPE, 1, 1, 0xa2c, 0, 0, 0xa2a, 0x0410, 0x0de0, 0, 0xd02f, 0xd1db, 0x0050, 0x0333, 0xfff0, 0x0, 0x0 },
    }
  },
  { 2, // chorus 2
    {
      { EV_EPE|EV_AEP, 0, 0, 0x22c, 0, 0, 0x22a, 0x0de0, 0x0410, 0xe010, 0xc05f, 0xc05f, 0x05f3, 0x0eff, 0xfff0, 0x2, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0x22c, 0, 0, 0x22a, 0x0410, 0x0de0, 0xe010, 0xd05f, 0xd05f, 0x05f3, 0x0eff, 0xfff0, 0x1, 0x0 },
    }
  },
  { 2, // chorus 3
    {
      { EV_EPE|EV_AEP, 0, 0, 0x52c, 0, 0, 0x52a, 0x1290, 0x0290, 0xe010, 0xc020, 0xc143, 0x0050, 0x0eff, 0xfff0, 0x2, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0x6e8, 0, 0, 0x6e6, 0x0290, 0x1290, 0xe010, 0xd020, 0xd143, 0x0050, 0x0eff, 0xfff0, 0x1, 0x0 },
    }
  },
  { 2, // chorus 4
    {
      { EV_EPE|EV_AEP, 0, 0, 0x1a8, 0, 0, 0x1a6, 0x0de0, 0x0410, 0xf000, 0xc05f, 0xc05f, 0x04ef, 0x0eff, 0xfff0, 0x2, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0x24c, 0, 0, 0x24a, 0x0410, 0x0de0, 0xf000, 0xd05f, 0xd05f, 0x04ef, 0x0eff, 0xfff0, 0x1, 0x0 },
    }
  },
  { 2, // feedback chorus
    {
      { EV_EPE|EV_AEP, 0, 0, 0xa2c, 0, 0, 0xa2a, 0x3520, 0x0010, 0xf000, 0xc02f, 0xc03f, 0x00fe, 0x0eff, 0xf000, 0x1, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0xa2c, 0, 0, 0xa2a, 0x0010, 0x3520, 0xf000, 0xd03f, 0xd03f, 0x00fe, 0x0eff, 0xfff0, 0x2, 0x0 },
    }
  },
  { 2, // flanger
    {
      { EV_EPE|EV_AEP, 0, 0, 0x820, 0, 0, 0x81e, 0x3520, 0x0010, 0xfb00, 0xc007, 0xc007, 0x00fe, 0x0eff, 0xfb00, 0x1, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0x820, 0, 0, 0x81e, 0x0010, 0x3520, 0xfb00, 0xd007, 0xd007, 0x00fe, 0x0eff, 0xfff0, 0x2, 0x0 },
    }
  },
  { 2, // short delay
    {
      { EV_EPE, 0, 0, 0x052c, 0, 0, 0x052a, 0x1a90, 0x0130, 0, 0xc007, 0xc007, 0x0000, 0x0000, 0xfb00, 0x0, 0x0 },
      { EV_EPE, 1, 1, 0x14b0, 0, 0, 0x14ae, 0x0130, 0x1a90, 0, 0xd007, 0xd007, 0x0000, 0x0000, 0xfff0, 0x0, 0x0 },
    }
  },
  { 2, // short delay (fb)
    {
      { EV_EPE|EV_AEP, 0, 0, 0x052c, 0, 0, 0x052a, 0x1a90, 0x0130, 0xf800, 0xc007, 0xc007, 0x0000, 0x0000, 0xfb00, 0x2, 0x0 },
      { EV_EPE|EV_AEP, 1, 1, 0x14b0, 0, 0, 0x14ae, 0x0130, 0x1a90, 0xf800, 0xd007, 0xd007, 0x0000, 0x0000, 0xfff0, 0x1, 0x0 },
    }
  },
};

short iwl_effects_master_volume = 100;
short iwl_effects_mute_atten = 0;
struct iwl_vol_handler {
  struct iwl_node n;
  void (RFAR *handler)(void);
};
#define MAX_VOL_HANDLERS 1
static struct iwl_vol_handler vol_handlers[MAX_VOL_HANDLERS];
static struct iwl_list vol_handler_list;


unsigned char iw_get_effects_mask(int chan)
{
    return(current_effect[chan].emask);
}

int iwl_effects_init(struct load_kernel_cfg RFAR *cfg)
{
    struct current_effect *ce;
    unsigned long memsize, chan_memsize;
    int i, j, chan;
    struct iwl_effect *ef;

    /* initialize list of requesters of master-effects-volume changes */
    iwl_InitList(&vol_handler_list);
    for (i=0; i<MAX_VOL_HANDLERS; i++) {
	vol_handlers[i].handler = 0;
    }
    /* initialize current_effect data structure for all channels */
    ce = current_effect;
    for (chan=0; chan < NUM_EFFECTS_CHANNELS; chan++, ce++) {
	ce->next_effect = 0;
	ce->emask = 0;
	ce->dram_base = 0;
	ce->flags = 0;
    }
    current_effect[0].chan_effect = 4; // default reverb - macro 4
    current_effect[1].chan_effect = 2; // default chorus - macro 2
    if (cfg->effects == 0) goto failure;
    /* find maximum amount of memory used by channel effects */
    ce = current_effect;
    for (chan=0; chan < NUM_EFFECTS_CHANNELS; chan++, ce++) {
	if (chan == 0) {
	    ef = iwl_chan1_effects;
	} else if (chan == 1) {
	    ef = iwl_chan2_effects;
	}
	chan_memsize = 0;
	for (i=0; i < NUM_CHAN_MACROS; i++, ef++) {
	    memsize = 0;
	    for (j = 0; j < ef->nvoices; j++) {
		ef->ev[j].flags &= (EV_EPE|EV_AEP); /* initialize voice flags */
		memsize += ef->ev[j].buffer_size;
		if (ef->ev[j].vdepth) memsize += 2048; // add 2K buffer for LFO
	    }
	    if (memsize > chan_memsize) chan_memsize = memsize;
	}
	/* try and allocate enough InterWave DRAM for effects */
	ce->dram_base = iw_malloc(chan_memsize);
	if (ce->dram_base == 0) goto failure;
    }
    ce = current_effect;
    for (chan=0; chan < NUM_EFFECTS_CHANNELS; chan++, ce++) {
	/* allocate & program all voices for channel effect */
	iw_start_effect(chan, ce->chan_effect);
    }
    return(IW_OK);

failure: /* not enough, or no dram for effects */
    iwl_mixer_remove_effects_node();
    ce = current_effect;
    for (chan=0; chan < NUM_EFFECTS_CHANNELS; chan++, ce++) {
	if (ce->dram_base != 0) {
	    iw_free(ce->dram_base);
	    ce->dram_base = 0;
	}
    }
    return(IW_OK);
}

static void iw_stop_effect_2(USHORT chan)
{
    struct current_effect *ce;
    struct iwl_effect *ef;
    struct iwl_effect_voice *ev;
    iwl_lfo_struct lfo_block;
    int i;

    ce = &current_effect[chan];
    if (chan == 0) {
	ef = &iwl_chan1_effects[ce->chan_effect];
    } else if (chan == 1) {
	ef = &iwl_chan2_effects[ce->chan_effect];
    }
    ENTER;
    ev = ef->ev;
    for (i = 0; i < ef->nvoices; i++, ev++) {
	if (ev->flags & EV_IN_USE && ev->flags & EV_STOPPING) {
	    IWL_SET_PAGE(ev->voice);
	    // stop voice & volume
	    IWL_OUT_CONTROL(SET_CONTROL, IWL_STOP);
	    IWL_OUT_CONTROL(SET_VOLUME_CONTROL, IWL_STOP);
	    // set volume to 0
	    IWL_OUT_W(SET_VOLUME, 0x0);
	    // disable effects (AEP|EPE)
	    IWL_OUT_B(SET_VOICE_MODE, IWL_SMSI_OFFEN);
	    IWL_OUT_B(SET_EFFECT_ACC_SEL, 0);
	    // kill vibrato
	    iwu_memset(&lfo_block, 0, sizeof(lfo_block));
	    iwl_lfo_write_block(ev->voice, IW_LFO_VIBRATO, &lfo_block);
	    // kill tremolo
	    iwl_lfo_write_block(ev->voice, IW_LFO_TREMOLO, &lfo_block);
	    // LFO initial value (frequency)
	    IWL_OUT_B(SET_FREQ_LFO, 0);
	    // LFO initial value (volume)
	    IWL_OUT_B(SET_VOLUME_LFO, 0);
	    // give voice back to free voice pool.
	    iw_free_voice(ev->voice);
	    ev->flags &= ~(EV_STOPPING|EV_IN_USE);
	}
    }
    ce->emask = 0;
    ce->flags = 0;
    LEAVE;
}

/* iw_stop_effect() will stop the current effect on the given channel. */
/* if abrupt is true, then effects will cease immediately possibly */
/* causing a click or pop if the synth is not silent. */
/* if abrubt is false, then the caller MUST call iwl_set_cs_timer() */
/* to set a 50ms timer.  On the callback from the timer, enough time */
/* will have occured to let the effects voices become quiet.  However, */
/* They are still summing data from the signal generators, and this MUST */
/* be stopped so that other processes using the chip can use local memory */
/* without the side effects. Call iw_stop_effect_2() to start the */
/* stage 2 shutdown.  New effects cannot be started until after the stage 2 */
/* shutdown is complete. */
/* iw_unload_kernel() must call this with abrupt until version 2 of the */
/* kernel which will have a 2-phase unload. */
int iw_stop_effect(int chan, int abrupt)
{
    struct current_effect *ce;
    struct iwl_effect *ef;
    struct iwl_effect_voice *ev;
    int i, wait=0;

    ce = &current_effect[chan];
    if (!(ce->flags & EFFECT_IN_USE)) return(0);
    if (chan == 0) {
	ef = &iwl_chan1_effects[ce->chan_effect];
    } else if (chan == 1) {
	ef = &iwl_chan2_effects[ce->chan_effect];
    }
    ev = ef->ev;
    ENTER;
    for (i = 0; i < ef->nvoices; i++, ev++) {
	if (ev->flags & EV_IN_USE) {
	    IWL_SET_PAGE(ev->voice);
	    IWL_OUT_W(SET_LEFT_FINAL_OFFSET, 0xfff0);
	    IWL_OUT_W(SET_RIGHT_FINAL_OFFSET, 0xfff0);
	    IWL_OUT_W(SET_EFFECT_FINAL_VOLUME, 0x0000);
	    ev->flags |= EV_STOPPING;
	    wait=ev->voice;
	}
    }
    if (abrupt) iw_stop_effect_2((USHORT)chan);
    LEAVE;
    return(wait);
}

static void do_start_effect(int chan, int effect)
{
    struct current_effect *ce;
    struct iwl_effect *ef;
    struct iwl_effect_voice *ev;
    unsigned long loc, loc1;
    iwl_lfo_struct lfo_block;
    int i, j;
    unsigned char mode;

    ce = &current_effect[chan];
    ce->chan_effect = effect;
    ce->emask = 0;
    if (ce->dram_base == 0) return; // effects not on.  ROM only?
    if (chan == 0) {
	ef = &iwl_chan1_effects[effect];
    } else if (chan == 1) {
	ef = &iwl_chan2_effects[effect];
    }
    ce->flags |= EFFECT_IN_USE;
    /* go through voice list, assign and clear DRAM, set flags */
    loc = ce->dram_base;
    ev = ef->ev;
    for (i = 0; i < ef->nvoices; i++, ev++) {
	ev->flags |= EV_IN_USE;
	ev->buffer_addr = loc;
	loc += ev->buffer_size;
	if (ev->vdepth) loc += 2048; // add 2K buffer for LFO
	ev->voice = iw_allocate_effect_voice();
	ce->emask |= 1 << (ev->voice & 7);
    }
    /* set base I/O addr */
    IWL_OUT_W(SET_DRAM_LOW, (unsigned short)ce->dram_base);
    IWL_OUT_B(SET_DRAM_HIGH, (unsigned char)((ce->dram_base >> 16)&0xFF));
    /* select local memory 16-bit I/O port */
    OS_OUTPORTB(iwl_register_select, IW_LMSBAI);
    for (; loc != ce->dram_base; loc -= 2) {
	OS_OUTPORTW(iwl_data_low, 0x0000); /* clear memory */
    }
    /* all voices allocated - set up effects mask for accumulators */
    ev = ef->ev;
    for (i = 0; i < ef->nvoices; i++, ev++) {
	ev->m_write_mask = 0;
	for (j=0; j < ef->nvoices; j++) {
	    if ((1 << j) & ev->write_mask) {
		ev->m_write_mask |= (1 << (ef->ev[j].voice & 7));
	    }
	}
    }
    /* program voices */
    ev = ef->ev;
    for (i = 0; i < ef->nvoices; i++, ev++) {
	// select voice
	IWL_SET_PAGE(ev->voice);
	// mode select
	mode = IWL_SMSI_OFFEN;
	if (ev->flags & EV_EPE) mode |= IWL_SMSI_EPE;
	if (ev->flags & EV_AEP) mode |= IWL_SMSI_AEP;
	IWL_OUT_B(SET_VOICE_MODE, mode);
	// calculate start of buffer
	if (ev->flags & EV_EPE) {
	    loc = ev->buffer_addr;
	} else {
	    loc = ef->ev[ev->read_buffer].buffer_addr;
	}
	// program start pos
	loc1 = iwl_convert_to_16bit(loc);
	iwl_set_addr_regs(ev->voice, loc1, SET_START_LOW, SET_START_HIGH);
	// program read pos
	loc1 = iwl_convert_to_16bit(ev->read_pos + loc);
	iwl_set_addr_regs(ev->voice, loc1, SET_ACC_LOW, SET_ACC_HIGH);
	// program end pos
	loc1 = iwl_convert_to_16bit(ev->buffer_size + loc - 2);
	if (ev->vdepth) loc1 += 1024; // add 2K buffer for LFO
	iwl_set_addr_regs(ev->voice, loc1, SET_END_LOW, SET_END_HIGH);
	// program write pos
	if (ev->flags & EV_EPE) {
	    loc1 = iwl_convert_to_16bit(ev->write_pos + loc);
	    iwl_set_addr_regs(ev->voice, loc1, SET_EFFECT_LOW, SET_EFFECT_HIGH);
	}
	// left pan to full atten
	IWL_OUT_W(SET_LEFT_FINAL_OFFSET, 0xFFF0);
	IWL_OUT_W(SET_LEFT_OFFSET, 0xFFF0);
	// right pan to full atten
	IWL_OUT_W(SET_RIGHT_FINAL_OFFSET, 0xFFF0);
	IWL_OUT_W(SET_RIGHT_OFFSET, 0xFFF0);
	// voice control
	IWL_OUT_CONTROL(SET_CONTROL, IWL_WT16|IWL_STOP);
	// volume control
	IWL_OUT_CONTROL(SET_VOLUME_CONTROL, IWL_STOP|IWL_ENPCM);
	// volume (ok to direct set volume when pan regs are at full atten)
	IWL_OUT_W(SET_VOLUME, ev->volume);
	// left pan
	IWL_OUT_W(SET_LEFT_FINAL_OFFSET, ev->lpan);
	// right pan
	IWL_OUT_W(SET_RIGHT_FINAL_OFFSET, ev->rpan);
	if (ev->flags & EV_AEP) {
	    // effects feedback volume
	    IWL_OUT_W(SET_EFFECT_FINAL_VOLUME, ev->evol);
	    IWL_OUT_W(SET_EFFECT_VOLUME, ev->evol);
	} else {
	    // effects feedback volume
	    IWL_OUT_W(SET_EFFECT_FINAL_VOLUME, 0);
	    IWL_OUT_W(SET_EFFECT_VOLUME, 0);
	}
	// frequency
	IWL_OUT_W(SET_FREQUENCY, 0x0400);
	// LFO initial value (frequency)
	IWL_OUT_B(SET_FREQ_LFO, 0);
	// LFO initial value (volume)
	IWL_OUT_B(SET_VOLUME_LFO, 0);
	// LFO block - freq
	iwu_memset(&lfo_block, 0, sizeof(lfo_block));
	lfo_block.control = ev->vctl;
	lfo_block.depth_1 = ev->vdepth;
	iwl_lfo_write_block(ev->voice, IW_LFO_VIBRATO, &lfo_block);
	// LFO block - volume
	lfo_block.control = ev->tctl;
	lfo_block.depth_1 = ev->tdepth;
	iwl_lfo_write_block(ev->voice, IW_LFO_TREMOLO, &lfo_block);
	// effects accumulator mask
	if (ev->flags & EV_AEP) {
	    IWL_OUT_B(SET_EFFECT_ACC_SEL, ev->m_write_mask);
	}
    }
    /* start voices */
    ev = ef->ev;
    for (i = 0; i < ef->nvoices; i++, ev++) {
	IWL_SET_PAGE(ev->voice);
	IWL_OUT_CONTROL(SET_CONTROL, IWL_LPE|IWL_WT16);
    }
}

static void stage_2_stop_and_restart(unsigned short chan)
{
    struct current_effect *ce = &current_effect[chan];

    ENTER;
    iw_stop_effect_2(chan);
    do_start_effect(chan, ce->next_effect);
    LEAVE;
}

void iw_start_effect(int chan, int effect)
{
    struct current_effect *ce = &current_effect[chan];
    unsigned short voice;

    ENTER;
    /* check to see if already in the process of stopping an effect */
    if (ce->flags & EFFECT_STOPPING) {
	ce->next_effect = effect;
	/* timer will kick in when last effect stopped and start next effect */
	LEAVE;
	return;
    }
    if (ce->flags & EFFECT_IN_USE) {
	/* if next effect is same as current effect, why bother? */
	if (ce->chan_effect == effect) {
	    LEAVE;
	    return;
	}
	/* stop current effect on channel */
	if ((voice=iw_stop_effect(chan, 0)) != 0) {
	    /* effect was already running on this channel.  set up timer */
	    /* to do stage 2 and also start next effect */
	    ce->next_effect = effect;
	    ce->flags |= EFFECT_STOPPING;
	    iwl_set_cs_timer(voice, 20, stage_2_stop_and_restart, chan);
	    LEAVE;
	    return;
	}
    }
    do_start_effect(chan, effect);
    LEAVE;
}

short iw_effects_get_master_volume(void)
{
    return(iwl_effects_master_volume);
}

void iw_effects_master_volume(short volume)
{
    struct iwl_vol_handler RFAR *n;

    if (volume < 0) volume = 0;
    if (volume > 127) volume = 127;
    iwl_effects_master_volume = volume;
    for (n = (struct iwl_vol_handler RFAR *)vol_handler_list.head;
	 n != 0;
	 n = (struct iwl_vol_handler RFAR *)n->n.next
	)
	(*(n->handler))();
}

void iw_effects_mute(short mute)
{
    struct iwl_vol_handler RFAR *n;

    if (mute) {
	iwl_effects_mute_atten = IW_MAX_VOLUME;
    } else {
	iwl_effects_mute_atten = 0;
    }
    for (n = (struct iwl_vol_handler RFAR *)vol_handler_list.head;
	 n != 0;
	 n = (struct iwl_vol_handler RFAR *)n->n.next
	)
	(*(n->handler))();
}

int iw_effects_add_volume_handler(
  void (RFAR *handler)(void)) /* pointer to caller's handler routine */
{
    int i, rc = IW_NO_MORE_HANDLERS;

    for (i=0; i<MAX_VOL_HANDLERS; i++) {
	 if (vol_handlers[i].handler == 0) break;
    }
    if (i != MAX_VOL_HANDLERS) {
	vol_handlers[i].handler = handler;
	iwl_AddNode(&vol_handler_list, &(vol_handlers[i].n), 0, IWL_APPEND);
	rc = IW_OK;
    }
    return(rc);
}
#endif
