packages feed

x-dsp-0.2: tools/CsoundImplTemplate.hs

{-# LANGUAGE
     NoMonomorphismRestriction
    ,DoRec
    ,ExtendedDefaultRules
    ,TypeFamilies
    ,DeriveDataTypeable
    ,DeriveFunctor
    ,GeneralizedNewtypeDeriving
    ,ScopedTypeVariables
    ,MultiParamTypeClasses
    ,FlexibleInstances
    ,TupleSections
    ,OverlappingInstances
    ,TypeSynonymInstances #-}

module Language.XDsp.Implementations.Csound (
  S (..)
 ,BusType (..)
 ,HostOut (..)
 ,RType
 ,defaultRType
 ,unifyCsd
 ,writeHeader
 ,makeInstrument
 ,TList
 ,unTList'
 ,module Language.XDsp.Semantics.CsoundExt
)

where

import           Language.XDsp.Semantics
import           Language.XDsp.Semantics.CsoundExt hiding (CsOscil (..), CsOscili (..), CsSum (..))
import qualified Language.XDsp.Semantics.CsoundExt as CSE

import           Data.Char
import           Data.Data
import           Data.List (intersperse, intercalate)
import qualified Data.Map as M
import           Data.Maybe (fromMaybe, catMaybes)
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import           Data.TypeLevel.Num ((:>=:), Nat, toInt)
import           Control.Monad.RWS
import           Control.Applicative
import           Control.Arrow

import           Text.Printf

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

-- |Initial csound pretty-printer

data CVar =
    Cnst Double
  | CStr String
  | CVar String
  deriving (Eq, Ord, Show, Read)

data VarType =
    A                  -- A-rate signal
  | K                  -- K-rate signal
  | I                  -- i-time value
  | F                  -- fsig
  | T                  -- function table
  | IC                 -- score i-statement
  | St                 -- String
  | U                  -- Unit
  | Tp VarType VarType -- tuple
  | T3 VarType VarType VarType         -- 3-tuple
  | T4 VarType VarType VarType VarType -- 4-element tuple
  | CList VarType      -- list
  deriving (Eq, Ord, Read, Show)

-- | get the initial character of a csound var from a VarType
mkChr :: VarType -> Char
mkChr T  = 'f'
mkChr IC = 'i'
mkChr St = 'i'  -- string type stored in ivals
mkChr x = toLower . head . show $ x

getLbl :: CVar -> String
getLbl (Cnst x)   = show x
getLbl (CStr s)   = show s
getLbl (CVar s)   = s

-- |Polyvariadic support for building argument lists
class ArgListBuilder r where
  appendToList :: [String] -> r

instance ArgListBuilder [String] where
  appendToList = reverse

instance (Varable a, ArgListBuilder r) => ArgListBuilder (a -> r) where
  appendToList arg = appendToList . (\acc a -> getVarLbl a : acc) arg

mkOp0 :: Varable out => String -> S n out
mkOp0 opname = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s\n" outName opname
  return out

-- |helper function to create a 1-argument opcode instance.
-- I should make these with TH, but that would require having "genName" in scope
-- which means S would need to be in scope, but then all the S instances
-- would be orphans, which I don't want either...
mkOp1 :: (Varable a, Varable out) => String -> a -> S n out
mkOp1 opname a1 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname (getVarLbl a1)
  return out

mkOp2 opname a1 a2 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname
    (argCleaner $ appendToList [] a1 a2)
  return out

mkOp3 opname a1 a2 a3 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname
    (argCleaner $ appendToList [] a1 a2 a3)
  return out

mkOp4 opname a1 a2 a3 a4 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname
    (argCleaner $ appendToList [] a1 a2 a3 a4)
  return out

mkOp5 opname a1 a2 a3 a4 a5 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname
    (argCleaner $ appendToList [] a1 a2 a3 a4 a5)
  return out

mkOp6 opname a1 a2 a3 a4 a5 a6 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname
    (argCleaner $ appendToList [] a1 a2 a3 a4 a5 a6)
  return out

mkOp7 opname a1 a2 a3 a4 a5 a6 a7 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname
    (argCleaner $ appendToList [] a1 a2 a3 a4 a5 a6 a7)
  return out

mkOp8 opname a1 a2 a3 a4 a5 a6 a7 a8 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname
    (argCleaner $ appendToList [] a1 a2 a3 a4 a5 a6 a7 a8)
  return out

mkOp9 opname a1 a2 a3 a4 a5 a6 a7 a8 a9 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname
    (argCleaner $ appendToList [] a1 a2 a3 a4 a5 a6 a7 a8 a9)
  return out

mkOp10 opname a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname
    (argCleaner $ appendToList [] a1 a2 a3 a4 a5 a6 a7 a8 a9 a10)
  return out

mkOp11 opname a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname
    (argCleaner $ appendToList [] a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11)
  return out

mkOp12 opname a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname
    (argCleaner $ appendToList [] a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12)
  return out

mkOp13 opname a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname
    (argCleaner $ appendToList [] a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13)
  return out

mkOp17 opname a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 = do
  (out, outName) <- genName
  tellOrc $ printf "%s %s %s\n" outName opname
    (argCleaner $ appendToList [] a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17)
  return out

-- | remove null arguments; these appear when a list of arguments is empty.
argCleaner :: [String] -> String
argCleaner = intercalate ", " . filter (Prelude.not . null)

mkOp14 = error "mkOp14"
mkOp15 = error "mkOp15"
mkOp16 = error "mkOp16"
mkOp18 = error "mkOp18"
mkOp19 = error "mkOp19"
mkOp22 = error "mkOp22"
mkOp26 = error "mkOp26"
mkOp32 = error "mkOp32"
mkOp83 = error "mkOp83"
mkOp99 = error "mkOp99"
mkOp51 = error "mkOp51"
mkOp34 = error "mkOp34"
mkOp43 = error "mkOp43"

-- ------------------------------------------
-- ------------------------------------------
-- sco helpers

-- | A score item.  The "Card" name is old-school
data Card = Card VarType [String] deriving (Eq, Read, Show)

card2str :: Card -> String
card2str (Card typ args) = mkChr typ : unwords args ++ "\n"

writeCard :: Card -> S n ()
writeCard = tellSco . card2str

mkTbl :: Int -> Int -> Int -> [Int] -> Card
mkTbl nm time sz args = Card T . map show $ nm:time:sz:args

-- |Create a 0-filled f-statement in the score section.
mkScoreBuf :: Int -> Int -> S n ()
mkScoreBuf nm sz = writeCard $ mkTbl nm 0 sz [17,0,0]

-- | Holds data on a score item for memoization purposes.
data CardM = CardM VarType String String deriving (Eq, Ord, Show)

cacheBuf :: String -> Int -> CardM
cacheBuf lbl = CardM T lbl . show

-- ------------------------------------------
-- ------------------------------------------
-- external interface helpers

data BusType = KBus | TblBus Int deriving (Eq, Read, Show)

-- |Specifies details for a Host output described within the language.
-- First string is a host-specified label.  Second is an auto-generated
-- name (e.g. tablenum)
data HostOut = HostOut {
  busType :: BusType,
  busLbl  :: String,
  busName :: String } deriving (Eq, Read, Show)

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

data CacheKey =
 CKC CardM    -- score cache
 deriving (Eq, Ord, Show)

type WCache  = M.Map CacheKey CVar
type GenMap  = M.Map VarType Int
type ICache = S.Set Int
data SState  = SState {
  wcache :: WCache
 ,genMap :: GenMap
 ,iCache :: ICache}

type ScoType = TL.Text
type OrcType = TL.Text
type HostType = [HostOut]

type WType = (ScoType, OrcType, HostType)
type RType = (Integer, Integer) -- SR and ksmps

defaultRType = (44100,16)

newtype S n a = S { unS :: RWS RType WType SState a }
  deriving (Functor, Applicative, Monad, Typeable)

instance MonadFix (S n) where
  mfix f = S $ mfix (unS . f)

instance MonadState (S n) where
  type StateType (S n) = SState
  get = S get
  put = S . put

instance MonadWriter (S n) where
  type WriterType (S n) = WType
  tell   = S . tell
  listen = S . listen . unS
  pass   = S . pass . unS

instance MonadReader (S n) where
  type EnvType (S n) = RType
  ask   = S ask
  local f = S . local f . unS

csdHeader =
  "<CsoundSynthesizer>\n\
  \<CsOptions>\n\
  \%s\n\
  \</CsOptions>\n"

-- -------------------------
-- helpers for working with states

getGen :: S n GenMap
getGen = fmap genMap get

putGen :: GenMap -> S n ()
putGen newstate = modify (\ss -> ss {genMap = newstate})

cache  :: CacheKey -> CVar -> S n ()
cache key val = do
  SState cmap gen imap <- get
  put $ SState (M.insert key val cmap) gen imap

cacheInstr :: Int -> S n ()
cacheInstr n = modify (\ss -> let ic' = S.insert n (iCache ss) in ss { iCache = ic' })

instrInCache :: Int -> S n Bool
instrInCache n = do
  (SState _ _ imap) <- get
  return $ S.member n imap

tellOrc :: String -> S n ()
tellOrc s = tell (TL.pack s,mempty, mempty)

tellSco :: String -> S n ()
tellSco s = tell (mempty,TL.pack s,mempty)

tellHost :: HostOut -> S n ()
tellHost s = tell (mempty,mempty, [s])

runS :: S n a -> RType -> (a, SState, WType)
runS e rt = runRWS (unS e) rt (SState M.empty M.empty S.empty)

evalS :: S n a -> RType -> a
evalS e rt = let (a,_,_) = runS e rt in a

getWritten :: RType -> S n x -> WType
getWritten rt e = let (_,_,w) = runS e rt in w

-- |Creates a CSD formatted String from args and an x-dsp expression.
unifyCsd :: [String] -> RType -> S n x -> (TL.Text, [HostOut])
unifyCsd args r = unifyCsd' args . getWritten r

-- |Creates a CSD file from an argument list and Writer output
unifyCsd' :: [String] -> WType -> (TL.Text, [HostOut])
unifyCsd' args (orc, sco, host) = (csd, host)
 where
  oH = TL.pack "<CsInstruments>\n"
  oF = TL.pack "\n</CsInstruments>\n"
  sH = TL.pack "<CsScore>\n"
  sF = TL.pack "\ne\n</CsScore>\n</CsoundSynthesizer>\n"

  csd = TL.concat [TL.pack (printf csdHeader (unwords args))
                  ,oH ,orc ,oF ,sH ,sco ,sF]

class Varable s where
  genName   :: S n (s, String)
  getVarLbl :: s -> String

class Varable s => PVar s where

class Varable s => KVar s where

-- |Create a new number of the appropriate type.
-- This can be made into a name by prepending the type character.
mkName :: VarType -> S n Int
mkName typ = do
  gen <- getGen
  let num = fromMaybe 1 $ M.lookup typ gen
  putGen $ M.alter (const $ Just (succ num)) typ gen
  return num


-- ---------------------------------
-- ---------------------------------
-- Instances

instance Dsp (S n) where
  data (ASig (S n)) = S_A CVar
  data (KSig (S n)) = S_K CVar
  data (INum (S n)) = S_IN CVar
  getSr    = fst <$> ask
  getKsmps = snd <$> ask

instance Constants (S n) where
  cnst = return . S_IN . Cnst
  ckig = return . S_K . Cnst
  csig = return . S_A . Cnst

instance Varable (ASig (S n)) where
  genName = do
    SState omap gen imap <- get
    let num = fromMaybe 1 $ M.lookup A gen
        nm  = 'a' : show num
    put $ SState omap (M.alter (const $ Just (succ num)) A gen) imap
    return (S_A $ CVar nm, nm)
  getVarLbl (S_A c) = getLbl c

instance PVar (ASig (S n)) where

instance Varable (KSig (S n)) where
  genName = do
    SState omap gen imap <- get
    let num = fromMaybe 1 $ M.lookup K gen
        nm  = 'k' : show num
    put $ SState omap (M.alter (const $ Just (succ num)) K gen) imap
    return (S_K $ CVar nm, nm)
  getVarLbl (S_K c) = getLbl c

instance PVar (KSig (S n)) where
instance KVar (KSig (S n)) where

instance Varable (INum (S n)) where
  genName = do
    SState omap gen imap <- get
    let num = fromMaybe 1 $ M.lookup I gen
        nm  = 'i' : show num
    put $ SState omap (M.alter (const $ Just (succ num)) I gen) imap
    return (S_IN $ CVar nm, nm)
  getVarLbl (S_IN c) = getLbl c

instance Varable () where
  genName            = return ((), "")
  getVarLbl _        = "()"

instance Varable String where
  genName = error "Can't call 'genName' on String directly"
  getVarLbl s = '"' : s ++ "\""

instance Varable (VString (S n)) where
  genName = do
    SState omap gen imap <- get
    let num = fromMaybe 1 $ M.lookup St gen
        nm  = 'S' : show num
    put $ SState omap (M.alter (const $ Just (succ num)) St gen) imap
    return (S_VS $ CVar nm, nm)
  getVarLbl (S_VS c) = getLbl c

instance KVar (VString (S n)) where

instance (Varable a, Varable b) => Varable (a,b) where
  genName = do
    (o1,nm1) <- genName
    (o2,nm2) <- genName
    return ((o1,o2), nm1 ++ ", " ++ nm2)
  getVarLbl (a,b)      = getVarLbl a ++ ", " ++ getVarLbl b

instance (Varable a, Varable b, Varable c) =>
 Varable (a,b,c) where
  genName = do
    (o1,nm1) <- genName
    (o2,nm2) <- genName
    (o3,nm3) <- genName
    return ((o1,o2,o3), intercalate ", " [nm1, nm2, nm3])
  getVarLbl (a,b,c) =
    intercalate ", " [getVarLbl a, getVarLbl b,getVarLbl c]

instance (Varable a, Varable b, Varable c, Varable d) => Varable (a,b,c,d) where
  genName = do
    (o1,nm1) <- genName
    (o2,nm2) <- genName
    (o3,nm3) <- genName
    (o4,nm4) <- genName
    return ((o1,o2,o3,o4), intercalate ", " [nm1, nm2, nm3, nm4])
  getVarLbl (a,b,c,d) =
    intercalate ", " [getVarLbl a, getVarLbl b,getVarLbl c, getVarLbl d]

instance Varable a => Varable [a] where
  genName             = error "Can't generate names for arbitrary lists"
  getVarLbl           = intercalate ", " . map getVarLbl

instance forall d a. (Varable a, Nat d) => Varable (TList d a) where
  genName = do
    outss <- replicateM (toInt (undefined :: d)) genName
    return (TList $ map fst outss, intercalate ", " $ map snd outss)
  getVarLbl           = intercalate ", " . map getVarLbl . unTList

instance KVar (INum (S n)) where

instance PVS (S n) where
  data FSig (S n) = S_FSig CVar

instance Varable (FSig (S n)) where
  genName = do
    SState omap gen imap <- get
    let num = fromMaybe 1 $ M.lookup F gen
        nm  = 'f' : show num
    put $ SState omap (M.alter (const $ Just (succ num)) F gen) imap
    return (S_FSig $ CVar nm, nm)
  getVarLbl (S_FSig c) = getLbl c

-- | need this to directly use numeric literals as arguments.
instance Varable Double where
  genName   = error "genName shouldn't be called with Doubles"
  getVarLbl = show

instance StringVar (S n) where
  data VString (S n) = S_VS CVar

-- ---------------------------------
-- ---------------------------------
-- The csound class
instance CsoundClass (S n) where
  nchnls n   = tellOrc (printf "nchnls = %i\n" n) >> return n
  set0dbfs n = tellOrc (printf "0dbfs = %f\n" n) >> return n

-- ---------------------------------
-- ---------------------------------
-- Numeric support for signals
instance Show (ASig (S n)) where
  show (S_A s) = show s

instance Eq (ASig (S n)) where
  (S_A a) == (S_A b) = a == b

instance Num (ASig (S n)) where
  (S_A a) + (S_A b) = S_A . CVar $ printf "(%s + %s)" (getLbl a) (getLbl b)
  (S_A a) - (S_A b) = S_A . CVar $ printf "(%s - %s)" (getLbl a) (getLbl b)
  (S_A a) * (S_A b) = S_A . CVar $ printf "(%s * %s)" (getLbl a) (getLbl b)
  abs _       = error "abs called on (ASig (S n))"
  signum      = error "signum called on (ASig (S n))"
  fromInteger = S_A . Cnst . fromInteger

instance Fractional (ASig (S n)) where
  (S_A a) / (S_A b) = S_A . CVar $ printf "(%s / %s)" (getLbl a) (getLbl b)
  fromRational = S_A . Cnst . fromRational

instance Show (KSig (S n)) where
  show (S_K s) = show s

instance Eq (KSig (S n)) where
  (S_K a) == (S_K b) = a == b

instance Num (KSig (S n)) where
  (S_K a) + (S_K b) = S_K . CVar $ printf "(%s + %s)" (getLbl a) (getLbl b)
  (S_K a) - (S_K b) = S_K . CVar $ printf "(%s - %s)" (getLbl a) (getLbl b)
  (S_K a) * (S_K b) = S_K . CVar $ printf "(%s * %s)" (getLbl a) (getLbl b)
  abs _       = error "abs called on (KSig (S n))"
  signum      = error "signum called on (KSig (S n))"
  fromInteger = S_K . Cnst . fromInteger

instance Fractional (KSig (S n)) where
  (S_K a) / (S_K b) = S_K . CVar $ printf "(%s / %s)" (getLbl a) (getLbl b)
  fromRational = S_K . Cnst . fromRational

instance Show (INum (S n)) where
  show (S_IN s) = show s

instance Eq (INum (S n)) where
  (S_IN a) == (S_IN b) = a == b

instance Num (INum (S n)) where
  (S_IN (Cnst a)) + (S_IN (Cnst b)) = S_IN $ Cnst (a+b)
  (S_IN a) + (S_IN b) = S_IN . CVar $ printf "(%s + %s)" (getLbl a) (getLbl b)
  (S_IN (Cnst a)) - (S_IN (Cnst b)) = S_IN $ Cnst (a-b)
  (S_IN a) - (S_IN b) = S_IN . CVar $ printf "(%s - %s)" (getLbl a) (getLbl b)
  (S_IN (Cnst a)) * (S_IN (Cnst b)) = S_IN $ Cnst (a*b)
  (S_IN a) * (S_IN b) = S_IN . CVar $ printf "(%s * %s)" (getLbl a) (getLbl b)
  abs _       = error "abs called on (INum (S n))"
  signum      = error "signum called on (INum (S n))"
  fromInteger = S_IN . Cnst . fromInteger

instance Fractional (INum (S n)) where
  (S_IN (Cnst a)) / (S_IN (Cnst b)) = S_IN . Cnst $ a/b
  (S_IN a) / (S_IN b) = S_IN . CVar $ printf "(%s / %s)" (getLbl a) (getLbl b)
  fromRational = S_IN . Cnst . fromRational

instance Show s => Show ((S n) s) where
  show s = show $ evalS s defaultRType

instance Eq e => Eq ((S n) e) where
  e1 == e2 = evalS ((==) <$> e1 <*> e2) defaultRType

instance Num e => Num ((S n) e) where
  e1 + e2     = (+) <$> e1 <*> e2
  e1 - e2     = (-) <$> e1 <*> e2
  e1 * e2     = (*) <$> e1 <*> e2
  abs         = fmap Prelude.abs
  signum      = fmap signum
  fromInteger = pure . fromInteger

instance Fractional e => Fractional ((S n) e) where
  e1 / e2 = (/) <$> e1 <*> e2
  recip   = fmap recip
  fromRational = pure . fromRational

-- ---------------------------------
-- ---------------------------------
-- core language extensions

-- | supports assignment
instance Varable a => Asn (S n) a where
  asn = mkOp1 "="

-- | supports output
instance Out (S n) where
  out = tellOrc . printf "       out %s\n" . getVarLbl
  outs a1 a2 = do
    (tellOrc $ printf "       outs %s, %s\n" (getVarLbl a1) (getVarLbl a2))
  outq a1 a2 a3 a4 = do
    (tellOrc $ printf "       outs %s, %s, %s, %s\n"
      (getVarLbl a1) (getVarLbl a2) (getVarLbl a3) (getVarLbl a4))
  outo a1 a2 a3 a4 a5 a6 a7 a8 = do
    (tellOrc $ printf "       outs %s, %s, %s, %s, %s, %s, %s, %s\n"
      (getVarLbl a1) (getVarLbl a2) (getVarLbl a3) (getVarLbl a4)
      (getVarLbl a5) (getVarLbl a6) (getVarLbl a7) (getVarLbl a8))

-- ---------------------------------
-- ---------------------------------
-- Buffers (function tables)

-- | creation of buffers
instance Buffer (S n) where
  type Buf (S n) = INum (S n)
  emptyBuffer sz = do
    bufNum <- mkName T
    mkScoreBuf bufNum sz
    return $ fromIntegral bufNum

instance (Varable a) => BufferR (S n) a a where
  lookupAt = flip (mkOp2 "tablei")

-- | creation of host-variable buffers
instance VBuf (S n) where
  vbuf lbl sz = do
    (SState cmap gen imap) <- get
    case M.lookup (CKC $ cacheBuf lbl sz) cmap of
      Just nm -> return $ S_IN nm
      Nothing -> do
        bufNum <- mkName T
        mkScoreBuf bufNum sz
        tellHost . HostOut (TblBus sz) lbl $ show bufNum
        let res = Cnst $ fromIntegral bufNum
        cache (CKC $ cacheBuf lbl sz) res
        return $ S_IN res

-- ---------------------------------
-- ---------------------------------
-- Instruments

instance (Nat m, Nat n, Show n, m :>=: n) => NumArgs S m n where
  getArg n = return . S_IN . CVar $ 'p': show n

-- | Labelled blocks (basic instrument creation)
instance LblBlock (S n) where
  type ArgTag (S n) = n
  type ArgTyp (S n) = Either String Double
  data Block (S n) = Instr Int
  lblBlock num e = do
    isCached <- instrInCache num
    when (Prelude.not isCached) $ do
      tellOrc (printf "instr %d\n" num) >> e >> tellOrc "endin\n\n"
      clearBlockData
      cacheInstr num
    return $ Instr num
  runBlock = runBlock'

getArgVal :: Either String Double -> String
getArgVal = either show show

makeInstrument :: S n () -> S n (Block (S n))
makeInstrument e = do
  SState _ _ imap <- get
  maybe (lblBlock 1 e) (\(n, _) -> lblBlock (n+1) e) $ S.maxView imap

-- | After labelling a block (csound instr), we need to clear the opcode
-- caches, otherwise code could be invalidly shared between instruments.
-- We can clear the name generator too (except for table nums).
clearBlockData :: S n ()
clearBlockData = do
  (SState cmap genmap imap) <- get
  let cmap' = M.mapMaybeWithKey cmf cmap
      genmap' = M.mapMaybeWithKey gmf genmap
  put $ SState cmap' genmap' imap
 where
  cmf k       v = Just v
  gmf T n       = Just n
  gmf _ _       = Nothing

-- | Runs a block (instr).
runBlock' ::
  Block (S n)
  -> Double
  -> Double
  -> TList m (Either String Double)
  -> S n ()
runBlock' (Instr blkLbl) startIn dur args =
  writeCard . Card IC $ [show blkLbl, show startIn, show dur]
                        ++ map getArgVal (unTList args)

-- runBlock' should have the constraint (n :>=: m), but that doesn't work
-- because the constraint isn't passed through the LblBlock class.
-- It's not a problem because the constraint is enforced by "getArg", so
-- only valid arguments will be used.

-- | Writes the header data
writeHeader :: Int -> Double -> S n ()
writeHeader nc dbfs = do
  (sr,ksmps) <- ask
  setSR sr
  setKSmps ksmps
  nchnls nc
  set0dbfs dbfs
  return ()

setSR n       = tellOrc (printf "sr = %i\n" n) >> return n
setKSmps n    = tellOrc (printf "ksmps = %i\n" n) >> return n

-- ---------------------------------
-- ---------------------------------
-- phasor
instance Phasor (S n) (KSig (S n)) (KSig (S n)) where
  phasor = mkOp1 "phasor"

instance (Varable a) => Phasor (S n) (ASig (S n)) a where
  phasor = mkOp1 "phasor"

-- oscillators

-- | Supports table-lookup oscillators
instance (Varable a, Varable b, PVar out) => Oscil (S n) out a b where
  oscil   = mkOp3 "oscil3"
  oscil'  = mkOp4 "oscil3"

-- ---------------------------------
-- ---------------------------------
-- delays
instance Delay (S n) where
  delaySamp = mkOp1 "delay1"
  vdelay3   = mkOp3 "vdelay3"

-- delay networks
instance DelayNet (S n) where
  type DelayN (S n) = SDelay n
  runDelay  = runDelay'
  tapA      = tap'
  tapK      = tap'
  tapI      = tap'

newtype SDelay n a = SDelay { unDelay :: S n a }
  deriving (Functor)

instance Applicative (SDelay n) where
  pure = SDelay . pure
  (SDelay a) <*> (SDelay b) = SDelay (a <*> b)

runDelay' :: INum (S n) -> ASig (S n) -> SDelay n a -> S n a
runDelay' maxdel insig dl = do
  tellOrc $ printf "aNull delayr %s\n" (getVarLbl maxdel)
  v <- unDelay dl
  tellOrc $ printf "    delayw %s\n" (getVarLbl insig)
  return v

tap' :: Varable b => b -> SDelay n (ASig (S n))
tap' dtime = SDelay $ mkOp1 "deltap3" dtime


-- ---------------------------------
-- ---------------------------------
-- test instruments

-- | very basic block
t1 = do
  v <- csig 1000
  outs v v

-- | allocate a "variable buffer" (i.e. host-updated buffer)
t2 = do
  buf <- vbuf "a buffer" 8192
  so <- oscil 1000 440 buf
  outs so so

-- | delay networks

-- | simple delay with constant signal
t3 = do
  so <- runDelay 1 1000 (tapK 1)
  outs so so

t4 = do
  buf <- vbuf "a buffer" 8192
  so <- oscil 1000 440 buf
  let d = (\d1 d2 -> 0.5*d1+0.25*d2) <$> tapI 1 <*> tapI 2
  dl <- runDelay 2 so d
  sig <- asn $ so + dl
  outs sig sig

t5 = do
  buf <- vbuf "a buffer" 8192
  so  <- oscil 1000 440 buf
  let d = (\d1 d2 -> 0.5*d1+0.25*d2) <$> tapI 1 <*> tapI 2
  rec dl <- runDelay 2 (so+dl) d
  sig <- asn $ so + dl
  outs sig sig

t5' = do
  buf <- vbuf "a buffer" 8192
  so  <- oscil 1000 440 buf
  let d = (\d1 d2 -> 0.5*d1+0.25*d2) <$> tapI 1 <*> tapI 2
  rec dl <- runDelay 2 (so + 0.1*dl) d
  sig <- asn $ so + dl
  outs sig sig

-- | additive synthesis.  Shared buffer, uses the Num instance for signals.
t6 = do
  so  <- zipWithM (\fq buf -> oscil 1000 (fq*110) buf) [4..] (replicate 20 1)
  so' <- asn $ sum so
  outs so' so'

-- | using fold to make an oscil stack.  Note that emptyBuffer is *not*
-- memoized.
t8 = do
  buf <- emptyBuffer 8192
  stack <- foldM (\a f -> f a) (40)
                       (replicate 4 (\fq -> oscil 1000 fq buf))
  outs stack stack

-- | It's necessary to use monadic sequencing (here implicit in makeInstruments)
-- to chain instruments together
all1 = mapM makeInstrument [t1,t2, t3, t4, t5', t6, t8]