lambdasound-1.0.0: src/LambdaSound/Effect.hs
module LambdaSound.Effect where
import Data.Coerce
import LambdaSound.Create
import LambdaSound.Sound
-- | Eases the volume of the sound. The given 'Int' controls the strength of the easing.
easeInOut :: Int -> Sound d Pulse -> Sound d Pulse
easeInOut strength = zipSoundWith (\p -> (f p *)) progress
where
f p = coerce $ -(2 * p - 1) ** (abs (fromIntegral strength) * 2) + 1
-- | Repeats a sound such that:
--
-- > repeatSound 3 sound = sound >>> sound >>> sound
repeatSound :: Int -> Sound T Pulse -> Sound T Pulse
repeatSound n s
| n <= 0 = mempty
| n == 1 = s
| even n = s' >>> s'
| otherwise = s' >>> s' >>> s
where
s' = repeatSound (n `quot` 2) s
-- | Plays the sound multiple times to get a simple reverb effect. The duration specifies the length of the reverb.
simpleReverb :: Duration -> Sound T Pulse -> Sound T Pulse
simpleReverb duration sound = flip foldMap (zip [1 ..] [0, (duration / 4) .. duration]) $ \(v, d) ->
reduce v (setDuration d silence >>> sound)
-- | ADSR envelope which specifies how the volume of a sound should behave over time
data Envelope = Envelope
{ attack :: !Duration,
decay :: !Duration,
release :: !Duration,
sustain :: !Float
}
deriving (Eq, Show)
-- | Apply an ADSR envelope to a sound
applyEnvelope :: Envelope -> Sound T Pulse -> Sound T Pulse
applyEnvelope envelope sound =
let attack = coerce <$> progress
decay = fmap (\p -> coerce envelope.sustain + (1 - coerce p) ** 3 * (1 - coerce envelope.sustain)) progress
sustain = constant (coerce envelope.sustain)
release = fmap (\p -> coerce envelope.sustain * (1 - coerce p) ** 3) progress
adsrCurve =
sequentially
[ envelope.attack |-> attack,
envelope.decay |-> decay,
(getDuration sound - envelope.release - envelope.decay - envelope.attack) |-> sustain,
envelope.release |-> release
]
in zipSoundWith (*) adsrCurve sound
-- | Add some harmonic frequencies
harmonic :: (Hz -> Sound I Pulse) -> Hz -> Sound I Pulse
harmonic f hz = parallel $ (\x -> reduce x $ f (coerce x * hz)) <$> take 6 [1 ..]