-- |
-- Module : Sound.MikMod
-- License : LGPL3
-- 
-- MikMod bindings for Haskell

module Sound.MikMod 
(
  -- * Overview
  -- $overview

  -- * Quickstart
  -- $quickstart

  -- * Globals
  mikmodSetMusicVolume,
  mikmodGetMusicVolume,
  mikmodSetPanSep,
  mikmodGetPanSep,
  mikmodSetReverb,
  mikmodGetReverb,
  mikmodSetSndFXVolume,
  mikmodGetSndFXVolume,
  mikmodSetVolume,
  mikmodGetVolume,
  mikmodSetDeviceIndex,
  mikmodGetDeviceIndex,
  MDriverInfo(..),
  mikmodGetDriver,
  mikmodGetMixFreq,
  mikmodSetMixFreq,
  DriverModeFlag(..),
  mikmodModifyDriverModeFlags,
  mikmodGetDriverModeFlags,
  mikmodSetDriverModeFlags,

  -- * Core Operations
  runMikMod,
  mikmodSetup,
  mikmodGetVersion,
  mikmodGetError,
  mikmodRegisterAllDrivers,
  mikmodRegisterAllLoaders,
  mikmodInit,
  mikmodInitSafe,
  mikmodActive,
  mikmodInfoDriver,
  mikmodInfoLoader,
  mikmodSetNumVoices,
  mikmodSetNumVoicesSafe,
  mikmodReset,
  mikmodResetSafe,
  mikmodDisableOutput,
  mikmodEnableOutput,
  mikmodUpdate,
  mikmodExit,

  -- * Module Player Operations
  CuriousFlag(..),
  playerLoad,
  playerLoadSafe,
  playerLoadGeneric,
  playerLoadGenericSafe,
  playerLoadTitle,
  playerStart,
  playerStop,
  playerPaused,
  playerTogglePause,
  playerActive,
  playerFree,
  playerGetChannelVoice,
  playerGetModule,
  MuteOperation(..),
  playerMuteChannel,
  playerMuteChannels,
  playerUnmuteChannel,
  playerUnmuteChannels,
  playerToggleMuteChannel,
  playerToggleMuteChannels,
  playerMuted,
  playerNextPosition,
  playerPrevPosition,
  playerSetPosition,
  playerSetSpeed,
  playerSetTempo,

  -- * Module Operations
  ModuleHandle,
  ModuleInfo(..),
  ModuleFlag(..),
  getModuleInfo,
  getModuleRealChannels,
  getModuleTotalChannels,
  getModuleSongTime,
  getModuleSongPosition,
  getModulePatternPosition,
  setModuleInitSpeed,
  getModuleInitSpeed,
  setModuleInitTempo,
  getModuleInitTempo,
  setModulePanning,
  getModulePanning,
  setModuleChannelVolume,
  getModuleChannelVolume,
  setModuleBPM,
  getModuleBPM,
  setModuleSongSpeed,
  getModuleSongSpeed,
  setModuleExtSpeed,
  getModuleExtSpeed,
  setModulePanFlag,
  getModulePanFlag,
  setModuleWrap,
  getModuleWrap,
  setModuleRepeatPosition,
  getModuleRepeatPosition,
  setModuleLoop,
  getModuleLoop,
  setModuleFadeout,
  getModuleFadeout,
  setModuleRelativeSpeed,
  getModuleRelativeSpeed,
  getModuleSamples,

  -- * Sample Operations
  SampleHandle,
  SampleInfo(..),
  sampleLoad,
  sampleLoadSafe,
  sampleLoadGeneric,
  sampleLoadGenericSafe,
  samplePlay,
  samplePlayCritical,
  sampleFree,
  getSampleInfo,
  Pan(..),
  panLeft,
  panRight,
  setSamplePanning,
  getSamplePanning,
  setSampleSpeed,
  getSampleSpeed,
  setSampleVolume,
  getSampleVolume,
  SampleFlag(..),
  modifySampleFlags,
  getSampleFlags,
  setSampleFlags,
  getSampleInFlags,
  getSampleLength,
  setSampleLoopStart,
  getSampleLoopStart,
  setSampleLoopEnd,
  getSampleLoopEnd,

  -- * Voice Operations
  Voice(..),
  voicePlay,
  voiceStop,
  voiceStopped,
  voiceSetVolume,
  voiceGetVolume,
  voiceSetFrequency,
  voiceGetFrequency,
  voiceSetPanning,
  voiceGetPanning,
  voiceGetPosition,
  voiceRealVolume,

  -- * MReaders
  MReader(..),
  Outcome(..),
  IsEOF(..),
  byteStringReader,
  handleReader,

  -- * Errors
  MikModError(..),
  MikModException(..),
  describeMikModError,
  getErrno,
  MikModErrno(..),

  -- * Esoterica
  mikmodInitThreads,
  withMikMod

)
where

import Foreign.Ptr
import Foreign.ForeignPtr
import Foreign.Storable
import Foreign.C.String
import Data.Functor
import Control.Applicative
import Control.Exception
import Data.Bits

import Sound.MikMod.Synonyms
import Sound.MikMod.Types
import Sound.MikMod.Errors
import Sound.MikMod.Flags
import Sound.MikMod.Internal
import Sound.MikMod.MReader
import Sound.MikMod.Module
import Sound.MikMod.Sample

-- $overview
--
-- <http://mikmod.sourceforge.net/ MikMod> is a C library for 
-- playing music modules and sound samples.
-- 
-- The user controls MikMod by manipulating a handful of global variables,
-- calling API functions, and manipulating fields of the Module and Sample
-- structure. These low-level bindings are basically convenience wrappers for
-- the above operations.
--
-- Module objects represent not only music but the playback state of a song.
-- In this sense you can think of Modules as being like cassette tapes. For
-- example if a playing module is paused or stopped, and another module begins
-- playing, then resuming the original module will start from the position it
-- was stopped at. You manipulate modules only via the type ModuleHandle.
--
-- Sample objects represent single sounds. Modules use samples to make music,
-- but you can use samples independently for sound effects. Samples are similar
-- to Modules in that they are only accessed via the type SampleHandle.
--
-- Music and sound effects both play samples on voices. There can be at most
-- one sample playing on a voice at a time. Voices can be individually adjusted
-- to change the characteristics of the samples that play on them. Voices are
-- exposed by MikMod as indexes. These indexes are wrapped in the Voice newtype.
-- 
-- Modifying values of global variables and structure fields is allowed during
-- playback and in most cases will have immediate effect.
--
-- MikMod allows loading modules and samples from the file system or from an
-- arbitrary source via the MReader structure.
--
-- API functions that may fail come in two flavors: one that throws an exception
-- and one that returns an Either.
--
-- The MikMod error callback mechanism and the MWriter are not supported yet.
--
-- Beware of using 'playerLoad' while a song is playing. It has the side effect
-- of adjusting the number of music voices (see 'mikmodSetNumVoices')!

-- $quickstart
--
-- @
-- import Control.Concurrent (threadDelay)
-- import Control.Monad.Loops (whileM_)
-- import Sound.MikMod
--
-- main = do
--   mikmodRegisterAllDrivers
--   mikmodRegisterAllLoaders
--   mikmodInit ""
--   mod <- playerLoad "rock on.mod" 128 NotCurious
--   playerStart mod
--   whileM_ playerActive $ do
--     mikmodUpdate -- might be unnecessary on your system
--     threadDelay 10000
--   playerFree mod
--   mikmodExit
-- @
--
-- Make sure to link your program to MikMod with -lmikmod. GHCI can be used to
-- experiment by using ghci -lmikmod.
--
-- Example of playing sound effects.
--
-- @
-- import Control.Concurrent (threadDelay)
-- import Control.Monad.Loops (whileM_)
-- import Data.Functor ((\<$\>))
-- import Sound.MikMod
--
-- main = do
--   mikmodRegisterAllDrivers
--   mikmodRegisterAllLoaders
--   mikmodInit ""
--   mikmodSetNumVoices (-1) 4
--   mikmodEnableOutput
--   samp <- sampleLoad "wilhelm.wav"
--   Just voice <- samplePlay samp 0
--   whileM_ (not \<$\> voiceStopped voice) $ do
--     mikmodUpdate -- might be unnecessary on your system
--     threadDelay 10000
--   sampleFree samp
--   mikmodExit
-- @
-- 
-- Or using convenience wrappers for the initialization sequence,
--
-- @
-- import Control.Concurrent (threadDelay)
-- import Control.Monad.Loops (whileM_)
-- import Data.Functor ((\<$\>))
-- import Sound.MikMod
--
-- main = runMikMod 4 $ do
--   samp <- sampleLoad "wilhelm.wav"
--   Just voice <- samplePlay samp 0
--   whileM_ (not \<$\> voiceStopped voice) $ do
--     mikmodUpdate
--     threadDelay 10000
--   sampleFree samp
-- @

-- | Set the global music volume. The argument must be in the range 0 to 128
-- (There are 129 volume levels).
mikmodSetMusicVolume :: Int -> IO ()
mikmodSetMusicVolume v = poke c_md_musicvolume (fromIntegral v)

mikmodGetMusicVolume :: IO Int
mikmodGetMusicVolume = fromIntegral <$> peek c_md_musicvolume

-- | Set the global stereo separation. The argument must be in the range 0 to
-- 128 where 0 means mono sound and 128 means full separation. The default
-- pan sep is 128.
mikmodSetPanSep :: Int -> IO ()
mikmodSetPanSep v = poke c_md_pansep (fromIntegral v)

mikmodGetPanSep :: IO Int
mikmodGetPanSep = fromIntegral <$> peek c_md_pansep

-- | Set the global reverb. The argument must be in the range 0 to 15 where 0
-- means no reverb and 15 means extreme reverb. The default reverb is zero.
mikmodSetReverb :: Int -> IO ()
mikmodSetReverb v = poke c_md_reverb (fromIntegral v)

mikmodGetReverb :: IO Int
mikmodGetReverb = fromIntegral <$> peek c_md_reverb

-- | Set the global sound effects volume. The argument must be in the range 0 to 128.
mikmodSetSndFXVolume :: Int -> IO ()
mikmodSetSndFXVolume v = poke c_md_sndfxvolume (fromIntegral v)

mikmodGetSndFXVolume :: IO Int
mikmodGetSndFXVolume = fromIntegral <$> peek c_md_sndfxvolume


-- | Set the global overall sound volume. The argument must be in the range 0 to 128.
mikmodSetVolume :: Int -> IO ()
mikmodSetVolume v = poke c_md_volume (fromIntegral v)

mikmodGetVolume :: IO Int
mikmodGetVolume = fromIntegral <$> peek c_md_volume


-- | The selected output driver from the global 1-based list of drivers.
mikmodGetDeviceIndex :: IO Int
mikmodGetDeviceIndex = fromIntegral <$> peek c_md_device

-- | Change the selected output driver by specifying a 1-based index into the
-- global list of drivers. Setting this to zero, the default, means autodetect.
-- To see the list use 'mikmodInfoDriver'.
mikmodSetDeviceIndex :: Int -> IO ()
mikmodSetDeviceIndex i = poke c_md_device (fromIntegral i)

-- | Get an info report of the sound driver currently in use, if any. MikMod
-- does not expose any functionality via MDriver field manipulation.
mikmodGetDriver :: IO (Maybe MDriverInfo)
mikmodGetDriver = do
  r <- peek c_md_driver
  if r == nullPtr
    then return Nothing
    else Just <$> peekMDriver r

-- | Set the mix frequency measured in Hertz. Higher values mean more sound
-- quality and more CPU usage. Common values are 8000, 11025, 22100, and
-- 44100. The default is 44100.
mikmodSetMixFreq :: Int -> IO ()
mikmodSetMixFreq freq = poke c_md_mixfreq (fromIntegral freq)

mikmodGetMixFreq :: IO Int
mikmodGetMixFreq = fromIntegral <$> peek c_md_mixfreq

-- | Modify the "mode flags". These flags affect sound output in various ways.
-- For a full explanation of each one see the MikMod docs. Changing DModeInterp,
-- DModeReverse, or DModeSurround will affect playing immediately. The other
-- flags will require a reset. The default flags are set to [DModeStereo, DModeSurround,
-- DMode16Bits, DModeSoftMusic, DModeSoftSndFX].
mikmodModifyDriverModeFlags :: ([DriverModeFlag] -> [DriverModeFlag]) -> IO ()
mikmodModifyDriverModeFlags f = do
  flags <- mikmodGetDriverModeFlags
  mikmodSetDriverModeFlags (f flags)

mikmodGetDriverModeFlags :: IO [DriverModeFlag]
mikmodGetDriverModeFlags = unpackFlags <$> peek c_md_mode

-- | See 'mikmodModifyDriverModeFlags' to avoid clobbering flags you aren't
-- trying to clear.
mikmodSetDriverModeFlags :: [DriverModeFlag] -> IO ()
mikmodSetDriverModeFlags flags = poke c_md_mode (packFlags flags)




-- | Initialize the MikMod system using an initialization string. An empty
-- string is acceptable (see MikMod docs for more info). If initialization
-- fails it will throw a MikModError.
--
-- Don't try this until you register a driver which is supported by your
-- system. See 'mikmodRegisterAllDrivers'.
--
-- See also the convenience functions 'mikmodSetup' and 'runMikMod'.
mikmodInit :: String -> IO ()
mikmodInit params = do
  r <- mikmodInitSafe params
  case r of
    Left e  -> throwIO (MikModException e)
    Right _ -> return ()

-- | Same as 'mikmodInit' but doesn't throw exceptions.
mikmodInitSafe :: String -> IO (Either MikModError ())
mikmodInitSafe params = withCString params $ \ptr -> do
  n <- c_MikMod_Init ptr
  if n == 0
    then return (Right ())
    else Left <$> mikmodGetError

-- | Registers all drivers and loaders, initializes MikMod, sets a number
-- of sample voices and enables output.
mikmodSetup :: Int -> IO ()
mikmodSetup sfxVoices = do
  mikmodRegisterAllDrivers
  mikmodRegisterAllLoaders
  mikmodInit ""
  mikmodSetNumVoices (-1) sfxVoices
  mikmodEnableOutput
  
-- | Run an action between 'mikmodSetup' and 'mikmodExit'. It does not handle
-- freeing of Modules or Samples.
runMikMod :: Int -> IO a -> IO a
runMikMod sfxVoices action = do
  mikmodSetup sfxVoices
  action `finally` mikmodExit

-- | Shutdown the MikMod system.
mikmodExit :: IO ()
mikmodExit = c_MikMod_Exit

-- | Returns True if and only if sound output is enabled.
mikmodActive :: IO Bool
mikmodActive = decodeBool <$> c_MikMod_Active

-- | Enable output. Playing modules will enable output automatically.
-- However playing samples does not. Therefore use 'mikmodEnableOutput' if
-- you intend to play sound effects with no music. The convenience function
-- 'mikmodSetup' enables output among other things.
mikmodEnableOutput :: IO ()
mikmodEnableOutput = c_MikMod_EnableOutput

-- | Disable output.
mikmodDisableOutput :: IO ()
mikmodDisableOutput = c_MikMod_DisableOutput

-- | Get the MikMod version as (major, minor, revision).
mikmodGetVersion :: IO (Int, Int, Int)
mikmodGetVersion = do
  enc <- fromIntegral <$> c_MikMod_GetVersion
  return (enc `shiftR` 16, 0xff .&. (enc `shiftR` 8), 0xff .&. enc)

-- | Get a formatted string describing the available drivers, if any.
mikmodInfoDriver :: IO (Maybe String)
mikmodInfoDriver = mikmodGetString c_MikMod_InfoDriver

-- | Get a formatted string describing the available loaders, if any.
mikmodInfoLoader :: IO (Maybe String)
mikmodInfoLoader = mikmodGetString c_MikMod_InfoLoader

-- | If your libmikmod has pthread support, returns True. Otherwise this
-- may initialize internal mutexes to support multi-threaded access anyway.
-- A result of True indicates this was successful. False indicates no support
-- for multi-threaded access is available. It is safe to call this multiple
-- times. Only the first call has any effect.
--
-- Short story: Before attempting to use MikMod from multiple threads execute
-- this and check that the result is True.
--
-- This only has an effect on Win32, OS/2, and EMX.
mikmodInitThreads :: IO Bool
mikmodInitThreads = decodeBool <$> c_MikMod_InitThreads

-- | Execute the action after calling MikMod_Lock. Calls MikMod_Unlock afterwards
-- even if an error occurred. If 'mikmodInitThreads' returns True then it means
-- all calls to libmikmod will be protected by internal mutexes. Therefore using
-- MikMod functions inside a 'withMikMod' will deadlock. Allowing clients to
-- manually lock MikMod is probably only useful when manipulating shared data
-- across the API boundary.
withMikMod :: IO a -> IO a
withMikMod = c_MikMod_Lock `bracket_` c_MikMod_Unlock

-- | Register all drivers. Use this before initializing MikMod with 'mikmodInit'.
mikmodRegisterAllDrivers :: IO ()
mikmodRegisterAllDrivers = c_MikMod_RegisterAllDrivers

-- | Register all loaders. Use this before loading any modules.
mikmodRegisterAllLoaders :: IO ()
mikmodRegisterAllLoaders = c_MikMod_RegisterAllLoaders

-- | Reset the driver using the new global variable settings.
-- If the driver has not been initialized, it will be now. Throws a
-- MikModError in case of failure.
mikmodReset :: String -> IO ()
mikmodReset params = do
  r <- mikmodResetSafe params
  case r of
    Left e  -> throwIO (MikModException e)
    Right _ -> return ()

-- | Same as 'mikmodReset' but doesn't throw exceptions.
mikmodResetSafe :: String -> IO (Either MikModError ())
mikmodResetSafe params = withCString params $ \ptr -> do
  n <- c_MikMod_Reset ptr
  if n == 0
    then return (Right ())
    else Left <$> mikmodGetError

-- | Set the number of music voices and sample voices to be used for playback.
-- If either parameter is -1, the currently set value will be retained.
--
-- This is executed by 'playerLoad' as a side effect of loading a module to
-- set the number of music voices.
--
-- If this operation fails it will throw a MikModError.
mikmodSetNumVoices :: Int -- ^ Number of music voices or -1
                   -> Int -- ^ Number of sample voices or -1
                   -> IO ()
mikmodSetNumVoices music sample = do
  r <- mikmodSetNumVoicesSafe music sample
  case r of
    Left e  -> throwIO (MikModException e)
    Right _ -> return ()

-- | Same as 'mikmodSetNumVoices' but doesn't throw exceptions.
mikmodSetNumVoicesSafe :: Int -> Int -> IO (Either MikModError ())
mikmodSetNumVoicesSafe music sample = do
  n <- c_MikMod_SetNumVoices (fromIntegral music) (fromIntegral sample)
  if n == 0
    then return (Right ())
    else Left <$> mikmodGetError

-- | Update the sound. If you don't call this often enough, then sound might drop
-- out. If you call this too often, the audio driver may eat CPU in a busy loop.
-- Higher quality audio requires calling mikmodUpdate more often (see
-- 'mikmodSetMixFreq'). And finally, on some drivers this is a no-op because
-- there is an audio callback.
--
-- Known:
--
-- - On OSX there is an audio callback and polling mikmodUpdate is unnecessary.
--
-- - On Linux mikmodUpdate triggers ALSA's non-blocking IO API and is necessary.
mikmodUpdate :: IO ()
mikmodUpdate = c_MikMod_Update

-- | Returns True if and only if a song is playing.
playerActive :: IO Bool
playerActive = decodeBool <$> c_Player_Active

-- | Free a module and all its contents. If the module was playing then it
-- will be stopped. Discard the ModuleHandle after using this operation.
playerFree :: ModuleHandle -> IO ()
playerFree = c_Player_Free

-- | Returns the voice corresponding to a module channel.
playerGetChannelVoice :: Int -> IO (Maybe Voice)
playerGetChannelVoice ch = do
  v <- c_Player_GetChannelVoice (fromIntegral ch)
  if v >= 0
    then return $ Just (Voice v)
    else return Nothing

-- | Get the currently playing module, if any.
playerGetModule :: IO (Maybe ModuleHandle)
playerGetModule = do
  ptr <- c_Player_GetModule
  if ptr == nullPtr
    then pure Nothing
    else pure (Just ptr)

-- | Load a module from a file.
--
-- The second argument is the maximum number of channels to allow. Basic 4
-- channel mods only need 4 music voices. But some impulse tracker modules
-- use a large number of extra channels for new note actions (NNAs). Only the
-- amount needed will be allocated so using a large value here like 64 doesn't
-- hurt.
--
-- This operation has side effects. When the module is loaded the number of
-- music voices will be adjusted (see 'mikmodSetNumVoices') to accommodate the
-- song. If a song is already playing that needs more voices than the song
-- being loaded, you will hear a reduction in music quality!
-- If want to load many ModuleHandles then use 'mikmodSetNumVoices' afterward
-- to set the number of music voices to some upper bound for the group.
--
-- If something goes wrong while loading the module it will throw a MikModError.
playerLoad :: FilePath -> Int -> CuriousFlag -> IO ModuleHandle
playerLoad path maxChans curious = do
  r <- playerLoadSafe path maxChans curious
  case r of
    Left e    -> throwIO (MikModException e)
    Right mod -> return mod

-- | Same as 'playerLoad' but doesn't throw exceptions.
playerLoadSafe :: FilePath -> Int -> CuriousFlag -> IO (Either MikModError ModuleHandle)
playerLoadSafe path maxChans curious = withCString path $ \cstr -> do
  ptr <- c_Player_Load cstr (fromIntegral maxChans) (marshalCurious curious)
  if ptr == nullPtr
    then Left <$> mikmodGetError
    else Right <$> pure ptr

-- | Same as 'playerLoad' but loads the module data from an MReader.
playerLoadGeneric :: MReader -> Int -> CuriousFlag -> IO ModuleHandle
playerLoadGeneric rd maxChans curious = do
  r <- playerLoadGenericSafe rd maxChans curious
  case r of
    Left e    -> throwIO (MikModException e)
    Right mod -> return mod

-- | Same as 'playerLoadGeneric' but doesn't throw exceptions.
playerLoadGenericSafe :: MReader -> Int -> CuriousFlag -> IO (Either MikModError ModuleHandle)
playerLoadGenericSafe rd maxChans curious = withMReader rd $ \rptr -> do
  mptr <- c_Player_LoadGeneric rptr (fromIntegral maxChans) (marshalCurious curious)
  if mptr == nullPtr
    then Left <$> mikmodGetError
    else Right <$> pure mptr

-- | Load only the title from a module file. Returns Nothing if there is
-- no title. If something goes wrong it will throw a MikModError.
playerLoadTitle :: FilePath -> IO (Maybe String)
playerLoadTitle path = do
  result <- playerLoadTitleSafe path
  case result of
    Left e -> throwIO (MikModException e)
    Right mtitle -> return mtitle


-- | Same as 'playerLoadTitle' but doesn't throw exceptions.
playerLoadTitleSafe :: FilePath -> IO (Either MikModError (Maybe String))
playerLoadTitleSafe path = do
  result <- withCString path c_Player_LoadTitle
  if result == nullPtr
    then do
      mme <- mikmodGetError
      if isNotAnError mme
        then return (Right Nothing)
        else return (Left mme)
    else Right . Just <$> peekCString result


-- | Mute a channel.
playerMuteChannel :: Int -> IO ()
playerMuteChannel ch = c_Player_MuteChannel (fromIntegral ch)

-- | Mute a range of channels. MuteOperation determines if the range is
-- inclusive or exclusive.
playerMuteChannels :: MuteOperation -> Int -> Int -> IO ()
playerMuteChannels op chanL chanU = c_Player_MuteChannels
  (marshalMuteOperation op)
  (fromIntegral chanL)
  (fromIntegral chanU)

-- | Return True if and only if a channel is muted.
playerMuted :: Int -> IO Bool
playerMuted ch = decodeBool <$> c_Player_Muted (fromIntegral ch)

-- | Skip to the next position in the current module.
playerNextPosition :: IO ()
playerNextPosition = c_Player_NextPosition

-- | Go back to the previous position in the current module.
playerPrevPosition :: IO ()
playerPrevPosition = c_Player_PrevPosition

-- | Returns True if and only if the player is paused.
playerPaused :: IO Bool
playerPaused = decodeBool <$> c_Player_Paused

-- | Set the position of the current module. 
playerSetPosition :: Int -> IO ()
playerSetPosition pos = c_Player_SetPosition (fromIntegral pos)

-- | Set the speed of the current module to a value in the range 1 to 32.
playerSetSpeed :: Int -> IO ()
playerSetSpeed speed = c_Player_SetSpeed (fromIntegral speed)

-- | Set the tempo of the current module to a value in the range 32 to 255.
playerSetTempo :: Int -> IO ()
playerSetTempo tempo = c_Player_SetTempo (fromIntegral tempo)

-- | Set the volume of the current module to a value in the range 0 to 128.
playerSetVolume :: Int -> IO ()
playerSetVolume volume = c_Player_SetVolume (fromIntegral volume)

-- | Begin playing a module. If another module is already playing it will
-- be stopped.
playerStart :: ModuleHandle -> IO ()
playerStart = c_Player_Start

-- | Stop the player.
playerStop :: IO ()
playerStop = c_Player_Stop

-- | Toggle the muting of the specified channel.
playerToggleMuteChannel :: Int -> IO ()
playerToggleMuteChannel ch = c_Player_ToggleMuteChannel (fromIntegral ch)

-- | Toggle the muting of a range of channels. MuteOperation determines if
-- the range is inclusive or exclusive.
playerToggleMuteChannels :: MuteOperation -> Int -> Int -> IO ()
playerToggleMuteChannels op chanL chanU = c_Player_ToggleMuteChannels
  (marshalMuteOperation op)
  (fromIntegral chanL)
  (fromIntegral chanU)

-- | Pause the player if it isn't paused. Otherwise unpause it.
playerTogglePause :: IO ()
playerTogglePause = c_Player_TogglePause

-- | Unmute the given channel.
playerUnmuteChannel :: Int -> IO ()
playerUnmuteChannel ch = c_Player_UnmuteChannel (fromIntegral ch)

-- | Toggle the muting of a range of channels. MuteOperation determines if the
-- range is inclusive or exclusive.
playerUnmuteChannels :: MuteOperation -> Int -> Int -> IO ()
playerUnmuteChannels op chanL chanU = c_Player_UnmuteChannels
  (marshalMuteOperation op)
  (fromIntegral chanL)
  (fromIntegral chanU)

-- | Load a sample from a mono, uncompressed RIFF WAV file. If something
-- goes wrong while loading the sample it will throw a MikModError.
sampleLoad :: FilePath -> IO SampleHandle
sampleLoad path = do
  r <- sampleLoadSafe path
  case r of
    Left e     -> throwIO (MikModException e)
    Right samp -> return samp

-- | Same as 'sampleLoad' but doesn't throw exceptions.
sampleLoadSafe :: FilePath -> IO (Either MikModError SampleHandle)
sampleLoadSafe path = withCString path $ \cstr -> do
  ptr <- c_Sample_Load cstr
  if ptr == nullPtr
    then Left <$> mikmodGetError
    else Right <$> pure ptr

-- | Same as 'sampleLoad' but read sample data from an MReader.
sampleLoadGeneric :: MReader -> IO SampleHandle
sampleLoadGeneric mr = do
  r <- sampleLoadGenericSafe mr
  case r of
    Left e     -> throwIO (MikModException e)
    Right samp -> return samp

-- | Same as 'sampleLoadGeneric' but doesn't throw exceptions.
sampleLoadGenericSafe :: MReader -> IO (Either MikModError SampleHandle)
sampleLoadGenericSafe mr = withMReader mr $ \rptr -> do
  sptr <- c_Sample_LoadGeneric rptr
  if sptr == nullPtr
    then Left <$> mikmodGetError
    else Right <$> pure sptr


-- | Plays a sound effects sample. Picks a voice from the number of voices
-- allocated for use as sound effects. Returns the voice that the sound is
-- being played on. The oldest playing sample will be interrupted if necessary,
-- unless all playing samples are "critical", in which case the sound will not
-- play.
--
-- The second argument is the position, in samples, to start playing from.
samplePlay :: SampleHandle -> Int -> IO (Maybe Voice)
samplePlay samp start = do
  v <- c_Sample_Play samp (fromIntegral start) 0
  if v >= 0
    then (return . Just . Voice) v
    else return Nothing

-- | Same behavior as 'samplePlay' except that the voice the sound is played
-- on (if any) is given the "critical" status. Note that this will still not
-- result in any critical samples being interrupted.
samplePlayCritical :: SampleHandle -> Int -> IO (Maybe Voice)
samplePlayCritical samp start = do
  v <- c_Sample_Play samp (fromIntegral start) sfxCritical
  if v >= 0
    then (return . Just . Voice) v
    else return Nothing

-- | Free a sample. Do not sampleFree samples aquired via 'getModuleSamples'.
-- Those are freed with 'playerFree'. Discard the SampleHandle after using
-- this operation.
sampleFree :: SampleHandle -> IO ()
sampleFree = c_Sample_Free

-- | Set a voice's volume to a value in the range 0 to 256. There are 257
-- volume levels.
voiceSetVolume :: Voice -> Int -> IO ()
voiceSetVolume v vol = c_Voice_SetVolume (marshalVoice v) (fromIntegral vol)

voiceGetVolume :: Voice -> IO Int
voiceGetVolume v = fromIntegral <$> c_Voice_GetVolume (marshalVoice v)

-- | Set a voice's frequency in Hertz.
voiceSetFrequency :: Voice -> Int -> IO ()
voiceSetFrequency v freq = c_Voice_SetFrequency (marshalVoice v) (fromIntegral freq)

voiceGetFrequency :: Voice -> IO Int
voiceGetFrequency v = fromIntegral <$> c_Voice_GetFrequency (marshalVoice v)

-- | Set a voice's pan position. 0 is far left. 127 is center. 255 is far right.
voiceSetPanning :: Voice -> Int -> IO ()
voiceSetPanning v pan = c_Voice_SetPanning (marshalVoice v) (fromIntegral pan)

voiceGetPanning :: Voice -> IO Int
voiceGetPanning v = fromIntegral <$> c_Voice_GetPanning (marshalVoice v)

-- | Play a sample on the specified voice starting from the specified position
-- in samples. The playing sample will have the same "critical status" as the
-- previous sample played on this voice.
voicePlay :: Voice -> SampleHandle -> Int -> IO ()
voicePlay v samp start = c_Voice_Play (marshalVoice v) samp (fromIntegral start)

-- | Stop a voice from playing.
voiceStop :: Voice -> IO ()
voiceStop v = c_Voice_Stop (marshalVoice v)

-- | Returns True if and only if the specified voice is /not/ playing.
voiceStopped :: Voice -> IO Bool
voiceStopped v = decodeBool <$> c_Voice_Stopped (marshalVoice v)

-- | Query the position (in samples) of the sample playing on this voice.
-- If no sample is playing it will return zero. On some drivers this operation
-- does not work and will return -1.
voiceGetPosition :: Voice -> IO Int
voiceGetPosition v = fromIntegral <$> c_Voice_GetPosition (marshalVoice v)

-- | Compute the "actual playing volume" of the voice. The result will be in
-- the range 0 - 65535. On some drivers this operation does not work and will
-- return zero.
voiceRealVolume :: Voice -> IO Int
voiceRealVolume v = fromIntegral <$> c_Voice_RealVolume (marshalVoice v)

