packages feed

opusfile (empty) → 0.1.0.0

raw patch · 7 files changed

+339/−0 lines, 7 filesdep +OpenALdep +StateVardep +basesetup-changed

Dependencies added: OpenAL, StateVar, base, bytestring, opusfile

Files

+ ChangeLog.md view
@@ -0,0 +1,7 @@+# Changelog for opusfile++## Unreleased changes++## [0.1.0.0] Initial import++[0.1.0.0]: https://gitlab.com/dpwiz/opusfile/-/tree/v0.1.0.0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Alexander Bondarenko (c) 2020++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Alexander Bondarenko nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,14 @@+# opusfile++https://opus-codec.org/++> Opus is a totally open, royalty-free, highly versatile audio codec.+> Opus is unmatched for interactive speech and music transmission over the Internet,+> but is also intended for storage and streaming applications.++My intention were to use the Opus codec for game assets with OpenAL.+There's `examples/openal-playfile` exactly for this.++You'll need `libopusfile-dev` or something like that for your distro.++Add to package dependencies and import `Sound.OpusFile` qualified.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ examples/openal-playfile/Main.hs view
@@ -0,0 +1,67 @@+module Main where++import Control.Concurrent (threadDelay)+import Data.StateVar (($=))+import System.Environment (getArgs)+import Text.Printf (printf)++import qualified Data.ByteString as ByteString+import qualified Foreign+import qualified Sound.OpenAL as OpenAL+import qualified Sound.OpusFile as OpusFile++main :: IO ()+main = getArgs >>= \case+  [] ->+    putStrLn "Usage: openal-playfile <filename.opus>"+  files ->+    withAL $+      mapM_ playFile files++playFile :: FilePath -> IO ()+playFile filename = do+  (time, source) <- sourceOpusFile filename+  OpenAL.play [source]+  putStrLn $ printf "Playing %s (%.1f s)" (show filename) time+  threadDelay $ truncate (time * 1000000)++sourceOpusFile :: FilePath -> IO (Double, OpenAL.Source)+sourceOpusFile filePath = do+  Right file <- ByteString.readFile filePath >>= OpusFile.openMemoryBS+  pcm <- OpusFile.decodeInt16 file+  OpusFile.free file++  buffer <- OpenAL.genObjectName+  Foreign.withForeignPtr (OpusFile.pcmData pcm) \ptr -> do+    let+      mreg =+        OpenAL.MemoryRegion ptr (fromIntegral $ OpusFile.pcmSize pcm)++      alFormat =+        case OpusFile.pcmChannels pcm of+          Right OpusFile.Stereo ->+            OpenAL.Stereo16+          Right OpusFile.Mono ->+            OpenAL.Mono16+          Left n ->+            error $ "unexpected channels: " <> show n++    OpenAL.bufferData buffer $= OpenAL.BufferData mreg alFormat 48000++  source <- OpenAL.genObjectName+  OpenAL.buffer source $= Just buffer+  pure (OpusFile.pcmTime pcm, source)++withAL :: IO () -> IO ()+withAL action = do+  OpenAL.openDevice Nothing >>= \case+    Nothing ->+      error  "boo. no device"+    Just device -> do+      OpenAL.createContext device [] >>= \case+        Nothing ->+          error "OpenAL.createContext failed"+        Just ctx ->+          OpenAL.currentContext $= Just ctx+      () <- action+      OpenAL.closeDevice device >>= print
+ opusfile.cabal view
@@ -0,0 +1,69 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.34.4.+--+-- see: https://github.com/sol/hpack+--+-- hash: c7063700f8e088ab3fbf8e386eba0e501f42d8f6332ae31aaa74b2084bea9b1f++name:           opusfile+version:        0.1.0.0+synopsis:       FFI bindings for libopusfile+category:       Sound+author:         Alexander Bondarenko+maintainer:     aenor.realm@gmail.com+copyright:      Alexander Bondarenko+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+  type: git+  location: https://gitlab.com/dpwiz/opusfile++flag executables+  manual: True+  default: False++library+  exposed-modules:+      Sound.OpusFile+  other-modules:+      Paths_opusfile+  hs-source-dirs:+      src+  default-extensions:+      BlockArguments+      LambdaCase+      TypeApplications+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints+  extra-libraries:+      opusfile+  build-depends:+      base >=4.7 && <5+    , bytestring+  default-language: Haskell2010++executable example-openal+  main-is: Main.hs+  other-modules:+      Paths_opusfile+  hs-source-dirs:+      examples/openal-playfile+  default-extensions:+      BlockArguments+      LambdaCase+      TypeApplications+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-maxN4+  build-depends:+      OpenAL+    , StateVar+    , base >=4.7 && <5+    , bytestring+    , opusfile+  if !flag(executables)+    buildable: False+  default-language: Haskell2010
+ src/Sound/OpusFile.hs view
@@ -0,0 +1,150 @@+{-# LANGUAGE ForeignFunctionInterface #-}++module Sound.OpusFile+  ( Handle(..)+  , OggOpusFile+  , openMemoryBS+  , openMemory+  , free++  , Channels(..)+  , channelCount+  , pcmTotal++  , Pcm(..)+  , decodeInt16+  ) where++import Data.ByteString (ByteString)+import Data.ByteString.Unsafe (unsafeUseAsCStringLen)+import Data.Int (Int16, Int64)+import Foreign (ForeignPtr, Ptr)+import Foreign.C.Types (CChar, CInt(..))+import System.IO.Unsafe (unsafePerformIO)++import qualified Foreign++-- * Loading++newtype Handle = Handle (Ptr OggOpusFile)+  deriving (Eq, Show)++data OggOpusFile++openMemoryBS :: ByteString -> IO (Either Int Handle)+openMemoryBS bs = unsafeUseAsCStringLen bs \(dataPtr, dataLen) ->+  openMemory dataPtr (fromIntegral dataLen)++openMemory :: Ptr CChar -> CInt -> IO (Either Int Handle)+openMemory dataPtr dataLen =+  Foreign.alloca \errorPtr -> do+    oggOpusFile <- op_test_memory dataPtr dataLen errorPtr+    Foreign.peek errorPtr >>= \case+      0 -> do+        op_test_open oggOpusFile >>= \case+          0 ->+            pure $ Right (Handle oggOpusFile)+          err ->+            pure $ Left (fromIntegral err)+      err ->+        pure $ Left (fromIntegral err)++free :: Handle -> IO ()+free (Handle handle) = op_free handle++-- * Information++data Channels+  = Mono+  | Stereo+  deriving (Eq, Ord, Show, Enum, Bounded)++channelCount :: Handle -> Either Int Channels+channelCount (Handle handle) =+  case unsafePerformIO (op_channel_count handle) of+    1 -> Right Mono+    2 -> Right Stereo+    n -> Left (fromIntegral n)++pcmTotal :: Handle -> Int+pcmTotal (Handle handle) =+  fromIntegral $ unsafePerformIO (op_pcm_total handle)++-- * Decoding++data Pcm a = Pcm+  { pcmData     :: ForeignPtr a+  , pcmSize     :: Int+  , pcmTime     :: Double+  , pcmChannels :: Either Int Channels+  }+  deriving (Eq, Show)++decodeInt16 :: Handle -> IO (Pcm Int16)+decodeInt16 h@(Handle handle) = do+  fptr <- Foreign.mallocForeignPtrBytes byteSize+  Foreign.withForeignPtr fptr (go 0) >>= \case+    Left ret ->+      error $ show ret+    Right _samplesDone ->+      pure Pcm+        { pcmData     = fptr+        , pcmSize     = byteSize+        , pcmTime     = fromIntegral hPcmSize / 48000+        , pcmChannels = hPcmChannels+        }++  where+    hPcmChannels = channelCount h+    hPcmSize = pcmTotal h++    byteSize = hPcmSize * numChannels * sampleBytes+    samples = hPcmSize * numChannels++    numChannels = case hPcmChannels of+      Right Mono   -> 1+      Right Stereo -> 2+      Left n       -> n++    sampleBytes = Foreign.sizeOf (undefined :: Int16)++    go samplesRead buf = do+      ret <- op_read+        handle+        buf+        (fromIntegral samples)+        Foreign.nullPtr+      if ret < 0 then+        pure $ Left ret+      else do+        let samplesDone = samplesRead + fromIntegral ret+        if samplesDone < hPcmSize then+          go samplesDone $+            Foreign.plusPtr buf (fromIntegral ret * numChannels * sampleBytes)+        else+          pure $ Right samplesDone++-- * FFI++foreign import ccall "op_free"+  op_free :: Ptr OggOpusFile -> IO ()++foreign import ccall "op_test_memory"+  op_test_memory :: Ptr CChar -> CInt -> Ptr CInt -> IO (Ptr OggOpusFile)++foreign import ccall "op_test_open"+  op_test_open :: Ptr OggOpusFile -> IO CInt++foreign import ccall "op_channel_count"+  op_channel_count :: Ptr OggOpusFile -> IO CInt++foreign import ccall "op_pcm_total"+  op_pcm_total :: Ptr OggOpusFile -> IO Int64++foreign import ccall "op_read"+  op_read+    :: Ptr OggOpusFile -- _of+    -> Ptr Int16       -- _pcm+    -> CInt            -- _buf_size+    -> Ptr CInt        -- _li+    -> IO CInt