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 +5/−0
- README.md +14/−4
- bench/Main.hs +22/−9
- example/Example1.hs +63/−48
- lambdasound.cabal +2/−2
- src/LambdaSound.hs +4/−2
- src/LambdaSound/Cache.hs +3/−3
- src/LambdaSound/Create.hs +3/−3
- src/LambdaSound/Note.hs +11/−6
- src/LambdaSound/Sound.hs +29/−14
- src/LambdaSound/Sound/ComputeSound.hs +29/−5
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