/*
 *  Copyright (c) by Jaroslav Kysela (Perex soft)
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef USE_FLOATING_POINT
#include <math.h>
#endif
#include "pmod.h"

#ifndef USE_FLOATING_POINT
unsigned short xm_period_table[] = {
#include "xm_table.inc"
};
#endif

byte *_seq_buf;
byte seqBuf[ SIZE_OF_SEQBUF ];

int gusDevice;
int seqfd = -1;
int gusMemorySize;


word finetuneNTSC[ 16 ] = {
7895, 7941, 7985, 8046, 8107, 8169, 8232, 8280,
8363, 8413, 8463, 8529, 8581, 8651, 8723, 8757
};

/*
word finetunePAL[ 16 ] = {
7819, 7865, 7909, 7970, 8031, 8093, 8156, 8204,
8287, 8337, 8387, 8453, 8505, 8575, 8647, 8681
};
*/

signed char finetuneTable[ 16 ] = {
0, 16, 32, 48, 64, 80, 96, 112,
-128, -112, -96, -80, -64, -48, -32, -16
};

short mainVolume = -1, songVolume = -1;
short freqType = FTYPE_NOTHING;
short currentPalFreq = 0, palFreq = 0;
short smoothValue = -1;
short rampAutoValue = -1;

dword disableChannels;
dword disabledChannels;

char *shortInstrs = NULL;

void gusInit()
{
  struct GUS_STRU_INFO info;
  int version;
  int temp, mixerfd;
#define MY_VERSION	0x300

  seqfd = open( "/dev/gus", O_WRONLY, 0 );
  switch( errno ) {
    case ENOMEM:
      fatal( "gus: not enough memory - cann't open /dev/gus" );
    case EBUSY:
      fatal( "gus: /dev/gus device busy" );
  }
  if ( seqfd < 0 )
    fatal( "gus: cann't open /dev/gus" );

  if ( ( version = ioctl( seqfd, GUS_IOCTL_VERSION ) ) < 0 )
    fatal( "gus: ioctl - VERSION" );
    
  if ( ( version >> 8 ) != ( MY_VERSION >> 8 ) ||
       ( MY_VERSION & 0xff ) > ( version & 0xff ) )
    fatal( "\
gus: very old or very new version of GUS driver - uncompatible\n\
gus: required version of synth protocol %i.%i (driver reports %i.%i)\n\
", MY_VERSION >> 8, MY_VERSION & 0xff, version >> 8, version & 0xff );

  if ( ioctl( seqfd, GUS_IOCTL_INFO, &info ) < 0 )
    fatal( "gus: ioctl - INFO" );

  if ( ioctl( seqfd, GUS_IOCTL_VOL_TYPE, GUS_VOL_TYPE_LINEAR ) < 0 )
    fatal( "gus: ioctl - VOL_TYPE" );
    
  if ( ioctl( seqfd, GUS_IOCTL_FREQ_TYPE, GUS_FREQ_TYPE_HZ2 ) < 0 )
    fatal( "gus: ioctl - FREQ_TYPE" );
    
  gusMemorySize = info.memory_size;

  if ( mainVolume < 0 )
    {
      if ( ( mixerfd = open( "/dev/mixer", O_RDONLY ) ) >= 0 &&
           ioctl( mixerfd, SOUND_MIXER_READ_SYNTH, &temp ) >= 0 )
        mainVolume = ( ( temp & 0xff ) + ( ( temp >> 8 ) & 0xff ) ) >> 1;
      if ( mixerfd >= 0 ) close( mixerfd );
    }
}

void gusDone()
{
  int temp, mixerfd;

  if ( seqfd < 0 ) return;
  if ( mainVolume >= 0 )
    {
      temp = ( mainVolume & 0xff ) | ( ( mainVolume & 0xff ) << 8 );
      if ( ( mixerfd = open( "/dev/mixer", O_WRONLY ) ) >= 0 )
        ioctl( mixerfd, SOUND_MIXER_WRITE_SYNTH, &temp );
      if ( mixerfd >= 0 ) close( mixerfd );
    }
  close( seqfd );
  seqfd = -1;
}

int gusMemFree()
{
  struct GUS_STRU_INFO info;

  if ( ioctl( seqfd, GUS_IOCTL_INFO, &info ) == -1 )
    fatal( "gus: ioctl failed - MEMAVL" );
  return info.memory_free;
}

void gusStopAllChannels()
{
  int i;
  
  for ( i = 0; i < songChannels; i++ )
    GUS_DO_VS_CURRENT_VOL( i, 0 );
  GUS_DO_WAIT( 1 );
  for ( i = 0; i < songChannels; i++ )
    GUS_DO_VS_VOICE_STOP( i );
  write( seqfd, seqBuf, _seq_buf - seqBuf );
  GUS_DO_WAIT( 1 );
  if ( ioctl( seqfd, GUS_IOCTL_FLUSH ) < 0 )
    dprintf( "gusStopAllChannels: flush error\n" );	/* flush queue */
}

void gusDownload( FILE *in, int instrument, int sample )
{
  struct SAMPLE *smp = songInstrs[ instrument ] -> samples[ sample ];
  struct GUS_STRU_DOWNLOAD gs;
  long l;

  if ( smp -> type == SMP_TYPE_16BIT && 
       ( ( smp -> length & 1 ) != 0 ||
         ( smp -> lstart & 1 ) != 0 ||
         ( smp -> lend & 1 ) != 0 ) )
    fatal( "gusDownload: unaligned 16-bit sample" );

  if ( gusMemFree() < smp -> length )
    {
      fseek( in, smp -> length, SEEK_CUR );
      smp -> used = -1;
      smp -> length = 0;
      wprintf( "Skipping instrument %d - no gus memory.\n", instrument + 1 );
      return;
    }

  if ( moduleType == MODULE_MOD )
    {
      smp -> length -= 2;
      if ( smp -> lstart > 1 ) smp -> lstart -= 2; else smp -> lstart = 0;
      if ( smp -> lend > 1 ) smp -> lend -= 2; else smp -> lend = 0;
      getc( in );
      getc( in );
    }

  if ( smp -> lend > smp -> length ) smp -> lend = smp -> length;

  gs.voice_cntrl_reg = 
    ( smp -> lend > 2 && smp -> lstart < smp -> lend ) ? GUS_WAVE_LOOP : 0;
  gs.voice_cntrl_reg |= smp -> type & SMP_TYPE_UNSIGNED ? GUS_WAVE_INVERT : 0;
  gs.voice_cntrl_reg |= smp -> type & SMP_TYPE_16BIT ? GUS_WAVE_16BIT : 0;
  gs.voice_cntrl_reg |= smp -> type & SMP_TYPE_PINGPONG_LOOP ? GUS_WAVE_BIDIRECTIONAL : 0;
  
  gs.length = smp -> length;
  gs.loop_start = smp -> lstart;
  gs.loop_end = smp -> lend > 0 ? smp -> lend : 0;
  if ( gs.loop_end > gs.length ) gs.loop_end = gs.length;

  gs.sample_ptr = malloc( smp -> length + ( gs.voice_cntrl_reg & GUS_WAVE_16BIT ? 2 : 1 ) );
  if ( !gs.sample_ptr )
    fatal( "gusDownload: memory alloc\n" );

  if ( gs.sample_ptr == NULL )
    fatal( "gus: alloc err" );

  if ( gs.voice_cntrl_reg & GUS_WAVE_16BIT )
    {
      if ( fread( gs.sample_ptr, 1, smp -> length, in ) != smp -> length )
        fatal( "gus: short 16-bit sample" );
      if ( smp -> type & SMP_TYPE_DELTA )
        {
          signed short old = 0, new;
        
          for ( l = 0; l < smp -> length; l += 2 )
            {
              new = (signed short)( gs.sample_ptr[ l ] | ( gs.sample_ptr[ l + 1 ] << 8 ) ) + old;
              gs.sample_ptr[ l ] = new & 0xff;
              gs.sample_ptr[ l + 1 ] = ( new >> 8 ) & 0xff;
              old = new;
            }
        }
    }
   else
    {
      if ( ( l = fread( gs.sample_ptr, 1, smp -> length, in ) ) != smp -> length )
        {
          byte b;
          
          wprintf( "gus: short sample (%i != %i)\n", l, smp -> length );
          if ( l <= 0 )
            {
              smp -> used = -1;
              smp -> length = 0;
              return;
            }          
          b = gs.sample_ptr[ l - 1 ];
          for ( ; l < smp -> length; l++ )
            gs.sample_ptr[ l ] = b;
        }
      if ( smp -> type & SMP_TYPE_DELTA )
        {
          signed char old = 0;
        
          for ( l = 0; l < smp -> length; l++ )
            (signed char)gs.sample_ptr[ l ] = old = (signed char)gs.sample_ptr[ l ] + old;
        }
    }

  if ( gs.voice_cntrl_reg & GUS_WAVE_16BIT )
    {
      gs.sample_ptr[ smp -> length ] = gs.sample_ptr[ smp -> length - 2 ];
      gs.sample_ptr[ smp -> length + 1 ] = gs.sample_ptr[ smp -> length - 1 ];
    }
   else
    gs.sample_ptr[ smp -> length ] = gs.sample_ptr[ smp -> length - 1 ];
    
  if ( rangeIn( shortInstrs, instrument + 1 ) )	/* short instrument */
    {
      if ( !( gs.voice_cntrl_reg & GUS_WAVE_16BIT ) )
        {
          if ( smp -> type & SMP_TYPE_UNSIGNED )
            {
              for ( l = 0; l < smp -> length >> 1; l++ )
                (byte)gs.sample_ptr[ l ] = 
                  ( (byte)gs.sample_ptr[ l << 1 ] + 
                  (byte)gs.sample_ptr[ ( l << 1 ) + 1 ] ) / 2;
            }
           else
            {
              for ( l = 0; l < smp -> length >> 1; l++ )
                (signed char)gs.sample_ptr[ l ] = 
                  ( (signed char)gs.sample_ptr[ l << 1 ] + 
                  (signed char)gs.sample_ptr[ ( l << 1 ) + 1 ] ) / 2;
            }
          gs.sample_ptr[ smp -> length >> 1 ] = gs.sample_ptr[ ( smp -> length >> 1 ) - 1 ];
          smp -> freqC4 >>= 1;
        }
       else
        {
          gs.voice_cntrl_reg &= ~GUS_WAVE_16BIT;	/* convert to 8 bit */
          smp -> type &= ~SMP_TYPE_16BIT;
          for ( l = 0; l < smp -> length >> 1; l++ )
            gs.sample_ptr[ l << 1 ] = gs.sample_ptr[ ( l << 1 ) + 1 ];
        }
      smp -> length >>= 1;
      smp -> lstart >>= 1;
      smp -> lend >>= 1;
      gs.length >>= 1;
      gs.loop_start >>= 1;
      gs.loop_end >>= 1;
    }

  if ( !( gs.voice_cntrl_reg & GUS_WAVE_16BIT ) && 
       gs.voice_cntrl_reg & GUS_WAVE_LOOP )
    {
      if ( smp -> type & SMP_TYPE_UNSIGNED )
        {
          gs.sample_ptr[ gs.length ] =
          gs.sample_ptr[ gs.loop_end ] = gs.sample_ptr[ gs.loop_start ];
          if ( gs.loop_end - gs.loop_start > 100 )
            (byte)gs.sample_ptr[ gs.loop_end - 1 ] =
              ( (byte)gs.sample_ptr[ gs.loop_end - 2 ] +
                (byte)gs.sample_ptr[ gs.loop_end ] ) >> 1;
          if ( gs.loop_end - gs.loop_start > 200 )
            (byte)gs.sample_ptr[ gs.loop_end - 2 ] =
              ( (byte)gs.sample_ptr[ gs.loop_end - 3 ] +
                (byte)gs.sample_ptr[ gs.loop_end - 1 ] ) >> 1;
        }
       else
        {
          gs.sample_ptr[ gs.length ] =
          gs.sample_ptr[ gs.loop_end ] = gs.sample_ptr[ gs.loop_start ];
          if ( gs.loop_end - gs.loop_start > 100 )
            (signed char)gs.sample_ptr[ gs.loop_end - 1 ] =
              ( (signed char)gs.sample_ptr[ gs.loop_end - 2 ] +
                (signed char)gs.sample_ptr[ gs.loop_end ] ) / 2;
          if ( gs.loop_end - gs.loop_start > 200 )
            (signed char)gs.sample_ptr[ gs.loop_end - 2 ] =
              ( (signed char)gs.sample_ptr[ gs.loop_end - 3 ] +
                (signed char)gs.sample_ptr[ gs.loop_end - 1 ] ) / 2;
        }
      if ( gs.length == gs.loop_end ) gs.length++;
    }

#if 0
  {
    static int smpNum = 0;
    int xx;

    dprintf( "\nsmp %i\n", smpNum++ );
    dprintf( "  %s %s %s %s\n", 
  	gs.voice_cntrl_reg & GUS_WAVE_16BIT ? "16bit" : "8bit",
  	gs.voice_cntrl_reg & GUS_WAVE_INVERT ? "unsigned" : "signed",
  	gs.voice_cntrl_reg & GUS_WAVE_LOOP ? "loop" : "no-loop",
  	gs.voice_cntrl_reg & GUS_WAVE_BIDIRECTIONAL ? "pingpong" : "no-pingpong" );
    dprintf( "  gs.loop_start = 0x%x\n", gs.loop_start );
    dprintf( "  gs.loop_end   = 0x%x\n", gs.loop_end );
    dprintf( "  gs.length     = 0x%x\n", gs.length );
    if ( gs.voice_cntrl_reg & GUS_WAVE_LOOP )
      xx = gs.loop_end;
     else
      xx = gs.length;
#if 0
    if ( smpNum == 15 + 1 )
      {
        gs.sample_ptr[ xx - 1 ] = 0x10;
        gs.sample_ptr[ xx - 0 ] = 0;  
      }
#endif
    dprintf( "  0x%x: %02x %02x %02x %02x\n",
    	xx,
    	gs.sample_ptr[ xx - 3 ],
    	gs.sample_ptr[ xx - 2 ],
    	gs.sample_ptr[ xx - 1 ],
    	gs.sample_ptr[ xx - 0 ] );
  }
#endif

  if ( ioctl( seqfd, GUS_IOCTL_DOWNLOAD, &gs ) < 0 )
    {
      smp -> used = -1;
      smp -> length = 0;
      wprintf( "Skipping instrument %d - download error.\n", instrument + 1 );
    }
#ifdef 0
  dprintf( "gus_addr: 0x%x\n", gs.gus_addr );
#endif
  free( gs.sample_ptr );
}

void gusSetupVolume()
{
  if ( mainVolume >= 0 || songVolume >= 0 )
    {
      int mixerfd, temp, vol;
      
      if ( ( mixerfd = open( "/dev/mixer", O_WRONLY ) ) >= 0 )
        {
          if ( songVolume < 0 ) songVolume = 100;
          vol = ( mainVolume * songVolume ) / 100;
          temp = ( ( vol & 0xff ) << 8 ) | ( vol & 0xff );
          if ( ioctl( mixerfd, SOUND_MIXER_WRITE_SYNTH, &temp ) < 0 )
            {
              close( mixerfd );
              mixerfd = -1;
            }
        }
      if ( mixerfd < 0 )      
        wprintf( "cann't setup main volume\n" );
       else
        close( mixerfd );
    }
}

void gusSetupChannels()
{
  int i;

  if ( ioctl( seqfd, GUS_IOCTL_RESET, songChannels ) < 0 )
    fatal( "gus: ioctl failed - RESET" );
  gusSetupVolume();
  if ( rampAutoValue >= 0 )
    {
      if ( ioctl( seqfd, GUS_IOCTL_SET_AUTO_RAMP, rampAutoValue ) < 0 )
        fatal( "gus: ioctl failed - SET_AUTO_RAMP" );
    }
  if ( smoothValue >= 0 )
    {
      if ( ioctl( seqfd, GUS_IOCTL_SET_SPAN, smoothValue ) < 0 )
        fatal( "gus: ioctl failed - SET_SPAN" );
    }
  _seq_buf = seqBuf;
  for ( i = 0; i < songChannels; i++ )
    GUS_DO_VS_CURRENT_PAN( i, voices[ i ].pan );
}

void SET_VOLUME( byte channel )
{ 
  short vol;

  voices[ channel ].what_is_new |= WIN_VOL;
  vol = voices[ channel ].volume;
  if ( vol < 0 ) vol = 0;
  if ( vol > 64 ) vol = 64;
  voices[ channel ].volume = vol;
}

void SET_SAMPLE( byte channel )
{
  struct VOICE *voice;
  struct INSTR *is;
  struct SAMPLE *smp;
  
  voice = &voices[ channel ];
  if ( voice -> instr < 0 || voice -> note < 0 ) 
    {
      voice -> note = -1;
      voice -> sample = -1;
      return;
    }
  is = songInstrs[ voice -> instr ];
#ifdef 0
  dprintf( "set_sample\n" );
#endif
  voice -> sample = is -> sampleNumber[ voice -> note ];
  smp = is -> samples[ voice -> sample ];
  voice -> eVolPos = voice -> ePanPos = 0;
  voice -> eVol = voice -> ePan = -1;
  voice -> eKeyoffVol = voice -> eKeyoffPan = 0;
  voice -> eFadeout = 0;
  voice -> eVibPos = 0;
  voice -> eVibPos1 = 0;
  voice -> eVib = 0;
  if ( smp && ( smp -> type & SMP_TYPE_PAN_USED ) )
    {
      voice -> pan = smp -> pan;
      voice -> what_is_new |= WIN_PAN;
    }
}

void SET_INSTR( byte channel, short instr )
{
  voices[ channel ].instr = instr;
  SET_NOTE( channel, voices[ channel ].note );
}

void SET_NOTE( byte channel, short note )
{
  if ( note < 0 ) note = 0;
  if ( note >= MAX_OCTAVE * 12 ) note = ( MAX_OCTAVE * 12 ) - 1;

  voices[ channel ].note = note;
  SET_SAMPLE( channel );
}

int GET_NOTE_PERIOD( byte channel, short note )
{
  short smpNum;
  struct VOICE *vc;
  struct INSTR *is;
  
  vc = &voices[ channel ];
  if ( vc -> instr < 0 || vc -> sample < 0 )
    return 0;
  is = songInstrs[ vc -> instr ];

  if ( note < 0 ) note = 0;
  if ( note >= MAX_OCTAVE * 12 ) note = ( MAX_OCTAVE * 12 ) - 1;

  smpNum = is -> sampleNumber[ note ];
  if ( smpNum > MAX_SAMPLES ) 
    fatal( "GET_NOTE_PERIOD: smpNum > MAX_SAMPLES" );

#ifdef 0
  dprintf( "note %d, smpNum %d\n", note, smpNum );
#endif
  
  if ( !is -> samples[ smpNum ] ) return 0;
  note += is -> samples[ smpNum ] -> relativeNote;
#ifdef 0
  if ( note < 0 ) note = 0;
  if ( note >= MAX_OCTAVE * 12 ) note = ( MAX_OCTAVE * 12 ) - 1;
#endif

  switch ( freqType ) {
    case FTYPE_LINEAR:
      return 10*12*16*4*2 - (int)note*16*4*2 - ( (int)is -> samples[ smpNum ] -> finetune );
    case FTYPE_S3M:
      {
        static word periodTable[ 12 ] = {
          1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,907,
        };
#ifdef USE_FLOATING_POINT
        return rint( ( NTSC_FREQ * ( ( periodTable[ note % 12 ] << 4 ) >> ( note / 12 ) ) ) /
                     (double)finetuneNTSC[ ( is -> samples[ smpNum ] -> finetune >> 4 ) & 0x0f ] );
#else
        return ( NTSC_FREQ * ( ( periodTable[ note % 12 ] << 4 ) >> ( note / 12 ) ) ) /
               finetuneNTSC[ ( is -> samples[ smpNum ] -> finetune >> 4 ) & 0x0f ];
#endif
      }
    case FTYPE_AMIGA:  
      {
        static word periodTab[ 12 * 8 ] = {
          907,900,894,887,881,875,868,862,856,850,844,838,
          832,826,820,814,808,802,796,791,785,779,774,768,
          762,757,752,746,741,736,730,725,720,715,709,704,
          699,694,689,684,678,675,670,665,660,655,651,646,
          640,636,632,628,623,619,614,610,604,601,597,592,
          588,584,580,575,570,567,563,559,555,551,547,543,
          538,535,532,528,524,520,516,513,508,505,502,498,
          494,491,487,484,480,477,474,470,467,463,460,457
        };
#ifdef USE_FLOATING_POINT
        int finetune;
        double tmp, frac;
        
        finetune = is -> samples[ smpNum ] -> finetune;
        tmp = (double)finetune / 16;
        frac = tmp - floor( tmp );
        tmp = periodTab[ ( note % 12 ) * 8 + finetune / 16 ];
        tmp = ( ( tmp * ( 1 - frac ) + tmp * frac ) * 16 * 4 ) / 
              ( 1 << ( note / 12 ) );
        return rint( tmp );
#else
        int finetune, tmp, frac;

        finetune = is -> samples[ smpNum ] -> finetune;
        frac = finetune % 16;
        tmp = periodTab[ ( note % 12 ) * 8 + finetune / 16 ];
        tmp = ( ( tmp * ( 16 - frac ) + tmp * frac ) * 16 * 4 ) / ( 1 << ( note / 12 ) );
        return tmp & 8 ? ( tmp >> 4 ) + 1 : tmp >> 4;
#endif 
      }
    default:
      fatal( "GET_NOTE_PERIOD: FTYPE_NOTHING" );
  }
  return 0;
}

void SET_PERIOD( byte channel, int period )
{
  voices[ channel ].finalPeriod = period;
  voices[ channel ].what_is_new |= WIN_FREQ;
}

static long GET_GUS_FREQ( byte channel, int period ) 
{
#ifdef USE_FLOATING_POINT
  double tmp = 0;
  double tmpd;
#else
  unsigned long tmp = 0;
#endif
  struct VOICE *vc;
  struct INSTR *is;

  if ( period <= 0 ) return 0;
  
  vc = &voices[ channel ];
  if ( vc -> instr < 0 || vc -> sample < 0 )
    return 0;
  is = songInstrs[ vc -> instr ];
#ifdef 0
  dprintf( "period: %d\n", period );
#endif
  
  switch ( freqType ) {
    case FTYPE_LINEAR:
#ifdef USE_FLOATING_POINT
      tmpd = pow2( ( 6 * 12 * 16 * 4 * 2 - (double)period ) / ( 12 * 16 * 4 * 2 ) );
      tmp = rint( (double)is -> samples[ vc -> sample ] -> freqC4 * tmpd * 2 );
#else
      if ( period < 0 || sizeof( xm_period_table ) / sizeof( unsigned short ) <= period )
        dprintf( "GET_GUS_FREQ (FTYPE_LINEAR): period out of range (%i)\n", period );
      tmp = (unsigned long)is -> samples[ voices[ channel ].sample ] -> freqC4 * 
            (unsigned long)xm_period_table[ period ];
      if ( tmp & 0x100 ) tmp += 0x200;
      tmp >>= 9;
#endif
      break;
    case FTYPE_S3M:
#ifdef USE_FLOATING_POINT
      tmp = ( 14317056L * 2 ) / (double)period;
      tmp = ( (double)is -> samples[ vc -> sample ] -> freqC4 * tmp ) /
            (double)NTSC_FREQ;
#else
      tmp = ( 14317056L * 2 * 2 ) / period;
      tmp = ( is -> samples[ vc -> sample ] -> freqC4 * tmp ) /
            (unsigned long)NTSC_FREQ;
      tmp = ( tmp & 1 ) ? ( tmp >> 1 ) + 1 : ( tmp >> 1 );
#endif
      break;
    case FTYPE_AMIGA:
#ifdef USE_FLOATING_POINT
      tmp = (double)is -> samples[ vc -> sample ] -> freqC4 *
            ( 1712 * 4 / (double)period );
#else
      tmp = is -> samples[ vc -> sample ] -> freqC4 *
            ( 1712 * 4 ) / period;
#endif          
      break;
    default:
      fatal( "SET_PERIOD: FTYPE_NOTHING\n" );
  }
#ifdef USE_FLOATING_POINT
  return rint( tmp );
#else
  return tmp;
#endif
}

void doEnvelopeThings()
{
  int i, j, old;
  struct VOICE *voice;
  struct INSTR *is;

  for ( i = 0; i < songChannels; i++ )
    {
      voice = &voices[ i ];
      if ( voice -> note < 0 || 
           voice -> instr < 0 || 
           voice -> sample < 0 ) continue;
      if ( voice -> eFadeout > 0xffff )		/* hard key off */
        {
          voice -> what_is_new = 0;
          continue;
        }
      is = songInstrs[ voice -> instr ];
      if ( !is || !is -> used ) continue;
/* ===========================================================================
 *    volume envelope
 */
      if ( is -> volType & IS_TYPE_ON )
        {
          if ( voice -> eKeyoffVol == 1 )	/* keyoff request */
            {
              voice -> eFadeout = 0;
              voice -> eKeyoffVol = 2;
            }
          if ( voice -> eKeyoffVol == 2 )	/* keyoff in progress */
            {
              voice -> eFadeout += is -> fadeout;
              if ( voice -> eFadeout > 0xffff )	/* ok. stop this voice */
                {
                  voice -> what_is_new = WIN_KEYOFF;
                  continue;
                }
            }
          j = 0;
          while ( is -> volEnvelope[ j ].pos <= voice -> eVolPos && 
                  j < is -> volPoints ) j++;
          if ( j > 0 ) j--;
          old = voice -> eVol;
          if ( is -> volEnvelope[ j ].pos == voice -> eVolPos )
            voice -> eVol = is -> volEnvelope[ j ].val;
           else
            {
#ifdef USE_FLOATING_POINT
              double k = (double)( is -> volEnvelope[ j + 1 ].val - 
                           is -> volEnvelope[ j ].val ) /
                           (double)( is -> volEnvelope[ j + 1 ].pos -
                           is -> volEnvelope[ j ].pos );
              voice -> eVol = is -> volEnvelope[ j ].val +
                              rint( k * ( voice -> eVolPos - 
                                          is -> volEnvelope[ j ].pos ) );
#else
              long k = ( ( is -> volEnvelope[ j + 1 ].val -
                           is -> volEnvelope[ j ].val ) * 0x1000L ) /
                         ( is -> volEnvelope[ j + 1 ].pos -
                         is -> volEnvelope[ j ].pos );
              k *= voice -> eVolPos - is -> volEnvelope[ j ].pos;
              if ( k & 0x800 ) k += 0x1000;
              k /= 0x1000;
              voice -> eVol = is -> volEnvelope[ j ].val + k;
#endif
            }
          if ( !( is -> volType & IS_TYPE_SUSTAIN ) ||
               is -> volSustain != voice -> eVolPos || 
               voice -> eKeyoffVol == 2 )
            {
              if ( is -> volType & IS_TYPE_LOOP )
                {
                  if ( voice -> eVolPos == is -> volEnvelope[ is -> volLoopEnd ].pos )
                    voice -> eVolPos = is -> volEnvelope[ is -> volLoopStart ].pos;
                   else
                    voice -> eVolPos++;
                  if ( voice -> eVolPos > is -> volEnvelope[ is -> volPoints - 1 ].pos )
                    voice -> eVolPos = is -> volEnvelope[ is -> volPoints - 1 ].pos;
                }
               else
                {
                  if ( voice -> eVolPos < is -> volEnvelope[ is -> volPoints - 1 ].pos )
                    voice -> eVolPos++;
                   else
                    voice -> eVolPos = is -> volEnvelope[ is -> volPoints - 1 ].pos;
                }
            }
          if ( voice -> eVol == 0 ) voice -> eVol = 1;
          if ( voice -> eVol != old )
            voice -> what_is_new |= WIN_VOL;
        }
       else
      if ( voice -> eKeyoffVol == 1 )	/* keyoff request and not envelope */
        {
          voice -> eFadeout = 0xffff + 1;
          voice -> what_is_new = WIN_KEYOFF;
          continue;
        }
/* ===========================================================================
 *    panning envelope
 */
      if ( is -> panType & IS_TYPE_ON )
        {
          j = 0;
          while ( is -> panEnvelope[ j ].pos <= voice -> ePanPos && 
                  j < is -> panPoints ) j++;
          if ( j > 0 ) j--;
          old = voice -> ePan;
          if ( is -> panEnvelope[ j ].pos == voice -> ePanPos )
            voice -> ePan = is -> panEnvelope[ j ].val;
           else
            {
#ifdef USE_FLOATING_POINT
              double k = (double)( is -> panEnvelope[ j + 1 ].val -
                           is -> panEnvelope[ j ].val ) /
                           (double)( is -> panEnvelope[ j + 1 ].pos -
                           is -> panEnvelope[ j ].pos );
              voice -> ePan = is -> panEnvelope[ j ].val +
                              rint( k * ( voice -> ePanPos -
                                          is -> panEnvelope[ j ].pos ) );
#ifdef 0
              dprintf( "pan: k = %04.04f j = %d, val1 = %d, pos1 = %d, val2 = %d, pos2 = %d, pos = %d\n",
              			k,
              			j, 
              			is -> panEnvelope[ j ].val,
              			is -> panEnvelope[ j ].pos,
              			is -> panEnvelope[ j + 1 ].val,
              			is -> panEnvelope[ j + 1 ].pos,
              			voice -> ePanPos );
#endif
#else
              long k = ( ( is -> panEnvelope[ j + 1 ].val -
                           is -> panEnvelope[ j ].val ) * 0x1000L ) /
                         ( is -> panEnvelope[ j + 1 ].pos -
                         is -> panEnvelope[ j ].pos );
              k *= voice -> ePanPos - is -> panEnvelope[ j ].pos;
              if ( k & 0x800 ) k += 0x1000;
              k /= 0x1000;
              voice -> ePan = is -> panEnvelope[ j ].val + k;
#endif
            }
          if ( !( is -> panType & IS_TYPE_SUSTAIN ) ||
               is -> panSustain != voice -> ePanPos ||
               voice -> eKeyoffPan == 1 )
            {
              if ( is -> panType & IS_TYPE_LOOP )
                {
                  if ( voice -> ePanPos == is -> panEnvelope[ is -> panLoopEnd ].pos )
                    voice -> ePanPos = is -> panEnvelope[ is -> panLoopStart ].pos;
                   else
                    voice -> ePanPos++;
                  if ( voice -> ePanPos > is -> panEnvelope[ is -> panPoints - 1 ].pos )
                    voice -> ePanPos = is -> panEnvelope[ is -> panPoints - 1 ].pos;
                }
               else
                {
                  if ( voice -> ePanPos < is -> panEnvelope[ is -> panPoints - 1 ].pos )
                    voice -> ePanPos++;
                   else
                    voice -> ePanPos = is -> panEnvelope[ is -> panPoints - 1 ].pos;
                }
            }
          if ( voice -> ePan != old )
            voice -> what_is_new |= WIN_PAN;
        }
       else
        voice -> ePan = 32;			/* middle position */
/* ===========================================================================
 *    auto vibrato
 */
      if ( is -> vibratoDepth != 0 && is -> vibratoRate != 0 &&
           voice -> eVibPos != -1 )
        {
          old = voice -> eVib;
          if ( voice -> eVibPos == is -> vibratoSweep )
            voice -> eVibPos = 0x7fff;
#ifdef USE_FLOATING_POINT
          voice -> eVib = rint( (double)( 
               (int)sineOscTbl[ is -> vibratoType & 3 ][ ( voice -> eVibPos1 >> 7 ) & 0x3f ] * 
               is -> vibratoDepth ) / 128.0 );
          if ( voice -> eVibPos != 0x7fff )
            voice -> eVib = rint( (double)voice -> eVib * 
            			  ( 1.0 / (double)is -> vibratoSweep ) *
            			  voice -> eVibPos );
#else
	  voice -> eVib = 
	  	( (long)sineOscTbl[ is -> vibratoType & 3 ][ ( voice -> eVibPos1 >> 7 ) & 0x3f ] *
	  	is -> vibratoDepth ) / 128L;
	  if ( voice -> eVibPos != 0x7fff )
	    {
	      long tmp;
	      
	      tmp = (long)voice -> eVib * ( 0x10000 / (long)is -> vibratoSweep ) *
                    voice -> eVibPos;
              if ( tmp & 0x8000 ) tmp += 0x10000;
              tmp /= 0x10000;
	      voice -> eVib = tmp;
	    }
#endif
          if ( voice -> eVibPos != 0x7fff )
            voice -> eVibPos++;
          voice -> eVibPos1 += is -> vibratoRate;
          if ( voice -> eVib != old )
            {
#ifdef 0
              dprintf( "eVib: %i\n", voice -> eVib );
#endif
              voice -> what_is_new |= WIN_FREQ;
            }
        }
       else
        voice -> eVib = 0;
    }
}

void doNewThings()
{
#undef SEE_START_NOTE
#undef SEE_ALL

#ifdef SEE_START_NOTE
  static char *tbl[ 12 * MAX_OCTAVE ] = {
"C-0", "C#0", "D-0", "D#0", "E-0", "F-0", "F#0", "G-0", "G#0", "A-0", "A#0", "B-0",
"C-1", "C#1", "D-1", "D#1", "E-1", "F-1", "F#1", "G-1", "G#1", "A-1", "A#1", "B-1",
"C-2", "C#2", "D-2", "D#2", "E-2", "F-2", "F#2", "G-2", "G#2", "A-2", "A#2", "B-2",
"C-3", "C#3", "D-3", "D#3", "E-3", "F-3", "F#3", "G-3", "G#3", "A-3", "A#3", "B-3",
"C-4", "C#4", "D-4", "D#4", "E-4", "F-4", "F#4", "G-4", "G#4", "A-4", "A#4", "B-4",
"C-5", "C#5", "D-5", "D#5", "E-5", "F-5", "F#5", "G-5", "G#5", "A-5", "A#5", "B-5",
"C-6", "C#6", "D-6", "D#6", "E-6", "F-6", "F#6", "G-6", "G#6", "A-6", "A#6", "B-6",
"C-7", "C#7", "D-7", "D#7", "E-7", "F-7", "F#7", "G-7", "G#7", "A-7", "A#7", "B-7"
};
#endif
  int i, flag;
  word win;
  short realVol = 0, realPan = 0;
  long realFreq = 0;
  struct VOICE *voice;
  struct INSTR *is;
  struct SAMPLE *smp;

#ifdef SEE_ALL
  dprintf( "G: tick\n" );
#endif

  if ( disabledChannels != disableChannels )
    {
      for ( i = 0; i < songChannels; i++ )
        {
          flag = 1 << i;
          if ( ( disabledChannels & flag ) != ( disableChannels & flag ) &&
               ( disableChannels & flag ) )
            {
              GUS_DO_VS_VOICE_STOP( i );
            }
        }
      disabledChannels = disableChannels;
    }
  
  for ( i = 0; i < songChannels; i++ )
    {
      if ( disabledChannels & ( 1 << i ) ) continue;
    
      voice = &voices[ i ];
      if ( ( win = voice -> what_is_new ) == 0 ||
           voice -> instr < 0 || voice -> sample < 0 ) continue;
      is = songInstrs[ voice -> instr ];
      smp = is -> samples[ voice -> sample ];
      if ( !is || !smp || smp -> ident < 0 || !is -> used || !smp -> used )
        continue; 		/* sample not loaded */
      flag = 0;
      if ( win & WIN_KEYOFF )
        {
          GUS_DO_VS_VOICE_STOP( i );
          voice -> eVib = -1;
          win = 0;
        }

      if ( win & ( WIN_NOTE_START | WIN_VOL ) )
        {
#ifdef USE_FLOATING_POINT
          double tmp;
        
          tmp = ( (double)songGlobalVolume / 128 ) * 
                ( (double)voice -> volume / 64 );
          if ( is -> volType & IS_TYPE_ON )
            {
              tmp *= 1.0 - ( (double)voice -> eFadeout / 0xffff );
              tmp *= (double)voice -> eVol / 64;
            }
          realVol = rint( tmp * 128 );
#else
	  unsigned long tmp;

          tmp = ( (long)songGlobalVolume * (long)voice -> volume ) << 2;
          if ( is -> volType & IS_TYPE_ON )
            {
              tmp *= 0xffff - (long)(voice -> eFadeout & 0xffff);
              if ( tmp & ( 1 << 15 ) ) tmp += 1 << 16; 
              tmp >>= 16;
              tmp *= (long)voice -> eVol; 
              if ( tmp & ( 1 << 5 ) ) tmp += 1 << 6; 
              tmp >>= 6;
            }
          realVol = tmp & ( 1 << 7 ) ? ( tmp >> 8 ) + 1 : ( tmp >> 8 );
#endif
        }
      if ( win & WIN_PAN )
        {
          realPan = voice -> pan;		/* 0..255 */
          if ( is -> panType & IS_TYPE_ON )
            {
              if ( voice -> ePan > 63 ) voice -> ePan = 63;
              realPan += (short)( voice -> ePan << 2 ) - 128;
              if ( realPan < 0 ) realPan = 0;
              if ( realPan > 255 ) realPan = 255;
            }
        }
      if ( win & ( WIN_NOTE_START | WIN_FREQ ) )
        realFreq = GET_GUS_FREQ( i, voice -> finalPeriod + voice -> eVib );
      
      if ( win & WIN_NOTE_START )
        {
#if defined( SEE_START_NOTE ) || defined( SEE_ALL )
          dprintf( "G: note start: ch: %d, i: %d, f: %d, v: 0x%x, p: %i, ap: %i [%s] %d\n",
          		       i,
                               smp -> ident,
                               (int)realFreq,
                               (int)realVol,
                               voice -> basePeriod,
                               voice -> basePeriod >> 2,
                               tbl[ voice -> note ],
                               voice -> note );
          dprintf( "G: instr = %d, smp = %d, relNote = %d\n",
          		(int)voice -> instr,
          		(int)voice -> sample,
          		(int)smp -> relativeNote );
#endif
          GUS_DO_SAMPLE_START( i,
          		       smp -> ident,
                               realFreq,
                               realVol );
          win &= ~( WIN_FREQ | WIN_VOL );
          flag |= 3;
        }
      if ( win & WIN_FREQ )
        {
          if ( win & WIN_VOL )
            {
#ifdef SEE_ALL
	      dprintf( "G: freq: %d, vol: %d\n", (int)realFreq, realVol );
#endif
              if ( !( flag & 1 ) )
                {
                  GUS_DO_VS_FREQ_AND_VOL( i, realFreq, realVol );
                  flag |= 1;
                }
               else
                GUS_DO_FREQ_AND_VOL( realFreq, realVol );
              win &= ~WIN_VOL;
            }
           else
            {
#ifdef SEE_ALL
              dprintf( "G: freq: %d\n", (int)realFreq );
#endif
              if ( !( flag & 1 ) )
                {
                  GUS_DO_VS_FREQUENCY( i, realFreq );
                  flag |= 1;
                }
               else
                GUS_DO_FREQUENCY( realFreq );
            }
        }
      if ( win & WIN_VOL )
        {
#ifdef SEE_ALL
          dprintf( "G: vol: %d\n", realVol );
#endif
          if ( !( flag & 1 ) )
            {
              GUS_DO_VS_CURRENT_VOL( i, realVol );
              flag |= 1;
            }
           else
            GUS_DO_CURRENT_VOL( realVol );
        }
      if ( win & WIN_OFFSET )
        {
#ifdef SEE_ALL
          dprintf( "G: offset: 0x%x\n", (int)voice -> gusOffset );
#endif
          if ( !( flag & 2 ) )
	    {
	      GUS_DO_SAMPLE_SELECT( smp -> ident );
	      flag |= 2;
	    }
          if ( !( flag & 1 ) )
            {
              GUS_DO_VS_CURRENT_LOC( i, voice -> gusOffset );
              flag |= 1;
            }
           else
            GUS_DO_CURRENT_LOC( voice -> gusOffset );
        }
      if ( win & WIN_PAN )
        {
#ifdef SEE_ALL
          dprintf( "G: pan: %d\n", realPan >> 4 );
#endif
          if ( !( flag & 1 ) )
            {
              GUS_DO_VS_CURRENT_PAN( i, ( realPan >> 4 ) & 0x0f );
              flag |= 1;
            }
           else
            GUS_DO_CURRENT_PAN( ( realPan >> 4 ) & 0x0f );
        }
      voices[ i ].what_is_new = 0;
    }
}
