-- | Sequencer type diagram.
module Music.Theory.Diagram.Sequencer where

import Data.Char {- base -}
import System.FilePath {- filepath -}
import System.Process {- process -}
import Text.Printf {- base -}

import Music.Theory.Math (R) {- hmt -}
import qualified Music.Theory.Dynamic_Mark as T {- hmt -}
import qualified Music.Theory.Time.Seq as T {- hmt -}

-- | Point
type P2 = (R,R)

-- | Greyscale colour.
type Grey = R

-- | Coloured rectangle as (lower-left,upper-right,greyscale).
type C_Rect = (P2,P2,Grey)

-- | 'C_Rect' with identifier.
type K_Rect = (Int,C_Rect)

-- | Gnuplot string for 'K_Rect'.
k_rect_gnuplot :: K_Rect -> String
k_rect_gnuplot (i,((x0,y0),(x1,y1),c)) =
    let fmt = "set object %d rect from %f,%f to %f,%f fc rgbcolor \"#%02x%02x%02x\""
        c' = floor (c * 255) :: Int
    in printf fmt i x0 y0 x1 y1 c' c' c'

-- | Sequencer plot options, (image-size,x-range,y-range).  For
-- standard midi data x-range is the time window and y-range is the
-- gamut.
type Seq_Plot_Opt = ((Int,Int),(R,R),(R,R))

-- | Sane defaults for size and gamut.
default_seq_plot_opt :: (R,R) -> Seq_Plot_Opt
default_seq_plot_opt x = ((1200,400),x,(21,108))

-- | Add identifiers.
to_k_rect :: [C_Rect] -> [K_Rect]
to_k_rect = zip [1..]

-- | Names for SVG terminal have character restrictions.
clean_name :: String -> String
clean_name =
    let f c = if isAlphaNum c then c else '_'
    in map f

sequencer_plot_rect :: Seq_Plot_Opt -> FilePath -> String -> [C_Rect] -> IO ()
sequencer_plot_rect ((w,h),(x0,x1),(y0,y1)) dir nm sq = do
  let nm_plot = dir </> nm <.> "plot"
      nm_svg = dir </> nm <.> "svg"
      x_range = concat ["[",show x0,":",show x1,"]"]
      y_range = concat ["[",show y0,":",show y1,"]"]
      pre = [concat ["set terminal svg name \"",clean_name nm,"\" size ",show w,",", show h]
            ,"set output '" ++ nm_svg ++ "'"
            ,"set tics font \"cmr10, 10\""
            ,"unset key"
            ,concat ["set xrange ",x_range]
            ,concat ["set yrange ",y_range]
            ,"set bars 0"]
      post = ["plot \"/dev/null\" with xyerrorbars lc rgbcolor \"black\""]
  writeFile nm_plot (unlines (pre ++ map k_rect_gnuplot (to_k_rect sq) ++ post))
  _ <- system ("gnuplot " ++ nm_plot)
  return ()

-- * MIDI

-- | Linear amplitude to grey scale (0 = white, 1 = black).
--
-- > map (floor . (* 255) . amp_to_grey (-60)) [0,0.25,0.5,0.75,1] == [255,51,25,10,0]
amp_to_grey :: R -> R -> R
amp_to_grey z am =
    let db = max (T.amp_db am) z
        z' = abs z
    in 1 - ((db + z') / z')

-- | Midi velocity number to linear amplitude.
vel_to_amp :: Int -> R
vel_to_amp vel = fromIntegral vel / 127

-- | Midi velocity number to grey scale.
vel_to_grey :: R -> Int -> R
vel_to_grey z = amp_to_grey z . vel_to_amp

-- | Midi sequence data.
type Sequencer_Midi n = T.Wseq R (n,n)

-- | Convert 'Sequencer_Midi' node to 'C_Rect'.
sequencer_midi_to_rect :: Real n => ((R,R),(n,n)) -> C_Rect
sequencer_midi_to_rect ((st,du),(mnn,vel)) =
    let x0 = st
        x1 = st + du
        y0 = realToFrac mnn
        y1 = y0 + 1
        c = vel_to_grey (-60) (floor (realToFrac vel))
    in ((x0,y0),(x1,y1),c)

-- | Plot 'Sequencer_Midi'.
sequencer_plot_midi :: Real n => Seq_Plot_Opt -> FilePath -> String -> Sequencer_Midi n -> IO ()
sequencer_plot_midi opt dir nm = sequencer_plot_rect opt dir nm . map sequencer_midi_to_rect