/***************************************************************************
*       NAME:  SYNTHC.C $Revision: 1.29 $
**      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."
****************************************************************************/

#define USE_IRQS 0x20
// #define USE_IRQS 0x00

#include <dos.h>
#include "sbosdefs.h"
#include "sbosdata.h"
#include "fm.h"
#include "shared.h"
#include "synproto.h"
#include "synth.h"
#include "hardware.h"
#ifdef DEBUG
#include "debug.h"
#endif
#include "mpu401.h"

extern SHARED shared;
extern SYN_VOICE *voice_data;
extern unsigned char iw_syn_flags[];
extern unsigned char pending_to_do[];
extern unsigned int reg_voice_sel;
extern unsigned int reg_index;
extern unsigned int reg_dram_io;
extern unsigned int reg_data_high;
extern unsigned int reg_data_low;
extern MPU_CHANNELS mpu_channels[16];
//extern MPU_CHANNELS *mpu_channels;
extern int rom_flag;

extern unsigned int asm_calc_fc(void);

extern PFV done_release_callback;
extern PFV start_release_callback;

extern unsigned int car_voice;
extern unsigned int mod_voice;

// ASM function prototypes
extern void syn_delay(void);
extern unsigned long convert_to_16bit(long);
#ifdef DEBUG
extern void add_to_debug_table(void);
#endif

/****************************************************************************

FUNCTION DEFINITION:
proc_midi - midi interrupt routine - not implemented

*/
//void proc_midi()
//{
//}

/****************************************************************************

FUNCTION DEFINITION:
out_w_delay -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void pascal out_w_delay(unsigned char reg,unsigned char val)
{
    outp(reg_index,reg);
    outp(reg_data_high,val);
    syn_delay();
    outp(reg_data_high,val);
}

/****************************************************************************

FUNCTION DEFINITION:
proc_voice - Voice Interrupt Routine

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void proc_voice(void)
{
unsigned char irq_source;
unsigned char voice;
unsigned int ivoice;
unsigned long volume_ignore;
unsigned long voice_bit;
unsigned char cur_vctrl;
unsigned int cur_vpos;
unsigned char total_points;
unsigned char current;
unsigned char flags;
SYN_VOICE *vptr;
SYN_ENVELOPE *eptr;

/* clear the ignore flags. These flags are needed because we get lots of */
/* 'double' interrupts. This will only allow one interrupt per voice */
volume_ignore = 0L;

/* The chip has a fifo (sort of) of all pending wave table irq's. You  */
/* should stay here & service ALL pending waveform IRQ's before returning. */
while (TRUE)
    {
    outp(reg_index,GET_IRQV_NC);
    irq_source = inp(reg_data_high);

    ivoice = voice = irq_source & 0x1F;    /* pick off the voice # */
    flags = iw_syn_flags[ivoice];
    irq_source &= (VOICE_VOLUME_IRQ | VOICE_WAVE_IRQ);/* isolate the irq bits */

    if (irq_source == (VOICE_VOLUME_IRQ | VOICE_WAVE_IRQ))/* negative logic */
        {
        goto done_proc_voice;                    /* No pending irqs left ... */
        }

    voice_bit = 1L << (long)voice;

    // select the proper voice ....
    outp(reg_voice_sel,voice);

    cur_vctrl = 0;

    if ((irq_source & (unsigned char)VOICE_WAVE_IRQ) == 0)
        {
        out_w_delay(SET_VOLUME_CONTROL,0x03);

        vptr = &voice_data[ivoice];
        eptr = &vptr->envelope;
        goto shut_voice_down;
        }

    if ((irq_source & VOICE_VOLUME_IRQ) == 0)
        {
// Carefull .... We don't preserve all the bits here. Don't need any
//  except ROLLOVER and we don't use it ....
        out_w_delay(SET_VOLUME_CONTROL,0x03);

        if (!(volume_ignore & voice_bit))
            {
            volume_ignore |= voice_bit;
            vptr = &voice_data[ivoice];
            eptr = &vptr->envelope;

// Get current pos so we know which way to go ....
//           outp(reg_index,GET_VOLUME);
//           cur_vpos = inpw(reg_data_low) >> 4;

            current = vptr->cur_env_point;
            if (flags & SYN_INJECT_POINT)
                {
                flags &= ~SYN_INJECT_POINT;
                iw_syn_flags[ivoice] = flags;
                outp(reg_index,GET_VOLUME);
                cur_vpos = inpw(reg_data_low)>>4;
                goto repeat_point;
                }
            else
                {
                outp(reg_index,SET_VOLUME);
                outpw(reg_data_low,eptr->level[current]<<4);
                cur_vpos = eptr->level[current];
                }

            total_points = eptr->nattacks+eptr->nreleases-1;

            vptr->start_env=vptr->end_env;

            while(TRUE)
                {
                if (current == total_points)
                    {
                    // Done with envelope ....
shut_voice_down:
                    vptr->cur_env_point = 0;
                    flags &= ~SYN_PLAYING;
                    iw_syn_flags[ivoice] = flags;

                    outp(reg_index,GET_CONTROL);
                    out_w_delay(SET_CONTROL,(inp(reg_data_high)&0x04)|0x03);

                    outp(reg_index,SET_VOLUME);
                    outpw(reg_data_low,4<<4);    // Shut down all volume ....

                    _AX=ivoice;
                    done_release_callback();
                    break;
                    }
    
                vptr->cur_env_point++;
                current++;

                // See if we are at sustain point ....
                if (current == eptr->nattacks)
                    {
                    if (flags & SYN_SUSTAINING)
                        break;
                    _AX=ivoice;
                    start_release_callback();
                    }

                // If this level is the same as the last level, skip this point
                if ((eptr->level[current] == eptr->level[current-1]))
                    continue;

repeat_point:
                outp(reg_index,SET_VOLUME_RATE);
                outp(reg_data_high,eptr->rate[current]);
        
                if (cur_vpos < eptr->level[current])
                    {  // Ramping up ...
                    outp(reg_index,SET_VOLUME_END);
                    }
                else
                    {  // Ramping down    
                    cur_vctrl |= 0x40;
                    outp(reg_index,SET_VOLUME_START);
                    }
                vptr->end_env = (char)(eptr->level[current] >>4);
                outp(reg_data_high,(vptr->end_env));
    
                cur_vctrl |= USE_IRQS;        // Enable IRQ
                out_w_delay(SET_VOLUME_CONTROL,cur_vctrl);
                break;
                }
            }
        }
    outp(reg_index,GET_IRQV);
    irq_source = inp(reg_data_high);
    }
done_proc_voice:
}

/****************************************************************************

FUNCTION DEFINITION:
syn_prog_frequency -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void pascal syn_prog_frequency(int voice, int channel)
{
unsigned int loc_index = reg_index;
unsigned int loc_data_low = reg_data_low;
unsigned int fc;
unsigned long begin;
unsigned int upper,lower;

// Actually program hardware here ....
_ECX = voice_data[voice].wave_info.magic_num;
_EAX = voice_data[voice].frequency;
_EBX = _ECX;    // cause loading frequency blows EBX .....
fc = asm_calc_fc();

if((channel >= 0) && (channel < 16))
    {

// ADD KEYBOARD SCALING HERE .......

/** 
 ** Account for Pitch Bending in MPU401 mode 
 **/
    fc = (((long)mpu_channels[channel].fc_bend_mult * (long)fc) >> 10);
    }

outp(reg_voice_sel,voice);
outp(loc_index,SET_FREQUENCY);
outpw(loc_data_low,fc);

if ((fc == 0) && (!syn_voices_running()))
    {
    // calc offset to where voice will point
    voice_data[voice].old_wave_num = -1;
    begin = shared.sbosdata->dram_stopped_voice+voice;
    lower = ADDR_LOW( begin );
    upper = ADDR_HIGH( begin );    

    outp(loc_index,SET_ACC_LOW);
    outpw(loc_data_low,lower);
    outp(loc_index,SET_ACC_HIGH);
    outpw(loc_data_low,upper);

    outp(loc_index,GET_CONTROL);
    out_w_delay(SET_CONTROL,(inp(reg_data_high)&0x04)|0x03);
    }
}

/****************************************************************************

FUNCTION DEFINITION:
syn_get_frequency - Returns the set frequency for a voice

EXPECTS:
  voice to be a valid voice number from 0 to NUM_VOICES

RETURNS:
  long - frequency

*/
unsigned long pascal syn_get_frequency(int voice)
{
    return voice_data[voice].frequency;
}

/****************************************************************************

FUNCTION DEFINITION:
syn_set_frequency - Set frequency for a specific voice

EXPECTS:
  voice to be a valid voice number from 0 to NUM_VOICES

*/
void pascal syn_set_frequency(int voice,unsigned long frequency)
{
    voice_data[voice].frequency = frequency;
}

/****************************************************************************

FUNCTION DEFINITION:
syn_get_envelope - Copies the current envelope assigned for the specified 
  voice

DESCRIPTION:

EXPECTS:
  voice to be a valid voice number from 0 to NUM_VOICES

MODIFIES:
  envelope structure

*/
void pascal syn_get_envelope(int voice,SYN_ENVELOPE *envelope)
{
    *envelope = voice_data[voice].envelope;
}

/****************************************************************************

FUNCTION DEFINITION:
PeekString -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void PeekString(unsigned int port, unsigned long location,
           void far *data,unsigned int length,int rom_read)
{
unsigned char temp;
unsigned int loc_index = reg_index;

    // Force access to read from ROM
    if (rom_read)
        {
        outp(loc_index,IW_LMCI);
        temp = inp(reg_data_high);
        temp |= 0x02;
        outp(reg_data_high,temp);
        }

    outp(loc_index,SET_DRAM_LOW);
    outpw(reg_data_low,LSW(location));    /* 16 bits */
    outp(loc_index,SET_DRAM_HIGH);
    outp(reg_data_high,LSB(MSW(location)));    /* 8 bits */

    asm mov dx,port;
    asm mov cx,length;
    asm les di,data
    asm cld
    asm rep insb

    // Force access back to RAM
    if (rom_read)
        {
        outp(loc_index,IW_LMCI);
        temp = inp(reg_data_high);
        temp &= ~0x02;
        outp(reg_data_high,temp);
        }

}

/****************************************************************************

FUNCTION DEFINITION:
upload_envelopes -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void upload_envelopes(int wave_num,SYN_ENVELOPE *envelope, int type)
{
unsigned char *ptr;
unsigned long start;
unsigned int level[6];
int i;

if(type != UPLOAD_MPU) /** if in mpu mode, don't erase vol levels **/
    for (i=0;i<6;i++)
        level[i] = envelope->level[i];

ptr = (unsigned char *)envelope;

start = shared.sbosdata->dram_mpu_info;
start += wave_num*sizeof(SYN_ENVELOPE);

PeekString(reg_dram_io,start,ptr,sizeof(SYN_ENVELOPE),rom_flag);

if(type != UPLOAD_MPU) /** if in mpu mode, don't erase vol levels **/
    {
    for (i=0;i<6;i++)
        envelope->level[i] = level[i];
    }
}

/****************************************************************************

FUNCTION DEFINITION:
stop_volume - Stops the current voice's accumulator from moving

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void pascal stop_volume(void)
{
unsigned char cur_vctrl;

outp(reg_index,GET_VOLUME_CONTROL);
cur_vctrl = inp(reg_data_high);
if (!(cur_vctrl & 0x03))
    out_w_delay(SET_VOLUME_CONTROL,0x03);
}

/****************************************************************************

FUNCTION DEFINITION:
start_volume -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void pascal start_volume(int voice)
{
unsigned int loc_index=reg_index;
unsigned int cur_vpos;
unsigned char cur_vctrl=0;
unsigned int new_level;
SYN_VOICE *vptr;
WAVE_INFO *wptr;
unsigned char temp_rate;

wptr = &voice_data[voice].wave_info;
vptr = &voice_data[voice];

outp(reg_voice_sel,voice);

// Re-attack note ....
stop_volume();

// Get current location of volume ramp
outp(loc_index,GET_VOLUME);
cur_vpos = ((unsigned int)inpw(reg_data_low)) >> 4U;

if (cur_vpos < (ATTACK_FLOOR<<4))
    {
    // its below the floor, so move it up ...
    cur_vpos = ATTACK_FLOOR<<4;
    outp(loc_index,SET_VOLUME);
    outpw(reg_data_low,ATTACK_FLOOR<<8);  // Start at the floor

    outp(loc_index,SET_VOLUME_START);
    outp(reg_data_high,ATTACK_FLOOR);    // Start at the floor
    }
vptr->start_env = ATTACK_FLOOR;
vptr->end_env = (char)(vptr->envelope.level[0]>>4);

vptr->cur_env_point = 0;        // Beginning of envelope ramps ..

if ((iw_syn_flags[voice] & SYN_MPU401_EMUL) != 0)
    {
    if ((vptr->end_env < vptr->start_env)&&(vptr->patch_type == TYPE_PERC))
       temp_rate = 0;
    else if ((wptr->mode & 0x80) && (vptr->patch_type != TYPE_BASIC))
       temp_rate = 0x3f;
    else
        {
        temp_rate = ((vptr->envelope.rate[0] & 0x3F) >> 1);
        temp_rate |= vptr->envelope.rate[0] & 0xC0;
        }
    }
else
    temp_rate = vptr->envelope.rate[vptr->cur_env_point];

new_level = vptr->envelope.level[0];

outp(loc_index,SET_VOLUME_RATE);
outp(reg_data_high,temp_rate);

// Pick which register to change ....
if (new_level > cur_vpos)
    outp(loc_index,SET_VOLUME_END);
else
    {
    outp(loc_index,SET_VOLUME_START);
    cur_vctrl |= 0x40;        // ramp down 
    }

outp(reg_data_high,(new_level>>4));//upper 8 only ....

cur_vctrl |= USE_IRQS;        // Enable IRQ
// This starts the voice - the envelope is licked!
out_w_delay(SET_VOLUME_CONTROL,cur_vctrl);
}

/****************************************************************************

FUNCTION DEFINITION:
syn_set_envelope -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void pascal syn_set_envelope(int voice,SYN_ENVELOPE *envelope)
{
    // Not running, just copy it ....
    voice_data[voice].envelope = *envelope;
}

/****************************************************************************

FUNCTION DEFINITION:
upload_wave_info -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void upload_wave_info(int wave_num,WAVE_INFO *wave)
{
unsigned char *ptr;
unsigned long start;

ptr = (unsigned char *)wave;

start = shared.sbosdata->dram_wave_info + wave_num*sizeof(WAVE_INFO);

PeekString(reg_dram_io,start,ptr,sizeof(WAVE_INFO),rom_flag);
}

/****************************************************************************

FUNCTION DEFINITION:
syn_get_voice_atten - returns voice's current attenuation

DESCRIPTION:

EXPECTS:

MODIFIES:

RETURNS:
  uint - 12 bit attenuation

*/
unsigned int pascal syn_get_voice_atten(int voice)
{
    WAVE_INFO *wptr;

    wptr = &voice_data[voice].wave_info;
    return wptr->atten << 5; /** make a 12 bit attenuation out of it **/
}

/****************************************************************************

FUNCTION DEFINITION:
syn_get_iw_atten - Gets the left and right attenuation from a voice's offset
  registers

SEE ALSO:
  syn_set_iw_atten

*/
void pascal syn_get_iw_atten(int voice, unsigned int *left, unsigned int *right)
{
outp(reg_voice_sel,voice);

outp(reg_index,GET_LEFT_OFFSET);
*left  = inpw(reg_data_low);

outp(reg_index,GET_RIGHT_OFFSET);
*right = inpw(reg_data_low);
}

/****************************************************************************

FUNCTION DEFINITION:
syn_set_iw_atten -

DESCRIPTION:

SEE ALSO:
  syn_get_iw_atten

*/
void pascal syn_set_iw_atten(int voice, unsigned int left, unsigned int right)
{
unsigned int loc_index;
unsigned int loc_data_low;
unsigned int temp;

_BX = loc_index = reg_index;
_CX = loc_data_low = reg_data_low;

outp(reg_voice_sel,voice);

outp(loc_index, GET_VOICE_MODE);
temp = inp(reg_data_high);
temp |= 0x20;
outp(loc_index, SET_VOICE_MODE);
outp(reg_data_high, temp);

outp(loc_index,SET_LEFT_OFFSET);
outpw(loc_data_low,left);
outp(loc_index,SET_LEFT_FINAL_OFFSET);
outpw(loc_data_low,left);

outp(loc_index,SET_RIGHT_OFFSET);
outpw(loc_data_low,right);
outp(loc_index,SET_RIGHT_FINAL_OFFSET);
outpw(loc_data_low,right);
}

/****************************************************************************

FUNCTION DEFINITION:
syn_set_instrument -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:
  syn_get_instrument

RETURNS:

*/
int pascal syn_set_instrument(int voice,int type,int instrument)
{
int start;
int wave_num;
unsigned int *map;
SYN_VOICE *vptr;
unsigned char test_freq;
unsigned char perc_inst=0;

vptr = &voice_data[voice];

if (type != CUR_BANK)
    vptr->patch_type = type;
if (instrument != CUR_INST)
    vptr->instrument = instrument;

// Load wave data from proper place (based on freq,type and instrument#)
switch(vptr->patch_type)
    {
    case TYPE_BASIC:
    default:
        start = shared.patch_info.start_basic;
        map = shared.instrument_maps.basic_map;
        break;
    case TYPE_MELODIC:
        start = shared.patch_info.start_melodics;
        map = shared.instrument_maps.melodic_map;
        break;
    case TYPE_PERC:
        start = shared.patch_info.start_percs;
        map = shared.instrument_maps.perc_map;
        break;
    }

// OK. We have liftoff .....
wave_num = start + map[vptr->instrument];

if (vptr->patch_type == TYPE_MELODIC)
    {
    // OK. Now figure out based on freq range
    // shift by 10 because its *1024. Shift by 5 to split keybrd
    test_freq = (vptr->frequency >> 15);
    while(shared.wave_data[wave_num].end_freq != 0xff)
        {
        if (shared.wave_data[wave_num].end_freq > test_freq)
            break;
        wave_num++;
        }
    }

vptr->wave_num = wave_num;    // wave current wave num for later ...

upload_wave_info(wave_num,&(vptr->wave_info));

if (type == TYPE_PERC)
    PeekString(reg_dram_io,shared.sbosdata->dram_perc_map+(vptr->instrument*2),&perc_inst,1,rom_flag);
return((int)perc_inst);
}

/****************************************************************************

FUNCTION DEFINITION:
syn_get_instrument -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:
  syn_set_instrument

RETURNS:

*/
void pascal syn_get_instrument(int voice, int *bank, int *instrument)
{
SYN_VOICE *vptr;

vptr = &voice_data[voice];
*bank = vptr->patch_type;
*instrument = vptr->instrument;
}

/****************************************************************************

FUNCTION DEFINITION:
syn_upload_envelopes -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void pascal syn_upload_envelopes(int voice, int type)
{
SYN_VOICE *vptr;
vptr = &voice_data[voice];

if(type == UPLOAD_MPU) /** upload only if MPU-401 **/
    upload_envelopes(vptr->wave_num,&vptr->envelope,UPLOAD_MPU);
else if(vptr->patch_type == TYPE_PERC) /** Force perc envelopes **/
    {
    vptr->envelope.rate[0] = 0x3f;    // attack rate
    vptr->envelope.rate[1] = 0xC4;    // decay rate
    vptr->envelope.rate[2] = 0xC4;    // release rate
    vptr->envelope.level[2] = ATTACK_FLOOR<<4;
    vptr->envelope.nattacks = 2;    
    vptr->envelope.nreleases = 1;
    }
}

/****************************************************************************

FUNCTION DEFINITION:
syn_play_voice -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void pascal syn_play_voice(int voice,int type)
{
unsigned int loc_index=reg_index;
unsigned long phys_begin,begin;
unsigned long phys_start_loop,start_loop;
unsigned long phys_end_loop,end_loop;
unsigned int upper,lower;
unsigned char mode;
WAVE_INFO *wptr;
SYN_VOICE *vptr;
unsigned char frac;
unsigned int cur_vpos;
unsigned char temp;
int rom_index;

if (type == MPU401_EMUL)
    iw_syn_flags[voice] |= SYN_MPU401_EMUL;
else
    iw_syn_flags[voice] &= ~SYN_MPU401_EMUL;

outp(reg_voice_sel,voice);

outp(loc_index,GET_CONTROL);
out_w_delay(SET_CONTROL,(inp(reg_data_high)&0x04)|0x03);

wptr = &voice_data[voice].wave_info;
vptr = &voice_data[voice];

    phys_begin = wptr->start_pos >> 8;        // Upper 24 bits are start address
    phys_start_loop = phys_begin + wptr->beg_offset;
    phys_start_loop = phys_start_loop+(((wptr->start_pos>>4L)&0x03)*(65536L));
    phys_end_loop = phys_begin + wptr->end_offset;
    phys_end_loop = phys_end_loop+(((wptr->start_pos>>6L)&0x03)*(65536L));

if (wptr->mode & 0x10)        // backward looping
    {
    phys_begin = phys_end_loop;
    }

if(shared.status & STAT_ROM)
    {
    // If actually using ROM image data, the upper 2 bits are the ROM series
    // select.
    rom_index = phys_begin >> 22L;
    phys_begin &= 0x3fffffL;
    phys_begin |= shared.rom_addr[rom_index];
    }
if (wptr->mode & 0x01)    // 16 bit patch ....
    begin = convert_to_16bit( phys_begin );
else
    begin = phys_begin;

lower = ADDR_LOW( begin );
upper = ADDR_HIGH( begin );

// Save and restore volumes because moving accumulator can cause audible clicks
outp(loc_index,GET_VOLUME);
cur_vpos = inpw(reg_data_low);

outp(loc_index,SET_VOLUME);
outpw(reg_data_low,4<<4);

outp(loc_index,SET_ACC_LOW);
outpw(reg_data_low,lower);
outp(loc_index,SET_ACC_HIGH);
outpw(reg_data_low,upper);
temp = (unsigned char)(begin>>22L);
outp(loc_index,SET_UPPER_ADDR);
outp(reg_data_high,temp);

outp(loc_index, GET_VOICE_MODE);
temp = inp(reg_data_high);
if (rom_flag)
    temp |= 0x80;
else
    temp &= ~0x80;
outp(loc_index, SET_VOICE_MODE);
outp(reg_data_high, temp);

outp(loc_index,SET_VOLUME);
outpw(reg_data_low,cur_vpos);

if (vptr->wave_num != vptr->old_wave_num)
    {
// MUST SET old_wave_num to something invalid in reset code ....
    vptr->old_wave_num = vptr->wave_num;
//    phys_start_loop = phys_begin + wptr->beg_offset;
//    phys_start_loop = phys_start_loop+(((wptr->start_pos>>4L)&0x03)*(65536L));
//    phys_end_loop = phys_begin + wptr->end_offset;
//    phys_end_loop = phys_end_loop+(((wptr->start_pos>>6L)&0x03)*(65536L));
    frac = wptr->start_pos & 0x0f;

    if (wptr->mode & 0x01)    // 16 bit patch ....
        {
        start_loop = convert_to_16bit( phys_start_loop );
        end_loop = convert_to_16bit( phys_end_loop );
        }
    else
        {
        start_loop = phys_start_loop;    
        end_loop = phys_end_loop;    
        }

    lower = ADDR_LOW( start_loop );
    upper = ADDR_HIGH( start_loop );    

    outp(loc_index,SET_START_LOW);
    outpw(reg_data_low,lower);
    outp(loc_index,SET_START_HIGH);
    outpw(reg_data_low,upper);

    lower = ADDR_LOW( end_loop );
    lower = lower | (((unsigned int)frac)<<5);    // put fraction back on
    upper = ADDR_HIGH( end_loop );    

    outp(loc_index,SET_END_LOW);
    outpw(reg_data_low,lower);
    outp(loc_index,SET_END_HIGH);
    outpw(reg_data_low,upper);
    }

mode = 0;

if (wptr->mode & 0x01)        // 8 or 16 bit patch
    mode |= 0x04;    
if (wptr->mode & 0x04)        // looping enabled
    mode |= 0x08;
else
    mode |= 0x20;             // Enable IRQ for NON-looping samples
if (wptr->mode & 0x08)        // Bidirectional looping
    mode |= 0x10;
if (wptr->mode & 0x10)        // backward looping
    mode |= 0x40;

// Start note from beginning ....
start_volume(voice);

iw_syn_flags[voice] |= SYN_PLAYING;

// OFF to the races !!!

out_w_delay(SET_CONTROL,mode);
}

/****************************************************************************

FUNCTION DEFINITION:
syn_stop_voice -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void pascal syn_stop_voice(int voice)
{
unsigned int loc_index=reg_index;
SYN_VOICE *vptr;
unsigned int cur_vpos;

vptr = &voice_data[voice];
// Done with envelope ....
outp(reg_voice_sel,voice);
vptr->cur_env_point = 0;
iw_syn_flags[voice] &= ~(SYN_PLAYING|SYN_INJECT_POINT);

outp(loc_index,GET_CONTROL);
out_w_delay(SET_CONTROL,(inp(reg_data_high)&0x04)|0x03);

stop_volume();
//out_w_delay(SET_VOLUME_CONTROL,0x03);

outp(loc_index,SET_VOLUME_RATE);
outp(reg_data_high,0x3f);

outp(loc_index,SET_VOLUME_START);
outp(reg_data_high,FLOOR_VOL);

outp(loc_index,GET_VOLUME);
cur_vpos = inpw(reg_data_low) >> 4;
if(cur_vpos > 0x45)
    out_w_delay(SET_VOLUME_CONTROL,0x40);    // down, no IRQS
}

/****************************************************************************

FUNCTION DEFINITION:
syn_voices_running -

DESCRIPTION:

RETURNS:
  boolean - True if one of the channel's operators are running

*/
int  pascal syn_voices_running(void)
{
if ((iw_syn_flags[car_voice] & SYN_PLAYING) ||
    (iw_syn_flags[mod_voice] & SYN_PLAYING))
    return(TRUE);
else
    return(FALSE);
}

/****************************************************************************

FUNCTION DEFINITION:
syn_start_release -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void pascal syn_start_release(int voice)
{
unsigned int loc_index=reg_index;
SYN_VOICE *vptr;
unsigned char current;
unsigned int cur_vpos;
unsigned char cur_vctrl;

vptr = &voice_data[voice];

//if ((iw_syn_flags[voice] & SYN_PLAYING) && (vptr->patch_type != TYPE_PERC))
if (iw_syn_flags[voice] & SYN_PLAYING)
    {
    // If already in the release phase(s), let it continue
    if (vptr->cur_env_point <= vptr->envelope.nattacks)
        {
        current = vptr->envelope.nattacks;
        vptr->cur_env_point = current;

        outp(reg_voice_sel,voice);

        out_w_delay(SET_VOLUME_CONTROL,0x03);

        iw_syn_flags[voice] &= ~(SYN_INJECT_POINT);

        outp(loc_index,GET_VOLUME);
        cur_vpos = inpw(reg_data_low) >> 4;
        cur_vctrl = 0;

        outp(loc_index,SET_VOLUME_RATE);
        outp(reg_data_high,vptr->envelope.rate[current]);
        
        if (cur_vpos < vptr->envelope.level[current])
            {  // Ramping up ...
            outp(loc_index,SET_VOLUME_END);
            }
        else
            {  // Ramping down    
            cur_vctrl |= 0x40;
            outp(loc_index,SET_VOLUME_START);
            }
        vptr->end_env = vptr->envelope.level[current] >>4;
        outp(reg_data_high,(vptr->end_env));
    
        cur_vctrl |= USE_IRQS;        // Enable IRQ
        out_w_delay(SET_VOLUME_CONTROL,cur_vctrl);
        }
    }
}

/****************************************************************************

FUNCTION DEFINITION:
syn_get_sustain -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
int pascal syn_get_sustain(int voice)
{
return (voice_data[voice].wave_info.mode & 0x60);
}

/****************************************************************************

FUNCTION DEFINITION:
syn_set_mpu_sustain -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
#ifdef MPU401
void pascal syn_set_mpu_sustain(int voice)
{
WAVE_INFO *wptr;

wptr = &voice_data[voice].wave_info;

if(wptr->mode & 0x20)
    iw_syn_flags[voice] |= SYN_SUSTAINING;
else
    iw_syn_flags[voice] &= ~SYN_SUSTAINING;

}
#endif

/****************************************************************************

FUNCTION DEFINITION:
syn_set_sustain -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void pascal syn_set_sustain(int voice,int flag)
{
if (flag)
    iw_syn_flags[voice] |= SYN_SUSTAINING;
else
    iw_syn_flags[voice] &= ~SYN_SUSTAINING;

}

/****************************************************************************

FUNCTION DEFINITION:
reset_synth -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void reset_synth(void)
{
int i;
SYN_VOICE *vptr;
int num_voices;

    if(shared.status & STAT_MPU401_ENABLED)
        num_voices = 32;
    else
        num_voices = 18;

// FM parms only ...
for (i=0;i<18;i++)
    {
    pending_to_do[i] = 0;
    }

// FM and MPU401 structs
for (i=0;i<num_voices;i++)
    {
    vptr = &voice_data[i];
    vptr->old_wave_num = 0xff;
    vptr->patch_type = 0;
    vptr->instrument = 0;
    vptr->cur_env_point = 0;
    iw_syn_flags[i] = 0;

    outp(reg_voice_sel,i);

    outp(reg_index,SET_VOLUME_RATE);
    outp(reg_data_high,0x3f);

    outp(reg_index,SET_VOLUME_START);
    outp(reg_data_high,FLOOR_VOL);

    out_w_delay(SET_VOLUME_CONTROL,0x40);        // Down, no IRQS

    outp(reg_index,GET_CONTROL);
    out_w_delay(SET_CONTROL,(inp(reg_data_high)&0x04)|0x03);
    }
}
