/* 
 * sidplay.cxx,v 1.7 1996/04/02 14:14:16 ms Exp
 * 
 * 
 * SIDPLAY for Linux
 *
 * Copyright (c) 1995,1996 Michael Schwendt
 * All rights reserved.
 * InterNet email: 3schwend@informatik.uni-hamburg.de
 *
 * Redistribution and use in source and binary forms, either unchanged or
 * modified, are permitted provided that the following conditions are met: 
 * 
 * (1) Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * (2) Redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
 * IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * sidplay.cxx,v
 * Revision 1.7  1996/04/02 14:14:16  ms
 * *** empty log message ***
 *
 * Revision 1.6  1996/03/27 01:03:05  ms
 * *** empty log message ***
 *
 * Revision 1.5  1996/03/25 07:49:25  ms
 * *** empty log message ***
 *
 * Revision 1.4  1996/02/29 08:39:57  ms
 * Major changes due to creation of class sidtune.cxx
 *
 * Revision 1.3.1.1  1996/02/24 01:24:09  ms
 * Fixed Sparcstation 10 support
 *
 * Revision 1.3  1996/02/21 06:50:21  ms
 * *** empty log message ***
 *
 * Revision 1.2  1996/02/16 14:38:36  3schwend
 * Added support for SPARCstations with DBRI device running SunOS 4.1.4
 * Using the sidemuconfig class
 *
 * Revision 1.1  1996/02/11 21:39:50  ms
 * Fixed #includes for FreeBSD; thanks to <aagero@aage.aage.priv.no>
 * Applied additional FreeBSD patches
 * Changes according to changes in mos_6581/6581.h
 * Changed/Fixed music data moving loops
 * Changed meaning of -a option; improves PlaySID compatibility
 * Added support for MUS file format (Stereo-/Sidplayer)
 *
 * Revision 1.0  1996/01/05 13:10:46  ms
 * Initial revision
 *
 */

#include <iostream.h>
#include <iomanip.h>
#include <fstream.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#ifdef linux
 #include <linux/soundcard.h>
#elif defined(__FreeBSD__)
 #include <machine/soundcard.h>
#elif defined(sun) && defined(sparc) && defined(solaris2)
 #include <sys/audioio.h>
#elif defined(sun) && defined(sparc)
 #include <sun/audioio.h>
 //#include <sun/dbriio.h>
#elif defined(sgi)
 #include <errno.h>
 #include <audio.h>
 #include <dmedia/audio.h>
#endif

#include "mytypes.h"
#include "sidtune.h"
#include "6581.h"

// before compiling the RCS distribution, consider doing
// a 'co -kv sidplay.cxx' to substitute the keyword string
#define version "1.7"

#if defined(linux) || defined(__FreeBSD__) 
 #define AUDIO "/dev/dsp"
#elif defined(sun) && defined(sparc)
 #define AUDIO "/dev/audio"
#endif

#define TXT_TITLE 1
#define ERR_NOT_ENOUGH_MEMORY 4
#define ERR_SYNTAX 5
#define ERR_IOCTL 6


#define FLAG_PLAYSID 1
#define FLAG_FORCENTSC 2


void error( char*, char* );
void printtext( int );


int main(int argc, char *argv[])
{
  // title
  printtext(TXT_TITLE);
  
  if ( argc < 2 )
	printtext(ERR_SYNTAX);
  
  //
  ubyte infile = 0;

  // defaults
  byte flags = 0;
  udword frequency = 22050;
  uword channels = SIDEMU_MONO;
  char selectedsong = 0;

  ubyte* samplebufferptr;
  uword fragments = 2;
  uword fragsizebase = 14;
    
  // parse command line arguments
  for ( int a = 1; a < argc; a++)  
  {
    if ( argv[a][0] == '-')  
    {
	  // reading from stdin
	  if ( strlen(argv[a]) == 1 ) 
		if ( infile == 0 )
	    {
	      infile = a;
	      break;
		}
	    else
		  printtext(ERR_SYNTAX);
      switch ( argv[a][1] )
      {
	   case 'a':
        flags |= FLAG_PLAYSID;
		break;
	   case 'b':
		if ( argv[a][2] == 'n' )  
		{
		  fragments = (unsigned)atoi(argv[a]+3);
		  if (( fragments < 2 ) || ( fragments > 255 )) 
			fragments = 2;
		}
		else if ( argv[a][2] == 's' )  
		{
		  fragsizebase = (unsigned)atoi(argv[a]+3);
		  if (( fragsizebase < 7 ) || ( fragsizebase > 17 )) 
			fragsizebase = 14;
		}
		break;
	   case 'f':
		frequency = (udword)atol(argv[a]+2);
		break;
	   case 'h':
		printtext(ERR_SYNTAX);
		break;
	   case 'o':
		selectedsong = atoi(argv[a]+2);
		break;
	   case 'n':
		flags |= FLAG_FORCENTSC;
		break;
	   default:
		printtext(ERR_SYNTAX);
		break;
      }
    }
    else  
    {
	  // filename argument
      if ( infile == 0 )  infile = a;
      else printtext(ERR_SYNTAX);
    }
  }

  sidtune mysid( argv[infile] );
  if ( !mysid )  
  {
	cerr << mysid.returnstatusstring() << endl;
	exit(-1);
  }
  else
  {
	cout << "File format  : " << mysid.returnformatstring() << endl;
	cout << "Condition    : " << mysid.returnstatusstring() << endl;
	if ( mysid.returnnumberofinfostrings() == 3 )
	{
	  cout << "Name         : " << mysid.returnnameinfo() << endl;
	  cout << "Author       : " << mysid.returnauthorinfo() << endl;
	  cout << "Copyright    : " << mysid.returncopyrightinfo() << endl;
	}
	else
	{
	  for ( int infoi = 0; infoi < mysid.returnnumberofinfostrings(); infoi++ )
		cout << "Description  : " << mysid.returninfostring(infoi) << endl;
	}
    cout << "Load address : $" << hex << setw(4) << setfill('0') 
	     << mysid.returnloadaddress() << endl;
    cout << "Init address : $" << hex << setw(4) << setfill('0') 
	     << mysid.returninitaddress() << endl;
    cout << "Play address : $" << hex << setw(4) << setfill('0') 
	     << mysid.returnplayaddress() << dec << endl;
  }
  
  if ( flags & FLAG_PLAYSID )
	mysid.setplaysidflag( TRUE );
  if ( flags & FLAG_FORCENTSC )
	mysid.setforcentsc( TRUE );
  
  // --- Begin of VoxWare dependent initialization part ---
  
#if defined(linux) || defined(__FreeBSD__)
  int audiohd = open(AUDIO, O_WRONLY, 0);
  if ( audiohd == -1 )
  { 
	cerr << "ERROR: Cannot open audio device " << AUDIO << endl;
    exit (-1);
  }
  
  int dsp_samplesize = 8;
  if ( ioctl(audiohd, SNDCTL_DSP_SAMPLESIZE, &dsp_samplesize) == -1 )  printtext(ERR_IOCTL);

  int dsp_stereo = 0;
  if ( ioctl(audiohd, SNDCTL_DSP_STEREO, &dsp_stereo ) == -1 )  printtext(ERR_IOCTL);

  int dsp_speed = frequency;
  if ( ioctl(audiohd, SNDCTL_DSP_SPEED, &dsp_speed ) == -1 )  printtext(ERR_IOCTL);

  // N fragments of size (2 ^ S) bytes
  //              NNNNSSSS
  // int frag = 0x0004000e;
  int frag = ( fragments << 16 ) + fragsizebase;
  ioctl(audiohd, SNDCTL_DSP_SETFRAGMENT, &frag);

  int frag_size;
  ioctl(audiohd, SNDCTL_DSP_GETBLKSIZE, &frag_size);
  cout << "Buffers      : " << fragments 
	   << "\nBuffersize   : " << (udword)(1 << fragsizebase) << "\n";
  
  if (( samplebufferptr = new ubyte[frag_size]) == 0 )  printtext(ERR_NOT_ENOUGH_MEMORY);
  
  // --- SunOS 4.1.4 with dbri driver dependent part ---
  
#elif defined(sparc) && defined(sun)
  
  int audiohd = open(AUDIO, O_WRONLY, 0);
  if ( audiohd == -1 )
  { 
	cerr << "ERROR: Cannot open audio device " << AUDIO << endl;
    exit (-1);
  }
#ifndef solaris2  
  int hwdevice;
  if ( ioctl( audiohd, AUDIO_GETDEV, &hwdevice ) == (-1))  printtext( ERR_IOCTL );
  if ( hwdevice != AUDIO_DEV_SPEAKERBOX )
  {
    cerr << "ERROR: Speakerbox not enabled" << endl;
    exit(-1);
  }
#endif
  // choose the nearest possible frequency
  uword dbrifreqs[] = 
  {
	8000, 9600, 11025, 16000, 18900, 22050, 32000, 37800, 44100, 48000, 0
  };
  int dbrifsel = 0;
  int dbrifreqdiff = 100000;
  int dbrifrequency = frequency;
  do
  {  
	int dbrifreqdiff2 = frequency - dbrifreqs[dbrifsel];
	dbrifreqdiff2 < 0 ? dbrifreqdiff2 = 0 - dbrifreqdiff2 : dbrifreqdiff2 += 0;
	if ( dbrifreqdiff2 < dbrifreqdiff )  
	{
	  dbrifreqdiff = dbrifreqdiff2;
	  dbrifrequency = dbrifreqs[dbrifsel];
	}
	dbrifsel++;
  }  while ( dbrifreqs[dbrifsel] != 0 );
  frequency = dbrifrequency;
  
  int dsp_samplesize = 16;

  audio_info myaudio_info;
  if ( ioctl( audiohd, AUDIO_GETINFO, &myaudio_info ) == (-1))  printtext(ERR_IOCTL);
  AUDIO_INITINFO( &myaudio_info );
  myaudio_info.play.sample_rate = frequency;
  if ( channels == SIDEMU_MONO )  myaudio_info.play.channels = 1;
  else if ( channels == SIDEMU_STEREO )  myaudio_info.play.channels = 2;
  myaudio_info.play.precision = dsp_samplesize;
  myaudio_info.play.encoding = AUDIO_ENCODING_LINEAR;
  myaudio_info.output_muted = 0;
  if ( ioctl( audiohd, AUDIO_SETINFO, &myaudio_info ) == (-1))  printtext(ERR_IOCTL);

  int bufsize = frequency;
  if (( samplebufferptr = new ubyte[bufsize]) == 0 )  printtext(ERR_NOT_ENOUGH_MEMORY);
  
  // --- Silicon Graphics Indigo dependent part
  
#elif defined(sgi)
  ALport audio;
  ALconfig config;
  
  long chpars[] = {AL_OUTPUT_RATE, 0};
 
  // Frequency
 
  chpars[1] = frequency;
  ALsetparams(AL_DEFAULT_DEVICE, chpars, 2);
  ALgetparams(AL_DEFAULT_DEVICE, chpars, 2);
  config = ALnewconfig();
 
 // Set sample format
 
  ALsetsampfmt(config, AL_SAMPFMT_TWOSCOMP);
 
 // Mono output
 
  ALsetchannels(config, AL_MONO);
 
 // 8-bit samplesize
 
  ALsetwidth(config, AL_SAMPLE_8);
 
 // Allocate an audio port
 
  if((audio = ALopenport("SIDPLAY sound", "w", config)) == NULL) {
	oserror();
	exit(1);
  }
 
 // Allocate sound buffers
  int bufsize = frequency;
  ALsetqueuesize(config, bufsize);
 
  if (( samplebufferptr = new ubyte[bufsize]) == 0 )  printtext(ERR_NOT_ENOUGH_MEMORY);
#endif /* SGI part */
 
  // emulator init
  sidemuconfig sidcfg;
  cout << "Frequency    : " << dec << sidcfg.setfrequency( frequency ) << " Hz" << endl;
  if ( dsp_samplesize != sidcfg.setbitspersample( dsp_samplesize ))
  {
	cerr << "SIDEMU: Not capable of requested sample precision" << endl;
    exit(-1);
  }
#if defined(linux) || defined(__FreeBSD__)
  if ( SIDEMU_UNSIGNED_PCM != sidcfg.setsampleformat( SIDEMU_UNSIGNED_PCM ))
#elif defined(sun) && defined(sparc)
  if ( SIDEMU_SIGNED_PCM != sidcfg.setsampleformat( SIDEMU_SIGNED_PCM ))
#elif defined(sgi)
  if ( SIDEMU_SIGNED_PCM != sidcfg.setsampleformat( SIDEMU_SIGNED_PCM ))
#else
#error Have to set sampleformat 
#endif	
  {
	cerr << "SIDEMU: Not capable of requested sample encoding" << endl;
    exit(-1);
  }
#if defined(linux) || defined(__FreeBSD__)
  //if ( SIDEMU_STEREO != sidcfg.setchannels( SIDEMU_STEREO ))
  //{
  //	cerr << "SIDEMU: Not capable of requested number of channels" << endl;
  //  exit(-1);
  //}
#endif	
  if ( sidemuinit( sidcfg ) == FALSE )  
  {
	cerr << "SIDEMU: Unable to allocate enough memory" << endl;
	exit(-1);
  }
  sidemureset( sidcfg );
  
  mysid.initializesong( selectedsong );
  if ( !mysid )
  {
	cerr << mysid.returnstatusstring();
    exit(-1);
  }
  cout << "Setting song : " << mysid.returncurrentsong()
       << " out of " << mysid.returnnumberofsongs() 
	   << " (default = " << mysid.returnstartsong() << ")\n";
  cout << "Song speed   : " << mysid.returnspeedstring() << endl;

  cout << "Playing, press ^C to stop ...\n" << flush;
  while ( TRUE )
  {
#if defined(linux) || defined(__FreeBSD__)
	sidemufillbuffer( sidcfg, mysid, samplebufferptr, frag_size );
	write( audiohd, samplebufferptr, frag_size );
#elif defined(sparc) && defined(sun)
	sidemufillbuffer( sidcfg, mysid, samplebufferptr, bufsize );
	write( audiohd, samplebufferptr, bufsize );
#elif defined(sgi)
	sidemufillbuffer( sidcfg, mysid, samplebufferptr, bufsize );
	ALwritesamps( audio, samplebufferptr, bufsize );
#endif	
  }

  // will possibly never come this far  
#if defined(linux) || defined(__FreeBSD__)
  close(audiohd);
#elif defined(sparc) && defined(sun)
  close(audiohd);
#elif defined(sgi)
  ALcloseport(audio);
  ALfreeconfig(config);
#endif
  delete(samplebufferptr);
  return(0);
}


void error(char* s1, char* s2 = "")
{
  cerr << "ERROR: " << s1 << ' ' << "'" << s2 << "'\n";
  exit(-1);
}


void printtext(int number)
{
  switch (number)  
  {
   case TXT_TITLE:
#ifdef __FreeBSD__
	  cout << endl << "SIDPLAY/FreeBSD";
#elif defined(linux)
	  cout << endl << "SIDPLAY/Linux";
#elif defined(solaris2)
	  cout << endl << "SIDPLAY/Solaris2";
#elif defined(sun)
	  cout << endl << "SIDPLAY/SunOS";
#elif defined(sgi)
	  cout << endl << "SIDPLAY/SGI";
#else
	  cout << endl << "SIDPLAY";
#endif	  
	  cout << "   Music player and C64 SID chip emulator   Version " << version << endl
	       << "Copyright (c) 1995,1996 Michael Schwendt   All rights reserved." << endl
           << "Internet e-mail: 3schwend@informatik.uni-hamburg.de" << endl;
#if defined(sgi)
	  cout << "Ported by <aagero@aage.aage.priv.no>" << endl;
#else 
	  cout << endl;
#endif	
      break;
    case ERR_NOT_ENOUGH_MEMORY:
	  cerr << "ERROR: Not enough memory\n";
      exit(-1);
    case ERR_SYNTAX:
      cout << " Syntax: SIDPLAY [-<commands>] -|[<datafile>] \n"
           << " commands: -h       display this screen\n"
           << "           -f<num>  set frequency in Hz (default: 22050)\n"
           << "           -o<num>  set song number (default: preset)\n"
           << "           -a       improve PlaySID compatibility (read the docs !)\n"
	       // following option is hidden, because it should *not* be used
	       // for more read the documentation to SIDPLAY/DOS
	       // << "           -n       force NTSC player speed\n"
           << "           -bn<num> set number of audio buffers to use\n"
           << "           -bs<num> set size 2^<num> of audio buffers\n\n";
      exit(-1);
    case ERR_IOCTL:
      cerr << "IOCTL: Could not set up sound device properly\n";
      exit(-1);
    default:
      cerr << "ERROR: Internal system error\n";
      exit(-1);
  }
}

