/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *  Routines for control of LFO generators (tremolo & vibrato) for
 *  GF1/InterWave chips...
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "driver.h"
#include "gus.h"

/*
 *  called by engine routines
 */

static signed char snd_gf1_lfo_compute_value( snd_gus_card_t *gus, unsigned char *ptr )
{
  unsigned int twaveinc, depth_delta;
  signed int result;
  unsigned short control, twave, depth, depth_final;
  unsigned char *ptr1;

  control = *(unsigned short *)(ptr + 0x00);
  ptr1 = ptr + ( ( control & 0x4000 ) >> 12 );
  /* 1. add TWAVEINC to TWAVE and write the result back */
  /* LFO update rate is 689Hz, effect timer is in ms */
  if ( gus -> gf1.timer_slave )
    twaveinc = ( 689 * gus -> gf1.timer_master_gus -> gf1.effect_timer ) / 1000;
   else
    twaveinc = ( 689 * gus -> gf1.effect_timer ) / 1000;
  if ( !twaveinc ) twaveinc++;
#if 0
  printk( "twaveinc = 0x%x, effect_timer = %i\n", twaveinc, gus -> gf1.effect_timer );
#endif

  depth = *(unsigned short *)(ptr1 + 0x0a);
  depth_final = *(unsigned char *)(ptr + 0x02) << 5;
  if ( depth != depth_final )
    {
      depth_delta = ( ( twaveinc * *(ptr + 0x03) ) + *(unsigned short *)(ptr + 0x04) );
      *(unsigned short *)(ptr + 0x04) = depth_delta % 8000;
      depth_delta /= 8000;
      if ( depth < depth_final )
        {
          if ( depth + depth_delta > depth_final )
            depth = depth_final;
           else
            depth += depth_delta;  
        }
      if ( depth > depth_final )
        {
          if ( depth - depth_delta < depth_final )
            depth = depth_final;
           else
            depth -= depth_delta;
        }
      *(unsigned short *)(ptr1 + 0x0a) = depth;
    }
  
  twaveinc *= (unsigned int)control & 0x7ff;
  twaveinc += *(unsigned short *)( ptr + 0x06 );
  *(unsigned short *)(ptr + 0x06) = twaveinc % 1000;

  twave = *(unsigned short *)(ptr1 + 0x08);
  twave += (unsigned short)(twaveinc / (unsigned int)1000);
  *(unsigned short *)(ptr1 + 0x08) = twave;

  if ( !(control & 0x2000) )
    {
      /* 2. if shift is low */
      if ( twave & 0x4000 )	/* bit 14 high -> invert TWAVE 13-0 */
        {
          twave ^= 0x3fff;
          twave &= ~0x4000;
        }
      /* TWAVE bit 15 is exclusive or'd with the invert bit (12) */
      twave ^= ( control & 0x1000 ) << 3;
    }
   else
    {
      /* 2. if shift is high */
      if ( twave & 0x8000 )	/* bit 15 high -> invert TWAVE 14-0 */
        twave ^= 0x7fff;
      /* the invert bit (12) is used as sign bit */
      if ( control & 0x1000 )
        twave |= 0x8000;
       else
        twave &= ~0x8000;
    }
  /* 3. multiply the 14-bit LFO waveform magnitude by 13-bit DEPTH */
#if 0
  printk( "c=0x%x,tw=0x%x,to=0x%x,d=0x%x,df=0x%x,di=0x%x,r=0x%x,r1=%i\n",
  		control, twave, 
  		*(unsigned short *)(ptr1 + 0x08),
  		depth, depth_final, *(ptr + 0x03),
  		(twave & 0x7fff) * depth, ( (twave & 0x7fff) * depth ) >> 21 );
#endif
  result = (twave & 0x7fff) * depth;
  if ( result )
    {
      /* shift */
      result >>= 21;
      result &= 0x3f;
    }
  /* add sign */      
  if ( twave & 0x8000 ) result = -result;
#if 0
  printk( "lfo final value = %i\n", result );
#endif
  return result;
}

static void snd_gf1_lfo_register_setup( snd_gus_card_t *gus, snd_gf1_voice_t *voice, int lfo_type )
{
  unsigned long flags;

  if ( gus -> gf1.enh_mode )
    {
      CLI( &flags );
      gf1_select_voice( gus, voice -> number );
      if ( lfo_type & 1 )
        {
          snd_gf1_write8( gus, GF1_VB_FREQUENCY_LFO, voice -> lfo_fc );
          voice -> lfo_fc = 0;
        }
      if ( lfo_type & 2 )
        {
          snd_gf1_write8( gus, GF1_VB_VOLUME_LFO, voice -> lfo_volume );
          voice -> lfo_volume = 0;
        }
      STI( &flags );
    }
   else
    {
      /*
       * ok.. with old GF1 chip can be only vibrato emulated...
       * volume register can be in volume ramp state, so tremolo isn't simple..
       */
      if ( !(lfo_type & 1) ) return;
#if 0
      if ( voice -> lfo_fc )
        printk( "setup - %i = %i\n", voice -> number, voice -> lfo_fc );
#endif
      CLI( &flags );
      gf1_select_voice( gus, voice -> number );
      snd_gf1_write16( gus, GF1_VW_FREQUENCY, voice -> fc_register + voice -> lfo_fc );
      STI( &flags );
    }
}

void snd_gf1_lfo_effect_interrupt( snd_gus_card_t *gus, snd_gf1_voice_t *voice )
{
  unsigned char *ptr;
  
#if 0
  if ( voice -> number != 0 ) return;
#endif
  ptr = gus -> gf1.lfos + ( ( voice -> number ) << 5 );
  /* 1. vibrato */
  if ( *(unsigned short *)(ptr + 0x00) & 0x8000 )
    voice -> lfo_fc = snd_gf1_lfo_compute_value( gus, ptr );
  /* 2. tremolo */
  ptr += 16;
  if ( *(unsigned short *)(ptr + 0x00) & 0x8000 )
    voice -> lfo_volume = snd_gf1_lfo_compute_value( gus, ptr );
  /* 3. register setup */
  snd_gf1_lfo_register_setup( gus, voice, 3 );
}

/*
 *
 */

void snd_gf1_lfo_init( snd_gus_card_t *gus )
{
  if ( gus -> gf1.hw_lfo )
    {
      snd_gf1_i_write16( gus, GF1_GW_LFO_BASE, 0x0000 );
      snd_gf1_dram_setmem( gus, 0, 0x0000, 1024 );
      /* now enable LFO */
      snd_gf1_i_write8( gus, GF1_GB_GLOBAL_MODE, snd_gf1_i_look8( gus, GF1_GB_GLOBAL_MODE ) | 0x02 );
    }
  if ( gus -> gf1.sw_lfo )
    {
#if 1
      gus -> gf1.lfos = snd_malloc( 1024 );
      if ( gus -> gf1.lfos )
        memset( gus -> gf1.lfos, 0, 1024 );
       else
#endif
        gus -> gf1.sw_lfo = 0;
    }
}

void snd_gf1_lfo_done( snd_gus_card_t *gus )
{
  if ( gus -> gf1.sw_lfo )
    {
      if ( gus -> gf1.lfos )
        {
          snd_gf1_free( gus -> gf1.lfos, 1024 );
          gus -> gf1.lfos = NULL;
        }
    }
}

void snd_gf1_lfo_program( snd_gus_card_t *gus, int voice, int lfo_type, struct ULTRA_STRU_IW_LFO_PROGRAM *program )
{
  unsigned int lfo_addr, wave_select;
  
  wave_select = ( program -> freq_and_control & 0x4000 ) >> 12;
  lfo_addr = ( voice << 5 ) | ( lfo_type << 4 );
  if ( gus -> gf1.hw_lfo )
    {
#if 0
      printk( "LMCI = 0x%x\n", snd_gf1_i_look8( gus, 0x53 ) );
      printk( "lfo_program: lfo_addr=0x%x,wave_sel=0x%x,fac=0x%x,df=0x%x,di=0x%x,twave=0x%x,depth=0x%x\n",
      		lfo_addr, wave_select,
      		program -> freq_and_control,
      		program -> depth_final,
      		program -> depth_inc,
      		program -> twave,
      		program -> depth );
#endif
      snd_gf1_poke( gus, lfo_addr + 0x02, program -> depth_final );
      snd_gf1_poke( gus, lfo_addr + 0x03, program -> depth_inc );
      snd_gf1_pokew( gus, lfo_addr + 0x08 + wave_select, program -> twave );
      snd_gf1_pokew( gus, lfo_addr + 0x0a + wave_select, program -> depth );
      snd_gf1_pokew( gus, lfo_addr + 0x00, program -> freq_and_control );
#if 0
      {
        int i = 0;
        for ( i = 0; i < 16; i++ )
          printk( "%02x:", snd_gf1_peek( gus, lfo_addr + i ) );
        printk( "\n" );
      }
#endif
    }
  if ( gus -> gf1.sw_lfo )
    {
      unsigned char *ptr = gus -> gf1.lfos + lfo_addr;
    
      *(ptr + 0x02) = program -> depth_final;
      *(ptr + 0x03) = program -> depth_inc;
      *(unsigned short *)(ptr + 0x08 + wave_select) = program -> twave;
      *(unsigned short *)(ptr + 0x0a + wave_select) = program -> depth;
      *(unsigned short *)(ptr + 0x00) = program -> freq_and_control;
    }
}

void snd_gf1_lfo_enable( snd_gus_card_t *gus, int voice, int lfo_type )
{
  unsigned int lfo_addr;
  
  lfo_addr = ( voice << 5 ) | ( lfo_type << 4 );
  if ( gus -> gf1.hw_lfo )
    snd_gf1_pokew( gus, lfo_addr + 0x00, snd_gf1_peekw( gus, lfo_addr + 0x00 ) | 0x8000 );
  if ( gus -> gf1.sw_lfo )
    {
      unsigned char *ptr = gus -> gf1.lfos + lfo_addr;
      
      *(unsigned short *)(ptr + 0x00) |= 0x8000;
    }
}

void snd_gf1_lfo_disable( snd_gus_card_t *gus, int voice, int lfo_type )
{
  unsigned int lfo_addr;

  lfo_addr = ( voice << 5 ) | ( lfo_type << 4 );  
  if ( gus -> gf1.hw_lfo )
    snd_gf1_pokew( gus, lfo_addr + 0x00, snd_gf1_peekw( gus, lfo_addr + 0x00 ) & ~0x8000 );
  if ( gus -> gf1.sw_lfo )
    {
      unsigned char *ptr = gus -> gf1.lfos + lfo_addr;
      
      *(unsigned short *)(ptr + 0x00) &= ~0x8000;
    }
}

void snd_gf1_lfo_change_freq( snd_gus_card_t *gus, int voice, int lfo_type, int freq )
{
  unsigned int lfo_addr;

  lfo_addr = ( voice << 5 ) | ( lfo_type << 4 );
  if ( gus -> gf1.hw_lfo )
    snd_gf1_pokew( gus, lfo_addr + 0x00, ( snd_gf1_peekw( gus, lfo_addr + 0x00 ) & ~0x7ff ) | ( freq & 0x7ff ) );
  if ( gus -> gf1.sw_lfo )
    {
      unsigned long flags;
      unsigned char *ptr = gus -> gf1.lfos + lfo_addr;
      
      CLI( &flags );
      *(unsigned short *)(ptr + 0x00) &= ~0x7ff;
      *(unsigned short *)(ptr + 0x00) |= freq & 0x7ff;
      STI( &flags );
    }
}

void snd_gf1_lfo_change_depth( snd_gus_card_t *gus, int voice, int lfo_type, int depth )
{
  unsigned long flags;
  unsigned int lfo_addr;
  unsigned short control = 0;
  unsigned char *ptr;

  lfo_addr = ( voice << 5 ) | ( lfo_type << 4 );
  ptr = gus -> gf1.lfos + lfo_addr;
  if ( gus -> gf1.hw_lfo )
    control = snd_gf1_peekw( gus, lfo_addr + 0x00 );
  if ( gus -> gf1.sw_lfo )
    control = *(unsigned short *)(ptr + 0x00);
  if ( depth < 0 )
    {
      control |= 0x1000;
      depth = -depth;
    }
   else
    control &= ~0x1000;
  if ( gus -> gf1.hw_lfo )
    {
      CLI( &flags );
      snd_gf1_poke( gus, lfo_addr + 0x02, (unsigned char)depth );
      snd_gf1_pokew( gus, lfo_addr + 0x0a + ( ( control & 0x4000 ) >> 12 ), depth << 5 );
      snd_gf1_pokew( gus, lfo_addr + 0x00, control );
      STI( &flags );
    }
  if ( gus -> gf1.sw_lfo )
    {
      unsigned char *ptr = gus -> gf1.lfos + lfo_addr;
      
      CLI( &flags );
      *(ptr + 0x02) = (unsigned char)depth;
      *(unsigned short *)(ptr + 0x0a + ( (control & 0x4000) >> 12 )) = depth << 5;
      *(unsigned short *)(ptr + 0x00) = control;
      STI( &flags );      
    }
}

void snd_gf1_lfo_setup( snd_gus_card_t *gus, int voice, int lfo_type, int freq, int current_depth, int depth, int sweep, int shape )
{
  struct ULTRA_STRU_IW_LFO_PROGRAM program;

  program.freq_and_control = 0x8000 | ( freq & 0x7ff );
  if ( shape & ULTRA_STRU_IW_LFO_SHAPE_POSTRIANGLE )
    program.freq_and_control |= 0x2000;
  if ( depth < 0 )
    {
      program.freq_and_control |= 0x1000;
      depth = -depth;
    }
  program.twave = 0;
  program.depth = current_depth;
  program.depth_final = depth;
  if ( sweep )
    {
      program.depth_inc = (unsigned char)(((int)((depth<<5)-current_depth)<<9)/(sweep*4410L));
      if ( !program.depth_inc ) program.depth_inc++;
    }
   else
    program.depth = (unsigned short)(depth << 5);
  snd_gf1_lfo_program( gus, voice, lfo_type, &program );
}

void snd_gf1_lfo_shutdown( snd_gus_card_t *gus, int voice, int lfo_type )
{
  unsigned long flags;
  unsigned int lfo_addr;

  lfo_addr = ( voice << 5 ) | ( lfo_type << 4 );
  if ( gus -> gf1.hw_lfo )
    {
      snd_gf1_pokew( gus, lfo_addr + 0x00, 0x0000 );
      CLI( &flags );
      gf1_select_voice( gus, voice );
      snd_gf1_write8( gus, lfo_type == ULTRA_LFO_VIBRATO ? GF1_VB_FREQUENCY_LFO : GF1_VB_VOLUME_LFO, 0 );
      STI( &flags );
    }
  if ( gus -> gf1.sw_lfo )
    {
      unsigned char *ptr = gus -> gf1.lfos + lfo_addr;
      snd_gf1_voice_t *pvoice;
    
      *(unsigned short *)(ptr + 0x00) = 0;
      *(unsigned short *)(ptr + 0x04) = 0;
      *(unsigned short *)(ptr + 0x06) = 0;
      if ( gus -> gf1.syn_voices )
        {
          pvoice = gus -> gf1.syn_voices + voice;
          if ( lfo_type == ULTRA_LFO_VIBRATO )
            pvoice -> lfo_fc = 0;
           else
            pvoice -> lfo_volume = 0;
          snd_gf1_lfo_register_setup( gus, pvoice, lfo_type == ULTRA_LFO_VIBRATO ? 1 : 2 );
        }
       else
      if ( gus -> gf1.enh_mode )
        {
          CLI( &flags );
          gf1_select_voice( gus, voice );
          snd_gf1_write8( gus, lfo_type == ULTRA_LFO_VIBRATO ? GF1_VB_FREQUENCY_LFO : GF1_VB_VOLUME_LFO, 0 );
          STI( &flags );
        }
    }
}

void snd_gf1_lfo_command( snd_gus_card_t *gus, int voice, unsigned char *data )
{
  int lfo_type;
  int lfo_command;
  int temp1, temp2;

  lfo_type = *data >> 7;
  lfo_command = *data & 0x7f;
  switch ( lfo_command ) {
    case ULTRA_LFO_SETUP:	/* setup */
      temp1 = snd_gf1_get_word( data, 2 );
      temp2 = snd_gf1_get_word( data, 4 );
      snd_gf1_lfo_setup( gus, voice, lfo_type, temp1 & 0x7ff, 0, temp2 > 255 ? 255 : temp2, snd_gf1_get_byte( data, 1 ), ( temp1 & 0x2000 ) >> 13 );
      break;
    case ULTRA_LFO_FREQ:	/* freq */
      snd_gf1_lfo_change_depth( gus, voice, lfo_type, snd_gf1_get_word( data, 2 ) );
      break;
    case ULTRA_LFO_DEPTH:	/* depth */
      snd_gf1_lfo_change_freq( gus, voice, lfo_type, snd_gf1_get_word( data, 2 ) );
      break;
    case ULTRA_LFO_ENABLE:	/* enable */
      snd_gf1_lfo_enable( gus, voice, lfo_type );
      break;
    case ULTRA_LFO_DISABLE:	/* disable */
      snd_gf1_lfo_disable( gus, voice, lfo_type );
      break;
    case ULTRA_LFO_SHUTDOWN:	/* shutdown */
      snd_gf1_lfo_shutdown( gus, voice, lfo_type );
      break;
  }
}
