--
module Music where

type Pitch      = (PitchClass, Octave)
data PitchClass = Cf | C  | Cs | Df | D  | Ds | Ef | E | Es | Ff | F
                | Fs | Gf | G  | Gs | Af | A  | As | Bf | B | Bs
     deriving (Eq,Show)
type Octave     = Int

data Music = Note Pitch Dur
           | Rest Dur
           | Music :+: Music
           | Music :=: Music
           | Tempo  (Ratio Int) Music
           | Trans  Int Music
           | Instr  IName Music
    deriving (Show, Eq)

type Dur   = Ratio Int

data IName
 = AcousticGrandPiano    | BrightAcousticPiano | ElectricGrandPiano
 | HonkyTonkPiano        | RhodesPiano         | ChorusedPiano
 | Harpsichord           | Clavinet            | Celesta
 | Glockenspiel          | MusicBox
 | Vibraphone            | Marimba             | Xylophone
 | TubularBells          | Dulcimer            | HammondOrgan
 | PercussiveOrgan       | RockOrgan           | ChurchOrgan
 | ReedOrgan             | Accordion           | Harmonica
 | TangoAccordion        | AcousticGuitarNylon | AcousticGuitarSteel
 | ElectricGuitarJazz    | ElectricGuitarClean | ElectricGuitarMuted | OverdrivenGuitar
 | DistortionGuitar      | GuitarHarmonics     | AcousticBass
 | ElectricBassFingered  | ElectricBassPicked  | FretlessBass
 | SlapBass1             | SlapBass2           | SynthBass1      | SynthBass2
 | Violin                | Viola               | Cello           | Contrabass
 | TremoloStrings        | PizzicatoStrings    | OrchestralHarp  | Timpani
 | StringEnsemble1       | StringEnsemble2     | SynthStrings1
 | SynthStrings2         | ChoirAahs           | VoiceOohs       | SynthVoice
 | OrchestraHit          | Trumpet             | Trombone        | Tuba
 | MutedTrumpet          | FrenchHorn          | BrassSection    | SynthBrass1
 | SynthBrass2           | SopranoSax          | AltoSax         | TenorSax
 | BaritoneSax           | Oboe                | Bassoon         | EnglishHorn         | Clarinet
 | Piccolo               | Flute               | Recorder | PanFlute  | BlownBottle
 | Shakuhachi            | Whistle             | Ocarina         | Lead1Square
 | Lead2Sawtooth         | Lead3Calliope       | Lead4Chiff
 | Lead5Charang          | Lead6Voice          | Lead7Fifths
 | Lead8BassLead         | Pad1NewAge          | Pad2Warm
 | Pad3Polysynth         | Pad4Choir           | Pad5Bowed
 | Pad6Metallic          | Pad7Halo            | Pad8Sweep
 | FX1Train              | FX2Soundtrack       | FX3Crystal
 | FX4Atmosphere         | FX5Brightness       | FX6Goblins
 | FX7Echoes             | FX8SciFi            | Sitar           | Banjo     | Shamisen
 | Koto                  | Kalimba             | Bagpipe         | Fiddle    | Shanai
 | TinkleBell            | Agogo               | SteelDrums      | Woodblock | TaikoDrum
 | MelodicDrum           | SynthDrum           | ReverseCymbal
 | GuitarFretNoise       | BreathNoise         | Seashore
 | BirdTweet             | TelephoneRing       | Helicopter
 | Applause              | Gunshot             | Percussion
 deriving (Show,Eq,Ord,Enum)


type AbsPitch = Int


absPitch :: Pitch -> AbsPitch
absPitch (pc,oct) = 12*oct + pcToInt pc

pitch    :: AbsPitch -> Pitch
pitch ap = ( [C,Cs,D,Ds,E,F,Fs,G,Gs,A,As,B] !! mod ap 12,
             quot ap 12 )


pcToInt :: PitchClass -> Int
pcToInt pc = case pc of
               Cf -> -1   -- should Cf be 11?
               C  -> 0  ;  Cs -> 1
               Df -> 1  ;  D  -> 2 ;  Ds -> 3
               Ef -> 3  ;  E  -> 4 ;  Es -> 5
               Ff -> 4  ;  F  -> 5 ;  Fs -> 6
               Gf -> 6  ;  G  -> 7 ;  Gs -> 8
               Af -> 8  ;  A  -> 9 ;  As -> 10
               Bf -> 10 ;  B  -> 11
               Bs -> 12 -- should Bs be 0?

trans    :: Int -> Pitch -> Pitch
trans i p = pitch (absPitch p + i)



cf,c,cs,df,d,ds,ef,e,es,ff,f,fs,gf,g,gs,af,a,as,bf,b,bs
  :: Octave -> Dur -> Music

cf o = Note (Cf,o);  c o = Note (C,o);  cs o = Note (Cs,o)
df o = Note (Df,o);  d o = Note (D,o);  ds o = Note (Ds,o)
ef o = Note (Ef,o);  e o = Note (E,o);  es o = Note (Es,o)
ff o = Note (Ff,o);  f o = Note (F,o);  fs o = Note (Fs,o)
gf o = Note (Gf,o);  g o = Note (G,o);  gs o = Note (Gs,o)
af o = Note (Af,o);  a o = Note (A,o);  as o = Note (As,o)
bf o = Note (Bf,o);  b o = Note (B,o);  bs o = Note (Bs,o)

wn,  hn,  qn,  en,  sn,  tn  :: Dur
dhn, dqn, den, dsn           :: Dur

wnr, hnr, qnr, enr, snr, tnr :: Music
dhnr, dqnr, denr, dsnr       :: Music

wn  = 1         ; wnr  = Rest wn      -- whole
hn  = 1%2       ; hnr  = Rest hn      -- half
qn  = 1%4       ; qnr  = Rest qn      -- quarter
en  = 1%8       ; enr  = Rest en      -- eight
sn  = 1%16      ; snr  = Rest sn      -- sixteenth
tn  = 1%32      ; tnr  = Rest tn      -- thirty-second

dhn = 3%4       ; dhnr = Rest dhn     -- dotted half
dqn = 3%8       ; dqnr = Rest dqn     -- dotted quarter
den = 3%16      ; denr = Rest den     -- dotted eighth
dsn = 3%32      ; dsnr = Rest dsn     -- dotted sixteenth

line, chord :: [Music] -> Music
line  = foldr (:+:) (Rest 0)
chord = foldr (:=:) (Rest 0)

cScale =
  line [c 4 qn, d 4 qn, e 4 qn,
        f 4 qn, g 4 qn, a 4 qn,
        b 4 qn, c 5 qn]
ex1 = cScale

cMaj = [ n 4 hn | n <- [c,e,g] ]
cMin = [ n 4 wn | n <- [c,ef, g] ]

cMajArp = line  cMaj
ex2 = cMajArp

cMajChd = chord cMaj
ex3 = cMajChd

ex4 = line [ chord cMaj, chord cMin ]


delay :: Dur -> Music -> Music
delay d m = Rest d :+: m

ex5 = cScale :=: (delay dhn cScale)

ex6 = line [line cMaj,Trans 12 (line cMaj)]



repeatM :: Music -> Music
repeatM m = m :+: repeatM m

nBeatsRest n note = line ((take n (repeat note)) ++ [qnr])

ex7 = line [e 4 qn, d 4 qn, c 4 qn, d 4 qn,
            line [ nBeatsRest 3 (n 4 qn) | n <- [e,d] ],
            e 4 qn, nBeatsRest 2 (g 4 qn) ]

pr1, pr2 :: Pitch -> Music
pr1 p = Tempo (5%6)
          (Tempo (4%3) (mkLn 1 p qn :+:
                        Tempo (3%2) (mkLn 3 p en :+:
                                     mkLn 2 p sn :+:
                                     mkLn 1 p qn    ) :+:
                        mkLn 1 p qn) :+:
           Tempo (3%2) (mkLn 6 p en))

pr2 p = Tempo (7%6)
          (m1 :+:
           Tempo (5%4) (mkLn 5 p en) :+:
           m1 :+:
           Tempo (3%2) m2)
  where m1 = Tempo (5%4) (Tempo (3%2) m2 :+: m2)
        m2 = mkLn 3 p en

mkLn n p d = line (take n (repeat (Note p d)))

pr12 :: Music
pr12 = pr1 (C,5) :=: pr2 (G,5)


(=:=) :: Dur -> Dur -> Music -> Music
old =:= new  =  Tempo (new/old)

dur :: Music -> Dur

dur (Note _ d)    = d
dur (Rest d)      = d
dur (m1 :+: m2)   = dur m1   +   dur m2
dur (m1 :=: m2)   = dur m1 `max` dur m2
dur (Tempo  a  m) = dur m / a
dur (Trans  _  m) = dur m
dur (Instr  _  m) = dur m

revM :: Music -> Music

revM n@(Note _ _) = n
revM r@(Rest _)   = r
revM (Tempo a  m) = Tempo a    (revM m)
revM (Trans i  m) = Trans i    (revM m)
revM (Instr i  m) = Instr i    (revM m)
revM (m1 :+: m2)  = revM m2 :+: revM m1
revM (m1 :=: m2)
  = let d1 = dur m1
        d2 = dur m2
    in if d1>d2 then revM m1 :=: (Rest (d1-d2) :+: revM m2)
                else (Rest (d2-d1) :+: revM m1) :=: revM m2


cut :: Dur -> Music -> Music
cut d m | d <= 0  = Rest 0
cut d (Note x d0) = Note x (min d0 d)
cut d (Rest d0)   = Rest (min d0 d)
cut d (m1 :=: m2) = cut d m1 :=: cut d m2
cut d (Tempo a m) = Tempo a (cut (d*a) m)
cut d (Trans a m) = Trans a (cut d m)
cut d (Instr a m) = Instr a (cut d m)
cut d (m1 :+: m2) = let m1' = cut d m1
                        m2' = cut (d - dur m1') m2
                    in m1' :+: m2'

(/=:) :: Music -> Music -> Music
m1 /=: m2 = cut (min (dur m1) (dur m2)) (m1 :=: m2)


trill :: Int -> Dur -> Music -> Music

trill i d n@(Note p nd)
  = if d >= nd then n
    else Note p d
         :+: trill (negate i) d
                   (Note (trans i p) (nd-d))
trill i d (Tempo a m) = Tempo  a (trill i (d*a) m)
trill i d (Trans a m) = Trans  a (trill i d m)
trill i d (Instr a m) = Instr  a (trill i d m)
trill _ _ _           = error "Trill input must be a single note"


trill'         :: Int -> Dur -> Music -> Music
trill' i sDur m = trill (negate i) sDur (Trans i m)

roll :: Dur -> Music -> Music
roll dur m = trill 0 dur m


trilln  :: Int -> Int -> Music -> Music
trilln i nTimes m = trill i (dur m / (nTimes%1)) m

trilln' :: Int -> Int -> Music -> Music
trilln' i nTimes m = trilln (negate i) nTimes (Trans i m)

rolln   :: Int -> Music -> Music
rolln nTimes m = trilln 0 nTimes m



data PercussionSound
  = AcousticBassDrum  -- MIDI Key 35
  | BassDrum1         -- MIDI Key 36
  | SideStick         -- ...
  | AcousticSnare | HandClap      | ElectricSnare | LowFloorTom
  | ClosedHiHat   | HighFloorTom  | PedalHiHat    | LowTom
  | OpenHiHat     | LowMidTom     | HiMidTom      | CrashCymbal1
  | HighTom       | RideCymbal1   | ChineseCymbal | RideBell
  | Tambourine    | SplashCymbal  | Cowbell       | CrashCymbal2
  | Vibraslap     | RideCymbal2   | HiBongo       | LowBongo
  | MuteHiConga   | OpenHiConga   | LowConga      | HighTimbale
  | LowTimbale    | HighAgogo     | LowAgogo      | Cabasa
  | Maracas       | ShortWhistle  | LongWhistle   | ShortGuiro
  | LongGuiro     | Claves        | HiWoodBlock   | LowWoodBlock
  | MuteCuica     | OpenCuica     | MuteTriangle
  | OpenTriangle      -- MIDI Key 82
    deriving (Show,Eq,Ord,Ix,Enum)


perc :: PercussionSound -> Dur -> Music
perc ps = Note (pitch (fromEnum ps + 35))

funkGroove
  = let p1 = perc LowTom        qn
        p2 = perc AcousticSnare en
    in Tempo 3 (Instr Percussion (cut 8 (repeatM
         ( (p1 :+: qnr :+: p2 :+: qnr :+: p2 :+:
            p1 :+: p1 :+: qnr :+: p2 :+: enr)
           :=: roll en (perc ClosedHiHat 2) )
          )))

--