hsc3-0.19.1: Sound/SC3/Common/Math/Operator.hs
{- | Non-standard mathematical enumerations, classes and base instances.
Enumerations of the unary and binary math unit generators.
Names that conflict with existing names have a @_@ suffix.
The Eq and Ord classes in the Prelude require Bool, hence EqE and OrdE.
True is 1.0, False is 0.0
The RealFrac class requires Integral results, hence RealFracE.
-}
module Sound.SC3.Common.Math.Operator where
import Control.Monad {- base -}
import qualified Data.Fixed as F {- base -}
import Data.Int {- base -}
import Data.Maybe {- base -}
import qualified Sound.SC3.Common.Base as Base {- hsc3 -}
import qualified Sound.SC3.Common.Math as Math {- hsc3 -}
-- * Unary
-- | Enumeration of @SC3@ unary operator UGens.
--
-- > zip (map show [minBound :: SC3_Unary_Op .. maxBound]) [0..]
data SC3_Unary_Op
= Neg -- -
| Not -- !
| IsNil
| NotNil
| BitNot
| Abs -- 5
| AsFloat
| AsInt
| Ceil -- 8
| Floor -- 9
| Frac -- 10
| Sign
| Squared
| Cubed
| Sqrt
| Exp -- 15
| Recip -- 16
| MIDICPS -- 17
| CPSMIDI
| MIDIRatio
| RatioMIDI -- 20
| DbAmp
| AmpDb
| OctCPS
| CPSOct
| Log -- 25
| Log2
| Log10
| Sin -- 28
| Cos
| Tan -- 30
| ArcSin
| ArcCos
| ArcTan
| SinH
| CosH -- 35
| TanH -- 36
| Rand_ -- UGen
| Rand2
| LinRand_ -- UGen
| BiLinRand -- 40
| Sum3Rand
| Distort -- 42
| SoftClip
| Coin
| DigitValue
| Silence
| Thru
| RectWindow
| HanWindow
| WelchWindow
| TriWindow
| Ramp_ -- UGen
| SCurve
deriving (Eq,Show,Enum,Bounded,Read)
-- | Type-specialised 'Base.parse_enum'.
--
-- > mapMaybe (parse_unary Base.CS) (words "Abs Rand_") == [Abs,Rand_]
parse_unary :: Base.Case_Rule -> String -> Maybe SC3_Unary_Op
parse_unary = Base.parse_enum
-- | Table of operator names (non-symbolic) and indices.
--
-- > map fst sc3_unary_op_tbl
sc3_unary_op_tbl :: [(String,Int)]
sc3_unary_op_tbl = zip (map show [Neg .. SCurve]) [0..]
-- | Table of symbolic names for standard unary operators.
unary_sym_tbl :: [(SC3_Unary_Op,String)]
unary_sym_tbl = [] -- (Neg,"-"),(Not,"!")
-- | Lookup possibly symbolic name for standard unary operators.
unaryName :: Int -> String
unaryName n =
let e = toEnum n
in fromMaybe (show e) (lookup e unary_sym_tbl)
-- | Given name of unary operator derive index.
--
-- > mapMaybe (unaryIndex Base.CI) (words "abs CUBED midicps NEG") == [5,13,17,0]
-- > unaryIndex Base.CS "SinOsc" == Nothing
unaryIndex :: Base.Case_Rule -> String -> Maybe Int
unaryIndex cr nm =
let ix = Base.rlookup_str cr nm unary_sym_tbl
ix' = parse_unary cr nm
in fmap fromEnum (mplus ix' ix)
-- | 'isJust' of 'unaryIndex'.
--
-- > map (is_unary CI) (words "ABS MIDICPS NEG")
-- > map (is_unary CI) (words "- RAND")
-- > map (is_unary CI) (words "arctan atan")
is_unary :: Base.Case_Rule -> String -> Bool
is_unary cr = isJust . unaryIndex cr
-- * Binary
-- | Enumeration of @SC3@ unary operator UGens.
--
-- > zip (map show [minBound :: SC3_Binary_Op .. maxBound]) [0..]
data SC3_Binary_Op
= Add -- 0
| Sub -- 1
| Mul -- 2
| IDiv
| FDiv -- 4
| Mod -- 5
| EQ_ -- 6
| NE -- 7
| LT_ -- 8
| GT_ -- 9
| LE -- 10
| GE -- 11
| Min -- 12
| Max -- 13
| BitAnd -- 14
| BitOr -- 15
| BitXor
| LCM -- 17
| GCD -- 18
| Round -- 19
| RoundUp -- 20
| Trunc
| Atan2
| Hypot
| Hypotx
| Pow -- 25
| ShiftLeft -- 26
| ShiftRight -- 27
| UnsignedShift
| Fill
| Ring1 -- 30
| Ring2
| Ring3
| Ring4
| DifSqr
| SumSqr -- 35
| SqrSum
| SqrDif
| AbsDif
| Thresh
| AMClip -- 40
| ScaleNeg
| Clip2 -- 42
| Excess
| Fold2
| Wrap2
| FirstArg
| RandRange
| ExpRandRange
deriving (Eq,Show,Enum,Bounded,Read)
-- | Table of operator names (non-symbolic) and indices.
sc3_binary_op_tbl :: [(String,Int)]
sc3_binary_op_tbl = zip (map show [Add .. ExpRandRange]) [0..]
-- | Type-specialised 'parse_enum'.
parse_binary :: Base.Case_Rule -> String -> Maybe SC3_Binary_Op
parse_binary = Base.parse_enum
-- | Table of symbolic names for standard binary operators.
binary_sym_tbl :: [(SC3_Binary_Op,String)]
binary_sym_tbl =
[(Add,"+")
,(Sub,"-")
,(Mul,"*")
,(FDiv,"/")
,(Mod,"%")
,(EQ_,"==")
,(NE,"/=") -- or !=
,(LT_,"<")
,(GT_,">")
,(LE,"<=")
,(GE,">=")
,(BitAnd,".&.") -- or &
,(BitOr,".|.") -- or |
,(Pow,"**")]
-- | Table of operator names (non-symbolic) and indices.
--
-- > map fst sc3_binary_op_sym_tbl
sc3_binary_op_sym_tbl :: [(String,Int)]
sc3_binary_op_sym_tbl =
let f x = fromMaybe (show x) (lookup x binary_sym_tbl)
in zip (map f [Add .. ExpRandRange]) [0..]
-- | Lookup possibly symbolic name for standard binary operators.
--
-- > map binaryName [1,2,8,12] == ["-","*","<","Min"]
binaryName :: Int -> String
binaryName n =
let e = toEnum n
in fromMaybe (show e) (lookup e binary_sym_tbl)
-- | Given name of binary operator derive index.
--
-- > mapMaybe (binaryIndex Base.CI) (words "* MUL RING1 +") == [2,2,30,0]
-- > binaryIndex Base.CI "SINOSC" == Nothing
-- > map (\x -> (x,binaryIndex Base.CI x)) (map snd binary_sym_tbl)
binaryIndex :: Base.Case_Rule -> String -> Maybe Int
binaryIndex cr nm =
let ix = Base.rlookup_str cr nm binary_sym_tbl
ix' = parse_binary cr nm
in fmap fromEnum (mplus ix' ix)
-- | 'isJust' of 'binaryIndex'.
--
-- > map (is_binary CI) (words "== > % TRUNC MAX")
is_binary :: Base.Case_Rule -> String -> Bool
is_binary cr = isJust . binaryIndex cr
-- * Operator
-- | Lookup operator name for operator UGens, else UGen name.
ugen_operator_name :: String -> Int -> Maybe String
ugen_operator_name nm n =
case nm of
"UnaryOpUGen" -> Just (unaryName n)
"BinaryOpUGen" -> Just (binaryName n)
_ -> Nothing
-- | Order of lookup: binary then unary.
--
-- > map (resolve_operator Sound.SC3.Common.Base.CI) (words "+ - ADD SUB NEG")
resolve_operator :: Base.Case_Rule -> String -> (String,Maybe Int)
resolve_operator cr nm =
case binaryIndex cr nm of
Just sp -> ("BinaryOpUGen",Just sp)
Nothing -> case unaryIndex cr nm of
Just sp -> ("UnaryOpUGen",Just sp)
_ -> (nm,Nothing)
-- * Classes
-- | Variant on 'Eq' class, result is of the same type as the values compared.
class (Eq a,Num a) => EqE a where
equal_to :: a -> a -> a
equal_to = Math.sc3_eq
not_equal_to :: a -> a -> a
not_equal_to = Math.sc3_neq
instance EqE Int where
instance EqE Integer where
instance EqE Int32 where
instance EqE Int64 where
instance EqE Float where
instance EqE Double where
-- | Variant on Ord class, result is of the same type as the values compared.
class (Ord a,Num a) => OrdE a where
less_than :: a -> a -> a
less_than = Math.sc3_lt
less_than_or_equal_to :: a -> a -> a
less_than_or_equal_to = Math.sc3_lte
greater_than :: a -> a -> a
greater_than = Math.sc3_gt
greater_than_or_equal_to :: a -> a -> a
greater_than_or_equal_to = Math.sc3_gte
instance OrdE Int
instance OrdE Integer
instance OrdE Int32
instance OrdE Int64
instance OrdE Float
instance OrdE Double
-- | Variant of 'RealFrac' with non 'Integral' results.
class RealFrac a => RealFracE a where
properFractionE :: a -> (a,a)
properFractionE = Math.sc3_properFraction
truncateE :: a -> a
truncateE = Math.sc3_truncate
roundE :: a -> a
roundE = Math.sc3_round
ceilingE :: a -> a
ceilingE = Math.sc3_ceiling
floorE :: a -> a
floorE = Math.sc3_floor
instance RealFracE Float
instance RealFracE Double
-- | Unary operator class.
--
-- > map (floor . (* 1e4) . dbAmp) [-90,-60,-30,0] == [0,10,316,10000]
class (Floating a, Ord a) => UnaryOp a where
ampDb :: a -> a
ampDb = Math.amp_to_db
asFloat :: a -> a
asFloat = error "asFloat"
asInt :: a -> a
asInt = error "asInt"
cpsMIDI :: a -> a
cpsMIDI = Math.cps_to_midi
cpsOct :: a -> a
cpsOct = Math.cps_to_oct
cubed :: a -> a
cubed n = n * n * n
dbAmp :: a -> a
dbAmp = Math.db_to_amp
distort :: a -> a
distort = Math.sc3_distort
frac :: a -> a
frac = error "frac"
isNil :: a -> a
isNil a = if a == 0.0 then 0.0 else 1.0
log10 :: a -> a
log10 = logBase 10
log2 :: a -> a
log2 = logBase 2
midiCPS :: a -> a
midiCPS = Math.midi_to_cps
midiRatio :: a -> a
midiRatio = Math.midi_to_ratio
notE :: a -> a
notE a = if a > 0.0 then 0.0 else 1.0
notNil :: a -> a
notNil a = if a /= 0.0 then 0.0 else 1.0
octCPS :: a -> a
octCPS = Math.oct_to_cps
ramp_ :: a -> a
ramp_ _ = error "ramp_"
ratioMIDI :: a -> a
ratioMIDI = Math.ratio_to_midi
softClip :: a -> a
softClip = Math.sc3_softclip
squared :: a -> a
squared = \z -> z * z
instance UnaryOp Float where
instance UnaryOp Double where
-- | SC3_Binary_Op operator class.
class (Floating a,RealFrac a, Ord a) => BinaryOp a where
absDif :: a -> a -> a
absDif a b = abs (a - b)
amClip :: a -> a -> a
amClip a b = if b <= 0 then 0 else a * b
atan2E :: a -> a -> a
atan2E a b = atan (b/a)
clip2 :: a -> a -> a
clip2 a b = Math.sc3_clip a (-b) b
difSqr :: a -> a -> a
difSqr = Math.sc3_dif_sqr
excess :: a -> a -> a
excess a b = a - Math.sc3_clip a (-b) b
exprandRange :: a -> a -> a
exprandRange = error "exprandRange"
fill :: a -> a -> a
fill = error "fill"
firstArg :: a -> a -> a
firstArg a _ = a
fold2 :: a -> a -> a
fold2 a b = Math.sc3_fold a (-b) b
gcdE :: a -> a -> a
gcdE = Math.sc3_gcd
hypot :: a -> a -> a
hypot = Math.sc3_hypot
hypotx :: a -> a -> a
hypotx = Math.sc3_hypotx
iDiv :: a -> a -> a
iDiv = Math.sc3_idiv
lcmE :: a -> a -> a
lcmE = Math.sc3_lcm
modE :: a -> a -> a
modE = Math.sc3_mod
randRange :: a -> a -> a
randRange = error "randRange"
ring1 :: a -> a -> a
ring1 a b = a * b + a
ring2 :: a -> a -> a
ring2 a b = a * b + a + b
ring3 :: a -> a -> a
ring3 a b = a * a * b
ring4 :: a -> a -> a
ring4 a b = a * a * b - a * b * b
roundUp :: a -> a -> a
roundUp = error "roundUp"
scaleNeg :: a -> a -> a
scaleNeg a b = (abs a - a) * b' + a where b' = 0.5 * b + 0.5
sqrDif :: a -> a -> a
sqrDif a b = (a-b) * (a-b)
sqrSum :: a -> a -> a
sqrSum a b = (a+b) * (a+b)
sumSqr :: a -> a -> a
sumSqr a b = (a*a) + (b*b)
thresh :: a -> a -> a
thresh a b = if a < b then 0 else a
trunc :: a -> a -> a
trunc = error "trunc"
wrap2 :: a -> a -> a
wrap2 = error "wrap2"
instance BinaryOp Float where
fold2 a b = Math.sc3_fold a (-b) b
modE = F.mod'
roundUp a b = if b == 0 then a else ceilingE (a/b + 0.5) * b
wrap2 a b = Math.sc3_wrap_ni (-b) b a
instance BinaryOp Double where
fold2 a b = Math.sc3_fold a (-b) b
modE = F.mod'
roundUp a b = if b == 0 then a else ceilingE (a/b + 0.5) * b
wrap2 a b = Math.sc3_wrap_ni (-b) b a
-- * Infix
(==**) :: EqE a => a -> a -> a
(==**) = equal_to
(/=**) :: EqE a => a -> a -> a
(/=**) = not_equal_to
(<**) :: OrdE a => a -> a -> a
(<**) = less_than
(<=**) :: OrdE a => a -> a -> a
(<=**) = less_than_or_equal_to
(>**) :: OrdE a => a -> a -> a
(>**) = greater_than
(>=**) :: OrdE a => a -> a -> a
(>=**) = greater_than_or_equal_to
-- * Tables
-- | Association table for 'SC3_Binary_Op' to haskell function implementing operator.
binop_hs_tbl :: (Real n,Floating n,RealFrac n) => [(SC3_Binary_Op,n -> n -> n)]
binop_hs_tbl =
[(Add,(+))
,(Sub,(-))
,(FDiv,(/))
,(IDiv,Math.sc3_idiv)
,(Mod,Math.sc3_mod)
,(EQ_,Math.sc3_eq)
,(NE,Math.sc3_neq)
,(LT_,Math.sc3_lt)
,(LE,Math.sc3_lte)
,(GT_,Math.sc3_gt)
,(GE,Math.sc3_gte)
,(Min,min)
,(Max,max)
,(Mul,(*))
,(Pow,(**))
,(Min,min)
,(Max,max)
,(Round,Math.sc3_round_to)]
-- | 'lookup' 'binop_hs_tbl' via 'toEnum'.
binop_special_hs :: (RealFrac n,Floating n) => Int -> Maybe (n -> n -> n)
binop_special_hs z = lookup (toEnum z) binop_hs_tbl
-- | Association table for 'Unary' to haskell function implementing operator.
uop_hs_tbl :: (RealFrac n,Floating n) => [(SC3_Unary_Op,n -> n)]
uop_hs_tbl =
[(Neg,negate)
,(Not,\z -> if z > 0 then 0 else 1)
,(Abs,abs)
,(Ceil,Math.sc3_ceiling)
,(Floor,Math.sc3_floor)
,(Squared,\z -> z * z)
,(Cubed,\z -> z * z * z)
,(Sqrt,sqrt)
,(Recip,recip)
,(MIDICPS,Math.midi_to_cps)
,(CPSMIDI,Math.cps_to_midi)
,(Sin,sin)
,(Cos,cos)
,(Tan,tan)]
-- | 'lookup' 'uop_hs_tbl' via 'toEnum'.
uop_special_hs :: (RealFrac n,Floating n) => Int -> Maybe (n -> n)
uop_special_hs z = lookup (toEnum z) uop_hs_tbl