/**
 * TrueReality - output/audio/bsd_and_linux.c
 * Copyright (C) 1998, 1999 Niki W. Waibel
 *
 * This program is free software; you can redistribute it and/
 * or modify it under the terms of the GNU General Public Li-
 * cence as published by the Free Software Foundation; either
 * version 2 of the Licence, or any later version.
 *
 * This program is distributed in the hope that it will be use-
 * ful, but WITHOUT ANY WARRANTY; without even the implied war-
 * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public Licence for more details.
 *
 * You should have received a copy of the GNU General Public
 * Licence along with this program; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
 * USA.
 *
 * Information about me (the author):
 *   Niki W. Waibel, Reichenau 20, 6890 Lustenau, Austria - EUROPE
 *   niki.waibel@gmx.net
**/





/**
 *
 * All of the ideas in this file are from
 * Guenther Sohler <guenther.sohler@newlogic.at>
 *
 * I, Niki W. Waibel, made this stuff work in TrueReality.
 *  o cleaned initialisation stuff
 *  o added stereo and "downsample" support.
 *  o added "write sound to file" stuff.
 *  o added "prefs" stuff - now you can choose sound via command line.
 *
**/




/* DOWNSAMPLE: use 0, 1, 2, 3, etc for /1, /2, /4, /8 etc */

#define DOWN_SAMPLE 0





#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#ifdef LINUX
#include <linux/soundcard.h>
#endif
#ifdef BSD
#include <machine/soundcard.h>
#endif
#include <math.h>

#include "audio.h"
#include "bsd_and_linux.h"

#include <type_sizes.h>
#include <memory_extern.h>
#include <parser_extern.h>
#include "../../N64/interrupts.h"





static int      audiofd;
static int      buffer_location;
static int      BUFFER_SIZE;





int OpenAudio()
{
        int             status;
        audio_buf_info  abi;



    /* open audio device - exit if NO_SOUND and later if FILE_SOUND */

        switch(prefs.sound)
        {
            case NO_SOUND:
                puts("No Sound ... OK\n"
                     "-------------------------------------------------------------------------------");
                fflush(stdout);

                return(0);   /* Exit here. There is nothing else to do! */

            case NORMAL_SOUND:
                fputs("Opening Linux audio device /dev/dsp (ideas by Guenther Sohler) ...", stdout);
                break;
        
            case FILE_SOUND:
                printf("Opening audio file (%s) (ideas by Niki W. Waibel) ...", prefs.sound_file);
                break;
        
        } /* switch(prefs.sound) */

        fflush(stdout);



        if(prefs.sound == NORMAL_SOUND)
                audiofd = open("/dev/dsp", O_RDWR);
        else if(prefs.sound == FILE_SOUND)
                audiofd = open(prefs.sound_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP);

        if(audiofd < 0)
	{
		puts(" FAIL\n"
                     "Turn off audio!!!"); fflush(stdout);
                prefs.sound = NO_SOUND;
		return(-1);
	}



        if(prefs.sound == FILE_SOUND)
        {
                puts(" OK\n"
                     "-------------------------------------------------------------------------------");
                fflush(stdout);

                return(0);   /* Exit here. There is nothing else to do! */

        } /* if(prefs.sound == FILE_SOUND) */



    /* reset */

        fputs(" OK\n"
              "Reset ...", stdout); fflush(stdout);

	if(ioctl(audiofd, SNDCTL_DSP_RESET) == -1)
	{
		puts(" FAIL\n"
                     "Turn off audio!!!"); fflush(stdout);
                prefs.sound = NO_SOUND;
                return(-1);
	}



    /* determine output buffer size */

        fputs(" OK\n"
              "Get audio output buffer size ...", stdout); fflush(stdout);

        status = ioctl(audiofd, SNDCTL_DSP_GETOSPACE, &abi);
        if(status == -1)
        {
		puts(" FAIL\n"
                     "Turn off audio!!!"); fflush(stdout);
                prefs.sound = NO_SOUND;
                return(-1);
        }
        BUFFER_SIZE = abi.bytes;
        buffer_location = 0;



    /* determine possible audio formats */

        fputs(" OK\n"
              "Get audio formats ...", stdout); fflush(stdout);

        if(ioctl(audiofd, SNDCTL_DSP_GETFMTS, &status) == -1)
        {
		puts(" FAIL\n"
                     "Turn off audio!!!"); fflush(stdout);
                prefs.sound = NO_SOUND;
                return(-1);
        }

        puts(" OK");

        if(status & AFMT_MU_LAW)
		puts("   MU_LAW");
        if(status & AFMT_A_LAW)
		puts("   A_LAW");
        if(status & AFMT_IMA_ADPCM)
		puts("   IMA_ADPCM");
        if(status & AFMT_U8)
		puts("   U8");
        if(status & AFMT_S16_LE)
		puts("   S16_LE");
        if(status & AFMT_S16_BE)
		puts("   S16_BE");
        if(status & AFMT_S8)
		puts("   S8");
        if(status & AFMT_U16_LE)
		puts("   U16_LE");
        if(status & AFMT_U16_BE)
		puts("   U16_BE");
        if(status & AFMT_MPEG)
		puts("   MPEG");

        if(status & AFMT_S16_BE)
	{
                fputs("   ... S16_BE is supported\n"
                      "Trying to set audio format to S16_BE ...", stdout); fflush(stdout);

                status = AFMT_S16_BE;
		if(ioctl(audiofd, SNDCTL_DSP_SETFMT, &status) == -1)
		{
                        puts(" FAIL\n"
                             "Turn off audio!!!"); fflush(stdout);
                        prefs.sound = NO_SOUND;
                        return(-1);
		}
	}
        else if(status & AFMT_S16_LE)
	{
                fputs("   ... S16_LE is supported\n"
                      "Trying to set audio format to S16_LE ...", stdout); fflush(stdout);
	
                status = AFMT_S16_LE;
		if(ioctl(audiofd, SNDCTL_DSP_SETFMT, &status) == -1)
		{
                        puts(" FAIL\n"
                             "Turn off audio!!!"); fflush(stdout);
                        prefs.sound = NO_SOUND;
                        return(-1);
		}
	}
        else
        {
                puts("   ... S16_BE is not supported ... turn off audio!!!"); fflush(stdout);
                prefs.sound = NO_SOUND;
                return(-1);
        }



    /* determine possible audio capabilities */

        fputs(" OK\n"
              "Get audio capabilities ...", stdout); fflush(stdout);

        if(ioctl(audiofd, SNDCTL_DSP_GETCAPS, &status) == -1)
        {
                puts(" FAIL\n"
                     "Turn off audio!!!"); fflush(stdout);
                prefs.sound = NO_SOUND;
                return(-1);
        }

        puts(" OK");

        printf("   Revision: $%02x (%03d)\n", status & 0xff, status & 0xff);
        if(status & DSP_CAP_DUPLEX)
		puts("   DUPLEX");
        if(status & DSP_CAP_REALTIME)
		puts("   REALTIME");
        if(status & DSP_CAP_BATCH)
		puts("   BATCH");
        if(status & DSP_CAP_COPROC)
		puts("   COPROC");
        if(status & DSP_CAP_TRIGGER)
		puts("   TRIGGER");
        if(status & DSP_CAP_MMAP)
		puts("   MMAP");



    /* set channels to 2 (stereo) */

        fputs("Set audio channels to 2 ...", stdout); fflush(stdout);

	status = 2;
	if(ioctl(audiofd, SNDCTL_DSP_CHANNELS, &status) == -1)
	{
                puts(" FAIL\n"
                     "Turn off audio!!!"); fflush(stdout);
                prefs.sound = NO_SOUND;
	}



    /* set frequency to INIT_FREQ */

        printf(" OK\n"
               "Set speed (frequency) to %d ...", INIT_FREQ); fflush(stdout);

	status = INIT_FREQ;
	if(ioctl(audiofd, SNDCTL_DSP_SPEED, &status) == -1)
	{
                puts(" FAIL\n"
                     "Turn off audio!!!"); fflush(stdout);
                prefs.sound = NO_SOUND;
	}



        puts(" OK\n"
             "-------------------------------------------------------------------------------"); fflush(stdout);

        return(0);

} /* int OpenAudio() */





int CloseAudio()
{
        switch(prefs.sound)
        {
            case NO_SOUND:
                fputs("No Sound ...", stdout); fflush(stdout);
                break;

            case NORMAL_SOUND:
                fputs("Closing Linux audio device /dev/dsp (ideas by Guenther Sohler) ...", stdout);
                fflush(stdout);
                if(ioctl(audiofd, SNDCTL_DSP_SYNC) == -1)
                {
                        puts(" FAIL"); fflush(stdout);
                        return(-1);
                }
                close(audiofd);
                break;
        
            case FILE_SOUND:
                printf("Closing audio file (%s) (ideas by Niki W. Waibel) ...", prefs.sound_file);
                fflush(stdout);
                close(audiofd);
                break;
        
        } /* switch(prefs.sound) */


	puts(" OK\n"  
             "-------------------------------------------------------------------------------"); fflush(stdout);

        return(0);

} /* int CloseAudio() */





void UpdateAudio()
{
        static WORD     audiofreq;
        static char     *audioptr;
        static WORD     audiolen;
	int             status;





        if(prefs.sound == NO_SOUND)
                return;



	if(audiofreq != (( (93750000 >> 1) / (((WORD *)mem.ai_reg)[4] & 0x3ffff) + 1) >> DOWN_SAMPLE))
	{
		audiofreq = ((93750000 >> 1) / (((WORD *) mem.ai_reg)[4] & 0x3ffff) + 1) >> DOWN_SAMPLE;

                if(prefs.sound == NORMAL_SOUND)
                {
                        status = ioctl(audiofd, SNDCTL_DSP_SPEED, &audiofreq);
                        if(status == -1)
                        {
                                puts("WARNING: UpdateAudio: Could not set audio frequency");
                        }
                }
	}



	if(audioptr != (char *)(mem.rd_ram + (((WORD *)mem.ai_reg)[0] & 0xfffff8)))
	{
		audioptr = (((WORD *)mem.ai_reg)[0] & 0xfffff8) + mem.rd_ram;
	}



        if(audiolen != (((WORD *)mem.ai_reg)[1] & 0x3fff8))
        {
		audiolen = ((WORD *)mem.ai_reg)[1] & 0x3fff8;
        }



        /* BUG: ??? */
        /*
        ((WORD *)mem.ai_reg)[3] = 0xc0000001;
        */

#ifdef DEBUG
        if(prefs.debug & DBG_AUDIO)
        {
                printf("AUDIO: Frequency: %ld\n", audiofreq);
                printf("AUDIO: Address:   %08lx\n", ((WORD *)mem.ai_reg)[0]);
                printf("AUDIO: Length:    %ld\n", audiolen);
        }
#endif

        write(audiofd, audioptr, audiolen);


    /* not used yet */
        buffer_location += audiolen;

} /* void UpdateAudio() */





int ReadyAudio()
{
/*
        count_info info;


        if( (((WORD *)mem.ai_reg)[2] & 1 ) && (((WORD *)mem.ai_reg)[1] != 0) )
        {
                if(ioctl(audiofd, SNDCTL_DSP_GETOPTR, &info) == -1)
                {
                        puts("Warning: unable to determine current audio postion!\n");
                        info.bytes = 0;
                }

                printf("AUDIO: bytes: %d\n", info.bytes);

                if( info.bytes <= ((((WORD *)mem.ai_reg)[1] & 0x3fff8)) )
                        return(!AUDIO_DMA_READY);
                else
                        return(AUDIO_DMA_READY);
        }
        else
*/        {
                puts("NO DMA or LEN == 0");
                return(AUDIO_DMA_READY);
        }

} /* int ReadyAudio() */





