---------------------------------------------------------------------------------- |-- Module : HarmTrace.Audio.Evaluation-- Copyright : (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford-- License : GPL3---- Maintainer : bash@cs.uu.nl, jpm@cs.ox.ac.uk-- Stability : experimental-- Portability : non-portable---- Summary: A module for evaluating chord and key annotations--------------------------------------------------------------------------------moduleHarmTrace.Audio.Evaluation(-- * Evaluation functionsrelCorrectOverlap,achievScore,chordChangeRatio,avgDistToOne-- * Chord and key equality functions,chordTriadEq,chordClassEq,majMinEq-- * Displaying evaluations ,printChordRCO,printRCO-- * Sampling ,sample)whereimportConstantsimportHarmTrace.Base.MusicTimeimportHarmTrace.Audio.Annotate(preProcessData)importHarmTrace.Base.MusicRepimportData.List(genericLength,zipWith5,foldl')importText.Printf(printf)importSystem.IO(stdout,hFlush)importData.Foldable(foldrM)importControl.Monad.State(State,execState,modify)-- TODO this is a parameter and should some how be integrated into Constants.hs-- this functions determines when two chords are considered the sameeqFunc::ChordLabel->ChordLabel->BooleqFunc=chordTriadEq---------------------------------------------------------------------------------- Chord and key equality functions---------------------------------------------------------------------------------- | Returns True if both 'ChordLabel's are equal at the chord class level: -- A chord is classified as being major, minor, dominant seventh, or dimished-- seventh. 'chordClassEq' only returns True if the class of compared chords-- is the same. "None Chords" match only with other None Chords and -- with nothing elsechordClassEq::ChordLabel->ChordLabel->BoolchordClassEqab=chordRoota`rootEQ`chordRootb&&toClassTypea==toClassTypeb-- | Returns True if both 'ChordLabel's are equal at the triad level: they are-- either moth major or both minor. "None Chords" match only with other "None-- Chords" and with nothing elsechordTriadEq::ChordLabel->ChordLabel->BoolchordTriadEqab=chordRoota`rootEQ`chordRootb&&toMajMin(toTriada)==toMajMin(toTriadb)-- | Returns True if the 'Root's of the 'Chord's are equal, but the one chord-- is Major and the other chord is Minor.majMinEq::ChordLabel->ChordLabel->BoolmajMinEqab=chordRoota`rootEQ`chordRootb&&toTriada`triadEq`toTriadbwhere-- ingore the NoClass and only return True in case of maj/min and min/majtriadEq::Triad->Triad->BooltriadEqxy=case(toMajMinx,toMajMiny)of(MajClass,MinClass)->True(MinClass,MajClass)->True_->False-- | enharmonic equality for 'Root' 'Note's, N == N, X == X, and G# == AbrootEQ::Root->Root->BoolrootEQ(NoteNothingX)(NoteNothingX)=True-- two unknown rootsrootEQ(NoteNothingN)(NoteNothingN)=True-- two none rootsrootEQ(NoteNothingX)_=False-- one unknown rootrootEQ_(NoteNothingX)=FalserootEQ(NoteNothingN)_=False-- one none rootrootEQ_(NoteNothingN)=FalserootEQab=toSemitonea==toSemitoneb---------------------------------------------------------------------------------- Evaluation functions-------------------------------------------------------------------------------- -- | Calculates the relative correct overlap, which is the recall-- of matching frames, and defined as the nr of matching frames (sampled at-- an 10 milisecond interval) divided by all frames.relCorrectOverlap::(a->a->Bool)->[TimedDataa]->[TimedDataa]->DoublerelCorrectOverlapeqab=foldl'countMatch0(zipWitheqsamasamb)/totwheresama=sampleasamb=samplebtot=max(genericLengthsama)(genericLengthsamb)countMatch::Double->Bool->DoublecountMatchxy|y=succx-- count the number of matching frames|otherwise=x-- | Given a chord annotation sample the chord label at every 10 mssample::[TimedDataa]->[a]sample=sampleWithevaluationSampleRate-- like sample, but takes a sample rate (seconds :: Float) as argumentsampleWith::NumData->[TimedDataa]->[a]sampleWithrate=sampleAt[0.00,rate..]-- samples at specific points in time, specified in a listsampleAt::[NumData]->[TimedDataa]->[a]sampleAt_[]=[]-- below, will never occursampleAt[]_=error"Harmtrace.Audio.Evaluation: No sampling grid specified"sampleAt(t:ts)(c:cs)|t<=offsetc=getDatac:sampleAtts(c:cs)|otherwise=sampleAt(t:ts)cs-- | calculates the maximal achievable score given a ground truth annotation-- and a chord candidate list.achievScore::[TimedDataChordLabel]->[TimedData[ChordLabel]]->DoubleachievScoreab=sum(zipWitheqsamasamb)/lenwheresama=sampleasamb=sampleblen=min(genericLengthsama)(genericLengthsamb)eqccs|foldr(\x->(chordTriadEqcx||))Falsecs=1.0|otherwise=0.0-- | calculates the number of chord changes in the ground-truth divided -- by the number of chord changes in the machine annotation. A number < 1 -- indicates that the machine annotation misses some chord changes. A number-- > 1 indicates that the machine annotation finds to many chord sequences.chordChangeRatio::(ChordLabel->ChordLabel->Bool)->[TimedDataChordLabel]->[TimedDataChordLabel]->DoublechordChangeRatioeqgtma=(fromIntegral.countChordChanges$gt)/(fromIntegral.countChordChanges$ma)wherecountChordChanges::[TimedDataChordLabel]->IntcountChordChangescs=execState(foldrMstep[]$dropTimedcs)0step::ChordLabel->[ChordLabel]->StateInt[ChordLabel]stepc[]=domodifysuccreturn[c]stepa(b:cs)|a`eq`b=return(a:b:cs)|otherwise=domodifysuccreturn(a:b:cs)-- | The 'chordChangeRatio' is optimal if it is one, but it can be larger or -- smaller than 1. Therefore, calculating the average blurs the actual result.-- 'avgDistToOne' takes the absolute difference to 1.0 and averages these for a-- list of Doubles.avgDistToOne::[Double]->DoubleavgDistToOneds=(sum.mapabsDistToOne$ds)/genericLengthdswhereabsDistToOne::Double->DoubleabsDistToOnea=abs(1.0-a)---------------------------------------------------------------------------------- Displaying evaluations (all in IO)-------------------------------------------------------------------------------- -- | does the same thing as relCorrectOverlap, but it also prints the-- chords and uses a lower sample rate. N.B. the number output by -- 'printRelCorrectOverlap' might differ from the output of -- 'relCorrectOverlap', because a different sample rate might be used (see-- 'Constants').printChordRCO::(AudioFeat->ChordAnnotation)->[TimedDataKey]->AudioFeat->[TimedDataChordLabel]->IODoubleprintChordRCOannotatorkeyafgt=dolet-- BUG: now alswo when we are evaluating a simple annotator grouping is -- is displayed, this is wrong. printRelCorrectOverlap should-- be independend of the kind of annotator.blks::[TimedData[ProbChord]]blks=concatMapsegChords$preProcessDataNothingaf-- sample the info for printing and evaluationsamaf=sampleWithdisplaySampleRate(dropProb.annotator$af)samgt=sampleWithdisplaySampleRategtsambk=sampleWithdisplaySampleRateblkssamk=sampleWithdisplaySampleRatekeytot=max(genericLengthsamaf)(genericLengthsamgt)showEqm=ifmthen"=="else"/="printEval::NumData->ChordLabel->ChordLabel->Key->[ProbChord]->IOBoolprintEvaltgabc=doputStrLn(printf"%.2f"t++'\t':showEqequal++'\t':showg++'\t':showa++'\t':showb++'\t':showc)>>hFlushstdoutreturnequalwhereequal=g`eqFunc`aputStrLn"time\tmatch\tGT\t\tMPTREE\tkey\toptional chords"m<-sequence(zipWith5printEval[0.0,displaySampleRate..]samgtsamafsamksambk)return(foldlcountMatch0m/tot)-- | Calculates the relative correct overlap, which is the recall-- of matching frames, and defined as the nr of matching frames (sampled at-- an interval set in 'HarnTrace.Constants' divided by all frames.-- This functions difers from 'relCorrectOverlap' in that it uses an-- equality function that is in IO.printRCO::(a->a->IO(Bool))->[TimedDataa]->[TimedDataa]->IO(Double)printRCOioeqab=domatches<-sequence(zipWith3printEq[0,displaySampleRate..]samasamb)return(foldl'countMatch0matches/tot)wheresama=sampleWithdisplaySampleRateasamb=sampleWithdisplaySampleRatebtot=max(genericLengthsama)(genericLengthsamb)-- printEq :: NumData -> a -> a -> IO (Bool)printEqtsxy=doputStr(printf"%.2f: "ts)ioeqxy