/* CSL - Common Sound Layer
 * Copyright (C) 2000-2001 Stefan Westerfeld and Tim Janik
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include	<csl/cslsample.h>
#include	<csl/cslmain.h>
#include	<csl/cslutils.h>
#include	<csl/cslprivate.h>

#include	<sys/types.h>
#include	<sys/stat.h>
#include	<errno.h>
#include	<fcntl.h>
#include	<unistd.h>

/**
 * @file cslsample.c
 *
 * The CSL sample API provides a simple means for playing PCM sound
 * samples. You create samples from files or PCM sound data, and send
 * them to the sound server to be played. You can reuse the sample
 * multiple times and share samples with other apps if they are
 * using the same sound server.
 *
 * Typical usage will follow these steps:
 *
 * @li Call @ref csl_sample_new to create an empty sample.
 * @li Call @ref csl_sample_write to write data into sample.
 * @li Call @ref csl_sample_write_done to indicate end of writing sample data.
 * @li Alternatively you can call @ref csl_sample_new_from_file instead of the above three steps,
 * if you have an existing sound file.
 * @li Call @ref csl_sample_play to play the sample.
 * @li Call @ref csl_sample_release when finished using the sample.
 *
 * Samples are cached in the sound server as long as they are active
 * (i.e. until released using @ref csl_sample_release). Using
 * @ref csl_sample_cache_add a sample can be permanently added to
 * the sample cache (at least until the server is restarted). Samples
 * can be loaded from the cache using @ref csl_sample_new_from_cache
 * and removed using @ref csl_sample_cache_remove. The caching behaviour
 * may vary depending on which backend is used.
 */

/**
 * This creates a new (empty) sample which can be played back later.
 * In order to use the sample you need to write PCM data into
 * it using @ref csl_sample_write.
 *
 * @param driver A pointer to a previously opened CSL driver.
 * @param name A name for the new sample. You should give a reasonable
 * name with extension (like "bong.wav"), as the implementation might
 * depend on the extension for figuring out the type.
 * @param sample_p A pointer to a pointer to the newly created sample.
 * @return Error code indicating success or failure.
 * @see csl_sample_new_from_file
 * @see csl_sample_write
 * @short Create a new sample
 */
CslErrorType
csl_sample_new (CslDriver *driver,
		const char *name,
		const char *role,
		const char *title,
		CslSample **sample_p)
{
  CslErrorType result;

  csl_return_val_if_fail (driver != NULL, CSL_EINTERN);
  csl_return_val_if_fail (driver->sample_vtable != NULL, CSL_EINTERN);
  csl_return_val_if_fail (name != NULL, CSL_EINTERN);
  csl_return_val_if_fail (sample_p != NULL, CSL_EINTERN);
  csl_return_val_if_fail (role != NULL, CSL_EINTERN);
  if (!title)
    title = role;

  DRIVER_LOCK (driver);
  result = driver->sample_vtable->sample_new (driver, name, role, title, sample_p);
  DRIVER_UNLOCK (driver);
  
  return result;
}

/**
 * This creates a sample from an existing sound file (e.g. .wav file). It is
 * equivalent to calling @ref csl_sample_new, writing the file contents
 * using @ref csl_sample_write, and then calling @ref csl_sample_write_done.
 *
 * @param driver A pointer to a previously opened CSL driver.
 * @param name A name for the new sample. This should be the name of
 * an existing sound file, with extension (like "bong.wav"). After
 * this call the sample is ready to be played.
 * @param sample_p A pointer to a pointer to the newly created sample.
 * @return Error code indicating success or failure.
 * @see csl_sample_new
 * @see csl_sample_write
 * @short Create a sample from a file
 */
CslErrorType
csl_sample_new_from_file (CslDriver  *driver,
			  const char *name,
			  const char *role,
			  const char *title,
			  CslSample **sample_p)
{
  CslErrorType result;

  csl_return_val_if_fail (driver != NULL, CSL_EINTERN);
  csl_return_val_if_fail (driver->sample_vtable != NULL, CSL_EINTERN);
  csl_return_val_if_fail (name != NULL, CSL_EINTERN);
  csl_return_val_if_fail (sample_p != NULL, CSL_EINTERN);
  csl_return_val_if_fail (role != NULL, CSL_EINTERN);
  if (!title)
    title = role;

  result = csl_sample_new (driver, name, role, title, sample_p);
  if (result == CSL_ENONE)
    {
      int fd = open (name, O_RDONLY);

      if (fd >= 0)
	{
	  char buffer[4096];
	  int len;		

	  do
	    {
	      do
		len = read (fd, buffer, sizeof(buffer));
	      while (len < 0 && errno == EINTR);
	      
	      if (len > 0)
		csl_sample_write (*sample_p, len, buffer);
	    }
	  while (len > 0);
	  close (fd);

	  csl_sample_write_done (*sample_p);
	}
      else
	{
	  csl_sample_release (*sample_p);
	  *sample_p = NULL;

	  result = CSL_EIO;
	}
    }
  return result;
}


/**
 * This writes PCM data into an empty sample, previously created using
 * @ref csl_sample_new. You can call this function multiple times if
 * desired, and the data will be concatenated together. You indicate
 * that no more data needs to be written by calling @ref csl_sample_write_done.
 * The data format is inferred from the name used when the sample is
 * created with @ref csl_sample_new.
 *
 * @param sample A pointer to a previously created CslSample.
 * @param n_bytes The number of bytes of sample data to be written.
 * @param bytes A pointer to the buffer of PCM data to be written into
 * the sample.
 * @return The number of bytes written to the sample, normally the
 * same as n_bytes.
 * @see csl_sample_new
 * @see csl_sample_write_done
 * @short Write data into a sample
 */
int
csl_sample_write (CslSample	 *sample,
		  unsigned int    n_bytes,
		  void           *bytes)
{
  int result;
  CslDriver *driver;

  csl_return_val_if_fail (sample != NULL, -1);
  csl_return_val_if_fail (sample->driver != NULL, -1);
  csl_return_val_if_fail (n_bytes > 0, -1);
  csl_return_val_if_fail (bytes != NULL, -1);

  driver = sample->driver;

  DRIVER_LOCK (driver);
  result =  driver->sample_vtable->sample_write(sample, n_bytes, bytes);
  DRIVER_UNLOCK (driver);

  return result;
}

/**
 * This is called after one or more calls to @ref csl_sample_write to
 * indicate that you are finished writing data into the sample.
 * The sample is now ready to be played using @ref csl_sample_play.
 *
 * @param sample A pointer to a previously created CslSample.
 * @see csl_sample_write
 * @short Finish writing data into a sample
 */
CslErrorType
csl_sample_write_done (CslSample *sample)
{
  CslDriver *driver;

  csl_return_val_if_fail (sample != NULL, CSL_EINTERN);
  csl_return_val_if_fail (sample->driver != NULL, CSL_EINTERN);

  driver = sample->driver;

  DRIVER_LOCK (driver);
  driver->sample_vtable->sample_write_done (sample);
  DRIVER_UNLOCK (driver);

  return CSL_ENONE;
}


/**
 * This plays a sound sample. Note that playing a sample will return
 * immediately. You can use @ref csl_sample_is_playing to check whether it
 * is still playing or @ref csl_sample_wait_done to wait until it is done.
 *
 * @param sample A pointer to a previously created CslSample.
 * @see csl_sample_write
 * @return Error code indicating success or failure.
 * @short Play a sound sample.
 */
CslErrorType
csl_sample_play	(CslSample *sample)
{
  CslDriver *driver;
  CslErrorType error;

  csl_return_val_if_fail (sample != NULL, CSL_EINTERN);
  csl_return_val_if_fail (sample->driver != NULL, CSL_EINTERN);

  driver = sample->driver;

  DRIVER_LOCK (driver);
  error = driver->sample_vtable->sample_play (sample);
  DRIVER_UNLOCK (driver);

  return error;
}

/**
 * This releases the resources associated with a sound sample. You should
 * release a sample when it is no longer required. You must not use the sample
 * after it has been release. A currently playing sample will not be stopped
 * through this function though, use @ref csl_sample_stop if you want that.
 *
 * @param sample	A pointer to a previously created CslSample.
 * @short		Free a sound sample.
 */
void
csl_sample_release (CslSample *sample)
{
  CslDriver *driver;

  csl_return_if_fail (sample != NULL);
  csl_return_if_fail (sample->driver != NULL);

  driver = sample->driver;

  DRIVER_LOCK (driver);
  driver->sample_vtable->sample_release (sample);
  DRIVER_UNLOCK (driver);
}

/**
 * This function attempts to find a sound sample in the sample cache.
 * If found, it returns the sample, otherwise it returns an error status.
 *
 * @param driver A pointer to a previously opened CSL driver.
 * @param name The name for the new sample, which must match the name
 * previously used when the sample was cached.
 * @param sample_p A pointer to a pointer to the cached sample.
 * @return Error code indicating success or failure.
 * @see csl_sample_new
 * @see csl_sample_new_from_file
 * @short Obtain a sample from the sample cache.
 */
CslErrorType
csl_sample_new_from_cache (CslDriver *driver,
			   const char *name,
			   const char *role,
			   const char *title,
			   CslSample **sample_p)
{
  CslErrorType result;

  csl_return_val_if_fail (driver != NULL, CSL_EINTERN);
  csl_return_val_if_fail (driver->sample_vtable != NULL, CSL_EINTERN);
  csl_return_val_if_fail (name != NULL, CSL_EINTERN);
  csl_return_val_if_fail (role != NULL, CSL_EINTERN);
  csl_return_val_if_fail (sample_p != NULL, CSL_EINTERN);
  if (!title)
    title = role;

  DRIVER_LOCK (driver);
  result = driver->sample_vtable->sample_cache_find (driver, name, role, title, sample_p);
  DRIVER_UNLOCK (driver);

  return result;
}

/**
 * This tells the library to remove a sample from the sample cache.
 *
 * @param sample A pointer to a previously created CslSample.
 * @short Remove a sound sample from the sample cache.
 */
CslErrorType
csl_sample_cache_remove	(CslSample *sample)
{
  CslDriver *driver;

  csl_return_val_if_fail (sample != NULL, CSL_EINTERN);
  csl_return_val_if_fail (sample->driver != NULL, CSL_EINTERN);

  driver = sample->driver;

  DRIVER_LOCK (driver);
  driver->sample_vtable->sample_cache_remove (sample);
  DRIVER_UNLOCK (driver);

  return CSL_ENONE;
}

/**
 * This adds a sample to the sample cache. It will remain cached even
 * after calling @ref csl_sample_release until @ref csl_sample_cache_remove
 * is called or the sound server is restarted.
 *
 * @param sample A pointer to a previously created CslSample.
 * @return Error code indicating success or failure.
 * @short Add a sound sample to the sample cache.
 */
CslErrorType
csl_sample_cache_add (CslSample *sample)
{
  CslErrorType result;
  CslDriver *driver;

  csl_return_val_if_fail (sample != NULL, CSL_EINTERN);
  csl_return_val_if_fail (sample->driver != NULL, CSL_EINTERN);

  driver = sample->driver;

  DRIVER_LOCK (driver);
  result = driver->sample_vtable->sample_cache_add (sample);
  DRIVER_UNLOCK (driver);

  return result;
}

/**
 * This checks whether a sample is currently being played. If you have started
 * a sample multiple times at once, this will return whether at least one of
 * them is still being played.
 *
 * @param sample A pointer to a previously created CslSample.
 * @return True if the sample is still playing.
 * @short Check whether a sample is being played.
 */
CslBool
csl_sample_is_playing (CslSample *sample)
{
  CslBool result;
  CslDriver *driver;

  csl_return_val_if_fail (sample != NULL, CSL_EINTERN);
  csl_return_val_if_fail (sample->driver != NULL, CSL_EINTERN);

  driver = sample->driver;

  DRIVER_LOCK (driver);
  result = driver->sample_vtable->sample_is_playing (sample);
  DRIVER_UNLOCK (driver);

  return result;
}


/**
 * This stops playing a sample (which was started with @ref csl_sample_play)
 * immediately.
 *
 * @param sample A pointer to a previously created CslSample.
 * @return Error code indicating success or failure.
 * @short Stop playing a sample.
 */
CslErrorType
csl_sample_stop (CslSample *sample)
{
  CslErrorType result;
  CslDriver *driver;

  csl_return_val_if_fail (sample != NULL, CSL_EINTERN);
  csl_return_val_if_fail (sample->driver != NULL, CSL_EINTERN);

  driver = sample->driver;

  DRIVER_LOCK (driver);
  result = driver->sample_vtable->sample_stop (sample);
  DRIVER_UNLOCK (driver);

  return result;
}

/**
 * This waits until playing a sample (which was started with @ref
 * csl_sample_play) is done.
 *
 * @param sample A pointer to a previously created CslSample.
 * @return Error code indicating success or failure.
 * @short Wait until playing a sample is done.
 */
CslErrorType
csl_sample_wait_done (CslSample *sample)
{
  CslErrorType result;
  CslDriver *driver;

  csl_return_val_if_fail (sample != NULL, CSL_EINTERN);
  csl_return_val_if_fail (sample->driver != NULL, CSL_EINTERN);

  driver = sample->driver;

  DRIVER_LOCK (driver);
  result = driver->sample_vtable->sample_wait_done (sample);
  DRIVER_UNLOCK (driver);

  return result;
}

/* vim:ts=8:sw=2:sts=2
 */
