/***************************************************************************
*       NAME:  SBC.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."
****************************************************************************/
#include <dos.h>
#include "sbosdata.h"
#include "sbosdefs.h"
#include "fm.h"

unsigned char csm_sel_reg=0;    /** register 8  **/
unsigned char rhythm_reg=0;     /** register BD **/
unsigned char things_to_do=0;   /** flag bits for indicating FM parm changed */
unsigned char perc_to_do=0;     /** flag bits for which perc to play **/

extern FM_CHANNEL fm_channel[];
extern FM_OPERATOR fm_operator[];
extern FM_CHANNEL * chan_ptr;
extern FM_OPERATOR * voice_ptr;
extern FM_OPERATOR * car_ptr;
extern FM_OPERATOR * mod_ptr;
extern unsigned char fm_data;
extern unsigned int fm_chan;
extern unsigned int fm_voice;
extern unsigned int car_voice;
extern unsigned int mod_voice;
extern unsigned char hit_perc;

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

FUNCTION DEFINITION:
set_global_ptrs - Sets up pointers to FM structures based on voice

DESCRIPTION:
  Sets pointers to channel and operator dependent array elements
  Called by the decode channel routines in the assembly routine

  chan_ptr -  pointer to the fm_channel element
              valid only if the port write was channel dependent
              not valid for port writes such as rhythm (BD) etc
  voice_ptr - pointer to the operator of the current fm channel which was
              at the focus of the last port write.
              valid only if the port write was channel dependent
              set to the operator specified in the register address
              otherwise set to the channel's modulator
  mod_ptr -   pointer to the current channel's modulator regardless of focus
              of port write - valid if chan_ptr is valid
  car_ptr -   pointer to the current channel's carrier regardless of focus
              of port write - valid if chan_ptr is valid

EXPECTS:
  fm_voice must be valid

MODIFIES:
  above pointers

*/
void set_global_ptrs(void)
{
    chan_ptr  = &fm_channel[fm_chan];
    voice_ptr = &fm_operator[fm_voice];
    mod_voice = MAKE_MOD(fm_voice);
    mod_ptr   = &fm_operator[mod_voice];
    car_voice = MAKE_CAR(fm_voice);
    car_ptr   = &fm_operator[car_voice];

    return;
}

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

FUNCTION DEFINITION:
fmC_parameter - ADLIB Ports 20h - 35h

DESCRIPTION:

        D7     D6     D5     D4     D3     D2     D1     D0
   |------------------------------------------------------------|
   |    AM  | VIB  |  EG  |  KSR |         MULTIPLE             |
   |------------------------------------------------------------|

    MULTIPLE - Multiplication factor for specified cell (.5-15x)
    KSR - used to model instruments at higher pitch ranges.  Instruments at 
      high pitches have shorter ADSR than at lower pitches.  This 2 bit value 
      selects the method in which the part decreases the level:
    EG - Envelope type - Diminishes when "0", continuous when "1"
    VIB - Adds vibrato to the operator cell
    AM - Adds Amplitude Modulation to the operator cell.

    Both AM and VIB are ignored.
    The MULTIPLE field modifies frequency.
    The KSR field modifies envelope and therefore sets UPD_VOLUME to
        recalculate envelopes.
    The EG field modified envelope but has its own function in the synth.

*/
void fmC_parameter(void)
{
    unsigned char data;
    unsigned char param;

    data = fm_data;
    param = voice_ptr->param;

    if ((param & (UCHAR)0x10) != (data & (UCHAR)0x10)) 
        things_to_do |= UPD_VOLUME;
    
    if ((param & (UCHAR)0x20) != (data & (UCHAR)0x20)) 
        things_to_do |= UPD_SUSTAIN;
    
    if ((param & (UCHAR)0x0F) != (data & (UCHAR)0x0F))
        things_to_do |= UPD_FREQUENCY;

    voice_ptr->param = data;
}

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

FUNCTION DEFINITION:
fmC_ksl_atten() - ADLIB Ports 40h - 55h

DESCRIPTION:

        D7     D6     D5     D4     D3     D2     D1     D0
   |------------------------------------------------------------|
   |      KSL      |             Total Level                    |
   |------------------------------------------------------------|
    
    Total Level sets the output attenuation for the channel specified.  
      An attenuation level of 0 is high output and can go no less than 
      63 * .75dB at 6 bit resolution.
    KSL - used to model instruments at higher pitch ranges.  Instruments at 
      high pitches have lower amplitude than lower pitches.  This 2 bit value 
      selects the method in which the part decreases the level:
      0 - 0   dB attenuation
      1 - 1.5 dB/oct
      2 - 3   dB/oct
      3 - 6   dB/oct

    Both fields in this register modify envelope.

*/
void fmC_ksl_atten(void)
{
    unsigned char data;
    unsigned char temp;

    data = fm_data & 0x3F;
    temp = voice_ptr->level;
    if (temp != data)
        {
        voice_ptr->level = data;
        things_to_do |= UPD_VOLUME;
        }

    data = fm_data >> 6;
    temp = voice_ptr->ksl;
    if (temp != data)
        {
        voice_ptr->ksl   = data;
        things_to_do |= UPD_VOLUME;
        }
}

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

FUNCTION DEFINITION:
fmC_attack_decay() - ADLIB Ports 60h - 75h

DESCRIPTION:
    
        D7     D6     D5     D4     D3     D2     D1     D0
   |------------------------------------------------------------|
   |         Attack Rate         |          Decay Rate          |
   |------------------------------------------------------------|

    Decay rate is the diminishing time after the attack.
    Attack rate sets the initial rising time for the sound.
    Both parameters range in value from 0 to 15.

    Both fields in this register modify envelope.
    Only the Attack field can modify an instrument choice.

*/
void fmC_attack_decay(void)
{
unsigned char temp; /** this way makes better code **/

    temp = fm_data >> 4;
    if (voice_ptr->attack != temp)
        {
        things_to_do |= (UPD_VOLUME | UPD_INSTRUMENT);
        voice_ptr->attack = temp;
        }

    temp = fm_data & 0x0F;
    if (voice_ptr->decay != temp)
        {
        things_to_do |= UPD_VOLUME;
        voice_ptr->decay = temp;
        }
}

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

FUNCTION DEFINITION:
fmC_sust_rel() - ADLIB Ports 80h - 95h

DESCRIPTION:

        D7     D6     D5     D4     D3     D2     D1     D0
   |------------------------------------------------------------|
   |         Sustain Level       |        Release Rate          |
   |------------------------------------------------------------|
       24     12     6       3      2^3    2^2    2^1     2^0 
                 dB

    Sustain Level is the level at which the note sustains after the decay time
    Release Rate is the time it takes to release the note after the sustain
      level has been reached

    Both fields in this register modify envelope and instrument choice.

*/
void fmC_sust_rel(void)
{
unsigned char temp; /** this way makes better code **/

    temp = fm_data >> 4;
    if (voice_ptr->sustain != temp)
        {
        things_to_do |= (UPD_VOLUME | UPD_INSTRUMENT);
        voice_ptr->sustain = temp;
        }

    temp = fm_data & 0x0F;
    if (voice_ptr->release != temp)
        {
        things_to_do |= (UPD_VOLUME | UPD_INSTRUMENT);
        voice_ptr->release = temp;
        }
}

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

FUNCTION DEFINITION:
fmC_freqL() - ADLIB Ports A0h - A8h

DESCRIPTION:
    
        D7     D6     D5     D4     D3     D2     D1     D0
   |------------------------------------------------------------|
   |                  F-Number[7:0] (L)                         |
   |------------------------------------------------------------|

    F-Number is a 10 bit number formed from 8 bits from register at address
      A0-A8 (this one!) and 2 bits [1:0] from address B0-B8.
    F-Number determines the frequency of the output signal.
    See Doc for _fm_key_freqH()

    This register modifies frequency only.

*/
void fmC_freqL(void)
{
    unsigned char data;

    data = fm_data;
    if (chan_ptr->freql != data)
        {
        chan_ptr->freql = data;
        things_to_do |= UPD_FREQUENCY;
        }
}

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

FUNCTION DEFINITION:
fmC_key_freqH() - ADLIB Ports B0h - B8h

DESCRIPTION:
    
        D7     D6     D5     D4     D3     D2     D1     D0
   |------------------------------------------------------------|
   |              | KEYON |        BLOCK       | F-Number[9:8]  |
   |------------------------------------------------------------|

    F-Number[9:8] is the 2 high bits of the F-Number frequency value
    Block is a 3 bit octave value
    KEYON turns on the channel when "1" and off the channel when "0"
    
    Equation for the relationship between F-Number and Block is:
    
                FREQUENCY = 50000 * F-Number * 2 ^ (BLOCK-20)

    The Fnum(High) and BLOCK fields modify frequency.
    The KEYON field modifies keyon.

*/
void fmC_key_freqH(void)
{
unsigned char data;  /** this way makes better code **/

    data = fm_data & 0x1F;
    if(data != chan_ptr->freqh)
        things_to_do |= UPD_FREQUENCY;
    chan_ptr->freqh = data;

    data = fm_data & 0x20;
    if (data != chan_ptr->keyon)
        things_to_do |= UPD_KEYON;
    chan_ptr->keyon = data;
}

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

FUNCTION DEFINITION:
fmC_rhythm() - ADLIB Ports BDh

DESCRIPTION:
    
        D7     D6     D5     D4     D3     D2     D1     D0
   |------------------------------------------------------------|
   |  VIBD  | THMD | RHYD | BASS | SNRE | TOMT | TOPH |  HI     |
   |------------------------------------------------------------|

    VIBD - Vibrato Depth: 14 cent when D6 = 1, 7 otherwise
    THMD - AM-Depth: 4.8dB when d7 = 1, 1dB otherwise
    RHYD - Allow enabling of instruments and sine wave distortion (E0-F5)
    BASS - Bass Drum
    SNRE - Snare Drum
    TOMT - Tom Tom
    TOPH - Top Cymbal
    HI   - Hi Hat

    Perc_to_do contains bits for each RECENTLY TOGGLED (lo to hi) percussion
    bit.  If any bit, including the PERC_ENABLE bit (0x20) has seen a low
    to high transition, the cooresponding bit in the perc_to_do will be set
    and analysed later in process_updates().

*/
void fmC_rhythm(void)
{
    perc_to_do = (UCHAR)0x00;
    if(rhythm_reg == fm_data)
        return;

    if(fm_data & PERC_ENABLE) /** enable is now currently on **/
        {
        /** See if any of the perc bits WAS off and is NOW on **/

        if(!(rhythm_reg & PERC_HIHAT) && (fm_data & PERC_HIHAT)) 
            perc_to_do |= PERC_HIHAT;

        if(!(rhythm_reg & PERC_TOPCYM) && (fm_data & PERC_TOPCYM)) 
            perc_to_do |= PERC_TOPCYM;

        if(!(rhythm_reg & PERC_TOMTOM) && (fm_data & PERC_TOMTOM))
            perc_to_do |= PERC_TOMTOM;

        if(!(rhythm_reg & PERC_SNARE) && (fm_data & PERC_SNARE))
            perc_to_do |= PERC_SNARE;

        if(!(rhythm_reg & PERC_BASS) && (fm_data & PERC_BASS))
            perc_to_do |= PERC_BASS;

        if(perc_to_do)
            things_to_do |= UPD_KEYON;
        }

    /** if enable bit is now on but was off **/
    if(!(rhythm_reg & PERC_ENABLE) && (fm_data & PERC_ENABLE)) 
        {
        things_to_do |= UPD_PERCUSSION;
        perc_to_do |= PERC_ENABLE_ON;
        }

    /** else if enable bit is now off but was on **/
    else if((rhythm_reg & PERC_ENABLE) && !(fm_data & PERC_ENABLE)) 
        {
        things_to_do |= UPD_PERCUSSION;
        perc_to_do |= PERC_ENABLE_OFF;
        }

    rhythm_reg = fm_data;

	if(things_to_do)
		hit_perc = TRUE;
}

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

FUNCTION DEFINITION:
fmC_con_feedback() - ADLIB Ports C0h - C8h

DESCRIPTION:

        D7     D6     D5     D4     D3     D2     D1     D0
   |------------------------------------------------------------|
   |                             |      Feedback      |  CON    |
   |------------------------------------------------------------|
    FM modulation mode is selected when CON = "0"
    Parallel sine wave generation mode when CON = "1"
    Feedback gives the modulation factor for feedback FM modulation of the 
    first operator cell.

    Both fields in this register can modify frequency and chosen instrument.
    
*/
void fmC_con_feedback(void)
{
    if( chan_ptr->con != fm_data)
        things_to_do |= (UPD_INSTRUMENT | UPD_FREQUENCY);

    chan_ptr->con = fm_data;
}

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

FUNCTION DEFINITION:
fmC_wavesel() - ADLIB Ports E0h - F5h

DESCRIPTION:

        D7     D6     D5     D4     D3     D2     D1     D0
   |------------------------------------------------------------|
   |                                           |   Wave Select  |
   |------------------------------------------------------------|

 00 - Sine Wave
 01 - Half rectified sine wave
 10 - Full rectified sine wave
 11 - Sine wave cut in half quarter period (full recified)

*/
void fmC_wavesel(void)
{
    if(voice_ptr->wave != fm_data)
        things_to_do |= (UPD_INSTRUMENT | UPD_FREQUENCY);

    voice_ptr->wave = fm_data&0x03;
}
