{-# LANGUAGE NoImplicitPrelude #-}
module Synthesizer.Plain.ToneModulation (
Cell,
interpolateCell,
Prototype,
makePrototype,
sampledToneCell,
oscillatorCells,
seekCell,
oscillatorSuffixes,
freqsToPhases,
dropFrac,
dropRem,
propDropFrac,
propDropRem,
oscillatorCoords,
integrateFractional,
limitRelativeShapes,
limitMinRelativeValues,
limitMaxRelativeValues,
limitMaxRelativeValuesNonNeg,
) where
import qualified Synthesizer.Basic.ToneModulation as ToneMod
import qualified Synthesizer.Basic.Phase as Phase
import qualified Synthesizer.Plain.Signal as Sig
import qualified Synthesizer.Plain.Interpolation as Interpolation
import Synthesizer.Interpolation (Margin, )
import Control.Monad (guard, )
import qualified Data.List as List
import qualified Data.List.HT as ListHT
import qualified Data.List.Match as ListMatch
import Data.Array (Array, (!), listArray, )
import Data.Tuple.HT (mapPair, mapSnd, forcePair, )
import Data.Ord.HT (limit, )
import qualified Algebra.RealField as RealField
import qualified Algebra.RealRing as RealRing
import qualified Algebra.Ring as Ring
import qualified Algebra.Additive as Additive
import qualified Number.NonNegative as NonNeg
import qualified Number.NonNegativeChunky as Chunky
import NumericPrelude.Numeric
import NumericPrelude.Base
type Cell y = Sig.T (Sig.T y)
interpolateCell ::
Interpolation.T a y ->
Interpolation.T b y ->
(a, b) ->
Cell y -> y
interpolateCell ipLeap ipStep (qLeap,qStep) =
Interpolation.func ipStep qStep .
map (Interpolation.func ipLeap qLeap)
data Prototype t y =
Prototype {
protoMarginLeap,
protoMarginStep :: Margin,
protoIpOffset :: Int,
protoPeriod :: t,
protoPeriodInt :: Int,
protoShapeLimits :: (t,t),
protoArray :: Array Int y
}
makePrototype :: (RealField.C t) =>
Margin ->
Margin ->
Int -> t -> Sig.T y -> Prototype t y
makePrototype marginLeap marginStep periodInt period tone =
let ipOffset =
ToneMod.interpolationOffset marginLeap marginStep periodInt
len = length tone
(lower,upper) =
ToneMod.shapeLimits marginLeap marginStep periodInt len
limits =
if lower > upper
then error "min>max"
else (fromIntegral lower, fromIntegral upper)
in Prototype {
protoMarginLeap = marginLeap,
protoMarginStep = marginStep,
protoIpOffset = ipOffset,
protoPeriod = period,
protoPeriodInt = periodInt,
protoShapeLimits = limits,
protoArray = listArray (0, pred len) tone
}
sampledToneCell :: (RealField.C t) =>
Prototype t y -> t -> Phase.T t -> ((t,t), Cell y)
sampledToneCell p shape phase =
let (n, q) =
ToneMod.flattenShapePhase (protoPeriodInt p) (protoPeriod p)
(limit (protoShapeLimits p) shape, phase)
in (q,
map (map (protoArray p ! ) . iterate (protoPeriodInt p +)) $
enumFrom (n - protoIpOffset p))
oscillatorCells :: (RealField.C t) =>
Margin ->
Margin ->
Int -> t ->
Sig.T y -> (t, Sig.T t) -> (Phase.T t, Sig.T t) -> Sig.T ((t,t), Cell y)
oscillatorCells
marginLeap marginStep periodInt period sampledTone shapes freqs =
map (seekCell periodInt period) $
oscillatorSuffixes
marginLeap marginStep periodInt period sampledTone shapes freqs
seekCell :: (RealField.C t) =>
Int -> t ->
((t, Phase.T t), Cell y) -> ((t,t), Cell y)
seekCell periodInt period =
(\(coords, ptr) ->
let (k,q) = ToneMod.flattenShapePhase periodInt period coords
in if k>0
then error "ToneModulation.oscillatorCells: k>0"
else (q, drop (periodInt+k) ptr))
oscillatorSuffixes :: (RealField.C t) =>
Margin ->
Margin ->
Int -> t -> Sig.T y ->
(t, Sig.T t) -> (Phase.T t, Sig.T t) ->
Sig.T ((t, Phase.T t), Cell y)
oscillatorSuffixes
marginLeap marginStep periodInt period sampledTone shapes freqs =
let ptrs =
List.transpose $
takeWhile (not . null) $
iterate (drop periodInt) sampledTone
ipOffset =
periodInt +
ToneMod.interpolationOffset marginLeap marginStep periodInt
(skip:skips,coords) =
unzip $
integrateFractional period
(limitRelativeShapes marginLeap marginStep periodInt sampledTone shapes)
freqs
in zip coords $
map (\(n,ptr) ->
if n>0
then error $ "ToneModulation.oscillatorCells: " ++
"limit of shape parameter is buggy"
else ptr) $
tail $
scanl
(\ (n,ptr0) d0 -> dropRem (n+d0) ptr0)
(0,ptrs)
((skip - ipOffset) : skips)
dropFrac :: RealField.C i => i -> Sig.T a -> (Int, i, Sig.T a)
dropFrac =
let recourse acc n xt =
if n>=1
then
case xt of
_:xs -> recourse (succ acc) (n-1) xs
[] -> (acc, n, [])
else (acc,n,xt)
in recourse 0
dropFrac' :: RealField.C i => i -> Sig.T a -> (Int, i, Sig.T a)
dropFrac' =
let recourse acc n xt =
maybe
(acc,n,xt)
(recourse (succ acc) (n-1) . snd)
(guard (n>=1) >> ListHT.viewL xt)
in recourse 0
propDropFrac :: (RealField.C i, Eq a) => i -> Sig.T a -> Bool
propDropFrac n xs =
dropFrac n xs == dropFrac' n xs
dropRem :: Int -> Sig.T a -> (Int, Sig.T a)
dropRem =
let recourse n xt =
if n>0
then
case xt of
_:xs -> recourse (pred n) xs
[] -> (n, [])
else (n,xt)
in recourse
dropRem' :: Int -> Sig.T a -> (Int, Sig.T a)
dropRem' =
let recourse n xt =
maybe
(n,xt)
(recourse (pred n) . snd)
(guard (n>0) >> ListHT.viewL xt)
in recourse
propDropRem :: (Eq a) => Int -> Sig.T a -> Bool
propDropRem n xs =
dropRem n xs == dropRem' n xs
oscillatorCoords :: (RealField.C t) =>
Int -> t -> (t, Sig.T t) -> (Phase.T t, Sig.T t) -> Sig.T (ToneMod.Coords t)
oscillatorCoords periodInt period shapes freqs =
map (mapSnd (ToneMod.flattenShapePhase periodInt period)) $
integrateFractional period shapes freqs
integrateFractional :: (RealField.C t) =>
t -> (t, Sig.T t) -> (Phase.T t, Sig.T t) -> Sig.T (ToneMod.Skip t)
integrateFractional period (shape0, shapes) (phase, freqs) =
let shapeOffsets =
scanl
(\(_,s) c -> splitFraction (s+c))
(splitFraction shape0) shapes
phases =
let (s:ss) = map (\(n,_) -> fromIntegral n / period) shapeOffsets
in freqsToPhases
(Phase.decrement s phase)
(zipWith (-) freqs ss)
in zipWith
(\(d,s) p -> (d, (s,p)))
shapeOffsets
phases
freqsToPhases :: RealRing.C a => Phase.T a -> Sig.T a -> Sig.T (Phase.T a)
freqsToPhases phase freq = scanl (flip Phase.increment) phase freq
limitRelativeShapes :: (Ring.C t, Ord t) =>
Margin ->
Margin ->
Int -> Sig.T y -> (t, Sig.T t) -> (t, Sig.T t)
limitRelativeShapes marginLeap marginStep periodInt sampledTone =
let
len = Chunky.fromChunks (ListMatch.replicate sampledTone one)
(minShape, maxShape) =
ToneMod.shapeLimits marginLeap marginStep periodInt len
fromChunky = NonNeg.toNumber . Chunky.toNumber
toChunky = Chunky.fromNumber . NonNeg.fromNumber
in mapPair (fromChunky, map fromChunky) .
uncurry (limitMaxRelativeValuesNonNeg maxShape) .
mapPair (toChunky, map toChunky) .
uncurry (limitMinRelativeValues (fromChunky minShape))
limitMinRelativeValues :: (Additive.C a, Ord a) =>
a -> a -> Sig.T a -> (a, Sig.T a)
limitMinRelativeValues xMin x0 xs =
let (ys,zs) =
span ((<zero).fst) (zip (scanl (+) (x0-xMin) xs) (x0:xs))
in case ys of
[] -> (x0,xs)
(_:yr) -> (xMin, ListMatch.replicate yr zero ++
case zs of
[] -> []
(z:zr) -> fst z : map snd zr)
limitMaxRelativeValues :: (Additive.C a, Ord a) =>
a -> a -> Sig.T a -> (a, Sig.T a)
limitMaxRelativeValues xMax x0 xs =
let (ys,zs) =
span (>zero) (scanl (-) (xMax-x0) xs)
in forcePair $
ListHT.switchR
(xMax, ListMatch.replicate xs zero)
(\ yl yr -> (x0, ListMatch.take yl xs ++ ListMatch.take zs (yr : repeat zero)))
ys
limitMaxRelativeValuesNonNeg :: (Additive.C a, Ord a) =>
a -> a -> Sig.T a -> (a, Sig.T a)
limitMaxRelativeValuesNonNeg xMax x0 xs =
let (ys,zs) =
span fst (scanl (\(_,acc) d -> safeSub acc d) (safeSub xMax x0) xs)
in forcePair $
ListHT.switchR
(xMax, ListMatch.replicate xs zero)
(\ yl ~(_,yr) -> (x0, ListMatch.take yl xs ++ ListMatch.take zs (yr : repeat zero)))
ys
safeSub :: (Additive.C a, Ord a) => a -> a -> (Bool, a)
safeSub a b = (a>=b, a-b)