{- | 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. The names here are from the enumeration at "server/plugins/UnaryOpUgens.cpp". The capitalisation is edited since these names become function names in rsc3. Names have a _ suffix if they conflict with Ugen names. > zip (map show [minBound :: Sc3_Unary_Op .. maxBound]) [0..] -} data Sc3_Unary_Op = OpNeg -- - | OpNot -- ! | OpIsNil | OpNotNil | OpBitNot | OpAbs -- 5 | OpAsFloat | OpAsInt | OpCeil -- 8 | OpFloor -- 9 | OpFrac -- 10 | OpSign -- 11 | OpSquared -- 12 | OpCubed -- 13 | OpSqrt -- 14 | OpExp -- 15 | OpRecip -- 16 | OpMidiCps -- 17 | OpCpsMidi -- 18 | OpMidiRatio -- 19 | OpRatioMidi -- 20 | OpDbAmp -- 21 | OpAmpDb -- 22 | OpOctCps | OpCpsOct | OpLog -- 25 (natural, base e) | OpLog2 -- 26 (base 2) | OpLog10 -- 27 (base 10) | OpSin -- 28 | OpCos -- 29 | OpTan -- 30 | OpArcSin | OpArcCos | OpArcTan | OpSinh | OpCosh -- 35 | OpTanh -- 36 | OpRand_ -- 37 ; Ugen | OpRand2 | OpLinRand_ -- 39 ; Ugen | OpBiLinRand -- 40 | OpSum3Rand | OpDistort -- 42 | OpSoftClip -- 43 | OpCoin | OpDigitValue -- 45 | OpSilence -- 46 | OpThru -- 47 | OpRectWindow -- 48 | OpHanWindow -- 49 | OpWelchWindow -- 50 | OpTriWindow | OpRamp_ -- 52 ; Ugen | OpScurve deriving (Eq, Show, Enum, Bounded, Read) -- | Enum name without Op prefix. sc3_unary_op_name :: Sc3_Unary_Op -> String sc3_unary_op_name = drop 2 . show {- | 'Base.parse_enum' with Op prefix. >>> Data.Maybe.mapMaybe (parse_unary Base.Cs) (words "Abs Rand") [OpAbs] -} parse_unary :: Base.Case_Rule -> String -> Maybe Sc3_Unary_Op parse_unary cr = Base.parse_enum cr . (++) "Op" {- | 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 sc3_unary_op_name [minBound .. maxBound]) [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 (sc3_unary_op_name e) (lookup e unary_sym_tbl) {- | Given name of unary operator derive index. >>> Data.Maybe.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 Base.Ci) (words "Abs MidiCps Neg") [True,True,True] >>> map (is_unary Base.Ci) (words "- rand") [False,False] >>> map (is_unary Base.Ci) (words "arctan atan") [True,False] -} is_unary :: Base.Case_Rule -> String -> Bool is_unary cr = isJust . unaryIndex cr -- * Binary {- | Enumeration of @Sc3@ unary operator Ugens. The names here are from the enumeration at "server/plugins/BinaryOpUgens.cpp". > zip (map show [minBound :: Sc3_Binary_Op .. maxBound]) [0..] -} data Sc3_Binary_Op = OpAdd -- 0 | OpSub -- 1 | OpMul -- 2 | OpIdiv -- 3 | OpFdiv -- 4 | OpMod -- 5 | OpEq -- 6 | OpNe -- 7 | OpLt -- 8 | OpGt -- 9 | OpLe -- 10 | OpGe -- 11 | OpMin -- 12 | OpMax -- 13 | OpBitAnd -- 14 | OpBitOr -- 15 | OpBitXor | OpLcm -- 17 | OpGcd -- 18 | OpRoundTo -- 19 -- i.e. roundTo: (renamed) | OpRoundUp -- 20 | OpTrunc -- 21 | OpAtan2 -- 22 | OpHypot -- 23 | OpHypotx | OpPow -- 25 | OpShiftLeft -- 26 | OpShiftRight -- 27 | OpUnsignedShift | OpFill | OpRing1 -- 30 | OpRing2 | OpRing3 | OpRing4 | OpDifSqr | OpSumSqr -- 35 | OpSqrSum | OpSqrDif | OpAbsDif -- 38 | OpThresh | OpAmClip -- 40 | OpScaleNeg -- 41 | OpClip2 -- 42 | OpExcess | OpFold2 -- 44 | OpWrap2 -- 45 | OpFirstArg -- 46 | OpRandRange -- 47 | OpExpRandRange -- 48 deriving (Eq, Show, Enum, Bounded, Read) -- | Enum name without Op prefix. sc3_binary_op_name :: Sc3_Binary_Op -> String sc3_binary_op_name = drop 2 . show -- | Table of operator names (non-symbolic) and indices. sc3_binary_op_tbl :: [(String, Int)] sc3_binary_op_tbl = zip (map sc3_binary_op_name [minBound .. maxBound]) [0 ..] {- | 'parse_enum' with Op prefix. >>> parse_binary Base.Ci "mul" Just OpMul -} parse_binary :: Base.Case_Rule -> String -> Maybe Sc3_Binary_Op parse_binary cr = Base.parse_enum cr . (++) "Op" -- | Table of symbolic names for standard binary operators. binary_sym_tbl :: [(Sc3_Binary_Op, String)] binary_sym_tbl = [ (OpAdd, "+") , (OpSub, "-") , (OpMul, "*") , (OpFdiv, "/") , (OpMod, "%") , (OpEq, "==") , (OpNe, "/=") -- or != , (OpLt, "<") , (OpGt, ">") , (OpLe, "<=") , (OpGe, ">=") , (OpBitAnd, ".&.") -- or & , (OpBitOr, ".|.") -- or | , (OpPow, "**") ] {- | 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 (sc3_binary_op_name x) (lookup x binary_sym_tbl) in zip (map f [minBound .. maxBound]) [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 (sc3_binary_op_name e) (lookup e binary_sym_tbl) {- | Given name of binary operator derive index. >>> Data.Maybe.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) [("+",Just 0),("-",Just 1),("*",Just 2),("/",Just 4),("%",Just 5),("==",Just 6),("/=",Just 7),("<",Just 8),(">",Just 9),("<=",Just 10),(">=",Just 11),(".&.",Just 14),(".|.",Just 15),("**",Just 25)] -} 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 Base.Ci) (words "== > % Trunc max") [True,True,True,True,True] -} 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 Base.Ci) (words "+ - sub abs max") [("BinaryOpUGen",Just 0),("BinaryOpUGen",Just 1),("BinaryOpUGen",Just 1),("UnaryOpUGen",Just 5),("BinaryOpUGen",Just 13)] >>> map (resolve_operator Base.Cs) (words "Abs Add Neg") [("UnaryOpUGen",Just 5),("BinaryOpUGen",Just 0),("UnaryOpUGen",Just 0)] -} 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) -- | Case-insensitive resolve_operator. resolve_operator_ci :: String -> (String, Maybe Int) resolve_operator_ci = resolve_operator Base.Ci -- * 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 instance EqE Integer instance EqE Int32 instance EqE Int64 instance EqE Float instance EqE Double -- | 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 instance UnaryOp Double -- | 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 = [ (OpAdd, (+)) , (OpSub, (-)) , (OpFdiv, (/)) , (OpIdiv, Math.sc3_idiv) , (OpMod, Math.sc3_mod) , (OpEq, Math.sc3_eq) , (OpNe, Math.sc3_neq) , (OpLt, Math.sc3_lt) , (OpLe, Math.sc3_lte) , (OpGt, Math.sc3_gt) , (OpGe, Math.sc3_gte) , (OpMin, min) , (OpMax, max) , (OpMul, (*)) , (OpPow, (**)) , (OpMin, min) , (OpMax, max) , (OpRoundTo, 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 = [ (OpNeg, negate) , (OpNot, \z -> if z > 0 then 0 else 1) , (OpAbs, abs) , (OpCeil, Math.sc3_ceiling) , (OpFloor, Math.sc3_floor) , (OpSquared, \z -> z * z) , (OpCubed, \z -> z * z * z) , (OpSqrt, sqrt) , (OpRecip, recip) , (OpMidiCps, Math.midi_to_cps) , (OpCpsMidi, Math.cps_to_midi) , (OpSin, sin) , (OpCos, cos) , (OpTan, 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