\subsubsection{The Score File}


> module Haskore.Interface.CSound.Score where
> import Haskore.Interface.CSound (Instrument, showInstrumentNumber, PField, Time)
> import qualified Haskore.Interface.CSound.Note as CSNote
> import qualified Haskore.Interface.CSound.Generator as Generator
> import Haskore.Interface.CSound.Generator
>           (compSine1, lineSeg1, randomTable, PStrength, RandDist(Uniform))
> import qualified Haskore.Music.Rhythmic        as RhyMusic
> import qualified Haskore.Performance           as Performance
> import qualified Haskore.Performance.BackEnd   as PerformanceBE
> import qualified Haskore.Performance.Context   as Context
> import qualified Haskore.Performance.Fancy     as FancyPf
> import qualified Data.EventList.Relative.TimeBody as TimeList
> import qualified Data.EventList.Absolute.TimeBody as TimeListAbs
> import qualified Haskore.Basic.Pitch           as Pitch
> import qualified Haskore.Interface.CSound.InstrumentMap as InstrMap
> import qualified Haskore.Interface.CSound.SoundMap as SoundMap

> import qualified Numeric.NonNegative.Class as NonNeg

> import System.IO


We will represent a score file as a sequence of \keyword{score statements}:

> type T = [Statement]

The {\tt Statement} data type is designed to simulate CSound's three
kinds of score statements:
\item A \keyword{tempo} statement, which sets the tempo.  In the absence
of a tempo statement, the tempo defaults to 60 beats per minute.

\item A \keyword{note event}, which defines the start time, pitch,
duration (in beats), volume (in decibels), and instrument to play a
note (and is thus more like a Haskore {\tt Event} than a Midi event,
thus making the conversion to CSound easier than to Midi, as we shall
see later).  Each note event also contains a number of optional
arguments called \keyword{p-fields}, which determine other properties of
the note, and whose interpretation depends on the instrument that
plays the note.  This will be discussed further in a later section.

\item \keyword{Function table} definitions.  A function table is used by
instruments to produce audio signals.  For example, sequencing through
a table containing a perfect sine wave will produce a very pure tone,
while a table containing an elaborate polynomial will produce a
complex sound with many overtones.  The tables can also be used to
produce control signals that modify other signals.  Perhaps the
simplest example of this is a tremolo or vibrato effect, but more
complex sound effects, and FM (frequency modulation) synthesis in
general, is possible.


> data Statement = Tempo Bpm
>                | Note  Instrument StartTime Duration Pch Volume [PField]
>                | Table Table CreatTime TableSize Normalize Generator.T
>      deriving Show
> type Bpm       = Int
> type StartTime = Time
> type Duration  = Time
> data Pch       = AbsPch Pitch.Absolute | Cps Float deriving Show
> type Volume    = Float
> type Table     = Int
> type CreatTime = Time
> type TableSize = Int
> type Normalize = Bool


This is all rather straightforward, except for function table
generation, which requires further explanation.


\subparagraph*{Common Tables}

For convenience, here are some common function tables, which take as
argument the identifier integer:

> simpleSine, square, sawtooth, triangle, whiteNoise :: Table -> Statement
> simpleSine n = Table n 0 8192 True
>                       (compSine1 [1])
> square     n = Table n 0 1024 True
>                       (lineSeg1 1 [(256, 1), (0, -1), (512, -1), (0, 1), (256, 1)])
> sawtooth   n = Table n 0 1024 True
>                       (lineSeg1 0 [(512, 1), (0, -1), (512, 0)])
> triangle   n = Table n 0 1024 True
>                       (lineSeg1 0 [(256, 1), (512, -1), (256, 0)])
> whiteNoise n = Table n 0 1024 True
>                       (randomTable Uniform)

The following function for a composite sine has an extra argument, a
list of harmonic partial strengths:

> compSine :: Table -> [PStrength] -> Statement
> compSine _ s = Table 6 0 8192 True (compSine1 s)



\paragraph{Converting Haskore Music.T to a CSound Score File}

To convert a {\tt Music.T} value into a CSound score file, we need to:
\item Convert the {\tt Music.T} value to a {\tt Performance.T}.
\item Convert the {\tt Performance.T} value to a {\tt Score.T}.
\item Write the {\tt Score.T} value to a CSound score file.

We already know how to do the first step.  Steps two and three will be
achieved by the following two functions:

> fromPerformanceBE :: (NonNeg.C time) =>
>    (time -> Time) ->
>    PerformanceBE.T time CSNote.T -> T

> saveIA :: T -> IO ()

The three steps can be put together in whatever way the user wishes,
but the most general way would be this:

> fromRhythmicMusic ::
>    (RealFrac time, NonNeg.C time, RealFrac dyn, Ord drum, Ord instr) =>
>       Tables ->
>       (InstrMap.SoundTable drum,
>        InstrMap.SoundTable instr,
>        Context.T time dyn (RhyMusic.Note drum instr),
>        RhyMusic.T drum instr) -> T
> fromRhythmicMusic tables (dMap, iMap, cont, m) =
>    tables ++ fromRhythmicPerformance dMap iMap
>                 (Performance.fromMusic FancyPf.map cont m)
> type Tables = T

The \type{Tables} argument is a user-defined set of function tables,
represented as a sequence of {\tt Statement}s (specifically, {\tt
Table} constructors).  (See \secref{function-table}.)

\subparagraph*{From Performance.T to Score.T}

The translation between \type{Performance.Event}s and score
\type{CSoundScore.Note}s is straightforward, the only tricky part being:
\item The unit of time in a {\tt Performance.T} is the second, whereas
in a {\tt Score.T} it is the beat.  However, the default CSound tempo is
60 beats per minute, or one beat per second, as was already mentioned,
and we use this default for our \keyword{score} files.  Thus the two are
equivalent, and no translation is necessary.
\item CSound wants to get pitch information in the form 'a.b'
but it interprets them very different.
Sometimes it is considered as 'octave.pitchclass'
sometimes it is considered as fraction frequency.
We try to cope with it using the two-constructor type Pch.
\item Like for MIDI data we must
distinguish between Velocity and Volume.
Velocity is instrument dependent and
different velocities might result in different flavors of a sound.
As a quick work-around we turn the velocity information into volume.
Cf. {\tt dbamp} in the CSound manual.


> fromPerformanceBE timeMap =
>    map (\(time, event) ->
>       noteToStatement timeMap time
>          (PerformanceBE.eventDur  event)
>          (PerformanceBE.eventNote event)) .
>    TimeListAbs.toPairList .
>    TimeList.toAbsoluteEventList 0
> fromRhythmicPerformance ::
>    (RealFrac time, NonNeg.C time, RealFrac dyn, Ord drum, Ord instr) =>
>    InstrMap.SoundTable drum ->
>    InstrMap.SoundTable instr ->
>       Performance.T time dyn (RhyMusic.Note drum instr) -> T
> fromRhythmicPerformance dMap iMap =
>    fromPerformanceBE realToFrac .
>    PerformanceBE.fromPerformance
>       (CSNote.fromRhyNote
>          (InstrMap.lookup dMap)
>          (InstrMap.lookup iMap))
> fromRhythmicPerformanceMap ::
>    (RealFrac time, NonNeg.C time, RealFrac dyn) =>
>    InstrMap.ToSound drum ->
>    InstrMap.ToSound instr ->
>       Performance.T time dyn (RhyMusic.Note drum instr) -> T
> fromRhythmicPerformanceMap dMap iMap =
>    fromPerformanceBE realToFrac .
>    PerformanceBE.fromPerformance (CSNote.fromRhyNote dMap iMap)
> fromRhythmicPerformanceWithAttributes ::
>    (RealFrac time, NonNeg.C time, RealFrac dyn) =>
>    SoundMap.DrumTableWithAttributes out drum ->
>    SoundMap.InstrumentTableWithAttributes out instr ->
>       Performance.T time dyn (RhyMusic.Note drum instr) -> T
> fromRhythmicPerformanceWithAttributes dMap iMap =
>    fromRhythmicPerformanceMap
>       (SoundMap.lookupDrum dMap)
>       (SoundMap.lookupInstrument iMap)
> noteToStatement ::
>    (time -> Time) -> time -> time ->
>       CSNote.T -> Statement
> noteToStatement timeMap t d (CSNote.Cons pfs v i p) =
>    Note i (timeMap t) (timeMap d)
>           (maybe (Cps 0 {- dummy -}) AbsPch p) v pfs


\subparagraph*{From Score to Score File}

Now that we have a value of type {\tt Score}, we must write it into a
plain text ASCII file with an extension {\tt .sco} in a way that
CSound will recognize.  This is done by the following function:

> saveIA s =
>    do putStr "\nName your score file "
>       putStr "(.sco extension will be added): "
>       name   <- getLine
>       save (name ++ ".sco") s

> save :: FilePath -> T -> IO ()
> save name s = writeFile (name ++ ".sco") (toString s)

This function asks the user for the name of the score file, opens that
file for writing, writes the score into the file using the function
\function{toString}, and then closes the file.

The score file is a plain text file containing one statement per line.
Each statement consists of an opcode, which is a single letter that
determines the action to be taken, and a number of arguments.  The
opcodes we will use are ``e'' for end of score, ``t'' to set tempo,
``f'' to create a function table, and ``i'' for note events.

> toString :: T -> String
> toString s = unlines (map statementToString s ++ ["e"])   -- end of score


Finally, the \function{statementToString} function:

> statementToString :: Statement -> String
> statementToString = unwords . statementToWords
> statementToWords :: Statement -> [String]
> statementToWords (Tempo t) =
>    ["t", "0", show t]
> statementToWords (Note i st d p v pfs) =
>    ["i", showInstrumentNumber i, show st, show d,
>     pchToString p, show v] ++ map show pfs
> statementToWords (Table t ct s n gr)   =
>    ["f", show t, show ct, show s,
>     (if n then id else ('-':))
>        (unwords (Generator.toStatementWords gr))]
> -- it's exciting whether CSound knows what we mean with the values
> -- (0 < note) is for compatibility with older CSound example files
> pchToString :: Pch -> String
> pchToString (AbsPch ap) =
>    let (oct, note) = divMod ap 12
>    in  show oct ++ "." ++
>        (if 0 < note && note < 10 then "0" else "") ++
>        show note
> pchToString (Cps freq) = show freq