% 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 (bDur-len, plc) > else (len-bDur, tail plc) > musicDur :: (Num a) => a -> a -> [Bool] -> a > musicDur fDur bDir plc = > sum (zipWith const (fDur : repeat bDir) (takeWhile id plc)) > -- sum (map fst (takeWhile snd (zip (fDur : repeat bDir) 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 (d-2%+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}