hsc3-plot-0.15: Sound/SC3/Plot.hs
-- | Simple-minded plotters for "Sound.SC3".
module Sound.SC3.Plot where
import Control.Monad {- base -}
import Data.Char {- base -}
import Data.List {- base -}
import Data.List.Split {- split -}
import Data.Ratio {- base -}
import qualified Data.Vector as V {- vector -}
import System.Directory {- directory -}
import System.FilePath {- filepath -}
import System.Process {- process -}
import Sound.OSC {- hosc -}
import Sound.SC3 {- hsc3 -}
import Sound.SC3.Lang.Collection.Vector {- hsc3-lang -}
import Sound.SC3.Plot.Histogram
-- * Math
-- | 'minimum' and 'maximum'.
minmax :: Ord t => [t] -> (t,t)
minmax l = (minimum l,maximum l)
linlin'' :: Fractional a => (a, a) -> (a, a) -> a -> a
linlin'' (l',r') (l,r) x = linlin' x l' r' l r
-- | 'linlin' of /z/.
normalise_seq :: (Fractional b, Ord b) => (b,b) -> (b,b) -> [b] -> [b]
normalise_seq d r = map (linlin'' d r)
-- | Variant that derives domain from 'minmax' of /z/.
normalise_seq' :: (Fractional b, Ord b) => (b,b) -> [b] -> [b]
normalise_seq' (l,r) z = normalise_seq (minmax z) (l,r) z
-- | /n/ '*' /n/.
square :: Num a => a -> a
square n = n * n
-- | Root mean square.
rms :: Floating a => [a] -> a
rms l =
let n = fromIntegral (length l)
s = sum (map square l)
in sqrt (s / n)
-- | 'rms' of 'chunksOf'.
resample_rms :: Floating b => Int -> [b] -> [b]
resample_rms m l =
let n = length l
c = n `div` m
s = chunksOf c l
in map rms s
-- * Data
-- | Class of plottable numbers. This is necessary to allow
-- 'Rational', the show instance of which cannot be read by @gnuplot@.
class Num n => PNum n where pshow :: n -> String
ratio_to_double :: Integral n => Ratio n -> Double
ratio_to_double = realToFrac
instance PNum Int where pshow = show
instance PNum Integer where pshow = show
instance PNum Float where pshow = show
instance PNum Double where pshow = show
instance Integral n => PNum (Ratio n) where pshow = show . ratio_to_double
-- * Attr
-- | Attibutes are generated given (i,j) where /i/ is the data set
-- (one indexed) and /j/ is the number of data sets.
type Attr = (Int,Int) -> String
attr_frac :: String -> Double -> Attr
attr_frac ty r (i,j) =
let n = fromIntegral (j - i) / fromIntegral j
n' = linlin'' (0,1) (0,r) n
in "with " ++ ty ++ " lt palette frac " ++ show n'
attr_frac_lines :: Double -> Attr
attr_frac_lines = attr_frac "lines"
-- * 1-dimensional
-- | List of /y/ values, at (implicit) equal /x/ increments.
type Table t = [t]
{- | Plot 'Table' data.
> plotTable [[0,2..12],[0..6],[0,4..12]]
> plotTable [map (\x -> x * cos (x / 20)) [-400 .. 800]]
-}
plotTable :: PNum t => [Table t] -> IO ()
plotTable = mkPlot (["set palette gray","unset colorbox"],"plot",attr_frac_lines 0.5) .
map (map return)
-- | Variant to plot singular 'Table'.
plotTable1 :: PNum t => Table t -> IO ()
plotTable1 = plotTable . return
{- | Variant of 'plotTable' where each value is drawn as an impulse.
> plotImpulses [[0,2..12],[0..6],[0,4..12]]
> plotImpulses [map (\x -> x * cos (x / 20)) [-400 .. 800]]
-}
plotImpulses :: PNum t => [[t]] -> IO ()
plotImpulses =
mkPlot (["set palette gray"
,"unset colorbox"],"plot",attr_frac "impulses" 0.5) .
map (map return)
-- | Variant that scales each plot lie within (0,1) and displaces them.
--
-- > plot_table_displace [[0,2..12],[0..6],[0,4..12]]
plot_table_displace :: (Enum t,Fractional t,Ord t,PNum t) => [[t]] -> IO ()
plot_table_displace t =
let rng = zip [0..] [1..]
t' = zipWith (normalise_seq (minmax (concat t))) rng t
in plotTable t'
-- | Variant that normalises each table separately.
--
-- > plot_table_displace_nrm [[0,2..12],[0..6],[0,4..12]]
plot_table_displace_nrm :: (Enum t,Fractional t,Ord t,PNum t) => [[t]] -> IO ()
plot_table_displace_nrm t =
let rng = zip [0..] [1..]
t' = zipWith normalise_seq' rng t
in plotTable t'
-- * 2-dimensional
-- | Cartesian (/x/,/y/) pair.
type P2 t = (t,t)
-- | List of 'P2'.
type Coord t = [P2 t]
{- | Plot set of 'Coord'.
> let {x = [-pi,-pi + 0.01 .. pi]
> ;f r t = (r t * cos t,r t * sin t)}
> in plot_p2 (attr_frac_lines 0.5)
> [zip (map cos x) (map sin x)
> ,zip (map cos x) (map (sin . (* 3)) x)
> ,map (f ((/ (2 * pi)) . (+ pi))) x]
-}
plot_p2 :: PNum t => Attr -> [Coord t] -> IO ()
plot_p2 attr = do
let f (x,y) = [x,y]
mkPlot (["set palette gray","unset colorbox"],"plot",attr) . map (map f)
-- | 'plot_p2' of @with lines@.
plot_p2_ln :: PNum t => [Coord t] -> IO ()
plot_p2_ln = plot_p2 (attr_frac_lines 0.5)
{- | 'plot_p2' of @with points@.
> let f a b c d (x,y) =
> (x ** 2 - y ** 2 + a * x + b * y
> ,2 * x * y + c * x + d * y)
> in plot_p2_pt [take 5000 (iterate (f 0.9 (-0.6013) 2.0 0.5) (0.1,0.0))]
-}
plot_p2_pt :: PNum t => [Coord t] -> IO ()
plot_p2_pt = plot_p2 (attr_frac "points pt 0" 0.5)
-- | 'plot_p2' of @with steps@.
--
-- > plot_p2_stp [[(0,0),(1,1),(2,0.5),(4,6),(5,1),(6,0.25)]]
plot_p2_stp :: PNum t => [Coord t] -> IO ()
plot_p2_stp = plot_p2 (attr_frac "steps" 0.5)
-- * 3-dimensional
-- | Cartesian (/x/,/y/,/z/) triple.
type P3 t = (t,t,t)
-- | List of 'P3'.
type Path t = [P3 t]
{- | Three-dimensional variant of 'plot_p2'.
> let {t' = [-pi,-pi + 0.01 .. pi]
> ;f0 n d = sin . (+) d . (*) n
> ;f1 t = (f0 1 (pi/2) t,f0 3 0 t,f0 5 0 t)
> ;e' = [0,0.005 .. pi]
> ;f2 a b r e t = ((a * t + r * sin e) * cos t
> ,(a * t + r * sin e) * sin t
> ,b * t + r * (1 - cos e))}
> in plot_p3 (const "with lines")
> [map f1 t'
> ,zipWith (f2 0.25 0.25 0.25) e' t']
-}
plot_p3 :: PNum t => Attr -> [Path t] -> IO ()
plot_p3 attr p = do
let f (x,y,z) = [x,y,z]
mkPlot ([],"splot",attr) (map (map f) p)
-- | 'plot_p3' of @with lines@.
plot_p3_ln :: PNum t => [Path t] -> IO ()
plot_p3_ln = plot_p3 (const "with lines")
-- | 'plot_p3' of @with points pt 0@.
plot_p3_pt :: PNum t => [Path t] -> IO ()
plot_p3_pt = plot_p3 (const "with points pt 0")
-- * Vector (Cartesian)
-- | List of ('P2','P2') vectors.
type Vc t = [(P2 t,P2 t)]
{- | Plot vectors given as (/p/,/q/).
> let {p = [((0,0),(2,1)),((2,2),(3,2)),((3,4),(4,1))]
> ;d = [1,2,3,2,3,2,1]
> ;x = 0 : scanl1 (+) d
> ;y = [6,4,5,3,7,2,8]
> ;f x y d = ((x,y),(x+d,y))}
> in plotVectors [p,zipWith3 f x y d]
-}
plotVectors :: (PNum t,Num t) => [Vc t] -> IO ()
plotVectors = do
let f ((x0,y0),(x1,y1)) = [x0,y0,x1-x0,y1-y0]
mkPlot ([],"plot",const "with vectors linewidth 1.25") . map (map f)
-- * Matrix
{- | Plot regular matrix data.
> plotMatrix ["set palette grey","unset colorbox"] [[1,3,2],[6,4,5],[8,9,7]]
> let d = [[1.00000000,1.00000000,1.00000000,0.73961496]
> ,[1.00000000,1.00000000,1.00000000,0.39490538]
> ,[0.53443549,0.31331112,0.90917979,0.58216201]
> ,[0.35888692,0.7361968,0.95389629,0.94283073]
> ,[0.85763543,0.1405479,0.78166569,0.43739318]
> ,[0.18519824,0.31907815,0.18394244,0.01633875]
> ,[0.0442339,0.33393132,0.77247883,0.79683943]
> ,[0.8472137,0.42471225,0.94257581,0.70417117]]
> in plotMatrix ["set palette color"] d
-}
plotMatrix :: (PNum t,Num t) => [String] -> [[t]] -> IO ()
plotMatrix opt =
mkPlot (opt,"plot",const "matrix with image") .
return
-- * Envelope
{- | Plot 'Envelope' data.
> import Sound.SC3
> plotEnvelope [envPerc 0.2 1
> ,envSine 1 0.75
> ,envADSR 0.4 0.4 0.8 0.9 1 (EnvNum (-4)) 0]
-}
plotEnvelope :: (PNum t,Ord t, Floating t, Enum t) => [Envelope t] -> IO ()
plotEnvelope = plotCoord . map (envelope_render 256)
-- * Buffer
-- | 'plotTable1' of 'b_fetch'.
--
-- > withSC3 (plot_buffer1 0)
plot_buffer1 :: (MonadIO m, DuplexOSC m) => Int -> m ()
plot_buffer1 k = do
d <- b_fetch 512 k
liftIO (plotTable1 d)
-- | 'plotTable1' of 'resample_rms' of 'b_fetch'.
--
-- > withSC3 (plot_buffer1_rms 512 0)
plot_buffer1_rms :: (MonadIO m, DuplexOSC m) => Int -> Int -> m ()
plot_buffer1_rms n k = do
d <- b_fetch 512 k
let d' = resample_rms n d
d'' = zipWith (*) (cycle [1,-1]) d'
liftIO (plotTable1 d'')
-- | 'plot_table1_resamp1' of 'b_fetch'.
--
-- > withSC3 (plot_buffer1_resamp1 512 0)
plot_buffer1_resamp1 :: (MonadIO m, DuplexOSC m) => Int -> Int -> m ()
plot_buffer1_resamp1 n k = do
d <- b_fetch 512 k
liftIO (plot_table1_resamp1 n (V.fromList d))
-- | 'plot_table_displace' of 'mapM' of 'b_fetch'.
plot_buffer :: (MonadIO m, DuplexOSC m) => [Int] -> m ()
plot_buffer k = do
d <- mapM (b_fetch 512) k
liftIO (plot_table_displace d)
-- | 'plot_table_displace_nrm' of 'mapM' of 'b_fetch'.
plot_buffer_nrm :: (MonadIO m, DuplexOSC m) => [Int] -> m ()
plot_buffer_nrm k = do
d <- mapM (b_fetch 512) k
liftIO (plot_table_displace_nrm d)
-- * Histogram
-- | Plot 'Histogram' data.
--
-- > plotHistogram [histogram 3 [0,0,1,2,2,2]
-- > ,histogram 9 [1,2,2,3,3,3,4,4,4,4]]
plotHistogram :: PNum t => [Histogram t] -> IO ()
plotHistogram =
let f (Histogram x y) = zip x y
in plot_p2_stp . map f
-- * Vector (Data Type)
-- | 'plotTable1' of 'V.toList'.
plot_table1_vector :: PNum t => V.Vector t -> IO ()
plot_table1_vector = plotTable1 . V.toList
-- | 'plot_table1_vector' of 'resamp1'.
--
-- > d <- withSC3 (b_fetch 512 0)
-- > plot_table1_resamp1 1024 (V.fromList d)
plot_table1_resamp1 :: (Enum t,RealFrac t,PNum t) => Int -> V.Vector t -> IO ()
plot_table1_resamp1 n t =
let t' = resamp1 n t
in plot_table1_vector t'
-- * Synonyms
-- | 'plot_p2_ln'
plotCoord :: PNum t => [Coord t] -> IO ()
plotCoord = plot_p2_ln
-- | 'plot_p2_pt'
plotPoints :: PNum t => [Coord t] -> IO ()
plotPoints = plot_p2_pt
-- | 'plot_p2_stp'
plotCoord_steps :: PNum t => [Coord t] -> IO ()
plotCoord_steps = plot_p2_stp
-- | 'plot_p3_ln'
plotPath :: PNum t => [Path t] -> IO ()
plotPath = plot_p3_ln
-- * Low-level
-- | Plot size (width,height).
type PlotSize = (Double,Double)
-- | Plot options.
data PlotOpt = PlotOpt {plotSize :: Maybe PlotSize
,plotXRange :: Maybe (Double,Double)
,plotYRange :: Maybe (Double,Double)
,plotDir :: FilePath
,plotName :: String
,plotTerminal :: String
,plotGnuplotOpt :: [String]}
-- | Default options for /X11/.
plotOptX11 :: PlotOpt
plotOptX11 = PlotOpt Nothing Nothing Nothing "/tmp" "mkPlot" "x11" ["-p"]
-- | Default options for /WXT/.
plotOptWXT :: PlotOpt
plotOptWXT = plotOptX11 {plotTerminal = "wxt"}
-- | Default options for /SVG/.
plotOptSVG :: PlotSize -> PlotOpt
plotOptSVG sz = PlotOpt (Just sz) Nothing Nothing "/tmp" "mkPlot" "svg" []
-- | Names for SVG terminal have character restrictions.
plotNameEnc :: PlotOpt -> String
plotNameEnc =
let f c = if isAlphaNum c then c else '_'
in map f . plotName
-- | Plot parameters, (/pre/,/cmd/,/attr/)
type PlotParam = ([String],String,Attr)
plotDataFile :: PlotOpt -> Int -> FilePath
plotDataFile opt k = plotDir opt </> plotName opt <.> show k <.> "data"
plotRCFile :: PlotOpt -> FilePath
plotRCFile opt = plotDir opt </> plotName opt <.> plotTerminal opt <.> "rc"
plotOutputFile :: PlotOpt -> FilePath
plotOutputFile opt = plotDir opt </> plotName opt <.> plotTerminal opt
writePlotRC :: PlotOpt -> PlotParam -> Int -> IO ()
writePlotRC opt (pre,cmnd,attr) n = do
let at = map (\i -> "'" ++ plotDataFile opt i ++ "' " ++ attr (i,n)) [1 .. n]
x_range = case plotXRange opt of
Just (x0,x1) -> concat ["set xrange [",show x0,":",show x1,"]"]
Nothing -> ""
y_range = case plotYRange opt of
Just (y0,y1) -> concat ["set yrange [",show y0,":",show y1,"]"]
Nothing -> ""
term = case plotSize opt of
Just (w,h) -> concat ["set terminal \""
,plotTerminal opt
,"\" name \""
,plotNameEnc opt
,"\" size ",show w,",", show h]
Nothing -> concat ["set terminal \"",plotTerminal opt,"\""]
output = case plotTerminal opt of
"svg" -> concat ["set output '",plotOutputFile opt,"'"]
_ -> ""
pre' = [term
,output
,"set tics font \"cmr10, 10\""
,x_range
,y_range
,"unset key"] ++ pre
_ <- writeFile (plotRCFile opt) (unlines pre' ++ cmnd ++ " " ++ intercalate "," at)
return ()
writePlotData :: (PNum t,Num t) => PlotOpt -> [[[t]]] -> IO ()
writePlotData opt d = do
let f = unwords . map pshow
zipWithM_ writeFile (map (plotDataFile opt) [1 .. length d]) (map (unlines . map f) d)
-- | Plotter given 'PlotOpt' and 'PlotParam'.
mkPlotOpt :: (PNum t,Num t) => PlotOpt -> PlotParam -> [[[t]]] -> IO ()
mkPlotOpt opt param dat = do
writePlotRC opt param (length dat)
writePlotData opt dat
_ <- rawSystem "gnuplot" (plotGnuplotOpt opt ++ [plotRCFile opt])
return ()
-- | 'mkPlotOpt' with 'plotOptX11'.
mkPlotX11 :: (PNum t,Num t) => PlotParam -> [[[t]]] -> IO ()
mkPlotX11 param dat = do
tmp <- getTemporaryDirectory
mkPlotOpt (plotOptX11 {plotDir = tmp}) param dat
-- | 'mkPlotOpt' with 'plotOptSVG'.
mkPlotSVG :: (Num t, PNum t) => PlotSize -> PlotParam -> [[[t]]] -> IO ()
mkPlotSVG sz = mkPlotOpt (plotOptSVG sz)
-- | WXT and SVG.
mkPlot :: (PNum t,Num t) => PlotParam -> [[[t]]] -> IO ()
mkPlot param dat = do
let n = length dat
svg_opt = plotOptSVG (1200,400)
writePlotRC plotOptWXT param n
writePlotRC svg_opt param n
writePlotData plotOptWXT dat
_ <- rawSystem "gnuplot" (plotGnuplotOpt plotOptWXT ++ [plotRCFile plotOptWXT])
_ <- rawSystem "gnuplot" (plotGnuplotOpt svg_opt ++ [plotRCFile svg_opt])
return ()