packages feed

lambdasound 1.0.0 → 1.0.1

raw patch · 11 files changed

+185/−96 lines, 11 filesdep ~massivPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: massiv

API changes (from Hackage documentation)

+ LambdaSound.Sound: withSampledSound :: Sound T a -> (Vector D a -> Sound I b) -> Sound I b
+ LambdaSound.Sound: withSampledSoundPulse :: Sound T Pulse -> (Vector S Pulse -> Sound I a) -> Sound I a

Files

CHANGELOG.md view
@@ -1,5 +1,10 @@ # Revision history for lambdasound +## 1.0.1 -- 2023-10-13++* Add `withSampledSound` and `withSampledSoundPulse`+* Fix incorrect memoization for sequential and parallel sounds+ ## 1.0.0 -- 2023-10-12  * First version. Released on an unsuspecting world.
README.md view
@@ -52,8 +52,13 @@   play sampleRate volume ascending ``` -You can also take a look at `example/Example1.hs` and `example/Example2.hs` for bigger examples and play them with `cabal run example1` and `cabal run example2`.+You can also take a look at `example/Example1.hs` and `example/Example2.hs` for bigger examples and play them with: +```haskell+cabal run example1+cabal run example2+```+ ## Feature Overview  - Play sounds with SDL2@@ -72,7 +77,7 @@  ## Building -`lambdasound` can be built as usual with the `cabal` package manager. For playing sounds, you will need to have **SDL2** installed.+`lambdasound` can be built as usual with the `cabal` package manager.   ``` git clone https://github.com/Simre1/lambdasound@@ -80,9 +85,14 @@ ```  You can run the example with:-```-cabal run example++```haskell+cabal run example1+cabal run example2 ```++For this library, you will need to have **SDL2** installed. Take a look at [proteeaudio-sdl](https://hackage.haskell.org/package/proteaaudio-sdl) for installation instructions.+  ## Contributing 
bench/Main.hs view
@@ -3,6 +3,7 @@ import Data.Coerce import LambdaSound import Test.Tasty.Bench+import qualified Data.Massiv.Array as M  main :: IO () main =@@ -21,7 +22,9 @@           bench "Cached sound" $ nfSound cachedSound,           bench "Timed parallel sound" $ nfSound timedParallelSound,           bench "Unfold pulse" $ nfSound unfoldPulse,-          bench "Unfold normally" $ nfSound unfoldNormally+          bench "Unfold normally" $ nfSound unfoldNormally,+          bench "With sampled sound" $ nfSound useSampledSound,+          bench "Repeated with sampled sound" $ nfSound repeatedSampledSound         ]     ] @@ -73,17 +76,27 @@  timedParallelSound :: Sound T Pulse timedParallelSound =-  parallel $ mconcat $-    replicate 5 [ 0.5 |-> simplePulse,-      1 |-> simplePulse,-      1.5 |-> simplePulse,-      0.7 |-> simplePulse,-      2 |-> simplePulse,-      1.5 |-> simplePulse-    ]+  parallel $+    mconcat $+      replicate+        5+        [ 0.5 |-> simplePulse,+          1 |-> simplePulse,+          1.5 |-> simplePulse,+          0.7 |-> simplePulse,+          2 |-> simplePulse,+          1.5 |-> simplePulse+        ]  unfoldPulse :: Sound T Pulse unfoldPulse = 5 |-> unfoldlSoundPulse (\s -> (s, succ s)) 0  unfoldNormally :: Sound T Pulse unfoldNormally = 5 |-> unfoldlSound (\s -> (s, succ s)) 0++useSampledSound :: Sound T Pulse+useSampledSound = setDuration 5 $ withSampledSoundPulse simplePulse $ \samples ->+  makeSound $ \_ index -> samples M.! (index `mod` M.unSz (M.size samples))++repeatedSampledSound :: Sound T Pulse+repeatedSampledSound = repeatSound 20 useSampledSound
example/Example1.hs view
@@ -2,60 +2,75 @@ import LambdaSound  main :: IO ()-main = play 44100 0.4 $ simpleReverb 0.1 $ applyIIRFilter (highPassFilter 600 1) sound+main = play 44100 0.4 $+    let downwards = sequentially $ setDuration 0.25 . note+          <$> [g4, f4, e4, d4, f4, e4, d4, c4, e4, d4, c4, b3]+     in sequentially+          [ downwards,+            dropSound 0.25 (reverseSound $ dropSound 0.5 downwards),+            setDuration 0.25 (parallel $ note <$> [c4, e4]),+            setDuration 0.25 (parallel $ note <$> [c4, e4, g4]),+            setDuration 0.5 (parallel $ note <$> [c4, g4, c5])+          ] -sound :: Sound T Pulse-sound = melody <> background+-- Design a note sample+note :: Semitone -> Sound I Pulse+note st = easeInOut 2 $ asNote sawWave st + asNote (harmonic triangleWave) st -background :: Sound T Pulse-background =-  repeatSound 3 $-    sequentially $-      fmap-        (setDuration 0.5)-        [ note c3,-          parallel $ note <$> [e3, g3],-          parallel $ note <$> [e3, g3],-          parallel $ note <$> [e3, g3]-        ]+-- -- simpleReverb 0.1 $ applyIIRFilter (highPassFilter 600 1) sound -melody :: Sound T Pulse-melody =-  let mel =-        repeatSound-          3-          ( sequentially $-              fmap-                (setDuration 0.5)-                [ note c4,-                  note e4,-                  note g4,-                  note e4-                ]-          )-          >>> end-      end = setDuration 2 $ parallel [note c4, note c3, note g3]-   in mel+-- sound :: Sound T Pulse+-- sound = melody <> background -note :: Semitone -> Sound T Pulse-note st =-  applyEnvelope (Envelope 0.2 0.1 0.2 0.8) $-    setDuration 1 $-      asNote (harmonic sineWave) st+-- background :: Sound T Pulse+-- background =+--   repeatSound 3 $+--     sequentially $+--       fmap+--         (setDuration 0.5)+--         [ note c3,+--           parallel $ note <$> [e3, g3],+--           parallel $ note <$> [e3, g3],+--           parallel $ note <$> [e3, g3]+--         ] --- Further examples+-- melody :: Sound T Pulse+-- melody =+--   let mel =+--         repeatSound+--           3+--           ( sequentially $+--               fmap+--                 (setDuration 0.5)+--                 [ note c4,+--                   note e4,+--                   note g4,+--                   note e4+--                 ]+--           )+--           >>> end+--       end = setDuration 2 $ parallel [note c4, note c3, note g3]+--    in mel -metronome :: Sound T Pulse-metronome = repeatSound 10 $ setDuration 1 $ note c4 >>> setDuration 2 silence+-- note :: Semitone -> Sound T Pulse+-- note st =+--   applyEnvelope (Envelope 0.2 0.1 0.2 0.8) $+--     setDuration 1 $+--       asNote (harmonic sineWave) st -upSound :: Sound T Pulse-upSound =-  zipSoundWith (*) ((\p -> 1 - coerce p) <$> progress) $-    speedUp $-      upwards >>> takeSound 2 (raiseSemitones 12 upwards) >>> setDuration 1 (note g5)+-- -- Further examples -upwards :: Sound T Pulse-upwards = setDuration 3.5 $ sequentially $ note <$> [c4, d4, e4, f4, g4, a4, b4]+-- metronome :: Sound T Pulse+-- metronome = repeatSound 10 $ setDuration 1 $ note c4 >>> setDuration 2 silence -speedUp :: Sound T Pulse -> Sound T Pulse-speedUp = changeTempo $ \p -> p ** 2+-- upSound :: Sound T Pulse+-- upSound =+--   zipSoundWith (*) ((\p -> 1 - coerce p) <$> progress) $+--     speedUp $+--       upwards >>> takeSound 2 (raiseSemitones 12 upwards) >>> setDuration 1 (note g5)++-- upwards :: Sound T Pulse+-- upwards = setDuration 3.5 $ sequentially $ note <$> [c4, d4, e4, f4, g4, a4, b4]++-- speedUp :: Sound T Pulse -> Sound T Pulse+-- speedUp = changeTempo $ \p -> p ** 2
lambdasound.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               lambdasound-version:            1.0.0+version:            1.0.1 synopsis:           A libary for generating low-level sounds with high-level combinators description:        'lambdasound' can generate all kinds of sounds, play them and save them as wav or pcm data.                     Sound can be manipulated in both a low and high-level way. It is possible to @@ -135,7 +135,7 @@         lambdasound,         tasty >= 1.4 && < 1.5,         tasty-bench >= 0.3.5 && < 0.4,-        vector >= 0.13.0 && < 0.14+        massiv     ghc-options: -O2  source-repository head
src/LambdaSound.hs view
@@ -1,9 +1,11 @@ -- |--- Library users should implement this module (@import LambdaSound@).+-- Library users should implement this module ( @import LambdaSound@ ). -- -- This module packages all the functions from the other modules and reexports them.--- A good starting place to explore the documentation is the *LambdaSound.Sound* module which+-- A good starting point to explore the documentation is the @LambdaSound.Sound@ module which -- exports all the datatypes and many of the useful combinators you will use.+-- +-- The @LambdaSound.Sample@ module contains some simple sound samples which you can play with the @LambdaSound.Play@ module.  module LambdaSound   ( -- * Sounds     module Sound,
src/LambdaSound/Cache.hs view
@@ -17,10 +17,10 @@ import System.FilePath (joinPath)  -- | Caches a sound. If the sound is cached, then--- the sound gets read from the XDG data directory and does not have to+-- the sound gets read from the XDG cache directory and does not have to -- be computed again.--- It might load a cached sound which is not the same--- as the computed one, but this should be very unlikely+-- +-- It might load a cached sound which which is incorrect, but this should be very unlikely cache :: Sound d Pulse -> Sound d Pulse cache (TimedSound d msc) = TimedSound d $ cacheComputation msc cache (InfiniteSound msc) = InfiniteSound $ cacheComputation msc
src/LambdaSound/Create.hs view
@@ -1,16 +1,16 @@ module LambdaSound.Create-  ( -- *** Basic sounds+  ( -- * Basic sounds     time,     progress,     sampleIndex,     constant,     silence, -    -- *** Iterating+    -- * Iterating     iterateSound,     iterateSoundPulse, -    -- *** Unfolding+    -- * Unfolding     unfoldlSound,     unfoldlSoundPulse,     unfoldrSound,
src/LambdaSound/Note.hs view
@@ -4,6 +4,8 @@  import LambdaSound.Sound +-- * Semitones+ -- | Semitones are tones like 'c4', 'd4' or 'c5'. -- The semitone is used to determine the hz of the tone based on 'pitchStandard' newtype Semitone = Semitone Int deriving (Show, Eq, Num, Ord, Enum)@@ -18,8 +20,10 @@  -- | Raise a sound by the given amount of semitones. -- This only works for sounds which use the period length given--- in the compute step of the sound. 'pulse' works but 'noise' does not.+-- in the compute step of the sound. 'sineWave' works but 'noise' does not.+-- -- For example:+-- -- > raiseSemitones 2 (asNote pulse c3) = asNote pulse d3 raiseSemitones :: Int -> Sound d Pulse -> Sound d Pulse raiseSemitones x = raise (2 ** (fromIntegral x / 12))@@ -29,7 +33,9 @@ diminishSemitones x = raiseSemitones (-x)  -- | Transforms a function taking a 'Hz' to one taking a 'Semitone'.--- Should be used with 'pulse' or 'harmonic'+-- Should be used like the following:+--+-- > asNote sineWave c4 asNote :: (Hz -> a) -> Semitone -> a asNote f s = f (semitoneToHz s) @@ -96,16 +102,15 @@ a7 = 36 b7 = 38 --- ** Notes+-- * Notes  -- | These are durations for the corresponding note lenghts -- assuming 60 bpm. -- -- If you know that a sound has 60 bpm, you can easily scale to -- different bpm with 'scaleDuration':--- @--- scaleDuration (wantedBPM / 60) soundWith60BPM--- @+--+-- > scaleDuration (wantedBPM / 60) soundWith60BPM wholeNote, halfNote, quarterNote, eightNote :: Duration wholeNote = 1 halfNote = 1 / 2
src/LambdaSound/Sound.hs view
@@ -1,7 +1,7 @@ -- | -- This module exports all needed datatypes and all the combinators needed to manipulate them. module LambdaSound.Sound-  ( -- ** Sound types+  ( -- * Sound types     Sound (..),     SoundDuration (..),     Pulse (..),@@ -13,7 +13,8 @@     Time (..),     DetermineDuration, -    -- ** Make new sounds+    -- * Make new sounds+     -- Also take a look at @LambdaSound.Create@!     makeSound,     makeSoundVector,@@ -21,25 +22,25 @@     fillWholeSoundST,     computeOnce, -    -- ** Sounds in sequence+    -- * Sounds in sequence     timedSequentially,     (>>>),     sequentially,     infiniteSequentially, -    -- ** Sounds in parallel+    -- * Sounds in parallel     parallel2,     parallel, -    -- ** Volume+    -- * Volume     amplify,     reduce, -    -- ** Pitch+    -- * Pitch     raise,     diminish, -    -- ** Duration+    -- * Duration     setDuration,     (|->),     getDuration,@@ -47,23 +48,27 @@     dropDuration,     adoptDuration, -    -- ** Sample order+    -- * Sample order     reverseSound,     dropSound,     takeSound, -    -- ** Zipping+    -- * Zipping     zipSoundWith,     zipSound, -    -- ** Change play behavior of a sound+    -- * Change play behavior of a sound     changeTempo,     changeTempoM, -    -- ** Modify the samples of a sound+    -- * Modify the samples of a sound     modifyWholeSound,     modifyWholeSoundST,++    -- * Access the samples of a sound     withSamplingInfo,+    withSampledSound,+    withSampledSoundPulse,   ) where @@ -76,9 +81,9 @@ import LambdaSound.Sound.Types import System.IO.Unsafe (unsafePerformIO) --- | Some 'Sound's have a different while others do not.--- 'I'nfinite 'Sound's have no duration.+-- | 'Sound's may have a duration attached to them. -- 'T'imed 'Sound's have a duration.+-- 'I'nfinite 'Sound's have no duration. data SoundDuration = I | T  -- | Determines the duration of two sounds when they are combined@@ -161,7 +166,7 @@     computeSequentially (coerce $ d1 / (d1 + d2)) c1 c2  -- | Append two infinite sounds where the 'Percentage' in the range @[0,1]@--- specified when the first sound ends and the next begins.+-- specifies when the first sound ends and the next begins. infiniteSequentially :: Percentage -> Sound I Pulse -> Sound I Pulse -> Sound I Pulse infiniteSequentially factor' (InfiniteSound c1) (InfiniteSound c2) =   InfiniteSound $@@ -374,6 +379,16 @@ -- | Access the sample rate of an infinite sound withSamplingInfo :: (SamplingInfo -> Sound d a) -> Sound I a withSamplingInfo f = InfiniteSound $ withSamplingInfoCS (getCS . f)++-- | Access the samples of a sound.+--+-- The pulse version is slightly faster since you get a storable vector+withSampledSoundPulse :: Sound T Pulse -> (M.Vector M.S Pulse -> Sound I a) -> Sound I a+withSampledSoundPulse (TimedSound duration cs) = InfiniteSound . withSampledSoundPulseCS duration cs . fmap getCS++-- | Access the samples of a sound.+withSampledSound :: Sound T a -> (M.Vector M.D a -> Sound I b) -> Sound I b+withSampledSound (TimedSound duration cs) = InfiniteSound . withSampledSoundCS duration cs . fmap getCS  -- | Calculate sound samples based on their index. -- Take a look at @LambdaSound.Create@ for other creation functions.
src/LambdaSound/Sound/ComputeSound.hs view
@@ -45,6 +45,29 @@    in compute si memo {-# 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 (ComputeSound compute) = f delayedVector+  (res, _) <- compute si memo+  stableF <- makeSomeStableName f+  pure (res, 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@@ -65,11 +88,11 @@     ( WriteResult $ \dest -> do         writeResult1 $ MU.unsafeLinearSliceMArray 0 (M.Sz1 splitIndex) dest         writeResult2 $ MU.unsafeLinearSliceMArray splitIndex (M.Sz1 $ si.samples - splitIndex) dest,-      ComputationInfoSequentially ci1 ci2+      ComputationInfoSequentially factor ci1 ci2     ) {-# INLINE computeSequentially #-} -computeParallel :: ComputeSound Pulse -> Float -> ComputeSound Pulse -> ComputeSound Pulse+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@@ -78,7 +101,7 @@     ( if si.samples == c2N         then DelayedResult $ M.zipWith (+) delayedResult1 delayedResult2         else DelayedResult $ M.imap (\index -> (+) $ if index < c2N then MU.unsafeIndex delayedResult2 index else 0) delayedResult1,-      ComputationInfoParallel p1 p2+      ComputationInfoParallel factor p1 p2     ) {-# INLINE computeParallel #-} @@ -225,14 +248,15 @@ data ComputationInfo   = ComputationInfoZip SomeStableName ComputationInfo ComputationInfo   | ComputationInfoMap SomeStableName ComputationInfo-  | ComputationInfoSequentially ComputationInfo ComputationInfo-  | ComputationInfoParallel ComputationInfo 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   deriving (Eq, Generic, Show)  instance Hashable ComputationInfo