{-# LANGUAGE FlexibleInstances #-}
module Boopadoop
(module Boopadoop
,module Boopadoop.Diagram
,module Boopadoop.Rhythm
,module Boopadoop.Interval
) where
import Data.WAVE as WAVE
import Control.Applicative
import Boopadoop.Diagram
import Boopadoop.Rhythm
import Boopadoop.Interval
import Data.List
newtype Waveform t a = Waveform
{sample :: t -> a
}
type DWave = Waveform Double Double
instance Show (Waveform Double Double) where
show w = intercalate "\n" . transpose $ map sampleToString waveSamples
where
sampleToString k = replicate (quantLevel - k) '.' ++ "x" ++ replicate (quantLevel + k) '.'
waveSamples = map (floor . (* realToFrac quantLevel) . sample w . (/sampleRate)) [0 .. 115]
quantLevel = 15 :: Int
sampleRate = 16000
sampleFrom :: (t -> a) -> Waveform t a
sampleFrom = Waveform
sampleAt :: t -> Waveform t a -> a
sampleAt = flip sample
sinWave :: Double -> DWave
sinWave f = sampleFrom $ \t -> sin (2 * pi * f * t)
compactWave :: (Ord t,Num t) => (t,t) -> Waveform t Bool
compactWave (low,high) = sampleFrom $ \t -> t >= low && t < high
modulateMuting :: Num a => Waveform t Bool -> Waveform t a -> Waveform t a
modulateMuting = modulate (\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 ((/ fromIntegral chordSize) . sampleAt t) $ notes
where
chordSize = length notes
mergeWaves :: Fractional a => [Waveform t a] -> Waveform t a
mergeWaves notes = sampleFrom $ \t -> sum (map (sampleAt t) notes)
waveformToWAVE :: Double -> DWave -> WAVE
waveformToWAVE outTime w = WAVE
{waveHeader = WAVEHeader
{waveNumChannels = 1
,waveFrameRate = sampleRate
,waveBitsPerSample = 32
,waveFrames = Just $ numFrames
}
,waveSamples = [map (doubleToSample . sample w . (/sampleRate)) [0 .. fromIntegral (numFrames - 1)]]
}
where
sampleRate :: Num a => a
sampleRate = 44100
numFrames = ceiling $ outTime * sampleRate
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)
testWave :: DWave -> IO ()
testWave w = print w >> pure w >>= putWAVEFile "test.wav" . waveformToWAVE 10 . amplitudeModulate (sampleFrom $ const 0.5)
testDiagram :: PitchFactorDiagram -> IO ()
testDiagram = putWAVEFile "diag.wav" . waveformToWAVE 3 . 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) = modulateMuting (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) -> modulateMuting (compactWave t) $ timeShift (fst t) w)
buildChord :: [Double] -> Double -> DWave
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
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