openal-ffi (empty) → 0.0.1
raw patch · 9 files changed
+678/−0 lines, 9 filesdep +basedep +openal-ffisetup-changed
Dependencies added: base, openal-ffi
Files
- ChangeLog.md +7/−0
- LICENSE +30/−0
- README.md +16/−0
- Setup.hs +2/−0
- openal-ffi.cabal +55/−0
- src/Sound/OpenAL/FFI/AL.hs +304/−0
- src/Sound/OpenAL/FFI/ALC.hs +151/−0
- src/Sound/OpenAL/FFI/Utils.hs +20/−0
- test/Spec.hs +93/−0
+ ChangeLog.md view
@@ -0,0 +1,7 @@+# Changelog for openal-ffi++## [0.0.1]++- Initial import.++[0.0.1]: https://gitlab.com/dpwiz/openal-ffi/-/tree/v0.0.1
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author name here (c) 2021++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 Author name here 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,16 @@+# openal-ffi++> Very low-level bindings to the [OpenAL] library.++- No OpenGL dependencies.+- No point/vector/tensor/spaces dependencies.+- No state and no resource management.+- Modest DLL size.++## Examples++- [C API overview](https://ffainelli.github.io/openal-example/)+- [Buffer-playing example](https://gitlab.com/dpwiz/openal-ffi/-/blob/main/test/Spec.hs)+- [Opus-playing example](https://gitlab.com/dpwiz/opusfile/-/blob/master/examples/openal-playfile/Main.hs)++[OpenAL]: https://openal.org/
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ openal-ffi.cabal view
@@ -0,0 +1,55 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.34.4.+--+-- see: https://github.com/sol/hpack++name: openal-ffi+version: 0.0.1+synopsis: Low-level bindings to OpenAL.+category: Sound+author: IC Rainbow+maintainer: aenor.realm@gmail.com+copyright: 2021 IC Rainbow+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/openal-ffi++library+ exposed-modules:+ Sound.OpenAL.FFI.AL+ Sound.OpenAL.FFI.ALC+ Sound.OpenAL.FFI.Utils+ other-modules:+ Paths_openal_ffi+ hs-source-dirs:+ src+ ghc-options: -Wall+ extra-libraries:+ openal+ build-depends:+ base >=4.7 && <5+ if os(darwin) || os(ios)+ frameworks:+ OpenAL+ default-language: Haskell2010++test-suite openal-ffi-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Paths_openal_ffi+ hs-source-dirs:+ test+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , openal-ffi+ default-language: Haskell2010
+ src/Sound/OpenAL/FFI/AL.hs view
@@ -0,0 +1,304 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralisedNewtypeDeriving #-}++module Sound.OpenAL.FFI.AL where++import Foreign (FunPtr, Ptr, Storable)+import Foreign.C.Types+import Foreign.C.String (CString)++import Sound.OpenAL.FFI.Utils (Dynamic)++-- * Source++newtype Source = Source CUInt+ deriving stock (Eq, Ord, Show)+ deriving newtype (Storable)++foreign import ccall unsafe "alGenSources"+ alGenSources :: CInt -> Ptr Source -> IO ()++foreign import ccall unsafe "alDeleteSources"+ alDeleteSources :: CInt -> Ptr Source -> IO ()++foreign import ccall unsafe "alIsSource"+ alIsSource :: Source -> IO CChar++foreign import ccall unsafe "alGetSourcefv"+ alGetSourcefv :: Source -> CInt -> Ptr CFloat -> IO ()++foreign import ccall unsafe "alSourcefv"+ alSourcefv :: Source -> CInt -> Ptr CFloat -> IO ()++foreign import ccall unsafe "alGetSourceiv"+ alGetSourceiv :: Source -> CInt -> Ptr CInt -> IO ()++foreign import ccall unsafe "alSourcei"+ alSourcei :: Source -> CInt -> CInt -> IO ()++foreign import ccall unsafe "alSourceQueueBuffers"+ alSourceQueueBuffers :: Source -> CInt -> Ptr Buffer -> IO ()++foreign import ccall unsafe "alSourceUnqueueBuffers"+ alSourceUnqueueBuffers :: Source -> CInt -> Ptr Buffer -> IO ()++foreign import ccall unsafe "alSourcePlayv"+ alSourcePlayv :: CInt -> Ptr Source -> IO ()++foreign import ccall unsafe "alSourcePausev"+ alSourcePausev :: CInt -> Ptr Source -> IO ()++foreign import ccall unsafe "alSourceStopv"+ alSourceStopv :: CInt -> Ptr Source -> IO ()++foreign import ccall unsafe "alSourceRewindv"+ alSourceRewindv :: CInt -> Ptr Source -> IO ()++-- * Buffer++newtype Buffer = Buffer CUInt+ deriving stock (Eq, Ord, Show)+ deriving newtype (Storable)++foreign import ccall unsafe "alGenBuffers"+ alGenBuffers :: CInt -> Ptr Buffer -> IO ()++foreign import ccall unsafe "alDeleteBuffers"+ alDeleteBuffers :: CInt -> Ptr Buffer -> IO ()++foreign import ccall unsafe "alBufferData"+ alBufferData :: Buffer -> CInt -> Ptr () -> CInt -> CInt -> IO ()++foreign import ccall unsafe "alGetBufferi"+ alGetBufferi :: Buffer -> CInt -> Ptr CInt -> IO ()++foreign import ccall unsafe "alIsBuffer"+ alIsBuffer :: Buffer -> IO CChar++-- * Errors++foreign import ccall unsafe "alGetError"+ alGetError :: IO CInt++pattern INVALID_NAME :: (Eq a, Num a) => a+pattern INVALID_NAME = 0xA001++pattern INVALID_ENUM :: (Eq a, Num a) => a+pattern INVALID_ENUM = 0xA002++pattern INVALID_VALUE :: (Eq a, Num a) => a+pattern INVALID_VALUE = 0xA003++pattern INVALID_OPERATION :: (Eq a, Num a) => a+pattern INVALID_OPERATION = 0xA004++pattern OUT_OF_MEMORY :: (Eq a, Num a) => a+pattern OUT_OF_MEMORY = 0xA005++-- * Listener++foreign import ccall unsafe "alListenerf"+ alListenerf :: CInt -> CFloat -> IO ()++foreign import ccall unsafe "alListenerfv"+ alListenerfv :: CInt -> Ptr () -> IO ()++foreign import ccall unsafe "alGetListenerfv"+ alGetListenerfv :: CInt -> Ptr CFloat -> IO ()++-- * Queries++foreign import ccall unsafe "alGetEnumValue"+ alGetEnumValue :: CString -> IO CInt++foreign import ccall unsafe "alGetFloat"+ alGetFloat :: CInt -> IO CFloat++foreign import ccall unsafe "alGetIntegerv"+ alGetIntegerv :: CInt -> Ptr CInt -> IO ()++foreign import ccall unsafe "alDistanceModel"+ alDistanceModel :: CInt -> IO ()++-- ** Strings++foreign import ccall unsafe "alGetString"+ alGetString :: CInt -> IO CString++pattern VERSION :: (Eq a, Num a) => a+pattern VERSION = 0xB002++pattern RENDERER :: (Eq a, Num a) => a+pattern RENDERER = 0xB003++pattern VENDOR :: (Eq a, Num a) => a+pattern VENDOR = 0xB001++pattern EXTENSIONS :: (Eq a, Num a) => a+pattern EXTENSIONS = 0xB004++-- ** Extensions++foreign import ccall unsafe "alIsExtensionPresent"+ alIsExtensionPresent_ :: CString -> IO CBool++foreign import ccall unsafe "alGetProcAddress"+ alGetProcAddress :: CString -> IO (FunPtr a)++foreign import ccall unsafe "dynamic"+ invokeWithFloat :: Dynamic (CFloat -> IO ())++-- * Enums++pattern DISTANCE_MODEL :: (Eq a, Num a) => a+pattern DISTANCE_MODEL = 0xD000++pattern DOPPLER_FACTOR :: (Eq a, Num a) => a+pattern DOPPLER_FACTOR = 0xC000++pattern SPEED_OF_SOUND :: (Eq a, Num a) => a+pattern SPEED_OF_SOUND = 0xC003+++pattern INVERSE_DISTANCE :: (Eq a, Num a) => a+pattern INVERSE_DISTANCE = 0xD001++pattern INVERSE_DISTANCE_CLAMPED :: (Eq a, Num a) => a+pattern INVERSE_DISTANCE_CLAMPED = 0xD002++pattern LINEAR_DISTANCE :: (Eq a, Num a) => a+pattern LINEAR_DISTANCE = 0xD003++pattern LINEAR_DISTANCE_CLAMPED :: (Eq a, Num a) => a+pattern LINEAR_DISTANCE_CLAMPED = 0xD004++pattern EXPONENT_DISTANCE :: (Eq a, Num a) => a+pattern EXPONENT_DISTANCE = 0xD005++pattern EXPONENT_DISTANCE_CLAMPED :: (Eq a, Num a) => a+pattern EXPONENT_DISTANCE_CLAMPED = 0xD006+++pattern POSITION :: (Eq a, Num a) => a+pattern POSITION = 0x1004++pattern VELOCITY :: (Eq a, Num a) => a+pattern VELOCITY = 0x1006++pattern GAIN :: (Eq a, Num a) => a+pattern GAIN = 0x100A+++pattern ORIENTATION :: (Eq a, Num a) => a+pattern ORIENTATION = 0x100F+++pattern SOURCE_RELATIVE :: (Eq a, Num a) => a+pattern SOURCE_RELATIVE = 0x0202++pattern SOURCE_TYPE :: (Eq a, Num a) => a+pattern SOURCE_TYPE = 0x1027++pattern LOOPING :: (Eq a, Num a) => a+pattern LOOPING = 0x1007++pattern BUFFER :: (Eq a, Num a) => a+pattern BUFFER = 0x1009++pattern BUFFERS_QUEUED :: (Eq a, Num a) => a+pattern BUFFERS_QUEUED = 0x1015++pattern BUFFERS_PROCESSED :: (Eq a, Num a) => a+pattern BUFFERS_PROCESSED = 0x1016++pattern MIN_GAIN :: (Eq a, Num a) => a+pattern MIN_GAIN = 0x100D++pattern MAX_GAIN :: (Eq a, Num a) => a+pattern MAX_GAIN = 0x100E++pattern REFERENCE_DISTANCE :: (Eq a, Num a) => a+pattern REFERENCE_DISTANCE = 0x1020++pattern ROLLOFF_FACTOR :: (Eq a, Num a) => a+pattern ROLLOFF_FACTOR = 0x1021++pattern MAX_DISTANCE :: (Eq a, Num a) => a+pattern MAX_DISTANCE = 0x1023++pattern PITCH :: (Eq a, Num a) => a+pattern PITCH = 0x1003++pattern DIRECTION :: (Eq a, Num a) => a+pattern DIRECTION = 0x1005++pattern CONE_INNER_ANGLE :: (Eq a, Num a) => a+pattern CONE_INNER_ANGLE = 0x1001++pattern CONE_OUTER_ANGLE :: (Eq a, Num a) => a+pattern CONE_OUTER_ANGLE = 0x1002++pattern CONE_OUTER_GAIN :: (Eq a, Num a) => a+pattern CONE_OUTER_GAIN = 0x1022++pattern SEC_OFFSET :: (Eq a, Num a) => a+pattern SEC_OFFSET = 0x1024++pattern SAMPLE_OFFSET :: (Eq a, Num a) => a+pattern SAMPLE_OFFSET = 0x1025++pattern BYTE_OFFSET :: (Eq a, Num a) => a+pattern BYTE_OFFSET = 0x1026++pattern SOURCE_STATE :: (Eq a, Num a) => a+pattern SOURCE_STATE = 0x1010+++pattern UNDETERMINED :: (Eq a, Num a) => a+pattern UNDETERMINED = 0x1030++pattern STATIC :: (Eq a, Num a) => a+pattern STATIC = 0x1028++pattern STREAMING :: (Eq a, Num a) => a+pattern STREAMING = 0x1029+++pattern INITIAL :: (Eq a, Num a) => a+pattern INITIAL = 0x1011++pattern PLAYING :: (Eq a, Num a) => a+pattern PLAYING = 0x1012++pattern PAUSED :: (Eq a, Num a) => a+pattern PAUSED = 0x1013++pattern STOPPED :: (Eq a, Num a) => a+pattern STOPPED = 0x1014+++pattern FREQUENCY :: (Eq a, Num a) => a+pattern FREQUENCY = 0x2001++pattern SIZE :: (Eq a, Num a) => a+pattern SIZE = 0x2004++pattern BITS :: (Eq a, Num a) => a+pattern BITS = 0x2002++pattern CHANNELS :: (Eq a, Num a) => a+pattern CHANNELS = 0x2003+++pattern FORMAT_MONO8 :: (Eq a, Num a) => a+pattern FORMAT_MONO8 = 0x1100++pattern FORMAT_MONO16 :: (Eq a, Num a) => a+pattern FORMAT_MONO16 = 0x1101++pattern FORMAT_STEREO8 :: (Eq a, Num a) => a+pattern FORMAT_STEREO8 = 0x1102++pattern FORMAT_STEREO16 :: (Eq a, Num a) => a+pattern FORMAT_STEREO16 = 0x1103
+ src/Sound/OpenAL/FFI/ALC.hs view
@@ -0,0 +1,151 @@+{-# LANGUAGE PatternSynonyms #-}++module Sound.OpenAL.FFI.ALC where++import Foreign (FunPtr, Ptr, nullPtr)+import Foreign.C.Types (CChar(..), CInt(..), CUInt(..))+import Foreign.C.String (CString)+import Sound.OpenAL.FFI.Utils (Dynamic)++-- * Context++newtype Context = Context (Ptr Context)+ deriving (Eq, Ord, Show)++foreign import ccall unsafe "alcCreateContext"+ alcCreateContext :: Device -> Ptr CInt -> IO Context++foreign import ccall unsafe "alcDestroyContext"+ alcDestroyContext :: Context -> IO ()++foreign import ccall unsafe "alcGetCurrentContext"+ alcGetCurrentContext :: IO Context++foreign import ccall unsafe "alcMakeContextCurrent"+ alcMakeContextCurrent :: Context -> IO CChar++foreign import ccall unsafe "alcSuspendContext"+ alcSuspendContext :: Context -> IO ()++foreign import ccall unsafe "alcProcessContext"+ alcProcessContext :: Context -> IO ()++foreign import ccall unsafe "alcGetContextsDevice"+ alcGetContextsDevice :: Context -> IO Device++-- * Device++newtype Device = Device (Ptr Device)+ deriving (Eq, Ord, Show)++nullDevice :: Device+nullDevice = Device nullPtr++foreign import ccall unsafe "alcOpenDevice"+ alcOpenDevice :: CString -> IO Device++foreign import ccall unsafe "alcCloseDevice"+ alcCloseDevice :: Device -> IO CChar++-- * Errors++foreign import ccall unsafe "alcGetError"+ alcGetError :: Device -> IO CInt++pattern INVALID_DEVICE :: (Eq a, Num a) => a+pattern INVALID_DEVICE = 0xA001++pattern INVALID_CONTEXT :: (Eq a, Num a) => a+pattern INVALID_CONTEXT = 0xA002++pattern INVALID_ENUM :: (Eq a, Num a) => a+pattern INVALID_ENUM = 0xA003++pattern INVALID_VALUE :: (Eq a, Num a) => a+pattern INVALID_VALUE = 0xA004++pattern INVALID_OPERATION :: (Eq a, Num a) => a+pattern INVALID_OPERATION = 0xA006++pattern OUT_OF_MEMORY :: (Eq a, Num a) => a+pattern OUT_OF_MEMORY = 0xA005++-- * Extensions++foreign import ccall unsafe "alcGetProcAddress"+ alcGetProcAddress :: Device -> CString -> IO (FunPtr a)++foreign import ccall unsafe "alcGetEnumValue"+ alcGetEnumValue :: Device -> CString -> IO CInt++-- * Queries++foreign import ccall unsafe "alcGetString"+ alcGetString :: Device -> CInt -> IO CString++foreign import ccall unsafe "alcGetIntegerv"+ alcGetIntegerv :: Device -> CInt -> CInt -> Ptr CInt -> IO ()++foreign import ccall unsafe "alcIsExtensionPresent"+ alcIsExtensionPresent_ :: Device -> CString -> IO CChar++-- * Capture++foreign import ccall unsafe "dynamic"+ invokeCaptureOpenDevice :: Dynamic (CString -> CUInt -> CInt -> CInt -> IO Device)++foreign import ccall unsafe "dynamic"+ invokeCaptureStartStop :: Dynamic (Device -> IO ())++foreign import ccall unsafe "dynamic"+ invokeCaptureSamples :: Dynamic (Device -> Ptr a -> CInt -> IO ())++foreign import ccall unsafe "dynamic"+ invokeCaptureCloseDevice :: Dynamic (Device -> IO CChar)++-- * Enums++pattern FREQUENCY :: (Eq a, Num a) => a+pattern FREQUENCY = 0x1007++pattern REFRESH :: (Eq a, Num a) => a+pattern REFRESH = 0x1008++pattern SYNC :: (Eq a, Num a) => a+pattern SYNC = 0x1009++pattern MONO_SOURCES :: (Eq a, Num a) => a+pattern MONO_SOURCES = 0x1010++pattern STEREO_SOURCES :: (Eq a, Num a) => a+pattern STEREO_SOURCES = 0x1011++pattern DEFAULT_DEVICE_SPECIFIER :: (Eq a, Num a) => a+pattern DEFAULT_DEVICE_SPECIFIER = 0x1004++pattern DEVICE_SPECIFIER :: (Eq a, Num a) => a+pattern DEVICE_SPECIFIER = 0x1005++pattern EXTENSIONS :: (Eq a, Num a) => a+pattern EXTENSIONS = 0x1006++pattern CAPTURE_DEFAULT_DEVICE_SPECIFIER :: (Eq a, Num a) => a+pattern CAPTURE_DEFAULT_DEVICE_SPECIFIER = 0x0311++pattern CAPTURE_DEVICE_SPECIFIER :: (Eq a, Num a) => a+pattern CAPTURE_DEVICE_SPECIFIER = 0x0310++pattern ATTRIBUTES_SIZE :: (Eq a, Num a) => a+pattern ATTRIBUTES_SIZE = 0x1002++pattern ALL_ATTRIBUTES :: (Eq a, Num a) => a+pattern ALL_ATTRIBUTES = 0x1003++pattern MAJOR_VERSION :: (Eq a, Num a) => a+pattern MAJOR_VERSION = 0x1000++pattern MINOR_VERSION :: (Eq a, Num a) => a+pattern MINOR_VERSION = 0x1001++pattern CAPTURE_SAMPLES :: (Eq a, Num a) => a+pattern CAPTURE_SAMPLES = 0x0312
+ src/Sound/OpenAL/FFI/Utils.hs view
@@ -0,0 +1,20 @@+module Sound.OpenAL.FFI.Utils+ ( Dynamic+ , peekCStrings+ ) where++import Foreign (FunPtr, lengthArray0, plusPtr)+import Foreign.C.String (CString, peekCAString)++type Dynamic a = FunPtr a -> a++peekCStrings :: CString -> IO [String]+peekCStrings = go []+ where+ go acc ptr = do+ str <- peekCAString ptr+ if null str then+ pure $ reverse acc+ else do+ len <- lengthArray0 0 ptr+ go (str : acc) $ plusPtr ptr (len + 1)
+ test/Spec.hs view
@@ -0,0 +1,93 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE TupleSections #-}++import Control.Concurrent (threadDelay)+import Control.Monad (when)+import Data.Int (Int16)+import Foreign.C.String (peekCString, withCString)+import qualified Foreign++import qualified Sound.OpenAL.FFI.AL as AL+import qualified Sound.OpenAL.FFI.ALC as ALC+import Sound.OpenAL.FFI.Utils (peekCStrings)++main :: IO ()+main = do+ extensions <- fmap words $+ ALC.alcGetString ALC.nullDevice ALC.EXTENSIONS >>= peekCString+ putStrLn $ "ALC: " <> show extensions++ enumerationExt <- fmap (== 1) . withCString "ALC_ENUMERATION_EXT" $+ ALC.alcIsExtensionPresent_ ALC.nullDevice+ putStrLn $ "Enumeration available: " <> show enumerationExt++ when enumerationExt do+ allDevices <- ALC.alcGetString ALC.nullDevice ALC.DEVICE_SPECIFIER >>= peekCStrings+ putStrLn $ "All devices: " <> show allDevices++ defaultDevice <- ALC.alcGetString ALC.nullDevice ALC.DEFAULT_DEVICE_SPECIFIER >>= peekCString+ putStrLn $ "Default device: " <> show defaultDevice++ device <- ALC.alcOpenDevice Foreign.nullPtr+ context <- ALC.alcCreateContext device Foreign.nullPtr++ ok <- fmap (== 1) $ ALC.alcMakeContextCurrent context+ putStrLn $ "Context OK: " <> show ok+ when ok do+ buf@(AL.Buffer bufId) <- Foreign.alloca \bufPtr -> do+ AL.alGenBuffers 1 bufPtr+ Foreign.peek bufPtr+ AL.alGetError >>= print . (buf,)++ Foreign.withArrayLen soundData \bufSize bufData ->+ AL.alBufferData+ buf+ AL.FORMAT_MONO16+ (Foreign.castPtr bufData)+ (fromIntegral bufSize)+ 48000+ AL.alGetError >>= print . (buf,)++ source <- Foreign.alloca \sourcePtr -> do+ AL.alGenSources 1 sourcePtr+ Foreign.peek sourcePtr+ AL.alGetError >>= print . (source,)++ AL.alSourcei source AL.BUFFER (fromIntegral bufId) -- XXX: unsigned to signed conversion+ AL.alGetError >>= print . (buf,)++ Foreign.with source $+ AL.alSourcePlayv 1++ threadDelay 1000000++ Foreign.with source $ AL.alDeleteSources 1+ Foreign.with buf $ AL.alDeleteBuffers 1++ ALC.alcDestroyContext context+ ALC.alcCloseDevice device >>= print++-- | Some funky sample+soundData :: [Int16]+soundData =+ map convert $+ zipWith+ (+)+ (sine 220) $+ zipWith+ (*)+ (sine 440)+ (sine 3)+ where+ convert d = round $ range * 0.5 * d+ range = fromIntegral (maxBound :: Int16)++ duration = 10.0+ rate = 48000++ sine freq =+ take (round $ duration * rate) $+ map sin [0.0 :: Double, step ..]+ where+ step =+ freq * 2 * pi / rate