csound-expression-5.1.0: src/Csound/Air/Granular/Morpheus.hs
-- | Wonderful echoes from morpheus.
-- Granular synthesis for morphing between waveforms.
-- It's a simplification of partikkel opcode for the case of morphing.
module Csound.Air.Granular.Morpheus(
WaveAmp, WaveKey, MorphWave,
MorphSpec(..), GrainDensity(..), GrainEnv(..),
morpheus,
-- * Sound files
morphSnd1, morphSnd,
-- * Amplitude modes
pairToSquare,
-- * Oscillators
morpheusOsc, morpheusOsc2
) where
import Control.Arrow
import Data.Default
import Csound.Typed
import Csound.Typed.Opcode
import Csound.Tab
import Csound.SigSpace
import Csound.Air.Granular(Pointer, csdPartikkel)
import Csound.Air.Wav
import Csound.Air.Wave
import Csound.Types(compareWhenD)
type WaveAmp = Sig
type WaveKey = Sig
type MorphWave = (Tab, WaveAmp, WaveKey, Pointer)
-- | Density of the grain stream.
--
-- * @rate@ is how many grains per second is generated
--
-- * @size@ is the size of each grain in milliseconds (it's good to set it relative to grain rate)
--
-- * @skip@ skip is a skip ratio (0 to 1). It's the probability of grain skip. Zero means no skip and 1 means every grain is left out.
--
-- see docs for Csound partikkel opcode for more detailed information <http://www.csounds.com/manual/html/partikkel.html>
data GrainDensity = GrainDensity
{ grainRate :: Sig
, grainSize :: Sig
, grainSkip :: Sig }
instance Default GrainDensity where
def = GrainDensity
{ grainRate = kGrainRate
, grainSize = kduration
, grainSkip = 0 }
where
kGrainDur = 2.5 -- length of each grain relative to grain rate
kduration = (kGrainDur*1000)/kGrainRate -- grain dur in milliseconds, relative to grain rate
kGrainRate = 12
-- | Parameters for grain envelope.
--
-- * attShape -- table that contains shape of the attack.
--
-- * decShape -- table that contains shape of the decay
--
-- * sustRatio -- how big is sustain phase relative to attack and decay
--
-- * attack to decay ration -- relative amount of attack decay ration. 0.5 means attack equals decay.
--
-- see docs for Csound partikkel opcode for more detailed information <http://www.csounds.com/manual/html/partikkel.html>
data GrainEnv = GrainEnv
{ grainAttShape :: Tab
, grainDecShape :: Tab
, grainSustRatio :: Sig
, grainAttDecRatio :: Sig }
instance Default GrainEnv where
def = GrainEnv
{ grainAttShape = sigmoidRise
, grainDecShape = sigmoidFall
, grainSustRatio = 0.25
, grainAttDecRatio = 0.5 }
-- sigmoidRise = guardPoint $ sines4 [(0.5, 1, 270, 1)]
-- sigmoidFall = guardPoint $ sines4 [(0.5, 1, 90, 1)]
-- | Specification of morphing synth. It has the default instance
-- and the values in its records has default instances too
data MorphSpec = MorphSpec
{ morphGrainDensity :: GrainDensity
, morphGrainEnv :: GrainEnv
}
instance Default MorphSpec where
def = MorphSpec
{ morphGrainDensity = def
, morphGrainEnv = def
}
-- | Synth that is based on partikkel. It allows easy morphing between unlimited number of waves.
-- While partikkel allows only 4 waves to be used. We can use as many as we like. Internally
-- the list is split on groups 4 elements or less in each and one partikkel is applied to each group.
-- Many parameters of partikel were simplified to get the good defaults for sound morphing behavior.
--
-- > morpheus spec waves frequencyScale
--
-- * spec -- contains many misc parameters
--
-- * waves list can contain up to four wave tables to read grains from.
--
-- * frequencyScale -- scaling factor for frequency. 1 means playing at the original frequency, 2 rises the pitch by octave.
-- We can use negative values to play the grains in reverse.
morpheus :: MorphSpec -> [MorphWave] -> Sig -> SE Sig2
morpheus spec pwaves cps = sum $ fmap (\waves -> morpheus4 spec waves cps) (splitBy4 pwaves)
splitBy4 :: [a] -> [[a]]
splitBy4 xs = case xs of
a:b:c:d:rest -> [a,b,c,d] : splitBy4 rest
rest -> [rest]
morpheus4 :: MorphSpec -> [MorphWave] -> Sig -> SE Sig2
morpheus4 spec pwaves cps = do
iwaveamptab <- makeMorphTable amp1 amp2 amp3 amp4
return $ csdPartikkel agrainrate kdistribution idisttab async kenv2amt ienv2tab
ienv_attack ienv_decay ksustain_amount ka_d_ratio kduration kamp igainmasks
kwavfreq ksweepshape iwavfreqstarttab iwavfreqendtab awavfm
ifmamptab ifmenv icosine kTrainCps knumpartials
kchroma ichannelmasks krandommask kwaveform1 kwaveform2 kwaveform3 kwaveform4
iwaveamptab asamplepos1 asamplepos2 asamplepos3 asamplepos4
kwavekey1 kwavekey2 kwavekey3 kwavekey4 imax_grains
where
wave1 : wave2 : wave3 : wave4 : _ = cycle pwaves
async = 0
kamp = 1
ichannelmasks = skipNorm $ doubles [0, 0, 0.5]
kdistribution = 1
idisttab = setSize 16 $ startEnds [1, 16, -10, 0]
-- grain shape settings
grainEnv = morphGrainEnv spec
ienv_attack = grainAttShape grainEnv
ienv_decay = grainDecShape grainEnv
ksustain_amount = grainSustRatio grainEnv
ka_d_ratio = grainAttDecRatio grainEnv
kenv2amt = 0
ienv2tab = eexps [1, 0.0001]
-- grain density
grainDensity = morphGrainDensity spec
kGrainRate = grainRate grainDensity
kduration = grainSize grainDensity
kwavfreq = cps
krandommask = grainSkip grainDensity
-- waves
kwavekey1 = getWaveKey wave1
kwavekey2 = getWaveKey wave2
kwavekey3 = getWaveKey wave3
kwavekey4 = getWaveKey wave4
asamplepos1 = getSamplePos wave1
asamplepos2 = getSamplePos wave2
asamplepos3 = getSamplePos wave3
asamplepos4 = getSamplePos wave4
kwaveform1 = getWaveForm wave1
kwaveform2 = getWaveForm wave2
kwaveform3 = getWaveForm wave3
kwaveform4 = getWaveForm wave4
amp1 = getAmp wave1
amp2 = getAmp wave2
amp3 = getAmp wave3
amp4 = getAmp wave4
imax_grains = 100
getWaveKey (tab1, amp1, key1, ptr1) = key1 / sig (getTabLen tab1)
getSamplePos (_, _, _, ptr) = ptr
getWaveForm (form, _, _, _) = form
getAmp (_, amp, _, _) = kr amp
-- no trainlets
icosine = cosine
kTrainCps = kGrainRate
knumpartials = 7
kchroma = 3
-- no FM
kGrFmFreq = kGrainRate / 4
kGrFmIndex = 0
aGrFmSig = kGrFmIndex * osc kGrFmFreq
agrainrate = kGrainRate + aGrFmSig * kGrainRate
ifmenv = elins [0, 1, 0]
ifmamptab = skipNorm $ doubles [0, 0, 1]
awavfm = 0
-- other params
igainmasks = skipNorm $ doubles [0, 0, 1]
ksweepshape = 0.5
iwavfreqstarttab = skipNorm $ doubles [0, 0, 1]
iwavfreqendtab = skipNorm $ doubles [0, 0, 1]
makeMorphTable a1 a2 a3 a4 = do
t <- newTab 64
mapM_ (\(i, amp) -> tablew amp (2 + sig (int i)) t ) (zip [0 .. ] [a1, a2, a3, a4])
return t
getTabLen t = ftlen t / getSampleRate
-- | Creates four control signals out two signals. The control signals are encoded by the position
-- of the point on XY-plane. The four resulting signals are derived from the proximity of the point
-- to four squares of the ((0, 1), (0, 1)) square. It can be useful to control the morpheus with XY-pad controller.
pairToSquare :: (Sig, Sig) -> (Sig, Sig, Sig, Sig)
pairToSquare (x, y) = ((1 - x) * (1 - y), x * (1 - y) , x * y, (1 - x) * y)
-- | Morpheus synth for mono-audio files. The first cell in each tripple is occupied by file name.
-- The rest arguments are the same as for @morpheus@.
morphSnd1 :: MorphSpec -> [(String, WaveAmp, WaveKey)] -> Sig -> SE Sig2
morphSnd1 spec waves cps = morpheus spec (fmap fromSnd waves) cps
where
fromSnd (file, amp, key) = (wavLeft file, amp, key, phasor (1 / sig (lengthSnd file)))
-- | Morpheus synth for stereo-audio files. The first cell in each tripple is occupied by file name.
-- The rest arguments are the same as for @morpheus@.
morphSnd :: MorphSpec -> [(String, WaveAmp, WaveKey)] -> Sig -> SE Sig2
morphSnd spec waves cps = morphSndByTab wavLeft spec waves cps + morphSndByTab wavRight spec waves cps
morphSndByTab :: (String -> Tab) -> MorphSpec -> [(String, WaveAmp, WaveKey)] -> Sig -> SE Sig2
morphSndByTab getTab spec waves cps = morpheus spec (fmap fromSnd waves) cps
where
fromSnd (file, amp, key) = (getTab file, amp, key, phasor (1 / sig (lengthSnd file)))
-- | Morpheus oscillator.
--
-- > morpheusOsc spec (baseFrequency, table) cps
--
-- @baseFrequency@ is the frequency of the sample contained in the table. With oscillator
-- we can read the table on different frequencies.
morpheusOsc :: MorphSpec -> (D, Tab) -> Sig -> SE Sig2
morpheusOsc spec (baseFreq, t) cps = morpheus spec waves ratio
where
ratio = cps / sig baseFreq
aptr = cycleTab t
waves = [(t, 1, 1, aptr)]
cycleTab t = phasor $ sig $ recip $ getTabLen t
-- | Morpheus oscillator. We control the four tables with pair of control signals (see the function @pairToSquare@).
--
-- > morpheusOsc2 spec baseFrequency waves (x, y) cps = ...
morpheusOsc2 :: MorphSpec -> D -> [(Sig, Tab)] -> (Sig, Sig) -> Sig -> SE Sig2
morpheusOsc2 spec baseFreq ts (x, y) cps = morpheus spec waves ratio
where
(a1, a2, a3, a4) = pairToSquare (x, y)
ratio = cps / sig baseFreq
waves = zipWith (\amp (key, t) -> (t, amp, key, cycleTab t)) (cycle [a1, a2, a3, a4]) ts
{- examples
main' = dac $ mul 0.2 $ morphSnd1 def [("floss/ClassGuit.wav", linseg [1, 3, 1, 3, 0], linseg [1, 3, 1, 3, 0]), ("floss/ClassGuit.wav", linseg [0, 3, 0, 3, 1], (-1))] 1
main = dac $ lift1 (\p -> mixAt 0.25 largeHall2 $ mixAt 0.6 (pingPong 0.124 0.5 0.7) $
at (filt 2 (\cfq res x -> moogladder x cfq res) (env * 12000) 0.1) $ mul (0.2 * env) $
morpheus (def { morphGrainDensity = def { grainRate = linseg [36, 18, 4], grainSize = linseg [ 1200, 6, 5700, 12, 750 ], grainSkip = 0.45 * uosc 0.17 }})
(tabs p) (negate $ semitone (5))) (ujoy (0.5, 0.5))
where
tabs (x, y) = [file a1 1, file a2 0.5, file2 a3 1, file3 a4 1]
where (a1, a2, a3, a4) = pairToSquare (x, y)
file a x = (wavl "floss/ClassGuit.wav", a, x, linseg [2.5, 18, 3.5])
file2 a x = (wavl "floss/hd.wav", a, x, linseg [0.2, 18, 0.6])
file3 a x = (wavl "floss/hd.wav", a, x, linseg [0.02, 18, 0.5])
env = linseg [0, 1, 1, 3, 1] -- 10, 0]
amp1 = linseg [1, 8, 1, 4, 0]
amp2 = linseg [0, 6, 0, 6, 1]
-}
{-
-- todo
-- playing samples in chain
pyramidWeights
partWaveChain :: [Double] -> Sig -> (Sig, Sig, Sig, Sig)
partWaveChain xs pointer = case xs of
[a, da] ->
let (amp1, amp2) = go1 a da pointer
in (amp1, amp2, 0, 0)
[a, da, b, db] ->
let (amp1, amp2, amp3) = go2 a da b db pointer
in (amp1, amp2, amp3, 0)
[a, da, b, db, c, dc] ->
let (amp1, amp2, amp3, amp4) = go3 a da b db c dc pointer
in (amp1, amp2, amp3, amp4)
_ -> error "partWaveChain: wrong number of elements in the list. Should be [a, da], [a, da, b, db] or [a, da, b, db, c, dc]."
where
go1 a da ptr = (readTab t1 ptr, readTab t2 ptr)
where
d = da / 2
t1 = leftTab (a - d) (a + d)
t2 = rightTab (a - d) (a + d)
go2 a da b db = (readTab t1 ptr, readTab t2 ptr, readTab t3 ptr)
where
da2 = da / 2
db2 = db / 2
t1 = leftTab (a - da2) (a + da2)
t2 = centerTab (a - da2) (a + da2) (b - db2) (b + db2)
t3 = rightTab (b - db2) (b + db2)
go3 = undefined
readTab t ptr = table ptr t1 `withD` 1
leftTab a b c = lins [1, a, 1, b, 0, c, 0]
rightTab a b c = lins [0, a, 0, b, 1, c, 1]
centerTab a b c d e = lins [0, a, 0, b, 1, c, 1, d, 0, e, 0]
partWaveChain2 :: Sig -> (Sig, Sig, Sig, Sig)
partWaveChain2 = partWaveChain [0.5, 0.25]
partWaveChain3 :: Sig -> (Sig, Sig, Sig, Sig)
partWaveChain3 = partWaveChain [1/3, 0.25, 1/3, 0.25]
partWaveChain4 :: Sig -> (Sig, Sig, Sig, Sig)
partWaveChain4 = partWaveChain [0.25, 0.2, 0.25, 0.2, 0.25, 0.2]
cfdChainWeights :: [Double] -> Sig -> [Sig]
cfdChainWeights xs ptr = getWeights ptr (getPairs xs)
where
getPairs xs = case xs of
a:b:rest -> (a, b) : getPairs rest
_ -> []
getPairs ptr xs = case xs of
[] -> [1]
[(a, rada)] -> go1 a rada ptr
a : as -> goN a (init as) (zip lengs $ makeAdjacentPairs xs) (last as)
where
go1 a da ptr = [readTab t1 ptr, readTab t2 ptr]
where
d = da / 2
t1 = leftTab (a - d) (a + d)
t2 = rightTab (a - d) (a + d)
goN (start, startRad) center (end, endRad) =
startTab ++ centerTabs ++ [endTab]
where
startTab = leftTab (start - startRad) (2 * startRad) (1 - (start + startRad))
endTab = rightTab (1 - (end - endRad)) (2 * endRad) (end + endRad)
centerTabs = fmap toCenterTab center
toCenterTab (leng, (a, rada), (b, radb)) = centerTab (leng - rada) (2 * rada)
readTab t ptr = table ptr t1 `withD` 1
leftTab a b c = lins [1, a, 1, b, 0, c, 0]
rightTab a b c = lins [0, a, 0, b, 1, c, 1]
centerTab a b c d e = lins [0, a, 0, b, 1, c, 1, d, 0, e, 0]
makeAdjacentPairs xs = case xs of
[] -> []
x:xs -> tail $ scanl (\(a, b) c -> (b, c)) (x, x) xs
lengs xs = tail $ scanl (\res (a, _) -> res + a) 0 xs
-}