module Exercise06 where import Data.List import Effects import Types sinPeriod :: Signal sinPeriod sec = sin (2 * sec * pi) triPeriod :: Signal triPeriod sec = 1 - abs ((- 4 * sec) + 2) sawPeriod :: Signal sawPeriod sec = 2 * sec -1 sqwPeriod :: Signal sqwPeriod sec = if sec < 0.5 then -1 else 1 silence :: Signal silence _ = 1 -- 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)) adsr :: ADSR -> Seconds -> Signal -> Signal adsr (attack, decay, sustainValue, release) duration signal sec | sec <= decayStart = (sec / attack) * o | sec <= sustainStart = o * (1 - decayP * decayDiff) | sec <= releaseStart = o * sustainValue | sec <= duration = o * (1 - releaseP) * sustainValue | otherwise = 0 where sustain = duration - attack - decay - release decayStart = attack decayP = (sec - decayStart) / decay decayDiff = 1 - sustainValue sustainStart = decayStart + decay releaseStart = sustainStart + sustain releaseP = (sec - releaseStart) / release o = signal sec getRest :: Double -> Double getRest n = n - fromIntegral (floor n) osc :: Signal -> ADSR -> Oscillator osc signal adsrValue tone duration sec = o * p where frequency = f tone secRest = getRest sec realSec = getRest (frequency * secRest) o = signal realSec p = adsr adsrValue duration silence sec mix :: [SampledSignal] -> SampledSignal mix signals | null valid = [] | otherwise = (here / num) : mix rest where valid = filter (not . null) signals notValid = filter null signals num = fromIntegral (length signals) here = sum (map head valid) rest = map tail valid ++ notValid {-MCCOMMENT Hi 🐧 Please use `stack run synth mid/star.mid b.wav` Output: b.wav There is no creativity and Simon realized, he is in a long way to be a good DJ I hope this music doesnt hurt your ears I chose star because it is short, so lower damage to ears Happy Christmas Day Simon -} {-WETT-} oscDown :: Integer -> Signal -> ADSR -> Oscillator oscDown x signal adsrValue tone duration sec = o * p where frequency = f (tone + x) realSec = getRest (frequency * getRest sec) o = signal realSec p = adsr adsrValue duration silence sec groupB :: Int -> [a] -> [[a]] groupB _ [] = [] groupB n x = take n x : groupB n (drop n x) -- you can add new oscillators here piano :: Oscillator piano = osc sinPeriod (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) drum :: Oscillator drum = osc sinPeriod (0.001, 0.1, 0.1, 0.4) idk :: Oscillator idk = osc triPeriod (0.001, 0.001, 0.9, 0.001) -- 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 tempMix :: Double -> Double -> SampledSignal -> SampledSignal -> SampledSignal tempMix _ _ [] _ = [] tempMix p sec (a : ax) (b : bx) = (a * factorA + b * factorB) / 2 : tempMix (p + 1) sec ax bx where factorA = (1 + sin (p / sampleRate / sec * pi)) / 2 factorB = 1 - factorA delay :: Double -> DSPEffect delay sec audio = replicate l 0 ++ reverse (drop l (reverse audio)) where l = round (sec * sampleRate) -- 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 = repeat piano audioTracks = zipWith playNotes instrs tracks audioNoEffects = mixTracks audioTracks [0.8, 0.7] audioNoEffectsDrum = mixTracks (map (playNotes drum) tracks) [0.8, 0.7] audioClip = addEffects [addGain 0.66, clip 0.9, addGain 4] audioNoEffects audioClipMix = mix [audioClip, audioClip, audioClip, audioNoEffects] audioMixClip = tempMix 0 3 audioClipMix audioNoEffects audioDumpMix = addEffects [addGain 0.7] audioNoEffectsDrum audioBackgroudTrack = mixTracks (map (playNotes (oscDown (-2) sinPeriod (0.001, 0.1, 0.1, 0.4))) tracks) [0.8, 0.7] audioBackgroudTrack2 = mixTracks (map (playNotes (oscDown (-4) sinPeriod (0.001, 0.1, 0.1, 0.4))) tracks) [0.8, 0.7] audioBackgroudTrack3 = mixTracks (map (playNotes (oscDown (-5) sinPeriod (0.001, 0.1, 0.1, 0.4))) tracks) [0.8, 0.7] audioBackgroudMix = mix [audioBackgroudTrack, addEffects [addGain 1.6] audioBackgroudTrack2, addEffects [addGain 2.1] audioBackgroudTrack3] idkMix = addEffects [addGain 0.4] (mixTracks (map (playNotes idk) tracks) [0.8, 0.7]) audio = mix [audioMixClip, audioDumpMix, audioBackgroudMix, idkMix] -- 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-}