module Exercise06 where import Data.List import Effects import Types sinPeriod :: Signal sinPeriod x = sin (2 * pi * x) sqwPeriod :: Signal sqwPeriod x | x < 0.5 = -1 | x == 0 = 0 | otherwise = 1 sawPeriod :: Signal sawPeriod x = 2 * x -1 triPeriod :: Signal triPeriod x | x <= 0.5 = 4 * x -1 | otherwise = -4 * x + 3 silence :: Signal silence = undefined -- NOTE: the formula is taken from https://pages.mtu.edu/~suits/NoteFreqCalcs.html f :: Semitone -> Hz f n = 440.0 * (2 ** (fromInteger n / 12.0)) osc :: Signal -> ADSR -> Oscillator osc signal params semitone duration = adsr params duration (\s -> signal $ s * f semitone - (fromIntegral . floor) (s * f semitone)) adsr :: ADSR -> Seconds -> Signal -> Signal adsr (attack, decay, sustain, release) duration signal seconds | seconds <= attack = seconds / attack * signal seconds | seconds <= attack + decay = (1 + (sustain -1) * (seconds - attack) / decay) * signal seconds | seconds <= duration - release = sustain * signal seconds | otherwise = sustain / release * (duration - seconds) * signal seconds mix :: [SampledSignal] -> SampledSignal mix ss = foldr (zipWith (+) . map (/ (fromIntegral . length) ss) . (\xs -> xs ++ replicate (maxi - length xs) 0)) (replicate maxi 0) ss where maxi = maximum (map length ss) {-WETT-} {-MCCOMMENT compile with stack run synth .\mid\song_of_storms_organ.mid .\mid\song_of_storms_organ.wav -} -- you can add new oscillators here piano :: Oscillator piano = osc sawPeriod (0.01, 0.1, 0.7, 0.2) lead :: Oscillator lead = osc sqwPeriod (0.01, 0.2, 0.3, 0.1) bass :: Oscillator bass = osc sinPeriod (0.001, 0.2, 0.9, 0.1) organ :: Oscillator organ = osc (sawNPeriod 3) (0.1, 0.05, 0.7, 0.05) --returns the nth partial sum of the Fourier series that converges to the sawtooth wave sawNPeriod :: Int -> Signal sawNPeriod n x = (-2 / pi) * sum (take n (snd $ mapAccumL (\a b -> (a + 1, sin b / a)) 1 (iterate (* 2) (2 * pi * x)))) -- you can add more effects here -- mix the signals with some volume mixTracks :: [SampledSignal] -> [Double] -> SampledSignal mixTracks trks vols = mix $ zipWith (\trk vol -> map (* vol) trk) trks vols -- here we mix it all together and apply the effects notesToSignal :: (Oscillator -> [Note] -> SampledSignal) -> [[Note]] -> SampledSignal notesToSignal playNotes tracks = audio where -- specify the instruments of each track -- try the following instruments with the mario.mid file instrs = cycle [organ, piano] audioTracks = zipWith playNotes instrs tracks audioNoEffects = mixTracks audioTracks [0.8, 0.05] audio = audioNoEffects -- To add effects, replace the line above by the line at the end of this comment and change the list off effects to your own effects. -- Note that the effects are applied from right to left, ie. the rightmost effect is applied first. -- audio = addEffects [clip 0.9, addGain 4] audioNoEffects -- If you want to apply an effect only to parts of the signal, you can use the function applyEffectToInterval, as shown below. -- Thereby the first argument specifies the interval to which the effect should be applied in seconds. -- audio = applyEffectToInterval (2, 6) audioNoEffects distortion {-TTEW-}