packages feed

csound-expression-5.3.2: examples/Tibetan.hs

{- | Example requires the package `random` to be installed

This remarkable tibetan harmonic chant like effect is created by nine sinusoidal
oscillators, whose frequencies are almost identical: separated by a fraction of
1 Hz from each other. Thus for each component, amplitude modulation leads to its
enhancement or cancelling out in turn. In his composition 'Mutations', Risset
gives the instrument two different envelopes: one with sharp rise and one is a
more gradual rise.
-}
module Tibetan where

import Data.List(zip4)
import Data.Traversable(traverse)

import Control.Monad.Trans.State
import Control.Applicative hiding ((<*))

import System.Random

import Csound.Base
import Color(blurp, blue)

-- | A pure tibetan instrument with randomized parameters.
tibetan :: (D, D, D, D) -> SE Sig
tibetan (amp, cps, rise, dec) = fmap (\x -> pureTibetan (amp, cps, x, rise, dec)) off
    where off = fmap (\x -> 0.3 + ir x) $ rand 0.15

pureTibetan :: (D, D, D, D, D) -> Sig
pureTibetan (amp, cps, off, rise, dec) = mean $ fmap partial $ 0 : offs ++ (fmap negate offs)
    where offs = fmap int [1 .. 4]
          partial rat = linen (sig amp) rise idur dec * oscBy wave (sig $ cps + off * rat)
          wave = ifB (cps `lessThan` 230) (waveBy 5) (ifB (cps `lessThan` 350) (waveBy 3) (waveBy 1))
          waveBy n = sines $ [0.3, 0, 0, 0] ++ replicate n 0.1

-----------------------------------------------------------
-- scores

-- tibetan

instant = 1
med = 3
long = 5

-- intersection
deep = 50
shallow = 90
sym = 70

-- myxo mode
ts = [1, 9/8, 5/4, 4/3, 3/2, 5/3, 16/9]

id2cps oct n = baseTone * (2 ^^ (oct + curOct)) * ts !! curId
    where (curOct, curId) = divMod n 7

data Act = Tone Int | Repeat Int | Wait Double

r = Repeat
w = Wait

instance Num Act where
    fromInteger = Tone . fromInteger

type N = (Sig, Sig, D, D, D, D)


data St = St
    { stRepeat :: Int
    , stWait   :: Double
    , stSpan   :: (Double, Double)
    , stRnds   :: [Double] }

-- constants
baseTone = 110 -- an A
innerOverlap = 0.5
noteLength = 25
fixedNoteLength = 15
offset = 0.03
rise = 5
dec = 15
durStep = 7

initRepeat = 2
initWait = 0.7
initSpan = (0, 0)

updateSt f = modify f >> return []

turtle :: Act -> State St [N]
turtle x = case x of
    Repeat n -> updateSt $ \s -> s{ stRepeat = n }
    Wait n -> updateSt $ \s -> s{ stWait = n }
    -- tones
    Tone t -> state (getNotes t)

getNotes :: Int -> St -> ([N], St)
getNotes k st = (notes, st')
    where t0s = startTimes (offsetStartTime (stWait st) $ stSpan st) durStep
          dts = durs noteLength
          cpss = freqs (ts !! (k - 1))
          amps = fmap cps2amp cpss
          offs = offsets offset
          decs = decays dec
          riss = rises rise

          notes = take (stRepeat st) $ getZipList $ (\t0 dt amp cps ris dec ->
                (sig $ double t0, sig $ double dt, double amp, double cps, double ris, double dec)) <$>
                ZipList t0s <*> ZipList dts <*> ZipList amps <*> ZipList cpss <*> ZipList decs <*> ZipList riss

          st' = st{ stSpan = getSpan (stRepeat st) t0s dts }

offsetStartTime :: Double -> (Double, Double) -> Double
offsetStartTime k (t0, dt) = t0 + k * dt

startTimes :: Double -> Double -> [Double]
startTimes t0 step = fmap (+ t0) $ [0, step ..]

durs    = repeat
offsets = repeat
rises   = repeat
decays  = repeat

getSpan :: Int -> [Double] -> [Double] -> (Double, Double)
getSpan num starts durs = (starts !! n, durs !! n)
    where n = num - 1

freqs k = fmap (* base) octs
    where octs = cycle [1, 0.5, 2, 1, 2, 1, 1, 0.5]
          base = k * baseTone

cps2amp a = 0.5 * full a
    where full x
            | x < 100 = 1
            | x < 200 = 0.8
            | x < 300 = 0.6
            | x < 400 = 0.5
            | otherwise = 0.4

run :: [Act] -> IO [N]
run as = fmap concat $ fmap (evalState (mapM turtle as)) initSt
    where initSt = fmap (St initRepeat initWait initSpan) $ fmap (randomRs (0, 1)) newStdGen

acts :: [Act]
acts = concat $ replicate 1 $ [1, 1, 5, 2, 5, 7, 5, 1, 1, 3, 5, 3, 1, 1, 5, 1 ]


note (start, dur, amp, cps, rise, dec) = del start $ str dur $
                temp (amp, cps, rise, dec)

globalEffect =  eff (bindSig $ return . blp 5000 . (0.3 * )) . eff rever

res2 ns = har [res ns, del 13 $ res ns]

res ns = sco tibetan $ har $ fmap note ns

rever x = do
    _ <- delayr 3
    [aleft, aright] <- mapM deltap3 [2.50, 1.25]
    delayw x
    return (f aleft, f aright)
    where x1 = reverb2 (0.6 * x) 6 0.5
          f a = 0.5 * (x1 + a)


------------------------------------------------------------
-- blurp

blurpVol = 0.5
introDur = 15

toStereo = eff $ \x -> return (x, x)

introBlurp = toStereo $ sco blurp $ introDur *| temp (0.7 * blurpVol)

blurpSco = toStereo $ mel [rest 100, cone 15 0.7, rest 60, cone 10 0.4]
    where cone dt v = eff (\x -> return $ linen x (0.25 * idur) idur (0.25 * idur)) $ sco blurp $ dt *| temp (v * blurpVol)

------------------------------------------------------------
-- stars

starLength      = 60
starParams      = zip4 starVolumes starLfos starHarms starSweeps

starVolume      = 0.3
starVolumes     = fmap (starVolume * ) $ cycle [0.7, 0.3, 0.5, 0.8, 0.9, 0.5, 0.2]
starLfos        = cycle [23, 10, 5, 15, 17]
starHarms       = cycle [10, 6, 7, 10]
starSweeps      = cycle [0.7, 0.6, 0.6, 0.5, 0.8, 0.9, 0.25, 0.7, 0.5, 0.5, 0.6]

starTotalDelay  = 1.5 * 60
starInitDelays  =              [ 0, 11,  2, 8,  21, 25]
starPeriods     = fmap (* 2.5) [ 7, 23, 77, 13, 17, 31]

starChord       = [0, 1, 2, 4, 7]

starSco = sco blue $
    flip evalState starParams $ traverse addParam $
    har $ zipWith3 phi starInitDelays starPeriods starChord
    where phi dt period note = del dt $ loopBy 50 $ har [4 *| temp (double $ id2cps 2 note), rest period]

          addParam cps = state $ \((amp, lfo, harm, sweep) : params) ->
            ((amp, cps, lfo, harm, sweep), params)


------------------------------------------------------------

main = do
    notes <- run acts
    dac $ mix $ har
        [ introBlurp
        , del (introDur * 0.70) $ har
            [ globalEffect $ har
                [ res2 notes
                , del starTotalDelay $ starSco
                , del (2 * starTotalDelay) $ starSco]
            , blurpSco
            ]]