packages feed

lambdasound-1.1: src/LambdaSound/Sound/ComputeSound.hs

module LambdaSound.Sound.ComputeSound where

import Control.Monad (join)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Data.HashTable.IO qualified as H
import Data.Hashable
import Data.Massiv.Array qualified as M
import Data.Massiv.Array.Unsafe qualified as MU
import Data.SomeStableName (SomeStableName, makeSomeStableName)
import Foreign.ForeignPtr (withForeignPtr)
import Foreign.Marshal (copyBytes)
import Foreign.Storable (Storable (..))
import GHC.Generics (Generic)
import LambdaSound.Sound.Types

makeWithIndexFunction :: (SamplingInfo -> Int -> a) -> ComputeSound a
makeWithIndexFunction f = makeDelayedResult $ \si ->
  let f' = f si
   in M.makeArray M.Seq (M.Sz1 si.samples) f'
{-# INLINE makeWithIndexFunction #-}

makeDelayedResult :: (SamplingInfo -> M.Vector M.D a) -> ComputeSound a
makeDelayedResult f = ComputeSound $ \si _ -> do
  stableF <- makeSomeStableName f
  pure (DelayedResult $ pure $ f si, ComputationInfoMakeDelayedResult stableF)
{-# INLINE makeDelayedResult #-}

changeSamplingInfo :: (SamplingInfo -> SamplingInfo) -> ComputeSound a -> ComputeSound a
changeSamplingInfo changeSI (ComputeSound compute) = ComputeSound $ \si memo -> do
  stableChangeSI <- makeSomeStableName changeSI
  (result, ci) <- compute (changeSI si) memo
  pure (result, ComputationInfoChangeSamplingInfo stableChangeSI ci)
{-# INLINE changeSamplingInfo #-}

mapDelayedResult :: (SamplingInfo -> M.Vector M.D a -> M.Vector M.D b) -> ComputeSound a -> ComputeSound b
mapDelayedResult mapVector cs = ComputeSound $ \si memo -> do
  (delayedVector, ci) <- asDelayedResult cs si memo
  stableMapVector <- makeSomeStableName mapVector
  let mapVector' = mapVector si
  pure (DelayedResult $ fmap mapVector' delayedVector, ComputationInfoMapDelayedResult stableMapVector ci)
{-# INLINE mapDelayedResult #-}

withSamplingInfoCS :: (SamplingInfo -> ComputeSound a) -> ComputeSound a
withSamplingInfoCS f = ComputeSound $ \si memo -> do
  stableF <- makeSomeStableName f
  let (ComputeSound compute) = f si
  (res, _) <- compute si memo
  pure (res, ComputationInfoWithSamplingInfo stableF)
{-# INLINE withSamplingInfoCS #-}

withSampledSoundPulseCS :: Duration -> ComputeSound Pulse -> (M.Vector M.S Pulse -> ComputeSound a) -> ComputeSound a
withSampledSoundPulseCS duration cs f = ComputeSound $ \si memo -> do
  let sampleSI = makeSamplingInfo si.sampleRate duration
  (writeSamples, ci) <- asWriteResult cs sampleSI memo
  dest <- MU.unsafeMallocMArray (M.Sz1 sampleSI.samples)
  writeSamples dest
  samples <- MU.unsafeFreeze M.Seq dest
  let (ComputeSound compute) = f samples
  (res, _) <- compute si memo
  stableF <- makeSomeStableName f
  pure (res, ComputationInfoWithSampledSound stableF ci)
{-# INLINE withSampledSoundPulseCS #-}

withSampledSoundCS :: Duration -> ComputeSound a -> (M.Vector M.D a -> ComputeSound b) -> ComputeSound b
withSampledSoundCS duration cs f = ComputeSound $ \si memo -> do
  let sampleSI = makeSamplingInfo si.sampleRate duration
  (delayedVector, ci) <- asDelayedResult cs sampleSI memo
  let nextCS = f <$> delayedVector
  stableF <- makeSomeStableName f
  pure
    ( DelayedResult $ do
        (finalDelayedVector, _) <- join $ asDelayedResult <$> nextCS <*> pure si <*> pure memo
        finalDelayedVector,
      ComputationInfoWithSampledSound stableF ci
    )
{-# INLINE withSampledSoundCS #-}

mergeDelayedResult :: (SamplingInfo -> M.Vector M.D a -> M.Vector M.D b -> M.Vector M.D c) -> ComputeSound a -> ComputeSound b -> ComputeSound c
mergeDelayedResult merge cs1 cs2 = ComputeSound $ \si memo -> do
  stableMerge <- makeSomeStableName merge
  (delayedResult1, ci1) <- asDelayedResult cs1 si memo
  (delayedResult2, ci2) <- asDelayedResult cs2 si memo
  let merge' = merge si
  pure (DelayedResult $ merge' <$> delayedResult1 <*> delayedResult2, ComputationInfoMergeDelayedResult stableMerge ci1 ci2)
{-# INLINE mergeDelayedResult #-}

computeSequentially :: Percentage -> ComputeSound Pulse -> ComputeSound Pulse -> ComputeSound Pulse
computeSequentially factor c1 c2 = ComputeSound $ \si memo -> do
  let splitIndex =
        round $
          factor * fromIntegral si.samples
  (writeResult1, ci1) <- asWriteResult c1 si {samples = splitIndex} memo
  (writeResult2, ci2) <- asWriteResult c2 si {samples = si.samples - splitIndex} memo
  pure
    ( WriteResult $ \dest -> do
        writeResult1 $ MU.unsafeLinearSliceMArray 0 (M.Sz1 splitIndex) dest
        writeResult2 $ MU.unsafeLinearSliceMArray splitIndex (M.Sz1 $ si.samples - splitIndex) dest,
      ComputationInfoSequentially factor ci1 ci2
    )
{-# INLINE computeSequentially #-}

computeParallel :: ComputeSound Pulse -> Percentage -> ComputeSound Pulse -> ComputeSound Pulse
computeParallel c1 factor c2 = ComputeSound $ \si memo -> do
  let c2N = round $ factor * fromIntegral si.samples
  (delayedResult1, p1) <- asDelayedResult c1 si memo
  (delayedResult2, p2) <- asDelayedResult c2 si {samples = c2N} memo
  pure
    ( if si.samples == c2N
        then DelayedResult $ M.zipWith (+) <$> delayedResult1 <*> delayedResult2
        else DelayedResult $ do
          dR1 <- delayedResult1
          dR2 <- delayedResult2
          pure $ M.imap (\index -> (+) $ if index < c2N then MU.unsafeIndex dR2 index else 0) dR1,
      ComputationInfoParallel factor p1 p2
    )
{-# INLINE computeParallel #-}

mapComputeSound :: (a -> b) -> ComputeSound a -> ComputeSound b
mapComputeSound f cs = ComputeSound $ \si memo -> do
  stableF <- makeSomeStableName f
  (result, ci) <- asDelayedResult cs si memo
  pure (DelayedResult $ M.map f <$> result, ComputationInfoMap stableF ci)
{-# INLINE mapComputeSound #-}

asDelayedResult ::
  ComputeSound a ->
  SamplingInfo ->
  MemoComputeSound ->
  IO (IO (M.Vector M.D a), ComputationInfo)
asDelayedResult (ComputeSound compute) si memo = do
  (result, ci) <- compute si memo
  case result of
    DelayedResult vector -> pure (vector, ci)
    WriteResult writeResult ->
      pure
        ( do
            marray <- MU.unsafeMallocMArray (M.Sz1 si.samples)
            writeResult marray
            array <- MU.unsafeFreeze M.Seq marray
            pure $ M.delay array,
          ci
        )
{-# INLINE asDelayedResult #-}

asWriteResult ::
  ComputeSound Pulse ->
  SamplingInfo ->
  MemoComputeSound ->
  IO (M.MVector M.RealWorld M.S Pulse -> IO (), ComputationInfo)
asWriteResult (ComputeSound compute) si memo = do
  (result, ci) <- compute si memo
  case result of
    WriteResult writeResult -> pure (writeResult, ci)
    DelayedResult vector -> do
      let memoInfo = MemoInfo si ci
      pure
        ( \dest -> do
            memoized <- lookupMemoizedComputeSound memo memoInfo

            case memoized of
              Just memoSource -> do
                copyArrayIntoMArray memoSource dest
              Nothing -> do
                vector >>= M.computeInto dest
                destArray <- MU.unsafeFreeze M.Seq dest
                memoizeComputeSound memo memoInfo destArray,
          ci
        )
{-# INLINE asWriteResult #-}

zipWithCompute :: (a -> b -> c) -> ComputeSound a -> ComputeSound b -> ComputeSound c
zipWithCompute f cs1 cs2 = ComputeSound $ \si memo -> do
  (dV1, p1) <- asDelayedResult cs1 si memo
  (dV2, p2) <- asDelayedResult cs2 si memo
  stableF <- makeSomeStableName f
  pure (DelayedResult $ M.zipWith f <$> dV1 <*> dV2, ComputationInfoZip stableF p1 p2)
{-# INLINE zipWithCompute #-}

mapSoundFromMemory :: (M.Load r M.Ix1 Pulse) => (M.Vector M.S Pulse -> M.Vector r Pulse) -> ComputeSound Pulse -> ComputeSound Pulse
mapSoundFromMemory f cs = ComputeSound $ \si memo -> do
  (writeSamples, ci) <- asWriteResult cs si memo
  stableF <- makeSomeStableName f
  pure
    ( WriteResult $ \dest -> do
        wholeSoundMArray <- MU.unsafeMallocMArray (M.Sz1 si.samples)
        writeSamples wholeSoundMArray
        wholeSoundArray <- MU.unsafeFreeze M.Seq wholeSoundMArray
        M.computeInto dest $ f wholeSoundArray,
      ComputationInfoMapMemory stableF ci
    )
{-# INLINE mapSoundFromMemory #-}

mapSoundFromMemoryIO :: (M.Vector M.S Pulse -> M.MVector M.RealWorld M.S Pulse -> IO ()) -> ComputeSound Pulse -> ComputeSound Pulse
mapSoundFromMemoryIO f cs = ComputeSound $ \si memo -> do
  (writeSamples, ci) <- asWriteResult cs si memo
  stableF <- makeSomeStableName f
  pure
    ( WriteResult $ \dest -> do
        wholeSoundMArray <- MU.unsafeMallocMArray (M.Sz1 si.samples)
        writeSamples wholeSoundMArray
        wholeSoundArray <- MU.unsafeFreeze M.Seq wholeSoundMArray
        f wholeSoundArray dest,
      ComputationInfoMapMemory stableF ci
    )
{-# INLINE mapSoundFromMemoryIO #-}

fillSoundInMemoryIO :: (SamplingInfo -> M.MVector M.RealWorld M.S Pulse -> IO ()) -> ComputeSound Pulse
fillSoundInMemoryIO f = ComputeSound $ \si _ -> do
  stableF <- makeSomeStableName f
  let f' = f si
  pure
    ( WriteResult $ \dest -> do
        f' dest,
      ComputationInfoFillMemory stableF
    )
{-# INLINE fillSoundInMemoryIO #-}

embedIOCS :: IO (ComputeSound a) -> ComputeSound a
embedIOCS makeCS = ComputeSound $ \si memo -> do
  stableIO <- makeSomeStableName makeCS
  (ComputeSound compute) <- makeCS
  (res, _) <- compute si memo
  pure (res, ComputationInfoIO stableIO)
{-# INLINE embedIOCS #-}

embedIOLazilyCS :: IO (ComputeSound a) -> ComputeSound a
embedIOLazilyCS makeCS = ComputeSound $ \si memo -> do
  stableIO <- makeSomeStableName makeCS
  pure
    ( DelayedResult $ do
        (res, _) <- join $ asDelayedResult <$> makeCS <*> pure si <*> pure memo
        res,
      ComputationInfoIO stableIO
    )
{-# INLINE embedIOLazilyCS #-}

pulseSize :: Int
pulseSize = sizeOf (undefined :: Pulse)
{-# INLINE pulseSize #-}

sampleComputeSound :: SamplingInfo -> ComputeSound Pulse -> IO (M.Vector M.S Pulse)
sampleComputeSound si cs = do
  hashTable <- H.new
  destArray <- MU.unsafeMallocMArray $ M.Sz1 si.samples
  (writeResult, _) <- asWriteResult cs si (MemoComputeSound hashTable)
  writeResult destArray
  MU.unsafeFreeze M.Seq destArray
{-# INLINE sampleComputeSound #-}

newtype MemoComputeSound = MemoComputeSound (H.BasicHashTable MemoInfo (M.Vector M.S Pulse))

data MemoInfo = MemoInfo
  { samplingInfo :: !SamplingInfo,
    computationInfo :: !ComputationInfo
  }
  deriving (Eq, Generic)

instance Hashable MemoInfo

lookupMemoizedComputeSound :: MemoComputeSound -> MemoInfo -> IO (Maybe (M.Vector M.S Pulse))
lookupMemoizedComputeSound (MemoComputeSound memoTable) memoInfo = do
  H.lookup memoTable memoInfo
{-# INLINE lookupMemoizedComputeSound #-}

memoizeComputeSound :: MemoComputeSound -> MemoInfo -> M.Vector M.S Pulse -> IO ()
memoizeComputeSound (MemoComputeSound hashTable) memoInfo vec = do
  H.insert hashTable memoInfo vec
{-# INLINE memoizeComputeSound #-}

newtype ComputeSound a = ComputeSound
  { compute ::
      SamplingInfo ->
      MemoComputeSound ->
      IO (SoundResult a, ComputationInfo)
  }

data SoundResult a where
  WriteResult :: (M.MVector M.RealWorld M.S Pulse -> IO ()) -> SoundResult Pulse
  DelayedResult :: IO (M.Vector M.D a) -> SoundResult a

data ComputationInfo
  = ComputationInfoZip SomeStableName ComputationInfo ComputationInfo
  | ComputationInfoMap SomeStableName ComputationInfo
  | ComputationInfoSequentially Percentage ComputationInfo ComputationInfo
  | ComputationInfoParallel Percentage ComputationInfo ComputationInfo
  | ComputationInfoMakeDelayedResult SomeStableName
  | ComputationInfoMapDelayedResult SomeStableName ComputationInfo
  | ComputationInfoMergeDelayedResult SomeStableName ComputationInfo ComputationInfo
  | ComputationInfoMapMemory SomeStableName ComputationInfo
  | ComputationInfoFillMemory SomeStableName
  | ComputationInfoChangeSamplingInfo SomeStableName ComputationInfo
  | ComputationInfoWithSampledSound SomeStableName ComputationInfo
  | ComputationInfoIO SomeStableName
  | ComputationInfoWithSamplingInfo SomeStableName
  deriving (Eq, Generic, Show)

instance Hashable ComputationInfo

copyArrayIntoMArray :: M.Vector M.S Pulse -> M.MVector M.RealWorld M.S Pulse -> IO ()
copyArrayIntoMArray source dest =
  let (sourceFPtr, _) = MU.unsafeArrayToForeignPtr source
      (destFPtr, _) = MU.unsafeMArrayToForeignPtr dest
   in liftIO $ withForeignPtr sourceFPtr $ \sourcePtr ->
        withForeignPtr destFPtr $ \destPtr ->
          copyBytes destPtr sourcePtr (M.unSz (M.size source) * pulseSize)
{-# INLINE copyArrayIntoMArray #-}

copyMArrayIntoMArray :: M.MVector M.RealWorld M.S Pulse -> M.MVector M.RealWorld M.S Pulse -> IO ()
copyMArrayIntoMArray source dest =
  let (sourceFPtr, _) = MU.unsafeMArrayToForeignPtr source
      (destFPtr, _) = MU.unsafeMArrayToForeignPtr dest
   in liftIO $ withForeignPtr sourceFPtr $ \sourcePtr ->
        withForeignPtr destFPtr $ \destPtr ->
          copyBytes destPtr sourcePtr (M.unSz (M.sizeOfMArray source) * pulseSize)
{-# INLINE copyMArrayIntoMArray #-}