packages feed

temporal-music-notation-0.1: src/Temporal/Music/Notation/Volume.hs

{-# LANGUAGE 
        FlexibleInstances, 
        TypeSynonymInstances, 
        Rank2Types #-}

-- | representing volume
module Temporal.Music.Notation.Volume(
	-- * Types
    -- | Main datatypes are 'Amplitude', 'Diapason' and 'Level'.
    -- 'Volume' consists of 'Diapason' and 'Level'. Every
    -- 'Volume' can be converted to 'Amplitude' (see 'absVolume').
    -- 'Amplitude' is linear units and 'Level' is logarithmic units,
    -- or decibels. 'Diapason' defines lower and upper bound for volume level
    -- in amplitude linear units. 
	Amplitude,
    Diapason, Accent,
    Volume(..), Level(..),
    volumeNum, levelNum,
    level, mediumLevel,
	-- * Transformers
	VolFunctor(..), LevelFunctor(..),
	setDiapason, setLevel, setAccent,
    accent,
    loud, quiet, louder, quieter,
    dynamic, dynamicRel,
    -- * Rendering
    amplitude, unsafeAmplitude, 
    absVolume, unsafeAbsVolume
)
where

import Data.Function(on)
import TypeLevel.NaturalNumber
import Temporal.Music.Notation.Score(Score, Time, tmapRel, linseg)

-- | Linear volume units
type Amplitude = Double

-- | Volume lower and upper bounds. Lower bound must be positive
-- and upper bound must exceed lower bound.
type Diapason = (Amplitude, Amplitude)

-- | 'Accent' defines values between 'Level' values on logarithmic 
-- scale. 1 'Accent' == 1 'Level' 's step.
type Accent   = Double

-- | 'Volume' consists of 'Diapason' and 'Level'.
data NaturalNumber n => Volume n = Volume 
        { volumeDiapason :: Diapason
        , volumeLevel    :: Level n
        } deriving (Show, Eq)

class VolFunctor a where
	mapVol :: (forall n . NaturalNumber n => Volume n -> Volume n) -> (a -> a)

instance NaturalNumber n => VolFunctor (Volume n) where
	mapVol f = f

instance VolFunctor a => VolFunctor (Score a) where
	mapVol f = fmap (mapVol f)

instance NaturalNumber n => LevelFunctor (Volume n) where
    mapLevel f = \(Volume d l) -> Volume d $ f l

-- | setDiapason
setDiapason :: VolFunctor a => (Amplitude, Amplitude) -> a -> a
setDiapason x = mapVol $ \(Volume _ l) -> Volume x l
--------------------------------------------------
--------------------------------------------------
-- Level

-- | 'Level' defines number of equally spaced stamps on
-- logarithmic scale (steps), and degree of diversion 
-- from the stamps (accents).
data NaturalNumber n => Level n = Level
        { levelAccent  :: Accent
        , levelStep    :: Int
        } deriving (Show, Eq)

-- | number of levels in 'Volume' scale
volumeNum :: NaturalNumber n => Volume n -> Int
volumeNum = phantomNum

-- | number of levels in 'Level' scale
levelNum :: NaturalNumber n => Level n -> Int
levelNum = phantomNum

phantomNum :: NaturalNumber n => f n -> Int
phantomNum = naturalNumberAsInt . num
    where num :: f n -> n
          num = const undefined

-------------------------------------------
-- constructors

-- | 'level' constructs 'Level' from 'Int'. 'Accent' is set to zero.
-- If input exceeds 'levelNum' then result is set to 'levelNum',
-- if input is negative then result set is to zero.
level :: NaturalNumber n => Int -> Level n
level x = res
    where n   = levelNum res
          res = Level 0 $ sat 0 n x

-------------------------------------------
-- instances

instance NaturalNumber n => Enum (Level n) where
    toEnum   = level
    fromEnum = levelStep
   
instance NaturalNumber n => Ord (Level n) where
    compare = compare `on` (\(Level a s) -> fromIntegral s + a)

instance NaturalNumber n => Num (Level n) where
    (+) = liftBi (+) (+)
    (-) = liftBi (-) (-)
    (*) = liftBi (*) (*)
    abs = id
    signum x
        | x == level 0 = 0
        | otherwise    = 1
    fromInteger = level . fromInteger

--------------------------------------
-- transformers

class LevelFunctor a where
    mapLevel :: (forall n . NaturalNumber n => Level n -> Level n) -> (a -> a)

instance NaturalNumber n => LevelFunctor (Level n) where
    mapLevel f = f

instance LevelFunctor a => LevelFunctor (Score a) where
	mapLevel f = fmap (mapLevel f)

-- | 'accent' increases 'Accent' value by some degree
accent :: LevelFunctor a => Accent -> a -> a
accent d = mapLevel $ \(Level a s) -> Level (a+d) s

-- | 'setAccent' sets 'Accent' value to given input
setAccent :: LevelFunctor a => Accent -> a -> a
setAccent d = mapLevel $ \(Level _ s) -> Level d s

-- | 'setLevel' sets 'levelStep' to given input 
setLevel :: LevelFunctor a => Int -> a -> a
setLevel n = mapLevel 
    (\l@(Level a _) -> Level a $ sat 0 (levelNum l) n)

-- | Input becomes one step louder
loud :: LevelFunctor a => a -> a
loud = louder 1

-- | Input becomes one step quieter
quiet :: LevelFunctor a => a -> a
quiet = quieter 1

-- | Input becomes given number of steps quieter
quieter :: LevelFunctor a => Int -> a -> a
quieter n = louder (-n)

-- | Input becomes given number of steps louder
louder :: LevelFunctor a => Int -> a -> a
louder n 
    | n > 0 = mapLevel ( + level n)
    | n < 0 = mapLevel (\x -> x - level n)


-- | Medium level
mediumLevel :: NaturalNumber n => Level n
mediumLevel = res
    where res = level $ round $ fromIntegral n / 2
          n   = levelNum res
          
-- | Accent that depends on time of note
dynamic :: LevelFunctor a => (Time -> Accent) -> Score a -> Score a
dynamic f = tmapRel $ \t -> accent (f t)

-- | Linear relative 'dynamic' function. Function is defined by list of
-- its values equaly spaced along time axis. For example
-- list [0, 1, 0] defines rise then decay lineary along full 'Score' 's
-- input duration. Time intervals of 
-- rise and decay segments are equal to 'dur' /2. And list [0, 1, 0.5, 0] 
-- defines
-- rise and decay again but here decay segment is twice longer then
-- rise segment.
dynamicRel :: LevelFunctor a => [Accent] -> Score a -> Score a
dynamicRel xs = dynamic $ linseg $ init $ f =<< xs
    where dt  = recip $ fromIntegral $ length xs
          f x = [x, dt]

--------------------------------------------------
-- rendering

-- | converts volume to amplitude with 'amplitude' function
absVolume :: NaturalNumber n => Volume n -> Amplitude
absVolume (Volume d l) = amplitude d l

-- | converts volume to amplitude with 'unsafeAmplitude' function
unsafeAbsVolume :: NaturalNumber n => Volume n -> Amplitude
unsafeAbsVolume (Volume d l) = unsafeAmplitude d l

-- | converts equally spaced between lower and upper diapason bounds
-- 'Level' values to amplitudes. 
-- Here resulting amplitude value lies within 'Diapason' interval.
-- All outsiders are placed inside interval with saturation.
amplitude :: NaturalNumber n => Diapason -> Level n -> Amplitude
amplitude d l = amplitudeGen (sat 0 $ fromIntegral $ levelNum l) d l

-- | unsafe analog of 'amplitude' function. Here result can go
-- beyond limits of 'Diapason' interval.
unsafeAmplitude :: NaturalNumber n => Diapason -> Level n -> Amplitude
unsafeAmplitude = amplitudeGen id

amplitudeGen :: NaturalNumber n 
    => (Double -> Double)
    -> Diapason -> Level n -> Amplitude
amplitudeGen bound (low, high) l@(Level a s) = (low * ) $ (high / low) ** x
    where n = fromIntegral $ levelNum l
          x = ( / n) $ bound $ fromIntegral s + a


--------------------------------------
-- level manipulation

liftBi :: NaturalNumber n
    => (Accent  -> Accent  -> Accent)
    -> (Int     -> Int    ->  Int )
    -> (Level n -> Level n -> Level n)
liftBi f g l@(Level a s) (Level a' s') =      
    Level (a `f` a') (sat 0 (levelNum l) $ s `g` s')

sat :: Ord a => a -> a -> a -> a
sat low high x
    | x < low   = low
    | x > high  = high
    | otherwise = x