{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE BangPatterns #-}
module Boopadoop
(module Boopadoop
,module Boopadoop.Diagram
,module Boopadoop.Rhythm
,module Boopadoop.Interval
,module Boopadoop.Discrete
) where
import Data.WAVE as WAVE
import Control.Applicative
import Boopadoop.Diagram
import Boopadoop.Rhythm
import Boopadoop.Interval
import Boopadoop.Discrete
import Data.List
import Data.Bits
import Data.Int
import Data.Complex
import Data.Foldable
import qualified Data.IntMap.Lazy as IntMap
import qualified Data.Vector.Unboxed as Vector
import qualified Data.ByteString.Lazy as BS
import qualified Data.ByteString.Builder as BSB
import Debug.Trace
newtype Waveform t a = Waveform
{sample :: t -> a
}
instance Functor (Waveform t) where
fmap f w = sampleFrom $ f . sample w
type DWave = Waveform Double Double
instance Show (Waveform Double Double) where
show w = intercalate "\n" . transpose $ map sampleToString waveSamples
where
sampleToString k = if k <= quantLevel && k >= -quantLevel
then replicate (quantLevel - k) '.' ++ "x" ++ replicate (quantLevel + k) '.'
else let m = "k = " ++ show k in m ++ replicate (quantLevel * 2 + 1 - length m) ' '
waveSamples = map (floor . (* realToFrac quantLevel) . sample w . (/sampleRate)) [0 .. 115]
quantLevel = 15 :: Int
sampleRate = 6400
instance Show (Waveform Double Discrete) where
show w = intercalate "\n" . transpose $ map sampleToString waveSamples
where
sampleToString k = if k <= quantLevel && k >= -quantLevel
then replicate (quantLevel - k) '.' ++ "x" ++ replicate (quantLevel + k) '.'
else let m = "k = " ++ show k in m ++ replicate (quantLevel * 2 + 1 - length m) ' '
waveSamples = map ((`quotRoundUp` (1 + (discFactor `quot` quantLevel))) . fromIntegral . unDiscrete . sample w . (/sampleRate)) [0 .. 115]
quantLevel = 15 :: Int
sampleRate = 6400
instance Show (Waveform Tick Discrete) where
show w = intercalate "\n" . transpose $ map sampleToString waveSamples
where
sampleToString k = if k <= quantLevel && k >= -quantLevel
then replicate (quantLevel - k) '.' ++ "x" ++ replicate (quantLevel + k) '.'
else let m = "k = " ++ show k in m ++ replicate (quantLevel * 2 + 1 - length m) ' '
waveSamples = map ((`quotRoundUp` (1 + (discFactor `quot` quantLevel))) . fromIntegral . unDiscrete . sample (skipTicks 1 w)) [0 .. 115]
quantLevel = 15 :: Int
quotRoundUp :: Int -> Int -> Int
quotRoundUp a b = if a `mod` b == 0 then a `quot` b else (signum a * signum b) + (a `quot` b)
sampleFrom :: (t -> a) -> Waveform t a
sampleFrom f = Waveform $ \t -> t `seq` f t
sampleAt :: t -> Waveform t a -> a
sampleAt = flip sample
sinWave :: Floating a => a -> Waveform a a
sinWave f = sampleFrom $ \t -> let !freq = 2 * pi * f in sin (freq * t)
fastSin :: Double -> Double -> Wavetable
fastSin f sampleRate = exploitPeriodicity (floor $ sampleRate / f) $ tickTable sampleRate $ discretize $ sinWave f
compactWave :: (Ord t,Num t) => (t,t) -> Waveform t Bool
compactWave (low,high) = sampleFrom $ \t -> t >= low && t < high
muting :: Num a => Bool -> a -> a
muting b s = if b then s else 0
modulate :: (a -> b -> c) -> Waveform t a -> Waveform t b -> Waveform t c
modulate f a b = sampleFrom $ \t -> f (sample a t) (sample b t)
amplitudeModulate :: Num a => Waveform t a -> Waveform t a -> Waveform t a
amplitudeModulate = modulate (*)
phaseModulate :: Num t
=> t
-> Waveform t t
-> Waveform t a
-> Waveform t a
phaseModulate beta modulation target = sampleFrom $ \t -> sample target (t + beta * sample modulation t)
changeSpeed :: (Ord a,Fractional a) => a -> a -> a -> Waveform a a -> Waveform a a
changeSpeed startTime lerpTime newSpeed wave = sampleFrom $ \t -> sample wave $ if t < startTime
then t
else if t > startTime + lerpTime
then startTime + newSpeed * t
else startTime + (1 + ((t - startTime)/lerpTime) * (newSpeed - 1)) * t
balanceChord :: Fractional a => [Waveform t a] -> Waveform t a
balanceChord notes = sampleFrom $ \t -> sum . map ((* (realToFrac . recip . fromIntegral . length $ notes)) . sampleAt t) $ notes
mergeWaves :: Num a => [Waveform t a] -> Waveform t a
mergeWaves notes = sampleFrom $ \t -> sum (map (sampleAt t) notes)
waveformToWAVE :: Tick -> Int -> Wavetable -> WAVE
waveformToWAVE outTicks sampleRate w = WAVE
{waveHeader = WAVEHeader
{waveNumChannels = 1
,waveFrameRate = sampleRate
,waveBitsPerSample = 32
,waveFrames = Just $ fromIntegral outTicks
}
,waveSamples = [map (unDiscrete . sample w) [0 .. outTicks - 1]]
}
triWave :: (Ord a,RealFrac a) => a -> Waveform a a
triWave f = sampleFrom $ \t -> let r = (t * f) - fromIntegral (floor (t * f)) in if r < 0.25
then 4 * r
else if r < 0.75
then 2 - (4 * r)
else -4 + (4 * r)
stdtr :: Num a => a
stdtr = 32000
testWave :: String -> Wavetable -> IO ()
testWave fp w = print w >> pure w >>= putWAVEFile (fp ++ ".wav") . waveformToWAVE (2*stdtr) stdtr . amplitudeModulate (sampleFrom $ const 0.5)
testDiagram :: PitchFactorDiagram -> IO ()
testDiagram = putWAVEFile "diag.wav" . waveformToWAVE (3*32000) 32000 . tickTable 32000 . fmap doubleToDiscrete . buildTestTrack . realToFrac . diagramToRatio . normalizePFD
where
buildTestTrack p = sequenceNotes [((0,1),sinWave concertA),((1,2),sinWave (concertA * p)),((2,3), buildChord [1,p] concertA)]
sequenceToBeat :: Double -> Double -> Beat DWave -> DWave
sequenceToBeat startAt totalLength (RoseBeat bs) = let dt = totalLength / genericLength bs in fst $ foldl (\(w,i) b -> (mergeWaves . (:[w]) . sequenceToBeat (i * dt) dt $ b,i+1)) (sampleFrom $ const 0,0) bs
sequenceToBeat startAt totalLength Rest = sampleFrom $ const 0
sequenceToBeat startAt totalLength (Beat w) = modulate muting (compactWave (startAt,startAt + totalLength)) $ timeShift startAt w
sequenceNotes :: (Ord t,Fractional t,Fractional a) => [((t,t),Waveform t a)] -> Waveform t a
sequenceNotes = mergeWaves . map (\(t,w) -> modulate muting (compactWave t) $ timeShift (fst t) w)
buildChord :: (Num a,RealFrac a) => [a] -> a -> Waveform a a
buildChord relPitches root = balanceChord $ map (triWave . (root *)) relPitches
buildChordNoBalance :: [Double] -> Double -> DWave
buildChordNoBalance relPitches root = mergeWaves $ map (triWave . (root *)) relPitches
majorChordOver :: Double -> DWave
majorChordOver = buildChord
[1
,diagramToRatio majorThird
,diagramToRatio perfectFifth
]
minorChordOver :: Double -> DWave
minorChordOver = buildChord
[semi ** 0
,semi ** 3
,semi ** 7
]
concertA :: Num a => a
concertA = 440
envelope :: Double -> Double -> Double -> Double -> Double -> Double -> DWave
envelope del att hol dec sus rel = sampleFrom $ \t -> if t < del
then 0
else if t - del < att
then (t - del) / att
else if t - del - att < hol
then 1
else if t - del - att - hol < dec
then 1 + (t - del - att - hol)/dec * (sus - 1)
else if t - del - att - hol - dec < rel
then sus * (1 - (t - del - att - hol - dec)/rel)
else 0
timeShift :: Num t => t -> Waveform t a -> Waveform t a
timeShift dt = sampleFrom . (. subtract dt) . sample
seekTo :: Num t => t -> Waveform t a -> Waveform t a
seekTo dt = sampleFrom . (. (+dt)) . sample
equalTime :: Double -> [DWave] -> DWave
equalTime dt = sequenceNotes . foldl go []
where
go xs@(((_,t1),_):_) k = ((t1,t1 + dt),k):xs
go [] k = [((0,dt),k)]
setVolume :: Num a => a -> Waveform t a -> Waveform t a
setVolume = amplitudeModulate . sampleFrom . const
emptyWave :: Num a => Waveform t a
emptyWave = sampleFrom $ const 0
discreteConvolve :: (Num a, Num t) => Waveform t [(t,a)] -> Waveform t a -> Waveform t a
discreteConvolve profile w = sampleFrom $ \t -> sum . map (\(dt,amp) -> amp * sample w (t + dt)) $ sample profile t
wackyNotConvolution :: (a -> b -> c) -> Waveform t (Waveform t a) -> Waveform t b -> Waveform t c
wackyNotConvolution modf profile w = sampleFrom $ \t -> sample (modulate modf (sample profile t) w) t
tickConvolution :: Fractional a
=> Tick
-> Tick
-> Waveform Tick (Waveform Tick a)
-> Waveform Tick a
-> Waveform Tick a
tickConvolution tickRadius skipRate profile w = sampleFrom $ \t -> let !kern = sample profile t in sum . map (\dt -> (*stepModifier) . (*sample w (t + dt)) . sample kern $ dt) $ sampleDeltas
where
sampleDeltas = map (*skipRate) [-stepsPerSide.. stepsPerSide]
stepsPerSide = tickRadius `div` skipRate
!stepModifier = realToFrac . recip . fromIntegral $ stepsPerSide
sampledConvolution :: (RealFrac t, Fractional a)
=> t
-> t
-> Waveform t (Waveform t a)
-> Waveform t a -> Waveform t a
sampledConvolution convolutionSampleRate convolutionRadius profile w = sampleFrom $ \t -> sum . map (\dt -> (*(realToFrac . recip $ convolutionSampleRate * convolutionRadius)) . (* sample w (t + dt)) . sample (sample profile t) $ dt) $ sampleDeltas
where
sampleDeltas = map ((/convolutionSampleRate) . realToFrac) [-samplesPerSide .. samplesPerSide]
samplesPerSide = floor (convolutionRadius * convolutionSampleRate)
sampleCount = 2 * samplesPerSide + 1
bandpassFilter :: Fractional a
=> Double
-> Double
-> Waveform Double a
bandpassFilter bandCenter bandSize = sampleFrom $ \t -> if t == 0 then 1 else realToFrac $ (sin (bandFreq * t)) / (bandFreq * t) * (cos (centerFreq * t))
where
!bandFreq = 2 * pi * bandSize
!centerFreq = 2 * pi * bandCenter
discretize :: Waveform t Double -> Waveform t Discrete
discretize = fmap (Discrete . properFloor . (*discFactor))
tickTable :: Double
-> Waveform Double a -> Waveform Tick a
tickTable tickrate w = sampleFrom $ \t -> sample w (fromIntegral t/tickrate)
tickTableMemo :: Double -> Waveform Double a -> Waveform Tick a
tickTableMemo tickrate w = sampleFrom $ \t -> if t < 0 then sample w (fromIntegral t/tickrate) else tab IntMap.! (fromIntegral t)
where
tab = IntMap.fromAscList . map (\k -> (fromIntegral k, sample w (fromIntegral k/tickrate))) $ [0..]
type Wavetable = Waveform Tick Discrete
data CompactWavetable = CompactWavetable {getWavetable :: Vector.Vector Int32}
solidSlice :: Tick -> Tick -> Wavetable -> Wavetable
solidSlice tickStart tickEnd w = sampleFrom $ \t -> case getWavetable cwt Vector.!? (fromIntegral (t-tickStart)) of
Just d -> Discrete d
Nothing -> sample w t
where
cwt = CompactWavetable {getWavetable = Vector.generate (fromIntegral $ tickEnd - tickStart + 1) (unDiscrete . sample w . (+tickStart) . fromIntegral)}
optimizeFilter :: Tick -> Wavetable -> Wavetable
optimizeFilter tickRadius = solidSlice (-tickRadius) tickRadius
fourierTransform :: Tick -> Double -> Waveform Tick (Complex Double) -> Waveform Double (Complex Double)
fourierTransform tickRadius fTickRate x = sampleFrom $ \f -> sum . map (\n -> sample x n / (fromIntegral tickRadius) * cis (2 * pi * f * (fromIntegral n / fTickRate))) $ [-tickRadius .. tickRadius]
realDFT :: Tick
-> Double
-> Wavetable -> Wavetable
realDFT tickRadius fTickRate x = discretize $ tickTable 1 $ fmap ((min 1) . magnitude) $ fourierTransform tickRadius fTickRate ((\x -> discreteToDouble x :+ 0) <$> solidSlice (-tickRadius) tickRadius x)
skipTicks :: Tick
-> Waveform Tick a -> Waveform Tick a
skipTicks skipRate w = sampleFrom $ \t -> sample w (skipRate * t)
exploitPeriodicity :: Tick
-> Wavetable -> Wavetable
exploitPeriodicity period x = sampleFrom $ \t -> case getWavetable cwt Vector.!? (fromIntegral (t `mod` period)) of
Just d -> Discrete d
Nothing -> sample x t
where
cwt = CompactWavetable {getWavetable = Vector.generate (fromIntegral period) (unDiscrete . sample x . fromIntegral)}
usingFFT :: Tick -> Wavetable -> Wavetable
usingFFT tickRadius w = sampleFrom $ \t -> if t < (fromIntegral $ length l)
then(!! fromIntegral t) . fmap (doubleToDiscrete . magnitude) $ l
else 0
where
l = fft (map ((\x -> discreteToDouble x :+ 0) . sample w) [-tickRadius .. tickRadius])
fft :: [Complex Double] -> [Complex Double]
fft [] = []
fft [x] = [x]
fft xs = zipWith (+) ys ts ++ zipWith (-) ys ts
where n = length xs
ys = fft evens
zs = fft odds
(evens, odds) = split xs
split [] = ([], [])
split [x] = ([x], [])
split (x:y:xs) = (x:xt, y:yt) where (xt, yt) = split xs
ts = zipWith (\z k -> exp' k n * z) zs [0..]
exp' k n = cis $ -2 * pi * (fromIntegral k) / (fromIntegral n)