--------------------------------------------------------------------------------
-- |
-- Module      :  Sound.OpenAL.AL.Buffer
-- Copyright   :  (c) Sven Panne 2003-2009
-- License     :  BSD-style (see the file LICENSE)
-- 
-- Maintainer  :  sven.panne@aedion.de
-- Stability   :  stable
-- Portability :  portable
--
-- This module corresponds to chapter 6 (Buffers) of the OpenAL Specification
-- and Reference (version 1.1).
--
-- A buffer encapsulates OpenAL state related to storing sample data. The
-- application can request and release buffer objects, and fill them with
-- data. Data can be supplied compressed and encoded as long as the format is
-- supported. Buffers can, internally, contain waveform data as uncompressed or
-- compressed samples.
-- 
-- Unlike source (see "Sound.OpenAL.AL.Source") and listener (see
-- "Sound.OpenAL.AL.Listener") objects, buffer objects can be shared among AL
-- contexts. Buffers are referenced by sources. A single buffer can be referred
-- to by multiple sources. This separation allows drivers and hardware to
-- optimize storage and processing where applicable.
-- 
-- The simplest supported format for buffer data is PCM. PCM data is assumed to
-- use the processor\'s native byte order. Other formats use the byte order
-- native to that format.
-- 
-- At this time, buffer states are defined for purposes of discussion. The
-- states described in this section are not exposed through the API (can not be
-- queried, or be set directly), and the state description used in the
-- implementation might differ from this.
--
-- A buffer is considered to be in one of the following states, with respect to
-- all sources:
--
-- [/unused/] The buffer is not included in any queue for any source. In
-- particular, the buffer is neither pending nor current for any source. The
-- buffer name can be deleted at this time.
--
-- [/processed/] The buffer is listed in the queue of at least one source, but
-- is neither pending nor current for any source. The buffer can be deleted as
-- soon as it has been unqueued for all sources it is queued with.
--
-- [/pending/] There is at least one source for which the buffer has been queued,
-- for which the buffer data has not yet been dereferenced. The buffer can only
-- be unqueued for those sources that have dereferenced the data in the buffer
-- in its entirety, and cannot be deleted or changed.
--
-- The buffer state is dependent on the state of all sources that is has been
-- queued for. A single queue occurrence of a buffer propagates the buffer state
-- (over all sources) from /unused/ to /processed/ or higher. Sources that are
-- in the 'Stopped' or 'Initial' states still have queue entries that cause
-- buffers to be /processed/.
-- 
-- A single queue entry with a single source for which the buffer is not yet
-- /processed/ propagates the buffer\'s queuing state to /pending/.
-- 
-- Buffers that are /processed/ for a given source can be unqueued from that
-- source\'s queue. Buffers that have been unqueued from all sources are
-- /unused/. Buffers that are /unused/ can be deleted, or changed by writing
-- 'bufferData'.
--
--------------------------------------------------------------------------------

module Sound.OpenAL.AL.Buffer (
   Buffer, MemoryRegion(..), Format(..), BufferData(..), bufferData
) where

import Data.StateVar
import Foreign.Marshal.Alloc ( alloca )
import Foreign.Ptr ( Ptr, nullPtr )
import Sound.OpenAL.AL.BufferInternal ( Buffer(..) )
import Sound.OpenAL.AL.BasicTypes ( ALint, ALsizei, ALenum )
import Sound.OpenAL.AL.Format ( Format(..), marshalFormat )
import Sound.OpenAL.AL.PeekPoke ( peek1 )
import Sound.OpenAL.ALC.Context ( Frequency )
import Sound.OpenAL.Constants ( al_FREQUENCY, al_SIZE, al_BITS, al_CHANNELS )

#ifdef __HADDOCK__
import Sound.OpenAL.AL.SourceState ( SourceState(..) )
import Sound.OpenAL.AL.Errors ( ALErrorCategory(..) )
#endif

--------------------------------------------------------------------------------

data MemoryRegion a = MemoryRegion (Ptr a) ALsizei
#ifdef __HADDOCK__
-- Help Haddock a bit, because it doesn't do any instance inference.
instance Eq (MemoryRegion a)
instance Ord (MemoryRegion a)
instance Show (MemoryRegion a)
#else
   deriving ( Eq, Ord, Show )
#endif

data BufferData a = BufferData (MemoryRegion a) Format Frequency
#ifdef __HADDOCK__
-- Help Haddock a bit, because it doesn't do any instance inference.
instance Eq (BufferData a)
instance Ord (BufferData a)
instance Show (BufferData a)
#else
   deriving ( Eq, Ord, Show )
#endif

--------------------------------------------------------------------------------

-- | A special case of buffer state is the actual sound sample data stored in
-- association with the buffer. Applications can specify sample data using
-- 'bufferData'.
--
-- The data specified is copied to an internal software, or if possible,
-- hardware buffer. The implementation is free to apply decompression,
-- conversion, resampling, and filtering as needed. The internal format of the
-- buffer is not exposed to the application, and not accessible.
--
-- Buffers containing audio data with more than one channel will be played
-- without 3D spatialization features, these formats are normally used for
-- background music. Applications should always check for an error condition
-- after attempting to specify buffer data in case an implementation has to
-- generate an 'ALOutOfMemory' or a conversion related 'ALInvalidValue' error.
-- The application is free to reuse the memory specified by the data pointer
-- once 'bufferData' is set. The implementation has to dereference, e.g. copy,
-- the data while accessing 'bufferData' execution.

bufferData :: Buffer -> StateVar (BufferData a)
bufferData buffer = makeStateVar (getBufferData buffer) (setBufferData buffer)

getBufferData :: Buffer -> IO (BufferData a)
getBufferData buffer = do
   channels <- getBufferi buffer Channels
   bits <- getBufferi buffer Bits
   raw <- return nullPtr -- ToDo: AL_DATA query missing!!!
   size <- getBufferi buffer Size
   frequency <- getBufferi buffer Frequency
   return $ BufferData (MemoryRegion  raw size) (makeFormat channels bits) frequency

setBufferData :: Buffer -> BufferData a -> IO ()
setBufferData buffer (BufferData (MemoryRegion raw size) format frequency) =
      alBufferData buffer (marshalFormat format) raw size (round frequency)

foreign import CALLCONV unsafe "alBufferData"
   alBufferData :: Buffer -> ALenum -> Ptr a -> ALsizei -> ALsizei -> IO ()

--------------------------------------------------------------------------------

-- ToDo: What about IMAADPCMMono16, IMAADPCMStereo16, Vorbis...?
makeFormat :: ALint -> ALint -> Format
makeFormat 1  8 = Mono8
makeFormat 2  8 = Stereo8
makeFormat 1 16 = Mono16
makeFormat 2 16 = Stereo16
makeFormat channels bits =
   error ("makeFormat: illegal values " ++ show (channels, bits))

--------------------------------------------------------------------------------

data BufferQuery =
     Frequency
   | Size
   | Bits
   | Channels

marshalBufferQuery :: BufferQuery -> ALenum
marshalBufferQuery x = case x of
   Frequency -> al_FREQUENCY
   Size -> al_SIZE
   Bits -> al_BITS
   Channels -> al_CHANNELS

--------------------------------------------------------------------------------

getBufferi :: Num a => Buffer -> BufferQuery -> IO a
getBufferi buffer query =
   alloca $ \buf -> do
      alGetBufferi buffer (marshalBufferQuery query) buf
      peek1 fromIntegral buf

foreign import CALLCONV unsafe "alGetBufferi"
   alGetBufferi :: Buffer -> ALenum -> Ptr ALint -> IO ()
