{- |
Demonstration song using with SuperCollider and global effects.
-}
module Haskore.Interface.SuperCollider.Example.Air where

-- import qualified Sound.SC3 as SC
import qualified Sound.SC3.UGen.UGen       as SCUGen
import qualified Sound.SC3.UGen.Oscillator as SCOsci
import qualified Sound.SC3.UGen.Filter     as SCFilt
import qualified Sound.SC3.UGen.Envelope   as SCUGEnv
import qualified Sound.SC3.UGen.Math       as SCMath

import Sound.SC3.UGen.Enum (DoneAction(PauseSynth))
import Sound.SC3.UGen.Rate (Rate(KR))

import Sound.SC3.UGen.UGen (UGen)

import qualified Haskore.Interface.SuperCollider.Example  as Example
import qualified Haskore.Interface.SuperCollider.Play     as Play
import qualified Haskore.Interface.SuperCollider.Schedule as Schedule
import qualified Haskore.Interface.SuperCollider.SoundMap as SoundMap

import qualified Haskore.Interface.SuperCollider.Schedule.Channel as CSchedule

import Haskore.Interface.SuperCollider.SoundMap
         (InstrumentUGen)

import Haskore.Interface.SuperCollider.Schedule.Channel
   (rhythmicMusicFromRhythm, rhythmicMusicFromDrum,
    rhythmicMusicFromMelody,
    ugenFromInstr, ugenFromDrum, installInstr, installDrum)

import           Haskore.Basic.Chord as Chord

import           Haskore.Melody         as Melody
import qualified Haskore.Music          as Music
import           Haskore.Music.Rhythmic as RhyMusic

import System.Random (randomRs, mkStdGen)

import Data.List (genericLength)
import Data.Ratio ((%))



transposition :: Int
transposition = 2

{-
bassMelody :: Melody.T (Rational, ())
bassMelody =
  Music.line $
  zipWith
     (\vel n -> n (vel%1000, ()))
     (randomRs (0,1000) (mkStdGen 914)) $
-}
bassMelodyA0 :: [() -> Melody.T ()]
bassMelodyA0 =
   a 1 den : c 2 en : a 1 sn : d 2 en : e 2 sn : g 2 en :
   a 1 den : a 1 sn : c 2 en : a 1 sn : c 2 en : d 2 sn : e 2 den :
   a 1 den : c 2 en : a 1 sn : d 2 en : e 2 sn : g 2 en :
   a 1 den : a 1 sn : c 2 en : a 1 sn : c 2 en : a 1 sn : g 1 den :
   []

bassMelodyA1 :: [() -> Melody.T ()]
bassMelodyA1 =
   a 1 den : d 2 en : a 1 sn : e 2 en : fs 2 sn : a 2 en :
   a 1 den : a 1 sn : d 2 en : a 1 sn : d 2 en : e 2 sn : a 2 den :
   a 1 den : d 2 en : a 1 sn : e 2 en : fs 2 sn : a 2 en :
   a 1 den : a 1 sn : c 2 en : d 2 sn : e 2 en : d 2 sn : a 1 den :
   []

bassMelodyA :: [() -> Melody.T ()]
bassMelodyA = bassMelodyA0 ++ bassMelodyA1

-- B == A

bassMelodyC :: [() -> Melody.T ()]
bassMelodyC =
   a 1 den : d 2 en : a 1 sn : e 2 en : f 2 sn : a 2 en :
   a 1 den : a 1 sn : d 2 en : a 1 sn : d 2 en : f 2 sn : a 2 den :
   a 1 den : d 2 en : a 1 sn : e 2 en : f 2 sn : a 2 en :
   a 1 den : a 1 sn : c 2 en : d 2 sn : e 2 en : d 2 sn : a 1 den :
   a 1 den : c 2 en : a 1 sn : d 2 en : e 2 sn : g 2 en :
   a 1 den : a 1 sn : c 2 en : a 1 sn : c 2 en : d 2 sn : e 2 den :
   a 1 den : c 2 en : a 1 sn : d 2 en : e 2 sn : g 2 en :
   a 1 den : a 1 sn : c 2 en : a 1 sn : c 2 en : a 1 sn : g 1 den :
   []

-- D == A

bassMelodyE :: [() -> Melody.T ()]
bassMelodyE =
   a 1 den : d 2 en : a 1 sn : e 2 en : f 2 sn : a 2 en :
   a 1 den : a 1 sn : d 2 en : a 1 sn : d 2 en : f 2 sn : a 2 den :
   a 1 den : d 2 en : a 1 sn : e 2 en : f 2 sn : a 2 en :
   a 1 den : a 1 sn : c 2 en : d 2 sn : e 2 en : d 2 sn : a 1 den :
   a 1 den : c 2 en : a 1 sn : d 2 en : e 2 sn : g 2 en :
   a 1 den : a 1 sn : c 2 en : a 1 sn : c 2 en : d 2 sn : e 2 den :
   a 1 den : c 2 en : a 1 sn : d 2 en : e 2 sn : g 2 en :
   a 1 den : a 1 sn : c 2 en : a 1 sn : c 2 en : a 1 sn : g 1 den :
   []

bassMelodyF :: [() -> Melody.T ()]
bassMelodyF =
   a 1 den : c 2 en : a 1 sn : c 2 en : e 2 sn : f 2 en :
   a 1 den : a 1 sn : c 2 en : a 1 sn : c 2 en : e 2 sn : f 2 den :
   a 1 den : d 2 en : a 1 sn : d 2 en : f 2 sn : a 2 en :
   a 1 den : a 1 sn : d 2 en : e 2 sn : f 2 en : e 2 sn : a 1 den :
   b 1 den : e 2 en : b 1 sn : e 2 en : a 2 sn : b 2 en :
   b 1 den : b 1 sn : e 2 en : b 1 sn : e 2 en : a 2 sn : b 2 den :
   b 1 den : e 2 en : b 1 sn : e 2 en : gs 2 sn : b 2 en :
   b 1 den : b 1 sn : e 2 en : b 1 sn : gs 2 en : e 2 sn : b 1 den :
   []



bassMelody :: Melody.T ()
bassMelody =
  let trans = map (Music.transpose transposition .)
  in  Music.line $
      map
        (Music.crescendo 4 . Music.loudness 0.2 .
         Music.line . map ($())) $
      bassMelodyA : bassMelodyA :
      bassMelodyA : bassMelodyC :
      bassMelodyA : bassMelodyE :
      bassMelodyA : bassMelodyC :
      trans bassMelodyA :
      trans bassMelodyF :
      trans bassMelodyA :
      trans bassMelodyF :
      trans bassMelodyA :
      trans (bassMelodyA0 ++ bassMelodyA0) :
      []

sliceVertInf :: Int -> [a] -> [[a]]
sliceVertInf n =
   map (take n) . iterate (drop n)

harpScale :: Melody.T ()
harpScale =
   -- could sliceVert or sliceHoriz
   let binomDists = map sum $ sliceVertInf 3 $
          randomRs (-1,1::Double) (mkStdGen 102)
       tones = [d,e,a,b]
       -- properFraction is useless for negative numbers
       splitFraction x =
          let n = floor x
          in  (n, x - fromIntegral n)
       makeNote x =
          let (oct,p) = splitFraction x
          in  (tones!!floor(p*genericLength tones)) oct sn ()
   in  Music.legato qn $ Music.line $ take (round(6*4*dhn/sn)) $
           map makeNote $ zipWith (+) binomDists $
             iterate (0.02+) (-2)


-- envelopedStrings :: Music.Dur -> InstrumentUGen
envelopedStrings :: UGen -> InstrumentUGen
envelopedStrings dur vel freq =
   let env = SCUGEnv.envGen KR 1 2 0 1 PauseSynth
                (Example.bridge 3 dur 0.5)
   in  env * Example.strings vel freq


chordConfuse :: Melody.T ()
chordConfuse =
   Chord.harmonicGen () (2*4*dhn) [a 1, b 1, d 2, e  2, g 2]


chordsA :: Melody.T ()
chordsA =
   Chord.harmonicGen () (4*dhn) [a 1, c 2, e  2] +:+
   Chord.harmonicGen () (4*dhn) [a 1, d 2, fs 2] +:+
   Chord.harmonicGen () (4*dhn) [a 2, c 2, e  2] +:+
   (Chord.harmonicGen () (4*dhn) [a 2, d 2] =:=
    (fs 2 (3*dhn) () +:+ g 2 dhn ()))

chordsB :: Melody.T ()
chordsB =
   let ce = Chord.harmonicGen () (4*dhn) [c 2, e 2]
   in  ce +:+
       (d 2 (2*4*dhn) () =:=
          fs 2 (4*dhn) () +:+
          f  2 (4*dhn) ()) +:+
       ce

chordsBlow :: Melody.T ()
chordsBlow =
   a 1 (4*4*dhn) () =:= chordsB

chordsBhigh :: Melody.T ()
chordsBhigh =
   a 2 (4*4*dhn) () =:= chordsB

chordsC :: Melody.T ()
chordsC =
   (a 1 (2*dhn) () +:+ a 2 (2*dhn) ()
      =:= c 2 (4*dhn) () =:= e  2 (4*dhn) ()) +:+
   (d 2 (2*4*dhn) () =:=
    fs 2 (2*dhn) () +:+ g 2 (dhn) () +:+ fs 2 (dhn) () +:+
    f  2 (2*dhn) () +:+ g 2 (dhn) () +:+ f  2 (dhn) () =:=
    a 2 (2*4*dhn) ()) +:+
   (a 2 (4*dhn) () =:= c 2 (4*dhn) () =:= e  2 (4*dhn) ())

chordsD :: Melody.T ()
chordsD =
   (a 1 (2*dhn) () +:+ a 2 (2*dhn) ()
      =:= c 2 (4*dhn) () =:= e  2 (4*dhn) ()) +:+
   (d 2 (4*dhn) () =:=
    fs 2 (2*dhn) () +:+ g 2 (dhn) () +:+ fs 2 (dhn) () =:=
    a 2 (4*dhn) ()) +:+
   (c 2 (2*dhn) () +:+ d 2 (2*dhn) () =:= f 2 (4*dhn) () =:= a 2 (4*dhn) ())

chordsD0 :: Melody.T ()
chordsD0 = chordsD +:+
   (b 1 (4*dhn) () =:=
    e 2 (4*dhn) () =:=
    gs 2 (2*dhn) () +:+ a 2 (dhn) () +:+ b 2 (dhn) ())

chordsD1 :: Melody.T ()
chordsD1 = chordsD +:+
   (b 1 (4*dhn) () =:=
    e 2 (4*dhn) () =:=
    a 2 (2*dhn) () +:+ gs 2 (2*dhn) ())

chordsE :: Melody.T ()
chordsE =
   Chord.harmonicGen () (4*dhn) [a 1, c 2, e  2] +:+
   Chord.harmonicGen () (4*dhn) [a 1, d 2, fs 2] +:+
   Chord.harmonicGen () (8*dhn) [a 1, c 2, e  2]

chords :: Melody.T Double
chords =
   Music.legato wn $ Music.mapDurNote
      (\dur (Melody.Note () p) ->
          Melody.Note (2 * fromRational dur) p) $
   chordConfuse +:+
   chordsA +:+
   chordsBlow +:+ chordsBhigh +:+
   chordsC +:+
   Music.transpose transposition (chordsD0 +:+ chordsD1 +:+ chordsE)


xylophoneSolo :: Melody.T ()
xylophoneSolo =
   let build n0 notes =
          Music.line $
          map (\n -> n sn () +:+ n0 sn () +:+ n0 sn ()) notes
   in  build (a 1)
          [e 2, d 2, e 2, d 2, e 2, d 2, e 2, d 2,
           e 2, d 2, e 2, g 2, a 2, g 2, e 2, d 2,
           e 2, d 2, e 2, d 2, e 2, d 2, e 2, d 2,
           g 2, a 2, c 3, a 2, d 3, c 3, a 2, g 2,
           f 2, d 2, f 2, d 2, f 2, d 2, f 2, d 2,
           f 2, d 2, f 2, d 2, g 2, d 2, f 2, d 2,
           e 2, d 2, e 2, d 2, e 2, d 2, e 2, d 2,
           a 2, gs 2, g 2, fs 2, f 2, e 2, ds 2, d 2]


bassStringMelody :: Melody.T ()
bassStringMelody =
   Music.transpose transposition $
   Music.line $ map (\n -> n dhn ()) $
      a 1 : b 1 : c 2 : e 2 : fs 2 : g 2 : a 2 : b 2 :
      d 2 : e 2 : f 2 : a 2 : gs 2 : a 2 : b 2 : c 3 :
      []


melodyA :: Melody.T ()
melodyA =
   Music.line $
   e 2 (9%8) () : d 2 den () : c 2 den () : a 2 dqn () :
   e 2 dqn () : d 2 (5%16) () : c 2 qn () : d 2 (21%16) () :
   e 2 den () : a 1 (15%16) () :
   a 1 (5%16) () : b 1 qn () : c 2 (wn + 5%16) () :
   b 1 den () : a 1 den () : d 2 dqn () : e 2 dqn () :
   c 2 dqn () : d 2 den () : a 1 (15%8 + 21%16) () :
   []

melodyB :: Melody.T ()
melodyB =
   Music.take (10 + 11%16) melodyA +:+
   (Music.line $
    g 2 (5%16) () : fs 2 qn () : f 2 (5%16) () : e 2 ddqn () :
    [])

melody :: Melody.T ()
melody =
   Music.rest (4*4*dhn)
     +:+ melodyB
     +:+ melodyB
     +:+ Music.rest (4*4*dhn)
     +:+ Music.transpose transposition (melodyA +:+ melodyA)


{-
scsynth -N air.osc _ air.aiff 44100 AIFF int16
-}

schedule :: Schedule.T
schedule =
   CSchedule.fromRhythmicMusic $
   do bass     <- installInstr SoundMap.with0Parameters "bass"
                     (Example.fmBass (SCUGen.MCE [0.99,1.01]))
      guitar   <- installInstr SoundMap.with0Parameters "guitar"
                     (Example.fmGuitar (SCUGen.MCE [2.99,3.01]))
      strings  <- installInstr SoundMap.with1Parameter "strings"
                     envelopedStrings
      harpsichord <- installInstr SoundMap.with0Parameters "harpsichord"
                     Example.harpsichord
      bassStrings <- installInstr SoundMap.with0Parameters "bassstrings"
                     Example.modulatedStrings
      xylophone <- installInstr SoundMap.with0Parameters "xylophone"
                     Example.xylophone
      hihat    <- installDrum SoundMap.with0Parameters "hihat"
                     Example.hihat
      bassdrum <- installDrum SoundMap.with0Parameters "bassdrum"
                     Example.bassdrum
      let lfoSine = 1000 * exp
             (0.3 * SCOsci.sinOsc KR 0.1 (SCUGen.MCE [-pi/2, 0]) +
              0.3 * SCOsci.sinOsc KR (sqrt 0.03) 0)
          mix = flip SCMath.clip2 1 $ 0.2 * (
            ugenFromInstr bass +
            0.3 * ugenFromInstr xylophone +
            0.5 * ugenFromInstr harpsichord +
            3 * SCFilt.lpf (ugenFromInstr bassStrings) 1000 +
            SCFilt.rlpf (0.5 * ugenFromInstr strings) lfoSine 0.1 +
            (let guit = 0.6 * ugenFromInstr guitar
             in  guit + SCFilt.combN (SCFilt.lpf guit 500) 0.2 0.2 5) +
            ugenFromDrum hihat +
            2*ugenFromDrum bassdrum)
      return (mix,
         Music.changeTempo 2 $
--         Music.take 10 $
--         Music.drop (4*4*4*dhn) $
         (rhythmicMusicFromMelody strings $ Music.transpose (-12) chords) =:=
         hnr +:+
         (rhythmicMusicFromMelody harpsichord harpScale =:=
          Music.rest (2*4*dhn) +:+ (Music.chord $
            (rhythmicMusicFromMelody bass $ Music.transpose (-36) bassMelody) :
            (rhythmicMusicFromMelody guitar $ Music.transpose (-12) melody) :
            (Music.rest (3*4*4*dhn) +:+
               (rhythmicMusicFromMelody xylophone $ Music.transpose (0) xylophoneSolo)) :
            (Music.rest (5*4*4*dhn) +:+
               (rhythmicMusicFromMelody bassStrings $ Music.transpose (-36) bassStringMelody)) :
            (Music.replicate (7*4*8 + 2) $
                rhythmicMusicFromRhythm bassdrum sn "x.x..x" =:=
                (Music.replicate 6 $ rhythmicMusicFromDrum hihat sn)) :
            [])))


main :: IO ()
main = Play.schedule Play.defaultLatency schedule
