\section{Interpretation and Performance} \label{performance} \begin{verbatim} > module Haskore.Performance > ( module Haskore.Performance > , module Haskore.Basics > -- module Players > ) where > > import Haskore.Basics > -- import Haskore.Players > instance Show (a -> b) where > showsPrec p f = showString "<>" \end{verbatim} Now that we have defined the structure of musical objects, let us turn to the issue of {\em performance}, which we define as a temporally ordered sequence of musical {\em events}: \begin{verbatim} > type Performance = [Event] > > data Event = Event {eTime :: Time, eInst :: IName, ePitch :: AbsPitch, > eDur :: DurT, eVol :: Volume, pFields :: [Float]} > deriving (Eq,Ord,Show) > > type Time = Float > type DurT = Float > type Volume = Float \end{verbatim} An event is the lowest of our music representations not yet committed to Midi, csound, or the MusicKit. An event {\tt Event \{eTime = s, eInst = i, ePitch = p, eDur = d, eVol = v\}} captures the fact that at start time {\tt s}, instrument {\tt i} sounds pitch {\tt p} with volume {\tt v} for a duration {\tt d} (where now duration is measured in seconds, rather than beats). To generate a complete performance of, i.e.\ give an interpretation to, a musical object, we must know the time to begin the performance, and the proper volume, key and tempo. We must also know what {\em players} to use; that is, we need a mapping from the {\tt PName}s in an abstract musical object to the actual players to be used. (We don't yet need a mapping from abstract {\tt INames} to instruments, since this is handled in the translation from a performance into, say, Midi, such as defined in Section \ref{midi}.) We can thus model a performer as a function {\tt perform} which maps all of this information and a musical object into a performance: \begin{verbatim} > perform :: PMap -> Context -> Music -> Performance > > type PMap = PName -> Player > data Context = Context {cTime :: Time, cPlayer :: Player, cInst :: IName, > cDur :: DurT, cKey :: Key, cVol :: Volume} > deriving Show > type Key = AbsPitch perform pmap c@Context {cTime = t, cPlayer = pl, cDur = dt, cKey = k} m = case m of Note p d nas -> playNote pl c p d nas Rest d -> [] m1 :+: m2 -> perform pmap c m1 ++ perform pmap (c {cTime = t + dur m1 * dt}) m2 m1 :=: m2 -> merge (perform pmap c m1) (perform pmap c m2) Tempo a m -> perform pmap (c {cDur = dt / rtof a}) m Trans p m -> perform pmap (c {cKey = k + p}) m Instr nm m -> perform pmap (c {cInst = nm}) m Player nm m -> perform pmap (c {cPlayer = pmap nm}) m Phrase pas m -> interpPhrase pl pmap c pas m \end{verbatim} \begin{figure} \begin{verbatim} > perform pmap c m = fst (perf pmap c m) > > perf :: PMap -> Context -> Music -> (Performance, DurT) > > perf pmap c@Context {cTime = t, cPlayer = pl, cDur = dt, cKey = k} m = > case m of > Note p d nas -> (playNote pl c p d nas, rtof d *dt) > Rest d -> ([], rtof d *dt) > m1 :+: m2 -> let (pf1,d1) = perf pmap c m1 > (pf2,d2) = perf pmap (c {cTime = t+d1}) m2 > in (pf1++pf2, d1+d2) > m1 :=: m2 -> let (pf1,d1) = perf pmap c m1 > (pf2,d2) = perf pmap c m2 > in (merge pf1 pf2, max d1 d2) > Tempo a m -> perf pmap (c {cDur = dt / rtof a}) m > Trans p m -> perf pmap (c {cKey = k + p}) m > Instr nm m -> perf pmap (c {cInst = nm}) m > Player nm m -> perf pmap (c {cPlayer = pmap nm}) m > Phrase pas m -> interpPhrase pl pmap c pas m \end{verbatim} \caption{The ``real'' {\tt perform} function.} \label{real-perform} \end{figure} Some things to note: \begin{enumerate} \item The {\tt Context} is the running ``state'' of the performance, and gets updated in several different ways. For example, the interpretation of the {\tt Tempo} constructor involves scaling {\tt dt} appropriately and updating the {\tt DurT} field of the context. \item Interpretation of notes and phrases is player dependent. Ultimately a single note is played by the {\tt playNote} function, which takes the player as an argument. Similarly, phrase interpretation is also player dependent, reflected in the use of {\tt interpPhrase}. Precisely how these two functions work is described in Section \ref{players}. \item The {\tt DurT} component of the context is the duration, in seconds, of one whole note. To make it easier to compute, we can define a ``metronome'' function that, given a standard metronome marking (in beats per minute) and the note type associated with one beat (quarter note, eighth note, etc.) generates the duration of one whole note: \begin{verbatim} > metro :: Float -> Dur -> DurT > metro setting dur = 60 / (setting * rtof dur) \end{verbatim} Thus, for example, {\tt metro 96 qn} creates a tempo of 96 quarter notes per minute. \item In the treatment of {\tt (:+:)}, note that the sub-sequences are appended together, with the start time of the second argument delayed by the duration of the first. The function {\tt dur} (defined in Section \ref{basic-examples}) is used to compute this duration. Note that this results in a quadratic time complexity for {\tt perform}. A more efficient solution is to have {\tt perform} compute the duration directly, returning it as part of its result. This version of {\tt perform} is shown in Figure \ref{real-perform}. \item In contrast, the sub-sequences derived from the arguments to {\tt (:=:)} are merged into a time-ordered stream. The definition of {\tt merge} is given below. \end{enumerate} \begin{verbatim} > merge :: Performance -> Performance -> Performance merge a@(e1:es1) b@(e2:es2) = if e1 < e2 then e1 : merge es1 b else e2 : merge a es2 merge [] es2 = es2 merge es1 [] = es1 \end{verbatim} Note that {\tt merge} compares entire events rather than just start times. This is to ensure that it is commutative, a desirable condition for some of the proofs used in Section \ref{equivalence}. Here is a more efficient version that will work just as well in practice: \begin{verbatim} > merge a@(e1:es1) b@(e2:es2) = > if eTime e1 < eTime e2 then e1 : merge es1 b > else e2 : merge a es2 > merge [] es2 = es2 > merge es1 [] = es1 \end{verbatim} \section{Players} \label{players} \begin{verbatim} module Players (module Players, module Music, module Performance) where import Music import Performance \end{verbatim} In the last section we saw how a performance involved the notion of a {\em player}. The reason for this is the same as for real players and their instruments: many of the note and phrase attributes (see Section \ref{phrasing}) are player and instrument dependent. For example, how should ``legato'' be interpreted in a performance? Or ``diminuendo?'' Different players interpret things in different ways, of course, but even more fundamental is the fact that a pianist, for example, realizes legato in a way fundamentally different from the way a violinist does, because of differences in their instruments. Similarly, diminuendo on a piano and a harpsichord are different concepts. With a slight stretch of the imagination, we can even consider a ``notator'' of a score as a kind of player: exactly how the music is rendered on the written page may be a personal, stylized process. For example, how many, and which staves should be used to notate a particular instrument? In any case, to handle these issues, Haskore has a notion of a {\em player} which ``knows'' about differences with respect to performance and notation. A Haskore player is a 4-tuple consisting of a name and three functions: one for interpreting notes, one for phrases, and one for producing a properly notated score. \begin{verbatim} > data Player = MkPlayer { pName :: PName, > playNote :: NoteFun, > interpPhrase :: PhraseFun, > notatePlayer :: NotateFun } > deriving Show > type NoteFun = > Context -> Pitch -> Dur -> [NoteAttribute] -> Performance > type PhraseFun = > PMap -> Context -> [PhraseAttribute] -> Music -> (Performance,DurT) > type NotateFun = () \end{verbatim} The last line above is because notation is currently not implemented. Note that both {\tt NoteFun} and {\tt PhraseFun} functions return a {\tt Performance} (imported from module {\tt Perform}). \begin{figure} \begin{verbatim} > defPlayer :: Player > defPlayer = MkPlayer { pName = "Default", > playNote = defPlayNote defNasHandler, > interpPhrase = defInterpPhrase defPasHandler, > notatePlayer = defNotatePlayer () } > > defPlayNote :: (Context->NoteAttribute->Event->Event) -> NoteFun > defPlayNote nasHandler > c@(Context cTime cPlayer cInst cDur cKey cVol) p d nas = > [ foldr (nasHandler c) > (Event {eTime = cTime, eInst = cInst, > ePitch = absPitch p + cKey, > eDur = rtof d * cDur, eVol = cVol, > pFields = []}) > nas ] > > defNasHandler :: Context-> NoteAttribute -> Event -> Event > defNasHandler c (Volume v) ev = ev {eVol = (cVol c + v)/2} > defNasHandler c (PFields pfs) ev = ev {pFields = pfs} > defNasHandler _ _ ev = ev > > defInterpPhrase :: (PhraseAttribute->Performance->Performance) -> PhraseFun > defInterpPhrase pasHandler pmap context pas m = > let (pf,dur) = perf pmap context m > in (foldr pasHandler pf pas, dur) > > defPasHandler :: PhraseAttribute -> Performance -> Performance > defPasHandler (Dyn (Accent x)) pf = map (\e -> e {eVol = x * eVol e}) pf > defPasHandler (Art (Staccato x)) pf = map (\e -> e {eDur = x * eDur e}) pf > defPasHandler (Art (Legato x)) pf = map (\e -> e {eDur = x * eDur e}) pf > defPasHandler _ pf = pf > > defNotatePlayer :: () -> NotateFun > defNotatePlayer _ = () \end{verbatim} \caption{Definition of default Player {\tt defPlayer}.} \label{default-Player} \end{figure} \subsection{Examples of Player Construction} A ``default player'' called {\tt defPlayer} (not to be confused with ``deaf player''!) is defined for use when none other is specified in the score; it also functions as a base from which other players can be derived. {\tt defPlayer} responds only to the {\tt Volume} note attribute and to the {\tt Accent}, {\tt Staccato}, and {\tt Legato} phrase attributes. It is defined in Figure \ref{default-Player}. Before reading this code, recall how players are invoked by the {\tt perform} function defined in the last section; in particular, note the calls to {\tt playNote} and {\tt interpPhase} defined above. Then note: \begin{enumerate} \item {\tt defPlayNote} is the only function (even in the definition of {\tt perform}) that actually generates an event. It also modifies that event based on an interpretation of each note attribute by the function {\tt defHasHandler}. \item {\tt defNasHandler} only recognizes the {\tt Volume} attribute, which it uses to set the event volume accordingly. \item {\tt defInterpPhrase} calls (mutually recursively) {\tt perform} to interpret a phrase, and then modifies the result based on an interpretation of each phrase attribute by the function {\tt defPasHandler}. \item {\tt defPasHandler} only recognizes the {\tt Accent}, {\tt Staccato}, and {\tt Legato} phrase attributes. For each of these it uses the numeric argument as a ``scaling'' factor of the volume (for {\tt Accent}) and duration (for {\tt Staccato} and {\tt Lagato}). Thus {\tt (Phrase [Legato 1.1] m)} effectively increases the duration of each note in {\tt m} by 10\% (without changing the tempo). \end{enumerate} It should be clear that much of the code in Figure \ref{default-Player} can be re-used in defining a new player. For example, to define a player {\tt weird} that interprets note attributes just like {\tt defPlayer} but behaves differently with respect to phrase attributes, we could write: \begin{verbatim} weird :: Player weird = MkPlayer { pname = "Weirdo", playNote = defPlayNote defNasHandler, interpPhrase = defInterpPhrase myPasHandler notatePlayer = defNotatePlayer () } \end{verbatim} and then supply a suitable definition of {\tt myPasHandler}. That definition could also re-use code, in the following sense: suppose we wish to add an interpretation for {\tt Crescendo}, but otherwise have {\tt myPasHandler} behave just like {\tt defPasHandler}. \begin{verbatim} myPasHandler :: PhraseAttribute -> Performance -> Performance myPasHandler (Dyn (Crescendo x)) pf = ... myPasHandler pa pf = defPasHandler pa pf \end{verbatim} \begin{exercise} Fill in the {\tt ...} in the definition of {\tt myPasHandler} according to the following strategy: Assume $0<{\tt x}<1$. Gradually scale the volume of each event by a factor of $1.0$ through $1.0+{\tt x}$, using linear interpolation. \end{exercise} \begin{exercise} Choose some of the other phrase attributes and provide interpretations of them, such as {\tt Diminuendo}, {\tt Slurred}, {\tt Trill}, etc. (The {\tt trill} functions from section \ref{basic-examples} may be useful here.) \end{exercise} Figure \ref{fancy-Player} defines a relatively sophisticated player called {\tt fancyPlayer} that knows all that {\tt defPlayer} knows, and much more. Note that {\tt Slurred} is different from {\tt Legato} in that it doesn't extend the duration of the {\em last} note(s). The behavior of ${\tt (Ritardando }\ x{\tt )}$ can be explained as follows. We'd like to ``stretch'' the time of each event by a factor from $0$ to $x$, linearly interpolated based on how far along the musical phrase the event occurs. I.e., given a start time $t_0$ for the first event in the phrase, total phrase duration $D$, and event time $t$, the new event time $t'$ is given by: \[ t' = (1 + \frac{t-t_0}{D}x)(t-t_0) + t_0 \] Further, if $d$ is the duration of the event, then the end of the event $t+d$ gets stretched to a new time $t_d'$ given by: \[ t_d' = (1 + \frac{t+d-t_0}{D}x)(t+d-t_0) + t_0 \] The difference $t_d' - t'$ gives us the new, stretched duration $d'$, which after simplification is: \[ d' = (1 + \frac{2(t-t_0)+d}{D}x)d \] {\tt Accelerando} behaves in exactly the same way, except that it shortens event times rather than lengthening them. And, a similar but simpler strategy explains the behaviors of {\tt Crescendo} and {\tt Diminuendo}. \begin{figure}\small \begin{verbatim} > fancyPlayer :: Player > fancyPlayer = MkPlayer { pName = "Fancy", > playNote = defPlayNote defNasHandler, > interpPhrase = fancyInterpPhrase, > notatePlayer = defNotatePlayer () } > fancyInterpPhrase :: PhraseFun > fancyInterpPhrase pmap c [] m = perf pmap c m > fancyInterpPhrase pmap c@Context {cTime = t, cPlayer = pl, cInst = i, > cDur = dt, cKey = k, cVol = v} > (pa:pas) m = > let pfd@(pf,dur) = fancyInterpPhrase pmap c pas m > loud x = fancyInterpPhrase pmap c (Dyn (Loudness x) : pas) m > stretch x = let t0 = eTime (head pf); r = x/dur > upd (e@Event {eTime = t, eDur = d}) = > let dt = t-t0 > t' = (1+dt*r)*dt + t0 > d' = (1+(2*dt+d)*r)*d > in e {eTime = t', eDur = d'} > in (map upd pf, (1+x)*dur) > inflate x = let t0 = eTime (head pf); r = x/dur > upd (e@Event {eTime = t, eVol = v}) = > e {eVol = (1+(t-t0)*r)*v} > in (map upd pf, dur) > in case pa of > Dyn (Accent x) -> (map (\e-> e {eVol = x * eVol e}) pf, dur) > Dyn PPP -> loud 40 ; Dyn PP -> loud 50 ; Dyn P -> loud 60 > Dyn MP -> loud 70 ; Dyn SF -> loud 80 ; Dyn MF -> loud 90 > Dyn NF -> loud 100 ; Dyn FF -> loud 110 ; Dyn FFF -> loud 120 > Dyn (Loudness x) -> fancyInterpPhrase pmap c {cVol = x} pas m > Dyn (Crescendo x) -> inflate x ; Dyn (Diminuendo x) -> inflate (-x) > Dyn (Ritardando x) -> stretch x ; Dyn (Accelerando x) -> stretch (-x) > Art (Staccato x) -> (map (\e-> e {eDur = x * eDur e}) pf, dur) > Art (Legato x) -> (map (\e-> e {eDur = x * eDur e}) pf, dur) > Art (Slurred x) -> > let lastStartTime = foldr (\e t -> max (eTime e) t) 0 pf > setDur e = if eTime e < lastStartTime > then e {eDur = x * eDur e} > else e > in (map setDur pf, dur) > Art _ -> pfd -- Remaining articulations: > {- Tenuto | Marcato | Pedal | Fermata | FermataDown > -- | Breath | DownBow | UpBow | Harmonic | Pizzicato > -- | LeftPizz | BartokPizz | Swell | Wedge | Thumb | Stopped -} > Orn _ -> pfd -- Remaining ornamenations: > {- Trill | Mordent | InvMordent | DoubleMordent | Turn > -- | TrilledTurn | ShortTrill | Arpeggio | ArpeggioUp > -- | ArpeggioDown | Instruction String | Head NoteHead -} > -- Design Bug: To do these right we need to keep the KEY SIGNATURE > -- around so that we can determine, for example, what the trill note is. > -- Alternatively, provide an argument to Trill to carry this info. \end{verbatim} \caption{Definition of Player {\tt fancyPlayer}.} \label{fancy-Player} \end{figure}