/*
 * bc_smpl.c
 *
 * Borland C interface, sound support
 *
 */

#include <alloc.h>
#include <dos.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "bc_frotz.h"

#define SWAP_BYTES(v)	{_AX=v;asm xchg al,ah;v=_AX;}

#define READ_DSP(v)	{while(!inportb(sound_adr+14)&0x80);v=inportb(sound_adr+10);}
#define WRITE_DSP(v)	{while(inportb(sound_adr+12)&0x80);outportb(sound_adr+12,v);}

static struct {
    word prefix;
    byte repeats;
    byte base_note;
    word frequency;
    word unused;
    word length;
} sheader;

static current_sample = 0;

static void interrupt (*vect) (void) = NULL;

static play_part = 0;
static play_count = 0;

static word sound_adr = 0;
static word sound_irq = 0;
static word sound_dma = 0;
static word sound_int = 0;
static word sound_ver = 0;

static byte far *sample_data = NULL;

static long sample_adr1 = 0;
static long sample_adr2 = 0;
static word sample_len1 = 0;
static word sample_len2 = 0;

/*
 * start_of_dma
 *
 * Start the DMA transfer to the sound board.
 *
 */

static void start_of_dma (long address, unsigned length)
{
    static unsigned dma_page_port[] = {
	0x87, 0x83, 0x81, 0x82
    };

    length--;

    /* Set up DMA chip */

    outportb (0x0a, 0x04 | sound_dma);
    outportb (0x0c, 0x00);
    outportb (0x0b, 0x48 | sound_dma);
    outportb (2 * sound_dma, byte0 (address));
    outportb (2 * sound_dma, byte1 (address));
    outportb (dma_page_port[sound_dma], byte2 (address));
    outportb (2 * sound_dma + 1, byte0 (length));
    outportb (2 * sound_dma + 1, byte1 (length));
    outportb (0x0a, sound_dma);

    /* Play 8-bit mono sample */

    WRITE_DSP (0x14)
    WRITE_DSP (byte0 (length))
    WRITE_DSP (byte1 (length))

}/* start_of_dma */

/*
 * end_of_dma
 *
 * This function is called when a hardware interrupt signals the
 * end of the current sound. We may have to play the second half
 * of the sound effect, or we may have to repeat it, or call the
 * end_of_sound function when we are finished.
 *
 */

static void interrupt end_of_dma (void)
{

    /* Play the second half, play another cycle or finish */

    if (play_part == 1 && sample_len2 != 0) {
	play_part = 2;
	start_of_dma (sample_adr2, sample_len2);
    } else if (play_count == 255 || --play_count != 0) {
	play_part = 1;
	start_of_dma (sample_adr1, sample_len1);
    } else {
	play_part = 0;
	end_of_sound ();
    }

    /* Tell interrupt controller(s) + sound board we are done */

    outportb (0x20, 0x20);

    if (sound_irq >= 8)
	outportb (0xa0, 0x20);

    inportb (sound_adr + 14);

}/* end_of_dma */

/*
 * init_sound
 *
 * Initialise the sound board and various sound related variables.
 *
 */

int init_sound (void)
{
    const char *settings = getenv ("BLASTER");
    word irc_mask_port;

    /* Read the IRQ, port address, DMA channel and SB version */

    if ((settings = getenv ("BLASTER")) == NULL)
	return 0;

    sound_irq = dectoi (strchr (settings, 'I') + 1);
    sound_adr = hextoi (strchr (settings, 'A') + 1);
    sound_dma = dectoi (strchr (settings, 'D') + 1);
    sound_ver = dectoi (strchr (settings, 'T') + 1);

    /* Reset mixer chip and DSP */

    outportb (sound_adr + 4, 0);
    outportb (sound_adr + 5, 0);

    outportb (sound_adr + 6, 1);
    inportb (sound_adr + 6);
    inportb (sound_adr + 6);
    inportb (sound_adr + 6);
    outportb (sound_adr + 6, 0);

    /* Turn on speakers */

    WRITE_DSP (0xd1)

    /* Install the end_of_dma interrupt */

    if (sound_irq < 8) {
	irc_mask_port = 0x21;
	sound_int = 0x08 + sound_irq;
    } else {
	irc_mask_port = 0xa1;
	sound_int = 0x68 + sound_irq;
    }

    vect = getvect (sound_int);

    setvect (sound_int, end_of_dma);

    /* Allocate 64KB RAM for sample data */

    if ((sample_data = (byte far *) farmalloc (0x10000L)) == NULL)
	return 0;

    word0 (sample_adr1) = FP_OFF (sample_data) | (FP_SEG (sample_data) << 4);
    word1 (sample_adr1) = FP_SEG (sample_data) >> 12;
    word0 (sample_adr2) = 0;
    word1 (sample_adr2) = word1 (sample_adr1) + 1;

    /* Enable the end_of_dma interrupt */

    outportb (0x20, 0x20);

    if (sound_irq >= 8)
	outportb (0xa0, 0x20);

    outportb (irc_mask_port, inportb (irc_mask_port) & ~(1 << (sound_irq & 7)));

    /* Indicate success */

    return 1;

}/* init_sound */

/*
 * reset_sound
 *
 * Free resources allocated for playing samples.
 *
 */

void reset_sound (void)
{

    os_stop_sample ();

    if (sample_data != NULL)
	farfree (sample_data);

    sample_data = NULL;

    if (sound_adr != 0)
	setvect (sound_int, vect);

    sound_adr = 0;

}/* reset_sound */

/*
 * os_beep
 *
 * Play a beep sound. Ideally, the sound should be high- (number == 1)
 * or low-pitched (number == 2).
 *
 */

void os_beep (int number)
{
    word T = 888 * number;

    outportb (0x43, 0xb6);
    outportb (0x42, lo (T));
    outportb (0x42, hi (T));
    outportb (0x61, inportb (0x61) | 3);

    delay (75);

    outportb (0x61, inportb (0x61) & ~3);

}/* os_beep */

/*
 * os_prepare_sample
 *
 * Load the sample from the disk.
 *
 */

void os_prepare_sample (int number)
{

    os_stop_sample ();

    /* Exit if the sound board isn't set up properly */

    if (sample_data == NULL)
	return;
    if (sound_adr == 0)
	return;

    /* Continue only if the desired sample is not already present */

    if (current_sample != number) {

	char sample_name[MAX_FILE_NAME + 1];
	char numstr[2];
	FILE *fp;

	/* Build sample file name */

	strcpy (sample_name, "sound\\");

	numstr[0] = '0' + number / 10;
	numstr[1] = '0' + number % 10;

	strncat (sample_name, stripped_story_name, 6);
	strncat (sample_name, numstr, 2);

	strcat (sample_name, ".snd");

	/* Open sample file */

	if ((fp = fopen (sample_name, "rb")) == NULL)
	    return;

	/* Load header and sample data */

	fread (&sheader, sizeof (sheader), 1, fp);

	SWAP_BYTES (sheader.frequency)
	SWAP_BYTES (sheader.length)

	fread (sample_data, 1, sheader.length, fp);

	sample_len1 = -word0 (sample_adr1);

	if (sample_len1 > sheader.length || sample_len1 == 0)
	    sample_len1 = sheader.length;

	sample_len2 = sheader.length - sample_len1;

	WRITE_DSP (0x40)
	WRITE_DSP (256 - 1000000L / sheader.frequency)

	current_sample = number;

	/* Close sample file */

	fclose (fp);

    }

}/* os_prepare_sample */

/*
 * os_start_sample
 *
 * Play the given sample at the given volume (ranging from 1 to 8 and
 * 255 meaning a default volume). The sound is played once or several
 * times in the background (255 meaning forever). In Z-code 3 the
 * repeats value is always 0 and the number of repeats is taken from
 * the sound file itself. The end_of_sound function is called as soon
 * as the sound finishes.
 *
 */

void os_start_sample (int number, int volume, int repeats)
{

    os_stop_sample ();

    /* Exit if the sound board isn't set up properly */

    if (sample_data == NULL)
	return;
    if (sound_adr == 0)
	return;

    /* Load new sample */

    os_prepare_sample (number);

    /* Continue only if the sample's now in memory */

    if (current_sample == number) {

	play_count = (h_version >= V5) ? repeats : sheader.repeats ? 1 : 255;

	if (sound_ver < 6) {	/* Set up SB pro mixer chip */

	    volume = (volume != 255) ? 7 + volume : 15;

	    outportb (sound_adr + 4, 0x04);
	    outportb (sound_adr + 5, (volume << 4) | volume);
	    outportb (sound_adr + 4, 0x22);
	    outportb (sound_adr + 5, 0xff);

	} else {		/* Set up SB 16 mixer chip */

	    /* Many thanks to Linards Ticmanis for writing this part! */

	    volume = (volume != 255) ? 127 + 16 * volume : 255;

	    outportb (sound_adr + 4, 0x32);
	    outportb (sound_adr + 5, volume);
	    outportb (sound_adr + 4, 0x33);
	    outportb (sound_adr + 5, volume);
	    outportb (sound_adr + 4, 0x30);
	    outportb (sound_adr + 5, 0xff);
	    outportb (sound_adr + 4, 0x31);
	    outportb (sound_adr + 5, 0xff);

	}

	play_part = 1;
	start_of_dma (sample_adr1, sample_len1);

    }

}/* os_start_sample */

/*
 * os_stop_sample
 *
 * Turn off the current sample.
 *
 */

void os_stop_sample (void)
{

    /* Exit if the sound board isn't set up properly */

    if (sample_data == NULL)
	return;
    if (sound_adr == 0)
	return;

    /* Tell DSP to stop the current sample */

    WRITE_DSP (0xd0)

}/* os_stop_sample */

/*
 * os_finish_with_sample
 *
 * Remove the current sample from memory (if any).
 *
 */

void os_finish_with_sample (void)
{

    os_stop_sample ();		/* we keep 64KB allocated all the time */

}/* os_finish_with_sample */

/*
 * os_wait_sample
 *
 * Stop repeating the current sample and wait until it finishes.
 *
 */

void os_wait_sample (void)
{

    play_count = 1;

    while (play_part);		/* wait for hardware interrupt */

}/* os_wait_sample */
