 /*
  * UAE - The Un*x Amiga Emulator
  *
  * OS specific functions
  *
  * Copyright 1995, 1996, 1997 Bernd Schmidt
  * Copyright 1996 Marcus Sundberg
  * Copyright 1996 Manfred Thole
  */

#include "sysconfig.h"
#include "sysdeps.h"

#include "config.h"
#include "options.h"
#include "memory.h"
#include "custom.h"
#include "gensound.h"
#include "sounddep/sound.h"
#include "events.h"
#include "audio.h"

int sound_available = 0;

struct audio_channel_data audio_channel[4];

int sound_table[256][64];
unsigned long int sample_evtime;

void init_sound_table16(void)
{
    int i,j;

    for (i = 0; i < 256; i++)
	for (j = 0; j < 64; j++)
	    sound_table[i][j] = j * (uae_s8)i * (currprefs.stereo ? 2 : 1);
}

void init_sound_table8 (void)
{
    int i,j;

    for (i = 0; i < 256; i++)
	for (j = 0; j < 64; j++)
	    sound_table[i][j] = (j * (uae_s8)i * (currprefs.stereo ? 2 : 1)) / 256;
}

void AUDxDAT(int nr, uae_u16 v)
{
#ifndef DONT_WANT_SOUND
    struct audio_channel_data *cdp = audio_channel + nr;
    cdp->dat = v;
    if (cdp->state == 0 && !(INTREQR() & (0x80 << nr))) {
	cdp->state = 2;
	INTREQ(0x8000 | (0x80 << nr));
	/* data_written = 2 ???? */
	eventtab[ev_aud0 + nr].evtime = cycles + cdp->per;
	eventtab[ev_aud0 + nr].oldcycles = cycles;
	eventtab[ev_aud0 + nr].active = 1;
	events_schedule();
    }
#endif
}

#ifdef MULTIPLICATION_PROFITABLE
#define DO_CHANNEL_1(c) do { thisdata = (uae_s8)audio_channel[c].current_sample * audio_channel[c].vol; } while (0)
#define FINISH_DATA(b,logn) do { if (14 - (b) + (logn) > 0) data >>= 14 - (b) + (logn); else data <<= (b) - 14 - (logn); } while (0);
#else
#define DO_CHANNEL_1(c) do { thisdata = sound_table[audio_channel[c].current_sample][audio_channel[c].vol]; } while (0)
#define FINISH_DATA(b,logn)
#endif

#ifdef BRANCHES_ARE_EXPENSIVE
#define INIT_ADK(adk) ((adk) | ((adk) >> 4))
#define DO_CHANNEL(c) do { uae_u32 thisdata; DO_CHANNEL_1(c); data += thisdata & (((adk >> c) & 1) - 1); } while (0);
#else
#define INIT_ADK(adk) (adk)
#define DO_CHANNEL(c) do { uae_u32 thisdata; if (! (adk & (0x11 << c))) { DO_CHANNEL_1(c); data += thisdata; } } while (0);
#endif

/* Templates! I want templates! */
void sample16_handler(void)
{
#ifndef DONT_WANT_SOUND
    int adk;
    uae_u32 data = SOUND16_BASE_VAL;

    eventtab[ev_sample].evtime = cycles + sample_evtime;
    eventtab[ev_sample].oldcycles = cycles;

    adk = INIT_ADK (adkcon);

    DO_CHANNEL (0);
    DO_CHANNEL (1);
    DO_CHANNEL (2);
    DO_CHANNEL (3);
    FINISH_DATA(16, 2);
    PUT_SOUND_WORD (data);

    check_sound_buffers ();
#endif
}

void sample8_handler(void)
{
#ifndef DONT_WANT_SOUND
    int adk;
    uae_u32 data = SOUND8_BASE_VAL;

    eventtab[ev_sample].evtime = cycles + sample_evtime;
    eventtab[ev_sample].oldcycles = cycles;

    adk = INIT_ADK (adkcon);

    DO_CHANNEL (0);
    DO_CHANNEL (1);
    DO_CHANNEL (2);
    DO_CHANNEL (3);
    FINISH_DATA(8, 2);
    PUT_SOUND_BYTE (data);

    check_sound_buffers ();
#endif
}

#ifdef HAVE_STEREO_SUPPORT
void sample16s_handler(void)
{
#ifndef DONT_WANT_SOUND
    int adk;
    uae_u32 data = SOUND16_BASE_VAL;

    eventtab[ev_sample].evtime = cycles + sample_evtime;
    eventtab[ev_sample].oldcycles = cycles;

    adk = INIT_ADK (adkcon);

    DO_CHANNEL (0);
    DO_CHANNEL (3);
    FINISH_DATA (16, 1);
    PUT_SOUND_WORD_RIGHT (data);

    data = SOUND16_BASE_VAL;
    DO_CHANNEL (1);
    DO_CHANNEL (2);
    FINISH_DATA (16, 1);
    PUT_SOUND_WORD_LEFT (data);

    check_sound_buffers ();
#endif
}

void sample8s_handler(void)
{
#ifndef DONT_WANT_SOUND
    int adk;
    uae_u32 data = SOUND8_BASE_VAL;

    eventtab[ev_sample].evtime = cycles + sample_evtime;
    eventtab[ev_sample].oldcycles = cycles;

    adk = INIT_ADK (adkcon);

    DO_CHANNEL (0);
    DO_CHANNEL (3);
    FINISH_DATA (8, 1);
    PUT_SOUND_BYTE_RIGHT (data);

    data = SOUND8_BASE_VAL;
    DO_CHANNEL (1);
    DO_CHANNEL (2);
    FINISH_DATA (8, 1);
    PUT_SOUND_BYTE_LEFT (data);
    check_sound_buffers ();
#endif
}
#else
void sample8s_handler(void)
{
    sample8_handler();
}
void sample16s_handler(void)
{
    sample16_handler();
}
#endif

static uae_u8 int2ulaw(int ch)
{
    int mask;

    if (ch < 0) {
      ch = -ch;
      mask = 0x7f;
    }
    else {
      mask = 0xff;
    }

    if (ch < 32) {
	ch = 0xF0 | ( 15 - (ch/2) );
    } else if (ch < 96) {
	ch = 0xE0 | ( 15 - (ch-32)/4 );
    } else if (ch < 224) {
	ch = 0xD0 | ( 15 - (ch-96)/8 );
    } else if (ch < 480) {
	ch = 0xC0 | ( 15 - (ch-224)/16 );
    } else if (ch < 992 ) {
	ch = 0xB0 | ( 15 - (ch-480)/32 );
    } else if (ch < 2016) {
	ch = 0xA0 | ( 15 - (ch-992)/64 );
    } else if (ch < 4064) {
	ch = 0x90 | ( 15 - (ch-2016)/128 );
    } else if (ch < 8160) {
	ch = 0x80 | ( 15 - (ch-4064)/256 );
    } else {
	ch = 0x80;
    }
    return (uae_u8)(mask & ch);
}

void sample_ulaw_handler(void)
{
#ifndef DONT_WANT_SOUND
    int nr;
    uae_u32 data = 0;

    eventtab[ev_sample].evtime += cycles - eventtab[ev_sample].oldcycles;
    eventtab[ev_sample].oldcycles = cycles;

    for (nr = 0; nr < 4; nr++) {
	if (!(adkcon & (0x11 << nr)))
	    data += sound_table[audio_channel[nr].current_sample][audio_channel[nr].vol];
    }
    PUT_SOUND_BYTE (int2ulaw (data));
    check_sound_buffers ();
#endif
}

#ifndef DONT_WANT_SOUND
static void audio_handler(int nr)
{
    struct audio_channel_data *cdp = audio_channel + nr;

    switch (cdp->state) {
     case 0:
	fprintf(stderr, "Bug in sound code\n");
	break;

     case 1:
	/* We come here at the first hsync after DMA was turned on. */
	eventtab[ev_aud0 + nr].evtime += maxhpos;
	eventtab[ev_aud0 + nr].oldcycles += maxhpos;

	cdp->state = 5;
	INTREQ(0x8000 | (0x80 << nr));
	if (cdp->wlen != 1)
	    cdp->wlen--;
	cdp->nextdat = chipmem_bank.wget(cdp->pt);

	cdp->pt += 2;
	break;

     case 5:
	/* We come here at the second hsync after DMA was turned on. */
	if (currprefs.produce_sound == 0)
	    cdp->per = 65535;

	eventtab[ev_aud0 + nr].evtime = cycles + cdp->per;
	eventtab[ev_aud0 + nr].oldcycles = cycles;
	cdp->dat = cdp->nextdat;
	cdp->current_sample = (uae_u8)(cdp->dat >> 8);

	cdp->state = 2;
	{
	    int audav = adkcon & (1 << nr);
	    int audap = adkcon & (16 << nr);
	    int napnav = (!audav && !audap) || audav;
	    if (napnav)
		cdp->data_written = 2;
	}
	break;

     case 2:
	/* We come here when a 2->3 transition occurs */
	if (currprefs.produce_sound == 0)
	    cdp->per = 65535;

	cdp->current_sample = (uae_u8)(cdp->dat & 0xFF);
	eventtab[ev_aud0 + nr].evtime = cycles + cdp->per;
	eventtab[ev_aud0 + nr].oldcycles = cycles;

	cdp->state = 3;

	/* Period attachment? */
	if (adkcon & (0x10 << nr)) {
	    if (cdp->intreq2 && cdp->dmaen)
		INTREQ(0x8000 | (0x80 << nr));
	    cdp->intreq2 = 0;

	    cdp->dat = cdp->nextdat;
	    if (cdp->dmaen)
		cdp->data_written = 2;
	    if (nr < 3) {
		if (cdp->dat == 0)
		    (cdp+1)->per = 65535;

		else if (cdp->dat < maxhpos/2 && currprefs.produce_sound < 3)
		    (cdp+1)->per = maxhpos/2;
		else
		    (cdp+1)->per = cdp->dat;
	    }
	}
	break;

     case 3:
	/* We come here when a 3->2 transition occurs */
	if (currprefs.produce_sound == 0)
	    cdp->per = 65535;

	eventtab[ev_aud0 + nr].evtime = cycles + cdp->per;
	eventtab[ev_aud0 + nr].oldcycles = cycles;

	if ((INTREQR() & (0x80 << nr)) && !cdp->dmaen) {
	    cdp->state = 0;
	    cdp->current_sample = 0;
	    eventtab[ev_aud0 + nr].active = 0;
	    break;
	} else {
	    int audav = adkcon & (1 << nr);
	    int audap = adkcon & (16 << nr);
	    int napnav = (!audav && !audap) || audav;
	    cdp->state = 2;

	    if ((cdp->intreq2 && cdp->dmaen && napnav)
		|| (napnav && !cdp->dmaen))
		INTREQ(0x8000 | (0x80 << nr));
	    cdp->intreq2 = 0;

	    cdp->dat = cdp->nextdat;
	    cdp->current_sample = (uae_u8)(cdp->dat >> 8);

	    if (cdp->dmaen && napnav)
		cdp->data_written = 2;

	    /* Volume attachment? */
	    if (audav) {
		if (nr < 3)
		    (cdp+1)->vol = cdp->dat;
	    }
	}
	break;

     default:
	cdp->state = 0;
	eventtab[ev_aud0 + nr].active = 0;
	break;
    }
}

void aud0_handler(void)
{
    audio_handler(0);
}
void aud1_handler(void)
{
    audio_handler(1);
}
void aud2_handler(void)
{
    audio_handler(2);
}
void aud3_handler(void)
{
    audio_handler(3);
}
#endif
