/*
  Original written for 'rsynth' and placed in public domain by  
  by B. Stuyts <benstn@olivetti.nl> 21-feb-94.  
  Perl modifications Copyright (c) 1997 Nick Ing-Simmons. 
  All rights reserved. This program is free software; you can redistribute it 
  and/or modify it under the same terms as Perl itself.
*/

#include <EXTERN.h>
#include <perl.h>
#include <XSUB.h>
#ifdef HAVE_LIBC_H
#include <libc.h>
#endif
#include <sound/sound.h>

/* Nested dynamic loaded extension magic ... */
#include "../../Data/Audio.m"  
AudioVtab     *AudioVptr;

/* Platform specific C level "object" data structure */
typedef struct
 {
  SNDSoundStruct *sound;
  long samp_rate;
  float gain; 
 } play_audio_t;

int
audio_init(play_audio_t *dev,int wait)
{
 dev->samp_rate = SND_RATE_CODEC;
 dev->sound     = NULL;
 dev->gain      = 1.0;
 return 1;
}

void
audio_play16(play_audio_t *dev, int n, short *data)
{
 if (n > 0)
  {
   SNDSoundStruct *sound = dev->sound;
   int err;
   if (!sound)
    {
     /* I hate these magic numbers - NI-S */
     err = SNDAlloc(&dev->sound, 1000000, SND_FORMAT_LINEAR_16, dev->samp_rate, 1, 0); 
     if (err)
      croak("SNDAlloc:%s",SNDSoundError(err));
     sound = dev->sound;
    }
   else
    {
     /* Wait for previous sound to finish before changing fields in sound */
     /* I hate these magic numbers - NI-S */
     err = SNDWait(0);
     if (err)
      croak("SNDWait:%s",SNDSoundError(err));
    }

   /* copying to buffer is a pain - should really convert into the buffer,
      unless "double buffer" means it it easier to keep up...
    */

   sound->dataSize = n * sizeof(short);
   /* Patch from  benstn@olivetti.nl (Ben Stuyts)
      Thanks to ugubser@avalon.unizh.ch for finding out why the NEXTSTEP
      version of rsynth didn't work on Intel systems. As suspected, it was a
      byte-order   problem. 
    */
#if i386
   swab((char *) data, (char *) sound + sound->dataLocation, n * sizeof(short));
#else /* i386 */
   bcopy(data, (char *) sound + sound->dataLocation, n * sizeof(short));
#endif

   /* I hate these magic numbers - NI-S */
   err = SNDStartPlaying(sound, 1, 5, 0, 0, 0);
   if (err)
    croak("SNDStartPlaying:%s",SNDSoundError(err));
  }
}

void
audio_flush(play_audio_t *dev)
{
 /* I hate these magic numbers - NI-S */
 inr err = SNDWait(0);
 if (err)
  croak("SNDWait:%s",SNDSoundError(err));
}

void
audio_DESTROY(play_audio_t *dev)
{
 SNDSoundStruct *sound = dev->sound;
 int err; 

 if (!sound)
  return;

 audio_flush(dev);

 if (err = SNDFree(sound))
  croak("SNDFree:%s",SNDSoundError(err));
 dev->sound = NULL;
}

IV
audio_rate(play_audio_t *dev,IV rate)
{
 IV old = dev->samp_rate;
 if (rate)
  {
   /* rate != 0 is setting the rate */
   if (dev->sound)
    {
     /* In progress - wait for it, and free that one - re-use DESTROY code */
     audio_DESTROY(dev); 
    }
   /* Note rate for allocation on next call to audio_play16() */
   dev->samp_rate = rate;
  }
 return old;
}

float
audio_gain(play_audio_t *dev,float gain)
{
 float prev_gain = dev->gain;
 if (gain >= 0.0)
  {
   if (gain != 1.0)
    warn("Cannot change audio gain");
   /* If you can tell me how,
      otherwise we could multiply out during conversion to short.
      ... NI-S 
   */
  }
 return prev_gain;
}


/*
   API level Play function 
    - volume may go from the interface - it is un-natural
    - convert to 'short' should be done at Audio::Play level 
    - likewise rate-matching needs to be higher level
*/
void
audio_play(play_audio_t *dev, Audio *au, float volume)
{
 STRLEN samp = Audio_samples(au);
 SV *tmp = Audio_shorts(au);
 if (volume >= 0)
  audio_gain(dev, volume);

 if (au->rate != audio_rate(dev,0))
  audio_rate(dev, au->rate);           /* Or re-sample to dev's rate ??? */

 audio_play16(dev, samp, (short *) SvPVX(tmp));
 SvREFCNT_dec(tmp);
}

/* Methods to select speaker and/or headphones etc. need adding
   if possible ...

   List of "valid" sample rates would be good too.

*/


MODULE = Audio::Play::#OSNAME#	PACKAGE=Audio::Play::#OSNAME#	PREFIX = audio_

PROTOTYPES: DISABLE

play_audio_t *
audio_new(class,wait = 1)
char *	class
IV	wait
CODE:
 {static play_audio_t buf;
  if (!audio_init(RETVAL = &buf,wait))
   {
    XSRETURN_NO;
   }
 }
OUTPUT:
 RETVAL

void
audio_DESTROY(dev)
play_audio_t *	dev

void
audio_flush(dev)
play_audio_t *	dev

float
audio_gain(dev,val = -1.0)
play_audio_t *	dev
float	val

IV
audio_rate(dev,rate = 0)
play_audio_t *	dev
IV	rate

void
audio_play(dev, au, vol = -1.0)
play_audio_t *	dev
Audio *		au;
float		vol

BOOT:
 {
  /* Nested dynamic loaded extension magic ... */
  AudioVptr = (AudioVtab *) SvIV(perl_get_sv("Audio::Data::AudioVtab",5)); 
 }