% from AutoTrack by Stefan Ratschan
\section{Styles}
\begin{haskelllisting}
> module Haskore.Interface.AutoTrack.Style
> (T, playToStream, jazz, bossa, takeFive, rock,
> thomasCarib, harmonic) where
> import Data.Bool.HT (select, )
> import Data.List.HT (viewR, )
> import Haskore.Basic.Duration (en, qn, (%+), )
> import Haskore.Music ((+:+), (=:=), )
> import qualified Haskore.Composition.Rhythm as Rhythm
> import qualified Haskore.Composition.Drum as Drum
> import qualified Haskore.Basic.Duration as Dur
> import qualified Haskore.Basic.Pitch as Pitch
> import qualified Haskore.Music as Music
> import qualified Haskore.Music.GeneralMIDI as MidiMusic
> import qualified Haskore.Melody as Melody
> import qualified Haskore.Interface.MIDI.Render as MidiRender
> import qualified Sound.MIDI.File.Save as MidiSave
> import qualified Haskore.Interface.AutoTrack.Transposeable as Transposeable
> import qualified Haskore.Interface.AutoTrack.ChordSymbol as ChordSymbol
> import qualified Haskore.Interface.AutoTrack.ChartBar as ChartBar
> import qualified Haskore.Interface.AutoTrack.ChordChart as ChordChart
> import qualified Haskore.Interface.AutoTrack.EventChart as EventChart
> import qualified Haskore.Interface.AutoTrack.Instrument as Instrument
> import qualified Data.ByteString.Lazy as B
\end{haskelllisting}
A style takes a chord chart and creates some music out of it.
\begin{haskelllisting}
> type T = ChordChart.T -> MidiMusic.T
> type TMelody = ChordChart.T -> Melody.T ()
\end{haskelllisting}
\subsection{Filtering music}
Filtering certain parts from music,
in order to introduce rests \emph{after} the creation of some music.
The needed information can be encoded in several ways:
\begin{enumerate}
\item [ (Music.Dur, Music.Dur) ]: Place of rest, length of rest, sorted
\item [ Music.Dur ]: Place to switch from rest to music, or other way round
\item Music.Dur [ Bool ]: Some basic duration and then True implies music, False implies Rest
\end{enumerate}
We use the third possibility here, but use a helper function with a more general
interface, which additionally specifies the length of the first list member
(different from the basic duration).
\begin{haskelllisting}
> filterMusic :: Music.Dur -> [ Bool ] -> Music.T note -> Music.T note
> filterMusic = fm 0
> fm :: Music.Dur -> Music.Dur -> [ Bool ] -> Music.T note -> Music.T note
> fm fDur bDur plc =
> Music.switchBinary
> (\dur at -> case at of
> (Just _) -> Music.atom (min dur (musicDur fDur bDur plc)) at
> (Nothing) -> Music.rest dur)
> (\ctrl m -> case ctrl of
> (Music.Tempo t) -> Music.changeTempo t (fm (fDur*t) (bDur*t) plc m)
> _ -> Music.control ctrl m)
> (\m0 m1 -> let m0' = fm fDur bDur plc m0
> (rFDur, rPlc) = remLen bDur plc (Music.dur m0 fDur)
> m1' = fm rFDur bDur rPlc m1
> in m0' +:+ m1')
> (\m0 m1 -> fm fDur bDur plc m0 =:= fm fDur bDur plc m1)
> (Music.rest 0)
> remLen :: Music.Dur -> [ Bool ] -> Music.Dur -> (Music.Dur, [ Bool ])
> remLen bDur plc len =
> if bDur>len
> then (bDurlen, plc)
> else (lenbDur, tail plc)
> musicDur :: (Num a) => a -> a -> [Bool] -> a
> musicDur fDur bDir plc =
> sum (zipWith const (fDur : repeat bDir) (takeWhile id plc))
>
\end{haskelllisting}
\subsection{Playing Styles}
Playing a chord chart and style into a stream of binary MIDI data.
We abuse a String to store it.
\begin{haskelllisting}
> playToStream :: Int -> T -> Integer -> Int -> ChordChart.T -> B.ByteString
> playToStream trans style tempo chornum chart =
> let countin = Rhythm.countIn (ChartBar.dur (head (ChordChart.bars chart)))
> choruses = Music.replicate chornum (style (Transposeable.transpose trans chart))
> music = Music.changeTempo (tempo%+60) (countin +:+ choruses)
> in MidiSave.toByteString (MidiRender.generalMidiDeflt music)
\end{haskelllisting}
\subsection{Drum Fill}
\begin{haskelllisting}
> jazzFill :: Music.Dur -> MidiMusic.T
> jazzFill d =
> if d >= 2%+4
> then
> let shuffle dr =
> Rhythm.toShuffledMusicWithDrumUnit en dr . Rhythm.fromString
> in Music.rest (d2%+4) +:+
> (shuffle Drum.SplashCymbal "...x" =:=
> shuffle Drum.AcousticBassDrum "...x" =:=
> shuffle Drum.AcousticSnare ".xx.")
> else error "jazzFill: d must be at least 2%+4"
> endFill :: [ ChartBar.T ] -> MidiMusic.T
> endFill l =
> let Just (initLd,lastLd) = viewR $ map ChartBar.dur l
> in Music.line (map Music.rest initLd) +:+
> jazzFill lastLd
\end{haskelllisting}
\subsection{Bass Lines}
First some auxiliary function to play the bass note of a chord.
\begin{haskelllisting}
> bassFromMelody :: Melody.T () -> MidiMusic.T
> bassFromMelody =
> MidiMusic.fromMelodyNullAttr MidiMusic.AcousticBass
> bassChoose :: (Music.Dur, ChordSymbol.T) -> Melody.T ()
> bassChoose (l, (ChordSymbol.Cons _ b _)) = bassNote l b
> bassNote :: Music.Dur -> Pitch.Class -> Melody.T ()
> bassNote l b =
> Melody.note (Instrument.bottomRange Instrument.bass b) l ()
\end{haskelllisting}
\subsubsection{Chart Bass}
This bass line style plays the root of a chord on every chord of a chord chart.
\begin{haskelllisting}
> evFromCC :: ChordChart.T -> [(Music.Dur, ChordSymbol.T)]
> evFromCC = EventChart.events . EventChart.fromChordChart
> chartBass :: TMelody
> chartBass =
> Music.line . map bassChoose . evFromCC
\end{haskelllisting}
\subsubsection{Quarter Bass}
This bass line style plays the root of the current chord on every quarter note.
It first creates chords on every beat, then maps bassChoose to it.
Problem: Right now only works if all chords are on quarter notes!
\begin{haskelllisting}
> splitToDur :: Music.Dur -> [ ( Music.Dur, e ) ] -> [ ( Music.Dur, e ) ]
> splitToDur sd =
> concatMap (\(d,e) -> replicate (fromInteger (Dur.divide d sd)) (sd, e))
> quarterBass :: TMelody
> quarterBass =
> Music.line . map bassChoose . splitToDur (1%+4) . evFromCC
> eighthBass :: TMelody
> eighthBass =
> Music.line . map bassChoose . splitToDur (1%+8) . evFromCC
\end{haskelllisting}
\subsubsection{Bossa Bass}
A simple bass for Bossas using the bass note and its fifth.
\begin{haskelllisting}
> bossaBass :: TMelody
> bossaBass = Music.line . map bossaBassC . evFromCC
> bossaBassC :: (Music.Dur, ChordSymbol.T) -> Melody.T ()
> bossaBassC (l, ch@(ChordSymbol.Cons r _ _)) =
> let r7 = Transposeable.transpose 7 r
> bossa' = bassNote (3%+8) r +:+ bassNote (1%+8) r7 +:+
> bassNote (1%+2) r7 +:+
> bossaBassC (l 1%+1, ch)
> in select (bassChoose (l, ch))
> [(l >= 1%+1, bossa'),
> (l >= 1%+2, bassNote (3%+8) r +:+ bassNote (1%+8) r)]
\end{haskelllisting}
\subsubsection{Walking Bass Line}
Creating a good walking bass is a science in itself. There are numerous books which give
various rules for creating good bass lines. The following code is still VERY experimental
and just follows these basic rules:
\begin{itemize}
\item Create the root on the first quarter note of a chord, and
\item create random quarter notes of the appropriate scale for the rest.
\end{itemize}
We do this by creating a walking bass line for every chord of a chart separately
and then concatenating the created bass lines.
\begin{haskelllisting}
walking :: T
walking = Music.line . map walkChord . evFromCC c
\end{haskelllisting}
Walking bass line for a single chord of a certain length. Take the root for the
first note and random notes for the rest.
\begin{haskelllisting}
walkChord :: (Music.Dur, ChordSymbol.T) -> Melody.T ()
walkChord (d, ch) | (divisible d (1%+4)) =
bassChoose ((1%+4), ch) +:+ walkRandom ((divide d (1%+4))-1) ch
\end{haskelllisting}
Create a random walking bass line of n quarter notes using chord ch.
\begin{haskelllisting}
walkRandom :: Int -> ChordSymbol.T -> Melody.T ()
walkRandom n ch = let scale = (chordToScale ch)
choice = \n -> bassChooseR n (1%+4, scale)
in line (map choice (take n (randList (length scale))))
bassChooseR :: Int -> (Music.Dur, Scale) -> Melody.T ()
bassChooseR n (d, s) = Melody.note d (pitch (s!!n)) ()
\end{haskelllisting}
\subsection{Full Styles}
The jazz style works for 3/4 and 4/4 measure. It currently does not yet use walking bass,
but uses the quarter bass style above.
\begin{haskelllisting}
> jazzDrum :: Music.Dur -> MidiMusic.T
> jazzDrum d =
> select (error "jazzDrum supports only 3%+4 and 4%+4")
> [(d==3%+4, Rhythm.jazzWaltzRideP Drum.RideCymbal2 =:=
> Rhythm.jazzWaltzHiHatP Drum.PedalHiHat),
> (d==4%+4, Music.replicate 2
> (Rhythm.jazzRideP Drum.RideCymbal2 =:=
> Rhythm.backBeatP Drum.PedalHiHat))]
> jazz :: T
> jazz s = let drums = Music.line (map (jazzDrum . ChartBar.dur) (ChordChart.bars s)) =:=
> endFill (ChordChart.bars s)
> (bd, hc) = ChordChart.hasChord s
> in filterMusic bd hc drums =:=
> bassFromMelody (quarterBass s)
>
\end{haskelllisting}
The bossa style just plays the usual bossa clave with the hi-hat on the backbeat and some
simple bass.
\begin{haskelllisting}
> bossa :: T
> bossa c = let drums = Music.repeat Rhythm.claveBossa =:=
> Music.repeat Rhythm.ride =:=
> Music.repeat (Rhythm.backBeatP Drum.PedalHiHat)
> bass = bassFromMelody (bossaBass c)
> in Music.take ((4 * ChordChart.length c) %+ 4) drums =:= bass
\end{haskelllisting}
The Take-Five style works for charts with 5/4 measures only.
\begin{haskelllisting}
> takeFiveBass :: ChartBar.T -> Melody.T ()
> takeFiveBass b =
> if ChartBar.dur b == 5%+4 && length (ChartBar.chords b) <= 2
> then
> let c=ChartBar.chords b
> bass d Nothing = Music.rest d
> bass d (Just x) = bassChoose (d, x)
> in if length c == 2
> then bass (3%+4) (c!!0) +:+ bass (2%+4) (c!!1)
> else bass (3%+4) (c!!0) +:+ bass (2%+4) (c!!0)
> else error "takeFiveBass: only allowed for 5%+4 and maximally 2 chords per bar"
> takeFive :: T
> takeFive (ChordChart.Cons l) =
> let rep pat = concat (replicate (length l) (Rhythm.fromString pat))
> hiHatR = rep "..x .x"
> cymbalR = rep "x. xx x. x. xx"
> in Rhythm.toMusicWithDrumUnit qn Drum.PedalHiHat hiHatR =:=
> Rhythm.toShuffledMusicWithDrumUnit en Drum.RideCymbal2 cymbalR =:=
> endFill l =:=
> bassFromMelody (Music.line (map takeFiveBass l))
\end{haskelllisting}
The rock style just plays the usual hi-hat eights, bass drum on downbeat, snare on backbeat.
\begin{haskelllisting}
> rock :: T
> rock c = let drums = Music.repeat Rhythm.basicBassDrum =:=
> Music.repeat Rhythm.basicSnare =:=
> Music.repeat Rhythm.basicHiHat
> bass = bassFromMelody (eighthBass c)
> in Music.take ((4 * ChordChart.length c) %+ 4) drums =:= bass
\end{haskelllisting}
This style is not yet finished.
\begin{haskelllisting}
> thomasCarib :: T
> thomasCarib c =
> Rhythm.backBeatP Drum.PedalHiHat =:=
> Rhythm.basicBassDrum =:=
> Rhythm.toShuffledMusicWithDrumUnit en Drum.Claves
> (Rhythm.fromString ".. .x .x x.") =:=
> bassFromMelody (chartBass c)
\end{haskelllisting}
This is a rather simple style
where the tones of a chord a played simultaneously.
\begin{haskelllisting}
> harmonic :: T
> harmonic =
> let chordSymbolToMusic (dur, cs) = Music.chord $
> map (\p -> Melody.note p dur ()) $
> ChordSymbol.toChord cs
> in bassFromMelody . Music.line .
> map chordSymbolToMusic . evFromCC
\end{haskelllisting}