/**
 * @file
 *
 * Audiere Sound System
 * Version 1.9.4
 * (c) 2001-2003 Chad Austin
 *
 * This API uses principles explained at
 * http://aegisknight.org/cppinterface.html
 *
 * This code licensed under the terms of the LGPL.  See doc/license.txt.
 *
 *
 * Note: When compiling this header in gcc, you may want to use the
 * -Wno-non-virtual-dtor flag to get rid of those annoying "class has
 * virtual functions but no virtual destructor" warnings.
 *
 * This file is structured as follows:
 * - includes, macro definitions, other general setup
 * - interface definitions
 * - DLL-safe entry points (not for general use)
 * - inline functions that use those entry points
 */

#ifndef AUDIERE_H
#define AUDIERE_H


#include <vector>
#include <string>
#include <cstring>

#ifdef _MSC_VER
#pragma warning(disable : 4786)
#endif


#ifndef __cplusplus
  #error Audiere requires C++
#endif


// DLLs in Windows should use the standard (Pascal) calling convention
#ifndef ADR_CALL
  #if defined(WIN32) || defined(_WIN32)
    #define ADR_CALL __stdcall
  #else
    #define ADR_CALL
  #endif
#endif

// Export functions from the DLL
#ifndef ADR_DECL
#  if defined(WIN32) || defined(_WIN32)
#    ifdef AUDIERE_EXPORTS
#      define ADR_DECL __declspec(dllexport)
#    else
#      define ADR_DECL __declspec(dllimport)
#    endif
#  else
#    define ADR_DECL
#  endif
#endif



#define ADR_FUNCTION(ret) extern "C" ADR_DECL ret ADR_CALL
#define ADR_METHOD(ret) virtual ret ADR_CALL


namespace audiere {

  class RefCounted {
  protected:
    /**
     * Protected so users of refcounted classes don't use std::auto_ptr
     * or the delete operator.
     *
     * Interfaces that derive from RefCounted should define an inline,
     * empty, protected destructor as well.
     */
    ~RefCounted() { }

  public:
    /**
     * Add a reference to the internal reference count.
     */
    ADR_METHOD(void) ref() = 0;

    /**
     * Remove a reference from the internal reference count.  When this
     * reaches 0, the object is destroyed.
     */
    ADR_METHOD(void) unref() = 0;
  };


  template<typename T>
  class RefPtr {
  public:
    RefPtr(T* ptr = 0) {
      m_ptr = 0;
      *this = ptr;
    }

    RefPtr(const RefPtr<T>& ptr) {
      m_ptr = 0;
      *this = ptr;
    }

    ~RefPtr() {
      if (m_ptr) {
        m_ptr->unref();
        m_ptr = 0;
      }
    }
 
    RefPtr<T>& operator=(T* ptr) {
      if (ptr != m_ptr) {
        if (m_ptr) {
          m_ptr->unref();
        }
        m_ptr = ptr;
        if (m_ptr) {
          m_ptr->ref();
        }
      }
      return *this;
    }

    RefPtr<T>& operator=(const RefPtr<T>& ptr) {
      *this = ptr.m_ptr;
      return *this;
    }

    T* operator->() const {
      return m_ptr;
    }

    T& operator*() const {
      return *m_ptr;
    }

    operator bool() const {
      return (m_ptr != 0);
    }

    T* get() const {
      return m_ptr;
    }

  private:
    T* m_ptr;
  };


  template<typename T, typename U>
  bool operator==(const RefPtr<T>& a, const RefPtr<U>& b) {
      return (a.get() == b.get());
  }

  template<typename T>
  bool operator==(const RefPtr<T>& a, const T* b) {
      return (a.get() == b);
  }

  template<typename T>
  bool operator==(const T* a, const RefPtr<T>& b) {
      return (a == b.get());
  }
  

  template<typename T, typename U>
  bool operator!=(const RefPtr<T>& a, const RefPtr<U>& b) {
      return (a.get() != b.get());
  }

  template<typename T>
  bool operator!=(const RefPtr<T>& a, const T* b) {
      return (a.get() != b);
  }

  template<typename T>
  bool operator!=(const T* a, const RefPtr<T>& b) {
      return (a != b.get());
  }


  /**
   * A basic implementation of the RefCounted interface.  Derive
   * your implementations from RefImplementation<YourInterface>.
   */
  template<class Interface>
  class RefImplementation : public Interface {
  protected:
    RefImplementation() {
      m_ref_count = 0;
    }

    /**
     * So the implementation can put its destruction logic in the destructor,
     * as natural C++ code does.
     */
    virtual ~RefImplementation() { }

  public:
    void ADR_CALL ref() {
      ++m_ref_count;
    }

    void ADR_CALL unref() {
      if (--m_ref_count == 0) {
        delete this;
      }
    }

  private:
    int m_ref_count;
  };


  /**
   * Represents a random-access file, usually stored on a disk.  Files
   * are always binary: that is, they do no end-of-line
   * transformations.  File objects are roughly analogous to ANSI C
   * FILE* objects.
   *
   * This interface is not synchronized.
   */
  class File : public RefCounted {
  protected:
    ~File() { }

  public:
    /**
     * The different ways you can seek within a file.
     */
    enum SeekMode {
      BEGIN,
      CURRENT,
      END,
    };

    /**
     * Read size bytes from the file, storing them in buffer.
     *
     * @param buffer  buffer to read into
     * @param size    number of bytes to read
     *
     * @return  number of bytes successfully read
     */
    ADR_METHOD(int) read(void* buffer, int size) = 0;

    /**
     * Jump to a new position in the file, using the specified seek
     * mode.  Remember: if mode is END, the position must be negative,
     * to seek backwards from the end of the file into its contents.
     * If the seek fails, the current position is undefined.
     *
     * @param position  position relative to the mode
     * @param mode      where to seek from in the file
     *
     * @return  true on success, false otherwise
     */
    ADR_METHOD(bool) seek(int position, SeekMode mode) = 0;

    /**
     * Get current position within the file.
     *
     * @return  current position
     */
    ADR_METHOD(int) tell() = 0;
  };
  typedef RefPtr<File> FilePtr;


  /// Storage formats for sample data.
  enum SampleFormat {
    SF_U8,  ///< unsigned 8-bit integer [0,255]
    SF_S16, ///< signed 16-bit integer in host endianness [-32768,32767]
  };


  /// Supported audio file formats.
  enum FileFormat {
    FF_AUTODETECT,
    FF_WAV,
    FF_OGG,
    FF_FLAC,
    FF_MP3,
    FF_MOD,
    FF_AIFF,
    FF_SPEEX,
  };


  /**
   * Source of raw PCM samples.  Sample sources have an intrinsic format
   * (@see SampleFormat), sample rate, and number of channels.  They can
   * be read from or reset.
   *
   * Some sample sources are seekable.  Seekable sources have two additional
   * properties: length and position.  Length is read-only.
   *
   * This interface is not synchronized.
   */
  class SampleSource : public RefCounted {
  protected:
    ~SampleSource() { }

  public:
    /**
     * Retrieve the number of channels, sample rate, and sample format of
     * the sample source.
     */
    ADR_METHOD(void) getFormat(
      int& channel_count,
      int& sample_rate,
      SampleFormat& sample_format) = 0;

    /**
     * Read frame_count samples into buffer.  buffer must be at least
     * |frame_count * GetSampleSize(format) * channel_count| bytes long.
     *
     * @param frame_count  number of frames to read
     * @param buffer       buffer to store samples in
     *
     * @return  number of frames actually read
     */
    ADR_METHOD(int) read(int frame_count, void* buffer) = 0;

    /**
     * Reset the sample source.  This has the same effect as setPosition(0)
     * on a seekable source.  On an unseekable source, it resets all internal
     * state to the way it was when the source was first created.
     */
    ADR_METHOD(void) reset() = 0;

    /**
     * @return  true if the stream is seekable, false otherwise
     */
    ADR_METHOD(bool) isSeekable() = 0;

    /**
     * @return  number of frames in the stream, or 0 if the stream is not
     *          seekable
     */
    ADR_METHOD(int) getLength() = 0;
    
    /**
     * Sets the current position within the sample source.  If the stream
     * is not seekable, this method does nothing.
     *
     * @param position  current position in frames
     */
    ADR_METHOD(void) setPosition(int position) = 0;

    /**
     * Returns the current position within the sample source.
     *
     * @return  current position in frames
     */
    ADR_METHOD(int) getPosition() = 0;

    /**
     * @return  true if the sample source is set to repeat
     */
    ADR_METHOD(bool) getRepeat() = 0;

    /**
     * Sets whether the sample source should repeat or not.  Note that not
     * all sample sources repeat by starting again at the beginning of the
     * sound.  For example MOD files can contain embedded loop points.
     *
     * @param repeat  true if the source should repeat, false otherwise
     */
    ADR_METHOD(void) setRepeat(bool repeat) = 0;

    /// Returns number of metadata tags present in this sample source.
    ADR_METHOD(int) getTagCount() = 0;

    /**
     * Returns the key of the i'th tag in the source.  If the tag is
     * "author=me", the key is "author".
     */
    virtual const char* ADR_CALL getTagKey(int i) = 0;

    /**
     * Returns the value of the i'th tag in the source.  If the tag is
     * "author=me", the value is "me".
     */
    virtual const char* ADR_CALL getTagValue(int i) = 0;

    /**
     * Returns the type of the i'th tag in the source.  The type is where
     * the tag comes from, i.e. "ID3v1", "ID3v2", or "vorbis".
     */
    virtual const char* ADR_CALL getTagType(int i) = 0;
  };
  typedef RefPtr<SampleSource> SampleSourcePtr;


  /**
   * LoopPointSource is a wrapper around another SampleSource, providing
   * custom loop behavior.  LoopPointSource maintains a set of links
   * within the sample stream and whenever the location of one of the links
   * (i.e. a loop point) is reached, the stream jumps to that link's target.
   * Each loop point maintains a count.  Every time a loop point comes into
   * effect, the count is decremented.  Once it reaches zero, that loop point
   * is temporarily disabled.  If a count is not a positive value, it
   * cannot be disabled.  Calling reset() resets all counts to their initial
   * values.
   *
   * Loop points only take effect when repeating has been enabled via the
   * setRepeat() method.
   *
   * Loop points are stored in sorted order by their location.  Each one
   * has an index based on its location within the list.  A loop point's
   * index will change if another is added before it.
   *
   * There is always one implicit loop point after the last sample that
   * points back to the first.  That way, this class's default looping
   * behavior is the same as a standard SampleSource.  This loop point
   * does not show up in the list.
   */
  class LoopPointSource : public SampleSource {
  protected:
    ~LoopPointSource() { }

  public:
    /**
     * Adds a loop point to the stream.  If a loop point at 'location'
     * already exists, the new one replaces it.  Location and target are
     * clamped to the actual length of the stream.
     *
     * @param location   frame where loop occurs
     * @param target     frame to jump to after loop point is hit
     * @param loopCount  number of times to execute this jump.
     */
    ADR_METHOD(void) addLoopPoint(
      int location, int target, int loopCount) = 0;

    /**
     * Removes the loop point at index 'index' from the stream.
     *
     * @param index  index of the loop point to remove
     */
    ADR_METHOD(void) removeLoopPoint(int index) = 0;

    /**
     * Returns the number of loop points in this stream.
     */
    ADR_METHOD(int) getLoopPointCount() = 0;

    /**
     * Retrieves information about a specific loop point.
     *
     * @param index      index of the loop point
     * @param location   frame where loop occurs
     * @param target     loop point's target frame
     * @param loopCount  number of times to loop from this particular point
     *
     * @return  true if the index is valid and information is returned
     */
    ADR_METHOD(bool) getLoopPoint(
      int index, int& location, int& target, int& loopCount) = 0;
  };
  typedef RefPtr<LoopPointSource> LoopPointSourcePtr;


  /**
   * A connection to an audio device.  Multiple output streams are
   * mixed by the audio device to produce the final waveform that the
   * user hears.
   *
   * Each output stream can be independently played and stopped.  They
   * also each have a volume from 0.0 (silence) to 1.0 (maximum volume).
   */
  class OutputStream : public RefCounted {
  protected:
    ~OutputStream() { }

  public:
    /**
     * Start playback of the output stream.  If the stream is already
     * playing, this does nothing.
     */
    ADR_METHOD(void) play() = 0;

    /**
     * Stop playback of the output stream.  If the stream is already
     * stopped, this does nothing.
     */
    ADR_METHOD(void) stop() = 0;

    /**
     * @return  true if the output stream is playing, false otherwise
     */
    ADR_METHOD(bool) isPlaying() = 0;

    /**
     * Reset the sample source or buffer to the beginning. On seekable
     * streams, this operation is equivalent to setPosition(0).
     *
     * On some output streams, this operation can be moderately slow, as up to
     * several seconds of PCM buffer must be refilled.
     */
    ADR_METHOD(void) reset() = 0;

    /**
     * Set whether the output stream should repeat.
     *
     * @param repeat  true if the stream should repeat, false otherwise
     */
    ADR_METHOD(void) setRepeat(bool repeat) = 0;

    /**
     * @return  true if the stream is repeating
     */
    ADR_METHOD(bool) getRepeat() = 0;

    /**
     * Sets the stream's volume.
     *
     * @param  volume  0.0 = silence, 1.0 = maximum volume (default)
     */
    ADR_METHOD(void) setVolume(float volume) = 0;

    /**
     * Gets the current volume.
     *
     * @return  current volume of the output stream
     */
    ADR_METHOD(float) getVolume() = 0;

    /**
     * Set current pan.
     *
     * @param pan  -1.0 = left, 0.0 = center (default), 1.0 = right
     */
    ADR_METHOD(void) setPan(float pan) = 0;

    /**
     * Get current pan.
     */
    ADR_METHOD(float) getPan() = 0;

    /**
     * Set current pitch shift.
     *
     * @param shift  can range from 0.5 to 2.0.  default is 1.0.
     */
    ADR_METHOD(void) setPitchShift(float shift) = 0;

    /**
     * Get current pitch shift.  Defaults to 1.0.
     */
    ADR_METHOD(float) getPitchShift() = 0;

    /**
     * @return  true if the stream is seekable, false otherwise
     */
    ADR_METHOD(bool) isSeekable() = 0;

    /**
     * @return  number of frames in the stream, or 0 if the stream is not
     *          seekable
     */
    ADR_METHOD(int) getLength() = 0;
    
    /**
     * Sets the current position within the sample source.  If the stream
     * is not seekable, this method does nothing.
     *
     * @param position  current position in frames
     */
    ADR_METHOD(void) setPosition(int position) = 0;

    /**
     * Returns the current position within the sample source.
     *
     * @return  current position in frames
     */
    ADR_METHOD(int) getPosition() = 0;
  };
  typedef RefPtr<OutputStream> OutputStreamPtr;


  /// An integral code representing a specific type of event.
  enum EventType {
    ET_STOP, ///< See StopEvent and StopCallback
  };


  /// Base interface for event-specific data passed to callbacks.
  class Event : public RefCounted {
  protected:
    ~Event() { }

  public:
    /// Returns the EventType code for this event.
    ADR_METHOD(EventType) getType() = 0;
  };
  typedef RefPtr<Event> EventPtr;


  /**
   * An event object that gets passed to implementations of StopCallback
   * when a stream has stopped playing.
   */
  class StopEvent : public Event {
  protected:
    ~StopEvent() { }

  public:
    EventType ADR_CALL getType() { return ET_STOP; }

    /// A code representing the reason the stream stopped playback.
    enum Reason {
      STOP_CALLED,  ///< stop() was called from an external source.
      STREAM_ENDED, ///< The stream reached its end.
    };

    /**
     * @return Pointer to the OutputStream that stopped playback.
     */
    ADR_METHOD(OutputStream*) getOutputStream() = 0;

    /**
     * @return Reason for the stop event.
     */
    ADR_METHOD(Reason) getReason() = 0;
  };
  typedef RefPtr<StopEvent> StopEventPtr;


  /**
   * Base interface for all callbacks.  See specific callback implementations
   * for descriptions.
   */  
  class Callback : public RefCounted {
  protected:
    ~Callback() { }

  public:
    /**
     * Returns the event type that this callback knows how to handle.
     */
    ADR_METHOD(EventType) getType() = 0;

    /**
     * Actually executes the callback with event-specific data.  This is
     * only called if event->getType() == this->getType().
     */
    ADR_METHOD(void) call(Event* event) = 0;
  };
  typedef RefPtr<Callback> CallbackPtr;

  
  /**
   * To listen for stream stopped events on a device, implement this interface
   * and call registerStopCallback() on the device, passing your
   * implementation.  streamStopped() will be called whenever a stream on that
   * device stops playback.
   *
   * WARNING: StopCallback is called from another thread.  Make sure your
   * callback is thread-safe.
   */
  class StopCallback : public Callback {
  protected:
    ~StopCallback() { }

  public:
    EventType ADR_CALL getType() { return ET_STOP; }
    void ADR_CALL call(Event* event) {
      streamStopped(static_cast<StopEvent*>(event));
    }

    /**
     * Called when a stream has stopped.
     *
     * @param event  Information pertaining to the event.
     */
    ADR_METHOD(void) streamStopped(StopEvent* event) = 0;
  };
  typedef RefPtr<StopCallback> StopCallbackPtr;


  /**
   * AudioDevice represents a device on the system which is capable
   * of opening and mixing multiple output streams.  In Windows,
   * DirectSound is such a device.
   *
   * This interface is synchronized.  update() and openStream() may
   * be called on different threads.
   */
  class AudioDevice : public RefCounted {
  protected:
    ~AudioDevice() { }

  public:
    /**
     * Tell the device to do any internal state updates.  Some devices
     * update on an internal thread.  If that is the case, this method
     * does nothing.
     */
    ADR_METHOD(void) update() = 0;

    /**
     * Open an output stream with a given sample source.  If the sample
     * source ever runs out of data, the output stream automatically stops
     * itself.
     *
     * The output stream takes ownership of the sample source, even if
     * opening the output stream fails (in which case the source is
     * immediately deleted).
     *
     * @param  source  the source used to feed the output stream with samples
     *
     * @return  new output stream if successful, 0 if failure
     */
    ADR_METHOD(OutputStream*) openStream(SampleSource* source) = 0;

    /**
     * Open a single buffer with the specified PCM data.  This is sometimes
     * more efficient than streaming and works on a larger variety of audio
     * devices.  In some implementations, this may download the audio data
     * to the sound card's memory itself.
     *
     * @param samples  Buffer containing sample data.  openBuffer() does
     *                 not take ownership of the memory.  The application
     *                 is responsible for freeing it.  There must be at
     *                 least |frame_count * channel_count *
     *                 GetSampleSize(sample_format)| bytes in the buffer.
     *
     * @param frame_count  Number of frames in the buffer.
     *
     * @param channel_count  Number of audio channels.  1 = mono, 2 = stereo.
     *
     * @param sample_rate  Number of samples per second.
     *
     * @param sample_format  Format of samples in buffer.
     *
     * @return  new output stream if successful, 0 if failure
     */
    ADR_METHOD(OutputStream*) openBuffer(
      void* samples,
      int frame_count,
      int channel_count,
      int sample_rate,
      SampleFormat sample_format) = 0;

    /**
     * Gets the name of the audio device.  For example "directsound" or "oss".
     *
     * @return name of audio device
     */
    ADR_METHOD(const char*) getName() = 0;

    /**
     * Registers 'callback' to receive events.  Callbacks can be
     * registered multiple times.
     */
    ADR_METHOD(void) registerCallback(Callback* callback) = 0;
    
    /**
     * Unregisters 'callback' once.  If it is registered multiple times,
     * each unregisterStopCallback call unregisters one of the instances.
     */
    ADR_METHOD(void) unregisterCallback(Callback* callback) = 0;

    /// Clears all of the callbacks from the device.
    ADR_METHOD(void) clearCallbacks() = 0;
  };
  typedef RefPtr<AudioDevice> AudioDevicePtr;


  /**
   * A readonly sample container which can open sample streams as iterators
   * through the buffer.  This is commonly used in cases where a very large
   * sound effect is loaded once into memory and then streamed several times
   * to the audio device.  This is more efficient memory-wise than loading
   * the effect multiple times.
   *
   * @see CreateSampleBuffer
   */
  class SampleBuffer : public RefCounted {
  protected:
    ~SampleBuffer() { }

  public:

    /**
     * Return the format of the sample data in the sample buffer.
     * @see SampleSource::getFormat
     */
    ADR_METHOD(void) getFormat(
      int& channel_count,
      int& sample_rate,
      SampleFormat& sample_format) = 0;

    /**
     * Get the length of the sample buffer in frames.
     */
    ADR_METHOD(int) getLength() = 0;

    /**
     * Get a readonly pointer to the samples contained within the buffer.  The
     * buffer is |channel_count * frame_count * GetSampleSize(sample_format)|
     * bytes long.
     */
    virtual const void* ADR_CALL getSamples() = 0;

    /**
     * Open a seekable sample source using the samples contained in the
     * buffer.
     */
    ADR_METHOD(SampleSource*) openStream() = 0;
  };
  typedef RefPtr<SampleBuffer> SampleBufferPtr;


  /**
   * Defines the type of SoundEffect objects.  @see SoundEffect
   */
  enum SoundEffectType {
    SINGLE,
    MULTIPLE,
  };


  /**
   * SoundEffect is a convenience class which provides a simple
   * mechanism for basic sound playback.  There are two types of sound
   * effects: SINGLE and MULTIPLE.  SINGLE sound effects only allow
   * the sound to be played once at a time.  MULTIPLE sound effects
   * always open a new stream to the audio device for each time it is
   * played (cleaning up or reusing old streams if possible).
   */
  class SoundEffect : public RefCounted {
  protected:
    ~SoundEffect() { }

  public:
    /**
     * Trigger playback of the sound.  If the SoundEffect is of type
     * SINGLE, this plays the sound if it isn't playing yet, and
     * starts it again if it is.  If the SoundEffect is of type
     * MULTIPLE, play() simply starts playing the sound again.
     */
    ADR_METHOD(void) play() = 0;

    /**
     * If the sound is of type SINGLE, stop the sound.  If it is of
     * type MULTIPLE, stop all playing instances of the sound.
     */
    ADR_METHOD(void) stop() = 0;

    /**
     * Sets the sound's volume.
     *
     * @param  volume  0.0 = silence, 1.0 = maximum volume (default)
     */
    ADR_METHOD(void) setVolume(float volume) = 0;

    /**
     * Gets the current volume.
     *
     * @return  current volume of the output stream
     */
    ADR_METHOD(float) getVolume() = 0;

    /**
     * Set current pan.
     *
     * @param pan  -1.0 = left, 0.0 = center (default), 1.0 = right
     */
    ADR_METHOD(void) setPan(float pan) = 0;

    /**
     * Get current pan.
     */
    ADR_METHOD(float) getPan() = 0;

    /**
     * Set current pitch shift.
     *
     * @param shift  can range from 0.5 to 2.0.  default is 1.0.
     */
    ADR_METHOD(void) setPitchShift(float shift) = 0;

    /**
     * Get current pitch shift.  Defaults to 1.0.
     */
    ADR_METHOD(float) getPitchShift() = 0;
  };
  typedef RefPtr<SoundEffect> SoundEffectPtr;


  /**
   * Represents a device capable of playing CD audio. Internally, this
   * uses the MCI subsystem in windows and libcdaudio on other platforms.
   * MCI subsystem: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_multimedia_command_strings.asp
   * libcdaudio: http://cdcd.undergrid.net/libcdaudio/
   */
  class CDDevice : public RefCounted {
  protected:
    virtual ~CDDevice() { }

  public:
    /**
     * Returns the name of this CD Device, often just the device name
     * it was created with.
     */
    virtual const char* ADR_CALL getName() = 0;

    /**
     * Returns the number of audio tracks on the disc.
     */
    ADR_METHOD(int) getTrackCount() = 0;

    /**
     * Starts playback of the given track. If another track was
     * already playing, the previous track is stopped.  IMPORTANT: Tracks are
     * indexed from 0 to getTrackCount() - 1.
     */
    ADR_METHOD(void) play(int track) = 0;

    /**
     * Stops the playback, if the playback was already stopped, this
     * does nothing.
     */
    ADR_METHOD(void) stop() = 0;
    
    /**
     * pauses playback of the track that is currently playing (if any)
     * This does nothing if no track is playing
     */
    ADR_METHOD(void) pause() = 0;

    /**
     * Resumes playback of the track that is currently paused (if any).
     * This does nothing if no track is paused.
     */
    ADR_METHOD(void) resume() = 0;

    /**
     * Returns true if the CD is currently playing a sound, this could
     * be through us, or through some other program.
     */
    ADR_METHOD(bool) isPlaying() = 0;

    /**
     * Returns true if the drive contains a cd. This might be slow
     * on some systems, use with care.
     */
    ADR_METHOD(bool) containsCD() = 0;

    /// Returns true if the door is open.
    ADR_METHOD(bool) isDoorOpen() = 0;

    /// Opens this device's door.
    ADR_METHOD(void) openDoor() = 0;

    /// Closes this device's door.
    ADR_METHOD(void) closeDoor() = 0;
  };
  typedef RefPtr<CDDevice> CDDevicePtr;


  /**
   * An opened MIDI song that can be played, stopped, and seeked within.
   * Uses MCI under Windows and is not supported in other platforms.
   */
  class MIDIStream : public RefCounted {
  protected:
    virtual ~MIDIStream() { }

  public:
    /**
     * Begins playback of the song and does nothing if the song is already
     * playing.
     */
    ADR_METHOD(void) play() = 0;

    /// Stops playback of the song and seeks to the beginning.
    ADR_METHOD(void) stop() = 0;

    /**
     * Stops playback of the song and does not change its current position.
     * A subsequent play() will resume the song where it left off.
     */
    ADR_METHOD(void) pause() = 0;

    /// Returns true if the song is currently playing, false otherwise.
    ADR_METHOD(bool) isPlaying() = 0;

    /// Returns the length of the song in milliseconds.
    ADR_METHOD(int) getLength() = 0;

    /// Returns the current position of the song in milliseconds.
    ADR_METHOD(int) getPosition() = 0;

    /// Sets the current position of the song.
    ADR_METHOD(void) setPosition(int position) = 0;

    /// Returns true if this song is set to repeat.
    ADR_METHOD(bool) getRepeat() = 0;

    /// Sets whether the song should repeat on completion.  Defaults to false.
    ADR_METHOD(void) setRepeat(bool repeat) = 0;
  };
  typedef RefPtr<MIDIStream> MIDIStreamPtr;


  /**
   * A MIDIDevice must be instantiated in order to open MIDIStreams.
   */
  class MIDIDevice : public RefCounted {
  protected:
    virtual ~MIDIDevice() { }

  public:
    /**
     * Returns the name of the device.
     */
    ADR_METHOD(const char*) getName() = 0;

    /**
     * openStream() creates and returns a new MIDIStream object from the
     * file with the specified name, which then can be queried and played.
     * This method returns NULL if the stream cannot be opened.
     *
     * Note: MCI subsystem limitations do not allow loading MIDIStream
     * objects from an audiere File implementation.  This may be addressed
     * in future versions of this API.
     */
    ADR_METHOD(MIDIStream*) openStream(const char* filename) = 0;
  };
  typedef RefPtr<MIDIDevice> MIDIDevicePtr;


  /// PRIVATE API - for internal use only
  namespace hidden {

    // these are extern "C" so we don't mangle the names

    ADR_FUNCTION(const char*) AdrGetVersion();

    /**
     * Returns a formatted string that lists the file formats that Audiere
     * supports.  This function is DLL-safe.
     *
     * It is formatted in the following way:
     *
     * description1:ext1,ext2,ext3;description2:ext1,ext2,ext3
     */
    ADR_FUNCTION(const char*) AdrGetSupportedFileFormats();

    /**
     * Returns a formatted string that lists the audio devices Audiere
     * supports.  This function is DLL-safe.
     *
     * It is formatted in the following way:
     *
     * name1:description1;name2:description2;...
     */
    ADR_FUNCTION(const char*) AdrGetSupportedAudioDevices();

    ADR_FUNCTION(int) AdrGetSampleSize(SampleFormat format);

    ADR_FUNCTION(AudioDevice*) AdrOpenDevice(
      const char* name,
      const char* parameters);

    ADR_FUNCTION(SampleSource*) AdrOpenSampleSource(
      const char* filename,
      FileFormat file_format);
    ADR_FUNCTION(SampleSource*) AdrOpenSampleSourceFromFile(
      File* file,
      FileFormat file_format);
    ADR_FUNCTION(SampleSource*) AdrCreateTone(double frequency);
    ADR_FUNCTION(SampleSource*) AdrCreateSquareWave(double frequency);
    ADR_FUNCTION(SampleSource*) AdrCreateWhiteNoise();
    ADR_FUNCTION(SampleSource*) AdrCreatePinkNoise();

    ADR_FUNCTION(LoopPointSource*) AdrCreateLoopPointSource(
      SampleSource* source);

    ADR_FUNCTION(OutputStream*) AdrOpenSound(
      AudioDevice* device,
      SampleSource* source,
      bool streaming);

    ADR_FUNCTION(SampleBuffer*) AdrCreateSampleBuffer(
      void* samples,
      int frame_count,
      int channel_count,
      int sample_rate,
      SampleFormat sample_format);
    ADR_FUNCTION(SampleBuffer*) AdrCreateSampleBufferFromSource(
      SampleSource* source);

    ADR_FUNCTION(SoundEffect*) AdrOpenSoundEffect(
      AudioDevice* device,
      SampleSource* source,
      SoundEffectType type);

    ADR_FUNCTION(File*) AdrOpenFile(
      const char* name,
      bool writeable);

    ADR_FUNCTION(File*) AdrCreateMemoryFile(
      const void* buffer,
      int size);

    ADR_FUNCTION(const char*) AdrEnumerateCDDevices();

    ADR_FUNCTION(CDDevice*) AdrOpenCDDevice(
      const char* name);  // Parameters?

    ADR_FUNCTION(MIDIDevice*) AdrOpenMIDIDevice(
      const char* name);  // Parameters?
  }




  /*-------- PUBLIC API FUNCTIONS --------*/


  /**
   * Returns the Audiere version string.
   *
   * @return  Audiere version information
   */
  inline const char* GetVersion() {
    return hidden::AdrGetVersion();
  }


  inline void SplitString(
    std::vector<std::string>& out,
    const char* in,
    char delim)
  {
    out.clear();
    while (*in) {
      const char* next = strchr(in, delim);
      if (next) {
        out.push_back(std::string(in, next));
      } else {
        out.push_back(in);
      }

      in = (next ? next + 1 : "");
    }
  }


  /// Describes a file format that Audiere supports.
  struct FileFormatDesc {
    /// Short description of format, such as "MP3 Files" or "Mod Files"
    std::string description;

    /// List of support extensions, such as {"mod", "it", "xm"}
    std::vector<std::string> extensions;
  };

  /// Populates a vector of FileFormatDesc structs.
  inline void GetSupportedFileFormats(std::vector<FileFormatDesc>& formats) {
    std::vector<std::string> descriptions;
    SplitString(descriptions, hidden::AdrGetSupportedFileFormats(), ';');

    formats.resize(descriptions.size());
    for (unsigned i = 0; i < descriptions.size(); ++i) {
      const char* d = descriptions[i].c_str();
      const char* colon = strchr(d, ':');
      formats[i].description.assign(d, colon);

      SplitString(formats[i].extensions, colon + 1, ',');
    }
  }


  /// Describes a supported audio device.
  struct AudioDeviceDesc {
    /// Name of device, i.e. "directsound", "winmm", or "oss"
    std::string name;

    // Textual description of device.
    std::string description;
  };

  /// Populates a vector of AudioDeviceDesc structs.
  inline void GetSupportedAudioDevices(std::vector<AudioDeviceDesc>& devices) {
    std::vector<std::string> descriptions;
    SplitString(descriptions, hidden::AdrGetSupportedAudioDevices(), ';');

    devices.resize(descriptions.size());
    for (unsigned i = 0; i < descriptions.size(); ++i) {
      std::vector<std::string> d;
      SplitString(d, descriptions[i].c_str(), ':');
      devices[i].name        = d[0];
      devices[i].description = d[1];
    }
  }


  /**
   * Get the size of a sample in a specific sample format.
   * This is commonly used to determine how many bytes a chunk of
   * PCM data will take.
   *
   * @return  Number of bytes a single sample in the specified format
   *          takes.
   */
  inline int GetSampleSize(SampleFormat format) {
    return hidden::AdrGetSampleSize(format);
  }

  /**
   * Open a new audio device. If name or parameters are not specified,
   * defaults are used. Each platform has its own set of audio devices.
   * Every platform supports the "null" audio device.
   *
   * @param  name  name of audio device that should be used
   * @param  parameters  comma delimited list of audio-device parameters;
   *                     for example, "buffer=100,rate=44100"
   *
   * @return  new audio device object if OpenDevice succeeds, and 0 in case
   *          of failure
   */
  inline AudioDevice* OpenDevice(
    const char* name = 0,
    const char* parameters = 0)
  {
    return hidden::AdrOpenDevice(name, parameters);
  }

  /**
   * Create a streaming sample source from a sound file.  This factory simply
   * opens a default file from the system filesystem and calls
   * OpenSampleSource(File*).
   *
   * @see OpenSampleSource(File*)
   */
  inline SampleSource* OpenSampleSource(
    const char* filename,
    FileFormat file_format = FF_AUTODETECT)
  {
    return hidden::AdrOpenSampleSource(filename, file_format);
  }

  /**
   * Opens a sample source from the specified file object.  If the sound file
   * cannot be opened, this factory function returns 0.
   *
   * @note  Some sound files support seeking, while some don't.
   *
   * @param file         File object from which to open the decoder
   * @param file_format  Format of the file to load.  If FF_AUTODETECT,
   *                     Audiere will try opening the file in each format.
   *
   * @return  new SampleSource if OpenSampleSource succeeds, 0 otherwise
   */
  inline SampleSource* OpenSampleSource(
    const FilePtr& file,
    FileFormat file_format = FF_AUTODETECT)
  {
    return hidden::AdrOpenSampleSourceFromFile(file.get(), file_format);
  }

  /**
   * Create a tone sample source with the specified frequency.
   *
   * @param  frequency  Frequency of the tone in Hz.
   *
   * @return  tone sample source
   */
  inline SampleSource* CreateTone(double frequency) {
    return hidden::AdrCreateTone(frequency);
  }

  /**
   * Create a square wave with the specified frequency.
   *
   * @param  frequency  Frequency of the wave in Hz.
   *
   * @return  wave sample source
   */
  inline SampleSource* CreateSquareWave(double frequency) {
    return hidden::AdrCreateSquareWave(frequency);
  }

  /**
   * Create a white noise sample source.  White noise is just random
   * data.
   *
   * @return  white noise sample source
   */
  inline SampleSource* CreateWhiteNoise() {
    return hidden::AdrCreateWhiteNoise();
  }

  /**
   * Create a pink noise sample source.  Pink noise is noise with equal
   * power distribution among octaves (logarithmic), not frequencies.
   *
   * @return  pink noise sample source
   */
  inline SampleSource* CreatePinkNoise() {
    return hidden::AdrCreatePinkNoise();
  }

  /**
   * Create a LoopPointSource from a SampleSource.  The SampleSource must
   * be seekable.  If it isn't, or the source isn't valid, this function
   * returns 0.
   */
  inline LoopPointSource* CreateLoopPointSource(
    const SampleSourcePtr& source)
  {
    return hidden::AdrCreateLoopPointSource(source.get());
  }

  /**
   * Creates a LoopPointSource from a source loaded from a file.
   */
  inline LoopPointSource* CreateLoopPointSource(
    const char* filename,
    FileFormat file_format = FF_AUTODETECT)
  {
    return CreateLoopPointSource(OpenSampleSource(filename, file_format));
  }

  /**
   * Creates a LoopPointSource from a source loaded from a file.
   */
  inline LoopPointSource* CreateLoopPointSource(
    const FilePtr& file,
    FileFormat file_format = FF_AUTODETECT)
  {
    return CreateLoopPointSource(OpenSampleSource(file, file_format));
  }

  /**
   * Try to open a sound buffer using the specified AudioDevice and
   * sample source.  If the specified sample source is seekable, it
   * loads it into memory and uses AudioDevice::openBuffer to create
   * the output stream.  If the stream is not seekable, it uses
   * AudioDevice::openStream to create the output stream.  This means
   * that certain file types must always be streamed, and therefore,
   * OpenSound will hold on to the file object.  If you must guarantee
   * that the file on disk is no longer referenced, you must create
   * your own memory file implementation and load your data into that
   * before calling OpenSound.
   *
   * @param device  AudioDevice in which to open the output stream.
   *
   * @param source  SampleSource used to generate samples for the sound
   *                object.  OpenSound takes ownership of source, even
   *                if it returns 0.  (In that case, OpenSound immediately
   *                deletes the SampleSource.)
   *
   * @param streaming  If false or unspecified, OpenSound attempts to
   *                   open the entire sound into memory.  Otherwise, it
   *                   streams the sound from the file.
   *
   * @return  new output stream if successful, 0 otherwise
   */
  inline OutputStream* OpenSound(
    const AudioDevicePtr& device,
    const SampleSourcePtr& source,
    bool streaming = false)
  {
    return hidden::AdrOpenSound(device.get(), source.get(), streaming);
  }

  /**
   * Calls OpenSound(AudioDevice*, SampleSource*) with a sample source
   * created via OpenSampleSource(const char*).
   */
  inline OutputStream* OpenSound(
    const AudioDevicePtr& device,
    const char* filename,
    bool streaming = false,
    FileFormat file_format = FF_AUTODETECT)
  {
    SampleSource* source = OpenSampleSource(filename, file_format);
    return OpenSound(device, source, streaming);
  }

  /**
   * Calls OpenSound(AudioDevice*, SampleSource*) with a sample source
   * created via OpenSampleSource(File* file).
   */
  inline OutputStream* OpenSound(
    const AudioDevicePtr& device,
    const FilePtr& file,
    bool streaming = false,
    FileFormat file_format = FF_AUTODETECT)
  {
    SampleSource* source = OpenSampleSource(file, file_format);
    return OpenSound(device, source, streaming);
  }

  /**
   * Create a SampleBuffer object using the specified samples and formats.
   *
   * @param samples  Pointer to a buffer of samples used to initialize the
   *                 new object.  If this is 0, the sample buffer contains
   *                 just silence.
   *
   * @param frame_count  Size of the sample buffer in frames.
   *
   * @param channel_count  Number of channels in each frame.
   *
   * @param sample_rate  Sample rate in Hz.
   *
   * @param sample_format  Format of each sample.  @see SampleFormat.
   *
   * @return  new SampleBuffer object
   */
  inline SampleBuffer* CreateSampleBuffer(
    void* samples,
    int frame_count,
    int channel_count,
    int sample_rate,
    SampleFormat sample_format)
  {
    return hidden::AdrCreateSampleBuffer(
      samples, frame_count,
      channel_count, sample_rate, sample_format);
  }

  /**
   * Create a SampleBuffer object from a SampleSource.
   *
   * @param source  Seekable sample source used to create the buffer.
   *                If the source is not seekable, then the function
   *                fails.
   *
   * @return  new sample buffer if success, 0 otherwise
   */
  inline SampleBuffer* CreateSampleBuffer(const SampleSourcePtr& source) {
    return hidden::AdrCreateSampleBufferFromSource(source.get());
  }

  /**
   * Open a SoundEffect object from the given sample source and sound
   * effect type.  @see SoundEffect
   *
   * @param device  AudioDevice on which the sound is played.
   *
   * @param source  The sample source used to feed the sound effect
   *                with data.
   *
   * @param type  The type of the sound effect.  If type is MULTIPLE,
   *              the source must be seekable.
   *
   * @return  new SoundEffect object if successful, 0 otherwise
   */
  inline SoundEffect* OpenSoundEffect(
    const AudioDevicePtr& device,
    const SampleSourcePtr& source,
    SoundEffectType type)
  {
    return hidden::AdrOpenSoundEffect(device.get(), source.get(), type);
  }

  /**
   * Calls OpenSoundEffect(AudioDevice*, SampleSource*,
   * SoundEffectType) with a sample source created from the filename.
   */
  inline SoundEffect* OpenSoundEffect(
    const AudioDevicePtr& device,
    const char* filename,
    SoundEffectType type,
    FileFormat file_format = FF_AUTODETECT)
  {
    SampleSource* source = OpenSampleSource(filename, file_format);
    return OpenSoundEffect(device, source, type);
  }

  /**
   * Calls OpenSoundEffect(AudioDevice*, SampleSource*,
   * SoundEffectType) with a sample source created from the file.
   */
  inline SoundEffect* OpenSoundEffect(
    const AudioDevicePtr& device,
    const FilePtr& file,
    SoundEffectType type,
    FileFormat file_format = FF_AUTODETECT)
  {
    SampleSource* source = OpenSampleSource(file, file_format);
    return OpenSoundEffect(device, source, type);
  }

  /**
   * Opens a default file implementation from the local filesystem.
   *
   * @param filename   The name of the file on the local filesystem.
   * @param writeable  Whether the writing to the file is allowed.
   */
  inline File* OpenFile(const char* filename, bool writeable) {
    return hidden::AdrOpenFile(filename, writeable);
  }

  /**
   * Creates a File implementation that reads from a buffer in memory.
   * It stores a copy of the buffer that is passed in.
   *
   * The File object does <i>not</i> take ownership of the memory buffer.
   * When the file is destroyed, it will not free the memory.
   *
   * @param buffer  Pointer to the beginning of the data.
   * @param size    Size of the buffer in bytes.
   *
   * @return  0 if size is non-zero and buffer is null. Otherwise,
   *          returns a valid File object.
   */
  inline File* CreateMemoryFile(const void* buffer, int size) {
    return hidden::AdrCreateMemoryFile(buffer, size);
  }

  /**
   * Generates a list of available CD device names.
   *
   * @param devices A vector of strings to be filled.
   */
  inline void EnumerateCDDevices(std::vector<std::string>& devices) {
    const char* d = hidden::AdrEnumerateCDDevices();
    while (d && *d) {
      devices.push_back(d);
      d += strlen(d) + 1;
    }
  }

  /**
   * Opens the specified CD playback device.
   * 
   * @param device  The filesystem device to be played.
   *                e.g. Linux: "/dev/cdrom", Windows: "D:"
   *
   * @return  0 if opening device failed, valid CDDrive object otherwise.
   */
  inline CDDevice* OpenCDDevice(const char* device) {
    return hidden::AdrOpenCDDevice(device);
  }

  /**
   * Opens the specified MIDI synthesizer device.
   *
   * @param device  The name of the device.  Unused for now.
   *
   * @return  0 if opening device failed, valid MIDIDevice object otherwise.
   */
  inline MIDIDevice* OpenMIDIDevice(const char* device) {
    return hidden::AdrOpenMIDIDevice(device);
  }

}


#endif
